create-vidra-app 0.1.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.
Files changed (28) hide show
  1. package/README.md +39 -0
  2. package/bin/create-vidra-app.mjs +2 -0
  3. package/bin/vidra.mjs +2 -0
  4. package/dist/cli.js +777 -0
  5. package/dist/index.js +241 -0
  6. package/package.json +56 -0
  7. package/templates/react-vite/NuGet.Config +8 -0
  8. package/templates/react-vite/README.md +57 -0
  9. package/templates/react-vite/_gitignore +16 -0
  10. package/templates/react-vite/package.json +14 -0
  11. package/templates/react-vite/src/{{projectName}}.Host/App.xaml +5 -0
  12. package/templates/react-vite/src/{{projectName}}.Host/App.xaml.cs +14 -0
  13. package/templates/react-vite/src/{{projectName}}.Host/MainPage.cs +28 -0
  14. package/templates/react-vite/src/{{projectName}}.Host/MauiProgram.cs +26 -0
  15. package/templates/react-vite/src/{{projectName}}.Host/Platforms/MacCatalyst/AppDelegate.cs +9 -0
  16. package/templates/react-vite/src/{{projectName}}.Host/Platforms/MacCatalyst/Info.plist +11 -0
  17. package/templates/react-vite/src/{{projectName}}.Host/Platforms/MacCatalyst/Program.cs +11 -0
  18. package/templates/react-vite/src/{{projectName}}.Host/Platforms/Windows/App.xaml +8 -0
  19. package/templates/react-vite/src/{{projectName}}.Host/Platforms/Windows/App.xaml.cs +11 -0
  20. package/templates/react-vite/src/{{projectName}}.Host/{{projectName}}.Host.csproj +57 -0
  21. package/templates/react-vite/ui/index.html +12 -0
  22. package/templates/react-vite/ui/package.json +23 -0
  23. package/templates/react-vite/ui/src/App.tsx +240 -0
  24. package/templates/react-vite/ui/src/index.css +109 -0
  25. package/templates/react-vite/ui/src/main.tsx +10 -0
  26. package/templates/react-vite/ui/src/vite-env.d.ts +1 -0
  27. package/templates/react-vite/ui/tsconfig.json +21 -0
  28. package/templates/react-vite/ui/vite.config.ts +33 -0
