From c5360e78c53d81f5e0cc6ce016647d804a35047b Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Fri, 7 Feb 2025 13:05:15 -0500 Subject: [PATCH] feat(web): shared link filters (#15948) --- i18n/en.json | 3 + mobile/openapi/README.md | Bin 32519 -> 32623 bytes mobile/openapi/lib/api.dart | Bin 11793 -> 11873 bytes mobile/openapi/lib/api_client.dart | Bin 30193 -> 30369 bytes .../lib/model/shared_links_response.dart | Bin 0 -> 3161 bytes .../lib/model/shared_links_update.dart | Bin 0 -> 3996 bytes .../model/user_preferences_response_dto.dart | Bin 5139 -> 5441 bytes .../model/user_preferences_update_dto.dart | Bin 8971 -> 9709 bytes open-api/immich-openapi-specs.json | 35 ++++++ open-api/typescript-sdk/src/fetch-client.ts | 10 ++ server/src/dtos/user-preferences.dto.ts | 19 +++ server/src/entities/user-metadata.entity.ts | 8 ++ .../lib/components/elements/group-tab.svelte | 5 +- .../side-bar/side-bar.svelte | 5 + .../actions/shared-link-edit.svelte | 11 +- .../sharedlinks-page/shared-link-card.svelte | 8 +- .../feature-settings.svelte | 20 +++ web/src/lib/constants.ts | 2 +- .../shared-links/[[id=id]]/+page.svelte | 119 ++++++++++++++++++ .../(user)/shared-links/[[id=id]]/+page.ts | 14 +++ .../(user)/sharing/sharedlinks/+page.svelte | 89 ------------- .../(user)/sharing/sharedlinks/+page.ts | 15 +-- 22 files changed, 253 insertions(+), 110 deletions(-) create mode 100644 mobile/openapi/lib/model/shared_links_response.dart create mode 100644 mobile/openapi/lib/model/shared_links_update.dart create mode 100644 web/src/routes/(user)/shared-links/[[id=id]]/+page.svelte create mode 100644 web/src/routes/(user)/shared-links/[[id=id]]/+page.ts delete mode 100644 web/src/routes/(user)/sharing/sharedlinks/+page.svelte diff --git a/i18n/en.json b/i18n/en.json index 55fa27c98..72559d450 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -436,6 +436,7 @@ "back_close_deselect": "Back, close, or deselect", "backward": "Backward", "birthdate_saved": "Date of birth saved successfully", + "show_shared_links": "Show shared links", "birthdate_set_description": "Date of birth is used to calculate the age of this person at the time of a photo.", "blurred_background": "Blurred background", "bugs_and_feature_requests": "Bugs & Feature Requests", @@ -804,6 +805,7 @@ "include_shared_albums": "Include shared albums", "include_shared_partner_assets": "Include shared partner assets", "individual_share": "Individual share", + "individual_shares": "Individual shares", "info": "Info", "interval": { "day_at_onepm": "Every day at 1pm", @@ -1172,6 +1174,7 @@ "shared_from_partner": "Photos from {partner}", "shared_link_options": "Shared link options", "shared_links": "Shared links", + "shared_links_description": "Share photos and videos with a link", "shared_photos_and_videos_count": "{assetCount, plural, other {# shared photos & videos.}}", "shared_with_partner": "Shared with {partner}", "sharing": "Sharing", diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 5e0558db1849bd1ed305c0677d3cb2c1fb3b753c..8a2b6b886a6f8c0fa935236b059690cdf5ba5432 100644 GIT binary patch delta 108 zcmZqw$N2sq?4C0O);#W%0YjTHm{P&6tO delta 14 VcmaF=kFot9vXEO@y14zVwv{$6=pl-o*xR(K zU25cCwKC{liY@-Fgu(xvG!}y!8+OlhX`Luts&Y)IR!SOnx4~qEDy2;eU9Qp0ZA6*A z`aaDz!r0LW(>c&9(3LDjr4s!2dNj%kVJ%#viOerl`Pp7cTVYNKcO!raVsIs0ZAuFQ zO1XnY!1NXr7n#WfeF6p%#R6~}WfM%kEMyK-aHf`VzhUu&OJV+yOWa$D!t&<*F%}81 z)VhGiwO{C6cS zVI!@wY!)@pB(483eCId%oc-Q|?AOkyat%)VkBIRZ2Z&rWd8W^sk9csX>GM9>?U<8% z6xACaGlqPRvRBy*(|oYH#3Wu7D^-dDKH(Db{=oX~{xu&2*kWPEP2%w_DQF<5f0*^9 z^p^daH#V$n4onO8B((pkco1a{zWpsIXW@$|YH49*^tK7N;3aI56&j4Gkrs%{Z46~y z6kcpQWx00-bXP{Mu!*_R! zjmYJx(-GGg!?OVC&#(oleX)LTMy-J-XrlleP{@&zOj!4T%*Z!01#7Smq>DHi*)&w2 zz*bbh6Rh*q&AyUvXc){QQcMiJAdZjxFd@&L=|`Mz?sbsj1&!|zdT;n)e?TR!nte+q-bYxqwC#Zi zQ-7!@k;<_WHXTs(2)PIs-U?Vm@>%X_D6_xUXU}QN?E%B++O-s(9DmYqWT3Rat#`_8 zaEaQ9o-P4aUwTV)Y(_6$FX%zfI0}fe3aEn=6>MDA2<--S62sG)7xNAk^zCf_M@LVT z$W!Fj4BEk9cvMYzv_rN7lq5t~2jDGYLVdO$m$`6~ZhAyVK;VOP zl~I-iDY>G{j^Py*zQ9Zl;L&;A=Qu=eUf2QBPD+_4%@d15O@#SVPWa*NA?op2!%zCX zl)_3<+5!0sgrKEC^~SBq&UIDhG#A&BCQ>RGsYj|ROHQ#z=Y`57vP!Z`nE|y-xI!yp z5@YaOOUt2Ae^yqx#0hgb6;L}O z0Drs&WQ-nDgTVMX(Ykz2k%|A|_7eZk;cjH^5r3hf8UunNAKa0Ujy5zL_Q#V?GDDw2 zyXKIFBZ3|JJ`-i1=}hW3U%!Um2o!g^jvhWlYzh{2-ywQU{Pz2%JT__m>J(cjM>FZ5 zUORuq!XA!%n$4i(`KV+jtrcd?^9`lXr7f&C>qpsf7S=sIr^j?$*R(gN!yeL4!M-Ww z8<*xq%d1oFxih|jb@-+4;XLSobs5VVKBxy&3xnZJL~<$&|b3D2$dt=fR&^-Ho4UL`m{k9C_w~H^BzPbhh$v&#~Ac-iY4mzucwVM6;IaqeR$awoG?!dgPV zqxw1qVWqXC=1m5fu&yJze%P)YETY~?*7vE{A`0%97Gn7aZ^QBQ0t@OHua8lmb)8Tn z%fqnJ*|FjK2fmHr!NxtF_12dWi>wvnr4f;SSMd41y|Rp`#PiqSeGBqX>vvGtGwn_nOSi6rf*cI>evGYG zyt3J~d0*2Ps2z4SG+-V3^Pu#I598eEq0_beIviwx`$JGo%QK+My~c)HSVu3X`CR=P ztm6%AK1!R2_9*Nk#^TvYM}7VP46NEivKyy8E^FkG`Mbb-yI~;au6Dq1Xu7?=i>J5_ z?gPNW@dKu4%;B#)gr$J`UpDH!ila0c5G(B>)gmHxLEVk+UU8}dlA5W^YZ)aG{9*-L zTV!ctRtH98BNN!T8O4#5+o~Wqn={zAGs+|Q#p;5d diff --git a/mobile/openapi/lib/model/user_preferences_update_dto.dart b/mobile/openapi/lib/model/user_preferences_update_dto.dart index 208dbf686078ab2e20d363afb265fe6af83082cf..3e420df119a3a50b9efc4d703f4e1ee406071c65 100644 GIT binary patch delta 272 zcmeBnd+WWymWiu4Be5to#V0c_d$S=^0IzH?oL?MTkdj!EYOjE#M2?T0SrWl7R RatingsUpdate) ratings?: RatingsUpdate; + @Optional() + @ValidateNested() + @Type(() => SharedLinksUpdate) + sharedLinks?: SharedLinksUpdate; + @Optional() @ValidateNested() @Type(() => TagsUpdate) @@ -152,6 +165,11 @@ class TagsResponse { sidebarWeb: boolean = true; } +class SharedLinksResponse { + enabled: boolean = true; + sidebarWeb: boolean = false; +} + class EmailNotificationsResponse { enabled!: boolean; albumInvite!: boolean; @@ -175,6 +193,7 @@ export class UserPreferencesResponseDto implements UserPreferences { memories!: MemoriesResponse; people!: PeopleResponse; ratings!: RatingsResponse; + sharedLinks!: SharedLinksResponse; tags!: TagsResponse; avatar!: AvatarResponse; emailNotifications!: EmailNotificationsResponse; diff --git a/server/src/entities/user-metadata.entity.ts b/server/src/entities/user-metadata.entity.ts index 2c901426c..65c187883 100644 --- a/server/src/entities/user-metadata.entity.ts +++ b/server/src/entities/user-metadata.entity.ts @@ -34,6 +34,10 @@ export interface UserPreferences { ratings: { enabled: boolean; }; + sharedLinks: { + enabled: boolean; + sidebarWeb: boolean; + }; tags: { enabled: boolean; sidebarWeb: boolean; @@ -74,6 +78,10 @@ export const getDefaultPreferences = (user: { email: string }): UserPreferences enabled: true, sidebarWeb: false, }, + sharedLinks: { + enabled: true, + sidebarWeb: false, + }, ratings: { enabled: false, }, diff --git a/web/src/lib/components/elements/group-tab.svelte b/web/src/lib/components/elements/group-tab.svelte index 021d5ca96..950c26f3f 100644 --- a/web/src/lib/components/elements/group-tab.svelte +++ b/web/src/lib/components/elements/group-tab.svelte @@ -3,12 +3,13 @@ interface Props { filters: string[]; + labels?: string[]; selected: string; label: string; onSelect: (selected: string) => void; } - let { filters, selected, label, onSelect }: Props = $props(); + let { filters, selected, label, labels, onSelect }: Props = $props(); const id = `group-tab-${generateId()}`; @@ -32,7 +33,7 @@ for="{id}-{index}" class="flex h-full cursor-pointer items-center px-4 text-sm hover:bg-gray-300 group-first-of-type:rounded-s-2xl group-last-of-type:rounded-e-2xl peer-checked:bg-gray-300 dark:hover:bg-gray-800 peer-checked:dark:bg-gray-700" > - {filter} + {labels?.[index] ?? filter} {/each} diff --git a/web/src/lib/components/shared-components/side-bar/side-bar.svelte b/web/src/lib/components/shared-components/side-bar/side-bar.svelte index 9c49b971b..5493495fd 100644 --- a/web/src/lib/components/shared-components/side-bar/side-bar.svelte +++ b/web/src/lib/components/shared-components/side-bar/side-bar.svelte @@ -21,6 +21,7 @@ mdiToolboxOutline, mdiFolderOutline, mdiTagMultipleOutline, + mdiLink, } from '@mdi/js'; import SideBarSection from './side-bar-section.svelte'; import SideBarLink from './side-bar-link.svelte'; @@ -72,6 +73,10 @@ /> {/if} + {#if $preferences.sharedLinks.enabled && $preferences.sharedLinks.sidebarWeb} + + {/if} + + import { goto } from '$app/navigation'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; + import { AppRoute } from '$lib/constants'; + import type { SharedLinkResponseDto } from '@immich/sdk'; import { mdiCircleEditOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; interface Props { menuItem?: boolean; - onEdit: () => void; + sharedLink: SharedLinkResponseDto; } - let { menuItem = false, onEdit }: Props = $props(); + let { sharedLink, menuItem = false }: Props = $props(); + + const onEdit = async () => { + await goto(`${AppRoute.SHARED_LINKS}/${sharedLink.id}`); + }; {#if menuItem} diff --git a/web/src/lib/components/sharedlinks-page/shared-link-card.svelte b/web/src/lib/components/sharedlinks-page/shared-link-card.svelte index 70f624753..15a4468c0 100644 --- a/web/src/lib/components/sharedlinks-page/shared-link-card.svelte +++ b/web/src/lib/components/sharedlinks-page/shared-link-card.svelte @@ -15,10 +15,9 @@ interface Props { link: SharedLinkResponseDto; onDelete: () => void; - onEdit: () => void; } - let { link, onDelete, onEdit }: Props = $props(); + let { link, onDelete }: Props = $props(); let now = DateTime.now(); let expiresAt = $derived(link.expiresAt ? DateTime.fromISO(link.expiresAt) : undefined); @@ -95,10 +94,9 @@ -
@@ -112,7 +110,7 @@ padding="3" hideContent > - + diff --git a/web/src/lib/components/user-settings-page/feature-settings.svelte b/web/src/lib/components/user-settings-page/feature-settings.svelte index 9a60f8364..c3868070c 100644 --- a/web/src/lib/components/user-settings-page/feature-settings.svelte +++ b/web/src/lib/components/user-settings-page/feature-settings.svelte @@ -27,6 +27,10 @@ // Ratings let ratingsEnabled = $state($preferences?.ratings?.enabled ?? false); + // Shared links + let sharedLinksEnabled = $state($preferences?.sharedLinks?.enabled ?? true); + let sharedLinkSidebar = $state($preferences?.sharedLinks?.sidebarWeb ?? false); + // Tags let tagsEnabled = $state($preferences?.tags?.enabled ?? false); let tagsSidebar = $state($preferences?.tags?.sidebarWeb ?? false); @@ -39,6 +43,7 @@ memories: { enabled: memoriesEnabled }, people: { enabled: peopleEnabled, sidebarWeb: peopleSidebar }, ratings: { enabled: ratingsEnabled }, + sharedLinks: { enabled: sharedLinksEnabled, sidebarWeb: sharedLinkSidebar }, tags: { enabled: tagsEnabled, sidebarWeb: tagsSidebar }, }, }); @@ -104,6 +109,21 @@
+ +
+ +
+ {#if sharedLinksEnabled} +
+ +
+ {/if} +
+
diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts index b7ea2cfb5..db04efa5d 100644 --- a/web/src/lib/constants.ts +++ b/web/src/lib/constants.ts @@ -30,7 +30,7 @@ export enum AppRoute { EXPLORE = '/explore', SHARE = '/share', SHARING = '/sharing', - SHARED_LINKS = '/sharing/sharedlinks', + SHARED_LINKS = '/shared-links', SEARCH = '/search', MAP = '/map', USER_SETTINGS = '/user-settings', diff --git a/web/src/routes/(user)/shared-links/[[id=id]]/+page.svelte b/web/src/routes/(user)/shared-links/[[id=id]]/+page.svelte new file mode 100644 index 000000000..436f3b47d --- /dev/null +++ b/web/src/routes/(user)/shared-links/[[id=id]]/+page.svelte @@ -0,0 +1,119 @@ + + + + {#snippet buttons()} + + {/snippet} + +
+ {#if sharedLinks.length === 0} +
+

{$t('you_dont_have_any_shared_links')}

+
+ {:else} +
+ {#each filteredSharedLinks as link (link.id)} + handleDeleteLink(link.id)} /> + {/each} +
+ {/if} + + {#if sharedLink} + + {/if} +
+
diff --git a/web/src/routes/(user)/shared-links/[[id=id]]/+page.ts b/web/src/routes/(user)/shared-links/[[id=id]]/+page.ts new file mode 100644 index 000000000..920e5bdba --- /dev/null +++ b/web/src/routes/(user)/shared-links/[[id=id]]/+page.ts @@ -0,0 +1,14 @@ +import { authenticate } from '$lib/utils/auth'; +import { getFormatter } from '$lib/utils/i18n'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + await authenticate(); + const $t = await getFormatter(); + + return { + meta: { + title: $t('shared_links'), + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/sharing/sharedlinks/+page.svelte b/web/src/routes/(user)/sharing/sharedlinks/+page.svelte deleted file mode 100644 index b7d4da294..000000000 --- a/web/src/routes/(user)/sharing/sharedlinks/+page.svelte +++ /dev/null @@ -1,89 +0,0 @@ - - - goto(backUrl)}> - {#snippet leading()} - {$t('shared_links')} - {/snippet} - - -
-
-

{$t('manage_shared_links')}

-
- {#if sharedLinks.length === 0} -
-

{$t('you_dont_have_any_shared_links')}

-
- {:else} -
- {#each sharedLinks as link (link.id)} - handleDeleteLink(link.id)} onEdit={() => (editSharedLink = link)} /> - {/each} -
- {/if} -
- -{#if editSharedLink} - -{/if} diff --git a/web/src/routes/(user)/sharing/sharedlinks/+page.ts b/web/src/routes/(user)/sharing/sharedlinks/+page.ts index 920e5bdba..59530fd83 100644 --- a/web/src/routes/(user)/sharing/sharedlinks/+page.ts +++ b/web/src/routes/(user)/sharing/sharedlinks/+page.ts @@ -1,14 +1,7 @@ -import { authenticate } from '$lib/utils/auth'; -import { getFormatter } from '$lib/utils/i18n'; +import { AppRoute } from '$lib/constants'; +import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const load = (async () => { - await authenticate(); - const $t = await getFormatter(); - - return { - meta: { - title: $t('shared_links'), - }, - }; +export const load = (() => { + redirect(307, AppRoute.SHARED_LINKS); }) satisfies PageLoad;