@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sequent-org/moodboard",
3
- "version": "1.2.30",
3
+ "version": "1.2.33",
4
4
  "type": "module",
5
5
  "description": "Interactive moodboard",
6
6
  "main": "./src/index.js",
@@ -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', {
@@ -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
- // Сохраняем пропорции для фреймов, кроме произвольных (lockedAspect=false)
1071
- if ((objectType === 'frame' || (objectType === 'image' && object?.properties?.isEmojiIcon)) && data.size) {
1072
- const lockedAspect = !!(object?.properties && (object.properties.lockedAspect === true));
1070
+ // Сохраняем пропорции:
1071
+ // - всегда для изображений (включая эмоджи-иконки, которые квадратные)
1072
+ // - для фреймов только если lockedAspect=true
1073
+ if (data.size && (objectType === 'image' || objectType === 'frame')) {
1073
1074
  const isEmoji = (objectType === 'image' && object?.properties?.isEmojiIcon);
1074
- if (!lockedAspect && !isEmoji) {
1075
- // произвольные фреймы без ограничений
1076
- } else {
1077
- const start = this._activeResize?.startSize || { width: object.width, height: object.height };
1078
- const aspect = isEmoji ? 1 : ((start.width > 0 && start.height > 0) ? (start.width / start.height) : (object.width / Math.max(1, object.height)));
1079
- let w = Math.max(1, data.size.width);
1080
- let h = Math.max(1, data.size.height);
1081
- const hndl = (this._activeResize?.handle || '').toLowerCase();
1082
- if (isEmoji) {
1083
- // Делаем квадрат, фиксируя противоположную сторону к старту
1084
- const s = Math.max(w, h);
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 - s); }
1093
- if (hndl.includes('n')) { y = startPos.y + (sh - s); }
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') x = startPos.x + Math.round((sw - s) / 2);
1097
- if (hndl === 'e' || hndl === 'w') y = startPos.y + Math.round((sh - s) / 2);
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
- async loadData(data) {
12
+ loadData(data) {
13
13
  if (!data) return;
14
14
 
15
- // Восстанавливаем отсутствующие URL изображений по imageId перед созданием объектов
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
- await this.dataManager.loadData(this.data || { objects: [] });
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
- await this.dataManager.loadData(boardData.data);
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
- await this.dataManager.loadData(this.data || { objects: [] });
388
+ this.dataManager.loadData(this.data || { objects: [] });
404
389
 
405
390
  // Вызываем коллбек onLoad
406
391
  if (typeof this.options.onLoad === 'function') {