@nyaruka/temba-components 0.129.2 → 0.129.3
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/.github/workflows/build.yml +6 -5
- package/.github/workflows/coverage.yml +80 -0
- package/CHANGELOG.md +22 -0
- package/README.md +6 -0
- package/check-coverage.js +133 -0
- package/demo/data/flows/sample-flow.json +107 -100
- package/dist/temba-components.js +893 -476
- package/dist/temba-components.js.map +1 -1
- package/generate-coverage-badge.sh +69 -0
- package/out-tsc/src/{vectoricon/index.js → Icons.js} +1 -1
- package/out-tsc/src/Icons.js.map +1 -0
- package/out-tsc/src/display/Alert.js.map +1 -0
- package/out-tsc/src/display/Anchor.js.map +1 -0
- package/out-tsc/src/display/Button.js.map +1 -0
- package/out-tsc/src/{charcount → display}/CharCount.js +159 -2
- package/out-tsc/src/display/CharCount.js.map +1 -0
- package/out-tsc/src/display/Chat.js.map +1 -0
- package/out-tsc/src/display/ContactName.js.map +1 -0
- package/out-tsc/src/display/ContactUrn.js.map +1 -0
- package/out-tsc/src/display/Dropdown.js.map +1 -0
- package/out-tsc/src/{vectoricon/VectorIcon.js → display/Icon.js} +2 -2
- package/out-tsc/src/display/Icon.js.map +1 -0
- package/out-tsc/src/display/Label.js.map +1 -0
- package/out-tsc/src/{leafletmap → display}/LeafletMap.js +16 -1
- package/out-tsc/src/display/LeafletMap.js.map +1 -0
- package/out-tsc/src/display/Lightbox.js.map +1 -0
- package/out-tsc/src/{loading → display}/Loading.js.map +1 -1
- package/out-tsc/src/{options → display}/Options.js.map +1 -1
- package/out-tsc/src/display/ProgressBar.js.map +1 -0
- package/out-tsc/src/display/TembaDate.js.map +1 -0
- package/out-tsc/src/display/TembaUser.js.map +1 -0
- package/out-tsc/src/display/Thumbnail.js.map +1 -0
- package/out-tsc/src/{tip → display}/Tip.js +1 -2
- package/out-tsc/src/display/Tip.js.map +1 -0
- package/out-tsc/src/display/Toast.js.map +1 -0
- package/out-tsc/src/display/sms/gsmsplitter.js.map +1 -0
- package/out-tsc/src/display/sms/gsmvalidator.js.map +1 -0
- package/out-tsc/src/display/sms/index.js.map +1 -0
- package/out-tsc/src/display/sms/unicodesplitter.js.map +1 -0
- package/out-tsc/src/events.js +2 -0
- package/out-tsc/src/events.js.map +1 -0
- package/out-tsc/src/excellent/ExcellentParser.js.map +1 -0
- package/out-tsc/src/excellent/helpers.js.map +1 -0
- package/out-tsc/src/flow/Editor.js +533 -140
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/EditorNode.js +287 -20
- package/out-tsc/src/flow/EditorNode.js.map +1 -1
- package/out-tsc/src/flow/Plumber.js +154 -74
- package/out-tsc/src/flow/Plumber.js.map +1 -1
- package/out-tsc/src/flow/StickyNote.js +153 -9
- package/out-tsc/src/flow/StickyNote.js.map +1 -1
- package/out-tsc/src/flow/config.js +88 -18
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/render.js +327 -10
- package/out-tsc/src/flow/render.js.map +1 -1
- package/out-tsc/src/{checkbox → form}/Checkbox.js +2 -2
- package/out-tsc/src/form/Checkbox.js.map +1 -0
- package/out-tsc/src/{colorpicker → form}/ColorPicker.js +1 -1
- package/out-tsc/src/form/ColorPicker.js.map +1 -0
- package/out-tsc/src/{completion → form}/Completion.js +2 -2
- package/out-tsc/src/form/Completion.js.map +1 -0
- package/out-tsc/src/{compose → form}/Compose.js +1 -1
- package/out-tsc/src/form/Compose.js.map +1 -0
- package/out-tsc/src/{contactsearch → form}/ContactSearch.js +2 -2
- package/out-tsc/src/form/ContactSearch.js.map +1 -0
- package/out-tsc/src/form/CroppieCSS.js.map +1 -0
- package/out-tsc/src/{datepicker → form}/DatePicker.js +1 -1
- package/out-tsc/src/form/DatePicker.js.map +1 -0
- package/out-tsc/src/{FormElement.js → form/FormElement.js} +1 -1
- package/out-tsc/src/form/FormElement.js.map +1 -0
- package/out-tsc/src/form/FormField.js.map +1 -0
- package/out-tsc/src/{imagepicker → form}/ImagePicker.js +2 -2
- package/out-tsc/src/form/ImagePicker.js.map +1 -0
- package/out-tsc/src/{mediapicker → form}/MediaPicker.js +1 -1
- package/out-tsc/src/form/MediaPicker.js.map +1 -0
- package/out-tsc/src/form/RangePicker.js.map +1 -0
- package/out-tsc/src/{slider → form}/TembaSlider.js +1 -1
- package/out-tsc/src/form/TembaSlider.js.map +1 -0
- package/out-tsc/src/{templates → form}/TemplateEditor.js +1 -1
- package/out-tsc/src/form/TemplateEditor.js.map +1 -0
- package/out-tsc/src/{textinput → form}/TextInput.js +3 -3
- package/out-tsc/src/form/TextInput.js.map +1 -0
- package/out-tsc/src/{omnibox → form/select}/Omnibox.js +2 -2
- package/out-tsc/src/form/select/Omnibox.js.map +1 -0
- package/out-tsc/src/{select → form/select}/PopupSelect.js +1 -1
- package/out-tsc/src/form/select/PopupSelect.js.map +1 -0
- package/out-tsc/src/{select → form/select}/Select.js +86 -87
- package/out-tsc/src/form/select/Select.js.map +1 -0
- package/out-tsc/src/{select → form/select}/UserSelect.js +1 -1
- package/out-tsc/src/form/select/UserSelect.js.map +1 -0
- package/out-tsc/src/{select → form/select}/WorkspaceSelect.js +1 -1
- package/out-tsc/src/form/select/WorkspaceSelect.js.map +1 -0
- package/out-tsc/src/interfaces.js +1 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/layout/Dialog.js.map +1 -0
- package/out-tsc/src/layout/Mask.js.map +1 -0
- package/out-tsc/src/{dialog → layout}/Modax.js.map +1 -1
- package/out-tsc/src/layout/Resizer.js.map +1 -0
- package/out-tsc/src/layout/Tab.js.map +1 -0
- package/out-tsc/src/layout/TabPane.js.map +1 -0
- package/out-tsc/src/list/NotificationList.js +1 -1
- package/out-tsc/src/list/NotificationList.js.map +1 -1
- package/out-tsc/src/list/RunList.js +1 -1
- package/out-tsc/src/list/RunList.js.map +1 -1
- package/out-tsc/src/list/ShortcutList.js.map +1 -1
- package/out-tsc/src/list/TembaMenu.js +1 -1
- package/out-tsc/src/list/TembaMenu.js.map +1 -1
- package/out-tsc/src/list/TicketList.js +1 -1
- package/out-tsc/src/list/TicketList.js.map +1 -1
- package/out-tsc/src/{aliaseditor → live}/AliasEditor.js +1 -1
- package/out-tsc/src/live/AliasEditor.js.map +1 -0
- package/out-tsc/src/{contacts → live}/ContactBadges.js +1 -1
- package/out-tsc/src/live/ContactBadges.js.map +1 -0
- package/out-tsc/src/{contacts → live}/ContactChat.js +79 -3
- package/out-tsc/src/live/ContactChat.js.map +1 -0
- package/out-tsc/src/{contacts → live}/ContactDetails.js +1 -1
- package/out-tsc/src/live/ContactDetails.js.map +1 -0
- package/out-tsc/src/{contacts → live}/ContactFieldEditor.js +2 -2
- package/out-tsc/src/live/ContactFieldEditor.js.map +1 -0
- package/out-tsc/src/live/ContactFields.js.map +1 -0
- package/out-tsc/src/live/ContactNameFetch.js.map +1 -0
- package/out-tsc/src/{contacts → live}/ContactNotepad.js +1 -1
- package/out-tsc/src/{contacts → live}/ContactNotepad.js.map +1 -1
- package/out-tsc/src/{contacts → live}/ContactPending.js +1 -1
- package/out-tsc/src/live/ContactPending.js.map +1 -0
- package/out-tsc/src/live/ContactStoreElement.js.map +1 -0
- package/out-tsc/src/live/FieldManager.js.map +1 -0
- package/out-tsc/src/live/StartProgress.js.map +1 -0
- package/out-tsc/src/live/TembaChart.js.map +1 -0
- package/out-tsc/src/store/AppState.js +54 -24
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/src/store/Store.js +1 -1
- package/out-tsc/src/store/Store.js.map +1 -1
- package/out-tsc/src/{utils/index.js → utils.js} +22 -1
- package/out-tsc/src/utils.js.map +1 -0
- package/out-tsc/src/webchat/WebChat.js +1 -1
- package/out-tsc/src/webchat/WebChat.js.map +1 -1
- package/out-tsc/temba-components.js +2 -2
- package/out-tsc/temba-components.js.map +1 -1
- package/out-tsc/temba-modules.js +54 -54
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/temba-webchat.js +2 -2
- package/out-tsc/temba-webchat.js.map +1 -1
- package/out-tsc/test/temba-alert.test.js +1 -1
- package/out-tsc/test/temba-alert.test.js.map +1 -1
- package/out-tsc/test/temba-appstate-language.test.js +90 -0
- package/out-tsc/test/temba-appstate-language.test.js.map +1 -1
- package/out-tsc/test/temba-charcount.test.js.map +1 -1
- package/out-tsc/test/temba-chart.test.js +1 -1
- package/out-tsc/test/temba-chart.test.js.map +1 -1
- package/out-tsc/test/temba-checkbox.test.js.map +1 -1
- package/out-tsc/test/temba-color-picker.test.js +1 -1
- package/out-tsc/test/temba-color-picker.test.js.map +1 -1
- package/out-tsc/test/temba-completion.test.js +1 -1
- package/out-tsc/test/temba-completion.test.js.map +1 -1
- package/out-tsc/test/temba-compose.test.js +1 -1
- package/out-tsc/test/temba-compose.test.js.map +1 -1
- package/out-tsc/test/temba-contact-badges.test.js +1 -1
- package/out-tsc/test/temba-contact-badges.test.js.map +1 -1
- package/out-tsc/test/temba-contact-chat.test.js +3 -1
- package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
- package/out-tsc/test/temba-contact-details.test.js +1 -1
- package/out-tsc/test/temba-contact-details.test.js.map +1 -1
- package/out-tsc/test/temba-contact-fields.test.js +1 -1
- package/out-tsc/test/temba-contact-fields.test.js.map +1 -1
- package/out-tsc/test/temba-contact-search.test.js +1 -1
- package/out-tsc/test/temba-contact-search.test.js.map +1 -1
- package/out-tsc/test/temba-date.test.js +5 -1
- package/out-tsc/test/temba-date.test.js.map +1 -1
- package/out-tsc/test/temba-datepicker.test.js +1 -1
- package/out-tsc/test/temba-datepicker.test.js.map +1 -1
- package/out-tsc/test/temba-dialog.test.js +1 -1
- package/out-tsc/test/temba-dialog.test.js.map +1 -1
- package/out-tsc/test/temba-dropdown.test.js +1 -1
- package/out-tsc/test/temba-dropdown.test.js.map +1 -1
- package/out-tsc/test/temba-excellent-helpers.test.js +316 -0
- package/out-tsc/test/temba-excellent-helpers.test.js.map +1 -0
- package/out-tsc/test/temba-field-manager.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor-node.test.js +414 -1
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor.test.js +185 -0
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber-connections.test.js +113 -0
- package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -0
- package/out-tsc/test/temba-flow-plumber.test.js +73 -93
- package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
- package/out-tsc/test/temba-flow-render.test.js +624 -1
- package/out-tsc/test/temba-flow-render.test.js.map +1 -1
- package/out-tsc/test/temba-flow-self-routing.test.js +172 -0
- package/out-tsc/test/temba-flow-self-routing.test.js.map +1 -0
- package/out-tsc/test/temba-formfield.test.js.map +1 -1
- package/out-tsc/test/temba-icon.test.js +1 -1
- package/out-tsc/test/temba-icon.test.js.map +1 -1
- package/out-tsc/test/temba-integration-markdown.test.js.map +1 -1
- package/out-tsc/test/temba-label.test.js +1 -1
- package/out-tsc/test/temba-label.test.js.map +1 -1
- package/out-tsc/test/temba-lightbox.test.js +1 -1
- package/out-tsc/test/temba-lightbox.test.js.map +1 -1
- package/out-tsc/test/temba-markdown.test.js +127 -0
- package/out-tsc/test/temba-markdown.test.js.map +1 -0
- package/out-tsc/test/temba-menu.test.js +1 -1
- package/out-tsc/test/temba-menu.test.js.map +1 -1
- package/out-tsc/test/temba-modax.test.js +1 -1
- package/out-tsc/test/temba-modax.test.js.map +1 -1
- package/out-tsc/test/temba-modules.test.js +47 -0
- package/out-tsc/test/temba-modules.test.js.map +1 -0
- package/out-tsc/test/temba-omnibox.test.js +1 -1
- package/out-tsc/test/temba-omnibox.test.js.map +1 -1
- package/out-tsc/test/temba-options.test.js.map +1 -1
- package/out-tsc/test/temba-range-picker.test.js +9 -2
- package/out-tsc/test/temba-range-picker.test.js.map +1 -1
- package/out-tsc/test/temba-rapid-element.test.js +273 -0
- package/out-tsc/test/temba-rapid-element.test.js.map +1 -0
- package/out-tsc/test/temba-resize-element.test.js +85 -0
- package/out-tsc/test/temba-resize-element.test.js.map +1 -0
- package/out-tsc/test/temba-select.test.js +2 -2
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/out-tsc/test/temba-slider.test.js.map +1 -1
- package/out-tsc/test/temba-sticky-note.test.js +194 -0
- package/out-tsc/test/temba-sticky-note.test.js.map +1 -0
- package/out-tsc/test/temba-template-editor.test.js.map +1 -1
- package/out-tsc/test/temba-textinput.test.js +1 -1
- package/out-tsc/test/temba-textinput.test.js.map +1 -1
- package/out-tsc/test/temba-tip.test.js +1 -1
- package/out-tsc/test/temba-tip.test.js.map +1 -1
- package/out-tsc/test/temba-toast.test.js +1 -1
- package/out-tsc/test/temba-toast.test.js.map +1 -1
- package/out-tsc/test/temba-utils-index.test.js +1 -1
- package/out-tsc/test/temba-utils-index.test.js.map +1 -1
- package/out-tsc/test/temba-utils-uuid.test.js +38 -0
- package/out-tsc/test/temba-utils-uuid.test.js.map +1 -0
- package/out-tsc/test/temba-webchat.test.js +28 -12
- package/out-tsc/test/temba-webchat.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +2 -6
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +18 -9
- package/rollup.components.mjs +1 -1
- package/screenshots/truth/datepicker/range-picker-all.png +0 -0
- package/screenshots/truth/datepicker/range-picker-button-states.png +0 -0
- package/screenshots/truth/datepicker/range-picker-default.png +0 -0
- package/screenshots/truth/datepicker/range-picker-editing-start.png +0 -0
- package/screenshots/truth/datepicker/range-picker-initial-values.png +0 -0
- package/screenshots/truth/datepicker/range-picker-week.png +0 -0
- package/screenshots/truth/datepicker/range-picker-year.png +0 -0
- package/screenshots/truth/sticky-note/blue-color.png +0 -0
- package/screenshots/truth/sticky-note/blue.png +0 -0
- package/screenshots/truth/sticky-note/color-picker-expanded.png +0 -0
- package/screenshots/truth/sticky-note/default.png +0 -0
- package/screenshots/truth/sticky-note/gray-color.png +0 -0
- package/screenshots/truth/sticky-note/gray.png +0 -0
- package/screenshots/truth/sticky-note/green-color.png +0 -0
- package/screenshots/truth/sticky-note/green.png +0 -0
- package/screenshots/truth/sticky-note/pink-color.png +0 -0
- package/screenshots/truth/sticky-note/pink.png +0 -0
- package/screenshots/truth/sticky-note/yellow-color.png +0 -0
- package/screenshots/truth/sticky-note/yellow.png +0 -0
- package/src/{charcount → display}/CharCount.ts +164 -2
- package/src/{vectoricon/VectorIcon.ts → display/Icon.ts} +1 -1
- package/src/{leafletmap → display}/LeafletMap.ts +19 -1
- package/src/{thumbnail → display}/Thumbnail.ts +1 -1
- package/src/{tip → display}/Tip.ts +1 -2
- package/src/{contacts/events.ts → events.ts} +1 -64
- package/src/flow/Editor.ts +655 -165
- package/src/flow/EditorNode.ts +337 -22
- package/src/flow/Plumber.ts +186 -79
- package/src/flow/StickyNote.ts +165 -9
- package/src/flow/config.ts +114 -18
- package/src/flow/render.ts +398 -11
- package/src/{checkbox → form}/Checkbox.ts +2 -2
- package/src/{colorpicker → form}/ColorPicker.ts +2 -2
- package/src/{completion → form}/Completion.ts +3 -3
- package/src/{compose → form}/Compose.ts +7 -7
- package/src/{contactsearch → form}/ContactSearch.ts +6 -6
- package/src/{datepicker → form}/DatePicker.ts +1 -1
- package/src/{FormElement.ts → form/FormElement.ts} +1 -1
- package/src/{imagepicker → form}/ImagePicker.ts +2 -2
- package/src/{mediapicker → form}/MediaPicker.ts +1 -1
- package/src/{slider → form}/TembaSlider.ts +1 -1
- package/src/{templates → form}/TemplateEditor.ts +2 -2
- package/src/{textinput → form}/TextInput.ts +5 -5
- package/src/{omnibox → form/select}/Omnibox.ts +2 -2
- package/src/{select → form/select}/PopupSelect.ts +1 -1
- package/src/{select → form/select}/Select.ts +124 -126
- package/src/{select → form/select}/UserSelect.ts +1 -1
- package/src/{select → form/select}/WorkspaceSelect.ts +1 -1
- package/src/interfaces.ts +2 -1
- package/src/{dialog → layout}/Dialog.ts +1 -1
- package/src/list/NotificationList.ts +2 -2
- package/src/list/RunList.ts +3 -3
- package/src/list/ShortcutList.ts +1 -1
- package/src/list/TembaMenu.ts +2 -2
- package/src/list/TicketList.ts +1 -1
- package/src/{aliaseditor → live}/AliasEditor.ts +3 -3
- package/src/{contacts → live}/ContactBadges.ts +1 -1
- package/src/{contacts → live}/ContactChat.ts +118 -8
- package/src/{contacts → live}/ContactDetails.ts +1 -1
- package/src/{contacts → live}/ContactFieldEditor.ts +4 -4
- package/src/{contacts → live}/ContactFields.ts +1 -1
- package/src/{contacts → live}/ContactNotepad.ts +1 -1
- package/src/{contacts → live}/ContactPending.ts +1 -1
- package/src/{chart → live}/TembaChart.ts +1 -1
- package/src/store/AppState.ts +75 -29
- package/src/store/Store.ts +1 -1
- package/src/store/flow-definition.d.ts +125 -0
- package/src/{utils/index.ts → utils.ts} +26 -10
- package/src/webchat/WebChat.ts +1 -1
- package/static/css/temba-components.css +1 -0
- package/svg.js +1 -4
- package/temba-components.ts +2 -2
- package/temba-modules.ts +54 -54
- package/temba-webchat.ts +2 -2
- package/test/temba-alert.test.ts +1 -1
- package/test/temba-appstate-language.test.ts +108 -0
- package/test/temba-charcount.test.ts +1 -1
- package/test/temba-chart.test.ts +1 -1
- package/test/temba-checkbox.test.ts +1 -1
- package/test/temba-color-picker.test.ts +1 -1
- package/test/temba-completion.test.ts +1 -1
- package/test/temba-compose.test.ts +1 -1
- package/test/temba-contact-badges.test.ts +1 -1
- package/test/temba-contact-chat.test.ts +6 -4
- package/test/temba-contact-details.test.ts +1 -1
- package/test/temba-contact-fields.test.ts +1 -1
- package/test/temba-contact-search.test.ts +2 -2
- package/test/temba-date.test.ts +8 -3
- package/test/temba-datepicker.test.ts +1 -1
- package/test/temba-dialog.test.ts +1 -1
- package/test/temba-dropdown.test.ts +1 -1
- package/test/temba-excellent-helpers.test.ts +417 -0
- package/test/temba-field-manager.test.ts +2 -2
- package/test/temba-flow-editor-node.test.ts +536 -1
- package/test/temba-flow-editor.test.ts +224 -0
- package/test/temba-flow-editor.test.ts.backup +563 -0
- package/test/temba-flow-plumber-connections.test.ts +142 -0
- package/test/temba-flow-plumber.test.ts +83 -120
- package/test/temba-flow-render.test.ts +787 -4
- package/test/temba-flow-self-routing.test.ts +215 -0
- package/test/temba-formfield.test.ts +1 -1
- package/test/temba-icon.test.ts +1 -1
- package/test/temba-integration-markdown.test.ts +1 -1
- package/test/temba-label.test.ts +1 -1
- package/test/temba-lightbox.test.ts +1 -1
- package/test/temba-markdown.test.ts +162 -0
- package/test/temba-menu.test.ts +1 -1
- package/test/temba-modax.test.ts +2 -2
- package/test/temba-modules.test.ts +56 -0
- package/test/temba-omnibox.test.ts +1 -1
- package/test/temba-options.test.ts +1 -1
- package/test/temba-range-picker.test.ts +17 -2
- package/test/temba-rapid-element.test.ts +341 -0
- package/test/temba-resize-element.test.ts +104 -0
- package/test/temba-select.test.ts +2 -2
- package/test/temba-slider.test.ts +1 -1
- package/test/temba-sticky-note.test.ts +281 -0
- package/test/temba-template-editor.test.ts +1 -1
- package/test/temba-textinput.test.ts +1 -1
- package/test/temba-tip.test.ts +1 -1
- package/test/temba-toast.test.ts +1 -1
- package/test/temba-utils-index.test.ts +1 -1
- package/test/temba-utils-index.test.ts.backup +1737 -0
- package/test/temba-utils-uuid.test.ts +48 -0
- package/test/temba-webchat.test.ts +30 -12
- package/test/utils.test.ts +5 -9
- package/web-dev-server.config.mjs +1 -1
- package/out-tsc/src/FormElement.js.map +0 -1
- package/out-tsc/src/alert/Alert.js.map +0 -1
- package/out-tsc/src/aliaseditor/AliasEditor.js.map +0 -1
- package/out-tsc/src/anchor/Anchor.js.map +0 -1
- package/out-tsc/src/button/Button.js.map +0 -1
- package/out-tsc/src/charcount/CharCount.js.map +0 -1
- package/out-tsc/src/charcount/helpers.js +0 -159
- package/out-tsc/src/charcount/helpers.js.map +0 -1
- package/out-tsc/src/chart/TembaChart.js.map +0 -1
- package/out-tsc/src/chat/Chat.js.map +0 -1
- package/out-tsc/src/checkbox/Checkbox.js.map +0 -1
- package/out-tsc/src/colorpicker/ColorPicker.js.map +0 -1
- package/out-tsc/src/completion/Completion.js.map +0 -1
- package/out-tsc/src/completion/ExcellentParser.js.map +0 -1
- package/out-tsc/src/completion/helpers.js.map +0 -1
- package/out-tsc/src/compose/Compose.js.map +0 -1
- package/out-tsc/src/contacts/ContactBadges.js.map +0 -1
- package/out-tsc/src/contacts/ContactChat.js.map +0 -1
- package/out-tsc/src/contacts/ContactDetails.js.map +0 -1
- package/out-tsc/src/contacts/ContactFieldEditor.js.map +0 -1
- package/out-tsc/src/contacts/ContactFields.js.map +0 -1
- package/out-tsc/src/contacts/ContactName.js.map +0 -1
- package/out-tsc/src/contacts/ContactNameFetch.js.map +0 -1
- package/out-tsc/src/contacts/ContactPending.js.map +0 -1
- package/out-tsc/src/contacts/ContactStoreElement.js.map +0 -1
- package/out-tsc/src/contacts/ContactUrn.js.map +0 -1
- package/out-tsc/src/contacts/events.js +0 -65
- package/out-tsc/src/contacts/events.js.map +0 -1
- package/out-tsc/src/contacts/helpers.js +0 -77
- package/out-tsc/src/contacts/helpers.js.map +0 -1
- package/out-tsc/src/contactsearch/ContactSearch.js.map +0 -1
- package/out-tsc/src/date/TembaDate.js.map +0 -1
- package/out-tsc/src/datepicker/DatePicker.js.map +0 -1
- package/out-tsc/src/datepicker/RangePicker.js.map +0 -1
- package/out-tsc/src/dialog/Dialog.js.map +0 -1
- package/out-tsc/src/dropdown/Dropdown.js.map +0 -1
- package/out-tsc/src/fields/FieldManager.js.map +0 -1
- package/out-tsc/src/formfield/FormField.js.map +0 -1
- package/out-tsc/src/imagepicker/CroppieCSS.js.map +0 -1
- package/out-tsc/src/imagepicker/ImagePicker.js.map +0 -1
- package/out-tsc/src/label/Label.js.map +0 -1
- package/out-tsc/src/leafletmap/LeafletMap.js.map +0 -1
- package/out-tsc/src/leafletmap/helpers.js +0 -17
- package/out-tsc/src/leafletmap/helpers.js.map +0 -1
- package/out-tsc/src/lightbox/Lightbox.js.map +0 -1
- package/out-tsc/src/mask/Mask.js.map +0 -1
- package/out-tsc/src/mediapicker/MediaPicker.js.map +0 -1
- package/out-tsc/src/omnibox/Omnibox.js.map +0 -1
- package/out-tsc/src/options/helpers.js +0 -28
- package/out-tsc/src/options/helpers.js.map +0 -1
- package/out-tsc/src/progress/ProgressBar.js.map +0 -1
- package/out-tsc/src/progress/StartProgress.js.map +0 -1
- package/out-tsc/src/resizer/Resizer.js.map +0 -1
- package/out-tsc/src/select/PopupSelect.js.map +0 -1
- package/out-tsc/src/select/Select.js.map +0 -1
- package/out-tsc/src/select/UserSelect.js.map +0 -1
- package/out-tsc/src/select/WorkspaceSelect.js.map +0 -1
- package/out-tsc/src/select/helpers.js +0 -1
- package/out-tsc/src/select/helpers.js.map +0 -1
- package/out-tsc/src/shadowless/Shadowless.js +0 -33
- package/out-tsc/src/shadowless/Shadowless.js.map +0 -1
- package/out-tsc/src/slider/TembaSlider.js.map +0 -1
- package/out-tsc/src/sms/gsmsplitter.js.map +0 -1
- package/out-tsc/src/sms/gsmvalidator.js.map +0 -1
- package/out-tsc/src/sms/index.js.map +0 -1
- package/out-tsc/src/sms/unicodesplitter.js.map +0 -1
- package/out-tsc/src/tabpane/Tab.js.map +0 -1
- package/out-tsc/src/tabpane/TabPane.js.map +0 -1
- package/out-tsc/src/templates/TemplateEditor.js.map +0 -1
- package/out-tsc/src/textinput/TextInput.js.map +0 -1
- package/out-tsc/src/textinput/helpers.js +0 -12
- package/out-tsc/src/textinput/helpers.js.map +0 -1
- package/out-tsc/src/thumbnail/Thumbnail.js.map +0 -1
- package/out-tsc/src/tip/Tip.js.map +0 -1
- package/out-tsc/src/tip/helpers.js +0 -7
- package/out-tsc/src/tip/helpers.js.map +0 -1
- package/out-tsc/src/toast/Toast.js.map +0 -1
- package/out-tsc/src/user/TembaUser.js.map +0 -1
- package/out-tsc/src/utils/index.js.map +0 -1
- package/out-tsc/src/vectoricon/VectorIcon.js.map +0 -1
- package/out-tsc/src/vectoricon/index.js.map +0 -1
- package/src/charcount/helpers.ts +0 -162
- package/src/contacts/helpers.ts +0 -103
- package/src/leafletmap/helpers.ts +0 -18
- package/src/options/helpers.ts +0 -37
- package/src/select/helpers.ts +0 -0
- package/src/shadowless/Shadowless.ts +0 -32
- package/src/textinput/helpers.ts +0 -11
- package/src/tip/helpers.ts +0 -7
- /package/out-tsc/src/{alert → display}/Alert.js +0 -0
- /package/out-tsc/src/{anchor → display}/Anchor.js +0 -0
- /package/out-tsc/src/{button → display}/Button.js +0 -0
- /package/out-tsc/src/{chat → display}/Chat.js +0 -0
- /package/out-tsc/src/{contacts → display}/ContactName.js +0 -0
- /package/out-tsc/src/{contacts → display}/ContactUrn.js +0 -0
- /package/out-tsc/src/{dropdown → display}/Dropdown.js +0 -0
- /package/out-tsc/src/{label → display}/Label.js +0 -0
- /package/out-tsc/src/{lightbox → display}/Lightbox.js +0 -0
- /package/out-tsc/src/{loading → display}/Loading.js +0 -0
- /package/out-tsc/src/{options → display}/Options.js +0 -0
- /package/out-tsc/src/{progress → display}/ProgressBar.js +0 -0
- /package/out-tsc/src/{date → display}/TembaDate.js +0 -0
- /package/out-tsc/src/{user → display}/TembaUser.js +0 -0
- /package/out-tsc/src/{thumbnail → display}/Thumbnail.js +0 -0
- /package/out-tsc/src/{toast → display}/Toast.js +0 -0
- /package/out-tsc/src/{sms → display/sms}/gsmsplitter.js +0 -0
- /package/out-tsc/src/{sms → display/sms}/gsmvalidator.js +0 -0
- /package/out-tsc/src/{sms → display/sms}/index.js +0 -0
- /package/out-tsc/src/{sms → display/sms}/unicodesplitter.js +0 -0
- /package/out-tsc/src/{completion → excellent}/ExcellentParser.js +0 -0
- /package/out-tsc/src/{completion → excellent}/helpers.js +0 -0
- /package/out-tsc/src/{imagepicker → form}/CroppieCSS.js +0 -0
- /package/out-tsc/src/{formfield → form}/FormField.js +0 -0
- /package/out-tsc/src/{datepicker → form}/RangePicker.js +0 -0
- /package/out-tsc/src/{dialog → layout}/Dialog.js +0 -0
- /package/out-tsc/src/{mask → layout}/Mask.js +0 -0
- /package/out-tsc/src/{dialog → layout}/Modax.js +0 -0
- /package/out-tsc/src/{resizer → layout}/Resizer.js +0 -0
- /package/out-tsc/src/{tabpane → layout}/Tab.js +0 -0
- /package/out-tsc/src/{tabpane → layout}/TabPane.js +0 -0
- /package/out-tsc/src/{contacts → live}/ContactFields.js +0 -0
- /package/out-tsc/src/{contacts → live}/ContactNameFetch.js +0 -0
- /package/out-tsc/src/{contacts → live}/ContactStoreElement.js +0 -0
- /package/out-tsc/src/{fields → live}/FieldManager.js +0 -0
- /package/out-tsc/src/{progress → live}/StartProgress.js +0 -0
- /package/out-tsc/src/{chart → live}/TembaChart.js +0 -0
- /package/src/{vectoricon/index.ts → Icons.ts} +0 -0
- /package/src/{alert → display}/Alert.ts +0 -0
- /package/src/{anchor → display}/Anchor.ts +0 -0
- /package/src/{button → display}/Button.ts +0 -0
- /package/src/{chat → display}/Chat.ts +0 -0
- /package/src/{contacts → display}/ContactName.ts +0 -0
- /package/src/{contacts → display}/ContactUrn.ts +0 -0
- /package/src/{dropdown → display}/Dropdown.ts +0 -0
- /package/src/{label → display}/Label.ts +0 -0
- /package/src/{lightbox → display}/Lightbox.ts +0 -0
- /package/src/{loading → display}/Loading.ts +0 -0
- /package/src/{options → display}/Options.ts +0 -0
- /package/src/{progress → display}/ProgressBar.ts +0 -0
- /package/src/{date → display}/TembaDate.ts +0 -0
- /package/src/{user → display}/TembaUser.ts +0 -0
- /package/src/{toast → display}/Toast.ts +0 -0
- /package/src/{sms → display/sms}/gsmsplitter.ts +0 -0
- /package/src/{sms → display/sms}/gsmvalidator.ts +0 -0
- /package/src/{sms → display/sms}/index.ts +0 -0
- /package/src/{sms → display/sms}/unicodesplitter.ts +0 -0
- /package/src/{completion → excellent}/ExcellentParser.ts +0 -0
- /package/src/{completion → excellent}/helpers.ts +0 -0
- /package/src/{imagepicker → form}/CroppieCSS.ts +0 -0
- /package/src/{formfield → form}/FormField.ts +0 -0
- /package/src/{datepicker → form}/RangePicker.ts +0 -0
- /package/src/{mask → layout}/Mask.ts +0 -0
- /package/src/{dialog → layout}/Modax.ts +0 -0
- /package/src/{resizer → layout}/Resizer.ts +0 -0
- /package/src/{tabpane → layout}/Tab.ts +0 -0
- /package/src/{tabpane → layout}/TabPane.ts +0 -0
- /package/src/{contacts → live}/ContactNameFetch.ts +0 -0
- /package/src/{contacts → live}/ContactStoreElement.ts +0 -0
- /package/src/{fields → live}/FieldManager.ts +0 -0
- /package/src/{progress → live}/StartProgress.ts +0 -0
package/src/flow/Editor.ts
CHANGED
|
@@ -5,12 +5,29 @@ import { FlowDefinition, FlowPosition } from '../store/flow-definition';
|
|
|
5
5
|
import { getStore } from '../store/Store';
|
|
6
6
|
import { AppState, fromStore, zustand } from '../store/AppState';
|
|
7
7
|
import { RapidElement } from '../RapidElement';
|
|
8
|
+
import { repeat } from 'lit-html/directives/repeat.js';
|
|
8
9
|
|
|
9
10
|
import { Plumber } from './Plumber';
|
|
10
11
|
import { EditorNode } from './EditorNode';
|
|
12
|
+
import { Dialog } from '../layout/Dialog';
|
|
13
|
+
import { Connection } from '@jsplumb/browser-ui';
|
|
11
14
|
|
|
12
15
|
export function snapToGrid(value: number): number {
|
|
13
|
-
|
|
16
|
+
const snapped = Math.round(value / 20) * 20;
|
|
17
|
+
return Math.max(snapped, 0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function findNodeForExit(
|
|
21
|
+
definition: FlowDefinition,
|
|
22
|
+
exitUuid: string
|
|
23
|
+
): string | null {
|
|
24
|
+
for (const node of definition.nodes) {
|
|
25
|
+
const exit = node.exits.find((e) => e.uuid === exitUuid);
|
|
26
|
+
if (exit) {
|
|
27
|
+
return node.uuid;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
14
31
|
}
|
|
15
32
|
|
|
16
33
|
const SAVE_QUIET_TIME = 500;
|
|
@@ -22,7 +39,14 @@ export interface DraggableItem {
|
|
|
22
39
|
type: 'node' | 'sticky';
|
|
23
40
|
}
|
|
24
41
|
|
|
25
|
-
|
|
42
|
+
export interface SelectionBox {
|
|
43
|
+
startX: number;
|
|
44
|
+
startY: number;
|
|
45
|
+
endX: number;
|
|
46
|
+
endY: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const DRAG_THRESHOLD = 5;
|
|
26
50
|
|
|
27
51
|
export class Editor extends RapidElement {
|
|
28
52
|
// unfortunately, jsplumb requires that we be in light DOM
|
|
@@ -61,9 +85,36 @@ export class Editor extends RapidElement {
|
|
|
61
85
|
private currentDragItem: DraggableItem | null = null;
|
|
62
86
|
private startPos = { left: 0, top: 0 };
|
|
63
87
|
|
|
88
|
+
// Selection state
|
|
89
|
+
@state()
|
|
90
|
+
private selectedItems: Set<string> = new Set();
|
|
91
|
+
|
|
92
|
+
@state()
|
|
93
|
+
private isSelecting = false;
|
|
94
|
+
|
|
95
|
+
@state()
|
|
96
|
+
private selectionBox: SelectionBox | null = null;
|
|
97
|
+
|
|
98
|
+
@state()
|
|
99
|
+
private targetId: string | null = null;
|
|
100
|
+
|
|
101
|
+
@state()
|
|
102
|
+
private sourceId: string | null = null;
|
|
103
|
+
|
|
104
|
+
@state()
|
|
105
|
+
private dragFromNodeId: string | null = null;
|
|
106
|
+
|
|
107
|
+
@state()
|
|
108
|
+
private isValidTarget = true;
|
|
109
|
+
|
|
110
|
+
private canvasMouseDown = false;
|
|
111
|
+
|
|
64
112
|
// Bound event handlers to maintain proper 'this' context
|
|
65
113
|
private boundMouseMove = this.handleMouseMove.bind(this);
|
|
66
114
|
private boundMouseUp = this.handleMouseUp.bind(this);
|
|
115
|
+
private boundGlobalMouseDown = this.handleGlobalMouseDown.bind(this);
|
|
116
|
+
private boundKeyDown = this.handleKeyDown.bind(this);
|
|
117
|
+
private boundCanvasDoubleClick = this.handleCanvasDoubleClick.bind(this);
|
|
67
118
|
|
|
68
119
|
static get styles() {
|
|
69
120
|
return css`
|
|
@@ -75,41 +126,23 @@ export class Editor extends RapidElement {
|
|
|
75
126
|
#grid {
|
|
76
127
|
position: relative;
|
|
77
128
|
background-color: #f9f9f9;
|
|
129
|
+
background-image: radial-gradient(
|
|
130
|
+
circle,
|
|
131
|
+
rgba(61, 177, 255, 0.3) 1px,
|
|
132
|
+
transparent 1px
|
|
133
|
+
);
|
|
134
|
+
background-size: 20px 20px;
|
|
78
135
|
background-position: 10px 10px;
|
|
79
|
-
background-image: linear-gradient(
|
|
80
|
-
0deg,
|
|
81
|
-
transparent 24%,
|
|
82
|
-
rgba(61, 177, 255, 0.15) 25%,
|
|
83
|
-
rgba(61, 177, 255, 0.15) 26%,
|
|
84
|
-
transparent 27%,
|
|
85
|
-
transparent 74%,
|
|
86
|
-
rgba(61, 177, 255, 0.15) 75%,
|
|
87
|
-
rgba(61, 177, 255, 0.15) 76%,
|
|
88
|
-
transparent 77%,
|
|
89
|
-
transparent
|
|
90
|
-
),
|
|
91
|
-
linear-gradient(
|
|
92
|
-
90deg,
|
|
93
|
-
transparent 24%,
|
|
94
|
-
rgba(61, 177, 255, 0.15) 25%,
|
|
95
|
-
rgba(61, 177, 255, 0.15) 26%,
|
|
96
|
-
transparent 27%,
|
|
97
|
-
transparent 74%,
|
|
98
|
-
rgba(61, 177, 255, 0.15) 75%,
|
|
99
|
-
rgba(61, 177, 255, 0.15) 76%,
|
|
100
|
-
transparent 77%,
|
|
101
|
-
transparent
|
|
102
|
-
);
|
|
103
|
-
background-size: 40px 40px;
|
|
104
136
|
box-shadow: inset -5px 0 10px rgba(0, 0, 0, 0.05);
|
|
105
137
|
border-top: 1px solid #e0e0e0;
|
|
106
|
-
display: inline-block;
|
|
107
138
|
width: 100%;
|
|
139
|
+
display: flex;
|
|
108
140
|
}
|
|
109
141
|
|
|
110
142
|
#canvas {
|
|
111
143
|
position: relative;
|
|
112
|
-
padding:
|
|
144
|
+
padding: 0px;
|
|
145
|
+
flex-grow: 1;
|
|
113
146
|
margin: 20px;
|
|
114
147
|
}
|
|
115
148
|
|
|
@@ -119,7 +152,7 @@ export class Editor extends RapidElement {
|
|
|
119
152
|
}
|
|
120
153
|
|
|
121
154
|
#canvas > .dragging {
|
|
122
|
-
z-index:
|
|
155
|
+
z-index: 99999 !important;
|
|
123
156
|
}
|
|
124
157
|
|
|
125
158
|
body .jtk-endpoint {
|
|
@@ -129,70 +162,44 @@ export class Editor extends RapidElement {
|
|
|
129
162
|
|
|
130
163
|
.jtk-endpoint {
|
|
131
164
|
z-index: 600;
|
|
165
|
+
opacity: 0;
|
|
132
166
|
}
|
|
133
167
|
|
|
134
168
|
.plumb-source {
|
|
135
169
|
z-index: 600;
|
|
136
|
-
|
|
170
|
+
cursor: pointer;
|
|
171
|
+
opacity: 0;
|
|
137
172
|
}
|
|
138
173
|
|
|
139
174
|
.plumb-source.connected {
|
|
140
|
-
box-shadow: 0 3px 3px 0px rgba(0, 0, 0, 0.1);
|
|
141
175
|
border-radius: 50%;
|
|
176
|
+
pointer-events: none;
|
|
142
177
|
}
|
|
143
178
|
|
|
144
179
|
.plumb-source circle {
|
|
145
|
-
fill:
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
.plumb-source.connected circle {
|
|
149
|
-
fill: #fff;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.plumb-source svg {
|
|
153
|
-
fill: var(--color-connectors) !important;
|
|
154
|
-
stroke: var(--color-connectors);
|
|
180
|
+
fill: purple;
|
|
155
181
|
}
|
|
156
182
|
|
|
157
183
|
.plumb-target {
|
|
158
|
-
margin-top: -6px;
|
|
159
184
|
z-index: 600;
|
|
160
185
|
opacity: 0;
|
|
161
186
|
cursor: pointer;
|
|
187
|
+
fill: transparent;
|
|
162
188
|
}
|
|
163
189
|
|
|
164
|
-
body .plumb-connector path {
|
|
190
|
+
body svg.jtk-connector.plumb-connector path {
|
|
165
191
|
stroke: var(--color-connectors) !important;
|
|
166
192
|
stroke-width: 3px;
|
|
167
|
-
z-index: 10;
|
|
168
193
|
}
|
|
169
194
|
|
|
170
195
|
body .plumb-connector {
|
|
171
|
-
z-index: 10;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
body .plumb-connector.elevated {
|
|
175
|
-
z-index: 550;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
body .plumb-connector.elevated path {
|
|
179
|
-
stroke: var(--color-connectors) !important;
|
|
180
|
-
stroke-width: 3px;
|
|
181
|
-
z-index: 550;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
body .plumb-connector.elevated .plumb-arrow {
|
|
185
|
-
fill: var(--color-connectors);
|
|
186
|
-
stroke: var(--color-connectors);
|
|
187
|
-
stroke-width: 0px;
|
|
188
|
-
margin-top: 6px;
|
|
189
|
-
z-index: 550;
|
|
196
|
+
z-index: 10 !important;
|
|
190
197
|
}
|
|
191
198
|
|
|
192
199
|
body .plumb-connector .plumb-arrow {
|
|
193
200
|
fill: var(--color-connectors);
|
|
194
201
|
stroke: var(--color-connectors);
|
|
195
|
-
stroke-width: 0px;
|
|
202
|
+
stroke-width: 0px !important;
|
|
196
203
|
margin-top: 6px;
|
|
197
204
|
z-index: 10;
|
|
198
205
|
}
|
|
@@ -207,6 +214,50 @@ export class Editor extends RapidElement {
|
|
|
207
214
|
stroke-width: 0px;
|
|
208
215
|
z-index: 10;
|
|
209
216
|
}
|
|
217
|
+
|
|
218
|
+
/* Connection dragging feedback */
|
|
219
|
+
body svg.jtk-connector.jtk-dragging {
|
|
220
|
+
z-index: 99999 !important;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.katavorio-drag-no-select svg.jtk-connector path,
|
|
224
|
+
.katavorio-drag-no-select svg.jtk-endpoint path {
|
|
225
|
+
pointer-events: none !important;
|
|
226
|
+
border: 1px solid purple;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* Connection target feedback */
|
|
230
|
+
temba-flow-node.connection-target-valid {
|
|
231
|
+
outline: 3px solid var(--color-success, #22c55e) !important;
|
|
232
|
+
outline-offset: 2px;
|
|
233
|
+
border-radius: var(--curvature);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
temba-flow-node.connection-target-invalid {
|
|
237
|
+
outline: 3px solid var(--color-error, #ef4444) !important;
|
|
238
|
+
outline-offset: 2px;
|
|
239
|
+
border-radius: var(--curvature);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* Selection box styles */
|
|
243
|
+
.selection-box {
|
|
244
|
+
position: absolute;
|
|
245
|
+
border: 2px dashed #6298f0ff;
|
|
246
|
+
background-color: rgba(59, 130, 246, 0.1);
|
|
247
|
+
z-index: 9999;
|
|
248
|
+
pointer-events: none;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* Selected item styles */
|
|
252
|
+
.draggable.selected {
|
|
253
|
+
outline: 3px solid #6298f0ff;
|
|
254
|
+
outline-offset: 0px;
|
|
255
|
+
border-radius: var(--curvature);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.jtk-floating-endpoint {
|
|
259
|
+
pointer-events: none;
|
|
260
|
+
}
|
|
210
261
|
`;
|
|
211
262
|
}
|
|
212
263
|
|
|
@@ -223,6 +274,51 @@ export class Editor extends RapidElement {
|
|
|
223
274
|
if (changes.has('flow')) {
|
|
224
275
|
getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
|
|
225
276
|
}
|
|
277
|
+
|
|
278
|
+
this.plumber.on('connection:drag', (info: Connection) => {
|
|
279
|
+
this.dragFromNodeId = document
|
|
280
|
+
.getElementById(info.sourceId)
|
|
281
|
+
.closest('.node').id;
|
|
282
|
+
this.sourceId = info.sourceId;
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
this.plumber.on('connection:abort', () => {
|
|
286
|
+
this.makeConnection();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
this.plumber.on('connection:detach', () => {
|
|
290
|
+
this.makeConnection();
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private makeConnection() {
|
|
295
|
+
if (this.sourceId && this.targetId && this.isValidTarget) {
|
|
296
|
+
this.plumber.connectIds(
|
|
297
|
+
this.dragFromNodeId,
|
|
298
|
+
this.sourceId,
|
|
299
|
+
this.targetId
|
|
300
|
+
);
|
|
301
|
+
getStore()
|
|
302
|
+
.getState()
|
|
303
|
+
.updateConnection(this.dragFromNodeId, this.sourceId, this.targetId);
|
|
304
|
+
|
|
305
|
+
setTimeout(() => {
|
|
306
|
+
this.plumber.repaintEverything();
|
|
307
|
+
}, 100);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Clean up visual feedback
|
|
311
|
+
document.querySelectorAll('temba-flow-node').forEach((node) => {
|
|
312
|
+
node.classList.remove(
|
|
313
|
+
'connection-target-valid',
|
|
314
|
+
'connection-target-invalid'
|
|
315
|
+
);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
this.sourceId = null;
|
|
319
|
+
this.targetId = null;
|
|
320
|
+
this.dragFromNodeId = null;
|
|
321
|
+
this.isValidTarget = true;
|
|
226
322
|
}
|
|
227
323
|
|
|
228
324
|
protected updated(
|
|
@@ -277,11 +373,25 @@ export class Editor extends RapidElement {
|
|
|
277
373
|
}
|
|
278
374
|
document.removeEventListener('mousemove', this.boundMouseMove);
|
|
279
375
|
document.removeEventListener('mouseup', this.boundMouseUp);
|
|
376
|
+
document.removeEventListener('mousedown', this.boundGlobalMouseDown);
|
|
377
|
+
document.removeEventListener('keydown', this.boundKeyDown);
|
|
378
|
+
|
|
379
|
+
const canvas = this.querySelector('#canvas');
|
|
380
|
+
if (canvas) {
|
|
381
|
+
canvas.removeEventListener('dblclick', this.boundCanvasDoubleClick);
|
|
382
|
+
}
|
|
280
383
|
}
|
|
281
384
|
|
|
282
385
|
private setupGlobalEventListeners(): void {
|
|
283
386
|
document.addEventListener('mousemove', this.boundMouseMove);
|
|
284
387
|
document.addEventListener('mouseup', this.boundMouseUp);
|
|
388
|
+
document.addEventListener('mousedown', this.boundGlobalMouseDown);
|
|
389
|
+
document.addEventListener('keydown', this.boundKeyDown);
|
|
390
|
+
|
|
391
|
+
const canvas = this.querySelector('#canvas');
|
|
392
|
+
if (canvas) {
|
|
393
|
+
canvas.addEventListener('dblclick', this.boundCanvasDoubleClick);
|
|
394
|
+
}
|
|
285
395
|
}
|
|
286
396
|
|
|
287
397
|
private getPosition(uuid: string, type: 'node' | 'sticky'): FlowPosition {
|
|
@@ -292,27 +402,10 @@ export class Editor extends RapidElement {
|
|
|
292
402
|
}
|
|
293
403
|
}
|
|
294
404
|
|
|
295
|
-
private updatePosition(
|
|
296
|
-
uuid: string,
|
|
297
|
-
type: 'node' | 'sticky',
|
|
298
|
-
position: FlowPosition
|
|
299
|
-
): void {
|
|
300
|
-
if (type === 'node') {
|
|
301
|
-
getStore().getState().updateNodePosition(uuid, position);
|
|
302
|
-
} else {
|
|
303
|
-
const currentSticky = this.definition._ui.stickies?.[uuid];
|
|
304
|
-
if (currentSticky) {
|
|
305
|
-
getStore()
|
|
306
|
-
.getState()
|
|
307
|
-
.updateStickyNote(uuid, {
|
|
308
|
-
...currentSticky,
|
|
309
|
-
position
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
405
|
private handleMouseDown(event: MouseEvent): void {
|
|
406
|
+
// ignore right clicks
|
|
407
|
+
if (event.button !== 0) return;
|
|
408
|
+
|
|
316
409
|
const element = event.currentTarget as HTMLElement;
|
|
317
410
|
// Only start dragging if clicking on the element itself, not on exits or other interactive elements
|
|
318
411
|
const target = event.target as HTMLElement;
|
|
@@ -326,7 +419,17 @@ export class Editor extends RapidElement {
|
|
|
326
419
|
const position = this.getPosition(uuid, type);
|
|
327
420
|
if (!position) return;
|
|
328
421
|
|
|
329
|
-
//
|
|
422
|
+
// If clicking on a non-selected item, clear selection unless Ctrl/Cmd is held
|
|
423
|
+
if (!this.selectedItems.has(uuid) && !event.ctrlKey && !event.metaKey) {
|
|
424
|
+
this.selectedItems.clear();
|
|
425
|
+
// Don't add single items to selection - single clicks just clear existing selection
|
|
426
|
+
} else if (!this.selectedItems.has(uuid)) {
|
|
427
|
+
// Add this item to selection only if Ctrl/Cmd is held
|
|
428
|
+
this.selectedItems.add(uuid);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Always set up drag state regardless of selection status
|
|
432
|
+
// This allows single nodes to be dragged without being selected
|
|
330
433
|
this.isMouseDown = true;
|
|
331
434
|
this.dragStartPos = { x: event.clientX, y: event.clientY };
|
|
332
435
|
this.startPos = { left: position.left, top: position.top };
|
|
@@ -341,7 +444,284 @@ export class Editor extends RapidElement {
|
|
|
341
444
|
event.stopPropagation();
|
|
342
445
|
}
|
|
343
446
|
|
|
447
|
+
private handleGlobalMouseDown(event: MouseEvent): void {
|
|
448
|
+
// ignore right clicks
|
|
449
|
+
if (event.button !== 0) return;
|
|
450
|
+
|
|
451
|
+
// Check if the click is within our canvas
|
|
452
|
+
const canvasRect = this.querySelector('#grid')?.getBoundingClientRect();
|
|
453
|
+
|
|
454
|
+
if (!canvasRect) return;
|
|
455
|
+
|
|
456
|
+
const isWithinCanvas =
|
|
457
|
+
event.clientX >= canvasRect.left &&
|
|
458
|
+
event.clientX <= canvasRect.right &&
|
|
459
|
+
event.clientY >= canvasRect.top &&
|
|
460
|
+
event.clientY <= canvasRect.bottom;
|
|
461
|
+
|
|
462
|
+
if (!isWithinCanvas) return;
|
|
463
|
+
|
|
464
|
+
// Check if we clicked on a draggable item (node or sticky)
|
|
465
|
+
const target = event.target as HTMLElement;
|
|
466
|
+
const clickedOnDraggable = target.closest('.draggable');
|
|
467
|
+
|
|
468
|
+
if (clickedOnDraggable) {
|
|
469
|
+
// This is handled by the individual item mousedown handlers
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// We clicked on empty canvas space, start selection
|
|
474
|
+
this.handleCanvasMouseDown(event);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
private handleCanvasMouseDown(event: MouseEvent): void {
|
|
478
|
+
const target = event.target as HTMLElement;
|
|
479
|
+
if (target.id === 'canvas' || target.id === 'grid') {
|
|
480
|
+
// Ignore clicks on exits
|
|
481
|
+
|
|
482
|
+
// Start selection box
|
|
483
|
+
this.canvasMouseDown = true;
|
|
484
|
+
this.dragStartPos = { x: event.clientX, y: event.clientY };
|
|
485
|
+
|
|
486
|
+
const canvasRect = this.querySelector('#canvas')?.getBoundingClientRect();
|
|
487
|
+
if (canvasRect) {
|
|
488
|
+
// Clear current selection
|
|
489
|
+
this.selectedItems.clear();
|
|
490
|
+
|
|
491
|
+
const relativeX = event.clientX - canvasRect.left;
|
|
492
|
+
const relativeY = event.clientY - canvasRect.top;
|
|
493
|
+
|
|
494
|
+
this.selectionBox = {
|
|
495
|
+
startX: relativeX,
|
|
496
|
+
startY: relativeY,
|
|
497
|
+
endX: relativeX,
|
|
498
|
+
endY: relativeY
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
event.preventDefault();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
private handleKeyDown(event: KeyboardEvent): void {
|
|
507
|
+
if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
508
|
+
if (this.selectedItems.size > 0) {
|
|
509
|
+
this.showDeleteConfirmation();
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (event.key === 'Escape') {
|
|
513
|
+
this.selectedItems.clear();
|
|
514
|
+
this.requestUpdate();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
private showDeleteConfirmation(): void {
|
|
519
|
+
const itemCount = this.selectedItems.size;
|
|
520
|
+
const itemType = itemCount === 1 ? 'item' : 'items';
|
|
521
|
+
|
|
522
|
+
// Create and show confirmation dialog
|
|
523
|
+
const dialog = document.createElement('temba-dialog') as Dialog;
|
|
524
|
+
dialog.header = 'Delete Items';
|
|
525
|
+
dialog.primaryButtonName = 'Delete';
|
|
526
|
+
dialog.cancelButtonName = 'Cancel';
|
|
527
|
+
dialog.destructive = true;
|
|
528
|
+
dialog.innerHTML = `<div style="padding: 20px;">Are you sure you want to delete ${itemCount} ${itemType}?</div>`;
|
|
529
|
+
|
|
530
|
+
dialog.addEventListener('temba-button-clicked', (event: any) => {
|
|
531
|
+
if (event.detail.button.name === 'Delete') {
|
|
532
|
+
this.deleteSelectedItems();
|
|
533
|
+
dialog.open = false;
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// Add to document and show
|
|
538
|
+
document.body.appendChild(dialog);
|
|
539
|
+
dialog.open = true;
|
|
540
|
+
|
|
541
|
+
// Clean up dialog when closed
|
|
542
|
+
dialog.addEventListener('temba-dialog-hidden', () => {
|
|
543
|
+
document.body.removeChild(dialog);
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
private deleteNodes(uuids: string[]): void {
|
|
548
|
+
// Clean up jsPlumb connections for nodes before removing them
|
|
549
|
+
uuids.forEach((uuid) => {
|
|
550
|
+
this.plumber.removeNodeConnections(uuid);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// Now remove them from the definition
|
|
554
|
+
if (uuids.length > 0 && this.plumber) {
|
|
555
|
+
getStore().getState().removeNodes(uuids);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private deleteSelectedItems(): void {
|
|
560
|
+
const nodes = Array.from(this.selectedItems).filter((uuid) =>
|
|
561
|
+
this.definition.nodes.some((node) => node.uuid === uuid)
|
|
562
|
+
);
|
|
563
|
+
this.deleteNodes(Array.from(nodes));
|
|
564
|
+
|
|
565
|
+
const stickies = Array.from(this.selectedItems).filter(
|
|
566
|
+
(uuid) => this.definition._ui?.stickies?.[uuid]
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
getStore().getState().removeStickyNotes(stickies);
|
|
570
|
+
|
|
571
|
+
// Clear selection
|
|
572
|
+
this.selectedItems.clear();
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
private updateSelectionBox(event: MouseEvent): void {
|
|
576
|
+
if (!this.selectionBox || !this.canvasMouseDown) return;
|
|
577
|
+
|
|
578
|
+
const canvasRect = this.querySelector('#canvas')?.getBoundingClientRect();
|
|
579
|
+
if (!canvasRect) return;
|
|
580
|
+
|
|
581
|
+
const relativeX = event.clientX - canvasRect.left;
|
|
582
|
+
const relativeY = event.clientY - canvasRect.top;
|
|
583
|
+
|
|
584
|
+
this.selectionBox = {
|
|
585
|
+
...this.selectionBox,
|
|
586
|
+
endX: relativeX,
|
|
587
|
+
endY: relativeY
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
// Update selected items based on selection box
|
|
591
|
+
this.updateSelectedItemsFromBox();
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
private updateSelectedItemsFromBox(): void {
|
|
595
|
+
if (!this.selectionBox) return;
|
|
596
|
+
|
|
597
|
+
const newSelection = new Set<string>();
|
|
598
|
+
|
|
599
|
+
const boxLeft = Math.min(this.selectionBox.startX, this.selectionBox.endX);
|
|
600
|
+
const boxTop = Math.min(this.selectionBox.startY, this.selectionBox.endY);
|
|
601
|
+
const boxRight = Math.max(this.selectionBox.startX, this.selectionBox.endX);
|
|
602
|
+
const boxBottom = Math.max(
|
|
603
|
+
this.selectionBox.startY,
|
|
604
|
+
this.selectionBox.endY
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
// Check nodes
|
|
608
|
+
this.definition?.nodes.forEach((node) => {
|
|
609
|
+
const nodeElement = this.querySelector(`[id="${node.uuid}"]`);
|
|
610
|
+
if (nodeElement) {
|
|
611
|
+
const position = this.definition._ui.nodes[node.uuid]?.position;
|
|
612
|
+
if (position) {
|
|
613
|
+
const rect = nodeElement.getBoundingClientRect();
|
|
614
|
+
const canvasRect =
|
|
615
|
+
this.querySelector('#canvas')?.getBoundingClientRect();
|
|
616
|
+
|
|
617
|
+
if (canvasRect) {
|
|
618
|
+
const nodeLeft = position.left;
|
|
619
|
+
const nodeTop = position.top;
|
|
620
|
+
const nodeRight = nodeLeft + rect.width;
|
|
621
|
+
const nodeBottom = nodeTop + rect.height;
|
|
622
|
+
|
|
623
|
+
// Check if selection box intersects with node
|
|
624
|
+
if (
|
|
625
|
+
boxLeft < nodeRight &&
|
|
626
|
+
boxRight > nodeLeft &&
|
|
627
|
+
boxTop < nodeBottom &&
|
|
628
|
+
boxBottom > nodeTop
|
|
629
|
+
) {
|
|
630
|
+
newSelection.add(node.uuid);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
// Check sticky notes
|
|
638
|
+
const stickies = this.definition?._ui?.stickies || {};
|
|
639
|
+
Object.entries(stickies).forEach(([uuid, sticky]) => {
|
|
640
|
+
if (sticky.position) {
|
|
641
|
+
const stickyElement = this.querySelector(
|
|
642
|
+
`temba-sticky-note[uuid="${uuid}"]`
|
|
643
|
+
) as HTMLElement;
|
|
644
|
+
|
|
645
|
+
if (stickyElement) {
|
|
646
|
+
// Use clientWidth/clientHeight instead of getBoundingClientRect() to get element dimensions
|
|
647
|
+
// This avoids the coordinate system mismatch between viewport and canvas coordinates
|
|
648
|
+
const width = stickyElement.clientWidth;
|
|
649
|
+
const height = stickyElement.clientHeight;
|
|
650
|
+
|
|
651
|
+
// Use the canvas coordinates from the sticky's position
|
|
652
|
+
const stickyLeft = sticky.position.left;
|
|
653
|
+
const stickyTop = sticky.position.top;
|
|
654
|
+
const stickyRight = stickyLeft + width;
|
|
655
|
+
const stickyBottom = stickyTop + height;
|
|
656
|
+
|
|
657
|
+
// Check if selection box intersects with sticky
|
|
658
|
+
if (
|
|
659
|
+
boxLeft < stickyRight &&
|
|
660
|
+
boxRight > stickyLeft &&
|
|
661
|
+
boxTop < stickyBottom &&
|
|
662
|
+
boxBottom > stickyTop
|
|
663
|
+
) {
|
|
664
|
+
newSelection.add(uuid);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
this.selectedItems = newSelection;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
private renderSelectionBox(): TemplateResult | string {
|
|
674
|
+
if (!this.selectionBox || !this.isSelecting) return '';
|
|
675
|
+
|
|
676
|
+
const left = Math.min(this.selectionBox.startX, this.selectionBox.endX);
|
|
677
|
+
const top = Math.min(this.selectionBox.startY, this.selectionBox.endY);
|
|
678
|
+
const width = Math.abs(this.selectionBox.endX - this.selectionBox.startX);
|
|
679
|
+
const height = Math.abs(this.selectionBox.endY - this.selectionBox.startY);
|
|
680
|
+
|
|
681
|
+
return html`<div
|
|
682
|
+
class="selection-box"
|
|
683
|
+
style="left: ${left}px; top: ${top}px; width: ${width}px; height: ${height}px;"
|
|
684
|
+
></div>`;
|
|
685
|
+
}
|
|
686
|
+
|
|
344
687
|
private handleMouseMove(event: MouseEvent): void {
|
|
688
|
+
// Handle selection box drawing
|
|
689
|
+
if (this.canvasMouseDown && !this.isMouseDown) {
|
|
690
|
+
this.isSelecting = true;
|
|
691
|
+
this.updateSelectionBox(event);
|
|
692
|
+
this.requestUpdate(); // Force re-render
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (this.plumber.connectionDragging) {
|
|
697
|
+
const targetNode = document.querySelector('temba-flow-node:hover');
|
|
698
|
+
|
|
699
|
+
// Clear previous target styles
|
|
700
|
+
document.querySelectorAll('temba-flow-node').forEach((node) => {
|
|
701
|
+
node.classList.remove(
|
|
702
|
+
'connection-target-valid',
|
|
703
|
+
'connection-target-invalid'
|
|
704
|
+
);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
if (targetNode) {
|
|
708
|
+
this.targetId = targetNode.getAttribute('uuid');
|
|
709
|
+
// Check if target is different from source node (prevent self-targeting)
|
|
710
|
+
this.isValidTarget = this.targetId !== this.dragFromNodeId;
|
|
711
|
+
|
|
712
|
+
// Apply visual feedback based on validity
|
|
713
|
+
if (this.isValidTarget) {
|
|
714
|
+
targetNode.classList.add('connection-target-valid');
|
|
715
|
+
} else {
|
|
716
|
+
targetNode.classList.add('connection-target-invalid');
|
|
717
|
+
}
|
|
718
|
+
} else {
|
|
719
|
+
this.targetId = null;
|
|
720
|
+
this.isValidTarget = true;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Handle item dragging
|
|
345
725
|
if (!this.isMouseDown || !this.currentDragItem) return;
|
|
346
726
|
|
|
347
727
|
const deltaX = event.clientX - this.dragStartPos.x;
|
|
@@ -351,77 +731,119 @@ export class Editor extends RapidElement {
|
|
|
351
731
|
// Only start dragging if we've moved beyond the threshold
|
|
352
732
|
if (!this.isDragging && distance > DRAG_THRESHOLD) {
|
|
353
733
|
this.isDragging = true;
|
|
354
|
-
|
|
355
|
-
// If this is a node, elevate connections
|
|
356
|
-
if (this.currentDragItem.type === 'node' && this.plumber) {
|
|
357
|
-
this.plumber.elevateNodeConnections(this.currentDragItem.uuid);
|
|
358
|
-
}
|
|
359
734
|
}
|
|
360
735
|
|
|
361
736
|
// If we're actually dragging, update positions
|
|
362
737
|
if (this.isDragging) {
|
|
363
|
-
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
738
|
+
// Determine what items to move
|
|
739
|
+
const itemsToMove =
|
|
740
|
+
this.selectedItems.has(this.currentDragItem.uuid) &&
|
|
741
|
+
this.selectedItems.size > 1
|
|
742
|
+
? Array.from(this.selectedItems)
|
|
743
|
+
: [this.currentDragItem.uuid];
|
|
744
|
+
|
|
745
|
+
itemsToMove.forEach((uuid) => {
|
|
746
|
+
const element = this.querySelector(`[uuid="${uuid}"]`) as HTMLElement;
|
|
747
|
+
if (element) {
|
|
748
|
+
const type =
|
|
749
|
+
element.tagName === 'TEMBA-FLOW-NODE' ? 'node' : 'sticky';
|
|
750
|
+
const position = this.getPosition(uuid, type);
|
|
751
|
+
|
|
752
|
+
if (position) {
|
|
753
|
+
const newLeft = position.left + deltaX;
|
|
754
|
+
const newTop = position.top + deltaY;
|
|
755
|
+
|
|
756
|
+
// Update the visual position during drag
|
|
757
|
+
element.style.left = `${newLeft}px`;
|
|
758
|
+
element.style.top = `${newTop}px`;
|
|
759
|
+
|
|
760
|
+
// Add dragging class to ensure highest z-index
|
|
761
|
+
element.classList.add('dragging');
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
});
|
|
369
765
|
|
|
370
|
-
|
|
371
|
-
if (this.currentDragItem.type === 'node' && this.plumber) {
|
|
372
|
-
this.plumber.repaintEverything();
|
|
373
|
-
}
|
|
766
|
+
this.plumber.revalidate(itemsToMove);
|
|
374
767
|
}
|
|
375
768
|
}
|
|
376
769
|
|
|
377
770
|
private handleMouseUp(event: MouseEvent): void {
|
|
771
|
+
// Handle selection box completion
|
|
772
|
+
if (this.canvasMouseDown && this.isSelecting) {
|
|
773
|
+
this.isSelecting = false;
|
|
774
|
+
this.selectionBox = null;
|
|
775
|
+
this.canvasMouseDown = false;
|
|
776
|
+
this.requestUpdate();
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Handle canvas click (clear selection)
|
|
781
|
+
if (this.canvasMouseDown && !this.isSelecting) {
|
|
782
|
+
this.canvasMouseDown = false;
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Handle item drag completion
|
|
378
787
|
if (!this.isMouseDown || !this.currentDragItem) return;
|
|
379
788
|
|
|
380
789
|
// If we were actually dragging, handle the drag end
|
|
381
790
|
if (this.isDragging) {
|
|
382
|
-
// Restore normal z-index for node connections
|
|
383
|
-
if (this.currentDragItem.type === 'node' && this.plumber) {
|
|
384
|
-
this.plumber.restoreNodeConnections(this.currentDragItem.uuid);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
791
|
const deltaX = event.clientX - this.dragStartPos.x;
|
|
388
792
|
const deltaY = event.clientY - this.dragStartPos.y;
|
|
389
793
|
|
|
390
|
-
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
794
|
+
// Determine what items were moved
|
|
795
|
+
const itemsToMove =
|
|
796
|
+
this.selectedItems.has(this.currentDragItem.uuid) &&
|
|
797
|
+
this.selectedItems.size > 1
|
|
798
|
+
? Array.from(this.selectedItems)
|
|
799
|
+
: [this.currentDragItem.uuid];
|
|
800
|
+
|
|
801
|
+
// Update positions for all moved items
|
|
802
|
+
const newPositions: { [uuid: string]: FlowPosition } = {};
|
|
803
|
+
|
|
804
|
+
itemsToMove.forEach((uuid) => {
|
|
805
|
+
const type = this.definition.nodes.find((node) => node.uuid === uuid)
|
|
806
|
+
? 'node'
|
|
807
|
+
: 'sticky';
|
|
808
|
+
const position = this.getPosition(uuid, type);
|
|
809
|
+
|
|
810
|
+
if (position) {
|
|
811
|
+
const newLeft = position.left + deltaX;
|
|
812
|
+
const newTop = position.top + deltaY;
|
|
813
|
+
|
|
814
|
+
// Snap to 20px grid for final position
|
|
815
|
+
const snappedLeft = snapToGrid(newLeft);
|
|
816
|
+
const snappedTop = snapToGrid(newTop);
|
|
817
|
+
|
|
818
|
+
const newPosition = { left: snappedLeft, top: snappedTop };
|
|
819
|
+
newPositions[uuid] = newPosition;
|
|
820
|
+
|
|
821
|
+
// Remove dragging class
|
|
822
|
+
const element = this.querySelector(`[uuid="${uuid}"]`) as HTMLElement;
|
|
823
|
+
if (element) {
|
|
824
|
+
element.classList.remove('dragging');
|
|
825
|
+
element.style.left = `${snappedLeft}px`;
|
|
826
|
+
element.style.top = `${snappedTop}px`;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
});
|
|
398
830
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
this.currentDragItem.uuid,
|
|
402
|
-
this.currentDragItem.type,
|
|
403
|
-
newPosition
|
|
404
|
-
);
|
|
831
|
+
if (Object.keys(newPositions).length > 0) {
|
|
832
|
+
getStore().getState().updateCanvasPositions(newPositions);
|
|
405
833
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
.getState()
|
|
410
|
-
.updateCanvasPositions({
|
|
411
|
-
[this.currentDragItem.uuid]: newPosition
|
|
412
|
-
});
|
|
834
|
+
setTimeout(() => {
|
|
835
|
+
this.plumber.repaintEverything();
|
|
836
|
+
}, 0);
|
|
413
837
|
}
|
|
414
838
|
|
|
415
|
-
|
|
416
|
-
if (this.currentDragItem.type === 'node' && this.plumber) {
|
|
417
|
-
this.plumber.repaintEverything();
|
|
418
|
-
}
|
|
839
|
+
this.selectedItems.clear();
|
|
419
840
|
}
|
|
420
841
|
|
|
421
842
|
// Reset all drag state
|
|
422
843
|
this.isDragging = false;
|
|
423
844
|
this.isMouseDown = false;
|
|
424
845
|
this.currentDragItem = null;
|
|
846
|
+
this.canvasMouseDown = false;
|
|
425
847
|
}
|
|
426
848
|
|
|
427
849
|
private updateCanvasSize(): void {
|
|
@@ -449,10 +871,25 @@ export class Editor extends RapidElement {
|
|
|
449
871
|
|
|
450
872
|
// Check sticky note positions
|
|
451
873
|
const stickies = this.definition._ui?.stickies || {};
|
|
452
|
-
Object.
|
|
874
|
+
Object.entries(stickies).forEach(([uuid, sticky]) => {
|
|
453
875
|
if (sticky.position) {
|
|
454
|
-
|
|
455
|
-
|
|
876
|
+
const stickyElement = this.querySelector(
|
|
877
|
+
`temba-sticky-note[uuid="${uuid}"]`
|
|
878
|
+
) as HTMLElement;
|
|
879
|
+
if (stickyElement) {
|
|
880
|
+
// Use clientWidth/clientHeight instead of getBoundingClientRect() to get element dimensions
|
|
881
|
+
// This avoids the coordinate system mismatch between viewport and canvas coordinates
|
|
882
|
+
const width = stickyElement.clientWidth;
|
|
883
|
+
const height = stickyElement.clientHeight;
|
|
884
|
+
|
|
885
|
+
// Both sticky.position and width/height are now in the same coordinate system
|
|
886
|
+
maxWidth = Math.max(maxWidth, sticky.position.left + width);
|
|
887
|
+
maxHeight = Math.max(maxHeight, sticky.position.top + height);
|
|
888
|
+
} else {
|
|
889
|
+
// Fallback to default sizes if element not found
|
|
890
|
+
maxWidth = Math.max(maxWidth, sticky.position.left + 200);
|
|
891
|
+
maxHeight = Math.max(maxHeight, sticky.position.top + 100);
|
|
892
|
+
}
|
|
456
893
|
}
|
|
457
894
|
});
|
|
458
895
|
|
|
@@ -460,6 +897,38 @@ export class Editor extends RapidElement {
|
|
|
460
897
|
store.getState().expandCanvas(maxWidth, maxHeight);
|
|
461
898
|
}
|
|
462
899
|
|
|
900
|
+
private handleCanvasDoubleClick(event: MouseEvent): void {
|
|
901
|
+
// Check if we double-clicked on empty canvas space
|
|
902
|
+
const target = event.target as HTMLElement;
|
|
903
|
+
if (target.id !== 'canvas') {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Get canvas position
|
|
908
|
+
const canvas = this.querySelector('#canvas');
|
|
909
|
+
if (!canvas) {
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
const canvasRect = canvas.getBoundingClientRect();
|
|
914
|
+
const relativeX = event.clientX - canvasRect.left - 10;
|
|
915
|
+
const relativeY = event.clientY - canvasRect.top - 10;
|
|
916
|
+
|
|
917
|
+
// Snap position to grid
|
|
918
|
+
const snappedLeft = snapToGrid(relativeX);
|
|
919
|
+
const snappedTop = snapToGrid(relativeY);
|
|
920
|
+
|
|
921
|
+
// Create new sticky note
|
|
922
|
+
const store = getStore();
|
|
923
|
+
store.getState().createStickyNote({
|
|
924
|
+
left: snappedLeft,
|
|
925
|
+
top: snappedTop
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
event.preventDefault();
|
|
929
|
+
event.stopPropagation();
|
|
930
|
+
}
|
|
931
|
+
|
|
463
932
|
public render(): TemplateResult {
|
|
464
933
|
// we have to embed our own style since we are in light DOM
|
|
465
934
|
const style = html`<style>
|
|
@@ -478,38 +947,59 @@ export class Editor extends RapidElement {
|
|
|
478
947
|
>
|
|
479
948
|
<div id="canvas">
|
|
480
949
|
${this.definition
|
|
481
|
-
?
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
950
|
+
? repeat(
|
|
951
|
+
this.definition.nodes,
|
|
952
|
+
(node) => node.uuid,
|
|
953
|
+
(node) => {
|
|
954
|
+
const position =
|
|
955
|
+
this.definition._ui.nodes[node.uuid].position;
|
|
956
|
+
|
|
957
|
+
const dragging =
|
|
958
|
+
this.isDragging &&
|
|
959
|
+
this.currentDragItem?.uuid === node.uuid;
|
|
960
|
+
|
|
961
|
+
const selected = this.selectedItems.has(node.uuid);
|
|
962
|
+
|
|
963
|
+
return html`<temba-flow-node
|
|
964
|
+
class="draggable ${dragging ? 'dragging' : ''} ${selected
|
|
965
|
+
? 'selected'
|
|
966
|
+
: ''}"
|
|
967
|
+
@mousedown=${this.handleMouseDown.bind(this)}
|
|
968
|
+
uuid=${node.uuid}
|
|
969
|
+
style="left:${position.left}px; top:${position.top}px"
|
|
970
|
+
.plumber=${this.plumber}
|
|
971
|
+
.node=${node}
|
|
972
|
+
.ui=${this.definition._ui.nodes[node.uuid]}
|
|
973
|
+
@temba-node-deleted=${(event) => {
|
|
974
|
+
this.deleteNodes([event.detail.uuid]);
|
|
975
|
+
}}
|
|
976
|
+
></temba-flow-node>`;
|
|
977
|
+
}
|
|
978
|
+
)
|
|
498
979
|
: html`<temba-loading></temba-loading>`}
|
|
499
|
-
${
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
980
|
+
${repeat(
|
|
981
|
+
Object.entries(stickies),
|
|
982
|
+
([uuid]) => uuid,
|
|
983
|
+
([uuid, sticky]) => {
|
|
984
|
+
const position = sticky.position || { left: 0, top: 0 };
|
|
985
|
+
const dragging =
|
|
986
|
+
this.isDragging && this.currentDragItem?.uuid === uuid;
|
|
987
|
+
const selected = this.selectedItems.has(uuid);
|
|
988
|
+
return html`<temba-sticky-note
|
|
989
|
+
class="draggable ${dragging ? 'dragging' : ''} ${selected
|
|
990
|
+
? 'selected'
|
|
991
|
+
: ''}"
|
|
992
|
+
@mousedown=${this.handleMouseDown.bind(this)}
|
|
993
|
+
style="left:${position.left}px; top:${position.top}px; z-index: ${1000 +
|
|
994
|
+
position.top}"
|
|
995
|
+
uuid=${uuid}
|
|
996
|
+
.data=${sticky}
|
|
997
|
+
.dragging=${dragging}
|
|
998
|
+
.selected=${selected}
|
|
999
|
+
></temba-sticky-note>`;
|
|
1000
|
+
}
|
|
1001
|
+
)}
|
|
1002
|
+
${this.renderSelectionBox()}
|
|
513
1003
|
</div>
|
|
514
1004
|
</div>
|
|
515
1005
|
</div>`;
|