You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
vizality/renderer/src/api/settings/Settings.js

351 lines
9.7 KiB

/**
* The settings API is meant .
* @module Settings
* @memberof API
* @namespace API.Settings
* @version 1.0.0
*/
/**
* @typedef SettingsCategory
* @property {Function} connectStore Connects a component to the settings store
* @property {function(string, string): string} get Gets a setting, or fallbacks to default value
* @property {function(): string[]} getKeys Get all settings key
* @property {function(string): void} delete Deletes a setting
* @property {function(string, string): void} set Sets a setting
*/
/**
* @typedef SettingsTab
* @property {string} category Settings category. Most of the time, you want this to be the entity ID
* @property {string|function(): string} label Settings tab label
* @property {function(): React.ReactNode} render Render method
* @property {undefined} settings Use it and you'll be fined 69 cookies
*/
import { Sidebar, Content, Layout } from '@vizality/components/dashboard';
import { toPlural } from '@vizality/util/string';
import { getCaller } from '@vizality/util/file';
import { Events } from '@vizality/constants';
import { Flux } from '@vizality/webpack';
import { API } from '@vizality/entities';
import React, { useState } from 'react';
import actions from './store/Actions';
import store from './store/Store';
/**
* @extends API
* @extends Events
*/
export default class Settings extends API {
constructor () {
super();
/**
* @property {Flux.Store} store Flux store
*/
this.store = store;
}
/**
* Shuts down the API, removing all listeners and stored objects.
*/
stop () {
this.removeAllListeners();
delete vizality.api.settings;
}
/**
* Builds a settings category that can be used by a plugin.
* @private
* @param {string} category Settings category name
* @returns {SettingsCategory}
*/
_buildCategoryObject (category) {
return {
connectStore: component => this.connectStores(category)(component),
getKeys: () => store.getSettingsKeys(category),
get: (setting, defaultValue) => store.getSetting(category, setting, defaultValue),
toggle: (setting, defaultValue) => {
return actions.toggleSetting(category, setting, defaultValue);
},
set: (setting, newValue) => {
actions.updateSetting(category, setting, newValue);
},
delete: (setting) => {
actions.deleteSetting(category, setting);
}
};
}
/**
* Creates a flux decorator for a given settings category.
* @param {string} category Settings category
* @returns {Function}
*/
connectStores (category) {
return Flux.connectStores([ this.store ], () => this._fluxProps(category));
}
/**
* Registers a settings tab.
* @private
* @param {SettingsTab} props Props of the settings tab
*/
registerSettings (props) {
try {
let { type, addonId, render } = props;
type = type || 'plugin';
render =
render?.__esModule
? render?.default
: render?.type
? render.type
: render;
if (!render) {
throw new Error(`You must specify a render component to register settings for "${addonId}"!`);
}
const addon = vizality.manager[toPlural(type)].get(addonId);
if (!addon) {
throw new Error(`Cannot register settings for "${addonId}" because it isn't installed!`);
}
addon.sections.settings = {
component: render,
render: this.connectStores(addonId)(render)
};
const Render = addon.sections.settings.render;
vizality.api.routes.registerRoute({
id: `${type}/${addonId}`,
path: `/${toPlural(type)}/${addonId}`,
render: props =>
<Layout>
<Content
header='Settings'
vz-plugin={Boolean(type === 'plugin') && addonId}
vz-theme={Boolean(type === 'theme') && addonId}
vz-plugin-section={Boolean(type === 'plugin') && 'settings'}
vz-theme-section={Boolean(type === 'theme') && 'settings'}
>
<Render {...props} />
</Content>
</Layout>,
sidebar: Sidebar
});
this.emit(Events.VIZALITY_SETTINGS_REGISTER, addonId);
} catch (err) {
return this.error(err);
}
}
/**
* Registers a settings tab.
* @param {SettingsTab} settings Props of the settings tab
* @private
*/
// registerSettings (settings) {
// try {
// let { type, addonId, render } = settings;
// type = type || 'plugins';
// render =
// render?.__esModule
// ? render?.default
// : render?.type
// ? render.type
// : render;
// if (!render) {
// throw new Error(`You must specify a render component to register settings for "${addonId}"!`);
// }
// const addon = vizality.manager[type].get(addonId);
// if (!addon) {
// throw new Error(`Cannot register settings for "${addonId}" because it isn't installed!`);
// }
// addon.sections.settings = {
// component: render,
// render: this.connectStores(addonId)(render)
// };
// this.emit(Events.VIZALITY_SETTINGS_REGISTER, addonId);
// } catch (err) {
// return this.error(err);
// }
// }
/**
* Unregisters a settings tab.
* @private
* @param {string} addonId Addon ID of the settings to unregister
* @param {string} type Type of the addon
*/
unregisterSettings (addonId, type) {
try {
const addon = vizality.manager[toPlural(type)].get(addonId);
if (addon?.sections?.settings) {
delete addon.sections.settings;
} else {
throw new Error(`Settings for "${addonId}" are not registered, so they cannot be unregistered!`);
}
vizality.api.routes.unregisterRoute(`/${toPlural(type)}/${addonId}`);
this.emit(Events.VIZALITY_SETTINGS_UNREGISTER, addonId);
} catch (err) {
return this.error(err);
}
}
/**
* @private
* @param {...any} props
*/
_registerBuiltinSection (props) {
try {
const addonId = getCaller()?.id;
const { header, description, icon, render } = props;
const builtin = vizality.manager.builtins.get(addonId);
builtin.sections.settings = props;
builtin.sections.settings.render = this.connectStores(addonId)(render);
const Render = builtin.sections.settings.render;
vizality.api.routes.registerRoute({
id: addonId,
path: `/${addonId}`,
render: props =>
<Layout>
<Content
header={header}
description={description}
icon={icon}
vz-builtin={addonId}
>
<Render {...props} />
</Content>
</Layout>,
sidebar: Sidebar
});
} catch (err) {
return this.error(err);
}
}
/**
* A hook that allows you to easily update settings values using states, automatically
* rerendering your component for you.
* @param {string} settingKey Setting key
* @param {...any} defaultValue Default setting value
* @example
* ```
* import { useSetting } from '@vizality/settings';
*
* export default memo(() => {
* const [ text, setText ] = useSetting('coolText', 'I like pie');
* return (
* {text}
* <Button onClick={() => setText('I like cake')} />
* )
* )};
* ```
*/
useSetting (settingKey, defaultValue, addonId) {
try {
const settings = this._fluxProps(addonId);
/**
* If it doesn't find any settings, just return.
*/
if (!settings) {
return;
}
const [ settingValue, setSettingValue ] = useState(settings.getSetting(settingKey, defaultValue));
/**
* Updates the setting value.
* @param {...any} newValue Updated setting value
* @returns {void}
*/
const setSetting = newValue => {
settings.updateSetting(settingKey, newValue);
setSettingValue(newValue);
};
return [ settingValue, setSetting ];
} catch (err) {
console.log('this-out', this);
return this.error(err);
}
}
/**
*
* @private
* @param {string} [addonId] Addon ID
*/
_fluxProps (addonId) {
/**
* If no addonId is provided, try to use getCaller to determine one.
*/
addonId = addonId || getCaller()?.id;
/**
* If the addonId is vizality, it was most likely returned from getCaller
* and is used in some core area, so we want to use the core Vizality settings.json
* file for its store.
*/
if (addonId === 'vizality') {
addonId = 'settings';
}
return {
/**
*
*/
settings: store.getSettings(addonId),
/**
*
* @param {*} setting
* @param {*} defaultValue
*/
getSetting: (setting, defaultValue) => {
return store.getSetting(addonId, setting, defaultValue);
},
/**
*
* @param {*} setting
* @param {*} value
*/
updateSetting: (setting, value) => {
if (addonId === 'settings') {
this.emit(Events.VIZALITY_SETTING_UPDATE, setting, value);
} else {
this.emit(Events.VIZALITY_ADDON_SETTING_UPDATE, addonId, setting, value);
}
return actions.updateSetting(addonId, setting, value);
},
/**
*
* @param {*} setting
* @param {*} defaultValue
*/
toggleSetting: (setting, defaultValue) => {
if (addonId === 'settings') {
this.emit(Events.VIZALITY_SETTING_TOGGLE, setting, defaultValue);
} else {
this.emit(Events.VIZALITY_ADDON_SETTING_TOGGLE, addonId, setting, defaultValue);
}
return actions.toggleSetting(addonId, setting, defaultValue);
}
};
}
}