mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
feat: docs.immich.app (#21819)
Co-authored-by: Jason Rasmussen <jason@rasm.me>
This commit is contained in:
parent
e7b57fc2f6
commit
37a3784d80
67 changed files with 297 additions and 3133 deletions
|
|
@ -11,7 +11,7 @@ export default function VersionSwitcher(): JSX.Element {
|
|||
useEffect(() => {
|
||||
async function getVersions() {
|
||||
try {
|
||||
let baseUrl = 'https://immich.app';
|
||||
let baseUrl = 'https://docs.immich.app';
|
||||
if (window.location.origin === 'http://localhost:3005') {
|
||||
baseUrl = window.location.origin;
|
||||
}
|
||||
|
|
@ -21,12 +21,13 @@ export default function VersionSwitcher(): JSX.Element {
|
|||
const archiveVersions = await response.json();
|
||||
|
||||
const allVersions = [
|
||||
{ label: 'Next', url: 'https://main.preview.immich.app' },
|
||||
{ label: 'Latest', url: 'https://immich.app' },
|
||||
{ label: 'Next', url: 'https://docs.main.preview.immich.app' },
|
||||
{ label: 'Latest', url: 'https://docs.immich.app' },
|
||||
...archiveVersions,
|
||||
].map(({ label, url }) => ({
|
||||
].map(({ label, url, rootPath }) => ({
|
||||
label,
|
||||
url: new URL(url),
|
||||
rootPath,
|
||||
}));
|
||||
setVersions(allVersions);
|
||||
|
||||
|
|
@ -50,12 +51,18 @@ export default function VersionSwitcher(): JSX.Element {
|
|||
className="version-switcher-34ab39"
|
||||
label={activeLabel}
|
||||
mobile={windowSize === 'mobile'}
|
||||
items={versions.map(({ label, url }) => ({
|
||||
label,
|
||||
to: new URL(location.pathname + location.search + location.hash, url).href,
|
||||
target: '_self',
|
||||
className: label === activeLabel ? 'dropdown__link--active menu__link--active' : '', // workaround because React Router `<NavLink>` only supports using URL path for checking if active: https://v5.reactrouter.com/web/api/NavLink/isactive-func
|
||||
}))}
|
||||
items={versions.map(({ label, url, rootPath }) => {
|
||||
let path = location.pathname + location.search + location.hash;
|
||||
if (rootPath && !path.startsWith(rootPath)) {
|
||||
path = rootPath + path;
|
||||
}
|
||||
return {
|
||||
label,
|
||||
to: new URL(path, url).href,
|
||||
target: '_self',
|
||||
className: label === activeLabel ? 'dropdown__link--active menu__link--active' : '', // workaround because React Router `<NavLink>` only supports using URL path for checking if active: https://v5.reactrouter.com/web/api/NavLink/isactive-func
|
||||
};
|
||||
})}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,273 +0,0 @@
|
|||
import {
|
||||
mdiBug,
|
||||
mdiCalendarToday,
|
||||
mdiCrosshairsOff,
|
||||
mdiCrop,
|
||||
mdiDatabase,
|
||||
mdiLeadPencil,
|
||||
mdiLockOff,
|
||||
mdiLockOutline,
|
||||
mdiMicrosoftWindows,
|
||||
mdiSecurity,
|
||||
mdiSpeedometerSlow,
|
||||
mdiTrashCan,
|
||||
mdiWeb,
|
||||
mdiWrap,
|
||||
mdiCloudKeyOutline,
|
||||
mdiRegex,
|
||||
mdiCodeJson,
|
||||
mdiClockOutline,
|
||||
mdiAccountOutline,
|
||||
mdiRestart,
|
||||
} from '@mdi/js';
|
||||
import Layout from '@theme/Layout';
|
||||
import React from 'react';
|
||||
import { Timeline, Item as TimelineItem } from '../components/timeline';
|
||||
|
||||
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),
|
||||
},
|
||||
];
|
||||
|
||||
export default function CursedKnowledgePage(): JSX.Element {
|
||||
return (
|
||||
<Layout title="Cursed Knowledge" description="Things we wish we didn't know">
|
||||
<section className="my-8">
|
||||
<h1 className="md:text-6xl text-center mb-10 text-immich-primary dark:text-immich-dark-primary px-2">
|
||||
Cursed Knowledge
|
||||
</h1>
|
||||
<p className="text-center text-xl px-2">
|
||||
Cursed knowledge we have learned as a result of building Immich that we wish we never knew.
|
||||
</p>
|
||||
<div className="flex justify-around mt-8 w-full max-w-full">
|
||||
<Timeline
|
||||
items={items
|
||||
.sort((a, b) => b.date.getTime() - a.date.getTime())
|
||||
.map((item) => ({ ...item, getDateLabel: withLanguage(item.date) }))}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,123 +1,5 @@
|
|||
import React from 'react';
|
||||
import Link from '@docusaurus/Link';
|
||||
import Layout from '@theme/Layout';
|
||||
import { discordPath, discordViewBox } from '@site/src/components/svg-paths';
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
import Icon from '@mdi/react';
|
||||
|
||||
function HomepageHeader() {
|
||||
return (
|
||||
<header>
|
||||
<div className="top-[calc(12%)] md:top-[calc(30%)] h-screen w-full absolute -z-10">
|
||||
<img src={'img/immich-logo.svg'} className="h-[110%] w-[110%] mb-2 antialiased -z-10" alt="Immich logo" />
|
||||
<div className="w-full h-[120vh] absolute left-0 top-0 backdrop-blur-3xl bg-immich-bg/40 dark:bg-transparent"></div>
|
||||
</div>
|
||||
<section className="text-center pt-12 sm:pt-24 bg-immich-bg/50 dark:bg-immich-dark-bg/80">
|
||||
<a href="https://futo.org" target="_blank" rel="noopener noreferrer">
|
||||
<ThemedImage
|
||||
sources={{ dark: 'img/logomark-dark-with-futo.svg', light: 'img/logomark-light-with-futo.svg' }}
|
||||
className="h-[125px] w-[125px] mb-2 antialiased rounded-none"
|
||||
alt="Immich logo"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<div className="mt-8">
|
||||
<p className="text-3xl md:text-5xl sm:leading-tight mb-1 font-extrabold text-black/90 dark:text-white px-4">
|
||||
Self-hosted{' '}
|
||||
<span className="text-immich-primary dark:text-immich-dark-primary">
|
||||
photo and <span className="block"></span>
|
||||
video management{' '}
|
||||
</span>
|
||||
solution<span className="block"></span>
|
||||
</p>
|
||||
|
||||
<p className="max-w-1/4 m-auto mt-4 px-4 text-lg text-gray-700 dark:text-gray-100">
|
||||
Easily back up, organize, and manage your photos on your own server. Immich helps you
|
||||
<span className="sm:block"></span> browse, search and organize your photos and videos with ease, without
|
||||
sacrificing your privacy.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row place-items-center place-content-center mt-9 gap-4 ">
|
||||
<Link
|
||||
className="flex place-items-center place-content-center py-3 px-8 border bg-immich-primary dark:bg-immich-dark-primary rounded-xl no-underline hover:no-underline text-white hover:text-gray-50 dark:text-immich-dark-bg font-bold"
|
||||
to="docs/overview/quick-start"
|
||||
>
|
||||
Get Started
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
className="flex place-items-center place-content-center py-3 px-8 border bg-white/90 dark:bg-gray-300 rounded-xl hover:no-underline text-immich-primary dark:text-immich-dark-bg font-bold"
|
||||
to="https://demo.immich.app/"
|
||||
>
|
||||
Open Demo
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="my-8 flex gap-1 font-medium place-items-center place-content-center text-immich-primary dark:text-immich-dark-primary">
|
||||
<Icon
|
||||
path={discordPath}
|
||||
viewBox={discordViewBox} /* viewBox may show an error in your IDE but it is normal. */
|
||||
size={1}
|
||||
/>
|
||||
<Link to="https://discord.immich.app/">Join our Discord</Link>
|
||||
</div>
|
||||
<ThemedImage
|
||||
sources={{ dark: '/img/screenshot-dark.webp', light: '/img/screenshot-light.webp' }}
|
||||
alt="screenshots"
|
||||
className="w-[95%] lg:w-[85%] xl:w-[70%] 2xl:w-[60%] "
|
||||
/>
|
||||
<div className="mx-[25%] m-auto my-14 md:my-28">
|
||||
<hr className="border bg-gray-500 dark:bg-gray-400" />
|
||||
</div>
|
||||
<ThemedImage
|
||||
sources={{ dark: 'img/logomark-dark.svg', light: 'img/logomark-light.svg' }}
|
||||
className="h-[115px] w-[115px] mb-2 antialiased rounded-none"
|
||||
alt="Immich logo"
|
||||
/>
|
||||
<div>
|
||||
<p className="font-bold text-2xl md:text-5xl ">Download the mobile app</p>
|
||||
<p className="text-lg">
|
||||
Download the Immich app and start backing up your photos and videos securely to your own server
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row place-items-center place-content-center mt-4 gap-1">
|
||||
<div className="h-24">
|
||||
<a href="https://play.google.com/store/apps/details?id=app.alextran.immich">
|
||||
<img className="h-24" alt="Get it on Google Play" src="/img/google-play-badge.png" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="h-24">
|
||||
<a href="https://apps.apple.com/sg/app/immich/id1613945652">
|
||||
<img className="h-24 sm:p-3.5 p-3" alt="Download on the App Store" src="/img/ios-app-store-badge.svg" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="h-24">
|
||||
<a href="https://github.com/immich-app/immich/releases/latest">
|
||||
<img className="h-24 sm:p-3.5 p-3" alt="Download APK" src="/img/download-apk-github.svg" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<ThemedImage
|
||||
sources={{ dark: '/img/app-qr-code-dark.svg', light: '/img/app-qr-code-light.svg' }}
|
||||
alt="app qr code"
|
||||
width={'150px'}
|
||||
className="shadow-lg p-3 my-8 dark:bg-immich-dark-bg "
|
||||
/>
|
||||
</section>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
import { Redirect } from '@docusaurus/router';
|
||||
|
||||
export default function Home(): JSX.Element {
|
||||
return (
|
||||
<Layout title="Home" description="Self-hosted photo and video management solution" noFooter={true}>
|
||||
<HomepageHeader />
|
||||
<div className="flex flex-col place-items-center text-center place-content-center dark:bg-immich-dark-bg py-8">
|
||||
<p>This project is available under GNU AGPL v3 license.</p>
|
||||
<p className="text-sm">Privacy should not be a luxury</p>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
return <Redirect to="/overview/welcome" />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,944 +0,0 @@
|
|||
import {
|
||||
mdiAccountGroup,
|
||||
mdiAccountGroupOutline,
|
||||
mdiAndroid,
|
||||
mdiAppleIos,
|
||||
mdiArchiveOutline,
|
||||
mdiBash,
|
||||
mdiBookSearchOutline,
|
||||
mdiBookmark,
|
||||
mdiCakeVariant,
|
||||
mdiCameraBurst,
|
||||
mdiChartBoxMultipleOutline,
|
||||
mdiCheckAll,
|
||||
mdiCheckboxMarked,
|
||||
mdiCloudUploadOutline,
|
||||
mdiCollage,
|
||||
mdiContentDuplicate,
|
||||
mdiCrop,
|
||||
mdiDevices,
|
||||
mdiEmailOutline,
|
||||
mdiExpansionCard,
|
||||
mdiEyeOutline,
|
||||
mdiEyeRefreshOutline,
|
||||
mdiFaceMan,
|
||||
mdiFaceManOutline,
|
||||
mdiFile,
|
||||
mdiFileSearch,
|
||||
mdiFlash,
|
||||
mdiFolder,
|
||||
mdiFolderMultiple,
|
||||
mdiForum,
|
||||
mdiHandshakeOutline,
|
||||
mdiHeart,
|
||||
mdiHistory,
|
||||
mdiImage,
|
||||
mdiImageAlbum,
|
||||
mdiImageEdit,
|
||||
mdiImageMultipleOutline,
|
||||
mdiImageSearch,
|
||||
mdiKeyboardSettingsOutline,
|
||||
mdiLicense,
|
||||
mdiLockOutline,
|
||||
mdiMagnify,
|
||||
mdiMagnifyScan,
|
||||
mdiMap,
|
||||
mdiMaterialDesign,
|
||||
mdiMatrix,
|
||||
mdiMerge,
|
||||
mdiMonitor,
|
||||
mdiMotionPlayOutline,
|
||||
mdiPalette,
|
||||
mdiPanVertical,
|
||||
mdiPartyPopper,
|
||||
mdiPencil,
|
||||
mdiRaw,
|
||||
mdiRocketLaunch,
|
||||
mdiRotate360,
|
||||
mdiScaleBalance,
|
||||
mdiSecurity,
|
||||
mdiServer,
|
||||
mdiShare,
|
||||
mdiShareAll,
|
||||
mdiShareCircle,
|
||||
mdiStar,
|
||||
mdiStarOutline,
|
||||
mdiTableKey,
|
||||
mdiTag,
|
||||
mdiTagMultiple,
|
||||
mdiText,
|
||||
mdiThemeLightDark,
|
||||
mdiTrashCanOutline,
|
||||
mdiVectorCombine,
|
||||
mdiFolderSync,
|
||||
mdiFaceRecognition,
|
||||
mdiVideo,
|
||||
mdiWeb,
|
||||
mdiDatabaseOutline,
|
||||
mdiLinkEdit,
|
||||
mdiTagFaces,
|
||||
mdiMovieOpenPlayOutline,
|
||||
mdiCast,
|
||||
} from '@mdi/js';
|
||||
import Layout from '@theme/Layout';
|
||||
import React from 'react';
|
||||
import { Item, Timeline } from '../components/timeline';
|
||||
|
||||
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),
|
||||
'v1.113.0': new Date(2024, 7, 30),
|
||||
'v1.112.0': new Date(2024, 7, 14),
|
||||
'v1.111.0': new Date(2024, 6, 26),
|
||||
'v1.110.0': new Date(2024, 5, 11),
|
||||
'v1.109.0': new Date(2024, 6, 18),
|
||||
'v1.106.1': new Date(2024, 5, 11),
|
||||
'v1.104.0': new Date(2024, 4, 13),
|
||||
'v1.103.0': new Date(2024, 3, 29),
|
||||
'v1.102.0': new Date(2024, 3, 15),
|
||||
'v1.99.0': new Date(2024, 2, 20),
|
||||
'v1.98.0': new Date(2024, 2, 7),
|
||||
'v1.95.0': new Date(2024, 1, 20),
|
||||
'v1.94.0': new Date(2024, 0, 31),
|
||||
'v1.93.0': new Date(2024, 0, 19),
|
||||
'v1.91.0': new Date(2023, 11, 15),
|
||||
'v1.90.0': new Date(2023, 11, 7),
|
||||
'v1.88.0': new Date(2023, 10, 20),
|
||||
'v1.84.0': new Date(2023, 10, 1),
|
||||
'v1.83.0': new Date(2023, 9, 28),
|
||||
'v1.82.0': new Date(2023, 9, 17),
|
||||
'v1.79.0': new Date(2023, 8, 21),
|
||||
'v1.76.0': new Date(2023, 7, 29),
|
||||
'v1.75.0': new Date(2023, 7, 26),
|
||||
'v1.72.0': new Date(2023, 7, 6),
|
||||
'v1.71.0': new Date(2023, 6, 29),
|
||||
'v1.69.0': new Date(2023, 6, 23),
|
||||
'v1.68.0': new Date(2023, 6, 20),
|
||||
'v1.67.0': new Date(2023, 6, 14),
|
||||
'v1.66.0': new Date(2023, 6, 4),
|
||||
'v1.65.0': new Date(2023, 5, 30),
|
||||
'v1.63.0': new Date(2023, 5, 24),
|
||||
'v1.61.0': new Date(2023, 5, 16),
|
||||
'v1.58.0': new Date(2023, 4, 28),
|
||||
'v1.57.0': new Date(2023, 4, 23),
|
||||
'v1.56.0': new Date(2023, 4, 18),
|
||||
'v1.55.0': new Date(2023, 4, 9),
|
||||
'v1.54.0': new Date(2023, 3, 18),
|
||||
'v1.52.0': new Date(2023, 2, 29),
|
||||
'v1.51.0': new Date(2023, 2, 20),
|
||||
'v1.48.0': new Date(2023, 1, 21),
|
||||
'v1.47.0': new Date(2023, 1, 13),
|
||||
'v1.46.0': new Date(2023, 1, 9),
|
||||
'v1.43.0': new Date(2023, 1, 3),
|
||||
'v1.41.0': new Date(2023, 0, 10),
|
||||
'v1.39.0': new Date(2022, 11, 19),
|
||||
'v1.36.0': new Date(2022, 10, 20),
|
||||
'v1.33.1': new Date(2022, 9, 26),
|
||||
'v1.32.0': new Date(2022, 9, 14),
|
||||
'v1.27.0': new Date(2022, 8, 6),
|
||||
'v1.24.0': new Date(2022, 7, 19),
|
||||
'v1.10.0': new Date(2022, 4, 29),
|
||||
'v1.7.0': new Date(2022, 3, 24),
|
||||
'v1.3.0': new Date(2022, 2, 22),
|
||||
'v1.2.0': new Date(2022, 1, 8),
|
||||
} as const;
|
||||
|
||||
const weirdTags = {
|
||||
'v1.41.0': 'v1.41.1_64-dev',
|
||||
'v1.39.0': 'v1.39.0_61-dev',
|
||||
'v1.36.0': 'v1.36.0_55-dev',
|
||||
'v1.33.1': 'v1.33.0_52-dev',
|
||||
'v1.32.0': 'v1.32.0_50-dev',
|
||||
'v1.27.0': 'v1.27.0_37-dev',
|
||||
'v1.24.0': 'v1.24.0_34-dev',
|
||||
'v1.10.0': 'v1.10.0_15-dev',
|
||||
'v1.7.0': 'v1.7.0_11-dev ',
|
||||
'v1.3.0': 'v1.3.0-dev ',
|
||||
'v1.2.0': 'v0.2-dev ',
|
||||
};
|
||||
|
||||
const title = 'Roadmap';
|
||||
const description = 'A list of future plans and goals, as well as past achievements and milestones.';
|
||||
|
||||
const withLanguage = (date: Date) => (language: string) => date.toLocaleDateString(language);
|
||||
|
||||
type Base = { icon: string; iconColor?: React.CSSProperties['color']; title: string; description: string };
|
||||
const withRelease = ({
|
||||
icon,
|
||||
iconColor,
|
||||
title,
|
||||
description,
|
||||
release: version,
|
||||
}: Base & { release: keyof typeof releases }) => {
|
||||
return {
|
||||
icon,
|
||||
iconColor: iconColor ?? 'gray',
|
||||
title,
|
||||
description,
|
||||
link: {
|
||||
url: `https://github.com/immich-app/immich/releases/tag/${weirdTags[version] ?? version}`,
|
||||
text: version,
|
||||
},
|
||||
getDateLabel: withLanguage(releases[version]),
|
||||
};
|
||||
};
|
||||
|
||||
const roadmap: Item[] = [
|
||||
{
|
||||
done: false,
|
||||
icon: mdiFlash,
|
||||
iconColor: 'gold',
|
||||
title: 'Workflows',
|
||||
description: 'Automate tasks with workflows',
|
||||
getDateLabel: () => 'Planned for 2025',
|
||||
},
|
||||
{
|
||||
done: false,
|
||||
icon: mdiImageEdit,
|
||||
iconColor: 'rebeccapurple',
|
||||
title: 'Basic editor',
|
||||
description: 'Basic photo editing capabilities',
|
||||
getDateLabel: () => 'Planned for 2025',
|
||||
},
|
||||
{
|
||||
done: false,
|
||||
icon: mdiRocketLaunch,
|
||||
iconColor: 'indianred',
|
||||
title: 'Stable release',
|
||||
description: 'Immich goes stable',
|
||||
getDateLabel: () => 'Planned for 2025',
|
||||
},
|
||||
{
|
||||
done: false,
|
||||
icon: mdiCloudUploadOutline,
|
||||
iconColor: 'cornflowerblue',
|
||||
title: 'Better background backups',
|
||||
description: 'Rework background backups to be more reliable',
|
||||
getDateLabel: () => 'Planned for 2025',
|
||||
},
|
||||
{
|
||||
done: false,
|
||||
icon: mdiCameraBurst,
|
||||
iconColor: 'rebeccapurple',
|
||||
title: 'Auto stacking',
|
||||
description: 'Auto stack burst photos',
|
||||
getDateLabel: () => 'Planned for 2025',
|
||||
},
|
||||
];
|
||||
|
||||
const milestones: Item[] = [
|
||||
{
|
||||
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',
|
||||
title: 'Automatic URL switching',
|
||||
description: 'The mobile app now supports automatic switching between different server URLs',
|
||||
release: 'v1.122.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiMovieOpenPlayOutline,
|
||||
iconColor: 'darksalmon',
|
||||
title: 'Native video player',
|
||||
description: 'HDR videos are now fully supported using the Immich native video player',
|
||||
release: 'v1.122.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiDatabaseOutline,
|
||||
iconColor: 'brown',
|
||||
title: 'Automatic database dumps',
|
||||
description: 'Database dumps are now integrated into the Immich server',
|
||||
release: 'v1.120.0',
|
||||
}),
|
||||
{
|
||||
icon: mdiStar,
|
||||
iconColor: 'gold',
|
||||
title: '50,000 Stars',
|
||||
description: 'Reached 50K Stars on GitHub!',
|
||||
getDateLabel: withLanguage(new Date(2024, 10, 1)),
|
||||
},
|
||||
withRelease({
|
||||
icon: mdiFaceRecognition,
|
||||
title: 'Metadata Face Import',
|
||||
description: 'Read face metadata in Digikam format during import',
|
||||
release: 'v1.114.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiTagMultiple,
|
||||
iconColor: 'orange',
|
||||
title: 'Tags',
|
||||
description: 'Tag your photos and videos',
|
||||
release: 'v1.113.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiFolderSync,
|
||||
iconColor: 'green',
|
||||
title: 'Album sync (mobile)',
|
||||
description: 'Sync or mirror an album from your phone to the Immich server',
|
||||
release: 'v1.113.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiFolderMultiple,
|
||||
iconColor: 'brown',
|
||||
title: 'Folders view',
|
||||
description: 'Browse your photos and videos in their folder structure',
|
||||
release: 'v1.113.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiPalette,
|
||||
title: 'Theming (mobile)',
|
||||
description: 'Pick a primary color for the mobile app',
|
||||
release: 'v1.112.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiStarOutline,
|
||||
iconColor: 'gold',
|
||||
title: 'Star rating',
|
||||
description: 'Rate your photos and videos',
|
||||
release: 'v1.112.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiCrop,
|
||||
iconColor: 'royalblue',
|
||||
title: 'Editor (mobile)',
|
||||
description: 'Crop and rotate on mobile',
|
||||
release: 'v1.111.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiMap,
|
||||
iconColor: 'green',
|
||||
title: 'Deploy tiles.immich.cloud',
|
||||
description: 'Dedicated tile server for Immich',
|
||||
release: 'v1.111.0',
|
||||
}),
|
||||
{
|
||||
icon: mdiStar,
|
||||
iconColor: 'gold',
|
||||
title: '40,000 Stars',
|
||||
description: 'Reached 40K Stars on GitHub!',
|
||||
getDateLabel: withLanguage(new Date(2024, 6, 21)),
|
||||
},
|
||||
withRelease({
|
||||
icon: mdiShare,
|
||||
title: 'Deploy my.immich.app',
|
||||
description: 'Url router for immich links',
|
||||
release: 'v1.109.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiLicense,
|
||||
iconColor: 'gold',
|
||||
title: 'Supporter Badge',
|
||||
description: 'The option to buy Immich to support its development!',
|
||||
release: 'v1.109.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiHistory,
|
||||
title: 'Versioned documentation',
|
||||
description: 'View documentation as it was at the time of past releases',
|
||||
release: 'v1.106.1',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiWeb,
|
||||
iconColor: 'royalblue',
|
||||
title: 'Web translations',
|
||||
description: 'Translate the web application to multiple languages',
|
||||
release: 'v1.106.1',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiContentDuplicate,
|
||||
title: 'Similar image detection',
|
||||
description: "Detect duplicate assets that aren't exactly identical",
|
||||
release: 'v1.106.1',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiVectorCombine,
|
||||
title: 'Container consolidation',
|
||||
description:
|
||||
'The microservices container can be run as a worker within the server image, allowing us to remove it from the default stack.',
|
||||
release: 'v1.106.1',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiPencil,
|
||||
iconColor: 'saddlebrown',
|
||||
title: 'Read-write external libraries',
|
||||
description: 'Edit, update, and delete files in external libraries',
|
||||
release: 'v1.104.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiEmailOutline,
|
||||
iconColor: 'crimson',
|
||||
title: 'Email notifications',
|
||||
description: 'Send emails for important events',
|
||||
release: 'v1.104.0',
|
||||
}),
|
||||
{
|
||||
icon: mdiHandshakeOutline,
|
||||
iconColor: 'magenta',
|
||||
title: 'Immich joins FUTO!',
|
||||
description: 'Joined Futo and Immich core team goes full-time',
|
||||
getDateLabel: withLanguage(new Date(2024, 4, 1)),
|
||||
},
|
||||
withRelease({
|
||||
icon: mdiEyeOutline,
|
||||
iconColor: 'darkslategray',
|
||||
title: 'Read-only albums',
|
||||
description: 'Share albums with other users as read-only',
|
||||
release: 'v1.103.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiBookmark,
|
||||
iconColor: 'orangered',
|
||||
title: 'Permanent URLs (Web)',
|
||||
description: 'Assets on the web now have permanent URLs',
|
||||
release: 'v1.103.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiStar,
|
||||
iconColor: 'gold',
|
||||
title: '30,000 Stars',
|
||||
description: 'Reached 30K Stars on GitHub!',
|
||||
release: 'v1.102.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiChartBoxMultipleOutline,
|
||||
iconColor: 'mediumvioletred',
|
||||
title: 'OpenTelemetry metrics',
|
||||
description: 'OpenTelemetry metrics for local evaluation and advanced debugging',
|
||||
release: 'v1.99.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: 'immich',
|
||||
title: 'New logo',
|
||||
description: 'Immich got its new logo',
|
||||
release: 'v1.98.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiMagnifyScan,
|
||||
title: 'Search enhancement with advanced filters',
|
||||
description: 'Advanced search with filters by date, location and more',
|
||||
release: 'v1.95.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiScaleBalance,
|
||||
iconColor: 'gold',
|
||||
title: 'AGPL License',
|
||||
description: 'Immich switches to AGPLv3 license',
|
||||
release: 'v1.95.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiEyeRefreshOutline,
|
||||
title: 'Library watching',
|
||||
description: 'Automatically import files in external libraries when the operating system detects changes.',
|
||||
release: 'v1.94.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiExpansionCard,
|
||||
iconColor: 'green',
|
||||
title: 'GPU acceleration for machine-learning',
|
||||
description: 'Hardware acceleration support for Nvidia and Intel devices through CUDA and OpenVINO.',
|
||||
release: 'v1.94.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiAccountGroupOutline,
|
||||
iconColor: 'gray',
|
||||
title: '250 unique contributors',
|
||||
description: '250 amazing people contributed to Immich',
|
||||
release: 'v1.93.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiMatrix,
|
||||
title: 'Search improvement with pgvecto.rs',
|
||||
description: 'Moved the search from typesense to pgvecto.rs',
|
||||
release: 'v1.91.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiPencil,
|
||||
iconColor: 'saddlebrown',
|
||||
title: 'Edit metadata',
|
||||
description: "Edit a photo or video's date, time, hours, timezone, and GPS information",
|
||||
release: 'v1.90.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiVectorCombine,
|
||||
title: 'Container consolidation',
|
||||
description:
|
||||
'The serving of the web app is merged into the server image, allowing us to remove two containers from the stack.',
|
||||
release: 'v1.88.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiBash,
|
||||
iconColor: 'gray',
|
||||
title: 'CLI v2',
|
||||
description: 'Version 2 of the Immich CLI is released, replacing the legacy v1 CLI.',
|
||||
release: 'v1.88.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiForum,
|
||||
iconColor: 'dodgerblue',
|
||||
title: 'Activity',
|
||||
description: 'Comment a photo or a video in a shared album',
|
||||
release: 'v1.84.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiStar,
|
||||
iconColor: 'gold',
|
||||
title: '20,000 Stars',
|
||||
description: 'Reached 20K Stars on GitHub!',
|
||||
release: 'v1.83.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiCameraBurst,
|
||||
iconColor: 'rebeccapurple',
|
||||
title: 'Stack assets',
|
||||
description: 'Manual asset stacking for grouping and hiding related assets in the main timeline.',
|
||||
release: 'v1.83.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiPalette,
|
||||
iconColor: 'magenta',
|
||||
title: 'Custom theme',
|
||||
description: 'Apply your custom CSS for modifying fonts, colors, and styles in the web application.',
|
||||
release: 'v1.83.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiTrashCanOutline,
|
||||
iconColor: 'brown',
|
||||
title: 'Trash feature',
|
||||
description: 'Trash, restore from trash, and automatically empty the recycle bin after 30 days.',
|
||||
release: 'v1.82.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiBookSearchOutline,
|
||||
title: 'External libraries',
|
||||
description: 'Automatically import media into Immich based on imports paths and ignore patterns.',
|
||||
release: 'v1.79.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiMap,
|
||||
iconColor: 'darksalmon',
|
||||
title: 'Map view (mobile)',
|
||||
description: 'Heat map implementation in the mobile app.',
|
||||
release: 'v1.76.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiFile,
|
||||
iconColor: 'lightblue',
|
||||
title: 'Configuration file',
|
||||
description: 'Auto-configure an Immich installation via a configuration file.',
|
||||
release: 'v1.75.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiMonitor,
|
||||
iconColor: 'darkcyan',
|
||||
title: 'Slideshow mode (web)',
|
||||
description: 'Start a full-screen slideshow from an Album on the web.',
|
||||
release: 'v1.75.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiServer,
|
||||
iconColor: 'lightskyblue',
|
||||
title: 'Hardware transcoding',
|
||||
description: 'Support hardware acceleration (QuickSync, VAAPI, and Nvidia) for video transcoding.',
|
||||
release: 'v1.72.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiImageAlbum,
|
||||
iconColor: 'olivedrab',
|
||||
title: 'View albums via time buckets',
|
||||
description: 'Upgrade albums to use time buckets, an optimized virtual viewport.',
|
||||
release: 'v1.72.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiImageAlbum,
|
||||
iconColor: 'olivedrab',
|
||||
title: 'Album description',
|
||||
description: 'Save an album description.',
|
||||
release: 'v1.72.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiRotate360,
|
||||
title: '360° Photos (web)',
|
||||
description: 'View 360° Photos on the web.',
|
||||
release: 'v1.71.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiMotionPlayOutline,
|
||||
title: 'Android motion photos',
|
||||
description: 'Add support for Android Motion Photos.',
|
||||
release: 'v1.69.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiFaceManOutline,
|
||||
iconColor: 'mistyrose',
|
||||
title: 'Show/hide faces',
|
||||
description: 'Add the options to show or hide faces.',
|
||||
release: 'v1.68.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiMerge,
|
||||
iconColor: 'forestgreen',
|
||||
title: 'Merge faces',
|
||||
description: 'Add the ability to merge multiple faces together.',
|
||||
release: 'v1.67.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiImage,
|
||||
iconColor: 'rebeccapurple',
|
||||
title: 'Feature photo',
|
||||
description: 'Add the option to change the feature photo for a person.',
|
||||
release: 'v1.66.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiKeyboardSettingsOutline,
|
||||
iconColor: 'darkslategray',
|
||||
title: 'Multi-select via SHIFT',
|
||||
description: 'Add the option to multi-select while holding SHIFT.',
|
||||
release: 'v1.66.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiImageMultipleOutline,
|
||||
iconColor: 'rebeccapurple',
|
||||
title: 'Memories (mobile)',
|
||||
description: 'View "On this day..." memories in the mobile app.',
|
||||
release: 'v1.65.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiFaceMan,
|
||||
iconColor: 'mistyrose',
|
||||
title: 'Facial recognition (mobile)',
|
||||
description: 'View detected faces in the mobile app.',
|
||||
release: 'v1.63.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiImageMultipleOutline,
|
||||
iconColor: 'rebeccapurple',
|
||||
title: 'Memories (web)',
|
||||
description: 'View pictures taken in past years on this day on the web.',
|
||||
release: 'v1.61.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiCollage,
|
||||
iconColor: 'deeppink',
|
||||
title: 'Justified layout (web)',
|
||||
description: 'Implement justified layout (collage) on the web.',
|
||||
release: 'v1.61.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiRaw,
|
||||
title: 'RAW file formats',
|
||||
description: 'Support for RAW file formats.',
|
||||
release: 'v1.61.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiShareAll,
|
||||
iconColor: 'darkturquoise',
|
||||
title: 'Partner sharing (mobile)',
|
||||
description: 'View shared partner photos in the mobile app.',
|
||||
release: 'v1.58.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiFile,
|
||||
iconColor: 'lightblue',
|
||||
title: 'XMP sidecar',
|
||||
description: 'Attach XMP sidecar files to assets.',
|
||||
release: 'v1.58.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiFolder,
|
||||
iconColor: 'brown',
|
||||
title: 'Custom storage label',
|
||||
description: 'Replace the user UUID in the storage template with a custom label.',
|
||||
release: 'v1.57.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiShareCircle,
|
||||
title: 'Partner sharing',
|
||||
description: 'Share your entire collection with another user.',
|
||||
release: 'v1.56.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiFaceMan,
|
||||
iconColor: 'mistyrose',
|
||||
title: 'Facial recognition',
|
||||
description: 'Detect faces in pictures and cluster them together as people, which can be named.',
|
||||
release: 'v1.56.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiMap,
|
||||
iconColor: 'darksalmon',
|
||||
title: 'Map view (web)',
|
||||
description: 'View a global map, with clusters of photos based on corresponding GPS data.',
|
||||
release: 'v1.55.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiDevices,
|
||||
iconColor: 'slategray',
|
||||
title: 'Manage auth devices',
|
||||
description: 'Manage logged-in devices and revoke access from User Settings.',
|
||||
release: 'v1.55.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiStar,
|
||||
iconColor: 'gold',
|
||||
title: '10,000 Stars',
|
||||
description: 'Reached 10K stars on GitHub!',
|
||||
release: 'v1.54.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiText,
|
||||
title: 'Asset descriptions',
|
||||
description: 'Save an asset description',
|
||||
release: 'v1.54.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiArchiveOutline,
|
||||
title: 'Archiving',
|
||||
description: 'Remove assets from the main timeline by archiving them.',
|
||||
release: 'v1.54.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiDevices,
|
||||
iconColor: 'slategray',
|
||||
title: 'Responsive web app',
|
||||
description: 'Optimize the web app for small screen.',
|
||||
release: 'v1.54.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiFileSearch,
|
||||
iconColor: 'brown',
|
||||
title: 'Search by metadata',
|
||||
description: 'Search images by filename, description, tagged people, make, model, and other metadata.',
|
||||
release: 'v1.52.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiImageSearch,
|
||||
iconColor: 'rebeccapurple',
|
||||
title: 'CLIP search',
|
||||
description: 'Search images with free-form text like "Sunset at the beach".',
|
||||
release: 'v1.51.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiMagnify,
|
||||
iconColor: 'lightblue',
|
||||
title: 'Explore page',
|
||||
description: 'View tagged places, object, and people.',
|
||||
release: 'v1.51.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiAppleIos,
|
||||
title: 'iOS background uploads',
|
||||
description: 'Automatically backup pictures in the background on iOS.',
|
||||
release: 'v1.48.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiMotionPlayOutline,
|
||||
title: 'Auto-Link live photos',
|
||||
description: 'Automatically link live photos, even when uploaded as separate files.',
|
||||
release: 'v1.48.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiMaterialDesign,
|
||||
iconColor: 'blue',
|
||||
title: 'Material design 3 (mobile)',
|
||||
description: 'Upgrade the mobile app to Material Design 3.',
|
||||
release: 'v1.47.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiHeart,
|
||||
iconColor: 'red',
|
||||
title: 'Favorites (mobile)',
|
||||
description: 'Show favorites on the mobile app.',
|
||||
release: 'v1.46.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiCakeVariant,
|
||||
iconColor: 'deeppink',
|
||||
title: 'Immich turns 1',
|
||||
description: 'Immich is officially one year old.',
|
||||
release: 'v1.43.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiHeart,
|
||||
iconColor: 'red',
|
||||
title: 'Favorites page (web)',
|
||||
description: 'Favorite and view favorites on the web.',
|
||||
release: 'v1.43.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiShareCircle,
|
||||
title: 'Public share links',
|
||||
description: 'Share photos and albums publicly via a shared link.',
|
||||
release: 'v1.41.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiFolder,
|
||||
iconColor: 'lightblue',
|
||||
title: 'User-defined storage structure',
|
||||
description: 'Support custom storage structures.',
|
||||
release: 'v1.39.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiMotionPlayOutline,
|
||||
title: 'iOS live photos',
|
||||
description: 'Backup and display iOS Live Photos.',
|
||||
release: 'v1.36.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiSecurity,
|
||||
iconColor: 'green',
|
||||
title: 'OAuth integration',
|
||||
description: 'Support OAuth2 and OIDC capable identity providers.',
|
||||
release: 'v1.36.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiWeb,
|
||||
iconColor: 'royalblue',
|
||||
title: 'Documentation site',
|
||||
description: 'Release an official documentation website.',
|
||||
release: 'v1.33.1',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiThemeLightDark,
|
||||
iconColor: 'slategray',
|
||||
title: 'Dark mode (web)',
|
||||
description: 'Dark mode on the web.',
|
||||
release: 'v1.32.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiPanVertical,
|
||||
title: 'Virtual scrollbar (web)',
|
||||
description: 'View the main timeline with a virtual scrollbar, allowing to jump to any point in time, instantly.',
|
||||
release: 'v1.27.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiCheckAll,
|
||||
iconColor: 'green',
|
||||
title: 'Checksum duplication check',
|
||||
description: 'Enforce per user sha1 checksum uniqueness.',
|
||||
release: 'v1.27.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiAndroid,
|
||||
iconColor: 'greenyellow',
|
||||
title: 'Android background backup',
|
||||
description: 'Automatic backup in the background on Android.',
|
||||
release: 'v1.24.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiAccountGroup,
|
||||
iconColor: 'gray',
|
||||
title: 'Admin portal',
|
||||
description: 'Manage users and admin settings from the web.',
|
||||
release: 'v1.10.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiShareCircle,
|
||||
title: 'Album sharing',
|
||||
description: 'Share albums with other users.',
|
||||
release: 'v1.7.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiTag,
|
||||
iconColor: 'coral',
|
||||
title: 'Image tagging',
|
||||
description: 'Tag images with custom values.',
|
||||
release: 'v1.7.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiImage,
|
||||
iconColor: 'rebeccapurple',
|
||||
title: 'View exif',
|
||||
description: 'View metadata about assets.',
|
||||
release: 'v1.3.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiCheckboxMarked,
|
||||
iconColor: 'green',
|
||||
title: 'Multi select',
|
||||
description: 'Select and execute actions on multiple assets at the same time.',
|
||||
release: 'v1.2.0',
|
||||
}),
|
||||
withRelease({
|
||||
icon: mdiVideo,
|
||||
iconColor: 'slategray',
|
||||
title: 'Video player',
|
||||
description: 'Play videos in the web and on mobile.',
|
||||
release: 'v1.2.0',
|
||||
}),
|
||||
{
|
||||
icon: mdiPartyPopper,
|
||||
iconColor: 'deeppink',
|
||||
title: 'First commit',
|
||||
description: 'First commit on GitHub, Immich is born.',
|
||||
getDateLabel: withLanguage(new Date(2022, 1, 3)),
|
||||
},
|
||||
];
|
||||
|
||||
export default function MilestonePage(): JSX.Element {
|
||||
return (
|
||||
<Layout title={title} description={description}>
|
||||
<section className="my-8">
|
||||
<h1 className="md:text-6xl text-center mb-10 text-immich-primary dark:text-immich-dark-primary px-2">
|
||||
{title}
|
||||
</h1>
|
||||
<p className="text-center text-xl px-2">{description}</p>
|
||||
<div className="flex justify-around mt-8 w-full max-w-full">
|
||||
<Timeline items={[...roadmap, ...milestones]} />
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue