@jxrstudios/jxr 1.0.10 → 1.1.11

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 (87) hide show
  1. package/bin/jxr.js +6 -0
  2. package/dist/index.js +57 -2
  3. package/dist/jxr-server-manager.d.ts.map +1 -1
  4. package/package.json +1 -1
  5. package/src/jxr-server-manager.ts +65 -2
  6. package/zzz_react_template/App.tsx +43 -156
  7. package/zzz_react_template/components/ErrorBoundary.tsx +62 -0
  8. package/zzz_react_template/components/ManusDialog.tsx +85 -0
  9. package/zzz_react_template/components/Map.tsx +155 -0
  10. package/zzz_react_template/components/jxr/CodeEditor.tsx +313 -0
  11. package/zzz_react_template/components/jxr/FileExplorer.tsx +230 -0
  12. package/zzz_react_template/components/jxr/IDEShell.tsx +159 -0
  13. package/zzz_react_template/components/jxr/LandingPage.tsx +414 -0
  14. package/zzz_react_template/components/jxr/LivePreview.tsx +169 -0
  15. package/zzz_react_template/components/jxr/PerformanceDashboard.tsx +379 -0
  16. package/zzz_react_template/components/jxr/TopBar.tsx +149 -0
  17. package/zzz_react_template/components/ui/accordion.tsx +64 -0
  18. package/zzz_react_template/components/ui/alert-dialog.tsx +155 -0
  19. package/zzz_react_template/components/ui/alert.tsx +66 -0
  20. package/zzz_react_template/components/ui/aspect-ratio.tsx +9 -0
  21. package/zzz_react_template/components/ui/avatar.tsx +51 -0
  22. package/zzz_react_template/components/ui/badge.tsx +46 -0
  23. package/zzz_react_template/components/ui/breadcrumb.tsx +109 -0
  24. package/zzz_react_template/components/ui/button-group.tsx +83 -0
  25. package/zzz_react_template/components/ui/button.tsx +60 -0
  26. package/zzz_react_template/components/ui/calendar.tsx +211 -0
  27. package/zzz_react_template/components/ui/card.tsx +92 -0
  28. package/zzz_react_template/components/ui/carousel.tsx +239 -0
  29. package/zzz_react_template/components/ui/chart.tsx +355 -0
  30. package/zzz_react_template/components/ui/checkbox.tsx +30 -0
  31. package/zzz_react_template/components/ui/collapsible.tsx +31 -0
  32. package/zzz_react_template/components/ui/command.tsx +184 -0
  33. package/zzz_react_template/components/ui/context-menu.tsx +250 -0
  34. package/zzz_react_template/components/ui/dialog.tsx +209 -0
  35. package/zzz_react_template/components/ui/drawer.tsx +133 -0
  36. package/zzz_react_template/components/ui/dropdown-menu.tsx +255 -0
  37. package/zzz_react_template/components/ui/empty.tsx +104 -0
  38. package/zzz_react_template/components/ui/field.tsx +242 -0
  39. package/zzz_react_template/components/ui/form.tsx +168 -0
  40. package/zzz_react_template/components/ui/hover-card.tsx +42 -0
  41. package/zzz_react_template/components/ui/input-group.tsx +168 -0
  42. package/zzz_react_template/components/ui/input-otp.tsx +75 -0
  43. package/zzz_react_template/components/ui/input.tsx +70 -0
  44. package/zzz_react_template/components/ui/item.tsx +193 -0
  45. package/zzz_react_template/components/ui/kbd.tsx +28 -0
  46. package/zzz_react_template/components/ui/label.tsx +22 -0
  47. package/zzz_react_template/components/ui/menubar.tsx +274 -0
  48. package/zzz_react_template/components/ui/navigation-menu.tsx +168 -0
  49. package/zzz_react_template/components/ui/pagination.tsx +127 -0
  50. package/zzz_react_template/components/ui/popover.tsx +46 -0
  51. package/zzz_react_template/components/ui/progress.tsx +29 -0
  52. package/zzz_react_template/components/ui/radio-group.tsx +43 -0
  53. package/zzz_react_template/components/ui/resizable.tsx +54 -0
  54. package/zzz_react_template/components/ui/scroll-area.tsx +56 -0
  55. package/zzz_react_template/components/ui/select.tsx +185 -0
  56. package/zzz_react_template/components/ui/separator.tsx +26 -0
  57. package/zzz_react_template/components/ui/sheet.tsx +139 -0
  58. package/zzz_react_template/components/ui/sidebar.tsx +734 -0
  59. package/zzz_react_template/components/ui/skeleton.tsx +13 -0
  60. package/zzz_react_template/components/ui/slider.tsx +61 -0
  61. package/zzz_react_template/components/ui/sonner.tsx +23 -0
  62. package/zzz_react_template/components/ui/spinner.tsx +16 -0
  63. package/zzz_react_template/components/ui/switch.tsx +29 -0
  64. package/zzz_react_template/components/ui/table.tsx +114 -0
  65. package/zzz_react_template/components/ui/tabs.tsx +64 -0
  66. package/zzz_react_template/components/ui/textarea.tsx +67 -0
  67. package/zzz_react_template/components/ui/toggle-group.tsx +73 -0
  68. package/zzz_react_template/components/ui/toggle.tsx +45 -0
  69. package/zzz_react_template/components/ui/tooltip.tsx +59 -0
  70. package/zzz_react_template/const.ts +17 -0
  71. package/zzz_react_template/contexts/JXRContext.tsx +264 -0
  72. package/zzz_react_template/contexts/ThemeContext.tsx +64 -0
  73. package/zzz_react_template/hooks/useComposition.ts +81 -0
  74. package/zzz_react_template/hooks/useMobile.tsx +21 -0
  75. package/zzz_react_template/hooks/usePersistFn.ts +20 -0
  76. package/zzz_react_template/index.css +518 -11
  77. package/zzz_react_template/lib/jxr-runtime/index.ts +201 -0
  78. package/zzz_react_template/lib/jxr-runtime/module-resolver.ts +520 -0
  79. package/zzz_react_template/lib/jxr-runtime/moq-transport.ts +267 -0
  80. package/zzz_react_template/lib/jxr-runtime/web-crypto.ts +279 -0
  81. package/zzz_react_template/lib/jxr-runtime/worker-pool.ts +321 -0
  82. package/zzz_react_template/lib/utils.ts +6 -0
  83. package/zzz_react_template/main.tsx +4 -9
  84. package/zzz_react_template/pages/Docs.tsx +955 -0
  85. package/zzz_react_template/pages/Home.tsx +1080 -0
  86. package/zzz_react_template/pages/NotFound.tsx +105 -0
  87. package/zzz_react_template/tsconfig.json +24 -0
