Graph page topic selection preset#629
Conversation
| const topicNames = this.currentSelection().map((dt) => dt.name); | ||
| const existing = this.presetService.findByName(name); | ||
| if (existing) { | ||
| if (!confirm(`A preset named "${name}" already exists. Replace its topics?`)) return; |
There was a problem hiding this comment.
Three native confirm() calls (here, line 94, line 138) block the JS thread, break the dialog's dark theme, and aren't testable. The repo already uses PrimeNG's ConfirmationService — see car-command-page/car-command.component.ts:101-108. Suggest swapping all three to confirmationService.confirm({ message, header, acceptLabel, rejectLabel, accept: () => ... }) and adding <p-confirmDialog /> to the dialog template.
| onDownload(preset: Preset): void { | ||
| const exported: PresetSeed = { name: preset.name, topicNames: [...preset.topicNames] }; | ||
| const blob = new Blob([JSON.stringify(exported, null, 2)], { type: 'application/json;charset=utf-8;' }); | ||
| const url = URL.createObjectURL(blob); | ||
|
|
||
| const link = document.createElement('a'); | ||
| link.href = url; | ||
| link.download = `${this.sanitizeFilename(preset.name)}.json`; | ||
| link.click(); | ||
|
|
||
| URL.revokeObjectURL(url); |
There was a problem hiding this comment.
This Blob + createObjectURL + hidden anchor pattern is line-for-line the same as notification-rules-page.component.ts:401-408 (downloadRulesAsCsv). Worth extracting downloadAsFile(filename, content, mimeType) into a utils/file-download.utils.ts and using it from both call sites.
| onFileSelected(event: Event): void { | ||
| const input = event.target as HTMLInputElement; | ||
| const file = input.files?.[0]; | ||
| if (!file) return; | ||
|
|
||
| input.value = ''; | ||
|
|
||
| if (!file.name.toLowerCase().endsWith('.json')) { | ||
| this.messageService.add({ severity: 'error', summary: 'Invalid File', detail: 'Please select a .json file' }); | ||
| return; | ||
| } | ||
|
|
||
| const reader = new FileReader(); | ||
| reader.onload = () => { | ||
| const text = reader.result as string; | ||
| const parsed = this.parsePresetJson(text); | ||
| if (!parsed) return; | ||
|
|
||
| const fallbackName = file.name.replace(/\.json$/i, '').trim() || 'Untitled Preset'; | ||
| const name = parsed.name?.trim() || fallbackName; | ||
|
|
||
| const existing = this.presetService.findByName(name); | ||
| if (existing) { | ||
| if (!confirm(`A preset named "${name}" already exists. Replace its topics?`)) return; | ||
| this.presetService.replacePreset(existing.id, { topicNames: parsed.topicNames }); | ||
| } else { | ||
| this.presetService.addPreset(name, parsed.topicNames); | ||
| } | ||
| this.messageService.add({ | ||
| severity: 'success', | ||
| summary: 'Preset Imported', | ||
| detail: `"${name}" with ${parsed.topicNames.length} topic(s)` | ||
| }); | ||
| }; | ||
| reader.onerror = () => { | ||
| this.messageService.add({ severity: 'error', summary: 'Read Error', detail: 'Failed to read file' }); | ||
| }; | ||
| reader.readAsText(file); |
There was a problem hiding this comment.
Same shape as notification-rules-page.component.ts:182-204 — extension check → reset input.value → FileReader.readAsText with onload/onerror, even down to the same toast wording. Pair with the downloadAsFile extraction above and add a readTextFile(file, allowedExt): Promise<string> helper.
| // silently skipped on Apply (with a warn toast), not hard-errored. | ||
| export const PRESET_SEEDS: PresetSeed[] = [ | ||
| { | ||
| name: 'test preset', |
There was a problem hiding this comment.
'test preset' ships to every fresh user via loadOrSeed(). Replace with real defaults or empty array.
| this.subject.next(this.loadOrSeed()); | ||
| } | ||
|
|
||
| getPresets = (): BehaviorSubject<Preset[]> => this.subject; |
There was a problem hiding this comment.
Returns the writable subject — callers can .next() and bypass save(). Return subject.asObservable() to force callers through the typed mutators.
| if (unknown.length > 0) { | ||
| this.messageService.add({ | ||
| severity: 'warn', | ||
| summary: 'Unknown Topics Skipped', | ||
| detail: unknown.join(', '), | ||
| life: 8000 | ||
| }); | ||
| } |
There was a problem hiding this comment.
Same warn toast lives in three files (here, graph-sidebar-mobile.component.ts, and preset-dialog.component.ts). Move into the service or a helper.
Changes
Adds a client-side topic-selection preset system to the graph page so engineers can save and reapply named topic groupings without having to reselect each session. New GraphPresetService persists presets in localStorage. New PresetDialogComponent provides save selection, apply, upload, download, delete, and restore defaults actions. It opens from both graph sidebars via DialogService. The graph sidebar gets a presets row above the topic tree containing a select dropdown for fast applying and a manage button, with the dropdown's defaultValue bound to activePresetName so it tracks the live selection.
Notes
Closes #566