chore: move theme extraction to tauri backend
This commit is contained in:
1 parent
d2ecc5210f
commit
94f78f281d
10 files changed
+753
-175
No files matched your search
@@ -87,7 +87,7 @@
|
||||
<div class="scrollbar" style="top: {topOffset}px;">
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="thumb"
|
||||
class="thumb bg-theme-600/60! hover:bg-theme-700! active:bg-theme-700 transition-colors"
|
||||
bind:this={thumb}
|
||||
onpointerdown={startDrag}
|
||||
onpointermove={drag}
|
||||
|
||||
@@ -18,4 +18,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div bind:this={rootEl}></div>
|
||||
<div class="z-9999" bind:this={rootEl}></div>
|
||||
+91
-122
@@ -2,9 +2,10 @@ import { active_custom_theme, custom_themes } from './global';
|
||||
import { calculateGitBlobSha, setGlobalVolume } from './utils';
|
||||
import { betterFetch } from '@better-fetch/fetch';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import * as path from '@tauri-apps/api/path';
|
||||
import * as fs from '@tauri-apps/plugin-fs';
|
||||
import zip from 'jszip';
|
||||
import { SemVer } from 'semver';
|
||||
|
||||
export type Theme = {
|
||||
@@ -30,6 +31,14 @@ export type ThemeInfo = {
|
||||
preview: string;
|
||||
};
|
||||
|
||||
type ExtractProgress = {
|
||||
theme_name: string;
|
||||
total: number;
|
||||
extracted: number;
|
||||
progress: number;
|
||||
current_file: string;
|
||||
};
|
||||
|
||||
export const getDownloadableThemes = async () => {
|
||||
const themes: Theme[] = [];
|
||||
|
||||
@@ -136,26 +145,21 @@ export const downloadTheme = async (
|
||||
|
||||
custom_themes.update((themes) => {
|
||||
const matchedTheme = themes.find((t) => t.name === theme.name);
|
||||
if (matchedTheme) {
|
||||
matchedTheme.status = 'downloading';
|
||||
}
|
||||
if (matchedTheme) matchedTheme.status = 'downloading';
|
||||
return themes;
|
||||
});
|
||||
|
||||
const themeFileName = `${toSafeName(theme.name)}.ezpplauncher-theme`;
|
||||
const themeContents = await betterFetch<{
|
||||
file_contents: {
|
||||
sha: string;
|
||||
download_url: string;
|
||||
};
|
||||
file_contents: { sha: string; download_url: string };
|
||||
}>(
|
||||
`https://git.ez-pp.farm/api/v1/repos/EZPPFarm/EZPPLauncher-Themes/contents-ext/themes/${themeFileName}?ref=main`
|
||||
);
|
||||
|
||||
if (themeContents.error) {
|
||||
custom_themes.update((themes) => {
|
||||
const matchedTheme = themes.find((t) => t.name === theme.name);
|
||||
if (matchedTheme) {
|
||||
matchedTheme.status = 'not-installed';
|
||||
}
|
||||
if (matchedTheme) matchedTheme.status = 'not-installed';
|
||||
return themes;
|
||||
});
|
||||
return {
|
||||
@@ -163,6 +167,7 @@ export const downloadTheme = async (
|
||||
error: themeContents.error.message ?? 'An unknown error occurred',
|
||||
};
|
||||
}
|
||||
|
||||
const downloadUrl = `https://git.ez-pp.farm/EZPPFarm/EZPPLauncher-Themes/raw/branch/main/themes/${themeFileName}`;
|
||||
const downloadReq = await fetch(downloadUrl, {
|
||||
method: 'GET',
|
||||
@@ -175,104 +180,90 @@ export const downloadTheme = async (
|
||||
if (!downloadReq.ok || downloadReq.body === null) {
|
||||
custom_themes.update((themes) => {
|
||||
const matchedTheme = themes.find((t) => t.name === theme.name);
|
||||
if (matchedTheme) {
|
||||
matchedTheme.status = 'not-installed';
|
||||
}
|
||||
if (matchedTheme) matchedTheme.status = 'not-installed';
|
||||
return themes;
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to download theme file.',
|
||||
};
|
||||
return { success: false, error: 'Failed to download theme file.' };
|
||||
}
|
||||
|
||||
custom_themes.update((themes) => {
|
||||
const matchedTheme = themes.find((t) => t.name === theme.name);
|
||||
if (matchedTheme) matchedTheme.progress = 0;
|
||||
return themes;
|
||||
});
|
||||
|
||||
const total = Number(downloadReq.headers.get('content-length')) || 0;
|
||||
const reader = downloadReq.body.getReader();
|
||||
let received = 0;
|
||||
const chunks = [];
|
||||
const chunks: Uint8Array[] = [];
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
chunks.push(value);
|
||||
received += value.length;
|
||||
const progress = received / total;
|
||||
custom_themes.update((themes) => {
|
||||
const matchedTheme = themes.find((t) => t.name === theme.name);
|
||||
if (matchedTheme) {
|
||||
matchedTheme.progress = progress;
|
||||
}
|
||||
if (matchedTheme) matchedTheme.progress = received / total;
|
||||
return themes;
|
||||
});
|
||||
}
|
||||
|
||||
const fileBuffer = combineChunks(chunks, total);
|
||||
|
||||
const sha = calculateGitBlobSha(Buffer.from(fileBuffer));
|
||||
|
||||
if (sha !== themeContents.data.file_contents.sha) {
|
||||
custom_themes.update((themes) => {
|
||||
const matchedTheme = themes.find((t) => t.name === theme.name);
|
||||
if (matchedTheme) {
|
||||
matchedTheme.status = 'not-installed';
|
||||
}
|
||||
if (matchedTheme) matchedTheme.status = 'not-installed';
|
||||
return themes;
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to verify downloaded theme file hash.',
|
||||
};
|
||||
return { success: false, error: 'Failed to verify downloaded theme file hash.' };
|
||||
}
|
||||
|
||||
//TODO: check file hash
|
||||
|
||||
custom_themes.update((themes) => {
|
||||
const matchedTheme = themes.find((t) => t.name === theme.name);
|
||||
if (matchedTheme) {
|
||||
matchedTheme.status = 'extracting';
|
||||
}
|
||||
if (matchedTheme) matchedTheme.status = 'extracting';
|
||||
return themes;
|
||||
});
|
||||
|
||||
const baseThemeFolder = await path.join(await path.homeDir(), '.ezpplauncher', 'themes');
|
||||
const themeFolder = await path.join(baseThemeFolder, toSafeName(theme.name));
|
||||
if (!(await fs.exists(baseThemeFolder))) await fs.mkdir(baseThemeFolder, { recursive: true });
|
||||
if (!(await fs.exists(themeFolder))) await fs.mkdir(themeFolder, { recursive: true });
|
||||
|
||||
const zipFile = await zip.loadAsync(fileBuffer);
|
||||
const totalFiles = Object.keys(zipFile.files).length;
|
||||
let extractedFiles = 0;
|
||||
for (const zipEntry of Object.keys(zipFile.files)) {
|
||||
const file = zipFile.file(zipEntry);
|
||||
if (file) {
|
||||
try {
|
||||
const fileData = await file.async('uint8array');
|
||||
const filePath = await path.join(themeFolder, zipEntry);
|
||||
const dirPath = await path.dirname(filePath);
|
||||
if (!(await fs.exists(dirPath))) await fs.mkdir(dirPath, { recursive: true });
|
||||
await new Promise((res) => setTimeout(res, 250));
|
||||
await fs.writeFile(filePath, fileData);
|
||||
extractedFiles++;
|
||||
custom_themes.update((themes) => {
|
||||
const matchedTheme = themes.find((t) => t.name === theme.name);
|
||||
if (matchedTheme) {
|
||||
matchedTheme.progress = extractedFiles / totalFiles;
|
||||
}
|
||||
return themes;
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
custom_themes.update((themes) => {
|
||||
const matchedTheme = themes.find((t) => t.name === theme.name);
|
||||
if (matchedTheme) {
|
||||
matchedTheme.status = 'not-installed';
|
||||
}
|
||||
return themes;
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to extract theme file.',
|
||||
};
|
||||
}
|
||||
}
|
||||
// Write buffer to temp file for Tauri invoke
|
||||
const tempPath = await path.join(await path.tempDir(), themeFileName);
|
||||
await fs.writeFile(tempPath, fileBuffer);
|
||||
|
||||
const unlisten = await listen<ExtractProgress>('extract_progress', (event) => {
|
||||
if (event.payload.theme_name !== theme.name) return;
|
||||
custom_themes.update((themes) => {
|
||||
const matchedTheme = themes.find((t) => t.name === theme.name);
|
||||
if (matchedTheme) matchedTheme.progress = event.payload.progress;
|
||||
return themes;
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
await invoke('extract_theme', {
|
||||
filePath: tempPath,
|
||||
themeFolder,
|
||||
themeName: theme.name,
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
custom_themes.update((themes) => {
|
||||
const matchedTheme = themes.find((t) => t.name === theme.name);
|
||||
if (matchedTheme) matchedTheme.status = 'not-installed';
|
||||
return themes;
|
||||
});
|
||||
return { success: false, error: 'Failed to extract theme file.' };
|
||||
} finally {
|
||||
unlisten();
|
||||
await fs.remove(tempPath).catch(() => {});
|
||||
}
|
||||
|
||||
const themeInfo = await fs.readTextFile(await path.join(themeFolder, 'theme.json'));
|
||||
const themeInfoObj = JSON.parse(themeInfo) as ThemeInfo;
|
||||
const themeScriptUrl = convertFileSrc(
|
||||
@@ -296,9 +287,7 @@ export const downloadTheme = async (
|
||||
});
|
||||
|
||||
await reloadThemes();
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
export const loadTheme = async (theme: Theme, themeContainer: HTMLElement, volume = 0.15) => {
|
||||
@@ -345,35 +334,17 @@ export const deleteTheme = async (themeToUninstall: Theme) => {
|
||||
export const checkThemeFromFile = async (filePath: string) => {
|
||||
const normalizedFilePath = await path.normalize(filePath);
|
||||
if (!(await fs.exists(normalizedFilePath))) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'File not found',
|
||||
};
|
||||
return { success: false, error: 'File not found' };
|
||||
}
|
||||
const fileBuffer = await fs.readFile(normalizedFilePath);
|
||||
|
||||
try {
|
||||
const zipFile = await zip.loadAsync(fileBuffer);
|
||||
|
||||
const themeConfig = zipFile.file('theme.json');
|
||||
if (!themeConfig) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Theme config file not found',
|
||||
};
|
||||
}
|
||||
const themeConfigData = await themeConfig.async('string');
|
||||
const themeInfo = JSON.parse(themeConfigData) as ThemeInfo;
|
||||
return {
|
||||
success: !!themeInfo,
|
||||
themeInfo,
|
||||
};
|
||||
const themeInfo = await invoke<ThemeInfo>('read_theme_info', {
|
||||
filePath: normalizedFilePath,
|
||||
});
|
||||
return { success: true, themeInfo };
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Could not read theme file',
|
||||
};
|
||||
return { success: false, error: 'Could not read theme file' };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -383,26 +354,26 @@ export const importThemeFromFile = async (themeName: string, filePath: string) =
|
||||
if (!(await fs.exists(baseThemeFolder))) await fs.mkdir(baseThemeFolder, { recursive: true });
|
||||
if (!(await fs.exists(themeFolder))) await fs.mkdir(themeFolder, { recursive: true });
|
||||
|
||||
const fileBuffer = await fs.readFile(filePath);
|
||||
const zipFile = await zip.loadAsync(fileBuffer);
|
||||
for (const zipEntry of Object.keys(zipFile.files)) {
|
||||
const file = zipFile.file(zipEntry);
|
||||
if (file) {
|
||||
try {
|
||||
const fileData = await file.async('uint8array');
|
||||
const filePath = await path.join(themeFolder, zipEntry);
|
||||
const dirPath = await path.dirname(filePath);
|
||||
if (!(await fs.exists(dirPath))) await fs.mkdir(dirPath, { recursive: true });
|
||||
await new Promise((res) => setTimeout(res, 250));
|
||||
await fs.writeFile(filePath, fileData);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to extract theme file.',
|
||||
};
|
||||
}
|
||||
}
|
||||
const unlisten = await listen<ExtractProgress>('extract_progress', (event) => {
|
||||
if (event.payload.theme_name !== themeName) return;
|
||||
custom_themes.update((themes) => {
|
||||
const matchedTheme = themes.find((t) => t.name === themeName);
|
||||
if (matchedTheme) matchedTheme.progress = event.payload.progress;
|
||||
return themes;
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
await invoke('extract_theme', {
|
||||
filePath,
|
||||
themeFolder,
|
||||
themeName,
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return { success: false, error: 'Failed to extract theme file.' };
|
||||
} finally {
|
||||
unlisten();
|
||||
}
|
||||
|
||||
const themeInfo = await fs.readTextFile(await path.join(themeFolder, 'theme.json'));
|
||||
@@ -428,9 +399,7 @@ export const importThemeFromFile = async (themeName: string, filePath: string) =
|
||||
});
|
||||
|
||||
await reloadThemes();
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
export const reloadThemes = async () => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Logo from '$assets/logo.png';
|
||||
import '../app.css';
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
import AnimatedBg from '$lib/components/ui/animated-bg/AnimatedBg.svelte';
|
||||
import Button from '$lib/components/ui/button/button.svelte';
|
||||
@@ -36,6 +35,7 @@
|
||||
userSettings,
|
||||
} from '$lib/userSettings';
|
||||
import { trackMediaContainer } from '$lib/utils';
|
||||
import '../app.css';
|
||||
import { Buffer } from 'buffer';
|
||||
import { SemVer } from 'semver';
|
||||
import { sileo } from 'sileo';
|
||||
@@ -185,6 +185,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
combinedThemes.sort((a, b) => {
|
||||
if (a.name === 'Default') return -1;
|
||||
if (b.name === 'Default') return 1;
|
||||
if (a.status === 'installed' && b.status !== 'installed') return -1;
|
||||
if (a.status !== 'installed' && b.status === 'installed') return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
custom_themes.set(combinedThemes);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
+27
-24
@@ -737,7 +737,7 @@
|
||||
|
||||
const setupThemeImport = async () => {
|
||||
const initialUrls = await invoke<string[]>('opened_urls');
|
||||
if (initialUrls.length > 0) {
|
||||
if (initialUrls.length > 0 && !droppedTheme) {
|
||||
const firstFile = initialUrls[0];
|
||||
if (!firstFile.endsWith('.ezpplauncher-theme')) {
|
||||
sileo.error({
|
||||
@@ -761,6 +761,7 @@
|
||||
}
|
||||
|
||||
await listen<string[]>('opened', async (event) => {
|
||||
if (droppedTheme) return;
|
||||
const files = event.payload;
|
||||
if (files.length > 0) {
|
||||
const firstFile = files[0];
|
||||
@@ -1003,7 +1004,7 @@
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Root>
|
||||
|
||||
<AlertDialog.Root open={$newVersion === undefined && !!droppedTheme}>
|
||||
<AlertDialog.Root open={$newVersion === undefined && !!droppedTheme && !themeInstalling}>
|
||||
<AlertDialog.Content
|
||||
class="bg-theme-950 border-theme-800 p-0 max-w-2xl"
|
||||
escapeKeydownBehavior="ignore"
|
||||
@@ -1044,29 +1045,31 @@
|
||||
return;
|
||||
}
|
||||
themeInstalling = true;
|
||||
|
||||
try {
|
||||
const importResult = await importThemeFromFile(
|
||||
droppedTheme.themeInfo.name,
|
||||
droppedTheme.filePath
|
||||
await sileo.promise(
|
||||
importThemeFromFile(droppedTheme.themeInfo.name, droppedTheme.filePath),
|
||||
{
|
||||
loading: {
|
||||
title: 'Importing theme...',
|
||||
},
|
||||
error() {
|
||||
return {
|
||||
title: 'Hmmm...',
|
||||
description: 'An unknown error occurred while importing your theme.',
|
||||
};
|
||||
},
|
||||
success(data) {
|
||||
return {
|
||||
title: data.success ? 'Yay!' : 'Hmmm...',
|
||||
description: data.success
|
||||
? 'Theme imported successfully'
|
||||
: data.error || 'An unknown error occurred while importing your theme.',
|
||||
type: data.success ? 'success' : 'error',
|
||||
};
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!importResult.success) {
|
||||
sileo.error({
|
||||
title: 'Hmmm...',
|
||||
description:
|
||||
importResult.error || 'An unknown error occurred while importing your theme.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
sileo.success({
|
||||
title: 'Yay!',
|
||||
description: 'Theme imported successfully',
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
sileo.error({
|
||||
title: 'Hmmm...',
|
||||
description: 'An unknown error occurred while importing your theme.',
|
||||
});
|
||||
} finally {
|
||||
droppedTheme = undefined;
|
||||
themeInstalling = false;
|
||||
@@ -1095,7 +1098,7 @@
|
||||
</AlertDialog.Root>
|
||||
|
||||
<div class="grid grid-cols-[0.085fr_1fr] h-screen relative" bind:this={dragAndDrop.ref}>
|
||||
{#if dragAndDrop.isDraggingOverApp}
|
||||
{#if dragAndDrop.isDraggingOverApp && !droppedTheme}
|
||||
<div
|
||||
class="fixed top-0 left-0 w-full h-full z-50 flex items-center justify-center bg-black/10 backdrop-blur-sm"
|
||||
transition:fade={{ duration: 300 }}
|
||||
|
||||
@@ -264,7 +264,7 @@
|
||||
<img
|
||||
src={Logo}
|
||||
alt="EZPPLauncher Logo"
|
||||
class="w-52 h-52 mb-2 relative z-10"
|
||||
class="w-52 h-52 mb-2 relative z-10 select-auto pointer-events-none"
|
||||
bind:this={ezppLogo}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user