mirror of
https://github.com/immich-app/immich.git
synced 2026-01-20 16:43:16 -08:00
Compare commits
3 Commits
feat/small
...
lower-case
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc58c0e78e | ||
|
|
0a0057e16c | ||
|
|
0a62435d5a |
@@ -1523,7 +1523,7 @@
|
||||
"no_albums_with_name_yet": "It looks like you do not have any albums with this name yet.",
|
||||
"no_albums_yet": "It looks like you do not have any albums yet.",
|
||||
"no_archived_assets_message": "Archive photos and videos to hide them from your Photos view",
|
||||
"no_assets_message": "CLICK TO UPLOAD YOUR FIRST PHOTO",
|
||||
"no_assets_message": "Click to upload your first photo",
|
||||
"no_assets_to_show": "No assets to show",
|
||||
"no_cast_devices_found": "No cast devices found",
|
||||
"no_checksum_local": "No checksum available - cannot fetch local assets",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
/* @import '/usr/ui/dist/theme/default.css'; */
|
||||
|
||||
@utility immich-form-input {
|
||||
@apply rounded-xl bg-slate-200 px-3 py-3 text-sm focus:border-immich-primary disabled:cursor-not-allowed disabled:bg-gray-400 disabled:text-gray-100 dark:bg-gray-600 dark:text-immich-dark-fg dark:disabled:bg-gray-800 dark:disabled:text-gray-200;
|
||||
@apply bg-gray-100 ring-1 ring-gray-200 transition outline-none focus-within:ring-1 disabled:cursor-not-allowed dark:bg-gray-800 dark:ring-black flex w-full items-center rounded-lg disabled:bg-gray-300 disabled:text-dark dark:disabled:bg-gray-900 dark:disabled:text-gray-200 flex-1 py-2.5 text-base pl-4 pr-4;
|
||||
}
|
||||
|
||||
@utility immich-form-label {
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<div class="flex items-center gap-2 text-xl font-semibold text-primary">
|
||||
<Link class="flex items-center gap-2 hover:underline" href={getQueueDetailUrl(queue)} underline={false}>
|
||||
<Icon {icon} size="1.25em" class="hidden shrink-0 sm:block" />
|
||||
<span class="uppercase">{title}</span>
|
||||
<span>{title}</span>
|
||||
</Link>
|
||||
<IconButton
|
||||
color="primary"
|
||||
@@ -130,7 +130,7 @@
|
||||
onClick={() => onCommand({ command: QueueCommand.Start, force: false })}
|
||||
>
|
||||
<Icon icon={mdiAlertCircle} size="36" />
|
||||
<span class="uppercase">{$t('disabled')}</span>
|
||||
<span>{$t('disabled')}</span>
|
||||
</QueueCardButton>
|
||||
{/if}
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
{#if waitingCount > 0}
|
||||
<QueueCardButton color="gray" onClick={() => onCommand({ command: QueueCommand.Empty, force: false })}>
|
||||
<Icon icon={mdiClose} size="24" />
|
||||
<span class="uppercase">{$t('clear')}</span>
|
||||
<span>{$t('clear')}</span>
|
||||
</QueueCardButton>
|
||||
{/if}
|
||||
{#if queue.isPaused}
|
||||
@@ -146,12 +146,12 @@
|
||||
<QueueCardButton color="light-gray" onClick={() => onCommand({ command: QueueCommand.Resume, force: false })}>
|
||||
<!-- size property is not reactive, so have to use width and height -->
|
||||
<Icon icon={mdiFastForward} {size} />
|
||||
<span class="uppercase">{$t('resume')}</span>
|
||||
<span>{$t('resume')}</span>
|
||||
</QueueCardButton>
|
||||
{:else}
|
||||
<QueueCardButton color="light-gray" onClick={() => onCommand({ command: QueueCommand.Pause, force: false })}>
|
||||
<Icon icon={mdiPause} size="24" />
|
||||
<span class="uppercase">{$t('pause')}</span>
|
||||
<span>{$t('pause')}</span>
|
||||
</QueueCardButton>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -160,25 +160,25 @@
|
||||
{#if allText}
|
||||
<QueueCardButton color="dark-gray" onClick={() => onCommand({ command: QueueCommand.Start, force: true })}>
|
||||
<Icon icon={mdiAllInclusive} size="24" />
|
||||
<span class="uppercase">{allText}</span>
|
||||
<span>{allText}</span>
|
||||
</QueueCardButton>
|
||||
{/if}
|
||||
{#if refreshText}
|
||||
<QueueCardButton color="gray" onClick={() => onCommand({ command: QueueCommand.Start, force: undefined })}>
|
||||
<Icon icon={mdiImageRefreshOutline} size="24" />
|
||||
<span class="uppercase">{refreshText}</span>
|
||||
<span>{refreshText}</span>
|
||||
</QueueCardButton>
|
||||
{/if}
|
||||
<QueueCardButton color="light-gray" onClick={() => onCommand({ command: QueueCommand.Start, force: false })}>
|
||||
<Icon icon={mdiSelectionSearch} size="24" />
|
||||
<span class="uppercase">{missingText}</span>
|
||||
<span>{missingText}</span>
|
||||
</QueueCardButton>
|
||||
{/if}
|
||||
|
||||
{#if !disabled && !multipleButtons && isIdle}
|
||||
<QueueCardButton color="light-gray" onClick={() => onCommand({ command: QueueCommand.Start, force: false })}>
|
||||
<Icon icon={mdiPlay} size="48" />
|
||||
<span class="uppercase">{missingText}</span>
|
||||
<span>{missingText}</span>
|
||||
</QueueCardButton>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
<h3 class="text-base font-medium text-primary">{$t('template')}</h3>
|
||||
|
||||
<div class="my-2 text-sm">
|
||||
<h4 class="uppercase">{$t('preview')}</h4>
|
||||
<h4 class="text-sm">{$t('preview')}</h4>
|
||||
</div>
|
||||
|
||||
<p class="text-sm">
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
</script>
|
||||
|
||||
<div class="mt-4 text-sm">
|
||||
<h4 class="uppercase">{$t('other_variables')}</h4>
|
||||
<h4 class="">{$t('other_variables')}</h4>
|
||||
</div>
|
||||
|
||||
<div class="p-4 mt-2 text-xs bg-gray-200 rounded-lg dark:bg-gray-700 dark:text-immich-dark-fg">
|
||||
<div class="flex gap-12">
|
||||
<div>
|
||||
<p class="uppercase font-medium text-primary">{$t('filename')}</p>
|
||||
<p class="font-medium text-primary">{$t('filename')}</p>
|
||||
<ul>
|
||||
<li>{`{{filename}}`} - IMG_123</li>
|
||||
<li>{`{{ext}}`} - jpg</li>
|
||||
@@ -17,14 +17,14 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="uppercase font-medium text-primary">{$t('filetype')}</p>
|
||||
<p class="font-medium text-primary">{$t('filetype')}</p>
|
||||
<ul>
|
||||
<li>{`{{filetype}}`} - VID or IMG</li>
|
||||
<li>{`{{filetypefull}}`} - VIDEO or IMAGE</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p class="uppercase font-medium text-primary">{$t('album')}</p>
|
||||
<p class="font-medium text-primary">{$t('album')}</p>
|
||||
<ul>
|
||||
<li>{`{{album}}`} - Album Name</li>
|
||||
<li>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import AssetTagModal from '$lib/modals/AssetTagModal.svelte';
|
||||
import { removeTag } from '$lib/utils/asset-utils';
|
||||
import { getAssetInfo, type AssetResponseDto } from '@immich/sdk';
|
||||
import { Icon, modalManager } from '@immich/ui';
|
||||
import { Icon, modalManager, Text } from '@immich/ui';
|
||||
import { mdiClose, mdiPlus } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
{#if isOwner && !authManager.isSharedLink}
|
||||
<section class="px-4 mt-4">
|
||||
<div class="flex h-10 w-full items-center justify-between text-sm">
|
||||
<h2 class="uppercase">{$t('tags')}</h2>
|
||||
<Text color="muted">{$t('tags')}</Text>
|
||||
</div>
|
||||
<section class="flex flex-wrap pt-2 gap-1" data-testid="detail-panel-tags">
|
||||
{#each tags as tag (tag.id)}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import { fromISODateTime, fromISODateTimeUTC, toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import { getParentPath } from '$lib/utils/tree-utils';
|
||||
import { AssetMediaSize, getAssetInfo, type AlbumResponseDto, type AssetResponseDto } from '@immich/sdk';
|
||||
import { Icon, IconButton, LoadingSpinner, modalManager } from '@immich/ui';
|
||||
import { Icon, IconButton, LoadingSpinner, modalManager, Text } from '@immich/ui';
|
||||
import {
|
||||
mdiCalendar,
|
||||
mdiCamera,
|
||||
@@ -163,7 +163,7 @@
|
||||
{#if !authManager.isSharedLink && isOwner}
|
||||
<section class="px-4 pt-4 text-sm">
|
||||
<div class="flex h-10 w-full items-center justify-between">
|
||||
<h2 class="uppercase">{$t('people')}</h2>
|
||||
<Text size="small" color="muted">{$t('people')}</Text>
|
||||
<div class="flex gap-2 items-center">
|
||||
{#if people.some((person) => person.isHidden)}
|
||||
<IconButton
|
||||
@@ -266,10 +266,10 @@
|
||||
<div class="px-4 py-4">
|
||||
{#if asset.exifInfo}
|
||||
<div class="flex h-10 w-full items-center justify-between text-sm">
|
||||
<h2 class="uppercase">{$t('details')}</h2>
|
||||
<Text size="small" color="muted">{$t('details')}</Text>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="uppercase text-sm">{$t('no_exif_info_available')}</p>
|
||||
<Text size="small" color="muted">{$t('no_exif_info_available')}</Text>
|
||||
{/if}
|
||||
|
||||
{#if dateTime}
|
||||
@@ -496,7 +496,7 @@
|
||||
|
||||
{#if currentAlbum && currentAlbum.albumUsers.length > 0 && asset.owner}
|
||||
<section class="px-6 dark:text-immich-dark-fg mt-4">
|
||||
<p class="uppercase text-sm">{$t('shared_by')}</p>
|
||||
<Text size="small" color="muted">{$t('shared_by')}</Text>
|
||||
<div class="flex gap-4 pt-4">
|
||||
<div>
|
||||
<UserAvatar user={asset.owner} size="md" />
|
||||
@@ -513,7 +513,9 @@
|
||||
|
||||
{#if albums.length > 0}
|
||||
<section class="px-6 py-6 dark:text-immich-dark-fg">
|
||||
<p class="uppercase pb-4 text-sm">{$t('appears_in')}</p>
|
||||
<div class="pb-4">
|
||||
<Text size="small" color="muted">{$t('appears_in')}</Text>
|
||||
</div>
|
||||
{#each albums as album (album.id)}
|
||||
<a href={resolve(`${AppRoute.ALBUMS}/${album.id}`)}>
|
||||
<div class="flex gap-4 pt-2 hover:cursor-pointer items-center">
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
import { focusOutside } from '$lib/actions/focus-outside';
|
||||
import { shortcuts } from '$lib/actions/shortcut';
|
||||
import { generateId } from '$lib/utils/generate-id';
|
||||
import { Icon, IconButton, Label } from '@immich/ui';
|
||||
import { mdiClose, mdiMagnify, mdiUnfoldMoreHorizontal } from '@mdi/js';
|
||||
import { Icon, IconButton } from '@immich/ui';
|
||||
import { mdiChevronDown, mdiClose, mdiMagnify } from '@mdi/js';
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { FormEventHandler } from 'svelte/elements';
|
||||
@@ -251,7 +251,7 @@
|
||||
</script>
|
||||
|
||||
<svelte:window onresize={onPositionChange} />
|
||||
<Label class="block mb-1 {hideLabel ? 'sr-only' : ''}" for={inputId}>{label}</Label>
|
||||
<p class="block mb-1 {hideLabel ? 'sr-only' : ''} text-xs text-neutral-500 font-light">{label}</p>
|
||||
<div
|
||||
class="relative w-full dark:text-gray-300 text-gray-700 text-base"
|
||||
use:focusOutside={{ onFocusOut: deactivate }}
|
||||
@@ -351,7 +351,7 @@
|
||||
size="small"
|
||||
/>
|
||||
{:else if !isOpen}
|
||||
<Icon icon={mdiUnfoldMoreHorizontal} aria-hidden />
|
||||
<Icon icon={mdiChevronDown} aria-hidden />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -391,7 +391,7 @@
|
||||
<li
|
||||
aria-selected={index === selectedIndex}
|
||||
bind:this={optionRefs[index]}
|
||||
class="text-start w-full px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all cursor-pointer aria-selected:bg-gray-200 aria-selected:dark:bg-gray-700 break-words"
|
||||
class="text-start w-full px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all cursor-pointer aria-selected:bg-gray-200 aria-selected:dark:bg-gray-700 wrap-break-words"
|
||||
id={`${listboxId}-${index}`}
|
||||
onclick={() => handleSelect(option)}
|
||||
role="option"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import Combobox, { asComboboxOptions, asSelectedOption } from '$lib/components/shared-components/combobox.svelte';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
import { SearchSuggestionType, getSearchSuggestions } from '@immich/sdk';
|
||||
import { Text } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
interface Props {
|
||||
@@ -81,8 +82,7 @@
|
||||
</script>
|
||||
|
||||
<div id="camera-selection">
|
||||
<p class="uppercase immich-form-label">{$t('camera')}</p>
|
||||
|
||||
<Text fontWeight="medium">{$t('camera')}</Text>
|
||||
<div class="grid grid-auto-fit-40 gap-5 mt-1">
|
||||
<div class="w-full">
|
||||
<Combobox
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script lang="ts" module>
|
||||
export interface SearchDateFilter {
|
||||
takenBefore?: string;
|
||||
takenAfter?: string;
|
||||
takenBefore?: DateTime;
|
||||
takenAfter?: DateTime;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import DateInput from '$lib/elements/DateInput.svelte';
|
||||
import { Text } from '@immich/ui';
|
||||
import { DatePicker, Text } from '@immich/ui';
|
||||
import type { DateTime } from 'luxon';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
interface Props {
|
||||
@@ -17,23 +17,19 @@
|
||||
let { filters = $bindable() }: Props = $props();
|
||||
|
||||
let invalid = $derived(filters.takenAfter && filters.takenBefore && filters.takenAfter > filters.takenBefore);
|
||||
|
||||
const inputClasses = $derived(
|
||||
`immich-form-input w-full mt-1 hover:cursor-pointer ${invalid ? 'border border-danger' : ''}`,
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<div id="date-range-selection" class="grid grid-auto-fit-40 gap-5">
|
||||
<label class="immich-form-label" for="start-date">
|
||||
<span class="uppercase">{$t('start_date')}</span>
|
||||
<DateInput class={inputClasses} type="date" id="start-date" name="start-date" bind:value={filters.takenAfter} />
|
||||
</label>
|
||||
<div>
|
||||
<Text class="mb-2" fontWeight="medium">{$t('start_date')}</Text>
|
||||
<DatePicker bind:value={filters.takenAfter} />
|
||||
</div>
|
||||
|
||||
<label class="immich-form-label" for="end-date">
|
||||
<span class="uppercase">{$t('end_date')}</span>
|
||||
<DateInput class={inputClasses} type="date" id="end-date" name="end-date" bind:value={filters.takenBefore} />
|
||||
</label>
|
||||
<div>
|
||||
<Text class="mb-2" fontWeight="medium">{$t('end_date')}</Text>
|
||||
<DatePicker bind:value={filters.takenBefore} />
|
||||
</div>
|
||||
</div>
|
||||
{#if invalid}
|
||||
<Text color="danger">{$t('start_date_before_end_date')}</Text>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Checkbox, Label } from '@immich/ui';
|
||||
import { Checkbox, Label, Text } from '@immich/ui';
|
||||
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
@@ -20,19 +20,20 @@
|
||||
|
||||
<div id="display-options-selection">
|
||||
<fieldset>
|
||||
<legend class="uppercase immich-form-label">{$t('display_options')}</legend>
|
||||
<Text class="mb-2" fontWeight="medium">{$t('display_options')}</Text>
|
||||
|
||||
<div class="flex flex-wrap gap-x-5 gap-y-2 mt-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="not-in-album-checkbox" size="tiny" bind:checked={filters.isNotInAlbum} />
|
||||
<Label label={$t('not_in_any_album')} for="not-in-album-checkbox" />
|
||||
<Label label={$t('not_in_any_album')} for="not-in-album-checkbox" class="text-sm font-normal" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="archive-checkbox" size="tiny" bind:checked={filters.isArchive} />
|
||||
<Label label={$t('archive')} for="archive-checkbox" />
|
||||
<Label label={$t('archive')} for="archive-checkbox" class="text-sm font-normal" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="favorites-checkbox" size="tiny" bind:checked={filters.isFavorite} />
|
||||
<Label label={$t('favorites')} for="favorites-checkbox" />
|
||||
<Label label={$t('favorites')} for="favorites-checkbox" class="text-sm font-normal" />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import Combobox, { asComboboxOptions, asSelectedOption } from '$lib/components/shared-components/combobox.svelte';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
import { getSearchSuggestions, SearchSuggestionType } from '@immich/sdk';
|
||||
import { Text } from '@immich/ui';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
@@ -74,7 +75,7 @@
|
||||
</script>
|
||||
|
||||
<div id="location-selection">
|
||||
<p class="uppercase immich-form-label">{$t('place')}</p>
|
||||
<Text fontWeight="medium">{$t('place')}</Text>
|
||||
|
||||
<div class="grid grid-auto-fit-40 gap-5 mt-1">
|
||||
<div class="w-full">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { MediaType } from '$lib/constants';
|
||||
import RadioButton from '$lib/elements/RadioButton.svelte';
|
||||
import { Text } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
interface Props {
|
||||
@@ -12,7 +13,8 @@
|
||||
|
||||
<div id="media-type-selection">
|
||||
<fieldset>
|
||||
<legend class="uppercase immich-form-label">{$t('media_type')}</legend>
|
||||
<Text class="mb-2" fontWeight="medium">{$t('media_type')}</Text>
|
||||
|
||||
<div class="flex flex-wrap gap-x-5 gap-y-2 mt-1">
|
||||
<RadioButton name="media-type" id="type-all" bind:group={filteredMedia} label={$t('all')} value={MediaType.All} />
|
||||
<RadioButton
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { getAllPeople, type PersonResponseDto } from '@immich/sdk';
|
||||
import { Button, LoadingSpinner } from '@immich/ui';
|
||||
import { Button, LoadingSpinner, Text } from '@immich/ui';
|
||||
import { mdiArrowRight, mdiClose } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { SvelteSet } from 'svelte/reactivity';
|
||||
@@ -63,12 +63,12 @@
|
||||
|
||||
<div id="people-selection" class="max-h-60 -mb-4 overflow-y-auto immich-scrollbar">
|
||||
<div class="flex items-center w-full justify-between gap-6">
|
||||
<p class="uppercase immich-form-label py-3">{$t('people')}</p>
|
||||
<Text class="py-3" fontWeight="medium">{$t('people')}</Text>
|
||||
<SearchBar bind:name placeholder={$t('filter_people')} showLoadingSpinner={false} />
|
||||
</div>
|
||||
|
||||
<SingleGridRow
|
||||
class="grid grid-auto-fill-20 gap-1 mt-2 overflow-y-auto immich-scrollbar"
|
||||
class="grid grid-auto-fill-20 gap-1 mt-2 overflow-y-auto immich-scrollbar space-between"
|
||||
bind:itemCount={numberOfPeople}
|
||||
>
|
||||
{#each peopleList as person (person.id)}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Text } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
import Combobox from '../combobox.svelte';
|
||||
|
||||
@@ -18,16 +19,14 @@
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="grid grid-auto-fit-40 gap-5">
|
||||
<label class="immich-form-label" for="start-date">
|
||||
<div class="[&_label]:uppercase">
|
||||
<Combobox
|
||||
label={$t('rating')}
|
||||
placeholder={$t('search_rating')}
|
||||
{options}
|
||||
selectedOption={rating === undefined ? undefined : options[rating]}
|
||||
onSelect={(r) => (rating = r === undefined ? undefined : Number.parseInt(r.value))}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<div class="flex flex-col">
|
||||
<Text class="mb-2" fontWeight="medium">{$t('rating')}</Text>
|
||||
<Combobox
|
||||
label={$t('rating')}
|
||||
placeholder={$t('search_rating')}
|
||||
hideLabel
|
||||
{options}
|
||||
selectedOption={rating === undefined ? undefined : options[rating]}
|
||||
onSelect={(r) => (rating = r === undefined ? undefined : Number.parseInt(r.value))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import Combobox, { type ComboBoxOption } from '$lib/components/shared-components/combobox.svelte';
|
||||
import { preferences } from '$lib/stores/user.store';
|
||||
import { getAllTags, type TagResponseDto } from '@immich/sdk';
|
||||
import { Checkbox, Icon, Label } from '@immich/ui';
|
||||
import { Checkbox, Icon, Label, Text } from '@immich/ui';
|
||||
import { mdiClose } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
@@ -43,18 +43,18 @@
|
||||
{#if $preferences?.tags?.enabled}
|
||||
<div id="location-selection">
|
||||
<form autocomplete="off" id="create-tag-form">
|
||||
<div class="my-4 flex flex-col gap-2">
|
||||
<div class="[&_label]:uppercase">
|
||||
<Combobox
|
||||
disabled={selectedTags === null}
|
||||
onSelect={handleSelect}
|
||||
label={$t('tags')}
|
||||
defaultFirstOption
|
||||
options={allTags.map((tag) => ({ id: tag.id, label: tag.value, value: tag.id }))}
|
||||
bind:selectedOption
|
||||
placeholder={$t('search_tags')}
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4 flex flex-col">
|
||||
<Text class="py-3" fontWeight="medium">{$t('tags')}</Text>
|
||||
<Combobox
|
||||
disabled={selectedTags === null}
|
||||
hideLabel
|
||||
onSelect={handleSelect}
|
||||
label={$t('tags')}
|
||||
defaultFirstOption
|
||||
options={allTags.map((tag) => ({ id: tag.id, label: tag.value, value: tag.id }))}
|
||||
bind:selectedOption
|
||||
placeholder={$t('search_tags')}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox
|
||||
@@ -65,7 +65,7 @@
|
||||
selectedTags = checked ? null : new SvelteSet();
|
||||
}}
|
||||
/>
|
||||
<Label label={$t('untagged')} for="untagged-checkbox" />
|
||||
<Label label={$t('untagged')} for="untagged-checkbox" class="text-sm font-normal" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import RadioButton from '$lib/elements/RadioButton.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { Field, Input, Text } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
interface Props {
|
||||
@@ -11,73 +12,76 @@
|
||||
let { query = $bindable(), queryType = $bindable('smart') }: Props = $props();
|
||||
</script>
|
||||
|
||||
<fieldset>
|
||||
<legend class="immich-form-label">{$t('search_type')}</legend>
|
||||
<div class="flex flex-wrap gap-x-5 gap-y-2 mt-1 mb-2">
|
||||
{#if featureFlagsManager.value.smartSearch}
|
||||
<RadioButton name="query-type" id="context-radio" label={$t('context')} bind:group={queryType} value="smart" />
|
||||
{/if}
|
||||
<RadioButton
|
||||
name="query-type"
|
||||
id="file-name-radio"
|
||||
label={$t('file_name_or_extension')}
|
||||
bind:group={queryType}
|
||||
value="metadata"
|
||||
/>
|
||||
<RadioButton
|
||||
name="query-type"
|
||||
id="description-radio"
|
||||
label={$t('description')}
|
||||
bind:group={queryType}
|
||||
value="description"
|
||||
/>
|
||||
{#if featureFlagsManager.value.ocr}
|
||||
<RadioButton name="query-type" id="ocr-radio" label={$t('ocr')} bind:group={queryType} value="ocr" />
|
||||
{/if}
|
||||
</div>
|
||||
</fieldset>
|
||||
<section>
|
||||
<fieldset>
|
||||
<Text class="mb-2" fontWeight="medium">{$t('search_type')}</Text>
|
||||
<div class="flex flex-wrap gap-x-5 gap-y-2 my-2">
|
||||
{#if featureFlagsManager.value.smartSearch}
|
||||
<RadioButton name="query-type" id="context-radio" label={$t('context')} bind:group={queryType} value="smart" />
|
||||
{/if}
|
||||
<RadioButton
|
||||
name="query-type"
|
||||
id="file-name-radio"
|
||||
label={$t('file_name_or_extension')}
|
||||
bind:group={queryType}
|
||||
value="metadata"
|
||||
/>
|
||||
<RadioButton
|
||||
name="query-type"
|
||||
id="description-radio"
|
||||
label={$t('description')}
|
||||
bind:group={queryType}
|
||||
value="description"
|
||||
/>
|
||||
{#if featureFlagsManager.value.ocr}
|
||||
<RadioButton name="query-type" id="ocr-radio" label={$t('ocr')} bind:group={queryType} value="ocr" />
|
||||
{/if}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
{#if queryType === 'smart'}
|
||||
<label for="context-input" class="immich-form-label">{$t('search_by_context')}</label>
|
||||
<input
|
||||
class="immich-form-input hover:cursor-text w-full mt-1!"
|
||||
type="text"
|
||||
id="context-input"
|
||||
name="context"
|
||||
placeholder={$t('sunrise_on_the_beach')}
|
||||
bind:value={query}
|
||||
/>
|
||||
{:else if queryType === 'metadata'}
|
||||
<label for="file-name-input" class="immich-form-label">{$t('search_by_filename')}</label>
|
||||
<input
|
||||
class="immich-form-input hover:cursor-text w-full mt-1!"
|
||||
type="text"
|
||||
id="file-name-input"
|
||||
name="file-name"
|
||||
placeholder={$t('search_by_filename_example')}
|
||||
bind:value={query}
|
||||
aria-labelledby="file-name-label"
|
||||
/>
|
||||
{:else if queryType === 'description'}
|
||||
<label for="description-input" class="immich-form-label">{$t('search_by_description')}</label>
|
||||
<input
|
||||
class="immich-form-input hover:cursor-text w-full mt-1!"
|
||||
type="text"
|
||||
id="description-input"
|
||||
name="description"
|
||||
placeholder={$t('search_by_description_example')}
|
||||
bind:value={query}
|
||||
aria-labelledby="description-label"
|
||||
/>
|
||||
{:else if queryType === 'ocr'}
|
||||
<label for="ocr-input" class="immich-form-label">{$t('search_by_ocr')}</label>
|
||||
<input
|
||||
class="immich-form-input hover:cursor-text w-full mt-1!"
|
||||
type="text"
|
||||
id="ocr-input"
|
||||
name="ocr"
|
||||
placeholder={$t('search_by_ocr_example')}
|
||||
bind:value={query}
|
||||
aria-labelledby="ocr-label"
|
||||
/>
|
||||
{/if}
|
||||
{#if queryType === 'smart'}
|
||||
<Field label={$t('search_by_context')} for="context-input">
|
||||
<Input
|
||||
type="text"
|
||||
id="context-input"
|
||||
name="context"
|
||||
placeholder={$t('sunrise_on_the_beach')}
|
||||
bind:value={query}
|
||||
aria-labelledby="context-label"
|
||||
/>
|
||||
</Field>
|
||||
{:else if queryType === 'metadata'}
|
||||
<Field label={$t('search_by_filename')} for="file-name-input">
|
||||
<Input
|
||||
type="text"
|
||||
id="file-name-input"
|
||||
name="context"
|
||||
placeholder={$t('search_by_filename_example')}
|
||||
bind:value={query}
|
||||
aria-labelledby="file-name-label"
|
||||
/>
|
||||
</Field>
|
||||
{:else if queryType === 'description'}
|
||||
<Field label={$t('search_by_description')} for="description">
|
||||
<Input
|
||||
type="text"
|
||||
id="description-input"
|
||||
name="description"
|
||||
placeholder={$t('search_by_description_example')}
|
||||
bind:value={query}
|
||||
aria-labelledby="description-label"
|
||||
/>
|
||||
</Field>
|
||||
{:else if queryType === 'ocr'}
|
||||
<Field label={$t('search_by_ocr')} for="ocr-input">
|
||||
<Input
|
||||
type="text"
|
||||
id="ocr-input"
|
||||
name="ocr"
|
||||
placeholder={$t('search_by_ocr_example')}
|
||||
bind:value={query}
|
||||
aria-labelledby="ocr-label"
|
||||
/>
|
||||
</Field>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="radio" {name} {id} {value} class="focus-visible:ring" bind:group />
|
||||
<label for={id}>{label}</label>
|
||||
<label for={id} class="text-sm">{label}</label>
|
||||
</div>
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
import { AssetTypeEnum, AssetVisibility, type MetadataSearchDto, type SmartSearchDto } from '@immich/sdk';
|
||||
import { Button, HStack, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiTune } from '@mdi/js';
|
||||
import type { DateTime } from 'luxon';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
|
||||
@@ -47,8 +48,8 @@
|
||||
|
||||
let { searchQuery, onClose }: Props = $props();
|
||||
|
||||
const parseOptionalDate = (dateString?: string) => (dateString ? parseUtcDate(dateString) : undefined);
|
||||
const toStartOfDayDate = (dateString: string) => parseUtcDate(dateString)?.startOf('day').toISODate() || undefined;
|
||||
const parseOptionalDate = (dateString?: DateTime) => (dateString ? parseUtcDate(dateString.toString()) : undefined);
|
||||
const toStartOfDayDate = (dateString: string) => parseUtcDate(dateString)?.startOf('day') || undefined;
|
||||
const formId = generateId();
|
||||
|
||||
// combobox and all the search components have terrible support for value | null so we use empty string instead.
|
||||
@@ -187,7 +188,7 @@
|
||||
<Modal icon={mdiTune} size="giant" title={$t('search_options')} {onClose}>
|
||||
<ModalBody>
|
||||
<form id={formId} autocomplete="off" {onsubmit} {onreset}>
|
||||
<div class="flex flex-col gap-4 pb-10" tabindex="-1">
|
||||
<div class="flex flex-col gap-5 pb-10" tabindex="-1">
|
||||
<!-- PEOPLE -->
|
||||
<SearchPeopleSection bind:selectedPeople={filter.personIds} />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user