Skip to content

Commit 565346b

Browse files
committed
Added FILE setting type + RANGE slider fix
1 parent 5140fd9 commit 565346b

File tree

12 files changed

+153
-73
lines changed

12 files changed

+153
-73
lines changed

DeskThingServer/package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

DeskThingServer/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "deskthing",
3-
"version": "0.11.9",
3+
"version": "0.11.10",
44
"description": "A DeskThing server UI to interface with the DeskThing car thing app",
55
"main": "./out/main/index.js",
66
"author": "Riprod",
@@ -51,7 +51,7 @@
5151
"zustand": "^5.0.0-rc.2"
5252
},
5353
"devDependencies": {
54-
"@deskthing/types": "^0.11.8",
54+
"@deskthing/types": "^0.11.11",
5555
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
5656
"@electron-toolkit/eslint-config-ts": "^3.0.0",
5757
"@electron-toolkit/preload": "^3.0.1",

DeskThingServer/src/main/services/apps/appValidator.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import {
1818
AppDataInterface,
1919
PlatformTypes,
2020
TagTypes,
21-
AppLatestJSONLatest
21+
AppLatestJSONLatest,
22+
SettingsFile
2223
} from '@deskthing/types'
2324
import { AppData, LegacyAppData } from '@shared/types'
2425

@@ -97,6 +98,8 @@ export const isValidSettings: (setting: unknown) => asserts setting is SettingsT
9798
if (typeof typedSetting.value !== 'string')
9899
throw new Error('[isValidSetting] Color setting value must be a string')
99100
break
101+
case SETTING_TYPES.FILE:
102+
break
100103
default:
101104
throw new Error(`[isValidSetting] Invalid setting type: ${JSON.stringify(typedSetting)}`)
102105
}
@@ -199,6 +202,15 @@ export const sanitizeSettings: (setting: Partial<SettingsType>) => SettingsType
199202
description: setting.description || ''
200203
} as SettingsColor
201204
break
205+
case SETTING_TYPES.FILE:
206+
setting = {
207+
type: SETTING_TYPES.FILE,
208+
value: setting.value,
209+
label: setting.label,
210+
description: setting.description || '',
211+
fileTypes: setting.fileTypes || []
212+
} as SettingsFile
213+
break
202214
default:
203215
throw new Error(`[isValidSetting] Unknown setting type: ${setting}`)
204216
}

DeskThingServer/src/main/services/ipc/utilityIpc.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ export const utilityHandler: {
6969
return await supporterStore.fetchSupporters(data.payload)
7070
}
7171
},
72+
[IPC_UTILITY_TYPES.OPEN_DIALOG]: async (data) => {
73+
switch (data.type) {
74+
case IPC_UTILITY_TYPES.OPEN_DIALOG:
75+
return await dialog.showOpenDialog(data.payload)
76+
}
77+
},
7278
// [IPC_UTILITY_TYPES.GITHUB]: async (data) => {
7379
// const releaseStore = await storeProvider.getStore('releaseStore')
7480
// switch (data.request) {

