@daydreamui/paper 0.0.0-stage → 0.3.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/index.js ADDED
@@ -0,0 +1,582 @@
1
+ import { className, createComponent, delegateEvents, effect, insert, memo, readShallow, ref, setAttribute, style, template } from "@solidjs/web";
2
+ import { For, createSignal, onCleanup, onSettled, untrack } from "solid-js";
3
+ var NOT_REACHABLE = "Paper is not reachable: open Paper desktop with the file you want to read";
4
+ function createProbe({ ask, answer, target, debounceMs = 400 }) {
5
+ let running = null;
6
+ let again = false;
7
+ let timer;
8
+ let gone = false;
9
+ const run = async () => {
10
+ let reachable;
11
+ try {
12
+ reachable = await ask() === true;
13
+ } catch {
14
+ reachable = false;
15
+ }
16
+ if (!gone) answer(reachable);
17
+ };
18
+ const probe = (soon = false) => {
19
+ if (gone) return;
20
+ if (soon) {
21
+ clearTimeout(timer);
22
+ timer = setTimeout(() => probe(), debounceMs);
23
+ return;
24
+ }
25
+ if (running !== null) {
26
+ again = true;
27
+ return;
28
+ }
29
+ running = run().finally(() => {
30
+ running = null;
31
+ if (again) {
32
+ again = false;
33
+ probe();
34
+ }
35
+ });
36
+ };
37
+ const focused = () => probe(true);
38
+ target?.addEventListener("focus", focused);
39
+ return {
40
+ probe,
41
+ async settled() {
42
+ while (running !== null) await running;
43
+ },
44
+ dispose() {
45
+ gone = true;
46
+ clearTimeout(timer);
47
+ target?.removeEventListener("focus", focused);
48
+ }
49
+ };
50
+ }
51
+ var messageOf = (error) => error instanceof Error ? error.message : String(error);
52
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
53
+ async function inBatches(items, width, work) {
54
+ const answers = new Array(items.length);
55
+ let next = 0;
56
+ const worker = async () => {
57
+ for (;;) {
58
+ const i = next++;
59
+ if (i >= items.length) return;
60
+ answers[i] = await work(items[i]);
61
+ }
62
+ };
63
+ await Promise.all(Array.from({ length: Math.min(width, items.length) }, worker));
64
+ return answers;
65
+ }
66
+ var ended = (sentence) => /[.!?]$/.test(sentence) ? sentence : `${sentence}.`;
67
+ var REFERENCE_KIND = "daydream.paper.reference";
68
+ var LEGACY_REFERENCE_KIND = "mrbavio.paper.reference";
69
+ var MAX_REFERENCE_WIDTH = 1200;
70
+ var NO_NODE = "Select an artboard in Paper first.";
71
+ var TOO_SLOW = "Paper took too long to answer; try again.";
72
+ var tooManySelected = (count) => `${count} nodes are selected in Paper; a gesture takes at most 12 at once. Select fewer.`;
73
+ var IMAGE_EXTENSIONS = {
74
+ "image/jpeg": "jpg",
75
+ "image/png": "png",
76
+ "image/webp": "webp",
77
+ "image/gif": "gif",
78
+ "image/avif": "avif"
79
+ };
80
+ var extensionOf = (mimeType) => Object.hasOwn(IMAGE_EXTENSIONS, mimeType) ? IMAGE_EXTENSIONS[mimeType] : "png";
81
+ var nonEmpty = (value) => typeof value === "string" && value !== "";
82
+ var nullOrString = (value) => value === null || typeof value === "string";
83
+ var nullOrNumber = (value) => value === null || typeof value === "number" && Number.isFinite(value);
84
+ var isStrings = (value) => Array.isArray(value) && value.every((each) => typeof each === "string");
85
+ function isPaperNode(value) {
86
+ return isRecord(value) && nonEmpty(value["id"]) && typeof value["name"] === "string" && typeof value["type"] === "string" && nullOrNumber(value["width"]) && nullOrNumber(value["height"]);
87
+ }
88
+ function isScreenshotAnswer(value) {
89
+ return isRecord(value) && typeof value["mimeType"] === "string" && typeof value["base64"] === "string" && nullOrString(value["name"]) && nullOrString(value["nodeId"]);
90
+ }
91
+ function isSelectionAnswer(value) {
92
+ if (!isRecord(value)) return false;
93
+ switch (value["kind"]) {
94
+ case "nodes": return typeof value["fileId"] === "string" && typeof value["fileName"] === "string" && Array.isArray(value["nodes"]) && value["nodes"].every(isPaperNode);
95
+ case "none": return typeof value["fileId"] === "string" && typeof value["fileName"] === "string";
96
+ case "late": return true;
97
+ case "unreachable": return typeof value["said"] === "string";
98
+ default: return false;
99
+ }
100
+ }
101
+ function isPageAnswer(value) {
102
+ return isRecord(value) && nonEmpty(value["path"]) && nonEmpty(value["viewportId"]) && typeof value["name"] === "string" && isStrings(value["notes"]);
103
+ }
104
+ var positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0;
105
+ function isLandRequest(value) {
106
+ return isRecord(value) && typeof value["html"] === "string" && nonEmpty(value["path"]) && isRecord(value["frame"]) && positive(value["frame"]["width"]) && positive(value["frame"]["height"]);
107
+ }
108
+ var PROJECT_FILE = /^assets\/(?:[A-Za-z0-9_-]{1,80}\/){0,7}[A-Za-z0-9_-]{1,80}\.[A-Za-z0-9]{1,8}$/;
109
+ var FIELDS = [
110
+ "src",
111
+ "name",
112
+ "nodeId"
113
+ ];
114
+ function referencePayloadProblem(raw, where) {
115
+ if (!isRecord(raw)) return `${where} is not a Paper image payload`;
116
+ if (Object.keys(raw).find((key) => !FIELDS.includes(key)) !== void 0) return `${where} may only contain src, name and nodeId`;
117
+ if (typeof raw["src"] !== "string" || !PROJECT_FILE.test(raw["src"])) return `${where}.src must be a file of the project's own, assets/<file>`;
118
+ if (typeof raw["name"] !== "string" || raw["name"].trim() === "") return `${where}.name must be a non-empty string`;
119
+ if (raw["nodeId"] !== null && (typeof raw["nodeId"] !== "string" || raw["nodeId"] === "")) return `${where}.nodeId must be a non-empty string or null`;
120
+ return null;
121
+ }
122
+ function describeReference(payload) {
123
+ return `Paper image · ${payload.name} · ${payload.nodeId ?? "no node id"}`;
124
+ }
125
+ var couldNot = (name, done, reason) => ended(reason === "" ? `${name} could not be ${done}` : `${name} could not be ${done}: ${reason}`);
126
+ var errorLine = (sentences) => sentences.length === 0 ? null : sentences.join(" ");
127
+ var onlyReason = (reasons, failures) => reasons.size === 1 ? [...reasons][0] : failures.join(" ");
128
+ var addedStatus = (names, noun) => names.length === 1 ? `Added ${names[0]} as ${/^[aeiou]/.test(noun) ? "an" : "a"} ${noun}.` : `Added ${names.length} ${noun}s: ${names.join(", ")}.`;
129
+ var stillHere = (dd) => {
130
+ const version = dd.loadVersion();
131
+ return () => dd.loadVersion() === version;
132
+ };
133
+ async function selectedNodes(dd) {
134
+ const answer = await dd.hostPart.call("paper-selection");
135
+ if (!isSelectionAnswer(answer)) throw new Error("the host part answered no selection");
136
+ switch (answer.kind) {
137
+ case "none": throw new Error(NO_NODE);
138
+ case "unreachable": throw new Error(answer.said);
139
+ case "late": throw new Error(TOO_SLOW);
140
+ case "nodes":
141
+ if (answer.nodes.length === 0) throw new Error(NO_NODE);
142
+ if (answer.nodes.length > 12) throw new Error(tooManySelected(answer.nodes.length));
143
+ return {
144
+ fileId: answer.fileId,
145
+ nodes: answer.nodes
146
+ };
147
+ }
148
+ }
149
+ function imageFile(answer) {
150
+ const binary = atob(answer.base64);
151
+ const bytes = new Uint8Array(binary.length);
152
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
153
+ const stem = answer.nodeId === null ? "selection" : answer.nodeId.replace(/[^A-Za-z0-9_-]+/g, "-");
154
+ return new File([bytes], `paper-${stem}.${extensionOf(answer.mimeType)}`, { type: answer.mimeType });
155
+ }
156
+ async function intrinsicWidth(file) {
157
+ try {
158
+ const bitmap = await createImageBitmap(file);
159
+ const width = bitmap.width;
160
+ bitmap.close();
161
+ return width;
162
+ } catch {
163
+ return;
164
+ }
165
+ }
166
+ async function vendored(dd, fileId, node, here) {
167
+ const answer = await dd.hostPart.call("paper-screenshot", {
168
+ fileId,
169
+ node: node.id
170
+ });
171
+ if (!isScreenshotAnswer(answer)) throw new Error("the host part answered no screenshot");
172
+ if (!here()) return null;
173
+ const file = imageFile({
174
+ ...answer,
175
+ nodeId: node.id
176
+ });
177
+ const [{ pageSrc }, width] = await Promise.all([dd.vendorFile(file), intrinsicWidth(file)]);
178
+ return {
179
+ pageSrc,
180
+ width,
181
+ name: node.name.trim() !== "" ? node.name : answer.name ?? "Paper image",
182
+ nodeId: node.id
183
+ };
184
+ }
185
+ function referenceItem(dd, shot, position) {
186
+ const payload = {
187
+ src: shot.pageSrc,
188
+ name: shot.name,
189
+ nodeId: shot.nodeId
190
+ };
191
+ return {
192
+ id: dd.core.generateId(),
193
+ kind: REFERENCE_KIND,
194
+ position,
195
+ ...shot.width !== void 0 && shot.width > 1200 ? { frame: { width: MAX_REFERENCE_WIDTH } } : {},
196
+ payload
197
+ };
198
+ }
199
+ var landedWidth = (shot) => shot.width === void 0 ? MAX_REFERENCE_WIDTH : Math.min(shot.width, MAX_REFERENCE_WIDTH);
200
+ function land(dd, items) {
201
+ dd.mutateItems((draft) => {
202
+ draft.push(...items);
203
+ });
204
+ const ids = items.map((item) => item.id);
205
+ dd.selectItems(ids);
206
+ dd.canvas.reveal(ids);
207
+ }
208
+ var SCREENSHOTS_AT_ONCE = 4;
209
+ async function addReference(dd) {
210
+ const here = stillHere(dd);
211
+ const selected = await selectedNodes(dd);
212
+ if (!here()) return null;
213
+ const reads = await inBatches(selected.nodes, SCREENSHOTS_AT_ONCE, async (node) => {
214
+ try {
215
+ return { shot: await vendored(dd, selected.fileId, node, here) };
216
+ } catch (error) {
217
+ return { error: messageOf(error) };
218
+ }
219
+ });
220
+ if (!here()) return null;
221
+ const shots = [];
222
+ const failures = [];
223
+ const reasons = new Set();
224
+ for (const [i, read] of reads.entries()) if ("error" in read) {
225
+ reasons.add(read.error);
226
+ failures.push(couldNot(selected.nodes[i].name, "read", read.error));
227
+ } else if (read.shot === null) return null;
228
+ else shots.push(read.shot);
229
+ if (shots.length === 0) throw new Error(onlyReason(reasons, failures));
230
+ const anchor = dd.core.freeSpaceAnchor(dd.items());
231
+ let x = anchor.x;
232
+ land(dd, shots.map((shot) => {
233
+ const item = referenceItem(dd, shot, {
234
+ x,
235
+ y: anchor.y
236
+ });
237
+ x += landedWidth(shot) + 40;
238
+ return item;
239
+ }));
240
+ return {
241
+ status: addedStatus(shots.map((shot) => shot.name), "image"),
242
+ error: errorLine(failures)
243
+ };
244
+ }
245
+ async function landPage(dd, request) {
246
+ if (!isLandRequest(request)) throw new Error("the host part asked for a page of no shape");
247
+ const created = await dd.createPage(request.html, {
248
+ path: request.path,
249
+ frame: request.frame
250
+ });
251
+ return {
252
+ path: created.path,
253
+ viewportId: created.viewportId,
254
+ unvendored: created.unvendored
255
+ };
256
+ }
257
+ async function addAsPage(dd) {
258
+ const here = stillHere(dd);
259
+ const selected = await selectedNodes(dd);
260
+ if (!here()) return null;
261
+ const many = selected.nodes.length > 1;
262
+ const landed = [];
263
+ const notes = [];
264
+ const failures = [];
265
+ const reasons = new Set();
266
+ for (const node of selected.nodes) try {
267
+ const answer = await dd.hostPart.call("paper-page", {
268
+ fileId: selected.fileId,
269
+ node: node.id
270
+ });
271
+ if (!isPageAnswer(answer)) throw new Error("the host part answered no page");
272
+ if (!here()) return null;
273
+ landed.push(answer);
274
+ notes.push(...answer.notes.map((note) => ended(many ? `${answer.name}: ${note}` : note)));
275
+ } catch (error) {
276
+ if (!here()) return null;
277
+ const message = messageOf(error);
278
+ reasons.add(message);
279
+ failures.push(message.includes(" was converted, but ") ? ended(message) : couldNot(node.name, "added", message));
280
+ }
281
+ if (!here()) return null;
282
+ if (landed.length === 0) throw new Error(onlyReason(reasons, failures));
283
+ return {
284
+ status: addedStatus(landed.map((answer) => answer.name), "page"),
285
+ error: errorLine([...failures, ...notes])
286
+ };
287
+ }
288
+ var _tmpl$$1 = template(`<div><p></p><div></div><p role=status></p><p role=alert>`);
289
+ var _tmpl$2 = template(`<button type=button>`);
290
+ var PROBING = "Looking for Paper desktop…";
291
+ function stateLine(state) {
292
+ const reachable = state.reachable();
293
+ if (reachable === null) return PROBING;
294
+ if (!reachable) return NOT_REACHABLE;
295
+ const count = state.selectedCount() ?? 0;
296
+ return `${state.fileName() ?? ""} · ${count === 0 ? "nothing selected" : `${count} selected`}`;
297
+ }
298
+ var GESTURES = [{
299
+ which: "image",
300
+ label: "Add as image",
301
+ run: addReference
302
+ }, {
303
+ which: "page",
304
+ label: "Add as page",
305
+ run: addAsPage
306
+ }];
307
+ function createPaperPanel(dd, state, p, focus = typeof window === "undefined" ? null : window) {
308
+ const [status, setStatus] = createSignal("");
309
+ const [error, setError] = createSignal("");
310
+ const [busy, setBusy] = createSignal(null);
311
+ let alive = true;
312
+ onCleanup(() => {
313
+ alive = false;
314
+ });
315
+ const onFocus = () => state.probe(true);
316
+ focus?.addEventListener("focus", onFocus);
317
+ onCleanup(() => focus?.removeEventListener("focus", onFocus));
318
+ state.probe();
319
+ const act = async (which, work) => {
320
+ if (busy() !== null) return;
321
+ setError("");
322
+ setStatus("");
323
+ setBusy(which);
324
+ try {
325
+ const said = await work();
326
+ if (alive && said !== null) {
327
+ setStatus(said.status);
328
+ if (said.error !== null) setError(said.error);
329
+ }
330
+ } catch (cause) {
331
+ if (alive) setError(messageOf(cause));
332
+ } finally {
333
+ if (alive) setBusy(null);
334
+ }
335
+ state.probe();
336
+ };
337
+ var _el$ = _tmpl$$1();
338
+ var _el$2 = _el$.firstChild;
339
+ var _el$3 = _el$2.nextSibling;
340
+ var _el$4 = _el$3.nextSibling;
341
+ var _el$5 = _el$4.nextSibling;
342
+ insert(_el$2, () => {
343
+ return stateLine(state);
344
+ });
345
+ insert(_el$3, createComponent(For, {
346
+ each: GESTURES,
347
+ children: (gesture) => (() => {
348
+ var _el$6 = _tmpl$2();
349
+ _el$6._$$click = () => void act(gesture.which, () => gesture.run(dd));
350
+ insert(_el$6, (() => {
351
+ var _c$ = memo(() => {
352
+ return busy() === gesture.which;
353
+ });
354
+ return () => {
355
+ return _c$() ? "Adding…" : gesture.label;
356
+ };
357
+ })());
358
+ effect(() => {
359
+ return {
360
+ e: `${p}-button`,
361
+ t: busy() !== null
362
+ };
363
+ }, ({ e, t }, _p$) => {
364
+ className(_el$6, e, _p$?.e);
365
+ t !== _p$?.t && setAttribute(_el$6, "disabled", t);
366
+ });
367
+ return _el$6;
368
+ })()
369
+ }));
370
+ insert(_el$4, status);
371
+ insert(_el$5, error);
372
+ effect(() => {
373
+ return {
374
+ e: `${p}-panel`,
375
+ t: `${p}-state`,
376
+ a: `${p}-actions`,
377
+ o: `${p}-status`,
378
+ i: `${p}-error`
379
+ };
380
+ }, ({ e, t, a, o, i }, _p$) => {
381
+ className(_el$, e, _p$?.e);
382
+ className(_el$2, t, _p$?.t);
383
+ className(_el$3, a, _p$?.a);
384
+ className(_el$4, o, _p$?.o);
385
+ className(_el$5, i, _p$?.i);
386
+ });
387
+ return _el$;
388
+ }
389
+ delegateEvents(["click"]);
390
+ var _tmpl$ = template(`<figure><img>`);
391
+ function ReferenceItemView(props) {
392
+ const id = untrack(() => props.item.id);
393
+ const p = untrack(() => props.prefix);
394
+ let binding;
395
+ onSettled(() => () => binding?.dispose());
396
+ const style$1 = () => ({
397
+ left: `${props.item.position.x}px`,
398
+ top: `${props.item.position.y}px`,
399
+ ...props.item.frame === void 0 ? {} : { width: `${props.item.frame.width}px` },
400
+ ...props.item.frame?.height === void 0 ? {} : { height: `${props.item.frame.height}px` }
401
+ });
402
+ var _el$ = _tmpl$();
403
+ var _el$2 = _el$.firstChild;
404
+ ref(() => {
405
+ return (node) => {
406
+ binding = props.dd.canvas.bindItemNode(id, node);
407
+ };
408
+ }, _el$);
409
+ effect(() => {
410
+ return {
411
+ e: `${p}-reference`,
412
+ t: readShallow(style$1()),
413
+ a: props.item.frame === void 0 ? void 0 : "",
414
+ o: props.item.frame?.height === void 0 ? void 0 : "",
415
+ i: props.dd.assetUrl(props.item.payload.src),
416
+ n: props.item.payload.name
417
+ };
418
+ }, ({ e, t, a, o, i, n }, _p$) => {
419
+ className(_el$, e, _p$?.e);
420
+ style(_el$, t, _p$?.t);
421
+ a !== _p$?.a && setAttribute(_el$, "data-framed", a);
422
+ o !== _p$?.o && setAttribute(_el$, "data-sized", o);
423
+ i !== _p$?.i && setAttribute(_el$2, "src", i);
424
+ n !== _p$?.n && setAttribute(_el$2, "alt", n);
425
+ });
426
+ return _el$;
427
+ }
428
+ function createPaperState(dd) {
429
+ const [reachable, setReachable] = createSignal(null);
430
+ const [fileName, setFileName] = createSignal(null);
431
+ const [selectedCount, setSelectedCount] = createSignal(null);
432
+ let read = null;
433
+ const probe = createProbe({
434
+ ask: async () => {
435
+ read = null;
436
+ const answer = await dd.hostPart.call("paper-selection");
437
+ if (!isSelectionAnswer(answer) || answer.kind === "unreachable" || answer.kind === "late") return false;
438
+ read = {
439
+ fileName: answer.fileName,
440
+ selectedCount: answer.kind === "nodes" ? answer.nodes.length : 0
441
+ };
442
+ return true;
443
+ },
444
+ answer: (known) => {
445
+ setFileName(known ? read?.fileName ?? null : null);
446
+ setSelectedCount(known ? read?.selectedCount ?? null : null);
447
+ setReachable(known);
448
+ },
449
+ target: null
450
+ });
451
+ dd.on("project", () => probe.probe());
452
+ onCleanup(() => probe.dispose());
453
+ return {
454
+ reachable,
455
+ fileName,
456
+ selectedCount,
457
+ probe: probe.probe
458
+ };
459
+ }
460
+ var classPrefix = (pluginId) => pluginId.replace(/[^A-Za-z0-9_-]/g, "-");
461
+ var panelCss = (p) => `
462
+ .${p}-panel {
463
+ --panel-text: var(--dream-app-text, #b8b8be);
464
+ --panel-bright: var(--dream-app-bright, #c8c8ce);
465
+ --panel-dim: var(--dream-app-dim, #9a9aa0);
466
+ --panel-muted: var(--dream-app-muted, #85858c);
467
+ --panel-chrome: var(--dream-app-line, #232328);
468
+
469
+ display: flex;
470
+ flex-direction: column;
471
+ gap: 8px;
472
+ min-height: 0;
473
+ padding: 6px 12px 12px;
474
+ font-size: 12px;
475
+ line-height: 1.4;
476
+ color: var(--panel-text);
477
+ }
478
+
479
+ .${p}-state {
480
+ margin: 0;
481
+ font-size: 11px;
482
+ color: var(--panel-dim);
483
+ overflow-wrap: anywhere;
484
+ }
485
+
486
+ .${p}-button:focus-visible {
487
+ outline: 1px solid var(--panel-bright);
488
+ outline-offset: -1px;
489
+ }
490
+
491
+ .${p}-actions {
492
+ display: flex;
493
+ flex-wrap: wrap;
494
+ align-items: center;
495
+ gap: 6px;
496
+ }
497
+
498
+ .${p}-button {
499
+ padding: 4px 10px;
500
+ font: inherit;
501
+ color: var(--panel-bright);
502
+ background: var(--panel-chrome);
503
+ border: 0;
504
+ border-radius: 4px;
505
+ cursor: pointer;
506
+ }
507
+
508
+ .${p}-button:disabled {
509
+ color: var(--panel-muted);
510
+ cursor: default;
511
+ }
512
+
513
+ .${p}-status,
514
+ .${p}-error {
515
+ margin: 0;
516
+ font-size: 11px;
517
+ color: var(--panel-muted);
518
+ overflow-wrap: anywhere;
519
+ }
520
+
521
+ .${p}-status:empty,
522
+ .${p}-error:empty {
523
+ display: none;
524
+ }
525
+
526
+ .${p}-error {
527
+ color: var(--dream-app-danger, #d08a8a);
528
+ }
529
+ `;
530
+ var itemCss = (p) => `
531
+ .${p}-reference {
532
+ position: absolute;
533
+ margin: 0;
534
+ }
535
+
536
+ /* With no frame the image is its own size, as a Media image lands; a
537
+ frame's width is the image's, its height too when a resize set one. */
538
+ .${p}-reference img {
539
+ display: block;
540
+ object-fit: contain;
541
+ object-position: top left;
542
+ }
543
+
544
+ .${p}-reference[data-framed] img {
545
+ width: 100%;
546
+ height: auto;
547
+ }
548
+
549
+ .${p}-reference[data-sized] img {
550
+ height: 100%;
551
+ }
552
+ `;
553
+ function activate(dd) {
554
+ const p = classPrefix(dd.plugin.id);
555
+ const state = createPaperState(dd);
556
+ dd.hostPart.handle("land-page", (input) => landPage(dd, input));
557
+ for (const kind of [REFERENCE_KIND, LEGACY_REFERENCE_KIND]) dd.registerItemKind({
558
+ kind,
559
+ payloadProblem: referencePayloadProblem,
560
+ styles: itemCss(p),
561
+ render: (props) => createComponent(ReferenceItemView, {
562
+ dd,
563
+ get item() {
564
+ return props.item;
565
+ },
566
+ prefix: p
567
+ }),
568
+ name: (item) => item.payload.name,
569
+ rename: (item, name) => {
570
+ item.payload.name = name;
571
+ },
572
+ describe: (item) => describeReference(item.payload),
573
+ resize: { reset: true }
574
+ });
575
+ dd.registerPanel({
576
+ id: "paper",
577
+ title: "Paper",
578
+ styles: panelCss(p),
579
+ render: () => createPaperPanel(dd, state, p)
580
+ });
581
+ }
582
+ export { activate as default };
package/icon.svg ADDED
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
2
+ <!-- Paper's geometric mark: https://paper.design/; Daydream palette. -->
3
+ <g transform="translate(2 2) scale(.952381)">
4
+ <path d="M3 0H21V13H13V3H3Z" fill="#E7E5DE"/>
5
+ <path d="M0 3H3V13H13V21H0Z" fill="#839999"/>
6
+ <path d="M10 13h3v3h-3Z" fill="#F66A2C"/>
7
+ </g>
8
+ </svg>
package/manifest.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "id": "daydream.paper",
3
+ "name": "Paper",
4
+ "description": "Bring artboards from the file open in Paper desktop onto the canvas, as images or as pages.",
5
+ "version": "0.3.0",
6
+ "api": "3.1",
7
+ "contributes": {
8
+ "panels": ["paper"],
9
+ "itemKinds": ["daydream.paper.reference", "mrbavio.paper.reference"],
10
+ "tools": [
11
+ "paper_info",
12
+ "paper_selection",
13
+ "paper_tree",
14
+ "paper_styles",
15
+ "paper_jsx",
16
+ "paper_tokens",
17
+ "paper_screenshot",
18
+ "paper_page"
19
+ ],
20
+ "instructions": "The Paper plugin reads the file open in Paper desktop on this machine through Paper's own MCP server, so you need no Paper setup of your own. paper_info answers the viewed file (its id, pages, artboards with sizes, font families, tokens); paper_selection what is selected there; paper_tree a node's subtree as Paper's indented summary (type, layer name, id, size, a text node's text); paper_styles the computed CSS of nodes, batched, as camelCase properties with units, tokens kept as var(--name); paper_jsx a node as JSX (inline-styles or tailwind), which is how an SVG's markup is read; paper_tokens the file's design tokens as a :root {} stylesheet (css) or structured (json); paper_screenshot a node's image, written into the open project's .daydream/paper/<node>.jpg (the extension follows what Paper sends, JPEG as it does today) and answered as that path; and paper_page converts an artboard into a page of the project with no agent in between \u2014 one html file, one stylesheet of named classes, the file's tokens as a sheet, its image fills downloaded into assets/ \u2014 and lands it on the canvas at the artboard's size, answering the page's path. The page is faithful and plain: every text a <p>, every box a <div> with a class named after its Paper layer, flex and absolute layout as Paper has it, no media queries; to make it semantic, rework the page's files afterwards. Paper desktop must be open with a file; when it is not, each tool answers one sentence saying so. The panel's two gestures take what is selected in Paper: Add as image lands each node's screenshot as a daydream.paper.reference item (payload {src, name, nodeId}, src a file of the project's own), Add as page runs the same conversion as paper_page for each selected artboard. Nothing writes to Paper."
21
+ },
22
+ "permissions": [
23
+ {
24
+ "name": "network",
25
+ "reason": "the host part talks to Paper desktop's MCP server on this machine only (http://127.0.0.1:29979), reading what Paper has open, and downloads the image fills a page uses from Paper's asset host; nothing of the project leaves the machine"
26
+ },
27
+ {
28
+ "name": "Writes project files",
29
+ "reason": "Add as page writes the page's html, its stylesheet and the file's tokens sheet at the project's root and the image fills into assets/; an image copies a screenshot into assets/; an agent's screenshot is written into .daydream/paper/"
30
+ }
31
+ ],
32
+ "built": "dist/index.js"
33
+ }
package/package.json CHANGED
@@ -1,6 +1,17 @@
1
1
  {
2
2
  "name": "@daydreamui/paper",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.0",
4
+ "type": "module",
5
+ "description": "Bring artboards from the file open in Paper desktop onto the canvas, as images or as pages.",
6
+ "keywords": [
7
+ "daydream-plugin"
8
+ ],
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/martinbavio/daydream-plugins.git",
15
+ "directory": "plugins/daydream.paper"
16
+ }
17
+ }