@mhmttosun/dnd 0.0.0-stage → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +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,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EditorCore - Merkezi veri modeli, durum yönetimi ve olay yayınlama sistemi.
|
|
3
|
+
* DOM'u veri kaynağı olarak kullanmaz; tek doğru kaynak (Single Source of Truth) bu sınıftır.
|
|
4
|
+
*/
|
|
5
|
+
class EditorCore {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.events = {};
|
|
8
|
+
this.mode = 'design'; // 'design' | 'answer-key' | 'preview'
|
|
9
|
+
this.data = this.createDefaultProject();
|
|
10
|
+
this.activeSubMode = null;
|
|
11
|
+
this.historyManager = null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Geçmiş Yönetimi Entegrasyonu (Merkezi API)
|
|
15
|
+
setHistoryManager(historyManager) {
|
|
16
|
+
this.historyManager = historyManager;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pushHistoryState(description = '') {
|
|
20
|
+
if (this.historyManager && typeof this.historyManager.pushState === 'function') {
|
|
21
|
+
this.historyManager.pushState(description);
|
|
22
|
+
} else {
|
|
23
|
+
this.emit('requestHistoryPush', { description });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
undo() {
|
|
28
|
+
if (this.historyManager && typeof this.historyManager.undo === 'function') {
|
|
29
|
+
return this.historyManager.undo();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
redo() {
|
|
34
|
+
if (this.historyManager && typeof this.historyManager.redo === 'function') {
|
|
35
|
+
return this.historyManager.redo();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
canUndo() {
|
|
40
|
+
return this.historyManager ? this.historyManager.canUndo() : false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
canRedo() {
|
|
44
|
+
return this.historyManager ? this.historyManager.canRedo() : false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
createDefaultProject(activityType = 'image-labeling', name = 'Yeni Etkinlik') {
|
|
48
|
+
return {
|
|
49
|
+
schemaVersion: 1,
|
|
50
|
+
version: '1.0',
|
|
51
|
+
project: {
|
|
52
|
+
id: 'proj_' + Date.now().toString(36),
|
|
53
|
+
name: name,
|
|
54
|
+
activityType: activityType,
|
|
55
|
+
description: 'Öğretmen tarafından hazırlanan etkileşimli sürükle-bırak etkinliği'
|
|
56
|
+
},
|
|
57
|
+
stage: {
|
|
58
|
+
width: 1200,
|
|
59
|
+
height: 675,
|
|
60
|
+
backgroundColor: '#f8fafc',
|
|
61
|
+
backgroundImage: null
|
|
62
|
+
},
|
|
63
|
+
objects: [],
|
|
64
|
+
answerKey: {},
|
|
65
|
+
settings: {
|
|
66
|
+
evaluationMode: 'instant', // 'instant' | 'submit'
|
|
67
|
+
wrongAnswerBehavior: 'return', // 'return' | 'stay'
|
|
68
|
+
correctAnswerBehavior: 'lock', // 'lock'
|
|
69
|
+
showFeedback: true,
|
|
70
|
+
showScore: true,
|
|
71
|
+
allowRetry: true,
|
|
72
|
+
shuffleDraggables: true,
|
|
73
|
+
soundEnabled: true,
|
|
74
|
+
showTargetsInGame: true,
|
|
75
|
+
completionMessage: 'Harika İş! Etkinliği başarıyla tamamladınız.'
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Olay Dinleme ve Tetikleme
|
|
81
|
+
on(event, handler) {
|
|
82
|
+
if (!this.events[event]) this.events[event] = [];
|
|
83
|
+
this.events[event].push(handler);
|
|
84
|
+
return () => this.off(event, handler);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
off(event, handler) {
|
|
88
|
+
if (!this.events[event]) return;
|
|
89
|
+
this.events[event] = this.events[event].filter(h => h !== handler);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
emit(event, payload) {
|
|
93
|
+
if (this.events[event]) {
|
|
94
|
+
this.events[event].forEach(handler => {
|
|
95
|
+
try {
|
|
96
|
+
handler(payload);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error(`Olay işleyici hatası [${event}]:`, err);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Mod Değişimi: 'design', 'answer-key', 'preview'
|
|
105
|
+
setMode(newMode) {
|
|
106
|
+
if (this.mode === newMode) return;
|
|
107
|
+
const oldMode = this.mode;
|
|
108
|
+
this.mode = newMode;
|
|
109
|
+
this.emit('modeChanged', { oldMode, newMode });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
getMode() {
|
|
113
|
+
return this.mode;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Proje Veri Yönetimi
|
|
117
|
+
loadProject(projectData, saveToHistory = true) {
|
|
118
|
+
const valResult = window.DND.ValidationEngine.validateProject(projectData);
|
|
119
|
+
if (!valResult.valid) {
|
|
120
|
+
throw new Error('Proje yüklenemedi: ' + valResult.errors.join(', '));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Güvenli kopyalama ve varsayılan alan tamamlama
|
|
124
|
+
const defaults = this.createDefaultProject(
|
|
125
|
+
projectData.project?.activityType || 'image-labeling',
|
|
126
|
+
projectData.project?.name || 'Etkinlik'
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
this.data = {
|
|
130
|
+
schemaVersion: projectData.schemaVersion || 1,
|
|
131
|
+
version: projectData.version || '1.0',
|
|
132
|
+
project: Object.assign({}, defaults.project, projectData.project),
|
|
133
|
+
stage: Object.assign({}, defaults.stage, projectData.stage),
|
|
134
|
+
objects: (projectData.objects || []).map(obj => Object.assign({}, obj)),
|
|
135
|
+
answerKey: Object.assign({}, projectData.answerKey || {}),
|
|
136
|
+
settings: Object.assign({}, defaults.settings, projectData.settings)
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const isHistoryRestore = (saveToHistory === false) || (typeof saveToHistory === 'object' && saveToHistory.isHistoryRestore);
|
|
140
|
+
const shouldSaveHistory = typeof saveToHistory === 'object' ? (saveToHistory.saveToHistory ?? true) : !!saveToHistory;
|
|
141
|
+
|
|
142
|
+
this.emit('projectLoaded', { project: this.data, saveToHistory: shouldSaveHistory, isHistoryRestore });
|
|
143
|
+
this.emit('dataChanged', { source: 'loadProject' });
|
|
144
|
+
this.autoSave();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getProjectData() {
|
|
148
|
+
// Derin kopya döndür
|
|
149
|
+
return JSON.parse(JSON.stringify(this.data));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
setActivityType(newType) {
|
|
153
|
+
if (!window.DND.ValidationEngine.VALID_ACTIVITY_TYPES.includes(newType)) return;
|
|
154
|
+
this.data.project.activityType = newType;
|
|
155
|
+
this.emit('activityTypeChanged', { activityType: newType });
|
|
156
|
+
this.emit('dataChanged', { source: 'setActivityType' });
|
|
157
|
+
this.autoSave();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
setProjectName(name) {
|
|
161
|
+
this.data.project.name = String(name).trim() || 'Adsız Etkinlik';
|
|
162
|
+
this.emit('projectNameChanged', { name: this.data.project.name });
|
|
163
|
+
this.emit('dataChanged', { source: 'setProjectName' });
|
|
164
|
+
this.autoSave();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
updateSettings(partialSettings) {
|
|
168
|
+
this.data.settings = Object.assign({}, this.data.settings, partialSettings);
|
|
169
|
+
this.emit('settingsChanged', { settings: this.data.settings });
|
|
170
|
+
this.emit('dataChanged', { source: 'updateSettings' });
|
|
171
|
+
this.autoSave();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
updateStage(partialStage) {
|
|
175
|
+
this.data.stage = Object.assign({}, this.data.stage, partialStage);
|
|
176
|
+
this.emit('stageChanged', { stage: this.data.stage });
|
|
177
|
+
this.emit('dataChanged', { source: 'updateStage' });
|
|
178
|
+
this.autoSave();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Yerel depolama (Autosave)
|
|
182
|
+
autoSave() {
|
|
183
|
+
try {
|
|
184
|
+
const serialized = JSON.stringify(this.data);
|
|
185
|
+
// localStorage boyut sınırı (~5MB) kontrolü
|
|
186
|
+
if (serialized.length < 4 * 1024 * 1024) {
|
|
187
|
+
localStorage.setItem('dnd_autosave_project', serialized);
|
|
188
|
+
localStorage.setItem('dnd_autosave_time', Date.now().toString());
|
|
189
|
+
}
|
|
190
|
+
} catch (e) {
|
|
191
|
+
console.warn('Otomatik kayıt localStorage kapasitesi aşıldı veya depolama devre dışı.');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
hasAutoSavedProject() {
|
|
196
|
+
try {
|
|
197
|
+
return !!localStorage.getItem('dnd_autosave_project');
|
|
198
|
+
} catch (e) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
loadAutoSavedProject() {
|
|
204
|
+
try {
|
|
205
|
+
const raw = localStorage.getItem('dnd_autosave_project');
|
|
206
|
+
if (raw) {
|
|
207
|
+
const parsed = JSON.parse(raw);
|
|
208
|
+
this.loadProject(parsed, false);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
} catch (e) {
|
|
212
|
+
console.error('Otomatik kayıt yükleme hatası:', e);
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Global namespace
|
|
219
|
+
window.DND = window.DND || {};
|
|
220
|
+
window.DND.EditorCore = EditorCore;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HistoryManager - Geri Al (Undo) ve İleri Al (Redo) geçmiş yönetim sistemi.
|
|
3
|
+
* Nesne taşıma, boyutlandırma, silme, ekleme ve eşleştirme durumlarını kaydeder.
|
|
4
|
+
*/
|
|
5
|
+
class HistoryManager {
|
|
6
|
+
constructor(core, maxHistory = 40, options = {}) {
|
|
7
|
+
this.core = core;
|
|
8
|
+
if (this.core) {
|
|
9
|
+
this.core.historyManager = this;
|
|
10
|
+
if (typeof this.core.setHistoryManager === 'function') {
|
|
11
|
+
this.core.setHistoryManager(this);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
this.maxHistory = maxHistory;
|
|
15
|
+
this.undoStack = [];
|
|
16
|
+
this.redoStack = [];
|
|
17
|
+
this.isApplying = false;
|
|
18
|
+
this.signal = options.signal;
|
|
19
|
+
|
|
20
|
+
this.init();
|
|
21
|
+
|
|
22
|
+
// Çekirdekte mevcut bir proje verisi varsa başlangıç taban durumunu kaydet
|
|
23
|
+
if (this.core && this.core.data) {
|
|
24
|
+
this.pushState('Başlangıç');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
init() {
|
|
29
|
+
// Dışarıdan yeni bir proje yüklendiğinde geçmiş sıfırlanır ve yeni başlangıç durumu kaydedilir;
|
|
30
|
+
// ancak iç geri yüklemelerde (undo/redo sırasında isApplying veya saveToHistory === false) geçmiş korunur!
|
|
31
|
+
this.core.on('projectLoaded', (payload) => {
|
|
32
|
+
if (this.isApplying) return;
|
|
33
|
+
if (payload && (payload.saveToHistory === false || payload.isHistoryRestore)) return;
|
|
34
|
+
this.clear();
|
|
35
|
+
this.pushState('Proje Yüklendi');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Çekirdekten veya bileşenlerden gelen açık geçmiş kaydetme isteği
|
|
39
|
+
this.core.on('requestHistoryPush', (payload) => {
|
|
40
|
+
if (this.isApplying) return;
|
|
41
|
+
this.pushState(payload?.description || '');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Nesne ekleme işlemi
|
|
45
|
+
this.core.on('objectAdded', (payload) => {
|
|
46
|
+
if (this.isApplying) return;
|
|
47
|
+
if (payload && payload.saveHistory === false) return;
|
|
48
|
+
this.pushState('Nesne Eklendi');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Nesne silme işlemi
|
|
52
|
+
this.core.on('objectDeleted', (payload) => {
|
|
53
|
+
if (this.isApplying) return;
|
|
54
|
+
if (payload && payload.saveHistory === false) return;
|
|
55
|
+
this.pushState('Nesne Silindi');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Nesne özelliği güncelleme (renk, rol, metin, boyut vb.)
|
|
59
|
+
this.core.on('objectUpdated', (payload) => {
|
|
60
|
+
if (this.isApplying) return;
|
|
61
|
+
if (payload && payload.saveHistory === false) return;
|
|
62
|
+
this.pushState('Nesne Güncellendi');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Katman sırası değişimi
|
|
66
|
+
this.core.on('layerChanged', (payload) => {
|
|
67
|
+
if (this.isApplying) return;
|
|
68
|
+
if (payload && payload.saveHistory === false) return;
|
|
69
|
+
this.pushState('Katman Değiştirildi');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Cevap anahtarı eşleşme değişimi
|
|
73
|
+
this.core.on('answerKeyChanged', (payload) => {
|
|
74
|
+
if (this.isApplying) return;
|
|
75
|
+
if (payload && payload.saveHistory === false) return;
|
|
76
|
+
this.pushState('Cevap Anahtarı Güncellendi');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Klavye kısayolları: Ctrl+Z / Ctrl+Y
|
|
80
|
+
window.addEventListener('keydown', (e) => {
|
|
81
|
+
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement?.tagName) ||
|
|
82
|
+
document.activeElement?.isContentEditable) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (this.core.getMode() !== 'design') return;
|
|
87
|
+
|
|
88
|
+
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
|
89
|
+
const cmdOrCtrl = isMac ? e.metaKey : e.ctrlKey;
|
|
90
|
+
|
|
91
|
+
if (cmdOrCtrl && e.key.toLowerCase() === 'z') {
|
|
92
|
+
if (e.shiftKey) {
|
|
93
|
+
// Redo
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
this.redo();
|
|
96
|
+
} else {
|
|
97
|
+
// Undo
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
this.undo();
|
|
100
|
+
}
|
|
101
|
+
} else if (cmdOrCtrl && e.key.toLowerCase() === 'y') {
|
|
102
|
+
// Redo
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
this.redo();
|
|
105
|
+
}
|
|
106
|
+
}, this.signal ? { signal: this.signal } : undefined);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
push(description = '') {
|
|
110
|
+
return this.pushState(description);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
pushState(description = '') {
|
|
114
|
+
if (this.isApplying) return;
|
|
115
|
+
|
|
116
|
+
const snapshot = JSON.stringify(this.core.data);
|
|
117
|
+
|
|
118
|
+
// En son kaydedilen durumla aynıysa mükerrer kayıt yapma
|
|
119
|
+
if (this.undoStack.length > 0 && this.undoStack[this.undoStack.length - 1] === snapshot) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.undoStack.push(snapshot);
|
|
124
|
+
if (this.undoStack.length > this.maxHistory) {
|
|
125
|
+
this.undoStack.shift();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Yeni işlem yapıldığında Redo yığını sıfırlanır
|
|
129
|
+
this.redoStack = [];
|
|
130
|
+
|
|
131
|
+
this.notifyState(description);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
undo() {
|
|
135
|
+
if (this.undoStack.length <= 1) return; // En az 1 başlangıç durumu kalmalı
|
|
136
|
+
|
|
137
|
+
this.isApplying = true;
|
|
138
|
+
try {
|
|
139
|
+
const currentState = this.undoStack.pop();
|
|
140
|
+
this.redoStack.push(currentState);
|
|
141
|
+
|
|
142
|
+
const prevState = this.undoStack[this.undoStack.length - 1];
|
|
143
|
+
const parsed = JSON.parse(prevState);
|
|
144
|
+
this.core.loadProject(parsed, { saveToHistory: false, isHistoryRestore: true });
|
|
145
|
+
} finally {
|
|
146
|
+
this.isApplying = false;
|
|
147
|
+
this.notifyState('Geri Alındı');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
redo() {
|
|
152
|
+
if (this.redoStack.length === 0) return;
|
|
153
|
+
|
|
154
|
+
this.isApplying = true;
|
|
155
|
+
try {
|
|
156
|
+
const nextState = this.redoStack.pop();
|
|
157
|
+
this.undoStack.push(nextState);
|
|
158
|
+
|
|
159
|
+
const parsed = JSON.parse(nextState);
|
|
160
|
+
this.core.loadProject(parsed, { saveToHistory: false, isHistoryRestore: true });
|
|
161
|
+
} finally {
|
|
162
|
+
this.isApplying = false;
|
|
163
|
+
this.notifyState('İleri Alındı');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
canUndo() {
|
|
168
|
+
return this.undoStack.length > 1;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
canRedo() {
|
|
172
|
+
return this.redoStack.length > 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
clear() {
|
|
176
|
+
this.undoStack = [];
|
|
177
|
+
this.redoStack = [];
|
|
178
|
+
this.notifyState('Geçmiş Sıfırlandı');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
notifyState(action = '') {
|
|
182
|
+
this.core.emit('historyChanged', {
|
|
183
|
+
canUndo: this.canUndo(),
|
|
184
|
+
canRedo: this.canRedo(),
|
|
185
|
+
undoCount: Math.max(0, this.undoStack.length - 1),
|
|
186
|
+
redoCount: this.redoStack.length,
|
|
187
|
+
action
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Global namespace
|
|
193
|
+
window.DND = window.DND || {};
|
|
194
|
+
window.DND.HistoryManager = HistoryManager;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LayerManager - Sahnedeki nesnelerin katman sırasını (z-index) ve
|
|
3
|
+
* öne/arkaya getirme işlemlerini yönetir.
|
|
4
|
+
*/
|
|
5
|
+
class LayerManager {
|
|
6
|
+
constructor(core, objectManager) {
|
|
7
|
+
this.core = core;
|
|
8
|
+
this.objectManager = objectManager;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
getSortedObjects() {
|
|
12
|
+
return [...this.core.data.objects].sort((a, b) => (a.zIndex || 1) - (b.zIndex || 1));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
normalizeLayers() {
|
|
16
|
+
const sorted = this.getSortedObjects();
|
|
17
|
+
sorted.forEach((obj, idx) => {
|
|
18
|
+
obj.zIndex = idx + 1;
|
|
19
|
+
this.objectManager.updateObjectDOM(obj);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
bringToFront(id, saveHistory = true) {
|
|
24
|
+
const obj = this.objectManager.getObject(id);
|
|
25
|
+
if (!obj) return;
|
|
26
|
+
|
|
27
|
+
this.normalizeLayers();
|
|
28
|
+
const sorted = this.getSortedObjects();
|
|
29
|
+
const maxZ = sorted[sorted.length - 1]?.zIndex || 1;
|
|
30
|
+
obj.zIndex = maxZ + 1;
|
|
31
|
+
|
|
32
|
+
this.normalizeLayers();
|
|
33
|
+
this.core.emit('layerChanged', { object: obj, saveHistory });
|
|
34
|
+
this.core.emit('dataChanged', { source: 'layer' });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
sendToBack(id, saveHistory = true) {
|
|
38
|
+
const obj = this.objectManager.getObject(id);
|
|
39
|
+
if (!obj) return;
|
|
40
|
+
|
|
41
|
+
this.normalizeLayers();
|
|
42
|
+
obj.zIndex = 0;
|
|
43
|
+
|
|
44
|
+
this.normalizeLayers();
|
|
45
|
+
this.core.emit('layerChanged', { object: obj, saveHistory });
|
|
46
|
+
this.core.emit('dataChanged', { source: 'layer' });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
bringForward(id, saveHistory = true) {
|
|
50
|
+
this.normalizeLayers();
|
|
51
|
+
const sorted = this.getSortedObjects();
|
|
52
|
+
const idx = sorted.findIndex(o => o.id === id);
|
|
53
|
+
if (idx === -1 || idx === sorted.length - 1) return;
|
|
54
|
+
|
|
55
|
+
// Bir üsttekiyle yer değiştir
|
|
56
|
+
const nextObj = sorted[idx + 1];
|
|
57
|
+
const tempZ = sorted[idx].zIndex;
|
|
58
|
+
sorted[idx].zIndex = nextObj.zIndex;
|
|
59
|
+
nextObj.zIndex = tempZ;
|
|
60
|
+
|
|
61
|
+
this.normalizeLayers();
|
|
62
|
+
this.core.emit('layerChanged', { object: sorted[idx], saveHistory });
|
|
63
|
+
this.core.emit('dataChanged', { source: 'layer' });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
sendBackward(id, saveHistory = true) {
|
|
67
|
+
this.normalizeLayers();
|
|
68
|
+
const sorted = this.getSortedObjects();
|
|
69
|
+
const idx = sorted.findIndex(o => o.id === id);
|
|
70
|
+
if (idx <= 0) return;
|
|
71
|
+
|
|
72
|
+
// Bir alttakiyle yer değiştir
|
|
73
|
+
const prevObj = sorted[idx - 1];
|
|
74
|
+
const tempZ = sorted[idx].zIndex;
|
|
75
|
+
sorted[idx].zIndex = prevObj.zIndex;
|
|
76
|
+
prevObj.zIndex = tempZ;
|
|
77
|
+
|
|
78
|
+
this.normalizeLayers();
|
|
79
|
+
this.core.emit('layerChanged', { object: sorted[idx], saveHistory });
|
|
80
|
+
this.core.emit('dataChanged', { source: 'layer' });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Global namespace
|
|
85
|
+
window.DND = window.DND || {};
|
|
86
|
+
window.DND.LayerManager = LayerManager;
|