feat: next improvements (#180)

This commit is contained in:
Jason Rasmussen
2025-09-05 14:33:25 -04:00
committed by GitHub
parent 57c7cf33ae
commit 053e9f99e1
15 changed files with 462 additions and 147 deletions
@@ -1,6 +1,7 @@
<script lang="ts">
import ApiPageContent from '$lib/api/components/ApiPageContent.svelte';
import CodeBlock from '$lib/components/CodeBlock.svelte';
import { Constants } from '$lib';
import { ApiPage } from '$lib/utils/api';
import { Code, Heading, Link, Text } from '@immich/ui';
import { bash } from 'svelte-highlight/languages';
@@ -17,7 +18,7 @@ init({ baseUrl: "https://demo.immich.app/api", apiKey: API_KEY });`;
title="@immich/sdk"
description="Learn about the auto-generated SDK"
nextSteps={[
{ href: 'https://www.npmjs.com/package/@immich/sdk', title: 'View on npm' },
{ href: Constants.Npm.Sdk, title: 'View on npm' },
{ href: ApiPage.Endpoints, title: 'View API endpoints' },
]}
>
@@ -25,8 +26,8 @@ init({ baseUrl: "https://demo.immich.app/api", apiKey: API_KEY });`;
>There are lots of tools to auto-generate software development kits (SDK) from <Link
href="https://swagger.io/specification/">OpenAPI Specification</Link
> documents. Immich uses <Link href="https://github.com/oazapfts/oazapfts">oazapfts</Link> to generate a TypeScript SDK,
and publishes it to <Link href="https://www.npmjs.com/">npm</Link> as <Link
href="https://www.npmjs.com/package/@immich/sdk">@immich/sdk</Link
and publishes it to <Link href="https://www.npmjs.com/">npm</Link> as <Link href={Constants.Npm.Sdk}
>@immich/sdk</Link
>.
</Text>
+3 -1
View File
@@ -1,4 +1,5 @@
<script lang="ts">
import { Constants } from '$lib';
import stackedBlocksUrl from '$lib/assets/img/stacked-blocks.svg';
import Header from '$lib/components/Header.svelte';
import PageContent from '$lib/components/PageContent.svelte';
@@ -36,7 +37,8 @@
<div class="flex flex-col gap-2">
<Heading size="giant" tag="h2">Not a developer?</Heading>
<Text color="muted">Learn more about Immich at <Link href="https://immich.app/">https://immich.app/</Link></Text
<Text color="muted"
>Learn more about Immich at <Link href={Constants.Sites.Docs}>{Constants.Sites.Docs}</Link></Text
>
</div>
<Button
@@ -193,7 +193,7 @@
</div>
<div class="gap-2 m-4">
<Text size="large" class="font-semibold text-ellipsis line-clamp-1 mb-2">{asset.name}</Text>
<Text size="large" fontWeight="semi-bold" class="text-ellipsis line-clamp-1 mb-2">{asset.name}</Text>
<Text size="medium" color={asset.metadata.captureType ? 'primary' : 'danger'}>
<Icon
+5 -4
View File
@@ -1,4 +1,5 @@
<script lang="ts">
import { Constants } from '$lib';
import portainerIcon from '$lib/assets/img/portainer.svg';
import truenasIcon from '$lib/assets/img/truenas-scale.svg';
import unraidIcon from '$lib/assets/img/unraid.svg';
@@ -47,16 +48,16 @@
</CardHeader>
<CardBody>
<Stack gap={4}>
<a href="https://get.immich.app/android">
<a href={Constants.Get.Android}>
<img class="h-24 mx-auto" alt="Get it on Google Play" src={playStoreBadge} />
</a>
<a href="https://get.immich.app/fdroid">
<a href={Constants.Get.FDroid}>
<img class="h-16 mx-auto rounded-xl border border-dark" alt="Get it on F-Droid" src={fdroidBadge} />
</a>
<Button
class="h-16 mx-auto mt-4 dark:bg-black dark:text-white"
size="giant"
href="https://github.com/immich-app/immich/releases/latest"
href={Constants.Get.GithubRelease}
color="secondary"
>
<Icon icon={mdiAndroid} size="2em" />
@@ -71,7 +72,7 @@
<CardTitle>iOS</CardTitle>
</CardHeader>
<CardBody>
<a href="https://get.immich.app/ios">
<a href={Constants.Get.iOS}>
<img class="h-16 mt-4 mx-auto" alt="Download on the App Store" src={appStoreBadge} />
</a>
</CardBody>
@@ -1,9 +1,10 @@
<script lang="ts">
import { beforeNavigate } from '$app/navigation';
import { Constants } from '$lib';
import Header from '$lib/components/Header.svelte';
import PageContent from '$lib/components/PageContent.svelte';
import type { HeaderItem } from '$lib/types';
import { AppShell, AppShellHeader, AppShellSidebar, Heading, NavbarItem, Stack, Text } from '@immich/ui';
import { AppShell, AppShellHeader, AppShellSidebar, NavbarItem } from '@immich/ui';
import { type Snippet } from 'svelte';
import { MediaQuery } from 'svelte/reactivity';
@@ -26,8 +27,8 @@
{ title: 'Download', href: '/download', variant: 'filled', color: 'primary' },
{ title: 'Features', href: '/features' },
{ title: 'Roadmap', href: '/roadmap' },
{ title: 'Docs', href: 'https://immich.app/' },
{ title: 'API', href: 'https://api.immich.app/' },
{ title: 'Docs', href: Constants.Sites.Docs },
{ title: 'API', href: Constants.Sites.Api },
];
</script>
@@ -45,10 +46,6 @@
</AppShellSidebar>
<PageContent class="mx-auto w-full max-w-(--breakpoint-lg)">
{@render children?.()};
<Stack>
<Heading size="title" tag="h1">About</Heading>
<Text>Meet the team</Text>
</Stack>
{@render children?.()}
</PageContent>
</AppShell>
@@ -1,13 +1,15 @@
<script lang="ts">
import { Constants } from '$lib';
import qrCode from '$lib/assets/img/app-qr-code-dark.svg';
import screenshot from '$lib/assets/img/screenshot-dark.webp';
import { appStoreBadge, Button, Heading, Icon, Logo, playStoreBadge, Text, VStack } from '@immich/ui';
import { mdiOpenInNew } from '@mdi/js';
import { siDiscord } from 'simple-icons';
</script>
<VStack gap={8} class="mt-4 lg:mt-16 text-center">
<Logo size="giant" variant="logo" class="border" />
<Heading size="title" tag="h1"
<Heading size="title" tag="h1" fontWeight="extra-bold"
>Self-hosted <span class="text-primary">photo and<br class="hidden lg:block" /> video management</span> solution</Heading
>
<Text
@@ -18,13 +20,13 @@
<div class="flex flex-col gap-4">
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<Button size="large" href="/download">Download</Button>
<Button size="large" href="https://demo.immich.app/" color="secondary">
<Button size="large" href={Constants.Sites.Demo} color="secondary">
<span>Open Demo</span>
<Icon icon={mdiOpenInNew} />
</Button>
</div>
<div class="flex justify-center">
<Button size="large" variant="ghost">Join our Discord</Button>
<Button size="large" variant="ghost" leadingIcon={siDiscord.path}>Join our Discord</Button>
</div>
</div>
@@ -33,12 +35,12 @@
<hr class="border-t w-full max-w-(--breakpoint-sm) m-8" />
<Heading size="title" tag="h2">Download mobile app</Heading>
<Text>Download Immich app and start backing up your photos and videos securely to your own server</Text>
<div class="flex flex-col lg:flex-row">
<Button href="" variant="ghost" class="p-0">
<img src={playStoreBadge} alt="Playstore Badge" class="h-24" />
<div class="flex flex-col lg:flex-row gap-4">
<Button href={Constants.Get.Android} target="_blank" rel="noopener noreferrer" variant="ghost" class="p-0">
<img src={playStoreBadge} alt="Playstore Badge" class="h-16" />
</Button>
<Button href="" variant="ghost" class="p-0">
<img src={appStoreBadge} alt="Playstore Badge" class="h-24 p-[16px]" />
<Button href={Constants.Get.iOS} target="_blank" rel="noopener noreferrer" variant="ghost" class="p-0">
<img src={appStoreBadge} alt="AppStore Badge" class="h-16" />
</Button>
</div>
<img src={qrCode} alt="QRCode" class="h-36" />
@@ -0,0 +1,268 @@
<script lang="ts">
import Timeline from '$lib/components/Timeline.svelte';
import type { TimelineItem } from '$lib/types';
import { Heading, Stack, Text } from '@immich/ui';
import {
mdiBug,
mdiCalendarToday,
mdiCrosshairsOff,
mdiCrop,
mdiDatabase,
mdiLeadPencil,
mdiLockOff,
mdiLockOutline,
mdiMicrosoftWindows,
mdiSecurity,
mdiSpeedometerSlow,
mdiTrashCan,
mdiWeb,
mdiWrap,
mdiCloudKeyOutline,
mdiRegex,
mdiCodeJson,
mdiClockOutline,
mdiAccountOutline,
mdiRestart,
} from '@mdi/js';
const withLanguage = (date: Date) => (language: string) => date.toLocaleDateString(language);
type Item = Omit<TimelineItem, 'done' | 'getDateLabel'> & { date: Date };
const items: Item[] = [
{
icon: mdiClockOutline,
iconColor: 'gray',
title: 'setTimeout is cursed',
description:
'The setTimeout method in JavaScript is cursed when used with small values because the implementation may or may not actually wait the specified time.',
link: {
url: 'https://github.com/immich-app/immich/pull/20655',
text: '#20655',
},
date: new Date(2025, 7, 4),
},
{
icon: mdiAccountOutline,
iconColor: '#DAB1DA',
title: 'PostgreSQL USER is cursed',
description:
'The USER keyword in PostgreSQL is cursed because you can select from it like a table, which leads to confusion if you have a table name user as well.',
link: {
url: 'https://github.com/immich-app/immich/pull/19891',
text: '#19891',
},
date: new Date(2025, 7, 4),
},
{
icon: mdiRestart,
iconColor: '#8395e3',
title: 'PostgreSQL RESET is cursed',
description:
'PostgreSQL RESET is cursed because it is impossible to RESET a PostgreSQL extension parameter if the extension has been uninstalled.',
link: {
url: 'https://github.com/immich-app/immich/pull/19363',
text: '#19363',
},
date: new Date(2025, 5, 20),
},
{
icon: mdiRegex,
iconColor: 'purple',
title: 'Zitadel Actions are cursed',
description:
"Zitadel is cursed because its custom scripting feature is executed with a JS engine that doesn't support regex named capture groups.",
link: {
url: 'https://github.com/dop251/goja',
text: 'Go JS engine',
},
date: new Date(2025, 5, 4),
},
{
icon: mdiCloudKeyOutline,
iconColor: '#0078d4',
title: 'Entra is cursed',
description:
"Microsoft Entra supports PKCE, but doesn't include it in its OpenID discovery document. This leads to clients thinking PKCE isn't available.",
link: {
url: 'https://github.com/immich-app/immich/pull/18725',
text: '#18725',
},
date: new Date(2025, 4, 30),
},
{
icon: mdiCrop,
iconColor: 'tomato',
title: 'Image dimensions in EXIF metadata are cursed',
description:
'The dimensions in EXIF metadata can be different from the actual dimensions of the image, causing issues with cropping and resizing.',
link: {
url: 'https://github.com/immich-app/immich/pull/17974',
text: '#17974',
},
date: new Date(2025, 4, 5),
},
{
icon: mdiCodeJson,
iconColor: 'yellow',
title: 'YAML whitespace is cursed',
description: 'YAML whitespaces are often handled in unintuitive ways.',
link: {
url: 'https://github.com/immich-app/immich/pull/17309',
text: '#17309',
},
date: new Date(2025, 3, 1),
},
{
icon: mdiMicrosoftWindows,
iconColor: '#357EC7',
title: 'Hidden files in Windows are cursed',
description:
'Hidden files in Windows cannot be opened with the "w" flag. That, combined with SMB option "hide dot files" leads to a lot of confusion.',
link: {
url: 'https://github.com/immich-app/immich/pull/12812',
text: '#12812',
},
date: new Date(2024, 8, 20),
},
{
icon: mdiWrap,
iconColor: 'gray',
title: 'Carriage returns in bash scripts are cursed',
description:
'Git can be configured to automatically convert LF to CRLF on checkout and CRLF breaks bash scripts.',
link: {
url: 'https://github.com/immich-app/immich/pull/11613',
text: '#11613',
},
date: new Date(2024, 7, 7),
},
{
icon: mdiLockOff,
iconColor: 'red',
title: 'Fetch inside Cloudflare Workers is cursed',
description:
'Fetch requests in Cloudflare Workers use http by default, even if you explicitly specify https, which can often cause redirect loops.',
link: {
url: 'https://community.cloudflare.com/t/does-cloudflare-worker-allow-secure-https-connection-to-fetch-even-on-flexible-ssl/68051/5',
text: 'Cloudflare',
},
date: new Date(2024, 7, 7),
},
{
icon: mdiCrosshairsOff,
iconColor: 'gray',
title: 'GPS sharing on mobile is cursed',
description:
'Some phones will silently strip GPS data from images when apps without location permission try to access them.',
link: {
url: 'https://github.com/immich-app/immich/discussions/11268',
text: '#11268',
},
date: new Date(2024, 6, 21),
},
{
icon: mdiLeadPencil,
iconColor: 'gold',
title: 'PostgreSQL NOTIFY is cursed',
description:
'PostgreSQL does everything in a transaction, including NOTIFY. This means using the socket.io postgres-adapter writes to WAL every 5 seconds.',
link: { url: 'https://github.com/immich-app/immich/pull/10801', text: '#10801' },
date: new Date(2024, 6, 3),
},
{
icon: mdiWeb,
iconColor: 'lightskyblue',
title: 'npm scripts are cursed',
description:
'npm scripts make a http call to the npm registry each time they run, which means they are a terrible way to execute a health check.',
link: { url: 'https://github.com/immich-app/immich/issues/10796', text: '#10796' },
date: new Date(2024, 6, 3),
},
{
icon: mdiSpeedometerSlow,
iconColor: 'brown',
title: '50 extra packages are cursed',
description:
'There is a user in the JavaScript community who goes around adding "backwards compatibility" to projects. They do this by adding 50 extra package dependencies to your project, which are maintained by them.',
link: { url: 'https://github.com/immich-app/immich/pull/10690', text: '#10690' },
date: new Date(2024, 5, 28),
},
{
icon: mdiLockOutline,
iconColor: 'gold',
title: 'Long passwords are cursed',
description:
'The bcrypt implementation only uses the first 72 bytes of a string. Any characters after that are ignored.',
// link: GHSA-4p64-9f7h-3432
date: new Date(2024, 5, 25),
},
{
icon: mdiCalendarToday,
iconColor: 'greenyellow',
title: 'JavaScript Date objects are cursed',
description: 'JavaScript date objects are 1 indexed for years and days, but 0 indexed for months.',
link: { url: 'https://github.com/immich-app/immich/pull/6787', text: '#6787' },
date: new Date(2024, 0, 31),
},
{
icon: mdiBug,
iconColor: 'green',
title: 'ESM imports are cursed',
description:
'Prior to Node.js v20.8 using --experimental-vm-modules in a CommonJS project that imported an ES module that imported a CommonJS modules would create a segfault and crash Node.js',
link: {
url: 'https://github.com/immich-app/immich/pull/6719',
text: '#6179',
},
date: new Date(2024, 0, 9),
},
{
icon: mdiDatabase,
iconColor: 'gray',
title: 'PostgreSQL parameters are cursed',
description: `PostgresSQL has a limit of ${Number(65535).toLocaleString()} parameters, so bulk inserts can fail with large datasets.`,
link: {
url: 'https://github.com/immich-app/immich/pull/6034',
text: '#6034',
},
date: new Date(2023, 11, 28),
},
{
icon: mdiSecurity,
iconColor: 'gold',
title: 'Secure contexts are cursed',
description: `Some web features like the clipboard API only work in "secure contexts" (ie. https or localhost)`,
link: {
url: 'https://github.com/immich-app/immich/issues/2981',
text: '#2981',
},
date: new Date(2023, 5, 26),
},
{
icon: mdiTrashCan,
iconColor: 'gray',
title: 'TypeORM deletes are cursed',
description: `The remove implementation in TypeORM mutates the input, deleting the id property from the original object.`,
link: {
url: 'https://github.com/typeorm/typeorm/issues/7024#issuecomment-948519328',
text: 'typeorm#6034',
},
date: new Date(2023, 1, 23),
},
];
</script>
<div class="p-4">
<Stack class="text-center" gap={4}>
<Heading size="title" tag="h1">Cursed Knowledge</Heading>
<Text>Cursed knowledge we have learned as a result of building Immich that we wish we never knew.</Text>
</Stack>
<div class="flex justify-around mt-8 w-full max-w-(--breakpoint-md) mx-auto">
<Timeline
items={items
.sort((a, b) => b.date.getTime() - a.date.getTime())
.map((item) => ({ ...item, getDateLabel: withLanguage(item.date) }))}
/>
</div>
</div>
@@ -1,4 +1,5 @@
<script lang="ts">
import { Constants } from '$lib';
import portainerIcon from '$lib/assets/img/portainer.svg';
import truenasIcon from '$lib/assets/img/truenas-scale.svg';
import unraidIcon from '$lib/assets/img/unraid.svg';
@@ -55,7 +56,6 @@
<Button fullWidth href="https://immich.app/docs/install/portainer">View Instructions</Button>
</CardFooter>
</Card>
<Card color="secondary" class="h-full">
<CardHeader>
<CardTitle>Unraid</CardTitle>
@@ -97,16 +97,16 @@
</CardHeader>
<CardBody>
<Stack gap={4}>
<a href="https://get.immich.app/android">
<img class="h-24 mx-auto" alt="Get it on Google Play" src={playStoreBadge} />
<a href={Constants.Get.Android}>
<img class="h-16 mx-auto" alt="Get it on Google Play" src={playStoreBadge} />
</a>
<a href="https://get.immich.app/fdroid">
<a href={Constants.Get.FDroid}>
<img class="h-16 mx-auto rounded-xl border border-dark" alt="Get it on F-Droid" src={fdroidBadge} />
</a>
<Button
class="h-16 mx-auto mt-4 dark:bg-black dark:text-white"
class="h-16 mx-auto dark:bg-black dark:text-white"
size="giant"
href="https://github.com/immich-app/immich/releases/latest"
href={Constants.Get.GithubRelease}
color="secondary"
>
<Icon icon={mdiAndroid} size="2em" />
@@ -121,7 +121,7 @@
<CardTitle>iOS</CardTitle>
</CardHeader>
<CardBody>
<a href="https://get.immich.app/ios">
<a href={Constants.Get.iOS}>
<img class="h-16 mt-4 mx-auto" alt="Download on the App Store" src={appStoreBadge} />
</a>
</CardBody>
@@ -13,6 +13,7 @@
mdiBookSearchOutline,
mdiCakeVariant,
mdiCameraBurst,
mdiCast,
mdiChartBoxMultipleOutline,
mdiCheckAll,
mdiCheckboxMarked,
@@ -74,6 +75,7 @@
mdiStarOutline,
mdiTableKey,
mdiTag,
mdiTagFaces,
mdiTagMultiple,
mdiText,
mdiThemeLightDark,
@@ -84,6 +86,10 @@
} from '@mdi/js';
const releases = {
'v1.135.0': new Date(2025, 5, 18),
'v1.133.0': new Date(2025, 4, 21),
'v1.130.0': new Date(2025, 2, 25),
'v1.127.0': new Date(2025, 1, 26),
'v1.122.0': new Date(2024, 11, 5),
'v1.120.0': new Date(2024, 10, 6),
'v1.114.0': new Date(2024, 8, 6),
@@ -191,14 +197,6 @@
description: 'Automate tasks with workflows',
getDateLabel: () => 'Planned for 2025',
},
{
done: false,
icon: mdiTableKey,
iconColor: 'gray',
title: 'Fine grained access controls',
description: 'Granular access controls for users and api keys',
getDateLabel: () => 'Planned for 2025',
},
{
done: false,
icon: mdiImageEdit,
@@ -213,14 +211,6 @@
iconColor: 'indianred',
title: 'Stable release',
description: 'Immich goes stable',
getDateLabel: () => 'Planned for early 2025',
},
{
done: false,
icon: mdiLockOutline,
iconColor: 'sandybrown',
title: 'Private/locked photos',
description: 'Private assets with extra protections',
getDateLabel: () => 'Planned for 2025',
},
{
@@ -242,6 +232,56 @@
];
const milestones: TimelineItem[] = [
{
icon: mdiStar,
iconColor: 'gold',
title: '70,000 Stars',
description: 'Reached 70K Stars on GitHub!',
getDateLabel: withLanguage(new Date(2025, 6, 9)),
},
withRelease({
icon: mdiTableKey,
iconColor: 'gray',
title: 'Fine grained access controls',
description: 'Granular access controls for api keys',
release: 'v1.135.0',
}),
withRelease({
icon: mdiCast,
iconColor: 'aqua',
title: 'Google Cast (web and mobile)',
description: 'Cast assets to Google Cast/Chromecast compatible devices',
release: 'v1.135.0',
}),
withRelease({
icon: mdiLockOutline,
iconColor: 'sandybrown',
title: 'Private/locked photos',
description: 'Private assets with extra protections',
release: 'v1.133.0',
}),
withRelease({
icon: mdiFolderMultiple,
iconColor: 'brown',
title: 'Folders view in the mobile app',
description: 'Browse your photos and videos in their folder structure inside the mobile app',
release: 'v1.130.0',
}),
{
icon: mdiStar,
iconColor: 'gold',
title: '60,000 Stars',
description: 'Reached 60K Stars on GitHub!',
getDateLabel: withLanguage(new Date(2025, 2, 4)),
},
withRelease({
icon: mdiTagFaces,
iconColor: 'teal',
title: 'Manual face tagging',
description:
'Manually tag or remove faces in photos and videos, even when automatic detection misses or misidentifies them.',
release: 'v1.127.0',
}),
withRelease({
icon: mdiLinkEdit,
iconColor: 'crimson',
@@ -259,8 +299,8 @@
withRelease({
icon: mdiDatabaseOutline,
iconColor: 'brown',
title: 'Automatic database backups',
description: 'Database backups are now integrated into the Immich server',
title: 'Automatic database dumps',
description: 'Database dumps are now integrated into the Immich server',
release: 'v1.120.0',
}),
{
@@ -293,7 +333,7 @@
withRelease({
icon: mdiFolderMultiple,
iconColor: 'brown',
title: 'Folders',
title: 'Folders view',
description: 'Browse your photos and videos in their folder structure',
release: 'v1.113.0',
}),
@@ -2,6 +2,7 @@
import { afterNavigate, beforeNavigate } from '$app/navigation';
import { page } from '$app/state';
import '$lib/app.css';
import { initializeTheme } from '@immich/ui';
import { onMount, type Snippet } from 'svelte';
interface Props {
@@ -10,6 +11,8 @@
let { children }: Props = $props();
initializeTheme({ selector: 'body' });
let pathname = '';
onMount(() => {
pathname = page.url.pathname;
+8 -8
View File
@@ -15,7 +15,7 @@
"simple-icons": "^15.9.0"
},
"devDependencies": {
"@immich/ui": "^0.25.0",
"@immich/ui": "^0.26.0",
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/kit": "^2.26.1",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
@@ -705,16 +705,16 @@
}
},
"node_modules/@immich/ui": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.25.0.tgz",
"integrity": "sha512-uP58weVr1UjmPwqNBXIUcg4+8ezhetqfyBYRH7gmQvSk+eko/WApywNZGa3/tCb1HTRWzaOlijXjQ/JdwQJq6Q==",
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.26.0.tgz",
"integrity": "sha512-I9UMJJMJrGHkl0XHV+R1fc0jou9DAH/qiu8t/zYJhiBiipaS5N9xAFliLb5JdY+uAVnpPRSNDrHIFdVjjax6nw==",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@mdi/js": "^7.4.47",
"bits-ui": "^2.0.0",
"tailwind-merge": "^3.0.0",
"tailwind-variants": "^2.0.0"
"tailwind-variants": "^3.0.0"
},
"peerDependencies": {
"svelte": "^5.0.0"
@@ -4285,9 +4285,9 @@
}
},
"node_modules/tailwind-variants": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-2.1.0.tgz",
"integrity": "sha512-82m0eRex0z6A3GpvfoTCpHr+wWJmbecfVZfP3mqLoDxeya5tN4mYJQZwa5Aw1hRZTedwpu1D2JizYenoEdyD8w==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.1.0.tgz",
"integrity": "sha512-ieiYaEfUr+sNhw/k++dosmZfVA4VIG5bV+G1eGdJSC4FcflqQv0iSIlOLj/RbzRuTu/VrIiNSlwh1esBM3BXUg==",
"dev": true,
"license": "MIT",
"engines": {
+1 -1
View File
@@ -15,7 +15,7 @@
"format:fix": "prettier --write ."
},
"devDependencies": {
"@immich/ui": "^0.25.0",
"@immich/ui": "^0.26.0",
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/kit": "^2.26.1",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
+37 -87
View File
@@ -1,7 +1,19 @@
<script lang="ts">
import { Heading, HStack, Icon, Link, Stack, Text, ThemeSwitcher, VStack } from '@immich/ui';
import { mdiCubeOutline, mdiKey, mdiOfficeBuildingOutline, mdiServerOutline, mdiShoppingOutline } from '@mdi/js';
import { siAndroid, siApple, siDiscord, siGithub, siImmich, siReddit } from 'simple-icons';
import { Constants } from '$lib';
import FooterLink from '$lib/components/FooterLink.svelte';
import { Heading, Stack, Text, ThemeSwitcher, VStack } from '@immich/ui';
import {
mdiBookshelf,
mdiChartGantt,
mdiCubeOutline,
mdiHomeSearchOutline,
mdiKey,
mdiOfficeBuildingOutline,
mdiScriptTextOutline,
mdiServerOutline,
mdiShoppingOutline,
} from '@mdi/js';
import { siAndroid, siApple, siDiscord, siGithub, siReddit } from 'simple-icons';
</script>
<div class="bg-dark/10 rounded-t-3xl mt-16 p-8">
@@ -10,98 +22,36 @@
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-8 place-center">
<Stack>
<Heading size="tiny">Social</Heading>
<Link href="https://github.com/immich-app/immich" target="_blank" rel="noopener noreferrer">
<HStack>
<Icon icon={siGithub} size="1.5em" />
<Text>GitHub</Text>
</HStack>
</Link>
<Link href="https://discord.immich.app/" target="_blank" rel="noopener noreferrer">
<HStack>
<Icon icon={siDiscord} size="1.5em" />
<Text>Discord</Text>
</HStack>
</Link>
<Link href="https://www.reddit.com/r/immich/" target="_blank" rel="noopener noreferrer">
<HStack>
<Icon icon={siReddit.path} size="1.5em" />
<Text>Reddit</Text>
</HStack>
</Link>
<FooterLink href={Constants.Socials.Github} icon={siGithub} text="GitHub" />
<FooterLink href={Constants.Socials.Discord} icon={siDiscord} text="Discord" />
<FooterLink href={Constants.Socials.Reddit} icon={siReddit} text="Reddit" />
</Stack>
<Stack>
<Heading size="tiny">Download</Heading>
<Link
href="https://play.google.com/store/apps/details?id=app.alextran.immich"
target="_blank"
rel="noopener noreferrer"
>
<HStack>
<Icon icon={siAndroid.path} size="1.5em" />
<Text>Android</Text>
</HStack>
</Link>
<Link href="https://apps.apple.com/us/app/immich/id1613945652" target="_blank" rel="noopener noreferrer">
<HStack>
<Icon icon={siApple.path} size="1.5em" />
<Text>iOS</Text>
</HStack>
</Link>
<Link href="https://get.immich.app/docker-compose" target="_blank" rel="noopener noreferrer">
<HStack>
<Icon icon={mdiServerOutline} size="1.5em" />
<Text>Server</Text>
</HStack>
</Link>
</Stack>
<Stack>
<Heading size="tiny">Sites</Heading>
<Link href="https://immich.app" target="_blank" rel="noopener noreferrer">
<HStack>
<Icon icon={siImmich} size="1.5em" />
<Text>Documentation</Text>
</HStack>
</Link>
<Link href="https://my.immich.app" target="_blank" rel="noopener noreferrer">
<HStack>
<Icon icon={siImmich} size="1.5em" />
<Text>My Immich</Text>
</HStack>
</Link>
<Link href="https://api.immich.app" target="_blank" rel="noopener noreferrer">
<HStack>
<Icon icon={mdiCubeOutline} size="1.5em" />
<Text>Immich API</Text>
</HStack>
</Link>
</Stack>
<Stack>
<Heading size="tiny">Support</Heading>
<Link href="https://buy.immich.app" target="_blank" rel="noopener noreferrer">
<HStack>
<Icon icon={mdiKey} size="1.5em" />
<Text>Purchase</Text>
</HStack>
</Link>
<Link href="https://immich.store/" target="_blank" rel="noopener noreferrer">
<HStack>
<Icon icon={mdiShoppingOutline} size="1.5em" />
<Text>Merch</Text>
</HStack>
</Link>
<FooterLink href={Constants.Get.Android} icon={siAndroid} text="Android" />
<FooterLink href={Constants.Get.iOS} icon={siApple} text="iOS" />
<FooterLink href={Constants.Get.DockerCompose} icon={mdiServerOutline} text="Server" />
</Stack>
<Stack>
<Heading size="tiny">Company</Heading>
<Link href="https://futo.org/" target="_blank" rel="noopener noreferrer">
<HStack>
<Icon icon={mdiOfficeBuildingOutline} size="1.5em" />
<Text>FUTO</Text>
</HStack>
</Link>
<FooterLink href={Constants.Socials.Futo} icon={mdiOfficeBuildingOutline} text="FUTO" />
<FooterLink href={Constants.Sites.Buy} icon={mdiKey} text="Purchase" />
<FooterLink href={Constants.Sites.Store} icon={mdiShoppingOutline} text="Merch" />
</Stack>
<Stack>
<Heading size="tiny">Sites</Heading>
<FooterLink href={Constants.Sites.Docs} icon={mdiScriptTextOutline} text="Documentation" />
<FooterLink href={Constants.Sites.My} icon={mdiHomeSearchOutline} text="My Immich" />
<FooterLink href={Constants.Sites.Api} icon={mdiCubeOutline} text="Immich API" />
</Stack>
<Stack>
<Heading size="tiny">Miscellaneous</Heading>
<FooterLink href={Constants.Pages.Roadmap} icon={mdiChartGantt} text="Roadmap" />
<FooterLink href={Constants.Pages.CursedKnowledge} icon={mdiBookshelf} text="Cursed Knowledge" />
</Stack>
</div>
<VStack class="text-center">
+18
View File
@@ -0,0 +1,18 @@
<script lang="ts">
import { HStack, Icon, Link, Text } from '@immich/ui';
type Props = {
href: string;
icon: string | { path: string };
text: string;
};
let { href, icon, text }: Props = $props();
</script>
<Link {href} external>
<HStack>
<Icon {icon} size="1.5em" />
<Text>{text}</Text>
</HStack>
</Link>
+33
View File
@@ -5,3 +5,36 @@ export enum StorageKey {
}
export type Fetch = typeof fetch;
export const Constants = {
Socials: {
Futo: 'https://futo.org/',
Github: 'https://github.com/immich-app/immich',
Discord: 'https://discord.immich.app/',
Reddit: 'https://apps.apple.com/us/app/immich/id1613945652',
},
Get: {
iOS: 'https://get.immich.app/ios',
Android: 'https://get.immich.app/android',
FDroid: 'https://get.immich.app/fdroid',
GithubRelease: 'https://github.com/immich-app/immich/releases/latest',
DockerCompose: 'https://get.immich.app/docker-compose',
},
Sites: {
Api: 'https://api.immich.app/',
Demo: 'https://demo.immich.app/',
My: 'https://my.immich.app/',
Buy: 'https://buy.immich.app/',
Get: 'https://get.immich.app/',
Docs: 'https://docs.immich.app/',
Store: 'https://immich.store/',
},
Pages: {
CursedKnowledge: 'https://immich.app/cursed-knowledge',
Roadmap: 'https://immich.app/roadmap',
},
Npm: {
Sdk: 'https://www.npmjs.com/package/@immich/sdk',
},
};