diff --git a/renderer/src/index.js b/renderer/src/index.js index 346a98cd..b6ca0d8f 100644 --- a/renderer/src/index.js +++ b/renderer/src/index.js @@ -1,6 +1,5 @@ import require from "./polyfill"; // eslint-disable-line no-unused-vars import secure from "./secure"; -import LoadingIcon from "./loadingicon"; import BetterDiscord from "@modules/core"; import BdApi from "@modules/api/index"; @@ -13,6 +12,4 @@ Object.defineProperty(window, "BdApi", { }); window.global = window; -// Add loading icon at the bottom right -LoadingIcon.show(); BetterDiscord.startup(); \ No newline at end of file diff --git a/renderer/src/loading.js b/renderer/src/loading.js new file mode 100644 index 00000000..7961a2c9 --- /dev/null +++ b/renderer/src/loading.js @@ -0,0 +1,128 @@ +import {version} from "@modules/api/legacy"; +import DOMManager from "@modules/dommanager"; +import LoadingStyles from "@styles/loading.css"; + +/** Don't forget to call {@link show} method. */ +class ElementManager { + + /** @type {HTMLDivElement} */ + #elementContainer; + /** @type {HTMLDivElement} */ + #element; + + /** + * @param {HTMLDivElement} element + * @param {HTMLDivElement} container + */ + constructor(element, container) { + this.#element = element; + this.#elementContainer = container; + } + + show() { + this.#elementContainer.appendChild(this.#element); + } + + hide() { + this.#element.remove(); + } +} + +/** Don't forget to call {@link show} method. */ +class ElementValueManager extends ElementManager { + + /** @type {(value: unknown, element: HTMLDivElement) => void} */ + #changer; + + /** + * @param {HTMLDivElement} element + * @param {HTMLDivElement} container + * @param {(value: unknown, element: HTMLDivElement) => void} changer + */ + constructor(element, container, changer) { + super(element, container); + this.#changer = changer; + } + + set(value) { + this.#changer(value); + } +} + +/** Element manager with all loading info elemets. Now we have note and status block with progress bar and status label. Don't forget to call {@link show} and {@link hide} methods. */ +class LoadingManager { + + /** @type {HTMLElement} */ + #elementContainer; + /** @type {HTMLDivElement} */ + #element; + + note; + status; + + constructor() { + const layout = DOMManager.createElement("div", {className: "bd-loaderv3"}); + const container = DOMManager.createElement("div", {className: "bd-loading-container", target: layout}); + + const leftside = DOMManager.createElement("div", {className: "bd-loading-left-side", target: container}); + DOMManager.createElement("div", {className: "bd-loading-icon", target: leftside}); + DOMManager.createElement("div", {className: "bd-loading-icon-note", target: leftside}, "v" + version); + + const rightside = DOMManager.createElement("div", {className: "bd-loading-right-side", target: container}); + + this.#elementContainer = document.body; + this.#element = layout; + this.status = new LoadingStatusManager(rightside); + } + + async show() { + this.#element.style.pointerEvents = "none"; + DOMManager.injectStyle("bd-loading", LoadingStyles.toString()); + this.#elementContainer.appendChild(this.#element); + await this.#element.animate([{opacity: 0}, {opacity: 1}], {duration: 500}).finished; + this.#element.style.pointerEvents = ""; + } + + async hide() { + this.#element.style.pointerEvents = "none"; + await this.#element.animate([{opacity: 1}, {opacity: 0}], {duration: 500}).finished; + this.#element.remove(); + DOMManager.removeStyle("bd-loading"); + } + +} + +/** Status element manager with progress elements, e.g.: progress bar and status label. Don't forget to call {@link show} method. */ +class LoadingStatusManager extends ElementManager { + + /** @type {HTMLDivElement} */ + #elementContainer; + /** @type {HTMLDivElement} */ + #element; + /** @type {HTMLDivElement} */ + #elementProgress; + /** @type {HTMLDivElement} */ + #elementProgressBar; + /** @type {HTMLDivElement} */ + #elementStatus; + + label; + progress; + + /** @param {HTMLDivElement} container */ + constructor(container) { + const element = DOMManager.createElement("div", {className: "bd-loading-item"}); + super(element, container); + this.#elementContainer = container; + this.#element = element; + this.#elementProgress = DOMManager.createElement("div", {className: "bd-loading-progress", target: this.#element}); + this.#elementProgressBar = DOMManager.createElement("div", {className: "bd-loading-progress-bar", target: this.#elementProgress}); + this.#elementStatus = DOMManager.createElement("div", {className: "bd-loading-status", target: this.#element}); + this.label = new ElementValueManager(this.#elementStatus, this.#elementContainer, (value) => this.#elementStatus.innerText = value); + this.progress = new ElementValueManager(this.#elementProgress, this.#elementContainer, (value) => this.#elementProgressBar.style.width = Math.min(Math.max(0, value), 100) + "%"); + } + +} + +const Loading = new LoadingManager(); +export default Loading; \ No newline at end of file diff --git a/renderer/src/loadingicon.js b/renderer/src/loadingicon.js deleted file mode 100644 index 41d06bd2..00000000 --- a/renderer/src/loadingicon.js +++ /dev/null @@ -1,45 +0,0 @@ -const css = `/* BEGIN V2 LOADER */ -/* =============== */ - -#bd-loading-icon { - background-image: url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyMDAwIDIwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9IiMzRTgyRTUiIGQ9Ik0xNDAyLjIsNjMxLjdjLTkuNy0zNTMuNC0yODYuMi00OTYtNjQyLjYtNDk2SDY4LjR2NzE0LjFsNDQyLDM5OFY0OTAuN2gyNTdjMjc0LjUsMCwyNzQuNSwzNDQuOSwwLDM0NC45SDU5Ny42djMyOS41aDE2OS44YzI3NC41LDAsMjc0LjUsMzQ0LjgsMCwzNDQuOGgtNjk5djM1NC45aDY5MS4yYzM1Ni4zLDAsNjMyLjgtMTQyLjYsNjQyLjYtNDk2YzAtMTYyLjYtNDQuNS0yODQuMS0xMjIuOS0zNjguNkMxMzU3LjcsOTE1LjgsMTQwMi4yLDc5NC4zLDE0MDIuMiw2MzEuN3oiLz48cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNMTI2Mi41LDEzNS4yTDEyNjIuNSwxMzUuMmwtNzYuOCwwYzI2LjYsMTMuMyw1MS43LDI4LjEsNzUsNDQuM2M3MC43LDQ5LjEsMTI2LjEsMTExLjUsMTY0LjYsMTg1LjNjMzkuOSw3Ni42LDYxLjUsMTY1LjYsNjQuMywyNjQuNmwwLDEuMnYxLjJjMCwxNDEuMSwwLDU5Ni4xLDAsNzM3LjF2MS4ybDAsMS4yYy0yLjcsOTktMjQuMywxODgtNjQuMywyNjQuNmMtMzguNSw3My44LTkzLjgsMTM2LjItMTY0LjYsMTg1LjNjLTIyLjYsMTUuNy00Ni45LDMwLjEtNzIuNiw0My4xaDcyLjVjMzQ2LjIsMS45LDY3MS0xNzEuMiw2NzEtNTY3LjlWNzE2LjdDMTkzMy41LDMxMi4yLDE2MDguNywxMzUuMiwxMjYyLjUsMTM1LjJ6Ii8+PC9nPjwvc3ZnPg==); -} -#bd-loading-icon { - position: fixed; - bottom:5px; - right:5px; - z-index: 2147483647; - display: block; - width: 20px; - height: 20px; - background-size: 100% 100%; - animation: bd-loading-animation 1.5s ease-in-out infinite; -} - -@keyframes bd-loading-animation { - 0% { opacity: 0.05; } - 50% { opacity: 0.6; } - 100% { opacity: 0.05; } -} -/* =============== */ -/* END V2 LOADER */`; - -const iconStyle = document.createElement("style"); -iconStyle.textContent = css; - -const loadingIcon = document.createElement("div"); -loadingIcon.id = "bd-loading-icon"; -loadingIcon.className = "bd-loaderv2"; -loadingIcon.title = "BetterDiscord is loading..."; - -export default class { - static show() { - document.body.appendChild(iconStyle); - document.body.appendChild(loadingIcon); - } - - static hide() { - if (iconStyle) iconStyle.remove(); - if (loadingIcon) loadingIcon.remove(); - } -} \ No newline at end of file diff --git a/renderer/src/modules/addonmanager.js b/renderer/src/modules/addonmanager.js index fec5711b..48f3dea9 100644 --- a/renderer/src/modules/addonmanager.js +++ b/renderer/src/modules/addonmanager.js @@ -90,7 +90,7 @@ export default class AddonManager { Logger.warn(this.name, `Duplicate files found: ${filename} and ${newFilename}`); return; } - + // Rename the file and let it go on try { fs.renameSync(absolutePath, path.resolve(this.addonFolder, newFilename)); @@ -215,7 +215,7 @@ export default class AddonManager { } return e; } - + const error = this.initializeAddon(addon); if (error) { @@ -227,7 +227,7 @@ export default class AddonManager { if (shouldToast) Toasts.success(Strings.Addons.wasLoaded.format({name: addon.name, version: addon.version})); this.emit("loaded", addon); - + if (!this.state[addon.id]) return this.state[addon.id] = false; return this.startAddon(addon); } @@ -329,7 +329,7 @@ export default class AddonManager { for (const name of results.removed) this.unloadAddon(name); } - loadAllAddons() { + async loadAllAddons() { this.loadState(); const errors = []; const files = fs.readdirSync(this.addonFolder); @@ -353,7 +353,7 @@ export default class AddonManager { Logger.warn("AddonManager", `Duplicate files found: ${filename} and ${newFilename}`); continue; } - + // Rename the file and let it go on fs.renameSync(absolutePath, path.resolve(this.addonFolder, newFilename)); } diff --git a/renderer/src/modules/core.js b/renderer/src/modules/core.js index 59a8400d..29c50001 100644 --- a/renderer/src/modules/core.js +++ b/renderer/src/modules/core.js @@ -5,8 +5,6 @@ import Changelog from "@data/changelog"; import * as Builtins from "@builtins/builtins"; -import LoadingIcon from "../loadingicon"; - import LocaleManager from "./localemanager"; import DOMManager from "./dommanager"; import PluginManager from "./pluginmanager"; @@ -16,6 +14,7 @@ import DataStore from "./datastore"; import DiscordModules from "./discordmodules"; import IPC from "./ipc"; +import Loading from "../loading"; import Editor from "./editor"; import Updater from "./updater"; @@ -34,52 +33,86 @@ export default new class Core { Config.userData = process.env.DISCORD_USER_DATA; Config.dataPath = process.env.BETTERDISCORD_DATA_PATH; + Loading.show(); + Loading.status.show(); + IPC.getSystemAccentColor().then(value => DOMManager.injectStyle("bd-os-values", `:root {--os-accent-color: #${value};}`)); - // Load css early - Logger.log("Startup", "Injecting BD Styles"); - DOMManager.injectStyle("bd-stylesheet", Styles.toString()); + let pluginErrors, themeErrors; - Logger.log("Startup", "Initializing DataStore"); - DataStore.initialize(); + const clusters = [ + async () => { + Logger.log("Startup", "Injecting BD Styles"); + Loading.status.label.set("Initialization..."); + DOMManager.injectStyle("bd-stylesheet", Styles.toString()); + }, + async () => { + Logger.log("Startup", "Initializing DataStore"); + DataStore.initialize(); + }, + async () => { + Logger.log("Startup", "Initializing LocaleManager"); + LocaleManager.initialize(); + }, + async () => { + Logger.log("Startup", "Initializing Settings"); + Settings.initialize(); + }, + async () => { + Logger.log("Startup", "Initializing DOMManager"); + DOMManager.initialize(); + }, + async () => { + Logger.log("Startup", "Waiting for connection..."); + await this.waitForConnection(); + }, + async () => { + Logger.log("Startup", "Initializing Editor"); + await Editor.initialize(); + }, + async () => { + Logger.log("Startup", "Initializing Modals"); + await Modals.initialize(); + }, + async () => { + Logger.log("Startup", "Initializing Floating windows"); + FloatingWindows.initialize(); + }, + async () => { + Logger.log("Startup", "Initializing Builtins"); + for (const module in Builtins) { + Builtins[module].initialize(); + } + }, + async () => { + Logger.log("Startup", "Loading Plugins"); + // const pluginErrors = []; + Loading.status.label.set("Loading plugins..."); + pluginErrors = await PluginManager.initialize(); + }, + async () => { + Logger.log("Startup", "Loading Themes"); + // const themeErrors = []; + Loading.status.label.set("Loading themes..."); + themeErrors = await ThemeManager.initialize(); + }, + async () => { + Logger.log("Startup", "Initializing Updater"); + Loading.status.label.set("Getting things ready..."); + Updater.initialize(); + }, + async () => { + Logger.log("Startup", "Removing Loading Interface"); + Loading.status.label.set("Done"); + Loading.hide(); + } + ]; - Logger.log("Startup", "Initializing LocaleManager"); - LocaleManager.initialize(); - - Logger.log("Startup", "Initializing Settings"); - Settings.initialize(); - - Logger.log("Startup", "Initializing DOMManager"); - DOMManager.initialize(); - - Logger.log("Startup", "Waiting for connection..."); - await this.waitForConnection(); - - Logger.log("Startup", "Initializing Editor"); - await Editor.initialize(); - - Modals.initialize(); - FloatingWindows.initialize(); - - Logger.log("Startup", "Initializing Builtins"); - for (const module in Builtins) { - Builtins[module].initialize(); + for (let clusterIndex = 0; clusterIndex < clusters.length; clusterIndex++) { + Loading.status.progress.set(clusterIndex / (clusters.length - 1) * 100); + await clusters[clusterIndex](); } - Logger.log("Startup", "Loading Plugins"); - // const pluginErrors = []; - const pluginErrors = PluginManager.initialize(); - - Logger.log("Startup", "Loading Themes"); - // const themeErrors = []; - const themeErrors = ThemeManager.initialize(); - - Logger.log("Startup", "Initializing Updater"); - Updater.initialize(); - - Logger.log("Startup", "Removing Loading Icon"); - LoadingIcon.hide(); - // Show loading errors Logger.log("Startup", "Collecting Startup Errors"); Modals.showAddonErrors({plugins: pluginErrors, themes: themeErrors}); diff --git a/renderer/src/styles/loading.css b/renderer/src/styles/loading.css new file mode 100644 index 00000000..73664841 --- /dev/null +++ b/renderer/src/styles/loading.css @@ -0,0 +1,107 @@ +.bd-loaderv3 { + pointer-events: none; + display: flex; + flex-direction: row; + align-items: flex-end; + justify-content: flex-end; + box-sizing: border-box; + padding: 20px; + z-index: 2147483647; + position: absolute; + right: 0; + bottom: 0; + width: 100%; + height: 100%; +} + +.bd-loading-container { + pointer-events: all; + display: flex; + flex-direction: row; + box-sizing: border-box; + gap: 5px; + background: var(--background-floating); + box-shadow: var(--elevation-high); + border-radius: 5px; + padding: 10px; + width: fit-content; + min-width: 30vh; +} + +.theme-dark .bd-loading-icon { + background-image: url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyMDAwIDIwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9IiMzRTgyRTUiIGQ9Ik0xNDAyLjIsNjMxLjdjLTkuNy0zNTMuNC0yODYuMi00OTYtNjQyLjYtNDk2SDY4LjR2NzE0LjFsNDQyLDM5OFY0OTAuN2gyNTdjMjc0LjUsMCwyNzQuNSwzNDQuOSwwLDM0NC45SDU5Ny42djMyOS41aDE2OS44YzI3NC41LDAsMjc0LjUsMzQ0LjgsMCwzNDQuOGgtNjk5djM1NC45aDY5MS4yYzM1Ni4zLDAsNjMyLjgtMTQyLjYsNjQyLjYtNDk2YzAtMTYyLjYtNDQuNS0yODQuMS0xMjIuOS0zNjguNkMxMzU3LjcsOTE1LjgsMTQwMi4yLDc5NC4zLDE0MDIuMiw2MzEuN3oiLz48cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNMTI2Mi41LDEzNS4yTDEyNjIuNSwxMzUuMmwtNzYuOCwwYzI2LjYsMTMuMyw1MS43LDI4LjEsNzUsNDQuM2M3MC43LDQ5LjEsMTI2LjEsMTExLjUsMTY0LjYsMTg1LjNjMzkuOSw3Ni42LDYxLjUsMTY1LjYsNjQuMywyNjQuNmwwLDEuMnYxLjJjMCwxNDEuMSwwLDU5Ni4xLDAsNzM3LjF2MS4ybDAsMS4yYy0yLjcsOTktMjQuMywxODgtNjQuMywyNjQuNmMtMzguNSw3My44LTkzLjgsMTM2LjItMTY0LjYsMTg1LjNjLTIyLjYsMTUuNy00Ni45LDMwLjEtNzIuNiw0My4xaDcyLjVjMzQ2LjIsMS45LDY3MS0xNzEuMiw2NzEtNTY3LjlWNzE2LjdDMTkzMy41LDMxMi4yLDE2MDguNywxMzUuMiwxMjYyLjUsMTM1LjJ6Ii8+PC9nPjwvc3ZnPg==); +} + +.theme-light .bd-loading-icon { + background-image: url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyMDAwIDIwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9IiMzRTgyRTUiIGQ9Ik0xNDAyLjIsNjMxLjdjLTkuNy0zNTMuNC0yODYuMi00OTYtNjQyLjYtNDk2SDY4LjR2NzE0LjFsNDQyLDM5OFY0OTAuN2gyNTdjMjc0LjUsMCwyNzQuNSwzNDQuOSwwLDM0NC45SDU5Ny42djMyOS41aDE2OS44YzI3NC41LDAsMjc0LjUsMzQ0LjgsMCwzNDQuOGgtNjk5djM1NC45aDY5MS4yYzM1Ni4zLDAsNjMyLjgtMTQyLjYsNjQyLjYtNDk2YzAtMTYyLjYtNDQuNS0yODQuMS0xMjIuOS0zNjguNkMxMzU3LjcsOTE1LjgsMTQwMi4yLDc5NC4zLDE0MDIuMiw2MzEuN3oiLz48cGF0aCBmaWxsPSIjMDAwMDAwIiBkPSJNMTI2Mi41LDEzNS4yTDEyNjIuNSwxMzUuMmwtNzYuOCwwYzI2LjYsMTMuMyw1MS43LDI4LjEsNzUsNDQuM2M3MC43LDQ5LjEsMTI2LjEsMTExLjUsMTY0LjYsMTg1LjNjMzkuOSw3Ni42LDYxLjUsMTY1LjYsNjQuMywyNjQuNmwwLDEuMnYxLjJjMCwxNDEuMSwwLDU5Ni4xLDAsNzM3LjF2MS4ybDAsMS4yYy0yLjcsOTktMjQuMywxODgtNjQuMywyNjQuNmMtMzguNSw3My44LTkzLjgsMTM2LjItMTY0LjYsMTg1LjNjLTIyLjYsMTUuNy00Ni45LDMwLjEtNzIuNiw0My4xaDcyLjVjMzQ2LjIsMS45LDY3MS0xNzEuMiw2NzEtNTY3LjlWNzE2LjdDMTkzMy41LDMxMi4yLDE2MDguNywxMzUuMiwxMjYyLjUsMTM1LjJ6Ii8+PC9nPjwvc3ZnPg==); +} + +.bd-loading-left-side, .bd-loading-right-side { + display: flex; + flex-direction: column; + gap: 5px; + position: relative; + width: fit-content; +} + +.bd-loading-right-side { + width: 100%; +} + +.bd-loading-icon { + bottom: 15px; + right: 5px; + display: block; + width: 25px; + height: 25px; + background-size: 100% 100%; + animation: bd-loading-animation 1.5s ease-in-out infinite; +} + +.bd-loading-icon-note { + font-size: 12px; + color: var(--header-secondary); +} + +.bd-loading-item { + display: flex; + flex-direction: column; + gap: 5px; + position: relative; + width: 100%; +} + +.bd-loading-progress { + width: 100%; + height: 5px; + margin: 10px 0; + border-radius: 3px; + background-color: var(--background-secondary); + overflow: hidden; +} + +.bd-loading-progress-bar { + background-color: #3E82E5; + height: 100%; +} + +.bd-loading-status { + white-space: nowrap; + text-align: right; + font-weight: 600; + color: var(--header-primary); +} + +@keyframes bd-loading-animation { + 0% { + opacity: 0.05; + } + + 50% { + opacity: 0.6; + } + + 100% { + opacity: 0.05; + } +} \ No newline at end of file