mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05:00 
			
		
		
		
	chore: svelte 5 🎉 (#13738)
chore: svelte 5 Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com> Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									fed882a28a
								
							
						
					
					
						commit
						eadcbd52fb
					
				
							
								
								
									
										250
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										250
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -23,9 +23,9 @@
 | 
				
			|||||||
        "justified-layout": "^4.1.0",
 | 
					        "justified-layout": "^4.1.0",
 | 
				
			||||||
        "lodash-es": "^4.17.21",
 | 
					        "lodash-es": "^4.17.21",
 | 
				
			||||||
        "luxon": "^3.4.4",
 | 
					        "luxon": "^3.4.4",
 | 
				
			||||||
        "socket.io-client": "^4.7.4",
 | 
					        "socket.io-client": "~4.7.5",
 | 
				
			||||||
        "svelte-gestures": "^5.0.4",
 | 
					        "svelte-gestures": "^5.0.4",
 | 
				
			||||||
        "svelte-i18n": "^4.0.0",
 | 
					        "svelte-i18n": "^4.0.1",
 | 
				
			||||||
        "svelte-local-storage-store": "^0.6.4",
 | 
					        "svelte-local-storage-store": "^0.6.4",
 | 
				
			||||||
        "svelte-maplibre": "^0.9.13",
 | 
					        "svelte-maplibre": "^0.9.13",
 | 
				
			||||||
        "thumbhash": "^0.1.1"
 | 
					        "thumbhash": "^0.1.1"
 | 
				
			||||||
@ -35,12 +35,12 @@
 | 
				
			|||||||
        "@eslint/js": "^9.8.0",
 | 
					        "@eslint/js": "^9.8.0",
 | 
				
			||||||
        "@faker-js/faker": "^9.0.0",
 | 
					        "@faker-js/faker": "^9.0.0",
 | 
				
			||||||
        "@socket.io/component-emitter": "^3.1.0",
 | 
					        "@socket.io/component-emitter": "^3.1.0",
 | 
				
			||||||
        "@sveltejs/adapter-static": "^3.0.1",
 | 
					        "@sveltejs/adapter-static": "^3.0.5",
 | 
				
			||||||
        "@sveltejs/enhanced-img": "^0.3.0",
 | 
					        "@sveltejs/enhanced-img": "^0.3.0",
 | 
				
			||||||
        "@sveltejs/kit": "^2.5.18",
 | 
					        "@sveltejs/kit": "^2.7.2",
 | 
				
			||||||
        "@sveltejs/vite-plugin-svelte": "^3.1.2",
 | 
					        "@sveltejs/vite-plugin-svelte": "^4.0.0",
 | 
				
			||||||
        "@testing-library/jest-dom": "^6.4.2",
 | 
					        "@testing-library/jest-dom": "^6.4.2",
 | 
				
			||||||
        "@testing-library/svelte": "^5.2.0",
 | 
					        "@testing-library/svelte": "^5.2.4",
 | 
				
			||||||
        "@testing-library/user-event": "^14.5.2",
 | 
					        "@testing-library/user-event": "^14.5.2",
 | 
				
			||||||
        "@types/dom-to-image": "^2.6.7",
 | 
					        "@types/dom-to-image": "^2.6.7",
 | 
				
			||||||
        "@types/justified-layout": "^4.1.4",
 | 
					        "@types/justified-layout": "^4.1.4",
 | 
				
			||||||
@ -63,7 +63,7 @@
 | 
				
			|||||||
        "prettier-plugin-sort-json": "^4.0.0",
 | 
					        "prettier-plugin-sort-json": "^4.0.0",
 | 
				
			||||||
        "prettier-plugin-svelte": "^3.2.6",
 | 
					        "prettier-plugin-svelte": "^3.2.6",
 | 
				
			||||||
        "rollup-plugin-visualizer": "^5.12.0",
 | 
					        "rollup-plugin-visualizer": "^5.12.0",
 | 
				
			||||||
        "svelte": "^4.2.19",
 | 
					        "svelte": "^5.1.5",
 | 
				
			||||||
        "svelte-check": "^4.0.0",
 | 
					        "svelte-check": "^4.0.0",
 | 
				
			||||||
        "tailwindcss": "^3.4.1",
 | 
					        "tailwindcss": "^3.4.1",
 | 
				
			||||||
        "tslib": "^2.6.2",
 | 
					        "tslib": "^2.6.2",
 | 
				
			||||||
@ -80,7 +80,7 @@
 | 
				
			|||||||
        "@oazapfts/runtime": "^1.0.2"
 | 
					        "@oazapfts/runtime": "^1.0.2"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "devDependencies": {
 | 
					      "devDependencies": {
 | 
				
			||||||
        "@types/node": "^22.8.0",
 | 
					        "@types/node": "^22.8.1",
 | 
				
			||||||
        "typescript": "^5.3.3"
 | 
					        "typescript": "^5.3.3"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -1994,43 +1994,42 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@sveltejs/vite-plugin-svelte": {
 | 
					    "node_modules/@sveltejs/vite-plugin-svelte": {
 | 
				
			||||||
      "version": "3.1.2",
 | 
					      "version": "4.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA==",
 | 
					      "integrity": "sha512-kpVJwF+gNiMEsoHaw+FJL76IYiwBikkxYU83+BpqQLdVMff19KeRKLd2wisS8niNBMJ2omv5gG+iGDDwd8jzag==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "license": "MIT",
 | 
					      "license": "MIT",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@sveltejs/vite-plugin-svelte-inspector": "^2.1.0",
 | 
					        "@sveltejs/vite-plugin-svelte-inspector": "^3.0.0-next.0||^3.0.0",
 | 
				
			||||||
        "debug": "^4.3.4",
 | 
					        "debug": "^4.3.7",
 | 
				
			||||||
        "deepmerge": "^4.3.1",
 | 
					        "deepmerge": "^4.3.1",
 | 
				
			||||||
        "kleur": "^4.1.5",
 | 
					        "kleur": "^4.1.5",
 | 
				
			||||||
        "magic-string": "^0.30.10",
 | 
					        "magic-string": "^0.30.12",
 | 
				
			||||||
        "svelte-hmr": "^0.16.0",
 | 
					        "vitefu": "^1.0.3"
 | 
				
			||||||
        "vitefu": "^0.2.5"
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": "^18.0.0 || >=20"
 | 
					        "node": "^18.0.0 || ^20.0.0 || >=22"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "peerDependencies": {
 | 
					      "peerDependencies": {
 | 
				
			||||||
        "svelte": "^4.0.0 || ^5.0.0-next.0",
 | 
					        "svelte": "^5.0.0-next.96 || ^5.0.0",
 | 
				
			||||||
        "vite": "^5.0.0"
 | 
					        "vite": "^5.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
 | 
					    "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
 | 
				
			||||||
      "version": "2.1.0",
 | 
					      "version": "3.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-3.0.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==",
 | 
					      "integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "license": "MIT",
 | 
					      "license": "MIT",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "debug": "^4.3.4"
 | 
					        "debug": "^4.3.7"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": "^18.0.0 || >=20"
 | 
					        "node": "^18.0.0 || ^20.0.0 || >=22"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "peerDependencies": {
 | 
					      "peerDependencies": {
 | 
				
			||||||
        "@sveltejs/vite-plugin-svelte": "^3.0.0",
 | 
					        "@sveltejs/vite-plugin-svelte": "^4.0.0-next.0||^4.0.0",
 | 
				
			||||||
        "svelte": "^4.0.0 || ^5.0.0-next.0",
 | 
					        "svelte": "^5.0.0-next.96 || ^5.0.0",
 | 
				
			||||||
        "vite": "^5.0.0"
 | 
					        "vite": "^5.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -2230,7 +2229,6 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.2.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.2.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-EFdy73+lULQgMJ1WolAymrxWWrPv9DWyDuDFKKlUip2PA/EXuHptzfYOKWljccFWDKhhGOu3dqNmoc2f/h/Ecg==",
 | 
					      "integrity": "sha512-EFdy73+lULQgMJ1WolAymrxWWrPv9DWyDuDFKKlUip2PA/EXuHptzfYOKWljccFWDKhhGOu3dqNmoc2f/h/Ecg==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "license": "MIT",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@testing-library/dom": "^10.0.0"
 | 
					        "@testing-library/dom": "^10.0.0"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -2797,6 +2795,15 @@
 | 
				
			|||||||
        "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
 | 
					        "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/acorn-typescript": {
 | 
				
			||||||
 | 
					      "version": "1.4.13",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==",
 | 
				
			||||||
 | 
					      "license": "MIT",
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "acorn": ">=8.9.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/agent-base": {
 | 
					    "node_modules/agent-base": {
 | 
				
			||||||
      "version": "7.1.0",
 | 
					      "version": "7.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
 | 
				
			||||||
@ -2883,6 +2890,7 @@
 | 
				
			|||||||
      "version": "5.3.0",
 | 
					      "version": "5.3.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
 | 
					      "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "dequal": "^2.0.3"
 | 
					        "dequal": "^2.0.3"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -2960,11 +2968,12 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/axobject-query": {
 | 
					    "node_modules/axobject-query": {
 | 
				
			||||||
      "version": "4.0.0",
 | 
					      "version": "4.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==",
 | 
					      "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
 | 
				
			||||||
      "dependencies": {
 | 
					      "license": "Apache-2.0",
 | 
				
			||||||
        "dequal": "^2.0.3"
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">= 0.4"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/balanced-match": {
 | 
					    "node_modules/balanced-match": {
 | 
				
			||||||
@ -3271,18 +3280,6 @@
 | 
				
			|||||||
        "node": ">=6"
 | 
					        "node": ">=6"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/code-red": {
 | 
					 | 
				
			||||||
      "version": "1.0.4",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@jridgewell/sourcemap-codec": "^1.4.15",
 | 
					 | 
				
			||||||
        "@types/estree": "^1.0.1",
 | 
					 | 
				
			||||||
        "acorn": "^8.10.0",
 | 
					 | 
				
			||||||
        "estree-walker": "^3.0.3",
 | 
					 | 
				
			||||||
        "periscopic": "^3.1.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/color": {
 | 
					    "node_modules/color": {
 | 
				
			||||||
      "version": "4.2.3",
 | 
					      "version": "4.2.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
 | 
				
			||||||
@ -3404,18 +3401,6 @@
 | 
				
			|||||||
        "node": ">= 8"
 | 
					        "node": ">= 8"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/css-tree": {
 | 
					 | 
				
			||||||
      "version": "2.3.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "mdn-data": "2.0.30",
 | 
					 | 
				
			||||||
        "source-map-js": "^1.0.1"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/css.escape": {
 | 
					    "node_modules/css.escape": {
 | 
				
			||||||
      "version": "1.5.1",
 | 
					      "version": "1.5.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
 | 
				
			||||||
@ -3505,11 +3490,12 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/debug": {
 | 
					    "node_modules/debug": {
 | 
				
			||||||
      "version": "4.3.6",
 | 
					      "version": "4.3.7",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
 | 
				
			||||||
      "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
 | 
					      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
 | 
				
			||||||
 | 
					      "license": "MIT",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "ms": "2.1.2"
 | 
					        "ms": "^2.1.3"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">=6.0"
 | 
					        "node": ">=6.0"
 | 
				
			||||||
@ -3656,22 +3642,23 @@
 | 
				
			|||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/engine.io-client": {
 | 
					    "node_modules/engine.io-client": {
 | 
				
			||||||
      "version": "6.6.1",
 | 
					      "version": "6.5.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-aYuoak7I+R83M/BBPIOs2to51BmFIpC1wZe6zZzMrT2llVsHy5cvcmdsJgP2Qz6smHu+sD9oexiSUAVd8OfBPw==",
 | 
					      "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==",
 | 
				
			||||||
      "license": "MIT",
 | 
					      "license": "MIT",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@socket.io/component-emitter": "~3.1.0",
 | 
					        "@socket.io/component-emitter": "~3.1.0",
 | 
				
			||||||
        "debug": "~4.3.1",
 | 
					        "debug": "~4.3.1",
 | 
				
			||||||
        "engine.io-parser": "~5.2.1",
 | 
					        "engine.io-parser": "~5.2.1",
 | 
				
			||||||
        "ws": "~8.17.1",
 | 
					        "ws": "~8.17.1",
 | 
				
			||||||
        "xmlhttprequest-ssl": "~2.1.1"
 | 
					        "xmlhttprequest-ssl": "~2.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/engine.io-parser": {
 | 
					    "node_modules/engine.io-parser": {
 | 
				
			||||||
      "version": "5.2.1",
 | 
					      "version": "5.2.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==",
 | 
					      "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
 | 
				
			||||||
 | 
					      "license": "MIT",
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">=10.0.0"
 | 
					        "node": ">=10.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -4151,8 +4138,7 @@
 | 
				
			|||||||
    "node_modules/esm-env": {
 | 
					    "node_modules/esm-env": {
 | 
				
			||||||
      "version": "1.0.0",
 | 
					      "version": "1.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==",
 | 
					      "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA=="
 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/esniff": {
 | 
					    "node_modules/esniff": {
 | 
				
			||||||
      "version": "2.0.1",
 | 
					      "version": "2.0.1",
 | 
				
			||||||
@ -4197,6 +4183,16 @@
 | 
				
			|||||||
        "node": ">=0.10"
 | 
					        "node": ">=0.10"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/esrap": {
 | 
				
			||||||
 | 
					      "version": "1.2.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==",
 | 
				
			||||||
 | 
					      "license": "MIT",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@jridgewell/sourcemap-codec": "^1.4.15",
 | 
				
			||||||
 | 
					        "@types/estree": "^1.0.1"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/esrecurse": {
 | 
					    "node_modules/esrecurse": {
 | 
				
			||||||
      "version": "4.3.0",
 | 
					      "version": "4.3.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
 | 
				
			||||||
@ -4222,6 +4218,7 @@
 | 
				
			|||||||
      "version": "3.0.3",
 | 
					      "version": "3.0.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
 | 
					      "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@types/estree": "^1.0.0"
 | 
					        "@types/estree": "^1.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -4962,6 +4959,7 @@
 | 
				
			|||||||
      "version": "3.0.2",
 | 
					      "version": "3.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
 | 
					      "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
 | 
				
			||||||
 | 
					      "license": "MIT",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@types/estree": "*"
 | 
					        "@types/estree": "*"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -5329,9 +5327,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/magic-string": {
 | 
					    "node_modules/magic-string": {
 | 
				
			||||||
      "version": "0.30.11",
 | 
					      "version": "0.30.12",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
 | 
				
			||||||
      "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
 | 
					      "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
 | 
				
			||||||
      "license": "MIT",
 | 
					      "license": "MIT",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@jridgewell/sourcemap-codec": "^1.5.0"
 | 
					        "@jridgewell/sourcemap-codec": "^1.5.0"
 | 
				
			||||||
@ -5475,11 +5473,6 @@
 | 
				
			|||||||
        "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1"
 | 
					        "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/mdn-data": {
 | 
					 | 
				
			||||||
      "version": "2.0.30",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/memoizee": {
 | 
					    "node_modules/memoizee": {
 | 
				
			||||||
      "version": "0.4.17",
 | 
					      "version": "0.4.17",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz",
 | 
				
			||||||
@ -5601,9 +5594,10 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/ms": {
 | 
					    "node_modules/ms": {
 | 
				
			||||||
      "version": "2.1.2",
 | 
					      "version": "2.1.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
 | 
					      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
 | 
				
			||||||
 | 
					      "license": "MIT"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/murmurhash-js": {
 | 
					    "node_modules/murmurhash-js": {
 | 
				
			||||||
      "version": "1.0.0",
 | 
					      "version": "1.0.0",
 | 
				
			||||||
@ -5943,16 +5937,6 @@
 | 
				
			|||||||
        "pbf": "bin/pbf"
 | 
					        "pbf": "bin/pbf"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/periscopic": {
 | 
					 | 
				
			||||||
      "version": "3.1.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@types/estree": "^1.0.0",
 | 
					 | 
				
			||||||
        "estree-walker": "^3.0.0",
 | 
					 | 
				
			||||||
        "is-reference": "^3.0.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/picocolors": {
 | 
					    "node_modules/picocolors": {
 | 
				
			||||||
      "version": "1.1.0",
 | 
					      "version": "1.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
 | 
				
			||||||
@ -6860,14 +6844,14 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/socket.io-client": {
 | 
					    "node_modules/socket.io-client": {
 | 
				
			||||||
      "version": "4.8.1",
 | 
					      "version": "4.7.5",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz",
 | 
				
			||||||
      "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
 | 
					      "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==",
 | 
				
			||||||
      "license": "MIT",
 | 
					      "license": "MIT",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@socket.io/component-emitter": "~3.1.0",
 | 
					        "@socket.io/component-emitter": "~3.1.0",
 | 
				
			||||||
        "debug": "~4.3.2",
 | 
					        "debug": "~4.3.2",
 | 
				
			||||||
        "engine.io-client": "~6.6.1",
 | 
					        "engine.io-client": "~6.5.2",
 | 
				
			||||||
        "socket.io-parser": "~4.2.4"
 | 
					        "socket.io-parser": "~4.2.4"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
@ -6930,6 +6914,7 @@
 | 
				
			|||||||
      "version": "1.2.1",
 | 
					      "version": "1.2.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
 | 
					      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
      "license": "BSD-3-Clause",
 | 
					      "license": "BSD-3-Clause",
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">=0.10.0"
 | 
					        "node": ">=0.10.0"
 | 
				
			||||||
@ -7176,28 +7161,27 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/svelte": {
 | 
					    "node_modules/svelte": {
 | 
				
			||||||
      "version": "4.2.19",
 | 
					      "version": "5.1.5",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.1.5.tgz",
 | 
				
			||||||
      "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==",
 | 
					      "integrity": "sha512-AyYondx6wS0g8mmBMfwJVnOYYBswjBv6L4bc99awfbET2KozWvVwxe8NSN7fhx7Pgr7pOfOXIv7K8+Impc0OoQ==",
 | 
				
			||||||
      "license": "MIT",
 | 
					      "license": "MIT",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@ampproject/remapping": "^2.2.1",
 | 
					        "@ampproject/remapping": "^2.3.0",
 | 
				
			||||||
        "@jridgewell/sourcemap-codec": "^1.4.15",
 | 
					        "@jridgewell/sourcemap-codec": "^1.5.0",
 | 
				
			||||||
        "@jridgewell/trace-mapping": "^0.3.18",
 | 
					        "@types/estree": "^1.0.5",
 | 
				
			||||||
        "@types/estree": "^1.0.1",
 | 
					        "acorn": "^8.12.1",
 | 
				
			||||||
        "acorn": "^8.9.0",
 | 
					        "acorn-typescript": "^1.4.13",
 | 
				
			||||||
        "aria-query": "^5.3.0",
 | 
					        "aria-query": "^5.3.1",
 | 
				
			||||||
        "axobject-query": "^4.0.0",
 | 
					        "axobject-query": "^4.1.0",
 | 
				
			||||||
        "code-red": "^1.0.3",
 | 
					        "esm-env": "^1.0.0",
 | 
				
			||||||
        "css-tree": "^2.3.1",
 | 
					        "esrap": "^1.2.2",
 | 
				
			||||||
        "estree-walker": "^3.0.3",
 | 
					        "is-reference": "^3.0.2",
 | 
				
			||||||
        "is-reference": "^3.0.1",
 | 
					 | 
				
			||||||
        "locate-character": "^3.0.0",
 | 
					        "locate-character": "^3.0.0",
 | 
				
			||||||
        "magic-string": "^0.30.4",
 | 
					        "magic-string": "^0.30.11",
 | 
				
			||||||
        "periscopic": "^3.1.0"
 | 
					        "zimmerframe": "^1.1.2"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">=16"
 | 
					        "node": ">=18"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/svelte-check": {
 | 
					    "node_modules/svelte-check": {
 | 
				
			||||||
@ -7318,18 +7302,6 @@
 | 
				
			|||||||
      "integrity": "sha512-kElJnoZrQtlkXE0O/RcKioz9NP0Sxx05j31ohyosNkydo6NOEsZB85mhoaCxOQNjxN+QPumYWfmIUsznYFjihA==",
 | 
					      "integrity": "sha512-kElJnoZrQtlkXE0O/RcKioz9NP0Sxx05j31ohyosNkydo6NOEsZB85mhoaCxOQNjxN+QPumYWfmIUsznYFjihA==",
 | 
				
			||||||
      "license": "MIT"
 | 
					      "license": "MIT"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/svelte-hmr": {
 | 
					 | 
				
			||||||
      "version": "0.16.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": "^12.20 || ^14.13.1 || >= 16"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependencies": {
 | 
					 | 
				
			||||||
        "svelte": "^3.19.0 || ^4.0.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/svelte-i18n": {
 | 
					    "node_modules/svelte-i18n": {
 | 
				
			||||||
      "version": "4.0.1",
 | 
					      "version": "4.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/svelte-i18n/-/svelte-i18n-4.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/svelte-i18n/-/svelte-i18n-4.0.1.tgz",
 | 
				
			||||||
@ -7821,6 +7793,15 @@
 | 
				
			|||||||
        "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.1"
 | 
					        "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/svelte/node_modules/aria-query": {
 | 
				
			||||||
 | 
					      "version": "5.3.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
 | 
				
			||||||
 | 
					      "license": "Apache-2.0",
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">= 0.4"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/symbol-tree": {
 | 
					    "node_modules/symbol-tree": {
 | 
				
			||||||
      "version": "3.2.4",
 | 
					      "version": "3.2.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
 | 
				
			||||||
@ -8420,12 +8401,17 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/vitefu": {
 | 
					    "node_modules/vitefu": {
 | 
				
			||||||
      "version": "0.2.5",
 | 
					      "version": "1.0.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==",
 | 
					      "integrity": "sha512-iKKfOMBHob2WxEJbqbJjHAkmYgvFDPhuqrO82om83S8RLk+17FtyMBfcyeH8GqD0ihShtkMW/zzJgiA51hCNCQ==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "license": "MIT",
 | 
				
			||||||
 | 
					      "workspaces": [
 | 
				
			||||||
 | 
					        "tests/deps/*",
 | 
				
			||||||
 | 
					        "tests/projects/*"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
      "peerDependencies": {
 | 
					      "peerDependencies": {
 | 
				
			||||||
        "vite": "^3.0.0 || ^4.0.0 || ^5.0.0"
 | 
					        "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0-beta.0"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "peerDependenciesMeta": {
 | 
					      "peerDependenciesMeta": {
 | 
				
			||||||
        "vite": {
 | 
					        "vite": {
 | 
				
			||||||
@ -8757,9 +8743,9 @@
 | 
				
			|||||||
      "peer": true
 | 
					      "peer": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/xmlhttprequest-ssl": {
 | 
					    "node_modules/xmlhttprequest-ssl": {
 | 
				
			||||||
      "version": "2.1.1",
 | 
					      "version": "2.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==",
 | 
					      "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">=0.4.0"
 | 
					        "node": ">=0.4.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -8820,6 +8806,12 @@
 | 
				
			|||||||
      "funding": {
 | 
					      "funding": {
 | 
				
			||||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
					        "url": "https://github.com/sponsors/sindresorhus"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/zimmerframe": {
 | 
				
			||||||
 | 
					      "version": "1.1.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
 | 
				
			||||||
 | 
					      "license": "MIT"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -8,7 +8,7 @@
 | 
				
			|||||||
    "build:stats": "BUILD_STATS=true vite build",
 | 
					    "build:stats": "BUILD_STATS=true vite build",
 | 
				
			||||||
    "package": "svelte-kit package",
 | 
					    "package": "svelte-kit package",
 | 
				
			||||||
    "preview": "vite preview",
 | 
					    "preview": "vite preview",
 | 
				
			||||||
    "check:svelte": "svelte-check --no-tsconfig --fail-on-warnings",
 | 
					    "check:svelte": "svelte-check --no-tsconfig --fail-on-warnings --compiler-warnings 'reactive_declaration_non_reactive_property:ignore'",
 | 
				
			||||||
    "check:typescript": "tsc --noEmit",
 | 
					    "check:typescript": "tsc --noEmit",
 | 
				
			||||||
    "check:watch": "npm run check:svelte -- --watch",
 | 
					    "check:watch": "npm run check:svelte -- --watch",
 | 
				
			||||||
    "check:code": "npm run format && npm run lint && npm run check:svelte && npm run check:typescript",
 | 
					    "check:code": "npm run format && npm run lint && npm run check:svelte && npm run check:typescript",
 | 
				
			||||||
@ -27,12 +27,12 @@
 | 
				
			|||||||
    "@eslint/js": "^9.8.0",
 | 
					    "@eslint/js": "^9.8.0",
 | 
				
			||||||
    "@faker-js/faker": "^9.0.0",
 | 
					    "@faker-js/faker": "^9.0.0",
 | 
				
			||||||
    "@socket.io/component-emitter": "^3.1.0",
 | 
					    "@socket.io/component-emitter": "^3.1.0",
 | 
				
			||||||
    "@sveltejs/adapter-static": "^3.0.1",
 | 
					    "@sveltejs/adapter-static": "^3.0.5",
 | 
				
			||||||
    "@sveltejs/enhanced-img": "^0.3.0",
 | 
					    "@sveltejs/enhanced-img": "^0.3.0",
 | 
				
			||||||
    "@sveltejs/kit": "^2.5.18",
 | 
					    "@sveltejs/kit": "^2.7.2",
 | 
				
			||||||
    "@sveltejs/vite-plugin-svelte": "^3.1.2",
 | 
					    "@sveltejs/vite-plugin-svelte": "^4.0.0",
 | 
				
			||||||
    "@testing-library/jest-dom": "^6.4.2",
 | 
					    "@testing-library/jest-dom": "^6.4.2",
 | 
				
			||||||
    "@testing-library/svelte": "^5.2.0",
 | 
					    "@testing-library/svelte": "^5.2.4",
 | 
				
			||||||
    "@testing-library/user-event": "^14.5.2",
 | 
					    "@testing-library/user-event": "^14.5.2",
 | 
				
			||||||
    "@types/dom-to-image": "^2.6.7",
 | 
					    "@types/dom-to-image": "^2.6.7",
 | 
				
			||||||
    "@types/justified-layout": "^4.1.4",
 | 
					    "@types/justified-layout": "^4.1.4",
 | 
				
			||||||
@ -55,7 +55,7 @@
 | 
				
			|||||||
    "prettier-plugin-sort-json": "^4.0.0",
 | 
					    "prettier-plugin-sort-json": "^4.0.0",
 | 
				
			||||||
    "prettier-plugin-svelte": "^3.2.6",
 | 
					    "prettier-plugin-svelte": "^3.2.6",
 | 
				
			||||||
    "rollup-plugin-visualizer": "^5.12.0",
 | 
					    "rollup-plugin-visualizer": "^5.12.0",
 | 
				
			||||||
    "svelte": "^4.2.19",
 | 
					    "svelte": "^5.1.5",
 | 
				
			||||||
    "svelte-check": "^4.0.0",
 | 
					    "svelte-check": "^4.0.0",
 | 
				
			||||||
    "tailwindcss": "^3.4.1",
 | 
					    "tailwindcss": "^3.4.1",
 | 
				
			||||||
    "tslib": "^2.6.2",
 | 
					    "tslib": "^2.6.2",
 | 
				
			||||||
@ -79,9 +79,9 @@
 | 
				
			|||||||
    "justified-layout": "^4.1.0",
 | 
					    "justified-layout": "^4.1.0",
 | 
				
			||||||
    "lodash-es": "^4.17.21",
 | 
					    "lodash-es": "^4.17.21",
 | 
				
			||||||
    "luxon": "^3.4.4",
 | 
					    "luxon": "^3.4.4",
 | 
				
			||||||
    "socket.io-client": "^4.7.4",
 | 
					    "socket.io-client": "~4.7.5",
 | 
				
			||||||
    "svelte-gestures": "^5.0.4",
 | 
					    "svelte-gestures": "^5.0.4",
 | 
				
			||||||
    "svelte-i18n": "^4.0.0",
 | 
					    "svelte-i18n": "^4.0.1",
 | 
				
			||||||
    "svelte-local-storage-store": "^0.6.4",
 | 
					    "svelte-local-storage-store": "^0.6.4",
 | 
				
			||||||
    "svelte-maplibre": "^0.9.13",
 | 
					    "svelte-maplibre": "^0.9.13",
 | 
				
			||||||
    "thumbhash": "^0.1.1"
 | 
					    "thumbhash": "^0.1.1"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										17
									
								
								web/src/lib/__mocks__/animate.mock.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								web/src/lib/__mocks__/animate.mock.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					import { tick } from 'svelte';
 | 
				
			||||||
 | 
					import { vi } from 'vitest';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getAnimateMock = () =>
 | 
				
			||||||
 | 
					  vi.fn().mockImplementation(() => {
 | 
				
			||||||
 | 
					    let onfinish: (() => void) | null = null;
 | 
				
			||||||
 | 
					    void tick().then(() => onfinish?.());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      set onfinish(fn: () => void) {
 | 
				
			||||||
 | 
					        onfinish = fn;
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      cancel() {
 | 
				
			||||||
 | 
					        onfinish = null;
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
@ -14,14 +14,14 @@
 | 
				
			|||||||
    mdiPlay,
 | 
					    mdiPlay,
 | 
				
			||||||
    mdiSelectionSearch,
 | 
					    mdiSelectionSearch,
 | 
				
			||||||
  } from '@mdi/js';
 | 
					  } from '@mdi/js';
 | 
				
			||||||
  import { type ComponentType } from 'svelte';
 | 
					  import { type Component } from 'svelte';
 | 
				
			||||||
  import { t } from 'svelte-i18n';
 | 
					  import { t } from 'svelte-i18n';
 | 
				
			||||||
  import JobTileButton from './job-tile-button.svelte';
 | 
					  import JobTileButton from './job-tile-button.svelte';
 | 
				
			||||||
  import JobTileStatus from './job-tile-status.svelte';
 | 
					  import JobTileStatus from './job-tile-status.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let title: string;
 | 
					  export let title: string;
 | 
				
			||||||
  export let subtitle: string | undefined;
 | 
					  export let subtitle: string | undefined;
 | 
				
			||||||
  export let description: ComponentType | undefined;
 | 
					  export let description: Component | undefined;
 | 
				
			||||||
  export let jobCounts: JobCountsDto;
 | 
					  export let jobCounts: JobCountsDto;
 | 
				
			||||||
  export let queueStatus: QueueStatusDto;
 | 
					  export let queueStatus: QueueStatusDto;
 | 
				
			||||||
  export let icon: string;
 | 
					  export let icon: string;
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,7 @@
 | 
				
			|||||||
    mdiTagFaces,
 | 
					    mdiTagFaces,
 | 
				
			||||||
    mdiVideo,
 | 
					    mdiVideo,
 | 
				
			||||||
  } from '@mdi/js';
 | 
					  } from '@mdi/js';
 | 
				
			||||||
  import type { ComponentType } from 'svelte';
 | 
					  import type { Component } from 'svelte';
 | 
				
			||||||
  import JobTile from './job-tile.svelte';
 | 
					  import JobTile from './job-tile.svelte';
 | 
				
			||||||
  import StorageMigrationDescription from './storage-migration-description.svelte';
 | 
					  import StorageMigrationDescription from './storage-migration-description.svelte';
 | 
				
			||||||
  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
 | 
					  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
 | 
				
			||||||
@ -30,7 +30,7 @@
 | 
				
			|||||||
  interface JobDetails {
 | 
					  interface JobDetails {
 | 
				
			||||||
    title: string;
 | 
					    title: string;
 | 
				
			||||||
    subtitle?: string;
 | 
					    subtitle?: string;
 | 
				
			||||||
    description?: ComponentType;
 | 
					    description?: Component;
 | 
				
			||||||
    allText?: string;
 | 
					    allText?: string;
 | 
				
			||||||
    refreshText?: string;
 | 
					    refreshText?: string;
 | 
				
			||||||
    missingText: string;
 | 
					    missingText: string;
 | 
				
			||||||
@ -56,6 +56,7 @@
 | 
				
			|||||||
    await handleCommand(jobId, dto);
 | 
					    await handleCommand(jobId, dto);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: jobDetails = <Partial<Record<JobName, JobDetails>>>{
 | 
					  $: jobDetails = <Partial<Record<JobName, JobDetails>>>{
 | 
				
			||||||
    [JobName.ThumbnailGeneration]: {
 | 
					    [JobName.ThumbnailGeneration]: {
 | 
				
			||||||
      icon: mdiFileJpgBox,
 | 
					      icon: mdiFileJpgBox,
 | 
				
			||||||
 | 
				
			|||||||
@ -87,6 +87,7 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: {
 | 
					  $: {
 | 
				
			||||||
    if (selectedGroupOption.id === AlbumGroupBy.None) {
 | 
					    if (selectedGroupOption.id === AlbumGroupBy.None) {
 | 
				
			||||||
      groupIcon = mdiFolderRemoveOutline;
 | 
					      groupIcon = mdiFolderRemoveOutline;
 | 
				
			||||||
@ -96,8 +97,10 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: sortIcon = $albumViewSettings.sortOrder === SortOrder.Desc ? mdiArrowDownThin : mdiArrowUpThin;
 | 
					  $: sortIcon = $albumViewSettings.sortOrder === SortOrder.Desc ? mdiArrowDownThin : mdiArrowUpThin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: albumFilterNames = ((): Record<AlbumFilter, string> => {
 | 
					  $: albumFilterNames = ((): Record<AlbumFilter, string> => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      [AlbumFilter.All]: $t('all'),
 | 
					      [AlbumFilter.All]: $t('all'),
 | 
				
			||||||
@ -106,6 +109,7 @@
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
  })();
 | 
					  })();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: albumSortByNames = ((): Record<AlbumSortBy, string> => {
 | 
					  $: albumSortByNames = ((): Record<AlbumSortBy, string> => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      [AlbumSortBy.Title]: $t('sort_title'),
 | 
					      [AlbumSortBy.Title]: $t('sort_title'),
 | 
				
			||||||
@ -117,6 +121,7 @@
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
  })();
 | 
					  })();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: albumGroupByNames = ((): Record<AlbumGroupBy, string> => {
 | 
					  $: albumGroupByNames = ((): Record<AlbumGroupBy, string> => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      [AlbumGroupBy.None]: $t('group_no'),
 | 
					      [AlbumGroupBy.None]: $t('group_no'),
 | 
				
			||||||
 | 
				
			|||||||
@ -135,6 +135,7 @@
 | 
				
			|||||||
  let isOpen = false;
 | 
					  let isOpen = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Step 1: Filter between Owned and Shared albums, or both.
 | 
					  // Step 1: Filter between Owned and Shared albums, or both.
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: {
 | 
					  $: {
 | 
				
			||||||
    switch (userSettings.filter) {
 | 
					    switch (userSettings.filter) {
 | 
				
			||||||
      case AlbumFilter.Owned: {
 | 
					      case AlbumFilter.Owned: {
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@
 | 
				
			|||||||
      $albumViewSettings.sortOrder = option.defaultOrder;
 | 
					      $albumViewSettings.sortOrder = option.defaultOrder;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: albumSortByNames = ((): Record<AlbumSortBy, string> => {
 | 
					  $: albumSortByNames = ((): Record<AlbumSortBy, string> => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      [AlbumSortBy.Title]: $t('sort_title'),
 | 
					      [AlbumSortBy.Title]: $t('sort_title'),
 | 
				
			||||||
 | 
				
			|||||||
@ -293,7 +293,7 @@
 | 
				
			|||||||
              class="h-[18px] {disabled
 | 
					              class="h-[18px] {disabled
 | 
				
			||||||
                ? 'cursor-not-allowed'
 | 
					                ? 'cursor-not-allowed'
 | 
				
			||||||
                : ''} w-full max-h-56 pr-2 items-center overflow-y-auto leading-4 outline-none resize-none bg-gray-200"
 | 
					                : ''} w-full max-h-56 pr-2 items-center overflow-y-auto leading-4 outline-none resize-none bg-gray-200"
 | 
				
			||||||
            />
 | 
					            ></textarea>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          {#if isSendingMessage}
 | 
					          {#if isSendingMessage}
 | 
				
			||||||
            <div class="flex items-end place-items-center pb-2 ml-0">
 | 
					            <div class="flex items-end place-items-center pb-2 ml-0">
 | 
				
			||||||
 | 
				
			|||||||
@ -15,13 +15,31 @@ describe('AssetViewerNavBar component', () => {
 | 
				
			|||||||
    showShareButton: false,
 | 
					    showShareButton: false,
 | 
				
			||||||
    onZoomImage: () => {},
 | 
					    onZoomImage: () => {},
 | 
				
			||||||
    onCopyImage: () => {},
 | 
					    onCopyImage: () => {},
 | 
				
			||||||
 | 
					    onAction: () => {},
 | 
				
			||||||
 | 
					    onRunJob: () => {},
 | 
				
			||||||
 | 
					    onPlaySlideshow: () => {},
 | 
				
			||||||
 | 
					    onShowDetail: () => {},
 | 
				
			||||||
 | 
					    onClose: () => {},
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(() => {
 | 
				
			||||||
 | 
					    Element.prototype.animate = vi.fn().mockImplementation(() => ({
 | 
				
			||||||
 | 
					      cancel: () => {},
 | 
				
			||||||
 | 
					    }));
 | 
				
			||||||
 | 
					    vi.stubGlobal(
 | 
				
			||||||
 | 
					      'ResizeObserver',
 | 
				
			||||||
 | 
					      vi.fn(() => ({ observe: vi.fn(), unobserve: vi.fn(), disconnect: vi.fn() })),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  afterEach(() => {
 | 
					  afterEach(() => {
 | 
				
			||||||
    vi.resetAllMocks();
 | 
					 | 
				
			||||||
    resetSavedUser();
 | 
					    resetSavedUser();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterAll(() => {
 | 
				
			||||||
 | 
					    vi.restoreAllMocks();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('shows back button', () => {
 | 
					  it('shows back button', () => {
 | 
				
			||||||
    const asset = assetFactory.build({ isTrashed: false });
 | 
					    const asset = assetFactory.build({ isTrashed: false });
 | 
				
			||||||
    const { getByTitle } = render(AssetViewerNavBar, { asset, ...additionalProps });
 | 
					    const { getByTitle } = render(AssetViewerNavBar, { asset, ...additionalProps });
 | 
				
			||||||
 | 
				
			|||||||
@ -61,6 +61,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const sharedLink = getSharedLink();
 | 
					  const sharedLink = getSharedLink();
 | 
				
			||||||
  $: isOwner = $user && asset.ownerId === $user?.id;
 | 
					  $: isOwner = $user && asset.ownerId === $user?.id;
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: showDownloadButton = sharedLink ? sharedLink.allowDownload : !asset.isOffline;
 | 
					  $: showDownloadButton = sharedLink ? sharedLink.allowDownload : !asset.isOffline;
 | 
				
			||||||
  // $: showEditorButton =
 | 
					  // $: showEditorButton =
 | 
				
			||||||
  //   isOwner &&
 | 
					  //   isOwner &&
 | 
				
			||||||
 | 
				
			|||||||
@ -598,7 +598,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            {#if stackedAsset.id == asset.id}
 | 
					            {#if stackedAsset.id == asset.id}
 | 
				
			||||||
              <div class="w-full flex place-items-center place-content-center">
 | 
					              <div class="w-full flex place-items-center place-content-center">
 | 
				
			||||||
                <div class="w-2 h-2 bg-white rounded-full flex mt-[2px]" />
 | 
					                <div class="w-2 h-2 bg-white rounded-full flex mt-[2px]"></div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            {/if}
 | 
					            {/if}
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -154,7 +154,7 @@
 | 
				
			|||||||
        <div class="border border-t-0 border-red-400 bg-red-100 px-4 py-3 text-red-700">
 | 
					        <div class="border border-t-0 border-red-400 bg-red-100 px-4 py-3 text-red-700">
 | 
				
			||||||
          <p>
 | 
					          <p>
 | 
				
			||||||
            {#if $user?.isAdmin}
 | 
					            {#if $user?.isAdmin}
 | 
				
			||||||
              <p>{$t('admin.asset_offline_description')}</p>
 | 
					              {$t('admin.asset_offline_description')}
 | 
				
			||||||
            {:else}
 | 
					            {:else}
 | 
				
			||||||
              {$t('asset_offline_description')}
 | 
					              {$t('asset_offline_description')}
 | 
				
			||||||
            {/if}
 | 
					            {/if}
 | 
				
			||||||
 | 
				
			|||||||
@ -32,7 +32,7 @@
 | 
				
			|||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="flex place-items-center gap-2">
 | 
					            <div class="flex place-items-center gap-2">
 | 
				
			||||||
              <div class="h-[7px] w-full rounded-full bg-gray-200 dark:bg-gray-700">
 | 
					              <div class="h-[7px] w-full rounded-full bg-gray-200 dark:bg-gray-700">
 | 
				
			||||||
                <div class="h-[7px] rounded-full bg-immich-primary" style={`width: ${download.percentage}%`} />
 | 
					                <div class="h-[7px] rounded-full bg-immich-primary" style={`width: ${download.percentage}%`}></div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              <p class="min-w-[4em] whitespace-nowrap text-right">
 | 
					              <p class="min-w-[4em] whitespace-nowrap text-right">
 | 
				
			||||||
                <span class="text-immich-primary">
 | 
					                <span class="text-immich-primary">
 | 
				
			||||||
 | 
				
			|||||||
@ -23,6 +23,7 @@
 | 
				
			|||||||
  export let onClose: () => void;
 | 
					  export let onClose: () => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let selectedType: string = editTypes[0].name;
 | 
					  let selectedType: string = editTypes[0].name;
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: selectedTypeObj = editTypes.find((t) => t.name === selectedType) || editTypes[0];
 | 
					  $: selectedTypeObj = editTypes.find((t) => t.name === selectedType) || editTypes[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setTimeout(() => {
 | 
					  setTimeout(() => {
 | 
				
			||||||
 | 
				
			|||||||
@ -55,4 +55,4 @@
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="h-full w-full mb-0" bind:this={container} />
 | 
					<div class="h-full w-full mb-0" bind:this={container}></div>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { getAnimateMock } from '$lib/__mocks__/animate.mock';
 | 
				
			||||||
import PhotoViewer from '$lib/components/asset-viewer/photo-viewer.svelte';
 | 
					import PhotoViewer from '$lib/components/asset-viewer/photo-viewer.svelte';
 | 
				
			||||||
import * as utils from '$lib/utils';
 | 
					import * as utils from '$lib/utils';
 | 
				
			||||||
import { AssetMediaSize } from '@immich/sdk';
 | 
					import { AssetMediaSize } from '@immich/sdk';
 | 
				
			||||||
@ -24,6 +25,10 @@ describe('PhotoViewer component', () => {
 | 
				
			|||||||
    getAssetThumbnailUrlSpy = vi.spyOn(utils, 'getAssetThumbnailUrl');
 | 
					    getAssetThumbnailUrlSpy = vi.spyOn(utils, 'getAssetThumbnailUrl');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(() => {
 | 
				
			||||||
 | 
					    Element.prototype.animate = getAnimateMock();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  afterEach(() => {
 | 
					  afterEach(() => {
 | 
				
			||||||
    vi.resetAllMocks();
 | 
					    vi.resetAllMocks();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
				
			|||||||
@ -193,7 +193,7 @@
 | 
				
			|||||||
        <div
 | 
					        <div
 | 
				
			||||||
          class="absolute border-solid border-white border-[3px] rounded-lg"
 | 
					          class="absolute border-solid border-white border-[3px] rounded-lg"
 | 
				
			||||||
          style="top: {boundingbox.top}px; left: {boundingbox.left}px; height: {boundingbox.height}px; width: {boundingbox.width}px;"
 | 
					          style="top: {boundingbox.top}px; left: {boundingbox.left}px; height: {boundingbox.height}px; width: {boundingbox.width}px;"
 | 
				
			||||||
        />
 | 
					        ></div>
 | 
				
			||||||
      {/each}
 | 
					      {/each}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  {/if}
 | 
					  {/if}
 | 
				
			||||||
 | 
				
			|||||||
@ -3,9 +3,9 @@ import { render } from '@testing-library/svelte';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
describe('ImageThumbnail component', () => {
 | 
					describe('ImageThumbnail component', () => {
 | 
				
			||||||
  beforeAll(() => {
 | 
					  beforeAll(() => {
 | 
				
			||||||
    Object.defineProperty(HTMLImageElement.prototype, 'complete', {
 | 
					    Element.prototype.animate = vi.fn().mockImplementation(() => ({
 | 
				
			||||||
      value: true,
 | 
					      cancel: () => {},
 | 
				
			||||||
    });
 | 
					    }));
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('shows thumbhash while image is loading', () => {
 | 
					  it('shows thumbhash while image is loading', () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -96,5 +96,5 @@
 | 
				
			|||||||
    class:rounded-full={circle}
 | 
					    class:rounded-full={circle}
 | 
				
			||||||
    draggable="false"
 | 
					    draggable="false"
 | 
				
			||||||
    out:fade={{ duration: THUMBHASH_FADE_DURATION }}
 | 
					    out:fade={{ duration: THUMBHASH_FADE_DURATION }}
 | 
				
			||||||
  />
 | 
					  ></canvas>
 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
 | 
				
			|||||||
@ -218,6 +218,7 @@
 | 
				
			|||||||
          href={currentUrlReplaceAssetId(asset.id)}
 | 
					          href={currentUrlReplaceAssetId(asset.id)}
 | 
				
			||||||
          on:click={(evt) => evt.preventDefault()}
 | 
					          on:click={(evt) => evt.preventDefault()}
 | 
				
			||||||
          tabindex={0}
 | 
					          tabindex={0}
 | 
				
			||||||
 | 
					          aria-label="Thumbnail URL"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
      {/if}
 | 
					      {/if}
 | 
				
			||||||
@ -255,12 +256,12 @@
 | 
				
			|||||||
        <div
 | 
					        <div
 | 
				
			||||||
          class="absolute z-10 h-full w-full bg-gradient-to-b from-black/25 via-[transparent_25%] opacity-0 transition-opacity group-hover:opacity-100"
 | 
					          class="absolute z-10 h-full w-full bg-gradient-to-b from-black/25 via-[transparent_25%] opacity-0 transition-opacity group-hover:opacity-100"
 | 
				
			||||||
          class:rounded-xl={selected}
 | 
					          class:rounded-xl={selected}
 | 
				
			||||||
        />
 | 
					        ></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <!-- Outline on focus -->
 | 
					        <!-- Outline on focus -->
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          class="absolute size-full group-focus-visible:outline outline-4 -outline-offset-4 outline-immich-primary"
 | 
					          class="absolute size-full group-focus-visible:outline outline-4 -outline-offset-4 outline-immich-primary"
 | 
				
			||||||
        />
 | 
					        ></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <!-- Favorite asset star -->
 | 
					        <!-- Favorite asset star -->
 | 
				
			||||||
        {#if !isSharedLink() && asset.isFavorite}
 | 
					        {#if !isSharedLink() && asset.isFavorite}
 | 
				
			||||||
@ -339,7 +340,7 @@
 | 
				
			|||||||
          class="absolute top-0 h-full w-full bg-immich-primary opacity-40"
 | 
					          class="absolute top-0 h-full w-full bg-immich-primary opacity-40"
 | 
				
			||||||
          in:fade={{ duration: 100 }}
 | 
					          in:fade={{ duration: 100 }}
 | 
				
			||||||
          out:fade={{ duration: 100 }}
 | 
					          out:fade={{ duration: 100 }}
 | 
				
			||||||
        />
 | 
					        ></div>
 | 
				
			||||||
      {/if}
 | 
					      {/if}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  {/if}
 | 
					  {/if}
 | 
				
			||||||
 | 
				
			|||||||
@ -113,7 +113,10 @@
 | 
				
			|||||||
    }}
 | 
					    }}
 | 
				
			||||||
    on:timeupdate={({ currentTarget }) => {
 | 
					    on:timeupdate={({ currentTarget }) => {
 | 
				
			||||||
      const remaining = currentTarget.duration - currentTarget.currentTime;
 | 
					      const remaining = currentTarget.duration - currentTarget.currentTime;
 | 
				
			||||||
      remainingSeconds = Math.min(Math.ceil(remaining), durationInSeconds);
 | 
					      remainingSeconds = Math.min(
 | 
				
			||||||
 | 
					        Math.ceil(Number.isNaN(remaining) ? Number.POSITIVE_INFINITY : remaining),
 | 
				
			||||||
 | 
					        durationInSeconds,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }}
 | 
					    }}
 | 
				
			||||||
  />
 | 
					  ></video>
 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
 | 
				
			|||||||
@ -102,7 +102,7 @@
 | 
				
			|||||||
              {renderedOption.title}
 | 
					              {renderedOption.title}
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
          {:else}
 | 
					          {:else}
 | 
				
			||||||
            <div />
 | 
					            <div></div>
 | 
				
			||||||
            <p class="justify-self-start">
 | 
					            <p class="justify-self-start">
 | 
				
			||||||
              {renderedOption.title}
 | 
					              {renderedOption.title}
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
 | 
				
			|||||||
@ -28,11 +28,11 @@
 | 
				
			|||||||
  {#if disabled}
 | 
					  {#if disabled}
 | 
				
			||||||
    <span
 | 
					    <span
 | 
				
			||||||
      class="slider slider-disabled cursor-not-allowed border border-transparent before:border before:border-transparent"
 | 
					      class="slider slider-disabled cursor-not-allowed border border-transparent before:border before:border-transparent"
 | 
				
			||||||
    />
 | 
					    ></span>
 | 
				
			||||||
  {:else}
 | 
					  {:else}
 | 
				
			||||||
    <span
 | 
					    <span
 | 
				
			||||||
      class="slider slider-enabled cursor-pointer border-2 border-transparent before:border-2 before:border-transparent peer-focus-visible:outline before:peer-focus-visible:outline peer-focus-visible:dark:outline-gray-200 before:peer-focus-visible:dark:outline-gray-200 peer-focus-visible:outline-gray-600 before:peer-focus-visible:outline-gray-600 peer-focus-visible:dark:border-black before:peer-focus-visible:dark:border-black peer-focus-visible:border-white before:peer-focus-visible:border-white"
 | 
					      class="slider slider-enabled cursor-pointer border-2 border-transparent before:border-2 before:border-transparent peer-focus-visible:outline before:peer-focus-visible:outline peer-focus-visible:dark:outline-gray-200 before:peer-focus-visible:dark:outline-gray-200 peer-focus-visible:outline-gray-600 before:peer-focus-visible:outline-gray-600 peer-focus-visible:dark:border-black before:peer-focus-visible:dark:border-black peer-focus-visible:border-white before:peer-focus-visible:border-white"
 | 
				
			||||||
    />
 | 
					    ></span>
 | 
				
			||||||
  {/if}
 | 
					  {/if}
 | 
				
			||||||
</label>
 | 
					</label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -36,14 +36,14 @@
 | 
				
			|||||||
    class:hover:opacity-100={selectable}
 | 
					    class:hover:opacity-100={selectable}
 | 
				
			||||||
    class:rounded-full={circle}
 | 
					    class:rounded-full={circle}
 | 
				
			||||||
    class:rounded-lg={!circle}
 | 
					    class:rounded-lg={!circle}
 | 
				
			||||||
  />
 | 
					  ></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  {#if selected}
 | 
					  {#if selected}
 | 
				
			||||||
    <div
 | 
					    <div
 | 
				
			||||||
      class="absolute left-0 top-0 h-full w-full bg-blue-500/80"
 | 
					      class="absolute left-0 top-0 h-full w-full bg-blue-500/80"
 | 
				
			||||||
      class:rounded-full={circle}
 | 
					      class:rounded-full={circle}
 | 
				
			||||||
      class:rounded-lg={!circle}
 | 
					      class:rounded-lg={!circle}
 | 
				
			||||||
    />
 | 
					    ></div>
 | 
				
			||||||
  {/if}
 | 
					  {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  {#if person.name}
 | 
					  {#if person.name}
 | 
				
			||||||
 | 
				
			|||||||
@ -44,6 +44,8 @@
 | 
				
			|||||||
    return personIsHidden;
 | 
					    return personIsHidden;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_module_script_dependency
 | 
				
			||||||
  $: toggleButtonOptions = ((): Record<ToggleVisibility, { icon: string; label: string }> => {
 | 
					  $: toggleButtonOptions = ((): Record<ToggleVisibility, { icon: string; label: string }> => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      [ToggleVisibility.HIDE_ALL]: { icon: mdiEyeOff, label: $t('hide_all_people') },
 | 
					      [ToggleVisibility.HIDE_ALL]: { icon: mdiEyeOff, label: $t('hide_all_people') },
 | 
				
			||||||
 | 
				
			|||||||
@ -102,7 +102,7 @@
 | 
				
			|||||||
      {:else}
 | 
					      {:else}
 | 
				
			||||||
        {$t('merge_people')}
 | 
					        {$t('merge_people')}
 | 
				
			||||||
      {/if}
 | 
					      {/if}
 | 
				
			||||||
      <div />
 | 
					      <div></div>
 | 
				
			||||||
    </svelte:fragment>
 | 
					    </svelte:fragment>
 | 
				
			||||||
    <svelte:fragment slot="trailing">
 | 
					    <svelte:fragment slot="trailing">
 | 
				
			||||||
      <Button size={'sm'} disabled={!hasSelection} on:click={handleMerge}>
 | 
					      <Button size={'sm'} disabled={!hasSelection} on:click={handleMerge}>
 | 
				
			||||||
 | 
				
			|||||||
@ -119,7 +119,7 @@
 | 
				
			|||||||
  <ControlAppBar {onClose}>
 | 
					  <ControlAppBar {onClose}>
 | 
				
			||||||
    <svelte:fragment slot="leading">
 | 
					    <svelte:fragment slot="leading">
 | 
				
			||||||
      <slot name="header" />
 | 
					      <slot name="header" />
 | 
				
			||||||
      <div />
 | 
					      <div></div>
 | 
				
			||||||
    </svelte:fragment>
 | 
					    </svelte:fragment>
 | 
				
			||||||
    <svelte:fragment slot="trailing">
 | 
					    <svelte:fragment slot="trailing">
 | 
				
			||||||
      <div class="flex gap-4">
 | 
					      <div class="flex gap-4">
 | 
				
			||||||
 | 
				
			|||||||
@ -18,7 +18,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  <div class="my-4 flex flex-col gap-2">
 | 
					  <div class="my-4 flex flex-col gap-2">
 | 
				
			||||||
    <!-- <label class="immich-form-label" for="secret">{ $t("api_key") }</label> -->
 | 
					    <!-- <label class="immich-form-label" for="secret">{ $t("api_key") }</label> -->
 | 
				
			||||||
    <textarea class="immich-form-input" id="secret" name="secret" readonly={true} value={secret} />
 | 
					    <textarea class="immich-form-input" id="secret" name="secret" readonly={true} value={secret}></textarea>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <svelte:fragment slot="sticky-bottom">
 | 
					  <svelte:fragment slot="sticky-bottom">
 | 
				
			||||||
 | 
				
			|||||||
@ -52,7 +52,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        <div class="m-4 flex flex-col gap-2">
 | 
					        <div class="m-4 flex flex-col gap-2">
 | 
				
			||||||
          <label class="immich-form-label" for="description">{$t('description')}</label>
 | 
					          <label class="immich-form-label" for="description">{$t('description')}</label>
 | 
				
			||||||
          <textarea class="immich-form-input" id="description" bind:value={description} />
 | 
					          <textarea class="immich-form-input" id="description" bind:value={description}></textarea>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,8 @@ import { render, screen } from '@testing-library/svelte';
 | 
				
			|||||||
import { init, locale, register, waitLocale, type Translations } from 'svelte-i18n';
 | 
					import { init, locale, register, waitLocale, type Translations } from 'svelte-i18n';
 | 
				
			||||||
import { describe } from 'vitest';
 | 
					import { describe } from 'vitest';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getSanitizedHTML = (container: HTMLElement) => container.innerHTML.replaceAll('<!---->', '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('FormatMessage component', () => {
 | 
					describe('FormatMessage component', () => {
 | 
				
			||||||
  beforeAll(async () => {
 | 
					  beforeAll(async () => {
 | 
				
			||||||
    register('en', () =>
 | 
					    register('en', () =>
 | 
				
			||||||
@ -57,7 +59,7 @@ describe('FormatMessage component', () => {
 | 
				
			|||||||
      key: 'html' as Translations,
 | 
					      key: 'html' as Translations,
 | 
				
			||||||
      values: { name: 'test' },
 | 
					      values: { name: 'test' },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    expect(container.innerHTML).toBe('Hello <strong>test</strong>');
 | 
					    expect(getSanitizedHTML(container)).toBe('Hello <strong>test</strong>');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('renders a message with html and plural', () => {
 | 
					  it('renders a message with html and plural', () => {
 | 
				
			||||||
@ -65,7 +67,7 @@ describe('FormatMessage component', () => {
 | 
				
			|||||||
      key: 'plural' as Translations,
 | 
					      key: 'plural' as Translations,
 | 
				
			||||||
      values: { count: 1 },
 | 
					      values: { count: 1 },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    expect(container.innerHTML).toBe('You have <strong>1 item</strong>');
 | 
					    expect(getSanitizedHTML(container)).toBe('You have <strong>1 item</strong>');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('protects agains XSS injection', () => {
 | 
					  it('protects agains XSS injection', () => {
 | 
				
			||||||
@ -85,7 +87,7 @@ describe('FormatMessage component', () => {
 | 
				
			|||||||
      key: 'plural_with_html' as Translations,
 | 
					      key: 'plural_with_html' as Translations,
 | 
				
			||||||
      values: { count: 10 },
 | 
					      values: { count: 10 },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    expect(container.innerHTML).toBe('You have <strong>10</strong> items');
 | 
					    expect(getSanitizedHTML(container)).toBe('You have <strong>10</strong> items');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('supports html tags inside select', () => {
 | 
					  it('supports html tags inside select', () => {
 | 
				
			||||||
@ -93,7 +95,7 @@ describe('FormatMessage component', () => {
 | 
				
			|||||||
      key: 'select_with_html' as Translations,
 | 
					      key: 'select_with_html' as Translations,
 | 
				
			||||||
      values: { status: true },
 | 
					      values: { status: true },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    expect(container.innerHTML).toBe('Item is <strong>disabled</strong>');
 | 
					    expect(getSanitizedHTML(container)).toBe('Item is <strong>disabled</strong>');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('supports html tags inside selectordinal', () => {
 | 
					  it('supports html tags inside selectordinal', () => {
 | 
				
			||||||
@ -101,6 +103,6 @@ describe('FormatMessage component', () => {
 | 
				
			|||||||
      key: 'ordinal_with_html' as Translations,
 | 
					      key: 'ordinal_with_html' as Translations,
 | 
				
			||||||
      values: { count: 4 },
 | 
					      values: { count: 4 },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    expect(container.innerHTML).toBe('<strong>4th</strong> item');
 | 
					    expect(getSanitizedHTML(container)).toBe('<strong>4th</strong> item');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -267,14 +267,15 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        {#each current.memory.assets as asset, index}
 | 
					        {#each current.memory.assets as asset, index}
 | 
				
			||||||
          <a class="relative w-full py-2" href={asHref(asset)}>
 | 
					          <a class="relative w-full py-2" href={asHref(asset)}>
 | 
				
			||||||
            <span class="absolute left-0 h-[2px] w-full bg-gray-500" />
 | 
					            <span class="absolute left-0 h-[2px] w-full bg-gray-500"></span>
 | 
				
			||||||
            {#await resetPromise}
 | 
					            {#await resetPromise}
 | 
				
			||||||
              <span class="absolute left-0 h-[2px] bg-white" style:width={`${index < current.assetIndex ? 100 : 0}%`} />
 | 
					              <span class="absolute left-0 h-[2px] bg-white" style:width={`${index < current.assetIndex ? 100 : 0}%`}
 | 
				
			||||||
 | 
					              ></span>
 | 
				
			||||||
            {:then}
 | 
					            {:then}
 | 
				
			||||||
              <span
 | 
					              <span
 | 
				
			||||||
                class="absolute left-0 h-[2px] bg-white"
 | 
					                class="absolute left-0 h-[2px] bg-white"
 | 
				
			||||||
                style:width={`${index < current.assetIndex ? 100 : index > current.assetIndex ? 0 : $progress * 100}%`}
 | 
					                style:width={`${index < current.assetIndex ? 100 : index > current.assetIndex ? 0 : $progress * 100}%`}
 | 
				
			||||||
              />
 | 
					              ></span>
 | 
				
			||||||
            {/await}
 | 
					            {/await}
 | 
				
			||||||
          </a>
 | 
					          </a>
 | 
				
			||||||
        {/each}
 | 
					        {/each}
 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const { clearSelect, getOwnedAssets } = getAssetControlContext();
 | 
					  const { clearSelect, getOwnedAssets } = getAssetControlContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: isAllVideos = [...getOwnedAssets()].every((asset) => asset.type === AssetTypeEnum.Video);
 | 
					  $: isAllVideos = [...getOwnedAssets()].every((asset) => asset.type === AssetTypeEnum.Video);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleRunJob = async (name: AssetJobName) => {
 | 
					  const handleRunJob = async (name: AssetJobName) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -766,7 +766,7 @@
 | 
				
			|||||||
{#if showShortcuts}
 | 
					{#if showShortcuts}
 | 
				
			||||||
  <ShowShortcuts onClose={() => (showShortcuts = !showShortcuts)} />
 | 
					  <ShowShortcuts onClose={() => (showShortcuts = !showShortcuts)} />
 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
{#if assetStore.buckets.length > 0}
 | 
					{#if $assetStore.buckets.length > 0}
 | 
				
			||||||
  <Scrubber
 | 
					  <Scrubber
 | 
				
			||||||
    invisible={showSkeleton}
 | 
					    invisible={showSkeleton}
 | 
				
			||||||
    {assetStore}
 | 
					    {assetStore}
 | 
				
			||||||
 | 
				
			|||||||
@ -86,7 +86,7 @@
 | 
				
			|||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
            <div
 | 
					            <div
 | 
				
			||||||
              class="absolute left-0 top-0 z-0 h-full w-full rounded-xl bg-gradient-to-t from-black/40 via-transparent to-transparent transition-all hover:bg-black/20"
 | 
					              class="absolute left-0 top-0 z-0 h-full w-full rounded-xl bg-gradient-to-t from-black/40 via-transparent to-transparent transition-all hover:bg-black/20"
 | 
				
			||||||
            />
 | 
					            ></div>
 | 
				
			||||||
          </a>
 | 
					          </a>
 | 
				
			||||||
        {/if}
 | 
					        {/if}
 | 
				
			||||||
      {/each}
 | 
					      {/each}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import NumberRangeInput from '$lib/components/shared-components/number-range-input.svelte';
 | 
					import NumberRangeInput from '$lib/components/shared-components/number-range-input.svelte';
 | 
				
			||||||
import { act, render, type RenderResult } from '@testing-library/svelte';
 | 
					import { render, type RenderResult } from '@testing-library/svelte';
 | 
				
			||||||
import userEvent from '@testing-library/user-event';
 | 
					import userEvent from '@testing-library/user-event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('NumberRangeInput component', () => {
 | 
					describe('NumberRangeInput component', () => {
 | 
				
			||||||
@ -8,13 +8,18 @@ describe('NumberRangeInput component', () => {
 | 
				
			|||||||
  let input: HTMLInputElement;
 | 
					  let input: HTMLInputElement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    sut = render(NumberRangeInput, { id: '', min: -90, max: 90, onInput: () => {} });
 | 
					    sut = render(NumberRangeInput, {
 | 
				
			||||||
 | 
					      id: '',
 | 
				
			||||||
 | 
					      min: -90,
 | 
				
			||||||
 | 
					      max: 90,
 | 
				
			||||||
 | 
					      onInput: () => {},
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    input = sut.getByRole('spinbutton') as HTMLInputElement;
 | 
					    input = sut.getByRole('spinbutton') as HTMLInputElement;
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('updates value', async () => {
 | 
					  it('updates value', async () => {
 | 
				
			||||||
    expect(input.value).toBe('');
 | 
					    expect(input.value).toBe('');
 | 
				
			||||||
    await act(() => sut.component.$set({ value: 10 }));
 | 
					    await sut.rerender({ value: 10 });
 | 
				
			||||||
    expect(input.value).toBe('10');
 | 
					    expect(input.value).toBe('10');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -51,12 +51,12 @@
 | 
				
			|||||||
    {#if loading}
 | 
					    {#if loading}
 | 
				
			||||||
      {#each { length: 3 } as _}
 | 
					      {#each { length: 3 } as _}
 | 
				
			||||||
        <div class="flex animate-pulse gap-4 px-6 py-2">
 | 
					        <div class="flex animate-pulse gap-4 px-6 py-2">
 | 
				
			||||||
          <div class="h-12 w-12 rounded-xl bg-slate-200" />
 | 
					          <div class="h-12 w-12 rounded-xl bg-slate-200"></div>
 | 
				
			||||||
          <div class="flex flex-col items-start justify-center gap-2">
 | 
					          <div class="flex flex-col items-start justify-center gap-2">
 | 
				
			||||||
            <span class="h-4 w-36 animate-pulse bg-slate-200" />
 | 
					            <span class="h-4 w-36 animate-pulse bg-slate-200"></span>
 | 
				
			||||||
            <div class="flex animate-pulse gap-1">
 | 
					            <div class="flex animate-pulse gap-1">
 | 
				
			||||||
              <span class="h-3 w-8 bg-slate-200" />
 | 
					              <span class="h-3 w-8 bg-slate-200"></span>
 | 
				
			||||||
              <span class="h-3 w-20 bg-slate-200" />
 | 
					              <span class="h-3 w-20 bg-slate-200"></span>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -52,6 +52,7 @@
 | 
				
			|||||||
    })),
 | 
					    })),
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: shareType = albumId ? SharedLinkType.Album : SharedLinkType.Individual;
 | 
					  $: shareType = albumId ? SharedLinkType.Album : SharedLinkType.Individual;
 | 
				
			||||||
  $: {
 | 
					  $: {
 | 
				
			||||||
    if (!showMetadata) {
 | 
					    if (!showMetadata) {
 | 
				
			||||||
 | 
				
			|||||||
@ -57,6 +57,7 @@
 | 
				
			|||||||
  let map: maplibregl.Map;
 | 
					  let map: maplibregl.Map;
 | 
				
			||||||
  let marker: maplibregl.Marker | null = null;
 | 
					  let marker: maplibregl.Marker | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: style = (async () => {
 | 
					  $: style = (async () => {
 | 
				
			||||||
    const config = await getServerConfig();
 | 
					    const config = await getServerConfig();
 | 
				
			||||||
    const theme = $mapSettings.allowDarkMode ? $colorTheme.value : Theme.LIGHT;
 | 
					    const theme = $mapSettings.allowDarkMode ? $colorTheme.value : Theme.LIGHT;
 | 
				
			||||||
 | 
				
			|||||||
@ -27,6 +27,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
{#if showing}
 | 
					{#if showing}
 | 
				
			||||||
  <div class="absolute left-0 top-0 z-[999999999] h-[3px] w-screen bg-white">
 | 
					  <div class="absolute left-0 top-0 z-[999999999] h-[3px] w-screen bg-white">
 | 
				
			||||||
    <span class="absolute h-[3px] bg-immich-primary" style:width={`${$progress}%`} />
 | 
					    <span class="absolute h-[3px] bg-immich-primary" style:width={`${$progress}%`}></span>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
 | 
				
			|||||||
@ -79,6 +79,8 @@ describe('NotificationCard component', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(sut.getByTestId('title')).toHaveTextContent('info');
 | 
					    expect(sut.getByTestId('title')).toHaveTextContent('info');
 | 
				
			||||||
    expect(sut.getByTestId('message').innerHTML).toEqual('Notification <b>message</b> with <a href="link">link</a>');
 | 
					    expect(sut.getByTestId('message').innerHTML.replaceAll('<!---->', '')).toEqual(
 | 
				
			||||||
 | 
					      'Notification <b>message</b> with <a href="link">link</a>',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { getAnimateMock } from '$lib/__mocks__/animate.mock';
 | 
				
			||||||
import '@testing-library/jest-dom';
 | 
					import '@testing-library/jest-dom';
 | 
				
			||||||
import { render, waitFor, type RenderResult } from '@testing-library/svelte';
 | 
					import { render, waitFor, type RenderResult } from '@testing-library/svelte';
 | 
				
			||||||
import { get } from 'svelte/store';
 | 
					import { get } from 'svelte/store';
 | 
				
			||||||
@ -10,10 +11,7 @@ function _getNotificationListElement(sut: RenderResult<NotificationList>): HTMLA
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
describe('NotificationList component', () => {
 | 
					describe('NotificationList component', () => {
 | 
				
			||||||
  beforeAll(() => {
 | 
					  beforeAll(() => {
 | 
				
			||||||
    // https://testing-library.com/docs/svelte-testing-library/faq#why-arent-transition-events-running
 | 
					    Element.prototype.animate = getAnimateMock();
 | 
				
			||||||
    vi.stubGlobal('requestAnimationFrame', (fn: FrameRequestCallback) => {
 | 
					 | 
				
			||||||
      setTimeout(() => fn(Date.now()), 16);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  afterAll(() => {
 | 
					  afterAll(() => {
 | 
				
			||||||
@ -21,7 +19,7 @@ describe('NotificationList component', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('shows a notification when added and closes it automatically after the delay timeout', async () => {
 | 
					  it('shows a notification when added and closes it automatically after the delay timeout', async () => {
 | 
				
			||||||
    const sut: RenderResult<NotificationList> = render(NotificationList);
 | 
					    const sut: RenderResult<NotificationList> = render(NotificationList, { intro: false });
 | 
				
			||||||
    const status = await sut.findAllByRole('status');
 | 
					    const status = await sut.findAllByRole('status');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(status).toHaveLength(1);
 | 
					    expect(status).toHaveLength(1);
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  export let notification: Notification | ComponentNotification;
 | 
					  export let notification: Notification | ComponentNotification;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: icon = notification.type === NotificationType.Error ? mdiCloseCircleOutline : mdiInformationOutline;
 | 
					  $: icon = notification.type === NotificationType.Error ? mdiCloseCircleOutline : mdiInformationOutline;
 | 
				
			||||||
  $: hoverStyle = notification.action.type === 'discard' ? 'hover:cursor-pointer' : '';
 | 
					  $: hoverStyle = notification.action.type === 'discard' ? 'hover:cursor-pointer' : '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import type { ComponentProps, ComponentType, SvelteComponent } from 'svelte';
 | 
					import type { Component as ComponentType } from 'svelte';
 | 
				
			||||||
import { writable } from 'svelte/store';
 | 
					import { writable } from 'svelte/store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum NotificationType {
 | 
					export enum NotificationType {
 | 
				
			||||||
@ -28,27 +28,26 @@ type NoopAction = { type: 'noop' };
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export type NotificationAction = DiscardAction | NoopAction;
 | 
					export type NotificationAction = DiscardAction | NoopAction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Component<T extends ComponentType> = {
 | 
					type Props = Record<string, unknown>;
 | 
				
			||||||
  type: T;
 | 
					type Component<T extends Props> = {
 | 
				
			||||||
  props: ComponentProps<InstanceType<T>>;
 | 
					  type: ComponentType<T>;
 | 
				
			||||||
 | 
					  props: T;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type BaseNotificationOptions<T, R extends keyof T> = Partial<Omit<T, 'id'>> & Pick<T, R>;
 | 
					type BaseNotificationOptions<T, R extends keyof T> = Partial<Omit<T, 'id'>> & Pick<T, R>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type NotificationOptions = BaseNotificationOptions<Notification, 'message'>;
 | 
					export type NotificationOptions = BaseNotificationOptions<Notification, 'message'>;
 | 
				
			||||||
export type ComponentNotificationOptions<T extends ComponentType> = BaseNotificationOptions<
 | 
					export type ComponentNotificationOptions<T extends Props> = BaseNotificationOptions<
 | 
				
			||||||
  ComponentNotification<T>,
 | 
					  ComponentNotification<T>,
 | 
				
			||||||
  'component'
 | 
					  'component'
 | 
				
			||||||
>;
 | 
					>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ComponentNotification<T extends ComponentType = ComponentType<SvelteComponent>> = Omit<
 | 
					// eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
				
			||||||
  Notification,
 | 
					export type ComponentNotification<T extends Props = any> = Omit<Notification, 'message'> & {
 | 
				
			||||||
  'message'
 | 
					 | 
				
			||||||
> & {
 | 
					 | 
				
			||||||
  component: Component<T>;
 | 
					  component: Component<T>;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const isComponentNotification = <T extends ComponentType>(
 | 
					export const isComponentNotification = <T extends Props>(
 | 
				
			||||||
  notification: Notification | ComponentNotification<T>,
 | 
					  notification: Notification | ComponentNotification<T>,
 | 
				
			||||||
): notification is ComponentNotification<T> => {
 | 
					): notification is ComponentNotification<T> => {
 | 
				
			||||||
  return 'component' in notification;
 | 
					  return 'component' in notification;
 | 
				
			||||||
@ -58,7 +57,7 @@ function createNotificationList() {
 | 
				
			|||||||
  const notificationList = writable<(Notification | ComponentNotification)[]>([]);
 | 
					  const notificationList = writable<(Notification | ComponentNotification)[]>([]);
 | 
				
			||||||
  let count = 1;
 | 
					  let count = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const show = <T>(options: T extends ComponentType ? ComponentNotificationOptions<T> : NotificationOptions) => {
 | 
					  const show = <T>(options: T extends Props ? ComponentNotificationOptions<T> : NotificationOptions) => {
 | 
				
			||||||
    notificationList.update((currentList) => {
 | 
					    notificationList.update((currentList) => {
 | 
				
			||||||
      currentList.push({
 | 
					      currentList.push({
 | 
				
			||||||
        id: count++,
 | 
					        id: count++,
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  interface $$Props extends HTMLInputAttributes {
 | 
					  interface $$Props extends HTMLInputAttributes {
 | 
				
			||||||
    password: string;
 | 
					    password: string;
 | 
				
			||||||
    autocomplete: string;
 | 
					    autocomplete: AutoFill;
 | 
				
			||||||
    required?: boolean;
 | 
					    required?: boolean;
 | 
				
			||||||
    onInput?: (value: string) => void;
 | 
					    onInput?: (value: string) => void;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -86,5 +86,5 @@
 | 
				
			|||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{#if !hidden}
 | 
					{#if !hidden}
 | 
				
			||||||
  <span class="absolute left-0 h-[3px] bg-immich-primary shadow-2xl" style:width={`${$progress * 100}%`} />
 | 
					  <span class="absolute left-0 h-[3px] bg-immich-primary shadow-2xl" style:width={`${$progress * 100}%`}></span>
 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
 | 
				
			|||||||
@ -50,7 +50,7 @@
 | 
				
			|||||||
        <p>
 | 
					        <p>
 | 
				
			||||||
          {$t('purchase_panel_info_2')}
 | 
					          {$t('purchase_panel_info_2')}
 | 
				
			||||||
        </p>
 | 
					        </p>
 | 
				
			||||||
        <div />
 | 
					        <div></div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    {/if}
 | 
					    {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -257,7 +257,7 @@
 | 
				
			|||||||
  {/if}
 | 
					  {/if}
 | 
				
			||||||
  <div id="lead-in" class="relative" style:height={relativeTopOffset + 'px'} data-label={segments.at(0)?.dateFormatted}>
 | 
					  <div id="lead-in" class="relative" style:height={relativeTopOffset + 'px'} data-label={segments.at(0)?.dateFormatted}>
 | 
				
			||||||
    {#if relativeTopOffset > 6}
 | 
					    {#if relativeTopOffset > 6}
 | 
				
			||||||
      <div class="absolute right-[0.75rem] h-[4px] w-[4px] rounded-full bg-gray-300" />
 | 
					      <div class="absolute right-[0.75rem] h-[4px] w-[4px] rounded-full bg-gray-300"></div>
 | 
				
			||||||
    {/if}
 | 
					    {/if}
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <!-- Time Segment -->
 | 
					  <!-- Time Segment -->
 | 
				
			||||||
@ -282,7 +282,7 @@
 | 
				
			|||||||
        <div
 | 
					        <div
 | 
				
			||||||
          aria-label={segment.dateFormatted + ' ' + segment.count}
 | 
					          aria-label={segment.dateFormatted + ' ' + segment.count}
 | 
				
			||||||
          class="absolute right-[0.75rem] bottom-0 h-[4px] w-[4px] rounded-full bg-gray-300"
 | 
					          class="absolute right-[0.75rem] bottom-0 h-[4px] w-[4px] rounded-full bg-gray-300"
 | 
				
			||||||
        />
 | 
					        ></div>
 | 
				
			||||||
      {/if}
 | 
					      {/if}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  {/each}
 | 
					  {/each}
 | 
				
			||||||
 | 
				
			|||||||
@ -28,7 +28,7 @@
 | 
				
			|||||||
  export let disabled = false;
 | 
					  export let disabled = false;
 | 
				
			||||||
  export let isEdited = false;
 | 
					  export let isEdited = false;
 | 
				
			||||||
  export let autofocus = false;
 | 
					  export let autofocus = false;
 | 
				
			||||||
  export let passwordAutocomplete: string = 'current-password';
 | 
					  export let passwordAutocomplete: AutoFill = 'current-password';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let input: HTMLInputElement;
 | 
					  let input: HTMLInputElement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -50,5 +50,5 @@
 | 
				
			|||||||
    {value}
 | 
					    {value}
 | 
				
			||||||
    on:input={handleInput}
 | 
					    on:input={handleInput}
 | 
				
			||||||
    {disabled}
 | 
					    {disabled}
 | 
				
			||||||
  />
 | 
					  ></textarea>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
				
			|||||||
@ -38,12 +38,12 @@
 | 
				
			|||||||
>
 | 
					>
 | 
				
			||||||
  {#if $connected}
 | 
					  {#if $connected}
 | 
				
			||||||
    <div class="flex gap-2 place-items-center place-content-center">
 | 
					    <div class="flex gap-2 place-items-center place-content-center">
 | 
				
			||||||
      <div class="w-[7px] h-[7px] bg-green-500 rounded-full" />
 | 
					      <div class="w-[7px] h-[7px] bg-green-500 rounded-full"></div>
 | 
				
			||||||
      <p class="dark:text-immich-gray">{$t('server_online')}</p>
 | 
					      <p class="dark:text-immich-gray">{$t('server_online')}</p>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  {:else}
 | 
					  {:else}
 | 
				
			||||||
    <div class="flex gap-2 place-items-center place-content-center">
 | 
					    <div class="flex gap-2 place-items-center place-content-center">
 | 
				
			||||||
      <div class="w-[7px] h-[7px] bg-red-500 rounded-full" />
 | 
					      <div class="w-[7px] h-[7px] bg-red-500 rounded-full"></div>
 | 
				
			||||||
      <p class="text-red-500">{$t('server_offline')}</p>
 | 
					      <p class="text-red-500">{$t('server_offline')}</p>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  {/if}
 | 
					  {/if}
 | 
				
			||||||
 | 
				
			|||||||
@ -63,7 +63,7 @@
 | 
				
			|||||||
      </p>
 | 
					      </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div class="mt-4 h-[7px] w-full rounded-full bg-gray-200 dark:bg-gray-700">
 | 
					      <div class="mt-4 h-[7px] w-full rounded-full bg-gray-200 dark:bg-gray-700">
 | 
				
			||||||
        <div class="h-[7px] rounded-full {usageClasses}" style="width: {usedPercentage}%" />
 | 
					        <div class="h-[7px] rounded-full {usageClasses}" style="width: {usedPercentage}%"></div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    {:else}
 | 
					    {:else}
 | 
				
			||||||
      <div class="mt-2">
 | 
					      <div class="mt-2">
 | 
				
			||||||
 | 
				
			|||||||
@ -5,8 +5,11 @@
 | 
				
			|||||||
  import { colorTheme, handleToggleTheme } from '$lib/stores/preferences.store';
 | 
					  import { colorTheme, handleToggleTheme } from '$lib/stores/preferences.store';
 | 
				
			||||||
  import { t } from 'svelte-i18n';
 | 
					  import { t } from 'svelte-i18n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: icon = $colorTheme.value === Theme.LIGHT ? moonPath : sunPath;
 | 
					  $: icon = $colorTheme.value === Theme.LIGHT ? moonPath : sunPath;
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: viewBox = $colorTheme.value === Theme.LIGHT ? moonViewBox : sunViewBox;
 | 
					  $: viewBox = $colorTheme.value === Theme.LIGHT ? moonViewBox : sunViewBox;
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: isDark = $colorTheme.value === Theme.DARK;
 | 
					  $: isDark = $colorTheme.value === Theme.DARK;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let padding: Padding = '3';
 | 
					  export let padding: Padding = '3';
 | 
				
			||||||
 | 
				
			|||||||
@ -92,7 +92,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  {#if uploadAsset.state === UploadState.STARTED}
 | 
					  {#if uploadAsset.state === UploadState.STARTED}
 | 
				
			||||||
    <div class="text-black relative mt-[5px] h-[15px] w-full rounded-md bg-gray-300 dark:bg-immich-dark-gray">
 | 
					    <div class="text-black relative mt-[5px] h-[15px] w-full rounded-md bg-gray-300 dark:bg-immich-dark-gray">
 | 
				
			||||||
      <div class="h-[15px] rounded-md bg-immich-primary transition-all" style={`width: ${uploadAsset.progress}%`} />
 | 
					      <div class="h-[15px] rounded-md bg-immich-primary transition-all" style={`width: ${uploadAsset.progress}%`}></div>
 | 
				
			||||||
      <p class="absolute top-0 h-full w-full text-center text-[10px]">
 | 
					      <p class="absolute top-0 h-full w-full text-center text-[10px]">
 | 
				
			||||||
        {#if uploadAsset.message}
 | 
					        {#if uploadAsset.message}
 | 
				
			||||||
          {uploadAsset.message}
 | 
					          {uploadAsset.message}
 | 
				
			||||||
 | 
				
			|||||||
@ -33,6 +33,7 @@
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
  $: selectedDate = `${formattedDate} ${timePortion}`;
 | 
					  $: selectedDate = `${formattedDate} ${timePortion}`;
 | 
				
			||||||
  $: editedLocale = findLocale($locale).code;
 | 
					  $: editedLocale = findLocale($locale).code;
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: selectedOption = {
 | 
					  $: selectedOption = {
 | 
				
			||||||
    value: findLocale(editedLocale).code || fallbackLocale.code,
 | 
					    value: findLocale(editedLocale).code || fallbackLocale.code,
 | 
				
			||||||
    label: findLocale(editedLocale).name || fallbackLocale.name,
 | 
					    label: findLocale(editedLocale).name || fallbackLocale.name,
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@ export const handleToggleTheme = () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const initTheme = (): ThemeSetting => {
 | 
					const initTheme = (): ThemeSetting => {
 | 
				
			||||||
  if (browser && !window.matchMedia('(prefers-color-scheme: dark)').matches) {
 | 
					  if (browser && window.matchMedia && !window.matchMedia('(prefers-color-scheme: dark)').matches) {
 | 
				
			||||||
    return { value: Theme.LIGHT, system: false };
 | 
					    return { value: Theme.LIGHT, system: false };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return { value: Theme.DARK, system: false };
 | 
					  return { value: Theme.DARK, system: false };
 | 
				
			||||||
 | 
				
			|||||||
@ -142,10 +142,12 @@
 | 
				
			|||||||
  $: showActivityStatus =
 | 
					  $: showActivityStatus =
 | 
				
			||||||
    album.albumUsers.length > 0 && !$showAssetViewer && (album.isActivityEnabled || $numberOfComments > 0);
 | 
					    album.albumUsers.length > 0 && !$showAssetViewer && (album.isActivityEnabled || $numberOfComments > 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: isEditor =
 | 
					  $: isEditor =
 | 
				
			||||||
    album.albumUsers.find(({ user: { id } }) => id === $user.id)?.role === AlbumUserRole.Editor ||
 | 
					    album.albumUsers.find(({ user: { id } }) => id === $user.id)?.role === AlbumUserRole.Editor ||
 | 
				
			||||||
    album.ownerId === $user.id;
 | 
					    album.ownerId === $user.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: albumHasViewers = album.albumUsers.some(({ role }) => role === AlbumUserRole.Viewer);
 | 
					  $: albumHasViewers = album.albumUsers.some(({ role }) => role === AlbumUserRole.Viewer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  afterNavigate(({ from }) => {
 | 
					  afterNavigate(({ from }) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -40,6 +40,7 @@
 | 
				
			|||||||
  let isAssetStackSelected: boolean;
 | 
					  let isAssetStackSelected: boolean;
 | 
				
			||||||
  let isLinkActionAvailable: boolean;
 | 
					  let isLinkActionAvailable: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // svelte-ignore reactive_declaration_non_reactive_property
 | 
				
			||||||
  $: {
 | 
					  $: {
 | 
				
			||||||
    const selection = [...$selectedAssets];
 | 
					    const selection = [...$selectedAssets];
 | 
				
			||||||
    isAllOwned = selection.every((asset) => asset.ownerId === $user.id);
 | 
					    isAllOwned = selection.every((asset) => asset.ownerId === $user.id);
 | 
				
			||||||
 | 
				
			|||||||
@ -242,7 +242,7 @@
 | 
				
			|||||||
              <th class="text-center text-sm font-medium">{$t('owner')}</th>
 | 
					              <th class="text-center text-sm font-medium">{$t('owner')}</th>
 | 
				
			||||||
              <th class="text-center text-sm font-medium">{$t('assets')}</th>
 | 
					              <th class="text-center text-sm font-medium">{$t('assets')}</th>
 | 
				
			||||||
              <th class="text-center text-sm font-medium">{$t('size')}</th>
 | 
					              <th class="text-center text-sm font-medium">{$t('size')}</th>
 | 
				
			||||||
              <th class="text-center text-sm font-medium" />
 | 
					              <th class="text-center text-sm font-medium"></th>
 | 
				
			||||||
            </tr>
 | 
					            </tr>
 | 
				
			||||||
          </thead>
 | 
					          </thead>
 | 
				
			||||||
          <tbody class="block overflow-y-auto rounded-md border dark:border-immich-dark-gray">
 | 
					          <tbody class="block overflow-y-auto rounded-md border dark:border-immich-dark-gray">
 | 
				
			||||||
@ -309,16 +309,19 @@
 | 
				
			|||||||
                </td>
 | 
					                </td>
 | 
				
			||||||
              </tr>
 | 
					              </tr>
 | 
				
			||||||
              {#if renameLibrary === index}
 | 
					              {#if renameLibrary === index}
 | 
				
			||||||
 | 
					                <!-- svelte-ignore node_invalid_placement_ssr -->
 | 
				
			||||||
                <div transition:slide={{ duration: 250 }}>
 | 
					                <div transition:slide={{ duration: 250 }}>
 | 
				
			||||||
                  <LibraryRenameForm {library} onSubmit={handleUpdate} onCancel={() => (renameLibrary = null)} />
 | 
					                  <LibraryRenameForm {library} onSubmit={handleUpdate} onCancel={() => (renameLibrary = null)} />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              {/if}
 | 
					              {/if}
 | 
				
			||||||
              {#if editImportPaths === index}
 | 
					              {#if editImportPaths === index}
 | 
				
			||||||
 | 
					                <!-- svelte-ignore node_invalid_placement_ssr -->
 | 
				
			||||||
                <div transition:slide={{ duration: 250 }}>
 | 
					                <div transition:slide={{ duration: 250 }}>
 | 
				
			||||||
                  <LibraryImportPathsForm {library} onSubmit={handleUpdate} onCancel={() => (editImportPaths = null)} />
 | 
					                  <LibraryImportPathsForm {library} onSubmit={handleUpdate} onCancel={() => (editImportPaths = null)} />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              {/if}
 | 
					              {/if}
 | 
				
			||||||
              {#if editScanSettings === index}
 | 
					              {#if editScanSettings === index}
 | 
				
			||||||
 | 
					                <!-- svelte-ignore node_invalid_placement_ssr -->
 | 
				
			||||||
                <div transition:slide={{ duration: 250 }} class="mb-4 ml-4 mr-4">
 | 
					                <div transition:slide={{ duration: 250 }} class="mb-4 ml-4 mr-4">
 | 
				
			||||||
                  <LibraryScanSettingsForm
 | 
					                  <LibraryScanSettingsForm
 | 
				
			||||||
                    {library}
 | 
					                    {library}
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,8 @@ process.env.PUBLIC_IMMICH_PAY_HOST = process.env.PUBLIC_IMMICH_PAY_HOST || 'http
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/** @type {import('@sveltejs/kit').Config} */
 | 
					/** @type {import('@sveltejs/kit').Config} */
 | 
				
			||||||
const config = {
 | 
					const config = {
 | 
				
			||||||
  preprocess: vitePreprocess(),
 | 
					  // TODO: migrate all enums to .ts files and remove `{script: true}` once
 | 
				
			||||||
 | 
					  preprocess: vitePreprocess({ script: true }),
 | 
				
			||||||
  kit: {
 | 
					  kit: {
 | 
				
			||||||
    adapter: adapter({
 | 
					    adapter: adapter({
 | 
				
			||||||
      fallback: 'index.html',
 | 
					      fallback: 'index.html',
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import { enhancedImages } from '@sveltejs/enhanced-img';
 | 
					import { enhancedImages } from '@sveltejs/enhanced-img';
 | 
				
			||||||
import { sveltekit } from '@sveltejs/kit/vite';
 | 
					import { sveltekit } from '@sveltejs/kit/vite';
 | 
				
			||||||
 | 
					import { svelteTesting } from '@testing-library/svelte/vite';
 | 
				
			||||||
import path from 'node:path';
 | 
					import path from 'node:path';
 | 
				
			||||||
import { visualizer } from 'rollup-plugin-visualizer';
 | 
					import { visualizer } from 'rollup-plugin-visualizer';
 | 
				
			||||||
import { defineConfig } from 'vite';
 | 
					import { defineConfig } from 'vite';
 | 
				
			||||||
@ -37,6 +38,7 @@ export default defineConfig({
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
      : undefined,
 | 
					      : undefined,
 | 
				
			||||||
    enhancedImages(),
 | 
					    enhancedImages(),
 | 
				
			||||||
 | 
					    svelteTesting(),
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  optimizeDeps: {
 | 
					  optimizeDeps: {
 | 
				
			||||||
    entries: ['src/**/*.{svelte,ts,html}'],
 | 
					    entries: ['src/**/*.{svelte,ts,html}'],
 | 
				
			||||||
@ -49,6 +51,5 @@ export default defineConfig({
 | 
				
			|||||||
    sequence: {
 | 
					    sequence: {
 | 
				
			||||||
      hooks: 'list',
 | 
					      hooks: 'list',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    alias: [{ find: /^svelte$/, replacement: 'svelte/internal' }],
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user