clay-server 5.3.0-beta.5 → 5.3.0-beta.7
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/lib/clay-product-control-tools.js +2 -2
- package/lib/clay-product-control.js +1 -1
- package/lib/clay-screen-controls.js +1 -0
- package/lib/clay-split-view-awareness.js +5 -2
- package/lib/clay-split-view-controls.js +14 -7
- package/lib/clay-split-view-input.js +5 -4
- package/lib/clay-split-view-render-proof.js +14 -4
- package/lib/clay-view-workspace-input.js +71 -0
- package/lib/project-admin-overview.js +22 -11
- package/lib/project-background-tasks.js +76 -0
- package/lib/project-connection.js +1 -1
- package/lib/project-file-history.js +11 -9
- package/lib/project-file-watch.js +26 -7
- package/lib/project-filesystem.js +5 -4
- package/lib/project-sessions.js +12 -5
- package/lib/project-shared-browser.js +7 -3
- package/lib/project-split-view-document.js +16 -2
- package/lib/project-split-views.js +27 -297
- package/lib/project-user-message.js +4 -3
- package/lib/project-vendor-login.js +2 -2
- package/lib/project-view-input.js +11 -0
- package/lib/project-view-pair.js +26 -0
- package/lib/project-view-store.js +348 -0
- package/lib/project-view-terminal-binding.js +16 -0
- package/lib/project-view-workspace.js +126 -0
- package/lib/project-worker-conversation.js +5 -0
- package/lib/project.js +1 -0
- package/lib/public/app.js +9 -0
- package/lib/public/css/contextual-clay.css +2 -1
- package/lib/public/css/issues.css +3 -0
- package/lib/public/css/mate-conversation.css +1 -1
- package/lib/public/css/pane.css +5 -1
- package/lib/public/css/pending-message-queue.css +24 -19
- package/lib/public/css/project-admin-overview.css +42 -13
- package/lib/public/css/right-workbench.css +0 -1
- package/lib/public/css/scheduled-tasks.css +7 -0
- package/lib/public/css/shared-browser.css +31 -8
- package/lib/public/css/split-view-layout.css +129 -10
- package/lib/public/css/tool-pane.css +124 -0
- package/lib/public/index.html +6 -6
- package/lib/public/modules/agent-message-bubbles.js +96 -0
- package/lib/public/modules/app-header.js +6 -0
- package/lib/public/modules/app-messages.js +26 -9
- package/lib/public/modules/background-task-previews.js +8 -0
- package/lib/public/modules/background-task-viewer.js +97 -0
- package/lib/public/modules/background-tasks-ui.js +34 -125
- package/lib/public/modules/chat-bubble-renderer.js +11 -5
- package/lib/public/modules/clay-file-links.js +2 -1
- package/lib/public/modules/clay-split-view-screen.js +6 -3
- package/lib/public/modules/clay-tool-pane-screen.js +35 -0
- package/lib/public/modules/contextual-clay-geometry.js +7 -3
- package/lib/public/modules/file-history-snapshot.js +12 -0
- package/lib/public/modules/file-pane-entrypoints.js +55 -0
- package/lib/public/modules/file-pane-history.js +78 -0
- package/lib/public/modules/file-pane-instances.js +140 -0
- package/lib/public/modules/file-pane-links.js +41 -0
- package/lib/public/modules/file-pane-render.js +48 -0
- package/lib/public/modules/file-pane-review.js +26 -0
- package/lib/public/modules/file-pane-toolbar.js +54 -0
- package/lib/public/modules/file-viewer-source.js +70 -0
- package/lib/public/modules/filebrowser.js +43 -82
- package/lib/public/modules/git-panel.js +5 -0
- package/lib/public/modules/issues.js +3 -4
- package/lib/public/modules/linear-panel.js +2 -2
- package/lib/public/modules/mate-knowledge-workbench.js +10 -6
- package/lib/public/modules/notifications.js +4 -1
- package/lib/public/modules/pane-activation.js +7 -3
- package/lib/public/modules/pane-binding.js +5 -1
- package/lib/public/modules/pane-browser-bridge.js +6 -2
- package/lib/public/modules/pane-browser-transport.js +110 -0
- package/lib/public/modules/pane-header-tooltips.js +32 -0
- package/lib/public/modules/pending-message-queue-drag.js +69 -0
- package/lib/public/modules/pending-message-queue.js +3 -33
- package/lib/public/modules/permission-control.js +6 -5
- package/lib/public/modules/plantuml-preview.js +12 -12
- package/lib/public/modules/project-admin-overview.js +125 -10
- package/lib/public/modules/project-grouping.js +9 -0
- package/lib/public/modules/right-workbench.js +34 -15
- package/lib/public/modules/scheduled-tasks.js +7 -6
- package/lib/public/modules/server-settings-controls.js +33 -4
- package/lib/public/modules/session-folder-toolbar.js +1 -1
- package/lib/public/modules/session-tool-item.js +86 -0
- package/lib/public/modules/settings-select.js +2 -1
- package/lib/public/modules/shared-browser-tabs.js +37 -21
- package/lib/public/modules/shared-browser.js +69 -9
- package/lib/public/modules/sidebar-projects.js +1 -10
- package/lib/public/modules/split-pane-renderer.js +1 -0
- package/lib/public/modules/split-session-empty-pane.js +47 -0
- package/lib/public/modules/split-view-create-menu.js +74 -65
- package/lib/public/modules/split-view-drag-preview.js +90 -0
- package/lib/public/modules/split-view-layout-client.js +1 -1
- package/lib/public/modules/split-view-layout-scope.js +4 -0
- package/lib/public/modules/split-view-picker-actions.js +71 -0
- package/lib/public/modules/split-view-picker-render.js +81 -0
- package/lib/public/modules/split-view-presentation-drag.js +36 -13
- package/lib/public/modules/split-view-presentation-drop.js +21 -18
- package/lib/public/modules/split-view-presentation-renderer.js +22 -17
- package/lib/public/modules/split-view-presentations.js +25 -33
- package/lib/public/modules/split-view-worker-activity.js +2 -2
- package/lib/public/modules/split-view-worker-transcript.js +70 -33
- package/lib/public/modules/split-view-workers.js +6 -4
- package/lib/public/modules/split-view.js +9 -2
- package/lib/public/modules/sticky-notes-browser.js +2 -2
- package/lib/public/modules/terminal-viewport.js +14 -0
- package/lib/public/modules/terminal.js +49 -23
- package/lib/public/modules/theme-palette.js +177 -0
- package/lib/public/modules/theme.js +2 -180
- package/lib/public/modules/tool-pane-chrome.js +88 -0
- package/lib/public/modules/tool-pane-drag.js +134 -0
- package/lib/public/modules/tool-pane-layout.js +95 -0
- package/lib/public/modules/tool-pane-legacy.js +60 -0
- package/lib/public/modules/tool-pane-tree.js +51 -0
- package/lib/public/modules/tools.js +5 -89
- package/lib/public/modules/tooltip.js +32 -6
- package/lib/public/modules/view-legacy-coordinates.js +16 -0
- package/lib/public/modules/view-mutations.js +24 -0
- package/lib/public/modules/view-pair-presentation.js +74 -0
- package/lib/public/modules/view-pair-validation.js +12 -0
- package/lib/public/modules/view-presentation-state.js +37 -0
- package/lib/public/modules/view-workspace-controller.js +210 -0
- package/lib/public/modules/view-workspace-state.js +103 -0
- package/lib/public/modules/view-workspace-validation.js +47 -0
- package/lib/public/modules/wireframe-workbench.js +13 -2
- package/lib/public/modules/workbench-layout-preference.js +12 -5
- package/lib/public/modules/workbench-panels.js +21 -0
- package/lib/public/modules/worker-message-previews.js +39 -0
- package/lib/public/pane-assets/app-5W6KJXK5.js +1 -0
- package/lib/public/pane-assets/chunk-2IJUTAB6.js +2 -0
- package/lib/public/pane-assets/chunk-6QFLYOVY.js +1 -0
- package/lib/public/pane-assets/chunk-7OVNC5HT.js +1 -0
- package/lib/public/pane-assets/chunk-BQN7B62H.js +3 -0
- package/lib/public/pane-assets/chunk-EMLN2Y7L.js +1 -0
- package/lib/public/pane-assets/chunk-ENPAHXLB.js +1 -0
- package/lib/public/pane-assets/chunk-EPBGGZRT.js +1 -0
- package/lib/public/pane-assets/chunk-EYTE56IA.js +1 -0
- package/lib/public/pane-assets/chunk-FQTNR4ZS.js +1 -0
- package/lib/public/pane-assets/chunk-JIV7O5GN.js +1 -0
- package/lib/public/pane-assets/chunk-L6LSA5WI.js +1 -0
- package/lib/public/pane-assets/chunk-M57UIGZV.js +1 -0
- package/lib/public/pane-assets/chunk-MGTFTEJP.js +1 -0
- package/lib/public/pane-assets/chunk-MMG6MYCN.js +1 -0
- package/lib/public/pane-assets/chunk-MZLMEXMW.js +1 -0
- package/lib/public/pane-assets/chunk-OG3E5SVA.js +166 -0
- package/lib/public/pane-assets/chunk-R7QVVFUG.js +1 -0
- package/lib/public/pane-assets/chunk-RGAFZ3T2.js +1 -0
- package/lib/public/pane-assets/chunk-RNDRURMF.js +1 -0
- package/lib/public/pane-assets/chunk-SJOT74VW.js +2 -0
- package/lib/public/pane-assets/chunk-SJTGHIN5.js +1 -0
- package/lib/public/pane-assets/chunk-SLMAFKI2.js +1 -0
- package/lib/public/pane-assets/chunk-U3I5KY3B.js +1 -0
- package/lib/public/pane-assets/chunk-UPXU2IPC.js +1 -0
- package/lib/public/pane-assets/chunk-VEBYD7TL.js +1 -0
- package/lib/public/pane-assets/chunk-VZ2OKDAO.js +1 -0
- package/lib/public/pane-assets/chunk-XBHYNXXT.js +1 -0
- package/lib/public/pane-assets/manifest.json +438 -381
- package/lib/public/pane-assets/style-BSKKK54Q.css +1 -0
- package/lib/public/style.css +1 -0
- package/lib/sdk-message-processor.js +5 -2
- package/lib/server-storage.js +27 -3
- package/lib/server-workbench-layout-settings.js +3 -3
- package/lib/server.js +4 -2
- package/lib/sessions.js +1 -1
- package/lib/split-view-layout.js +11 -7
- package/lib/storage-usage.js +29 -7
- package/lib/terminal-manager.js +2 -2
- package/lib/users-workbench-layout-preferences.js +14 -4
- package/lib/workbench-window-layout.js +30 -0
- package/lib/worker-message-previews.js +50 -0
- package/lib/ws-schema.js +1 -1
- package/lib/yoke/adapters/claude-worker.js +8 -0
- package/lib/yoke/adapters/claude.js +17 -2
- package/lib/yoke/claude-task-reader.js +94 -0
- package/lib/yoke/interface.js +1 -0
- package/package.json +1 -1
- package/lib/public/pane-assets/app-6H5S2Y3Z.js +0 -1
- package/lib/public/pane-assets/chunk-3QV5SDC4.js +0 -1
- package/lib/public/pane-assets/chunk-4FM3U3DW.js +0 -1
- package/lib/public/pane-assets/chunk-7GZH3FZY.js +0 -1
- package/lib/public/pane-assets/chunk-AGJTF5WQ.js +0 -1
- package/lib/public/pane-assets/chunk-DGOFGEUK.js +0 -1
- package/lib/public/pane-assets/chunk-I57URYSL.js +0 -1
- package/lib/public/pane-assets/chunk-NPKRTUNG.js +0 -2
- package/lib/public/pane-assets/chunk-VGA2Y3D2.js +0 -2
- package/lib/public/pane-assets/chunk-WKO3T75E.js +0 -167
- package/lib/public/pane-assets/style-TSRDU54Z.css +0 -1
|
@@ -1,83 +1,92 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Direction menu, then a centered session picker inside the reserved empty pane.
|
|
2
2
|
import { store } from './store.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { retryOptions } from './session-create.js';
|
|
4
|
+
import { pickerReceipt, pickerCurrent, insertPickerSession, createPickerSession } from './split-view-picker-actions.js';
|
|
5
|
+
import { renderPicker, renderPickerResults } from './split-view-picker-render.js';
|
|
6
|
+
import { showToast } from './utils.js';
|
|
6
7
|
|
|
7
8
|
function menuElement() { return document.getElementById('split-view-create-menu'); }
|
|
8
9
|
function close(returnFocus) {
|
|
9
10
|
var menu = menuElement(); if (!menu) return;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
var state = store.get('splitSessionPicker');
|
|
12
|
+
var update = { splitSessionPicker: null, projectSplitViewCreateSelection: [] };
|
|
13
|
+
if (returnFocus && state && pickerCurrent(state.receipt) && !store.get('projectSplitViewPending')) update.projectSplitViewFocusedOrigin = state.priorFocus || null;
|
|
14
|
+
var anchor = menu.clayCreateAnchor; menu.clayCreateAnchor = null;
|
|
15
|
+
menu.hidden = true; store.set(update);
|
|
16
|
+
if (menu.contains(document.activeElement)) document.activeElement.blur();
|
|
17
|
+
if (menu.parentNode !== document.body) document.body.appendChild(menu);
|
|
18
|
+
if (anchor) anchor.setAttribute('aria-expanded', 'false');
|
|
19
|
+
document.dispatchEvent(new CustomEvent('clay-split-picker-layout'));
|
|
20
|
+
if (returnFocus && anchor && anchor.isConnected) anchor.focus();
|
|
13
21
|
}
|
|
14
|
-
function
|
|
15
|
-
var
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
function position() {
|
|
23
|
+
var menu = menuElement(), anchor = menu && menu.clayCreateAnchor;
|
|
24
|
+
if (!menu || menu.hidden) return;
|
|
25
|
+
if (!anchor || !anchor.isConnected) { close(false); return; }
|
|
26
|
+
var state = store.get('splitSessionPicker');
|
|
27
|
+
if (state && state.side) { menu.style.left = ''; menu.style.top = ''; return; }
|
|
28
|
+
var rect = anchor.getBoundingClientRect(), width = menu.offsetWidth;
|
|
29
|
+
menu.style.left = Math.max(8, Math.min(rect.right - width, innerWidth - width - 8)) + 'px';
|
|
30
|
+
menu.style.top = Math.max(8, Math.min(rect.bottom + 5, innerHeight - menu.offsetHeight - 8)) + 'px';
|
|
18
31
|
}
|
|
19
32
|
function open(anchor) {
|
|
33
|
+
var receipt = pickerReceipt(anchor);
|
|
34
|
+
if (!receipt || !pickerCurrent(receipt)) { showToast('Wait for this View to finish loading, then try again.', 'warn'); return; }
|
|
20
35
|
var menu = menuElement();
|
|
36
|
+
if (menu && !menu.hidden && menu.clayCreateAnchor === anchor) { close(true); return; }
|
|
37
|
+
if (menu && !menu.hidden) close(false);
|
|
21
38
|
if (!menu) {
|
|
22
|
-
menu = document.createElement('section'); menu.id = 'split-view-create-menu'; menu.className = 'split-view-arrange';
|
|
23
|
-
menu.
|
|
24
|
-
var header = document.createElement('div'); header.className = 'split-view-arrange-header';
|
|
25
|
-
var title = document.createElement('strong'); title.textContent = 'Arrange sessions'; header.appendChild(title);
|
|
26
|
-
var closeButton = document.createElement('button'); closeButton.type = 'button'; closeButton.setAttribute('aria-label', 'Close arrangement picker'); closeButton.innerHTML = iconHtml('x'); header.appendChild(closeButton); menu.appendChild(header);
|
|
27
|
-
var list = document.createElement('div'); list.className = 'split-view-arrange-grid'; list.setAttribute('role', 'group'); list.setAttribute('aria-label', 'Choose sessions');
|
|
28
|
-
list.style.cssText = 'display:flex;flex-direction:column;max-height:230px;overflow:auto'; menu.appendChild(list);
|
|
29
|
-
var create = document.createElement('button'); create.type = 'button'; create.dataset.createSplitView = 'true'; menu.appendChild(create);
|
|
30
|
-
document.body.appendChild(menu);
|
|
39
|
+
menu = document.createElement('section'); menu.id = 'split-view-create-menu'; menu.className = 'split-view-arrange split-session-picker'; menu.hidden = true;
|
|
40
|
+
menu.setAttribute('role', 'dialog'); menu.setAttribute('aria-label', 'Add a session split'); document.body.appendChild(menu);
|
|
31
41
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
var current = (getCachedSessions() || []).find(function (session) { return session.id === currentId; });
|
|
37
|
-
var selected = activeView ? activeView.sessions.map(function (item) { return item.sessionOriginId; }) :
|
|
38
|
-
current && current.sessionRole !== 'worker' && !current.isMate && !current.isTui ? [current.sessionOriginId] : [];
|
|
39
|
-
var candidates = (getCachedSessions() || []).filter(function (session) {
|
|
40
|
-
if (!session.sessionOriginId || session.sessionRole === 'worker' || session.isMate || session.isTui) return false;
|
|
41
|
-
var owner = viewForOrigin(session.sessionOriginId); return !owner || activeView && owner.id === activeView.id;
|
|
42
|
-
});
|
|
43
|
-
candidates.forEach(function (session) {
|
|
44
|
-
var choice = document.createElement('button'); choice.type = 'button'; choice.dataset.createSessionOrigin = session.sessionOriginId;
|
|
45
|
-
choice.setAttribute('aria-pressed', selected.indexOf(session.sessionOriginId) >= 0 ? 'true' : 'false');
|
|
46
|
-
choice.textContent = session.title || 'Session'; list.appendChild(choice);
|
|
47
|
-
});
|
|
48
|
-
store.set({ projectSplitViewCreateSelection: selected }); refreshCreateButton(menu); menu.hidden = false;
|
|
49
|
-
var rect = anchor.getBoundingClientRect(), width = Math.min(280, window.innerWidth - 16);
|
|
50
|
-
menu.style.left = Math.max(8, Math.min(rect.left, window.innerWidth - width - 8)) + 'px';
|
|
51
|
-
menu.style.top = Math.max(8, Math.min(rect.bottom + 4, window.innerHeight - menu.offsetHeight - 8)) + 'px';
|
|
52
|
-
var first = menu.querySelector('[data-create-session-origin]'); if (first) first.focus();
|
|
42
|
+
receipt.reservationId = 'split-picker-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2);
|
|
43
|
+
menu.clayCreateAnchor = anchor; anchor.setAttribute('aria-expanded', 'true');
|
|
44
|
+
store.set({ splitSessionPicker: { receipt: receipt, side: null, query: '', error: '', priorFocus: store.get('projectSplitViewFocusedOrigin') } });
|
|
45
|
+
renderPicker(menu); menu.hidden = false; position(); menu.querySelector('[data-split-side]').focus();
|
|
53
46
|
}
|
|
54
47
|
function onClick(event) {
|
|
55
|
-
var
|
|
56
|
-
var
|
|
57
|
-
if (menu.contains(target)) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (index === -1) selected.push(origin); else selected.splice(index, 1);
|
|
65
|
-
store.set({ projectSplitViewCreateSelection: selected }); choice.setAttribute('aria-pressed', selected.indexOf(origin) >= 0 ? 'true' : 'false'); refreshCreateButton(menu); return;
|
|
66
|
-
}
|
|
67
|
-
if (target.closest('[data-create-split-view]') && !target.closest('[data-create-split-view]').disabled) {
|
|
68
|
-
event.preventDefault(); document.dispatchEvent(new CustomEvent('clay-split-view-create', { detail: { origins: (store.get('projectSplitViewCreateSelection') || []).slice() } })); close(true);
|
|
69
|
-
}
|
|
70
|
-
return;
|
|
48
|
+
var menu = menuElement(), state = store.get('splitSessionPicker'); if (!menu || menu.hidden || !state) return;
|
|
49
|
+
var button = event.target.closest && event.target.closest('button');
|
|
50
|
+
if (!menu.contains(event.target)) { if (!state.side && (!event.target.closest || !event.target.closest('.split-view-arrange-trigger'))) close(false); return; }
|
|
51
|
+
if (!button || button.disabled) return;
|
|
52
|
+
event.preventDefault();
|
|
53
|
+
if (button.dataset.splitClose) { close(true); return; }
|
|
54
|
+
if (!pickerCurrent(state.receipt)) { close(true); return; }
|
|
55
|
+
if (button.dataset.splitSide) {
|
|
56
|
+
store.set({ splitSessionPicker: Object.assign({}, state, { side: button.dataset.splitSide, query: '' }), projectSplitViewFocusedOrigin: null }); renderPicker(menu); document.dispatchEvent(new CustomEvent('clay-split-picker-layout')); position(); menu.querySelector('input').focus(); retryOptions(); return;
|
|
71
57
|
}
|
|
72
|
-
if (
|
|
73
|
-
|
|
58
|
+
if (button.dataset.splitRetry) { retryOptions(); return; }
|
|
59
|
+
var accepted = button.dataset.splitSession ? insertPickerSession(state.receipt, state.side, Number(button.dataset.splitSession)) : button.dataset.splitVendor ? createPickerSession(state.receipt, state.side, button.dataset.splitVendor) : false;
|
|
60
|
+
if (accepted) renderPickerResults(menu);
|
|
61
|
+
else { store.set({ splitSessionPicker: Object.assign({}, state, { error: 'The split could not be added. Check the View and try again.' }) }); renderPickerResults(menu); }
|
|
62
|
+
}
|
|
63
|
+
function onKey(event) {
|
|
64
|
+
var menu = menuElement(), state = store.get('splitSessionPicker'); if (!menu || menu.hidden || !state) return;
|
|
65
|
+
if (event.key === 'Escape') { event.preventDefault(); event.stopImmediatePropagation(); close(true); return; }
|
|
66
|
+
if (!menu.contains(event.target)) return;
|
|
67
|
+
var keys = ['ArrowDown', 'ArrowUp', 'Home', 'End'];
|
|
68
|
+
if (keys.indexOf(event.key) < 0 && !(event.key === 'Enter' && event.target.tagName === 'INPUT')) return;
|
|
69
|
+
var items = Array.from(menu.querySelectorAll('[data-split-side], [data-split-session], [data-split-vendor]')).filter(function (button) { return !button.disabled; });
|
|
70
|
+
if (!items.length) return;
|
|
71
|
+
if (event.target.tagName === 'INPUT' && (event.key === 'Home' || event.key === 'End')) return;
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
var index = items.indexOf(document.activeElement);
|
|
74
|
+
if (event.key === 'Enter') { items[0].click(); return; }
|
|
75
|
+
var next = event.key === 'Home' ? 0 : event.key === 'End' ? items.length - 1 : index < 0 ? event.key === 'ArrowUp' ? items.length - 1 : 0 : (index + (event.key === 'ArrowUp' ? -1 : 1) + items.length) % items.length;
|
|
76
|
+
items[next].focus();
|
|
74
77
|
}
|
|
75
78
|
export function initSplitViewCreateMenu() {
|
|
76
79
|
document.addEventListener('clay-split-view-open-create', function (event) { if (event.detail && event.detail.anchor) open(event.detail.anchor); });
|
|
77
|
-
document.addEventListener('click', onClick, true);
|
|
78
|
-
document.addEventListener('
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
document.addEventListener('click', onClick, true); document.addEventListener('keydown', onKey, true);
|
|
81
|
+
document.addEventListener('input', function (event) {
|
|
82
|
+
var state = store.get('splitSessionPicker'); if (!state || !event.target.matches('#split-view-create-menu input')) return;
|
|
83
|
+
store.set({ splitSessionPicker: Object.assign({}, state, { query: event.target.value }) });
|
|
84
|
+
var label = menuElement().querySelector('.split-session-section'); label.textContent = event.target.value ? 'Search results' : 'Recent sessions'; renderPickerResults(menuElement()); position();
|
|
85
|
+
});
|
|
86
|
+
window.addEventListener('resize', position);
|
|
87
|
+
store.subscribe(function (state, previous) {
|
|
88
|
+
if (!state.splitSessionPicker) return;
|
|
89
|
+
if (!pickerCurrent(state.splitSessionPicker.receipt)) { close(false); return; }
|
|
90
|
+
if (state.sessionCreateOptions !== previous.sessionCreateOptions || state.projectSplitViewPending !== previous.projectSplitViewPending || state.paneBindingSessions !== previous.paneBindingSessions || state.splitSessionCreation !== previous.splitSessionCreation) { renderPickerResults(menuElement()); position(); }
|
|
91
|
+
});
|
|
83
92
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { livePaneBinding, samePaneBinding } from './pane-binding.js';
|
|
4
|
+
import { layoutScope, currentLayoutScope } from './split-view-layout-scope.js';
|
|
5
|
+
import { presentationDropTree } from './split-view-presentation-drop.js';
|
|
6
|
+
import { rectangles } from './split-view-layout-client.js';
|
|
7
|
+
|
|
8
|
+
function activeView() {
|
|
9
|
+
return (store.get('projectSplitViews') || []).find(function (view) { return view.id === store.get('projectSplitViewActiveId'); });
|
|
10
|
+
}
|
|
11
|
+
function reducedMotion() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
|
|
12
|
+
export function captureDragPreview(drag, host) {
|
|
13
|
+
var settling = store.get('projectSplitViewDragSettling');
|
|
14
|
+
if (settling && settling.timer) clearTimeout(settling.timer);
|
|
15
|
+
store.set({ projectSplitViewDragSettling: null });
|
|
16
|
+
var view = activeView();
|
|
17
|
+
drag.preview = { host: host, socket: getWs(), slug: store.get('currentSlug'), owner: store.get('myUserId'), projectOwner: store.get('currentProjectOwnerId'),
|
|
18
|
+
scope: view && layoutScope(view), width: host.clientWidth, height: host.clientHeight, tree: null,
|
|
19
|
+
panes: Array.from(host.querySelectorAll(':scope > .split-pane:not(.split-pane-cached)')).filter(function (pane) {
|
|
20
|
+
return !pane.hidden && pane.getBoundingClientRect().width > 0;
|
|
21
|
+
}).map(function (pane) {
|
|
22
|
+
var frame = pane.querySelector('.split-pane-frame'), live = frame && frame.clayPane && frame.clayPane.identity();
|
|
23
|
+
return { pane: pane, frame: frame, document: frame && frame.contentDocument, window: frame && frame.contentWindow,
|
|
24
|
+
binding: live && live.binding, rect: pane.getBoundingClientRect() };
|
|
25
|
+
}) };
|
|
26
|
+
}
|
|
27
|
+
export function dragPreviewCurrent(drag) {
|
|
28
|
+
var preview = drag.preview;
|
|
29
|
+
return !!preview && preview.socket === getWs() && preview.slug === store.get('currentSlug') && preview.owner === store.get('myUserId') &&
|
|
30
|
+
preview.projectOwner === store.get('currentProjectOwnerId') && store.get('connected') && !store.get('projectSplitViewDividerDrag') && (!preview.scope || currentLayoutScope(preview.scope)) &&
|
|
31
|
+
preview.width === preview.host.clientWidth && preview.height === preview.host.clientHeight && preview.panes.every(function (entry) {
|
|
32
|
+
var live = entry.frame && entry.frame.clayPane && entry.frame.clayPane.identity();
|
|
33
|
+
return entry.pane.isConnected && entry.frame.contentDocument === entry.document && entry.frame.contentWindow === entry.window &&
|
|
34
|
+
live && samePaneBinding(live.binding, entry.binding) && livePaneBinding(entry.frame);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// Hit-test the starting layout, never panes that are travelling under the hand.
|
|
38
|
+
export function dragPreviewPaneAt(drag, x, y) {
|
|
39
|
+
var entries = drag && drag.preview && drag.preview.panes || [];
|
|
40
|
+
return entries.find(function (entry) {
|
|
41
|
+
var r = entry.rect; return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export function renderDragPreview(drag, target) {
|
|
45
|
+
var preview = drag.preview, view = activeView();
|
|
46
|
+
if (!preview || !view || !dragPreviewCurrent(drag)) return null;
|
|
47
|
+
var sourceView = (store.get('projectSplitViews') || []).find(function (item) { return item.id === drag.sourceViewId; });
|
|
48
|
+
var tree = target && !target.sidebar && target.viewId === view.id && sourceView && sourceView.id === view.id && !store.get('projectSplitViewFocusedOrigin') ?
|
|
49
|
+
presentationDropTree(drag, target, sourceView, view) : null;
|
|
50
|
+
if (JSON.stringify(tree) === JSON.stringify(preview.tree)) return preview.dropRect || null;
|
|
51
|
+
preview.tree = tree;
|
|
52
|
+
preview.host.classList.add('split-view-drag-preview');
|
|
53
|
+
store.set({ projectSplitViewPreviewTree: tree, projectSplitViewPreviewScope: preview.scope });
|
|
54
|
+
preview.dropRect = null;
|
|
55
|
+
if (tree) {
|
|
56
|
+
var style = window.getComputedStyle(preview.host), left = parseFloat(style.paddingLeft) || 0, top = parseFloat(style.paddingTop) || 0;
|
|
57
|
+
var geometry = rectangles(tree, preview.width - left - (parseFloat(style.paddingRight) || 0), preview.height - top - (parseFloat(style.paddingBottom) || 0), 7);
|
|
58
|
+
var rect = geometry.panes[drag.origin];
|
|
59
|
+
if (rect) preview.dropRect = { left: left + rect.x, top: top + rect.y, width: rect.width, height: rect.height };
|
|
60
|
+
}
|
|
61
|
+
return preview.dropRect;
|
|
62
|
+
}
|
|
63
|
+
function settleMotion() {
|
|
64
|
+
var settling = store.get('projectSplitViewDragSettling');
|
|
65
|
+
if (!settling || settling.timer || store.get('projectSplitViewDrag') || store.get('projectSplitViewPending')) return;
|
|
66
|
+
settling.timer = setTimeout(function () {
|
|
67
|
+
if (store.get('projectSplitViewDragSettling') !== settling) return;
|
|
68
|
+
settling.host.classList.remove('split-view-drag-preview');
|
|
69
|
+
store.set({ projectSplitViewDragSettling: null });
|
|
70
|
+
}, reducedMotion() ? 0 : 320);
|
|
71
|
+
}
|
|
72
|
+
export function restoreDragPreview(drag, keep) {
|
|
73
|
+
var preview = drag && drag.preview;
|
|
74
|
+
if (!preview) return;
|
|
75
|
+
if (!keep && preview.tree && store.get('projectSplitViewPreviewTree') === preview.tree) {
|
|
76
|
+
store.set({ projectSplitViewPreviewTree: null, projectSplitViewPreviewScope: null });
|
|
77
|
+
}
|
|
78
|
+
var previous = store.get('projectSplitViewDragSettling');
|
|
79
|
+
if (previous && previous.timer) clearTimeout(previous.timer);
|
|
80
|
+
store.set({ projectSplitViewDragSettling: { host: preview.host, timer: null } });
|
|
81
|
+
settleMotion();
|
|
82
|
+
}
|
|
83
|
+
// Preserve transcript anchors until animated width/height changes have settled.
|
|
84
|
+
export function afterPaneMotion(host, callback) {
|
|
85
|
+
window.requestAnimationFrame(function () {
|
|
86
|
+
if (host.classList.contains('split-view-drag-preview') && !reducedMotion()) setTimeout(callback, 300);
|
|
87
|
+
else callback();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
store.subscribe(function () { settleMotion(); });
|
|
@@ -24,7 +24,7 @@ export function validateTree(tree, expectedIds) {
|
|
|
24
24
|
!Number.isFinite(node.ratio) || node.ratio < MIN_RATIO || node.ratio > MAX_RATIO) return false;
|
|
25
25
|
return walk(node.left, depth + 1) && walk(node.right, depth + 1);
|
|
26
26
|
}
|
|
27
|
-
valid = walk(tree, 0) && ids.length >=
|
|
27
|
+
valid = (tree === null || walk(tree, 0)) && ids.length >= 0 && ids.length <= MAX_SESSIONS && new Set(ids).size === ids.length;
|
|
28
28
|
if (valid && expectedIds) {
|
|
29
29
|
valid = Array.isArray(expectedIds) && expectedIds.length === ids.length &&
|
|
30
30
|
expectedIds.every(function (id) { return ids.indexOf(id) !== -1; });
|
|
@@ -36,3 +36,7 @@ export function presentationTree(view) {
|
|
|
36
36
|
JSON.stringify(receipt) === JSON.stringify(layoutScope(view)) ?
|
|
37
37
|
store.get('projectSplitViewPreviewTree') : view && view.tree;
|
|
38
38
|
}
|
|
39
|
+
export function bindingMatches(left, right) {
|
|
40
|
+
return !!left && !!right && left.sessionId === right.sessionId && left.sessionOriginId === right.sessionOriginId &&
|
|
41
|
+
left.generation === right.generation && left.requestId === right.requestId;
|
|
42
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Exact-context session insertion through the existing View and creation services.
|
|
2
|
+
import { store } from './store.js';
|
|
3
|
+
import { getWs } from './ws-ref.js';
|
|
4
|
+
import { getCachedSessions } from './sidebar-sessions.js';
|
|
5
|
+
import { activeSavedView } from './view-workspace-state.js';
|
|
6
|
+
import { layoutScope, currentLayoutScope } from './split-view-layout-scope.js';
|
|
7
|
+
import { viewForOrigin } from './split-view-presentation-lookup.js';
|
|
8
|
+
import { applyPresentationDrop } from './split-view-presentation-drop.js';
|
|
9
|
+
import { mutation } from './view-mutations.js';
|
|
10
|
+
import { checkComposerDeparture } from './session-rich-drafts.js';
|
|
11
|
+
import { createOptions } from './session-create.js';
|
|
12
|
+
import { showToast } from './utils.js';
|
|
13
|
+
|
|
14
|
+
export function pickerReceipt(anchor) {
|
|
15
|
+
var view = activeSavedView(), pane = anchor && anchor.closest('.split-pane'), frame = pane && pane.querySelector('.split-pane-frame');
|
|
16
|
+
var origin = pane && pane.dataset.presentationOrigin || frame && frame.dataset.sessionOriginId || store.get('sessionOriginId');
|
|
17
|
+
return view && view.sessions.some(function (session) { return session.sessionOriginId === origin; }) ? { scope: layoutScope(view), origin: origin } : null;
|
|
18
|
+
}
|
|
19
|
+
export function pickerCurrent(receipt) {
|
|
20
|
+
var picker = store.get('splitSessionPicker');
|
|
21
|
+
return receipt && (!receipt.reservationId || picker && picker.receipt.reservationId === receipt.reservationId) && !store.get('paneMode') && currentLayoutScope(receipt.scope) && activeSavedView().sessions.some(function (session) { return session.sessionOriginId === receipt.origin; });
|
|
22
|
+
}
|
|
23
|
+
export function pickerCapacity() {
|
|
24
|
+
var view = activeSavedView();
|
|
25
|
+
return !!view && view.sessions.length + (view.workspace.linkedPair ? view.workspace.linkedPair.workerOrigins.length : 0) < 8;
|
|
26
|
+
}
|
|
27
|
+
function checkDrafts() {
|
|
28
|
+
checkComposerDeparture();
|
|
29
|
+
document.querySelectorAll('#split-host .split-pane:not(.split-pane-cached) .split-pane-frame').forEach(function (frame) { if (frame.clayPane) frame.clayPane.checkDraft(); });
|
|
30
|
+
}
|
|
31
|
+
export function insertPickerSession(receipt, side, id) {
|
|
32
|
+
if (['left', 'right', 'top', 'bottom'].indexOf(side) < 0 || !pickerCurrent(receipt) || !pickerCapacity() || store.get('projectSplitViewPending') || store.get('projectSplitViewError')) return false;
|
|
33
|
+
var view = activeSavedView(), session = (getCachedSessions() || []).find(function (item) { return item.id === id; });
|
|
34
|
+
if (!session || session.sessionRole === 'worker' || session.isMate || session.isTui || (session.ownerId || null) !== (store.get('myUserId') || null) || view.sessions.some(function (ref) { return ref.sessionOriginId === session.sessionOriginId; })) return false;
|
|
35
|
+
var source = viewForOrigin(session.sessionOriginId);
|
|
36
|
+
return applyPresentationDrop({ origin: session.sessionOriginId, sessionId: id, sourceViewId: source && source.id },
|
|
37
|
+
{ origin: receipt.origin, viewId: view.id, side: side }, mutation, checkDrafts) === true;
|
|
38
|
+
}
|
|
39
|
+
export function createPickerSession(receipt, side, vendor) {
|
|
40
|
+
if (['left', 'right', 'top', 'bottom'].indexOf(side) < 0 || !pickerCurrent(receipt) || !pickerCapacity() || store.get('splitSessionCreation') || store.get('projectSplitViewPending')) return false;
|
|
41
|
+
if (!createOptions().vendors.some(function (item) { return item.id === vendor && item.installed; })) return false;
|
|
42
|
+
try { checkDrafts(); } catch (error) { showToast(error.message, 'warn'); return false; }
|
|
43
|
+
var id = 'split-create-' + (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? crypto.randomUUID() : Date.now().toString(36) + '-' + Math.random().toString(36).slice(2));
|
|
44
|
+
store.set({ splitSessionCreation: { receipt: receipt, side: side, requestId: id, createdId: null } });
|
|
45
|
+
try { getWs().send(JSON.stringify({ type: 'new_session', slug: store.get('currentSlug'), requestId: id, vendor: vendor, forceNew: true, activate: false })); }
|
|
46
|
+
catch (error) { store.set({ splitSessionCreation: null }); return false; }
|
|
47
|
+
setTimeout(function () {
|
|
48
|
+
var pending = store.get('splitSessionCreation');
|
|
49
|
+
if (pending && pending.requestId === id) {
|
|
50
|
+
store.set({ splitSessionCreation: null });
|
|
51
|
+
showToast('Creation was not confirmed. Check recent sessions before trying again.', 'warn');
|
|
52
|
+
}
|
|
53
|
+
}, 30000);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
export function handleSplitSessionCreated(message) {
|
|
57
|
+
var pending = store.get('splitSessionCreation');
|
|
58
|
+
if (message.type !== 'new_session_result' || !pending || message.requestId !== pending.requestId) return false;
|
|
59
|
+
if (!message.ok) { store.set({ splitSessionCreation: null }); showToast(message.error || 'The session could not be created.', 'error'); return true; }
|
|
60
|
+
store.set({ splitSessionCreation: Object.assign({}, pending, { createdId: message.sessionId }) });
|
|
61
|
+
finishPickerCreation(); return true;
|
|
62
|
+
}
|
|
63
|
+
export function finishPickerCreation() {
|
|
64
|
+
var pending = store.get('splitSessionCreation');
|
|
65
|
+
if (!pending || !pending.createdId || !getCachedSessions().some(function (session) { return session.id === pending.createdId; })) return;
|
|
66
|
+
store.set({ splitSessionCreation: null });
|
|
67
|
+
if (!insertPickerSession(pending.receipt, pending.side, pending.createdId)) showToast('The session was created in Unfiled. The View changed, so it was not added to the split.', 'warn');
|
|
68
|
+
}
|
|
69
|
+
store.subscribe(function (state, previous) {
|
|
70
|
+
if (state.splitSessionCreation && (state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId || state.projectSplitViewSocketGeneration !== previous.projectSplitViewSocketGeneration || !state.connected)) store.set({ splitSessionCreation: null });
|
|
71
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getCachedSessions } from './sidebar-sessions.js';
|
|
3
|
+
import { activeSavedView } from './view-workspace-state.js';
|
|
4
|
+
import { viewForOrigin } from './split-view-presentation-lookup.js';
|
|
5
|
+
import { createOptions } from './session-create.js';
|
|
6
|
+
import { sortVendorEntries } from './vendor-priority.js';
|
|
7
|
+
import { VENDOR_AVATARS, VENDOR_NAMES } from './chat-render-runtime.js';
|
|
8
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
9
|
+
import { pickerCapacity } from './split-view-picker-actions.js';
|
|
10
|
+
|
|
11
|
+
export var SPLIT_SIDES = [['right', 'Right', 'panel-right-open'], ['left', 'Left', 'panel-left-open'], ['top', 'Top', 'panel-top-open'], ['bottom', 'Bottom', 'panel-bottom-open']];
|
|
12
|
+
function button(label, action, value) {
|
|
13
|
+
var el = document.createElement('button'); el.type = 'button'; el.dataset[action] = value; el.setAttribute('aria-label', label); el.title = label; return el;
|
|
14
|
+
}
|
|
15
|
+
function vendorIcon(vendor) {
|
|
16
|
+
var image = document.createElement('img'); image.src = VENDOR_AVATARS[vendor] || '/icon-192.png'; image.alt = ''; return image;
|
|
17
|
+
}
|
|
18
|
+
function age(time) {
|
|
19
|
+
var minutes = Math.max(0, Math.floor((Date.now() - (time || 0)) / 60000));
|
|
20
|
+
return minutes < 1 ? 'Now' : minutes < 60 ? minutes + 'm' : minutes < 1440 ? Math.floor(minutes / 60) + 'h' : Math.floor(minutes / 1440) + 'd';
|
|
21
|
+
}
|
|
22
|
+
export function pickerCandidates(query) {
|
|
23
|
+
var view = activeSavedView(), text = (query || '').trim().toLocaleLowerCase();
|
|
24
|
+
return (getCachedSessions() || []).filter(function (session) {
|
|
25
|
+
return view && session.sessionOriginId && !session.hidden && session.sessionRole !== 'worker' && !session.isMate && !session.isTui &&
|
|
26
|
+
(session.ownerId || null) === (store.get('myUserId') || null) && !view.sessions.some(function (ref) { return ref.sessionOriginId === session.sessionOriginId; }) &&
|
|
27
|
+
(!text || (session.title || 'New session').toLocaleLowerCase().indexOf(text) >= 0);
|
|
28
|
+
}).sort(function (a, b) { return (b.lastActivity || b.createdAt || 0) - (a.lastActivity || a.createdAt || 0); }).slice(0, text ? 40 : 6);
|
|
29
|
+
}
|
|
30
|
+
export function renderPickerResults(menu) {
|
|
31
|
+
var state = store.get('splitSessionPicker'); if (!state) return;
|
|
32
|
+
var list = menu.querySelector('.split-session-results'); if (!list) return;
|
|
33
|
+
var focused = menu.contains(document.activeElement) && document.activeElement.dataset, focusKey = focused && (focused.splitSession ? ['splitSession', focused.splitSession] : focused.splitVendor ? ['splitVendor', focused.splitVendor] : null);
|
|
34
|
+
list.replaceChildren();
|
|
35
|
+
var candidates = pickerCandidates(state.query), blocked = !pickerCapacity() || !!store.get('projectSplitViewPending') || !!store.get('splitSessionCreation');
|
|
36
|
+
candidates.forEach(function (session) {
|
|
37
|
+
var title = session.title || 'New session', owner = viewForOrigin(session.sessionOriginId);
|
|
38
|
+
var choice = button('Add ' + title, 'splitSession', String(session.id)); choice.className = 'split-session-result'; choice.disabled = blocked;
|
|
39
|
+
choice.appendChild(vendorIcon(session.vendor));
|
|
40
|
+
var content = document.createElement('span'); content.className = 'split-session-result-title'; content.textContent = title; choice.appendChild(content);
|
|
41
|
+
var time = document.createElement('small'); time.textContent = age(session.lastActivity || session.createdAt); choice.appendChild(time);
|
|
42
|
+
choice.title = title + (owner ? ' · Move from ' + owner.name : '');
|
|
43
|
+
if (owner && owner.sessions.length > 1) { var note = document.createElement('small'); note.className = 'split-session-move'; note.textContent = 'Move from ' + owner.name; content.appendChild(note); }
|
|
44
|
+
list.appendChild(choice);
|
|
45
|
+
});
|
|
46
|
+
if (!candidates.length) { var empty = document.createElement('p'); empty.className = 'split-session-empty'; empty.textContent = state.query ? 'No matching sessions. Start a new one below.' : 'No other sessions yet. Start a new one below.'; list.appendChild(empty); }
|
|
47
|
+
var vendors = menu.querySelector('.split-session-vendors'); vendors.replaceChildren();
|
|
48
|
+
var options = createOptions(), installed = sortVendorEntries(options.vendors.filter(function (vendor) { return vendor.installed; }));
|
|
49
|
+
installed.forEach(function (vendor) {
|
|
50
|
+
var label = 'New ' + (VENDOR_NAMES[vendor.id] || vendor.displayName || vendor.id) + ' session';
|
|
51
|
+
var control = button(label, 'splitVendor', vendor.id); control.disabled = blocked; control.appendChild(vendorIcon(vendor.id)); vendors.appendChild(control);
|
|
52
|
+
});
|
|
53
|
+
if (options.error || !installed.length) {
|
|
54
|
+
var hint = document.createElement('span'); hint.className = 'split-session-empty'; hint.textContent = options.error || (options.loaded ? 'No installed vendors available.' : 'Loading vendors…'); vendors.appendChild(hint);
|
|
55
|
+
if (options.error) { var retry = button('Retry loading vendors', 'splitRetry', 'true'); retry.textContent = 'Retry'; vendors.appendChild(retry); }
|
|
56
|
+
}
|
|
57
|
+
var status = menu.querySelector('[role="status"]');
|
|
58
|
+
if (focusKey) { var restored = Array.from(menu.querySelectorAll('button')).find(function (item) { return item.dataset[focusKey[0]] === focusKey[1] && !item.disabled; }); if (restored) restored.focus({ preventScroll: true }); }
|
|
59
|
+
status.textContent = !pickerCapacity() ? 'This View already has eight session panes.' : store.get('projectSplitViewPending') ? 'Saving the View…' : store.get('splitSessionCreation') ? 'Creating session…' : state.error || '';
|
|
60
|
+
}
|
|
61
|
+
export function renderPicker(menu) {
|
|
62
|
+
var state = store.get('splitSessionPicker'); if (!state) return;
|
|
63
|
+
menu.replaceChildren(); menu.dataset.stage = state.side ? 'sessions' : 'direction';
|
|
64
|
+
if (!state.side) {
|
|
65
|
+
var header = document.createElement('div'); header.className = 'split-view-arrange-header';
|
|
66
|
+
var title = document.createElement('strong'); title.textContent = 'Add split'; header.appendChild(title);
|
|
67
|
+
var close = button('Close split picker', 'splitClose', 'true'); close.innerHTML = iconHtml('x'); header.appendChild(close); menu.appendChild(header);
|
|
68
|
+
var directions = document.createElement('div'); directions.className = 'split-session-directions';
|
|
69
|
+
SPLIT_SIDES.forEach(function (side) { var choice = button('Add ' + side[1] + ' Split', 'splitSide', side[0]); choice.disabled = !pickerCapacity() || !!store.get('projectSplitViewPending'); choice.innerHTML = iconHtml(side[2]); var label = document.createElement('span'); label.textContent = 'Add ' + side[1] + ' Split'; choice.appendChild(label); directions.appendChild(choice); });
|
|
70
|
+
menu.appendChild(directions); refreshIcons(); return;
|
|
71
|
+
}
|
|
72
|
+
var search = document.createElement('label'); search.className = 'split-session-search'; search.innerHTML = iconHtml('search');
|
|
73
|
+
var input = document.createElement('input'); input.type = 'search'; input.placeholder = 'Search sessions…'; input.setAttribute('aria-label', 'Search sessions'); input.value = state.query || ''; search.appendChild(input); menu.appendChild(search);
|
|
74
|
+
var recent = document.createElement('div'); recent.className = 'split-session-section'; recent.textContent = state.query ? 'Search results' : 'Recent sessions'; menu.appendChild(recent);
|
|
75
|
+
var list = document.createElement('div'); list.className = 'split-session-results'; list.setAttribute('aria-label', 'Available sessions'); menu.appendChild(list);
|
|
76
|
+
var heading = document.createElement('div'); heading.className = 'split-session-section split-session-new'; heading.textContent = 'New session'; menu.appendChild(heading);
|
|
77
|
+
var vendors = document.createElement('div'); vendors.className = 'split-session-vendors'; vendors.setAttribute('aria-label', 'Installed vendors'); menu.appendChild(vendors);
|
|
78
|
+
var status = document.createElement('p'); status.className = 'split-session-status'; status.setAttribute('role', 'status'); menu.appendChild(status);
|
|
79
|
+
var footer = document.createElement('small'); footer.className = 'split-session-hint'; footer.textContent = '↑↓ Choose · Enter Open · Esc Cancel'; menu.appendChild(footer);
|
|
80
|
+
renderPickerResults(menu); refreshIcons();
|
|
81
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Pointer drag controller for presentation views. Captured gestures are
|
|
2
2
|
// source-bound by pointerId and always recompute the release target.
|
|
3
3
|
import { store } from './store.js';
|
|
4
|
+
import { livePaneBinding } from './pane-binding.js';
|
|
5
|
+
import { captureDragPreview, dragPreviewCurrent, dragPreviewPaneAt, renderDragPreview, restoreDragPreview } from './split-view-drag-preview.js';
|
|
4
6
|
import { getCachedSessions } from './sidebar-sessions.js';
|
|
5
7
|
import { showToast } from './utils.js';
|
|
6
8
|
import { currentFolderState, sendFolderOp } from './session-folders.js';
|
|
@@ -29,22 +31,28 @@ function validSession(session) {
|
|
|
29
31
|
function legacyMember(session) { return !!session && session.sessionRole === "worker"; }
|
|
30
32
|
|
|
31
33
|
function onPointerDown(event) {
|
|
32
|
-
if (event.button !== 0 || event.isPrimary === false || store.get('projectSplitViewDrag')) return;
|
|
34
|
+
if (event.button !== 0 || event.isPrimary === false || store.get('projectSplitViewDrag') || store.get('projectSplitViewPending')) return;
|
|
33
35
|
if (!event.target || !event.target.closest) return;
|
|
34
36
|
var paneHeader = event.target.closest(".split-pane-header");
|
|
35
37
|
var row = event.target.closest(".session-item[data-session-id], .session-loop-child[data-session-id]");
|
|
36
38
|
if (!paneHeader && !row) return;
|
|
39
|
+
var workspace = store.get('toolPaneWindowGeometry');
|
|
40
|
+
if (paneHeader && workspace && workspace.windows.some(function (entry) { return !!entry.tool; })) return;
|
|
41
|
+
if (paneHeader && event.target.closest(".split-pane-title, .split-view-runtime, .split-worker-runtime")) return;
|
|
37
42
|
var button = event.target.closest("button");
|
|
38
43
|
if ((paneHeader && button) || (button && button !== row) || event.target.closest("a, input, textarea, select, [contenteditable='true'], .workbench, .split-view-menu, .split-view-arrange, .session-row-actions, .session-close-btn, .session-more-btn")) return;
|
|
39
44
|
var pane = paneHeader && paneHeader.closest('.split-pane');
|
|
45
|
+
if (pane && !livePaneBinding(pane.querySelector('.split-pane-frame'))) return;
|
|
40
46
|
var id = pane ? Number(pane.dataset.sessionId) : row ? Number(row.dataset.sessionId) : 0;
|
|
41
47
|
var source = sessionById(id);
|
|
42
48
|
if (!validSession(source) || legacyMember(source)) return;
|
|
43
49
|
var group = row && row.closest('.split-view-group[data-split-view-id]');
|
|
44
50
|
var view = viewForOrigin(source.sessionOriginId);
|
|
45
|
-
|
|
51
|
+
var drag = { pointerId: event.pointerId, sessionId: source.id, origin: source.sessionOriginId,
|
|
46
52
|
sourceViewId: group ? group.dataset.splitViewId : view && view.id || null, sourceElement: row || paneHeader, startX: event.clientX, startY: event.clientY,
|
|
47
|
-
moved: false, target: null, pointerType: event.pointerType, frames: [] }
|
|
53
|
+
moved: false, target: null, pointerType: event.pointerType, frames: [] };
|
|
54
|
+
captureDragPreview(drag, host);
|
|
55
|
+
store.set({ projectSplitViewDrag: drag });
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
function targetAt(x, y) {
|
|
@@ -59,6 +67,8 @@ function targetAt(x, y) {
|
|
|
59
67
|
}
|
|
60
68
|
var sidebar = sidebarTargetAt(element);
|
|
61
69
|
if (sidebar) return sidebar;
|
|
70
|
+
var snapshot = dragPreviewPaneAt(store.get('projectSplitViewDrag'), x, y);
|
|
71
|
+
if (snapshot) return paneTargetAt(snapshot.pane, x, y, snapshot.rect);
|
|
62
72
|
var pane = element.closest('.split-pane');
|
|
63
73
|
if (!pane || !host.contains(pane) || !pane.isConnected) return null;
|
|
64
74
|
return paneTargetAt(pane, x, y);
|
|
@@ -81,11 +91,11 @@ function sidebarTargetAt(element) {
|
|
|
81
91
|
rect: { left: bounds.left, top: bounds.top, width: bounds.width, height: bounds.height } };
|
|
82
92
|
}
|
|
83
93
|
|
|
84
|
-
function paneTargetAt(pane, x, y) {
|
|
94
|
+
function paneTargetAt(pane, x, y, originalBounds) {
|
|
85
95
|
var id = Number(pane.dataset.sessionId), session = sessionById(id), frame = pane.querySelector('.split-pane-frame');
|
|
86
96
|
if (!validSession(session) || legacyMember(session) || !frame || frame.dataset.projectSlug !== store.get('currentSlug') ||
|
|
87
97
|
frame.dataset.sessionId !== String(id) || frame.dataset.sessionOriginId !== session.sessionOriginId) return null;
|
|
88
|
-
var bounds = pane.getBoundingClientRect(), dx = (x - bounds.left) / Math.max(1, bounds.width), dy = (y - bounds.top) / Math.max(1, bounds.height);
|
|
98
|
+
var bounds = originalBounds || pane.getBoundingClientRect(), dx = (x - bounds.left) / Math.max(1, bounds.width), dy = (y - bounds.top) / Math.max(1, bounds.height);
|
|
89
99
|
var edge = Math.min(dx, 1 - dx, dy, 1 - dy), side = "center";
|
|
90
100
|
if (edge < 0.25) side = edge === dx ? "left" : edge === 1 - dx ? "right" : edge === dy ? "top" : "bottom";
|
|
91
101
|
var view = viewForOrigin(session.sessionOriginId);
|
|
@@ -102,12 +112,13 @@ function fallbackTargetAt(x, y) {
|
|
|
102
112
|
var panes = Array.from(host.querySelectorAll(':scope > .split-pane')), nearest = null, nearestDistance = Infinity;
|
|
103
113
|
panes.forEach(function (pane) {
|
|
104
114
|
if (pane.hidden) return;
|
|
105
|
-
var
|
|
115
|
+
var snapshot = store.get('projectSplitViewDrag'), entry = snapshot && snapshot.preview && snapshot.preview.panes.find(function (item) { return item.pane === pane; });
|
|
116
|
+
var rect = entry ? entry.rect : pane.getBoundingClientRect(), dx = x < rect.left ? rect.left - x : x > rect.right ? x - rect.right : 0;
|
|
106
117
|
var dy = y < rect.top ? rect.top - y : y > rect.bottom ? y - rect.bottom : 0;
|
|
107
118
|
var distance = dx * dx + dy * dy;
|
|
108
119
|
if (distance < nearestDistance) { nearest = pane; nearestDistance = distance; }
|
|
109
120
|
});
|
|
110
|
-
if (nearest) return paneTargetAt(nearest, x, y);
|
|
121
|
+
if (nearest) { var entry = store.get('projectSplitViewDrag').preview.panes.find(function (item) { return item.pane === nearest; }); return paneTargetAt(nearest, x, y, entry && entry.rect); }
|
|
111
122
|
}
|
|
112
123
|
if (!inApp) return null;
|
|
113
124
|
var active = sessionById(Number(store.get('activeSessionId')));
|
|
@@ -123,9 +134,12 @@ function resolveTarget(x, y) { return targetAt(x, y) || fallbackTargetAt(x, y);
|
|
|
123
134
|
function onPointerMove(event) {
|
|
124
135
|
var drag = store.get('projectSplitViewDrag');
|
|
125
136
|
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
137
|
+
if (!dragPreviewCurrent(drag)) { cleanup(true, true); return; }
|
|
126
138
|
if (!drag.moved && Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) < 6) return;
|
|
127
139
|
if (!drag.moved) {
|
|
128
140
|
drag.moved = true;
|
|
141
|
+
var source = drag.preview.panes.find(function (entry) { return entry.binding && entry.binding.sessionOriginId === drag.origin; });
|
|
142
|
+
if (source) { drag.sourcePane = source.pane; source.pane.classList.add('split-pane-drag-source'); }
|
|
129
143
|
try { document.documentElement.setPointerCapture(event.pointerId); } catch (error) {}
|
|
130
144
|
drag.frames = Array.from(host.querySelectorAll('.split-pane-frame')).map(function (frame) { return { frame: frame, pointerEvents: frame.style.pointerEvents }; });
|
|
131
145
|
drag.frames.forEach(function (entry) { entry.frame.style.pointerEvents = "none"; });
|
|
@@ -138,6 +152,7 @@ function onPointerMove(event) {
|
|
|
138
152
|
|
|
139
153
|
function renderPreview(drag, x, y) {
|
|
140
154
|
clearSidebarPreview();
|
|
155
|
+
var plannedRect = renderDragPreview(drag, drag.target);
|
|
141
156
|
var preview = host.querySelector(':scope > .split-view-drop-preview'), ghost = document.getElementById("split-view-drag-ghost");
|
|
142
157
|
if (!ghost) { ghost = document.createElement("div"); ghost.id = "split-view-drag-ghost"; ghost.className = "split-view-drag-ghost"; ghost.hidden = true; document.body.appendChild(ghost); }
|
|
143
158
|
if (!preview) {
|
|
@@ -188,9 +203,10 @@ function renderPreview(drag, x, y) {
|
|
|
188
203
|
var hostRect = host.getBoundingClientRect();
|
|
189
204
|
preview.style.left = (box.left - hostRect.left) + "px"; preview.style.top = (box.top - hostRect.top) + "px";
|
|
190
205
|
preview.style.width = box.width + "px"; preview.style.height = box.height + "px";
|
|
206
|
+
if (plannedRect) { preview.style.left = plannedRect.left + "px"; preview.style.top = plannedRect.top + "px"; preview.style.width = plannedRect.width + "px"; preview.style.height = plannedRect.height + "px"; }
|
|
191
207
|
preview.dataset.blocked = blocked ? "true" : "false"; preview.hidden = false;
|
|
192
208
|
var label = preview.querySelector(".drop-label");
|
|
193
|
-
if (label) label.textContent = blocked ? "Eight sessions already open" : drag.origin === target.origin ? "Keep this pane" :
|
|
209
|
+
if (label) label.textContent = blocked ? (drag.origin === target.origin ? "Keep this pane" : "Eight sessions already open") : drag.origin === target.origin ? "Keep this pane" :
|
|
194
210
|
target.side === "center" ? (sourceView && targetView && sourceView.id === targetView.id ? "Swap panes" : "Split right") : "Split " + target.side;
|
|
195
211
|
}
|
|
196
212
|
|
|
@@ -199,8 +215,10 @@ function clearSidebarPreview() {
|
|
|
199
215
|
document.querySelectorAll('[data-split-view-drop]').forEach(function (element) { delete element.dataset.splitViewDrop; });
|
|
200
216
|
}
|
|
201
217
|
|
|
202
|
-
export function restorePresentationDrag(drag) {
|
|
218
|
+
export function restorePresentationDrag(drag, keepPreview) {
|
|
203
219
|
if (!drag) return;
|
|
220
|
+
if (drag.sourcePane) drag.sourcePane.classList.remove('split-pane-drag-source');
|
|
221
|
+
restoreDragPreview(drag, keepPreview);
|
|
204
222
|
clearSidebarPreview();
|
|
205
223
|
drag.frames.forEach(function (entry) { if (entry.frame.isConnected) entry.frame.style.pointerEvents = entry.pointerEvents; });
|
|
206
224
|
var preview = host.querySelector(':scope > .split-view-drop-preview'); if (preview) preview.hidden = true;
|
|
@@ -215,23 +233,24 @@ export function restorePresentationDrag(drag) {
|
|
|
215
233
|
}
|
|
216
234
|
}
|
|
217
235
|
|
|
218
|
-
function cleanup(release, suppressClick) {
|
|
236
|
+
function cleanup(release, suppressClick, keepPreview) {
|
|
219
237
|
var drag = store.get('projectSplitViewDrag');
|
|
220
238
|
if (!drag) return;
|
|
221
|
-
restorePresentationDrag(drag);
|
|
239
|
+
restorePresentationDrag(drag, keepPreview);
|
|
222
240
|
if (suppressClick && drag.moved) {
|
|
223
241
|
var token = { pointerId: drag.pointerId, sourceElement: drag.sourceElement, sourceSessionId: drag.sessionId,
|
|
224
242
|
targetOrigin: drag.target && drag.target.origin, until: Date.now() + 250 };
|
|
225
243
|
store.set({ projectSplitViewSuppressClick: token });
|
|
226
244
|
setTimeout(function () { if (store.get('projectSplitViewSuppressClick') === token) store.set({ projectSplitViewSuppressClick: null }); }, 500);
|
|
227
245
|
}
|
|
228
|
-
if (release) try { if (document.documentElement.hasPointerCapture(drag.pointerId)) document.documentElement.releasePointerCapture(drag.pointerId); } catch (error) {}
|
|
229
246
|
store.set({ projectSplitViewDrag: null });
|
|
247
|
+
if (release) try { if (document.documentElement.hasPointerCapture(drag.pointerId)) document.documentElement.releasePointerCapture(drag.pointerId); } catch (error) {}
|
|
230
248
|
}
|
|
231
249
|
|
|
232
250
|
function onPointerUp(event) {
|
|
233
251
|
var drag = store.get('projectSplitViewDrag');
|
|
234
252
|
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
253
|
+
if (!dragPreviewCurrent(drag)) { cleanup(true, true); return; }
|
|
235
254
|
if (!drag.moved) { cleanup(true, false); return; }
|
|
236
255
|
var target = resolveTarget(event.clientX, event.clientY);
|
|
237
256
|
drag.target = target;
|
|
@@ -239,6 +258,11 @@ function onPointerUp(event) {
|
|
|
239
258
|
(store.get('projectSplitViews') || []).some(function (view) { return view.id === target.viewId && view.sessions.length >= 8; })) {
|
|
240
259
|
cleanup(true, true); showToast("Eight sessions are already open. Close a pane to add another.", "warn"); return;
|
|
241
260
|
}
|
|
261
|
+
if (target && !target.folderId && drag.origin !== target.origin && commitDrop) {
|
|
262
|
+
renderDragPreview(drag, target);
|
|
263
|
+
var queued = commitDrop(drag, target);
|
|
264
|
+
cleanup(true, true, queued === true); return;
|
|
265
|
+
}
|
|
242
266
|
cleanup(true, true);
|
|
243
267
|
if (target && target.folderId) {
|
|
244
268
|
var session = sessionById(drag.sessionId);
|
|
@@ -249,7 +273,6 @@ function onPointerUp(event) {
|
|
|
249
273
|
else sendFolderOp(favorite ? { op: 'set_favorite', sessionId: session.id, favorite: true } : { op: 'place_session', sessionId: session.id, folderId: folderId });
|
|
250
274
|
return;
|
|
251
275
|
}
|
|
252
|
-
if (target && drag.origin !== target.origin && commitDrop) commitDrop(drag, target);
|
|
253
276
|
}
|
|
254
277
|
|
|
255
278
|
function onPointerCancel(event) {
|