DeskThingServer/src/main/services/releases/releaseUtils.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -316,9 +316,8 @@ export const addRepositoryUrl = async (
316316

317317
const githubReleases = await githubStore.getAllReleases(validatedUrl)
318318

319-
const latestGithubRelease = githubReleases[0]
320-
321-
const latestJson = findJsonAsset(latestGithubRelease, appId || 'latest')
319+
const latestJson = findFirstJsonAsset(githubReleases, appId || 'latest')
320+
const firstZipAsset = findFirstZipAsset(githubReleases, appId || 'latest')
322321

323322
// Handle the recovery with the file
324323
if (!latestJson) {
@@ -367,6 +366,10 @@ export const addRepositoryUrl = async (
367366
const pastReleases = collectPastReleases(githubReleases, migratedRelease.appManifest.id)
368367
const totalDownloads = pastReleases.reduce((sum, release) => sum + release.downloads, 0)
369368

369+
if (firstZipAsset) {
370+
migratedRelease.downloads = firstZipAsset.download_count
371+
}
372+
370373
const appServer: AppLatestServer = {
371374
id: migratedRelease.appManifest.id,
372375
type: 'app',
@@ -390,6 +393,10 @@ export const addRepositoryUrl = async (
390393
const pastReleases = collectPastReleases(githubReleases, migratedRelease.clientManifest.id)
391394
const totalDownloads = pastReleases.reduce((sum, release) => sum + release.downloads, 0)
392395

396+
if (firstZipAsset) {
397+
migratedRelease.downloads = firstZipAsset.download_count
398+
}
399+
393400
const clientServer: ClientLatestServer = {
394401
id: migratedRelease.clientManifest.id,
395402
type: 'client',
@@ -607,6 +614,22 @@ export const findZipAsset = (release: GithubRelease, appId: string): GithubAsset
607614
)
608615
}
609616

617+
export const findAllZipAssets = (
618+
release: GithubRelease,
619+
appId: string
620+
): GithubAsset[] | undefined => {
621+
// This ensures the latest (if there are multiple similar ones) is always chosen first
622+
const sortedAssets = release.assets.sort(
623+
(a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
624+
)
625+
626+
// This may inflate apps that have multiple types (i.e. weather will have all weather assets AND all weatherwaves assets )
627+
return sortedAssets.filter(
628+
(asset) =>
629+
asset.name.includes(appId) && (asset.name.endsWith('.zip') || asset.name.endsWith('.tar.gz'))
630+
)
631+
}
632+
610633
type ConversionReturnData =
611634
| { repos: string[]; type: 'converted-clients'; releases: ClientLatestServer[] }
612635
| { repos: string[]; type: 'converted-apps'; releases: AppLatestServer[] }
@@ -704,7 +727,7 @@ export const convertIdToReleaseServer = async (
704727
// Find first release containing the app ID
705728

706729
const firstJsonAsset = findFirstJsonAsset(ghReleases, appId)
707-
730+
const firstZipAsset = findFirstZipAsset(ghReleases, appId)
708731
let mainRelease: AppLatestJSONLatest | ClientLatestJSONLatest | MultiReleaseJSONLatest
709732

710733
// This will split the logic between migration logic and up-to-date logic
@@ -748,6 +771,8 @@ export const convertIdToReleaseServer = async (
748771
// Calculate total downloads
749772
const totalDownloads = pastReleases.reduce((sum, release) => sum + release.downloads, 0)
750773

774+
if (firstZipAsset) mainRelease.downloads = firstZipAsset.download_count
775+
751776
// This is the way it must be done for type-safety despite the fact that it is the same as just a single return - oh well
752777
if (mainRelease.meta_type === 'app') {
753778
return {
@@ -772,21 +797,21 @@ export const convertIdToReleaseServer = async (
772797

773798
export const collectPastReleases = (
774799
ghReleases: GithubRelease[],
775-
appId: string
800+
fileId: string
776801
): PastReleaseInfo[] => {
777802
return ghReleases.flatMap((release) => {
778-
const zipAsset = findZipAsset(release, appId)
779-
if (!zipAsset) return []
803+
const zipAssets = findAllZipAssets(release, fileId)
804+
if (!zipAssets) return []
780805

781-
return [
782-
{
806+
return zipAssets.map((zipAsset) => {
807+
return {
783808
tag: release.tag_name,
784809
downloads: zipAsset.download_count,
785810
size: zipAsset.size,
786811
name: zipAsset.name,
787812
download_url: zipAsset.browser_download_url,
788813
created_at: release.created_at
789814
}
790-
]
815+
})
791816
})
792817
}

DeskThingServer/src/preload/api/ipcUtility.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
UtilityHandlerReturnMap
1818
} from '@shared/types'
1919
import { PaginatedResponse, SupporterData, SupporterFetchOptions } from '@shared/types/supporter'
20-
import { ipcRenderer } from 'electron'
20+
import { ipcRenderer, OpenDialogOptions, OpenDialogReturnValue } from 'electron'
2121

2222
export const utility = {
2323
ping: async (): Promise<string> =>
@@ -81,6 +81,13 @@ export const utility = {
8181
type: IPC_UTILITY_TYPES.ZIP
8282
}),
8383

84+
showOpenDialog: async (options: OpenDialogOptions): Promise<OpenDialogReturnValue> =>
85+
await sendCommand({
86+
kind: IPC_HANDLERS.UTILITY,
87+
type: IPC_UTILITY_TYPES.OPEN_DIALOG,
88+
payload: options
89+
}),
90+
8491
refreshFirewall: async (): Promise<void> =>
8592
await sendCommand({
8693
kind: IPC_HANDLERS.UTILITY,

DeskThingServer/src/renderer/src/components/ReleaseComponent.tsx

Lines changed: 0 additions & 49 deletions
This file was deleted.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React from 'react'
2+
import SettingComponent from './SettingComponent'
3+
import { SettingsFile } from '@deskthing/types'
4+
import { IconFolderOpen } from '@renderer/assets/icons'
5+
6+
interface SettingsFileProps {
7+
setting: SettingsFile
8+
handleSettingChange: (value: number | boolean | string | string[]) => void
9+
className?: string
10+
}
11+
12+
const commonClasses =
13+
'px-3 py-2 text-black bg-white border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500'
14+
const buttonClasses = 'px-3 py-2 rounded-md mx-1'
15+
16+
/**
17+
* A React component that renders a settings file input with file selection button.
18+
*
19+
* @param props - The component props.
20+
* @param props.setting - The settings file object, containing the value, fileTypes, and other properties.
21+
* @param props.handleSettingChange - A function to handle changes to the settings file value.
22+
* @param props.className - An optional CSS class name to apply to the component.
23+
*/
24+
export const SettingsFileComponent: React.FC<SettingsFileProps> = ({
25+
className,
26+
setting,
27+
handleSettingChange
28+
}) => {
29+
const handleFileSelect = async (): Promise<void> => {
30+
try {
31+
const result = await window.electron.utility.showOpenDialog({
32+
properties: ['openFile'],
33+
filters:
34+
setting.fileTypes?.map((type) => ({
35+
name: type.name,
36+
extensions: type.extensions
37+
})) || []
38+
})
39+
if (!result.canceled && result.filePaths.length > 0) {
40+
handleSettingChange(result.filePaths[0])
41+
}
42+
} catch (error) {
43+
console.error('Error selecting file:', error)
44+
}
45+
}
46+
47+
return (
48+
<SettingComponent setting={setting} className={className}>
49+
<div className="flex items-center">
50+
{setting.type === 'file' && (
51+
<>
52+
<input
53+
type="text"
54+
value={setting.value as string}
55+
readOnly
56+
placeholder={setting.placeholder || 'Select a file...'}
57+
className={`${commonClasses} flex-grow`}
58+
/>
59+
<button
60+
onClick={handleFileSelect}
61+
className={`${buttonClasses} border border-emerald-500 text-white hover:bg-emerald-600`}
62+
>
63+
<IconFolderOpen />
64+
</button>
65+
</>
66+
)}
67+
</div>
68+
</SettingComponent>
69+
)
70+
}

DeskThingServer/src/renderer/src/components/settings/SettingsRange.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const SettingsRangeComponent: React.FC<SettingsRangeProps> = ({
2929
min={setting.min}
3030
max={setting.max}
3131
step={setting.step || 1}
32-
onChange={(e) => handleSettingChange(e.target.value)}
32+
onChange={(e) => handleSettingChange(Number(e.target.value))}
3333
className="w-96 max-w-s"
3434
/>
3535
)}

DeskThingServer/src/renderer/src/components/settings/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { SettingsRankedComponent } from './SettingsRanked'
99
import { SettingsSelectComponent } from './SettingsSelect'
1010
import { SettingsStringComponent } from './SettingsString'
1111
import { SettingsColorComponent } from './SettingsColor'
12+
import { SettingsFileComponent } from './SettingFile'
1213

1314
export interface SettingsProps {
1415
setting: SettingsType
@@ -31,7 +32,8 @@ const SETTINGS_COMPONENTS: {
3132
ranked: SettingsRankedComponent,
3233
select: SettingsSelectComponent,
3334
string: SettingsStringComponent,
34-
color: SettingsColorComponent
35+
color: SettingsColorComponent,
36+
file: SettingsFileComponent
3537
} as const
3638

3739
/**

0 commit comments

Comments
 (0)