@1urso/generic-editor 0.1.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/README.md +216 -0
- package/dist/App.d.ts +2 -0
- package/dist/editor/components/Canvas.d.ts +5 -0
- package/dist/editor/components/EditorSettings.d.ts +2 -0
- package/dist/editor/components/ElementContextMenu.d.ts +6 -0
- package/dist/editor/components/ExportDialog.d.ts +4 -0
- package/dist/editor/components/Preview.d.ts +2 -0
- package/dist/editor/context.d.ts +49 -0
- package/dist/editor/index.d.ts +9 -0
- package/dist/editor/types.d.ts +10 -0
- package/dist/editor/utils/htmlGenerator.d.ts +8 -0
- package/dist/generic-editor.css +2 -0
- package/dist/generic-editor.js +13712 -0
- package/dist/generic-editor.umd.cjs +209 -0
- package/dist/index.d.ts +4 -0
- package/dist/main.d.ts +0 -0
- package/dist/vite.svg +1 -0
- package/package.json +75 -0
package/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Generic Editor
|
|
2
|
+
|
|
3
|
+
Uma biblioteca React poderosa e agnóstica de framework para criação de layouts dinâmicos, geração de templates e edição visual. Projetada para ser integrada em qualquer aplicação React (Web, Electron, Tauri, Next.js, etc.).
|
|
4
|
+
|
|
5
|
+
## Características
|
|
6
|
+
|
|
7
|
+
- **Editor Visual Drag & Drop**: Posicionamento livre, redimensionamento e rotação de elementos.
|
|
8
|
+
- **Data Binding**: Suporte a variáveis dinâmicas (ex: `{{produto.nome}}`) para geração de templates.
|
|
9
|
+
- **Framework Agnostic**: Funciona em qualquer ambiente React.
|
|
10
|
+
- **JSON Based**: Entrada e saída puramente em JSON, facilitando persistência e integração com backends.
|
|
11
|
+
- **Socket Ready**: Projetado para suportar atualizações em tempo real via WebSockets.
|
|
12
|
+
- **Preview em Tempo Real**: Visualização instantânea de como o layout ficará renderizado.
|
|
13
|
+
|
|
14
|
+
## Instalação
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install generic-editor
|
|
18
|
+
# ou
|
|
19
|
+
yarn add generic-editor
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Peer Dependencies
|
|
23
|
+
|
|
24
|
+
Certifique-se de ter as seguintes dependências instaladas em seu projeto, pois o editor depende delas para UI e funcionalidades:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @radix-ui/themes @radix-ui/react-icons react-resizable-panels re-resizable framer-motion @dnd-kit/core
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
E não esqueça de importar os estilos do Radix UI no topo da sua aplicação:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import '@radix-ui/themes/styles.css';
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Como Usar
|
|
37
|
+
|
|
38
|
+
### Exemplo Básico
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import React, { useState } from 'react';
|
|
42
|
+
import { EditorContent } from 'generic-editor';
|
|
43
|
+
|
|
44
|
+
const MyPage = () => {
|
|
45
|
+
// Configuração das variáveis disponíveis para o usuário arrastar/usar
|
|
46
|
+
const layoutConfig = {
|
|
47
|
+
props: [
|
|
48
|
+
{ name: 'Nome do Produto', dataName: 'productName' },
|
|
49
|
+
{ name: 'Preço', dataName: 'price' },
|
|
50
|
+
{ name: 'Imagem URL', dataName: 'imageUrl' }
|
|
51
|
+
]
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleSave = (jsonState: string) => {
|
|
55
|
+
console.log("Layout salvo:", jsonState);
|
|
56
|
+
// Envie para sua API, salve em arquivo, etc.
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div style={{ height: '100vh' }}>
|
|
61
|
+
<EditorContent
|
|
62
|
+
layout={layoutConfig}
|
|
63
|
+
onSave={handleSave}
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## API Reference
|
|
71
|
+
|
|
72
|
+
### `<EditorContent />`
|
|
73
|
+
|
|
74
|
+
Componente principal do editor.
|
|
75
|
+
|
|
76
|
+
| Prop | Tipo | Obrigatório | Descrição |
|
|
77
|
+
|------|------|-------------|-----------|
|
|
78
|
+
| `layout` | `ILayout` | Sim | Configuração das variáveis disponíveis para data-binding. |
|
|
79
|
+
| `initialState` | `any` (JSON string ou Objeto) | Não | Estado inicial do editor. Útil para carregar layouts salvos ou atualizações via socket. |
|
|
80
|
+
| `onSave` | `(json: string) => void` | Não | Callback disparado quando o usuário clica em "Salvar". Retorna o estado completo em JSON. |
|
|
81
|
+
|
|
82
|
+
### Interfaces
|
|
83
|
+
|
|
84
|
+
#### `ILayout`
|
|
85
|
+
|
|
86
|
+
Define as propriedades disponíveis para o usuário utilizar no layout (Data Binding).
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
interface ILayout {
|
|
90
|
+
props: IProp[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface IProp {
|
|
94
|
+
name: string; // Nome visível para o usuário (ex: "Nome do Cliente")
|
|
95
|
+
dataName: string; // Chave da variável no JSON de dados (ex: "customerName") -> gera {{customerName}}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Estrutura de Dados (JSON)
|
|
100
|
+
|
|
101
|
+
O editor exporta e importa um JSON com a seguinte estrutura:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"elements": [
|
|
106
|
+
{
|
|
107
|
+
"id": "uuid-1234",
|
|
108
|
+
"type": "text", // ou 'image', 'box'
|
|
109
|
+
"content": "Olá {{name}}",
|
|
110
|
+
"x": 50,
|
|
111
|
+
"y": 100,
|
|
112
|
+
"width": 200,
|
|
113
|
+
"height": 50,
|
|
114
|
+
"rotation": 0,
|
|
115
|
+
"style": { "color": "#000000" }
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"listSettings": { ... },
|
|
119
|
+
"mockData": [ ... ],
|
|
120
|
+
"isList": false
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Guias de Integração
|
|
125
|
+
|
|
126
|
+
### 1. Integração com WebSockets (Real-time)
|
|
127
|
+
|
|
128
|
+
O editor reage a mudanças na prop `initialState`. Isso permite que você conecte o editor a um WebSocket e atualize o layout em tempo real quando outro usuário fizer alterações (colaboração básica) ou carregar dados remotamente.
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import React, { useEffect, useState } from 'react';
|
|
132
|
+
import { EditorContent } from 'generic-editor';
|
|
133
|
+
import { io } from 'socket.io-client';
|
|
134
|
+
|
|
135
|
+
const socket = io('http://localhost:3000');
|
|
136
|
+
|
|
137
|
+
const SocketEditor = () => {
|
|
138
|
+
const [remoteState, setRemoteState] = useState(null);
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
// Escuta atualizações do servidor
|
|
142
|
+
socket.on('layout-update', (data) => {
|
|
143
|
+
setRemoteState(data);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return () => socket.off('layout-update');
|
|
147
|
+
}, []);
|
|
148
|
+
|
|
149
|
+
const handleSave = (jsonState) => {
|
|
150
|
+
// Envia alterações para o servidor
|
|
151
|
+
socket.emit('save-layout', jsonState);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<EditorContent
|
|
156
|
+
layout={{ props: [] }}
|
|
157
|
+
initialState={remoteState} // O editor atualizará quando remoteState mudar
|
|
158
|
+
onSave={handleSave}
|
|
159
|
+
/>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
> **Nota**: O editor faz um "merge" inteligente ao receber `initialState`, mas para colaboração em tempo real (estilo Google Docs), recomenda-se gerenciar conflitos no backend ou usar bibliotecas como Yjs, passando apenas o estado final consolidado para o `initialState`.
|
|
165
|
+
|
|
166
|
+
### 2. Integração com Electron / Tauri (File System)
|
|
167
|
+
|
|
168
|
+
Para aplicativos desktop, você pode usar o `onSave` para escrever diretamente no disco.
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
// Exemplo Tauri / Electron
|
|
172
|
+
import { writeFile, readTextFile } from '@tauri-apps/api/fs'; // ou 'fs' do Node no Electron
|
|
173
|
+
|
|
174
|
+
const DesktopEditor = () => {
|
|
175
|
+
const [fileContent, setFileContent] = useState(null);
|
|
176
|
+
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
// Carregar arquivo ao abrir
|
|
179
|
+
readTextFile('path/to/layout.json').then(content => {
|
|
180
|
+
setFileContent(content);
|
|
181
|
+
});
|
|
182
|
+
}, []);
|
|
183
|
+
|
|
184
|
+
const handleSave = async (jsonState) => {
|
|
185
|
+
await writeFile({
|
|
186
|
+
path: 'path/to/layout.json',
|
|
187
|
+
contents: jsonState
|
|
188
|
+
});
|
|
189
|
+
alert('Salvo com sucesso no disco!');
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<EditorContent
|
|
194
|
+
layout={{ props: [] }}
|
|
195
|
+
initialState={fileContent}
|
|
196
|
+
onSave={handleSave}
|
|
197
|
+
/>
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### 3. Integração com Backend (REST API)
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
const CloudEditor = () => {
|
|
206
|
+
const handleSave = async (jsonState) => {
|
|
207
|
+
await fetch('/api/layouts/123', {
|
|
208
|
+
method: 'PUT',
|
|
209
|
+
headers: { 'Content-Type': 'application/json' },
|
|
210
|
+
body: jsonState // Envia o JSON completo
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
return <EditorContent layout={...} onSave={handleSave} />;
|
|
215
|
+
};
|
|
216
|
+
```
|
package/dist/App.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { default as React, ReactNode } from 'react';
|
|
2
|
+
export interface IElement {
|
|
3
|
+
id: string;
|
|
4
|
+
type: 'text' | 'image' | 'box';
|
|
5
|
+
content: string;
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
rotation?: number;
|
|
11
|
+
style?: React.CSSProperties;
|
|
12
|
+
dataBinding?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface IListSettings {
|
|
15
|
+
sortProp?: string;
|
|
16
|
+
sortOrder: 'asc' | 'desc';
|
|
17
|
+
}
|
|
18
|
+
export interface IProp {
|
|
19
|
+
name: string;
|
|
20
|
+
dataName: string;
|
|
21
|
+
}
|
|
22
|
+
interface IEditorState {
|
|
23
|
+
elements: IElement[];
|
|
24
|
+
selectedElementId: string | null;
|
|
25
|
+
isList: boolean;
|
|
26
|
+
mockData: any[];
|
|
27
|
+
singleMockData: Record<string, any>;
|
|
28
|
+
listSettings: IListSettings;
|
|
29
|
+
availableProps: IProp[];
|
|
30
|
+
availableFonts: string[];
|
|
31
|
+
}
|
|
32
|
+
interface IEditorContext {
|
|
33
|
+
state: IEditorState;
|
|
34
|
+
addElement: (element: Omit<IElement, 'id' | 'x' | 'y' | 'width' | 'height'> & Partial<Pick<IElement, 'x' | 'y' | 'width' | 'height'>>) => void;
|
|
35
|
+
removeElement: (id: string) => void;
|
|
36
|
+
selectElement: (id: string | null) => void;
|
|
37
|
+
moveElement: (dragIndex: number, hoverIndex: number) => void;
|
|
38
|
+
updateElement: (id: string, updates: Partial<IElement>) => void;
|
|
39
|
+
setMockData: (data: any[], singleData: Record<string, any>) => void;
|
|
40
|
+
updateListSettings: (settings: Partial<IListSettings>) => void;
|
|
41
|
+
loadState: (savedState: Partial<IEditorState>) => void;
|
|
42
|
+
}
|
|
43
|
+
export declare const EditorProvider: React.FC<{
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
isList?: boolean;
|
|
46
|
+
availableProps?: IProp[];
|
|
47
|
+
}>;
|
|
48
|
+
export declare const useEditor: () => IEditorContext;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { IElement, IListSettings } from '../context';
|
|
2
|
+
interface RenderOptions {
|
|
3
|
+
isList?: boolean;
|
|
4
|
+
listSettings?: IListSettings;
|
|
5
|
+
}
|
|
6
|
+
export declare const generateHTML: (elements: IElement[], data: any, options?: RenderOptions) => string;
|
|
7
|
+
export declare const getRendererCode: () => string;
|
|
8
|
+
export {};
|