mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
feat: make memories slideshow duration configurable
This commit is contained in:
parent
53680d9643
commit
639b47a9fc
12 changed files with 68 additions and 6 deletions
|
|
@ -1986,6 +1986,7 @@
|
||||||
"they_will_be_merged_together": "They will be merged together",
|
"they_will_be_merged_together": "They will be merged together",
|
||||||
"third_party_resources": "Third-Party Resources",
|
"third_party_resources": "Third-Party Resources",
|
||||||
"time_based_memories": "Time-based memories",
|
"time_based_memories": "Time-based memories",
|
||||||
|
"time_based_memories_duration": "Number of seconds to display each image. This setting also affects the default slideshow duration.",
|
||||||
"timeline": "Timeline",
|
"timeline": "Timeline",
|
||||||
"timezone": "Timezone",
|
"timezone": "Timezone",
|
||||||
"to_archive": "Archive",
|
"to_archive": "Archive",
|
||||||
|
|
|
||||||
10
mobile/openapi/lib/model/memories_response.dart
generated
10
mobile/openapi/lib/model/memories_response.dart
generated
|
|
@ -13,25 +13,31 @@ part of openapi.api;
|
||||||
class MemoriesResponse {
|
class MemoriesResponse {
|
||||||
/// Returns a new [MemoriesResponse] instance.
|
/// Returns a new [MemoriesResponse] instance.
|
||||||
MemoriesResponse({
|
MemoriesResponse({
|
||||||
|
this.duration = 5,
|
||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
int duration;
|
||||||
|
|
||||||
bool enabled;
|
bool enabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is MemoriesResponse &&
|
bool operator ==(Object other) => identical(this, other) || other is MemoriesResponse &&
|
||||||
|
other.duration == duration &&
|
||||||
other.enabled == enabled;
|
other.enabled == enabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
|
(duration.hashCode) +
|
||||||
(enabled.hashCode);
|
(enabled.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'MemoriesResponse[enabled=$enabled]';
|
String toString() => 'MemoriesResponse[duration=$duration, enabled=$enabled]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
|
json[r'duration'] = this.duration;
|
||||||
json[r'enabled'] = this.enabled;
|
json[r'enabled'] = this.enabled;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
@ -45,6 +51,7 @@ class MemoriesResponse {
|
||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
return MemoriesResponse(
|
return MemoriesResponse(
|
||||||
|
duration: mapValueOfType<int>(json, r'duration')!,
|
||||||
enabled: mapValueOfType<bool>(json, r'enabled')!,
|
enabled: mapValueOfType<bool>(json, r'enabled')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -93,6 +100,7 @@ class MemoriesResponse {
|
||||||
|
|
||||||
/// The list of required keys that must be present in a JSON.
|
/// The list of required keys that must be present in a JSON.
|
||||||
static const requiredKeys = <String>{
|
static const requiredKeys = <String>{
|
||||||
|
'duration',
|
||||||
'enabled',
|
'enabled',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
mobile/openapi/lib/model/memories_update.dart
generated
20
mobile/openapi/lib/model/memories_update.dart
generated
|
|
@ -13,9 +13,19 @@ part of openapi.api;
|
||||||
class MemoriesUpdate {
|
class MemoriesUpdate {
|
||||||
/// Returns a new [MemoriesUpdate] instance.
|
/// Returns a new [MemoriesUpdate] instance.
|
||||||
MemoriesUpdate({
|
MemoriesUpdate({
|
||||||
|
this.duration,
|
||||||
this.enabled,
|
this.enabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Minimum value: 1
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
int? duration;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
/// does not include a default value (using the "default:" property), however, the generated
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
|
@ -26,18 +36,25 @@ class MemoriesUpdate {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is MemoriesUpdate &&
|
bool operator ==(Object other) => identical(this, other) || other is MemoriesUpdate &&
|
||||||
|
other.duration == duration &&
|
||||||
other.enabled == enabled;
|
other.enabled == enabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
|
(duration == null ? 0 : duration!.hashCode) +
|
||||||
(enabled == null ? 0 : enabled!.hashCode);
|
(enabled == null ? 0 : enabled!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'MemoriesUpdate[enabled=$enabled]';
|
String toString() => 'MemoriesUpdate[duration=$duration, enabled=$enabled]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
|
if (this.duration != null) {
|
||||||
|
json[r'duration'] = this.duration;
|
||||||
|
} else {
|
||||||
|
// json[r'duration'] = null;
|
||||||
|
}
|
||||||
if (this.enabled != null) {
|
if (this.enabled != null) {
|
||||||
json[r'enabled'] = this.enabled;
|
json[r'enabled'] = this.enabled;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -55,6 +72,7 @@ class MemoriesUpdate {
|
||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
return MemoriesUpdate(
|
return MemoriesUpdate(
|
||||||
|
duration: mapValueOfType<int>(json, r'duration'),
|
||||||
enabled: mapValueOfType<bool>(json, r'enabled'),
|
enabled: mapValueOfType<bool>(json, r'enabled'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12349,18 +12349,27 @@
|
||||||
},
|
},
|
||||||
"MemoriesResponse": {
|
"MemoriesResponse": {
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"duration": {
|
||||||
|
"default": 5,
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"default": true,
|
"default": true,
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
"duration",
|
||||||
"enabled"
|
"enabled"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"MemoriesUpdate": {
|
"MemoriesUpdate": {
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"duration": {
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,7 @@ export type FoldersResponse = {
|
||||||
sidebarWeb: boolean;
|
sidebarWeb: boolean;
|
||||||
};
|
};
|
||||||
export type MemoriesResponse = {
|
export type MemoriesResponse = {
|
||||||
|
duration: number;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
export type PeopleResponse = {
|
export type PeopleResponse = {
|
||||||
|
|
@ -208,6 +209,7 @@ export type FoldersUpdate = {
|
||||||
sidebarWeb?: boolean;
|
sidebarWeb?: boolean;
|
||||||
};
|
};
|
||||||
export type MemoriesUpdate = {
|
export type MemoriesUpdate = {
|
||||||
|
duration?: number;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
};
|
};
|
||||||
export type PeopleUpdate = {
|
export type PeopleUpdate = {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,12 @@ class AvatarUpdate {
|
||||||
class MemoriesUpdate {
|
class MemoriesUpdate {
|
||||||
@ValidateBoolean({ optional: true })
|
@ValidateBoolean({ optional: true })
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
|
||||||
|
@Optional()
|
||||||
|
@IsInt()
|
||||||
|
@IsPositive()
|
||||||
|
@ApiProperty({ type: 'integer' })
|
||||||
|
duration?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RatingsUpdate {
|
class RatingsUpdate {
|
||||||
|
|
@ -166,6 +172,9 @@ class RatingsResponse {
|
||||||
|
|
||||||
class MemoriesResponse {
|
class MemoriesResponse {
|
||||||
enabled: boolean = true;
|
enabled: boolean = true;
|
||||||
|
|
||||||
|
@ApiProperty({ type: 'integer' })
|
||||||
|
duration: number = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
class FoldersResponse {
|
class FoldersResponse {
|
||||||
|
|
|
||||||
|
|
@ -493,6 +493,7 @@ export interface UserPreferences {
|
||||||
};
|
};
|
||||||
memories: {
|
memories: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
duration: number;
|
||||||
};
|
};
|
||||||
people: {
|
people: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ const getDefaultPreferences = (): UserPreferences => {
|
||||||
},
|
},
|
||||||
memories: {
|
memories: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
duration: 5,
|
||||||
},
|
},
|
||||||
people: {
|
people: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
progressBarController = new Tween<number>(0, {
|
progressBarController = new Tween<number>(0, {
|
||||||
duration: (from: number, to: number) => (to ? 5000 * (to - from) : 0),
|
duration: (from: number, to: number) => (to ? $preferences.memories.duration * 1000 * (to - from) : 0),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@
|
||||||
NotificationType,
|
NotificationType,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte';
|
import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte';
|
||||||
|
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
|
||||||
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
|
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
|
||||||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||||
|
import { SettingInputFieldType } from '$lib/constants';
|
||||||
import { preferences } from '$lib/stores/user.store';
|
import { preferences } from '$lib/stores/user.store';
|
||||||
import { AssetOrder, updateMyPreferences } from '@immich/sdk';
|
import { AssetOrder, updateMyPreferences } from '@immich/sdk';
|
||||||
import { Button } from '@immich/ui';
|
import { Button } from '@immich/ui';
|
||||||
|
|
@ -22,6 +24,7 @@
|
||||||
|
|
||||||
// Memories
|
// Memories
|
||||||
let memoriesEnabled = $state($preferences?.memories?.enabled ?? true);
|
let memoriesEnabled = $state($preferences?.memories?.enabled ?? true);
|
||||||
|
let memoriesDuration = $state($preferences?.memories?.duration ?? 5);
|
||||||
|
|
||||||
// People
|
// People
|
||||||
let peopleEnabled = $state($preferences?.people?.enabled ?? false);
|
let peopleEnabled = $state($preferences?.people?.enabled ?? false);
|
||||||
|
|
@ -47,7 +50,7 @@
|
||||||
userPreferencesUpdateDto: {
|
userPreferencesUpdateDto: {
|
||||||
albums: { defaultAssetOrder },
|
albums: { defaultAssetOrder },
|
||||||
folders: { enabled: foldersEnabled, sidebarWeb: foldersSidebar },
|
folders: { enabled: foldersEnabled, sidebarWeb: foldersSidebar },
|
||||||
memories: { enabled: memoriesEnabled },
|
memories: { enabled: memoriesEnabled, duration: memoriesDuration },
|
||||||
people: { enabled: peopleEnabled, sidebarWeb: peopleSidebar },
|
people: { enabled: peopleEnabled, sidebarWeb: peopleSidebar },
|
||||||
ratings: { enabled: ratingsEnabled },
|
ratings: { enabled: ratingsEnabled },
|
||||||
sharedLinks: { enabled: sharedLinksEnabled, sidebarWeb: sharedLinkSidebar },
|
sharedLinks: { enabled: sharedLinksEnabled, sidebarWeb: sharedLinkSidebar },
|
||||||
|
|
@ -107,6 +110,14 @@
|
||||||
<div class="ms-4 mt-6">
|
<div class="ms-4 mt-6">
|
||||||
<SettingSwitch title={$t('enable')} bind:checked={memoriesEnabled} />
|
<SettingSwitch title={$t('enable')} bind:checked={memoriesEnabled} />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ms-4 mt-6">
|
||||||
|
<SettingInputField
|
||||||
|
inputType={SettingInputFieldType.NUMBER}
|
||||||
|
label={$t('duration')}
|
||||||
|
description={$t('time_based_memories_duration')}
|
||||||
|
bind:value={memoriesDuration}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</SettingAccordion>
|
</SettingAccordion>
|
||||||
|
|
||||||
<SettingAccordion key="people" title={$t('people')} subtitle={$t('people_feature_description')}>
|
<SettingAccordion key="people" title={$t('people')} subtitle={$t('people_feature_description')}>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
|
import { preferences } from '$lib/stores/user.store';
|
||||||
import { persisted } from 'svelte-persisted-store';
|
import { persisted } from 'svelte-persisted-store';
|
||||||
import { writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
|
|
||||||
export enum SlideshowState {
|
export enum SlideshowState {
|
||||||
PlaySlideshow = 'play-slideshow',
|
PlaySlideshow = 'play-slideshow',
|
||||||
|
|
@ -37,7 +38,7 @@ function createSlideshowStore() {
|
||||||
const slideshowState = writable<SlideshowState>(SlideshowState.None);
|
const slideshowState = writable<SlideshowState>(SlideshowState.None);
|
||||||
|
|
||||||
const showProgressBar = persisted<boolean>('slideshow-show-progressbar', true);
|
const showProgressBar = persisted<boolean>('slideshow-show-progressbar', true);
|
||||||
const slideshowDelay = persisted<number>('slideshow-delay', 5, {});
|
const slideshowDelay = persisted<number>('slideshow-delay', get(preferences)?.memories.duration ?? 5, {});
|
||||||
const slideshowTransition = persisted<boolean>('slideshow-transition', true);
|
const slideshowTransition = persisted<boolean>('slideshow-transition', true);
|
||||||
const slideshowAutoplay = persisted<boolean>('slideshow-autoplay', true, {});
|
const slideshowAutoplay = persisted<boolean>('slideshow-autoplay', true, {});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export const preferencesFactory = Sync.makeFactory<UserPreferencesResponseDto>({
|
||||||
},
|
},
|
||||||
memories: {
|
memories: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
duration: 5,
|
||||||
},
|
},
|
||||||
people: {
|
people: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue