Merge pull request #59 from ragestudio/core-integration

Core integration
This commit is contained in:
srgooglo 2022-05-31 00:18:53 +02:00 committed by GitHub
commit b48450e92f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 423 additions and 528 deletions

View File

@ -2,10 +2,11 @@ const path = require("path")
const { builtinModules } = require("module") const { builtinModules } = require("module")
const aliases = { const aliases = {
"~/": `${path.resolve(__dirname, "src")}/`,
"__": __dirname, "__": __dirname,
"@src": path.resolve(__dirname, "src"), "~/": `${path.resolve(__dirname, "src")}/`,
schemas: path.resolve(__dirname, "constants"), "@src": path.join(__dirname, "src"),
cores: path.join(__dirname, "src/cores"),
schemas: path.join(__dirname, "constants"),
config: path.join(__dirname, "config"), config: path.join(__dirname, "config"),
extensions: path.resolve(__dirname, "src/extensions"), extensions: path.resolve(__dirname, "src/extensions"),
pages: path.join(__dirname, "src/pages"), pages: path.join(__dirname, "src/pages"),

View File

@ -31,7 +31,7 @@
"antd-mobile": "^5.0.0-rc.17", "antd-mobile": "^5.0.0-rc.17",
"chart.js": "3.7.0", "chart.js": "3.7.0",
"classnames": "2.3.1", "classnames": "2.3.1",
"evite": "0.10.4", "evite": "0.11.0",
"faye": "1.4.0", "faye": "1.4.0",
"feather-reactjs": "2.0.13", "feather-reactjs": "2.0.13",
"fuse.js": "6.5.3", "fuse.js": "6.5.3",

View File

@ -43,7 +43,7 @@ Promise.tasked = function (promises) {
} }
import React from "react" import React from "react"
import { CreateEviteApp, BindPropsProvider } from "evite" import { EviteRuntime, BindPropsProvider } from "evite"
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet"
import * as antd from "antd" import * as antd from "antd"
import { ActionSheet, Toast } from "antd-mobile" 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 { Icons } from "components/Icons"
import Layout from "./layout" import Layout from "./layout"
import * as Render from "extensions/render.extension.jsx"
import * as Render from "cores/render"
import "theme/index.less" import "theme/index.less"
class App extends React.Component { class App extends React.Component {
//static debugMode = true // this will enable debug mode of evite app (dah...)
sessionController = new Session() sessionController = new Session()
userController = new User() userController = new User()
@ -73,8 +72,6 @@ class App extends React.Component {
user: null, user: null,
} }
loadingMessage = false
static initialize() { static initialize() {
window.app.version = config.package.version window.app.version = config.package.version
} }
@ -101,7 +98,7 @@ class App extends React.Component {
}, },
"destroyed_session": async function () { "destroyed_session": async function () {
await this.flushState() await this.flushState()
this.eventBus.emit("forceToLogin") app.eventBus.emit("forceToLogin")
}, },
"forceToLogin": function () { "forceToLogin": function () {
// if (window.location.pathname !== "/login") { // if (window.location.pathname !== "/login") {
@ -116,7 +113,7 @@ class App extends React.Component {
await this.flushState() await this.flushState()
if (window.location.pathname !== "/login") { if (window.location.pathname !== "/login") {
this.eventBus.emit("forceToLogin") app.eventBus.emit("forceToLogin")
antd.notification.open({ antd.notification.open({
message: <Translation> message: <Translation>
@ -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 = { static staticRenders = {
NotFound: (props) => { NotFound: (props) => {
return <NotFound /> return <NotFound />
@ -246,7 +193,7 @@ class App extends React.Component {
return <RenderError {...props} /> return <RenderError {...props} />
}, },
Crash: Crash.CrashWrapper, Crash: Crash.CrashWrapper,
initialization: () => { Initialization: () => {
return <div className="splash_wrapper"> return <div className="splash_wrapper">
<div className="splash_logo"> <div className="splash_logo">
<img src={config.logo.alt} /> <img src={config.logo.alt} />
@ -256,7 +203,7 @@ class App extends React.Component {
} }
static publicMethods = { static publicMethods = {
"openSettings": (goTo) => { openSettings: (goTo) => {
window.app.DrawerController.open("settings", Settings, { window.app.DrawerController.open("settings", Settings, {
props: { props: {
width: "fit-content", width: "fit-content",
@ -265,7 +212,43 @@ class App extends React.Component {
goTo, 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 () => { flushState = async () => {
@ -275,7 +258,7 @@ class App extends React.Component {
componentDidMount = async () => { componentDidMount = async () => {
if (window.app.isAppCapacitor()) { if (window.app.isAppCapacitor()) {
window.addEventListener("statusTap", () => { window.addEventListener("statusTap", () => {
this.eventBus.emit("statusTap") app.eventBus.emit("statusTap")
}) })
StatusBar.setOverlaysWebView({ overlay: true }) StatusBar.setOverlaysWebView({ overlay: true })
@ -285,7 +268,7 @@ class App extends React.Component {
const userAgentPlatform = window.navigator.userAgent.toLowerCase() const userAgentPlatform = window.navigator.userAgent.toLowerCase()
if (userAgentPlatform.includes("mac")) { if (userAgentPlatform.includes("mac")) {
window.app.ShortcutsController.register({ this.props.cores.ShortcutsCore.register({
key: ",", key: ",",
meta: true, meta: true,
preventDefault: true, preventDefault: true,
@ -293,7 +276,7 @@ class App extends React.Component {
App.publicMethods.openSettings(...args) App.publicMethods.openSettings(...args)
}) })
} else { } else {
window.app.ShortcutsController.register({ this.props.cores.ShortcutsCore.register({
key: ",", key: ",",
ctrl: true, ctrl: true,
preventDefault: 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() await this.initialization()
this.eventBus.emit("app.render_initialization_done") app.eventBus.emit("app.render_initialization_done")
} }
initialization = async () => { initialization = async () => {
@ -373,7 +356,10 @@ class App extends React.Component {
await Promise.tasked(initializationTasks).catch((reason) => { await Promise.tasked(initializationTasks).catch((reason) => {
console.error(`[App] Initialization failed: ${reason.cause}`) 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) export default new EviteRuntime(App)

View File

@ -8,7 +8,7 @@ export const CrashComponent = (props) => {
return <Result return <Result
status="error" status="error"
title="Crash" title="Well, we're sorry! The application has a fatal crash."
subTitle={crash.message} subTitle={crash.message}
extra={[ extra={[
<Button type="primary" key="reload" onClick={() => window.location.reload()}> <Button type="primary" key="reload" onClick={() => window.location.reload()}>
@ -16,9 +16,10 @@ export const CrashComponent = (props) => {
</Button> </Button>
]} ]}
> >
<div> {crash.details &&
<code>{crash.error}</code> <div>
</div> <code>{crash.details}</code>
</div>}
</Result> </Result>
} }
@ -30,5 +31,4 @@ export const CrashWrapper = (props) => {
</div> </div>
} }
export default CrashComponent export default CrashComponent

View File

@ -9,11 +9,53 @@
z-index: 10000; z-index: 10000;
background: rgba(0, 0, 0, 0.5); background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(10px); 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; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
animation: opacity-fade-in 0.3s ease-in-out;
}
@keyframes opacity-fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
} }

View File

@ -1,62 +1,53 @@
import { Extension } from "evite" import Core from "evite/src/core"
import config from "config" import config from "config"
import { Bridge } from "linebridge/dist/client" import { Bridge } from "linebridge/dist/client"
import { Session } from "models" import { Session } from "models"
export default class ApiExtension extends Extension { export default class ApiCore extends Core {
depends = ["SettingsExtension"] apiBridge = this.createBridge()
constructor(app, main) { WSInterface = {
super(app, main) ...this.apiBridge.wsInterface,
request: this.WSRequest,
//this.config = this.getServerOrigins() listen: this.handleWSListener,
mainSocketConnected: false
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
} }
getServerOrigins = () => { WSSockets = this.WSInterface.sockets
// TODO: try to get origins from settings
// const storagedOrigins = window.app.settings.get("serverOrigins") publicMethods = {
api: this.apiBridge,
ws: this.WSInterface,
request: this.apiBridge.endpoints,
WSRequest: this.WSInterface.wsEndpoints,
} }
initializers = [ async initialize() {
async () => { this.WSSockets.main.on("authenticated", () => {
this.WSSockets.main.on("authenticated", () => { console.debug("[WS] Authenticated")
console.debug("[WS] Authenticated") })
}) this.WSSockets.main.on("authenticateFailed", (error) => {
this.WSSockets.main.on("authenticateFailed", (error) => { console.error("[WS] Authenticate Failed", error)
console.error("[WS] Authenticate Failed", error) })
})
this.WSSockets.main.on("connect", () => { this.WSSockets.main.on("connect", () => {
window.app.eventBus.emit("websocket_connected") this.ctx.eventBus.emit("websocket_connected")
this.WSInterface.mainSocketConnected = true
})
this.WSSockets.main.on("disconnect", (...context) => { this.WSInterface.mainSocketConnected = true
window.app.eventBus.emit("websocket_disconnected", ...context) })
this.WSInterface.mainSocketConnected = false
})
this.WSSockets.main.on("connect_error", (...context) => { this.WSSockets.main.on("disconnect", (...context) => {
window.app.eventBus.emit("websocket_connection_error", ...context) this.ctx.eventBus.emit("websocket_disconnected", ...context)
this.WSInterface.mainSocketConnected = false
})
this.mainContext.setToWindowContext("api", this.apiBridge) this.WSInterface.mainSocketConnected = false
this.mainContext.setToWindowContext("ws", this.WSInterface) })
this.mainContext.setToWindowContext("request", this.apiBridge.endpoints) this.WSSockets.main.on("connect_error", (...context) => {
this.mainContext.setToWindowContext("WSRequest", this.WSInterface.wsEndpoints) this.ctx.eventBus.emit("websocket_connection_error", ...context)
}
] this.WSInterface.mainSocketConnected = false
})
}
createBridge() { createBridge() {
const getSessionContext = async () => { const getSessionContext = async () => {
@ -172,8 +163,4 @@ export default class ApiExtension extends Extension {
}) })
}) })
} }
window = {
ApiController: this
}
} }

View File

@ -1,4 +1,4 @@
import { Extension } from "evite" import Core from "evite/src/core"
import config from "config" import config from "config"
import i18n from "i18next" import i18n from "i18next"
import { initReactI18next } from "react-i18next" import { initReactI18next } from "react-i18next"
@ -14,8 +14,36 @@ export function extractLocaleFromPath(path = "") {
const messageImports = import.meta.glob("./translations/*.json") const messageImports = import.meta.glob("./translations/*.json")
export default class I18nExtension extends Extension { export default class I18nCore extends Core {
depends = ["SettingsExtension"] 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) => { importLocale = async (locale) => {
const [, importLocale] = const [, importLocale] =
@ -40,34 +68,4 @@ export default class I18nExtension extends Extension {
console.error(error) 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)
})
},
]
} }

View File

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

View File

@ -1,11 +1,26 @@
import { Extension } from "evite" import Core from "evite/src/core"
import React from "react" import React from "react"
import { notification as Notf } from "antd" import { notification as Notf } from "antd"
import { Icons, createIconRender } from "components/Icons" import { Icons, createIconRender } from "components/Icons"
import { Translation } from "react-i18next" import { Translation } from "react-i18next"
import { Haptics } from "@capacitor/haptics" 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 = () => { getSoundVolume = () => {
return (window.app.settings.get("notifications_sound_volume") ?? 50) / 100 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() const soundVolume = options.soundVolume ? options.soundVolume / 100 : this.getSoundVolume()
if (soundEnabled) { if (soundEnabled) {
window.app.SoundEngine.play("notification", { if (typeof window.app.sound?.play === "function") {
volume: soundVolume, window.app.sound.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,
}) })
}) }
} }
]
window = {
notifications: this
} }
} }

View File

@ -1,5 +1,6 @@
import Core from "evite/src/core"
import React from "react" import React from "react"
import { EvitePureComponent, Extension } from "evite" import { EvitePureComponent } from "evite"
import progressBar from "nprogress" import progressBar from "nprogress"
import routes from "virtual:generated-pages" import routes from "virtual:generated-pages"
@ -89,8 +90,8 @@ export class RouteRender extends EvitePureComponent {
return JSON.stringify(this.state.renderError) return JSON.stringify(this.state.renderError)
} }
if (this.state.renderInitialization) { if (this.state.renderInitialization && this.props.staticRenders?.Initialization) {
const StaticInitializationRender = this.props.staticRenders?.initialization ?? null const StaticInitializationRender = this.props.staticRenders?.Initialization ?? <div>Loading...</div>
return <StaticInitializationRender /> return <StaticInitializationRender />
} }
@ -103,92 +104,88 @@ export class RouteRender extends EvitePureComponent {
} }
} }
export class RenderExtension extends Extension { export class RenderCore extends Core {
initializers = [ progressBar = progressBar.configure({ parent: "html", showSpinner: false })
async function () {
const defaultTransitionDelay = 150
this.progressBar = progressBar.configure({ parent: "html", showSpinner: false }) publicMethods = {
setLocation: this.ctx.history.setLocation,
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
},
} }
window = { initialize = () => {
isAppCapacitor: () => window.navigator.userAgent === "capacitor", const defaultTransitionDelay = 150
bindContexts: (component) => {
let contexts = { this.ctx.history.listen((event) => {
main: {}, this.ctx.eventBus.emit("transitionDone", event)
app: {}, 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") { this.progressBar.start()
if (component.bindApp === "all") { this.ctx.eventBus.emit("transitionStart", delay)
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") { setTimeout(() => {
if (component.bindMain === "all") { this.ctx.history.push({
Object.keys(main).forEach((key) => { pathname: to,
contexts.main[key] = main[key] }, state)
}) this.ctx.history.lastLocation = this.history.location
} }, delay ?? defaultTransitionDelay)
} else { }
if (Array.isArray(component.bindMain)) { }
component.bindMain.forEach((key) => {
contexts.main[key] = main[key]
})
}
}
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 export default RenderCore

View File

@ -1,18 +1,22 @@
import { Extension } from "evite" import Core from "evite/src/core"
import store from "store" import store from "store"
import defaultSettings from "schemas/defaultSettings.json" import defaultSettings from "schemas/defaultSettings.json"
import { Observable } from "rxjs" import { Observable } from "rxjs"
export default class SettingsExtension extends Extension { export default class SettingsCore extends Core {
constructor(app, main) { storeKey = "app_settings"
super(app, main)
this.storeKey = "app_settings"
this.settings = store.get(this.storeKey) ?? {}
this._setDefaultUndefined() settings = store.get(this.storeKey) ?? {}
publicMethods = {
settings: this
} }
_setDefaultUndefined = () => { initialize() {
this.fulfillUndefinedWithDefaults()
}
fulfillUndefinedWithDefaults = () => {
Object.keys(defaultSettings).forEach((key) => { Object.keys(defaultSettings).forEach((key) => {
const value = defaultSettings[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) => { is = (key, value) => {
return this.settings[key] === value return this.settings[key] === value
} }
@ -53,6 +49,14 @@ export default class SettingsExtension extends Extension {
return this.settings[key] return this.settings[key]
} }
getDefaults = (key) => {
if (typeof key === "undefined") {
return defaultSettings
}
return defaultSettings[key]
}
withEvent = (listenEvent, defaultValue) => { withEvent = (listenEvent, defaultValue) => {
let value = defaultValue ?? this.settings[key] ?? false let value = defaultValue ?? this.settings[key] ?? false
@ -69,8 +73,4 @@ export default class SettingsExtension extends Extension {
return value return value
}) })
} }
window = {
"settings": this
}
} }

View File

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

View File

@ -1,10 +1,18 @@
import { Extension } from "evite" import Core from "evite/src/core"
import { Howl } from "howler" import { Howl } from "howler"
import config from "config" import config from "config"
export default class SoundEngineExtension extends Extension { export default class SoundCore extends Core {
sounds = {} sounds = {}
publicMethods = {
sound: this,
}
async initialize() {
this.sounds = await this.getSounds()
}
getSounds = async () => { getSounds = async () => {
// TODO: Load custom soundpacks manifests // TODO: Load custom soundpacks manifests
let soundPack = config.defaultSoundPack ?? {} let soundPack = config.defaultSoundPack ?? {}
@ -26,18 +34,8 @@ export default class SoundEngineExtension extends Extension {
if (this.sounds[name]) { if (this.sounds[name]) {
return this.sounds[name](options).play() return this.sounds[name](options).play()
} else { } else {
console.error(`Sound ${name} not found.`) console.error(`Sound [${name}] not found or is not available.`)
return false return false
} }
} }
initializers = [
async () => {
this.sounds = await this.getSounds()
}
]
window = {
SoundEngine: this
}
} }

View File

@ -1,39 +1,33 @@
import { Extension } from "evite" import Core from "evite/src/core"
import config from "config" import config from "config"
import store from "store" import store from "store"
import { ConfigProvider } from "antd" import { ConfigProvider } from "antd"
export default class ThemeExtension extends Extension { export default class StyleCore extends Core {
constructor(app, main) { themeManifestStorageKey = "theme"
super(app, main) modificationStorageKey = "themeModifications"
this.themeManifestStorageKey = "theme" theme = null
this.modificationStorageKey = "themeModifications" mutation = null
currentVariant = null
this.theme = null events = {
"theme.applyVariant": (value) => {
this.mutation = null this.applyVariant(value)
this.currentVariant = null this.setVariant(value)
},
"modifyTheme": (value) => {
this.update(value)
this.setModifications(this.mutation)
},
"resetTheme": () => {
this.resetDefault()
}
} }
initializers = [ publicMethods = {
async () => { style: this
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()
},
]
static get currentVariant() { static get currentVariant() {
return document.documentElement.style.getPropertyValue("--themeVariant") return document.documentElement.style.getPropertyValue("--themeVariant")
@ -145,8 +139,4 @@ export default class ThemeExtension extends Extension {
this.update(values) this.update(values)
} }
} }
window = {
ThemeController: this
}
} }

View File

@ -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 (
<div>
<h2>Debugger Error</h2>
<i>
<h4>
Catch on [<strong>{key}</strong>]
</h4>
</i>
`<code>{error.message}</code>`
<hr />
<code>{error.stack}</code>
</div>
)
}
renderTabs = () => {
return Object.keys(this.state.debuggers).map((key) => {
return <Tabs.TabPane tab={key} key={key} />
})
}
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 <Skeleton active />
}
return (
<div>
<Tabs
onChange={this.onChangeTab}
activeKey={this.state.active}
>
{this.renderTabs()}
</Tabs>
{error && this.renderError(this.state.active, error)}
{!this.state.active ? (
<div> Select an debugger to start </div>
) : (
this.renderDebugger(this.state.debuggers[this.state.active])
)}
</div>
)
}
}
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)
}
}

View File

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