@bardioc/create-bardioc-app 0.7.0 → 0.7.1
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/package.json +2 -2
- package/templates/_base/.changeset/README.md +4 -6
- package/templates/_base/.claude/commands/changeset-app.md +11 -12
- package/templates/_base/.claude/commands/refresh-bundle.md +1 -1
- package/templates/_base/AGENTS.md +128 -0
- package/templates/_base/CLAUDE.md +1 -0
- package/templates/_base/README.md +11 -17
- package/templates/_opt-link/scripts/link-source.mjs +1 -1
- package/templates/_opt-pipeline/bitbucket-pipelines.yml +12 -56
- package/templates/angular/package.json +1 -1
- package/templates/nextjs/package.json +1 -1
- package/templates/preact/package.json +1 -1
- package/templates/solid/package.json +1 -1
- package/templates/svelte/package.json +1 -1
- package/templates/vite/CLAUDE.md +149 -135
- package/templates/vite/package.json +3 -3
- package/templates/vite/src/App.tsx +6 -3
- package/templates/vite/src/auth/onboarding.tsx +19 -23
- package/templates/vite/src/climate/climate-data.ts +19 -0
- package/templates/vite/src/climate/conditions-chart.tsx +11 -6
- package/templates/vite/src/climate/location-card.tsx +2 -5
- package/templates/vite/src/climate/location-select.tsx +7 -2
- package/templates/vite/src/climate/metrics-grid.tsx +6 -1
- package/templates/vite/src/climate/overview-view.tsx +26 -7
- package/templates/vite/src/climate/stations-view.tsx +73 -9
- package/templates/vite/src/climate/types.ts +5 -5
- package/templates/vite/src/dashboard-provider.tsx +15 -5
- package/templates/vite/src/events/live-events-view.tsx +67 -37
- package/templates/vite/src/events/mock-events.ts +33 -0
- package/templates/vite/src/graph/explorer-view.tsx +81 -63
- package/templates/vite/src/i18n.tsx +6 -2
- package/templates/vite/src/index.css +0 -6
- package/templates/vite/src/locales/de.json +55 -51
- package/templates/vite/src/locales/en.json +44 -40
- package/templates/vite/src/preview-state.tsx +29 -0
- package/templates/vite/src/profile/profile-view.tsx +43 -25
- package/templates/vite/src/shell/about-dialog.tsx +6 -5
- package/templates/vite/src/shell/app-shell.tsx +46 -45
- package/templates/vite/src/shell/debug-menu.tsx +86 -0
- package/templates/vite/src/shell/header.tsx +6 -4
- package/templates/vite/src/shell/view-content.tsx +69 -0
- package/templates/vue/package.json +1 -1
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { isInsideIframe } from '@bardioc/app-sdk/dev';
|
|
2
2
|
import { useSdk } from '@bardioc/app-sdk/react';
|
|
3
|
-
import { Badge, Button, Card } from '@bardioc/ui/components';
|
|
3
|
+
import { Badge, Button, Card, Tabs } from '@bardioc/ui/components';
|
|
4
|
+
import { useElementWidth } from '@bardioc/ui/hooks';
|
|
5
|
+
import { cn } from '@bardioc/ui/lib';
|
|
4
6
|
import { useState } from 'react';
|
|
5
7
|
import { useTranslation } from '../i18n';
|
|
6
8
|
import { useAppNotify } from '../use-app-notify';
|
|
7
9
|
import { SDK_REQUESTS, type SdkRequest } from './requests';
|
|
8
10
|
|
|
11
|
+
// Matches the `@3xl` container step: below it the request list moves above the panel.
|
|
12
|
+
const VERTICAL_TABS_MIN_WIDTH = 768;
|
|
13
|
+
|
|
9
14
|
type Result =
|
|
10
15
|
| { status: 'idle' }
|
|
11
16
|
| { status: 'running' }
|
|
@@ -17,6 +22,8 @@ export function ExplorerView() {
|
|
|
17
22
|
const notify = useAppNotify();
|
|
18
23
|
const [activeId, setActiveId] = useState(SDK_REQUESTS[0].id);
|
|
19
24
|
const [result, setResult] = useState<Result>({ status: 'idle' });
|
|
25
|
+
// Orientation drives Radix's arrow keys and aria-orientation, so it is measured, not CSS-only.
|
|
26
|
+
const [explorerRef, narrow] = useElementWidth<HTMLDivElement>(VERTICAL_TABS_MIN_WIDTH);
|
|
20
27
|
|
|
21
28
|
const active = SDK_REQUESTS.find(request => request.id === activeId) ?? SDK_REQUESTS[0];
|
|
22
29
|
|
|
@@ -39,73 +46,84 @@ export function ExplorerView() {
|
|
|
39
46
|
}
|
|
40
47
|
}
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
// One panel for every tab: only the active tab's content is mounted, and it reads `active`.
|
|
50
|
+
const panel = (
|
|
51
|
+
<Card
|
|
52
|
+
className="min-h-0 min-w-0 flex-1"
|
|
53
|
+
contentClassName="flex min-h-0 min-w-0 flex-1 flex-col gap-2"
|
|
54
|
+
>
|
|
55
|
+
<div className="flex min-w-0 items-center justify-between gap-2">
|
|
56
|
+
<span className="text-muted-foreground truncate font-mono text-xs">{active.label}</span>
|
|
57
|
+
<Button
|
|
58
|
+
size="sm"
|
|
59
|
+
onClick={() => void run(active)}
|
|
60
|
+
isLoading={result.status === 'running'}
|
|
61
|
+
stableWidth
|
|
62
|
+
>
|
|
63
|
+
{t('explorer.run', { fallback: 'Run Request' })}
|
|
64
|
+
</Button>
|
|
65
|
+
</div>
|
|
45
66
|
|
|
46
|
-
|
|
47
|
-
<div className="flex
|
|
48
|
-
{
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
fallback: request.category,
|
|
62
|
-
})}
|
|
63
|
-
</Badge>
|
|
64
|
-
</button>
|
|
65
|
-
))}
|
|
67
|
+
{result.status === 'done' && (
|
|
68
|
+
<div className="flex items-center gap-2 text-xs">
|
|
69
|
+
<Badge variant={result.live ? 'success' : 'warning'}>
|
|
70
|
+
{result.live
|
|
71
|
+
? t('explorer.live', { fallback: 'Live' })
|
|
72
|
+
: t('explorer.mock', { fallback: 'Simulated' })}
|
|
73
|
+
</Badge>
|
|
74
|
+
<span className="text-muted-foreground">
|
|
75
|
+
{t('explorer.durationMs', { fallback: '{ms} ms', params: { ms: result.ms } })}
|
|
76
|
+
</span>
|
|
77
|
+
{!isInsideIframe() && (
|
|
78
|
+
<span className="text-muted-foreground">
|
|
79
|
+
· {t('explorer.standalone', { fallback: 'Standalone' })}
|
|
80
|
+
</span>
|
|
81
|
+
)}
|
|
66
82
|
</div>
|
|
83
|
+
)}
|
|
67
84
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
disabled={result.status === 'running'}
|
|
78
|
-
>
|
|
79
|
-
{result.status === 'running'
|
|
80
|
-
? t('explorer.running', { fallback: 'Running…' })
|
|
81
|
-
: t('explorer.run', { fallback: 'Run Request' })}
|
|
82
|
-
</Button>
|
|
83
|
-
</div>
|
|
85
|
+
<pre className="bg-muted/40 min-h-40 flex-1 overflow-auto rounded-md p-3 text-xs">
|
|
86
|
+
{result.status === 'done'
|
|
87
|
+
? JSON.stringify(result.data, null, 2)
|
|
88
|
+
: t('explorer.empty', {
|
|
89
|
+
fallback: 'Select a request and run it to see the response.',
|
|
90
|
+
})}
|
|
91
|
+
</pre>
|
|
92
|
+
</Card>
|
|
93
|
+
);
|
|
84
94
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
{t('explorer.durationMs', { fallback: '{ms} ms', params: { ms: result.ms } })}
|
|
94
|
-
</span>
|
|
95
|
-
{!isInsideIframe() && <span className="text-muted-foreground">· standalone</span>}
|
|
96
|
-
</div>
|
|
97
|
-
)}
|
|
95
|
+
return (
|
|
96
|
+
<div ref={explorerRef} className="flex min-w-0 flex-1 flex-col gap-3">
|
|
97
|
+
<p className="text-muted-foreground text-xs">
|
|
98
|
+
{t('explorer.description', {
|
|
99
|
+
fallback:
|
|
100
|
+
'Run real Bardioc SDK transport requests. Without a session, each request falls back to simulated data so you can still explore the shape.',
|
|
101
|
+
})}
|
|
102
|
+
</p>
|
|
98
103
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
<Tabs
|
|
105
|
+
orientation={narrow ? 'horizontal' : 'vertical'}
|
|
106
|
+
value={activeId}
|
|
107
|
+
onValueChange={setActiveId}
|
|
108
|
+
className="min-h-0 flex-1"
|
|
109
|
+
contentWithGap
|
|
110
|
+
listClassName={cn(!narrow && 'w-60')}
|
|
111
|
+
// `max-w-60` keeps a horizontal tab as wide as the vertical list; longer labels truncate.
|
|
112
|
+
triggerClassName={cn('font-mono', narrow && 'max-w-60')}
|
|
113
|
+
contentClassName="flex flex-col"
|
|
114
|
+
items={SDK_REQUESTS.map(request => ({
|
|
115
|
+
value: request.id,
|
|
116
|
+
label: request.label,
|
|
117
|
+
badge: (
|
|
118
|
+
<Badge variant={request.category === 'graph' ? 'info' : 'secondary'} numerical>
|
|
119
|
+
{t(`explorer.category${request.category === 'graph' ? 'Graph' : 'Os'}`, {
|
|
120
|
+
fallback: request.category,
|
|
121
|
+
})}
|
|
122
|
+
</Badge>
|
|
123
|
+
),
|
|
124
|
+
content: panel,
|
|
125
|
+
}))}
|
|
126
|
+
/>
|
|
109
127
|
</div>
|
|
110
128
|
);
|
|
111
129
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { useOsConfig } from '@bardioc/app-sdk/react';
|
|
2
2
|
import {
|
|
3
3
|
I18nProvider as BaseI18nProvider,
|
|
4
|
+
mergeTranslations,
|
|
4
5
|
useI18n,
|
|
5
6
|
type I18nConfig,
|
|
6
7
|
type TranslationMessages,
|
|
7
8
|
} from '@bardioc/i18n-core';
|
|
9
|
+
import { deMessages as uiDeMessages, enMessages as uiEnMessages } from '@bardioc/ui/i18n';
|
|
8
10
|
import { useEffect, useMemo, type ReactNode } from 'react';
|
|
9
11
|
import deMessages from './locales/de.json';
|
|
10
12
|
import enMessages from './locales/en.json';
|
|
@@ -12,9 +14,11 @@ import enMessages from './locales/en.json';
|
|
|
12
14
|
export const SUPPORTED_LOCALES = ['en', 'de'] as const;
|
|
13
15
|
export type Locale = (typeof SUPPORTED_LOCALES)[number];
|
|
14
16
|
|
|
17
|
+
// @bardioc/ui ships the strings of its own components (table pager, search, language switcher…);
|
|
18
|
+
// the app's catalog is laid over them, so the app only carries its own keys and can override any.
|
|
15
19
|
const DEFAULT_MESSAGES: Record<Locale, TranslationMessages> = {
|
|
16
|
-
en: enMessages,
|
|
17
|
-
de: deMessages,
|
|
20
|
+
en: mergeTranslations(uiEnMessages, enMessages),
|
|
21
|
+
de: mergeTranslations(uiDeMessages, deMessages),
|
|
18
22
|
};
|
|
19
23
|
|
|
20
24
|
const STORAGE_KEY = '{{APP_NAME}}.locale';
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"app": { "title": "Wetter-Dashboard"
|
|
2
|
+
"app": { "title": "Wetter-Dashboard" },
|
|
3
|
+
"common": {
|
|
4
|
+
"filter": {
|
|
5
|
+
"filter": "Filter",
|
|
6
|
+
"activeFilter": { "_one": "{count} Filter", "_other": "{count} Filter" }
|
|
7
|
+
}
|
|
8
|
+
},
|
|
3
9
|
"nav": {
|
|
4
10
|
"overview": "Übersicht",
|
|
5
11
|
"stations": "Stationen",
|
|
6
12
|
"explorer": "Graph-Explorer",
|
|
7
13
|
"events": "Live-Ereignisse",
|
|
8
|
-
"profile": "Profil"
|
|
14
|
+
"profile": "Profil",
|
|
15
|
+
"search": "Ansichten suchen…"
|
|
9
16
|
},
|
|
10
17
|
"menu": {
|
|
11
18
|
"view": "Ansicht",
|
|
@@ -16,49 +23,34 @@
|
|
|
16
23
|
"version": "Version",
|
|
17
24
|
"id": "App-ID",
|
|
18
25
|
"description": "Beschreibung",
|
|
19
|
-
"error": "app-manifest.json konnte nicht gelesen werden",
|
|
20
|
-
"source": "Live aus public/app-manifest.json gelesen"
|
|
21
|
-
},
|
|
22
|
-
"genericTable": {
|
|
23
|
-
"filterPlaceholder": "Ergebnisse filtern…",
|
|
24
|
-
"itemLabel": "Zeilen",
|
|
25
|
-
"showing": "{from} bis {to} von {total}",
|
|
26
|
-
"columns": "Spalten",
|
|
27
|
-
"noResults": "Keine Ergebnisse gefunden.",
|
|
28
|
-
"goToPreviousPage": "Zur vorherigen Seite",
|
|
29
|
-
"goToNextPage": "Zur nächsten Seite"
|
|
26
|
+
"error": "app-manifest.json konnte nicht gelesen werden.",
|
|
27
|
+
"source": "Live aus public/app-manifest.json gelesen."
|
|
30
28
|
},
|
|
31
|
-
"common": { "button": { "previous": "Zurück", "next": "Weiter" } },
|
|
32
29
|
"header": {
|
|
33
|
-
"search": "Stationen suchen…",
|
|
34
30
|
"sync": "Sync",
|
|
35
|
-
"syncing": "Synchronisiere…",
|
|
36
31
|
"location": "Station",
|
|
37
|
-
"savedAt": "Gespeichert {time}"
|
|
38
|
-
"neverSaved": "Noch nicht gespeichert"
|
|
32
|
+
"savedAt": "Gespeichert {time}"
|
|
39
33
|
},
|
|
40
34
|
"auth": {
|
|
41
35
|
"title": "Mit Bardioc verbinden",
|
|
42
36
|
"description": "Dieses Dashboard liest Live-Daten über den Bardioc-SDK-Transport. Es läuft in Bardioc oder eigenständig mit Dev-Auth.",
|
|
43
|
-
"stepsTitle": "Eigenständig mit Dev-Auth ausführen
|
|
44
|
-
"step1": "Setze DEV_SECRET=<dein-secret> und API_BASE_URL=<dein-host> in .env",
|
|
45
|
-
"step2": "Dev-Server starten
|
|
37
|
+
"stepsTitle": "Eigenständig mit Dev-Auth ausführen",
|
|
38
|
+
"step1": "Setze DEV_SECRET=<dein-secret> und API_BASE_URL=<dein-host> in .env.",
|
|
39
|
+
"step2": "Dev-Server starten.",
|
|
46
40
|
"detectedTitle": "Erkannte Umgebung",
|
|
47
41
|
"insideHost": "Im Host-Iframe",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"no": "nein",
|
|
42
|
+
"yes": "Ja",
|
|
43
|
+
"no": "Nein",
|
|
51
44
|
"retry": "Neu laden"
|
|
52
45
|
},
|
|
53
46
|
"overview": {
|
|
54
|
-
"operator": "Betreiber",
|
|
55
|
-
"noOperator": "Betreiber nicht verfügbar",
|
|
56
47
|
"updated": "Aktualisiert {time}",
|
|
57
|
-
"persistence": "In IndexedDB gespeichert",
|
|
58
|
-
"notPersisted": "Vorschau — nicht gespeichert (in Bardioc öffnen zum Speichern)",
|
|
48
|
+
"persistence": "In IndexedDB gespeichert.",
|
|
49
|
+
"notPersisted": "Vorschau — nicht gespeichert (in Bardioc öffnen zum Speichern).",
|
|
59
50
|
"historyOne": "{count} Snapshot im Verlauf",
|
|
60
51
|
"historyOther": "{count} Snapshots im Verlauf",
|
|
61
|
-
"
|
|
52
|
+
"emptyTitle": "Keine Stationsdaten",
|
|
53
|
+
"emptyDescription": "Stationen erscheinen hier, sobald sie Daten melden."
|
|
62
54
|
},
|
|
63
55
|
"conditions": { "sunny": "Sonnig", "cloudy": "Bewölkt", "rainy": "Regnerisch" },
|
|
64
56
|
"source": { "graph": "Bardioc-Graph", "mock": "Simuliert" },
|
|
@@ -74,7 +66,7 @@
|
|
|
74
66
|
"windSpeedHint": "Anhaltend",
|
|
75
67
|
"vegetationHint": "Oberflächendeckung"
|
|
76
68
|
},
|
|
77
|
-
"chart": { "title": "7-Tage-Ausblick", "description": "Sonnenstunden nach Wetterlage" },
|
|
69
|
+
"chart": { "title": "7-Tage-Ausblick", "description": "Sonnenstunden nach Wetterlage." },
|
|
78
70
|
"stations": {
|
|
79
71
|
"title": "Messstationen",
|
|
80
72
|
"description": "Alle Stationen dieser Instanz. Suchen, filtern oder auswählen.",
|
|
@@ -86,40 +78,34 @@
|
|
|
86
78
|
"colTemp": "Temp",
|
|
87
79
|
"colHumidity": "Feuchte",
|
|
88
80
|
"colVegetation": "Vegetation",
|
|
81
|
+
"select": "Station auswählen",
|
|
82
|
+
"current": "Aktuelle Station",
|
|
89
83
|
"itemLabel": "Stationen",
|
|
90
|
-
"empty": "Keine Stationen passen zu den Filtern"
|
|
91
|
-
"apply": "Anwenden",
|
|
92
|
-
"clear": "Zurücksetzen"
|
|
84
|
+
"empty": "Keine Stationen passen zu den Filtern."
|
|
93
85
|
},
|
|
94
86
|
"explorer": {
|
|
95
87
|
"title": "Graph-Explorer",
|
|
96
88
|
"description": "Führe echte Bardioc-SDK-Transport-Anfragen aus. Ohne Session greift jede Anfrage auf simulierte Daten zurück.",
|
|
97
89
|
"run": "Anfrage ausführen",
|
|
98
|
-
"
|
|
99
|
-
"empty": "Wähle eine Anfrage und führe sie aus, um die Antwort zu sehen",
|
|
90
|
+
"empty": "Wähle eine Anfrage und führe sie aus, um die Antwort zu sehen.",
|
|
100
91
|
"live": "Live",
|
|
101
92
|
"mock": "Simuliert",
|
|
102
93
|
"categoryGraph": "Graph",
|
|
103
94
|
"categoryOs": "OS",
|
|
104
|
-
"
|
|
105
|
-
"
|
|
95
|
+
"durationMs": "{ms} ms",
|
|
96
|
+
"standalone": "Eigenständig"
|
|
106
97
|
},
|
|
107
98
|
"profile": {
|
|
108
99
|
"title": "Betreiberprofil",
|
|
109
100
|
"description": "Live aus dem OS-Transport geladen (os.profile).",
|
|
110
|
-
"name": "Name",
|
|
111
101
|
"displayName": "Anzeigename",
|
|
112
102
|
"firstName": "Vorname",
|
|
113
103
|
"lastName": "Nachname",
|
|
114
|
-
"email": "E-Mail",
|
|
115
|
-
"id": "ID",
|
|
116
|
-
"organization": "Organisation",
|
|
117
|
-
"units": "Einheiten",
|
|
118
|
-
"members": "Mitglieder",
|
|
119
|
-
"raw": "Rohantwort",
|
|
120
104
|
"errorTitle": "Profil konnte nicht geladen werden",
|
|
121
105
|
"errorHint": "Der Transport meldete einen Fehler. Autorisiere eine Session und lade neu.",
|
|
122
|
-
"unknown": "Unbekannt"
|
|
106
|
+
"unknown": "Unbekannt",
|
|
107
|
+
"emptyTitle": "Keine Profildaten",
|
|
108
|
+
"emptyDescription": "Das OS hat für diese Sitzung kein Profil geliefert."
|
|
123
109
|
},
|
|
124
110
|
"events": {
|
|
125
111
|
"title": "Live-Ereignisse",
|
|
@@ -128,12 +114,30 @@
|
|
|
128
114
|
"emptyHost": "Noch keine Graph-Ereignisse — Aktivität erscheint hier, sobald sie auftritt.",
|
|
129
115
|
"emptyStandalone": "In Bardioc öffnen (oder eigenständig mit Dev-Auth ausführen), um Live-Ereignisse zu empfangen."
|
|
130
116
|
},
|
|
117
|
+
"debug": {
|
|
118
|
+
"menu": "Debug",
|
|
119
|
+
"menuWithState": "Debug: {state}",
|
|
120
|
+
"showInfo": "Debug-Infos anzeigen",
|
|
121
|
+
"previewState": "Vorschauzustand",
|
|
122
|
+
"state": {
|
|
123
|
+
"live": "Live",
|
|
124
|
+
"mock": "Testdaten",
|
|
125
|
+
"loading": "Laden",
|
|
126
|
+
"empty": "Leer",
|
|
127
|
+
"error": "Fehler"
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"preview": {
|
|
131
|
+
"errorTitle": "Daten konnten nicht geladen werden",
|
|
132
|
+
"errorDescription": "Ein simulierter Fehler aus dem Vorschauzustand im Debug-Menü.",
|
|
133
|
+
"debugView": "Ansicht",
|
|
134
|
+
"retry": "Erneut versuchen"
|
|
135
|
+
},
|
|
131
136
|
"notify": {
|
|
132
|
-
"synced": "{count} Stationen synchronisiert",
|
|
133
|
-
"
|
|
134
|
-
"
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
"queryFailed": "Anfrage fehlgeschlagen — zeige simulierte Daten"
|
|
137
|
+
"synced": "{count} Stationen synchronisiert.",
|
|
138
|
+
"stationSelected": "Aktive Station: {name}.",
|
|
139
|
+
"localeChanged": "Sprache gewechselt zu {locale}.",
|
|
140
|
+
"queryOk": "Anfrage OK ({ms} ms).",
|
|
141
|
+
"queryFailed": "Anfrage fehlgeschlagen — zeige simulierte Daten."
|
|
138
142
|
}
|
|
139
143
|
}
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"app": { "title": "Weather Dashboard"
|
|
2
|
+
"app": { "title": "Weather Dashboard" },
|
|
3
|
+
"common": {
|
|
4
|
+
"filter": {
|
|
5
|
+
"filter": "Filter",
|
|
6
|
+
"activeFilter": { "_one": "{count} Filter", "_other": "{count} Filters" }
|
|
7
|
+
}
|
|
8
|
+
},
|
|
3
9
|
"nav": {
|
|
4
10
|
"overview": "Overview",
|
|
5
11
|
"stations": "Stations",
|
|
6
12
|
"explorer": "Graph Explorer",
|
|
7
13
|
"events": "Live Events",
|
|
8
|
-
"profile": "Profile"
|
|
14
|
+
"profile": "Profile",
|
|
15
|
+
"search": "Search Views…"
|
|
9
16
|
},
|
|
10
17
|
"menu": {
|
|
11
18
|
"view": "View",
|
|
@@ -19,46 +26,31 @@
|
|
|
19
26
|
"error": "Could not read app-manifest.json.",
|
|
20
27
|
"source": "Read live from public/app-manifest.json."
|
|
21
28
|
},
|
|
22
|
-
"genericTable": {
|
|
23
|
-
"filterPlaceholder": "Filter Results…",
|
|
24
|
-
"itemLabel": "rows",
|
|
25
|
-
"showing": "{from} to {to} of {total}",
|
|
26
|
-
"columns": "Columns",
|
|
27
|
-
"noResults": "No results found.",
|
|
28
|
-
"goToPreviousPage": "Go to Previous Page",
|
|
29
|
-
"goToNextPage": "Go to Next Page"
|
|
30
|
-
},
|
|
31
|
-
"common": { "button": { "previous": "Previous", "next": "Next" } },
|
|
32
29
|
"header": {
|
|
33
|
-
"search": "Search Stations…",
|
|
34
30
|
"sync": "Sync",
|
|
35
|
-
"syncing": "Syncing…",
|
|
36
31
|
"location": "Station",
|
|
37
|
-
"savedAt": "Saved {time}"
|
|
38
|
-
"neverSaved": "Not Yet Saved"
|
|
32
|
+
"savedAt": "Saved {time}"
|
|
39
33
|
},
|
|
40
34
|
"auth": {
|
|
41
35
|
"title": "Connect to Bardioc",
|
|
42
36
|
"description": "This dashboard reads live data through the Bardioc SDK transport. It runs when opened inside Bardioc, or standalone with dev auth.",
|
|
43
|
-
"stepsTitle": "Run
|
|
37
|
+
"stepsTitle": "Run Standalone with Dev Auth",
|
|
44
38
|
"step1": "Set DEV_SECRET=<your-secret> and API_BASE_URL=<your-host> in the .env file.",
|
|
45
|
-
"step2": "Start the dev server
|
|
39
|
+
"step2": "Start the dev server.",
|
|
46
40
|
"detectedTitle": "Detected Environment",
|
|
47
41
|
"insideHost": "Inside Host Iframe",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"no": "no",
|
|
42
|
+
"yes": "Yes",
|
|
43
|
+
"no": "No",
|
|
51
44
|
"retry": "Reload"
|
|
52
45
|
},
|
|
53
46
|
"overview": {
|
|
54
|
-
"operator": "Operator",
|
|
55
|
-
"noOperator": "Operator Unavailable",
|
|
56
47
|
"updated": "Updated {time}",
|
|
57
48
|
"persistence": "Persisted to IndexedDB",
|
|
58
|
-
"notPersisted": "Preview — not persisted (open inside Bardioc to persist)",
|
|
49
|
+
"notPersisted": "Preview — not persisted (open inside Bardioc to persist).",
|
|
59
50
|
"historyOne": "{count} snapshot in history",
|
|
60
51
|
"historyOther": "{count} snapshots in history",
|
|
61
|
-
"
|
|
52
|
+
"emptyTitle": "No Station Data",
|
|
53
|
+
"emptyDescription": "Stations appear here once they report."
|
|
62
54
|
},
|
|
63
55
|
"conditions": { "sunny": "Sunny", "cloudy": "Cloudy", "rainy": "Rainy" },
|
|
64
56
|
"source": { "graph": "Bardioc Graph", "mock": "Simulated" },
|
|
@@ -86,40 +78,34 @@
|
|
|
86
78
|
"colTemp": "Temp",
|
|
87
79
|
"colHumidity": "Humidity",
|
|
88
80
|
"colVegetation": "Vegetation",
|
|
81
|
+
"select": "Select Station",
|
|
82
|
+
"current": "Current Station",
|
|
89
83
|
"itemLabel": "stations",
|
|
90
|
-
"empty": "No stations match your filters."
|
|
91
|
-
"apply": "Apply",
|
|
92
|
-
"clear": "Clear"
|
|
84
|
+
"empty": "No stations match your filters."
|
|
93
85
|
},
|
|
94
86
|
"explorer": {
|
|
95
87
|
"title": "Graph Explorer",
|
|
96
88
|
"description": "Run real Bardioc SDK transport requests. Without a session, each request falls back to simulated data so you can still explore the shape.",
|
|
97
89
|
"run": "Run Request",
|
|
98
|
-
"running": "Running…",
|
|
99
90
|
"empty": "Select a request and run it to see the response.",
|
|
100
91
|
"live": "Live",
|
|
101
92
|
"mock": "Simulated",
|
|
102
93
|
"categoryGraph": "Graph",
|
|
103
94
|
"categoryOs": "OS",
|
|
104
|
-
"
|
|
105
|
-
"
|
|
95
|
+
"durationMs": "{ms} ms",
|
|
96
|
+
"standalone": "Standalone"
|
|
106
97
|
},
|
|
107
98
|
"profile": {
|
|
108
99
|
"title": "Operator Profile",
|
|
109
100
|
"description": "Pulled live from the OS transport (os.profile).",
|
|
110
|
-
"name": "Name",
|
|
111
101
|
"displayName": "Display Name",
|
|
112
102
|
"firstName": "First Name",
|
|
113
103
|
"lastName": "Last Name",
|
|
114
|
-
"email": "Email",
|
|
115
|
-
"id": "ID",
|
|
116
|
-
"organization": "Organization",
|
|
117
|
-
"units": "Units",
|
|
118
|
-
"members": "Members",
|
|
119
|
-
"raw": "Raw Response",
|
|
120
104
|
"errorTitle": "Could Not Load Profile",
|
|
121
105
|
"errorHint": "The transport returned an error. Authorize a session, then reload.",
|
|
122
|
-
"unknown": "Unknown"
|
|
106
|
+
"unknown": "Unknown",
|
|
107
|
+
"emptyTitle": "No Profile Data",
|
|
108
|
+
"emptyDescription": "The OS returned no profile for this session."
|
|
123
109
|
},
|
|
124
110
|
"events": {
|
|
125
111
|
"title": "Live Events",
|
|
@@ -128,9 +114,27 @@
|
|
|
128
114
|
"emptyHost": "No graph events yet — activity will appear here as it happens.",
|
|
129
115
|
"emptyStandalone": "Open inside Bardioc (or run standalone with dev auth) to receive live events."
|
|
130
116
|
},
|
|
117
|
+
"debug": {
|
|
118
|
+
"menu": "Debug",
|
|
119
|
+
"menuWithState": "Debug: {state}",
|
|
120
|
+
"showInfo": "Show Debug Info",
|
|
121
|
+
"previewState": "Preview State",
|
|
122
|
+
"state": {
|
|
123
|
+
"live": "Live",
|
|
124
|
+
"mock": "Mock Data",
|
|
125
|
+
"loading": "Loading",
|
|
126
|
+
"empty": "Empty",
|
|
127
|
+
"error": "Error"
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"preview": {
|
|
131
|
+
"errorTitle": "Could Not Load Data",
|
|
132
|
+
"errorDescription": "A simulated error from the preview state in the debug menu.",
|
|
133
|
+
"debugView": "View",
|
|
134
|
+
"retry": "Retry"
|
|
135
|
+
},
|
|
131
136
|
"notify": {
|
|
132
137
|
"synced": "Synced {count} stations.",
|
|
133
|
-
"saved": "Saved to IndexedDB.",
|
|
134
138
|
"stationSelected": "Active station: {name}.",
|
|
135
139
|
"localeChanged": "Language switched to {locale}.",
|
|
136
140
|
"queryOk": "Request OK ({ms} ms).",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo, useState, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
// Development aid: forces every view into one of its states, so loaders, empty states and error
|
|
4
|
+
// states can be checked without breaking the backend, and fills live-only views (Live Events) with
|
|
5
|
+
// sample data. Set from the sidebar's debug menu.
|
|
6
|
+
export const PREVIEW_STATES = ['live', 'mock', 'loading', 'empty', 'error'] as const;
|
|
7
|
+
export type PreviewState = (typeof PREVIEW_STATES)[number];
|
|
8
|
+
|
|
9
|
+
interface PreviewStateValue {
|
|
10
|
+
previewState: PreviewState;
|
|
11
|
+
setPreviewState: (state: PreviewState) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const PreviewStateContext = createContext<PreviewStateValue | null>(null);
|
|
15
|
+
|
|
16
|
+
export function PreviewStateProvider({ children }: Readonly<{ children: ReactNode }>) {
|
|
17
|
+
const [previewState, setPreviewState] = useState<PreviewState>('live');
|
|
18
|
+
const value = useMemo(() => ({ previewState, setPreviewState }), [previewState]);
|
|
19
|
+
|
|
20
|
+
return <PreviewStateContext.Provider value={value}>{children}</PreviewStateContext.Provider>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function usePreviewState(): PreviewStateValue {
|
|
24
|
+
const context = useContext(PreviewStateContext);
|
|
25
|
+
if (!context) {
|
|
26
|
+
throw new Error('usePreviewState must be used within PreviewStateProvider');
|
|
27
|
+
}
|
|
28
|
+
return context;
|
|
29
|
+
}
|