@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mhmttosun
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,328 @@
|
|
|
1
|
-
#
|
|
1
|
+
# HTML Tabanlı Görsel Sürükle-Bırak Etkinlik Editörü
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Öğretmenlerin kod yazmadan etkileşimli eğitim etkinlikleri tasarlayabildiği, görsel cevap anahtarı oluşturabildiği, anında önizleme yapabildiği ve **tek tıkla tamamen bağımsız, çevrimdışı çalışan tek parça HTML etkinlikleri üretebildiği** profesyonel açık kaynak web platformudur.
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
-success.svg)
|
|
7
|
+

|
|
8
|
+
-blue.svg)
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 🌟 Temel Özellikler
|
|
14
|
+
|
|
15
|
+
* **Canva Benzeri Görsel Tasarım Deneyimi:** Nesneleri serbestçe sürükleme, 8 tutamaçla boyutlandırma, döndürme, katman sıralaması, hizalama kılavuzları ve ızgara desteği.
|
|
16
|
+
* **Kesinlikle HTML Canvas Kullanmaz:** Bütün nesneler DOM hiyerarşisi, CSS Transforms (`translate`, `scale`, `rotate`) ve gerektiğinde SVG ile yönetilir.
|
|
17
|
+
* **Kontekstüel Mini Araç Çubuğu (Mini Toolbar):**
|
|
18
|
+
* Nesne seçildiğinde veya seçim alanı (marquee) bırakıldığında doğrudan nesnenin üzerinde beliren dinamik araç çubuğu.
|
|
19
|
+
* **Metin Ayarları:** Yazı tipi, boyutu, kalınlık, yatay ve dikey hizalama, 14 renkli açılır palet.
|
|
20
|
+
* **Nesne Görünümü:** Kompakt kart tasarımı, açılır renk seçimi, `+` ve `−` butonlarıyla hassas çizgi kalınlığı ve köşe yuvarlama (border-radius) ayarları.
|
|
21
|
+
* **Katman ve Kilit Menüsü:** Minimalist tasarımda *Öne*, *En öne*, *Arkaya*, *En arkaya* ve *Kilit* seçenekleri.
|
|
22
|
+
* **Kilitli Nesne Desteği:** Tuval üzerinden kilitli nesne seçildiğinde tek tıkla **"Kilidi Aç"** aksiyonu.
|
|
23
|
+
* **Sahne Rolü:** *Taşınan*, *Hedef* ve *Arka Plan* rolleri arasında hızlı geçiş.
|
|
24
|
+
* **Hizalama ve Boyutlandırma:** Sola, ortaya, sağa, üste, merkeze, alta hizalama ile çoklu seçimde *En Kısa* ve *En Uzun* genişliğe eşitleme.
|
|
25
|
+
* **Çizgi Nesnesi ve Özel Uç Tutamaçları:** Yalnızca başlangıç ve bitiş noktalarında beliren minimalist tutamaçlarla ok ve çizgi oluşturma.
|
|
26
|
+
* **Görsel Cevap Anahtarı Sistemi:** Kod veya ID yazmaya gerek olmadan, sürükle-bırak nesnesine ve hedef alana tıklayarak karşılıklı kenarlardan bağlanan dinamik SVG çizgileriyle eşleştirme.
|
|
27
|
+
* **Tek Parça Bağımsız HTML Dışa Aktarma:** Dışa aktarılan etkinlik; harici kütüphane, CDN veya internet bağlantısı gerektirmez. Dosyaya çift tıklanarak akıllı tahta, tablet veya bilgisayarlarda doğrudan çalışır.
|
|
28
|
+
* **Pointer Events API:** Masaüstü fare, tablet ve dokunmatik ekranlı mobil telefonlarda kusursuz sürükleme performansı.
|
|
29
|
+
* **Web Audio API Entegre Ses Sentezleyici:** Harici ses dosyalarına ihtiyaç duymadan, tarayıcı içinde dinamik doğru/yanlış melodileri ve kutlama fanfarı üretir.
|
|
30
|
+
* **Klavye ile Erişilebilirlik ve Kısayollar:** Geri Al (`Ctrl+Z`), İleri Al (`Ctrl+Y`), Kopyala/Yapıştır (`Ctrl+C`/`Ctrl+V`), Gruplama (`Ctrl+G`), Grubu Çözme (`Ctrl+Shift+G`), Çift tıklama ile yerinde metin düzenleme (inline editing).
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 🎯 Desteklenen 4 Etkinlik Türü
|
|
35
|
+
|
|
36
|
+
| Etkinlik Türü | Açıklama | Cevap Anahtarı Mantığı |
|
|
37
|
+
| :--- | :--- | :--- |
|
|
38
|
+
| **1. Görsel Üzerinde Etiketleme** | Ana görsel (örn: İnsan Anatomisi) üzerindeki hedef noktalara doğru etiketleri yerleştirme. | Sürüklenebilir Nesne ID ➔ Hedef ID (1'e 1) |
|
|
39
|
+
| **2. Sürükle-Bırak Eşleştirme** | İki sütun halindeki nesne veya kavramları (örn: Hayvanlar ve Doğal Yaşam Alanları) eşleştirme. | Kaynak Nesne ID ➔ Hedef Nesne ID (1'e 1) |
|
|
40
|
+
| **3. Kategorilere Ayırma** | Nesneleri (örn: Meyveler ve Sebzeler) ilgili kategori kutularına yerleştirme. | Nesne ID ➔ Kategori ID (Çoklu kabul) |
|
|
41
|
+
| **4. Sürükleyerek Sıralama** | Süreç adımlarını (örn: Kelebeğin Yaşam Döngüsü) doğru sıraya dizme. | Nesne ID ➔ Sıra Numarası / Slot ID |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 🏗️ Modüler Yazılım Mimarisi
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
dnd/
|
|
49
|
+
├── index.html # Ana görsel etkinlik editörü
|
|
50
|
+
├── README.md # Proje dokümantasyonu ve kullanım kılavuzu
|
|
51
|
+
├── LICENSE # MIT Açık Kaynak Lisansı
|
|
52
|
+
├── package.json # NPM betikleri ve paket metaverileri
|
|
53
|
+
├── .gitignore # Sürüm kontrolünden hariç tutulan dosyalar
|
|
54
|
+
├── css/
|
|
55
|
+
│ ├── editor.css # Editör arayüzü, mini toolbar ve açılır popup kartları
|
|
56
|
+
│ ├── stage.css # 1200x675 tuval, 8 boyutlandırma tutamacı, çizgi tutamaçları
|
|
57
|
+
│ ├── answer-key.css # Cevap anahtarı modu SVG bağlantı çizgileri ve vurgular
|
|
58
|
+
│ └── player.css # Önizleme ve dışa aktarılan etkinlik oynatıcısının stilleri
|
|
59
|
+
├── js/
|
|
60
|
+
│ ├── core/
|
|
61
|
+
│ │ ├── EditorCore.js # Merkezi veri modeli (Single Source of Truth) ve durum yönetimi
|
|
62
|
+
│ │ ├── StageManager.js # 1200x675 mantıksal koordinat sistemi, ölçekleme ve ızgara
|
|
63
|
+
│ │ ├── ObjectManager.js # Nesne CRUD, z-index ve DOM oluşturma işlemleri
|
|
64
|
+
│ │ ├── SelectionManager.js# Tekli/çoklu seçim (Marquee), kopyala/yapıştır, kısayollar
|
|
65
|
+
│ │ ├── HistoryManager.js # Sınırsız Geri Al (Ctrl+Z) ve İleri Al (Ctrl+Y) yığını
|
|
66
|
+
│ │ ├── LayerManager.js # Katman yönetimi (Öne Getir, Arkaya Gönder)
|
|
67
|
+
│ │ └── AnswerKeyManager.js# Görsel SVG eşleştirme ve cevap anahtarı yönetimi
|
|
68
|
+
│ ├── engine/
|
|
69
|
+
│ │ ├── TransformEngine.js # Pointer Events ile taşıma, boyutlandırma ve döndürme
|
|
70
|
+
│ │ └── ValidationEngine.js# JSON şema ve oynanabilirlik doğrulaması, XSS sanitizasyonu
|
|
71
|
+
│ ├── player/
|
|
72
|
+
│ │ ├── ActivityEngine.js # Gerçek zamanlı öğrenci oyun motoru (Preview ve Export için)
|
|
73
|
+
│ │ └── ExportManager.js # Tek parça, bağımsız HTML dosyası derleyici
|
|
74
|
+
│ ├── utils/
|
|
75
|
+
│ │ └── AudioSynthesizer.js# Web Audio API ile sıfır harici dosya ses motoru
|
|
76
|
+
│ ├── ui/
|
|
77
|
+
│ │ ├── MiniToolbar.js # Kontekstüel yüzen mini araç çubuğu ve popup kontrolleri
|
|
78
|
+
│ │ ├── PropertyPanel.js # Seçili nesne ve oyun ayarları paneli
|
|
79
|
+
│ │ └── UIManager.js # Araç çubukları, menüler ve kullanıcı bildirimleri
|
|
80
|
+
│ ├── data/
|
|
81
|
+
│ │ └── SampleProjects.js # 4 etkinlik türü için zengin, hazır şablonlar
|
|
82
|
+
│ └── app.js # Uygulama başlatıcı ve modül bağlayıcı
|
|
83
|
+
├── samples/ # Hazır etkinliklerin bağımsız HTML ve JSON dosyaları
|
|
84
|
+
│ ├── 1_organlari_etiketleme.html / .json
|
|
85
|
+
│ ├── 2_hayvanlar_eslestirme.html / .json
|
|
86
|
+
│ ├── 2_kavram_tanim_eslestirme.html / .json
|
|
87
|
+
│ ├── 3_meyveler_sebzeler_kategori.html / .json
|
|
88
|
+
│ └── 4_kelebek_yasam_dongusu_siralama.html / .json
|
|
89
|
+
└── tests/
|
|
90
|
+
└── run_tests.py # 27 aşamalı otomatik kabul ve entegrasyon test paketi
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 📐 Mantıksal Koordinat Sistemi ve Ölçekleme
|
|
96
|
+
|
|
97
|
+
Editör, **1200 x 675 piksel (16:9 sabit en-boy oranı)** mantıksal koordinat sistemi üzerinde çalışır:
|
|
98
|
+
* Tüm nesne konumları `(x, y)` ve boyutları `(width, height)` bu mantıksal sistemde saklanır.
|
|
99
|
+
* Ekran boyutu veya tarayıcı yakınlaştırması değiştiğinde `CSS transform: scale(...)` kullanılarak orantılı ölçekleme yapılır.
|
|
100
|
+
* Pointer koordinatları aşağıdaki formülle mantıksal sahne koordinatına dönüştürülür:
|
|
101
|
+
$$\text{stageX} = \frac{\text{clientX} - \text{stageRect.left}}{\text{scale}}$$
|
|
102
|
+
$$\text{stageY} = \frac{\text{clientY} - \text{stageRect.top}}{\text{scale}}$$
|
|
103
|
+
* Bu sayede mobilde veya 4K ekranda açıldığında hedefler ve nesneler milimetrik olarak aynı yerde kalır.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 🚀 Kurulum ve Çalıştırma
|
|
108
|
+
|
|
109
|
+
Proje hiçbir backend, derleyici (build tool) veya harici npm paketi bağımlılığı gerektirmez.
|
|
110
|
+
|
|
111
|
+
### 1. Yöntem: Doğrudan Çift Tıklama (En Hızlı)
|
|
112
|
+
Herhangi bir kurulum yapmadan `index.html` dosyasına çift tıklayarak tarayıcınızda açabilirsiniz.
|
|
113
|
+
|
|
114
|
+
### 2. Yöntem: Yerel Sunucu ile (Tavsiye Edilen)
|
|
115
|
+
Terminalden proje klasöründe yerel bir HTTP sunucu başlatabilirsiniz:
|
|
116
|
+
```bash
|
|
117
|
+
# Python ile:
|
|
118
|
+
python3 -m http.server 8000
|
|
119
|
+
|
|
120
|
+
# veya NPM betiği ile:
|
|
121
|
+
npm start
|
|
122
|
+
```
|
|
123
|
+
Ardından tarayıcınızdan `http://localhost:8000` adresine gidin.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 📦 Başka JS Projelerine Entegrasyon (Kütüphane Kullanımı)
|
|
128
|
+
|
|
129
|
+
Bu proje hem görsel bir editör hem de diğer JavaScript projelerine (React, Vue, Angular, Svelte, Next.js veya düz Vanilla JS) **kolayca eklenebilen bağımsız bir etkinlik oynatıcısı (Activity Player)** olarak tasarlanmıştır.
|
|
130
|
+
|
|
131
|
+
### Editörü doğrudan gömme
|
|
132
|
+
|
|
133
|
+
Editör iframe gerektirmeden bir DOM elemanına bağlanabilir. İç arayüz Shadow DOM
|
|
134
|
+
içinde tutulur; görsel yükleme, bekleyen işlerin tamamlanması ve statik SVG
|
|
135
|
+
önizleme üretimi host uygulamaya açık bir API üzerinden yürütülür.
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
import { initializeDndEditor, createDefaultActivity } from '@mhmttosun/dnd/editor';
|
|
139
|
+
|
|
140
|
+
const editor = initializeDndEditor({
|
|
141
|
+
container: document.querySelector('#editor'),
|
|
142
|
+
activity: createDefaultActivity('matching', 'Eşleştirme'),
|
|
143
|
+
uploadImage: async (file, { signal }) => {
|
|
144
|
+
const url = await uploadToYourStorage(file, { signal });
|
|
145
|
+
return { url };
|
|
146
|
+
},
|
|
147
|
+
ui: { fileActions: false },
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
await editor.flush();
|
|
151
|
+
const activity = editor.getActivity();
|
|
152
|
+
const previewSvg = await editor.exportPreview();
|
|
153
|
+
editor.destroy();
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`ActivityPlayer` önceki bir denemeyi `initialState` ile geri yükleyebilir; güncel
|
|
157
|
+
durum `getState()` ile alınır ve gerekirse `setState()` ile değiştirilebilir.
|
|
158
|
+
|
|
159
|
+
Gömülü arayüzlerde sahneyi kapsayıcı kenarlarına kadar ölçeklemek için
|
|
160
|
+
`stagePadding: 0` verilebilir. Seçenek belirtilmezse bağımsız oynatıcı mevcut
|
|
161
|
+
duyarlı 16/32 px boşluğunu korur.
|
|
162
|
+
|
|
163
|
+
### 1. Kurulum (NPM ile)
|
|
164
|
+
```bash
|
|
165
|
+
npm install @mhmttosun/dnd
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### 2. Kullanım Senaryoları
|
|
171
|
+
|
|
172
|
+
#### A. W3C Standart Web Component (`<dnd-player>`)
|
|
173
|
+
Hiçbir framework bağımlılığı olmadan, HTML'e doğrudan standart etiket olarak eklenir:
|
|
174
|
+
|
|
175
|
+
```html
|
|
176
|
+
<!-- CSS ve Script dahil edilir -->
|
|
177
|
+
<link rel="stylesheet" href="node_modules/@mhmttosun/dnd/dist/player.css">
|
|
178
|
+
<script src="node_modules/@mhmttosun/dnd/dist/dnd-player-element.js"></script>
|
|
179
|
+
|
|
180
|
+
<!-- Standart HTML Etiketi -->
|
|
181
|
+
<dnd-player
|
|
182
|
+
src="/path/to/activity.json"
|
|
183
|
+
sound="true"
|
|
184
|
+
style="height: 600px;">
|
|
185
|
+
</dnd-player>
|
|
186
|
+
|
|
187
|
+
<script>
|
|
188
|
+
const playerEl = document.querySelector('dnd-player');
|
|
189
|
+
playerEl.addEventListener('dnd:complete', (e) => {
|
|
190
|
+
console.log('Etkinlik Bitti! Skor:', e.detail.score, 'Deneme:', e.detail.attempts);
|
|
191
|
+
});
|
|
192
|
+
</script>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### B. React Entegrasyonu (React Component & Lifecycle)
|
|
196
|
+
```jsx
|
|
197
|
+
import React, { useEffect, useRef } from 'react';
|
|
198
|
+
import { ActivityPlayer } from '@mhmttosun/dnd/player';
|
|
199
|
+
import '@mhmttosun/dnd/player.css';
|
|
200
|
+
|
|
201
|
+
export function ActivityView({ activityData, onFinished }) {
|
|
202
|
+
const containerRef = useRef(null);
|
|
203
|
+
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
// 1. Oynatıcıyı Başlat (Mount)
|
|
206
|
+
const player = new ActivityPlayer({
|
|
207
|
+
target: containerRef.current,
|
|
208
|
+
data: activityData,
|
|
209
|
+
sound: true,
|
|
210
|
+
onComplete: (stats) => onFinished(stats)
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// 2. Bellek Temizliği (Unmount Destroy)
|
|
214
|
+
return () => player.destroy();
|
|
215
|
+
}, [activityData]);
|
|
216
|
+
|
|
217
|
+
return <div ref={containerRef} style={{ width: '100%', height: '600px' }} />;
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### C. Vue 3 (Composition API)
|
|
222
|
+
```vue
|
|
223
|
+
<template>
|
|
224
|
+
<div ref="container" style="width: 100%; height: 600px;"></div>
|
|
225
|
+
</template>
|
|
226
|
+
|
|
227
|
+
<script setup>
|
|
228
|
+
import { ref, onMounted, onUnmounted } from 'vue';
|
|
229
|
+
import { ActivityPlayer } from '@mhmttosun/dnd/player';
|
|
230
|
+
import '@mhmttosun/dnd/player.css';
|
|
231
|
+
|
|
232
|
+
const props = defineProps({ activityData: Object });
|
|
233
|
+
const emit = defineEmits(['complete']);
|
|
234
|
+
const container = ref(null);
|
|
235
|
+
let player = null;
|
|
236
|
+
|
|
237
|
+
onMounted(() => {
|
|
238
|
+
player = new ActivityPlayer({
|
|
239
|
+
target: container.value,
|
|
240
|
+
data: props.activityData,
|
|
241
|
+
onComplete: (stats) => emit('complete', stats)
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
onUnmounted(() => {
|
|
246
|
+
if (player) player.destroy();
|
|
247
|
+
});
|
|
248
|
+
</script>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### D. Vanilla JS (Düz Web Sayfası)
|
|
252
|
+
```javascript
|
|
253
|
+
import { ActivityPlayer } from '@mhmttosun/dnd/player';
|
|
254
|
+
import '@mhmttosun/dnd/player.css';
|
|
255
|
+
|
|
256
|
+
const player = new ActivityPlayer({
|
|
257
|
+
target: '#my-container',
|
|
258
|
+
src: 'samples/2_hayvanlar_eslestirme.json',
|
|
259
|
+
sound: true,
|
|
260
|
+
onComplete: (stats) => alert(`Tebrikler! Puan: %${stats.score}`)
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// İstenildiğinde:
|
|
264
|
+
// player.reset();
|
|
265
|
+
// player.destroy();
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### E. Editörün Başka Uygulamalara Gömülmesi (Embedded Editor) & Canlı JSON Export
|
|
269
|
+
|
|
270
|
+
Editör bir CMS, LMS (Moodle, Canvas vb.), öğretmen yönetim paneli veya React/Vue uygulamasının içerisine `<iframe>` veya bileşen olarak gömüldüğünde:
|
|
271
|
+
* **Üst Başlık (Header) Otomatik Gizlenir:** Sayfa bir `<iframe>` içinde açıldığında veya URL'e `?embedded=true` (veya `?header=false`) eklendiğinde editörün üst başlık çubuğu (`.app-top-header`) gizlenir ve çalışma alanı tüm ekranı kaplar.
|
|
272
|
+
* **Sahnenin JSON Verisini Kolayca Alma:** Host uygulama tek satırda güncel sahne JSON'ını alabilir.
|
|
273
|
+
|
|
274
|
+
```html
|
|
275
|
+
<!-- Host Uygulama (LMS / Portal / React) -->
|
|
276
|
+
<iframe id="dnd-editor" src="/dnd/index.html?embedded=true" style="width: 100%; height: 750px; border: none;"></iframe>
|
|
277
|
+
|
|
278
|
+
<script>
|
|
279
|
+
const editorFrame = document.getElementById('dnd-editor');
|
|
280
|
+
|
|
281
|
+
// 1. YÖNTEM: Doğrudan JavaScript Kısayolu (Same-Origin)
|
|
282
|
+
function saveFromEditor() {
|
|
283
|
+
// Tek satırda güncel sahnenin JSON nesnesi:
|
|
284
|
+
const projectData = editorFrame.contentWindow.dndExport();
|
|
285
|
+
console.log('Sahne JSON:', projectData);
|
|
286
|
+
return projectData;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 2. YÖNTEM: postMessage API (Cross-Origin / Güvenli İletişim)
|
|
290
|
+
function requestEditorData() {
|
|
291
|
+
editorFrame.contentWindow.postMessage({ type: 'DND_GET_DATA' }, '*');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Editörden gelen yanıtları ve anlık değişiklikleri dinleme:
|
|
295
|
+
window.addEventListener('message', (event) => {
|
|
296
|
+
// İstek üzerine dönen JSON
|
|
297
|
+
if (event.data.type === 'DND_DATA_RESPONSE') {
|
|
298
|
+
console.log('Alınan Proje JSON:', event.data.data);
|
|
299
|
+
}
|
|
300
|
+
// Sahnede kullanıcı nesne taşıdığında / düzenlediğinde canlı fırlatılan bildirim
|
|
301
|
+
if (event.data.type === 'DND_DATA_CHANGED') {
|
|
302
|
+
console.log('Canlı Değişiklik:', event.data.data);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// İstenirse ebeveynden yeni proje verisi yükleme:
|
|
307
|
+
// editorFrame.contentWindow.postMessage({ type: 'DND_LOAD_DATA', data: newProjectJson }, '*');
|
|
308
|
+
</script>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## 🧪 Otomatik Testleri Çalıştırma
|
|
314
|
+
|
|
315
|
+
Proje; mimari bütünlüğü, Canvas kullanılmama kuralını, matematiksel koordinat dönüşümlerini, mini araç çubuğunun tüm popup ve stepper özelliklerini doğrulayan 27 aşamalı test paketine sahiptir.
|
|
316
|
+
|
|
317
|
+
Testleri çalıştırmak için:
|
|
318
|
+
```bash
|
|
319
|
+
python3 tests/run_tests.py
|
|
320
|
+
# veya
|
|
321
|
+
npm test
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## 📄 Lisans
|
|
327
|
+
|
|
328
|
+
Bu proje [MIT Lisansı](LICENSE) kapsamında açık kaynak olarak sunulmaktadır.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
ANSWER KEY CSS - Cevap Anahtarı Modu Görsel Eşleştirme Stilleri
|
|
3
|
+
========================================================================== */
|
|
4
|
+
|
|
5
|
+
/* Cevap Anahtarı Modunda Nesneler */
|
|
6
|
+
.mode-answer-key .stage-object {
|
|
7
|
+
cursor: default;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.mode-answer-key .stage-object[data-role="draggable"] {
|
|
11
|
+
cursor: pointer !important;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* Seçilen Kaynak Sürüklenebilir Öğe (Sadece seçili nesne vurgulanır, konumu korunur) */
|
|
15
|
+
.mode-answer-key .stage-object.ak-source-active {
|
|
16
|
+
outline: 3px solid #6366f1 !important;
|
|
17
|
+
outline-offset: 3px !important;
|
|
18
|
+
box-shadow: 0 0 0 2px #ffffff, 0 8px 24px rgba(99, 102, 241, 0.35) !important;
|
|
19
|
+
z-index: 1000 !important;
|
|
20
|
+
cursor: pointer !important;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* ==========================================================================
|
|
24
|
+
Hedef Seçim Aşaması: Sürüklenecek nesne seçildikten sonra
|
|
25
|
+
Sadece sahne rolü "Hedef" olan nesneler tıklanabilir olmalıdır.
|
|
26
|
+
========================================================================== */
|
|
27
|
+
.mode-answer-key.ak-selecting-target .stage-object {
|
|
28
|
+
cursor: default;
|
|
29
|
+
pointer-events: none; /* Hedef olmayan (dekoratif vb.) nesneler tıklanamaz */
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Kaynak nesnenin kendisi tıklanarak seçim iptal edilebilir */
|
|
33
|
+
.mode-answer-key.ak-selecting-target .stage-object.ak-source-active {
|
|
34
|
+
cursor: pointer !important;
|
|
35
|
+
pointer-events: auto !important;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Başka bir sürüklenebilir nesneye tıklanarak kaynak değiştirilebilir */
|
|
39
|
+
.mode-answer-key.ak-selecting-target .stage-object[data-role="draggable"] {
|
|
40
|
+
cursor: pointer !important;
|
|
41
|
+
pointer-events: auto !important;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Sahne rolü "Hedef" olan nesneler tıklanabilir */
|
|
45
|
+
.mode-answer-key.ak-selecting-target .stage-object[data-role="target"],
|
|
46
|
+
.mode-answer-key.ak-selecting-target .stage-object[data-role="category"],
|
|
47
|
+
.mode-answer-key.ak-selecting-target .stage-object[data-role="sequence_slot"] {
|
|
48
|
+
cursor: pointer !important;
|
|
49
|
+
pointer-events: auto !important;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* SVG Bağlantı Çizgileri */
|
|
53
|
+
.ak-connection-group {
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
pointer-events: auto;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.ak-connection-hitbox {
|
|
59
|
+
fill: none;
|
|
60
|
+
stroke: transparent;
|
|
61
|
+
stroke-width: 24;
|
|
62
|
+
pointer-events: stroke;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.ak-connection-path {
|
|
66
|
+
fill: none;
|
|
67
|
+
stroke: #6366f1;
|
|
68
|
+
stroke-width: 3.5;
|
|
69
|
+
stroke-linecap: round;
|
|
70
|
+
stroke-dasharray: 6 4;
|
|
71
|
+
animation: dashAnimation 1s linear infinite;
|
|
72
|
+
filter: drop-shadow(0 2px 4px rgba(99, 102, 241, 0.3));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@keyframes dashAnimation {
|
|
76
|
+
to {
|
|
77
|
+
stroke-dashoffset: -20;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.ak-node-circle {
|
|
82
|
+
fill: #4f46e5;
|
|
83
|
+
stroke: #ffffff;
|
|
84
|
+
stroke-width: 2;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.ak-node-circle.start {
|
|
88
|
+
fill: #ec4899;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.ak-node-circle.end {
|
|
92
|
+
fill: #10b981;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Bağlantı Kaldırma (Unlink) Butonu */
|
|
96
|
+
.ak-unlink-btn {
|
|
97
|
+
width: 26px;
|
|
98
|
+
height: 26px;
|
|
99
|
+
border-radius: 50%;
|
|
100
|
+
background: #ef4444;
|
|
101
|
+
color: #ffffff;
|
|
102
|
+
border: 2px solid #ffffff;
|
|
103
|
+
font-size: 12px;
|
|
104
|
+
font-weight: 800;
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
justify-content: center;
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
|
|
110
|
+
transition: transform 0.15s ease, background 0.15s ease;
|
|
111
|
+
pointer-events: auto;
|
|
112
|
+
padding: 0;
|
|
113
|
+
margin: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.ak-unlink-btn:hover {
|
|
117
|
+
background: #dc2626;
|
|
118
|
+
transform: scale(1.2);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* ==========================================================================
|
|
122
|
+
Cevap Anahtarı Toolbar İçi Rehber Bloğu (Answer Key Toolbar Guide)
|
|
123
|
+
Toolbar yüksekliğini değiştirmeden sol mod kümesinden sonra dikey ayraçla yerleşir
|
|
124
|
+
========================================================================== */
|
|
125
|
+
.answer-key-toolbar-guide {
|
|
126
|
+
display: flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
height: 100%;
|
|
129
|
+
user-select: none;
|
|
130
|
+
animation: akFadeIn 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@keyframes akFadeIn {
|
|
134
|
+
from {
|
|
135
|
+
opacity: 0;
|
|
136
|
+
transform: translateX(-4px);
|
|
137
|
+
}
|
|
138
|
+
to {
|
|
139
|
+
opacity: 1;
|
|
140
|
+
transform: translateX(0);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.ak-toolbar-divider {
|
|
145
|
+
width: 1px;
|
|
146
|
+
height: 24px;
|
|
147
|
+
background-color: var(--border-color, #e2e8f0);
|
|
148
|
+
margin: 0 14px 0 10px;
|
|
149
|
+
flex-shrink: 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.ak-toolbar-content {
|
|
153
|
+
display: flex;
|
|
154
|
+
flex-direction: column;
|
|
155
|
+
justify-content: center;
|
|
156
|
+
gap: 1px;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.ak-toolbar-title-row {
|
|
160
|
+
display: flex;
|
|
161
|
+
align-items: center;
|
|
162
|
+
gap: 6px;
|
|
163
|
+
line-height: 1.15;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.ak-toolbar-status-icon {
|
|
167
|
+
display: inline-flex;
|
|
168
|
+
align-items: center;
|
|
169
|
+
justify-content: center;
|
|
170
|
+
flex-shrink: 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.ak-guide-title {
|
|
174
|
+
font-size: 0.88rem;
|
|
175
|
+
font-weight: 700;
|
|
176
|
+
color: #0f172a;
|
|
177
|
+
letter-spacing: -0.01em;
|
|
178
|
+
white-space: nowrap;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.ak-guide-instruction {
|
|
182
|
+
font-size: 0.76rem;
|
|
183
|
+
font-weight: 500;
|
|
184
|
+
color: #64748b;
|
|
185
|
+
line-height: 1.15;
|
|
186
|
+
white-space: nowrap;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* Cevap Anahtarı Modunda Orta (Nesne Ekleme), Sağ (Geçmiş & Zoom) ve Şablon Kümelerini Gizle */
|
|
190
|
+
.app-sub-toolbar.mode-answer-key .cluster-add-objects,
|
|
191
|
+
.app-sub-toolbar.mode-answer-key .cluster-history-zoom,
|
|
192
|
+
.app-sub-toolbar.mode-answer-key .template-dropdown-wrapper,
|
|
193
|
+
body.mode-answer-key .app-sub-toolbar .cluster-add-objects,
|
|
194
|
+
body.mode-answer-key .app-sub-toolbar .cluster-history-zoom,
|
|
195
|
+
body.mode-answer-key .app-sub-toolbar .template-dropdown-wrapper,
|
|
196
|
+
.dnd-editor-root.mode-answer-key .app-sub-toolbar .cluster-add-objects,
|
|
197
|
+
.dnd-editor-root.mode-answer-key .app-sub-toolbar .cluster-history-zoom,
|
|
198
|
+
.dnd-editor-root.mode-answer-key .app-sub-toolbar .template-dropdown-wrapper {
|
|
199
|
+
display: none !important;
|
|
200
|
+
}
|