@@ -0,0 +1,240 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { app, appWindow, clipboard, notifications, vidra } from "@vidra-dev/sdk";
3
+ import type { WindowInfo, WindowSupport } from "@vidra-dev/sdk";
4
+
5
+ const describeWindow = (windowInfo: WindowInfo): string => {
6
+ const title = windowInfo.title || "(untitled)";
7
+ return `${title} • ${Math.round(windowInfo.width)}x${Math.round(windowInfo.height)} • ${windowInfo.state}`;
8
+ };
9
+
10
+ const formatError = (error: unknown): string => {
11
+ return error instanceof Error ? error.message : String(error);
12
+ };
13
+
14
+ const App = () => {
15
+ const [info, setInfo] = useState("Press a button to call a native module.");
16
+ const [windowSummary, setWindowSummary] = useState(
17
+ "Window controls are ready. Click a button to inspect or update the native window.",
18
+ );
19
+ const [windowSupport, setWindowSupport] = useState<WindowSupport | null>(null);
20
+ const [caps, setCaps] = useState<Record<string, string[]> | null>(null);
21
+ const [count, setCount] = useState(0);
22
+ const countRef = useRef(0);
23
+
24
+ useEffect(() => {
25
+ const unsubscribeCounter = vidra.handle<void, number>("counter.increment", () => {
26
+ countRef.current += 1;
27
+ setCount(countRef.current);
28
+ return countRef.current;
29
+ });
30
+
31
+ const unsubscribeResized = appWindow.onResized((windowInfo) => {
32
+ setWindowSummary(`Window resized: ${describeWindow(windowInfo)}`);
33
+ });
34
+
35
+ const unsubscribeStateChanged = appWindow.onStateChanged((windowInfo) => {
36
+ setWindowSummary(`Window state changed: ${describeWindow(windowInfo)}`);
37
+ });
38
+
39
+ void appWindow
40
+ .getSupport()
41
+ .then((result: WindowSupport) => {
42
+ setWindowSupport(result);
43
+ })
44
+ .catch(() => {
45
+ setWindowSupport(null);
46
+ });
47
+
48
+ return () => {
49
+ unsubscribeCounter();
50
+ unsubscribeResized();
51
+ unsubscribeStateChanged();
52
+ };
53
+ }, []);
54
+
55
+ const handleGetAppInfo = async () => {
56
+ try {
57
+ const result = await app.getInfo();
58
+ setInfo(`${result.appName} v${result.version} on ${result.platform}`);
59
+ } catch (error: unknown) {
60
+ setInfo(`Error: ${formatError(error)}`);
61
+ }
62
+ };
63
+
64
+ const handleReadClipboard = async () => {
65
+ try {
66
+ const result = await clipboard.getText();
67
+ setInfo(`Clipboard: ${result.text || "(empty)"}`);
68
+ } catch (error: unknown) {
69
+ setInfo(`Error: ${formatError(error)}`);
70
+ }
71
+ };
72
+
73
+ const handleCapabilities = async () => {
74
+ try {
75
+ const result = await vidra.capabilities();
76
+ setCaps(result);
77
+ } catch (error: unknown) {
78
+ setInfo(`Error: ${formatError(error)}`);
79
+ }
80
+ };
81
+
82
+ const handleNotification = async () => {
83
+ try {
84
+ const permission = await notifications.requestPermission();
85
+ if (!permission.granted) {
86
+ setInfo(
87
+ "Notifications are disabled. Enable them in your system settings and try again.",
88
+ );
89
+ return;
90
+ }
91
+
92
+ const result = await notifications.show({
93
+ title: "{{appTitle}}",
94
+ body: `Counter is ${countRef.current}. Sent from the native notifications module.`,
95
+ });
96
+
97
+ setInfo(
98
+ result.scheduled
99
+ ? "Notification sent."
100
+ : "Notification could not be shown. Check your notification settings.",
101
+ );
102
+ } catch (error: unknown) {
103
+ setInfo(`Error: ${formatError(error)}`);
104
+ }
105
+ };
106
+
107
+ const handleGetWindowInfo = async () => {
108
+ try {
109
+ const result = await appWindow.getCurrent();
110
+ setWindowSummary(`Current window: ${describeWindow(result)}`);
111
+ } catch (error: unknown) {
112
+ setWindowSummary(`Window error: ${formatError(error)}`);
113
+ }
114
+ };
115
+
116
+ const handleRenameWindow = async () => {
117
+ try {
118
+ const result = await appWindow.setTitle({
119
+ title: `{{appTitle}} (${countRef.current})`,
120
+ });
121
+ setWindowSummary(`Window renamed: ${describeWindow(result)}`);
122
+ } catch (error: unknown) {
123
+ setWindowSummary(`Window error: ${formatError(error)}`);
124
+ }
125
+ };
126
+
127
+ const handleResizeWindow = async () => {
128
+ try {
129
+ const result = await appWindow.configure({
130
+ width: 1100,
131
+ height: 760,
132
+ });
133
+ setWindowSummary(`Window resized: ${describeWindow(result)}`);
134
+ } catch (error: unknown) {
135
+ setWindowSummary(`Window error: ${formatError(error)}`);
136
+ }
137
+ };
138
+
139
+ const handleMaximizeWindow = async () => {
140
+ try {
141
+ const result = await appWindow.maximize();
142
+ setWindowSummary(`Window maximized: ${describeWindow(result)}`);
143
+ } catch (error: unknown) {
144
+ setWindowSummary(`Window error: ${formatError(error)}`);
145
+ }
146
+ };
147
+
148
+ const handleCenterWindow = async () => {
149
+ try {
150
+ const result = await appWindow.center();
151
+ setWindowSummary(`Window centered: ${describeWindow(result)}`);
152
+ } catch (error: unknown) {
153
+ setWindowSummary(`Window error: ${formatError(error)}`);
154
+ }
155
+ };
156
+
157
+ const handleMinimizeWindow = async () => {
158
+ try {
159
+ const result = await appWindow.minimize();
160
+ setWindowSummary(`Window minimized: ${describeWindow(result)}`);
161
+ } catch (error: unknown) {
162
+ setWindowSummary(`Window error: ${formatError(error)}`);
163
+ }
164
+ };
165
+
166
+ const handleRestoreWindow = async () => {
167
+ try {
168
+ const result = await appWindow.restore();
169
+ setWindowSummary(`Window restored: ${describeWindow(result)}`);
170
+ } catch (error: unknown) {
171
+ setWindowSummary(`Window error: ${formatError(error)}`);
172
+ }
173
+ };
174
+
175
+ const showsAdvancedWindowActions =
176
+ !!windowSupport &&
177
+ (windowSupport.center ||
178
+ windowSupport.maximize ||
179
+ windowSupport.minimize ||
180
+ windowSupport.restore ||
181
+ windowSupport.setFullscreen);
182
+
183
+ return (
184
+ <div className="container">
185
+ <h1>{{appTitle}}</h1>
186
+ <p className="subtitle">React + .NET MAUI</p>
187
+
188
+ <div className="card">
189
+ <p className="counter">
190
+ Counter: <strong>{count}</strong>
191
+ <span className="counter-hint">Incremented by .NET every 10s</span>
192
+ </p>
193
+ </div>
194
+
195
+ <div className="card">
196
+ <p className="result">{info}</p>
197
+
198
+ <div className="actions">
199
+ <button onClick={handleGetAppInfo}>Get App Info</button>
200
+ <button onClick={handleReadClipboard}>Read Clipboard</button>
201
+ <button onClick={handleNotification}>Send Notification</button>
202
+ <button onClick={handleCapabilities}>List Capabilities</button>
203
+ </div>
204
+
205
+ {caps && (
206
+ <pre className="capabilities">{JSON.stringify(caps, null, 2)}</pre>
207
+ )}
208
+ </div>
209
+
210
+ <div className="card">
211
+ <p className="window-summary">{windowSummary}</p>
212
+
213
+ <div className="actions">
214
+ <button onClick={handleGetWindowInfo}>Get Window Info</button>
215
+ <button onClick={handleRenameWindow}>Rename Window</button>
216
+ <button onClick={handleResizeWindow}>Resize Window</button>
217
+ {windowSupport?.center && <button onClick={handleCenterWindow}>Center Window</button>}
218
+ {windowSupport?.maximize && (
219
+ <>
220
+ <button onClick={handleMaximizeWindow}>Maximize Window</button>
221
+ <button onClick={handleRestoreWindow}>Restore Window</button>
222
+ </>
223
+ )}
224
+ {windowSupport?.minimize && (
225
+ <button onClick={handleMinimizeWindow}>Minimize Window</button>
226
+ )}
227
+ </div>
228
+
229
+ {!showsAdvancedWindowActions && windowSupport && (
230
+ <p className="result">
231
+ This runtime currently supports title and size updates only. Unsupported window actions
232
+ are hidden automatically based on native support metadata.
233
+ </p>
234
+ )}
235
+ </div>
236
+ </div>
237
+ );
238
+ };
239
+
240
+ export default App;
@@ -0,0 +1,109 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ margin: 0;
6
+ padding: 0;
7
+ }
8
+
9
+ :root {
10
+ --bg: #0f0f12;
11
+ --surface: #1a1a22;
12
+ --border: #2a2a35;
13
+ --text: #e4e4e8;
14
+ --text-muted: #8888a0;
15
+ --accent: #6366f1;
16
+ --accent-hover: #818cf8;
17
+ --radius: 12px;
18
+ }
19
+
20
+ body {
21
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
22
+ background: var(--bg);
23
+ color: var(--text);
24
+ min-height: 100dvh;
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: center;
28
+ }
29
+
30
+ .container {
31
+ max-width: 760px;
32
+ width: 100%;
33
+ padding: 2rem;
34
+ text-align: center;
35
+ }
36
+
37
+ h1 {
38
+ font-size: 2.5rem;
39
+ font-weight: 700;
40
+ letter-spacing: -0.02em;
41
+ }
42
+
43
+ .subtitle {
44
+ color: var(--text-muted);
45
+ margin-top: 0.25rem;
46
+ margin-bottom: 2rem;
47
+ }
48
+
49
+ .card {
50
+ background: var(--surface);
51
+ border: 1px solid var(--border);
52
+ border-radius: var(--radius);
53
+ padding: 1.5rem;
54
+ margin-top: 1rem;
55
+ }
56
+
57
+ .result {
58
+ min-height: 2rem;
59
+ margin-bottom: 1.25rem;
60
+ font-size: 0.95rem;
61
+ color: var(--text-muted);
62
+ word-break: break-word;
63
+ }
64
+
65
+ .window-summary {
66
+ min-height: 2rem;
67
+ margin-bottom: 1.25rem;
68
+ font-size: 0.95rem;
69
+ color: var(--text-muted);
70
+ word-break: break-word;
71
+ }
72
+
73
+ .actions {
74
+ display: flex;
75
+ flex-wrap: wrap;
76
+ gap: 0.5rem;
77
+ justify-content: center;
78
+ }
79
+
80
+ button {
81
+ background: var(--accent);
82
+ color: white;
83
+ border: none;
84
+ border-radius: 8px;
85
+ padding: 0.6rem 1.2rem;
86
+ font-size: 0.9rem;
87
+ font-weight: 500;
88
+ cursor: pointer;
89
+ transition: background 0.15s;
90
+ }
91
+
92
+ button:hover {
93
+ background: var(--accent-hover);
94
+ }
95
+
96
+ button:active {
97
+ transform: scale(0.97);
98
+ }
99
+
100
+ .capabilities {
101
+ margin-top: 1rem;
102
+ text-align: left;
103
+ font-size: 0.8rem;
104
+ color: var(--text-muted);
105
+ background: var(--bg);
106
+ border-radius: 8px;
107
+ padding: 1rem;
108
+ overflow-x: auto;
109
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import App from "./App";
4
+ import "./index.css";
5
+
6
+ createRoot(document.getElementById("root")!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>
10
+ );
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "isolatedModules": true,
11
+ "moduleDetection": "force",
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "noUncheckedSideEffectImports": true
19
+ },
20
+ "include": ["src"]
21
+ }
@@ -0,0 +1,33 @@
1
+ import { defineConfig, type Plugin } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ const nativeAppCompat = (): Plugin => {
5
+ return {
6
+ name: "native-app-compat",
7
+ apply: "build",
8
+ transformIndexHtml: (html) => {
9
+ return html
10
+ .replace(/ crossorigin/g, "")
11
+ .replace(/<script (?:type="module" )?/g, "<script defer ");
12
+ },
13
+ };
14
+ };
15
+
16
+ export default defineConfig({
17
+ plugins: [react(), nativeAppCompat()],
18
+ base: "./",
19
+ server: {
20
+ port: 5173,
21
+ strictPort: true,
22
+ },
23
+ build: {
24
+ outDir: "dist",
25
+ emptyOutDir: true,
26
+ rollupOptions: {
27
+ output: {
28
+ format: "iife",
29
+ inlineDynamicImports: true,
30
+ },
31
+ },
32
+ },
33
+ });