feat(themes): polish installed cards and add workshop theme details
Installed themes now use a 16:9 preview with stable card spacing and a single overflow menu for edit, restore, update, report and uninstall actions. Community theme previews and titles open a large detail dialog with a full-size preview, metadata, tags, review status and the relevant install action.
This commit is contained in:
1 parent
5613c3be25
commit
47c022642f
2 files changed
+435
-100
No files matched your search
+144
-96
@@ -163,6 +163,7 @@
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Clock,
|
||||
Ellipsis,
|
||||
ExternalLink,
|
||||
Flag,
|
||||
Globe,
|
||||
@@ -505,6 +506,29 @@
|
||||
await config.value<string>('theme').set(theme.name);
|
||||
};
|
||||
|
||||
const uninstallInstalledTheme = async (theme: Theme) => {
|
||||
try {
|
||||
const deleteResult = await deleteTheme(theme);
|
||||
if (deleteResult) {
|
||||
sileo.success({
|
||||
title: 'Theme uninstalled',
|
||||
description: `"${theme.name}" was removed from your library.`,
|
||||
});
|
||||
} else {
|
||||
sileo.error({
|
||||
title: 'Could not uninstall theme',
|
||||
description: 'The theme files could not be removed.',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
sileo.error({
|
||||
title: 'Could not uninstall theme',
|
||||
description: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const updateInstalledTheme = async (theme: Theme) => {
|
||||
const wasActive = $active_custom_theme?.folder_name === theme.folder_name;
|
||||
const updateResult = await downloadTheme(theme, true);
|
||||
@@ -3911,6 +3935,28 @@
|
||||
{#each filteredThemes as theme, index (theme.folder_name)}
|
||||
{@const isActive =
|
||||
$active_custom_theme?.folder_name === theme.folder_name}
|
||||
{@const canEditTheme =
|
||||
theme.status === 'installed' && Boolean(theme.definition)}
|
||||
{@const canRestoreTheme =
|
||||
theme.status === 'installed' && (theme.backups?.length ?? 0) > 0}
|
||||
{@const canUpdateTheme =
|
||||
theme.status === 'installed' && theme.updateAvailable}
|
||||
{@const canReportTheme =
|
||||
Boolean($currentUser && ownedWorkshopLoaded) &&
|
||||
!isOwnInstalledTheme(theme) &&
|
||||
theme.status === 'installed' &&
|
||||
theme.name !== 'Default' &&
|
||||
theme.folder_name !== 'default'}
|
||||
{@const canUninstallTheme =
|
||||
(theme.status === 'installed' || theme.status === 'deleting') &&
|
||||
!isActive &&
|
||||
theme.name !== 'Default'}
|
||||
{@const hasThemeMenu =
|
||||
canEditTheme ||
|
||||
canRestoreTheme ||
|
||||
canUpdateTheme ||
|
||||
canReportTheme ||
|
||||
canUninstallTheme}
|
||||
<div
|
||||
class="group flex flex-col overflow-hidden rounded-xl border bg-theme-900/30 {isActive
|
||||
? 'border-primary/60 ring-1 ring-primary/15'
|
||||
@@ -3925,7 +3971,7 @@
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="relative h-36 w-full shrink-0 overflow-hidden bg-theme-950/60"
|
||||
class="relative aspect-video w-full shrink-0 overflow-hidden bg-theme-950/60"
|
||||
>
|
||||
<div
|
||||
class="h-full w-full {$reduceAnimations
|
||||
@@ -4034,13 +4080,11 @@
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{#if theme.description}
|
||||
<p
|
||||
class="line-clamp-2 text-[10px] leading-snug text-muted-foreground"
|
||||
>
|
||||
{theme.description}
|
||||
</p>
|
||||
{/if}
|
||||
<p
|
||||
class="min-h-8 line-clamp-2 text-[10px] leading-snug text-muted-foreground"
|
||||
>
|
||||
{theme.description}
|
||||
</p>
|
||||
<div class="mt-auto flex items-center gap-1 pt-0.5">
|
||||
<Button
|
||||
class="relative h-8 min-w-0 flex-1 overflow-hidden px-2 text-xs {theme.status ===
|
||||
@@ -4089,94 +4133,98 @@
|
||||
/>
|
||||
</span>
|
||||
</Button>
|
||||
{#if theme.status === 'installed' && theme.definition}
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
class="size-7 shrink-0"
|
||||
title="Edit theme"
|
||||
onclick={() => openThemeEditor(theme)}
|
||||
>
|
||||
<ThemeIcon name="edit" set={iconSet} class="size-3.5" />
|
||||
</Button>
|
||||
{/if}
|
||||
{#if theme.status === 'installed' && (theme.backups?.length ?? 0) > 0}
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
class="size-7 shrink-0"
|
||||
title="Restore previous version ({new Date(
|
||||
theme.backups![0].createdAt
|
||||
).toLocaleDateString()})"
|
||||
onclick={() => requestThemeRestore(theme, theme.backups![0])}
|
||||
>
|
||||
<ThemeIcon name="history" set={iconSet} class="size-3.5" />
|
||||
</Button>
|
||||
{/if}
|
||||
{#if theme.status === 'installed' && theme.updateAvailable}
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
class="size-7 shrink-0"
|
||||
title={`Update theme${theme.availableVersion ? ` to v${theme.availableVersion}` : ''}`}
|
||||
onclick={() => updateInstalledTheme(theme)}
|
||||
>
|
||||
<ThemeIcon name="download" set={iconSet} class="size-3.5" />
|
||||
</Button>
|
||||
{/if}
|
||||
{#if (theme.status === 'installed' || theme.status === 'deleting') && !isActive && theme.name !== 'Default'}
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="icon"
|
||||
class="size-7 shrink-0"
|
||||
title="Uninstall theme"
|
||||
disabled={theme.status !== 'installed'}
|
||||
onclick={async () => {
|
||||
try {
|
||||
const deleteResult = await deleteTheme(theme);
|
||||
if (deleteResult) {
|
||||
sileo.success({
|
||||
title: 'Yaay!',
|
||||
description: 'Theme uninstalled successfully!',
|
||||
});
|
||||
} else {
|
||||
sileo.error({
|
||||
title: 'Uhhm..',
|
||||
description: 'Failed to uninstall theme.',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
sileo.error({
|
||||
title: 'Uhhm..',
|
||||
description: 'Failed to uninstall theme.',
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if theme.status === 'deleting'}
|
||||
<ThemeIcon
|
||||
name="loader"
|
||||
set={iconSet}
|
||||
class="size-3.5 animate-spin"
|
||||
/>
|
||||
{:else}
|
||||
<ThemeIcon name="trash" set={iconSet} class="size-3.5" />
|
||||
{/if}
|
||||
</Button>
|
||||
{/if}
|
||||
{#if $currentUser && ownedWorkshopLoaded && !isOwnInstalledTheme(theme) && theme.status === 'installed' && theme.name !== 'Default' && theme.folder_name !== 'default'}
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
class="size-7 shrink-0 text-muted-foreground hover:bg-amber-500/15 hover:text-amber-300"
|
||||
title="Report theme"
|
||||
aria-label={`Report ${theme.name}`}
|
||||
disabled={installedReportLoading}
|
||||
onclick={() => void openInstalledReport(theme)}
|
||||
>
|
||||
<Flag class="size-3.5" />
|
||||
</Button>
|
||||
{#if hasThemeMenu}
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button
|
||||
{...props}
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
class="size-7 shrink-0 text-muted-foreground hover:text-white"
|
||||
title="More theme actions"
|
||||
aria-label={`More actions for ${theme.name}`}
|
||||
>
|
||||
<Ellipsis class="size-3.5" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
align="end"
|
||||
class="w-48 border-theme-700 bg-theme-950 text-theme-50"
|
||||
>
|
||||
{#if canEditTheme}
|
||||
<DropdownMenu.Item
|
||||
class="gap-2 text-[11px]"
|
||||
onclick={() => openThemeEditor(theme)}
|
||||
>
|
||||
<ThemeIcon name="edit" set={iconSet} class="size-3.5" />
|
||||
Edit theme
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canRestoreTheme}
|
||||
<DropdownMenu.Item
|
||||
class="gap-2 text-[11px]"
|
||||
onclick={() =>
|
||||
requestThemeRestore(theme, theme.backups![0])}
|
||||
>
|
||||
<ThemeIcon
|
||||
name="history"
|
||||
set={iconSet}
|
||||
class="size-3.5"
|
||||
/>
|
||||
Restore previous version
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canUpdateTheme}
|
||||
<DropdownMenu.Item
|
||||
class="gap-2 text-[11px]"
|
||||
onclick={() => updateInstalledTheme(theme)}
|
||||
>
|
||||
<ThemeIcon
|
||||
name="download"
|
||||
set={iconSet}
|
||||
class="size-3.5"
|
||||
/>
|
||||
Update theme
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canReportTheme}
|
||||
<DropdownMenu.Item
|
||||
class="gap-2 text-[11px]"
|
||||
disabled={installedReportLoading}
|
||||
onclick={() => void openInstalledReport(theme)}
|
||||
>
|
||||
<Flag class="size-3.5" />
|
||||
Report theme
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canUninstallTheme}
|
||||
<DropdownMenu.Separator class="my-1 bg-theme-800" />
|
||||
<DropdownMenu.Item
|
||||
class="gap-2 text-[11px] text-red-300 data-[highlighted]:bg-red-500/15 data-[highlighted]:text-red-200"
|
||||
disabled={theme.status !== 'installed'}
|
||||
onclick={() => void uninstallInstalledTheme(theme)}
|
||||
>
|
||||
{#if theme.status === 'deleting'}
|
||||
<ThemeIcon
|
||||
name="loader"
|
||||
set={iconSet}
|
||||
class="size-3.5 animate-spin"
|
||||
/>
|
||||
Uninstalling...
|
||||
{:else}
|
||||
<ThemeIcon
|
||||
name="trash"
|
||||
set={iconSet}
|
||||
class="size-3.5"
|
||||
/>
|
||||
Uninstall theme
|
||||
{/if}
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user