@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
package/src/initNoBundler.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
567
|
-
const
|
|
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
|
|
727
|
-
const
|
|
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
|
-
|
|
820
|
-
const
|
|
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
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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
|
-
}
|
|
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
|
-
//
|
|
998
|
-
setTimeout(() => {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
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
|
-
//
|
|
1097
|
-
return '
|
|
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
|
-
|
|
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
|
|
71
|
-
//
|
|
72
|
-
const _localEmojiModules =
|
|
73
|
-
|
|
74
|
-
|
|
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
|
+
|