chore(lint): resolve launcher lint issues

Format files and fix unused code, reactive collection usage, redundant mustaches, and missing each-block keys.
This commit is contained in:
HorizonCode committed 2026-11-03 16:21:23 +01:00
1 parent 3ef59f94ee
commit 93536af6a6
10 files changed
+1860 -1763

No files matched your search

+10 -6
View File
@@ -1,7 +1,3 @@
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 { config } from './config';
import {
reloadThemes,
@@ -9,6 +5,10 @@ import {
updateInstalledThemeTags,
updateInstalledThemeWorkshopId,
} from './themes';
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';
export interface CommunityThemeAuthor {
id: number;
@@ -159,7 +159,9 @@ const DEFAULT_THEMES_SERVICE_URL = 'https://themes.ez-pp.farm';
export async function getThemesServiceUrl(): Promise<string> {
const customUrl = await config.value<string>('themes_service_url').get('');
return customUrl.trim().length > 0 ? customUrl.trim().replace(/\/+$/, '') : DEFAULT_THEMES_SERVICE_URL;
return customUrl.trim().length > 0
? customUrl.trim().replace(/\/+$/, '')
: DEFAULT_THEMES_SERVICE_URL;
}
/**
@@ -273,7 +275,9 @@ export async function fetchCommunityThemeDetails(themeId: string): Promise<Commu
return item;
}
export async function toggleCommunityThemeLike(themeId: string): Promise<{ liked: boolean; likeCount: number }> {
export async function toggleCommunityThemeLike(
themeId: string
): Promise<{ liked: boolean; likeCount: number }> {
const baseUrl = await getThemesServiceUrl();
const headers = await getAuthHeaders();
@@ -1,8 +1,5 @@
<script lang="ts">
import { Bell, CheckCheck, LoaderCircle, Megaphone, RefreshCw, ShieldAlert, X } from '@lucide/svelte';
import { Portal } from 'bits-ui';
import { Button } from '$lib/components/ui/button';
import { currentUser } from '$lib/userAuthentication';
import {
fetchNotifications,
fetchUnreadNotificationCount,
@@ -10,6 +7,17 @@
markNotificationRead,
type UserNotification,
} from '$lib/notifications';
import { currentUser } from '$lib/userAuthentication';
import {
Bell,
CheckCheck,
LoaderCircle,
Megaphone,
RefreshCw,
ShieldAlert,
X,
} from '@lucide/svelte';
import { Portal } from 'bits-ui';
let open = $state(false);
let loading = $state(false);
@@ -80,7 +88,10 @@
beforeId: notifications[notifications.length - 1].id,
});
const known = new Set(notifications.map((item) => item.id));
notifications = [...notifications, ...data.notifications.filter((item) => !known.has(item.id))];
notifications = [
...notifications,
...data.notifications.filter((item) => !known.has(item.id)),
];
unreadCount = data.unreadCount;
hasMore = data.hasMore;
} catch (err) {
@@ -149,158 +160,165 @@
{#if open}
<Portal>
<button
type="button"
class="fixed inset-0 z-[80] cursor-default bg-black/30 backdrop-blur-[1px]"
aria-label="Close notifications"
onclick={() => (open = false)}
></button>
<button
type="button"
class="fixed inset-0 z-[80] cursor-default bg-black/30 backdrop-blur-[1px]"
aria-label="Close notifications"
onclick={() => (open = false)}
></button>
<div
class="fixed bottom-4 left-[4.75rem] z-[90] flex max-h-[min(34rem,calc(100vh-2rem))] w-[22rem] flex-col overflow-hidden rounded-xl border border-theme-800 bg-theme-950 shadow-2xl"
role="dialog"
aria-modal="true"
aria-labelledby="notification-hub-title"
>
<div class="flex items-start justify-between gap-3 border-b border-theme-800/70 p-4">
<div>
<h2 id="notification-hub-title" class="flex items-center gap-2 text-sm font-semibold text-white">
<Bell class="size-4 text-primary" />
Notifications
{#if unreadCount > 0}
<span class="rounded-full bg-primary/20 px-1.5 py-0.5 text-[10px] text-primary">
{unreadCount} new
</span>
{/if}
</h2>
<p class="mt-1 text-[11px] text-muted-foreground">Moderation updates and system messages.</p>
</div>
<div class="flex items-center gap-1">
<Button
variant="ghost"
size="icon"
class="size-7 text-muted-foreground hover:text-white"
title="Refresh notifications"
onclick={() => void loadNotifications()}
disabled={loading}
>
<RefreshCw class="size-3.5 {loading ? 'animate-spin' : ''}" />
</Button>
<Button
variant="ghost"
size="icon"
class="size-7 text-muted-foreground hover:text-white"
title="Close"
onclick={() => (open = false)}
>
<X class="size-4" />
</Button>
</div>
</div>
<div class="flex items-center justify-between border-b border-theme-800/50 px-4 py-2">
<span class="text-[10px] text-muted-foreground">
{unreadCount > 0 ? `${unreadCount} unread` : 'All caught up'}
</span>
<Button
variant="ghost"
size="sm"
class="h-6 gap-1 px-1.5 text-[10px] text-muted-foreground hover:text-white"
onclick={() => void markAllRead()}
disabled={markingAll || unreadCount === 0}
>
{#if markingAll}
<LoaderCircle class="size-3 animate-spin" />
{:else}
<CheckCheck class="size-3" />
{/if}
Mark all read
</Button>
</div>
<div class="min-h-0 flex-1 overflow-y-auto p-2">
{#if error}
<div class="m-2 rounded-lg border border-red-900/40 bg-red-950/20 p-3 text-[11px] text-red-300">
{error}
</div>
{/if}
{#if loading && notifications.length === 0}
<div class="flex h-40 items-center justify-center gap-2 text-xs text-muted-foreground">
<LoaderCircle class="size-4 animate-spin text-primary" /> Loading notifications...
</div>
{:else if notifications.length === 0}
<div class="flex h-40 flex-col items-center justify-center gap-2 px-6 text-center">
<Bell class="size-7 text-muted-foreground/40" />
<p class="text-xs text-muted-foreground">No notifications yet.</p>
<p class="text-[10px] text-muted-foreground/70">
Moderation decisions about your themes and reports will appear here.
<div
class="fixed bottom-4 left-[4.75rem] z-[90] flex max-h-[min(34rem,calc(100vh-2rem))] w-[22rem] flex-col overflow-hidden rounded-xl border border-theme-800 bg-theme-950 shadow-2xl"
role="dialog"
aria-modal="true"
aria-labelledby="notification-hub-title"
>
<div class="flex items-start justify-between gap-3 border-b border-theme-800/70 p-4">
<div>
<h2
id="notification-hub-title"
class="flex items-center gap-2 text-sm font-semibold text-white"
>
<Bell class="size-4 text-primary" />
Notifications
{#if unreadCount > 0}
<span class="rounded-full bg-primary/20 px-1.5 py-0.5 text-[10px] text-primary">
{unreadCount} new
</span>
{/if}
</h2>
<p class="mt-1 text-[11px] text-muted-foreground">
Moderation updates and system messages.
</p>
</div>
{:else}
<div class="space-y-1">
{#each notifications as notification (notification.id)}
<button
type="button"
class="w-full rounded-lg border p-3 text-left transition-colors {notification.readAt
? 'border-transparent bg-transparent hover:bg-theme-900/50'
: 'border-primary/20 bg-primary/5 hover:bg-primary/10'}"
onclick={() => void markRead(notification)}
>
<div class="flex items-start gap-2.5">
<div
class="mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full {notification.type ===
'theme_hidden' || notification.type === 'theme_taken_down'
? 'bg-red-500/10 text-red-300'
: notification.type === 'report_resolved'
? 'bg-emerald-500/10 text-emerald-300'
: notification.type === 'system'
? 'bg-cyan-500/10 text-cyan-300'
: 'bg-amber-500/10 text-amber-300'}"
>
{#if notification.type === 'system'}
<Megaphone class="size-3.5" />
{:else}
<ShieldAlert class="size-3.5" />
{/if}
</div>
<div class="min-w-0 flex-1">
<div class="flex items-start justify-between gap-2">
<p class="text-[11px] font-semibold text-white">{notification.title}</p>
{#if !notification.readAt}
<span class="mt-1 size-1.5 shrink-0 rounded-full bg-primary"></span>
{/if}
</div>
<p class="mt-1 text-[10px] leading-relaxed text-muted-foreground">
{notification.message}
</p>
<p class="mt-1.5 text-[9px] text-muted-foreground/70">
{formatDate(notification.createdAt)}
</p>
</div>
</div>
</button>
{/each}
<div class="flex items-center gap-1">
<Button
variant="ghost"
size="icon"
class="size-7 text-muted-foreground hover:text-white"
title="Refresh notifications"
onclick={() => void loadNotifications()}
disabled={loading}
>
<RefreshCw class="size-3.5 {loading ? 'animate-spin' : ''}" />
</Button>
<Button
variant="ghost"
size="icon"
class="size-7 text-muted-foreground hover:text-white"
title="Close"
onclick={() => (open = false)}
>
<X class="size-4" />
</Button>
</div>
</div>
{#if hasMore}
<div class="flex justify-center p-2">
<Button
variant="ghost"
size="sm"
class="h-7 text-[10px] text-muted-foreground"
onclick={() => void loadOlder()}
disabled={loadingMore}
>
{#if loadingMore}
<LoaderCircle class="mr-1 size-3 animate-spin" />
{/if}
Load older
</Button>
<div class="flex items-center justify-between border-b border-theme-800/50 px-4 py-2">
<span class="text-[10px] text-muted-foreground">
{unreadCount > 0 ? `${unreadCount} unread` : 'All caught up'}
</span>
<Button
variant="ghost"
size="sm"
class="h-6 gap-1 px-1.5 text-[10px] text-muted-foreground hover:text-white"
onclick={() => void markAllRead()}
disabled={markingAll || unreadCount === 0}
>
{#if markingAll}
<LoaderCircle class="size-3 animate-spin" />
{:else}
<CheckCheck class="size-3" />
{/if}
Mark all read
</Button>
</div>
<div class="min-h-0 flex-1 overflow-y-auto p-2">
{#if error}
<div
class="m-2 rounded-lg border border-red-900/40 bg-red-950/20 p-3 text-[11px] text-red-300"
>
{error}
</div>
{/if}
{/if}
{#if loading && notifications.length === 0}
<div class="flex h-40 items-center justify-center gap-2 text-xs text-muted-foreground">
<LoaderCircle class="size-4 animate-spin text-primary" /> Loading notifications...
</div>
{:else if notifications.length === 0}
<div class="flex h-40 flex-col items-center justify-center gap-2 px-6 text-center">
<Bell class="size-7 text-muted-foreground/40" />
<p class="text-xs text-muted-foreground">No notifications yet.</p>
<p class="text-[10px] text-muted-foreground/70">
Moderation decisions about your themes and reports will appear here.
</p>
</div>
{:else}
<div class="space-y-1">
{#each notifications as notification (notification.id)}
<button
type="button"
class="w-full rounded-lg border p-3 text-left transition-colors {notification.readAt
? 'border-transparent bg-transparent hover:bg-theme-900/50'
: 'border-primary/20 bg-primary/5 hover:bg-primary/10'}"
onclick={() => void markRead(notification)}
>
<div class="flex items-start gap-2.5">
<div
class="mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full {notification.type ===
'theme_hidden' || notification.type === 'theme_taken_down'
? 'bg-red-500/10 text-red-300'
: notification.type === 'report_resolved'
? 'bg-emerald-500/10 text-emerald-300'
: notification.type === 'system'
? 'bg-cyan-500/10 text-cyan-300'
: 'bg-amber-500/10 text-amber-300'}"
>
{#if notification.type === 'system'}
<Megaphone class="size-3.5" />
{:else}
<ShieldAlert class="size-3.5" />
{/if}
</div>
<div class="min-w-0 flex-1">
<div class="flex items-start justify-between gap-2">
<p class="text-[11px] font-semibold text-white">{notification.title}</p>
{#if !notification.readAt}
<span class="mt-1 size-1.5 shrink-0 rounded-full bg-primary"></span>
{/if}
</div>
<p class="mt-1 text-[10px] leading-relaxed text-muted-foreground">
{notification.message}
</p>
<p class="mt-1.5 text-[9px] text-muted-foreground/70">
{formatDate(notification.createdAt)}
</p>
</div>
</div>
</button>
{/each}
</div>
{#if hasMore}
<div class="flex justify-center p-2">
<Button
variant="ghost"
size="sm"
class="h-7 text-[10px] text-muted-foreground"
onclick={() => void loadOlder()}
disabled={loadingMore}
>
{#if loadingMore}
<LoaderCircle class="mr-1 size-3 animate-spin" />
{/if}
Load older
</Button>
</div>
{/if}
{/if}
</div>
</div>
</div>
</Portal>
{/if}
File diff suppressed because it is too large. Load diff
@@ -1,5 +1,6 @@
<script lang="ts">
import Logo from '$assets/logo.png';
import { publishThemeToWorkshop } from '$lib/communityThemes';
import FontPreview from '$lib/components/theme/FontPreview.svelte';
import ThemeBehaviorInspector from '$lib/components/theme/ThemeBehaviorInspector.svelte';
import ThemeElementInspector from '$lib/components/theme/ThemeElementInspector.svelte';
@@ -102,7 +103,6 @@
toSafeName,
updateInstalledThemeWorkshopId,
} from '$lib/themes';
import { publishThemeToWorkshop } from '$lib/communityThemes';
import type { EZPPUser, EZPPUserInfo } from '$lib/types';
import { currentUser } from '$lib/userAuthentication';
import { osuInstallationPath } from '$lib/userSettings';
@@ -5091,8 +5091,8 @@
Allow forking
</Label>
<p class="text-[10px] leading-relaxed text-muted-foreground">
Let other creators fork this theme from the workshop. Takes effect when you publish
or update it; existing forks are not affected.
Let other creators fork this theme from the workshop. Takes effect when you
publish or update it; existing forks are not affected.
</p>
</div>
<Checkbox
@@ -6900,7 +6900,9 @@
</div>
{#if $currentUser}
<div class="mt-4 flex items-center justify-between rounded-xl border border-theme-800/70 bg-theme-900/40 px-3.5 py-2.5">
<div
class="mt-4 flex items-center justify-between rounded-xl border border-theme-800/70 bg-theme-900/40 px-3.5 py-2.5"
>
<div class="flex items-center gap-2.5">
<img
src={`https://a.ez-pp.farm/${$currentUser.id}`}
@@ -6914,7 +6916,9 @@
<div class="flex items-center gap-1.5">
<span class="text-xs font-semibold text-white">{$currentUser.name}</span>
{#if $currentUser.donor}
<span class="rounded bg-amber-500/20 px-1 py-0.2 text-[8px] font-bold text-amber-300 border border-amber-500/30">
<span
class="rounded bg-amber-500/20 px-1 py-0.2 text-[8px] font-bold text-amber-300 border border-amber-500/30"
>
Supporter
</span>
{/if}
@@ -6953,7 +6957,9 @@
{#if publishThemeId}
<p class="mt-1 truncate font-mono text-[11px] text-theme-100">{publishThemeId}</p>
{:else}
<p class="mt-1 text-[11px] text-muted-foreground">Assigned by EZPPFarm on publish</p>
<p class="mt-1 text-[11px] text-muted-foreground">
Assigned by EZPPFarm on publish
</p>
{/if}
</div>
<div>
@@ -6962,7 +6968,7 @@
</span>
{#if (definition.tags ?? []).length > 0}
<div class="mt-1 flex flex-wrap gap-1">
{#each definition.tags ?? [] as tag}
{#each definition.tags ?? [] as tag (tag)}
<span class="rounded bg-theme-800 px-1.5 py-0.5 text-[10px] text-theme-100">
{tag}
</span>
@@ -1,15 +1,15 @@
<script lang="ts">
import { Flag, LoaderCircle, X } from '@lucide/svelte';
import { Button } from '$lib/components/ui/button';
import * as Dialog from '$lib/components/ui/dialog';
import { Label } from '$lib/components/ui/label';
import * as Select from '$lib/components/ui/select';
import Textarea from '$lib/components/ui/textarea/textarea.svelte';
import {
reportCommunityTheme,
type CommunityTheme,
type ThemeReportCategory,
} from '$lib/communityThemes';
import { Button } from '$lib/components/ui/button';
import * as Dialog from '$lib/components/ui/dialog';
import { Label } from '$lib/components/ui/label';
import * as Select from '$lib/components/ui/select';
import Textarea from '$lib/components/ui/textarea/textarea.svelte';
import { Flag, LoaderCircle, X } from '@lucide/svelte';
import { sileo } from 'sileo';
let {
@@ -79,8 +79,8 @@
<Flag class="size-4 text-amber-300" /> Report theme
</Dialog.Title>
<Dialog.Description class="mt-1 text-[11px] text-muted-foreground">
Reporting <span class="text-white/80">{theme.name}</span> by {theme.author.name}. Reports
are visible to moderators only.
Reporting <span class="text-white/80">{theme.name}</span> by {theme.author.name}.
Reports are visible to moderators only.
</Dialog.Description>
</div>
<Button
@@ -97,7 +97,10 @@
<div class="space-y-3 p-4">
<div class="space-y-1.5">
<Label for="installed-report-category" class="text-[10px] uppercase tracking-wide text-white/70">
<Label
for="installed-report-category"
class="text-[10px] uppercase tracking-wide text-white/70"
>
Category
</Label>
<Select.Root
@@ -122,7 +125,10 @@
</div>
<div class="space-y-1.5">
<Label for="installed-report-details" class="text-[10px] uppercase tracking-wide text-white/70">
<Label
for="installed-report-details"
class="text-[10px] uppercase tracking-wide text-white/70"
>
Details
</Label>
<Textarea
@@ -141,7 +147,9 @@
</div>
{#if error}
<p class="rounded-lg border border-red-900/40 bg-red-950/20 p-2.5 text-[11px] text-red-300">
<p
class="rounded-lg border border-red-900/40 bg-red-950/20 p-2.5 text-[11px] text-red-300"
>
{error}
</p>
{/if}
+2 -4
View File
@@ -1,10 +1,8 @@
<script lang="ts">
import { Dialog as DialogPrimitive, type WithoutChildrenOrChild } from 'bits-ui';
let {
open = $bindable(false),
...restProps
}: WithoutChildrenOrChild<DialogPrimitive.RootProps> = $props();
let { open = $bindable(false), ...restProps }: WithoutChildrenOrChild<DialogPrimitive.RootProps> =
$props();
</script>
<DialogPrimitive.Root bind:open {...restProps} />
+1 -1
View File
@@ -13,10 +13,10 @@
* theme videos and videos picked in the editor (served as /preview/ URLs) are
* transcoded to a cached H.264 copy by the Rust backend before playback.
*/
import { custom_theme_volume } from './global';
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
import { get, writable } from 'svelte/store';
import { custom_theme_volume } from './global';
type MediaCleanup = () => void;
+7 -20
View File
@@ -506,7 +506,9 @@ export const reloadThemes = async () => {
const ACTIVE_STATUSES = new Set(['downloading', 'extracting', 'deleting']);
const updatedThemes = storeThemes
.filter((storeTheme) => installedMap.has(storeTheme.name) || ACTIVE_STATUSES.has(storeTheme.status))
.filter(
(storeTheme) => installedMap.has(storeTheme.name) || ACTIVE_STATUSES.has(storeTheme.status)
)
.map((storeTheme) => {
if (ACTIVE_STATUSES.has(storeTheme.status)) return storeTheme;
const theme = installedMap.get(storeTheme.name);
@@ -564,10 +566,7 @@ export const saveThemeProject = async (
return themeFolder;
};
const updateInstalledThemeManifest = async (
folderName: string,
patch: Record<string, unknown>
) => {
const updateInstalledThemeManifest = async (folderName: string, patch: Record<string, unknown>) => {
const themeFolder = await path.join(await themesDirectory(), folderName);
const configPath = await path.join(themeFolder, 'theme.json');
if (!(await fs.exists(configPath))) {
@@ -585,10 +584,7 @@ const updateInstalledThemeManifest = async (
}
const updated = { ...(parsed as Record<string, unknown>), ...patch };
await fs.writeFile(
configPath,
new TextEncoder().encode(`${JSON.stringify(updated, null, 2)}\n`)
);
await fs.writeFile(configPath, new TextEncoder().encode(`${JSON.stringify(updated, null, 2)}\n`));
};
/** Write workshop tags into an installed theme's manifest without dropping other fields. */
@@ -596,10 +592,8 @@ export const updateInstalledThemeTags = async (folderName: string, tags: string[
updateInstalledThemeManifest(folderName, { tags });
/** Remember the server-assigned workshop ID so later publishes update the same theme. */
export const updateInstalledThemeWorkshopId = async (
folderName: string,
workshopId: string
) => updateInstalledThemeManifest(folderName, { workshopId });
export const updateInstalledThemeWorkshopId = async (folderName: string, workshopId: string) =>
updateInstalledThemeManifest(folderName, { workshopId });
export const exportThemeFolder = async (folderName: string, destinationPath: string) => {
const themeFolder = await path.join(await themesDirectory(), folderName);
@@ -632,10 +626,3 @@ export const toSafeName = (name: string) => {
.replace(/[. ]+$/g, '_')
);
};
const generateRandomString = (length: number) => {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
return Array.from({ length }, () =>
characters.charAt(Math.floor(Math.random() * characters.length))
).join('');
};
+1 -3
View File
@@ -266,9 +266,7 @@ export function trackMediaContainer(node: HTMLElement) {
const media = new Set<HTMLMediaElement>();
function update() {
custom_theme_audio_playing.set(
Array.from(media).some((el) => !el.paused)
);
custom_theme_audio_playing.set(Array.from(media).some((el) => !el.paused));
}
function register(el: HTMLMediaElement) {