@sequent-org/moodboard 1.4.52 → 1.4.54

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.4.52",
3
+ "version": "1.4.54",
4
4
  "type": "module",
5
5
  "description": "Interactive moodboard",
6
6
  "main": "./src/index.js",
package/src/core/index.js CHANGED
@@ -39,7 +39,7 @@ export class CoreMoodBoard {
39
39
  autoSave: false,
40
40
  width: this.container.clientWidth || 800,
41
41
  height: this.container.clientHeight || 600,
42
- backgroundColor: 0xF5F5F5,
42
+ backgroundColor: 0xFFFFFF,
43
43
  ...options
44
44
  };
45
45
 
@@ -624,7 +624,7 @@ export class CoreMoodBoard {
624
624
  const app = this.pixi?.app;
625
625
  const rendererBg = app?.renderer?.background?.color ?? app?.renderer?.backgroundColor;
626
626
  const toHex = (num) => {
627
- try { return '#' + Number(num >>> 0).toString(16).padStart(6, '0'); } catch (_) { return '#F5F5F5'; }
627
+ try { return '#' + Number(num >>> 0).toString(16).padStart(6, '0'); } catch (_) { return '#FFFFFF'; }
628
628
  };
629
629
  const world = this.pixi?.worldLayer || app?.stage;
630
630
  const currentZoom = Math.max(0.02, Math.min(5, world?.scale?.x || 1));
@@ -653,7 +653,7 @@ export class CoreMoodBoard {
653
653
  })();
654
654
 
