Skip to content

Commit 2e54d34

Browse files
committed
Redesign Add searched word and make vlist more stable
1 parent fb3dc9c commit 2e54d34

12 files changed

Lines changed: 131 additions & 129 deletions

File tree

frontend/viewer/src/locales/en.po

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,12 @@ msgstr "Add part of"
168168
msgid "Add Sense"
169169
msgstr "Add Sense"
170170

171+
#. Subtitle of a placeholder row shown at the end of the entry list while searching (also in the no-results state)
172+
#. The search text is displayed above it as a headword; clicking opens the new-entry dialog pre-filled with that text
173+
#: src/project/browse/EntriesList.svelte
174+
msgid "Add to dictionary"
175+
msgstr "Add to dictionary"
176+
171177
#. Dialog title
172178
#: src/lib/entry-editor/NewEntryDialog.svelte
173179
msgid "Add Word"
@@ -454,16 +460,6 @@ msgstr "Create Entry"
454460
msgid "Create Example Project"
455461
msgstr "Create Example Project"
456462

457-
#: src/project/browse/EntriesList.svelte
458-
#: src/project/browse/EntriesList.svelte
459-
msgid "Create new entry {0}"
460-
msgstr "Create new entry {0}"
461-
462-
#: src/project/browse/EntriesList.svelte
463-
#: src/project/browse/EntriesList.svelte
464-
msgid "Create new word {0}"
465-
msgstr "Create new word {0}"
466-
467463
#. Submit button in the create custom view dialog.
468464
#: src/lib/views/custom/CreateCustomViewDialog.svelte
469465
msgid "Create View"

frontend/viewer/src/locales/es.po

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ msgstr "Añadir parte de"
173173
msgid "Add Sense"
174174
msgstr "Añadir acepción"
175175

176+
#. Subtitle of a placeholder row shown at the end of the entry list while searching (also in the no-results state)
177+
#. The search text is displayed above it as a headword; clicking opens the new-entry dialog pre-filled with that text
178+
#: src/project/browse/EntriesList.svelte
179+
msgid "Add to dictionary"
180+
msgstr ""
181+
176182
#. Dialog title
177183
#: src/lib/entry-editor/NewEntryDialog.svelte
178184
msgid "Add Word"
@@ -459,16 +465,6 @@ msgstr "Crear entrada"
459465
msgid "Create Example Project"
460466
msgstr "Crear un proyecto de ejemplo"
461467

462-
#: src/project/browse/EntriesList.svelte
463-
#: src/project/browse/EntriesList.svelte
464-
msgid "Create new entry {0}"
465-
msgstr ""
466-
467-
#: src/project/browse/EntriesList.svelte
468-
#: src/project/browse/EntriesList.svelte
469-
msgid "Create new word {0}"
470-
msgstr ""
471-
472468
#. Submit button in the create custom view dialog.
473469
#: src/lib/views/custom/CreateCustomViewDialog.svelte
474470
msgid "Create View"

frontend/viewer/src/locales/fr.po

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ msgstr "Ajouter une partie de"
173173
msgid "Add Sense"
174174
msgstr "Ajouter du sens"
175175

176+
#. Subtitle of a placeholder row shown at the end of the entry list while searching (also in the no-results state)
177+
#. The search text is displayed above it as a headword; clicking opens the new-entry dialog pre-filled with that text
178+
#: src/project/browse/EntriesList.svelte
179+
msgid "Add to dictionary"
180+
msgstr ""
181+
176182
#. Dialog title
177183
#: src/lib/entry-editor/NewEntryDialog.svelte
178184
msgid "Add Word"
@@ -459,16 +465,6 @@ msgstr "Créer une entrée"
459465
msgid "Create Example Project"
460466
msgstr "Créer un projet exemplaire"
461467

462-
#: src/project/browse/EntriesList.svelte
463-
#: src/project/browse/EntriesList.svelte
464-
msgid "Create new entry {0}"
465-
msgstr ""
466-
467-
#: src/project/browse/EntriesList.svelte
468-
#: src/project/browse/EntriesList.svelte
469-
msgid "Create new word {0}"
470-
msgstr ""
471-
472468
#. Submit button in the create custom view dialog.
473469
#: src/lib/views/custom/CreateCustomViewDialog.svelte
474470
msgid "Create View"

frontend/viewer/src/locales/id.po

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ msgstr "Tambahkan bagian dari"
173173
msgid "Add Sense"
174174
msgstr "Tambahkan Pengertian"
175175

