Migrate SvelteKit to the latest version 431 (#526)

This commit is contained in:
Alex 2022-08-24 21:10:48 -07:00 committed by GitHub
parent fb0fa742f5
commit db2ed2d881
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1509 additions and 1216 deletions

View File

@ -6,6 +6,7 @@ services:
build: build:
context: ../server context: ../server
dockerfile: Dockerfile dockerfile: Dockerfile
target: builder
command: npm run start:dev immich command: npm run start:dev immich
volumes: volumes:
- ../server:/usr/src/app - ../server:/usr/src/app
@ -24,6 +25,7 @@ services:
build: build:
context: ../machine-learning context: ../machine-learning
dockerfile: Dockerfile dockerfile: Dockerfile
target: builder
command: npm run start:dev command: npm run start:dev
volumes: volumes:
- ../machine-learning:/usr/src/app - ../machine-learning:/usr/src/app
@ -41,6 +43,7 @@ services:
build: build:
context: ../server context: ../server
dockerfile: Dockerfile dockerfile: Dockerfile
target: builder
command: npm run start:dev microservices command: npm run start:dev microservices
volumes: volumes:
- ../server:/usr/src/app - ../server:/usr/src/app

View File

@ -5,7 +5,7 @@ WORKDIR /usr/src/app
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
RUN apk add --update-cache build-base python3 libheif vips-dev RUN apk add --update-cache build-base python3 libheif vips-dev ffmpeg
RUN npm ci RUN npm ci
COPY . . COPY . .

1550
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,6 @@
"build": "vite build", "build": "vite build",
"package": "svelte-kit package", "package": "svelte-kit package",
"preview": "vite preview", "preview": "vite preview",
"prepare": "svelte-kit sync",
"check": "svelte-check --tsconfig ./tsconfig.json", "check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check --plugin-search-dir=. . && eslint .", "lint": "prettier --check --plugin-search-dir=. . && eslint .",

6
web/src/app.d.ts vendored
View File

@ -8,10 +8,4 @@ declare namespace App {
} }
// interface Platform {} // interface Platform {}
interface Session {
user?: import('@api').UserResponseDto;
}
// interface Stuff {}
} }

View File

@ -1,31 +1,5 @@
import type { ExternalFetch, GetSession, Handle } from '@sveltejs/kit'; import type { Handle } from '@sveltejs/kit';
import * as cookie from 'cookie';
import { serverApi } from '@api';
export const handle: Handle = async ({ event, resolve }) => { export const handle: Handle = async ({ event, resolve }) => {
const cookies = cookie.parse(event.request.headers.get('cookie') || '');
if (!cookies['immich_is_authenticated']) {
return await resolve(event); return await resolve(event);
}
const accessToken = cookies['immich_access_token'];
try {
serverApi.setAccessToken(accessToken);
const { data } = await serverApi.userApi.getMyUserInfo();
event.locals.user = data;
return await resolve(event);
} catch (error) {
event.locals.user = undefined;
return await resolve(event);
}
};
export const getSession: GetSession = async ({ locals }) => {
if (!locals.user) return {};
return {
user: locals.user
};
}; };

View File

@ -2,7 +2,7 @@
import { AlbumResponseDto, api, ThumbnailFormat } from '@api'; import { AlbumResponseDto, api, ThumbnailFormat } from '@api';
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
import DotsVertical from 'svelte-material-icons/DotsVertical.svelte'; import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
import { fade } from 'svelte/transition'; import { fly } from 'svelte/transition';
import CircleIconButton from '../shared-components/circle-icon-button.svelte'; import CircleIconButton from '../shared-components/circle-icon-button.svelte';
export let album: AlbumResponseDto; export let album: AlbumResponseDto;

View File

@ -6,7 +6,6 @@
import WindowMinimize from 'svelte-material-icons/WindowMinimize.svelte'; import WindowMinimize from 'svelte-material-icons/WindowMinimize.svelte';
import type { UploadAsset } from '$lib/models/upload-asset'; import type { UploadAsset } from '$lib/models/upload-asset';
import { getAssetsInfo } from '$lib/stores/assets'; import { getAssetsInfo } from '$lib/stores/assets';
import { session } from '$app/stores';
let showDetail = true; let showDetail = true;
let uploadLength = 0; let uploadLength = 0;

View File

@ -0,0 +1,29 @@
<script>
import { page } from '$app/stores';
</script>
<div class="h-screen w-screen flex place-items-center place-content-center flex-col">
<div class="min-w-[500px] bg-gray-300 rounded-2xl my-4 p-4">
<code class="text-xs text-red-500">Error code {$page.status}</code>
<br />
<code class="text-sm">
{$page.error.message}
</code>
<br />
<div class="mt-5">
<p class="text-sm font-medium">Verbose</p>
<pre class="text-xs">{Object.values($page.error)}</pre>
</div>
<a
href="https://github.com/immich-app/immich/issues/new/choose"
target="_blank"
rel="noopener noreferrer"
>
<button
class="px-5 py-2 rounded-lg text-sm mt-6 bg-immich-primary text-white hover:bg-immich-primary/75"
>Get help</button
>
</a>
</div>
</div>

