@sequent-org/moodboard 1.2.94 → 1.2.96

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.94",
3
+ "version": "1.2.96",
4
4
  "type": "module",
5
5
  "description": "Interactive moodboard",
6
6
  "main": "./src/index.js",
@@ -530,7 +530,6 @@ export class PlacementTool extends BaseTool {
530
530
  const world = this.app.stage.getChildByName && this.app.stage.getChildByName('worldLayer');
531
531
  return world || this.app.stage;
532
532
  }
533
-
534
533
 
535
534
  /**
536
535
  * Обработчик движения мыши для обновления позиции "призрака"
@@ -1847,9 +1847,21 @@ export class SelectTool extends BaseTool {
1847
1847
  const toScreen = (wx, wy) => {
1848
1848
  const worldLayer = this.textEditor.world || (this.app?.stage);
1849
1849
  if (!worldLayer) return { x: wx, y: wy };
1850
+
1851
+ // Используем тот же подход, что в HtmlHandlesLayer для рамки выделения
1852
+ const containerRect = view.parentElement.getBoundingClientRect();
1853
+ const viewRect = view.getBoundingClientRect();
1854
+ const offsetLeft = viewRect.left - containerRect.left;
1855
+ const offsetTop = viewRect.top - containerRect.top;
1856
+
1857
+ // Преобразуем мировые координаты в экранные через toGlobal
1850
1858
  const global = worldLayer.toGlobal(new PIXI.Point(wx, wy));
1851
- const viewRes = (this.app?.renderer?.resolution) || (view.width && view.clientWidth ? (view.width / view.clientWidth) : 1);
1852
- return { x: global.x / viewRes, y: global.y / viewRes };
1859
+
1860
+ // Возвращаем CSS координаты с учетом offset
1861
+ return {
1862
+ x: offsetLeft + global.x,
1863
+ y: offsetTop + global.y
1864
+ };
1853
1865
  };
1854
1866
  const screenPos = toScreen(position.x, position.y);
1855
1867
 
@@ -2032,30 +2044,10 @@ export class SelectTool extends BaseTool {
2032
2044
  }
2033
2045
  } catch (_) {}
2034
2046
 
2035
- // Учитываем масштаб браузера для позиционирования формы редактирования
2036
- const zoomFactor = this._getBrowserZoomFactor();
2037
- const correctedBaseLeftPx = baseLeftPx / zoomFactor;
2038
- const correctedBaseTopPx = baseTopPx / zoomFactor;
2039
-
2040
- console.log('🔍 Browser zoom detection:', {
2041
- zoomFactor,
2042
- basePos: { left: baseLeftPx, top: baseTopPx },
2043
- correctedPos: { left: correctedBaseLeftPx, top: correctedBaseTopPx },
2044
- screenPos,
2045
- window: {
2046
- innerWidth: window.innerWidth,
2047
- outerWidth: window.outerWidth,
2048
- visualViewport: window.visualViewport ? {
2049
- width: window.visualViewport.width,
2050
- height: window.visualViewport.height
2051
- } : null
2052
- }
2053
- });
2054
-
2055
- const leftPx = Math.round(correctedBaseLeftPx - padLeft);
2047
+ const leftPx = Math.round(baseLeftPx - padLeft);
2056
2048
  const topPx = create
2057
- ? Math.round(correctedBaseTopPx - padTop - (lineHeightPx / 2)) // по клику совмещаем центр строки с точкой клика
2058
- : Math.round(correctedBaseTopPx - padTop); // при редактировании совмещаем верх контента
2049
+ ? Math.round(baseTopPx - padTop - (lineHeightPx / 2)) // по клику совмещаем центр строки с точкой клика
2050
+ : Math.round(baseTopPx - padTop); // при редактировании совмещаем верх контента
2059
2051
  wrapper.style.left = `${leftPx}px`;
2060
2052
  wrapper.style.top = `${topPx}px`;
2061
2053
  // Сохраняем CSS-позицию редактора для точной синхронизации при закрытии
@@ -2824,84 +2816,5 @@ export class SelectTool extends BaseTool {
2824
2816
  // Обновляем ручки
2825
2817
  this.updateResizeHandles();
2826
2818
  }
2827
-
2828
- /**
2829
- * Получение коэффициента масштабирования браузера
2830
- */
2831
- _getBrowserZoomFactor() {
2832
- // Новый подход: через измерение canvas размеров
2833
- try {
2834
- if (this.app && this.app.view) {
2835
- const canvas = this.app.view;
2836
-
2837
- // Получаем CSS размеры и реальные размеры canvas
2838
- const cssWidth = canvas.clientWidth;
2839
- const cssHeight = canvas.clientHeight;
2840
- const realWidth = canvas.width;
2841
- const realHeight = canvas.height;
2842
-
2843
- // Проверяем device pixel ratio
2844
- const dpr = window.devicePixelRatio || 1;
2845
-
2846
- // Вычисляем ожидаемые реальные размеры с учетом DPR
2847
- const expectedRealWidth = cssWidth * dpr;
2848
- const expectedRealHeight = cssHeight * dpr;
2849
-
2850
- // Если реальные размеры отличаются от ожидаемых, значит есть zoom
2851
- let zoomFactorW = 1.0;
2852
- let zoomFactorH = 1.0;
2853
-
2854
- if (expectedRealWidth > 0) {
2855
- zoomFactorW = realWidth / expectedRealWidth;
2856
- }
2857
- if (expectedRealHeight > 0) {
2858
- zoomFactorH = realHeight / expectedRealHeight;
2859
- }
2860
-
2861
- // Берем среднее значение или наиболее близкое к 1.0
2862
- const avgZoom = (zoomFactorW + zoomFactorH) / 2;
2863
-
2864
- console.log('🔍 Canvas zoom detection:', {
2865
- css: { width: cssWidth, height: cssHeight },
2866
- real: { width: realWidth, height: realHeight },
2867
- dpr,
2868
- expected: { width: expectedRealWidth, height: expectedRealHeight },
2869
- zoom: { width: zoomFactorW, height: zoomFactorH, avg: avgZoom }
2870
- });
2871
-
2872
- // Если обнаружили разумное отклонение от 1.0, используем его
2873
- if (isFinite(avgZoom) && avgZoom > 0.1 && avgZoom < 10 && Math.abs(avgZoom - 1.0) > 0.01) {
2874
- return avgZoom;
2875
- }
2876
- }
2877
- } catch (_) {}
2878
-
2879
- // Fallback: простой метод через screen размеры
2880
- try {
2881
- // Сравниваем screen.width с window.innerWidth
2882
- const screenRatio = screen.width / window.innerWidth;
2883
- const dpr = window.devicePixelRatio || 1;
2884
- const expectedRatio = dpr;
2885
-
2886
- if (Math.abs(screenRatio - expectedRatio) > 0.1) {
2887
- const zoomFactor = screenRatio / expectedRatio;
2888
-
2889
- console.log('🔍 Screen zoom detection:', {
2890
- screen: { width: screen.width, height: screen.height },
2891
- window: { innerWidth: window.innerWidth, innerHeight: window.innerHeight },
2892
- screenRatio,
2893
- expectedRatio,
2894
- zoomFactor
2895
- });
2896
-
2897
- if (isFinite(zoomFactor) && zoomFactor > 0.1 && zoomFactor < 10) {
2898
- return zoomFactor;
2899
- }
2900
- }
2901
- } catch (_) {}
2902
-
2903
- console.log('🔍 Zoom detection: using fallback (no zoom correction)');
2904
- return 1.0; // Fallback: без коррекции
2905
- }
2906
2819
 
