@nyaruka/temba-components 0.137.0 → 0.138.4
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/.devcontainer/Dockerfile +0 -9
- package/.devcontainer/devcontainer.json +8 -3
- package/.github/workflows/build.yml +6 -1
- package/.github/workflows/cla.yml +1 -1
- package/.github/workflows/publish.yml +6 -1
- package/CHANGELOG.md +42 -0
- package/dist/locales/es.js +5 -5
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +5 -5
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/locale-codes.js +11 -2
- package/dist/locales/locale-codes.js.map +1 -1
- package/dist/locales/pt.js +5 -5
- package/dist/locales/pt.js.map +1 -1
- package/dist/temba-components.js +445 -278
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/FloatingTab.js +16 -8
- package/out-tsc/src/display/FloatingTab.js.map +1 -1
- package/out-tsc/src/flow/CanvasMenu.js +33 -15
- package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +49 -24
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +583 -70
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeTypeSelector.js +13 -11
- package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -1
- package/out-tsc/src/flow/Plumber.js +110 -64
- package/out-tsc/src/flow/Plumber.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_field.js +5 -1
- package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
- package/out-tsc/src/list/RunList.js +2 -1
- package/out-tsc/src/list/RunList.js.map +1 -1
- package/out-tsc/src/list/TicketList.js +2 -1
- package/out-tsc/src/list/TicketList.js.map +1 -1
- package/out-tsc/src/locales/es.js +5 -5
- package/out-tsc/src/locales/es.js.map +1 -1
- package/out-tsc/src/locales/fr.js +5 -5
- package/out-tsc/src/locales/fr.js.map +1 -1
- package/out-tsc/src/locales/locale-codes.js +11 -2
- package/out-tsc/src/locales/locale-codes.js.map +1 -1
- package/out-tsc/src/locales/pt.js +5 -5
- package/out-tsc/src/locales/pt.js.map +1 -1
- package/out-tsc/src/simulator/Simulator.js +11 -4
- package/out-tsc/src/simulator/Simulator.js.map +1 -1
- package/out-tsc/src/store/AppState.js +17 -2
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/test/temba-contact-fields.test.js +3 -3
- package/out-tsc/test/temba-contact-fields.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor-node.test.js +3 -1
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor-revisions.test.js +106 -0
- package/out-tsc/test/temba-flow-editor-revisions.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor.test.js +14 -10
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber-connections.test.js +7 -1
- package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber.test.js +6 -0
- package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
- package/out-tsc/test/temba-select.test.js +1 -0
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/floating-tab/gray.png +0 -0
- package/screenshots/truth/floating-tab/green.png +0 -0
- package/screenshots/truth/floating-tab/purple.png +0 -0
- package/screenshots/truth/node-type-selector/action-mode.png +0 -0
- package/screenshots/truth/node-type-selector/split-mode.png +0 -0
- package/src/display/FloatingTab.ts +18 -8
- package/src/flow/CanvasMenu.ts +38 -16
- package/src/flow/CanvasNode.ts +62 -29
- package/src/flow/Editor.ts +699 -74
- package/src/flow/NodeTypeSelector.ts +13 -11
- package/src/flow/Plumber.ts +123 -69
- package/src/flow/actions/set_contact_field.ts +5 -1
- package/src/list/RunList.ts +2 -1
- package/src/list/TicketList.ts +2 -1
- package/src/locales/es.ts +18 -13
- package/src/locales/fr.ts +18 -13
- package/src/locales/locale-codes.ts +11 -2
- package/src/locales/pt.ts +18 -13
- package/src/simulator/Simulator.ts +11 -5
- package/src/store/AppState.ts +18 -2
- package/test/temba-contact-fields.test.ts +8 -3
- package/test/temba-flow-editor-node.test.ts +3 -1
- package/test/temba-flow-editor-revisions.test.ts +134 -0
- package/test/temba-flow-editor.test.ts +16 -10
- package/test/temba-flow-plumber-connections.test.ts +7 -1
- package/test/temba-flow-plumber.test.ts +6 -0
- package/test/temba-select.test.ts +1 -0
|
@@ -10,6 +10,7 @@ export class FloatingTab extends RapidElement {
|
|
|
10
10
|
this.color = '#6B7280';
|
|
11
11
|
this.top = -1; // -1 means auto-calculate position
|
|
12
12
|
this.hidden = false;
|
|
13
|
+
this.autoPositioned = false;
|
|
13
14
|
}
|
|
14
15
|
static get styles() {
|
|
15
16
|
return css `
|
|
@@ -72,7 +73,13 @@ export class FloatingTab extends RapidElement {
|
|
|
72
73
|
connectedCallback() {
|
|
73
74
|
super.connectedCallback();
|
|
74
75
|
FloatingTab.allTabs.push(this);
|
|
75
|
-
|
|
76
|
+
}
|
|
77
|
+
firstUpdated() {
|
|
78
|
+
// only auto-calculate position if no top was provided (still at default -1)
|
|
79
|
+
if (this.top === -1) {
|
|
80
|
+
this.autoPositioned = true;
|
|
81
|
+
this.updatePosition();
|
|
82
|
+
}
|
|
76
83
|
}
|
|
77
84
|
disconnectedCallback() {
|
|
78
85
|
super.disconnectedCallback();
|
|
@@ -80,8 +87,12 @@ export class FloatingTab extends RapidElement {
|
|
|
80
87
|
if (index > -1) {
|
|
81
88
|
FloatingTab.allTabs.splice(index, 1);
|
|
82
89
|
}
|
|
83
|
-
// update positions of remaining tabs
|
|
84
|
-
FloatingTab.allTabs.forEach((tab) =>
|
|
90
|
+
// update positions of remaining tabs that use auto-positioning
|
|
91
|
+
FloatingTab.allTabs.forEach((tab) => {
|
|
92
|
+
if (tab.autoPositioned) {
|
|
93
|
+
tab.updatePosition();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
85
96
|
}
|
|
86
97
|
updatePosition() {
|
|
87
98
|
// auto-calculate position based on index
|
|
@@ -90,8 +101,8 @@ export class FloatingTab extends RapidElement {
|
|
|
90
101
|
this.top = 100; // default fallback
|
|
91
102
|
}
|
|
92
103
|
else {
|
|
93
|
-
// start at
|
|
94
|
-
this.top =
|
|
104
|
+
// start at 150px and stack with TAB_HEIGHT gap between tabs
|
|
105
|
+
this.top = 150 + index * (FloatingTab.TAB_HEIGHT + 0);
|
|
95
106
|
}
|
|
96
107
|
}
|
|
97
108
|
updated(changes) {
|
|
@@ -99,9 +110,6 @@ export class FloatingTab extends RapidElement {
|
|
|
99
110
|
if (changes.has('hidden')) {
|
|
100
111
|
this.classList.toggle('hidden', this.hidden);
|
|
101
112
|
}
|
|
102
|
-
if (changes.has('top')) {
|
|
103
|
-
this.updatePosition();
|
|
104
|
-
}
|
|
105
113
|
}
|
|
106
114
|
handleClick() {
|
|
107
115
|
// hide all tabs when one is clicked
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FloatingTab.js","sourceRoot":"","sources":["../../../src/display/FloatingTab.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAoC,MAAM,KAAK,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,MAAM,OAAO,WAAY,SAAQ,YAAY;IAA7C;;QAsEE,UAAK,GAAG,SAAS,CAAC;QAGlB,QAAG,GAAG,CAAC,CAAC,CAAC,CAAC,mCAAmC;QAG7C,WAAM,GAAG,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"FloatingTab.js","sourceRoot":"","sources":["../../../src/display/FloatingTab.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, PropertyValueMap, TemplateResult } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { RapidElement } from '../RapidElement';\nimport { CustomEventType } from '../interfaces';\nimport { getClasses } from '../utils';\n\nexport class FloatingTab extends RapidElement {\n static get styles() {\n return css`\n .tab.hidden {\n transform: translateX(100%);\n }\n .tab {\n position: fixed;\n right: 0;\n z-index: 4998;\n transition: transform var(--transition-duration, 300ms) ease-in-out;\n display: flex;\n align-items: center;\n padding: 12px;\n border-top-left-radius: 8px;\n border-bottom-left-radius: 8px;\n cursor: pointer;\n box-shadow: -2px 2px 8px rgba(0, 0, 0, 0.2);\n transition: all calc(var(--transition-duration, 300ms) * 0.7)\n ease-in-out;\n user-select: none;\n }\n\n .tab:hover {\n padding-right: 16px;\n box-shadow: -4px 4px 12px rgba(0, 0, 0, 0.3);\n }\n\n .icon-container {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n }\n\n temba-icon {\n --icon-color: white;\n }\n\n .label {\n color: white;\n font-weight: 500;\n font-size: 16px;\n max-width: 0;\n overflow: hidden;\n white-space: nowrap;\n margin-left: 0;\n opacity: 0;\n transition: all var(--transition-duration, 300ms) ease-in-out;\n }\n\n .tab:hover .label {\n max-width: 200px;\n margin-left: 12px;\n opacity: 1;\n }\n `;\n }\n\n static TAB_HEIGHT = 50; // height of tab for auto-stacking\n static allTabs: FloatingTab[] = [];\n\n @property({ type: String })\n icon: string;\n\n @property({ type: String })\n label: string;\n\n @property({ type: String })\n color = '#6B7280';\n\n @property({ type: Number })\n top = -1; // -1 means auto-calculate position\n\n @property({ type: Boolean })\n hidden = false;\n\n private autoPositioned = false;\n\n connectedCallback() {\n super.connectedCallback();\n FloatingTab.allTabs.push(this);\n }\n\n protected firstUpdated(): void {\n // only auto-calculate position if no top was provided (still at default -1)\n if (this.top === -1) {\n this.autoPositioned = true;\n this.updatePosition();\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n const index = FloatingTab.allTabs.indexOf(this);\n if (index > -1) {\n FloatingTab.allTabs.splice(index, 1);\n }\n // update positions of remaining tabs that use auto-positioning\n FloatingTab.allTabs.forEach((tab) => {\n if (tab.autoPositioned) {\n tab.updatePosition();\n }\n });\n }\n\n private updatePosition() {\n // auto-calculate position based on index\n const index = FloatingTab.allTabs.indexOf(this);\n if (index === -1) {\n this.top = 100; // default fallback\n } else {\n // start at 150px and stack with TAB_HEIGHT gap between tabs\n this.top = 150 + index * (FloatingTab.TAB_HEIGHT + 0);\n }\n }\n\n public updated(\n changes: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.updated(changes);\n if (changes.has('hidden')) {\n this.classList.toggle('hidden', this.hidden);\n }\n }\n\n private handleClick() {\n // hide all tabs when one is clicked\n FloatingTab.allTabs.forEach((tab) => {\n tab.hidden = true;\n });\n\n this.fireCustomEvent(CustomEventType.ButtonClicked, {\n action: 'toggle'\n });\n }\n\n public static showAllTabs() {\n FloatingTab.allTabs.forEach((tab) => {\n tab.hidden = false;\n });\n }\n\n public static hideAllTabs() {\n FloatingTab.allTabs.forEach((tab) => {\n tab.hidden = true;\n });\n }\n\n public render(): TemplateResult {\n const tabStyle = `\n background-color: ${this.color};\n top: ${this.top}px;\n `;\n\n const classes = getClasses({\n tab: true,\n hidden: this.hidden\n });\n\n return html`\n <div class=\"${classes}\" style=\"${tabStyle}\" @click=${this.handleClick}>\n <div class=\"icon-container\">\n ${this.icon\n ? html`<temba-icon size=\"1.5\" name=\"${this.icon}\"></temba-icon>`\n : ''}\n </div>\n <div class=\"label\">${this.label}</div>\n </div>\n `;\n }\n}\n"]}
|
|
@@ -13,6 +13,7 @@ export class CanvasMenu extends RapidElement {
|
|
|
13
13
|
this.x = 0;
|
|
14
14
|
this.y = 0;
|
|
15
15
|
this.open = false;
|
|
16
|
+
this.showStickyNote = true;
|
|
16
17
|
this.clickPosition = { x: 0, y: 0 };
|
|
17
18
|
}
|
|
18
19
|
static get styles() {
|
|
@@ -94,10 +95,11 @@ export class CanvasMenu extends RapidElement {
|
|
|
94
95
|
document.removeEventListener('click', this._clickOutsideHandler);
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
|
-
show(x, y, clickPosition) {
|
|
98
|
+
show(x, y, clickPosition, showStickyNote = true) {
|
|
98
99
|
this.x = x;
|
|
99
100
|
this.y = y;
|
|
100
101
|
this.clickPosition = clickPosition;
|
|
102
|
+
this.showStickyNote = showStickyNote;
|
|
101
103
|
this.open = true;
|
|
102
104
|
// Adjust position after menu renders to ensure it fits on screen
|
|
103
105
|
requestAnimationFrame(() => {
|
|
@@ -129,15 +131,22 @@ export class CanvasMenu extends RapidElement {
|
|
|
129
131
|
this.y = adjustedY;
|
|
130
132
|
}
|
|
131
133
|
}
|
|
132
|
-
close() {
|
|
133
|
-
this.open
|
|
134
|
+
close(fireCanceledEvent = true) {
|
|
135
|
+
if (this.open) {
|
|
136
|
+
this.open = false;
|
|
137
|
+
// Fire close event so parent can clean up, but only if not from a selection
|
|
138
|
+
if (fireCanceledEvent) {
|
|
139
|
+
this.fireCustomEvent(CustomEventType.Canceled, {});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
134
142
|
}
|
|
135
143
|
handleMenuItemClick(action) {
|
|
136
144
|
this.fireCustomEvent(CustomEventType.Selection, {
|
|
137
145
|
action,
|
|
138
146
|
position: this.clickPosition
|
|
139
147
|
});
|
|
140
|
-
|
|
148
|
+
// Close without firing canceled event since we made a selection
|
|
149
|
+
this.close(false);
|
|
141
150
|
}
|
|
142
151
|
render() {
|
|
143
152
|
if (!this.open) {
|
|
@@ -169,18 +178,24 @@ export class CanvasMenu extends RapidElement {
|
|
|
169
178
|
</div>
|
|
170
179
|
</div>
|
|
171
180
|
|
|
172
|
-
|
|
181
|
+
${this.showStickyNote
|
|
182
|
+
? html `
|
|
183
|
+
<div class="divider"></div>
|
|
173
184
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
185
|
+
<div
|
|
186
|
+
class="menu-item"
|
|
187
|
+
@click=${() => this.handleMenuItemClick('sticky')}
|
|
188
|
+
>
|
|
189
|
+
<temba-icon name="note" size="1.25"></temba-icon>
|
|
190
|
+
<div class="menu-item-content">
|
|
191
|
+
<div class="menu-item-title">Add Sticky Note</div>
|
|
192
|
+
<div class="menu-item-description">
|
|
193
|
+
Add a note to the canvas
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
`
|
|
198
|
+
: ''}
|
|
184
199
|
</div>
|
|
185
200
|
`;
|
|
186
201
|
}
|
|
@@ -194,6 +209,9 @@ __decorate([
|
|
|
194
209
|
__decorate([
|
|
195
210
|
property({ type: Boolean })
|
|
196
211
|
], CanvasMenu.prototype, "open", void 0);
|
|
212
|
+
__decorate([
|
|
213
|
+
property({ type: Boolean })
|
|
214
|
+
], CanvasMenu.prototype, "showStickyNote", void 0);
|
|
197
215
|
__decorate([
|
|
198
216
|
state()
|
|
199
217
|
], CanvasMenu.prototype, "clickPosition", void 0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CanvasMenu.js","sourceRoot":"","sources":["../../../src/flow/CanvasMenu.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, PropertyValueMap, TemplateResult } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { RapidElement } from '../RapidElement';\nimport { CustomEventType } from '../interfaces';\n\n/**\n * Event detail for canvas menu selection\n */\nexport interface CanvasMenuSelection {\n action: 'sticky' | 'action' | 'split';\n position: { x: number; y: number };\n}\n\n/**\n * CanvasMenu - A popup menu for adding items to the flow canvas\n * Displayed when double-clicking on empty canvas space\n */\nexport class CanvasMenu extends RapidElement {\n static get styles() {\n return css`\n :host {\n position: fixed;\n z-index: 10000;\n display: block;\n pointer-events: none;\n }\n\n .menu {\n position: fixed;\n background: white;\n border-radius: var(--curvature);\n box-shadow: var(--dropdown-shadow);\n padding: 0.5em 0;\n width: 265px;\n pointer-events: auto;\n }\n\n .menu-item {\n padding: 0.75em 1.5em;\n cursor: pointer;\n display: flex;\n align-items: flex-start;\n gap: 0.75em;\n transition: background-color 0.15s ease;\n }\n\n .menu-item:hover {\n background-color: rgba(0, 0, 0, 0.05);\n }\n\n .menu-item temba-icon {\n --icon-color: var(--color-text);\n margin-top: 0.15em;\n }\n\n .menu-item-content {\n display: flex;\n flex-direction: column;\n gap: 0.15em;\n }\n\n .menu-item-title {\n font-weight: 500;\n font-size: 1rem;\n color: var(--color-text-dark);\n }\n\n .menu-item-description {\n font-size: 0.85rem;\n color: var(--color-text);\n }\n\n .divider {\n height: 1px;\n background: rgba(0, 0, 0, 0.1);\n margin: 0.5em 0;\n }\n `;\n }\n\n @property({ type: Number })\n public x = 0;\n\n @property({ type: Number })\n public y = 0;\n\n @property({ type: Boolean })\n public open = false;\n\n @state()\n private clickPosition = { x: 0, y: 0 };\n\n protected firstUpdated(\n _changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.firstUpdated(_changedProperties);\n\n // Close menu when clicking outside\n const handleClickOutside = (e: MouseEvent) => {\n if (this.open && !this.contains(e.target as Node)) {\n this.close();\n }\n };\n\n document.addEventListener('click', handleClickOutside);\n\n // Store cleanup function\n (this as any)._clickOutsideHandler = handleClickOutside;\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n if ((this as any)._clickOutsideHandler) {\n document.removeEventListener('click', (this as any)._clickOutsideHandler);\n }\n }\n\n public show(x: number, y: number, clickPosition: { x: number; y: number }) {\n this.x = x;\n this.y = y;\n this.clickPosition = clickPosition;\n this.open = true;\n\n // Adjust position after menu renders to ensure it fits on screen\n requestAnimationFrame(() => {\n this.adjustPosition();\n });\n }\n\n private adjustPosition(): void {\n const menuElement = this.shadowRoot?.querySelector('.menu') as HTMLElement;\n if (!menuElement) return;\n\n const menuRect = menuElement.getBoundingClientRect();\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n const margin = 10; // margin from viewport edges\n\n let adjustedX = this.x;\n let adjustedY = this.y;\n\n // Check if menu goes off the right edge\n if (this.x + menuRect.width + margin > viewportWidth) {\n adjustedX = viewportWidth - menuRect.width - margin;\n }\n\n // Check if menu goes off the bottom edge\n if (this.y + menuRect.height + margin > viewportHeight) {\n adjustedY = viewportHeight - menuRect.height - margin;\n }\n\n // Update position if needed\n if (adjustedX !== this.x || adjustedY !== this.y) {\n this.x = adjustedX;\n this.y = adjustedY;\n }\n }\n\n public close() {\n this.open = false;\n }\n\n private handleMenuItemClick(action: 'sticky' | 'action' | 'split') {\n this.fireCustomEvent(CustomEventType.Selection, {\n action,\n position: this.clickPosition\n } as CanvasMenuSelection);\n this.close();\n }\n\n public render(): TemplateResult {\n if (!this.open) {\n return html``;\n }\n\n return html`\n <div class=\"menu\" style=\"left: ${this.x}px; top: ${this.y}px;\">\n <div\n class=\"menu-item\"\n @click=${() => this.handleMenuItemClick('action')}\n >\n <temba-icon name=\"action\" size=\"1.25\"></temba-icon>\n <div class=\"menu-item-content\">\n <div class=\"menu-item-title\">Add Action</div>\n <div class=\"menu-item-description\">\n Send messages, update contacts\n </div>\n </div>\n </div>\n\n <div\n class=\"menu-item\"\n @click=${() => this.handleMenuItemClick('split')}\n >\n <temba-icon name=\"split\" size=\"1.25\"></temba-icon>\n <div class=\"menu-item-content\">\n <div class=\"menu-item-title\">Add Split</div>\n <div class=\"menu-item-description\">Branch based on conditions</div>\n </div>\n </div>\n\n <div class=\"divider\"></div>\n\n <div\n class=\"menu-item\"\n @click=${() => this.handleMenuItemClick('sticky')}\n >\n <temba-icon name=\"note\" size=\"1.25\"></temba-icon>\n <div class=\"menu-item-content\">\n <div class=\"menu-item-title\">Add Sticky Note</div>\n <div class=\"menu-item-description\">Add a note to the canvas</div>\n </div>\n </div>\n </div>\n `;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"CanvasMenu.js","sourceRoot":"","sources":["../../../src/flow/CanvasMenu.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, PropertyValueMap, TemplateResult } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { RapidElement } from '../RapidElement';\nimport { CustomEventType } from '../interfaces';\n\n/**\n * Event detail for canvas menu selection\n */\nexport interface CanvasMenuSelection {\n action: 'sticky' | 'action' | 'split';\n position: { x: number; y: number };\n}\n\n/**\n * CanvasMenu - A popup menu for adding items to the flow canvas\n * Displayed when double-clicking on empty canvas space\n */\nexport class CanvasMenu extends RapidElement {\n static get styles() {\n return css`\n :host {\n position: fixed;\n z-index: 10000;\n display: block;\n pointer-events: none;\n }\n\n .menu {\n position: fixed;\n background: white;\n border-radius: var(--curvature);\n box-shadow: var(--dropdown-shadow);\n padding: 0.5em 0;\n width: 265px;\n pointer-events: auto;\n }\n\n .menu-item {\n padding: 0.75em 1.5em;\n cursor: pointer;\n display: flex;\n align-items: flex-start;\n gap: 0.75em;\n transition: background-color 0.15s ease;\n }\n\n .menu-item:hover {\n background-color: rgba(0, 0, 0, 0.05);\n }\n\n .menu-item temba-icon {\n --icon-color: var(--color-text);\n margin-top: 0.15em;\n }\n\n .menu-item-content {\n display: flex;\n flex-direction: column;\n gap: 0.15em;\n }\n\n .menu-item-title {\n font-weight: 500;\n font-size: 1rem;\n color: var(--color-text-dark);\n }\n\n .menu-item-description {\n font-size: 0.85rem;\n color: var(--color-text);\n }\n\n .divider {\n height: 1px;\n background: rgba(0, 0, 0, 0.1);\n margin: 0.5em 0;\n }\n `;\n }\n\n @property({ type: Number })\n public x = 0;\n\n @property({ type: Number })\n public y = 0;\n\n @property({ type: Boolean })\n public open = false;\n\n @property({ type: Boolean })\n public showStickyNote = true;\n\n @state()\n private clickPosition = { x: 0, y: 0 };\n\n protected firstUpdated(\n _changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.firstUpdated(_changedProperties);\n\n // Close menu when clicking outside\n const handleClickOutside = (e: MouseEvent) => {\n if (this.open && !this.contains(e.target as Node)) {\n this.close();\n }\n };\n\n document.addEventListener('click', handleClickOutside);\n\n // Store cleanup function\n (this as any)._clickOutsideHandler = handleClickOutside;\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n if ((this as any)._clickOutsideHandler) {\n document.removeEventListener('click', (this as any)._clickOutsideHandler);\n }\n }\n\n public show(\n x: number,\n y: number,\n clickPosition: { x: number; y: number },\n showStickyNote: boolean = true\n ) {\n this.x = x;\n this.y = y;\n this.clickPosition = clickPosition;\n this.showStickyNote = showStickyNote;\n this.open = true;\n\n // Adjust position after menu renders to ensure it fits on screen\n requestAnimationFrame(() => {\n this.adjustPosition();\n });\n }\n\n private adjustPosition(): void {\n const menuElement = this.shadowRoot?.querySelector('.menu') as HTMLElement;\n if (!menuElement) return;\n\n const menuRect = menuElement.getBoundingClientRect();\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n const margin = 10; // margin from viewport edges\n\n let adjustedX = this.x;\n let adjustedY = this.y;\n\n // Check if menu goes off the right edge\n if (this.x + menuRect.width + margin > viewportWidth) {\n adjustedX = viewportWidth - menuRect.width - margin;\n }\n\n // Check if menu goes off the bottom edge\n if (this.y + menuRect.height + margin > viewportHeight) {\n adjustedY = viewportHeight - menuRect.height - margin;\n }\n\n // Update position if needed\n if (adjustedX !== this.x || adjustedY !== this.y) {\n this.x = adjustedX;\n this.y = adjustedY;\n }\n }\n\n public close(fireCanceledEvent: boolean = true) {\n if (this.open) {\n this.open = false;\n // Fire close event so parent can clean up, but only if not from a selection\n if (fireCanceledEvent) {\n this.fireCustomEvent(CustomEventType.Canceled, {});\n }\n }\n }\n\n private handleMenuItemClick(action: 'sticky' | 'action' | 'split') {\n this.fireCustomEvent(CustomEventType.Selection, {\n action,\n position: this.clickPosition\n } as CanvasMenuSelection);\n // Close without firing canceled event since we made a selection\n this.close(false);\n }\n\n public render(): TemplateResult {\n if (!this.open) {\n return html``;\n }\n\n return html`\n <div class=\"menu\" style=\"left: ${this.x}px; top: ${this.y}px;\">\n <div\n class=\"menu-item\"\n @click=${() => this.handleMenuItemClick('action')}\n >\n <temba-icon name=\"action\" size=\"1.25\"></temba-icon>\n <div class=\"menu-item-content\">\n <div class=\"menu-item-title\">Add Action</div>\n <div class=\"menu-item-description\">\n Send messages, update contacts\n </div>\n </div>\n </div>\n\n <div\n class=\"menu-item\"\n @click=${() => this.handleMenuItemClick('split')}\n >\n <temba-icon name=\"split\" size=\"1.25\"></temba-icon>\n <div class=\"menu-item-content\">\n <div class=\"menu-item-title\">Add Split</div>\n <div class=\"menu-item-description\">Branch based on conditions</div>\n </div>\n </div>\n\n ${this.showStickyNote\n ? html`\n <div class=\"divider\"></div>\n\n <div\n class=\"menu-item\"\n @click=${() => this.handleMenuItemClick('sticky')}\n >\n <temba-icon name=\"note\" size=\"1.25\"></temba-icon>\n <div class=\"menu-item-content\">\n <div class=\"menu-item-title\">Add Sticky Note</div>\n <div class=\"menu-item-description\">\n Add a note to the canvas\n </div>\n </div>\n </div>\n `\n : ''}\n </div>\n `;\n }\n}\n"]}
|
|
@@ -22,11 +22,8 @@ export class CanvasNode extends RapidElement {
|
|
|
22
22
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
|
|
23
23
|
min-width: 200px;
|
|
24
24
|
border-radius: var(--curvature);
|
|
25
|
-
|
|
26
25
|
color: #333;
|
|
27
|
-
cursor: move;
|
|
28
26
|
user-select: none;
|
|
29
|
-
|
|
30
27
|
}
|
|
31
28
|
|
|
32
29
|
/* Flow start indicator */
|
|
@@ -109,7 +106,7 @@ export class CanvasNode extends RapidElement {
|
|
|
109
106
|
opacity: 1;
|
|
110
107
|
}
|
|
111
108
|
|
|
112
|
-
.
|
|
109
|
+
.read-only-hidden {
|
|
113
110
|
visibility: hidden !important;
|
|
114
111
|
pointer-events: none !important;
|
|
115
112
|
}
|
|
@@ -304,6 +301,12 @@ export class CanvasNode extends RapidElement {
|
|
|
304
301
|
.exit.connected:hover {
|
|
305
302
|
background-color: var(--color-connectors, #e6e6e6);
|
|
306
303
|
}
|
|
304
|
+
|
|
305
|
+
.exit.connected.read-only, .exit.connected.read-only:hover {
|
|
306
|
+
background-color: #fff;
|
|
307
|
+
pointer-events: none !important;
|
|
308
|
+
cursor: default;
|
|
309
|
+
}
|
|
307
310
|
|
|
308
311
|
.exit.removing, .exit.removing:hover {
|
|
309
312
|
background-color: var(--color-error);
|
|
@@ -405,6 +408,7 @@ export class CanvasNode extends RapidElement {
|
|
|
405
408
|
super();
|
|
406
409
|
// Track exits that are in "removing" state
|
|
407
410
|
this.exitRemovalTimeouts = new Map();
|
|
411
|
+
this.connectionTimeout = null;
|
|
408
412
|
// Set of exit UUIDs that are in the removing state
|
|
409
413
|
this.exitRemovingState = new Set();
|
|
410
414
|
// Track actions that are in "removing" state
|
|
@@ -460,19 +464,25 @@ export class CanvasNode extends RapidElement {
|
|
|
460
464
|
if (changes.has('node')) {
|
|
461
465
|
// Only proceed if plumber is available (for tests that don't set it up)
|
|
462
466
|
if (this.plumber) {
|
|
463
|
-
|
|
467
|
+
if (this.connectionTimeout) {
|
|
468
|
+
clearTimeout(this.connectionTimeout);
|
|
469
|
+
}
|
|
470
|
+
// Pass exit IDs explicitly to avoid DOM querying dependency
|
|
471
|
+
const exitIds = this.node.exits.map((e) => e.uuid);
|
|
472
|
+
this.plumber.removeNodeConnections(this.node.uuid, exitIds);
|
|
464
473
|
// make our initial connections
|
|
465
|
-
for
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
// so make our source endpoint
|
|
474
|
+
// We use setTimeout to allow for DOM updates to complete before querying for exits
|
|
475
|
+
this.connectionTimeout = window.setTimeout(() => {
|
|
476
|
+
for (const exit of this.node.exits) {
|
|
469
477
|
this.plumber.makeSource(exit.uuid);
|
|
478
|
+
if (exit.destination_uuid) {
|
|
479
|
+
this.plumber.connectIds(this.node.uuid, exit.uuid, exit.destination_uuid);
|
|
480
|
+
}
|
|
470
481
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
this.plumber.revalidate([this.node.uuid]);
|
|
482
|
+
// Note: revalidation is handled by plumber's processPendingConnections which calls repaintEverything
|
|
483
|
+
this.connectionTimeout = null;
|
|
484
|
+
this.plumber.revalidate([this.node.uuid]);
|
|
485
|
+
}, 0);
|
|
476
486
|
}
|
|
477
487
|
const ele = this.parentElement;
|
|
478
488
|
if (ele) {
|
|
@@ -482,6 +492,14 @@ export class CanvasNode extends RapidElement {
|
|
|
482
492
|
}
|
|
483
493
|
}
|
|
484
494
|
disconnectedCallback() {
|
|
495
|
+
// Force cleanup of connections for this node
|
|
496
|
+
if (this.plumber && this.node) {
|
|
497
|
+
if (this.connectionTimeout) {
|
|
498
|
+
clearTimeout(this.connectionTimeout);
|
|
499
|
+
this.connectionTimeout = null;
|
|
500
|
+
}
|
|
501
|
+
this.plumber.forgetNode(this.node.uuid);
|
|
502
|
+
}
|
|
485
503
|
// Remove the event listener when the component is removed
|
|
486
504
|
super.disconnectedCallback();
|
|
487
505
|
// Remove external drag event listeners
|
|
@@ -503,6 +521,10 @@ export class CanvasNode extends RapidElement {
|
|
|
503
521
|
// Clear the removing state
|
|
504
522
|
this.exitRemovingState.clear();
|
|
505
523
|
this.actionRemovingState.clear();
|
|
524
|
+
// only proceed if plumber is available (for tests that don't set it up)
|
|
525
|
+
if (this.plumber) {
|
|
526
|
+
this.plumber.removeNodeConnections(this.node.uuid, this.node.exits.map((e) => e.uuid));
|
|
527
|
+
}
|
|
506
528
|
}
|
|
507
529
|
handleExitClick(event, exit) {
|
|
508
530
|
event.preventDefault();
|
|
@@ -1047,23 +1069,19 @@ export class CanvasNode extends RapidElement {
|
|
|
1047
1069
|
return html `<div class="cn-title" style="background:${color}">
|
|
1048
1070
|
${((_c = this.ui) === null || _c === void 0 ? void 0 : _c.type) === 'execute_actions'
|
|
1049
1071
|
? html `<temba-icon
|
|
1050
|
-
class="drag-handle ${this.
|
|
1051
|
-
? 'translating-hidden'
|
|
1052
|
-
: ''}"
|
|
1072
|
+
class="drag-handle ${this.isReadOnly() ? 'read-only-hidden' : ''}"
|
|
1053
1073
|
name="sort"
|
|
1054
1074
|
></temba-icon>`
|
|
1055
1075
|
: ((_e = (_d = this.node) === null || _d === void 0 ? void 0 : _d.actions) === null || _e === void 0 ? void 0 : _e.length) > 1
|
|
1056
1076
|
? html `<temba-icon
|
|
1057
|
-
class="drag-handle ${this.
|
|
1058
|
-
? 'translating-hidden'
|
|
1059
|
-
: ''}"
|
|
1077
|
+
class="drag-handle ${this.isReadOnly() ? 'read-only-hidden' : ''}"
|
|
1060
1078
|
name="sort"
|
|
1061
1079
|
></temba-icon>`
|
|
1062
1080
|
: html `<div class="title-spacer"></div>`}
|
|
1063
1081
|
|
|
1064
1082
|
<div class="name">${isRemoving ? 'Remove?' : config.name}</div>
|
|
1065
1083
|
<div
|
|
1066
|
-
class="remove-button ${this.
|
|
1084
|
+
class="remove-button ${this.isReadOnly() ? 'read-only-hidden' : ''}"
|
|
1067
1085
|
@click=${(e) => this.handleActionRemoveClick(e, action, index)}
|
|
1068
1086
|
title="Remove action"
|
|
1069
1087
|
>
|
|
@@ -1091,7 +1109,7 @@ export class CanvasNode extends RapidElement {
|
|
|
1091
1109
|
: html `${config.name}`}
|
|
1092
1110
|
</div>
|
|
1093
1111
|
<div
|
|
1094
|
-
class="remove-button ${this.
|
|
1112
|
+
class="remove-button ${this.isReadOnly() ? 'read-only-hidden' : ''}"
|
|
1095
1113
|
@click=${(e) => this.handleNodeRemoveClick(e)}
|
|
1096
1114
|
title="Remove node"
|
|
1097
1115
|
>
|
|
@@ -1292,12 +1310,16 @@ export class CanvasNode extends RapidElement {
|
|
|
1292
1310
|
class=${getClasses({
|
|
1293
1311
|
exit: true,
|
|
1294
1312
|
connected: !!exit.destination_uuid,
|
|
1295
|
-
removing: this.exitRemovingState.has(exit.uuid)
|
|
1313
|
+
removing: this.exitRemovingState.has(exit.uuid),
|
|
1314
|
+
'read-only': this.isReadOnly()
|
|
1296
1315
|
})}
|
|
1297
1316
|
@click=${(e) => this.handleExitClick(e, exit)}
|
|
1298
1317
|
></div>
|
|
1299
1318
|
</div>`;
|
|
1300
1319
|
}
|
|
1320
|
+
isReadOnly() {
|
|
1321
|
+
return this.viewingRevision || this.isTranslating;
|
|
1322
|
+
}
|
|
1301
1323
|
render() {
|
|
1302
1324
|
var _b;
|
|
1303
1325
|
if (!this.node || !this.ui) {
|
|
@@ -1370,7 +1392,7 @@ export class CanvasNode extends RapidElement {
|
|
|
1370
1392
|
: html `<div class="action-exits">
|
|
1371
1393
|
${repeat(this.node.exits, (exit) => exit.uuid, (exit) => this.renderExit(exit))}
|
|
1372
1394
|
</div>`}
|
|
1373
|
-
${this.ui.type === 'execute_actions' && !this.
|
|
1395
|
+
${this.ui.type === 'execute_actions' && !this.isReadOnly()
|
|
1374
1396
|
? html `<div
|
|
1375
1397
|
class="add-action-button"
|
|
1376
1398
|
@click=${(e) => this.handleAddActionClick(e)}
|
|
@@ -1398,6 +1420,9 @@ __decorate([
|
|
|
1398
1420
|
__decorate([
|
|
1399
1421
|
fromStore(zustand, (state) => state.languageCode)
|
|
1400
1422
|
], CanvasNode.prototype, "languageCode", void 0);
|
|
1423
|
+
__decorate([
|
|
1424
|
+
fromStore(zustand, (state) => state.viewingRevision)
|
|
1425
|
+
], CanvasNode.prototype, "viewingRevision", void 0);
|
|
1401
1426
|
__decorate([
|
|
1402
1427
|
fromStore(zustand, (state) => state.flowDefinition)
|
|
1403
1428
|
], CanvasNode.prototype, "flowDefinition", void 0);
|