kingkont 0.7.99 → 0.8.0
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 +9 -5
- package/renderer/media.js +23 -0
- package/renderer/state.js +15 -1
package/package.json
CHANGED
package/renderer/board.js
CHANGED
|
@@ -1885,15 +1885,19 @@ function showNodeContextMenu(node, clientX, clientY) {
|
|
|
1885
1885
|
add('📥 Вставить (Cmd+V)', () => pasteClipboardNodes(), { disabled: !canPaste });
|
|
1886
1886
|
const canReplace = !!(state.clipboard?.length);
|
|
1887
1887
|
add('⇄ Заменить из буфера', () => replaceNodeFromClipboard(node), { disabled: !canReplace });
|
|
1888
|
-
|
|
1889
|
-
|
|
1888
|
+
// «Сохранить как…» — для любого медиа-файла (image / audio / video).
|
|
1889
|
+
// Показывает нативный системный диалог через FS-Access-API; если API
|
|
1890
|
+
// недоступен (web-режим) — auto-сохранение в Downloads/.
|
|
1891
|
+
if ((node.type === 'image' || node.type === 'audio' || node.type === 'video') && node.file) {
|
|
1892
|
+
add('💾 Сохранить как…', async () => {
|
|
1890
1893
|
try {
|
|
1891
1894
|
const fh = await resolveBoardFile(state.currentBoard.handle, node.file);
|
|
1892
1895
|
const file = await fh.getFile();
|
|
1893
|
-
|
|
1896
|
+
const suggested = node.file.split('/').pop();
|
|
1897
|
+
await saveAsToDisk(file, suggested);
|
|
1894
1898
|
} catch (err) {
|
|
1895
|
-
console.error('
|
|
1896
|
-
alert('Не удалось
|
|
1899
|
+
console.error('Save failed:', err);
|
|
1900
|
+
alert('Не удалось сохранить: ' + (err?.message || err));
|
|
1897
1901
|
}
|
|
1898
1902
|
});
|
|
1899
1903
|
}
|
package/renderer/media.js
CHANGED
|
@@ -152,6 +152,29 @@ async function downloadAudioAtSpeed(file, filename, speed, onProgress) {
|
|
|
152
152
|
await ff.deleteFile(outName).catch(() => {});
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
// «Сохранить как…» — показывает нативный системный диалог выбора пути
|
|
156
|
+
// (через FS-Access-API), куда сохранить файл. Если API недоступен или
|
|
157
|
+
// юзер ничего не выбрал — fallback на triggerDownload (auto-сохранение
|
|
158
|
+
// в Downloads/). Возвращает true если файл сохранён, false если отменено.
|
|
159
|
+
async function saveAsToDisk(file, suggestedName) {
|
|
160
|
+
if (typeof window.showSaveFilePicker === 'function') {
|
|
161
|
+
try {
|
|
162
|
+
const handle = await window.showSaveFilePicker({ suggestedName });
|
|
163
|
+
const w = await handle.createWritable();
|
|
164
|
+
await w.write(file);
|
|
165
|
+
await w.close();
|
|
166
|
+
return true;
|
|
167
|
+
} catch (err) {
|
|
168
|
+
// AbortError — юзер закрыл диалог. Не fallback'аемся, чтобы не
|
|
169
|
+
// дёргать auto-download против его воли.
|
|
170
|
+
if (err?.name === 'AbortError') return false;
|
|
171
|
+
// Любая другая ошибка — fall through на triggerDownload.
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
triggerDownload(file, suggestedName);
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
155
178
|
function triggerDownload(blob, filename) {
|
|
156
179
|
const url = URL.createObjectURL(blob);
|
|
157
180
|
const a = document.createElement('a');
|
package/renderer/state.js
CHANGED
|
@@ -439,6 +439,20 @@ function getFileType(file) {
|
|
|
439
439
|
} catch {}
|
|
440
440
|
})();
|
|
441
441
|
|
|
442
|
+
// One-time migration: дефолт video-разрешения раньше был 720p — сбрасываем
|
|
443
|
+
// тех, у кого залипло '720p' (вероятно, они никогда явно не выбирали),
|
|
444
|
+
// на минимальное '480p'. После явного выбора 720p/1080p — он сохранится
|
|
445
|
+
// как и раньше, миграция повторно не отрабатывает.
|
|
446
|
+
(function migrateVideoResolutionDefault() {
|
|
447
|
+
try {
|
|
448
|
+
if (localStorage.getItem('videoResolutionResetMin') === '1') return;
|
|
449
|
+
if (localStorage.getItem('videoResolution') === '720p') {
|
|
450
|
+
localStorage.setItem('videoResolution', '480p');
|
|
451
|
+
}
|
|
452
|
+
localStorage.setItem('videoResolutionResetMin', '1');
|
|
453
|
+
} catch {}
|
|
454
|
+
})();
|
|
455
|
+
|
|
442
456
|
const state = {
|
|
443
457
|
filmHandle: null,
|
|
444
458
|
currentBoard: null, // { kind, name, key, handle, metadata, urls }
|
|
@@ -449,7 +463,7 @@ const state = {
|
|
|
449
463
|
videoModel: localStorage.getItem('videoModel') || 'seedance-2', // 'seedance-2' | 'kling-o1' | 'kling-3.0' | ...
|
|
450
464
|
ttsModel: localStorage.getItem('ttsModel') || 'qwen/qwen3-tts', // qwen/elevenlabs/v3/minimax/speech-02-hd/gemini
|
|
451
465
|
videoDuration: +(localStorage.getItem('videoDuration') || 5),
|
|
452
|
-
videoResolution: localStorage.getItem('videoResolution') || '
|
|
466
|
+
videoResolution: localStorage.getItem('videoResolution') || '480p',
|
|
453
467
|
videoAspect: localStorage.getItem('videoAspect') || '9:16',
|
|
454
468
|
jobs: new Map(), // nodeId -> { boardKey, boardHandle, kind, taskId }
|
|
455
469
|
undoStack: [], // {type, ...payload}
|