@sequent-org/moodboard 1.2.29 → 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/index.js +66 -62
- package/src/moodboard/DataManager.js +2 -23
- package/src/moodboard/MoodBoard.js +3 -3
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/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') {
|
|
@@ -377,7 +377,7 @@ export class MoodBoard {
|
|
|
377
377
|
|
|
378
378
|
if (boardData && boardData.data) {
|
|
379
379
|
console.log('✅ MoodBoard: данные загружены с сервера', boardData.data);
|
|
380
|
-
|
|
380
|
+
this.dataManager.loadData(boardData.data);
|
|
381
381
|
|
|
382
382
|
// Вызываем коллбек onLoad
|
|
383
383
|
if (typeof this.options.onLoad === 'function') {
|
|
@@ -385,7 +385,7 @@ export class MoodBoard {
|
|
|
385
385
|
}
|
|
386
386
|
} else {
|
|
387
387
|
console.log('📦 MoodBoard: нет данных с сервера, загружаем пустую доску');
|
|
388
|
-
|
|
388
|
+
this.dataManager.loadData(this.data || { objects: [] });
|
|
389
389
|
|
|
390
390
|
// Вызываем коллбек onLoad
|
|
391
391
|
if (typeof this.options.onLoad === 'function') {
|