clay-server 5.2.1-beta.1 → 5.3.0-beta.1
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 +158 -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 -5
- package/lib/public/css/popup-chat.css +123 -0
- package/lib/public/css/right-workbench.css +50 -18
- package/lib/public/css/split-view-layout.css +460 -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-messages.js +25 -8
- package/lib/public/modules/chat-render-runtime.js +45 -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/generation-stop.js +7 -1
- package/lib/public/modules/knowledge-workbench-entry.js +43 -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-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-context.js +24 -13
- package/lib/public/modules/session-folder-delete.js +18 -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 +254 -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 +495 -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 +352 -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/pane-assets/app-OJEQXBBS.js +1 -0
- package/lib/public/pane-assets/chunk-4SMIVAB4.js +1 -0
- package/lib/public/pane-assets/chunk-623V5CEO.js +1 -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-DGOFGEUK.js +1 -0
- package/lib/public/pane-assets/chunk-GML2R5JE.js +167 -0
- package/lib/public/pane-assets/chunk-I57URYSL.js +1 -0
- package/lib/public/pane-assets/chunk-I5P3MWQG.js +1 -0
- package/lib/public/pane-assets/chunk-UVGOPFEX.js +2 -0
- package/lib/public/pane-assets/manifest.json +551 -0
- package/lib/public/pane-assets/style-GN7R6XKV.css +1 -0
- package/lib/public/style.css +5 -1
- package/lib/sdk-bridge.js +16 -3
- 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 +15 -0
- package/lib/yoke/adapters/codex.js +1 -0
- package/lib/yoke/codex-app-server.js +3 -1
- package/lib/yoke/codex-idle-recovery.js +73 -20
- package/lib/yoke/codex-recovery-admission.js +65 -4
- package/package.json +6 -3
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import { preserveReconnectingPresentation } from './split-pane-retention.js';
|
|
2
|
+
import { layoutScope, currentLayoutScope, presentationTree } from './split-view-layout-scope.js';
|
|
3
|
+
import { validPresentationClose } from './split-view-presentation-close.js';
|
|
4
|
+
import { applyPresentationDrop } from './split-view-presentation-drop.js';
|
|
5
|
+
import { closeSessionContextMenu } from './session-context-menu.js';
|
|
6
|
+
import { openSplitViewMenu, closeSplitViewMenu as closeGroupMenus } from './split-view-sidebar-menu.js';
|
|
7
|
+
import { store } from './store.js';
|
|
8
|
+
import { organizeViewAction } from './split-view-organization.js';
|
|
9
|
+
import { getWs } from './ws-ref.js';
|
|
10
|
+
import { getCachedSessions } from './sidebar-sessions.js';
|
|
11
|
+
import { renderPresentationView, initPresentationRenderer, hidePresentationView, closePresentationArrange } from './split-view-presentation-renderer.js';
|
|
12
|
+
import { initPresentationDrag, restorePresentationDrag } from './split-view-presentation-drag.js';
|
|
13
|
+
import { detachTuiView } from './session-tui-view.js';
|
|
14
|
+
import { checkComposerDeparture } from './session-rich-drafts.js';
|
|
15
|
+
import { showToast } from './utils.js';
|
|
16
|
+
import { preset, listIds, validateTree, remove, arrangeRegion } from './split-view-layout-client.js';
|
|
17
|
+
import { viewIdForOrigin, updateActiveSession, exactDriverForOrigin, restorablePresentation } from './split-view-presentation-lookup.js';
|
|
18
|
+
import { groupedSessionIds } from './split-group-helpers.js';
|
|
19
|
+
import { handleSplitViewCreateEvent } from './split-view-create-action.js';
|
|
20
|
+
import { initSplitViewCreateMenu } from './split-view-create-menu.js';
|
|
21
|
+
import { currentSingletonPresentation } from './split-view-singleton.js';
|
|
22
|
+
var host = null;
|
|
23
|
+
var nativeApp = null;
|
|
24
|
+
var mainPanels = null;
|
|
25
|
+
var stickyContainer = null;
|
|
26
|
+
var stickyHome = null;
|
|
27
|
+
var stickyAnchor = null;function contextKey(state) {
|
|
28
|
+
return [state.currentSlug || "", state.currentProjectOwnerId || "", state.myUserId || "", state.projectSplitViewSocketGeneration || 0].join("|");
|
|
29
|
+
}
|
|
30
|
+
function projectReady(state) {
|
|
31
|
+
return getWs() !== state.splitIdentityBlockedSocket && !state.paneMode && !state.dmMode && state.currentSlug && state.activeProjectSlug === state.currentSlug &&
|
|
32
|
+
state.sessionListProjectSlug === state.currentSlug && state.authIdentityLoaded && state.connected;
|
|
33
|
+
}
|
|
34
|
+
function resetForContext() {
|
|
35
|
+
var state = store.snap();
|
|
36
|
+
var key = contextKey(state);
|
|
37
|
+
if (state.projectSplitViewContextKey === key) return false;
|
|
38
|
+
var uncertain = state.projectSplitViewPending ? "Connection or project changed during a Split view change. Refreshing saved state before another change." : "";
|
|
39
|
+
cleanupTransientInteractions(state);
|
|
40
|
+
store.set({ projectSplitViews: [], projectSplitViewRevision: 0, projectSplitViewContextKey: key,
|
|
41
|
+
projectSplitViewHydrated: false, projectSplitViewPending: null, projectSplitViewError: uncertain,
|
|
42
|
+
projectSplitViewActiveId: null, projectSplitViewFocusedOrigin: null, projectSplitViewPreviewTree: null, projectSplitViewStandaloneTarget: null,
|
|
43
|
+
projectSplitViewDrag: null, projectSplitViewDividerDrag: null, projectSplitViewRequestSocket: null,
|
|
44
|
+
projectSplitViewRequestGeneration: null, projectSplitViewRequestOwnerId: null, projectSplitViewRequestSlug: null });
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
function cleanupTransientInteractions(state) {
|
|
48
|
+
var drag = state.projectSplitViewDrag, divider = state.projectSplitViewDividerDrag;
|
|
49
|
+
if (drag) restorePresentationDrag(drag);
|
|
50
|
+
if (divider && divider.frames) divider.frames.forEach(function (item) { if (item.frame.isConnected) item.frame.style.pointerEvents = item.pointerEvents; });
|
|
51
|
+
if (drag || divider) {
|
|
52
|
+
document.body.classList.remove("split-view-resizing");
|
|
53
|
+
var pointerId = drag ? drag.pointerId : divider.pointerId;
|
|
54
|
+
try { if (document.documentElement.hasPointerCapture(pointerId)) document.documentElement.releasePointerCapture(pointerId); } catch (error) {}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function sendMessage(message) {
|
|
58
|
+
var ws = getWs();
|
|
59
|
+
if (!ws || ws.readyState !== 1 || !projectReady(store.snap())) return false;
|
|
60
|
+
try { ws.send(JSON.stringify(message)); return true; } catch (error) { return false; }
|
|
61
|
+
}
|
|
62
|
+
export function requestProjectSplitViews() {
|
|
63
|
+
var state = store.snap();
|
|
64
|
+
if (!projectReady(state)) return false;
|
|
65
|
+
resetForContext();
|
|
66
|
+
var ws = getWs();
|
|
67
|
+
if (!ws || ws.readyState !== 1) return false;
|
|
68
|
+
store.set({ projectSplitViewRequestSocket: ws, projectSplitViewRequestGeneration: state.projectSplitViewSocketGeneration,
|
|
69
|
+
projectSplitViewRequestOwnerId: state.myUserId || null, projectSplitViewRequestSlug: state.currentSlug });
|
|
70
|
+
try { ws.send(JSON.stringify({ type: "project_split_views_request" })); return true; } catch (error) { return false; }
|
|
71
|
+
}
|
|
72
|
+
function exactProjectMessage(msg) {
|
|
73
|
+
var state = store.snap();
|
|
74
|
+
return !!msg && msg.projectSlug === state.currentSlug && projectReady(state) &&
|
|
75
|
+
state.projectSplitViewRequestSlug === state.currentSlug && state.projectSplitViewRequestGeneration === state.projectSplitViewSocketGeneration &&
|
|
76
|
+
state.projectSplitViewRequestOwnerId === (state.myUserId || null) && state.projectSplitViewRequestSocket === getWs();
|
|
77
|
+
}
|
|
78
|
+
function viewExists(views, id) {
|
|
79
|
+
for (var i = 0; i < (views || []).length; i++) if (views[i].id === id) return views[i];
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function viewSessions(view) {
|
|
83
|
+
if (!view || !Array.isArray(view.sessions)) return [];
|
|
84
|
+
var sessions = getCachedSessions() || [], mapped = [];
|
|
85
|
+
for (var i = 0; i < view.sessions.length; i++) {
|
|
86
|
+
var ref = view.sessions[i];
|
|
87
|
+
var session = ref && exactDriverForOrigin(ref.sessionOriginId, sessions);
|
|
88
|
+
if (!session || session.id !== ref.id || !session.sessionOriginId) return [];
|
|
89
|
+
mapped.push(session);
|
|
90
|
+
}
|
|
91
|
+
return mapped;
|
|
92
|
+
}
|
|
93
|
+
export function handleProjectSplitViews(msg) {
|
|
94
|
+
if (!exactProjectMessage(msg)) return false;
|
|
95
|
+
if (!Number.isInteger(msg.revision) || msg.revision < 0 || !Array.isArray(msg.views)) return true;
|
|
96
|
+
var state = store.snap();
|
|
97
|
+
if (state.projectSplitViewHydrated && msg.revision < state.projectSplitViewRevision) return true;
|
|
98
|
+
var nextViews = [];
|
|
99
|
+
var invalidView = false;
|
|
100
|
+
msg.views.forEach(function (view) {
|
|
101
|
+
var ids = view && Array.isArray(view.sessions) ? view.sessions.map(function (session) { return session.sessionOriginId; }) : [];
|
|
102
|
+
if (ids.length < 2 || ids.length > 8 || viewSessions(view).length !== ids.length || !validateTree(view.tree, ids).ok) invalidView = true;
|
|
103
|
+
else nextViews.push(view);
|
|
104
|
+
});
|
|
105
|
+
if (invalidView) {
|
|
106
|
+
store.set({ projectSplitViews: [], projectSplitViewHydrated: false, projectSplitViewError: "Saved Split view references are unavailable. Refresh the session list before changing layouts.",
|
|
107
|
+
projectSplitViewPending: null, projectSplitViewPreviewTree: null, projectSplitViewActiveId: null });
|
|
108
|
+
renderActive();
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
var pending = state.projectSplitViewPending;
|
|
112
|
+
if (state.projectSplitViewDrag || state.projectSplitViewDividerDrag) cleanupTransientInteractions(state);
|
|
113
|
+
var activeId = state.projectSplitViewActiveId;
|
|
114
|
+
if (!viewExists(nextViews, activeId)) {
|
|
115
|
+
var restored = restorablePresentation(state, nextViews, getCachedSessions());
|
|
116
|
+
if (restored) activeId = restored.id;
|
|
117
|
+
}
|
|
118
|
+
var removedActive = activeId && !viewExists(nextViews, activeId) ? activeId : null;
|
|
119
|
+
var completedPending = pending && pending.awaitingBroadcast && msg.revision >= pending.resultRevision ? pending : null;
|
|
120
|
+
var readyPending = completedPending ? null : pending;
|
|
121
|
+
var standaloneTarget = state.projectSplitViewStandaloneTarget || null;
|
|
122
|
+
if (removedActive && completedPending && completedPending.meta && completedPending.meta.separateViewId === removedActive) {
|
|
123
|
+
var survivor = exactDriverForOrigin(completedPending.meta.activeOrigin, getCachedSessions());
|
|
124
|
+
if (survivor) standaloneTarget = { id: survivor.id, origin: survivor.sessionOriginId, previousSessionId: state.activeSessionId, contextKey: contextKey(state) };
|
|
125
|
+
}
|
|
126
|
+
store.set({ projectSplitViews: nextViews, projectSplitViewRevision: msg.revision, projectSplitViewHydrated: true,
|
|
127
|
+
projectSplitViewError: msg.error || "", projectSplitViewStandaloneTarget: standaloneTarget, projectSplitViewActiveId: viewExists(nextViews, activeId) ? activeId : null,
|
|
128
|
+
projectSplitViewFocusedOrigin: viewExists(nextViews, activeId) ? state.projectSplitViewFocusedOrigin : null,
|
|
129
|
+
projectSplitViewPending: readyPending, projectSplitViewPreviewTree: readyPending ? state.projectSplitViewPreviewTree : null,
|
|
130
|
+
projectSplitViewDrag: null });
|
|
131
|
+
if (removedActive && completedPending && completedPending.meta && completedPending.meta.separateViewId === removedActive) {
|
|
132
|
+
var target = exactDriverForOrigin(completedPending.meta.activeOrigin, getCachedSessions());
|
|
133
|
+
if (target) switchToStandalone(target.id);
|
|
134
|
+
}
|
|
135
|
+
if (completedPending && completedPending.meta && completedPending.meta.activateCreatedView && completedPending.meta.createdViewId &&
|
|
136
|
+
viewExists(nextViews, completedPending.meta.createdViewId)) setActiveView(completedPending.meta.createdViewId);
|
|
137
|
+
if (completedPending && completedPending.meta && completedPending.meta.closedOrigin) {
|
|
138
|
+
var meta = completedPending.meta;
|
|
139
|
+
if (store.get('projectSplitViewFocusedOrigin') === meta.closedOrigin) store.set({ projectSplitViewFocusedOrigin: null });
|
|
140
|
+
var selected = exactDriverForOrigin(meta.closedOrigin, getCachedSessions());
|
|
141
|
+
var remaining = exactDriverForOrigin(meta.activeOrigin, getCachedSessions());
|
|
142
|
+
if (selected && remaining && store.get('activeSessionId') === selected.id && !sendMessage({ type: "switch_session", id: remaining.id })) {
|
|
143
|
+
showToast("Pane removed, but the remaining session could not be selected. Reconnect before continuing.", "error");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
renderActive();
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
export function handleProjectSplitViewResult(msg) {
|
|
150
|
+
var state = store.snap(), pending = state.projectSplitViewPending;
|
|
151
|
+
if (!pending || !msg || msg.requestId !== pending.requestId ||
|
|
152
|
+
pending.contextKey !== contextKey(state) || pending.socket !== getWs()) return false;
|
|
153
|
+
if (!msg.ok) {
|
|
154
|
+
store.set({ projectSplitViewPending: null, projectSplitViewPreviewTree: null,
|
|
155
|
+
projectSplitViewError: msg.error || "Split view change was not saved" });
|
|
156
|
+
showToast(msg.error || "Split view change was not saved", "error");
|
|
157
|
+
if (msg.code === "conflict" || msg.code === "session" || msg.code === "identity" || msg.code === "folder") {
|
|
158
|
+
store.set({ projectSplitViewHydrated: false });
|
|
159
|
+
requestProjectSplitViews();
|
|
160
|
+
}
|
|
161
|
+
renderActive();
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
if (!Number.isInteger(msg.revision) || msg.revision < pending.expectedRevision) {
|
|
165
|
+
store.set({ projectSplitViewPending: null, projectSplitViewPreviewTree: null,
|
|
166
|
+
projectSplitViewHydrated: false, projectSplitViewError: "Split view confirmation was incomplete. Refreshing saved state." });
|
|
167
|
+
requestProjectSplitViews();
|
|
168
|
+
renderActive();
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
var nextMeta = pending.meta;
|
|
172
|
+
if (nextMeta && nextMeta.activateCreatedView && msg.view && msg.view.id) nextMeta = Object.assign({}, nextMeta, { createdViewId: msg.view.id });
|
|
173
|
+
store.set({ projectSplitViewPending: Object.assign({}, pending, { awaitingBroadcast: true, resultRevision: msg.revision, meta: nextMeta }),
|
|
174
|
+
projectSplitViewError: "" });
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
export function organizePresentationView(id, revision, folderId, favorite) {
|
|
178
|
+
var view = viewExists(store.get('projectSplitViews'), id);
|
|
179
|
+
if (!view || view.revision !== revision) return false;
|
|
180
|
+
return mutation('project_split_view_organize', { id: id, expectedViewRevision: revision, projectSlug: store.get('currentSlug'), folderId: folderId, favorite: favorite });
|
|
181
|
+
}
|
|
182
|
+
function mutation(type, body, meta) {
|
|
183
|
+
var state = store.snap();
|
|
184
|
+
if (!projectReady(state) || !state.projectSplitViewHydrated || state.projectSplitViewError || state.projectSplitViewPending) return false;
|
|
185
|
+
var requestId = "split-view-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10);
|
|
186
|
+
var ws = getWs();
|
|
187
|
+
var message = Object.assign({ type: type, requestId: requestId, expectedRevision: state.projectSplitViewRevision }, body || {});
|
|
188
|
+
var pending = { requestId: requestId, type: type, socket: ws, contextKey: contextKey(state), expectedRevision: state.projectSplitViewRevision, meta: meta || null,
|
|
189
|
+
awaitingBroadcast: false, resultRevision: null };
|
|
190
|
+
store.set({ projectSplitViewPending: pending, projectSplitViewError: "" });
|
|
191
|
+
if (!sendMessage(message)) {
|
|
192
|
+
store.set({ projectSplitViewPending: null, projectSplitViewError: "Split view change could not be sent. Refresh before trying again." });
|
|
193
|
+
showToast("Split view change could not be sent. Refresh before trying again.", "error");
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
function setActiveView(id, preferredOrigin) {
|
|
199
|
+
var state = store.snap(), view = viewExists(state.projectSplitViews, id);
|
|
200
|
+
if (!view) return false;
|
|
201
|
+
try {
|
|
202
|
+
if (state.splitPanes || state.projectSplitViewActiveId) checkMountedPaneDrafts();
|
|
203
|
+
else checkComposerDeparture();
|
|
204
|
+
} catch (error) { showToast(error.message, "warn"); return false; }
|
|
205
|
+
if (!state.splitPanes && !state.projectSplitViewActiveId) detachTuiView();
|
|
206
|
+
var activeOrigin = preferredOrigin && view.sessions.some(function (session) { return session.sessionOriginId === preferredOrigin; }) ? preferredOrigin : view.activeSessionOriginId;
|
|
207
|
+
var activeSession = viewSessions(view).filter(function (session) { return session.sessionOriginId === activeOrigin; })[0];
|
|
208
|
+
if (activeSession && store.get('activeSessionId') !== activeSession.id) {
|
|
209
|
+
store.set({ projectSplitViewSwitching: true });
|
|
210
|
+
if (!sendMessage({ type: "switch_session", id: activeSession.id })) { store.set({ projectSplitViewSwitching: false }); return false; }
|
|
211
|
+
}
|
|
212
|
+
store.set({ projectSplitViewActiveId: id, projectSplitViewFocusedOrigin: null, projectSplitViewPreviewTree: null, splitPanes: null });
|
|
213
|
+
renderActive();
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
function checkMountedPaneDrafts() {
|
|
217
|
+
if (!host) return;
|
|
218
|
+
Array.from(host.querySelectorAll('.split-pane-frame')).forEach(function (frame) { if (frame.clayPane) frame.clayPane.checkDraft(); });
|
|
219
|
+
}
|
|
220
|
+
function switchToStandalone(sessionId) {
|
|
221
|
+
var state = store.snap();
|
|
222
|
+
try {
|
|
223
|
+
if (state.projectSplitViewActiveId || state.splitPanes) checkMountedPaneDrafts();
|
|
224
|
+
else checkComposerDeparture();
|
|
225
|
+
} catch (error) { showToast(error.message, "warn"); return false; }
|
|
226
|
+
if (!sendMessage({ type: "switch_session", id: sessionId })) {
|
|
227
|
+
showToast("Session switch could not be sent. The current Split view is still open.", "error");
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
store.set({ projectSplitViewActiveId: null, projectSplitViewFocusedOrigin: null, projectSplitViewPreviewTree: null, projectSplitViewSwitching: false, splitPanes: null });
|
|
231
|
+
renderActive();
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
function renderActive() {
|
|
235
|
+
if (!host || !nativeApp || preserveReconnectingPresentation(host)) return;
|
|
236
|
+
var state = store.snap(), view = viewExists(state.projectSplitViews, state.projectSplitViewActiveId);
|
|
237
|
+
if (state.splitIdentityBlockedSocket === getWs()) { hidePresentationView(); return; }
|
|
238
|
+
if (!view) {
|
|
239
|
+
var restored = state.projectSplitViewHydrated && restorablePresentation(state, state.projectSplitViews, getCachedSessions());
|
|
240
|
+
if (restored) {
|
|
241
|
+
store.set({ projectSplitViewActiveId: restored.id, projectSplitViewSingletonOrigin: null, projectSplitViewFocusedOrigin: null });
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
var singleton = currentSingletonPresentation(state, getCachedSessions());
|
|
245
|
+
if (singleton) {
|
|
246
|
+
store.set({ projectSplitViewSingletonOrigin: singleton.origin });
|
|
247
|
+
if (stickyContainer && mainPanels) mainPanels.appendChild(stickyContainer);
|
|
248
|
+
renderPresentationView(singleton.view, { id: singleton.origin }, [singleton.session]);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (state.projectSplitViewSingletonOrigin) store.set({ projectSplitViewSingletonOrigin: null });
|
|
252
|
+
hidePresentationView();
|
|
253
|
+
if (stickyContainer && stickyHome) stickyHome.insertBefore(stickyContainer, stickyAnchor && stickyAnchor.parentNode === stickyHome ? stickyAnchor : null);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (state.projectSplitViewSingletonOrigin) store.set({ projectSplitViewSingletonOrigin: null });
|
|
257
|
+
var sessions = viewSessions(view);
|
|
258
|
+
var tree = presentationTree(view);
|
|
259
|
+
var ids = listIds(tree);
|
|
260
|
+
if (sessions.length !== view.sessions.length || ids.length < 2 || ids.length > 8 || !validateTree(tree, ids).ok) return;
|
|
261
|
+
if (stickyContainer && mainPanels) mainPanels.appendChild(stickyContainer);
|
|
262
|
+
renderPresentationView(view, tree, sessions);
|
|
263
|
+
}
|
|
264
|
+
function onClick(event) {
|
|
265
|
+
var target = event.target;
|
|
266
|
+
if (target.closest('.split-view-menu')) return;
|
|
267
|
+
var suppressed = store.get('projectSplitViewSuppressClick');
|
|
268
|
+
if (suppressed && Date.now() <= suppressed.until && event.pointerId === suppressed.pointerId) {
|
|
269
|
+
var clickedRow = target.closest(".session-item[data-session-id], .session-loop-child[data-session-id]"), clickedPane = target.closest(".split-pane");
|
|
270
|
+
var matchesSource = suppressed.sourceElement && (target === suppressed.sourceElement || suppressed.sourceElement.contains(target)) ||
|
|
271
|
+
clickedRow && Number(clickedRow.dataset.sessionId) === suppressed.sourceSessionId ||
|
|
272
|
+
clickedPane && clickedPane.querySelector('.split-pane-frame') && clickedPane.querySelector('.split-pane-frame').dataset.sessionOriginId === suppressed.targetOrigin;
|
|
273
|
+
if (matchesSource) { event.preventDefault(); event.stopImmediatePropagation(); store.set({ projectSplitViewSuppressClick: null }); return; }
|
|
274
|
+
}
|
|
275
|
+
var menu = target.closest(".split-view-arrange"); if (menu) return;
|
|
276
|
+
var splitGroup = target.closest(".split-view-group[data-split-view-id]");
|
|
277
|
+
if (splitGroup) {
|
|
278
|
+
var view = viewExists(store.get('projectSplitViews'), splitGroup.dataset.splitViewId); if (!view) return;
|
|
279
|
+
var more = target.closest(".split-view-group-more");
|
|
280
|
+
if (more) { event.preventDefault(); event.stopImmediatePropagation(); toggleGroupMenu(splitGroup, more); return; }
|
|
281
|
+
var option = target.closest("[data-split-view-action]");
|
|
282
|
+
if (option) { event.preventDefault(); event.stopImmediatePropagation(); runGroupAction(view, option.dataset.splitViewAction, splitGroup); return; }
|
|
283
|
+
var child = target.closest(".session-item[data-session-id], .session-loop-child[data-session-id]");
|
|
284
|
+
if (child && target.closest("button, a, input, textarea, select, [contenteditable='true']")) return;
|
|
285
|
+
var open = target.closest(".split-view-group-open");
|
|
286
|
+
if (open || child) {
|
|
287
|
+
event.preventDefault(); event.stopImmediatePropagation();
|
|
288
|
+
var session = child && exactDriverForOrigin(child.dataset.controlSessionOrigin, getCachedSessions());
|
|
289
|
+
if (child && !session) session = (getCachedSessions() || []).find(function (item) { return item.id === Number(child.dataset.sessionId); });
|
|
290
|
+
if (!setActiveView(view.id, session && session.sessionOriginId)) return;
|
|
291
|
+
if (session && session.sessionOriginId !== view.activeSessionOriginId) updateActiveSession(view, session.sessionOriginId, mutation);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (store.get('projectSplitViewActiveId') && target.closest('.split-group-item[data-split-group-id]')) {
|
|
296
|
+
store.set({ projectSplitViewActiveId: null, projectSplitViewFocusedOrigin: null }); return;
|
|
297
|
+
}
|
|
298
|
+
var row = target.closest(".session-item[data-session-id], .session-loop-child[data-session-id]"); if (row && store.get('projectSplitViewActiveId')) {
|
|
299
|
+
var session = (getCachedSessions() || []).find(function (item) { return item.id === Number(row.dataset.sessionId); });
|
|
300
|
+
var membershipViewId = session && viewIdForOrigin(session.sessionOriginId); if (membershipViewId) {
|
|
301
|
+
event.preventDefault(); event.stopImmediatePropagation(); var membershipView = viewExists(store.get('projectSplitViews'), membershipViewId);
|
|
302
|
+
if (membershipView && setActiveView(membershipView.id, session.sessionOriginId) && session.sessionOriginId !== membershipView.activeSessionOriginId) updateActiveSession(membershipView, session.sessionOriginId, mutation);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (session && (session.sessionRole === "worker" || groupedSessionIds(store.get('splitGroups') || []).has(session.id))) {
|
|
306
|
+
store.set({ projectSplitViewActiveId: null, projectSplitViewFocusedOrigin: null });
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (session) { event.preventDefault(); event.stopImmediatePropagation(); switchToStandalone(session.id); }
|
|
310
|
+
}
|
|
311
|
+
if (!target.closest(".split-view-group-more")) {
|
|
312
|
+
var groupFocusTarget = target.closest("button, a, input, textarea, select, [tabindex]:not([tabindex='-1'])");
|
|
313
|
+
closeGroupMenus(!groupFocusTarget);
|
|
314
|
+
}
|
|
315
|
+
if (!menu) {
|
|
316
|
+
var arrangeFocusTarget = target.closest("button, a, input, textarea, select, [tabindex]:not([tabindex='-1'])");
|
|
317
|
+
closePresentationArrange(!arrangeFocusTarget);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function onContextMenu(event) {
|
|
321
|
+
var target = event.target;
|
|
322
|
+
if (!target || !target.closest) return;
|
|
323
|
+
var heading = target.closest('.split-view-group-heading'), group = heading && heading.closest('.split-view-group[data-split-view-id]');
|
|
324
|
+
if (!group || target.closest('input, textarea, [contenteditable="true"]') ||
|
|
325
|
+
!projectReady(store.snap()) || !viewExists(store.get('projectSplitViews'), group.dataset.splitViewId)) return;
|
|
326
|
+
var anchor = group.querySelector('.split-view-group-more');
|
|
327
|
+
if (!anchor) return;
|
|
328
|
+
event.preventDefault(); event.stopImmediatePropagation();
|
|
329
|
+
closeSessionContextMenu(false); closePresentationArrange(false);
|
|
330
|
+
toggleGroupMenu(group, anchor, event.clientX || event.clientY ? { x: event.clientX, y: event.clientY } : null);
|
|
331
|
+
}
|
|
332
|
+
function toggleGroupMenu(group, anchor, point) {
|
|
333
|
+
openSplitViewMenu(group, anchor, point, runGroupAction);
|
|
334
|
+
}
|
|
335
|
+
function runGroupAction(view, action, root) {
|
|
336
|
+
closeGroupMenus(false);
|
|
337
|
+
if (action === 'favorite' || action === 'folder') { organizeViewAction(view, action); return; }
|
|
338
|
+
if (action === "rename") { startRename(root, view); return; }
|
|
339
|
+
if (action === "separate") {
|
|
340
|
+
if (view.id === store.get('projectSplitViewActiveId')) {
|
|
341
|
+
try { checkMountedPaneDrafts(); } catch (error) { showToast(error.message, "warn"); return; }
|
|
342
|
+
}
|
|
343
|
+
var selectedOrigin = view.activeSessionOriginId;
|
|
344
|
+
mutation("project_split_view_separate", { id: view.id }, { separateViewId: view.id, activeOrigin: selectedOrigin });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function startRename(root, view) {
|
|
348
|
+
var name = root.querySelector('.split-view-group-name'), heading = root.querySelector('.split-view-group-heading');
|
|
349
|
+
if (!name || !heading || root.querySelector('.split-view-group-name-input')) return;
|
|
350
|
+
var input = document.createElement("input"); input.type = "text"; input.className = "split-view-group-name-input"; input.maxLength = 80;
|
|
351
|
+
input.value = view.name; input.setAttribute("aria-label", "Rename Split view"); name.replaceWith(input); input.focus(); input.select();
|
|
352
|
+
var finished = false;
|
|
353
|
+
function restore(returnFocus) {
|
|
354
|
+
if (finished) return;
|
|
355
|
+
finished = true;
|
|
356
|
+
if (!input.isConnected) return;
|
|
357
|
+
var span = document.createElement("span"); span.className = "split-view-group-name"; span.textContent = view.name; input.replaceWith(span);
|
|
358
|
+
if (returnFocus) { var trigger = root.querySelector('.split-view-group-open'); if (trigger) trigger.focus(); }
|
|
359
|
+
}
|
|
360
|
+
function showInlineError(message) {
|
|
361
|
+
var error = root.querySelector('.split-view-group-error');
|
|
362
|
+
if (!error) return;
|
|
363
|
+
error.textContent = message;
|
|
364
|
+
error.hidden = false;
|
|
365
|
+
input.setAttribute("aria-invalid", "true");
|
|
366
|
+
input.removeAttribute("aria-describedby");
|
|
367
|
+
}
|
|
368
|
+
input.addEventListener("click", function (event) { event.stopPropagation(); });
|
|
369
|
+
var inlineError = root.querySelector('.split-view-group-error');
|
|
370
|
+
if (inlineError) inlineError.hidden = true;
|
|
371
|
+
input.addEventListener("keydown", function (event) {
|
|
372
|
+
if (event.key === "Escape") { event.preventDefault(); restore(true); return; }
|
|
373
|
+
if (event.key === "Enter") {
|
|
374
|
+
event.preventDefault(); var next = input.value.trim();
|
|
375
|
+
if (!next || next.length > 80) { showInlineError("Use a name from 1 to 80 characters."); return; }
|
|
376
|
+
input.removeAttribute("aria-invalid");
|
|
377
|
+
var currentError = root.querySelector('.split-view-group-error'); if (currentError) currentError.hidden = true;
|
|
378
|
+
if (!mutation("project_split_view_rename", { id: view.id, name: next })) { showInlineError(store.get('projectSplitViewError') || "Rename could not be saved."); return; }
|
|
379
|
+
restore();
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
input.addEventListener("blur", restore);
|
|
383
|
+
}
|
|
384
|
+
function onKeyDown(event) {
|
|
385
|
+
if (event.key === "Escape") closePresentationArrange();
|
|
386
|
+
}
|
|
387
|
+
function onArrangement(event) {
|
|
388
|
+
var view = viewExists(store.get('projectSplitViews'), store.get('projectSplitViewActiveId'));
|
|
389
|
+
if (!view || !event.detail) return;
|
|
390
|
+
var source = view.sessions.find(function (session) { return session.sessionOriginId === event.detail.sessionOriginId; });
|
|
391
|
+
if (!source || !exactDriverForOrigin(source.sessionOriginId, getCachedSessions())) return;
|
|
392
|
+
var nextTree = null, nextLayout = view.layout;
|
|
393
|
+
if (event.detail.preset) {
|
|
394
|
+
nextLayout = event.detail.preset;
|
|
395
|
+
var orderedIds = [source.sessionOriginId].concat(listIds(view.tree).filter(function (origin) { return origin !== source.sessionOriginId; }));
|
|
396
|
+
nextTree = preset(orderedIds, nextLayout);
|
|
397
|
+
}
|
|
398
|
+
else if (event.detail.position) {
|
|
399
|
+
var arranged = arrangeRegion(view.tree, source.sessionOriginId, event.detail.position);
|
|
400
|
+
if (!arranged.ok) { showToast(arranged.reason === "quarter-requires-three" ? "Quarter layouts require at least three sessions." : "This arrangement is unavailable.", "warn"); return; }
|
|
401
|
+
nextTree = arranged.tree;
|
|
402
|
+
}
|
|
403
|
+
if (!nextTree || !validateTree(nextTree, view.sessions.map(function (session) { return session.sessionOriginId; })).ok) return;
|
|
404
|
+
store.set({ projectSplitViewPreviewTree: nextTree, projectSplitViewPreviewScope: layoutScope(view), projectSplitViewFocusedOrigin: null }); renderActive();
|
|
405
|
+
if (!mutation("project_split_view_update", { id: view.id, sessionOrigins: listIds(nextTree), tree: nextTree,
|
|
406
|
+
layout: nextLayout, activeSessionOriginId: source.sessionOriginId })) {
|
|
407
|
+
store.set({ projectSplitViewPreviewTree: null }); renderActive();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function onResize(event) {
|
|
411
|
+
if (!event.detail || !currentLayoutScope(event.detail.scope) || event.detail.viewId !== event.detail.scope.viewId) return;
|
|
412
|
+
var view = viewExists(store.get('projectSplitViews'), event.detail.viewId);
|
|
413
|
+
if (!view) return;
|
|
414
|
+
if (store.get('projectSplitViewPending') || !mutation("project_split_view_update", { id: view.id, sessionOrigins: event.detail.ids, tree: event.detail.tree,
|
|
415
|
+
layout: view.layout, activeSessionOriginId: view.activeSessionOriginId })) { store.set({ projectSplitViewPreviewTree: null }); renderActive(); }
|
|
416
|
+
}
|
|
417
|
+
function onClosePane(event) {
|
|
418
|
+
var view = viewExists(store.get('projectSplitViews'), store.get('projectSplitViewActiveId'));
|
|
419
|
+
if (!view || !event.detail || !validPresentationClose(event.detail)) return;
|
|
420
|
+
var session = (getCachedSessions() || []).find(function (item) { return item.id === Number(event.detail.sessionId); });
|
|
421
|
+
if (!session || !view.sessions.some(function (item) { return item.sessionOriginId === session.sessionOriginId; })) return;
|
|
422
|
+
try { checkMountedPaneDrafts(); } catch (error) { showToast(error.message, "warn"); return; }
|
|
423
|
+
var origins = view.sessions.map(function (item) { return item.sessionOriginId; }).filter(function (origin) { return origin !== session.sessionOriginId; });
|
|
424
|
+
if (origins.length < 2) {
|
|
425
|
+
var remaining = view.sessions.filter(function (item) { return item.sessionOriginId !== session.sessionOriginId; })[0];
|
|
426
|
+
if (remaining) mutation("project_split_view_separate", { id: view.id }, { separateViewId: view.id, activeOrigin: remaining.sessionOriginId });
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
var nextTree = remove(view.tree, session.sessionOriginId);
|
|
430
|
+
if (!nextTree || !validateTree(nextTree, origins).ok) return;
|
|
431
|
+
var nextActive = view.activeSessionOriginId === session.sessionOriginId ? origins[0] : view.activeSessionOriginId;
|
|
432
|
+
mutation("project_split_view_update", { id: view.id, sessionOrigins: origins, tree: nextTree,
|
|
433
|
+
layout: view.layout, activeSessionOriginId: nextActive }, { closedOrigin: session.sessionOriginId, activeOrigin: nextActive });
|
|
434
|
+
}
|
|
435
|
+
function onFocus(event) {
|
|
436
|
+
var view = viewExists(store.get('projectSplitViews'), store.get('projectSplitViewActiveId'));
|
|
437
|
+
if (!view) return;
|
|
438
|
+
var source = event.detail && view.sessions.find(function (session) { return session.sessionOriginId === event.detail.sessionOriginId; });
|
|
439
|
+
if (event.detail && event.detail.focused && !source) return;
|
|
440
|
+
store.set({ projectSplitViewFocusedOrigin: event.detail && event.detail.focused ? source.sessionOriginId : null });
|
|
441
|
+
renderActive();
|
|
442
|
+
}
|
|
443
|
+
function init(hostElement) {
|
|
444
|
+
host = hostElement; nativeApp = document.getElementById("app"); mainPanels = document.getElementById("main-panels");
|
|
445
|
+
stickyContainer = document.getElementById("sticky-notes-container");
|
|
446
|
+
if (stickyContainer) { stickyHome = stickyContainer.parentNode; stickyAnchor = stickyContainer.nextSibling; }
|
|
447
|
+
initPresentationRenderer(host);
|
|
448
|
+
initPresentationDrag(host, function (drag, target) { applyPresentationDrop(drag, target, mutation, checkMountedPaneDrafts); });
|
|
449
|
+
initSplitViewCreateMenu();
|
|
450
|
+
document.addEventListener('clay-split-view-create', function (event) { handleSplitViewCreateEvent(event, mutation, checkMountedPaneDrafts); });
|
|
451
|
+
document.addEventListener("click", onClick, true);
|
|
452
|
+
document.addEventListener("contextmenu", onContextMenu, true);
|
|
453
|
+
document.addEventListener("clay-split-view-arrange", onArrangement);
|
|
454
|
+
document.addEventListener("clay-split-view-resize", onResize);
|
|
455
|
+
document.addEventListener("clay-split-view-focus", onFocus);
|
|
456
|
+
document.addEventListener("clay-split-view-close-pane", onClosePane);
|
|
457
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
458
|
+
store.subscribe(function (state, previous) {
|
|
459
|
+
if ((previous.projectSplitViewDrag && !state.projectSplitViewDrag) ||
|
|
460
|
+
(previous.projectSplitViewDividerDrag && !state.projectSplitViewDividerDrag)) cleanupTransientInteractions(previous);
|
|
461
|
+
if (state.projectSplitViewStandaloneTarget && state.activeSessionId !== previous.activeSessionId) store.set({ projectSplitViewStandaloneTarget: null });
|
|
462
|
+
var contextChanged = resetForContext();
|
|
463
|
+
if (previous.connected && !state.connected) {
|
|
464
|
+
var pending = state.projectSplitViewPending;
|
|
465
|
+
store.set({ projectSplitViews: [], projectSplitViewRevision: 0, projectSplitViewHydrated: false,
|
|
466
|
+
projectSplitViewPending: null, projectSplitViewError: pending ? "Connection changed during a Split view change. Saved state will be refreshed after reconnect." : "",
|
|
467
|
+
projectSplitViewActiveId: null, projectSplitViewFocusedOrigin: null, projectSplitViewPreviewTree: null, projectSplitViewRequestSocket: null });
|
|
468
|
+
}
|
|
469
|
+
var becameReady = projectReady(state) && (!projectReady(previous) || contextChanged);
|
|
470
|
+
if (becameReady) requestProjectSplitViews();
|
|
471
|
+
if (state.projectSplitViews !== previous.projectSplitViews || state.projectSplitViewActiveId !== previous.projectSplitViewActiveId || state.activeSessionId !== previous.activeSessionId || state.sessionOriginId !== previous.sessionOriginId || state.splitPanes !== previous.splitPanes ||
|
|
472
|
+
state.projectSplitViewPreviewTree !== previous.projectSplitViewPreviewTree || state.currentSlug !== previous.currentSlug ||
|
|
473
|
+
state.paneBindingSessions !== previous.paneBindingSessions || state.sessionActivatedProjectSlug !== previous.sessionActivatedProjectSlug) renderActive();
|
|
474
|
+
if (state.activeSessionId !== previous.activeSessionId && state.projectSplitViewActiveId) {
|
|
475
|
+
var activeView = viewExists(state.projectSplitViews, state.projectSplitViewActiveId);
|
|
476
|
+
if (activeView && !activeView.sessions.some(function (session) { return session.id === state.activeSessionId; })) {
|
|
477
|
+
store.set({ projectSplitViewActiveId: null, projectSplitViewPreviewTree: null });
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
var changed = resetForContext();
|
|
482
|
+
if (changed && projectReady(store.snap())) requestProjectSplitViews();
|
|
483
|
+
}
|
|
484
|
+
export function initSplitViewPresentations(hostElement) { init(hostElement); }
|
|
485
|
+
export function renderPresentationSplitView() { renderActive(); }
|
|
486
|
+
export function presentationViewForSidebar(viewId) { return viewExists(store.get('projectSplitViews'), viewId); }
|
|
487
|
+
|
|
488
|
+
// Canonical navigation uses the same draft guard and view renderer as human entry.
|
|
489
|
+
export function applyReviewedSplitView(target) {
|
|
490
|
+
var view = viewExists(store.get('projectSplitViews'), target.view.id);
|
|
491
|
+
if (!projectReady(store.snap()) || store.get('projectSplitViewPending') || !view || JSON.stringify(view) !== JSON.stringify(target.view)) return false;
|
|
492
|
+
if (!setActiveView(view.id, view.activeSessionOriginId)) return false;
|
|
493
|
+
onFocus({ detail: { focused: !!target.focusOrigin, sessionOriginId: target.focusOrigin } });
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { toggleSidebarCollapse } from './sidebar.js';
|
|
2
|
+
|
|
3
|
+
export function syncSidebarReopenButton(button, active) {
|
|
4
|
+
if (!button) return;
|
|
5
|
+
var layout = document.getElementById("layout");
|
|
6
|
+
var desktop = !window.matchMedia || window.matchMedia("(min-width: 769px)").matches;
|
|
7
|
+
button.hidden = !layout || !desktop || !active || !layout.classList.contains("sidebar-collapsed");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function createSidebarReopenButton() {
|
|
11
|
+
var target = document.querySelector("#top-bar .top-bar-left-pills");
|
|
12
|
+
if (!target) return null;
|
|
13
|
+
var button = document.createElement("button");
|
|
14
|
+
button.id = "sidebar-shell-expand-btn";
|
|
15
|
+
button.type = "button";
|
|
16
|
+
button.title = "Open sidebar";
|
|
17
|
+
button.setAttribute("aria-label", "Open sidebar");
|
|
18
|
+
button.setAttribute("aria-controls", "sidebar-column");
|
|
19
|
+
button.setAttribute("aria-expanded", "false");
|
|
20
|
+
button.innerHTML = '<i data-lucide="panel-left-open"></i>';
|
|
21
|
+
button.hidden = true;
|
|
22
|
+
button.addEventListener("click", function () {
|
|
23
|
+
if (button.hidden) return;
|
|
24
|
+
toggleSidebarCollapse();
|
|
25
|
+
});
|
|
26
|
+
target.prepend(button);
|
|
27
|
+
return button;
|
|
28
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getWs } from './ws-ref.js';
|
|
2
|
+
import { store } from './store.js';
|
|
3
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
4
|
+
import { closeSessionContextMenu, mountSessionContextMenu, positionSessionContextMenu } from './session-context-menu.js';
|
|
5
|
+
import { closeFolderMenu } from './session-folder-context.js';
|
|
6
|
+
|
|
7
|
+
export function closeSplitViewMenu(returnFocus) {
|
|
8
|
+
var current = store.get('sessionContextMenu');
|
|
9
|
+
if (current && current.el.classList.contains('split-view-menu')) closeSessionContextMenu(returnFocus);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function openSplitViewMenu(group, anchor, point, onAction) {
|
|
13
|
+
var view = (store.get('projectSplitViews') || []).find(function (item) { return item.id === group.dataset.splitViewId; });
|
|
14
|
+
if (!view) return;
|
|
15
|
+
var surface = group.dataset.splitViewSurface || 'desktop';
|
|
16
|
+
var socket = getWs(), generation = store.get('projectSplitViewSocketGeneration');
|
|
17
|
+
var slug = store.get('currentSlug'), viewer = store.get('myUserId'), projection = group.dataset.splitViewProjection;
|
|
18
|
+
function currentRoot() {
|
|
19
|
+
var live = (store.get('projectSplitViews') || []).find(function (item) { return item.id === view.id && item.revision === view.revision && item.name === view.name; });
|
|
20
|
+
if (!live || socket !== getWs() || generation !== store.get('projectSplitViewSocketGeneration') || slug !== store.get('currentSlug') || viewer !== store.get('myUserId') || !store.get('connected')) return null;
|
|
21
|
+
function matches(root) { return root.isConnected && root.dataset.splitViewId === view.id && root.dataset.splitViewProjection === projection && (root.dataset.splitViewSurface || 'desktop') === surface; }
|
|
22
|
+
if (matches(group)) return group;
|
|
23
|
+
var roots = Array.from(document.querySelectorAll('.split-view-group[data-split-view-id]')).filter(matches);
|
|
24
|
+
return roots.length === 1 ? roots[0] : null;
|
|
25
|
+
}
|
|
26
|
+
closeFolderMenu(false); closeSessionContextMenu(false);
|
|
27
|
+
var menu = document.createElement('div'); menu.className = 'session-ctx-menu split-view-menu';
|
|
28
|
+
[['favorite', view.favorite ? 'Remove from Favorites' : 'Add to Favorites', 'star'], ['folder', 'Move to folder', 'folder-input'], ['rename', 'Rename', 'pencil'], ['separate', 'Separate', 'columns-2']].forEach(function (entry) {
|
|
29
|
+
var button = document.createElement('button'); button.className = 'session-ctx-item'; button.dataset.splitViewAction = entry[0];
|
|
30
|
+
button.innerHTML = iconHtml(entry[2]) + ' <span>' + entry[1] + '</span>';
|
|
31
|
+
button.addEventListener('click', function (event) {
|
|
32
|
+
event.stopPropagation(); var root = currentRoot(); closeSessionContextMenu(false);
|
|
33
|
+
if (root) onAction(view, entry[0], root);
|
|
34
|
+
});
|
|
35
|
+
menu.appendChild(button);
|
|
36
|
+
});
|
|
37
|
+
document.body.appendChild(menu);
|
|
38
|
+
mountSessionContextMenu(menu, anchor, null, { resolveAnchor: function () {
|
|
39
|
+
var root = currentRoot(); return root && root.querySelector('.split-view-group-more');
|
|
40
|
+
} });
|
|
41
|
+
refreshIcons(); positionSessionContextMenu(menu, anchor, point);
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { appendViewOrganization } from './split-view-organization.js';
|
|
3
|
+
import { iconHtml } from './icons.js';
|
|
4
|
+
|
|
5
|
+
export function renderPresentationSplitViewItem(view, members, renderSessionItem, containerId, surface) {
|
|
6
|
+
var root = document.createElement("div"), activeId = store.get('projectSplitViewActiveId');
|
|
7
|
+
var fullName = view.nameMode === "automatic" ? members.map(function (session) { return session.title || "Session"; }).join(" + ") : view.name;
|
|
8
|
+
var nameCharacters = Array.from(fullName);
|
|
9
|
+
var displayName = nameCharacters.length > 80 ? nameCharacters.slice(0, 79).join("") + "…" : fullName;
|
|
10
|
+
root.className = "split-view-group"; root.dataset.splitViewId = view.id; root.dataset.splitViewProjection = containerId || 'unfiled'; root.dataset.splitViewSurface = surface || 'desktop';
|
|
11
|
+
root.dataset.active = activeId === view.id ? "true" : "false"; root.title = fullName;
|
|
12
|
+
var heading = document.createElement("div"); heading.className = "split-view-group-heading";
|
|
13
|
+
var open = document.createElement("button"); open.type = "button"; open.className = "split-view-group-open";
|
|
14
|
+
open.setAttribute("aria-expanded", activeId === view.id ? "true" : "false");
|
|
15
|
+
open.setAttribute("aria-label", "Open Split view " + fullName); open.title = fullName;
|
|
16
|
+
open.innerHTML = iconHtml("columns-2");
|
|
17
|
+
var name = document.createElement("span"); name.className = "split-view-group-name"; name.textContent = displayName; open.appendChild(name);
|
|
18
|
+
var count = document.createElement("span"); count.className = "split-view-group-count"; count.textContent = String(members.length); open.appendChild(count);
|
|
19
|
+
heading.appendChild(open);
|
|
20
|
+
appendViewOrganization(heading, view);
|
|
21
|
+
var more = document.createElement("button"); more.type = "button"; more.className = "split-view-group-more";
|
|
22
|
+
more.setAttribute("aria-label", "Split view options " + fullName); more.setAttribute("aria-haspopup", "menu"); more.setAttribute("aria-expanded", "false");
|
|
23
|
+
more.innerHTML = iconHtml("ellipsis"); heading.appendChild(more); root.appendChild(heading);
|
|
24
|
+
var children = document.createElement("div"); children.className = "split-view-group-children";
|
|
25
|
+
for (var i = 0; containerId !== 'favorites' && i < members.length; i++) children.appendChild(renderSessionItem(members[i]));
|
|
26
|
+
root.appendChild(children);
|
|
27
|
+
var error = document.createElement("div"); error.className = "split-view-group-error";
|
|
28
|
+
error.textContent = store.get('projectSplitViewError') || ""; error.hidden = !error.textContent; root.appendChild(error);
|
|
29
|
+
return root;
|
|
30
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getWs } from './ws-ref.js';
|
|
2
|
+
// Build the transient one-Driver pane model without creating saved view state.
|
|
3
|
+
export function singletonPresentation(state, sessions) {
|
|
4
|
+
if (!state || state.splitPanes) return null;
|
|
5
|
+
var target = state.projectSplitViewStandaloneTarget;
|
|
6
|
+
var validTarget = target && target.previousSessionId === state.activeSessionId && target.contextKey === state.projectSplitViewContextKey;
|
|
7
|
+
var candidate = (sessions || []).find(function (session) {
|
|
8
|
+
return validTarget ? session.id === target.id && session.sessionOriginId === target.origin && (session.ownerId || null) === (state.myUserId || null) : session.id === state.activeSessionId;
|
|
9
|
+
});
|
|
10
|
+
if (!candidate || !candidate.sessionOriginId || candidate.sessionRole === "worker" || candidate.isMate === true || candidate.isTui === true ||
|
|
11
|
+
(candidate.runtimeMode || candidate.mode || "gui") !== "gui") return null;
|
|
12
|
+
return { origin: candidate.sessionOriginId, view: { id: "single:" + candidate.sessionOriginId,
|
|
13
|
+
sessions: [candidate], activeSessionOriginId: candidate.sessionOriginId }, session: candidate };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function currentSingletonPresentation(state, sessions) {
|
|
17
|
+
if (!state || state.splitIdentityBlockedSocket && state.splitIdentityBlockedSocket === getWs() || state.paneMode || state.dmMode || !state.currentSlug || state.activeProjectSlug !== state.currentSlug ||
|
|
18
|
+
state.sessionListProjectSlug !== state.currentSlug || !state.authIdentityLoaded || !state.connected ||
|
|
19
|
+
state.projectSplitViewContextKey !== [state.currentSlug || "", state.currentProjectOwnerId || "", state.myUserId || "", state.projectSplitViewSocketGeneration || 0].join("|")) return null;
|
|
20
|
+
return singletonPresentation(state, sessions);
|
|
21
|
+
}
|