kingkont 0.7.94 → 0.7.95

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kingkont",
3
- "version": "0.7.94",
3
+ "version": "0.7.95",
4
4
  "description": "KingKont · Chatium — нод-редактор сцен с AI-генерацией (картинки/видео/голос/SFX/музыка/текст)",
5
5
  "main": "main.js",
6
6
  "bin": {
@@ -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') media.controls = true;
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();