@realnation/builder-shared-sdk 2.4.1 → 2.4.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/souvenir/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EAEpB,MAAM,YAAY,CAAC;AA6CpB;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAI7E;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAO5F;AA6ND,gFAAgF;AAChF,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACrC,OAAO,CAAC,iBAAiB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBhE;AAED,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,EAAE,CAOtD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAYnE;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAMpF;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,YAAY,EAClB,MAAM,GAAE,cAAmB,EAC3B,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,iBAAiB,CAAC,CAsE5B"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/souvenir/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EAEpB,MAAM,YAAY,CAAC;AA6CpB;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAI7E;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAO5F;AAyPD,gFAAgF;AAChF,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACrC,OAAO,CAAC,iBAAiB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBhE;AAED,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,EAAE,CAStD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAYnE;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAMpF;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,YAAY,EAClB,MAAM,GAAE,cAAmB,EAC3B,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,iBAAiB,CAAC,CAsE5B"}
@@ -12,7 +12,7 @@
12
12
  import { computeImageFitDrawRect, hashSignature, normalizeImageFit } from './imageFit.js';
13
13
  import { parseSouvenirOutputId } from './outputId.js';
14
14
  import { layoutLines, wrapText } from './text.js';
15
- import { isSouvenirSizeAllowed, SouvenirError, SOUVENIR_ELEMENT_TYPES, } from './types.js';
15
+ import { isSouvenirInteractive, isSouvenirSizeAllowed, SouvenirError, SOUVENIR_ELEMENT_TYPES, } from './types.js';
16
16
  const DEFAULT_LINE_HEIGHT = 1.2;
17
17
  const TRANSPARENT = 'TRANSPARENT';
