Merge 8ce32a7846
into e5dc449130
This commit is contained in:
commit
23e03613a2
|
@ -130,29 +130,23 @@ export default class AddonManager {
|
|||
|
||||
extractMeta(fileContent, filename) {
|
||||
const firstLine = fileContent.split("\n")[0];
|
||||
const hasOldMeta = firstLine.includes("//META") && firstLine.includes("*//");
|
||||
if (hasOldMeta) return this.parseOldMeta(fileContent, filename);
|
||||
const hasNewMeta = firstLine.includes("/**");
|
||||
if (hasNewMeta) return this.parseNewMeta(fileContent);
|
||||
throw new AddonError(filename, filename, Strings.Addons.metaNotFound, {message: "", stack: fileContent}, this.prefix);
|
||||
const hasMetaComment = firstLine.includes("/**");
|
||||
if (!hasMetaComment) throw new AddonError(filename, filename, Strings.Addons.metaNotFound, {message: "", stack: fileContent}, this.prefix);
|
||||
const metaInfo = this.parseJSDoc(fileContent);
|
||||
|
||||
/**
|
||||
* Okay we have a meta JSDoc, let's validate it
|
||||
* and do some extra parsing for advanced options
|
||||
*/
|
||||
|
||||
if (!metaInfo.author || typeof(metaInfo.author) !== "string") metaInfo.author = Strings.Addons.unknownAuthor;
|
||||
if (!metaInfo.version || typeof(metaInfo.version) !== "string") metaInfo.version = "???";
|
||||
if (!metaInfo.description || typeof(metaInfo.description) !== "string") metaInfo.description = Strings.Addons.noDescription;
|
||||
|
||||
return metaInfo;
|
||||
}
|
||||
|
||||
parseOldMeta(fileContent, filename) {
|
||||
const meta = fileContent.split("\n")[0];
|
||||
const metaData = meta.substring(meta.lastIndexOf("//META") + 6, meta.lastIndexOf("*//"));
|
||||
let parsed = null;
|
||||
try {
|
||||
parsed = JSON.parse(metaData);
|
||||
}
|
||||
catch (err) {
|
||||
throw new AddonError(filename, filename, Strings.Addons.metaError, err, this.prefix);
|
||||
}
|
||||
if (!parsed || !parsed.name) throw new AddonError(filename, filename, Strings.Addons.missingNameData, {message: "", stack: meta}, this.prefix);
|
||||
parsed.format = "json";
|
||||
return parsed;
|
||||
}
|
||||
|
||||
parseNewMeta(fileContent) {
|
||||
parseJSDoc(fileContent) {
|
||||
const block = fileContent.split("/**", 2)[1].split("*/", 1)[0];
|
||||
const out = {};
|
||||
let field = "";
|
||||
|
@ -160,8 +154,15 @@ export default class AddonManager {
|
|||
for (const line of block.split(splitRegex)) {
|
||||
if (line.length === 0) continue;
|
||||
if (line.charAt(0) === "@" && line.charAt(1) !== " ") {
|
||||
out[field] = accum.trim();
|
||||
const l = line.indexOf(" ");
|
||||
if (!out[field]) {
|
||||
out[field] = accum.trim();
|
||||
}
|
||||
else {
|
||||
if (!Array.isArray(out[field])) out[field] = [out[field]];
|
||||
out[field].push(accum.trim());
|
||||
}
|
||||
let l = line.indexOf(" ");
|
||||
if (l < 0) l = line.length;
|
||||
field = line.substring(1, l);
|
||||
accum = line.substring(l + 1);
|
||||
}
|
||||
|
@ -169,7 +170,13 @@ export default class AddonManager {
|
|||
accum += " " + line.replace("\\n", "\n").replace(escapedAtRegex, "@");
|
||||
}
|
||||
}
|
||||
out[field] = accum.trim();
|
||||
if (!out[field]) {
|
||||
out[field] = accum.trim();
|
||||
}
|
||||
else {
|
||||
if (!Array.isArray(out[field])) out[field] = [out[field]];
|
||||
out[field].push(accum.trim());
|
||||
}
|
||||
delete out[""];
|
||||
out.format = "jsdoc";
|
||||
return out;
|
||||
|
@ -181,9 +188,7 @@ export default class AddonManager {
|
|||
fileContent = stripBOM(fileContent);
|
||||
const stats = fs.statSync(filename);
|
||||
const addon = this.extractMeta(fileContent, path.basename(filename));
|
||||
if (!addon.author) addon.author = Strings.Addons.unknownAuthor;
|
||||
if (!addon.version) addon.version = "???";
|
||||
if (!addon.description) addon.description = Strings.Addons.noDescription;
|
||||
|
||||
// if (!addon.name || !addon.author || !addon.description || !addon.version) return new AddonError(addon.name || path.basename(filename), filename, "Addon is missing name, author, description, or version", {message: "Addon must provide name, author, description, and version.", stack: ""}, this.prefix);
|
||||
addon.id = addon.name || path.basename(filename);
|
||||
addon.slug = path.basename(filename).replace(this.extension, "").replace(/ /g, "-");
|
||||
|
|
|
@ -8,12 +8,16 @@ import AddonManager from "./addonmanager";
|
|||
import Settings from "./settingsmanager";
|
||||
import DOMManager from "./dommanager";
|
||||
import Strings from "./strings";
|
||||
import DataStore from "./datastore";
|
||||
import Utilities from "./utilities";
|
||||
|
||||
import Toasts from "@ui/toasts";
|
||||
import Modals from "@ui/modals";
|
||||
import SettingsRenderer from "@ui/settings";
|
||||
|
||||
|
||||
const varRegex = /^(checkbox|text|color|select|number|range)\s+([A-Za-z0-9-_]+)\s+"([^"]+)"\s+(.*)$/;
|
||||
|
||||
export default new class ThemeManager extends AddonManager {
|
||||
get name() {return "ThemeManager";}
|
||||
get extension() {return ".theme.css";}
|
||||
|
@ -64,10 +68,57 @@ export default new class ThemeManager extends AddonManager {
|
|||
if (!addon.name || !addon.author || !addon.description || !addon.version) return new AddonError(addon.name || addon.filename, addon.filename, "Addon is missing name, author, description, or version", {message: "Addon must provide name, author, description, and version.", stack: ""}, this.prefix);
|
||||
}
|
||||
|
||||
extractMeta(fileContent, filename) {
|
||||
const metaInfo = super.extractMeta(fileContent, filename);
|
||||
if (!metaInfo.var) return metaInfo;
|
||||
|
||||
if (!Array.isArray(metaInfo.var)) metaInfo.var = [metaInfo.var];
|
||||
|
||||
const variables = [];
|
||||
for (const v of metaInfo.var) {
|
||||
const match = v.match(varRegex);
|
||||
if (!match || match.length !== 5) continue;
|
||||
const type = match[1];
|
||||
const variable = match[2];
|
||||
const label = match[3].split(":");
|
||||
const name = label[0].trim();
|
||||
const note = label[1]?.trim();
|
||||
const value = match[4];
|
||||
if (type === "checkbox") variables.push({type: "switch", id: variable, name: name, note: note, value: parseInt(value) === 1});
|
||||
if (type === "text") variables.push({type: "text", id: variable, name: name, note: note, value: value});
|
||||
if (type === "color") variables.push({type: "color", id: variable, name: name, note: note, value: value, defaultValue: value});
|
||||
|
||||
if (type === "number" || type === "range") {
|
||||
// [default, min, max, step, units]
|
||||
const parsed = JSON.parse(value);
|
||||
variables.push({type: type === "number" ? type : "slider", id: variable, name: name, note: note, value: parsed[0], min: parsed[1], max: parsed[2], step: parsed[3]});
|
||||
}
|
||||
if (type === "select") {
|
||||
const parsed = JSON.parse(value);
|
||||
let selected, options;
|
||||
if (Array.isArray(parsed)) {
|
||||
selected = parsed.find(o => o.endsWith("*")).replace("*", "");
|
||||
options = parsed.map(o => ({label: o.replace("*", ""), value: o.replace("*", "")}));
|
||||
}
|
||||
else {
|
||||
selected = Object.keys(parsed).find(k => k.endsWith("*"));
|
||||
selected = parsed[selected];
|
||||
options = Object.entries(parsed).map(a => ({label: a[0].replace("*", ""), value: a[1]}));
|
||||
}
|
||||
variables.push({type: "dropdown", id: variable, name: name, note: note, options: options, value: selected || options[0].value});
|
||||
}
|
||||
}
|
||||
metaInfo.var = variables;
|
||||
metaInfo.instance = {getSettingsPanel: this.getThemeSettingsPanel(metaInfo.name, metaInfo.var)};
|
||||
|
||||
return metaInfo;
|
||||
}
|
||||
|
||||
requireAddon(filename) {
|
||||
const addon = super.requireAddon(filename);
|
||||
addon.css = addon.fileContent;
|
||||
delete addon.fileContent;
|
||||
this.loadThemeSettings(addon);
|
||||
if (addon.format == "json") addon.css = addon.css.split("\n").slice(1).join("\n");
|
||||
return addon;
|
||||
}
|
||||
|
@ -79,6 +130,7 @@ export default new class ThemeManager extends AddonManager {
|
|||
const addon = typeof(idOrAddon) == "string" ? this.addonList.find(p => p.id == idOrAddon) : idOrAddon;
|
||||
if (!addon) return;
|
||||
DOMManager.injectTheme(addon.slug + "-theme-container", addon.css);
|
||||
DOMManager.injectTheme(addon.slug + "-theme-settings", this.buildCSSVars(addon));
|
||||
Toasts.show(Strings.Addons.enabled.format({name: addon.name, version: addon.version}));
|
||||
}
|
||||
|
||||
|
@ -86,6 +138,52 @@ export default new class ThemeManager extends AddonManager {
|
|||
const addon = typeof(idOrAddon) == "string" ? this.addonList.find(p => p.id == idOrAddon) : idOrAddon;
|
||||
if (!addon) return;
|
||||
DOMManager.removeTheme(addon.slug + "-theme-container");
|
||||
DOMManager.removeTheme(addon.slug + "-theme-settings");
|
||||
Toasts.show(Strings.Addons.disabled.format({name: addon.name, version: addon.version}));
|
||||
}
|
||||
|
||||
getThemeSettingsPanel(themeId, vars) {
|
||||
return SettingsRenderer.getSettingsGroup(vars, Utilities.debounce((id, value) => this.updateThemeSettings(themeId, id, value), 100));
|
||||
}
|
||||
|
||||
loadThemeSettings(addon) {
|
||||
const all = DataStore.getData("theme_settings") || {};
|
||||
const stored = all?.[addon.id];
|
||||
if (!stored || !addon.var || !Array.isArray(addon.var)) return;
|
||||
for (const v of addon.var) {
|
||||
if (v.id in stored) v.value = stored[v.id];
|
||||
}
|
||||
}
|
||||
|
||||
updateThemeSettings(themeId, id, value) {
|
||||
const addon = this.addonList.find(p => p.id == themeId);
|
||||
const varToUpdate = addon.var.find(v => v.id === id);
|
||||
varToUpdate.value = value;
|
||||
DOMManager.injectTheme(addon.slug + "-theme-settings", this.buildCSSVars(addon));
|
||||
this.saveThemeSettings(themeId);
|
||||
}
|
||||
|
||||
saveThemeSettings(themeId) {
|
||||
const all = DataStore.getData("theme_settings") || {};
|
||||
const addon = this.addonList.find(p => p.id == themeId);
|
||||
const data = {};
|
||||
for (const v of addon.var) {
|
||||
data[v.id] = v.value;
|
||||
}
|
||||
all[themeId] = data;
|
||||
DataStore.setData("theme_settings", all);
|
||||
}
|
||||
|
||||
buildCSSVars(idOrAddon) {
|
||||
const addon = typeof(idOrAddon) == "string" ? this.addonList.find(p => p.id == idOrAddon) : idOrAddon;
|
||||
const lines = [`:root {`];
|
||||
if (Array.isArray(addon.var)) {
|
||||
for (const v of addon.var) {
|
||||
const value = typeof(v.value) === "boolean" ? v.value ? 1 : 0 : v.value;
|
||||
lines.push(` --${v.id}: ${value};`);
|
||||
}
|
||||
}
|
||||
lines.push(`}`);
|
||||
return lines.join("\n");
|
||||
}
|
||||
};
|
|
@ -50,7 +50,7 @@
|
|||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
margin-left: 5px !important;
|
||||
max-width: 340px;
|
||||
max-width: 310px;
|
||||
}
|
||||
|
||||
.bd-color-picker-swatch-item {
|
||||
|
|
|
@ -85,6 +85,12 @@
|
|||
max-height: 100%;
|
||||
}
|
||||
|
||||
.bd-modal-root > div[role="dialog"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bd-close-button {
|
||||
height: 26px;
|
||||
padding: 4px;
|
||||
|
@ -118,7 +124,7 @@
|
|||
.bd-modal-medium {
|
||||
width: 600px;
|
||||
max-height: 800px;
|
||||
min-height: 400px;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.bd-modal-large {
|
||||
|
|
|
@ -19,6 +19,9 @@ export const Styles = Object.freeze({
|
|||
});
|
||||
|
||||
|
||||
const FocusLock = WebpackModules.getModule(m => m?.render?.toString().includes("impressionProperties") && m?.render?.toString().includes(".Provider"), {searchExports: true}) ?? React.Fragment;
|
||||
|
||||
|
||||
export default function ModalRoot({className, transitionState, children, size = Sizes.DYNAMIC, style = Styles.CUSTOM}) {
|
||||
const visible = transitionState == 0 || transitionState == 1; // 300 ms
|
||||
// const visible = transitionState;
|
||||
|
@ -36,7 +39,9 @@ export default function ModalRoot({className, transitionState, children, size =
|
|||
className={Utilities.className("bd-modal-root", size, className, style)}
|
||||
style={springStyles}
|
||||
>
|
||||
<FocusLock disableTrack={true}>
|
||||
{children}
|
||||
</FocusLock>
|
||||
</Spring.animated.div>;
|
||||
// const [visible, setVisible] = React.useState(true);
|
||||
|
||||
|
|
|
@ -58,6 +58,15 @@ export default new class SettingsRenderer {
|
|||
})];
|
||||
}
|
||||
|
||||
getSettingsGroup(settings, onChange) {
|
||||
return () => React.createElement(SettingsGroup, {
|
||||
onChange: onChange,
|
||||
shown: true,
|
||||
collapsible: false,
|
||||
settings: settings
|
||||
});
|
||||
}
|
||||
|
||||
getAddonPanel(title, addonList, addonState, options = {}) {
|
||||
return () => React.createElement(AddonList, Object.assign({}, {
|
||||
title: title,
|
||||
|
|
|
@ -7,7 +7,7 @@ export default function SettingItem({id, name, note, inline, children}) {
|
|||
<label htmlFor={id} className={"bd-setting-title"}>{name}</label>
|
||||
{inline && children}
|
||||
</div>
|
||||
<div className={"bd-setting-note"}>{note}</div>
|
||||
{note && <div className={"bd-setting-note"}>{note}</div>}
|
||||
{!inline && children}
|
||||
<div className={"bd-setting-divider"} />
|
||||
</div>;
|
||||
|
|
Loading…
Reference in New Issue