mirror of
https://github.com/samsonjs/immich.git
synced 2026-04-27 15:07:45 +00:00
feat(web): star rating keyboard shortcut (#24620)
Co-authored-by: idubnori <i.dub.nori@gmail.com> Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
This commit is contained in:
parent
f5667cefd4
commit
f22affd836
7 changed files with 77 additions and 0 deletions
|
|
@ -1082,6 +1082,7 @@
|
||||||
"unable_to_scan_library": "Unable to scan library",
|
"unable_to_scan_library": "Unable to scan library",
|
||||||
"unable_to_set_feature_photo": "Unable to set feature photo",
|
"unable_to_set_feature_photo": "Unable to set feature photo",
|
||||||
"unable_to_set_profile_picture": "Unable to set profile picture",
|
"unable_to_set_profile_picture": "Unable to set profile picture",
|
||||||
|
"unable_to_set_rating": "Unable to set rating",
|
||||||
"unable_to_submit_job": "Unable to submit job",
|
"unable_to_submit_job": "Unable to submit job",
|
||||||
"unable_to_trash_asset": "Unable to trash asset",
|
"unable_to_trash_asset": "Unable to trash asset",
|
||||||
"unable_to_unlink_account": "Unable to unlink account",
|
"unable_to_unlink_account": "Unable to unlink account",
|
||||||
|
|
@ -1702,10 +1703,12 @@
|
||||||
"purchase_settings_server_activated": "The server product key is managed by the admin",
|
"purchase_settings_server_activated": "The server product key is managed by the admin",
|
||||||
"query_asset_id": "Query Asset ID",
|
"query_asset_id": "Query Asset ID",
|
||||||
"queue_status": "Queuing {count}/{total}",
|
"queue_status": "Queuing {count}/{total}",
|
||||||
|
"rate_asset": "Rate Asset",
|
||||||
"rating": "Star rating",
|
"rating": "Star rating",
|
||||||
"rating_clear": "Clear rating",
|
"rating_clear": "Clear rating",
|
||||||
"rating_count": "{count, plural, one {# star} other {# stars}}",
|
"rating_count": "{count, plural, one {# star} other {# stars}}",
|
||||||
"rating_description": "Display the EXIF rating in the info panel",
|
"rating_description": "Display the EXIF rating in the info panel",
|
||||||
|
"rating_set": "Rating set to {rating, plural, one {# star} other {# stars}}",
|
||||||
"reaction_options": "Reaction options",
|
"reaction_options": "Reaction options",
|
||||||
"read_changelog": "Read Changelog",
|
"read_changelog": "Read Changelog",
|
||||||
"readonly_mode_disabled": "Read-only mode disabled",
|
"readonly_mode_disabled": "Read-only mode disabled",
|
||||||
|
|
@ -2296,6 +2299,7 @@
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"you_dont_have_any_shared_links": "You don't have any shared links",
|
"you_dont_have_any_shared_links": "You don't have any shared links",
|
||||||
"your_wifi_name": "Your Wi-Fi name",
|
"your_wifi_name": "Your Wi-Fi name",
|
||||||
|
"zero_to_clear_rating": "press 0 to clear asset rating",
|
||||||
"zoom_image": "Zoom Image",
|
"zoom_image": "Zoom Image",
|
||||||
"zoom_to_bounds": "Zoom to bounds"
|
"zoom_to_bounds": "Zoom to bounds"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ type ActionMap = {
|
||||||
[AssetAction.SET_VISIBILITY_LOCKED]: { asset: TimelineAsset };
|
[AssetAction.SET_VISIBILITY_LOCKED]: { asset: TimelineAsset };
|
||||||
[AssetAction.SET_VISIBILITY_TIMELINE]: { asset: TimelineAsset };
|
[AssetAction.SET_VISIBILITY_TIMELINE]: { asset: TimelineAsset };
|
||||||
[AssetAction.SET_PERSON_FEATURED_PHOTO]: { asset: AssetResponseDto; person: PersonResponseDto };
|
[AssetAction.SET_PERSON_FEATURED_PHOTO]: { asset: AssetResponseDto; person: PersonResponseDto };
|
||||||
|
[AssetAction.RATING]: { asset: TimelineAsset; rating: number | null };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Action = {
|
export type Action = {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { shortcuts } from '$lib/actions/shortcut';
|
||||||
|
import type { OnAction } from '$lib/components/asset-viewer/actions/action';
|
||||||
|
import { AssetAction } from '$lib/constants';
|
||||||
|
import { preferences } from '$lib/stores/user.store';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||||
|
import { updateAsset, type AssetResponseDto } from '@immich/sdk';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
asset: AssetResponseDto;
|
||||||
|
onAction: OnAction;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { asset, onAction }: Props = $props();
|
||||||
|
|
||||||
|
const rateAsset = async (rating: number | null) => {
|
||||||
|
try {
|
||||||
|
const updateAssetDto = rating === null ? {} : { rating };
|
||||||
|
await updateAsset({
|
||||||
|
id: asset.id,
|
||||||
|
updateAssetDto,
|
||||||
|
});
|
||||||
|
|
||||||
|
asset = {
|
||||||
|
...asset,
|
||||||
|
exifInfo: {
|
||||||
|
...asset.exifInfo,
|
||||||
|
rating,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
onAction({
|
||||||
|
type: AssetAction.RATING,
|
||||||
|
asset: toTimelineAsset(asset),
|
||||||
|
rating,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, $t('errors.unable_to_set_rating'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:document
|
||||||
|
use:shortcuts={$preferences?.ratings.enabled
|
||||||
|
? [
|
||||||
|
{ shortcut: { key: '0' }, onShortcut: () => rateAsset(null) },
|
||||||
|
...[1, 2, 3, 4, 5].map((rating) => ({
|
||||||
|
shortcut: { key: String(rating) },
|
||||||
|
onShortcut: () => rateAsset(rating),
|
||||||
|
})),
|
||||||
|
]
|
||||||
|
: []}
|
||||||
|
/>
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
import DownloadAction from '$lib/components/asset-viewer/actions/download-action.svelte';
|
import DownloadAction from '$lib/components/asset-viewer/actions/download-action.svelte';
|
||||||
import FavoriteAction from '$lib/components/asset-viewer/actions/favorite-action.svelte';
|
import FavoriteAction from '$lib/components/asset-viewer/actions/favorite-action.svelte';
|
||||||
import KeepThisDeleteOthersAction from '$lib/components/asset-viewer/actions/keep-this-delete-others.svelte';
|
import KeepThisDeleteOthersAction from '$lib/components/asset-viewer/actions/keep-this-delete-others.svelte';
|
||||||
|
import RatingAction from '$lib/components/asset-viewer/actions/rating-action.svelte';
|
||||||
import RemoveAssetFromStack from '$lib/components/asset-viewer/actions/remove-asset-from-stack.svelte';
|
import RemoveAssetFromStack from '$lib/components/asset-viewer/actions/remove-asset-from-stack.svelte';
|
||||||
import RestoreAction from '$lib/components/asset-viewer/actions/restore-action.svelte';
|
import RestoreAction from '$lib/components/asset-viewer/actions/restore-action.svelte';
|
||||||
import SetAlbumCoverAction from '$lib/components/asset-viewer/actions/set-album-cover-action.svelte';
|
import SetAlbumCoverAction from '$lib/components/asset-viewer/actions/set-album-cover-action.svelte';
|
||||||
|
|
@ -179,6 +180,7 @@
|
||||||
|
|
||||||
{#if isOwner}
|
{#if isOwner}
|
||||||
<FavoriteAction {asset} {onAction} />
|
<FavoriteAction {asset} {onAction} />
|
||||||
|
<RatingAction {asset} {onAction} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if isOwner}
|
{#if isOwner}
|
||||||
|
|
|
||||||
|
|
@ -331,6 +331,16 @@
|
||||||
asset = { ...asset, people: assetInfo.people };
|
asset = { ...asset, people: assetInfo.people };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case AssetAction.RATING: {
|
||||||
|
asset = {
|
||||||
|
...asset,
|
||||||
|
exifInfo: {
|
||||||
|
...asset.exifInfo,
|
||||||
|
rating: action.rating,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
case AssetAction.KEEP_THIS_DELETE_OTHERS:
|
case AssetAction.KEEP_THIS_DELETE_OTHERS:
|
||||||
case AssetAction.UNSTACK: {
|
case AssetAction.UNSTACK: {
|
||||||
closeViewer();
|
closeViewer();
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export enum AssetAction {
|
||||||
SET_VISIBILITY_LOCKED = 'set-visibility-locked',
|
SET_VISIBILITY_LOCKED = 'set-visibility-locked',
|
||||||
SET_VISIBILITY_TIMELINE = 'set-visibility-timeline',
|
SET_VISIBILITY_TIMELINE = 'set-visibility-timeline',
|
||||||
SET_PERSON_FEATURED_PHOTO = 'set-person-featured-photo',
|
SET_PERSON_FEATURED_PHOTO = 'set-person-featured-photo',
|
||||||
|
RATING = 'rating',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AppRoute {
|
export enum AppRoute {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { preferences } from '$lib/stores/user.store';
|
||||||
import { Icon, Modal, ModalBody } from '@immich/ui';
|
import { Icon, Modal, ModalBody } from '@immich/ui';
|
||||||
import { mdiInformationOutline } from '@mdi/js';
|
import { mdiInformationOutline } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
@ -44,6 +45,9 @@
|
||||||
{ key: ['⇧', 'd'], action: $t('download') },
|
{ key: ['⇧', 'd'], action: $t('download') },
|
||||||
{ key: ['Space'], action: $t('play_or_pause_video') },
|
{ key: ['Space'], action: $t('play_or_pause_video') },
|
||||||
{ key: ['Del'], action: $t('trash_delete_asset'), info: $t('shift_to_permanent_delete') },
|
{ key: ['Del'], action: $t('trash_delete_asset'), info: $t('shift_to_permanent_delete') },
|
||||||
|
...($preferences?.ratings.enabled
|
||||||
|
? [{ key: ['1-5'], action: $t('rate_asset'), info: $t('zero_to_clear_rating') }]
|
||||||
|
: []),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue