From 9a1174a188998fb2f51656dd4b2b63cd8c2eb344 Mon Sep 17 00:00:00 2001 From: ItsRiprod Date: Mon, 29 Jul 2024 05:57:39 -0400 Subject: [PATCH] v0.6.0 release --- DeskThing/src/helpers/WebSocketService.ts | 4 +- DeskThing/src/views/local/index.tsx | 2 +- DeskThing/src/views/utility/index.tsx | 2 +- .../src/main/handlers/adbHandler.ts | 27 +++++- .../src/main/handlers/appHandler.ts | 40 +++++--- .../src/main/handlers/configHandler.ts | 22 +---- .../src/main/handlers/dataHandler.ts | 27 +----- .../src/main/handlers/deviceHandler.ts | 66 ++++++++++---- .../src/main/handlers/keyMapHandler.ts | 38 +++++--- .../src/main/handlers/websocketServer.ts | 12 ++- DeskThingServer/src/main/index.ts | 77 ++++++++++++---- DeskThingServer/src/main/utils/events.ts | 3 +- DeskThingServer/src/main/utils/fileHandler.ts | 17 +--- DeskThingServer/src/preload/index.d.ts | 2 + DeskThingServer/src/preload/index.ts | 4 +- DeskThingServer/src/renderer/src/App.tsx | 6 +- .../renderer/src/components/ADB/Devices.tsx | 33 +++++-- .../src/renderer/src/components/ADB/Tabs.tsx | 24 +---- .../src/renderer/src/components/ADB/Web.tsx | 42 ++++++--- .../src/renderer/src/components/Apps/Apps.tsx | 2 +- .../renderer/src/components/Apps/Local.tsx | 39 +++++++- .../src/renderer/src/components/Apps/Tabs.tsx | 24 +---- .../src/renderer/src/components/Apps/Web.tsx | 47 ++++++++-- .../renderer/src/components/ContentArea.tsx | 8 +- .../src/renderer/src/components/Dev/Adb.tsx | 13 +-- .../src/renderer/src/components/Dev/Tabs.tsx | 24 +---- .../src/components/Device/Devices.tsx | 91 +++++++++++++++++++ .../renderer/src/components/Device/Key.tsx | 24 +++++ .../renderer/src/components/Device/Tabs.tsx | 34 +++++++ .../renderer/src/components/Device/index.tsx | 29 ++++++ .../src/renderer/src/components/Loading.tsx | 2 +- .../components/Overlays/DisplayDeviceData.tsx | 60 +++++++++--- .../src/renderer/src/components/Sidebar.tsx | 6 +- .../icons/icon/Icons/IconCarThing.tsx | 35 ++++--- 34 files changed, 603 insertions(+), 283 deletions(-) create mode 100644 DeskThingServer/src/renderer/src/components/Device/Devices.tsx create mode 100644 DeskThingServer/src/renderer/src/components/Device/Key.tsx create mode 100644 DeskThingServer/src/renderer/src/components/Device/Tabs.tsx create mode 100644 DeskThingServer/src/renderer/src/components/Device/index.tsx diff --git a/DeskThing/src/helpers/WebSocketService.ts b/DeskThing/src/helpers/WebSocketService.ts index 9e2da0b..c3563e1 100644 --- a/DeskThing/src/helpers/WebSocketService.ts +++ b/DeskThing/src/helpers/WebSocketService.ts @@ -1,5 +1,5 @@ -//const BASE_URL = 'ws://192.168.7.1:8891'; -const BASE_URL = 'ws://localhost:8891'; +const BASE_URL = 'ws://192.168.7.1:8891'; +//const BASE_URL = 'ws://localhost:8891'; type SocketEventListener = (msg: socketData) => void; diff --git a/DeskThing/src/views/local/index.tsx b/DeskThing/src/views/local/index.tsx index 85bd053..c671e25 100644 --- a/DeskThing/src/views/local/index.tsx +++ b/DeskThing/src/views/local/index.tsx @@ -84,7 +84,7 @@ const Local: React.FC = () => {

{`Listening On: ${songData?.device || 'Device name'}`}

{songData?.album || 'Album'}

-

{songData?.track_name || 'Song Title'}

+

{songData?.track_name || 'Song Title'}

{songData?.artist || 'Artist'}

