fix(web): show relevant navbar options for partner assets (#24832)

* fix(web): show relevant navbar options for partner

* fix(web): AssetSelectControlBar on photos & search routes

* chore: remove duplicate AssetSelectControlBar from search

* chore: formatting fix

* chore: change let to const
This commit is contained in:
Yaros 2026-01-12 16:41:33 +01:00 committed by GitHub
parent 5e3f5f2b55
commit afe925a55e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 116 additions and 151 deletions

View file

@ -27,7 +27,7 @@
import { photoViewerImgElement } from '$lib/stores/assets-store.svelte'; import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
import { user } from '$lib/stores/user.store'; import { user } from '$lib/stores/user.store';
import { photoZoomState } from '$lib/stores/zoom-image.store'; import { photoZoomState } from '$lib/stores/zoom-image.store';
import { getAssetJobName, withoutIcons } from '$lib/utils'; import { getAssetJobName, getSharedLink, withoutIcons } from '$lib/utils';
import type { OnUndoDelete } from '$lib/utils/actions'; import type { OnUndoDelete } from '$lib/utils/actions';
import { canCopyImageToClipboard } from '$lib/utils/asset-utils'; import { canCopyImageToClipboard } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toTimelineAsset } from '$lib/utils/timeline-util';
@ -114,6 +114,7 @@
const { Share, Download, SharedLinkDownload, Offline, Favorite, Unfavorite, PlayMotionPhoto, StopMotionPhoto, Info } = const { Share, Download, SharedLinkDownload, Offline, Favorite, Unfavorite, PlayMotionPhoto, StopMotionPhoto, Info } =
$derived(getAssetActions($t, asset)); $derived(getAssetActions($t, asset));
const sharedLink = getSharedLink();
// TODO: Enable when edits are ready for release // TODO: Enable when edits are ready for release
// let showEditorButton = $derived( // let showEditorButton = $derived(
@ -185,7 +186,9 @@
{#if isOwner} {#if isOwner}
<DeleteAction {asset} {onAction} {preAction} {onUndoDelete} /> <DeleteAction {asset} {onAction} {preAction} {onUndoDelete} />
{/if}
{#if !sharedLink}
<ButtonContextMenu direction="left" align="top-right" color="secondary" title={$t('more')} icon={mdiDotsVertical}> <ButtonContextMenu direction="left" align="top-right" color="secondary" title={$t('more')} icon={mdiDotsVertical}>
{#if showSlideshow && !isLocked} {#if showSlideshow && !isLocked}
<MenuOption icon={mdiPresentationPlay} text={$t('slideshow')} onClick={onPlaySlideshow} /> <MenuOption icon={mdiPresentationPlay} text={$t('slideshow')} onClick={onPlaySlideshow} />
@ -214,17 +217,19 @@
{/if} {/if}
{/if} {/if}
{/if} {/if}
{#if album} {/if}
<SetAlbumCoverAction {asset} {album} /> {#if album}
{/if} <SetAlbumCoverAction {asset} {album} />
{#if person} {/if}
<SetFeaturedPhotoAction {asset} {person} {onAction} /> {#if person}
{/if} <SetFeaturedPhotoAction {asset} {person} {onAction} />
{#if asset.type === AssetTypeEnum.Image && !isLocked} {/if}
<SetProfilePictureAction {asset} /> {#if asset.type === AssetTypeEnum.Image && !isLocked}
{/if} <SetProfilePictureAction {asset} />
{/if}
{#if !isLocked} {#if !isLocked}
{#if isOwner}
<ArchiveAction {asset} {onAction} {preAction} /> <ArchiveAction {asset} {onAction} {preAction} />
<MenuOption <MenuOption
icon={mdiUpload} icon={mdiUpload}
@ -238,28 +243,29 @@
text={$t('view_in_timeline')} text={$t('view_in_timeline')}
/> />
{/if} {/if}
{#if !asset.isArchived && !asset.isTrashed && smartSearchEnabled}
<MenuOption
icon={mdiCompare}
onClick={() =>
goto(resolve(`${AppRoute.SEARCH}?query={"queryAssetId":"${stack?.primaryAssetId ?? asset.id}"}`))}
text={$t('view_similar_photos')}
/>
{/if}
{/if} {/if}
{#if !asset.isArchived && !asset.isTrashed && smartSearchEnabled}
{#if !asset.isTrashed}
<SetVisibilityAction asset={toTimelineAsset(asset)} {onAction} {preAction} />
{/if}
{#if asset.type === AssetTypeEnum.Video}
<MenuOption <MenuOption
icon={mdiVideoOutline} icon={mdiCompare}
onClick={() => setPlayOriginalVideo(!playOriginalVideo)} onClick={() =>
text={playOriginalVideo ? $t('play_transcoded_video') : $t('play_original_video')} goto(resolve(`${AppRoute.SEARCH}?query={"queryAssetId":"${stack?.primaryAssetId ?? asset.id}"}`))}
text={$t('view_similar_photos')}
/> />
{/if} {/if}
{/if}
{#if !asset.isTrashed && isOwner}
<SetVisibilityAction asset={toTimelineAsset(asset)} {onAction} {preAction} />
{/if}
{#if asset.type === AssetTypeEnum.Video}
<MenuOption
icon={mdiVideoOutline}
onClick={() => setPlayOriginalVideo(!playOriginalVideo)}
text={playOriginalVideo ? $t('play_transcoded_video') : $t('play_original_video')}
/>
{/if}
{#if isOwner}
<hr /> <hr />
<MenuOption <MenuOption
icon={mdiHeadSyncOutline} icon={mdiHeadSyncOutline}

View file

@ -59,6 +59,9 @@
return assetInteraction.isAllUserOwned && (isLivePhoto || isLivePhotoCandidate); return assetInteraction.isAllUserOwned && (isLivePhoto || isLivePhotoCandidate);
}); });
const isAllUserOwned = $derived($user && selectedAssets.every((asset) => asset.ownerId === $user.id));
const handleEscape = () => { const handleEscape = () => {
if ($showAssetViewer) { if ($showAssetViewer) {
return; return;
@ -130,45 +133,51 @@
<AddToAlbum /> <AddToAlbum />
<AddToAlbum shared /> <AddToAlbum shared />
</ButtonContextMenu> </ButtonContextMenu>
<FavoriteAction
removeFavorite={assetInteraction.isAllFavorite} {#if isAllUserOwned}
onFavorite={(ids, isFavorite) => timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))} <FavoriteAction
></FavoriteAction> removeFavorite={assetInteraction.isAllFavorite}
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}> onFavorite={(ids, isFavorite) => timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))}
<DownloadAction menuItem /> />
{#if assetInteraction.selectedAssets.length > 1 || isAssetStackSelected}
<StackAction <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
unstack={isAssetStackSelected} <DownloadAction menuItem />
onStack={(result) => updateStackedAssetInTimeline(timelineManager, result)} {#if assetInteraction.selectedAssets.length > 1 || isAssetStackSelected}
onUnstack={(assets) => updateUnstackedAssetInTimeline(timelineManager, assets)} <StackAction
/> unstack={isAssetStackSelected}
{/if} onStack={(result) => updateStackedAssetInTimeline(timelineManager, result)}
{#if isLinkActionAvailable} onUnstack={(assets) => updateUnstackedAssetInTimeline(timelineManager, assets)}
<LinkLivePhotoAction />
{/if}
{#if isLinkActionAvailable}
<LinkLivePhotoAction
menuItem
unlink={assetInteraction.selectedAssets.length === 1}
onLink={handleLink}
onUnlink={handleUnlink}
/>
{/if}
<ChangeDate menuItem />
<ChangeDescription menuItem />
<ChangeLocation menuItem />
<ArchiveAction
menuItem menuItem
unlink={assetInteraction.selectedAssets.length === 1} onArchive={(ids, visibility) => timelineManager.update(ids, (asset) => (asset.visibility = visibility))}
onLink={handleLink}
onUnlink={handleUnlink}
/> />
{/if} {#if $preferences.tags.enabled}
<ChangeDate menuItem /> <TagAction menuItem />
<ChangeDescription menuItem /> {/if}
<ChangeLocation menuItem /> <DeleteAssets
<ArchiveAction menuItem
menuItem onAssetDelete={(assetIds) => timelineManager.removeAssets(assetIds)}
onArchive={(ids, visibility) => timelineManager.update(ids, (asset) => (asset.visibility = visibility))} onUndoDelete={(assets) => timelineManager.upsertAssets(assets)}
/> />
{#if $preferences.tags.enabled} <SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} />
<TagAction menuItem /> <hr />
{/if} <AssetJobActions />
<DeleteAssets </ButtonContextMenu>
menuItem {:else}
onAssetDelete={(assetIds) => timelineManager.removeAssets(assetIds)} <DownloadAction />
onUndoDelete={(assets) => timelineManager.upsertAssets(assets)} {/if}
/>
<SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} />
<hr />
<AssetJobActions />
</ButtonContextMenu>
</AssetSelectControlBar> </AssetSelectControlBar>
{/if} {/if}

View file

@ -26,7 +26,7 @@
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { lang, locale } from '$lib/stores/preferences.store'; import { lang, locale } from '$lib/stores/preferences.store';
import { preferences } from '$lib/stores/user.store'; import { preferences, user } from '$lib/stores/user.store';
import { handlePromiseError } from '$lib/utils'; import { handlePromiseError } from '$lib/utils';
import { cancelMultiselect } from '$lib/utils/asset-utils'; import { cancelMultiselect } from '$lib/utils/asset-utils';
import { parseUtcDate } from '$lib/utils/date-time'; import { parseUtcDate } from '$lib/utils/date-time';
@ -71,6 +71,10 @@
let smartSearchEnabled = $derived(featureFlagsManager.value.smartSearch); let smartSearchEnabled = $derived(featureFlagsManager.value.smartSearch);
let terms = $derived(searchQuery ? JSON.parse(searchQuery) : {}); let terms = $derived(searchQuery ? JSON.parse(searchQuery) : {});
const isAllUserOwned = $derived(
$user && assetInteraction.selectedAssets.every((asset) => asset.ownerId === $user.id),
);
$effect(() => { $effect(() => {
// we want this to *only* be reactive on `terms` // we want this to *only* be reactive on `terms`
// eslint-disable-next-line @typescript-eslint/no-unused-expressions // eslint-disable-next-line @typescript-eslint/no-unused-expressions
@ -258,64 +262,6 @@
<svelte:window bind:scrollY /> <svelte:window bind:scrollY />
<svelte:document use:shortcut={{ shortcut: { key: 'Escape' }, onShortcut: onEscape }} /> <svelte:document use:shortcut={{ shortcut: { key: 'Escape' }, onShortcut: onEscape }} />
<section>
{#if assetInteraction.selectionActive}
<div class="fixed top-0 start-0 w-full">
<AssetSelectControlBar
assets={assetInteraction.selectedAssets}
clearSelect={() => cancelMultiselect(assetInteraction)}
>
<CreateSharedLink />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('select_all')}
icon={mdiSelectAll}
onclick={handleSelectAll}
/>
<ButtonContextMenu icon={mdiPlus} title={$t('add_to')}>
<AddToAlbum {onAddToAlbum} />
<AddToAlbum shared {onAddToAlbum} />
</ButtonContextMenu>
<FavoriteAction
removeFavorite={assetInteraction.isAllFavorite}
onFavorite={(assetIds, isFavorite) => {
for (const assetId of assetIds) {
const asset = searchResultAssets.find((searchAsset) => searchAsset.id === assetId);
if (asset) {
asset.isFavorite = isFavorite;
}
}
}}
/>
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<DownloadAction menuItem />
<ChangeDate menuItem />
<ChangeLocation menuItem />
<ArchiveAction menuItem unarchive={assetInteraction.isAllArchived} />
{#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
<TagAction menuItem />
{/if}
<DeleteAssets menuItem {onAssetDelete} onUndoDelete={onSearchQueryUpdate} />
<hr />
<AssetJobActions />
</ButtonContextMenu>
</AssetSelectControlBar>
</div>
{:else}
<div class="fixed top-0 start-0 w-full">
<ControlAppBar onClose={() => goto(previousRoute)} backIcon={mdiArrowLeft}>
<div class="absolute bg-light"></div>
<div class="w-full flex-1 ps-4">
<SearchBar grayTheme={false} value={terms?.query ?? ''} searchQuery={terms} />
</div>
</ControlAppBar>
</div>
{/if}
</section>
{#if terms} {#if terms}
<section <section
id="search-chips" id="search-chips"
@ -419,34 +365,38 @@
<AddToAlbum {onAddToAlbum} /> <AddToAlbum {onAddToAlbum} />
<AddToAlbum shared {onAddToAlbum} /> <AddToAlbum shared {onAddToAlbum} />
</ButtonContextMenu> </ButtonContextMenu>
<FavoriteAction {#if isAllUserOwned}
removeFavorite={assetInteraction.isAllFavorite} <FavoriteAction
onFavorite={(ids, isFavorite) => { removeFavorite={assetInteraction.isAllFavorite}
for (const id of ids) { onFavorite={(ids, isFavorite) => {
const asset = searchResultAssets.find((asset) => asset.id === id); for (const id of ids) {
if (asset) { const asset = searchResultAssets.find((asset) => asset.id === id);
asset.isFavorite = isFavorite; if (asset) {
asset.isFavorite = isFavorite;
}
} }
} }}
}} />
/>
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}> <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<DownloadAction menuItem /> <DownloadAction menuItem />
<ChangeDate menuItem /> <ChangeDate menuItem />
<ChangeDescription menuItem /> <ChangeDescription menuItem />
<ChangeLocation menuItem /> <ChangeLocation menuItem />
<ArchiveAction menuItem unarchive={assetInteraction.isAllArchived} /> <ArchiveAction menuItem unarchive={assetInteraction.isAllArchived} />
{#if assetInteraction.isAllUserOwned} {#if assetInteraction.isAllUserOwned}
<SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} /> <SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} />
{/if} {/if}
{#if $preferences.tags.enabled && assetInteraction.isAllUserOwned} {#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
<TagAction menuItem /> <TagAction menuItem />
{/if} {/if}
<DeleteAssets menuItem {onAssetDelete} onUndoDelete={onSearchQueryUpdate} /> <DeleteAssets menuItem {onAssetDelete} onUndoDelete={onSearchQueryUpdate} />
<hr /> <hr />
<AssetJobActions /> <AssetJobActions />
</ButtonContextMenu> </ButtonContextMenu>
{:else}
<DownloadAction />
{/if}
</AssetSelectControlBar> </AssetSelectControlBar>
</div> </div>
{:else} {:else}