@bardioc/create-bardioc-app 0.6.1 → 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/README.md +5 -1
- 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 +3 -3
- package/templates/_base/AGENTS.md +128 -0
- package/templates/_base/CLAUDE.md +1 -0
- package/templates/_base/README.md +17 -20
- package/templates/_base/scripts/audit.mjs +50 -0
- package/templates/_base/scripts/compliance-summary.mjs +42 -0
- package/templates/_base/scripts/compliance.mjs +64 -0
- package/templates/_base/scripts/stamp-manifest.mjs +10 -4
- package/templates/_opt-link/scripts/link-source.mjs +1 -1
- package/templates/_opt-pipeline/bitbucket-pipelines.yml +12 -56
- package/templates/angular/_gitignore +6 -0
- package/templates/angular/package.json +8 -2
- package/templates/nextjs/_gitignore +6 -0
- package/templates/nextjs/package.json +8 -2
- package/templates/preact/_gitignore +6 -0
- package/templates/preact/package.json +8 -2
- package/templates/solid/_gitignore +6 -0
- package/templates/solid/package.json +8 -2
- package/templates/svelte/_gitignore +6 -0
- package/templates/svelte/package.json +8 -2
- package/templates/vite/CLAUDE.md +149 -135
- package/templates/vite/_gitignore +6 -0
- package/templates/vite/package.json +10 -4
- 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/_gitignore +6 -0
- package/templates/vue/package.json +8 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Avatar, Card, Empty } from '@bardioc/ui/components';
|
|
2
|
-
import { UserX } from 'lucide-react';
|
|
1
|
+
import { Avatar, Card, Empty, SkeletonWrapper } from '@bardioc/ui/components';
|
|
2
|
+
import { UserRound, UserX } from 'lucide-react';
|
|
3
3
|
import { useDashboard } from '../dashboard-provider';
|
|
4
4
|
import { useTranslation } from '../i18n';
|
|
5
5
|
import { operatorLabel } from './use-profile';
|
|
@@ -22,17 +22,31 @@ export function ProfileView() {
|
|
|
22
22
|
const { profile } = useDashboard();
|
|
23
23
|
const unknown = t('profile.unknown', { fallback: 'Unknown' });
|
|
24
24
|
|
|
25
|
-
if (profile.loading) {
|
|
26
|
-
return
|
|
25
|
+
if (!profile.loading && !profile.error && !profile.profile) {
|
|
26
|
+
return (
|
|
27
|
+
<Empty
|
|
28
|
+
icon={UserRound}
|
|
29
|
+
iconVariant="chip"
|
|
30
|
+
title={t('profile.emptyTitle', { fallback: 'No Profile Data' })}
|
|
31
|
+
description={t('profile.emptyDescription', {
|
|
32
|
+
fallback: 'The OS returned no profile for this session.',
|
|
33
|
+
})}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
27
36
|
}
|
|
28
37
|
|
|
29
|
-
if (profile.
|
|
38
|
+
if (!profile.loading && profile.error) {
|
|
30
39
|
return (
|
|
31
40
|
<Empty
|
|
32
41
|
icon={UserX}
|
|
33
42
|
iconVariant="chip"
|
|
34
43
|
title={t('profile.errorTitle', { fallback: 'Could Not Load Profile' })}
|
|
35
|
-
description={
|
|
44
|
+
description={
|
|
45
|
+
profile.error ??
|
|
46
|
+
t('profile.errorHint', {
|
|
47
|
+
fallback: 'The transport returned an error. Authorize a session, then reload.',
|
|
48
|
+
})
|
|
49
|
+
}
|
|
36
50
|
/>
|
|
37
51
|
);
|
|
38
52
|
}
|
|
@@ -42,27 +56,31 @@ export function ProfileView() {
|
|
|
42
56
|
|
|
43
57
|
return (
|
|
44
58
|
<div className="flex flex-col gap-4">
|
|
45
|
-
<p className="text-muted-foreground text-xs">
|
|
59
|
+
<p className="text-muted-foreground text-xs">
|
|
60
|
+
{t('profile.description', { fallback: 'Pulled live from the OS transport (os.profile).' })}
|
|
61
|
+
</p>
|
|
46
62
|
|
|
47
|
-
<
|
|
48
|
-
<
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
<SkeletonWrapper loading={profile.loading}>
|
|
64
|
+
<Card>
|
|
65
|
+
<div className="flex items-center gap-4">
|
|
66
|
+
<Avatar firstName={name} size="lg" />
|
|
67
|
+
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
|
68
|
+
<Row
|
|
69
|
+
label={t('profile.displayName', { fallback: 'Display Name' })}
|
|
70
|
+
value={asString(data?.display_name, unknown)}
|
|
71
|
+
/>
|
|
72
|
+
<Row
|
|
73
|
+
label={t('profile.firstName', { fallback: 'First Name' })}
|
|
74
|
+
value={asString(data?.first_name, unknown)}
|
|
75
|
+
/>
|
|
76
|
+
<Row
|
|
77
|
+
label={t('profile.lastName', { fallback: 'Last Name' })}
|
|
78
|
+
value={asString(data?.last_name, unknown)}
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
63
81
|
</div>
|
|
64
|
-
</
|
|
65
|
-
</
|
|
82
|
+
</Card>
|
|
83
|
+
</SkeletonWrapper>
|
|
66
84
|
</div>
|
|
67
85
|
);
|
|
68
86
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BardiocLogoIcon, Dialog } from '@bardioc/ui/components';
|
|
1
|
+
import { Alert, BardiocLogoIcon, Dialog } from '@bardioc/ui/components';
|
|
2
2
|
import { HashIcon, PackageIcon, TagIcon } from 'lucide-react';
|
|
3
3
|
import { useEffect, useState, type ReactNode } from 'react';
|
|
4
4
|
import { useTranslation } from '../i18n';
|
|
@@ -34,7 +34,7 @@ function AboutRow({
|
|
|
34
34
|
value,
|
|
35
35
|
}: Readonly<{ icon: ReactNode; label: string; value: string }>) {
|
|
36
36
|
return (
|
|
37
|
-
<div className="flex items-center justify-between gap-4
|
|
37
|
+
<div className="flex items-center justify-between gap-4 p-2">
|
|
38
38
|
<div className="text-foreground flex items-center gap-2">
|
|
39
39
|
{icon}
|
|
40
40
|
<span className="text-xs font-semibold">{label}</span>
|
|
@@ -91,9 +91,10 @@ export function AboutDialog({ open, onOpenChange }: Readonly<AboutDialogProps>)
|
|
|
91
91
|
</div>
|
|
92
92
|
|
|
93
93
|
{failed ? (
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
<Alert
|
|
95
|
+
variant="destructive"
|
|
96
|
+
description={t('about.error', { fallback: 'Could not read app-manifest.json.' })}
|
|
97
|
+
/>
|
|
97
98
|
) : (
|
|
98
99
|
<div className="w-full">
|
|
99
100
|
<AboutRow
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { MenuRoot } from '@bardioc/app-sdk';
|
|
2
2
|
import { useCommandMenu, type AppCommand } from '@bardioc/app-sdk/react';
|
|
3
3
|
import {
|
|
4
|
-
BardiocLogoIcon,
|
|
5
4
|
Page,
|
|
6
5
|
SidebarBasic,
|
|
7
6
|
SidebarContainedProvider,
|
|
7
|
+
SidebarContainedTrigger,
|
|
8
|
+
SidebarSearch,
|
|
8
9
|
Toaster,
|
|
9
10
|
} from '@bardioc/ui/components';
|
|
11
|
+
import { useFuzzySearch } from '@bardioc/ui/hooks';
|
|
10
12
|
import {
|
|
11
13
|
Activity,
|
|
12
14
|
LayoutDashboard,
|
|
@@ -16,16 +18,13 @@ import {
|
|
|
16
18
|
type LucideIcon,
|
|
17
19
|
} from 'lucide-react';
|
|
18
20
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
19
|
-
import { OverviewView } from '../climate/overview-view';
|
|
20
|
-
import { StationsView } from '../climate/stations-view';
|
|
21
21
|
import type { ViewId } from '../climate/types';
|
|
22
|
-
import { LiveEventsView } from '../events/live-events-view';
|
|
23
|
-
import { ExplorerView } from '../graph/explorer-view';
|
|
24
22
|
import { useI18n, useTranslation } from '../i18n';
|
|
25
|
-
import { ProfileView } from '../profile/profile-view';
|
|
26
23
|
import { useAppNotify } from '../use-app-notify';
|
|
27
24
|
import { AboutDialog } from './about-dialog';
|
|
25
|
+
import { DebugMenu } from './debug-menu';
|
|
28
26
|
import { Header } from './header';
|
|
27
|
+
import { ViewContent } from './view-content';
|
|
29
28
|
|
|
30
29
|
// `icon` is the sidebar's React component; `iconName` is the matching lucide export name the
|
|
31
30
|
// OS resolves for the top-menu entry (host calls resolveLucideIcon on the string). `titleKey` is
|
|
@@ -74,47 +73,39 @@ const NAV: Array<{
|
|
|
74
73
|
},
|
|
75
74
|
];
|
|
76
75
|
|
|
77
|
-
function ViewContent({ view }: Readonly<{ view: ViewId }>) {
|
|
78
|
-
switch (view) {
|
|
79
|
-
case 'overview':
|
|
80
|
-
return <OverviewView />;
|
|
81
|
-
case 'stations':
|
|
82
|
-
return <StationsView />;
|
|
83
|
-
case 'explorer':
|
|
84
|
-
return <ExplorerView />;
|
|
85
|
-
case 'events':
|
|
86
|
-
return <LiveEventsView />;
|
|
87
|
-
case 'profile':
|
|
88
|
-
return <ProfileView />;
|
|
89
|
-
default:
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
76
|
export function AppShell() {
|
|
95
77
|
const { t } = useTranslation();
|
|
96
78
|
const { locale } = useI18n();
|
|
97
79
|
const notify = useAppNotify();
|
|
98
80
|
const [view, setView] = useState<ViewId>('overview');
|
|
99
81
|
const [aboutOpen, setAboutOpen] = useState(false);
|
|
82
|
+
const [navQuery, setNavQuery] = useState('');
|
|
100
83
|
|
|
101
|
-
|
|
84
|
+
// Compared with the last locale, not skipped once: `t` changes identity when the catalog loads,
|
|
85
|
+
// which re-runs this effect without the language changing.
|
|
86
|
+
const shownLocale = useRef(locale);
|
|
102
87
|
useEffect(() => {
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
88
|
+
if (shownLocale.current === locale) return;
|
|
89
|
+
shownLocale.current = locale;
|
|
107
90
|
notify(
|
|
108
91
|
t('notify.localeChanged', { fallback: 'Language switched to {locale}.', params: { locale } }),
|
|
109
92
|
'info'
|
|
110
93
|
);
|
|
111
94
|
}, [locale, notify, t]);
|
|
112
95
|
|
|
113
|
-
const items =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
96
|
+
const items = useMemo(
|
|
97
|
+
() =>
|
|
98
|
+
NAV.map(entry => ({
|
|
99
|
+
name: entry.id,
|
|
100
|
+
label: t(entry.labelKey, { fallback: entry.id }),
|
|
101
|
+
icon: entry.icon,
|
|
102
|
+
})),
|
|
103
|
+
[t]
|
|
104
|
+
);
|
|
105
|
+
// The sidebar search filters the navigation here; an app with records to find can point the
|
|
106
|
+
// same field at them instead, as an app-wide search.
|
|
107
|
+
const visibleItems = useFuzzySearch(items, navQuery, { keys: ['label'] });
|
|
108
|
+
const searchPlaceholder = t('nav.search', { fallback: 'Search Views…' });
|
|
118
109
|
|
|
119
110
|
// Mirror the section switcher into the OS top menu: each view is a checkable command, so the
|
|
120
111
|
// active section can be changed from the host menu bar (next to About / Version) — not just
|
|
@@ -161,22 +152,30 @@ export function AppShell() {
|
|
|
161
152
|
const activeNav = NAV.find(entry => entry.id === view);
|
|
162
153
|
const pageTitle = activeNav ? t(activeNav.titleKey, { fallback: activeNav.id }) : undefined;
|
|
163
154
|
|
|
164
|
-
const header = (
|
|
165
|
-
<div className="flex items-center gap-2 px-2 py-1">
|
|
166
|
-
<BardiocLogoIcon className="size-5" />
|
|
167
|
-
<span className="truncate text-sm font-semibold">
|
|
168
|
-
{t('app.title', { fallback: 'Weather Dashboard' })}
|
|
169
|
-
</span>
|
|
170
|
-
</div>
|
|
171
|
-
);
|
|
172
|
-
|
|
173
155
|
return (
|
|
174
156
|
<SidebarContainedProvider defaultCollapsed={false}>
|
|
175
157
|
<div className="bg-background text-foreground @container/app flex h-full min-h-0">
|
|
176
158
|
<SidebarBasic
|
|
177
|
-
|
|
159
|
+
// No logo or app name here: the OS window frame already shows both.
|
|
160
|
+
header={
|
|
161
|
+
<SidebarSearch
|
|
162
|
+
value={navQuery}
|
|
163
|
+
onChange={setNavQuery}
|
|
164
|
+
placeholder={searchPlaceholder}
|
|
165
|
+
collapsedLabel={searchPlaceholder}
|
|
166
|
+
shortcut={{ key: 'f' }}
|
|
167
|
+
size="sm"
|
|
168
|
+
showClearButton
|
|
169
|
+
/>
|
|
170
|
+
}
|
|
171
|
+
footer={
|
|
172
|
+
<>
|
|
173
|
+
{import.meta.env.DEV && <DebugMenu />}
|
|
174
|
+
<SidebarContainedTrigger />
|
|
175
|
+
</>
|
|
176
|
+
}
|
|
178
177
|
rail={false}
|
|
179
|
-
items={
|
|
178
|
+
items={visibleItems}
|
|
180
179
|
selectedItem={view}
|
|
181
180
|
onItemSelected={item => {
|
|
182
181
|
if (!item) return;
|
|
@@ -190,8 +189,10 @@ export function AppShell() {
|
|
|
190
189
|
actionsClassName="gap-2"
|
|
191
190
|
sticky
|
|
192
191
|
className="min-w-0"
|
|
192
|
+
// Grows the body to the page height, so a view can fill it (the explorer's panel does).
|
|
193
|
+
contentClassName="flex-1"
|
|
193
194
|
>
|
|
194
|
-
<ViewContent view={view} />
|
|
195
|
+
<ViewContent view={view} title={pageTitle} />
|
|
195
196
|
</Page>
|
|
196
197
|
</div>
|
|
197
198
|
<AboutDialog open={aboutOpen} onOpenChange={setAboutOpen} />
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DropdownMenuCheckboxItem,
|
|
3
|
+
DropdownMenuItem,
|
|
4
|
+
DropdownMenuLabel,
|
|
5
|
+
SidebarMenuBadge,
|
|
6
|
+
SidebarMenuButton,
|
|
7
|
+
useContainedSidebarState,
|
|
8
|
+
} from '@bardioc/ui/components';
|
|
9
|
+
import { useDebugMode } from '@bardioc/ui/lib';
|
|
10
|
+
import * as Shadcn from '@bardioc/ui/shadcn';
|
|
11
|
+
import { Bug } from 'lucide-react';
|
|
12
|
+
import { useTranslation } from '../i18n';
|
|
13
|
+
import { PREVIEW_STATES, usePreviewState, type PreviewState } from '../preview-state';
|
|
14
|
+
|
|
15
|
+
const PREVIEW_STATE_FALLBACKS: Record<PreviewState, string> = {
|
|
16
|
+
live: 'Live',
|
|
17
|
+
mock: 'Mock Data',
|
|
18
|
+
loading: 'Loading',
|
|
19
|
+
empty: 'Empty',
|
|
20
|
+
error: 'Error',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Sidebar footer entry for development: the same debug flag the OS dock toggles (it adds debug
|
|
24
|
+
// rows to error states), plus a preview state: Loading, Empty or Error for every view, or Mock Data
|
|
25
|
+
// for views that otherwise show only live data.
|
|
26
|
+
export function DebugMenu() {
|
|
27
|
+
const { t } = useTranslation();
|
|
28
|
+
const { isCollapsed } = useContainedSidebarState();
|
|
29
|
+
const [debugMode, setDebugMode] = useDebugMode();
|
|
30
|
+
const { previewState, setPreviewState } = usePreviewState();
|
|
31
|
+
const label = t('debug.menu', { fallback: 'Debug' });
|
|
32
|
+
const stateLabel = (state: PreviewState) =>
|
|
33
|
+
t(`debug.state.${state}`, { fallback: PREVIEW_STATE_FALLBACKS[state] });
|
|
34
|
+
const previewing = previewState !== 'live';
|
|
35
|
+
// A forced state marks the entry active and names the state in a badge; collapsed hides menu
|
|
36
|
+
// badges, so the tooltip names it there.
|
|
37
|
+
const collapsedLabel = previewing
|
|
38
|
+
? t('debug.menuWithState', {
|
|
39
|
+
fallback: 'Debug: {state}',
|
|
40
|
+
params: { state: stateLabel(previewState) },
|
|
41
|
+
})
|
|
42
|
+
: label;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Shadcn.SidebarMenu>
|
|
46
|
+
<Shadcn.SidebarMenuItem>
|
|
47
|
+
<Shadcn.DropdownMenu>
|
|
48
|
+
<Shadcn.DropdownMenuTrigger asChild>
|
|
49
|
+
<SidebarMenuButton
|
|
50
|
+
isActive={previewing}
|
|
51
|
+
tooltip={{ hidden: !isCollapsed, children: collapsedLabel }}
|
|
52
|
+
aria-label={isCollapsed ? collapsedLabel : undefined}
|
|
53
|
+
>
|
|
54
|
+
<Bug aria-hidden="true" />
|
|
55
|
+
{!isCollapsed && <span>{label}</span>}
|
|
56
|
+
</SidebarMenuButton>
|
|
57
|
+
</Shadcn.DropdownMenuTrigger>
|
|
58
|
+
<Shadcn.DropdownMenuContent side="right" align="end">
|
|
59
|
+
<DropdownMenuCheckboxItem
|
|
60
|
+
checked={debugMode}
|
|
61
|
+
onCheckedChange={checked => setDebugMode(checked === true)}
|
|
62
|
+
>
|
|
63
|
+
{t('debug.showInfo', { fallback: 'Show Debug Info' })}
|
|
64
|
+
</DropdownMenuCheckboxItem>
|
|
65
|
+
<Shadcn.DropdownMenuSeparator />
|
|
66
|
+
<DropdownMenuLabel>
|
|
67
|
+
{t('debug.previewState', { fallback: 'Preview State' })}
|
|
68
|
+
</DropdownMenuLabel>
|
|
69
|
+
{PREVIEW_STATES.map(state => (
|
|
70
|
+
<DropdownMenuItem
|
|
71
|
+
key={state}
|
|
72
|
+
variant="check-icon"
|
|
73
|
+
reserveCheckSpace
|
|
74
|
+
checked={previewState === state}
|
|
75
|
+
onSelect={() => setPreviewState(state)}
|
|
76
|
+
>
|
|
77
|
+
{stateLabel(state)}
|
|
78
|
+
</DropdownMenuItem>
|
|
79
|
+
))}
|
|
80
|
+
</Shadcn.DropdownMenuContent>
|
|
81
|
+
</Shadcn.DropdownMenu>
|
|
82
|
+
{previewing && <SidebarMenuBadge>{stateLabel(previewState)}</SidebarMenuBadge>}
|
|
83
|
+
</Shadcn.SidebarMenuItem>
|
|
84
|
+
</Shadcn.SidebarMenu>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -12,7 +12,7 @@ export function Header() {
|
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
14
|
<>
|
|
15
|
-
{state
|
|
15
|
+
{state && state.stations.length > 0 && (
|
|
16
16
|
<LocationSelect
|
|
17
17
|
stations={state.stations}
|
|
18
18
|
value={state.activeStationId}
|
|
@@ -23,14 +23,16 @@ export function Header() {
|
|
|
23
23
|
useI18n={useI18n}
|
|
24
24
|
supportedLocales={[...SUPPORTED_LOCALES]}
|
|
25
25
|
flags={{ en: 'EN', de: 'DE' }}
|
|
26
|
+
size="icon-sm"
|
|
26
27
|
/>
|
|
27
28
|
<Button
|
|
28
|
-
className="gap-2"
|
|
29
29
|
size="sm"
|
|
30
30
|
onClick={() => void refresh()}
|
|
31
|
-
disabled={
|
|
31
|
+
disabled={!state}
|
|
32
|
+
isLoading={syncing}
|
|
33
|
+
stableWidth
|
|
32
34
|
>
|
|
33
|
-
<RefreshCw
|
|
35
|
+
<RefreshCw />
|
|
34
36
|
{t('header.sync', { fallback: 'Sync' })}
|
|
35
37
|
</Button>
|
|
36
38
|
</>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Button, Empty, SkeletonWrapper } from '@bardioc/ui/components';
|
|
2
|
+
import { TriangleAlert } from 'lucide-react';
|
|
3
|
+
import { OverviewView } from '../climate/overview-view';
|
|
4
|
+
import { StationsView } from '../climate/stations-view';
|
|
5
|
+
import type { ViewId } from '../climate/types';
|
|
6
|
+
import { LiveEventsView } from '../events/live-events-view';
|
|
7
|
+
import { ExplorerView } from '../graph/explorer-view';
|
|
8
|
+
import { useTranslation } from '../i18n';
|
|
9
|
+
import { usePreviewState } from '../preview-state';
|
|
10
|
+
import { ProfileView } from '../profile/profile-view';
|
|
11
|
+
|
|
12
|
+
function View({ view }: Readonly<{ view: ViewId }>) {
|
|
13
|
+
switch (view) {
|
|
14
|
+
case 'overview':
|
|
15
|
+
return <OverviewView />;
|
|
16
|
+
case 'stations':
|
|
17
|
+
return <StationsView />;
|
|
18
|
+
case 'explorer':
|
|
19
|
+
return <ExplorerView />;
|
|
20
|
+
case 'events':
|
|
21
|
+
return <LiveEventsView />;
|
|
22
|
+
case 'profile':
|
|
23
|
+
return <ProfileView />;
|
|
24
|
+
default:
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// The active view, or the state the debug menu previews: Loading skeletonizes the view's own
|
|
30
|
+
// layout, Error shows the shared error state (with debug rows while "Show Debug Info" is on),
|
|
31
|
+
// and Empty reaches the views through their data (see `DashboardProvider`).
|
|
32
|
+
export function ViewContent({ view, title }: Readonly<{ view: ViewId; title?: string }>) {
|
|
33
|
+
const { t } = useTranslation();
|
|
34
|
+
const { previewState, setPreviewState } = usePreviewState();
|
|
35
|
+
|
|
36
|
+
switch (previewState) {
|
|
37
|
+
case 'loading':
|
|
38
|
+
return (
|
|
39
|
+
<SkeletonWrapper loading>
|
|
40
|
+
<View view={view} />
|
|
41
|
+
</SkeletonWrapper>
|
|
42
|
+
);
|
|
43
|
+
case 'error':
|
|
44
|
+
return (
|
|
45
|
+
<Empty
|
|
46
|
+
icon={TriangleAlert}
|
|
47
|
+
iconVariant="chip"
|
|
48
|
+
title={t('preview.errorTitle', { fallback: 'Could Not Load Data' })}
|
|
49
|
+
description={t('preview.errorDescription', {
|
|
50
|
+
fallback: 'A simulated error from the preview state in the debug menu.',
|
|
51
|
+
})}
|
|
52
|
+
debugRows={[
|
|
53
|
+
{ label: t('preview.debugView', { fallback: 'View' }), value: title ?? view },
|
|
54
|
+
{
|
|
55
|
+
label: t('debug.previewState', { fallback: 'Preview State' }),
|
|
56
|
+
value: t('debug.state.error', { fallback: 'Error' }),
|
|
57
|
+
},
|
|
58
|
+
]}
|
|
59
|
+
actions={
|
|
60
|
+
<Button size="sm" onClick={() => setPreviewState('live')}>
|
|
61
|
+
{t('preview.retry', { fallback: 'Retry' })}
|
|
62
|
+
</Button>
|
|
63
|
+
}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
default:
|
|
67
|
+
return <View view={view} />;
|
|
68
|
+
}
|
|
69
|
+
}
|
package/templates/vue/_gitignore
CHANGED
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "vite",
|
|
8
8
|
"build": "vue-tsc -b && vite build && node scripts/stamp-manifest.mjs dist",
|
|
9
|
-
"bundle": "vue-tsc -b && vite build && node scripts/stamp-manifest.mjs dist && cd dist && zip -r ../{{APP_NAME}}.zip .",
|
|
9
|
+
"bundle": "npm run compliance:all && vue-tsc -b && vite build && node scripts/stamp-manifest.mjs dist && cd dist && zip -r ../{{APP_NAME}}.zip .",
|
|
10
|
+
"sbom": "cyclonedx-npm --omit dev --output-format json --output-reproducible --output-file public/sbom.cdx.json",
|
|
11
|
+
"compliance": "node scripts/compliance.mjs public",
|
|
12
|
+
"audit:security": "node scripts/audit.mjs public",
|
|
13
|
+
"compliance:all": "npm run sbom && npm run compliance && npm run audit:security && node scripts/compliance-summary.mjs public",
|
|
10
14
|
"preview": "vite preview",
|
|
11
15
|
"check-types": "vue-tsc --noEmit",
|
|
12
16
|
"lint": "eslint .",
|
|
@@ -18,16 +22,18 @@
|
|
|
18
22
|
"release:publish": "changeset publish"
|
|
19
23
|
},
|
|
20
24
|
"dependencies": {
|
|
21
|
-
"@bardioc/app-sdk": "^1.0.
|
|
25
|
+
"@bardioc/app-sdk": "^1.0.3",
|
|
22
26
|
"vue": "^3.5.16"
|
|
23
27
|
},
|
|
24
28
|
"devDependencies": {
|
|
25
29
|
"@bardioc/configs": "0.2.1",
|
|
26
30
|
"@changesets/cli": "^2.31.0",
|
|
31
|
+
"@cyclonedx/cyclonedx-npm": "^6.0.1",
|
|
27
32
|
"@tailwindcss/vite": "^4.3.0",
|
|
28
33
|
"@types/node": "^22.19.19",
|
|
29
34
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
30
35
|
"eslint": "^10.0.0",
|
|
36
|
+
"license-checker-rseidelsohn": "^5.0.1",
|
|
31
37
|
"prettier": "^3.6.2",
|
|
32
38
|
"tailwindcss": "4.3.0",
|
|
33
39
|
"typescript": "6.0.3",
|