@hyperframes/studio-server 0.7.16 → 0.7.18

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/index.d.ts CHANGED
@@ -32,6 +32,45 @@ interface LintResult {
32
32
  fixHint?: string;
33
33
  }>;
34
34
  }
35
+ interface StudioSelectionTextField {
36
+ key: string;
37
+ label: string;
38
+ value: string;
39
+ tagName: string;
40
+ source: "self" | "child" | "text-node";
41
+ }
42
+ interface StudioSelectionSnapshot {
43
+ schemaVersion: 1;
44
+ projectId: string;
45
+ compositionPath: string;
46
+ sourceFile: string;
47
+ currentTime: number;
48
+ target: {
49
+ id?: string | null;
50
+ hfId?: string;
51
+ selector?: string;
52
+ selectorIndex?: number;
53
+ };
54
+ label: string;
55
+ tagName: string;
56
+ boundingBox: {
57
+ x: number;
58
+ y: number;
59
+ width: number;
60
+ height: number;
61
+ };
62
+ textContent: string | null;
63
+ dataAttributes: Record<string, string>;
64
+ inlineStyles: Record<string, string>;
65
+ computedStyles: Record<string, string>;
66
+ textFields: StudioSelectionTextField[];
67
+ capabilities: Record<string, boolean | string | undefined>;
68
+ thumbnailUrl: string;
69
+ }
70
+ interface StudioSelectionResponse {
71
+ selection: StudioSelectionSnapshot | null;
72
+ updatedAt: string | null;
73
+ }
35
74
  /**
36
75
  * Adapter interface — injected by each consumer to handle host-specific behavior.
37
76
  * The shared API module calls these methods; each host (vite dev, CLI embedded)
@@ -164,4 +203,4 @@ declare function getMimeType(path: string): string;
164
203
  */
165
204
  declare function buildSubCompositionHtml(projectDir: string, compPath: string, runtimeUrl: string, baseHref?: string): string | null;
166
205
 
167
- export { type LintResult, MIME_TYPES, type RenderJobState, type ResolvedProject, type StudioApiAdapter, buildSubCompositionHtml, createProjectSignature, createStudioApi, getMimeType, walkDir };
206
+ export { type LintResult, MIME_TYPES, type RenderJobState, type ResolvedProject, type StudioApiAdapter, type StudioSelectionResponse, type StudioSelectionSnapshot, type StudioSelectionTextField, buildSubCompositionHtml, createProjectSignature, createStudioApi, getMimeType, walkDir };
package/dist/index.js CHANGED
@@ -4114,6 +4114,104 @@ function registerRegistryRoutes(api, adapter) {
4114
4114
  });
4115
4115
  }
4116
4116
 
