mirror of
https://github.com/immich-app/static-pages.git
synced 2026-09-30 13:23:05 +08:00
feat: next improvements (#180)
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Generated
+8
-8
@@ -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
@@ -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",
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
@@ -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',
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user