@futo-org/backups-orchestrator-ui 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/dist/components/backends/BackendItem.svelte +28 -24
  2. package/dist/components/backends/BackendsList.svelte +33 -34
  3. package/dist/components/backends/BackendsList.svelte.d.ts +1 -0
  4. package/dist/components/backends/dialogs/SelectBackendModal.svelte +44 -55
  5. package/dist/components/backups/BackupItem.svelte +33 -26
  6. package/dist/components/backups/BackupStatus.svelte +200 -0
  7. package/dist/components/backups/BackupStatus.svelte.d.ts +19 -0
  8. package/dist/components/backups/BackupsList.svelte +17 -11
  9. package/dist/components/backups/dialogs/RestoreSnapshotModal.svelte +2 -2
  10. package/dist/components/backups/dialogs/SelectRepositoryModal.svelte +26 -36
  11. package/dist/components/backups/dialogs/ViewStatusModal.svelte +175 -0
  12. package/dist/components/backups/dialogs/ViewStatusModal.svelte.d.ts +8 -0
  13. package/dist/components/backups/run-history/RepositoryRunHistory.svelte +22 -10
  14. package/dist/components/backups/run-history/RepositoryRunHistory.svelte.d.ts +2 -0
  15. package/dist/components/backups/run-history/RepositoryRunHistoryItem.svelte +46 -20
  16. package/dist/components/backups/run-history/RepositoryRunHistoryPage.svelte +135 -0
  17. package/dist/components/backups/run-history/RepositoryRunHistoryPage.svelte.d.ts +8 -0
  18. package/dist/components/backups/run-history/RunHistoryModal.svelte +2 -2
  19. package/dist/components/backups/snapshots-list/RepositorySnapshotsList.svelte +22 -10
  20. package/dist/components/backups/snapshots-list/RepositorySnapshotsList.svelte.d.ts +2 -0
  21. package/dist/components/backups/snapshots-list/RepositorySnapshotsListItem.svelte +23 -21
  22. package/dist/components/backups/snapshots-list/RepositorySnapshotsPage.svelte +115 -0
  23. package/dist/components/backups/snapshots-list/RepositorySnapshotsPage.svelte.d.ts +9 -0
  24. package/dist/components/backups/snapshots-list/SnapshotsListModal.svelte +2 -2
  25. package/dist/components/dashboard/Dashboard.svelte +14 -7
  26. package/dist/components/dashboard/Dashboard.svelte.d.ts +1 -1
  27. package/dist/components/dashboard/DashboardAvgBackupTime.svelte +1 -1
  28. package/dist/components/dashboard/DashboardBackupHealth.svelte +5 -9
  29. package/dist/components/dashboard/DashboardBackupHealth.svelte.d.ts +1 -1
  30. package/dist/components/dashboard/DashboardCurrentUsage.svelte +1 -1
  31. package/dist/components/dashboard/DashboardDailyBackupTime.svelte +1 -1
  32. package/dist/components/dashboard/DashboardInstall.svelte +1 -1
  33. package/dist/components/dashboard/DashboardRecentBackups.svelte +47 -78
  34. package/dist/components/dashboard/DashboardTotalStored.svelte +1 -1
  35. package/dist/components/integrations/immich/ImmichBackupSettings.svelte +12 -0
  36. package/dist/components/integrations/immich/ImmichBackupSettings.svelte.d.ts +18 -0
  37. package/dist/components/integrations/immich/ImmichBackupsCard.svelte +73 -0
  38. package/dist/components/integrations/immich/ImmichBackupsCard.svelte.d.ts +12 -0
  39. package/dist/components/integrations/immich/ImmichBackupsNavButton.svelte +55 -0
  40. package/dist/components/integrations/immich/ImmichBackupsNavButton.svelte.d.ts +10 -0
  41. package/dist/components/integrations/immich/ImmichBackupsPage.svelte +15 -3
  42. package/dist/components/integrations/immich/ImmichBackupsPage.svelte.d.ts +7 -1
  43. package/dist/components/integrations/immich/ImmichBackupsSidebarItem.svelte +66 -0
  44. package/dist/components/integrations/immich/ImmichBackupsSidebarItem.svelte.d.ts +8 -0
  45. package/dist/components/integrations/immich/ImmichManageBackup.svelte +53 -14
  46. package/dist/components/integrations/immich/ImmichManageBackupOverview.svelte +45 -71
  47. package/dist/components/integrations/immich/ImmichOnboardingSetupFlow.svelte +123 -82
  48. package/dist/components/integrations/immich/ImmichOnboardingSetupFlow.svelte.d.ts +1 -1
  49. package/dist/components/integrations/immich/settings/ImmichSettingsBackupContents.svelte +313 -0
  50. package/dist/components/integrations/immich/settings/ImmichSettingsBackupContents.svelte.d.ts +3 -0
  51. package/dist/components/integrations/immich/settings/ImmichSettingsSchedule.svelte +218 -0
  52. package/dist/components/integrations/immich/settings/ImmichSettingsSchedule.svelte.d.ts +3 -0
  53. package/dist/components/integrations/immich/settings/ImmichSettingsStorage.svelte +207 -0
  54. package/dist/components/integrations/immich/settings/ImmichSettingsStorage.svelte.d.ts +3 -0
  55. package/dist/components/onboarding/RecoveryKeyDisplay.svelte +34 -20
  56. package/dist/components/onboarding/SampleOnboarding.svelte +57 -38
  57. package/dist/components/onboarding/stages/OnboardingStageBackupServices.svelte +10 -24
  58. package/dist/components/onboarding/steps/OnboardingStep1FinishSetup.svelte +44 -0
  59. package/dist/components/onboarding/steps/OnboardingStep1FinishSetup.svelte.d.ts +7 -0
  60. package/dist/components/onboarding/steps/OnboardingStep2ConnectAccount.svelte +50 -0
  61. package/dist/components/onboarding/steps/OnboardingStep2ConnectAccount.svelte.d.ts +7 -0
  62. package/dist/components/onboarding/steps/OnboardingStep3SaveRecoveryKey.svelte +45 -0
  63. package/dist/components/onboarding/steps/OnboardingStep3SaveRecoveryKey.svelte.d.ts +8 -0
  64. package/dist/components/onboarding/steps/OnboardingStep4FirstBackup.svelte +67 -0
  65. package/dist/components/onboarding/steps/OnboardingStep4FirstBackup.svelte.d.ts +9 -0
  66. package/dist/components/onboarding/steps/OnboardingStepLayout.svelte +58 -0
  67. package/dist/components/onboarding/steps/OnboardingStepLayout.svelte.d.ts +19 -0
  68. package/dist/components/onboarding/upsell/UpsellBackupFeaturesGrid.svelte +52 -0
  69. package/dist/components/onboarding/upsell/UpsellBackupFeaturesGrid.svelte.d.ts +18 -0
  70. package/dist/components/onboarding/upsell/UpsellExplainFutoBackups.svelte +33 -0
  71. package/dist/components/onboarding/upsell/UpsellExplainFutoBackups.svelte.d.ts +8 -0
  72. package/dist/components/onboarding/upsell/UpsellFrequentlyAskedQuestions.svelte +27 -0
  73. package/dist/components/onboarding/upsell/UpsellFrequentlyAskedQuestions.svelte.d.ts +10 -0
  74. package/dist/components/onboarding/upsell/UpsellFutoBackupsBadge.svelte +7 -0
  75. package/dist/components/onboarding/upsell/UpsellFutoBackupsBadge.svelte.d.ts +18 -0
  76. package/dist/components/onboarding/upsell/UpsellHowItWorks.svelte +46 -0
  77. package/dist/components/onboarding/upsell/UpsellHowItWorks.svelte.d.ts +6 -0
  78. package/dist/components/onboarding/upsell/UpsellModal.svelte +43 -0
  79. package/dist/components/onboarding/upsell/UpsellModal.svelte.d.ts +9 -0
  80. package/dist/components/onboarding/upsell/UpsellPage.svelte +39 -0
  81. package/dist/components/onboarding/upsell/UpsellPage.svelte.d.ts +13 -0
  82. package/dist/components/onboarding/upsell/UpsellProtectYourImmichLibrary.svelte +58 -0
  83. package/dist/components/onboarding/upsell/UpsellProtectYourImmichLibrary.svelte.d.ts +8 -0
  84. package/dist/components/schedules/RepositoryPicker.svelte +45 -38
  85. package/dist/components/schedules/ScheduleItem.svelte +32 -26
  86. package/dist/components/schedules/ScheduleList.svelte +16 -10
  87. package/dist/components/settings/GlobalSettings.svelte +8 -0
  88. package/dist/components/settings/GlobalSettings.svelte.d.ts +18 -0
  89. package/dist/components/settings/SettingsBackends.svelte +16 -0
  90. package/dist/components/settings/SettingsBackends.svelte.d.ts +18 -0
  91. package/dist/components/settings/SettingsBilling.svelte +69 -0
  92. package/dist/components/settings/SettingsBilling.svelte.d.ts +6 -0
  93. package/dist/components/settings/SettingsNotifications.svelte +47 -0
  94. package/dist/components/settings/SettingsNotifications.svelte.d.ts +6 -0
  95. package/dist/components/test/ImmichTestUi.svelte +129 -27
  96. package/dist/components/test/TestUi.svelte +4 -4
  97. package/dist/components/test/dashboard/ActiveJobs.svelte +2 -2
  98. package/dist/components/test/immich/MockImmichPhotos.svelte +36 -0
  99. package/dist/components/test/immich/MockImmichPhotos.svelte.d.ts +18 -0
  100. package/dist/components/test/immich/MockImmichPurchaseInfo.svelte +29 -0
  101. package/dist/components/test/immich/MockImmichPurchaseInfo.svelte.d.ts +18 -0
  102. package/dist/components/test/immich/MockImmichStorageSpace.svelte +37 -0
  103. package/dist/components/test/immich/MockImmichStorageSpace.svelte.d.ts +6 -0
  104. package/dist/components/ui/Accordion.svelte +67 -0
  105. package/dist/components/ui/Accordion.svelte.d.ts +11 -0
  106. package/dist/components/ui/ListToolbar.svelte +60 -0
  107. package/dist/components/ui/ListToolbar.svelte.d.ts +16 -0
  108. package/dist/components/ui/PageLayout.svelte +21 -9
  109. package/dist/components/ui/PageLayout.svelte.d.ts +1 -0
  110. package/dist/components/ui/Pagination.svelte +80 -0
  111. package/dist/components/ui/Pagination.svelte.d.ts +8 -0
  112. package/dist/components/ui/PathListField.svelte +17 -14
  113. package/dist/components/ui/StackList.svelte +18 -18
  114. package/dist/components/ui/StackList.svelte.d.ts +6 -27
  115. package/dist/components/ui/StackListItem.svelte +47 -25
  116. package/dist/components/ui/StackListItem.svelte.d.ts +5 -2
  117. package/dist/components/ui/StackListOption.svelte +33 -0
  118. package/dist/components/ui/StackListOption.svelte.d.ts +11 -0
  119. package/dist/components/ui/StackListPlaceholder.svelte +12 -0
  120. package/dist/components/ui/StackListPlaceholder.svelte.d.ts +7 -0
  121. package/dist/components/ui/StepNumber.svelte +20 -0
  122. package/dist/components/ui/StepNumber.svelte.d.ts +7 -0
  123. package/dist/components/util/Suspense.svelte +7 -3
  124. package/dist/components/util/YuccaContext.svelte +1 -1
  125. package/dist/events.js +1 -1
  126. package/dist/fetch-client.d.ts +2 -0
  127. package/dist/index.d.ts +6 -1
  128. package/dist/index.js +6 -1
  129. package/dist/services/backend.service.js +3 -0
  130. package/dist/services/immich.integration.service.d.ts +1 -1
  131. package/dist/services/immich.integration.service.js +2 -3
  132. package/dist/services/integrations.service.d.ts +1 -0
  133. package/dist/services/integrations.service.js +2 -1
  134. package/dist/services/log.service.svelte.d.ts +12 -0
  135. package/dist/services/log.service.svelte.js +15 -1
  136. package/dist/services/repository.service.js +9 -3
  137. package/dist/services/runHistory.service.js +2 -2
  138. package/dist/utils/format.d.ts +1 -1
  139. package/dist/utils/format.js +2 -2
  140. package/package.json +3 -3
  141. package/dist/components/backups/dialogs/ViewLogModal.svelte +0 -208
  142. package/dist/components/backups/dialogs/ViewLogModal.svelte.d.ts +0 -7
  143. package/dist/components/integrations/immich/ImmichConfigureBackup.svelte +0 -403
  144. package/dist/components/integrations/immich/ImmichConfigureBackup.svelte.d.ts +0 -9
  145. package/dist/components/integrations/immich/ImmichConfirmDefaultBackup.svelte +0 -80
  146. package/dist/components/integrations/immich/ImmichConfirmDefaultBackup.svelte.d.ts +0 -8
  147. package/dist/components/onboarding/stages/OnboardingStageKeyConfirm.svelte +0 -56
  148. package/dist/components/onboarding/stages/OnboardingStageKeyConfirm.svelte.d.ts +0 -9
  149. package/dist/components/onboarding/stages/OnboardingStageKeyIntro.svelte +0 -50
  150. package/dist/components/onboarding/stages/OnboardingStageKeyIntro.svelte.d.ts +0 -7
  151. package/dist/components/onboarding/stages/OnboardingStageKeySave.svelte +0 -44
  152. package/dist/components/onboarding/stages/OnboardingStageKeySave.svelte.d.ts +0 -8
  153. package/dist/components/onboarding/stages/OnboardingStageWelcome.svelte +0 -56
  154. package/dist/components/onboarding/stages/OnboardingStageWelcome.svelte.d.ts +0 -9
  155. package/dist/components/util/TimedButton.svelte +0 -37
  156. package/dist/components/util/TimedButton.svelte.d.ts +0 -7
@@ -19,7 +19,7 @@
19
19
  mdiCog,
20
20
  mdiViewDashboard,
21
21
  } from "@mdi/js";
22
- import BackendsList from "../backends/BackendsList.svelte";
22
+ import GlobalSettings from "../settings/GlobalSettings.svelte";
23
23
  import BackupsList from "../backups/BackupsList.svelte";
24
24
  import Dashboard from "../dashboard/Dashboard.svelte";
25
25
  import ScheduleList from "../schedules/ScheduleList.svelte";
@@ -37,7 +37,7 @@
37
37
  let route = $state("dashboard");
38
38
  </script>
39
39
 
40
- <AppShell>
40
+ <AppShell class="h-full">
41
41
  <AppShellHeader>
42
42
  <div class="flex items-center justify-between w-full px-4 py-2">
43
43
  <Heading>
@@ -118,12 +118,12 @@
118
118
 
119
119
  <div class="p-4 flex flex-col gap-2 max-w-6xl m-auto">
120
120
  {#if route === "dashboard"}
121
- <Dashboard local onNavigate={(target) => (route = target)} />
121
+ <Dashboard local onViewBackups={() => (route = "backups")} />
122
122
  {:else if route === "backups"}
123
123
  <BackupsList local />
124
124
  {:else if route === "config"}
125
125
  {#if !mock}
126
- <BackendsList />
126
+ <GlobalSettings />
127
127
  {/if}
128
128
  {:else if route === "schedules"}
129
129
  {#if !mock}
@@ -34,7 +34,7 @@
34
34
  import { SvelteMap } from "svelte/reactivity";
35
35
  import OnEvents from "../../util/OnEvents.svelte";
36
36
  import RelativeTime from "../../util/RelativeTime.svelte";
37
- import ViewLogModal from "../../backups/dialogs/ViewLogModal.svelte";
37
+ import ViewStatusModal from "../../backups/dialogs/ViewStatusModal.svelte";
38
38
 
39
39
  const LINGER_MS = 3000;
40
40
  const FADE_MS = 600;
@@ -171,7 +171,7 @@
171
171
  };
172
172
 
173
173
  const openLog = (logId: string) => {
174
- modalManager.open(ViewLogModal, { logId });
174
+ modalManager.open(ViewStatusModal, { logId });
175
175
  };
176
176
 
177
177
  const onCancel = (parentId: string) => handleCancelTask(parentId);
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ import { Stack, Text } from "@immich/ui";
3
+
4
+ const groups = [
5
+ { title: "Friday, 31 July 2026", count: 13 },
6
+ { title: "Thursday, 30 July 2026", count: 8 },
7
+ { title: "Sunday, 26 July 2026", count: 17 },
8
+ ];
9
+
10
+ const aspects = [1.5, 0.75, 1.33, 1, 0.7, 1.78, 1.2, 0.8, 1.4, 1];
11
+ const greys = [88, 82, 91, 78, 85, 93, 80, 87];
12
+
13
+ const seedOf = (group: number, index: number) => group * 7 + index * 3;
14
+ const aspect = (seed: number) => aspects[seed % aspects.length];
15
+ const grey = (seed: number) => greys[seed % greys.length];
16
+ </script>
17
+
18
+ <Stack gap={8} class="p-4">
19
+ {#each groups as group, groupIndex (group.title)}
20
+ <Stack gap={2}>
21
+ <Text size="small" fontWeight="medium" color="muted">{group.title}</Text>
22
+
23
+ <div class="flex flex-wrap gap-1">
24
+ {#each Array.from({ length: group.count }, (_, index) => index) as index (index)}
25
+ {@const seed = seedOf(groupIndex, index)}
26
+ <div
27
+ class="h-36 grow rounded-sm"
28
+ style="background-color: hsl(0 0% {grey(seed)}%); flex-basis: {aspect(
29
+ seed,
30
+ ) * 9}rem"
31
+ ></div>
32
+ {/each}
33
+ </div>
34
+ </Stack>
35
+ {/each}
36
+ </Stack>
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const MockImmichPhotos: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type MockImmichPhotos = InstanceType<typeof MockImmichPhotos>;
18
+ export default MockImmichPhotos;
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import { Icon, Logo } from "@immich/ui";
3
+ import { mdiInformationOutline } from "@mdi/js";
4
+ </script>
5
+
6
+ <div class="license-status text-sm">
7
+ <button
8
+ type="button"
9
+ onclick={() => {}}
10
+ class="flex w-full cursor-pointer place-content-center place-items-center justify-between rounded-lg border border-primary/20 p-2 shadow-md dark:border-primary/10 dark:bg-primary/10"
11
+ >
12
+ <div
13
+ class="flex w-full place-content-center place-items-center justify-between"
14
+ >
15
+ <div class="flex place-content-center place-items-center gap-1">
16
+ <Logo variant="icon" size="tiny" />
17
+ <p class="flex font-medium text-primary">Buy Immich</p>
18
+ </div>
19
+
20
+ <div>
21
+ <Icon
22
+ icon={mdiInformationOutline}
23
+ class="flex font-medium text-primary"
24
+ size="18"
25
+ />
26
+ </div>
27
+ </div>
28
+ </button>
29
+ </div>
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const MockImmichPurchaseInfo: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type MockImmichPurchaseInfo = InstanceType<typeof MockImmichPurchaseInfo>;
18
+ export default MockImmichPurchaseInfo;
@@ -0,0 +1,37 @@
1
+ <script lang="ts">
2
+ import { Icon, Meter, Text } from "@immich/ui";
3
+ import { mdiChevronRight, mdiCloudCheckOutline } from "@mdi/js";
4
+
5
+ type Props = {
6
+ onBackups: () => void;
7
+ };
8
+
9
+ const { onBackups }: Props = $props();
10
+ </script>
11
+
12
+ <div class="bg-light-100 overflow-hidden rounded-lg text-sm">
13
+ <div class="p-4">
14
+ <Meter
15
+ size="tiny"
16
+ class="bg-light-200"
17
+ containerClass="gap-2 leading-6"
18
+ label="Storage"
19
+ valueLabel="22.1 GiB of 100 GiB used"
20
+ value={0.221}
21
+ />
22
+ </div>
23
+
24
+ <button
25
+ type="button"
26
+ onclick={onBackups}
27
+ class="bg-success-50 text-success-700 flex w-full cursor-pointer items-center gap-2 px-3 py-3 text-start"
28
+ >
29
+ <Icon icon={mdiCloudCheckOutline} size="1em" class="shrink-0" />
30
+
31
+ <Text size="small" color="success" class="flex-1 truncate">
32
+ Backed up 2 hours ago
33
+ </Text>
34
+
35
+ <Icon icon={mdiChevronRight} size="1em" class="shrink-0" />
36
+ </button>
37
+ </div>
@@ -0,0 +1,6 @@
1
+ type Props = {
2
+ onBackups: () => void;
3
+ };
4
+ declare const MockImmichStorageSpace: import("svelte").Component<Props, {}, "">;
5
+ type MockImmichStorageSpace = ReturnType<typeof MockImmichStorageSpace>;
6
+ export default MockImmichStorageSpace;
@@ -0,0 +1,67 @@
1
+ <script lang="ts">
2
+ import { Icon, Text } from "@immich/ui";
3
+ import { mdiChevronDown } from "@mdi/js";
4
+ import type { Snippet } from "svelte";
5
+ import { slide } from "svelte/transition";
6
+
7
+ type Props = {
8
+ title: string;
9
+ subtitle?: string;
10
+ icon?: string;
11
+ isOpen?: boolean;
12
+ children?: Snippet;
13
+ };
14
+
15
+ let {
16
+ title,
17
+ subtitle,
18
+ icon,
19
+ isOpen = $bindable(false),
20
+ children,
21
+ }: Props = $props();
22
+ </script>
23
+
24
+ <div
25
+ class="border-primary/20 mt-4 rounded-2xl border-2 px-6 py-4 transition-all"
26
+ class:border-primary={isOpen}
27
+ class:shadow-md={isOpen}
28
+ >
29
+ <button
30
+ type="button"
31
+ aria-expanded={isOpen}
32
+ onclick={() => (isOpen = !isOpen)}
33
+ class="flex w-full place-items-center justify-between gap-4 text-start"
34
+ >
35
+ <div>
36
+ <div class="flex place-items-center gap-2">
37
+ {#if icon}
38
+ <Icon {icon} class="text-primary" size="1.5rem" aria-hidden="true" />
39
+ {/if}
40
+ <Text fontWeight="medium" color="primary">{title}</Text>
41
+ </div>
42
+
43
+ {#if subtitle}
44
+ <Text size="small" color="muted" class="mt-1">{subtitle}</Text>
45
+ {/if}
46
+ </div>
47
+
48
+ <div
49
+ class="hover:bg-primary/10 flex shrink-0 place-content-center place-items-center rounded-full p-3 transition-all"
50
+ >
51
+ <Icon
52
+ icon={mdiChevronDown}
53
+ size="1.25rem"
54
+ class="transition-transform duration-200 ease-in {isOpen
55
+ ? 'rotate-180'
56
+ : ''}"
57
+ aria-hidden="true"
58
+ />
59
+ </div>
60
+ </button>
61
+
62
+ {#if isOpen}
63
+ <div transition:slide={{ duration: 150 }} class="mb-2 ms-4">
64
+ {@render children?.()}
65
+ </div>
66
+ {/if}
67
+ </div>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from "svelte";
2
+ type Props = {
3
+ title: string;
4
+ subtitle?: string;
5
+ icon?: string;
6
+ isOpen?: boolean;
7
+ children?: Snippet;
8
+ };
9
+ declare const Accordion: import("svelte").Component<Props, {}, "isOpen">;
10
+ type Accordion = ReturnType<typeof Accordion>;
11
+ export default Accordion;
@@ -0,0 +1,60 @@
1
+ <script lang="ts">
2
+ import {
3
+ Button,
4
+ HStack,
5
+ Input,
6
+ menuManager,
7
+ type ActionItem,
8
+ } from "@immich/ui";
9
+ import { mdiFilterVariant, mdiMagnify, mdiSortVariant } from "@mdi/js";
10
+
11
+ type Menu = {
12
+ label: string;
13
+ items: ActionItem[];
14
+ active?: boolean;
15
+ };
16
+
17
+ type Props = {
18
+ placeholder: string;
19
+ search: string;
20
+ onSearch: (value: string) => void;
21
+ sort?: Menu;
22
+ filters?: Menu;
23
+ };
24
+
25
+ const { placeholder, search, onSearch, sort, filters }: Props = $props();
26
+
27
+ const openMenu = (items: ActionItem[]) => (event: Event) =>
28
+ void menuManager.show({
29
+ target: event.currentTarget as HTMLElement,
30
+ position: "bottom-left",
31
+ items,
32
+ });
33
+ </script>
34
+
35
+ <HStack class="items-center justify-between">
36
+ <Input
37
+ class="max-w-72"
38
+ leadingIcon={mdiMagnify}
39
+ {placeholder}
40
+ value={search}
41
+ oninput={(event) => onSearch(event.currentTarget.value)}
42
+ />
43
+
44
+ <HStack gap={2}>
45
+ {#each [{ menu: sort, icon: mdiSortVariant }, { menu: filters, icon: mdiFilterVariant }] as { menu, icon } (icon)}
46
+ {#if menu}
47
+ <Button
48
+ variant="outline"
49
+ size="small"
50
+ color={menu.active ? "primary" : "secondary"}
51
+ class="whitespace-nowrap"
52
+ leadingIcon={icon}
53
+ onclick={openMenu(menu.items)}
54
+ >
55
+ {menu.label}
56
+ </Button>
57
+ {/if}
58
+ {/each}
59
+ </HStack>
60
+ </HStack>
@@ -0,0 +1,16 @@
1
+ import { type ActionItem } from "@immich/ui";
2
+ type Menu = {
3
+ label: string;
4
+ items: ActionItem[];
5
+ active?: boolean;
6
+ };
7
+ type Props = {
8
+ placeholder: string;
9
+ search: string;
10
+ onSearch: (value: string) => void;
11
+ sort?: Menu;
12
+ filters?: Menu;
13
+ };
14
+ declare const ListToolbar: import("svelte").Component<Props, {}, "">;
15
+ type ListToolbar = ReturnType<typeof ListToolbar>;
16
+ export default ListToolbar;
@@ -4,26 +4,42 @@
4
4
  Button,
5
5
  ContextMenuButton,
6
6
  HStack,
7
+ IconButton,
7
8
  type ActionItem,
8
9
  } from "@immich/ui";
10
+ import { mdiArrowLeft } from "@mdi/js";
9
11
  import type { Snippet } from "svelte";
10
12
 
11
13
  type Props = {
12
14
  title?: string;
13
15
  actions?: ActionItem[];
16
+ onBack?: () => void;
14
17
  children?: Snippet;
15
18
  };
16
19
 
17
- const { title, actions = [], children }: Props = $props();
20
+ const { title, actions = [], onBack, children }: Props = $props();
18
21
  const { demoPadding } = options;
22
+
23
+ const hasHeader = $derived(Boolean(title) || actions.length > 0 || Boolean(onBack));
19
24
  </script>
20
25
 
21
- <main class="absolute inset-0">
22
- {#if title || actions.length > 0}
26
+ <main class="flex h-full w-full flex-col">
27
+ {#if hasHeader}
23
28
  <div
24
- class="absolute top-0 left-0 flex h-16 w-full place-items-center justify-between border-b p-2 text-dark"
29
+ class="flex h-16 shrink-0 place-items-center justify-between border-b p-2 text-dark"
25
30
  >
26
31
  <div class="flex gap-2 items-center">
32
+ {#if onBack}
33
+ <IconButton
34
+ icon={mdiArrowLeft}
35
+ aria-label="Back"
36
+ variant="ghost"
37
+ color="secondary"
38
+ shape="round"
39
+ onclick={onBack}
40
+ />
41
+ {/if}
42
+
27
43
  {#if title}
28
44
  <div class="outline-none pe-8">{title}</div>
29
45
  {/if}
@@ -57,11 +73,7 @@
57
73
  </div>
58
74
  {/if}
59
75
 
60
- <div
61
- class="absolute left-0 w-full overflow-y-auto {title || actions.length > 0
62
- ? 'top-16 h-[calc(100%-4rem)]'
63
- : 'top-0 h-full'} {$demoPadding ? 'p-4' : ''}"
64
- >
76
+ <div class="min-h-0 grow overflow-y-auto {$demoPadding ? 'p-4' : ''}">
65
77
  {@render children?.()}
66
78
  </div>
67
79
  </main>
@@ -3,6 +3,7 @@ import type { Snippet } from "svelte";
3
3
  type Props = {
4
4
  title?: string;
5
5
  actions?: ActionItem[];
6
+ onBack?: () => void;
6
7
  children?: Snippet;
7
8
  };
8
9
  declare const PageLayout: import("svelte").Component<Props, {}, "">;
@@ -0,0 +1,80 @@
1
+ <script lang="ts">
2
+ import { Button, HStack, IconButton, Text } from "@immich/ui";
3
+ import { mdiChevronLeft, mdiChevronRight } from "@mdi/js";
4
+
5
+ type Props = {
6
+ page: number;
7
+ pageCount: number;
8
+ onChange: (page: number) => void;
9
+ };
10
+
11
+ const { page, pageCount, onChange }: Props = $props();
12
+
13
+ const boundaryCount = 2;
14
+ const siblingCount = 0;
15
+
16
+ const range = (from: number, to: number) =>
17
+ Array.from({ length: Math.max(to - from + 1, 0) }, (_, index) => from + index);
18
+
19
+ const pages = $derived.by(() => {
20
+ const shown = new Set([
21
+ ...range(1, Math.min(boundaryCount, pageCount)),
22
+ ...range(Math.max(pageCount - boundaryCount + 1, 1), pageCount),
23
+ ...range(
24
+ Math.max(page - siblingCount, 1),
25
+ Math.min(page + siblingCount, pageCount),
26
+ ),
27
+ ]);
28
+
29
+ const items: (number | "ellipsis")[] = [];
30
+ for (const candidate of range(1, pageCount)) {
31
+ if (shown.has(candidate)) {
32
+ items.push(candidate);
33
+ } else if (items.at(-1) !== "ellipsis") {
34
+ items.push("ellipsis");
35
+ }
36
+ }
37
+
38
+ return items;
39
+ });
40
+ </script>
41
+
42
+ <HStack class="items-center justify-between">
43
+ <IconButton
44
+ icon={mdiChevronLeft}
45
+ aria-label="Previous page"
46
+ variant="ghost"
47
+ color="secondary"
48
+ disabled={page <= 1}
49
+ onclick={() => onChange(page - 1)}
50
+ />
51
+
52
+ <HStack gap={1} class="items-center">
53
+ {#each pages as item, index (index)}
54
+ {#if item === "ellipsis"}
55
+ <Text class="w-8 text-center" color="muted">&hellip;</Text>
56
+ {:else}
57
+ <Button
58
+ size="small"
59
+ variant="ghost"
60
+ color="secondary"
61
+ class={item === page ? "bg-subtle" : undefined}
62
+ aria-label={`Page ${item}`}
63
+ aria-current={item === page ? "page" : undefined}
64
+ onclick={() => onChange(item)}
65
+ >
66
+ {item}
67
+ </Button>
68
+ {/if}
69
+ {/each}
70
+ </HStack>
71
+
72
+ <IconButton
73
+ icon={mdiChevronRight}
74
+ aria-label="Next page"
75
+ variant="ghost"
76
+ color="secondary"
77
+ disabled={page >= pageCount}
78
+ onclick={() => onChange(page + 1)}
79
+ />
80
+ </HStack>
@@ -0,0 +1,8 @@
1
+ type Props = {
2
+ page: number;
3
+ pageCount: number;
4
+ onChange: (page: number) => void;
5
+ };
6
+ declare const Pagination: import("svelte").Component<Props, {}, "">;
7
+ type Pagination = ReturnType<typeof Pagination>;
8
+ export default Pagination;
@@ -1,9 +1,9 @@
1
1
  <script lang="ts">
2
2
  import StackList from "./StackList.svelte";
3
+ import StackListItem from "./StackListItem.svelte";
3
4
  import type { FilesystemListingResponseDto } from "../../fetch-client";
4
5
  import {
5
6
  Button,
6
- HStack,
7
7
  IconButton,
8
8
  modalManager,
9
9
  Text,
@@ -58,26 +58,29 @@
58
58
 
59
59
  {#if paths.size > 0}
60
60
  {#each [...paths] as path (path)}
61
- <HStack gap={2} class="px-4 py-3">
61
+ <StackListItem>
62
62
  <Text class="grow truncate" title={path}>{path}</Text>
63
- <IconButton
64
- icon={mdiClose}
65
- aria-label="Remove"
66
- size="tiny"
67
- variant="ghost"
68
- onclick={() => paths.delete(path)}
69
- />
70
- </HStack>
63
+
64
+ {#snippet trailing()}
65
+ <IconButton
66
+ icon={mdiClose}
67
+ aria-label="Remove"
68
+ size="tiny"
69
+ variant="ghost"
70
+ onclick={() => paths.delete(path)}
71
+ />
72
+ {/snippet}
73
+ </StackListItem>
71
74
  {/each}
72
75
  {:else if empty}
73
- <HStack class="px-4 py-3">
76
+ <StackListItem>
74
77
  <Text color="secondary">{@render empty()}</Text>
75
- </HStack>
78
+ </StackListItem>
76
79
  {/if}
77
80
 
78
- <HStack class="px-4 py-2">
81
+ <StackListItem>
79
82
  <Button size="small" variant="ghost" onclick={openPicker}>
80
83
  {paths.size > 0 ? addLabel : manageLabel}
81
84
  </Button>
82
- </HStack>
85
+ </StackListItem>
83
86
  </StackList>
@@ -1,30 +1,30 @@
1
- <script lang="ts" generics="T">
2
- import { getReadableErrorMessage } from "../../utils/handle-error";
3
- import { Alert, Heading, LoadingSpinner, Stack } from "@immich/ui";
4
- import type { CreateQueryResult } from "@tanstack/svelte-query";
1
+ <script lang="ts">
2
+ import { Card, CardBody, Heading, HStack, Stack } from "@immich/ui";
5
3
  import type { Snippet } from "svelte";
6
4
 
7
5
  type Props = {
8
6
  title?: Snippet;
9
- query?: CreateQueryResult<T>;
10
- children: Snippet<[T]>;
7
+ action?: Snippet;
8
+ children?: Snippet;
11
9
  };
12
10
 
13
- const { title, query, children }: Props = $props();
11
+ const { title, action, children }: Props = $props();
14
12
  </script>
15
13
 
16
14
  <Stack gap={2}>
17
- {#if title}
18
- <Heading class="px-1" size="tiny">{@render title()}</Heading>
19
- {/if}
15
+ {#if title || action}
16
+ <HStack class="items-center justify-between px-1">
17
+ {#if title}
18
+ <Heading size="tiny">{@render title()}</Heading>
19
+ {/if}
20
20
 
21
- {#if query?.isLoading}
22
- <LoadingSpinner />
23
- {:else if query?.isError}
24
- <Alert color="danger">{getReadableErrorMessage(query.error)}</Alert>
25
- {:else}
26
- <Stack gap={0} class="divide-y rounded-2xl border overflow-hidden">
27
- {@render children(query?.data as T)}
28
- </Stack>
21
+ {@render action?.()}
22
+ </HStack>
29
23
  {/if}
24
+
25
+ <Card class="border-primary-100 shadow-none">
26
+ <CardBody class="divide-y p-0">
27
+ {@render children?.()}
28
+ </CardBody>
29
+ </Card>
30
30
  </Stack>
@@ -1,30 +1,9 @@
1
- import type { CreateQueryResult } from "@tanstack/svelte-query";
2
1
  import type { Snippet } from "svelte";
3
- declare function $$render<T>(): {
4
- props: {
5
- title?: Snippet;
6
- query?: CreateQueryResult<T>;
7
- children: Snippet<[T]>;
8
- };
9
- exports: {};
10
- bindings: "";
11
- slots: {};
12
- events: {};
2
+ type Props = {
3
+ title?: Snippet;
4
+ action?: Snippet;
5
+ children?: Snippet;
13
6
  };
14
- declare class __sveltets_Render<T> {
15
- props(): ReturnType<typeof $$render<T>>['props'];
16
- events(): ReturnType<typeof $$render<T>>['events'];
17
- slots(): ReturnType<typeof $$render<T>>['slots'];
18
- bindings(): "";
19
- exports(): {};
20
- }
21
- interface $$IsomorphicComponent {
22
- new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
23
- $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
24
- } & ReturnType<__sveltets_Render<T>['exports']>;
25
- <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
26
- z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
27
- }
28
- declare const StackList: $$IsomorphicComponent;
29
- type StackList<T> = InstanceType<typeof StackList<T>>;
7
+ declare const StackList: import("svelte").Component<Props, {}, "">;
8
+ type StackList = ReturnType<typeof StackList>;
30
9
  export default StackList;