mirror of
https://github.com/immich-app/immich.git
synced 2025-12-16 01:30:47 -08:00
Compare commits
10 Commits
v1.125.5
...
asset-user
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7277ea3d7a | ||
|
|
da580d4685 | ||
|
|
cb6d94c7a7 | ||
|
|
060300de8a | ||
|
|
c2ba1cc202 | ||
|
|
08db77db23 | ||
|
|
92dff839d0 | ||
|
|
fe1e09e51f | ||
|
|
f44669447f | ||
|
|
92412ca2f7 |
6
cli/package-lock.json
generated
6
cli/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.46",
|
"version": "2.2.47",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.46",
|
"version": "2.2.47",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.46",
|
"version": "2.2.47",
|
||||||
"description": "Command Line Interface (CLI) for Immich",
|
"description": "Command Line Interface (CLI) for Immich",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": "./dist/index.js",
|
"exports": "./dist/index.js",
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ const projects: CommunityProjectProps[] = [
|
|||||||
description: 'Downloads a configurable number of random photos based on people or album ID.',
|
description: 'Downloads a configurable number of random photos based on people or album ID.',
|
||||||
url: 'https://github.com/jon6fingrs/immich-dl',
|
url: 'https://github.com/jon6fingrs/immich-dl',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Immich Upload Optimizer',
|
||||||
|
description: 'Automatically optimize files uploaded to Immich in order to save storage space',
|
||||||
|
url: 'https://github.com/miguelangel-nubla/immich-upload-optimizer',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element {
|
function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element {
|
||||||
|
|||||||
4
docs/static/archived-versions.json
vendored
4
docs/static/archived-versions.json
vendored
@@ -1,4 +1,8 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"label": "v1.125.6",
|
||||||
|
"url": "https://v1.125.6.archive.immich.app"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "v1.125.5",
|
"label": "v1.125.5",
|
||||||
"url": "https://v1.125.5.archive.immich.app"
|
"url": "https://v1.125.5.archive.immich.app"
|
||||||
|
|||||||
8
e2e/package-lock.json
generated
8
e2e/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
},
|
},
|
||||||
"../cli": {
|
"../cli": {
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.46",
|
"version": "2.2.47",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -701,6 +701,20 @@ describe('/asset', () => {
|
|||||||
expect(status).toEqual(200);
|
expect(status).toEqual(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set the negative rating', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/assets/${user1Assets[0].id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ rating: -1 });
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
id: user1Assets[0].id,
|
||||||
|
exifInfo: expect.objectContaining({
|
||||||
|
rating: -1,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
it('should reject invalid rating', async () => {
|
it('should reject invalid rating', async () => {
|
||||||
for (const test of [{ rating: 7 }, { rating: 3.5 }, { rating: null }]) {
|
for (const test of [{ rating: 7 }, { rating: 3.5 }, { rating: null }]) {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "machine-learning"
|
name = "machine-learning"
|
||||||
version = "1.125.5"
|
version = "1.125.6"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 181,
|
"android.injected.version.code" => 182,
|
||||||
"android.injected.version.name" => "1.125.5",
|
"android.injected.version.name" => "1.125.6",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
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')
|
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')
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
"action_common_select": "Вибрати",
|
"action_common_select": "Вибрати",
|
||||||
"action_common_update": "Оновити",
|
"action_common_update": "Оновити",
|
||||||
"add_a_name": "Додати ім'я",
|
"add_a_name": "Додати ім'я",
|
||||||
"add_endpoint": "Add endpoint",
|
"add_endpoint": "Додати кінцеву точку",
|
||||||
"add_to_album_bottom_sheet_added": "Додано до {album}",
|
"add_to_album_bottom_sheet_added": "Додано до {album}",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Вже є в {album}",
|
"add_to_album_bottom_sheet_already_exists": "Вже є в {album}",
|
||||||
"advanced_settings_log_level_title": "Log level: {}",
|
"advanced_settings_log_level_title": "Рівень журналу: {}",
|
||||||
"advanced_settings_prefer_remote_subtitle": "Деякі пристрої вельми повільно завантажують мініатюри із елементів на пристрої. Активуйте для завантаження віддалених мініатюр натомість.",
|
"advanced_settings_prefer_remote_subtitle": "Деякі пристрої вельми повільно завантажують мініатюри із елементів на пристрої. Активуйте для завантаження віддалених мініатюр натомість.",
|
||||||
"advanced_settings_prefer_remote_title": "Перевага віддаленим зображенням",
|
"advanced_settings_prefer_remote_title": "Перевага віддаленим зображенням",
|
||||||
"advanced_settings_proxy_headers_subtitle": "Визначте заголовки проксі-сервера, які Immich має надсилати з кожним мережевим запитом.",
|
"advanced_settings_proxy_headers_subtitle": "Визначте заголовки проксі-сервера, які Immich має надсилати з кожним мережевим запитом.",
|
||||||
@@ -66,12 +66,12 @@
|
|||||||
"assets_restored_successfully": "{} елемент(и) успішно відновлено",
|
"assets_restored_successfully": "{} елемент(и) успішно відновлено",
|
||||||
"assets_trashed": "{} елемент(и) поміщено до кошика",
|
"assets_trashed": "{} елемент(и) поміщено до кошика",
|
||||||
"assets_trashed_from_server": "{} елемент(и) поміщено до кошика на сервері Immich",
|
"assets_trashed_from_server": "{} елемент(и) поміщено до кошика на сервері Immich",
|
||||||
"asset_viewer_settings_subtitle": "Manage your gallery viewer settings",
|
"asset_viewer_settings_subtitle": "Керувати налаштуваннями переглядача галереї",
|
||||||
"asset_viewer_settings_title": "Переглядач зображень",
|
"asset_viewer_settings_title": "Переглядач зображень",
|
||||||
"automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere",
|
"automatic_endpoint_switching_subtitle": "Підключатися локально через визначену Wi-Fi мережу, коли вона доступна, і використовувати альтернативні з'єднання в інших випадках",
|
||||||
"automatic_endpoint_switching_title": "Automatic URL switching",
|
"automatic_endpoint_switching_title": "Автоперемикання URL-адрес",
|
||||||
"background_location_permission": "Background location permission",
|
"background_location_permission": "Дозвіл на місцезнаходження у фоні",
|
||||||
"background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name",
|
"background_location_permission_content": "Щоб перемикатися між мережами під час роботи у фоновому режимі, Immich *завжди* повинен мати доступ до точного місцезнаходження, щоб додаток міг зчитувати назву Wi-Fi мережі",
|
||||||
"backup_album_selection_page_albums_device": "Альбоми на пристрої ({})",
|
"backup_album_selection_page_albums_device": "Альбоми на пристрої ({})",
|
||||||
"backup_album_selection_page_albums_tap": "Торкніться, щоб включити,\nторкніться двічі, щоб виключити",
|
"backup_album_selection_page_albums_tap": "Торкніться, щоб включити,\nторкніться двічі, щоб виключити",
|
||||||
"backup_album_selection_page_assets_scatter": "Елементи можуть належати до кількох альбомів водночас. Таким чином, альбоми можуть бути включені або вилучені під час резервного копіювання.",
|
"backup_album_selection_page_assets_scatter": "Елементи можуть належати до кількох альбомів водночас. Таким чином, альбоми можуть бути включені або вилучені під час резервного копіювання.",
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
"backup_manual_success": "Успіх",
|
"backup_manual_success": "Успіх",
|
||||||
"backup_manual_title": "Стан завантаження",
|
"backup_manual_title": "Стан завантаження",
|
||||||
"backup_options_page_title": "Резервне копіювання",
|
"backup_options_page_title": "Резервне копіювання",
|
||||||
"backup_setting_subtitle": "Manage background and foreground upload settings",
|
"backup_setting_subtitle": "Керування завантаженням у фоновому та передньому режимах",
|
||||||
"cache_settings_album_thumbnails": "Мініатюри сторінок бібліотеки ({} елементи)",
|
"cache_settings_album_thumbnails": "Мініатюри сторінок бібліотеки ({} елементи)",
|
||||||
"cache_settings_clear_cache_button": "Очистити кеш",
|
"cache_settings_clear_cache_button": "Очистити кеш",
|
||||||
"cache_settings_clear_cache_button_title": "Очищає кеш програми. Це суттєво знизить продуктивність програми, доки кеш не буде перебудовано.",
|
"cache_settings_clear_cache_button_title": "Очищає кеш програми. Це суттєво знизить продуктивність програми, доки кеш не буде перебудовано.",
|
||||||
@@ -156,17 +156,17 @@
|
|||||||
"cache_settings_tile_subtitle": "Керування поведінкою локального сховища",
|
"cache_settings_tile_subtitle": "Керування поведінкою локального сховища",
|
||||||
"cache_settings_tile_title": "Локальне сховище",
|
"cache_settings_tile_title": "Локальне сховище",
|
||||||
"cache_settings_title": "Налаштування кешування",
|
"cache_settings_title": "Налаштування кешування",
|
||||||
"cancel": "Cancel",
|
"cancel": "Скасувати",
|
||||||
"canceled": "Canceled",
|
"canceled": "Скасовано",
|
||||||
"change_display_order": "Change display order",
|
"change_display_order": "Змінити порядок відображення",
|
||||||
"change_password_form_confirm_password": "Підтвердити пароль",
|
"change_password_form_confirm_password": "Підтвердити пароль",
|
||||||
"change_password_form_description": "Привіт {name},\n\nВи або або вперше входите у систему, або було зроблено запит на зміну вашого пароля. \nВведіть ваш новий пароль.",
|
"change_password_form_description": "Привіт {name},\n\nВи або або вперше входите у систему, або було зроблено запит на зміну вашого пароля. \nВведіть ваш новий пароль.",
|
||||||
"change_password_form_new_password": "Новий пароль",
|
"change_password_form_new_password": "Новий пароль",
|
||||||
"change_password_form_password_mismatch": "Паролі не співпадають",
|
"change_password_form_password_mismatch": "Паролі не співпадають",
|
||||||
"change_password_form_reenter_new_password": "Повторіть новий пароль",
|
"change_password_form_reenter_new_password": "Повторіть новий пароль",
|
||||||
"check_corrupt_asset_backup": "Check for corrupt asset backups",
|
"check_corrupt_asset_backup": "Перевірити пошкоджені резервні копії",
|
||||||
"check_corrupt_asset_backup_button": "Perform check",
|
"check_corrupt_asset_backup_button": "Виконати перевірку",
|
||||||
"check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.",
|
"check_corrupt_asset_backup_description": "Виконуйте цю перевірку лише через Wi-Fi та після того, як усі активи будуть заархівовані. Процес може зайняти кілька хвилин.",
|
||||||
"client_cert_dialog_msg_confirm": "OK",
|
"client_cert_dialog_msg_confirm": "OK",
|
||||||
"client_cert_enter_password": "Введіть пароль",
|
"client_cert_enter_password": "Введіть пароль",
|
||||||
"client_cert_import": "Імпорт",
|
"client_cert_import": "Імпорт",
|
||||||
@@ -181,7 +181,7 @@
|
|||||||
"common_create_new_album": "Створити новий альбом",
|
"common_create_new_album": "Створити новий альбом",
|
||||||
"common_server_error": "Будь ласка, перевірте з'єднання, переконайтеся, що сервер доступний і версія програми/сервера сумісна.",
|
"common_server_error": "Будь ласка, перевірте з'єднання, переконайтеся, що сервер доступний і версія програми/сервера сумісна.",
|
||||||
"common_shared": "Спільні",
|
"common_shared": "Спільні",
|
||||||
"completed": "Completed",
|
"completed": "Завершено",
|
||||||
"contextual_search": "Схід сонця на пляжі",
|
"contextual_search": "Схід сонця на пляжі",
|
||||||
"control_bottom_app_bar_add_to_album": "Додати у альбом",
|
"control_bottom_app_bar_add_to_album": "Додати у альбом",
|
||||||
"control_bottom_app_bar_album_info": "{} елементи",
|
"control_bottom_app_bar_album_info": "{} елементи",
|
||||||
@@ -199,7 +199,7 @@
|
|||||||
"control_bottom_app_bar_share": "Поділитися",
|
"control_bottom_app_bar_share": "Поділитися",
|
||||||
"control_bottom_app_bar_share_to": "Поділитися",
|
"control_bottom_app_bar_share_to": "Поділитися",
|
||||||
"control_bottom_app_bar_stack": "Стек",
|
"control_bottom_app_bar_stack": "Стек",
|
||||||
"control_bottom_app_bar_trash_from_immich": "Перемістити до кошика",
|
"control_bottom_app_bar_trash_from_immich": "В кошик",
|
||||||
"control_bottom_app_bar_unarchive": "Розархівувати",
|
"control_bottom_app_bar_unarchive": "Розархівувати",
|
||||||
"control_bottom_app_bar_unfavorite": "Видалити з улюблених",
|
"control_bottom_app_bar_unfavorite": "Видалити з улюблених",
|
||||||
"control_bottom_app_bar_upload": "Завантажити",
|
"control_bottom_app_bar_upload": "Завантажити",
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
"crop": "Кадрувати",
|
"crop": "Кадрувати",
|
||||||
"curated_location_page_title": "Місця",
|
"curated_location_page_title": "Місця",
|
||||||
"curated_object_page_title": "Речі",
|
"curated_object_page_title": "Речі",
|
||||||
"current_server_address": "Current server address",
|
"current_server_address": "Поточна адреса сервера",
|
||||||
"daily_title_text_date": "E, MMM dd",
|
"daily_title_text_date": "E, MMM dd",
|
||||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||||
"date_format": "E, LLL d, y • h:mm a",
|
"date_format": "E, LLL d, y • h:mm a",
|
||||||
@@ -250,10 +250,10 @@
|
|||||||
"edit_date_time_dialog_timezone": "Часовий пояс",
|
"edit_date_time_dialog_timezone": "Часовий пояс",
|
||||||
"edit_image_title": "Редагувати",
|
"edit_image_title": "Редагувати",
|
||||||
"edit_location_dialog_title": "Місцезнаходження",
|
"edit_location_dialog_title": "Місцезнаходження",
|
||||||
"end_date": "End date",
|
"end_date": "Дата завершення",
|
||||||
"enqueued": "Enqueued",
|
"enqueued": "В черзі",
|
||||||
"enter_wifi_name": "Enter WiFi name",
|
"enter_wifi_name": "Введіть назву Wi-Fi",
|
||||||
"error_change_sort_album": "Failed to change album sort order",
|
"error_change_sort_album": "Не вдалося змінити порядок сортування альбому",
|
||||||
"error_saving_image": "Помилка: {}",
|
"error_saving_image": "Помилка: {}",
|
||||||
"exif_bottom_sheet_description": "Додати опис...",
|
"exif_bottom_sheet_description": "Додати опис...",
|
||||||
"exif_bottom_sheet_details": "ПОДРОБИЦІ",
|
"exif_bottom_sheet_details": "ПОДРОБИЦІ",
|
||||||
@@ -265,16 +265,16 @@
|
|||||||
"experimental_settings_new_asset_list_title": "Експериментальний макет знімків",
|
"experimental_settings_new_asset_list_title": "Експериментальний макет знімків",
|
||||||
"experimental_settings_subtitle": "На власний ризик!",
|
"experimental_settings_subtitle": "На власний ризик!",
|
||||||
"experimental_settings_title": "Експериментальні",
|
"experimental_settings_title": "Експериментальні",
|
||||||
"external_network": "External network",
|
"external_network": "Зовнішня мережа",
|
||||||
"external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom",
|
"external_network_sheet_info": "Якщо ви не підключені до бажаної Wi-Fi мережі, додаток підключиться до сервера через першу доступну URL-адресу зверху вниз",
|
||||||
"failed": "Failed",
|
"failed": "Не вдалося",
|
||||||
"favorites": "Вибране",
|
"favorites": "Вибране",
|
||||||
"favorites_page_no_favorites": "Немає улюблених елементів",
|
"favorites_page_no_favorites": "Немає улюблених елементів",
|
||||||
"favorites_page_title": "Улюблені",
|
"favorites_page_title": "Улюблені",
|
||||||
"filename_search": "Ім'я або розширення файлу",
|
"filename_search": "Ім'я або розширення файлу",
|
||||||
"filter": "Фільтр",
|
"filter": "Фільтр",
|
||||||
"get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network",
|
"get_wifiname_error": "Не вдалося отримати назву Wi-Fi мережі. Переконайтеся, що ви надали необхідні дозволи та підключені до Wi-Fi мережі",
|
||||||
"grant_permission": "Grant permission",
|
"grant_permission": "Надати дозвіл",
|
||||||
"haptic_feedback_switch": "Увімкнути тактильну віддачу",
|
"haptic_feedback_switch": "Увімкнути тактильну віддачу",
|
||||||
"haptic_feedback_title": "Тактильна віддача",
|
"haptic_feedback_title": "Тактильна віддача",
|
||||||
"header_settings_add_header_tip": "Додати заголовок",
|
"header_settings_add_header_tip": "Додати заголовок",
|
||||||
@@ -320,10 +320,10 @@
|
|||||||
"library_page_sort_most_oldest_photo": "Найдавніші фото",
|
"library_page_sort_most_oldest_photo": "Найдавніші фото",
|
||||||
"library_page_sort_most_recent_photo": "Найновіші фото",
|
"library_page_sort_most_recent_photo": "Найновіші фото",
|
||||||
"library_page_sort_title": "Назва альбому",
|
"library_page_sort_title": "Назва альбому",
|
||||||
"local_network": "Local network",
|
"local_network": "Локальна мережа",
|
||||||
"local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network",
|
"local_network_sheet_info": "Додаток підключатиметься до сервера через цю URL-адресу при використанні вказаної Wi-Fi мережі",
|
||||||
"location_permission": "Location permission",
|
"location_permission": "Дозвіл до місцезнаходження",
|
||||||
"location_permission_content": "In order to use the auto-switching feature, Immich needs precise location permission so it can read the current WiFi network's name",
|
"location_permission_content": "Для використання функції автоперемикання Immich потрібен дозвіл на точне місцезнаходження, щоб зчитувати назву поточної Wi-Fi мережі",
|
||||||
"location_picker_choose_on_map": "Обрати на мапі",
|
"location_picker_choose_on_map": "Обрати на мапі",
|
||||||
"location_picker_latitude": "Широта",
|
"location_picker_latitude": "Широта",
|
||||||
"location_picker_latitude_error": "Вкажіть дійсну широту",
|
"location_picker_latitude_error": "Вкажіть дійсну широту",
|
||||||
@@ -393,8 +393,8 @@
|
|||||||
"multiselect_grid_edit_date_time_err_read_only": "Неможливо редагувати дату елементів лише для читання, пропущено",
|
"multiselect_grid_edit_date_time_err_read_only": "Неможливо редагувати дату елементів лише для читання, пропущено",
|
||||||
"multiselect_grid_edit_gps_err_read_only": "Неможливо редагувати місцезнаходження елементів лише для читання, пропущено",
|
"multiselect_grid_edit_gps_err_read_only": "Неможливо редагувати місцезнаходження елементів лише для читання, пропущено",
|
||||||
"my_albums": "Мої альбоми",
|
"my_albums": "Мої альбоми",
|
||||||
"networking_settings": "Networking",
|
"networking_settings": "Мережа",
|
||||||
"networking_subtitle": "Manage the server endpoint settings",
|
"networking_subtitle": "Керувати налаштуваннями кінцевої точки сервера",
|
||||||
"no_assets_to_show": "Елементи відсутні",
|
"no_assets_to_show": "Елементи відсутні",
|
||||||
"no_name": "Без імені",
|
"no_name": "Без імені",
|
||||||
"notification_permission_dialog_cancel": "Скасувати",
|
"notification_permission_dialog_cancel": "Скасувати",
|
||||||
@@ -403,7 +403,7 @@
|
|||||||
"notification_permission_list_tile_content": "Надати дозвіл для сповіщень.",
|
"notification_permission_list_tile_content": "Надати дозвіл для сповіщень.",
|
||||||
"notification_permission_list_tile_enable_button": "Увімкнути Сповіщення",
|
"notification_permission_list_tile_enable_button": "Увімкнути Сповіщення",
|
||||||
"notification_permission_list_tile_title": "Дозвіл на Сповіщення",
|
"notification_permission_list_tile_title": "Дозвіл на Сповіщення",
|
||||||
"not_selected": "Not selected",
|
"not_selected": "Не вибрано",
|
||||||
"on_this_device": "На цьому пристрої",
|
"on_this_device": "На цьому пристрої",
|
||||||
"partner_list_user_photos": "Фотографії {user}",
|
"partner_list_user_photos": "Фотографії {user}",
|
||||||
"partner_list_view_all": "Переглянути усі",
|
"partner_list_view_all": "Переглянути усі",
|
||||||
@@ -417,7 +417,7 @@
|
|||||||
"partner_page_stop_sharing_title": "Припинити надання ваших знімків?",
|
"partner_page_stop_sharing_title": "Припинити надання ваших знімків?",
|
||||||
"partner_page_title": "Партнер",
|
"partner_page_title": "Партнер",
|
||||||
"partners": "\nПартнери",
|
"partners": "\nПартнери",
|
||||||
"paused": "Paused",
|
"paused": "Призупинено",
|
||||||
"people": "Люди",
|
"people": "Люди",
|
||||||
"permission_onboarding_back": "Назад",
|
"permission_onboarding_back": "Назад",
|
||||||
"permission_onboarding_continue_anyway": "Все одно продовжити",
|
"permission_onboarding_continue_anyway": "Все одно продовжити",
|
||||||
@@ -430,7 +430,7 @@
|
|||||||
"permission_onboarding_permission_limited": "Обмежений доступ. Аби дозволити Immich резервне копіювання та керування вашою галереєю, надайте доступ до знімків та відео у Налаштуваннях",
|
"permission_onboarding_permission_limited": "Обмежений доступ. Аби дозволити Immich резервне копіювання та керування вашою галереєю, надайте доступ до знімків та відео у Налаштуваннях",
|
||||||
"permission_onboarding_request": "Immich потребує доступу до ваших знімків та відео.",
|
"permission_onboarding_request": "Immich потребує доступу до ваших знімків та відео.",
|
||||||
"places": "Місця",
|
"places": "Місця",
|
||||||
"preferences_settings_subtitle": "Manage the app's preferences",
|
"preferences_settings_subtitle": "Керувати налаштуваннями додатка",
|
||||||
"preferences_settings_title": "Параметри",
|
"preferences_settings_title": "Параметри",
|
||||||
"profile_drawer_app_logs": "Журнал",
|
"profile_drawer_app_logs": "Журнал",
|
||||||
"profile_drawer_client_out_of_date_major": "Мобільний додаток застарів. Будь ласка, оновіть до останньої мажорної версії.",
|
"profile_drawer_client_out_of_date_major": "Мобільний додаток застарів. Будь ласка, оновіть до останньої мажорної версії.",
|
||||||
@@ -445,7 +445,7 @@
|
|||||||
"profile_drawer_trash": "Кошик",
|
"profile_drawer_trash": "Кошик",
|
||||||
"recently_added": "Нещодавно додані",
|
"recently_added": "Нещодавно додані",
|
||||||
"recently_added_page_title": "Нещодавні",
|
"recently_added_page_title": "Нещодавні",
|
||||||
"save": "Save",
|
"save": "Зберегти",
|
||||||
"save_to_gallery": "Зберегти в галерею",
|
"save_to_gallery": "Зберегти в галерею",
|
||||||
"scaffold_body_error_occurred": "Виникла помилка",
|
"scaffold_body_error_occurred": "Виникла помилка",
|
||||||
"search_albums": "Пошук альбому",
|
"search_albums": "Пошук альбому",
|
||||||
@@ -491,7 +491,7 @@
|
|||||||
"search_page_places": "Місця",
|
"search_page_places": "Місця",
|
||||||
"search_page_recently_added": "Нещодавно додані",
|
"search_page_recently_added": "Нещодавно додані",
|
||||||
"search_page_screenshots": "Знімки екрану",
|
"search_page_screenshots": "Знімки екрану",
|
||||||
"search_page_search_photos_videos": "Search for your photos and videos",
|
"search_page_search_photos_videos": "Шукайте ваші фотографії та відео",
|
||||||
"search_page_selfies": "Селфі",
|
"search_page_selfies": "Селфі",
|
||||||
"search_page_things": "Речі",
|
"search_page_things": "Речі",
|
||||||
"search_page_videos": "Відео",
|
"search_page_videos": "Відео",
|
||||||
@@ -504,7 +504,7 @@
|
|||||||
"select_additional_user_for_sharing_page_suggestions": "Пропозиції",
|
"select_additional_user_for_sharing_page_suggestions": "Пропозиції",
|
||||||
"select_user_for_sharing_page_err_album": "Не вдалося створити альбом",
|
"select_user_for_sharing_page_err_album": "Не вдалося створити альбом",
|
||||||
"select_user_for_sharing_page_share_suggestions": "Пропозиції",
|
"select_user_for_sharing_page_share_suggestions": "Пропозиції",
|
||||||
"server_endpoint": "Server Endpoint",
|
"server_endpoint": "Кінцева точка сервера",
|
||||||
"server_info_box_app_version": "Версія додатка",
|
"server_info_box_app_version": "Версія додатка",
|
||||||
"server_info_box_latest_release": "Остання версія",
|
"server_info_box_latest_release": "Остання версія",
|
||||||
"server_info_box_server_url": "URL сервера",
|
"server_info_box_server_url": "URL сервера",
|
||||||
@@ -516,7 +516,7 @@
|
|||||||
"setting_image_viewer_preview_title": "Завантажувати зображення попереднього перегляду",
|
"setting_image_viewer_preview_title": "Завантажувати зображення попереднього перегляду",
|
||||||
"setting_image_viewer_title": "Зображення",
|
"setting_image_viewer_title": "Зображення",
|
||||||
"setting_languages_apply": "Застосувати",
|
"setting_languages_apply": "Застосувати",
|
||||||
"setting_languages_subtitle": "Change the app's language",
|
"setting_languages_subtitle": "Змінити мову додатка",
|
||||||
"setting_languages_title": "Мова",
|
"setting_languages_title": "Мова",
|
||||||
"setting_notifications_notify_failures_grace_period": "Повідомити про помилки фонового резервного копіювання: {}",
|
"setting_notifications_notify_failures_grace_period": "Повідомити про помилки фонового резервного копіювання: {}",
|
||||||
"setting_notifications_notify_hours": "{} годин",
|
"setting_notifications_notify_hours": "{} годин",
|
||||||
@@ -534,8 +534,8 @@
|
|||||||
"settings_require_restart": "Перезавантажте програму для застосування цього налаштування",
|
"settings_require_restart": "Перезавантажте програму для застосування цього налаштування",
|
||||||
"setting_video_viewer_looping_subtitle": "Увімкнути циклічне відтворення відео",
|
"setting_video_viewer_looping_subtitle": "Увімкнути циклічне відтворення відео",
|
||||||
"setting_video_viewer_looping_title": "Циклічне відтворення",
|
"setting_video_viewer_looping_title": "Циклічне відтворення",
|
||||||
"setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.",
|
"setting_video_viewer_original_video_subtitle": "При трансляції відео з сервера відтворювати оригінал, навіть якщо доступне транскодоване відео. Це може призвести до буферизації. Відео, доступні локально, відтворюються в оригінальній якості, незалежно від цього налаштування.",
|
||||||
"setting_video_viewer_original_video_title": "Force original video",
|
"setting_video_viewer_original_video_title": "Примусово відтворювати оригінальне відео",
|
||||||
"setting_video_viewer_title": "Відео",
|
"setting_video_viewer_title": "Відео",
|
||||||
"share_add": "Додати",
|
"share_add": "Додати",
|
||||||
"share_add_photos": "Додати знімки",
|
"share_add_photos": "Додати знімки",
|
||||||
@@ -554,7 +554,7 @@
|
|||||||
"shared_album_section_people_owner_label": "Власник",
|
"shared_album_section_people_owner_label": "Власник",
|
||||||
"shared_album_section_people_title": "ЛЮДИ",
|
"shared_album_section_people_title": "ЛЮДИ",
|
||||||
"share_dialog_preparing": "Підготовка...",
|
"share_dialog_preparing": "Підготовка...",
|
||||||
"shared_intent_upload_button_progress_text": "{} / {} Uploaded",
|
"shared_intent_upload_button_progress_text": "{} / {} Завантажено",
|
||||||
"shared_link_app_bar_title": "Спільні посилання",
|
"shared_link_app_bar_title": "Спільні посилання",
|
||||||
"shared_link_clipboard_copied_massage": "Скопійовано в буфер обміну",
|
"shared_link_clipboard_copied_massage": "Скопійовано в буфер обміну",
|
||||||
"shared_link_clipboard_text": "Посилання: {}\nПароль: {}",
|
"shared_link_clipboard_text": "Посилання: {}\nПароль: {}",
|
||||||
@@ -649,15 +649,15 @@
|
|||||||
"trash_page_select_assets_btn": "Вибрані елементи",
|
"trash_page_select_assets_btn": "Вибрані елементи",
|
||||||
"trash_page_select_btn": "Вибрати",
|
"trash_page_select_btn": "Вибрати",
|
||||||
"trash_page_title": "Кошик ({})",
|
"trash_page_title": "Кошик ({})",
|
||||||
"upload": "Upload",
|
"upload": "Завантажити",
|
||||||
"upload_dialog_cancel": "Скасувати",
|
"upload_dialog_cancel": "Скасувати",
|
||||||
"upload_dialog_info": "Бажаєте створити резервну копію вибраних елементів на сервері?",
|
"upload_dialog_info": "Бажаєте створити резервну копію вибраних елементів на сервері?",
|
||||||
"upload_dialog_ok": "Завантажити",
|
"upload_dialog_ok": "Завантажити",
|
||||||
"upload_dialog_title": "Завантажити Елементи",
|
"upload_dialog_title": "Завантажити Елементи",
|
||||||
"uploading": "Uploading",
|
"uploading": "Завантажується",
|
||||||
"upload_to_immich": "Upload to Immich ({})",
|
"upload_to_immich": "Завантажити в Immich ({})",
|
||||||
"use_current_connection": "use current connection",
|
"use_current_connection": "використовувати поточне з'єднання",
|
||||||
"validate_endpoint_error": "Please enter a valid URL",
|
"validate_endpoint_error": "Будь ласка, введіть дійсну URL-адресу.",
|
||||||
"version_announcement_overlay_ack": "Прийняти",
|
"version_announcement_overlay_ack": "Прийняти",
|
||||||
"version_announcement_overlay_release_notes": "примітки до випуску",
|
"version_announcement_overlay_release_notes": "примітки до випуску",
|
||||||
"version_announcement_overlay_text_1": "Вітаємо, є новий випуск ",
|
"version_announcement_overlay_text_1": "Вітаємо, є новий випуск ",
|
||||||
@@ -668,6 +668,6 @@
|
|||||||
"viewer_remove_from_stack": "Видалити зі стеку",
|
"viewer_remove_from_stack": "Видалити зі стеку",
|
||||||
"viewer_stack_use_as_main_asset": "Використовувати як основний елементи",
|
"viewer_stack_use_as_main_asset": "Використовувати як основний елементи",
|
||||||
"viewer_unstack": "Розібрати стек",
|
"viewer_unstack": "Розібрати стек",
|
||||||
"wifi_name": "WiFi Name",
|
"wifi_name": "Назва Wi-Fi",
|
||||||
"your_wifi_name": "Your WiFi name"
|
"your_wifi_name": "Ваша Wi-Fi мережа"
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ platform :ios do
|
|||||||
desc "iOS Release"
|
desc "iOS Release"
|
||||||
lane :release do
|
lane :release do
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: "1.125.5"
|
version_number: "1.125.6"
|
||||||
)
|
)
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number + 1,
|
build_number: latest_testflight_build_number + 1,
|
||||||
|
|||||||
2
mobile/openapi/README.md
generated
2
mobile/openapi/README.md
generated
@@ -3,7 +3,7 @@ Immich API
|
|||||||
|
|
||||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||||
|
|
||||||
- API version: 1.125.5
|
- API version: 1.125.6
|
||||||
- Generator version: 7.8.0
|
- Generator version: 7.8.0
|
||||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class AssetBulkUpdateDto {
|
|||||||
///
|
///
|
||||||
num? longitude;
|
num? longitude;
|
||||||
|
|
||||||
/// Minimum value: 0
|
/// Minimum value: -1
|
||||||
/// Maximum value: 5
|
/// Maximum value: 5
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
|||||||
2
mobile/openapi/lib/model/update_asset_dto.dart
generated
2
mobile/openapi/lib/model/update_asset_dto.dart
generated
@@ -73,7 +73,7 @@ class UpdateAssetDto {
|
|||||||
///
|
///
|
||||||
num? longitude;
|
num? longitude;
|
||||||
|
|
||||||
/// Minimum value: 0
|
/// Minimum value: -1
|
||||||
/// Maximum value: 5
|
/// Maximum value: 5
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name: immich_mobile
|
|||||||
description: Immich - selfhosted backup media file on mobile phone
|
description: Immich - selfhosted backup media file on mobile phone
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.125.5+181
|
version: 1.125.6+182
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.3.0 <4.0.0'
|
sdk: '>=3.3.0 <4.0.0'
|
||||||
|
|||||||
@@ -7454,7 +7454,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"title": "Immich",
|
"title": "Immich",
|
||||||
"description": "Immich API",
|
"description": "Immich API",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"contact": {}
|
"contact": {}
|
||||||
},
|
},
|
||||||
"tags": [],
|
"tags": [],
|
||||||
@@ -7951,7 +7951,7 @@
|
|||||||
},
|
},
|
||||||
"rating": {
|
"rating": {
|
||||||
"maximum": 5,
|
"maximum": 5,
|
||||||
"minimum": 0,
|
"minimum": -1,
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -12780,7 +12780,7 @@
|
|||||||
},
|
},
|
||||||
"rating": {
|
"rating": {
|
||||||
"maximum": 5,
|
"maximum": 5,
|
||||||
"minimum": 0,
|
"minimum": -1,
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
4
open-api/typescript-sdk/package-lock.json
generated
4
open-api/typescript-sdk/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./build/index.js",
|
"main": "./build/index.js",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Immich
|
* Immich
|
||||||
* 1.125.5
|
* 1.125.6
|
||||||
* DO NOT MODIFY - This file has been generated using oazapfts.
|
* DO NOT MODIFY - This file has been generated using oazapfts.
|
||||||
* See https://www.npmjs.com/package/oazapfts
|
* See https://www.npmjs.com/package/oazapfts
|
||||||
*/
|
*/
|
||||||
|
|||||||
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/bullmq": "^11.0.0",
|
"@nestjs/bullmq": "^11.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
28
server/src/db.d.ts
vendored
28
server/src/db.d.ts
vendored
@@ -3,21 +3,16 @@
|
|||||||
* Please do not edit it manually.
|
* Please do not edit it manually.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ColumnType } from "kysely";
|
import type { ColumnType } from 'kysely';
|
||||||
|
|
||||||
export type ArrayType<T> = ArrayTypeImpl<T> extends (infer U)[]
|
export type ArrayType<T> = ArrayTypeImpl<T> extends (infer U)[] ? U[] : ArrayTypeImpl<T>;
|
||||||
? U[]
|
|
||||||
: ArrayTypeImpl<T>;
|
|
||||||
|
|
||||||
export type ArrayTypeImpl<T> = T extends ColumnType<infer S, infer I, infer U>
|
export type ArrayTypeImpl<T> = T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S[], I[], U[]> : T[];
|
||||||
? ColumnType<S[], I[], U[]>
|
|
||||||
: T[];
|
|
||||||
|
|
||||||
export type AssetsStatusEnum = "active" | "deleted" | "trashed";
|
export type AssetsStatusEnum = 'active' | 'deleted' | 'trashed';
|
||||||
|
|
||||||
export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
|
export type Generated<T> =
|
||||||
? ColumnType<S, I | undefined, U>
|
T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S, I | undefined, U> : ColumnType<T, T | undefined, T>;
|
||||||
: ColumnType<T, T | undefined, T>;
|
|
||||||
|
|
||||||
export type Int8 = ColumnType<string, bigint | number | string, bigint | number | string>;
|
export type Int8 = ColumnType<string, bigint | number | string, bigint | number | string>;
|
||||||
|
|
||||||
@@ -33,7 +28,7 @@ export type JsonPrimitive = boolean | number | string | null;
|
|||||||
|
|
||||||
export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
|
export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
|
||||||
|
|
||||||
export type Sourcetype = "exif" | "machine-learning";
|
export type Sourcetype = 'exif' | 'machine-learning';
|
||||||
|
|
||||||
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
|
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
|
||||||
|
|
||||||
@@ -154,6 +149,12 @@ export interface AssetStack {
|
|||||||
primaryAssetId: string;
|
primaryAssetId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AssetUser {
|
||||||
|
assetId: string;
|
||||||
|
createdAt: Timestamp;
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Audit {
|
export interface Audit {
|
||||||
action: string;
|
action: string;
|
||||||
createdAt: Generated<Timestamp>;
|
createdAt: Generated<Timestamp>;
|
||||||
@@ -413,6 +414,7 @@ export interface DB {
|
|||||||
asset_files: AssetFiles;
|
asset_files: AssetFiles;
|
||||||
asset_job_status: AssetJobStatus;
|
asset_job_status: AssetJobStatus;
|
||||||
asset_stack: AssetStack;
|
asset_stack: AssetStack;
|
||||||
|
asset_user: AssetUser;
|
||||||
assets: Assets;
|
assets: Assets;
|
||||||
audit: Audit;
|
audit: Audit;
|
||||||
exif: Exif;
|
exif: Exif;
|
||||||
@@ -438,6 +440,6 @@ export interface DB {
|
|||||||
tags_closure: TagsClosure;
|
tags_closure: TagsClosure;
|
||||||
user_metadata: UserMetadata;
|
user_metadata: UserMetadata;
|
||||||
users: Users;
|
users: Users;
|
||||||
"vectors.pg_vector_index_stat": VectorsPgVectorIndexStat;
|
'vectors.pg_vector_index_stat': VectorsPgVectorIndexStat;
|
||||||
version_history: VersionHistory;
|
version_history: VersionHistory;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { albumStub } from 'test/fixtures/album.stub';
|
|||||||
describe('mapAlbum', () => {
|
describe('mapAlbum', () => {
|
||||||
it('should set start and end dates', () => {
|
it('should set start and end dates', () => {
|
||||||
const dto = mapAlbum(albumStub.twoAssets, false);
|
const dto = mapAlbum(albumStub.twoAssets, false);
|
||||||
expect(dto.startDate).toEqual(new Date('2023-02-22T05:06:29.716Z'));
|
expect(dto.startDate).toEqual(new Date('2020-12-31T23:59:00.000Z'));
|
||||||
expect(dto.endDate).toEqual(new Date('2023-02-23T05:06:29.716Z'));
|
expect(dto.endDate).toEqual(new Date('2025-01-01T01:02:03.456Z'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not set start and end dates for empty assets', () => {
|
it('should not set start and end dates for empty assets', () => {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { AuthDto } from 'src/dtos/auth.dto';
|
|||||||
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
|
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
|
||||||
import { AlbumEntity } from 'src/entities/album.entity';
|
import { AlbumEntity } from 'src/entities/album.entity';
|
||||||
import { AlbumUserRole, AssetOrder } from 'src/enum';
|
import { AlbumUserRole, AssetOrder } from 'src/enum';
|
||||||
import { getAssetDateTime } from 'src/utils/date-time';
|
|
||||||
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
|
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
|
||||||
|
|
||||||
export class AlbumInfoDto {
|
export class AlbumInfoDto {
|
||||||
@@ -165,8 +164,8 @@ export const mapAlbum = (entity: AlbumEntity, withAssets: boolean, auth?: AuthDt
|
|||||||
const hasSharedLink = entity.sharedLinks?.length > 0;
|
const hasSharedLink = entity.sharedLinks?.length > 0;
|
||||||
const hasSharedUser = sharedUsers.length > 0;
|
const hasSharedUser = sharedUsers.length > 0;
|
||||||
|
|
||||||
let startDate = getAssetDateTime(assets.at(0));
|
let startDate = assets.at(0)?.localDateTime;
|
||||||
let endDate = getAssetDateTime(assets.at(-1));
|
let endDate = assets.at(-1)?.localDateTime;
|
||||||
// Swap dates if start date is greater than end date.
|
// Swap dates if start date is greater than end date.
|
||||||
if (startDate && endDate && startDate > endDate) {
|
if (startDate && endDate && startDate > endDate) {
|
||||||
[startDate, endDate] = [endDate, startDate];
|
[startDate, endDate] = [endDate, startDate];
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export class UpdateAssetBase {
|
|||||||
@Optional()
|
@Optional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Max(5)
|
@Max(5)
|
||||||
@Min(0)
|
@Min(-1)
|
||||||
rating?: number;
|
rating?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
22
server/src/entities/asset-user.entity.ts
Normal file
22
server/src/entities/asset-user.entity.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
|
import { UserEntity } from 'src/entities/user.entity';
|
||||||
|
import { Column, Entity, Index, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity('asset_user')
|
||||||
|
@Index('IDX_assetId_userId', ['assetId', 'userId'])
|
||||||
|
export class AssetUserEntity {
|
||||||
|
@PrimaryColumn()
|
||||||
|
assetId!: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => AssetEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
|
||||||
|
asset!: AssetEntity;
|
||||||
|
|
||||||
|
@PrimaryColumn()
|
||||||
|
userId!: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => UserEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
|
||||||
|
user!: UserEntity;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
createdAt!: Date;
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { APIKeyEntity } from 'src/entities/api-key.entity';
|
|||||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||||
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||||
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
|
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
|
||||||
|
import { AssetUserEntity } from 'src/entities/asset-user.entity';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
import { AuditEntity } from 'src/entities/audit.entity';
|
import { AuditEntity } from 'src/entities/audit.entity';
|
||||||
import { ExifEntity } from 'src/entities/exif.entity';
|
import { ExifEntity } from 'src/entities/exif.entity';
|
||||||
@@ -34,6 +35,7 @@ export const entities = [
|
|||||||
AssetEntity,
|
AssetEntity,
|
||||||
AssetFaceEntity,
|
AssetFaceEntity,
|
||||||
AssetFileEntity,
|
AssetFileEntity,
|
||||||
|
AssetUserEntity,
|
||||||
AssetJobStatusEntity,
|
AssetJobStatusEntity,
|
||||||
AuditEntity,
|
AuditEntity,
|
||||||
ExifEntity,
|
ExifEntity,
|
||||||
|
|||||||
20
server/src/migrations/1738099775096-AddAssetUserTable.ts
Normal file
20
server/src/migrations/1738099775096-AddAssetUserTable.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class AddAssetUserTable1738099775096 implements MigrationInterface {
|
||||||
|
name = 'AddAssetUserTable1738099775096'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`CREATE TABLE "asset_user" ("assetId" uuid NOT NULL, "userId" uuid NOT NULL, "createdAt" TIMESTAMP NOT NULL, CONSTRAINT "PK_f3d7f17ab93d60e007282726058" PRIMARY KEY ("assetId", "userId"))`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_assetId_userId" ON "asset_user" ("assetId", "userId") `);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_user" ADD CONSTRAINT "FK_07c8478e0936e78b553aaeceedb" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_user" ADD CONSTRAINT "FK_85e2ef24493bdf649dfdfb769a2" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_user" DROP CONSTRAINT "FK_85e2ef24493bdf649dfdfb769a2"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_user" DROP CONSTRAINT "FK_07c8478e0936e78b553aaeceedb"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_assetId_userId"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "asset_user"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -202,8 +202,8 @@ order by
|
|||||||
-- AlbumRepository.getMetadataForIds
|
-- AlbumRepository.getMetadataForIds
|
||||||
select
|
select
|
||||||
"albums"."id" as "albumId",
|
"albums"."id" as "albumId",
|
||||||
min("assets"."fileCreatedAt") as "startDate",
|
min("assets"."localDateTime") as "startDate",
|
||||||
max("assets"."fileCreatedAt") as "endDate",
|
max("assets"."localDateTime") as "endDate",
|
||||||
count("assets"."id")::int as "assetCount"
|
count("assets"."id")::int as "assetCount"
|
||||||
from
|
from
|
||||||
"albums"
|
"albums"
|
||||||
|
|||||||
@@ -127,8 +127,8 @@ export class AlbumRepository implements IAlbumRepository {
|
|||||||
.innerJoin('albums_assets_assets as album_assets', 'album_assets.albumsId', 'albums.id')
|
.innerJoin('albums_assets_assets as album_assets', 'album_assets.albumsId', 'albums.id')
|
||||||
.innerJoin('assets', 'assets.id', 'album_assets.assetsId')
|
.innerJoin('assets', 'assets.id', 'album_assets.assetsId')
|
||||||
.select('albums.id as albumId')
|
.select('albums.id as albumId')
|
||||||
.select((eb) => eb.fn.min('assets.fileCreatedAt').as('startDate'))
|
.select((eb) => eb.fn.min('assets.localDateTime').as('startDate'))
|
||||||
.select((eb) => eb.fn.max('assets.fileCreatedAt').as('endDate'))
|
.select((eb) => eb.fn.max('assets.localDateTime').as('endDate'))
|
||||||
.select((eb) => sql<number>`${eb.fn.count('assets.id')}::int`.as('assetCount'))
|
.select((eb) => sql<number>`${eb.fn.count('assets.id')}::int`.as('assetCount'))
|
||||||
.where('albums.id', 'in', ids)
|
.where('albums.id', 'in', ids)
|
||||||
.where('assets.deletedAt', 'is', null)
|
.where('assets.deletedAt', 'is', null)
|
||||||
|
|||||||
@@ -81,7 +81,24 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
create(asset: Insertable<Assets>): Promise<AssetEntity> {
|
create(asset: Insertable<Assets>): Promise<AssetEntity> {
|
||||||
return this.db.insertInto('assets').values(asset).returningAll().executeTakeFirst() as any as Promise<AssetEntity>;
|
return this.db.transaction().execute(async (tx) => {
|
||||||
|
const newAsset = (await tx
|
||||||
|
.insertInto('assets')
|
||||||
|
.values(asset)
|
||||||
|
.returningAll()
|
||||||
|
.executeTakeFirst()) as any as AssetEntity;
|
||||||
|
|
||||||
|
await tx
|
||||||
|
.insertInto('asset_user')
|
||||||
|
.values({
|
||||||
|
assetId: newAsset.id,
|
||||||
|
userId: newAsset.ownerId,
|
||||||
|
createdAt: new Date(),
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return newAsset;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID, { day: 1, month: 1 }] })
|
@GenerateSql({ params: [DummyValue.UUID, { day: 1, month: 1 }] })
|
||||||
@@ -495,7 +512,6 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
.$if(property === WithoutProperty.THUMBNAIL, (qb) =>
|
.$if(property === WithoutProperty.THUMBNAIL, (qb) =>
|
||||||
qb
|
qb
|
||||||
.innerJoin('asset_job_status as job_status', 'assetId', 'assets.id')
|
.innerJoin('asset_job_status as job_status', 'assetId', 'assets.id')
|
||||||
.select(withFiles)
|
|
||||||
.where('assets.isVisible', '=', true)
|
.where('assets.isVisible', '=', true)
|
||||||
.where((eb) =>
|
.where((eb) =>
|
||||||
eb.or([
|
eb.or([
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ export class PersonRepository implements IPersonRepository {
|
|||||||
.$if(!!options.personId, (qb) => qb.where('asset_faces.personId', '=', options.personId!))
|
.$if(!!options.personId, (qb) => qb.where('asset_faces.personId', '=', options.personId!))
|
||||||
.$if(!!options.sourceType, (qb) => qb.where('asset_faces.sourceType', '=', options.sourceType!))
|
.$if(!!options.sourceType, (qb) => qb.where('asset_faces.sourceType', '=', options.sourceType!))
|
||||||
.$if(!!options.assetId, (qb) => qb.where('asset_faces.assetId', '=', options.assetId!))
|
.$if(!!options.assetId, (qb) => qb.where('asset_faces.assetId', '=', options.assetId!))
|
||||||
.$if(!!options.assetId, (qb) => qb.where('asset_faces.assetId', '=', options.assetId!))
|
|
||||||
.stream() as AsyncIterableIterator<AssetFaceEntity>;
|
.stream() as AsyncIterableIterator<AssetFaceEntity>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +108,7 @@ export class PersonRepository implements IPersonRepository {
|
|||||||
.selectFrom('person')
|
.selectFrom('person')
|
||||||
.selectAll('person')
|
.selectAll('person')
|
||||||
.$if(!!options.ownerId, (qb) => qb.where('person.ownerId', '=', options.ownerId!))
|
.$if(!!options.ownerId, (qb) => qb.where('person.ownerId', '=', options.ownerId!))
|
||||||
.$if(!!options.thumbnailPath, (qb) => qb.where('person.thumbnailPath', '=', options.thumbnailPath!))
|
.$if(options.thumbnailPath !== undefined, (qb) => qb.where('person.thumbnailPath', '=', options.thumbnailPath!))
|
||||||
.$if(options.faceAssetId === null, (qb) => qb.where('person.faceAssetId', 'is', null))
|
.$if(options.faceAssetId === null, (qb) => qb.where('person.faceAssetId', 'is', null))
|
||||||
.$if(!!options.faceAssetId, (qb) => qb.where('person.faceAssetId', '=', options.faceAssetId!))
|
.$if(!!options.faceAssetId, (qb) => qb.where('person.faceAssetId', '=', options.faceAssetId!))
|
||||||
.$if(options.isHidden !== undefined, (qb) => qb.where('person.isHidden', '=', options.isHidden!))
|
.$if(options.isHidden !== undefined, (qb) => qb.where('person.isHidden', '=', options.isHidden!))
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ export class MediaService extends BaseService {
|
|||||||
await Promise.all(pathsToDelete.map((path) => this.storageRepository.unlink(path)));
|
await Promise.all(pathsToDelete.map((path) => this.storageRepository.unlink(path)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (asset.thumbhash != generated.thumbhash) {
|
if (!asset.thumbhash || Buffer.compare(asset.thumbhash, generated.thumbhash) !== 0) {
|
||||||
await this.assetRepository.update({ id: asset.id, thumbhash: generated.thumbhash });
|
await this.assetRepository.update({ id: asset.id, thumbhash: generated.thumbhash });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -335,8 +335,8 @@ describe(MetadataService.name, () => {
|
|||||||
expect(assetMock.update).toHaveBeenCalledWith({
|
expect(assetMock.update).toHaveBeenCalledWith({
|
||||||
id: assetStub.image.id,
|
id: assetStub.image.id,
|
||||||
duration: null,
|
duration: null,
|
||||||
fileCreatedAt: assetStub.image.createdAt,
|
fileCreatedAt: assetStub.image.fileCreatedAt,
|
||||||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
localDateTime: assetStub.image.fileCreatedAt,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1162,6 +1162,17 @@ describe(MetadataService.name, () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('should handle valid negative rating value', async () => {
|
||||||
|
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||||
|
mockReadTags({ Rating: -1 });
|
||||||
|
|
||||||
|
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||||
|
expect(assetMock.upsertExif).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
rating: -1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('handleQueueSidecar', () => {
|
describe('handleQueueSidecar', () => {
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ export class MetadataService extends BaseService {
|
|||||||
// comments
|
// comments
|
||||||
description: String(exifTags.ImageDescription || exifTags.Description || '').trim(),
|
description: String(exifTags.ImageDescription || exifTags.Description || '').trim(),
|
||||||
profileDescription: exifTags.ProfileDescription || null,
|
profileDescription: exifTags.ProfileDescription || null,
|
||||||
rating: validateRange(exifTags.Rating, 0, 5),
|
rating: validateRange(exifTags.Rating, -1, 5),
|
||||||
|
|
||||||
// grouping
|
// grouping
|
||||||
livePhotoCID: (exifTags.ContentIdentifier || exifTags.MediaGroupUUID) ?? null,
|
livePhotoCID: (exifTags.ContentIdentifier || exifTags.MediaGroupUUID) ?? null,
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
|
||||||
|
|
||||||
export const getAssetDateTime = (asset: AssetEntity | undefined) => {
|
|
||||||
return asset?.exifInfo?.dateTimeOriginal || asset?.fileCreatedAt;
|
|
||||||
};
|
|
||||||
4
server/test/fixtures/asset.stub.ts
vendored
4
server/test/fixtures/asset.stub.ts
vendored
@@ -210,7 +210,7 @@ export const assetStub = {
|
|||||||
encodedVideoPath: null,
|
encodedVideoPath: null,
|
||||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
localDateTime: new Date('2025-01-01T01:02:03.456Z'),
|
||||||
isFavorite: true,
|
isFavorite: true,
|
||||||
isArchived: false,
|
isArchived: false,
|
||||||
duration: null,
|
duration: null,
|
||||||
@@ -574,7 +574,7 @@ export const assetStub = {
|
|||||||
encodedVideoPath: null,
|
encodedVideoPath: null,
|
||||||
createdAt: new Date('2023-02-22T05:06:29.716Z'),
|
createdAt: new Date('2023-02-22T05:06:29.716Z'),
|
||||||
updatedAt: new Date('2023-02-22T05:06:29.716Z'),
|
updatedAt: new Date('2023-02-22T05:06:29.716Z'),
|
||||||
localDateTime: new Date('2023-02-22T05:06:29.716Z'),
|
localDateTime: new Date('2020-12-31T23:59:00.000Z'),
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
isArchived: false,
|
isArchived: false,
|
||||||
isExternal: false,
|
isExternal: false,
|
||||||
|
|||||||
2
server/test/fixtures/shared-link.stub.ts
vendored
2
server/test/fixtures/shared-link.stub.ts
vendored
@@ -311,7 +311,7 @@ export const sharedLinkResponseStub = {
|
|||||||
allowUpload: false,
|
allowUpload: false,
|
||||||
allowDownload: false,
|
allowDownload: false,
|
||||||
showMetadata: false,
|
showMetadata: false,
|
||||||
album: { ...albumResponse, startDate: assetResponse.fileCreatedAt, endDate: assetResponse.fileCreatedAt },
|
album: { ...albumResponse, startDate: assetResponse.localDateTime, endDate: assetResponse.localDateTime },
|
||||||
assets: [{ ...assetResponseWithoutMetadata, exifInfo: undefined }],
|
assets: [{ ...assetResponseWithoutMetadata, exifInfo: undefined }],
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Mocked, vitest } from 'vitest';
|
|||||||
export const newMediaRepositoryMock = (): Mocked<IMediaRepository> => {
|
export const newMediaRepositoryMock = (): Mocked<IMediaRepository> => {
|
||||||
return {
|
return {
|
||||||
generateThumbnail: vitest.fn().mockImplementation(() => Promise.resolve()),
|
generateThumbnail: vitest.fn().mockImplementation(() => Promise.resolve()),
|
||||||
generateThumbhash: vitest.fn().mockImplementation(() => Promise.resolve()),
|
generateThumbhash: vitest.fn().mockResolvedValue(Buffer.from('')),
|
||||||
decodeImage: vitest.fn().mockResolvedValue({ data: Buffer.from(''), info: {} }),
|
decodeImage: vitest.fn().mockResolvedValue({ data: Buffer.from(''), info: {} }),
|
||||||
extract: vitest.fn().mockResolvedValue(false),
|
extract: vitest.fn().mockResolvedValue(false),
|
||||||
probe: vitest.fn(),
|
probe: vitest.fn(),
|
||||||
|
|||||||
27
web/package-lock.json
generated
27
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||||
@@ -16,6 +16,8 @@
|
|||||||
"@mdi/js": "^7.4.47",
|
"@mdi/js": "^7.4.47",
|
||||||
"@photo-sphere-viewer/core": "^5.11.5",
|
"@photo-sphere-viewer/core": "^5.11.5",
|
||||||
"@photo-sphere-viewer/equirectangular-video-adapter": "^5.11.5",
|
"@photo-sphere-viewer/equirectangular-video-adapter": "^5.11.5",
|
||||||
|
"@photo-sphere-viewer/resolution-plugin": "^5.11.5",
|
||||||
|
"@photo-sphere-viewer/settings-plugin": "^5.11.5",
|
||||||
"@photo-sphere-viewer/video-plugin": "^5.11.5",
|
"@photo-sphere-viewer/video-plugin": "^5.11.5",
|
||||||
"@zoom-image/svelte": "^0.3.0",
|
"@zoom-image/svelte": "^0.3.0",
|
||||||
"dom-to-image": "^2.6.0",
|
"dom-to-image": "^2.6.0",
|
||||||
@@ -75,7 +77,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
@@ -1669,6 +1671,25 @@
|
|||||||
"@photo-sphere-viewer/video-plugin": "5.11.5"
|
"@photo-sphere-viewer/video-plugin": "5.11.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@photo-sphere-viewer/resolution-plugin": {
|
||||||
|
"version": "5.11.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@photo-sphere-viewer/resolution-plugin/-/resolution-plugin-5.11.5.tgz",
|
||||||
|
"integrity": "sha512-Dbvp5bBtozD3IWt1Q0wORVaZBcB1bV9xUeoOS9A7F7b3EkQ2pkC5/jot/1AyM4wtU5wJ63NWHskQ1d7m6WWazQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@photo-sphere-viewer/core": "5.11.5",
|
||||||
|
"@photo-sphere-viewer/settings-plugin": "5.11.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@photo-sphere-viewer/settings-plugin": {
|
||||||
|
"version": "5.11.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@photo-sphere-viewer/settings-plugin/-/settings-plugin-5.11.5.tgz",
|
||||||
|
"integrity": "sha512-ZgYaWjiBMhsoRH5ddW3h+v4J4LPmofsT7BBRq5UCssWw2Fsrvv7mFFRi4UbZ1qzeKmvNUOr8BaFQgX1ZLvUWfQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@photo-sphere-viewer/core": "5.11.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@photo-sphere-viewer/video-plugin": {
|
"node_modules/@photo-sphere-viewer/video-plugin": {
|
||||||
"version": "5.11.5",
|
"version": "5.11.5",
|
||||||
"resolved": "https://registry.npmjs.org/@photo-sphere-viewer/video-plugin/-/video-plugin-5.11.5.tgz",
|
"resolved": "https://registry.npmjs.org/@photo-sphere-viewer/video-plugin/-/video-plugin-5.11.5.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.125.5",
|
"version": "1.125.6",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev --host 0.0.0.0 --port 3000",
|
"dev": "vite dev --host 0.0.0.0 --port 3000",
|
||||||
@@ -72,6 +72,8 @@
|
|||||||
"@mdi/js": "^7.4.47",
|
"@mdi/js": "^7.4.47",
|
||||||
"@photo-sphere-viewer/core": "^5.11.5",
|
"@photo-sphere-viewer/core": "^5.11.5",
|
||||||
"@photo-sphere-viewer/equirectangular-video-adapter": "^5.11.5",
|
"@photo-sphere-viewer/equirectangular-video-adapter": "^5.11.5",
|
||||||
|
"@photo-sphere-viewer/resolution-plugin": "^5.11.5",
|
||||||
|
"@photo-sphere-viewer/settings-plugin": "^5.11.5",
|
||||||
"@photo-sphere-viewer/video-plugin": "^5.11.5",
|
"@photo-sphere-viewer/video-plugin": "^5.11.5",
|
||||||
"@zoom-image/svelte": "^0.3.0",
|
"@zoom-image/svelte": "^0.3.0",
|
||||||
"dom-to-image": "^2.6.0",
|
"dom-to-image": "^2.6.0",
|
||||||
|
|||||||
@@ -11,7 +11,10 @@
|
|||||||
let { album }: Props = $props();
|
let { album }: Props = $props();
|
||||||
|
|
||||||
const formatDate = (date?: string) => {
|
const formatDate = (date?: string) => {
|
||||||
return date ? new Date(date).toLocaleDateString($locale, dateFormats.album) : undefined;
|
const dateWithoutTimeZone = date?.slice(0, -1);
|
||||||
|
return dateWithoutTimeZone
|
||||||
|
? new Date(dateWithoutTimeZone).toLocaleDateString($locale, dateFormats.album)
|
||||||
|
: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDateRange = (start?: string, end?: string) => {
|
const getDateRange = (start?: string, end?: string) => {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
{:then [data, { default: PhotoSphereViewer }]}
|
{:then [data, { default: PhotoSphereViewer }]}
|
||||||
<PhotoSphereViewer
|
<PhotoSphereViewer
|
||||||
panorama={data}
|
panorama={data}
|
||||||
originalImageUrl={isWebCompatibleImage(asset) ? getAssetOriginalUrl(asset.id) : undefined}
|
originalPanorama={isWebCompatibleImage(asset) ? getAssetOriginalUrl(asset.id) : undefined}
|
||||||
/>
|
/>
|
||||||
{:catch}
|
{:catch}
|
||||||
{$t('errors.failed_to_load_asset')}
|
{$t('errors.failed_to_load_asset')}
|
||||||
|
|||||||
@@ -7,18 +7,21 @@
|
|||||||
type AdapterConstructor,
|
type AdapterConstructor,
|
||||||
type PluginConstructor,
|
type PluginConstructor,
|
||||||
} from '@photo-sphere-viewer/core';
|
} from '@photo-sphere-viewer/core';
|
||||||
|
import { SettingsPlugin } from '@photo-sphere-viewer/settings-plugin';
|
||||||
|
import { ResolutionPlugin } from '@photo-sphere-viewer/resolution-plugin';
|
||||||
import '@photo-sphere-viewer/core/index.css';
|
import '@photo-sphere-viewer/core/index.css';
|
||||||
|
import '@photo-sphere-viewer/settings-plugin/index.css';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
panorama: string | { source: string };
|
panorama: string | { source: string };
|
||||||
originalImageUrl?: string;
|
originalPanorama?: string | { source: string };
|
||||||
adapter?: AdapterConstructor | [AdapterConstructor, unknown];
|
adapter?: AdapterConstructor | [AdapterConstructor, unknown];
|
||||||
plugins?: (PluginConstructor | [PluginConstructor, unknown])[];
|
plugins?: (PluginConstructor | [PluginConstructor, unknown])[];
|
||||||
navbar?: boolean;
|
navbar?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { panorama, originalImageUrl, adapter = EquirectangularAdapter, plugins = [], navbar = false }: Props = $props();
|
let { panorama, originalPanorama, adapter = EquirectangularAdapter, plugins = [], navbar = false }: Props = $props();
|
||||||
|
|
||||||
let container: HTMLDivElement | undefined = $state();
|
let container: HTMLDivElement | undefined = $state();
|
||||||
let viewer: Viewer;
|
let viewer: Viewer;
|
||||||
@@ -30,9 +33,33 @@
|
|||||||
|
|
||||||
viewer = new Viewer({
|
viewer = new Viewer({
|
||||||
adapter,
|
adapter,
|
||||||
plugins,
|
plugins: [
|
||||||
container,
|
SettingsPlugin,
|
||||||
|
[
|
||||||
|
ResolutionPlugin,
|
||||||
|
{
|
||||||
|
defaultResolution: $alwaysLoadOriginalFile && originalPanorama ? 'original' : 'default',
|
||||||
|
resolutions: [
|
||||||
|
{
|
||||||
|
id: 'default',
|
||||||
|
label: 'Default',
|
||||||
panorama,
|
panorama,
|
||||||
|
},
|
||||||
|
...(originalPanorama
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: 'original',
|
||||||
|
label: 'Original',
|
||||||
|
panorama: originalPanorama,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
...plugins,
|
||||||
|
],
|
||||||
|
container,
|
||||||
touchmoveTwoFingers: false,
|
touchmoveTwoFingers: false,
|
||||||
mousewheelCtrlKey: false,
|
mousewheelCtrlKey: false,
|
||||||
navbar,
|
navbar,
|
||||||
@@ -40,15 +67,14 @@
|
|||||||
maxFov: 120,
|
maxFov: 120,
|
||||||
fisheye: false,
|
fisheye: false,
|
||||||
});
|
});
|
||||||
|
const resolutionPlugin = viewer.getPlugin(ResolutionPlugin) as ResolutionPlugin;
|
||||||
|
|
||||||
if (originalImageUrl && !$alwaysLoadOriginalFile) {
|
if (originalPanorama && !$alwaysLoadOriginalFile) {
|
||||||
const zoomHandler = ({ zoomLevel }: events.ZoomUpdatedEvent) => {
|
const zoomHandler = ({ zoomLevel }: events.ZoomUpdatedEvent) => {
|
||||||
// zoomLevel range: [0, 100]
|
// zoomLevel range: [0, 100]
|
||||||
if (Math.round(zoomLevel) >= 75) {
|
if (Math.round(zoomLevel) >= 75) {
|
||||||
// Replace the preview with the original
|
// Replace the preview with the original
|
||||||
viewer.setPanorama(originalImageUrl, { showLoader: false, speed: 150 }).catch(() => {
|
void resolutionPlugin.setResolution('original');
|
||||||
viewer.setPanorama(panorama, { showLoader: false, speed: 0 }).catch(() => {});
|
|
||||||
});
|
|
||||||
viewer.removeEventListener(events.ZoomUpdatedEvent.type, zoomHandler);
|
viewer.removeEventListener(events.ZoomUpdatedEvent.type, zoomHandler);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getAssetOriginalUrl } from '$lib/utils';
|
import { getAssetPlaybackUrl, getAssetOriginalUrl } from '$lib/utils';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
@@ -22,7 +22,13 @@
|
|||||||
{#await modules}
|
{#await modules}
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
{:then [PhotoSphereViewer, adapter, videoPlugin]}
|
{:then [PhotoSphereViewer, adapter, videoPlugin]}
|
||||||
<PhotoSphereViewer panorama={{ source: getAssetOriginalUrl(assetId) }} plugins={[videoPlugin]} {adapter} navbar />
|
<PhotoSphereViewer
|
||||||
|
panorama={{ source: getAssetPlaybackUrl(assetId) }}
|
||||||
|
originalPanorama={{ source: getAssetOriginalUrl(assetId) }}
|
||||||
|
plugins={[videoPlugin]}
|
||||||
|
{adapter}
|
||||||
|
navbar
|
||||||
|
/>
|
||||||
{:catch}
|
{:catch}
|
||||||
{$t('errors.failed_to_load_asset')}
|
{$t('errors.failed_to_load_asset')}
|
||||||
{/await}
|
{/await}
|
||||||
|
|||||||
@@ -157,7 +157,6 @@ async function fileUploader(
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error calculating sha1 file=${assetFile.name})`, error);
|
console.error(`Error calculating sha1 file=${assetFile.name})`, error);
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,7 @@
|
|||||||
let personMerge1: PersonResponseDto | undefined = $state();
|
let personMerge1: PersonResponseDto | undefined = $state();
|
||||||
let personMerge2: PersonResponseDto | undefined = $state();
|
let personMerge2: PersonResponseDto | undefined = $state();
|
||||||
let potentialMergePeople: PersonResponseDto[] = $state([]);
|
let potentialMergePeople: PersonResponseDto[] = $state([]);
|
||||||
|
let isSuggestionSelectedByUser = $state(false);
|
||||||
|
|
||||||
let personName = '';
|
let personName = '';
|
||||||
let suggestedPeople: PersonResponseDto[] = $state([]);
|
let suggestedPeople: PersonResponseDto[] = $state([]);
|
||||||
@@ -233,15 +234,22 @@
|
|||||||
personName = person.name;
|
personName = person.name;
|
||||||
personMerge1 = person;
|
personMerge1 = person;
|
||||||
personMerge2 = person2;
|
personMerge2 = person2;
|
||||||
|
isSuggestionSelectedByUser = true;
|
||||||
viewMode = PersonPageViewMode.SUGGEST_MERGE;
|
viewMode = PersonPageViewMode.SUGGEST_MERGE;
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeName = async () => {
|
const changeName = async () => {
|
||||||
viewMode = PersonPageViewMode.VIEW_ASSETS;
|
viewMode = PersonPageViewMode.VIEW_ASSETS;
|
||||||
person.name = personName;
|
person.name = personName;
|
||||||
try {
|
|
||||||
isEditingName = false;
|
isEditingName = false;
|
||||||
|
|
||||||
|
if (isSuggestionSelectedByUser) {
|
||||||
|
// User canceled the merge
|
||||||
|
isSuggestionSelectedByUser = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
person = await updatePerson({ id: person.id, personUpdateDto: { name: personName } });
|
person = await updatePerson({ id: person.id, personUpdateDto: { name: personName } });
|
||||||
|
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
|
|||||||
Reference in New Issue
Block a user