diff --git a/Plugins/ThemeRepo/ThemeRepo.plugin.js b/Plugins/ThemeRepo/ThemeRepo.plugin.js index ec530dafc3..ee6546bde3 100644 --- a/Plugins/ThemeRepo/ThemeRepo.plugin.js +++ b/Plugins/ThemeRepo/ThemeRepo.plugin.js @@ -2,7 +2,7 @@ * @name ThemeRepo * @author DevilBro * @authorId 278543574059057154 - * @version 2.1.7 + * @version 2.1.9 * @description Allows you to download all Themes from BD's Website within Discord * @invite Jx3TjNS * @donate https://www.paypal.me/MircoWittrien @@ -17,12 +17,12 @@ module.exports = (_ => { "info": { "name": "ThemeRepo", "author": "DevilBro", - "version": "2.1.7", + "version": "2.1.9", "description": "Allows you to download all Themes from BD's Website within Discord" }, "changeLog": { - "fixed": { - "Old META": "Downloading a Theme that still uses the old META Format will now automatically transform it into the new META Format" + "progress": { + "New Style and Website Store": "The Repo now directly reflects the Themes hosted on https://betterdiscord.app/ and uses a new Card Style" } } }; @@ -65,23 +65,27 @@ module.exports = (_ => { return template.content.firstElementChild; } } : (([Plugin, BDFDB]) => { - const isBeta = !(window.BdApi && !Array.isArray(BdApi.settings)); var _this; - var loading, cachedThemes, grabbedThemes, foundThemes, loadedThemes, generatorThemes, updateInterval; - var list, header, preview, searchTimeout, updateGeneratorTimeout, forceRerenderGenerator, nativeCSS, nativeCSSvars, forcedSort, forcedOrder, showOnlyOutdated; - var settings = {}, modalSettings = {}, favorites = [], customList = []; + + var list, header, preview; + + var loading, cachedThemes, grabbedThemes, generatorThemes, updateInterval; + var searchTimeout, forcedSort, forcedOrder, showOnlyOutdated; + var updateGeneratorTimeout, forceRerenderGenerator, nativeCSS, nativeCSSvars; + + var favorites = []; const themeStates = { - UPDATED: 0, + INSTALLED: 0, OUTDATED: 1, DOWNLOADABLE: 2 }; const buttonData = { - UPDATED: { + INSTALLED: { colorClass: "GREEN", backgroundColor: "STATUS_GREEN", icon: "CHECKMARK", - text: "updated" + text: "USER_SETTINGS_VOICE_INSTALLED_LABEL" }, OUTDATED: { colorClass: "RED", @@ -96,20 +100,17 @@ module.exports = (_ => { text: "download" } }; - const favStates = { - FAVORIZED: 0, - NOT_FAVORIZED: 1 - }; - const newStates = { - NEW: 0, - NOT_NEW: 1 - }; + const reverseSorts = [ + "DOWNLOADS", "LIKES", "FAV", "NEW" + ]; const sortKeys = { NAME: "Name", AUTHOR: "Author", VERSION: "Version", DESCRIPTION: "Description", STATE: "Update State", + DOWNLOADS: "Downloads", + LIKES: "Likes", FAV: "Favorites", NEW: "New Themes" }; @@ -134,35 +135,30 @@ module.exports = (_ => { this.closePreview(); } filterThemes() { - let themes = Object.keys(loadedThemes).map(url => { - let theme = loadedThemes[url]; - let instTheme = BDFDB.BDUtils.getTheme(theme.name); - if (instTheme && instTheme.author && instTheme.author.toUpperCase() == theme.author.toUpperCase()) theme.state = instTheme.version != theme.version ? themeStates.OUTDATED : themeStates.UPDATED; - else theme.state = themeStates.DOWNLOADABLE; + let themes = grabbedThemes.map(theme => { + const installedTheme = theme.version && _this.getInstalledTheme(theme); + const state = installedTheme ? (BDFDB.NumberUtils.compareVersions(theme.version, _this.getString(installedTheme.version)) ? themeStates.OUTDATED : themeStates.INSTALLED) : themeStates.DOWNLOADABLE; return { - url: theme.url, - search: (theme.name + " " + theme.version + " " + theme.author + " " + theme.description).toUpperCase(), - name: theme.name, - version: theme.version, - author: theme.author, + ...theme, + search: [theme.name, theme.version, theme.author, theme.description, theme.tags].flat(10).filter(n => typeof n == "string").join(" ").toUpperCase(), description: theme.description || "No Description found", - fav: favorites.includes(url) ? favStates.FAVORIZED : favStates.NOT_FAVORIZED, - new: theme.state == themeStates.DOWNLOADABLE && !cachedThemes.includes(url) ? newStates.NEW : newStates.NOT_NEW, - state: theme.state, - css: theme.css, - fullCSS: theme.fullCSS + fav: favorites.includes(theme.id) && 1, + new: state == themeStates.DOWNLOADABLE && !cachedThemes.includes(theme.id) && 1, + state: state }; }); - if (!this.props.updated) themes = themes.filter(theme => theme.state != themeStates.UPDATED); - if (!this.props.outdated) themes = themes.filter(theme => theme.state != themeStates.OUTDATED); - if (!this.props.downloadable) themes = themes.filter(theme => theme.state != themeStates.DOWNLOADABLE); - if (this.props.searchString) { + if (!this.props.updated) themes = themes.filter(theme => theme.state != themeStates.INSTALLED); + if (!this.props.outdated) themes = themes.filter(theme => theme.state != themeStates.OUTDATED); + if (!this.props.downloadable) themes = themes.filter(theme => theme.state != themeStates.DOWNLOADABLE); + if (this.props.searchString) { let searchString = this.props.searchString.toUpperCase(); themes = themes.filter(theme => theme.search.indexOf(searchString) > -1); } - - BDFDB.ArrayUtils.keySort(themes, (!this.props.sortKey || this.props.sortKey == "NEW" && !themes.some(theme => theme.new == newStates.NEW) ? Object.keys(sortKeys)[0] : this.props.sortKey).toLowerCase()); + + const sortKey = !this.props.sortKey || this.props.sortKey == "NEW" && !themes.some(theme => theme.new) ? Object.keys(sortKeys)[0] : this.props.sortKey; + BDFDB.ArrayUtils.keySort(themes, sortKey.toLowerCase()); if (this.props.orderKey == "DESC") themes.reverse(); + if (reverseSorts.includes(sortKey)) themes.reverse(); return themes; } openPreview() { @@ -275,16 +271,12 @@ module.exports = (_ => { if (this.props.currentGenerator) this.runInPreview({ reason: "NewTheme", checked: true, - css: (loadedThemes[this.props.currentGenerator] || {}).fullCSS + css: (generatorThemes.find(t => t.id == this.props.currentGenerator) || {}).fullCSS }); if (this.props.useLightMode) this.runInPreview({ reason: "DarkLight", checked: true }); - if (this.props.useNormalizer) this.runInPreview({ - reason: "Normalize", - checked: true - }); if (this.props.useCustomCSS) this.runInPreview({ reason: "CustomCSS", checked: true @@ -353,11 +345,41 @@ module.exports = (_ => { break; } } + createThemeFile(name, filename, body) { + return new Promise(callback => BDFDB.LibraryRequires.fs.writeFile(BDFDB.LibraryRequires.path.join(BDFDB.BDUtils.getThemesFolder(), filename), body, error => { + if (error) BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("save_fail", `Theme "${name}"`), {type: "danger"}); + else { + BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("save_success", `Theme "${name}"`), {type: "success"}); + if (_this.settings.general.rnmStart) BDFDB.TimeUtils.timeout(_ => { + if (BDFDB.BDUtils.isThemeEnabled(name) == false) { + BDFDB.BDUtils.enableTheme(name, false); + BDFDB.LogUtils.log(BDFDB.LanguageUtils.LibraryStringsFormat("toast_plugin_started", name), _this); + } + }, 3000); + callback(); + } + })); + } + generateTheme(css) { + if (!css || !BDFDB.ObjectUtils.is(this.props.generatorValues)) return ""; + for (let inputId in this.props.generatorValues) if (this.props.generatorValues[inputId].value && this.props.generatorValues[inputId].value.trim() && this.props.generatorValues[inputId].value != this.props.generatorValues[inputId].oldValue) css = css.replace(new RegExp(`--${BDFDB.StringUtils.regEscape(inputId)}(\\s*):(\\s*)${BDFDB.StringUtils.regEscape(this.props.generatorValues[inputId].oldValue)}`,"g"),`--${inputId}$1: $2${this.props.generatorValues[inputId].value}`); + return css; + } + createFixerCSS(body) { + let oldCSS = body.replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/\r/g, "\\r").split("REPLACE_CLASS_"); + let newCSS = oldCSS.shift(); + for (let str of oldCSS) { + let reg = /([A-z0-9_]+)(.*)/.exec(str); + newCSS += BDFDB.dotCN[reg[1]] + reg[2]; + } + return newCSS.replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\r/g, "\r"); + } render() { let automaticLoading = BDFDB.BDUtils.getSettings(BDFDB.BDUtils.settingsIds.automaticLoading); if (!this.props.tab) this.props.tab = "Themes"; - this.props.entries = (!loading.is && !BDFDB.ObjectUtils.isEmpty(loadedThemes) ? this.filterThemes() : []).map(theme => BDFDB.ReactUtils.createElement(RepoCardComponent, { - theme: theme + + this.props.entries = (!loading.is && grabbedThemes.length ? this.filterThemes() : []).map(theme => BDFDB.ReactUtils.createElement(RepoCardComponent, { + data: theme })).filter(n => n); BDFDB.TimeUtils.timeout(_ => { @@ -391,16 +413,10 @@ module.exports = (_ => { children: `${BDFDB.LanguageUtils.LibraryStringsFormat("loading", "Theme Repo")} - ${BDFDB.LanguageUtils.LibraryStrings.please_wait}` }) ] - }) : BDFDB.ReactUtils.forceStyle(BDFDB.ReactUtils.createElement("div", { - className: BDFDB.disCN._repolist, - style: { - display: "flex", - flexDirection: "column", - margin: "unset", - width: "unset" - }, + }) : BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycards, children: this.props.entries - }), ["display", "flex-direction", "margin", "width"]) + }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalTabContent, { tab: "Generator", @@ -413,9 +429,10 @@ module.exports = (_ => { label: "Choose a Generator Theme", basis: "60%", value: this.props.currentGenerator || "-----", - options: [{value: "-----", label: "-----"}, nativeCSSvars && {value: "nativediscord", label: "Discord"}].concat((generatorThemes).map(url => ({value: url, label: (loadedThemes[url] || {}).name || "-----"})).sort((x, y) => (x.label < y.label ? -1 : x.label > y.label ? 1 : 0))).filter(n => n), + options: [{value: "-----", label: "-----"}, nativeCSSvars && {value: "nativediscord", label: "Discord"}].concat(generatorThemes.map(t => ({value: t.id, label: t.name || "-----"})).sort((x, y) => (x.label < y.label ? -1 : x.label > y.label ? 1 : 0))).filter(n => n), onChange: value => { - if (loadedThemes[value] || value == "nativediscord") { + let generatorTheme = generatorThemes.find(t => t.id == value); + if (generatorTheme || value == "nativediscord") { if (this.props.currentGenerator) forceRerenderGenerator = true; this.props.currentGenerator = value; this.props.currentGeneratorIsNative = value == "nativediscord"; @@ -430,7 +447,7 @@ module.exports = (_ => { if (preview) this.runInPreview({ reason: "NewTheme", checked: true, - css: (loadedThemes[value.value] || {}).fullCSS + css: (generatorTheme || {}).fullCSS }); else this.openPreview(); BDFDB.ReactUtils.forceUpdate(this); @@ -451,10 +468,11 @@ module.exports = (_ => { children: "Download", onClick: _ => { if (this.props.currentGeneratorIsNative) { - _this.createThemeFile("Discord.theme.css", `/**\n * @name Discord\n * @description Allow you to easily customize Discord's native Look \n * @author DevilBro\n * @version 1.0.0\n * @authorId 278543574059057154\n * @invite Jx3TjNS\n * @donate https://www.paypal.me/MircoWittrien\n * @patreon https://www.patreon.com/MircoWittrien\n */\n\n` + _this.generateTheme(nativeCSSvars, this.props.generatorValues)); + this.createThemeFile("Discord", "Discord.theme.css", `/**\n * @name Discord\n * @description Allow you to easily customize Discord's native Look \n * @author DevilBro\n * @version 1.0.0\n * @authorId 278543574059057154\n * @invite Jx3TjNS\n * @donate https://www.paypal.me/MircoWittrien\n * @patreon https://www.patreon.com/MircoWittrien\n */\n\n` + this.generateTheme(nativeCSSvars)); } - else if (loadedThemes[this.props.currentGenerator]) { - _this.createThemeFile(loadedThemes[this.props.currentGenerator].name + ".theme.css", _this.generateTheme(loadedThemes[this.props.currentGenerator].fullCSS, this.props.generatorValues)); + else { + let generatorTheme = generatorThemes.find(t => t.id == this.props.currentGenerator); + if (generatorTheme) this.createThemeFile(generatorTheme.name, generatorTheme.name + ".theme.css", this.generateTheme(generatorTheme.fullCSS)); } } }), @@ -462,7 +480,8 @@ module.exports = (_ => { className: BDFDB.disCN.marginbottom20 }), (_ => { - let vars = this.props.currentGeneratorIsNative ? nativeCSSvars.split(".theme-dark, .theme-light") : loadedThemes[this.props.currentGenerator].fullCSS.split(":root"); + let generatorTheme = generatorThemes.find(t => t.id == this.props.currentGenerator); + let vars = this.props.currentGeneratorIsNative ? nativeCSSvars.split(".theme-dark, .theme-light") : ((generatorTheme || {}).fullCSS || "").split(":root"); if (vars.length < 2) return null; vars = vars[1].replace(/\t\(/g, " (").replace(/\r|\t| {2,}/g, "").replace(/\/\*\n*((?!\/\*|\*\/).|\n)*\n+((?!\/\*|\*\/).|\n)*\n*\*\//g, "").replace(/\n\/\*.*?\*\//g, "").replace(/\n/g, ""); vars = vars.split("{"); @@ -514,7 +533,7 @@ module.exports = (_ => { if (preview) this.runInPreview({ reason: "NewTheme", checked: true, - css: _this.generateTheme(this.props.currentGeneratorIsNative ? nativeCSSvars : loadedThemes[this.props.currentGenerator].fullCSS, this.props.generatorValues) + css: this.generateTheme(this.props.currentGeneratorIsNative ? nativeCSSvars : (generatorTheme || {}).fullCSS) }); }, 1000); } @@ -531,69 +550,79 @@ module.exports = (_ => { open: this.props.tab == BDFDB.LanguageUtils.LanguageStrings.SETTINGS, render: false, children: [ - !automaticLoading && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { - className: BDFDB.disCN.marginbottom20, - children: BDFDB.ReactUtils.createElement("div", { - className: BDFDB.disCNS.settingsrowtitle + BDFDB.disCNS.settingsrowtitledefault + BDFDB.disCN.cursordefault, - children: "To experience ThemeRepo in the best way. I would recommend you to enable BD intern reload function, that way all downloaded files are loaded into Discord without the need to reload." - }) + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, { + title: "Show following Themes", + children: Object.keys(_this.defaults.filters).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + type: "Switch", + theme: this, + keys: ["filters", key], + label: _this.defaults.filters[key].description, + value: _this.settings.filters[key], + onChange: value => { + this.props[key] = _this.settings.filters[key] = value; + BDFDB.ReactUtils.forceUpdate(this); + } + })) }), - BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + Object.keys(_this.defaults.general).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { type: "Switch", - margin: 20, - label: "Preview in light mode", - value: this.props.useLightMode, + theme: this, + keys: ["general", key], + label: _this.defaults.general[key].description, + note: key == "rnmStart" && !automaticLoading && "Automatic Loading has to be enabled", + disabled: key == "rnmStart" && !automaticLoading, + value: _this.settings.general[key], onChange: value => { - this.props.useLightMode = value; - if (preview) this.runInPreview({ - reason: "DarkLight", - checked: this.props.useLightMode - }); - } - }), - BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { - type: "Switch", - margin: 20, - label: "Preview with useNormalizer classes", - value: this.props.useNormalizer, - onChange: value => { - this.props.useNormalizer = value; - if (preview) this.runInPreview({ - reason: "Normalize", - checked: this.props.useNormalizer - }); - } - }), - BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { - type: "Switch", - margin: 20, - label: "Include Custom CSS in Preview", - value: this.props.useCustomCSS, - onChange: value => { - this.props.useCustomCSS = value; - let customCSS = document.querySelector("style#customcss"); - if (preview && customCSS && customCSS.innerText.length > 0) this.runInPreview({ - reason: "CustomCSS", - checked: this.props.useCustomCSS, - css: customCSS.innerText - }); - } - }), - BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { - type: "Switch", - margin: 20, - label: "Include ThemeFixer CSS in Preview", - value: this.props.useThemeFixer, - onChange: value => { - this.props.useThemeFixer = value; - BDFDB.LibraryRequires.request("https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/_res/ThemeFixer.css", (error, response, body) => { - if (preview) this.runInPreview({ - reason: "ThemeFixer", - checked: this.props.useThemeFixer, - css: _this.createFixerCSS(body) - }); - }); + _this.settings.general[key] = value; + BDFDB.ReactUtils.forceUpdate(this); } + })), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, { + title: "Preview Settings", + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Switch", + label: "Use Light Mode", + value: this.props.useLightMode, + onChange: value => { + this.props.useLightMode = value; + if (preview) this.runInPreview({ + reason: "DarkLight", + checked: this.props.useLightMode + }); + } + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Switch", + label: "Include Custom CSS", + value: this.props.useCustomCSS, + onChange: value => { + this.props.useCustomCSS = value; + let customCSS = document.querySelector("style#customcss"); + if (preview && customCSS && customCSS.innerText.length > 0) this.runInPreview({ + reason: "CustomCSS", + checked: this.props.useCustomCSS, + css: customCSS.innerText + }); + } + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Switch", + margin: 20, + label: "Include ThemeFixer", + value: this.props.useThemeFixer, + onChange: value => { + this.props.useThemeFixer = value; + BDFDB.LibraryRequires.request("https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/_res/ThemeFixer.css", (error, response, body) => { + if (preview) this.runInPreview({ + reason: "ThemeFixer", + checked: this.props.useThemeFixer, + css: this.createFixerCSS(body) + }); + }); + } + }) + ] }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { type: "Button", @@ -602,24 +631,17 @@ module.exports = (_ => { children: "Download", onClick: _ => { BDFDB.LibraryRequires.request("https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/_res/ThemeFixer.css", (error, response, body) => { - _this.createThemeFile("ThemeFixer.theme.css", `/**\n * @name ThemeFixer\n * @description ThemeFixerCSS for transparent themes\n * @author DevilBro\n * @version 1.0.3\n * @authorId 278543574059057154\n * @invite Jx3TjNS\n * @donate https://www.paypal.me/MircoWittrien\n * @patreon https://www.patreon.com/MircoWittrien\n */\n\n` + _this.createFixerCSS(body)); + this.createThemeFile("ThemeFixer", "ThemeFixer.theme.css", `/**\n * @name ThemeFixer\n * @description ThemeFixerCSS for transparent themes\n * @author DevilBro\n * @version 1.0.3\n * @authorId 278543574059057154\n * @invite Jx3TjNS\n * @donate https://www.paypal.me/MircoWittrien\n * @patreon https://www.patreon.com/MircoWittrien\n */\n\n` + this.createFixerCSS(body)); }); } }), - Object.keys(modalSettings).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + !automaticLoading && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { className: BDFDB.disCN.marginbottom20, - type: "Switch", - plugin: _this, - keys: ["modalSettings", key], - label: _this.defaults.modalSettings[key].description, - note: key == "rnmStart" && !automaticLoading && "Automatic Loading has to be enabled", - disabled: key == "rnmStart" && !automaticLoading, - value: this.props[key], - onChange: value => { - this.props[key] = modalSettings[key] = value; - BDFDB.ReactUtils.forceUpdate(this); - } - })) + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCNS.settingsrowtitle + BDFDB.disCNS.settingsrowtitledefault + BDFDB.disCN.cursordefault, + children: "To experience Theme Repo in the best way. I would recommend you to enable BD's intern Automatic-Loading Feature, that way all downloaded Files are loaded into Discord without the need to reload." + }) + }) ].flat(10).filter(n => n) }) ]; @@ -628,177 +650,228 @@ module.exports = (_ => { const RepoCardComponent = class ThemeCard extends BdApi.React.Component { render() { - let buttonConfig = buttonData[(Object.entries(themeStates).find(n => n[1] == this.props.theme.state) || [])[0]]; - return buttonConfig && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.AddonCard, { - icon: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { - className: BDFDB.disCN._repoicon, - nativeClass: true, - iconSVG: `` - }), - data: this.props.theme, - controls: [ - this.props.theme.new == newStates.NEW && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.TextBadge, { - style: { - borderRadius: 3, - textTransform: "uppercase", - background: BDFDB.DiscordConstants.Colors.STATUS_YELLOW - }, - text: BDFDB.LanguageUtils.LanguageStrings.NEW - }), - BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FavButton, { - className: BDFDB.disCN._repocontrolsbutton, - isFavorite: this.props.theme.fav == favStates.FAVORIZED, - onClick: value => { - this.props.theme.fav = value ? favStates.FAVORIZED : favStates.NOT_FAVORIZED; - if (value) favorites.push(this.props.theme.url); - else BDFDB.ArrayUtils.remove(favorites, this.props.theme.url, true); - BDFDB.DataUtils.save(favorites, _this, "favorites"); - } - }), - !isBeta && BDFDB.ReactUtils.createElement("div", { - className: BDFDB.disCN._repocontrolsbutton, - children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { - text: "Go to Source", - children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { - name: BDFDB.LibraryComponents.SvgIcon.Names.GITHUB, - className: BDFDB.disCN._repoicon, - onClick: _ => { - let gitUrl = null; - if (this.props.theme.url.indexOf("https://raw.githubusercontent.com") == 0) { - let temp = this.props.theme.url.replace("//raw.githubusercontent", "//github").split("/"); - temp.splice(5, 0, "blob"); - gitUrl = temp.join("/"); - } - else if (this.props.theme.url.indexOf("https://gist.githubusercontent.com/") == 0) { - gitUrl = this.props.theme.url.replace("//gist.githubusercontent", "//gist.github").split("/raw/")[0]; - } - if (gitUrl) BDFDB.DiscordUtils.openLink(gitUrl); - } + const thumbnailUrl = this.props.data.thumbnailUrl && `https://betterdiscord.app${this.props.data.thumbnailUrl}`; + return BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycard, + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycardheader, + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycardcoverwrapper, + children: [ + thumbnailUrl && BDFDB.ReactUtils.createElement("img", { + className: BDFDB.disCN.discoverycardcover, + src: thumbnailUrl, + onClick: _ => { + let img = document.createElement("img"); + img.addEventListener("load", function() { + BDFDB.LibraryModules.ModalUtils.openModal(modalData => { + return BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalRoot, Object.assign({ + className: BDFDB.disCN.imagemodal + }, modalData, { + size: BDFDB.LibraryComponents.ModalComponents.ModalSize.DYNAMIC, + "aria-label": BDFDB.LanguageUtils.LanguageStrings.IMAGE, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ImageModal, { + animated: false, + src: thumbnailUrl, + original: thumbnailUrl, + width: this.width, + height: this.height, + className: BDFDB.disCN.imagemodalimage, + shouldAnimate: true, + renderLinkComponent: props => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Anchor, props) + }) + }), true); + }); + }); + img.src = thumbnailUrl; + } + }), + this.props.data.new && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.TextBadge, { + className: BDFDB.disCN.discoverycardcoverbadge, + style: { + borderRadius: 3, + textTransform: "uppercase", + background: BDFDB.DiscordConstants.Colors.STATUS_YELLOW + }, + text: BDFDB.LanguageUtils.LanguageStrings.NEW + }) + ] + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycardiconwrapper, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycardicon, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + nativeClass: true, + iconSVG: `` + }) + }) }) - }) + ] }), - BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Switch, { - value: list && list.props.currentTheme && list.props.currentTheme.url == this.props.theme.url, - onChange: value => { - if (!list) return; - - if (list.props.currentTheme) for (let ins of BDFDB.ReactUtils.findOwner(BDFDB.ObjectUtils.get(this, `${BDFDB.ReactUtils.instanceKey}.return`), {name: "ThemeCard", all: true}).filter(ins => ins && ins.props && ins.props.theme && ins.props.theme.url == list.props.currentTheme.url)) BDFDB.ReactUtils.forceUpdate(ins); - - if (value) list.props.currentTheme = this.props.theme; - else delete list.props.currentTheme; - delete list.props.currentGenerator; - delete list.props.generatorValues; - - if (preview) list.runInPreview({ - reason: "NewTheme", - checked: value, - css: this.props.theme.css - }); - else list.openPreview(); - - BDFDB.ReactUtils.forceUpdate(this); - } - }) - ], - links: isBeta && [{ - label: "Source", - icon: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { - name: BDFDB.LibraryComponents.SvgIcon.Names.GITHUB, - nativeClass: true, - width: 18, - height: 18 - }), - onClick: _ => { - let gitUrl = null; - if (this.props.theme.url.indexOf("https://raw.githubusercontent.com") == 0) { - let temp = this.props.theme.url.replace("//raw.githubusercontent", "//github").split("/"); - temp.splice(5, 0, "blob"); - gitUrl = temp.join("/"); - } - else if (this.props.theme.url.indexOf("https://gist.githubusercontent.com/") == 0) { - gitUrl = this.props.theme.url.replace("//gist.githubusercontent", "//gist.github").split("/raw/")[0]; - } - if (gitUrl) BDFDB.DiscordUtils.openLink(gitUrl); - } - }], - buttons: isBeta ? [ - this.props.theme.state != themeStates.DOWNLOADABLE && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { - text: BDFDB.LanguageUtils.LanguageStrings.DELETE, - children: BDFDB.ReactUtils.createElement("div", { - className: BDFDB.disCNS._repobutton + BDFDB.disCNS._repocontrolsbutton + BDFDB.disCN._repobuttondanger, - onClick: _ => { - _this.removeTheme(this.props.theme); - _this.deleteThemeFile(this.props.theme); - this.props.theme.state = themeStates.DOWNLOADABLE; - BDFDB.ReactUtils.forceUpdate(this); - }, - children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { - name: BDFDB.LibraryComponents.SvgIcon.Names.TRASH, - nativeClass: true, - color: "#FFFFFF", - width: 20, - height: 20 + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycardinfo, + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycardtitle, + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycardname, + children: this.props.data.name + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + text: BDFDB.LanguageUtils.LanguageStrings.FORM_LABEL_VIDEO_PREVIEW, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Clickable, { + className: BDFDB.disCN.discoverycardtitlebutton, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + nativeClass: true, + width: 16, + height: 16, + name: BDFDB.LibraryComponents.SvgIcon.Names.EYE + }) + }), + onClick: _ => { + if (!list) return; + + list.props.currentTheme = this.props.data; + delete list.props.currentGenerator; + delete list.props.generatorValues; + + if (preview) list.runInPreview({ + reason: "NewTheme", + checked: value, + css: this.props.data.css + }); + else list.openPreview(); + + BDFDB.ReactUtils.forceUpdate(this); + } + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FavButton, { + className: BDFDB.disCN.discoverycardtitlebutton, + isFavorite: this.props.data.fav, + onClick: value => { + this.props.data.fav = value && 1; + if (value) favorites.push(this.props.data.id); + else BDFDB.ArrayUtils.remove(favorites, this.props.data.id, true); + BDFDB.DataUtils.save(BDFDB.ArrayUtils.numSort(favorites).join(" "), _this, "favorites"); + } + }) + ] + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycardauthor, + children: `by ${this.props.data.author}` + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Scrollers.Thin, { + className: BDFDB.disCN.discoverycarddescription, + children: this.props.data.description + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycardfooter, + children: [ + BDFDB.ArrayUtils.is(this.props.data.tags) && this.props.data.tags.length && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycardtags, + children: this.props.data.tags.map(tag => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.TextBadge, { + className: BDFDB.disCN.discoverycardtag, + style: {background: "var(--background-accent)"}, + text: tag + })) + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycardcontrols, + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycardstats, + children: [ + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycardstat, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.discoverycardstaticon, + name: BDFDB.LibraryComponents.SvgIcon.Names.DOWNLOAD + }), + this.props.data.downloads + ] + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycardstat, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.discoverycardstaticon, + name: BDFDB.LibraryComponents.SvgIcon.Names.HEART + }), + this.props.data.likes + ] + }) + ] + }), + BDFDB.ReactUtils.createElement(RepoCardDownloadButtonComponent, { + ...buttonData[(Object.entries(themeStates).find(n => n[1] == this.props.data.state) || [])[0]], + installed: this.props.data.state == themeStates.INSTALLED, + onDownload: _ => { + list && BDFDB.LibraryRequires.request(this.props.data.rawSourceUrl, (error, response, body) => { + if (error) BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("download_fail", `Theme "${this.props.data.name}"`), {type: "danger"}); + else list.createThemeFile(this.props.data.name, this.props.data.rawSourceUrl.split("/").pop(), body).then(_ => { + this.props.data.state = themeStates.INSTALLED; + BDFDB.ReactUtils.forceUpdate(this); + }); + }); + }, + onDelete: _ => { + BDFDB.LibraryRequires.fs.unlink(BDFDB.LibraryRequires.path.join(BDFDB.BDUtils.getThemesFolder(), this.props.data.rawSourceUrl.split("/").pop()), error => { + if (error) BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("delete_fail", `Theme "${this.props.data.name}"`), {type: "danger"}); + else { + BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("delete_success", `Theme "${this.props.data.name}"`)); + this.props.data.state = themeStates.DOWNLOADABLE; + BDFDB.ReactUtils.forceUpdate(this); + } + }); + } + }) + ] + }) + ] }) - }) - }), - BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { - text: BDFDB.LanguageUtils.LibraryStrings[buttonConfig.text], - children: BDFDB.ReactUtils.createElement("div", { - className: BDFDB.disCNS._repobutton + BDFDB.disCN._repocontrolsbutton, - style: {backgroundColor: BDFDB.DiscordConstants.Colors[buttonConfig.backgroundColor] || buttonConfig.backgroundColor}, - onClick: _ => { - _this.downloadTheme(this.props.theme); - if (list && list.props.rnmStart) BDFDB.TimeUtils.timeout(_ => { - if (this.props.theme.state == themeStates.UPDATED) _this.applyTheme(this.props.theme); - }, 3000); - this.props.theme.state = themeStates.UPDATED; - BDFDB.ReactUtils.forceUpdate(this); - }, - children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { - name: BDFDB.LibraryComponents.SvgIcon.Names[buttonConfig.icon], - nativeClass: true, - color: "#FFFFFF", - width: 20, - height: 20 - }) - }) - }) - ] : [ - this.props.theme.state != themeStates.DOWNLOADABLE && BDFDB.ReactUtils.createElement("div", { - className: BDFDB.disCN._repocontrolsbutton, - children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { - text: BDFDB.LanguageUtils.LanguageStrings.DELETE, - children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { - name: BDFDB.LibraryComponents.SvgIcon.Names.NOVA_TRASH, - className: BDFDB.disCN._repoicon, - onClick: (e, instance) => { - _this.removeTheme(this.props.theme); - _this.deleteThemeFile(this.props.theme); - this.props.theme.state = themeStates.DOWNLOADABLE; - BDFDB.ReactUtils.forceUpdate(this); - } - }) - }) - }), - BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Button, { - size: BDFDB.LibraryComponents.Button.Sizes.MIN, - color: BDFDB.LibraryComponents.Button.Colors[buttonConfig.colorClass], - style: {backgroundColor: BDFDB.DiscordConstants.Colors[buttonConfig.backgroundColor] || buttonConfig.backgroundColor}, - children: BDFDB.LanguageUtils.LibraryStrings[buttonConfig.text], - onClick: (e, instance) => { - _this.downloadTheme(this.props.theme); - if (list && list.props.rnmStart) BDFDB.TimeUtils.timeout(_ => { - if (this.props.theme.state == themeStates.UPDATED) _this.applyTheme(this.props.theme); - }, 3000); - this.props.theme.state = themeStates.UPDATED; - BDFDB.ReactUtils.forceUpdate(this); - } + ] }) ] }); } }; + const RepoCardDownloadButtonComponent = class ThemeCardDownloadButton extends BdApi.React.Component { + render() { + return BDFDB.ReactUtils.createElement("button", { + className: BDFDB.disCN.discoverycardbutton, + style: {backgroundColor: this.props.delete ? BDFDB.DiscordConstants.Colors.STATUS_RED : (BDFDB.DiscordConstants.Colors[this.props.backgroundColor] || this.props.backgroundColor)}, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.discoverycardstaticon, + width: 16, + height: 16, + name: this.props.delete ? BDFDB.LibraryComponents.SvgIcon.Names.TRASH : BDFDB.LibraryComponents.SvgIcon.Names[this.props.icon] + }), + this.props.delete ? BDFDB.LanguageUtils.LanguageStrings.APPLICATION_CONTEXT_MENU_UNINSTALL : (BDFDB.LanguageUtils.LibraryStringsCheck[this.props.text] ? BDFDB.LanguageUtils.LibraryStrings[this.props.text] : BDFDB.LanguageUtils.LanguageStrings[this.props.text]) + ], + onClick: _ => { + if (this.props.delete) typeof this.props.onDelete == "function" && this.props.onDelete(); + else typeof this.props.onDelete == "function" && this.props.onDownload(); + }, + onMouseEnter: this.props.installed && (_ => { + this.props.delete = true; + BDFDB.ReactUtils.forceUpdate(this); + }), + onMouseLeave: this.props.installed && (_ => { + this.props.delete = false; + BDFDB.ReactUtils.forceUpdate(this); + }) + }); + } + }; + const RepoListHeaderComponent = class ThemeListHeader extends BdApi.React.Component { componentDidMount() { header = this; @@ -818,14 +891,14 @@ module.exports = (_ => { children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormTitle, { tag: BDFDB.LibraryComponents.FormComponents.FormTitle.Tags.H2, className: BDFDB.disCN.marginreset, - children: `Theme Repo — ${loading.is ? 0 : this.props.amount || 0}/${loading.is ? 0 : Object.keys(loadedThemes).length}` + children: `Theme Repo — ${loading.is ? 0 : this.props.amount || 0}/${loading.is ? 0 : grabbedThemes.length}` }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SearchBar, { autoFocus: true, query: this.props.searchString, - onChange: value => { + onChange: (value, instance) => { if (loading.is) return; BDFDB.TimeUtils.clear(searchTimeout); searchTimeout = BDFDB.TimeUtils.timeout(_ => { @@ -875,11 +948,11 @@ module.exports = (_ => { label: sortKeys[this.props.sortKey], value: this.props.sortKey }, - options: Object.keys(sortKeys).filter(n => n != "NEW" || Object.keys(loadedThemes).some(p => !cachedThemes.includes(p))).map(key => ({ + options: Object.keys(sortKeys).filter(n => n != "NEW" || grabbedThemes.some(t => !cachedThemes.includes(t.id))).map(key => ({ label: sortKeys[key], value: key })), - onChange: (key, instance) => { + onChange: key => { this.props.sortKey = list.props.sortKey = key; BDFDB.ReactUtils.forceUpdate(this, list); } @@ -896,7 +969,7 @@ module.exports = (_ => { label: BDFDB.LanguageUtils.LibraryStrings[orderKeys[key]], value: key })), - onChange: (key, instance) => { + onChange: key => { this.props.orderKey = list.props.orderKey = key; BDFDB.ReactUtils.forceUpdate(this, list); } @@ -908,32 +981,30 @@ module.exports = (_ => { }); } }; + return class ThemeRepo extends Plugin { onLoad () { _this = this; loading = {is: false, timeout: null, amount: 0}; - + cachedThemes = []; grabbedThemes = []; - foundThemes = []; - loadedThemes = {}; generatorThemes = []; this.defaults = { - settings: { - keepOnTop: {value: false, description: "Keep the Preview Window always on top"}, - notifyOutdated: {value: true, description: "Get a Notification when one of your Themes is outdated"}, - notifyNewentries: {value: true, description: "Get a Notification when there are new Entries in the Repo"} + general: { + notifyOutdated: {value: true, description: "Get a Notification when one of your Themes is outdated"}, + notifyNewEntries: {value: true, description: "Get a Notification when there are new Entries in the Repo"}, + rnmStart: {value: true, description: "Start Theme after Download"} }, - modalSettings: { - updated: {value: true, modify: true, description: "Show updated Themes",}, - outdated: {value: true, modify: true, description: "Show outdated Themes"}, - downloadable: {value: true, modify: true, description: "Show downloadable Themes"}, - rnmStart: {value: true, modify: false, description: "Apply Theme after Download"} + filters: { + updated: {value: true, description: "Updated"}, + outdated: {value: true, description: "Outdated"}, + downloadable: {value: true, description: "Downloadable"}, } }; - + this.patchedModules = { before: { SettingsView: "render" @@ -1034,6 +1105,9 @@ module.exports = (_ => { } onStart () { + // REMOVE 01.05.2021 + BDFDB.DataUtils.remove(this, "newentriesdata"); + this.forceUpdateAll(); this.loadThemes(); @@ -1049,83 +1123,7 @@ module.exports = (_ => { BDFDB.DOMUtils.remove(BDFDB.dotCN._themereponotice, BDFDB.dotCN._themerepoloadingicon); } - - getSettingsPanel (collapseStates = {}) { - let customUrl = ""; - let settingsPanel, settingsItems = []; - - settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { - title: "Settings", - collapseStates: collapseStates, - children: Object.keys(settings).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { - type: "Switch", - plugin: this, - keys: ["settings", key], - label: this.defaults.settings[key].description, - value: settings[key] - })) - })); - settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { - title: "Custom Themes", - collapseStates: collapseStates, - children: [ - BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { - title: "Add Theme:", - tag: BDFDB.LibraryComponents.FormComponents.FormTitleTags.H3, - className: BDFDB.disCNS.margintop4 + BDFDB.disCN.marginbottom8, - children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { - align: BDFDB.LibraryComponents.Flex.Align.CENTER, - children: [ - BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { - children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { - placeholder: "Insert Raw Github Link of Theme (https://raw.githubusercontent.com/...)", - onChange: value => {customUrl = value.trim();} - }) - }), - BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Button, { - onClick: _ => { - if (customUrl) { - customList.push(customUrl); - BDFDB.DataUtils.save(BDFDB.ArrayUtils.removeCopies(customList), this, "custom"); - BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates); - } - }, - children: BDFDB.LanguageUtils.LanguageStrings.ADD - }) - ] - }) - }), - customList.length ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, { - title: "Custom Theme List:", - className: BDFDB.disCNS.margintop8 + BDFDB.disCN.marginbottom20, - children: customList.map(url => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Card, { - children: url, - onRemove: _ => { - BDFDB.ArrayUtils.remove(customList, url, true); - BDFDB.DataUtils.save(customList, this, "custom"); - BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates); - } - })) - }) : null, - BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { - type: "Button", - margin: 0, - color: BDFDB.LibraryComponents.Button.Colors.RED, - label: "Remove all custom added Themes", - onClick: _ => { - BDFDB.ModalUtils.confirm(this, "Are you sure you want to remove all added Themes from your own List", _ => { - BDFDB.DataUtils.save([], this, "custom"); - BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates); - }); - }, - children: BDFDB.LanguageUtils.LanguageStrings.REMOVE - }) - ].flat(10).filter(n => n) - })); - - return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, settingsItems); - } - + onSettingsClosed () { if (this.SettingsUpdated) { delete this.SettingsUpdated; @@ -1134,12 +1132,8 @@ module.exports = (_ => { } forceUpdateAll () { - settings = BDFDB.DataUtils.get(this, "settings"); - modalSettings = BDFDB.DataUtils.get(this, "modalSettings"); favorites = BDFDB.DataUtils.load(this, "favorites"); - favorites = BDFDB.ArrayUtils.is(favorites) ? favorites : []; - customList = BDFDB.DataUtils.load(this, "custom"); - customList = BDFDB.ArrayUtils.is(customList) ? customList : []; + favorites = (typeof favorites == "string" ? favorites.split(" ") : []).map(n => parseInt(n)).filter(n => !isNaN(n)); BDFDB.PatchUtils.forceAllUpdates(this); } @@ -1166,7 +1160,7 @@ module.exports = (_ => { section: "themerepo", label: "Theme Repo", element: _ => { - let options = Object.assign({}, modalSettings); + let options = Object.assign({}, this.settings.filters); options.updated = options.updated && !showOnlyOutdated; options.outdated = options.outdated || showOnlyOutdated; options.downloadable = options.downloadable && !showOnlyOutdated; @@ -1174,7 +1168,6 @@ module.exports = (_ => { options.sortKey = forcedSort || Object.keys(sortKeys)[0]; options.orderKey = forcedOrder || Object.keys(orderKeys)[0]; options.useLightMode = BDFDB.DiscordUtils.getTheme() == BDFDB.disCN.themelight; - options.useNormalizer = BDFDB.BDUtils.getSettings(BDFDB.BDUtils.settingsIds.normalizedClasses); options.useThemeFixer = false; options.useCustomCSS = false; @@ -1204,21 +1197,6 @@ module.exports = (_ => { } } - createFixerCSS (body) { - let oldcss = body.replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/\r/g, "\\r").split("REPLACE_CLASS_"); - let newcss = oldcss.shift(); - for (let str of oldcss) { - let reg = /([A-z0-9_]+)(.*)/.exec(str); - newcss += BDFDB.dotCN[reg[1]] + reg[2]; - } - return newcss.replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\r/g, "\r"); - } - - createGeneratorInputs (theme, generatorValues) { - if (!BDFDB.ObjectUtils.is(theme) || !BDFDB.ObjectUtils.is(generatorValues) || !theme.fullCSS) return null; - return inputRefs; - } - generateTheme (fullCSS, generatorValues) { if (!fullCSS || !BDFDB.ObjectUtils.is(generatorValues)) return ""; for (let inputId in generatorValues) if (generatorValues[inputId].value && generatorValues[inputId].value.trim() && generatorValues[inputId].value != generatorValues[inputId].oldValue) fullCSS = fullCSS.replace(new RegExp(`--${BDFDB.StringUtils.regEscape(inputId)}(\\s*):(\\s*)${BDFDB.StringUtils.regEscape(generatorValues[inputId].oldValue)}`,"g"),`--${inputId}$1: $2${generatorValues[inputId].value}`); @@ -1227,57 +1205,29 @@ module.exports = (_ => { loadThemes () { BDFDB.DOMUtils.remove(BDFDB.dotCN._themerepoloadingicon); - let getThemeInfo, outdated = 0, newEntries = 0, i = 0; - let tags = ["name", "description", "author", "version"]; - let newEntriesData = BDFDB.DataUtils.load(this, "newentriesdata"); - cachedThemes = (newEntriesData.urlbase64 ? atob(newEntriesData.urlbase64).split("\n") : []).concat(customList); - BDFDB.LibraryRequires.request("https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/_res/ThemeList.txt", (error, response, body) => { - if (!error && body) { - body = body.replace(/[\r\t]/g, ""); - BDFDB.DataUtils.save(btoa(body), this, "newentriesdata", "urlbase64"); - - loadedThemes = {}; - grabbedThemes = body.split("\n").filter(n => n); - foundThemes = grabbedThemes.concat(customList); - - loading = {is: true, timeout: BDFDB.TimeUtils.timeout(_ => { - BDFDB.TimeUtils.clear(loading.timeout); - if (this.started) { - if (loading.is && loading.amount < 4) BDFDB.TimeUtils.timeout(_ => {this.loadThemes();}, 10000); - loading = {is: false, timeout: null, amount: loading.amount}; - } - }, 1200000), amount: loading.amount+1}; - - let loadingIcon = BDFDB.DOMUtils.create(themeRepoIcon.replace(/COLOR_1/gi, "var(--bdfdb-blurple)").replace(/COLOR_2/gi, "#72767d")); - BDFDB.DOMUtils.addClass(loadingIcon, BDFDB.disCN._themerepoloadingicon); - loadingIcon.addEventListener("mouseenter", _ => { - BDFDB.TooltipUtils.create(loadingIcon, this.getLoadingTooltipText(), { - type: "left", - className: BDFDB.disCN._themerepoloadingtooltip, - delay: 500, - style: "max-width: unset;" - }); - }); - BDFDB.PluginUtils.addLoadingIcon(loadingIcon); - - BDFDB.ReactUtils.forceUpdate(list, header); - - getThemeInfo(_ => { - if (!this.started) { - BDFDB.TimeUtils.clear(loading.timeout); - return; - } + cachedThemes = BDFDB.DataUtils.load(this, "cached"); + cachedThemes = (typeof cachedThemes == "string" ? cachedThemes.split(" ") : []).map(n => parseInt(n)).filter(n => !isNaN(n)); + + let loadingIcon; + let newEntries = 0, outdatedEntries = 0, checkIndex = 0, checksRunning = 0, callbackCalled = false; + + const checkThemeVersion = _ => { + if (checksRunning > 20) return; + else if (grabbedThemes.every(t => t.loaded || !t.latestSourceUrl) || !this.started || !loading.is) { + if (!callbackCalled) { + callbackCalled = true; + if (!this.started) return BDFDB.TimeUtils.clear(loading.timeout); BDFDB.TimeUtils.clear(loading.timeout); BDFDB.DOMUtils.remove(loadingIcon, BDFDB.dotCN._themerepoloadingicon); loading = {is: false, timeout: null, amount: loading.amount}; BDFDB.LogUtils.log("Finished fetching Themes", this); - if (list) BDFDB.ReactUtils.forceUpdate(list); + BDFDB.ReactUtils.forceUpdate(list); - if (settings.notifyOutdated && outdated > 0) { + if (this.settings.general.notifyOutdated && outdatedEntries > 0) { let notice = document.querySelector(BDFDB.dotCN._themerepooutdatednotice); if (notice) notice.close(); - BDFDB.NotificationUtils.notice(this.labels.notice_outdated_themes.replace("{{var0}}", outdated), { + BDFDB.NotificationUtils.notice(this.labels.notice_outdated_themes.replace("{{var0}}", outdatedEntries), { type: "danger", className: BDFDB.disCNS._themereponotice + BDFDB.disCN._themerepooutdatednotice, customIcon: themeRepoIcon.replace(/COLOR_[0-9]+/gi, "currentColor"), @@ -1292,7 +1242,7 @@ module.exports = (_ => { }); } - if (settings.notifyNewEntries && newEntries > 0) { + if (this.settings.general.notifyNewEntries && newEntries > 0) { let notice = document.querySelector(BDFDB.dotCN._themereponewentriesnotice); if (notice) notice.close(); BDFDB.NotificationUtils.notice(this.labels.notice_new_themes.replace("{{var0}}", newEntries), { @@ -1311,174 +1261,145 @@ module.exports = (_ => { }); } - if (BDFDB.UserUtils.me.id == "278543574059057154") { - let notice = document.querySelector(BDFDB.dotCN._themerepofailnotice); - if (notice) notice.close(); - let wrongUrls = []; - for (let url of foundThemes) if (url && !loadedThemes[url] && !wrongUrls.includes(url)) wrongUrls.push(url); - if (wrongUrls.length) { - BDFDB.NotificationUtils.notice(this.labels.notice_failed_themes.replace("{{var0}}", wrongUrls.length), { - type: "danger", - className: BDFDB.disCNS._themereponotice + BDFDB.disCN._themerepofailnotice, - customIcon: themeRepoIcon.replace(/COLOR_[0-9]+/gi, "currentColor"), - buttons: [{ - contents: this.labels.list, - onClick: _ => { - let toast = BDFDB.NotificationUtils.toast(wrongUrls.join("\n"), {type: "danger"}); - toast.style.setProperty("overflow", "hidden"); - for (let url of wrongUrls) console.log(url); - } - }] - }); + BDFDB.LibraryRequires.request("https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/_res/GeneratorList.txt", (error, response, body) => { + if (!error && body) for (let id of "23 40 45 47".replace(/[\r\t]/g, "").split(" ").map(n => parseInt(n)).filter(n => n != null)) { + let theme = grabbedThemes.find(t => t.id == id); + if (theme) generatorThemes.push(theme); } - } - - BDFDB.LibraryRequires.request("https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/_res/GeneratorList.txt", (error3, response3, body3) => { - if (!error3 && body3) for (let url of body3.replace(/[\r\t]/g, "").split("\n").filter(n => n)) if (loadedThemes[url]) generatorThemes.push(url); }); - BDFDB.LibraryRequires.request(document.querySelector("head link[rel='stylesheet'][integrity]").href, (error3, response3, body3) => { - if (!error3 && body3) { - nativeCSS = body3; + BDFDB.LibraryRequires.request(document.querySelector("head link[rel='stylesheet'][integrity]").href, (error, response, body) => { + if (!error && body) { + nativeCSS = body; let theme = BDFDB.DiscordUtils.getTheme(); let vars = (nativeCSS.split(`.${theme}{`)[1] || "").split("}")[0]; nativeCSSvars = vars ? `.theme-dark, .theme-light {${vars}}` : ""; } else nativeCSS = nativeCSSvars = ""; }); - }); - } - }); - - getThemeInfo = (callback) => { - if (i >= foundThemes.length || !this.started || !loading.is) { - callback(); + } return; } - let url = foundThemes[i]; - BDFDB.LibraryRequires.request(url, (error, response, body) => { - if (!response) { - if (url && BDFDB.ArrayUtils.getAllIndexes(foundThemes, url).length < 2) foundThemes.push(url); - } - else if (body && body.indexOf("404: Not Found") != 0 && response.statusCode == 200) { - let theme = {}, text = body.trim(); - if ((text.split("*//").length > 1 || text.indexOf("/**") == 0) && text.split("\n").length > 1) { - let hasMETAline = text.replace(/\s/g, "").indexOf("//META{"); + else if (checkIndex > grabbedThemes.length) return; + + const theme = grabbedThemes[checkIndex++]; + if (!theme || !theme.latestSourceUrl) checkThemeVersion(); + else { + checksRunning++; + theme.rawSourceUrl = theme.latestSourceUrl.replace("https://github.com/", "https://raw.githubusercontent.com/").replace(/\/blob\/(.{32,})/i, "/$1") + BDFDB.LibraryRequires.request(theme.rawSourceUrl, (error, response, body) => { + if (body && body.indexOf("404: Not Found") != 0 && response.statusCode == 200) { + theme.name = BDFDB.LibraryModules.StringUtils.upperCaseFirstChar((/@name\s+([^\s^\t^\r^\n]+)|\/\/\**META.*["']name["']\s*:\s*["'](.+?)["']/i.exec(body) || []).filter(n => n)[1] || theme.name || ""); + theme.author = (/@author\s+(.+)|\/\/\**META.*["']author["']\s*:\s*["'](.+?)["']/i.exec(body) || []).filter(n => n)[1] || theme.author; + const version = (/@version\s+(.+)|\/\/\**META.*["']version["']\s*:\s*["'](.+?)["']/i.exec(body) || []).filter(n => n)[1]; + if (version) { + theme.version = version; + const installedTheme = this.getInstalledTheme(theme); + if (installedTheme && BDFDB.NumberUtils.compareVersions(version, this.getString(installedTheme.version))) outdatedEntries++; + } + let text = body.trim(); + let hasMETAline = text.replace(/\s/g, "").indexOf("//META{"), newMeta = ""; if (hasMETAline < 20 && hasMETAline > -1) { - let searchText = text.replace(/\s*:\s*/g, ":").replace(/\s*}\s*/g, "}"); - for (let tag of tags) { - let result = searchText.split('"' + tag + '":"'); - result = result.length > 1 ? result[1].split('",')[0].split('"}')[0] : null; - result = result && tag != "version" ? result.charAt(0).toUpperCase() + result.slice(1) : result; - theme[tag] = result ? result.trim() : result; - } - } - else { - let searchText = text.replace(/[\r\t| ]*\*\s*/g, "*"); - for (let tag of tags) { - let result = searchText.split('@' + tag + ' '); - result = result.length > 1 ? result[1].split('\n')[0] : null; - result = result && tag != "version" ? result.charAt(0).toUpperCase() + result.slice(1) : result; - theme[tag] = result ? result.trim() : result; - } - } - - let valid = true; - for (let tag of tags) if (theme[tag] === null) { - if (tag == "author") theme[tag] = "???"; - else valid = false; - } - if (valid) { - let newMeta = ""; - if (hasMETAline < 20 && hasMETAline > -1) { - let i = 0, j = 0, metaString = ""; - try { - for (let c of `{${text.split("{").slice(1).join("{")}`) { - metaString += c; - if (c == "{") i++; - else if (c == "}") j++; - if (i > 0 && i == j) break; - } - let metaObj = JSON.parse(metaString); - newMeta = "/**\n"; - for (let key in metaObj) newMeta += ` * @${key} ${metaObj[key]}\n`; - newMeta += "*/"; + let i = 0, j = 0, metaString = ""; + try { + for (let c of `{${text.split("{").slice(1).join("{")}`) { + metaString += c; + if (c == "{") i++; + else if (c == "}") j++; + if (i > 0 && i == j) break; } - catch (err) {newMeta = "";} + let metaObj = JSON.parse(metaString); + newMeta = "/**\n"; + for (let key in metaObj) newMeta += ` * @${key} ${metaObj[key]}\n`; + newMeta += "*/"; } - theme.fullCSS = [newMeta, newMeta ? text.split("\n").slice(1).join("\n") : text].filter(n => n).join("\n"); - theme.css = (hasMETAline < 20 && hasMETAline > -1 ? text.split("\n").slice(1).join("\n") : text).replace(/[\r|\n|\t]/g, ""); - theme.url = url; - loadedThemes[url] = theme; - let instTheme = BDFDB.BDUtils.getTheme(theme.name); - if (instTheme && instTheme.author && instTheme.author.toUpperCase() == theme.author.toUpperCase() && instTheme.version != theme.version) outdated++; - if (!cachedThemes.includes(url)) newEntries++; + catch (err) {newMeta = "";} } + theme.fullCSS = [newMeta, newMeta ? text.split("\n").slice(1).join("\n") : text].filter(n => n).join("\n"); + theme.css = (hasMETAline < 20 && hasMETAline > -1 ? text.split("\n").slice(1).join("\n") : text).replace(/[\r|\n|\t]/g, ""); } - } - i++; + if (!cachedThemes.includes(theme.id)) newEntries++; + + theme.loaded = true; + + let loadingTooltip = document.querySelector(BDFDB.dotCN._themerepoloadingtooltip); + if (loadingTooltip) loadingTooltip.update(this.getLoadingTooltipText()); + + checksRunning--; + checkThemeVersion(); + }); + } + }; + + BDFDB.LibraryRequires.request("https://api.betterdiscord.app/v1/store/themes", (error, response, body) => { + if (!error && body) try { + grabbedThemes = BDFDB.ArrayUtils.keySort(JSON.parse(body).filter(n => n), "name"); + BDFDB.DataUtils.save(BDFDB.ArrayUtils.numSort(grabbedThemes.map(n => n.id)).join(" "), this, "cached"); - let loadingTooltip = document.querySelector(BDFDB.dotCN._themerepoloadingtooltip); - if (loadingTooltip) loadingTooltip.update(this.getLoadingTooltipText()); + loading = {is: true, timeout: BDFDB.TimeUtils.timeout(_ => { + BDFDB.TimeUtils.clear(loading.timeout); + if (this.started) { + if (loading.is && loading.amount < 4) BDFDB.TimeUtils.timeout(_ => this.loadThemes(), 10000); + loading = {is: false, timeout: null, amount: loading.amount}; + } + }, 1200000), amount: loading.amount + 1}; - getThemeInfo(callback); - }); - } + loadingIcon = BDFDB.DOMUtils.create(themeRepoIcon.replace(/COLOR_1/gi, "var(--bdfdb-blurple)").replace(/COLOR_2/gi, "#72767d")); + BDFDB.DOMUtils.addClass(loadingIcon, BDFDB.disCN._themerepoloadingicon); + loadingIcon.addEventListener("mouseenter", _ => { + BDFDB.TooltipUtils.create(loadingIcon, this.getLoadingTooltipText(), { + type: "left", + className: BDFDB.disCN._themerepoloadingtooltip, + delay: 500, + style: "max-width: unset;" + }); + }); + BDFDB.PluginUtils.addLoadingIcon(loadingIcon); + + BDFDB.ReactUtils.forceUpdate(list, header); + + for (let i = 0; i <= 20; i++) checkThemeVersion(); + } + catch (err) {BDFDB.NotificationUtils.toast("Failed to load Theme Store", {type: "danger"});} + }); } getLoadingTooltipText () { - return BDFDB.LanguageUtils.LibraryStringsFormat("loading", `Theme Repo - [${Object.keys(loadedThemes).length}/${Object.keys(grabbedThemes).length}]`); + return BDFDB.LanguageUtils.LibraryStringsFormat("loading", `Theme Repo - [${grabbedThemes.filter(n => n.loaded).length}/${grabbedThemes.length}]`); + } + + getString (obj) { + let string = ""; + if (typeof obj == "string") string = obj; + else if (obj && obj.props) { + if (typeof obj.props.children == "string") string = obj.props.children; + else if (Array.isArray(obj.props.children)) for (let c of obj.props.children) string += typeof c == "string" ? c : this.getString(c); + } + return string; + } + + getInstalledTheme (theme) { + if (!theme || typeof theme.author != "string") return; + const iTheme = BDFDB.BDUtils.getTheme(theme.name, false, true); + if (iTheme && theme.author.toUpperCase() == this.getString(iTheme.author).toUpperCase()) return iTheme; + else if (theme.rawSourceUrl && window.BdApi && BdApi.Themes && typeof BdApi.Themes.getAll == "function") { + const filename = theme.rawSourceUrl.split("/").pop(); + for (let t of BdApi.Themes.getAll()) if (t.filename == filename && theme.author.toUpperCase() == this.getString(t.author).toUpperCase()) return t; + } } checkForNewThemes () { - BDFDB.LibraryRequires.request("https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/_res/ThemeList.txt", (error, response, result) => { - if (response && !BDFDB.equals(result.replace(/\t|\r/g, "").split("\n").filter(n => n), grabbedThemes)) { - loading = {is: false, timeout: null, amount: 0}; - this.loadThemes(); + BDFDB.LibraryRequires.request("https://api.betterdiscord.app/v1/store/themes", (error, response, body) => { + if (!error && body) try { + if (JSON.parse(body).filter(n => n).length != grabbedThemes.length) { + loading = {is: false, timeout: null, amount: 0}; + this.loadThemes(); + } } + catch (err) {BDFDB.NotificationUtils.toast("Failed to load Theme Store", {type: "danger"});} }); } - downloadTheme (data) { - BDFDB.LibraryRequires.request(data.url, (error, response, body) => { - if (error) BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("download_fail", `Theme "${data.name}"`), {type: "danger"}); - else this.createThemeFile(data.url.split("/").pop(), body); - }); - } - - createThemeFile (filename, content) { - BDFDB.LibraryRequires.fs.writeFile(BDFDB.LibraryRequires.path.join(BDFDB.BDUtils.getThemesFolder(), filename), content, (error) => { - if (error) BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("save_fail", `Theme "${filename}"`), {type: "danger"}); - else BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("save_success", `Theme "${filename}"`), {type: "success"}); - }); - } - - applyTheme (data) { - if (data.name && BDFDB.BDUtils.isThemeEnabled(data.name) == false) { - let id = data.name.replace(/^[^a-z]+|[^\w-]+/gi, "-"); - BDFDB.DOMUtils.remove(`style#${id}`); - BDFDB.BDUtils.enableTheme(data.name, false); - BDFDB.LogUtils.log(BDFDB.LanguageUtils.LibraryStringsFormat("toast_plugin_started", data.name), this); - } - } - - deleteThemeFile (data) { - let filename = data.url.split("/").pop(); - BDFDB.LibraryRequires.fs.unlink(BDFDB.LibraryRequires.path.join(BDFDB.BDUtils.getThemesFolder(), filename), (error) => { - if (error) BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("delete_fail", `Theme "${filename}"`), {type: "danger"}); - else BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("delete_success", `Theme "${filename}"`)); - }); - } - - removeTheme (data) { - if (data.name && BDFDB.BDUtils.isThemeEnabled(data.name) == true) { - let id = data.name.replace(/^[^a-z]+|[^\w-]+/gi, "-"); - BDFDB.DOMUtils.remove(`style#${id}`); - BDFDB.BDUtils.disableTheme(data.name, false); - BDFDB.LogUtils.log(BDFDB.LanguageUtils.LibraryStringsFormat("toast_plugin_stopped", data.name), this); - } - } - setLabelsByLanguage () { switch (BDFDB.LanguageUtils.getLanguage().id) { case "bg": // Bulgarian