diff --git a/packages/app/src/cores/api/index.js b/packages/app/src/cores/api/index.js
index 8630e91f..803a3622 100755
--- a/packages/app/src/cores/api/index.js
+++ b/packages/app/src/cores/api/index.js
@@ -1,7 +1,8 @@
import Core from "evite/src/core"
-import config from "config"
import { Bridge } from "linebridge/dist/client"
-import { Session } from "models"
+
+import config from "config"
+import { SessionModel } from "models"
function generateWSFunctionHandler(socket, type = "listen") {
if (!socket) {
@@ -47,15 +48,23 @@ function generateWSFunctionHandler(socket, type = "listen") {
}
export default class ApiCore extends Core {
- constructor(props) {
- super(props)
+ static namespace = "api"
- this.namespaces = Object()
+ excludedExpiredExceptionURL = ["/session/regenerate"]
- this.onExpiredExceptionEvent = false
- this.excludedExpiredExceptionURL = ["/regenerate_session_token"]
+ onExpiredExceptionEvent = false
- this.ctx.registerPublicMethod("api", this)
+ namespaces = Object()
+
+ public = {
+ namespaces: this.namespaces,
+ customRequest: this.customRequest,
+ request: this.request,
+ withEndpoints: this.withEndpoints,
+ attachBridge: this.attachBridge,
+ detachBridge: this.detachBridge,
+ createBridge: this.createBridge,
+ autenticateWS: this.autenticateWS,
}
async customRequest(
@@ -83,7 +92,7 @@ export default class ApiCore extends Core {
payload.headers = {}
}
- const sessionToken = await Session.token
+ const sessionToken = await SessionModel.token
if (sessionToken) {
payload.headers["Authorization"] = `Bearer ${sessionToken}`
@@ -95,7 +104,7 @@ export default class ApiCore extends Core {
return await this.namespaces[namepace].httpInterface(payload, ...args)
}
- request = (namespace = "main", method, endpoint, ...args) => {
+ request(namespace = "main", method, endpoint, ...args) {
if (!this.namespaces[namespace]) {
throw new Error(`Namespace ${namespace} not found`)
}
@@ -111,7 +120,7 @@ export default class ApiCore extends Core {
return this.namespaces[namespace].endpoints[method][endpoint](...args)
}
- withEndpoints = (namespace = "main") => {
+ withEndpoints(namespace = "main") {
if (!this.namespaces[namespace]) {
throw new Error(`Namespace ${namespace} not found`)
}
@@ -119,7 +128,7 @@ export default class ApiCore extends Core {
return this.namespaces[namespace].endpoints
}
- handleBeforeRequest = async (request) => {
+ async handleBeforeRequest(request) {
if (this.onExpiredExceptionEvent) {
if (this.excludedExpiredExceptionURL.includes(request.url)) return
@@ -132,19 +141,21 @@ export default class ApiCore extends Core {
}
}
- handleRegenerationEvent = async (refreshToken, makeRequest) => {
+ async handleRegenerationEvent(refreshToken, makeRequest) {
window.app.eventBus.emit("session.expiredExceptionEvent", refreshToken)
this.onExpiredExceptionEvent = true
- const expiredToken = await Session.token
-
- // exclude regeneration endpoint
+ const expiredToken = await SessionModel.token
// send request to regenerate token
- const response = await this.request("main", "post", "regenerateSessionToken", {
- expiredToken: expiredToken,
- refreshToken,
+ const response = await this.customRequest("main", {
+ method: "POST",
+ url: "/session/regenerate",
+ data: {
+ expiredToken: expiredToken,
+ refreshToken,
+ }
}).catch((error) => {
console.error(`Failed to regenerate token: ${error.message}`)
return false
@@ -155,7 +166,7 @@ export default class ApiCore extends Core {
}
// set new token
- Session.token = response.token
+ SessionModel.token = response.token
//this.namespaces["main"].internalAbortController.abort()
@@ -165,18 +176,18 @@ export default class ApiCore extends Core {
window.app.eventBus.emit("session.regenerated")
}
- attachBridge = (key, params) => {
+ attachBridge(key, params) {
return this.namespaces[key] = this.createBridge(params)
}
- detachBridge = (key) => {
+ detachBridge(key) {
return delete this.namespaces[key]
}
createBridge(params = {}) {
const getSessionContext = async () => {
const obj = {}
- const token = await Session.token
+ const token = await SessionModel.token
if (token) {
// append token to context
@@ -224,7 +235,7 @@ export default class ApiCore extends Core {
const bridge = new Bridge(bridgeOptions)
- // handle main ws events
+ // handle main ws onEvents
const mainWSSocket = bridge.wsInterface.sockets["main"]
mainWSSocket.on("authenticated", () => {
@@ -236,16 +247,23 @@ export default class ApiCore extends Core {
})
mainWSSocket.on("connect", () => {
- this.ctx.eventBus.emit(`api.ws.main.connect`)
+ if (this.ctx.eventBus) {
+ this.ctx.eventBus.emit(`api.ws.main.connect`)
+ }
+
this.autenticateWS(mainWSSocket)
})
mainWSSocket.on("disconnect", (...context) => {
- this.ctx.eventBus.emit(`api.ws.main.disconnect`, ...context)
+ if (this.ctx.eventBus) {
+ this.ctx.eventBus.emit(`api.ws.main.disconnect`, ...context)
+ }
})
mainWSSocket.on("connect_error", (...context) => {
- this.ctx.eventBus.emit(`api.ws.main.connect_error`, ...context)
+ if (this.ctx.eventBus) {
+ this.ctx.eventBus.emit(`api.ws.main.connect_error`, ...context)
+ }
})
// generate functions
@@ -256,8 +274,8 @@ export default class ApiCore extends Core {
return bridge
}
- autenticateWS = async (socket) => {
- const token = await Session.token
+ async autenticateWS(socket) {
+ const token = await SessionModel.token
if (token) {
socket.emit("authenticate", {
diff --git a/packages/app/src/cores/audioPlayer/index.jsx b/packages/app/src/cores/audioPlayer/index.jsx
deleted file mode 100755
index 9827ee82..00000000
--- a/packages/app/src/cores/audioPlayer/index.jsx
+++ /dev/null
@@ -1,271 +0,0 @@
-import Core from "evite/src/core"
-
-import React from "react"
-import { Howl } from "howler"
-
-import { EmbbededMediaPlayer } from "components"
-import { DOMWindow } from "components/RenderWindow"
-
-export default class AudioPlayerCore extends Core {
- audioMuted = false
- audioVolume = 1
-
- audioQueueHistory = []
- audioQueue = []
- currentAudio = null
-
- currentDomWindow = null
-
- preloadAudioDebounce = null
-
- publicMethods = {
- AudioPlayer: this,
- }
-
- async initialize() {
- app.eventBus.on("audioPlayer.end", () => {
- this.nextAudio()
- })
- }
-
- toogleMute() {
- this.audioMuted = !this.audioMuted
-
- if (this.currentAudio) {
- this.currentAudio.instance.mute(this.audioMuted)
- }
-
- // apply to all audio in queue
- this.audioQueue.forEach((audio) => {
- audio.instance.mute(this.audioMuted)
- })
-
- app.eventBus.emit("audioPlayer.muted", this.audioMuted)
-
- return this.audioMuted
- }
-
- setVolume(volume) {
- if (typeof volume !== "number") {
- console.warn("Volume must be a number")
- return false
- }
-
- if (volume > 1) {
- volume = 1
- }
-
- if (volume < 0) {
- volume = 0
- }
-
- this.audioVolume = volume
-
- if (this.currentAudio) {
- this.currentAudio.instance.volume(volume)
- }
-
- // apply to all audio in queue
- this.audioQueue.forEach((audio) => {
- audio.instance.volume(volume)
- })
-
- app.eventBus.emit("audioPlayer.volumeChanged", volume)
-
- return volume
- }
-
- async preloadAudio() {
- // debounce to prevent multiple preload
- if (this.preloadAudioDebounce) {
- clearTimeout(this.preloadAudioDebounce)
- }
-
- this.preloadAudioDebounce = setTimeout(async () => {
- // load the first 2 audio in queue
- const audioToLoad = this.audioQueue.slice(0, 2)
-
- // filter undefined
- const audioToLoadFiltered = audioToLoad.filter((audio) => audio.instance)
-
- audioToLoad.forEach(async (audio) => {
- const audioState = audio.instance.state()
-
- if (audioState !== "loaded" && audioState !== "loading") {
- await audio.instance.load()
- }
- })
- }, 600)
- }
-
- startPlaylist = async (data) => {
- if (typeof data === "undefined") {
- console.warn("No data provided")
- return false
- }
-
- if (!Array.isArray(data)) {
- data = [data]
- }
-
- await this.clearAudioQueues()
-
- this.attachEmbbededMediaPlayer()
-
- for await (const item of data) {
- const audioInstance = await this.createAudioInstance(item)
-
- await this.audioQueue.push({
- data: item,
- instance: audioInstance,
- })
- }
- await this.preloadAudio()
-
- this.currentAudio = this.audioQueue.shift()
-
- this.playCurrentAudio()
- }
-
- clearAudioQueues() {
- if (this.currentAudio) {
- this.currentAudio.instance.stop()
- }
-
- this.audioQueueHistory = []
- this.audioQueue = []
- this.currentAudio = null
- }
-
- async playCurrentAudio() {
- if (!this.currentAudio) {
- console.warn("No audio playing")
- return false
- }
-
- const audioState = this.currentAudio.instance.state()
-
- console.log(`Current Audio State: ${audioState}`)
-
- // check if the instance is loaded
- if (audioState !== "loaded") {
- console.warn("Audio not loaded")
-
- app.eventBus.emit("audioPlayer.loading", this.currentAudio)
-
- await this.currentAudio.instance.load()
-
- app.eventBus.emit("audioPlayer.loaded", this.currentAudio)
- }
-
- this.currentAudio.instance.play()
- }
-
- pauseAudioQueue() {
- if (!this.currentAudio) {
- console.warn("No audio playing")
- return false
- }
-
- this.currentAudio.instance.pause()
- }
-
- previousAudio() {
- // check if there is audio playing
- if (this.currentAudio) {
- this.currentAudio.instance.stop()
- }
-
- // check if there is audio in queue
- if (!this.audioQueueHistory[0]) {
- console.warn("No audio in queue")
- return false
- }
-
- // move current audio to queue
- this.audioQueue.unshift(this.currentAudio)
-
- this.currentAudio = this.audioQueueHistory.pop()
-
- this.playCurrentAudio()
- }
-
- nextAudio() {
- // check if there is audio playing
- if (this.currentAudio) {
- this.currentAudio.instance.stop()
- }
-
- // check if there is audio in queue
- if (!this.audioQueue[0]) {
- console.warn("No audio in queue")
-
- this.currentAudio = null
-
- // if there is no audio in queue, close the embbeded media player
- this.destroyPlayer()
-
- return false
- }
-
- // move current audio to history
- this.audioQueueHistory.push(this.currentAudio)
-
- this.currentAudio = this.audioQueue.shift()
-
- this.playCurrentAudio()
-
- this.preloadAudio()
- }
-
- destroyPlayer() {
- this.currentDomWindow.destroy()
- this.currentDomWindow = null
- }
-
- async createAudioInstance(data) {
- const audio = new Howl({
- src: data.src,
- preload: false,
- //html5: true,
- mute: this.audioMuted,
- volume: this.audioVolume,
- onplay: () => {
- app.eventBus.emit("audioPlayer.playing", data)
- },
- onend: () => {
- app.eventBus.emit("audioPlayer.end", data)
- },
- onload: () => {
- app.eventBus.emit("audioPlayer.preloaded", data)
- },
- onpause: () => {
- app.eventBus.emit("audioPlayer.paused", data)
- },
- onstop: () => {
- app.eventBus.emit("audioPlayer.stopped", data)
- },
- onseek: () => {
- app.eventBus.emit("audioPlayer.seeked", data)
- },
- onvolume: () => {
- app.eventBus.emit("audioPlayer.volumeChanged", data)
- },
- })
-
- return audio
- }
-
- attachEmbbededMediaPlayer() {
- if (this.currentDomWindow) {
- console.warn("EmbbededMediaPlayer already attached")
- return false
- }
-
- this.currentDomWindow = new DOMWindow({
- id: "mediaPlayer"
- })
-
- this.currentDomWindow.render()
- }
-}
\ No newline at end of file
diff --git a/packages/app/src/cores/contextMenu/components/contextMenu/index.less b/packages/app/src/cores/contextMenu/components/contextMenu/index.less
index 21607e0f..19ff4dfe 100755
--- a/packages/app/src/cores/contextMenu/components/contextMenu/index.less
+++ b/packages/app/src/cores/contextMenu/components/contextMenu/index.less
@@ -42,9 +42,7 @@
align-items: center;
justify-content: space-between;
- width: 100%;
-
- padding: 5px 10px 5px 20px;
+ padding: 10px 10px 10px 20px;
transition: all 50ms ease-in-out;
@@ -58,7 +56,7 @@
}
&:active {
- background-color: var(--background-color-primary2);
+ background-color: var(--background-color-primary-2);
transform: scale(0.95);
}
}
diff --git a/packages/app/src/cores/contextMenu/index.js b/packages/app/src/cores/contextMenu/index.js
index 6a665012..610f272f 100755
--- a/packages/app/src/cores/contextMenu/index.js
+++ b/packages/app/src/cores/contextMenu/index.js
@@ -18,7 +18,7 @@ export default class ContextMenuCore extends Core {
clickOutsideToClose: true,
})
- async initialize() {
+ async onInitialize() {
document.addEventListener("contextmenu", this.handleEvent)
}
diff --git a/packages/app/src/cores/i18n/index.js b/packages/app/src/cores/i18n/index.js
index 5b42b8d2..b366f01e 100755
--- a/packages/app/src/cores/i18n/index.js
+++ b/packages/app/src/cores/i18n/index.js
@@ -15,14 +15,14 @@ export function extractLocaleFromPath(path = "") {
const messageImports = import.meta.glob("schemas/translations/*.json")
export default class I18nCore extends Core {
- events = {
+ onEvents = {
"changeLanguage": (locale) => {
this.loadAsyncLanguage(locale)
}
}
- initialize = async () => {
- let locale = app.settings.get("language") ?? DEFAULT_LOCALE
+ onInitialize = async () => {
+ let locale = app.cores.settings.get("language") ?? DEFAULT_LOCALE
if (!SUPPORTED_LOCALES.includes(locale)) {
locale = DEFAULT_LOCALE
diff --git a/packages/app/src/cores/index.js b/packages/app/src/cores/index.js
index 493b78be..d1471307 100755
--- a/packages/app/src/cores/index.js
+++ b/packages/app/src/cores/index.js
@@ -2,7 +2,6 @@ import SettingsCore from "./settings"
import APICore from "./api"
import StyleCore from "./style"
import PermissionsCore from "./permissions"
-import SearchCore from "./search"
import ContextMenuCore from "./contextMenu"
import I18nCore from "./i18n"
@@ -10,13 +9,12 @@ import NotificationsCore from "./notifications"
import ShortcutsCore from "./shortcuts"
import SoundCore from "./sound"
-import AudioPlayer from "./audioPlayer"
+import Player from "./player"
// DEFINE LOAD ORDER HERE
export default [
SettingsCore,
APICore,
- SearchCore,
PermissionsCore,
StyleCore,
I18nCore,
@@ -24,6 +22,6 @@ export default [
NotificationsCore,
ShortcutsCore,
- AudioPlayer,
+ Player,
ContextMenuCore,
]
\ No newline at end of file
diff --git a/packages/app/src/cores/notifications/index.jsx b/packages/app/src/cores/notifications/index.jsx
index b6be8f89..6b624d03 100755
--- a/packages/app/src/cores/notifications/index.jsx
+++ b/packages/app/src/cores/notifications/index.jsx
@@ -6,7 +6,7 @@ import { Translation } from "react-i18next"
import { Haptics } from "@capacitor/haptics"
export default class NotificationCore extends Core {
- events = {
+ onEvents = {
"changeNotificationsSoundVolume": (value) => {
this.playAudio({ soundVolume: value })
},
@@ -17,12 +17,12 @@ export default class NotificationCore extends Core {
}
}
- publicMethods = {
+ registerToApp = {
notification: this
}
getSoundVolume = () => {
- return (window.app.settings.get("notifications_sound_volume") ?? 50) / 100
+ return (window.app.cores.settings.get("notifications_sound_volume") ?? 50) / 100
}
new = (notification, options = {}) => {
@@ -31,7 +31,9 @@ export default class NotificationCore extends Core {
this.playAudio(options)
}
- notify = (notification, options = {}) => {
+ notify = (notification, options = {
+ type: "info"
+ }) => {
if (typeof notification === "string") {
notification = {
title: "New notification",
@@ -39,20 +41,69 @@ export default class NotificationCore extends Core {
}
}
- Notf.open({
- message:
- {(t) => t(notification.title)}
- ,
- description:
- {(t) => t(notification.description)}
- ,
+ const notfObj = {
duration: notification.duration ?? 4,
- icon: React.isValidElement(notification.icon) ? notification.icon : (createIconRender(notification.icon) ?? ),
- })
+ }
+
+ if (notification.message) {
+ switch (typeof notification.message) {
+ case "function": {
+ notfObj.message = React.createElement(notification.message)
+
+ break
+ }
+ case "object": {
+ notfObj.message = notification.message
+
+ break
+ }
+ default: {
+ notfObj.message =
+ {(t) => t(notification.message)}
+
+
+ break
+ }
+ }
+ }
+
+ if (notification.description) {
+ switch (typeof notification.description) {
+ case "function": {
+ notfObj.description = React.createElement(notification.description)
+
+ break
+ }
+
+ case "object": {
+ notfObj.description = notification.description
+
+ break
+ }
+
+ default: {
+ notfObj.description =
+ {(t) => t(notification.description)}
+
+
+ break
+ }
+ }
+ }
+
+ if (notification.icon) {
+ notfObj.icon = React.isValidElement(notification.icon) ? notification.icon : (createIconRender(notification.icon) ?? )
+ }
+
+ if (typeof Notf[options.type] !== "function") {
+ options.type = "info"
+ }
+
+ return Notf[options.type](notfObj)
}
playHaptic = async (options = {}) => {
- const vibrationEnabled = options.vibrationEnabled ?? window.app.settings.get("notifications_vibrate")
+ const vibrationEnabled = options.vibrationEnabled ?? window.app.cores.settings.get("notifications_vibrate")
if (vibrationEnabled) {
await Haptics.vibrate()
@@ -60,7 +111,7 @@ export default class NotificationCore extends Core {
}
playAudio = (options = {}) => {
- const soundEnabled = options.soundEnabled ?? window.app.settings.get("notifications_sound")
+ const soundEnabled = options.soundEnabled ?? window.app.cores.settings.get("notifications_sound")
const soundVolume = options.soundVolume ? options.soundVolume / 100 : this.getSoundVolume()
if (soundEnabled) {
diff --git a/packages/app/src/cores/permissions/index.js b/packages/app/src/cores/permissions/index.js
index 320f7184..927ffc21 100755
--- a/packages/app/src/cores/permissions/index.js
+++ b/packages/app/src/cores/permissions/index.js
@@ -6,20 +6,22 @@ import SessionModel from "models/session"
export default class PermissionsCore extends Core {
static namespace = "permissions"
static dependencies = ["api"]
- static public = ["hasAdmin", "checkUserIdIsSelf", "hasPermission"]
- userData = null
- isUserAdmin = null
+ public = {
+ hasAdmin: this.hasAdmin,
+ checkUserIdIsSelf: this.checkUserIdIsSelf,
+ hasPermission: this.hasPermission,
+ }
- hasAdmin = async () => {
+ async hasAdmin() {
return await UserModel.hasAdmin()
}
- checkUserIdIsSelf = (userId) => {
- return SessionModel.user_id === userId
+ checkUserIdIsSelf(user_id) {
+ return SessionModel.user_id === user_id
}
- hasPermission = async (permission) => {
+ async hasPermission(permission) {
let query = []
if (Array.isArray(permission)) {
diff --git a/packages/app/src/cores/player/index.jsx b/packages/app/src/cores/player/index.jsx
new file mode 100644
index 00000000..c85edc1b
--- /dev/null
+++ b/packages/app/src/cores/player/index.jsx
@@ -0,0 +1,715 @@
+import Core from "evite/src/core"
+import { Observable } from "object-observer"
+import store from "store"
+// import { createRealTimeBpmProcessor } from "realtime-bpm-analyzer"
+
+import { EmbbededMediaPlayer } from "components"
+import { DOMWindow } from "components/RenderWindow"
+
+class AudioPlayerStorage {
+ static storeKey = "audioPlayer"
+
+ static get(key) {
+ const data = store.get(AudioPlayerStorage.storeKey)
+
+ if (data) {
+ return data[key]
+ }
+
+ return null
+ }
+
+ static set(key, value) {
+ const data = store.get(AudioPlayerStorage.storeKey) ?? {}
+
+ data[key] = value
+
+ store.set(AudioPlayerStorage.storeKey, data)
+
+ return data
+ }
+}
+
+export default class Player extends Core {
+ static namespace = "player"
+
+ currentDomWindow = null
+
+ audioContext = new AudioContext()
+
+ audioQueueHistory = []
+ audioQueue = []
+ audioProcessors = []
+
+ currentAudioInstance = null
+
+ state = Observable.from({
+ loading: false,
+ audioMuted: AudioPlayerStorage.get("mute") ?? false,
+ playbackMode: AudioPlayerStorage.get("mode") ?? "repeat",
+ audioVolume: AudioPlayerStorage.get("volume") ?? 0.3,
+ velocity: AudioPlayerStorage.get("velocity") ?? 1,
+
+ currentAudioManifest: null,
+ playbackStatus: "stopped",
+ crossfading: false,
+ trackBPM: 0,
+ })
+
+ public = {
+ audioContext: this.audioContext,
+ attachPlayerComponent: this.attachPlayerComponent.bind(this),
+ detachPlayerComponent: this.detachPlayerComponent.bind(this),
+ toogleMute: this.toogleMute.bind(this),
+ volume: this.volume.bind(this),
+ start: this.start.bind(this),
+ startPlaylist: this.startPlaylist.bind(this),
+ playback: {
+ mode: function (mode) {
+ if (mode) {
+ this.state.playbackMode = mode
+ }
+
+ return this.state.playbackMode
+ }.bind(this),
+ toogle: function () {
+ if (!this.currentAudioInstance) {
+ console.error("No audio instance")
+ return null
+ }
+
+ if (this.currentAudioInstance.audioElement.paused) {
+ this.public.playback.play()
+ } else {
+ this.public.playback.pause()
+ }
+ }.bind(this),
+ play: function () {
+ if (!this.currentAudioInstance) {
+ console.error("No audio instance")
+ return null
+ }
+
+ // set gain exponentially
+ this.currentAudioInstance.gainNode.gain.linearRampToValueAtTime(
+ this.state.audioVolume,
+ this.audioContext.currentTime + 0.1
+ )
+
+ setTimeout(() => {
+ this.currentAudioInstance.audioElement.play()
+ }, 100)
+
+ }.bind(this),
+ pause: function () {
+ if (!this.currentAudioInstance) {
+ console.error("No audio instance")
+ return null
+ }
+
+ // set gain exponentially
+ this.currentAudioInstance.gainNode.gain.linearRampToValueAtTime(
+ 0.0001,
+ this.audioContext.currentTime + 0.1
+ )
+
+ setTimeout(() => {
+ this.currentAudioInstance.audioElement.pause()
+ }, 100)
+ }.bind(this),
+ next: this.next.bind(this),
+ previous: this.previous.bind(this),
+ stop: this.stop.bind(this),
+ status: function () {
+ return this.state.playbackStatus
+ }.bind(this),
+ },
+ getState: function (key) {
+ if (key) {
+ return this.state[key]
+ }
+
+ return this.state
+ }.bind(this),
+ seek: this.seek.bind(this),
+ duration: this.duration.bind(this),
+ velocity: this.velocity.bind(this),
+ }
+
+ async onInitialize() {
+ Observable.observe(this.state, (changes) => {
+ changes.forEach((change) => {
+ if (change.type === "update") {
+ switch (change.path[0]) {
+ case "trackBPM": {
+ app.eventBus.emit("player.bpm.update", change.object.trackBPM)
+
+ break
+ }
+ case "crossfading": {
+ app.eventBus.emit("player.crossfading.update", change.object.crossfading)
+
+ console.log("crossfading", change.object.crossfading)
+
+ break
+ }
+ case "loading": {
+ app.eventBus.emit("player.loading.update", change.object.loading)
+
+ break
+ }
+ case "currentAudioManifest": {
+ app.eventBus.emit("player.current.update", change.object.currentAudioManifest)
+
+ break
+ }
+ case "audioMuted": {
+ AudioPlayerStorage.set("muted", change.object.audioMuted)
+
+ app.eventBus.emit("player.mute.update", change.object.audioMuted)
+
+ break
+ }
+ case "audioVolume": {
+ AudioPlayerStorage.set("volume", change.object.audioVolume)
+
+ app.eventBus.emit("player.volume.update", change.object.audioVolume)
+
+ break
+ }
+ case "velocity": {
+ AudioPlayerStorage.set("velocity", change.object.velocity)
+
+ app.eventBus.emit("player.velocity.update", change.object.velocity)
+
+ break
+ }
+ case "playbackMode": {
+ AudioPlayerStorage.set("mode", change.object.playbackMode)
+
+ this.currentAudioInstance.audioElement.loop = change.object.playbackMode === "repeat"
+
+ app.eventBus.emit("player.mode.update", change.object.playbackMode)
+
+ break
+ }
+ case "playbackStatus": {
+ app.eventBus.emit("player.status.update", change.object.playbackStatus)
+
+ break
+ }
+ }
+ }
+ })
+ })
+ }
+
+ // async instanciateRealtimeAnalyzerNode() {
+ // if (this.realtimeAnalyzerNode) {
+ // return false
+ // }
+
+ // this.realtimeAnalyzerNode = await createRealTimeBpmProcessor(this.audioContext)
+
+ // this.realtimeAnalyzerNode.port.onmessage = (event) => {
+ // if (event.data.result.bpm[0]) {
+ // if (this.state.trackBPM != event.data.result.bpm[0].tempo) {
+ // this.state.trackBPM = event.data.result.bpm[0].tempo
+ // }
+ // }
+
+ // if (event.data.message === "BPM_STABLE") {
+ // console.log("BPM STABLE", event.data.result)
+ // }
+ // }
+ // }
+
+ attachPlayerComponent() {
+ if (this.currentDomWindow) {
+ console.warn("EmbbededMediaPlayer already attached")
+ return false
+ }
+
+ this.currentDomWindow = new DOMWindow({
+ id: "mediaPlayer"
+ })
+
+ this.currentDomWindow.render()
+ }
+
+ detachPlayerComponent() {
+ if (!this.currentDomWindow) {
+ console.warn("EmbbededMediaPlayer not attached")
+ return false
+ }
+
+ this.currentDomWindow.close()
+ this.currentDomWindow = null
+ }
+
+ destroyCurrentInstance() {
+ if (!this.currentAudioInstance) {
+ return false
+ }
+
+ // stop playback
+ if (this.currentAudioInstance.audioElement) {
+ this.currentAudioInstance.audioElement.pause()
+ }
+
+ this.currentAudioInstance = null
+ }
+
+ async createInstance(manifest) {
+ if (!manifest) {
+ console.error("Manifest is required")
+ return false
+ }
+
+ if (typeof manifest === "string") {
+ manifest = {
+ src: manifest,
+ }
+ }
+
+ if (!manifest.src && !manifest.source) {
+ console.error("Manifest source is required")
+ return false
+ }
+
+ const audioSource = manifest.src ?? manifest.source
+
+ if (!manifest.title) {
+ manifest.title = audioSource.split("/").pop()
+ }
+
+ let instanceObj = {
+ audioElement: new Audio(audioSource),
+ audioSource: audioSource,
+ manifest: manifest,
+ track: null,
+ gainNode: null,
+ crossfadeInterval: null,
+ crossfading: false
+ }
+
+ instanceObj.audioElement.loop = this.state.playbackMode === "repeat"
+ instanceObj.audioElement.crossOrigin = "anonymous"
+ instanceObj.audioElement.preload = "metadata"
+
+ const createCrossfadeInterval = () => {
+ console.warn("Crossfader is not supported yet")
+ return false
+
+ const crossfadeDuration = app.cores.settings.get("player.crossfade")
+
+ if (crossfadeDuration === 0) {
+ return false
+ }
+
+ if (instanceObj.crossfadeInterval) {
+ clearInterval(instanceObj.crossfadeInterval)
+ }
+
+ // fix audioElement.duration to be the duration of the audio minus the crossfade time
+ const crossfadeTime = Number.parseFloat(instanceObj.audioElement.duration).toFixed(0) - crossfadeDuration
+
+ const crossfaderTick = () => {
+ // check the if current audio has reached the crossfade time
+ if (instanceObj.audioElement.currentTime >= crossfadeTime) {
+ instanceObj.crossfading = true
+
+ this.next({
+ crossfading: crossfadeDuration,
+ instance: instanceObj
+ })
+
+ clearInterval(instanceObj.crossfadeInterval)
+ }
+ }
+
+ crossfaderTick()
+
+ instanceObj.crossfadeInterval = setInterval(() => {
+ crossfaderTick()
+ }, 1000)
+ }
+
+ // handle on end
+ instanceObj.audioElement.addEventListener("ended", () => {
+ // cancel if is crossfading
+ if (this.state.crossfading || instanceObj.crossfading) {
+ return false
+ }
+
+ this.next()
+ })
+
+ instanceObj.audioElement.addEventListener("play", () => {
+ this.state.loading = false
+
+ this.state.playbackStatus = "playing"
+
+ instanceObj.audioElement.loop = this.state.playbackMode === "repeat"
+ })
+
+ instanceObj.audioElement.addEventListener("playing", () => {
+ this.state.loading = false
+
+ this.state.playbackStatus = "playing"
+
+ if (this.waitUpdateTimeout) {
+ clearTimeout(this.waitUpdateTimeout)
+ this.waitUpdateTimeout = null
+ }
+
+ createCrossfadeInterval()
+ })
+
+ instanceObj.audioElement.addEventListener("pause", () => {
+ if (this.state.crossfading || instanceObj.crossfading) {
+ return false
+ }
+
+ this.state.playbackStatus = "paused"
+
+ if (instanceObj.crossfadeInterval) {
+ clearInterval(instanceObj.crossfadeInterval)
+ }
+ })
+
+ instanceObj.audioElement.addEventListener("durationchange", (duration) => {
+ if (instanceObj.audioElement.paused) {
+ return
+ }
+
+ app.eventBus.emit("player.duration.update", duration)
+ })
+
+ instanceObj.audioElement.addEventListener("waiting", () => {
+ if (instanceObj.audioElement.paused) {
+ return
+ }
+
+ if (this.waitUpdateTimeout) {
+ clearTimeout(this.waitUpdateTimeout)
+ this.waitUpdateTimeout = null
+ }
+
+ // if takes more than 200ms to load, update loading state
+ this.waitUpdateTimeout = setTimeout(() => {
+ this.state.loading = true
+ }, 200)
+ })
+
+ instanceObj.audioElement.addEventListener("seeked", () => {
+ app.eventBus.emit("player.seek.update", instanceObj.audioElement.currentTime)
+ createCrossfadeInterval()
+ })
+
+ //await this.instanciateRealtimeAnalyzerNode()
+
+ instanceObj.track = this.audioContext.createMediaElementSource(instanceObj.audioElement)
+
+ instanceObj.gainNode = this.audioContext.createGain()
+
+ instanceObj.gainNode.gain.value = this.state.audioVolume
+
+ const processorsList = [
+ instanceObj.gainNode,
+ ...this.audioProcessors,
+ ]
+
+ let lastProcessor = null
+
+ processorsList.forEach((processor) => {
+ if (lastProcessor) {
+ lastProcessor.connect(processor)
+ } else {
+ instanceObj.track.connect(processor)
+ }
+
+ lastProcessor = processor
+ })
+
+ lastProcessor.connect(this.audioContext.destination)
+
+ return instanceObj
+ }
+
+ play(instance, params = {}) {
+ if (typeof instance === "number") {
+ instance = this.audioQueue[instance]
+ }
+
+ if (!instance) {
+ throw new Error("Audio instance is required")
+ }
+
+ if (this.audioContext.state === "suspended") {
+ this.audioContext.resume()
+ }
+
+ this.currentAudioInstance = instance
+ this.state.currentAudioManifest = instance.manifest
+
+ // set time to 0
+ this.currentAudioInstance.audioElement.currentTime = 0
+
+ if (params.time >= 0) {
+ this.currentAudioInstance.audioElement.currentTime = params.time
+ }
+
+ if (params.volume >= 0) {
+ this.currentAudioInstance.gainNode.gain.value = params.volume
+ }
+
+ if (this.realtimeAnalyzerNode) {
+ const filter = this.audioContext.createBiquadFilter()
+
+ filter.type = "lowpass"
+
+ this.currentAudioInstance.track.connect(filter).connect(this.realtimeAnalyzerNode)
+ }
+
+ instance.audioElement.play()
+
+ if (!this.currentDomWindow) {
+ // FIXME: i gonna attach the player component after 500ms to avoid error calculating the player position and duration on the first play
+ setTimeout(() => {
+ this.attachPlayerComponent()
+ }, 300)
+ }
+ }
+
+ async startPlaylist(playlist, startIndex = 0) {
+ // playlist is an array of audio manifests
+ if (!playlist || !Array.isArray(playlist)) {
+ throw new Error("Playlist is required")
+ }
+
+ this.destroyCurrentInstance()
+
+ // clear current queue
+ this.audioQueue = []
+
+ this.audioQueueHistory = []
+
+ this.state.loading = true
+
+ for await (const [index, manifest] of playlist.entries()) {
+ const instance = await this.createInstance(manifest)
+
+ if (index < startIndex) {
+ this.audioQueueHistory.push(instance)
+ } else {
+ this.audioQueue.push(instance)
+ }
+ }
+
+ // play first audio
+ this.play(this.audioQueue[0])
+ }
+
+ async start(manifest) {
+ this.destroyCurrentInstance()
+
+ const instance = await this.createInstance(manifest)
+
+ this.audioQueue = [instance]
+
+ this.audioQueueHistory = []
+
+ this.state.loading = true
+
+ this.play(this.audioQueue[0])
+ }
+
+ next(params = {}) {
+ if (this.audioQueue.length > 0) {
+ // move current audio instance to history
+ this.audioQueueHistory.push(this.audioQueue.shift())
+ }
+
+ // check if there is a next audio in queue
+ if (this.audioQueue.length === 0) {
+ console.log("no more audio on queue, stopping playback")
+
+ this.destroyCurrentInstance()
+
+ this.state.playbackStatus = "stopped"
+ this.state.currentAudioManifest = null
+
+ return false
+ }
+
+ const nextParams = {}
+ let nextIndex = 0
+
+ if (params.crossfading && params.crossfading > 0 && this.state.playbackStatus === "playing" && params.instance) {
+ this.state.crossfading = true
+
+ // calculate the current audio context time with the current audio duration (subtracting time offset)
+ const linearFadeoutTime = Number(
+ this.audioContext.currentTime +
+ Number(params.crossfading.toFixed(2))
+ )
+
+ console.log("linearFadeoutTime", this.audioContext.currentTime, linearFadeoutTime)
+
+ console.log("crossfading offset", ( this.currentAudioInstance.audioElement.duration - this.currentAudioInstance.audioElement.currentTime) - Number(params.crossfading.toFixed(2)))
+
+ params.instance.gainNode.gain.linearRampToValueAtTime(0.00001, linearFadeoutTime)
+
+ nextParams.volume = 0
+
+ setTimeout(() => {
+ this.state.crossfading = false
+ }, params.crossfading)
+ } else {
+ this.destroyCurrentInstance()
+ }
+
+ // if is in shuffle mode, play a random audio
+ if (this.state.playbackMode === "shuffle") {
+ nextIndex = Math.floor(Math.random() * this.audioQueue.length)
+ }
+
+ // play next audio
+ this.play(this.audioQueue[nextIndex], nextParams)
+
+ if (this.state.crossfading) {
+ // calculate the current audio context time (fixing times) with the crossfading duration
+ const linearFadeinTime = Number(this.audioContext.currentTime + Number(params.crossfading.toFixed(2)))
+
+ console.log("linearFadeinTime", this.audioContext.currentTime, linearFadeinTime)
+
+ // set a linear ramp to 1
+ this.currentAudioInstance.gainNode.gain.linearRampToValueAtTime(
+ this.state.audioVolume,
+ linearFadeinTime
+ )
+ }
+ }
+
+ previous() {
+ this.destroyCurrentInstance()
+
+ if (this.audioQueueHistory.length > 0) {
+ // move current audio instance to queue
+ this.audioQueue.unshift(this.audioQueueHistory.pop())
+
+ // play previous audio
+ this.play(this.audioQueue[0])
+ }
+
+ // check if there is a previous audio in history
+ if (this.audioQueueHistory.length === 0) {
+ // if there is no previous audio, start again from the first audio
+ this.play(this.audioQueue[0])
+ }
+ }
+
+ stop() {
+ this.destroyCurrentInstance()
+
+ this.state.playbackStatus = "stopped"
+ this.state.currentAudioManifest = null
+
+ this.audioQueue = []
+ }
+
+ toogleMute(to) {
+ this.state.audioMuted = to ?? !this.state.audioMuted
+
+ if (this.currentAudioInstance) {
+ this.currentAudioInstance.audioElement.muted = this.state.audioMuted
+ }
+
+ return this.state.audioMuted
+ }
+
+ volume(volume) {
+ if (typeof volume !== "number") {
+ return this.state.audioVolume
+ }
+
+ if (volume > 1) {
+ console.log(app.cores.settings.get("player.allowVolumeOver100"))
+
+ if (!app.cores.settings.get("player.allowVolumeOver100")) {
+ volume = 1
+ }
+ }
+
+ if (volume < 0) {
+ volume = 0
+ }
+
+ this.state.audioVolume = volume
+
+ if (this.currentAudioInstance) {
+ if (this.currentAudioInstance.gainNode) {
+ this.currentAudioInstance.gainNode.gain.value = this.state.audioVolume
+ }
+ }
+
+ return this.state.audioVolume
+ }
+
+ seek(time) {
+ if (!this.currentAudioInstance) {
+ return false
+ }
+
+ // if time not provided, return current time
+ if (typeof time === "undefined") {
+ return this.currentAudioInstance.audioElement.currentTime
+ }
+
+ // if time is provided, seek to that time
+ if (typeof time === "number") {
+ this.currentAudioInstance.audioElement.currentTime = time
+
+ return time
+ }
+ }
+
+ duration() {
+ if (!this.currentAudioInstance) {
+ return false
+ }
+
+ return this.currentAudioInstance.audioElement.duration
+ }
+
+ loop(to) {
+ if (typeof to !== "boolean") {
+ console.warn("Loop must be a boolean")
+ return false
+ }
+
+ this.state.loop = to ?? !this.state.loop
+
+ if (this.currentAudioInstance) {
+ this.currentAudioInstance.audioElement.loop = this.state.loop
+ }
+
+ return this.state.loop
+ }
+
+ velocity(to) {
+ if (typeof to !== "number") {
+ console.warn("Velocity must be a number")
+ return false
+ }
+
+ this.state.velocity = to
+
+ if (this.currentAudioInstance) {
+ this.currentAudioInstance.audioElement.playbackRate = this.state.velocity
+ }
+
+ return this.state.velocity
+ }
+}
\ No newline at end of file
diff --git a/packages/app/src/cores/remoteStorage/index.js b/packages/app/src/cores/remoteStorage/index.js
index d18df5b8..845da79b 100755
--- a/packages/app/src/cores/remoteStorage/index.js
+++ b/packages/app/src/cores/remoteStorage/index.js
@@ -8,7 +8,7 @@ export default class RemoteStorage extends Core {
connection = null
- async initialize() {
+ async onInitialize() {
}
diff --git a/packages/app/src/cores/search/index.js b/packages/app/src/cores/search/index.js
deleted file mode 100755
index a43db953..00000000
--- a/packages/app/src/cores/search/index.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import Core from "evite/src/core"
-
-export default class Search extends Core {
- static namespace = "searchEngine"
- static dependencies = ["api"]
- static public = ["search"]
-
- apiBridge = null
-
- search = async (keywords, params = {}) => {
- if (!this.apiBridge) {
- this.apiBridge = app.api.withEndpoints("main")
- }
-
- return await this.apiBridge.get.search(undefined, { keywords: keywords, params })
- }
-}
\ No newline at end of file
diff --git a/packages/app/src/cores/settings/index.js b/packages/app/src/cores/settings/index.js
index 6f264f12..b8fee9b8 100755
--- a/packages/app/src/cores/settings/index.js
+++ b/packages/app/src/cores/settings/index.js
@@ -4,52 +4,60 @@ import defaultSettings from "schemas/defaultSettings.json"
import { Observable } from "rxjs"
export default class SettingsCore extends Core {
- storeKey = "app_settings"
+ static namespace = "settings"
- settings = store.get(this.storeKey) ?? {}
+ static storeKey = "app_settings"
- publicMethods = {
- settings: this
+ public = {
+ is: this.is,
+ set: this.set,
+ get: this.get,
+ getDefaults: this.getDefaults,
+ withEvent: this.withEvent,
}
- initialize() {
- this.fulfillUndefinedWithDefaults()
- }
+ onInitialize() {
+ const settings = this.get()
- fulfillUndefinedWithDefaults = () => {
+ // fulfillUndefinedWithDefaults
Object.keys(defaultSettings).forEach((key) => {
const value = defaultSettings[key]
// Only set default if value is undefined
- if (typeof this.settings[key] === "undefined") {
- this.settings[key] = value
+ if (typeof settings[key] === "undefined") {
+ this.set(key, value)
}
})
}
- is = (key, value) => {
- return this.settings[key] === value
+ is(key, value) {
+ return this.get(key) === value
}
- set = (key, value) => {
- this.settings[key] = value
- store.set(this.storeKey, this.settings)
+ set(key, value) {
+ const settings = this.get()
+
+ settings[key] = value
+
+ store.set(SettingsCore.storeKey, settings)
window.app.eventBus.emit("setting.update", { key, value })
window.app.eventBus.emit(`setting.update.${key}`, value)
- return this.settings
+ return settings
}
- get = (key) => {
+ get(key) {
+ const settings = store.get(SettingsCore.storeKey) ?? {}
+
if (typeof key === "undefined") {
- return this.settings
+ return settings
}
- return this.settings[key]
+ return settings[key]
}
- getDefaults = (key) => {
+ getDefaults(key) {
if (typeof key === "undefined") {
return defaultSettings
}
@@ -57,8 +65,8 @@ export default class SettingsCore extends Core {
return defaultSettings[key]
}
- withEvent = (listenEvent, defaultValue) => {
- let value = defaultValue ?? this.settings[key] ?? false
+ withEvent(listenEvent, defaultValue) {
+ let value = defaultValue ?? false
const observable = new Observable((subscriber) => {
subscriber.next(value)
diff --git a/packages/app/src/cores/shortcuts/index.js b/packages/app/src/cores/shortcuts/index.js
index f6678b1b..3b730418 100755
--- a/packages/app/src/cores/shortcuts/index.js
+++ b/packages/app/src/cores/shortcuts/index.js
@@ -3,7 +3,7 @@ import Core from "evite/src/core"
export default class ShortcutsCore extends Core {
shortcutsRegister = []
- publicMethods = {
+ registerToApp = {
shortcuts: this
}
diff --git a/packages/app/src/cores/sound/index.js b/packages/app/src/cores/sound/index.js
index cc1e3f8d..2fb2cf11 100755
--- a/packages/app/src/cores/sound/index.js
+++ b/packages/app/src/cores/sound/index.js
@@ -3,36 +3,29 @@ import { Howl } from "howler"
import config from "config"
export default class SoundCore extends Core {
- sounds = {}
+ static namespace = "sound"
- publicMethods = {
- sound: this,
+ public = {
+ play: this.play,
+ getSounds: this.getSounds,
}
- async initialize() {
- this.sounds = await this.getSounds()
- }
-
- getSounds = async () => {
+ async getSounds() {
// TODO: Load custom soundpacks manifests
let soundPack = config.defaultSoundPack ?? {}
- Object.keys(soundPack).forEach((key) => {
- const src = soundPack[key]
-
- soundPack[key] = (options) => new Howl({
- volume: window.app.settings.get("generalAudioVolume") ?? 0.5,
- ...options,
- src: [src],
- })
- })
-
return soundPack
}
- play = (name, options) => {
- if (this.sounds[name]) {
- return this.sounds[name](options).play()
+ async play(name, options) {
+ let soundPack = await this.getSounds()
+
+ if (soundPack[name]) {
+ return new Howl({
+ volume: window.app.cores.settings.get("generalAudioVolume") ?? 0.5,
+ ...options,
+ src: [soundPack[name]],
+ }).play()
} else {
console.error(`Sound [${name}] not found or is not available.`)
return false
diff --git a/packages/app/src/cores/style/index.jsx b/packages/app/src/cores/style/index.jsx
index 1bf8f4a2..e6fd53bc 100755
--- a/packages/app/src/cores/style/index.jsx
+++ b/packages/app/src/cores/style/index.jsx
@@ -1,87 +1,118 @@
+import React from "react"
+import ReactDOM from "react-dom"
+import SVG from "react-inlinesvg"
+
import Core from "evite/src/core"
import config from "config"
import store from "store"
-import { ConfigProvider } from "antd"
+import { ConfigProvider, theme } from "antd"
+import RemoteSVGToComponent from "components/RemoteSVGToComponent"
+
+const variantToAlgorithm = {
+ light: theme.defaultAlgorithm,
+ dark: theme.darkAlgorithm,
+}
+
+export class ThemeProvider extends React.Component {
+ state = {
+ useAlgorigthm: app.cores.style.currentVariant ?? "dark",
+ useCompactMode: app.cores.style.getValue("compact-mode"),
+ }
+
+ handleUpdate = (update) => {
+ console.log("[THEME] Update", update)
+
+ if (update.themeVariant) {
+ this.setState({
+ useAlgorigthm: update.themeVariant
+ })
+ }
+
+ this.setState({
+ useCompactMode: update["compact-mode"]
+ })
+ }
+
+ componentDidMount() {
+ app.eventBus.on("style.update", this.handleUpdate)
+ }
+
+ componentWillUnmount() {
+ app.eventBus.off("style.update", this.handleUpdate)
+ }
+
+ render() {
+ const themeAlgorithms = [
+ variantToAlgorithm[this.state.useAlgorigthm ?? "dark"],
+ ]
+
+ if (this.state.useCompactMode) {
+ themeAlgorithms.push(theme.compactAlgorithm)
+ }
+
+ return
+ {this.props.children}
+
+ }
+}
export default class StyleCore extends Core {
- themeManifestStorageKey = "theme"
- modificationStorageKey = "themeModifications"
+ static namespace = "style"
- theme = null
- mutation = null
- currentVariant = null
+ static themeManifestStorageKey = "theme"
+ static modificationStorageKey = "themeModifications"
- events = {
- "style.compactMode": (value = !window.app.settings.get("style.compactMode")) => {
- if (value) {
- return this.update({
- layoutMargin: 0,
- layoutPadding: 0,
- })
- }
+ static get rootVariables() {
+ let attributes = document.documentElement.getAttribute("style").trim().split(";")
- return this.update({
- layoutMargin: this.getValue("layoutMargin"),
- layoutPadding: this.getValue("layoutPadding"),
- })
- },
- "style.autoDarkModeToogle": (value) => {
- if (value === true) {
- this.handleAutoColorScheme()
- } else {
- this.applyVariant(this.getStoragedVariant())
- }
- },
- "theme.applyVariant": (value) => {
- this.applyVariant(value)
- this.setVariant(value)
- },
- "modifyTheme": (value) => {
- this.update(value)
- this.setModifications(this.mutation)
- },
- "resetTheme": () => {
- this.resetDefault()
- }
+ attributes = attributes.slice(0, (attributes.length - 1))
+
+ attributes = attributes.map((variable) => {
+ let [key, value] = variable.split(":")
+ key = key.split("--")[1]
+
+ return [key, value]
+ })
+
+ return Object.fromEntries(attributes)
}
- publicMethods = {
- style: this
+ static get storagedTheme() {
+ return store.get(StyleCore.themeManifestStorageKey)
}
- static get currentVariant() {
- return document.documentElement.style.getPropertyValue("--themeVariant")
+ static get storagedVariant() {
+ return app.cores.settings.get("style.darkMode") ? "dark" : "light"
}
- handleAutoColorScheme() {
- const prefered = window.matchMedia("(prefers-color-scheme: light)")
+ static set storagedModifications(modifications) {
+ return store.set(StyleCore.modificationStorageKey, modifications)
+ }
- if (!prefered.matches) {
- this.applyVariant("dark")
+ static get storagedModifications() {
+ return store.get(StyleCore.modificationStorageKey) ?? {}
+ }
+
+ async onInitialize() {
+ if (StyleCore.storagedTheme) {
+ // TODO: Start remote theme loader
} else {
- this.applyVariant("light")
- }
- }
-
- initialize = async () => {
- let theme = this.getStoragedTheme()
-
- const modifications = this.getStoragedModifications()
- const variantKey = this.getStoragedVariant()
-
- if (!theme) {
- // load default theme
- theme = this.getDefaultTheme()
- } else {
- // load URL and initialize theme
+ this.public.theme = config.defaultTheme
}
- // set global theme
- this.theme = theme
+ const modifications = StyleCore.storagedModifications
+ const variantKey = StyleCore.storagedVariant
// override with static vars
- if (theme.staticVars) {
- this.update(theme.staticVars)
+ if (this.public.theme.defaultVars) {
+ this.update(this.public.theme.defaultVars)
}
// override theme with modifications
@@ -96,104 +127,130 @@ export default class StyleCore extends Core {
window.matchMedia("(prefers-color-scheme: light)").addListener(() => {
console.log(`[THEME] Auto color scheme changed`)
- if (window.app.settings.get("auto_darkMode")) {
+ if (window.app.cores.settings.get("auto_darkMode")) {
this.handleAutoColorScheme()
}
})
- if (window.app.settings.get("auto_darkMode")) {
+ if (window.app.cores.settings.get("auto_darkMode")) {
this.handleAutoColorScheme()
}
}
- getRootVariables = () => {
- let attributes = document.documentElement.getAttribute("style").trim().split(";")
- attributes = attributes.slice(0, (attributes.length - 1))
- attributes = attributes.map((variable) => {
- let [key, value] = variable.split(":")
- key = key.split("--")[1]
-
- return [key, value]
- })
-
- return Object.fromEntries(attributes)
+ onEvents = {
+ "style.autoDarkModeToogle": (value) => {
+ if (value === true) {
+ this.handleAutoColorScheme()
+ } else {
+ this.applyVariant(StyleCore.storagedVariant)
+ }
+ }
}
- getDefaultTheme = () => {
- // TODO: Use evite CONSTANTS_API
- return config.defaultTheme
+ public = {
+ theme: null,
+ mutation: null,
+ currentVariant: "dark",
+
+ getValue: (...args) => this.getValue(...args),
+ setDefault: () => this.setDefault(),
+ update: (...args) => this.update(...args),
+ applyVariant: (...args) => this.applyVariant(...args),
+ compactMode: (value = !window.app.cores.settings.get("style.compactMode")) => {
+ if (value) {
+ return this.update({
+ layoutMargin: 0,
+ layoutPadding: 0,
+ })
+ }
+
+ return this.update({
+ layoutMargin: this.getValue("layoutMargin"),
+ layoutPadding: this.getValue("layoutPadding"),
+ })
+ },
+ modify: (value) => {
+ this.public.update(value)
+
+ this.applyVariant(this.public.mutation.themeVariant ?? this.public.currentVariant)
+
+ StyleCore.storagedModifications = this.public.mutation
+ },
+ defaultVar: (key) => {
+ if (!key) {
+ return this.public.theme.defaultVars
+ }
+
+ return this.public.theme.defaultVars[key]
+ },
+ storagedVariant: StyleCore.storagedVariant,
+ storagedModifications: StyleCore.storagedModifications,
}
- getStoragedTheme = () => {
- return store.get(this.themeManifestStorageKey)
- }
-
- getStoragedModifications = () => {
- return store.get(this.modificationStorageKey) ?? {}
- }
-
- getStoragedVariant = () => {
- return app.settings.get("themeVariant")
- }
-
- getValue = (key) => {
- const storagedModifications = this.getStoragedModifications()
- const staticValues = this.theme.staticVars
-
+ getValue(key) {
if (typeof key === "undefined") {
return {
- ...staticValues,
- ...storagedModifications
+ ...this.public.theme.defaultVars,
+ ...StyleCore.storagedModifications
}
}
- return storagedModifications[key] || staticValues[key]
+ return StyleCore.storagedModifications[key] || this.public.theme.defaultVars[key]
}
- setVariant = (variationKey) => {
- return app.settings.set("themeVariant", variationKey)
+ setDefault() {
+ store.remove(StyleCore.themeManifestStorageKey)
+ store.remove(StyleCore.modificationStorageKey)
+
+ app.cores.settings.set("colorPrimary", this.public.theme.defaultVars.colorPrimary)
+
+ this.onInitialize()
}
- setModifications = (modifications) => {
- return store.set(this.modificationStorageKey, modifications)
- }
-
- resetDefault = () => {
- store.remove(this.themeManifestStorageKey)
- store.remove(this.modificationStorageKey)
-
- window.app.settings.set("primaryColor", this.theme.staticVars.primaryColor)
-
- this.initialize()
- }
-
- update = (update) => {
+ update(update) {
if (typeof update !== "object") {
return false
}
- this.mutation = {
- ...this.theme.staticVars,
- ...this.mutation,
+ this.public.mutation = {
+ ...this.public.theme.defaultVars,
+ ...this.public.mutation,
...update
}
- Object.keys(this.mutation).forEach(key => {
- document.documentElement.style.setProperty(`--${key}`, this.mutation[key])
+ Object.keys(this.public.mutation).forEach(key => {
+ document.documentElement.style.setProperty(`--${key}`, this.public.mutation[key])
})
- document.documentElement.className = `theme-${this.currentVariant}`
- document.documentElement.style.setProperty(`--themeVariant`, this.currentVariant)
+ app.eventBus.emit("style.update", {
+ ...this.public.mutation,
+ })
- ConfigProvider.config({ theme: this.mutation })
+ ConfigProvider.config({ theme: this.public.mutation })
}
- applyVariant = (variant = (this.theme.defaultVariant ?? "light")) => {
- const values = this.theme.variants[variant]
+ applyVariant(variant = (this.public.theme.defaultVariant ?? "light")) {
+ const values = this.public.theme.variants[variant]
- if (values) {
- this.currentVariant = variant
- this.update(values)
+ if (!values) {
+ console.error(`Variant [${variant}] not found`)
+ return false
+ }
+
+ values.themeVariant = variant
+
+ this.public.currentVariant = variant
+
+ this.update(values)
+ }
+
+ handleAutoColorScheme() {
+ const prefered = window.matchMedia("(prefers-color-scheme: light)")
+
+ if (!prefered.matches) {
+ this.applyVariant("dark")
+ } else {
+ this.applyVariant("light")
}
}
}
\ No newline at end of file