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

merge main

This commit is contained in:
martabal 2023-12-16 13:26:45 +01:00
commit 80299772fe
No known key found for this signature in database
GPG key ID: C00196E3148A52BD
97 changed files with 425 additions and 214 deletions

View file

@ -69,7 +69,7 @@ jobs:
flutter build apk --release --split-per-abi --target-platform android-arm,android-arm64,android-x64
- name: Publish Android Artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: release-apk-signed
path: mobile/build/app/outputs/flutter-apk/*.apk

View file

@ -69,7 +69,7 @@ jobs:
token: ${{ secrets.ORG_RELEASE_TOKEN }}
- name: Download APK
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: release-apk-signed

View file

@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.2
* The version of the OpenAPI document: 1.91.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View file

@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.2
* The version of the OpenAPI document: 1.91.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View file

@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.2
* The version of the OpenAPI document: 1.91.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View file

@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.2
* The version of the OpenAPI document: 1.91.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View file

@ -0,0 +1,103 @@
# External Library
This guide walks you through adding an [External Library](../features/libraries#external-libraries).
This guide assumes you are running Immich in Docker and that the files you wish to access are stored
in a directory on the same machine.
# Mount the directory into the containers.
Edit `docker-compose.yml` to add two new mount points under `volumes:`
```
immich-server:
volumes:
- ${EXTERNAL_PATH}:/usr/src/app/external
```
Be sure to add exactly the same line to both `immich-server:` and `immich-microservices:`.
[Question for the devs: Is editing docker-compose.yml really the desirable way to solve this problem?
I assumed user changes were supposed to be kept to .env?]
Edit `.env` to define `EXTERNAL_PATH`, substituting in the correct path for your computer:
```
EXTERNAL_PATH=<your-path-here>
```
On my computer, for example, I use this path:
```
EXTERNAL_PATH=/home/tenino/photos
```
Restart Immich.
```
docker compose down
docker compose up -d
```
# Set the External Path
In the Immich web UI:
- click the **Administration** link in the upper right corner.
<img src={require('./img/administration-link.png').default} width="50%" title="Administration link" />
- Select the **Users** tab
<img src={require('./img/users-tab.png').default} width="50%" title="Users tab" />
- Select the **pencil** next to your user ID
<img src={require('./img/pencil.png').default} width="50%" title="Pencil" />
- Fill in the **External Path** field with `/usr/src/app/external`
<img src={require('./img/external-path.png').default} width="50%" title="External Path field" />
Notice this matches the path _inside the container_ where we mounted your photos.
The purpose of the external path field is for administrators who have multiple users
on their Immich instance. It lets you prevent other authorized users from
navigating to your external library.
# Import the library
In the Immich web UI:
- Click your user avatar in the upper-right corner (circle with your initials)
<img src={require('./img/user-avatar.png').default} width="50%" title="User avatar" />
- Click **Account Settings**
<img src={require('./img/account-settings.png').default} width="50%" title="Account Settings button" />
- Click to expand **Libraries**
<img src={require('./img/libraries-dropdown.png').default} width="50%" title="Libraries dropdown" />
- Click the **Create External Library** button
<img src={require('./img/create-external-library-button.png').default} width="50%" title="Create External Library button" />
- Click the three-dots menu and select **Edit Import Paths**
<img src={require('./img/edit-import-paths.png').default} width="50%" title="Edit Import Paths menu option" />
- Click \*_Add path_
<img src={require('./img/add-path-button.png').default} width="50%" title="Add Path button" />
- Enter **.** as the path and click Add
<img src={require('./img/add-path-field.png').default} width="50%" title="Add Path field" />
- Save the new path
<img src={require('./img/path-save.png').default} width="50%" title="Path Save button" />
- Click the three-dots menu and select **Scan New Library Files** [I'm not sure whether this is necessary]
<img src={require('./img/scan-new-library-files.png').default} width="50%" title="Scan New Library Files menu option" />
# Confirm stuff is happening
- Click **Administration**
<img src={require('./img/administration-link.png').default} width="50%" title="Administration link" />
- Select the **Jobs** tab
<img src={require('./img/jobs-tab.png').default} width="50%" title="Jobs tab" />
- You should see non-zero Active jobs for
Library, Generate Thumbnails, and Extract Metadata.
<img src={require('./img/job-status.png').default} width="50%" title="Job Status display" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -8,7 +8,7 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula');
const config = {
title: 'Immich',
tagline: 'High performance self-hosted photo and video backup solution directly from your mobile phone',
url: 'https://documentation.immich.app',
url: 'https://immich.app',
baseUrl: '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',

View file

@ -26,12 +26,14 @@ import {
mdiMagnify,
mdiMap,
mdiMaterialDesign,
mdiMatrix,
mdiMerge,
mdiMonitor,
mdiMotionPlayOutline,
mdiPalette,
mdiPanVertical,
mdiPartyPopper,
mdiPencil,
mdiRaw,
mdiRotate360,
mdiSecurity,
@ -52,6 +54,24 @@ import React from 'react';
import Timeline, { DateType, Item } from '../components/timeline';
const items: Item[] = [
{
icon: mdiMatrix,
description: 'Moved the search from typesense to pgvecto.rs',
title: 'Search improvement with pgvecto.rs',
release: 'v1.91.0',
tag: 'v1.91.0',
date: new Date(2023, 11, 7),
dateType: DateType.RELEASE,
},
{
icon: mdiPencil,
description: "Edit a photo or video's date, time, hours, timezone, and GPS information",
title: 'Edit metadata',
release: 'v1.90.0',
tag: 'v1.90.0',
date: new Date(2023, 11, 7),
dateType: DateType.RELEASE,
},
{
icon: mdiVectorCombine,
description:

View file

@ -23,4 +23,3 @@
/docs/features/storage-template /docs/administration/storage-template 301
/docs/features/user-management /docs/administration/user-management 301
/docs/developer/contributing /docs/developer/pr-checklist 301

View file

@ -1,29 +0,0 @@
{
"redirects": [
{ "source": "/docs", "destination": "/docs/overview/introduction" },
{ "source": "/docs/mobile-app-beta-program", "destination": "/docs/features/mobile-app" },
{ "source": "/docs/contribution-guidelines", "destination": "/docs/overview/support-the-project#contributing" },
{ "source": "/docs/install", "destination": "/docs/install/docker-compose" },
{ "source": "/docs/installation/one-step-installation", "destination": "/docs/install/script" },
{ "source": "/docs/installation/portainer-installation", "destination": "/docs/install/portainer" },
{ "source": "/docs/installation/recommended-installation", "destination": "/docs/install/docker-compose" },
{ "source": "/docs/installation/unraid", "destination": "/docs/install/unraid" },
{ "source": "/docs/installation/requirements", "destination": "/docs/install/requirements" },
{ "source": "/docs/overview/logo-meaning", "destination": "/docs/overview/logo" },
{ "source": "/docs/overview/technology-stack", "destination": "/docs/developer/architecture" },
{ "source": "/docs/usage/automatic-backup", "destination": "/docs/features/automatic-backup" },
{ "source": "/docs/usage/bulk-upload", "destination": "/docs/features/command-line-interface" },
{ "source": "/docs/features/bulk-upload", "destination": "/docs/features/command-line-interface" },
{ "source": "/docs/usage/oauth", "destination": "/docs/administration/oauth" },
{ "source": "/docs/usage/post-installation", "destination": "/docs/install/post-install" },
{ "source": "/docs/usage/update", "destination": "/docs/install/docker-compose#step-4---upgrading" },
{ "source": "/docs/usage/server-commands", "destination": "/docs/administration/server-commands" },
{ "source": "/docs/features/jobs", "destination": "/docs/administration/jobs" },
{ "source": "/docs/features/oauth", "destination": "/docs/administration/oauth" },
{ "source": "/docs/features/password-login", "destination": "/docs/administration/password-login" },
{ "source": "/docs/features/server-commands", "destination": "/docs/administration/server-commands" },
{ "source": "/docs/features/storage-template", "destination": "/docs/administration/storage-template" },
{ "source": "/docs/features/user-management", "destination": "/docs/administration/user-management" },
{ "source": "/docs/developer/contributing", "destination": "/docs/developer/pr-checklist" }
]
}

View file

@ -26,6 +26,9 @@ _OPENCLIP_MODELS = {
"ViT-L-14-336__openai",
"ViT-H-14__laion2b-s32b-b79k",
"ViT-g-14__laion2b-s12b-b42k",
"ViT-L-14-quickgelu__dfn2b",
"ViT-H-14-quickgelu__dfn5b",
"ViT-H-14-378-quickgelu__dfn5b",
}
@ -34,6 +37,9 @@ _MCLIP_MODELS = {
"XLM-Roberta-Large-Vit-B-32",
"XLM-Roberta-Large-Vit-B-16Plus",
"XLM-Roberta-Large-Vit-L-14",
"XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k",
"nllb-clip-base-siglip__v1",
"nllb-clip-large-siglip__v1",
}

View file

@ -1,4 +1,4 @@
FROM mambaorg/micromamba:bookworm-slim@sha256:e296d47be09fc5d260eba9b191f60496f028a4f3ec41e8a14d48c0bae2c60244 as builder
FROM mambaorg/micromamba:bookworm-slim@sha256:5ea70d22075f7209d0410e28b7ce5b1703623099fa04d1154081156b180f739c as builder
ENV NODE_ENV=production \
TRANSFORMERS_CACHE=/cache \

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "machine-learning"
version = "1.90.2"
version = "1.91.0"
description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md"

View file

@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 114,
"android.injected.version.name" => "1.90.2",
"android.injected.version.code" => 115,
"android.injected.version.name" => "1.91.0",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')

View file

@ -5,17 +5,17 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000235">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000217">
</testcase>
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="27.74518">
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="66.694734">
</testcase>
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="25.612783">
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="27.6926">
</testcase>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 679 KiB

View file

@ -169,4 +169,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382
COCOAPODS: 1.11.3
COCOAPODS: 1.12.1

View file

@ -379,7 +379,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 130;
CURRENT_PROJECT_VERSION = 131;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -515,7 +515,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 130;
CURRENT_PROJECT_VERSION = 131;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -543,7 +543,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 130;
CURRENT_PROJECT_VERSION = 131;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;

View file

@ -54,11 +54,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.90.0</string>
<string>1.91.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>130</string>
<string>131</string>
<key>FLTEnableImpeller</key>
<true />
<key>ITSAppUsesNonExemptEncryption</key>

View file

@ -19,7 +19,7 @@ platform :ios do
desc "iOS Beta"
lane :beta do
increment_version_number(
version_number: "1.90.2"
version_number: "1.91.0"
)
increment_build_number(
build_number: latest_testflight_build_number + 1,

View file

@ -5,32 +5,32 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000234">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000273">
</testcase>
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.207521">
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.162117">
</testcase>
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="18.516191">
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="3.645923">
</testcase>
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.23018">
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.158953">
</testcase>
<testcase classname="fastlane.lanes" name="4: build_app" time="104.984834">
<testcase classname="fastlane.lanes" name="4: build_app" time="114.023733">
</testcase>
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="61.879749">
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="97.572612">
</testcase>

View file

@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone
publish_to: "none"
version: 1.90.2+114
version: 1.91.0+115
isar_version: &isar_version 3.1.0+1
environment:

View file

@ -1,5 +1,5 @@
# dev build
FROM ghcr.io/immich-app/base-server-dev:20231207@sha256:175d55f2fff48e0edeaf359c1aa0572b923db0c19304c22136a39061b8bc8179 as dev
FROM ghcr.io/immich-app/base-server-dev:20231214@sha256:cd5be516b27c0c402bee3a6a93d8c83dfd5a827c18a2343cb97b55f3be98151b as dev
RUN apt-get install --no-install-recommends -yqq tini
WORKDIR /usr/src/app
@ -31,7 +31,7 @@ RUN npm run build
# prod build
FROM ghcr.io/immich-app/base-server-prod:20231207@sha256:006c9170f8ddafabe49d447ea2b892563951e05e4f3e5434635cdb7ab7dd8911
FROM ghcr.io/immich-app/base-server-prod:20231214@sha256:b214f86683fde081b09beed2d7bfc28bec55c829751ccf2e02ad7dd18293f5e0
WORKDIR /usr/src/app
ENV NODE_ENV=production

View file

@ -6271,7 +6271,7 @@
"info": {
"title": "Immich",
"description": "Immich API",
"version": "1.90.2",
"version": "1.91.0",
"contact": {}
},
"tags": [],

View file

@ -1,12 +1,12 @@
{
"name": "immich",
"version": "1.90.2",
"version": "1.91.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "immich",
"version": "1.90.2",
"version": "1.91.0",
"license": "UNLICENSED",
"dependencies": {
"@babel/runtime": "^7.22.11",
@ -90,7 +90,7 @@
"prettier-plugin-organize-imports": "^3.2.3",
"rimraf": "^5.0.1",
"source-map-support": "^0.5.21",
"sql-formatter": "^14.0.0",
"sql-formatter": "^15.0.0",
"supertest": "^6.3.3",
"testcontainers": "^10.2.1",
"ts-jest": "^29.1.1",
@ -11210,9 +11210,9 @@
"dev": true
},
"node_modules/sql-formatter": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-14.0.0.tgz",
"integrity": "sha512-VcHYMRvZqg3RNjjxNB/puT9O1hR5QLXTvgTaBtxXcvmRQwSnH9M+oW2Ti+uFuVVU8HoNlOjU2uKHv8c0FQNsdQ==",
"version": "15.0.2",
"resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.0.2.tgz",
"integrity": "sha512-B8FTRc1dhb36lfuwSdiLhwrhkvT3PU/3es7YDPPQBOhbGHdXKlweAXTRS+QfCWk06ufAh118yFja6NcukBS4gg==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1",
@ -21274,9 +21274,9 @@
"dev": true
},
"sql-formatter": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-14.0.0.tgz",
"integrity": "sha512-VcHYMRvZqg3RNjjxNB/puT9O1hR5QLXTvgTaBtxXcvmRQwSnH9M+oW2Ti+uFuVVU8HoNlOjU2uKHv8c0FQNsdQ==",
"version": "15.0.2",
"resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.0.2.tgz",
"integrity": "sha512-B8FTRc1dhb36lfuwSdiLhwrhkvT3PU/3es7YDPPQBOhbGHdXKlweAXTRS+QfCWk06ufAh118yFja6NcukBS4gg==",
"dev": true,
"requires": {
"argparse": "^2.0.1",

View file

@ -1,6 +1,6 @@
{
"name": "immich",
"version": "1.90.2",
"version": "1.91.0",
"description": "",
"author": "",
"private": true,
@ -117,7 +117,7 @@
"prettier-plugin-organize-imports": "^3.2.3",
"rimraf": "^5.0.1",
"source-map-support": "^0.5.21",
"sql-formatter": "^14.0.0",
"sql-formatter": "^15.0.0",
"supertest": "^6.3.3",
"testcontainers": "^10.2.1",
"ts-jest": "^29.1.1",

View file

@ -159,6 +159,9 @@ describe(JobService.name, () => {
it('should handle a start object tagging command', async () => {
jobMock.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.MACHINE_LEARNING_CLASSIFICATION_ENABLED, value: true },
]);
await sut.handleCommand(QueueName.OBJECT_TAGGING, { command: JobCommand.START, force: false });

View file

@ -171,7 +171,7 @@ describe(ServerInfoService.name, () => {
passwordLogin: true,
search: true,
sidecar: true,
tagImage: true,
tagImage: false,
configFile: false,
trash: true,
});

View file

@ -72,9 +72,18 @@ export const CLIP_MODEL_INFO: Record<string, ModelInfo> = {
'ViT-L-14-336__openai': {
dimSize: 768,
},
'ViT-L-14-quickgelu__dfn2b': {
dimSize: 768,
},
'ViT-H-14__laion2b-s32b-b79k': {
dimSize: 1024,
},
'ViT-H-14-quickgelu__dfn5b': {
dimSize: 1024,
},
'ViT-H-14-378-quickgelu__dfn5b': {
dimSize: 1024,
},
'ViT-g-14__laion2b-s12b-b42k': {
dimSize: 1024,
},
@ -90,6 +99,15 @@ export const CLIP_MODEL_INFO: Record<string, ModelInfo> = {
'XLM-Roberta-Large-Vit-L-14': {
dimSize: 768,
},
'XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k': {
dimSize: 1024,
},
'nllb-clip-base-siglip__v1': {
dimSize: 768,
},
'nllb-clip-large-siglip__v1': {
dimSize: 1152,
},
};
export function cleanModelName(modelName: string): string {

View file

@ -48,6 +48,12 @@ describe(SmartInfoService.name, () => {
});
describe('handleQueueObjectTagging', () => {
beforeEach(async () => {
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.MACHINE_LEARNING_CLASSIFICATION_ENABLED, value: true },
]);
});
it('should do nothing if machine learning is disabled', async () => {
configMock.load.mockResolvedValue([{ key: SystemConfigKey.MACHINE_LEARNING_ENABLED, value: false }]);
@ -58,6 +64,9 @@ describe(SmartInfoService.name, () => {
});
it('should queue the assets without tags', async () => {
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.MACHINE_LEARNING_CLASSIFICATION_ENABLED, value: true },
]);
assetMock.getWithout.mockResolvedValue({
items: [assetStub.image],
hasNextPage: false,
@ -70,6 +79,9 @@ describe(SmartInfoService.name, () => {
});
it('should queue all the assets', async () => {
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.MACHINE_LEARNING_CLASSIFICATION_ENABLED, value: true },
]);
assetMock.getAll.mockResolvedValue({
items: [assetStub.image],
hasNextPage: false,
@ -103,6 +115,9 @@ describe(SmartInfoService.name, () => {
});
it('should save the returned tags', async () => {
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.MACHINE_LEARNING_CLASSIFICATION_ENABLED, value: true },
]);
machineMock.classifyImage.mockResolvedValue(['tag1', 'tag2', 'tag3']);
await sut.handleClassifyImage({ id: asset.id });
@ -121,6 +136,9 @@ describe(SmartInfoService.name, () => {
});
it('should always overwrite old tags', async () => {
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.MACHINE_LEARNING_CLASSIFICATION_ENABLED, value: true },
]);
machineMock.classifyImage.mockResolvedValue([]);
await sut.handleClassifyImage({ id: asset.id });

View file

@ -67,7 +67,7 @@ export const defaults = Object.freeze<SystemConfig>({
enabled: process.env.IMMICH_MACHINE_LEARNING_ENABLED !== 'false',
url: process.env.IMMICH_MACHINE_LEARNING_URL || 'http://immich-machine-learning:3003',
classification: {
enabled: true,
enabled: false,
modelName: 'microsoft/resnet-50',
minScore: 0.9,
},

View file

@ -66,7 +66,7 @@ const updatedConfig = Object.freeze<SystemConfig>({
enabled: true,
url: 'http://immich-machine-learning:3003',
classification: {
enabled: true,
enabled: false,
modelName: 'microsoft/resnet-50',
minScore: 0.9,
},

View file

@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.2
* The version of the OpenAPI document: 1.91.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View file

@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.2
* The version of the OpenAPI document: 1.91.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View file

@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.2
* The version of the OpenAPI document: 1.91.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View file

@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.2
* The version of the OpenAPI document: 1.91.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View file

@ -6,19 +6,22 @@
export let user: UserResponseDto;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
success: void;
fail: void;
}>();
const deleteUser = async () => {
try {
const deletedUser = await api.userApi.deleteUser({ id: user.id });
if (deletedUser.data.deletedAt != null) {
dispatch('user-delete-success');
dispatch('success');
} else {
dispatch('user-delete-fail');
dispatch('fail');
}
} catch (error) {
handleError(error, 'Unable to delete user');
dispatch('user-delete-fail');
dispatch('fail');
}
};
</script>

View file

@ -5,14 +5,17 @@
export let user: UserResponseDto;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
success: void;
fail: void;
}>();
const restoreUser = async () => {
const restoredUser = await api.userApi.restoreUser({ id: user.id });
if (restoredUser.data.deletedAt == null) {
dispatch('user-restore-success');
dispatch('success');
} else {
dispatch('user-restore-fail');
dispatch('fail');
}
};
</script>

View file

@ -10,6 +10,7 @@
import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingSwitch from '../setting-switch.svelte';
import SettingSelect from '../setting-select.svelte';
import type { ResetOptions } from '$lib/utils/dipatch';
export let loggingConfig: SystemConfigLoggingDto; // this is the config that is being edited
export let disabled = false;
@ -24,6 +25,14 @@
]);
}
const handleReset = (detail: ResetOptions) => {
if (detail.default) {
resetToDefault();
} else {
reset();
}
};
async function saveSetting() {
try {
const { data: current } = await api.systemConfigApi.getConfig();
@ -96,9 +105,8 @@
/>
<SettingButtonsRow
on:reset={reset}
on:reset={({ detail }) => handleReset(detail)}
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>

View file

@ -10,7 +10,10 @@
export let album: AlbumResponseDto;
let selectedThumbnail: AssetResponseDto | undefined;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
close: void;
thumbnail: AssetResponseDto | undefined;
}>();
$: isSelected = (id: string): boolean | undefined => {
if (!selectedThumbnail && album.albumThumbnailAssetId == id) {
@ -25,7 +28,7 @@
transition:fly={{ y: 500, duration: 100, easing: quintOut }}
class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg py-[160px] dark:bg-immich-dark-bg"
>
<ControlAppBar on:close-button-click={() => dispatch('close')}>
<ControlAppBar on:close={() => dispatch('close')}>
<svelte:fragment slot="leading">
<p class="text-lg">Select album cover</p>
</svelte:fragment>
@ -35,7 +38,7 @@
size="sm"
rounded="lg"
disabled={selectedThumbnail == undefined}
on:click={() => dispatch('thumbnail-selected', { asset: selectedThumbnail })}
on:click={() => dispatch('thumbnail', selectedThumbnail)}
>
Done
</Button>

View file

@ -9,7 +9,10 @@
export let isShowActivity: boolean | undefined;
export let disabled: boolean;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
openActivityTab: void;
favorite: void;
}>();
</script>
<div

View file

@ -2,7 +2,9 @@
import { AlbumResponseDto, ThumbnailFormat, api } from '@api';
import { createEventDispatcher } from 'svelte';
const dispatcher = createEventDispatcher();
const dispatch = createEventDispatcher<{
album: void;
}>();
export let album: AlbumResponseDto;
export let variant: 'simple' | 'full' = 'full';
@ -24,7 +26,7 @@
</script>
<button
on:click={() => dispatcher('album')}
on:click={() => dispatch('album')}
class="flex w-full gap-4 px-6 py-2 text-left transition-colors hover:bg-gray-200 dark:hover:bg-gray-700"
>
<div class="h-12 w-12 shrink-0 rounded-xl bg-slate-300">

View file

@ -39,7 +39,7 @@
type MenuItemEvent = 'addToAlbum' | 'addToSharedAlbum' | 'asProfileImage' | 'runJob' | 'playSlideShow' | 'unstack';
const dispatch = createEventDispatcher<{
goBack: void;
back: void;
stopMotionPhoto: void;
playMotionPhoto: void;
download: void;
@ -78,7 +78,7 @@
class="z-[1001] flex h-16 place-items-center justify-between bg-gradient-to-b from-black/40 px-3 transition-transform duration-200"
>
<div class="text-white">
<CircleIconButton isOpacity={true} icon={mdiArrowLeft} on:click={() => dispatch('goBack')} />
<CircleIconButton isOpacity={true} icon={mdiArrowLeft} on:click={() => dispatch('back')} />
</div>
<div class="flex w-[calc(100%-3rem)] justify-end gap-2 overflow-hidden text-white">
{#if asset.isOffline}

View file

@ -429,19 +429,17 @@
addToSharedAlbum = shared;
};
const handleAddToNewAlbum = (event: CustomEvent) => {
const handleAddToNewAlbum = (albumName: string) => {
isShowAlbumPicker = false;
const { albumName }: { albumName: string } = event.detail;
api.albumApi.createAlbum({ createAlbumDto: { albumName, assetIds: [asset.id] } }).then((response) => {
const album = response.data;
goto(`${AppRoute.ALBUMS}/${album.id}`);
});
};
const handleAddToAlbum = async (event: CustomEvent<{ album: AlbumResponseDto }>) => {
const handleAddToAlbum = async (album: AlbumResponseDto) => {
isShowAlbumPicker = false;
const album = event.detail.album;
await addAssetsToAlbum(album.id, [asset.id]);
await getAllAlbums();
@ -580,7 +578,7 @@
showDetailButton={shouldShowDetailButton}
showSlideshow={!!assetStore}
hasStackChildren={$stackAssetsStore.length > 0}
on:goBack={closeViewer}
on:back={closeViewer}
on:showDetail={showDetailInfoHandler}
on:download={() => downloadFile(asset)}
on:delete={trashOrDelete}
@ -764,9 +762,8 @@
{#if isShowAlbumPicker}
<AlbumSelectionModal
shared={addToSharedAlbum}
on:newAlbum={handleAddToNewAlbum}
on:newSharedAlbum={handleAddToNewAlbum}
on:album={handleAddToAlbum}
on:newAlbum={({ detail }) => handleAddToNewAlbum(detail)}
on:album={({ detail }) => handleAddToAlbum(detail)}
on:close={() => (isShowAlbumPicker = false)}
/>
{/if}
@ -789,11 +786,7 @@
{/if}
{#if isShowProfileImageCrop}
<ProfileImageCropper
{asset}
on:close={() => (isShowProfileImageCrop = false)}
on:close-viewer={handleCloseViewer}
/>
<ProfileImageCropper {asset} on:close={() => (isShowProfileImageCrop = false)} />
{/if}
</section>

View file

@ -1,5 +1,4 @@
<script lang="ts">
import { page } from '$app/stores';
import { locale } from '$lib/stores/preferences.store';
import { featureFlags } from '$lib/stores/server-config.store';
import { getAssetFilename } from '$lib/utils/asset-utils';
@ -54,7 +53,7 @@
}
}
$: isOwner = $page?.data?.user?.id === asset.ownerId;
$: isOwner = $user.id === asset.ownerId;
$: {
// Get latest description from server

View file

@ -35,7 +35,11 @@
}
}
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
close: void;
createPerson: string | null;
reassign: PersonResponseDto;
}>();
const handleBackButton = () => {
dispatch('close');
};

View file

@ -10,7 +10,9 @@
export let circle = false;
export let border = false;
let dispatch = createEventDispatcher();
let dispatch = createEventDispatcher<{
click: PersonResponseDto;
}>();
const handleOnClicked = () => {
dispatch('click', person);

View file

@ -24,7 +24,10 @@
let screenHeight: number;
let isShowConfirmation = false;
let dispatch = createEventDispatcher();
let dispatch = createEventDispatcher<{
back: void;
merge: void;
}>();
$: hasSelection = selectedPeople.length > 0;
$: unselectedPeople = people.filter(
@ -37,7 +40,7 @@
});
const onClose = () => {
dispatch('go-back');
dispatch('back');
};
const handleSwapPeople = () => {
@ -89,7 +92,7 @@
transition:fly={{ y: 500, duration: 100, easing: quintOut }}
class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
>
<ControlAppBar on:close-button-click={onClose}>
<ControlAppBar on:close={onClose}>
<svelte:fragment slot="leading">
{#if hasSelection}
Selected {selectedPeople.length}

View file

@ -48,7 +48,10 @@
let automaticRefreshTimeout: NodeJS.Timeout;
const { onPersonThumbnail } = websocketStore;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
close: void;
refresh: void;
}>();
// Reset value
$onPersonThumbnail = '';

View file

@ -7,7 +7,12 @@
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
import { mdiClose, mdiEye, mdiEyeOff, mdiRestart } from '@mdi/js';
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
close: void;
reset: void;
change: void;
done: void;
}>();
export let showLoadingSpinner: boolean;
export let toggleVisibility: boolean;
@ -21,24 +26,20 @@
class="sticky top-0 z-10 flex h-16 w-full items-center justify-between border-b bg-white p-1 dark:border-immich-dark-gray dark:bg-black dark:text-immich-dark-fg md:p-8"
>
<div class="flex items-center">
<CircleIconButton icon={mdiClose} on:click={() => dispatch('closeClick')} />
<CircleIconButton icon={mdiClose} on:click={() => dispatch('close')} />
<p class="ml-4 hidden sm:block">Show & hide people</p>
</div>
<div class="flex items-center justify-end">
<div class="flex items-center md:mr-8">
<CircleIconButton
title="Reset people visibility"
icon={mdiRestart}
on:click={() => dispatch('reset-visibility')}
/>
<CircleIconButton title="Reset people visibility" icon={mdiRestart} on:click={() => dispatch('reset')} />
<CircleIconButton
title="Toggle visibility"
icon={toggleVisibility ? mdiEye : mdiEyeOff}
on:click={() => dispatch('toggle-visibility')}
on:click={() => dispatch('change')}
/>
</div>
{#if !showLoadingSpinner}
<IconButton on:click={() => dispatch('doneClick')}>Done</IconButton>
<IconButton on:click={() => dispatch('done')}>Done</IconButton>
{:else}
<LoadingSpinner />
{/if}

View file

@ -29,7 +29,10 @@
? people.filter((person) => selectedPerson && person.id !== selectedPerson.id && personAssets.id !== person.id)
: people;
let dispatch = createEventDispatcher();
let dispatch = createEventDispatcher<{
confirm: void;
close: void;
}>();
const selectedPeople: AssetFaceUpdateItem[] = [];
@ -141,7 +144,7 @@
transition:fly={{ y: 500, duration: 100, easing: quintOut }}
class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
>
<ControlAppBar on:close-button-click={onClose}>
<ControlAppBar on:close={onClose}>
<svelte:fragment slot="leading">
<slot name="header" />
<div />

View file

@ -5,18 +5,32 @@
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import { mdiKeyVariant } from '@mdi/js';
import { NotificationType, notificationController } from '../shared-components/notification/notification';
export let apiKey: Partial<APIKeyResponseDto>;
export let title = 'API Key';
export let cancelText = 'Cancel';
export let submitText = 'Save';
export let apiKeyName = 'API Key';
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
cancel: void;
submit: Partial<APIKeyResponseDto>;
}>();
const handleCancel = () => dispatch('cancel');
const handleSubmit = () => dispatch('submit', { ...apiKey, name: apiKey.name });
const handleSubmit = () => {
if (apiKeyName) {
dispatch('submit', { ...apiKey, name: apiKeyName });
} else {
notificationController.show({
message: "Your API Key name shouldn't be empty",
type: NotificationType.Warning,
});
}
};
</script>
<FullScreenModal on:clickOutside={() => handleCancel()}>
<FullScreenModal on:clickOutside={handleCancel}>
<div
class="w-[500px] max-w-[95vw] rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
>
@ -29,14 +43,14 @@
</h1>
</div>
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off">
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
<div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="name">Name</label>
<input class="immich-form-input" id="name" name="name" type="text" bind:value={apiKey.name} />
<input class="immich-form-input" id="name" name="name" type="text" bind:value={apiKeyName} />
</div>
<div class="mt-8 flex w-full gap-4 px-4">
<Button color="gray" fullwidth on:click={() => handleCancel()}>{cancelText}</Button>
<Button color="gray" fullwidth on:click={handleCancel}>{cancelText}</Button>
<Button type="submit" fullwidth>{submitText}</Button>
</div>
</form>

View file

@ -8,7 +8,9 @@
export let secret = '';
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
done: void;
}>();
const handleDone = () => dispatch('done');
let canCopyImagesToClipboard = true;

View file

@ -22,7 +22,9 @@
}
}
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
success: void;
}>();
async function changePassword() {
if (changeChagePassword) {

View file

@ -24,7 +24,10 @@
canCreateUser = true;
}
}
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
submit: void;
cancel: void;
}>();
async function registerUser(event: SubmitEvent) {
if (canCreateUser && !isCreatingUser) {
@ -52,7 +55,7 @@
if (status === 201) {
success = 'New user created';
dispatch('user-created');
dispatch('submit');
isCreatingUser = false;
return;

View file

@ -9,7 +9,11 @@
export let canDelete = false;
export let submitText = 'Submit';
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
cancel: void;
submit: { excludePattern: string };
delete: void;
}>();
const handleCancel = () => dispatch('cancel');
const handleSubmit = () => dispatch('submit', { excludePattern: exclusionPattern });
</script>

View file

@ -11,7 +11,11 @@
export let submitText = 'Save';
export let canDelete = false;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
cancel: void;
submit: { importPath: string };
delete: void;
}>();
const handleCancel = () => dispatch('cancel');
const handleSubmit = () => dispatch('submit', { importPath });
</script>

View file

@ -26,7 +26,10 @@
}
});
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
cancel: void;
submit: Partial<LibraryResponseDto>;
}>();
const handleCancel = () => {
dispatch('cancel');
};

View file

@ -5,7 +5,10 @@
export let library: Partial<LibraryResponseDto>;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
cancel: void;
submit: Partial<LibraryResponseDto>;
}>();
const handleCancel = () => {
dispatch('cancel');
};

View file

@ -26,13 +26,16 @@
}
});
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
cancel: void;
submit: { library: Partial<LibraryResponseDto>; type: LibraryType };
}>();
const handleCancel = () => {
dispatch('cancel');
};
const handleSubmit = () => {
dispatch('submit', { ...library, libraryType: LibraryType.External });
dispatch('submit', { library, type: LibraryType.External });
};
const handleAddExclusionPattern = async () => {

View file

@ -101,7 +101,7 @@
<section id="memory-viewer" class="w-full bg-immich-dark-gray" bind:this={memoryWrapper}>
{#if currentMemory}
<ControlAppBar on:close-button-click={() => goto(AppRoute.PHOTOS)} forceDark>
<ControlAppBar on:close={() => goto(AppRoute.PHOTOS)} forceDark>
<svelte:fragment slot="leading">
<p class="text-lg">
{currentMemory.title}

View file

@ -23,10 +23,9 @@
closeMenu();
};
const handleAddToNewAlbum = (event: CustomEvent) => {
const handleAddToNewAlbum = (albumName: string) => {
showAlbumPicker = false;
const { albumName }: { albumName: string } = event.detail;
const assetIds = Array.from(getAssets()).map((asset) => asset.id);
api.albumApi.createAlbum({ createAlbumDto: { albumName, assetIds } }).then((response) => {
const { id, albumName } = response.data;
@ -42,9 +41,8 @@
});
};
const handleAddToAlbum = async (event: CustomEvent<{ album: AlbumResponseDto }>) => {
const handleAddToAlbum = async (album: AlbumResponseDto) => {
showAlbumPicker = false;
const album = event.detail.album;
const assetIds = Array.from(getAssets()).map((asset) => asset.id);
await addAssetsToAlbum(album.id, assetIds);
clearSelect();
@ -56,9 +54,8 @@
{#if showAlbumPicker}
<AlbumSelectionModal
{shared}
on:newAlbum={handleAddToNewAlbum}
on:newSharedAlbum={handleAddToNewAlbum}
on:album={handleAddToAlbum}
on:newAlbum={({ detail }) => handleAddToNewAlbum(detail)}
on:album={({ detail }) => handleAddToAlbum(detail)}
on:close={handleHideAlbumPicker}
/>
{/if}

View file

@ -36,7 +36,7 @@
});
</script>
<ControlAppBar on:close-button-click={clearSelect} backIcon={mdiClose} tailwindClasses="bg-white shadow-md">
<ControlAppBar on:close={clearSelect} backIcon={mdiClose} tailwindClasses="bg-white shadow-md">
<p class="font-medium text-immich-primary dark:text-immich-dark-primary" slot="leading">
Selected {assets.size.toLocaleString($locale)}
</p>

View file

@ -79,7 +79,7 @@
{/if}
</AssetSelectControlBar>
{:else}
<ControlAppBar on:close-button-click={() => goto(AppRoute.PHOTOS)} backIcon={mdiArrowLeft} showBackButton={false}>
<ControlAppBar on:close={() => goto(AppRoute.PHOTOS)} backIcon={mdiArrowLeft} showBackButton={false}>
<svelte:fragment slot="leading">
<a
data-sveltekit-preload-data="hover"

View file

@ -13,15 +13,8 @@
let search = '';
const dispatch = createEventDispatcher<{
newAlbum: {
albumName: string;
};
album: {
album: AlbumResponseDto;
};
newSharedAlbum: {
albumName: string;
};
newAlbum: string;
album: AlbumResponseDto;
close: void;
}>();
@ -47,15 +40,11 @@
}
const handleSelect = (album: AlbumResponseDto) => {
dispatch('album', { album });
dispatch('album', album);
};
const handleNew = () => {
if (shared) {
dispatch('newAlbum', { albumName: search.length > 0 ? search : '' });
} else {
dispatch('newSharedAlbum', { albumName: search.length > 0 ? search : '' });
}
dispatch('newAlbum', search.length > 0 ? search : '');
};
</script>

View file

@ -13,7 +13,9 @@
let appBarBorder = 'bg-immich-bg border border-transparent';
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
close: void;
}>();
const onScroll = () => {
if (window.pageYOffset > 80) {
@ -50,7 +52,7 @@
<div class="flex place-items-center gap-6 justify-self-start dark:text-immich-dark-fg">
{#if showBackButton}
<CircleIconButton
on:click={() => dispatch('close-button-click')}
on:click={() => dispatch('close')}
icon={backIcon}
backgroundColor={'transparent'}
hoverColor={'#e2e7e9'}

View file

@ -167,7 +167,7 @@
>
<img
src={api.getAssetThumbnailUrl(feature.properties?.id)}
class="rounded-full w-[60px] h-[60px] border-2 border-immich-primary shadow-lg hover:border-immich-dark-primary transition-all duration-200 hover:scale-150"
class="rounded-full w-[60px] h-[60px] border-2 border-immich-primary shadow-lg hover:border-immich-dark-primary transition-all duration-200 hover:scale-150 object-contain bg-immich-primary"
alt={`Image with id ${feature.properties?.id}`}
/>
{#if $$slots.popup}

View file

@ -16,7 +16,7 @@
import UserAvatar from '../user-avatar.svelte';
import { featureFlags } from '$lib/stores/server-config.store';
import { mdiMagnify, mdiTrayArrowUp, mdiCog } from '@mdi/js';
import { user } from '$lib/stores/user.store';
import { resetSavedUser, user } from '$lib/stores/user.store';
export let showUploadButton = true;
@ -28,6 +28,7 @@
}>();
const logOut = async () => {
resetSavedUser();
const { data } = await api.authenticationApi.logout();
goto(data.redirectUri || '/auth/login?autoLaunch=0');
};

View file

@ -12,7 +12,11 @@
export let link: SharedLinkResponseDto;
let expirationCountdown: luxon.DurationObjectUnits;
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
delete: void;
copy: void;
edit: void;
}>();
const getThumbnail = async (): Promise<AssetResponseDto> => {
let assetId = '';

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { api, UpdateLibraryDto, LibraryResponseDto, LibraryType, LibraryStatsResponseDto } from '@api';
import { api, LibraryResponseDto, LibraryType, LibraryStatsResponseDto } from '@api';
import { onMount } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import { notificationController, NotificationType } from '../shared-components/notification/notification';
@ -112,16 +112,15 @@
}
};
const handleUpdate = async (event: CustomEvent<UpdateLibraryDto>) => {
const handleUpdate = async (event: Partial<LibraryResponseDto>) => {
if (updateLibraryIndex === null) {
return;
}
try {
const dto = event.detail;
const libraryId = libraries[updateLibraryIndex].id;
await api.libraryApi.updateLibrary({ id: libraryId, updateLibraryDto: dto });
await api.libraryApi.updateLibrary({ id: libraryId, updateLibraryDto: { ...event } });
} catch (error) {
handleError(error, 'Unable to update library');
} finally {
@ -375,19 +374,27 @@
</tr>
{#if renameLibrary === index}
<div transition:slide={{ duration: 250 }}>
<LibraryRenameForm {library} on:submit={handleUpdate} on:cancel={() => (renameLibrary = null)} />
<LibraryRenameForm
{library}
on:submit={({ detail }) => handleUpdate(detail)}
on:cancel={() => (renameLibrary = null)}
/>
</div>
{/if}
{#if editImportPaths === index}
<div transition:slide={{ duration: 250 }}>
<LibraryImportPathsForm {library} on:submit={handleUpdate} on:cancel={() => (editImportPaths = null)} />
<LibraryImportPathsForm
{library}
on:submit={({ detail }) => handleUpdate(detail)}
on:cancel={() => (editImportPaths = null)}
/>
</div>
{/if}
{#if editScanSettings === index}
<div transition:slide={{ duration: 250 }} class="mb-4 ml-4 mr-4">
<LibraryScanSettingsForm
{library}
on:submit={handleUpdate}
on:submit={({ detail }) => handleUpdate(detail)}
on:cancel={() => (editScanSettings = null)}
/>
</div>

View file

@ -29,10 +29,9 @@
keys = data;
}
const handleCreate = async (event: CustomEvent<APIKeyResponseDto>) => {
const handleCreate = async (detail: Partial<APIKeyResponseDto>) => {
try {
const dto = event.detail;
const { data } = await api.keyApi.createApiKey({ aPIKeyCreateDto: dto });
const { data } = await api.keyApi.createApiKey({ aPIKeyCreateDto: detail });
secret = data.secret;
} catch (error) {
handleError(error, 'Unable to create a new API Key');
@ -42,15 +41,13 @@
}
};
const handleUpdate = async (event: CustomEvent<APIKeyResponseDto>) => {
if (!editKey) {
const handleUpdate = async (detail: Partial<APIKeyResponseDto>) => {
if (!editKey || !detail.name) {
return;
}
const dto = event.detail;
try {
await api.keyApi.updateApiKey({ id: editKey.id, aPIKeyUpdateDto: { name: dto.name } });
await api.keyApi.updateApiKey({ id: editKey.id, aPIKeyUpdateDto: { name: detail.name } });
notificationController.show({
message: `Saved API Key`,
type: NotificationType.Info,
@ -88,7 +85,7 @@
title="New API Key"
submitText="Create"
apiKey={newKey}
on:submit={handleCreate}
on:submit={({ detail }) => handleCreate(detail)}
on:cancel={() => (newKey = null)}
/>
{/if}
@ -98,7 +95,12 @@
{/if}
{#if editKey}
<APIKeyForm submitText="Save" apiKey={editKey} on:submit={handleUpdate} on:cancel={() => (editKey = null)} />
<APIKeyForm
submitText="Save"
apiKey={editKey}
on:submit={({ detail }) => handleUpdate(detail)}
on:cancel={() => (editKey = null)}
/>
{/if}
{#if deleteKey}

View file

@ -1,7 +1,7 @@
import { get, writable } from 'svelte/store';
import type { UserResponseDto } from '@api';
export const user = writable<UserResponseDto>();
export let user = writable<UserResponseDto>();
export const setUser = (value: UserResponseDto) => {
user.set(value);
@ -10,3 +10,7 @@ export const setUser = (value: UserResponseDto) => {
export const getSavedUser = () => {
return get(user);
};
export const resetSavedUser = () => {
user = writable<UserResponseDto>();
};

View file

@ -2,7 +2,6 @@ import type { AssetResponseDto, ServerVersionResponseDto } from '@api';
import { Socket, io } from 'socket.io-client';
import { writable } from 'svelte/store';
import { loadConfig } from './server-config.store';
import { getSavedUser } from './user.store';
export interface ReleaseEvent {
isAvailable: boolean;
@ -26,7 +25,7 @@ let websocket: Socket | null = null;
export const openWebsocketConnection = () => {
try {
if (websocket || !getSavedUser()) {
if (websocket) {
return;
}

View file

@ -457,7 +457,7 @@
</AssetSelectControlBar>
{:else}
{#if viewMode === ViewMode.VIEW || viewMode === ViewMode.ALBUM_OPTIONS}
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close-button-click={() => goto(backUrl)}>
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close={() => goto(backUrl)}>
<svelte:fragment slot="trailing">
<CircleIconButton
title="Add Photos"
@ -513,7 +513,7 @@
{/if}
{#if viewMode === ViewMode.SELECT_ASSETS}
<ControlAppBar on:close-button-click={handleCloseSelectAssets}>
<ControlAppBar on:close={handleCloseSelectAssets}>
<svelte:fragment slot="leading">
<p class="text-lg dark:text-immich-dark-fg">
{#if $timelineSelected.size === 0}
@ -539,7 +539,7 @@
{/if}
{#if viewMode === ViewMode.SELECT_THUMBNAIL}
<ControlAppBar on:close-button-click={() => (viewMode = ViewMode.VIEW)}>
<ControlAppBar on:close={() => (viewMode = ViewMode.VIEW)}>
<svelte:fragment slot="leading">Select Album Cover</svelte:fragment>
</ControlAppBar>
{/if}

View file

@ -37,7 +37,7 @@
<DownloadAction />
</AssetSelectControlBar>
{:else}
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close-button-click={() => goto(AppRoute.SHARING)}>
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close={() => goto(AppRoute.SHARING)}>
<svelte:fragment slot="leading">
<p class="whitespace-nowrap text-immich-fg dark:text-immich-dark-fg">
{data.partner.name}'s photos

View file

@ -438,10 +438,10 @@
</UserPageLayout>
{#if selectHidden}
<ShowHide
on:doneClick={handleDoneClick}
on:closeClick={handleCloseClick}
on:reset-visibility={handleResetVisibility}
on:toggle-visibility={handleToggleVisibility}
on:done={handleDoneClick}
on:close={handleCloseClick}
on:reset={handleResetVisibility}
on:change={handleToggleVisibility}
bind:showLoadingSpinner
bind:toggleVisibility
>

View file

@ -374,7 +374,7 @@
{/if}
{#if viewMode === ViewMode.MERGE_PEOPLE}
<MergeFaceSelector person={data.person} on:go-back={handleGoBack} on:merge={handleMerge} />
<MergeFaceSelector person={data.person} on:back={handleGoBack} on:merge={handleMerge} />
{/if}
<header>
@ -398,7 +398,7 @@
</AssetSelectControlBar>
{:else}
{#if viewMode === ViewMode.VIEW_ASSETS || viewMode === ViewMode.SUGGEST_MERGE || viewMode === ViewMode.BIRTH_DATE}
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close-button-click={() => goto(previousRoute)}>
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close={() => goto(previousRoute)}>
<svelte:fragment slot="trailing">
<AssetSelectContextMenu icon={mdiDotsVertical} title="Menu">
<MenuOption text="Change feature photo" on:click={() => (viewMode = ViewMode.SELECT_PERSON)} />
@ -414,7 +414,7 @@
{/if}
{#if viewMode === ViewMode.SELECT_PERSON}
<ControlAppBar on:close-button-click={() => (viewMode = ViewMode.VIEW_ASSETS)}>
<ControlAppBar on:close={() => (viewMode = ViewMode.VIEW_ASSETS)}>
<svelte:fragment slot="leading">Select feature photo</svelte:fragment>
</ControlAppBar>
{/if}

View file

@ -130,7 +130,7 @@
</AssetSelectControlBar>
</div>
{:else}
<ControlAppBar on:close-button-click={() => goto(previousRoute)} backIcon={mdiArrowLeft}>
<ControlAppBar on:close={() => goto(previousRoute)} backIcon={mdiArrowLeft}>
<div class="w-full flex-1 pl-4">
<SearchBar grayTheme={false} value={term} />
</div>

View file

@ -53,7 +53,7 @@
};
</script>
<ControlAppBar backIcon={mdiArrowLeft} on:close-button-click={() => goto(AppRoute.SHARING)}>
<ControlAppBar backIcon={mdiArrowLeft} on:close={() => goto(AppRoute.SHARING)}>
<svelte:fragment slot="leading">Shared links</svelte:fragment>
</ControlAppBar>

View file

@ -110,7 +110,7 @@
<section class="w-full pb-28 lg:w-[850px]">
{#if shouldShowCreateUserForm}
<FullScreenModal on:clickOutside={() => (shouldShowCreateUserForm = false)}>
<CreateUserForm on:user-created={onUserCreated} on:cancel={() => (shouldShowCreateUserForm = false)} />
<CreateUserForm on:submit={onUserCreated} on:cancel={() => (shouldShowCreateUserForm = false)} />
</FullScreenModal>
{/if}
@ -120,7 +120,7 @@
user={selectedUser}
canResetPassword={selectedUser?.id !== $user.id}
on:editSuccess={onEditUserSuccess}
on:reset-password-success={onEditPasswordSuccess}
on:resetPasswordSuccess={onEditPasswordSuccess}
on:close={() => (shouldShowEditUserForm = false)}
/>
</FullScreenModal>
@ -129,8 +129,8 @@
{#if shouldShowDeleteConfirmDialog}
<DeleteConfirmDialog
user={selectedUser}
on:user-delete-success={onUserDeleteSuccess}
on:user-delete-fail={onUserDeleteFail}
on:succes={onUserDeleteSuccess}
on:fail={onUserDeleteFail}
on:cancel={() => (shouldShowDeleteConfirmDialog = false)}
/>
{/if}
@ -138,8 +138,8 @@
{#if shouldShowRestoreDialog}
<RestoreDialogue
user={selectedUser}
on:user-restore-success={onUserRestoreSuccess}
on:user-restore-fail={onUserRestoreFail}
on:success={onUserRestoreSuccess}
on:fail={onUserRestoreFail}
on:cancel={() => (shouldShowRestoreDialog = false)}
/>
{/if}

View file

@ -18,7 +18,7 @@
<LoginForm
on:success={() => goto(AppRoute.PHOTOS, { invalidateAll: true })}
on:first-login={() => goto(AppRoute.AUTH_CHANGE_PASSWORD)}
on:firstLogin={() => goto(AppRoute.AUTH_CHANGE_PASSWORD)}
/>
</FullscreenContainer>
{/if}