@sequent-org/moodboard 1.2.30 → 1.2.33
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/package.json +1 -1
- package/src/core/ApiClient.js +0 -16
- package/src/core/SaveManager.js +0 -35
- package/src/core/index.js +66 -62
- package/src/moodboard/DataManager.js +2 -23
- package/src/moodboard/MoodBoard.js +3 -18
package/package.json
CHANGED
package/src/core/ApiClient.js
CHANGED
|
@@ -45,22 +45,6 @@ export class ApiClient {
|
|
|
45
45
|
// Фильтруем объекты изображений и файлов - убираем избыточные данные
|
|
46
46
|
const cleanedData = this._cleanObjectData(boardData);
|
|
47
47
|
|
|
48
|
-
// Диагностика размеров изображений перед отправкой
|
|
49
|
-
try {
|
|
50
|
-
const images = Array.isArray(cleanedData?.objects)
|
|
51
|
-
? cleanedData.objects.filter(o => o && o.type === 'image')
|
|
52
|
-
: [];
|
|
53
|
-
images.forEach((o) => {
|
|
54
|
-
console.log('🧪 SAVE image size', {
|
|
55
|
-
id: o.id,
|
|
56
|
-
width: o.width,
|
|
57
|
-
height: o.height,
|
|
58
|
-
propWidth: o.properties?.width,
|
|
59
|
-
propHeight: o.properties?.height
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
} catch (_) {}
|
|
63
|
-
|
|
64
48
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
|
65
49
|
|
|
66
50
|
const response = await fetch('/api/moodboard/save', {
|
package/src/core/SaveManager.js
CHANGED
|
@@ -157,41 +157,6 @@ export class SaveManager {
|
|
|
157
157
|
data: saveData,
|
|
158
158
|
timestamp: new Date().toISOString()
|
|
159
159
|
});
|
|
160
|
-
|
|
161
|
-
// Диагностика: по флагу проверяем, что сервер отдает те же размеры на чтение
|
|
162
|
-
try {
|
|
163
|
-
if (typeof window !== 'undefined' && window.MOODBOARD_DEBUG_VERIFY_LOAD === true) {
|
|
164
|
-
const boardId = saveData?.id;
|
|
165
|
-
if (boardId) {
|
|
166
|
-
const verifyUrl = `${this.options.loadEndpoint}/${boardId}`;
|
|
167
|
-
const resp = await fetch(verifyUrl, {
|
|
168
|
-
method: 'GET',
|
|
169
|
-
headers: {
|
|
170
|
-
'Accept': 'application/json',
|
|
171
|
-
'X-Requested-With': 'XMLHttpRequest'
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
if (resp.ok) {
|
|
175
|
-
const result = await resp.json();
|
|
176
|
-
const data = result?.data || result;
|
|
177
|
-
const imgs = Array.isArray(data?.objects) ? data.objects.filter(o => o && o.type === 'image') : [];
|
|
178
|
-
imgs.forEach((o) => {
|
|
179
|
-
console.log('🧪 VERIFY LOAD after save image size', {
|
|
180
|
-
id: o.id,
|
|
181
|
-
width: o.width,
|
|
182
|
-
height: o.height,
|
|
183
|
-
propWidth: o.properties?.width,
|
|
184
|
-
propHeight: o.properties?.height
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
} else {
|
|
188
|
-
console.warn('⚠️ VERIFY LOAD failed:', resp.status, resp.statusText);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
} catch (e) {
|
|
193
|
-
console.warn('⚠️ VERIFY LOAD exception:', e?.message || e);
|
|
194
|
-
}
|
|
195
160
|
} else {
|
|
196
161
|
throw new Error(response.message || 'Ошибка сохранения');
|
|
197
162
|
}
|
package/src/core/index.js
CHANGED
|
@@ -1067,80 +1067,84 @@ export class CoreMoodBoard {
|
|
|
1067
1067
|
const object = objects.find(obj => obj.id === data.object);
|
|
1068
1068
|
const objectType = object ? object.type : null;
|
|
1069
1069
|
|
|
1070
|
-
// Сохраняем
|
|
1071
|
-
|
|
1072
|
-
|
|
1070
|
+
// Сохраняем пропорции:
|
|
1071
|
+
// - всегда для изображений (включая эмоджи-иконки, которые квадратные)
|
|
1072
|
+
// - для фреймов только если lockedAspect=true
|
|
1073
|
+
if (data.size && (objectType === 'image' || objectType === 'frame')) {
|
|
1073
1074
|
const isEmoji = (objectType === 'image' && object?.properties?.isEmojiIcon);
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1075
|
+
const isImage = (objectType === 'image');
|
|
1076
|
+
const lockedAspect = objectType === 'frame'
|
|
1077
|
+
? !!(object?.properties && object.properties.lockedAspect === true)
|
|
1078
|
+
: true; // для изображений всегда держим аспект
|
|
1079
|
+
|
|
1080
|
+
if (lockedAspect || isImage || isEmoji) {
|
|
1081
|
+
const start = this._activeResize?.startSize || { width: object.width, height: object.height };
|
|
1082
|
+
const startW = Math.max(1, start.width);
|
|
1083
|
+
const startH = Math.max(1, start.height);
|
|
1084
|
+
const aspect = isEmoji ? 1 : (startW / startH);
|
|
1085
|
+
|
|
1086
|
+
let w = Math.max(1, data.size.width);
|
|
1087
|
+
let h = Math.max(1, data.size.height);
|
|
1088
|
+
const hndl = (this._activeResize?.handle || '').toLowerCase();
|
|
1089
|
+
|
|
1090
|
+
if (isEmoji) {
|
|
1091
|
+
// Квадрат
|
|
1092
|
+
const s = Math.max(w, h);
|
|
1093
|
+
if (!data.position && this._activeResize && this._activeResize.objectId === data.object) {
|
|
1094
|
+
const startPos = this._activeResize.startPosition;
|
|
1095
|
+
const sw = this._activeResize.startSize.width;
|
|
1096
|
+
const sh = this._activeResize.startSize.height;
|
|
1097
|
+
let x = startPos.x;
|
|
1098
|
+
let y = startPos.y;
|
|
1099
|
+
if (hndl.includes('w')) { x = startPos.x + (sw - s); }
|
|
1100
|
+
if (hndl.includes('n')) { y = startPos.y + (sh - s); }
|
|
1101
|
+
const isEdge = ['n','s','e','w'].includes(hndl);
|
|
1102
|
+
if (isEdge) {
|
|
1103
|
+
if (hndl === 'n' || hndl === 's') x = startPos.x + Math.round((sw - s) / 2);
|
|
1104
|
+
if (hndl === 'e' || hndl === 'w') y = startPos.y + Math.round((sh - s) / 2);
|
|
1105
|
+
}
|
|
1106
|
+
data.position = { x: Math.round(x), y: Math.round(y) };
|
|
1107
|
+
}
|
|
1108
|
+
w = s; h = s;
|
|
1109
|
+
} else {
|
|
1110
|
+
// Поддержка аспекта (для images всегда; для frames — если lockedAspect)
|
|
1111
|
+
const dw = Math.abs(w - startW);
|
|
1112
|
+
const dh = Math.abs(h - startH);
|
|
1113
|
+
if (dw >= dh) { h = Math.round(w / aspect); } else { w = Math.round(h * aspect); }
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// Минимальная площадь — только для фреймов (как раньше)
|
|
1117
|
+
if (objectType === 'frame') {
|
|
1118
|
+
const minArea = 1800;
|
|
1119
|
+
const area = Math.max(1, w * h);
|
|
1120
|
+
if (area < minArea) {
|
|
1121
|
+
const scale = Math.sqrt(minArea / area);
|
|
1122
|
+
w = Math.round(w * scale);
|
|
1123
|
+
h = Math.round(h * scale);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
data.size = { width: w, height: h };
|
|
1128
|
+
|
|
1129
|
+
// Компенсация позиции по зафиксированной стороне
|
|
1086
1130
|
if (!data.position && this._activeResize && this._activeResize.objectId === data.object) {
|
|
1087
1131
|
const startPos = this._activeResize.startPosition;
|
|
1088
1132
|
const sw = this._activeResize.startSize.width;
|
|
1089
1133
|
const sh = this._activeResize.startSize.height;
|
|
1090
1134
|
let x = startPos.x;
|
|
1091
1135
|
let y = startPos.y;
|
|
1092
|
-
if (hndl.includes('w')) { x = startPos.x + (sw -
|
|
1093
|
-
if (hndl.includes('n')) { y = startPos.y + (sh -
|
|
1136
|
+
if (hndl.includes('w')) { x = startPos.x + (sw - data.size.width); }
|
|
1137
|
+
if (hndl.includes('n')) { y = startPos.y + (sh - data.size.height); }
|
|
1094
1138
|
const isEdge = ['n','s','e','w'].includes(hndl);
|
|
1095
1139
|
if (isEdge) {
|
|
1096
|
-
if (hndl === 'n' || hndl === 's')
|
|
1097
|
-
|
|
1140
|
+
if (hndl === 'n' || hndl === 's') {
|
|
1141
|
+
x = startPos.x + Math.round((sw - data.size.width) / 2);
|
|
1142
|
+
} else if (hndl === 'e' || hndl === 'w') {
|
|
1143
|
+
y = startPos.y + Math.round((sh - data.size.height) / 2);
|
|
1144
|
+
}
|
|
1098
1145
|
}
|
|
1099
1146
|
data.position = { x: Math.round(x), y: Math.round(y) };
|
|
1100
1147
|
}
|
|
1101
|
-
w = s; h = s;
|
|
1102
|
-
} else {
|
|
1103
|
-
// Обычная поддержка аспекта для фреймов
|
|
1104
|
-
const dw = Math.abs(w - start.width);
|
|
1105
|
-
const dh = Math.abs(h - start.height);
|
|
1106
|
-
if (dw >= dh) { h = Math.round(w / aspect); } else { w = Math.round(h * aspect); }
|
|
1107
|
-
}
|
|
1108
|
-
// Минимальная площадь — только для фреймов
|
|
1109
|
-
if (!isEmoji) {
|
|
1110
|
-
const minArea = 1800;
|
|
1111
|
-
const area = Math.max(1, w * h);
|
|
1112
|
-
if (area < minArea) {
|
|
1113
|
-
const scale = Math.sqrt(minArea / area);
|
|
1114
|
-
w = Math.round(w * scale);
|
|
1115
|
-
h = Math.round(h * scale);
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
data.size = { width: w, height: h };
|
|
1119
|
-
|
|
1120
|
-
// Если позиция известна (фиксированная противоположная сторона) — откорректируем её
|
|
1121
|
-
if (!data.position && this._activeResize && this._activeResize.objectId === data.object) {
|
|
1122
|
-
const hndl = (this._activeResize.handle || '').toLowerCase();
|
|
1123
|
-
const startPos = this._activeResize.startPosition;
|
|
1124
|
-
const sw = this._activeResize.startSize.width;
|
|
1125
|
-
const sh = this._activeResize.startSize.height;
|
|
1126
|
-
let x = startPos.x;
|
|
1127
|
-
let y = startPos.y;
|
|
1128
|
-
// Базовая привязка противоположной стороны
|
|
1129
|
-
if (hndl.includes('w')) { x = startPos.x + (sw - w); }
|
|
1130
|
-
if (hndl.includes('n')) { y = startPos.y + (sh - h); }
|
|
1131
|
-
// Симметрическая компенсация по перпендикулярной оси для edge-хэндлов
|
|
1132
|
-
const isEdge = ['n','s','e','w'].includes(hndl);
|
|
1133
|
-
if (isEdge) {
|
|
1134
|
-
if (hndl === 'n' || hndl === 's') {
|
|
1135
|
-
// Вверх/вниз: верх или низ фиксируется, ширина меняется симметрично относительно центра
|
|
1136
|
-
x = startPos.x + Math.round((sw - w) / 2);
|
|
1137
|
-
} else if (hndl === 'e' || hndl === 'w') {
|
|
1138
|
-
// Вправо/влево: левая или правая фиксируется, высота меняется симметрично относительно центра
|
|
1139
|
-
y = startPos.y + Math.round((sh - h) / 2);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
data.position = { x: Math.round(x), y: Math.round(y) };
|
|
1143
|
-
}
|
|
1144
1148
|
}
|
|
1145
1149
|
}
|
|
1146
1150
|
|
|
@@ -9,18 +9,10 @@ export class DataManager {
|
|
|
9
9
|
/**
|
|
10
10
|
* Загружает данные в MoodBoard
|
|
11
11
|
*/
|
|
12
|
-
|
|
12
|
+
loadData(data) {
|
|
13
13
|
if (!data) return;
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
const apiClient = this.coreMoodboard?.apiClient;
|
|
18
|
-
if (apiClient && typeof apiClient.restoreObjectUrls === 'function') {
|
|
19
|
-
data = await apiClient.restoreObjectUrls(data);
|
|
20
|
-
}
|
|
21
|
-
} catch (e) {
|
|
22
|
-
console.warn('⚠️ Не удалось восстановить URL изображений по imageId:', e?.message || e);
|
|
23
|
-
}
|
|
15
|
+
|
|
24
16
|
|
|
25
17
|
// Очищаем доску перед загрузкой
|
|
26
18
|
this.clearBoard();
|
|
@@ -31,19 +23,6 @@ export class DataManager {
|
|
|
31
23
|
|
|
32
24
|
data.objects.forEach((objectData, index) => {
|
|
33
25
|
try {
|
|
34
|
-
// Диагностика размеров изображений на загрузке до создания
|
|
35
|
-
if (objectData && objectData.type === 'image') {
|
|
36
|
-
try {
|
|
37
|
-
console.log('🧪 LOAD image size (before create)', {
|
|
38
|
-
id: objectData.id,
|
|
39
|
-
width: objectData.width,
|
|
40
|
-
height: objectData.height,
|
|
41
|
-
propWidth: objectData.properties?.width,
|
|
42
|
-
propHeight: objectData.properties?.height
|
|
43
|
-
});
|
|
44
|
-
} catch (_) {}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
26
|
// Используем полные данные объекта, включая ID
|
|
48
27
|
const createdObject = this.coreMoodboard.createObjectFromData(objectData);
|
|
49
28
|
|
|
@@ -344,7 +344,7 @@ export class MoodBoard {
|
|
|
344
344
|
|
|
345
345
|
if (!boardId || !this.options.apiUrl) {
|
|
346
346
|
console.log('📦 MoodBoard: нет boardId или apiUrl, загружаем пустую доску');
|
|
347
|
-
|
|
347
|
+
this.dataManager.loadData(this.data || { objects: [] });
|
|
348
348
|
|
|
349
349
|
// Вызываем коллбек onLoad
|
|
350
350
|
if (typeof this.options.onLoad === 'function') {
|
|
@@ -374,25 +374,10 @@ export class MoodBoard {
|
|
|
374
374
|
}
|
|
375
375
|
|
|
376
376
|
const boardData = await response.json();
|
|
377
|
-
|
|
378
|
-
// Диагностика: лог размеров изображений прямо из ответа API до любой обработки
|
|
379
|
-
try {
|
|
380
|
-
const objects = (boardData && boardData.data && Array.isArray(boardData.data.objects)) ? boardData.data.objects : [];
|
|
381
|
-
const images = objects.filter(o => o && o.type === 'image');
|
|
382
|
-
images.forEach((o) => {
|
|
383
|
-
console.log('🧪 API LOAD payload image size', {
|
|
384
|
-
id: o.id,
|
|
385
|
-
width: o.width,
|
|
386
|
-
height: o.height,
|
|
387
|
-
propWidth: o.properties?.width,
|
|
388
|
-
propHeight: o.properties?.height
|
|
389
|
-
});
|
|
390
|
-
});
|
|
391
|
-
} catch (_) {}
|
|
392
377
|
|
|
393
378
|
if (boardData && boardData.data) {
|
|
394
379
|
console.log('✅ MoodBoard: данные загружены с сервера', boardData.data);
|
|
395
|
-
|
|
380
|
+
this.dataManager.loadData(boardData.data);
|
|
396
381
|
|
|
397
382
|
// Вызываем коллбек onLoad
|
|
398
383
|
if (typeof this.options.onLoad === 'function') {
|
|
@@ -400,7 +385,7 @@ export class MoodBoard {
|
|
|
400
385
|
}
|
|
401
386
|
} else {
|
|
402
387
|
console.log('📦 MoodBoard: нет данных с сервера, загружаем пустую доску');
|
|
403
|
-
|
|
388
|
+
this.dataManager.loadData(this.data || { objects: [] });
|
|
404
389
|
|
|
405
390
|
// Вызываем коллбек onLoad
|
|
406
391
|
if (typeof this.options.onLoad === 'function') {
|