2907
2820
  }
@@ -1,4 +1,21 @@
1
1
  import { BaseTool } from '../BaseTool.js';
2
+ import iCursorSvg from '../../assets/icons/i-cursor.svg?raw';
3
+
4
+ // Масштабируем I-курсор в 2 раза меньше (примерно 16x32 с сохранением пропорций 1:2)
5
+ const _scaledICursorSvg = (() => {
6
+ try {
7
+ if (!/\bwidth="/i.test(iCursorSvg)) {
8
+ return iCursorSvg.replace('<svg ', '<svg width="16px" height="32px" ');
9
+ }
10
+ return iCursorSvg
11
+ .replace(/width="[^"]+"/i, 'width="16px"')
12
+ .replace(/height="[^"]+"/i, 'height="32px"');
13
+ } catch (_) {
14
+ return iCursorSvg;
15
+ }
16
+ })();
17
+
18
+ const TEXT_CURSOR = `url("data:image/svg+xml;charset=utf-8,${encodeURIComponent(_scaledICursorSvg)}") 0 0, text`;
2
19
 
3
20
  /**
4
21
  * Инструмент для создания и редактирования текстовых объектов
@@ -6,7 +23,7 @@ import { BaseTool } from '../BaseTool.js';
6
23
  export class TextTool extends BaseTool {
7
24
  constructor(eventBus) {
8
25
  super('text', eventBus);
9
- this.cursor = 'text';
26
+ this.cursor = 'text';
10
27
  this.hotkey = 't';
11
28
  this.container = null;
12
29
 
@@ -32,35 +49,13 @@ export class TextTool extends BaseTool {
32
49
  onMouseDown(event) {
33
50
  super.onMouseDown(event);
34
51
 
35
- // Учитываем масштаб браузера для корректных координат
36
- const zoomFactor = this._getBrowserZoomFactor();
37
- const correctedX = event.x * zoomFactor;
38
- const correctedY = event.y * zoomFactor;
39
-
40
- // Диагностика координат для решения проблемы с масштабом браузера
41
- console.log('🔍 TextTool onMouseDown диагностика:', {
42
- 'исходные event.x/y': { x: event.x, y: event.y },
43
- 'zoomFactor': zoomFactor,
44
- 'исправленные x/y': { x: correctedX, y: correctedY },
45
- 'window.devicePixelRatio': window.devicePixelRatio,
46
- 'масштаб браузера': window.outerWidth / window.innerWidth
47
- });
48
-
49
- // Преобразуем экранные координаты в мировые координаты PIXI
50
- const worldPoint = this._toWorld(correctedX, correctedY);
51
-
52
- console.log('🔍 Преобразование координат:', {
53
- 'исправленные': { x: correctedX, y: correctedY },
54
- 'мировые': worldPoint
55
- });
56
-
57
52
  // Проверяем, кликнули ли на существующий текстовый объект
58
- const hitObject = this.getTextObjectAt(correctedX, correctedY);
53
+ const hitObject = this.getTextObjectAt(event.x, event.y);
59
54
 
60
55
  if (hitObject) {
61
56
  this.startEditing(hitObject, event);
62
57
  } else {
63
- this.createNewText(worldPoint.x, worldPoint.y);
58
+ this.createNewText(event.x, event.y);
64
59
  }
65
60
  }
66
61
 
@@ -137,19 +132,10 @@ export class TextTool extends BaseTool {
137
132
  /**
138
133
  * Создание HTML input для редактирования текста
139
134
  */
