From 42e9a769f7b086c7aefc0393e32b587dcf473037 Mon Sep 17 00:00:00 2001 From: Satont Date: Tue, 19 Sep 2023 00:43:16 +0300 Subject: [PATCH] refactor(overlays): use vue instead of react --- .gitignore | 2 + .../internal/namespaces/alerts/alerts.go | 5 +- apps/websockets/internal/namespaces/base.go | 2 + .../internal/namespaces/obs/handle_message.go | 5 +- .../websockets/internal/namespaces/obs/obs.go | 2 + .../namespaces/registry/overlays/overlays.go | 2 + .../internal/namespaces/tts/skip.go | 2 + .../websockets/internal/namespaces/tts/tts.go | 2 + .../namespaces/youtube/add_song_to_queue.go | 2 + .../namespaces/youtube/handle_connect.go | 16 +- .../namespaces/youtube/handle_message.go | 12 +- .../youtube/remove_song_from_queue.go | 2 + apps/websockets/types/types.go | 1 + docker-compose.dev.yml | 2 +- .../src/components/overlays/obs/settings.vue | 21 +- frontend/overlays/Dockerfile | 17 - frontend/overlays/README.md | 18 + frontend/overlays/index.html | 6 +- frontend/overlays/package.json | 24 +- frontend/overlays/src/App.vue | 7 + .../overlays/src/{libs/twirp.ts => api.ts} | 0 .../overlays/src/components/htmlLayer.vue | 32 ++ frontend/overlays/src/main.ts | 28 ++ frontend/overlays/src/main.tsx | 33 -- frontend/overlays/src/pages/alerts.tsx | 100 ------ frontend/overlays/src/pages/alerts.vue | 95 +++++ frontend/overlays/src/pages/obs.tsx | 109 ------ frontend/overlays/src/pages/obs.vue | 98 +++++ frontend/overlays/src/pages/overlays.vue | 44 +++ .../overlays/src/pages/overlaysRegistry.tsx | 181 ---------- frontend/overlays/src/pages/tts.tsx | 100 ------ frontend/overlays/src/pages/tts.vue | 86 +++++ .../overlays/src/{hooks => sockets}/obs.ts | 112 +++--- frontend/overlays/src/sockets/overlays.ts | 103 ++++++ frontend/overlays/src/vite-env.d.ts | 10 + frontend/overlays/tsconfig.json | 26 +- frontend/overlays/tsconfig.node.json | 3 +- frontend/overlays/vite.config.ts | 6 +- ...vite.config.ts.timestamp-1679603767237.mjs | 15 - frontend/overlays/vite.dev.js | 15 - libs/config/config.go | 2 +- libs/config/src/index.ts | 48 +-- pnpm-lock.yaml | 339 +++++------------- 43 files changed, 779 insertions(+), 956 deletions(-) delete mode 100644 frontend/overlays/Dockerfile create mode 100644 frontend/overlays/README.md create mode 100644 frontend/overlays/src/App.vue rename frontend/overlays/src/{libs/twirp.ts => api.ts} (100%) create mode 100644 frontend/overlays/src/components/htmlLayer.vue create mode 100644 frontend/overlays/src/main.ts delete mode 100644 frontend/overlays/src/main.tsx delete mode 100644 frontend/overlays/src/pages/alerts.tsx create mode 100644 frontend/overlays/src/pages/alerts.vue delete mode 100644 frontend/overlays/src/pages/obs.tsx create mode 100644 frontend/overlays/src/pages/obs.vue create mode 100644 frontend/overlays/src/pages/overlays.vue delete mode 100644 frontend/overlays/src/pages/overlaysRegistry.tsx delete mode 100644 frontend/overlays/src/pages/tts.tsx create mode 100644 frontend/overlays/src/pages/tts.vue rename frontend/overlays/src/{hooks => sockets}/obs.ts (50%) create mode 100644 frontend/overlays/src/sockets/overlays.ts delete mode 100644 frontend/overlays/vite.config.ts.timestamp-1679603767237.mjs delete mode 100644 frontend/overlays/vite.dev.js diff --git a/.gitignore b/.gitignore index 9b14666b0..6917fe394 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ main.~ .idea !.vscode/extensions.json !traefik/* + +vite.config.ts.timestamp-* diff --git a/apps/websockets/internal/namespaces/alerts/alerts.go b/apps/websockets/internal/namespaces/alerts/alerts.go index 3055d31ba..bce23fe9c 100644 --- a/apps/websockets/internal/namespaces/alerts/alerts.go +++ b/apps/websockets/internal/namespaces/alerts/alerts.go @@ -2,10 +2,12 @@ package alerts import ( "encoding/json" + "net/http" + "time" + "github.com/olahol/melody" "github.com/satont/twir/apps/websockets/internal/namespaces/helpers" "github.com/satont/twir/apps/websockets/types" - "net/http" ) type Alerts struct { @@ -43,6 +45,7 @@ func (c *Alerts) SendEvent(channelId, eventName string, data any) error { message := &types.WebSocketMessage{ EventName: eventName, Data: data, + CreatedAt: time.Now().UTC().String(), } bytes, err := json.Marshal(message) diff --git a/apps/websockets/internal/namespaces/base.go b/apps/websockets/internal/namespaces/base.go index 416538fd3..ad27c60c4 100644 --- a/apps/websockets/internal/namespaces/base.go +++ b/apps/websockets/internal/namespaces/base.go @@ -3,6 +3,7 @@ package namespaces import ( "encoding/json" "net/http" + "time" "github.com/olahol/melody" "github.com/satont/twir/apps/websockets/internal/namespaces/helpers" @@ -39,6 +40,7 @@ func (c *NameSpace) SendEvent(userId, eventName string, data any) error { message := &types.WebSocketMessage{ EventName: eventName, Data: data, + CreatedAt: time.Now().UTC().String(), } bytes, err := json.Marshal(message) diff --git a/apps/websockets/internal/namespaces/obs/handle_message.go b/apps/websockets/internal/namespaces/obs/handle_message.go index 96996544c..3171fe9f6 100644 --- a/apps/websockets/internal/namespaces/obs/handle_message.go +++ b/apps/websockets/internal/namespaces/obs/handle_message.go @@ -19,7 +19,9 @@ func (c *OBS) handleMessage(session *melody.Session, msg []byte) { return } - data := &types.WebSocketMessage{} + data := &types.WebSocketMessage{ + CreatedAt: time.Now().UTC().String(), + } err := json.Unmarshal(msg, data) if err != nil { c.services.Logger.Error(err) @@ -133,6 +135,7 @@ func (c *OBS) handleRequestSettings(channelId string) { outCome := &types.WebSocketMessage{ EventName: "settings", Data: obsSettings, + CreatedAt: time.Now().UTC().String(), } bytes, err := json.Marshal(outCome) diff --git a/apps/websockets/internal/namespaces/obs/obs.go b/apps/websockets/internal/namespaces/obs/obs.go index b5f58dfda..d4eb9d0f8 100644 --- a/apps/websockets/internal/namespaces/obs/obs.go +++ b/apps/websockets/internal/namespaces/obs/obs.go @@ -2,6 +2,7 @@ package obs import ( "encoding/json" + "time" "github.com/olahol/melody" "github.com/satont/twir/apps/websockets/internal/namespaces/helpers" @@ -70,6 +71,7 @@ func (c *OBS) SendEvent(userId, eventName string, data any) error { message := &types.WebSocketMessage{ EventName: eventName, Data: data, + CreatedAt: time.Now().UTC().String(), } bytes, err := json.Marshal(message) diff --git a/apps/websockets/internal/namespaces/registry/overlays/overlays.go b/apps/websockets/internal/namespaces/registry/overlays/overlays.go index 3dcd6c520..3c42b03ab 100644 --- a/apps/websockets/internal/namespaces/registry/overlays/overlays.go +++ b/apps/websockets/internal/namespaces/registry/overlays/overlays.go @@ -3,6 +3,7 @@ package overlays import ( "encoding/json" "net/http" + "time" "github.com/olahol/melody" "github.com/satont/twir/apps/websockets/internal/namespaces/helpers" @@ -49,6 +50,7 @@ func (c *Registry) SendEvent(channelId, eventName string, data any) error { message := &types.WebSocketMessage{ EventName: eventName, Data: data, + CreatedAt: time.Now().UTC().String(), } bytes, err := json.Marshal(message) diff --git a/apps/websockets/internal/namespaces/tts/skip.go b/apps/websockets/internal/namespaces/tts/skip.go index 2f3b73a93..29f12c578 100644 --- a/apps/websockets/internal/namespaces/tts/skip.go +++ b/apps/websockets/internal/namespaces/tts/skip.go @@ -3,6 +3,7 @@ package tts import ( "context" "encoding/json" + "time" "github.com/olahol/melody" "github.com/satont/twir/apps/websockets/types" @@ -14,6 +15,7 @@ func (c *TTS) Skip(ctx context.Context, msg *websockets.TTSSkipMessage) (*emptyp message := &types.WebSocketMessage{ EventName: "skip", Data: msg, + CreatedAt: time.Now().UTC().String(), } bytes, err := json.Marshal(message) diff --git a/apps/websockets/internal/namespaces/tts/tts.go b/apps/websockets/internal/namespaces/tts/tts.go index a4d4cae41..07973715b 100644 --- a/apps/websockets/internal/namespaces/tts/tts.go +++ b/apps/websockets/internal/namespaces/tts/tts.go @@ -3,6 +3,7 @@ package tts import ( "encoding/json" "net/http" + "time" "github.com/olahol/melody" "github.com/satont/twir/apps/websockets/internal/namespaces/helpers" @@ -38,6 +39,7 @@ func (c *TTS) SendEvent(userId, eventName string, data any) error { message := &types.WebSocketMessage{ EventName: eventName, Data: data, + CreatedAt: time.Now().UTC().String(), } bytes, err := json.Marshal(message) diff --git a/apps/websockets/internal/namespaces/youtube/add_song_to_queue.go b/apps/websockets/internal/namespaces/youtube/add_song_to_queue.go index 980f381cf..452fb639c 100644 --- a/apps/websockets/internal/namespaces/youtube/add_song_to_queue.go +++ b/apps/websockets/internal/namespaces/youtube/add_song_to_queue.go @@ -3,6 +3,7 @@ package youtube import ( "context" "encoding/json" + "time" "github.com/olahol/melody" "github.com/satont/twir/apps/websockets/types" @@ -25,6 +26,7 @@ func (c *YouTube) AddSongToQueue(_ context.Context, msg *websockets.YoutubeAddSo message := &types.WebSocketMessage{ EventName: "newTrack", Data: song, + CreatedAt: time.Now().UTC().String(), } bytes, err := json.Marshal(message) diff --git a/apps/websockets/internal/namespaces/youtube/handle_connect.go b/apps/websockets/internal/namespaces/youtube/handle_connect.go index bc6f08499..285e32004 100644 --- a/apps/websockets/internal/namespaces/youtube/handle_connect.go +++ b/apps/websockets/internal/namespaces/youtube/handle_connect.go @@ -2,6 +2,7 @@ package youtube import ( "encoding/json" + "time" "github.com/olahol/melody" "github.com/satont/twir/apps/websockets/types" @@ -9,15 +10,15 @@ import ( ) func (c *YouTube) handleConnect(session *melody.Session) { - //const songs = await repository.find({ - //where: { + // const songs = await repository.find({ + // where: { // channelId, // deletedAt: IsNull(), - //}, - //order: { - //queuePosition: 'asc', - //}, - //}); + // }, + // order: { + // queuePosition: 'asc', + // }, + // }); userId, _ := session.Get("userId") @@ -36,6 +37,7 @@ func (c *YouTube) handleConnect(session *melody.Session) { outCome := &types.WebSocketMessage{ EventName: "currentQueue", Data: currentSongs, + CreatedAt: time.Now().UTC().String(), } bytes, err := json.Marshal(outCome) diff --git a/apps/websockets/internal/namespaces/youtube/handle_message.go b/apps/websockets/internal/namespaces/youtube/handle_message.go index b0e91e3be..20fef9afa 100644 --- a/apps/websockets/internal/namespaces/youtube/handle_message.go +++ b/apps/websockets/internal/namespaces/youtube/handle_message.go @@ -27,7 +27,9 @@ func (c *YouTube) handleMessage(session *melody.Session, msg []byte) { return } - data := &types.WebSocketMessage{} + data := &types.WebSocketMessage{ + CreatedAt: time.Now().UTC().String(), + } err := json.Unmarshal(msg, data) if err != nil { c.services.Logger.Error(err) @@ -69,7 +71,7 @@ func (c *YouTube) handleMessage(session *melody.Session, msg []byte) { } if data.EventName == "pause" { - //fmt.Println("get paused") + // fmt.Println("get paused") } } @@ -156,7 +158,11 @@ func (c *YouTube) handlePlay(userId string, data *playEvent) { message = strings.ReplaceAll(message, "{{songTitle}}", song.Title) message = strings.ReplaceAll(message, "{{songLink}}", songLink) message = strings.ReplaceAll(message, "{{orderedByName}}", song.OrderedByName) - message = strings.ReplaceAll(message, "{{orderedByDisplayName}}", song.OrderedByDisplayName.String) + message = strings.ReplaceAll( + message, + "{{orderedByDisplayName}}", + song.OrderedByDisplayName.String, + ) c.services.Grpc.Bots.SendMessage( ctx, &bots.SendMessageRequest{ diff --git a/apps/websockets/internal/namespaces/youtube/remove_song_from_queue.go b/apps/websockets/internal/namespaces/youtube/remove_song_from_queue.go index f4661f189..86305652f 100644 --- a/apps/websockets/internal/namespaces/youtube/remove_song_from_queue.go +++ b/apps/websockets/internal/namespaces/youtube/remove_song_from_queue.go @@ -3,6 +3,7 @@ package youtube import ( "context" "encoding/json" + "time" "github.com/olahol/melody" "github.com/satont/twir/apps/websockets/types" @@ -24,6 +25,7 @@ func (c *YouTube) RemoveSongFromQueue( message := &types.WebSocketMessage{ EventName: "removeTrack", Data: song, + CreatedAt: time.Now().UTC().String(), } bytes, err := json.Marshal(message) diff --git a/apps/websockets/types/types.go b/apps/websockets/types/types.go index 8965076a2..e3e5a405b 100644 --- a/apps/websockets/types/types.go +++ b/apps/websockets/types/types.go @@ -23,4 +23,5 @@ type Services struct { type WebSocketMessage struct { EventName string `json:"eventName"` Data any `json:"data"` + CreatedAt string `json:"createdAt"` } diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index d7f769ace..7fdeb56d9 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -64,7 +64,7 @@ services: restart: unless-stopped image: aculeasis/rhvoice-rest ports: - - "7000:8080" + - "7001:8080" networks: - tsuwari-dev diff --git a/frontend/dashboard/src/components/overlays/obs/settings.vue b/frontend/dashboard/src/components/overlays/obs/settings.vue index 340d119ae..b525c4309 100644 --- a/frontend/dashboard/src/components/overlays/obs/settings.vue +++ b/frontend/dashboard/src/components/overlays/obs/settings.vue @@ -15,15 +15,21 @@ import { NSpace, useMessage, } from 'naive-ui'; -import { ref, watch, toRaw } from 'vue'; +import { ref, toRaw, onMounted } from 'vue'; import { useI18n } from 'vue-i18n'; import { useObsOverlayManager } from '@/api/index.js'; const obsSettingsManager = useObsOverlayManager(); -const obsSettings = obsSettingsManager.getSettings(); +const { refetch, data: settings } = obsSettingsManager.getSettings(); const obsSettingsUpdater = obsSettingsManager.updateSettings(); +onMounted(async () => { + const settings = await refetch(); + if (!settings.data) return; + formValue.value = toRaw(settings.data); +}); + const { t } = useI18n(); const formRef = ref(null); @@ -68,11 +74,6 @@ const rules: FormRules = { }, }; -watch(obsSettings.data, (v) => { - if (!v) return; - formValue.value = toRaw(v); -}, { immediate: true }); - const message = useMessage(); async function save() { @@ -86,7 +87,7 @@ async function save() { } async function checkConnection() { - await obsSettings.refetch(); + await refetch(); } @@ -127,9 +128,9 @@ async function checkConnection() { /> - + {{ - obsSettings.data.value?.isConnected ? t('overlays.obs.connected') : t('overlays.obs.notConnected') + settings?.isConnected ? t('overlays.obs.connected') : t('overlays.obs.notConnected') }} diff --git a/frontend/overlays/Dockerfile b/frontend/overlays/Dockerfile deleted file mode 100644 index 010fc4ace..000000000 --- a/frontend/overlays/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM node:18-alpine as builder - -WORKDIR /app - -RUN apk add git openssh libc6-compat -RUN npm i -g pnpm@7 - -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json tsconfig.json turbo.json .npmrc ./ - -COPY frontend frontend -COPY patches patches - -RUN pnpm install --frozen-lockfile -RUN cd frontend/overlays && pnpm build - -FROM codecentric/single-page-application-server -COPY --from=builder /app/frontend/overlays/dist/ /app \ No newline at end of file diff --git a/frontend/overlays/README.md b/frontend/overlays/README.md new file mode 100644 index 000000000..ef72fd524 --- /dev/null +++ b/frontend/overlays/README.md @@ -0,0 +1,18 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` +
+ diff --git a/frontend/overlays/package.json b/frontend/overlays/package.json index cb9915aa8..61077e802 100644 --- a/frontend/overlays/package.json +++ b/frontend/overlays/package.json @@ -1,28 +1,26 @@ { - "name": "@twir/overlays", + "name": "overlays", "private": true, + "version": "0.0.0", "type": "module", "scripts": { - "dev": "node ./vite.dev.js", - "build": "tsc && vite build", + "dev": "vite", + "build": "vue-tsc && vite build", "preview": "vite preview" }, "dependencies": { "@protobuf-ts/twirp-transport": "^2.9.1", "@twir/grpc": "workspace:^", + "@vueuse/core": "^10.4.1", "nested-css-to-flat": "^1.0.5", "obs-websocket-js": "^5.0.3", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^6.12.1", - "react-use-websocket": "^4.3.1", - "socket.io-client": "^4.6.2" + "vue": "^3.3.4", + "vue-router": "^4.2.4" }, "devDependencies": { - "@types/react": "^18.2.12", - "@types/react-dom": "^18.2.5", - "@vitejs/plugin-react": "^4.0.0", - "typescript": "^5.1.3", - "vite": "^4.3.9" + "@vitejs/plugin-vue": "^4.2.3", + "typescript": "^5.0.2", + "vite": "^4.4.5", + "vue-tsc": "^1.8.5" } } diff --git a/frontend/overlays/src/App.vue b/frontend/overlays/src/App.vue new file mode 100644 index 000000000..e6d5f508d --- /dev/null +++ b/frontend/overlays/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/frontend/overlays/src/libs/twirp.ts b/frontend/overlays/src/api.ts similarity index 100% rename from frontend/overlays/src/libs/twirp.ts rename to frontend/overlays/src/api.ts diff --git a/frontend/overlays/src/components/htmlLayer.vue b/frontend/overlays/src/components/htmlLayer.vue new file mode 100644 index 000000000..d8b19dda7 --- /dev/null +++ b/frontend/overlays/src/components/htmlLayer.vue @@ -0,0 +1,32 @@ + + + diff --git a/frontend/overlays/src/main.ts b/frontend/overlays/src/main.ts new file mode 100644 index 000000000..cce46dfd7 --- /dev/null +++ b/frontend/overlays/src/main.ts @@ -0,0 +1,28 @@ +import { createApp } from 'vue'; +import { createRouter, createWebHistory } from 'vue-router'; + +import App from './App.vue'; + +const routes = createRouter({ + history: createWebHistory('/overlays'), + routes: [ + { + path: '/:apiKey/registry/overlays/:overlayId', + component: () => import('./pages/overlays.vue'), + }, + { + path: '/:apiKey/tts', + component: () => import('./pages/tts.vue'), + }, + { + path: '/:apiKey/obs', + component: () => import('./pages/obs.vue'), + }, + { + path: '/:apiKey/alerts', + component: () => import('./pages/alerts.vue'), + }, + ], +}); + +createApp(App).use(routes).mount('#app'); diff --git a/frontend/overlays/src/main.tsx b/frontend/overlays/src/main.tsx deleted file mode 100644 index 99b8218e9..000000000 --- a/frontend/overlays/src/main.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import { createBrowserRouter, RouterProvider } from 'react-router-dom'; - -import { Alerts } from './pages/alerts.js'; -import { OBS } from './pages/obs'; -import { OverlaysRegistry } from './pages/overlaysRegistry'; -import { TTS } from './pages/tts'; - -const router = createBrowserRouter([ - { - path: '/:apiKey/tts', - element: , - }, - { - path: '/:apiKey/obs', - element: , - }, - { - path: '/:apiKey/alerts', - element: , - }, - { - path: '/:apiKey/registry/overlays/:overlayId', - element: , - }, -], { - basename: '/overlays', -}); - -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - , -); diff --git a/frontend/overlays/src/pages/alerts.tsx b/frontend/overlays/src/pages/alerts.tsx deleted file mode 100644 index b09c5bdb1..000000000 --- a/frontend/overlays/src/pages/alerts.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; -import { useParams } from 'react-router-dom'; -import useWebSocket from 'react-use-websocket'; - -declare global { - interface Window { - webkitAudioContext: typeof AudioContext - } -} - -export const Alerts: React.FC = () => { - const [url, setUrl] = useState(null); - const { apiKey } = useParams(); - const { lastMessage } = useWebSocket(url, { - shouldReconnect: () => true, - onOpen: () => console.log('Opened'), - reconnectInterval: 500, - }); - - useEffect(() => { - if (!lastMessage) return; - const parsedData = JSON.parse(lastMessage.data); - - if (parsedData.eventName === 'trigger') { - queueRef.current.push(parsedData.data); - - if (queueRef.current.length === 1) { - processQueue(); - } - } - }, [lastMessage]); - - useEffect(() => { - if (!apiKey) return; - - setUrl(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}/socket/alerts?apiKey=${apiKey}`); - }, [apiKey]); - - const queueRef = useRef>([]); - const currentAudioBuffer = useRef(null); - - const processQueue = useCallback(async () => { - if (queueRef.current.length === 0) { - return; - } - - const current = queueRef.current[0]; - if (current.audio_id) { - await playAudio(current.channel_id, current.audio_id, current.audio_volume); - } - - // change next val - queueRef.current = queueRef.current.slice(1); - - // Process the next item in the queue - processQueue(); - }, []); - - const playAudio = async (channelId: string, audioId: string, volume: number) => { - const query = new URLSearchParams({ - channel_id: channelId, - file_id: audioId, - }); - const req = await fetch(`${window.location.origin}/api/files/?${query}`); - if (!req.ok) { - console.error(await req.text()); - return; - } - - const audioContext = new (window.AudioContext || window.webkitAudioContext)(); - const gainNode = audioContext.createGain(); - - const data = await req.arrayBuffer(); - - const source = audioContext.createBufferSource(); - currentAudioBuffer.current = source; - - source.buffer = await audioContext.decodeAudioData(data); - - gainNode.gain.value = volume / 100; - source.connect(gainNode); - gainNode.connect(audioContext.destination); - - return new Promise((resolve) => { - source.onended = () => { - currentAudioBuffer.current = null; - resolve(null); - }; - - source.start(0); - }); - }; - - return <>; -}; diff --git a/frontend/overlays/src/pages/alerts.vue b/frontend/overlays/src/pages/alerts.vue new file mode 100644 index 000000000..dd8347198 --- /dev/null +++ b/frontend/overlays/src/pages/alerts.vue @@ -0,0 +1,95 @@ + diff --git a/frontend/overlays/src/pages/obs.tsx b/frontend/overlays/src/pages/obs.tsx deleted file mode 100644 index 964a6ba0e..000000000 --- a/frontend/overlays/src/pages/obs.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { useEffect, useState } from 'react'; -import { useParams } from 'react-router-dom'; -import useWebSocket from 'react-use-websocket'; - -import { useObs } from '../hooks/obs'; - -export const OBS: React.FC = () => { - const { apiKey } = useParams(); - const obs = useObs(); - - const [settings, setSettings] = useState>({}); - - const [url, setUrl] = useState(null); - useEffect(() => { - if (!apiKey) return; - - const urlPrefix = window.location.protocol === 'https:' ? 'wss' : 'ws'; - setUrl(`${urlPrefix}://${window.location.host}/socket/obs?apiKey=${apiKey}`); - }, [apiKey]); - - const { lastMessage, sendMessage } = useWebSocket(url, { - shouldReconnect: () => true, - onOpen: () => console.log('Twir socket opened'), - reconnectInterval: 500, - }); - - useEffect(() => { - if (!lastMessage) return; - const { eventName, data } = JSON.parse(lastMessage.data); - - if (eventName === 'connected') { - sendMessage(JSON.stringify({ eventName: 'requestSettings' })); - } - - switch (eventName) { - case 'settings': setSettings(data); break; - case 'setScene': obs.setScene(data.sceneName); break; - case 'toggleSource': obs.toggleSource(data.sourceName); break; - case 'toggleAudioSource': obs.toggleAudioSource(data.audioSourceName); break; - case 'setVolume': obs.setVolume(data.audioSourceName, data.volume); break; - case 'increaseVolume': obs.changeVolume(data.audioSourceName, data.step, 'increase'); break; - case 'decreaseVolume': obs.changeVolume(data.audioSourceName, data.step, 'decrease'); break; - case 'enableAudio': obs.toggleAudioSource(data.audioSourceName, true); break; - case 'disableAudio': obs.toggleAudioSource(data.audioSourceName, false); break; - case 'startStart': obs.startStream(); break; - case 'stopStream': obs.stopStream(); break; - } - }, [lastMessage]); - - useEffect(() => { - if (!settings || !Object.keys(settings).length) { - obs.disconnect(); - return; - } - - obs.connect(settings.serverAddress, settings.serverPort, settings.serverPassword).then(() => { - console.log('Twir obs socket opened'); - sendMessage(JSON.stringify({ eventName: 'obsConnected' })); - - obs.getSources().then((sources) => { - if (!sources) return; - sendMessage(JSON.stringify({ - eventName: 'setSources', - data: sources, - })); - }); - - obs.getAudioSources().then((sources) => { - if (!sources) return; - sendMessage(JSON.stringify({ - eventName: 'setAudioSources', - data: sources, - })); - }); - - const scenesHandler = async () => { - const sources = await obs.getSources(); - sendMessage(JSON.stringify({ - eventName: 'setSources', - data: sources, - })); - }; - - const audioHandler = async () => { - const sources = await obs.getAudioSources(); - sendMessage(JSON.stringify({ - eventName: 'setAudioSources', - data: sources, - })); - }; - - obs.instance.current - .on('SceneListChanged', scenesHandler) - - .on('InputCreated', audioHandler) - .on('InputRemoved', audioHandler) - .on('InputNameChanged', audioHandler) - - .on('SceneItemCreated', scenesHandler) - .on('SceneItemRemoved', scenesHandler); - }); - - return () => { - obs.disconnect(); - }; - }, [settings]); - - return
; -}; diff --git a/frontend/overlays/src/pages/obs.vue b/frontend/overlays/src/pages/obs.vue new file mode 100644 index 000000000..79e7cb05d --- /dev/null +++ b/frontend/overlays/src/pages/obs.vue @@ -0,0 +1,98 @@ + diff --git a/frontend/overlays/src/pages/overlays.vue b/frontend/overlays/src/pages/overlays.vue new file mode 100644 index 000000000..847e8547a --- /dev/null +++ b/frontend/overlays/src/pages/overlays.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/frontend/overlays/src/pages/overlaysRegistry.tsx b/frontend/overlays/src/pages/overlaysRegistry.tsx deleted file mode 100644 index cb50b313a..000000000 --- a/frontend/overlays/src/pages/overlaysRegistry.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import { transform } from 'nested-css-to-flat'; -import { Fragment, useCallback, useEffect, useRef, useState } from 'react'; -import { useParams } from 'react-router-dom'; -import useWebSocket from 'react-use-websocket'; - - -declare global { - interface Window { - webkitAudioContext: typeof AudioContext - } -} - -interface Layer { - id: string - type: 'HTML' - settings: LayerSettings - overlay_id: string - pos_x: number - pos_y: number - width: number - height: number - createdAt: string - updatedAt: string - overlay: any - periodically_refetch_data: boolean; - - htmlContent?: string; -} - -export interface LayerSettings { - htmlOverlayDataPollSecondsInterval: number - htmlOverlayHtml: string - htmlOverlayCss: string - htmlOverlayJs: string -} - -export const OverlaysRegistry: React.FC = () => { - const [url, setUrl] = useState(null); - const { apiKey, overlayId } = useParams(); - const contentRef = useRef(null); - - const { lastMessage, sendMessage } = useWebSocket(url, { - shouldReconnect: () => true, - onOpen: () => { - sendMessage(JSON.stringify({ - eventName: 'getLayers', - data: { - overlayId, - }, - })); - }, - reconnectInterval: 500, - }); - - const [layers, setLayers] = useState([]); - - useEffect(() => { - if (!apiKey) return; - - const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'; - const host = window.location.host; - - setUrl(`${protocol}://${host}/socket/registry/overlays?apiKey=${apiKey}`); - }, [apiKey]); - - useEffect(() => { - if (!lastMessage) return; - try { - const parsedData = JSON.parse(lastMessage.data); - - if (parsedData.eventName === 'refreshOverlays') { - window.location.reload(); - } - - if (parsedData.eventName === 'layers') { - setLayers(parsedData.layers); - for (const layer of parsedData.layers) { - if (layer.type === 'HTML') { - preparePollHtmlOverlayData(layer as Layer); - } - } - } - - if (parsedData.eventName === 'parsedLayerVariables') { - processParsedLayerVariables(parsedData); - } - } catch (e) { - console.error('cannot parse message', lastMessage.data); - } - }, [lastMessage]); - - const processParsedLayerVariables = useCallback((parsedData: any) => { - const layer = layers.find((l) => l.id === parsedData.layerId); - if (!layer) return; - - setLayers((prevLayers) => { - return prevLayers.map((l) => { - if (l.id !== parsedData.layerId) return l; - return { - ...l, - htmlContent: parsedData.data, - }; - }); - }); - }, [layers]); - - const preparePollHtmlOverlayData = useCallback((l: Layer) => { - if (l.settings.htmlOverlayDataPollSecondsInterval <= 0) return; - - const getInfo = () => sendMessage(JSON.stringify({ - eventName: 'parseLayerVariables', - data: { - layerId: l.id, - }, - })); - getInfo(); - - if (!l.periodically_refetch_data) return; - - const interval = setInterval(() => { - getInfo(); - }, l.settings.htmlOverlayDataPollSecondsInterval * 1000); - - return () => { - clearInterval(interval); - }; - }, [layers]); - - return
- {layers.filter(l => l.type === 'HTML').map((layer) => { - return - -
- ; - })} -
; -}; - - -// function b64EncodeUnicode(str: string) { -// return btoa( -// encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function toSolidBytes(_, p1) { -// return String.fromCharCode(parseInt('0x' + p1)); -// }), -// ); -// } - -function b64DecodeUnicode(str: string) { - return decodeURIComponent( - atob(str) - .split('') - .map(function (c) { - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }) - .join(''), - ); -} diff --git a/frontend/overlays/src/pages/tts.tsx b/frontend/overlays/src/pages/tts.tsx deleted file mode 100644 index 5cb14ce59..000000000 --- a/frontend/overlays/src/pages/tts.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; -import { useParams } from 'react-router-dom'; -import useWebSocket from 'react-use-websocket'; - -import { unprotectedApiClient } from '../libs/twirp'; - -declare global { - interface Window { - webkitAudioContext: typeof AudioContext - } -} - -export const TTS: React.FC = () => { - const [url, setUrl] = useState(null); - const { apiKey } = useParams(); - const { lastMessage } = useWebSocket(url, { - shouldReconnect: () => true, - onOpen: () => console.log('Opened'), - reconnectInterval: 500, - }); - - useEffect(() => { - if (!lastMessage) return; - const parsedData = JSON.parse(lastMessage.data); - - if (parsedData.eventName === 'say') { - queueRef.current.push(parsedData.data); - - if (queueRef.current.length === 1) { - processQueue(); - } - } - - if (parsedData.eventName === 'skip') { - currentAudioBuffer.current?.stop(); - } - }, [lastMessage]); - - useEffect(() => { - if (!apiKey) return; - - setUrl(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}/socket/tts?apiKey=${apiKey}`); - }, [apiKey]); - - const queueRef = useRef>>([]); - const currentAudioBuffer = useRef(null); - - const processQueue = useCallback(async () => { - if (queueRef.current.length === 0) { - return; - } - - await say(queueRef.current[0]); - queueRef.current = queueRef.current.slice(1); - - // Process the next item in the queue - processQueue(); - }, []); - - const say = async (data: Record) => { - if (!apiKey || !data.text) return; - const audioContext = new (window.AudioContext || window.webkitAudioContext)(); - const gainNode = audioContext.createGain(); - - console.log({ - voice: data.voice, - text: data.text, - volume: Number(data.volume), - pitch: Number(data.pitch), - rate: Number(data.rate), - }); - const req = await unprotectedApiClient.modulesTTSSay({ - voice: data.voice, - text: data.text, - volume: Number(data.volume), - pitch: Number(data.pitch), - rate: Number(data.rate), - }); - - const source = audioContext.createBufferSource(); - currentAudioBuffer.current = source; - - source.buffer = await audioContext.decodeAudioData(req.response.file.buffer); - - gainNode.gain.value = parseInt(data.volume) / 100; - source.connect(gainNode); - gainNode.connect(audioContext.destination); - - return new Promise((resolve) => { - source.onended = () => { - currentAudioBuffer.current = null; - resolve(null); - }; - - source.start(0); - }); - }; - - return <>; -}; diff --git a/frontend/overlays/src/pages/tts.vue b/frontend/overlays/src/pages/tts.vue new file mode 100644 index 000000000..decbe2ece --- /dev/null +++ b/frontend/overlays/src/pages/tts.vue @@ -0,0 +1,86 @@ + diff --git a/frontend/overlays/src/hooks/obs.ts b/frontend/overlays/src/sockets/obs.ts similarity index 50% rename from frontend/overlays/src/hooks/obs.ts rename to frontend/overlays/src/sockets/obs.ts index 58cc6259e..1f365982c 100644 --- a/frontend/overlays/src/hooks/obs.ts +++ b/frontend/overlays/src/sockets/obs.ts @@ -1,79 +1,68 @@ import OBSWebSocket from 'obs-websocket-js'; -import { useCallback, useRef, useState } from 'react'; - -type ObsSource = { - name: string, - type: string | null -} - -type OBSScenes = { - [x: string]: ObsSource[] -} +import { ref } from 'vue'; export const useObs = () => { - const obs = useRef(new OBSWebSocket()); - const [connected, setConnected] = useState(false); + const obs = ref(new OBSWebSocket()); + const connected = ref(false); - const connect = useCallback(async (address: string, port: number | string, password: string) => { + const connect = async (address: string, port: number | string, password: string) => { if (!address || !port || !password) { return; } - await obs.current.disconnect(); + await obs.value.disconnect(); try { - await obs.current.connect(`ws://${address}:${port}`, password); - setConnected(true); + await obs.value.connect(`ws://${address}:${port}`, password); + connected.value = true; } catch (e) { - setConnected(false); + connected.value = false; throw e; } - }, []); + }; - const disconnect = useCallback(async () => { - obs?.current.disconnect(); - setConnected(false); - }, [obs]); + const disconnect = async () => { + await obs.value.disconnect(); + }; - const setScene = useCallback((sceneName: string) => { - obs.current.call('SetCurrentProgramScene', { sceneName }) - .catch(console.error); - }, [obs]); + const setScene = (sceneName: string) => { + obs.value.call('SetCurrentProgramScene', { sceneName }); + }; - const toggleSource = useCallback(async (sourceName: string) => { - const currentSceneReq = await obs.current.call('GetCurrentProgramScene'); + const toggleSource = async (sourceName: string) => { + const currentSceneReq = await obs.value.call('GetCurrentProgramScene'); if (!currentSceneReq) return; const [currentStateReq, idReq] = await Promise.all([ - obs.current.call('GetSourceActive', { sourceName }), - obs.current.call('GetSceneItemId', { sourceName, sceneName: currentSceneReq.currentProgramSceneName }), + obs.value.call('GetSourceActive', { sourceName }), + obs.value.call('GetSceneItemId', { sourceName, sceneName: currentSceneReq.currentProgramSceneName }), ]); if (!currentStateReq || !idReq) return; - await obs.current.call('SetSceneItemEnabled', { + await obs.value.call('SetSceneItemEnabled', { sceneName: currentSceneReq.currentProgramSceneName, sceneItemId: idReq.sceneItemId, sceneItemEnabled: !currentStateReq.videoShowing, }); - }, [obs]); + }; - const toggleAudioSource = useCallback(async (sourceName: string, muted?: boolean) => { + const toggleAudioSource = async (sourceName: string, muted?: boolean) => { if (typeof muted !== 'undefined') { - await obs.current.call('SetInputMute', { inputName: sourceName, inputMuted: !muted }); + await obs.value.call('SetInputMute', { inputName: sourceName, inputMuted: !muted }); } else { - await obs.current.call('ToggleInputMute', { inputName: sourceName }); + await obs.value.call('ToggleInputMute', { inputName: sourceName }); } - }, [obs]); + }; - const setVolume = useCallback(async (inputName: string, volume: number) => { - await obs.current.call('SetInputVolume', { + const setVolume = async (inputName: string, volume: number) => { + await obs.value.call('SetInputVolume', { inputName, inputVolumeDb: volume * 3 - 60, }); - }, [obs]); + }; - const changeVolume = useCallback(async (inputName: string, step: number, operation: 'increase' | 'decrease') => { - const currentVolumeReq = await obs.current.call('GetInputVolume', { inputName }); + const changeVolume =async (inputName: string, step: number, operation: 'increase' | 'decrease') => { + const currentVolumeReq = await obs.value.call('GetInputVolume', { inputName }); if (!currentVolumeReq) return; if (currentVolumeReq.inputVolumeDb === 0 && operation === 'increase') { @@ -86,28 +75,28 @@ export const useObs = () => { const newVolume = currentVolumeReq.inputVolumeDb + (operation === 'increase' ? step : -step); - await obs.current.call('SetInputVolume', { + await obs.value.call('SetInputVolume', { inputName, inputVolumeDb: newVolume, }); - }, [obs]); + }; - const startStream = useCallback(async () => { - await obs.current.call('StartStream'); - }, [obs]); + const startStream = async () => { + await obs.value.call('StartStream'); + }; - const stopStream = useCallback(async () => { - await obs.current.call('StopStream'); - }, [obs]); + const stopStream = async () => { + await obs.value.call('StopStream'); + }; - const getSources = useCallback(async () => { - const scenesReq = await obs.current.call('GetSceneList'); + const getSources = async () => { + const scenesReq = await obs.value.call('GetSceneList'); if (!scenesReq) return; const mappedScenesNames = scenesReq.scenes.map(s => s.sceneName as string); const itemsPromises = await Promise.all(mappedScenesNames.map((sceneName) => { - return obs.current.call('GetSceneItemList', { sceneName }); + return obs.value.call('GetSceneItemList', { sceneName }); })); const result: OBSScenes = {}; @@ -125,7 +114,7 @@ export const useObs = () => { .map(g => g.sourceName); await Promise.all(groups.map(async (g) => { - const group = await obs.current.call('GetGroupSceneItemList', { sceneName: g as string }); + const group = await obs.value.call('GetGroupSceneItemList', { sceneName: g as string }); if (!group) return; result[sceneName] = [ @@ -139,13 +128,13 @@ export const useObs = () => { })); return result; - }, [obs]); + }; - const getAudioSources = useCallback(async () => { - const req = await obs.current.call('GetInputList'); + const getAudioSources = async () => { + const req = await obs.value.call('GetInputList'); return req?.inputs.map(i => i.inputName as string) ?? []; - }, [obs]); + }; return { connect, @@ -163,3 +152,12 @@ export const useObs = () => { instance: obs, }; }; + +type ObsSource = { + name: string, + type: string | null +} + +type OBSScenes = { + [x: string]: ObsSource[] +} diff --git a/frontend/overlays/src/sockets/overlays.ts b/frontend/overlays/src/sockets/overlays.ts new file mode 100644 index 000000000..7431c8dfb --- /dev/null +++ b/frontend/overlays/src/sockets/overlays.ts @@ -0,0 +1,103 @@ +import { useWebSocket } from '@vueuse/core'; +import { ref, watch } from 'vue'; + +export interface Layer { + id: string + type: 'HTML' + settings: LayerSettings + overlay_id: string + pos_x: number + pos_y: number + width: number + height: number + createdAt: string + updatedAt: string + overlay: any + periodically_refetch_data: boolean; + + htmlContent?: string; +} + +export interface LayerSettings { + htmlOverlayDataPollSecondsInterval: number + htmlOverlayHtml: string + htmlOverlayCss: string + htmlOverlayJs: string +} + +export const useOverlays = (apiKey: string, overlayId: string) => { + const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'; + const host = window.location.host; + + const layers = ref>([]); + + const { data, send } = useWebSocket( + `${protocol}://${host}/socket/registry/overlays?apiKey=${apiKey}`, + { + immediate: true, + autoReconnect: { + delay: 500, + }, + onConnected() { + send(JSON.stringify({ + eventName: 'getLayers', + data: { + overlayId, + }, + })); + }, + }, + ); + + const parsedLayersData = ref>({}); + + watch(data, (d) => { + const parsedData = JSON.parse(d); + + if (parsedData.eventName === 'layers') { + const parsedLayers = parsedData.layers as Array; + + layers.value = parsedLayers.map(l => ({ + ...l, + settings: { + ...l.settings, + htmlOverlayCss: b64DecodeUnicode(l.settings.htmlOverlayCss), + }, + })); + } + + if (parsedData.eventName === 'parsedLayerVariables') { + parsedLayersData.value[parsedData.layerId] = b64DecodeUnicode(parsedData.data); + } + + if (parsedData.eventName === 'refreshOverlays') { + window.location.reload(); + } + }); + + const requestLayerData = (layerId: string) => { + send(JSON.stringify({ + eventName: 'parseLayerVariables', + data: { + layerId, + }, + })); + }; + + return { + layers, + parsedLayersData, + requestLayerData, + }; +}; + +function b64DecodeUnicode(str: string) { + return decodeURIComponent( + atob(str) + .split('') + .map(function (c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }) + .join(''), + ); +} diff --git a/frontend/overlays/src/vite-env.d.ts b/frontend/overlays/src/vite-env.d.ts index 11f02fe2a..de503b58b 100644 --- a/frontend/overlays/src/vite-env.d.ts +++ b/frontend/overlays/src/vite-env.d.ts @@ -1 +1,11 @@ /// + +declare global { + interface Window { + webkitAudioContext: typeof AudioContext + } +} + +/// +/// +declare module '*.vue'; diff --git a/frontend/overlays/tsconfig.json b/frontend/overlays/tsconfig.json index 3d0a51a86..f82888f3d 100644 --- a/frontend/overlays/tsconfig.json +++ b/frontend/overlays/tsconfig.json @@ -1,21 +1,25 @@ { "compilerOptions": { - "target": "ESNext", + "target": "ES2020", "useDefineForClassFields": true, - "lib": ["DOM", "DOM.Iterable", "ESNext"], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, "module": "ESNext", - "moduleResolution": "Node", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true }, - "include": ["src"], + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/frontend/overlays/tsconfig.node.json b/frontend/overlays/tsconfig.node.json index 9d31e2aed..42872c59f 100644 --- a/frontend/overlays/tsconfig.node.json +++ b/frontend/overlays/tsconfig.node.json @@ -1,8 +1,9 @@ { "compilerOptions": { "composite": true, + "skipLibCheck": true, "module": "ESNext", - "moduleResolution": "Node", + "moduleResolution": "bundler", "allowSyntheticDefaultImports": true }, "include": ["vite.config.ts"] diff --git a/frontend/overlays/vite.config.ts b/frontend/overlays/vite.config.ts index a6ad044cd..eb1359f13 100644 --- a/frontend/overlays/vite.config.ts +++ b/frontend/overlays/vite.config.ts @@ -1,10 +1,10 @@ -import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], - base: '/overlays', + plugins: [vue()], + base: '/overlays', server: { host: true, port: 3008, diff --git a/frontend/overlays/vite.config.ts.timestamp-1679603767237.mjs b/frontend/overlays/vite.config.ts.timestamp-1679603767237.mjs deleted file mode 100644 index 4aa6764ee..000000000 --- a/frontend/overlays/vite.config.ts.timestamp-1679603767237.mjs +++ /dev/null @@ -1,15 +0,0 @@ -// vite.config.ts -import react from "file:///home/satont/Projects/tsuwari/node_modules/.pnpm/@vitejs+plugin-react@3.1.0_vite@4.1.4/node_modules/@vitejs/plugin-react/dist/index.mjs"; -import { defineConfig } from "file:///home/satont/Projects/tsuwari/node_modules/.pnpm/vite@4.1.4/node_modules/vite/dist/node/index.js"; -var vite_config_default = defineConfig({ - plugins: [react()], - base: "/overlays", - server: { - host: true, - port: 3008 - } -}); -export { - vite_config_default as default -}; -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS9zYXRvbnQvUHJvamVjdHMvdHN1d2FyaS9mcm9udGVuZC9vdmVybGF5c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL2hvbWUvc2F0b250L1Byb2plY3RzL3RzdXdhcmkvZnJvbnRlbmQvb3ZlcmxheXMvdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL2hvbWUvc2F0b250L1Byb2plY3RzL3RzdXdhcmkvZnJvbnRlbmQvb3ZlcmxheXMvdml0ZS5jb25maWcudHNcIjtpbXBvcnQgcmVhY3QgZnJvbSAnQHZpdGVqcy9wbHVnaW4tcmVhY3QnO1xuaW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSAndml0ZSc7XG5cbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xuICBwbHVnaW5zOiBbcmVhY3QoKV0sXG4gIGJhc2U6ICcvb3ZlcmxheXMnLFxuICBzZXJ2ZXI6IHtcbiAgICBob3N0OiB0cnVlLFxuICAgIHBvcnQ6IDMwMDgsXG4gIH0sXG59KTtcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBK1QsT0FBTyxXQUFXO0FBQ2pWLFNBQVMsb0JBQW9CO0FBRzdCLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFNBQVMsQ0FBQyxNQUFNLENBQUM7QUFBQSxFQUNqQixNQUFNO0FBQUEsRUFDTixRQUFRO0FBQUEsSUFDTixNQUFNO0FBQUEsSUFDTixNQUFNO0FBQUEsRUFDUjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg== diff --git a/frontend/overlays/vite.dev.js b/frontend/overlays/vite.dev.js deleted file mode 100644 index 1ed9abecc..000000000 --- a/frontend/overlays/vite.dev.js +++ /dev/null @@ -1,15 +0,0 @@ -import { fileURLToPath } from 'url'; - -import { createServer } from 'vite'; - -const __dirname = fileURLToPath(new URL('.', import.meta.url)) - -;(async () => { - const server = await createServer({ - configFile: './vite.config.ts', - root: __dirname, - }); - await server.listen(); - - server.printUrls(); -})(); diff --git a/libs/config/config.go b/libs/config/config.go index 4c3d7edbc..a088856fe 100644 --- a/libs/config/config.go +++ b/libs/config/config.go @@ -22,7 +22,7 @@ type Config struct { SentryDsn string `required:"false" envconfig:"SENTRY_DSN"` HostName string `required:"false" default:"localhost:3005" envconfig:"HOSTNAME"` TokensCipherKey string `required:"false" default:"pnyfwfiulmnqlhkvixaeligpprcnlyke" envconfig:"TOKENS_CIPHER_KEY"` - TTSServiceUrl string `required:"false" default:"localhost:7000" envconfig:"TTS_SERVICE_URL"` + TTSServiceUrl string `required:"false" default:"localhost:7001" envconfig:"TTS_SERVICE_URL"` OdesliApiKey string `required:"false" envconfig:"ODESLI_API_KEY"` S3Host string `required:"false" envconfig:"CDN_HOST"` S3Bucket string `required:"false" envconfig:"CDN_BUCKET"` diff --git a/libs/config/src/index.ts b/libs/config/src/index.ts index f3c1091d5..843efb897 100644 --- a/libs/config/src/index.ts +++ b/libs/config/src/index.ts @@ -4,33 +4,33 @@ import * as dotenv from 'dotenv'; import { bool, cleanEnv, str } from 'envalid'; try { - dotenv.config({ path: resolve(process.cwd(), '../../.env') }); - // eslint-disable-next-line no-empty + dotenv.config({ path: resolve(process.cwd(), '../../.env') }); + // eslint-disable-next-line no-empty } catch { } export const config = cleanEnv(process.env, { - DATABASE_URL: str({ - default: 'postgresql://tsuwari:tsuwari@postgres:5432/tsuwari?schema=public', - }), - NODE_ENV: str({ choices: ['development', 'production'], default: 'development' }), - TWITCH_CLIENTID: str({ default: '' }), - TWITCH_CLIENTSECRET: str({ default: '' }), - TWITCH_CALLBACKURL: str({ default: 'http://localhost:3005/login' }), - REDIS_URL: str({ default: 'redis://localhost:6379/0' }), - SAY_IN_CHAT: bool({ default: true }), - HOSTNAME: str({ default: 'localhost:3005' }), - STEAM_USERNAME: str({ default: '' }), - STEAM_PASSWORD: str({ default: '' }), - STEAM_API_KEY: str({ default: '' }), - MINIO_USER: str({ default: '' }), - MINIO_PASSWORD: str({ default: '' }), - MINIO_URL: str({ default: '' }), - TOKENS_CIPHER_KEY: str({ default: 'pnyfwfiulmnqlhkvixaeligpprcnlyke' }), - EVENTSUB_SECRET: str({ default: 'coolEventsubSecret' }), - TTS_SERVICE_URL: str({ default: 'http://localhost:7000' }), - SPOTIFY_CLIENT_ID: str({ default: '' }), - SPOTIFY_CLIENT_SECRET: str({ default: '' }), - ODESLI_API_KEY: str({ default: '' }), + DATABASE_URL: str({ + default: 'postgresql://tsuwari:tsuwari@postgres:5432/tsuwari?schema=public', + }), + NODE_ENV: str({ choices: ['development', 'production'], default: 'development' }), + TWITCH_CLIENTID: str({ default: '' }), + TWITCH_CLIENTSECRET: str({ default: '' }), + TWITCH_CALLBACKURL: str({ default: 'http://localhost:3005/login' }), + REDIS_URL: str({ default: 'redis://localhost:6379/0' }), + SAY_IN_CHAT: bool({ default: true }), + HOSTNAME: str({ default: 'localhost:3005' }), + STEAM_USERNAME: str({ default: '' }), + STEAM_PASSWORD: str({ default: '' }), + STEAM_API_KEY: str({ default: '' }), + MINIO_USER: str({ default: '' }), + MINIO_PASSWORD: str({ default: '' }), + MINIO_URL: str({ default: '' }), + TOKENS_CIPHER_KEY: str({ default: 'pnyfwfiulmnqlhkvixaeligpprcnlyke' }), + EVENTSUB_SECRET: str({ default: 'coolEventsubSecret' }), + TTS_SERVICE_URL: str({ default: 'http://localhost:7001' }), + SPOTIFY_CLIENT_ID: str({ default: '' }), + SPOTIFY_CLIENT_SECRET: str({ default: '' }), + ODESLI_API_KEY: str({ default: '' }), DISCORD_FEEDBACK_URL: str({ default: '' }), }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f1b42885..701c255fc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -334,43 +334,34 @@ importers: '@twir/grpc': specifier: workspace:^ version: link:../../libs/grpc + '@vueuse/core': + specifier: ^10.4.1 + version: 10.4.1(vue@3.3.4) nested-css-to-flat: specifier: ^1.0.5 version: 1.0.5 obs-websocket-js: specifier: ^5.0.3 version: 5.0.3 - react: - specifier: ^18.2.0 - version: 18.2.0 - react-dom: - specifier: ^18.2.0 - version: 18.2.0(react@18.2.0) - react-router-dom: - specifier: ^6.12.1 - version: 6.12.1(react-dom@18.2.0)(react@18.2.0) - react-use-websocket: - specifier: ^4.3.1 - version: 4.3.1(react-dom@18.2.0)(react@18.2.0) - socket.io-client: - specifier: ^4.6.2 - version: 4.6.2 + vue: + specifier: ^3.3.4 + version: 3.3.4 + vue-router: + specifier: ^4.2.4 + version: 4.2.4(vue@3.3.4) devDependencies: - '@types/react': - specifier: ^18.2.12 - version: 18.2.12 - '@types/react-dom': - specifier: ^18.2.5 - version: 18.2.5 - '@vitejs/plugin-react': - specifier: ^4.0.0 - version: 4.0.0(vite@4.3.9) + '@vitejs/plugin-vue': + specifier: ^4.2.3 + version: 4.2.3(vite@4.4.9)(vue@3.3.4) typescript: - specifier: ^5.1.3 - version: 5.1.3 + specifier: ^5.0.2 + version: 5.1.6 vite: - specifier: ^4.3.9 - version: 4.3.9(@types/node@20.4.2) + specifier: ^4.4.5 + version: 4.4.9(@types/node@20.4.2) + vue-tsc: + specifier: ^1.8.5 + version: 1.8.5(typescript@5.1.6) frontend/public-page: dependencies: @@ -1684,26 +1675,6 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-transform-react-jsx-self@7.22.5(@babel/core@7.22.5): - resolution: {integrity: sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 - dev: true - - /@babel/plugin-transform-react-jsx-source@7.22.5(@babel/core@7.22.5): - resolution: {integrity: sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-transform-react-jsx@7.22.5(@babel/core@7.22.5): resolution: {integrity: sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==} engines: {node: '>=6.9.0'} @@ -2235,6 +2206,7 @@ packages: cpu: [arm64] os: [android] requiresBuild: true + dev: false optional: true /@esbuild/android-arm64@0.18.11: @@ -2251,6 +2223,7 @@ packages: cpu: [arm] os: [android] requiresBuild: true + dev: false optional: true /@esbuild/android-arm@0.18.11: @@ -2267,6 +2240,7 @@ packages: cpu: [x64] os: [android] requiresBuild: true + dev: false optional: true /@esbuild/android-x64@0.18.11: @@ -2283,6 +2257,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: false optional: true /@esbuild/darwin-arm64@0.18.11: @@ -2299,6 +2274,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: false optional: true /@esbuild/darwin-x64@0.18.11: @@ -2315,6 +2291,7 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true + dev: false optional: true /@esbuild/freebsd-arm64@0.18.11: @@ -2331,6 +2308,7 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true + dev: false optional: true /@esbuild/freebsd-x64@0.18.11: @@ -2347,6 +2325,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: false optional: true /@esbuild/linux-arm64@0.18.11: @@ -2363,6 +2342,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: false optional: true /@esbuild/linux-arm@0.18.11: @@ -2379,6 +2359,7 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true + dev: false optional: true /@esbuild/linux-ia32@0.18.11: @@ -2395,6 +2376,7 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true + dev: false optional: true /@esbuild/linux-loong64@0.18.11: @@ -2411,6 +2393,7 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true + dev: false optional: true /@esbuild/linux-mips64el@0.18.11: @@ -2427,6 +2410,7 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true + dev: false optional: true /@esbuild/linux-ppc64@0.18.11: @@ -2443,6 +2427,7 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true + dev: false optional: true /@esbuild/linux-riscv64@0.18.11: @@ -2459,6 +2444,7 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true + dev: false optional: true /@esbuild/linux-s390x@0.18.11: @@ -2475,6 +2461,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: false optional: true /@esbuild/linux-x64@0.18.11: @@ -2491,6 +2478,7 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true + dev: false optional: true /@esbuild/netbsd-x64@0.18.11: @@ -2507,6 +2495,7 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true + dev: false optional: true /@esbuild/openbsd-x64@0.18.11: @@ -2523,6 +2512,7 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true + dev: false optional: true /@esbuild/sunos-x64@0.18.11: @@ -2539,6 +2529,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: false optional: true /@esbuild/win32-arm64@0.18.11: @@ -2555,6 +2546,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: false optional: true /@esbuild/win32-ia32@0.18.11: @@ -2571,6 +2563,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: false optional: true /@esbuild/win32-x64@0.18.11: @@ -3134,11 +3127,6 @@ packages: '@redis/client': 1.5.6 dev: false - /@remix-run/router@1.6.3: - resolution: {integrity: sha512-EXJysQ7J3veRECd0kZFQwYYd5sJMcq2O/m60zu1W2l3oVQ9xtub8jTOtYRE0+M2iomyG/W3Ps7+vp2kna0C27Q==} - engines: {node: '>=14'} - dev: false - /@rollup/plugin-babel@5.3.1(@babel/core@7.22.5)(rollup@2.79.1): resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} engines: {node: '>= 10.0.0'} @@ -3227,10 +3215,6 @@ packages: '@daybrush/utils': 1.13.0 dev: false - /@socket.io/component-emitter@3.1.0: - resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==} - dev: false - /@surma/rollup-plugin-off-main-thread@2.2.3: resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} dependencies: @@ -3518,33 +3502,11 @@ packages: resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} dev: false - /@types/prop-types@15.7.5: - resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} - dev: true - - /@types/react-dom@18.2.5: - resolution: {integrity: sha512-sRQsOS/sCLnpQhR4DSKGTtWFE3FZjpQa86KPVbhUqdYMRZ9FEFcfAytKhR/vUG2rH1oFbOOej6cuD7MFSobDRQ==} - dependencies: - '@types/react': 18.2.12 - dev: true - - /@types/react@18.2.12: - resolution: {integrity: sha512-ndmBMLCgn38v3SntMeoJaIrO6tGHYKMEBohCUmw8HoLLQdRMOIGXfeYaBTLe2lsFaSB3MOK1VXscYFnmLtTSmw==} - dependencies: - '@types/prop-types': 15.7.5 - '@types/scheduler': 0.16.3 - csstype: 3.1.2 - dev: true - /@types/resolve@1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} dependencies: '@types/node': 20.4.2 - /@types/scheduler@0.16.3: - resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} - dev: true - /@types/semver@7.5.0: resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} dev: true @@ -3751,21 +3713,6 @@ packages: unconfig: 0.3.10 dev: true - /@vitejs/plugin-react@4.0.0(vite@4.3.9): - resolution: {integrity: sha512-HX0XzMjL3hhOYm+0s95pb0Z7F8O81G7joUHgfDd/9J/ZZf5k4xX6QAMFkKsHFxaHlf6X7GD7+XuaZ66ULiJuhQ==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 - dependencies: - '@babel/core': 7.22.5 - '@babel/plugin-transform-react-jsx-self': 7.22.5(@babel/core@7.22.5) - '@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.22.5) - react-refresh: 0.14.0 - vite: 4.3.9(@types/node@20.4.2) - transitivePeerDependencies: - - supports-color - dev: true - /@vitejs/plugin-vue-jsx@3.0.1(vite@4.4.9)(vue@3.3.4): resolution: {integrity: sha512-+Jb7ggL48FSPS1uhPnJbJwWa9Sr90vQ+d0InW+AhBM22n+cfuYqJZDckBc+W3QSHe1WDvewMZfa4wZOtk5pRgw==} engines: {node: ^14.18.0 || >=16.0.0} @@ -3811,9 +3758,8 @@ packages: vite: ^4.0.0 vue: ^3.2.25 dependencies: - vite: 4.4.9(@types/node@18.13.0) + vite: 4.4.9(@types/node@20.4.2) vue: 3.3.4 - dev: false /@volar/language-core@1.8.1: resolution: {integrity: sha512-TA3qcpFGDsu8SZj/yYybu0ZZWkqi8mi0awL8lC3K/YlJlo+WjNk/yCb43FUfnSYXWuXWnI/i+NzIKYMskx6IEQ==} @@ -3989,7 +3935,7 @@ packages: '@vue/compiler-core': 3.3.4 '@vue/shared': 3.3.4 estree-walker: 2.0.2 - magic-string: 0.30.1 + magic-string: 0.30.2 /@vue/reactivity@3.3.4: resolution: {integrity: sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==} @@ -4074,6 +4020,18 @@ packages: - vue dev: false + /@vueuse/core@10.4.1(vue@3.3.4): + resolution: {integrity: sha512-DkHIfMIoSIBjMgRRvdIvxsyboRZQmImofLyOHADqiVbQVilP8VVHDhBX2ZqoItOgu7dWa8oXiNnScOdPLhdEXg==} + dependencies: + '@types/web-bluetooth': 0.0.17 + '@vueuse/metadata': 10.4.1 + '@vueuse/shared': 10.4.1(vue@3.3.4) + vue-demi: 0.14.6(vue@3.3.4) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: false + /@vueuse/core@9.13.0(vue@3.3.4): resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==} dependencies: @@ -4094,6 +4052,10 @@ packages: resolution: {integrity: sha512-Ema3YhNOa4swDsV0V7CEY5JXvK19JI/o1szFO1iWxdFg3vhdFtCtSTP26PCvbUpnUtNHBY2wx5y3WDXND5Pvnw==} dev: false + /@vueuse/metadata@10.4.1: + resolution: {integrity: sha512-2Sc8X+iVzeuMGHr6O2j4gv/zxvQGGOYETYXEc41h0iZXIRnRbJZGmY/QP8dvzqUelf8vg0p/yEA5VpCEu+WpZg==} + dev: false + /@vueuse/metadata@9.13.0: resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==} dev: false @@ -4116,6 +4078,15 @@ packages: - vue dev: false + /@vueuse/shared@10.4.1(vue@3.3.4): + resolution: {integrity: sha512-vz5hbAM4qA0lDKmcr2y3pPdU+2EVw/yzfRsBdu+6+USGa4PxqSQRYIUC9/NcT06y+ZgaTsyURw2I9qOFaaXHAg==} + dependencies: + vue-demi: 0.14.6(vue@3.3.4) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: false + /@vueuse/shared@9.13.0(vue@3.3.4): resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==} dependencies: @@ -5561,20 +5532,6 @@ packages: - utf-8-validate dev: false - /engine.io-client@6.4.0: - resolution: {integrity: sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==} - dependencies: - '@socket.io/component-emitter': 3.1.0 - debug: 4.3.4 - engine.io-parser: 5.0.7 - ws: 8.11.0 - xmlhttprequest-ssl: 2.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: false - /engine.io-parser@2.2.1: resolution: {integrity: sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==} dependencies: @@ -5585,11 +5542,6 @@ packages: has-binary2: 1.0.3 dev: false - /engine.io-parser@5.0.7: - resolution: {integrity: sha512-P+jDFbvK6lE3n1OL+q9KuzdOFWkkZ/cMV9gol/SbVfpyqfvrfrFTOFJ6fQm2VC3PZHlU3QPhVwmbsCnauHF2MQ==} - engines: {node: '>=10.0.0'} - dev: false - /entities@2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} dev: true @@ -5722,6 +5674,7 @@ packages: '@esbuild/win32-arm64': 0.17.19 '@esbuild/win32-ia32': 0.17.19 '@esbuild/win32-x64': 0.17.19 + dev: false /esbuild@0.18.11: resolution: {integrity: sha512-i8u6mQF0JKJUlGR3OdFLKldJQMMs8OqM9Cc3UCi9XXziJ9WERM5bfkHaEAy0YAvPRMgqSW55W7xYn84XtEFTtA==} @@ -6698,6 +6651,7 @@ packages: /he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true dev: true /highlight.js@11.8.0: @@ -7428,12 +7382,6 @@ packages: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} dev: false - /loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - dependencies: - js-tokens: 4.0.0 - dev: false - /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: @@ -7466,7 +7414,6 @@ packages: engines: {node: '>=12'} dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - dev: false /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} @@ -8276,7 +8223,7 @@ packages: debug: 4.3.4 eventemitter3: 5.0.1 isomorphic-ws: 5.0.0(ws@8.13.0) - type-fest: 3.11.1 + type-fest: 3.13.1 ws: 8.13.0 transitivePeerDependencies: - bufferutil @@ -8963,16 +8910,6 @@ packages: framework-utils: 1.1.0 dev: false - /react-dom@18.2.0(react@18.2.0): - resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} - peerDependencies: - react: ^18.2.0 - dependencies: - loose-envify: 1.4.0 - react: 18.2.0 - scheduler: 0.23.0 - dev: false - /react-moveable@0.54.1: resolution: {integrity: sha512-Kj2ifw9nk3LZvu7ezhst8Z5WBPRr+yVv9oROwrBirFlHmwGHHZXUGk5Gaezu+JGqqNRsQJncVMW5Uf68KSSOvg==} dependencies: @@ -8991,57 +8928,12 @@ packages: react-selecto: 1.26.0 dev: false - /react-refresh@0.14.0: - resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} - engines: {node: '>=0.10.0'} - dev: true - - /react-router-dom@6.12.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-POIZN9UDKWwEDga054LvYr2KnK8V+0HR4Ny4Bwv8V7/FZCPxJgsCjYxXGxqxzHs7VBxMKZfgvtKhafuJkJSPGA==} - engines: {node: '>=14'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - dependencies: - '@remix-run/router': 1.6.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-router: 6.12.1(react@18.2.0) - dev: false - - /react-router@6.12.1(react@18.2.0): - resolution: {integrity: sha512-evd/GrKJOeOypD0JB9e1r7pQh2gWCsTbUfq059Wm1AFT/K2MNZuDo19lFtAgIhlBrp0MmpgpqtvZC7LPAs7vSw==} - engines: {node: '>=14'} - peerDependencies: - react: '>=16.8' - dependencies: - '@remix-run/router': 1.6.3 - react: 18.2.0 - dev: false - /react-selecto@1.26.0: resolution: {integrity: sha512-aBTZEYA68uE+o8TytNjTb2GpIn4oKEv0U4LIow3cspJQlF/PdAnBwkq9UuiKVuFluu5kfLQ7Keu3S2Tihlmw0g==} dependencies: selecto: 1.26.0 dev: false - /react-use-websocket@4.3.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-zHPLWrgcqydJaak2O5V9hiz4q2dwkwqNQqpgFVmSuPxLZdsZlnDs8DVHy3WtHH+A6ms/8aHIyX7+7ulOcrnR0Q==} - peerDependencies: - react: '>= 18.0.0' - react-dom: '>= 18.0.0' - dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} - engines: {node: '>=0.10.0'} - dependencies: - loose-envify: 1.4.0 - dev: false - /read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} dependencies: @@ -9373,13 +9265,6 @@ packages: fsevents: 2.3.2 dev: true - /rollup@3.25.1: - resolution: {integrity: sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==} - engines: {node: '>=14.18.0', npm: '>=8.0.0'} - optionalDependencies: - fsevents: 2.3.2 - dev: true - /rollup@3.26.2: resolution: {integrity: sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} @@ -9462,12 +9347,6 @@ packages: source-map-js: 1.0.2 dev: true - /scheduler@0.23.0: - resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} - dependencies: - loose-envify: 1.4.0 - dev: false - /section-matter@1.0.0: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} @@ -9672,20 +9551,6 @@ packages: - utf-8-validate dev: false - /socket.io-client@4.6.2: - resolution: {integrity: sha512-OwWrMbbA8wSqhBAR0yoPK6EdQLERQAYjXb3A0zLpgxfM1ZGLKoxHx8gVmCHA6pcclRX5oA/zvQf7bghAS11jRA==} - engines: {node: '>=10.0.0'} - dependencies: - '@socket.io/component-emitter': 3.1.0 - debug: 4.3.4 - engine.io-client: 6.4.0 - socket.io-parser: 4.2.4 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: false - /socket.io-parser@3.3.3: resolution: {integrity: sha512-qOg87q1PMWWTeO01768Yh9ogn7chB9zkKtQnya41Y355S0UmpXgpcrFwAgjYJxu9BdKug5r5e9YtVSeWhKBUZg==} dependencies: @@ -9696,16 +9561,6 @@ packages: - supports-color dev: false - /socket.io-parser@4.2.4: - resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} - engines: {node: '>=10.0.0'} - dependencies: - '@socket.io/component-emitter': 3.1.0 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: false - /socks-proxy-agent@7.0.0: resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} engines: {node: '>= 10'} @@ -10516,8 +10371,8 @@ packages: engines: {node: '>=12.20'} dev: false - /type-fest@3.11.1: - resolution: {integrity: sha512-aCuRNRERRVh33lgQaJRlUxZqzfhzwTrsE98Mc3o3VXqmiaQdHacgUtJ0esp+7MvZ92qhtzKPeusaX6vIEcoreA==} + /type-fest@3.13.1: + resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} engines: {node: '>=14.16'} dev: false @@ -10840,13 +10695,14 @@ packages: svgo: 3.0.2 dev: true - /vite@4.3.9(@types/node@20.4.2): - resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==} + /vite@4.4.3(@types/node@20.4.2)(sass@1.63.6): + resolution: {integrity: sha512-IMnXQXXWgLi5brBQx/4WzDxdzW0X3pjO4nqFJAuNvwKtxzAmPzFE1wszW3VDpAGQJm3RZkm/brzRdyGsnwgJIA==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: '@types/node': '>= 14' less: '*' + lightningcss: ^1.21.0 sass: '*' stylus: '*' sugarss: '*' @@ -10856,6 +10712,8 @@ packages: optional: true less: optional: true + lightningcss: + optional: true sass: optional: true stylus: @@ -10866,15 +10724,16 @@ packages: optional: true dependencies: '@types/node': 20.4.2 - esbuild: 0.17.19 + esbuild: 0.18.11 postcss: 8.4.27 - rollup: 3.25.1 + rollup: 3.26.2 + sass: 1.63.6 optionalDependencies: fsevents: 2.3.2 dev: true - /vite@4.4.3(@types/node@20.4.2)(sass@1.63.6): - resolution: {integrity: sha512-IMnXQXXWgLi5brBQx/4WzDxdzW0X3pjO4nqFJAuNvwKtxzAmPzFE1wszW3VDpAGQJm3RZkm/brzRdyGsnwgJIA==} + /vite@4.4.5(@types/node@20.4.2): + resolution: {integrity: sha512-4m5kEtAWHYr0O1Fu7rZp64CfO1PsRGZlD3TAB32UmQlpd7qg15VF7ROqGN5CyqN7HFuwr7ICNM2+fDWRqFEKaA==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -10905,13 +10764,12 @@ packages: esbuild: 0.18.11 postcss: 8.4.27 rollup: 3.26.2 - sass: 1.63.6 optionalDependencies: fsevents: 2.3.2 dev: true - /vite@4.4.5(@types/node@20.4.2): - resolution: {integrity: sha512-4m5kEtAWHYr0O1Fu7rZp64CfO1PsRGZlD3TAB32UmQlpd7qg15VF7ROqGN5CyqN7HFuwr7ICNM2+fDWRqFEKaA==} + /vite@4.4.9(@types/node@18.13.0): + resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -10938,15 +10796,14 @@ packages: terser: optional: true dependencies: - '@types/node': 20.4.2 + '@types/node': 18.13.0 esbuild: 0.18.11 postcss: 8.4.27 - rollup: 3.26.2 + rollup: 3.28.0 optionalDependencies: fsevents: 2.3.2 - dev: true - /vite@4.4.9(@types/node@18.13.0): + /vite@4.4.9(@types/node@20.4.2): resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -10974,7 +10831,7 @@ packages: terser: optional: true dependencies: - '@types/node': 18.13.0 + '@types/node': 20.4.2 esbuild: 0.18.11 postcss: 8.4.27 rollup: 3.28.0 @@ -11530,19 +11387,6 @@ packages: async-limiter: 1.0.1 dev: false - /ws@8.11.0: - resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: false - /ws@8.13.0: resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} engines: {node: '>=10.0.0'} @@ -11566,11 +11410,6 @@ packages: engines: {node: '>=0.4.0'} dev: false - /xmlhttprequest-ssl@2.0.0: - resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==} - engines: {node: '>=0.4.0'} - dev: false - /xmlhttprequest@1.8.0: resolution: {integrity: sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==} engines: {node: '>=0.4.0'}