diff --git a/DeskThingServer/src/main/handlers/musicHandler.ts b/DeskThingServer/src/main/handlers/musicHandler.ts new file mode 100644 index 00000000..dfcdc25d --- /dev/null +++ b/DeskThingServer/src/main/handlers/musicHandler.ts @@ -0,0 +1,99 @@ +import dataListener, { MESSAGE_TYPES } from '../utils/events' +import settingsStore from '../stores/settingsStore' +import { Settings, SocketData } from '@shared/types' +import { sendMessageToApp } from '../services/apps' +import { getAppByName } from './configHandler' + +export class MusicHandler { + private static instance: MusicHandler + private refreshInterval: NodeJS.Timeout | null = null + private currentApp: string | null = null + + private constructor() { + this.initializeRefreshInterval() + } + + public static getInstance(): MusicHandler { + if (!MusicHandler.instance) { + MusicHandler.instance = new MusicHandler() + } + return MusicHandler.instance + } + + private async initializeRefreshInterval(): Promise { + const settings = await settingsStore.getSettings() // Get from your settings store + this.updateRefreshInterval(settings.refreshInterval) + dataListener.on(MESSAGE_TYPES.SETTINGS, this.handleSettingsUpdate) + } + + private handleSettingsUpdate = (settings: Settings): void => { + this.updateRefreshInterval(settings.refreshInterval) + + if (settings.playbackLocation && settings.playbackLocation !== this.currentApp) { + this.currentApp = settings.playbackLocation + } + } + + public updateRefreshInterval(refreshRate: number): void { + if (this.refreshInterval) { + clearInterval(this.refreshInterval) + } + + if (refreshRate < 0) { + dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `[MusicHandler]: Cancelling Refresh Interval!`) + return + } + + this.refreshInterval = setInterval(() => { + this.refreshMusicData() + }, refreshRate) + } + + private async refreshMusicData(): Promise { + if (!this.currentApp || this.currentApp.length == 0) { + dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `[MusicHandler]: No current app set!`) + return + } + + const app = await getAppByName(this.currentApp) + + if (!app || app.running == false) { + dataListener.asyncEmit( + MESSAGE_TYPES.ERROR, + `[MusicHandler]: App ${this.currentApp} not found or not running!!` + ) + } + + try { + await sendMessageToApp(this.currentApp, { type: 'get', request: 'refresh', payload: '' }) + dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `[MusicHandler]: Refreshing Music Data!`) + } catch (error) { + dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `[MusicHandler]: Music refresh failed: ${error}`) + } + } + + public async handleClientRequest(request: SocketData): Promise { + if (!this.currentApp) return + if (request.app != 'music' && request.app != 'utility') return + + if (request.app == 'utility') { + dataListener.asyncEmit( + MESSAGE_TYPES.LOGGING, + `[MusicHandler]: Legacy Name called! Support for this will be dropped in future updates. Migrate your app to use 'music' instead!` + ) + } + + dataListener.asyncEmit( + MESSAGE_TYPES.LOGGING, + `[MusicHandler]: ${request.type} ${request.request}` + ) + + sendMessageToApp(this.currentApp, { + type: request.type, + request: request.request, + payload: request.payload + }) + } +} + +export default MusicHandler.getInstance() diff --git a/DeskThingServer/src/main/services/client/websocket.ts b/DeskThingServer/src/main/services/client/websocket.ts index 83b08c27..dbe2281c 100644 --- a/DeskThingServer/src/main/services/client/websocket.ts +++ b/DeskThingServer/src/main/services/client/websocket.ts @@ -22,6 +22,7 @@ import { sendSettingsData } from './clientCom' import { sendIpcData } from '../..' +import MusicHandler from '../../handlers/musicHandler' export let server: WebSocketServer | null = null export let httpServer: HttpServer @@ -177,14 +178,23 @@ export const setupServer = async (): Promise => { messageThrottles.set(messageKey, now) // Handle non-server messages - if (messageData.app && messageData.app !== 'server') { + if ( + messageData.app && + messageData.app !== 'server' && + messageData.app !== 'utility' && + messageData.app !== 'music' + ) { sendMessageToApp(messageData.app.toLowerCase(), { type: messageData.type, request: messageData.request, payload: messageData.payload }) } else if (messageData.app === 'server') { + // Handle server requests handleServerMessage(socket, client, messageData) + } else if (messageData.app === 'utility' || messageData.app === 'music') { + // Handle music requests + MusicHandler.handleClientRequest(messageData) } // Cleanup throttle diff --git a/DeskThingServer/src/main/stores/settingsStore.ts b/DeskThingServer/src/main/stores/settingsStore.ts index 61ebaf76..f14e9ada 100644 --- a/DeskThingServer/src/main/stores/settingsStore.ts +++ b/DeskThingServer/src/main/stores/settingsStore.ts @@ -3,8 +3,8 @@ import dataListener, { MESSAGE_TYPES } from '../utils/events' import os from 'os' import { Settings } from '@shared/types' -const settingsVersion = '0.9.0' -const version_code = 9.0 +const settingsVersion = '0.9.1' +const version_code = 9.1 class SettingsStore { private settings: Settings @@ -116,6 +116,8 @@ class SettingsStore { minimizeApp: true, globalADB: false, autoDetectADB: false, + refreshInterval: -1, + playbackLocation: undefined, localIp: getLocalIpAddress(), appRepos: ['https://github.com/ItsRiprod/deskthing-apps'], clientRepos: ['https://github.com/ItsRiprod/deskthing-client'] diff --git a/DeskThingServer/src/renderer/src/assets/icons/icon/IconMusic.tsx b/DeskThingServer/src/renderer/src/assets/icons/icon/IconMusic.tsx new file mode 100644 index 00000000..da147432 --- /dev/null +++ b/DeskThingServer/src/renderer/src/assets/icons/icon/IconMusic.tsx @@ -0,0 +1,21 @@ +import { Icon } from '.' + +function IconMusic(props): JSX.Element { + return ( + + + + + + + ) +} + +export default IconMusic diff --git a/DeskThingServer/src/renderer/src/assets/icons/icon/IconStop.tsx b/DeskThingServer/src/renderer/src/assets/icons/icon/IconStop.tsx new file mode 100644 index 00000000..4afecaf3 --- /dev/null +++ b/DeskThingServer/src/renderer/src/assets/icons/icon/IconStop.tsx @@ -0,0 +1,18 @@ +import { Icon, IconProps } from '.' + +function IconStop(props: IconProps): JSX.Element { + return ( + + + + + + ) +} + +export default IconStop diff --git a/DeskThingServer/src/renderer/src/assets/icons/icon/IconTrash.tsx b/DeskThingServer/src/renderer/src/assets/icons/icon/IconTrash.tsx new file mode 100644 index 00000000..9b61ad7a --- /dev/null +++ b/DeskThingServer/src/renderer/src/assets/icons/icon/IconTrash.tsx @@ -0,0 +1,25 @@ +import { Icon } from '.' + +function IconTrash(props): JSX.Element { + return ( + + + + + + + + + + + + + ) +} + +export default IconTrash diff --git a/DeskThingServer/src/renderer/src/assets/icons/index.ts b/DeskThingServer/src/renderer/src/assets/icons/index.ts index 1778ad3f..d7eb53e5 100644 --- a/DeskThingServer/src/renderer/src/assets/icons/index.ts +++ b/DeskThingServer/src/renderer/src/assets/icons/index.ts @@ -32,6 +32,8 @@ export { default as IconLogoGear } from './icon/IconLogoGear' export { default as IconLogo } from './icon/IconLogo' export { default as IconLogs } from './icon/IconLogs' export { default as IconLink } from './icon/IconLink' +export { default as IconMusic } from './icon/IconMusic' +export { default as IconTrash } from './icon/IconTrash' export { default as IconToggle } from './icon/IconToggle' export { default as IconMobile } from './icon/IconMobile' export { default as IconCheckCircle } from './icon/IconCheckCircle' @@ -42,15 +44,13 @@ export { default as IconFolderOpen } from './icon/IconFolderOpen' export { default as IconComputer } from './icon/IconComputer' export { default as IconX } from './icon/IconX' export { default as IconGlobe } from './icon/IconGlobe' - +export { default as IconStop } from './icon/IconStop' export { default as IconEye } from './icon/IconEye' - export { default as IconPulsing } from './icon/IconPulsing' export { default as IconReload } from './icon/IconReload' export { default as IconSave } from './icon/IconSave' export { default as IconServer } from './icon/IconServer' export { default as IconDetails } from './icon/IconDetails' - export { default as IconGear } from './icon/IconGear' export { default as IconHeart } from './icon/IconHeart' export { default as IconHeartActive } from './icon/IconHeartActive' diff --git a/DeskThingServer/src/renderer/src/components/Connection.tsx b/DeskThingServer/src/renderer/src/components/Connection.tsx index 9247b04d..7ce26ad6 100644 --- a/DeskThingServer/src/renderer/src/components/Connection.tsx +++ b/DeskThingServer/src/renderer/src/components/Connection.tsx @@ -200,7 +200,7 @@ const ConnectionComponent: React.FC = ({ client }) => {!client.connected && offline ? (

Device Offline!

) : ( -

Not Connected!

+ !client.connected &&

Not Connected!

)} {client.adbId && !offline && ( <> diff --git a/DeskThingServer/src/renderer/src/overlays/apps/AppActions.tsx b/DeskThingServer/src/renderer/src/overlays/apps/AppActions.tsx index 3030b006..5923c1a2 100644 --- a/DeskThingServer/src/renderer/src/overlays/apps/AppActions.tsx +++ b/DeskThingServer/src/renderer/src/overlays/apps/AppActions.tsx @@ -1,23 +1,72 @@ import React from 'react' import { AppSettingProps } from './AppsOverlay' import Button from '@renderer/components/Button' -import { IconPlay, IconX } from '@renderer/assets/icons' +import { IconPlay, IconStop, IconTrash, IconX } from '@renderer/assets/icons' +import { useAppStore } from '@renderer/stores' const AppActions: React.FC = ({ app }: AppSettingProps) => { + const stopApp = useAppStore((state) => state.stopApp) + const runApp = useAppStore((state) => state.runApp) + const disableApp = useAppStore((state) => state.disableApp) + const enableApp = useAppStore((state) => state.enableApp) + const handlePurge = (): void => { window.electron.purgeApp(app.name) } return ( -
- - +
+
+ + + {app.enabled ? ( + + ) : ( + + )} + {app.running ? ( + + ) : ( + + )} +
+
+

+ {app.manifest?.label || app.name} is {app.running ? 'running' : 'not running'} +

+

App will {app.enabled ? 'Start Automatically' : 'Not Start Automatically'}

+
) } diff --git a/DeskThingServer/src/renderer/src/overlays/apps/AppDetails.tsx b/DeskThingServer/src/renderer/src/overlays/apps/AppDetails.tsx index 07a0561c..90a472c2 100644 --- a/DeskThingServer/src/renderer/src/overlays/apps/AppDetails.tsx +++ b/DeskThingServer/src/renderer/src/overlays/apps/AppDetails.tsx @@ -6,72 +6,77 @@ const AppDetails: React.FC = ({ app }: AppSettingProps) => { return (
-
-
-
-

Is Audio Source:

-

{app.manifest.isAudioSource ? 'Yes' : 'No'}

-
-
-

Requires:

-
    - {app.manifest.requires.map((req, index) => ( -
  • {req}
  • +
    +
    + +
    + {app.manifest.requires.map((required, index) => ( +
    +

    {required}

    +
    ))} -
-
-
-

Description:

-

{app.manifest.description || 'N/A'}

-
-
-

Author:

-

{app.manifest.author || 'N/A'}

-
-
-

ID:

-

{app.manifest.id}

-
-
-

Is Web App:

-

{app.manifest.isWebApp ? 'Yes' : 'No'}

-
-
-

Is Screen Saver:

-

{app.manifest.isScreenSaver ? 'Yes' : 'No'}

-
-
-

Is Local App:

-

{app.manifest.isLocalApp ? 'Yes' : 'No'}

-
-
-

Platforms:

-
    +
+ + + + + + + + + + +
{app.manifest.platforms.map((platform, index) => ( -
  • {platform}
  • +
    +

    {platform}

    +
    ))} - -
    -
    -

    Homepage:

    - {app.manifest.homepage && ( - - {app.manifest.homepage || 'N/A'} - - )} -
    -
    -

    Repository:

    - {app.manifest.repository && ( - - {app.manifest.repository || 'N/A'} - - )} -
    +
    + + + + {app.manifest.homepage} + + + + + {app.manifest.homepage} + +
    ) } +interface AppDetailsInterface { + value?: string + title: string + children?: React.ReactNode +} + +const AppDetail: React.FC = ({ value, title, children }) => { + if (value) { + return ( +
    +

    {title}

    + {children ||

    {value}

    } +
    + ) + } else { + return null + } +} + export default AppDetails diff --git a/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx b/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx index 7fbf2237..dd636452 100644 --- a/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx +++ b/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx @@ -60,14 +60,16 @@ const AppSettings: React.FC = ({ app }) => { return (
    - handleSettingChange(key, Number(e.target.value))} - className={commonClasses} - /> + {setting.type == 'number' && ( + handleSettingChange(key, Number(e.target.value))} + className={commonClasses} + /> + )}
    ) @@ -86,39 +88,47 @@ const AppSettings: React.FC = ({ app }) => { case 'select': return ( - + {setting.type == 'select' && ( + + )} ) case 'multiselect': return ( -
    - {setting.options?.map((option, index) => ( - - ))} -
    + {setting.type == 'multiselect' && ( +
    + {setting.options?.map((option, index) => ( + + ))} +
    + )}
    ) default: diff --git a/DeskThingServer/src/renderer/src/overlays/settings/ClientSettings.tsx b/DeskThingServer/src/renderer/src/overlays/settings/ClientSettings.tsx index 10b5c4c8..d0e8227e 100644 --- a/DeskThingServer/src/renderer/src/overlays/settings/ClientSettings.tsx +++ b/DeskThingServer/src/renderer/src/overlays/settings/ClientSettings.tsx @@ -1,26 +1,43 @@ -import React from 'react' +import React, { useState } from 'react' import { useClientStore, useAppStore } from '@renderer/stores' -import { Client } from '@shared/types' +import { ClientManifest } from '@shared/types' +import Button from '@renderer/components/Button' +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) - const handleSettingChange = (key: keyof Client, value: string | boolean | number): void => { - if (clientSettings) { - updateClientSettings({ ...clientSettings, [key]: value }) + const handleSettingChange = ( + key: keyof ClientManifest, + value: string | boolean | number + ): void => { + if (localSettings) { + setLocalSettings({ ...localSettings, [key]: value }) } } - if (!clientSettings) return null + const handleSave = async (): Promise => { + if (localSettings) { + setLoading(true) + updateClientSettings(localSettings) + setTimeout(() => { + setLoading(false) + }, 500) + } + } + + if (!localSettings) return null return ( -
    +

    Default View

    handleSettingChange('miniplayer', e.target.value)} className="focus:text-white bg-zinc-900 text-white rounded px-2 py-2" > @@ -47,22 +64,52 @@ const ClientSettings: React.FC = () => {

    IP Address

    - handleSettingChange('ip', e.target.value)} - className="focus:text-white bg-zinc-900 text-white rounded px-2 py-2" - /> +
    + handleSettingChange('ip', e.target.value)} + className="focus:text-white bg-zinc-900 text-white rounded px-2 py-2" + /> +

    Port

    handleSettingChange('port', Number(e.target.value))} className="focus:text-white bg-zinc-900 text-white rounded px-2 py-2" />
    +
    +

    Use RNDIS

    + +
    +
    + +
    ) } diff --git a/DeskThingServer/src/renderer/src/overlays/settings/DeviceSettings.tsx b/DeskThingServer/src/renderer/src/overlays/settings/DeviceSettings.tsx index 0ae70067..be75b451 100644 --- a/DeskThingServer/src/renderer/src/overlays/settings/DeviceSettings.tsx +++ b/DeskThingServer/src/renderer/src/overlays/settings/DeviceSettings.tsx @@ -38,7 +38,7 @@ const DeviceSettings: React.FC = () => { } return ( -
    +

    Auto Detect ADB

    +
    +
    +
    +

    Playback Sources

    + +
    +
    + +
    +
    + ) +} + +export default MusicSettings diff --git a/DeskThingServer/src/renderer/src/overlays/settings/SettingsOverlay.tsx b/DeskThingServer/src/renderer/src/overlays/settings/SettingsOverlay.tsx index a8111395..9c927418 100644 --- a/DeskThingServer/src/renderer/src/overlays/settings/SettingsOverlay.tsx +++ b/DeskThingServer/src/renderer/src/overlays/settings/SettingsOverlay.tsx @@ -1,16 +1,18 @@ import React from 'react' import Overlay from '../Overlay' -import { IconCarThingSmall, IconComputer, IconServer } from '@renderer/assets/icons' +import { IconCarThingSmall, IconComputer, IconMusic, IconServer } from '@renderer/assets/icons' import Button from '@renderer/components/Button' import ClientSettings from './ClientSettings' import DeviceSettings from './DeviceSettings' import ServerSettings from './ServerSettings' import { useSearchParams } from 'react-router-dom' +import MusicSettings from './MusicSettings' const settingsPages = [ { key: 'server', label: 'Server', Icon: IconServer }, { key: 'client', label: 'Client', Icon: IconComputer }, - { key: 'device', label: 'Device', Icon: IconCarThingSmall } + { key: 'device', label: 'Device', Icon: IconCarThingSmall }, + { key: 'music', label: 'Music', Icon: IconMusic } ] /** @@ -60,6 +62,7 @@ const SettingsOverlay: React.FC = () => { {currentPage == 'client' && } {currentPage == 'device' && } {currentPage == 'server' && } + {currentPage == 'music' && }
    diff --git a/DeskThingServer/src/renderer/src/pages/Clients/Connections.tsx b/DeskThingServer/src/renderer/src/pages/Clients/Connections.tsx index f831d6c4..5a3db499 100644 --- a/DeskThingServer/src/renderer/src/pages/Clients/Connections.tsx +++ b/DeskThingServer/src/renderer/src/pages/Clients/Connections.tsx @@ -18,6 +18,7 @@ const ClientConnections: React.FC = () => { const settings = useSettingsStore((settings) => settings.settings) const saveSettings = useSettingsStore((settings) => settings.saveSettings) const clients = useClientStore((clients) => clients.clients) + const stagedClient = useClientStore((clients) => clients.clientManifest) const devices = useClientStore((clients) => clients.ADBDevices) const refreshClients = useClientStore((clients) => clients.requestADBDevices) const setPage = usePageStore((pageStore) => pageStore.setPage) @@ -96,6 +97,17 @@ const ClientConnections: React.FC = () => { settings.localIp.map((ip, index) => (
    {ip + ':' + settings.devicePort}
    ))} +
    +

    Staged Client

    + {stagedClient ? ( + <> +

    {stagedClient.name}

    +

    Version: {stagedClient.version}

    + + ) : ( +

    No Client Downloaded

    + )} +
    diff --git a/DeskThingServer/src/renderer/src/pages/Dev/Logs.tsx b/DeskThingServer/src/renderer/src/pages/Dev/Logs.tsx index bfa8a634..7934e3df 100644 --- a/DeskThingServer/src/renderer/src/pages/Dev/Logs.tsx +++ b/DeskThingServer/src/renderer/src/pages/Dev/Logs.tsx @@ -54,30 +54,6 @@ const Logs: React.FC = () => {
    -
    -

    Filters

    - - - -
    -

    Logs

    +
    + + + +
    diff --git a/DeskThingServer/src/renderer/src/stores/settingsStore.ts b/DeskThingServer/src/renderer/src/stores/settingsStore.ts index 8614554c..ce8b881b 100644 --- a/DeskThingServer/src/renderer/src/stores/settingsStore.ts +++ b/DeskThingServer/src/renderer/src/stores/settingsStore.ts @@ -23,6 +23,8 @@ const useSettingsStore = create((set, get) => ({ autoConfig: false, globalADB: false, autoDetectADB: false, + refreshInterval: -1, + playbackLocation: undefined, localIp: ['-.-.-.-'], appRepos: ['https://github.com/ItsRiprod/deskthing-apps'], clientRepos: ['https://github.com/ItsRiprod/deskthing-client'] diff --git a/DeskThingServer/src/shared/types/app.ts b/DeskThingServer/src/shared/types/app.ts index 8113471a..d6e2c748 100644 --- a/DeskThingServer/src/shared/types/app.ts +++ b/DeskThingServer/src/shared/types/app.ts @@ -127,12 +127,13 @@ export interface SettingsSelect { } export interface SettingsMultiSelect { - value: boolean[] + value: string[] type: 'multiselect' label: string description?: string options: { label: string + value: string }[] } diff --git a/DeskThingServer/src/shared/types/types.ts b/DeskThingServer/src/shared/types/types.ts index e3fec26c..d70369bd 100644 --- a/DeskThingServer/src/shared/types/types.ts +++ b/DeskThingServer/src/shared/types/types.ts @@ -146,6 +146,8 @@ export interface Settings { appRepos: string[] autoDetectADB: boolean clientRepos: string[] + playbackLocation?: string + refreshInterval: number [key: string]: any // For any additional settings }