View File

@ -0,0 +1,23 @@
import { browser } from '$app/env';
import { api, serverApi } from '@api';
import * as cookieParser from 'cookie';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ request }) => {
const cookies = cookieParser.parse(request.headers.get('cookie') || '');
const accessToken = cookies['immich_access_token'];
if (!accessToken) {
return {
user: undefined
};
}
serverApi.setAccessToken(accessToken);
const { data: userInfo } = await serverApi.userApi.getMyUserInfo();
return {
user: userInfo
};
};

View File

@ -1,14 +1,3 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
import { checkAppVersion } from '$lib/utils/check-app-version';
export const load: Load = async ({ url }) => {
return {
props: { url }
};
};
</script>
<script lang="ts"> <script lang="ts">
import '../app.css'; import '../app.css';
@ -17,8 +6,9 @@
import AnnouncementBox from '$lib/components/shared-components/announcement-box.svelte'; import AnnouncementBox from '$lib/components/shared-components/announcement-box.svelte';
import UploadPanel from '$lib/components/shared-components/upload-panel.svelte'; import UploadPanel from '$lib/components/shared-components/upload-panel.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { checkAppVersion } from '$lib/utils/check-app-version';
import { page } from '$app/stores';
export let url: string;
let shouldShowAnnouncement: boolean; let shouldShowAnnouncement: boolean;
let localVersion: string; let localVersion: string;
let remoteVersion: string; let remoteVersion: string;
@ -33,7 +23,7 @@
</script> </script>
<main> <main>
{#key url} {#key $page.url}
<div in:fade={{ duration: 100 }}> <div in:fade={{ duration: 100 }}>
<slot /> <slot />
<DownloadPanel /> <DownloadPanel />

View File

@ -0,0 +1,29 @@
<script lang="ts">
import { goto } from '$app/navigation';
import type { PageData } from './$types';
export let data: PageData;
async function onGettingStartedClicked() {
data.isAdminUserExist ? await goto('/auth/login') : await goto('/auth/register');
}
</script>
<svelte:head>
<title>Welcome 🎉 - Immich</title>
<meta name="description" content="Immich Web Interface" />
</svelte:head>
<section class="h-screen w-screen flex place-items-center place-content-center">
<div class="flex flex-col place-items-center gap-8 text-center max-w-[350px]">
<div class="flex place-items-center place-content-center ">
<img class="text-center" src="immich-logo.svg" height="200" width="200" alt="immich-logo" />
</div>
<h1 class="text-4xl text-immich-primary font-bold font-immich-title">Welcome to IMMICH Web</h1>
<button
class="border px-4 py-2 rounded-md bg-immich-primary hover:bg-immich-primary/75 text-white font-bold w-[200px]"
on:click={onGettingStartedClicked}
>Getting Started
</button>
</div>
</section>

21
web/src/routes/+page.ts Normal file
View File

@ -0,0 +1,21 @@
export const prerender = false;
import { redirect } from '@sveltejs/kit';
import { api } from '@api';
import { browser } from '$app/env';
import type { PageLoad } from './$types';
import { goto } from '$app/navigation';
export const load: PageLoad = async ({ parent }) => {
const { user } = await parent();
if (user) {
throw redirect(302, '/photos');
}
if (browser) {
const { data } = await api.userApi.getUserCount();
return {
isAdminUserExist: data.userCount != 0
};
}
};

View File

@ -0,0 +1,19 @@
import { redirect } from '@sveltejs/kit';
import { serverApi, UserResponseDto } from '@api';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ parent }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, '/auth/login');
} else if (!user.isAdmin) {
throw redirect(302, '/photos');
}
const { data: allUsers } = await serverApi.userApi.getAllUsers(false);
return {
user: user,
allUsers: allUsers
};
};

View File