4117
+ // src/routes/selection.ts
4118
+ function isRecord2(value) {
4119
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4120
+ }
4121
+ function isFiniteNumber(value) {
4122
+ return typeof value === "number" && Number.isFinite(value);
4123
+ }
4124
+ function isStringRecord(value) {
4125
+ return isRecord2(value) && Object.values(value).every((v) => typeof v === "string");
4126
+ }
4127
+ function hasString(value, key) {
4128
+ return typeof value[key] === "string";
4129
+ }
4130
+ function hasRequiredStrings(value, keys) {
4131
+ return keys.every((key) => hasString(value, key));
4132
+ }
4133
+ function hasOptionalString(value, key) {
4134
+ return value[key] === void 0 || typeof value[key] === "string";
4135
+ }
4136
+ function hasOptionalNullableString(value, key) {
4137
+ return value[key] == null || typeof value[key] === "string";
4138
+ }
4139
+ function hasOptionalNumber(value, key) {
4140
+ return value[key] === void 0 || isFiniteNumber(value[key]);
4141
+ }
4142
+ function isBoundingBox(value) {
4143
+ return isRecord2(value) && ["x", "y", "width", "height"].every((key) => isFiniteNumber(value[key]));
4144
+ }
4145
+ function isTarget(value) {
4146
+ if (!isRecord2(value)) return false;
4147
+ return hasOptionalNullableString(value, "id") && hasOptionalString(value, "hfId") && hasOptionalString(value, "selector") && hasOptionalNumber(value, "selectorIndex");
4148
+ }
4149
+ function isTextField(value) {
4150
+ return isRecord2(value) && hasRequiredStrings(value, ["key", "label", "value", "tagName"]) && ["self", "child", "text-node"].includes(value.source);
4151
+ }
4152
+ function isTextFields(value) {
4153
+ return Array.isArray(value) && value.every(isTextField);
4154
+ }
4155
+ function isSelectionSnapshot(value) {
4156
+ if (!isRecord2(value)) return false;
4157
+ const checks = [
4158
+ value.schemaVersion === 1 && hasRequiredStrings(value, [
4159
+ "projectId",
4160
+ "compositionPath",
4161
+ "sourceFile",
4162
+ "label",
4163
+ "tagName",
4164
+ "thumbnailUrl"
4165
+ ]),
4166
+ isFiniteNumber(value.currentTime),
4167
+ isTarget(value.target),
4168
+ isBoundingBox(value.boundingBox),
4169
+ value.textContent === null || typeof value.textContent === "string",
4170
+ isStringRecord(value.dataAttributes),
4171
+ isStringRecord(value.inlineStyles),
4172
+ isStringRecord(value.computedStyles),
4173
+ isTextFields(value.textFields),
4174
+ isRecord2(value.capabilities)
4175
+ ];
4176
+ return checks.every(Boolean);
4177
+ }
4178
+ function registerSelectionRoutes(api, adapter) {
4179
+ const selections = /* @__PURE__ */ new Map();
4180
+ api.get("/projects/:id/selection", async (c) => {
4181
+ const project = await adapter.resolveProject(c.req.param("id"));
4182
+ if (!project) return c.json({ error: "not found" }, 404);
4183
+ const stored = selections.get(project.id);
4184
+ return c.json({
4185
+ selection: stored?.selection ?? null,
4186
+ updatedAt: stored?.updatedAt ?? null
4187
+ });
4188
+ });
4189
+ api.put("/projects/:id/selection", async (c) => {
4190
+ const project = await adapter.resolveProject(c.req.param("id"));
4191
+ if (!project) return c.json({ error: "not found" }, 404);
4192
+ let body;
4193
+ try {
4194
+ body = await c.req.json();
4195
+ } catch {
4196
+ return c.json({ error: "invalid json" }, 400);
4197
+ }
4198
+ if (!isRecord2(body) || !("selection" in body)) {
4199
+ return c.json({ error: "missing selection" }, 400);
4200
+ }
4201
+ if (body.selection === null) {
4202
+ selections.delete(project.id);
4203
+ return c.json({ ok: true, selection: null, updatedAt: null });
4204
+ }
4205
+ if (!isSelectionSnapshot(body.selection)) {
4206
+ return c.json({ error: "invalid selection" }, 400);
4207
+ }
4208
+ const selection = { ...body.selection, projectId: project.id };
4209
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
4210
+ selections.set(project.id, { selection, updatedAt });
4211
+ return c.json({ ok: true, selection, updatedAt });
4212
+ });
4213
+ }
4214
+
4117
4215
  // src/createStudioApi.ts
4118
4216
  function createStudioApi(adapter) {
4119
4217
  const api = new Hono();
@@ -4124,6 +4222,7 @@ function createStudioApi(adapter) {
4124
4222
  registerLintRoutes(api, adapter);
4125
4223
  registerRenderRoutes(api, adapter);
4126
4224
  registerThumbnailRoutes(api, adapter);
4225
+ registerSelectionRoutes(api, adapter);
4127
4226
  registerWaveformRoutes(api, adapter);
4128
4227
  registerFontRoutes(api);
4129
4228
  registerRegistryRoutes(api, adapter);