@sequent-org/moodboard 1.2.57 → 1.2.61
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
|
@@ -17,46 +17,33 @@ export class DataManager {
|
|
|
17
17
|
// Очищаем доску перед загрузкой
|
|
18
18
|
this.clearBoard();
|
|
19
19
|
|
|
20
|
-
//
|
|
20
|
+
// Применяем settings централизованно (если есть апплаер), иначе — старым способом
|
|
21
21
|
try {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
if (this.coreMoodboard?.settingsApplier && data.settings) {
|
|
23
|
+
this.coreMoodboard.settingsApplier.apply(data.settings);
|
|
24
|
+
} else {
|
|
25
|
+
// fallback оставлен для совместимости (если где-то аплеер не создан)
|
|
26
|
+
const grid = (data.settings && data.settings.grid) || data.grid || (data.board && data.board.grid);
|
|
27
|
+
if (grid && grid.type) {
|
|
28
|
+
const payload = { type: grid.type };
|
|
29
|
+
const opts = grid.options || null;
|
|
30
|
+
if (opts && typeof opts === 'object') payload.options = opts;
|
|
31
|
+
this.coreMoodboard.eventBus.emit(this.coreMoodboard.Events?.UI?.GridChange || 'ui:grid:change', payload);
|
|
32
|
+
}
|
|
33
|
+
const settings = data.settings || {};
|
|
34
|
+
const bg = settings.backgroundColor;
|
|
35
|
+
if (bg && this.coreMoodboard?.pixi?.app?.renderer) {
|
|
36
|
+
const s = typeof bg === 'string' ? (bg.startsWith('#') ? bg.slice(1) : bg) : null;
|
|
37
|
+
const bgInt = typeof bg === 'number' ? bg : (s ? parseInt(s, 16) : null);
|
|
38
|
+
if (bgInt != null) this.coreMoodboard.pixi.app.renderer.backgroundColor = bgInt;
|
|
39
|
+
}
|
|
40
|
+
const z = settings.zoom && (settings.zoom.current || settings.zoom.default);
|
|
41
|
+
const world = this.coreMoodboard?.pixi?.worldLayer || this.coreMoodboard?.pixi?.app?.stage;
|
|
42
|
+
if (world && Number.isFinite(z) && z > 0) {
|
|
43
|
+
world.scale.set(z);
|
|
44
|
+
const percent = Math.round(z * 100);
|
|
45
|
+
try { this.coreMoodboard.eventBus.emit('ui:zoom:percent', { percentage: percent }); } catch (_) {}
|
|
28
46
|
}
|
|
29
|
-
this.coreMoodboard.eventBus.emit(this.coreMoodboard.Events?.UI?.GridChange || 'ui:grid:change', payload);
|
|
30
|
-
}
|
|
31
|
-
} catch (_) {}
|
|
32
|
-
|
|
33
|
-
// Восстановим фон и зум из settings, если есть
|
|
34
|
-
try {
|
|
35
|
-
const settings = data.settings || {};
|
|
36
|
-
// Фон
|
|
37
|
-
const bg = settings.backgroundColor;
|
|
38
|
-
if (bg && this.coreMoodboard?.pixi?.app?.renderer) {
|
|
39
|
-
const parseHex = (v) => {
|
|
40
|
-
if (typeof v === 'number') return v;
|
|
41
|
-
if (typeof v === 'string') {
|
|
42
|
-
const s = v.startsWith('#') ? v.slice(1) : v;
|
|
43
|
-
const n = parseInt(s, 16);
|
|
44
|
-
if (Number.isFinite(n)) return n;
|
|
45
|
-
}
|
|
46
|
-
return null;
|
|
47
|
-
};
|
|
48
|
-
const bgInt = parseHex(bg);
|
|
49
|
-
if (bgInt != null) this.coreMoodboard.pixi.app.renderer.backgroundColor = bgInt;
|
|
50
|
-
}
|
|
51
|
-
// Зум
|
|
52
|
-
const z = settings.zoom && (settings.zoom.current || settings.zoom.default);
|
|
53
|
-
const world = this.coreMoodboard?.pixi?.worldLayer || this.coreMoodboard?.pixi?.app?.stage;
|
|
54
|
-
if (world && Number.isFinite(z) && z > 0) {
|
|
55
|
-
world.scale.set(z);
|
|
56
|
-
const percent = Math.round(z * 100);
|
|
57
|
-
try {
|
|
58
|
-
this.coreMoodboard.eventBus.emit('ui:zoom:percent', { percentage: percent });
|
|
59
|
-
} catch (_) {}
|
|
60
47
|
}
|
|
61
48
|
} catch (_) {}
|
|
62
49
|
|
|
@@ -18,6 +18,7 @@ import { NotePropertiesPanel } from '../ui/NotePropertiesPanel.js';
|
|
|
18
18
|
import { FilePropertiesPanel } from '../ui/FilePropertiesPanel.js';
|
|
19
19
|
import { AlignmentGuides } from '../tools/AlignmentGuides.js';
|
|
20
20
|
import { ImageUploadService } from '../services/ImageUploadService.js';
|
|
21
|
+
import { SettingsApplier } from '../services/SettingsApplier.js';
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Готовый MoodBoard с UI - главный класс пакета
|
|
@@ -84,6 +85,15 @@ export class MoodBoard {
|
|
|
84
85
|
// Инициализируем CoreMoodBoard
|
|
85
86
|
await this.initCoreMoodBoard();
|
|
86
87
|
|
|
88
|
+
// Настройки (единая точка применения)
|
|
89
|
+
this.settingsApplier = new SettingsApplier(
|
|
90
|
+
this.coreMoodboard.eventBus,
|
|
91
|
+
this.coreMoodboard.pixi,
|
|
92
|
+
this.coreMoodboard.boardService || null
|
|
93
|
+
);
|
|
94
|
+
// Делаем доступным для других подсистем
|
|
95
|
+
this.coreMoodboard.settingsApplier = this.settingsApplier;
|
|
96
|
+
|
|
87
97
|
// Создаем менеджеры
|
|
88
98
|
this.dataManager = new DataManager(this.coreMoodboard);
|
|
89
99
|
this.actionHandler = new ActionHandler(this.dataManager, this.workspaceManager);
|
|
@@ -131,6 +141,10 @@ export class MoodBoard {
|
|
|
131
141
|
|
|
132
142
|
// Предоставляем доступ к сервису через core
|
|
133
143
|
this.coreMoodboard.imageUploadService = this.imageUploadService;
|
|
144
|
+
// Передаем ссылку на topbar в апплаер настроек для синхронизации UI
|
|
145
|
+
if (this.settingsApplier && this.topbar) {
|
|
146
|
+
this.settingsApplier.setUI({ topbar: this.topbar });
|
|
147
|
+
}
|
|
134
148
|
|
|
135
149
|
// Настраиваем коллбеки событий
|
|
136
150
|
this.setupEventCallbacks();
|
|
@@ -223,10 +237,6 @@ export class MoodBoard {
|
|
|
223
237
|
: color;
|
|
224
238
|
if (this.coreMoodboard?.pixi?.app?.renderer) {
|
|
225
239
|
this.coreMoodboard.pixi.app.renderer.backgroundColor = hex;
|
|
226
|
-
// Сигнализируем, что настройки доски изменились (для автосохранения)
|
|
227
|
-
try {
|
|
228
|
-
this.coreMoodboard.eventBus.emit(Events.Grid.BoardDataChanged, { settings: { backgroundColor: color } });
|
|
229
|
-
} catch (_) {}
|
|
230
240
|
}
|
|
231
241
|
});
|
|
232
242
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Events } from '../core/events/Events.js';
|
|
2
|
+
|
|
3
|
+
export class SettingsApplier {
|
|
4
|
+
constructor(eventBus, pixi, boardService = null, uiRefs = {}) {
|
|
5
|
+
this.eventBus = eventBus;
|
|
6
|
+
this.pixi = pixi;
|
|
7
|
+
this.boardService = boardService;
|
|
8
|
+
this.ui = uiRefs || {};
|
|
9
|
+
this.settings = {};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
setUI(uiRefs = {}) {
|
|
13
|
+
this.ui = { ...this.ui, ...uiRefs };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
set(partial) {
|
|
17
|
+
this.settings = { ...this.settings, ...(partial || {}) };
|
|
18
|
+
this.apply(this.settings);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get() {
|
|
22
|
+
return { ...this.settings };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
apply(settings = {}) {
|
|
26
|
+
this.settings = { ...this.settings, ...settings };
|
|
27
|
+
const s = this.settings;
|
|
28
|
+
|
|
29
|
+
// 1) Фон
|
|
30
|
+
if (s.backgroundColor && this.pixi?.app?.renderer) {
|
|
31
|
+
const bgInt = this._toIntColor(s.backgroundColor);
|
|
32
|
+
if (bgInt != null) this.pixi.app.renderer.backgroundColor = bgInt;
|
|
33
|
+
// Синхронизация UI цвета (если доступен topbar)
|
|
34
|
+
if (this.ui.topbar) {
|
|
35
|
+
try {
|
|
36
|
+
const boardHex = this._toHex(bgInt);
|
|
37
|
+
this.ui.topbar.setCurrentBoardHex(boardHex);
|
|
38
|
+
} catch (_) {}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 2) Сетка
|
|
43
|
+
if (s.grid && s.grid.type) {
|
|
44
|
+
try {
|
|
45
|
+
const payload = { type: s.grid.type };
|
|
46
|
+
// Пробрасываем дополнительные опции, если есть
|
|
47
|
+
const options = s.grid.options || {
|
|
48
|
+
size: s.grid.size,
|
|
49
|
+
color: this._maybeInt(s.grid.color),
|
|
50
|
+
enabled: s.grid.visible !== false
|
|
51
|
+
};
|
|
52
|
+
if (options) payload.options = options;
|
|
53
|
+
this.eventBus.emit(Events.UI.GridChange, payload);
|
|
54
|
+
if (this.ui.topbar && s.grid.type) {
|
|
55
|
+
this.ui.topbar.setActive(s.grid.type);
|
|
56
|
+
}
|
|
57
|
+
} catch (_) {}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 3) Зум
|
|
61
|
+
if (s.zoom && (typeof s.zoom.current === 'number')) {
|
|
62
|
+
const world = this.pixi?.worldLayer || this.pixi?.app?.stage;
|
|
63
|
+
if (world) {
|
|
64
|
+
const z = Math.max(0.1, Math.min(5, s.zoom.current));
|
|
65
|
+
world.scale.set(z);
|
|
66
|
+
try { this.eventBus.emit(Events.UI.ZoomPercent, { percentage: Math.round(z * 100) }); } catch (_) {}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_toIntColor(hexOrInt) {
|
|
72
|
+
if (typeof hexOrInt === 'number') return hexOrInt;
|
|
73
|
+
if (typeof hexOrInt === 'string') {
|
|
74
|
+
const s = hexOrInt.startsWith('#') ? hexOrInt.slice(1) : hexOrInt;
|
|
75
|
+
const n = parseInt(s, 16);
|
|
76
|
+
return Number.isFinite(n) ? n : null;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
_maybeInt(v) {
|
|
82
|
+
if (v == null) return v;
|
|
83
|
+
if (typeof v === 'number') return v;
|
|
84
|
+
if (typeof v === 'string') {
|
|
85
|
+
const s = v.startsWith('#') ? v.slice(1) : v;
|
|
86
|
+
const n = parseInt(s, 16);
|
|
87
|
+
return Number.isFinite(n) ? n : v;
|
|
88
|
+
}
|
|
89
|
+
return v;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
_toHex(intColor) {
|
|
93
|
+
try { return `#${(intColor >>> 0).toString(16).padStart(6, '0')}`.toLowerCase(); } catch (_) { return '#f5f5f5'; }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
@@ -32,12 +32,7 @@ export class ZoomPanController {
|
|
|
32
32
|
world.scale.set(newScale);
|
|
33
33
|
world.x = x - worldX * newScale;
|
|
34
34
|
world.y = y - worldY * newScale;
|
|
35
|
-
|
|
36
|
-
this.eventBus.emit(Events.UI.ZoomPercent, { percentage: pct });
|
|
37
|
-
// Сигнализируем об изменении настроек (чтобы сохранить текущий зум)
|
|
38
|
-
try {
|
|
39
|
-
this.eventBus.emit(Events.Grid.BoardDataChanged, { settings: { zoom: { current: newScale, percentage: pct } } });
|
|
40
|
-
} catch (_) {}
|
|
35
|
+
this.eventBus.emit(Events.UI.ZoomPercent, { percentage: Math.round(newScale * 100) });
|
|
41
36
|
});
|
|
42
37
|
|
|
43
38
|
// Кнопки зума из UI
|
package/src/ui/Topbar.js
CHANGED
|
@@ -11,7 +11,6 @@ export class Topbar {
|
|
|
11
11
|
this.theme = theme;
|
|
12
12
|
this.element = null;
|
|
13
13
|
this._paintPopover = null;
|
|
14
|
-
this._currentBoardHex = null; // фактический цвет фона доски (#rrggbb)
|
|
15
14
|
// Загружаем иконки
|
|
16
15
|
this.iconLoader = new TopbarIconLoader();
|
|
17
16
|
this.icons = this.iconLoader.icons;
|
|
@@ -37,37 +36,26 @@ export class Topbar {
|
|
|
37
36
|
|
|
38
37
|
this.createTopbar();
|
|
39
38
|
this.attachEvents();
|
|
39
|
+
// Активируем дефолтную кнопку (line) до прихода события из ядра
|
|
40
|
+
this.setActive('line');
|
|
40
41
|
|
|
41
42
|
// Синхронизация активного состояния по событию из ядра
|
|
42
43
|
this.eventBus.on(Events.UI.GridCurrent, ({ type }) => {
|
|
43
44
|
this.setActive(type);
|
|
44
45
|
});
|
|
45
46
|
|
|
46
|
-
// Обновляем цвет кнопки "краски"
|
|
47
|
-
this.eventBus.on(Events.UI.PaintPick, ({ btnHex
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (!v) return null;
|
|
51
|
-
if (typeof v === 'number') {
|
|
52
|
-
return `#${v.toString(16).padStart(6,'0')}`.toLowerCase();
|
|
53
|
-
}
|
|
54
|
-
if (typeof v === 'string') {
|
|
55
|
-
const s = v.startsWith('#') ? v : `#${v}`;
|
|
56
|
-
return s.toLowerCase();
|
|
57
|
-
}
|
|
58
|
-
return null;
|
|
59
|
-
};
|
|
60
|
-
const boardHex = normalizeHex(color);
|
|
61
|
-
if (boardHex) this._currentBoardHex = boardHex;
|
|
62
|
-
const mapped = btnHex || this.mapBoardToBtnHex(boardHex);
|
|
63
|
-
if (mapped) this.setPaintButtonHex(mapped);
|
|
47
|
+
// Обновляем цвет кнопки "краски" при выборе цвета фона
|
|
48
|
+
this.eventBus.on(Events.UI.PaintPick, ({ btnHex }) => {
|
|
49
|
+
if (!btnHex) return;
|
|
50
|
+
this.setPaintButtonHex(btnHex);
|
|
64
51
|
});
|
|
65
52
|
|
|
66
53
|
// Инициализация цвета кнопки "краска" из текущего фона доски, если доступен
|
|
67
54
|
try {
|
|
68
55
|
const bgHex = this._getCurrentBoardBackgroundHex();
|
|
69
56
|
if (bgHex) {
|
|
70
|
-
this.
|
|
57
|
+
const mapped = this.mapBoardToBtnHex(bgHex);
|
|
58
|
+
this.setPaintButtonHex(mapped);
|
|
71
59
|
}
|
|
72
60
|
} catch (_) {}
|
|
73
61
|
}
|
|
@@ -190,8 +178,7 @@ export class Topbar {
|
|
|
190
178
|
/** Возвращает текущий цвет фона канваса как hex-строку #RRGGBB */
|
|
191
179
|
_getCurrentBoardBackgroundHex() {
|
|
192
180
|
try {
|
|
193
|
-
|
|
194
|
-
const app = window?.moodboard?.coreMoodboard?.pixi?.app || window?.moodboard?.core?.pixi?.app;
|
|
181
|
+
const app = window?.moodboard?.core?.pixi?.app;
|
|
195
182
|
const colorInt = app?.renderer?.background?.color ?? app?.renderer?.backgroundColor;
|
|
196
183
|
if (typeof colorInt !== 'number') return null;
|
|
197
184
|
const hex = `#${colorInt.toString(16).padStart(6, '0')}`;
|
|
@@ -216,15 +203,6 @@ export class Topbar {
|
|
|
216
203
|
}
|
|
217
204
|
}
|
|
218
205
|
|
|
219
|
-
/** Задать текущий фактический цвет фона и синхронизировать кнопку */
|
|
220
|
-
setCurrentBoardHex(boardHex) {
|
|
221
|
-
if (!boardHex) return;
|
|
222
|
-
const v = String(boardHex).toLowerCase();
|
|
223
|
-
this._currentBoardHex = v.startsWith('#') ? v : `#${v}`;
|
|
224
|
-
const mapped = this.mapBoardToBtnHex(this._currentBoardHex) || this._currentBoardHex;
|
|
225
|
-
this.setPaintButtonHex(mapped);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
206
|
setActive(type) {
|
|
229
207
|
const buttons = this.element.querySelectorAll('.moodboard-topbar__button');
|
|
230
208
|
buttons.forEach((b) => b.classList.remove('moodboard-topbar__button--active'));
|
|
@@ -277,9 +255,8 @@ export class Topbar {
|
|
|
277
255
|
b.style.borderColor = darken(c.hex, 0.35);
|
|
278
256
|
// Цвет галочки — чёрный для максимальной видимости
|
|
279
257
|
b.style.color = '#111';
|
|
280
|
-
// Для надёжного сравнения — сохраняем
|
|
281
|
-
b.dataset.hex = String(c.hex).toLowerCase();
|
|
282
|
-
b.dataset.board = String(c.board).toLowerCase(); // фактический цвет фона доски
|
|
258
|
+
// Для надёжного сравнения — сохраняем исходный hex в dataset
|
|
259
|
+
b.dataset.hex = String(c.hex).toLowerCase();
|
|
283
260
|
// Галочка по центру
|
|
284
261
|
const tick = document.createElement('i');
|
|
285
262
|
tick.className = 'moodboard-topbar__paint-tick';
|
|
@@ -302,37 +279,11 @@ export class Topbar {
|
|
|
302
279
|
});
|
|
303
280
|
pop.appendChild(grid);
|
|
304
281
|
|
|
305
|
-
// Выделяем активный цвет галочкой
|
|
282
|
+
// Выделяем активный цвет галочкой
|
|
306
283
|
try {
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
let match = currentBoardHex ? grid.querySelector(`[data-board="${currentBoardHex}"]`) : null;
|
|
311
|
-
// Если точного совпадения нет — выбираем ближайший по евклидовой дистанции в RGB
|
|
312
|
-
if (!match) {
|
|
313
|
-
const hexToRgb = (hex) => {
|
|
314
|
-
try {
|
|
315
|
-
const h = hex.replace('#','');
|
|
316
|
-
return {
|
|
317
|
-
r: parseInt(h.substring(0,2), 16),
|
|
318
|
-
g: parseInt(h.substring(2,4), 16),
|
|
319
|
-
b: parseInt(h.substring(4,6), 16)
|
|
320
|
-
};
|
|
321
|
-
} catch (_) { return { r: 0, g: 0, b: 0 }; }
|
|
322
|
-
};
|
|
323
|
-
const dist2 = (a, b) => {
|
|
324
|
-
const dr = a.r - b.r, dg = a.g - b.g, db = a.b - b.b;
|
|
325
|
-
return dr*dr + dg*dg + db*db;
|
|
326
|
-
};
|
|
327
|
-
const target = hexToRgb(currentBoardHex || '#f7fbff');
|
|
328
|
-
let best = null, bestD = Infinity;
|
|
329
|
-
Array.from(grid.children).forEach((el) => {
|
|
330
|
-
const boardHex = el.dataset.board || '';
|
|
331
|
-
const d = dist2(target, hexToRgb(boardHex));
|
|
332
|
-
if (d < bestD) { bestD = d; best = el; }
|
|
333
|
-
});
|
|
334
|
-
match = best;
|
|
335
|
-
}
|
|
284
|
+
const boardHex = this._getCurrentBoardBackgroundHex();
|
|
285
|
+
const targetHex = (this.mapBoardToBtnHex(boardHex) || '#B3E5FC').toLowerCase();
|
|
286
|
+
const match = grid.querySelector(`[data-hex="${targetHex}"]`);
|
|
336
287
|
if (match) match.classList.add('is-active');
|
|
337
288
|
} catch (_) {}
|
|
338
289
|
|
|
@@ -342,7 +293,16 @@ export class Topbar {
|
|
|
342
293
|
pop.style.left = `${rect.left - this.element.getBoundingClientRect().left}px`;
|
|
343
294
|
pop.style.top = `${rect.bottom - this.element.getBoundingClientRect().top + 6}px`;
|
|
344
295
|
|
|
345
|
-
//
|
|
296
|
+
// Подсветить текущий активный кружок (с галочкой)
|
|
297
|
+
try {
|
|
298
|
+
const currentBtnHex = (this._paintBtn && getComputedStyle(this._paintBtn).getPropertyValue('--paint-btn-color')) || '';
|
|
299
|
+
const normalized = currentBtnHex.trim() || '#B3E5FC';
|
|
300
|
+
const match = Array.from(grid.children).find((el) => {
|
|
301
|
+
const bg = el && el.style && el.style.background ? el.style.background.toLowerCase() : '';
|
|
302
|
+
return bg === normalized.toLowerCase();
|
|
303
|
+
});
|
|
304
|
+
if (match) match.classList.add('is-active');
|
|
305
|
+
} catch (_) {}
|
|
346
306
|
|
|
347
307
|
this.element.appendChild(pop);
|
|
348
308
|
this._paintPopover = pop;
|