group with sub-section
parent
e4f1c4ee64
commit
41c20244b5
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"metadata": "Metadata",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"account_settings": "Account Settings",
|
"account_settings": "Account Settings",
|
||||||
|
|
@ -756,6 +757,7 @@
|
||||||
"connected_device": "Connected device",
|
"connected_device": "Connected device",
|
||||||
"connected_to": "Connected to",
|
"connected_to": "Connected to",
|
||||||
"contain": "Contain",
|
"contain": "Contain",
|
||||||
|
"content": "Content",
|
||||||
"context": "Context",
|
"context": "Context",
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
columns?: 1 | 2;
|
||||||
|
children?: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { title, columns = 1, children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend class="mb-3 text-xs font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
||||||
|
{title}
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="grid gap-3"
|
||||||
|
class:grid-cols-1={columns === 1}
|
||||||
|
class:md:grid-cols-2={columns === 2}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
@ -1,56 +1,23 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Icon } from '@immich/ui';
|
import { Icon } from '@immich/ui';
|
||||||
import { mdiChevronDown } from '@mdi/js';
|
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import { slide } from 'svelte/transition';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
expanded?: boolean;
|
|
||||||
children?: Snippet;
|
children?: Snippet;
|
||||||
isFirst?: boolean;
|
|
||||||
isLast?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let { title, icon, expanded = $bindable(false), children, isFirst = false, isLast = false }: Props = $props();
|
let { title, icon, children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<section class="rounded-xl border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800/50">
|
||||||
class="border border-gray-200 dark:border-gray-700 overflow-hidden transition-all duration-200"
|
<header class="mb-3 flex items-center gap-2">
|
||||||
class:rounded-t-2xl={isFirst}
|
<Icon {icon} size="18" class="text-immich-primary dark:text-immich-dark-primary" />
|
||||||
class:rounded-b-2xl={isLast}
|
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-200">{title}</h3>
|
||||||
>
|
</header>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-expanded={expanded}
|
|
||||||
onclick={() => (expanded = !expanded)}
|
|
||||||
class="flex w-full items-center justify-between gap-3 px-4 py-3 text-start bg-light-50 hover:bg-primary-50 dark:hover:bg-gray-800 transition-colors"
|
|
||||||
class:bg-light-200={expanded}
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<div
|
|
||||||
class="flex items-center justify-center w-8 h-8 rounded-lg bg-immich-primary/10 dark:bg-immich-dark-primary/20"
|
|
||||||
>
|
|
||||||
<Icon {icon} size="18" class="text-immich-primary dark:text-immich-dark-primary" />
|
|
||||||
</div>
|
|
||||||
<span class="font-medium text-gray-900 dark:text-gray-100">{title}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div>
|
||||||
class="flex items-center justify-center w-7 h-7 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
|
{@render children?.()}
|
||||||
>
|
</div>
|
||||||
<Icon
|
</section>
|
||||||
icon={mdiChevronDown}
|
|
||||||
size="20"
|
|
||||||
class="text-gray-500 dark:text-gray-400 transition-transform duration-200 {expanded ? 'rotate-180' : ''}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{#if expanded}
|
|
||||||
<div transition:slide={{ duration: 150 }} class="px-4 py-4">
|
|
||||||
{@render children?.()}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,12 @@
|
||||||
} from '$lib/components/shared-components/search-bar/search-camera-section.svelte';
|
} from '$lib/components/shared-components/search-bar/search-camera-section.svelte';
|
||||||
import SearchDateSection from '$lib/components/shared-components/search-bar/search-date-section.svelte';
|
import SearchDateSection from '$lib/components/shared-components/search-bar/search-date-section.svelte';
|
||||||
import SearchDisplaySection from '$lib/components/shared-components/search-bar/search-display-section.svelte';
|
import SearchDisplaySection from '$lib/components/shared-components/search-bar/search-display-section.svelte';
|
||||||
|
import SearchFilterGroup from '$lib/components/shared-components/search-bar/search-filter-group.svelte';
|
||||||
import SearchFilterSection from '$lib/components/shared-components/search-bar/search-filter-section.svelte';
|
import SearchFilterSection from '$lib/components/shared-components/search-bar/search-filter-section.svelte';
|
||||||
import SearchLocationSection from '$lib/components/shared-components/search-bar/search-location-section.svelte';
|
import SearchLocationSection from '$lib/components/shared-components/search-bar/search-location-section.svelte';
|
||||||
import SearchMediaSection from '$lib/components/shared-components/search-bar/search-media-section.svelte';
|
import SearchMediaSection from '$lib/components/shared-components/search-bar/search-media-section.svelte';
|
||||||
import SearchPeopleSection from '$lib/components/shared-components/search-bar/search-people-section.svelte';
|
import SearchPeopleSection from '$lib/components/shared-components/search-bar/search-people-section.svelte';
|
||||||
import SearchRatingsSection from '$lib/components/shared-components/search-bar/search-ratings-section.svelte';
|
import SearchRatingsSection from '$lib/components/shared-components/search-bar/search-ratings-section.svelte';
|
||||||
import SearchTagsSection from '$lib/components/shared-components/search-bar/search-tags-section.svelte';
|
|
||||||
import SearchTextSection from '$lib/components/shared-components/search-bar/search-text-section.svelte';
|
import SearchTextSection from '$lib/components/shared-components/search-bar/search-text-section.svelte';
|
||||||
import { preferences } from '$lib/stores/user.store';
|
import { preferences } from '$lib/stores/user.store';
|
||||||
import { parseUtcDate } from '$lib/utils/date-time';
|
import { parseUtcDate } from '$lib/utils/date-time';
|
||||||
|
|
@ -46,7 +46,6 @@
|
||||||
mdiMagnify,
|
mdiMagnify,
|
||||||
mdiMapMarker,
|
mdiMapMarker,
|
||||||
mdiStar,
|
mdiStar,
|
||||||
mdiTag,
|
|
||||||
mdiTune,
|
mdiTune,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
@ -199,62 +198,54 @@
|
||||||
<Modal icon={mdiTune} size="giant" title={$t('search_options')} {onClose}>
|
<Modal icon={mdiTune} size="giant" title={$t('search_options')} {onClose}>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<form id={formId} autocomplete="off" {onsubmit} {onreset}>
|
<form id={formId} autocomplete="off" {onsubmit} {onreset}>
|
||||||
<div class="flex flex-col gap-0" tabindex="-1">
|
<div class="flex flex-col gap-6" tabindex="-1">
|
||||||
<!-- TEXT -->
|
<SearchFilterGroup title={$t('content')}>
|
||||||
<SearchFilterSection title={$t('search_type')} icon={mdiMagnify} isFirst expanded>
|
<SearchFilterSection title={$t('search_type')} icon={mdiMagnify}>
|
||||||
<SearchTextSection bind:query={filter.query} bind:queryType={filter.queryType} />
|
<SearchTextSection bind:query={filter.query} bind:queryType={filter.queryType} />
|
||||||
</SearchFilterSection>
|
|
||||||
|
|
||||||
<!-- PEOPLE -->
|
|
||||||
<SearchFilterSection title={$t('people')} icon={mdiAccountMultiple}>
|
|
||||||
<SearchPeopleSection bind:selectedPeople={filter.personIds} />
|
|
||||||
</SearchFilterSection>
|
|
||||||
|
|
||||||
<!-- TAGS -->
|
|
||||||
<SearchFilterSection title={$t('tags')} icon={mdiTag}>
|
|
||||||
<SearchTagsSection bind:selectedTags={filter.tagIds} />
|
|
||||||
</SearchFilterSection>
|
|
||||||
|
|
||||||
<!-- LOCATION -->
|
|
||||||
<SearchFilterSection title={$t('place')} icon={mdiMapMarker}>
|
|
||||||
<SearchLocationSection bind:filters={filter.location} />
|
|
||||||
</SearchFilterSection>
|
|
||||||
|
|
||||||
<!-- CAMERA MODEL -->
|
|
||||||
<SearchFilterSection title={$t('camera')} icon={mdiCamera}>
|
|
||||||
<SearchCameraSection bind:filters={filter.camera} />
|
|
||||||
</SearchFilterSection>
|
|
||||||
|
|
||||||
<!-- DATE RANGE -->
|
|
||||||
<SearchFilterSection title={$t('date_range')} icon={mdiCalendarRange}>
|
|
||||||
<SearchDateSection bind:filters={filter.date} />
|
|
||||||
</SearchFilterSection>
|
|
||||||
|
|
||||||
<!-- RATING -->
|
|
||||||
{#if $preferences?.ratings.enabled}
|
|
||||||
<SearchFilterSection title={$t('rating')} icon={mdiStar}>
|
|
||||||
<SearchRatingsSection bind:rating={filter.rating} />
|
|
||||||
</SearchFilterSection>
|
</SearchFilterSection>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!-- MEDIA TYPE & DISPLAY OPTIONS -->
|
<SearchFilterSection title={$t('people')} icon={mdiAccountMultiple}>
|
||||||
<SearchFilterSection title={$t('media_type')} icon={mdiImageMultiple}>
|
<SearchPeopleSection bind:selectedPeople={filter.personIds} />
|
||||||
<SearchMediaSection bind:filteredMedia={filter.mediaType} />
|
</SearchFilterSection>
|
||||||
</SearchFilterSection>
|
</SearchFilterGroup>
|
||||||
|
|
||||||
<SearchFilterSection title={$t('display_options')} icon={mdiCog} isLast>
|
<SearchFilterGroup title={$t('metadata')} columns={2}>
|
||||||
<SearchDisplaySection bind:filters={filter.display} />
|
<SearchFilterSection title={$t('place')} icon={mdiMapMarker}>
|
||||||
</SearchFilterSection>
|
<SearchLocationSection bind:filters={filter.location} />
|
||||||
|
</SearchFilterSection>
|
||||||
|
|
||||||
|
<SearchFilterSection title={$t('camera')} icon={mdiCamera}>
|
||||||
|
<SearchCameraSection bind:filters={filter.camera} />
|
||||||
|
</SearchFilterSection>
|
||||||
|
|
||||||
|
<SearchFilterSection title={$t('date_range')} icon={mdiCalendarRange}>
|
||||||
|
<SearchDateSection bind:filters={filter.date} />
|
||||||
|
</SearchFilterSection>
|
||||||
|
|
||||||
|
{#if $preferences?.ratings.enabled}
|
||||||
|
<SearchFilterSection title={$t('rating')} icon={mdiStar}>
|
||||||
|
<SearchRatingsSection bind:rating={filter.rating} />
|
||||||
|
</SearchFilterSection>
|
||||||
|
{/if}
|
||||||
|
</SearchFilterGroup>
|
||||||
|
|
||||||
|
<SearchFilterGroup title={$t('options')} columns={2}>
|
||||||
|
<SearchFilterSection title={$t('media_type')} icon={mdiImageMultiple}>
|
||||||
|
<SearchMediaSection bind:filteredMedia={filter.mediaType} />
|
||||||
|
</SearchFilterSection>
|
||||||
|
|
||||||
|
<SearchFilterSection title={$t('display_options')} icon={mdiCog}>
|
||||||
|
<SearchDisplaySection bind:filters={filter.display} />
|
||||||
|
</SearchFilterSection>
|
||||||
|
</SearchFilterGroup>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<HStack fullWidth>
|
<HStack fullWidth>
|
||||||
<Button shape="round" size="large" type="reset" color="secondary" fullWidth form={formId}
|
<Button shape="round" type="reset" color="secondary" fullWidth form={formId}>{$t('clear_all')}</Button>
|
||||||
>{$t('clear_all')}</Button
|
<Button shape="round" type="submit" fullWidth form={formId}>{$t('search')}</Button>
|
||||||
>
|
|
||||||
<Button shape="round" size="large" type="submit" fullWidth form={formId}>{$t('search')}</Button>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue