@nyaruka/temba-components 0.127.0 → 0.129.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.
Files changed (109) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/demo/chart/horizontal-demo.html +81 -0
  3. package/demo/components/datepicker/example.html +63 -0
  4. package/demo/components/datepicker/range-picker-demo.html +161 -0
  5. package/demo/data/flows/sample-flow.json +127 -100
  6. package/demo/index.html +8 -0
  7. package/demo/static/css/prism.css +2 -0
  8. package/demo/static/js/prism-loader.js +12 -0
  9. package/demo/sticky-note-demo.html +152 -0
  10. package/demo/styles.css +71 -1
  11. package/dist/locales/es.js +5 -5
  12. package/dist/locales/es.js.map +1 -1
  13. package/dist/locales/fr.js +5 -5
  14. package/dist/locales/fr.js.map +1 -1
  15. package/dist/locales/locale-codes.js +11 -2
  16. package/dist/locales/locale-codes.js.map +1 -1
  17. package/dist/locales/pt.js +5 -5
  18. package/dist/locales/pt.js.map +1 -1
  19. package/dist/temba-components.js +509 -87
  20. package/dist/temba-components.js.map +1 -1
  21. package/out-tsc/src/chart/TembaChart.js +136 -62
  22. package/out-tsc/src/chart/TembaChart.js.map +1 -1
  23. package/out-tsc/src/datepicker/DatePicker.js +11 -1
  24. package/out-tsc/src/datepicker/DatePicker.js.map +1 -1
  25. package/out-tsc/src/datepicker/RangePicker.js +595 -0
  26. package/out-tsc/src/datepicker/RangePicker.js.map +1 -0
  27. package/out-tsc/src/flow/Editor.js +210 -1
  28. package/out-tsc/src/flow/Editor.js.map +1 -1
  29. package/out-tsc/src/flow/EditorNode.js +98 -139
  30. package/out-tsc/src/flow/EditorNode.js.map +1 -1
  31. package/out-tsc/src/flow/StickyNote.js +272 -0
  32. package/out-tsc/src/flow/StickyNote.js.map +1 -0
  33. package/out-tsc/src/interfaces.js +1 -0
  34. package/out-tsc/src/interfaces.js.map +1 -1
  35. package/out-tsc/src/list/RunList.js +2 -1
  36. package/out-tsc/src/list/RunList.js.map +1 -1
  37. package/out-tsc/src/list/SortableList.js +9 -0
  38. package/out-tsc/src/list/SortableList.js.map +1 -1
  39. package/out-tsc/src/locales/es.js +5 -5
  40. package/out-tsc/src/locales/es.js.map +1 -1
  41. package/out-tsc/src/locales/fr.js +5 -5
  42. package/out-tsc/src/locales/fr.js.map +1 -1
  43. package/out-tsc/src/locales/locale-codes.js +11 -2
  44. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  45. package/out-tsc/src/locales/pt.js +5 -5
  46. package/out-tsc/src/locales/pt.js.map +1 -1
  47. package/out-tsc/src/store/AppState.js +33 -0
  48. package/out-tsc/src/store/AppState.js.map +1 -1
  49. package/out-tsc/src/vectoricon/index.js +2 -1
  50. package/out-tsc/src/vectoricon/index.js.map +1 -1
  51. package/out-tsc/temba-modules.js +5 -1
  52. package/out-tsc/temba-modules.js.map +1 -1
  53. package/out-tsc/test/temba-chart.test.js +36 -0
  54. package/out-tsc/test/temba-chart.test.js.map +1 -1
  55. package/out-tsc/test/temba-datepicker.test.js +1 -1
  56. package/out-tsc/test/temba-datepicker.test.js.map +1 -1
  57. package/out-tsc/test/temba-flow-editor-node.test.js +249 -5
  58. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  59. package/out-tsc/test/temba-range-picker.test.js +123 -0
  60. package/out-tsc/test/temba-range-picker.test.js.map +1 -0
  61. package/out-tsc/test/temba-select.test.js +10 -16
  62. package/out-tsc/test/temba-select.test.js.map +1 -1
  63. package/out-tsc/test/temba-webchat.test.js +4 -0
  64. package/out-tsc/test/temba-webchat.test.js.map +1 -1
  65. package/out-tsc/test/utils.test.js +62 -0
  66. package/out-tsc/test/utils.test.js.map +1 -1
  67. package/package.json +1 -1
  68. package/screenshots/truth/datepicker/range-picker-all.png +0 -0
  69. package/screenshots/truth/datepicker/range-picker-button-states.png +0 -0
  70. package/screenshots/truth/datepicker/range-picker-default.png +0 -0
  71. package/screenshots/truth/datepicker/range-picker-editing-start.png +0 -0
  72. package/screenshots/truth/datepicker/range-picker-initial-values.png +0 -0
  73. package/screenshots/truth/datepicker/range-picker-min-max.png +0 -0
  74. package/screenshots/truth/datepicker/range-picker-week.png +0 -0
  75. package/screenshots/truth/datepicker/range-picker-year.png +0 -0
  76. package/screenshots/truth/sticky-note/blue.png +0 -0
  77. package/screenshots/truth/sticky-note/gray.png +0 -0
  78. package/screenshots/truth/sticky-note/green.png +0 -0
  79. package/screenshots/truth/sticky-note/pink.png +0 -0
  80. package/screenshots/truth/sticky-note/yellow.png +0 -0
  81. package/screenshots/truth/webchat/connected-state.png +0 -0
  82. package/src/chart/TembaChart.ts +144 -66
  83. package/src/datepicker/DatePicker.ts +9 -1
  84. package/src/datepicker/RangePicker.ts +602 -0
  85. package/src/flow/Editor.ts +252 -2
  86. package/src/flow/EditorNode.ts +98 -156
  87. package/src/flow/StickyNote.ts +284 -0
  88. package/src/interfaces.ts +2 -1
  89. package/src/list/RunList.ts +2 -1
  90. package/src/list/SortableList.ts +11 -0
  91. package/src/locales/es.ts +18 -13
  92. package/src/locales/fr.ts +18 -13
  93. package/src/locales/locale-codes.ts +11 -2
  94. package/src/locales/pt.ts +18 -13
  95. package/src/store/AppState.ts +51 -1
  96. package/src/store/flow-definition.d.ts +8 -0
  97. package/src/vectoricon/index.ts +2 -1
  98. package/static/svg/index.pdf +137 -0
  99. package/temba-modules.ts +5 -1
  100. package/test/temba-chart.test.ts +47 -0
  101. package/test/temba-datepicker.test.ts +1 -1
  102. package/test/temba-flow-editor-node.test.ts +322 -6
  103. package/test/temba-range-picker.test.ts +193 -0
  104. package/test/temba-select.test.ts +11 -19
  105. package/test/temba-webchat.test.ts +7 -0
  106. package/test/utils.test.ts +98 -0
  107. package/web-dev-server.config.mjs +30 -22
  108. package/web-test-runner.config.mjs +2 -0
  109. package/demo/datepicker/example.html +0 -69
