@nordcraft/runtime 1.0.34 → 1.0.36

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.
@@ -0,0 +1,12 @@
1
+ import { type ApiRequest } from '@nordcraft/core/dist/api/apiTypes';
2
+ import type { FormulaContext } from '@nordcraft/core/dist/formula/formula';
3
+ /**
4
+ * Run an introspection query for an existing API
5
+ * The introspection will usually be a POST request, but we use the method
6
+ * from the original API to support other methods.
7
+ */
8
+ export declare const introspectApiRequest: ({ api, componentName, formulaContext, }: {
9
+ api: ApiRequest;
10
+ componentName: string;
11
+ formulaContext: FormulaContext;
12
+ }) => Promise<any>;
@@ -0,0 +1,150 @@
1
+ import { createApiRequest, HttpMethodsWithAllowedBody, } from '@nordcraft/core/dist/api/api';
2
+ import { ApiMethod } from '@nordcraft/core/dist/api/apiTypes';
3
+ import { PROXY_URL_HEADER } from '@nordcraft/core/dist/utils/url';
4
+ const INTROSPECTION_QUERY = `\
5
+ query IntrospectionQuery {
6
+ __schema {
7
+ queryType { ...FullType }
8
+ mutationType { ...FullType }
9
+ subscriptionType { ...FullType }
10
+ types {
11
+ ...FullType
12
+ }
13
+ directives {
14
+ name
15
+ description
16
+ locations
17
+ args {
18
+ ...InputValue
19
+ }
20
+ }
21
+ }
22
+ }
23
+ fragment FullType on __Type {
24
+ kind
25
+ name
26
+ description
27
+ fields(includeDeprecated: true) {
28
+ name
29
+ description
30
+ args {
31
+ ...InputValue
32
+ }
33
+ type {
34
+ ...TypeRef
35
+ }
36
+ isDeprecated
37
+ deprecationReason
38
+ }
39
+ inputFields {
40
+ ...InputValue
41
+ }
42
+ interfaces {
43
+ ...TypeRef
44
+ }
45
+ enumValues(includeDeprecated: true) {
46
+ name
47
+ description
48
+ isDeprecated
49
+ deprecationReason
50
+ }
51
+ possibleTypes {
52
+ ...TypeRef
53
+ }
54
+ }
55
+ fragment InputValue on __InputValue {
56
+ name
57
+ description
58
+ type { ...TypeRef }
59
+ defaultValue
60
+ }
61
+ fragment TypeRef on __Type {
62
+ kind
63
+ name
64
+ ofType {
65
+ kind
66
+ name
67
+ ofType {
68
+ kind
69
+ name
70
+ ofType {
71
+ kind
72
+ name
73
+ ofType {
74
+ kind
75
+ name
76
+ ofType {
77
+ kind
78
+ name
79
+ ofType {
80
+ kind
81
+ name
82
+ ofType {
83
+ kind
84
+ name
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }`;
93
+ /**
94
+ * Run an introspection query for an existing API
95
+ * The introspection will usually be a POST request, but we use the method
96
+ * from the original API to support other methods.
97
+ */
98
+ export const introspectApiRequest = async ({ api, componentName, formulaContext, }) => {
99
+ const { url, requestSettings } = createApiRequest({
100
+ api: {
101
+ ...api,
102
+ // Default to a POST request if the method of the original API doesn't support a body
103
+ // The introspection query should never be initiated in that case though
104
+ method: HttpMethodsWithAllowedBody.includes(api.method ?? ApiMethod.POST)
105
+ ? api.method
106
+ : ApiMethod.POST,
107
+ // Overwrite the body with a default introspection query (in a value formula)
108
+ body: {
109
+ type: 'value',
110
+ value: { query: INTROSPECTION_QUERY },
111
+ },
112
+ },
113
+ baseUrl: window.origin,
114
+ defaultHeaders: undefined,
115
+ formulaContext,
116
+ });
117
+ // We must proxy to be able to include cookies
118
+ const proxyUrl = `/.toddle/omvej/components/${encodeURIComponent(componentName)}/apis/${encodeURIComponent(componentName)}:${encodeURIComponent(api.name)}`;
119
+ const headers = new Headers(requestSettings.headers);
120
+ headers.set(PROXY_URL_HEADER, decodeURIComponent(url.href.replace(/\+/g, ' ')));
121
+ requestSettings.headers = headers;
122
+ const response = await fetch(proxyUrl, {
123
+ ...requestSettings,
124
+ // Set credentials to what was set on the original API
125
+ credentials: api.client?.credentials &&
126
+ ['include', 'same-origin', 'omit'].includes(api.client.credentials)
127
+ ? api.client.credentials
128
+ : // Default to same-origin
129
+ undefined,
130
+ });
131
+ try {
132
+ const data = await response.json();
133
+ if (response.ok) {
134
+ return data;
135
+ }
136
+ else {
137
+ // eslint-disable-next-line no-console
138
+ console.error('Failed to introspect API:', api.name, data);
139
+ // Return a generic error message if introspection failed
140
+ return { error: data?.message ?? 'Failed to parse introspection result' };
141
+ }
142
+ }
143
+ catch (e) {
144
+ // eslint-disable-next-line no-console
145
+ console.error('Failed to parses API response:', api.name, e);
146
+ // Return a generic error message if introspection failed
147
+ return { error: `Failed to introspect API: ${api.name}` };
148
+ }
149
+ };
150
+ //# sourceMappingURL=graphql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql.js","sourceRoot":"","sources":["../../src/editor/graphql.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,0BAA0B,GAC3B,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAAE,SAAS,EAAmB,MAAM,mCAAmC,CAAA;AAE9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AAEjE,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwF1B,CAAA;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,EAAE,EACzC,GAAG,EACH,aAAa,EACb,cAAc,GAKf,EAAE,EAAE;IACH,MAAM,EAAE,GAAG,EAAE,eAAe,EAAE,GAAG,gBAAgB,CAAC;QAChD,GAAG,EAAE;YACH,GAAI,GAAkB;YACtB,qFAAqF;YACrF,wEAAwE;YACxE,MAAM,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,IAAI,SAAS,CAAC,IAAI,CAAC;gBACvE,CAAC,CAAC,GAAG,CAAC,MAAM;gBACZ,CAAC,CAAC,SAAS,CAAC,IAAI;YAClB,6EAA6E;YAC7E,IAAI,EAAE;gBACJ,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE;aACtC;SACF;QACD,OAAO,EAAE,MAAM,CAAC,MAAM;QACtB,cAAc,EAAE,SAAS;QACzB,cAAc;KACf,CAAC,CAAA;IACF,8CAA8C;IAC9C,MAAM,QAAQ,GAAG,6BAA6B,kBAAkB,CAC9D,aAAa,CACd,SAAS,kBAAkB,CAAC,aAAa,CAAC,IAAI,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAA;IAC7E,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;IACpD,OAAO,CAAC,GAAG,CACT,gBAAgB,EAChB,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CACjD,CAAA;IACD,eAAe,CAAC,OAAO,GAAG,OAAO,CAAA;IACjC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;QACrC,GAAG,eAAe;QAClB,sDAAsD;QACtD,WAAW,EACT,GAAG,CAAC,MAAM,EAAE,WAAW;YACvB,CAAC,SAAS,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC;YACjE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW;YACxB,CAAC,CAAC,yBAAyB;gBACzB,SAAS;KAChB,CAAC,CAAA;IACF,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;QAClC,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YAChB,OAAO,IAAI,CAAA;QACb,CAAC;aAAM,CAAC;YACN,sCAAsC;YACtC,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;YAC1D,yDAAyD;YACzD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,IAAI,sCAAsC,EAAE,CAAA;QAC3E,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,sCAAsC;QACtC,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QAC5D,yDAAyD;QACzD,OAAO,EAAE,KAAK,EAAE,6BAA6B,GAAG,CAAC,IAAI,EAAE,EAAE,CAAA;IAC3D,CAAC;AACH,CAAC,CAAA"}
@@ -3,6 +3,7 @@
3
3
  /* eslint-disable no-case-declarations */
