chore: add theme uninstall and update
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"shell:allow-open",
|
||||
@@ -19,65 +21,127 @@
|
||||
{
|
||||
"identifier": "fs:allow-read",
|
||||
"allow": [
|
||||
{ "path": "$HOME/.ezpplauncher" },
|
||||
{ "path": "$HOME/.ezpplauncher/*" },
|
||||
{ "path": "$HOME/.ezpplauncher/**/*" }
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher/*"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher/**/*"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "fs:allow-write",
|
||||
"allow": [
|
||||
{ "path": "$HOME/.ezpplauncher" },
|
||||
{ "path": "$HOME/.ezpplauncher/*" },
|
||||
{ "path": "$HOME/.ezpplauncher/**/*" }
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher/*"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher/**/*"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "fs:allow-exists",
|
||||
"allow": [
|
||||
{ "path": "$HOME/.ezpplauncher" },
|
||||
{ "path": "$HOME/.ezpplauncher/*" },
|
||||
{ "path": "$HOME/.ezpplauncher/**/*" }
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher/*"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher/**/*"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "fs:allow-read-file",
|
||||
"allow": [
|
||||
{ "path": "$HOME/.ezpplauncher" },
|
||||
{ "path": "$HOME/.ezpplauncher/*" },
|
||||
{ "path": "$HOME/.ezpplauncher/**/*" }
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher/*"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher/**/*"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "fs:allow-write-file",
|
||||
"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",
|
||||
"allow": [
|
||||
{ "path": "$HOME/.ezpplauncher" },
|
||||
{ "path": "$HOME/.ezpplauncher/*" },
|
||||
{ "path": "$HOME/.ezpplauncher/**/*" }
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher/*"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher/**/*"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "fs:allow-mkdir",
|
||||
"allow": [
|
||||
{ "path": "$HOME/.ezpplauncher" },
|
||||
{ "path": "$HOME/.ezpplauncher/*" },
|
||||
{ "path": "$HOME/.ezpplauncher/**/*" }
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher/*"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.ezpplauncher/**/*"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "fs:allow-read-dir",
|
||||
"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
@@ -5,6 +5,7 @@ import { active_custom_theme, custom_themes } from './global';
|
||||
import { setGlobalVolume } from './utils';
|
||||
import { betterFetch } from '@better-fetch/fetch';
|
||||
import zip from 'jszip';
|
||||
import { SemVer } from 'semver';
|
||||
|
||||
export type Theme = {
|
||||
name: string;
|
||||
@@ -15,6 +16,7 @@ export type Theme = {
|
||||
preview: string;
|
||||
status: 'installed' | 'downloading' | 'extracting' | 'not-installed';
|
||||
progress: number;
|
||||
updateAvailable: boolean;
|
||||
};
|
||||
|
||||
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`,
|
||||
status: 'not-installed',
|
||||
progress: 0,
|
||||
updateAvailable: false,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -66,6 +69,7 @@ export const getThemes = async (): Promise<Theme[]> => {
|
||||
preview: '',
|
||||
status: 'installed',
|
||||
progress: 0,
|
||||
updateAvailable: false,
|
||||
});
|
||||
const folderPath = await path.join(await path.homeDir(), '.ezpplauncher', 'themes');
|
||||
if (await fs.exists(folderPath)) {
|
||||
@@ -97,6 +101,7 @@ export const getThemes = async (): Promise<Theme[]> => {
|
||||
),
|
||||
status: 'installed',
|
||||
progress: 0,
|
||||
updateAvailable: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -117,8 +122,8 @@ function combineChunks(chunks: Uint8Array[], totalLength: number): Uint8Array {
|
||||
return result;
|
||||
}
|
||||
|
||||
export const downloadTheme = async (theme: Theme): Promise<boolean> => {
|
||||
if (theme.status !== 'not-installed') return false;
|
||||
export const downloadTheme = async (theme: Theme, force = false): Promise<boolean> => {
|
||||
if (theme.status !== 'not-installed' && !force) return false;
|
||||
|
||||
custom_themes.update((themes) => {
|
||||
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(
|
||||
await path.normalize(`${themeFolder}/${themeConfigObj.entry}`)
|
||||
);
|
||||
const themeAssets = convertFileSrc(await path.normalize(`${themeFolder}/assets`));
|
||||
const themePreview = convertFileSrc(
|
||||
await path.normalize(`${themeFolder}/assets/${themeConfigObj.preview}`)
|
||||
);
|
||||
custom_themes.update((themes) => {
|
||||
const matchedTheme = themes.find((t) => t.name === theme.name);
|
||||
if (matchedTheme) {
|
||||
matchedTheme.scriptUrl = themeScript;
|
||||
matchedTheme.assets = themeAssets;
|
||||
matchedTheme.preview = themePreview;
|
||||
matchedTheme.name = themeConfigObj.name;
|
||||
matchedTheme.version = themeConfigObj.version;
|
||||
matchedTheme.author = themeConfigObj.author;
|
||||
matchedTheme.status = 'installed';
|
||||
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 themes;
|
||||
return reloadedThemes;
|
||||
});
|
||||
return true;
|
||||
};
|
||||
@@ -252,6 +247,36 @@ export const loadTheme = async (theme: Theme, themeContainer: HTMLElement) => {
|
||||
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) => {
|
||||
return name
|
||||
.toLowerCase()
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
import { getDownloadableThemes, getThemes, loadTheme } from '@/themes';
|
||||
import { sileo } from 'sileo';
|
||||
import AnimatedBg from '@/components/ui/animated-bg/AnimatedBg.svelte';
|
||||
import { SemVer } from 'semver';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
@@ -112,6 +113,18 @@
|
||||
const combinedThemes = [...themes];
|
||||
for (const theme of downloadableThemes) {
|
||||
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);
|
||||
|
||||
+78
-31
@@ -43,6 +43,8 @@
|
||||
Settings,
|
||||
House,
|
||||
Paintbrush,
|
||||
Trash,
|
||||
CloudDownload,
|
||||
} from 'lucide-svelte';
|
||||
import NumberFlow from '@number-flow/svelte';
|
||||
import * as AlertDialog from '@/components/ui/alert-dialog';
|
||||
@@ -116,7 +118,7 @@
|
||||
import { animate } from 'animejs';
|
||||
import { sileo } from 'sileo';
|
||||
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 progress = $state(-1);
|
||||
@@ -1552,41 +1554,86 @@
|
||||
</div>
|
||||
<span class="text-xs text-muted-foreground">v{theme.version}</span>
|
||||
</div>
|
||||
<Button
|
||||
disabled={($active_custom_theme && $active_custom_theme.name === theme.name) ||
|
||||
theme.status === 'downloading' ||
|
||||
theme.status === 'extracting'}
|
||||
onclick={() => {
|
||||
if (theme.status === 'installed') {
|
||||
if ($custom_theme_container) {
|
||||
loadTheme(theme, $custom_theme_container);
|
||||
$userSettings.value('theme').set(theme.name);
|
||||
$userSettings.save();
|
||||
<div class="flex items-center gap-1">
|
||||
<Button
|
||||
class="w-full"
|
||||
disabled={($active_custom_theme &&
|
||||
$active_custom_theme.name === theme.name) ||
|
||||
theme.status === 'downloading' ||
|
||||
theme.status === 'extracting'}
|
||||
onclick={async () => {
|
||||
if (theme.status === 'installed') {
|
||||
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 {
|
||||
sileo.error({
|
||||
title: 'Uhhm..',
|
||||
description: 'Failed to apply theme.',
|
||||
});
|
||||
if (!(await downloadTheme(theme))) {
|
||||
sileo.error({
|
||||
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 theme.status === 'downloading'}
|
||||
Downloading Theme... ({Math.round(theme.progress * 100)}%)
|
||||
{:else if theme.status === 'extracting'}
|
||||
Extracting Theme... ({Math.round(theme.progress * 100)}%)
|
||||
}}
|
||||
>
|
||||
{#if $active_custom_theme && $active_custom_theme.name === theme.name}
|
||||
Theme in use
|
||||
{:else if theme.status !== 'installed'}
|
||||
{#if theme.status === 'downloading'}
|
||||
{theme.updateAvailable ? 'Updating' : 'Downloading'} Theme... ({Math.round(
|
||||
theme.progress * 100
|
||||
)}%)
|
||||
{:else if theme.status === 'extracting'}
|
||||
Extracting Theme... ({Math.round(theme.progress * 100)}%)
|
||||
{:else}
|
||||
Download Theme
|
||||
{/if}
|
||||
{:else}
|
||||
Download Theme
|
||||
Use Theme
|
||||
{/if}
|
||||
{:else}
|
||||
Use Theme
|
||||
</Button>
|
||||
{#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}
|
||||
</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>
|
||||
{/each}
|
||||
|
||||
Reference in New Issue
Block a user