@bardioc/create-bardioc-app 0.5.6 → 0.6.0
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 +1 -1
- package/package.json +1 -1
- package/templates/_base/README.md +1 -1
- package/templates/_base/public/app-manifest.json +1 -2
- package/templates/angular/.env.example +4 -1
- package/templates/angular/package.json +2 -2
- package/templates/angular/scripts/dev-env.mjs +62 -0
- package/templates/angular/scripts/dev.mjs +56 -0
- package/templates/angular/scripts/sync-runtime-env.mjs +2 -28
- package/templates/angular/src/app/app.component.ts +44 -26
- package/templates/angular/src/app/bardioc.token.ts +2 -2
- package/templates/angular/src/main.ts +6 -8
- package/templates/angular/src/styles.css +4 -0
- package/templates/nextjs/.env.example +4 -1
- package/templates/nextjs/app/page.tsx +18 -15
- package/templates/nextjs/next.config.ts +1 -1
- package/templates/nextjs/package.json +3 -3
- package/templates/nextjs/scripts/dev-env.mjs +62 -0
- package/templates/nextjs/scripts/dev.mjs +56 -0
- package/templates/preact/.env.example +3 -0
- package/templates/preact/package.json +2 -2
- package/templates/preact/src/App.tsx +10 -10
- package/templates/preact/src/main.tsx +3 -3
- package/templates/preact/vite.config.ts +4 -1
- package/templates/solid/.env.example +3 -0
- package/templates/solid/package.json +2 -2
- package/templates/solid/src/App.tsx +10 -10
- package/templates/solid/src/bardioc-sdk.tsx +11 -23
- package/templates/solid/src/main.tsx +5 -3
- package/templates/solid/vite.config.ts +4 -1
- package/templates/svelte/.env.example +3 -0
- package/templates/svelte/package.json +2 -2
- package/templates/svelte/src/App.svelte +12 -12
- package/templates/svelte/src/main.ts +5 -5
- package/templates/svelte/vite.config.ts +4 -1
- package/templates/vite/.env.example +3 -0
- package/templates/vite/CLAUDE.md +34 -7
- package/templates/vite/package.json +3 -3
- package/templates/vite/public/app-manifest.json +0 -1
- package/templates/vite/src/auth/onboarding.tsx +7 -5
- package/templates/vite/src/climate/conditions-chart.tsx +2 -2
- package/templates/vite/src/climate/overview-view.tsx +1 -1
- package/templates/vite/src/climate/stations-view.tsx +3 -10
- package/templates/vite/src/climate/use-climate.ts +8 -8
- package/templates/vite/src/events/live-events-view.tsx +8 -10
- package/templates/vite/src/graph/explorer-view.tsx +7 -14
- package/templates/vite/src/graph/requests.ts +16 -19
- package/templates/vite/src/locales/de.json +7 -8
- package/templates/vite/src/locales/en.json +45 -46
- package/templates/vite/src/main.tsx +3 -3
- package/templates/vite/src/profile/profile-view.tsx +5 -12
- package/templates/vite/src/profile/use-profile.ts +3 -3
- package/templates/vite/src/shell/about-dialog.tsx +5 -11
- package/templates/vite/src/shell/app-shell.tsx +63 -22
- package/templates/vite/src/shell/header.tsx +24 -24
- package/templates/vite/vite.config.ts +4 -1
- package/templates/vue/.env.example +3 -0
- package/templates/vue/package.json +2 -2
- package/templates/vue/src/App.vue +12 -12
- package/templates/vue/src/bardioc-sdk.ts +6 -15
- package/templates/vue/src/main.ts +5 -4
- package/templates/vue/vite.config.ts +4 -1
- package/templates/angular/scripts/dev-auth-proxy.mjs +0 -17
- package/templates/angular/src/app/bardioc-bridge.ts +0 -5
- package/templates/nextjs/scripts/dev-auth-proxy.mjs +0 -17
|
@@ -31,7 +31,7 @@ export function ProfileView() {
|
|
|
31
31
|
<Empty
|
|
32
32
|
icon={UserX}
|
|
33
33
|
iconVariant="chip"
|
|
34
|
-
title={t('profile.errorTitle', { fallback: 'Could
|
|
34
|
+
title={t('profile.errorTitle', { fallback: 'Could Not Load Profile' })}
|
|
35
35
|
description={profile.error ?? t('profile.errorHint', { fallback: '' })}
|
|
36
36
|
/>
|
|
37
37
|
);
|
|
@@ -42,29 +42,22 @@ export function ProfileView() {
|
|
|
42
42
|
|
|
43
43
|
return (
|
|
44
44
|
<div className="flex flex-col gap-4">
|
|
45
|
-
<
|
|
46
|
-
<h2 className="text-lg font-semibold">
|
|
47
|
-
{t('profile.title', { fallback: 'Operator profile' })}
|
|
48
|
-
</h2>
|
|
49
|
-
<p className="text-muted-foreground text-xs">
|
|
50
|
-
{t('profile.description', { fallback: '' })}
|
|
51
|
-
</p>
|
|
52
|
-
</div>
|
|
45
|
+
<p className="text-muted-foreground text-xs">{t('profile.description', { fallback: '' })}</p>
|
|
53
46
|
|
|
54
47
|
<Card>
|
|
55
48
|
<div className="flex items-center gap-4">
|
|
56
49
|
<Avatar firstName={name} size="lg" />
|
|
57
50
|
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
|
58
51
|
<Row
|
|
59
|
-
label={t('profile.displayName', { fallback: 'Display
|
|
52
|
+
label={t('profile.displayName', { fallback: 'Display Name' })}
|
|
60
53
|
value={asString(data.display_name, unknown)}
|
|
61
54
|
/>
|
|
62
55
|
<Row
|
|
63
|
-
label={t('profile.firstName', { fallback: 'First
|
|
56
|
+
label={t('profile.firstName', { fallback: 'First Name' })}
|
|
64
57
|
value={asString(data.first_name, unknown)}
|
|
65
58
|
/>
|
|
66
59
|
<Row
|
|
67
|
-
label={t('profile.lastName', { fallback: 'Last
|
|
60
|
+
label={t('profile.lastName', { fallback: 'Last Name' })}
|
|
68
61
|
value={asString(data.last_name, unknown)}
|
|
69
62
|
/>
|
|
70
63
|
</div>
|
|
@@ -25,7 +25,7 @@ export function operatorLabel(profile: Record_ | null): string | null {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function useProfile(): ProfileState {
|
|
28
|
-
const
|
|
28
|
+
const bardioc = useSdk();
|
|
29
29
|
const [state, setState] = useState<ProfileState>({
|
|
30
30
|
loading: true,
|
|
31
31
|
profile: null,
|
|
@@ -36,7 +36,7 @@ export function useProfile(): ProfileState {
|
|
|
36
36
|
let active = true;
|
|
37
37
|
void (async () => {
|
|
38
38
|
try {
|
|
39
|
-
const profile = (await
|
|
39
|
+
const profile = (await bardioc.os.profile.get()) as Record_;
|
|
40
40
|
if (active) setState({ loading: false, profile, error: null });
|
|
41
41
|
} catch (error) {
|
|
42
42
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -46,7 +46,7 @@ export function useProfile(): ProfileState {
|
|
|
46
46
|
return () => {
|
|
47
47
|
active = false;
|
|
48
48
|
};
|
|
49
|
-
}, [
|
|
49
|
+
}, [bardioc]);
|
|
50
50
|
|
|
51
51
|
return state;
|
|
52
52
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BardiocLogoIcon, Dialog } from '@bardioc/ui/components';
|
|
2
|
-
import { HashIcon, PackageIcon,
|
|
2
|
+
import { HashIcon, PackageIcon, TagIcon } from 'lucide-react';
|
|
3
3
|
import { useEffect, useState, type ReactNode } from 'react';
|
|
4
4
|
import { useTranslation } from '../i18n';
|
|
5
5
|
|
|
@@ -8,7 +8,6 @@ interface AppManifest {
|
|
|
8
8
|
name: string;
|
|
9
9
|
description: string;
|
|
10
10
|
version: string;
|
|
11
|
-
permissions?: string[];
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
interface AboutDialogProps {
|
|
@@ -18,7 +17,7 @@ interface AboutDialogProps {
|
|
|
18
17
|
|
|
19
18
|
// Reads the app's own manifest (public/app-manifest.json) at runtime. The manifest is served at
|
|
20
19
|
// the Vite base path, so the same code works in dev and inside the host — this is the example of
|
|
21
|
-
// reading metadata (name, version,
|
|
20
|
+
// reading metadata (name, version, description) from the manifest instead of hard-coding it.
|
|
22
21
|
async function loadManifest(): Promise<AppManifest> {
|
|
23
22
|
const response = await fetch(`${import.meta.env.BASE_URL}app-manifest.json`, {
|
|
24
23
|
cache: 'no-store',
|
|
@@ -81,7 +80,7 @@ export function AboutDialog({ open, onOpenChange }: Readonly<AboutDialogProps>)
|
|
|
81
80
|
modal
|
|
82
81
|
showCloseOnHeader
|
|
83
82
|
width={440}
|
|
84
|
-
title={t('about.title', { fallback: 'About
|
|
83
|
+
title={t('about.title', { fallback: 'About This App' })}
|
|
85
84
|
>
|
|
86
85
|
<div className="flex flex-col items-center gap-4 px-2 pb-2">
|
|
87
86
|
<div className="flex flex-col items-center gap-2 py-2">
|
|
@@ -93,7 +92,7 @@ export function AboutDialog({ open, onOpenChange }: Readonly<AboutDialogProps>)
|
|
|
93
92
|
|
|
94
93
|
{failed ? (
|
|
95
94
|
<p className="text-destructive text-sm">
|
|
96
|
-
{t('about.error', { fallback: 'Could not read app-manifest.json' })}
|
|
95
|
+
{t('about.error', { fallback: 'Could not read app-manifest.json.' })}
|
|
97
96
|
</p>
|
|
98
97
|
) : (
|
|
99
98
|
<div className="w-full">
|
|
@@ -112,16 +111,11 @@ export function AboutDialog({ open, onOpenChange }: Readonly<AboutDialogProps>)
|
|
|
112
111
|
label={t('about.description', { fallback: 'Description' })}
|
|
113
112
|
value={manifest?.description ?? dash}
|
|
114
113
|
/>
|
|
115
|
-
<AboutRow
|
|
116
|
-
icon={<ShieldCheckIcon className="size-4" />}
|
|
117
|
-
label={t('about.permissions', { fallback: 'Permissions' })}
|
|
118
|
-
value={manifest?.permissions?.join(', ') ?? dash}
|
|
119
|
-
/>
|
|
120
114
|
</div>
|
|
121
115
|
)}
|
|
122
116
|
|
|
123
117
|
<p className="text-muted-foreground text-center text-xs">
|
|
124
|
-
{t('about.source', { fallback: 'Read live from public/app-manifest.json' })}
|
|
118
|
+
{t('about.source', { fallback: 'Read live from public/app-manifest.json.' })}
|
|
125
119
|
</p>
|
|
126
120
|
</div>
|
|
127
121
|
</Dialog>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MenuRoot } from '@bardioc/app-sdk';
|
|
2
2
|
import { useCommandMenu, type AppCommand } from '@bardioc/app-sdk/react';
|
|
3
3
|
import {
|
|
4
4
|
BardiocLogoIcon,
|
|
5
|
+
Page,
|
|
5
6
|
SidebarBasic,
|
|
6
7
|
SidebarContainedProvider,
|
|
7
8
|
Toaster,
|
|
8
9
|
} from '@bardioc/ui/components';
|
|
9
|
-
import { ScrollArea } from '@bardioc/ui/shadcn';
|
|
10
10
|
import {
|
|
11
11
|
Activity,
|
|
12
12
|
LayoutDashboard,
|
|
@@ -28,13 +28,50 @@ import { AboutDialog } from './about-dialog';
|
|
|
28
28
|
import { Header } from './header';
|
|
29
29
|
|
|
30
30
|
// `icon` is the sidebar's React component; `iconName` is the matching lucide export name the
|
|
31
|
-
// OS resolves for the top-menu entry (host calls resolveLucideIcon on the string).
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
// OS resolves for the top-menu entry (host calls resolveLucideIcon on the string). `titleKey` is
|
|
32
|
+
// the view's Page title, which can say more than its short navigation label.
|
|
33
|
+
const NAV: Array<{
|
|
34
|
+
id: ViewId;
|
|
35
|
+
icon: LucideIcon;
|
|
36
|
+
iconName: string;
|
|
37
|
+
labelKey: string;
|
|
38
|
+
titleKey: string;
|
|
39
|
+
}> = [
|
|
40
|
+
{
|
|
41
|
+
id: 'overview',
|
|
42
|
+
icon: LayoutDashboard,
|
|
43
|
+
iconName: 'LayoutDashboard',
|
|
44
|
+
labelKey: 'nav.overview',
|
|
45
|
+
titleKey: 'nav.overview',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'stations',
|
|
49
|
+
icon: Table2,
|
|
50
|
+
iconName: 'Table2',
|
|
51
|
+
labelKey: 'nav.stations',
|
|
52
|
+
titleKey: 'stations.title',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'explorer',
|
|
56
|
+
icon: Network,
|
|
57
|
+
iconName: 'Network',
|
|
58
|
+
labelKey: 'nav.explorer',
|
|
59
|
+
titleKey: 'explorer.title',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'events',
|
|
63
|
+
icon: Activity,
|
|
64
|
+
iconName: 'Activity',
|
|
65
|
+
labelKey: 'nav.events',
|
|
66
|
+
titleKey: 'events.title',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'profile',
|
|
70
|
+
icon: UserRound,
|
|
71
|
+
iconName: 'UserRound',
|
|
72
|
+
labelKey: 'nav.profile',
|
|
73
|
+
titleKey: 'profile.title',
|
|
74
|
+
},
|
|
38
75
|
];
|
|
39
76
|
|
|
40
77
|
function ViewContent({ view }: Readonly<{ view: ViewId }>) {
|
|
@@ -68,7 +105,7 @@ export function AppShell() {
|
|
|
68
105
|
return;
|
|
69
106
|
}
|
|
70
107
|
notify(
|
|
71
|
-
t('notify.localeChanged', { fallback: 'Language switched to {locale}', params: { locale } }),
|
|
108
|
+
t('notify.localeChanged', { fallback: 'Language switched to {locale}.', params: { locale } }),
|
|
72
109
|
'info'
|
|
73
110
|
);
|
|
74
111
|
}, [locale, notify, t]);
|
|
@@ -86,7 +123,7 @@ export function AppShell() {
|
|
|
86
123
|
() => [
|
|
87
124
|
{
|
|
88
125
|
id: 'app.about',
|
|
89
|
-
title: t('menu.about', { fallback: 'About
|
|
126
|
+
title: t('menu.about', { fallback: 'About This App' }),
|
|
90
127
|
handler: () => setAboutOpen(true),
|
|
91
128
|
},
|
|
92
129
|
...NAV.map(entry => ({
|
|
@@ -99,7 +136,7 @@ export function AppShell() {
|
|
|
99
136
|
[t, view]
|
|
100
137
|
);
|
|
101
138
|
|
|
102
|
-
const menu = useMemo<
|
|
139
|
+
const menu = useMemo<MenuRoot[]>(
|
|
103
140
|
() => [
|
|
104
141
|
{
|
|
105
142
|
label: t('app.title', { fallback: 'Weather Dashboard' }),
|
|
@@ -121,18 +158,21 @@ export function AppShell() {
|
|
|
121
158
|
|
|
122
159
|
useCommandMenu(commands, menu);
|
|
123
160
|
|
|
161
|
+
const activeNav = NAV.find(entry => entry.id === view);
|
|
162
|
+
const pageTitle = activeNav ? t(activeNav.titleKey, { fallback: activeNav.id }) : undefined;
|
|
163
|
+
|
|
124
164
|
const header = (
|
|
125
165
|
<div className="flex items-center gap-2 px-2 py-1">
|
|
126
166
|
<BardiocLogoIcon className="size-5" />
|
|
127
167
|
<span className="truncate text-sm font-semibold">
|
|
128
|
-
{t('app.title', { fallback: '
|
|
168
|
+
{t('app.title', { fallback: 'Weather Dashboard' })}
|
|
129
169
|
</span>
|
|
130
170
|
</div>
|
|
131
171
|
);
|
|
132
172
|
|
|
133
173
|
return (
|
|
134
174
|
<SidebarContainedProvider defaultCollapsed={false}>
|
|
135
|
-
<div className="bg-background text-foreground flex h-full min-h-0">
|
|
175
|
+
<div className="bg-background text-foreground @container/app flex h-full min-h-0">
|
|
136
176
|
<SidebarBasic
|
|
137
177
|
header={header}
|
|
138
178
|
rail={false}
|
|
@@ -144,14 +184,15 @@ export function AppShell() {
|
|
|
144
184
|
}}
|
|
145
185
|
className="border-r"
|
|
146
186
|
/>
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
<
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
187
|
+
<Page
|
|
188
|
+
title={pageTitle}
|
|
189
|
+
actions={<Header />}
|
|
190
|
+
actionsClassName="gap-2"
|
|
191
|
+
sticky
|
|
192
|
+
className="min-w-0"
|
|
193
|
+
>
|
|
194
|
+
<ViewContent view={view} />
|
|
195
|
+
</Page>
|
|
155
196
|
</div>
|
|
156
197
|
<AboutDialog open={aboutOpen} onOpenChange={setAboutOpen} />
|
|
157
198
|
<Toaster position="bottom-right" />
|
|
@@ -4,35 +4,35 @@ import { LocationSelect } from '../climate/location-select';
|
|
|
4
4
|
import { useDashboard } from '../dashboard-provider';
|
|
5
5
|
import { SUPPORTED_LOCALES, useI18n, useTranslation } from '../i18n';
|
|
6
6
|
|
|
7
|
+
// Goes into Page's `actions` slot, so it renders bare controls: Page owns the header row, its
|
|
8
|
+
// padding and its border, and lines all three up with the body below.
|
|
7
9
|
export function Header() {
|
|
8
10
|
const { t } = useTranslation();
|
|
9
11
|
const { state, syncing, refresh, setActiveStation } = useDashboard();
|
|
10
12
|
|
|
11
13
|
return (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
onSelect={setActiveStation}
|
|
19
|
-
/>
|
|
20
|
-
)}
|
|
21
|
-
<ConnectedLanguageSwitcher
|
|
22
|
-
useI18n={useI18n}
|
|
23
|
-
supportedLocales={[...SUPPORTED_LOCALES]}
|
|
24
|
-
flags={{ en: 'EN', de: 'DE' }}
|
|
14
|
+
<>
|
|
15
|
+
{state?.stations && (
|
|
16
|
+
<LocationSelect
|
|
17
|
+
stations={state.stations}
|
|
18
|
+
value={state.activeStationId}
|
|
19
|
+
onSelect={setActiveStation}
|
|
25
20
|
/>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
21
|
+
)}
|
|
22
|
+
<ConnectedLanguageSwitcher
|
|
23
|
+
useI18n={useI18n}
|
|
24
|
+
supportedLocales={[...SUPPORTED_LOCALES]}
|
|
25
|
+
flags={{ en: 'EN', de: 'DE' }}
|
|
26
|
+
/>
|
|
27
|
+
<Button
|
|
28
|
+
className="gap-2"
|
|
29
|
+
size="sm"
|
|
30
|
+
onClick={() => void refresh()}
|
|
31
|
+
disabled={syncing || !state}
|
|
32
|
+
>
|
|
33
|
+
<RefreshCw className={`size-4 ${syncing ? 'animate-spin' : ''}`} />
|
|
34
|
+
{t('header.sync', { fallback: 'Sync' })}
|
|
35
|
+
</Button>
|
|
36
|
+
</>
|
|
37
37
|
);
|
|
38
38
|
}
|
|
@@ -3,9 +3,12 @@ import tailwindcss from '@tailwindcss/vite';
|
|
|
3
3
|
import react from '@vitejs/plugin-react';
|
|
4
4
|
import { defineConfig, loadEnv } from 'vite';
|
|
5
5
|
|
|
6
|
+
const DEFAULT_PORT = {{PORT}};
|
|
7
|
+
|
|
6
8
|
export default defineConfig(({ mode }) => {
|
|
7
9
|
const env = loadEnv(mode, __dirname, '');
|
|
8
10
|
const appName = env.VITE_APP_NAME?.trim();
|
|
11
|
+
const port = Number(env.LOCALHOST_PORT) || DEFAULT_PORT;
|
|
9
12
|
|
|
10
13
|
if (!appName) {
|
|
11
14
|
throw new Error('Missing VITE_APP_NAME for Vite config');
|
|
@@ -15,7 +18,7 @@ export default defineConfig(({ mode }) => {
|
|
|
15
18
|
plugins: [
|
|
16
19
|
bardiocDevAuth({ appName }),
|
|
17
20
|
react(),
|
|
18
|
-
bardiocApp({ appName, port
|
|
21
|
+
bardiocApp({ appName, port }),
|
|
19
22
|
tailwindcss(),
|
|
20
23
|
],
|
|
21
24
|
};
|
|
@@ -7,3 +7,6 @@ DEV_SECRET=
|
|
|
7
7
|
|
|
8
8
|
# Base URL of the Bardioc BFF / backend-for-frontend (e.g. https://your-host).
|
|
9
9
|
API_BASE_URL=
|
|
10
|
+
|
|
11
|
+
# By default, the app will start on port {{PORT}}. Feel free to uncomment the following variable and change it to match your local setup preferences.
|
|
12
|
+
#LOCALHOST_PORT={{PORT}}
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"release:publish": "changeset publish"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@bardioc/app-sdk": "^0.
|
|
21
|
+
"@bardioc/app-sdk": "^1.0.0",
|
|
22
22
|
"vue": "^3.5.16"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@bardioc/configs": "
|
|
25
|
+
"@bardioc/configs": "0.2.1",
|
|
26
26
|
"@changesets/cli": "^2.31.0",
|
|
27
27
|
"@tailwindcss/vite": "^4.3.0",
|
|
28
28
|
"@types/node": "^22.19.19",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { isDevStandalone, isInsideIframe } from '@bardioc/app-sdk/dev';
|
|
3
3
|
import { ref } from 'vue';
|
|
4
|
-
import {
|
|
4
|
+
import { useBardioc, useNotify } from './bardioc-sdk';
|
|
5
5
|
|
|
6
6
|
type ResultState =
|
|
7
7
|
| { status: 'idle' }
|
|
@@ -10,26 +10,26 @@ type ResultState =
|
|
|
10
10
|
| { status: 'error'; request: string; message: string };
|
|
11
11
|
|
|
12
12
|
const showSdkDemo = isInsideIframe() || isDevStandalone();
|
|
13
|
-
const
|
|
13
|
+
const bardioc = useBardioc();
|
|
14
14
|
const notify = useNotify();
|
|
15
15
|
const result = ref<ResultState>({ status: 'idle' });
|
|
16
16
|
const running = ref(false);
|
|
17
17
|
|
|
18
18
|
async function runGetProfile() {
|
|
19
|
-
if (running.value) return;
|
|
19
|
+
if (running.value || !bardioc) return;
|
|
20
20
|
running.value = true;
|
|
21
21
|
|
|
22
|
-
const request = '
|
|
22
|
+
const request = 'os.profile.get()';
|
|
23
23
|
result.value = { status: 'loading', request };
|
|
24
24
|
|
|
25
25
|
const startTime = Date.now();
|
|
26
|
-
notify('Getting user profile', 'info');
|
|
26
|
+
notify('Getting user profile…', 'info');
|
|
27
27
|
|
|
28
28
|
try {
|
|
29
|
-
const data = await
|
|
29
|
+
const data = await bardioc.os.profile.get();
|
|
30
30
|
|
|
31
31
|
const duration = Date.now() - startTime;
|
|
32
|
-
notify(`Profile loaded (${duration}ms)
|
|
32
|
+
notify(`Profile loaded (${duration}ms).`, 'success');
|
|
33
33
|
result.value = { status: 'success', request, data };
|
|
34
34
|
} catch (err) {
|
|
35
35
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -41,10 +41,10 @@ async function runGetProfile() {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function handleNotify() {
|
|
44
|
-
notify('Hello from {{DISPLAY_NAME}}', 'info');
|
|
44
|
+
notify('Hello from {{DISPLAY_NAME}}.', 'info');
|
|
45
45
|
result.value = {
|
|
46
46
|
status: 'success',
|
|
47
|
-
request: '
|
|
47
|
+
request: 'ui.notify',
|
|
48
48
|
data: { ok: true, message: 'Notification sent to OS' },
|
|
49
49
|
};
|
|
50
50
|
}
|
|
@@ -55,7 +55,7 @@ function clearResult() {
|
|
|
55
55
|
</script>
|
|
56
56
|
|
|
57
57
|
<template>
|
|
58
|
-
<div class="bg-background text-foreground min-h-screen">
|
|
58
|
+
<div class="bg-background text-foreground @container/app min-h-screen">
|
|
59
59
|
<template v-if="showSdkDemo">
|
|
60
60
|
<div class="mx-auto flex min-h-screen w-full max-w-3xl flex-col gap-3 p-4">
|
|
61
61
|
<div class="flex flex-col gap-1">
|
|
@@ -110,7 +110,7 @@ function clearResult() {
|
|
|
110
110
|
<div class="text-muted-foreground border-border border-b pb-2 font-mono text-xs">
|
|
111
111
|
{{ result.request }}
|
|
112
112
|
</div>
|
|
113
|
-
<div class="text-destructive
|
|
113
|
+
<div class="text-destructive whitespace-pre-wrap break-all">{{ result.message }}</div>
|
|
114
114
|
</div>
|
|
115
115
|
<div
|
|
116
116
|
v-else-if="result.status === 'success'"
|
|
@@ -132,7 +132,7 @@ function clearResult() {
|
|
|
132
132
|
<div class="mx-auto flex min-h-screen w-full max-w-3xl flex-col gap-4 p-4">
|
|
133
133
|
<h1 class="m-0 text-2xl font-semibold">Hello Bardioc App</h1>
|
|
134
134
|
<p class="text-muted-foreground m-0 max-w-2xl text-xs leading-5">
|
|
135
|
-
Open this app inside Bardioc
|
|
135
|
+
Open this app inside Bardioc to use transport requests and send host notifications.
|
|
136
136
|
</p>
|
|
137
137
|
<div class="border-border bg-muted/20 rounded-xl border p-3 text-xs leading-5">
|
|
138
138
|
Outside the host iframe this page is only a local preview shell.
|
|
@@ -1,23 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Bardioc, NotifyLevel } from '@bardioc/app-sdk';
|
|
2
2
|
import { inject, type InjectionKey } from 'vue';
|
|
3
3
|
|
|
4
|
-
export const
|
|
4
|
+
export const BARDIOC_KEY: InjectionKey<Bardioc> = Symbol('bardioc');
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function useBardiocBridge(): HostBridge {
|
|
11
|
-
const bridge = inject(BARDIOC_BRIDGE_KEY);
|
|
12
|
-
if (!bridge) {
|
|
13
|
-
throw new Error('useBardiocBridge() must be used after providing BARDIOC_BRIDGE_KEY');
|
|
14
|
-
}
|
|
15
|
-
return bridge;
|
|
16
|
-
}
|
|
6
|
+
/** The host connection, or null while the app runs outside Bardioc. */
|
|
7
|
+
export const useBardioc = (): Bardioc | null => inject(BARDIOC_KEY, null);
|
|
17
8
|
|
|
18
9
|
export function useNotify() {
|
|
19
|
-
const
|
|
10
|
+
const bardioc = useBardioc();
|
|
20
11
|
return (message: string, level?: NotifyLevel) => {
|
|
21
|
-
|
|
12
|
+
bardioc?.ui.notify(message, level).catch(console.error);
|
|
22
13
|
};
|
|
23
14
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { connect } from '@bardioc/app-sdk';
|
|
2
|
+
import { installDevHost, isDevStandalone, isInsideIframe } from '@bardioc/app-sdk/dev';
|
|
2
3
|
import { createApp } from 'vue';
|
|
3
4
|
import App from './App.vue';
|
|
4
|
-
import {
|
|
5
|
+
import { BARDIOC_KEY } from './bardioc-sdk';
|
|
5
6
|
import './index.css';
|
|
6
7
|
|
|
7
8
|
const APP_NAME = import.meta.env.VITE_APP_NAME?.trim();
|
|
@@ -15,13 +16,13 @@ const devStandalone = isDevStandalone();
|
|
|
15
16
|
const useSdkProvider = insideIframe || devStandalone;
|
|
16
17
|
|
|
17
18
|
if (devStandalone) {
|
|
18
|
-
|
|
19
|
+
installDevHost({ appId: APP_NAME });
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
const app = createApp(App);
|
|
22
23
|
|
|
23
24
|
if (useSdkProvider) {
|
|
24
|
-
app.provide(
|
|
25
|
+
app.provide(BARDIOC_KEY, await connect());
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
app.mount('#app');
|
|
@@ -3,9 +3,12 @@ import tailwindcss from '@tailwindcss/vite';
|
|
|
3
3
|
import vue from '@vitejs/plugin-vue';
|
|
4
4
|
import { defineConfig, loadEnv } from 'vite';
|
|
5
5
|
|
|
6
|
+
const DEFAULT_PORT = {{PORT}};
|
|
7
|
+
|
|
6
8
|
export default defineConfig(({ mode }) => {
|
|
7
9
|
const env = loadEnv(mode, __dirname, '');
|
|
8
10
|
const appName = env.VITE_APP_NAME?.trim();
|
|
11
|
+
const port = Number(env.LOCALHOST_PORT) || DEFAULT_PORT;
|
|
9
12
|
|
|
10
13
|
if (!appName) {
|
|
11
14
|
throw new Error('Missing VITE_APP_NAME for Vite config');
|
|
@@ -15,7 +18,7 @@ export default defineConfig(({ mode }) => {
|
|
|
15
18
|
plugins: [
|
|
16
19
|
bardiocDevAuth({ appName }),
|
|
17
20
|
vue(),
|
|
18
|
-
bardiocApp({ appName, port
|
|
21
|
+
bardiocApp({ appName, port }),
|
|
19
22
|
tailwindcss(),
|
|
20
23
|
],
|
|
21
24
|
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { startDevAuthProxy } from '@bardioc/app-sdk/dev-proxy';
|
|
4
|
-
import { dirname, resolve } from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
|
|
7
|
-
const projectDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
|
-
|
|
9
|
-
// Angular's dev server cannot host the SDK Vite plugin, so the proxy runs beside it
|
|
10
|
-
// on the app port + 100 — the same origin src/main.ts points installDevBridge at.
|
|
11
|
-
const server = await startDevAuthProxy({ projectDir, port: {{PORT}} + 100 });
|
|
12
|
-
|
|
13
|
-
// Without DEV_SECRET / API_BASE_URL there is nothing to serve — exit and let the dev server run
|
|
14
|
-
// on its own, matching how the SDK Vite plugin disables itself.
|
|
15
|
-
if (!server) {
|
|
16
|
-
process.exit(0);
|
|
17
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { startDevAuthProxy } from '@bardioc/app-sdk/dev-proxy';
|
|
4
|
-
import { dirname, resolve } from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
|
|
7
|
-
const projectDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
|
-
|
|
9
|
-
// `next dev` cannot host the SDK Vite plugin, so the proxy runs beside it on the app
|
|
10
|
-
// port + 100 — the same origin app/page.tsx points installDevBridge at.
|
|
11
|
-
const server = await startDevAuthProxy({ projectDir, port: {{PORT}} + 100 });
|
|
12
|
-
|
|
13
|
-
// Without DEV_SECRET / API_BASE_URL there is nothing to serve — exit and let the dev server run
|
|
14
|
-
// on its own, matching how the SDK Vite plugin disables itself.
|
|
15
|
-
if (!server) {
|
|
16
|
-
process.exit(0);
|
|
17
|
-
}
|