@ -1,47 +1,3 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
import { api, UserResponseDto } from '@api';
import { browser } from '$app/env';
export const load: Load = async ({ fetch, session }) => {
if (!browser && !session.user) {
return {
status: 302,
redirect: '/auth/login'
};
}
try {
const user: UserResponseDto = await fetch('/data/user/get-my-user-info').then((r) =>
r.json()
);
const allUsers: UserResponseDto[] = await fetch('/data/user/get-all-users?isAll=false').then(
(r) => r.json()
);
if (!user.isAdmin) {
return {
status: 302,
redirect: '/photos'
};
}
return {
status: 200,
props: {
user: user,
allUsers: allUsers
}
};
} catch (e) {
return {
status: 302,
redirect: '/auth/login'
};
}
};
</script>
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
@ -54,11 +10,12 @@
import CreateUserForm from '$lib/components/forms/create-user-form.svelte'; import CreateUserForm from '$lib/components/forms/create-user-form.svelte';
import EditUserForm from '$lib/components/forms/edit-user-form.svelte'; import EditUserForm from '$lib/components/forms/edit-user-form.svelte';
import StatusBox from '$lib/components/shared-components/status-box.svelte'; import StatusBox from '$lib/components/shared-components/status-box.svelte';
import type { PageData } from './$types';
import { api, UserResponseDto } from '@api';
let selectedAction: AdminSideBarSelection = AdminSideBarSelection.USER_MANAGEMENT; let selectedAction: AdminSideBarSelection = AdminSideBarSelection.USER_MANAGEMENT;
export let user: UserResponseDto; export let data: PageData;
export let allUsers: UserResponseDto[];
let editUser: UserResponseDto; let editUser: UserResponseDto;
@ -75,8 +32,8 @@
}); });
const onUserCreated = async () => { const onUserCreated = async () => {
const { data } = await api.userApi.getAllUsers(false); const getAllUsersRes = await api.userApi.getAllUsers(false);
allUsers = data; data.allUsers = getAllUsersRes.data;
shouldShowCreateUserForm = false; shouldShowCreateUserForm = false;
}; };
@ -87,14 +44,14 @@
}; };
const onEditUserSuccess = async () => { const onEditUserSuccess = async () => {
const { data } = await api.userApi.getAllUsers(false); const getAllUsersRes = await api.userApi.getAllUsers(false);
allUsers = data; data.allUsers = getAllUsersRes.data;
shouldShowEditUserForm = false; shouldShowEditUserForm = false;
}; };
const onEditPasswordSuccess = async () => { const onEditPasswordSuccess = async () => {
const { data } = await api.userApi.getAllUsers(false); const getAllUsersRes = await api.userApi.getAllUsers(false);
allUsers = data; data.allUsers = getAllUsersRes.data;
shouldShowEditUserForm = false; shouldShowEditUserForm = false;
shouldShowInfoPanel = true; shouldShowInfoPanel = true;
}; };
@ -104,7 +61,7 @@
<title>Administration - Immich</title> <title>Administration - Immich</title>
</svelte:head> </svelte:head>
<NavigationBar {user} /> <NavigationBar user={data.user} />
{#if shouldShowCreateUserForm} {#if shouldShowCreateUserForm}
<FullScreenModal on:clickOutside={() => (shouldShowCreateUserForm = false)}> <FullScreenModal on:clickOutside={() => (shouldShowCreateUserForm = false)}>
@ -125,7 +82,7 @@
{#if shouldShowInfoPanel} {#if shouldShowInfoPanel}
<FullScreenModal on:clickOutside={() => (shouldShowInfoPanel = false)}> <FullScreenModal on:clickOutside={() => (shouldShowInfoPanel = false)}>
<div class="border bg-white shadow-sm w-[500px] rounded-3xl p-8 text-sm"> <div class="border bg-white shadow-sm w-[500px] rounded-3xl p-8 text-sm">
<h1 class="font-bold text-immich-primary text-lg mb-4">Password reset success</h1> <h1 class="font-medium text-immich-primary text-lg mb-4">Password reset success</h1>
<p> <p>
The user's password has been reset to the default <code The user's password has been reset to the default <code
@ -170,7 +127,7 @@
<section class="w-[800px] pt-4"> <section class="w-[800px] pt-4">
{#if selectedAction === AdminSideBarSelection.USER_MANAGEMENT} {#if selectedAction === AdminSideBarSelection.USER_MANAGEMENT}
<UserManagement <UserManagement
{allUsers} allUsers={data.allUsers}
on:create-user={() => (shouldShowCreateUserForm = true)} on:create-user={() => (shouldShowCreateUserForm = true)}
on:edit-user={editUserHandler} on:edit-user={editUserHandler}
/> />

View File

@ -0,0 +1,22 @@
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { AlbumResponseDto, serverApi } from '@api';
export const load: PageServerLoad = async ({ parent }) => {
try {
const { user } = await parent();
if (!user) {
throw Error('User is not logged in');
}
const { data: albums } = await serverApi.albumApi.getAllAlbums();
return {
user: user,
albums: albums
};
} catch (e) {
throw redirect(302, '/auth/login');
}
};

View File

@ -1,44 +1,3 @@
<script context="module" lang="ts">
export const prerender = false;
import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
import { ImmichUser } from '$lib/models/immich-user';
import type { Load } from '@sveltejs/kit';
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
import { AlbumResponseDto, api } from '@api';
export const load: Load = async ({ fetch, session }) => {
if (!browser && !session.user) {
return {
status: 302,
redirect: '/auth/login'
};
}
try {
const [user, albums] = await Promise.all([
fetch('/data/user/get-my-user-info').then((r) => r.json()),
fetch('/data/album/get-all-albums').then((r) => r.json())
]);
return {
status: 200,
props: {
user: user,
albums: albums
}
};
} catch (e) {
return {
status: 302,
redirect: '/auth/login'
};
}
};
</script>
<script lang="ts"> <script lang="ts">
import AlbumCard from '$lib/components/album-page/album-card.svelte'; import AlbumCard from '$lib/components/album-page/album-card.svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
@ -46,27 +5,29 @@
import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte'; import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte'; import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
import { browser } from '$app/env'; import type { PageData } from './$types';
import { AlbumResponseDto, api } from '@api';
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
export let user: ImmichUser; export let data: PageData;
export let albums: AlbumResponseDto[];
let isShowContextMenu = false; let isShowContextMenu = false;
let contextMenuPosition = { x: 0, y: 0 }; let contextMenuPosition = { x: 0, y: 0 };
let targetAlbum: AlbumResponseDto; let targetAlbum: AlbumResponseDto;
onMount(async () => { onMount(async () => {
const { data } = await api.albumApi.getAllAlbums(); const getAllAlbumsRes = await api.albumApi.getAllAlbums();
albums = data; data.albums = getAllAlbumsRes.data;
// Delete album that has no photos and is named 'Untitled' // Delete album that has no photos and is named 'Untitled'
for (const album of albums) { for (const album of data.albums) {
if (album.albumName === 'Untitled' && album.assetCount === 0) { if (album.albumName === 'Untitled' && album.assetCount === 0) {
const isDeleted = await autoDeleteAlbum(album); setTimeout(async () => {
await autoDeleteAlbum(album);
if (isDeleted) { data.albums = data.albums.filter((a) => a.id !== album.id);
albums = albums.filter((a) => a.id !== album.id); }, 500);
}
} }
} }
}); });
@ -101,7 +62,7 @@
) { ) {
try { try {
await api.albumApi.deleteAlbum(targetAlbum.id); await api.albumApi.deleteAlbum(targetAlbum.id);
albums = albums.filter((a) => a.id !== targetAlbum.id); data.albums = data.albums.filter((a) => a.id !== targetAlbum.id);
} catch (e) { } catch (e) {
console.log('Error [userDeleteMenu] ', e); console.log('Error [userDeleteMenu] ', e);
} }
@ -127,7 +88,7 @@
</svelte:head> </svelte:head>
<section> <section>
<NavigationBar {user} on:uploadClicked={() => {}} /> <NavigationBar user={data.user} on:uploadClicked={() => {}} />
</section> </section>
<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg "> <section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg ">
@ -158,7 +119,7 @@
<!-- Album Card --> <!-- Album Card -->
<div class="flex flex-wrap gap-8"> <div class="flex flex-wrap gap-8">
{#each albums as album} {#each data.albums as album}
{#key album.id} {#key album.id}
<a sveltekit:prefetch href={`albums/${album.id}`}> <a sveltekit:prefetch href={`albums/${album.id}`}>
<AlbumCard {album} on:showalbumcontextmenu={(e) => showAlbumContextMenu(e, album)} /> <AlbumCard {album} on:showalbumcontextmenu={(e) => showAlbumContextMenu(e, album)} />
@ -168,7 +129,7 @@
</div> </div>
<!-- Empty Message --> <!-- Empty Message -->
{#if albums.length === 0} {#if data.albums.length === 0}
<div <div
class="border p-5 w-[50%] m-auto mt-10 bg-gray-50 rounded-3xl flex flex-col place-content-center place-items-center" class="border p-5 w-[50%] m-auto mt-10 bg-gray-50 rounded-3xl flex flex-col place-content-center place-items-center"
> >

View File

@ -0,0 +1,23 @@
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { serverApi } from '@api';
export const load: PageServerLoad = async ({ parent, params }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, '/auth/login');
}
const albumId = params['albumId'];
try {
const { data: album } = await serverApi.albumApi.getAlbumInfo(albumId);
return {
album
};
} catch (e) {
throw redirect(302, '/albums');
}
};

View File

@ -0,0 +1,14 @@
<script lang="ts">
import AlbumViewer from '$lib/components/album-page/album-viewer.svelte';
import type { PageData } from './$types';
export let data: PageData;
</script>
<svelte:head>
<title>{data.album.albumName} - Immich</title>
</svelte:head>
<div class="immich-scrollbar">
<AlbumViewer album={data.album} />
</div>

View File

@ -1,57 +0,0 @@
<script context="module" lang="ts">
export const prerender = false;
import type { Load } from '@sveltejs/kit';
import { AlbumResponseDto } from '@api';
export const load: Load = async ({ fetch, params, session }) => {
if (!browser && !session.user) {
return {
status: 302,
redirect: '/auth/login'
};
}
try {
const albumId = params['albumId'];
const albumInfo = await fetch(`/data/album/get-album-info?albumId=${albumId}`).then((r) =>
r.json()
);
return {
status: 200,
props: {
album: albumInfo
}
};
} catch (e: any) {
if (e.response?.status === 404) {
return {
status: 302,
redirect: '/albums'
};
}
return {
status: 302,
redirect: '/auth/login'
};
}
};
</script>
<script lang="ts">
import AlbumViewer from '$lib/components/album-page/album-viewer.svelte';
import { browser } from '$app/env';
export let album: AlbumResponseDto;
</script>
<svelte:head>
<title>{album.albumName} - Immich</title>
</svelte:head>
<div class="immich-scrollbar">
<AlbumViewer {album} />
</div>

View File

@ -1,28 +0,0 @@
<script context="module" lang="ts">
export const prerender = false;
import { browser } from '$app/env';
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ params, session }) => {
if (!browser && !session.user) {
return {
status: 302,
redirect: '/auth/login'
};
}
const albumId = params['albumId'];
if (albumId) {
return {
status: 302,
redirect: `/albums/${albumId}`
};
} else {
return {
status: 302,
redirect: `/photos`
};
}
};
</script>

View File

@ -0,0 +1,18 @@
import { redirect } from '@sveltejs/kit';
export const prerender = false;
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ params, parent }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, '/auth/login');
}
const albumId = params['albumId'];
if (albumId) {
throw redirect(302, `/albums/${albumId}`);
} else {
throw redirect(302, `/photos`);
}
};

