@sequent-org/moodboard 1.2.77 → 1.2.79

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.77",
3
+ "version": "1.2.79",
4
4
  "type": "module",
5
5
  "description": "Interactive moodboard",
6
6
  "main": "./src/index.js",
package/src/core/index.js CHANGED
@@ -361,46 +361,82 @@ export class CoreMoodBoard {
361
361
  this._cursor.y = y;
362
362
  });
363
363
 
364
- // Вставка изображения из буфера обмена — по курсору, если он над холстом; иначе по центру видимой области
365
- this.eventBus.on(Events.UI.PasteImage, ({ src, name, imageId }) => {
366
- if (!src) return;
367
- const view = this.pixi.app.view;
368
- const world = this.pixi.worldLayer || this.pixi.app.stage;
369
- const s = world?.scale?.x || 1;
370
- const hasCursor = Number.isFinite(this._cursor.x) && Number.isFinite(this._cursor.y);
371
-
372
- let screenX, screenY;
373
- if (hasCursor) {
374
- // Используем позицию курсора
375
- screenX = this._cursor.x;
376
- screenY = this._cursor.y;
377
- } else {
378
- // Центр экрана
379
- screenX = view.clientWidth / 2;
380
- screenY = view.clientHeight / 2;
381
- }
382
-
383
- // Преобразуем экранные координаты в мировые (с учетом zoom и pan)
384
- const worldX = (screenX - (world?.x || 0)) / s;
385
- const worldY = (screenY - (world?.y || 0)) / s;
386
-
387
- // Центруем изображение относительно точки вставки
388
- const properties = { src, name, width: 300, height: 200 };
389
- const extraData = imageId ? { imageId } : {};
390
- this.createObject('image', { x: Math.round(worldX - 150), y: Math.round(worldY - 100) }, properties, extraData);
391
- });
392
-
393
- // Вставка изображения из буфера обмена по контекстному клику (координаты на экране)
394
- this.eventBus.on(Events.UI.PasteImageAt, ({ x, y, src, name, imageId }) => {
395
- if (!src) return;
396
- const world = this.pixi.worldLayer || this.pixi.app.stage;
397
- const s = world?.scale?.x || 1;
398
- const worldX = (x - (world?.x || 0)) / s;
399
- const worldY = (y - (world?.y || 0)) / s;
400
- const properties = { src, name, width: 300, height: 200 };
401
- const extraData = imageId ? { imageId } : {};
402
- this.createObject('image', { x: Math.round(worldX - 150), y: Math.round(worldY - 100) }, properties, extraData);
403
- });
364
+ // Вставка изображения из буфера обмена — по курсору, если он над холстом; иначе по центру видимой области
365
+ this.eventBus.on(Events.UI.PasteImage, ({ src, name, imageId }) => {
366
+ if (!src) return;
367
+ const view = this.pixi.app.view;
368
+ const world = this.pixi.worldLayer || this.pixi.app.stage;
369
+ const s = world?.scale?.x || 1;
370
+ const hasCursor = Number.isFinite(this._cursor.x) && Number.isFinite(this._cursor.y);
371
+
372
+ let screenX, screenY;
373
+ if (hasCursor) {
374
+ // Используем позицию курсора
375
+ screenX = this._cursor.x;
376
+ screenY = this._cursor.y;
377
+ } else {
378
+ // Центр экрана
379
+ screenX = view.clientWidth / 2;
380
+ screenY = view.clientHeight / 2;
381
+ }
382
+
383
+ // Преобразуем экранные координаты в мировые (с учетом zoom и pan)
384
+ const worldX = (screenX - (world?.x || 0)) / s;
385
+ const worldY = (screenY - (world?.y || 0)) / s;
386
+
387
+ const placeWithAspect = (natW, natH) => {
388
+ let w = 300, h = 200;
389
+ if (natW > 0 && natH > 0) {
390
+ const ar = natW / natH;
391
+ w = 300;
392
+ h = Math.max(1, Math.round(w / ar));
393
+ }
394
+ const properties = { src, name, width: w, height: h };
395
+ const extraData = imageId ? { imageId } : {};
396
+ this.createObject('image', { x: Math.round(worldX - Math.round(w / 2)), y: Math.round(worldY - Math.round(h / 2)) }, properties, extraData);
397
+ };
398
+
399
+ try {
400
+ const img = new Image();
401
+ img.decoding = 'async';
402
+ img.onload = () => placeWithAspect(img.naturalWidth || 0, img.naturalHeight || 0);
403
+ img.onerror = () => placeWithAspect(0, 0);
404
+ img.src = src;
405
+ } catch (_) {
406
+ placeWithAspect(0, 0);
407
+ }
408
+ });
409
+
410
+ // Вставка изображения из буфера обмена по контекстному клику (координаты на экране)
411
+ this.eventBus.on(Events.UI.PasteImageAt, ({ x, y, src, name, imageId }) => {
412
+ if (!src) return;
413
+ const world = this.pixi.worldLayer || this.pixi.app.stage;
414
+ const s = world?.scale?.x || 1;
415
+ const worldX = (x - (world?.x || 0)) / s;
416
+ const worldY = (y - (world?.y || 0)) / s;
417
+
418
+ const placeWithAspect = (natW, natH) => {
419
+ let w = 300, h = 200;
420
+ if (natW > 0 && natH > 0) {
421
+ const ar = natW / natH;
422
+ w = 300;
423
+ h = Math.max(1, Math.round(w / ar));
424
+ }
425
+ const properties = { src, name, width: w, height: h };
426
+ const extraData = imageId ? { imageId } : {};
427
+ this.createObject('image', { x: Math.round(worldX - Math.round(w / 2)), y: Math.round(worldY - Math.round(h / 2)) }, properties, extraData);
428
+ };
429
+
430
+ try {
431
+ const img = new Image();
432
+ img.decoding = 'async';
433
+ img.onload = () => placeWithAspect(img.naturalWidth || 0, img.naturalHeight || 0);
434
+ img.onerror = () => placeWithAspect(0, 0);
435
+ img.src = src;
436
+ } catch (_) {
437
+ placeWithAspect(0, 0);
438
+ }
439
+ });
404
440
 
