@sequent-org/moodboard 1.4.4 → 1.4.6
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 +1 -1
- package/src/core/ApiClient.js +30 -86
- package/src/core/HistoryManager.js +0 -102
- package/src/core/SaveManager.js +27 -0
- package/src/core/commands/CreateObjectCommand.js +1 -9
- package/src/core/commands/DeleteObjectCommand.js +19 -88
- package/src/core/commands/EditFileNameCommand.js +1 -2
- package/src/core/commands/GroupDeleteCommand.js +11 -44
- package/src/core/commands/GroupMoveCommand.js +1 -26
- package/src/core/commands/GroupReorderZCommand.js +1 -2
- package/src/core/commands/GroupResizeCommand.js +1 -4
- package/src/core/commands/GroupRotateCommand.js +1 -9
- package/src/core/commands/MindmapStatePatchCommand.js +1 -1
- package/src/core/commands/MoveObjectCommand.js +1 -3
- package/src/core/commands/PasteObjectCommand.js +1 -10
- package/src/core/commands/ReorderZCommand.js +1 -1
- package/src/core/commands/ResizeObjectCommand.js +1 -3
- package/src/core/commands/RotateObjectCommand.js +1 -9
- package/src/core/commands/UpdateContentCommand.js +1 -1
- package/src/core/commands/UpdateFramePropertiesCommand.js +1 -1
- package/src/core/commands/UpdateFrameTypeCommand.js +1 -1
- package/src/core/commands/UpdateNoteStyleCommand.js +1 -1
- package/src/core/commands/UpdateTextStyleCommand.js +1 -1
- package/src/core/events/Events.js +0 -2
- package/src/core/flows/ClipboardFlow.js +50 -14
- package/src/core/flows/SaveFlow.js +5 -0
- package/src/core/index.js +32 -0
- package/src/core/keyboard/KeyboardClipboardImagePaste.js +6 -30
- package/src/services/FileUploadService.js +2 -2
- package/src/services/ImageUploadService.js +2 -2
- package/src/tools/manager/ToolEventRouter.js +34 -74
- package/src/tools/object-tools/PlacementTool.js +5 -29
- package/src/tools/object-tools/placement/PlacementInputRouter.js +1 -6
- package/src/tools/object-tools/placement/PlacementPayloadFactory.js +0 -29
|
@@ -47,32 +47,7 @@ export class GroupMoveCommand extends BaseCommand {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
undo() {
|
|
50
|
-
//
|
|
51
|
-
for (const item of this.moves) {
|
|
52
|
-
if (this.coordinatesAreTopLeft) {
|
|
53
|
-
// Координаты уже левый-верх (Frame перемещение)
|
|
54
|
-
this.core.updateObjectPositionDirect(item.id, item.from);
|
|
55
|
-
this.emit(Events.Object.TransformUpdated, {
|
|
56
|
-
objectId: item.id,
|
|
57
|
-
type: 'position',
|
|
58
|
-
position: item.from
|
|
59
|
-
});
|
|
60
|
-
} else {
|
|
61
|
-
// Координаты - центры PIXI (обычное групповое перемещение)
|
|
62
|
-
const pixiObject = this.core?.pixi?.objects?.get(item.id);
|
|
63
|
-
if (pixiObject) {
|
|
64
|
-
const halfW = (pixiObject.width || 0) / 2;
|
|
65
|
-
const halfH = (pixiObject.height || 0) / 2;
|
|
66
|
-
const topLeft = { x: item.from.x - halfW, y: item.from.y - halfH };
|
|
67
|
-
this.core.updateObjectPositionDirect(item.id, topLeft);
|
|
68
|
-
this.emit(Events.Object.TransformUpdated, {
|
|
69
|
-
objectId: item.id,
|
|
70
|
-
type: 'position',
|
|
71
|
-
position: topLeft
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
50
|
+
// Локальный undo отключен: история состояния загружается с сервера по версиям.
|
|
76
51
|
}
|
|
77
52
|
|
|
78
53
|
getDescription() {
|
|
@@ -22,8 +22,7 @@ export class GroupReorderZCommand extends BaseCommand {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
undo() {
|
|
25
|
-
|
|
26
|
-
this.applyOrder(this.beforeOrder);
|
|
25
|
+
// Локальный undo отключен: история состояния загружается с сервера по версиям.
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
applyOrder(idOrder) {
|
|
@@ -23,10 +23,7 @@ export class GroupResizeCommand extends BaseCommand {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
undo() {
|
|
26
|
-
|
|
27
|
-
this.core.updateObjectSizeAndPositionDirect(c.id, c.fromSize, c.fromPos, c.type || null);
|
|
28
|
-
this.emit(Events.Object.TransformUpdated, { objectId: c.id, type: 'resize', size: c.fromSize, position: c.fromPos });
|
|
29
|
-
}
|
|
26
|
+
// Локальный undo отключен: история состояния загружается с сервера по версиям.
|
|
30
27
|
}
|
|
31
28
|
|
|
32
29
|
getDescription() {
|
|
@@ -28,15 +28,7 @@ export class GroupRotateCommand extends BaseCommand {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
undo() {
|
|
31
|
-
|
|
32
|
-
if (this.core.pixi?.updateObjectRotation) {
|
|
33
|
-
this.core.pixi.updateObjectRotation(c.id, c.fromAngle);
|
|
34
|
-
}
|
|
35
|
-
this.core.updateObjectRotationDirect(c.id, c.fromAngle);
|
|
36
|
-
this.core.updateObjectPositionDirect(c.id, c.fromPos);
|
|
37
|
-
this.emit(Events.Object.TransformUpdated, { objectId: c.id, type: 'rotation', angle: c.fromAngle });
|
|
38
|
-
this.emit(Events.Object.TransformUpdated, { objectId: c.id, type: 'position', position: c.fromPos });
|
|
39
|
-
}
|
|
31
|
+
// Локальный undo отключен: история состояния загружается с сервера по версиям.
|
|
40
32
|
}
|
|
41
33
|
|
|
42
34
|
getDescription() {
|
|
@@ -23,9 +23,7 @@ export class MoveObjectCommand extends BaseCommand {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
undo() {
|
|
26
|
-
//
|
|
27
|
-
this._setPosition(this.oldPosition);
|
|
28
|
-
|
|
26
|
+
// Локальный undo отключен: история состояния загружается с сервера по версиям.
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
_setPosition(position) {
|
|
@@ -96,16 +96,7 @@ export class PasteObjectCommand extends BaseCommand {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
undo() {
|
|
99
|
-
|
|
100
|
-
// Удаляем созданный объект
|
|
101
|
-
this.coreMoodboard.state.removeObject(this.newObjectId);
|
|
102
|
-
this.coreMoodboard.pixi.removeObject(this.newObjectId);
|
|
103
|
-
|
|
104
|
-
// Соответствующего константного события нет — остаёмся без эмита или используем Object.Deleted, если надо глобально
|
|
105
|
-
this.emit(Events.Object.Deleted, {
|
|
106
|
-
objectId: this.newObjectId
|
|
107
|
-
});
|
|
108
|
-
}
|
|
99
|
+
// Локальный undo отключен: история состояния загружается с сервера по версиям.
|
|
109
100
|
}
|
|
110
101
|
|
|
111
102
|
getDescription() {
|
|
@@ -25,9 +25,7 @@ export class ResizeObjectCommand extends BaseCommand {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
undo() {
|
|
28
|
-
//
|
|
29
|
-
this._setSizeAndPosition(this.oldSize, this.oldPosition);
|
|
30
|
-
this._updateResizeHandles();
|
|
28
|
+
// Локальный undo отключен: история состояния загружается с сервера по версиям.
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
_setSizeAndPosition(size, position = null) {
|
|
@@ -25,15 +25,7 @@ export class RotateObjectCommand extends BaseCommand {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
undo() {
|
|
28
|
-
//
|
|
29
|
-
this._setRotation(this.oldAngle);
|
|
30
|
-
|
|
31
|
-
// Возвращаем старый угол поворота
|
|
32
|
-
this.emit(Events.Object.Rotate, {
|
|
33
|
-
objectId: this.objectId,
|
|
34
|
-
angle: this.oldAngle
|
|
35
|
-
});
|
|
36
|
-
console.log(`↩️ Отменяем поворот объекта ${this.objectId}, возвращаем ${this.oldAngle}°`);
|
|
28
|
+
// Локальный undo отключен: история состояния загружается с сервера по версиям.
|
|
37
29
|
}
|
|
38
30
|
|
|
39
31
|
/**
|
|
@@ -22,7 +22,7 @@ export class UpdateContentCommand extends BaseCommand {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
undo() {
|
|
25
|
-
|
|
25
|
+
// Локальный undo отключен: история состояния загружается с сервера по версиям.
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
canMergeWith(otherCommand) {
|
|
@@ -32,7 +32,7 @@ export class UpdateFrameTypeCommand extends BaseCommand {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
undo() {
|
|
35
|
-
|
|
35
|
+
// Локальный undo отключен: история состояния загружается с сервера по версиям.
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
_apply(typeValue, size, position) {
|
|
@@ -19,6 +19,38 @@ export function setupClipboardFlow(core) {
|
|
|
19
19
|
};
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
const ensureServerImage = async ({ src, name, imageId }) => {
|
|
23
|
+
if (imageId) {
|
|
24
|
+
return { src, name, imageId };
|
|
25
|
+
}
|
|
26
|
+
if (!core.imageUploadService) {
|
|
27
|
+
alert('Сервис загрузки изображений недоступен. Изображение не добавлено.');
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
let uploadResult = null;
|
|
32
|
+
if (typeof src === 'string' && /^data:image\//i.test(src)) {
|
|
33
|
+
uploadResult = await core.imageUploadService.uploadFromDataUrl(src, name || 'clipboard-image.png');
|
|
34
|
+
} else {
|
|
35
|
+
const response = await fetch(src);
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
38
|
+
}
|
|
39
|
+
const blob = await response.blob();
|
|
40
|
+
uploadResult = await core.imageUploadService.uploadImage(blob, name || 'clipboard-image');
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
src: uploadResult.url,
|
|
44
|
+
name: uploadResult.name || name,
|
|
45
|
+
imageId: uploadResult.imageId || uploadResult.id
|
|
46
|
+
};
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error('Ошибка загрузки вставленного изображения на сервер:', error);
|
|
49
|
+
alert('Ошибка загрузки изображения на сервер. Изображение не добавлено.');
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
22
54
|
core.eventBus.on(Events.UI.CopyObject, ({ objectId }) => {
|
|
23
55
|
if (!objectId) return;
|
|
24
56
|
core.copyObject(objectId);
|
|
@@ -186,8 +218,10 @@ export function setupClipboardFlow(core) {
|
|
|
186
218
|
core._cursor.y = y;
|
|
187
219
|
});
|
|
188
220
|
|
|
189
|
-
core.eventBus.on(Events.UI.PasteImage, ({ src, name, imageId }) => {
|
|
221
|
+
core.eventBus.on(Events.UI.PasteImage, async ({ src, name, imageId }) => {
|
|
190
222
|
if (!src) return;
|
|
223
|
+
const uploaded = await ensureServerImage({ src, name, imageId });
|
|
224
|
+
if (!uploaded?.imageId) return;
|
|
191
225
|
const view = core.pixi.app.view;
|
|
192
226
|
const world = core.pixi.worldLayer || core.pixi.app.stage;
|
|
193
227
|
const s = world?.scale?.x || 1;
|
|
@@ -214,18 +248,18 @@ export function setupClipboardFlow(core) {
|
|
|
214
248
|
w = 300;
|
|
215
249
|
h = Math.max(1, Math.round(w / ar));
|
|
216
250
|
}
|
|
217
|
-
const revitPayload = await resolveRevitImagePayload(src, {
|
|
251
|
+
const revitPayload = await resolveRevitImagePayload(uploaded.src, {
|
|
218
252
|
source: 'clipboard:paste-image',
|
|
219
|
-
name
|
|
253
|
+
name: uploaded.name
|
|
220
254
|
});
|
|
221
255
|
const properties = {
|
|
222
|
-
src,
|
|
223
|
-
name,
|
|
256
|
+
src: uploaded.src,
|
|
257
|
+
name: uploaded.name,
|
|
224
258
|
width: w,
|
|
225
259
|
height: h,
|
|
226
260
|
...revitPayload.properties
|
|
227
261
|
};
|
|
228
|
-
const extraData = imageId ? { imageId } : {};
|
|
262
|
+
const extraData = uploaded.imageId ? { imageId: uploaded.imageId } : {};
|
|
229
263
|
core.createObject(
|
|
230
264
|
revitPayload.type,
|
|
231
265
|
{ x: Math.round(worldX - Math.round(w / 2)), y: Math.round(worldY - Math.round(h / 2)) },
|
|
@@ -239,14 +273,16 @@ export function setupClipboardFlow(core) {
|
|
|
239
273
|
img.decoding = 'async';
|
|
240
274
|
img.onload = () => { void placeWithAspect(img.naturalWidth || 0, img.naturalHeight || 0); };
|
|
241
275
|
img.onerror = () => { void placeWithAspect(0, 0); };
|
|
242
|
-
img.src = src;
|
|
276
|
+
img.src = uploaded.src;
|
|
243
277
|
} catch (_) {
|
|
244
278
|
void placeWithAspect(0, 0);
|
|
245
279
|
}
|
|
246
280
|
});
|
|
247
281
|
|
|
248
|
-
core.eventBus.on(Events.UI.PasteImageAt, ({ x, y, src, name, imageId }) => {
|
|
282
|
+
core.eventBus.on(Events.UI.PasteImageAt, async ({ x, y, src, name, imageId }) => {
|
|
249
283
|
if (!src) return;
|
|
284
|
+
const uploaded = await ensureServerImage({ src, name, imageId });
|
|
285
|
+
if (!uploaded?.imageId) return;
|
|
250
286
|
const world = core.pixi.worldLayer || core.pixi.app.stage;
|
|
251
287
|
const s = world?.scale?.x || 1;
|
|
252
288
|
const worldX = (x - (world?.x || 0)) / s;
|
|
@@ -260,18 +296,18 @@ export function setupClipboardFlow(core) {
|
|
|
260
296
|
w = 300;
|
|
261
297
|
h = Math.max(1, Math.round(w / ar));
|
|
262
298
|
}
|
|
263
|
-
const revitPayload = await resolveRevitImagePayload(src, {
|
|
299
|
+
const revitPayload = await resolveRevitImagePayload(uploaded.src, {
|
|
264
300
|
source: 'clipboard:paste-image-at',
|
|
265
|
-
name
|
|
301
|
+
name: uploaded.name
|
|
266
302
|
});
|
|
267
303
|
const properties = {
|
|
268
|
-
src,
|
|
269
|
-
name,
|
|
304
|
+
src: uploaded.src,
|
|
305
|
+
name: uploaded.name,
|
|
270
306
|
width: w,
|
|
271
307
|
height: h,
|
|
272
308
|
...revitPayload.properties
|
|
273
309
|
};
|
|
274
|
-
const extraData = imageId ? { imageId } : {};
|
|
310
|
+
const extraData = uploaded.imageId ? { imageId: uploaded.imageId } : {};
|
|
275
311
|
core.createObject(
|
|
276
312
|
revitPayload.type,
|
|
277
313
|
{ x: Math.round(worldX - Math.round(w / 2)), y: Math.round(worldY - Math.round(h / 2)) },
|
|
@@ -285,7 +321,7 @@ export function setupClipboardFlow(core) {
|
|
|
285
321
|
img.decoding = 'async';
|
|
286
322
|
img.onload = () => { void placeWithAspect(img.naturalWidth || 0, img.naturalHeight || 0); };
|
|
287
323
|
img.onerror = () => { void placeWithAspect(0, 0); };
|
|
288
|
-
img.src = src;
|
|
324
|
+
img.src = uploaded.src;
|
|
289
325
|
} catch (_) {
|
|
290
326
|
void placeWithAspect(0, 0);
|
|
291
327
|
}
|
|
@@ -23,6 +23,11 @@ export function setupSaveFlow(core) {
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
core.eventBus.on(Events.Save.Success, async () => {
|
|
26
|
+
if (typeof core.revealPendingObjectsAfterSave === 'function') {
|
|
27
|
+
core.revealPendingObjectsAfterSave();
|
|
28
|
+
} else if (typeof core.revealPendingImageObjectsAfterSave === 'function') {
|
|
29
|
+
core.revealPendingImageObjectsAfterSave();
|
|
30
|
+
}
|
|
26
31
|
// ВРЕМЕННО ОТКЛЮЧЕНО:
|
|
27
32
|
// cleanup-фича требует доработки контракта и серверной поддержки.
|
|
28
33
|
// Автоматический вызов удален, чтобы не запускать cleanup после сохранения.
|
package/src/core/index.js
CHANGED
|
@@ -66,6 +66,8 @@ export class CoreMoodBoard {
|
|
|
66
66
|
csrfToken: this.options.csrfToken
|
|
67
67
|
});
|
|
68
68
|
this.gridSnapResolver = new GridSnapResolver(this);
|
|
69
|
+
// Объекты, требующие подтверждения сохранения (image/file), показываем только после save:success.
|
|
70
|
+
this._pendingPersistAckVisibilityIds = new Set();
|
|
69
71
|
|
|
70
72
|
// Связываем SaveManager с ApiClient для правильной обработки изображений
|
|
71
73
|
this.saveManager.setApiClient(this.apiClient);
|
|
@@ -435,9 +437,39 @@ export class CoreMoodBoard {
|
|
|
435
437
|
const command = new CreateObjectCommand(this, objectData);
|
|
436
438
|
this.history.executeCommand(command);
|
|
437
439
|
|
|
440
|
+
// Строгий UX-контракт: image/file появляются только после успешного сохранения.
|
|
441
|
+
if (this._isPersistAckRequiredType(type)) {
|
|
442
|
+
this._pendingPersistAckVisibilityIds.add(objectData.id);
|
|
443
|
+
this._setObjectVisibility(objectData.id, false);
|
|
444
|
+
}
|
|
445
|
+
|
|
438
446
|
return objectData;
|
|
439
447
|
}
|
|
440
448
|
|
|
449
|
+
_isPersistAckRequiredType(type) {
|
|
450
|
+
return type === 'image' || type === 'revit-screenshot-img' || type === 'file';
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
_setObjectVisibility(objectId, visible) {
|
|
454
|
+
const pixiObject = this.pixi?.objects?.get?.(objectId);
|
|
455
|
+
if (pixiObject) {
|
|
456
|
+
pixiObject.visible = !!visible;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
revealPendingObjectsAfterSave() {
|
|
461
|
+
if (!this._pendingPersistAckVisibilityIds || this._pendingPersistAckVisibilityIds.size === 0) return;
|
|
462
|
+
for (const objectId of this._pendingPersistAckVisibilityIds) {
|
|
463
|
+
this._setObjectVisibility(objectId, true);
|
|
464
|
+
}
|
|
465
|
+
this._pendingPersistAckVisibilityIds.clear();
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Backward-compat alias for tests/integrations created in previous step.
|
|
469
|
+
revealPendingImageObjectsAfterSave() {
|
|
470
|
+
this.revealPendingObjectsAfterSave();
|
|
471
|
+
}
|
|
472
|
+
|
|
441
473
|
// === Прикрепления к фреймам ===
|
|
442
474
|
// Логика фреймов перенесена в FrameService
|
|
443
475
|
|
|
@@ -16,11 +16,11 @@ export class KeyboardClipboardImagePaste {
|
|
|
16
16
|
imageId: uploadResult.imageId || uploadResult.id
|
|
17
17
|
});
|
|
18
18
|
} else {
|
|
19
|
-
|
|
19
|
+
alert('Сервис загрузки изображений недоступен. Изображение не добавлено.');
|
|
20
20
|
}
|
|
21
21
|
} catch (error) {
|
|
22
22
|
console.error('Ошибка загрузки изображения:', error);
|
|
23
|
-
|
|
23
|
+
alert('Ошибка загрузки изображения на сервер. Изображение не добавлено.');
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -34,29 +34,11 @@ export class KeyboardClipboardImagePaste {
|
|
|
34
34
|
imageId: uploadResult.imageId || uploadResult.id
|
|
35
35
|
});
|
|
36
36
|
} else {
|
|
37
|
-
|
|
38
|
-
reader.onload = () => {
|
|
39
|
-
this.eventBus.emit(Events.UI.PasteImage, {
|
|
40
|
-
src: reader.result,
|
|
41
|
-
name: fileName
|
|
42
|
-
});
|
|
43
|
-
};
|
|
44
|
-
reader.readAsDataURL(file);
|
|
37
|
+
alert('Сервис загрузки изображений недоступен. Изображение не добавлено.');
|
|
45
38
|
}
|
|
46
39
|
} catch (error) {
|
|
47
40
|
console.error('Ошибка загрузки файла изображения:', error);
|
|
48
|
-
|
|
49
|
-
const reader = new FileReader();
|
|
50
|
-
reader.onload = () => {
|
|
51
|
-
this.eventBus.emit(Events.UI.PasteImage, {
|
|
52
|
-
src: reader.result,
|
|
53
|
-
name: fileName
|
|
54
|
-
});
|
|
55
|
-
};
|
|
56
|
-
reader.readAsDataURL(file);
|
|
57
|
-
} catch (fallbackError) {
|
|
58
|
-
console.error('Критическая ошибка при чтении файла:', fallbackError);
|
|
59
|
-
}
|
|
41
|
+
alert('Ошибка загрузки изображения на сервер. Изображение не добавлено.');
|
|
60
42
|
}
|
|
61
43
|
}
|
|
62
44
|
|
|
@@ -105,10 +87,7 @@ export class KeyboardClipboardImagePaste {
|
|
|
105
87
|
const dataUrl = await this._blobToDataUrl(blob);
|
|
106
88
|
this.handleImageUpload(dataUrl, srcInHtml.split('/').pop() || 'image');
|
|
107
89
|
} catch (_) {
|
|
108
|
-
|
|
109
|
-
src: srcInHtml,
|
|
110
|
-
name: srcInHtml.split('/').pop() || 'image'
|
|
111
|
-
});
|
|
90
|
+
alert('Не удалось загрузить изображение из URL. Изображение не добавлено.');
|
|
112
91
|
}
|
|
113
92
|
return;
|
|
114
93
|
}
|
|
@@ -151,10 +130,7 @@ export class KeyboardClipboardImagePaste {
|
|
|
151
130
|
this.handleImageUpload(dataUrl, trimmed.split('/').pop() || 'image');
|
|
152
131
|
return;
|
|
153
132
|
} catch (_) {
|
|
154
|
-
|
|
155
|
-
src: trimmed,
|
|
156
|
-
name: trimmed.split('/').pop() || 'image'
|
|
157
|
-
});
|
|
133
|
+
alert('Не удалось загрузить изображение из URL. Изображение не добавлено.');
|
|
158
134
|
return;
|
|
159
135
|
}
|
|
160
136
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
export class FileUploadService {
|
|
5
5
|
constructor(apiClient, options = {}) {
|
|
6
6
|
this.apiClient = apiClient;
|
|
7
|
-
this.uploadEndpoint = '/api/files/upload';
|
|
8
|
-
this.deleteEndpoint = '/api/files';
|
|
7
|
+
this.uploadEndpoint = '/api/v2/files/upload';
|
|
8
|
+
this.deleteEndpoint = '/api/v2/files';
|
|
9
9
|
this.options = {
|
|
10
10
|
csrfToken: null, // Можно передать токен напрямую
|
|
11
11
|
csrfTokenSelector: 'meta[name="csrf-token"]', // Селектор для поиска токена в DOM
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
export class ImageUploadService {
|
|
5
5
|
constructor(apiClient, options = {}) {
|
|
6
6
|
this.apiClient = apiClient;
|
|
7
|
-
this.uploadEndpoint = '/api/images/upload';
|
|
8
|
-
this.deleteEndpoint = '/api/images';
|
|
7
|
+
this.uploadEndpoint = '/api/v2/images/upload';
|
|
8
|
+
this.deleteEndpoint = '/api/v2/images';
|
|
9
9
|
this.options = {
|
|
10
10
|
csrfToken: null, // Можно передать токен напрямую
|
|
11
11
|
csrfTokenSelector: 'meta[name="csrf-token"]', // Селектор для поиска токена в DOM
|
|
@@ -339,28 +339,13 @@ export class ToolEventRouter {
|
|
|
339
339
|
index
|
|
340
340
|
};
|
|
341
341
|
} else {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (!isCurrentDrop()) {
|
|
350
|
-
logDropDebug(diagnostics, 'image_local_fallback_stale_drop_ignored', {
|
|
351
|
-
fileName: file.name || 'image'
|
|
352
|
-
});
|
|
353
|
-
return null;
|
|
354
|
-
}
|
|
355
|
-
logDropDebug(diagnostics, 'image_local_fallback_success', {
|
|
356
|
-
fileName: file.name || 'image'
|
|
357
|
-
});
|
|
358
|
-
return {
|
|
359
|
-
src: localSrc,
|
|
360
|
-
name: file.name || 'image',
|
|
361
|
-
imageId: null,
|
|
362
|
-
index
|
|
363
|
-
};
|
|
342
|
+
showDropWarning(
|
|
343
|
+
manager,
|
|
344
|
+
`Не удалось добавить "${file.name || 'image'}": сервис загрузки изображений недоступен`,
|
|
345
|
+
diagnostics,
|
|
346
|
+
{ fileName: file.name || 'image' }
|
|
347
|
+
);
|
|
348
|
+
return null;
|
|
364
349
|
}
|
|
365
350
|
} catch (error) {
|
|
366
351
|
console.warn('Ошибка загрузки изображения через drag-and-drop:', error);
|
|
@@ -368,28 +353,16 @@ export class ToolEventRouter {
|
|
|
368
353
|
fileName: file.name || 'image',
|
|
369
354
|
message: error?.message || String(error)
|
|
370
355
|
});
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
});
|
|
382
|
-
return null;
|
|
383
|
-
}
|
|
384
|
-
logDropDebug(diagnostics, 'image_error_fallback_success', {
|
|
385
|
-
fileName: file.name || 'image'
|
|
386
|
-
});
|
|
387
|
-
return {
|
|
388
|
-
src: fallbackSrc,
|
|
389
|
-
name: file.name || 'image',
|
|
390
|
-
imageId: null,
|
|
391
|
-
index
|
|
392
|
-
};
|
|
356
|
+
showDropWarning(
|
|
357
|
+
manager,
|
|
358
|
+
`Не удалось загрузить "${file.name || 'image'}" на сервер. Изображение не добавлено.`,
|
|
359
|
+
diagnostics,
|
|
360
|
+
{
|
|
361
|
+
fileName: file.name || 'image',
|
|
362
|
+
message: error?.message || String(error)
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
return null;
|
|
393
366
|
}
|
|
394
367
|
});
|
|
395
368
|
for (const placement of imagePlacements) {
|
|
@@ -461,21 +434,13 @@ export class ToolEventRouter {
|
|
|
461
434
|
fileId: uploadResult.fileId || uploadResult.id || null
|
|
462
435
|
};
|
|
463
436
|
} else {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
fileName: fallbackProps.fileName
|
|
472
|
-
});
|
|
473
|
-
return {
|
|
474
|
-
type: 'file',
|
|
475
|
-
id: 'file',
|
|
476
|
-
position,
|
|
477
|
-
properties: fallbackProps
|
|
478
|
-
};
|
|
437
|
+
showDropWarning(
|
|
438
|
+
manager,
|
|
439
|
+
`Не удалось добавить "${fallbackProps.fileName}": сервис загрузки файлов недоступен`,
|
|
440
|
+
diagnostics,
|
|
441
|
+
{ fileName: fallbackProps.fileName }
|
|
442
|
+
);
|
|
443
|
+
return null;
|
|
479
444
|
}
|
|
480
445
|
} catch (error) {
|
|
481
446
|
console.warn('Ошибка загрузки файла через drag-and-drop:', error);
|
|
@@ -483,21 +448,16 @@ export class ToolEventRouter {
|
|
|
483
448
|
fileName: fallbackProps.fileName,
|
|
484
449
|
message: error?.message || String(error)
|
|
485
450
|
});
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
return
|
|
496
|
-
type: 'file',
|
|
497
|
-
id: 'file',
|
|
498
|
-
position,
|
|
499
|
-
properties: fallbackProps
|
|
500
|
-
};
|
|
451
|
+
showDropWarning(
|
|
452
|
+
manager,
|
|
453
|
+
`Не удалось загрузить "${fallbackProps.fileName}" на сервер. Файл не добавлен.`,
|
|
454
|
+
diagnostics,
|
|
455
|
+
{
|
|
456
|
+
fileName: fallbackProps.fileName,
|
|
457
|
+
message: error?.message || String(error)
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
return null;
|
|
501
461
|
}
|
|
502
462
|
});
|
|
503
463
|
for (const actionPayload of filePlacements) {
|