4
4
  /* eslint-disable no-fallthrough */
5
5
  import { isLegacyApi } from '@nordcraft/core/dist/api/api';
6
+ import {} from '@nordcraft/core/dist/api/apiTypes';
6
7
  import { isPageComponent } from '@nordcraft/core/dist/component/isPageComponent';
7
8
  import { applyFormula } from '@nordcraft/core/dist/formula/formula';
8
9
  import { valueFormula } from '@nordcraft/core/dist/formula/formulaUtils';
@@ -23,6 +24,7 @@ import { dragEnded } from './editor/drag-drop/dragEnded';
23
24
  import { dragMove } from './editor/drag-drop/dragMove';
24
25
  import { dragReorder } from './editor/drag-drop/dragReorder';
25
26
  import { dragStarted } from './editor/drag-drop/dragStarted';
27
+ import { introspectApiRequest } from './editor/graphql';
26
28
  import { handleAction } from './events/handleAction';
27
29
  import { signal } from './signal/signal';
28
30
  import { insertStyles, styleToCss } from './styles/style';
@@ -200,7 +202,7 @@ export const createRoot = (domNode = document.getElementById('App')) => {
200
202
  });
201
203
  return component;
202
204
  };
203
- window.addEventListener('message', (message) => {
205
+ window.addEventListener('message', async (message) => {
204
206
  if (!message.isTrusted) {
205
207
  console.error('UNTRUSTED MESSAGE');
206
208
  }
@@ -323,20 +325,20 @@ export const createRoot = (domNode = document.getElementById('App')) => {
323
325
  element.type === 'text' &&
324
326
  element.value.type === 'value') {
325
327
  const computedStyle = window.getComputedStyle(node);
326
- window.parent?.postMessage({
328
+ postMessageToEditor({
327
329
  type: 'textComputedStyle',
328
330
  computedStyle: Object.fromEntries(Object.values(TextNodeComputedStyles).map((style) => [
329
331
  style,
330
332
  computedStyle.getPropertyValue(style),
331
333
  ])),
332
- }, '*');
334
+ });
333
335
  }
334
336
  else if (node && node.getAttribute('data-node-type') !== 'text') {
335
337
  // Reset computed style on blur
336
- window.parent?.postMessage({
338
+ postMessageToEditor({
337
339
  type: 'textComputedStyle',
338
340
  computedStyle: {},
339
- }, '*');
341
+ });
340
342
  }
341
343
  }
342
344
  return;
@@ -412,36 +414,36 @@ export const createRoot = (domNode = document.getElementById('App')) => {
412
414
  if (root && id) {
413
415
  const nodeLookup = getNodeAndAncestors(component, root, id);
414
416
  if (nodeLookup?.node.type === 'text') {
415
- window.parent?.postMessage({
417
+ postMessageToEditor({
416
418
  type: 'selection',
417
419
  selectedNodeId: id,
418
- }, '*');
420
+ });
419
421
  }
420
422
  else {
421
423
  const firstTextChild = nodeLookup?.node.type === 'element'
422
424
  ? nodeLookup.node.children.find((c) => component?.nodes[c]?.type === 'text')
423
425
  : undefined;
424
426
  if (firstTextChild) {
425
- window.parent?.postMessage({
427
+ postMessageToEditor({
426
428
  type: 'selection',
427
429
  selectedNodeId: `${id}.0`,
428
- }, '*');
430
+ });
429
431
  }
430
432
  }
431
433
  }
432
434
  }
433
435
  else {
434
- window.parent?.postMessage({
436
+ postMessageToEditor({
435
437
  type: 'selection',
436
438
  selectedNodeId: id,
437
- }, '*');
439
+ });
438
440
  }
439
441
  }
440
442
  else if (type === 'mousemove' && id !== highlightedNodeId) {
441
- window.parent?.postMessage({
443
+ postMessageToEditor({
442
444
  type: 'highlight',
443
445
  highlightedNodeId: id,
444
- }, '*');
446
+ });
445
447
  }
446
448
  else if (type === 'dblclick' &&
447
449
  id &&
@@ -453,17 +455,17 @@ export const createRoot = (domNode = document.getElementById('App')) => {
453
455
  const nodeLookup = getNodeAndAncestors(component, root, id);
454
456
  if (nodeLookup?.node.type === 'component' &&
455
457
  nodeLookup.node.name) {
456
- window.parent?.postMessage({
458
+ postMessageToEditor({
457
459
  type: 'navigate',
458
460
  name: nodeLookup.node.name,
459
- }, '*');
461
+ });
460
462
  }
461
463
  // Double click on text node should select the text node for editing
462
464
  else if (nodeLookup?.node.type === 'text') {
463
- window.parent?.postMessage({
465
+ postMessageToEditor({
464
466
  type: 'selection',
465
467
  selectedNodeId: id,
466
- }, '*');
468
+ });
467
469
  }
468
470
  }
469
471
  }
@@ -475,16 +477,16 @@ export const createRoot = (domNode = document.getElementById('App')) => {
475
477
  // We request manually instead of automatic to avoid mutation observer spam.
476
478
  // Also, reporting automatically proved unreliable when elements' height was in %
477
479
  case 'report_document_scroll_size':
478
- window.parent?.postMessage({
480
+ postMessageToEditor({
479
481
  type: 'documentScrollSize',
480
482
  scrollHeight: domNode.scrollHeight,
481
483
  scrollWidth: domNode.scrollWidth,
482
- }, '*');
484
+ });
483
485
  break;
484
486
  case 'reload':
485
487
  window.location.reload();
486
488
  break;
487
- case 'fetch_api':
489
+ case 'fetch_api': {
488
490
  const { apiKey } = message.data;
489
491
  dataSignal.update((data) => ({
490
492
  ...data,
@@ -499,6 +501,33 @@ export const createRoot = (domNode = document.getElementById('App')) => {
499
501
  }));
500
502
  void ctx?.apis[apiKey]?.fetch({});
501
503
  break;
504
+ }
505
+ case 'introspect_qraphql_api': {
506
+ const { apiKey } = message.data;
507
+ const api = component?.apis[apiKey];
508
+ if (api && !isLegacyApi(api) && component) {
509
+ const Attributes = mapObject(component.attributes, ([name, { testValue }]) => [name, testValue]);
510
+ const formulaContext = {
511
+ component,
512
+ data: { Attributes },
513
+ root: document,
514
+ package: ctx?.package,
515
+ toddle: window.toddle,
516
+ env,
517
+ };
518
+ const introspectionResult = await introspectApiRequest({
519
+ api: api,
520
+ componentName: component.name,
521
+ formulaContext,
522
+ });
523
+ postMessageToEditor({
524
+ type: 'introspectionResult',
525
+ data: introspectionResult,
526
+ apiKey,
527
+ });
528
+ }
529
+ break;
530
+ }
502
531
  case 'drag-started':
503
532
  const draggedElement = getDOMNodeFromNodeId(selectedNodeId);
504
533
  if (!draggedElement || !draggedElement.parentElement) {
@@ -534,14 +563,14 @@ export const createRoot = (domNode = document.getElementById('App')) => {
534
563
  (nextSibling !== dragState?.initialNextSibling ||
535
564
  dragState?.copy)) {
536
565
  void dragEnded(dragState, false).then(() => {
537
- window.parent?.postMessage({
566
+ postMessageToEditor({
538
567
  type: 'nodeMoved',
539
568
  copy: Boolean(dragState?.copy),
540
569
  parent: parentDataId,
541
570
  index: !isNaN(nextSiblingId)
542
571
  ? nextSiblingId
543
572
  : component?.nodes[parentNodeId]?.children?.length,
544
- }, '*');
573
+ });
545
574
  dragState = null;
546
575
  });
547
576
  }
@@ -555,12 +584,12 @@ export const createRoot = (domNode = document.getElementById('App')) => {
555
584
  const selectedPermutation = dragState?.insertAreas?.[dragState?.selectedInsertAreaIndex ?? -1];
556
585
  if (selectedPermutation && !message.data.canceled) {
557
586
  void dragEnded(dragState, false).then(() => {
558
- window.parent?.postMessage({
587
+ postMessageToEditor({
559
588
  type: 'nodeMoved',
560
589
  copy: Boolean(dragState?.copy),
561
590
  parent: selectedPermutation?.parent.getAttribute('data-id'),
562
591
  index: selectedPermutation?.index,
563
- }, '*');
592
+ });
564
593
  dragState = null;
565
594
  });
566
595
  }
@@ -606,13 +635,13 @@ export const createRoot = (domNode = document.getElementById('App')) => {
606
635
  }
607
636
  const { styles } = message.data;
608
637
  const computedStyle = window.getComputedStyle(selectedNode);
609
- window.parent?.postMessage({
638
+ postMessageToEditor({
610
639
  type: 'computedStyle',
611
640
  computedStyle: Object.fromEntries((styles ?? []).map((style) => [
612
641
  style,
613
642
  computedStyle.getPropertyValue(style),
614
643
  ])),
615
- }, '*');
644
+ });
616
645
  break;
617
646
  case 'set_timeline_keyframes':
618
647
  const { keyframes } = message.data;
@@ -1070,12 +1099,12 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1070
1099
  }
1071
1100
  console.error(name, message, error);
1072
1101
  }
1073
- window.parent?.postMessage({
1102
+ postMessageToEditor({
1074
1103
  type: 'style',
1075
1104
  time: new Intl.DateTimeFormat('en-GB', {
1076
1105
  timeStyle: 'long',
1077
1106
  }).format(new Date()),
1078
- }, '*');
1107
+ });
1079
1108
  }
1080
1109
  }
1081
1110
  ctx = newCtx;
@@ -1085,14 +1114,14 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1085
1114
  component,
1086
1115
  components,
1087
1116
  triggerEvent: (event, data) => {
1088
- window.parent?.postMessage({
1117
+ postMessageToEditor({
1089
1118
  type: 'component event',
1090
1119
  event,
1091
1120
  time: new Intl.DateTimeFormat('en-GB', {
1092
1121
  timeStyle: 'long',
1093
1122
  }).format(new Date()),
1094
1123
  data,
1095
- }, '*');
1124
+ });
1096
1125
  },
1097
1126
  dataSignal,
1098
1127
  root: document,
@@ -1143,7 +1172,7 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1143
1172
  event.preventDefault();
1144
1173
  }
1145
1174
  }
