@adatechnology/conversations-ui 0.1.0-rc.39 → 0.1.0-rc.40
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/dist/{chunk-DXPSPUWF.js → chunk-DKPXKQGC.js} +1 -1
- package/dist/flows/index.d.ts +88 -5
- package/dist/flows/index.js +1104 -686
- package/dist/index.js +1 -1
- package/package.json +8 -7
- package/src/Tooltip.tsx +5 -2
- package/src/flows/FlowConnectionEdge.tsx +104 -0
- package/src/flows/FlowLegend.tsx +125 -0
- package/src/flows/FlowNodeCard.tsx +188 -30
- package/src/flows/FlowNodePanel.tsx +5 -2
- package/src/flows/FlowPalette.tsx +105 -78
- package/src/flows/FlowsWorkspace.tsx +177 -7
- package/src/flows/flowCanvasModel.test.ts +145 -1
- package/src/flows/flowCanvasModel.ts +54 -44
- package/src/flows/flowEditorOps.test.ts +35 -0
- package/src/flows/flowEditorOps.ts +25 -0
- package/src/flows/flowGraph.ts +72 -47
- package/src/flows/index.ts +4 -1
- package/src/flows/labels.ts +39 -0
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { namespaceNodeId, parseNamespacedId } from './flowEditorOps'
|
|
14
14
|
import {
|
|
15
15
|
NODE_CARD_WIDTH,
|
|
16
|
+
cascadeOrder,
|
|
16
17
|
crossFlowKey,
|
|
17
18
|
estimateNodeHeight,
|
|
18
19
|
isCrossFlowTarget,
|
|
@@ -107,11 +108,12 @@ export function countLiveByNode(params: {
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
/**
|
|
110
|
-
* Um layout só para TODOS os nós de TODOS os fluxos abertos juntos
|
|
111
|
+
* Um layout só para TODOS os nós de TODOS os fluxos abertos juntos, na mesma cascata do fluxo
|
|
112
|
+
* único: um passo para a direita a cada avanço da conversa, um degrau para baixo a cada card.
|
|
111
113
|
*
|
|
112
114
|
* Posicionar cada fluxo à parte e deslocar não resolve: nada impede dois fluxos de ocuparem o mesmo
|
|
113
|
-
* espaço, e a altura real de cada card é ignorada. Aqui
|
|
114
|
-
*
|
|
115
|
+
* espaço, e a altura real de cada card é ignorada. Aqui a travessia roda sobre o grafo mesclado
|
|
116
|
+
* inteiro, com os saltos `flow:<key>` já resolvidos para o nó inicial do alvo.
|
|
115
117
|
*/
|
|
116
118
|
export function computeMergedLayout(params: {
|
|
117
119
|
readonly openKeys: readonly string[]
|
|
@@ -128,7 +130,10 @@ export function computeMergedLayout(params: {
|
|
|
128
130
|
}
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
function forwardEdges(
|
|
133
|
+
function forwardEdges(id: string): string[] {
|
|
134
|
+
const node = nodeById.get(id)
|
|
135
|
+
if (!node) return []
|
|
136
|
+
const flowKey = parseNamespacedId(id).flowKey
|
|
132
137
|
const result: string[] = []
|
|
133
138
|
|
|
134
139
|
for (const { target } of targetsOf(node)) {
|
|
@@ -144,49 +149,19 @@ export function computeMergedLayout(params: {
|
|
|
144
149
|
return result
|
|
145
150
|
}
|
|
146
151
|
|
|
147
|
-
const rank = new Map<string, number>()
|
|
148
152
|
const primaryGraph = graphs[primaryFlowKey]
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
while (queue.length > 0) {
|
|
155
|
-
const id = queue.shift()!
|
|
156
|
-
const node = nodeById.get(id)
|
|
157
|
-
if (!node) continue
|
|
158
|
-
for (const nextId of forwardEdges(parseNamespacedId(id).flowKey, node)) {
|
|
159
|
-
if (!rank.has(nextId)) {
|
|
160
|
-
rank.set(nextId, rank.get(id)! + 1)
|
|
161
|
-
queue.push(nextId)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Todo nó não alcançado vai para UMA coluna extra depois de tudo. Uma coluna por órfão empurrava
|
|
168
|
-
// cada um mais para longe da área visível — e desligar um fio se lia como "o card sumiu".
|
|
169
|
-
const strayRank = Math.max(0, ...rank.values()) + 1
|
|
170
|
-
for (const id of nodeById.keys()) {
|
|
171
|
-
if (!rank.has(id)) rank.set(id, strayRank)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const layers = new Map<number, string[]>()
|
|
175
|
-
for (const [id, value] of rank) {
|
|
176
|
-
const layer = layers.get(value)
|
|
177
|
-
if (layer) layer.push(id)
|
|
178
|
-
else layers.set(value, [id])
|
|
179
|
-
}
|
|
153
|
+
const placed = cascadeOrder({
|
|
154
|
+
rootId: primaryGraph ? namespaceNodeId(primaryFlowKey, primaryGraph.startNodeId) : '',
|
|
155
|
+
allIds: [...nodeById.keys()],
|
|
156
|
+
forwardEdges,
|
|
157
|
+
})
|
|
180
158
|
|
|
181
159
|
const positions = new Map<string, FlowNodePosition>()
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
positions.set(id, { x: value * COLUMN_GAP, y: cursorY })
|
|
188
|
-
cursorY += estimateNodeHeight(nodeById.get(id)!) + ROW_GAP
|
|
189
|
-
}
|
|
160
|
+
let cursorY = 0
|
|
161
|
+
|
|
162
|
+
for (const [id, { depth }] of [...placed.entries()].sort((a, b) => a[1].order - b[1].order)) {
|
|
163
|
+
positions.set(id, { x: depth * COLUMN_GAP, y: cursorY })
|
|
164
|
+
cursorY += estimateNodeHeight(nodeById.get(id)!) + ROW_GAP
|
|
190
165
|
}
|
|
191
166
|
|
|
192
167
|
return positions
|
|
@@ -256,6 +231,11 @@ export function buildFlowEdges(params: {
|
|
|
256
231
|
const source = namespaceNodeId(flowKey, nodeId)
|
|
257
232
|
const edgeTarget = namespaceNodeId(targetFlowKey, targetNodeId)
|
|
258
233
|
|
|
234
|
+
// Voltar ao próprio card não é trajeto, é comportamento — e como aresta não tem onde
|
|
235
|
+
// caber: por baixo some atrás do card, por cima o cobre. Quem mostra isso é o ícone de
|
|
236
|
+
// repetição na linha de saída, dentro do card (ver `FlowNodeCard`).
|
|
237
|
+
if (source === edgeTarget) continue
|
|
238
|
+
|
|
259
239
|
if (isDefault) {
|
|
260
240
|
edges.push({
|
|
261
241
|
id: `${source}->${edgeTarget}-default`,
|
|
@@ -366,3 +346,33 @@ export function newNodeFromSpec(spec: NewNodeSpec, existingIds: ReadonlySet<stri
|
|
|
366
346
|
const id = slugifyNodeId('nova_acao', existingIds)
|
|
367
347
|
return { id, type: 'action', actionKind: spec.actionKind }
|
|
368
348
|
}
|
|
349
|
+
|
|
350
|
+
/** Distância vertical mínima entre dois cards para que não se leiam como um só. */
|
|
351
|
+
const FREE_SLOT_STEP = 120
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Empurra a posição para baixo até achar espaço livre na mesma coluna.
|
|
355
|
+
*
|
|
356
|
+
* O "+" põe o nó novo à direita de quem o criou — e é justamente ali que costuma estar o nó que
|
|
357
|
+
* acabou de perder a ligação. Sem isto, os dois se sobrepõem e a tela mostra um card onde há dois,
|
|
358
|
+
* que é o mesmo susto de "sumiu" por outro caminho.
|
|
359
|
+
*/
|
|
360
|
+
export function findFreeSlot(params: {
|
|
361
|
+
readonly desired: FlowNodePosition
|
|
362
|
+
readonly taken: readonly FlowNodePosition[]
|
|
363
|
+
readonly step?: number
|
|
364
|
+
}): FlowNodePosition {
|
|
365
|
+
const step = params.step ?? FREE_SLOT_STEP
|
|
366
|
+
const isOccupied = (candidate: FlowNodePosition) =>
|
|
367
|
+
params.taken.some(
|
|
368
|
+
(each) => Math.abs(each.x - candidate.x) < NODE_CARD_WIDTH && Math.abs(each.y - candidate.y) < step,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
let slot = params.desired
|
|
372
|
+
// Teto igual ao número de cards: com N ocupados, N descidas bastam para sair de todos.
|
|
373
|
+
for (let attempt = 0; attempt <= params.taken.length && isOccupied(slot); attempt += 1) {
|
|
374
|
+
slot = { x: slot.x, y: slot.y + step }
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return slot
|
|
378
|
+
}
|
|
@@ -15,6 +15,7 @@ import type { FlowGraphData, FlowNodeData } from '@adatechnology/meta-whatsapp-c
|
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
17
|
applyConnection,
|
|
18
|
+
clearConnection,
|
|
18
19
|
isGraphDirty,
|
|
19
20
|
mergedFlowKeysFrom,
|
|
20
21
|
namespaceNodeId,
|
|
@@ -239,3 +240,37 @@ describe('rascunho sujo', () => {
|
|
|
239
240
|
expect(isGraphDirty(published, undefined)).toBe(false)
|
|
240
241
|
})
|
|
241
242
|
})
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Desligar um fio é a operação que o relato de campo pegou errada: o card sumia. Some porque a
|
|
246
|
+
* opção deixa de existir no `byAnswer`, não porque alguém apagou o nó — e o teste fixa isso.
|
|
247
|
+
*/
|
|
248
|
+
describe('clearConnection', () => {
|
|
249
|
+
it('saída única vira destino vazio, e o nó continua lá', () => {
|
|
250
|
+
expect(clearConnection(node('a', 'b'), 'next').next).toBe('')
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('desligar uma opção NÃO apaga a chave — a opção sumindo levaria junto o botão do WhatsApp', () => {
|
|
254
|
+
const branching = node('a', { byAnswer: { sim: 'b', nao: 'c' }, default: 'd' })
|
|
255
|
+
|
|
256
|
+
const result = clearConnection(branching, 'sim')
|
|
257
|
+
|
|
258
|
+
expect(result.next).toEqual({ byAnswer: { sim: '', nao: 'c' }, default: 'd' })
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('desligar o fallback preserva as opções', () => {
|
|
262
|
+
const branching = node('a', { byAnswer: { sim: 'b' }, default: 'd' })
|
|
263
|
+
|
|
264
|
+
expect(clearConnection(branching, '__default').next).toEqual({ byAnswer: { sim: 'b' }, default: '' })
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('handle inexistente não inventa ramificação nem derruba as existentes', () => {
|
|
268
|
+
const branching = node('a', { byAnswer: { sim: 'b' }, default: 'd' })
|
|
269
|
+
|
|
270
|
+
expect(clearConnection(branching, 'talvez').next).toEqual({ byAnswer: { sim: 'b', talvez: '' }, default: 'd' })
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('nó sem next algum não quebra', () => {
|
|
274
|
+
expect(clearConnection(node('a'), 'next').next).toBe('')
|
|
275
|
+
})
|
|
276
|
+
})
|
|
@@ -175,3 +175,28 @@ export function isGraphDirty(working: FlowGraphData | undefined, published: Flow
|
|
|
175
175
|
if (!working || !published) return false
|
|
176
176
|
return JSON.stringify(working) !== JSON.stringify(published)
|
|
177
177
|
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Desliga UMA saída do nó, preservando as outras.
|
|
181
|
+
*
|
|
182
|
+
* O que não é óbvio: desligar é gravar destino vazio, nunca apagar a chave. Numa ramificação, a
|
|
183
|
+
* opção sem `byAnswer` some do card — e some junto o botão que o cliente via no WhatsApp, o que se
|
|
184
|
+
* lê como "o editor apagou minha opção". Vazio mantém a linha visível, cobrando a religação.
|
|
185
|
+
*/
|
|
186
|
+
export function clearConnection(node: FlowNodeData, handle: string): FlowNodeData {
|
|
187
|
+
if (handle === 'next' || typeof node.next === 'string' || node.next === undefined) {
|
|
188
|
+
return { ...node, next: '' }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (handle === '__default') {
|
|
192
|
+
return { ...node, next: { byAnswer: node.next.byAnswer ?? {}, default: '' } }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
...node,
|
|
197
|
+
next: {
|
|
198
|
+
byAnswer: { ...(node.next.byAnswer ?? {}), [handle]: '' },
|
|
199
|
+
default: node.next.default ?? '',
|
|
200
|
+
},
|
|
201
|
+
}
|
|
202
|
+
}
|
package/src/flows/flowGraph.ts
CHANGED
|
@@ -38,6 +38,16 @@ export const BUILT_IN_ACTION_KINDS = FLOW_ACTION_KIND
|
|
|
38
38
|
// (em vez de importado do contracts) de propósito: são três linhas triviais e importá-las como
|
|
39
39
|
// valor puxaria o runtime do contracts para o bundle do frontend só por causa disso. O contracts
|
|
40
40
|
// exporta as mesmas funções para o backend; a convenção "flow:" é o contrato de fato.
|
|
41
|
+
/**
|
|
42
|
+
* Ações depois das quais a conversa CONTINUA no grafo, em vez de terminar.
|
|
43
|
+
*
|
|
44
|
+
* A distinção não é estética: o motor do bot só anda para o `next` depois de `send_media` — as
|
|
45
|
+
* demais ações (handoff, encerrar, simular, catálogo) encerram o passo ali. Dar saída a uma ação
|
|
46
|
+
* que o motor não atravessa desenharia um fio que o bot ignora em produção, deixando a conversa
|
|
47
|
+
* parada sem ninguém entender por quê — o mesmo erro que `resolveConnection` recusa cometer.
|
|
48
|
+
*/
|
|
49
|
+
export const PASS_THROUGH_ACTION_KINDS: readonly string[] = ['send_media']
|
|
50
|
+
|
|
41
51
|
export const CROSS_FLOW_PREFIX = 'flow:'
|
|
42
52
|
export const isCrossFlowTarget = (target: string): boolean => target.startsWith(CROSS_FLOW_PREFIX)
|
|
43
53
|
export const crossFlowKey = (target: string): string => target.slice(CROSS_FLOW_PREFIX.length)
|
|
@@ -54,7 +64,10 @@ export function estimateNodeHeight(node: FlowNodeData): number {
|
|
|
54
64
|
const ROW_HEIGHT = 34
|
|
55
65
|
const rowCount =
|
|
56
66
|
node.type === 'action'
|
|
57
|
-
?
|
|
67
|
+
? // Ação de passagem desenha uma linha de saída; terminal não desenha nenhuma.
|
|
68
|
+
node.actionKind && PASS_THROUGH_ACTION_KINDS.includes(node.actionKind)
|
|
69
|
+
? 1
|
|
70
|
+
: 0
|
|
58
71
|
: node.type === 'condition'
|
|
59
72
|
? 2
|
|
60
73
|
: node.type === 'menu' || node.questionType === 'choice'
|
|
@@ -206,59 +219,71 @@ function findUnreachable(graph: FlowGraphData): string[] {
|
|
|
206
219
|
return Object.keys(graph.nodes).filter((id) => !reachable.has(id))
|
|
207
220
|
}
|
|
208
221
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
export
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
222
|
+
/** Distância horizontal entre um card e o seguinte na cascata. */
|
|
223
|
+
export const LAYOUT_COLUMN_GAP = 300
|
|
224
|
+
/** Folga vertical entre um card e o de baixo, somada à altura real do de cima. */
|
|
225
|
+
export const LAYOUT_ROW_GAP = 40
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Ordem de leitura do grafo: profundidade primeiro, seguindo as saídas na ordem em que o card as
|
|
229
|
+
* mostra.
|
|
230
|
+
*
|
|
231
|
+
* Profundidade, e não largura, porque é ela que mantém um caminho de conversa junto na tela — com
|
|
232
|
+
* BFS, os dois ramos de uma decisão se intercalam linha a linha e o olho perde qual leva a qual.
|
|
233
|
+
*/
|
|
234
|
+
export function cascadeOrder(params: {
|
|
235
|
+
readonly rootId: string
|
|
236
|
+
readonly allIds: readonly string[]
|
|
237
|
+
readonly forwardEdges: (id: string) => readonly string[]
|
|
238
|
+
}): Map<string, { depth: number; order: number }> {
|
|
239
|
+
const placed = new Map<string, { depth: number; order: number }>()
|
|
240
|
+
|
|
241
|
+
function visit(id: string, depth: number): void {
|
|
242
|
+
if (placed.has(id)) return
|
|
243
|
+
placed.set(id, { depth, order: placed.size })
|
|
244
|
+
for (const next of params.forwardEdges(id)) visit(next, depth + 1)
|
|
233
245
|
}
|
|
234
246
|
|
|
235
|
-
|
|
236
|
-
const maxRank = Math.max(0, ...Object.values(rank))
|
|
237
|
-
let strayRank = maxRank + 1
|
|
238
|
-
for (const id of Object.keys(graph.nodes)) {
|
|
239
|
-
if (rank[id] === undefined) rank[id] = strayRank++
|
|
240
|
-
}
|
|
247
|
+
if (params.allIds.includes(params.rootId)) visit(params.rootId, 0)
|
|
241
248
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
249
|
+
// Órfãos entram depois, todos na mesma coluna extra: uma coluna por órfão empurrava cada um para
|
|
250
|
+
// mais longe da área visível, e desligar um fio se lia como "o card sumiu".
|
|
251
|
+
const strayDepth = Math.max(0, ...[...placed.values()].map((each) => each.depth)) + 1
|
|
252
|
+
for (const id of params.allIds) {
|
|
253
|
+
if (!placed.has(id)) placed.set(id, { depth: strayDepth, order: placed.size })
|
|
245
254
|
}
|
|
246
255
|
|
|
256
|
+
return placed
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Auto-layout em cascata: avança para a direita a cada passo do fluxo e desce a cada card.
|
|
261
|
+
*
|
|
262
|
+
* **Um card por linha, sempre.** Empilhar por camada — todos do mesmo nível na mesma coluna, todas
|
|
263
|
+
* as colunas começando na mesma altura — deixava os fios correndo na horizontal, e um fio horizontal
|
|
264
|
+
* passa por trás de qualquer card que esteja entre a origem e o destino. Descendo um degrau por
|
|
265
|
+
* card, toda ligação vira uma diagonal curta e visível, e o caminho da conversa se lê de cima para
|
|
266
|
+
* baixo enquanto avança da esquerda para a direita.
|
|
267
|
+
*/
|
|
268
|
+
export function computeAutoLayout(graph: FlowGraphData): Record<string, { x: number; y: number }> {
|
|
269
|
+
const placed = cascadeOrder({
|
|
270
|
+
rootId: graph.startNodeId,
|
|
271
|
+
allIds: Object.keys(graph.nodes),
|
|
272
|
+
forwardEdges: (id) =>
|
|
273
|
+
targetsOf(graph.nodes[id] ?? { id, type: 'action' })
|
|
274
|
+
.map((edge) => edge.target)
|
|
275
|
+
.filter((target) => !isCrossFlowTarget(target) && Boolean(graph.nodes[target])),
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
const byOrder = [...placed.entries()].sort((a, b) => a[1].order - b[1].order)
|
|
247
279
|
const positions: Record<string, { x: number; y: number }> = {}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const ids = layers[r]!
|
|
254
|
-
const width = (ids.length - 1) * H_GAP
|
|
255
|
-
let maxHeight = 0
|
|
256
|
-
ids.forEach((id, index) => {
|
|
257
|
-
maxHeight = Math.max(maxHeight, estimateNodeHeight(graph.nodes[id]!))
|
|
258
|
-
positions[id] = { x: index * H_GAP - width / 2, y: cumulativeY }
|
|
259
|
-
})
|
|
260
|
-
cumulativeY += maxHeight + V_GAP
|
|
280
|
+
let cursorY = 0
|
|
281
|
+
|
|
282
|
+
for (const [id, { depth }] of byOrder) {
|
|
283
|
+
positions[id] = { x: depth * LAYOUT_COLUMN_GAP, y: cursorY }
|
|
284
|
+
cursorY += estimateNodeHeight(graph.nodes[id]!) + LAYOUT_ROW_GAP
|
|
261
285
|
}
|
|
286
|
+
|
|
262
287
|
return positions
|
|
263
288
|
}
|
|
264
289
|
|
package/src/flows/index.ts
CHANGED
|
@@ -6,7 +6,9 @@ export { FlowMapCanvas } from './FlowMapCanvas'
|
|
|
6
6
|
export { FlowGroupFrame, flowGroupFrameNodeTypes } from './FlowGroupFrame'
|
|
7
7
|
export { FlowGroupHeader, flowGroupHeaderNodeTypes } from './FlowGroupHeader'
|
|
8
8
|
export { FlowPortalNode, flowPortalNodeTypes } from './FlowPortalNode'
|
|
9
|
-
export { FlowPalette } from './FlowPalette'
|
|
9
|
+
export { FlowPalette, FlowPaletteMenu } from './FlowPalette'
|
|
10
|
+
export { FlowLegend } from './FlowLegend'
|
|
11
|
+
export { FlowConnectionEdge, flowEdgeTypes } from './FlowConnectionEdge'
|
|
10
12
|
export { FlowNodePanel } from './FlowNodePanel'
|
|
11
13
|
export { FlowWhatsAppPreview } from './FlowWhatsAppPreview'
|
|
12
14
|
export { FlowsWorkspace } from './FlowsWorkspace'
|
|
@@ -16,6 +18,7 @@ export { DEFAULT_FLOW_EDITOR_LABELS, mergeFlowEditorLabels } from './labels'
|
|
|
16
18
|
export {
|
|
17
19
|
CONDITION_OPERATORS,
|
|
18
20
|
BUILT_IN_ACTION_KINDS,
|
|
21
|
+
PASS_THROUGH_ACTION_KINDS,
|
|
19
22
|
NODE_CARD_WIDTH,
|
|
20
23
|
WHATSAPP_LIMITS,
|
|
21
24
|
CROSS_FLOW_PREFIX,
|
package/src/flows/labels.ts
CHANGED
|
@@ -56,6 +56,25 @@ export interface FlowEditorLabels {
|
|
|
56
56
|
media: string
|
|
57
57
|
mediaUnavailable: string
|
|
58
58
|
}
|
|
59
|
+
quickAdd: {
|
|
60
|
+
fromHandle: string
|
|
61
|
+
title: string
|
|
62
|
+
disconnect: string
|
|
63
|
+
}
|
|
64
|
+
/** Legenda do canvas: o que cada traço e cada contorno querem dizer. */
|
|
65
|
+
legendPanel: {
|
|
66
|
+
title: string
|
|
67
|
+
nodes: string
|
|
68
|
+
connections: string
|
|
69
|
+
linear: string
|
|
70
|
+
branch: string
|
|
71
|
+
fallback: string
|
|
72
|
+
crossFlow: string
|
|
73
|
+
live: string
|
|
74
|
+
selfLoop: string
|
|
75
|
+
detached: string
|
|
76
|
+
startNode: string
|
|
77
|
+
}
|
|
59
78
|
palette: {
|
|
60
79
|
title: string
|
|
61
80
|
question: string
|
|
@@ -224,6 +243,24 @@ export const DEFAULT_FLOW_EDITOR_LABELS: FlowEditorLabels = {
|
|
|
224
243
|
media: 'Arquivos enviados neste ponto',
|
|
225
244
|
mediaUnavailable: 'A biblioteca de arquivos não está disponível neste painel.',
|
|
226
245
|
},
|
|
246
|
+
quickAdd: {
|
|
247
|
+
fromHandle: 'Criar o próximo nó já ligado aqui',
|
|
248
|
+
title: 'Ligar em um nó novo',
|
|
249
|
+
disconnect: 'Desligar este fio (o nó continua no fluxo)',
|
|
250
|
+
},
|
|
251
|
+
legendPanel: {
|
|
252
|
+
title: 'Legenda',
|
|
253
|
+
nodes: 'Cards',
|
|
254
|
+
connections: 'Ligações',
|
|
255
|
+
linear: 'Segue direto para o próximo',
|
|
256
|
+
branch: 'Caminho de uma opção escolhida',
|
|
257
|
+
fallback: 'Quando a resposta não casa com nenhuma opção',
|
|
258
|
+
crossFlow: 'Salta para outro fluxo',
|
|
259
|
+
live: 'Tem conversa passando por aqui agora',
|
|
260
|
+
selfLoop: 'Volta ao mesmo card — repete a pergunta',
|
|
261
|
+
detached: 'Ninguém aponta para este card: o bot não chega nele',
|
|
262
|
+
startNode: 'Onde o fluxo começa',
|
|
263
|
+
},
|
|
227
264
|
palette: {
|
|
228
265
|
title: 'Adicionar ao fluxo',
|
|
229
266
|
question: 'Pergunta',
|
|
@@ -316,6 +353,8 @@ export function mergeFlowEditorLabels(override?: Partial<FlowEditorLabels>): Flo
|
|
|
316
353
|
},
|
|
317
354
|
questionTypeLabels: { ...DEFAULT_FLOW_EDITOR_LABELS.questionTypeLabels, ...override.questionTypeLabels },
|
|
318
355
|
nodePanel: { ...DEFAULT_FLOW_EDITOR_LABELS.nodePanel, ...override.nodePanel },
|
|
356
|
+
quickAdd: { ...DEFAULT_FLOW_EDITOR_LABELS.quickAdd, ...override.quickAdd },
|
|
357
|
+
legendPanel: { ...DEFAULT_FLOW_EDITOR_LABELS.legendPanel, ...override.legendPanel },
|
|
319
358
|
palette: { ...DEFAULT_FLOW_EDITOR_LABELS.palette, ...override.palette },
|
|
320
359
|
flowMap: { ...DEFAULT_FLOW_EDITOR_LABELS.flowMap, ...override.flowMap },
|
|
321
360
|
flowGroup: { ...DEFAULT_FLOW_EDITOR_LABELS.flowGroup, ...override.flowGroup },
|