kingkont 0.7.94 → 0.7.96
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/settings.js +39 -1
- package/renderer/state.js +13 -0
package/package.json
CHANGED
package/renderer/settings.js
CHANGED
|
@@ -396,10 +396,22 @@ async function renderNodeBody(node, body) {
|
|
|
396
396
|
media = document.createElement('img');
|
|
397
397
|
media.src = url; media.alt = node.file; media.draggable = false;
|
|
398
398
|
media.decoding = 'async';
|
|
399
|
+
// Auto-fit высоты ноды под aspect картинки. Иначе после генерации
|
|
400
|
+
// (где node.width/height заморожены до прихода файла) или после
|
|
401
|
+
// замены картинки через history новый <img> с object-fit:contain
|
|
402
|
+
// оставляет пустое место сверху/снизу — node.height не совпадает
|
|
403
|
+
// с display-height image'а.
|
|
404
|
+
media.addEventListener('load', () => fitNodeHeightToMedia(node, nodeEl, media), { once: true });
|
|
399
405
|
} else {
|
|
400
406
|
media = document.createElement(node.type);
|
|
401
407
|
media.src = url;
|
|
402
|
-
if (node.type === 'video')
|
|
408
|
+
if (node.type === 'video') {
|
|
409
|
+
media.controls = true;
|
|
410
|
+
// То же для видео — но через loadedmetadata, который у preload:none
|
|
411
|
+
// выстрелит при первой попытке play. Когда юзер запустит — нода
|
|
412
|
+
// подстроится под реальный aspect.
|
|
413
|
+
media.addEventListener('loadedmetadata', () => fitNodeHeightToMedia(node, nodeEl, media), { once: true });
|
|
414
|
+
}
|
|
403
415
|
// preload="none" — заголовки video/audio НЕ читаются пока юзер не запустит play.
|
|
404
416
|
// Раньше "metadata" грузил chunk для duration/dimensions с каждой ноды на selectBoard.
|
|
405
417
|
media.preload = 'none';
|
|
@@ -488,6 +500,32 @@ async function renderNodeBody(node, body) {
|
|
|
488
500
|
}
|
|
489
501
|
}
|
|
490
502
|
|
|
503
|
+
// Подгоняет node.height под natural-aspect медиа (image/video). Сохраняет
|
|
504
|
+
// текущую node.width, пересчитывает height = width * (natH/natW) + chrome.
|
|
505
|
+
// Без этого после генерации (или history-undo на ноду с другим aspect'ом)
|
|
506
|
+
// нода оставалась прежнего размера, и <img width:100%; height:auto> не
|
|
507
|
+
// заполнял её по высоте → пустое место снизу.
|
|
508
|
+
function fitNodeHeightToMedia(node, nodeEl, mediaEl) {
|
|
509
|
+
if (!nodeEl || !mediaEl) return;
|
|
510
|
+
const natW = mediaEl.naturalWidth || mediaEl.videoWidth || 0;
|
|
511
|
+
const natH = mediaEl.naturalHeight || mediaEl.videoHeight || 0;
|
|
512
|
+
if (!natW || !natH) return;
|
|
513
|
+
// Chrome — header + footer (если есть). image-node padding=0, ничего не отъедает.
|
|
514
|
+
const headerH = nodeEl.querySelector('.node-header')?.offsetHeight || 0;
|
|
515
|
+
const footerH = nodeEl.querySelector('.node-footer')?.offsetHeight || 0;
|
|
516
|
+
const chromeH = headerH + footerH;
|
|
517
|
+
// Берём актуальную width — node.width если задано, иначе текущий offsetWidth.
|
|
518
|
+
const w = node.width || nodeEl.offsetWidth;
|
|
519
|
+
if (!w) return;
|
|
520
|
+
const newH = Math.round(w * (natH / natW) + chromeH);
|
|
521
|
+
// 2px tolerance — не шумим в save/connections при суб-пиксельных колебаниях.
|
|
522
|
+
if (Math.abs(newH - (node.height || 0)) <= 2) return;
|
|
523
|
+
node.height = newH;
|
|
524
|
+
nodeEl.style.height = newH + 'px';
|
|
525
|
+
if (typeof renderConnections === 'function') renderConnections();
|
|
526
|
+
scheduleSave();
|
|
527
|
+
}
|
|
528
|
+
|
|
491
529
|
function attachAnchor(node, el, anchor) {
|
|
492
530
|
anchor.addEventListener('mousedown', e => {
|
|
493
531
|
e.preventDefault();
|
package/renderer/state.js
CHANGED
|
@@ -426,6 +426,19 @@ function getFileType(file) {
|
|
|
426
426
|
}
|
|
427
427
|
|
|
428
428
|
// =================== State ===================
|
|
429
|
+
// One-time migration: если юзер ранее залипал на Pro — сбрасываем на 2.
|
|
430
|
+
// Pro дороже и медленнее, как дефолт не оптимально. Маркер не даёт
|
|
431
|
+
// миграции отрабатывать повторно: после ручного выбора Pro он останется.
|
|
432
|
+
(function migrateImageModelDefault() {
|
|
433
|
+
try {
|
|
434
|
+
if (localStorage.getItem('imageModelResetV2') === '1') return;
|
|
435
|
+
if (localStorage.getItem('imageModel') === 'nano-banana-pro') {
|
|
436
|
+
localStorage.setItem('imageModel', 'nano-banana-2');
|
|
437
|
+
}
|
|
438
|
+
localStorage.setItem('imageModelResetV2', '1');
|
|
439
|
+
} catch {}
|
|
440
|
+
})();
|
|
441
|
+
|
|
429
442
|
const state = {
|
|
430
443
|
filmHandle: null,
|
|
431
444
|
currentBoard: null, // { kind, name, key, handle, metadata, urls }
|