140
- createTextInput(worldX, worldY, initialText) {
135
+ createTextInput(x, y, initialText) {
141
136
  // Удаляем предыдущий input если есть
142
137
  this.removeTextInput();
143
138
 
144
- // Преобразуем мировые координаты в экранные для HTML элемента
145
- const screenPos = this._toScreen(worldX, worldY);
146
-
147
- console.log('🔍 Позиционирование input:', {
148
- 'мировые координаты': { x: worldX, y: worldY },
149
- 'экранные координаты': screenPos,
150
- 'container': this.getContainer()
151
- });
152
-
153
139
  // Создаем новый input
154
140
  this.textInput = document.createElement('textarea');
155
141
  this.textInput.value = initialText;
@@ -158,8 +144,8 @@ export class TextTool extends BaseTool {
158
144
  // Стили input
159
145
  Object.assign(this.textInput.style, {
160
146
  position: 'absolute',
161
- left: `${screenPos.x}px`,
162
- top: `${screenPos.y}px`,
147
+ left: `${x}px`,
148
+ top: `${y}px`,
163
149
  border: '2px solid #007bff',
164
150
  borderRadius: '4px',
165
151
  padding: '4px 8px',
@@ -415,15 +401,14 @@ export class TextTool extends BaseTool {
415
401
  */
416
402
  activate(pixiApp) {
417
403
  super.activate();
418
- this.pixiApp = pixiApp;
419
404
 
420
405
  // Устанавливаем контейнер для размещения input
421
406
  if (pixiApp && pixiApp.view && pixiApp.view.parentElement) {
422
407
  this.setContainer(pixiApp.view.parentElement);
423
408
  }
424
- // Устанавливаем стандартный текстовый курсор
409
+ // Устанавливаем кастомный курсор на холст
425
410
  if (pixiApp && pixiApp.view) {
426
- pixiApp.view.style.cursor = 'text';
411
+ pixiApp.view.style.cursor = (this.cursor && this.cursor !== 'default') ? this.cursor : '';
427
412
  }
428
413
  }
429
414
 
@@ -436,50 +421,6 @@ export class TextTool extends BaseTool {
436
421
  }
437
422
  }
438
423
 
439
- /**
440
- * Преобразование экранных координат в мировые (worldLayer)
441
- */
442
- _toWorld(x, y) {
443
- if (!this.pixiApp || !this.pixiApp.stage) return { x, y };
444
- const world = this.pixiApp.stage.getChildByName && this.pixiApp.stage.getChildByName('worldLayer');
445
- if (!world || !world.toLocal) return { x, y };
446
- const p = new PIXI.Point(x, y);
447
- const local = world.toLocal(p);
448
- return { x: local.x, y: local.y };
449
- }
450
-
451
- /**
452
- * Преобразование мировых координат в экранные для HTML элементов
453
- */
454
- _toScreen(worldX, worldY) {
455
- if (!this.pixiApp || !this.pixiApp.stage) return { x: worldX, y: worldY };
456
- const world = this.pixiApp.stage.getChildByName && this.pixiApp.stage.getChildByName('worldLayer');
457
- if (!world || !world.toGlobal) return { x: worldX, y: worldY };
458
- const p = new PIXI.Point(worldX, worldY);
459
- const global = world.toGlobal(p);
460
-
461
- // Обратная коррекция для HTML элементов с учетом масштаба браузера
462
- const zoomFactor = this._getBrowserZoomFactor();
463
- return {
464
- x: global.x / zoomFactor,
465
- y: global.y / zoomFactor
466
- };
467
- }
468
-
469
- /**
470
- * Получение коэффициента масштабирования браузера
471
- */
472
- _getBrowserZoomFactor() {
473
- // Определяем масштаб браузера разными способами
474
- const outerInnerRatio = window.outerWidth / window.innerWidth;
475
- const devicePixelRatio = window.devicePixelRatio || 1;
476
-
477
- // Используемый подход: соотношение размеров окна браузера
478
- const zoomFactor = outerInnerRatio;
479
-
480
- return Math.max(0.1, Math.min(5, zoomFactor)); // Ограничиваем разумными пределами
481
- }
482
-
483
424
  /**
484
425
  * Установка настроек текста по умолчанию
485
426
  */
@@ -305,15 +305,42 @@ export class HtmlTextLayer {
305
305
  el.style.lineHeight = newLH;
306
306
  }
307
307
 
308
- // Позиция и габариты в экранных координатах с учетом масштаба браузера
309
- const zoomFactor = this._getBrowserZoomFactor();
310
- const left = ((tx + s * x) / res) / zoomFactor;
311
- const top = ((ty + s * y) / res) / zoomFactor;
312
- el.style.left = `${left}px`;
313
- el.style.top = `${top}px`;
314
- if (w && h) {
315
- el.style.width = `${Math.max(1, ((w * s) / res) / zoomFactor)}px`;
316
- el.style.height = `${Math.max(1, ((h * s) / res) / zoomFactor)}px`;
308
+ // Позиция и габариты в CSS координатах - используем тот же подход что в HtmlHandlesLayer
309
+ const worldLayer = this.core.pixi.worldLayer || this.core.pixi.app.stage;
310
+ const view = this.core.pixi.app.view;
311
+
312
+ if (worldLayer && view && view.parentElement) {
313
+ const containerRect = view.parentElement.getBoundingClientRect();
314
+ const viewRect = view.getBoundingClientRect();
315
+ const offsetLeft = viewRect.left - containerRect.left;
316
+ const offsetTop = viewRect.top - containerRect.top;
317
+
318
+ // Преобразуем мировые координаты в экранные через toGlobal
319
+ const tl = worldLayer.toGlobal(new PIXI.Point(x, y));
320
+ const br = worldLayer.toGlobal(new PIXI.Point(x + w, y + h));
321
+
322
+ // CSS координаты с учетом offset
323
+ const left = offsetLeft + tl.x;
324
+ const top = offsetTop + tl.y;
325
+ const width = Math.max(1, br.x - tl.x);
326
+ const height = Math.max(1, br.y - tl.y);
327
+
328
+ el.style.left = `${left}px`;
329
+ el.style.top = `${top}px`;
330
+ if (w && h) {
331
+ el.style.width = `${width}px`;
332
+ el.style.height = `${height}px`;
333
+ }
334
+ } else {
335
+ // Fallback к старому методу
336
+ const left = (tx + s * x) / res;
337
+ const top = (ty + s * y) / res;
338
+ el.style.left = `${left}px`;
339
+ el.style.top = `${top}px`;
340
+ if (w && h) {
341
+ el.style.width = `${Math.max(1, (w * s) / res)}px`;
342
+ el.style.height = `${Math.max(1, (h * s) / res)}px`;
343
+ }
317
344
  }
318
345
  // Поворот вокруг центра (как у PIXI и HTML-ручек)
319
346
  el.style.transformOrigin = 'center center';
@@ -369,68 +396,7 @@ export class HtmlTextLayer {
369
396
  position
370
397
  });
371
398
  }
372
- } catch (_) { }
373
- }
374
-
375
- /**
376
- * Получение коэффициента масштабирования браузера
377
- */
378
- _getBrowserZoomFactor() {
379
- // Новый подход: через измерение canvas размеров
380
- try {
381
- if (this.core && this.core.pixi && this.core.pixi.app && this.core.pixi.app.view) {
382
- const canvas = this.core.pixi.app.view;
383
-
384
- // Получаем CSS размеры и реальные размеры canvas
385
- const cssWidth = canvas.clientWidth;
386
- const cssHeight = canvas.clientHeight;
387
- const realWidth = canvas.width;
388
- const realHeight = canvas.height;
389
-
390
- // Проверяем device pixel ratio
391
- const dpr = window.devicePixelRatio || 1;
392
-
393
- // Вычисляем ожидаемые реальные размеры с учетом DPR
394
- const expectedRealWidth = cssWidth * dpr;
395
- const expectedRealHeight = cssHeight * dpr;
396
-
397
- // Если реальные размеры отличаются от ожидаемых, значит есть zoom
398
- let zoomFactorW = 1.0;
399
- let zoomFactorH = 1.0;
400
-
401
- if (expectedRealWidth > 0) {
402
- zoomFactorW = realWidth / expectedRealWidth;
403
- }
404
- if (expectedRealHeight > 0) {
405
- zoomFactorH = realHeight / expectedRealHeight;
406
- }
407
-
408
- // Берем среднее значение
409
- const avgZoom = (zoomFactorW + zoomFactorH) / 2;
410
-
411
- // Если обнаружили разумное отклонение от 1.0, используем его
412
- if (isFinite(avgZoom) && avgZoom > 0.1 && avgZoom < 10 && Math.abs(avgZoom - 1.0) > 0.01) {
413
- return avgZoom;
414
- }
415
- }
416
399
  } catch (_) {}
417
-
418
- // Fallback: простой метод через screen размеры
419
- try {
420
- const screenRatio = screen.width / window.innerWidth;
421
- const dpr = window.devicePixelRatio || 1;
422
- const expectedRatio = dpr;
423
-
424
- if (Math.abs(screenRatio - expectedRatio) > 0.1) {
425
- const zoomFactor = screenRatio / expectedRatio;
426
-
427
- if (isFinite(zoomFactor) && zoomFactor > 0.1 && zoomFactor < 10) {
428
- return zoomFactor;
429
- }
430
- }
431
- } catch (_) {}
432
-
433
- return 1.0; // Fallback: без коррекции
434
400
  }
435
401
  }
436
402