Skip to content

Commit b5bf602

Browse files
authored
Sort data returned from server (as much as feasible) (#1074)
* Add a few more sorting utilities * Sort data by locale (simple cases) * Sort in org settings * Sort users page * Shorten sorting util names * Users already sorted on server * Componentize OrganizationDropdown This allows consistent sorting for a common use case * Remove flatpickr import * Sort /directory on server (pagination) * Fix state mutation error * Remove $state.snapshot in favor of array.toSorted * Remove $state.snapshot from /directory * Use toSorted in #each * Add more sorts * Even more sorting * Fix bug in /users/id/settings/groups * Fix residual store usage from before upgrade/remove-deprecated I somehow overlooked this when rebasing... * Convert OrganizationDropdown to Svelte 5 * Fixed bug with project count not updating on org change
1 parent 1543183 commit b5bf602

File tree

44 files changed

+347
-234
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+347
-234
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script lang="ts">
2+
import { org_allOrganizations } from '$lib/paraglide/messages';
3+
import { languageTag } from '$lib/paraglide/runtime';
4+
import { byName } from '$lib/utils';
5+
interface Props {
6+
organizations: { Id: number; Name: string | null }[];
7+
value: number | null;
8+
className?: string;
9+
allowNull?: boolean;
10+
[key: string]: any
11+
}
12+
13+
let {
14+
organizations,
15+
value = $bindable(),
16+
className = '',
17+
allowNull = false,
18+
...rest
19+
}: Props = $props();
20+
</script>
21+
22+
<select class="select select-bordered {className}" bind:value {...rest}>
23+
{#if allowNull}
24+
<option value={null} selected>{org_allOrganizations()}</option>
25+
{/if}
26+
{#each organizations.toSorted((a, b) => byName(a, b, languageTag())) as organization}
27+
<option value={organization.Id}>{organization.Name}</option>
28+
{/each}
29+
</select>

source/SIL.AppBuilder.Portal/src/lib/components/OrganizationSelector.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script lang="ts">
2+
import { languageTag } from '$lib/paraglide/runtime';
3+
import { byName } from '$lib/utils';
24
import type { Prisma } from '@prisma/client';
35
interface Props {
46
organizations: Prisma.OrganizationsGetPayload<{
@@ -23,7 +25,7 @@
2325
</tr>
2426
</thead>
2527
<tbody>
26-
{#each organizations as org}
28+
{#each organizations.toSorted((a, b) => byName(a, b, languageTag())) as org}
2729
<tr
2830
class="h-16 border-y hover:bg-base-200 cursor-pointer"
2931
onclick={() => onSelect(org.Id)}

source/SIL.AppBuilder.Portal/src/lib/components/Pagination.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
</div>
6969
<div class="grow">&nbsp;</div>
7070
<select class="select select-bordered" name="size" bind:value={size}>
71-
{#each [10, 25, 50, 100].concat(extraSizeOptions).sort((a, b) => a - b) as value}
71+
{#each [10, 25, 50, 100].concat(extraSizeOptions).toSorted((a, b) => a - b) as value}
7272
<option {value}>{value}</option>
7373
{/each}
7474
</select>

source/SIL.AppBuilder.Portal/src/lib/products/components/BuildArtifacts.svelte

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,26 @@
44
import * as m from '$lib/paraglide/messages';
55
import { languageTag } from '$lib/paraglide/runtime';
66
import { getRelativeTime } from '$lib/timeUtils';
7-
import { bytesToHumanSize } from '$lib/utils';
8-
7+
import { byString, bytesToHumanSize } from '$lib/utils';
98
109
interface Props {
1110
build: {
12-
Version: string | null;
13-
Success: boolean | null;
14-
BuildId: number;
15-
ProductArtifacts: {
16-
ArtifactType: string | null;
17-
FileSize: bigint | null;
18-
Url: string | null;
19-
DateUpdated: Date | null;
20-
}[];
21-
ProductPublications: {
22-
Channel: string | null;
11+
Version: string | null;
2312
Success: boolean | null;
24-
LogUrl: string | null;
25-
DateUpdated: Date | null;
26-
}[];
27-
};
13+
BuildId: number;
14+
ProductArtifacts: {
15+
ArtifactType: string | null;
16+
FileSize: bigint | null;
17+
Url: string | null;
18+
DateUpdated: Date | null;
19+
}[];
20+
ProductPublications: {
21+
Channel: string | null;
22+
Success: boolean | null;
23+
LogUrl: string | null;
24+
DateUpdated: Date | null;
25+
}[];
26+
};
2827
latestBuildId: number | undefined;
2928
}
3029
@@ -82,13 +81,11 @@
8281
</tr>
8382
</thead>
8483
<tbody>
85-
{#each build.ProductArtifacts as artifact}
84+
{#each build.ProductArtifacts.toSorted( (a, b) => byString(a.ArtifactType, b.ArtifactType, langTag) ) as artifact}
8685
<tr>
8786
<td><IconContainer icon="mdi:file" width="20" /> {artifact.ArtifactType}</td>
8887
<td>
89-
<Tooltip
90-
tip={artifact.DateUpdated?.toLocaleString(langTag)}
91-
>
88+
<Tooltip tip={artifact.DateUpdated?.toLocaleString(langTag)}>
9289
{getRelativeTime(artifact.DateUpdated)}
9390
</Tooltip>
9491
</td>

source/SIL.AppBuilder.Portal/src/lib/projects/components/ProjectCard.svelte

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
import IconContainer from '$lib/components/IconContainer.svelte';
33
import { getIcon } from '$lib/icons/productDefinitionIcon';
44
import * as m from '$lib/paraglide/messages';
5+
import { languageTag } from '$lib/paraglide/runtime';
56
import { getTimeDateString } from '$lib/timeUtils';
7+
import { byString } from '$lib/utils';
68
import type { PrunedProject } from '../common';
79
810
interface Props {
@@ -76,6 +78,7 @@
7678
</div>
7779
<div class="w-full bg-base-100 p-4 pt-2">
7880
{#if project.Products.length > 0}
81+
{@const langTag = languageTag()}
7982
<table class="w-full">
8083
<thead>
8184
<tr class="text-left">
@@ -85,7 +88,7 @@
8588
</tr>
8689
</thead>
8790
<tbody>
88-
{#each project.Products as product}
91+
{#each project.Products.toSorted( (a, b) => byString(a.ProductDefinitionName, b.ProductDefinitionName, langTag) ) as product}
8992
<tr>
9093
<td class="p-2">
9194
<div class="flex items-center">

source/SIL.AppBuilder.Portal/src/lib/utils.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,24 @@ interface NamedEntity {
2020
Name: string | null;
2121
}
2222

23-
export function sortByName(a: NamedEntity, b: NamedEntity, languageTag: string): number {
24-
return a.Name?.localeCompare(b.Name ?? '', languageTag) ?? 0;
23+
export function byName(
24+
a: NamedEntity | null | undefined,
25+
b: NamedEntity | null | undefined,
26+
languageTag: string
27+
): number {
28+
return byString(a?.Name, b?.Name, languageTag);
29+
}
30+
31+
export function byString(
32+
a: string | null | undefined,
33+
b: string | null | undefined,
34+
languageTag: string
35+
): number {
36+
return a?.localeCompare(b ?? '', languageTag) ?? 0;
37+
}
38+
39+
export function byNumber(a: number | bigint | null, b: number | bigint | null): number {
40+
return a === b ? 0 : (a ?? 0) > (b ?? 0) ? 1 : -1;
2541
}
2642

2743
/** returns true if user is a SuperAdmin, or is an OrgAdmin for the specified organization

source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/settings/build-engines/+page.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import * as m from '$lib/paraglide/messages';
55
import { languageTag } from '$lib/paraglide/runtime';
66
import { getRelativeTime } from '$lib/timeUtils';
7+
import { byString } from '$lib/utils';
78
import type { PageData } from './$types';
89
910
interface Props {
@@ -21,7 +22,7 @@
2122
{/snippet}
2223

2324
<div class="flex flex-col w-full">
24-
{#each data.buildEngines as buildEngine}
25+
{#each data.buildEngines.toSorted( (a, b) => byString(a.BuildEngineUrl, b.BuildEngineUrl, languageTag()) ) as buildEngine}
2526
<DataDisplayBox
2627
title={buildEngine.BuildEngineUrl}
2728
data={buildEngine}

source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/settings/organizations/+page.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import DataDisplayBox from '$lib/components/settings/DataDisplayBox.svelte';
44
import * as m from '$lib/paraglide/messages';
55
import { languageTag } from '$lib/paraglide/runtime';
6-
import { sortByName } from '$lib/utils';
6+
import { byName } from '$lib/utils';
77
import type { PageData } from './$types';
88
99
interface Props {
@@ -18,7 +18,7 @@
1818
</a>
1919

2020
<div class="flex flex-col w-full">
21-
{#each data.organizations.sort((a, b) => sortByName(a, b, languageTag())) as organization}
21+
{#each data.organizations.toSorted((a, b) => byName(a, b, languageTag())) as organization}
2222
<DataDisplayBox
2323
editable
2424
onEdit={() => goto('/admin/settings/organizations/edit?id=' + organization.Id)}

source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/settings/organizations/edit/+page.svelte

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import MultiselectBox from '$lib/components/settings/MultiselectBox.svelte';
55
import MultiselectBoxElement from '$lib/components/settings/MultiselectBoxElement.svelte';
66
import * as m from '$lib/paraglide/messages';
7+
import { languageTag } from '$lib/paraglide/runtime';
8+
import { byName } from '$lib/utils';
79
import { superForm } from 'sveltekit-superforms';
810
import type { PageData } from './$types';
911
@@ -42,7 +44,7 @@
4244
</LabeledFormInput>
4345
<LabeledFormInput name="admin_settings_organizations_owner">
4446
<select class="select select-bordered" name="owner" bind:value={$superFormData.owner}>
45-
{#each data.options.users as option}
47+
{#each data.options.users.toSorted((a, b) => byName(a, b, languageTag())) as option}
4648
<option value={option.Id}>{option.Name}</option>
4749
{/each}
4850
</select>
@@ -99,7 +101,7 @@
99101
</div>
100102
</label>
101103
</div>
102-
104+
<!-- TODO: sort this. I think this will need a refactor of MultiselectBox -->
103105
<MultiselectBox header={m.org_storeSelectTitle()}>
104106
{#each $superFormData.stores as store}
105107
<MultiselectBoxElement

source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/settings/organizations/new/+page.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import { goto } from '$app/navigation';
33
import LabeledFormInput from '$lib/components/settings/LabeledFormInput.svelte';
44
import * as m from '$lib/paraglide/messages';
5+
import { languageTag } from '$lib/paraglide/runtime';
6+
import { byName } from '$lib/utils';
57
import { superForm } from 'sveltekit-superforms';
68
import type { PageData } from './$types';
79
@@ -28,7 +30,7 @@
2830
</LabeledFormInput>
2931
<LabeledFormInput name="admin_settings_organizations_owner">
3032
<select class="select select-bordered" name="owner" bind:value={$form.owner}>
31-
{#each data.options.users as user}
33+
{#each data.options.users.toSorted((a, b) => byName(a, b, languageTag())) as user}
3234
<option value={user.Id}>{user.Name}</option>
3335
{/each}
3436
</select>

0 commit comments

Comments
 (0)