kingkont 0.7.86 → 0.7.88

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.86",
3
+ "version": "0.7.88",
4
4
  "description": "KingKont · Chatium — нод-редактор сцен с AI-генерацией (картинки/видео/голос/SFX/музыка/текст)",
5
5
  "main": "main.js",
6
6
  "bin": {
package/renderer/board.js CHANGED
@@ -1111,25 +1111,35 @@ function askName(title, placeholder = '', initialValue = '', opts = {}) {
1111
1111
  // Возвращает выбранную строку или null если юзер закрыл.
1112
1112
  // window.prompt() в Electron renderer молча возвращает null — поэтому
1113
1113
  // нельзя использовать его для select-подобных диалогов.
1114
- function askChoice(title, options, currentValue) {
1114
+ // askChoice(title, options, currentValue, opts?)
1115
+ // opts:
1116
+ // vertical: bool — стэкаем кнопки вертикально (по умолчанию — flex-wrap)
1117
+ // optionStyles: { [opt]: 'css-string' } — кастомный CSS на кнопку (для preview-эффектов,
1118
+ // напр. рендер опции «Карандашом» в самом шрифте Karandashom)
1119
+ function askChoice(title, options, currentValue, opts = {}) {
1115
1120
  return new Promise(resolve => {
1116
1121
  const overlay = document.createElement('div');
1117
1122
  overlay.className = 'modal';
1118
1123
  overlay.style.cssText = 'position:fixed; inset:0; background:rgba(0,0,0,0.55); display:flex; align-items:center; justify-content:center; z-index:9999;';
1119
1124
  const box = document.createElement('div');
1120
- box.style.cssText = 'background:#222; border:1px solid #444; border-radius:8px; padding:18px 20px; min-width:360px; box-shadow:0 8px 32px rgba(0,0,0,0.6);';
1125
+ box.style.cssText = 'background:#222; border:1px solid #444; border-radius:8px; padding:18px 20px; min-width:360px; max-height:80vh; overflow-y:auto; box-shadow:0 8px 32px rgba(0,0,0,0.6);';
1121
1126
  const h = document.createElement('h3');
1122
1127
  h.textContent = title;
1123
1128
  h.style.cssText = 'margin:0 0 12px; font-size:14px; color:#e0e0e0;';
1124
1129
  box.append(h);
1125
1130
  const grid = document.createElement('div');
1126
- grid.style.cssText = 'display:flex; flex-wrap:wrap; gap:6px; margin-bottom:14px;';
1131
+ grid.style.cssText = opts.vertical
1132
+ ? 'display:flex; flex-direction:column; gap:6px; margin-bottom:14px;'
1133
+ : 'display:flex; flex-wrap:wrap; gap:6px; margin-bottom:14px;';
1127
1134
  const close = (val) => { overlay.remove(); resolve(val); };
1128
1135
  for (const opt of options) {
1129
1136
  const b = document.createElement('button');
1130
1137
  b.textContent = opt;
1131
- b.style.cssText = 'padding:6px 12px; background:#1a1a1a; color:#e0e0e0; border:1px solid #444; border-radius:4px; font-size:13px; cursor:pointer;' +
1132
- (opt === currentValue ? 'border-color:#7c3aed; background:#2a1a3a;' : '');
1138
+ let css = 'padding:6px 12px; background:#1a1a1a; color:#e0e0e0; border:1px solid #444; border-radius:4px; font-size:13px; cursor:pointer;';
1139
+ if (opts.vertical) css += 'text-align:left;';
1140
+ if (opt === currentValue) css += 'border-color:#7c3aed; background:#2a1a3a;';
1141
+ if (opts.optionStyles?.[opt]) css += opts.optionStyles[opt];
1142
+ b.style.cssText = css;
1133
1143
  b.addEventListener('click', () => close(opt));
1134
1144
  grid.append(b);
1135
1145
  }
@@ -1781,7 +1791,18 @@ function showNodeContextMenu(node, clientX, clientY) {
1781
1791
  const ts = node.textStyle;
1782
1792
  const curFont = LABEL_FONTS.find(f => f.id === ts.fontFamily) || LABEL_FONTS[0];
1783
1793
  add(`🔤 Шрифт: ${curFont.label}`, async () => {
1784
- const choice = await askChoice('Шрифт label-ноды', LABEL_FONTS.map(f => f.label), curFont.label);
1794
+ // Каждая опция в picker'е рендерится в собственно её шрифте — юзер
1795
+ // сразу видит как «Карандашом» / «Прописью» / «Кистью» выглядят.
1796
+ const fontStyles = {};
1797
+ for (const f of LABEL_FONTS) {
1798
+ fontStyles[f.label] = `font-family: ${getLabelFontFamily(f.id)}; font-size: 22px; padding: 8px 14px; line-height: 1.3;`;
1799
+ }
1800
+ const choice = await askChoice(
1801
+ 'Шрифт label-ноды',
1802
+ LABEL_FONTS.map(f => f.label),
1803
+ curFont.label,
1804
+ { vertical: true, optionStyles: fontStyles },
1805
+ );
1785
1806
  if (!choice) return;
1786
1807
  const picked = LABEL_FONTS.find(f => f.label === choice);
1787
1808
  if (!picked) return;
@@ -1790,7 +1811,19 @@ function showNodeContextMenu(node, clientX, clientY) {
1790
1811
  await refreshNodeDOM(node.id);
1791
1812
  });
1792
1813
  add(`📏 Размер: ${ts.fontSize || 32}px`, async () => {
1793
- const choice = await askChoice('Размер шрифта', LABEL_FONT_SIZES.map(s => s + 'px'), (ts.fontSize || 32) + 'px');
1814
+ // Каждый размер в picker'е рендерится в реальном размере лимитом),
1815
+ // чтобы юзер чувствовал «насколько крупно». Также вертикально.
1816
+ const sizeStyles = {};
1817
+ for (const s of LABEL_FONT_SIZES) {
1818
+ const previewSize = Math.min(s, 36); // не растим окно — лимит превью
1819
+ sizeStyles[s + 'px'] = `font-size: ${previewSize}px; line-height: 1.2; padding: 6px 14px;`;
1820
+ }
1821
+ const choice = await askChoice(
1822
+ 'Размер шрифта',
1823
+ LABEL_FONT_SIZES.map(s => s + 'px'),
1824
+ (ts.fontSize || 32) + 'px',
1825
+ { vertical: true, optionStyles: sizeStyles },
1826
+ );
1794
1827
  if (!choice) return;
1795
1828
  const n = parseInt(choice, 10);
1796
1829
  if (Number.isFinite(n)) {
@@ -85,6 +85,27 @@ function applyLabelStyle(ed, style) {
85
85
  ed.dataset.font = style.fontFamily || 'default';
86
86
  }
87
87
 
88
+ // Соответствие label-font-id → CSS font-family stack. Используется для
89
+ // preview-кнопок в picker'e шрифта (askChoice показывает каждую опцию
90
+ // в собственно её шрифте, чтобы юзер видел как выглядит). Семейства
91
+ // должны совпадать с теми, что заданы в .label-text[data-font="..."]
92
+ // в renderer/styles.css.
93
+ function getLabelFontFamily(id) {
94
+ const map = {
95
+ default: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
96
+ pencil: "'KK Neucha', 'Marker Felt', 'Comic Sans MS', cursive",
97
+ handwritten: "'KK Caveat', 'Caveat', 'Marker Felt', cursive",
98
+ brush: "'KK BadScript', 'Bad Script', 'Snell Roundhand', cursive",
99
+ marker: "'KK Pacifico', 'Pacifico', 'Marker Felt', cursive",
100
+ display: "'KK Lobster', 'Lobster', Georgia, serif",
101
+ elegant: "'KK YesevaOne', 'Yeseva One', Georgia, serif",
102
+ rounded: "'KK Comfortaa', 'Comfortaa', -apple-system, sans-serif",
103
+ serif: "'KK PTSerif', 'PT Serif', Georgia, serif",
104
+ mono: "'KK PTMono', 'PT Mono', ui-monospace, Menlo, monospace",
105
+ };
106
+ return map[id] || map.default;
107
+ }
108
+
88
109
  function renderLabelNodeBody(node, body) {
89
110
  if (!node.textStyle) node.textStyle = { ...LABEL_DEFAULT_STYLE };
90
111
 
@@ -706,6 +727,12 @@ function attachDrag(el, node) {
706
727
  // Edit-режим label — не таскаем (контекст редактирования текста).
707
728
  if (e.currentTarget?.classList?.contains('label-text') && e.currentTarget.isContentEditable) return;
708
729
  if (e.target.closest('.delete')) return;
730
+ // Источник mousedown'а: header или label-text. Для label-text НЕ
731
+ // вызываем preventDefault и canvas.appendChild на mousedown — они
732
+ // ломают браузерный счётчик кликов и dblclick на label не срабатывает.
733
+ // Z-promote и preventDefault выполнятся ниже, в onMove, когда drag
734
+ // реально начнётся (перешёл порог 4px).
735
+ const sourceIsLabelText = e.currentTarget?.classList?.contains('label-text');
709
736
  // Multi-select c модификаторами — без drag
710
737
  if (e.metaKey || e.ctrlKey || e.shiftKey) {
711
738
  e.preventDefault();
@@ -720,16 +747,18 @@ function attachDrag(el, node) {
720
747
  state.selectedNodeIds.add(node.id);
721
748
  renderSelection();
722
749
  }
723
- e.preventDefault();
750
+ if (!sourceIsLabelText) e.preventDefault();
724
751
  const startX = e.clientX, startY = e.clientY;
725
752
  const origX = node.x, origY = node.y;
726
753
  el.classList.add('selected');
727
- canvas.appendChild(el);
728
754
  const arr = state.currentBoard.metadata.nodes;
729
- const idx = arr.indexOf(node);
730
- if (idx >= 0 && idx < arr.length - 1) {
731
- arr.splice(idx, 1);
732
- arr.push(node);
755
+ if (!sourceIsLabelText) {
756
+ canvas.appendChild(el);
757
+ const idx = arr.indexOf(node);
758
+ if (idx >= 0 && idx < arr.length - 1) {
759
+ arr.splice(idx, 1);
760
+ arr.push(node);
761
+ }
733
762
  }
734
763
 
735
764
  // Multi-drag: если выделено несколько нод и кликнули по одной из них —
@@ -759,6 +788,18 @@ function attachDrag(el, node) {
759
788
  const onMove = ev => {
760
789
  const dx = ev.clientX - startX, dy = ev.clientY - startY;
761
790
  if (!dragInitialized && Math.hypot(dx, dy) < 4) return;
791
+ // Первый раз перешли порог — для label-text z-promote отложен сюда,
792
+ // чтобы не ломать dblclick. Также гасим случайно стартовавший
793
+ // text-selection в read-режиме.
794
+ if (!dragInitialized && sourceIsLabelText) {
795
+ canvas.appendChild(el);
796
+ const idx = arr.indexOf(node);
797
+ if (idx >= 0 && idx < arr.length - 1) {
798
+ arr.splice(idx, 1);
799
+ arr.push(node);
800
+ }
801
+ try { window.getSelection()?.removeAllRanges(); } catch {}
802
+ }
762
803
  dragInitialized = true;
763
804
  // Проверяем — курсор над таймлайном?
764
805
  const tlTrackEl = document.elementsFromPoint(ev.clientX, ev.clientY)
@@ -526,6 +526,17 @@
526
526
  природу» ноды, в read-mode сигнал что dblclick → редактировать. */
527
527
  .node.label-node .label-text { cursor: text; }
528
528
  .node.label-node:hover .label-text:not([contenteditable]) { cursor: pointer; }
529
+ /* В read-режиме блокируем text-selection — чтобы single-click не выделял
530
+ слово, и mousedown без preventDefault (для работы dblclick) не давал
531
+ браузеру стартовать selection-drag. В edit-режиме выделение разрешено. */
532
+ .node.label-node .label-text:not([contenteditable]) {
533
+ user-select: none;
534
+ -webkit-user-select: none;
535
+ }
536
+ .node.label-node .label-text[contenteditable] {
537
+ user-select: text;
538
+ -webkit-user-select: text;
539
+ }
529
540
  /* === Label-шрифты ===
530
541
  Все варианты — bundled woff2 (см. assets/fonts/), кириллица поддержана. */
531
542
  .node.label-node .label-text[data-font="pencil"] {