@@ -0,0 +1,264 @@
1
+ /**
2
+ * JXR.js — React Context & Runtime Hooks
3
+ * ─────────────────────────────────────────────────────────────────────────────
4
+ * Design: LavaFlow OS — Thermal Precision + Edge Command
5
+ * Layer: UI / State Management
6
+ *
7
+ * Provides reactive access to all JXR runtime subsystems via React context.
8
+ * All state updates are batched and debounced to prevent render thrashing.
9
+ * ─────────────────────────────────────────────────────────────────────────────
10
+ */
11
+
12
+ import React, {
13
+ createContext,
14
+ useContext,
15
+ useEffect,
16
+ useState,
17
+ useCallback,
18
+ useRef,
19
+ type ReactNode,
20
+ } from 'react';
21
+ import {
22
+ jxrRuntime,
23
+ type JXRRuntimeMetrics,
24
+ type VirtualFile,
25
+ type VirtualDirectory,
26
+ } from '@/lib/jxr-runtime';
27
+
28
+ // ─── Types ────────────────────────────────────────────────────────────────────
29
+
30
+ export type EditorTab = {
31
+ path: string;
32
+ dirty: boolean;
33
+ };
34
+
35
+ export type PreviewState = 'idle' | 'building' | 'ready' | 'error';
36
+
37
+ export interface JXRContextValue {
38
+ // Runtime
39
+ runtime: typeof jxrRuntime;
40
+ metrics: JXRRuntimeMetrics | null;
41
+ isInitialized: boolean;
42
+
43
+ // File system
44
+ fileTree: VirtualDirectory | null;
45
+ openFile: (path: string) => void;
46
+ closeFile: (path: string) => void;
47
+ saveFile: (path: string, content: string) => void;
48
+ createFile: (path: string, content?: string) => void;
49
+ deleteFile: (path: string) => void;
50
+ activeFile: VirtualFile | null;
51
+ openTabs: EditorTab[];
52
+
53
+ // Preview
54
+ previewState: PreviewState;
55
+ previewHtml: string;
56
+ previewError: string | null;
57
+ refreshPreview: () => void;
58
+
59
+ // Terminal
60
+ terminalLines: TerminalLine[];
61
+ pushTerminalLine: (line: TerminalLine) => void;
62
+ clearTerminal: () => void;
63
+ }
64
+
65
+ export interface TerminalLine {
66
+ id: string;
67
+ type: 'info' | 'success' | 'error' | 'warn' | 'command' | 'output';
68
+ text: string;
69
+ timestamp: number;
70
+ }
71
+
72
+ // ─── Context ──────────────────────────────────────────────────────────────────
73
+
74
+ const JXRContext = createContext<JXRContextValue | null>(null);
75
+
76
+ export function useJXR(): JXRContextValue {
77
+ const ctx = useContext(JXRContext);
78
+ if (!ctx) throw new Error('useJXR must be used within JXRProvider');
79
+ return ctx;
80
+ }
81
+
82
+ // ─── Provider ─────────────────────────────────────────────────────────────────
83
+
84
+ export function JXRProvider({ children }: { children: ReactNode }) {
85
+ const [isInitialized, setIsInitialized] = useState(false);
86
+ const [metrics, setMetrics] = useState<JXRRuntimeMetrics | null>(null);
87
+ const [fileTree, setFileTree] = useState<VirtualDirectory | null>(null);
88
+ const [activeFilePath, setActiveFilePath] = useState<string | null>('/src/App.tsx');
89
+ const [openTabs, setOpenTabs] = useState<EditorTab[]>([
90
+ { path: '/src/App.tsx', dirty: false },
91
+ { path: '/src/components/Button.tsx', dirty: false },
92
+ { path: '/src/hooks/useCounter.ts', dirty: false },
93
+ ]);
94
+ const [previewState, setPreviewState] = useState<PreviewState>('idle');
95
+ const [previewHtml, setPreviewHtml] = useState('');
96
+ const [previewError, setPreviewError] = useState<string | null>(null);
97
+ const [terminalLines, setTerminalLines] = useState<TerminalLine[]>([]);
98
+ const previewDebounce = useRef<ReturnType<typeof setTimeout> | null>(null);
99
+ const lineCounter = useRef(0);
100
+
101
+ // ─── Initialize runtime ───────────────────────────────────────────────────
102
+
103
+ useEffect(() => {
104
+ let unsubMetrics: (() => void) | null = null;
105
+ let unsubFS: (() => void) | null = null;
106
+
107
+ const init = async () => {
108
+ pushLine('command', '$ jxr init --edge --moq --crypto');
109
+ pushLine('info', 'Initializing JXR.js Edge Runtime v1.0.0...');
110
+
111
+ await jxrRuntime.init();
112
+
113
+ pushLine('success', '✓ MoQ transport connected (edge://jxr-local)');
114
+ pushLine('success', '✓ Worker pool initialized (cores: ' + (navigator.hardwareConcurrency ?? 4) + ')');
115
+ pushLine('success', '✓ Web Crypto engine ready (AES-GCM-256 + ECDSA P-256)');
116
+ pushLine('success', '✓ Virtual FS mounted (/src)');
117
+ pushLine('success', '✓ Module resolver online (esm.sh CDN)');
118
+ pushLine('output', '');
119
+ pushLine('output', 'JXR.js ready. No build step required.');
120
+
121
+ // Subscribe to metrics
122
+ unsubMetrics = jxrRuntime.onMetrics((m) => setMetrics(m));
123
+
124
+ // Subscribe to FS changes
125
+ unsubFS = jxrRuntime.vfs.onChange(() => {
126
+ setFileTree(jxrRuntime.vfs.buildTree('/'));
127
+ schedulePreviewRefresh();
128
+ });
129
+
130
+ setFileTree(jxrRuntime.vfs.buildTree('/'));
131
+ setIsInitialized(true);
132
+ schedulePreviewRefresh();
133
+ };
134
+
135
+ init().catch((err) => {
136
+ pushLine('error', `✗ Runtime init failed: ${err.message}`);
137
+ });
138
+
139
+ return () => {
140
+ unsubMetrics?.();
141
+ unsubFS?.();
142
+ jxrRuntime.dispose();
143
+ };
144
+ }, []);
145
+
146
+ // ─── Terminal helpers ─────────────────────────────────────────────────────
147
+
148
+ const pushLine = useCallback((type: TerminalLine['type'], text: string) => {
149
+ setTerminalLines((prev) => [
150
+ ...prev.slice(-200), // Keep last 200 lines
151
+ { id: `line-${++lineCounter.current}`, type, text, timestamp: Date.now() },
152
+ ]);
153
+ }, []);
154
+
155
+ const pushTerminalLine = useCallback(
156
+ (line: TerminalLine) => setTerminalLines((prev) => [...prev.slice(-200), line]),
157
+ []
158
+ );
159
+
160
+ const clearTerminal = useCallback(() => setTerminalLines([]), []);
161
+
162
+ // ─── Preview engine ───────────────────────────────────────────────────────
163
+
164
+ const schedulePreviewRefresh = useCallback(() => {
165
+ if (previewDebounce.current) clearTimeout(previewDebounce.current);
166
+ previewDebounce.current = setTimeout(() => {
167
+ refreshPreview();
168
+ }, 300);
169
+ }, []);
170
+
171
+ const refreshPreview = useCallback(() => {
172
+ setPreviewState('building');
173
+ setPreviewError(null);
174
+
175
+ try {
176
+ const html = jxrRuntime.buildPreviewDocument();
177
+ setPreviewHtml(html);
178
+ setPreviewState('ready');
179
+ } catch (err: unknown) {
180
+ const message = err instanceof Error ? err.message : String(err);
181
+ setPreviewError(message);
182
+ setPreviewState('error');
183
+ pushLine('error', `Preview error: ${message}`);
184
+ }
185
+ }, [pushLine]);
186
+
187
+ // ─── File operations ──────────────────────────────────────────────────────
188
+
189
+ const openFile = useCallback((path: string) => {
190
+ setActiveFilePath(path);
191
+ setOpenTabs((prev) => {
192
+ if (prev.find((t) => t.path === path)) return prev;
193
+ return [...prev, { path, dirty: false }];
194
+ });
195
+ }, []);
196
+
197
+ const closeFile = useCallback(
198
+ (path: string) => {
199
+ setOpenTabs((prev) => {
200
+ const next = prev.filter((t) => t.path !== path);
201
+ if (activeFilePath === path && next.length > 0) {
202
+ setActiveFilePath(next[next.length - 1].path);
203
+ }
204
+ return next;
205
+ });
206
+ },
207
+ [activeFilePath]
208
+ );
209
+
210
+ const saveFile = useCallback(
211
+ (path: string, content: string) => {
212
+ jxrRuntime.vfs.write(path, content);
213
+ jxrRuntime.cache.invalidate(path);
214
+ setOpenTabs((prev) =>
215
+ prev.map((t) => (t.path === path ? { ...t, dirty: false } : t))
216
+ );
217
+ pushLine('success', `✓ Saved ${path}`);
218
+ },
219
+ [pushLine]
220
+ );
221
+
222
+ const createFile = useCallback(
223
+ (path: string, content = '') => {
224
+ jxrRuntime.vfs.write(path, content);
225
+ openFile(path);
226
+ pushLine('success', `✓ Created ${path}`);
227
+ },
228
+ [openFile, pushLine]
229
+ );
230
+
231
+ const deleteFile = useCallback(
232
+ (path: string) => {
233
+ jxrRuntime.vfs.delete(path);
234
+ closeFile(path);
235
+ pushLine('warn', `⚠ Deleted ${path}`);
236
+ },
237
+ [closeFile, pushLine]
238
+ );
239
+
240
+ const activeFile = activeFilePath ? jxrRuntime.vfs.read(activeFilePath) : null;
241
+
242
+ const value: JXRContextValue = {
243
+ runtime: jxrRuntime,
244
+ metrics,
245
+ isInitialized,
246
+ fileTree,
247
+ openFile,
248
+ closeFile,
249
+ saveFile,
250
+ createFile,
251
+ deleteFile,
252
+ activeFile,
253
+ openTabs,
254
+ previewState,
255
+ previewHtml,
256
+ previewError,
257
+ refreshPreview,
258
+ terminalLines,
259
+ pushTerminalLine,
260
+ clearTerminal,
261
+ };
262
+
263
+ return <JXRContext.Provider value={value}>{children}</JXRContext.Provider>;
264
+ }
@@ -0,0 +1,64 @@
1
+ import React, { createContext, useContext, useEffect, useState } from "react";
2
+
3
+ type Theme = "light" | "dark";
4
+
5
+ interface ThemeContextType {
6
+ theme: Theme;
7
+ toggleTheme: () => void;
8
+ switchable: boolean;
9
+ }
10
+
11
+ const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
12
+
13
+ interface ThemeProviderProps {
14
+ children: React.ReactNode;
15
+ defaultTheme?: Theme;
16
+ switchable?: boolean;
17
+ }
18
+
19
+ export function ThemeProvider({
20
+ children,
21
+ defaultTheme = "light",
22
+ switchable = false,
23
+ }: ThemeProviderProps) {
24
+ const [theme, setTheme] = useState<Theme>(() => {
25
+ if (switchable) {
26
+ const stored = localStorage.getItem("theme");
27
+ return (stored as Theme) || defaultTheme;
28
+ }
29
+ return defaultTheme;
30
+ });
31
+
32
+ useEffect(() => {
33
+ const root = document.documentElement;
34
+ if (theme === "dark") {
35
+ root.classList.add("dark");
36
+ } else {
37
+ root.classList.remove("dark");
38
+ }
39
+
40
+ if (switchable) {
41
+ localStorage.setItem("theme", theme);
42
+ }
43
+ }, [theme, switchable]);
44
+
45
+ const toggleTheme = () => {
46
+ if (switchable) {
47
+ setTheme(prev => (prev === "light" ? "dark" : "light"));
48
+ }
49
+ };
50
+
51
+ return (
52
+ <ThemeContext.Provider value={{ theme, toggleTheme, switchable }}>
53
+ {children}
54
+ </ThemeContext.Provider>
55
+ );
56
+ }
57
+
58
+ export function useTheme() {
59
+ const context = useContext(ThemeContext);
60
+ if (!context) {
61
+ throw new Error("useTheme must be used within ThemeProvider");
62
+ }
63
+ return context;
64
+ }
@@ -0,0 +1,81 @@
1
+ import { useRef } from "react";
2
+ import { usePersistFn } from "./usePersistFn";
3
+
4
+ export interface UseCompositionReturn<
5
+ T extends HTMLInputElement | HTMLTextAreaElement,
6
+ > {
7
+ onCompositionStart: React.CompositionEventHandler<T>;
8
+ onCompositionEnd: React.CompositionEventHandler<T>;
9
+ onKeyDown: React.KeyboardEventHandler<T>;
10
+ isComposing: () => boolean;
11
+ }
12
+
13
+ export interface UseCompositionOptions<
14
+ T extends HTMLInputElement | HTMLTextAreaElement,
15
+ > {
16
+ onKeyDown?: React.KeyboardEventHandler<T>;
17
+ onCompositionStart?: React.CompositionEventHandler<T>;
18
+ onCompositionEnd?: React.CompositionEventHandler<T>;
19
+ }
20
+
21
+ type TimerResponse = ReturnType<typeof setTimeout>;
22
+
23
+ export function useComposition<
24
+ T extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement,
25
+ >(options: UseCompositionOptions<T> = {}): UseCompositionReturn<T> {
26
+ const {
27
+ onKeyDown: originalOnKeyDown,
28
+ onCompositionStart: originalOnCompositionStart,
29
+ onCompositionEnd: originalOnCompositionEnd,
30
+ } = options;
31
+
32
+ const c = useRef(false);
33
+ const timer = useRef<TimerResponse | null>(null);
34
+ const timer2 = useRef<TimerResponse | null>(null);
35
+
36
+ const onCompositionStart = usePersistFn((e: React.CompositionEvent<T>) => {
37
+ if (timer.current) {
38
+ clearTimeout(timer.current);
39
+ timer.current = null;
40
+ }
41
+ if (timer2.current) {
42
+ clearTimeout(timer2.current);
43
+ timer2.current = null;
44
+ }
45
+ c.current = true;
46
+ originalOnCompositionStart?.(e);
47
+ });
48
+
49
+ const onCompositionEnd = usePersistFn((e: React.CompositionEvent<T>) => {
50
+ // 使用两层 setTimeout 来处理 Safari 浏览器中 compositionEnd 先于 onKeyDown 触发的问题
51
+ timer.current = setTimeout(() => {
52
+ timer2.current = setTimeout(() => {
53
+ c.current = false;
54
+ });
55
+ });
56
+ originalOnCompositionEnd?.(e);
57
+ });
58
+
59
+ const onKeyDown = usePersistFn((e: React.KeyboardEvent<T>) => {
60
+ // 在 composition 状态下,阻止 ESC 和 Enter(非 shift+Enter)事件的冒泡
61
+ if (
62
+ c.current &&
63
+ (e.key === "Escape" || (e.key === "Enter" && !e.shiftKey))
64
+ ) {
65
+ e.stopPropagation();
66
+ return;
67
+ }
68
+ originalOnKeyDown?.(e);
69
+ });
70
+
71
+ const isComposing = usePersistFn(() => {
72
+ return c.current;
73
+ });
74
+
75
+ return {
76
+ onCompositionStart,
77
+ onCompositionEnd,
78
+ onKeyDown,
79
+ isComposing,
80
+ };
81
+ }
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+
3
+ const MOBILE_BREAKPOINT = 768;
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
7
+ undefined
8
+ );
9
+
10
+ React.useEffect(() => {
11
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
12
+ const onChange = () => {
13
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
14
+ };
15
+ mql.addEventListener("change", onChange);
16
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
17
+ return () => mql.removeEventListener("change", onChange);
18
+ }, []);
19
+
20
+ return !!isMobile;
21
+ }
@@ -0,0 +1,20 @@
1
+ import { useRef } from "react";
2
+
3
+ type noop = (...args: any[]) => any;
4
+
5
+ /**
6
+ * usePersistFn instead of useCallback to reduce cognitive load
7
+ */
8
+ export function usePersistFn<T extends noop>(fn: T) {
9
+ const fnRef = useRef<T>(fn);
10
+ fnRef.current = fn;
11
+
12
+ const persistFn = useRef<T>(null);
13
+ if (!persistFn.current) {
14
+ persistFn.current = function (this: unknown, ...args) {
15
+ return fnRef.current!.apply(this, args);
16
+ } as T;
17
+ }
18
+
19
+ return persistFn.current!;
20
+ }