kingkont 0.18.6 → 0.18.8

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.18.6",
3
+ "version": "0.18.8",
4
4
  "description": "KingKont · Chatium — нод-редактор сцен с AI-генерацией (картинки/видео/голос/SFX/музыка/текст)",
5
5
  "main": "main.js",
6
6
  "bin": {
package/renderer/board.js CHANGED
@@ -2461,11 +2461,16 @@ async function createNodeEl(node) {
2461
2461
  el.dataset.id = node.id;
2462
2462
  el.style.left = node.x + 'px';
2463
2463
  el.style.top = node.y + 'px';
2464
- // Label-ноды всегда auto-размер (по тексту) — фиксированный width/height
2465
- // создавал «невидимые боксы» вокруг текста, что ломало hit-testing когда
2466
- // несколько label оказывались рядом (клик попадал в перекрывающий, а не
2467
- // в видимый текст). Игнорируем сохранённые width/height для label-нод.
2468
- if (node.type !== 'label') {
2464
+ // Label-ноды: высоту НЕ применяем (auto по контенту), но ширину ДА,
2465
+ // если юзер явно её задал через label-width-handle. Это позволяет
2466
+ // сделать многострочный label («ширина задаёт перенос»). Без width
2467
+ // auto-width по тексту (как раньше).
2468
+ if (node.type === 'label') {
2469
+ if (node.width) {
2470
+ el.style.width = node.width + 'px';
2471
+ el.dataset.fixedWidth = '1'; // CSS: разрешаем label-text занять 100%
2472
+ }
2473
+ } else {
2469
2474
  if (node.width) el.style.width = node.width + 'px';
2470
2475
  if (node.height) el.style.height = node.height + 'px';
2471
2476
  }
@@ -2514,6 +2519,18 @@ async function createNodeEl(node) {
2514
2519
  el.appendChild(rh);
2515
2520
  attachResize(el, node, rh);
2516
2521
 
2522
+ // Label-ноды получают ВТОРОЙ хендл — на правом краю — для управления
2523
+ // только ШИРИНОЙ (юзер просил: «ноде лейбл позволь менять ширину
2524
+ // отдельно, чтобы менялось количество строк если текст длинный»).
2525
+ // Существующий .resize-handle (правый-нижний угол) меняет fontSize.
2526
+ if (node.type === 'label') {
2527
+ const wh = document.createElement('div');
2528
+ wh.className = 'label-width-handle';
2529
+ wh.title = 'Тяни — ширина (для переноса строк)';
2530
+ el.appendChild(wh);
2531
+ attachLabelWidthResize(el, node, wh);
2532
+ }
2533
+
2517
2534
  const anchor = document.createElement('div');
2518
2535
  anchor.className = 'anchor';
2519
2536
  anchor.title = 'Тяни, чтобы сослаться или сгенерировать ноду со ссылкой';
@@ -746,6 +746,48 @@ async function openGenerateForRef(fromNode, clientX, clientY, forceKind) {
746
746
  }, 50);
747
747
  }
748
748
 
749
+ // Width-only resize для label-нод. Юзер тянет хендл на правом краю —
750
+ // устанавливаем node.width, label-text оборачивается. Высота auto.
751
+ // Двойной клик по хендлу — снять width (вернуть auto-width по тексту).
752
+ function attachLabelWidthResize(el, node, handle) {
753
+ handle.addEventListener('mousedown', e => {
754
+ e.preventDefault();
755
+ e.stopPropagation();
756
+ const startX = e.clientX;
757
+ const startW = el.offsetWidth;
758
+ const labelText = el.querySelector('.label-text');
759
+ const onMove = ev => {
760
+ const dx = (ev.clientX - startX) / state.zoom;
761
+ const newW = Math.max(80, Math.min(2000, startW + dx));
762
+ node.width = Math.round(newW);
763
+ el.style.width = node.width + 'px';
764
+ el.dataset.fixedWidth = '1';
765
+ // Force label-text to fill — иначе inline-block остаётся узким.
766
+ if (labelText) labelText.style.width = '100%';
767
+ renderConnections();
768
+ };
769
+ const onUp = () => {
770
+ document.removeEventListener('mousemove', onMove);
771
+ document.removeEventListener('mouseup', onUp);
772
+ scheduleSave();
773
+ };
774
+ document.addEventListener('mousemove', onMove);
775
+ document.addEventListener('mouseup', onUp);
776
+ });
777
+ // dblclick — сбросить ширину (вернуть auto-size).
778
+ handle.addEventListener('dblclick', e => {
779
+ e.preventDefault();
780
+ e.stopPropagation();
781
+ delete node.width;
782
+ el.style.width = '';
783
+ delete el.dataset.fixedWidth;
784
+ const labelText = el.querySelector('.label-text');
785
+ if (labelText) labelText.style.width = '';
786
+ renderConnections();
787
+ scheduleSave();
788
+ });
789
+ }
790
+
749
791
  function attachResize(el, node, handle) {
750
792
  handle.addEventListener('mousedown', e => {
751
793
  e.preventDefault();
@@ -519,16 +519,34 @@
519
519
  overflow: hidden;
520
520
  }
521
521
  .welcome-card-thumb {
522
+ /* position:absolute + inset:0 — заполняем всю карточку (4:3).
523
+ БАДЖИ внутри (cloud/bg) тоже absolute → этот thumb работает как
524
+ positioning context для них. РАНЬШЕ ниже было `.welcome-card-thumb
525
+ { position: relative; }` — оно переопределяло absolute → thumb
526
+ shrink'ался до размера контента (символ «+» / «☁» / «🎬») и
527
+ прибивался к верху карточки. Слились в один rule. */
522
528
  position: absolute; inset: 0;
523
529
  background: #1a1a1a;
524
530
  display: flex; align-items: center; justify-content: center;
531
+ /* padding-bottom компенсирует overlay-meta снизу (название+дата
532
+ занимают ~60px). Без padding-bottom иконка центрируется по всей
533
+ карте → визуально кажется что она «под подписью». С padding'ом
534
+ центрируется в видимой области выше meta — иконка чуть выше. */
535
+ padding-bottom: 56px;
525
536
  color: #444; font-size: 32px;
526
537
  overflow: hidden; /* страховка чтобы img/badge не выезжали */
527
538
  }
528
- .welcome-card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
539
+ /* Для thumb с НАСТОЯЩЕЙ обложкой (img внутри) padding не нужен
540
+ img:absolute заполняет thumb полностью независимо от padding. */
541
+ /* img заполняет thumb ПОЛНОСТЬЮ (игнорирует padding-bottom родителя
542
+ который сдвигает иконку «+/☁/🎬» вверх — для картинок-обложек он
543
+ не нужен). position:absolute + inset:0 — bypass content-box. */
544
+ .welcome-card-thumb img {
545
+ position: absolute; inset: 0;
546
+ width: 100%; height: 100%; object-fit: cover; display: block;
547
+ }
529
548
  /* ☁-бейдж в углу обложки облачного проекта. Делает cloud/folder-проекты
530
549
  визуально различимыми в общем grid'е (без необходимости второго рядa). */
531
- .welcome-card-thumb { position: relative; }
532
550
  .welcome-card-cloud-badge {
533
551
  position: absolute; right: 6px; bottom: 6px;
534
552
  background: rgba(20, 30, 50, 0.85); color: #9cf;
@@ -764,6 +782,32 @@
764
782
  }
765
783
  .node.label-node:hover .resize-handle,
766
784
  .node.label-node:focus-within .resize-handle { opacity: 1; }
785
+ /* Width-only handle на правом краю — отдельно от font-size handle.
786
+ Тянуть → меняется ширина label, текст переносится. dblclick →
787
+ сбросить ширину обратно к auto. */
788
+ .node.label-node .label-width-handle {
789
+ position: absolute;
790
+ right: -3px; top: 50%;
791
+ transform: translateY(-50%);
792
+ width: 6px; height: 36px;
793
+ background: rgba(90,168,255,0.7);
794
+ border: 1px solid rgba(255,255,255,0.35);
795
+ border-radius: 3px;
796
+ cursor: ew-resize;
797
+ opacity: 0; transition: opacity 0.15s;
798
+ z-index: 11;
799
+ }
800
+ .node.label-node:hover .label-width-handle,
801
+ .node.label-node:focus-within .label-width-handle { opacity: 1; }
802
+ /* Когда label имеет explicit width (data-fixed-width="1") — внутренний
803
+ label-text должен ЗАПОЛНЯТЬ родителя, иначе inline-block остаётся
804
+ узким и текст не переносится. Сбрасываем min/max-width inner'a. */
805
+ .node.label-node[data-fixed-width="1"] .label-text {
806
+ width: 100%;
807
+ min-width: 0;
808
+ max-width: none;
809
+ box-sizing: border-box;
810
+ }
767
811
 
768
812
  /* Body — wrapper без визуального вклада. */
769
813
  .node.label-node .node-body {