0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-02-04 01:09:14 -05:00

feat: use <a> tag for albums in list view (#5645)

* fix: multiple improvements

* pr feedback

* optimize
This commit is contained in:
martin 2023-12-12 03:35:57 +01:00 committed by GitHub
parent fb4b4e5895
commit fba9e784fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 86 additions and 59 deletions

View file

@ -19,7 +19,7 @@
import PhotoViewer from './photo-viewer.svelte'; import PhotoViewer from './photo-viewer.svelte';
import VideoViewer from './video-viewer.svelte'; import VideoViewer from './video-viewer.svelte';
import PanoramaViewer from './panorama-viewer.svelte'; import PanoramaViewer from './panorama-viewer.svelte';
import { AssetAction, ProjectionType } from '$lib/constants'; import { AppRoute, AssetAction, ProjectionType } from '$lib/constants';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import ProfileImageCropper from '../shared-components/profile-image-cropper.svelte'; import ProfileImageCropper from '../shared-components/profile-image-cropper.svelte';
import { isShowDetail } from '$lib/stores/preferences.store'; import { isShowDetail } from '$lib/stores/preferences.store';
@ -430,7 +430,7 @@
const { albumName }: { albumName: string } = event.detail; const { albumName }: { albumName: string } = event.detail;
api.albumApi.createAlbum({ createAlbumDto: { albumName, assetIds: [asset.id] } }).then((response) => { api.albumApi.createAlbum({ createAlbumDto: { albumName, assetIds: [asset.id] } }).then((response) => {
const album = response.data; const album = response.data;
goto('/albums/' + album.id); goto(`${AppRoute.ALBUMS}/${album.id}`);
}); });
}; };

View file

@ -238,7 +238,9 @@
on:mouseleave={() => ($boundingBoxesArray = [])} on:mouseleave={() => ($boundingBoxesArray = [])}
> >
<a <a
href="/people/{person.id}?previousRoute={albumId ? `${AppRoute.ALBUMS}/${albumId}` : AppRoute.PHOTOS}" href="{AppRoute.PEOPLE}/{person.id}?previousRoute={albumId
? `${AppRoute.ALBUMS}/${albumId}`
: AppRoute.PHOTOS}"
on:click={() => dispatch('close-viewer')} on:click={() => dispatch('close-viewer')}
> >
<div class="relative"> <div class="relative">

View file

@ -45,7 +45,7 @@
on:mouseleave={() => (showVerticalDots = false)} on:mouseleave={() => (showVerticalDots = false)}
role="group" role="group"
> >
<a href="/people/{person.id}?previousRoute={AppRoute.PEOPLE}" draggable="false"> <a href="{AppRoute.PEOPLE}/{person.id}?previousRoute={AppRoute.PEOPLE}" draggable="false">
<div class="h-48 w-48 rounded-xl brightness-95 filter"> <div class="h-48 w-48 rounded-xl brightness-95 filter">
<ImageThumbnail <ImageThumbnail
shadow shadow

View file

@ -7,6 +7,7 @@
import { handleError } from '../../utils/handle-error'; import { handleError } from '../../utils/handle-error';
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import { mdiAccountEditOutline } from '@mdi/js'; import { mdiAccountEditOutline } from '@mdi/js';
import { AppRoute } from '$lib/constants';
export let user: UserResponseDto; export let user: UserResponseDto;
export let canResetPassword = true; export let canResetPassword = true;
@ -99,7 +100,7 @@
<p> <p>
Note: To apply the Storage Label to previously uploaded assets, run the Note: To apply the Storage Label to previously uploaded assets, run the
<a href="/admin/jobs-status" class="text-immich-primary dark:text-immich-dark-primary"> <a href={AppRoute.ADMIN_JOBS} class="text-immich-primary dark:text-immich-dark-primary">
Storage Migration Job</a Storage Migration Job</a
> >
</p> </p>

View file