655
655
  const settings = {
656
- backgroundColor: toHex(rendererBg ?? 0xF5F5F5),
656
+ backgroundColor: toHex(rendererBg ?? 0xFFFFFF),
657
657
  grid: gridSettings || undefined,
658
658
  zoom: { min: 0.1, max: 5.0, default: 1.0, current: currentZoom },
659
659
  pan: currentPan,
@@ -20,6 +20,9 @@ export class BaseGrid {
20
20
  this.color = options.color;
21
21
  this.opacity = options.opacity;
22
22
  this.lineWidth = options.lineWidth;
23
+ // Эфемерный override цвета сетки (производный от фона доски, не сериализуется).
24
+ // null — используется штатный цвет/бэнды типа сетки.
25
+ this.colorOverride = null;
23
26
 
24
27
  // Размеры области отрисовки
25
28
  this.width = options.width || 1920;
@@ -169,6 +172,15 @@ export class BaseGrid {
169
172
  this.color = color;
170
173
  this.updateVisual();
171
174
  }
175
+
176
+ /**
177
+ * Устанавливает override-цвет сетки (производный от фона доски).
178
+ * @param {number|null} color — int-цвет либо null для сброса к штатному.
179
+ */
180
+ setColorOverride(color) {
181
+ this.colorOverride = (typeof color === 'number' && Number.isFinite(color)) ? color : null;
182
+ this.updateVisual();
183
+ }
172
184
 
173
185
  /**
174
186
  * Устанавливает прозрачность
@@ -38,7 +38,7 @@ export class CrossGrid extends BaseGrid {
38
38
  const g = this.graphics;
39
39
  // Строго без прозрачности.
40
40
  g.alpha = 1;
41
- const lineColor = getCrossColor(this._zoom, this.color);
41
+ const lineColor = this.colorOverride != null ? this.colorOverride : getCrossColor(this._zoom, this.color);
42
42
  g.beginFill(lineColor, 1);
43
43
 
44
44
  const checkpoint = getCrossCheckpointForZoom(this._zoom);
@@ -105,7 +105,7 @@ export class DotGrid extends BaseGrid {
105
105
  const zoomPct = checkpoint?.zoomPercent ?? 100;
106
106
  const use1px = DOT_1PX_CHECKPOINTS.has(zoomPct);
107
107
  const dotSize = use1px ? 0.4 : rawDotSize;
108
- const dotColor = getDotColor(this._zoom, this.color);
108
+ const dotColor = this.colorOverride != null ? this.colorOverride : getDotColor(this._zoom, this.color);
109
109
 
110
110
  this.graphics.beginFill(dotColor, alpha);
111
111
  for (let x = startX; x <= endX; x += stepPx) {
@@ -44,15 +44,21 @@ export class LineGrid extends BaseGrid {
44
44
  this.graphics.alpha = this.opacity;
45
45
  }
46
46
  const state = this.getScreenGridState();
47
+ // Miro-профиль: по умолчанию нативная пара цветов. Если из палитры пришёл colorOverride
48
+ // (например светлый фон #f0f6fc с gridColor #d4d4d4), используем его — иначе мелкая сетка
49
+ // сливается с фоном.
50
+ const mainColor = (typeof this.colorOverride === 'number' && Number.isFinite(this.colorOverride))
51
+ ? this._deriveMinorColor(this.colorOverride)
52
+ : this.color;
47
53
  try {
48
54
  this.graphics.lineStyle({
49
55
  width: Math.max(1, Math.round(this.lineWidth || 1)),
50
- color: this.color,
56
+ color: mainColor,
51
57
  alpha: 1,
52
58
  alignment: 0
53
59
  });
54
60
  } catch (_) {
55
- this.graphics.lineStyle(Math.max(1, Math.round(this.lineWidth || 1)), this.color, 1);
61
+ this.graphics.lineStyle(Math.max(1, Math.round(this.lineWidth || 1)), mainColor, 1);
56
62
  }
57
63
 
58
64
  // Основные линии сетки
@@ -160,15 +166,18 @@ export class LineGrid extends BaseGrid {
160
166
  const { secondGridStep } = this.getScreenGridState();
161
167
  const step = Math.max(1, Math.round(secondGridStep || 0));
162
168
  if (step <= 0) return;
169
+ const bigColor = (typeof this.colorOverride === 'number' && Number.isFinite(this.colorOverride))
170
+ ? this.colorOverride
171
+ : this.bigGridColor;
163
172
  try {
164
173
  this.graphics.lineStyle({
165
174
  width: 1,
166
- color: this.bigGridColor,
175
+ color: bigColor,
167
176
  alpha: this.bigGridOpacity,
168
177
  alignment: 0
169
178
  });
170
179
  } catch (_) {
171
- this.graphics.lineStyle(1, this.bigGridColor, this.bigGridOpacity);
180
+ this.graphics.lineStyle(1, bigColor, this.bigGridOpacity);
172
181
  }
173
182
  const b = this.getDrawBounds();
174
183
  const worldX = this.viewportTransform?.worldX || 0;
@@ -198,6 +207,18 @@ export class LineGrid extends BaseGrid {
198
207
  }
199
208
  }
200
209
 
210
+ /**
211
+ * Цвет мелкой сетки из цвета крупной: на 20 светлее на канал, чтобы крупная (override)
212
+ * читалась как заметный якорь, а мелкая была тоньше/бледнее. Для #d4d4d4 даёт #e8e8e8.
213
+ */
214
+ _deriveMinorColor(bigColor) {
215
+ const delta = 0x14;
216
+ const r = Math.min(0xFF, ((bigColor >> 16) & 0xFF) + delta);
217
+ const g = Math.min(0xFF, ((bigColor >> 8) & 0xFF) + delta);
218
+ const b = Math.min(0xFF, (bigColor & 0xFF) + delta);
219
+ return (r << 16) | (g << 8) | b;
220
+ }
221
+
201
222
  _normalizeAnchor(anchor, stepPx) {
202
223
  const step = Math.max(1, Math.round(stepPx));
203
224
  return ((Math.round(anchor) % step) + step) % step;
@@ -59,7 +59,7 @@ export function injectCriticalStyles() {
59
59
  width: 100%;
60
60
  height: 100%;
61
61
  overflow: hidden;
62
- background: #f0f6fc;
62
+ background: #ffffff;
63
63
  }
64
64
 
65
65
  .moodboard-workspace__toolbar {
@@ -1,7 +1,7 @@
1
1
  import { Events } from '../../core/events/Events.js';
2
2
 
3
3
  const BOARD_DEFAULTS = {
4
- backgroundColor: '#daeefb',
4
+ backgroundColor: '#ffffff',
5
5
  grid: { type: 'cross' },
6
6
  };
7
7
 
@@ -16,6 +16,20 @@ export class BoardService {
16
16
  this._destroyed = false;
17
17
  this._lastZoomCursor = null;
18
18
  this._consumeZoomCursorAnchor = false;
19
+ // Override цвета сетки, производный от фона доски (null — штатный цвет типа сетки).
20
+ this._gridColorOverride = null;
21
+ }
22
+
23
+ /**
24
+ * Устанавливает override цвета сетки от фона доски и применяет к текущей сетке.
25
+ * Сохраняется, чтобы переехать на новую сетку при смене её типа.
26
+ * @param {number|null} color
27
+ */
28
+ setGridColorOverride(color) {
29
+ this._gridColorOverride = (typeof color === 'number' && Number.isFinite(color)) ? color : null;
30
+ if (this.grid && typeof this.grid.setColorOverride === 'function') {
31
+ this.grid.setColorOverride(this._gridColorOverride);
32
+ }
19
33
  }
20
34
 
21
35
  async init(getCanvasSize) {
@@ -57,6 +71,9 @@ export class BoardService {
57
71
  };
58
72
  try {
59
73
  this.grid = GridFactory.createGrid(type, gridOptions);
74
+ if (this._gridColorOverride != null && typeof this.grid.setColorOverride === 'function') {
75
+ this.grid.setColorOverride(this._gridColorOverride);
76
+ }
60
77
  this.pixi.setGrid(this.grid);
61
78
  this.refreshGridViewport();
62
79
  this.eventBus.emit(Events.UI.GridCurrent, { type });
@@ -1,4 +1,5 @@
1
1
  import { Events } from '../core/events/Events.js';
2
+ import { BOARD_PALETTE } from '../ui/boardPalette.js';
2
3
 
3
4
  export class SettingsApplier {
4
5
  constructor(eventBus, pixi, boardService = null, uiRefs = {}) {
@@ -43,6 +44,12 @@ export class SettingsApplier {
43
44
  this.ui.topbar.setPaintButtonHex(this.ui.topbar.mapBoardToBtnHex(boardHex));
44
45
  } catch (_) {}
45
46
  }
47
+ // Цвет сетки производный от фона: для палитры с заданным gridColor — override, иначе сброс.
48
+ try {
49
+ if (this.boardService && typeof this.boardService.setGridColorOverride === 'function') {
50
+ this.boardService.setGridColorOverride(this._resolveGridColorOverride(bgInt));
51
+ }
52
+ } catch (_) {}
46
53
  }
47
54
 
48
55
  // 2) Сетка — применяем только если grid пришёл в partial
@@ -114,6 +121,21 @@ export class SettingsApplier {
114
121
  _toHex(intColor) {
115
122
  try { return `#${(intColor >>> 0).toString(16).padStart(6, '0')}`.toLowerCase(); } catch (_) { return '#f5f5f5'; }
116
123
  }
124
+
125
+ /**
126
+ * Возвращает int-цвет сетки для фона из палитры (если задан gridColor), иначе null.
127
+ * @param {number|null} bgInt
128
+ * @returns {number|null}
129
+ */
130
+ _resolveGridColorOverride(bgInt) {
131
+ if (bgInt == null) return null;
132
+ const hex = this._toHex(bgInt);
133
+ const entry = BOARD_PALETTE.find((p) => String(p.board).toLowerCase() === hex);
134
+ if (entry && entry.gridColor) {
135
+ return this._toIntColor(entry.gridColor);
136
+ }
137
+ return null;
138
+ }
117
139
  }
118
140
 
119
141
 
@@ -10,6 +10,7 @@ import {
10
10
  getSelectedConnectorId,
11
11
  getConnectorData,
12
12
  getConnectorMidpointScreen,
13
+ getConnectorTopScreen,
13
14
  getConnectorStyle,
14
15
  buildStyleUpdate,
15
16
  } from './connector-properties/ConnectorPropertiesPanelMapper.js';
@@ -62,18 +63,21 @@ export class ConnectorPropertiesPanel {
62
63
  const stillSelected = getSelectedConnectorId(this.core) === this.currentId;
63
64
  if (!stillSelected) { this.hide(); return; }
64
65
 
65
- const mid = getConnectorMidpointScreen(this.core, this.currentId);
66
- if (!mid) return;
66
+ const top = getConnectorTopScreen(this.core, this.currentId);
67
+ if (!top) return;
67
68
 
68
69
  const panelW = this.panel.offsetWidth || 480;
69
70
  const panelH = this.panel.offsetHeight || 40;
70
- const GAP = 18;
71
+ const GAP = 20;
71
72
 
72
- let px = mid.x - Math.round(panelW / 2);
73
- let py = mid.y - panelH - GAP;
73
+ let px = top.x - Math.round(panelW / 2);
74
+ let py = top.topY - panelH - GAP;
74
75
 
75
- // Если уходит вверх за контейнер — переносим ниже середины
76
- if (py < 0) py = mid.y + GAP;
76
+ // Если уходит вверх за контейнер — переносим ниже наивысшей точки
77
+ if (py < 0) {
78
+ const mid = getConnectorMidpointScreen(this.core, this.currentId);
79
+ py = (mid?.y ?? top.topY) + GAP;
80
+ }
77
81
 
78
82
  // Clamp по ширине контейнера
79
83
  const cw = this.container.offsetWidth || window.innerWidth;
package/src/ui/Topbar.js CHANGED
@@ -176,10 +176,18 @@ export class Topbar {
176
176
  _getCurrentBoardBackgroundHex() {
177
177
  try {
178
178
  const app = window?.moodboard?.coreMoodboard?.pixi?.app || window?.moodboard?.core?.pixi?.app;
179
- const colorInt = app?.renderer?.background?.color ?? app?.renderer?.backgroundColor;
179
+ const renderer = app?.renderer;
180
+ if (!renderer) return null;
181
+ const bg = renderer.background;
182
+ // PIXI v7+: bg.color — экземпляр Color с методом toHex()
183
+ if (bg?.color && typeof bg.color.toHex === 'function') {
184
+ return String(bg.color.toHex()).toLowerCase();
185
+ }
186
+ const colorInt = (typeof bg?.color === 'number')
187
+ ? bg.color
188
+ : (typeof renderer.backgroundColor === 'number' ? renderer.backgroundColor : null);
180
189
  if (typeof colorInt !== 'number') return null;
181
- const hex = `#${colorInt.toString(16).padStart(6, '0')}`;
182
- return hex.toLowerCase();
190
+ return `#${colorInt.toString(16).padStart(6, '0')}`.toLowerCase();
183
191
  } catch (_) { return null; }
184
192
  }
185
193
 
@@ -288,14 +296,20 @@ export class Topbar {
288
296
  // Выделяем активный цвет галочкой по фактическому boardHex
289
297
  try {
290
298
  const ap = window?.moodboard?.coreMoodboard?.settingsApplier;
291
- const boardHex = (
292
- (ap && ap.get && ap.get().backgroundColor) ||
293
- this._currentBoardHex ||
294
- this._getCurrentBoardBackgroundHex() ||
295
- ''
296
- ).toLowerCase();
299
+ const raw = (ap && ap.get && ap.get().backgroundColor)
300
+ || this._currentBoardHex
301
+ || this._getCurrentBoardBackgroundHex()
302
+ || '';
303
+ let boardHex = '';
304
+ if (typeof raw === 'number') {
305
+ boardHex = `#${raw.toString(16).padStart(6, '0')}`;
306
+ } else if (typeof raw === 'string') {
307
+ boardHex = raw;
308
+ }
309
+ boardHex = boardHex.toLowerCase();
297
310
  const match = boardHex ? grid.querySelector(`[data-board="${boardHex}"]`) : null;
298
- if (match) match.classList.add('is-active');
311
+ const toActivate = match || grid.querySelector('[data-board="#ffffff"]');
312
+ if (toActivate) toActivate.classList.add('is-active');
299
313
  } catch (_) {}
300
314
 
301
315
  // позиционируем поповер
@@ -4,7 +4,7 @@
4
4
  * btnHex — цвет кнопки в Topbar; board — реальный цвет фона canvas.
5
5
  */
6
6
  export const BOARD_PALETTE = [
7
- { id: 1, name: 'default-light', btnHex: '#d6e8f7', board: '#f0f6fc' },
7
+ { id: 1, name: 'default-light', btnHex: '#d6e8f7', board: '#f0f6fc', gridColor: '#d4d4d4' },
8
8
  { id: 2, name: 'mint-light', btnHex: '#E8F5E9', board: '#f8fff7' },
9
9
  { id: 3, name: 'peach-light', btnHex: '#FFF3E0', board: '#fffcf7' },
10
10
  { id: 4, name: 'gray-light', btnHex: '#f5f5f5', board: '#f5f5f5' },
@@ -119,6 +119,45 @@ export function getConnectorMidpointScreen(core, connectorId) {
119
119
  };
120
120
  }
121
121
 
122
+ /**
123
+ * Возвращает наивысшую экранную точку коннектора (минимальный y) и горизонтальный центр.
124
+ * Координаты — целые числа (screen-space integer contract).
125
+ * @returns {{ x: number, topY: number } | null}
126
+ */
127
+ export function getConnectorTopScreen(core, connectorId) {
128
+ const segments = core?.connectorLayer?._lastSegments;
129
+ if (!segments) return null;
130
+ const seg = segments.find(s => s.id === connectorId);
131
+ if (!seg) return null;
132
+
133
+ const worldLayer = core?.pixi?.worldLayer;
134
+ const scale = worldLayer?.scale?.x ?? 1;
135
+ const worldX = worldLayer?.x ?? 0;
136
+ const worldY = worldLayer?.y ?? 0;
137
+
138
+ let pts;
139
+ if (seg.points && seg.points.length >= 2) {
140
+ pts = seg.points;
141
+ } else if (seg.start && seg.end) {
142
+ pts = [seg.start, seg.end];
143
+ } else {
144
+ return null;
145
+ }
146
+
147
+ let minWorldY = Infinity;
148
+ let sumWorldX = 0;
149
+ for (const p of pts) {
150
+ if (p.y < minWorldY) minWorldY = p.y;
151
+ sumWorldX += p.x;
152
+ }
153
+ const avgWorldX = sumWorldX / pts.length;
154
+
155
+ return {
156
+ x: Math.round(avgWorldX * scale + worldX),
157
+ topY: Math.round(minWorldY * scale + worldY),
158
+ };
159
+ }
160
+
122
161
  /**
123
162
  * Формирует updates.properties.style для StateChanged-эмита.
124
163
  */
@@ -731,7 +731,7 @@
731
731
 
732
732
  /* Light Theme */
733
733
  .moodboard-workspace--light {
734
- background: #f8f9fa;
734
+ background: #ffffff;
735
735
  color: #333;
736
736
  }
737
737