clay-server 5.2.1-beta.2 → 5.3.0-beta.2
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-control-presentation-binding.js +5 -3
- package/lib/clay-product-control-tools.js +3 -3
- package/lib/clay-product-control.js +4 -2
- package/lib/clay-screen-controls.js +18 -3
- package/lib/clay-split-view-awareness.js +35 -0
- package/lib/clay-split-view-controls.js +122 -0
- package/lib/clay-split-view-input.js +20 -0
- package/lib/clay-split-view-render-proof.js +22 -0
- package/lib/pane-asset-manifest.js +63 -0
- package/lib/project-connection.js +19 -10
- package/lib/project-session-actions.js +1 -0
- package/lib/project-session-delete.js +2 -2
- package/lib/project-session-folders.js +1 -0
- package/lib/project-sessions.js +6 -1
- package/lib/project-split-view-document.js +33 -0
- package/lib/project-split-view-names.js +67 -0
- package/lib/project-split-view-organization.js +23 -0
- package/lib/project-split-views.js +479 -0
- package/lib/project-worker-conversation.js +222 -0
- package/lib/project-worker-creation-proposal.js +3 -4
- package/lib/project-worker-proposal.js +1 -1
- package/lib/project-worker-removal.js +101 -0
- package/lib/project.js +28 -1
- package/lib/public/app.js +32 -13
- package/lib/public/css/base.css +9 -0
- package/lib/public/css/filebrowser.css +0 -1
- package/lib/public/css/pane-header-controls.css +100 -0
- package/lib/public/css/pane.css +24 -41
- package/lib/public/css/popup-chat.css +123 -0
- package/lib/public/css/right-workbench.css +50 -18
- package/lib/public/css/session-folders.css +1 -1
- package/lib/public/css/split-view-layout.css +463 -0
- package/lib/public/css/split-view-worker-activity.css +97 -0
- package/lib/public/css/sticky-notes.css +13 -0
- package/lib/public/index.html +3 -3
- package/lib/public/modules/app-connection.js +4 -1
- package/lib/public/modules/app-favicon.js +1 -1
- package/lib/public/modules/app-messages.js +31 -9
- package/lib/public/modules/chat-render-runtime.js +54 -0
- package/lib/public/modules/clay-control-copy.js +3 -0
- package/lib/public/modules/clay-navigation-render.js +1 -1
- package/lib/public/modules/clay-owner-navigation.js +15 -5
- package/lib/public/modules/clay-split-view-screen.js +74 -0
- package/lib/public/modules/clay-tool-activity.js +5 -0
- package/lib/public/modules/command-palette.js +30 -28
- package/lib/public/modules/contextual-clay-session.js +26 -2
- package/lib/public/modules/contextual-clay.js +5 -2
- package/lib/public/modules/contextual-mate-sessions.js +2 -0
- package/lib/public/modules/filebrowser.js +6 -2
- package/lib/public/modules/generation-stop.js +7 -1
- package/lib/public/modules/knowledge-workbench-entry.js +43 -0
- package/lib/public/modules/markdown-view-mode.js +34 -0
- package/lib/public/modules/markdown.js +11 -0
- package/lib/public/modules/mate-knowledge-workbench.js +2 -7
- package/lib/public/modules/mate-sidebar.js +1 -1
- package/lib/public/modules/palette-clay-conversation.js +87 -0
- package/lib/public/modules/palette-render-helpers.js +25 -0
- package/lib/public/modules/pane-activation.js +46 -7
- package/lib/public/modules/pane-binding.js +12 -2
- package/lib/public/modules/pane-bridge.js +12 -3
- package/lib/public/modules/pane-deferred-bootstrap.js +32 -0
- package/lib/public/modules/pane-feature-assets.js +35 -0
- package/lib/public/modules/pane-file-bridge.js +8 -3
- package/lib/public/modules/pane-search-receipt.js +12 -0
- package/lib/public/modules/permission-control.js +6 -6
- package/lib/public/modules/popup-chat-rendering.js +244 -0
- package/lib/public/modules/project-activation.js +2 -2
- package/lib/public/modules/qrcode.js +12 -1
- package/lib/public/modules/right-workbench.js +68 -0
- package/lib/public/modules/search-hints.js +79 -0
- package/lib/public/modules/session-context-menu.js +35 -2
- package/lib/public/modules/session-folder-collapse.js +2 -1
- package/lib/public/modules/session-folder-context.js +24 -13
- package/lib/public/modules/session-folder-delete.js +18 -1
- package/lib/public/modules/session-folder-dnd.js +4 -1
- package/lib/public/modules/session-folder-layout.js +7 -6
- package/lib/public/modules/session-search-pane-state.js +30 -0
- package/lib/public/modules/session-search.js +53 -13
- package/lib/public/modules/sidebar-mobile.js +3 -3
- package/lib/public/modules/sidebar-projects.js +1 -0
- package/lib/public/modules/sidebar-session-folders-ui.js +3 -3
- package/lib/public/modules/sidebar-sessions.js +57 -24
- package/lib/public/modules/sidebar.js +20 -3
- package/lib/public/modules/split-group-helpers.js +1 -1
- package/lib/public/modules/split-group-restore.js +8 -11
- package/lib/public/modules/split-pair-ui.js +18 -0
- package/lib/public/modules/split-pane-header-state.js +45 -0
- package/lib/public/modules/split-pane-reconciler.js +23 -4
- package/lib/public/modules/split-pane-renderer.js +330 -23
- package/lib/public/modules/split-pane-retention.js +157 -0
- package/lib/public/modules/split-session-boundary.js +21 -0
- package/lib/public/modules/split-view-create-action.js +15 -0
- package/lib/public/modules/split-view-create-menu.js +83 -0
- package/lib/public/modules/split-view-create-request.js +23 -0
- package/lib/public/modules/split-view-layout-client.js +198 -0
- package/lib/public/modules/split-view-layout-icons.js +37 -0
- package/lib/public/modules/split-view-layout-scope.js +38 -0
- package/lib/public/modules/split-view-organization.js +73 -0
- package/lib/public/modules/split-view-presentation-close.js +35 -0
- package/lib/public/modules/split-view-presentation-drag.js +278 -0
- package/lib/public/modules/split-view-presentation-drop.js +57 -0
- package/lib/public/modules/split-view-presentation-lookup.js +44 -0
- package/lib/public/modules/split-view-presentation-renderer.js +494 -0
- package/lib/public/modules/split-view-presentations.js +498 -0
- package/lib/public/modules/split-view-shell-controls.js +28 -0
- package/lib/public/modules/split-view-sidebar-menu.js +42 -0
- package/lib/public/modules/split-view-sidebar-renderer.js +30 -0
- package/lib/public/modules/split-view-singleton.js +21 -0
- package/lib/public/modules/split-view-worker-activity.js +94 -0
- package/lib/public/modules/split-view-worker-controls.js +30 -0
- package/lib/public/modules/split-view-worker-removal.js +137 -0
- package/lib/public/modules/split-view-worker-scroll.js +57 -0
- package/lib/public/modules/split-view-worker-transcript.js +46 -0
- package/lib/public/modules/split-view-workers.js +418 -0
- package/lib/public/modules/split-view.js +61 -22
- package/lib/public/modules/sticky-notes.js +9 -2
- package/lib/public/modules/tools.js +25 -5
- package/lib/public/modules/workbench-layout-preference.js +76 -0
- package/lib/public/modules/worker-created-presentation.js +37 -0
- package/lib/public/pane-assets/app-2DRBYY3L.js +1 -0
- package/lib/public/pane-assets/chunk-4BM7UVNG.js +2 -0
- package/lib/public/pane-assets/chunk-6SM2WQJX.js +1 -0
- package/lib/public/pane-assets/chunk-7H36SUZA.js +2 -0
- package/lib/public/pane-assets/chunk-C4RVVYNT.js +1 -0
- package/lib/public/pane-assets/chunk-DGOFGEUK.js +1 -0
- package/lib/public/pane-assets/chunk-I57URYSL.js +1 -0
- package/lib/public/pane-assets/chunk-O6MQL6MR.js +167 -0
- package/lib/public/pane-assets/chunk-PO2JOHHW.js +1 -0
- package/lib/public/pane-assets/chunk-UMGHINOT.js +1 -0
- package/lib/public/pane-assets/manifest.json +553 -0
- package/lib/public/pane-assets/style-63EZCREE.css +1 -0
- package/lib/public/style.css +5 -1
- package/lib/server-owner-ui.js +6 -2
- package/lib/server-owner-view-observation.js +34 -0
- package/lib/server-palette.js +1 -0
- package/lib/server-pane-bootstrap.js +24 -0
- package/lib/server-settings.js +2 -0
- package/lib/server-static-assets.js +52 -0
- package/lib/server-workbench-layout-settings.js +56 -0
- package/lib/server.js +6 -45
- package/lib/session-activity-state.js +28 -0
- package/lib/session-list-delivery.js +17 -0
- package/lib/session-pair-turn-control.js +1 -6
- package/lib/session-search.js +3 -0
- package/lib/session-split-group-anchors.js +2 -2
- package/lib/session-split-group-roles.js +5 -8
- package/lib/session-split-group-v2-store.js +0 -3
- package/lib/sessions.js +16 -6
- package/lib/split-view-layout.js +132 -0
- package/lib/users-workbench-layout-preferences.js +33 -0
- package/lib/users.js +3 -0
- package/lib/workspace-query-service.js +1 -5
- package/lib/ws-schema.js +16 -0
- package/package.json +6 -3
|
@@ -39,7 +39,7 @@ export function splitGroupRoles(group) {
|
|
|
39
39
|
var driverId = pair.driverId;
|
|
40
40
|
var workerIds = hasWorkerIds ? (Array.isArray(pair.workerIds) ? pair.workerIds.slice() : []) :
|
|
41
41
|
(pair.workerId !== undefined ? [pair.workerId] : []);
|
|
42
|
-
if (!Number.isInteger(driverId) || workerIds.length < 1
|
|
42
|
+
if (!Number.isInteger(driverId) || workerIds.length < 1) return null;
|
|
43
43
|
if (group.members.indexOf(driverId) === -1) return null;
|
|
44
44
|
var ordered = [driverId];
|
|
45
45
|
for (var i = 0; i < workerIds.length; i++) {
|
|
@@ -26,7 +26,7 @@ export function reconcileSplitIdentity(state, previous, host) {
|
|
|
26
26
|
|
|
27
27
|
// The initial owner response can arrive after history and split metadata.
|
|
28
28
|
// Wait for it so identity cleanup cannot discard panes we just restored.
|
|
29
|
-
export function restoreSplitGroup(
|
|
29
|
+
export function restoreSplitGroup() {
|
|
30
30
|
var state = store.snap();
|
|
31
31
|
if (state.authIdentityLoaded === false || state.isMultiUserMode && !state.myUserId) return;
|
|
32
32
|
if (!isCurrentProjectSessionReady(state, getWs())) return;
|
|
@@ -36,6 +36,10 @@ export function restoreSplitGroup(openGroup) {
|
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
if (state.paneMode) return;
|
|
39
|
+
var activePresentation = (state.projectSplitViews || []).find(function (view) { return view.id === state.projectSplitViewActiveId; });
|
|
40
|
+
if (activePresentation && activePresentation.sessions.some(function (session) {
|
|
41
|
+
return session.id === state.activeSessionId || session.sessionOriginId === state.projectSplitViewFocusedOrigin;
|
|
42
|
+
})) return;
|
|
39
43
|
if (split) {
|
|
40
44
|
var reconciliation = reconcileRestoredSplit(split, state.splitGroups);
|
|
41
45
|
if (reconciliation.action === "clear") {
|
|
@@ -55,14 +59,7 @@ export function restoreSplitGroup(openGroup) {
|
|
|
55
59
|
}
|
|
56
60
|
return;
|
|
57
61
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
for (var i = 0; i < groups.length; i++) {
|
|
62
|
-
var members = groups[i].members || [];
|
|
63
|
-
if (members.indexOf(activeId) !== -1) {
|
|
64
|
-
openGroup(groups[i]);
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
62
|
+
// An execution group is not a presentation choice. Restore an explicitly
|
|
63
|
+
// saved legacy split above, but keep an ordinary selected Driver in its
|
|
64
|
+
// single Pane even when it owns Workers. Worker Controls opens the pair.
|
|
68
65
|
}
|
|
@@ -325,3 +325,21 @@ export function syncPairChrome(host, split) {
|
|
|
325
325
|
livePane.querySelector(".split-pane-header").appendChild(live);
|
|
326
326
|
}
|
|
327
327
|
}
|
|
328
|
+
|
|
329
|
+
// A reconciled pane may move from a legacy Worker pair into a presentation
|
|
330
|
+
// Split view. Remove pair-only role and runtime chrome before refreshing the
|
|
331
|
+
// new session metadata; legacy rendering adds it again immediately afterward.
|
|
332
|
+
export function clearPanePairChrome(paneEl) {
|
|
333
|
+
if (!paneEl) return;
|
|
334
|
+
paneEl.classList.remove("split-pane-role-driver");
|
|
335
|
+
paneEl.classList.remove("split-pane-role-worker");
|
|
336
|
+
if (paneEl.style.removeProperty) {
|
|
337
|
+
paneEl.style.removeProperty("--split-pane-order");
|
|
338
|
+
paneEl.style.removeProperty("--split-worker-row");
|
|
339
|
+
}
|
|
340
|
+
[".split-pair-role", ".split-pair-live", ".split-pair-set-driver", ".split-worker-runtime"].forEach(function (selector) {
|
|
341
|
+
Array.from(paneEl.querySelectorAll(selector)).forEach(function (item) { item.remove(); });
|
|
342
|
+
});
|
|
343
|
+
var header = paneEl.querySelector(".split-pane-header");
|
|
344
|
+
if (header) header.classList.remove("has-worker-runtime");
|
|
345
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getCachedSessions } from './sidebar-sessions.js';
|
|
3
|
+
import { livePaneBinding, paneTarget, samePaneTarget } from './pane-binding.js';
|
|
4
|
+
|
|
5
|
+
function exactReadyPane(paneEl, frame) {
|
|
6
|
+
if (!paneEl || !frame || !frame.clayPane || !store.get('connected') || frame.dataset.requestedSessionId) return false;
|
|
7
|
+
var live = frame.clayPane.identity();
|
|
8
|
+
var sessionId = Number(paneEl.dataset.sessionId);
|
|
9
|
+
var target = live && paneTarget(store.snap(), getCachedSessions(), live.sessionId);
|
|
10
|
+
return !!(live && live.ready && sessionId === live.sessionId && Number(frame.dataset.sessionId) === live.sessionId &&
|
|
11
|
+
live.binding && live.binding.sessionOriginId === frame.dataset.sessionOriginId &&
|
|
12
|
+
samePaneTarget(target, live.binding) && livePaneBinding(frame));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function syncPaneHeaderState(paneEl) {
|
|
16
|
+
var header = paneEl && paneEl.querySelector('.split-pane-header');
|
|
17
|
+
var frame = paneEl && paneEl.querySelector('.split-pane-frame');
|
|
18
|
+
if (!header || !frame) return false;
|
|
19
|
+
var ready = exactReadyPane(paneEl, frame);
|
|
20
|
+
header.setAttribute('aria-busy', ready ? 'false' : 'true');
|
|
21
|
+
header.dataset.paneLoading = ready ? 'false' : 'true';
|
|
22
|
+
header.inert = !ready;
|
|
23
|
+
var actions = header.querySelectorAll('.split-pane-info, .split-pane-search, .split-pane-context, .split-pane-sidebar, .split-view-arrange-trigger');
|
|
24
|
+
for (var i = 0; i < actions.length; i++) {
|
|
25
|
+
var action = actions[i];
|
|
26
|
+
if (!ready) {
|
|
27
|
+
if (action.dataset.panePriorDisabled == null) action.dataset.panePriorDisabled = action.disabled ? 'true' : 'false';
|
|
28
|
+
action.disabled = true;
|
|
29
|
+
} else if (action.dataset.panePriorDisabled != null) {
|
|
30
|
+
action.disabled = action.dataset.panePriorDisabled === 'true';
|
|
31
|
+
delete action.dataset.panePriorDisabled;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
var close = paneEl.querySelector('.split-pane-close');
|
|
35
|
+
if (close) {
|
|
36
|
+
var host = paneEl.parentNode;
|
|
37
|
+
var singletonOnly = !!host && host.id === 'split-host' && host.dataset && host.dataset.singlePane === 'true' &&
|
|
38
|
+
host.querySelectorAll('.split-pane').length === 1;
|
|
39
|
+
close.hidden = singletonOnly;
|
|
40
|
+
close.disabled = !ready || singletonOnly;
|
|
41
|
+
}
|
|
42
|
+
var title = paneEl.querySelector('.split-pane-title');
|
|
43
|
+
if (title) title.setAttribute('aria-disabled', ready ? 'false' : 'true');
|
|
44
|
+
return ready;
|
|
45
|
+
}
|
|
@@ -11,9 +11,11 @@ export function splitPaneIdentity(pane) {
|
|
|
11
11
|
return String(pane && pane.slug || "") + "#" + String(pane && pane.sessionId || "");
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export function reconcileSplitPanes(host, panes, createPane, updatePane) {
|
|
14
|
+
export function reconcileSplitPanes(host, panes, createPane, updatePane, retention) {
|
|
15
15
|
var desired = Array.isArray(panes) ? panes : [];
|
|
16
|
-
var existing = host.querySelectorAll(".split-pane");
|
|
16
|
+
var existing = host.querySelectorAll(retention ? ".split-pane, .split-pane-cached" : ".split-pane");
|
|
17
|
+
existing = Array.from(existing);
|
|
18
|
+
if (retention) existing.sort(function (a, b) { return retention.age(a) - retention.age(b); });
|
|
17
19
|
var byKey = new Map();
|
|
18
20
|
for (var i = 0; i < existing.length; i++) byKey.set(existing[i].dataset.splitPaneKey, existing[i]);
|
|
19
21
|
var used = new Set();
|
|
@@ -24,20 +26,37 @@ export function reconcileSplitPanes(host, panes, createPane, updatePane) {
|
|
|
24
26
|
if (!paneEl) {
|
|
25
27
|
for (var ci = 0; ci < existing.length; ci++) {
|
|
26
28
|
var candidate = existing[ci], frame = candidate.querySelector('.split-pane-frame');
|
|
27
|
-
if (!used.has(candidate) && frame && frame.dataset.projectSlug === pane.slug && frame.clayPane &&
|
|
29
|
+
if (candidate.parentNode === host && !used.has(candidate) && (!retention || retention.canRetarget(candidate)) && frame && frame.dataset.projectSlug === pane.slug && frame.clayPane &&
|
|
28
30
|
!desired.some(function (item) { return splitPaneIdentity(item) === candidate.dataset.splitPaneKey; })) {
|
|
29
31
|
paneEl = candidate; break;
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
}
|
|
35
|
+
if (!paneEl && retention) {
|
|
36
|
+
while (host.querySelectorAll('.split-pane, .split-pane-cached').length >= retention.limit) {
|
|
37
|
+
var victim = existing.find(function (item) { return item.parentNode === host && !used.has(item) &&
|
|
38
|
+
!desired.some(function (model) { return splitPaneIdentity(model) === item.dataset.splitPaneKey; }); });
|
|
39
|
+
if (!victim) break;
|
|
40
|
+
retention.evict(victim);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
33
43
|
if (!paneEl) paneEl = createPane(pane, pi);
|
|
44
|
+
if (retention) retention.restore(paneEl);
|
|
34
45
|
paneEl.dataset.splitPaneKey = key;
|
|
35
46
|
updatePane(paneEl, pane, pi);
|
|
47
|
+
if (retention) retention.remember(paneEl, pane);
|
|
36
48
|
if (paneEl.style) paneEl.style.order = String(pi);
|
|
37
49
|
used.add(paneEl);
|
|
38
50
|
if (!paneEl.parentNode) host.appendChild(paneEl);
|
|
39
51
|
}
|
|
40
52
|
for (var ei = existing.length - 1; ei >= 0; ei--) {
|
|
41
|
-
if (!used.has(existing[ei])
|
|
53
|
+
if (!used.has(existing[ei]) && existing[ei].parentNode === host) {
|
|
54
|
+
if (retention && retention.retain(existing[ei])) retention.park(existing[ei]);
|
|
55
|
+
else if (retention) retention.evict(existing[ei]);
|
|
56
|
+
else {
|
|
57
|
+
if (existing[ei].dispatchEvent) existing[ei].dispatchEvent(new Event('clay-pane-dispose'));
|
|
58
|
+
existing[ei].remove();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
42
61
|
}
|
|
43
62
|
}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { store } from './store.js';
|
|
2
2
|
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { copyToClipboard } from './utils.js';
|
|
3
4
|
import { getCachedSessions } from './sidebar-sessions.js';
|
|
4
5
|
import { iconHtml } from './icons.js';
|
|
5
6
|
import { formatTokens } from './app-panels.js';
|
|
6
7
|
import { VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
|
|
7
8
|
import { createPermissionControl, bindPermissionControl, renderPermissionControl } from './permission-control.js';
|
|
8
9
|
import { splitGroupRoles } from './split-group-helpers.js';
|
|
9
|
-
import { paneTarget, livePaneBinding, samePaneTarget } from './pane-binding.js';
|
|
10
|
-
import { syncPairChrome } from './split-pair-ui.js';
|
|
10
|
+
import { paneTarget, livePaneBinding, samePaneTarget, samePaneBinding } from './pane-binding.js';
|
|
11
|
+
import { syncPairChrome, clearPanePairChrome } from './split-pair-ui.js';
|
|
12
|
+
import { renderPaneWorkers } from './split-view-workers.js';
|
|
13
|
+
import { toggleSidebarCollapse } from './sidebar.js';
|
|
14
|
+
import { syncPaneHeaderState } from './split-pane-header-state.js';
|
|
11
15
|
|
|
12
16
|
function sessionById(sessionId) {
|
|
13
17
|
var sessions = getCachedSessions() || [];
|
|
@@ -25,19 +29,219 @@ function headerBindingStamp(frame) {
|
|
|
25
29
|
live && live.binding && live.binding.requestId, live && live.binding && live.binding.generation]);
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
function capturePaneSearchReceipt(paneEl, frame, binding) {
|
|
33
|
+
var live = frame.clayPane && frame.clayPane.identity(), state = store.snap();
|
|
34
|
+
if (!live || !live.ready || !binding || !samePaneBinding(live.binding, binding) || !livePaneBinding(frame, { paneBinding: binding }) ||
|
|
35
|
+
live.sessionId !== Number(frame.dataset.sessionId) || binding.sessionOriginId !== frame.dataset.sessionOriginId ||
|
|
36
|
+
Number(paneEl.dataset.sessionId) !== live.sessionId || paneEl.hidden || paneEl.inert || frame.closest('.split-pane-cached') ||
|
|
37
|
+
state.paneSuspended || !state.connected || !getWs()) return null;
|
|
38
|
+
return { binding: binding, source: frame.contentWindow, document: frame.contentDocument, socket: getWs(),
|
|
39
|
+
projectSlug: state.currentSlug, ownerId: state.myUserId, sessionId: live.sessionId, sessionOriginId: binding.sessionOriginId };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function paneSearchReceiptCurrent(paneEl, frame, receipt) {
|
|
43
|
+
if (!receipt || !paneEl.isConnected || paneEl.hidden || paneEl.inert || frame.closest('.split-pane-cached') ||
|
|
44
|
+
frame.contentWindow !== receipt.source || frame.contentDocument !== receipt.document || getWs() !== receipt.socket) return false;
|
|
45
|
+
var state = store.snap(), live = frame.clayPane && frame.clayPane.identity();
|
|
46
|
+
return state.connected && !state.paneSuspended && state.currentSlug === receipt.projectSlug && state.myUserId === receipt.ownerId &&
|
|
47
|
+
live && live.ready && live.sessionId === receipt.sessionId && live.binding.sessionOriginId === receipt.sessionOriginId &&
|
|
48
|
+
samePaneBinding(live.binding, receipt.binding) && livePaneBinding(frame, { paneBinding: receipt.binding }) &&
|
|
49
|
+
Number(paneEl.dataset.sessionId) === receipt.sessionId && Number(frame.dataset.sessionId) === receipt.sessionId &&
|
|
50
|
+
frame.dataset.sessionOriginId === receipt.sessionOriginId;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function sendPaneSearchCommand(paneEl, frame, receipt, action, query) {
|
|
54
|
+
if (!paneSearchReceiptCurrent(paneEl, frame, receipt) || !['open', 'query', 'next', 'previous', 'close'].includes(action)) return false;
|
|
55
|
+
frame.contentWindow.postMessage({ type: "clay-pane-search-command", paneBinding: receipt.binding, action: action, query: query }, window.location.origin);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
var paneSearchDismissalInstalled = false;
|
|
60
|
+
|
|
61
|
+
function createPaneSearchPopover(paneEl, frame, searchButton) {
|
|
62
|
+
var popover = document.createElement("div");
|
|
63
|
+
popover.className = "split-pane-search-popover";
|
|
64
|
+
popover.hidden = true;
|
|
65
|
+
popover.setAttribute("role", "search");
|
|
66
|
+
var input = document.createElement("input");
|
|
67
|
+
input.type = "search"; input.className = "split-pane-search-input";
|
|
68
|
+
input.placeholder = "Search this session"; input.autocomplete = "off"; input.spellcheck = false;
|
|
69
|
+
input.setAttribute("aria-label", "Search this session");
|
|
70
|
+
var count = document.createElement("span"); count.className = "split-pane-search-count"; count.setAttribute("aria-live", "polite");
|
|
71
|
+
var paneHeader = paneEl.querySelector(".split-pane-header");
|
|
72
|
+
var receipt = null;
|
|
73
|
+
var timer = null;
|
|
74
|
+
var disposed = false;
|
|
75
|
+
var actionGesture = null;
|
|
76
|
+
var headerObserver = null;
|
|
77
|
+
var unsubscribe = null;
|
|
78
|
+
function setSearchTop() {
|
|
79
|
+
if (!popover.hidden && paneHeader && paneEl.getBoundingClientRect && paneHeader.getBoundingClientRect && paneEl.style && paneEl.style.setProperty) {
|
|
80
|
+
var paneRect = paneEl.getBoundingClientRect(), headerRect = paneHeader.getBoundingClientRect();
|
|
81
|
+
paneEl.style.setProperty('--split-pane-search-top', Math.max(0, headerRect.bottom - paneRect.top) + 'px');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function actionButton(label, action, icon) {
|
|
85
|
+
var button = document.createElement("button"); button.type = "button";
|
|
86
|
+
button.className = "split-pane-search-action"; button.title = label; button.setAttribute("aria-label", label);
|
|
87
|
+
button.innerHTML = iconHtml(icon);
|
|
88
|
+
function arm(event) { if (event.type === 'pointerdown' || event.key === 'Enter' || event.key === ' ') actionGesture = { receipt: receipt }; }
|
|
89
|
+
button.addEventListener('pointerdown', arm);
|
|
90
|
+
button.addEventListener('keydown', arm);
|
|
91
|
+
button.addEventListener("click", function () {
|
|
92
|
+
var gesture = actionGesture; actionGesture = null;
|
|
93
|
+
var captured = gesture ? gesture.receipt : receipt;
|
|
94
|
+
if (!disposed && captured === receipt && sendPaneSearchCommand(paneEl, frame, captured, action)) input.focus();
|
|
95
|
+
});
|
|
96
|
+
return button;
|
|
97
|
+
}
|
|
98
|
+
var previous = actionButton("Previous match", "previous", "chevron-up");
|
|
99
|
+
var next = actionButton("Next match", "next", "chevron-down");
|
|
100
|
+
var close = document.createElement("button"); close.type = "button"; close.className = "split-pane-search-action";
|
|
101
|
+
close.title = "Close search"; close.setAttribute("aria-label", "Close search"); close.innerHTML = iconHtml("x");
|
|
102
|
+
input.addEventListener("input", function () {
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
var openedReceipt = receipt;
|
|
105
|
+
timer = setTimeout(function () {
|
|
106
|
+
if (!disposed && openedReceipt === receipt) sendPaneSearchCommand(paneEl, frame, openedReceipt, "query", input.value);
|
|
107
|
+
}, 180);
|
|
108
|
+
});
|
|
109
|
+
input.addEventListener("keydown", function (event) {
|
|
110
|
+
if (event.key === "Enter") { event.preventDefault(); sendPaneSearchCommand(paneEl, frame, receipt, event.shiftKey ? "previous" : "next"); }
|
|
111
|
+
if (event.key === "Escape") { event.preventDefault(); closeSearch(); }
|
|
112
|
+
});
|
|
113
|
+
popover.addEventListener("click", function (event) { event.stopPropagation(); });
|
|
114
|
+
function closeSearch(restoreFocus, sendClose, expectedReceipt) {
|
|
115
|
+
if (disposed) return;
|
|
116
|
+
if (expectedReceipt && expectedReceipt !== receipt) return;
|
|
117
|
+
var closingReceipt = receipt;
|
|
118
|
+
clearTimeout(timer);
|
|
119
|
+
if (sendClose !== false) sendPaneSearchCommand(paneEl, frame, closingReceipt, "close");
|
|
120
|
+
receipt = null;
|
|
121
|
+
popover.hidden = true; searchButton.setAttribute("aria-expanded", "false");
|
|
122
|
+
if (restoreFocus !== false) searchButton.focus();
|
|
123
|
+
}
|
|
124
|
+
function armClose(event) { if (event.type === 'pointerdown' || event.key === 'Enter' || event.key === ' ') actionGesture = { receipt: receipt }; }
|
|
125
|
+
close.addEventListener('pointerdown', armClose);
|
|
126
|
+
close.addEventListener('keydown', armClose);
|
|
127
|
+
close.addEventListener("click", function () {
|
|
128
|
+
var gesture = actionGesture; actionGesture = null;
|
|
129
|
+
if (!disposed && (!gesture || gesture.receipt === receipt)) closeSearch();
|
|
130
|
+
});
|
|
131
|
+
popover.closePaneSearch = closeSearch;
|
|
132
|
+
popover.getPaneSearchReceipt = function () { return receipt; };
|
|
133
|
+
popover.openPaneSearch = function (binding) {
|
|
134
|
+
if (disposed) return false;
|
|
135
|
+
var captured = capturePaneSearchReceipt(paneEl, frame, binding);
|
|
136
|
+
if (!captured) return false;
|
|
137
|
+
receipt = captured;
|
|
138
|
+
popover.hidden = false; searchButton.setAttribute("aria-expanded", "true");
|
|
139
|
+
setSearchTop(); sendPaneSearchCommand(paneEl, frame, receipt, "open", input.value); input.focus(); input.select();
|
|
140
|
+
return true;
|
|
141
|
+
};
|
|
142
|
+
if (!paneSearchDismissalInstalled) {
|
|
143
|
+
paneSearchDismissalInstalled = true;
|
|
144
|
+
document.addEventListener("pointerdown", function (event) {
|
|
145
|
+
var openPopovers = document.querySelectorAll(".split-pane-search-popover:not([hidden])");
|
|
146
|
+
for (var i = 0; i < openPopovers.length; i++) {
|
|
147
|
+
var openPopover = openPopovers[i];
|
|
148
|
+
var owner = openPopover.closest(".split-pane");
|
|
149
|
+
var trigger = owner && owner.querySelector(".split-pane-search");
|
|
150
|
+
if (!openPopover.contains(event.target) && event.target !== trigger && openPopover.closePaneSearch) openPopover.closePaneSearch(false);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
searchButton.setAttribute("aria-expanded", "false");
|
|
155
|
+
searchButton.addEventListener("click", function (event) {
|
|
156
|
+
event.stopPropagation();
|
|
157
|
+
if (!syncPaneHeaderState(paneEl)) return;
|
|
158
|
+
var live = frame.clayPane && frame.clayPane.identity();
|
|
159
|
+
if (!live || !live.ready || !livePaneBinding(frame) || live.sessionId !== Number(frame.dataset.sessionId) ||
|
|
160
|
+
live.binding.sessionOriginId !== frame.dataset.sessionOriginId || Number(paneEl.dataset.sessionId) !== live.sessionId) return;
|
|
161
|
+
if (!popover.hidden) { closeSearch(); return; }
|
|
162
|
+
popover.openPaneSearch(live.binding);
|
|
163
|
+
});
|
|
164
|
+
function resetForBindingChange() { closeSearch(false, false); input.value = ""; count.textContent = ""; syncPaneHeaderState(paneEl); }
|
|
165
|
+
function onStoreChange() {
|
|
166
|
+
if (receipt && !paneSearchReceiptCurrent(paneEl, frame, receipt)) resetForBindingChange();
|
|
167
|
+
else syncPaneHeaderState(paneEl);
|
|
168
|
+
}
|
|
169
|
+
function dispose() {
|
|
170
|
+
if (disposed) return;
|
|
171
|
+
closeSearch(false, false);
|
|
172
|
+
disposed = true; clearTimeout(timer); timer = null; actionGesture = null;
|
|
173
|
+
frame.removeEventListener("clay-pane-retarget", resetForBindingChange);
|
|
174
|
+
if (paneHeader) paneHeader.removeEventListener("clay-pane-retarget", resetForBindingChange);
|
|
175
|
+
frame.removeEventListener("clay-pane-activation-failed", resetForBindingChange);
|
|
176
|
+
frame.removeEventListener("clay-pane-suspend", resetForBindingChange);
|
|
177
|
+
if (unsubscribe) { unsubscribe(); unsubscribe = null; }
|
|
178
|
+
window.removeEventListener("resize", setSearchTop);
|
|
179
|
+
if (headerObserver) { headerObserver.disconnect(); headerObserver = null; }
|
|
180
|
+
paneEl.removeEventListener('clay-pane-dispose', dispose);
|
|
181
|
+
}
|
|
182
|
+
paneEl.addEventListener('clay-pane-dispose', dispose);
|
|
183
|
+
paneEl.disposePaneSearchPopover = dispose;
|
|
184
|
+
try {
|
|
185
|
+
frame.addEventListener("clay-pane-retarget", resetForBindingChange);
|
|
186
|
+
if (paneHeader) paneHeader.addEventListener("clay-pane-retarget", resetForBindingChange);
|
|
187
|
+
frame.addEventListener("clay-pane-activation-failed", resetForBindingChange);
|
|
188
|
+
frame.addEventListener("clay-pane-suspend", resetForBindingChange);
|
|
189
|
+
unsubscribe = store.subscribe(onStoreChange);
|
|
190
|
+
window.addEventListener("resize", setSearchTop);
|
|
191
|
+
if (typeof ResizeObserver === 'function') {
|
|
192
|
+
headerObserver = new ResizeObserver(setSearchTop);
|
|
193
|
+
if (paneHeader) headerObserver.observe(paneHeader);
|
|
194
|
+
}
|
|
195
|
+
popover.appendChild(input); popover.appendChild(count); popover.appendChild(previous); popover.appendChild(next); popover.appendChild(close);
|
|
196
|
+
paneEl.appendChild(popover);
|
|
197
|
+
return { popover: popover, input: input, count: count, close: closeSearch, button: searchButton };
|
|
198
|
+
} catch (error) {
|
|
199
|
+
dispose();
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function handlePaneSearchMessage(event, host) {
|
|
205
|
+
var frames = host ? host.querySelectorAll(".split-pane-frame") : [];
|
|
206
|
+
for (var i = 0; i < frames.length; i++) {
|
|
207
|
+
var frame = frames[i], live = frame.clayPane && frame.clayPane.identity();
|
|
208
|
+
if (frame.contentWindow !== event.source || !live || !livePaneBinding(frame, event.data) || live.sessionId !== Number(frame.dataset.sessionId)) continue;
|
|
209
|
+
var paneEl = frame.closest(".split-pane"), popover = paneEl && paneEl.querySelector(".split-pane-search-popover");
|
|
210
|
+
if (!popover) return true;
|
|
211
|
+
if (event.data.type === "clay-pane-search-status") {
|
|
212
|
+
var count = popover.querySelector(".split-pane-search-count");
|
|
213
|
+
var input = popover.querySelector(".split-pane-search-input");
|
|
214
|
+
var receipt = popover.getPaneSearchReceipt && popover.getPaneSearchReceipt();
|
|
215
|
+
if (receipt && samePaneBinding(receipt.binding, live.binding) && paneSearchReceiptCurrent(paneEl, frame, receipt)) {
|
|
216
|
+
if (event.data.open === false) popover.closePaneSearch(true, false, receipt);
|
|
217
|
+
else if (!popover.hidden && event.data.query === input.value.trim()) count.textContent = event.data.status || "";
|
|
218
|
+
}
|
|
219
|
+
} else if (event.data.type === "clay-pane-search-open" && popover.hidden) {
|
|
220
|
+
var input = popover.querySelector('.split-pane-search-input');
|
|
221
|
+
popover.openPaneSearch(live.binding);
|
|
222
|
+
}
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
28
228
|
function startPaneRename(header, titleEl, pane) {
|
|
29
229
|
if (header.querySelector(".split-pane-rename-input")) return;
|
|
230
|
+
var section = header.parentNode, frame = section.querySelector('.split-pane-frame');
|
|
231
|
+
var initial = frame && frame.clayPane && frame.clayPane.identity();
|
|
232
|
+
if (!initial || !initial.ready || !livePaneBinding(frame) || initial.sessionId !== pane.sessionId ||
|
|
233
|
+
initial.binding.sessionOriginId !== frame.dataset.sessionOriginId) return;
|
|
30
234
|
var input = document.createElement("input");
|
|
31
235
|
input.type = "text"; input.className = "split-pane-rename-input"; input.value = pane.title;
|
|
32
236
|
titleEl.style.display = "none"; header.insertBefore(input, titleEl); input.focus(); input.select();
|
|
33
237
|
var done = false;
|
|
34
|
-
var
|
|
35
|
-
var origin = frame.dataset.sessionOriginId, binding = frame.clayPane && frame.clayPane.identity().binding, socket = getWs();
|
|
238
|
+
var origin = frame.dataset.sessionOriginId, binding = initial.binding, socket = getWs();
|
|
36
239
|
function current() {
|
|
37
240
|
var live = frame.clayPane && frame.clayPane.identity();
|
|
38
241
|
return header.isConnected && !header.inert && socket === getWs() && store.get('connected') &&
|
|
39
242
|
frame.dataset.sessionId === String(pane.sessionId) && frame.dataset.sessionOriginId === origin && !frame.dataset.requestedSessionId &&
|
|
40
|
-
|
|
243
|
+
!!live && live.ready && live.sessionId === pane.sessionId && live.binding.sessionOriginId === origin &&
|
|
244
|
+
livePaneBinding(frame) && samePaneBinding(live.binding, binding);
|
|
41
245
|
}
|
|
42
246
|
function finish(commit) {
|
|
43
247
|
if (done) return;
|
|
@@ -86,11 +290,11 @@ export function updatePaneFullAccessButton(button, session) {
|
|
|
86
290
|
export function createSplitPane(pane, closePane) {
|
|
87
291
|
var paneEl = document.createElement("section"); paneEl.className = "split-pane";
|
|
88
292
|
var header = document.createElement("header"); header.className = "split-pane-header"; header.title = pane.title;
|
|
89
|
-
var session = sessionById(pane.sessionId); var vendor = session && session.vendor || "
|
|
90
|
-
var vendorIcon = document.createElement("img"); vendorIcon.className = "split-pane-vendor";
|
|
91
|
-
vendorIcon.src = VENDOR_AVATARS[vendor]
|
|
293
|
+
var session = sessionById(pane.sessionId); var vendor = session && session.vendor || "unknown";
|
|
294
|
+
var vendorIcon = document.createElement("img"); vendorIcon.className = "split-pane-vendor"; vendorIcon.hidden = !VENDOR_AVATARS[vendor];
|
|
295
|
+
if (!vendorIcon.hidden) vendorIcon.src = VENDOR_AVATARS[vendor]; vendorIcon.alt = ""; vendorIcon.title = VENDOR_NAMES[vendor] || vendor; header.appendChild(vendorIcon);
|
|
92
296
|
var title = document.createElement("span"); title.className = "split-pane-title"; title.textContent = pane.title; title.title = "Rename session";
|
|
93
|
-
title.addEventListener("click", function () { var current = sessionById(Number(paneEl.dataset.sessionId)); if (current) startPaneRename(header, title, { sessionId: current.id, title: current.title }); }); header.appendChild(title);
|
|
297
|
+
title.addEventListener("click", function () { if (title.getAttribute('aria-disabled') === 'true' || !syncPaneHeaderState(paneEl)) return; var current = sessionById(Number(paneEl.dataset.sessionId)); if (current) startPaneRename(header, title, { sessionId: current.id, title: current.title }); }); header.appendChild(title);
|
|
94
298
|
var frame = document.createElement("iframe"); frame.className = "split-pane-frame"; frame.dataset.projectSlug = pane.slug;
|
|
95
299
|
frame.dataset.sessionId = String(pane.sessionId); frame.src = paneUrl(pane); frame.title = pane.title;
|
|
96
300
|
frame.dataset.sessionOriginId = session && session.sessionOriginId || '';
|
|
@@ -109,12 +313,38 @@ export function createSplitPane(pane, closePane) {
|
|
|
109
313
|
});
|
|
110
314
|
var ctxChip = document.createElement("button"); ctxChip.type = "button"; ctxChip.className = "split-pane-context"; ctxChip.title = "Context usage";
|
|
111
315
|
ctxChip.innerHTML = '<span class="split-pane-context-bar"><span class="split-pane-context-fill"></span></span><span class="split-pane-context-label"></span>';
|
|
112
|
-
ctxChip.addEventListener("click", function () { if (frame.contentWindow &&
|
|
316
|
+
ctxChip.addEventListener("click", function () { if (syncPaneHeaderState(paneEl) && frame.contentWindow && frame.clayPane && livePaneBinding(frame)) frame.contentWindow.postMessage({ type: "clay-pane-toggle-context", paneBinding: frame.clayPane.identity().binding }, window.location.origin); }); header.appendChild(ctxChip);
|
|
113
317
|
var fullAccess = createPermissionControl("split-pane-full-access hidden", "Driver session permission mode");
|
|
114
318
|
updatePaneFullAccessButton(fullAccess, session); bindPermissionControl(fullAccess, function () { return permissionState(sessionById(Number(paneEl.dataset.sessionId))); }); header.appendChild(fullAccess); header.insertBefore(fullAccess, ctxChip);
|
|
115
319
|
var close = document.createElement("button"); close.type = "button"; close.className = "split-pane-close"; close.title = "Close pane"; close.setAttribute("aria-label", "Close pane"); close.innerHTML = iconHtml("x");
|
|
116
|
-
|
|
320
|
+
if (store.get('projectSplitViewSingletonOrigin') === (session && session.sessionOriginId)) {
|
|
321
|
+
close.hidden = true; close.title = "The only pane cannot be closed"; close.setAttribute("aria-label", "The only pane cannot be closed");
|
|
322
|
+
}
|
|
323
|
+
close.dataset.sessionId = String(pane.sessionId); close.addEventListener("click", function () { if (syncPaneHeaderState(paneEl)) closePane(Number(this.dataset.sessionId)); }); header.appendChild(close);
|
|
324
|
+
var info = document.createElement("button"); info.type = "button"; info.className = "split-pane-info"; info.setAttribute("aria-label", "Session information"); info.title = "Session information"; info.innerHTML = iconHtml("info");
|
|
325
|
+
info.addEventListener("click", function (event) { event.stopPropagation(); if (syncPaneHeaderState(paneEl)) togglePaneInfo(info, frame); }); header.appendChild(info);
|
|
326
|
+
var sidebarButton = document.createElement("button"); sidebarButton.type = "button"; sidebarButton.className = "split-pane-sidebar";
|
|
327
|
+
var sidebarExpanded = !document.getElementById("layout").classList.contains("sidebar-collapsed");
|
|
328
|
+
sidebarButton.setAttribute("aria-label", sidebarExpanded ? "Collapse sidebar" : "Open sidebar");
|
|
329
|
+
sidebarButton.setAttribute("aria-expanded", sidebarExpanded ? "true" : "false");
|
|
330
|
+
sidebarButton.title = sidebarExpanded ? "Collapse sidebar" : "Open sidebar"; sidebarButton.innerHTML = iconHtml("panel-left");
|
|
331
|
+
sidebarButton.addEventListener("click", function (event) {
|
|
332
|
+
event.stopPropagation();
|
|
333
|
+
if (!syncPaneHeaderState(paneEl)) return;
|
|
334
|
+
var live = frame.clayPane && frame.clayPane.identity();
|
|
335
|
+
var activePane = paneEl.dataset.active === "true" || live && store.get('projectSplitViewSingletonOrigin') === live.binding.sessionOriginId;
|
|
336
|
+
if (!live || !live.ready || !livePaneBinding(frame) || live.sessionId !== Number(frame.dataset.sessionId) ||
|
|
337
|
+
live.binding.sessionOriginId !== frame.dataset.sessionOriginId || !activePane) return;
|
|
338
|
+
toggleSidebarCollapse();
|
|
339
|
+
var expanded = !document.getElementById("layout").classList.contains("sidebar-collapsed");
|
|
340
|
+
sidebarButton.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
341
|
+
sidebarButton.setAttribute("aria-label", expanded ? "Collapse sidebar" : "Open sidebar");
|
|
342
|
+
sidebarButton.title = expanded ? "Collapse sidebar" : "Open sidebar";
|
|
343
|
+
}); header.appendChild(sidebarButton);
|
|
344
|
+
var search = document.createElement("button"); search.type = "button"; search.className = "split-pane-search"; search.setAttribute("aria-label", "Search this session"); search.title = "Search this session"; search.innerHTML = iconHtml("search");
|
|
345
|
+
header.insertBefore(search, close);
|
|
117
346
|
frame.addEventListener('clay-pane-ready', function () {
|
|
347
|
+
if (paneEl.classList.contains('split-pane-cached')) return;
|
|
118
348
|
var id = Number(frame.dataset.requestedSessionId || frame.dataset.sessionId), current = sessionById(id);
|
|
119
349
|
var live = frame.clayPane && frame.clayPane.identity(), target = paneTarget(store.snap(), getCachedSessions(), id);
|
|
120
350
|
if (!current || !live || !live.ready || !samePaneTarget(target, live.binding) || live.sessionId !== id) return;
|
|
@@ -124,24 +354,74 @@ export function createSplitPane(pane, closePane) {
|
|
|
124
354
|
syncPairChrome(paneEl.parentNode, store.get('splitPanes'));
|
|
125
355
|
});
|
|
126
356
|
frame.addEventListener('clay-pane-activation-failed', function () {
|
|
357
|
+
if (paneEl.classList.contains('split-pane-cached')) return;
|
|
358
|
+
syncPaneHeaderState(paneEl);
|
|
127
359
|
var id = Number(frame.dataset.requestedSessionId || frame.dataset.sessionId);
|
|
128
360
|
var fallbackTarget = paneTarget(store.snap(), getCachedSessions(), id);
|
|
129
|
-
if (!fallbackTarget) { header.inert = true; return; }
|
|
361
|
+
if (!fallbackTarget) { header.inert = true; syncPaneHeaderState(paneEl); return; }
|
|
130
362
|
frame.dataset.sessionOriginId = fallbackTarget.sessionOriginId;
|
|
131
363
|
frame.src = paneUrl({ slug: frame.dataset.projectSlug, sessionId: id });
|
|
132
364
|
frame.dataset.sessionId = String(id); delete frame.dataset.requestedSessionId;
|
|
365
|
+
syncPaneHeaderState(paneEl);
|
|
133
366
|
});
|
|
134
|
-
paneEl.appendChild(header);
|
|
367
|
+
paneEl.appendChild(header);
|
|
368
|
+
createPaneSearchPopover(paneEl, frame, search);
|
|
369
|
+
var runtime = document.createElement("div"); runtime.className = "split-view-runtime";
|
|
370
|
+
vendorIcon.classList.add("split-view-runtime-icon"); runtime.appendChild(vendorIcon);
|
|
371
|
+
var runtimeModel = document.createElement("span"); runtimeModel.className = "split-view-model"; runtime.appendChild(runtimeModel);
|
|
372
|
+
header.insertBefore(runtime, title); header.appendChild(close); paneEl.appendChild(frame);
|
|
373
|
+
syncPaneHeaderState(paneEl);
|
|
374
|
+
return paneEl;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function togglePaneInfo(anchor, frame) {
|
|
378
|
+
var existing = document.getElementById("split-pane-info-popover");
|
|
379
|
+
if (existing) { existing.remove(); if (existing.dataset.origin === frame.dataset.sessionOriginId) return; }
|
|
380
|
+
var live = frame.clayPane && frame.clayPane.identity();
|
|
381
|
+
if (!live || !live.ready || !livePaneBinding(frame) || live.binding.sessionOriginId !== frame.dataset.sessionOriginId || live.sessionId !== Number(frame.dataset.sessionId)) return;
|
|
382
|
+
var session = sessionById(live.sessionId);
|
|
383
|
+
if (!session || session.sessionOriginId !== live.binding.sessionOriginId) return;
|
|
384
|
+
var binding = live.binding;
|
|
385
|
+
function current() {
|
|
386
|
+
var currentLive = frame.clayPane && frame.clayPane.identity();
|
|
387
|
+
return anchor.isConnected && !anchor.closest('.split-pane-header').inert && frame.dataset.sessionId === String(session.id) &&
|
|
388
|
+
frame.dataset.sessionOriginId === session.sessionOriginId && !frame.dataset.requestedSessionId && currentLive &&
|
|
389
|
+
currentLive.ready && currentLive.sessionId === session.id && samePaneBinding(currentLive.binding, binding) && livePaneBinding(frame);
|
|
390
|
+
}
|
|
391
|
+
var pop = document.createElement("div"); pop.id = "split-pane-info-popover"; pop.className = "session-info-popover"; pop.dataset.origin = session.sessionOriginId;
|
|
392
|
+
var values = [["Local ID", session.id]];
|
|
393
|
+
if (session.cliSessionId) values.push(["Session ID", session.cliSessionId]);
|
|
394
|
+
var vendor = (store.get("vendorInfo") || {})[session.vendor || "claude"] || {};
|
|
395
|
+
if (session.cliSessionId && vendor.terminalResumeCommand) values.push(["Resume", vendor.terminalResumeCommand + " " + session.cliSessionId]);
|
|
396
|
+
values.forEach(function (entry) {
|
|
397
|
+
var row = document.createElement("div"); row.className = "info-row";
|
|
398
|
+
var label = document.createElement("span"); label.className = "info-label"; label.textContent = entry[0];
|
|
399
|
+
var value = document.createElement("span"); value.className = "info-value"; value.textContent = String(entry[1]);
|
|
400
|
+
var copy = document.createElement("button"); copy.type = "button"; copy.className = "info-copy-btn"; copy.title = "Copy " + entry[0]; copy.setAttribute("aria-label", "Copy " + entry[0]); copy.innerHTML = iconHtml("copy");
|
|
401
|
+
copy.addEventListener("click", function () { if (!current()) { pop.remove(); return; } copyToClipboard(String(entry[1])).then(function () { if (pop.isConnected && current()) copy.innerHTML = iconHtml("check"); }); });
|
|
402
|
+
row.append(label, value, copy); pop.appendChild(row);
|
|
403
|
+
});
|
|
404
|
+
document.body.appendChild(pop);
|
|
405
|
+
var rect = anchor.getBoundingClientRect(); pop.style.top = (rect.bottom + 6) + "px"; pop.style.left = Math.max(8, Math.min(rect.left, window.innerWidth - pop.offsetWidth - 8)) + "px";
|
|
406
|
+
function dismiss(event) { if (event.type === "keydown" && event.key !== "Escape") return; if (event.type === "click" && (pop.contains(event.target) || anchor.contains(event.target))) return; pop.remove(); document.removeEventListener("click", dismiss, true); document.removeEventListener("keydown", dismiss, true); frame.removeEventListener('clay-pane-retarget', dismiss); frame.removeEventListener('clay-pane-ready', dismiss); if (event.type === "keydown") anchor.focus(); }
|
|
407
|
+
document.addEventListener("click", dismiss, true); document.addEventListener("keydown", dismiss, true);
|
|
408
|
+
frame.addEventListener('clay-pane-retarget', dismiss); frame.addEventListener('clay-pane-ready', dismiss);
|
|
135
409
|
}
|
|
136
410
|
|
|
137
411
|
export function updateSplitPaneMetadata(paneEl, pane) {
|
|
412
|
+
clearPanePairChrome(paneEl);
|
|
138
413
|
var session = sessionById(pane.sessionId); var header = paneEl.querySelector(".split-pane-header");
|
|
139
414
|
var title = paneEl.querySelector(".split-pane-title"); var frame = paneEl.querySelector(".split-pane-frame"); var vendor = paneEl.querySelector(".split-pane-vendor");
|
|
140
415
|
var live = frame && frame.clayPane && frame.clayPane.identity();
|
|
141
416
|
var retarget = frame && (Number(frame.dataset.sessionId) !== pane.sessionId || frame.dataset.sessionOriginId !== (session && session.sessionOriginId || '') || frame.clayPane && (!live || !live.ready));
|
|
417
|
+
if (retarget && frame && frame.clayPane) {
|
|
418
|
+
// Invalidate the old ready binding before header sync exposes any action.
|
|
419
|
+
frame.dataset.requestedSessionId = String(pane.sessionId);
|
|
420
|
+
}
|
|
142
421
|
if (retarget && header) {
|
|
143
422
|
header.dispatchEvent(new Event('clay-pane-retarget'));
|
|
144
423
|
header.inert = true;
|
|
424
|
+
syncPaneHeaderState(paneEl);
|
|
145
425
|
header.title = 'Loading conversation';
|
|
146
426
|
if (title) { title.style.display = ''; title.textContent = 'Loading conversation'; }
|
|
147
427
|
if (vendor) vendor.hidden = true;
|
|
@@ -151,24 +431,51 @@ export function updateSplitPaneMetadata(paneEl, pane) {
|
|
|
151
431
|
}
|
|
152
432
|
if (frame && frame.clayPane && retarget) {
|
|
153
433
|
var target = paneTarget(store.snap(), getCachedSessions(), pane.sessionId);
|
|
154
|
-
if (target && frame.clayPane.source === frame.contentWindow && frame.clayPane.document === frame.contentDocument
|
|
155
|
-
frame.
|
|
156
|
-
|
|
157
|
-
|
|
434
|
+
if (target && frame.clayPane.source === frame.contentWindow && frame.clayPane.document === frame.contentDocument) {
|
|
435
|
+
if (frame.clayPane.activate(pane.sessionId, false, target)) {
|
|
436
|
+
if (header) header.inert = true;
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
// A same-target document can expose its controller before its socket and
|
|
440
|
+
// identity are ready. Its bootstrap retries activation; reloading here
|
|
441
|
+
// restarts that bootstrap on every metadata update.
|
|
442
|
+
if ((!live || !live.ready) && Number(frame.dataset.sessionId) === pane.sessionId &&
|
|
443
|
+
frame.dataset.projectSlug === pane.slug && frame.dataset.sessionOriginId === target.sessionOriginId) return;
|
|
158
444
|
}
|
|
445
|
+
delete frame.dataset.requestedSessionId;
|
|
159
446
|
frame.src = paneUrl(pane);
|
|
160
447
|
frame.dataset.sessionId = String(pane.sessionId); frame.dataset.projectSlug = pane.slug;
|
|
161
448
|
frame.dataset.sessionOriginId = session && session.sessionOriginId || '';
|
|
162
449
|
}
|
|
163
|
-
if (header) header.inert = false;
|
|
164
450
|
paneEl.dataset.sessionId = String(pane.sessionId); paneEl.dataset.projectSlug = pane.slug;
|
|
451
|
+
paneEl.dataset.sessionOrigin = session && session.sessionOriginId || "";
|
|
165
452
|
if (header) header.title = pane.title; if (title && title.style.display !== "none") title.textContent = pane.title; if (frame) frame.title = pane.title;
|
|
166
|
-
|
|
453
|
+
var sidebarButton = paneEl.querySelector('.split-pane-sidebar');
|
|
454
|
+
if (sidebarButton) sidebarButton.hidden = store.get('projectSplitViewSingletonOrigin') !== (session && session.sessionOriginId) && paneEl.dataset.active !== 'true';
|
|
455
|
+
if (vendor) { var vendorName = session && session.vendor || "unknown"; vendor.hidden = !VENDOR_AVATARS[vendorName]; if (!vendor.hidden) vendor.src = VENDOR_AVATARS[vendorName]; vendor.title = VENDOR_NAMES[vendorName] || vendorName; }
|
|
456
|
+
var runtime = paneEl.querySelector('.split-view-runtime');
|
|
457
|
+
var runtimeName = session && session.vendor || "unknown", runtimeModelName = session && typeof session.model === "string" && session.model.trim() || "Model unavailable";
|
|
458
|
+
var runtimeIcon = runtime.querySelector('.split-view-runtime-icon'), runtimeModel = runtime.querySelector('.split-view-model');
|
|
459
|
+
if (runtimeIcon) { runtimeIcon.hidden = !VENDOR_AVATARS[runtimeName]; if (!runtimeIcon.hidden) runtimeIcon.src = VENDOR_AVATARS[runtimeName]; runtimeIcon.title = VENDOR_NAMES[runtimeName] || runtimeName; runtimeIcon.alt = ""; }
|
|
460
|
+
if (runtimeModel) { runtimeModel.textContent = runtimeModelName; runtimeModel.title = (VENDOR_NAMES[runtimeName] || runtimeName) + " · " + runtimeModelName; }
|
|
461
|
+
runtime.setAttribute("aria-label", (VENDOR_NAMES[runtimeName] || runtimeName) + " · " + runtimeModelName);
|
|
462
|
+
renderPaneWorkers(paneEl, session);
|
|
167
463
|
updatePaneFullAccessButton(paneEl.querySelector('.split-pane-full-access'), session);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
464
|
+
syncPaneHeaderState(paneEl);
|
|
465
|
+
updateSplitPaneCloseVisibility(paneEl, pane.sessionId);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export function updateSplitPaneCloseVisibility(paneEl, sessionId) {
|
|
469
|
+
var close = paneEl && paneEl.querySelector('.split-pane-close');
|
|
470
|
+
if (!close) return;
|
|
471
|
+
var paneHost = paneEl.parentNode;
|
|
472
|
+
var singletonOnly = !!paneHost && paneHost.id === 'split-host' && paneHost.dataset && paneHost.dataset.singlePane === 'true' &&
|
|
473
|
+
paneHost.querySelectorAll(':scope > .split-pane').length === 1;
|
|
474
|
+
if (sessionId != null) close.dataset.sessionId = String(sessionId);
|
|
475
|
+
close.hidden = singletonOnly;
|
|
476
|
+
close.disabled = singletonOnly;
|
|
477
|
+
close.title = singletonOnly ? "The only pane cannot be closed" : "Close pane";
|
|
478
|
+
close.setAttribute("aria-label", close.title);
|
|
172
479
|
}
|
|
173
480
|
|
|
174
481
|
function resetPaneContextChip(paneEl) {
|