405
441
  // Слойность: изменение порядка отрисовки (локальные операции)
406
442
  const applyZOrderFromState = () => {
@@ -30,6 +30,21 @@ export class HtmlHandlesLayer {
30
30
  this.layer.className = 'moodboard-html-handles';
31
31
  this.container.appendChild(this.layer);
32
32
 
33
+ // Обновление при изменении размеров окна/масштаба (DPR)
34
+ window.addEventListener('resize', () => this.update(), { passive: true });
35
+ // Некоторые браузеры меняют devicePixelRatio без resize — страхуемся
36
+ if (typeof window !== 'undefined' && 'matchMedia' in window) {
37
+ try {
38
+ // media-query, реагирующая на изменение DPR
39
+ const mq = window.matchMedia(`(resolution: ${window.devicePixelRatio || 1}dppx)`);
40
+ if (mq && mq.addEventListener) {
41
+ mq.addEventListener('change', () => this.update());
42
+ } else if (mq && mq.addListener) {
43
+ mq.addListener(() => this.update());
44
+ }
45
+ } catch (_) {}
46
+ }
47
+
33
48
  // Подписки: обновлять при изменениях выбора и трансформациях
34
49
  this.eventBus.on(Events.Tool.SelectionAdd, () => this.update());
35
50
  this.eventBus.on(Events.Tool.SelectionRemove, () => this.update());
@@ -185,8 +200,12 @@ export class HtmlHandlesLayer {
185
200
  _showBounds(worldBounds, id) {
186
201
  if (!this.layer) return;
187
202
  // Преобразуем world координаты в CSS-пиксели
188
- const res = (this.core.pixi.app.renderer?.resolution) || 1;
189
203
  const view = this.core.pixi.app.view;
204
+ // Динамически вычисляем фактическое соотношение пикселей canvas/CSS,
205
+ // чтобы корректно работать при зуме браузера
206
+ const res = (view && view.width && view.clientWidth)
207
+ ? (view.width / view.clientWidth)
208
+ : (this.core.pixi.app.renderer?.resolution || 1);
190
209
  const containerRect = this.container.getBoundingClientRect();
191
210
  const viewRect = view.getBoundingClientRect();
192
211
  const offsetLeft = viewRect.left - containerRect.left;
@@ -374,7 +393,10 @@ export class HtmlHandlesLayer {
374
393
  _toWorldScreenInverse(dx, dy) {
375
394
  const world = this.core.pixi.worldLayer || this.core.pixi.app.stage;
376
395
  const s = world?.scale?.x || 1;
377
- const res = (this.core.pixi.app.renderer?.resolution) || 1;
396
+ const view = this.core.pixi.app.view;
397
+ const res = (view && view.width && view.clientWidth)
398
+ ? (view.width / view.clientWidth)
399
+ : (this.core.pixi.app.renderer?.resolution || 1);
378
400
  return { dxWorld: (dx * res) / s, dyWorld: (dy * res) / s };
379
401
  }
380
402
 
@@ -387,8 +409,10 @@ export class HtmlHandlesLayer {
387
409
  const s = world?.scale?.x || 1;
388
410
  const tx = world?.x || 0;
389
411
  const ty = world?.y || 0;
390
- const res = (this.core.pixi.app.renderer?.resolution) || 1;
391
412
  const view = this.core.pixi.app.view;
413
+ const res = (view && view.width && view.clientWidth)
414
+ ? (view.width / view.clientWidth)
415
+ : (this.core.pixi.app.renderer?.resolution || 1);
392
416
  const containerRect = this.container.getBoundingClientRect();
393
417
  const viewRect = view.getBoundingClientRect();
394
418
  const offsetLeft = viewRect.left - containerRect.left;
@@ -664,8 +688,10 @@ export class HtmlHandlesLayer {
664
688
  const s = world?.scale?.x || 1;
665
689
  const tx = world?.x || 0;
666
690
  const ty = world?.y || 0;
667
- const res = (this.core.pixi.app.renderer?.resolution) || 1;
668
691
  const view = this.core.pixi.app.view;
692
+ const res = (view && view.width && view.clientWidth)
693
+ ? (view.width / view.clientWidth)
694
+ : (this.core.pixi.app.renderer?.resolution || 1);
669
695
  const containerRect = this.container.getBoundingClientRect();
670
696
  const viewRect = view.getBoundingClientRect();
671
697
  const offsetLeft = viewRect.left - containerRect.left;