fix: hide faces (#3352)

* fix: hide faces

* remove unused variable

* fix: work even if one fails

* better style for hidden people

* add hide face in the menu dropdown

* add buttons to toggle visibility for all faces

* add server test

* close modal with escape key

* fix: explore page

* improve show & hide faces modal

* keep name on people card

* simplify layout

* sticky app bar in show-hide page

* fix format

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
martin 2023-07-23 05:00:43 +02:00 committed by GitHub
parent c40aa4399b
commit ed64c91da6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1097 additions and 72 deletions

View file

@ -15,12 +15,15 @@
export let circle = false;
export let hidden = false;
let complete = false;
export let eyeColor = 'white';
</script>
<img
style:width={widthStyle}
style:height={heightStyle}
style:filter={hidden ? 'grayscale(75%)' : 'none'}
style:filter={hidden ? 'grayscale(50%)' : 'none'}
style:opacity={hidden ? '0.5' : '1'}
src={url}
alt={altText}
class="object-cover transition duration-300"
@ -32,9 +35,10 @@
use:imageLoad
on:image-load|once={() => (complete = true)}
/>
{#if hidden}
<div class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform">
<EyeOffOutline size="2em" />
<EyeOffOutline size="2em" color={eyeColor} />
</div>
{/if}

View file

@ -25,7 +25,7 @@
$: unselectedPeople = people.filter((source) => !selectedPeople.includes(source) && source.id !== person.id);
onMount(async () => {
const { data } = await api.personApi.getAllPeople({ withHidden: true });
const { data } = await api.personApi.getAllPeople({ withHidden: false });
people = data.people;
});

View file

@ -20,17 +20,19 @@
const onMergeFacesClicked = () => {
dispatch('merge-faces', person);
};
const onHideFaceClicked = () => {
dispatch('hide-face', person);
};
</script>
<div id="people-card" class="relative">
<a href="/people/{person.id}" draggable="false">
<div class="w-48 rounded-xl brightness-95 filter">
<div class="h-48 w-48 rounded-xl brightness-95 filter">
<ImageThumbnail shadow url={api.getPeopleThumbnailUrl(person.id)} altText={person.name} widthStyle="100%" />
</div>
{#if person.name}
<span
class="w-100 absolute bottom-2 w-full text-ellipsis px-1 text-center font-medium text-white backdrop-blur-[1px] hover:cursor-pointer"
>
<span class="absolute bottom-2 left-0 w-full select-text px-1 text-center font-medium text-white">
{person.name}
</span>
{/if}
@ -50,6 +52,7 @@
{#if showContextMenu}
<ContextMenu on:outclick={() => (showContextMenu = false)}>
<MenuOption on:click={() => onHideFaceClicked()} text="Hide face" />
<MenuOption on:click={() => onChangeNameClicked()} text="Change name" />
<MenuOption on:click={() => onMergeFacesClicked()} text="Merge faces" />
</ContextMenu>

View file

@ -1,12 +1,19 @@
<script>
<script lang="ts">
import { fly } from 'svelte/transition';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import { quintOut } from 'svelte/easing';
import Close from 'svelte-material-icons/Close.svelte';
import IconButton from '../elements/buttons/icon-button.svelte';
import { createEventDispatcher } from 'svelte';
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
import Restart from 'svelte-material-icons/Restart.svelte';
import Eye from 'svelte-material-icons/Eye.svelte';
import EyeOff from 'svelte-material-icons/EyeOff.svelte';
const dispatch = createEventDispatcher();
export let showLoadingSpinner: boolean;
export let toggleVisibility: boolean;
</script>
<section
@ -14,17 +21,30 @@
class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
>
<div
class="absolute flex h-16 w-full place-items-center justify-between border-b dark:border-immich-dark-gray dark:text-immich-dark-fg"
class="sticky top-0 z-10 flex h-16 w-full items-center justify-between border-b bg-white p-1 dark:border-immich-dark-gray dark:bg-black dark:text-immich-dark-fg md:p-8"
>
<div class="flex w-full items-center justify-between p-8">
<div class="flex items-center">
<CircleIconButton logo={Close} on:click={() => dispatch('closeClick')} />
<p class="ml-4">Show & hide faces</p>
</div>
<IconButton on:click={() => dispatch('doneClick')}>Done</IconButton>
<div class="flex items-center">
<CircleIconButton logo={Close} on:click={() => dispatch('closeClick')} />
<p class="ml-4 hidden sm:block">Show & hide faces</p>
</div>
<div class="immich-scrollbar absolute top-16 h-[calc(100%-theme(spacing.16))] w-full p-4 pb-8">
<slot />
<div class="flex items-center justify-end">
<div class="flex items-center md:mr-8">
<CircleIconButton title="Reset faces visibility" logo={Restart} on:click={() => dispatch('reset-visibility')} />
<CircleIconButton
title="Toggle visibility"
logo={toggleVisibility ? Eye : EyeOff}
on:click={() => dispatch('toggle-visibility')}
/>
</div>
{#if !showLoadingSpinner}
<IconButton on:click={() => dispatch('doneClick')}>Done</IconButton>
{:else}
<LoadingSpinner />
{/if}
</div>
</div>
<div class="flex w-full flex-wrap gap-1 bg-immich-bg p-2 pb-8 dark:bg-immich-dark-bg md:px-8 md:pt-4">
<slot />
</div>
</section>