/* eslint-disable max-lines */ require('./metadataEditor.less'); const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); import request from '../../utils/request-middleware.js'; const Combobox = require('client/components/combobox.jsx'); const TagInput = require('../tagInput/tagInput.jsx'); const Themes = require('themes/themes.json'); const validations = require('./validations.js'); const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder']; const homebreweryThumbnail = require('../../thumbnail.png'); const callIfExists = (val, fn, ...args)=>{ if(val[fn]) { val[fn](...args); } }; const MetadataEditor = createClass({ displayName : 'MetadataEditor', getDefaultProps : function() { return { metadata : { editId : null, shareId : null, title : '', description : '', thumbnail : '', tags : [], published : false, authors : [], systems : [], renderer : 'legacy', theme : '5ePHB', lang : 'en' }, onChange : ()=>{}, reportError : ()=>{} }; }, getInitialState : function(){ return { showThumbnail : true }; }, toggleThumbnailDisplay : function(){ this.setState({ showThumbnail : !this.state.showThumbnail }); }, renderThumbnail : function(){ if(!this.state.showThumbnail) return; return ; }, handleFieldChange : function(name, e){ // load validation rules, and check input value against them const inputRules = validations[name] ?? []; const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean); const debouncedReportValidity = _.debounce((target, errMessage)=>{ callIfExists(target, 'setCustomValidity', errMessage); callIfExists(target, 'reportValidity'); }, 300); // 300ms debounce delay, adjust as needed // if no validation rules, save to props if(validationErr.length === 0){ callIfExists(e.target, 'setCustomValidity', ''); this.props.onChange({ ...this.props.metadata, [name] : e.target.value }); return true; } else { // if validation issues, display built-in browser error popup with each error. const errMessage = validationErr.map((err)=>{ return `- ${err}`; }).join('\n'); debouncedReportValidity(e.target, errMessage); return false; } }, handleSystem : function(system, e){ if(e.target.checked){ this.props.metadata.systems.push(system); } else { this.props.metadata.systems = _.without(this.props.metadata.systems, system); } this.props.onChange(this.props.metadata); }, handleRenderer : function(renderer, e){ if(e.target.checked){ this.props.metadata.renderer = renderer; if(renderer == 'legacy') this.props.metadata.theme = '5ePHB'; } this.props.onChange(this.props.metadata, 'renderer'); }, handlePublish : function(val){ this.props.onChange({ ...this.props.metadata, published : val }); }, handleTheme : function(theme){ this.props.metadata.renderer = theme.renderer; this.props.metadata.theme = theme.path; this.props.onChange(this.props.metadata, 'theme'); }, handleThemeWritein : function(e) { const shareId = e.target.value.split('/').pop(); //Extract just the ID if a URL was pasted in this.props.metadata.theme = shareId; this.props.onChange(this.props.metadata, 'theme'); }, handleLanguage : function(languageCode){ this.props.metadata.lang = languageCode; this.props.onChange(this.props.metadata); }, handleDelete : function(){ if(this.props.metadata.authors && this.props.metadata.authors.length <= 1){ if(!confirm('Are you sure you want to delete this brew? Because you are the only owner of this brew, the document will be deleted permanently.')) return; if(!confirm('Are you REALLY sure? You will not be able to recover the document.')) return; } else { if(!confirm('Are you sure you want to remove this brew from your collection? This will remove you as an editor, but other owners will still be able to access the document.')) return; if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return; } request.delete(`/api/${this.props.metadata.googleId ?? ''}${this.props.metadata.editId}`) .send() .end((err, res)=>{ if(err) { this.props.reportError(err); } else { window.location.href = '/'; } }); }, renderSystems : function(){ return _.map(SYSTEMS, (val)=>{ return ; }); }, renderPublish : function(){ if(this.props.metadata.published){ return ; } else { return ; } }, renderDelete : function(){ if(!this.props.metadata.editId) return; return
; }, renderAuthors : function(){ let text = 'None.'; if(this.props.metadata.authors && this.props.metadata.authors.length){ text = this.props.metadata.authors.join(', '); } return
{text}
; }, renderThemeDropdown : function(){ const mergedThemes = _.merge(Themes, this.props.userThemes); const listThemes = (renderer)=>{ return _.map(_.values(mergedThemes[renderer]), (theme)=>{ if(theme.path == this.props.metadata.shareId) return; const preview = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownPreview.png`; const texture = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownTexture.png`; return
{theme.author ?? renderer} : {theme.name}
{theme.name} preview
; }).filter(Boolean); }; const currentRenderer = this.props.metadata.renderer; const currentThemeDisplay = this.props.themeBundle?.name ? `${this.props.themeBundle.author ?? currentRenderer} : ${this.props.themeBundle.name}` : 'No Theme Selected'; let dropdown; if(currentRenderer == 'legacy') { dropdown =
Themes are not supported in the Legacy Renderer
; } else { dropdown =
this.handleTheme(value)} onEntry={(e)=>{ e.target.setCustomValidity(''); //Clear the validation popup while typing if(this.handleFieldChange('theme', e)) this.handleThemeWritein(e); }} options={listThemes(currentRenderer)} autoSuggest={{ suggestMethod : 'includes', clearAutoSuggestOnClick : true, filterOn : ['value', 'title'] }} /> Select from the list below (built-in themes and brews you have tagged "meta:theme"), or paste in the Share URL or Share ID of any brew.
; } return
{dropdown}
; }, renderLanguageDropdown : function(){ const langCodes = ['en', 'de', 'de-ch', 'fr', 'ja', 'es', 'it', 'sv', 'ru', 'zh-Hans', 'zh-Hant']; const listLanguages = ()=>{ return _.map(langCodes.sort(), (code, index)=>{ const localName = new Intl.DisplayNames([code], { type: 'language' }); const englishName = new Intl.DisplayNames('en', { type: 'language' }); return
{code}
{localName.of(code)}
; }); }; return
this.handleLanguage(value)} onEntry={(e)=>{ e.target.setCustomValidity(''); //Clear the validation popup while typing this.handleFieldChange('lang', e); }} options={listLanguages()} autoSuggest={{ suggestMethod : 'startsWith', clearAutoSuggestOnClick : true, filterOn : ['value', 'detail', 'title'] }} /> Sets the HTML Lang property for your brew. May affect hyphenation or spellcheck.
; }, renderRenderOptions : function(){ return
Click here to see the demo page for the old Legacy renderer!
; }, render : function(){ return

Properties Editor

this.handleFieldChange('title', e)} />