@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.
@@ -0,0 +1,147 @@
1
+ /**
2
+ * ValidationEngine - Proje JSON verisi, cevap anahtarı ve nesne tutarlılığı
3
+ * doğrulamalarını gerçekleştirir. Hatalı ve eksik durumları tespit eder.
4
+ */
5
+ class ValidationEngine {
6
+ static get VALID_ACTIVITY_TYPES() {
7
+ return ['image-labeling', 'matching', 'categorization', 'sequencing'];
8
+ }
9
+
10
+ static get VALID_ROLES() {
11
+ return ['decorative', 'draggable', 'target', 'category', 'sequence_slot'];
12
+ }
13
+
14
+ /**
15
+ * Bir projenin şemasını doğrular
16
+ * @param {Object} projectData
17
+ * @returns {{ valid: boolean, errors: string[], warnings: string[] }}
18
+ */
19
+ static validateProject(projectData) {
20
+ const errors = [];
21
+ const warnings = [];
22
+
23
+ if (!projectData || typeof projectData !== 'object') {
24
+ return { valid: false, errors: ['Proje verisi geçerli bir JSON nesnesi değil.'], warnings };
25
+ }
26
+
27
+ // Sürüm kontrolü
28
+ if (!projectData.version) {
29
+ warnings.push('Proje sürüm bilgisi bulunamadı, varsayılan sürüm (1.0) kabul edildi.');
30
+ }
31
+
32
+ // Proje başlığı ve türü
33
+ if (!projectData.project || typeof projectData.project !== 'object') {
34
+ errors.push('Proje meta verisi (project alanı) eksik.');
35
+ } else {
36
+ if (!projectData.project.name || typeof projectData.project.name !== 'string') {
37
+ warnings.push('Proje adı belirtilmemiş.');
38
+ }
39
+ if (!this.VALID_ACTIVITY_TYPES.includes(projectData.project.activityType)) {
40
+ errors.push(`Geçersiz etkinlik türü: ${projectData.project.activityType}. Desteklenenler: ${this.VALID_ACTIVITY_TYPES.join(', ')}`);
41
+ }
42
+ }
43
+
44
+ // Sahne (Stage) kontrolleri
45
+ if (!projectData.stage || typeof projectData.stage !== 'object') {
46
+ errors.push('Sahne ayarları (stage alanı) eksik.');
47
+ } else {
48
+ if (typeof projectData.stage.width !== 'number' || projectData.stage.width <= 0) {
49
+ errors.push('Sahne genişliği pozitif bir sayı olmalıdır.');
50
+ }
51
+ if (typeof projectData.stage.height !== 'number' || projectData.stage.height <= 0) {
52
+ errors.push('Sahne yüksekliği pozitif bir sayı olmalıdır.');
53
+ }
54
+ }
55
+
56
+ // Nesneler (Objects) kontrolleri
57
+ if (!Array.isArray(projectData.objects)) {
58
+ errors.push('Nesneler listesi (objects alanı) bir dizi olmalıdır.');
59
+ } else {
60
+ const idSet = new Set();
61
+ let draggablesCount = 0;
62
+ let targetsCount = 0;
63
+
64
+ projectData.objects.forEach((obj, idx) => {
65
+ if (!obj.id) {
66
+ errors.push(`Nesne #${idx + 1} için 'id' alanı eksik.`);
67
+ } else if (idSet.has(obj.id)) {
68
+ errors.push(`Mükerrer nesne ID'si tespit edildi: '${obj.id}'.`);
69
+ } else {
70
+ idSet.add(obj.id);
71
+ }
72
+
73
+ if (typeof obj.x !== 'number' || typeof obj.y !== 'number') {
74
+ errors.push(`'${obj.id || idx}' nesnesinin X ve Y koordinatları sayı olmalıdır.`);
75
+ }
76
+
77
+ if (obj.role === 'draggable') draggablesCount++;
78
+ if (['target', 'category', 'sequence_slot'].includes(obj.role)) targetsCount++;
79
+ });
80
+
81
+ if (draggablesCount === 0) {
82
+ warnings.push('Etkinlikte henüz hiç sürüklenebilir nesne bulunmuyor.');
83
+ }
84
+ if (targetsCount === 0) {
85
+ warnings.push('Etkinlikte henüz hiç hedef alan bulunmuyor.');
86
+ }
87
+
88
+ // Cevap anahtarı kontrolü
89
+ const answerKey = projectData.answerKey || {};
90
+ for (const [dragId, targetId] of Object.entries(answerKey)) {
91
+ if (!idSet.has(dragId)) {
92
+ warnings.push(`Cevap anahtarındaki '${dragId}' ID'li sürüklenebilir nesne sahnede bulunamadı.`);
93
+ }
94
+ if (projectData.project && projectData.project.activityType !== 'sequencing') {
95
+ if (!idSet.has(targetId)) {
96
+ warnings.push(`Cevap anahtarındaki '${targetId}' ID'li hedef nesne sahnede bulunamadı.`);
97
+ }
98
+ }
99
+ }
100
+ }
101
+
102
+ return {
103
+ valid: errors.length === 0,
104
+ errors,
105
+ warnings
106
+ };
107
+ }
108
+
109
+ /**
110
+ * HTML dışa aktarmadan önce cevap anahtarının oynanabilirlik durumunu kontrol eder
111
+ */
112
+ static validatePlayability(projectData) {
113
+ const issues = [];
114
+ const objects = projectData.objects || [];
115
+ const answerKey = projectData.answerKey || {};
116
+ const draggables = objects.filter(o => o.role === 'draggable');
117
+
118
+ if (draggables.length === 0) {
119
+ issues.push('En az bir adet sürüklenebilir nesne eklemelisiniz.');
120
+ }
121
+
122
+ const unassignedDraggables = draggables.filter(d => !answerKey[d.id]);
123
+ if (unassignedDraggables.length > 0) {
124
+ const names = unassignedDraggables.map(d => d.name || d.text || d.id).join(', ');
125
+ issues.push(`Şu sürüklenebilir nesnelerin cevap anahtarında hedefi belirlenmemiş: ${names}`);
126
+ }
127
+
128
+ return {
129
+ ready: issues.length === 0,
130
+ issues
131
+ };
132
+ }
133
+
134
+ /**
135
+ * XSS ve zararlı HTML girişlerini engellemek için metin temizleme
136
+ */
137
+ static sanitizeText(str) {
138
+ if (str === null || str === undefined) return '';
139
+ const temp = document.createElement('div');
140
+ temp.textContent = String(str);
141
+ return temp.innerHTML;
142
+ }
143
+ }
144
+
145
+ // Global namespace
146
+ window.DND = window.DND || {};
147
+ window.DND.ValidationEngine = ValidationEngine;