import Button from '../../common/components/Button'; import Link from '../../common/components/Link'; import LinkButton from '../../common/components/LinkButton'; import Page from '../../common/components/Page'; import Select from '../../common/components/Select'; import Switch from '../../common/components/Switch'; import icon from '../../common/helpers/icon'; import punctuateSeries from '../../common/helpers/punctuateSeries'; import listItems from '../../common/helpers/listItems'; import ItemList from '../../common/utils/ItemList'; import Stream from '../../common/utils/Stream'; import LoadingModal from './LoadingModal'; import ExtensionPermissionGrid from './ExtensionPermissionGrid'; import saveSettings from '../utils/saveSettings'; import isExtensionEnabled from '../utils/isExtensionEnabled'; export default class ExtensionPage extends Page { oninit(vnode) { super.oninit(vnode); this.loading = false; this.extension = app.data.extensions[this.attrs.id]; this.changingState = false; this.settings = {}; this.infoFields = { discuss: 'fas fa-comment-alt', documentation: 'fas fa-book', support: 'fas fa-life-ring', website: 'fas fa-link', donate: 'fas fa-donate', source: 'fas fa-code', }; // Backwards compatibility layer will be removed in // Beta 16 if (app.extensionSettings[this.extension.id]) { app.extensionData[this.extension.id] = app.extensionSettings[this.extension.id]; } } className() { return this.extension.id + '-Page'; } view() { return (
{this.header()} {!this.isEnabled() ? (

{app.translator.trans('core.admin.extension.enable_to_see')}

) : (
{this.sections().toArray()}
)}
); } header() { const isEnabled = this.isEnabled(); return [
{this.extension.icon ? icon(this.extension.icon.name) : ''}

{this.extension.extra['flarum-extension'].title}

    {listItems(this.topItems().toArray())}
{this.extension.description}
{isEnabled ? app.translator.trans('core.admin.extension.enabled') : app.translator.trans('core.admin.extension.disabled')}
, ]; } sections() { const items = new ItemList(); items.add('content', this.content()); items.add('permissions', [

{app.translator.trans('core.admin.extension.permissions_title')}

{app.extensionData.extensionHasPermissions(this.extension.id) ? ( ExtensionPermissionGrid.component({ extensionId: this.extension.id }) ) : (

{app.translator.trans('core.admin.extension.no_permissions')}

)}
, ]); return items; } content() { const settings = app.extensionData.getSettings(this.extension.id); return (
{typeof app.extensionData[this.extension.id] === 'function' ? ( ) : settings ? (
{settings.map(this.buildSettingComponent.bind(this))}
{this.submitButton()}
) : (

{app.translator.trans('core.admin.extension.no_settings')}

)}
); } topItems() { const items = new ItemList(); items.add('version', {this.extension.version}); if (!this.isEnabled()) { const uninstall = () => { if (confirm(app.translator.trans('core.admin.extension.confirm_uninstall'))) { app .request({ url: app.forum.attribute('apiUrl') + '/extensions/' + this.extension.id, method: 'DELETE', }) .then(() => window.location.reload()); app.modal.show(LoadingModal); } }; items.add( 'uninstall', ); } return items; } infoItems() { const items = new ItemList(); const links = this.extension.links; if (links.authors.length) { let authors = []; links.authors.map((author) => { authors.push( {author.name} ); }); items.add('authors', [icon('fas fa-user'), {punctuateSeries(authors)}]); } Object.keys(this.infoFields).map((field) => { if (links[field]) { items.add( field, {app.translator.trans(`core.admin.extension.info_links.${field}`)} ); } }); return items; } submitButton() { return ( ); } /** * getSetting takes a settings object and turns it into a component. * Depending on the type of input, you can set the type to 'bool', 'select', or * any standard type. * * Alternatively, you can pass a callback that will be executed in ExtensionPage's * context to include custom JSX elements. * * @example * * { * setting: 'acme.checkbox', * label: app.translator.trans('acme.admin.setting_label'), * type: 'bool' * } * * @example * * { * setting: 'acme.select', * label: app.translator.trans('acme.admin.setting_label'), * type: 'select', * options: { * 'option1': 'Option 1 label', * 'option2': 'Option 2 label', * }, * default: 'option1', * } * * @param setting * @returns {JSX.Element} */ buildSettingComponent(entry) { if (typeof entry === 'function') { return entry.call(this); } const setting = entry.setting; const value = this.setting([setting])(); if (['bool', 'checkbox', 'switch', 'boolean'].includes(entry.type)) { return (
{entry.label}
); } else if (['select', 'dropdown', 'selectdropdown'].includes(entry.type)) { return (
); } } toggle() { const enabled = this.isEnabled(); this.changingState = true; app .request({ url: app.forum.attribute('apiUrl') + '/extensions/' + this.extension.id, method: 'PATCH', body: { enabled: !enabled }, errorHandler: this.onerror.bind(this), }) .then(() => { if (!enabled) localStorage.setItem('enabledExtension', this.extension.id); window.location.reload(); }); app.modal.show(LoadingModal); } dirty() { const dirty = {}; Object.keys(this.settings).forEach((key) => { const value = this.settings[key](); if (value !== app.data.settings[key]) { dirty[key] = value; } }); return dirty; } isChanged() { return Object.keys(this.dirty()).length; } saveSettings(e) { e.preventDefault(); app.alerts.clear(); this.loading = true; saveSettings(this.dirty()).then(this.onsaved.bind(this)); } onsaved() { this.loading = false; app.alerts.show({ type: 'success' }, app.translator.trans('core.admin.extension.saved_message')); } setting(key, fallback = '') { this.settings[key] = this.settings[key] || Stream(app.data.settings[key] || fallback); return this.settings[key]; } isEnabled() { return isExtensionEnabled(this.extension.id); } onerror(e) { // We need to give the modal animation time to start; if we close the modal too early, // it breaks the bootstrap modal library. // TODO: This workaround should be removed when we move away from bootstrap JS for modals. setTimeout(() => { app.modal.close(); }, 300); // Bootstrap's Modal.TRANSITION_DURATION is 300 ms. this.changingState = false; if (e.status !== 409) { throw e; } const error = e.response.errors[0]; app.alerts.show( { type: 'error' }, app.translator.trans(`core.lib.error.${error.code}_message`, { extension: error.extension, extensions: error.extensions.join(', '), }) ); } }