mirror of
https://github.com/immich-app/immich.git
synced 2025-12-12 15:50:43 -08:00
chore: refactor svelte reactivity (#24072)
This commit is contained in:
@@ -62,50 +62,60 @@ export const setupTimelineMockApiRoutes = async (
|
|||||||
return route.continue();
|
return route.continue();
|
||||||
});
|
});
|
||||||
|
|
||||||
await context.route('**/api/assets/**', async (route, request) => {
|
await context.route('**/api/assets/*', async (route, request) => {
|
||||||
|
const url = new URL(request.url());
|
||||||
|
const pathname = url.pathname;
|
||||||
|
const assetId = basename(pathname);
|
||||||
|
const asset = getAsset(timelineRestData, assetId);
|
||||||
|
return route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
json: asset,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await context.route('**/api/assets/*/ocr', async (route) => {
|
||||||
|
return route.fulfill({ status: 200, contentType: 'application/json', json: [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
await context.route('**/api/assets/*/thumbnail?size=*', async (route, request) => {
|
||||||
const pattern = /\/api\/assets\/(?<assetId>[^/]+)\/thumbnail\?size=(?<size>preview|thumbnail)/;
|
const pattern = /\/api\/assets\/(?<assetId>[^/]+)\/thumbnail\?size=(?<size>preview|thumbnail)/;
|
||||||
const match = request.url().match(pattern);
|
const match = request.url().match(pattern);
|
||||||
if (!match) {
|
if (!match?.groups) {
|
||||||
const url = new URL(request.url());
|
throw new Error(`Invalid URL for thumbnail endpoint: ${request.url()}`);
|
||||||
const pathname = url.pathname;
|
|
||||||
const assetId = basename(pathname);
|
|
||||||
const asset = getAsset(timelineRestData, assetId);
|
|
||||||
return route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
contentType: 'application/json',
|
|
||||||
json: asset,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (match.groups?.size === 'preview') {
|
|
||||||
|
if (match.groups.size === 'preview') {
|
||||||
if (!route.request().serviceWorker()) {
|
if (!route.request().serviceWorker()) {
|
||||||
return route.continue();
|
return route.continue();
|
||||||
}
|
}
|
||||||
const asset = getAsset(timelineRestData, match.groups?.assetId);
|
const asset = getAsset(timelineRestData, match.groups.assetId);
|
||||||
return route.fulfill({
|
return route.fulfill({
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { 'content-type': 'image/jpeg', ETag: 'abc123', 'Cache-Control': 'public, max-age=3600' },
|
headers: { 'content-type': 'image/jpeg', ETag: 'abc123', 'Cache-Control': 'public, max-age=3600' },
|
||||||
body: await randomPreview(
|
body: await randomPreview(
|
||||||
match.groups?.assetId,
|
match.groups.assetId,
|
||||||
(asset?.exifInfo?.exifImageWidth ?? 0) / (asset?.exifInfo?.exifImageHeight ?? 1),
|
(asset?.exifInfo?.exifImageWidth ?? 0) / (asset?.exifInfo?.exifImageHeight ?? 1),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (match.groups?.size === 'thumbnail') {
|
if (match.groups.size === 'thumbnail') {
|
||||||
if (!route.request().serviceWorker()) {
|
if (!route.request().serviceWorker()) {
|
||||||
return route.continue();
|
return route.continue();
|
||||||
}
|
}
|
||||||
const asset = getAsset(timelineRestData, match.groups?.assetId);
|
const asset = getAsset(timelineRestData, match.groups.assetId);
|
||||||
return route.fulfill({
|
return route.fulfill({
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { 'content-type': 'image/jpeg' },
|
headers: { 'content-type': 'image/jpeg' },
|
||||||
body: await randomThumbnail(
|
body: await randomThumbnail(
|
||||||
match.groups?.assetId,
|
match.groups.assetId,
|
||||||
(asset?.exifInfo?.exifImageWidth ?? 0) / (asset?.exifInfo?.exifImageHeight ?? 1),
|
(asset?.exifInfo?.exifImageWidth ?? 0) / (asset?.exifInfo?.exifImageHeight ?? 1),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return route.continue();
|
return route.continue();
|
||||||
});
|
});
|
||||||
|
|
||||||
await context.route('**/api/albums/**', async (route, request) => {
|
await context.route('**/api/albums/**', async (route, request) => {
|
||||||
const pattern = /\/api\/albums\/(?<albumId>[^/?]+)/;
|
const pattern = /\/api\/albums\/(?<albumId>[^/?]+)/;
|
||||||
const match = request.url().match(pattern);
|
const match = request.url().match(pattern);
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
let innerHeight: number = $state(0);
|
let innerHeight: number = $state(0);
|
||||||
let activityHeight: number = $state(0);
|
let activityHeight: number = $state(0);
|
||||||
let chatHeight: number = $state(0);
|
let chatHeight: number = $state(0);
|
||||||
let divHeight: number = $state(0);
|
let divHeight = $derived(innerHeight - activityHeight);
|
||||||
let previousAssetId: string | undefined = $state(assetId);
|
let previousAssetId: string | undefined = $state(assetId);
|
||||||
let message = $state('');
|
let message = $state('');
|
||||||
let isSendingMessage = $state(false);
|
let isSendingMessage = $state(false);
|
||||||
@@ -96,11 +96,7 @@
|
|||||||
}
|
}
|
||||||
isSendingMessage = false;
|
isSendingMessage = false;
|
||||||
};
|
};
|
||||||
$effect(() => {
|
|
||||||
if (innerHeight && activityHeight) {
|
|
||||||
divHeight = innerHeight - activityHeight;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (assetId && previousAssetId != assetId) {
|
if (assetId && previousAssetId != assetId) {
|
||||||
previousAssetId = assetId;
|
previousAssetId = assetId;
|
||||||
|
|||||||
@@ -35,15 +35,13 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let albumNameArray: string[] = $state(['', '', '']);
|
|
||||||
|
|
||||||
// This part of the code is responsible for splitting album name into 3 parts where part 2 is the search query
|
// This part of the code is responsible for splitting album name into 3 parts where part 2 is the search query
|
||||||
// It is used to highlight the search query in the album name
|
// It is used to highlight the search query in the album name
|
||||||
$effect(() => {
|
const albumNameArray: string[] = $derived.by(() => {
|
||||||
let { albumName } = album;
|
let { albumName } = album;
|
||||||
let findIndex = normalizeSearchString(albumName).indexOf(normalizeSearchString(searchQuery));
|
let findIndex = normalizeSearchString(albumName).indexOf(normalizeSearchString(searchQuery));
|
||||||
let findLength = searchQuery.length;
|
let findLength = searchQuery.length;
|
||||||
albumNameArray = [
|
return [
|
||||||
albumName.slice(0, findIndex),
|
albumName.slice(0, findIndex),
|
||||||
albumName.slice(findIndex, findIndex + findLength),
|
albumName.slice(findIndex, findIndex + findLength),
|
||||||
albumName.slice(findIndex + findLength),
|
albumName.slice(findIndex + findLength),
|
||||||
|
|||||||
@@ -395,13 +395,11 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let currentAssetId = $derived(asset.id);
|
// primarily, this is reactive on `asset`
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (currentAssetId) {
|
handlePromiseError(handleGetAllAlbums());
|
||||||
untrack(() => handlePromiseError(handleGetAllAlbums()));
|
ocrManager.clear();
|
||||||
ocrManager.clear();
|
handlePromiseError(ocrManager.getAssetOcr(asset.id));
|
||||||
handlePromiseError(ocrManager.getAssetOcr(currentAssetId));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -171,7 +171,6 @@
|
|||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (assetFileUrl) {
|
if (assetFileUrl) {
|
||||||
// this can't be in an async context with $effect
|
|
||||||
void cast(assetFileUrl);
|
void cast(assetFileUrl);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,7 +43,9 @@
|
|||||||
|
|
||||||
let videoPlayer: HTMLVideoElement | undefined = $state();
|
let videoPlayer: HTMLVideoElement | undefined = $state();
|
||||||
let isLoading = $state(true);
|
let isLoading = $state(true);
|
||||||
let assetFileUrl = $state('');
|
let assetFileUrl = $derived(
|
||||||
|
playOriginalVideo ? getAssetOriginalUrl({ id: assetId, cacheKey }) : getAssetPlaybackUrl({ id: assetId, cacheKey }),
|
||||||
|
);
|
||||||
let isScrubbing = $state(false);
|
let isScrubbing = $state(false);
|
||||||
let showVideo = $state(false);
|
let showVideo = $state(false);
|
||||||
|
|
||||||
@@ -53,11 +55,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
assetFileUrl = playOriginalVideo
|
// reactive on `assetFileUrl` changes
|
||||||
? getAssetOriginalUrl({ id: assetId, cacheKey })
|
if (assetFileUrl) {
|
||||||
: getAssetPlaybackUrl({ id: assetId, cacheKey });
|
videoPlayer?.load();
|
||||||
if (videoPlayer) {
|
|
||||||
videoPlayer.load();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,6 @@
|
|||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (assetFileUrl) {
|
if (assetFileUrl) {
|
||||||
// this can't be in an async context with $effect
|
|
||||||
void cast(assetFileUrl);
|
void cast(assetFileUrl);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
import { type PlacesGroup, getSelectedPlacesGroupOption } from '$lib/utils/places-utils';
|
import { type PlacesGroup, getSelectedPlacesGroupOption } from '$lib/utils/places-utils';
|
||||||
import { Icon } from '@immich/ui';
|
import { Icon } from '@immich/ui';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { run } from 'svelte/legacy';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
places?: AssetResponseDto[];
|
places?: AssetResponseDto[];
|
||||||
@@ -70,39 +69,27 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let filteredPlaces: AssetResponseDto[] = $state([]);
|
const filteredPlaces = $derived.by(() => {
|
||||||
let groupedPlaces: PlacesGroup[] = $state([]);
|
const searchQueryNormalized = normalizeSearchString(searchQuery);
|
||||||
|
return searchQueryNormalized
|
||||||
|
? places.filter((place) => normalizeSearchString(place.exifInfo?.city ?? '').includes(searchQueryNormalized))
|
||||||
|
: places;
|
||||||
|
});
|
||||||
|
|
||||||
let placesGroupOption: string = $state(PlacesGroupBy.None);
|
const placesGroupOption: string = $derived(getSelectedPlacesGroupOption(userSettings));
|
||||||
|
const groupingFunction = $derived(groupOptions[placesGroupOption] ?? groupOptions[PlacesGroupBy.None]);
|
||||||
let hasPlaces = $derived(places.length > 0);
|
const groupedPlaces: PlacesGroup[] = $derived(groupingFunction(filteredPlaces));
|
||||||
|
|
||||||
// Step 1: Filter using the given search query.
|
|
||||||
run(() => {
|
|
||||||
if (searchQuery) {
|
|
||||||
const searchQueryNormalized = normalizeSearchString(searchQuery);
|
|
||||||
|
|
||||||
filteredPlaces = places.filter((place) => {
|
|
||||||
return normalizeSearchString(place.exifInfo?.city ?? '').includes(searchQueryNormalized);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
filteredPlaces = places;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
searchResultCount = filteredPlaces.length;
|
searchResultCount = filteredPlaces.length;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Step 2: Group places.
|
$effect(() => {
|
||||||
run(() => {
|
|
||||||
placesGroupOption = getSelectedPlacesGroupOption(userSettings);
|
|
||||||
const groupFunc = groupOptions[placesGroupOption] ?? groupOptions[PlacesGroupBy.None];
|
|
||||||
groupedPlaces = groupFunc(filteredPlaces);
|
|
||||||
|
|
||||||
placesGroupIds = groupedPlaces.map(({ id }) => id);
|
placesGroupIds = groupedPlaces.map(({ id }) => id);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if hasPlaces}
|
{#if places.length > 0}
|
||||||
<!-- Album Cards -->
|
<!-- Album Cards -->
|
||||||
{#if placesGroupOption === PlacesGroupBy.None}
|
{#if placesGroupOption === PlacesGroupBy.None}
|
||||||
<PlacesCardGroup places={groupedPlaces[0].places} />
|
<PlacesCardGroup places={groupedPlaces[0].places} />
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
let { asset = undefined, point: initialPoint, onClose }: Props = $props();
|
let { asset = undefined, point: initialPoint, onClose }: Props = $props();
|
||||||
|
|
||||||
let places: PlacesResponseDto[] = $state([]);
|
let places: PlacesResponseDto[] = $state([]);
|
||||||
let suggestedPlaces: PlacesResponseDto[] = $state([]);
|
let suggestedPlaces: PlacesResponseDto[] = $derived(places.slice(0, 5));
|
||||||
let searchWord: string = $state('');
|
let searchWord: string = $state('');
|
||||||
let latestSearchTimeout: number;
|
let latestSearchTimeout: number;
|
||||||
let showLoadingSpinner = $state(false);
|
let showLoadingSpinner = $state(false);
|
||||||
@@ -52,9 +52,6 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (places) {
|
|
||||||
suggestedPlaces = places.slice(0, 5);
|
|
||||||
}
|
|
||||||
if (searchWord === '') {
|
if (searchWord === '') {
|
||||||
suggestedPlaces = [];
|
suggestedPlaces = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,37 +33,36 @@
|
|||||||
children,
|
children,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
let left: number = $state(0);
|
const swap = (direction: string) => (direction === 'left' ? 'right' : 'left');
|
||||||
let top: number = $state(0);
|
|
||||||
|
const layoutDirection = $derived(languageManager.rtl ? swap(direction) : direction);
|
||||||
|
const position = $derived.by(() => {
|
||||||
|
if (!menuElement) {
|
||||||
|
return { left: 0, top: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = menuElement.getBoundingClientRect();
|
||||||
|
const directionWidth = layoutDirection === 'left' ? rect.width : 0;
|
||||||
|
const menuHeight = Math.min(menuElement.clientHeight, height) || 0;
|
||||||
|
|
||||||
|
const left = Math.max(8, Math.min(window.innerWidth - rect.width, x - directionWidth));
|
||||||
|
const top = Math.max(8, Math.min(window.innerHeight - menuHeight, y));
|
||||||
|
|
||||||
|
return { left, top };
|
||||||
|
});
|
||||||
|
|
||||||
// We need to bind clientHeight since the bounding box may return a height
|
// We need to bind clientHeight since the bounding box may return a height
|
||||||
// of zero when starting the 'slide' animation.
|
// of zero when starting the 'slide' animation.
|
||||||
let height: number = $state(0);
|
let height: number = $state(0);
|
||||||
|
|
||||||
let isTransitioned = $state(false);
|
let isTransitioned = $state(false);
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (menuElement) {
|
|
||||||
let layoutDirection = direction;
|
|
||||||
if (languageManager.rtl) {
|
|
||||||
layoutDirection = direction === 'left' ? 'right' : 'left';
|
|
||||||
}
|
|
||||||
|
|
||||||
const rect = menuElement.getBoundingClientRect();
|
|
||||||
const directionWidth = layoutDirection === 'left' ? rect.width : 0;
|
|
||||||
const menuHeight = Math.min(menuElement.clientHeight, height) || 0;
|
|
||||||
|
|
||||||
left = Math.max(8, Math.min(window.innerWidth - rect.width, x - directionWidth));
|
|
||||||
top = Math.max(8, Math.min(window.innerHeight - menuHeight, y));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:clientHeight={height}
|
bind:clientHeight={height}
|
||||||
class="fixed min-w-50 w-max max-w-75 overflow-hidden rounded-lg shadow-lg z-1"
|
class="fixed min-w-50 w-max max-w-75 overflow-hidden rounded-lg shadow-lg z-1"
|
||||||
style:left="{left}px"
|
style:left="{position.left}px"
|
||||||
style:top="{top}px"
|
style:top="{position.top}px"
|
||||||
transition:slide={{ duration: 250, easing: quintOut }}
|
transition:slide={{ duration: 250, easing: quintOut }}
|
||||||
use:clickOutside={{ onOutclick: onClose }}
|
use:clickOutside={{ onOutclick: onClose }}
|
||||||
onintroend={() => {
|
onintroend={() => {
|
||||||
|
|||||||
@@ -64,10 +64,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let makeFilter = $derived(filters.make);
|
const makeFilter = $derived(filters.make);
|
||||||
let modelFilter = $derived(filters.model);
|
const modelFilter = $derived(filters.model);
|
||||||
let lensModelFilter = $derived(filters.lensModel);
|
const lensModelFilter = $derived(filters.lensModel);
|
||||||
|
|
||||||
|
// TODO replace by async $derived, at the latest when it's in stable https://svelte.dev/docs/svelte/await-expressions
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
handlePromiseError(updateMakes());
|
handlePromiseError(updateMakes());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,11 +7,10 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { run } from 'svelte/legacy';
|
|
||||||
|
|
||||||
import Combobox, { asComboboxOptions, asSelectedOption } from '$lib/components/shared-components/combobox.svelte';
|
import Combobox, { asComboboxOptions, asSelectedOption } from '$lib/components/shared-components/combobox.svelte';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
import { getSearchSuggestions, SearchSuggestionType } from '@immich/sdk';
|
import { getSearchSuggestions, SearchSuggestionType } from '@immich/sdk';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -66,15 +65,12 @@
|
|||||||
}
|
}
|
||||||
let countryFilter = $derived(filters.country);
|
let countryFilter = $derived(filters.country);
|
||||||
let stateFilter = $derived(filters.state);
|
let stateFilter = $derived(filters.state);
|
||||||
run(() => {
|
|
||||||
handlePromiseError(updateCountries());
|
// TODO replace by async $derived, at the latest when it's in stable https://svelte.dev/docs/svelte/await-expressions
|
||||||
});
|
$effect(() => handlePromiseError(updateStates(countryFilter)));
|
||||||
run(() => {
|
$effect(() => handlePromiseError(updateCities(countryFilter, stateFilter)));
|
||||||
handlePromiseError(updateStates(countryFilter));
|
|
||||||
});
|
onMount(() => updateCountries());
|
||||||
run(() => {
|
|
||||||
handlePromiseError(updateCities(countryFilter, stateFilter));
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="location-selection">
|
<div id="location-selection">
|
||||||
|
|||||||
@@ -19,21 +19,23 @@ export type OcrBoundingBox = {
|
|||||||
class OcrManager {
|
class OcrManager {
|
||||||
#data = $state<OcrBoundingBox[]>([]);
|
#data = $state<OcrBoundingBox[]>([]);
|
||||||
showOverlay = $state(false);
|
showOverlay = $state(false);
|
||||||
hasOcrData = $state(false);
|
#hasOcrData = $derived(this.#data.length > 0);
|
||||||
|
|
||||||
get data() {
|
get data() {
|
||||||
return this.#data;
|
return this.#data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hasOcrData() {
|
||||||
|
return this.#hasOcrData;
|
||||||
|
}
|
||||||
|
|
||||||
async getAssetOcr(id: string) {
|
async getAssetOcr(id: string) {
|
||||||
this.#data = await getAssetOcr({ id });
|
this.#data = await getAssetOcr({ id });
|
||||||
this.hasOcrData = this.#data.length > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.#data = [];
|
this.#data = [];
|
||||||
this.showOverlay = false;
|
this.showOverlay = false;
|
||||||
this.hasOcrData = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleOcrBoundingBox() {
|
toggleOcrBoundingBox() {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { run } from 'svelte/legacy';
|
import { page } from '$app/state';
|
||||||
|
|
||||||
import UploadCover from '$lib/components/shared-components/drag-and-drop-upload-overlay.svelte';
|
import UploadCover from '$lib/components/shared-components/drag-and-drop-upload-overlay.svelte';
|
||||||
import { page } from '$app/stores';
|
|
||||||
|
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
@@ -15,13 +13,13 @@
|
|||||||
|
|
||||||
// $page.data.asset is loaded by route specific +page.ts loaders if that
|
// $page.data.asset is loaded by route specific +page.ts loaders if that
|
||||||
// route contains the assetId path.
|
// route contains the assetId path.
|
||||||
run(() => {
|
$effect.pre(() => {
|
||||||
if ($page.data.asset) {
|
if (page.data.asset) {
|
||||||
setAsset($page.data.asset);
|
setAsset(page.data.asset);
|
||||||
} else {
|
} else {
|
||||||
$showAssetViewer = false;
|
$showAssetViewer = false;
|
||||||
}
|
}
|
||||||
const asset = $page.url.searchParams.get('at');
|
const asset = page.url.searchParams.get('at');
|
||||||
$gridScrollTarget = { at: asset };
|
$gridScrollTarget = { at: asset };
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { Icon, IconButton, LoadingSpinner } from '@immich/ui';
|
import { Icon, IconButton, LoadingSpinner } from '@immich/ui';
|
||||||
import { mdiArrowLeft, mdiDotsVertical, mdiImageOffOutline, mdiPlus, mdiSelectAll } from '@mdi/js';
|
import { mdiArrowLeft, mdiDotsVertical, mdiImageOffOutline, mdiPlus, mdiSelectAll } from '@mdi/js';
|
||||||
import { tick } from 'svelte';
|
import { tick, untrack } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
let { isViewing: showAssetViewer } = assetViewingStore;
|
let { isViewing: showAssetViewer } = assetViewingStore;
|
||||||
@@ -71,11 +71,10 @@
|
|||||||
let terms = $derived(searchQuery ? JSON.parse(searchQuery) : {});
|
let terms = $derived(searchQuery ? JSON.parse(searchQuery) : {});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
// we want this to *only* be reactive on `terms`
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
terms;
|
terms;
|
||||||
setTimeout(() => {
|
untrack(() => handlePromiseError(onSearchQueryUpdate()));
|
||||||
handlePromiseError(onSearchQueryUpdate());
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const onEscape = () => {
|
const onEscape = () => {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
import { modalManager, setTranslations } from '@immich/ui';
|
import { modalManager, setTranslations } from '@immich/ui';
|
||||||
import { onMount, type Snippet } from 'svelte';
|
import { onMount, type Snippet } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { run } from 'svelte/legacy';
|
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -69,7 +68,8 @@
|
|||||||
afterNavigate(() => {
|
afterNavigate(() => {
|
||||||
showNavigationLoadingBar = false;
|
showNavigationLoadingBar = false;
|
||||||
});
|
});
|
||||||
run(() => {
|
|
||||||
|
$effect.pre(() => {
|
||||||
if ($user || page.url.pathname.startsWith(AppRoute.MAINTENANCE)) {
|
if ($user || page.url.pathname.startsWith(AppRoute.MAINTENANCE)) {
|
||||||
openWebsocketConnection();
|
openWebsocketConnection();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import OnboardingBackup from '$lib/components/onboarding-page/onboarding-backup.svelte';
|
import OnboardingBackup from '$lib/components/onboarding-page/onboarding-backup.svelte';
|
||||||
import OnboardingCard from '$lib/components/onboarding-page/onboarding-card.svelte';
|
import OnboardingCard from '$lib/components/onboarding-page/onboarding-card.svelte';
|
||||||
import OnboardingHello from '$lib/components/onboarding-page/onboarding-hello.svelte';
|
import OnboardingHello from '$lib/components/onboarding-page/onboarding-hello.svelte';
|
||||||
@@ -95,7 +95,11 @@
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let index = $state(0);
|
const index = $derived.by(() => {
|
||||||
|
const stepState = page.url.searchParams.get('step');
|
||||||
|
const temporaryIndex = onboardingSteps.findIndex((step) => step.name === stepState);
|
||||||
|
return temporaryIndex === -1 ? 0 : temporaryIndex;
|
||||||
|
});
|
||||||
let userRole = $derived(
|
let userRole = $derived(
|
||||||
$user.isAdmin && !serverConfigManager.value.isOnboarded ? OnboardingRole.SERVER : OnboardingRole.USER,
|
$user.isAdmin && !serverConfigManager.value.isOnboarded ? OnboardingRole.SERVER : OnboardingRole.USER,
|
||||||
);
|
);
|
||||||
@@ -114,12 +118,6 @@
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
const stepState = $page.url.searchParams.get('step');
|
|
||||||
const temporaryIndex = onboardingSteps.findIndex((step) => step.name === stepState);
|
|
||||||
index = temporaryIndex === -1 ? 0 : temporaryIndex;
|
|
||||||
});
|
|
||||||
|
|
||||||
const previousStepIndex = $derived(
|
const previousStepIndex = $derived(
|
||||||
onboardingSteps.findLastIndex((step, i) => shouldRunStep(step.role, userRole) && i < index),
|
onboardingSteps.findLastIndex((step, i) => shouldRunStep(step.role, userRole) && i < index),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user