diff --git a/framework/core/js/src/admin/components/AdminPage.tsx b/framework/core/js/src/admin/components/AdminPage.tsx index d3b1c9861d..a705c6b047 100644 --- a/framework/core/js/src/admin/components/AdminPage.tsx +++ b/framework/core/js/src/admin/components/AdminPage.tsx @@ -12,6 +12,8 @@ import AdminHeader from './AdminHeader'; import generateElementId from '../utils/generateElementId'; import ColorPreviewInput from '../../common/components/ColorPreviewInput'; import ItemList from '../../common/utils/ItemList'; +import type { IUploadImageButtonAttrs } from './UploadImageButton'; +import UploadImageButton from './UploadImageButton'; export interface AdminHeaderOptions { title: Mithril.Children; @@ -79,6 +81,7 @@ const BooleanSettingTypes = ['bool', 'checkbox', 'switch', 'boolean'] as const; const SelectSettingTypes = ['select', 'dropdown', 'selectdropdown'] as const; const TextareaSettingTypes = ['textarea'] as const; const ColorPreviewSettingType = 'color-preview' as const; +const ImageUploadSettingType = 'image-upload' as const; /** * Valid options for the setting component builder to generate a Switch. @@ -113,6 +116,10 @@ export interface ColorPreviewSettingComponentOptions extends CommonSettingsItemO type: typeof ColorPreviewSettingType; } +export interface ImageUploadSettingComponentOptions extends CommonSettingsItemOptions, IUploadImageButtonAttrs { + type: typeof ImageUploadSettingType; +} + export interface CustomSettingComponentOptions extends CommonSettingsItemOptions { type: string; [key: string]: unknown; @@ -127,6 +134,7 @@ export type SettingsComponentOptions = | SelectSettingComponentOptions | TextareaSettingComponentOptions | ColorPreviewSettingComponentOptions + | ImageUploadSettingComponentOptions | CustomSettingComponentOptions; /** @@ -311,6 +319,10 @@ export default abstract class AdminPage ); + } else if (type === ImageUploadSettingType) { + const { value, ...otherAttrs } = componentAttrs; + + settingElement = ; } else if (customSettingComponents.has(type)) { return customSettingComponents.get(type)({ setting, help, label, ...componentAttrs }); } else { diff --git a/framework/core/js/src/admin/components/AppearancePage.tsx b/framework/core/js/src/admin/components/AppearancePage.tsx index f5f44cba73..54c2c3a60a 100644 --- a/framework/core/js/src/admin/components/AppearancePage.tsx +++ b/framework/core/js/src/admin/components/AppearancePage.tsx @@ -37,13 +37,13 @@ export default class AppearancePage extends AdminPage {
{app.translator.trans('core.admin.appearance.logo_text')}
- +
{app.translator.trans('core.admin.appearance.favicon_text')}
- +
diff --git a/framework/core/js/src/admin/components/UploadImageButton.js b/framework/core/js/src/admin/components/UploadImageButton.js deleted file mode 100644 index 1a7a1a7210..0000000000 --- a/framework/core/js/src/admin/components/UploadImageButton.js +++ /dev/null @@ -1,99 +0,0 @@ -import app from '../../admin/app'; -import Button from '../../common/components/Button'; -import classList from '../../common/utils/classList'; - -export default class UploadImageButton extends Button { - loading = false; - - view(vnode) { - this.attrs.loading = this.loading; - this.attrs.className = classList(this.attrs.className, 'Button'); - - if (app.data.settings[this.attrs.name + '_path']) { - this.attrs.onclick = this.remove.bind(this); - - return ( -
-

- -

-

{super.view({ ...vnode, children: app.translator.trans('core.admin.upload_image.remove_button') })}

-
- ); - } else { - this.attrs.onclick = this.upload.bind(this); - } - - return super.view({ ...vnode, children: app.translator.trans('core.admin.upload_image.upload_button') }); - } - - /** - * Prompt the user to upload an image. - */ - upload() { - if (this.loading) return; - - const $input = $(''); - - $input - .appendTo('body') - .hide() - .trigger('click') - .on('change', (e) => { - const body = new FormData(); - body.append(this.attrs.name, $(e.target)[0].files[0]); - - this.loading = true; - m.redraw(); - - app - .request({ - method: 'POST', - url: this.resourceUrl(), - serialize: (raw) => raw, - body, - }) - .then(this.success.bind(this), this.failure.bind(this)); - }); - } - - /** - * Remove the logo. - */ - remove() { - this.loading = true; - m.redraw(); - - app - .request({ - method: 'DELETE', - url: this.resourceUrl(), - }) - .then(this.success.bind(this), this.failure.bind(this)); - } - - resourceUrl() { - return app.forum.attribute('apiUrl') + '/' + this.attrs.name; - } - - /** - * After a successful upload/removal, reload the page. - * - * @param {object} response - * @protected - */ - success(response) { - window.location.reload(); - } - - /** - * If upload/removal fails, stop loading. - * - * @param {object} response - * @protected - */ - failure(response) { - this.loading = false; - m.redraw(); - } -} diff --git a/framework/core/js/src/admin/components/UploadImageButton.tsx b/framework/core/js/src/admin/components/UploadImageButton.tsx new file mode 100644 index 0000000000..f1e4ab452e --- /dev/null +++ b/framework/core/js/src/admin/components/UploadImageButton.tsx @@ -0,0 +1,110 @@ +import app from '../../admin/app'; +import Button from '../../common/components/Button'; +import type { IButtonAttrs } from '../../common/components/Button'; +import classList from '../../common/utils/classList'; +import type Mithril from 'mithril'; +import Component from '../../common/Component'; + +export interface IUploadImageButtonAttrs extends IButtonAttrs { + name: string; + routePath: string; + value?: string | null | (() => string | null); + url?: string | null | (() => string | null); +} + +export default class UploadImageButton extends Component { + loading = false; + + view(vnode: Mithril.Vnode) { + let { name, value, url, ...attrs } = vnode.attrs as IButtonAttrs; + + attrs.loading = this.loading; + attrs.className = classList(attrs.className, 'Button'); + + if (typeof value === 'function') { + value = value(); + } + + if (typeof url === 'function') { + url = url(); + } + + return ( +
+ {value ? ( + <> +
+ {name} +
+ + + ) : ( + + )} +
+ ); + } + + upload() { + if (this.loading) return; + + const $input = $(''); + + $input + .appendTo('body') + .hide() + .trigger('click') + .on('change', (e) => { + const body = new FormData(); + // @ts-ignore + body.append(this.attrs.name, $(e.target)[0].files[0]); + + this.loading = true; + m.redraw(); + + app + .request({ + method: 'POST', + url: this.resourceUrl(), + serialize: (raw) => raw, + body, + }) + .then(this.success.bind(this), this.failure.bind(this)); + }); + } + + remove() { + this.loading = true; + m.redraw(); + + app + .request({ + method: 'DELETE', + url: this.resourceUrl(), + }) + .then(this.success.bind(this), this.failure.bind(this)); + } + + resourceUrl() { + return app.forum.attribute('apiUrl') + '/' + this.attrs.routePath; + } + + /** + * After a successful upload/removal, reload the page. + */ + protected success(response: any) { + window.location.reload(); + } + + /** + * If upload/removal fails, stop loading. + */ + protected failure(response: any) { + this.loading = false; + m.redraw(); + } +}