diff --git a/packages/app/.config.js b/packages/app/.config.js index 5ca62a21..4f3d8eaf 100644 --- a/packages/app/.config.js +++ b/packages/app/.config.js @@ -2,10 +2,11 @@ const path = require("path") const { builtinModules } = require("module") const aliases = { - "~/": `${path.resolve(__dirname, "src")}/`, "__": __dirname, - "@src": path.resolve(__dirname, "src"), - schemas: path.resolve(__dirname, "constants"), + "~/": `${path.resolve(__dirname, "src")}/`, + "@src": path.join(__dirname, "src"), + cores: path.join(__dirname, "src/cores"), + schemas: path.join(__dirname, "constants"), config: path.join(__dirname, "config"), extensions: path.resolve(__dirname, "src/extensions"), pages: path.join(__dirname, "src/pages"), diff --git a/packages/app/package.json b/packages/app/package.json index c4adc3a3..10345439 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -31,7 +31,7 @@ "antd-mobile": "^5.0.0-rc.17", "chart.js": "3.7.0", "classnames": "2.3.1", - "evite": "0.10.4", + "evite": "0.11.0", "faye": "1.4.0", "feather-reactjs": "2.0.13", "fuse.js": "6.5.3", diff --git a/packages/app/src/App.jsx b/packages/app/src/App.jsx index c069a7b1..f48bf090 100644 --- a/packages/app/src/App.jsx +++ b/packages/app/src/App.jsx @@ -43,7 +43,7 @@ Promise.tasked = function (promises) { } import React from "react" -import { CreateEviteApp, BindPropsProvider } from "evite" +import { EviteRuntime, BindPropsProvider } from "evite" import { Helmet } from "react-helmet" import * as antd from "antd" import { ActionSheet, Toast } from "antd-mobile" @@ -57,13 +57,12 @@ import { NotFound, RenderError, Crash, Settings, Navigation, Login } from "compo import { Icons } from "components/Icons" import Layout from "./layout" -import * as Render from "extensions/render.extension.jsx" + +import * as Render from "cores/render" import "theme/index.less" class App extends React.Component { - //static debugMode = true // this will enable debug mode of evite app (dah...) - sessionController = new Session() userController = new User() @@ -73,8 +72,6 @@ class App extends React.Component { user: null, } - loadingMessage = false - static initialize() { window.app.version = config.package.version } @@ -101,7 +98,7 @@ class App extends React.Component { }, "destroyed_session": async function () { await this.flushState() - this.eventBus.emit("forceToLogin") + app.eventBus.emit("forceToLogin") }, "forceToLogin": function () { // if (window.location.pathname !== "/login") { @@ -116,7 +113,7 @@ class App extends React.Component { await this.flushState() if (window.location.pathname !== "/login") { - this.eventBus.emit("forceToLogin") + app.eventBus.emit("forceToLogin") antd.notification.open({ message: @@ -188,56 +185,6 @@ class App extends React.Component { }, } - static windowContext() { - return { - // TODO: Open with popup controller instead drawer controller - openNavigationMenu: () => window.app.DrawerController.open("navigation", Navigation), - openSettings: App.publicMethods.openSettings, - goMain: () => { - return window.app.setLocation(config.app.mainPath) - }, - goToAccount: (username) => { - return window.app.setLocation(`/account`, { username }) - }, - setStatusBarStyleDark: async () => { - if (!window.app.isAppCapacitor()) { - console.warn("[App] setStatusBarStyleDark is only available on capacitor") - return false - } - return await StatusBar.setStyle({ style: Style.Dark }) - }, - setStatusBarStyleLight: async () => { - if (!window.app.isAppCapacitor()) { - console.warn("[App] setStatusBarStyleLight is not supported on this platform") - return false - } - return await StatusBar.setStyle({ style: Style.Light }) - }, - hideStatusBar: async () => { - if (!window.app.isAppCapacitor()) { - console.warn("[App] hideStatusBar is not supported on this platform") - return false - } - return await StatusBar.hide() - }, - showStatusBar: async () => { - if (!window.app.isAppCapacitor()) { - console.warn("[App] showStatusBar is not supported on this platform") - return false - } - return await StatusBar.show() - }, - } - } - - static appContext() { - return { - renderRef: this.renderRef, - sessionController: this.sessionController, - userController: this.userController, - } - } - static staticRenders = { NotFound: (props) => { return @@ -246,7 +193,7 @@ class App extends React.Component { return }, Crash: Crash.CrashWrapper, - initialization: () => { + Initialization: () => { return
@@ -256,7 +203,7 @@ class App extends React.Component { } static publicMethods = { - "openSettings": (goTo) => { + openSettings: (goTo) => { window.app.DrawerController.open("settings", Settings, { props: { width: "fit-content", @@ -265,7 +212,43 @@ class App extends React.Component { goTo, } }) - } + }, + openNavigationMenu: () => window.app.DrawerController.open("navigation", Navigation), + goMain: () => { + return window.app.setLocation(config.app.mainPath) + }, + goToAccount: (username) => { + return window.app.setLocation(`/account`, { username }) + }, + isAppCapacitor: () => window.navigator.userAgent === "capacitor", + setStatusBarStyleDark: async () => { + if (!window.app.isAppCapacitor()) { + console.warn("[App] setStatusBarStyleDark is only available on capacitor") + return false + } + return await StatusBar.setStyle({ style: Style.Dark }) + }, + setStatusBarStyleLight: async () => { + if (!window.app.isAppCapacitor()) { + console.warn("[App] setStatusBarStyleLight is not supported on this platform") + return false + } + return await StatusBar.setStyle({ style: Style.Light }) + }, + hideStatusBar: async () => { + if (!window.app.isAppCapacitor()) { + console.warn("[App] hideStatusBar is not supported on this platform") + return false + } + return await StatusBar.hide() + }, + showStatusBar: async () => { + if (!window.app.isAppCapacitor()) { + console.warn("[App] showStatusBar is not supported on this platform") + return false + } + return await StatusBar.show() + }, } flushState = async () => { @@ -275,7 +258,7 @@ class App extends React.Component { componentDidMount = async () => { if (window.app.isAppCapacitor()) { window.addEventListener("statusTap", () => { - this.eventBus.emit("statusTap") + app.eventBus.emit("statusTap") }) StatusBar.setOverlaysWebView({ overlay: true }) @@ -285,7 +268,7 @@ class App extends React.Component { const userAgentPlatform = window.navigator.userAgent.toLowerCase() if (userAgentPlatform.includes("mac")) { - window.app.ShortcutsController.register({ + this.props.cores.ShortcutsCore.register({ key: ",", meta: true, preventDefault: true, @@ -293,7 +276,7 @@ class App extends React.Component { App.publicMethods.openSettings(...args) }) } else { - window.app.ShortcutsController.register({ + this.props.cores.ShortcutsCore.register({ key: ",", ctrl: true, preventDefault: true, @@ -302,11 +285,11 @@ class App extends React.Component { }) } - this.eventBus.emit("app.render_initialization") + app.eventBus.emit("app.render_initialization") await this.initialization() - this.eventBus.emit("app.render_initialization_done") + app.eventBus.emit("app.render_initialization_done") } initialization = async () => { @@ -373,7 +356,10 @@ class App extends React.Component { await Promise.tasked(initializationTasks).catch((reason) => { console.error(`[App] Initialization failed: ${reason.cause}`) - app.eventBus.emit("app.crash", reason) + app.eventBus.emit("runtime.crash", { + message: `App initialization failed`, + details: reason.cause, + }) }) } @@ -433,4 +419,4 @@ class App extends React.Component { } } -export default CreateEviteApp(App) \ No newline at end of file +export default new EviteRuntime(App) \ No newline at end of file diff --git a/packages/app/src/components/Crash/index.jsx b/packages/app/src/components/Crash/index.jsx index 9375be6b..8b6839d5 100644 --- a/packages/app/src/components/Crash/index.jsx +++ b/packages/app/src/components/Crash/index.jsx @@ -8,7 +8,7 @@ export const CrashComponent = (props) => { return window.location.reload()}> @@ -16,9 +16,10 @@ export const CrashComponent = (props) => { ]} > -
- {crash.error} -
+ {crash.details && +
+ {crash.details} +
}
} @@ -30,5 +31,4 @@ export const CrashWrapper = (props) => {
} - export default CrashComponent \ No newline at end of file diff --git a/packages/app/src/components/Crash/index.less b/packages/app/src/components/Crash/index.less index b3089e79..4ffa6f26 100644 --- a/packages/app/src/components/Crash/index.less +++ b/packages/app/src/components/Crash/index.less @@ -3,17 +3,59 @@ width: 100vw; height: 100vh; - + top: 0; left: 0; z-index: 10000; - background: rgba(0, 0, 0, 0.5); + background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(10px); + color: #fff; + + .ant-result, + .ant-result-title, + .ant-result-subtitle, + .ant-result-extra { + color: #fff; + } + + .ant-result-content { + background-color: rgba(0, 0, 0, 0.7) !important; + } + + code { + color: #fff; + font-family: "DM Mono", monospace; + } + + h1, + h2, + h3, + h4, + h5, + h6, + p, + span, + pre { + color: #fff; + } + display: flex; flex-direction: column; justify-content: center; align-items: center; + + animation: opacity-fade-in 0.3s ease-in-out; +} + +@keyframes opacity-fade-in { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } } \ No newline at end of file diff --git a/packages/app/src/extensions/api.extension.js b/packages/app/src/cores/api/index.js similarity index 64% rename from packages/app/src/extensions/api.extension.js rename to packages/app/src/cores/api/index.js index c328d562..b7c4cdf4 100644 --- a/packages/app/src/extensions/api.extension.js +++ b/packages/app/src/cores/api/index.js @@ -1,62 +1,53 @@ -import { Extension } from "evite" +import Core from "evite/src/core" import config from "config" import { Bridge } from "linebridge/dist/client" import { Session } from "models" -export default class ApiExtension extends Extension { - depends = ["SettingsExtension"] +export default class ApiCore extends Core { + apiBridge = this.createBridge() - constructor(app, main) { - super(app, main) - - //this.config = this.getServerOrigins() - - this.apiBridge = this.createBridge() - this.WSInterface = this.apiBridge.wsInterface - - this.WSInterface.request = this.WSRequest - this.WSInterface.listen = this.handleWSListener - - this.WSSockets = this.WSInterface.sockets - this.WSInterface.mainSocketConnected = false + WSInterface = { + ...this.apiBridge.wsInterface, + request: this.WSRequest, + listen: this.handleWSListener, + mainSocketConnected: false } - getServerOrigins = () => { - // TODO: try to get origins from settings - // const storagedOrigins = window.app.settings.get("serverOrigins") + WSSockets = this.WSInterface.sockets + + publicMethods = { + api: this.apiBridge, + ws: this.WSInterface, + request: this.apiBridge.endpoints, + WSRequest: this.WSInterface.wsEndpoints, } + + async initialize() { + this.WSSockets.main.on("authenticated", () => { + console.debug("[WS] Authenticated") + }) + this.WSSockets.main.on("authenticateFailed", (error) => { + console.error("[WS] Authenticate Failed", error) + }) - initializers = [ - async () => { - this.WSSockets.main.on("authenticated", () => { - console.debug("[WS] Authenticated") - }) - this.WSSockets.main.on("authenticateFailed", (error) => { - console.error("[WS] Authenticate Failed", error) - }) + this.WSSockets.main.on("connect", () => { + this.ctx.eventBus.emit("websocket_connected") - this.WSSockets.main.on("connect", () => { - window.app.eventBus.emit("websocket_connected") - this.WSInterface.mainSocketConnected = true - }) + this.WSInterface.mainSocketConnected = true + }) - this.WSSockets.main.on("disconnect", (...context) => { - window.app.eventBus.emit("websocket_disconnected", ...context) - this.WSInterface.mainSocketConnected = false - }) + this.WSSockets.main.on("disconnect", (...context) => { + this.ctx.eventBus.emit("websocket_disconnected", ...context) - this.WSSockets.main.on("connect_error", (...context) => { - window.app.eventBus.emit("websocket_connection_error", ...context) - this.WSInterface.mainSocketConnected = false - }) + this.WSInterface.mainSocketConnected = false + }) - this.mainContext.setToWindowContext("api", this.apiBridge) - this.mainContext.setToWindowContext("ws", this.WSInterface) + this.WSSockets.main.on("connect_error", (...context) => { + this.ctx.eventBus.emit("websocket_connection_error", ...context) - this.mainContext.setToWindowContext("request", this.apiBridge.endpoints) - this.mainContext.setToWindowContext("WSRequest", this.WSInterface.wsEndpoints) - } - ] + this.WSInterface.mainSocketConnected = false + }) + } createBridge() { const getSessionContext = async () => { @@ -172,8 +163,4 @@ export default class ApiExtension extends Extension { }) }) } - - window = { - ApiController: this - } } \ No newline at end of file diff --git a/packages/app/src/extensions/i18n.extension.js b/packages/app/src/cores/i18n/index.js similarity index 53% rename from packages/app/src/extensions/i18n.extension.js rename to packages/app/src/cores/i18n/index.js index 86f5e80f..869c9e5e 100644 --- a/packages/app/src/extensions/i18n.extension.js +++ b/packages/app/src/cores/i18n/index.js @@ -1,4 +1,4 @@ -import { Extension } from "evite" +import Core from "evite/src/core" import config from "config" import i18n from "i18next" import { initReactI18next } from "react-i18next" @@ -14,8 +14,36 @@ export function extractLocaleFromPath(path = "") { const messageImports = import.meta.glob("./translations/*.json") -export default class I18nExtension extends Extension { - depends = ["SettingsExtension"] +export default class I18nCore extends Core { + events = { + "changeLanguage": (locale) => { + this.loadAsyncLanguage(locale) + } + } + + initialize = async () => { + let locale = app.settings.get("language") ?? DEFAULT_LOCALE + + if (!SUPPORTED_LOCALES.includes(locale)) { + locale = DEFAULT_LOCALE + } + + const messages = await this.importLocale(locale) + + i18n + .use(initReactI18next) // passes i18n down to react-i18next + .init({ + // debug: true, + resources: { + [locale]: { translation: messages.default || messages }, + }, + lng: locale, + //fallbackLng: DEFAULT_LOCALE, + interpolation: { + escapeValue: false, // react already safes from xss + }, + }) + } importLocale = async (locale) => { const [, importLocale] = @@ -40,34 +68,4 @@ export default class I18nExtension extends Extension { console.error(error) } } - - initializers = [ - async () => { - let locale = app.settings.get("language") ?? DEFAULT_LOCALE - - if (!SUPPORTED_LOCALES.includes(locale)) { - locale = DEFAULT_LOCALE - } - - const messages = await this.importLocale(locale) - - i18n - .use(initReactI18next) // passes i18n down to react-i18next - .init({ - // debug: true, - resources: { - [locale]: { translation: messages.default || messages }, - }, - lng: locale, - //fallbackLng: DEFAULT_LOCALE, - interpolation: { - escapeValue: false, // react already safes from xss - }, - }) - - this.mainContext.eventBus.on("changeLanguage", (locale) => { - this.loadAsyncLanguage(locale) - }) - }, - ] } \ No newline at end of file diff --git a/packages/app/src/extensions/translations/en.json b/packages/app/src/cores/i18n/translations/en.json similarity index 100% rename from packages/app/src/extensions/translations/en.json rename to packages/app/src/cores/i18n/translations/en.json diff --git a/packages/app/src/extensions/translations/es.json b/packages/app/src/cores/i18n/translations/es.json similarity index 100% rename from packages/app/src/extensions/translations/es.json rename to packages/app/src/cores/i18n/translations/es.json diff --git a/packages/app/src/cores/index.js b/packages/app/src/cores/index.js new file mode 100644 index 00000000..f71a2942 --- /dev/null +++ b/packages/app/src/cores/index.js @@ -0,0 +1,21 @@ +import SettingsCore from "./settings" +import APICore from "./api" +import StyleCore from "./style" +import Render from "./render" + +import I18nCore from "./i18n" +import NotificationsCore from "./notifications" +import ShortcutsCore from "./shortcuts" +import SoundCore from "./sound" + +// DEFINE LOAD ORDER HERE +export default [ + SettingsCore, + APICore, + StyleCore, + I18nCore, + SoundCore, + NotificationsCore, + ShortcutsCore, + Render, +] \ No newline at end of file diff --git a/packages/app/src/extensions/notifications.extension.jsx b/packages/app/src/cores/notifications/index.jsx similarity index 73% rename from packages/app/src/extensions/notifications.extension.jsx rename to packages/app/src/cores/notifications/index.jsx index fd7d5776..b6be8f89 100644 --- a/packages/app/src/extensions/notifications.extension.jsx +++ b/packages/app/src/cores/notifications/index.jsx @@ -1,11 +1,26 @@ -import { Extension } from "evite" +import Core from "evite/src/core" import React from "react" import { notification as Notf } from "antd" import { Icons, createIconRender } from "components/Icons" import { Translation } from "react-i18next" import { Haptics } from "@capacitor/haptics" -export default class NotificationController extends Extension { +export default class NotificationCore extends Core { + events = { + "changeNotificationsSoundVolume": (value) => { + this.playAudio({ soundVolume: value }) + }, + "changeNotificationsVibrate": (value) => { + this.playHaptic({ + vibrationEnabled: value, + }) + } + } + + publicMethods = { + notification: this + } + getSoundVolume = () => { return (window.app.settings.get("notifications_sound_volume") ?? 50) / 100 } @@ -49,26 +64,11 @@ export default class NotificationController extends Extension { const soundVolume = options.soundVolume ? options.soundVolume / 100 : this.getSoundVolume() if (soundEnabled) { - window.app.SoundEngine.play("notification", { - volume: soundVolume, - }) - } - } - - initializers = [ - function () { - this.eventBus.on("changeNotificationsSoundVolume", (value) => { - app.notifications.playAudio({ soundVolume: value }) - }) - this.eventBus.on("changeNotificationsVibrate", (value) => { - app.notifications.playHaptic({ - vibrationEnabled: value, + if (typeof window.app.sound?.play === "function") { + window.app.sound.play("notification", { + volume: soundVolume, }) - }) + } } - ] - - window = { - notifications: this } } \ No newline at end of file diff --git a/packages/app/src/extensions/render.extension.jsx b/packages/app/src/cores/render/index.jsx similarity index 54% rename from packages/app/src/extensions/render.extension.jsx rename to packages/app/src/cores/render/index.jsx index 84a62cdc..ff6e49dc 100644 --- a/packages/app/src/extensions/render.extension.jsx +++ b/packages/app/src/cores/render/index.jsx @@ -1,5 +1,6 @@ +import Core from "evite/src/core" import React from "react" -import { EvitePureComponent, Extension } from "evite" +import { EvitePureComponent } from "evite" import progressBar from "nprogress" import routes from "virtual:generated-pages" @@ -89,8 +90,8 @@ export class RouteRender extends EvitePureComponent { return JSON.stringify(this.state.renderError) } - if (this.state.renderInitialization) { - const StaticInitializationRender = this.props.staticRenders?.initialization ?? null + if (this.state.renderInitialization && this.props.staticRenders?.Initialization) { + const StaticInitializationRender = this.props.staticRenders?.Initialization ??
Loading...
return } @@ -103,92 +104,88 @@ export class RouteRender extends EvitePureComponent { } } -export class RenderExtension extends Extension { - initializers = [ - async function () { - const defaultTransitionDelay = 150 +export class RenderCore extends Core { + progressBar = progressBar.configure({ parent: "html", showSpinner: false }) - this.progressBar = progressBar.configure({ parent: "html", showSpinner: false }) - - this.history.listen((event) => { - this.eventBus.emit("transitionDone", event) - this.eventBus.emit("locationChange", event) - this.progressBar.done() - }) - - this.history.setLocation = (to, state, delay) => { - const lastLocation = this.history.lastLocation - - if (typeof lastLocation !== "undefined" && lastLocation?.pathname === to && lastLocation?.state === state) { - return false - } - - this.progressBar.start() - this.eventBus.emit("transitionStart", delay) - - setTimeout(() => { - this.history.push({ - pathname: to, - }, state) - this.history.lastLocation = this.history.location - }, delay ?? defaultTransitionDelay) - } - - this.setToWindowContext("setLocation", this.history.setLocation) - }, - ] - - expose = { - validateLocationSlash: (location) => { - let key = location ?? window.location.pathname - - while (key[0] === "/") { - key = key.slice(1, key.length) - } - - return key - }, + publicMethods = { + setLocation: this.ctx.history.setLocation, } - window = { - isAppCapacitor: () => window.navigator.userAgent === "capacitor", - bindContexts: (component) => { - let contexts = { - main: {}, - app: {}, + initialize = () => { + const defaultTransitionDelay = 150 + + this.ctx.history.listen((event) => { + this.ctx.eventBus.emit("transitionDone", event) + this.ctx.eventBus.emit("locationChange", event) + + this.progressBar.done() + }) + + this.ctx.history.setLocation = (to, state, delay) => { + const lastLocation = this.ctx.history.lastLocation + + if (typeof lastLocation !== "undefined" && lastLocation?.pathname === to && lastLocation?.state === state) { + return false } - if (typeof component.bindApp === "string") { - if (component.bindApp === "all") { - Object.keys(app).forEach((key) => { - contexts.app[key] = app[key] - }) - } - } else { - if (Array.isArray(component.bindApp)) { - component.bindApp.forEach((key) => { - contexts.app[key] = app[key] - }) - } - } + this.progressBar.start() + this.ctx.eventBus.emit("transitionStart", delay) - if (typeof component.bindMain === "string") { - if (component.bindMain === "all") { - Object.keys(main).forEach((key) => { - contexts.main[key] = main[key] - }) - } - } else { - if (Array.isArray(component.bindMain)) { - component.bindMain.forEach((key) => { - contexts.main[key] = main[key] - }) - } - } + setTimeout(() => { + this.ctx.history.push({ + pathname: to, + }, state) + this.ctx.history.lastLocation = this.history.location + }, delay ?? defaultTransitionDelay) + } + } - return (props) => React.createElement(component, { ...props, contexts }) - }, + validateLocationSlash = (location) => { + let key = location ?? window.location.pathname + + while (key[0] === "/") { + key = key.slice(1, key.length) + } + + return key + } + + bindContexts = (component) => { + let contexts = { + main: {}, + app: {}, + } + + if (typeof component.bindApp === "string") { + if (component.bindApp === "all") { + Object.keys(app).forEach((key) => { + contexts.app[key] = app[key] + }) + } + } else { + if (Array.isArray(component.bindApp)) { + component.bindApp.forEach((key) => { + contexts.app[key] = app[key] + }) + } + } + + if (typeof component.bindMain === "string") { + if (component.bindMain === "all") { + Object.keys(main).forEach((key) => { + contexts.main[key] = main[key] + }) + } + } else { + if (Array.isArray(component.bindMain)) { + component.bindMain.forEach((key) => { + contexts.main[key] = main[key] + }) + } + } + + return (props) => React.createElement(component, { ...props, contexts }) } } -export default RenderExtension \ No newline at end of file +export default RenderCore \ No newline at end of file diff --git a/packages/app/src/extensions/staticsRenders/404/index.jsx b/packages/app/src/cores/render/staticsRenders/404/index.jsx similarity index 100% rename from packages/app/src/extensions/staticsRenders/404/index.jsx rename to packages/app/src/cores/render/staticsRenders/404/index.jsx diff --git a/packages/app/src/extensions/staticsRenders/crash/index.jsx b/packages/app/src/cores/render/staticsRenders/crash/index.jsx similarity index 100% rename from packages/app/src/extensions/staticsRenders/crash/index.jsx rename to packages/app/src/cores/render/staticsRenders/crash/index.jsx diff --git a/packages/app/src/extensions/staticsRenders/crash/index.less b/packages/app/src/cores/render/staticsRenders/crash/index.less similarity index 100% rename from packages/app/src/extensions/staticsRenders/crash/index.less rename to packages/app/src/cores/render/staticsRenders/crash/index.less diff --git a/packages/app/src/extensions/settings.extension.js b/packages/app/src/cores/settings/index.js similarity index 80% rename from packages/app/src/extensions/settings.extension.js rename to packages/app/src/cores/settings/index.js index ec845ea6..6f264f12 100644 --- a/packages/app/src/extensions/settings.extension.js +++ b/packages/app/src/cores/settings/index.js @@ -1,18 +1,22 @@ -import { Extension } from "evite" +import Core from "evite/src/core" import store from "store" import defaultSettings from "schemas/defaultSettings.json" import { Observable } from "rxjs" -export default class SettingsExtension extends Extension { - constructor(app, main) { - super(app, main) - this.storeKey = "app_settings" - this.settings = store.get(this.storeKey) ?? {} +export default class SettingsCore extends Core { + storeKey = "app_settings" - this._setDefaultUndefined() + settings = store.get(this.storeKey) ?? {} + + publicMethods = { + settings: this } - _setDefaultUndefined = () => { + initialize() { + this.fulfillUndefinedWithDefaults() + } + + fulfillUndefinedWithDefaults = () => { Object.keys(defaultSettings).forEach((key) => { const value = defaultSettings[key] @@ -23,14 +27,6 @@ export default class SettingsExtension extends Extension { }) } - defaults = (key) => { - if (typeof key === "undefined") { - return defaultSettings - } - - return defaultSettings[key] - } - is = (key, value) => { return this.settings[key] === value } @@ -53,6 +49,14 @@ export default class SettingsExtension extends Extension { return this.settings[key] } + getDefaults = (key) => { + if (typeof key === "undefined") { + return defaultSettings + } + + return defaultSettings[key] + } + withEvent = (listenEvent, defaultValue) => { let value = defaultValue ?? this.settings[key] ?? false @@ -69,8 +73,4 @@ export default class SettingsExtension extends Extension { return value }) } - - window = { - "settings": this - } } \ No newline at end of file diff --git a/packages/app/src/cores/shortcuts/index.js b/packages/app/src/cores/shortcuts/index.js new file mode 100644 index 00000000..034dfc03 --- /dev/null +++ b/packages/app/src/cores/shortcuts/index.js @@ -0,0 +1,74 @@ +import Core from "evite/src/core" + +export default class ShortcutsCore extends Core { + shortcuts = {} + + publicMethods = { + shortcuts: this + } + + initialize() { + document.addEventListener("keydown", this.handleEvent) + } + + handleEvent = (event) => { + // FIXME: event.key sometimes is not defined + const key = event.key.toLowerCase() + + const shortcut = this.shortcuts[key] + + if (shortcut) { + if (typeof shortcut.ctrl === "boolean" && event.ctrlKey !== shortcut.ctrl) { + return + } + + if (typeof shortcut.shift === "boolean" && event.shiftKey !== shortcut.shift) { + return + } + + if (typeof shortcut.alt === "boolean" && event.altKey !== shortcut.alt) { + return + } + + if (typeof shortcut.meta === "boolean" && event.metaKey !== shortcut.meta) { + return + } + + if (shortcut.preventDefault) { + event.preventDefault() + } + + if (typeof shortcut.fn === "function") { + shortcut.fn() + } + } + } + + register = (keybind = {}, fn) => { + if (typeof keybind === "string") { + keybind = { + key: keybind, + } + } + + + this.shortcuts[keybind.key] = { + ...keybind, + fn, + } + } + + remove = (array) => { + if (typeof array === "string") { + array = [array] + } + + array.forEach(key => { + delete this.shortcuts[key] + }) + } + + window = { + ShortcutsController: this + } +} \ No newline at end of file diff --git a/packages/app/src/extensions/sound.extension.js b/packages/app/src/cores/sound/index.js similarity index 70% rename from packages/app/src/extensions/sound.extension.js rename to packages/app/src/cores/sound/index.js index bd650e71..cc1e3f8d 100644 --- a/packages/app/src/extensions/sound.extension.js +++ b/packages/app/src/cores/sound/index.js @@ -1,10 +1,18 @@ -import { Extension } from "evite" +import Core from "evite/src/core" import { Howl } from "howler" import config from "config" -export default class SoundEngineExtension extends Extension { +export default class SoundCore extends Core { sounds = {} + publicMethods = { + sound: this, + } + + async initialize() { + this.sounds = await this.getSounds() + } + getSounds = async () => { // TODO: Load custom soundpacks manifests let soundPack = config.defaultSoundPack ?? {} @@ -26,18 +34,8 @@ export default class SoundEngineExtension extends Extension { if (this.sounds[name]) { return this.sounds[name](options).play() } else { - console.error(`Sound ${name} not found.`) + console.error(`Sound [${name}] not found or is not available.`) return false } } - - initializers = [ - async () => { - this.sounds = await this.getSounds() - } - ] - - window = { - SoundEngine: this - } } \ No newline at end of file diff --git a/packages/app/src/extensions/theme.extension.jsx b/packages/app/src/cores/style/index.jsx similarity index 77% rename from packages/app/src/extensions/theme.extension.jsx rename to packages/app/src/cores/style/index.jsx index 78259161..42b9daa9 100644 --- a/packages/app/src/extensions/theme.extension.jsx +++ b/packages/app/src/cores/style/index.jsx @@ -1,39 +1,33 @@ -import { Extension } from "evite" +import Core from "evite/src/core" import config from "config" import store from "store" import { ConfigProvider } from "antd" -export default class ThemeExtension extends Extension { - constructor(app, main) { - super(app, main) +export default class StyleCore extends Core { + themeManifestStorageKey = "theme" + modificationStorageKey = "themeModifications" - this.themeManifestStorageKey = "theme" - this.modificationStorageKey = "themeModifications" + theme = null + mutation = null + currentVariant = null - this.theme = null - - this.mutation = null - this.currentVariant = null + events = { + "theme.applyVariant": (value) => { + this.applyVariant(value) + this.setVariant(value) + }, + "modifyTheme": (value) => { + this.update(value) + this.setModifications(this.mutation) + }, + "resetTheme": () => { + this.resetDefault() + } } - initializers = [ - async () => { - this.mainContext.eventBus.on("theme.applyVariant", (value) => { - this.applyVariant(value) - this.setVariant(value) - }) - this.mainContext.eventBus.on("modifyTheme", (value) => { - this.update(value) - this.setModifications(this.mutation) - }) - - this.mainContext.eventBus.on("resetTheme", () => { - this.resetDefault() - }) - - await this.initialize() - }, - ] + publicMethods = { + style: this + } static get currentVariant() { return document.documentElement.style.getPropertyValue("--themeVariant") @@ -145,8 +139,4 @@ export default class ThemeExtension extends Extension { this.update(values) } } - - window = { - ThemeController: this - } } \ No newline at end of file diff --git a/packages/app/src/extensions/debug.extension.jsx b/packages/app/src/extensions/debug.extension.jsx deleted file mode 100644 index 1df5ae68..00000000 --- a/packages/app/src/extensions/debug.extension.jsx +++ /dev/null @@ -1,129 +0,0 @@ -import { Extension } from "evite" -import React from "react" -import { Window } from "components" -import { Skeleton, Tabs } from "antd" - -class DebuggerUI extends React.Component { - state = { - loading: true, - error: null, - debuggers: null, - active: null, - } - - toogleLoading = (to = !this.state.loading ?? false) => { - this.setState({ loading: to }) - } - - loadDebuggers = async () => { - this.toogleLoading(true) - - const debuggers = await import(`~/debugComponents`) - let renders = {} - - Object.keys(debuggers).forEach((key) => { - renders[key] = debuggers[key] - }) - - this.setState({ debuggers: renders }, () => { - this.toogleLoading(false) - }) - } - - componentDidMount = async () => { - await this.loadDebuggers() - } - - componentDidCatch = (error, info) => { - this.setState({ error }) - } - - onChangeTab = (key) => { - console.debug(`Changing tab to ${key}`) - this.setState({ active: key, error: null }) - } - - renderError = (key, error) => { - return ( -
-

Debugger Error

- -

- Catch on [{key}] -

-
- `{error.message}` -
- {error.stack} -
- ) - } - - renderTabs = () => { - return Object.keys(this.state.debuggers).map((key) => { - return - }) - } - - renderDebugger = (_debugger) => { - try { - return React.createElement(window.app.bindContexts(_debugger)) - } catch (error) { - return this.renderError(key, error) - } - } - - render() { - const { loading, error } = this.state - - if (loading) { - return - } - - return ( -
- - {this.renderTabs()} - - {error && this.renderError(this.state.active, error)} - {!this.state.active ? ( -
Select an debugger to start
- ) : ( - this.renderDebugger(this.state.debuggers[this.state.active]) - )} -
- ) - } -} - -class Debugger { - constructor(mainContext, params = {}) { - this.mainContext = mainContext - this.params = { ...params } - - this.bindings = {} - } - - openWindow = () => { - new Window.DOMWindow({ id: "debugger", children: window.app.bindContexts(DebuggerUI) }).create() - } - - bind = (id, binding) => { - this.bindings[id] = binding - - return binding - } - - unbind = (id) => { - delete this.bindings[id] - } -} - -export default class VisualDebugger extends Extension { - window = { - debug: new Debugger(this.mainContext) - } -} \ No newline at end of file diff --git a/packages/app/src/extensions/shortcuts.extension.js b/packages/app/src/extensions/shortcuts.extension.js deleted file mode 100644 index e45f77e4..00000000 --- a/packages/app/src/extensions/shortcuts.extension.js +++ /dev/null @@ -1,70 +0,0 @@ -import { Extension } from "evite" - -export default class ShortcutsExtension extends Extension { - constructor(app, main) { - super(app, main) - - this.shortcuts = {} - - document.addEventListener("keydown", (event) => { - // FIXME: event.key sometimes is not defined - const key = event.key.toLowerCase() - - const shortcut = this.shortcuts[key] - - if (shortcut) { - if (typeof shortcut.ctrl === "boolean" && event.ctrlKey !== shortcut.ctrl) { - return - } - - if (typeof shortcut.shift === "boolean" && event.shiftKey !== shortcut.shift) { - return - } - - if (typeof shortcut.alt === "boolean" && event.altKey !== shortcut.alt) { - return - } - - if (typeof shortcut.meta === "boolean" && event.metaKey !== shortcut.meta) { - return - } - - if (shortcut.preventDefault) { - event.preventDefault() - } - - if (typeof shortcut.fn === "function") { - shortcut.fn() - } - } - }) - } - - register = (keybind = {}, fn) => { - if (typeof keybind === "string") { - keybind = { - key: keybind, - } - } - - - this.shortcuts[keybind.key] = { - ...keybind, - fn, - } - } - - remove = (array) => { - if (typeof array === "string") { - array = [array] - } - - array.forEach(key => { - delete this.shortcuts[key] - }) - } - - window = { - ShortcutsController: this - } -} \ No newline at end of file