Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AdminUX Patch and Admin Page #2593

Merged
merged 15 commits into from Feb 18, 2021
12 changes: 0 additions & 12 deletions js/src/admin/AdminApplication.js
@@ -1,16 +1,12 @@
import HeaderPrimary from './components/HeaderPrimary';
import HeaderSecondary from './components/HeaderSecondary';
import routes from './routes';
import ExtensionPage from './components/ExtensionPage';
import Application from '../common/Application';
import Navigation from '../common/components/Navigation';
import AdminNav from './components/AdminNav';
import ExtensionData from './utils/ExtensionData';

export default class AdminApplication extends Application {
// Deprecated as of beta 15
extensionSettings = {};

extensionData = new ExtensionData();

extensionCategories = {
Expand Down Expand Up @@ -61,14 +57,6 @@ export default class AdminApplication extends Application {
m.mount(document.getElementById('header-primary'), HeaderPrimary);
m.mount(document.getElementById('header-secondary'), HeaderSecondary);
m.mount(document.getElementById('admin-navigation'), AdminNav);

// If an extension has just been enabled, then we will run its settings
// callback.
const enabled = localStorage.getItem('enabledExtension');
if (enabled && this.extensionSettings[enabled] && typeof this.extensionSettings[enabled] === 'function') {
this.extensionSettings[enabled]();
localStorage.removeItem('enabledExtension');
}
}

getRequiredPermissions(permission) {
Expand Down
4 changes: 2 additions & 2 deletions js/src/admin/compat.js
Expand Up @@ -8,6 +8,7 @@ import SettingDropdown from './components/SettingDropdown';
import EditCustomFooterModal from './components/EditCustomFooterModal';
import SessionDropdown from './components/SessionDropdown';
import HeaderPrimary from './components/HeaderPrimary';
import AdminPage from './components/AdminPage';
import AppearancePage from './components/AppearancePage';
import StatusWidget from './components/StatusWidget';
import ExtensionsWidget from './components/ExtensionsWidget';
Expand All @@ -16,7 +17,6 @@ import SettingsModal from './components/SettingsModal';
import DashboardWidget from './components/DashboardWidget';
import ExtensionPage from './components/ExtensionPage';
import ExtensionLinkButton from './components/ExtensionLinkButton';
import AdminLinkButton from './components/AdminLinkButton';
import PermissionGrid from './components/PermissionGrid';
import ExtensionPermissionGrid from './components/ExtensionPermissionGrid';
import MailPage from './components/MailPage';
Expand All @@ -43,6 +43,7 @@ export default Object.assign(compat, {
'components/EditCustomFooterModal': EditCustomFooterModal,
'components/SessionDropdown': SessionDropdown,
'components/HeaderPrimary': HeaderPrimary,
'components/AdminPage': AdminPage,
'components/AppearancePage': AppearancePage,
'components/StatusWidget': StatusWidget,
'components/ExtensionsWidget': ExtensionsWidget,
Expand All @@ -51,7 +52,6 @@ export default Object.assign(compat, {
'components/DashboardWidget': DashboardWidget,
'components/ExtensionPage': ExtensionPage,
'components/ExtensionLinkButton': ExtensionLinkButton,
'components/AdminLinkButton': AdminLinkButton,
'components/PermissionGrid': PermissionGrid,
'components/ExtensionPermissionGrid': ExtensionPermissionGrid,
'components/MailPage': MailPage,
Expand Down
32 changes: 0 additions & 32 deletions js/src/admin/components/AddExtensionModal.js

This file was deleted.

16 changes: 0 additions & 16 deletions js/src/admin/components/AdminLinkButton.js

This file was deleted.

174 changes: 174 additions & 0 deletions js/src/admin/components/AdminPage.js
@@ -0,0 +1,174 @@
import Page from '../../common/components/Page';
import Button from '../../common/components/Button';
import Switch from '../../common/components/Switch';
import Select from '../../common/components/Select';
import classList from '../../common/utils/classList';
import Stream from '../../common/utils/Stream';
import saveSettings from '../utils/saveSettings';
import AdminHeader from './AdminHeader';

export default class AdminPage extends Page {
oninit(vnode) {
super.oninit(vnode);

this.settings = {};

this.loading = false;
}

view() {
const className = classList(['AdminPage', this.headerInfo().className]);

return (
<div className={className}>
{this.header()}
<div className="container">{this.content()}</div>
</div>
);
}

content() {
return '';
}

submitButton() {
return (
<Button onclick={this.saveSettings.bind(this)} className="Button Button--primary" loading={this.loading} disabled={!this.isChanged()}>
{app.translator.trans('core.admin.settings.submit_button')}
</Button>
);
}

header() {
const headerInfo = this.headerInfo();

return (
<AdminHeader icon={headerInfo.icon} description={headerInfo.description} className={headerInfo.className + '-header'}>
{headerInfo.title}
</AdminHeader>
);
}

headerInfo() {
return {
className: '',
icon: '',
title: '',
description: '',
};
}

/**
* buildSettingComponent 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 <input> type. Any values inside the 'extra' object will be added
* to the component as an attribute.
*
* 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',
* help: app.translator.trans('acme.admin.setting_help'),
* className: 'Setting-item'
* }
*
* @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 help = entry.help;
delete entry.help;

const value = this.setting([setting])();
if (['bool', 'checkbox', 'switch', 'boolean'].includes(entry.type)) {
return (
<div className="Form-group">
<Switch state={!!value && value !== '0'} onchange={this.settings[setting]} {...entry}>
{entry.label}
</Switch>
<div className="helpText">{help}</div>
</div>
);
} else if (['select', 'dropdown', 'selectdropdown'].includes(entry.type)) {
return (
<div className="Form-group">
<label>{entry.label}</label>
<div className="helpText">{help}</div>
<Select value={value || entry.default} options={entry.options} buttonClassName="Button" onchange={this.settings[setting]} {...entry} />
</div>
);
} else {
entry.className = classList(['FormControl', entry.className]);
return (
<div className="Form-group">
{entry.label ? <label>{entry.label}</label> : ''}
<div className="helpText">{help}</div>
<input type={entry.type} bidi={this.setting(setting)} {...entry} />
</div>
);
}
}

onsaved() {
this.loading = false;

app.alerts.show({ type: 'success' }, app.translator.trans('core.admin.settings.saved_message'));
}

setting(key, fallback = '') {
this.settings[key] = this.settings[key] || Stream(app.data.settings[key] || fallback);

return this.settings[key];
}

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;

return saveSettings(this.dirty()).then(this.onsaved.bind(this));
}
}