From 3edac56784d8c3642905c2c8515b1b3dfa17139f Mon Sep 17 00:00:00 2001 From: ItsRiprod Date: Sat, 2 Nov 2024 13:48:49 -0700 Subject: [PATCH 01/13] Automatic Client Downloads --- .../src/main/handlers/deviceHandler.ts | 147 ++++++++++++++---- .../src/main/handlers/githubHandler.ts | 26 ++++ .../src/main/handlers/musicHandler.ts | 15 ++ .../renderer/src/assets/refreshMessages.ts | 66 ++++---- .../src/renderer/src/pages/Dev/DevApp.tsx | 1 + 5 files changed, 193 insertions(+), 62 deletions(-) diff --git a/DeskThingServer/src/main/handlers/deviceHandler.ts b/DeskThingServer/src/main/handlers/deviceHandler.ts index 88e800f5..5546d020 100644 --- a/DeskThingServer/src/main/handlers/deviceHandler.ts +++ b/DeskThingServer/src/main/handlers/deviceHandler.ts @@ -6,6 +6,7 @@ import { app, net } from 'electron' import { handleAdbCommands } from './adbHandler' import { Client, ClientManifest, ReplyData } from '@shared/types' import settingsStore from '../stores/settingsStore' +import { getLatestRelease } from './githubHandler' export const HandleDeviceData = async (data: string): Promise => { try { @@ -72,11 +73,56 @@ export const configureDevice = async ( }) } + const clientExists = checkForClient(reply) + + if (!clientExists) { + // Download it from github + const repos = settings.clientRepos + + reply && reply('logging', { status: true, data: 'Fetching Latest Client...', final: false }) + const latestReleases = await Promise.all( + repos.map(async (repo) => { + return await getLatestRelease(repo) + }) + ) + + // Sort releases by date and get the latest one + const clientAsset = latestReleases + .flatMap((release) => + release.assets.map((asset) => ({ ...asset, created_at: release.created_at })) + ) + .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) + .find((asset) => asset.name.includes('-client')) + + // Download it + if (clientAsset) { + reply && reply('logging', { status: true, data: 'Downloading Client...', final: false }) + await HandleWebappZipFromUrl(reply, clientAsset.browser_download_url) + + await setTimeout(async () => { + await checkForClient() + }, 1000) + } else { + reply && + reply('logging', { + status: false, + data: 'No client asset found in latest releases', + final: false + }) + } + } + const deviceManifestVersion = await getDeviceManifestVersion(deviceId) const clientManifest = await getClientManifest(true, reply) if (clientManifest && deviceManifestVersion !== clientManifest.version) { try { - await HandlePushWebApp(deviceId, reply) + // Give a 30 second timeout to flash the webapp + await Promise.race([ + HandlePushWebApp(deviceId, reply), + new Promise((_, reject) => + setTimeout(() => reject('Timeout: Operation took longer than 30 seconds'), 30000) + ) + ]) } catch (error) { reply && reply('logging', { @@ -117,6 +163,23 @@ export const HandlePushWebApp = async ( try { const userDataPath = app.getPath('userData') const extractDir = join(userDataPath, 'webapp') + + const clientExists = await checkForClient(reply) + if (!clientExists) { + reply && + reply('logging', { + status: false, + data: 'Unable to push webapp!', + error: 'Client not found!', + final: false + }) + dataListener.asyncEmit( + MESSAGE_TYPES.ERROR, + '[HandlePushWebApp] Client Not Found! Ensure it is downloaded' + ) + return + } + let response console.log('Remounting...') reply && reply('logging', { status: true, data: 'Remounting...', final: false }) @@ -197,7 +260,7 @@ export const HandlePushWebApp = async ( } export const HandleWebappZipFromUrl = async ( - reply: (channel: string, data: ReplyData) => void, + reply: ((channel: string, data: ReplyData) => void) | undefined, zipFileUrl: string ): Promise => { const userDataPath = app.getPath('userData') @@ -210,7 +273,7 @@ export const HandleWebappZipFromUrl = async ( const AdmZip = await import('adm-zip') - reply('logging', { status: true, data: 'Downloading...', final: false }) + reply && reply('logging', { status: true, data: 'Downloading...', final: false }) const request = net.request(zipFileUrl) @@ -244,18 +307,19 @@ export const HandleWebappZipFromUrl = async ( ) // Notify success to the frontend - reply('logging', { status: true, data: 'Success!', final: true }) + reply && reply('logging', { status: true, data: 'Success!', final: true }) } catch (error) { console.error('Error extracting zip file:', error) dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `Error extracting zip file: ${error}`) // Notify failure to the frontend - reply('logging', { - status: false, - data: 'Failed to extract!', - final: true, - error: error instanceof Error ? error.message : String(error) - }) + reply && + reply('logging', { + status: false, + data: 'Failed to extract!', + final: true, + error: error instanceof Error ? error.message : String(error) + }) } }) response.on('error', (error) => { @@ -263,12 +327,13 @@ export const HandleWebappZipFromUrl = async ( dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `Error downloading zip file: ${error}`) // Notify failure to the frontend - reply('logging', { - status: false, - data: 'ERR Downloading file!', - final: true, - error: error.message - }) + reply && + reply('logging', { + status: false, + data: 'ERR Downloading file!', + final: true, + error: error.message + }) }) } else { const errorMessage = `Failed to download zip file: ${response.statusCode}` @@ -276,12 +341,13 @@ export const HandleWebappZipFromUrl = async ( dataListener.asyncEmit(MESSAGE_TYPES.ERROR, errorMessage) // Notify failure to the frontend - reply('logging', { - status: false, - data: 'Failed to download zip file!', - final: true, - error: errorMessage - }) + reply && + reply('logging', { + status: false, + data: 'Failed to download zip file!', + final: true, + error: errorMessage + }) } }) @@ -290,12 +356,13 @@ export const HandleWebappZipFromUrl = async ( dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `Error sending request: ${error}`) // Notify failure to the frontend - reply('logging', { - status: false, - data: 'Failed to download zip file!', - final: true, - error: error.message - }) + reply && + reply('logging', { + status: false, + data: 'Failed to download zip file!', + final: true, + error: error.message + }) }) request.end() @@ -343,6 +410,28 @@ export const handleClientManifestUpdate = async ( } } +export const checkForClient = async ( + reply?: (channel: string, data: ReplyData) => void +): Promise => { + reply && reply('logging', { status: true, data: 'Checking for client...', final: false }) + const userDataPath = app.getPath('userData') + const extractDir = join(userDataPath, 'webapp') + const manifestPath = join(extractDir, 'manifest.js') + + const manifestExists = fs.existsSync(manifestPath) + if (!manifestExists) { + console.log('Manifest file not found') + reply && + reply('logging', { + status: false, + data: 'Manifest file not found', + final: false + }) + dataListener.asyncEmit(MESSAGE_TYPES.ERROR, 'DEVICE HANDLER: Manifest file not found') + } + return manifestExists +} + export const getClientManifest = async ( utility: boolean = false, reply?: (channel: string, data: ReplyData) => void @@ -361,7 +450,7 @@ export const getClientManifest = async ( }) dataListener.asyncEmit( MESSAGE_TYPES.ERROR, - 'DEVICE HANDLER: Client Manifest file not found! (Is the client downloaded?)' + 'DEVICE HANDLER: Client is not detected or downloaded! Please download the client! (downloads -> client)' ) return null } diff --git a/DeskThingServer/src/main/handlers/githubHandler.ts b/DeskThingServer/src/main/handlers/githubHandler.ts index fb729e23..36792a0d 100644 --- a/DeskThingServer/src/main/handlers/githubHandler.ts +++ b/DeskThingServer/src/main/handlers/githubHandler.ts @@ -1,5 +1,31 @@ import { GithubRelease } from '@shared/types' +export async function getLatestRelease(repoUrl: string): Promise { + try { + // Extract the owner and repo from the URL + const repoMatch = repoUrl.match(/github\.com\/([^/]+)\/([^/]+)/) + if (!repoMatch) { + throw new Error('Invalid GitHub repository URL') + } + + const owner = repoMatch[1] + const repo = repoMatch[2] + + const apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases/latest` + const response = await fetch(apiUrl) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + const release = await response.json() + return release + } catch (error) { + console.error('Error fetching latest release:', error) + throw error + } +} + export async function getReleases(repoUrl: string): Promise { try { // Extract the owner and repo from the URL diff --git a/DeskThingServer/src/main/handlers/musicHandler.ts b/DeskThingServer/src/main/handlers/musicHandler.ts index 0d9cc27f..337a64f3 100644 --- a/DeskThingServer/src/main/handlers/musicHandler.ts +++ b/DeskThingServer/src/main/handlers/musicHandler.ts @@ -88,6 +88,21 @@ export class MusicHandler { } } + public async setAudioSource(source: string): Promise { + if (source.length == 0) { + dataListener.asyncEmit( + MESSAGE_TYPES.ERROR, + `[MusicHandler]: Unable to update playback location. No playback location passed!` + ) + return + } + dataListener.asyncEmit( + MESSAGE_TYPES.LOGGING, + `[MusicHandler]: Setting Playback Location to ${source}` + ) + this.currentApp = source + } + public async handleClientRequest(request: SocketData): Promise { if (!this.currentApp) { const settings = await settingsStore.getSettings() diff --git a/DeskThingServer/src/renderer/src/assets/refreshMessages.ts b/DeskThingServer/src/renderer/src/assets/refreshMessages.ts index f3c102e9..c9a18bc8 100644 --- a/DeskThingServer/src/renderer/src/assets/refreshMessages.ts +++ b/DeskThingServer/src/renderer/src/assets/refreshMessages.ts @@ -1,37 +1,37 @@ export const deviceMessages = [ { message: 'No device found.', weight: 1, minimum: 0 }, { message: 'Have you turning it off and back on again?', weight: 3, minimum: 5 }, - { message: 'Still nothing...', weight: 1, minimum: 10 }, + { message: 'Still nothing...', weight: 1, minimum: 3 }, { message: 'Ensure your device is ADB enabled!', weight: 4, minimum: 2 }, - { message: 'Maybe try checking the logs', weight: 2, minimum: 15 }, - { message: 'Double check your cables!', weight: 3, minimum: 7 }, - { message: 'Make sure its plugged directly into your motherboard!', weight: 3, minimum: 12 }, - { message: 'Try a different USB port!', weight: 3, minimum: 8 }, - { message: 'Try a different USB cable!', weight: 3, minimum: 13 }, - { message: 'Try a different USB adapter!', weight: 2, minimum: 18 }, - { message: 'Try a different USB hub! (if applicable)', weight: 2, minimum: 22 }, - { message: 'Does it at least show up in device manager?', weight: 3, minimum: 14 }, + { message: 'Maybe try checking the logs', weight: 2, minimum: 3 }, + { message: 'Double check your cables!', weight: 3, minimum: 3 }, + { message: 'Make sure its plugged directly into your motherboard!', weight: 3, minimum: 3 }, + { message: 'Try a different USB port!', weight: 3, minimum: 3 }, + { message: 'Try a different USB cable!', weight: 3, minimum: 3 }, + { message: 'Try a different USB adapter!', weight: 2, minimum: 3 }, + { message: 'Try a different USB hub! (if applicable)', weight: 2, minimum: 3 }, + { message: 'Does it at least show up in device manager?', weight: 3, minimum: 3 }, { message: 'Is there a USB connection sound?', weight: 2, minimum: 6 }, - { message: 'Try turning if off and on again!', weight: 3, minimum: 16 }, + { message: 'Try turning if off and on again!', weight: 3, minimum: 3 }, { message: 'Are you sure you have ADB enabled?', weight: 4, minimum: 9 }, - { message: 'Try running ADB Devices in the terminal! (If you have ADB)', weight: 4, minimum: 11 }, - { message: 'Installing ADB separately and using Global ADB might help!', weight: 3, minimum: 25 }, + { message: 'Try running ADB Devices in the terminal! (If you have ADB)', weight: 4, minimum: 5 }, + { message: 'Installing ADB separately and using Global ADB might help!', weight: 3, minimum: 5 }, { message: '(Phones) Check if USB debugging is enabled in Developer Options', weight: 4, - minimum: 30 + minimum: 5 }, { message: '(Phones) Try revoking USB debugging authorizations and reconnecting', weight: 3, - minimum: 35 + minimum: 5 }, { message: 'Ensure your device drivers are up to date', weight: 3, minimum: 17 }, { message: '(Phones) Check if your device is in the correct mode (e.g., not in charging only mode)', weight: 3, - minimum: 40 + minimum: 5 }, { message: 'Try using a different computer if possible', weight: 2, minimum: 20 }, { @@ -47,7 +47,7 @@ export const deviceMessages = [ { message: 'Ensure that no other program is using ADB or has locked the device', weight: 3, - minimum: 23 + minimum: 5 }, { message: 'Try restarting ADB (Dev -> ADB -> Restart Server)', weight: 4, minimum: 32 }, { message: 'Have you tried asking the device nicely?', weight: 1, minimum: 19 }, @@ -55,87 +55,87 @@ export const deviceMessages = [ { message: "Plot twist: The device was working all along, and we're in a simulation!", weight: 1, - minimum: 50 + minimum: 12 }, { message: 'Why do we try if it only continues to bring suffering', weight: 1, - minimum: 50 + minimum: 12 }, { message: 'I aspire to have your level of persistance', weight: 1, - minimum: 50 + minimum: 15 }, { message: 'I dont think you understand how difficult it is to debug this', weight: 1, - minimum: 50 + minimum: 15 }, { message: 'Okay I get it - I dont think its working', weight: 1, - minimum: 50 + minimum: 20 }, { message: "You've been at this for quite awhile now, I think you should take a break", weight: 1, - minimum: 50 + minimum: 20 }, { message: 'TOUCH GRASS', weight: 1, - minimum: 70 + minimum: 30 }, { message: '"Insanity is doing the same thing over and over again and expecting different results,"', weight: 1, - minimum: 70 + minimum: 30 }, { message: 'Try one more time. I believe in you', weight: 1, - minimum: 70 + minimum: 30 }, { message: 'Im sure the problem is something simple, you just need to find it', weight: 1, - minimum: 70 + minimum: 30 }, { message: 'At this point just ask on the discord!', weight: 1, - minimum: 120 + minimum: 50 }, { message: 'At this point just ask on the discord!', weight: 1, - minimum: 120 + minimum: 50 }, { message: 'At this point just ask on the discord!', weight: 1, - minimum: 120 + minimum: 50 }, { message: 'Some people never give up!', weight: 1, - minimum: 150 + minimum: 60 }, { message: 'Woah! You have a lot of patience. This means youve been trying for a while', weight: 1, - minimum: 1000 + minimum: 60 }, { message: 'This is an easter egg. Send a screenshot of this over the discord', weight: 1, - minimum: 1200 + minimum: 100 }, { message: 'There are no more messages. You have reached the end of the line', weight: 1, - minimum: 1250 + minimum: 110 } ] diff --git a/DeskThingServer/src/renderer/src/pages/Dev/DevApp.tsx b/DeskThingServer/src/renderer/src/pages/Dev/DevApp.tsx index 98d679e8..05ac04d2 100644 --- a/DeskThingServer/src/renderer/src/pages/Dev/DevApp.tsx +++ b/DeskThingServer/src/renderer/src/pages/Dev/DevApp.tsx @@ -4,6 +4,7 @@ import MainElement from '@renderer/nav/MainElement' import Loading from '@renderer/components/Loading' const DevApp: React.FC = () => { + return (
From 2f47c9c65e712d4117c4426a88e96b709b540832 Mon Sep 17 00:00:00 2001 From: ItsRiprod Date: Tue, 12 Nov 2024 04:19:48 -0700 Subject: [PATCH 02/13] Auto client downloads + better URL handling --- .../src/main/handlers/deviceHandler.ts | 14 +- .../main/services/apps/appCommunication.ts | 2 +- .../src/main/services/client/clientCom.ts | 4 +- .../src/main/services/client/expressServer.ts | 63 +++++++- .../src/main/static/defaultMapping.ts | 144 +++++++++--------- .../src/main/stores/keyMapStore.ts | 38 ++--- .../renderer/src/overlays/apps/AppDetails.tsx | 2 +- .../src/renderer/src/pages/Dev/DevApp.tsx | 1 - .../src/renderer/src/stores/keyStore.ts | 10 +- DeskThingServer/src/shared/types/maps.ts | 8 +- DeskThingServer/src/shared/types/types.ts | 7 +- 11 files changed, 186 insertions(+), 107 deletions(-) diff --git a/DeskThingServer/src/main/handlers/deviceHandler.ts b/DeskThingServer/src/main/handlers/deviceHandler.ts index 5546d020..b9401376 100644 --- a/DeskThingServer/src/main/handlers/deviceHandler.ts +++ b/DeskThingServer/src/main/handlers/deviceHandler.ts @@ -73,12 +73,14 @@ export const configureDevice = async ( }) } - const clientExists = checkForClient(reply) + const clientExists = await checkForClient(reply) + console.log('Device Status:', clientExists) if (!clientExists) { // Download it from github const repos = settings.clientRepos + console.log('Fetching Latest Client...') reply && reply('logging', { status: true, data: 'Fetching Latest Client...', final: false }) const latestReleases = await Promise.all( repos.map(async (repo) => { @@ -102,6 +104,16 @@ export const configureDevice = async ( await setTimeout(async () => { await checkForClient() }, 1000) + + await setTimeout(async () => { + reply && + reply('logging', { + status: true, + data: 'Client not found! Downloading Client...', + final: false + }) + await checkForClient() + }, 5000) } else { reply && reply('logging', { diff --git a/DeskThingServer/src/main/services/apps/appCommunication.ts b/DeskThingServer/src/main/services/apps/appCommunication.ts index c643b348..818e2876 100644 --- a/DeskThingServer/src/main/services/apps/appCommunication.ts +++ b/DeskThingServer/src/main/services/apps/appCommunication.ts @@ -90,7 +90,7 @@ export async function handleDataFromApp(app: string, appData: IncomingData): Pro source: app, version: appData.payload.version || '0.0.0', enabled: true, - flavors: appData.payload.flavors || [] + Modes: appData.payload.Modes || [] } keyMapStore.addKey(Key) dataListener.asyncEmit( diff --git a/DeskThingServer/src/main/services/client/clientCom.ts b/DeskThingServer/src/main/services/client/clientCom.ts index 517a3157..9b2eaa55 100644 --- a/DeskThingServer/src/main/services/client/clientCom.ts +++ b/DeskThingServer/src/main/services/client/clientCom.ts @@ -54,7 +54,9 @@ export const sendConfigData = async (clientId?: string): Promise => { try { const appData = await appState.getAllBase() - sendMessageToClient(clientId, { app: 'client', type: 'config', payload: appData }) + const filteredAppData = appData.filter((app) => app.manifest?.isWebApp !== false) + + sendMessageToClient(clientId, { app: 'client', type: 'config', payload: filteredAppData }) console.log('WSOCKET: Preferences sent!') } catch (error) { diff --git a/DeskThingServer/src/main/services/client/expressServer.ts b/DeskThingServer/src/main/services/client/expressServer.ts index 9e3da617..c37973d6 100644 --- a/DeskThingServer/src/main/services/client/expressServer.ts +++ b/DeskThingServer/src/main/services/client/expressServer.ts @@ -58,7 +58,7 @@ export const setupExpressServer = async (expressApp: express.Application): Promi }) // Serve web apps dynamically based on the URL - expressApp.use('/:appName', (req, res, next) => { + expressApp.use('/app/:appName', (req, res, next) => { const appName = req.params.appName if (appName === 'client' || appName == null) { handleClientConnection(appName, req, res, next) @@ -73,4 +73,65 @@ export const setupExpressServer = async (expressApp: express.Application): Promi } } }) + + // Serve icons dynamically based on the URL + expressApp.use('/icon/:appName/:iconName', (req, res, next) => { + const iconName = req.params.iconName + const appName = req.params.appName + if (iconName != null) { + const appPath = getAppFilePath(appName) + dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `WEBSOCKET: Serving ${appName} from ${appPath}`) + + if (fs.existsSync(join(appPath, 'icons', iconName))) { + express.static(join(appPath, 'icons'))(req, res, next) + } else { + res.status(404).send('Icon not found') + } + } + }) + + // Serve icons dynamically based on the URL + expressApp.use('/image/:appName/:imageName', (req, res, next) => { + const imageName = req.params.imageName + const appName = req.params.appName + if (imageName != null) { + const appPath = getAppFilePath(appName) + dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `WEBSOCKET: Serving ${appName} from ${appPath}`) + + if (fs.existsSync(join(appPath, 'images', imageName))) { + express.static(join(appPath, 'icons'))(req, res, next) + } else { + res.status(404).send('Icon not found') + } + } + }) + + // Proxy external resources + expressApp.use('/fetch/:url(*)', async (req, res) => { + try { + const url = decodeURIComponent(req.params.url) + dataListener.asyncEmit( + MESSAGE_TYPES.LOGGING, + `WEBSOCKET: Fetching external resource from ${url}` + ) + + const response = await fetch(url) + const contentType = response.headers.get('content-type') + + if (contentType) { + res.setHeader('Content-Type', contentType) + } + if (response.body) { + response.body.pipeTo(res) + } + } catch (error) { + if (error instanceof Error) { + dataListener.asyncEmit( + MESSAGE_TYPES.LOGGING, + `WEBSOCKET: Error fetching external resource: ${error.message}` + ) + } + res.status(500).send('Error fetching resource') + } + }) } diff --git a/DeskThingServer/src/main/static/defaultMapping.ts b/DeskThingServer/src/main/static/defaultMapping.ts index c7a44633..9add34c8 100644 --- a/DeskThingServer/src/main/static/defaultMapping.ts +++ b/DeskThingServer/src/main/static/defaultMapping.ts @@ -1,4 +1,4 @@ -import { Key, EventFlavor, Action, MappingStructure, ButtonMapping } from '@shared/types' +import { Key, EventMode, Action, MappingStructure, ButtonMapping } from '@shared/types' const keys: Key[] = [ { @@ -7,7 +7,7 @@ const keys: Key[] = [ description: 'First dynamic action button on the miniplayer', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] }, { id: 'DynamicAction2', @@ -15,7 +15,7 @@ const keys: Key[] = [ description: 'Second dynamic action button on the miniplayer', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] }, { id: 'DynamicAction3', @@ -23,7 +23,7 @@ const keys: Key[] = [ description: 'Third dynamic action button on the miniplayer', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] }, { id: 'DynamicAction4', @@ -31,7 +31,7 @@ const keys: Key[] = [ description: 'Fourth dynamic action button on the miniplayer', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] }, { id: 'Action5', @@ -39,7 +39,7 @@ const keys: Key[] = [ description: 'Fifth action button, always visible on the miniplayer', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] }, { id: 'Action6', @@ -47,7 +47,7 @@ const keys: Key[] = [ description: 'Sixth action button, always visible on the miniplayer', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] }, { id: 'Action7', @@ -55,7 +55,7 @@ const keys: Key[] = [ description: 'Seventh action button, always visible on the miniplayer', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] }, { id: 'Digit1', @@ -63,7 +63,7 @@ const keys: Key[] = [ description: 'Physical Button Digit1', version: '0.9.0', enabled: true, - flavors: [EventFlavor.PressShort, EventFlavor.PressLong, EventFlavor.KeyDown, EventFlavor.KeyUp] + Modes: [EventMode.PressShort, EventMode.PressLong, EventMode.KeyDown, EventMode.KeyUp] }, { id: 'Digit2', @@ -71,7 +71,7 @@ const keys: Key[] = [ description: 'Physical Button Digit2', version: '0.9.0', enabled: true, - flavors: [EventFlavor.PressShort, EventFlavor.PressLong, EventFlavor.KeyDown, EventFlavor.KeyUp] + Modes: [EventMode.PressShort, EventMode.PressLong, EventMode.KeyDown, EventMode.KeyUp] }, { id: 'Digit3', @@ -79,7 +79,7 @@ const keys: Key[] = [ description: 'Physical Button Digit3', version: '0.9.0', enabled: true, - flavors: [EventFlavor.PressShort, EventFlavor.PressLong, EventFlavor.KeyDown, EventFlavor.KeyUp] + Modes: [EventMode.PressShort, EventMode.PressLong, EventMode.KeyDown, EventMode.KeyUp] }, { id: 'Digit4', @@ -87,7 +87,7 @@ const keys: Key[] = [ description: 'Physical Button Digit4', version: '0.9.0', enabled: true, - flavors: [EventFlavor.PressShort, EventFlavor.PressLong, EventFlavor.KeyDown, EventFlavor.KeyUp] + Modes: [EventMode.PressShort, EventMode.PressLong, EventMode.KeyDown, EventMode.KeyUp] }, { id: 'KeyM', @@ -95,7 +95,7 @@ const keys: Key[] = [ description: 'Physical Button M', version: '0.9.0', enabled: true, - flavors: [EventFlavor.PressShort, EventFlavor.PressLong, EventFlavor.KeyDown, EventFlavor.KeyUp] + Modes: [EventMode.PressShort, EventMode.PressLong, EventMode.KeyDown, EventMode.KeyUp] }, { id: 'Scroll', @@ -103,11 +103,11 @@ const keys: Key[] = [ description: 'Physical Button Scroll', version: '0.9.0', enabled: true, - flavors: [ - EventFlavor.ScrollUp, - EventFlavor.ScrollDown, - EventFlavor.ScrollLeft, - EventFlavor.ScrollRight + Modes: [ + EventMode.ScrollUp, + EventMode.ScrollDown, + EventMode.ScrollLeft, + EventMode.ScrollRight ] }, { @@ -116,7 +116,7 @@ const keys: Key[] = [ description: 'Physical Button Enter', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown, EventFlavor.PressLong, EventFlavor.KeyDown, EventFlavor.KeyUp] + Modes: [EventMode.KeyDown, EventMode.PressLong, EventMode.KeyDown, EventMode.KeyUp] }, { id: 'Escape', @@ -124,7 +124,7 @@ const keys: Key[] = [ description: 'Physical Button Escape', version: '0.9.0', enabled: true, - flavors: [EventFlavor.PressShort, EventFlavor.PressLong, EventFlavor.KeyDown, EventFlavor.KeyUp] + Modes: [EventMode.PressShort, EventMode.PressLong, EventMode.KeyDown, EventMode.KeyUp] }, { id: 'Swipe', @@ -132,11 +132,11 @@ const keys: Key[] = [ description: 'Touchpad Swipe Button', version: '0.9.0', enabled: true, - flavors: [ - EventFlavor.ScrollUp, - EventFlavor.ScrollDown, - EventFlavor.ScrollLeft, - EventFlavor.ScrollRight + Modes: [ + EventMode.ScrollUp, + EventMode.ScrollDown, + EventMode.ScrollLeft, + EventMode.ScrollRight ] }, { @@ -145,7 +145,7 @@ const keys: Key[] = [ description: 'Touch Pad 1 on the fullscreen miniplayer view', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] }, { id: 'Pad2', @@ -153,7 +153,7 @@ const keys: Key[] = [ description: 'Touch Pad 2 on the fullscreen miniplayer view', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] }, { id: 'Pad3', @@ -161,7 +161,7 @@ const keys: Key[] = [ description: 'Touch Pad 3 on the fullscreen miniplayer view', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] }, { id: 'Pad4', @@ -169,7 +169,7 @@ const keys: Key[] = [ description: 'Touch Pad 4 on the fullscreen miniplayer view', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] }, { id: 'Pad5', @@ -177,7 +177,7 @@ const keys: Key[] = [ description: 'Touch Pad 5 on the fullscreen miniplayer view', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] }, { id: 'Pad6', @@ -185,7 +185,7 @@ const keys: Key[] = [ description: 'Touch Pad 6 on the fullscreen miniplayer view', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] }, { id: 'Pad7', @@ -193,7 +193,7 @@ const keys: Key[] = [ description: 'Touch Pad 7 on the fullscreen miniplayer view', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] }, { id: 'Pad8', @@ -201,7 +201,7 @@ const keys: Key[] = [ description: 'Touch Pad 8 on the fullscreen miniplayer view', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] }, { id: 'Pad9', @@ -209,7 +209,7 @@ const keys: Key[] = [ description: 'Touch Pad 9 on the fullscreen miniplayer view', version: '0.9.0', enabled: true, - flavors: [EventFlavor.KeyDown] + Modes: [EventMode.KeyDown] } ] @@ -364,7 +364,7 @@ const defaults: ButtonMapping = { version: '0.9.0', mapping: { Pad1: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'Volume Up', id: 'volUp', value: '15', @@ -376,7 +376,7 @@ const defaults: ButtonMapping = { } }, Pad2: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'Open Previous', id: 'swipeL', description: 'Opens the app at the previous index', @@ -386,7 +386,7 @@ const defaults: ButtonMapping = { } }, Pad3: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'Open Next', id: 'swipeR', description: 'Opens the app at the next index', @@ -396,7 +396,7 @@ const defaults: ButtonMapping = { } }, Pad4: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'Volume Down', id: 'volDown', value: '15', @@ -408,7 +408,7 @@ const defaults: ButtonMapping = { } }, Pad5: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'Toggle AppsList', id: 'appsList', value: 'hide', @@ -421,7 +421,7 @@ const defaults: ButtonMapping = { } }, Pad6: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'Toggle AppsList', id: 'appsList', value: 'show', @@ -434,7 +434,7 @@ const defaults: ButtonMapping = { } }, Pad7: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'Repeat', id: 'repeat', description: 'Toggles repeat', @@ -444,7 +444,7 @@ const defaults: ButtonMapping = { } }, Pad8: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'PlayPause', id: 'play', icon: 'play', @@ -455,7 +455,7 @@ const defaults: ButtonMapping = { } }, Pad9: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'Fullscreen', id: 'fullscreen', description: 'Toggles Fullscreen on most devices', @@ -465,7 +465,7 @@ const defaults: ButtonMapping = { } }, DynamicAction1: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'Shuffle', id: 'shuffle', value: 'toggle', @@ -478,7 +478,7 @@ const defaults: ButtonMapping = { } }, DynamicAction2: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'Repeat', id: 'repeat', description: 'Repeats the song', @@ -488,7 +488,7 @@ const defaults: ButtonMapping = { } }, DynamicAction3: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'Rewind', id: 'rewind', value: 'stop', @@ -502,7 +502,7 @@ const defaults: ButtonMapping = { } }, DynamicAction4: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'Hidden Button', id: 'hidden', description: 'Hides the button. Has no action', @@ -512,7 +512,7 @@ const defaults: ButtonMapping = { } }, Action5: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'Hidden Button', id: 'hidden', description: 'Hides the button. Has no action', @@ -522,7 +522,7 @@ const defaults: ButtonMapping = { } }, Action6: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'PlayPause', id: 'play', icon: 'play', @@ -533,7 +533,7 @@ const defaults: ButtonMapping = { } }, Action7: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'Skip', id: 'skip', description: 'Skips the song', @@ -543,7 +543,7 @@ const defaults: ButtonMapping = { } }, Digit1: { - [EventFlavor.PressShort]: { + [EventMode.PressShort]: { name: 'Open Preference App', id: 'pref', value: '0', @@ -553,7 +553,7 @@ const defaults: ButtonMapping = { version: '0.9.0', enabled: true }, - [EventFlavor.PressLong]: { + [EventMode.PressLong]: { name: 'Swap Apps', id: 'swap', value: '0', @@ -565,7 +565,7 @@ const defaults: ButtonMapping = { } }, Digit2: { - [EventFlavor.PressShort]: { + [EventMode.PressShort]: { name: 'Open Preference App', id: 'pref', value: '1', @@ -575,7 +575,7 @@ const defaults: ButtonMapping = { version: '0.9.0', enabled: true }, - [EventFlavor.PressLong]: { + [EventMode.PressLong]: { name: 'Swap Apps', id: 'swap', value: '1', @@ -587,7 +587,7 @@ const defaults: ButtonMapping = { } }, Digit3: { - [EventFlavor.PressShort]: { + [EventMode.PressShort]: { name: 'Open Preference App', id: 'pref', value: '2', @@ -597,7 +597,7 @@ const defaults: ButtonMapping = { version: '0.9.0', enabled: true }, - [EventFlavor.PressLong]: { + [EventMode.PressLong]: { name: 'Swap Apps', id: 'swap', value: '2', @@ -609,7 +609,7 @@ const defaults: ButtonMapping = { } }, Digit4: { - [EventFlavor.PressShort]: { + [EventMode.PressShort]: { name: 'Open Preference App', id: 'pref', value: '3', @@ -619,7 +619,7 @@ const defaults: ButtonMapping = { version: '0.9.0', enabled: true }, - [EventFlavor.PressLong]: { + [EventMode.PressLong]: { name: 'Swap Apps', id: 'swap', value: '3', @@ -631,7 +631,7 @@ const defaults: ButtonMapping = { } }, KeyM: { - [EventFlavor.PressShort]: { + [EventMode.PressShort]: { name: 'Open App', id: 'open', value: 'dashboard', @@ -641,7 +641,7 @@ const defaults: ButtonMapping = { version: '0.9.0', enabled: true }, - [EventFlavor.PressLong]: { + [EventMode.PressLong]: { name: 'Open App', id: 'open', value: 'utility', @@ -653,7 +653,7 @@ const defaults: ButtonMapping = { } }, Scroll: { - [EventFlavor.ScrollRight]: { + [EventMode.ScrollRight]: { name: 'Volume Up', id: 'volUp', value: '15', @@ -663,7 +663,7 @@ const defaults: ButtonMapping = { version: '0.9.0', enabled: true }, - [EventFlavor.ScrollUp]: { + [EventMode.ScrollUp]: { name: 'Volume Up', id: 'volUp', value: '15', @@ -673,7 +673,7 @@ const defaults: ButtonMapping = { version: '0.9.0', enabled: true }, - [EventFlavor.ScrollLeft]: { + [EventMode.ScrollLeft]: { name: 'Volume Down', id: 'volDown', value: '15', @@ -683,7 +683,7 @@ const defaults: ButtonMapping = { version: '0.9.0', enabled: true }, - [EventFlavor.ScrollDown]: { + [EventMode.ScrollDown]: { name: 'Volume Down', id: 'volDown', value: '15', @@ -695,7 +695,7 @@ const defaults: ButtonMapping = { } }, Enter: { - [EventFlavor.KeyDown]: { + [EventMode.KeyDown]: { name: 'PlayPause', id: 'play', icon: 'play', @@ -704,7 +704,7 @@ const defaults: ButtonMapping = { version: '0.9.0', enabled: true }, - [EventFlavor.PressLong]: { + [EventMode.PressLong]: { name: 'Skip', id: 'skip', description: 'Skips the song', @@ -714,7 +714,7 @@ const defaults: ButtonMapping = { } }, Escape: { - [EventFlavor.PressShort]: { + [EventMode.PressShort]: { name: 'Toggle AppsList', id: 'appsList', value: 'show', @@ -725,7 +725,7 @@ const defaults: ButtonMapping = { version: '0.9.0', enabled: true }, - [EventFlavor.PressLong]: { + [EventMode.PressLong]: { name: 'Toggle AppsList', id: 'appsList', value: 'hide', @@ -738,7 +738,7 @@ const defaults: ButtonMapping = { } }, Swipe: { - [EventFlavor.SwipeUp]: { + [EventMode.SwipeUp]: { name: 'Toggle AppsList', id: 'appsList', value: 'hide', @@ -748,7 +748,7 @@ const defaults: ButtonMapping = { version: '0.9.0', enabled: true }, - [EventFlavor.SwipeDown]: { + [EventMode.SwipeDown]: { name: 'Toggle AppsList', id: 'appsList', value: 'show', @@ -758,7 +758,7 @@ const defaults: ButtonMapping = { version: '0.9.0', enabled: true }, - [EventFlavor.SwipeLeft]: { + [EventMode.SwipeLeft]: { name: 'Open Previous', id: 'swipeL', description: 'Opens the app at the previous index', @@ -766,7 +766,7 @@ const defaults: ButtonMapping = { version: '0.9.0', enabled: true }, - [EventFlavor.SwipeRight]: { + [EventMode.SwipeRight]: { name: 'Open Next', id: 'swipeR', description: 'Opens the app at the next index', diff --git a/DeskThingServer/src/main/stores/keyMapStore.ts b/DeskThingServer/src/main/stores/keyMapStore.ts index 8caa4a3d..c6e4eb32 100644 --- a/DeskThingServer/src/main/stores/keyMapStore.ts +++ b/DeskThingServer/src/main/stores/keyMapStore.ts @@ -1,5 +1,5 @@ import { defaultData } from '../static/defaultMapping' -import { Action, ButtonMapping, EventFlavor, Key, MappingStructure } from '@shared/types' +import { Action, ButtonMapping, EventMode, Key, MappingStructure } from '@shared/types' import dataListener, { MESSAGE_TYPES } from '../utils/events' import { readFromFile, @@ -114,12 +114,12 @@ export class MappingState { */ isValidButtonMapping = (mapping: ButtonMapping): boolean => { try { - for (const [key, flavors] of Object.entries(mapping.mapping)) { + for (const [key, Modes] of Object.entries(mapping.mapping)) { if (typeof key !== 'string') return false - if (typeof flavors !== 'object') return false + if (typeof Modes !== 'object') return false - for (const [flavor, action] of Object.entries(flavors)) { - if (!Object.values(EventFlavor).includes(Number(flavor))) { + for (const [Mode, action] of Object.entries(Modes)) { + if (!Object.values(EventMode).includes(Number(Mode))) { return false } if ( @@ -174,8 +174,8 @@ export class MappingState { typeof key.source === 'string' && typeof key.version === 'string' && typeof key.enabled === 'boolean' && - Array.isArray(key.flavors) && - key.flavors.every((flavor) => Object.values(EventFlavor).includes(flavor)) + Array.isArray(key.Modes) && + key.Modes.every((Mode) => Object.values(EventMode).includes(Mode)) ) } @@ -189,13 +189,13 @@ export class MappingState { * adds a new button mapping to the mapping structure. If the key already exists, it will update the mapping. * @param DynamicAction2 - the button to add * @param key - The key to map the button to - * @param flavor - default is 'onPress' + * @param Mode - default is 'onPress' * @param profile - default is 'default' */ addButton = ( action: Action, key: string, - flavor: EventFlavor, + Mode: EventMode, profile: string = 'default' ): void => { const mappings = this.mappings @@ -219,7 +219,7 @@ export class MappingState { } // Adding the button to the mapping - mappings[profile][key][flavor] = action + mappings[profile][key][Mode] = action // Save the mappings to file this.mappings = mappings @@ -228,10 +228,10 @@ export class MappingState { /** * Removes a button mapping from the mapping structure. * @param key - The key to remove the button from - * @param flavor - The flavor of the button to remove. Default removes all flavors + * @param Mode - The Mode of the button to remove. Default removes all Modes * @param profile - default is 'default' */ - removeButton = (key: string, flavor: EventFlavor | null, profile: string = 'default'): void => { + removeButton = (key: string, Mode: EventMode | null, profile: string = 'default'): void => { const mappings = this.mappings if (!mappings[profile]) { dataListener.asyncEmit( @@ -249,7 +249,7 @@ export class MappingState { return } - if (flavor === null) { + if (Mode === null) { // Remove the entire key delete mappings[profile][key] dataListener.asyncEmit( @@ -257,15 +257,15 @@ export class MappingState { `MAPHANDLER: Key ${key} removed from profile ${profile}` ) } else { - // Ensure that the flavor exists in the mapping - if (!mappings[profile][key][flavor]) { + // Ensure that the Mode exists in the mapping + if (!mappings[profile][key][Mode]) { dataListener.asyncEmit( MESSAGE_TYPES.ERROR, - `MAPHANDLER: Flavor ${flavor} does not exist in key ${key} in profile ${profile}!` + `MAPHANDLER: Mode ${Mode} does not exist in key ${key} in profile ${profile}!` ) } else { // Removing the button from the mapping - delete mappings[profile][key][flavor] + delete mappings[profile][key][Mode] } } @@ -455,8 +455,8 @@ export class MappingState { const currentMap = mappings.selected_profile if (currentMap) { const currentMapActions = mappings.profiles[currentMap].mapping - Object.values(currentMapActions).forEach((buttonFlavors) => { - Object.values(buttonFlavors).forEach((action) => { + Object.values(currentMapActions).forEach((buttonModes) => { + Object.values(buttonModes).forEach((action) => { if (action && action.id === actionId) { action.icon = icon } diff --git a/DeskThingServer/src/renderer/src/overlays/apps/AppDetails.tsx b/DeskThingServer/src/renderer/src/overlays/apps/AppDetails.tsx index 90a472c2..7f13a61d 100644 --- a/DeskThingServer/src/renderer/src/overlays/apps/AppDetails.tsx +++ b/DeskThingServer/src/renderer/src/overlays/apps/AppDetails.tsx @@ -51,7 +51,7 @@ const AppDetails: React.FC = ({ app }: AppSettingProps) => { rel="noreferrer noopener" className="text-blue-500 hover:text-blue-400" > - {app.manifest.homepage} + {app.manifest.repository}
diff --git a/DeskThingServer/src/renderer/src/pages/Dev/DevApp.tsx b/DeskThingServer/src/renderer/src/pages/Dev/DevApp.tsx index 05ac04d2..98d679e8 100644 --- a/DeskThingServer/src/renderer/src/pages/Dev/DevApp.tsx +++ b/DeskThingServer/src/renderer/src/pages/Dev/DevApp.tsx @@ -4,7 +4,6 @@ import MainElement from '@renderer/nav/MainElement' import Loading from '@renderer/components/Loading' const DevApp: React.FC = () => { - return (
diff --git a/DeskThingServer/src/renderer/src/stores/keyStore.ts b/DeskThingServer/src/renderer/src/stores/keyStore.ts index 71d56f62..6a013928 100644 --- a/DeskThingServer/src/renderer/src/stores/keyStore.ts +++ b/DeskThingServer/src/renderer/src/stores/keyStore.ts @@ -5,7 +5,7 @@ * @version 0.9.0 */ import { create } from 'zustand' -import { Action, Key, MappingStructure, EventFlavor } from '@shared/types' +import { Action, Key, MappingStructure, EventMode } from '@shared/types' interface KeyMapStoreState { mappingStructure: MappingStructure | null @@ -35,7 +35,7 @@ interface KeyMapStoreState { setSelectedProfile: (profile: string) => void // Update a specific button mapping (e.g., for remapping actions) - updateButtonMapping: (profile: string, keyId: string, flavor: EventFlavor, action: Action) => void + updateButtonMapping: (profile: string, keyId: string, Mode: EventMode, action: Action) => void } const useKeyMapStore = create((set) => ({ @@ -112,11 +112,11 @@ const useKeyMapStore = create((set) => ({ })) }, - // Update a specific button mapping for a key and flavor in the selected profile + // Update a specific button mapping for a key and Mode in the selected profile updateButtonMapping: ( profile: string, keyId: string, - flavor: EventFlavor, + Mode: EventMode, action: Action ): void => { set((state) => { @@ -128,7 +128,7 @@ const useKeyMapStore = create((set) => ({ if (!profileMapping) return state const mapping = profileMapping.mapping[keyId] || {} - mapping[flavor] = action + mapping[Mode] = action profileMapping.mapping[keyId] = mapping updatedMappingStructure.profiles[profile] = profileMapping diff --git a/DeskThingServer/src/shared/types/maps.ts b/DeskThingServer/src/shared/types/maps.ts index 4fcbd4fe..a7d8e45b 100644 --- a/DeskThingServer/src/shared/types/maps.ts +++ b/DeskThingServer/src/shared/types/maps.ts @@ -17,10 +17,11 @@ export type Key = { description?: string // User Readable description version: string // The version of the key enabled: boolean // Whether or not the app associated with the key is enabled - flavors: EventFlavor[] // The flavors of the key + Modes: EventMode[] // The Modes of the key } -export enum EventFlavor { +// The different possible modes of an event +export enum EventMode { KeyUp, KeyDown, ScrollUp, @@ -35,6 +36,7 @@ export enum EventFlavor { PressLong } +// The button mapping profile stored in the file system export type ButtonMapping = { // The ID of the key version: string @@ -44,7 +46,7 @@ export type ButtonMapping = { trigger_app?: string mapping: { [key: string]: { - [flavor in EventFlavor]?: Action + [Mode in EventMode]?: Action } } } diff --git a/DeskThingServer/src/shared/types/types.ts b/DeskThingServer/src/shared/types/types.ts index d70369bd..64707908 100644 --- a/DeskThingServer/src/shared/types/types.ts +++ b/DeskThingServer/src/shared/types/types.ts @@ -75,6 +75,7 @@ export type GithubAsset = { browser_download_url: string } +// The Client is how the clients are stored to keep track of them export interface Client { ip: string port?: number @@ -94,6 +95,7 @@ export interface Client { miniplayer?: string } +// The standard manifest that all the clients should have export interface ClientManifest { name: string id: string @@ -105,8 +107,6 @@ export interface ClientManifest { version: string port: number ip: string - default_view: string - miniplayer: string compatible_server?: number[] uuid?: string version_code?: number @@ -119,6 +119,7 @@ export interface RepoReleases { releases: GithubRelease[] } +// The socket data that is used for any communication. I.e. between the app-server or server-client export interface SocketData { app: string type: string @@ -132,6 +133,7 @@ export interface SocketData { | Settings } +// The settings for the app export interface Settings { version: string version_code: number @@ -151,6 +153,7 @@ export interface Settings { [key: string]: any // For any additional settings } +// Used in the Refresh ADB screen to display little messages for the user export interface StatusMessage { message: string weight: number From ddb802ce3bb18282f3b93fa24765f6150797940e Mon Sep 17 00:00:00 2001 From: Dakota Kallas Date: Tue, 12 Nov 2024 16:15:21 -0600 Subject: [PATCH 03/13] Add settings attribute & validations --- .../src/overlays/apps/AppSettings.tsx | 26 +++++++++++++++---- DeskThingServer/src/shared/types/app.ts | 1 + 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx b/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx index 672a9867..4663e0a0 100644 --- a/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx +++ b/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState, useCallback, useMemo } from 'react' import { useAppStore } from '@renderer/stores' -import { AppDataInterface, SettingsType } from '@shared/types' +import { AppDataInterface, SettingsString, SettingsType } from '@shared/types' import { AppSettingProps } from './AppsOverlay' import Button from '@renderer/components/Button' import { IconCheck, IconLoading, IconSave, IconToggle, IconX } from '@renderer/assets/icons' @@ -42,6 +42,12 @@ const AppSettings: React.FC = ({ app }) => { [] ) + const clampValue = (value: number, min?: number, max?: number): number => { + if (min !== undefined && value < min) return min + if (max !== undefined && value > max) return max + return value + } + const renderSettingInput = useCallback( (setting: SettingsType, key: string) => { const commonClasses = @@ -55,6 +61,7 @@ const AppSettings: React.FC = ({ app }) => { handleSettingChange(key, e.target.value)} className={commonClasses + ' w-full'} /> @@ -71,7 +78,11 @@ const AppSettings: React.FC = ({ app }) => { value={setting.value as number} min={setting.min} max={setting.max} - onChange={(e) => handleSettingChange(key, Number(e.target.value))} + onChange={(e) => { + let inputValue = Number(e.target.value) + inputValue = clampValue(inputValue, setting.min, setting.max) + handleSettingChange(key, inputValue) + }} className={commonClasses} /> )} @@ -204,9 +215,14 @@ const SettingComponent = ({ setting, children, className }: SettingComponentProp className={`py-3 flex gap-3 items-center hover:bg-zinc-950 justify-between w-full border-t relative border-gray-900 ${className}`} >
-

- {setting.type?.toUpperCase() || 'Legacy Setting'} -

+
+

{setting.type?.toUpperCase() || 'Legacy Setting'}

+ {setting.type === 'number' && ( +

+ MIN: {setting.min} | MAX: {setting.max} +

+ )} +

{setting.label}

{setting.description && ( diff --git a/DeskThingServer/src/shared/types/app.ts b/DeskThingServer/src/shared/types/app.ts index d6e2c748..b2c682dd 100644 --- a/DeskThingServer/src/shared/types/app.ts +++ b/DeskThingServer/src/shared/types/app.ts @@ -112,6 +112,7 @@ export interface SettingsString { value: string type: 'string' label: string + maxLength?: number description?: string } From 19d358e920df2fe776aa72e9bfa95dd521cf11aa Mon Sep 17 00:00:00 2001 From: Dakota Kallas Date: Thu, 14 Nov 2024 08:33:01 -0600 Subject: [PATCH 04/13] Select control refresh --- DeskThingServer/package-lock.json | 362 +++++++++++++++--- DeskThingServer/package.json | 1 + .../src/renderer/src/components/Select.tsx | 93 +++++ .../src/overlays/apps/AppSettings.tsx | 56 ++- DeskThingServer/src/shared/types/app.ts | 17 +- 5 files changed, 441 insertions(+), 88 deletions(-) create mode 100644 DeskThingServer/src/renderer/src/components/Select.tsx diff --git a/DeskThingServer/package-lock.json b/DeskThingServer/package-lock.json index 9cb6be01..5fdb0009 100644 --- a/DeskThingServer/package-lock.json +++ b/DeskThingServer/package-lock.json @@ -1,12 +1,12 @@ { "name": "deskthing", - "version": "0.9.0", + "version": "0.9.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "deskthing", - "version": "0.9.0", + "version": "0.9.2", "hasInstallScript": true, "dependencies": { "@electron-toolkit/preload": "^3.0.0", @@ -22,6 +22,7 @@ "react-qr-code": "^2.0.15", "react-rewards": "^2.0.4", "react-router-dom": "^6.26.2", + "react-select": "^5.8.3", "uuid": "^10.0.0", "ws": "^8.17.1", "zustand": "^5.0.0-rc.2" @@ -81,7 +82,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/highlight": "^7.24.7", @@ -146,7 +146,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.24.7", @@ -189,7 +188,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.24.7" @@ -202,7 +200,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.24.7", @@ -216,7 +213,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.24.7" @@ -229,7 +225,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.24.7", @@ -287,7 +282,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.24.7" @@ -300,7 +294,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -310,7 +303,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -344,7 +336,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.24.7", @@ -360,7 +351,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", - "dev": true, "license": "MIT", "bin": { "parser": "bin/babel-parser.js" @@ -417,11 +407,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.24.7", @@ -436,7 +436,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.24.7", @@ -458,7 +457,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.24.7", @@ -833,6 +831,133 @@ "node": ">= 10.0.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", + "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.2.0", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.13.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", + "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.13.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", + "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", + "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", + "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz", + "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -1337,6 +1462,28 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", + "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", + "dependencies": { + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.12", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz", + "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", + "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -1506,7 +1653,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -1521,7 +1667,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1531,7 +1676,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1541,14 +1685,12 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -2171,6 +2313,11 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, "node_modules/@types/plist": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", @@ -2187,14 +2334,12 @@ "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -2211,6 +2356,14 @@ "@types/react": "*" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/responselike": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", @@ -2595,7 +2748,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^1.9.0" @@ -3135,6 +3287,59 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/babel-plugin-macros/node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-plugin-macros/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3600,7 +3805,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3652,7 +3856,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", @@ -3806,7 +4009,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "1.1.3" @@ -3816,7 +4018,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -4113,7 +4314,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, "license": "MIT" }, "node_modules/data-view-buffer": { @@ -4487,6 +4687,15 @@ "node": ">=6.0.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dotenv": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", @@ -5038,7 +5247,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -5275,7 +5483,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.0" @@ -5955,6 +6162,11 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -6339,7 +6551,6 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -6448,7 +6659,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -6556,6 +6766,14 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -6732,7 +6950,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -6818,7 +7035,6 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, "license": "MIT" }, "node_modules/is-async-function": { @@ -6910,7 +7126,6 @@ "version": "2.14.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -7426,7 +7641,6 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -7445,7 +7659,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { @@ -7610,7 +7823,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, "license": "MIT" }, "node_modules/linkify-it": { @@ -7857,6 +8069,11 @@ "node": ">= 0.6" } }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -8454,7 +8671,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -8467,7 +8683,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -8524,7 +8739,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, "license": "MIT" }, "node_modules/path-scurry": { @@ -8561,7 +8775,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8578,7 +8791,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -9170,6 +9382,41 @@ "react-dom": ">=16.8" } }, + "node_modules/react-select": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.3.tgz", + "integrity": "sha512-lVswnIq8/iTj1db7XCG74M/3fbGB6ZaluCzvwPGT5ZOjCdL/k0CLWhEK0vCBLuU5bHTEf6Gj8jtSvi+3v+tO1w==", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.1.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -9274,6 +9521,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/regex": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/regex/-/regex-4.3.3.tgz", @@ -9339,7 +9591,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -10124,6 +10375,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -10221,7 +10477,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^3.0.0" @@ -10234,7 +10489,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10468,7 +10722,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -10885,6 +11138,19 @@ "punycode": "^2.1.0" } }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/utf8-byte-length": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", diff --git a/DeskThingServer/package.json b/DeskThingServer/package.json index e34ea32a..47cc5684 100644 --- a/DeskThingServer/package.json +++ b/DeskThingServer/package.json @@ -35,6 +35,7 @@ "react-qr-code": "^2.0.15", "react-rewards": "^2.0.4", "react-router-dom": "^6.26.2", + "react-select": "^5.8.3", "uuid": "^10.0.0", "ws": "^8.17.1", "zustand": "^5.0.0-rc.2" diff --git a/DeskThingServer/src/renderer/src/components/Select.tsx b/DeskThingServer/src/renderer/src/components/Select.tsx new file mode 100644 index 00000000..f818b224 --- /dev/null +++ b/DeskThingServer/src/renderer/src/components/Select.tsx @@ -0,0 +1,93 @@ +import { SettingOption } from '@shared/types' +import React from 'react' +import ReactSelect, { MultiValue, SingleValue } from 'react-select' + +interface SelectProps { + options: SettingOption[] + value: string[] | string + isMulti?: boolean + placeholder: string + onChange: (value: SingleValue | MultiValue) => void +} +const Select: React.FC = ({ options, isMulti, onChange, value, placeholder }) => { + // We can't use tailwind css here because of how classes are passed into the child components + const customStyles = { + control: (provided) => ({ + ...provided, + backgroundColor: 'rgb(24, 24, 27)', // Tailwind `bg-zinc-900` + borderColor: 'rgb(63, 63, 70)', // Tailwind `border-zinc-700` + color: 'white', + padding: '0.25rem 0.5rem', // Tailwind `p-2` + boxShadow: 'none', + '&:hover': { + borderColor: 'rgb(113, 113, 122)' // Tailwind `border-zinc-600` + } + }), + menu: (provided) => ({ + ...provided, + backgroundColor: 'rgb(24, 24, 27)', // Tailwind `bg-zinc-900` + color: 'white', + borderRadius: '0.25rem', // Tailwind `rounded-md` + padding: '0.5rem' // Tailwind `p-2` + }), + menuList: (provided) => ({ + ...provided, + padding: 0 + }), + option: (provided, state) => ({ + ...provided, + backgroundColor: state.isSelected + ? 'rgb(34, 197, 94)' // Tailwind `bg-green-500` + : state.isFocused + ? 'rgb(39, 39, 42)' // Tailwind `bg-zinc-800` + : 'transparent', + color: state.isSelected ? 'white' : 'rgb(229, 231, 235)', // Tailwind `text-gray-200` + padding: '0.5rem 1rem', // Tailwind `px-4 py-2` + '&:hover': { + backgroundColor: 'rgb(39, 39, 42)', // Tailwind `bg-zinc-800` + color: 'rgb(229, 231, 235)' // Tailwind `text-gray-200` + } + }), + singleValue: (provided) => ({ + ...provided, + color: 'white' + }), + multiValue: (provided) => ({ + ...provided, + backgroundColor: 'rgb(63, 63, 70)', // Tailwind `bg-zinc-700` + color: 'white' + }), + multiValueLabel: (provided) => ({ + ...provided, + color: 'rgb(229, 231, 235)' // Tailwind `text-gray-200` + }), + multiValueRemove: (provided) => ({ + ...provided, + color: 'rgb(229, 231, 235)', // Tailwind `text-gray-200` + '&:hover': { + backgroundColor: 'rgb(185, 28, 28)', // Tailwind `bg-red-700` + color: 'white' + } + }) + } + + return ( + <> + value.includes(option.value)) + : options.find((option) => option.value === value) + } + options={options} + isMulti={isMulti} + closeMenuOnSelect={!isMulti} + styles={customStyles} + placeholder={placeholder} + onChange={onChange} + /> + + ) +} + +export default Select diff --git a/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx b/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx index 4663e0a0..b4fc2570 100644 --- a/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx +++ b/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx @@ -1,9 +1,11 @@ import React, { useEffect, useState, useCallback, useMemo } from 'react' import { useAppStore } from '@renderer/stores' -import { AppDataInterface, SettingsString, SettingsType } from '@shared/types' +import { AppDataInterface, SettingOption, SettingsString, SettingsType } from '@shared/types' import { AppSettingProps } from './AppsOverlay' import Button from '@renderer/components/Button' -import { IconCheck, IconLoading, IconSave, IconToggle, IconX } from '@renderer/assets/icons' +import { IconLoading, IconSave, IconToggle } from '@renderer/assets/icons' +import Select from '@renderer/components/Select' +import { MultiValue, SingleValue } from 'react-select' const AppSettings: React.FC = ({ app }) => { const getAppData = useAppStore((state) => state.getAppData) @@ -105,17 +107,15 @@ const AppSettings: React.FC = ({ app }) => { return ( {setting.type == 'select' && ( - + onChange={(selected) => { + const selectedValue = selected as SingleValue + handleSettingChange(key, selectedValue!.value) + }} + /> )} ) @@ -123,26 +123,18 @@ const AppSettings: React.FC = ({ app }) => { return ( {setting.type == 'multiselect' && ( -
- {setting.options?.map((option, index) => ( - - ))} +
+ { - const selectedValue = selected as SingleValue - handleSettingChange(key, selectedValue!.value) - }} - /> +
+ = ({ app }) => { return ( {setting.type == 'multiselect' && ( -
+
handleSettingChange(key, e.target.value)} + className="w-96 max-w-s" + /> + )} + + ) case 'select': return ( @@ -181,7 +197,7 @@ const AppSettings: React.FC = ({ app }) => { } return ( -
+
{settingsEntries.map(([key, setting]) => renderSettingInput(setting, key))}
- {children} +
+ {setting.type === 'range' &&
{setting.value}
} + {children} +
) } diff --git a/DeskThingServer/src/shared/types/app.ts b/DeskThingServer/src/shared/types/app.ts index 3f870d48..d3a019e6 100644 --- a/DeskThingServer/src/shared/types/app.ts +++ b/DeskThingServer/src/shared/types/app.ts @@ -108,6 +108,16 @@ export interface SettingsBoolean { description?: string } +export interface SettingsRange { + value: number + type: 'range' + label: string + min: number + max: number + step?: number + description?: string +} + export interface SettingsString { value: string type: 'string' @@ -145,6 +155,7 @@ export type SettingsType = | SettingsString | SettingsSelect | SettingsMultiSelect + | SettingsRange export interface AppSettings { [key: string]: SettingsType From 52ec6483486295004872bf3f442f5bae12c472d9 Mon Sep 17 00:00:00 2001 From: Dakota Kallas Date: Thu, 14 Nov 2024 10:52:59 -0600 Subject: [PATCH 09/13] Add ranked setting --- .../renderer/src/components/RankableList.tsx | 74 +++++++++++++++++++ .../src/overlays/apps/AppSettings.tsx | 17 +++++ DeskThingServer/src/shared/types/app.ts | 9 +++ 3 files changed, 100 insertions(+) create mode 100644 DeskThingServer/src/renderer/src/components/RankableList.tsx diff --git a/DeskThingServer/src/renderer/src/components/RankableList.tsx b/DeskThingServer/src/renderer/src/components/RankableList.tsx new file mode 100644 index 00000000..944b2155 --- /dev/null +++ b/DeskThingServer/src/renderer/src/components/RankableList.tsx @@ -0,0 +1,74 @@ +import React, { useEffect, useState } from 'react' +import { SettingOption } from '@shared/types' +import { IconArrowDown, IconArrowUp } from '@renderer/assets/icons' + +type RankableListProps = { + options: SettingOption[] + value: string[] + onChange: (orderedOptions: string[]) => void +} + +const RankableList: React.FC = ({ options, onChange, value }) => { + const [orderedOptions, setOrderedOptions] = useState(options) + + useEffect(() => { + // Sort options based on the value order + const sortedOptions = [...options].sort((a, b) => { + const aIndex = value.indexOf(a.value) + const bIndex = value.indexOf(b.value) + return aIndex - bIndex + }) + setOrderedOptions(sortedOptions) + }, [options, value]) // Re-run when options or value change + + const handleMoveUp = (index: number): void => { + if (index > 0) { + const newOptions = [...orderedOptions] + ;[newOptions[index], newOptions[index - 1]] = [newOptions[index - 1], newOptions[index]] + setOrderedOptions(newOptions) + onChange(newOptions.map((option) => option.value)) + } + } + + const handleMoveDown = (index: number): void => { + if (index < orderedOptions.length - 1) { + const newOptions = [...orderedOptions] + ;[newOptions[index], newOptions[index + 1]] = [newOptions[index + 1], newOptions[index]] + setOrderedOptions(newOptions) + onChange(newOptions.map((option) => option.value)) + } + } + + return ( +
+ {orderedOptions.map((option, index) => ( +
+
+

{option.label}

+
+
+ + +
+
+ ))} +
+ ) +} + +export default RankableList diff --git a/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx b/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx index ed9e7bcf..d393ce8d 100644 --- a/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx +++ b/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx @@ -6,6 +6,7 @@ import Button from '@renderer/components/Button' import { IconLoading, IconSave, IconToggle } from '@renderer/assets/icons' import Select from '@renderer/components/Select' import { MultiValue, SingleValue } from 'react-select' +import RankableList from '@renderer/components/RankableList' const AppSettings: React.FC = ({ app }) => { const getAppData = useAppStore((state) => state.getAppData) @@ -157,6 +158,22 @@ const AppSettings: React.FC = ({ app }) => { )} ) + case 'ranked': + return ( + + {setting.type == 'ranked' && ( +
+ { + handleSettingChange(key, rankedValues) + }} + /> +
+ )} +
+ ) default: return ( diff --git a/DeskThingServer/src/shared/types/app.ts b/DeskThingServer/src/shared/types/app.ts index d3a019e6..571d1dca 100644 --- a/DeskThingServer/src/shared/types/app.ts +++ b/DeskThingServer/src/shared/types/app.ts @@ -140,6 +140,14 @@ export type SettingOption = { value: string } +export interface SettingsRanked { + value: string[] + type: 'ranked' + label: string + description?: string + options: SettingOption[] +} + export interface SettingsMultiSelect { value: string[] type: 'multiselect' @@ -156,6 +164,7 @@ export type SettingsType = | SettingsSelect | SettingsMultiSelect | SettingsRange + | SettingsRanked export interface AppSettings { [key: string]: SettingsType From 52d82faa4647abad92938088064d4fc523f965b7 Mon Sep 17 00:00:00 2001 From: ItsRiprod Date: Thu, 14 Nov 2024 19:16:04 -0700 Subject: [PATCH 10/13] AutoConfig Updates Automatically downloads a client if there isnt one and you hit Config Better error handling when doing autoconfig A few random bugfixes with logging More reliable playback location setting (circumvents the startup error) Automatically setting an app as the audiosource when added if there isnt one set already --- .../src/main/handlers/adbHandler.ts | 21 +- .../src/main/handlers/appHandler.ts | 2 +- .../src/main/handlers/clientHandler.ts | 49 ++- .../src/main/handlers/deviceHandler.ts | 325 +++++++++++------- .../src/main/handlers/githubHandler.ts | 2 + .../src/main/handlers/musicHandler.ts | 20 +- .../src/main/services/apps/appState.ts | 18 + .../src/main/services/client/expressServer.ts | 45 ++- .../renderer/src/components/Connection.tsx | 3 - .../src/overlays/settings/ClientSettings.tsx | 31 +- .../src/overlays/settings/MusicSettings.tsx | 2 +- 11 files changed, 305 insertions(+), 213 deletions(-) diff --git a/DeskThingServer/src/main/handlers/adbHandler.ts b/DeskThingServer/src/main/handlers/adbHandler.ts index 3fecc4a9..e46f53c9 100644 --- a/DeskThingServer/src/main/handlers/adbHandler.ts +++ b/DeskThingServer/src/main/handlers/adbHandler.ts @@ -3,7 +3,7 @@ import { execFile } from 'child_process' import getPlatform from '../utils/get-platform' import dataListener, { MESSAGE_TYPES } from '../utils/events' import settingsStore from '../stores/settingsStore' -import { LoggingData } from '@shared/types' +import { ReplyFn } from '@shared/types' const isDevelopment = process.env.NODE_ENV === 'development' const execPath = isDevelopment @@ -25,10 +25,7 @@ const splitArgs = (str: string): string[] => { return matches } -export const handleAdbCommands = async ( - command: string, - send?: (channel: string, ...args: LoggingData[]) => void -): Promise => { +export const handleAdbCommands = async (command: string, replyFn?: ReplyFn): Promise => { const settings = await settingsStore.getSettings() const useGlobalADB = settings.globalADB === true dataListener.asyncEmit( @@ -43,27 +40,25 @@ export const handleAdbCommands = async ( (error, stdout, stderr) => { console.log(error, stdout, stderr) if (error) { - if (send) { - send('logging', { + replyFn && + replyFn('logging', { status: false, data: 'Error Encountered!', - final: true, + final: false, error: stderr }) - } dataListener.asyncEmit( MESSAGE_TYPES.ERROR, `ADB Error: ${stderr}, ${command}, ${adbPath}` ) reject(`ADB Error: ${stderr}, ${command}, ${adbPath}`) } else { - if (send) { - send('logging', { + replyFn && + replyFn('logging', { status: true, data: 'ADB Success!', - final: true + final: false }) - } resolve(stdout) } } diff --git a/DeskThingServer/src/main/handlers/appHandler.ts b/DeskThingServer/src/main/handlers/appHandler.ts index 6df63eca..6ca5febf 100644 --- a/DeskThingServer/src/main/handlers/appHandler.ts +++ b/DeskThingServer/src/main/handlers/appHandler.ts @@ -137,7 +137,7 @@ const getApps = (replyFn: ReplyFn): App[] => { return data } -const setAppData = async (replyFn, id, data: AppDataInterface): Promise => { +const setAppData = async (replyFn: ReplyFn, id, data: AppDataInterface): Promise => { console.log('Saving app data: ', data) dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, 'SERVER: Saving ' + id + "'s data " + data) await setData(id, data) diff --git a/DeskThingServer/src/main/handlers/clientHandler.ts b/DeskThingServer/src/main/handlers/clientHandler.ts index 6e86da2d..88548ee6 100644 --- a/DeskThingServer/src/main/handlers/clientHandler.ts +++ b/DeskThingServer/src/main/handlers/clientHandler.ts @@ -48,24 +48,21 @@ export const clientHandler: Record< console.log('Running ADB command:', data.payload) replyFn('logging', { status: true, data: 'Working', final: false }) - const reply = async (status, data, final, error): Promise => { - replyFn('logging', { - status: status, - data: data, - final: final, - error: error - }) - } - - return await handleAdbCommands(data.payload, reply) + const response = await handleAdbCommands(data.payload, replyFn) + replyFn('logging', { status: true, data: response, final: true }) + return response }, configure: async (data, replyFn) => { replyFn('logging', { status: true, data: 'Configuring Device', final: false }) - return await configureDevice(data.payload, replyFn) + const response = await configureDevice(data.payload, replyFn) + replyFn('logging', { status: true, data: 'Device Configured!', final: true }) + return response }, 'client-manifest': async (data, replyFn) => { if (data.request === 'get') { - return await getClientManifest(false, replyFn) + const response = await getClientManifest(replyFn) + replyFn('logging', { status: true, data: response, final: true }) + return response } else if (data.request === 'set') { return await handleClientManifestUpdate(data.payload, replyFn) } @@ -90,6 +87,11 @@ export const clientHandler: Record< try { console.log('Pushing proxy script...') SetupProxy(replyFn, data.payload) + replyFn('logging', { + status: true, + data: 'Proxy script pushed!', + final: true + }) } catch (error) { replyFn('logging', { status: false, @@ -116,7 +118,7 @@ export const clientHandler: Record< } } -const handleUrl = (data, replyFn: ReplyFn): void => { +const handleUrl = async (data, replyFn: ReplyFn): Promise => { try { replyFn('logging', { status: true, @@ -124,15 +126,24 @@ const handleUrl = (data, replyFn: ReplyFn): void => { final: false }) - const reply = async (channel: string, data): Promise => { - replyFn(channel, data) - } - - HandleWebappZipFromUrl(reply, data.payload) + await HandleWebappZipFromUrl(replyFn, data.payload) + replyFn('logging', { status: true, data: 'Successfully downloaded client', final: true }) } catch (error) { console.error('Error extracting zip file:', error) if (error instanceof Error) { - replyFn('zip-extracted', { status: false, error: error.message, data: null, final: true }) + replyFn('logging', { + status: false, + error: error.message, + data: 'Error handling URL', + final: true + }) + } else { + replyFn('logging', { + status: false, + error: 'Unable to download CLIENT', + data: 'Error handling URL', + final: true + }) } } } diff --git a/DeskThingServer/src/main/handlers/deviceHandler.ts b/DeskThingServer/src/main/handlers/deviceHandler.ts index b9401376..8155f5c6 100644 --- a/DeskThingServer/src/main/handlers/deviceHandler.ts +++ b/DeskThingServer/src/main/handlers/deviceHandler.ts @@ -4,7 +4,7 @@ import { join } from 'path' import * as fs from 'fs' import { app, net } from 'electron' import { handleAdbCommands } from './adbHandler' -import { Client, ClientManifest, ReplyData } from '@shared/types' +import { Client, ClientManifest, ReplyData, ReplyFn } from '@shared/types' import settingsStore from '../stores/settingsStore' import { getLatestRelease } from './githubHandler' @@ -46,126 +46,195 @@ export const getDeviceManifestVersion = async (deviceId: string): Promise void -): Promise => { +export const configureDevice = async (deviceId: string, reply?: ReplyFn): Promise => { const settings = await settingsStore.getSettings() - if (settings && settings.devicePort) { - console.error('Settings not found') - reply && reply('logging', { status: true, data: 'Opening Port...', final: false }) - try { - const response = await handleAdbCommands( - `-s ${deviceId} reverse tcp:${settings.devicePort} tcp:${settings.devicePort}` - ) - reply && reply('logging', { status: true, data: response || 'Port Opened', final: false }) - } catch (error) { - reply && reply('logging', { status: false, data: 'Unable to open port!', final: false }) + // Opens the socket port + try { + if (settings && settings.devicePort) { + console.error('Settings not found') + reply && reply('logging', { status: true, data: 'Opening Port...', final: false }) + try { + const response = await handleAdbCommands( + `-s ${deviceId} reverse tcp:${settings.devicePort} tcp:${settings.devicePort}`, + reply + ) + reply && reply('logging', { status: true, data: response || 'Port Opened', final: false }) + } catch (error) { + reply && reply('logging', { status: false, data: 'Unable to open port!', final: false }) + } + } else { + reply && + reply('logging', { + status: false, + data: 'Unable to open port!', + error: 'Device Port not found in settings', + final: false + }) + } + } catch (error) { + console.error('Error opening device port:', error) + if (error instanceof Error) { + reply && + reply('logging', { + status: false, + data: 'Unable to open port!', + error: error.message, + final: false + }) } - } else { - reply && - reply('logging', { - status: false, - data: 'Unable to open port!', - error: 'Device Port not found in settings', - final: false - }) } - const clientExists = await checkForClient(reply) - console.log('Device Status:', clientExists) - - if (!clientExists) { - // Download it from github - const repos = settings.clientRepos - - console.log('Fetching Latest Client...') - reply && reply('logging', { status: true, data: 'Fetching Latest Client...', final: false }) - const latestReleases = await Promise.all( - repos.map(async (repo) => { - return await getLatestRelease(repo) - }) - ) + // Check if the client is already installed. Install it if it is missing + try { + const clientExists = await checkForClient(reply) + console.log('Client Exists?:', clientExists) - // Sort releases by date and get the latest one - const clientAsset = latestReleases - .flatMap((release) => - release.assets.map((asset) => ({ ...asset, created_at: release.created_at })) + if (!clientExists) { + // Download it from github + const repos = settings.clientRepos + + console.log('Fetching Latest Client...') + reply && reply('logging', { status: true, data: 'Fetching Latest Client...', final: false }) + const latestReleases = await Promise.all( + repos.map(async (repo) => { + return await getLatestRelease(repo) + }) ) - .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) - .find((asset) => asset.name.includes('-client')) - // Download it - if (clientAsset) { - reply && reply('logging', { status: true, data: 'Downloading Client...', final: false }) - await HandleWebappZipFromUrl(reply, clientAsset.browser_download_url) - - await setTimeout(async () => { - await checkForClient() - }, 1000) - - await setTimeout(async () => { + // Sort releases by date and get the latest one + const clientAsset = latestReleases + .flatMap((release) => + release.assets.map((asset) => ({ ...asset, created_at: release.created_at })) + ) + .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) + .find((asset) => asset.name.includes('-client')) + + // Download it + if (clientAsset) { + reply && reply('logging', { status: true, data: 'Downloading Client...', final: false }) + await HandleWebappZipFromUrl(reply, clientAsset.browser_download_url) + + await new Promise((resolve) => { + setTimeout(async () => { + await checkForClient(reply) + resolve(null) + }, 5000) + }) + } else { reply && reply('logging', { - status: true, - data: 'Client not found! Downloading Client...', + status: false, + data: 'No client asset found in latest releases', final: false }) - await checkForClient() - }, 5000) + } + } + } catch (error) { + if (error instanceof Error) { + reply && + reply('logging', { + status: false, + data: 'Unable to check for client!', + error: error.message, + final: false + }) } else { reply && reply('logging', { status: false, - data: 'No client asset found in latest releases', + data: 'Unable to check for client!', + error: 'Unknown error', final: false }) } + console.error('Error checking for client:', error) } - const deviceManifestVersion = await getDeviceManifestVersion(deviceId) - const clientManifest = await getClientManifest(true, reply) - if (clientManifest && deviceManifestVersion !== clientManifest.version) { - try { - // Give a 30 second timeout to flash the webapp - await Promise.race([ - HandlePushWebApp(deviceId, reply), - new Promise((_, reject) => - setTimeout(() => reject('Timeout: Operation took longer than 30 seconds'), 30000) - ) - ]) - } catch (error) { + // Push the webapp to the device + try { + reply && + reply('logging', { status: true, data: 'Fetching Device Manifest Version...', final: false }) + const deviceManifestVersion = await getDeviceManifestVersion(deviceId) + + reply && + reply('logging', { status: true, data: 'Fetching Client Manifest Version...', final: false }) + const clientManifest = await getClientManifest(reply) + if ( + clientManifest && + deviceManifestVersion && + deviceManifestVersion !== clientManifest.version + ) { + try { + reply && reply('logging', { status: true, data: 'Pushing client...', final: false }) + // Give a 30 second timeout to flash the webapp + await Promise.race([ + HandlePushWebApp(deviceId, reply), + new Promise((_, reject) => + setTimeout(() => reject('Timeout: Operation took longer than 30 seconds'), 30000) + ) + ]) + } catch (error) { + reply && + reply('logging', { + status: false, + data: 'Unable to push webapp!', + error: typeof error == 'string' ? error : 'Unknown Error', + final: false + }) + } + } else { + reply && + reply('logging', { + status: true, + data: 'Device has the same webapp version or doesnt exist!', + final: false + }) + } + } catch (error) { + if (error instanceof Error) { reply && reply('logging', { status: false, - data: 'Unable to push webapp!', - error: typeof error == 'string' ? error : 'Unknown Error', + error: error.message, + data: 'Error pushing webapp!', + final: false + }) + } else { + reply && + reply('logging', { + status: false, + error: 'Unknown Error', + data: 'Error pushing webapp!', final: false }) } - } else { - reply && - reply('logging', { - status: true, - data: 'Device has the same webapp version!', - final: false - }) + console.error('Error pushing webapp', error) } - reply && reply('logging', { status: true, data: 'Restarting Chromium', final: false }) try { - await handleAdbCommands(`-s ${deviceId} shell supervisorctl restart chromium`) + reply && reply('logging', { status: true, data: 'Restarting Chromium', final: false }) + await handleAdbCommands(`-s ${deviceId} shell supervisorctl restart chromium`, reply) } catch (error) { - reply && - reply('logging', { - status: false, - data: 'Unable to restart chromium!', - error: typeof error == 'string' ? error : 'Unknown Error', - final: false - }) + if (error instanceof Error) { + reply && + reply('logging', { + status: false, + data: 'Unable to restart chromium!', + error: error.message, + final: false + }) + } else { + reply && + reply('logging', { + status: false, + data: 'Unable to restart chromium!', + error: 'Unknown Error', + final: false + }) + } + console.error('Error restarting chromium', error) } - reply && reply('logging', { status: true, data: 'Device Configured!', final: true }) } export const HandlePushWebApp = async ( @@ -196,11 +265,12 @@ export const HandlePushWebApp = async ( console.log('Remounting...') reply && reply('logging', { status: true, data: 'Remounting...', final: false }) response = await handleAdbCommands(`-s ${deviceId} shell mount -o remount,rw /`) - reply && reply('logging', { status: true, data: response || 'Moving...', final: false }) + reply && reply('logging', { status: true, data: response || 'Writing to tmp...', final: false }) response = await handleAdbCommands( `-s ${deviceId} shell mv /usr/share/qt-superbird-app/webapp /tmp/webapp-orig` ) - reply && reply('logging', { status: true, data: response || 'Moving...', final: false }) + reply && + reply('logging', { status: true, data: response || 'Moving from tmp...', final: false }) response = await handleAdbCommands( `-s ${deviceId} shell mv /tmp/webapp-orig /usr/share/qt-superbird-app/` ) @@ -222,7 +292,7 @@ export const HandlePushWebApp = async ( reply('logging', { status: false, data: 'There has been an error updating the client manifests ID.', - final: true, + final: false, error: `${error}` }) } @@ -250,20 +320,30 @@ export const HandlePushWebApp = async ( reply('logging', { status: false, data: 'There has been an error cleaning the client manifests ID.', - final: true, + final: false, error: `${error}` }) } - reply && reply('logging', { status: true, data: await response, final: true }) + reply && reply('logging', { status: true, data: await response, final: false }) } catch (Exception) { - reply && - reply('logging', { - status: false, - data: 'There has been an error', - final: true, - error: `${Exception}` - }) + if (Exception instanceof Error) { + reply && + reply('logging', { + status: false, + data: 'Error while trying to push webapp!', + final: false, + error: Exception.message + }) + } else { + reply && + reply('logging', { + status: false, + data: 'Error while trying to push webapp!', + final: false, + error: `${Exception}` + }) + } dataListener.asyncEmit( MESSAGE_TYPES.ERROR, 'HandlePushWebApp encountered the error ' + Exception @@ -272,7 +352,7 @@ export const HandlePushWebApp = async ( } export const HandleWebappZipFromUrl = async ( - reply: ((channel: string, data: ReplyData) => void) | undefined, + reply: ReplyFn | undefined, zipFileUrl: string ): Promise => { const userDataPath = app.getPath('userData') @@ -319,7 +399,7 @@ export const HandleWebappZipFromUrl = async ( ) // Notify success to the frontend - reply && reply('logging', { status: true, data: 'Success!', final: true }) + reply && reply('logging', { status: true, data: 'Success!', final: false }) } catch (error) { console.error('Error extracting zip file:', error) dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `Error extracting zip file: ${error}`) @@ -329,7 +409,7 @@ export const HandleWebappZipFromUrl = async ( reply('logging', { status: false, data: 'Failed to extract!', - final: true, + final: false, error: error instanceof Error ? error.message : String(error) }) } @@ -343,7 +423,7 @@ export const HandleWebappZipFromUrl = async ( reply('logging', { status: false, data: 'ERR Downloading file!', - final: true, + final: false, error: error.message }) }) @@ -357,7 +437,7 @@ export const HandleWebappZipFromUrl = async ( reply('logging', { status: false, data: 'Failed to download zip file!', - final: true, + final: false, error: errorMessage }) } @@ -372,7 +452,7 @@ export const HandleWebappZipFromUrl = async ( reply('logging', { status: false, data: 'Failed to download zip file!', - final: true, + final: false, error: error.message }) }) @@ -394,7 +474,7 @@ export const handleClientManifestUpdate = async ( await fs.promises.mkdir(extractDir, { recursive: true }) // Read the existing manifest - const existingManifest = await getClientManifest(true, reply) + const existingManifest = await getClientManifest(reply) // Merge the existing manifest with the partial updates const updatedManifest: Partial = { @@ -408,7 +488,7 @@ export const handleClientManifestUpdate = async ( // Write the updated manifest to the file await fs.promises.writeFile(manifestPath, manifestContent, 'utf8') console.log('Manifest file updated successfully') - reply && reply('logging', { status: true, data: 'Manifest Updated!', final: true }) + reply && reply('logging', { status: true, data: 'Manifest Updated!', final: false }) dataListener.asyncEmit( MESSAGE_TYPES.LOGGING, 'DEVICE HANDLER: Manifest file updated successfully' @@ -445,7 +525,6 @@ export const checkForClient = async ( } export const getClientManifest = async ( - utility: boolean = false, reply?: (channel: string, data: ReplyData) => void ): Promise => { console.log('Getting manifest...') @@ -457,8 +536,9 @@ export const getClientManifest = async ( reply && reply('logging', { status: false, + error: 'Unable to find the client manifest!', data: 'Manifest file not found', - final: true + final: false }) dataListener.asyncEmit( MESSAGE_TYPES.ERROR, @@ -485,7 +565,7 @@ export const getClientManifest = async ( reply('logging', { status: true, data: 'Manifest loaded!', - final: !utility + final: false }) dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, 'DEVICE HANDLER: Manifest file read successfully') console.log(manifest) @@ -500,7 +580,7 @@ export const getClientManifest = async ( reply('logging', { status: false, data: 'Unable to read Server Manifest file', - final: true, + final: false, error: 'Unable to read manifest file' + error }) return null @@ -564,7 +644,7 @@ export const SetupProxy = async ( status: false, data: 'Error ensuring supervisor include: ' + error, error: 'Error ensuring supervisor include: ' + error, - final: true + final: false }) } @@ -603,14 +683,14 @@ user=root` reply('logging', { status: true, data: response || 'Supervisor updated configuration.', - final: true + final: false }) // Start the Supervisor program response = await handleAdbCommands(`-s ${deviceId} shell supervisorctl start setupProxy`) reply('logging', { status: true, data: response || 'Started setup-proxy program.', - final: true + final: false }) fs.unlinkSync(tempProxyConfPath) @@ -618,10 +698,11 @@ user=root` reply('logging', { status: false, data: 'There has been an error setting up the proxy.', - final: true, + final: false, error: `${Exception}` }) dataListener.asyncEmit(MESSAGE_TYPES.ERROR, 'SetupProxy encountered the error ' + Exception) + throw new Error('SetupProxy encountered the error ' + Exception) } } @@ -725,13 +806,13 @@ export const AppendToSupervisor = async ( reply('logging', { status: true, data: 'Supervisor configuration updated and applied.', - final: true + final: false }) } catch (Exception) { reply('logging', { status: false, data: `Error appending to Supervisor: ${Exception}`, - final: true, + final: false, error: `${Exception}` }) dataListener.asyncEmit( @@ -813,20 +894,20 @@ files = /etc/supervisor.d/*.conf\n` reply('logging', { status: true, data: response || 'Supervisor updated configuration.', - final: true + final: false }) } else { reply('logging', { status: true, data: '[include] section already present. No need to update.', - final: true + final: false }) } } catch (Exception) { reply('logging', { status: false, data: `Error ensuring Supervisor [include] section: ${Exception}`, - final: true, + final: false, error: `${Exception}` }) dataListener.asyncEmit( diff --git a/DeskThingServer/src/main/handlers/githubHandler.ts b/DeskThingServer/src/main/handlers/githubHandler.ts index 36792a0d..150f2f3f 100644 --- a/DeskThingServer/src/main/handlers/githubHandler.ts +++ b/DeskThingServer/src/main/handlers/githubHandler.ts @@ -11,10 +11,12 @@ export async function getLatestRelease(repoUrl: string): Promise const owner = repoMatch[1] const repo = repoMatch[2] + console.log('Repo:', owner, repo) const apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases/latest` const response = await fetch(apiUrl) if (!response.ok) { + console.log('HTTP error fetching the latest release! Response: ', response) throw new Error(`HTTP error! status: ${response.status}`) } diff --git a/DeskThingServer/src/main/handlers/musicHandler.ts b/DeskThingServer/src/main/handlers/musicHandler.ts index 337a64f3..b03b8d02 100644 --- a/DeskThingServer/src/main/handlers/musicHandler.ts +++ b/DeskThingServer/src/main/handlers/musicHandler.ts @@ -64,11 +64,21 @@ export class MusicHandler { private async refreshMusicData(): Promise { if (!this.currentApp || this.currentApp.length == 0) { - dataListener.asyncEmit( - MESSAGE_TYPES.ERROR, - `[MusicHandler]: No playback location set! Go to settings -> Music to set the playback location!` - ) - return + // Attempt to get music data + const currentApp = (await settingsStore.getSettings()).playbackLocation + if (!currentApp || currentApp.length == 0) { + dataListener.asyncEmit( + MESSAGE_TYPES.ERROR, + `[MusicHandler]: No playback location set! Go to settings -> Music to set the playback location!` + ) + return + } else { + dataListener.asyncEmit( + MESSAGE_TYPES.LOGGING, + `[MusicHandler]: Playback location was not set! Setting to ${currentApp}` + ) + this.currentApp = currentApp + } } const app = await getAppByName(this.currentApp) diff --git a/DeskThingServer/src/main/services/apps/appState.ts b/DeskThingServer/src/main/services/apps/appState.ts index e494ded1..21dbdff5 100644 --- a/DeskThingServer/src/main/services/apps/appState.ts +++ b/DeskThingServer/src/main/services/apps/appState.ts @@ -1,5 +1,7 @@ import { App, AppInstance, Manifest, AppReturnData } from '@shared/types' import { sendConfigData, sendSettingsData } from '../client/clientCom' +import settingsStore from 'src/main/stores/settingsStore' +import dataListener from 'src/main/utils/events' /** * TODO: Sync with the file @@ -34,7 +36,9 @@ export class AppHandler { async loadApps(): Promise { console.log('[appState] [loadApps]: Loading apps...') const { getAppData } = await import('../../handlers/configHandler') + const data = await getAppData() + data.apps.forEach((app) => { if (this.apps[app.name]) { // Update existing app instance with stored data @@ -298,6 +302,20 @@ export class AppHandler { // Add the manifest to the config file addAppManifest(manifest, appName) this.saveAppToFile(appName) + + // Check if there is an audiosource set + if (manifest.isAudioSource) { + const settings = await settingsStore.getSettings() + const audiosource = settings.audioSource + if (audiosource === 'none') { + settings.audioSource = appName + dataListener.asyncEmit( + 'log', + '[apps] [appendManifest] [audiosource]: Setting audiosource to ' + appName + ) + settingsStore.saveSettings(settings) + } + } } } diff --git a/DeskThingServer/src/main/services/client/expressServer.ts b/DeskThingServer/src/main/services/client/expressServer.ts index c37973d6..9cd4cf40 100644 --- a/DeskThingServer/src/main/services/client/expressServer.ts +++ b/DeskThingServer/src/main/services/client/expressServer.ts @@ -29,36 +29,43 @@ export const setupExpressServer = async (expressApp: express.Application): Promi const clientIp = req.hostname console.log(`WEBSOCKET: Serving ${appName} from ${webAppDir} to ${clientIp}`) - - if (req.path.endsWith('manifest.js')) { - const manifestPath = join(webAppDir, 'manifest.js') - if (fs.existsSync(manifestPath)) { - let manifestContent = fs.readFileSync(manifestPath, 'utf8') - - manifestContent = manifestContent.replace( - /"ip":\s*".*?"/, - `"ip": "${clientIp === '127.0.0.1' ? 'localhost' : clientIp}"` - ) - - res.type('application/javascript').send(manifestContent) + try { + if (req.path.endsWith('manifest.js')) { + const manifestPath = join(webAppDir, 'manifest.js') + if (fs.existsSync(manifestPath)) { + let manifestContent = fs.readFileSync(manifestPath, 'utf8') + + manifestContent = manifestContent.replace( + /"ip":\s*".*?"/, + `"ip": "${clientIp === '127.0.0.1' ? 'localhost' : clientIp}"` + ) + + res.type('application/javascript').send(manifestContent) + } else { + res.status(404).send('Manifest not found') + } } else { - res.status(404).send('Manifest not found') + if (fs.existsSync(webAppDir)) { + express.static(webAppDir)(req, res, next) + } else { + res.status(404).send('App not found') + } } - } else { - if (fs.existsSync(webAppDir)) { - express.static(webAppDir)(req, res, next) + } catch (error) { + if (error instanceof Error) { + console.error('WEBSOCKET: Error serving app:', error.message) } else { - res.status(404).send('App not found') + console.error('WEBSOCKET: Error serving app:', error) } } } - expressApp.use('/', (req, res, next) => { + expressApp.use(['/', '/client/'], (req, res, next) => { handleClientConnection('client', req, res, next) }) // Serve web apps dynamically based on the URL - expressApp.use('/app/:appName', (req, res, next) => { + expressApp.use(['/app/:appName', '/:appName'], (req, res, next) => { const appName = req.params.appName if (appName === 'client' || appName == null) { handleClientConnection(appName, req, res, next) diff --git a/DeskThingServer/src/renderer/src/components/Connection.tsx b/DeskThingServer/src/renderer/src/components/Connection.tsx index 7ce26ad6..0ea1b85d 100644 --- a/DeskThingServer/src/renderer/src/components/Connection.tsx +++ b/DeskThingServer/src/renderer/src/components/Connection.tsx @@ -107,9 +107,6 @@ const ConnectionComponent: React.FC = ({ client }) => if (reply.final) { unsubscribe() } - if (!reply.status) { - unsubscribe() - } }) } catch (error) { setLoading(false) diff --git a/DeskThingServer/src/renderer/src/overlays/settings/ClientSettings.tsx b/DeskThingServer/src/renderer/src/overlays/settings/ClientSettings.tsx index f0068414..c4b5da86 100644 --- a/DeskThingServer/src/renderer/src/overlays/settings/ClientSettings.tsx +++ b/DeskThingServer/src/renderer/src/overlays/settings/ClientSettings.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react' -import { useClientStore, useAppStore } from '@renderer/stores' +import { useClientStore } from '@renderer/stores' import { ClientManifest } from '@shared/types' import Button from '@renderer/components/Button' import { IconToggle, IconSave, IconLoading } from '@renderer/assets/icons' @@ -7,7 +7,6 @@ import { IconToggle, IconSave, IconLoading } from '@renderer/assets/icons' const ClientSettings: React.FC = () => { const clientSettings = useClientStore((state) => state.clientManifest) const updateClientSettings = useClientStore((state) => state.updateClientManifest) - const apps = useAppStore((state) => state.appsList) const [localSettings, setLocalSettings] = useState(clientSettings) const [loading, setLoading] = useState(false) @@ -34,34 +33,6 @@ const ClientSettings: React.FC = () => { return (
-
-

Default View

- -
-
-

Miniplayer Mode

- -

IP Address

diff --git a/DeskThingServer/src/renderer/src/overlays/settings/MusicSettings.tsx b/DeskThingServer/src/renderer/src/overlays/settings/MusicSettings.tsx index 9b70bf3d..775e8a50 100644 --- a/DeskThingServer/src/renderer/src/overlays/settings/MusicSettings.tsx +++ b/DeskThingServer/src/renderer/src/overlays/settings/MusicSettings.tsx @@ -43,7 +43,7 @@ const MusicSettings: React.FC = () => { {!client.connected && ( diff --git a/DeskThingServer/src/renderer/src/components/NotificationButton.tsx b/DeskThingServer/src/renderer/src/components/NotificationButton.tsx index 70a3764d..a4267d0a 100644 --- a/DeskThingServer/src/renderer/src/components/NotificationButton.tsx +++ b/DeskThingServer/src/renderer/src/components/NotificationButton.tsx @@ -9,6 +9,7 @@ const NotificationButton: React.FC = () => { const taskNum = useNotificationStore((state) => state.totalTasks) const logs = useNotificationStore((state) => state.logs) + const requests = useNotificationStore((state) => state.requestQueue) const issues = useNotificationStore((state) => state.issues.length) const [errors, setErrors] = useState(0) @@ -30,8 +31,11 @@ const NotificationButton: React.FC = () => { +
+ + ) +} diff --git a/DeskThingServer/src/renderer/src/overlays/notifications/EventsPage.tsx b/DeskThingServer/src/renderer/src/overlays/notifications/EventsPage.tsx index 93756901..0c049114 100644 --- a/DeskThingServer/src/renderer/src/overlays/notifications/EventsPage.tsx +++ b/DeskThingServer/src/renderer/src/overlays/notifications/EventsPage.tsx @@ -2,6 +2,7 @@ import React from 'react' import { useNotificationStore } from '@renderer/stores' import { IconTrash } from '@renderer/assets/icons' import Button from '@renderer/components/Button' +import { MESSAGE_TYPES } from '@shared/types' const EvensPage: React.FC = () => { const logs = useNotificationStore((state) => state.logs) @@ -21,21 +22,27 @@ const EvensPage: React.FC = () => {
  • ))} diff --git a/DeskThingServer/src/renderer/src/overlays/notifications/NotificationOverlay.tsx b/DeskThingServer/src/renderer/src/overlays/notifications/NotificationOverlay.tsx index 3fe835cf..15aa3ebc 100644 --- a/DeskThingServer/src/renderer/src/overlays/notifications/NotificationOverlay.tsx +++ b/DeskThingServer/src/renderer/src/overlays/notifications/NotificationOverlay.tsx @@ -60,7 +60,12 @@ const NotificationOverlay: React.FC = () => { curPage={page} value={notifState.requestQueue.length} Icon={} - /> + className="relative" + > + {notifState.requestQueue.length > 0 && ( +
    + )} + ( ) diff --git a/DeskThingServer/src/renderer/src/overlays/notifications/RequestsPage.tsx b/DeskThingServer/src/renderer/src/overlays/notifications/RequestsPage.tsx index 1fa94c6e..a01fd665 100644 --- a/DeskThingServer/src/renderer/src/overlays/notifications/RequestsPage.tsx +++ b/DeskThingServer/src/renderer/src/overlays/notifications/RequestsPage.tsx @@ -43,7 +43,7 @@ interface RequestProps { const RequestComponent = ({ request }: RequestProps): React.ReactElement => { const resolveRequest = useNotificationStore((state) => state.resolveRequest) const [expanded, setIsExpanded] = useState(false) - const [focusedIndex, setFocusedIndex] = useState(-1) + const [focusedIndex, setFocusedIndex] = useState(0) const [formData, setFormData] = useState<{ [key: string]: string }>({}) const [allFieldsFilled, setAllFieldsFilled] = useState(false) @@ -73,7 +73,7 @@ const RequestComponent = ({ request }: RequestProps): React.ReactElement => { const toggleExpanded = (): void => { setIsExpanded(!expanded) - setFocusedIndex(-1) + setFocusedIndex(0) } useEffect(() => { diff --git a/DeskThingServer/src/renderer/src/overlays/settings/MusicSettings.tsx b/DeskThingServer/src/renderer/src/overlays/settings/MusicSettings.tsx index 775e8a50..699c9988 100644 --- a/DeskThingServer/src/renderer/src/overlays/settings/MusicSettings.tsx +++ b/DeskThingServer/src/renderer/src/overlays/settings/MusicSettings.tsx @@ -3,13 +3,16 @@ import useSettingsStore from '../../stores/settingsStore' import useAppStore from '../../stores/appStore' import Button from '@renderer/components/Button' import { IconLoading, IconSave, IconToggle } from '@renderer/assets/icons' +import Select from '@renderer/components/Select' +import { SingleValue } from 'react-select' +import { SettingOption, Settings } from '@shared/types' const MusicSettings: React.FC = () => { - const initialSettings = useSettingsStore((settings) => settings.settings) const saveSettings = useSettingsStore((settings) => settings.saveSettings) + const requestSettings = useSettingsStore((settings) => settings.requestSettings) const appsList = useAppStore((state) => state.appsList) const [audioSources, setAudioSources] = useState<{ id: string; name: string }[]>([]) - const [settings, setSettings] = useState(initialSettings) + const [settings, setSettings] = useState(null) const [loading, setLoading] = useState(false) useEffect(() => { @@ -20,14 +23,23 @@ const MusicSettings: React.FC = () => { name: app.manifest?.label || app.name })) setAudioSources(sources) + + const fetchSettings = async (): Promise => { + const settings = await requestSettings() + setSettings(settings) + } + + fetchSettings() }, [appsList]) const handleSettingChange = (key: string, value: string | boolean | number | string[]): void => { + if (!settings) return setSettings({ ...settings, [key]: value }) console.log('Settings Updated:', settings) } const handleSave = async (): Promise => { + if (!settings) return setLoading(true) await saveSettings(settings) setTimeout(() => { @@ -43,55 +55,68 @@ const MusicSettings: React.FC = () => {

    Refresh Interval (seconds)

    handleSettingChange('refreshInterval', Number(e.target.value) * 1000) } className="focus:text-white bg-zinc-900 text-white rounded px-2 py-2" placeholder="Enter A Value" - disabled={settings.refreshInterval === -1} + disabled={!settings || settings.refreshInterval === -1} />
    -

    Playback Sources

    - { - handleSettingChange('playbackLocation', e.target.value) + const value = e as SingleValue + handleSettingChange('playbackLocation', value?.value || '') }} - defaultValue={'Unset'} - className="bg-zinc-900 rounded hover:cursor-pointer text-white px-2 py-2" - > - {audioSources.map((app) => ( - - ))} - - + value={settings ? settings.playbackLocation || '' : 'Disabled'} + className="bg-zinc-900 rounded hover:cursor-pointer text-white px-2 py-2 w-full" + options={[ + ...audioSources.map((app) => ({ + value: app.id, + label: app.name + })), + { + value: 'none', + label: 'None' + }, + { + value: 'disabled', + label: 'Disabled' + } + ]} + />
    +
    +

    Logging Level

    +