diff --git a/DeskThing/src/views/utility/index.tsx b/DeskThing/src/views/utility/index.tsx index ebe2904..9c8e587 100644 --- a/DeskThing/src/views/utility/index.tsx +++ b/DeskThing/src/views/utility/index.tsx @@ -128,7 +128,7 @@ const Utility: FC = (): JSX.Element => { )) : (
- +
)} diff --git a/DeskThingServer/src/main/handlers/adbHandler.ts b/DeskThingServer/src/main/handlers/adbHandler.ts index e2e1d51..acca2e0 100644 --- a/DeskThingServer/src/main/handlers/adbHandler.ts +++ b/DeskThingServer/src/main/handlers/adbHandler.ts @@ -1,21 +1,38 @@ import path from 'path' -import { exec } from 'child_process' +import { execFile } from 'child_process' import getPlatform from '../utils/get-platform' +import dataListener, { MESSAGE_TYPES } from '../utils/events' -const isProduction = process.env.NODE_ENV === 'development' -const execPath = isProduction +const isDevelopment = process.env.NODE_ENV === 'development' +const execPath = isDevelopment ? path.join(__dirname, '..', '..', 'adb_source', getPlatform()) : path.join(process.resourcesPath, getPlatform()) const adbExecutableName = process.platform === 'win32' ? 'adb.exe' : 'adb' const adbPath = path.join(execPath, adbExecutableName) -export const handleAdbCommands = (command: string): Promise => { +export const handleAdbCommands = (command: string, event?): Promise => { return new Promise((resolve, reject) => { - exec(`cd '${execPath}' && ${adbExecutableName} ${command}`, (error, stdout, stderr) => { + execFile(adbPath, command.split(' '), { cwd: execPath }, (error, stdout, stderr) => { if (error) { + if (event) { + event.sender.send('logging', { + status: false, + data: 'Error Encountered!', + final: true, + error: stderr + }) + } + dataListener.emit(MESSAGE_TYPES.ERROR, `ADB Error: ${stderr}, ${command}, ${adbPath}`) reject(`ADB Error: ${stderr}, ${command}, ${adbPath}`) } else { + if (event) { + event.sender.send('logging', { + status: true, + data: 'ADB Success!', + final: true + }) + } resolve(stdout) } }) diff --git a/DeskThingServer/src/main/handlers/appHandler.ts b/DeskThingServer/src/main/handlers/appHandler.ts index e3dee1b..dfb6ff9 100644 --- a/DeskThingServer/src/main/handlers/appHandler.ts +++ b/DeskThingServer/src/main/handlers/appHandler.ts @@ -461,7 +461,7 @@ interface returnData { platforms: string[] requirements: string[] } -async function handleZip(zipFilePath: string): Promise { +async function handleZip(zipFilePath: string, event?): Promise { try { const extractDir = join(app.getPath('userData'), 'apps') // Extract to user data folder // Create the extraction directory if it doesn't exist @@ -475,13 +475,18 @@ async function handleZip(zipFilePath: string): Promise { fs.mkdirSync(tempDir, { recursive: true }) } + event && event.reply('logging', { status: true, data: 'Extracting App', final: false }) + // Extract the .zip file to a temporary location to get the manifest.json await new Promise((resolve, reject) => { const extractStream = fs .createReadStream(zipFilePath) .pipe(unzipper.Extract({ path: tempDir })) - extractStream.on('error', reject) + extractStream.on('error', () => { + event && event.reply('logging', { status: false, data: 'Extraction failed!', final: true }) + reject() + }) extractStream.on('close', () => { console.log('Extraction finished') resolve() @@ -492,6 +497,7 @@ async function handleZip(zipFilePath: string): Promise { console.log(file) }) + event && event.reply('logging', { status: true, data: 'Getting Manifest', final: false }) let returnData = await getManifest(tempDir) if (!returnData) { @@ -509,6 +515,8 @@ async function handleZip(zipFilePath: string): Promise { } } + event && + event.reply('logging', { status: true, data: 'Disabling and purging app', final: false }) await disableApp(returnData.appId) await purgeAppData(returnData.appId) @@ -531,23 +539,20 @@ async function handleZip(zipFilePath: string): Promise { } } -async function handleZipFromUrl( - zipUrlPath: string, - reply: (data: string, payload: any) => void -): Promise { +async function handleZipFromUrl(zipUrlPath: string, event): Promise { const tempZipPath = getAppFilePath('apps', 'temp') let returnData: returnData | undefined try { if (!fs.existsSync(getAppFilePath('apps', 'temp'))) { fs.mkdirSync(getAppFilePath('apps', 'temp'), { recursive: true }) } - const writeStream = fs.createWriteStream(join(tempZipPath, 'temp.zip')) writeStream.on('error', (error) => { dataListener.emit(MESSAGE_TYPES.ERROR, `Error writing to temp file: ${error.message}`) throw new Error(`Error writing to temp file: ${error.message}`) }) + event.reply('logging', { status: true, data: 'Making Request...', final: false }) const request = net.request(zipUrlPath) request.on('response', (response) => { if (response.statusCode !== 200) { @@ -555,6 +560,12 @@ async function handleZipFromUrl( MESSAGE_TYPES.ERROR, `Failed to download zip file: ${response.statusCode}` ) + event.reply('logging', { + status: false, + data: 'Encountered an error', + final: true, + error: `Failed to download zip file: ${response.statusCode}` + }) return } @@ -565,10 +576,11 @@ async function handleZipFromUrl( response.on('end', async () => { writeStream.end() try { + event.reply('logging', { status: true, data: 'Extracting zip file...', final: false }) // Run file like a normal zip file - returnData = await handleZip(join(tempZipPath, 'temp.zip')) + returnData = await handleZip(join(tempZipPath, 'temp.zip'), event) console.log(`Successfully processed and deleted ${tempZipPath}`) - reply('zip-name', returnData) + event.reply('zip-name', { status: true, data: returnData, final: true }) } catch (error) { dataListener.emit(MESSAGE_TYPES.ERROR, `Error processing zip file: ${error}`) } finally { @@ -629,12 +641,13 @@ async function loadAndRunEnabledApps(): Promise { * Adds an app to the program. This will check the config for if the app exists already or not, and update accordingly * This will also enable the app and then run the app * - * @param _event + * @param event * @param appName */ -async function addApp(_event, appName: string, appPath?: string): Promise { +async function addApp(event, appName: string, appPath?: string): Promise { try { // Load existing apps config + event.reply('logging', { status: true, data: 'Loading apps config', final: false }) if (appPath != undefined) { console.log('Developer app detected: Purging old app...') await purgeAppData(appName) @@ -656,15 +669,12 @@ async function addApp(_event, appName: string, appPath?: string): Promise } else { console.log(`App '${appName}' already exists and is enabled.`) } - + event.reply('logging', { status: true, data: 'Running App', final: false }) console.log('Running app...') if (runningApps.has(appName)) { await stopApp(appName) } - await runApp(appName) - - // Run the app if enabled } catch (error) { console.error('Error adding app:', error) } diff --git a/DeskThingServer/src/main/handlers/configHandler.ts b/DeskThingServer/src/main/handlers/configHandler.ts index 7ddb19e..353546b 100644 --- a/DeskThingServer/src/main/handlers/configHandler.ts +++ b/DeskThingServer/src/main/handlers/configHandler.ts @@ -1,6 +1,7 @@ import { sendIpcData } from '..' import dataListener, { MESSAGE_TYPES } from '../utils/events' import { readFromFile, writeToFile } from '../utils/fileHandler' +import { ButtonMapping } from './keyMapHandler' export interface Manifest { isAudioSource: boolean requires: Array @@ -44,7 +45,7 @@ const defaultData: AppData = { const readData = (): AppData => { const dataFilePath = 'apps.json' try { - const data = readFromFile(dataFilePath) + const data = readFromFile(dataFilePath) if (!data) { // File does not exist, create it with default data writeToFile(defaultData, dataFilePath) @@ -52,28 +53,17 @@ const readData = (): AppData => { } // If data is of type AppData, return it - if (isAppData(data)) { - return data as AppData - } else { - // Handle case where data is not of type AppData - console.error('Data format is incorrect') - return defaultData - } + return data as AppData } catch (err) { console.error('Error reading data:', err) return defaultData } } -// Type guard to check if data is of type AppData -const isAppData = (data: any): data is AppData => { - return 'apps' in data && 'config' in data -} - // Helper function to write data const writeData = (data: AppData): void => { try { - const result = writeToFile(data, 'apps.json') + const result = writeToFile(data, 'apps.json') if (!result) { dataListener.emit(MESSAGE_TYPES.ERROR, 'Error writing data') } @@ -212,7 +202,5 @@ export { addAppManifest, addConfig, getConfig, - purgeAppConfig, - saveMappings, - loadMappings + purgeAppConfig } diff --git a/DeskThingServer/src/main/handlers/dataHandler.ts b/DeskThingServer/src/main/handlers/dataHandler.ts index c4496b1..1f91f83 100644 --- a/DeskThingServer/src/main/handlers/dataHandler.ts +++ b/DeskThingServer/src/main/handlers/dataHandler.ts @@ -12,7 +12,7 @@ const defaultData: Data = {} const readData = (): Data => { const dataFilePath = 'data.json' try { - const data = readFromFile(dataFilePath) + const data = readFromFile(dataFilePath) if (!data) { // File does not exist, create it with default data writeToFile(defaultData, dataFilePath) @@ -20,39 +20,18 @@ const readData = (): Data => { } // If data is of type Data, return it - if (isData(data)) { - return data as Data - } else { - // Handle case where data is not of type Data - console.error('Data format is incorrect') - return defaultData - } + return data as Data } catch (err) { console.error('Error reading data:', err) return defaultData } } -// Type guard to check if data is of type Data -const isData = (data: any): data is Data => { - // Simple check to verify if data conforms to the Data interface - return ( - typeof data === 'object' && - data !== null && - Object.values(data).every( - (value) => - typeof value === 'object' && - value !== null && - Object.values(value).every((val) => typeof val === 'string') - ) - ) -} - // Updated function to write Data using the new fileHandler const writeData = (data: Data): void => { try { const dataFilePath = 'data.json' - writeToFile(data, dataFilePath) + writeToFile(data, dataFilePath) } catch (err) { console.error('Error writing data:', err) } diff --git a/DeskThingServer/src/main/handlers/deviceHandler.ts b/DeskThingServer/src/main/handlers/deviceHandler.ts index c4dee24..5eec584 100644 --- a/DeskThingServer/src/main/handlers/deviceHandler.ts +++ b/DeskThingServer/src/main/handlers/deviceHandler.ts @@ -5,6 +5,7 @@ import * as fs from 'fs' import * as unzipper from 'unzipper' import { app, net } from 'electron' import { handleAdbCommands } from './adbHandler' +import { ReplyData } from '..' export const HandleDeviceData = async (data: any): Promise => { try { @@ -23,34 +24,45 @@ export const HandleDeviceData = async (data: any): Promise => { } } export const HandlePushWebApp = async ( - reply: (data: string, payload: any) => void + reply: (channel: string, data: ReplyData) => void ): Promise => { try { const userDataPath = app.getPath('userData') const extractDir = join(userDataPath, 'webapp') let response + console.log('Remounting...') + reply('logging', { status: true, data: 'Remounting...', final: false }) response = await handleAdbCommands('shell mount -o remount,rw /') - // Set when replies are handled - // reply('reply-interface', response) - response = await handleAdbCommands('shell mv /usr/share/qt-superbird-app/webapp /tmp/webapp-orig') - // reply('reply-interface', response) + reply('logging', { status: true, data: response || 'Moving...', final: false }) + response = await handleAdbCommands( + 'shell mv /usr/share/qt-superbird-app/webapp /tmp/webapp-orig' + ) + reply('logging', { status: true, data: response || 'Moving...', final: false }) response = await handleAdbCommands('shell mv /tmp/webapp-orig /usr/share/qt-superbird-app/') - // reply('reply-interface', response) + + reply('logging', { status: true, data: response || 'Removing old app...', final: false }) response = await handleAdbCommands('shell rm -r /tmp/webapp-orig') - // reply('reply-interface', response) + + reply('logging', { status: true, data: response || 'Pushing new app...', final: false }) response = await handleAdbCommands(`push ${extractDir}/ /usr/share/qt-superbird-app/webapp`) - // reply('reply-interface', { type: update, status: {running: true, message: ${response}}}) + + reply('logging', { status: true, data: response || 'Restarting Chromium', final: false }) response = await handleAdbCommands('shell supervisorctl restart chromium') - // reply('reply-interface', { type: update, status: {running: true, message: ${response}}}) - reply('pushed-staged', { success: true }) + + reply('logging', { status: true, data: response, final: true }) } catch (Exception) { - reply('pushed-staged', { success: false, error: Exception }) + reply('logging', { + status: false, + data: 'There has been an error', + final: true, + error: `${Exception}` + }) dataListener.emit(MESSAGE_TYPES.ERROR, 'HandlePushWebApp encountered the error ' + Exception) } } export const HandleWebappZipFromUrl = ( - reply: (data: string, payload: any) => void, + reply: (channel: string, data: ReplyData) => void, zipFileUrl: string ): void => { const userDataPath = app.getPath('userData') @@ -92,13 +104,18 @@ export const HandleWebappZipFromUrl = ( ) // Notify success to the frontend - reply('zip-extracted', { success: true }) + reply('logging', { status: true, data: 'Success!', final: true }) } catch (error) { console.error('Error extracting zip file:', error) dataListener.emit(MESSAGE_TYPES.ERROR, `Error extracting zip file: ${error}`) // Notify failure to the frontend - reply('zip-extracted', { success: false, error: error }) + reply('logging', { + status: false, + data: 'Failed to extract!', + final: true, + error: 'No error provided' + }) } }) @@ -107,7 +124,12 @@ export const HandleWebappZipFromUrl = ( dataListener.emit(MESSAGE_TYPES.ERROR, `Error downloading zip file: ${error}`) // Notify failure to the frontend - reply('zip-extracted', { success: false, error: error.message }) + reply('logging', { + status: false, + data: 'ERR Downloading file!', + final: true, + error: error.message + }) }) } else { const errorMessage = `Failed to download zip file: ${response.statusCode}` @@ -115,7 +137,12 @@ export const HandleWebappZipFromUrl = ( dataListener.emit(MESSAGE_TYPES.ERROR, errorMessage) // Notify failure to the frontend - reply('zip-extracted', { success: false, error: errorMessage }) + reply('logging', { + status: false, + data: 'Failed to download zip file!', + final: true, + error: errorMessage + }) } }) @@ -124,7 +151,12 @@ export const HandleWebappZipFromUrl = ( dataListener.emit(MESSAGE_TYPES.ERROR, `Error sending request: ${error}`) // Notify failure to the frontend - reply('zip-extracted', { success: false, error: error.message }) + reply('logging', { + status: false, + data: 'Failed to download zip file!', + final: true, + error: error.message + }) }) request.end() diff --git a/DeskThingServer/src/main/handlers/keyMapHandler.ts b/DeskThingServer/src/main/handlers/keyMapHandler.ts index c7c2ca2..0190892 100644 --- a/DeskThingServer/src/main/handlers/keyMapHandler.ts +++ b/DeskThingServer/src/main/handlers/keyMapHandler.ts @@ -1,4 +1,6 @@ +import dataListener, { MESSAGE_TYPES } from '../utils/events' import { readFromFile, writeToFile } from '../utils/fileHandler' +import { sendMappings } from './websocketServer' export type Button = { name: string @@ -6,7 +8,7 @@ export type Button = { source: string } -export type key = { +export type Key = { id: string source: string } @@ -17,16 +19,16 @@ export type ButtonMapping = { export type FileStructure = { default: ButtonMapping - [key: string]: ButtonMapping | Button[] | key[] | string + [key: string]: ButtonMapping | Button[] | Key[] | string functions: Button[] - keys: key[] + keys: Key[] version: string } const defaultData: FileStructure = { version: '0.6.0', default: { - tray1: { name: 'Skip', description: 'Shuffle', source: 'server' }, + tray1: { name: 'Shuffle', description: 'Shuffle', source: 'server' }, tray2: { name: 'Rewind', description: 'Rewind', source: 'server' }, tray3: { name: 'PlayPause', description: 'PlayPause', source: 'server' }, tray4: { name: 'Skip', description: 'Skip', source: 'server' }, @@ -57,15 +59,10 @@ const defaultData: FileStructure = { { name: 'Pref3', description: 'Pref3', source: 'server' }, { name: 'Pref4', description: 'Pref4', source: 'server' }, { name: 'Swap', description: 'Swap', source: 'server' }, - { name: 'Swap', description: 'Swap', source: 'server' }, - { name: 'Swap', description: 'Swap', source: 'server' }, - { name: 'Swap', description: 'Swap', source: 'server' }, { name: 'VolDown', description: 'VolDown', source: 'server' }, { name: 'VolUp', description: 'VolUp', source: 'server' }, { name: 'PlayPause', description: 'PlayPause', source: 'server' }, - { name: 'Skip', description: 'Skip', source: 'server' }, - { name: 'Repeat', description: 'Repeat', source: 'server' }, - { name: 'Repeat', description: 'Repeat', source: 'server' } + { name: 'Skip', description: 'Skip', source: 'server' } ], keys: [ { id: 'tray1', source: 'server' }, @@ -106,12 +103,24 @@ const saveMappings = (mappings: FileStructure): void => { writeToFile(mappings, 'mappings.json') } -const getMappings = (): FileStructure => { - return loadMappings() +const getMappings = (mappingName: string = 'default'): ButtonMapping => { + const mappings = loadMappings() + if (!(mappingName in mappings)) { + dataListener.emit( + MESSAGE_TYPES.ERROR, + `MAPHANDLER: Mapping ${mappingName} does not exist, using default` + ) + throw new Error(`Mapping ${mappingName} does not exist`) + } + return mappings[mappingName] as ButtonMapping } -const setMappings = (newMappings: FileStructure): void => { - saveMappings(newMappings) +const setMappings = (mappingName: string, newMappings: ButtonMapping): void => { + const mappings = loadMappings() + mappings[mappingName] = newMappings + saveMappings(mappings) + dataListener.emit(MESSAGE_TYPES.LOGGING, 'MAPHANDLER: Map saved successfully!') + sendMappings() } const getDefaultMappings = (): ButtonMapping => { @@ -189,6 +198,7 @@ const removeKey = (keyId: string): void => { } export { + loadMappings, getMappings, setMappings, getDefaultMappings, diff --git a/DeskThingServer/src/main/handlers/websocketServer.ts b/DeskThingServer/src/main/handlers/websocketServer.ts index c996a53..fd62400 100644 --- a/DeskThingServer/src/main/handlers/websocketServer.ts +++ b/DeskThingServer/src/main/handlers/websocketServer.ts @@ -98,8 +98,14 @@ const sendPrefData = async (socket = null): Promise => { const sendMappings = async (socket: WebSocket | null = null): Promise => { try { const mappings = getDefaultMappings() - sendData(socket, { app: 'client', type: 'button_mappings', data: mappings }) - console.log('WSOCKET: Button mappings sent!') + if (socket) { + sendData(socket, { app: 'client', type: 'button_mappings', data: mappings }) + console.log('WSOCKET: Button mappings sent!') + dataListener.asyncEmit(MESSAGE_TYPES.MESSAGE, `WEBSOCKET: Client has been sent button maps!` ) + } else { + sendMessageToClients({ app: 'client', type: 'button_mappings', data: mappings }) + dataListener.asyncEmit(MESSAGE_TYPES.MESSAGE, `WEBSOCKET: Client has been sent button maps!` ) + } } catch (error) { console.error('WSOCKET: Error getting button mappings:', error) if (socket) sendError(socket, 'Error getting button mappings') @@ -282,4 +288,4 @@ server.on('connection', async (socket) => { }) }) -export { sendMessageToClients, sendResponse, sendError, sendData, sendPrefData } +export { sendMessageToClients, sendMappings, sendResponse, sendError, sendData, sendPrefData } diff --git a/DeskThingServer/src/main/index.ts b/DeskThingServer/src/main/index.ts index 39168ed..bbe13c9 100644 --- a/DeskThingServer/src/main/index.ts +++ b/DeskThingServer/src/main/index.ts @@ -7,12 +7,22 @@ import { GithubRelease } from './types/types' let mainWindow: BrowserWindow | null = null let tray: Tray | null = null +export interface ReplyData { + status: boolean + data: any + final: boolean + error?: string +} + const IPC_CHANNELS = { PING: 'ping', GET_CONNECTIONS: 'get-connections', ADD_APP: 'add-app', RUN_STOP_APP: 'stop-app', + RUN_ADB: 'run-adb-command', + RUN_DEVICE_COMMAND: 'run-device-command', GET_APPS: 'get-apps', + GET_GITHUB: 'fetch-github-releases', GET_MAPS: 'get-maps', SET_MAP: 'set-maps', STOP_APP: 'stop-app', @@ -21,6 +31,9 @@ const IPC_CHANNELS = { HANDLE_ZIP: 'handle-zip', USER_DATA_RESPONSE: 'user-data-response', SELECT_ZIP_FILE: 'select-zip-file', + EXTRACT_WEBAPP_ZIP: 'extract-webapp-zip', + EXTRACT_APP_ZIP_URL: 'extract-app-zip-url', + PUSH_STAGED_WEBAPP: 'push-staged', DEV_ADD_APP: 'dev-add-app' } @@ -119,7 +132,7 @@ async function setupIpcHandlers(): Promise { stopApp, purgeAppData } = await import('./handlers/appHandler') - import { getMappings } from './handlers/keyMapHandler' + const { loadMappings, setMappings } = await import('./handlers/keyMapHandler') const { handleAdbCommands } = await import('./handlers/adbHandler') const { sendData } = await import('./handlers/websocketServer') const { getReleases } = await import('./handlers/githubHandler') @@ -129,56 +142,70 @@ async function setupIpcHandlers(): Promise { let connections = 0 ipcMain.on(IPC_CHANNELS.PING, () => console.log('pong')) - ipcMain.on(IPC_CHANNELS.GET_CONNECTIONS, async (event) => { - event.sender.send('connections', connections) + event.reply('connections', { status: true, data: connections, final: true }) console.log('SERVER: connections', connections) }) ipcMain.on(IPC_CHANNELS.ADD_APP, async (event, appName: string) => { await addApp(event, appName) + event.reply('logging', { status: true, data: 'Finished', final: true }) }) ipcMain.on(IPC_CHANNELS.DEV_ADD_APP, async (event, appPath: string) => { await addApp(event, 'developer-app', appPath) + event.reply('logging', { status: true, data: 'Finished', final: true }) }) ipcMain.on(IPC_CHANNELS.GET_APPS, (event) => { + event.reply('logging', { status: true, data: 'Getting data', final: false }) const data = getAppData() - event.sender.send('app-data', data) + event.reply('logging', { status: true, data: 'Finished', final: true }) + event.reply('app-data', { status: true, data: data, final: true }) }) - ipcMain.on(IPC_CHANNELS.STOP_APP, async (_event, appName: string) => { + ipcMain.on(IPC_CHANNELS.STOP_APP, async (event, appName: string) => { + event.reply('logging', { status: true, data: 'Stopping App', final: false }) await stopApp(appName) + event.reply('logging', { status: true, data: 'Finished', final: true }) }) - ipcMain.on(IPC_CHANNELS.DISABLE_APP, async (_event, appName: string) => { + ipcMain.on(IPC_CHANNELS.DISABLE_APP, async (event, appName: string) => { + event.reply('logging', { status: true, data: 'Disabling App', final: false }) await disableApp(appName) + event.reply('logging', { status: true, data: 'Finished', final: true }) }) - ipcMain.on(IPC_CHANNELS.PURGE_APP, async (_event, appName: string) => { + ipcMain.on(IPC_CHANNELS.PURGE_APP, async (event, appName: string) => { + event.reply('logging', { status: true, data: 'Purging App', final: false }) console.log(`====== PURGING APP ${appName} ========`) await purgeAppData(appName) + event.reply('logging', { status: true, data: 'Finished', final: true }) }) ipcMain.on(IPC_CHANNELS.HANDLE_ZIP, async (event, zipFilePath: string) => { + event.reply('logging', { status: true, data: 'Handling zipped app', final: false }) console.log('SERVER: handling zip file event', event) - const returnData = await handleZip(zipFilePath) // Extract to user data folder - console.log('SERVER: Return Data after Extraction:', returnData) - event.reply('zip-name', returnData) + + const returnData = await handleZip(zipFilePath, event) // Extract to user data folder + + event.reply('logging', { status: true, data: 'Finished', final: true }) + event.reply('zip-name', { status: true, data: returnData, final: true }) }) - ipcMain.on('extract-webapp-zip', async (event, zipFileUrl) => { + ipcMain.on(IPC_CHANNELS.EXTRACT_WEBAPP_ZIP, async (event, zipFileUrl) => { try { + event.reply('logging', { status: true, data: 'Handling web app from URL', final: false }) HandleWebappZipFromUrl(event.reply, zipFileUrl) } catch (error) { console.error('Error extracting zip file:', error) event.reply('zip-extracted', { success: false, error: error }) } }) - ipcMain.on('extract-app-zip-url', async (event, zipFileUrl) => { - console.log('SERVER: handling zip file event', event) - const returnData = await handleZipFromUrl(zipFileUrl, event.reply) // Extract to user data folder + ipcMain.on(IPC_CHANNELS.EXTRACT_APP_ZIP_URL, async (event, zipFileUrl) => { + event.reply('logging', { status: true, data: 'Handling app from URL...', final: false }) + const returnData = await handleZipFromUrl(zipFileUrl, event) // Extract to user data folder console.log('SERVER: Return Data after Extraction:', returnData) }) - ipcMain.on('push-staged', async (event) => { + ipcMain.on(IPC_CHANNELS.PUSH_STAGED_WEBAPP, async (event) => { try { + console.log('Pushing stages webapp...') HandlePushWebApp(event.reply) } catch (error) { - event.reply('pushed-staged', { success: false, error: error }) + event.reply(IPC_CHANNELS.PUSH_STAGED_WEBAPP, { success: false, error: error }) console.error('Error extracting zip file:', error) dataListener.emit(MESSAGE_TYPES.ERROR, error) } @@ -204,10 +231,18 @@ async function setupIpcHandlers(): Promise { return { path: filePath, name: path.basename(filePath) } }) - ipcMain.handle('run-adb-command', async (_event, command) => { - return await handleAdbCommands(command) + ipcMain.handle(IPC_CHANNELS.RUN_ADB, async (event, command) => { + return await handleAdbCommands(command, event) + }) + ipcMain.handle(IPC_CHANNELS.GET_MAPS, async (event) => { + event.sender.send('logging', { status: true, data: 'Maps Retrieved!', final: true }) + return await loadMappings() }) - ipcMain.handle('fetch-github-releases', async (_event, url): Promise => { + ipcMain.handle(IPC_CHANNELS.SET_MAP, async (event, name, map) => { + event.sender.send('logging', { status: true, data: 'Maps Saved!', final: true }) + await setMappings(name, map) + }) + ipcMain.handle(IPC_CHANNELS.GET_GITHUB, async (_event, url): Promise => { try { return await getReleases(url) } catch (error) { @@ -215,9 +250,10 @@ async function setupIpcHandlers(): Promise { return [] } }) - ipcMain.handle('run-device-command', async (_event, type, command) => { + ipcMain.handle(IPC_CHANNELS.RUN_DEVICE_COMMAND, async (event, type, command) => { const data = { app: 'client', type: type, data: JSON.parse(command) } console.log('Sending data', data) + event.sender.send('logging', { status: true, data: 'Finished', final: true }) return await sendData(null, data) }) @@ -236,6 +272,7 @@ async function setupIpcHandlers(): Promise { console.log('Number of clients', numConnections) }) } + // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. diff --git a/DeskThingServer/src/main/utils/events.ts b/DeskThingServer/src/main/utils/events.ts index 11ed7cc..4ba8a03 100644 --- a/DeskThingServer/src/main/utils/events.ts +++ b/DeskThingServer/src/main/utils/events.ts @@ -6,7 +6,8 @@ export const MESSAGE_TYPES = { LOGGING: 'log', MESSAGE: 'message', CONFIG: 'config', - CONNECTION: 'connection' + CONNECTION: 'connection', + MAPPINGS: 'mapping' } /* Handles server-wide announcements for certain events */ diff --git a/DeskThingServer/src/main/utils/fileHandler.ts b/DeskThingServer/src/main/utils/fileHandler.ts index 3d3749e..4525d33 100644 --- a/DeskThingServer/src/main/utils/fileHandler.ts +++ b/DeskThingServer/src/main/utils/fileHandler.ts @@ -1,18 +1,8 @@ import { app } from 'electron' import { join } from 'path' import fs from 'fs' -import { AppData } from '../handlers/configHandler' -import { fileStructure } from '../handlers/keyMapHandler' -interface fileData { - [appName: string]: - | { - [key: string]: string | [string] - } - | string -} - -export const readFromFile = (filename: string): fileStructure | fileData | false | AppData => { +export const readFromFile = (filename: string): T | false => { const dataFilePath = join(app.getPath('userData'), filename) try { if (!fs.existsSync(dataFilePath)) { @@ -29,10 +19,7 @@ export const readFromFile = (filename: string): fileStructure | fileData | false } // Helper function to write data -export const writeToFile = ( - data: fileData | AppData | fileStructure, - filepath: string -): boolean => { +export const writeToFile = (data: T, filepath: string): boolean => { try { const dataFilePath = join(app.getPath('userData'), filepath) fs.writeFileSync(dataFilePath, JSON.stringify(data, null, 2)) diff --git a/DeskThingServer/src/preload/index.d.ts b/DeskThingServer/src/preload/index.d.ts index cbcde55..acef000 100644 --- a/DeskThingServer/src/preload/index.d.ts +++ b/DeskThingServer/src/preload/index.d.ts @@ -7,6 +7,8 @@ declare global { runAdbCommand: (command: string) => Promise runDeviceCommand: (type: string, command: string) => Promise fetchReleases: (url: string) => Promise<[]> + getMaps: () => Promise + setMaps: (name: string, map: any) => Promise } api: unknown // Or define `api` more specifically if you have a shape for it } diff --git a/DeskThingServer/src/preload/index.ts b/DeskThingServer/src/preload/index.ts index ea461f4..e909e39 100644 --- a/DeskThingServer/src/preload/index.ts +++ b/DeskThingServer/src/preload/index.ts @@ -9,7 +9,9 @@ const api = { ipcRenderer.invoke('run-adb-command', command), runDeviceCommand: (type: string, command: string): Promise => ipcRenderer.invoke('run-device-command', type, command), - fetchReleases: (url: string): Promise<[]> => ipcRenderer.invoke('fetch-github-releases', url) + fetchReleases: (url: string): Promise<[]> => ipcRenderer.invoke('fetch-github-releases', url), + getMaps: (): Promise => ipcRenderer.invoke('get-maps'), + setMaps: (name: string, map: any): Promise => ipcRenderer.invoke('set-maps', name, map) } // Use `contextBridge` APIs to expose Electron APIs to // renderer only if context isolation is enabled, otherwise diff --git a/DeskThingServer/src/renderer/src/App.tsx b/DeskThingServer/src/renderer/src/App.tsx index c77a6f2..b3be30d 100644 --- a/DeskThingServer/src/renderer/src/App.tsx +++ b/DeskThingServer/src/renderer/src/App.tsx @@ -14,7 +14,7 @@ function App(): JSX.Element { const [currentView, setCurrentView] = useState('appsList') useEffect(() => { - const handleAppData = (_event: Electron.IpcRendererEvent, data: AppData): void => { + const handleAppData = (data: AppData): void => { handleAppDataJson(data) } const handleLog = async (errorData, type): Promise => { @@ -22,7 +22,9 @@ function App(): JSX.Element { } // Set up listener for 'app-data' event - const removeListener = window.electron.ipcRenderer.on('app-data', handleAppData) + const removeListener = window.electron.ipcRenderer.on('app-data', (_event, response) => + handleAppData(response.data) + ) const removeErrorListener = window.electron.ipcRenderer.on('error', (_event, errorData) => handleLog(errorData, 'error') ) diff --git a/DeskThingServer/src/renderer/src/components/ADB/Devices.tsx b/DeskThingServer/src/renderer/src/components/ADB/Devices.tsx index 6201114..c7e3cf2 100644 --- a/DeskThingServer/src/renderer/src/components/ADB/Devices.tsx +++ b/DeskThingServer/src/renderer/src/components/ADB/Devices.tsx @@ -71,14 +71,21 @@ const Device = (): JSX.Element => { setError('Pushing app...') setLoading(true) window.electron.ipcRenderer.send('push-staged') - window.electron.ipcRenderer.once('pushed-staged', (_event, reply) => { - setError('App pushed') + const unsubscribe = window.electron.ipcRenderer.on('logging', (_event, reply) => { console.log(reply) - setLoading(false) - if (!reply.success) { + if (reply.final) { + setLoading(false) + unsubscribe() + } else { + setLoading(true) + } + if (!reply.status) { setError(reply.error || 'Unknown error occurred') + unsubscribe() } else { - // Optionally handle success, e.g., navigate to the extracted app + if (reply.data) { + setError(reply.data) + } } }) } catch (error) { @@ -123,42 +130,50 @@ const Device = (): JSX.Element => { )}
-

{tooltip}

+

{loading ? '' : tooltip}

diff --git a/DeskThingServer/src/renderer/src/components/ADB/Tabs.tsx b/DeskThingServer/src/renderer/src/components/ADB/Tabs.tsx index a1668ab..58a6ace 100644 --- a/DeskThingServer/src/renderer/src/components/ADB/Tabs.tsx +++ b/DeskThingServer/src/renderer/src/components/ADB/Tabs.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import React from 'react' import { View } from '.' interface TopbarProps { @@ -7,32 +7,10 @@ interface TopbarProps { } const Tabs: React.FC = ({ setCurrentView, currentView }) => { - const [connections, setConnections] = React.useState(0) const handleClick = (view: View): void => { setCurrentView(view) } - const getConnections = (): void => window.electron.ipcRenderer.send('get-connections') - - useEffect(() => { - const handleConnection = (_event, num: number): void => { - setConnections(num) - console.log('got connections', num) - } - - console.log('got connections', connections) - const removeListener = window.electron.ipcRenderer.on('connections', handleConnection) - - const timeoutId = setTimeout(() => { - getConnections() - }, 1500) - - return () => { - removeListener() - clearTimeout(timeoutId) - } - }, []) - return (
diff --git a/DeskThingServer/src/renderer/src/components/ADB/Web.tsx b/DeskThingServer/src/renderer/src/components/ADB/Web.tsx index ffee634..eb098be 100644 --- a/DeskThingServer/src/renderer/src/components/ADB/Web.tsx +++ b/DeskThingServer/src/renderer/src/components/ADB/Web.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { IconLogoGearLoading } from '../icons' +import { IconLogoLoading } from '../icons' import githubStore, { GithubRelease, GithubAsset } from '../../store/githubStore' import ReleaseList from '../ReleaseList' @@ -8,6 +8,7 @@ const Web = (): JSX.Element => { const [openReleaseId, setOpenReleaseId] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) + const [status, setStatus] = useState('') useEffect(() => { const fetchData = async (): Promise => { @@ -26,20 +27,34 @@ const Web = (): JSX.Element => { return assets.filter((asset) => asset.name.includes('deskthing-client-build')) } + const handleLogging = async (): Promise => { + setLoading(true) + const unsubscribe = window.electron.ipcRenderer.on('logging', (_event, reply) => { + console.log(reply) + if (reply.final) { + unsubscribe() + setLoading(false) + } else { + setLoading(true) + } + if (!reply.status) { + setStatus(reply.error || 'Unknown error occurred') + unsubscribe() + setLoading(false) + } else { + if (reply.data) { + setStatus(reply.data) + } + } + }) + } + const handleAssetClick = async (asset: GithubAsset): Promise => { // Send the selected asset to Electron backend for extraction setLoading(true) try { window.electron.ipcRenderer.send('extract-webapp-zip', asset.browser_download_url) - window.electron.ipcRenderer.once('zip-extracted', (_event, reply) => { - console.log(reply) - setLoading(false) - if (!reply.success) { - setError(reply.error || 'Unknown error occurred') - } else { - // Optionally handle success, e.g., navigate to the extracted app - } - }) + handleLogging() } catch (error) { setLoading(false) if (error) { @@ -52,10 +67,9 @@ const Web = (): JSX.Element => {
{loading ? ( -
-
- -
+
+ + {status &&

{status}

}
) : error ? (
diff --git a/DeskThingServer/src/renderer/src/components/Apps/Apps.tsx b/DeskThingServer/src/renderer/src/components/Apps/Apps.tsx index f94f44f..878e2dc 100644 --- a/DeskThingServer/src/renderer/src/components/Apps/Apps.tsx +++ b/DeskThingServer/src/renderer/src/components/Apps/Apps.tsx @@ -62,7 +62,7 @@ const Apps = (): JSX.Element => {
{enabled && } - {Object.keys(appsList.apps).length > 0 ? ( + {appsList?.apps?.length > 0 && Object.keys(appsList.apps).length > 0 ? (
{(appsList.apps as App[]).map((app, appIndex) => (
{ const [appData, setAppData] = useState(null) const [dragActive, setDragActive] = useState(false) const [loading, setLoading] = useState(false) + const [status, setStatus] = useState('') const handleDrop = async (event: DragEvent): Promise => { event.preventDefault() @@ -32,9 +39,12 @@ const index = (): JSX.Element => { try { // Notify the main process to handle the zip file window.electron.ipcRenderer.send('handle-zip', zipFilePath) - window.electron.ipcRenderer.once('zip-name', (_event, data: returnData) => { - console.log('Received appId:', data) - setAppData(data) + handleLogging() + window.electron.ipcRenderer.once('zip-name', (_event, response: responseData) => { + console.log('Received appId:', response) + if (response.status) { + setAppData(response.data) + } setLoading(false) }) } catch (error) { @@ -42,6 +52,28 @@ const index = (): JSX.Element => { } } + const handleLogging = async (): Promise => { + setLoading(true) + const unsubscribe = window.electron.ipcRenderer.on('logging', (_event, reply) => { + console.log(reply) + if (reply.final) { + unsubscribe() + setLoading(false) + } else { + setLoading(true) + } + if (!reply.status) { + setStatus(reply.error || 'Unknown error occurred') + unsubscribe() + setLoading(false) + } else { + if (reply.data) { + setStatus(reply.data) + } + } + }) + } + const handleAddAndRunApp = async (): Promise => { window.electron.ipcRenderer.send('add-app', appData?.appId) window.electron.ipcRenderer.send('get-apps') @@ -89,6 +121,7 @@ const index = (): JSX.Element => { {loading ? (
+ {status &&

{status.trim()}

}
) : ( <> diff --git a/DeskThingServer/src/renderer/src/components/Apps/Tabs.tsx b/DeskThingServer/src/renderer/src/components/Apps/Tabs.tsx index 0862dd0..b907f63 100644 --- a/DeskThingServer/src/renderer/src/components/Apps/Tabs.tsx +++ b/DeskThingServer/src/renderer/src/components/Apps/Tabs.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import React from 'react' import { View } from '.' interface TopbarProps { @@ -7,32 +7,10 @@ interface TopbarProps { } const Tabs: React.FC = ({ setCurrentView, currentView }) => { - const [connections, setConnections] = React.useState(0) const handleClick = (view: View): void => { setCurrentView(view) } - const getConnections = (): void => window.electron.ipcRenderer.send('get-connections') - - useEffect(() => { - const handleConnection = (_event, num: number): void => { - setConnections(num) - console.log('got connections', num) - } - - console.log('got connections', connections) - const removeListener = window.electron.ipcRenderer.on('connections', handleConnection) - - const timeoutId = setTimeout(() => { - getConnections() - }, 1500) - - return () => { - removeListener() - clearTimeout(timeoutId) - } - }, []) - return (
diff --git a/DeskThingServer/src/renderer/src/components/Apps/Web.tsx b/DeskThingServer/src/renderer/src/components/Apps/Web.tsx index 01d501e..4fdd2a8 100644 --- a/DeskThingServer/src/renderer/src/components/Apps/Web.tsx +++ b/DeskThingServer/src/renderer/src/components/Apps/Web.tsx @@ -1,8 +1,14 @@ import { useEffect, useState } from 'react' -import { IconLogoGearLoading } from '../icons' +import { IconLogoLoading } from '../icons' import githubStore, { GithubRelease, GithubAsset } from '../../store/githubStore' import ReleaseList from '../ReleaseList' import RunPreppedApp from './RunPreppedApp' +interface responseData { + status: boolean + data: returnData + final: boolean + error?: string +} interface returnData { appId: string appName: string @@ -18,6 +24,7 @@ const Web = (): JSX.Element => { const [loading, setLoading] = useState(false) const [appData, setAppData] = useState(null) const [error, setError] = useState(null) + const [status, setStatus] = useState(null) useEffect(() => { const fetchData = async (): Promise => { @@ -36,15 +43,40 @@ const Web = (): JSX.Element => { return assets.filter((asset) => asset.name.includes('-app')) } + const handleLogging = async (): Promise => { + setLoading(true) + const unsubscribe = window.electron.ipcRenderer.on('logging', (_event, reply) => { + console.log(reply) + if (reply.final) { + unsubscribe() + setLoading(false) + } else { + setLoading(true) + } + if (!reply.status) { + setStatus(reply.error || 'Unknown error occurred') + unsubscribe() + setLoading(false) + } else { + if (reply.data) { + setStatus(reply.data) + } + } + }) + } + const handleAssetClick = async (asset: GithubAsset): Promise => { // Send the selected asset to Electron backend for extraction setLoading(true) try { window.electron.ipcRenderer.send('extract-app-zip-url', asset.browser_download_url) - window.electron.ipcRenderer.on('zip-name', (_event, data: returnData) => { + handleLogging() + window.electron.ipcRenderer.on('zip-name', (_event, response: responseData) => { setLoading(false) - setAppData(data) - console.log(data) + if (response.status) { + setAppData(response.data) + } + console.log(response) }) } catch (error) { setLoading(false) @@ -65,10 +97,9 @@ const Web = (): JSX.Element => { {!appData?.appId ? (
{loading ? ( -
-
- -
+
+ + {status &&

{status.trim()}

}
) : error ? (
diff --git a/DeskThingServer/src/renderer/src/components/ContentArea.tsx b/DeskThingServer/src/renderer/src/components/ContentArea.tsx index c7de1f9..b3cda7d 100644 --- a/DeskThingServer/src/renderer/src/components/ContentArea.tsx +++ b/DeskThingServer/src/renderer/src/components/ContentArea.tsx @@ -1,9 +1,9 @@ import React from 'react' import AppsList from './Apps' import Dev from './Dev' -import Device from './ADB' +import Adb from './ADB' +import Device from './Device' import LogDisplay from './LogDisplay' -import Loading from './Loading' type View = 'appsList' | 'adb' | 'logDisplay' | 'preferences' | 'dev' // Define possible views @@ -17,11 +17,11 @@ const ContentArea: React.FC = ({ currentView }) => { case 'appsList': return case 'adb': - return + return case 'logDisplay': return case 'preferences': - return + return case 'dev': return default: diff --git a/DeskThingServer/src/renderer/src/components/Dev/Adb.tsx b/DeskThingServer/src/renderer/src/components/Dev/Adb.tsx index 3c9a592..c0849bf 100644 --- a/DeskThingServer/src/renderer/src/components/Dev/Adb.tsx +++ b/DeskThingServer/src/renderer/src/components/Dev/Adb.tsx @@ -1,16 +1,5 @@ import { useState } from 'react' -import { - IconCarThing, - IconDisconnect, - IconLightbulbOff, - IconLightbulbOn, - IconLogoGearLoading, - IconPlay, - IconPower, - IconRefresh, - IconReload, - IconX -} from '../icons' +import { IconLogoGearLoading, IconPlay, IconRefresh } from '../icons' const Adb = (): JSX.Element => { const [type, setType] = useState('') diff --git a/DeskThingServer/src/renderer/src/components/Dev/Tabs.tsx b/DeskThingServer/src/renderer/src/components/Dev/Tabs.tsx index e38ed2c..b435669 100644 --- a/DeskThingServer/src/renderer/src/components/Dev/Tabs.tsx +++ b/DeskThingServer/src/renderer/src/components/Dev/Tabs.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import React from 'react' import { View } from '.' interface TopbarProps { @@ -7,32 +7,10 @@ interface TopbarProps { } const Tabs: React.FC = ({ setCurrentView, currentView }) => { - const [connections, setConnections] = React.useState(0) const handleClick = (view: View): void => { setCurrentView(view) } - const getConnections = (): void => window.electron.ipcRenderer.send('get-connections') - - useEffect(() => { - const handleConnection = (_event, num: number): void => { - setConnections(num) - console.log('got connections', num) - } - - console.log('got connections', connections) - const removeListener = window.electron.ipcRenderer.on('connections', handleConnection) - - const timeoutId = setTimeout(() => { - getConnections() - }, 1500) - - return () => { - removeListener() - clearTimeout(timeoutId) - } - }, []) - return (
diff --git a/DeskThingServer/src/renderer/src/components/Device/Devices.tsx b/DeskThingServer/src/renderer/src/components/Device/Devices.tsx new file mode 100644 index 0000000..729784f --- /dev/null +++ b/DeskThingServer/src/renderer/src/components/Device/Devices.tsx @@ -0,0 +1,91 @@ +import { useEffect, useState } from 'react' +import { IconCarThing } from '../icons' +import Key from './Key' + +export type Button = { + name: string + description: string + source: string +} + +export type key = { + id: string + source: string +} + +export type ButtonMapping = { + [key: string]: Button +} + +type FileStructure = { + default: ButtonMapping + [key: string]: ButtonMapping | Button[] | key[] | string + functions: Button[] + keys: key[] + version: string +} + +const Device = (): JSX.Element => { + const [mapping, setMapping] = useState({}) + const [buttons, setButtons] = useState([]) + const [functions, setFunctions] = useState([]) + const [isVisible, setIsVisible] = useState(false) + const [currentKey, setCurrentKey] = useState('') + + useEffect(() => { + const loadMapping = async (): Promise => { + const mappings = (await window.electron.getMaps()) as FileStructure + console.log(mappings) + setMapping(mappings.default) + setFunctions(mappings.functions) + setButtons(mappings.keys) + } + + loadMapping() + }, []) + + const handleButtonSelect = async (button: Button): Promise => { + const newMapping = { + ...mapping, + [currentKey]: button + } + console.log('Sending mappings') + setMapping(newMapping) + setIsVisible(false) + await window.electron.setMaps('default', newMapping) + } + const handleSelect = async (selectedKey: string): Promise => { + setCurrentKey(selectedKey) + setIsVisible(true) + } + + return ( +
+
+ {isVisible && + functions.map((f, index) => ( + + ))} +
+
+ {buttons.map((button) => ( + + ))} +
+ +
+ ) +} + +export default Device diff --git a/DeskThingServer/src/renderer/src/components/Device/Key.tsx b/DeskThingServer/src/renderer/src/components/Device/Key.tsx new file mode 100644 index 0000000..ca5e380 --- /dev/null +++ b/DeskThingServer/src/renderer/src/components/Device/Key.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import { Button } from './Devices' + +interface KeyProps { + keyName: string + currentFunction: Button + onSelect: (key: string) => void +} + +const Key: React.FC = ({ keyName, currentFunction, onSelect }) => { + const handleButtonClick = (): void => { + onSelect(keyName) + } + + return ( +
+ +
+ ) +} + +export default Key diff --git a/DeskThingServer/src/renderer/src/components/Device/Tabs.tsx b/DeskThingServer/src/renderer/src/components/Device/Tabs.tsx new file mode 100644 index 0000000..8e60d7a --- /dev/null +++ b/DeskThingServer/src/renderer/src/components/Device/Tabs.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import { View } from '.' + +interface TopbarProps { + setCurrentView: React.Dispatch> + currentView: View +} + +const Tabs: React.FC = ({ setCurrentView, currentView }) => { + const handleClick = (view: View): void => { + setCurrentView(view) + } + + return ( +
+
+ + +
+
+ ) +} + +export default Tabs diff --git a/DeskThingServer/src/renderer/src/components/Device/index.tsx b/DeskThingServer/src/renderer/src/components/Device/index.tsx new file mode 100644 index 0000000..90daeb0 --- /dev/null +++ b/DeskThingServer/src/renderer/src/components/Device/index.tsx @@ -0,0 +1,29 @@ +import { useState } from 'react' +import Tabs from './Tabs' +import Devices from './Devices' +import Loading from '../Loading' + +export type View = 'mappings' | 'status' + +const Index = (): JSX.Element => { + const [currentView, setCurrentView] = useState('mappings') + const renderView = (): JSX.Element | undefined => { + switch (currentView) { + case 'mappings': + return + case 'status': + return + default: + return undefined + } + } + + return ( + <> + +
{renderView()}
+ + ) +} + +export default Index diff --git a/DeskThingServer/src/renderer/src/components/Loading.tsx b/DeskThingServer/src/renderer/src/components/Loading.tsx index 34df8a5..61ca59f 100644 --- a/DeskThingServer/src/renderer/src/components/Loading.tsx +++ b/DeskThingServer/src/renderer/src/components/Loading.tsx @@ -7,7 +7,7 @@ interface LoadingProps { const Loading: React.FC = ({ message }) => { return ( -
+
{IconLogoLoading && } {message &&

{message}

}
diff --git a/DeskThingServer/src/renderer/src/components/Overlays/DisplayDeviceData.tsx b/DeskThingServer/src/renderer/src/components/Overlays/DisplayDeviceData.tsx index 116bc61..8c49a0a 100644 --- a/DeskThingServer/src/renderer/src/components/Overlays/DisplayDeviceData.tsx +++ b/DeskThingServer/src/renderer/src/components/Overlays/DisplayDeviceData.tsx @@ -28,6 +28,7 @@ const DisplayDeviceData = ({ setEnabled, device }: DisplayDeviceDataProps): JSX. const [supervisorData, setSupervisorData] = useState<{ [key: string]: string }>({}) const [tooltip, setTooltip] = useState('') const [weToolTip, setWStooltip] = useState('') + const [loading, setIsLoading] = useState(false) const getSupervisorData = async (): Promise => { const supervisorResponse = await window.electron.runAdbCommand( @@ -43,7 +44,10 @@ const DisplayDeviceData = ({ setEnabled, device }: DisplayDeviceDataProps): JSX. parsedData[name] = status } }) + setIsLoading(false) setSupervisorData(parsedData) + } else { + setIsLoading(false) } } @@ -127,24 +131,50 @@ const DisplayDeviceData = ({ setEnabled, device }: DisplayDeviceDataProps): JSX. if (type == 'adb') { const response = await window.electron.runAdbCommand(command) console.log(response) + handleLogging() } else { await window.electron.runDeviceCommand(type, command) + handleLogging() } } } + const handleLogging = async (): Promise => { + setIsLoading(true) + const unsubscribe = window.electron.ipcRenderer.on('logging', (_event, reply) => { + console.log(reply) + if (reply.final) { + unsubscribe() + getSupervisorData() + } else { + setIsLoading(true) + } + if (!reply.status) { + setTooltip(reply.error || 'Unknown error occurred') + unsubscribe() + setIsLoading(false) + } else { + if (reply.data) { + setTooltip(reply.data) + } + } + }) + } + const handleRestart = async (): Promise => { await window.electron.runAdbCommand('shell reboot') + handleLogging() handleExit() } const handlePowerOff = async (): Promise => { await window.electron.runDeviceCommand('post', '{"type":"device","action":"reboot"}') + handleLogging() handleExit() } const handleAdbCommand = async (command: string): Promise => { try { - window.electron.runAdbCommand(command) - getSupervisorData() + window.electron.runAdbCommand((deviceData?.usid ? `-s ${deviceData?.usid} ` : '') + command) + handleLogging() } catch (Error) { console.log(Error) return undefined @@ -188,24 +218,22 @@ const DisplayDeviceData = ({ setEnabled, device }: DisplayDeviceDataProps): JSX.

{tooltip}

) : ( )}
diff --git a/DeskThingServer/src/renderer/src/components/Sidebar.tsx b/DeskThingServer/src/renderer/src/components/Sidebar.tsx index d3d1a31..1d23dc0 100644 --- a/DeskThingServer/src/renderer/src/components/Sidebar.tsx +++ b/DeskThingServer/src/renderer/src/components/Sidebar.tsx @@ -41,7 +41,9 @@ const Sidebar: React.FC = ({ setCurrentView, currentView }) => { } console.log('got connections', connections) - const removeListener = window.electron.ipcRenderer.on('connections', handleConnection) + const removeListener = window.electron.ipcRenderer.on('connections', (event, data) => + handleConnection(event, data.data) + ) const timeoutId = setTimeout(() => { getConnections() @@ -94,7 +96,7 @@ const Sidebar: React.FC = ({ setCurrentView, currentView }) => { className={`${currentView === 'preferences' ? 'bg-zinc-800 hover:bg-zinc-700 border-green-500' : 'hover:bg-zinc-900'} sm:border-l rounded-md flex gap-3 w-full p-3`} onClick={() => handleClick('preferences')} > - + Device diff --git a/DeskThingServer/src/renderer/src/components/icons/icon/Icons/IconCarThing.tsx b/DeskThingServer/src/renderer/src/components/icons/icon/Icons/IconCarThing.tsx index 3b884dd..f41d5ac 100644 --- a/DeskThingServer/src/renderer/src/components/icons/icon/Icons/IconCarThing.tsx +++ b/DeskThingServer/src/renderer/src/components/icons/icon/Icons/IconCarThing.tsx @@ -3,7 +3,7 @@ import { Icon } from '..' function IconCarThing(props): JSX.Element { const strokeWidth = props.strokeWidth || 2 const highlighted = props.highlighted || [] - const highlightColor = props.highlightColor || [] + const highlightColor = props.highlightColor || '' const svgContent = ` + fill="${highlighted.includes('button1') ? highlightColor : 'currentColor'}" + stroke="${highlighted.includes('button1') ? highlightColor : 'currentColor'}" + /> + + + ${props.text || ''}