Skip to content

Commit e8a8232

Browse files
authored
Make sync result message clearer and add FW loading state (#2027)
* Adapt sync message so it's more balanced * Add pulse animations for loading sync-state values * Reword sync result message * Add accordian component * Make SyncDialog a drawer on mobile * Make sync arrows responsive * Put commit counts and explanation behind tooltip * Prevent ms from being used in sync dialog * Use new shimmer animation for loading-text * Make relative-date tooltip target include date * Clarify sync tabs with titles, tooltip and new name * Apply punctuation and wording suggestions. * Reword lexbox sync explanation
1 parent 04aad41 commit e8a8232

25 files changed

Lines changed: 793 additions & 249 deletions

frontend/viewer/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,5 @@ For formatted values you can do this:
3737
Add a new component with this:
3838

3939
```bash
40-
pnpx shadcn-svelte@next add context-menu
40+
npx shadcn-svelte@next add context-menu
4141
```

frontend/viewer/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@
7474
"tailwindcss": "catalog:",
7575
"tailwindcss-animate": "^1.0.7",
7676
"tslib": "catalog:",
77-
"typescript-eslint": "catalog:",
7877
"tw-colors": "^3.3.2",
7978
"typescript": "catalog:",
79+
"typescript-eslint": "catalog:",
8080
"vaul-svelte": "1.0.0-next.6",
8181
"vite": "catalog:",
8282
"vite-plugin-webfont-dl": "^3.11.1",
@@ -85,15 +85,15 @@
8585
"@vitejs/plugin-basic-ssl": "catalog:"
8686
},
8787
"dependencies": {
88+
"@ffmpeg/core": "0.12.10",
89+
"@ffmpeg/ffmpeg": "0.12.15",
90+
"@ffmpeg/util": "0.12.2",
8891
"@formatjs/intl-durationformat": "^0.7.4",
8992
"@lingui/core": "^5.3.3",
9093
"@microsoft/dotnet-js-interop": "^8.0.0",
9194
"@microsoft/signalr": "^8.0.7",
9295
"autoprefixer": "^10.4.21",
9396
"fast-json-patch": "^3.1.1",
94-
"@ffmpeg/ffmpeg": "0.12.15",
95-
"@ffmpeg/util": "0.12.2",
96-
"@ffmpeg/core": "0.12.10",
9797
"jsdom": "^26.1.0",
9898
"just-throttle": "^4.2.0",
9999
"postcss": "catalog:",

frontend/viewer/src/app.postcss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
.app {
2727
@apply bg-background text-foreground;
2828
}
29+
30+
.loading-text {
31+
@apply bg-shimmer animate-shimmer rounded-md text-transparent select-none pointer-events-none;
32+
}
2933
}
3034

