@sequent-org/moodboard 1.2.93 → 1.2.95
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
|
@@ -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
|
* Обработчик движения мыши для обновления позиции "призрака"
|
|
@@ -2032,30 +2032,10 @@ export class SelectTool extends BaseTool {
|
|
|
2032
2032
|
}
|
|
2033
2033
|
} catch (_) {}
|
|
2034
2034
|
|
|
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);
|
|
2035
|
+
const leftPx = Math.round(baseLeftPx - padLeft);
|
|
2056
2036
|
const topPx = create
|
|
2057
|
-
? Math.round(
|
|
2058
|
-
: Math.round(
|
|
2037
|
+
? Math.round(baseTopPx - padTop - (lineHeightPx / 2)) // по клику совмещаем центр строки с точкой клика
|
|
2038
|
+
: Math.round(baseTopPx - padTop); // при редактировании совмещаем верх контента
|
|
2059
2039
|
wrapper.style.left = `${leftPx}px`;
|
|
2060
2040
|
wrapper.style.top = `${topPx}px`;
|
|
2061
2041
|
// Сохраняем CSS-позицию редактора для точной синхронизации при закрытии
|
|
@@ -2824,77 +2804,5 @@ export class SelectTool extends BaseTool {
|
|
|
2824
2804
|
// Обновляем ручки
|
|
2825
2805
|
this.updateResizeHandles();
|
|
2826
2806
|
}
|
|
2827
|
-
|
|
2828
|
-
/**
|
|
2829
|
-
* Получение коэффициента масштабирования браузера
|
|
2830
|
-
*/
|
|
2831
|
-
_getBrowserZoomFactor() {
|
|
2832
|
-
// Множественные методы определения масштаба браузера для надежности
|
|
2833
|
-
let zoomFactors = [];
|
|
2834
|
-
|
|
2835
|
-
try {
|
|
2836
|
-
// Метод 1: Через window размеры
|
|
2837
|
-
const outerInnerRatio = window.outerWidth / window.innerWidth;
|
|
2838
|
-
if (isFinite(outerInnerRatio) && outerInnerRatio > 0.1 && outerInnerRatio < 10) {
|
|
2839
|
-
zoomFactors.push({ method: 'outerInner', value: outerInnerRatio });
|
|
2840
|
-
}
|
|
2841
|
-
} catch (_) {}
|
|
2842
|
-
|
|
2843
|
-
try {
|
|
2844
|
-
// Метод 2: Через visualViewport если доступно
|
|
2845
|
-
if (window.visualViewport) {
|
|
2846
|
-
const vpZoom = window.innerWidth / window.visualViewport.width;
|
|
2847
|
-
if (isFinite(vpZoom) && vpZoom > 0.1 && vpZoom < 10) {
|
|
2848
|
-
zoomFactors.push({ method: 'visualViewport', value: vpZoom });
|
|
2849
|
-
}
|
|
2850
|
-
}
|
|
2851
|
-
} catch (_) {}
|
|
2852
|
-
|
|
2853
|
-
try {
|
|
2854
|
-
// Метод 3: Через тестовый элемент (проверяем разные единицы)
|
|
2855
|
-
const testEl = document.createElement('div');
|
|
2856
|
-
testEl.style.position = 'absolute';
|
|
2857
|
-
testEl.style.left = '-9999px';
|
|
2858
|
-
testEl.style.top = '-9999px';
|
|
2859
|
-
testEl.style.width = '100px';
|
|
2860
|
-
testEl.style.height = '100px';
|
|
2861
|
-
testEl.style.visibility = 'hidden';
|
|
2862
|
-
testEl.style.pointerEvents = 'none';
|
|
2863
|
-
|
|
2864
|
-
document.body.appendChild(testEl);
|
|
2865
|
-
|
|
2866
|
-
const rect = testEl.getBoundingClientRect();
|
|
2867
|
-
const elemZoom = 100 / rect.width;
|
|
2868
|
-
|
|
2869
|
-
document.body.removeChild(testEl);
|
|
2870
|
-
|
|
2871
|
-
if (isFinite(elemZoom) && elemZoom > 0.1 && elemZoom < 10) {
|
|
2872
|
-
zoomFactors.push({ method: 'testElement', value: elemZoom });
|
|
2873
|
-
}
|
|
2874
|
-
} catch (_) {}
|
|
2875
|
-
|
|
2876
|
-
// Логирование всех методов для диагностики
|
|
2877
|
-
console.log('🔍 Zoom detection methods:', zoomFactors);
|
|
2878
|
-
|
|
2879
|
-
// Выбираем наиболее подходящий результат
|
|
2880
|
-
if (zoomFactors.length === 0) {
|
|
2881
|
-
return 1.0; // Fallback
|
|
2882
|
-
}
|
|
2883
|
-
|
|
2884
|
-
// Если есть visualViewport, предпочитаем его
|
|
2885
|
-
const vpMethod = zoomFactors.find(f => f.method === 'visualViewport');
|
|
2886
|
-
if (vpMethod) {
|
|
2887
|
-
return vpMethod.value;
|
|
2888
|
-
}
|
|
2889
|
-
|
|
2890
|
-
// Иначе берем outerInner как наиболее стабильный
|
|
2891
|
-
const outerMethod = zoomFactors.find(f => f.method === 'outerInner');
|
|
2892
|
-
if (outerMethod) {
|
|
2893
|
-
return outerMethod.value;
|
|
2894
|
-
}
|
|
2895
|
-
|
|
2896
|
-
// Последний fallback - первый доступный метод
|
|
2897
|
-
return zoomFactors[0].value;
|
|
2898
|
-
}
|
|
2899
2807
|
|
|
2900
2808
|
}
|
|
@@ -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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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: `${
|
|
162
|
-
top: `${
|
|
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 = '
|
|
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
|
*/
|
package/src/ui/HtmlTextLayer.js
CHANGED
|
@@ -305,15 +305,14 @@ export class HtmlTextLayer {
|
|
|
305
305
|
el.style.lineHeight = newLH;
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
// Позиция и габариты в экранных координатах
|
|
309
|
-
const
|
|
310
|
-
const
|
|
311
|
-
const top = ((ty + s * y) / res) / zoomFactor;
|
|
308
|
+
// Позиция и габариты в экранных координатах
|
|
309
|
+
const left = (tx + s * x) / res;
|
|
310
|
+
const top = (ty + s * y) / res;
|
|
312
311
|
el.style.left = `${left}px`;
|
|
313
312
|
el.style.top = `${top}px`;
|
|
314
313
|
if (w && h) {
|
|
315
|
-
el.style.width = `${Math.max(1, (
|
|
316
|
-
el.style.height = `${Math.max(1, (
|
|
314
|
+
el.style.width = `${Math.max(1, (w * s) / res)}px`;
|
|
315
|
+
el.style.height = `${Math.max(1, (h * s) / res)}px`;
|
|
317
316
|
}
|
|
318
317
|
// Поворот вокруг центра (как у PIXI и HTML-ручек)
|
|
319
318
|
el.style.transformOrigin = 'center center';
|
|
@@ -369,76 +368,7 @@ export class HtmlTextLayer {
|
|
|
369
368
|
position
|
|
370
369
|
});
|
|
371
370
|
}
|
|
372
|
-
} catch (_) { }
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Получение коэффициента масштабирования браузера
|
|
377
|
-
*/
|
|
378
|
-
_getBrowserZoomFactor() {
|
|
379
|
-
// Множественные методы определения масштаба браузера для надежности
|
|
380
|
-
let zoomFactors = [];
|
|
381
|
-
|
|
382
|
-
try {
|
|
383
|
-
// Метод 1: Через window размеры
|
|
384
|
-
const outerInnerRatio = window.outerWidth / window.innerWidth;
|
|
385
|
-
if (isFinite(outerInnerRatio) && outerInnerRatio > 0.1 && outerInnerRatio < 10) {
|
|
386
|
-
zoomFactors.push({ method: 'outerInner', value: outerInnerRatio });
|
|
387
|
-
}
|
|
388
|
-
} catch (_) {}
|
|
389
|
-
|
|
390
|
-
try {
|
|
391
|
-
// Метод 2: Через visualViewport если доступно
|
|
392
|
-
if (window.visualViewport) {
|
|
393
|
-
const vpZoom = window.innerWidth / window.visualViewport.width;
|
|
394
|
-
if (isFinite(vpZoom) && vpZoom > 0.1 && vpZoom < 10) {
|
|
395
|
-
zoomFactors.push({ method: 'visualViewport', value: vpZoom });
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
} catch (_) {}
|
|
399
|
-
|
|
400
|
-
try {
|
|
401
|
-
// Метод 3: Через тестовый элемент (проверяем разные единицы)
|
|
402
|
-
const testEl = document.createElement('div');
|
|
403
|
-
testEl.style.position = 'absolute';
|
|
404
|
-
testEl.style.left = '-9999px';
|
|
405
|
-
testEl.style.top = '-9999px';
|
|
406
|
-
testEl.style.width = '100px';
|
|
407
|
-
testEl.style.height = '100px';
|
|
408
|
-
testEl.style.visibility = 'hidden';
|
|
409
|
-
testEl.style.pointerEvents = 'none';
|
|
410
|
-
|
|
411
|
-
document.body.appendChild(testEl);
|
|
412
|
-
|
|
413
|
-
const rect = testEl.getBoundingClientRect();
|
|
414
|
-
const elemZoom = 100 / rect.width;
|
|
415
|
-
|
|
416
|
-
document.body.removeChild(testEl);
|
|
417
|
-
|
|
418
|
-
if (isFinite(elemZoom) && elemZoom > 0.1 && elemZoom < 10) {
|
|
419
|
-
zoomFactors.push({ method: 'testElement', value: elemZoom });
|
|
420
|
-
}
|
|
421
371
|
} catch (_) {}
|
|
422
|
-
|
|
423
|
-
// Выбираем наиболее подходящий результат
|
|
424
|
-
if (zoomFactors.length === 0) {
|
|
425
|
-
return 1.0; // Fallback
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Если есть visualViewport, предпочитаем его
|
|
429
|
-
const vpMethod = zoomFactors.find(f => f.method === 'visualViewport');
|
|
430
|
-
if (vpMethod) {
|
|
431
|
-
return vpMethod.value;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Иначе берем outerInner как наиболее стабильный
|
|
435
|
-
const outerMethod = zoomFactors.find(f => f.method === 'outerInner');
|
|
436
|
-
if (outerMethod) {
|
|
437
|
-
return outerMethod.value;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Последний fallback - первый доступный метод
|
|
441
|
-
return zoomFactors[0].value;
|
|
442
372
|
}
|
|
443
373
|
}
|
|
444
374
|
|