Skip to content

Commit 7e2a20d

Browse files
authored
Persist entry list view mode and dictionary preview pin per project (#2284)
1 parent 0f9f678 commit 7e2a20d

3 files changed

Lines changed: 21 additions & 6 deletions

File tree

frontend/viewer/src/lib/storage/project-storage.svelte.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,13 @@ class ProjectStorageProp extends StorageProp {
3535
export class ProjectStorage {
3636
readonly selectedTaskId: ProjectStorageProp;
3737
readonly currentView: ProjectStorageProp;
38+
readonly entryListViewMode: ProjectStorageProp;
39+
readonly dictionaryPreview: ProjectStorageProp;
3840

3941
constructor(projectCode: string, backend: IPreferencesService) {
4042
this.selectedTaskId = new ProjectStorageProp(projectCode, 'selectedTaskId', backend);
4143
this.currentView = new ProjectStorageProp(projectCode, 'currentView', backend);
44+
this.entryListViewMode = new ProjectStorageProp(projectCode, 'entryListViewMode', backend);
45+
this.dictionaryPreview = new ProjectStorageProp(projectCode, 'dictionaryPreview', backend);
4246
}
4347
}

frontend/viewer/src/project/browse/BrowseView.svelte

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
import {useProjectContext} from '$project/project-context.svelte';
2020
import type {EntryListViewMode} from './EntryListViewOptions.svelte';
2121
import EntryListViewOptions from './EntryListViewOptions.svelte';
22+
import {useProjectStorage} from '$lib/storage/project-storage.svelte';
2223
2324
const projectContext = useProjectContext();
2425
const viewService = useViewService();
2526
const dialogsService = useDialogsService();
27+
const entryListViewMode = useProjectStorage().entryListViewMode;
2628
2729
// DESKTOP: the entry is a sibling of the list (it's a split view). We can switch between selected entries.
2830
// So, selectedEntryId itself drives navigation.
@@ -37,7 +39,7 @@
3739
let semanticDomain = $state<ISemanticDomain>();
3840
let partOfSpeech = $state<IPartOfSpeech>();
3941
let sort = $state<SortConfig>();
40-
let entryMode: EntryListViewMode = $state('simple');
42+
const entryMode: EntryListViewMode = $derived(entryListViewMode.current === 'preview' ? 'preview' : 'simple');
4143
4244
async function newEntry() {
4345
const entry = await dialogsService.createNewEntry(undefined, {
@@ -85,7 +87,7 @@
8587
<div class="my-2 flex items-center justify-between">
8688
<SortMenu bind:value={sort}
8789
autoSelector={() => search ? SortField.SearchRelevance : SortField.Headword} />
88-
<EntryListViewOptions bind:entryMode />
90+
<EntryListViewOptions bind:entryMode={() => entryMode, (v) => void entryListViewMode.set(v)} />
8991
</div>
9092
</div>
9193
<EntriesList bind:this={entriesList}

frontend/viewer/src/project/browse/EntryView.svelte

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,16 @@
2323
import * as Alert from '$lib/components/ui/alert';
2424
import {pt} from '$lib/views/view-text';
2525
import {useViewService} from '$lib/views/view-service.svelte';
26+
import {useProjectStorage} from '$lib/storage/project-storage.svelte';
27+
28+
type DictionaryPreviewMode = 'show' | 'hide' | 'sticky';
2629
2730
const writingSystemService = useWritingSystemService();
2831
const eventBus = useProjectEventBus();
2932
const miniLcmApi = useMiniLcmApi();
3033
const features = useFeatures();
3134
const viewService = useViewService();
35+
const dictionaryPreviewStorage = useProjectStorage().dictionaryPreview;
3236
const {
3337
entryId,
3438
onClose,
@@ -88,8 +92,13 @@
8892
let entry = $derived(entryResource.current ?? undefined);
8993
const headword = $derived((entry && writingSystemService.headword(entry)) || $t`Untitled`);
9094
const loadingDebounced = new Debounced(() => entryResource.loading, 50);
91-
let dictionaryPreview: 'show' | 'hide' | 'sticky' = $state('show');
92-
const sticky = $derived.by(() => dictionaryPreview === 'sticky');
95+
const dictionaryPreview: DictionaryPreviewMode = $derived(
96+
isDictionaryPreviewMode(dictionaryPreviewStorage.current) ? dictionaryPreviewStorage.current : 'show'
97+
);
98+
function isDictionaryPreviewMode(value: string): value is DictionaryPreviewMode {
99+
return value === 'show' || value === 'hide' || value === 'sticky';
100+
}
101+
const sticky = $derived(dictionaryPreview === 'sticky');
93102
94103
let readonly = $state(false);
95104
let deleted = $state(false);
@@ -107,7 +116,7 @@
107116
<div class="md:pb-4">
108117
<DictionaryEntry {entry} showLinks class={cn('rounded bg-muted/80 dark:bg-muted/50 p-4')}>
109118
{#snippet actions()}
110-
<Toggle bind:pressed={() => sticky, (value) => dictionaryPreview = value ? 'sticky' : 'show'}
119+
<Toggle bind:pressed={() => sticky, (value) => void dictionaryPreviewStorage.set(value ? 'sticky' : 'show')}
111120
aria-label={$t`Toggle pinned`} class="aspect-square" size="sm">
112121
<Icon icon="i-mdi-pin-outline" class="size-5" />
113122
</Toggle>
@@ -125,7 +134,7 @@
125134
{/if}
126135
<h2 class="ml-4 text-2xl font-semibold mb-2 inline">{headword}</h2>
127136
<div class="flex">
128-
<ViewPicker bind:dictionaryPreview bind:readonly />
137+
<ViewPicker bind:dictionaryPreview={() => dictionaryPreview, (v) => void dictionaryPreviewStorage.set(v)} bind:readonly />
129138
<EntryMenu {entry} />
130139
</div>
131140
</div>

0 commit comments

Comments
 (0)