Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ImmichFrame.Core/Interfaces/IServerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ public interface IGeneralSettings
public bool ImageFill { get; }
public string Layout { get; }
public string Language { get; }
public int AssetBatchSize { get; }
public bool ClientPersistAssetQueue { get; }
public bool ClientPersistAssetHistory { get; }

public void Validate();
}
Expand Down
2 changes: 1 addition & 1 deletion ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ private IAssetPool BuildPool(IAccountSettings accountSettings)

public Task<IEnumerable<AssetResponseDto>> GetAssets()
{
return _pool.GetAssets(25);
return _pool.GetAssets(_generalSettings.AssetBatchSize);
}

public Task<AssetResponseDto> GetAssetInfoById(Guid assetId) => _immichApi.GetAssetInfoAsync(assetId, null);
Expand Down
3 changes: 3 additions & 0 deletions ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ class GeneralSettingsV1Adapter(ServerSettingsV1 _delegate) : IGeneralSettings
public bool ImageFill => _delegate.ImageFill;
public string Layout => _delegate.Layout;
public string Language => _delegate.Language;
public int AssetBatchSize => 25; // Default value for V1 config
public bool ClientPersistAssetQueue => false; // Default value for V1 config
public bool ClientPersistAssetHistory => false; // Default value for V1 config

public void Validate() { }
}
Expand Down
6 changes: 6 additions & 0 deletions ImmichFrame.WebApi/Models/ClientSettingsDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public class ClientSettingsDto
public bool ImageFill { get; set; }
public string Layout { get; set; }
public string Language { get; set; }
public int AssetBatchSize { get; set; }
public bool ClientPersistAssetQueue { get; set; }
public bool ClientPersistAssetHistory { get; set; }

public static ClientSettingsDto FromGeneralSettings(IGeneralSettings generalSettings)
{
Expand Down Expand Up @@ -60,6 +63,9 @@ public static ClientSettingsDto FromGeneralSettings(IGeneralSettings generalSett
dto.ImageFill = generalSettings.ImageFill;
dto.Layout = generalSettings.Layout;
dto.Language = generalSettings.Language;
dto.AssetBatchSize = generalSettings.AssetBatchSize;
dto.ClientPersistAssetQueue = generalSettings.ClientPersistAssetQueue;
dto.ClientPersistAssetHistory = generalSettings.ClientPersistAssetHistory;
return dto;
}
}
11 changes: 10 additions & 1 deletion ImmichFrame.WebApi/Models/ServerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,17 @@ public class GeneralSettings : IGeneralSettings, IConfigSettable
public string? WeatherLatLong { get; set; } = "40.7128,74.0060";
public string? Webhook { get; set; }
public string? AuthenticationSecret { get; set; }
public int AssetBatchSize { get; set; } = 25;
public bool ClientPersistAssetQueue { get; set; } = false;
public bool ClientPersistAssetHistory { get; set; } = false;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

public void Validate() { }
public void Validate()
{
if (AssetBatchSize < 1)
{
AssetBatchSize = 1;
}
}
}

public class ServerAccountSettings : IAccountSettings, IConfigSettable
Expand Down
10 changes: 10 additions & 0 deletions docs/docs/getting-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ General:
ImageFill: false # boolean
# Allow two portrait images to be displayed next to each other
Layout: 'splitview' # single | splitview
# Number of assets to fetch per batch request
AssetBatchSize: 25 # int
# Persist current and upcoming assets in client localStorage across restarts.
# WARNING: Assets may become stale if removed from albums while stored locally.
# Set to false and restart clients to clear stored data.
ClientPersistAssetQueue: false # boolean
# Persist asset history (back button) in client localStorage across restarts
# WARNING: Assets may become stale if removed from albums while stored locally.
# Set to false and restart clients to clear stored data.
ClientPersistAssetHistory: false # boolean

# multiple accounts permitted
Accounts:
Expand Down
78 changes: 69 additions & 9 deletions immichFrame.Web/src/lib/components/home-page/home-page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import * as api from '$lib/index';
import ProgressBar from '$lib/components/elements/progress-bar.svelte';
import { slideshowStore } from '$lib/stores/slideshow.store';
import { clientIdentifierStore, authSecretStore } from '$lib/stores/persist.store';
import { clientIdentifierStore, authSecretStore, assetBacklogStore, assetHistoryStore, displayingAssetsStore, clearPersistedStore } from '$lib/stores/persist.store';
import { onDestroy, onMount, setContext } from 'svelte';
import OverlayControls from '../elements/overlay-controls.svelte';
import ImageComponent from '../elements/image-component.svelte';
Expand Down Expand Up @@ -30,6 +30,32 @@
let assetHistory: api.AssetResponseDto[] = [];
let assetBacklog: api.AssetResponseDto[] = [];

// persist helpers - save to localStorage if enabled
function persistBacklog() {
if ($configStore.clientPersistAssetQueue) {
assetBacklogStore.set(assetBacklog);
}
}

function persistHistory() {
if ($configStore.clientPersistAssetHistory) {
assetHistoryStore.set(assetHistory);
}
}

function persistDisplaying() {
if ($configStore.clientPersistAssetQueue) {
displayingAssetsStore.set(displayingAssets);
}
}

async function showAssets(assets: api.AssetResponseDto[]) {
displayingAssets = assets;
persistDisplaying();
updateImagePromises();
imagesState = await loadImages(assets);
}

let displayingAssets: api.AssetResponseDto[] = $state() as api.AssetResponseDto[];

const { restartProgress, stopProgress, instantTransition } = slideshowStore;
Expand Down Expand Up @@ -118,7 +144,7 @@

async function loadAssets() {
try {
let assetRequest = await api.getAsset();
let assetRequest = await api.getAsset({ clientIdentifier: $clientIdentifierStore });

if (assetRequest.status != 200) {
if (assetRequest.status == 401) {
Expand All @@ -130,6 +156,7 @@

error = false;
assetBacklog = assetRequest.data;
persistBacklog();
} catch {
error = true;
}
Expand Down Expand Up @@ -166,6 +193,7 @@
next = assetBacklog.splice(0, 1);
}
assetBacklog = [...assetBacklog];
persistBacklog();

if (displayingAssets) {
// Push to History
Expand All @@ -176,10 +204,9 @@
if (assetHistory.length > 250) {
assetHistory = assetHistory.splice(assetHistory.length - 250, 250);
}
persistHistory();

displayingAssets = next;
updateImagePromises();
imagesState = await loadImages(next);
await showAssets(next);
}

async function getPreviousAssets() {
Expand All @@ -200,14 +227,16 @@
}

assetHistory = [...assetHistory];
persistHistory();

// Unshift to Backlog
if (displayingAssets) {
assetBacklog.unshift(...displayingAssets);
}
displayingAssets = next;
updateImagePromises();
imagesState = await loadImages(next);
assetBacklog = [...assetBacklog];
persistBacklog();

await showAssets(next);
}

function isHorizontal(asset: api.AssetResponseDto) {
Expand Down Expand Up @@ -312,6 +341,33 @@
document.documentElement.style.fontSize = $configStore.baseFontSize;
}

// load or clear persisted asset queue and displaying assets
let restoredDisplaying = false;
if ($configStore.clientPersistAssetQueue) {
const storedBacklog = $assetBacklogStore as api.AssetResponseDto[];
if (storedBacklog && storedBacklog.length > 0) {
assetBacklog = storedBacklog;
}
const storedDisplaying = $displayingAssetsStore as api.AssetResponseDto[];
if (storedDisplaying && storedDisplaying.length > 0) {
displayingAssets = storedDisplaying;
restoredDisplaying = true;
}
} else {
clearPersistedStore('assetBacklog');
clearPersistedStore('displayingAssets');
}

// load or clear persisted asset history
if ($configStore.clientPersistAssetHistory) {
const stored = $assetHistoryStore as api.AssetResponseDto[];
if (stored && stored.length > 0) {
assetHistory = stored;
}
} else {
clearPersistedStore('assetHistory');
}

unsubscribeRestart = restartProgress.subscribe((value) => {
if (value) {
progressBar.restart(value);
Expand All @@ -324,7 +380,11 @@
}
});

getNextAssets();
if (restoredDisplaying) {
showAssets(displayingAssets);
} else {
getNextAssets();
}

return () => {
window.removeEventListener('mousemove', showCursor);
Expand Down
3 changes: 3 additions & 0 deletions immichFrame.Web/src/lib/immichFrameApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ export type ClientSettingsDto = {
imageFill?: boolean;
layout?: string | null;
language?: string | null;
assetBatchSize?: number;
clientPersistAssetQueue?: boolean;
clientPersistAssetHistory?: boolean;
};
export type IWeather = {
location?: string | null;
Expand Down
22 changes: 21 additions & 1 deletion immichFrame.Web/src/lib/stores/persist.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ function persistStore(key: string, defaultValue: string | null) {
return store;
}

function persistArrayStore<T>(key: string, defaultValue: T[]) {
const storedValue = localStorage?.getItem(key);
const initialValue: T[] = storedValue ? JSON.parse(storedValue) : defaultValue;

const store = writable(initialValue);

store.subscribe((value) => {
localStorage?.setItem(key, JSON.stringify(value));
});

return store;
}

export function clearPersistedStore(key: string) {
localStorage?.removeItem(key);
}

function generateGUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0,
Expand All @@ -23,4 +40,7 @@ function generateGUID() {
}

export const clientIdentifierStore = persistStore('clientIdentifier', generateGUID());
export const authSecretStore = persistStore('authSecret', null);
export const authSecretStore = persistStore('authSecret', null);
export const assetBacklogStore = persistArrayStore<unknown>('assetBacklog', []);
export const assetHistoryStore = persistArrayStore<unknown>('assetHistory', []);
export const displayingAssetsStore = persistArrayStore<unknown>('displayingAssets', []);