active_canvas 0.0.1
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +318 -0
- data/Rakefile +6 -0
- data/app/assets/javascripts/active_canvas/editor/ai_panel.js +1607 -0
- data/app/assets/javascripts/active_canvas/editor/asset_manager.js +498 -0
- data/app/assets/javascripts/active_canvas/editor/blocks.js +1083 -0
- data/app/assets/javascripts/active_canvas/editor/code_panel.js +572 -0
- data/app/assets/javascripts/active_canvas/editor/component_toolbar.js +394 -0
- data/app/assets/javascripts/active_canvas/editor/panels.js +460 -0
- data/app/assets/javascripts/active_canvas/editor/utils.js +56 -0
- data/app/assets/javascripts/active_canvas/editor.js +295 -0
- data/app/assets/stylesheets/active_canvas/application.css +15 -0
- data/app/assets/stylesheets/active_canvas/editor.css +2929 -0
- data/app/controllers/active_canvas/admin/ai_controller.rb +181 -0
- data/app/controllers/active_canvas/admin/application_controller.rb +56 -0
- data/app/controllers/active_canvas/admin/media_controller.rb +61 -0
- data/app/controllers/active_canvas/admin/page_types_controller.rb +57 -0
- data/app/controllers/active_canvas/admin/page_versions_controller.rb +23 -0
- data/app/controllers/active_canvas/admin/pages_controller.rb +133 -0
- data/app/controllers/active_canvas/admin/partials_controller.rb +88 -0
- data/app/controllers/active_canvas/admin/settings_controller.rb +256 -0
- data/app/controllers/active_canvas/application_controller.rb +20 -0
- data/app/controllers/active_canvas/pages_controller.rb +18 -0
- data/app/controllers/concerns/active_canvas/current_user.rb +12 -0
- data/app/controllers/concerns/active_canvas/rate_limitable.rb +75 -0
- data/app/controllers/concerns/active_canvas/tailwind_compilation.rb +39 -0
- data/app/helpers/active_canvas/application_helper.rb +4 -0
- data/app/jobs/active_canvas/application_job.rb +4 -0
- data/app/jobs/active_canvas/compile_tailwind_job.rb +64 -0
- data/app/mailers/active_canvas/application_mailer.rb +6 -0
- data/app/models/active_canvas/ai_model.rb +136 -0
- data/app/models/active_canvas/application_record.rb +5 -0
- data/app/models/active_canvas/media.rb +141 -0
- data/app/models/active_canvas/page.rb +85 -0
- data/app/models/active_canvas/page_type.rb +22 -0
- data/app/models/active_canvas/page_version.rb +80 -0
- data/app/models/active_canvas/partial.rb +73 -0
- data/app/models/active_canvas/setting.rb +292 -0
- data/app/services/active_canvas/ai_configuration.rb +40 -0
- data/app/services/active_canvas/ai_models.rb +128 -0
- data/app/services/active_canvas/ai_service.rb +289 -0
- data/app/services/active_canvas/content_sanitizer.rb +112 -0
- data/app/services/active_canvas/tailwind_compiler.rb +156 -0
- data/app/views/active_canvas/admin/media/index.html.erb +401 -0
- data/app/views/active_canvas/admin/media/show.html.erb +297 -0
- data/app/views/active_canvas/admin/page_types/_form.html.erb +25 -0
- data/app/views/active_canvas/admin/page_types/edit.html.erb +13 -0
- data/app/views/active_canvas/admin/page_types/index.html.erb +29 -0
- data/app/views/active_canvas/admin/page_types/new.html.erb +9 -0
- data/app/views/active_canvas/admin/page_types/show.html.erb +18 -0
- data/app/views/active_canvas/admin/page_versions/show.html.erb +469 -0
- data/app/views/active_canvas/admin/pages/_form.html.erb +62 -0
- data/app/views/active_canvas/admin/pages/content.html.erb +139 -0
- data/app/views/active_canvas/admin/pages/edit.html.erb +335 -0
- data/app/views/active_canvas/admin/pages/editor.html.erb +710 -0
- data/app/views/active_canvas/admin/pages/index.html.erb +149 -0
- data/app/views/active_canvas/admin/pages/new.html.erb +19 -0
- data/app/views/active_canvas/admin/pages/show.html.erb +258 -0
- data/app/views/active_canvas/admin/pages/versions.html.erb +333 -0
- data/app/views/active_canvas/admin/partials/edit.html.erb +182 -0
- data/app/views/active_canvas/admin/partials/editor.html.erb +703 -0
- data/app/views/active_canvas/admin/partials/index.html.erb +131 -0
- data/app/views/active_canvas/admin/settings/show.html.erb +1864 -0
- data/app/views/active_canvas/pages/no_homepage.html.erb +45 -0
- data/app/views/active_canvas/pages/show.html.erb +113 -0
- data/app/views/layouts/active_canvas/admin/application.html.erb +960 -0
- data/app/views/layouts/active_canvas/admin/editor.html.erb +826 -0
- data/app/views/layouts/active_canvas/application.html.erb +55 -0
- data/config/routes.rb +48 -0
- data/db/migrate/20260202000001_create_active_canvas_tables.rb +113 -0
- data/db/migrate/20260202000002_create_active_canvas_ai_models.rb +26 -0
- data/lib/active_canvas/configuration.rb +232 -0
- data/lib/active_canvas/engine.rb +44 -0
- data/lib/active_canvas/version.rb +3 -0
- data/lib/active_canvas.rb +26 -0
- data/lib/generators/active_canvas/install/install_generator.rb +263 -0
- data/lib/generators/active_canvas/install/templates/initializer.rb.tt +163 -0
- data/lib/tasks/active_canvas_tasks.rake +69 -0
- metadata +150 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ActiveCanvas Editor - Component Toolbar and Context Menu
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
(function() {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
window.ActiveCanvasEditor = window.ActiveCanvasEditor || {};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Setup component code editing and toolbar
|
|
12
|
+
* @param {Object} editor - GrapeJS editor instance
|
|
13
|
+
*/
|
|
14
|
+
function setupComponentToolbar(editor) {
|
|
15
|
+
const { showToast } = window.ActiveCanvasEditor;
|
|
16
|
+
|
|
17
|
+
// Define default toolbar for all components
|
|
18
|
+
const defaultToolbar = [
|
|
19
|
+
{
|
|
20
|
+
id: 'edit-code',
|
|
21
|
+
label: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>',
|
|
22
|
+
attributes: { class: 'toolbar-icon-btn', title: 'Edit Code' },
|
|
23
|
+
command: 'edit-component-code'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'edit-ai',
|
|
27
|
+
label: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/></svg>',
|
|
28
|
+
attributes: { class: 'toolbar-icon-btn toolbar-ai-btn', title: 'Edit with AI' },
|
|
29
|
+
command: 'edit-component-ai'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'component-menu',
|
|
33
|
+
label: '⋮',
|
|
34
|
+
attributes: { class: 'toolbar-menu-btn', title: 'More Actions' },
|
|
35
|
+
command: 'ac-show-menu'
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// Add toolbar buttons on component selection
|
|
40
|
+
editor.on('component:selected', (component) => {
|
|
41
|
+
if (!component) return;
|
|
42
|
+
|
|
43
|
+
let toolbar = component.get('toolbar') || [];
|
|
44
|
+
|
|
45
|
+
// Ensure toolbar is an array and make a copy
|
|
46
|
+
if (!Array.isArray(toolbar)) {
|
|
47
|
+
toolbar = [];
|
|
48
|
+
} else {
|
|
49
|
+
toolbar = [...toolbar];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check if our buttons already exist
|
|
53
|
+
const hasCodeBtn = toolbar.some(item => item.id === 'edit-code' || item.command === 'edit-component-code');
|
|
54
|
+
const hasAiBtn = toolbar.some(item => item.id === 'edit-ai' || item.command === 'edit-component-ai');
|
|
55
|
+
const hasMenuBtn = toolbar.some(item => item.id === 'component-menu' || item.command === 'ac-show-menu');
|
|
56
|
+
|
|
57
|
+
if (!hasCodeBtn) {
|
|
58
|
+
toolbar.unshift(defaultToolbar[0]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!hasAiBtn) {
|
|
62
|
+
toolbar.splice(1, 0, defaultToolbar[1]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!hasMenuBtn) {
|
|
66
|
+
toolbar.push(defaultToolbar[2]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
component.set('toolbar', toolbar);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Add command for editing component code
|
|
73
|
+
editor.Commands.add('edit-component-code', {
|
|
74
|
+
run(editor) {
|
|
75
|
+
const selected = editor.getSelected();
|
|
76
|
+
if (!selected) return;
|
|
77
|
+
|
|
78
|
+
window.ActiveCanvasEditor.openComponentCodeEditor(selected);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Add command for editing with AI
|
|
83
|
+
editor.Commands.add('edit-component-ai', {
|
|
84
|
+
run(editor) {
|
|
85
|
+
const selected = editor.getSelected();
|
|
86
|
+
if (!selected) return;
|
|
87
|
+
|
|
88
|
+
// Open AI panel
|
|
89
|
+
const panelAi = document.getElementById('panel-ai');
|
|
90
|
+
const btnToggleAi = document.getElementById('btn-toggle-ai');
|
|
91
|
+
if (panelAi && panelAi.classList.contains('collapsed')) {
|
|
92
|
+
panelAi.classList.remove('collapsed');
|
|
93
|
+
if (btnToggleAi) btnToggleAi.classList.add('active');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Switch to Element mode
|
|
97
|
+
const elementModeBtn = document.querySelector('.ai-mode-btn[data-mode="element"]');
|
|
98
|
+
if (elementModeBtn && !elementModeBtn.classList.contains('active')) {
|
|
99
|
+
elementModeBtn.click();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Focus the prompt input
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
const promptInput = document.getElementById('ai-text-prompt');
|
|
105
|
+
if (promptInput) promptInput.focus();
|
|
106
|
+
}, 100);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Helper to cleanup menu
|
|
111
|
+
const cleanupComponentMenu = () => {
|
|
112
|
+
const existingMenu = document.querySelector('.component-context-menu');
|
|
113
|
+
if (existingMenu) {
|
|
114
|
+
existingMenu.remove();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Close menu when selection changes
|
|
119
|
+
editor.on('component:selected', cleanupComponentMenu);
|
|
120
|
+
editor.on('component:deselected', cleanupComponentMenu);
|
|
121
|
+
|
|
122
|
+
// Function to show the component menu
|
|
123
|
+
const showComponentMenu = () => {
|
|
124
|
+
const selected = editor.getSelected();
|
|
125
|
+
if (!selected) return;
|
|
126
|
+
|
|
127
|
+
// Always cleanup first
|
|
128
|
+
cleanupComponentMenu();
|
|
129
|
+
|
|
130
|
+
// Get position - use the toolbar
|
|
131
|
+
let btnRect = null;
|
|
132
|
+
const toolbarEl = document.querySelector('.gjs-toolbar');
|
|
133
|
+
|
|
134
|
+
if (toolbarEl) {
|
|
135
|
+
const allItems = toolbarEl.querySelectorAll('.gjs-toolbar-item');
|
|
136
|
+
const menuBtn = allItems[allItems.length - 1];
|
|
137
|
+
btnRect = menuBtn ? menuBtn.getBoundingClientRect() : toolbarEl.getBoundingClientRect();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Final fallback: center of screen
|
|
141
|
+
if (!btnRect) {
|
|
142
|
+
btnRect = {
|
|
143
|
+
top: 100,
|
|
144
|
+
bottom: 120,
|
|
145
|
+
left: window.innerWidth / 2 - 80,
|
|
146
|
+
right: window.innerWidth / 2 + 80
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Create dropdown menu
|
|
151
|
+
const menu = document.createElement('div');
|
|
152
|
+
menu.className = 'component-context-menu';
|
|
153
|
+
menu.innerHTML = `
|
|
154
|
+
<div class="context-menu-item" data-action="edit-ai">
|
|
155
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
156
|
+
<path d="M12 8V4H8"/>
|
|
157
|
+
<rect width="16" height="12" x="4" y="8" rx="2"/>
|
|
158
|
+
<path d="M2 14h2"/>
|
|
159
|
+
<path d="M20 14h2"/>
|
|
160
|
+
<path d="M15 13v2"/>
|
|
161
|
+
<path d="M9 13v2"/>
|
|
162
|
+
</svg>
|
|
163
|
+
<span>Edit with AI</span>
|
|
164
|
+
</div>
|
|
165
|
+
<div class="context-menu-item" data-action="open-styles">
|
|
166
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
167
|
+
<path d="M12 19l7-7 3 3-7 7-3-3z"></path>
|
|
168
|
+
<path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"></path>
|
|
169
|
+
<path d="M2 2l7.586 7.586"></path>
|
|
170
|
+
<circle cx="11" cy="11" r="2"></circle>
|
|
171
|
+
</svg>
|
|
172
|
+
<span>Edit Styles</span>
|
|
173
|
+
</div>
|
|
174
|
+
<div class="context-menu-divider"></div>
|
|
175
|
+
<div class="context-menu-item" data-action="duplicate">
|
|
176
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
177
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
178
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
179
|
+
</svg>
|
|
180
|
+
<span>Duplicate</span>
|
|
181
|
+
</div>
|
|
182
|
+
<div class="context-menu-item" data-action="add-div-above">
|
|
183
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
184
|
+
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
185
|
+
<line x1="12" y1="8" x2="12" y2="16"></line>
|
|
186
|
+
<line x1="8" y1="12" x2="16" y2="12"></line>
|
|
187
|
+
</svg>
|
|
188
|
+
<span>Add Div Above</span>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="context-menu-item" data-action="add-div-below">
|
|
191
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
192
|
+
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
193
|
+
<line x1="12" y1="8" x2="12" y2="16"></line>
|
|
194
|
+
<line x1="8" y1="12" x2="16" y2="12"></line>
|
|
195
|
+
</svg>
|
|
196
|
+
<span>Add Div Below</span>
|
|
197
|
+
</div>
|
|
198
|
+
<div class="context-menu-divider"></div>
|
|
199
|
+
<div class="context-menu-item" data-action="move-up">
|
|
200
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
201
|
+
<polyline points="18 15 12 9 6 15"></polyline>
|
|
202
|
+
</svg>
|
|
203
|
+
<span>Move Up</span>
|
|
204
|
+
</div>
|
|
205
|
+
<div class="context-menu-item" data-action="move-down">
|
|
206
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
207
|
+
<polyline points="6 9 12 15 18 9"></polyline>
|
|
208
|
+
</svg>
|
|
209
|
+
<span>Move Down</span>
|
|
210
|
+
</div>
|
|
211
|
+
<div class="context-menu-divider"></div>
|
|
212
|
+
<div class="context-menu-item context-menu-item-danger" data-action="delete">
|
|
213
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
214
|
+
<polyline points="3 6 5 6 21 6"></polyline>
|
|
215
|
+
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
|
216
|
+
</svg>
|
|
217
|
+
<span>Delete</span>
|
|
218
|
+
</div>
|
|
219
|
+
`;
|
|
220
|
+
|
|
221
|
+
// Position the menu
|
|
222
|
+
menu.style.position = 'fixed';
|
|
223
|
+
menu.style.zIndex = '10000';
|
|
224
|
+
|
|
225
|
+
document.body.appendChild(menu);
|
|
226
|
+
|
|
227
|
+
// Get menu dimensions after adding to DOM
|
|
228
|
+
const menuRect = menu.getBoundingClientRect();
|
|
229
|
+
|
|
230
|
+
// Position below the button, aligned to the right edge
|
|
231
|
+
let top = btnRect.bottom + 5;
|
|
232
|
+
let left = btnRect.right - menuRect.width;
|
|
233
|
+
|
|
234
|
+
// Keep menu on screen
|
|
235
|
+
if (left < 10) left = 10;
|
|
236
|
+
if (top + menuRect.height > window.innerHeight - 10) {
|
|
237
|
+
top = btnRect.top - menuRect.height - 5;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
menu.style.top = top + 'px';
|
|
241
|
+
menu.style.left = left + 'px';
|
|
242
|
+
|
|
243
|
+
// Handle menu item clicks
|
|
244
|
+
menu.querySelectorAll('.context-menu-item').forEach(item => {
|
|
245
|
+
item.addEventListener('click', (e) => {
|
|
246
|
+
e.preventDefault();
|
|
247
|
+
e.stopPropagation();
|
|
248
|
+
const action = item.dataset.action;
|
|
249
|
+
cleanupComponentMenu();
|
|
250
|
+
handleComponentAction(editor, selected, action);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Close menu on click outside (with delay)
|
|
255
|
+
setTimeout(() => {
|
|
256
|
+
const closeHandler = (e) => {
|
|
257
|
+
if (menu.contains(e.target)) return;
|
|
258
|
+
if (e.target.closest('.toolbar-menu-btn') || e.target.textContent === '⋮') return;
|
|
259
|
+
cleanupComponentMenu();
|
|
260
|
+
document.removeEventListener('mousedown', closeHandler);
|
|
261
|
+
};
|
|
262
|
+
document.addEventListener('mousedown', closeHandler);
|
|
263
|
+
}, 50);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// Add command that just calls our function
|
|
267
|
+
editor.Commands.add('ac-show-menu', {
|
|
268
|
+
run() {
|
|
269
|
+
showComponentMenu();
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Handle component context menu actions
|
|
276
|
+
*/
|
|
277
|
+
function handleComponentAction(editor, component, action) {
|
|
278
|
+
const { showToast } = window.ActiveCanvasEditor;
|
|
279
|
+
|
|
280
|
+
if (!component) return;
|
|
281
|
+
|
|
282
|
+
const parent = component.parent();
|
|
283
|
+
|
|
284
|
+
switch (action) {
|
|
285
|
+
case 'edit-ai':
|
|
286
|
+
// Open AI panel in element mode
|
|
287
|
+
editor.runCommand('edit-component-ai');
|
|
288
|
+
break;
|
|
289
|
+
|
|
290
|
+
case 'open-styles':
|
|
291
|
+
// Ensure the component is selected
|
|
292
|
+
editor.select(component);
|
|
293
|
+
|
|
294
|
+
// Open the right panel if collapsed
|
|
295
|
+
const panelRight = document.getElementById('panel-right');
|
|
296
|
+
const btnToggleRight = document.getElementById('btn-toggle-right');
|
|
297
|
+
if (panelRight && panelRight.classList.contains('collapsed')) {
|
|
298
|
+
panelRight.classList.remove('collapsed');
|
|
299
|
+
if (btnToggleRight) btnToggleRight.classList.add('active');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Switch to the Styles tab
|
|
303
|
+
const stylesTab = document.querySelector('.editor-panel-right .panel-tab[data-panel="styles"]');
|
|
304
|
+
if (stylesTab) {
|
|
305
|
+
stylesTab.click();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Scroll the style manager into view if needed
|
|
309
|
+
const stylesContainer = document.getElementById('styles-container');
|
|
310
|
+
if (stylesContainer) {
|
|
311
|
+
stylesContainer.style.display = 'block';
|
|
312
|
+
// Hide traits container
|
|
313
|
+
const traitsContainer = document.getElementById('traits-container');
|
|
314
|
+
if (traitsContainer) traitsContainer.style.display = 'none';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Refresh the editor to update the style manager
|
|
318
|
+
setTimeout(() => editor.refresh(), 100);
|
|
319
|
+
|
|
320
|
+
showToast('Style panel opened', 'success');
|
|
321
|
+
break;
|
|
322
|
+
|
|
323
|
+
case 'duplicate':
|
|
324
|
+
const cloned = component.clone();
|
|
325
|
+
if (parent) {
|
|
326
|
+
const index = parent.components().indexOf(component);
|
|
327
|
+
parent.components().add(cloned, { at: index + 1 });
|
|
328
|
+
editor.select(cloned);
|
|
329
|
+
showToast('Component duplicated', 'success');
|
|
330
|
+
}
|
|
331
|
+
break;
|
|
332
|
+
|
|
333
|
+
case 'add-div-above':
|
|
334
|
+
if (parent) {
|
|
335
|
+
const index = parent.components().indexOf(component);
|
|
336
|
+
const newDiv = parent.components().add({
|
|
337
|
+
tagName: 'div',
|
|
338
|
+
style: { padding: '1rem', 'min-height': '50px' },
|
|
339
|
+
content: ''
|
|
340
|
+
}, { at: index });
|
|
341
|
+
editor.select(newDiv);
|
|
342
|
+
showToast('Div added above', 'success');
|
|
343
|
+
}
|
|
344
|
+
break;
|
|
345
|
+
|
|
346
|
+
case 'add-div-below':
|
|
347
|
+
if (parent) {
|
|
348
|
+
const index = parent.components().indexOf(component);
|
|
349
|
+
const newDiv = parent.components().add({
|
|
350
|
+
tagName: 'div',
|
|
351
|
+
style: { padding: '1rem', 'min-height': '50px' },
|
|
352
|
+
content: ''
|
|
353
|
+
}, { at: index + 1 });
|
|
354
|
+
editor.select(newDiv);
|
|
355
|
+
showToast('Div added below', 'success');
|
|
356
|
+
}
|
|
357
|
+
break;
|
|
358
|
+
|
|
359
|
+
case 'move-up':
|
|
360
|
+
if (parent) {
|
|
361
|
+
const index = parent.components().indexOf(component);
|
|
362
|
+
if (index > 0) {
|
|
363
|
+
parent.components().remove(component);
|
|
364
|
+
parent.components().add(component, { at: index - 1 });
|
|
365
|
+
editor.select(component);
|
|
366
|
+
showToast('Moved up', 'success');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
break;
|
|
370
|
+
|
|
371
|
+
case 'move-down':
|
|
372
|
+
if (parent) {
|
|
373
|
+
const components = parent.components();
|
|
374
|
+
const index = components.indexOf(component);
|
|
375
|
+
if (index < components.length - 1) {
|
|
376
|
+
components.remove(component);
|
|
377
|
+
components.add(component, { at: index + 1 });
|
|
378
|
+
editor.select(component);
|
|
379
|
+
showToast('Moved down', 'success');
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
break;
|
|
383
|
+
|
|
384
|
+
case 'delete':
|
|
385
|
+
component.remove();
|
|
386
|
+
showToast('Component deleted', 'success');
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Expose function
|
|
392
|
+
window.ActiveCanvasEditor.setupComponentToolbar = setupComponentToolbar;
|
|
393
|
+
|
|
394
|
+
})();
|