@selvajs/ui 6.6.0-beta.3 → 6.6.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ErrorScreen.svelte +6 -2
- package/dist/components/compute/AppLayout.svelte +157 -71
- package/dist/components/compute/AppLayout.svelte.d.ts +6 -0
- package/dist/components/compute/ComputeApp.svelte +39 -20
- package/dist/components/compute/ComputeApp.svelte.d.ts +19 -2
- package/dist/components/compute/ComputeMessagesDialog.svelte +30 -40
- package/dist/components/compute/ParameterPresetManager.svelte +7 -44
- package/dist/components/compute/ParameterPresetManager.svelte.d.ts +4 -5
- package/dist/components/compute/SolveMessages.svelte +30 -37
- package/dist/components/compute/SolveMessages.svelte.d.ts +0 -12
- package/dist/components/layout/PageFooter.svelte +21 -11
- package/dist/components/preview/ChartOutput.svelte +12 -7
- package/dist/components/preview/Group.svelte +33 -18
- package/dist/components/preview/HelpButton.svelte +4 -2
- package/dist/components/preview/ImageOutput.svelte +427 -68
- package/dist/components/preview/InputControl.svelte +11 -2
- package/dist/components/preview/OutputDisplay.svelte +17 -12
- package/dist/components/preview/TabContent.svelte +3 -0
- package/dist/components/preview/inputs/ColorInput.svelte +3 -1
- package/dist/components/preview/inputs/ColorInput.svelte.d.ts +1 -0
- package/dist/components/preview/inputs/DropdownInput.svelte +4 -1
- package/dist/components/preview/inputs/FileInput.svelte +50 -34
- package/dist/components/preview/inputs/NumberInput.svelte +92 -10
- package/dist/components/preview/inputs/NumberInput.svelte.d.ts +2 -0
- package/dist/components/preview/inputs/TextInput.svelte +8 -3
- package/dist/components/primitives/CalculateButton.svelte +49 -23
- package/dist/components/primitives/CalculateButton.svelte.d.ts +3 -0
- package/dist/components/primitives/SolvingIndicator.svelte +21 -17
- package/dist/components/primitives/badge/badge.svelte +2 -2
- package/dist/components/primitives/badge/badge.svelte.d.ts +4 -4
- package/dist/components/primitives/button/button.svelte +2 -2
- package/dist/components/primitives/button/button.svelte.d.ts +4 -4
- package/dist/components/primitives/checkbox/checkbox.svelte +1 -1
- package/dist/components/primitives/dialog/dialog-content.svelte +6 -2
- package/dist/components/primitives/dropdown-menu/dropdown-menu-content.svelte +1 -1
- package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
- package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
- package/dist/components/primitives/input/input.svelte +2 -2
- package/dist/components/primitives/input-group/input-group.svelte +1 -1
- package/dist/components/primitives/mode-toggle/mode-toggle.svelte +4 -1
- package/dist/components/primitives/popover/popover-content.svelte +1 -1
- package/dist/components/primitives/scroll-area/scroll-area.svelte +1 -1
- package/dist/components/primitives/search/search.svelte +1 -1
- package/dist/components/primitives/select/select-trigger.svelte +1 -1
- package/dist/components/primitives/slider/slider.svelte +7 -2
- package/dist/components/primitives/slider/slider.svelte.d.ts +5 -1
- package/dist/components/primitives/switch/switch.svelte +1 -1
- package/dist/components/primitives/tabs/tabs-trigger.svelte +1 -1
- package/dist/components/primitives/tabs/tabs-trigger.svelte.d.ts +1 -1
- package/dist/components/primitives/textarea/textarea.svelte +1 -1
- package/dist/components/primitives/toggle/toggle.svelte +1 -1
- package/dist/components/primitives/toggle/toggle.svelte.d.ts +1 -1
- package/dist/components/primitives/toggle-group/toggle-group-item.svelte +1 -1
- package/dist/components/primitives/toggle-group/toggle-group.svelte +1 -1
- package/dist/components/viewer/Viewer.svelte +20 -17
- package/dist/compute/run-panel-action.d.ts +8 -0
- package/dist/compute/run-panel-action.js +12 -0
- package/dist/compute/useSolveSession.svelte.js +3 -0
- package/dist/i18n/localeContext.svelte.d.ts +10 -3
- package/dist/i18n/localeContext.svelte.js +27 -5
- package/dist/i18n/messages.d.ts +115 -2
- package/dist/i18n/messages.js +199 -11
- package/dist/index.d.ts +1 -1
- package/dist/public.d.ts +1 -1
- package/dist/schema/visibility-rules.js +21 -0
- package/dist/styles/base.css +9 -6
- package/dist/types/actionButton.d.ts +17 -3
- package/dist/types/presetLabels.d.ts +3 -2
- package/dist/types/presetLabels.js +9 -8
- package/package.json +8 -8
- package/src/lib/components/ErrorScreen.svelte +6 -2
- package/src/lib/components/compute/AppLayout.svelte +157 -71
- package/src/lib/components/compute/ComputeApp.svelte +39 -20
- package/src/lib/components/compute/ComputeMessagesDialog.svelte +30 -40
- package/src/lib/components/compute/ParameterPresetManager.svelte +7 -44
- package/src/lib/components/compute/SolveMessages.svelte +30 -37
- package/src/lib/components/layout/PageFooter.svelte +21 -11
- package/src/lib/components/preview/ChartOutput.svelte +12 -7
- package/src/lib/components/preview/Group.svelte +33 -18
- package/src/lib/components/preview/HelpButton.svelte +4 -2
- package/src/lib/components/preview/ImageOutput.svelte +427 -68
- package/src/lib/components/preview/InputControl.svelte +11 -2
- package/src/lib/components/preview/OutputDisplay.svelte +17 -12
- package/src/lib/components/preview/TabContent.svelte +3 -0
- package/src/lib/components/preview/inputs/ColorInput.svelte +3 -1
- package/src/lib/components/preview/inputs/DropdownInput.svelte +4 -1
- package/src/lib/components/preview/inputs/FileInput.svelte +50 -34
- package/src/lib/components/preview/inputs/NumberInput.svelte +92 -10
- package/src/lib/components/preview/inputs/TextInput.svelte +8 -3
- package/src/lib/components/primitives/CalculateButton.svelte +49 -23
- package/src/lib/components/primitives/SolvingIndicator.svelte +21 -17
- package/src/lib/components/primitives/badge/badge.svelte +2 -2
- package/src/lib/components/primitives/button/button.svelte +2 -2
- package/src/lib/components/primitives/checkbox/checkbox.svelte +1 -1
- package/src/lib/components/primitives/dialog/dialog-content.svelte +6 -2
- package/src/lib/components/primitives/dropdown-menu/dropdown-menu-content.svelte +1 -1
- package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
- package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
- package/src/lib/components/primitives/input/input.svelte +2 -2
- package/src/lib/components/primitives/input-group/input-group.svelte +1 -1
- package/src/lib/components/primitives/mode-toggle/mode-toggle.svelte +4 -1
- package/src/lib/components/primitives/popover/popover-content.svelte +1 -1
- package/src/lib/components/primitives/scroll-area/scroll-area.svelte +1 -1
- package/src/lib/components/primitives/search/search.svelte +1 -1
- package/src/lib/components/primitives/select/select-trigger.svelte +1 -1
- package/src/lib/components/primitives/slider/slider.svelte +7 -2
- package/src/lib/components/primitives/switch/switch.svelte +1 -1
- package/src/lib/components/primitives/tabs/tabs-trigger.svelte +1 -1
- package/src/lib/components/primitives/textarea/textarea.svelte +1 -1
- package/src/lib/components/primitives/toggle/toggle.svelte +1 -1
- package/src/lib/components/primitives/toggle-group/toggle-group-item.svelte +1 -1
- package/src/lib/components/primitives/toggle-group/toggle-group.svelte +1 -1
- package/src/lib/components/viewer/Viewer.svelte +20 -17
- package/src/lib/compute/run-panel-action.ts +16 -0
- package/src/lib/compute/useSolveSession.svelte.ts +3 -0
- package/src/lib/i18n/localeContext.svelte.ts +40 -5
- package/src/lib/i18n/messages.ts +351 -13
- package/src/lib/index.ts +1 -0
- package/src/lib/public.ts +1 -0
- package/src/lib/schema/visibility-rules.ts +23 -0
- package/src/lib/styles/base.css +9 -6
- package/src/lib/types/actionButton.ts +16 -3
- package/src/lib/types/presetLabels.ts +13 -11
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
import type { SolveDiagnostic } from '@selvajs/solve/shared';
|
|
11
11
|
import * as Dialog from '../primitives/dialog';
|
|
12
12
|
import IssueList, { type Issue } from '../primitives/IssueList.svelte';
|
|
13
|
+
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
14
|
+
import { formatCount } from '../../i18n/messages';
|
|
13
15
|
|
|
14
16
|
interface Props {
|
|
15
17
|
/** The last solve's messages. Preferred over `errors`/`warnings` when both are given. */
|
|
@@ -23,6 +25,9 @@
|
|
|
23
25
|
|
|
24
26
|
let { diagnostics, errors = [], warnings = [], trigger }: Props = $props();
|
|
25
27
|
|
|
28
|
+
const locale = getLocaleContext();
|
|
29
|
+
const t = $derived(locale.messages);
|
|
30
|
+
|
|
26
31
|
let open = $state(false);
|
|
27
32
|
|
|
28
33
|
const rows = $derived<SolveDiagnostic[]>(
|
|
@@ -41,26 +46,11 @@
|
|
|
41
46
|
count: number;
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
const
|
|
45
|
-
{
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
many: 'errors'
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
level: 'warning',
|
|
53
|
-
severity: 'warning',
|
|
54
|
-
one: 'warning',
|
|
55
|
-
many: 'warnings'
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
level: 'remark',
|
|
59
|
-
severity: 'note',
|
|
60
|
-
one: 'note',
|
|
61
|
-
many: 'notes'
|
|
62
|
-
}
|
|
63
|
-
] as const;
|
|
49
|
+
const levels = $derived([
|
|
50
|
+
{ level: 'error', severity: 'error', one: t.errorCountOne, other: t.errorCountOther },
|
|
51
|
+
{ level: 'warning', severity: 'warning', one: t.warningCountOne, other: t.warningCountOther },
|
|
52
|
+
{ level: 'remark', severity: 'note', one: t.noteCountOne, other: t.noteCountOther }
|
|
53
|
+
] as const);
|
|
64
54
|
|
|
65
55
|
/**
|
|
66
56
|
* One section per level that has messages, most severe first; within a level, the order the
|
|
@@ -68,23 +58,25 @@
|
|
|
68
58
|
* branches raises the same warning 200 times, and an unstacked list buries every other message.
|
|
69
59
|
*/
|
|
70
60
|
const sections = $derived(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
byKey[key]
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
61
|
+
levels
|
|
62
|
+
.map((meta) => {
|
|
63
|
+
// A null-prototype object rather than a Map: a local built fresh on each derive, never
|
|
64
|
+
// reactive state, and the lint rule that steers Map → SvelteMap cannot tell them apart.
|
|
65
|
+
const byKey: Record<string, Item> = Object.create(null);
|
|
66
|
+
const items: Item[] = [];
|
|
67
|
+
for (const d of rows) {
|
|
68
|
+
if (d.level !== meta.level) continue;
|
|
69
|
+
const key = `${d.message} ${d.source ?? ''}`;
|
|
70
|
+
if (byKey[key]) {
|
|
71
|
+
byKey[key].count++;
|
|
72
|
+
} else {
|
|
73
|
+
byKey[key] = { level: d.level, message: d.message, source: d.source, count: 1 };
|
|
74
|
+
items.push(byKey[key]);
|
|
75
|
+
}
|
|
84
76
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
77
|
+
return { ...meta, items };
|
|
78
|
+
})
|
|
79
|
+
.filter((section) => section.items.length > 0)
|
|
88
80
|
);
|
|
89
81
|
|
|
90
82
|
const issues = $derived<Issue[]>(
|
|
@@ -106,13 +98,11 @@
|
|
|
106
98
|
|
|
107
99
|
<Dialog.Content class="max-w-lg">
|
|
108
100
|
<Dialog.Header>
|
|
109
|
-
<Dialog.Title>
|
|
101
|
+
<Dialog.Title>{t.messagesTitle}</Dialog.Title>
|
|
110
102
|
<Dialog.Description>
|
|
111
103
|
{#each sections as section, i (section.level)}
|
|
112
104
|
{#if i > 0}<span class="mx-1.5">·</span>{/if}
|
|
113
|
-
<span
|
|
114
|
-
>{section.items.length} {section.items.length === 1 ? section.one : section.many}</span
|
|
115
|
-
>
|
|
105
|
+
<span>{formatCount(section.items.length, section.one, section.other)}</span>
|
|
116
106
|
{/each}
|
|
117
107
|
</Dialog.Description>
|
|
118
108
|
</Dialog.Header>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { Upload } from '@lucide/svelte';
|
|
3
3
|
import type { UISchema, ParameterPreset } from '@selvajs/schemas';
|
|
4
4
|
import {
|
|
5
5
|
createSavedState,
|
|
@@ -9,17 +9,12 @@
|
|
|
9
9
|
importStateFromJson
|
|
10
10
|
} from '../../schema/param-exporter';
|
|
11
11
|
import { Button, Input, Label, Textarea, Dialog, Callout, IssueList, toast } from '../primitives';
|
|
12
|
-
|
|
13
|
-
import type { ActionButton } from '../../types/actionButton';
|
|
14
12
|
import { DEFAULT_PRESET_LABELS, type PresetLabels } from '../../types/presetLabels';
|
|
15
13
|
|
|
16
14
|
interface Props {
|
|
17
15
|
schema: UISchema;
|
|
18
16
|
currentValues: Record<string, unknown>;
|
|
19
17
|
onLoadValues: (values: Record<string, unknown>) => void;
|
|
20
|
-
showSaveButton?: boolean;
|
|
21
|
-
showLoadButton?: boolean;
|
|
22
|
-
actions?: ActionButton[];
|
|
23
18
|
/** When set, persist saved states via this callback instead of downloading a .slvp file. */
|
|
24
19
|
onSaveState?: (state: ParameterPreset) => void | Promise<void>;
|
|
25
20
|
/** When set, the Load dialog lists these states instead of showing a file input. */
|
|
@@ -28,17 +23,7 @@
|
|
|
28
23
|
labels?: Partial<PresetLabels>;
|
|
29
24
|
}
|
|
30
25
|
|
|
31
|
-
let {
|
|
32
|
-
schema,
|
|
33
|
-
currentValues,
|
|
34
|
-
onLoadValues,
|
|
35
|
-
showSaveButton = true,
|
|
36
|
-
showLoadButton = true,
|
|
37
|
-
actions = [],
|
|
38
|
-
onSaveState,
|
|
39
|
-
onListStates,
|
|
40
|
-
labels
|
|
41
|
-
}: Props = $props();
|
|
26
|
+
let { schema, currentValues, onLoadValues, onSaveState, onListStates, labels }: Props = $props();
|
|
42
27
|
|
|
43
28
|
const t = $derived({ ...DEFAULT_PRESET_LABELS, ...labels });
|
|
44
29
|
|
|
@@ -58,7 +43,8 @@
|
|
|
58
43
|
let isLoadingList = $state(false);
|
|
59
44
|
let listError = $state('');
|
|
60
45
|
|
|
61
|
-
|
|
46
|
+
/** Opens the save dialog. The panel footer's menu calls it. */
|
|
47
|
+
export function openSave() {
|
|
62
48
|
exportName = `State ${new Date().toLocaleDateString()}`;
|
|
63
49
|
exportDescription = '';
|
|
64
50
|
exportAuthor = schema.author || '';
|
|
@@ -128,7 +114,8 @@
|
|
|
128
114
|
loadResult = null;
|
|
129
115
|
}
|
|
130
116
|
|
|
131
|
-
|
|
117
|
+
/** Opens the load dialog. The panel footer's menu calls it. */
|
|
118
|
+
export async function openLoad() {
|
|
132
119
|
showLoadDialog = true;
|
|
133
120
|
if (!onListStates) return;
|
|
134
121
|
|
|
@@ -155,30 +142,6 @@
|
|
|
155
142
|
}
|
|
156
143
|
</script>
|
|
157
144
|
|
|
158
|
-
{#if showSaveButton}
|
|
159
|
-
<Button variant="outline" size="sm" onclick={openExportDialog}>
|
|
160
|
-
<Download class="mr-2 h-4 w-4" />
|
|
161
|
-
{t.saveButton}
|
|
162
|
-
</Button>
|
|
163
|
-
{/if}
|
|
164
|
-
|
|
165
|
-
{#if showLoadButton}
|
|
166
|
-
<Button variant="outline" size="sm" onclick={openLoadDialog}>
|
|
167
|
-
<Upload class="mr-2 h-4 w-4" />
|
|
168
|
-
{t.loadButton}
|
|
169
|
-
</Button>
|
|
170
|
-
{/if}
|
|
171
|
-
|
|
172
|
-
{#each actions as action (action.id)}
|
|
173
|
-
<Button variant={action.variant ?? 'outline'} size={action.size ?? 'sm'} onclick={action.onclick}>
|
|
174
|
-
{#if action.icon}
|
|
175
|
-
{@const IconComponent = action.icon}
|
|
176
|
-
<IconComponent class="mr-2 h-4 w-4" />
|
|
177
|
-
{/if}
|
|
178
|
-
{action.label}
|
|
179
|
-
</Button>
|
|
180
|
-
{/each}
|
|
181
|
-
|
|
182
145
|
<!-- .sps is the pre-rename extension; files with it exist on users' drives, so keep accepting it. -->
|
|
183
146
|
<input
|
|
184
147
|
bind:this={fileInputRef}
|
|
@@ -225,7 +188,7 @@
|
|
|
225
188
|
|
|
226
189
|
<Dialog.Footer>
|
|
227
190
|
<Button variant="outline" onclick={() => (showExportDialog = false)}>{t.cancelButton}</Button>
|
|
228
|
-
<Button onclick={handleExport}>{t.
|
|
191
|
+
<Button onclick={handleExport}>{t.saveConfirmButton}</Button>
|
|
229
192
|
</Dialog.Footer>
|
|
230
193
|
</Dialog.Content>
|
|
231
194
|
</Dialog.Root>
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import type { UISchema, ParameterPreset } from '@selvajs/schemas';
|
|
2
|
-
import type { ActionButton } from '../../types/actionButton';
|
|
3
2
|
import { type PresetLabels } from '../../types/presetLabels';
|
|
4
3
|
interface Props {
|
|
5
4
|
schema: UISchema;
|
|
6
5
|
currentValues: Record<string, unknown>;
|
|
7
6
|
onLoadValues: (values: Record<string, unknown>) => void;
|
|
8
|
-
showSaveButton?: boolean;
|
|
9
|
-
showLoadButton?: boolean;
|
|
10
|
-
actions?: ActionButton[];
|
|
11
7
|
/** When set, persist saved states via this callback instead of downloading a .slvp file. */
|
|
12
8
|
onSaveState?: (state: ParameterPreset) => void | Promise<void>;
|
|
13
9
|
/** When set, the Load dialog lists these states instead of showing a file input. */
|
|
@@ -15,6 +11,9 @@ interface Props {
|
|
|
15
11
|
/** Overrides merged over `DEFAULT_PRESET_LABELS`. */
|
|
16
12
|
labels?: Partial<PresetLabels>;
|
|
17
13
|
}
|
|
18
|
-
declare const ParameterPresetManager: import("svelte").Component<Props, {
|
|
14
|
+
declare const ParameterPresetManager: import("svelte").Component<Props, {
|
|
15
|
+
openSave: () => void;
|
|
16
|
+
openLoad: () => Promise<void>;
|
|
17
|
+
}, "">;
|
|
19
18
|
type ParameterPresetManager = ReturnType<typeof ParameterPresetManager>;
|
|
20
19
|
export default ParameterPresetManager;
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { Button } from '../primitives/button';
|
|
20
20
|
import IssueList, { type Issue } from '../primitives/IssueList.svelte';
|
|
21
21
|
import SeverityIcon from '../primitives/SeverityIcon.svelte';
|
|
22
|
+
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
22
23
|
|
|
23
24
|
interface Props {
|
|
24
25
|
/** What the running (or most recent) solve has said over the live channel. */
|
|
@@ -30,43 +31,20 @@
|
|
|
30
31
|
onabort: () => void;
|
|
31
32
|
onconfirm: () => void;
|
|
32
33
|
ondiscard: () => void;
|
|
33
|
-
labels?: Labels;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
interface Labels {
|
|
37
|
-
solving: string;
|
|
38
|
-
blockedTitle: string;
|
|
39
|
-
messagesTitle: string;
|
|
40
|
-
blockedDescription: string;
|
|
41
|
-
messagesDescription: string;
|
|
42
|
-
confirm: string;
|
|
43
|
-
discard: string;
|
|
44
|
-
abort: string;
|
|
45
|
-
dismiss: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const DEFAULT_LABELS: Labels = {
|
|
49
|
-
solving: 'Solving…',
|
|
50
|
-
blockedTitle: 'Solve stopped',
|
|
51
|
-
messagesTitle: 'Solve messages',
|
|
52
|
-
blockedDescription: 'The definition stopped this solve. No results were produced.',
|
|
53
|
-
messagesDescription: 'This solve reported the following. Continue to see the result.',
|
|
54
|
-
confirm: 'Continue',
|
|
55
|
-
discard: 'Discard',
|
|
56
|
-
abort: 'Abort',
|
|
57
|
-
dismiss: 'Dismiss'
|
|
58
|
-
};
|
|
59
|
-
|
|
60
36
|
let {
|
|
61
37
|
live,
|
|
62
38
|
phase: sessionPhase,
|
|
63
39
|
diagnostics = [],
|
|
64
40
|
onabort,
|
|
65
41
|
onconfirm,
|
|
66
|
-
ondiscard
|
|
67
|
-
labels = DEFAULT_LABELS
|
|
42
|
+
ondiscard
|
|
68
43
|
}: Props = $props();
|
|
69
44
|
|
|
45
|
+
const locale = getLocaleContext();
|
|
46
|
+
const t = $derived(locale.messages);
|
|
47
|
+
|
|
70
48
|
const REMARK_LINGER_MS = 6000;
|
|
71
49
|
|
|
72
50
|
const progress = $derived([...live.progress.values()]);
|
|
@@ -239,6 +217,19 @@
|
|
|
239
217
|
: rows.length > 0 && lingering
|
|
240
218
|
);
|
|
241
219
|
|
|
220
|
+
// What a screen reader hears of the passive views, through a region that outlives the panel: one
|
|
221
|
+
// created together with its text is not announced. The step name, not its counter, so a long
|
|
222
|
+
// solve announces each step rather than every tick. Review and blocked are alerts of their own.
|
|
223
|
+
const announcement = $derived(
|
|
224
|
+
!visible
|
|
225
|
+
? ''
|
|
226
|
+
: view === 'live'
|
|
227
|
+
? ((headline ?? progress[0])?.label ?? t.solveSolving)
|
|
228
|
+
: view === 'idle'
|
|
229
|
+
? rows.map((d) => d.message).join(' ')
|
|
230
|
+
: ''
|
|
231
|
+
);
|
|
232
|
+
|
|
242
233
|
function dismiss() {
|
|
243
234
|
dismissedFor = diagnostics;
|
|
244
235
|
}
|
|
@@ -254,13 +245,15 @@
|
|
|
254
245
|
|
|
255
246
|
<svelte:window onkeydown={handleKeydown} />
|
|
256
247
|
|
|
248
|
+
<div class="sr-only" role="status" aria-live="polite">{announcement}</div>
|
|
249
|
+
|
|
257
250
|
{#if visible}
|
|
258
251
|
<div
|
|
259
252
|
class="bottom-4 p-3 shadow-lg w-lg fixed left-1/2 z-50 max-w-screen-inset -translate-x-1/2 rounded-lg border bg-background {view ===
|
|
260
253
|
'blocked'
|
|
261
254
|
? 'border-destructive/50'
|
|
262
255
|
: ''}"
|
|
263
|
-
role={view === '
|
|
256
|
+
role={view === 'review' || view === 'blocked' ? 'alert' : undefined}
|
|
264
257
|
data-testid="solve-messages"
|
|
265
258
|
data-view={view}
|
|
266
259
|
>
|
|
@@ -270,7 +263,7 @@
|
|
|
270
263
|
<div>
|
|
271
264
|
<div class="gap-2 flex items-center">
|
|
272
265
|
<LoaderCircle class="h-4 w-4 animate-spin shrink-0 text-muted-foreground" />
|
|
273
|
-
<span class="min-w-0 text-sm flex-1 truncate">{head?.label ??
|
|
266
|
+
<span class="min-w-0 text-sm flex-1 truncate">{head?.label ?? t.solveSolving}</span>
|
|
274
267
|
{#if head?.done !== undefined && head?.total !== undefined}
|
|
275
268
|
<span class="text-xs shrink-0 text-muted-foreground tabular-nums">
|
|
276
269
|
{head.done} / {head.total}
|
|
@@ -283,7 +276,7 @@
|
|
|
283
276
|
disabled={finishing}
|
|
284
277
|
onclick={onabort}
|
|
285
278
|
>
|
|
286
|
-
{
|
|
279
|
+
{t.solveMessagesAbort}
|
|
287
280
|
</Button>
|
|
288
281
|
</div>
|
|
289
282
|
{#each progress as p, i (p.source ?? '')}
|
|
@@ -300,7 +293,7 @@
|
|
|
300
293
|
<div
|
|
301
294
|
class="h-1 overflow-hidden rounded-full bg-muted"
|
|
302
295
|
role="progressbar"
|
|
303
|
-
aria-label={p.label ?? p.source ??
|
|
296
|
+
aria-label={p.label ?? p.source ?? t.solveSolving}
|
|
304
297
|
aria-valuemin={0}
|
|
305
298
|
aria-valuemax={100}
|
|
306
299
|
aria-valuenow={p.fraction !== undefined ? Math.round(p.fraction * 100) : undefined}
|
|
@@ -327,14 +320,14 @@
|
|
|
327
320
|
<div class="gap-2 text-sm font-medium flex items-center">
|
|
328
321
|
{#if view === 'blocked'}
|
|
329
322
|
<SeverityIcon severity="error" />
|
|
330
|
-
{
|
|
323
|
+
{t.solveBlockedTitle}
|
|
331
324
|
{:else}
|
|
332
325
|
<SeverityIcon severity="warning" />
|
|
333
|
-
{
|
|
326
|
+
{t.solveMessagesTitle}
|
|
334
327
|
{/if}
|
|
335
328
|
</div>
|
|
336
329
|
<p class="text-xs text-muted-foreground">
|
|
337
|
-
{view === 'blocked' ?
|
|
330
|
+
{view === 'blocked' ? t.solveBlocked : t.solveMessagesDescription}
|
|
338
331
|
</p>
|
|
339
332
|
</div>
|
|
340
333
|
{/if}
|
|
@@ -348,10 +341,10 @@
|
|
|
348
341
|
{#if view === 'review' || view === 'blocked'}
|
|
349
342
|
<div class="mt-3 gap-2 flex justify-end">
|
|
350
343
|
{#if view === 'blocked'}
|
|
351
|
-
<Button variant="outline" size="sm" onclick={dismiss}>{
|
|
344
|
+
<Button variant="outline" size="sm" onclick={dismiss}>{t.solveMessagesDismiss}</Button>
|
|
352
345
|
{:else}
|
|
353
|
-
<Button variant="outline" size="sm" onclick={ondiscard}>{
|
|
354
|
-
<Button size="sm" onclick={onconfirm}>{
|
|
346
|
+
<Button variant="outline" size="sm" onclick={ondiscard}>{t.solveMessagesDiscard}</Button>
|
|
347
|
+
<Button size="sm" onclick={onconfirm}>{t.solveMessagesConfirm}</Button>
|
|
355
348
|
{/if}
|
|
356
349
|
</div>
|
|
357
350
|
{/if}
|
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
import type { SolveDiagnostic } from '@selvajs/solve/shared';
|
|
2
2
|
import type { LiveSolveState, SolvePhase } from '@selvajs/solve/client';
|
|
3
|
-
interface Labels {
|
|
4
|
-
solving: string;
|
|
5
|
-
blockedTitle: string;
|
|
6
|
-
messagesTitle: string;
|
|
7
|
-
blockedDescription: string;
|
|
8
|
-
messagesDescription: string;
|
|
9
|
-
confirm: string;
|
|
10
|
-
discard: string;
|
|
11
|
-
abort: string;
|
|
12
|
-
dismiss: string;
|
|
13
|
-
}
|
|
14
3
|
interface Props {
|
|
15
4
|
/** What the running (or most recent) solve has said over the live channel. */
|
|
16
5
|
live: LiveSolveState;
|
|
@@ -21,7 +10,6 @@ interface Props {
|
|
|
21
10
|
onabort: () => void;
|
|
22
11
|
onconfirm: () => void;
|
|
23
12
|
ondiscard: () => void;
|
|
24
|
-
labels?: Labels;
|
|
25
13
|
}
|
|
26
14
|
declare const SolveMessages: import("svelte").Component<Props, {}, "">;
|
|
27
15
|
type SolveMessages = ReturnType<typeof SolveMessages>;
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import { useFooter } from '../../contexts/footerContext.svelte';
|
|
6
6
|
import ComputeMessagesDialog from '../compute/ComputeMessagesDialog.svelte';
|
|
7
7
|
import FooterItemRenderer from './FooterItemRenderer.svelte';
|
|
8
|
+
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
9
|
+
import { formatCount, formatMessage } from '../../i18n/messages';
|
|
8
10
|
|
|
9
11
|
interface Props {
|
|
10
12
|
errors?: string[];
|
|
@@ -26,6 +28,9 @@
|
|
|
26
28
|
children
|
|
27
29
|
}: Props = $props();
|
|
28
30
|
|
|
31
|
+
const locale = getLocaleContext();
|
|
32
|
+
const t = $derived(locale.messages);
|
|
33
|
+
|
|
29
34
|
let footerStore = (() => {
|
|
30
35
|
try {
|
|
31
36
|
return useFooter();
|
|
@@ -66,16 +71,15 @@
|
|
|
66
71
|
const hasMessages = $derived(totalCount > 0);
|
|
67
72
|
|
|
68
73
|
const copyrightLine = $derived(
|
|
69
|
-
footerText
|
|
70
|
-
? footerText.replace('{name}', copyrightName).replace('{year}', String(_currentYear))
|
|
71
|
-
: `by ${copyrightName} © ${_currentYear}`
|
|
74
|
+
formatMessage(footerText ?? t.copyright, { name: copyrightName, year: _currentYear })
|
|
72
75
|
);
|
|
73
76
|
</script>
|
|
74
77
|
|
|
78
|
+
<!-- One line at any width: the message counts keep their size and the copyright truncates. -->
|
|
75
79
|
<footer
|
|
76
|
-
class="px-4 gap-4 text-xs h-7 flex shrink-0 items-center justify-between border-t border-border bg-background text-muted-foreground select-none"
|
|
80
|
+
class="px-4 gap-4 text-xs h-7 flex shrink-0 items-center justify-between border-t border-border bg-background whitespace-nowrap text-muted-foreground select-none"
|
|
77
81
|
>
|
|
78
|
-
<div class="gap-4 flex items-center">
|
|
82
|
+
<div class="gap-4 flex shrink-0 items-center">
|
|
79
83
|
{#each leftItems as item (item.id)}
|
|
80
84
|
<FooterItemRenderer {item} />
|
|
81
85
|
{/each}
|
|
@@ -90,11 +94,13 @@
|
|
|
90
94
|
: warningCount > 0
|
|
91
95
|
? 'text-warning hover:bg-warning/10'
|
|
92
96
|
: 'hover:bg-muted'}"
|
|
93
|
-
title={
|
|
97
|
+
title={formatCount(totalCount, t.lastSolveMessagesOne, t.lastSolveMessagesOther)}
|
|
94
98
|
>
|
|
95
99
|
{#if errorCount > 0}
|
|
96
100
|
<CircleAlert class="h-3.5 w-3.5" />
|
|
97
|
-
<span class="font-medium"
|
|
101
|
+
<span class="font-medium"
|
|
102
|
+
>{formatCount(errorCount, t.errorCountOne, t.errorCountOther)}</span
|
|
103
|
+
>
|
|
98
104
|
{/if}
|
|
99
105
|
|
|
100
106
|
{#if warningCount > 0}
|
|
@@ -102,12 +108,16 @@
|
|
|
102
108
|
<span class="text-border">•</span>
|
|
103
109
|
{/if}
|
|
104
110
|
<TriangleAlert class="h-3.5 w-3.5" />
|
|
105
|
-
<span class="font-medium"
|
|
111
|
+
<span class="font-medium"
|
|
112
|
+
>{formatCount(warningCount, t.warningCountOne, t.warningCountOther)}</span
|
|
113
|
+
>
|
|
106
114
|
{/if}
|
|
107
115
|
|
|
108
116
|
{#if errorCount === 0 && warningCount === 0}
|
|
109
117
|
<Info class="h-3.5 w-3.5" />
|
|
110
|
-
<span class="font-medium"
|
|
118
|
+
<span class="font-medium"
|
|
119
|
+
>{formatCount(remarkCount, t.noteCountOne, t.noteCountOther)}</span
|
|
120
|
+
>
|
|
111
121
|
{/if}
|
|
112
122
|
</div>
|
|
113
123
|
{/snippet}
|
|
@@ -121,11 +131,11 @@
|
|
|
121
131
|
{/if}
|
|
122
132
|
</div>
|
|
123
133
|
|
|
124
|
-
<div class="gap-4 ml-auto flex items-center">
|
|
134
|
+
<div class="gap-4 min-w-0 ml-auto flex items-center">
|
|
125
135
|
{#each rightItems as item (item.id)}
|
|
126
136
|
<FooterItemRenderer {item} />
|
|
127
137
|
{/each}
|
|
128
138
|
|
|
129
|
-
<p>{copyrightLine}</p>
|
|
139
|
+
<p class="truncate">{copyrightLine}</p>
|
|
130
140
|
</div>
|
|
131
141
|
</footer>
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { onMount } from 'svelte';
|
|
4
4
|
import { Loader, Maximize, Minimize } from '@lucide/svelte';
|
|
5
5
|
import { loadScript } from '../../utils/loadScript';
|
|
6
|
+
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
7
|
+
import { formatMessage } from '../../i18n/messages';
|
|
6
8
|
|
|
7
9
|
interface Props {
|
|
8
10
|
item: OutputChartLayoutItem;
|
|
@@ -11,6 +13,9 @@
|
|
|
11
13
|
|
|
12
14
|
let { item: _item, value }: Props = $props();
|
|
13
15
|
|
|
16
|
+
const locale = getLocaleContext();
|
|
17
|
+
const t = $derived(locale.messages);
|
|
18
|
+
|
|
14
19
|
const MAP_TYPE_SET = new Set(['scattermap', 'scattermapbox', 'choropleth', 'scattergeo']);
|
|
15
20
|
|
|
16
21
|
type PlotlyTrace = Record<string, unknown>;
|
|
@@ -49,11 +54,11 @@
|
|
|
49
54
|
if (!wrapperEl) return;
|
|
50
55
|
if (!document.fullscreenElement) {
|
|
51
56
|
wrapperEl.requestFullscreen().catch((err) => {
|
|
52
|
-
error =
|
|
57
|
+
error = formatMessage(t.fullscreenUnavailable, { error: err.message });
|
|
53
58
|
});
|
|
54
59
|
} else {
|
|
55
60
|
document.exitFullscreen().catch((err) => {
|
|
56
|
-
error =
|
|
61
|
+
error = formatMessage(t.exitFullscreenFailed, { error: err.message });
|
|
57
62
|
});
|
|
58
63
|
}
|
|
59
64
|
}
|
|
@@ -129,7 +134,7 @@
|
|
|
129
134
|
...figData.config
|
|
130
135
|
});
|
|
131
136
|
} catch (e) {
|
|
132
|
-
error = e instanceof Error ? e.message :
|
|
137
|
+
error = e instanceof Error ? e.message : t.chartRenderFailed;
|
|
133
138
|
} finally {
|
|
134
139
|
loading = false;
|
|
135
140
|
}
|
|
@@ -183,10 +188,9 @@
|
|
|
183
188
|
>
|
|
184
189
|
{#if error}
|
|
185
190
|
<div class="px-4 py-3 text-sm text-destructive">{error}</div>
|
|
186
|
-
{:else if !figData && value}
|
|
187
|
-
<div class="px-4 py-8 text-sm text-center text-muted-foreground">Receiving chart data...</div>
|
|
188
191
|
{:else if !figData}
|
|
189
|
-
|
|
192
|
+
<!-- The layout hides this output while it has no data, so this is data that doesn't parse yet. -->
|
|
193
|
+
<div class="px-4 py-8 text-sm text-center text-muted-foreground">{t.chartReceiving}</div>
|
|
190
194
|
{:else}
|
|
191
195
|
<div bind:this={containerEl} class="w-full {isFullscreen ? 'h-screen' : 'h-95'}"></div>
|
|
192
196
|
|
|
@@ -206,7 +210,8 @@
|
|
|
206
210
|
<button
|
|
207
211
|
onclick={toggleFullscreen}
|
|
208
212
|
disabled={loading}
|
|
209
|
-
title={isFullscreen ?
|
|
213
|
+
title={isFullscreen ? t.exitFullscreen : t.fullscreen}
|
|
214
|
+
aria-label={isFullscreen ? t.exitFullscreen : t.fullscreen}
|
|
210
215
|
class="rounded p-1.5 backdrop-blur-sm flex items-center border border-border bg-background/80 text-foreground transition-colors hover:bg-background disabled:cursor-not-allowed disabled:opacity-50"
|
|
211
216
|
>
|
|
212
217
|
{#if isFullscreen}
|
|
@@ -38,6 +38,9 @@
|
|
|
38
38
|
|
|
39
39
|
const visibilityMap = $derived(buildVisibilityMap(items, values));
|
|
40
40
|
|
|
41
|
+
const uid = $props.id();
|
|
42
|
+
const descriptionId = `${uid}-description`;
|
|
43
|
+
|
|
41
44
|
// Below this a label and its control stop fitting side by side, so a wide group
|
|
42
45
|
// sheds columns as its panel narrows instead of squeezing every input.
|
|
43
46
|
const MIN_COLUMN_WIDTH = 160;
|
|
@@ -72,24 +75,36 @@
|
|
|
72
75
|
{@render grid()}
|
|
73
76
|
</div>
|
|
74
77
|
{:else}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
{
|
|
84
|
-
|
|
85
|
-
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
78
|
+
<!-- The host's page owns the h1 and the panel holds the h2. The description sits outside the
|
|
79
|
+
heading's button, so the heading and the button are named by the label alone. -->
|
|
80
|
+
<h3>
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
class="gap-3 px-4 pt-3.5 flex w-full items-start text-left outline-none select-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset {description
|
|
84
|
+
? 'pb-0'
|
|
85
|
+
: 'pb-3.5'}"
|
|
86
|
+
aria-expanded={!collapsed}
|
|
87
|
+
aria-describedby={description ? descriptionId : undefined}
|
|
88
|
+
onclick={onToggle}
|
|
89
|
+
>
|
|
90
|
+
<span class="min-w-0 text-base font-semibold leading-snug flex-1 text-foreground"
|
|
91
|
+
>{label}</span
|
|
92
|
+
>
|
|
93
|
+
<ChevronDown
|
|
94
|
+
class="mt-1 h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200 {collapsed
|
|
95
|
+
? '-rotate-90'
|
|
96
|
+
: ''}"
|
|
97
|
+
/>
|
|
98
|
+
</button>
|
|
99
|
+
</h3>
|
|
100
|
+
{#if description}
|
|
101
|
+
<p
|
|
102
|
+
id={descriptionId}
|
|
103
|
+
class="pl-4 pr-11 pt-0.5 pb-3.5 text-xs leading-snug text-muted-foreground"
|
|
104
|
+
>
|
|
105
|
+
{description}
|
|
106
|
+
</p>
|
|
107
|
+
{/if}
|
|
93
108
|
<div class="content-wrapper" class:collapsed inert={collapsed}>
|
|
94
109
|
<div class="content-inner">
|
|
95
110
|
<div class="px-4 pt-1 pb-5">
|
|
@@ -12,13 +12,15 @@
|
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
14
|
<!-- Every field has one, so a column of them is noise. Where a fine pointer can hover, it shows
|
|
15
|
-
only while the pointer or focus is inside the enclosing `group/field`; touch keeps it visible.
|
|
15
|
+
only while the pointer or focus is inside the enclosing `group/field`; touch keeps it visible.
|
|
16
|
+
The box is 24px, touch gets a 44px hit area, and the negative margin keeps the label row from
|
|
17
|
+
growing. -->
|
|
16
18
|
<Dialog.Root>
|
|
17
19
|
<Dialog.Trigger>
|
|
18
20
|
{#snippet child({ props })}
|
|
19
21
|
<button
|
|
20
22
|
{...props}
|
|
21
|
-
class="-m-1
|
|
23
|
+
class="-m-1 size-6 pointer-coarse:after:-inset-2.5 relative flex shrink-0 cursor-help items-center justify-center rounded-sm text-muted-foreground transition hover:text-foreground pointer-coarse:after:absolute pointer-fine:opacity-0 pointer-fine:group-focus-within/field:opacity-100 pointer-fine:group-hover/field:opacity-100 pointer-fine:focus-visible:opacity-100"
|
|
22
24
|
aria-label={label}
|
|
23
25
|
>
|
|
24
26
|
<HelpCircle class="size-3.5" />
|