chore: add theme uninstall and update
This commit is contained in:
@@ -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,66 +21,128 @@
|
|||||||
{
|
{
|
||||||
"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
@@ -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()
|
||||||
|
|||||||
@@ -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
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user