View File

@ -0,0 +1,25 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { fade } from 'svelte/transition';
import ChangePasswordForm from '$lib/components/forms/change-password-form.svelte';
import type { PageData } from './$types';
export let data: PageData;
const onSuccessHandler = async () => {
await fetch('auth/logout', { method: 'POST' });
goto('/auth/login');
};
</script>
<svelte:head>
<title>Change Password - Immich</title>
</svelte:head>
<section class="h-screen w-screen flex place-items-center place-content-center">
<div in:fade={{ duration: 100 }} out:fade={{ duration: 100 }}>
<ChangePasswordForm user={data.user} on:success={onSuccessHandler} />
</div>
</section>

View File

@ -0,0 +1,21 @@
import { api } from '@api';
import { redirect } from '@sveltejs/kit';
export const prerender = false;
import type { PageLoad } from './$types';
export const load: PageLoad = async () => {
try {
const { data: userInfo } = await api.userApi.getMyUserInfo();
if (userInfo.shouldChangePassword) {
return {
user: userInfo
};
} else {
throw redirect(302, '/photos');
}
} catch (e) {
throw redirect(302, '/auth/login');
}
};

View File

@ -1,56 +0,0 @@
<script context="module" lang="ts">
export const prerender = false;
import type { Load } from '@sveltejs/kit';
export const load: Load = async () => {
try {
const { data: userInfo } = await api.userApi.getMyUserInfo();
if (userInfo.shouldChangePassword) {
return {
status: 200,
props: {
user: userInfo
}
};
} else {
return {
status: 302,
redirect: '/photos'
};
}
} catch (e) {
return {
status: 302,
redirect: '/auth/login'
};
}
};
</script>
<script lang="ts">
import { goto } from '$app/navigation';
import { fade } from 'svelte/transition';
import ChangePasswordForm from '$lib/components/forms/change-password-form.svelte';
import { api, UserResponseDto } from '@api';
export let user: UserResponseDto;
const onSuccessHandler = async () => {
await fetch('auth/logout', { method: 'POST' });
goto('/auth/login');
};
</script>
<svelte:head>
<title>Change Password - Immich</title>
</svelte:head>
<section class="h-screen w-screen flex place-items-center place-content-center">
<div in:fade={{ duration: 100 }} out:fade={{ duration: 100 }}>
<ChangePasswordForm {user} on:success={onSuccessHandler} />
</div>
</section>

