@pinepaper.studio/mcp-server 1.5.4 → 1.5.5
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/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/tools/compact-descriptions.d.ts.map +1 -1
- package/dist/tools/compact-descriptions.js +0 -1
- package/dist/tools/compact-descriptions.js.map +1 -1
- package/dist/tools/definitions.d.ts +1 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +19 -546
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/handlers/camera.d.ts.map +1 -1
- package/dist/tools/handlers/camera.js +7 -1
- package/dist/tools/handlers/camera.js.map +1 -1
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +23 -107
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts.map +1 -1
- package/dist/tools/minimal-descriptions.js +3 -15
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/toolkits.d.ts.map +1 -1
- package/dist/tools/toolkits.js +4 -21
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +1 -35
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +107 -500
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +9 -622
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +1 -75
- package/dist/types/schemas.js.map +1 -1
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +18 -0
- package/dist/version.js.map +1 -0
- package/package.json +1 -1
|
@@ -10,10 +10,6 @@ import { CreateItemInputSchema, ModifyItemInputSchema, AddRelationInputSchema, A
|
|
|
10
10
|
CreateDiagramShapeInputSchema, ConnectInputSchema, ConnectPortsInputSchema, AddPortsInputSchema, AutoLayoutInputSchema, GetDiagramShapesInputSchema, UpdateConnectorInputSchema, RemoveConnectorInputSchema, DiagramModeInputSchema,
|
|
11
11
|
// Agent flow mode types
|
|
12
12
|
AgentStartJobInputSchema, AgentEndJobInputSchema, AgentResetInputSchema, AgentBatchExecuteInputSchema, AgentExportInputSchema, AgentAnalyzeInputSchema,
|
|
13
|
-
// Interactive/Trigger types
|
|
14
|
-
AddTriggerInputSchema, RemoveTriggerInputSchema, QueryTriggersInputSchema,
|
|
15
|
-
// Quiz types
|
|
16
|
-
CreateQuizInputSchema, GetQuizStateInputSchema, ResetQuizInputSchema,
|
|
17
13
|
// Letter collage types
|
|
18
14
|
CreateLetterCollageInputSchema, AnimateLetterCollageInputSchema,
|
|
19
15
|
// Map types
|
|
@@ -292,11 +288,27 @@ app.animate(item, {
|
|
|
292
288
|
({ success: true, itemId: '${itemId}', animationType: '${animationType}' });
|
|
293
289
|
`.trim();
|
|
294
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* PinePaper's keyframe interpolator only tweens `position` when it is an [x, y]
|
|
293
|
+
* ARRAY. Callers (and other agents) routinely pass a { x, y } object, which is
|
|
294
|
+
* silently ignored — the item then stays frozen for the whole animation. Accept
|
|
295
|
+
* both shapes by normalizing { x, y } -> [x, y] before the keyframes are emitted.
|
|
296
|
+
*/
|
|
297
|
+
function normalizeKeyframePositions(keyframes) {
|
|
298
|
+
return (keyframes || []).map((kf) => {
|
|
299
|
+
const props = kf?.properties;
|
|
300
|
+
const p = props?.position;
|
|
301
|
+
if (p && !Array.isArray(p) && typeof p === 'object' && 'x' in p && 'y' in p) {
|
|
302
|
+
return { ...kf, properties: { ...props, position: [p.x, p.y] } };
|
|
303
|
+
}
|
|
304
|
+
return kf;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
295
307
|
/**
|
|
296
308
|
* Template for keyframe animation
|
|
297
309
|
*/
|
|
298
310
|
function generateKeyframeAnimateCode(itemId, keyframes, duration, loop = false, timeOffset, clipInPoint, clipOutPoint) {
|
|
299
|
-
const keyframesJson = JSON.stringify(keyframes, null, 2);
|
|
311
|
+
const keyframesJson = JSON.stringify(normalizeKeyframePositions(keyframes), null, 2);
|
|
300
312
|
const calculatedDuration = duration || Math.max(...keyframes.map(k => k.time));
|
|
301
313
|
const opts = { duration: calculatedDuration, loop };
|
|
302
314
|
if (timeOffset !== undefined)
|
|
@@ -456,35 +468,29 @@ const size = app.getCanvasSize();
|
|
|
456
468
|
*/
|
|
457
469
|
function generateClearCanvasCode() {
|
|
458
470
|
return `
|
|
459
|
-
// Clear all items from canvas
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// Also clear any relations
|
|
478
|
-
if (app.clearAllRelations) {
|
|
479
|
-
app.clearAllRelations();
|
|
471
|
+
// Clear all items from canvas. Prefer app.clearCanvas() — the canonical
|
|
472
|
+
// reset that also tears down relations, the (virtual) camera animation,
|
|
473
|
+
// generators, effects and background. The old manual loop here was broken:
|
|
474
|
+
// it keyed item lookups on entry.id/entry.itemId (the registry getAll()
|
|
475
|
+
// entries don't expose those — getItemById wants the registry id), so it
|
|
476
|
+
// removed 0 items, and it called the non-existent app.clearAllRelations.
|
|
477
|
+
const before = app.itemRegistry ? app.itemRegistry.getAll().length : 0;
|
|
478
|
+
|
|
479
|
+
if (typeof app.clearCanvas === 'function') {
|
|
480
|
+
app.clearCanvas();
|
|
481
|
+
} else {
|
|
482
|
+
// Fallback for older builds without clearCanvas().
|
|
483
|
+
if (app.textItemGroup) app.textItemGroup.removeChildren();
|
|
484
|
+
if (app.patternGroup) app.patternGroup.removeChildren();
|
|
485
|
+
if (app.itemRegistry) app.itemRegistry.clear();
|
|
486
|
+
if (app.relationRegistry) app.relationRegistry.clear();
|
|
480
487
|
}
|
|
481
488
|
|
|
482
|
-
app.historyManager.saveState();
|
|
489
|
+
if (app.historyManager) app.historyManager.saveState();
|
|
483
490
|
|
|
484
|
-
|
|
485
|
-
const remaining = app.itemRegistry.getAll().length;
|
|
491
|
+
const remaining = app.itemRegistry ? app.itemRegistry.getAll().length : 0;
|
|
486
492
|
|
|
487
|
-
({ success: true, removedCount, remainingItems: remaining });
|
|
493
|
+
({ success: true, removedCount: Math.max(0, before - remaining), remainingItems: remaining });
|
|
488
494
|
`.trim();
|
|
489
495
|
}
|
|
490
496
|
/**
|
|
@@ -1178,13 +1184,9 @@ if (typeof app.generators !== 'undefined' && app.generators['${backgroundGenerat
|
|
|
1178
1184
|
const sourceId = nameToId['${source}'];
|
|
1179
1185
|
const targetId = nameToId['${target}'];
|
|
1180
1186
|
if (sourceId && targetId) {
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
const params = ${paramsStr};
|
|
1185
|
-
app.relationManager.addRelation(sourceItem, targetItem, '${type}', params);
|
|
1186
|
-
results.relations.push({ source: '${source}', target: '${target}', type: '${type}' });
|
|
1187
|
-
}
|
|
1187
|
+
const params = ${paramsStr};
|
|
1188
|
+
app.addRelation(sourceId, targetId, '${type}', params);
|
|
1189
|
+
results.relations.push({ source: '${source}', target: '${target}', type: '${type}' });
|
|
1188
1190
|
}
|
|
1189
1191
|
})();
|
|
1190
1192
|
`);
|
|
@@ -1601,14 +1603,7 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
1601
1603
|
if (item.shadowColor || item.shadowBlur) analysis.hasShadows = true;
|
|
1602
1604
|
});
|
|
1603
1605
|
|
|
1604
|
-
//
|
|
1605
|
-
if (app.animationManager) {
|
|
1606
|
-
const animations = app.animationManager.getAll ? app.animationManager.getAll() : (app.animationManager.animations || []);
|
|
1607
|
-
if (animations.length > 0) {
|
|
1608
|
-
analysis.hasAnimations = true;
|
|
1609
|
-
animationSet.add('managed');
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1606
|
+
// FxTool has no animationManager — animations are detected via item.data.animationType walk above
|
|
1612
1607
|
// Check timeline for active animations
|
|
1613
1608
|
if (app.timeline && (app.timeline.isPlaying || app.timeline.animations?.length > 0)) {
|
|
1614
1609
|
analysis.hasAnimations = true;
|
|
@@ -1779,16 +1774,29 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
1779
1774
|
*/
|
|
1780
1775
|
generateBatchOperationCode(op, index) {
|
|
1781
1776
|
switch (op.type) {
|
|
1782
|
-
case 'create':
|
|
1777
|
+
case 'create': {
|
|
1783
1778
|
const pos = op.position || { x: 400, y: 300 };
|
|
1784
|
-
const
|
|
1785
|
-
|
|
1779
|
+
const createProps = (op.properties || {});
|
|
1780
|
+
const props = JSON.stringify(createProps);
|
|
1781
|
+
let createCode = `
|
|
1786
1782
|
const item = app.create('${op.itemType}', { position: { x: ${pos.x}, y: ${pos.y} }, ...${props} });
|
|
1787
|
-
const itemId = item.data && item.data.id ? item.data.id : app.registerItem(item, '${op.itemType}', { source: 'mcp-batch' })
|
|
1783
|
+
const itemId = item.data && item.data.id ? item.data.id : app.registerItem(item, '${op.itemType}', { source: 'mcp-batch' });`;
|
|
1784
|
+
// app.create() does not read blendMode/opacity from its params, but the
|
|
1785
|
+
// underlying Paper.js item supports them directly — apply post-create so
|
|
1786
|
+
// callers can pass them inline in `properties` and have them take effect.
|
|
1787
|
+
if (createProps.blendMode !== undefined) {
|
|
1788
|
+
createCode += `\nif ('blendMode' in item) item.blendMode = ${JSON.stringify(createProps.blendMode)};`;
|
|
1789
|
+
}
|
|
1790
|
+
if (createProps.opacity !== undefined) {
|
|
1791
|
+
createCode += `\nif ('opacity' in item) item.opacity = ${JSON.stringify(createProps.opacity)};`;
|
|
1792
|
+
}
|
|
1793
|
+
createCode += `
|
|
1788
1794
|
// Ensure item is visible above backgrounds/generators
|
|
1789
1795
|
if (item.bringToFront) item.bringToFront();
|
|
1790
1796
|
return { itemId };
|
|
1791
1797
|
`;
|
|
1798
|
+
return createCode;
|
|
1799
|
+
}
|
|
1792
1800
|
case 'modify':
|
|
1793
1801
|
const itemRef = op.itemId?.startsWith('$')
|
|
1794
1802
|
? `itemIds[${op.itemId.substring(1)}]`
|
|
@@ -1869,7 +1877,7 @@ return { success: true, width: ${w}, height: ${h} };
|
|
|
1869
1877
|
const kfItemRef = op.itemId?.startsWith('$')
|
|
1870
1878
|
? `itemIds[${op.itemId.substring(1)}]`
|
|
1871
1879
|
: `'${op.itemId}'`;
|
|
1872
|
-
const kfJson = JSON.stringify(op.keyframes || []);
|
|
1880
|
+
const kfJson = JSON.stringify(normalizeKeyframePositions(op.keyframes || []));
|
|
1873
1881
|
const kfDuration = op.duration || (op.keyframes?.length ? Math.max(...op.keyframes.map(k => k.time)) : 5);
|
|
1874
1882
|
const kfLoop = op.loop ?? false;
|
|
1875
1883
|
return `
|
|
@@ -1883,16 +1891,14 @@ return { itemId: targetId, duration: ${kfDuration}, loop: ${kfLoop} };
|
|
|
1883
1891
|
? `itemIds[${op.itemId.substring(1)}]`
|
|
1884
1892
|
: `'${op.itemId}'`;
|
|
1885
1893
|
const maskOpts = JSON.stringify(op.maskOptions || {});
|
|
1894
|
+
const preset = op.maskPreset || op.maskType || 'wipeLeft';
|
|
1886
1895
|
return `
|
|
1887
1896
|
const targetId = ${maskItemRef};
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
maskOptions: ${maskOpts}
|
|
1894
|
-
});
|
|
1895
|
-
return result;
|
|
1897
|
+
const item = app.getItemById(targetId);
|
|
1898
|
+
if (!item) throw new Error('Item not found: ' + targetId);
|
|
1899
|
+
if (!app.applyAnimatedMask) throw new Error('Mask system not available');
|
|
1900
|
+
const maskedGroup = app.applyAnimatedMask(item, '${preset}', ${maskOpts});
|
|
1901
|
+
return { success: !!maskedGroup, itemId: targetId, preset: '${preset}' };
|
|
1896
1902
|
`;
|
|
1897
1903
|
}
|
|
1898
1904
|
case 'apply_effect': {
|
|
@@ -1940,8 +1946,9 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
1940
1946
|
*/
|
|
1941
1947
|
generateAgentExport(input) {
|
|
1942
1948
|
const validated = AgentExportInputSchema.parse(input);
|
|
1943
|
-
const { platform, format, quality, framing } = validated;
|
|
1949
|
+
const { platform, format, quality, framing, duration } = validated;
|
|
1944
1950
|
const qualityLevel = quality || 'standard';
|
|
1951
|
+
const videoDuration = duration ?? 5;
|
|
1945
1952
|
// Quality settings (bitrate in bps for VideoEncoder)
|
|
1946
1953
|
const qualitySettings = {
|
|
1947
1954
|
draft: { compression: 0.6, fps: 15, dpi: 72, bitrate: 2_000_000 },
|
|
@@ -2055,7 +2062,7 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
2055
2062
|
// ExportEngine._quickExportVideo overrides quality with undefined for
|
|
2056
2063
|
// mp4/webm, _calculateBitrate yields NaN, and VideoEncoder.configure
|
|
2057
2064
|
// rejects with 'malformed bitrate value'.
|
|
2058
|
-
const baseVideoSettings = { format, fps: settings.fps, quality: settings.compression, duration:
|
|
2065
|
+
const baseVideoSettings = { format, fps: settings.fps, quality: settings.compression, duration: ${videoDuration} };
|
|
2059
2066
|
const blobToDataUrl = (b) => new Promise(resolve => {
|
|
2060
2067
|
const r = new FileReader();
|
|
2061
2068
|
r.onloadend = () => resolve(r.result);
|
|
@@ -2178,14 +2185,7 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
2178
2185
|
if (item.shadowColor || item.shadowBlur) hasShadow = true;
|
|
2179
2186
|
});
|
|
2180
2187
|
|
|
2181
|
-
//
|
|
2182
|
-
if (app.animationManager) {
|
|
2183
|
-
const animations = app.animationManager.getAll ? app.animationManager.getAll() : (app.animationManager.animations || []);
|
|
2184
|
-
if (animations.length > 0) {
|
|
2185
|
-
analysis.hasAnimations = true;
|
|
2186
|
-
animationSet.add('managed');
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2188
|
+
// FxTool has no animationManager — animations are detected via item.data.animationType walk above
|
|
2189
2189
|
// Check timeline for active animations
|
|
2190
2190
|
if (app.timeline && (app.timeline.isPlaying || app.timeline.animations?.length > 0)) {
|
|
2191
2191
|
analysis.hasAnimations = true;
|
|
@@ -2249,257 +2249,6 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
2249
2249
|
suggestedPlatforms
|
|
2250
2250
|
};
|
|
2251
2251
|
})();
|
|
2252
|
-
`.trim();
|
|
2253
|
-
}
|
|
2254
|
-
// ===========================================================================
|
|
2255
|
-
// INTERACTIVE TRIGGER CODE GENERATORS
|
|
2256
|
-
// ===========================================================================
|
|
2257
|
-
/**
|
|
2258
|
-
* Generate code for adding a trigger
|
|
2259
|
-
*/
|
|
2260
|
-
generateAddTrigger(input) {
|
|
2261
|
-
const validated = AddTriggerInputSchema.parse(input);
|
|
2262
|
-
const { itemId, event, actions, condition, timelineOffset } = validated;
|
|
2263
|
-
const actionsStr = JSON.stringify(actions);
|
|
2264
|
-
const conditionStr = condition ? `'${condition.replace(/'/g, "\\'")}'` : 'null';
|
|
2265
|
-
const offsetStr = timelineOffset !== undefined ? timelineOffset : 'null';
|
|
2266
|
-
return `
|
|
2267
|
-
// Add trigger to ${itemId}
|
|
2268
|
-
const item = app.getItemById('${itemId}');
|
|
2269
|
-
if (!item) throw new Error('Item not found: ${itemId}');
|
|
2270
|
-
|
|
2271
|
-
const trigger = {
|
|
2272
|
-
event: '${event}',
|
|
2273
|
-
actions: ${actionsStr},
|
|
2274
|
-
condition: ${conditionStr},
|
|
2275
|
-
timelineOffset: ${offsetStr}
|
|
2276
|
-
};
|
|
2277
|
-
|
|
2278
|
-
// Initialize triggers array if needed
|
|
2279
|
-
if (!item.data) item.data = {};
|
|
2280
|
-
if (!item.data.triggers) item.data.triggers = [];
|
|
2281
|
-
|
|
2282
|
-
// Add trigger
|
|
2283
|
-
item.data.triggers.push(trigger);
|
|
2284
|
-
|
|
2285
|
-
// Register event handler
|
|
2286
|
-
if (app.triggerManager) {
|
|
2287
|
-
app.triggerManager.registerTrigger(item, trigger);
|
|
2288
|
-
}
|
|
2289
|
-
|
|
2290
|
-
app.historyManager.saveState();
|
|
2291
|
-
({ success: true, itemId: '${itemId}', event: '${event}', triggerId: item.data.triggers.length - 1 });
|
|
2292
|
-
`.trim();
|
|
2293
|
-
}
|
|
2294
|
-
/**
|
|
2295
|
-
* Generate code for removing a trigger
|
|
2296
|
-
*/
|
|
2297
|
-
generateRemoveTrigger(input) {
|
|
2298
|
-
const validated = RemoveTriggerInputSchema.parse(input);
|
|
2299
|
-
const { itemId, event, removeAll } = validated;
|
|
2300
|
-
if (removeAll) {
|
|
2301
|
-
return `
|
|
2302
|
-
// Remove all triggers from ${itemId}
|
|
2303
|
-
const item = app.getItemById('${itemId}');
|
|
2304
|
-
if (!item) throw new Error('Item not found: ${itemId}');
|
|
2305
|
-
|
|
2306
|
-
const removedCount = item.data?.triggers?.length || 0;
|
|
2307
|
-
if (item.data) item.data.triggers = [];
|
|
2308
|
-
|
|
2309
|
-
if (app.triggerManager) {
|
|
2310
|
-
app.triggerManager.unregisterAll(item);
|
|
2311
|
-
}
|
|
2312
|
-
|
|
2313
|
-
app.historyManager.saveState();
|
|
2314
|
-
({ success: true, itemId: '${itemId}', removedCount, removeAll: true });
|
|
2315
|
-
`.trim();
|
|
2316
|
-
}
|
|
2317
|
-
return `
|
|
2318
|
-
// Remove ${event} trigger from ${itemId}
|
|
2319
|
-
const item = app.getItemById('${itemId}');
|
|
2320
|
-
if (!item) throw new Error('Item not found: ${itemId}');
|
|
2321
|
-
|
|
2322
|
-
let removedCount = 0;
|
|
2323
|
-
if (item.data?.triggers) {
|
|
2324
|
-
const before = item.data.triggers.length;
|
|
2325
|
-
item.data.triggers = item.data.triggers.filter(t => t.event !== '${event}');
|
|
2326
|
-
removedCount = before - item.data.triggers.length;
|
|
2327
|
-
}
|
|
2328
|
-
|
|
2329
|
-
if (app.triggerManager) {
|
|
2330
|
-
app.triggerManager.unregisterEvent(item, '${event}');
|
|
2331
|
-
}
|
|
2332
|
-
|
|
2333
|
-
app.historyManager.saveState();
|
|
2334
|
-
({ success: true, itemId: '${itemId}', event: '${event}', removedCount });
|
|
2335
|
-
`.trim();
|
|
2336
|
-
}
|
|
2337
|
-
/**
|
|
2338
|
-
* Generate code for querying triggers
|
|
2339
|
-
*/
|
|
2340
|
-
generateQueryTriggers(input) {
|
|
2341
|
-
const validated = QueryTriggersInputSchema.parse(input);
|
|
2342
|
-
const { itemId, event } = validated;
|
|
2343
|
-
if (itemId) {
|
|
2344
|
-
return `
|
|
2345
|
-
// Query triggers for ${itemId}
|
|
2346
|
-
const item = app.getItemById('${itemId}');
|
|
2347
|
-
if (!item) throw new Error('Item not found: ${itemId}');
|
|
2348
|
-
|
|
2349
|
-
let triggers = item.data?.triggers || [];
|
|
2350
|
-
${event ? `triggers = triggers.filter(t => t.event === '${event}');` : ''}
|
|
2351
|
-
|
|
2352
|
-
({ success: true, itemId: '${itemId}', triggers, count: triggers.length });
|
|
2353
|
-
`.trim();
|
|
2354
|
-
}
|
|
2355
|
-
return `
|
|
2356
|
-
// Query all triggers
|
|
2357
|
-
const allTriggers = [];
|
|
2358
|
-
const items = app.itemRegistry ? app.itemRegistry.getAll() : [];
|
|
2359
|
-
|
|
2360
|
-
items.forEach(entry => {
|
|
2361
|
-
const item = entry.item;
|
|
2362
|
-
if (item.data?.triggers?.length > 0) {
|
|
2363
|
-
let triggers = item.data.triggers;
|
|
2364
|
-
${event ? `triggers = triggers.filter(t => t.event === '${event}');` : ''}
|
|
2365
|
-
triggers.forEach((trigger, idx) => {
|
|
2366
|
-
allTriggers.push({
|
|
2367
|
-
itemId: entry.id,
|
|
2368
|
-
triggerId: idx,
|
|
2369
|
-
...trigger
|
|
2370
|
-
});
|
|
2371
|
-
});
|
|
2372
|
-
}
|
|
2373
|
-
});
|
|
2374
|
-
|
|
2375
|
-
({ success: true, triggers: allTriggers, count: allTriggers.length${event ? `, event: '${event}'` : ''} });
|
|
2376
|
-
`.trim();
|
|
2377
|
-
}
|
|
2378
|
-
// ===========================================================================
|
|
2379
|
-
// QUIZ CODE GENERATORS
|
|
2380
|
-
// ===========================================================================
|
|
2381
|
-
/**
|
|
2382
|
-
* Generate code for creating a quiz
|
|
2383
|
-
*/
|
|
2384
|
-
generateCreateQuiz(input) {
|
|
2385
|
-
const validated = CreateQuizInputSchema.parse(input);
|
|
2386
|
-
const { title, questions, passingScore, showScore, allowRetry, shuffleQuestions, shuffleOptions } = validated;
|
|
2387
|
-
const quizConfig = JSON.stringify({
|
|
2388
|
-
title: title || 'Untitled Quiz',
|
|
2389
|
-
questions,
|
|
2390
|
-
passingScore: passingScore || 70,
|
|
2391
|
-
showScore: showScore !== false,
|
|
2392
|
-
allowRetry: allowRetry !== false,
|
|
2393
|
-
shuffleQuestions: shuffleQuestions || false,
|
|
2394
|
-
shuffleOptions: shuffleOptions || false,
|
|
2395
|
-
}, null, 2);
|
|
2396
|
-
return `
|
|
2397
|
-
// Create quiz
|
|
2398
|
-
const quizConfig = ${quizConfig};
|
|
2399
|
-
|
|
2400
|
-
// Initialize quiz manager if needed
|
|
2401
|
-
if (!app.quizManager) {
|
|
2402
|
-
app.quizManager = {
|
|
2403
|
-
quizzes: [],
|
|
2404
|
-
activeQuiz: null,
|
|
2405
|
-
createQuiz: function(config) {
|
|
2406
|
-
const quiz = {
|
|
2407
|
-
id: 'quiz_' + Date.now(),
|
|
2408
|
-
...config,
|
|
2409
|
-
state: {
|
|
2410
|
-
currentQuestion: 0,
|
|
2411
|
-
answers: [],
|
|
2412
|
-
score: 0,
|
|
2413
|
-
maxScore: config.questions.reduce((sum, q) => sum + q.points, 0),
|
|
2414
|
-
complete: false
|
|
2415
|
-
}
|
|
2416
|
-
};
|
|
2417
|
-
this.quizzes.push(quiz);
|
|
2418
|
-
this.activeQuiz = quiz;
|
|
2419
|
-
return quiz;
|
|
2420
|
-
}
|
|
2421
|
-
};
|
|
2422
|
-
}
|
|
2423
|
-
|
|
2424
|
-
const quiz = app.quizManager.createQuiz(quizConfig);
|
|
2425
|
-
app.historyManager.saveState();
|
|
2426
|
-
|
|
2427
|
-
({
|
|
2428
|
-
success: true,
|
|
2429
|
-
quizId: quiz.id,
|
|
2430
|
-
title: quiz.title,
|
|
2431
|
-
questionCount: quiz.questions.length,
|
|
2432
|
-
maxScore: quiz.state.maxScore,
|
|
2433
|
-
passingScore: quiz.passingScore
|
|
2434
|
-
});
|
|
2435
|
-
`.trim();
|
|
2436
|
-
}
|
|
2437
|
-
/**
|
|
2438
|
-
* Generate code for getting quiz state
|
|
2439
|
-
*/
|
|
2440
|
-
generateGetQuizState(input) {
|
|
2441
|
-
const validated = GetQuizStateInputSchema.parse(input);
|
|
2442
|
-
const { quizId } = validated;
|
|
2443
|
-
const quizRef = quizId
|
|
2444
|
-
? `app.quizManager.quizzes.find(q => q.id === '${quizId}')`
|
|
2445
|
-
: 'app.quizManager?.activeQuiz';
|
|
2446
|
-
return `
|
|
2447
|
-
// Get quiz state
|
|
2448
|
-
if (!app.quizManager) {
|
|
2449
|
-
return { success: false, error: 'No quiz manager initialized' };
|
|
2450
|
-
}
|
|
2451
|
-
|
|
2452
|
-
const quiz = ${quizRef};
|
|
2453
|
-
if (!quiz) {
|
|
2454
|
-
return { success: false, error: 'Quiz not found' };
|
|
2455
|
-
}
|
|
2456
|
-
|
|
2457
|
-
({
|
|
2458
|
-
success: true,
|
|
2459
|
-
quizId: quiz.id,
|
|
2460
|
-
title: quiz.title,
|
|
2461
|
-
currentQuestion: quiz.state.currentQuestion,
|
|
2462
|
-
totalQuestions: quiz.questions.length,
|
|
2463
|
-
score: quiz.state.score,
|
|
2464
|
-
maxScore: quiz.state.maxScore,
|
|
2465
|
-
answers: quiz.state.answers,
|
|
2466
|
-
complete: quiz.state.complete,
|
|
2467
|
-
passed: quiz.state.complete ? (quiz.state.score / quiz.state.maxScore * 100) >= quiz.passingScore : null
|
|
2468
|
-
});
|
|
2469
|
-
`.trim();
|
|
2470
|
-
}
|
|
2471
|
-
/**
|
|
2472
|
-
* Generate code for resetting a quiz
|
|
2473
|
-
*/
|
|
2474
|
-
generateResetQuiz(input) {
|
|
2475
|
-
const validated = ResetQuizInputSchema.parse(input);
|
|
2476
|
-
const { quizId } = validated;
|
|
2477
|
-
const quizRef = quizId
|
|
2478
|
-
? `app.quizManager.quizzes.find(q => q.id === '${quizId}')`
|
|
2479
|
-
: 'app.quizManager?.activeQuiz';
|
|
2480
|
-
return `
|
|
2481
|
-
// Reset quiz
|
|
2482
|
-
if (!app.quizManager) {
|
|
2483
|
-
return { success: false, error: 'No quiz manager initialized' };
|
|
2484
|
-
}
|
|
2485
|
-
|
|
2486
|
-
const quiz = ${quizRef};
|
|
2487
|
-
if (!quiz) {
|
|
2488
|
-
return { success: false, error: 'Quiz not found' };
|
|
2489
|
-
}
|
|
2490
|
-
|
|
2491
|
-
// Reset state
|
|
2492
|
-
quiz.state = {
|
|
2493
|
-
currentQuestion: 0,
|
|
2494
|
-
answers: [],
|
|
2495
|
-
score: 0,
|
|
2496
|
-
maxScore: quiz.questions.reduce((sum, q) => sum + q.points, 0),
|
|
2497
|
-
complete: false
|
|
2498
|
-
};
|
|
2499
|
-
|
|
2500
|
-
app.historyManager.saveState();
|
|
2501
|
-
|
|
2502
|
-
({ success: true, quizId: quiz.id, reset: true });
|
|
2503
2252
|
`.trim();
|
|
2504
2253
|
}
|
|
2505
2254
|
// ===========================================================================
|
|
@@ -2588,80 +2337,6 @@ app.historyManager.saveState();
|
|
|
2588
2337
|
|
|
2589
2338
|
return result;
|
|
2590
2339
|
})();
|
|
2591
|
-
`.trim();
|
|
2592
|
-
}
|
|
2593
|
-
/**
|
|
2594
|
-
* Generate code to get letter collage options
|
|
2595
|
-
*/
|
|
2596
|
-
generateGetLetterCollageOptions() {
|
|
2597
|
-
return `
|
|
2598
|
-
// Get letter collage options
|
|
2599
|
-
(function() {
|
|
2600
|
-
if (!app.getLetterCollageOptions) {
|
|
2601
|
-
// Return static options if function not available
|
|
2602
|
-
return {
|
|
2603
|
-
success: true,
|
|
2604
|
-
styles: ['tile', 'magazine', 'paperCut', 'fold', 'gradient', 'image'],
|
|
2605
|
-
tilePalettes: {
|
|
2606
|
-
game: ['wordle', 'scrabble'],
|
|
2607
|
-
vibrant: ['candy', 'neon', 'rainbow'],
|
|
2608
|
-
soft: ['pastel', 'cotton'],
|
|
2609
|
-
natural: ['earth', 'ocean', 'forest', 'sunset'],
|
|
2610
|
-
professional: ['corporate', 'minimal', 'slate'],
|
|
2611
|
-
seasonal: ['christmas', 'halloween', 'spring'],
|
|
2612
|
-
magazine: ['magazine', 'newspaper', 'vintage'],
|
|
2613
|
-
paperCraft: ['paperCraft', 'origami', 'craftPaper']
|
|
2614
|
-
},
|
|
2615
|
-
gradientPalettes: ['rainbow', 'sunset', 'ocean', 'fire', 'gold', 'rose', 'ice', 'cyberpunk', 'neonGlow', 'purplePink'],
|
|
2616
|
-
gradientDirections: ['vertical', 'horizontal', 'diagonal', 'radial']
|
|
2617
|
-
};
|
|
2618
|
-
}
|
|
2619
|
-
|
|
2620
|
-
return app.getLetterCollageOptions();
|
|
2621
|
-
})();
|
|
2622
|
-
`.trim();
|
|
2623
|
-
}
|
|
2624
|
-
// ===========================================================================
|
|
2625
|
-
// CANVAS PRESETS CODE GENERATORS
|
|
2626
|
-
// ===========================================================================
|
|
2627
|
-
/**
|
|
2628
|
-
* Generate code to get canvas presets
|
|
2629
|
-
*/
|
|
2630
|
-
generateGetCanvasPresets() {
|
|
2631
|
-
return `
|
|
2632
|
-
// Get canvas presets
|
|
2633
|
-
(function() {
|
|
2634
|
-
if (app.getCanvasPresets) {
|
|
2635
|
-
return app.getCanvasPresets();
|
|
2636
|
-
}
|
|
2637
|
-
|
|
2638
|
-
// Return static presets if function not available
|
|
2639
|
-
return {
|
|
2640
|
-
success: true,
|
|
2641
|
-
presets: [
|
|
2642
|
-
{ key: 'youtube-thumbnail', name: 'YouTube Thumbnail', width: 1280, height: 720, aspectRatio: '16:9', category: 'video' },
|
|
2643
|
-
{ key: 'youtube-short', name: 'YouTube Shorts', width: 1080, height: 1920, aspectRatio: '9:16', category: 'video' },
|
|
2644
|
-
{ key: 'tiktok', name: 'TikTok', width: 1080, height: 1920, aspectRatio: '9:16', category: 'video' },
|
|
2645
|
-
{ key: 'instagram-story', name: 'Instagram Story', width: 1080, height: 1920, aspectRatio: '9:16', category: 'social' },
|
|
2646
|
-
{ key: 'instagram-post', name: 'Instagram Post (Square)', width: 1080, height: 1080, aspectRatio: '1:1', category: 'social' },
|
|
2647
|
-
{ key: 'instagram-landscape', name: 'Instagram Landscape', width: 1080, height: 566, aspectRatio: '1.91:1', category: 'social' },
|
|
2648
|
-
{ key: 'instagram-portrait', name: 'Instagram Portrait', width: 1080, height: 1350, aspectRatio: '4:5', category: 'social' },
|
|
2649
|
-
{ key: 'facebook-post', name: 'Facebook Post', width: 1200, height: 630, aspectRatio: '1.91:1', category: 'social' },
|
|
2650
|
-
{ key: 'facebook-cover', name: 'Facebook Cover', width: 820, height: 312, aspectRatio: '2.63:1', category: 'social' },
|
|
2651
|
-
{ key: 'facebook-story', name: 'Facebook Story', width: 1080, height: 1920, aspectRatio: '9:16', category: 'social' },
|
|
2652
|
-
{ key: 'twitter-post', name: 'Twitter/X Post', width: 1200, height: 675, aspectRatio: '16:9', category: 'social' },
|
|
2653
|
-
{ key: 'twitter-header', name: 'Twitter/X Header', width: 1500, height: 500, aspectRatio: '3:1', category: 'social' },
|
|
2654
|
-
{ key: 'linkedin-post', name: 'LinkedIn Post', width: 1200, height: 627, aspectRatio: '1.91:1', category: 'social' },
|
|
2655
|
-
{ key: 'linkedin-banner', name: 'LinkedIn Banner', width: 1584, height: 396, aspectRatio: '4:1', category: 'social' },
|
|
2656
|
-
{ key: 'pinterest-pin', name: 'Pinterest Pin', width: 1000, height: 1500, aspectRatio: '2:3', category: 'social' },
|
|
2657
|
-
{ key: 'presentation-16x9', name: 'Presentation 16:9', width: 1920, height: 1080, aspectRatio: '16:9', category: 'presentation' },
|
|
2658
|
-
{ key: 'presentation-4x3', name: 'Presentation 4:3', width: 1024, height: 768, aspectRatio: '4:3', category: 'presentation' },
|
|
2659
|
-
{ key: 'hd-720p', name: 'HD 720p', width: 1280, height: 720, aspectRatio: '16:9', category: 'video' },
|
|
2660
|
-
{ key: 'full-hd-1080p', name: 'Full HD 1080p', width: 1920, height: 1080, aspectRatio: '16:9', category: 'video' },
|
|
2661
|
-
{ key: 'default', name: 'Default', width: 800, height: 600, aspectRatio: '4:3', category: 'general' }
|
|
2662
|
-
]
|
|
2663
|
-
};
|
|
2664
|
-
})();
|
|
2665
2340
|
`.trim();
|
|
2666
2341
|
}
|
|
2667
2342
|
// ===========================================================================
|
|
@@ -3446,22 +3121,27 @@ app.historyManager.saveState();
|
|
|
3446
3121
|
const optionsStr = validated.options ? JSON.stringify(validated.options) : '{}';
|
|
3447
3122
|
const keyframesStr = validated.keyframes ? JSON.stringify(validated.keyframes) : 'null';
|
|
3448
3123
|
const maskOptionsStr = validated.maskOptions ? JSON.stringify(validated.maskOptions) : '{}';
|
|
3124
|
+
const preset = validated.preset || validated.maskType || 'wipeLeft';
|
|
3125
|
+
// Per-preset options. Custom keyframes are an animated-mask extension —
|
|
3126
|
+
// when supplied, fall through to maskingSystem.applyCustomMask instead.
|
|
3449
3127
|
return `
|
|
3450
3128
|
// Apply animated mask to ${validated.itemId}
|
|
3451
3129
|
(function() {
|
|
3452
|
-
if (!app.
|
|
3130
|
+
if (!app.applyAnimatedMask) {
|
|
3453
3131
|
return { success: false, error: 'Mask system not available' };
|
|
3454
3132
|
}
|
|
3133
|
+
const item = app.getItemById('${validated.itemId}');
|
|
3134
|
+
if (!item) return { success: false, error: 'Item not found: ${validated.itemId}' };
|
|
3455
3135
|
|
|
3456
3136
|
try {
|
|
3457
|
-
const
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
}
|
|
3464
|
-
return
|
|
3137
|
+
const mergedOpts = Object.assign({}, ${optionsStr}, ${maskOptionsStr});
|
|
3138
|
+
let maskedGroup;
|
|
3139
|
+
if (${keyframesStr} && app.maskingSystem && app.maskingSystem.applyCustomMask) {
|
|
3140
|
+
maskedGroup = app.maskingSystem.applyCustomMask(item, '${preset}', ${keyframesStr}, mergedOpts);
|
|
3141
|
+
} else {
|
|
3142
|
+
maskedGroup = app.applyAnimatedMask(item, '${preset}', mergedOpts);
|
|
3143
|
+
}
|
|
3144
|
+
return { success: !!maskedGroup, itemId: '${validated.itemId}', preset: '${preset}' };
|
|
3465
3145
|
} catch (error) {
|
|
3466
3146
|
return { success: false, error: error.message };
|
|
3467
3147
|
}
|
|
@@ -3475,17 +3155,15 @@ app.historyManager.saveState();
|
|
|
3475
3155
|
return `
|
|
3476
3156
|
// Apply custom mask to ${validated.itemId}
|
|
3477
3157
|
(function() {
|
|
3478
|
-
if (!app.
|
|
3158
|
+
if (!app.maskingSystem || !app.maskingSystem.applyCustomMask) {
|
|
3479
3159
|
return { success: false, error: 'Mask system not available' };
|
|
3480
3160
|
}
|
|
3161
|
+
const item = app.getItemById('${validated.itemId}');
|
|
3162
|
+
if (!item) return { success: false, error: 'Item not found: ${validated.itemId}' };
|
|
3481
3163
|
|
|
3482
3164
|
try {
|
|
3483
|
-
const
|
|
3484
|
-
|
|
3485
|
-
keyframes: ${keyframesStr},
|
|
3486
|
-
maskOptions: ${maskOptionsStr}
|
|
3487
|
-
});
|
|
3488
|
-
return result;
|
|
3165
|
+
const maskedGroup = app.maskingSystem.applyCustomMask(item, '${validated.maskType}', ${keyframesStr}, ${maskOptionsStr});
|
|
3166
|
+
return { success: !!maskedGroup, itemId: '${validated.itemId}', maskType: '${validated.maskType}' };
|
|
3489
3167
|
} catch (error) {
|
|
3490
3168
|
return { success: false, error: error.message };
|
|
3491
3169
|
}
|
|
@@ -3497,12 +3175,17 @@ app.historyManager.saveState();
|
|
|
3497
3175
|
return `
|
|
3498
3176
|
// Remove mask from ${validated.itemId}
|
|
3499
3177
|
(function() {
|
|
3500
|
-
if (!app.
|
|
3178
|
+
if (!app.removeMask) {
|
|
3501
3179
|
return { success: false, error: 'Mask system not available' };
|
|
3502
3180
|
}
|
|
3181
|
+
// The item id may point at either the masked group OR the original item;
|
|
3182
|
+
// app.removeMask expects the masked group, so resolve and walk up if needed.
|
|
3183
|
+
const item = app.getItemById('${validated.itemId}');
|
|
3184
|
+
if (!item) return { success: false, error: 'Item not found: ${validated.itemId}' };
|
|
3185
|
+
const maskedGroup = (item.data && item.data.isMaskedGroup) ? item : (item.parent && item.parent.data && item.parent.data.isMaskedGroup ? item.parent : item);
|
|
3503
3186
|
|
|
3504
3187
|
try {
|
|
3505
|
-
|
|
3188
|
+
app.removeMask(maskedGroup);
|
|
3506
3189
|
return { success: true, itemId: '${validated.itemId}', message: 'Mask removed' };
|
|
3507
3190
|
} catch (error) {
|
|
3508
3191
|
return { success: false, error: error.message };
|
|
@@ -3596,10 +3279,12 @@ app.historyManager.saveState();
|
|
|
3596
3279
|
})();
|
|
3597
3280
|
`.trim();
|
|
3598
3281
|
}
|
|
3282
|
+
// =============================================================================
|
|
3283
|
+
// TEMPLATE
|
|
3284
|
+
// =============================================================================
|
|
3599
3285
|
generateApplyTemplate(input) {
|
|
3600
3286
|
const { templateId, category, listOnly } = input;
|
|
3601
3287
|
if (listOnly || !templateId) {
|
|
3602
|
-
// List mode: return available templates, optionally filtered by category
|
|
3603
3288
|
const categoryFilter = category ? `'${category}'` : 'null';
|
|
3604
3289
|
return `
|
|
3605
3290
|
// List available templates
|
|
@@ -3625,7 +3310,6 @@ app.historyManager.saveState();
|
|
|
3625
3310
|
})();
|
|
3626
3311
|
`.trim();
|
|
3627
3312
|
}
|
|
3628
|
-
// Load mode: apply a specific template
|
|
3629
3313
|
return `
|
|
3630
3314
|
// Apply template: ${templateId}
|
|
3631
3315
|
(async function() {
|
|
@@ -3901,7 +3585,7 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const ite
|
|
|
3901
3585
|
// Select all items
|
|
3902
3586
|
(function() {
|
|
3903
3587
|
app.selectAll();
|
|
3904
|
-
const items = app.
|
|
3588
|
+
const items = app.getSelectedItems().map(i => i.data?.itemId || i.name || i.id);
|
|
3905
3589
|
return { success: true, action: 'select_all', count: items.length, items };
|
|
3906
3590
|
})();`.trim();
|
|
3907
3591
|
case 'deselect_all':
|
|
@@ -3915,7 +3599,7 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const ite
|
|
|
3915
3599
|
return `
|
|
3916
3600
|
// Get current selection
|
|
3917
3601
|
(function() {
|
|
3918
|
-
const items = app.
|
|
3602
|
+
const items = app.getSelectedItems().map(i => ({
|
|
3919
3603
|
itemId: i.data?.itemId || i.name || i.id,
|
|
3920
3604
|
type: i.data?.itemType || i.className,
|
|
3921
3605
|
bounds: i.bounds ? { x: i.bounds.x, y: i.bounds.y, width: i.bounds.width, height: i.bounds.height } : null
|
|
@@ -3926,7 +3610,7 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const ite
|
|
|
3926
3610
|
return `
|
|
3927
3611
|
// Delete selected items
|
|
3928
3612
|
(function() {
|
|
3929
|
-
const selection = app.
|
|
3613
|
+
const selection = app.getSelectedItems();
|
|
3930
3614
|
const deleted = selection.map(i => i.data?.itemId || i.name || i.id);
|
|
3931
3615
|
app.deleteSelected();
|
|
3932
3616
|
return { success: true, action: 'delete_selected', deleted, count: deleted.length };
|
|
@@ -4160,29 +3844,6 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const ite
|
|
|
4160
3844
|
return `(function() { return { error: 'Unknown precomp action: ${input.action}' }; })();`;
|
|
4161
3845
|
}
|
|
4162
3846
|
}
|
|
4163
|
-
generateView(input) {
|
|
4164
|
-
switch (input.action) {
|
|
4165
|
-
case 'fit': {
|
|
4166
|
-
const mode = input.mode || 'content';
|
|
4167
|
-
const padding = input.padding ?? 20;
|
|
4168
|
-
return `
|
|
4169
|
-
// Fit view to ${mode}
|
|
4170
|
-
(function() {
|
|
4171
|
-
app.fitView('${mode}', ${padding});
|
|
4172
|
-
return { success: true, action: 'fit', mode: '${mode}', padding: ${padding} };
|
|
4173
|
-
})();`.trim();
|
|
4174
|
-
}
|
|
4175
|
-
case 'get_state':
|
|
4176
|
-
return `
|
|
4177
|
-
// Get current view state
|
|
4178
|
-
(function() {
|
|
4179
|
-
const state = app.getViewState();
|
|
4180
|
-
return { success: true, action: 'get_state', ...state };
|
|
4181
|
-
})();`.trim();
|
|
4182
|
-
default:
|
|
4183
|
-
return `(function() { return { error: 'Unknown view action: ${input.action}' }; })();`;
|
|
4184
|
-
}
|
|
4185
|
-
}
|
|
4186
3847
|
generateBackground(input) {
|
|
4187
3848
|
switch (input.action) {
|
|
4188
3849
|
case 'set': {
|
|
@@ -4226,8 +3887,9 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const ite
|
|
|
4226
3887
|
return `
|
|
4227
3888
|
// Get background info
|
|
4228
3889
|
(function() {
|
|
4229
|
-
|
|
4230
|
-
|
|
3890
|
+
// FxTool exposes getBackgroundMode() returning the mode string only.
|
|
3891
|
+
const mode = app.getBackgroundMode ? app.getBackgroundMode() : null;
|
|
3892
|
+
return { success: true, action: 'get', mode };
|
|
4231
3893
|
})();`.trim();
|
|
4232
3894
|
default:
|
|
4233
3895
|
return `(function() { return { error: 'Unknown background action: ${input.action}' }; })();`;
|
|
@@ -4435,61 +4097,6 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const ite
|
|
|
4435
4097
|
}
|
|
4436
4098
|
}
|
|
4437
4099
|
// ===========================================================================
|
|
4438
|
-
// STORAGE
|
|
4439
|
-
// ===========================================================================
|
|
4440
|
-
generateStorage(input) {
|
|
4441
|
-
const guard = `if (!app.storageManager) return { error: 'StorageManager not available' };`;
|
|
4442
|
-
switch (input.action) {
|
|
4443
|
-
case 'save': {
|
|
4444
|
-
const name = input.name ? JSON.stringify(input.name) : "'Untitled Project'";
|
|
4445
|
-
const thumbnailCode = input.thumbnail
|
|
4446
|
-
? `const thumbnail = app.exportThumbnail ? app.exportThumbnail() : null;\n if (thumbnail) project.thumbnail = thumbnail;`
|
|
4447
|
-
: '';
|
|
4448
|
-
return `
|
|
4449
|
-
// Save project
|
|
4450
|
-
(async function() {
|
|
4451
|
-
${guard}
|
|
4452
|
-
const project = { name: ${name}, data: app.serialize() };
|
|
4453
|
-
${thumbnailCode}
|
|
4454
|
-
const projectId = await app.storageManager.saveProject(project);
|
|
4455
|
-
return { success: true, action: 'save', projectId, name: ${name} };
|
|
4456
|
-
})();`.trim();
|
|
4457
|
-
}
|
|
4458
|
-
case 'load': {
|
|
4459
|
-
const projId = JSON.stringify(input.projectId || '');
|
|
4460
|
-
return `
|
|
4461
|
-
// Load project
|
|
4462
|
-
(async function() {
|
|
4463
|
-
${guard}
|
|
4464
|
-
const project = await app.storageManager.loadProject(${projId});
|
|
4465
|
-
if (!project) return { error: 'Project not found: ' + ${projId} };
|
|
4466
|
-
app.deserialize(project.data);
|
|
4467
|
-
return { success: true, action: 'load', projectId: ${projId}, name: project.name };
|
|
4468
|
-
})();`.trim();
|
|
4469
|
-
}
|
|
4470
|
-
case 'list':
|
|
4471
|
-
return `
|
|
4472
|
-
// List saved projects
|
|
4473
|
-
(async function() {
|
|
4474
|
-
${guard}
|
|
4475
|
-
const projects = await app.storageManager.listProjects();
|
|
4476
|
-
return { success: true, action: 'list', projects: projects.map(p => ({ id: p.id, name: p.name, updatedAt: p.updatedAt })), count: projects.length };
|
|
4477
|
-
})();`.trim();
|
|
4478
|
-
case 'delete': {
|
|
4479
|
-
const projId = JSON.stringify(input.projectId || '');
|
|
4480
|
-
return `
|
|
4481
|
-
// Delete project
|
|
4482
|
-
(async function() {
|
|
4483
|
-
${guard}
|
|
4484
|
-
await app.storageManager.deleteProject(${projId});
|
|
4485
|
-
return { success: true, action: 'delete', projectId: ${projId} };
|
|
4486
|
-
})();`.trim();
|
|
4487
|
-
}
|
|
4488
|
-
default:
|
|
4489
|
-
return `(function() { return { error: 'Unknown storage action: ${input.action}' }; })();`;
|
|
4490
|
-
}
|
|
4491
|
-
}
|
|
4492
|
-
// ===========================================================================
|
|
4493
4100
|
// INTERACTION
|
|
4494
4101
|
// ===========================================================================
|
|
4495
4102
|
generateInteraction(input) {
|
|
@@ -4559,8 +4166,8 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const ite
|
|
|
4559
4166
|
return `
|
|
4560
4167
|
// Export widget (pp:PinePaper ontology JSON)
|
|
4561
4168
|
(async function() {
|
|
4562
|
-
if (!app.exportWidget) return { error: 'Widget export not available' };
|
|
4563
|
-
const result = await app.exportWidget(${JSON.stringify(opts)});
|
|
4169
|
+
if (!app.exportEngine || !app.exportEngine.exportWidget) return { error: 'Widget export not available' };
|
|
4170
|
+
const result = await app.exportEngine.exportWidget(${JSON.stringify(opts)});
|
|
4564
4171
|
return { success: true, json: result.json, filename: result.filename, embedCode: result.embedCode, metadata: result.data?.metadata };
|
|
4565
4172
|
})();`.trim();
|
|
4566
4173
|
}
|
|
@@ -4573,8 +4180,8 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const ite
|
|
|
4573
4180
|
return `
|
|
4574
4181
|
// Export widget as self-contained HTML
|
|
4575
4182
|
(async function() {
|
|
4576
|
-
if (!app.exportWidgetHTML) return { error: 'Widget HTML export not available' };
|
|
4577
|
-
const result = await app.exportWidgetHTML(${JSON.stringify(opts)});
|
|
4183
|
+
if (!app.exportEngine || !app.exportEngine.exportWidgetHTML) return { error: 'Widget HTML export not available' };
|
|
4184
|
+
const result = await app.exportEngine.exportWidgetHTML(${JSON.stringify(opts)});
|
|
4578
4185
|
return { success: true, html: result.html, estimatedSize: result.estimatedSize, analysis: { itemTypes: [...result.analysis.itemTypes], relationTypes: [...result.analysis.relationTypes], hasSimpleAnimations: result.analysis.hasSimpleAnimations, hasKeyframeAnimations: result.analysis.hasKeyframeAnimations, hasMasks: result.analysis.hasMasks } };
|
|
4579
4186
|
})();`.trim();
|
|
4580
4187
|
}
|