3135
.font-inter {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
// used in the is-mobile.svelte hook
1+
// used in the is-mobile.svelte hook and for the tailwind md breakpoint
22
export const MOBILE_BREAKPOINT = 768;

frontend/viewer/src/lib/components/responsive-dialog/responsive-dialog.svelte

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,35 @@
33
import * as Dialog from '$lib/components/ui/dialog';
44
import * as Drawer from '$lib/components/ui/drawer';
55
import { buttonVariants } from '$lib/components/ui/button';
6-
import type {PopoverTriggerProps, WithChildren} from 'bits-ui';
6+
import type {PopoverTriggerProps, WithChildren, WithoutChildren} from 'bits-ui';
77
import {Icon} from '../ui/icon';
88
import type {ComponentProps} from 'svelte';
9+
import {cn} from '$lib/utils';
910
1011
type TriggerSnippet = PopoverTriggerProps['child'];
12+
type ContentProps = WithoutChildren<ComponentProps<typeof Dialog.Content>>;
1113
type Props = ComponentProps<typeof Drawer.Root> & {
1214
open?: boolean,
1315
title: string,
1416
trigger?: TriggerSnippet,
17+
contentProps?: ContentProps,
1518
};
1619
20+
1721
let {
1822
open = $bindable(false),
1923
children,
2024
title,
2125
trigger,
26+
contentProps,
2227
...rest
2328
}: WithChildren<Props> = $props();
2429
</script>
2530

2631
{#if !IsMobile.value}
2732
<Dialog.Root bind:open {...rest}>
2833
<Dialog.Trigger child={trigger} />
29-
<Dialog.Content class="min-h-auto">
34+
<Dialog.Content {...contentProps} class={cn('min-h-auto', contentProps?.class)}>
3035
<Dialog.Header>
3136
<Dialog.Title>{title}</Dialog.Title>
3237
</Dialog.Header>
@@ -36,7 +41,7 @@
3641
{:else}
3742
<Drawer.Root bind:open {...rest}>
3843
<Drawer.Trigger child={trigger} />
39-
<Drawer.Content>
44+
<Drawer.Content {...contentProps}>
4045
<Drawer.Close class={buttonVariants({ variant: 'ghost', size: 'icon', class: 'absolute top-4 right-4 z-10' })}>
4146
<Icon icon="i-mdi-close" />
4247
</Drawer.Close>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script lang="ts">
2+
import { Accordion as AccordionPrimitive, type WithoutChild } from 'bits-ui';
3+
import { cn } from '$lib/utils.js';
4+
5+
let {
6+
ref = $bindable(null),
7+
class: className,
8+
children,
9+
...restProps
10+
}: WithoutChild<AccordionPrimitive.ContentProps> = $props();
11+
</script>
12+
13+
<AccordionPrimitive.Content
14+
bind:ref
15+
class={cn(
16+
'data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm transition-all',
17+
className,
18+
)}
19+
{...restProps}
20+
>
21+
<div class="pb-4 pt-0">
22+
{@render children?.()}
23+
</div>
24+
</AccordionPrimitive.Content>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script lang="ts">
2+
import { Accordion as AccordionPrimitive } from 'bits-ui';
3+
import { cn } from '$lib/utils.js';
4+
5+
let { ref = $bindable(null), class: className, ...restProps }: AccordionPrimitive.ItemProps = $props();
6+
</script>
7+
8+
<AccordionPrimitive.Item bind:ref class={cn('border-b', className)} {...restProps} />
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script lang="ts">
2+
import { Accordion as AccordionPrimitive, type WithoutChild } from 'bits-ui';
3+
import { cn } from '$lib/utils.js';
4+
import { Icon } from '../icon';
5+
6+
let {
7+
ref = $bindable(null),
8+
class: className,
9+
level = 3,
10+
children,
11+
...restProps
12+
}: WithoutChild<AccordionPrimitive.TriggerProps> & {
13+
level?: AccordionPrimitive.HeaderProps['level'];
14+
} = $props();
15+
</script>
16+
17+
<AccordionPrimitive.Header {level} class="flex">
18+
<AccordionPrimitive.Trigger
19+
bind:ref
20+
class={cn(
21+
'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>.i-mdi-chevron-down]:rotate-180',
22+
className,
23+
)}
24+
{...restProps}
25+
>
26+
{@render children?.()}
27+
<Icon icon="i-mdi-chevron-down" class="size-4 shrink-0 transition-transform duration-200" />
28+
</AccordionPrimitive.Trigger>
29+
</AccordionPrimitive.Header>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {Accordion as AccordionPrimitive} from 'bits-ui';
2+
import Content from './accordion-content.svelte';
3+
import Item from './accordion-item.svelte';
4+
import Trigger from './accordion-trigger.svelte';
5+
// eslint-disable-next-line @typescript-eslint/naming-convention
6+
const Root = AccordionPrimitive.Root;
7+
8+
export {
9+
Root,
10+
Content,
11+
Item,
12+
Trigger,
13+
//
14+
Root as Accordion,
15+
Content as AccordionContent,
16+
Item as AccordionItem,
17+
Trigger as AccordionTrigger,
18+
};

frontend/viewer/src/lib/components/ui/format/format-duration.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {locale} from 'svelte-i18n-lingui';
55

66
const currentLocale = fromStore(locale);
77
type Duration = Pick<Intl.DurationLike, 'days' | 'hours' | 'minutes' | 'seconds' | 'milliseconds'>;
8+
export type SmallestUnit = 'hours' | 'minutes' | 'seconds' | 'milliseconds';
89

910
function limitDurationUnits(duration: Duration, maxUnits: number): Duration {
1011
const units: (keyof Duration)[] = ['days', 'hours', 'minutes', 'seconds', 'milliseconds'];
@@ -52,17 +53,17 @@ export function formatDigitalDuration(value: Duration) {
5253
});
5354
}
5455

55-
export function formatDuration(value: Duration, smallestUnit?: 'hours' | 'minutes' | 'seconds' | 'milliseconds', options?: Intl.DurationFormatOptions, maxUnits?: number) {
56+
export function formatDuration(value: Duration, smallestUnit?: SmallestUnit, options?: Intl.DurationFormatOptions, maxUnits?: number) {
5657
const formatter = new Intl.DurationFormat(currentLocale.current, options);//has been polyfilled in main.ts
5758
const normalized = normalizeDuration(value, smallestUnit);
5859
const limitedDuration = maxUnits ? limitDurationUnits(normalized, maxUnits) : normalized;
5960
return formatter.format(limitedDuration);
6061
}
6162

62-
export function normalizeDuration(value: Duration, smallestUnit?: 'hours' | 'minutes' | 'seconds' | 'milliseconds'): Duration
63+
export function normalizeDuration(value: Duration, smallestUnit?: SmallestUnit): Duration
6364
export function normalizeDuration(value: Duration, smallestUnit: 'seconds'): Omit<Duration, 'milliseconds'>
6465
export function normalizeDuration(value: Duration): Duration
65-
export function normalizeDuration(value: Duration, smallestUnit?: 'hours' | 'minutes' | 'seconds' | 'milliseconds'): Duration {
66+
export function normalizeDuration(value: Duration, smallestUnit?: SmallestUnit): Duration {
6667
const msPerHour = 3_600_000;
6768
const msPerMinute = 60_000;
6869
const msPerSecond = 1_000;

0 commit comments

Comments
 (0)