Skip to content

Commit df943d2

Browse files
authored
improve startup time of CRDT projects (#1961)
* speed up project start up * don't use the loading view, just display the project view and let it wait for the api to show be bound in and display skeletons while it's loading
1 parent 893bc73 commit df943d2

7 files changed

Lines changed: 38 additions & 26 deletions

File tree

backend/FwLite/LcmCrdt/FullTextSearch/EntrySearchService.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -212,23 +212,17 @@ await EntrySearchRecordsTable
212212

213213
public async Task RegenerateIfMissing()
214214
{
215-
if (await QueryMissingEntries().AnyAsync())
215+
if (await HasMissingEntries())
216216
{
217217
logger.LogWarning("Regenerating entry search table because it is missing entries. This may take a while.");
218218
await RegenerateEntrySearchTable();
219219
}
220220
}
221221

222-
public IAsyncEnumerable<Entry> EntriesMissingInSearchTable()
222+
private async Task<bool> HasMissingEntries()
223223
{
224-
return QueryMissingEntries()
225-
.AsAsyncEnumerable();
226-
}
227-
228-
private IQueryable<Entry> QueryMissingEntries()
229-
{
230-
return dbContext.Set<Entry>()
231-
.Where(e => !EntrySearchRecords.Any(esr => esr.Id == e.Id));
224+
//not using a query to check Ids because the Id on the search table isn't indexed so it will be slow
225+
return await EntrySearchRecordsTable.CountAsync() != await dbContext.Set<Entry>().CountAsync();
232226
}
233227

234228
private static EntrySearchRecord ToEntrySearchRecord(Entry entry, WritingSystem[] writingSystems)

backend/harmony

frontend/viewer/src/DotnetProjectView.svelte

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,27 @@
1111
import type {
1212
ISyncServiceJsInvokable
1313
} from '$lib/dotnet-types/generated-types/FwLiteShared/Services/ISyncServiceJsInvokable';
14-
import ProjectLoader from './ProjectLoader.svelte';
1514
import {initProjectContext} from '$lib/project-context.svelte';
1615
1716
const projectServicesProvider = useProjectServicesProvider();
18-
const projectContext = initProjectContext();
1917
2018
const {code, type: projectType, paratext = false}: {
2119
code: string; // Code for CRDTs, project-name for FWData
2220
type: 'fwdata' | 'crdt';
2321
paratext?: boolean;
2422
} = $props();
23+
const projectContext = initProjectContext();
24+
projectContext.projectCode = code;
2525
2626
2727
2828
let projectName = $state<string>(code);
2929
let projectScope: IProjectScope;
3030
let serviceLoaded = $state(false);
31-
let loading = $state(true);
3231
let destroyed = false;
3332
onMount(async () => {
3433
console.debug('ProjectView mounted');
34+
projectContext.projectCode = code;
3535
if (projectType === 'crdt') {
3636
const maybeProjectName = await projectServicesProvider.tryGetCrdtProjectName(code);
3737
projectName = maybeProjectName ? maybeProjectName : code;
@@ -81,7 +81,7 @@
8181
}
8282
</script>
8383

84-
<ProjectLoader readyToLoadProject={serviceLoaded} {loading} {projectName}>
85-
<ProjectView onloaded={() => loading = false} data-paratext={paratext}></ProjectView>
86-
</ProjectLoader>
84+
85+
<ProjectView data-paratext={paratext}/>
86+
8787

frontend/viewer/src/ProjectView.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
import type {HTMLAttributes} from 'svelte/elements';
44
55
const {
6-
onloaded,
6+
onloaded = () => {},
77
...rest
88
}: {
9-
onloaded: (loaded: boolean) => void;
9+
onloaded?: (loaded: boolean) => void;
1010
} & HTMLAttributes<HTMLDivElement> = $props();
1111
</script>
1212

frontend/viewer/src/lib/DialogsProvider.svelte

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
import DeleteDialog from '$lib/entry-editor/DeleteDialog.svelte';
44
import AudioDialog from './components/audio/AudioDialog.svelte';
55
import {useDialogsService} from '$lib/services/dialogs-service';
6+
import {useProjectContext} from '$lib/project-context.svelte';
7+
const projectContext = useProjectContext();
68
const dialogsService = useDialogsService();
79
let deleteDialog = $state<DeleteDialog>();
810
$effect(() => {
911
dialogsService.invokeDeleteDialog = deleteDialog?.prompt;
1012
})
1113
</script>
1214

13-
<NewEntryDialog/>
15+
{#if projectContext.maybeApi}
16+
<NewEntryDialog/>
17+
<AudioDialog/>
18+
{/if}
1419
<DeleteDialog bind:this={deleteDialog}/>
15-
<AudioDialog/>

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,25 @@ export class ProjectContext {
5656
return this.#api;
5757
}
5858
public get projectName(): string {
59-
if (!this.#projectName) throw new Error('projectName not set');
59+
if (!this.#projectName) return this.projectCode;
6060
return this.#projectName;
6161
}
6262
public get projectCode(): string {
6363
if (!this.#projectCode) throw new Error('projectCode not set');
6464
return this.#projectCode;
6565
}
66+
public set projectCode(value: string) {
67+
if (this.#projectCode === value) return;
68+
if (this.#projectCode) {
69+
if (import.meta.env.DEV) {
70+
throw new Error('Cannot set projectCode after it is already set');
71+
} else {
72+
console.error('Cannot set projectCode after it is already set');
73+
return;
74+
}
75+
}
76+
this.#projectCode = value;
77+
}
6678
public get projectType(): ProjectType {
6779
return this.#projectType;
6880
}

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import type {IQueryOptions} from '$lib/dotnet-types/generated-types/MiniLcm/IQueryOptions';
44
import {SortField} from '$lib/dotnet-types/generated-types/MiniLcm/SortField';
55
import {Debounced, resource, useDebounce} from 'runed';
6-
import {useMiniLcmApi} from '$lib/services/service-provider';
76
import EntryRow from './EntryRow.svelte';
87
import Button from '$lib/components/ui/button/button.svelte';
98
import {cn} from '$lib/utils';
@@ -18,6 +17,7 @@
1817
import type {SortConfig} from './SortMenu.svelte';
1918
import {AppNotification} from '$lib/notifications/notifications';
2019
import {Icon} from '$lib/components/ui/icon';
20+
import {useProjectContext} from '$lib/project-context.svelte';
2121
2222
const {
2323
search = '',
@@ -34,7 +34,8 @@
3434
gridifyFilter?: string;
3535
previewDictionary?: boolean
3636
} = $props();
37-
const miniLcmApi = useMiniLcmApi();
37+
const projectContext = useProjectContext();
38+
const miniLcmApi = $derived(projectContext.maybeApi);
3839
const dialogsService = useDialogsService();
3940
const projectEventBus = useProjectEventBus();
4041
@@ -61,9 +62,10 @@
6162
entriesResource.mutate(updatedEntries);
6263
}
6364
64-
let loadingUndebounced = $state(false);
65+
let loadingUndebounced = $state(true);
6566
const loading = new Debounced(() => loadingUndebounced, 50);
6667
const fetchCurrentEntries = useDebounce(async (silent = false) => {
68+
if (!miniLcmApi) return [];
6769
if (!silent) loadingUndebounced = true;
6870
try {
6971
const queryOptions: IQueryOptions = {
@@ -90,7 +92,7 @@
9092
}, 300);
9193
9294
const entriesResource = resource(
93-
() => ({ search, sort, gridifyFilter }),
95+
() => ({ search, sort, gridifyFilter, miniLcmApi }),
9496
async () => await fetchCurrentEntries());
9597
const entries = $derived(entriesResource.current ?? []);
9698

0 commit comments

Comments
 (0)