kingkont 0.20.10 → 0.20.12
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 +39 -15
- package/renderer/generate.js +28 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kingkont",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.12",
|
|
4
4
|
"description": "KingKont \u00b7 Chatium \u2014 \u043d\u043e\u0434-\u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u0441\u0446\u0435\u043d \u0441 AI-\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0435\u0439 (\u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438/\u0432\u0438\u0434\u0435\u043e/\u0433\u043e\u043b\u043e\u0441/SFX/\u043c\u0443\u0437\u044b\u043a\u0430/\u0442\u0435\u043a\u0441\u0442)",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"bin": {
|
package/renderer/board.js
CHANGED
|
@@ -3144,6 +3144,26 @@ $('newLocation').addEventListener('click', async () => {
|
|
|
3144
3144
|
}
|
|
3145
3145
|
});
|
|
3146
3146
|
// =================== Board (универсально для серии и персонажа) ===================
|
|
3147
|
+
// Выделить ноду + проскроллить view так, чтобы она была в центре viewport'а.
|
|
3148
|
+
// Юзер: «после создания ноды (любой) нужно её выделять и перемещаться к ней».
|
|
3149
|
+
// Используется после addText/addLabel/genNode'а — иначе если spot оказался
|
|
3150
|
+
// за пределами текущего scroll'а, юзер не понимает что что-то создалось.
|
|
3151
|
+
function selectAndPanToNode(node) {
|
|
3152
|
+
if (!node || !state.currentBoard) return;
|
|
3153
|
+
try {
|
|
3154
|
+
clearSelection();
|
|
3155
|
+
state.selectedNodeIds.add(node.id);
|
|
3156
|
+
renderSelection();
|
|
3157
|
+
} catch {}
|
|
3158
|
+
const { padX, padY } = _getFramePadding();
|
|
3159
|
+
const z = state.zoom || 1;
|
|
3160
|
+
const w = node.width || 280, h = node.height || 220;
|
|
3161
|
+
const cx = node.x + w / 2;
|
|
3162
|
+
const cy = node.y + h / 2;
|
|
3163
|
+
canvasWrap.scrollLeft = cx * z + padX - canvasWrap.clientWidth / 2;
|
|
3164
|
+
canvasWrap.scrollTop = cy * z + padY - canvasWrap.clientHeight / 2;
|
|
3165
|
+
}
|
|
3166
|
+
window.selectAndPanToNode = selectAndPanToNode;
|
|
3147
3167
|
// Если ни одна нода не попадает в видимую область canvas-wrap, скроллим
|
|
3148
3168
|
// view на центр bbox всех нод. Используется после selectBoard / openFilm
|
|
3149
3169
|
// — типичный кейс: юзер открывает старый проект, scroll был сохранён в
|
|
@@ -3155,27 +3175,31 @@ function _autoScrollToNodesIfHidden(padX, padY) {
|
|
|
3155
3175
|
const nodes = board.metadata.nodes || [];
|
|
3156
3176
|
if (!nodes.length)
|
|
3157
3177
|
return;
|
|
3158
|
-
// Считаем bbox в canvas-coords.
|
|
3159
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
3160
|
-
for (const n of nodes) {
|
|
3161
|
-
const w = n.width || 280, h = n.height || 220;
|
|
3162
|
-
minX = Math.min(minX, n.x);
|
|
3163
|
-
minY = Math.min(minY, n.y);
|
|
3164
|
-
maxX = Math.max(maxX, n.x + w);
|
|
3165
|
-
maxY = Math.max(maxY, n.y + h);
|
|
3166
|
-
}
|
|
3167
3178
|
const z = state.zoom || 1;
|
|
3168
|
-
// Видимое окно в canvas-coords (viewport / zoom).
|
|
3169
3179
|
const wrap = canvasWrap;
|
|
3180
|
+
// Видимое окно в canvas-coords (viewport / zoom).
|
|
3170
3181
|
const viewLeft = (wrap.scrollLeft - padX) / z;
|
|
3171
3182
|
const viewTop = (wrap.scrollTop - padY) / z;
|
|
3172
3183
|
const viewRight = viewLeft + wrap.clientWidth / z;
|
|
3173
3184
|
const viewBottom = viewTop + wrap.clientHeight / z;
|
|
3174
|
-
//
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3185
|
+
// Раньше проверяли пересечение viewport'а с BBOX всех нод — для нод
|
|
3186
|
+
// в углах (0,0) и (5000,5000) bbox покрывал ВЕСЬ canvas → false-positive
|
|
3187
|
+
// «не trigger'им». Теперь спрашиваем: есть ли ХОТЯ БЫ ОДНА нода хоть
|
|
3188
|
+
// частично внутри viewport'а? Если нет — центрируем на bbox-у нод.
|
|
3189
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
3190
|
+
let anyVisible = false;
|
|
3191
|
+
for (const n of nodes) {
|
|
3192
|
+
const w = n.width || 280, h = n.height || 220;
|
|
3193
|
+
const nL = n.x, nR = n.x + w, nT = n.y, nB = n.y + h;
|
|
3194
|
+
minX = Math.min(minX, nL);
|
|
3195
|
+
minY = Math.min(minY, nT);
|
|
3196
|
+
maxX = Math.max(maxX, nR);
|
|
3197
|
+
maxY = Math.max(maxY, nB);
|
|
3198
|
+
if (!(nR < viewLeft || nL > viewRight || nB < viewTop || nT > viewBottom)) {
|
|
3199
|
+
anyVisible = true;
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
if (anyVisible) return;
|
|
3179
3203
|
const cx = (minX + maxX) / 2;
|
|
3180
3204
|
const cy = (minY + maxY) / 2;
|
|
3181
3205
|
wrap.scrollLeft = cx * z + padX - wrap.clientWidth / 2;
|
package/renderer/generate.js
CHANGED
|
@@ -228,9 +228,18 @@ document.querySelectorAll('#addMenu button').forEach(btn => {
|
|
|
228
228
|
|
|
229
229
|
async function addTextAt(pos) {
|
|
230
230
|
if (!state.currentBoard) return;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
231
|
+
let x, y;
|
|
232
|
+
if (pos) {
|
|
233
|
+
const rect = canvas.getBoundingClientRect();
|
|
234
|
+
x = (pos.clientX - rect.left) / state.zoom;
|
|
235
|
+
y = (pos.clientY - rect.top) / state.zoom;
|
|
236
|
+
} else {
|
|
237
|
+
// Без позиции (клик по «Написать» в toolbar) — кладём в свободное место
|
|
238
|
+
// ВИДИМОЙ зоны. Раньше использовали scrollLeft+80 без учёта canvas-padding
|
|
239
|
+
// → нода уезжала за вьюпорт (padX=2000 в canvas-frame).
|
|
240
|
+
const spot = findFreeSpot();
|
|
241
|
+
x = spot.x; y = spot.y;
|
|
242
|
+
}
|
|
234
243
|
const dir = await getOrCreateBoardSubdir(state.currentBoard.handle, 'texts');
|
|
235
244
|
const mdName = await uniqueName(dir, 'text.md');
|
|
236
245
|
await writeFile(dir, mdName, '');
|
|
@@ -238,6 +247,7 @@ async function addTextAt(pos) {
|
|
|
238
247
|
state.currentBoard.metadata.nodes.push(node);
|
|
239
248
|
canvas.appendChild(await createNodeEl(node));
|
|
240
249
|
scheduleSave();
|
|
250
|
+
if (typeof selectAndPanToNode === 'function') selectAndPanToNode(node);
|
|
241
251
|
}
|
|
242
252
|
|
|
243
253
|
// Label-нода: лёгкая надпись поверх холста, без файла. Текст хранится
|
|
@@ -245,9 +255,15 @@ async function addTextAt(pos) {
|
|
|
245
255
|
// удаляется при сериализации).
|
|
246
256
|
async function addLabelAt(pos) {
|
|
247
257
|
if (!state.currentBoard) return;
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
258
|
+
let x, y;
|
|
259
|
+
if (pos) {
|
|
260
|
+
const rect = canvas.getBoundingClientRect();
|
|
261
|
+
x = (pos.clientX - rect.left) / state.zoom;
|
|
262
|
+
y = (pos.clientY - rect.top) / state.zoom;
|
|
263
|
+
} else {
|
|
264
|
+
const spot = findFreeSpot();
|
|
265
|
+
x = spot.x; y = spot.y;
|
|
266
|
+
}
|
|
251
267
|
const node = {
|
|
252
268
|
id: crypto.randomUUID(),
|
|
253
269
|
type: 'label',
|
|
@@ -261,6 +277,7 @@ async function addLabelAt(pos) {
|
|
|
261
277
|
const el = await createNodeEl(node);
|
|
262
278
|
canvas.appendChild(el);
|
|
263
279
|
scheduleSave();
|
|
280
|
+
if (typeof selectAndPanToNode === 'function') selectAndPanToNode(node);
|
|
264
281
|
// Сразу входим в режим редактирования и выделяем весь текст —
|
|
265
282
|
// юзер начинает печатать, и «Подпись» заменяется на введённое.
|
|
266
283
|
setTimeout(() => {
|
|
@@ -524,6 +541,7 @@ $('textGenSubmit').addEventListener('click', async () => {
|
|
|
524
541
|
canvas.appendChild(await createNodeEl(node));
|
|
525
542
|
if (pendingFrom) addConnection(pendingFrom, node.id);
|
|
526
543
|
scheduleSave();
|
|
544
|
+
if (typeof selectAndPanToNode === 'function') selectAndPanToNode(node);
|
|
527
545
|
$('textGenModal').classList.add('hidden');
|
|
528
546
|
// Фоновая генерация — модалку уже закрыли.
|
|
529
547
|
runTextJob(node, resolvedPrompt, model, state.currentBoard.handle, state.currentBoard.key, imageRefs);
|
|
@@ -731,6 +749,7 @@ $('sfxSubmit').addEventListener('click', async () => {
|
|
|
731
749
|
state.currentBoard.metadata.nodes.push(node);
|
|
732
750
|
canvas.appendChild(await createNodeEl(node));
|
|
733
751
|
scheduleSave();
|
|
752
|
+
if (typeof selectAndPanToNode === 'function') selectAndPanToNode(node);
|
|
734
753
|
runSfxJob(node, text, durationSeconds, state.currentBoard.handle, state.currentBoard.key);
|
|
735
754
|
});
|
|
736
755
|
|
|
@@ -863,6 +882,7 @@ $('musicSubmit').addEventListener('click', async () => {
|
|
|
863
882
|
state.currentBoard.metadata.nodes.push(node);
|
|
864
883
|
canvas.appendChild(await createNodeEl(node));
|
|
865
884
|
scheduleSave();
|
|
885
|
+
if (typeof selectAndPanToNode === 'function') selectAndPanToNode(node);
|
|
866
886
|
runMusicJob(node, prompt, durationMs, state.currentBoard.handle, state.currentBoard.key);
|
|
867
887
|
});
|
|
868
888
|
|
|
@@ -1633,6 +1653,7 @@ $('genSubmit').addEventListener('click', async () => {
|
|
|
1633
1653
|
addConnection(state.pendingConnectionFrom, node.id);
|
|
1634
1654
|
state.pendingConnectionFrom = null;
|
|
1635
1655
|
}
|
|
1656
|
+
if (typeof selectAndPanToNode === 'function') selectAndPanToNode(node);
|
|
1636
1657
|
if (saveOnly) {
|
|
1637
1658
|
scheduleSave();
|
|
1638
1659
|
$('genModal').classList.add('hidden');
|
|
@@ -1785,6 +1806,7 @@ $('genSubmit').addEventListener('click', async () => {
|
|
|
1785
1806
|
if (saveOnly) node.status = 'draft';
|
|
1786
1807
|
state.currentBoard.metadata.nodes.push(node);
|
|
1787
1808
|
canvas.appendChild(await createNodeEl(node));
|
|
1809
|
+
if (typeof selectAndPanToNode === 'function') selectAndPanToNode(node);
|
|
1788
1810
|
|
|
1789
1811
|
// Async auto-name через LLM. НЕ блокируем — нода уже на холсте.
|
|
1790
1812
|
// Когда LLM ответит — обновим node.name + DOM. Дедуп через uniqueNodeName.
|