chore: add theme uninstall and update

This commit is contained in:
2026-05-02 02:19:00 +02:00
parent b188e2dee8
commit d435f0c09a
4 changed files with 236 additions and 87 deletions
+89 -25
View File
@@ -2,7 +2,9 @@
"$schema": "../gen/schemas/desktop-schema.json", "$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default", "identifier": "default",
"description": "Capability for the main window", "description": "Capability for the main window",
"windows": ["main"], "windows": [
"main"
],
"permissions": [ "permissions": [
"core:default", "core:default",
"shell:allow-open", "shell:allow-open",
@@ -19,65 +21,127 @@
{ {
"identifier": "fs:allow-read", "identifier": "fs:allow-read",
"allow": [ "allow": [
{ "path": "$HOME/.ezpplauncher" }, {
{ "path": "$HOME/.ezpplauncher/*" }, "path": "$HOME/.ezpplauncher"
{ "path": "$HOME/.ezpplauncher/**/*" } },
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
] ]
}, },
{ {
"identifier": "fs:allow-write", "identifier": "fs:allow-write",
"allow": [ "allow": [
{ "path": "$HOME/.ezpplauncher" }, {
{ "path": "$HOME/.ezpplauncher/*" }, "path": "$HOME/.ezpplauncher"
{ "path": "$HOME/.ezpplauncher/**/*" } },
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
] ]
}, },
{ {
"identifier": "fs:allow-exists", "identifier": "fs:allow-exists",
"allow": [ "allow": [
{ "path": "$HOME/.ezpplauncher" }, {
{ "path": "$HOME/.ezpplauncher/*" }, "path": "$HOME/.ezpplauncher"
{ "path": "$HOME/.ezpplauncher/**/*" } },
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
] ]
}, },
{ {
"identifier": "fs:allow-read-file", "identifier": "fs:allow-read-file",
"allow": [ "allow": [
{ "path": "$HOME/.ezpplauncher" }, {
{ "path": "$HOME/.ezpplauncher/*" }, "path": "$HOME/.ezpplauncher"
{ "path": "$HOME/.ezpplauncher/**/*" } },
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
] ]
}, },
{ {
"identifier": "fs:allow-write-file", "identifier": "fs:allow-write-file",
"allow": [ "allow": [
{ "path": "$HOME/.ezpplauncher" }, {
{ "path": "$HOME/.ezpplauncher/*" }, "path": "$HOME/.ezpplauncher"
{ "path": "$HOME/.ezpplauncher/**/*" } },
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
] ]
}, },
{ {
"identifier": "fs:allow-read-text-file", "identifier": "fs:allow-read-text-file",
"allow": [ "allow": [
{ "path": "$HOME/.ezpplauncher" }, {
{ "path": "$HOME/.ezpplauncher/*" }, "path": "$HOME/.ezpplauncher"
{ "path": "$HOME/.ezpplauncher/**/*" } },
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
] ]
}, },
{ {
"identifier": "fs:allow-mkdir", "identifier": "fs:allow-mkdir",
"allow": [ "allow": [
{ "path": "$HOME/.ezpplauncher" }, {
{ "path": "$HOME/.ezpplauncher/*" }, "path": "$HOME/.ezpplauncher"
{ "path": "$HOME/.ezpplauncher/**/*" } },
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
] ]
}, },
{ {
"identifier": "fs:allow-read-dir", "identifier": "fs:allow-read-dir",
"allow": [ "allow": [
{ "path": "$HOME/.ezpplauncher" }, {
{ "path": "$HOME/.ezpplauncher/*" }, "path": "$HOME/.ezpplauncher"
{ "path": "$HOME/.ezpplauncher/**/*" } },
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
]
},
{
"identifier": "fs:allow-remove",
"allow": [
{
"path": "$HOME/.ezpplauncher"
},
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
] ]
} }
] ]
+55 -30
View File
@@ -5,6 +5,7 @@ import { active_custom_theme, custom_themes } from './global';
import { setGlobalVolume } from './utils'; import { setGlobalVolume } from './utils';
import { betterFetch } from '@better-fetch/fetch'; import { betterFetch } from '@better-fetch/fetch';
import zip from 'jszip'; import zip from 'jszip';
import { SemVer } from 'semver';
export type Theme = { export type Theme = {
name: string; name: string;
@@ -15,6 +16,7 @@ export type Theme = {
preview: string; preview: string;
status: 'installed' | 'downloading' | 'extracting' | 'not-installed'; status: 'installed' | 'downloading' | 'extracting' | 'not-installed';
progress: number; progress: number;
updateAvailable: boolean;
}; };
export const getDownloadableThemes = async () => { export const getDownloadableThemes = async () => {
@@ -46,6 +48,7 @@ export const getDownloadableThemes = async () => {
preview: `https://git.ez-pp.farm/EZPPFarm/EZPPLauncher-Themes/raw/branch/main/previews/${toSafeName(theme.name)}.png`, preview: `https://git.ez-pp.farm/EZPPFarm/EZPPLauncher-Themes/raw/branch/main/previews/${toSafeName(theme.name)}.png`,
status: 'not-installed', status: 'not-installed',
progress: 0, progress: 0,
updateAvailable: false,
}); });
} }
} else { } else {
@@ -66,6 +69,7 @@ export const getThemes = async (): Promise<Theme[]> => {
preview: '', preview: '',
status: 'installed', status: 'installed',
progress: 0, progress: 0,
updateAvailable: false,
}); });
const folderPath = await path.join(await path.homeDir(), '.ezpplauncher', 'themes'); const folderPath = await path.join(await path.homeDir(), '.ezpplauncher', 'themes');
if (await fs.exists(folderPath)) { if (await fs.exists(folderPath)) {
@@ -97,6 +101,7 @@ export const getThemes = async (): Promise<Theme[]> => {
), ),
status: 'installed', status: 'installed',
progress: 0, progress: 0,
updateAvailable: false,
}); });
} }
} }
@@ -117,8 +122,8 @@ function combineChunks(chunks: Uint8Array[], totalLength: number): Uint8Array {
return result; return result;
} }
export const downloadTheme = async (theme: Theme): Promise<boolean> => { export const downloadTheme = async (theme: Theme, force = false): Promise<boolean> => {
if (theme.status !== 'not-installed') return false; if (theme.status !== 'not-installed' && !force) return false;
custom_themes.update((themes) => { custom_themes.update((themes) => {
const matchedTheme = themes.find((t) => t.name === theme.name); const matchedTheme = themes.find((t) => t.name === theme.name);
@@ -207,36 +212,26 @@ export const downloadTheme = async (theme: Theme): Promise<boolean> => {
} }
} }
} }
const themeConfig = await fs.readTextFile(await path.join(themeFolder, 'theme.json'));
const themeConfigObj = JSON.parse(themeConfig) as {
name: string;
version: string;
apiVersion: string;
author: string;
entry: string;
style: string;
preview: string;
};
const themeScript = convertFileSrc( const currentThemes = await getThemes();
await path.normalize(`${themeFolder}/${themeConfigObj.entry}`) const downloadableThemes = await getDownloadableThemes();
); custom_themes.update(() => {
const themeAssets = convertFileSrc(await path.normalize(`${themeFolder}/assets`)); const reloadedThemes = [...currentThemes];
const themePreview = convertFileSrc( for (const theme of downloadableThemes) {
await path.normalize(`${themeFolder}/assets/${themeConfigObj.preview}`) if (!reloadedThemes.find((t) => t.name === theme.name)) reloadedThemes.push(theme);
);
custom_themes.update((themes) => { const installedTheme = currentThemes.find((t) => t.name === theme.name);
const matchedTheme = themes.find((t) => t.name === theme.name); if (installedTheme) {
if (matchedTheme) { const installedThemeVersion = new SemVer(installedTheme.version);
matchedTheme.scriptUrl = themeScript; const downloadableThemeVersion = new SemVer(theme.version);
matchedTheme.assets = themeAssets; if (downloadableThemeVersion.compare(installedThemeVersion) > 0) {
matchedTheme.preview = themePreview; installedTheme.updateAvailable = true;
matchedTheme.name = themeConfigObj.name; const index = reloadedThemes.findIndex((t) => t.name === theme.name);
matchedTheme.version = themeConfigObj.version; reloadedThemes[index] = installedTheme;
matchedTheme.author = themeConfigObj.author; }
matchedTheme.status = 'installed'; }
} }
return themes; return reloadedThemes;
}); });
return true; return true;
}; };
@@ -252,6 +247,36 @@ export const loadTheme = async (theme: Theme, themeContainer: HTMLElement) => {
setGlobalVolume(0.15); setGlobalVolume(0.15);
}; };
export const deleteTheme = async (themeToUninstall: Theme) => {
if (themeToUninstall.status !== 'installed') return false;
const baseThemeFolder = await path.join(await path.homeDir(), '.ezpplauncher', 'themes');
if (!(await fs.exists(baseThemeFolder))) return false;
const themeFolder = await path.join(baseThemeFolder, toSafeName(themeToUninstall.name));
if (!(await fs.exists(themeFolder))) return false;
await fs.remove(themeFolder, { recursive: true });
const currentThemes = await getThemes();
const downloadableThemes = await getDownloadableThemes();
custom_themes.update(() => {
const reloadedThemes = [...currentThemes];
for (const theme of downloadableThemes) {
if (!reloadedThemes.find((t) => t.name === theme.name)) reloadedThemes.push(theme);
const installedTheme = currentThemes.find((t) => t.name === theme.name);
if (installedTheme) {
const installedThemeVersion = new SemVer(installedTheme.version);
const downloadableThemeVersion = new SemVer(theme.version);
if (downloadableThemeVersion.compare(installedThemeVersion) > 0) {
installedTheme.updateAvailable = true;
const index = reloadedThemes.findIndex((t) => t.name === theme.name);
reloadedThemes[index] = installedTheme;
}
}
}
return reloadedThemes;
});
return true;
};
export const toSafeName = (name: string) => { export const toSafeName = (name: string) => {
return name return name
.toLowerCase() .toLowerCase()
+13
View File
@@ -40,6 +40,7 @@
import { getDownloadableThemes, getThemes, loadTheme } from '@/themes'; import { getDownloadableThemes, getThemes, loadTheme } from '@/themes';
import { sileo } from 'sileo'; import { sileo } from 'sileo';
import AnimatedBg from '@/components/ui/animated-bg/AnimatedBg.svelte'; import AnimatedBg from '@/components/ui/animated-bg/AnimatedBg.svelte';
import { SemVer } from 'semver';
let { children } = $props(); let { children } = $props();
@@ -112,6 +113,18 @@
const combinedThemes = [...themes]; const combinedThemes = [...themes];
for (const theme of downloadableThemes) { for (const theme of downloadableThemes) {
if (!combinedThemes.find((t) => t.name === theme.name)) combinedThemes.push(theme); if (!combinedThemes.find((t) => t.name === theme.name)) combinedThemes.push(theme);
//set updateAvailable to true if downloadableTheme version is higher than installed theme version
const installedTheme = themes.find((t) => t.name === theme.name);
if (installedTheme) {
const installedThemeVersion = new SemVer(installedTheme.version);
const downloadableThemeVersion = new SemVer(theme.version);
if (downloadableThemeVersion.compare(installedThemeVersion) > 0) {
installedTheme.updateAvailable = true;
const index = combinedThemes.findIndex((t) => t.name === theme.name);
combinedThemes[index] = installedTheme;
}
}
} }
custom_themes.set(combinedThemes); custom_themes.set(combinedThemes);
+78 -31
View File
@@ -43,6 +43,8 @@
Settings, Settings,
House, House,
Paintbrush, Paintbrush,
Trash,
CloudDownload,
} from 'lucide-svelte'; } from 'lucide-svelte';
import NumberFlow from '@number-flow/svelte'; import NumberFlow from '@number-flow/svelte';
import * as AlertDialog from '@/components/ui/alert-dialog'; import * as AlertDialog from '@/components/ui/alert-dialog';
@@ -116,7 +118,7 @@
import { animate } from 'animejs'; import { animate } from 'animejs';
import { sileo } from 'sileo'; import { sileo } from 'sileo';
import ScrollContainer from '@/components/ui/scroll-container/ScrollContainer.svelte'; import ScrollContainer from '@/components/ui/scroll-container/ScrollContainer.svelte';
import { downloadTheme, loadTheme } from '@/themes'; import { deleteTheme, downloadTheme, loadTheme } from '@/themes';
let selectedView = $state('home'); let selectedView = $state('home');
let progress = $state(-1); let progress = $state(-1);
@@ -1552,41 +1554,86 @@
</div> </div>
<span class="text-xs text-muted-foreground">v{theme.version}</span> <span class="text-xs text-muted-foreground">v{theme.version}</span>
</div> </div>
<Button <div class="flex items-center gap-1">
disabled={($active_custom_theme && $active_custom_theme.name === theme.name) || <Button
theme.status === 'downloading' || class="w-full"
theme.status === 'extracting'} disabled={($active_custom_theme &&
onclick={() => { $active_custom_theme.name === theme.name) ||
if (theme.status === 'installed') { theme.status === 'downloading' ||
if ($custom_theme_container) { theme.status === 'extracting'}
loadTheme(theme, $custom_theme_container); onclick={async () => {
$userSettings.value('theme').set(theme.name); if (theme.status === 'installed') {
$userSettings.save(); if ($custom_theme_container) {
loadTheme(theme, $custom_theme_container);
$userSettings.value('theme').set(theme.name);
$userSettings.save();
} else {
sileo.error({
title: 'Uhhm..',
description: 'Failed to apply theme.',
});
}
} else { } else {
sileo.error({ if (!(await downloadTheme(theme))) {
title: 'Uhhm..', sileo.error({
description: 'Failed to apply theme.', title: 'Uhhm..',
}); description: 'Failed to download theme.',
});
}
} }
} else { }}
downloadTheme(theme); >
} {#if $active_custom_theme && $active_custom_theme.name === theme.name}
}} Theme in use
> {:else if theme.status !== 'installed'}
{#if $active_custom_theme && $active_custom_theme.name === theme.name} {#if theme.status === 'downloading'}
Theme in use {theme.updateAvailable ? 'Updating' : 'Downloading'} Theme... ({Math.round(
{:else if theme.status !== 'installed'} theme.progress * 100
{#if theme.status === 'downloading'} )}%)
Downloading Theme... ({Math.round(theme.progress * 100)}%) {:else if theme.status === 'extracting'}
{:else if theme.status === 'extracting'} Extracting Theme... ({Math.round(theme.progress * 100)}%)
Extracting Theme... ({Math.round(theme.progress * 100)}%) {:else}
Download Theme
{/if}
{:else} {:else}
Download Theme Use Theme
{/if} {/if}
{:else} </Button>
Use Theme {#if theme.status === 'installed' && theme.updateAvailable}
<Button
class="min-w-[40px]"
size="icon"
variant="secondary"
onclick={async () => {
const defaultTheme = $custom_themes.find((t) => t.name === 'Default');
if (defaultTheme) {
loadTheme(defaultTheme, $custom_theme_container!);
$userSettings.value('theme').set(defaultTheme.name);
$userSettings.save();
if (!(await downloadTheme(theme, true))) {
sileo.error({
title: 'Uhhm..',
description: 'Failed to update theme.',
});
}
}
}}
>
<CloudDownload />
</Button>
{/if} {/if}
</Button> {#if theme.status === 'installed' && $active_custom_theme && $active_custom_theme.name !== theme.name && theme.name !== 'Default'}
<Button
class="min-w-[40px]"
size="icon"
variant="destructive"
onclick={() => deleteTheme(theme)}
>
<Trash />
</Button>
{/if}
</div>
</div> </div>
</div> </div>
{/each} {/each}