@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.
- package/dist/assets/index-B1830ANq.js +78 -0
- package/dist/assets/index-KoBceNoU.css +1 -0
- package/dist/icons/timeline/audio.svg +7 -0
- package/dist/icons/timeline/captions.svg +5 -0
- package/dist/icons/timeline/composition.svg +12 -0
- package/dist/icons/timeline/image.svg +18 -0
- package/dist/icons/timeline/music.svg +10 -0
- package/dist/icons/timeline/text.svg +3 -0
- package/dist/index.html +13 -0
- package/package.json +50 -0
- package/src/App.tsx +557 -0
- package/src/components/editor/FileTree.tsx +70 -0
- package/src/components/editor/PropertyPanel.tsx +209 -0
- package/src/components/editor/SourceEditor.tsx +116 -0
- package/src/components/nle/CompositionBreadcrumb.tsx +57 -0
- package/src/components/nle/NLELayout.tsx +252 -0
- package/src/components/nle/NLEPreview.tsx +37 -0
- package/src/components/ui/Button.tsx +123 -0
- package/src/components/ui/index.ts +2 -0
- package/src/hooks/useCodeEditor.ts +82 -0
- package/src/hooks/useElementPicker.ts +338 -0
- package/src/hooks/useMountEffect.ts +18 -0
- package/src/icons/SystemIcons.tsx +130 -0
- package/src/index.ts +31 -0
- package/src/main.tsx +10 -0
- package/src/player/components/AgentActivityTrack.tsx +98 -0
- package/src/player/components/Player.tsx +120 -0
- package/src/player/components/PlayerControls.tsx +181 -0
- package/src/player/components/PreviewPanel.tsx +149 -0
- package/src/player/components/Timeline.tsx +431 -0
- package/src/player/hooks/useTimelinePlayer.ts +465 -0
- package/src/player/index.ts +17 -0
- package/src/player/lib/time.ts +5 -0
- package/src/player/lib/useMountEffect.ts +10 -0
- package/src/player/store/playerStore.ts +93 -0
- package/src/styles/studio.css +31 -0
- 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
|
+
}
|