@mhmttosun/dnd 0.0.0-stage → 1.1.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.
- package/LICENSE +21 -0
- package/README.md +327 -2
- package/css/answer-key.css +200 -0
- package/css/editor.css +1797 -0
- package/css/player.css +782 -0
- package/css/stage.css +490 -0
- package/dist/dnd-player-element.js +1758 -0
- package/dist/dnd.js +2041 -0
- package/dist/editor.css +3278 -0
- package/dist/editor.d.ts +23 -0
- package/dist/editor.js +8506 -0
- package/dist/index.d.ts +102 -0
- package/dist/player.css +782 -0
- package/dist/player.d.ts +1 -0
- package/dist/player.js +2041 -0
- package/dist/player.umd.cjs +1758 -0
- package/index.html +203 -0
- package/js/app.js +188 -0
- package/js/core/AnswerKeyManager.js +494 -0
- package/js/core/EditorCore.js +220 -0
- package/js/core/HistoryManager.js +194 -0
- package/js/core/LayerManager.js +86 -0
- package/js/core/ObjectManager.js +468 -0
- package/js/core/SelectionManager.js +459 -0
- package/js/core/StageManager.js +295 -0
- package/js/data/SampleProjects.js +883 -0
- package/js/engine/TransformEngine.js +632 -0
- package/js/engine/ValidationEngine.js +147 -0
- package/js/player/ActivityEngine.js +1272 -0
- package/js/player/ExportManager.js +820 -0
- package/js/ui/MiniToolbar.js +1546 -0
- package/js/ui/PropertyPanel.js +776 -0
- package/js/ui/UIManager.js +596 -0
- package/js/utils/AudioSynthesizer.js +111 -0
- package/package.json +69 -4
- package/src/ActivityPlayer.js +309 -0
- package/src/DNDEditor.js +217 -0
- package/src/components/DNDPlayerElement.js +191 -0
- package/src/index.js +12 -0
- package/src/player.js +13 -0
|
@@ -0,0 +1,1546 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MiniToolbar - Seçili nesnenin hemen yanında beliren bağlamsal mini araç çubuğu.
|
|
3
|
+
* Canva / Figma deneyiminde olduğu gibi; Sahne Rolü, Nesne Görünümü, Metin Biçimlendirme,
|
|
4
|
+
* Kopyalama, Silme, Katman/Kilit ve Çoklu Hizalama işlemlerini nesne üzerinde yönetir.
|
|
5
|
+
*/
|
|
6
|
+
class MiniToolbar {
|
|
7
|
+
constructor(core, objectManager, selectionManager, layerManager, stageManager, root = document, options = {}) {
|
|
8
|
+
this.core = core;
|
|
9
|
+
this.objectManager = objectManager;
|
|
10
|
+
this.selectionManager = selectionManager;
|
|
11
|
+
this.layerManager = layerManager;
|
|
12
|
+
this.stageManager = stageManager;
|
|
13
|
+
this.root = root;
|
|
14
|
+
this.signal = options.signal;
|
|
15
|
+
|
|
16
|
+
this.toolbarEl = null;
|
|
17
|
+
this.activePopup = null; // 'role' | 'style' | 'text' | 'layers' | 'align' | null
|
|
18
|
+
this.activeStyleTab = 'fill'; // 'fill' | 'border' | 'width' | 'borderColor' | 'radius'
|
|
19
|
+
this.activeTextTab = 'font'; // 'font' | 'align' | 'color'
|
|
20
|
+
this.activeLineTab = 'type'; // Çizgi popup'ında aktif olan sekme ('type', 'width', 'color', 'arrows', 'align')
|
|
21
|
+
this.isNewlyOpened = false;
|
|
22
|
+
this.currentPlacementAbove = false;
|
|
23
|
+
|
|
24
|
+
this.init();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
init() {
|
|
28
|
+
this.createDOM();
|
|
29
|
+
this.bindEvents();
|
|
30
|
+
|
|
31
|
+
// 1. Sahneye basıldığı anda mini toolbar hemen gizlenir (basıldığı an değil, bırakıldığı an görünür)
|
|
32
|
+
this.core.on('stagePointerDown', () => {
|
|
33
|
+
this.hide();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 2. Bırakıldığı anda: Sürükleme yapılmamışsa ve seçili nesne varsa görünür olsun
|
|
37
|
+
this.core.on('stagePointerUp', (data) => {
|
|
38
|
+
const mode = this.core.getMode();
|
|
39
|
+
if (mode !== 'design') {
|
|
40
|
+
this.hide();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Tutulduğu anla bırakıldığı an nesne sürükleme/taşıma yapılmışsa mini toolbar görünmesin
|
|
45
|
+
// Ancak seçim alanı (selection area / marquee) bırakıldığında seçili nesneler için görünsün
|
|
46
|
+
const isMarqueeSelection = data && (data.isMarquee || data.action === 'marquee');
|
|
47
|
+
if (data && data.hasDragged && !isMarqueeSelection) {
|
|
48
|
+
this.hide();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Sürükleme yapılmamışsa (üzerine basılıp bırakıldığı anda) göster
|
|
53
|
+
const selectedObjs = this.selectionManager.getSelectedObjects();
|
|
54
|
+
if (selectedObjs.length > 0) {
|
|
55
|
+
if (selectedObjs.length === 1 && selectedObjs[0].locked) {
|
|
56
|
+
this.hide();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!window.app || !window.app.uiManager || !window.app.uiManager.isEditingText) {
|
|
60
|
+
this.show();
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
this.hide();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// 3. Seçim değiştiğinde:
|
|
68
|
+
this.core.on('selectionChanged', () => {
|
|
69
|
+
const selectedObjs = this.selectionManager.getSelectedObjects();
|
|
70
|
+
if (selectedObjs.length === 0 || (selectedObjs.length === 1 && selectedObjs[0].locked)) {
|
|
71
|
+
this.hide();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// Toolbar zaten açıksa içeriğini ve konumunu güncelle
|
|
75
|
+
if (this.isVisible()) {
|
|
76
|
+
this.render();
|
|
77
|
+
this.reposition();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.core.on('objectUpdated', (data) => {
|
|
82
|
+
if (!this.isVisible()) return;
|
|
83
|
+
// Sadece nesnenin konumu veya boyutu değiştiğinde mini araç çubuğunu yeniden konumlandır
|
|
84
|
+
if (data && data.partialProps) {
|
|
85
|
+
const p = data.partialProps;
|
|
86
|
+
if (p.x !== undefined || p.y !== undefined || p.width !== undefined || p.height !== undefined) {
|
|
87
|
+
this.reposition();
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
this.reposition();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
this.core.on('stageScaleChanged', () => {
|
|
95
|
+
if (this.isVisible()) {
|
|
96
|
+
this.reposition();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
this.core.on('modeChanged', () => {
|
|
101
|
+
if (this.core.getMode() !== 'design') {
|
|
102
|
+
this.hide();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Tuvalde boşluğa tıklandığında popupları kapat
|
|
107
|
+
window.addEventListener('pointerdown', (e) => {
|
|
108
|
+
if (this.toolbarEl && !this.toolbarEl.contains(e.target) && !e.target.closest('.resize-handle, .rotate-handle')) {
|
|
109
|
+
this.closePopup();
|
|
110
|
+
}
|
|
111
|
+
}, this.signal ? { signal: this.signal } : undefined);
|
|
112
|
+
|
|
113
|
+
window.addEventListener('scroll', () => {
|
|
114
|
+
if (this.isVisible()) this.reposition();
|
|
115
|
+
}, this.signal ? { capture: true, signal: this.signal } : true);
|
|
116
|
+
window.addEventListener('resize', () => {
|
|
117
|
+
if (this.isVisible()) this.reposition();
|
|
118
|
+
}, this.signal ? { signal: this.signal } : undefined);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
createDOM() {
|
|
122
|
+
this.toolbarEl = document.createElement('div');
|
|
123
|
+
this.toolbarEl.id = 'contextual-mini-toolbar';
|
|
124
|
+
this.toolbarEl.className = 'mini-toolbar-container';
|
|
125
|
+
this.toolbarEl.style.display = 'none';
|
|
126
|
+
|
|
127
|
+
(this.root.body || this.root).appendChild(this.toolbarEl);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
bindEvents() {
|
|
131
|
+
// Toolbar içine basıldığında sahne olaylarının tetiklenmesini engelle
|
|
132
|
+
this.toolbarEl.addEventListener('pointerdown', (e) => {
|
|
133
|
+
e.stopPropagation();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Toolbar içindeki tıklamalar
|
|
137
|
+
this.toolbarEl.addEventListener('click', (e) => {
|
|
138
|
+
e.stopPropagation();
|
|
139
|
+
|
|
140
|
+
const btn = e.target.closest('[data-action]');
|
|
141
|
+
if (!btn) return;
|
|
142
|
+
|
|
143
|
+
const action = btn.getAttribute('data-action');
|
|
144
|
+
this.handleAction(action, btn);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
handleAction(action, btn) {
|
|
149
|
+
const selectedObjs = this.selectionManager.getSelectedObjects();
|
|
150
|
+
if (selectedObjs.length === 0) return;
|
|
151
|
+
|
|
152
|
+
if (action === 'toggle-line-popup') {
|
|
153
|
+
this.linePopupClosedByUser = (this.activePopup === 'line');
|
|
154
|
+
this.togglePopup('line');
|
|
155
|
+
} else if (action === 'toggle-role-popup') {
|
|
156
|
+
this.togglePopup('role');
|
|
157
|
+
} else if (action === 'toggle-style-popup') {
|
|
158
|
+
this.togglePopup('style');
|
|
159
|
+
} else if (action === 'toggle-text-popup') {
|
|
160
|
+
this.togglePopup('text');
|
|
161
|
+
} else if (action === 'toggle-layer-popup') {
|
|
162
|
+
this.togglePopup('layers');
|
|
163
|
+
} else if (action === 'toggle-align-popup') {
|
|
164
|
+
this.togglePopup('align');
|
|
165
|
+
} else if (action === 'toggle-group') {
|
|
166
|
+
const selected = this.selectionManager.getSelectedObjects();
|
|
167
|
+
const hasGroup = selected.some(o => o.groupId);
|
|
168
|
+
if (hasGroup) {
|
|
169
|
+
this.selectionManager.ungroupSelected();
|
|
170
|
+
} else {
|
|
171
|
+
this.selectionManager.groupSelected();
|
|
172
|
+
}
|
|
173
|
+
this.closePopup();
|
|
174
|
+
this.render();
|
|
175
|
+
} else if (action === 'duplicate') {
|
|
176
|
+
this.selectionManager.duplicateSelected();
|
|
177
|
+
this.closePopup();
|
|
178
|
+
} else if (action === 'delete') {
|
|
179
|
+
this.selectionManager.deleteSelected();
|
|
180
|
+
this.closePopup();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
togglePopup(popupName) {
|
|
185
|
+
if (this.activePopup === popupName) {
|
|
186
|
+
this.activePopup = null;
|
|
187
|
+
this.isNewlyOpened = false;
|
|
188
|
+
} else {
|
|
189
|
+
this.activePopup = popupName;
|
|
190
|
+
this.isNewlyOpened = true;
|
|
191
|
+
}
|
|
192
|
+
this.render();
|
|
193
|
+
if (this.isNewlyOpened) {
|
|
194
|
+
setTimeout(() => {
|
|
195
|
+
const card = this.toolbarEl.querySelector('.mt-popup-card');
|
|
196
|
+
if (card) card.classList.remove('is-opening');
|
|
197
|
+
this.isNewlyOpened = false;
|
|
198
|
+
}, 250);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
closePopup() {
|
|
203
|
+
if (this.activePopup) {
|
|
204
|
+
this.activePopup = null;
|
|
205
|
+
this.isNewlyOpened = false;
|
|
206
|
+
this.render();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
isVisible() {
|
|
211
|
+
return this.toolbarEl && this.toolbarEl.style.display !== 'none';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
show() {
|
|
215
|
+
if (!this.toolbarEl) return;
|
|
216
|
+
const mode = this.core.getMode();
|
|
217
|
+
const selectedObjs = this.selectionManager.getSelectedObjects();
|
|
218
|
+
|
|
219
|
+
if (mode !== 'design' || selectedObjs.length === 0 || (selectedObjs.length === 1 && selectedObjs[0].locked)) {
|
|
220
|
+
this.hide();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this.toolbarEl.style.display = 'flex';
|
|
225
|
+
this.render();
|
|
226
|
+
this.reposition();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
hide() {
|
|
230
|
+
if (!this.toolbarEl) return;
|
|
231
|
+
this.toolbarEl.style.display = 'none';
|
|
232
|
+
this.activePopup = null;
|
|
233
|
+
this.isNewlyOpened = false;
|
|
234
|
+
this.linePopupClosedByUser = false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
updatePositionAndVisibility() {
|
|
238
|
+
if (this.isVisible()) {
|
|
239
|
+
const mode = this.core.getMode();
|
|
240
|
+
const selectedObjs = this.selectionManager.getSelectedObjects();
|
|
241
|
+
|
|
242
|
+
if (mode !== 'design' || selectedObjs.length === 0 || (selectedObjs.length === 1 && selectedObjs[0].locked)) {
|
|
243
|
+
this.hide();
|
|
244
|
+
} else {
|
|
245
|
+
this.render();
|
|
246
|
+
this.reposition();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
reposition() {
|
|
252
|
+
if (!this.isVisible()) return;
|
|
253
|
+
const selectedObjs = this.selectionManager.getSelectedObjects();
|
|
254
|
+
if (selectedObjs.length === 0 || (selectedObjs.length === 1 && selectedObjs[0].locked)) {
|
|
255
|
+
this.hide();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Seçili nesnelerin sahne koordinatlarında AABB sınırları
|
|
260
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
261
|
+
selectedObjs.forEach(o => {
|
|
262
|
+
minX = Math.min(minX, o.x);
|
|
263
|
+
minY = Math.min(minY, o.y);
|
|
264
|
+
maxX = Math.max(maxX, o.x + o.width);
|
|
265
|
+
maxY = Math.max(maxY, o.y + o.height);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const midStageX = (minX + maxX) / 2;
|
|
269
|
+
const clientTopObj = this.stageManager.stageToClient(midStageX, minY);
|
|
270
|
+
const clientBottomObj = this.stageManager.stageToClient(midStageX, maxY);
|
|
271
|
+
const clientCenterObj = this.stageManager.stageToClient(midStageX, (minY + maxY) / 2);
|
|
272
|
+
|
|
273
|
+
// Üst kısımdaki sabit başlık ve ikincil araç çubuğu (58px + 48px = 106px)
|
|
274
|
+
const headerOffset = 112;
|
|
275
|
+
const screenBottom = window.innerHeight - 15;
|
|
276
|
+
const screenLeft = 16;
|
|
277
|
+
const screenRight = window.innerWidth - 16;
|
|
278
|
+
|
|
279
|
+
// Nesnenin altındaki ve üstündeki kullanılabilir ekran dikey boşluğu
|
|
280
|
+
const spaceBelow = screenBottom - (clientBottomObj.clientY + 12);
|
|
281
|
+
const spaceAbove = (clientTopObj.clientY - 12) - headerOffset;
|
|
282
|
+
|
|
283
|
+
// Toolbar ve menü için gereken dikey boşluk (açıkken gerçek yükseklik, kapalıyken potansiyel popup için ~240px)
|
|
284
|
+
const tbRect = this.toolbarEl.getBoundingClientRect();
|
|
285
|
+
const currentHeight = tbRect.height || (this.activePopup ? 260 : 44);
|
|
286
|
+
const neededHeight = this.activePopup ? currentHeight : 240;
|
|
287
|
+
|
|
288
|
+
let isAbove = false;
|
|
289
|
+
// Eğer alt tarafta yeterli yer varsa altta aç, yetersizse ve üstte yer varsa veya üst taraf daha genişse üstte aç
|
|
290
|
+
if (spaceBelow >= neededHeight) {
|
|
291
|
+
isAbove = false;
|
|
292
|
+
} else if (spaceAbove >= neededHeight) {
|
|
293
|
+
isAbove = true;
|
|
294
|
+
} else {
|
|
295
|
+
isAbove = spaceAbove > spaceBelow;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
this.toolbarEl.classList.toggle('placement-above', isAbove);
|
|
299
|
+
this.toolbarEl.classList.toggle('placement-below', !isAbove);
|
|
300
|
+
|
|
301
|
+
// Sınıf geçişinden sonra güncel boyutları oku
|
|
302
|
+
const updatedRect = this.toolbarEl.getBoundingClientRect();
|
|
303
|
+
const finalTbHeight = updatedRect.height || currentHeight;
|
|
304
|
+
const finalTbWidth = updatedRect.width || 280;
|
|
305
|
+
|
|
306
|
+
let top = 0;
|
|
307
|
+
if (isAbove) {
|
|
308
|
+
// Nesnenin üst kenarının 10px yukarısına yerleştir
|
|
309
|
+
top = clientTopObj.clientY - 10 - finalTbHeight;
|
|
310
|
+
top = Math.max(headerOffset, top);
|
|
311
|
+
if (top + finalTbHeight > screenBottom) {
|
|
312
|
+
top = Math.max(headerOffset, screenBottom - finalTbHeight);
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
// Nesnenin alt kenarının 10px aşağısına yerleştir
|
|
316
|
+
top = clientBottomObj.clientY + 10;
|
|
317
|
+
if (top + finalTbHeight > screenBottom) {
|
|
318
|
+
top = Math.max(headerOffset, screenBottom - finalTbHeight);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Yatay konum (Nesnenin ortası, ekran sınırlarına taşmayacak şekilde)
|
|
323
|
+
let left = clientCenterObj.clientX - finalTbWidth / 2;
|
|
324
|
+
left = Math.max(screenLeft, Math.min(screenRight - finalTbWidth, left));
|
|
325
|
+
|
|
326
|
+
this.toolbarEl.style.left = `${Math.round(left)}px`;
|
|
327
|
+
this.toolbarEl.style.top = `${Math.round(top)}px`;
|
|
328
|
+
|
|
329
|
+
// Açık popup varsa taşma ve kaydırma çubuğu oluşmasını önle
|
|
330
|
+
const popupEl = this.toolbarEl.querySelector('.mt-popup-card');
|
|
331
|
+
if (popupEl) {
|
|
332
|
+
popupEl.style.maxHeight = 'none';
|
|
333
|
+
popupEl.style.overflow = 'visible';
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
render() {
|
|
338
|
+
const selectedObjs = this.selectionManager.getSelectedObjects();
|
|
339
|
+
const primary = this.selectionManager.getPrimarySelected();
|
|
340
|
+
if (selectedObjs.length === 0 || !primary || (selectedObjs.length === 1 && primary.locked)) {
|
|
341
|
+
this.toolbarEl.style.display = 'none';
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const isMulti = selectedObjs.length > 1;
|
|
345
|
+
const hasGroup = selectedObjs.some(o => o.groupId);
|
|
346
|
+
const canHaveText = selectedObjs.some(o => o.type !== 'line');
|
|
347
|
+
const allLines = selectedObjs.length > 0 && selectedObjs.every(o => o.type === 'line');
|
|
348
|
+
|
|
349
|
+
if (!allLines && this.activePopup === 'line') {
|
|
350
|
+
this.activePopup = null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (allLines) {
|
|
354
|
+
if (this.activePopup === null && !this.linePopupClosedByUser) {
|
|
355
|
+
this.activePopup = 'line';
|
|
356
|
+
}
|
|
357
|
+
let html = `
|
|
358
|
+
<div class="mini-toolbar-pill line-mini-toolbar-pill">
|
|
359
|
+
<button type="button" class="mt-btn ${this.activePopup === 'line' ? 'active' : ''}" data-action="toggle-line-popup" title="Çizgi Ayarları">
|
|
360
|
+
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 18l6-6 4 4 6-8"/></svg>
|
|
361
|
+
</button>
|
|
362
|
+
|
|
363
|
+
<div class="mt-divider"></div>
|
|
364
|
+
|
|
365
|
+
<!-- Çoğalt Butonu -->
|
|
366
|
+
<button type="button" class="mt-btn" data-action="duplicate" title="Çoğalt (Ctrl+D)">
|
|
367
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
368
|
+
</button>
|
|
369
|
+
|
|
370
|
+
<!-- Sil Butonu -->
|
|
371
|
+
<button type="button" class="mt-btn mt-btn-danger" data-action="delete" title="Sil (Delete)">
|
|
372
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><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"/></svg>
|
|
373
|
+
</button>
|
|
374
|
+
</div>
|
|
375
|
+
|
|
376
|
+
<!-- Popuplar -->
|
|
377
|
+
${this.renderActivePopup(primary, isMulti, hasGroup)}
|
|
378
|
+
`;
|
|
379
|
+
|
|
380
|
+
this.toolbarEl.innerHTML = html;
|
|
381
|
+
this.bindPopupInteractions(primary);
|
|
382
|
+
this.reposition();
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (this.activePopup === 'text' && !canHaveText) {
|
|
387
|
+
this.activePopup = null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
let html = `
|
|
391
|
+
<div class="mini-toolbar-pill ${isMulti ? 'is-multi' : ''}">
|
|
392
|
+
${(isMulti || hasGroup) ? `
|
|
393
|
+
<button type="button" class="mt-btn ${this.activePopup === 'align' ? 'active' : ''}" data-action="toggle-align-popup" title="Hizalama">
|
|
394
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><path d="M12 7v10M7 12h10"/></svg>
|
|
395
|
+
</button>
|
|
396
|
+
` : ''}
|
|
397
|
+
|
|
398
|
+
${(isMulti || hasGroup) ? `
|
|
399
|
+
<button type="button" class="mt-btn ${hasGroup ? 'active' : ''}" data-action="toggle-group" title="${hasGroup ? 'Grubu Çöz (Ctrl+Shift+G)' : 'Grupla (Ctrl+G)'}">
|
|
400
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" stroke-dasharray="3 3"/><circle cx="9" cy="9" r="2"/><circle cx="15" cy="15" r="2"/></svg>
|
|
401
|
+
</button>
|
|
402
|
+
` : ''}
|
|
403
|
+
|
|
404
|
+
<!-- Sahne Rolü Butonu -->
|
|
405
|
+
<button type="button" class="mt-btn ${this.activePopup === 'role' ? 'active' : ''}" data-action="toggle-role-popup" title="Sahne Rolü">
|
|
406
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="6" height="6" rx="1"/><rect x="15" y="3" width="6" height="6" rx="1"/><rect x="15" y="15" width="6" height="6" rx="1"/><rect x="3" y="15" width="6" height="6" rx="1"/><path d="M9 6h6M6 9v6M18 9v6M9 18h6"/></svg>
|
|
407
|
+
</button>
|
|
408
|
+
|
|
409
|
+
<!-- Görünüm / Şekil Butonu -->
|
|
410
|
+
<button type="button" class="mt-btn ${this.activePopup === 'style' ? 'active' : ''}" data-action="toggle-style-popup" title="Nesne Görünümü">
|
|
411
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l4 7H8l4-7z"/><rect x="3" y="14" width="7" height="7"/><circle cx="17.5" cy="17.5" r="3.5"/></svg>
|
|
412
|
+
</button>
|
|
413
|
+
|
|
414
|
+
<!-- Metin Butonu (Çizgide metin olmayacağı için gizlenir) -->
|
|
415
|
+
${canHaveText ? `
|
|
416
|
+
<button type="button" class="mt-btn ${this.activePopup === 'text' ? 'active' : ''}" data-action="toggle-text-popup" title="Metin Biçimlendirme">
|
|
417
|
+
<span class="mt-text-icon">Aa</span>
|
|
418
|
+
</button>
|
|
419
|
+
` : ''}
|
|
420
|
+
|
|
421
|
+
<div class="mt-divider"></div>
|
|
422
|
+
|
|
423
|
+
<!-- Çoğalt Butonu -->
|
|
424
|
+
<button type="button" class="mt-btn" data-action="duplicate" title="Çoğalt (Ctrl+D)">
|
|
425
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
426
|
+
</button>
|
|
427
|
+
|
|
428
|
+
<!-- Sil Butonu -->
|
|
429
|
+
<button type="button" class="mt-btn mt-btn-danger" data-action="delete" title="Sil (Delete)">
|
|
430
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><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"/></svg>
|
|
431
|
+
</button>
|
|
432
|
+
|
|
433
|
+
<!-- Katmanlar & Kilit Butonu -->
|
|
434
|
+
<button type="button" class="mt-btn ${this.activePopup === 'layers' ? 'active' : ''}" data-action="toggle-layer-popup" title="Katman Sırası ve Kilit">
|
|
435
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></svg>
|
|
436
|
+
</button>
|
|
437
|
+
</div>
|
|
438
|
+
|
|
439
|
+
<!-- Popuplar -->
|
|
440
|
+
${this.renderActivePopup(primary, isMulti, hasGroup)}
|
|
441
|
+
`;
|
|
442
|
+
|
|
443
|
+
this.toolbarEl.innerHTML = html;
|
|
444
|
+
this.bindPopupInteractions(primary);
|
|
445
|
+
this.reposition();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
renderActivePopup(primary, isMulti, hasGroup = false) {
|
|
449
|
+
if (!this.activePopup || !primary) return '';
|
|
450
|
+
const openClass = this.isNewlyOpened ? 'is-opening' : '';
|
|
451
|
+
|
|
452
|
+
if (this.activePopup === 'line') {
|
|
453
|
+
const bStyle = primary.borderStyle || 'solid';
|
|
454
|
+
const bWidth = primary.borderWidth || 3;
|
|
455
|
+
const bColor = primary.borderColor || '#1e293b';
|
|
456
|
+
const rot = Math.round(primary.rotation || 0) % 360;
|
|
457
|
+
const normalizedRot = (rot + 360) % 360;
|
|
458
|
+
const isHoriz = normalizedRot === 0 || normalizedRot === 180;
|
|
459
|
+
const isVert = normalizedRot === 90 || normalizedRot === 270;
|
|
460
|
+
const activeTab = this.activeLineTab || 'type';
|
|
461
|
+
|
|
462
|
+
const arrowMode = (primary.arrowStart && primary.arrowEnd) ? 'both' : (primary.arrowStart ? 'start' : (primary.arrowEnd ? 'end' : 'none'));
|
|
463
|
+
|
|
464
|
+
const paletteColors = [
|
|
465
|
+
'#1e293b', '#475569', '#94a3b8', '#ef4444',
|
|
466
|
+
'#f97316', '#f59e0b', '#10b981', '#06b6d4',
|
|
467
|
+
'#3b82f6', '#6366f1', '#8b5cf6', '#ec4899'
|
|
468
|
+
];
|
|
469
|
+
const paletteNames = ['Koyu gri', 'Gri', 'Açık gri', 'Kırmızı', 'Turuncu', 'Sarı', 'Yeşil', 'Turkuaz', 'Mavi', 'İndigo', 'Mor', 'Pembe'];
|
|
470
|
+
|
|
471
|
+
return `
|
|
472
|
+
<div class="mt-popup-card mt-line-popup ${openClass}" id="mt-line-popup">
|
|
473
|
+
<div class="mt-line-title">Çizgi</div>
|
|
474
|
+
|
|
475
|
+
<!-- Üst Menü Sekmeleri -->
|
|
476
|
+
<div class="mt-line-tabs-bar" role="group" aria-label="Çizgi ayarları">
|
|
477
|
+
<!-- 1. Çizgi Tipi -->
|
|
478
|
+
<button type="button" class="mt-line-tab-btn ${activeTab === 'type' ? 'active' : ''}" data-line-tab="type" title="Çizgi tipi">
|
|
479
|
+
<svg width="20" height="12" viewBox="0 0 20 12"><line x1="2" y1="6" x2="18" y2="6" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></svg>
|
|
480
|
+
</button>
|
|
481
|
+
|
|
482
|
+
<!-- 2. Çizgi Kalınlığı -->
|
|
483
|
+
<button type="button" class="mt-line-tab-btn ${activeTab === 'width' ? 'active' : ''}" data-line-tab="width" title="Çizgi kalınlığı">
|
|
484
|
+
<span class="mt-line-tab-width-icon">
|
|
485
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="2" y1="4" x2="10" y2="4"/><line x1="2" y1="8" x2="10" y2="8"/></svg>
|
|
486
|
+
</span>
|
|
487
|
+
<span class="mt-line-tab-width-text" id="mt-tab-width-val">${bWidth}</span>
|
|
488
|
+
</button>
|
|
489
|
+
|
|
490
|
+
<!-- 3. Çizgi Rengi -->
|
|
491
|
+
<button type="button" class="mt-line-tab-btn ${activeTab === 'color' ? 'active' : ''}" data-line-tab="color" title="Çizgi rengi">
|
|
492
|
+
<span class="mt-line-tab-swatch" id="mt-tab-color-swatch" style="background-color: ${bColor};"></span>
|
|
493
|
+
</button>
|
|
494
|
+
|
|
495
|
+
<!-- 4. Ok Uçları -->
|
|
496
|
+
<button type="button" class="mt-line-tab-btn ${activeTab === 'arrows' ? 'active' : ''}" data-line-tab="arrows" title="Ok uçları">
|
|
497
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="9 8 5 12 9 16"/><polyline points="15 8 19 12 15 16"/></svg>
|
|
498
|
+
</button>
|
|
499
|
+
|
|
500
|
+
<!-- 5. Yön -->
|
|
501
|
+
<button type="button" class="mt-line-tab-btn ${activeTab === 'align' ? 'active' : ''}" data-line-tab="align" title="Çizgi yönü">
|
|
502
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 18h16M6 6v14M16 14l4 4-4 4M2 10l4-4 4 4"/></svg>
|
|
503
|
+
</button>
|
|
504
|
+
</div>
|
|
505
|
+
|
|
506
|
+
<!-- Alt Kart Panelleri -->
|
|
507
|
+
|
|
508
|
+
<!-- Panel 1: Çizgi tipi -->
|
|
509
|
+
<div class="mt-line-panel" data-panel="type" style="display: ${activeTab === 'type' ? 'flex' : 'none'};">
|
|
510
|
+
<div class="mt-line-panel-title">Çizgi tipi</div>
|
|
511
|
+
<div class="mt-line-type-row">
|
|
512
|
+
<button type="button" class="mt-line-type-btn ${bStyle === 'solid' ? 'active' : ''}" data-line-style="solid" title="Düz Çizgi">
|
|
513
|
+
<svg width="28" height="12" viewBox="0 0 28 12"><line x1="2" y1="6" x2="26" y2="6" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg>
|
|
514
|
+
</button>
|
|
515
|
+
<button type="button" class="mt-line-type-btn ${bStyle === 'dashed' ? 'active' : ''}" data-line-style="dashed" title="Kesikli Çizgi">
|
|
516
|
+
<svg width="28" height="12" viewBox="0 0 28 12"><line x1="2" y1="6" x2="26" y2="6" stroke="currentColor" stroke-width="3" stroke-dasharray="6 4" stroke-linecap="round"/></svg>
|
|
517
|
+
</button>
|
|
518
|
+
<button type="button" class="mt-line-type-btn ${bStyle === 'dotted' ? 'active' : ''}" data-line-style="dotted" title="Noktalı Çizgi">
|
|
519
|
+
<svg width="28" height="12" viewBox="0 0 28 12"><line x1="2" y1="6" x2="26" y2="6" stroke="currentColor" stroke-width="3" stroke-dasharray="1 5" stroke-linecap="round"/></svg>
|
|
520
|
+
</button>
|
|
521
|
+
</div>
|
|
522
|
+
</div>
|
|
523
|
+
|
|
524
|
+
<!-- Panel 2: Kalınlık -->
|
|
525
|
+
<div class="mt-line-panel" data-panel="width" style="display: ${activeTab === 'width' ? 'flex' : 'none'};">
|
|
526
|
+
<div class="mt-line-panel-title">Kalınlık <span class="mt-line-unit">px</span></div>
|
|
527
|
+
<div class="mt-line-chips-row">
|
|
528
|
+
${[1, 2, 3, 4, 6, 8].map(w => `
|
|
529
|
+
<button type="button" class="mt-line-chip-btn ${bWidth === w ? 'active' : ''}" data-line-width-preset="${w}" aria-label="${w} piksel">
|
|
530
|
+
<span class="mt-line-width-sample" style="--line-width: ${w}px;" aria-hidden="true"></span>
|
|
531
|
+
<span>${w}</span>
|
|
532
|
+
</button>
|
|
533
|
+
`).join('')}
|
|
534
|
+
</div>
|
|
535
|
+
</div>
|
|
536
|
+
|
|
537
|
+
<!-- Panel 3: Renk -->
|
|
538
|
+
<div class="mt-line-panel" data-panel="color" style="display: ${activeTab === 'color' ? 'flex' : 'none'};">
|
|
539
|
+
<div class="mt-line-panel-title">Çizgi rengi</div>
|
|
540
|
+
<div class="mt-line-color-grid">
|
|
541
|
+
${paletteColors.map((c, i) => `
|
|
542
|
+
<button type="button" class="mt-line-color-swatch ${bColor.toLowerCase() === c.toLowerCase() ? 'is-active' : ''}" data-line-color="${c}" style="--line-color: ${c}; --line-check-color: ${['#94a3b8', '#f59e0b', '#10b981', '#06b6d4'].includes(c) ? '#0f172a' : '#ffffff'};" title="${paletteNames[i]}" aria-label="${paletteNames[i]}">
|
|
543
|
+
<span class="mt-line-color-dot" aria-hidden="true"><span class="mt-line-color-check">✓</span></span>
|
|
544
|
+
</button>
|
|
545
|
+
`).join('')}
|
|
546
|
+
</div>
|
|
547
|
+
</div>
|
|
548
|
+
|
|
549
|
+
<!-- Panel 4: Ok uçları -->
|
|
550
|
+
<div class="mt-line-panel" data-panel="arrows" style="display: ${activeTab === 'arrows' ? 'flex' : 'none'};">
|
|
551
|
+
<div class="mt-line-panel-title">Ok uçları</div>
|
|
552
|
+
<div class="mt-line-arrow-grid">
|
|
553
|
+
<button type="button" class="mt-line-arrow-btn ${arrowMode === 'none' ? 'active' : ''}" data-line-arrow="none" title="Uçsuz Düz">
|
|
554
|
+
<svg width="24" height="14" viewBox="0 0 24 14"><line x1="2" y1="7" x2="22" y2="7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></svg>
|
|
555
|
+
<span>Uçsuz</span>
|
|
556
|
+
</button>
|
|
557
|
+
<button type="button" class="mt-line-arrow-btn ${arrowMode === 'start' ? 'active' : ''}" data-line-arrow="start" title="Başlangıç Oku">
|
|
558
|
+
<svg width="24" height="14" viewBox="0 0 24 14"><line x1="6" y1="7" x2="22" y2="7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/><polygon points="6,3 0,7 6,11" fill="currentColor"/></svg>
|
|
559
|
+
<span>Başlangıç</span>
|
|
560
|
+
</button>
|
|
561
|
+
<button type="button" class="mt-line-arrow-btn ${arrowMode === 'end' ? 'active' : ''}" data-line-arrow="end" title="Bitiş Oku">
|
|
562
|
+
<svg width="24" height="14" viewBox="0 0 24 14"><line x1="2" y1="7" x2="18" y2="7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/><polygon points="18,3 24,7 18,11" fill="currentColor"/></svg>
|
|
563
|
+
<span>Bitiş</span>
|
|
564
|
+
</button>
|
|
565
|
+
<button type="button" class="mt-line-arrow-btn ${arrowMode === 'both' ? 'active' : ''}" data-line-arrow="both" title="Çift Yönlü Ok">
|
|
566
|
+
<svg width="24" height="14" viewBox="0 0 24 14"><line x1="6" y1="7" x2="18" y2="7" stroke="currentColor" stroke-width="2.5"/><polygon points="6,3 0,7 6,11" fill="currentColor"/><polygon points="18,3 24,7 18,11" fill="currentColor"/></svg>
|
|
567
|
+
<span>Çift yön</span>
|
|
568
|
+
</button>
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
571
|
+
|
|
572
|
+
<!-- Panel 5: Yön -->
|
|
573
|
+
<div class="mt-line-panel" data-panel="align" style="display: ${activeTab === 'align' ? 'flex' : 'none'};">
|
|
574
|
+
<div class="mt-line-panel-title">Yön</div>
|
|
575
|
+
<div class="mt-line-align-grid">
|
|
576
|
+
<button type="button" class="mt-line-align-btn ${isHoriz ? 'active' : ''}" data-line-align="horizontal" title="Yatay Yap">
|
|
577
|
+
<svg width="18" height="14" viewBox="0 0 18 14"><line x1="1" y1="7" x2="17" y2="7" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg>
|
|
578
|
+
<span>Yatay</span>
|
|
579
|
+
</button>
|
|
580
|
+
<button type="button" class="mt-line-align-btn ${isVert ? 'active' : ''}" data-line-align="vertical" title="Dikey Yap">
|
|
581
|
+
<svg width="14" height="18" viewBox="0 0 14 18"><line x1="7" y1="1" x2="7" y2="17" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg>
|
|
582
|
+
<span>Dikey</span>
|
|
583
|
+
</button>
|
|
584
|
+
</div>
|
|
585
|
+
</div>
|
|
586
|
+
</div>
|
|
587
|
+
`;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (this.activePopup === 'role') {
|
|
591
|
+
const r = primary.role || 'decorative';
|
|
592
|
+
return `
|
|
593
|
+
<div class="mt-popup-card mt-role-popup ${openClass}" id="mt-role-popup">
|
|
594
|
+
<div class="mt-role-section">
|
|
595
|
+
<span class="mt-role-section-label">Sahne rolü</span>
|
|
596
|
+
<div class="mt-role-grid" role="radiogroup" aria-label="Sahne rolü">
|
|
597
|
+
<button type="button" role="radio" aria-checked="${r === 'draggable'}" class="mt-role-btn ${r === 'draggable' ? 'active' : ''}" data-role-val="draggable" title="Taşınan nesne">
|
|
598
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v20M2 12h20"/><path d="m8 6 4-4 4 4M8 18l4 4 4-4M6 8l-4 4 4 4M18 8l4 4-4 4"/></svg>
|
|
599
|
+
<span>Taşınan</span>
|
|
600
|
+
</button>
|
|
601
|
+
<button type="button" role="radio" aria-checked="${['target', 'category', 'sequence_slot'].includes(r)}" class="mt-role-btn ${['target', 'category', 'sequence_slot'].includes(r) ? 'active' : ''}" data-role-val="target" title="Hedef nesne">
|
|
602
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="4"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/></svg>
|
|
603
|
+
<span>Hedef</span>
|
|
604
|
+
</button>
|
|
605
|
+
<button type="button" role="radio" aria-checked="${r === 'decorative'}" class="mt-role-btn ${r === 'decorative' ? 'active' : ''}" data-role-val="decorative" title="Nötr nesne">
|
|
606
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M8 12h8"/></svg>
|
|
607
|
+
<span>Nötr</span>
|
|
608
|
+
</button>
|
|
609
|
+
</div>
|
|
610
|
+
</div>
|
|
611
|
+
</div>
|
|
612
|
+
`;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (this.activePopup === 'style') {
|
|
616
|
+
const isLine = primary.type === 'line';
|
|
617
|
+
const bStyle = primary.borderStyle || 'solid';
|
|
618
|
+
const bWidth = primary.borderWidth ?? 2;
|
|
619
|
+
const bRadius = primary.borderRadius ?? 8;
|
|
620
|
+
const tab = isLine && ['fill', 'radius'].includes(this.activeStyleTab) ? 'border' : this.activeStyleTab;
|
|
621
|
+
const colors = [
|
|
622
|
+
['transparent', 'Şeffaf'], ['#ffffff', 'Beyaz'], ['#cbd5e1', 'Açık gri'],
|
|
623
|
+
['#64748b', 'Gri'], ['#1e293b', 'Koyu gri'], ['#ef4444', 'Kırmızı'],
|
|
624
|
+
['#f97316', 'Turuncu'], ['#eab308', 'Sarı'], ['#22c55e', 'Yeşil'],
|
|
625
|
+
['#14b8a6', 'Turkuaz'], ['#0ea5e9', 'Açık mavi'], ['#2563eb', 'Mavi'],
|
|
626
|
+
['#8b5cf6', 'Mor'], ['#ec4899', 'Pembe']
|
|
627
|
+
];
|
|
628
|
+
const palette = (prop) => {
|
|
629
|
+
const selectedColor = (primary[prop] || 'transparent').toLowerCase();
|
|
630
|
+
return `<div class="mt-style-color-grid" role="group" aria-label="${prop === 'backgroundColor' ? 'Dolgu rengi' : 'Kenarlık rengi'}">
|
|
631
|
+
${colors.map(([color, name]) => `
|
|
632
|
+
<button type="button" class="mt-style-color-swatch ${selectedColor === color ? 'is-active' : ''}" data-style-color="${color}" data-style-color-prop="${prop}" title="${name}" aria-label="${name}" style="--line-color: ${color}; --line-check-color: ${['transparent', '#ffffff', '#cbd5e1', '#eab308', '#22c55e', '#14b8a6', '#0ea5e9'].includes(color) ? '#0f172a' : '#ffffff'};">
|
|
633
|
+
<span class="mt-style-color-dot ${color === 'transparent' ? 'is-transparent' : ''}" aria-hidden="true"><span class="mt-style-color-check">✓</span></span>
|
|
634
|
+
</button>`).join('')}
|
|
635
|
+
</div>`;
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
return `
|
|
639
|
+
<div class="mt-popup-card mt-style-popup ${openClass}" id="mt-style-popup">
|
|
640
|
+
<div class="mt-style-title">${isLine ? 'Çizgi görünümü' : 'Nesne görünümü'}</div>
|
|
641
|
+
<div class="mt-style-tabs-bar" role="group" aria-label="Görünüm ayarları">
|
|
642
|
+
${!isLine ? `
|
|
643
|
+
<button type="button" class="mt-style-tab-btn ${tab === 'fill' ? 'active' : ''}" data-style-tab="fill" title="Dolgu rengi">
|
|
644
|
+
<span class="mt-style-tab-swatch ${!primary.backgroundColor || primary.backgroundColor === 'transparent' ? 'is-transparent' : ''}" data-style-preview="backgroundColor" style="background-color: ${primary.backgroundColor || 'transparent'};"></span>
|
|
645
|
+
</button>` : ''}
|
|
646
|
+
<button type="button" class="mt-style-tab-btn ${tab === 'border' ? 'active' : ''}" data-style-tab="border" title="Kenarlık tipi">
|
|
647
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="4" width="16" height="16" rx="3" stroke-dasharray="3 3"/></svg>
|
|
648
|
+
</button>
|
|
649
|
+
<button type="button" class="mt-style-tab-btn ${tab === 'width' ? 'active' : ''}" data-style-tab="width" title="Kenarlık kalınlığı">
|
|
650
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 4h8M2 8h8"/></svg>
|
|
651
|
+
<span class="mt-style-tab-width-text" data-style-value="borderWidth">${bWidth}</span>
|
|
652
|
+
</button>
|
|
653
|
+
<button type="button" class="mt-style-tab-btn ${tab === 'borderColor' ? 'active' : ''}" data-style-tab="borderColor" title="Kenarlık rengi">
|
|
654
|
+
<span class="mt-style-border-preview ${!primary.borderColor || primary.borderColor === 'transparent' ? 'is-transparent' : ''}" data-style-preview="borderColor" style="border-color: ${primary.borderColor || 'transparent'};"></span>
|
|
655
|
+
</button>
|
|
656
|
+
${!isLine ? `
|
|
657
|
+
<button type="button" class="mt-style-tab-btn ${tab === 'radius' ? 'active' : ''}" data-style-tab="radius" title="Köşe yuvarlaklığı">
|
|
658
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M5 20V12a7 7 0 0 1 7-7h8"/><path d="M5 5h1M19 19h1"/></svg>
|
|
659
|
+
</button>` : ''}
|
|
660
|
+
</div>
|
|
661
|
+
${!isLine ? `
|
|
662
|
+
<div class="mt-style-panel" data-style-panel="fill" style="display: ${tab === 'fill' ? 'flex' : 'none'};">
|
|
663
|
+
<div class="mt-style-panel-title">Dolgu rengi</div>
|
|
664
|
+
${palette('backgroundColor')}
|
|
665
|
+
</div>` : ''}
|
|
666
|
+
<div class="mt-style-panel" data-style-panel="border" style="display: ${tab === 'border' ? 'flex' : 'none'};">
|
|
667
|
+
<div class="mt-style-panel-title">Kenarlık tipi</div>
|
|
668
|
+
<div class="mt-style-type-row" role="group" aria-label="Kenarlık tipi">
|
|
669
|
+
<button type="button" class="mt-style-type-btn ${bStyle === 'none' ? 'active' : ''}" data-border-style="none" title="Kenarlık yok">⊘</button>
|
|
670
|
+
<button type="button" class="mt-style-type-btn ${bStyle === 'solid' ? 'active' : ''}" data-border-style="solid" title="Düz kenarlık">━</button>
|
|
671
|
+
<button type="button" class="mt-style-type-btn ${bStyle === 'dashed' ? 'active' : ''}" data-border-style="dashed" title="Kesikli kenarlık">┅</button>
|
|
672
|
+
<button type="button" class="mt-style-type-btn ${bStyle === 'dotted' ? 'active' : ''}" data-border-style="dotted" title="Noktalı kenarlık">⋯</button>
|
|
673
|
+
</div>
|
|
674
|
+
</div>
|
|
675
|
+
<div class="mt-style-panel" data-style-panel="width" style="display: ${tab === 'width' ? 'flex' : 'none'};">
|
|
676
|
+
<div class="mt-style-panel-title">Kenarlık kalınlığı</div>
|
|
677
|
+
<div class="mt-stepper-group" role="group" aria-label="Kenarlık kalınlığı">
|
|
678
|
+
<button type="button" class="mt-step-btn" data-step-prop="borderWidth" data-step-dir="-1" title="Kalınlığı azalt">−</button>
|
|
679
|
+
<div class="mt-style-step-value"><span class="mt-step-val" id="mt-val-borderWidth" aria-live="polite">${bWidth}</span><span class="mt-step-unit">px</span></div>
|
|
680
|
+
<button type="button" class="mt-step-btn" data-step-prop="borderWidth" data-step-dir="1" title="Kalınlığı artır">+</button>
|
|
681
|
+
</div>
|
|
682
|
+
</div>
|
|
683
|
+
<div class="mt-style-panel" data-style-panel="borderColor" style="display: ${tab === 'borderColor' ? 'flex' : 'none'};">
|
|
684
|
+
<div class="mt-style-panel-title">Kenarlık rengi</div>
|
|
685
|
+
${palette('borderColor')}
|
|
686
|
+
</div>
|
|
687
|
+
${!isLine ? `
|
|
688
|
+
<div class="mt-style-panel" data-style-panel="radius" style="display: ${tab === 'radius' ? 'flex' : 'none'};">
|
|
689
|
+
<div class="mt-style-panel-title">Köşe yuvarlaklığı</div>
|
|
690
|
+
<div class="mt-stepper-group" role="group" aria-label="Köşe yuvarlaklığı">
|
|
691
|
+
<button type="button" class="mt-step-btn" data-step-prop="borderRadius" data-step-dir="-1" title="Köşe yuvarlaklığını azalt">−</button>
|
|
692
|
+
<div class="mt-style-step-value"><span class="mt-step-val" id="mt-val-borderRadius" aria-live="polite">${bRadius}</span><span class="mt-step-unit">px</span></div>
|
|
693
|
+
<button type="button" class="mt-step-btn" data-step-prop="borderRadius" data-step-dir="1" title="Köşe yuvarlaklığını artır">+</button>
|
|
694
|
+
</div>
|
|
695
|
+
</div>` : ''}
|
|
696
|
+
</div>
|
|
697
|
+
`;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (this.activePopup === 'text') {
|
|
701
|
+
const textPrimary = this.selectionManager.getSelectedObjects().find(object => object.type !== 'line') || primary;
|
|
702
|
+
const isBold = textPrimary.fontWeight === 'bold' || textPrimary.fontWeight === '700' || textPrimary.fontWeight === '800';
|
|
703
|
+
const isItalic = textPrimary.fontStyle === 'italic';
|
|
704
|
+
const isUnderline = textPrimary.textDecoration === 'underline';
|
|
705
|
+
const fSize = textPrimary.fontSize || 16;
|
|
706
|
+
const tColor = textPrimary.textColor || '#1e293b';
|
|
707
|
+
const hAlign = textPrimary.textAlign || 'center';
|
|
708
|
+
const vAlign = textPrimary.verticalAlign || 'middle';
|
|
709
|
+
const tab = this.activeTextTab || 'font';
|
|
710
|
+
|
|
711
|
+
const textColors = [
|
|
712
|
+
['#1e293b', 'Koyu gri'], ['#ffffff', 'Beyaz'], ['#cbd5e1', 'Açık gri'],
|
|
713
|
+
['#64748b', 'Gri'], ['#ef4444', 'Kırmızı'], ['#f97316', 'Turuncu'],
|
|
714
|
+
['#eab308', 'Sarı'], ['#22c55e', 'Yeşil'], ['#14b8a6', 'Turkuaz'],
|
|
715
|
+
['#0ea5e9', 'Açık mavi'], ['#2563eb', 'Mavi'], ['#6366f1', 'İndigo'],
|
|
716
|
+
['#8b5cf6', 'Mor'], ['#ec4899', 'Pembe']
|
|
717
|
+
];
|
|
718
|
+
|
|
719
|
+
return `
|
|
720
|
+
<div class="mt-popup-card mt-text-popup ${openClass}" id="mt-text-popup">
|
|
721
|
+
<div class="mt-text-title">Metin</div>
|
|
722
|
+
|
|
723
|
+
<!-- Üst Menü / 3 Sekme Çubuğu -->
|
|
724
|
+
<div class="mt-text-tabs-bar" role="group" aria-label="Metin ayarları">
|
|
725
|
+
<!-- 1. Biçimlendirme ve Font Sekmesi -->
|
|
726
|
+
<button type="button" class="mt-text-tab-btn ${tab === 'font' ? 'active' : ''}" data-text-tab="font" title="Biçim ve yazı boyutu">
|
|
727
|
+
<span class="mt-text-tab-font-icon" style="font-weight: 700; font-size: 0.95rem; line-height: 1;">Aa</span>
|
|
728
|
+
</button>
|
|
729
|
+
|
|
730
|
+
<!-- 2. Hizalama Sekmesi -->
|
|
731
|
+
<button type="button" class="mt-text-tab-btn ${tab === 'align' ? 'active' : ''}" data-text-tab="align" title="Hizalama">
|
|
732
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="15" y2="12"/><line x1="3" y1="18" x2="18" y2="18"/></svg>
|
|
733
|
+
</button>
|
|
734
|
+
|
|
735
|
+
<!-- 3. Renk Sekmesi -->
|
|
736
|
+
<button type="button" class="mt-text-tab-btn ${tab === 'color' ? 'active' : ''}" data-text-tab="color" title="Metin Rengi">
|
|
737
|
+
<span class="mt-text-tab-swatch" id="mt-tab-text-color-swatch" style="background-color: ${tColor};"></span>
|
|
738
|
+
</button>
|
|
739
|
+
</div>
|
|
740
|
+
|
|
741
|
+
<!-- Alt Kart Panelleri -->
|
|
742
|
+
|
|
743
|
+
<!-- Panel 1: Biçimlendirme ve Font -->
|
|
744
|
+
<div class="mt-text-panel" data-text-panel="font" style="display: ${tab === 'font' ? 'flex' : 'none'};">
|
|
745
|
+
<div class="mt-text-format-grid">
|
|
746
|
+
<div class="mt-text-format-column">
|
|
747
|
+
<span class="mt-text-section-label">Biçim</span>
|
|
748
|
+
<div class="mt-pill-group" role="group" aria-label="Metin biçimi">
|
|
749
|
+
<button type="button" class="mt-format-btn ${isBold ? 'active' : ''}" data-text-action="bold" title="Kalın (Bold)"><strong>B</strong></button>
|
|
750
|
+
<button type="button" class="mt-format-btn ${isItalic ? 'active' : ''}" data-text-action="italic" title="İtalik (Italic)"><em>I</em></button>
|
|
751
|
+
<button type="button" class="mt-format-btn ${isUnderline ? 'active' : ''}" data-text-action="underline" title="Altı Çizili (Underline)"><u>U</u></button>
|
|
752
|
+
</div>
|
|
753
|
+
</div>
|
|
754
|
+
<div class="mt-text-format-column">
|
|
755
|
+
<span class="mt-text-section-label">Boyut</span>
|
|
756
|
+
<div class="mt-stepper-group" role="group" aria-label="Yazı boyutu">
|
|
757
|
+
<button type="button" class="mt-step-btn" data-text-action="size-down" title="Yazı boyutunu küçült">−</button>
|
|
758
|
+
<div class="mt-style-step-value">
|
|
759
|
+
<span class="mt-step-val mt-size-val" id="mt-val-fontSize" aria-live="polite">${fSize}</span>
|
|
760
|
+
<span class="mt-step-unit mt-size-unit">px</span>
|
|
761
|
+
</div>
|
|
762
|
+
<button type="button" class="mt-step-btn" data-text-action="size-up" title="Yazı boyutunu büyüt">+</button>
|
|
763
|
+
</div>
|
|
764
|
+
</div>
|
|
765
|
+
</div>
|
|
766
|
+
</div>
|
|
767
|
+
|
|
768
|
+
<!-- Panel 2: Hizalama -->
|
|
769
|
+
<div class="mt-text-panel" data-text-panel="align" style="display: ${tab === 'align' ? 'flex' : 'none'};">
|
|
770
|
+
<div class="mt-text-align-grid">
|
|
771
|
+
<div class="mt-text-align-column">
|
|
772
|
+
<span class="mt-text-section-label">Yatay</span>
|
|
773
|
+
<div class="mt-pill-group" role="group" aria-label="Yatay hizalama">
|
|
774
|
+
<button type="button" class="mt-icon-btn ${hAlign === 'left' ? 'active' : ''}" data-align-h="left" title="Sola Hizala">
|
|
775
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="15" y2="12"/><line x1="3" y1="18" x2="18" y2="18"/></svg>
|
|
776
|
+
</button>
|
|
777
|
+
<button type="button" class="mt-icon-btn ${hAlign === 'center' ? 'active' : ''}" data-align-h="center" title="Ortala">
|
|
778
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="6" x2="21" y2="6"/><line x1="6" y1="12" x2="18" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/></svg>
|
|
779
|
+
</button>
|
|
780
|
+
<button type="button" class="mt-icon-btn ${hAlign === 'right' ? 'active' : ''}" data-align-h="right" title="Sağa Hizala">
|
|
781
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="6" x2="21" y2="6"/><line x1="9" y1="12" x2="21" y2="12"/><line x1="6" y1="18" x2="21" y2="18"/></svg>
|
|
782
|
+
</button>
|
|
783
|
+
</div>
|
|
784
|
+
</div>
|
|
785
|
+
<div class="mt-text-align-column">
|
|
786
|
+
<span class="mt-text-section-label">Dikey</span>
|
|
787
|
+
<div class="mt-pill-group" role="group" aria-label="Dikey hizalama">
|
|
788
|
+
<button type="button" class="mt-icon-btn ${vAlign === 'top' ? 'active' : ''}" data-align-v="top" title="Üste Hizala">
|
|
789
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="4" x2="21" y2="4"/><path d="M12 20V8"/><polyline points="8 12 12 8 16 12"/></svg>
|
|
790
|
+
</button>
|
|
791
|
+
<button type="button" class="mt-icon-btn ${vAlign === 'middle' ? 'active' : ''}" data-align-v="middle" title="Dikey Ortala">
|
|
792
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="12" x2="21" y2="12"/><path d="M8 7l4 5 4-5"/><path d="M8 17l4-5 4 5"/></svg>
|
|
793
|
+
</button>
|
|
794
|
+
<button type="button" class="mt-icon-btn ${vAlign === 'bottom' ? 'active' : ''}" data-align-v="bottom" title="Alta Hizala">
|
|
795
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="20" x2="21" y2="20"/><path d="M12 4v12"/><polyline points="8 12 12 16 16 12"/></svg>
|
|
796
|
+
</button>
|
|
797
|
+
</div>
|
|
798
|
+
</div>
|
|
799
|
+
</div>
|
|
800
|
+
</div>
|
|
801
|
+
|
|
802
|
+
<!-- Panel 3: Renk -->
|
|
803
|
+
<div class="mt-text-panel mt-text-palette-section" data-text-panel="color" style="display: ${tab === 'color' ? 'flex' : 'none'};">
|
|
804
|
+
<div class="mt-text-panel-title">Metin rengi</div>
|
|
805
|
+
<div class="mt-color-palette" role="group" aria-label="Metin rengi">
|
|
806
|
+
${textColors.map(([c, name]) => `
|
|
807
|
+
<button type="button" class="mt-color-swatch ${c.toLowerCase() === (tColor || '').toLowerCase() ? 'is-active-swatch' : ''}" data-text-color-val="${c}" style="background-color: ${c};" title="${name}" aria-label="${name}">
|
|
808
|
+
${c.toLowerCase() === (tColor || '').toLowerCase() ? '<span class="swatch-check">✓</span>' : ''}
|
|
809
|
+
</button>
|
|
810
|
+
`).join('')}
|
|
811
|
+
</div>
|
|
812
|
+
</div>
|
|
813
|
+
</div>
|
|
814
|
+
`;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if (this.activePopup === 'layers') {
|
|
818
|
+
const isLocked = primary.locked;
|
|
819
|
+
return `
|
|
820
|
+
<div class="mt-popup-card mt-layer-popup ${openClass}" id="mt-layer-popup">
|
|
821
|
+
<div class="mt-layer-section">
|
|
822
|
+
<span class="mt-layer-section-label">Katman sırası</span>
|
|
823
|
+
<div class="mt-layer-order-grid" role="group" aria-label="Katman sırası">
|
|
824
|
+
<button type="button" class="mt-layer-btn" data-layer-action="forward" title="Öne Getir">
|
|
825
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>
|
|
826
|
+
<span>Öne</span>
|
|
827
|
+
</button>
|
|
828
|
+
<button type="button" class="mt-layer-btn" data-layer-action="front" title="En Öne Getir">
|
|
829
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 11-5-5-5 5"/><path d="m17 18-5-5-5 5"/></svg>
|
|
830
|
+
<span>En öne</span>
|
|
831
|
+
</button>
|
|
832
|
+
<button type="button" class="mt-layer-btn" data-layer-action="backward" title="Arkaya Gönder">
|
|
833
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
|
|
834
|
+
<span>Arkaya</span>
|
|
835
|
+
</button>
|
|
836
|
+
<button type="button" class="mt-layer-btn" data-layer-action="back" title="En Arkaya Gönder">
|
|
837
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m7 13 5 5 5-5"/><path d="m7 6 5 5 5-5"/></svg>
|
|
838
|
+
<span>En arkaya</span>
|
|
839
|
+
</button>
|
|
840
|
+
</div>
|
|
841
|
+
</div>
|
|
842
|
+
<div class="mt-layer-section mt-layer-lock-section">
|
|
843
|
+
<span class="mt-layer-section-label">Kilit durumu</span>
|
|
844
|
+
<button type="button" class="mt-layer-btn mt-layer-lock-item ${isLocked ? 'is-locked active-lock' : ''}" data-layer-action="toggle-lock" title="${isLocked ? 'Kilidi Aç' : 'Nesneyi Kilitle'}" aria-pressed="${isLocked}">
|
|
845
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
846
|
+
${isLocked
|
|
847
|
+
? '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/>'
|
|
848
|
+
: '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>'}
|
|
849
|
+
</svg>
|
|
850
|
+
<span class="mt-lock-label">${isLocked ? 'Kilidi Aç' : 'Kilit'}</span>
|
|
851
|
+
</button>
|
|
852
|
+
</div>
|
|
853
|
+
</div>
|
|
854
|
+
`;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (this.activePopup === 'align') {
|
|
858
|
+
const selectedObjs = this.selectionManager.getSelectedObjects().filter(o => !o.locked);
|
|
859
|
+
const units = this.getSelectionUnits(selectedObjs);
|
|
860
|
+
const canEqualize = selectedObjs.length >= 2;
|
|
861
|
+
const canDistribute = units.length >= 3;
|
|
862
|
+
|
|
863
|
+
return `
|
|
864
|
+
<div class="mt-popup-card mt-align-popup ${openClass}" id="mt-align-popup">
|
|
865
|
+
<div class="mt-popup-title">Hizalama</div>
|
|
866
|
+
<div class="mt-row">
|
|
867
|
+
<div class="mt-pill-group">
|
|
868
|
+
<button type="button" class="mt-icon-btn" data-multi-align="left" title="Sola Hizala">
|
|
869
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="4" y1="3" x2="4" y2="21"/><rect x="8" y="7" width="12" height="10" rx="1"/></svg>
|
|
870
|
+
</button>
|
|
871
|
+
<button type="button" class="mt-icon-btn" data-multi-align="center" title="Yatay Ortala">
|
|
872
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="3" x2="12" y2="21"/><rect x="6" y="7" width="12" height="10" rx="1"/></svg>
|
|
873
|
+
</button>
|
|
874
|
+
<button type="button" class="mt-icon-btn" data-multi-align="right" title="Sağa Hizala">
|
|
875
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="20" y1="3" x2="20" y2="21"/><rect x="4" y="7" width="12" height="10" rx="1"/></svg>
|
|
876
|
+
</button>
|
|
877
|
+
</div>
|
|
878
|
+
<div class="mt-pill-group">
|
|
879
|
+
<button type="button" class="mt-icon-btn" data-multi-align="top" title="Üste Hizala">
|
|
880
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="4" x2="21" y2="4"/><rect x="7" y="8" width="10" height="12" rx="1"/></svg>
|
|
881
|
+
</button>
|
|
882
|
+
<button type="button" class="mt-icon-btn" data-multi-align="middle" title="Dikey Ortala">
|
|
883
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="12" x2="21" y2="12"/><rect x="7" y="6" width="10" height="12" rx="1"/></svg>
|
|
884
|
+
</button>
|
|
885
|
+
<button type="button" class="mt-icon-btn" data-multi-align="bottom" title="Alta Hizala">
|
|
886
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="20" x2="21" y2="20"/><rect x="7" y="4" width="10" height="12" rx="1"/></svg>
|
|
887
|
+
</button>
|
|
888
|
+
</div>
|
|
889
|
+
</div>
|
|
890
|
+
<div class="mt-row" style="margin-top: 6px; justify-content: space-between;">
|
|
891
|
+
<div class="mt-pill-group">
|
|
892
|
+
<button type="button" class="mt-icon-btn" data-distribute="horizontal" ${canDistribute ? '' : 'disabled'} title="${canDistribute ? 'Yatay Eşit Dağıt' : 'Yatay Eşit Dağıt (En az 3 nesne seçilmeli)'}">
|
|
893
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="4" y1="3" x2="4" y2="21"/><line x1="20" y1="3" x2="20" y2="21"/><rect x="8.5" y="7" width="7" height="10" rx="1"/></svg>
|
|
894
|
+
</button>
|
|
895
|
+
<button type="button" class="mt-icon-btn" data-distribute="vertical" ${canDistribute ? '' : 'disabled'} title="${canDistribute ? 'Dikey Eşit Dağıt' : 'Dikey Eşit Dağıt (En az 3 nesne seçilmeli)'}">
|
|
896
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="4" x2="21" y2="4"/><line x1="3" y1="20" x2="21" y2="20"/><rect x="7" y="8.5" width="10" height="7" rx="1"/></svg>
|
|
897
|
+
</button>
|
|
898
|
+
<button type="button" class="mt-icon-btn" data-equalize-width="min" ${canEqualize ? '' : 'disabled'} title="${canEqualize ? 'En Kısa Genişliğe Eşitle' : 'En Kısa Genişliğe Eşitle (En az 2 nesne seçilmeli)'}">
|
|
899
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><rect x="8" y="5" width="8" height="14" rx="1.5"/><path d="M2 12h4m-2-2.5l2.5 2.5-2.5 2.5M22 12h-4m2-2.5l-2.5 2.5 2.5 2.5"/></svg>
|
|
900
|
+
</button>
|
|
901
|
+
<button type="button" class="mt-icon-btn" data-equalize-width="max" ${canEqualize ? '' : 'disabled'} title="${canEqualize ? 'En Uzun Genişliğe Eşitle' : 'En Uzun Genişliğe Eşitle (En az 2 nesne seçilmeli)'}">
|
|
902
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><rect x="5" y="5" width="14" height="14" rx="1.5"/><path d="M9 12H4m2.5-2.5L4 12l2.5 2.5M15 12h5m-2.5-2.5l2.5 2.5-2.5 2.5"/></svg>
|
|
903
|
+
</button>
|
|
904
|
+
</div>
|
|
905
|
+
<button type="button" class="mt-group-action-btn ${hasGroup ? 'active-group' : ''}" data-action="toggle-group" title="${hasGroup ? 'Grubu Çöz (Ctrl+Shift+G)' : 'Grupla (Ctrl+G)'}">
|
|
906
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-dasharray="3 3"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>
|
|
907
|
+
<span>${hasGroup ? 'Grubu Çöz' : 'Grupla'}</span>
|
|
908
|
+
</button>
|
|
909
|
+
</div>
|
|
910
|
+
</div>
|
|
911
|
+
`;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
return '';
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
bindPopupInteractions(primary) {
|
|
918
|
+
if (!primary) return;
|
|
919
|
+
const getTargets = () => this.selectionManager.getSelectedObjects();
|
|
920
|
+
const getTextTargets = () => getTargets().filter(o => o.type !== 'line' && (o.type === 'text' || o.text !== undefined || o.role === 'category' || o.role === 'sequence_slot'));
|
|
921
|
+
const textPrimary = getTextTargets()[0] || primary;
|
|
922
|
+
|
|
923
|
+
// İkonlu kontrollerin adı ve seçili durumu yardımcı teknolojilere de aktarılır.
|
|
924
|
+
const syncAppearanceControls = () => {
|
|
925
|
+
this.toolbarEl.querySelectorAll('.mt-line-popup button, .mt-style-popup button, .mt-text-popup button').forEach(button => {
|
|
926
|
+
if (!button.hasAttribute('data-step-prop') && !['size-up', 'size-down'].includes(button.getAttribute('data-text-action'))) {
|
|
927
|
+
button.setAttribute('aria-pressed', String(button.classList.contains('active') || button.classList.contains('is-active') || button.classList.contains('is-active-swatch')));
|
|
928
|
+
}
|
|
929
|
+
if (!button.hasAttribute('aria-label') && button.title) {
|
|
930
|
+
button.setAttribute('aria-label', button.title);
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
};
|
|
934
|
+
syncAppearanceControls();
|
|
935
|
+
|
|
936
|
+
// Görünüm sekmeleri içerikleri yerinde değiştirir, klavye odağı korunur.
|
|
937
|
+
this.toolbarEl.querySelectorAll('[data-style-tab]').forEach(btn => {
|
|
938
|
+
btn.onclick = (e) => {
|
|
939
|
+
e.stopPropagation();
|
|
940
|
+
this.activeStyleTab = btn.getAttribute('data-style-tab');
|
|
941
|
+
this.toolbarEl.querySelectorAll('[data-style-tab]').forEach(button => {
|
|
942
|
+
button.classList.toggle('active', button === btn);
|
|
943
|
+
});
|
|
944
|
+
this.toolbarEl.querySelectorAll('[data-style-panel]').forEach(panel => {
|
|
945
|
+
panel.style.display = panel.getAttribute('data-style-panel') === this.activeStyleTab ? 'flex' : 'none';
|
|
946
|
+
});
|
|
947
|
+
syncAppearanceControls();
|
|
948
|
+
this.reposition();
|
|
949
|
+
};
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
// Çizgi Popup Sekme Değişimi
|
|
953
|
+
this.toolbarEl.querySelectorAll('[data-line-tab]').forEach(btn => {
|
|
954
|
+
btn.onclick = (e) => {
|
|
955
|
+
e.stopPropagation();
|
|
956
|
+
const tab = btn.getAttribute('data-line-tab');
|
|
957
|
+
this.activeLineTab = tab;
|
|
958
|
+
this.toolbarEl.querySelectorAll('[data-line-tab]').forEach(b => b.classList.toggle('active', b === btn));
|
|
959
|
+
this.toolbarEl.querySelectorAll('.mt-line-panel').forEach(p => {
|
|
960
|
+
p.style.display = p.getAttribute('data-panel') === tab ? 'flex' : 'none';
|
|
961
|
+
});
|
|
962
|
+
syncAppearanceControls();
|
|
963
|
+
this.reposition();
|
|
964
|
+
};
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
// Çizgi Tipi Seçimi
|
|
968
|
+
this.toolbarEl.querySelectorAll('[data-line-style]').forEach(btn => {
|
|
969
|
+
btn.onclick = (e) => {
|
|
970
|
+
e.stopPropagation();
|
|
971
|
+
const style = btn.getAttribute('data-line-style');
|
|
972
|
+
this.toolbarEl.querySelectorAll('[data-line-style]').forEach(b => b.classList.remove('active'));
|
|
973
|
+
btn.classList.add('active');
|
|
974
|
+
const targets = getTargets();
|
|
975
|
+
targets.forEach((o, i) => this.objectManager.updateObject(o.id, { borderStyle: style }, i === targets.length - 1));
|
|
976
|
+
syncAppearanceControls();
|
|
977
|
+
};
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
// Çizgi Kalınlığı Hızlı Seçim Butonları
|
|
981
|
+
const updateLineWidth = (newWidth) => {
|
|
982
|
+
const clamped = Math.max(1, Math.min(24, Math.round(newWidth)));
|
|
983
|
+
const targets = getTargets();
|
|
984
|
+
targets.forEach((o, i) => this.objectManager.updateObject(o.id, { borderWidth: clamped }, i === targets.length - 1));
|
|
985
|
+
const tabValEl = this.root.getElementById('mt-tab-width-val');
|
|
986
|
+
if (tabValEl) tabValEl.textContent = `${clamped}`;
|
|
987
|
+
this.toolbarEl.querySelectorAll('[data-line-width-preset]').forEach(b => {
|
|
988
|
+
const pVal = parseInt(b.getAttribute('data-line-width-preset'), 10);
|
|
989
|
+
b.classList.toggle('active', pVal === clamped);
|
|
990
|
+
});
|
|
991
|
+
syncAppearanceControls();
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
this.toolbarEl.querySelectorAll('[data-line-width-preset]').forEach(btn => {
|
|
995
|
+
btn.onclick = (e) => {
|
|
996
|
+
e.stopPropagation();
|
|
997
|
+
const preset = parseInt(btn.getAttribute('data-line-width-preset'), 10);
|
|
998
|
+
updateLineWidth(preset);
|
|
999
|
+
};
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
// Çizgi Rengi Seçimi (Palet Swatchları)
|
|
1003
|
+
const updateLineColor = (hex) => {
|
|
1004
|
+
const targets = getTargets();
|
|
1005
|
+
targets.forEach((o, i) => this.objectManager.updateObject(o.id, { borderColor: hex }, i === targets.length - 1));
|
|
1006
|
+
const swatchEl = this.root.getElementById('mt-tab-color-swatch');
|
|
1007
|
+
if (swatchEl) swatchEl.style.backgroundColor = hex;
|
|
1008
|
+
this.toolbarEl.querySelectorAll('[data-line-color]').forEach(b => {
|
|
1009
|
+
b.classList.toggle('is-active', (b.getAttribute('data-line-color') || '').toLowerCase() === hex.toLowerCase());
|
|
1010
|
+
});
|
|
1011
|
+
syncAppearanceControls();
|
|
1012
|
+
};
|
|
1013
|
+
|
|
1014
|
+
this.toolbarEl.querySelectorAll('[data-line-color]').forEach(btn => {
|
|
1015
|
+
btn.onclick = (e) => {
|
|
1016
|
+
e.stopPropagation();
|
|
1017
|
+
const color = btn.getAttribute('data-line-color');
|
|
1018
|
+
updateLineColor(color);
|
|
1019
|
+
};
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
// Çizgi Hizalama (Yatay Yap / Dikey Yap)
|
|
1023
|
+
this.toolbarEl.querySelectorAll('[data-line-align]').forEach(btn => {
|
|
1024
|
+
btn.onclick = (e) => {
|
|
1025
|
+
e.stopPropagation();
|
|
1026
|
+
const align = btn.getAttribute('data-line-align');
|
|
1027
|
+
const rot = align === 'horizontal' ? 0 : 90;
|
|
1028
|
+
this.toolbarEl.querySelectorAll('[data-line-align]').forEach(b => b.classList.remove('active'));
|
|
1029
|
+
btn.classList.add('active');
|
|
1030
|
+
const targets = getTargets();
|
|
1031
|
+
targets.forEach((o, i) => this.objectManager.updateObject(o.id, { rotation: rot }, i === targets.length - 1));
|
|
1032
|
+
syncAppearanceControls();
|
|
1033
|
+
this.selectionManager.updateSelectionBox();
|
|
1034
|
+
};
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
// Çizgi Ok Uçları (Uçsuz / Başlangıç / Bitiş / Çift Yön)
|
|
1038
|
+
this.toolbarEl.querySelectorAll('[data-line-arrow]').forEach(btn => {
|
|
1039
|
+
btn.onclick = (e) => {
|
|
1040
|
+
e.stopPropagation();
|
|
1041
|
+
const arrowType = btn.getAttribute('data-line-arrow');
|
|
1042
|
+
let start = false;
|
|
1043
|
+
let end = false;
|
|
1044
|
+
if (arrowType === 'start') {
|
|
1045
|
+
start = true;
|
|
1046
|
+
end = false;
|
|
1047
|
+
} else if (arrowType === 'end') {
|
|
1048
|
+
start = false;
|
|
1049
|
+
end = true;
|
|
1050
|
+
} else if (arrowType === 'both') {
|
|
1051
|
+
start = true;
|
|
1052
|
+
end = true;
|
|
1053
|
+
} else if (arrowType === 'none') {
|
|
1054
|
+
start = false;
|
|
1055
|
+
end = false;
|
|
1056
|
+
}
|
|
1057
|
+
this.toolbarEl.querySelectorAll('[data-line-arrow]').forEach(b => b.classList.remove('active'));
|
|
1058
|
+
btn.classList.add('active');
|
|
1059
|
+
const targets = getTargets();
|
|
1060
|
+
targets.forEach((o, i) => this.objectManager.updateObject(o.id, { arrowStart: start, arrowEnd: end }, i === targets.length - 1));
|
|
1061
|
+
syncAppearanceControls();
|
|
1062
|
+
};
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
// Rol Seçimi
|
|
1066
|
+
this.toolbarEl.querySelectorAll('[data-role-val]').forEach(btn => {
|
|
1067
|
+
btn.onclick = (e) => {
|
|
1068
|
+
e.stopPropagation();
|
|
1069
|
+
const role = btn.getAttribute('data-role-val');
|
|
1070
|
+
this.toolbarEl.querySelectorAll('[data-role-val]').forEach(b => {
|
|
1071
|
+
b.classList.remove('active');
|
|
1072
|
+
b.setAttribute('aria-checked', 'false');
|
|
1073
|
+
});
|
|
1074
|
+
btn.classList.add('active');
|
|
1075
|
+
btn.setAttribute('aria-checked', 'true');
|
|
1076
|
+
const targets = getTargets();
|
|
1077
|
+
targets.forEach((o, i) => this.objectManager.updateObject(o.id, { role }, i === targets.length - 1));
|
|
1078
|
+
};
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
// Kenarlık Stili
|
|
1082
|
+
this.toolbarEl.querySelectorAll('[data-border-style]').forEach(btn => {
|
|
1083
|
+
btn.onclick = (e) => {
|
|
1084
|
+
e.stopPropagation();
|
|
1085
|
+
const style = btn.getAttribute('data-border-style');
|
|
1086
|
+
this.toolbarEl.querySelectorAll('[data-border-style]').forEach(b => b.classList.remove('active'));
|
|
1087
|
+
btn.classList.add('active');
|
|
1088
|
+
const targets = getTargets();
|
|
1089
|
+
targets.forEach((o, i) => this.objectManager.updateObject(o.id, { borderStyle: style }, i === targets.length - 1));
|
|
1090
|
+
syncAppearanceControls();
|
|
1091
|
+
};
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
// Kalınlık ve Köşe Yuvarlaklığı (+/-) Stepper Etkileşimi
|
|
1095
|
+
this.toolbarEl.querySelectorAll('[data-step-prop]').forEach(btn => {
|
|
1096
|
+
btn.onclick = (e) => {
|
|
1097
|
+
e.stopPropagation();
|
|
1098
|
+
const prop = btn.getAttribute('data-step-prop'); // 'borderWidth' | 'borderRadius'
|
|
1099
|
+
const dir = parseInt(btn.getAttribute('data-step-dir'), 10); // -1 | 1
|
|
1100
|
+
const isLine = primary.type === 'line';
|
|
1101
|
+
const step = prop === 'borderRadius' ? 2 : 1;
|
|
1102
|
+
const minVal = isLine && prop === 'borderWidth' ? 1 : 0;
|
|
1103
|
+
const maxVal = prop === 'borderRadius' ? 50 : 24;
|
|
1104
|
+
|
|
1105
|
+
const currentVal = primary[prop] !== undefined ? primary[prop] : (prop === 'borderRadius' ? 8 : 2);
|
|
1106
|
+
const newVal = Math.max(minVal, Math.min(maxVal, currentVal + (dir * step)));
|
|
1107
|
+
|
|
1108
|
+
const targets = getTargets();
|
|
1109
|
+
targets.forEach((o, i) => this.objectManager.updateObject(o.id, { [prop]: newVal }, i === targets.length - 1));
|
|
1110
|
+
|
|
1111
|
+
const valEl = this.toolbarEl.querySelector(`#mt-val-${prop}`);
|
|
1112
|
+
if (valEl) {
|
|
1113
|
+
valEl.textContent = newVal;
|
|
1114
|
+
}
|
|
1115
|
+
const tabValue = this.toolbarEl.querySelector(`[data-style-value="${prop}"]`);
|
|
1116
|
+
if (tabValue) tabValue.textContent = newVal;
|
|
1117
|
+
};
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
// Her palet kendi özelliğini taşır; sekmeler arasında dolgu ve kenarlık karışmaz.
|
|
1121
|
+
this.toolbarEl.querySelectorAll('[data-style-color]').forEach(btn => {
|
|
1122
|
+
btn.onclick = (e) => {
|
|
1123
|
+
e.stopPropagation();
|
|
1124
|
+
const color = btn.getAttribute('data-style-color');
|
|
1125
|
+
const prop = btn.getAttribute('data-style-color-prop');
|
|
1126
|
+
const targets = getTargets();
|
|
1127
|
+
targets.forEach((object, i) => {
|
|
1128
|
+
const changes = { [prop]: color };
|
|
1129
|
+
if (prop === 'borderColor') {
|
|
1130
|
+
changes.borderStyle = color === 'transparent' ? 'none' : (object.borderStyle === 'none' ? 'solid' : object.borderStyle || 'solid');
|
|
1131
|
+
}
|
|
1132
|
+
this.objectManager.updateObject(object.id, changes, i === targets.length - 1);
|
|
1133
|
+
});
|
|
1134
|
+
this.toolbarEl.querySelectorAll(`[data-style-color-prop="${prop}"]`).forEach(button => {
|
|
1135
|
+
button.classList.toggle('is-active', button.getAttribute('data-style-color') === color);
|
|
1136
|
+
});
|
|
1137
|
+
const preview = this.toolbarEl.querySelector(`[data-style-preview="${prop}"]`);
|
|
1138
|
+
if (preview) {
|
|
1139
|
+
preview.style[prop] = color;
|
|
1140
|
+
preview.classList.toggle('is-transparent', color === 'transparent');
|
|
1141
|
+
}
|
|
1142
|
+
if (prop === 'borderColor') {
|
|
1143
|
+
this.toolbarEl.querySelectorAll('[data-border-style]').forEach(button => {
|
|
1144
|
+
button.classList.toggle('active', button.getAttribute('data-border-style') === primary.borderStyle);
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
syncAppearanceControls();
|
|
1148
|
+
};
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
// Metin Sekmeleri Değiştirme (Biçimlendirme/Font, Hizalama, Renk)
|
|
1152
|
+
this.toolbarEl.querySelectorAll('[data-text-tab]').forEach(btn => {
|
|
1153
|
+
btn.onclick = (e) => {
|
|
1154
|
+
e.stopPropagation();
|
|
1155
|
+
this.activeTextTab = btn.getAttribute('data-text-tab');
|
|
1156
|
+
this.toolbarEl.querySelectorAll('[data-text-tab]').forEach(button => {
|
|
1157
|
+
button.classList.toggle('active', button === btn);
|
|
1158
|
+
});
|
|
1159
|
+
this.toolbarEl.querySelectorAll('[data-text-panel]').forEach(panel => {
|
|
1160
|
+
panel.style.display = panel.getAttribute('data-text-panel') === this.activeTextTab ? 'flex' : 'none';
|
|
1161
|
+
});
|
|
1162
|
+
syncAppearanceControls();
|
|
1163
|
+
this.reposition();
|
|
1164
|
+
};
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
// Metin Formatlama
|
|
1168
|
+
this.toolbarEl.querySelectorAll('[data-text-action]').forEach(btn => {
|
|
1169
|
+
btn.onclick = (e) => {
|
|
1170
|
+
e.stopPropagation();
|
|
1171
|
+
const act = btn.getAttribute('data-text-action');
|
|
1172
|
+
const tTargets = getTextTargets();
|
|
1173
|
+
|
|
1174
|
+
if (act === 'bold') {
|
|
1175
|
+
const isBold = textPrimary.fontWeight === 'bold' || textPrimary.fontWeight === '700' || textPrimary.fontWeight === '800';
|
|
1176
|
+
const newWeight = isBold ? 'normal' : '700';
|
|
1177
|
+
btn.classList.toggle('active', newWeight === '700');
|
|
1178
|
+
tTargets.forEach((o, i) => this.objectManager.updateObject(o.id, { fontWeight: newWeight }, i === tTargets.length - 1));
|
|
1179
|
+
} else if (act === 'italic') {
|
|
1180
|
+
const isItalic = textPrimary.fontStyle === 'italic';
|
|
1181
|
+
const newStyle = isItalic ? 'normal' : 'italic';
|
|
1182
|
+
btn.classList.toggle('active', newStyle === 'italic');
|
|
1183
|
+
tTargets.forEach((o, i) => this.objectManager.updateObject(o.id, { fontStyle: newStyle }, i === tTargets.length - 1));
|
|
1184
|
+
} else if (act === 'underline') {
|
|
1185
|
+
const isUnderline = textPrimary.textDecoration === 'underline';
|
|
1186
|
+
const newDecor = isUnderline ? 'none' : 'underline';
|
|
1187
|
+
btn.classList.toggle('active', newDecor === 'underline');
|
|
1188
|
+
tTargets.forEach((o, i) => this.objectManager.updateObject(o.id, { textDecoration: newDecor }, i === tTargets.length - 1));
|
|
1189
|
+
} else if (act === 'size-up' || act === 'size-down') {
|
|
1190
|
+
const delta = act === 'size-up' ? 2 : -2;
|
|
1191
|
+
tTargets.forEach((o, i) => this.objectManager.updateObject(o.id, { fontSize: Math.max(10, (o.fontSize || 16) + delta) }, i === tTargets.length - 1));
|
|
1192
|
+
const newSize = textPrimary.fontSize || 16;
|
|
1193
|
+
const sizeVal = this.toolbarEl.querySelector('.mt-size-val');
|
|
1194
|
+
if (sizeVal) sizeVal.textContent = newSize;
|
|
1195
|
+
}
|
|
1196
|
+
syncAppearanceControls();
|
|
1197
|
+
};
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
// Metin Tanımlı Renk Seçimi
|
|
1201
|
+
this.toolbarEl.querySelectorAll('[data-text-color-val]').forEach(btn => {
|
|
1202
|
+
btn.onclick = (e) => {
|
|
1203
|
+
e.stopPropagation();
|
|
1204
|
+
const c = btn.getAttribute('data-text-color-val');
|
|
1205
|
+
const tabSwatch = this.toolbarEl.querySelector('#mt-tab-text-color-swatch');
|
|
1206
|
+
if (tabSwatch) tabSwatch.style.backgroundColor = c;
|
|
1207
|
+
this.toolbarEl.querySelectorAll('[data-text-color-val]').forEach(b => {
|
|
1208
|
+
b.classList.remove('is-active-swatch');
|
|
1209
|
+
b.innerHTML = '';
|
|
1210
|
+
});
|
|
1211
|
+
btn.classList.add('is-active-swatch');
|
|
1212
|
+
btn.innerHTML = '<span class="swatch-check">✓</span>';
|
|
1213
|
+
const tTargets = getTextTargets();
|
|
1214
|
+
tTargets.forEach((o, i) => this.objectManager.updateObject(o.id, { textColor: c }, i === tTargets.length - 1));
|
|
1215
|
+
syncAppearanceControls();
|
|
1216
|
+
};
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
// Yatay Hizalama
|
|
1220
|
+
this.toolbarEl.querySelectorAll('[data-align-h]').forEach(btn => {
|
|
1221
|
+
btn.onclick = (e) => {
|
|
1222
|
+
e.stopPropagation();
|
|
1223
|
+
const align = btn.getAttribute('data-align-h');
|
|
1224
|
+
this.toolbarEl.querySelectorAll('[data-align-h]').forEach(b => b.classList.remove('active'));
|
|
1225
|
+
btn.classList.add('active');
|
|
1226
|
+
const tTargets = getTextTargets();
|
|
1227
|
+
tTargets.forEach((o, i) => this.objectManager.updateObject(o.id, { textAlign: align }, i === tTargets.length - 1));
|
|
1228
|
+
syncAppearanceControls();
|
|
1229
|
+
};
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
// Dikey Hizalama
|
|
1233
|
+
this.toolbarEl.querySelectorAll('[data-align-v]').forEach(btn => {
|
|
1234
|
+
btn.onclick = (e) => {
|
|
1235
|
+
e.stopPropagation();
|
|
1236
|
+
const vAlign = btn.getAttribute('data-align-v');
|
|
1237
|
+
this.toolbarEl.querySelectorAll('[data-align-v]').forEach(b => b.classList.remove('active'));
|
|
1238
|
+
btn.classList.add('active');
|
|
1239
|
+
const tTargets = getTextTargets();
|
|
1240
|
+
tTargets.forEach((o, i) => this.objectManager.updateObject(o.id, { verticalAlign: vAlign }, i === tTargets.length - 1));
|
|
1241
|
+
syncAppearanceControls();
|
|
1242
|
+
};
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
// Katman / Kilit Aksiyonları
|
|
1246
|
+
this.toolbarEl.querySelectorAll('[data-layer-action]').forEach(btn => {
|
|
1247
|
+
btn.onclick = (e) => {
|
|
1248
|
+
e.stopPropagation();
|
|
1249
|
+
const act = btn.getAttribute('data-layer-action');
|
|
1250
|
+
const targets = getTargets();
|
|
1251
|
+
if (act === 'forward') {
|
|
1252
|
+
const sorted = [...targets].sort((a, b) => (b.zIndex || 1) - (a.zIndex || 1));
|
|
1253
|
+
sorted.forEach(o => this.layerManager.bringForward(o.id, false));
|
|
1254
|
+
this.selectionManager.updateSelectionBox();
|
|
1255
|
+
this.core.pushHistoryState('Katmanı Öne Al');
|
|
1256
|
+
} else if (act === 'front') {
|
|
1257
|
+
const sorted = [...targets].sort((a, b) => (a.zIndex || 1) - (b.zIndex || 1));
|
|
1258
|
+
sorted.forEach(o => this.layerManager.bringToFront(o.id, false));
|
|
1259
|
+
this.selectionManager.updateSelectionBox();
|
|
1260
|
+
this.core.pushHistoryState('Katmanı En Öne Al');
|
|
1261
|
+
} else if (act === 'backward') {
|
|
1262
|
+
const sorted = [...targets].sort((a, b) => (a.zIndex || 1) - (b.zIndex || 1));
|
|
1263
|
+
sorted.forEach(o => this.layerManager.sendBackward(o.id, false));
|
|
1264
|
+
this.selectionManager.updateSelectionBox();
|
|
1265
|
+
this.core.pushHistoryState('Katmanı Arkaya Al');
|
|
1266
|
+
} else if (act === 'back') {
|
|
1267
|
+
const sorted = [...targets].sort((a, b) => (b.zIndex || 1) - (a.zIndex || 1));
|
|
1268
|
+
sorted.forEach(o => this.layerManager.sendToBack(o.id, false));
|
|
1269
|
+
this.selectionManager.updateSelectionBox();
|
|
1270
|
+
this.core.pushHistoryState('Katmanı En Arkaya Al');
|
|
1271
|
+
} else if (act === 'toggle-lock') {
|
|
1272
|
+
const allLocked = targets.every(o => o.locked);
|
|
1273
|
+
const newLocked = !allLocked;
|
|
1274
|
+
btn.classList.toggle('active-lock', newLocked);
|
|
1275
|
+
btn.classList.toggle('is-locked', newLocked);
|
|
1276
|
+
btn.setAttribute('aria-pressed', String(newLocked));
|
|
1277
|
+
btn.title = newLocked ? 'Kilidi Aç' : 'Nesneyi Kilitle';
|
|
1278
|
+
const lockIcon = btn.querySelector('svg');
|
|
1279
|
+
if (lockIcon) {
|
|
1280
|
+
lockIcon.innerHTML = newLocked
|
|
1281
|
+
? '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/>'
|
|
1282
|
+
: '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>';
|
|
1283
|
+
}
|
|
1284
|
+
const labelSpan = btn.querySelector('.mt-lock-label') || btn.querySelector('span');
|
|
1285
|
+
if (labelSpan) {
|
|
1286
|
+
labelSpan.textContent = newLocked ? 'Kilidi Aç' : 'Kilit';
|
|
1287
|
+
}
|
|
1288
|
+
targets.forEach(o => this.objectManager.updateObject(o.id, { locked: newLocked }, false));
|
|
1289
|
+
this.selectionManager.updateSelectionBox();
|
|
1290
|
+
this.core.pushHistoryState(newLocked ? 'Nesneyi Kilitle' : 'Kilidi Aç');
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
// Çoklu Nesne Hizalama
|
|
1296
|
+
this.toolbarEl.querySelectorAll('[data-multi-align]').forEach(btn => {
|
|
1297
|
+
btn.onclick = (e) => {
|
|
1298
|
+
e.stopPropagation();
|
|
1299
|
+
const dir = btn.getAttribute('data-multi-align');
|
|
1300
|
+
this.alignSelectedObjects(dir);
|
|
1301
|
+
this.closePopup();
|
|
1302
|
+
};
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
// Eşit Dağıtma
|
|
1306
|
+
this.toolbarEl.querySelectorAll('[data-distribute]').forEach(btn => {
|
|
1307
|
+
btn.onclick = (e) => {
|
|
1308
|
+
e.stopPropagation();
|
|
1309
|
+
const axis = btn.getAttribute('data-distribute');
|
|
1310
|
+
this.distributeSelected(axis);
|
|
1311
|
+
this.closePopup();
|
|
1312
|
+
};
|
|
1313
|
+
});
|
|
1314
|
+
|
|
1315
|
+
// Genişlik Eşitleme (En Kısa / En Uzun)
|
|
1316
|
+
this.toolbarEl.querySelectorAll('[data-equalize-width]').forEach(btn => {
|
|
1317
|
+
btn.onclick = (e) => {
|
|
1318
|
+
e.stopPropagation();
|
|
1319
|
+
const mode = btn.getAttribute('data-equalize-width');
|
|
1320
|
+
this.equalizeSelectedWidth(mode);
|
|
1321
|
+
this.closePopup();
|
|
1322
|
+
};
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
// Çoklu Seçim Grupla / Grubu Çöz
|
|
1326
|
+
this.toolbarEl.querySelectorAll('[data-action="toggle-group"]').forEach(btn => {
|
|
1327
|
+
btn.onclick = (e) => {
|
|
1328
|
+
e.stopPropagation();
|
|
1329
|
+
const targets = this.selectionManager.getSelectedObjects();
|
|
1330
|
+
const hasGroup = targets.some(o => o.groupId);
|
|
1331
|
+
if (hasGroup) {
|
|
1332
|
+
this.selectionManager.ungroupSelected();
|
|
1333
|
+
} else {
|
|
1334
|
+
this.selectionManager.groupSelected();
|
|
1335
|
+
}
|
|
1336
|
+
this.closePopup();
|
|
1337
|
+
this.render();
|
|
1338
|
+
};
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
getSelectionUnits(selectedObjects) {
|
|
1343
|
+
if (!selectedObjects || selectedObjects.length === 0) return [];
|
|
1344
|
+
const unitsMap = new Map();
|
|
1345
|
+
const standaloneUnits = [];
|
|
1346
|
+
|
|
1347
|
+
selectedObjects.forEach(obj => {
|
|
1348
|
+
if (obj.groupId) {
|
|
1349
|
+
if (!unitsMap.has(obj.groupId)) {
|
|
1350
|
+
unitsMap.set(obj.groupId, []);
|
|
1351
|
+
}
|
|
1352
|
+
unitsMap.get(obj.groupId).push(obj);
|
|
1353
|
+
} else {
|
|
1354
|
+
standaloneUnits.push({
|
|
1355
|
+
isGroup: false,
|
|
1356
|
+
groupId: null,
|
|
1357
|
+
objects: [obj],
|
|
1358
|
+
minX: obj.x,
|
|
1359
|
+
maxX: obj.x + obj.width,
|
|
1360
|
+
minY: obj.y,
|
|
1361
|
+
maxY: obj.y + obj.height,
|
|
1362
|
+
width: obj.width,
|
|
1363
|
+
height: obj.height
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
|
|
1368
|
+
const groupUnits = Array.from(unitsMap.entries()).map(([groupId, objs]) => {
|
|
1369
|
+
const minX = Math.min(...objs.map(o => o.x));
|
|
1370
|
+
const maxX = Math.max(...objs.map(o => o.x + o.width));
|
|
1371
|
+
const minY = Math.min(...objs.map(o => o.y));
|
|
1372
|
+
const maxY = Math.max(...objs.map(o => o.y + o.height));
|
|
1373
|
+
return {
|
|
1374
|
+
isGroup: true,
|
|
1375
|
+
groupId,
|
|
1376
|
+
objects: objs,
|
|
1377
|
+
minX,
|
|
1378
|
+
maxX,
|
|
1379
|
+
minY,
|
|
1380
|
+
maxY,
|
|
1381
|
+
width: maxX - minX,
|
|
1382
|
+
height: maxY - minY
|
|
1383
|
+
};
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
return [...groupUnits, ...standaloneUnits];
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
distributeSelected(axis) {
|
|
1390
|
+
const selected = this.selectionManager.getSelectedObjects().filter(o => !o.locked);
|
|
1391
|
+
const units = this.getSelectionUnits(selected);
|
|
1392
|
+
if (units.length < 3) return;
|
|
1393
|
+
|
|
1394
|
+
if (axis === 'horizontal') {
|
|
1395
|
+
units.sort((a, b) => a.minX - b.minX);
|
|
1396
|
+
const minX = units[0].minX;
|
|
1397
|
+
const lastUnit = units[units.length - 1];
|
|
1398
|
+
const maxX = lastUnit.maxX;
|
|
1399
|
+
const totalWidths = units.reduce((s, u) => s + u.width, 0);
|
|
1400
|
+
const totalGap = (maxX - minX) - totalWidths;
|
|
1401
|
+
const gap = totalGap / (units.length - 1);
|
|
1402
|
+
|
|
1403
|
+
let curX = minX;
|
|
1404
|
+
units.forEach(u => {
|
|
1405
|
+
const deltaX = Math.round(curX) - u.minX;
|
|
1406
|
+
u.objects.forEach(o => {
|
|
1407
|
+
this.objectManager.updateObject(o.id, { x: Math.round(o.x + deltaX) }, false);
|
|
1408
|
+
});
|
|
1409
|
+
curX += u.width + gap;
|
|
1410
|
+
});
|
|
1411
|
+
} else if (axis === 'vertical') {
|
|
1412
|
+
units.sort((a, b) => a.minY - b.minY);
|
|
1413
|
+
const minY = units[0].minY;
|
|
1414
|
+
const lastUnit = units[units.length - 1];
|
|
1415
|
+
const maxY = lastUnit.maxY;
|
|
1416
|
+
const totalHeights = units.reduce((s, u) => s + u.height, 0);
|
|
1417
|
+
const totalGap = (maxY - minY) - totalHeights;
|
|
1418
|
+
const gap = totalGap / (units.length - 1);
|
|
1419
|
+
|
|
1420
|
+
let curY = minY;
|
|
1421
|
+
units.forEach(u => {
|
|
1422
|
+
const deltaY = Math.round(curY) - u.minY;
|
|
1423
|
+
u.objects.forEach(o => {
|
|
1424
|
+
this.objectManager.updateObject(o.id, { y: Math.round(o.y + deltaY) }, false);
|
|
1425
|
+
});
|
|
1426
|
+
curY += u.height + gap;
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
if (this.core && this.core.pushHistoryState) {
|
|
1431
|
+
this.core.pushHistoryState('Eşit Dağıt');
|
|
1432
|
+
}
|
|
1433
|
+
this.selectionManager.updateSelectionBox();
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
equalizeSelectedWidth(mode) {
|
|
1437
|
+
const selected = this.selectionManager.getSelectedObjects().filter(o => !o.locked);
|
|
1438
|
+
if (selected.length < 2) return;
|
|
1439
|
+
|
|
1440
|
+
const targetWidth = mode === 'min'
|
|
1441
|
+
? Math.min(...selected.map(o => o.width))
|
|
1442
|
+
: Math.max(...selected.map(o => o.width));
|
|
1443
|
+
|
|
1444
|
+
const stageW = (this.core && this.core.data && this.core.data.stage && this.core.data.stage.width) || 1200;
|
|
1445
|
+
|
|
1446
|
+
selected.forEach(o => {
|
|
1447
|
+
let newX = o.x;
|
|
1448
|
+
if (newX + targetWidth > stageW) {
|
|
1449
|
+
newX = Math.max(0, stageW - targetWidth);
|
|
1450
|
+
}
|
|
1451
|
+
this.objectManager.updateObject(o.id, { width: targetWidth, x: newX }, false);
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1454
|
+
if (this.core && this.core.pushHistoryState) {
|
|
1455
|
+
this.core.pushHistoryState('Genişlikleri Eşitle');
|
|
1456
|
+
}
|
|
1457
|
+
this.selectionManager.updateSelectionBox();
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
alignSelectedObjects(direction) {
|
|
1461
|
+
const selected = this.selectionManager.getSelectedObjects().filter(o => !o.locked);
|
|
1462
|
+
if (!selected || selected.length === 0) return;
|
|
1463
|
+
|
|
1464
|
+
const units = this.getSelectionUnits(selected);
|
|
1465
|
+
if (units.length === 0) return;
|
|
1466
|
+
|
|
1467
|
+
const stageW = (this.stageManager && this.stageManager.logicalWidth) || (this.core && this.core.data && this.core.data.stage && this.core.data.stage.width) || 1200;
|
|
1468
|
+
const stageH = (this.stageManager && this.stageManager.logicalHeight) || (this.core && this.core.data && this.core.data.stage && this.core.data.stage.height) || 675;
|
|
1469
|
+
|
|
1470
|
+
if (units.length === 1) {
|
|
1471
|
+
// Tek bir birim (örneğin grup haline getirilmiş bir nesneler topluluğu) sahneye tek parça olarak hizalanır
|
|
1472
|
+
const unit = units[0];
|
|
1473
|
+
let deltaX = 0;
|
|
1474
|
+
let deltaY = 0;
|
|
1475
|
+
|
|
1476
|
+
if (direction === 'left') {
|
|
1477
|
+
deltaX = 0 - unit.minX;
|
|
1478
|
+
} else if (direction === 'right') {
|
|
1479
|
+
deltaX = (stageW - unit.width) - unit.minX;
|
|
1480
|
+
} else if (direction === 'center') {
|
|
1481
|
+
deltaX = Math.round((stageW - unit.width) / 2) - unit.minX;
|
|
1482
|
+
} else if (direction === 'top') {
|
|
1483
|
+
deltaY = 0 - unit.minY;
|
|
1484
|
+
} else if (direction === 'bottom') {
|
|
1485
|
+
deltaY = (stageH - unit.height) - unit.minY;
|
|
1486
|
+
} else if (direction === 'middle') {
|
|
1487
|
+
deltaY = Math.round((stageH - unit.height) / 2) - unit.minY;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
unit.objects.forEach(o => {
|
|
1491
|
+
this.objectManager.updateObject(o.id, {
|
|
1492
|
+
x: Math.round(o.x + deltaX),
|
|
1493
|
+
y: Math.round(o.y + deltaY)
|
|
1494
|
+
}, false);
|
|
1495
|
+
});
|
|
1496
|
+
} else {
|
|
1497
|
+
// Birden fazla birim seçiliyse (gruplar ve/veya bağımsız nesneler), her grup tek bir nesne gibi davranır ve birbirine hizalanır
|
|
1498
|
+
if (direction === 'left') {
|
|
1499
|
+
const minX = Math.min(...units.map(u => u.minX));
|
|
1500
|
+
units.forEach(u => {
|
|
1501
|
+
const deltaX = minX - u.minX;
|
|
1502
|
+
u.objects.forEach(o => this.objectManager.updateObject(o.id, { x: Math.round(o.x + deltaX) }, false));
|
|
1503
|
+
});
|
|
1504
|
+
} else if (direction === 'right') {
|
|
1505
|
+
const maxR = Math.max(...units.map(u => u.maxX));
|
|
1506
|
+
units.forEach(u => {
|
|
1507
|
+
const deltaX = (maxR - u.width) - u.minX;
|
|
1508
|
+
u.objects.forEach(o => this.objectManager.updateObject(o.id, { x: Math.round(o.x + deltaX) }, false));
|
|
1509
|
+
});
|
|
1510
|
+
} else if (direction === 'center') {
|
|
1511
|
+
const avgX = units.reduce((sum, u) => sum + (u.minX + u.width / 2), 0) / units.length;
|
|
1512
|
+
units.forEach(u => {
|
|
1513
|
+
const deltaX = Math.round(avgX - u.width / 2) - u.minX;
|
|
1514
|
+
u.objects.forEach(o => this.objectManager.updateObject(o.id, { x: Math.round(o.x + deltaX) }, false));
|
|
1515
|
+
});
|
|
1516
|
+
} else if (direction === 'top') {
|
|
1517
|
+
const minY = Math.min(...units.map(u => u.minY));
|
|
1518
|
+
units.forEach(u => {
|
|
1519
|
+
const deltaY = minY - u.minY;
|
|
1520
|
+
u.objects.forEach(o => this.objectManager.updateObject(o.id, { y: Math.round(o.y + deltaY) }, false));
|
|
1521
|
+
});
|
|
1522
|
+
} else if (direction === 'bottom') {
|
|
1523
|
+
const maxB = Math.max(...units.map(u => u.maxY));
|
|
1524
|
+
units.forEach(u => {
|
|
1525
|
+
const deltaY = (maxB - u.height) - u.minY;
|
|
1526
|
+
u.objects.forEach(o => this.objectManager.updateObject(o.id, { y: Math.round(o.y + deltaY) }, false));
|
|
1527
|
+
});
|
|
1528
|
+
} else if (direction === 'middle') {
|
|
1529
|
+
const avgY = units.reduce((sum, u) => sum + (u.minY + u.height / 2), 0) / units.length;
|
|
1530
|
+
units.forEach(u => {
|
|
1531
|
+
const deltaY = Math.round(avgY - u.height / 2) - u.minY;
|
|
1532
|
+
u.objects.forEach(o => this.objectManager.updateObject(o.id, { y: Math.round(o.y + deltaY) }, false));
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
if (this.core && this.core.pushHistoryState) {
|
|
1538
|
+
this.core.pushHistoryState('Nesneleri Hizala');
|
|
1539
|
+
}
|
|
1540
|
+
this.selectionManager.updateSelectionBox();
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// Global namespace
|
|
1545
|
+
window.DND = window.DND || {};
|
|
1546
|
+
window.DND.MiniToolbar = MiniToolbar;
|