mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-14 01:39:56 -05:00
Chore: Completed Splash-Screen & Iniital Tauri Wire-Up
This commit is contained in:
+25
-27
@@ -1,8 +1,8 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import {
|
||||
persistSettings,
|
||||
persistLibrary,
|
||||
persistUpdates,
|
||||
saveSettings,
|
||||
saveLibrary,
|
||||
saveUpdates,
|
||||
} from "$lib/core/persistence/persist";
|
||||
|
||||
const STORE_FILES = ["settings.json", "library.json", "updates.json"] as const;
|
||||
@@ -37,19 +37,17 @@ export async function importAppData(): Promise<void> {
|
||||
const u = decode("updates.json");
|
||||
|
||||
await Promise.all([
|
||||
persistSettings({
|
||||
saveSettings({
|
||||
storeVersion: s.storeVersion ?? 2,
|
||||
settings: s.settings ?? null,
|
||||
storeVersion: s.storeVersion ?? 1,
|
||||
}),
|
||||
persistLibrary({
|
||||
history: l.history ?? [],
|
||||
saveLibrary({
|
||||
sessions: l.sessions ?? [],
|
||||
bookmarks: l.bookmarks ?? [],
|
||||
markers: l.markers ?? [],
|
||||
readLog: l.readLog ?? [],
|
||||
readingStats: l.readingStats ?? null,
|
||||
dailyReadCounts: l.dailyReadCounts ?? {},
|
||||
}),
|
||||
persistUpdates({
|
||||
saveUpdates({
|
||||
libraryUpdates: u.libraryUpdates ?? [],
|
||||
lastLibraryRefresh: u.lastLibraryRefresh ?? 0,
|
||||
acknowledgedUpdateIds: u.acknowledgedUpdateIds ?? [],
|
||||
@@ -60,6 +58,23 @@ export async function importAppData(): Promise<void> {
|
||||
invoke("exit_app");
|
||||
}
|
||||
|
||||
export async function autoBackupAppData(): Promise<void> {
|
||||
try {
|
||||
const entries: [string, string][] = await invoke("read_store_files", {
|
||||
names: [...STORE_FILES],
|
||||
});
|
||||
const zip = buildZip(
|
||||
entries.map(([name, content]) => ({
|
||||
name,
|
||||
bytes: new TextEncoder().encode(content),
|
||||
}))
|
||||
);
|
||||
await invoke("auto_backup_app_data", { bytes: Array.from(zip) });
|
||||
} catch (e) {
|
||||
console.warn("[moku] auto-backup failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function showExitModal(): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
const backdrop = document.createElement("div");
|
||||
@@ -123,23 +138,6 @@ function showExitModal(): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
export async function autoBackupAppData(): Promise<void> {
|
||||
try {
|
||||
const entries: [string, string][] = await invoke("read_store_files", {
|
||||
names: [...STORE_FILES],
|
||||
});
|
||||
const zip = buildZip(
|
||||
entries.map(([name, content]) => ({
|
||||
name,
|
||||
bytes: new TextEncoder().encode(content),
|
||||
}))
|
||||
);
|
||||
await invoke("auto_backup_app_data", { bytes: Array.from(zip) });
|
||||
} catch (e) {
|
||||
console.warn("[moku] auto-backup failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function crc32(data: Uint8Array): number {
|
||||
let crc = 0xffffffff;
|
||||
for (const byte of data) {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { platformService } from '$lib/platform-service'
|
||||
import type { Manga } from '$lib/types/manga'
|
||||
import type { Chapter } from '$lib/types/chapter'
|
||||
|
||||
const APP_BUTTONS = [
|
||||
{ label: 'GitHub', url: 'https://github.com/moku-project/Moku' },
|
||||
{ label: 'Discord', url: 'https://discord.gg/Jq3pwuNqPp' },
|
||||
]
|
||||
|
||||
const FALLBACK_IMAGE = 'moku_logo'
|
||||
|
||||
let sessionStart: number | null = null
|
||||
|
||||
function isPublicUrl(url: string | null | undefined): boolean {
|
||||
return typeof url === 'string' && url.startsWith('https://')
|
||||
}
|
||||
|
||||
function trunc(s: string, max = 128): string {
|
||||
return s.length <= max ? s : `${s.slice(0, max - 1)}…`
|
||||
}
|
||||
|
||||
function formatChapter(chapter: Chapter): string {
|
||||
const n = chapter.chapterNumber
|
||||
return `Chapter ${Number.isInteger(n) ? n : n.toFixed(1)}`
|
||||
}
|
||||
|
||||
export async function initRpc(): Promise<void> {
|
||||
if (!platformService.isSupported('discord-rpc')) return
|
||||
sessionStart = Date.now()
|
||||
}
|
||||
|
||||
export async function destroyRpc(): Promise<void> {
|
||||
if (!platformService.isSupported('discord-rpc')) return
|
||||
sessionStart = null
|
||||
}
|
||||
|
||||
export async function setReading(manga: Manga, chapter: Chapter): Promise<void> {
|
||||
if (!platformService.isSupported('discord-rpc')) return
|
||||
await platformService.setDiscordPresence({
|
||||
details: trunc(manga.title),
|
||||
state: `${formatChapter(chapter)} · Reading`,
|
||||
timestamps: { start: sessionStart ?? Date.now() },
|
||||
assets: {
|
||||
largeImage: isPublicUrl(manga.thumbnailUrl) ? manga.thumbnailUrl : FALLBACK_IMAGE,
|
||||
largeText: trunc(manga.title),
|
||||
smallImage: FALLBACK_IMAGE,
|
||||
smallText: 'Moku',
|
||||
},
|
||||
buttons: APP_BUTTONS,
|
||||
})
|
||||
}
|
||||
|
||||
export async function setIdle(): Promise<void> {
|
||||
if (!platformService.isSupported('discord-rpc')) return
|
||||
await platformService.setDiscordPresence({
|
||||
details: 'Browsing',
|
||||
timestamps: { start: sessionStart ?? Date.now() },
|
||||
assets: { largeImage: FALLBACK_IMAGE, largeText: 'Moku' },
|
||||
buttons: APP_BUTTONS,
|
||||
})
|
||||
}
|
||||
|
||||
export async function clearReading(): Promise<void> {
|
||||
if (!platformService.isSupported('discord-rpc')) return
|
||||
await platformService.clearDiscordPresence()
|
||||
}
|
||||
+132
-142
@@ -1,166 +1,156 @@
|
||||
import { LazyStore } from "@tauri-apps/plugin-store";
|
||||
import { platformService } from '$lib/platform-service'
|
||||
import type { ReadSession } from '$lib/types/history'
|
||||
import type { BookmarkEntry, MarkerEntry } from '$lib/types/history'
|
||||
|
||||
const settingsStore = new LazyStore("settings.json", { autoSave: false });
|
||||
const libraryStore = new LazyStore("library.json", { autoSave: false });
|
||||
const updatesStore = new LazyStore("updates.json", { autoSave: false });
|
||||
const backupsStore = new LazyStore("backups.json", { autoSave: false });
|
||||
const STORE_VERSION = 2
|
||||
|
||||
export interface PersistedData {
|
||||
settings: any;
|
||||
storeVersion: number | null;
|
||||
history: any[];
|
||||
bookmarks: any[];
|
||||
markers: any[];
|
||||
readLog: any[];
|
||||
readingStats: any | null;
|
||||
dailyReadCounts: Record<string, number>;
|
||||
libraryUpdates: any[];
|
||||
lastLibraryRefresh: number;
|
||||
acknowledgedUpdateIds: number[];
|
||||
export interface PersistedSettings {
|
||||
storeVersion: number
|
||||
settings: unknown
|
||||
}
|
||||
|
||||
export async function loadAllStores(): Promise<PersistedData> {
|
||||
const migrated = await migrateFromLocalStorage();
|
||||
if (migrated) return migrated;
|
||||
|
||||
const [sv, s, hist, bk, mk, rl, rs, dc, lu, llr, au] = await Promise.all([
|
||||
settingsStore.get<number>("storeVersion"),
|
||||
settingsStore.get<any>("settings"),
|
||||
libraryStore.get<any[]>("history"),
|
||||
libraryStore.get<any[]>("bookmarks"),
|
||||
libraryStore.get<any[]>("markers"),
|
||||
libraryStore.get<any[]>("readLog"),
|
||||
libraryStore.get<any>("readingStats"),
|
||||
libraryStore.get<Record<string, number>>("dailyReadCounts"),
|
||||
updatesStore.get<any[]>("libraryUpdates"),
|
||||
updatesStore.get<number>("lastLibraryRefresh"),
|
||||
updatesStore.get<number[]>("acknowledgedUpdateIds"),
|
||||
]);
|
||||
|
||||
return {
|
||||
storeVersion: sv ?? null,
|
||||
settings: s ?? null,
|
||||
history: hist ?? [],
|
||||
bookmarks: bk ?? [],
|
||||
markers: mk ?? [],
|
||||
readLog: rl ?? [],
|
||||
readingStats: rs ?? null,
|
||||
dailyReadCounts: dc ?? {},
|
||||
libraryUpdates: lu ?? [],
|
||||
lastLibraryRefresh: llr ?? 0,
|
||||
acknowledgedUpdateIds: au ?? [],
|
||||
};
|
||||
export interface PersistedLibrary {
|
||||
sessions: ReadSession[]
|
||||
bookmarks: BookmarkEntry[]
|
||||
markers: MarkerEntry[]
|
||||
dailyReadCounts: Record<string, number>
|
||||
}
|
||||
|
||||
async function migrateFromLocalStorage(): Promise<PersistedData | null> {
|
||||
try {
|
||||
const raw = localStorage.getItem("moku-store");
|
||||
if (!raw) return null;
|
||||
const data = JSON.parse(raw);
|
||||
export interface PersistedUpdates {
|
||||
libraryUpdates: unknown[]
|
||||
lastLibraryRefresh: number
|
||||
acknowledgedUpdateIds: number[]
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
persistSettings({ settings: data.settings ?? null, storeVersion: data.storeVersion ?? 1 }),
|
||||
persistLibrary({
|
||||
history: data.history ?? [],
|
||||
bookmarks: data.bookmarks ?? [],
|
||||
markers: data.markers ?? [],
|
||||
readLog: data.readLog ?? [],
|
||||
readingStats: data.readingStats ?? null,
|
||||
dailyReadCounts: data.dailyReadCounts ?? {},
|
||||
}),
|
||||
persistUpdates({
|
||||
libraryUpdates: data.libraryUpdates ?? [],
|
||||
lastLibraryRefresh: data.lastLibraryRefresh ?? 0,
|
||||
acknowledgedUpdateIds: data.acknowledgedUpdateIds ?? [],
|
||||
}),
|
||||
]);
|
||||
export interface PersistedBackups {
|
||||
backupList: { url: string; name: string }[]
|
||||
}
|
||||
|
||||
localStorage.removeItem("moku-store");
|
||||
function migrateLibrary(raw: unknown, fromVersion: number): PersistedLibrary {
|
||||
const data = (raw ?? {}) as Record<string, unknown>
|
||||
|
||||
if (fromVersion < 2) {
|
||||
const oldHistory = (data.history ?? []) as Array<{
|
||||
mangaId: number; mangaTitle: string; thumbnailUrl: string
|
||||
chapterId: number; chapterName: string; chapterNumber?: number
|
||||
pageNumber?: number; readAt: number
|
||||
}>
|
||||
|
||||
const sessions: ReadSession[] = oldHistory.map(e => ({
|
||||
id: crypto.randomUUID(),
|
||||
mangaId: e.mangaId,
|
||||
mangaTitle: e.mangaTitle,
|
||||
thumbnailUrl: e.thumbnailUrl,
|
||||
startChapterId: e.chapterId,
|
||||
startChapterName: e.chapterName,
|
||||
endChapterId: e.chapterId,
|
||||
endChapterName: e.chapterName,
|
||||
startPage: 1,
|
||||
endPage: e.pageNumber ?? 1,
|
||||
startedAt: e.readAt,
|
||||
endedAt: e.readAt,
|
||||
durationMs: 0,
|
||||
chaptersSpanned: 1,
|
||||
}))
|
||||
|
||||
return {
|
||||
storeVersion: data.storeVersion ?? null,
|
||||
settings: data.settings ?? null,
|
||||
history: data.history ?? [],
|
||||
bookmarks: data.bookmarks ?? [],
|
||||
markers: data.markers ?? [],
|
||||
readLog: data.readLog ?? [],
|
||||
readingStats: data.readingStats ?? null,
|
||||
dailyReadCounts: data.dailyReadCounts ?? {},
|
||||
libraryUpdates: data.libraryUpdates ?? [],
|
||||
lastLibraryRefresh: data.lastLibraryRefresh ?? 0,
|
||||
acknowledgedUpdateIds: data.acknowledgedUpdateIds ?? [],
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
sessions,
|
||||
bookmarks: (data.bookmarks ?? []) as BookmarkEntry[],
|
||||
markers: (data.markers ?? []) as MarkerEntry[],
|
||||
dailyReadCounts: (data.dailyReadCounts ?? {}) as Record<string, number>,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sessions: (data.sessions ?? []) as ReadSession[],
|
||||
bookmarks: (data.bookmarks ?? []) as BookmarkEntry[],
|
||||
markers: (data.markers ?? []) as MarkerEntry[],
|
||||
dailyReadCounts: (data.dailyReadCounts ?? {}) as Record<string, number>,
|
||||
}
|
||||
}
|
||||
|
||||
export async function persistSettings(data: { settings: any; storeVersion: number }) {
|
||||
await Promise.all([
|
||||
settingsStore.set("settings", data.settings),
|
||||
settingsStore.set("storeVersion", data.storeVersion),
|
||||
]);
|
||||
await settingsStore.save();
|
||||
export async function loadSettings(): Promise<PersistedSettings> {
|
||||
const raw = await platformService.loadStore('settings')
|
||||
const data = (raw ?? {}) as Record<string, unknown>
|
||||
|
||||
const legacyRaw = typeof window !== 'undefined' ? localStorage.getItem('moku_settings') : null
|
||||
if (legacyRaw && !data.settings) {
|
||||
try {
|
||||
const legacySettings = JSON.parse(legacyRaw)
|
||||
localStorage.removeItem('moku_settings')
|
||||
const result: PersistedSettings = { storeVersion: STORE_VERSION, settings: legacySettings }
|
||||
await saveSettings(result)
|
||||
return result
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return {
|
||||
storeVersion: (data.storeVersion as number) ?? STORE_VERSION,
|
||||
settings: data.settings ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
export async function persistLibrary(data: {
|
||||
history: any[];
|
||||
bookmarks: any[];
|
||||
markers: any[];
|
||||
readLog: any[];
|
||||
readingStats: any;
|
||||
dailyReadCounts: Record<string, number>;
|
||||
}) {
|
||||
await Promise.all([
|
||||
libraryStore.set("history", data.history),
|
||||
libraryStore.set("bookmarks", data.bookmarks),
|
||||
libraryStore.set("markers", data.markers),
|
||||
libraryStore.set("readLog", data.readLog),
|
||||
libraryStore.set("readingStats", data.readingStats),
|
||||
libraryStore.set("dailyReadCounts", data.dailyReadCounts),
|
||||
]);
|
||||
await libraryStore.save();
|
||||
export async function saveSettings(data: PersistedSettings): Promise<void> {
|
||||
await platformService.saveStore('settings', data)
|
||||
}
|
||||
|
||||
export async function persistUpdates(data: {
|
||||
libraryUpdates: any[];
|
||||
lastLibraryRefresh: number;
|
||||
acknowledgedUpdateIds: number[];
|
||||
}) {
|
||||
await Promise.all([
|
||||
updatesStore.set("libraryUpdates", data.libraryUpdates),
|
||||
updatesStore.set("lastLibraryRefresh", data.lastLibraryRefresh),
|
||||
updatesStore.set("acknowledgedUpdateIds", data.acknowledgedUpdateIds),
|
||||
]);
|
||||
await updatesStore.save();
|
||||
export async function loadLibrary(): Promise<PersistedLibrary> {
|
||||
const raw = await platformService.loadStore('library')
|
||||
const data = (raw ?? {}) as Record<string, unknown>
|
||||
const version = (data.storeVersion as number) ?? 1
|
||||
|
||||
const legacyRaw = typeof window !== 'undefined' ? localStorage.getItem('moku-store') : null
|
||||
if (legacyRaw && !(data.sessions || data.history)) {
|
||||
try {
|
||||
const legacy = JSON.parse(legacyRaw)
|
||||
const migrated = migrateLibrary(legacy, 1)
|
||||
localStorage.removeItem('moku-store')
|
||||
await saveLibrary(migrated)
|
||||
return migrated
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return migrateLibrary(raw, version)
|
||||
}
|
||||
|
||||
export interface BackupEntry { url: string; name: string; }
|
||||
|
||||
export async function loadBackups(): Promise<BackupEntry[]> {
|
||||
const fromStore = await backupsStore.get<BackupEntry[]>("backupList");
|
||||
if (fromStore) return fromStore;
|
||||
try {
|
||||
const raw = localStorage.getItem("moku_backups");
|
||||
if (!raw) return [];
|
||||
const migrated: BackupEntry[] = JSON.parse(raw);
|
||||
await persistBackups(migrated);
|
||||
localStorage.removeItem("moku_backups");
|
||||
return migrated;
|
||||
} catch { return []; }
|
||||
export async function saveLibrary(data: PersistedLibrary): Promise<void> {
|
||||
await platformService.saveStore('library', { ...data, storeVersion: STORE_VERSION })
|
||||
}
|
||||
|
||||
export async function persistBackups(list: BackupEntry[]): Promise<void> {
|
||||
await backupsStore.set("backupList", list);
|
||||
await backupsStore.save();
|
||||
export async function loadUpdates(): Promise<PersistedUpdates> {
|
||||
const raw = await platformService.loadStore('updates')
|
||||
const data = (raw ?? {}) as Record<string, unknown>
|
||||
return {
|
||||
libraryUpdates: (data.libraryUpdates ?? []) as unknown[],
|
||||
lastLibraryRefresh: (data.lastLibraryRefresh ?? 0) as number,
|
||||
acknowledgedUpdateIds: (data.acknowledgedUpdateIds ?? []) as number[],
|
||||
}
|
||||
}
|
||||
|
||||
export async function resetAuthSettings(): Promise<void> {
|
||||
const current = await settingsStore.get<any>("settings") ?? {};
|
||||
current.serverAuthMode = "NONE";
|
||||
current.serverAuthUser = "";
|
||||
current.serverAuthPass = "";
|
||||
await settingsStore.set("settings", current);
|
||||
await settingsStore.save();
|
||||
localStorage.removeItem("moku-credential-vault");
|
||||
export async function saveUpdates(data: PersistedUpdates): Promise<void> {
|
||||
await platformService.saveStore('updates', data)
|
||||
}
|
||||
|
||||
export async function loadBackups(): Promise<{ url: string; name: string }[]> {
|
||||
const raw = await platformService.loadStore('backups')
|
||||
const data = (raw ?? {}) as Record<string, unknown>
|
||||
|
||||
if (!data.backupList) {
|
||||
try {
|
||||
const legacyRaw = typeof window !== 'undefined' ? localStorage.getItem('moku_backups') : null
|
||||
if (legacyRaw) {
|
||||
const list = JSON.parse(legacyRaw) as { url: string; name: string }[]
|
||||
localStorage.removeItem('moku_backups')
|
||||
await saveBackups(list)
|
||||
return list
|
||||
}
|
||||
} catch {}
|
||||
return []
|
||||
}
|
||||
|
||||
return data.backupList as { url: string; name: string }[]
|
||||
}
|
||||
|
||||
export async function saveBackups(list: { url: string; name: string }[]): Promise<void> {
|
||||
await platformService.saveStore('backups', { backupList: list })
|
||||
}
|
||||
Reference in New Issue
Block a user