1.0.36
This commit is contained in:
63 files changed
+1324
-281
No files matched your search
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { TogglePill } from '$lib/components/ui/toggle-pill';
|
||||
import { themeStore } from '$lib/stores/theme';
|
||||
import { authStore } from '$lib/stores/auth';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
interface Props {
|
||||
userId?: number; // omit for global default (login page / auth-disabled)
|
||||
}
|
||||
|
||||
let { userId }: Props = $props();
|
||||
|
||||
const skipApply = $derived($authStore.loading ? true : ($authStore.authEnabled && !userId));
|
||||
|
||||
let checked = $state(false);
|
||||
$effect(() => {
|
||||
checked = $themeStore.coloredActionButtons;
|
||||
});
|
||||
|
||||
function onToggle(value: boolean) {
|
||||
checked = value;
|
||||
themeStore.setPreference('coloredActionButtons', value, userId, skipApply);
|
||||
toast.success(value ? 'Action buttons colored' : 'Action buttons reset to default');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-1">
|
||||
<div class="flex items-center gap-3">
|
||||
<Label>Colored grid buttons</Label>
|
||||
<TogglePill {checked} onchange={onToggle} />
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground">Use semantic colors instead of muted gray</p>
|
||||
</div>
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { Sun, Moon, Type, AArrowUp, Table, Terminal, CodeXml } from 'lucide-svelte';
|
||||
import { Sun, Moon, Type, AArrowUp, Table, Terminal, CodeXml, MousePointerClick } from 'lucide-svelte';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { lightThemes, darkThemes, fonts, monospaceFonts } from '$lib/themes';
|
||||
import { themeStore, applyTheme, type FontSize } from '$lib/stores/theme';
|
||||
import { themeStore, applyTheme, type FontSize, type ActionIconSize } from '$lib/stores/theme';
|
||||
import { authStore } from '$lib/stores/auth';
|
||||
|
||||
// Preload all monospace Google Fonts so dropdown previews render correctly
|
||||
@@ -20,6 +20,13 @@
|
||||
{ id: 'xlarge', name: 'Extra Large' }
|
||||
];
|
||||
|
||||
const actionIconSizes: { id: ActionIconSize; name: string }[] = [
|
||||
{ id: 'small', name: 'Small' },
|
||||
{ id: 'normal', name: 'Normal' },
|
||||
{ id: 'large', name: 'Large' },
|
||||
{ id: 'xlarge', name: 'Extra Large' }
|
||||
];
|
||||
|
||||
interface Props {
|
||||
userId?: number; // Pass userId for per-user settings, undefined for global
|
||||
}
|
||||
@@ -39,6 +46,7 @@
|
||||
let selectedFont = $state('system');
|
||||
let selectedFontSize = $state<FontSize>('normal');
|
||||
let selectedGridFontSize = $state<FontSize>('normal');
|
||||
let selectedActionIconSize = $state<ActionIconSize>('normal');
|
||||
let selectedTerminalFont = $state('system-mono');
|
||||
let selectedEditorFont = $state('system-mono');
|
||||
|
||||
@@ -66,6 +74,7 @@
|
||||
selectedFont = $themeStore.font;
|
||||
selectedFontSize = $themeStore.fontSize;
|
||||
selectedGridFontSize = $themeStore.gridFontSize;
|
||||
selectedActionIconSize = $themeStore.actionIconSize;
|
||||
selectedTerminalFont = $themeStore.terminalFont;
|
||||
selectedEditorFont = $themeStore.editorFont;
|
||||
} else {
|
||||
@@ -79,6 +88,7 @@
|
||||
selectedFont = data.font || 'system';
|
||||
selectedFontSize = data.fontSize || 'normal';
|
||||
selectedGridFontSize = data.gridFontSize || 'normal';
|
||||
selectedActionIconSize = data.actionIconSize || 'normal';
|
||||
selectedTerminalFont = data.terminalFont || 'system-mono';
|
||||
selectedEditorFont = data.editorFont || 'system-mono';
|
||||
}
|
||||
@@ -96,6 +106,7 @@
|
||||
selectedFont = $themeStore.font;
|
||||
selectedFontSize = $themeStore.fontSize;
|
||||
selectedGridFontSize = $themeStore.gridFontSize;
|
||||
selectedActionIconSize = $themeStore.actionIconSize;
|
||||
selectedTerminalFont = $themeStore.terminalFont;
|
||||
selectedEditorFont = $themeStore.editorFont;
|
||||
}
|
||||
@@ -131,6 +142,12 @@
|
||||
await themeStore.setPreference('gridFontSize', value as FontSize, userId, skipApply);
|
||||
}
|
||||
|
||||
async function handleActionIconSizeChange(value: string | undefined) {
|
||||
if (!value) return;
|
||||
selectedActionIconSize = value as ActionIconSize;
|
||||
await themeStore.setPreference('actionIconSize', value as ActionIconSize, userId, skipApply);
|
||||
}
|
||||
|
||||
async function handleTerminalFontChange(value: string | undefined) {
|
||||
if (!value) return;
|
||||
selectedTerminalFont = value;
|
||||
@@ -290,6 +307,30 @@
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- Grid Buttons Size (#1072) -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<MousePointerClick class="w-4 h-4 text-muted-foreground" />
|
||||
<Label>Grid buttons</Label>
|
||||
</div>
|
||||
<Select.Root type="single" value={selectedActionIconSize} onValueChange={handleActionIconSizeChange}>
|
||||
<Select.Trigger class="w-56">
|
||||
{#each actionIconSizes as size}
|
||||
{#if size.id === selectedActionIconSize}
|
||||
<span>{size.name}</span>
|
||||
{/if}
|
||||
{/each}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each actionIconSizes as size}
|
||||
<Select.Item value={size.id}>
|
||||
<span>{size.name}</span>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- Terminal Font -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import * as Sidebar from '$lib/components/ui/sidebar';
|
||||
@@ -23,16 +24,25 @@
|
||||
ClipboardList,
|
||||
Activity,
|
||||
Timer,
|
||||
LibraryBig
|
||||
LibraryBig,
|
||||
CircleArrowUp
|
||||
} from 'lucide-svelte';
|
||||
import { licenseStore } from '$lib/stores/license';
|
||||
import { authStore, hasAnyAccess } from '$lib/stores/auth';
|
||||
import { selfUpdate } from '$lib/stores/self-update';
|
||||
import { appSettings } from '$lib/stores/settings';
|
||||
import * as Avatar from '$lib/components/ui/avatar';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
|
||||
const appVersion = __APP_VERSION__ || 'unknown';
|
||||
const buildCommit = __BUILD_COMMIT__ ?? null;
|
||||
|
||||
onMount(() => {
|
||||
// One-shot Dockhand update check (#1146). Result is cached for the
|
||||
// browser session — the Settings → About page reads the same store.
|
||||
selfUpdate.checkOnce();
|
||||
});
|
||||
|
||||
import type { Permissions } from '$lib/stores/auth';
|
||||
|
||||
// TypeScript interface for menu items
|
||||
@@ -111,6 +121,21 @@
|
||||
] as const;
|
||||
</script>
|
||||
|
||||
{#snippet versionTooltip()}
|
||||
<div class="space-y-0.5 text-left">
|
||||
<div class="flex items-center gap-1.5"><svg class="w-4 h-4 shrink-0" viewBox="0 0 24 18" fill="currentColor"><path d="M23.76 8.68c-.26-.18-.86-.58-1.53-.58-.24 0-.48.04-.72.12-.12-.84-.68-1.56-1.34-2.14l-.28-.22-.24.26c-.28.34-.48.72-.56 1.14-.1.42-.06.82.1 1.2-.42.22-.88.36-1.32.42-.24.04-.48.06-.72.06H.78a.77.77 0 0 0-.78.78c-.02 1.46.22 2.9.72 4.24.56 1.44 1.4 2.5 2.5 3.16 1.26.74 3.32 1.16 5.64 1.16.98 0 2-.1 2.98-.3a11.5 11.5 0 0 0 3.3-1.3 9.67 9.67 0 0 0 2.54-2.34c1.16-1.42 1.86-3.02 2.34-4.38h.2c1.22 0 1.98-.48 2.4-.9.28-.26.5-.58.64-.94l.08-.24-.28-.2zM2.74 8.84H4.7c.1 0 .18-.08.18-.18V7.02c0-.1-.08-.18-.18-.18H2.74c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zm2.72 0h1.96c.1 0 .18-.08.18-.18V7.02c0-.1-.08-.18-.18-.18H5.46c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zm2.76 0h1.96c.1 0 .18-.08.18-.18V7.02c0-.1-.08-.18-.18-.18H8.22c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zm2.76 0h1.96c.1 0 .18-.08.18-.18V7.02c0-.1-.08-.18-.18-.18h-1.96c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zM5.46 6.2h1.96c.1 0 .18-.08.18-.18V4.38c0-.1-.08-.18-.18-.18H5.46c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zm2.76 0h1.96c.1 0 .18-.08.18-.18V4.38c0-.1-.08-.18-.18-.18H8.22c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zm2.76 0h1.96c.1 0 .18-.08.18-.18V4.38c0-.1-.08-.18-.18-.18h-1.96c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zm0-2.64h1.96c.1 0 .18-.08.18-.18V1.74c0-.1-.08-.18-.18-.18h-1.96c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zm2.76 5.28h1.96c.1 0 .18-.08.18-.18V7.02c0-.1-.08-.18-.18-.18h-1.96c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18z"/></svg><span class="font-mono">fnsys/dockhand:{appVersion}</span></div>
|
||||
{#if buildCommit}
|
||||
<div>Commit: <span class="font-mono">{buildCommit.slice(0, 7)}</span></div>
|
||||
{/if}
|
||||
{#if $selfUpdate.updateAvailable && $selfUpdate.latestVersion}
|
||||
<div class="flex items-center gap-1.5 pt-1 text-amber-500">
|
||||
<CircleArrowUp class="w-3.5 h-3.5 shrink-0" />
|
||||
Update available: <span class="font-mono">v{$selfUpdate.latestVersion}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<Sidebar.Root collapsible="icon">
|
||||
<Sidebar.Header class="overflow-visible flex items-center justify-center p-0">
|
||||
<!-- Expanded state: logo + collapse button -->
|
||||
@@ -161,23 +186,48 @@
|
||||
</Sidebar.Group>
|
||||
</Sidebar.Content>
|
||||
|
||||
<!-- Version (expanded sidebar only) -->
|
||||
<div class="group-data-[state=collapsed]:hidden px-3 py-2 mt-auto text-center">
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<span class="text-[10px] text-muted-foreground/60 hover:text-muted-foreground transition-colors cursor-default">
|
||||
{appVersion}
|
||||
</span>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="top" align="start" sideOffset={8} class="text-xs">
|
||||
<div class="space-y-0.5">
|
||||
<div class="flex items-center gap-1.5"><svg class="w-4 h-4 shrink-0" viewBox="0 0 24 18" fill="currentColor"><path d="M23.76 8.68c-.26-.18-.86-.58-1.53-.58-.24 0-.48.04-.72.12-.12-.84-.68-1.56-1.34-2.14l-.28-.22-.24.26c-.28.34-.48.72-.56 1.14-.1.42-.06.82.1 1.2-.42.22-.88.36-1.32.42-.24.04-.48.06-.72.06H.78a.77.77 0 0 0-.78.78c-.02 1.46.22 2.9.72 4.24.56 1.44 1.4 2.5 2.5 3.16 1.26.74 3.32 1.16 5.64 1.16.98 0 2-.1 2.98-.3a11.5 11.5 0 0 0 3.3-1.3 9.67 9.67 0 0 0 2.54-2.34c1.16-1.42 1.86-3.02 2.34-4.38h.2c1.22 0 1.98-.48 2.4-.9.28-.26.5-.58.64-.94l.08-.24-.28-.2zM2.74 8.84H4.7c.1 0 .18-.08.18-.18V7.02c0-.1-.08-.18-.18-.18H2.74c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zm2.72 0h1.96c.1 0 .18-.08.18-.18V7.02c0-.1-.08-.18-.18-.18H5.46c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zm2.76 0h1.96c.1 0 .18-.08.18-.18V7.02c0-.1-.08-.18-.18-.18H8.22c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zm2.76 0h1.96c.1 0 .18-.08.18-.18V7.02c0-.1-.08-.18-.18-.18h-1.96c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zM5.46 6.2h1.96c.1 0 .18-.08.18-.18V4.38c0-.1-.08-.18-.18-.18H5.46c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zm2.76 0h1.96c.1 0 .18-.08.18-.18V4.38c0-.1-.08-.18-.18-.18H8.22c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zm2.76 0h1.96c.1 0 .18-.08.18-.18V4.38c0-.1-.08-.18-.18-.18h-1.96c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zm0-2.64h1.96c.1 0 .18-.08.18-.18V1.74c0-.1-.08-.18-.18-.18h-1.96c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18zm2.76 5.28h1.96c.1 0 .18-.08.18-.18V7.02c0-.1-.08-.18-.18-.18h-1.96c-.1 0-.18.08-.18.18v1.64c0 .1.08.18.18.18z"/></svg><span class="font-mono">fnsys/dockhand:{appVersion}</span></div>
|
||||
{#if buildCommit}
|
||||
<div>Commit: <span class="font-mono">{buildCommit.slice(0, 7)}</span></div>
|
||||
{/if}
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
<!-- Collapsed-only update indicator at the bottom. Same CSS-popover trick
|
||||
as the expanded version row — anchored to the icon's top edge, grows
|
||||
upward, opens to the right of the narrow sidebar. -->
|
||||
{#if $selfUpdate.updateAvailable}
|
||||
<div class="hidden group-data-[state=collapsed]:flex justify-center mt-auto pb-2 relative group/update">
|
||||
<a
|
||||
href="/settings?tab=about"
|
||||
class="inline-flex p-1 rounded-md hover:bg-sidebar-accent transition-colors"
|
||||
aria-label="Dockhand update available"
|
||||
>
|
||||
<CircleArrowUp class="w-4 h-4 text-amber-500 {$appSettings.highlightUpdates ? 'glow-amber' : ''}" />
|
||||
</a>
|
||||
<div
|
||||
role="tooltip"
|
||||
class="pointer-events-none absolute left-0 bottom-full mb-1 whitespace-nowrap rounded-md border bg-popover text-popover-foreground text-xs px-3 py-1.5 shadow-lg opacity-0 group-hover/update:opacity-100 transition-opacity z-50"
|
||||
>
|
||||
{@render versionTooltip()}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Version (expanded sidebar only). Pure-CSS popover anchored to the row's
|
||||
top edge, so it always grows upward into available space. -->
|
||||
<div class="group-data-[state=collapsed]:hidden px-3 py-1 mt-auto text-center relative group/version">
|
||||
<span class="inline-flex items-center gap-1.5 text-[10px] text-muted-foreground/60 hover:text-muted-foreground transition-colors cursor-default">
|
||||
{appVersion}
|
||||
{#if $selfUpdate.updateAvailable}
|
||||
<a
|
||||
href="/settings?tab=about"
|
||||
class="inline-flex"
|
||||
aria-label="Dockhand update available"
|
||||
>
|
||||
<CircleArrowUp class="w-3 h-3 text-amber-500 {$appSettings.highlightUpdates ? 'glow-amber' : ''}" />
|
||||
</a>
|
||||
{/if}
|
||||
</span>
|
||||
<div
|
||||
role="tooltip"
|
||||
class="pointer-events-none absolute left-0 bottom-full mb-1 whitespace-nowrap rounded-md border bg-popover text-popover-foreground text-xs px-3 py-1.5 shadow-lg opacity-0 group-hover/version:opacity-100 transition-opacity z-50"
|
||||
>
|
||||
{@render versionTooltip()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User info footer (only when auth is enabled) -->
|
||||
|
||||
@@ -172,22 +172,24 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Generate hours array based on time format preference
|
||||
// Generate hours array based on time format preference. 24h shows just the
|
||||
// 2-digit hour so the combined picker reads "at 03 :15" rather than
|
||||
// the confusing "at 03:00 :15" (#1198).
|
||||
const hours = $derived(
|
||||
Array.from({ length: 24 }, (_, i) => ({
|
||||
value: String(i),
|
||||
label: is12Hour
|
||||
? i === 0 ? '12 AM' : i < 12 ? `${i} AM` : i === 12 ? '12 PM' : `${i - 12} PM`
|
||||
: i.toString().padStart(2, '0') + ':00'
|
||||
: i.toString().padStart(2, '0')
|
||||
}))
|
||||
);
|
||||
|
||||
const minutes = [
|
||||
{ value: '0', label: ':00' },
|
||||
{ value: '15', label: ':15' },
|
||||
{ value: '30', label: ':30' },
|
||||
{ value: '45', label: ':45' }
|
||||
];
|
||||
// 5-minute granularity — fine enough to stagger notifications without
|
||||
// drowning the dropdown in 60 entries (#1198).
|
||||
const minutes = Array.from({ length: 12 }, (_, i) => ({
|
||||
value: String(i * 5),
|
||||
label: ':' + (i * 5).toString().padStart(2, '0')
|
||||
}));
|
||||
|
||||
const daysOfWeek = [
|
||||
{ value: '1', label: 'Monday' },
|
||||
|
||||
Reference in new issue
Block a user