176+
#. Subtitle of a placeholder row shown at the end of the entry list while searching (also in the no-results state)
177+
#. The search text is displayed above it as a headword; clicking opens the new-entry dialog pre-filled with that text
178+
#: src/project/browse/EntriesList.svelte
179+
msgid "Add to dictionary"
180+
msgstr ""
181+
176182
#. Dialog title
177183
#: src/lib/entry-editor/NewEntryDialog.svelte
178184
msgid "Add Word"
@@ -459,16 +465,6 @@ msgstr "Membuat Entri"
459465
msgid "Create Example Project"
460466
msgstr "Buat Proyek Contoh"
461467

462-
#: src/project/browse/EntriesList.svelte
463-
#: src/project/browse/EntriesList.svelte
464-
msgid "Create new entry {0}"
465-
msgstr ""
466-
467-
#: src/project/browse/EntriesList.svelte
468-
#: src/project/browse/EntriesList.svelte
469-
msgid "Create new word {0}"
470-
msgstr ""
471-
472468
#. Submit button in the create custom view dialog.
473469
#: src/lib/views/custom/CreateCustomViewDialog.svelte
474470
msgid "Create View"

frontend/viewer/src/locales/ko.po

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ msgstr "의 일부를 추가합니다."
173173
msgid "Add Sense"
174174
msgstr "센스 추가"
175175

176+
#. Subtitle of a placeholder row shown at the end of the entry list while searching (also in the no-results state)
177+
#. The search text is displayed above it as a headword; clicking opens the new-entry dialog pre-filled with that text
178+
#: src/project/browse/EntriesList.svelte
179+
msgid "Add to dictionary"
180+
msgstr ""
181+
176182
#. Dialog title
177183
#: src/lib/entry-editor/NewEntryDialog.svelte
178184
msgid "Add Word"
@@ -459,16 +465,6 @@ msgstr "항목 만들기"
459465
msgid "Create Example Project"
460466
msgstr "예제 프로젝트 만들기"
461467

462-
#: src/project/browse/EntriesList.svelte
463-
#: src/project/browse/EntriesList.svelte
464-
msgid "Create new entry {0}"
465-
msgstr ""
466-
467-
#: src/project/browse/EntriesList.svelte
468-
#: src/project/browse/EntriesList.svelte
469-
msgid "Create new word {0}"
470-
msgstr ""
471-
472468
#. Submit button in the create custom view dialog.
473469
#: src/lib/views/custom/CreateCustomViewDialog.svelte
474470
msgid "Create View"

frontend/viewer/src/locales/ms.po

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ msgstr "Tambah sebahagian daripada"
173173
msgid "Add Sense"
174174
msgstr "Tambah Makna"
175175

176+
#. Subtitle of a placeholder row shown at the end of the entry list while searching (also in the no-results state)
177+
#. The search text is displayed above it as a headword; clicking opens the new-entry dialog pre-filled with that text
178+
#: src/project/browse/EntriesList.svelte
179+
msgid "Add to dictionary"
180+
msgstr ""
181+
176182
#. Dialog title
177183
#: src/lib/entry-editor/NewEntryDialog.svelte
178184
msgid "Add Word"
@@ -459,16 +465,6 @@ msgstr "Cipta Entri"
459465
msgid "Create Example Project"
460466
msgstr "Cipta Projek Contoh"
461467

462-
#: src/project/browse/EntriesList.svelte
463-
#: src/project/browse/EntriesList.svelte
464-
msgid "Create new entry {0}"
465-
msgstr ""
466-
467-
#: src/project/browse/EntriesList.svelte
468-
#: src/project/browse/EntriesList.svelte
469-
msgid "Create new word {0}"
470-
msgstr ""
471-
472468
#. Submit button in the create custom view dialog.
473469
#: src/lib/views/custom/CreateCustomViewDialog.svelte
474470
msgid "Create View"

frontend/viewer/src/locales/sw.po

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ msgstr "Ongeza sehemu ya"
173173
msgid "Add Sense"
174174
msgstr "Ongeza Maana"
175175

176+
#. Subtitle of a placeholder row shown at the end of the entry list while searching (also in the no-results state)
177+
#. The search text is displayed above it as a headword; clicking opens the new-entry dialog pre-filled with that text
178+
#: src/project/browse/EntriesList.svelte
179+
msgid "Add to dictionary"
180+
msgstr ""
181+
176182
#. Dialog title
177183
#: src/lib/entry-editor/NewEntryDialog.svelte
178184
msgid "Add Word"
@@ -459,16 +465,6 @@ msgstr "Tengeneza Ingizo"
459465
msgid "Create Example Project"
460466
msgstr "Tengeneza Mradi wa Mfano"
461467