View File

@ -4,7 +4,7 @@
import LoginForm from '$lib/components/forms/login-form.svelte'; import LoginForm from '$lib/components/forms/login-form.svelte';
const onLoginSuccess = async () => { const onLoginSuccess = () => {
goto('/photos'); goto('/photos');
}; };
</script> </script>

View File

@ -1,19 +0,0 @@
import { api, serverApi } from '@api';
import type { RequestHandler } from '@sveltejs/kit';
export const POST: RequestHandler = async () => {
api.removeAccessToken();
serverApi.removeAccessToken();
return {
headers: {
'Set-Cookie': [
'immich_is_authenticated=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT;',
'immich_access_token=delete; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'
]
},
body: {
ok: true
}
};
};

View File

@ -0,0 +1,27 @@
import { json } from '@sveltejs/kit';
import { api, serverApi } from '@api';
import type { RequestHandler } from '@sveltejs/kit';
export const POST: RequestHandler = async () => {
api.removeAccessToken();
serverApi.removeAccessToken();
const headers = new Headers();
headers.append(
'set-cookie',
'immich_is_authenticated=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT;'
);
headers.append(
'set-cookie',
'immich_access_token=delete; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'
);
return json(
{
ok: true
},
{
headers
}
);
};

View File

@ -0,0 +1,13 @@
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { serverApi } from '@api';
export const load: PageServerLoad = async () => {
const { data } = await serverApi.userApi.getUserCount();
if (data.userCount != 0) {
// Admin has been registered, redirect to login
throw redirect(302, '/auth/login');
}
return;
};

View File

