kingkont 0.20.66 → 0.20.67
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/renderer/board.js +123 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kingkont",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.67",
|
|
4
4
|
"description": "KingKont \u00b7 Chatium \u2014 \u043d\u043e\u0434-\u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u0441\u0446\u0435\u043d \u0441 AI-\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0435\u0439 (\u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438/\u0432\u0438\u0434\u0435\u043e/\u0433\u043e\u043b\u043e\u0441/SFX/\u043c\u0443\u0437\u044b\u043a\u0430/\u0442\u0435\u043a\u0441\u0442)",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"bin": {
|
package/renderer/board.js
CHANGED
|
@@ -2647,6 +2647,110 @@ function attachInlineDescription(el, node) {
|
|
|
2647
2647
|
});
|
|
2648
2648
|
}
|
|
2649
2649
|
|
|
2650
|
+
// Копирование медиа-файла ноды в СИСТЕМНЫЙ буфер обмена (не в state.clipboard,
|
|
2651
|
+
// который для внутренней copy/paste нод). Юзер: «нужно чтобы любую картинку и
|
|
2652
|
+
// любое видео можно было скопировать в буфер обмена чтобы вставить в другое
|
|
2653
|
+
// приложение».
|
|
2654
|
+
//
|
|
2655
|
+
// IMAGE: ClipboardItem с image/png MIME. Если исходный формат — jpeg/webp/gif,
|
|
2656
|
+
// перерисовываем в PNG через canvas (большинство ОС-приложений принимают PNG
|
|
2657
|
+
// из clipboard). Также пишем оригинальный MIME как дополнительный тип —
|
|
2658
|
+
// некоторые приложения предпочитают исходный формат.
|
|
2659
|
+
// VIDEO: Web Clipboard API не поддерживает video MIME-типы (только image/* и
|
|
2660
|
+
// text/*). Fallback: пишем file:// URL (Electron) или name файла в text/plain
|
|
2661
|
+
// и blob:URL — юзер может вставить как ссылку. В Finder/проводник file://
|
|
2662
|
+
// URL открывает файл; в браузер — открывает в новой вкладке.
|
|
2663
|
+
async function copyMediaToSystemClipboard(node) {
|
|
2664
|
+
if (!node || !node.file) throw new Error('У ноды нет файла');
|
|
2665
|
+
if (!navigator.clipboard || !navigator.clipboard.write) {
|
|
2666
|
+
throw new Error('Clipboard API недоступен в этом браузере');
|
|
2667
|
+
}
|
|
2668
|
+
const fh = await resolveBoardFile(state.currentBoard.handle, node.file);
|
|
2669
|
+
const file = await fh.getFile();
|
|
2670
|
+
if (node.type === 'image') {
|
|
2671
|
+
const mime = file.type || _guessMimeFromName(node.file) || 'image/png';
|
|
2672
|
+
let pngBlob;
|
|
2673
|
+
if (mime === 'image/png') {
|
|
2674
|
+
pngBlob = file;
|
|
2675
|
+
} else {
|
|
2676
|
+
// Перерисовываем в PNG через canvas. Clipboard API требует именно
|
|
2677
|
+
// image/png; другие форматы (jpeg/webp/gif) могут не приниматься.
|
|
2678
|
+
pngBlob = await _imageBlobToPng(file);
|
|
2679
|
+
}
|
|
2680
|
+
const items = { 'image/png': pngBlob };
|
|
2681
|
+
// Дополнительно отдаём исходный формат если он отличается от PNG —
|
|
2682
|
+
// некоторые приложения (Photos, Photoshop) предпочитают original.
|
|
2683
|
+
if (mime !== 'image/png' && (mime === 'image/jpeg' || mime === 'image/webp')) {
|
|
2684
|
+
items[mime] = file;
|
|
2685
|
+
}
|
|
2686
|
+
await navigator.clipboard.write([new ClipboardItem(items)]);
|
|
2687
|
+
return { format: 'image/png', size: pngBlob.size };
|
|
2688
|
+
}
|
|
2689
|
+
if (node.type === 'video') {
|
|
2690
|
+
// Video не поддерживается Clipboard API → ставим text/plain с file-name +
|
|
2691
|
+
// text/uri-list с blob:URL (приложения принимающие URL смогут открыть).
|
|
2692
|
+
const url = URL.createObjectURL(file);
|
|
2693
|
+
const filename = node.file.split('/').pop() || 'video';
|
|
2694
|
+
try {
|
|
2695
|
+
await navigator.clipboard.write([new ClipboardItem({
|
|
2696
|
+
'text/plain': new Blob([url], { type: 'text/plain' }),
|
|
2697
|
+
'text/uri-list': new Blob([url], { type: 'text/uri-list' }),
|
|
2698
|
+
})]);
|
|
2699
|
+
} catch {
|
|
2700
|
+
// Fallback на старый writeText API если ClipboardItem с uri-list не
|
|
2701
|
+
// поддерживается.
|
|
2702
|
+
await navigator.clipboard.writeText(url);
|
|
2703
|
+
}
|
|
2704
|
+
// ВНИМАНИЕ: blob:URL живёт только пока эта вкладка открыта. Юзер должен
|
|
2705
|
+
// вставить URL ДО reload'а. Альтернатива — использовать ПКМ «💾 Сохранить
|
|
2706
|
+
// как…» и потом drag-n-drop'ать файл из системы (сообщаем юзеру).
|
|
2707
|
+
if (typeof showToast === 'function') {
|
|
2708
|
+
showToast('⚠ Видео скопировано как blob:URL — вставь ДО перезагрузки. Для надёжного копирования используй «💾 Сохранить как…».', 'warn');
|
|
2709
|
+
}
|
|
2710
|
+
return { format: 'blob-url', filename };
|
|
2711
|
+
}
|
|
2712
|
+
throw new Error('Тип ноды не поддерживается: ' + node.type);
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
// Конвертация blob картинки в PNG через canvas.
|
|
2716
|
+
async function _imageBlobToPng(blob) {
|
|
2717
|
+
return new Promise((resolve, reject) => {
|
|
2718
|
+
const img = new Image();
|
|
2719
|
+
const url = URL.createObjectURL(blob);
|
|
2720
|
+
img.onload = () => {
|
|
2721
|
+
try {
|
|
2722
|
+
const c = document.createElement('canvas');
|
|
2723
|
+
c.width = img.naturalWidth;
|
|
2724
|
+
c.height = img.naturalHeight;
|
|
2725
|
+
const ctx = c.getContext('2d');
|
|
2726
|
+
ctx.drawImage(img, 0, 0);
|
|
2727
|
+
c.toBlob(b => {
|
|
2728
|
+
URL.revokeObjectURL(url);
|
|
2729
|
+
if (b) resolve(b);
|
|
2730
|
+
else reject(new Error('canvas.toBlob вернул null'));
|
|
2731
|
+
}, 'image/png');
|
|
2732
|
+
} catch (e) {
|
|
2733
|
+
URL.revokeObjectURL(url);
|
|
2734
|
+
reject(e);
|
|
2735
|
+
}
|
|
2736
|
+
};
|
|
2737
|
+
img.onerror = () => {
|
|
2738
|
+
URL.revokeObjectURL(url);
|
|
2739
|
+
reject(new Error('Не удалось загрузить картинку'));
|
|
2740
|
+
};
|
|
2741
|
+
img.src = url;
|
|
2742
|
+
});
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
function _guessMimeFromName(name) {
|
|
2746
|
+
const ext = (name || '').toLowerCase().split('.').pop();
|
|
2747
|
+
return ({
|
|
2748
|
+
png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg',
|
|
2749
|
+
webp: 'image/webp', gif: 'image/gif',
|
|
2750
|
+
mp4: 'video/mp4', webm: 'video/webm', mov: 'video/quicktime',
|
|
2751
|
+
})[ext] || null;
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2650
2754
|
// Скопировать / вставить настройки сцены (aspectRatio + defaultPrompts).
|
|
2651
2755
|
// Хранение — in-memory state.clipboardSceneSettings (живёт до closeProject).
|
|
2652
2756
|
// Юзер: «сделай возможность скопировать настройки из одной сцены в другую».
|
|
@@ -4380,6 +4484,25 @@ function showNodeContextMenu(node, clientX, clientY) {
|
|
|
4380
4484
|
add('📥 Вставить (Cmd+V)', () => pasteClipboardNodes(), { disabled: !canPaste });
|
|
4381
4485
|
const canReplace = !!(state.clipboard?.length);
|
|
4382
4486
|
add('⇄ Заменить из буфера', () => replaceNodeFromClipboard(node), { disabled: !canReplace });
|
|
4487
|
+
// «📋 Копировать в системный буфер» — image/video. Юзер: «нужно чтобы
|
|
4488
|
+
// любую картинку и любое видео можно было скопировать в буфер обмена
|
|
4489
|
+
// чтобы вставить в другое приложение». Image — через ClipboardItem
|
|
4490
|
+
// (image/png). Video — Web Clipboard API не поддерживает video MIME,
|
|
4491
|
+
// делаем fallback: пишем file:// URL / название файла в text/plain,
|
|
4492
|
+
// юзер вставит в Finder/проводник (URL) или как текст.
|
|
4493
|
+
if ((node.type === 'image' || node.type === 'video') && node.file) {
|
|
4494
|
+
add('📋 Копировать в буфер (системный)', async () => {
|
|
4495
|
+
try {
|
|
4496
|
+
await copyMediaToSystemClipboard(node);
|
|
4497
|
+
if (typeof showToast === 'function') {
|
|
4498
|
+
showToast('📋 Скопировано в системный буфер', 'ok');
|
|
4499
|
+
}
|
|
4500
|
+
} catch (e) {
|
|
4501
|
+
console.error('clipboard copy failed:', e);
|
|
4502
|
+
alert('Не удалось скопировать: ' + (e?.message || e));
|
|
4503
|
+
}
|
|
4504
|
+
});
|
|
4505
|
+
}
|
|
4383
4506
|
// «Сохранить как…» — для любого медиа-файла (image / audio / video).
|
|
4384
4507
|
// Показывает нативный системный диалог через FS-Access-API; если API
|
|
4385
4508
|
// недоступен (web-режим) — auto-сохранение в Downloads/.
|