mirror of
https://github.com/immich-app/immich.git
synced 2026-05-27 01:52:33 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c3105e3fb |
@@ -840,7 +840,6 @@
|
||||
"copy_error": "Copy error",
|
||||
"copy_file_path": "Copy file path",
|
||||
"copy_image": "Copy Image",
|
||||
"copy_json": "Copy JSON",
|
||||
"copy_link": "Copy link",
|
||||
"copy_link_to_clipboard": "Copy link to clipboard",
|
||||
"copy_password": "Copy password",
|
||||
@@ -980,8 +979,6 @@
|
||||
"downloading_media": "Downloading media",
|
||||
"drag_to_reorder": "Drag to reorder",
|
||||
"drop_files_to_upload": "Drop files anywhere to upload",
|
||||
"duplicate": "Duplicate",
|
||||
"duplicate_workflow": "Duplicate workflow",
|
||||
"duplicates": "Duplicates",
|
||||
"duplicates_description": "Resolve each group by indicating which, if any, are duplicates.",
|
||||
"duration": "Duration",
|
||||
|
||||
Generated
+28
-34
@@ -609,7 +609,7 @@ importers:
|
||||
version: 10.0.1(eslint@10.4.0(jiti@2.7.0))
|
||||
'@nestjs/cli':
|
||||
specifier: ^11.0.2
|
||||
version: 11.0.21(@swc/core@1.15.33(@swc/helpers@0.5.22))(@types/node@24.12.4)(esbuild@0.28.0)(lightningcss@1.32.0)(prettier@3.8.3)
|
||||
version: 11.0.21(@swc/core@1.15.33(@swc/helpers@0.5.21))(@types/node@24.12.4)(esbuild@0.28.0)(lightningcss@1.32.0)(prettier@3.8.3)
|
||||
'@nestjs/schematics':
|
||||
specifier: ^11.0.0
|
||||
version: 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@6.0.3)
|
||||
@@ -618,7 +618,7 @@ importers:
|
||||
version: 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-express@11.1.21)
|
||||
'@swc/core':
|
||||
specifier: ^1.4.14
|
||||
version: 1.15.33(@swc/helpers@0.5.22)
|
||||
version: 1.15.33(@swc/helpers@0.5.21)
|
||||
'@types/archiver':
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
@@ -738,7 +738,7 @@ importers:
|
||||
version: 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)
|
||||
unplugin-swc:
|
||||
specifier: ^1.4.5
|
||||
version: 1.5.9(@swc/core@1.15.33(@swc/helpers@0.5.22))(rollup@4.60.4)
|
||||
version: 1.5.9(@swc/core@1.15.33(@swc/helpers@0.5.21))(rollup@4.60.4)
|
||||
vite-tsconfig-paths:
|
||||
specifier: ^6.0.0
|
||||
version: 6.1.1(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))
|
||||
@@ -758,8 +758,8 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../packages/sdk
|
||||
'@immich/ui':
|
||||
specifier: ^0.79.2
|
||||
version: 0.79.2(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))
|
||||
specifier: ^0.79.0
|
||||
version: 0.79.0(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))
|
||||
'@mapbox/mapbox-gl-rtl-text':
|
||||
specifier: 0.4.0
|
||||
version: 0.4.0
|
||||
@@ -1691,10 +1691,6 @@ packages:
|
||||
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/runtime@7.29.7':
|
||||
resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/template@7.28.6':
|
||||
resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -3208,8 +3204,8 @@ packages:
|
||||
resolution: {integrity: sha512-O1SJ+BbeFVsUTF4af1MfagJZM+lPgLjI8lQ3SZNjpo8SGJReSbUl2ii03OKuGni/G0yp2GnRLpOTNSHYGtVrcg==}
|
||||
hasBin: true
|
||||
|
||||
'@immich/ui@0.79.2':
|
||||
resolution: {integrity: sha512-tnpYhYHrjrFJK18QglRMzPUtHv6q5V6tW38HiAraQJBv7MCg+yaJDrdF8omM2L5F311FGlv1PZLJYvmR4e49PA==}
|
||||
'@immich/ui@0.79.0':
|
||||
resolution: {integrity: sha512-UEQZrP8CTc4Kth1xCV8/6Xmk1P51GQKISC7vKqcrM0BO0fxCaNwJK8Ocn6R8baVqH52JYfPb1yQR9bweBnCjXw==}
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^2.13.0
|
||||
svelte: ^5.0.0
|
||||
@@ -4986,8 +4982,8 @@ packages:
|
||||
'@swc/counter@0.1.3':
|
||||
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||
|
||||
'@swc/helpers@0.5.22':
|
||||
resolution: {integrity: sha512-/e2Ly3Docn9kYByap6TV4oquJ3wQuz3c+kC74riqtkwU9CwTMeuj6t2rW+bRr4pyOx/CYQM4wr0RgaKQwGEz0A==}
|
||||
'@swc/helpers@0.5.21':
|
||||
resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==}
|
||||
|
||||
'@swc/types@0.1.26':
|
||||
resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==}
|
||||
@@ -13791,8 +13787,6 @@ snapshots:
|
||||
|
||||
'@babel/runtime@7.29.2': {}
|
||||
|
||||
'@babel/runtime@7.29.7': {}
|
||||
|
||||
'@babel/template@7.28.6':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.0
|
||||
@@ -15885,7 +15879,7 @@ snapshots:
|
||||
pg-connection-string: 2.13.0
|
||||
postgres: 3.4.9
|
||||
|
||||
'@immich/ui@0.79.2(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))':
|
||||
'@immich/ui@0.79.0(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))':
|
||||
dependencies:
|
||||
'@internationalized/date': 3.12.1
|
||||
'@mdi/js': 7.4.47
|
||||
@@ -16041,7 +16035,7 @@ snapshots:
|
||||
|
||||
'@internationalized/date@3.12.1':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.22
|
||||
'@swc/helpers': 0.5.21
|
||||
|
||||
'@ioredis/commands@1.5.1': {}
|
||||
|
||||
@@ -16444,7 +16438,7 @@ snapshots:
|
||||
bullmq: 5.76.10
|
||||
tslib: 2.8.1
|
||||
|
||||
'@nestjs/cli@11.0.21(@swc/core@1.15.33(@swc/helpers@0.5.22))(@types/node@24.12.4)(esbuild@0.28.0)(lightningcss@1.32.0)(prettier@3.8.3)':
|
||||
'@nestjs/cli@11.0.21(@swc/core@1.15.33(@swc/helpers@0.5.21))(@types/node@24.12.4)(esbuild@0.28.0)(lightningcss@1.32.0)(prettier@3.8.3)':
|
||||
dependencies:
|
||||
'@angular-devkit/core': 19.2.24(chokidar@4.0.3)
|
||||
'@angular-devkit/schematics': 19.2.24(chokidar@4.0.3)
|
||||
@@ -16455,17 +16449,17 @@ snapshots:
|
||||
chokidar: 4.0.3
|
||||
cli-table3: 0.6.5
|
||||
commander: 4.1.1
|
||||
fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.22))(esbuild@0.28.0)(lightningcss@1.32.0))
|
||||
fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0))
|
||||
glob: 13.0.6
|
||||
node-emoji: 1.11.0
|
||||
ora: 5.4.1
|
||||
tsconfig-paths: 4.2.0
|
||||
tsconfig-paths-webpack-plugin: 4.2.0
|
||||
typescript: 5.9.3
|
||||
webpack: 5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.22))(esbuild@0.28.0)(lightningcss@1.32.0)
|
||||
webpack: 5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0)
|
||||
webpack-node-externals: 3.0.0
|
||||
optionalDependencies:
|
||||
'@swc/core': 1.15.33(@swc/helpers@0.5.22)
|
||||
'@swc/core': 1.15.33(@swc/helpers@0.5.21)
|
||||
transitivePeerDependencies:
|
||||
- '@minify-html/node'
|
||||
- '@swc/css'
|
||||
@@ -17444,7 +17438,7 @@ snapshots:
|
||||
|
||||
'@slorber/react-helmet-async@1.3.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.7
|
||||
'@babel/runtime': 7.29.2
|
||||
invariant: 2.2.4
|
||||
prop-types: 15.8.1
|
||||
react: 19.2.6
|
||||
@@ -17653,7 +17647,7 @@ snapshots:
|
||||
'@swc/core-win32-x64-msvc@1.15.33':
|
||||
optional: true
|
||||
|
||||
'@swc/core@1.15.33(@swc/helpers@0.5.22)':
|
||||
'@swc/core@1.15.33(@swc/helpers@0.5.21)':
|
||||
dependencies:
|
||||
'@swc/counter': 0.1.3
|
||||
'@swc/types': 0.1.26
|
||||
@@ -17670,11 +17664,11 @@ snapshots:
|
||||
'@swc/core-win32-arm64-msvc': 1.15.33
|
||||
'@swc/core-win32-ia32-msvc': 1.15.33
|
||||
'@swc/core-win32-x64-msvc': 1.15.33
|
||||
'@swc/helpers': 0.5.22
|
||||
'@swc/helpers': 0.5.21
|
||||
|
||||
'@swc/counter@0.1.3': {}
|
||||
|
||||
'@swc/helpers@0.5.22':
|
||||
'@swc/helpers@0.5.21':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
@@ -21090,7 +21084,7 @@ snapshots:
|
||||
cross-spawn: 7.0.6
|
||||
signal-exit: 4.1.0
|
||||
|
||||
fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.22))(esbuild@0.28.0)(lightningcss@1.32.0)):
|
||||
fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0)):
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.0
|
||||
chalk: 4.1.2
|
||||
@@ -21105,7 +21099,7 @@ snapshots:
|
||||
semver: 7.8.0
|
||||
tapable: 2.3.3
|
||||
typescript: 5.9.3
|
||||
webpack: 5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.22))(esbuild@0.28.0)(lightningcss@1.32.0)
|
||||
webpack: 5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0)
|
||||
|
||||
form-data-encoder@2.1.4: {}
|
||||
|
||||
@@ -25767,15 +25761,15 @@ snapshots:
|
||||
- bare-abort-controller
|
||||
- react-native-b4a
|
||||
|
||||
terser-webpack-plugin@5.6.0(@swc/core@1.15.33(@swc/helpers@0.5.22))(esbuild@0.28.0)(lightningcss@1.32.0)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.22))(esbuild@0.28.0)(lightningcss@1.32.0)):
|
||||
terser-webpack-plugin@5.6.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0)):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
jest-worker: 27.5.1
|
||||
schema-utils: 4.3.3
|
||||
terser: 5.47.1
|
||||
webpack: 5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.22))(esbuild@0.28.0)(lightningcss@1.32.0)
|
||||
webpack: 5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0)
|
||||
optionalDependencies:
|
||||
'@swc/core': 1.15.33(@swc/helpers@0.5.22)
|
||||
'@swc/core': 1.15.33(@swc/helpers@0.5.21)
|
||||
esbuild: 0.28.0
|
||||
lightningcss: 1.32.0
|
||||
|
||||
@@ -26182,10 +26176,10 @@ snapshots:
|
||||
|
||||
unpipe@1.0.0: {}
|
||||
|
||||
unplugin-swc@1.5.9(@swc/core@1.15.33(@swc/helpers@0.5.22))(rollup@4.60.4):
|
||||
unplugin-swc@1.5.9(@swc/core@1.15.33(@swc/helpers@0.5.21))(rollup@4.60.4):
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.60.4)
|
||||
'@swc/core': 1.15.33(@swc/helpers@0.5.22)
|
||||
'@swc/core': 1.15.33(@swc/helpers@0.5.21)
|
||||
load-tsconfig: 0.2.5
|
||||
unplugin: 2.3.11
|
||||
transitivePeerDependencies:
|
||||
@@ -26584,7 +26578,7 @@ snapshots:
|
||||
|
||||
webpack-virtual-modules@0.6.2: {}
|
||||
|
||||
webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.22))(esbuild@0.28.0)(lightningcss@1.32.0):
|
||||
webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0):
|
||||
dependencies:
|
||||
'@types/eslint-scope': 3.7.7
|
||||
'@types/estree': 1.0.9
|
||||
@@ -26608,7 +26602,7 @@ snapshots:
|
||||
neo-async: 2.6.2
|
||||
schema-utils: 4.3.3
|
||||
tapable: 2.3.3
|
||||
terser-webpack-plugin: 5.6.0(@swc/core@1.15.33(@swc/helpers@0.5.22))(esbuild@0.28.0)(lightningcss@1.32.0)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.22))(esbuild@0.28.0)(lightningcss@1.32.0))
|
||||
terser-webpack-plugin: 5.6.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0))
|
||||
watchpack: 2.5.1
|
||||
webpack-sources: 3.4.1
|
||||
transitivePeerDependencies:
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
"@formatjs/icu-messageformat-parser": "^3.0.0",
|
||||
"@immich/justified-layout-wasm": "^0.4.3",
|
||||
"@immich/sdk": "workspace:*",
|
||||
"@immich/ui": "^0.79.2",
|
||||
"@immich/ui": "^0.79.0",
|
||||
"@mapbox/mapbox-gl-rtl-text": "0.4.0",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@noble/hashes": "^2.2.0",
|
||||
|
||||
@@ -30,14 +30,11 @@
|
||||
|
||||
const transitionDuration = $derived(manager.suspendTransitions && !$isUploading ? 0 : 150);
|
||||
const scaleDuration = $derived(transitionDuration === 0 ? 0 : transitionDuration + 100);
|
||||
|
||||
const firstInOrNearViewport = $derived(viewerAssets.findIndex((a) => a.isInOrNearViewport));
|
||||
const lastInOrNearViewport = $derived(viewerAssets.findLastIndex((a) => a.isInOrNearViewport));
|
||||
</script>
|
||||
|
||||
<!-- Image grid -->
|
||||
<div data-image-grid class="relative overflow-clip" style:height={height + 'px'} style:width={width + 'px'}>
|
||||
{#each viewerAssets.slice(firstInOrNearViewport, lastInOrNearViewport + 1) as viewerAsset (viewerAsset.id)}
|
||||
{#each viewerAssets as viewerAsset (viewerAsset.id)}
|
||||
{@const position = viewerAsset.position!}
|
||||
{@const asset = viewerAsset.asset!}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
|
||||
<AssetLayout
|
||||
{manager}
|
||||
viewerAssets={timelineDay.viewerAssets}
|
||||
viewerAssets={timelineDay.activeViewerAssets}
|
||||
height={timelineDay.height}
|
||||
width={timelineDay.width}
|
||||
{customThumbnailLayout}
|
||||
|
||||
@@ -53,17 +53,3 @@ export function updateTimelineMonthViewportProximity(timelineManager: TimelineMa
|
||||
timelineManager.clearDeferredLayout(month);
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateViewerAssetViewportProximity(
|
||||
timelineManager: TimelineManager,
|
||||
positionTop: number,
|
||||
positionHeight: number,
|
||||
) {
|
||||
const headerHeight = timelineManager.headerHeight;
|
||||
return calculateViewportProximity(
|
||||
positionTop,
|
||||
positionTop + positionHeight,
|
||||
timelineManager.visibleWindow.top - headerHeight,
|
||||
timelineManager.visibleWindow.bottom + headerHeight,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,31 @@
|
||||
import { AssetOrder, AssetOrderBy } from '@immich/sdk';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import type { CommonLayoutOptions } from '$lib/utils/layout-utils';
|
||||
import type { CommonLayoutOptions, CommonPosition } from '$lib/utils/layout-utils';
|
||||
import { getJustifiedLayoutFromAssets } from '$lib/utils/layout-utils';
|
||||
import { getOrderingDate, plainDateTimeCompare } from '$lib/utils/timeline-util';
|
||||
import { TUNABLES } from '$lib/utils/tunables';
|
||||
import type { TimelineMonth } from './timeline-month.svelte';
|
||||
import type { Direction, MoveAsset, TimelineAsset } from './types';
|
||||
import { ViewerAsset } from './viewer-asset.svelte';
|
||||
|
||||
const {
|
||||
TIMELINE: { INTERSECTION_EXPAND_TOP, INTERSECTION_EXPAND_BOTTOM },
|
||||
} = TUNABLES;
|
||||
|
||||
function lowerBound(assets: ViewerAsset[], target: number, key: (pos: CommonPosition) => number): number {
|
||||
let lo = 0;
|
||||
let hi = assets.length;
|
||||
while (lo < hi) {
|
||||
const mid = Math.floor((lo + hi) / 2);
|
||||
if (key(assets[mid].position!) < target) {
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
hi = mid;
|
||||
}
|
||||
}
|
||||
return lo;
|
||||
}
|
||||
|
||||
export class TimelineDay {
|
||||
readonly timelineMonth: TimelineMonth;
|
||||
readonly index: number;
|
||||
@@ -18,12 +37,15 @@ export class TimelineDay {
|
||||
height = $state(0);
|
||||
width = $state(0);
|
||||
|
||||
// Assets in or near the viewport; active assets should be added to the DOM.
|
||||
activeViewerAssets: ViewerAsset[] = $state([]);
|
||||
isInOrNearViewport = $state(false);
|
||||
|
||||
#top: number = $state(0);
|
||||
#start: number = $state(0);
|
||||
#row = $state(0);
|
||||
#col = $state(0);
|
||||
#deferredLayout = false;
|
||||
#lastInOrNearViewport = -1;
|
||||
|
||||
constructor(timelineMonth: TimelineMonth, index: number, day: number, groupTitle: string, orderBy: AssetOrderBy) {
|
||||
this.index = index;
|
||||
@@ -149,18 +171,32 @@ export class TimelineDay {
|
||||
for (let i = 0; i < this.viewerAssets.length; i++) {
|
||||
this.viewerAssets[i].position = geometry.getPosition(i);
|
||||
}
|
||||
this.updateAssetBoundaries();
|
||||
}
|
||||
|
||||
updateAssetBoundaries() {
|
||||
const manager = this.timelineMonth.timelineManager;
|
||||
const visibleWindow = manager.visibleWindow;
|
||||
if (this.viewerAssets.length === 0 || !this.viewerAssets[0].position) {
|
||||
this.activeViewerAssets = [];
|
||||
this.isInOrNearViewport = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const dayOffset = this.absoluteTimelineDayTop;
|
||||
const headerHeight = manager.headerHeight;
|
||||
const expandedTop = visibleWindow.top - headerHeight - INTERSECTION_EXPAND_TOP - dayOffset;
|
||||
const expandedBottom = visibleWindow.bottom + headerHeight + INTERSECTION_EXPAND_BOTTOM - dayOffset;
|
||||
|
||||
const first = lowerBound(this.viewerAssets, expandedTop, (p) => p.top + p.height);
|
||||
const last = lowerBound(this.viewerAssets, expandedBottom, (p) => p.top) - 1;
|
||||
|
||||
const hasActive = last >= first && first < this.viewerAssets.length;
|
||||
this.activeViewerAssets = hasActive ? this.viewerAssets.slice(first, last + 1) : [];
|
||||
this.isInOrNearViewport = hasActive;
|
||||
}
|
||||
|
||||
get absoluteTimelineDayTop() {
|
||||
return this.timelineMonth.top + this.#top;
|
||||
}
|
||||
|
||||
get isInOrNearViewport() {
|
||||
if (this.#lastInOrNearViewport !== -1 && this.viewerAssets[this.#lastInOrNearViewport].isInOrNearViewport) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.#lastInOrNearViewport = this.viewerAssets.findIndex((viewAsset) => viewAsset.isInOrNearViewport);
|
||||
return this.#lastInOrNearViewport !== -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +214,11 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
|
||||
for (const month of this.months) {
|
||||
updateTimelineMonthViewportProximity(this, month);
|
||||
if (month.isInOrNearViewport && month.isLoaded) {
|
||||
for (const day of month.timelineDays) {
|
||||
day.updateAssetBoundaries();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const month = this.months.find((month) => month.isInViewport);
|
||||
|
||||
@@ -254,7 +254,7 @@ export class TimelineMonth {
|
||||
addContext.newTimelineDays.add(timelineDay);
|
||||
}
|
||||
|
||||
const viewerAsset = new ViewerAsset(timelineDay, timelineAsset);
|
||||
const viewerAsset = new ViewerAsset(timelineAsset);
|
||||
timelineDay.viewerAssets.push(viewerAsset);
|
||||
addContext.changedTimelineDays.add(timelineDay);
|
||||
}
|
||||
|
||||
@@ -1,36 +1,12 @@
|
||||
import type { CommonPosition } from '$lib/utils/layout-utils';
|
||||
import {
|
||||
ViewportProximity,
|
||||
calculateViewerAssetViewportProximity,
|
||||
isInOrNearViewport,
|
||||
} from './internal/intersection-support.svelte';
|
||||
import type { TimelineDay } from './timeline-day.svelte';
|
||||
import type { TimelineAsset } from './types';
|
||||
|
||||
export class ViewerAsset {
|
||||
readonly #group: TimelineDay;
|
||||
|
||||
#viewportProximity = $derived.by(() => {
|
||||
if (!this.position) {
|
||||
return ViewportProximity.FarFromViewport;
|
||||
}
|
||||
|
||||
const store = this.#group.timelineMonth.timelineManager;
|
||||
const positionTop = this.#group.absoluteTimelineDayTop + this.position.top;
|
||||
|
||||
return calculateViewerAssetViewportProximity(store, positionTop, this.position.height);
|
||||
});
|
||||
|
||||
get isInOrNearViewport() {
|
||||
return isInOrNearViewport(this.#viewportProximity);
|
||||
}
|
||||
|
||||
position: CommonPosition | undefined = $state.raw();
|
||||
asset: TimelineAsset = $state() as TimelineAsset;
|
||||
id: string = $derived(this.asset.id);
|
||||
|
||||
constructor(group: TimelineDay, asset: TimelineAsset) {
|
||||
this.#group = group;
|
||||
constructor(asset: TimelineAsset) {
|
||||
this.asset = asset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import WorkflowTriggerPicker from '$lib/modals/WorkflowTriggerPicker.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { handleCreateWorkflow } from '$lib/services/workflow.service';
|
||||
import { getTriggerDescription, getTriggerName } from '$lib/utils/workflow';
|
||||
import { WorkflowTrigger, type WorkflowResponseDto } from '@immich/sdk';
|
||||
import { Text, Field, FormModal, IconButton, Input, modalManager, Textarea, VStack } from '@immich/ui';
|
||||
import { mdiPencilOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
type Props = {
|
||||
workflow: WorkflowResponseDto;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const { workflow, onClose }: Props = $props();
|
||||
|
||||
let name = $state(workflow.name ?? '');
|
||||
let description = $state(workflow.description ?? '');
|
||||
let trigger = $state<WorkflowTrigger>(workflow.trigger);
|
||||
|
||||
const onSubmit = async () => {
|
||||
const response = await handleCreateWorkflow({
|
||||
name,
|
||||
description,
|
||||
trigger,
|
||||
steps: workflow.steps,
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
if (response) {
|
||||
await goto(Route.viewWorkflow({ id: response.id }));
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<FormModal
|
||||
title={$t('duplicate_workflow')}
|
||||
{onClose}
|
||||
{onSubmit}
|
||||
disabled={!name || !trigger}
|
||||
size="medium"
|
||||
submitText={$t('create')}
|
||||
>
|
||||
<VStack gap={4}>
|
||||
<Field label={$t('name')} required>
|
||||
<Input placeholder={$t('workflow_name')} bind:value={name} />
|
||||
</Field>
|
||||
|
||||
<Field label={$t('description')} for="workflow-description">
|
||||
<Textarea id="workflow-description" grow placeholder={$t('workflow_description')} bind:value={description} />
|
||||
</Field>
|
||||
</VStack>
|
||||
</FormModal>
|
||||
@@ -42,7 +42,7 @@ import AssetAddToAlbumModal from '$lib/modals/AssetAddToAlbumModal.svelte';
|
||||
import AssetTagModal from '$lib/modals/AssetTagModal.svelte';
|
||||
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
|
||||
import { getAssetMediaUrl, getSharedLink, sleep } from '$lib/utils';
|
||||
import { downloadUrl } from '$lib/utils';
|
||||
import { downloadUrl } from '$lib/utils/asset-utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ import { toastManager, type ActionItem } from '@immich/ui';
|
||||
import { mdiContentCopy, mdiDownload, mdiUpload } from '@mdi/js';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import type { MessageFormatter } from 'svelte-i18n';
|
||||
import { downloadManager } from '$lib/managers/download-manager.svelte';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { copyToClipboard, downloadJson } from '$lib/utils';
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
import { downloadBlob } from '$lib/utils/asset-utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
|
||||
@@ -17,7 +19,7 @@ export const getSystemConfigActions = (
|
||||
title: $t('copy_to_clipboard'),
|
||||
description: $t('admin.copy_config_to_clipboard_description'),
|
||||
icon: mdiContentCopy,
|
||||
onAction: () => copyToClipboard(config),
|
||||
onAction: () => handleCopyToClipboard(config),
|
||||
shortcuts: { shift: true, key: 'c' },
|
||||
};
|
||||
|
||||
@@ -25,7 +27,7 @@ export const getSystemConfigActions = (
|
||||
title: $t('export_as_json'),
|
||||
description: $t('admin.export_config_as_json_description'),
|
||||
icon: mdiDownload,
|
||||
onAction: () => downloadJson(config, 'immich-config.json'),
|
||||
onAction: () => handleDownloadConfig(config),
|
||||
shortcuts: [
|
||||
{ shift: true, key: 's' },
|
||||
{ shift: true, key: 'd' },
|
||||
@@ -63,6 +65,31 @@ export const handleSystemConfigSave = async (update: Partial<SystemConfigDto>) =
|
||||
}
|
||||
};
|
||||
|
||||
// https://stackoverflow.com/questions/16167581/sort-object-properties-and-json-stringify/43636793#43636793
|
||||
const jsonReplacer = (_key: string, value: unknown) =>
|
||||
value instanceof Object && !Array.isArray(value)
|
||||
? Object.keys(value)
|
||||
.sort()
|
||||
// eslint-disable-next-line unicorn/no-array-reduce
|
||||
.reduce((sorted: { [key: string]: unknown }, key) => {
|
||||
sorted[key] = (value as { [key: string]: unknown })[key];
|
||||
return sorted;
|
||||
}, {})
|
||||
: value;
|
||||
|
||||
export const handleCopyToClipboard = async (config: SystemConfigDto) => {
|
||||
await copyToClipboard(JSON.stringify(config, jsonReplacer, 2));
|
||||
};
|
||||
|
||||
export const handleDownloadConfig = (config: SystemConfigDto) => {
|
||||
const blob = new Blob([JSON.stringify(config, jsonReplacer, 2)], { type: 'application/json' });
|
||||
const downloadKey = 'immich-config.json';
|
||||
downloadManager.add(downloadKey, blob.size);
|
||||
downloadManager.update(downloadKey, blob.size);
|
||||
downloadBlob(blob, downloadKey);
|
||||
setTimeout(() => downloadManager.clear(downloadKey), 5000);
|
||||
};
|
||||
|
||||
export const handleUploadConfig = () => {
|
||||
const input = globalThis.document.createElement('input');
|
||||
input.setAttribute('type', 'file');
|
||||
|
||||
@@ -10,25 +10,12 @@ import {
|
||||
type WorkflowUpdateDto,
|
||||
} from '@immich/sdk';
|
||||
import { modalManager, toastManager, type ActionItem } from '@immich/ui';
|
||||
import {
|
||||
mdiCodeJson,
|
||||
mdiContentCopy,
|
||||
mdiContentDuplicate,
|
||||
mdiDeleteOutline,
|
||||
mdiDownload,
|
||||
mdiFileDocumentMultipleOutline,
|
||||
mdiPause,
|
||||
mdiPencil,
|
||||
mdiPlay,
|
||||
mdiPlus,
|
||||
} from '@mdi/js';
|
||||
import { mdiCodeJson, mdiDelete, mdiFileDocumentMultipleOutline, mdiPause, mdiPencil, mdiPlay, mdiPlus } from '@mdi/js';
|
||||
import type { MessageFormatter } from 'svelte-i18n';
|
||||
import { goto } from '$app/navigation';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import WorkflowDuplicateModal from '$lib/modals/WorkflowDuplicateModal.svelte';
|
||||
import WorkflowTemplatePicker from '$lib/modals/WorkflowTemplatePicker.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { copyToClipboard, downloadJson } from '$lib/utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
|
||||
@@ -60,50 +47,10 @@ export const getWorkflowActions = ($t: MessageFormatter, workflow: WorkflowRespo
|
||||
const ToggleEnabled: ActionItem = {
|
||||
title: workflow.enabled ? $t('disable') : $t('enable'),
|
||||
icon: workflow.enabled ? mdiPause : mdiPlay,
|
||||
color: workflow.enabled ? 'danger' : 'primary',
|
||||
onAction: () => handleUpdateWorkflow(workflow.id, { enabled: !workflow.enabled }),
|
||||
};
|
||||
|
||||
const CopyJson: ActionItem = {
|
||||
title: $t('copy_json'),
|
||||
icon: mdiContentCopy,
|
||||
onAction: () =>
|
||||
copyToClipboard(
|
||||
JSON.stringify(
|
||||
{
|
||||
name: workflow.name,
|
||||
description: workflow.description,
|
||||
enabled: workflow.enabled,
|
||||
trigger: workflow.trigger,
|
||||
steps: workflow.steps,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
const Download: ActionItem = {
|
||||
title: $t('download'),
|
||||
icon: mdiDownload,
|
||||
onAction: () =>
|
||||
downloadJson(
|
||||
{
|
||||
name: workflow.name,
|
||||
description: workflow.description,
|
||||
enabled: workflow.enabled,
|
||||
trigger: workflow.trigger,
|
||||
steps: workflow.steps,
|
||||
},
|
||||
'workflow.json',
|
||||
),
|
||||
};
|
||||
|
||||
const Duplicate: ActionItem = {
|
||||
title: $t('duplicate'),
|
||||
icon: mdiContentDuplicate,
|
||||
onAction: async () => modalManager.show(WorkflowDuplicateModal, { workflow }),
|
||||
};
|
||||
|
||||
const Edit: ActionItem = {
|
||||
title: $t('edit'),
|
||||
icon: mdiPencil,
|
||||
@@ -112,12 +59,14 @@ export const getWorkflowActions = ($t: MessageFormatter, workflow: WorkflowRespo
|
||||
|
||||
const Delete: ActionItem = {
|
||||
title: $t('delete'),
|
||||
icon: mdiDeleteOutline,
|
||||
icon: mdiDelete,
|
||||
color: 'danger',
|
||||
onAction: () => handleDeleteWorkflow(workflow),
|
||||
onAction: async () => {
|
||||
await handleDeleteWorkflow(workflow);
|
||||
},
|
||||
};
|
||||
|
||||
return { CopyJson, Download, Duplicate, ToggleEnabled, Edit, Delete };
|
||||
return { ToggleEnabled, Edit, Delete };
|
||||
};
|
||||
|
||||
export const getWorkflowShowSchemaAction = (
|
||||
@@ -136,10 +85,10 @@ export const handleCreateWorkflow = async (dto: WorkflowCreateDto) => {
|
||||
try {
|
||||
const response = await createWorkflow({ workflowCreateDto: dto });
|
||||
eventManager.emit('WorkflowCreate', response);
|
||||
toastManager.success();
|
||||
return response;
|
||||
return true;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_create'));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+2
-51
@@ -24,7 +24,6 @@ import { init, register, t } from 'svelte-i18n';
|
||||
import { derived, get } from 'svelte/store';
|
||||
import { defaultLang, locales } from '$lib/constants';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { downloadManager } from '$lib/managers/download-manager.svelte';
|
||||
import { alwaysLoadOriginalFile, lang } from '$lib/stores/preferences.store';
|
||||
import { isWebCompatibleImage } from '$lib/utils/asset-utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
@@ -250,65 +249,17 @@ export const getProfileImageUrl = (user: UserResponseDto) =>
|
||||
export const getPeopleThumbnailUrl = (person: PersonResponseDto, updatedAt?: string) =>
|
||||
createUrl(getPeopleThumbnailPath(person.id), { updatedAt: updatedAt ?? person.updatedAt });
|
||||
|
||||
export const copyToClipboard = async (secret: string | unknown) => {
|
||||
export const copyToClipboard = async (secret: string) => {
|
||||
const $t = get(t);
|
||||
|
||||
try {
|
||||
const value = typeof secret === 'string' ? secret : JSON.stringify(secret, jsonReplacer, 2);
|
||||
await navigator.clipboard.writeText(value);
|
||||
await navigator.clipboard.writeText(secret);
|
||||
toastManager.info($t('copied_to_clipboard'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_copy_to_clipboard'));
|
||||
}
|
||||
};
|
||||
|
||||
// https://stackoverflow.com/questions/16167581/sort-object-properties-and-json-stringify/43636793#43636793
|
||||
const jsonReplacer = (_key: string, value: unknown) =>
|
||||
value instanceof Object && !Array.isArray(value)
|
||||
? Object.keys(value)
|
||||
.sort()
|
||||
// eslint-disable-next-line unicorn/no-array-reduce
|
||||
.reduce((sorted: { [key: string]: unknown }, key) => {
|
||||
sorted[key] = (value as { [key: string]: unknown })[key];
|
||||
return sorted;
|
||||
}, {})
|
||||
: value;
|
||||
|
||||
export const downloadJson = (data: unknown, filename: string) => {
|
||||
const blob = new Blob([JSON.stringify(data, jsonReplacer, 2)], { type: 'application/json' });
|
||||
const downloadKey = filename;
|
||||
downloadManager.add(downloadKey, blob.size);
|
||||
downloadManager.update(downloadKey, blob.size);
|
||||
downloadBlob(blob, downloadKey);
|
||||
setTimeout(() => downloadManager.clear(downloadKey), 5000);
|
||||
};
|
||||
|
||||
export const downloadBlob = (data: Blob, filename: string) => {
|
||||
const url = URL.createObjectURL(data);
|
||||
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = filename;
|
||||
|
||||
document.body.append(anchor);
|
||||
anchor.click();
|
||||
anchor.remove();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
export const downloadUrl = (url: string, filename: string) => {
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = filename;
|
||||
|
||||
document.body.append(anchor);
|
||||
anchor.click();
|
||||
anchor.remove();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
export const oauth = {
|
||||
isCallback: (location: Location) => {
|
||||
const search = location.search;
|
||||
|
||||
@@ -26,7 +26,7 @@ import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { downloadManager } from '$lib/managers/download-manager.svelte';
|
||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||
import { downloadBlob, downloadRequest, withError } from '$lib/utils';
|
||||
import { downloadRequest, withError } from '$lib/utils';
|
||||
import { getByteUnitString } from '$lib/utils/byte-units';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import { navigate } from '$lib/utils/navigation';
|
||||
@@ -73,6 +73,32 @@ export const removeTag = async ({
|
||||
return assetIds;
|
||||
};
|
||||
|
||||
export const downloadBlob = (data: Blob, filename: string) => {
|
||||
const url = URL.createObjectURL(data);
|
||||
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = filename;
|
||||
|
||||
document.body.append(anchor);
|
||||
anchor.click();
|
||||
anchor.remove();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
export const downloadUrl = (url: string, filename: string) => {
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = filename;
|
||||
|
||||
document.body.append(anchor);
|
||||
anchor.click();
|
||||
anchor.remove();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
export const downloadArchive = async (fileName: string, options: Omit<DownloadInfoDto, 'archiveSize'>) => {
|
||||
const archiveSize = authManager.authenticated ? authManager.preferences.download.archiveSize : undefined;
|
||||
const dto = { ...options, archiveSize };
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
CardTitle,
|
||||
CodeBlock,
|
||||
Container,
|
||||
ContextMenuButton,
|
||||
Icon,
|
||||
IconButton,
|
||||
MenuItemType,
|
||||
menuManager,
|
||||
} from '@immich/ui';
|
||||
import { mdiClose, mdiFlashOutline } from '@mdi/js';
|
||||
import { mdiClose, mdiDotsVertical, mdiFlashOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import type { PageData } from './$types';
|
||||
@@ -35,7 +36,7 @@
|
||||
|
||||
const expandedIds = new SvelteSet<string>();
|
||||
|
||||
const onToggleExpand = (id: string) => {
|
||||
const toggleExpanded = (id: string) => {
|
||||
if (expandedIds.has(id)) {
|
||||
expandedIds.delete(id);
|
||||
} else {
|
||||
@@ -43,6 +44,21 @@
|
||||
}
|
||||
};
|
||||
|
||||
const showWorkflowMenu = (event: MouseEvent, workflow: WorkflowResponseDto) => {
|
||||
const { ToggleEnabled, Edit, Delete } = getWorkflowActions($t, workflow);
|
||||
void menuManager.show({
|
||||
target: event.currentTarget as HTMLElement,
|
||||
position: 'top-left',
|
||||
items: [
|
||||
ToggleEnabled,
|
||||
Edit,
|
||||
getWorkflowShowSchemaAction($t, expandedIds.has(workflow.id), () => toggleExpanded(workflow.id)),
|
||||
MenuItemType.Divider,
|
||||
Delete,
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const { Create, UseTemplate } = $derived(getWorkflowsActions($t));
|
||||
|
||||
const onWorkflowCreate = async (response: WorkflowResponseDto) => {
|
||||
@@ -75,8 +91,6 @@
|
||||
{:else}
|
||||
<div class="my-6 flex flex-col gap-3">
|
||||
{#each workflows as workflow (workflow.id)}
|
||||
{@const { ToggleEnabled, Duplicate, Edit, Delete } = getWorkflowActions($t, workflow)}
|
||||
|
||||
<Card class="group shadow-none transition-colors hover:border-primary">
|
||||
<CardHeader>
|
||||
<a
|
||||
@@ -114,16 +128,17 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<ContextMenuButton
|
||||
position="top-left"
|
||||
items={[
|
||||
ToggleEnabled,
|
||||
Edit,
|
||||
Duplicate,
|
||||
getWorkflowShowSchemaAction($t, expandedIds.has(workflow.id), () => onToggleExpand(workflow.id)),
|
||||
MenuItemType.Divider,
|
||||
Delete,
|
||||
]}
|
||||
<IconButton
|
||||
shape="round"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
icon={mdiDotsVertical}
|
||||
aria-label={$t('menu')}
|
||||
onclick={(event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
showWorkflowMenu(event, workflow);
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
|
||||
@@ -137,7 +152,7 @@
|
||||
fullWidth
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
onclick={() => onToggleExpand(workflow.id)}
|
||||
onclick={() => toggleExpanded(workflow.id)}
|
||||
>
|
||||
{$t('close')}
|
||||
</Button>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import WorkflowEditStepModal from '$lib/modals/WorkflowEditStepModal.svelte';
|
||||
import WorkflowTriggerPicker from '$lib/modals/WorkflowTriggerPicker.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { getWorkflowActions, handleUpdateWorkflow } from '$lib/services/workflow.service';
|
||||
import { handleUpdateWorkflow } from '$lib/services/workflow.service';
|
||||
import { getTriggerDescription, getTriggerName } from '$lib/utils/workflow';
|
||||
import type { WorkflowResponseDto, WorkflowStepDto, WorkflowUpdateDto } from '@immich/sdk';
|
||||
import {
|
||||
@@ -28,7 +28,6 @@
|
||||
Icon,
|
||||
IconButton,
|
||||
Input,
|
||||
MenuItemType,
|
||||
modalManager,
|
||||
Switch,
|
||||
Textarea,
|
||||
@@ -84,7 +83,7 @@
|
||||
let dropTargetIndex = $state<number | null>(null);
|
||||
|
||||
const workflowSummary = $derived({ name, description, trigger, steps });
|
||||
const workflowJsonContent = $derived<WorkflowJsonContent>({ name, description, enabled, trigger, steps });
|
||||
const workflowJsonContent = $derived<WorkflowJsonContent>({ description, enabled, name, steps, trigger });
|
||||
|
||||
const hasChanges = $derived(
|
||||
enabled !== savedWorkflow.enabled ||
|
||||
@@ -218,12 +217,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
const onWorkflowDelete = async (response: WorkflowResponseDto) => {
|
||||
if (id === response.id) {
|
||||
await goto(Route.workflows());
|
||||
}
|
||||
};
|
||||
|
||||
const confirmNavigation = async () => {
|
||||
if (!hasChanges) {
|
||||
return true;
|
||||
@@ -280,73 +273,60 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const { Download, Duplicate, CopyJson, Delete } = $derived(
|
||||
getWorkflowActions($t, { ...savedWorkflow, name, description, enabled, trigger, steps }),
|
||||
);
|
||||
</script>
|
||||
|
||||
<OnEvents {onWorkflowUpdate} {onWorkflowDelete} />
|
||||
<OnEvents {onWorkflowUpdate} />
|
||||
|
||||
<AppShell class="">
|
||||
<AppShellBar>
|
||||
<ActionBar
|
||||
shape="round"
|
||||
static
|
||||
{onClose}
|
||||
translations={{ close: $t('back') }}
|
||||
closeIcon={mdiArrowLeft}
|
||||
actions={[Duplicate, CopyJson, Download, Delete].map((item) => ({ ...item, color: undefined }))}
|
||||
>
|
||||
<ActionBar static {onClose} translations={{ close: $t('back') }} closeIcon={mdiArrowLeft}>
|
||||
<ControlBarHeader>
|
||||
<ControlBarTitle>{data.workflow.name}</ControlBarTitle>
|
||||
<ControlBarDescription>{data.workflow.description}</ControlBarDescription>
|
||||
</ControlBarHeader>
|
||||
<ControlBarContent class="flex items-center justify-end gap-6">
|
||||
{#if hasChanges}
|
||||
<div class="flex gap-1 rounded-full border border-light-200 bg-light p-1" role="group">
|
||||
<Button
|
||||
variant="filled"
|
||||
variant={editMode === 'visual' ? 'filled' : 'ghost'}
|
||||
color={editMode === 'visual' ? 'primary' : 'secondary'}
|
||||
size="small"
|
||||
color="primary"
|
||||
leadingIcon={mdiContentSave}
|
||||
disabled={!hasChanges || isSaving}
|
||||
loading={isSaving}
|
||||
onclick={saveWorkflow}
|
||||
leadingIcon={mdiFormatListBulletedSquare}
|
||||
aria-pressed={editMode === 'visual'}
|
||||
onclick={() => (editMode = 'visual')}
|
||||
shape="round"
|
||||
>
|
||||
{$t('save')}
|
||||
{$t('visual')}
|
||||
</Button>
|
||||
{/if}
|
||||
<Button
|
||||
variant={editMode === 'json' ? 'filled' : 'ghost'}
|
||||
color={editMode === 'json' ? 'primary' : 'secondary'}
|
||||
size="small"
|
||||
leadingIcon={mdiCodeJson}
|
||||
aria-pressed={editMode === 'json'}
|
||||
onclick={() => (editMode = 'json')}
|
||||
shape="round"
|
||||
>
|
||||
JSON
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="filled"
|
||||
size="small"
|
||||
color="primary"
|
||||
leadingIcon={mdiContentSave}
|
||||
disabled={!hasChanges || isSaving}
|
||||
loading={isSaving}
|
||||
onclick={saveWorkflow}
|
||||
>
|
||||
{$t('save')}
|
||||
</Button>
|
||||
</ControlBarContent>
|
||||
</ActionBar>
|
||||
</AppShellBar>
|
||||
|
||||
<Container size="medium" class="pt-8 pb-24" center>
|
||||
<VStack gap={4}>
|
||||
<div class="flex gap-1 rounded-full border border-light-200 bg-light p-1" role="group">
|
||||
<Button
|
||||
variant={editMode === 'visual' ? 'filled' : 'ghost'}
|
||||
color={editMode === 'visual' ? 'primary' : 'secondary'}
|
||||
size="small"
|
||||
leadingIcon={mdiFormatListBulletedSquare}
|
||||
aria-pressed={editMode === 'visual'}
|
||||
onclick={() => (editMode = 'visual')}
|
||||
shape="round"
|
||||
>
|
||||
{$t('visual')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={editMode === 'json' ? 'filled' : 'ghost'}
|
||||
color={editMode === 'json' ? 'primary' : 'secondary'}
|
||||
size="small"
|
||||
leadingIcon={mdiCodeJson}
|
||||
aria-pressed={editMode === 'json'}
|
||||
onclick={() => (editMode = 'json')}
|
||||
shape="round"
|
||||
>
|
||||
JSON
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{#if editMode === 'visual'}
|
||||
<Card class="shadow-none" expandable>
|
||||
<CardHeader>
|
||||
|
||||
Reference in New Issue
Block a user