@ -0,0 +1,11 @@
<script lang="ts">
import AdminRegistrationForm from '$lib/components/forms/admin-registration-form.svelte';
</script>
<svelte:head>
<title>Admin Registration - Immich</title>
</svelte:head>
<section class="h-screen w-screen flex place-items-center place-content-center">
<AdminRegistrationForm />
</section>

View File

@ -1,31 +0,0 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async () => {
const { data } = await api.userApi.getUserCount();
if (data.userCount != 0) {
// Admin has been registered, redirect to login
return {
status: 302,
redirect: '/auth/login'
};
}
return {
status: 200
};
};
</script>
<script lang="ts">
import AdminRegistrationForm from '$lib/components/forms/admin-registration-form.svelte';
import { api } from '@api';
</script>
<svelte:head>
<title>Admin Registration - Immich</title>
</svelte:head>
<section class="h-screen w-screen flex place-items-center place-content-center">
<AdminRegistrationForm />
</section>

View File

@ -1 +0,0 @@
This directory contain SSR endpoints to user serverApi instance to make request directly to DNS

View File

@ -1,18 +0,0 @@
import { AlbumResponseDto, serverApi } from '@api';
import type { RequestEvent, RequestHandlerOutput } from '@sveltejs/kit';
export const GET = async ({
url
}: RequestEvent): Promise<RequestHandlerOutput<AlbumResponseDto>> => {
try {
const albumId = url.searchParams.get('albumId') || '';
const { data } = await serverApi.albumApi.getAlbumInfo(albumId);
return {
body: data
};
} catch {
return {
status: 500
};
}
};

View File

@ -1,18 +0,0 @@
import { AlbumResponseDto, serverApi } from '@api';
import type { RequestEvent, RequestHandler, RequestHandlerOutput } from '@sveltejs/kit';
export const GET = async ({
url
}: RequestEvent): Promise<RequestHandlerOutput<AlbumResponseDto[]>> => {
try {
const isShared = url.searchParams.get('isShared') === 'true' || undefined;
const { data } = await serverApi.albumApi.getAllAlbums(isShared);
return {
body: data
};
} catch {
return {
status: 500
};
}
};

View File

@ -1,15 +0,0 @@
import { AssetResponseDto, serverApi } from '@api';
import type { RequestHandlerOutput } from '@sveltejs/kit';
export const GET = async (): Promise<RequestHandlerOutput<AssetResponseDto[]>> => {
try {
const { data } = await serverApi.assetApi.getAllAssets();
return {
body: data
};
} catch {
return {
status: 500
};
}
};

View File

@ -1,17 +0,0 @@
import { serverApi, UserResponseDto } from '@api';
import type { RequestEvent, RequestHandlerOutput } from '@sveltejs/kit';
export const GET = async ({url} : RequestEvent): Promise<RequestHandlerOutput<UserResponseDto[]>> => {
try {
const isAll = url.searchParams.get('isAll') === 'true';
const { data } = await serverApi.userApi.getAllUsers(isAll);
return {
body: data
};
} catch {
return {
status: 500
};
}
};

View File

@ -1,15 +0,0 @@
import { serverApi, UserResponseDto } from '@api';
import type { RequestHandlerOutput } from '@sveltejs/kit';
export const GET = async (): Promise<RequestHandlerOutput<UserResponseDto>> => {
try {
const { data } = await serverApi.userApi.getMyUserInfo();
return {
body: data
};
} catch {
return {
status: 500
};
}
};

View File

@ -1,58 +0,0 @@
<script context="module" lang="ts">
export const prerender = false;
import type { Load } from '@sveltejs/kit';
import { api } from '@api';
import { browser } from '$app/env';
export const load: Load = async () => {
if (browser) {
try {
const {data: user} = await api.userApi.getMyUserInfo();
return {
status: 302,
redirect: '/photos'
};
} catch (e) {
}
const {data} = await api.userApi.getUserCount();
return {
status: 200,
props: {
isAdminUserExist: data.userCount != 0
}
};
}
};
</script>
<script lang="ts">
import { goto } from '$app/navigation';
export let isAdminUserExist: boolean;
async function onGettingStartedClicked() {
isAdminUserExist ? await goto('/auth/login') : await goto('/auth/register');
}
</script>
<svelte:head>
<title>Welcome 🎉 - Immich</title>
<meta name="description" content="Immich Web Interface"/>
</svelte:head>
<section class="h-screen w-screen flex place-items-center place-content-center">
<div class="flex flex-col place-items-center gap-8 text-center max-w-[350px]">
<div class="flex place-items-center place-content-center ">
<img class="text-center" src="immich-logo.svg" height="200" width="200" alt="immich-logo"/>
</div>
<h1 class="text-4xl text-immich-primary font-bold font-immich-title">Welcome to IMMICH Web</h1>
<button
class="border px-4 py-2 rounded-md bg-immich-primary hover:bg-immich-primary/75 text-white font-bold w-[200px]"
on:click={onGettingStartedClicked}>Getting Started
</button
>
</div>
</section>

View File