462-
#: src/project/browse/EntriesList.svelte
463-
#: src/project/browse/EntriesList.svelte
464-
msgid "Create new entry {0}"
465-
msgstr ""
466-
467-
#: src/project/browse/EntriesList.svelte
468-
#: src/project/browse/EntriesList.svelte
469-
msgid "Create new word {0}"
470-
msgstr ""
471-
472468
#. Submit button in the create custom view dialog.
473469
#: src/lib/views/custom/CreateCustomViewDialog.svelte
474470
msgid "Create View"

frontend/viewer/src/locales/vi.po

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ msgstr "Thêm phần của"
173173
msgid "Add Sense"
174174
msgstr "Thêm Ý nghĩa (sense)"
175175

176+
#. Subtitle of a placeholder row shown at the end of the entry list while searching (also in the no-results state)
177+
#. The search text is displayed above it as a headword; clicking opens the new-entry dialog pre-filled with that text
178+
#: src/project/browse/EntriesList.svelte
179+
msgid "Add to dictionary"
180+
msgstr ""
181+
176182
#. Dialog title
177183
#: src/lib/entry-editor/NewEntryDialog.svelte
178184
msgid "Add Word"
@@ -459,16 +465,6 @@ msgstr "Tạo mục"
459465
msgid "Create Example Project"
460466
msgstr "Tạo Dự án ví dụ"
461467

462-
#: src/project/browse/EntriesList.svelte
463-
#: src/project/browse/EntriesList.svelte
464-
msgid "Create new entry {0}"
465-
msgstr ""
466-
467-
#: src/project/browse/EntriesList.svelte
468-
#: src/project/browse/EntriesList.svelte
469-
msgid "Create new word {0}"
470-
msgstr ""
471-
472468
#. Submit button in the create custom view dialog.
473469
#: src/lib/views/custom/CreateCustomViewDialog.svelte
474470
msgid "Create View"

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