@ -10,6 +10,7 @@
import { AlbumResponseDto, api } from '@api'; import { AlbumResponseDto, api } from '@api';
import { getMenuContext } from '../asset-select-context-menu.svelte'; import { getMenuContext } from '../asset-select-context-menu.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { AppRoute } from '$lib/constants';
export let shared = false; export let shared = false;
let showAlbumPicker = false; let showAlbumPicker = false;
@ -37,7 +38,7 @@
clearSelect(); clearSelect();
goto('/albums/' + id); goto(`${AppRoute.ALBUMS}/${id}`);
}); });
}; };

View file

@ -14,6 +14,7 @@
import { notificationController, NotificationType } from '../shared-components/notification/notification'; import { notificationController, NotificationType } from '../shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { mdiArrowLeft, mdiFileImagePlusOutline, mdiFolderDownloadOutline, mdiSelectAll } from '@mdi/js'; import { mdiArrowLeft, mdiFileImagePlusOutline, mdiFolderDownloadOutline, mdiSelectAll } from '@mdi/js';
import { AppRoute } from '$lib/constants';
export let sharedLink: SharedLinkResponseDto; export let sharedLink: SharedLinkResponseDto;
export let isOwned: boolean; export let isOwned: boolean;
@ -78,7 +79,7 @@
{/if} {/if}
</AssetSelectControlBar> </AssetSelectControlBar>
{:else} {:else}
<ControlAppBar on:close-button-click={() => goto('/photos')} backIcon={mdiArrowLeft} showBackButton={false}> <ControlAppBar on:close-button-click={() => goto(AppRoute.PHOTOS)} backIcon={mdiArrowLeft} showBackButton={false}>
<svelte:fragment slot="leading"> <svelte:fragment slot="leading">
<a <a
data-sveltekit-preload-data="hover" data-sveltekit-preload-data="hover"

View file

@ -30,6 +30,7 @@ export enum AppRoute {
USER_SETTINGS = '/user-settings', USER_SETTINGS = '/user-settings',
MEMORY = '/memory', MEMORY = '/memory',
TRASH = '/trash', TRASH = '/trash',
PARTNERS = '/partners',
AUTH_LOGIN = '/auth/login', AUTH_LOGIN = '/auth/login',
AUTH_LOGOUT = '/auth/logout', AUTH_LOGOUT = '/auth/logout',

View file

@ -1,4 +1,12 @@
import { writable } from 'svelte/store'; import { get, writable } from 'svelte/store';
import type { UserResponseDto } from '@api'; import type { UserResponseDto } from '@api';
export const user = writable<UserResponseDto | null>(null); export const user = writable<UserResponseDto | null>(null);
export const setUser = (value: UserResponseDto | null) => {
user.set(value);
};
export const getSavedUser = () => {
return get(user);
};

View file

@ -1,6 +1,7 @@
import { api } from '@api'; import { api } from '@api';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import { AppRoute } from '../constants'; import { AppRoute } from '../constants';
import { getSavedUser, setUser } from '$lib/stores/user.store';
export interface AuthOptions { export interface AuthOptions {
admin?: true; admin?: true;
@ -19,7 +20,9 @@ export const getAuthUser = async () => {
export const authenticate = async (options?: AuthOptions) => { export const authenticate = async (options?: AuthOptions) => {
options = options || {}; options = options || {};
const user = await getAuthUser(); const savedUser = getSavedUser();
const user = savedUser || (await getAuthUser());
if (!user) { if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN); throw redirect(302, AppRoute.AUTH_LOGIN);
} }
@ -28,6 +31,10 @@ export const authenticate = async (options?: AuthOptions) => {
throw redirect(302, AppRoute.PHOTOS); throw redirect(302, AppRoute.PHOTOS);
} }
if (!savedUser) {
setUser(user);
}
return user; return user;
}; };

View file

@ -22,7 +22,7 @@
import { flip } from 'svelte/animate'; import { flip } from 'svelte/animate';
import Dropdown from '$lib/components/elements/dropdown.svelte'; import Dropdown from '$lib/components/elements/dropdown.svelte';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import { dateFormats } from '$lib/constants'; import { AppRoute, dateFormats } from '$lib/constants';
import { locale, AlbumViewMode } from '$lib/stores/preferences.store'; import { locale, AlbumViewMode } from '$lib/stores/preferences.store';
import { import {
notificationController, notificationController,
@ -46,6 +46,7 @@
} from '@mdi/js'; } from '@mdi/js';
export let data: PageData; export let data: PageData;
let shouldShowEditUserForm = false; let shouldShowEditUserForm = false;
let selectedAlbum: AlbumResponseDto; let selectedAlbum: AlbumResponseDto;
@ -192,7 +193,7 @@
const handleCreateAlbum = async () => { const handleCreateAlbum = async () => {
const newAlbum = await createAlbum(); const newAlbum = await createAlbum();
if (newAlbum) { if (newAlbum) {
goto('/albums/' + newAlbum.id); goto(`${AppRoute.ALBUMS}/${newAlbum.id}`);
} }
}; };
@ -289,7 +290,7 @@
{#if $albumViewSettings.view === AlbumViewMode.Cover} {#if $albumViewSettings.view === AlbumViewMode.Cover}
<div class="grid grid-cols-[repeat(auto-fill,minmax(14rem,1fr))]"> <div class="grid grid-cols-[repeat(auto-fill,minmax(14rem,1fr))]">
{#each $albums as album (album.id)} {#each $albums as album (album.id)}
<a data-sveltekit-preload-data="hover" href={`albums/${album.id}`} animate:flip={{ duration: 200 }}> <a data-sveltekit-preload-data="hover" href="{AppRoute.ALBUMS}/{album.id}" animate:flip={{ duration: 200 }}>
<AlbumCard <AlbumCard
{album} {album}
on:showalbumcontextmenu={(e) => showAlbumContextMenu(e.detail, album)} on:showalbumcontextmenu={(e) => showAlbumContextMenu(e.detail, album)}
@ -316,47 +317,49 @@
{#each $albums as album (album.id)} {#each $albums as album (album.id)}
<tr <tr
class="flex h-[50px] w-full place-items-center border-[3px] border-transparent p-2 text-center odd:bg-immich-gray even:bg-immich-bg hover:cursor-pointer hover:border-immich-primary/75 odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50 dark:hover:border-immich-dark-primary/75 md:p-5" class="flex h-[50px] w-full place-items-center border-[3px] border-transparent p-2 text-center odd:bg-immich-gray even:bg-immich-bg hover:cursor-pointer hover:border-immich-primary/75 odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50 dark:hover:border-immich-dark-primary/75 md:p-5"
on:click={() => goto(`albums/${album.id}`)} on:click={() => goto(`${AppRoute.ALBUMS}/${album.id}`)}
on:keydown={(event) => event.key === 'Enter' && goto(`albums/${album.id}`)} on:keydown={(event) => event.key === 'Enter' && goto(`${AppRoute.ALBUMS}/${album.id}`)}
tabindex="0" tabindex="0"
> >
<td class="text-md text-ellipsis text-left w-8/12 sm:w-4/12 md:w-4/12 xl:w-[30%] 2xl:w-[40%]" <a data-sveltekit-preload-data="hover" class="flex w-full" href="{AppRoute.ALBUMS}/{album.id}">
>{album.albumName}</td <td class="text-md text-ellipsis text-left w-8/12 sm:w-4/12 md:w-4/12 xl:w-[30%] 2xl:w-[40%]"
> >{album.albumName}</td
<td class="text-md text-ellipsis text-center sm:w-2/12 md:w-2/12 xl:w-[15%] 2xl:w-[12%]"> >
{album.assetCount} <td class="text-md text-ellipsis text-center sm:w-2/12 md:w-2/12 xl:w-[15%] 2xl:w-[12%]">
{album.assetCount > 1 ? `items` : `item`} {album.assetCount}
</td> {album.assetCount > 1 ? `items` : `item`}
<td class="text-md hidden text-ellipsis text-center sm:block w-3/12 xl:w-[15%] 2xl:w-[12%]" </td>
>{dateLocaleString(album.updatedAt)} <td class="text-md hidden text-ellipsis text-center sm:block w-3/12 xl:w-[15%] 2xl:w-[12%]"
</td> >{dateLocaleString(album.updatedAt)}
<td class="text-md hidden text-ellipsis text-center sm:block w-3/12 xl:w-[15%] 2xl:w-[12%]" </td>
>{dateLocaleString(album.createdAt)}</td <td class="text-md hidden text-ellipsis text-center sm:block w-3/12 xl:w-[15%] 2xl:w-[12%]"
> >{dateLocaleString(album.createdAt)}</td
<td class="text-md text-ellipsis text-center hidden xl:block xl:w-[15%] 2xl:w-[12%]"> >
{#if album.endDate} <td class="text-md text-ellipsis text-center hidden xl:block xl:w-[15%] 2xl:w-[12%]">
{dateLocaleString(album.endDate)} {#if album.endDate}
{:else} {dateLocaleString(album.endDate)}
&#10060; {:else}
{/if}</td &#10060;
> {/if}</td
<td class="text-md text-ellipsis text-center hidden xl:block xl:w-[15%] 2xl:w-[12%]" >
>{#if album.startDate} <td class="text-md text-ellipsis text-center hidden xl:block xl:w-[15%] 2xl:w-[12%]"
{dateLocaleString(album.startDate)} >{#if album.startDate}
{:else} {dateLocaleString(album.startDate)}
&#10060; {:else}
{/if}</td &#10060;
> {/if}</td
>
</a>
<td class="text-md hidden text-ellipsis text-center 2xl:block xl:w-[15%] 2xl:w-[12%]"> <td class="text-md hidden text-ellipsis text-center 2xl:block xl:w-[15%] 2xl:w-[12%]">
<button <button
on:click|stopPropagation={() => handleEdit(album)} on:click|stopPropagation={() => handleEdit(album)}
class="rounded-full bg-immich-primary p-3 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700" class="rounded-full z-1 bg-immich-primary p-3 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700"
> >
<Icon path={mdiPencilOutline} size="16" /> <Icon path={mdiPencilOutline} size="16" />
</button> </button>
<button <button
on:click|stopPropagation={() => chooseAlbumToDelete(album)} on:click|stopPropagation={() => chooseAlbumToDelete(album)}
class="rounded-full bg-immich-primary p-3 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700" class="rounded-full z-1 bg-immich-primary p-3 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700"
> >
<Icon path={mdiTrashCanOutline} size="16" /> <Icon path={mdiTrashCanOutline} size="16" />
</button> </button>

View file

@ -68,8 +68,6 @@
let album = data.album; let album = data.album;
$user = data.user;
$: album = data.album; $: album = data.album;
$: { $: {

View file

@ -50,7 +50,7 @@
<div class="flex flex-row {MAX_ITEMS < 5 ? 'justify-center' : ''} flex-wrap gap-4" bind:offsetWidth={innerWidth}> <div class="flex flex-row {MAX_ITEMS < 5 ? 'justify-center' : ''} flex-wrap gap-4" bind:offsetWidth={innerWidth}>
{#if MAX_ITEMS} {#if MAX_ITEMS}
{#each people as person (person.id)} {#each people as person (person.id)}
<a href="/people/{person.id}" class="w-20 md:w-24 text-center"> <a href="{AppRoute.PEOPLE}/{person.id}" class="w-20 md:w-24 text-center">
<ImageThumbnail <ImageThumbnail
circle circle
shadow shadow
@ -73,7 +73,7 @@
</div> </div>
<div class="flex flex-row flex-wrap gap-4"> <div class="flex flex-row flex-wrap gap-4">
{#each places as item (item.data.id)} {#each places as item (item.data.id)}
<a class="relative" href="/search?{Field.CITY}={item.value}" draggable="false"> <a class="relative" href="{AppRoute.SEARCH}?{Field.CITY}={item.value}" draggable="false">
<div <div
class="flex w-[calc((100vw-(72px+5rem))/2)] max-w-[156px] justify-center overflow-hidden rounded-xl brightness-75 filter" class="flex w-[calc((100vw-(72px+5rem))/2)] max-w-[156px] justify-center overflow-hidden rounded-xl brightness-75 filter"
> >
@ -97,7 +97,7 @@
</div> </div>
<div class="flex flex-row flex-wrap gap-4"> <div class="flex flex-row flex-wrap gap-4">
{#each things as item} {#each things as item}
<a class="relative" href="/search?{Field.OBJECTS}={item.value}" draggable="false"> <a class="relative" href="{AppRoute.SEARCH}?{Field.OBJECTS}={item.value}" draggable="false">
<div <div
class="flex w-[calc((100vw-(72px+5rem))/2)] max-w-[156px] justify-center overflow-hidden rounded-xl brightness-75 filter" class="flex w-[calc((100vw-(72px+5rem))/2)] max-w-[156px] justify-center overflow-hidden rounded-xl brightness-75 filter"
> >

View file

@ -24,7 +24,6 @@
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { mdiDotsVertical, mdiPlus } from '@mdi/js'; import { mdiDotsVertical, mdiPlus } from '@mdi/js';
import UpdatePanel from '$lib/components/shared-components/update-panel.svelte'; import UpdatePanel from '$lib/components/shared-components/update-panel.svelte';
import { user } from '$lib/stores/user.store';
export let data: PageData; export let data: PageData;
@ -34,8 +33,6 @@
const assetInteractionStore = createAssetInteractionStore(); const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore; const { isMultiSelectState, selectedAssets } = assetInteractionStore;
$user = data.user;
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite); $: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
const handleEscape = () => { const handleEscape = () => {

View file

@ -27,7 +27,7 @@
}, },
}); });
goto('/albums/' + newAlbum.id); goto(`${AppRoute.ALBUMS}/${newAlbum.id}`);
} catch (e) { } catch (e) {
notificationController.show({ notificationController.show({
message: 'Error creating album, check console for more details', message: 'Error creating album, check console for more details',
@ -66,7 +66,7 @@
<div class="flex flex-row flex-wrap gap-4"> <div class="flex flex-row flex-wrap gap-4">
{#each data.partners as partner (partner.id)} {#each data.partners as partner (partner.id)}
<a <a
href="/partners/{partner.id}" href="{AppRoute.PARTNERS}/{partner.id}"
class="flex gap-4 rounded-lg px-5 py-4 transition-all hover:bg-gray-200 dark:hover:bg-gray-700" class="flex gap-4 rounded-lg px-5 py-4 transition-all hover:bg-gray-200 dark:hover:bg-gray-700"
> >
<UserAvatar user={partner} size="lg" /> <UserAvatar user={partner} size="lg" />

View file

@ -1,5 +1,6 @@
import { api } from '../api'; import { api } from '@api';
import type { LayoutLoad } from './$types'; import type { LayoutLoad } from './$types';
import { getSavedUser, setUser } from '$lib/stores/user.store';
const getUser = async () => { const getUser = async () => {
try { try {
@ -14,7 +15,12 @@ export const ssr = false;
export const csr = true; export const csr = true;
export const load = (async () => { export const load = (async () => {
const user = await getUser(); const savedUser = getSavedUser();
const user = savedUser || (await getUser());
if (!savedUser) {
setUser(user);
}
return { return {
user, user,

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation';
import Button from '$lib/components/elements/buttons/button.svelte'; import Button from '$lib/components/elements/buttons/button.svelte';
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte'; import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
import { AppRoute } from '$lib/constants';
</script> </script>
<section class="flex h-screen w-screen place-content-center place-items-center"> <section class="flex h-screen w-screen place-content-center place-items-center">
@ -12,8 +12,10 @@
<h1 class="font-immich-title text-4xl font-bold text-immich-primary dark:text-immich-dark-primary"> <h1 class="font-immich-title text-4xl font-bold text-immich-primary dark:text-immich-dark-primary">
Welcome to IMMICH Web Welcome to IMMICH Web
</h1> </h1>
<Button size="lg" rounded="lg" on:click={() => goto('/auth/register')}> <a href={AppRoute.AUTH_REGISTER}>
<span class="px-2 font-bold">Getting Started</span> <Button size="lg" rounded="lg">
</Button> <span class="px-2 font-bold">Getting Started</span>
</Button>
</a>
</div> </div>
</section> </section>