manager progress

This commit is contained in:
Zack Rauen 2019-06-07 16:27:44 -04:00
parent 1b84d577db
commit 0ccf84f803
9 changed files with 1111 additions and 62 deletions

View File

@ -2334,8 +2334,8 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _localstorage__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./localstorage */ "./src/localstorage.js");
/* harmony import */ var _modules_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./modules/core */ "./src/modules/core.js");
/* harmony import */ var _modules_pluginapi__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./modules/pluginapi */ "./src/modules/pluginapi.js");
/* harmony import */ var _modules_pluginmanager__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./modules/pluginmanager */ "./src/modules/pluginmanager.js");
/* harmony import */ var _modules_thememanager__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./modules/thememanager */ "./src/modules/thememanager.js");
/* harmony import */ var _modules_pluginmanager2__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./modules/pluginmanager2 */ "./src/modules/pluginmanager2.js");
/* harmony import */ var _modules_thememanager2__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./modules/thememanager2 */ "./src/modules/thememanager2.js");
/* harmony import */ var _modules_oldstorage__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./modules/oldstorage */ "./src/modules/oldstorage.js");
/* harmony import */ var _modules_emitter__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./modules/emitter */ "./src/modules/emitter.js");
@ -2358,8 +2358,8 @@ window.settings = data__WEBPACK_IMPORTED_MODULE_0__["SettingsInfo"];
window.settingsCookie = data__WEBPACK_IMPORTED_MODULE_0__["SettingsCookie"];
window.pluginCookie = data__WEBPACK_IMPORTED_MODULE_0__["PluginCookie"];
window.themeCookie = data__WEBPACK_IMPORTED_MODULE_0__["ThemeCookie"];
window.pluginModule = _modules_pluginmanager__WEBPACK_IMPORTED_MODULE_4__["default"];
window.themeModule = _modules_thememanager__WEBPACK_IMPORTED_MODULE_5__["default"];
window.pluginModule = _modules_pluginmanager2__WEBPACK_IMPORTED_MODULE_4__["default"];
window.themeModule = _modules_thememanager2__WEBPACK_IMPORTED_MODULE_5__["default"];
window.bdthemes = data__WEBPACK_IMPORTED_MODULE_0__["Themes"];
window.bdplugins = data__WEBPACK_IMPORTED_MODULE_0__["Plugins"];
window.bdEmotes = data__WEBPACK_IMPORTED_MODULE_0__["Emotes"];
@ -2915,6 +2915,283 @@ const originalCSSRequire = Module._extensions[".css"] ? Module._extensions[".css
/***/ }),
/***/ "./src/modules/contentmanager2.js":
/*!****************************************!*\
!*** ./src/modules/contentmanager2.js ***!
\****************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return ContentManager; });
/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js");
/* harmony import */ var _settingsmanager__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./settingsmanager */ "./src/modules/settingsmanager.js");
/* harmony import */ var _emitter__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./emitter */ "./src/modules/emitter.js");
/* harmony import */ var _datastore__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./datastore */ "./src/modules/datastore.js");
/* harmony import */ var _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../structs/contenterror */ "./src/structs/contenterror.js");
/* harmony import */ var _structs_metaerror__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../structs/metaerror */ "./src/structs/metaerror.js");
const path = __webpack_require__(/*! path */ "path");
const fs = __webpack_require__(/*! fs */ "fs");
const Module = __webpack_require__(/*! module */ "module").Module;
Module.globalPaths.push(path.resolve(__webpack_require__(/*! electron */ "electron").remote.app.getAppPath(), "node_modules"));
const splitRegex = /[^\S\r\n]*?\n[^\S\r\n]*?\*[^\S\r\n]?/;
const escapedAtRegex = /^\\@/;
class ContentManager {
get name() {
return "";
}
get moduleExtension() {
return "";
}
get extension() {
return "";
}
get contentFolder() {
return "";
}
get prefix() {
return "content";
}
get collection() {
return "settings";
}
get category() {
return "content";
}
get id() {
return "autoReload";
}
emit(event, ...args) {
return _emitter__WEBPACK_IMPORTED_MODULE_2__["default"].emit(`${this.prefix}-${event}`, ...args);
}
constructor() {
this.timeCache = {};
this.contentList = [];
this.state = {};
this.originalRequire = Module._extensions[this.moduleExtension];
Module._extensions[this.moduleExtension] = this.getContentRequire();
_settingsmanager__WEBPACK_IMPORTED_MODULE_1__["default"].on(this.collection, this.category, this.id, enabled => {
if (enabled) this.watchContent();else this.unwatchContent();
});
}
loadState() {
const saved = _datastore__WEBPACK_IMPORTED_MODULE_3__["default"].getData(`${this.prefix}s`);
if (!saved) return;
Object.assign(this.state, saved);
}
saveState() {
_datastore__WEBPACK_IMPORTED_MODULE_3__["default"].setData(`${this.prefix}s`, this.state);
}
watchContent() {
if (this.watcher) return _utilities__WEBPACK_IMPORTED_MODULE_0__["default"].err(this.name, "Already watching content.");
_utilities__WEBPACK_IMPORTED_MODULE_0__["default"].log(this.name, "Starting to watch content.");
this.watcher = fs.watch(this.contentFolder, {
persistent: false
}, async (eventType, filename) => {
if (!eventType || !filename || !filename.endsWith(this.extension)) return;
await new Promise(r => setTimeout(r, 50));
try {
fs.statSync(path.resolve(this.contentFolder, filename));
} catch (err) {
if (err.code !== "ENOENT") return;
delete this.timeCache[filename];
this.unloadContent(filename, true);
}
if (!fs.statSync(path.resolve(this.contentFolder, filename)).isFile()) return;
const stats = fs.statSync(path.resolve(this.contentFolder, filename));
if (!stats || !stats.mtime || !stats.mtime.getTime()) return;
if (typeof stats.mtime.getTime() !== "number") return;
if (this.timeCache[filename] == stats.mtime.getTime()) return;
this.timeCache[filename] = stats.mtime.getTime();
if (eventType == "rename") this.loadContent(filename, true);
if (eventType == "change") this.reloadContent(filename, true);
});
}
unwatchContent() {
if (!this.watcher) return _utilities__WEBPACK_IMPORTED_MODULE_0__["default"].err(this.name, "Was not watching content.");
this.watcher.close();
delete this.watcher;
_utilities__WEBPACK_IMPORTED_MODULE_0__["default"].log(this.name, "No longer watching content.");
}
extractMeta(content) {
const firstLine = content.split("\n")[0];
const hasOldMeta = firstLine.includes("//META");
if (hasOldMeta) return this.parseOldMeta(content);
const hasNewMeta = firstLine.includes("/**");
if (hasNewMeta) return this.parseNewMeta(content);
throw new _structs_metaerror__WEBPACK_IMPORTED_MODULE_5__["default"]("META was not found.");
}
parseOldMeta(content) {
const meta = content.split("\n")[0];
const rawMeta = meta.substring(meta.lastIndexOf("//META") + 6, meta.lastIndexOf("*//"));
if (meta.indexOf("META") < 0) throw new _structs_metaerror__WEBPACK_IMPORTED_MODULE_5__["default"]("META was not found.");
if (!_utilities__WEBPACK_IMPORTED_MODULE_0__["default"].testJSON(rawMeta)) throw new _structs_metaerror__WEBPACK_IMPORTED_MODULE_5__["default"]("META could not be parsed.");
const parsed = JSON.parse(rawMeta);
if (!parsed.name) throw new _structs_metaerror__WEBPACK_IMPORTED_MODULE_5__["default"]("META missing name data.");
return parsed;
}
parseNewMeta(content) {
const block = content.split("/**", 2)[1].split("*/", 1)[0];
const stripped = block.replace(/^\s*\*\s?/mg, "");
const out = {};
let field = "";
let accum = "";
stripped.split("\n").forEach(line => {
const fieldCandidate = line.split(/\s/g, 1)[0];
if (fieldCandidate.length > 1 && fieldCandidate.charAt(0) === "@") {
out[field] = accum.trim();
field = fieldCandidate.substr(1);
accum = line.substr(fieldCandidate.length);
} else {
accum += " " + line.trim().replace("\\n", "\n").replace(/^\\@/, "@");
}
});
out[field] = accum.trim();
delete out[""];
return out;
}
parseNewMeta2(content) {
const block = content.split("/**", 2)[1].split("*/", 1)[0];
const out = {};
let field = "";
let accum = "";
for (const line of block.split(splitRegex)) {
if (line.length === 0) continue;
if (line.charAt(0) === "@" && line.charAt(1) !== " ") {
out[field] = accum;
const l = line.indexOf(" ");
field = line.substr(1, l - 1);
accum = line.substr(l + 1);
} else {
accum += " " + line.replace("\\n", "\n").replace(escapedAtRegex, "@");
}
}
out[field] = accum.trim();
delete out[""];
return out;
}
getContentRequire() {
const self = this; // const baseFolder = this.contentFolder;
const originalRequire = this.originalRequire;
return function (module, filename) {
const possiblePath = path.resolve(self.contentFolder, path.basename(filename));
if (!fs.existsSync(possiblePath) || filename !== fs.realpathSync(possiblePath)) return Reflect.apply(originalRequire, this, arguments);
let content = fs.readFileSync(filename, "utf8");
content = _utilities__WEBPACK_IMPORTED_MODULE_0__["default"].stripBOM(content);
const meta = self.extractMeta(content);
meta.id = meta.name;
meta.filename = path.basename(filename);
content = self.getContentModification(module, content, meta);
module._compile(content, filename);
};
} // Subclasses should use the return (if not ContentError) and push to this.contentList
loadContent(filename) {
if (typeof filename === "undefined") return;
try {
require(path.resolve(this.contentFolder, filename));
} catch (error) {
return new _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"](filename, filename, "Could not be compiled.", {
message: error.message,
stack: error.stack
});
}
return require(path.resolve(this.contentFolder, filename));
} // Subclasses should overload this and modify the content as needed to require() the file
getContentModification(module, content) {
return content;
}
unloadContent(idOrFileOrContent) {
const content = typeof idOrFileOrContent == "string" ? this.contentList.find(c => c.id == idOrFileOrContent || c.filename == idOrFileOrContent) : idOrFileOrContent;
if (!content) return false;
delete require.cache[require.resolve(path.resolve(this.contentFolder, content.filename))];
this.contentList.splice(this.contentList.indexOf(content), 1);
return true;
}
isLoaded(idOrFile) {
const content = this.contentList.find(c => c.id == idOrFile || c.filename == idOrFile);
if (!content) return false;
return true;
}
reloadContent(filename, fromWatcher) {
const didUnload = this.unloadContent(filename, fromWatcher);
if (!didUnload) return didUnload;
return this.loadContent(filename, fromWatcher);
}
loadNewContent() {
const files = fs.readdirSync(this.contentFolder);
const removed = this.contentList.filter(t => !files.includes(t.filename)).map(c => c.id);
const added = files.filter(f => !this.contentList.find(t => t.filename == f) && f.endsWith(this.extension) && fs.statSync(path.resolve(this.contentFolder, f)).isFile());
return {
added,
removed
};
}
loadAllContent() {
const errors = [];
const files = fs.readdirSync(this.contentFolder);
for (const filename of files) {
if (!fs.statSync(path.resolve(this.contentFolder, filename)).isFile() || !filename.endsWith(this.extension)) continue;
const content = this.loadContent(filename);
if (content instanceof _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"]) errors.push(content);
}
if (_settingsmanager__WEBPACK_IMPORTED_MODULE_1__["default"].get(this.collection, this.category, this.id)) this.watchContent();
return errors;
}
}
/***/ }),
/***/ "./src/modules/core.js":
/*!*****************************!*\
!*** ./src/modules/core.js ***!
@ -2927,8 +3204,8 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _bdv2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./bdv2 */ "./src/modules/bdv2.js");
/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js");
/* harmony import */ var data__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! data */ "./src/data/data.js");
/* harmony import */ var _pluginmanager__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./pluginmanager */ "./src/modules/pluginmanager.js");
/* harmony import */ var _thememanager__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./thememanager */ "./src/modules/thememanager.js");
/* harmony import */ var _pluginmanager2__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./pluginmanager2 */ "./src/modules/pluginmanager2.js");
/* harmony import */ var _thememanager2__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./thememanager2 */ "./src/modules/thememanager2.js");
/* harmony import */ var _settingsmanager__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./settingsmanager */ "./src/modules/settingsmanager.js");
/* harmony import */ var builtins__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! builtins */ "./src/builtins/builtins.js");
/* harmony import */ var ui__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ui */ "./src/ui/ui.js");
@ -2981,9 +3258,9 @@ Core.prototype.init = async function () {
for (const module in builtins__WEBPACK_IMPORTED_MODULE_6__) builtins__WEBPACK_IMPORTED_MODULE_6__[module].initialize();
_utilities__WEBPACK_IMPORTED_MODULE_1__["default"].log("Startup", "Loading Plugins");
const pluginErrors = _pluginmanager__WEBPACK_IMPORTED_MODULE_3__["default"].loadPlugins();
const pluginErrors = _pluginmanager2__WEBPACK_IMPORTED_MODULE_3__["default"].loadAllContent();
_utilities__WEBPACK_IMPORTED_MODULE_1__["default"].log("Startup", "Loading Themes");
const themeErrors = _thememanager__WEBPACK_IMPORTED_MODULE_4__["default"].loadThemes();
const themeErrors = _thememanager2__WEBPACK_IMPORTED_MODULE_4__["default"].loadAllContent();
$("#customcss").detach().appendTo(document.head); // PublicServers.initialize();
// EmoteModule.autoCapitalize();
@ -3023,8 +3300,7 @@ Core.prototype.injectExternals = async function () {
Core.prototype.initObserver = function () {
const mainObserver = new MutationObserver(mutations => {
for (let i = 0, mlen = mutations.length; i < mlen; i++) {
const mutation = mutations[i];
if (typeof _pluginmanager__WEBPACK_IMPORTED_MODULE_3__["default"] !== "undefined") _pluginmanager__WEBPACK_IMPORTED_MODULE_3__["default"].rawObserver(mutation); // if there was nothing added, skip
const mutation = mutations[i]; // if there was nothing added, skip
if (!mutation.addedNodes.length || !(mutation.addedNodes[0] instanceof Element)) continue;
const node = mutation.addedNodes[0];
@ -3854,6 +4130,264 @@ PluginModule.prototype.rawObserver = function (e) {
/***/ }),
/***/ "./src/modules/pluginmanager2.js":
/*!***************************************!*\
!*** ./src/modules/pluginmanager2.js ***!
\***************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var data__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! data */ "./src/data/data.js");
/* harmony import */ var _contentmanager2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./contentmanager2 */ "./src/modules/contentmanager2.js");
/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js");
/* harmony import */ var ui__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ui */ "./src/ui/ui.js");
/* harmony import */ var _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../structs/contenterror */ "./src/structs/contenterror.js");
const path = __webpack_require__(/*! path */ "path");
const electronRemote = __webpack_require__(/*! electron */ "electron").remote;
/* harmony default export */ __webpack_exports__["default"] = (new class PluginManager extends _contentmanager2__WEBPACK_IMPORTED_MODULE_1__["default"] {
get name() {
return "PluginManager";
}
get moduleExtension() {
return ".js";
}
get extension() {
return ".plugin.js";
}
get contentFolder() {
return path.resolve(data__WEBPACK_IMPORTED_MODULE_0__["Config"].dataPath, "plugins");
}
get prefix() {
return "plugin";
}
constructor() {
super();
this.onSwitch = this.onSwitch.bind(this);
this.observer = new MutationObserver(mutations => {
for (let i = 0, mlen = mutations.length; i < mlen; i++) {
this.onMutation(mutations[i]);
}
});
}
loadContent(filename, fromWatcher) {
const error = this.loadPlugin(filename, fromWatcher);
if (!fromWatcher) return error;
if (error) ui__WEBPACK_IMPORTED_MODULE_3__["Modals"].showContentErrors({
plugins: [error]
});
}
unloadContent(idOrFileOrContent) {
return this.unloadPlugin(idOrFileOrContent);
}
getContentModification(module, content, meta) {
module._compile(content, module.filename);
const didExport = !_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].isEmpty(module.exports);
if (didExport) {
meta.type = module.exports;
module.exports = meta;
return "";
}
content += `\nmodule.exports = ${JSON.stringify(meta)};\nmodule.exports.type = ${meta.exports || meta.name};`;
return content;
}
loadPlugin(filename) {
const content = super.loadContent(filename);
if (content instanceof _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"]) return content;
console.log(content);
if (!content.type) return new _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"](filename, filename, "Plugin had no exports", {
message: "Plugin had no exports or no name property.",
stack: ""
});
try {
const thePlugin = new content.type();
const pluginName = thePlugin.getName() || thePlugin.name || content.id;
if (this.contentList.find(c => c.id == pluginName)) return new _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"](pluginName, filename, `There is already a plugin with name ${pluginName}`);
content.plugin = thePlugin;
content.name = pluginName;
content.author = content.author || thePlugin.getAuthor();
content.description = content.description || thePlugin.getDescription();
content.version = content.version || thePlugin.getVersion();
this.contentList.push(content);
try {
if (typeof content.plugin.load == "function") content.plugin.load();
} catch (error) {
this.state[content.id] = false;
return new _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"](pluginName, filename, "load() could not be fired.", {
message: error.message,
stack: error.stack
});
}
this.emit("loaded", content.id);
if (!this.state[content.id]) return this.state[content.id] = false;
return this.startPlugin(content);
} catch (error) {
return new _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"](filename, filename, "Could not be constructed.", {
message: error.message,
stack: error.stack
});
}
}
unloadPlugin(idOrFileOrContent) {
const content = typeof idOrFileOrContent == "string" ? this.contentList.find(c => c.id == idOrFileOrContent || c.filename == idOrFileOrContent) : idOrFileOrContent;
if (!content) return false;
if (this.state[content.id]) this.disablePlugin(content);
super.unloadContent(content);
ui__WEBPACK_IMPORTED_MODULE_3__["Toasts"].success(`${content.name} was unloaded.`);
this.emit("unloaded", content.id);
return true;
}
reloadPlugin(filename) {
this.reloadContent(filename);
}
startPlugin(idOrContent) {
const content = typeof idOrContent == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
if (!content) return;
const plugin = content.plugin;
try {
plugin.start();
this.emit("started", content.id);
ui__WEBPACK_IMPORTED_MODULE_3__["Toasts"].show(`${content.name} v${content.version} has started.`);
} catch (err) {
this.state[content.id] = false;
ui__WEBPACK_IMPORTED_MODULE_3__["Toasts"].error(`${content.name} v${content.version} could not be started.`);
_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].err("Plugins", content.name + " could not be started.", err);
return new _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"](content.name, content.filename, "start() could not be fired.", {
message: err.message,
stack: err.stack
});
}
}
stopPlugin(idOrContent) {
const content = typeof idOrContent == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
if (!content) return;
const plugin = content.plugin;
try {
plugin.stop();
this.emit("stopped", content.id);
ui__WEBPACK_IMPORTED_MODULE_3__["Toasts"].show(`${content.name} v${content.version} has stopped.`);
} catch (err) {
this.state[content.id] = false;
ui__WEBPACK_IMPORTED_MODULE_3__["Toasts"].error(`${content.name} v${content.version} could not be stopped.`);
_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].err("Plugins", content.name + " could not be stopped.", err);
return new _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"](content.name, content.filename, "stop() could not be fired.", {
message: err.message,
stack: err.stack
});
}
}
updatePluginList() {
const results = this.loadNewContent();
for (const filename of results.added) this.loadPlugin(filename);
for (const name of results.removed) this.unloadPlugin(name);
}
enablePlugin(idOrContent) {
const content = typeof idOrContent == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
if (!content) return;
if (this.state[content.id]) return;
this.state[content.id] = true;
this.startPlugin(content);
this.saveState();
}
disablePlugin(idOrContent) {
const content = typeof idOrContent == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
if (!content) return;
if (!this.state[content.id]) return;
this.state[content.id] = false;
this.stopPlugin(content);
this.saveState();
}
togglePlugin(id) {
if (this.state[id]) this.disablePlugin(id);else this.enablePlugin(id);
}
loadAllPlugins() {
this.loadState();
const errors = this.loadAllContent();
this.saveState();
return errors;
}
setupFunctions() {
electronRemote.getCurrentWebContents().on("did-navigate-in-page", this.onSwitch.bind(this));
this.observer.observe(document, {
childList: true,
subtree: true
});
}
onSwitch() {
this.emit("page-switch");
for (let i = 0; i < this.contentList.length; i++) {
const plugin = this.contentList[i].plugin;
if (!this.state[this.contentList[i].id]) continue;
if (typeof plugin.onSwitch === "function") {
try {
plugin.onSwitch();
} catch (err) {
_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].err("Plugins", "Unable to fire onSwitch for " + this.contentList[i].name + ".", err);
}
}
}
}
onMutation(mutation) {
for (let i = 0; i < this.contentList.length; i++) {
const plugin = this.contentList[i].plugin;
if (!this.state[this.contentList[i].id]) continue;
if (typeof plugin.observer === "function") {
try {
plugin.observer(mutation);
} catch (err) {
_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].err("Plugins", "Unable to fire observer for " + this.contentList[i].name + ".", err);
}
}
}
}
}());
/***/ }),
/***/ "./src/modules/settingsmanager.js":
/*!****************************************!*\
!*** ./src/modules/settingsmanager.js ***!
@ -4282,6 +4816,61 @@ ThemeModule.prototype.saveThemeData = function () {
/***/ }),
/***/ "./src/modules/thememanager2.js":
/*!**************************************!*\
!*** ./src/modules/thememanager2.js ***!
\**************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var data__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! data */ "./src/data/data.js");
/* harmony import */ var _contentmanager2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./contentmanager2 */ "./src/modules/contentmanager2.js");
/* harmony import */ var _structs_contenterror__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../structs/contenterror */ "./src/structs/contenterror.js");
const path = __webpack_require__(/*! path */ "path");
/* harmony default export */ __webpack_exports__["default"] = (new class ThemeManager extends _contentmanager2__WEBPACK_IMPORTED_MODULE_1__["default"] {
get name() {
return "ThemeManager";
}
get moduleExtension() {
return ".css";
}
get extension() {
return ".theme.css";
}
get contentFolder() {
return path.resolve(data__WEBPACK_IMPORTED_MODULE_0__["Config"].dataPath, "themes");
}
get prefix() {
return "theme";
}
loadContent(filename) {
const content = super.loadContent(filename);
if (content instanceof _structs_contenterror__WEBPACK_IMPORTED_MODULE_2__["default"]) return content;
console.log(content);
this.contentList.push(content);
}
getContentModification(module, content, meta) {
meta.css = content.split("\n").slice(1).join("\n");
return `module.exports = ${JSON.stringify(meta)};`;
}
}());
/***/ }),
/***/ "./src/modules/utilities.js":
/*!**********************************!*\
!*** ./src/modules/utilities.js ***!
@ -4462,6 +5051,18 @@ class Utilities {
childList: true
});
}
static isEmpty(obj) {
if (obj == null || obj == undefined || obj == "") return true;
if (typeof obj !== "object") return false;
if (Array.isArray(obj)) return obj.length == 0;
for (const key in obj) {
if (obj.hasOwnProperty(key)) return false;
}
return true;
}
/**
* Generates an automatically memoizing version of an object.
* @author Zerebos
@ -5360,6 +5961,48 @@ class BuiltinModule {
/***/ }),
/***/ "./src/structs/contenterror.js":
/*!*************************************!*\
!*** ./src/structs/contenterror.js ***!
\*************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return ContentError; });
class ContentError extends Error {
constructor(name, filename, message, error) {
super(message);
this.name = name;
this.file = filename;
this.error = error;
}
}
/***/ }),
/***/ "./src/structs/metaerror.js":
/*!**********************************!*\
!*** ./src/structs/metaerror.js ***!
\**********************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return MetaError; });
class MetaError extends Error {
constructor(message) {
super(message);
this.name = "MetaError";
}
}
/***/ }),
/***/ "./src/ui/emote.js":
/*!*************************!*\
!*** ./src/ui/emote.js ***!

View File

@ -2,8 +2,8 @@ import {SettingsCookie, SettingsInfo, Config, PluginCookie, ThemeCookie, Plugins
import proxyLocalStorage from "./localstorage";
import Core from "./modules/core";
import BdApi from "./modules/pluginapi";
import PluginManager from "./modules/pluginmanager";
import ThemeManager from "./modules/thememanager";
import PluginManager from "./modules/pluginmanager2";
import ThemeManager from "./modules/thememanager2";
import {bdPluginStorage} from "./modules/oldstorage";
import Events from "./modules/emitter";

View File

@ -1,50 +1,57 @@
import Utilities from "./utilities";
import Settings from "./settingsmanager";
import Events from "./emitter";
import DataStore from "./datastore";
import ContentError from "../structs/contenterror";
import MetaError from "../structs/metaerror";
const path = require("path");
const fs = require("fs");
const Module = require("module").Module;
Module.globalPaths.push(path.resolve(require("electron").remote.app.getAppPath(), "node_modules"));
class MetaError extends Error {
constructor(message) {
super(message);
this.name = "MetaError";
}
}
class ContentError extends Error {
constructor(name, filename, message, error) {
super(message);
this.name = name;
this.file = filename;
this.error = error;
}
}
const splitRegex = /[^\S\r\n]*?\n[^\S\r\n]*?\*[^\S\r\n]?/;
const escapedAtRegex = /^\\@/;
export default new class ContentManager {
export default class ContentManager {
get fileExtension() {return "";}
get name() {return "";}
get moduleExtension() {return "";}
get extension() {return "";}
get contentFolder() {return "";}
get prefix() {return "content";}
get collection() {return "settings";}
get category() {return "content";}
get id() {return "autoReload";}
getContentModification(content) {return content;}
emit(event, ...args) {return Events.emit(`${this.prefix}-${event}`, ...args);}
constructor() {
this.timeCache = {};
this.content = [];
this.originalRequire = Module._extensions[this.fileExtension];
Module._extensions[this.fileExtension] = this.getContentRequire();
this.contentList = [];
this.state = {};
this.originalRequire = Module._extensions[this.moduleExtension];
Module._extensions[this.moduleExtension] = this.getContentRequire();
Settings.on(this.collection, this.category, this.id, (enabled) => {
if (enabled) this.watchContent();
else this.unwatchContent();
});
if (Settings.get(this.collection, this.category, this.id)) this.watchContent();
}
loadState() {
const saved = DataStore.getData(`${this.prefix}s`);
console.log(saved);
if (!saved) return;
Object.assign(this.state, saved);
}
saveState() {
DataStore.setData(`${this.prefix}s`, this.state);
}
watchContent() {
if (this.watcher) return;
if (this.watcher) return Utilities.err(this.name, "Already watching content.");
Utilities.log(this.name, "Starting to watch content.");
this.watcher = fs.watch(this.contentFolder, {persistent: false}, async (eventType, filename) => {
if (!eventType || !filename || !filename.endsWith(this.extension)) return;
await new Promise(r => setTimeout(r, 50));
@ -52,7 +59,7 @@ export default new class ContentManager {
catch (err) {
if (err.code !== "ENOENT") return;
delete this.timeCache[filename];
this.unloadContent(filename);
this.unloadContent(filename, true);
}
if (!fs.statSync(path.resolve(this.contentFolder, filename)).isFile()) return;
const stats = fs.statSync(path.resolve(this.contentFolder, filename));
@ -60,18 +67,28 @@ export default new class ContentManager {
if (typeof(stats.mtime.getTime()) !== "number") return;
if (this.timeCache[filename] == stats.mtime.getTime()) return;
this.timeCache[filename] = stats.mtime.getTime();
if (eventType == "rename") this.loadContent(filename);
if (eventType == "change") this.reloadContent(filename);
if (eventType == "rename") this.loadContent(filename, true);
if (eventType == "change") this.reloadContent(filename, true);
});
}
unwatchContent() {
if (!this.watcher) return;
if (!this.watcher) return Utilities.err(this.name, "Was not watching content.");
this.watcher.close();
delete this.watcher;
Utilities.log(this.name, "No longer watching content.");
}
extractMeta(content) {
const firstLine = content.split("\n")[0];
const hasOldMeta = firstLine.includes("//META");
if (hasOldMeta) return this.parseOldMeta(content);
const hasNewMeta = firstLine.includes("/**");
if (hasNewMeta) return this.parseNewMeta(content);
throw new MetaError("META was not found.");
}
parseOldMeta(content) {
const meta = content.split("\n")[0];
const rawMeta = meta.substring(meta.lastIndexOf("//META") + 6, meta.lastIndexOf("*//"));
if (meta.indexOf("META") < 0) throw new MetaError("META was not found.");
@ -82,68 +99,150 @@ export default new class ContentManager {
return parsed;
}
parseNewMeta(content) {
const block = content.split("/**", 2)[1].split("*/", 1)[0];
const stripped = block.replace(/^\s*\*\s?/mg, "");
const out = {};
let field = "";
let accum = "";
stripped.split("\n").forEach(line => {
const fieldCandidate = line.split(/\s/g, 1)[0];
if (fieldCandidate.length > 1 && fieldCandidate.charAt(0) === "@") {
out[field] = accum.trim();
field = fieldCandidate.substr(1);
accum = line.substr(fieldCandidate.length);
}
else {
accum += " " + line.trim().replace("\\n", "\n").replace(/^\\@/, "@");
}
});
out[field] = accum.trim();
delete out[""];
return out;
}
parseNewMeta2(content) {
const block = content.split("/**", 2)[1].split("*/", 1)[0];
const out = {};
let field = "";
let accum = "";
for (const line of block.split(splitRegex)) {
if (line.length === 0) continue;
if (line.charAt(0) === "@" && line.charAt(1) !== " ") {
out[field] = accum;
const l = line.indexOf(" ");
field = line.substr(1, l - 1);
accum = line.substr(l + 1);
}
else {
accum += " " + line.replace("\\n", "\n").replace(escapedAtRegex, "@");
}
}
out[field] = accum.trim();
delete out[""];
return out;
}
getContentRequire() {
const self = this;
const baseFolder = this.contentFolder;
// const baseFolder = this.contentFolder;
const originalRequire = this.originalRequire;
return function(module, filename) {
const possiblePath = path.resolve(baseFolder, path.basename(filename));
const possiblePath = path.resolve(self.contentFolder, path.basename(filename));
if (!fs.existsSync(possiblePath) || filename !== fs.realpathSync(possiblePath)) return Reflect.apply(originalRequire, this, arguments);
let content = fs.readFileSync(filename, "utf8");
content = Utilities.stripBOM(content);
const meta = self.extractMeta(content);
meta.id = meta.name;
meta.filename = path.basename(filename);
content = self.getContentModification(content, meta);
content = self.getContentModification(module, content, meta);
module._compile(content, filename);
};
}
// Subclasses should use the return (if not ContentError) and push to this.contentList
loadContent(filename, type) {
if (typeof(filename) === "undefined" || typeof(type) === "undefined") return;
loadContent(filename) {
if (typeof(filename) === "undefined") return;
try {__non_webpack_require__(path.resolve(this.contentFolder, filename));}
catch (error) {return new ContentError(filename, filename, "Could not be compiled.", {message: error.message, stack: error.stack});}
return __non_webpack_require__(path.resolve(this.contentFolder, filename));
}
unloadContent(idOrFile) {
if (typeof(filename) === "undefined") return;
const content = this.content.find(c => c.id == idOrFile || c.filename == idOrFile);
// Subclasses should overload this and modify the content as needed to require() the file
getContentModification(module, content) {return content;}
unloadContent(idOrFileOrContent, fromWatcher) {
const content = typeof(idOrFileOrContent) == "string" ? this.contentList.find(c => c.id == idOrFileOrContent || c.filename == idOrFileOrContent) : idOrFileOrContent;
if (!content) return false;
delete __non_webpack_require__.cache[__non_webpack_require__.resolve(path.resolve(this.contentFolder, content.file))];
this.content.splice(this.content.indexOf(content), 1);
if (this.state[content.id]) this.disableContent(content, fromWatcher);
delete __non_webpack_require__.cache[__non_webpack_require__.resolve(path.resolve(this.contentFolder, content.filename))];
this.contentList.splice(this.contentList.indexOf(content), 1);
this.emit("unloaded", content.id);
Toasts.success(`${content.name} was unloaded.`);
return true;
}
isLoaded(idOrFile) {
const content = this.content.find(c => c.id == idOrFile || c.filename == idOrFile);
const content = this.contentList.find(c => c.id == idOrFile || c.filename == idOrFile);
if (!content) return false;
return true;
}
reloadContent(filename, type) {
const didUnload = this.unloadContent(filename, type);
reloadContent(filename, fromWatcher) {
const didUnload = this.unloadContent(filename, fromWatcher);
if (!didUnload) return didUnload;
return this.loadContent(filename, type);
return this.loadContent(filename, fromWatcher);
}
enableContent(idOrContent, fromWatcher = false) {
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
if (!content) return;
if (this.state[content.id]) return;
this.state[content.id] = true;
this.startContent(content);
if (!fromWatcher) this.saveState();
}
disableContent(idOrContent, fromWatcher = false) {
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
if (!content) return;
if (!this.state[content.id]) return;
this.state[content.id] = false;
this.stopContent(content);
if (!fromWatcher) this.saveState();
}
toggleContent(id) {
if (this.state[id]) this.disableContent(id);
else this.enableContent(id);
}
loadNewContent() {
const files = fs.readdirSync(this.contentFolder);
const removed = this.content.filter(t => !files.includes(t.filename)).map(c => c.id);
const added = files.filter(f => !this.content.find(t => t.filename == f) && f.endsWith(this.extension) && fs.statSync(path.resolve(this.contentFolder, f)).isFile());
const removed = this.contentList.filter(t => !files.includes(t.filename)).map(c => c.id);
const added = files.filter(f => !this.contentList.find(t => t.filename == f) && f.endsWith(this.extension) && fs.statSync(path.resolve(this.contentFolder, f)).isFile());
return {added, removed};
}
loadAllContent(type) {
updateList() {
const results = this.loadNewContent();
for (const filename of results.added) this.loadContent(filename);
for (const name of results.removed) this.unloadContent(name);
}
loadAllContent() {
this.loadState();
const errors = [];
const files = fs.readdirSync(this.contentFolder);
for (const filename of files) {
if (!fs.statSync(path.resolve(this.contentFolder, filename)).isFile() || !filename.endsWith(this.extension)) continue;
const content = this.loadContent(filename, type);
const content = this.loadContent(filename);
if (content instanceof ContentError) errors.push(content);
}
this.saveState();
if (Settings.get(this.collection, this.category, this.id)) this.watchContent();
return errors;
}
};
}

View File

@ -3,8 +3,8 @@ import Utilities from "./utilities";
import {Config} from "data";
// import EmoteModule from "./emotes";
// import QuickEmoteMenu from "../builtins/emotemenu";
import PluginManager from "./pluginmanager";
import ThemeManager from "./thememanager";
import PluginManager from "./pluginmanager2";
import ThemeManager from "./thememanager2";
import Settings from "./settingsmanager";
import * as Builtins from "builtins";
import {Modals} from "ui";
@ -48,10 +48,10 @@ Core.prototype.init = async function() {
for (const module in Builtins) Builtins[module].initialize();
Utilities.log("Startup", "Loading Plugins");
const pluginErrors = PluginManager.loadPlugins();
const pluginErrors = PluginManager.loadAllContent();
Utilities.log("Startup", "Loading Themes");
const themeErrors = ThemeManager.loadThemes();
const themeErrors = ThemeManager.loadAllContent();
$("#customcss").detach().appendTo(document.head);
@ -93,7 +93,6 @@ Core.prototype.initObserver = function () {
for (let i = 0, mlen = mutations.length; i < mlen; i++) {
const mutation = mutations[i];
if (typeof PluginManager !== "undefined") PluginManager.rawObserver(mutation);
// if there was nothing added, skip
if (!mutation.addedNodes.length || !(mutation.addedNodes[0] instanceof Element)) continue;

View File

@ -0,0 +1,165 @@
import {Config} from "data";
import ContentManager from "./contentmanager2";
import Utilities from "./utilities";
import {Toasts, Modals} from "ui";
import ContentError from "../structs/contenterror";
const path = require("path");
const electronRemote = require("electron").remote;
export default new class PluginManager extends ContentManager {
get name() {return "PluginManager";}
get moduleExtension() {return ".js";}
get extension() {return ".plugin.js";}
get contentFolder() {return path.resolve(Config.dataPath, "plugins");}
get prefix() {return "plugin";}
constructor() {
super();
this.onSwitch = this.onSwitch.bind(this);
this.observer = new MutationObserver((mutations) => {
for (let i = 0, mlen = mutations.length; i < mlen; i++) {
this.onMutation(mutations[i]);
}
});
}
/* Aliases */
updatePluginList() {return this.updateList();}
loadAllPlugins() {return this.loadAllContent();}
enablePlugin(idOrContent, fromWatcher = false) {return this.enableContent(idOrContent, fromWatcher);}
disablePlugin(idOrContent, fromWatcher = false) {return this.disableContent(idOrContent, fromWatcher);}
togglePlugin(id) {return this.toggleContent(id);}
loadContent(filename, fromWatcher) {
const error = this.loadPlugin(filename, fromWatcher);
if (!fromWatcher) return error;
if (error) Modals.showContentErrors({plugins: [error]});
}
unloadContent(idOrFileOrContent, fromWatcher) {return this.unloadPlugin(idOrFileOrContent, fromWatcher);}
/* Overrides */
getContentModification(module, content, meta) {
module._compile(content, module.filename);
const didExport = !Utilities.isEmpty(module.exports);
if (didExport) {
meta.type = module.exports;
module.exports = meta;
return "";
}
content += `\nmodule.exports = ${JSON.stringify(meta)};\nmodule.exports.type = ${meta.exports || meta.name};`;
return content;
}
loadPlugin(filename) {
const content = super.loadContent(filename);
if (content instanceof ContentError) return content;
console.log(content);
if (!content.type) return new ContentError(filename, filename, "Plugin had no exports", {message: "Plugin had no exports or no name property.", stack: ""});
try {
const thePlugin = new content.type();
if (this.contentList.find(c => c.id == content.id)) return new ContentError(content.id, filename, `There is already a plugin with name ${content.id}`);
content.plugin = thePlugin;
content.name = content.name || thePlugin.getName();
content.author = content.author || thePlugin.getAuthor();
content.description = content.description || thePlugin.getDescription();
content.version = content.version || thePlugin.getVersion();
this.contentList.push(content);
try {
if (typeof(content.plugin.load) == "function") content.plugin.load();
}
catch (error) {
this.state[content.id] = false;
return new ContentError(content.name, filename, "load() could not be fired.", {message: error.message, stack: error.stack});
}
this.emit("loaded", content.id);
Toasts.success(`${content.name} was loaded.`);
if (!this.state[content.id]) return this.state[content.id] = false;
return this.startPlugin(content);
}
catch (error) {return new ContentError(filename, filename, "Could not be constructed.", {message: error.message, stack: error.stack});}
}
unloadPlugin(idOrFileOrContent, fromWatcher) {
const content = typeof(idOrFileOrContent) == "string" ? this.contentList.find(c => c.id == idOrFileOrContent || c.filename == idOrFileOrContent) : idOrFileOrContent;
if (!content) return false;
if (this.state[content.id]) this.disablePlugin(content, fromWatcher);
super.unloadContent(content);
Toasts.success(`${content.name} was unloaded.`);
this.emit("unloaded", content.id);
return true;
}
reloadPlugin(filename) {
this.reloadContent(filename);
}
startContent(id) {return this.startPlugin(id);}
stopContent(id) {return this.stopPlugin(id);}
startPlugin(idOrContent) {
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
if (!content) return;
const plugin = content.plugin;
try {
plugin.start();
this.emit("started", content.id);
Toasts.show(`${content.name} v${content.version} has started.`);
}
catch (err) {
this.state[content.id] = false;
Toasts.error(`${content.name} v${content.version} could not be started.`);
Utilities.err("Plugins", content.name + " could not be started.", err);
return new ContentError(content.name, content.filename, "start() could not be fired.", {message: err.message, stack: err.stack});
}
}
stopPlugin(idOrContent) {
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
if (!content) return;
const plugin = content.plugin;
try {
plugin.stop();
this.emit("stopped", content.id);
Toasts.show(`${content.name} v${content.version} has stopped.`);
}
catch (err) {
this.state[content.id] = false;
Toasts.error(`${content.name} v${content.version} could not be stopped.`);
Utilities.err("Plugins", content.name + " could not be stopped.", err);
return new ContentError(content.name, content.filename, "stop() could not be fired.", {message: err.message, stack: err.stack});
}
}
setupFunctions() {
electronRemote.getCurrentWebContents().on("did-navigate-in-page", this.onSwitch.bind(this));
this.observer.observe(document, {
childList: true,
subtree: true
});
}
onSwitch() {
this.emit("page-switch");
for (let i = 0; i < this.contentList.length; i++) {
const plugin = this.contentList[i].plugin;
if (!this.state[this.contentList[i].id]) continue;
if (typeof(plugin.onSwitch) === "function") {
try { plugin.onSwitch(); }
catch (err) { Utilities.err("Plugins", "Unable to fire onSwitch for " + this.contentList[i].name + ".", err); }
}
}
}
onMutation(mutation) {
for (let i = 0; i < this.contentList.length; i++) {
const plugin = this.contentList[i].plugin;
if (!this.state[this.contentList[i].id]) continue;
if (typeof plugin.observer === "function") {
try { plugin.observer(mutation); }
catch (err) { Utilities.err("Plugins", "Unable to fire observer for " + this.contentList[i].name + ".", err); }
}
}
}
};

View File

@ -0,0 +1,119 @@
import {Config} from "data";
import ContentManager from "./contentmanager2";
import Utilities from "./utilities";
import ContentError from "../structs/contenterror";
import {Toasts, Modals} from "ui";
const path = require("path");
export default new class ThemeManager extends ContentManager {
get name() {return "ThemeManager";}
get moduleExtension() {return ".css";}
get extension() {return ".theme.css";}
get contentFolder() {return path.resolve(Config.dataPath, "themes");}
get prefix() {return "theme";}
/* Aliases */
updateThemeList() {return this.updateList();}
loadAllThemes() {return this.loadAllContent();}
enableTheme(idOrContent, fromWatcher = false) {return this.enableContent(idOrContent, fromWatcher);}
disableTheme(idOrContent, fromWatcher = false) {return this.disableContent(idOrContent, fromWatcher);}
toggleTheme(id) {return this.toggleContent(id);}
unloadTheme(idOrFileOrContent, fromWatcher) {return this.unloadContent(idOrFileOrContent, fromWatcher);}
loadContent(filename, fromWatcher) {
const error = this.loadTheme(filename, fromWatcher);
if (!fromWatcher) return error;
if (error) Modals.showContentErrors({themes: [error]});
}
/* Overrides */
getContentModification(module, content, meta) {
meta.css = content.split("\n").slice(1).join("\n");
return `module.exports = ${JSON.stringify(meta)};`;
}
loadTheme(filename) {
const content = super.loadContent(filename);
if (content instanceof ContentError) return content;
console.log(content);
if (this.contentList.find(c => c.id == content.name)) return new ContentError(content.name, filename, `There is already a plugin with name ${content.name}`);
this.contentList.push(content);
Toasts.success(`${content.name} v${content.version} was loaded.`);
this.emit("loaded", content.name);
if (!this.state[content.id]) return this.state[content.id] = false;
return this.addTheme(content);
}
reloadTheme(filename) {
this.reloadContent(filename);
}
startContent(id) {return this.addTheme(id);}
stopContent(id) {return this.removeTheme(id);}
addTheme() {
}
removeTheme() {
}
};
function ThemeModule() {
}
ThemeModule.prototype.enableTheme = function(theme, reload = false) {
ThemeCookie[theme] = true;
this.saveThemeData();
$("head").append($("<style>", {id: Utilities.escapeID(theme), text: unescape(Themes[theme].css)}));
if (!reload) Toasts.show(`${Themes[theme].name} v${Themes[theme].version} has been applied.`);
};
ThemeModule.prototype.disableTheme = function(theme, reload = false) {
ThemeCookie[theme] = false;
this.saveThemeData();
$(`#${Utilities.escapeID(Themes[theme].name)}`).remove();
if (!reload) Toasts.show(`${Themes[theme].name} v${Themes[theme].version} has been disabled.`);
};
ThemeModule.prototype.toggleTheme = function(theme) {
if (ThemeCookie[theme]) this.disableTheme(theme);
else this.enableTheme(theme);
};
ThemeModule.prototype.unloadTheme = function(filenameOrName) {
const bdtheme = Object.values(Themes).find(p => p.filename == filenameOrName) || Themes[filenameOrName];
if (!bdtheme) return;
const theme = bdtheme.name;
if (ThemeCookie[theme]) this.disableTheme(theme, true);
const error = ContentManager.unloadContent(Themes[theme].filename, "theme");
delete Themes[theme];
if (error) {
Modals.showContentErrors({themes: [error]});
Toasts.show(`${theme} could not be unloaded. It may have not been loaded yet.`, {type: "error"});
return Utilities.err("ContentManager", `${theme} could not be unloaded. It may have not been loaded yet.`, error);
}
Utilities.log("ContentManager", `${theme} was unloaded.`);
Toasts.show(`${theme} was unloaded.`, {type: "success"});
Emitter.dispatch("theme-unloaded", theme);
};
ThemeModule.prototype.reloadTheme = function(filenameOrName) {
const bdtheme = Object.values(Themes).find(p => p.filename == filenameOrName) || Themes[filenameOrName];
if (!bdtheme) return this.loadTheme(filenameOrName);
const theme = bdtheme.name;
const error = ContentManager.reloadContent(Themes[theme].filename, "theme");
if (ThemeCookie[theme]) this.disableTheme(theme, true), this.enableTheme(theme, true);
if (error) {
Modals.showContentErrors({themes: [error]});
Toasts.show(`${theme} could not be reloaded.`, {type: "error"});
return Utilities.err("ContentManager", `${theme} could not be reloaded.`, error);
}
Utilities.log("ContentManager", `${theme} v${Themes[theme].version} was reloaded.`);
Toasts.show(`${theme} v${Themes[theme].version} was reloaded.`, {type: "success"});
Emitter.dispatch("theme-reloaded", theme);
};

View File

@ -144,6 +144,16 @@ export default class Utilities {
observer.observe(document.body, {subtree: true, childList: true});
}
static isEmpty(obj) {
if (obj == null || obj == undefined || obj == "") return true;
if (typeof(obj) !== "object") return false;
if (Array.isArray(obj)) return obj.length == 0;
for (const key in obj) {
if (obj.hasOwnProperty(key)) return false;
}
return true;
}
/**
* Generates an automatically memoizing version of an object.
* @author Zerebos

View File

@ -0,0 +1,8 @@
export default class ContentError extends Error {
constructor(name, filename, message, error) {
super(message);
this.name = name;
this.file = filename;
this.error = error;
}
}

6
src/structs/metaerror.js Normal file
View File

@ -0,0 +1,6 @@
export default class MetaError extends Error {
constructor(message) {
super(message);
this.name = "MetaError";
}
}