18
18
  const num = (value, fallback = 0) => {
@@ -144,39 +144,55 @@ function fontOf(property, fontSize) {
144
144
  const quoted = /[",]/.test(family) || !/\s/.test(family) ? family : `"${family}"`;
145
145
  return `${style}${weight}${fontSize}px ${quoted}`;
146
146
  }
147
- function drawText(ctx, property, geo, content) {
147
+ /**
148
+ * Lays the text out inside the box already in element space. `padX` is the side
149
+ * padding the runner leaves on shape text.
150
+ */
151
+ function drawTextInBox(c, property, radius, width, height, content, padX = 0) {
148
152
  const fontSize = num(property['font-size'], 14);
149
153
  if (fontSize <= 0 || !content)
150
154
  return;
151
155
  const lineHeight = num(property['line-height'], DEFAULT_LINE_HEIGHT) || DEFAULT_LINE_HEIGHT;
152
156
  const align = (property['alignment'] || 'center');
153
157
  const vAlign = (property['v-alignment'] || 'middle');
158
+ const boxWidth = Math.max(0, width - padX * 2);
159
+ if (boxWidth <= 0)
160
+ return;
161
+ c.save();
162
+ roundedRectPath(c, 0, 0, width, height, radius);
163
+ c.clip();
164
+ c.font = fontOf(property, fontSize);
165
+ c.textBaseline = 'alphabetic';
166
+ c.textAlign = 'left';
167
+ c.fillStyle = colorOf(property['color']) || '#000000';
168
+ const measure = (text) => c.measureText(text).width;
169
+ const lines = wrapText(content, boxWidth, measure);
170
+ const placed = layoutLines(lines, { x: padX, y: 0, width: boxWidth, height }, fontSize, lineHeight, align, vAlign, measure);
171
+ const formatting = Array.isArray(property['formatting']) ? property['formatting'] : [];
172
+ const underline = formatting.includes('Underline');
173
+ const strike = formatting.includes('Strikethrough');
174
+ for (const line of placed) {
175
+ if (!line.text)
176
+ continue;
177
+ c.fillText(line.text, line.x, line.y);
178
+ if (underline)
179
+ c.fillRect(line.x, line.y + fontSize * 0.12, line.width, Math.max(1, fontSize / 16));
180
+ if (strike)
181
+ c.fillRect(line.x, line.y - fontSize * 0.28, line.width, Math.max(1, fontSize / 16));
182
+ }
183
+ c.restore();
184
+ }
185
+ function drawText(ctx, property, geo, content) {
186
+ if (!content)
187
+ return;
154
188
  inElementSpace(ctx, geo, (c, width, height) => {
155
189
  fillBackground(c, property, width, height, geo.radius, false);
156
- roundedRectPath(c, 0, 0, width, height, geo.radius);
157
- c.clip();
158
- c.font = fontOf(property, fontSize);
159
- c.textBaseline = 'alphabetic';
160
- c.textAlign = 'left';
161
- c.fillStyle = colorOf(property['color']) || '#000000';
162
- const measure = (text) => c.measureText(text).width;
163
- const lines = wrapText(content, width, measure);
164
- const placed = layoutLines(lines, { x: 0, y: 0, width, height }, fontSize, lineHeight, align, vAlign, measure);
165
- const formatting = Array.isArray(property['formatting']) ? property['formatting'] : [];
166
- const underline = formatting.includes('Underline');
167
- const strike = formatting.includes('Strikethrough');
168
- for (const line of placed) {
169
- if (!line.text)
170
- continue;
171
- c.fillText(line.text, line.x, line.y);
172
- if (underline)
173
- c.fillRect(line.x, line.y + fontSize * 0.12, line.width, Math.max(1, fontSize / 16));
174
- if (strike)
175
- c.fillRect(line.x, line.y - fontSize * 0.28, line.width, Math.max(1, fontSize / 16));
176
- }
190
+ drawTextInBox(c, property, geo.radius, width, height, content);
177
191
  });
178
192
  inElementSpace(ctx, geo, (c, width, height) => strokeBorder(c, property, width, height, geo.radius, false));
179
193
  }
194
+ /** The runner pads shape text by 4px on each side; match it so the wrap is the same. */
195
+ const SHAPE_TEXT_PADDING = 4;
180
196
  function drawImage(ctx, property, geo, image) {
181
197
  inElementSpace(ctx, geo, (c, width, height) => {
182
198
  fillBackground(c, property, width, height, geo.radius, false);
@@ -233,6 +249,11 @@ function drawShape(ctx, type, property, geo) {
233
249
  // Rectangles and ellipses take their outline from the border settings, like
234
250
  // on a live page; a new shape stores a 1px grey border with style None.
235
251
  strokeBorder(c, property, width, height, geo.radius, ellipse);
252
+ // Text typed into a shape (a Button preset is a shape with text) is part of
253
+ // the picture, the same as it is on a live page.
254
+ const content = typeof property['content'] === 'string' ? property['content'] : '';
255
+ if (content)
256
+ drawTextInBox(c, property, geo.radius, width, height, content, SHAPE_TEXT_PADDING);
236
257
  });
237
258
  }
238
259
  /** Loads one image with EXIF orientation applied, or throws a SouvenirError. */
@@ -265,6 +286,8 @@ export function drawOrder(page) {
265
286
  return Object.keys(page.elements ?? {})
266
287
  .filter((eid) => SOUVENIR_ELEMENT_TYPES.includes(page.elements[eid]?.type))
267
288
  .filter((eid) => !isHidden(page.properties?.[eid] ?? {}))
289
+ // Controls stay on screen over the picture, so they are not in it
290
+ .filter((eid) => !isSouvenirInteractive(page.elements[eid]?.type, page.properties?.[eid] ?? {}))
268
291
  .map((eid, index) => ({ eid, index, z: num((page.properties?.[eid] ?? {})['zIndex']) }))
269
292
  .sort((a, b) => (a.z === b.z ? a.index - b.index : a.z - b.z))
270
293
  .map((entry) => entry.eid);
@@ -14,6 +14,19 @@
14
14
  /** Element types the composer draws. Anything else on the page is skipped. */
15
15
  export declare const SOUVENIR_ELEMENT_TYPES: readonly ["bg", "image", "image-frame", "text", "shape-rect", "shape-ellipse", "shape-line"];
16
16
  export type SouvenirElementType = (typeof SOUVENIR_ELEMENT_TYPES)[number];
17
+ /**
18
+ * Element types that do something when the participant taps them, or that count
19
+ * down. On a page the module composes and displays itself, the runner keeps
20
+ * these on screen over the picture, so the composer must leave them out or the
21
+ * same thing would appear twice (`qik-sense-runner/src/services/pageRender.ts`
22
+ * keeps the same list).
23
+ */
24
+ export declare const SOUVENIR_INTERACTIVE_TYPES: readonly ["timeout", "button", "qr", "link"];
25
+ /**
26
+ * True when this element is a control rather than part of the picture: an
27
+ * interactive type, or any element the operator gave a target to.
28
+ */
29
+ export declare function isSouvenirInteractive(type: string | undefined | null, property?: SouvenirProperty): boolean;
17
30
  /** Anything the Builder stores for one element. Values arrive untyped from the API. */
18
31
  export type SouvenirProperty = Record<string, unknown>;
19
32
  /** One element as the Builder's `detail.elements` records it. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/souvenir/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,sBAAsB,8FAQzB,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E,uFAAuF;AACvF,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEvD,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,mFAAmF;IACnF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC1C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAC9C;AAED;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAEvE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAErD,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,wGAAwG;IACxG,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,iBAAiB,CAAC;IAC1B,oFAAoF;IACpF,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;CACzC;AAED,0EAA0E;AAC1E,qBAAa,aAAc,SAAQ,KAAK;aAEpB,IAAI,EAChB,QAAQ,GACR,UAAU,GACV,WAAW,GACX,oBAAoB,GACpB,cAAc,GACd,eAAe;gBANH,IAAI,EAChB,QAAQ,GACR,UAAU,GACV,WAAW,GACX,oBAAoB,GACpB,cAAc,GACd,eAAe,EACnB,OAAO,EAAE,MAAM;CAKlB;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,WAAa,CAAC;AAE5C,oFAAoF;AACpF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAK5E;AAED,4DAA4D;AAC5D,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,EAAE,CAW5E"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/souvenir/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,sBAAsB,8FAQzB,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,8CAA+C,CAAC;AAKvF;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAAE,QAAQ,GAAE,gBAAqB,GAAG,OAAO,CAG/G;AAED,uFAAuF;AACvF,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEvD,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,mFAAmF;IACnF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC1C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAC9C;AAED;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAEvE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAErD,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,wGAAwG;IACxG,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,iBAAiB,CAAC;IAC1B,oFAAoF;IACpF,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;CACzC;AAED,0EAA0E;AAC1E,qBAAa,aAAc,SAAQ,KAAK;aAEpB,IAAI,EAChB,QAAQ,GACR,UAAU,GACV,WAAW,GACX,oBAAoB,GACpB,cAAc,GACd,eAAe;gBANH,IAAI,EAChB,QAAQ,GACR,UAAU,GACV,WAAW,GACX,oBAAoB,GACpB,cAAc,GACd,eAAe,EACnB,OAAO,EAAE,MAAM;CAKlB;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,WAAa,CAAC;AAE5C,oFAAoF;AACpF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAK5E;AAED,4DAA4D;AAC5D,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,EAAE,CAW5E"}
@@ -21,6 +21,25 @@ export const SOUVENIR_ELEMENT_TYPES = [
21
21
  'shape-ellipse',
22
22
  'shape-line',
23
23
  ];
24
+ /**
25
+ * Element types that do something when the participant taps them, or that count
26
+ * down. On a page the module composes and displays itself, the runner keeps
27
+ * these on screen over the picture, so the composer must leave them out or the
28
+ * same thing would appear twice (`qik-sense-runner/src/services/pageRender.ts`
29
+ * keeps the same list).
30
+ */
31
+ export const SOUVENIR_INTERACTIVE_TYPES = ['timeout', 'button', 'qr', 'link'];
32
+ /** Property keys that turn any element into something tappable. */
33
+ const INTERACTIVE_PROPERTIES = ['connection-key', 'url', 'key-code'];
34
+ /**
35
+ * True when this element is a control rather than part of the picture: an
36
+ * interactive type, or any element the operator gave a target to.
37
+ */
38
+ export function isSouvenirInteractive(type, property = {}) {
39
+ if (SOUVENIR_INTERACTIVE_TYPES.includes(String(type ?? '')))
40
+ return true;
41
+ return INTERACTIVE_PROPERTIES.some((key) => String(property?.[key] ?? '').trim() !== '');
42
+ }
24
43
  /** Thrown for every refusal the caller is expected to handle and show. */
25
44
  export class SouvenirError extends Error {
26
45
  code;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realnation/builder-shared-sdk",
3
- "version": "2.4.1",
3
+ "version": "2.4.2",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "exports": {