@flowtomic/logic 0.1.1 → 0.1.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.
@@ -16,8 +16,9 @@ function calculateLevels(hierarchy) {
16
16
  const visited = new Set();
17
17
  // Função recursiva para calcular nível
18
18
  const calculateLevel = (entityId) => {
19
- if (levels.has(entityId)) {
20
- return levels.get(entityId);
19
+ const cachedLevel = levels.get(entityId);
20
+ if (cachedLevel !== undefined) {
21
+ return cachedLevel;
21
22
  }
22
23
  if (visited.has(entityId)) {
23
24
  // Ciclo detectado, retornar 0 como fallback
@@ -46,7 +47,7 @@ function calculateLevels(hierarchy) {
46
47
  /**
47
48
  * Organiza entidades por nível (geração) e calcula posições hierárquicas
48
49
  */
49
- function calculateHierarchicalPositions(data, hierarchy, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing) {
50
+ function calculateHierarchicalPositions(_data, hierarchy, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing) {
50
51
  const positions = new Map();
51
52
  const levels = calculateLevels(hierarchy);
52
53
  // Agrupar entidades por nível
@@ -55,12 +56,14 @@ function calculateHierarchicalPositions(data, hierarchy, nodeWidth, nodeHeight,
55
56
  if (!entitiesByLevel.has(level)) {
56
57
  entitiesByLevel.set(level, []);
57
58
  }
58
- entitiesByLevel.get(level).push(entityId);
59
+ entitiesByLevel.get(level)?.push(entityId);
59
60
  });
60
61
  // Calcular posições para cada nível, começando do nível 0 (raiz)
61
62
  const sortedLevels = Array.from(entitiesByLevel.keys()).sort((a, b) => a - b);
62
63
  sortedLevels.forEach((level) => {
63
64
  const entityIds = entitiesByLevel.get(level);
65
+ if (!entityIds)
66
+ return;
64
67
  // Ordenar entidades do mesmo nível (por ID para consistência)
65
68
  entityIds.sort();
66
69
  // Calcular posição Y baseada no nível (pais acima, filhos abaixo)
@@ -91,10 +94,13 @@ function calculateHierarchicalPositions(data, hierarchy, nodeWidth, nodeHeight,
91
94
  // Ajustar posições para evitar sobreposição
92
95
  entityIds.forEach((entityId, index) => {
93
96
  const currentPos = positions.get(entityId);
94
- const minX = currentPos.x;
97
+ if (!currentPos)
98
+ return;
95
99
  // Verificar se há sobreposição com outros nós do mesmo nível
96
100
  entityIds.slice(0, index).forEach((otherId) => {
97
101
  const otherPos = positions.get(otherId);
102
+ if (!otherPos)
103
+ return;
98
104
  if (Math.abs(currentPos.x - otherPos.x) < nodeWidth + horizontalSpacing) {
99
105
  // Ajustar para evitar sobreposição
100
106
  currentPos.x = otherPos.x + nodeWidth + horizontalSpacing;
@@ -0,0 +1,3 @@
1
+ export type { ExecuteScriptResponse, TerminalLine, UseScriptEditorOptions, UseScriptEditorReturn, } from "./useScriptEditor";
2
+ export { useScriptEditor } from "./useScriptEditor";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/useScriptEditor/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1 @@
1
+ export { useScriptEditor } from "./useScriptEditor";
@@ -0,0 +1,147 @@
1
+ /**
2
+ * useScriptEditor - Headless UI Hook
3
+ *
4
+ * Fornece apenas: lógica, estado, processamento e API
5
+ * NÃO fornece: markup, styles ou implementações pré-construídas
6
+ *
7
+ * Padrão Headless UI: você controla o markup e styles
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * function MyScriptEditor() {
12
+ * const {
13
+ * script,
14
+ * setScript,
15
+ * terminalLines,
16
+ * preview,
17
+ * isRunning,
18
+ * isConnected,
19
+ * executeScript,
20
+ * stopExecution,
21
+ * clearTerminal,
22
+ * } = useScriptEditor({
23
+ * wsUrl: "ws://localhost:8080/ws/terminal",
24
+ * executeScript: async (script) => {
25
+ * const response = await fetch("/api/scripts/execute", {
26
+ * method: "POST",
27
+ * body: JSON.stringify({ script }),
28
+ * });
29
+ * return response.json();
30
+ * },
31
+ * });
32
+ *
33
+ * return (
34
+ * <div>
35
+ * <textarea value={script} onChange={(e) => setScript(e.target.value)} />
36
+ * <button onClick={executeScript}>Executar</button>
37
+ * <div>
38
+ * {terminalLines.map((line) => (
39
+ * <div key={line.id}>{line.content}</div>
40
+ * ))}
41
+ * </div>
42
+ * </div>
43
+ * );
44
+ * }
45
+ * ```
46
+ */
47
+ export interface TerminalLine {
48
+ id: string;
49
+ type: "input" | "output" | "error" | "system";
50
+ content: string;
51
+ timestamp: Date;
52
+ }
53
+ export interface ExecuteScriptResponse {
54
+ output?: string;
55
+ error?: string;
56
+ result?: unknown;
57
+ }
58
+ export interface UseScriptEditorOptions {
59
+ /**
60
+ * URL do WebSocket para conexão em tempo real
61
+ * @default undefined
62
+ */
63
+ wsUrl?: string;
64
+ /**
65
+ * Função para executar script via HTTP (fallback quando WebSocket não está disponível)
66
+ * @default undefined
67
+ */
68
+ executeScript?: (script: string) => Promise<ExecuteScriptResponse>;
69
+ /**
70
+ * Callback quando uma linha é adicionada ao terminal
71
+ */
72
+ onOutput?: (line: TerminalLine) => void;
73
+ /**
74
+ * Callback quando ocorre um erro
75
+ */
76
+ onError?: (error: Error) => void;
77
+ /**
78
+ * Se deve conectar automaticamente ao WebSocket
79
+ * @default true
80
+ */
81
+ autoConnect?: boolean;
82
+ /**
83
+ * Número máximo de tentativas de reconexão
84
+ * @default 3
85
+ */
86
+ maxReconnectAttempts?: number;
87
+ /**
88
+ * Script inicial
89
+ * @default ""
90
+ */
91
+ defaultScript?: string;
92
+ }
93
+ export interface UseScriptEditorReturn {
94
+ /**
95
+ * Script atual
96
+ */
97
+ script: string;
98
+ /**
99
+ * Atualizar script
100
+ */
101
+ setScript: (script: string) => void;
102
+ /**
103
+ * Linhas do terminal
104
+ */
105
+ terminalLines: TerminalLine[];
106
+ /**
107
+ * Preview formatado (JSON)
108
+ */
109
+ preview: string;
110
+ /**
111
+ * Aba ativa (terminal ou preview)
112
+ */
113
+ activeTab: "terminal" | "preview";
114
+ /**
115
+ * Mudar aba ativa
116
+ */
117
+ setActiveTab: (tab: "terminal" | "preview") => void;
118
+ /**
119
+ * Se o script está sendo executado
120
+ */
121
+ isRunning: boolean;
122
+ /**
123
+ * Se o WebSocket está conectado
124
+ */
125
+ isConnected: boolean;
126
+ /**
127
+ * Executar script
128
+ */
129
+ executeScript: () => Promise<void>;
130
+ /**
131
+ * Parar execução
132
+ */
133
+ stopExecution: () => void;
134
+ /**
135
+ * Limpar terminal e preview
136
+ */
137
+ clearTerminal: () => void;
138
+ /**
139
+ * Adicionar linha ao terminal manualmente
140
+ */
141
+ addTerminalLine: (type: TerminalLine["type"], content: string) => void;
142
+ }
143
+ /**
144
+ * Hook headless para gerenciar editor de scripts com terminal interativo
145
+ */
146
+ export declare function useScriptEditor(options?: UseScriptEditorOptions): UseScriptEditorReturn;
147
+ //# sourceMappingURL=useScriptEditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useScriptEditor.d.ts","sourceRoot":"","sources":["../../../src/hooks/useScriptEditor/useScriptEditor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAIH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAEnE;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAExC;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAEpC;;OAEG;IACH,aAAa,EAAE,YAAY,EAAE,CAAC;IAE9B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,SAAS,EAAE,UAAU,GAAG,SAAS,CAAC;IAElC;;OAEG;IACH,YAAY,EAAE,CAAC,GAAG,EAAE,UAAU,GAAG,SAAS,KAAK,IAAI,CAAC;IAEpD;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC;IAErB;;OAEG;IACH,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC;;OAEG;IACH,aAAa,EAAE,MAAM,IAAI,CAAC;IAE1B;;OAEG;IACH,aAAa,EAAE,MAAM,IAAI,CAAC;IAE1B;;OAEG;IACH,eAAe,EAAE,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACxE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,qBAAqB,CAoO3F"}
@@ -0,0 +1,247 @@
1
+ /**
2
+ * useScriptEditor - Headless UI Hook
3
+ *
4
+ * Fornece apenas: lógica, estado, processamento e API
5
+ * NÃO fornece: markup, styles ou implementações pré-construídas
6
+ *
7
+ * Padrão Headless UI: você controla o markup e styles
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * function MyScriptEditor() {
12
+ * const {
13
+ * script,
14
+ * setScript,
15
+ * terminalLines,
16
+ * preview,
17
+ * isRunning,
18
+ * isConnected,
19
+ * executeScript,
20
+ * stopExecution,
21
+ * clearTerminal,
22
+ * } = useScriptEditor({
23
+ * wsUrl: "ws://localhost:8080/ws/terminal",
24
+ * executeScript: async (script) => {
25
+ * const response = await fetch("/api/scripts/execute", {
26
+ * method: "POST",
27
+ * body: JSON.stringify({ script }),
28
+ * });
29
+ * return response.json();
30
+ * },
31
+ * });
32
+ *
33
+ * return (
34
+ * <div>
35
+ * <textarea value={script} onChange={(e) => setScript(e.target.value)} />
36
+ * <button onClick={executeScript}>Executar</button>
37
+ * <div>
38
+ * {terminalLines.map((line) => (
39
+ * <div key={line.id}>{line.content}</div>
40
+ * ))}
41
+ * </div>
42
+ * </div>
43
+ * );
44
+ * }
45
+ * ```
46
+ */
47
+ import { useCallback, useEffect, useRef, useState } from "react";
48
+ /**
49
+ * Hook headless para gerenciar editor de scripts com terminal interativo
50
+ */
51
+ export function useScriptEditor(options = {}) {
52
+ const { wsUrl, executeScript: executeScriptFn, onOutput, onError, autoConnect = true, maxReconnectAttempts = 3, defaultScript = "", } = options;
53
+ const [script, setScript] = useState(defaultScript);
54
+ const [terminalLines, setTerminalLines] = useState([]);
55
+ const [preview, setPreview] = useState("");
56
+ const [activeTab, setActiveTab] = useState("terminal");
57
+ const [isRunning, setIsRunning] = useState(false);
58
+ const [isConnected, setIsConnected] = useState(false);
59
+ const wsRef = useRef(null);
60
+ const reconnectAttemptsRef = useRef(0);
61
+ const shouldReconnectRef = useRef(true);
62
+ // Adicionar linha ao terminal
63
+ const addTerminalLine = useCallback((type, content) => {
64
+ const line = {
65
+ id: `${Date.now()}-${Math.random()}`,
66
+ type,
67
+ content,
68
+ timestamp: new Date(),
69
+ };
70
+ setTerminalLines((prev) => [...prev, line]);
71
+ onOutput?.(line);
72
+ }, [onOutput]);
73
+ // Conectar WebSocket
74
+ const connectWebSocket = useCallback(() => {
75
+ if (!wsUrl)
76
+ return;
77
+ // Verificar se já existe uma conexão ativa
78
+ if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
79
+ return;
80
+ }
81
+ // Fechar conexão anterior se existir
82
+ if (wsRef.current) {
83
+ try {
84
+ wsRef.current.close();
85
+ }
86
+ catch {
87
+ // Ignorar erros ao fechar
88
+ }
89
+ wsRef.current = null;
90
+ }
91
+ if (!shouldReconnectRef.current)
92
+ return;
93
+ try {
94
+ const ws = new WebSocket(wsUrl);
95
+ wsRef.current = ws;
96
+ ws.onopen = () => {
97
+ setIsConnected(true);
98
+ reconnectAttemptsRef.current = 0;
99
+ };
100
+ ws.onmessage = (event) => {
101
+ try {
102
+ const data = JSON.parse(event.data);
103
+ if (data.type === "output") {
104
+ addTerminalLine("output", data.content);
105
+ }
106
+ else if (data.type === "error") {
107
+ addTerminalLine("error", data.content);
108
+ }
109
+ else if (data.type === "result") {
110
+ setPreview(JSON.stringify(data.content, null, 2));
111
+ setActiveTab("preview");
112
+ }
113
+ else if (data.type === "system") {
114
+ // Mensagens de sistema não são exibidas no terminal
115
+ console.log("[useScriptEditor] Mensagem de sistema:", data.content);
116
+ }
117
+ }
118
+ catch {
119
+ addTerminalLine("output", event.data);
120
+ }
121
+ };
122
+ ws.onerror = (error) => {
123
+ console.error("[useScriptEditor] WebSocket error:", error);
124
+ setIsConnected(false);
125
+ onError?.(new Error("Erro na conexão WebSocket"));
126
+ };
127
+ ws.onclose = (event) => {
128
+ setIsConnected(false);
129
+ wsRef.current = null;
130
+ // Tentar reconectar apenas se não excedeu tentativas e ainda deve reconectar
131
+ if (event.code !== 1000 &&
132
+ shouldReconnectRef.current &&
133
+ reconnectAttemptsRef.current < maxReconnectAttempts) {
134
+ reconnectAttemptsRef.current += 1;
135
+ setTimeout(() => {
136
+ if (shouldReconnectRef.current) {
137
+ connectWebSocket();
138
+ }
139
+ }, 3000);
140
+ }
141
+ else if (reconnectAttemptsRef.current >= maxReconnectAttempts) {
142
+ console.warn("[useScriptEditor] Máximo de tentativas de reconexão atingido");
143
+ }
144
+ };
145
+ }
146
+ catch (error) {
147
+ console.error("[useScriptEditor] Erro ao conectar WebSocket:", error);
148
+ setIsConnected(false);
149
+ onError?.(error instanceof Error ? error : new Error("Erro desconhecido ao conectar"));
150
+ }
151
+ }, [wsUrl, maxReconnectAttempts, addTerminalLine, onError]);
152
+ // Conectar WebSocket quando montar (se autoConnect)
153
+ useEffect(() => {
154
+ if (autoConnect && wsUrl) {
155
+ const connectTimeout = setTimeout(() => {
156
+ connectWebSocket();
157
+ }, 100);
158
+ return () => {
159
+ clearTimeout(connectTimeout);
160
+ };
161
+ }
162
+ }, [autoConnect, wsUrl, connectWebSocket]);
163
+ // Limpar conexão ao desmontar
164
+ useEffect(() => {
165
+ return () => {
166
+ shouldReconnectRef.current = false;
167
+ if (wsRef.current) {
168
+ try {
169
+ wsRef.current.close(1000, "Component unmounting");
170
+ }
171
+ catch {
172
+ // Ignorar erros ao fechar
173
+ }
174
+ wsRef.current = null;
175
+ }
176
+ };
177
+ }, []);
178
+ // Executar script
179
+ const executeScript = useCallback(async () => {
180
+ if (!script.trim()) {
181
+ addTerminalLine("error", "❌ Script vazio");
182
+ return;
183
+ }
184
+ setIsRunning(true);
185
+ addTerminalLine("input", script);
186
+ try {
187
+ // Enviar script via WebSocket se conectado
188
+ if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
189
+ wsRef.current.send(JSON.stringify({ type: "execute", script }));
190
+ }
191
+ else if (executeScriptFn) {
192
+ // Fallback para HTTP se WebSocket não estiver disponível
193
+ try {
194
+ const result = await executeScriptFn(script);
195
+ if (result.output) {
196
+ addTerminalLine("output", result.output);
197
+ }
198
+ if (result.error) {
199
+ addTerminalLine("error", result.error);
200
+ }
201
+ if (result.result) {
202
+ setPreview(JSON.stringify(result.result, null, 2));
203
+ setActiveTab("preview");
204
+ }
205
+ }
206
+ catch (error) {
207
+ addTerminalLine("error", `❌ Erro na requisição: ${error instanceof Error ? error.message : "Erro desconhecido"}`);
208
+ }
209
+ }
210
+ else {
211
+ addTerminalLine("error", "❌ Nenhuma forma de execução disponível (WebSocket ou HTTP)");
212
+ }
213
+ }
214
+ catch (error) {
215
+ addTerminalLine("error", `❌ Erro ao executar: ${error instanceof Error ? error.message : "Erro desconhecido"}`);
216
+ }
217
+ finally {
218
+ setIsRunning(false);
219
+ }
220
+ }, [script, executeScriptFn, addTerminalLine]);
221
+ // Parar execução
222
+ const stopExecution = useCallback(() => {
223
+ if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
224
+ wsRef.current.send(JSON.stringify({ type: "stop" }));
225
+ }
226
+ setIsRunning(false);
227
+ }, []);
228
+ // Limpar terminal
229
+ const clearTerminal = useCallback(() => {
230
+ setTerminalLines([]);
231
+ setPreview("");
232
+ }, []);
233
+ return {
234
+ script,
235
+ setScript,
236
+ terminalLines,
237
+ preview,
238
+ activeTab,
239
+ setActiveTab,
240
+ isRunning,
241
+ isConnected,
242
+ executeScript,
243
+ stopExecution,
244
+ clearTerminal,
245
+ addTerminalLine,
246
+ };
247
+ }
package/dist/index.d.ts CHANGED
@@ -12,6 +12,8 @@ export * from "./hooks/useProjectStats";
12
12
  export * from "./hooks/useReactTableBack";
13
13
  export * from "./hooks/useReactTableFront";
14
14
  export * from "./hooks/useResizable";
15
+ export * from "./hooks/useScriptEditor";
15
16
  export * from "./hooks/useStatCard";
16
17
  export * from "./hooks/useTimeTracker";
18
+ export * from "./hooks/useAutocomplete";
17
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AAErC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AAErC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC"}