mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-11 03:24:16 +00:00
rewrites to lb cores v2
This commit is contained in:
parent
0e5604a466
commit
2efcc8a3cd
@ -1,7 +1,8 @@
|
|||||||
import Core from "evite/src/core"
|
import Core from "evite/src/core"
|
||||||
import config from "config"
|
|
||||||
import { Bridge } from "linebridge/dist/client"
|
import { Bridge } from "linebridge/dist/client"
|
||||||
import { Session } from "models"
|
|
||||||
|
import config from "config"
|
||||||
|
import { SessionModel } from "models"
|
||||||
|
|
||||||
function generateWSFunctionHandler(socket, type = "listen") {
|
function generateWSFunctionHandler(socket, type = "listen") {
|
||||||
if (!socket) {
|
if (!socket) {
|
||||||
@ -47,15 +48,23 @@ function generateWSFunctionHandler(socket, type = "listen") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class ApiCore extends Core {
|
export default class ApiCore extends Core {
|
||||||
constructor(props) {
|
static namespace = "api"
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.namespaces = Object()
|
excludedExpiredExceptionURL = ["/session/regenerate"]
|
||||||
|
|
||||||
this.onExpiredExceptionEvent = false
|
onExpiredExceptionEvent = false
|
||||||
this.excludedExpiredExceptionURL = ["/regenerate_session_token"]
|
|
||||||
|
|
||||||
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(
|
async customRequest(
|
||||||
@ -83,7 +92,7 @@ export default class ApiCore extends Core {
|
|||||||
payload.headers = {}
|
payload.headers = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionToken = await Session.token
|
const sessionToken = await SessionModel.token
|
||||||
|
|
||||||
if (sessionToken) {
|
if (sessionToken) {
|
||||||
payload.headers["Authorization"] = `Bearer ${sessionToken}`
|
payload.headers["Authorization"] = `Bearer ${sessionToken}`
|
||||||
@ -95,7 +104,7 @@ export default class ApiCore extends Core {
|
|||||||
return await this.namespaces[namepace].httpInterface(payload, ...args)
|
return await this.namespaces[namepace].httpInterface(payload, ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
request = (namespace = "main", method, endpoint, ...args) => {
|
request(namespace = "main", method, endpoint, ...args) {
|
||||||
if (!this.namespaces[namespace]) {
|
if (!this.namespaces[namespace]) {
|
||||||
throw new Error(`Namespace ${namespace} not found`)
|
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)
|
return this.namespaces[namespace].endpoints[method][endpoint](...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
withEndpoints = (namespace = "main") => {
|
withEndpoints(namespace = "main") {
|
||||||
if (!this.namespaces[namespace]) {
|
if (!this.namespaces[namespace]) {
|
||||||
throw new Error(`Namespace ${namespace} not found`)
|
throw new Error(`Namespace ${namespace} not found`)
|
||||||
}
|
}
|
||||||
@ -119,7 +128,7 @@ export default class ApiCore extends Core {
|
|||||||
return this.namespaces[namespace].endpoints
|
return this.namespaces[namespace].endpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBeforeRequest = async (request) => {
|
async handleBeforeRequest(request) {
|
||||||
if (this.onExpiredExceptionEvent) {
|
if (this.onExpiredExceptionEvent) {
|
||||||
if (this.excludedExpiredExceptionURL.includes(request.url)) return
|
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)
|
window.app.eventBus.emit("session.expiredExceptionEvent", refreshToken)
|
||||||
|
|
||||||
this.onExpiredExceptionEvent = true
|
this.onExpiredExceptionEvent = true
|
||||||
|
|
||||||
const expiredToken = await Session.token
|
const expiredToken = await SessionModel.token
|
||||||
|
|
||||||
// exclude regeneration endpoint
|
|
||||||
|
|
||||||
// send request to regenerate token
|
// send request to regenerate token
|
||||||
const response = await this.request("main", "post", "regenerateSessionToken", {
|
const response = await this.customRequest("main", {
|
||||||
expiredToken: expiredToken,
|
method: "POST",
|
||||||
refreshToken,
|
url: "/session/regenerate",
|
||||||
|
data: {
|
||||||
|
expiredToken: expiredToken,
|
||||||
|
refreshToken,
|
||||||
|
}
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error(`Failed to regenerate token: ${error.message}`)
|
console.error(`Failed to regenerate token: ${error.message}`)
|
||||||
return false
|
return false
|
||||||
@ -155,7 +166,7 @@ export default class ApiCore extends Core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set new token
|
// set new token
|
||||||
Session.token = response.token
|
SessionModel.token = response.token
|
||||||
|
|
||||||
//this.namespaces["main"].internalAbortController.abort()
|
//this.namespaces["main"].internalAbortController.abort()
|
||||||
|
|
||||||
@ -165,18 +176,18 @@ export default class ApiCore extends Core {
|
|||||||
window.app.eventBus.emit("session.regenerated")
|
window.app.eventBus.emit("session.regenerated")
|
||||||
}
|
}
|
||||||
|
|
||||||
attachBridge = (key, params) => {
|
attachBridge(key, params) {
|
||||||
return this.namespaces[key] = this.createBridge(params)
|
return this.namespaces[key] = this.createBridge(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
detachBridge = (key) => {
|
detachBridge(key) {
|
||||||
return delete this.namespaces[key]
|
return delete this.namespaces[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
createBridge(params = {}) {
|
createBridge(params = {}) {
|
||||||
const getSessionContext = async () => {
|
const getSessionContext = async () => {
|
||||||
const obj = {}
|
const obj = {}
|
||||||
const token = await Session.token
|
const token = await SessionModel.token
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
// append token to context
|
// append token to context
|
||||||
@ -224,7 +235,7 @@ export default class ApiCore extends Core {
|
|||||||
|
|
||||||
const bridge = new Bridge(bridgeOptions)
|
const bridge = new Bridge(bridgeOptions)
|
||||||
|
|
||||||
// handle main ws events
|
// handle main ws onEvents
|
||||||
const mainWSSocket = bridge.wsInterface.sockets["main"]
|
const mainWSSocket = bridge.wsInterface.sockets["main"]
|
||||||
|
|
||||||
mainWSSocket.on("authenticated", () => {
|
mainWSSocket.on("authenticated", () => {
|
||||||
@ -236,16 +247,23 @@ export default class ApiCore extends Core {
|
|||||||
})
|
})
|
||||||
|
|
||||||
mainWSSocket.on("connect", () => {
|
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)
|
this.autenticateWS(mainWSSocket)
|
||||||
})
|
})
|
||||||
|
|
||||||
mainWSSocket.on("disconnect", (...context) => {
|
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) => {
|
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
|
// generate functions
|
||||||
@ -256,8 +274,8 @@ export default class ApiCore extends Core {
|
|||||||
return bridge
|
return bridge
|
||||||
}
|
}
|
||||||
|
|
||||||
autenticateWS = async (socket) => {
|
async autenticateWS(socket) {
|
||||||
const token = await Session.token
|
const token = await SessionModel.token
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
socket.emit("authenticate", {
|
socket.emit("authenticate", {
|
||||||
|
@ -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(<EmbbededMediaPlayer />)
|
|
||||||
}
|
|
||||||
}
|
|
@ -42,9 +42,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
width: 100%;
|
padding: 10px 10px 10px 20px;
|
||||||
|
|
||||||
padding: 5px 10px 5px 20px;
|
|
||||||
|
|
||||||
transition: all 50ms ease-in-out;
|
transition: all 50ms ease-in-out;
|
||||||
|
|
||||||
@ -58,7 +56,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: var(--background-color-primary2);
|
background-color: var(--background-color-primary-2);
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ export default class ContextMenuCore extends Core {
|
|||||||
clickOutsideToClose: true,
|
clickOutsideToClose: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
async initialize() {
|
async onInitialize() {
|
||||||
document.addEventListener("contextmenu", this.handleEvent)
|
document.addEventListener("contextmenu", this.handleEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,14 +15,14 @@ export function extractLocaleFromPath(path = "") {
|
|||||||
const messageImports = import.meta.glob("schemas/translations/*.json")
|
const messageImports = import.meta.glob("schemas/translations/*.json")
|
||||||
|
|
||||||
export default class I18nCore extends Core {
|
export default class I18nCore extends Core {
|
||||||
events = {
|
onEvents = {
|
||||||
"changeLanguage": (locale) => {
|
"changeLanguage": (locale) => {
|
||||||
this.loadAsyncLanguage(locale)
|
this.loadAsyncLanguage(locale)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize = async () => {
|
onInitialize = async () => {
|
||||||
let locale = app.settings.get("language") ?? DEFAULT_LOCALE
|
let locale = app.cores.settings.get("language") ?? DEFAULT_LOCALE
|
||||||
|
|
||||||
if (!SUPPORTED_LOCALES.includes(locale)) {
|
if (!SUPPORTED_LOCALES.includes(locale)) {
|
||||||
locale = DEFAULT_LOCALE
|
locale = DEFAULT_LOCALE
|
||||||
|
@ -2,7 +2,6 @@ import SettingsCore from "./settings"
|
|||||||
import APICore from "./api"
|
import APICore from "./api"
|
||||||
import StyleCore from "./style"
|
import StyleCore from "./style"
|
||||||
import PermissionsCore from "./permissions"
|
import PermissionsCore from "./permissions"
|
||||||
import SearchCore from "./search"
|
|
||||||
import ContextMenuCore from "./contextMenu"
|
import ContextMenuCore from "./contextMenu"
|
||||||
|
|
||||||
import I18nCore from "./i18n"
|
import I18nCore from "./i18n"
|
||||||
@ -10,13 +9,12 @@ import NotificationsCore from "./notifications"
|
|||||||
import ShortcutsCore from "./shortcuts"
|
import ShortcutsCore from "./shortcuts"
|
||||||
import SoundCore from "./sound"
|
import SoundCore from "./sound"
|
||||||
|
|
||||||
import AudioPlayer from "./audioPlayer"
|
import Player from "./player"
|
||||||
|
|
||||||
// DEFINE LOAD ORDER HERE
|
// DEFINE LOAD ORDER HERE
|
||||||
export default [
|
export default [
|
||||||
SettingsCore,
|
SettingsCore,
|
||||||
APICore,
|
APICore,
|
||||||
SearchCore,
|
|
||||||
PermissionsCore,
|
PermissionsCore,
|
||||||
StyleCore,
|
StyleCore,
|
||||||
I18nCore,
|
I18nCore,
|
||||||
@ -24,6 +22,6 @@ export default [
|
|||||||
NotificationsCore,
|
NotificationsCore,
|
||||||
ShortcutsCore,
|
ShortcutsCore,
|
||||||
|
|
||||||
AudioPlayer,
|
Player,
|
||||||
ContextMenuCore,
|
ContextMenuCore,
|
||||||
]
|
]
|
@ -6,7 +6,7 @@ import { Translation } from "react-i18next"
|
|||||||
import { Haptics } from "@capacitor/haptics"
|
import { Haptics } from "@capacitor/haptics"
|
||||||
|
|
||||||
export default class NotificationCore extends Core {
|
export default class NotificationCore extends Core {
|
||||||
events = {
|
onEvents = {
|
||||||
"changeNotificationsSoundVolume": (value) => {
|
"changeNotificationsSoundVolume": (value) => {
|
||||||
this.playAudio({ soundVolume: value })
|
this.playAudio({ soundVolume: value })
|
||||||
},
|
},
|
||||||
@ -17,12 +17,12 @@ export default class NotificationCore extends Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
publicMethods = {
|
registerToApp = {
|
||||||
notification: this
|
notification: this
|
||||||
}
|
}
|
||||||
|
|
||||||
getSoundVolume = () => {
|
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 = {}) => {
|
new = (notification, options = {}) => {
|
||||||
@ -31,7 +31,9 @@ export default class NotificationCore extends Core {
|
|||||||
this.playAudio(options)
|
this.playAudio(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
notify = (notification, options = {}) => {
|
notify = (notification, options = {
|
||||||
|
type: "info"
|
||||||
|
}) => {
|
||||||
if (typeof notification === "string") {
|
if (typeof notification === "string") {
|
||||||
notification = {
|
notification = {
|
||||||
title: "New notification",
|
title: "New notification",
|
||||||
@ -39,20 +41,69 @@ export default class NotificationCore extends Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Notf.open({
|
const notfObj = {
|
||||||
message: <Translation>
|
|
||||||
{(t) => t(notification.title)}
|
|
||||||
</Translation>,
|
|
||||||
description: <Translation>
|
|
||||||
{(t) => t(notification.description)}
|
|
||||||
</Translation>,
|
|
||||||
duration: notification.duration ?? 4,
|
duration: notification.duration ?? 4,
|
||||||
icon: React.isValidElement(notification.icon) ? notification.icon : (createIconRender(notification.icon) ?? <Icons.Bell />),
|
}
|
||||||
})
|
|
||||||
|
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 = <Translation>
|
||||||
|
{(t) => t(notification.message)}
|
||||||
|
</Translation>
|
||||||
|
|
||||||
|
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 = <Translation>
|
||||||
|
{(t) => t(notification.description)}
|
||||||
|
</Translation>
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notification.icon) {
|
||||||
|
notfObj.icon = React.isValidElement(notification.icon) ? notification.icon : (createIconRender(notification.icon) ?? <Icons.Bell />)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof Notf[options.type] !== "function") {
|
||||||
|
options.type = "info"
|
||||||
|
}
|
||||||
|
|
||||||
|
return Notf[options.type](notfObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
playHaptic = async (options = {}) => {
|
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) {
|
if (vibrationEnabled) {
|
||||||
await Haptics.vibrate()
|
await Haptics.vibrate()
|
||||||
@ -60,7 +111,7 @@ export default class NotificationCore extends Core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
playAudio = (options = {}) => {
|
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()
|
const soundVolume = options.soundVolume ? options.soundVolume / 100 : this.getSoundVolume()
|
||||||
|
|
||||||
if (soundEnabled) {
|
if (soundEnabled) {
|
||||||
|
@ -6,20 +6,22 @@ import SessionModel from "models/session"
|
|||||||
export default class PermissionsCore extends Core {
|
export default class PermissionsCore extends Core {
|
||||||
static namespace = "permissions"
|
static namespace = "permissions"
|
||||||
static dependencies = ["api"]
|
static dependencies = ["api"]
|
||||||
static public = ["hasAdmin", "checkUserIdIsSelf", "hasPermission"]
|
|
||||||
|
|
||||||
userData = null
|
public = {
|
||||||
isUserAdmin = null
|
hasAdmin: this.hasAdmin,
|
||||||
|
checkUserIdIsSelf: this.checkUserIdIsSelf,
|
||||||
|
hasPermission: this.hasPermission,
|
||||||
|
}
|
||||||
|
|
||||||
hasAdmin = async () => {
|
async hasAdmin() {
|
||||||
return await UserModel.hasAdmin()
|
return await UserModel.hasAdmin()
|
||||||
}
|
}
|
||||||
|
|
||||||
checkUserIdIsSelf = (userId) => {
|
checkUserIdIsSelf(user_id) {
|
||||||
return SessionModel.user_id === userId
|
return SessionModel.user_id === user_id
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPermission = async (permission) => {
|
async hasPermission(permission) {
|
||||||
let query = []
|
let query = []
|
||||||
|
|
||||||
if (Array.isArray(permission)) {
|
if (Array.isArray(permission)) {
|
||||||
|
715
packages/app/src/cores/player/index.jsx
Normal file
715
packages/app/src/cores/player/index.jsx
Normal file
@ -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(<EmbbededMediaPlayer />)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ export default class RemoteStorage extends Core {
|
|||||||
|
|
||||||
connection = null
|
connection = null
|
||||||
|
|
||||||
async initialize() {
|
async onInitialize() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 })
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,52 +4,60 @@ import defaultSettings from "schemas/defaultSettings.json"
|
|||||||
import { Observable } from "rxjs"
|
import { Observable } from "rxjs"
|
||||||
|
|
||||||
export default class SettingsCore extends Core {
|
export default class SettingsCore extends Core {
|
||||||
storeKey = "app_settings"
|
static namespace = "settings"
|
||||||
|
|
||||||
settings = store.get(this.storeKey) ?? {}
|
static storeKey = "app_settings"
|
||||||
|
|
||||||
publicMethods = {
|
public = {
|
||||||
settings: this
|
is: this.is,
|
||||||
|
set: this.set,
|
||||||
|
get: this.get,
|
||||||
|
getDefaults: this.getDefaults,
|
||||||
|
withEvent: this.withEvent,
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
onInitialize() {
|
||||||
this.fulfillUndefinedWithDefaults()
|
const settings = this.get()
|
||||||
}
|
|
||||||
|
|
||||||
fulfillUndefinedWithDefaults = () => {
|
// fulfillUndefinedWithDefaults
|
||||||
Object.keys(defaultSettings).forEach((key) => {
|
Object.keys(defaultSettings).forEach((key) => {
|
||||||
const value = defaultSettings[key]
|
const value = defaultSettings[key]
|
||||||
|
|
||||||
// Only set default if value is undefined
|
// Only set default if value is undefined
|
||||||
if (typeof this.settings[key] === "undefined") {
|
if (typeof settings[key] === "undefined") {
|
||||||
this.settings[key] = value
|
this.set(key, value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
is = (key, value) => {
|
is(key, value) {
|
||||||
return this.settings[key] === value
|
return this.get(key) === value
|
||||||
}
|
}
|
||||||
|
|
||||||
set = (key, value) => {
|
set(key, value) {
|
||||||
this.settings[key] = value
|
const settings = this.get()
|
||||||
store.set(this.storeKey, this.settings)
|
|
||||||
|
settings[key] = value
|
||||||
|
|
||||||
|
store.set(SettingsCore.storeKey, settings)
|
||||||
|
|
||||||
window.app.eventBus.emit("setting.update", { key, value })
|
window.app.eventBus.emit("setting.update", { key, value })
|
||||||
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") {
|
if (typeof key === "undefined") {
|
||||||
return this.settings
|
return settings
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.settings[key]
|
return settings[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaults = (key) => {
|
getDefaults(key) {
|
||||||
if (typeof key === "undefined") {
|
if (typeof key === "undefined") {
|
||||||
return defaultSettings
|
return defaultSettings
|
||||||
}
|
}
|
||||||
@ -57,8 +65,8 @@ export default class SettingsCore extends Core {
|
|||||||
return defaultSettings[key]
|
return defaultSettings[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
withEvent = (listenEvent, defaultValue) => {
|
withEvent(listenEvent, defaultValue) {
|
||||||
let value = defaultValue ?? this.settings[key] ?? false
|
let value = defaultValue ?? false
|
||||||
|
|
||||||
const observable = new Observable((subscriber) => {
|
const observable = new Observable((subscriber) => {
|
||||||
subscriber.next(value)
|
subscriber.next(value)
|
||||||
|
@ -3,7 +3,7 @@ import Core from "evite/src/core"
|
|||||||
export default class ShortcutsCore extends Core {
|
export default class ShortcutsCore extends Core {
|
||||||
shortcutsRegister = []
|
shortcutsRegister = []
|
||||||
|
|
||||||
publicMethods = {
|
registerToApp = {
|
||||||
shortcuts: this
|
shortcuts: this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,36 +3,29 @@ import { Howl } from "howler"
|
|||||||
import config from "config"
|
import config from "config"
|
||||||
|
|
||||||
export default class SoundCore extends Core {
|
export default class SoundCore extends Core {
|
||||||
sounds = {}
|
static namespace = "sound"
|
||||||
|
|
||||||
publicMethods = {
|
public = {
|
||||||
sound: this,
|
play: this.play,
|
||||||
|
getSounds: this.getSounds,
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize() {
|
async getSounds() {
|
||||||
this.sounds = await this.getSounds()
|
|
||||||
}
|
|
||||||
|
|
||||||
getSounds = async () => {
|
|
||||||
// TODO: Load custom soundpacks manifests
|
// TODO: Load custom soundpacks manifests
|
||||||
let soundPack = config.defaultSoundPack ?? {}
|
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
|
return soundPack
|
||||||
}
|
}
|
||||||
|
|
||||||
play = (name, options) => {
|
async play(name, options) {
|
||||||
if (this.sounds[name]) {
|
let soundPack = await this.getSounds()
|
||||||
return this.sounds[name](options).play()
|
|
||||||
|
if (soundPack[name]) {
|
||||||
|
return new Howl({
|
||||||
|
volume: window.app.cores.settings.get("generalAudioVolume") ?? 0.5,
|
||||||
|
...options,
|
||||||
|
src: [soundPack[name]],
|
||||||
|
}).play()
|
||||||
} else {
|
} else {
|
||||||
console.error(`Sound [${name}] not found or is not available.`)
|
console.error(`Sound [${name}] not found or is not available.`)
|
||||||
return false
|
return false
|
||||||
|
@ -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 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, 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 <ConfigProvider
|
||||||
|
theme={{
|
||||||
|
token: {
|
||||||
|
...app.cores.style.getValue(),
|
||||||
|
},
|
||||||
|
algorithm: themeAlgorithms,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.props.children}
|
||||||
|
</ConfigProvider>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default class StyleCore extends Core {
|
export default class StyleCore extends Core {
|
||||||
themeManifestStorageKey = "theme"
|
static namespace = "style"
|
||||||
modificationStorageKey = "themeModifications"
|
|
||||||
|
|
||||||
theme = null
|
static themeManifestStorageKey = "theme"
|
||||||
mutation = null
|
static modificationStorageKey = "themeModifications"
|
||||||
currentVariant = null
|
|
||||||
|
|
||||||
events = {
|
static get rootVariables() {
|
||||||
"style.compactMode": (value = !window.app.settings.get("style.compactMode")) => {
|
let attributes = document.documentElement.getAttribute("style").trim().split(";")
|
||||||
if (value) {
|
|
||||||
return this.update({
|
|
||||||
layoutMargin: 0,
|
|
||||||
layoutPadding: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.update({
|
attributes = attributes.slice(0, (attributes.length - 1))
|
||||||
layoutMargin: this.getValue("layoutMargin"),
|
|
||||||
layoutPadding: this.getValue("layoutPadding"),
|
attributes = attributes.map((variable) => {
|
||||||
})
|
let [key, value] = variable.split(":")
|
||||||
},
|
key = key.split("--")[1]
|
||||||
"style.autoDarkModeToogle": (value) => {
|
|
||||||
if (value === true) {
|
return [key, value]
|
||||||
this.handleAutoColorScheme()
|
})
|
||||||
} else {
|
|
||||||
this.applyVariant(this.getStoragedVariant())
|
return Object.fromEntries(attributes)
|
||||||
}
|
|
||||||
},
|
|
||||||
"theme.applyVariant": (value) => {
|
|
||||||
this.applyVariant(value)
|
|
||||||
this.setVariant(value)
|
|
||||||
},
|
|
||||||
"modifyTheme": (value) => {
|
|
||||||
this.update(value)
|
|
||||||
this.setModifications(this.mutation)
|
|
||||||
},
|
|
||||||
"resetTheme": () => {
|
|
||||||
this.resetDefault()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
publicMethods = {
|
static get storagedTheme() {
|
||||||
style: this
|
return store.get(StyleCore.themeManifestStorageKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
static get currentVariant() {
|
static get storagedVariant() {
|
||||||
return document.documentElement.style.getPropertyValue("--themeVariant")
|
return app.cores.settings.get("style.darkMode") ? "dark" : "light"
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAutoColorScheme() {
|
static set storagedModifications(modifications) {
|
||||||
const prefered = window.matchMedia("(prefers-color-scheme: light)")
|
return store.set(StyleCore.modificationStorageKey, modifications)
|
||||||
|
}
|
||||||
|
|
||||||
if (!prefered.matches) {
|
static get storagedModifications() {
|
||||||
this.applyVariant("dark")
|
return store.get(StyleCore.modificationStorageKey) ?? {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onInitialize() {
|
||||||
|
if (StyleCore.storagedTheme) {
|
||||||
|
// TODO: Start remote theme loader
|
||||||
} else {
|
} else {
|
||||||
this.applyVariant("light")
|
this.public.theme = config.defaultTheme
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// set global theme
|
const modifications = StyleCore.storagedModifications
|
||||||
this.theme = theme
|
const variantKey = StyleCore.storagedVariant
|
||||||
|
|
||||||
// override with static vars
|
// override with static vars
|
||||||
if (theme.staticVars) {
|
if (this.public.theme.defaultVars) {
|
||||||
this.update(theme.staticVars)
|
this.update(this.public.theme.defaultVars)
|
||||||
}
|
}
|
||||||
|
|
||||||
// override theme with modifications
|
// override theme with modifications
|
||||||
@ -96,104 +127,130 @@ export default class StyleCore extends Core {
|
|||||||
window.matchMedia("(prefers-color-scheme: light)").addListener(() => {
|
window.matchMedia("(prefers-color-scheme: light)").addListener(() => {
|
||||||
console.log(`[THEME] Auto color scheme changed`)
|
console.log(`[THEME] Auto color scheme changed`)
|
||||||
|
|
||||||
if (window.app.settings.get("auto_darkMode")) {
|
if (window.app.cores.settings.get("auto_darkMode")) {
|
||||||
this.handleAutoColorScheme()
|
this.handleAutoColorScheme()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (window.app.settings.get("auto_darkMode")) {
|
if (window.app.cores.settings.get("auto_darkMode")) {
|
||||||
this.handleAutoColorScheme()
|
this.handleAutoColorScheme()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getRootVariables = () => {
|
onEvents = {
|
||||||
let attributes = document.documentElement.getAttribute("style").trim().split(";")
|
"style.autoDarkModeToogle": (value) => {
|
||||||
attributes = attributes.slice(0, (attributes.length - 1))
|
if (value === true) {
|
||||||
attributes = attributes.map((variable) => {
|
this.handleAutoColorScheme()
|
||||||
let [key, value] = variable.split(":")
|
} else {
|
||||||
key = key.split("--")[1]
|
this.applyVariant(StyleCore.storagedVariant)
|
||||||
|
}
|
||||||
return [key, value]
|
}
|
||||||
})
|
|
||||||
|
|
||||||
return Object.fromEntries(attributes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultTheme = () => {
|
public = {
|
||||||
// TODO: Use evite CONSTANTS_API
|
theme: null,
|
||||||
return config.defaultTheme
|
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 = () => {
|
getValue(key) {
|
||||||
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
|
|
||||||
|
|
||||||
if (typeof key === "undefined") {
|
if (typeof key === "undefined") {
|
||||||
return {
|
return {
|
||||||
...staticValues,
|
...this.public.theme.defaultVars,
|
||||||
...storagedModifications
|
...StyleCore.storagedModifications
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return storagedModifications[key] || staticValues[key]
|
return StyleCore.storagedModifications[key] || this.public.theme.defaultVars[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
setVariant = (variationKey) => {
|
setDefault() {
|
||||||
return app.settings.set("themeVariant", variationKey)
|
store.remove(StyleCore.themeManifestStorageKey)
|
||||||
|
store.remove(StyleCore.modificationStorageKey)
|
||||||
|
|
||||||
|
app.cores.settings.set("colorPrimary", this.public.theme.defaultVars.colorPrimary)
|
||||||
|
|
||||||
|
this.onInitialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
setModifications = (modifications) => {
|
update(update) {
|
||||||
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) => {
|
|
||||||
if (typeof update !== "object") {
|
if (typeof update !== "object") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mutation = {
|
this.public.mutation = {
|
||||||
...this.theme.staticVars,
|
...this.public.theme.defaultVars,
|
||||||
...this.mutation,
|
...this.public.mutation,
|
||||||
...update
|
...update
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(this.mutation).forEach(key => {
|
Object.keys(this.public.mutation).forEach(key => {
|
||||||
document.documentElement.style.setProperty(`--${key}`, this.mutation[key])
|
document.documentElement.style.setProperty(`--${key}`, this.public.mutation[key])
|
||||||
})
|
})
|
||||||
|
|
||||||
document.documentElement.className = `theme-${this.currentVariant}`
|
app.eventBus.emit("style.update", {
|
||||||
document.documentElement.style.setProperty(`--themeVariant`, this.currentVariant)
|
...this.public.mutation,
|
||||||
|
})
|
||||||
|
|
||||||
ConfigProvider.config({ theme: this.mutation })
|
ConfigProvider.config({ theme: this.public.mutation })
|
||||||
}
|
}
|
||||||
|
|
||||||
applyVariant = (variant = (this.theme.defaultVariant ?? "light")) => {
|
applyVariant(variant = (this.public.theme.defaultVariant ?? "light")) {
|
||||||
const values = this.theme.variants[variant]
|
const values = this.public.theme.variants[variant]
|
||||||
|
|
||||||
if (values) {
|
if (!values) {
|
||||||
this.currentVariant = variant
|
console.error(`Variant [${variant}] not found`)
|
||||||
this.update(values)
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user