@sequent-org/moodboard 1.2.1 → 1.2.2

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.1",
3
+ "version": "1.2.2",
4
4
  "type": "module",
5
5
  "description": "Interactive moodboard",
6
6
  "main": "./src/index.js",
@@ -31,6 +31,7 @@ export async function initMoodBoardNoBundler(container, options = {}, basePath =
31
31
  ...options,
32
32
  emojiLoader: emojiLoader,
33
33
  emojiGroups: emojiGroups,
34
+ emojiBasePath: basePath ? `${basePath}src/assets/emodji/` : null,
34
35
  noBundler: true
35
36
  };
36
37
 
@@ -138,7 +139,8 @@ export function quickInitMoodBoard(container, options = {}, basePath = '') {
138
139
  const moodboard = new MoodBoard(container, {
139
140
  ...options,
140
141
  noBundler: true,
141
- skipEmojiLoader: true // Пропускаем автозагрузку эмоджи
142
+ skipEmojiLoader: true, // Пропускаем автозагрузку эмоджи
143
+ emojiBasePath: basePath ? `${basePath}src/assets/emodji/` : null
142
144
  });
143
145
 
144
146
  return moodboard;
@@ -159,7 +159,10 @@ export class MoodBoard {
159
159
  this.toolbar = new Toolbar(
160
160
  this.toolbarContainer,
161
161
  this.coreMoodboard.eventBus,
162
- this.options.theme
162
+ this.options.theme,
163
+ {
164
+ emojiBasePath: this.options.emojiBasePath || null
165
+ }
163
166
  );
164
167
 
165
168
  // Добавляем функцию для отладки иконок в window
@@ -103,9 +103,16 @@ export class PlacementTool extends BaseTool {
103
103
 
104
104
  // Обработка выбора файла
105
105
  this.eventBus.on(Events.Place.FileSelected, (fileData) => {
106
+ console.log('📁 PlacementTool: получен FileSelected:', fileData);
106
107
  this.selectedFile = fileData;
107
108
  this.selectedImage = null;
108
- this.showFileGhost();
109
+
110
+ // Если PlacementTool уже активен - показываем призрак сразу
111
+ if (this.world) {
112
+ this.showFileGhost();
113
+ } else {
114
+ console.log('📁 PlacementTool: world не готов, призрак будет показан при активации');
115
+ }
109
116
  });
110
117
 
111
118
  // Обработка отмены выбора файла
@@ -547,7 +554,16 @@ export class PlacementTool extends BaseTool {
547
554
  * Показать "призрак" файла
548
555
  */
549
556
  showFileGhost() {
550
- if (!this.selectedFile || !this.world) return;
557
+ console.log('📁 PlacementTool.showFileGhost:', {
558
+ hasSelectedFile: !!this.selectedFile,
559
+ hasWorld: !!this.world,
560
+ selectedFileData: this.selectedFile
561
+ });
562
+
563
+ if (!this.selectedFile || !this.world) {
564
+ console.warn('⚠️ Не можем показать призрак файла - нет selectedFile или world');
565
+ return;
566
+ }
551
567
 
552
568
  this.hideGhost(); // Сначала убираем старый призрак
553
569
 
@@ -562,9 +578,10 @@ export class PlacementTool extends BaseTool {
562
578
  const worldPoint = this._toWorld(cursorX, cursorY);
563
579
  this.updateGhostPosition(worldPoint.x, worldPoint.y);
564
580
  }
565
- // Попробуем дождаться загрузки веб-шрифта Caveat до отрисовки
566
- const pendingFont = (this.pending.properties?.fontFamily) || 'Caveat, Arial, cursive';
567
- const primaryFont = String(pendingFont).split(',')[0].trim().replace(/^['"]|['"]$/g, '') || 'Caveat';
581
+ // Попробуем дождаться загрузки веб-шрифта Caveat до отрисовки
582
+ // Для файлов используем selectedFile, а не pending
583
+ const fileFont = (this.selectedFile.properties?.fontFamily) || 'Caveat, Arial, cursive';
584
+ const primaryFont = String(fileFont).split(',')[0].trim().replace(/^['"]|['"]$/g, '') || 'Caveat';
568
585
 
569
586
  // Размеры
570
587
  const width = this.selectedFile.properties.width || 120;
@@ -622,6 +639,13 @@ export class PlacementTool extends BaseTool {
622
639
  this.ghostContainer.pivot.y = height / 2;
623
640
 
624
641
  this.world.addChild(this.ghostContainer);
642
+
643
+ console.log('📁 Призрак файла создан и добавлен в world:', {
644
+ ghostContainerSize: { w: width, h: height },
645
+ ghostContainerAlpha: this.ghostContainer.alpha,
646
+ worldHasContainer: this.world.children.includes(this.ghostContainer),
647
+ ghostContainerChildren: this.ghostContainer.children.length
648
+ });
625
649
  }
626
650
 
627
651
  /**
@@ -722,9 +746,10 @@ export class PlacementTool extends BaseTool {
722
746
  this.ghostContainer = new PIXI.Container();
723
747
  this.ghostContainer.alpha = 0.6; // Полупрозрачность
724
748
 
725
- // Размеры призрака
726
- const maxWidth = this.selectedImage.properties.width || 300;
727
- const maxHeight = this.selectedImage.properties.height || 200;
749
+ // Размеры призрака - используем размеры из pending/selected, если есть
750
+ const isEmojiIcon = this.selectedImage.properties?.isEmojiIcon;
751
+ const maxWidth = this.selectedImage.properties.width || (isEmojiIcon ? 64 : 300);
752
+ const maxHeight = this.selectedImage.properties.height || (isEmojiIcon ? 64 : 200);
728
753
 
729
754
  try {
730
755
  // Создаем превью изображения
@@ -816,8 +841,10 @@ export class PlacementTool extends BaseTool {
816
841
  this.ghostContainer = new PIXI.Container();
817
842
  this.ghostContainer.alpha = 0.6;
818
843
 
819
- const maxWidth = this.pending.size?.width || this.pending.properties?.width || 56;
820
- const maxHeight = this.pending.size?.height || this.pending.properties?.height || 56;
844
+ // Для эмоджи используем точные размеры из pending для согласованности
845
+ const isEmojiIcon = this.pending.properties?.isEmojiIcon;
846
+ const maxWidth = this.pending.size?.width || this.pending.properties?.width || (isEmojiIcon ? 64 : 56);
847
+ const maxHeight = this.pending.size?.height || this.pending.properties?.height || (isEmojiIcon ? 64 : 56);
821
848
 
822
849
  try {
823
850
  const texture = await PIXI.Texture.fromURL(src);
@@ -853,15 +880,23 @@ export class PlacementTool extends BaseTool {
853
880
 
854
881
  this.world.addChild(this.ghostContainer);
855
882
 
856
- // Кастомный курсор: мини-превью иконки рядом с курсором
857
- try {
858
- if (this.app && this.app.view && src) {
859
- const cursorSize = 24;
860
- const url = encodeURI(src);
861
- // Используем CSS cursor с изображением, если поддерживается
862
- this.app.view.style.cursor = `url(${url}) ${Math.floor(cursorSize/2)} ${Math.floor(cursorSize/2)}, default`;
883
+ // Для эмоджи не используем кастомный курсор, чтобы избежать дублирования призраков
884
+ if (!isEmojiIcon) {
885
+ // Кастомный курсор только для обычных изображений
886
+ try {
887
+ if (this.app && this.app.view && src) {
888
+ const cursorSize = 24;
889
+ const url = encodeURI(src);
890
+ // Используем CSS cursor с изображением, если поддерживается
891
+ this.app.view.style.cursor = `url(${url}) ${Math.floor(cursorSize/2)} ${Math.floor(cursorSize/2)}, default`;
892
+ }
893
+ } catch (_) {}
894
+ } else {
895
+ // Для эмоджи используем стандартный курсор
896
+ if (this.app && this.app.view) {
897
+ this.app.view.style.cursor = 'crosshair';
863
898
  }
864
- } catch (_) {}
899
+ }
865
900
  }
866
901
 
867
902
  /**
package/src/ui/Toolbar.js CHANGED
@@ -5,11 +5,14 @@ import { Events } from '../core/events/Events.js';
5
5
  import { IconLoader } from '../utils/iconLoader.js';
6
6
 
7
7
  export class Toolbar {
8
- constructor(container, eventBus, theme = 'light') {
8
+ constructor(container, eventBus, theme = 'light', options = {}) {
9
9
  this.container = container;
10
10
  this.eventBus = eventBus;
11
11
  this.theme = theme;
12
12
 
13
+ // Базовый путь для ассетов (эмоджи и другие ресурсы)
14
+ this.emojiBasePath = options.emojiBasePath || null;
15
+
13
16
  // Инициализируем IconLoader
14
17
  this.iconLoader = new IconLoader();
15
18
 
@@ -966,9 +969,13 @@ export class Toolbar {
966
969
 
967
970
  // Перетаскивание: начинаем только если был реальный drag (движение > 4px)
968
971
  btn.addEventListener('mousedown', (e) => {
972
+ // Блокируем одновременную обработку
973
+ if (btn.__clickProcessing || btn.__dragActive) return;
974
+
969
975
  const startX = e.clientX;
970
976
  const startY = e.clientY;
971
977
  let startedDrag = false;
978
+
972
979
  const onMove = (ev) => {
973
980
  if (startedDrag) return;
974
981
  const dx = Math.abs(ev.clientX - startX);
@@ -976,6 +983,10 @@ export class Toolbar {
976
983
  if (dx > 4 || dy > 4) {
977
984
  startedDrag = true;
978
985
  btn.__dragActive = true;
986
+
987
+ // Блокируем click handler
988
+ btn.__clickProcessing = true;
989
+
979
990
  const target = 64;
980
991
  const targetW = target;
981
992
  const targetH = target;
@@ -994,8 +1005,11 @@ export class Toolbar {
994
1005
  };
995
1006
  const onUp = () => {
996
1007
  cleanup();
997
- // Снимем флаг сразу после клика, чтобы click мог отфильтроваться
998
- setTimeout(() => { btn.__dragActive = false; }, 0);
1008
+ // Снимаем флаги с задержкой
1009
+ setTimeout(() => {
1010
+ btn.__dragActive = false;
1011
+ btn.__clickProcessing = false;
1012
+ }, 50);
999
1013
  };
1000
1014
  const cleanup = () => {
1001
1015
  document.removeEventListener('mousemove', onMove);
@@ -1005,8 +1019,13 @@ export class Toolbar {
1005
1019
  document.addEventListener('mouseup', onUp, { once: true });
1006
1020
  });
1007
1021
 
1008
- btn.addEventListener('click', () => {
1009
- if (btn.__dragActive) return; // не обрабатываем клик после drag
1022
+ btn.addEventListener('click', (e) => {
1023
+ // Блокируем обработку клика если был drag или если уже обрабатывается
1024
+ if (btn.__dragActive || btn.__clickProcessing) return;
1025
+
1026
+ btn.__clickProcessing = true;
1027
+ setTimeout(() => { btn.__clickProcessing = false; }, 100);
1028
+
1010
1029
  this.animateButton(btn);
1011
1030
  const target = 64; // кратно 128 для лучшей четкости при даунскейле
1012
1031
  const targetW = target;
@@ -1079,22 +1098,43 @@ export class Toolbar {
1079
1098
  * Определяет базовый путь для эмоджи в зависимости от режима
1080
1099
  */
1081
1100
  getEmojiBasePath() {
1082
- // Проверяем, есть ли глобальная настройка базового пути
1101
+ // 1. Приоритет: опция basePath из конструктора
1102
+ if (this.emojiBasePath) {
1103
+ return this.emojiBasePath.endsWith('/') ? this.emojiBasePath : this.emojiBasePath + '/';
1104
+ }
1105
+
1106
+ // 2. Глобальная настройка (абсолютный URL)
1083
1107
  if (window.MOODBOARD_BASE_PATH) {
1084
- return `${window.MOODBOARD_BASE_PATH}src/assets/emodji/`;
1108
+ const basePath = window.MOODBOARD_BASE_PATH.endsWith('/') ? window.MOODBOARD_BASE_PATH : window.MOODBOARD_BASE_PATH + '/';
1109
+ return `${basePath}src/assets/emodji/`;
1085
1110
  }
1086
1111
 
1087
- // Попытка определить автоматически
1088
- const scripts = document.querySelectorAll('script[src]');
1089
- for (const script of scripts) {
1090
- if (script.src.includes('moodboard') || script.src.includes('node_modules')) {
1091
- const baseUrl = new URL(script.src).origin;
1092
- return `${baseUrl}/node_modules/moodboard-futurello/src/assets/emodji/`;
1112
+ // 3. Вычисление от URL текущего модуля (import.meta.url)
1113
+ try {
1114
+ // Используем import.meta.url для получения абсолютного пути к ассетам
1115
+ const currentModuleUrl = import.meta.url;
1116
+ // От текущего модуля (ui/Toolbar.js) поднимаемся к корню пакета и идем к assets
1117
+ const emojiUrl = new URL('../assets/emodji/', currentModuleUrl).href;
1118
+ return emojiUrl;
1119
+ } catch (error) {
1120
+ console.warn('⚠️ Не удалось определить путь через import.meta.url:', error);
1121
+ }
1122
+
1123
+ // 4. Fallback: поиск script тега для определения базового URL
1124
+ try {
1125
+ const currentScript = document.currentScript;
1126
+ if (currentScript && currentScript.src) {
1127
+ // Пытаемся определить от текущего скрипта
1128
+ const scriptUrl = new URL(currentScript.src);
1129
+ const baseUrl = new URL('../assets/emodji/', scriptUrl).href;
1130
+ return baseUrl;
1093
1131
  }
1132
+ } catch (error) {
1133
+ console.warn('⚠️ Не удалось определить путь через currentScript:', error);
1094
1134
  }
1095
1135
 
1096
- // Fallback: относительный путь
1097
- return './src/assets/emodji/';
1136
+ // 5. Последний fallback: абсолютный путь от корня домена
1137
+ return '/src/assets/emodji/';
1098
1138
  }
1099
1139
 
1100
1140
  toggleEmojiPopup(anchorButton) {
@@ -1202,7 +1242,7 @@ export class Toolbar {
1202
1242
  }
1203
1243
 
1204
1244
  // Файл выбран - запускаем режим "призрака"
1205
- this.eventBus.emit(Events.Place.FileSelected, {
1245
+ const fileSelectedData = {
1206
1246
  file: file,
1207
1247
  fileName: file.name,
1208
1248
  fileSize: file.size,
@@ -1211,7 +1251,10 @@ export class Toolbar {
1211
1251
  width: 120,
1212
1252
  height: 140
1213
1253
  }
1214
- });
1254
+ };
1255
+
1256
+ console.log('📁 Toolbar: эмитируем FileSelected:', fileSelectedData);
1257
+ this.eventBus.emit(Events.Place.FileSelected, fileSelectedData);
1215
1258
 
1216
1259
  // Активируем инструмент размещения
1217
1260
  this.eventBus.emit(Events.Keyboard.ToolSelect, { tool: 'place' });
@@ -67,13 +67,19 @@ export function emojiToLocalUrl(emoji) {
67
67
  }
68
68
  }
69
69
 
70
- // Карта локальных изображений (SVG и PNG) из src/assets/emodji (собирается Vite'ом)
71
- // Ключи вида '../assets/emodji/1f600.svg' / '../assets/emodji/1f600.png' URL
72
- const _localEmojiModules = import.meta && typeof import.meta.glob === 'function'
73
- ? {
74
- ...import.meta.glob('../assets/emodji/**/*.{svg,SVG,png,PNG}', { eager: true, query: '?url', import: 'default' })
70
+ // Карта локальных изображений (SVG и PNG) из src/assets/emodji
71
+ // В режиме с bundler используем import.meta.glob, иначе fallback
72
+ const _localEmojiModules = (() => {
73
+ if (typeof import.meta !== 'undefined' && import.meta.glob) {
74
+ try {
75
+ return import.meta.glob('../assets/emodji/**/*.{svg,SVG,png,PNG}', { eager: true, query: '?url', import: 'default' });
76
+ } catch (error) {
77
+ console.warn('⚠️ import.meta.glob failed, using fallback for emoji resolution');
78
+ return {};
75
79
  }
76
- : {};
80
+ }
81
+ return {};
82
+ })();
77
83
 
78
84
  // Индекс по имени файла (без пути)
79
85
  const _localEmojiIndex = (() => {
@@ -119,3 +125,44 @@ export function buildLocalPaths(emoji) {
119
125
  }
120
126
  }
121
127
 
128
+ /**
129
+ * Возвращает абсолютный URL для эмоджи с учетом базового пути
130
+ * @param {string} emoji - эмоджи символ
131
+ * @param {string} basePath - базовый путь к ассетам
132
+ * @returns {string|null} абсолютный URL или null
133
+ */
134
+ export function resolveEmojiAbsoluteUrl(emoji, basePath = null) {
135
+ try {
136
+ const base = emojiFilenameBase(emoji);
137
+ if (!base) return null;
138
+
139
+ // Определяем базовый путь
140
+ let resolvedBasePath = basePath;
141
+
142
+ if (!resolvedBasePath) {
143
+ // Пытаемся определить от import.meta.url
144
+ try {
145
+ resolvedBasePath = new URL('../assets/emodji/', import.meta.url).href;
146
+ } catch (error) {
147
+ // Fallback на глобальную настройку
148
+ if (window.MOODBOARD_BASE_PATH) {
149
+ const globalPath = window.MOODBOARD_BASE_PATH.endsWith('/') ? window.MOODBOARD_BASE_PATH : window.MOODBOARD_BASE_PATH + '/';
150
+ resolvedBasePath = `${globalPath}src/assets/emodji/`;
151
+ } else {
152
+ resolvedBasePath = '/src/assets/emodji/';
153
+ }
154
+ }
155
+ }
156
+
157
+ // Формируем URL (приоритет PNG, потом SVG)
158
+ if (!resolvedBasePath.endsWith('/')) resolvedBasePath += '/';
159
+
160
+ // Возвращаем URL в формате готовом для использования
161
+ return `${resolvedBasePath}Смайлики/${base}.png`; // Большинство эмоджи в папке Смайлики
162
+
163
+ } catch (error) {
164
+ console.warn('⚠️ Ошибка resolveEmojiAbsoluteUrl:', error);
165
+ return null;
166
+ }
167
+ }
168
+