@craftile/core 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/LICENSE.md +21 -0
- package/package.json +49 -0
- package/src/blocks-manager.ts +115 -0
- package/src/commands/duplicate-block.ts +203 -0
- package/src/commands/insert-block.ts +143 -0
- package/src/commands/move-block.ts +182 -0
- package/src/commands/remove-block.ts +94 -0
- package/src/commands/set-block-property.ts +84 -0
- package/src/commands/toggle-block.ts +76 -0
- package/src/engine.ts +329 -0
- package/src/history-manager.ts +78 -0
- package/src/index.ts +3 -0
- package/src/types.ts +46 -0
- package/src/utils.ts +7 -0
- package/src/vue/index.ts +6 -0
- package/src/vue/useBlocksEngine.ts +307 -0
package/src/utils.ts
ADDED
package/src/vue/index.ts
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { ref, onUnmounted, computed, toRaw, nextTick, type Ref } from 'vue';
|
|
2
|
+
import type { Block, Page, Region } from '@craftile/types';
|
|
3
|
+
import type { EngineConfig } from '../types';
|
|
4
|
+
import { Engine } from '../engine';
|
|
5
|
+
|
|
6
|
+
export interface UseBlocksEngineOptions extends EngineConfig {
|
|
7
|
+
autoSync?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface UseBlocksEngineFromInstanceOptions {
|
|
11
|
+
autoSync?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface UseBlocksEngineReturn {
|
|
15
|
+
engine: Engine;
|
|
16
|
+
|
|
17
|
+
page: Ref<Page>;
|
|
18
|
+
blocks: Ref<Record<string, Block>>;
|
|
19
|
+
regions: Ref<Region[]>;
|
|
20
|
+
|
|
21
|
+
rootLevelBlocks: Ref<Block[]>;
|
|
22
|
+
|
|
23
|
+
getBlockById: (id: string) => Block | undefined;
|
|
24
|
+
getBlocksByType: (type: string) => Block[];
|
|
25
|
+
getChildrenBlocks: (parentId: string) => Block[];
|
|
26
|
+
|
|
27
|
+
insertBlock: (
|
|
28
|
+
blockType: string,
|
|
29
|
+
options?: {
|
|
30
|
+
parentId?: string;
|
|
31
|
+
regionName?: string;
|
|
32
|
+
index?: number;
|
|
33
|
+
}
|
|
34
|
+
) => string;
|
|
35
|
+
removeBlock: (blockId: string) => void;
|
|
36
|
+
moveBlock: (
|
|
37
|
+
blockId: string,
|
|
38
|
+
options: {
|
|
39
|
+
targetParentId?: string;
|
|
40
|
+
targetIndex?: number;
|
|
41
|
+
targetRegionName?: string;
|
|
42
|
+
}
|
|
43
|
+
) => void;
|
|
44
|
+
setBlockProperty: (blockId: string, propertyKey: string, propertyValue: any) => void;
|
|
45
|
+
toggleBlock: (blockId: string, disabled?: boolean) => void;
|
|
46
|
+
duplicateBlock: (blockId: string) => string;
|
|
47
|
+
|
|
48
|
+
undo: () => boolean;
|
|
49
|
+
redo: () => boolean;
|
|
50
|
+
canUndo: Ref<boolean>;
|
|
51
|
+
canRedo: Ref<boolean>;
|
|
52
|
+
|
|
53
|
+
refresh: () => void;
|
|
54
|
+
destroy: () => void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Vue composable for reactive integration with Blocks Engine
|
|
59
|
+
*
|
|
60
|
+
* @param options Configuration options for the engine and composable
|
|
61
|
+
* @returns Reactive interface to the blocks engine
|
|
62
|
+
*/
|
|
63
|
+
export function useBlocksEngine(options?: UseBlocksEngineOptions): UseBlocksEngineReturn;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Vue composable for reactive integration with existing Blocks Engine
|
|
67
|
+
*
|
|
68
|
+
* @param engine Existing engine instance to wrap with reactive features
|
|
69
|
+
* @param options Options for the composable behavior
|
|
70
|
+
* @returns Reactive interface to the blocks engine
|
|
71
|
+
*/
|
|
72
|
+
export function useBlocksEngine(engine: Engine, options?: UseBlocksEngineFromInstanceOptions): UseBlocksEngineReturn;
|
|
73
|
+
|
|
74
|
+
export function useBlocksEngine(
|
|
75
|
+
configOrEngine?: UseBlocksEngineOptions | Engine,
|
|
76
|
+
instanceOptions?: UseBlocksEngineFromInstanceOptions
|
|
77
|
+
): UseBlocksEngineReturn {
|
|
78
|
+
const isEngineInstance = configOrEngine instanceof Engine;
|
|
79
|
+
let engine: Engine;
|
|
80
|
+
let autoSync: boolean;
|
|
81
|
+
|
|
82
|
+
if (isEngineInstance) {
|
|
83
|
+
engine = configOrEngine;
|
|
84
|
+
autoSync = instanceOptions?.autoSync ?? true;
|
|
85
|
+
} else {
|
|
86
|
+
const config = configOrEngine || {};
|
|
87
|
+
const { autoSync: configAutoSync = true, ...engineConfig } = config;
|
|
88
|
+
engine = new Engine(engineConfig);
|
|
89
|
+
autoSync = configAutoSync;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const pageState = ref<Page>({
|
|
93
|
+
blocks: {},
|
|
94
|
+
regions: [{ name: 'main', blocks: [] }],
|
|
95
|
+
});
|
|
96
|
+
const blocks = computed(() => pageState.value.blocks);
|
|
97
|
+
const regions = computed(() => pageState.value.regions);
|
|
98
|
+
|
|
99
|
+
const canUndo = ref(engine.canUndo());
|
|
100
|
+
const canRedo = ref(engine.canRedo());
|
|
101
|
+
|
|
102
|
+
const rootLevelBlocks = computed(() => {
|
|
103
|
+
const rootLevel: Block[] = [];
|
|
104
|
+
|
|
105
|
+
for (const region of regions.value!) {
|
|
106
|
+
for (const blockId of region.blocks) {
|
|
107
|
+
const block = blocks.value[blockId];
|
|
108
|
+
|
|
109
|
+
if (block) {
|
|
110
|
+
rootLevel.push(block);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return rootLevel;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const getBlockById = (id: string): Block | undefined => {
|
|
119
|
+
return blocks.value[id];
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const getBlocksByType = (type: string): Block[] => {
|
|
123
|
+
return Object.values(blocks.value).filter((block: Block) => {
|
|
124
|
+
return block.type === type;
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const getChildrenBlocks = (parentId: string): Block[] => {
|
|
129
|
+
const parent = blocks.value[parentId];
|
|
130
|
+
|
|
131
|
+
if (!parent) {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return parent.children.map((childId: string) => blocks.value[childId]).filter(Boolean) as Block[];
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const syncStateFromEngine = () => {
|
|
139
|
+
const rawPage = toRaw(engine.getPage());
|
|
140
|
+
pageState.value = rawPage;
|
|
141
|
+
|
|
142
|
+
// Defer history state updates to next tick to ensure command is added to history first
|
|
143
|
+
nextTick(() => {
|
|
144
|
+
canUndo.value = engine.canUndo();
|
|
145
|
+
canRedo.value = engine.canRedo();
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
syncStateFromEngine();
|
|
150
|
+
|
|
151
|
+
const eventCleanups: Array<() => void> = [];
|
|
152
|
+
|
|
153
|
+
if (autoSync) {
|
|
154
|
+
const eventTypes = [
|
|
155
|
+
'page:set',
|
|
156
|
+
'block:insert',
|
|
157
|
+
'block:remove',
|
|
158
|
+
'block:move',
|
|
159
|
+
'block:toggle',
|
|
160
|
+
'block:duplicate',
|
|
161
|
+
'block:property:set',
|
|
162
|
+
'undo',
|
|
163
|
+
'redo',
|
|
164
|
+
] as const;
|
|
165
|
+
|
|
166
|
+
eventTypes.forEach((eventType) => {
|
|
167
|
+
const cleanup = engine.on(eventType, () => {
|
|
168
|
+
syncStateFromEngine();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
eventCleanups.push(cleanup);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const insertBlock = (
|
|
176
|
+
blockType: string,
|
|
177
|
+
options?: {
|
|
178
|
+
parentId?: string;
|
|
179
|
+
regionName?: string;
|
|
180
|
+
index?: number;
|
|
181
|
+
}
|
|
182
|
+
): string => {
|
|
183
|
+
const blockId = engine.insertBlock(blockType, options);
|
|
184
|
+
|
|
185
|
+
if (!autoSync) {
|
|
186
|
+
syncStateFromEngine();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return blockId;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const removeBlock = (blockId: string): void => {
|
|
193
|
+
engine.removeBlock(blockId);
|
|
194
|
+
|
|
195
|
+
if (!autoSync) {
|
|
196
|
+
syncStateFromEngine();
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const moveBlock = (
|
|
201
|
+
blockId: string,
|
|
202
|
+
options: {
|
|
203
|
+
targetParentId?: string;
|
|
204
|
+
targetIndex?: number;
|
|
205
|
+
targetRegionName?: string;
|
|
206
|
+
}
|
|
207
|
+
): void => {
|
|
208
|
+
engine.moveBlock(blockId, options);
|
|
209
|
+
|
|
210
|
+
if (!autoSync) {
|
|
211
|
+
syncStateFromEngine();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const setBlockProperty = (blockId: string, propertyKey: string, propertyValue: any): void => {
|
|
216
|
+
engine.setBlockProperty(blockId, propertyKey, propertyValue);
|
|
217
|
+
|
|
218
|
+
if (!autoSync) {
|
|
219
|
+
syncStateFromEngine();
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const toggleBlock = (blockId: string, disabled?: boolean): void => {
|
|
224
|
+
engine.toggleBlock(blockId, disabled);
|
|
225
|
+
|
|
226
|
+
if (!autoSync) {
|
|
227
|
+
syncStateFromEngine();
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const duplicateBlock = (blockId: string): string => {
|
|
232
|
+
const newBlockId = engine.duplicateBlock(blockId);
|
|
233
|
+
|
|
234
|
+
if (!autoSync) {
|
|
235
|
+
syncStateFromEngine();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return newBlockId;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const undo = (): boolean => {
|
|
242
|
+
const result = engine.undo();
|
|
243
|
+
|
|
244
|
+
if (!autoSync) {
|
|
245
|
+
syncStateFromEngine();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return result;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const redo = (): boolean => {
|
|
252
|
+
const result = engine.redo();
|
|
253
|
+
|
|
254
|
+
if (!autoSync) {
|
|
255
|
+
syncStateFromEngine();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return result;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const refresh = () => {
|
|
262
|
+
syncStateFromEngine();
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const destroy = () => {
|
|
266
|
+
eventCleanups.forEach((cleanup) => cleanup());
|
|
267
|
+
eventCleanups.length = 0;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
onUnmounted(() => {
|
|
271
|
+
destroy();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
// Reactive state
|
|
276
|
+
page: pageState,
|
|
277
|
+
blocks,
|
|
278
|
+
regions,
|
|
279
|
+
|
|
280
|
+
// Engine instance
|
|
281
|
+
engine,
|
|
282
|
+
|
|
283
|
+
// Computed helpers
|
|
284
|
+
rootLevelBlocks,
|
|
285
|
+
getBlockById,
|
|
286
|
+
getBlocksByType,
|
|
287
|
+
getChildrenBlocks,
|
|
288
|
+
|
|
289
|
+
// Command methods
|
|
290
|
+
insertBlock,
|
|
291
|
+
removeBlock,
|
|
292
|
+
moveBlock,
|
|
293
|
+
setBlockProperty,
|
|
294
|
+
toggleBlock,
|
|
295
|
+
duplicateBlock,
|
|
296
|
+
|
|
297
|
+
// History methods
|
|
298
|
+
undo,
|
|
299
|
+
redo,
|
|
300
|
+
canUndo,
|
|
301
|
+
canRedo,
|
|
302
|
+
|
|
303
|
+
// Utility methods
|
|
304
|
+
refresh,
|
|
305
|
+
destroy,
|
|
306
|
+
};
|
|
307
|
+
}
|