1146
- window.parent?.postMessage({
1175
+ postMessageToEditor({
1147
1176
  type: 'keydown',
1148
1177
  event: {
1149
1178
  key: event.key,
@@ -1151,13 +1180,13 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1151
1180
  shiftKey: event.shiftKey,
1152
1181
  altKey: event.altKey,
1153
1182
  },
1154
- }, '*');
1183
+ });
1155
1184
  });
1156
1185
  document.addEventListener('keyup', (event) => {
1157
1186
  if (isInputTarget(event)) {
1158
1187
  return;
1159
1188
  }
1160
- window.parent?.postMessage({
1189
+ postMessageToEditor({
1161
1190
  type: 'keyup',
1162
1191
  event: {
1163
1192
  key: event.key,
@@ -1165,13 +1194,13 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1165
1194
  shiftKey: event.shiftKey,
1166
1195
  altKey: event.altKey,
1167
1196
  },
1168
- }, '*');
1197
+ });
1169
1198
  });
1170
1199
  document.addEventListener('keypress', (event) => {
1171
1200
  if (isInputTarget(event)) {
1172
1201
  return;
1173
1202
  }
1174
- window.parent?.postMessage({
1203
+ postMessageToEditor({
1175
1204
  type: 'keypress',
1176
1205
  event: {
1177
1206
  key: event.key,
@@ -1179,16 +1208,19 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1179
1208
  shiftKey: event.shiftKey,
1180
1209
  altKey: event.altKey,
1181
1210
  },
1182
- }, '*');
1211
+ });
1183
1212
  });
