mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Feat: Reworked ENTIRE Project for Readability
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
export type NavPage =
|
||||
| "home" | "library" | "sources" | "explore"
|
||||
| "downloads" | "extensions" | "history" | "search" | "tracking";
|
||||
|
||||
class AppStore {
|
||||
navPage: NavPage = $state("home");
|
||||
settingsOpen: boolean = $state(false);
|
||||
searchPrefill: string = $state("");
|
||||
genreFilter: string = $state("");
|
||||
|
||||
setNavPage(next: NavPage) { this.navPage = next; }
|
||||
setSettingsOpen(next: boolean) { this.settingsOpen = next; }
|
||||
setSearchPrefill(next: string) { this.searchPrefill = next; }
|
||||
setGenreFilter(next: string) { this.genreFilter = next; }
|
||||
}
|
||||
|
||||
export const app = new AppStore();
|
||||
|
||||
export function setNavPage(next: NavPage) { app.setNavPage(next); }
|
||||
export function setSettingsOpen(next: boolean) { app.setSettingsOpen(next); }
|
||||
export function setSearchPrefill(next: string) { app.setSearchPrefill(next); }
|
||||
export function setGenreFilter(next: string) { app.setGenreFilter(next); }
|
||||
@@ -0,0 +1,107 @@
|
||||
import { store } from "@store/state.svelte";
|
||||
import { probeServer, loginBasic } from "@core/auth";
|
||||
|
||||
const MAX_ATTEMPTS = 10;
|
||||
|
||||
export const boot = $state({
|
||||
serverProbeOk: false,
|
||||
failed: false,
|
||||
notConfigured: false,
|
||||
loginRequired: false,
|
||||
unsupportedMode: false,
|
||||
loginUser: "",
|
||||
loginPass: "",
|
||||
loginError: null as string | null,
|
||||
loginBusy: false,
|
||||
});
|
||||
|
||||
let cancelProbe = false;
|
||||
|
||||
export function startProbe() {
|
||||
cancelProbe = false;
|
||||
boot.failed = false;
|
||||
boot.loginRequired = false;
|
||||
boot.unsupportedMode = false;
|
||||
let tries = 0;
|
||||
|
||||
async function probe() {
|
||||
if (cancelProbe) return;
|
||||
tries++;
|
||||
const result = await probeServer();
|
||||
if (cancelProbe) return;
|
||||
|
||||
if (result === "ok") {
|
||||
boot.serverProbeOk = true;
|
||||
boot.loginRequired = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result === "auth_required") {
|
||||
boot.serverProbeOk = true;
|
||||
const savedUser = store.settings.serverAuthUser?.trim() ?? "";
|
||||
const savedPass = store.settings.serverAuthPass?.trim() ?? "";
|
||||
if (savedUser && savedPass) {
|
||||
try {
|
||||
await loginBasic(savedUser, savedPass);
|
||||
boot.loginRequired = false;
|
||||
return;
|
||||
} catch {}
|
||||
}
|
||||
boot.loginRequired = true;
|
||||
boot.loginUser = store.settings.serverAuthUser ?? "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (result === "unsupported_mode") {
|
||||
boot.serverProbeOk = true;
|
||||
boot.unsupportedMode = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (tries >= MAX_ATTEMPTS) { boot.failed = true; return; }
|
||||
setTimeout(probe, 750);
|
||||
}
|
||||
|
||||
setTimeout(probe, 800);
|
||||
}
|
||||
|
||||
export function stopProbe() {
|
||||
cancelProbe = true;
|
||||
}
|
||||
|
||||
export async function submitLogin(onSuccess: () => void) {
|
||||
if (!boot.loginUser.trim() || !boot.loginPass.trim()) {
|
||||
boot.loginError = "Username and password are required";
|
||||
return;
|
||||
}
|
||||
boot.loginBusy = true;
|
||||
boot.loginError = null;
|
||||
try {
|
||||
await loginBasic(boot.loginUser.trim(), boot.loginPass.trim());
|
||||
boot.loginRequired = false;
|
||||
boot.loginPass = "";
|
||||
boot.loginError = null;
|
||||
onSuccess();
|
||||
} catch (e: any) {
|
||||
boot.loginError = e?.message ?? "Login failed";
|
||||
} finally {
|
||||
boot.loginBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
export function retryBoot() {
|
||||
boot.serverProbeOk = false;
|
||||
boot.failed = false;
|
||||
boot.notConfigured = false;
|
||||
boot.loginRequired = false;
|
||||
boot.unsupportedMode = false;
|
||||
startProbe();
|
||||
}
|
||||
|
||||
export function bypassBoot(onReady: () => void) {
|
||||
cancelProbe = true;
|
||||
boot.serverProbeOk = true;
|
||||
boot.loginRequired = false;
|
||||
boot.unsupportedMode = false;
|
||||
onReady();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { connect, disconnect, setActivity, clearActivity } from "tauri-plugin-discord-rpc-api";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import type { Manga, Chapter } from "@types";
|
||||
|
||||
const APP_ID = "1487894643613106298";
|
||||
const FALLBACK_IMAGE = "moku_logo";
|
||||
const BUTTONS = [
|
||||
{ label: "GitHub", url: "https://github.com/Youwes09/Moku" },
|
||||
{ label: "Discord", url: "https://discord.gg/Jq3pwuNqPp" },
|
||||
];
|
||||
|
||||
let sessionStart: number | null = null;
|
||||
let unlisten: (() => void) | 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> {
|
||||
sessionStart = Date.now();
|
||||
unlisten = await listen("discord-rpc://running", ({ payload }) => {
|
||||
if (payload) setIdle().catch(() => {});
|
||||
});
|
||||
await connect(APP_ID).catch(() => {});
|
||||
}
|
||||
|
||||
export async function setReading(manga: Manga, chapter: Chapter): Promise<void> {
|
||||
await setActivity({
|
||||
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: BUTTONS,
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
export async function setIdle(): Promise<void> {
|
||||
await setActivity({
|
||||
details: "Browsing",
|
||||
timestamps: { start: sessionStart ?? Date.now() },
|
||||
assets: { largeImage: FALLBACK_IMAGE, largeText: "Moku" },
|
||||
buttons: BUTTONS,
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
export async function clearReading(): Promise<void> {
|
||||
await clearActivity().catch(() => {});
|
||||
}
|
||||
|
||||
export async function destroyRpc(): Promise<void> {
|
||||
unlisten?.();
|
||||
unlisten = null;
|
||||
sessionStart = null;
|
||||
await disconnect().catch(() => {});
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './app.svelte';
|
||||
export * from './boot.svelte';
|
||||
export * from './notifications.svelte';
|
||||
export * from './state.svelte';
|
||||
@@ -0,0 +1,36 @@
|
||||
export interface Toast {
|
||||
id: string;
|
||||
kind: "success" | "error" | "info" | "download";
|
||||
title: string;
|
||||
body?: string;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
export interface ActiveDownload {
|
||||
chapterId: number;
|
||||
mangaId: number;
|
||||
progress: number;
|
||||
}
|
||||
|
||||
class NotificationStore {
|
||||
toasts: Toast[] = $state([]);
|
||||
activeDownloads: ActiveDownload[] = $state([]);
|
||||
|
||||
addToast(toast: Omit<Toast, "id">) {
|
||||
this.toasts = [...this.toasts, { ...toast, id: Math.random().toString(36).slice(2) }].slice(-5);
|
||||
}
|
||||
|
||||
dismissToast(id: string) {
|
||||
this.toasts = this.toasts.filter(x => x.id !== id);
|
||||
}
|
||||
|
||||
setActiveDownloads(next: ActiveDownload[]) {
|
||||
this.activeDownloads = next;
|
||||
}
|
||||
}
|
||||
|
||||
export const notifications = new NotificationStore();
|
||||
|
||||
export function addToast(toast: Omit<Toast, "id">) { notifications.addToast(toast); }
|
||||
export function dismissToast(id: string) { notifications.dismissToast(id); }
|
||||
export function setActiveDownloads(next: ActiveDownload[]) { notifications.setActiveDownloads(next); }
|
||||
+299
-604
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user