@hyperframes/studio 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 (37) hide show
  1. package/dist/assets/index-B1830ANq.js +78 -0
  2. package/dist/assets/index-KoBceNoU.css +1 -0
  3. package/dist/icons/timeline/audio.svg +7 -0
  4. package/dist/icons/timeline/captions.svg +5 -0
  5. package/dist/icons/timeline/composition.svg +12 -0
  6. package/dist/icons/timeline/image.svg +18 -0
  7. package/dist/icons/timeline/music.svg +10 -0
  8. package/dist/icons/timeline/text.svg +3 -0
  9. package/dist/index.html +13 -0
  10. package/package.json +50 -0
  11. package/src/App.tsx +557 -0
  12. package/src/components/editor/FileTree.tsx +70 -0
  13. package/src/components/editor/PropertyPanel.tsx +209 -0
  14. package/src/components/editor/SourceEditor.tsx +116 -0
  15. package/src/components/nle/CompositionBreadcrumb.tsx +57 -0
  16. package/src/components/nle/NLELayout.tsx +252 -0
  17. package/src/components/nle/NLEPreview.tsx +37 -0
  18. package/src/components/ui/Button.tsx +123 -0
  19. package/src/components/ui/index.ts +2 -0
  20. package/src/hooks/useCodeEditor.ts +82 -0
  21. package/src/hooks/useElementPicker.ts +338 -0
  22. package/src/hooks/useMountEffect.ts +18 -0
  23. package/src/icons/SystemIcons.tsx +130 -0
  24. package/src/index.ts +31 -0
  25. package/src/main.tsx +10 -0
  26. package/src/player/components/AgentActivityTrack.tsx +98 -0
  27. package/src/player/components/Player.tsx +120 -0
  28. package/src/player/components/PlayerControls.tsx +181 -0
  29. package/src/player/components/PreviewPanel.tsx +149 -0
  30. package/src/player/components/Timeline.tsx +431 -0
  31. package/src/player/hooks/useTimelinePlayer.ts +465 -0
  32. package/src/player/index.ts +17 -0
  33. package/src/player/lib/time.ts +5 -0
  34. package/src/player/lib/useMountEffect.ts +10 -0
  35. package/src/player/store/playerStore.ts +93 -0
  36. package/src/styles/studio.css +31 -0
  37. package/src/utils/sourcePatcher.ts +149 -0
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Source Patcher — Maps visual property edits back to source HTML files.
3
+ * Handles inline style updates, attribute changes, and text content.
4
+ */
5
+
6
+ function escapeRegex(s: string): string {
7
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8
+ }
9
+
10
+ export interface PatchOperation {
11
+ type: "inline-style" | "attribute" | "text-content";
12
+ property: string;
13
+ value: string;
14
+ }
15
+
16
+ /**
17
+ * Find which source file contains an element by its ID.
18
+ */
19
+ export function resolveSourceFile(
20
+ elementId: string | null,
21
+ selector: string,
22
+ files: Record<string, string>,
23
+ ): string | null {
24
+ if (!elementId && !selector) return null;
25
+
26
+ // Strategy 1: Search by id attribute
27
+ if (elementId) {
28
+ for (const [path, content] of Object.entries(files)) {
29
+ if (content.includes(`id="${elementId}"`) || content.includes(`id='${elementId}'`)) {
30
+ return path;
31
+ }
32
+ }
33
+ }
34
+
35
+ // Strategy 2: Search by data-composition-id from the selector
36
+ const compIdMatch = selector.match(/data-composition-id="([^"]+)"/);
37
+ if (compIdMatch) {
38
+ const compId = compIdMatch[1];
39
+ for (const [path, content] of Object.entries(files)) {
40
+ if (content.includes(`data-composition-id="${compId}"`)) {
41
+ return path;
42
+ }
43
+ }
44
+ }
45
+
46
+ // Strategy 3: Search by class from the selector
47
+ const classMatch = selector.match(/^\.([a-zA-Z0-9_-]+)/);
48
+ if (classMatch) {
49
+ const cls = classMatch[1];
50
+ for (const [path, content] of Object.entries(files)) {
51
+ if (content.includes(`class="${cls}"`) || content.includes(`class="${cls} `) || content.includes(` ${cls}"`)) {
52
+ return path;
53
+ }
54
+ }
55
+ }
56
+
57
+ // Fallback: index.html
58
+ if ("index.html" in files) return "index.html";
59
+ return null;
60
+ }
61
+
62
+ /**
63
+ * Apply a style property change to an element's inline style in the HTML source.
64
+ */
65
+ function patchInlineStyle(html: string, elementId: string, prop: string, value: string): string {
66
+ // Find the element tag with this id
67
+ const idPattern = new RegExp(`(<[^>]*\\bid="${escapeRegex(elementId)}"[^>]*)>`, "i");
68
+ const match = idPattern.exec(html);
69
+ if (!match) return html;
70
+
71
+ const tag = match[1];
72
+
73
+ // Check if there's an existing style attribute
74
+ const styleMatch = /\bstyle="([^"]*)"/.exec(tag);
75
+ if (styleMatch) {
76
+ const existingStyle = styleMatch[1];
77
+ // Parse existing properties
78
+ const props = new Map<string, string>();
79
+ for (const part of existingStyle.split(";")) {
80
+ const colon = part.indexOf(":");
81
+ if (colon < 0) continue;
82
+ const key = part.slice(0, colon).trim();
83
+ const val = part.slice(colon + 1).trim();
84
+ if (key) props.set(key, val);
85
+ }
86
+ // Update/add the property
87
+ props.set(prop, value);
88
+ // Rebuild style string
89
+ const newStyle = Array.from(props.entries())
90
+ .map(([k, v]) => `${k}: ${v}`)
91
+ .join("; ");
92
+ const newTag = tag.replace(/\bstyle="[^"]*"/, `style="${newStyle}"`);
93
+ return html.replace(tag, newTag);
94
+ } else {
95
+ // No existing style — add one
96
+ const newTag = tag.replace(/>$/, "") + ` style="${prop}: ${value}"`;
97
+ return html.replace(tag, newTag);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Apply an attribute change to an element in the HTML source.
103
+ */
104
+ function patchAttribute(html: string, elementId: string, attr: string, value: string): string {
105
+ const idPattern = new RegExp(`(<[^>]*\\bid="${escapeRegex(elementId)}"[^>]*)>`, "i");
106
+ const match = idPattern.exec(html);
107
+ if (!match) return html;
108
+
109
+ const tag = match[1];
110
+ const fullAttr = attr.startsWith("data-") ? attr : `data-${attr}`;
111
+ const attrPattern = new RegExp(`\\b${fullAttr}="[^"]*"`);
112
+
113
+ if (attrPattern.test(tag)) {
114
+ // Update existing attribute
115
+ const newTag = tag.replace(attrPattern, `${fullAttr}="${value}"`);
116
+ return html.replace(tag, newTag);
117
+ } else {
118
+ // Add new attribute
119
+ const newTag = tag + ` ${fullAttr}="${value}"`;
120
+ return html.replace(tag, newTag);
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Apply a text content change to an element.
126
+ */
127
+ function patchTextContent(html: string, elementId: string, value: string): string {
128
+ // Match the element and its content: <tagname id="elementId"...>content</tagname>
129
+ const pattern = new RegExp(`(<[^>]*\\bid="${elementId}"[^>]*>)([\\s\\S]*?)(<\\/[a-z]+>)`, "i");
130
+ const match = pattern.exec(html);
131
+ if (!match) return html;
132
+ return html.replace(pattern, `${match[1]}${value}${match[3]}`);
133
+ }
134
+
135
+ /**
136
+ * Apply a patch operation to an HTML source file.
137
+ */
138
+ export function applyPatch(html: string, elementId: string, op: PatchOperation): string {
139
+ switch (op.type) {
140
+ case "inline-style":
141
+ return patchInlineStyle(html, elementId, op.property, op.value);
142
+ case "attribute":
143
+ return patchAttribute(html, elementId, op.property, op.value);
144
+ case "text-content":
145
+ return patchTextContent(html, elementId, op.value);
146
+ default:
147
+ return html;
148
+ }
149
+ }