1184
1213
  dataSignal.subscribe((data) => {
1185
1214
  if (component && components && packageComponents && data) {
1186
1215
  try {
1187
- window.parent?.postMessage({ type: 'data', data }, '*');
1216
+ postMessageToEditor({ type: 'data', data });
1188
1217
  }
1189
1218
  catch {
1190
1219
  // If we're unable to send the data, let's try to JSON serialize it
1191
- window.parent?.postMessage({ type: 'data', data: JSON.parse(JSON.stringify(data)) }, '*');
1220
+ postMessageToEditor({
1221
+ type: 'data',
1222
+ data: JSON.parse(JSON.stringify(data)),
1223
+ });
1192
1224
  }
1193
1225
  }
1194
1226
  });
@@ -1223,17 +1255,17 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1223
1255
  (function syncOverlayRects(prevSelectionRect, prevHighlightedRect) {
1224
1256
  const selectionRect = getRectData(getDOMNodeFromNodeId(selectedNodeId));
1225
1257
  if (!fastDeepEqual(prevSelectionRect, selectionRect)) {
1226
- window.parent?.postMessage({
1258
+ postMessageToEditor({
1227
1259
  type: 'selectionRect',
1228
1260
  rect: selectionRect,
1229
- }, '*');
1261
+ });
1230
1262
  }
1231
1263
  const highlightRect = getRectData(getDOMNodeFromNodeId(highlightedNodeId));
1232
1264
  if (!fastDeepEqual(prevHighlightedRect, highlightRect)) {
1233
- window.parent?.postMessage({
1265
+ postMessageToEditor({
1234
1266
  type: 'highlightRect',
1235
1267
  rect: highlightRect,
1236
- }, '*');
1268
+ });
1237
1269
  }
1238
1270
  requestAnimationFrame(() => syncOverlayRects(selectionRect, highlightRect));
1239
1271
  })();
@@ -1330,4 +1362,7 @@ const insertTheme = (parent, theme) => {
1330
1362
  });
1331
1363
  parent.appendChild(styleElem);
1332
1364
  };
1365
+ const postMessageToEditor = (message) => {
1366
+ window.parent?.postMessage(message, '*');
1367
+ };
1333
1368
  //# sourceMappingURL=editor-preview.main.js.map