Lines changed: 61 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import {Debounced, watch} from 'runed';
44
import EntryRow from './EntryRow.svelte';
55
import Button from '$lib/components/ui/button/button.svelte';
6+
import ListItem from '$lib/components/ListItem.svelte';
67
import {cn} from '$lib/utils';
78
import {t} from 'svelte-i18n-lingui';
89
import DevContent from '$lib/layout/DevContent.svelte';
@@ -20,7 +21,7 @@
2021
import {EntryLoaderService} from '$lib/services/entry-loader-service.svelte';
2122
import {onDestroy, untrack} from 'svelte';
2223
import {useViewService} from '$lib/views/view-service.svelte';
23-
import { pt } from '$lib/views/view-text';
24+
import {pt} from '$lib/views/view-text';
2425
2526
let {
2627
search = '',
@@ -61,6 +62,7 @@
6162
};
6263
6364
let entryLoader = $derived(!miniLcmApi ? undefined : untrack(() => new EntryLoaderService(miniLcmApi, deps)));
65+
const displayedEntryCount = $derived(entryLoader?.totalCount ?? 0);
6466
6567
// Destroy the previous entryLoader when a new one is created
6668
watch(
@@ -102,14 +104,21 @@
102104
// Generate a random number of skeleton rows
103105
const skeletonRowCount = Math.ceil(Math.random() * 3) + 3;
104106
105-
// Generate index array for virtual list.
106-
// We use a small number of skeletons if the total count is not yet known
107-
// to avoid a "white phase" between initial load and list initialization.
108-
const indexArray = $derived(
109-
entryLoader?.totalCount !== undefined
110-
? Array.from({ length: entryLoader.totalCount + 1 }, (_, i) => i)
111-
: Array.from({ length: skeletonRowCount }, (_, i) => i)
112-
);
107+
const canCreateFromSearch = $derived(search?.trim() && !disableNewEntry);
108+
const showTerminalCreateRow = $derived(canCreateFromSearch && displayedEntryCount > 0);
109+
110+
type ListRow = {key: string, index: number, create?: boolean};
111+
const rows: ListRow[] = $derived.by(() => {
112+
if (entryLoader?.totalCount === undefined) {
113+
return Array.from({length: skeletonRowCount}, (_, i) => ({key: `skeleton-${i}`, index: i}));
114+
}
115+
const generation = entryLoader.generation;
116+
const entryRows: ListRow[] = Array.from({length: displayedEntryCount}, (_, i) => ({key: `${generation}-${i}`, index: i}));
117+
if (showTerminalCreateRow) {
118+
entryRows.push({key: `${generation}-create`, index: displayedEntryCount, create: true});
119+
}
120+
return entryRows;
121+
});
113122
114123
async function handleNewEntry(headword: string | undefined = undefined) {
115124
const entry = await dialogsService.createNewEntry(headword, {
@@ -176,6 +185,18 @@
176185
177186
</script>
178187

188+
{#snippet newEntryFromSearchRow(className: string = '')}
189+
<ListItem
190+
class={cn('bg-transparent shadow-none hover:shadow-none border-2 border-dashed border-muted-foreground/40', className)}
191+
onclick={() => handleNewEntry(search)}>
192+
{#snippet icon()}
193+
<Icon icon="i-mdi-plus-thick" class="size-6 text-primary/60" />
194+
{/snippet}
195+
<span class="font-medium text-2xl">{search}</span>
196+
<span class="text-sm text-muted-foreground">{$t`Add to dictionary`}</span>
197+
</ListItem>
198+
{/snippet}
199+
179200
<FabContainer>
180201
<DevContent>
181202
<Button
@@ -200,54 +221,45 @@
200221
{:else}
201222
<div class="h-full">
202223
{#if entryLoader?.totalCount === 0}
203-
<div class="flex flex-col items-center justify-center h-full text-muted-foreground gap-2">
204-
<p>{pt($t`No entries found`, $t`No words found`, viewService.currentView)}</p>
224+
<div class="flex flex-col items-center justify-center h-full gap-4 px-4">
225+
<p class="text-muted-foreground">{pt($t`No entries found`, $t`No words found`, viewService.currentView)}</p>
205226

206-
{#if search}
207-
<Button icon="i-mdi-plus" class="" onclick={() => handleNewEntry(search)}>
208-
{pt($t`Create new entry ${search}`, $t`Create new word ${search}`, viewService.currentView)}
209-
</Button>
227+
{#if canCreateFromSearch}
228+
{@render newEntryFromSearchRow('max-w-md')}
210229
{/if}
211230
</div>
212231
{/if}
213232

214233
<VList bind:this={vList}
215-
data={indexArray}
234+
data={rows}
216235
class="h-full p-0.5 md:pr-3 after:h-12 after:block"
217-
getKey={(index: number) => `${entryLoader?.generation ?? EntryLoaderService.DEFAULT_GENERATION}-${index}`}
236+
getKey={(row: ListRow) => row.key}
218237
bufferSize={450}>
219-
{#snippet children(index: number)}
220-
{#key entryLoader?.generation ?? EntryLoaderService.DEFAULT_GENERATION}
221-
<!--the last item is a button to create a new entry based on the search query-->
222-
{#if index !== entryLoader?.totalCount}
223-
<Delayed
224-
getCached={() => entryLoader?.getCachedEntryByIndex(index)}
225-
load={() => entryLoader?.getOrLoadEntryByIndex(index)}
226-
delay={250}
227-
>
228-
{#snippet children(state)}
229-
{#if state.loading || !state.current}
230-
<!-- we want the initial loading state and the first loading entries
231-
to share the same skeletons, so there's no flicker -->
232-
<EntryRow class="mb-2" skeleton={true}/>
233-
{:else}
234-
{@const entry = state.current}
235-
<EntryMenu {entry} contextMenu>
236-
<EntryRow {entry}
237-
class="mb-2"
238-
selected={selectedEntryId === entry.id}
239-
onclick={() => onSelectEntry(entry)}
240-
{previewDictionary}/>
241-
</EntryMenu>
242-
{/if}
243-
{/snippet}
244-
</Delayed>
245-
{:else if search && entryLoader?.totalCount !== 0}
246-
<Button icon="i-mdi-plus" onclick={() => handleNewEntry(search)}>
247-
{pt($t`Create new entry ${search}`, $t`Create new word ${search}`, viewService.currentView)}
248-
</Button>
249-
{/if}
250-
{/key}
238+
{#snippet children(row: ListRow)}
239+
{#if row.create}
240+
{@render newEntryFromSearchRow()}
241+
{:else}
242+
<Delayed
243+
getCached={() => entryLoader?.getCachedEntryByIndex(row.index)}
244+
load={() => entryLoader?.getOrLoadEntryByIndex(row.index)}
245+
delay={250}
246+
>
247+
{#snippet children(state)}
248+
{#if state.loading || !state.current}
249+
<EntryRow class="mb-2" skeleton={true}/>
250+
{:else}
251+
{@const entry = state.current}
252+
<EntryMenu {entry} contextMenu>
253+
<EntryRow {entry}
254+
class="mb-2"
255+
selected={selectedEntryId === entry.id}
256+
onclick={() => onSelectEntry(entry)}
257+
{previewDictionary}/>
258+
</EntryMenu>
259+
{/if}
260+
{/snippet}
261+
</Delayed>
262+
{/if}
251263
{/snippet}
252264
</VList>
253265
</div>

0 commit comments

Comments
 (0)