This commit is contained in:
Zerebos 2023-09-19 21:45:47 +00:00 committed by GitHub
commit 23e03613a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 153 additions and 30 deletions

View File

@ -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, "-");

View File

@ -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");
}
};

View File

@ -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 {

View File

@ -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 {

View File

@ -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);

View File

@ -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,

View File

@ -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>;