@ -0,0 +1,21 @@
import { serverApi } from './../../api/api';
import type { PageServerLoad } from './$types';
import { redirect, error } from '@sveltejs/kit';
export const load: PageServerLoad = async ({ parent }) => {
try {
const { user } = await parent();
if (!user) {
throw error(400, 'Not logged in');
}
const { data: assets } = await serverApi.assetApi.getAllAssets();
return {
user,
assets
};
} catch (e) {
throw redirect(302, '/auth/login');
}
};

View File

@ -1,60 +1,28 @@
<script context="module" lang="ts">
export const prerender = false;
import type { Load } from '@sveltejs/kit';
import { setAssetInfo } from '$lib/stores/assets';
export const load: Load = async ({ fetch, session }) => {
if (!browser && !session.user) {
return {
status: 302,
redirect: '/auth/login'
};
}
try {
const [userInfo, assets] = await Promise.all([
fetch('/data/user/get-my-user-info').then((r) => r.json()),
fetch('/data/asset/get-all-assets').then((r) => r.json())
]);
setAssetInfo(assets);
return {
status: 200,
props: {
user: userInfo
}
};
} catch (e) {
console.log('ERROR load photos index');
return {
status: 302,
redirect: '/auth/login'
};
}
};
</script>
<script lang="ts"> <script lang="ts">
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte'; import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
import CheckCircle from 'svelte-material-icons/CheckCircle.svelte'; import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import { assetsGroupByDate, flattenAssetGroupByDate, assets } from '$lib/stores/assets'; import {
assetsGroupByDate,
flattenAssetGroupByDate,
assets,
setAssetInfo
} from '$lib/stores/assets';
import ImmichThumbnail from '$lib/components/shared-components/immich-thumbnail.svelte'; import ImmichThumbnail from '$lib/components/shared-components/immich-thumbnail.svelte';
import moment from 'moment'; import moment from 'moment';
import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte'; import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
import { openFileUploadDialog, UploadType } from '$lib/utils/file-uploader'; import { openFileUploadDialog, UploadType } from '$lib/utils/file-uploader';
import { api, AssetResponseDto, UserResponseDto } from '@api'; import { api, AssetResponseDto } from '@api';
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte'; import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
import CircleOutline from 'svelte-material-icons/CircleOutline.svelte'; import CircleOutline from 'svelte-material-icons/CircleOutline.svelte';
import CircleIconButton from '$lib/components/shared-components/circle-icon-button.svelte'; import CircleIconButton from '$lib/components/shared-components/circle-icon-button.svelte';
import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte'; import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
import Close from 'svelte-material-icons/Close.svelte'; import Close from 'svelte-material-icons/Close.svelte';
import { browser } from '$app/env';
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte'; import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
import type { PageData } from './$types';
import { onMount } from 'svelte';
export let user: UserResponseDto; export let data: PageData;
let selectedGroupThumbnail: number | null; let selectedGroupThumbnail: number | null;
let isMouseOverGroup: boolean; let isMouseOverGroup: boolean;
@ -73,6 +41,10 @@
let currentViewAssetIndex = 0; let currentViewAssetIndex = 0;
let selectedAsset: AssetResponseDto; let selectedAsset: AssetResponseDto;
onMount(() => {
setAssetInfo(data.assets);
});
const thumbnailMouseEventHandler = (event: CustomEvent) => { const thumbnailMouseEventHandler = (event: CustomEvent) => {
const { selectedGroupIndex }: { selectedGroupIndex: number } = event.detail; const { selectedGroupIndex }: { selectedGroupIndex: number } = event.detail;
@ -234,7 +206,10 @@
{/if} {/if}
{#if !isMultiSelectionMode} {#if !isMultiSelectionMode}
<NavigationBar {user} on:uploadClicked={() => openFileUploadDialog(UploadType.GENERAL)} /> <NavigationBar
user={data.user}
on:uploadClicked={() => openFileUploadDialog(UploadType.GENERAL)}
/>
{/if} {/if}
</section> </section>

View File

@ -1,20 +0,0 @@
<script context="module" lang="ts">
export const prerender = false;
import { browser } from '$app/env';
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ session }) => {
if (!browser && !session.user) {
return {
status: 302,
redirect: '/auth/login'
};
} else {
return {
status: 302,
redirect: '/photos'
};
}
};
</script>

View File

@ -0,0 +1,14 @@
import { redirect } from '@sveltejs/kit';
export const prerender = false;
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ parent }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, '/auth/login');
} else {
throw redirect(302, '/photos');
}
};

View File

@ -0,0 +1,23 @@
import { redirect } from '@sveltejs/kit';
export const prerender = false;
import { serverApi } from '@api';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ parent }) => {
try {
const { user } = await parent();
if (!user) {
throw redirect(302, '/auth/login');
}
const { data: sharedAlbums } = await serverApi.albumApi.getAllAlbums(true);
return {
user: user,
sharedAlbums: sharedAlbums
};
} catch (e) {
throw redirect(302, '/auth/login');
}
};

View File