@@ -1,13 +1,17 @@
1
1
  import { __decorate } from "tslib";
2
2
  import { html } from 'lit-html';
3
3
  import { css, unsafeCSS } from 'lit';
4
- import { property } from 'lit/decorators.js';
4
+ import { property, state } from 'lit/decorators.js';
5
5
  import { getStore } from '../store/Store';
6
6
  import { fromStore, zustand } from '../store/AppState';
7
7
  import { RapidElement } from '../RapidElement';
8
8
  import { Plumber } from './Plumber';
9
9
  import { EditorNode } from './EditorNode';
10
+ export function snapToGrid(value) {
11
+ return Math.round(value / 20) * 20;
12
+ }
10
13
  const SAVE_QUIET_TIME = 500;
14
+ const DRAG_THRESHOLD = 10;
11
15
  export class Editor extends RapidElement {
12
16
  // unfortunately, jsplumb requires that we be in light DOM
13
17
  createRenderRoot() {
@@ -61,6 +65,15 @@ export class Editor extends RapidElement {
61
65
  margin: 20px;
62
66
  }
63
67
 
68
+ #canvas > .draggable {
69
+ position: absolute;
70
+ z-index: 100;
71
+ }
72
+
73
+ #canvas > .dragging {
74
+ z-index: 10000 !important;
75
+ }
76
+
64
77
  body .jtk-endpoint {
65
78
  width: initial;
66
79
  height: initial;
@@ -152,10 +165,20 @@ export class Editor extends RapidElement {
152
165
  super();
153
166
  // timer for debounced saving
154
167
  this.saveTimer = null;
168
+ // Drag state
169
+ this.isDragging = false;
170
+ this.isMouseDown = false;
171
+ this.dragStartPos = { x: 0, y: 0 };
172
+ this.currentDragItem = null;
173
+ this.startPos = { left: 0, top: 0 };
174
+ // Bound event handlers to maintain proper 'this' context
175
+ this.boundMouseMove = this.handleMouseMove.bind(this);
176
+ this.boundMouseUp = this.handleMouseUp.bind(this);
155
177
  }
156
178
  firstUpdated(changes) {
157
179
  super.firstUpdated(changes);
158
180
  this.plumber = new Plumber(this.querySelector('#canvas'));
181
+ this.setupGlobalEventListeners();
159
182
  if (changes.has('flow')) {
160
183
  getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
161
184
  }
@@ -165,6 +188,9 @@ export class Editor extends RapidElement {
165
188
  if (changes.has('canvasSize')) {
166
189
  // console.log('Setting canvas size', this.canvasSize);
167
190
  }
191
+ if (changes.has('definition')) {
192
+ this.updateCanvasSize();
193
+ }
168
194
  if (changes.has('dirtyDate')) {
169
195
  if (this.dirtyDate) {
170
196
  this.debouncedSave();
@@ -199,13 +225,169 @@ export class Editor extends RapidElement {
199
225
  clearTimeout(this.saveTimer);
200
226
  this.saveTimer = null;
201
227
  }
228
+ document.removeEventListener('mousemove', this.boundMouseMove);
229
+ document.removeEventListener('mouseup', this.boundMouseUp);
230
+ }
231
+ setupGlobalEventListeners() {
232
+ document.addEventListener('mousemove', this.boundMouseMove);
233
+ document.addEventListener('mouseup', this.boundMouseUp);
234
+ }
235
+ getPosition(uuid, type) {
236
+ var _a, _b, _c;
237
+ if (type === 'node') {
238
+ return (_a = this.definition._ui.nodes[uuid]) === null || _a === void 0 ? void 0 : _a.position;
239
+ }
240
+ else {
241
+ return (_c = (_b = this.definition._ui.stickies) === null || _b === void 0 ? void 0 : _b[uuid]) === null || _c === void 0 ? void 0 : _c.position;
242
+ }
243
+ }
244
+ updatePosition(uuid, type, position) {
245
+ var _a;
246
+ if (type === 'node') {
247
+ getStore().getState().updateNodePosition(uuid, position);
248
+ }
249
+ else {
250
+ const currentSticky = (_a = this.definition._ui.stickies) === null || _a === void 0 ? void 0 : _a[uuid];
251
+ if (currentSticky) {
252
+ getStore()
253
+ .getState()
254
+ .updateStickyNote(uuid, {
255
+ ...currentSticky,
256
+ position
257
+ });
258
+ }
259
+ }
260
+ }
261
+ handleMouseDown(event) {
262
+ const element = event.currentTarget;
263
+ // Only start dragging if clicking on the element itself, not on exits or other interactive elements
264
+ const target = event.target;
265
+ if (target.classList.contains('exit') || target.closest('.exit')) {
266
+ return;
267
+ }
268
+ const uuid = element.getAttribute('uuid');
269
+ const type = element.tagName === 'TEMBA-FLOW-NODE' ? 'node' : 'sticky';
270
+ const position = this.getPosition(uuid, type);
271
+ if (!position)
272
+ return;
273
+ // Set up potential drag state, but don't start dragging yet
274
+ this.isMouseDown = true;
275
+ this.dragStartPos = { x: event.clientX, y: event.clientY };
276
+ this.startPos = { left: position.left, top: position.top };
277
+ this.currentDragItem = {
278
+ uuid,
279
+ position,
280
+ element,
281
+ type
282
+ };
283
+ event.preventDefault();
284
+ event.stopPropagation();
285
+ }
286
+ handleMouseMove(event) {
287
+ if (!this.isMouseDown || !this.currentDragItem)
288
+ return;
289
+ const deltaX = event.clientX - this.dragStartPos.x;
290
+ const deltaY = event.clientY - this.dragStartPos.y;
291
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
292
+ // Only start dragging if we've moved beyond the threshold
293
+ if (!this.isDragging && distance > DRAG_THRESHOLD) {
294
+ this.isDragging = true;
295
+ // If this is a node, elevate connections
296
+ if (this.currentDragItem.type === 'node' && this.plumber) {
297
+ this.plumber.elevateNodeConnections(this.currentDragItem.uuid);
298
+ }
299
+ }
300
+ // If we're actually dragging, update positions
301
+ if (this.isDragging) {
302
+ const newLeft = this.startPos.left + deltaX;
303
+ const newTop = this.startPos.top + deltaY;
304
+ // Update the visual position during drag
305
+ this.currentDragItem.element.style.left = `${newLeft}px`;
306
+ this.currentDragItem.element.style.top = `${newTop}px`;
307
+ // Repaint connections if this is a node
308
+ if (this.currentDragItem.type === 'node' && this.plumber) {
309
+ this.plumber.repaintEverything();
310
+ }
311
+ }
312
+ }
313
+ handleMouseUp(event) {
314
+ if (!this.isMouseDown || !this.currentDragItem)
315
+ return;
316
+ // If we were actually dragging, handle the drag end
317
+ if (this.isDragging) {
318
+ // Restore normal z-index for node connections
319
+ if (this.currentDragItem.type === 'node' && this.plumber) {
320
+ this.plumber.restoreNodeConnections(this.currentDragItem.uuid);
321
+ }
322
+ const deltaX = event.clientX - this.dragStartPos.x;
323
+ const deltaY = event.clientY - this.dragStartPos.y;
324
+ const newLeft = this.startPos.left + deltaX;
325
+ const newTop = this.startPos.top + deltaY;
326
+ // Snap to 20px grid for final position
327
+ const snappedLeft = snapToGrid(newLeft);
328
+ const snappedTop = snapToGrid(newTop);
329
+ const newPosition = { left: snappedLeft, top: snappedTop };
330
+ // Update the store with the new snapped position
331
+ this.updatePosition(this.currentDragItem.uuid, this.currentDragItem.type, newPosition);
332
+ // Update canvas positions for nodes
333
+ if (this.currentDragItem.type === 'node') {
334
+ getStore()
335
+ .getState()
336
+ .updateCanvasPositions({
337
+ [this.currentDragItem.uuid]: newPosition
338
+ });
339
+ }
340
+ // Repaint connections if this is a node
341
+ if (this.currentDragItem.type === 'node' && this.plumber) {
342
+ this.plumber.repaintEverything();
343
+ }
344
+ }
345
+ // Reset all drag state
346
+ this.isDragging = false;
347
+ this.isMouseDown = false;
348
+ this.currentDragItem = null;
349
+ }
350
+ updateCanvasSize() {
351
+ var _a;
352
+ if (!this.definition)
353
+ return;
354
+ const store = getStore();
355
+ if (!store)
356
+ return;
357
+ // Calculate required canvas size based on all elements
358
+ let maxWidth = 0;
359
+ let maxHeight = 0;
360
+ // Check node positions
361
+ this.definition.nodes.forEach((node) => {
362
+ const ui = this.definition._ui.nodes[node.uuid];
363
+ if (ui && ui.position) {
364
+ const nodeElement = this.querySelector(`[id="${node.uuid}"]`);
365
+ if (nodeElement) {
366
+ const rect = nodeElement.getBoundingClientRect();
367
+ maxWidth = Math.max(maxWidth, ui.position.left + rect.width);
368
+ maxHeight = Math.max(maxHeight, ui.position.top + rect.height);
369
+ }
370
+ }
371
+ });
372
+ // Check sticky note positions
373
+ const stickies = ((_a = this.definition._ui) === null || _a === void 0 ? void 0 : _a.stickies) || {};
374
+ Object.values(stickies).forEach((sticky) => {
375
+ if (sticky.position) {
376
+ maxWidth = Math.max(maxWidth, sticky.position.left + 200); // Sticky note width
377
+ maxHeight = Math.max(maxHeight, sticky.position.top + 100); // Sticky note height
378
+ }
379
+ });
380
+ // Update canvas size in store
381
+ store.getState().expandCanvas(maxWidth, maxHeight);
202
382
  }
203
383
  render() {
384
+ var _a, _b;
204
385
  // we have to embed our own style since we are in light DOM
205
386
  const style = html `<style>
206
387
  ${unsafeCSS(Editor.styles.cssText)}
207
388
  ${unsafeCSS(EditorNode.styles.cssText)}
208
389
  </style>`;
390
+ const stickies = ((_b = (_a = this.definition) === null || _a === void 0 ? void 0 : _a._ui) === null || _b === void 0 ? void 0 : _b.stickies) || {};
209
391
  return html `${style}
210
392
  <div id="editor">
211
393
  <div
@@ -216,13 +398,34 @@ export class Editor extends RapidElement {
216
398
  <div id="canvas">
217
399
  ${this.definition
218
400
  ? this.definition.nodes.map((node) => {
401
+ var _a;
402
+ const position = this.definition._ui.nodes[node.uuid].position;
403
+ const dragging = this.isDragging && ((_a = this.currentDragItem) === null || _a === void 0 ? void 0 : _a.uuid) === node.uuid;
219
404
  return html `<temba-flow-node
405
+ class="draggable ${dragging ? 'dragging' : ''}"
406
+ @mousedown=${this.handleMouseDown.bind(this)}
407
+ uuid=${node.uuid}
408
+ style="left:${position.left}px; top:${position.top}px"
220
409
  .plumber=${this.plumber}
221
410
  .node=${node}
222
411
  .ui=${this.definition._ui.nodes[node.uuid]}
223
412
  ></temba-flow-node>`;
224
413
  })
225
414
  : html `<temba-loading></temba-loading>`}
415
+ ${Object.entries(stickies).map(([uuid, sticky]) => {
416
+ var _a;
417
+ const position = sticky.position || { left: 0, top: 0 };
418
+ const dragging = this.isDragging && ((_a = this.currentDragItem) === null || _a === void 0 ? void 0 : _a.uuid) === uuid;
419
+ return html `<temba-sticky-note
420
+ class="draggable ${dragging ? 'dragging' : ''}"
421
+ @mousedown=${this.handleMouseDown.bind(this)}
422
+ style="left:${position.left}px; top:${position.top}px; z-index: ${1000 +
423
+ position.top}"
424
+ uuid=${uuid}
425
+ .data=${sticky}
426
+ .dragging=${dragging}
427
+ ></temba-sticky-note>`;
428
+ })}
226
429
  </div>
227
430
  </div>
228
431
  </div>`;
@@ -243,4 +446,10 @@ __decorate([
243
446
  __decorate([
244
447
  fromStore(zustand, (state) => state.dirtyDate)
245
448
  ], Editor.prototype, "dirtyDate", void 0);
449
+ __decorate([
450
+ state()
451
+ ], Editor.prototype, "isDragging", void 0);
452
+ __decorate([
453
+ state()
454
+ ], Editor.prototype, "currentDragItem", void 0);
246
455
  //# sourceMappingURL=Editor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Editor.js","sourceRoot":"","sources":["../../../src/flow/Editor.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, TemplateResult } from 'lit-html';\nimport { css, PropertyValueMap, unsafeCSS } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { FlowDefinition } from '../store/flow-definition';\nimport { getStore } from '../store/Store';\nimport { AppState, fromStore, zustand } from '../store/AppState';\nimport { RapidElement } from '../RapidElement';\n\nimport { Plumber } from './Plumber';\nimport { EditorNode } from './EditorNode';\n\nconst SAVE_QUIET_TIME = 500;\n\nexport class Editor extends RapidElement {\n // unfortunately, jsplumb requires that we be in light DOM\n createRenderRoot() {\n return this;\n }\n\n // this is the master plumber\n private plumber: Plumber;\n\n // timer for debounced saving\n private saveTimer: number | null = null;\n\n @property({ type: String })\n public flow: string;\n\n @property({ type: String })\n public version: string;\n\n @fromStore(zustand, (state: AppState) => state.flowDefinition)\n private definition!: FlowDefinition;\n\n @fromStore(zustand, (state: AppState) => state.canvasSize)\n private canvasSize!: { width: number; height: number };\n\n @fromStore(zustand, (state: AppState) => state.dirtyDate)\n private dirtyDate!: Date;\n\n static get styles() {\n return css`\n #editor {\n overflow: scroll;\n flex: 1;\n }\n\n #grid {\n position: relative;\n background-color: #f9f9f9;\n background-position: 10px 10px;\n background-image: linear-gradient(\n 0deg,\n transparent 24%,\n rgba(61, 177, 255, 0.15) 25%,\n rgba(61, 177, 255, 0.15) 26%,\n transparent 27%,\n transparent 74%,\n rgba(61, 177, 255, 0.15) 75%,\n rgba(61, 177, 255, 0.15) 76%,\n transparent 77%,\n transparent\n ),\n linear-gradient(\n 90deg,\n transparent 24%,\n rgba(61, 177, 255, 0.15) 25%,\n rgba(61, 177, 255, 0.15) 26%,\n transparent 27%,\n transparent 74%,\n rgba(61, 177, 255, 0.15) 75%,\n rgba(61, 177, 255, 0.15) 76%,\n transparent 77%,\n transparent\n );\n background-size: 40px 40px;\n box-shadow: inset -5px 0 10px rgba(0, 0, 0, 0.05);\n border-top: 1px solid #e0e0e0;\n display: inline-block;\n width: 100%;\n }\n\n #canvas {\n position: relative;\n padding: 20px;\n margin: 20px;\n }\n\n body .jtk-endpoint {\n width: initial;\n height: initial;\n }\n\n .jtk-endpoint {\n z-index: 600;\n }\n\n .plumb-source {\n z-index: 600;\n border: 0px solid var(--color-connectors);\n }\n\n .plumb-source.connected {\n box-shadow: 0 3px 3px 0px rgba(0, 0, 0, 0.1);\n border-radius: 50%;\n }\n\n .plumb-source circle {\n fill: tomato;\n }\n\n .plumb-source.connected circle {\n fill: #fff;\n }\n\n .plumb-source svg {\n fill: var(--color-connectors) !important;\n stroke: var(--color-connectors);\n }\n\n .plumb-target {\n margin-top: -6px;\n z-index: 600;\n opacity: 0;\n cursor: pointer;\n }\n\n body .plumb-connector path {\n stroke: var(--color-connectors) !important;\n stroke-width: 3px;\n z-index: 10;\n }\n\n body .plumb-connector {\n z-index: 10;\n }\n\n body .plumb-connector.elevated {\n z-index: 550;\n }\n\n body .plumb-connector.elevated path {\n stroke: var(--color-connectors) !important;\n stroke-width: 3px;\n z-index: 550;\n }\n\n body .plumb-connector.elevated .plumb-arrow {\n fill: var(--color-connectors);\n stroke: var(--color-connectors);\n stroke-width: 0px;\n margin-top: 6px;\n z-index: 550;\n }\n\n body .plumb-connector .plumb-arrow {\n fill: var(--color-connectors);\n stroke: var(--color-connectors);\n stroke-width: 0px;\n margin-top: 6px;\n z-index: 10;\n }\n\n body svg.jtk-connector.jtk-hover path {\n stroke: var(--color-success) !important;\n stroke-width: 3px;\n }\n\n body .plumb-connector.jtk-hover .plumb-arrow {\n fill: var(--color-success) !important;\n stroke-width: 0px;\n z-index: 10;\n }\n `;\n }\n\n constructor() {\n super();\n }\n\n protected firstUpdated(\n changes: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.firstUpdated(changes);\n this.plumber = new Plumber(this.querySelector('#canvas'));\n if (changes.has('flow')) {\n getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);\n }\n }\n\n protected updated(\n changes: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.updated(changes);\n if (changes.has('canvasSize')) {\n // console.log('Setting canvas size', this.canvasSize);\n }\n\n if (changes.has('dirtyDate')) {\n if (this.dirtyDate) {\n this.debouncedSave();\n }\n }\n }\n\n private debouncedSave(): void {\n // Clear any existing timer\n if (this.saveTimer !== null) {\n clearTimeout(this.saveTimer);\n }\n\n this.saveTimer = window.setTimeout(() => {\n const now = new Date();\n const timeSinceLastChange = now.getTime() - this.dirtyDate.getTime();\n\n if (timeSinceLastChange >= SAVE_QUIET_TIME) {\n this.saveChanges();\n this.saveTimer = null;\n } else {\n this.debouncedSave();\n }\n }, SAVE_QUIET_TIME);\n }\n\n private saveChanges(): void {\n // post the flow definition to the server\n getStore().postJSON(`/flow/revisions/${this.flow}`, this.definition);\n getStore().getState().setDirtyDate(null);\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n if (this.saveTimer !== null) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n }\n\n public render(): TemplateResult {\n // we have to embed our own style since we are in light DOM\n const style = html`<style>\n ${unsafeCSS(Editor.styles.cssText)}\n ${unsafeCSS(EditorNode.styles.cssText)}\n </style>`;\n\n return html`${style}\n <div id=\"editor\">\n <div\n id=\"grid\"\n style=\"min-width:100%;width:${this.canvasSize.width}px; height:${this\n .canvasSize.height}px\"\n >\n <div id=\"canvas\">\n ${this.definition\n ? this.definition.nodes.map((node) => {\n return html`<temba-flow-node\n .plumber=${this.plumber}\n .node=${node}\n .ui=${this.definition._ui.nodes[node.uuid]}\n ></temba-flow-node>`;\n })\n : html`<temba-loading></temba-loading>`}\n </div>\n </div>\n </div>`;\n }\n}\n"]}
1
+ {"version":3,"file":"Editor.js","sourceRoot":"","sources":["../../../src/flow/Editor.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, TemplateResult } from 'lit-html';\nimport { css, PropertyValueMap, unsafeCSS } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { FlowDefinition, FlowPosition } from '../store/flow-definition';\nimport { getStore } from '../store/Store';\nimport { AppState, fromStore, zustand } from '../store/AppState';\nimport { RapidElement } from '../RapidElement';\n\nimport { Plumber } from './Plumber';\nimport { EditorNode } from './EditorNode';\n\nexport function snapToGrid(value: number): number {\n return Math.round(value / 20) * 20;\n}\n\nconst SAVE_QUIET_TIME = 500;\n\nexport interface DraggableItem {\n uuid: string;\n position: FlowPosition;\n element: HTMLElement;\n type: 'node' | 'sticky';\n}\n\nconst DRAG_THRESHOLD = 10;\n\nexport class Editor extends RapidElement {\n // unfortunately, jsplumb requires that we be in light DOM\n createRenderRoot() {\n return this;\n }\n\n // this is the master plumber\n private plumber: Plumber;\n\n // timer for debounced saving\n private saveTimer: number | null = null;\n\n @property({ type: String })\n public flow: string;\n\n @property({ type: String })\n public version: string;\n\n @fromStore(zustand, (state: AppState) => state.flowDefinition)\n private definition!: FlowDefinition;\n\n @fromStore(zustand, (state: AppState) => state.canvasSize)\n private canvasSize!: { width: number; height: number };\n\n @fromStore(zustand, (state: AppState) => state.dirtyDate)\n private dirtyDate!: Date;\n\n // Drag state\n @state()\n private isDragging = false;\n private isMouseDown = false;\n private dragStartPos = { x: 0, y: 0 };\n\n @state()\n private currentDragItem: DraggableItem | null = null;\n private startPos = { left: 0, top: 0 };\n\n // Bound event handlers to maintain proper 'this' context\n private boundMouseMove = this.handleMouseMove.bind(this);\n private boundMouseUp = this.handleMouseUp.bind(this);\n\n static get styles() {\n return css`\n #editor {\n overflow: scroll;\n flex: 1;\n }\n\n #grid {\n position: relative;\n background-color: #f9f9f9;\n background-position: 10px 10px;\n background-image: linear-gradient(\n 0deg,\n transparent 24%,\n rgba(61, 177, 255, 0.15) 25%,\n rgba(61, 177, 255, 0.15) 26%,\n transparent 27%,\n transparent 74%,\n rgba(61, 177, 255, 0.15) 75%,\n rgba(61, 177, 255, 0.15) 76%,\n transparent 77%,\n transparent\n ),\n linear-gradient(\n 90deg,\n transparent 24%,\n rgba(61, 177, 255, 0.15) 25%,\n rgba(61, 177, 255, 0.15) 26%,\n transparent 27%,\n transparent 74%,\n rgba(61, 177, 255, 0.15) 75%,\n rgba(61, 177, 255, 0.15) 76%,\n transparent 77%,\n transparent\n );\n background-size: 40px 40px;\n box-shadow: inset -5px 0 10px rgba(0, 0, 0, 0.05);\n border-top: 1px solid #e0e0e0;\n display: inline-block;\n width: 100%;\n }\n\n #canvas {\n position: relative;\n padding: 20px;\n margin: 20px;\n }\n\n #canvas > .draggable {\n position: absolute;\n z-index: 100;\n }\n\n #canvas > .dragging {\n z-index: 10000 !important;\n }\n\n body .jtk-endpoint {\n width: initial;\n height: initial;\n }\n\n .jtk-endpoint {\n z-index: 600;\n }\n\n .plumb-source {\n z-index: 600;\n border: 0px solid var(--color-connectors);\n }\n\n .plumb-source.connected {\n box-shadow: 0 3px 3px 0px rgba(0, 0, 0, 0.1);\n border-radius: 50%;\n }\n\n .plumb-source circle {\n fill: tomato;\n }\n\n .plumb-source.connected circle {\n fill: #fff;\n }\n\n .plumb-source svg {\n fill: var(--color-connectors) !important;\n stroke: var(--color-connectors);\n }\n\n .plumb-target {\n margin-top: -6px;\n z-index: 600;\n opacity: 0;\n cursor: pointer;\n }\n\n body .plumb-connector path {\n stroke: var(--color-connectors) !important;\n stroke-width: 3px;\n z-index: 10;\n }\n\n body .plumb-connector {\n z-index: 10;\n }\n\n body .plumb-connector.elevated {\n z-index: 550;\n }\n\n body .plumb-connector.elevated path {\n stroke: var(--color-connectors) !important;\n stroke-width: 3px;\n z-index: 550;\n }\n\n body .plumb-connector.elevated .plumb-arrow {\n fill: var(--color-connectors);\n stroke: var(--color-connectors);\n stroke-width: 0px;\n margin-top: 6px;\n z-index: 550;\n }\n\n body .plumb-connector .plumb-arrow {\n fill: var(--color-connectors);\n stroke: var(--color-connectors);\n stroke-width: 0px;\n margin-top: 6px;\n z-index: 10;\n }\n\n body svg.jtk-connector.jtk-hover path {\n stroke: var(--color-success) !important;\n stroke-width: 3px;\n }\n\n body .plumb-connector.jtk-hover .plumb-arrow {\n fill: var(--color-success) !important;\n stroke-width: 0px;\n z-index: 10;\n }\n `;\n }\n\n constructor() {\n super();\n }\n\n protected firstUpdated(\n changes: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.firstUpdated(changes);\n this.plumber = new Plumber(this.querySelector('#canvas'));\n this.setupGlobalEventListeners();\n if (changes.has('flow')) {\n getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);\n }\n }\n\n protected updated(\n changes: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.updated(changes);\n if (changes.has('canvasSize')) {\n // console.log('Setting canvas size', this.canvasSize);\n }\n\n if (changes.has('definition')) {\n this.updateCanvasSize();\n }\n\n if (changes.has('dirtyDate')) {\n if (this.dirtyDate) {\n this.debouncedSave();\n }\n }\n }\n\n private debouncedSave(): void {\n // Clear any existing timer\n if (this.saveTimer !== null) {\n clearTimeout(this.saveTimer);\n }\n\n this.saveTimer = window.setTimeout(() => {\n const now = new Date();\n const timeSinceLastChange = now.getTime() - this.dirtyDate.getTime();\n\n if (timeSinceLastChange >= SAVE_QUIET_TIME) {\n this.saveChanges();\n this.saveTimer = null;\n } else {\n this.debouncedSave();\n }\n }, SAVE_QUIET_TIME);\n }\n\n private saveChanges(): void {\n // post the flow definition to the server\n getStore().postJSON(`/flow/revisions/${this.flow}`, this.definition);\n getStore().getState().setDirtyDate(null);\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n if (this.saveTimer !== null) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n document.removeEventListener('mousemove', this.boundMouseMove);\n document.removeEventListener('mouseup', this.boundMouseUp);\n }\n\n private setupGlobalEventListeners(): void {\n document.addEventListener('mousemove', this.boundMouseMove);\n document.addEventListener('mouseup', this.boundMouseUp);\n }\n\n private getPosition(uuid: string, type: 'node' | 'sticky'): FlowPosition {\n if (type === 'node') {\n return this.definition._ui.nodes[uuid]?.position;\n } else {\n return this.definition._ui.stickies?.[uuid]?.position;\n }\n }\n\n private updatePosition(\n uuid: string,\n type: 'node' | 'sticky',\n position: FlowPosition\n ): void {\n if (type === 'node') {\n getStore().getState().updateNodePosition(uuid, position);\n } else {\n const currentSticky = this.definition._ui.stickies?.[uuid];\n if (currentSticky) {\n getStore()\n .getState()\n .updateStickyNote(uuid, {\n ...currentSticky,\n position\n });\n }\n }\n }\n\n private handleMouseDown(event: MouseEvent): void {\n const element = event.currentTarget as HTMLElement;\n // Only start dragging if clicking on the element itself, not on exits or other interactive elements\n const target = event.target as HTMLElement;\n if (target.classList.contains('exit') || target.closest('.exit')) {\n return;\n }\n\n const uuid = element.getAttribute('uuid');\n const type = element.tagName === 'TEMBA-FLOW-NODE' ? 'node' : 'sticky';\n\n const position = this.getPosition(uuid, type);\n if (!position) return;\n\n // Set up potential drag state, but don't start dragging yet\n this.isMouseDown = true;\n this.dragStartPos = { x: event.clientX, y: event.clientY };\n this.startPos = { left: position.left, top: position.top };\n this.currentDragItem = {\n uuid,\n position,\n element,\n type\n };\n\n event.preventDefault();\n event.stopPropagation();\n }\n\n private handleMouseMove(event: MouseEvent): void {\n if (!this.isMouseDown || !this.currentDragItem) return;\n\n const deltaX = event.clientX - this.dragStartPos.x;\n const deltaY = event.clientY - this.dragStartPos.y;\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n\n // Only start dragging if we've moved beyond the threshold\n if (!this.isDragging && distance > DRAG_THRESHOLD) {\n this.isDragging = true;\n\n // If this is a node, elevate connections\n if (this.currentDragItem.type === 'node' && this.plumber) {\n this.plumber.elevateNodeConnections(this.currentDragItem.uuid);\n }\n }\n\n // If we're actually dragging, update positions\n if (this.isDragging) {\n const newLeft = this.startPos.left + deltaX;\n const newTop = this.startPos.top + deltaY;\n\n // Update the visual position during drag\n this.currentDragItem.element.style.left = `${newLeft}px`;\n this.currentDragItem.element.style.top = `${newTop}px`;\n\n // Repaint connections if this is a node\n if (this.currentDragItem.type === 'node' && this.plumber) {\n this.plumber.repaintEverything();\n }\n }\n }\n\n private handleMouseUp(event: MouseEvent): void {\n if (!this.isMouseDown || !this.currentDragItem) return;\n\n // If we were actually dragging, handle the drag end\n if (this.isDragging) {\n // Restore normal z-index for node connections\n if (this.currentDragItem.type === 'node' && this.plumber) {\n this.plumber.restoreNodeConnections(this.currentDragItem.uuid);\n }\n\n const deltaX = event.clientX - this.dragStartPos.x;\n const deltaY = event.clientY - this.dragStartPos.y;\n\n const newLeft = this.startPos.left + deltaX;\n const newTop = this.startPos.top + deltaY;\n\n // Snap to 20px grid for final position\n const snappedLeft = snapToGrid(newLeft);\n const snappedTop = snapToGrid(newTop);\n\n const newPosition = { left: snappedLeft, top: snappedTop };\n\n // Update the store with the new snapped position\n this.updatePosition(\n this.currentDragItem.uuid,\n this.currentDragItem.type,\n newPosition\n );\n\n // Update canvas positions for nodes\n if (this.currentDragItem.type === 'node') {\n getStore()\n .getState()\n .updateCanvasPositions({\n [this.currentDragItem.uuid]: newPosition\n });\n }\n\n // Repaint connections if this is a node\n if (this.currentDragItem.type === 'node' && this.plumber) {\n this.plumber.repaintEverything();\n }\n }\n\n // Reset all drag state\n this.isDragging = false;\n this.isMouseDown = false;\n this.currentDragItem = null;\n }\n\n private updateCanvasSize(): void {\n if (!this.definition) return;\n\n const store = getStore();\n if (!store) return;\n\n // Calculate required canvas size based on all elements\n let maxWidth = 0;\n let maxHeight = 0;\n\n // Check node positions\n this.definition.nodes.forEach((node) => {\n const ui = this.definition._ui.nodes[node.uuid];\n if (ui && ui.position) {\n const nodeElement = this.querySelector(`[id=\"${node.uuid}\"]`);\n if (nodeElement) {\n const rect = nodeElement.getBoundingClientRect();\n maxWidth = Math.max(maxWidth, ui.position.left + rect.width);\n maxHeight = Math.max(maxHeight, ui.position.top + rect.height);\n }\n }\n });\n\n // Check sticky note positions\n const stickies = this.definition._ui?.stickies || {};\n Object.values(stickies).forEach((sticky) => {\n if (sticky.position) {\n maxWidth = Math.max(maxWidth, sticky.position.left + 200); // Sticky note width\n maxHeight = Math.max(maxHeight, sticky.position.top + 100); // Sticky note height\n }\n });\n\n // Update canvas size in store\n store.getState().expandCanvas(maxWidth, maxHeight);\n }\n\n public render(): TemplateResult {\n // we have to embed our own style since we are in light DOM\n const style = html`<style>\n ${unsafeCSS(Editor.styles.cssText)}\n ${unsafeCSS(EditorNode.styles.cssText)}\n </style>`;\n\n const stickies = this.definition?._ui?.stickies || {};\n\n return html`${style}\n <div id=\"editor\">\n <div\n id=\"grid\"\n style=\"min-width:100%;width:${this.canvasSize.width}px; height:${this\n .canvasSize.height}px\"\n >\n <div id=\"canvas\">\n ${this.definition\n ? this.definition.nodes.map((node) => {\n const position =\n this.definition._ui.nodes[node.uuid].position;\n\n const dragging =\n this.isDragging && this.currentDragItem?.uuid === node.uuid;\n\n return html`<temba-flow-node\n class=\"draggable ${dragging ? 'dragging' : ''}\"\n @mousedown=${this.handleMouseDown.bind(this)}\n uuid=${node.uuid}\n style=\"left:${position.left}px; top:${position.top}px\"\n .plumber=${this.plumber}\n .node=${node}\n .ui=${this.definition._ui.nodes[node.uuid]}\n ></temba-flow-node>`;\n })\n : html`<temba-loading></temba-loading>`}\n ${Object.entries(stickies).map(([uuid, sticky]) => {\n const position = sticky.position || { left: 0, top: 0 };\n const dragging =\n this.isDragging && this.currentDragItem?.uuid === uuid;\n return html`<temba-sticky-note\n class=\"draggable ${dragging ? 'dragging' : ''}\"\n @mousedown=${this.handleMouseDown.bind(this)}\n style=\"left:${position.left}px; top:${position.top}px; z-index: ${1000 +\n position.top}\"\n uuid=${uuid}\n .data=${sticky}\n .dragging=${dragging}\n ></temba-sticky-note>`;\n })}\n </div>\n </div>\n </div>`;\n }\n}\n"]}