mirror of
https://github.com/immich-app/immich.git
synced 2025-11-25 12:00:42 +09:00
@@ -1,64 +1,60 @@
|
||||
<script lang="ts">
|
||||
import type Icon from 'svelte-material-icons/AbTesting.svelte';
|
||||
import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type Icon from 'svelte-material-icons/AbTesting.svelte';
|
||||
import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let title: string;
|
||||
export let logo: typeof Icon;
|
||||
export let isSelected: boolean;
|
||||
export let flippedLogo = false;
|
||||
export let title: string;
|
||||
export let logo: typeof Icon;
|
||||
export let isSelected: boolean;
|
||||
export let flippedLogo = false;
|
||||
|
||||
let showMoreInformation = false;
|
||||
let showMoreInformation = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const onButtonClicked = () => dispatch('selected');
|
||||
const dispatch = createEventDispatcher();
|
||||
const onButtonClicked = () => dispatch('selected');
|
||||
</script>
|
||||
|
||||
<div
|
||||
on:click={onButtonClicked}
|
||||
on:keydown={onButtonClicked}
|
||||
class="flex gap-4 justify-between place-items-center w-full transition-[padding] delay-100 duration-100 py-3 rounded-r-full hover:bg-immich-gray dark:hover:bg-immich-dark-gray hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary hover:cursor-pointer
|
||||
on:click={onButtonClicked}
|
||||
on:keydown={onButtonClicked}
|
||||
class="flex gap-4 justify-between place-items-center w-full transition-[padding] delay-100 duration-100 py-3 rounded-r-full hover:bg-immich-gray dark:hover:bg-immich-dark-gray hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary hover:cursor-pointer
|
||||
{isSelected
|
||||
? 'bg-immich-primary/10 dark:bg-immich-dark-primary/10 text-immich-primary dark:text-immich-dark-primary hover:bg-immich-primary/25'
|
||||
: ''}
|
||||
? 'bg-immich-primary/10 dark:bg-immich-dark-primary/10 text-immich-primary dark:text-immich-dark-primary hover:bg-immich-primary/25'
|
||||
: ''}
|
||||
pl-5 group-hover:sm:px-5 md:px-5
|
||||
"
|
||||
>
|
||||
<div class="flex gap-4 place-items-center w-full overflow-hidden truncate">
|
||||
<svelte:component
|
||||
this={logo}
|
||||
size="1.5em"
|
||||
class="shrink-0 {flippedLogo ? '-scale-x-100' : ''}"
|
||||
/>
|
||||
<p class="font-medium text-sm">{title}</p>
|
||||
</div>
|
||||
<div class="flex gap-4 place-items-center w-full overflow-hidden truncate">
|
||||
<svelte:component this={logo} size="1.5em" class="shrink-0 {flippedLogo ? '-scale-x-100' : ''}" />
|
||||
<p class="font-medium text-sm">{title}</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="transition-[height] group-hover:sm:overflow-visible md:overflow-visible overflow-hidden duration-100 delay-1000 sm:group-hover:h-auto md:h-auto h-0"
|
||||
>
|
||||
{#if $$slots.moreInformation}
|
||||
<div
|
||||
class="relative flex justify-center select-none cursor-default"
|
||||
on:mouseenter={() => (showMoreInformation = true)}
|
||||
on:mouseleave={() => (showMoreInformation = false)}
|
||||
>
|
||||
<div class="hover:cursor-help p-1 text-gray-600 dark:text-gray-400">
|
||||
<InformationOutline />
|
||||
</div>
|
||||
<div
|
||||
class="transition-[height] group-hover:sm:overflow-visible md:overflow-visible overflow-hidden duration-100 delay-1000 sm:group-hover:h-auto md:h-auto h-0"
|
||||
>
|
||||
{#if $$slots.moreInformation}
|
||||
<div
|
||||
class="relative flex justify-center select-none cursor-default"
|
||||
on:mouseenter={() => (showMoreInformation = true)}
|
||||
on:mouseleave={() => (showMoreInformation = false)}
|
||||
>
|
||||
<div class="hover:cursor-help p-1 text-gray-600 dark:text-gray-400">
|
||||
<InformationOutline />
|
||||
</div>
|
||||
|
||||
{#if showMoreInformation}
|
||||
<div class="absolute right-6 top-0">
|
||||
<div
|
||||
class="flex place-items-center place-content-center whitespace-nowrap rounded-3xl shadow-lg py-3 px-6 bg-immich-bg text-immich-fg dark:bg-gray-600 dark:text-immich-dark-fg text-xs border dark:border-immich-dark-gray"
|
||||
class:hidden={!showMoreInformation}
|
||||
transition:fade={{ duration: 200 }}
|
||||
>
|
||||
<slot name="moreInformation" />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if showMoreInformation}
|
||||
<div class="absolute right-6 top-0">
|
||||
<div
|
||||
class="flex place-items-center place-content-center whitespace-nowrap rounded-3xl shadow-lg py-3 px-6 bg-immich-bg text-immich-fg dark:bg-gray-600 dark:text-immich-dark-fg text-xs border dark:border-immich-dark-gray"
|
||||
class:hidden={!showMoreInformation}
|
||||
transition:fade={{ duration: 200 }}
|
||||
>
|
||||
<slot name="moreInformation" />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
</script>
|
||||
|
||||
<section
|
||||
id="sidebar"
|
||||
class="group flex flex-col gap-1 pt-8 bg-immich-bg dark:bg-immich-dark-bg transition-all duration-200 z-10 w-18 md:w-64 hover:sm:pr-6 md:pr-6 hover:sm:w-64 hover:sm:shadow-2xl hover:md:shadow-none hover:md:border-none hover:sm:border-r hover:sm:dark:border-r-immich-dark-gray relative overflow-y-auto immich-scrollbar"
|
||||
id="sidebar"
|
||||
class="group flex flex-col gap-1 pt-8 bg-immich-bg dark:bg-immich-dark-bg transition-all duration-200 z-10 w-18 md:w-64 hover:sm:pr-6 md:pr-6 hover:sm:w-64 hover:sm:shadow-2xl hover:md:shadow-none hover:md:border-none hover:sm:border-r hover:sm:dark:border-r-immich-dark-gray relative overflow-y-auto immich-scrollbar"
|
||||
>
|
||||
<slot />
|
||||
<slot />
|
||||
</section>
|
||||
|
||||
@@ -1,197 +1,174 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { api } from '@api';
|
||||
import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
|
||||
import AccountMultiple from 'svelte-material-icons/AccountMultiple.svelte';
|
||||
import ImageAlbum from 'svelte-material-icons/ImageAlbum.svelte';
|
||||
import ImageMultipleOutline from 'svelte-material-icons/ImageMultipleOutline.svelte';
|
||||
import ImageMultiple from 'svelte-material-icons/ImageMultiple.svelte';
|
||||
import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte';
|
||||
import Magnify from 'svelte-material-icons/Magnify.svelte';
|
||||
import Map from 'svelte-material-icons/Map.svelte';
|
||||
import HeartMultipleOutline from 'svelte-material-icons/HeartMultipleOutline.svelte';
|
||||
import HeartMultiple from 'svelte-material-icons/HeartMultiple.svelte';
|
||||
import { AppRoute } from '../../../constants';
|
||||
import LoadingSpinner from '../loading-spinner.svelte';
|
||||
import StatusBox from '../status-box.svelte';
|
||||
import SideBarButton from './side-bar-button.svelte';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import SideBarSection from './side-bar-section.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { api } from '@api';
|
||||
import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
|
||||
import AccountMultiple from 'svelte-material-icons/AccountMultiple.svelte';
|
||||
import ImageAlbum from 'svelte-material-icons/ImageAlbum.svelte';
|
||||
import ImageMultipleOutline from 'svelte-material-icons/ImageMultipleOutline.svelte';
|
||||
import ImageMultiple from 'svelte-material-icons/ImageMultiple.svelte';
|
||||
import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte';
|
||||
import Magnify from 'svelte-material-icons/Magnify.svelte';
|
||||
import Map from 'svelte-material-icons/Map.svelte';
|
||||
import HeartMultipleOutline from 'svelte-material-icons/HeartMultipleOutline.svelte';
|
||||
import HeartMultiple from 'svelte-material-icons/HeartMultiple.svelte';
|
||||
import { AppRoute } from '../../../constants';
|
||||
import LoadingSpinner from '../loading-spinner.svelte';
|
||||
import StatusBox from '../status-box.svelte';
|
||||
import SideBarButton from './side-bar-button.svelte';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import SideBarSection from './side-bar-section.svelte';
|
||||
|
||||
const getAssetCount = async () => {
|
||||
const { data: allAssetCount } = await api.assetApi.getAssetCountByUserId();
|
||||
const { data: archivedCount } = await api.assetApi.getArchivedAssetCountByUserId();
|
||||
const getAssetCount = async () => {
|
||||
const { data: allAssetCount } = await api.assetApi.getAssetCountByUserId();
|
||||
const { data: archivedCount } = await api.assetApi.getArchivedAssetCountByUserId();
|
||||
|
||||
return {
|
||||
videos: allAssetCount.videos - archivedCount.videos,
|
||||
photos: allAssetCount.photos - archivedCount.photos
|
||||
};
|
||||
};
|
||||
return {
|
||||
videos: allAssetCount.videos - archivedCount.videos,
|
||||
photos: allAssetCount.photos - archivedCount.photos,
|
||||
};
|
||||
};
|
||||
|
||||
const getFavoriteCount = async () => {
|
||||
try {
|
||||
const { data: assets } = await api.assetApi.getAllAssets({
|
||||
isFavorite: true,
|
||||
withoutThumbs: true
|
||||
});
|
||||
const getFavoriteCount = async () => {
|
||||
try {
|
||||
const { data: assets } = await api.assetApi.getAllAssets({
|
||||
isFavorite: true,
|
||||
withoutThumbs: true,
|
||||
});
|
||||
|
||||
return {
|
||||
favorites: assets.length
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
favorites: 0
|
||||
};
|
||||
}
|
||||
};
|
||||
return {
|
||||
favorites: assets.length,
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
favorites: 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const getAlbumCount = async () => {
|
||||
try {
|
||||
const { data: albumCount } = await api.albumApi.getAlbumCount();
|
||||
return albumCount;
|
||||
} catch {
|
||||
return { owned: 0, shared: 0, notShared: 0 };
|
||||
}
|
||||
};
|
||||
const getAlbumCount = async () => {
|
||||
try {
|
||||
const { data: albumCount } = await api.albumApi.getAlbumCount();
|
||||
return albumCount;
|
||||
} catch {
|
||||
return { owned: 0, shared: 0, notShared: 0 };
|
||||
}
|
||||
};
|
||||
|
||||
const getArchivedAssetsCount = async () => {
|
||||
try {
|
||||
const { data: assetCount } = await api.assetApi.getArchivedAssetCountByUserId();
|
||||
const getArchivedAssetsCount = async () => {
|
||||
try {
|
||||
const { data: assetCount } = await api.assetApi.getArchivedAssetCountByUserId();
|
||||
|
||||
return {
|
||||
videos: assetCount.videos,
|
||||
photos: assetCount.photos
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
videos: 0,
|
||||
photos: 0
|
||||
};
|
||||
}
|
||||
};
|
||||
return {
|
||||
videos: assetCount.videos,
|
||||
photos: assetCount.photos,
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
videos: 0,
|
||||
photos: 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const isFavoritesSelected = $page.route.id === '/(user)/favorites';
|
||||
const isPhotosSelected = $page.route.id === '/(user)/photos';
|
||||
const isSharingSelected = $page.route.id === '/(user)/sharing';
|
||||
const isFavoritesSelected = $page.route.id === '/(user)/favorites';
|
||||
const isPhotosSelected = $page.route.id === '/(user)/photos';
|
||||
const isSharingSelected = $page.route.id === '/(user)/sharing';
|
||||
</script>
|
||||
|
||||
<SideBarSection>
|
||||
<a
|
||||
data-sveltekit-preload-data="hover"
|
||||
data-sveltekit-noscroll
|
||||
href={AppRoute.PHOTOS}
|
||||
draggable="false"
|
||||
>
|
||||
<SideBarButton
|
||||
title="Photos"
|
||||
logo={isPhotosSelected ? ImageMultiple : ImageMultipleOutline}
|
||||
isSelected={isPhotosSelected}
|
||||
>
|
||||
<svelte:fragment slot="moreInformation">
|
||||
{#await getAssetCount()}
|
||||
<LoadingSpinner />
|
||||
{:then data}
|
||||
<div>
|
||||
<p>{data.videos.toLocaleString($locale)} Videos</p>
|
||||
<p>{data.photos.toLocaleString($locale)} Photos</p>
|
||||
</div>
|
||||
{/await}
|
||||
</svelte:fragment>
|
||||
</SideBarButton>
|
||||
</a>
|
||||
<a
|
||||
data-sveltekit-preload-data="hover"
|
||||
data-sveltekit-noscroll
|
||||
href={AppRoute.EXPLORE}
|
||||
draggable="false"
|
||||
>
|
||||
<SideBarButton
|
||||
title="Explore"
|
||||
logo={Magnify}
|
||||
isSelected={$page.route.id === '/(user)/explore'}
|
||||
/>
|
||||
</a>
|
||||
<a data-sveltekit-preload-data="hover" href={AppRoute.MAP} draggable="false">
|
||||
<SideBarButton title="Map" logo={Map} isSelected={$page.route.id === '/(user)/map'} />
|
||||
</a>
|
||||
<a data-sveltekit-preload-data="hover" href={AppRoute.SHARING} draggable="false">
|
||||
<SideBarButton
|
||||
title="Sharing"
|
||||
logo={isSharingSelected ? AccountMultiple : AccountMultipleOutline}
|
||||
isSelected={isSharingSelected}
|
||||
>
|
||||
<svelte:fragment slot="moreInformation">
|
||||
{#await getAlbumCount()}
|
||||
<LoadingSpinner />
|
||||
{:then data}
|
||||
<div>
|
||||
<p>{data.shared.toLocaleString($locale)} Albums</p>
|
||||
</div>
|
||||
{/await}
|
||||
</svelte:fragment>
|
||||
</SideBarButton>
|
||||
</a>
|
||||
<a data-sveltekit-preload-data="hover" data-sveltekit-noscroll href={AppRoute.PHOTOS} draggable="false">
|
||||
<SideBarButton
|
||||
title="Photos"
|
||||
logo={isPhotosSelected ? ImageMultiple : ImageMultipleOutline}
|
||||
isSelected={isPhotosSelected}
|
||||
>
|
||||
<svelte:fragment slot="moreInformation">
|
||||
{#await getAssetCount()}
|
||||
<LoadingSpinner />
|
||||
{:then data}
|
||||
<div>
|
||||
<p>{data.videos.toLocaleString($locale)} Videos</p>
|
||||
<p>{data.photos.toLocaleString($locale)} Photos</p>
|
||||
</div>
|
||||
{/await}
|
||||
</svelte:fragment>
|
||||
</SideBarButton>
|
||||
</a>
|
||||
<a data-sveltekit-preload-data="hover" data-sveltekit-noscroll href={AppRoute.EXPLORE} draggable="false">
|
||||
<SideBarButton title="Explore" logo={Magnify} isSelected={$page.route.id === '/(user)/explore'} />
|
||||
</a>
|
||||
<a data-sveltekit-preload-data="hover" href={AppRoute.MAP} draggable="false">
|
||||
<SideBarButton title="Map" logo={Map} isSelected={$page.route.id === '/(user)/map'} />
|
||||
</a>
|
||||
<a data-sveltekit-preload-data="hover" href={AppRoute.SHARING} draggable="false">
|
||||
<SideBarButton
|
||||
title="Sharing"
|
||||
logo={isSharingSelected ? AccountMultiple : AccountMultipleOutline}
|
||||
isSelected={isSharingSelected}
|
||||
>
|
||||
<svelte:fragment slot="moreInformation">
|
||||
{#await getAlbumCount()}
|
||||
<LoadingSpinner />
|
||||
{:then data}
|
||||
<div>
|
||||
<p>{data.shared.toLocaleString($locale)} Albums</p>
|
||||
</div>
|
||||
{/await}
|
||||
</svelte:fragment>
|
||||
</SideBarButton>
|
||||
</a>
|
||||
|
||||
<div class="text-xs dark:text-immich-dark-fg transition-all duration-200">
|
||||
<p class="p-6 hidden md:block group-hover:sm:block">LIBRARY</p>
|
||||
<hr class="mt-8 mb-[31px] mx-4 block md:hidden group-hover:sm:hidden" />
|
||||
</div>
|
||||
<a data-sveltekit-preload-data="hover" href={AppRoute.FAVORITES} draggable="false">
|
||||
<SideBarButton
|
||||
title="Favorites"
|
||||
logo={isFavoritesSelected ? HeartMultiple : HeartMultipleOutline}
|
||||
isSelected={isFavoritesSelected}
|
||||
>
|
||||
<svelte:fragment slot="moreInformation">
|
||||
{#await getFavoriteCount()}
|
||||
<LoadingSpinner />
|
||||
{:then data}
|
||||
<div>
|
||||
<p>{data.favorites} Favorites</p>
|
||||
</div>
|
||||
{/await}
|
||||
</svelte:fragment>
|
||||
</SideBarButton>
|
||||
</a>
|
||||
<a data-sveltekit-preload-data="hover" href={AppRoute.ALBUMS} draggable="false">
|
||||
<SideBarButton
|
||||
title="Albums"
|
||||
logo={ImageAlbum}
|
||||
flippedLogo={true}
|
||||
isSelected={$page.route.id === '/(user)/albums'}
|
||||
>
|
||||
<svelte:fragment slot="moreInformation">
|
||||
{#await getAlbumCount()}
|
||||
<LoadingSpinner />
|
||||
{:then data}
|
||||
<div>
|
||||
<p>{data.owned.toLocaleString($locale)} Albums</p>
|
||||
</div>
|
||||
{/await}
|
||||
</svelte:fragment>
|
||||
</SideBarButton>
|
||||
</a>
|
||||
<a data-sveltekit-preload-data="hover" href={AppRoute.ARCHIVE} draggable="false">
|
||||
<SideBarButton
|
||||
title="Archive"
|
||||
logo={ArchiveArrowDownOutline}
|
||||
isSelected={$page.route.id === '/(user)/archive'}
|
||||
>
|
||||
<svelte:fragment slot="moreInformation">
|
||||
{#await getArchivedAssetsCount()}
|
||||
<LoadingSpinner />
|
||||
{:then data}
|
||||
<div>
|
||||
<p>{data.videos.toLocaleString($locale)} Videos</p>
|
||||
<p>{data.photos.toLocaleString($locale)} Photos</p>
|
||||
</div>
|
||||
{/await}
|
||||
</svelte:fragment>
|
||||
</SideBarButton>
|
||||
</a>
|
||||
<div class="text-xs dark:text-immich-dark-fg transition-all duration-200">
|
||||
<p class="p-6 hidden md:block group-hover:sm:block">LIBRARY</p>
|
||||
<hr class="mt-8 mb-[31px] mx-4 block md:hidden group-hover:sm:hidden" />
|
||||
</div>
|
||||
<a data-sveltekit-preload-data="hover" href={AppRoute.FAVORITES} draggable="false">
|
||||
<SideBarButton
|
||||
title="Favorites"
|
||||
logo={isFavoritesSelected ? HeartMultiple : HeartMultipleOutline}
|
||||
isSelected={isFavoritesSelected}
|
||||
>
|
||||
<svelte:fragment slot="moreInformation">
|
||||
{#await getFavoriteCount()}
|
||||
<LoadingSpinner />
|
||||
{:then data}
|
||||
<div>
|
||||
<p>{data.favorites} Favorites</p>
|
||||
</div>
|
||||
{/await}
|
||||
</svelte:fragment>
|
||||
</SideBarButton>
|
||||
</a>
|
||||
<a data-sveltekit-preload-data="hover" href={AppRoute.ALBUMS} draggable="false">
|
||||
<SideBarButton title="Albums" logo={ImageAlbum} flippedLogo={true} isSelected={$page.route.id === '/(user)/albums'}>
|
||||
<svelte:fragment slot="moreInformation">
|
||||
{#await getAlbumCount()}
|
||||
<LoadingSpinner />
|
||||
{:then data}
|
||||
<div>
|
||||
<p>{data.owned.toLocaleString($locale)} Albums</p>
|
||||
</div>
|
||||
{/await}
|
||||
</svelte:fragment>
|
||||
</SideBarButton>
|
||||
</a>
|
||||
<a data-sveltekit-preload-data="hover" href={AppRoute.ARCHIVE} draggable="false">
|
||||
<SideBarButton title="Archive" logo={ArchiveArrowDownOutline} isSelected={$page.route.id === '/(user)/archive'}>
|
||||
<svelte:fragment slot="moreInformation">
|
||||
{#await getArchivedAssetsCount()}
|
||||
<LoadingSpinner />
|
||||
{:then data}
|
||||
<div>
|
||||
<p>{data.videos.toLocaleString($locale)} Videos</p>
|
||||
<p>{data.photos.toLocaleString($locale)} Photos</p>
|
||||
</div>
|
||||
{/await}
|
||||
</svelte:fragment>
|
||||
</SideBarButton>
|
||||
</a>
|
||||
|
||||
<!-- Status Box -->
|
||||
<div class="mb-6 mt-auto">
|
||||
<StatusBox />
|
||||
</div>
|
||||
<!-- Status Box -->
|
||||
<div class="mb-6 mt-auto">
|
||||
<StatusBox />
|
||||
</div>
|
||||
</SideBarSection>
|
||||
|
||||
Reference in New Issue
Block a user