@ -0,0 +1,83 @@
<script lang="ts">
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
import SharedAlbumListTile from '$lib/components/sharing-page/shared-album-list-tile.svelte';
import { goto } from '$app/navigation';
import { api } from '@api';
import type { PageData } from './$types';
export let data: PageData;
const createSharedAlbum = async () => {
try {
const { data: newAlbum } = await api.albumApi.createAlbum({
albumName: 'Untitled'
});
goto('/albums/' + newAlbum.id);
} catch (e) {
console.log('Error [createAlbum] ', e);
}
};
</script>
<svelte:head>
<title>Albums - Immich</title>
</svelte:head>
<section>
<NavigationBar user={data.user} on:uploadClicked={() => {}} />
</section>
<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg">
<SideBar />
<section class="overflow-y-auto relative">
<section id="album-content" class="relative pt-8 pl-4 mb-12 bg-immich-bg">
<!-- Main Section -->
<div class="px-4 flex justify-between place-items-center">
<div>
<p class="font-medium">Sharing</p>
</div>
<div>
<button
on:click={createSharedAlbum}
class="flex place-items-center gap-1 text-sm hover:bg-immich-primary/5 p-2 rounded-lg font-medium hover:text-gray-700"
>
<span>
<PlusBoxOutline size="18" />
</span>
<p>Create shared album</p>
</button>
</div>
</div>
<div class="my-4">
<hr />
</div>
<!-- Share Album List -->
<div class="w-full flex flex-col place-items-center">
{#each data.sharedAlbums as album}
<a sveltekit:prefetch href={`albums/${album.id}`}>
<SharedAlbumListTile {album} user={data.user} />
</a>
{/each}
</div>
<!-- Empty List -->
{#if data.sharedAlbums.length === 0}
<div
class="border p-5 w-[50%] m-auto mt-10 bg-gray-50 rounded-3xl flex flex-col place-content-center place-items-center"
>
<img src="/empty-2.svg" alt="Empty shared album" width="500" />
<p class="text-center text-immich-text-gray-500">
Create a shared album to share photos and videos with people in your network
</p>
</div>
{/if}
</section>
</section>
</section>

View File

@ -1,120 +0,0 @@
<script context="module" lang="ts">
export const prerender = false;
import type { Load } from '@sveltejs/kit';
import { AlbumResponseDto, api, UserResponseDto } from '@api';
import { browser } from '$app/env';
export const load: Load = async ({fetch, session}) => {
if (!browser && !session.user) {
return {
status: 302,
redirect: '/auth/login'
};
}
try {
const [user, sharedAlbums] = await Promise.all([
fetch('/data/user/get-my-user-info').then((r) => r.json()),
fetch('/data/album/get-all-albums?isShared=true').then((r) => r.json())
]);
return {
status: 200,
props: {
user: user,
sharedAlbums: sharedAlbums
}
};
} catch (e) {
return {
status: 302,
redirect: '/auth/login'
};
}
};
</script>
<script lang="ts">
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
import SharedAlbumListTile from '$lib/components/sharing-page/shared-album-list-tile.svelte';
import { goto } from '$app/navigation';
export let user: UserResponseDto;
export let sharedAlbums: AlbumResponseDto[];
const createSharedAlbum = async () => {
try {
const {data: newAlbum} = await api.albumApi.createAlbum({
albumName: 'Untitled'
});
goto('/albums/' + newAlbum.id);
} catch (e) {
console.log('Error [createAlbum] ', e);
}
};
</script>
<svelte:head>
<title>Albums - Immich</title>
</svelte:head>
<section>
<NavigationBar {user} on:uploadClicked={() => {}}/>
</section>
<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg">
<SideBar/>
<section class="overflow-y-auto relative">
<section id="album-content" class="relative pt-8 pl-4 mb-12 bg-immich-bg">
<!-- Main Section -->
<div class="px-4 flex justify-between place-items-center">
<div>
<p class="font-medium">Sharing</p>
</div>
<div>
<button
on:click={createSharedAlbum}
class="flex place-items-center gap-1 text-sm hover:bg-immich-primary/5 p-2 rounded-lg font-medium hover:text-gray-700"
>
<span>
<PlusBoxOutline size="18"/>
</span>
<p>Create shared album</p>
</button>
</div>
</div>
<div class="my-4">
<hr/>
</div>
<!-- Share Album List -->
<div class="w-full flex flex-col place-items-center">
{#each sharedAlbums as album}
<a sveltekit:prefetch href={`albums/${album.id}`}>
<SharedAlbumListTile {album} {user}/>
</a
>
{/each}
</div>
<!-- Empty List -->
{#if sharedAlbums.length === 0}
<div
class="border p-5 w-[50%] m-auto mt-10 bg-gray-50 rounded-3xl flex flex-col place-content-center place-items-center"
>
<img src="/empty-2.svg" alt="Empty shared album" width="500"/>
<p class="text-center text-immich-text-gray-500">
Create a shared album to share photos and videos with people in your network
</p>
</div>
{/if}
</section>
</section>
</section>