@nyaruka/temba-components 0.126.0 → 0.128.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/demo/chart/example.html +18 -1
- package/demo/data/flows/sample-flow.json +127 -100
- package/demo/data/server/opened-tickets-long.json +53 -0
- package/demo/sticky-note-demo.html +152 -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 +346 -86
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/chart/TembaChart.js +44 -5
- package/out-tsc/src/chart/TembaChart.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +210 -1
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/EditorNode.js +98 -142
- package/out-tsc/src/flow/EditorNode.js.map +1 -1
- package/out-tsc/src/flow/StickyNote.js +272 -0
- package/out-tsc/src/flow/StickyNote.js.map +1 -0
- package/out-tsc/src/list/RunList.js +2 -1
- package/out-tsc/src/list/RunList.js.map +1 -1
- package/out-tsc/src/list/SortableList.js +9 -0
- package/out-tsc/src/list/SortableList.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/store/AppState.js +33 -0
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/src/vectoricon/index.js +2 -1
- package/out-tsc/src/vectoricon/index.js.map +1 -1
- package/out-tsc/temba-modules.js +2 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/temba-flow-editor-node.test.js +249 -5
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-select.test.js +9 -14
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +62 -0
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/sticky-note/blue.png +0 -0
- package/screenshots/truth/sticky-note/gray.png +0 -0
- package/screenshots/truth/sticky-note/green.png +0 -0
- package/screenshots/truth/sticky-note/pink.png +0 -0
- package/screenshots/truth/sticky-note/yellow.png +0 -0
- package/src/chart/TembaChart.ts +47 -5
- package/src/flow/Editor.ts +252 -2
- package/src/flow/EditorNode.ts +98 -160
- package/src/flow/StickyNote.ts +284 -0
- package/src/list/RunList.ts +2 -1
- package/src/list/SortableList.ts +11 -0
- 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/store/AppState.ts +51 -1
- package/src/store/flow-definition.d.ts +8 -0
- package/src/vectoricon/index.ts +2 -1
- package/static/svg/index.pdf +137 -0
- package/temba-modules.ts +2 -0
- package/test/temba-flow-editor-node.test.ts +322 -6
- package/test/temba-select.test.ts +10 -17
- package/test/utils.test.ts +98 -0
- package/web-dev-server.config.mjs +30 -22
package/src/flow/EditorNode.ts
CHANGED
|
@@ -21,26 +21,10 @@ export class EditorNode extends RapidElement {
|
|
|
21
21
|
@property({ type: Object })
|
|
22
22
|
private ui: NodeUI;
|
|
23
23
|
|
|
24
|
-
// Drag state properties
|
|
25
|
-
private isDragging = false;
|
|
26
|
-
private dragStartPos = { x: 0, y: 0 };
|
|
27
|
-
private nodeStartPos = { left: 0, top: 0 };
|
|
28
|
-
|
|
29
|
-
// Bound event handlers to maintain proper 'this' context
|
|
30
|
-
private boundMouseMove = this.handleMouseMove.bind(this);
|
|
31
|
-
private boundMouseUp = this.handleMouseUp.bind(this);
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Snaps a coordinate value to the nearest 20px grid position
|
|
35
|
-
*/
|
|
36
|
-
private snapToGrid(value: number): number {
|
|
37
|
-
return Math.round(value / 20) * 20;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
24
|
static get styles() {
|
|
41
25
|
return css`
|
|
26
|
+
|
|
42
27
|
.node {
|
|
43
|
-
position: absolute;
|
|
44
28
|
background-color: #fff;
|
|
45
29
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
|
|
46
30
|
min-width: 200px;
|
|
@@ -49,7 +33,7 @@ export class EditorNode extends RapidElement {
|
|
|
49
33
|
color: #333;
|
|
50
34
|
cursor: move;
|
|
51
35
|
user-select: none;
|
|
52
|
-
|
|
36
|
+
|
|
53
37
|
}
|
|
54
38
|
|
|
55
39
|
.node:hover {
|
|
@@ -64,21 +48,59 @@ export class EditorNode extends RapidElement {
|
|
|
64
48
|
|
|
65
49
|
.action {
|
|
66
50
|
max-width: 200px;
|
|
51
|
+
position: relative;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.action.sortable {
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: stretch;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.action .action-content {
|
|
60
|
+
flex-grow: 1;
|
|
61
|
+
display: flex;
|
|
62
|
+
flex-direction: column;
|
|
67
63
|
}
|
|
68
64
|
|
|
69
65
|
.action .body {
|
|
70
66
|
padding: 1em;
|
|
71
67
|
}
|
|
72
68
|
|
|
69
|
+
.action .drag-handle {
|
|
70
|
+
opacity: 0;
|
|
71
|
+
transition: all 200ms ease-in-out;
|
|
72
|
+
cursor: move;
|
|
73
|
+
background: rgba(0, 0, 0, 0.02);
|
|
74
|
+
max-width:0px;
|
|
75
|
+
position: absolute;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.action:hover .drag-handle {
|
|
79
|
+
opacity: 0.5;
|
|
80
|
+
padding: 0.25em;
|
|
81
|
+
max-width: 20px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.action .drag-handle:hover {
|
|
85
|
+
opacity: 1;
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
.action .title,
|
|
74
90
|
.router .title {
|
|
91
|
+
display: flex;
|
|
75
92
|
color: #fff;
|
|
76
93
|
padding: 5px 1px;
|
|
77
94
|
text-align: center;
|
|
78
95
|
font-size: 1em;
|
|
79
96
|
font-weight: normal;
|
|
97
|
+
|
|
80
98
|
}
|
|
81
99
|
|
|
100
|
+
.title .name {
|
|
101
|
+
flex-grow: 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
82
104
|
.quick-replies {
|
|
83
105
|
margin-top: 0.5em;
|
|
84
106
|
}
|
|
@@ -133,180 +155,89 @@ export class EditorNode extends RapidElement {
|
|
|
133
155
|
}`;
|
|
134
156
|
}
|
|
135
157
|
|
|
158
|
+
constructor() {
|
|
159
|
+
super();
|
|
160
|
+
this.handleActionOrderChanged = this.handleActionOrderChanged.bind(this);
|
|
161
|
+
}
|
|
162
|
+
|
|
136
163
|
protected updated(
|
|
137
164
|
changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
|
138
165
|
): void {
|
|
139
166
|
super.updated(changes);
|
|
140
167
|
if (changes.has('node')) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
168
|
+
// make our initial connections
|
|
169
|
+
if (changes.get('node') === undefined) {
|
|
170
|
+
// this.plumber.makeTarget(this.node.uuid);
|
|
171
|
+
for (const exit of this.node.exits) {
|
|
172
|
+
if (!exit.destination_uuid) {
|
|
173
|
+
this.plumber.makeSource(exit.uuid);
|
|
174
|
+
} else {
|
|
175
|
+
this.plumber.connectIds(exit.uuid, exit.destination_uuid);
|
|
176
|
+
}
|
|
149
177
|
}
|
|
150
178
|
}
|
|
151
179
|
|
|
152
|
-
const ele = this.
|
|
180
|
+
const ele = this.parentElement;
|
|
153
181
|
const rect = ele.getBoundingClientRect();
|
|
154
182
|
|
|
155
183
|
getStore()
|
|
156
|
-
|
|
184
|
+
?.getState()
|
|
157
185
|
.expandCanvas(
|
|
158
186
|
this.ui.position.left + rect.width,
|
|
159
187
|
this.ui.position.top + rect.height
|
|
160
188
|
);
|
|
161
|
-
|
|
162
|
-
// Add drag event listeners to the node
|
|
163
|
-
this.addDragEventListeners();
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
private addDragEventListeners(): void {
|
|
168
|
-
const nodeElement = this.querySelector('.node') as HTMLElement;
|
|
169
|
-
if (!nodeElement) return;
|
|
170
|
-
|
|
171
|
-
nodeElement.addEventListener('mousedown', this.handleMouseDown.bind(this));
|
|
172
|
-
document.addEventListener('mousemove', this.boundMouseMove);
|
|
173
|
-
document.addEventListener('mouseup', this.boundMouseUp);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
private handleMouseDown(event: MouseEvent): void {
|
|
177
|
-
// Only start dragging if clicking on the node itself, not on exits or other interactive elements
|
|
178
|
-
const target = event.target as HTMLElement;
|
|
179
|
-
if (target.classList.contains('exit') || target.closest('.exit')) {
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
this.isDragging = true;
|
|
184
|
-
this.dragStartPos = { x: event.clientX, y: event.clientY };
|
|
185
|
-
this.nodeStartPos = {
|
|
186
|
-
left: this.ui.position.left,
|
|
187
|
-
top: this.ui.position.top
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
// Add dragging class for visual feedback
|
|
191
|
-
const nodeElement = this.querySelector('.node') as HTMLElement;
|
|
192
|
-
if (nodeElement) {
|
|
193
|
-
nodeElement.classList.add('dragging');
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Elevate connections for this node during dragging
|
|
197
|
-
if (this.plumber) {
|
|
198
|
-
this.plumber.elevateNodeConnections(this.node.uuid);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
event.preventDefault();
|
|
202
|
-
event.stopPropagation();
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
private handleMouseMove(event: MouseEvent): void {
|
|
206
|
-
if (!this.isDragging) return;
|
|
207
|
-
|
|
208
|
-
const deltaX = event.clientX - this.dragStartPos.x;
|
|
209
|
-
const deltaY = event.clientY - this.dragStartPos.y;
|
|
210
|
-
|
|
211
|
-
const newLeft = this.nodeStartPos.left + deltaX;
|
|
212
|
-
const newTop = this.nodeStartPos.top + deltaY;
|
|
213
|
-
|
|
214
|
-
// Snap to 20px grid
|
|
215
|
-
const snappedLeft = this.snapToGrid(newLeft);
|
|
216
|
-
const snappedTop = this.snapToGrid(newTop);
|
|
217
|
-
|
|
218
|
-
// Update the UI position temporarily (for visual feedback)
|
|
219
|
-
const nodeElement = this.querySelector('.node') as HTMLElement;
|
|
220
|
-
if (nodeElement) {
|
|
221
|
-
nodeElement.style.left = `${snappedLeft}px`;
|
|
222
|
-
nodeElement.style.top = `${snappedTop}px`;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Repaint connections during dragging for smooth updates
|
|
226
|
-
if (this.plumber) {
|
|
227
|
-
this.plumber.repaintEverything();
|
|
228
189
|
}
|
|
229
190
|
}
|
|
230
191
|
|
|
231
|
-
private
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
this.isDragging = false;
|
|
192
|
+
private handleActionOrderChanged(event: CustomEvent) {
|
|
193
|
+
const [fromIdx, toIdx] = event.detail.swap;
|
|
235
194
|
|
|
236
|
-
//
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Restore normal z-index for connections
|
|
243
|
-
if (this.plumber) {
|
|
244
|
-
this.plumber.restoreNodeConnections(this.node.uuid);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const deltaX = event.clientX - this.dragStartPos.x;
|
|
248
|
-
const deltaY = event.clientY - this.dragStartPos.y;
|
|
195
|
+
// swap our actions
|
|
196
|
+
const newActions = [...this.node.actions];
|
|
197
|
+
const movedAction = newActions.splice(fromIdx, 1)[0];
|
|
198
|
+
newActions.splice(toIdx, 0, movedAction);
|
|
249
199
|
|
|
250
|
-
|
|
251
|
-
|
|
200
|
+
// udate our internal reprensentation, this isn't strictly necessary
|
|
201
|
+
// since the editor will update us from it's definition subscription
|
|
202
|
+
// but it makes testing a lot easier
|
|
203
|
+
this.node = { ...this.node, actions: newActions };
|
|
252
204
|
|
|
253
|
-
// Snap to 20px grid for final position
|
|
254
|
-
const snappedLeft = this.snapToGrid(newLeft);
|
|
255
|
-
const snappedTop = this.snapToGrid(newTop);
|
|
256
|
-
|
|
257
|
-
// Update the store with the new snapped position
|
|
258
|
-
const newPosition = { left: snappedLeft, top: snappedTop };
|
|
259
205
|
getStore()
|
|
260
|
-
|
|
261
|
-
.
|
|
262
|
-
[this.node.uuid]: newPosition
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
// Repaint connections if plumber is available
|
|
266
|
-
if (this.plumber) {
|
|
267
|
-
this.plumber.repaintEverything();
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
getStore().getState().updateNodePosition(this.node.uuid, newPosition);
|
|
271
|
-
|
|
272
|
-
// Fire a custom event with the new coordinates
|
|
273
|
-
/*this.fireCustomEvent(CustomEventType.Moved, {
|
|
274
|
-
nodeId: this.node.uuid,
|
|
275
|
-
position: newPosition,
|
|
276
|
-
oldPosition: {
|
|
277
|
-
left: this.nodeStartPos.left,
|
|
278
|
-
top: this.nodeStartPos.top
|
|
279
|
-
}
|
|
280
|
-
});*/
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
disconnectedCallback(): void {
|
|
284
|
-
super.disconnectedCallback();
|
|
285
|
-
// Clean up event listeners
|
|
286
|
-
document.removeEventListener('mousemove', this.boundMouseMove);
|
|
287
|
-
document.removeEventListener('mouseup', this.boundMouseUp);
|
|
206
|
+
?.getState()
|
|
207
|
+
.updateNode(this.node.uuid, { ...this.node, actions: newActions });
|
|
288
208
|
}
|
|
289
209
|
|
|
290
210
|
private renderTitle(config: UIConfig) {
|
|
291
211
|
return html`<div class="title" style="background:${config.color}">
|
|
292
|
-
${
|
|
212
|
+
${this.node?.actions?.length > 1
|
|
213
|
+
? html`<temba-icon class="drag-handle" name="sort"></temba-icon>`
|
|
214
|
+
: null}
|
|
215
|
+
|
|
216
|
+
<div class="name">${config.name}</div>
|
|
293
217
|
</div>`;
|
|
294
218
|
}
|
|
295
219
|
|
|
296
|
-
private renderAction(node: Node, action: Action) {
|
|
220
|
+
private renderAction(node: Node, action: Action, index: number) {
|
|
297
221
|
const config = EDITOR_CONFIG[action.type];
|
|
298
222
|
|
|
299
223
|
if (config) {
|
|
300
|
-
return html`<div
|
|
301
|
-
${
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
224
|
+
return html`<div
|
|
225
|
+
class="action sortable ${action.type}"
|
|
226
|
+
id="action-${index}"
|
|
227
|
+
>
|
|
228
|
+
<div class="action-content">
|
|
229
|
+
${this.renderTitle(config)}
|
|
230
|
+
<div class="body">
|
|
231
|
+
${config.render
|
|
232
|
+
? config.render(node, action)
|
|
233
|
+
: html`<pre>${action.type}</pre>`}
|
|
234
|
+
</div>
|
|
306
235
|
</div>
|
|
307
236
|
</div>`;
|
|
308
237
|
}
|
|
309
|
-
return html`<div
|
|
238
|
+
return html`<div class="action sortable" id="action-${index}">
|
|
239
|
+
${action.type}
|
|
240
|
+
</div>`;
|
|
310
241
|
}
|
|
311
242
|
|
|
312
243
|
private renderRouter(router: Router, ui: NodeUI) {
|
|
@@ -363,9 +294,16 @@ export class EditorNode extends RapidElement {
|
|
|
363
294
|
class="node"
|
|
364
295
|
style="left:${this.ui.position.left}px;top:${this.ui.position.top}px"
|
|
365
296
|
>
|
|
366
|
-
${this.node.actions.
|
|
367
|
-
|
|
368
|
-
|
|
297
|
+
${this.node.actions.length > 0
|
|
298
|
+
? html`<temba-sortable-list
|
|
299
|
+
dragHandle="drag-handle"
|
|
300
|
+
@temba-order-changed="${this.handleActionOrderChanged}"
|
|
301
|
+
>
|
|
302
|
+
${this.node.actions.map((actionSpec, index) => {
|
|
303
|
+
return this.renderAction(this.node, actionSpec, index);
|
|
304
|
+
})}
|
|
305
|
+
</temba-sortable-list>`
|
|
306
|
+
: ''}
|
|
369
307
|
${this.node.router
|
|
370
308
|
? html` ${this.renderRouter(this.node.router, this.ui)}
|
|
371
309
|
${this.renderCategories(this.node)}`
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { css, html, PropertyValueMap, TemplateResult } from 'lit';
|
|
2
|
+
import { property } from 'lit/decorators.js';
|
|
3
|
+
import { RapidElement } from '../RapidElement';
|
|
4
|
+
import { StickyNote as StickyNoteData } from '../store/flow-definition';
|
|
5
|
+
import { getStore } from '../store/Store';
|
|
6
|
+
|
|
7
|
+
export class StickyNote extends RapidElement {
|
|
8
|
+
@property({ type: String })
|
|
9
|
+
public uuid: string;
|
|
10
|
+
|
|
11
|
+
@property({ type: Object })
|
|
12
|
+
public data: StickyNoteData;
|
|
13
|
+
|
|
14
|
+
@property({ type: Boolean })
|
|
15
|
+
private dragging = false;
|
|
16
|
+
|
|
17
|
+
static get styles() {
|
|
18
|
+
return css`
|
|
19
|
+
:host {
|
|
20
|
+
--sticky-color: #fef08a;
|
|
21
|
+
--sticky-border-color: #facc15;
|
|
22
|
+
--sticky-text-color: #451a03;
|
|
23
|
+
--curvature: 8px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.sticky-note {
|
|
27
|
+
width: 200px;
|
|
28
|
+
background-color: var(--sticky-color);
|
|
29
|
+
border: 1px solid var(--sticky-border-color);
|
|
30
|
+
border-radius: var(--curvature);
|
|
31
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
32
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
33
|
+
sans-serif;
|
|
34
|
+
font-size: 12px;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
transition: transform 0.1s ease, box-shadow 0.2s ease;
|
|
37
|
+
color: var(--sticky-text-color);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.sticky-note.dragging {
|
|
41
|
+
opacity: 0.5;
|
|
42
|
+
z-index: 1000;
|
|
43
|
+
transform: rotate(0deg);
|
|
44
|
+
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.sticky-note:hover {
|
|
48
|
+
transform: translateY(0px);
|
|
49
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Color themes */
|
|
53
|
+
.sticky-note.yellow {
|
|
54
|
+
--sticky-color: #fef08a;
|
|
55
|
+
--sticky-border-color: #facc15;
|
|
56
|
+
--sticky-text-color: #451a03;
|
|
57
|
+
}
|
|
58
|
+
.sticky-note.blue {
|
|
59
|
+
--sticky-color: #bfdbfe;
|
|
60
|
+
--sticky-border-color: #3b82f6;
|
|
61
|
+
--sticky-text-color: #1e3a8a;
|
|
62
|
+
}
|
|
63
|
+
.sticky-note.pink {
|
|
64
|
+
--sticky-color: #fce7f3;
|
|
65
|
+
--sticky-border-color: #ec4899;
|
|
66
|
+
--sticky-text-color: #831843;
|
|
67
|
+
}
|
|
68
|
+
.sticky-note.green {
|
|
69
|
+
--sticky-color: #d1fae5;
|
|
70
|
+
--sticky-border-color: #10b981;
|
|
71
|
+
--sticky-text-color: #064e3b;
|
|
72
|
+
}
|
|
73
|
+
.sticky-note.gray {
|
|
74
|
+
--sticky-color: #f3f4f6;
|
|
75
|
+
--sticky-border-color: #6b7280;
|
|
76
|
+
--sticky-text-color: #374151;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Title and body containers */
|
|
80
|
+
.sticky-title-container {
|
|
81
|
+
position: relative;
|
|
82
|
+
border-bottom: 1px solid var(--sticky-border-color);
|
|
83
|
+
background-color: rgba(255, 255, 255, 0.5);
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
}
|
|
87
|
+
.sticky-body-container {
|
|
88
|
+
position: relative;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Editable fields */
|
|
92
|
+
[contenteditable='true'] {
|
|
93
|
+
margin: 2px;
|
|
94
|
+
padding: 4px 8px;
|
|
95
|
+
outline: none;
|
|
96
|
+
border-radius: var(--curvature);
|
|
97
|
+
transition: background 0.2s;
|
|
98
|
+
}
|
|
99
|
+
[contenteditable='true']:focus {
|
|
100
|
+
background-color: rgba(255, 255, 255, 0.8);
|
|
101
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
102
|
+
outline-color: var(--sticky-border-color);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Title */
|
|
106
|
+
.sticky-title {
|
|
107
|
+
font-weight: 600;
|
|
108
|
+
font-size: 13px;
|
|
109
|
+
color: var(--sticky-text-color);
|
|
110
|
+
min-height: 20px;
|
|
111
|
+
line-height: 20px;
|
|
112
|
+
border-top-left-radius: var(--curvature);
|
|
113
|
+
border-top-right-radius: var(--curvature);
|
|
114
|
+
flex-grow: 1;
|
|
115
|
+
padding-left: 8px;
|
|
116
|
+
}
|
|
117
|
+
.sticky-title:empty::before {
|
|
118
|
+
content: 'Click to add title';
|
|
119
|
+
opacity: 0.5;
|
|
120
|
+
font-style: italic;
|
|
121
|
+
}
|
|
122
|
+
.sticky-title:focus {
|
|
123
|
+
border-bottom-left-radius: 0px;
|
|
124
|
+
border-bottom-right-radius: 0px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Body */
|
|
128
|
+
.sticky-body {
|
|
129
|
+
padding: 8px 10px;
|
|
130
|
+
color: var(--sticky-text-color);
|
|
131
|
+
line-height: 1.4;
|
|
132
|
+
min-height: 48px;
|
|
133
|
+
word-wrap: break-word;
|
|
134
|
+
white-space: pre-wrap;
|
|
135
|
+
}
|
|
136
|
+
.sticky-body:empty::before {
|
|
137
|
+
content: 'Click to add note';
|
|
138
|
+
opacity: 0.5;
|
|
139
|
+
font-style: italic;
|
|
140
|
+
}
|
|
141
|
+
.sticky-body:focus {
|
|
142
|
+
border-top-left-radius: 0px;
|
|
143
|
+
border-top-right-radius: 0px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Drag icon */
|
|
147
|
+
.sticky-title-container > .drag-handle {
|
|
148
|
+
--icon-color: var(--sticky-border-color);
|
|
149
|
+
cursor: move;
|
|
150
|
+
max-width: 0px;
|
|
151
|
+
overflow: hidden;
|
|
152
|
+
transition: all 0.2s ease;
|
|
153
|
+
padding-left: 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.sticky-note:hover .drag-handle {
|
|
157
|
+
max-width: 20px;
|
|
158
|
+
padding-left: 8px;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.sticky-note:focus-within .sticky-title-container > .drag-handle {
|
|
162
|
+
max-width: 0px;
|
|
163
|
+
padding-left: 0px;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* Focus/active states */
|
|
167
|
+
.sticky-note:focus-within {
|
|
168
|
+
box-shadow: 0 0 0 1px var(--sticky-border-color),
|
|
169
|
+
0 10px 20px rgba(0, 0, 0, 0.3);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.sticky-note:focus-within .drag-handle {
|
|
173
|
+
max-width: 0px;
|
|
174
|
+
}
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
protected updated(
|
|
179
|
+
changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
|
180
|
+
): void {
|
|
181
|
+
super.updated(changes);
|
|
182
|
+
if (changes.has('data') || changes.has('uuid')) {
|
|
183
|
+
this.updateCanvasSize();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private updateCanvasSize(): void {
|
|
188
|
+
if (!this.data) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const element = this.querySelector('.sticky-note');
|
|
193
|
+
if (element) {
|
|
194
|
+
const rect = element.getBoundingClientRect();
|
|
195
|
+
getStore()
|
|
196
|
+
.getState()
|
|
197
|
+
.expandCanvas(
|
|
198
|
+
this.data.position.left + rect.width,
|
|
199
|
+
this.data.position.top + rect.height
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private handleTitleBlur(event: FocusEvent): void {
|
|
205
|
+
const target = event.target as HTMLElement;
|
|
206
|
+
const newTitle = target.textContent || '';
|
|
207
|
+
|
|
208
|
+
if (this.data && newTitle !== this.data.title) {
|
|
209
|
+
getStore()
|
|
210
|
+
.getState()
|
|
211
|
+
.updateStickyNote(this.uuid, {
|
|
212
|
+
...this.data,
|
|
213
|
+
title: newTitle
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
this.requestUpdate();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private handleBodyBlur(event: FocusEvent): void {
|
|
220
|
+
const target = event.target as HTMLElement;
|
|
221
|
+
const newBody = target.textContent || '';
|
|
222
|
+
|
|
223
|
+
if (this.data && newBody !== this.data.body) {
|
|
224
|
+
getStore()
|
|
225
|
+
.getState()
|
|
226
|
+
.updateStickyNote(this.uuid, {
|
|
227
|
+
...this.data,
|
|
228
|
+
body: newBody
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
this.requestUpdate();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private handleKeyDown(event: KeyboardEvent): void {
|
|
235
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
236
|
+
event.preventDefault();
|
|
237
|
+
(event.target as HTMLElement).blur();
|
|
238
|
+
}
|
|
239
|
+
if (event.key === 'Escape') {
|
|
240
|
+
(event.target as HTMLElement).blur();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
public render(): TemplateResult {
|
|
245
|
+
if (!this.data) {
|
|
246
|
+
return html`<div class="sticky-note" style="display: none;"></div>`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const style = `left: ${this.data.position.left}px; top: ${this.data.position.top}px;`;
|
|
250
|
+
|
|
251
|
+
return html`
|
|
252
|
+
<div
|
|
253
|
+
class="sticky-note ${this.data.color} ${this.dragging
|
|
254
|
+
? 'dragging'
|
|
255
|
+
: ''}"
|
|
256
|
+
style="${style}"
|
|
257
|
+
data-uuid="${this.uuid}"
|
|
258
|
+
>
|
|
259
|
+
<div class="sticky-title-container">
|
|
260
|
+
<temba-icon name="drag" class="drag-handle"></temba-icon>
|
|
261
|
+
<div
|
|
262
|
+
class="sticky-title"
|
|
263
|
+
contenteditable="true"
|
|
264
|
+
@blur="${this.handleTitleBlur}"
|
|
265
|
+
@keydown="${this.handleKeyDown}"
|
|
266
|
+
@mousedown="${(e: MouseEvent) => e.stopPropagation()}"
|
|
267
|
+
.textContent="${this.data.title}"
|
|
268
|
+
></div>
|
|
269
|
+
</div>
|
|
270
|
+
<div class="sticky-body-container">
|
|
271
|
+
<div
|
|
272
|
+
class="sticky-body"
|
|
273
|
+
contenteditable="true"
|
|
274
|
+
@blur="${this.handleBodyBlur}"
|
|
275
|
+
@keydown="${this.handleKeyDown}"
|
|
276
|
+
@mousedown="${(e: MouseEvent) => e.stopPropagation()}"
|
|
277
|
+
.textContent="${this.data.body}"
|
|
278
|
+
></div>
|
|
279
|
+
<div class="edit-icon" title="Edit note"></div>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
`;
|
|
283
|
+
}
|
|
284
|
+
}
|
package/src/list/RunList.ts
CHANGED
package/src/list/SortableList.ts
CHANGED
|
@@ -108,6 +108,9 @@ export class SortableList extends RapidElement {
|
|
|
108
108
|
@property({ type: String })
|
|
109
109
|
dropTargetId: string;
|
|
110
110
|
|
|
111
|
+
@property({ type: String })
|
|
112
|
+
dragHandle: string;
|
|
113
|
+
|
|
111
114
|
/**
|
|
112
115
|
* Optional callback to allow parent components to customize the ghost node.
|
|
113
116
|
* Called after the ghost node is cloned but before it is appended to the DOM.
|
|
@@ -260,6 +263,14 @@ export class SortableList extends RapidElement {
|
|
|
260
263
|
|
|
261
264
|
private handleMouseDown(event: MouseEvent) {
|
|
262
265
|
let ele = event.target as HTMLDivElement;
|
|
266
|
+
|
|
267
|
+
// if we have a drag handle, only allow dragging from that element
|
|
268
|
+
if (this.dragHandle) {
|
|
269
|
+
if (!ele.classList.contains(this.dragHandle)) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
263
274
|
ele = ele.closest('.sortable');
|
|
264
275
|
if (ele) {
|
|
265
276
|
event.preventDefault();
|
package/src/locales/es.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
|
|
2
|
+
// Do not modify this file by hand!
|
|
3
|
+
// Re-generate this file by running lit-localize
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/* eslint-disable no-irregular-whitespace */
|
|
9
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
10
|
+
|
|
11
|
+
export const templates = {
|
|
12
|
+
'scf1453991c986b25': `Tab para completar, enter para seleccionar`,
|
|
13
|
+
's73b4d70c02f4b4e0': `No options`,
|
|
14
|
+
's8f02e3a18ffc083a': `Are not currently in a flow`,
|
|
15
|
+
's638236250662c6b3': `Have sent a message in the last`,
|
|
16
|
+
's4788ee206c4570c7': `Have not started this flow in the last 90 days`,
|
|
17
|
+
};
|
|
18
|
+
|