clay-server 5.3.0-beta.5 → 5.3.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/clay-product-control-tools.js +2 -2
- package/lib/clay-product-control.js +1 -1
- package/lib/clay-screen-controls.js +1 -0
- package/lib/clay-split-view-awareness.js +5 -2
- package/lib/clay-split-view-controls.js +14 -7
- package/lib/clay-split-view-input.js +5 -4
- package/lib/clay-split-view-render-proof.js +14 -4
- package/lib/clay-view-workspace-input.js +71 -0
- package/lib/project-file-history.js +11 -9
- package/lib/project-file-watch.js +26 -7
- package/lib/project-filesystem.js +5 -4
- package/lib/project-shared-browser.js +7 -3
- package/lib/project-split-view-document.js +16 -2
- package/lib/project-split-views.js +15 -294
- package/lib/project-user-message.js +4 -3
- package/lib/project-vendor-login.js +2 -2
- package/lib/project-view-input.js +11 -0
- package/lib/project-view-pair.js +26 -0
- package/lib/project-view-store.js +346 -0
- package/lib/project-view-terminal-binding.js +16 -0
- package/lib/project-view-workspace.js +126 -0
- package/lib/project.js +1 -0
- package/lib/public/app.js +2 -0
- package/lib/public/css/issues.css +3 -0
- package/lib/public/css/pane.css +5 -1
- package/lib/public/css/right-workbench.css +0 -1
- package/lib/public/css/scheduled-tasks.css +7 -0
- package/lib/public/css/split-view-layout.css +37 -3
- package/lib/public/css/tool-pane.css +127 -0
- package/lib/public/index.html +1 -1
- package/lib/public/modules/clay-file-links.js +2 -1
- package/lib/public/modules/clay-split-view-screen.js +6 -3
- package/lib/public/modules/clay-tool-pane-screen.js +35 -0
- package/lib/public/modules/file-history-snapshot.js +12 -0
- package/lib/public/modules/file-pane-entrypoints.js +55 -0
- package/lib/public/modules/file-pane-history.js +78 -0
- package/lib/public/modules/file-pane-instances.js +140 -0
- package/lib/public/modules/file-pane-links.js +41 -0
- package/lib/public/modules/file-pane-render.js +48 -0
- package/lib/public/modules/file-pane-review.js +26 -0
- package/lib/public/modules/file-pane-toolbar.js +54 -0
- package/lib/public/modules/file-viewer-source.js +70 -0
- package/lib/public/modules/filebrowser.js +43 -82
- package/lib/public/modules/git-panel.js +5 -0
- package/lib/public/modules/issues.js +3 -4
- package/lib/public/modules/linear-panel.js +2 -2
- package/lib/public/modules/mate-knowledge-workbench.js +10 -6
- package/lib/public/modules/notifications.js +4 -1
- package/lib/public/modules/pane-activation.js +2 -0
- package/lib/public/modules/pane-binding.js +5 -1
- package/lib/public/modules/pane-browser-bridge.js +6 -2
- package/lib/public/modules/pane-browser-transport.js +110 -0
- package/lib/public/modules/plantuml-preview.js +12 -12
- package/lib/public/modules/right-workbench.js +34 -15
- package/lib/public/modules/scheduled-tasks.js +7 -6
- package/lib/public/modules/shared-browser.js +69 -9
- package/lib/public/modules/split-pane-renderer.js +1 -0
- package/lib/public/modules/split-view-drag-preview.js +90 -0
- package/lib/public/modules/split-view-layout-client.js +1 -1
- package/lib/public/modules/split-view-presentation-drag.js +36 -13
- package/lib/public/modules/split-view-presentation-drop.js +21 -18
- package/lib/public/modules/split-view-presentation-renderer.js +15 -10
- package/lib/public/modules/split-view-presentations.js +25 -33
- package/lib/public/modules/split-view.js +9 -2
- package/lib/public/modules/sticky-notes-browser.js +2 -2
- package/lib/public/modules/terminal.js +46 -19
- package/lib/public/modules/theme.js +1 -6
- package/lib/public/modules/tool-pane-chrome.js +82 -0
- package/lib/public/modules/tool-pane-drag.js +134 -0
- package/lib/public/modules/tool-pane-layout.js +95 -0
- package/lib/public/modules/tool-pane-legacy.js +60 -0
- package/lib/public/modules/tool-pane-tree.js +51 -0
- package/lib/public/modules/view-legacy-coordinates.js +16 -0
- package/lib/public/modules/view-mutations.js +24 -0
- package/lib/public/modules/view-pair-presentation.js +74 -0
- package/lib/public/modules/view-pair-validation.js +12 -0
- package/lib/public/modules/view-presentation-state.js +37 -0
- package/lib/public/modules/view-workspace-controller.js +210 -0
- package/lib/public/modules/view-workspace-state.js +103 -0
- package/lib/public/modules/view-workspace-validation.js +47 -0
- package/lib/public/modules/wireframe-workbench.js +11 -0
- package/lib/public/modules/workbench-layout-preference.js +12 -5
- package/lib/public/modules/workbench-panels.js +21 -0
- package/lib/public/pane-assets/{app-6H5S2Y3Z.js → app-7UZ727NA.js} +1 -1
- package/lib/public/pane-assets/chunk-2AIWCI6Y.js +1 -0
- package/lib/public/pane-assets/chunk-6MIHIQ2W.js +1 -0
- package/lib/public/pane-assets/chunk-6NYCNWVF.js +1 -0
- package/lib/public/pane-assets/chunk-6QFLYOVY.js +1 -0
- package/lib/public/pane-assets/chunk-6UJ2ALOB.js +1 -0
- package/lib/public/pane-assets/chunk-6XK2OATZ.js +1 -0
- package/lib/public/pane-assets/chunk-BKIDR2ME.js +1 -0
- package/lib/public/pane-assets/chunk-BMHTBRMO.js +1 -0
- package/lib/public/pane-assets/chunk-GEFCFZEN.js +1 -0
- package/lib/public/pane-assets/chunk-GN2NQEVC.js +2 -0
- package/lib/public/pane-assets/chunk-IMLMBPT4.js +166 -0
- package/lib/public/pane-assets/chunk-JIV7O5GN.js +1 -0
- package/lib/public/pane-assets/chunk-K5S6AILM.js +1 -0
- package/lib/public/pane-assets/chunk-LDIZLKB2.js +1 -0
- package/lib/public/pane-assets/chunk-OWNNLZ36.js +1 -0
- package/lib/public/pane-assets/chunk-P2C3LOWL.js +3 -0
- package/lib/public/pane-assets/chunk-PV4PCH2V.js +2 -0
- package/lib/public/pane-assets/chunk-R4LMPTKU.js +1 -0
- package/lib/public/pane-assets/chunk-R7QVVFUG.js +1 -0
- package/lib/public/pane-assets/chunk-TMGSDFJI.js +1 -0
- package/lib/public/pane-assets/chunk-VKYBAVWT.js +1 -0
- package/lib/public/pane-assets/chunk-VNAV23CM.js +1 -0
- package/lib/public/pane-assets/chunk-VWTHIQCE.js +1 -0
- package/lib/public/pane-assets/chunk-VZ2OKDAO.js +1 -0
- package/lib/public/pane-assets/chunk-WM3K5AFH.js +1 -0
- package/lib/public/pane-assets/chunk-XOB3O327.js +1 -0
- package/lib/public/pane-assets/chunk-XOOUCFYT.js +1 -0
- package/lib/public/pane-assets/manifest.json +426 -381
- package/lib/public/pane-assets/style-ZM7OOU3M.css +1 -0
- package/lib/public/style.css +1 -0
- package/lib/server-workbench-layout-settings.js +3 -3
- package/lib/split-view-layout.js +11 -7
- package/lib/users-workbench-layout-preferences.js +14 -4
- package/lib/workbench-window-layout.js +30 -0
- package/package.json +1 -1
- package/lib/public/pane-assets/chunk-3QV5SDC4.js +0 -1
- package/lib/public/pane-assets/chunk-4FM3U3DW.js +0 -1
- package/lib/public/pane-assets/chunk-7GZH3FZY.js +0 -1
- package/lib/public/pane-assets/chunk-AGJTF5WQ.js +0 -1
- package/lib/public/pane-assets/chunk-DGOFGEUK.js +0 -1
- package/lib/public/pane-assets/chunk-I57URYSL.js +0 -1
- package/lib/public/pane-assets/chunk-NPKRTUNG.js +0 -2
- package/lib/public/pane-assets/chunk-VGA2Y3D2.js +0 -2
- package/lib/public/pane-assets/chunk-WKO3T75E.js +0 -167
- package/lib/public/pane-assets/style-TSRDU54Z.css +0 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { livePaneBinding, paneMessageBinding, samePaneBinding, paneTarget } from './pane-binding.js';
|
|
4
|
+
|
|
5
|
+
// Browser-only presentation bridge. Effects still use the source pane's real
|
|
6
|
+
// authenticated socket and the existing server browser service.
|
|
7
|
+
function browserSource() { return store.get('paneBrowserSource') || null; }
|
|
8
|
+
function focusedOrigin() {
|
|
9
|
+
var focused = store.get('projectSplitViewFocusedOrigin') || store.get('projectSplitViewSingletonOrigin');
|
|
10
|
+
if (focused) return focused;
|
|
11
|
+
var view = (store.get('projectSplitViews') || []).find(function (item) { return item.id === store.get('projectSplitViewActiveId'); });
|
|
12
|
+
return view && view.activeSessionOriginId || store.get('sessionOriginId') || null;
|
|
13
|
+
}
|
|
14
|
+
var receive = null;
|
|
15
|
+
var changed = null;
|
|
16
|
+
var fieldsByAction = {
|
|
17
|
+
state_request: [], open: ['url', 'newTab', 'incognito'], view: ['browserId', 'visible'],
|
|
18
|
+
control: ['browserId', 'control'], close: ['browserId'], end: ['browserId'], input: ['browserId', 'epoch', 'event'],
|
|
19
|
+
};
|
|
20
|
+
function frames() {
|
|
21
|
+
return Array.from(document.querySelectorAll('#split-host .split-pane:not(.split-pane-cached) .split-pane-frame'));
|
|
22
|
+
}
|
|
23
|
+
export function hasBrowserPaneHost() { return !store.get('paneMode') && frames().some(function (frame) { return !!frame.clayPane || !!paneTarget(store.snap(), store.get('paneBindingSessions') || [], Number(frame.dataset.sessionId)); }); }
|
|
24
|
+
function current() {
|
|
25
|
+
var source = browserSource();
|
|
26
|
+
if (!source || source.socket !== getWs() || source.owner !== store.get('myUserId') || source.project !== store.get('currentSlug')) return false;
|
|
27
|
+
var frame = source.frame;
|
|
28
|
+
return frames().includes(frame) && frame.contentWindow === source.window && frame.contentDocument === source.document &&
|
|
29
|
+
!frame.dataset.requestedSessionId && livePaneBinding(frame, { paneBinding: source.binding });
|
|
30
|
+
}
|
|
31
|
+
export function browserPaneSessionId() { return current() ? browserSource().binding.sessionId : null; }
|
|
32
|
+
export function browserPaneStamp() { var source = browserSource(); return current() ? source.binding.requestId + ':' + source.binding.generation : null; }
|
|
33
|
+
export function browserPaneBound() { return !!browserSource(); }
|
|
34
|
+
export function selectBrowserPane(frame, binding) {
|
|
35
|
+
var source = browserSource();
|
|
36
|
+
if (!frame || !binding || !frames().includes(frame) || frame.dataset.requestedSessionId || !livePaneBinding(frame, { paneBinding: binding })) return false;
|
|
37
|
+
if (current() && source.frame === frame && samePaneBinding(source.binding, binding)) {
|
|
38
|
+
if (source.focusedOrigin !== focusedOrigin()) store.set({ paneBrowserSource: Object.assign({}, source, { focusedOrigin: focusedOrigin() }) });
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
var previous = store.get('sharedBrowser');
|
|
42
|
+
if (current() && previous) sendBrowserPaneCommand('view', { browserId: previous.id, visible: false });
|
|
43
|
+
store.set({ paneBrowserSource: { frame: frame, window: frame.contentWindow, document: frame.contentDocument, binding: binding,
|
|
44
|
+
socket: getWs(), owner: store.get('myUserId'), project: store.get('currentSlug'), focusedOrigin: focusedOrigin() }, paneBrowserTarget: binding });
|
|
45
|
+
if (changed) changed(true);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
export function ensureBrowserPaneSource() {
|
|
49
|
+
if (current() && browserSource().focusedOrigin === focusedOrigin()) return true;
|
|
50
|
+
var candidates = frames();
|
|
51
|
+
var origin = focusedOrigin();
|
|
52
|
+
var frame = candidates.find(function (item) { return origin && item.dataset.sessionOriginId === origin; }) ||
|
|
53
|
+
candidates.find(function (item) { var pane = item.closest('.split-pane'); return pane && pane.dataset.active === 'true'; }) || candidates[0];
|
|
54
|
+
var live = frame && frame.clayPane && frame.clayPane.identity();
|
|
55
|
+
return !!live && selectBrowserPane(frame, live.binding);
|
|
56
|
+
}
|
|
57
|
+
export function sendBrowserPaneCommand(action, fields) {
|
|
58
|
+
var source = browserSource();
|
|
59
|
+
if (!Object.prototype.hasOwnProperty.call(fieldsByAction, action)) return false;
|
|
60
|
+
if (!source && !hasBrowserPaneHost()) return null;
|
|
61
|
+
if (!current() || !source.frame.clayPane || typeof source.frame.clayPane.browserCommand !== 'function') return false;
|
|
62
|
+
return source.frame.clayPane.browserCommand(action, fields || {}, source.binding);
|
|
63
|
+
}
|
|
64
|
+
export function sendBoundBrowserCommand(action, fields, binding) {
|
|
65
|
+
var allowed = Object.prototype.hasOwnProperty.call(fieldsByAction, action) && fieldsByAction[action], ws = getWs();
|
|
66
|
+
if (!allowed || !ws || ws.readyState !== 1 || !samePaneBinding(binding, paneMessageBinding()) ||
|
|
67
|
+
!store.get('connected') || store.get('panePendingBinding') || store.get('activeSessionId') !== binding.sessionId) return false;
|
|
68
|
+
var message = { type: 'shared_browser_' + action, sessionId: binding.sessionId, paneBinding: binding };
|
|
69
|
+
allowed.forEach(function (key) { if (Object.prototype.hasOwnProperty.call(fields, key)) message[key] = fields[key]; });
|
|
70
|
+
ws.send(JSON.stringify(message));
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
export function forwardBrowserPaneMessage(message) {
|
|
74
|
+
var frame = window.frameElement;
|
|
75
|
+
if (!store.get('paneMode') || !frame || !frame.clayPane) return false;
|
|
76
|
+
if (['shared_browser_state', 'shared_browser_frame', 'shared_browser_error'].indexOf(message.type) < 0) return false;
|
|
77
|
+
var binding = paneMessageBinding();
|
|
78
|
+
if (binding && samePaneBinding(binding, message.paneActivation)) {
|
|
79
|
+
window.parent.postMessage({ type: 'clay-pane-browser-update', paneBinding: binding, message: message }, location.origin);
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
export function initBrowserPaneTransport(onMessage, onChanged) {
|
|
84
|
+
if (store.get('paneMode')) return;
|
|
85
|
+
receive = onMessage; changed = onChanged;
|
|
86
|
+
window.addEventListener('message', function (event) {
|
|
87
|
+
var data = event.data;
|
|
88
|
+
if (event.origin !== location.origin || !data || data.type !== 'clay-pane-browser-update' || !data.message) return;
|
|
89
|
+
var frame = frames().find(function (item) { return item.contentWindow === event.source; });
|
|
90
|
+
var message = data.message, source = browserSource();
|
|
91
|
+
if (!frame || !livePaneBinding(frame, data) || !samePaneBinding(data.paneBinding, message.paneActivation) ||
|
|
92
|
+
message.sessionId !== data.paneBinding.sessionId || ['shared_browser_state', 'shared_browser_frame', 'shared_browser_error'].indexOf(message.type) < 0) return;
|
|
93
|
+
if (!current() || source.frame !== frame || !samePaneBinding(source.binding, data.paneBinding)) {
|
|
94
|
+
if (message.type !== 'shared_browser_state' || !message.present || !selectBrowserPane(frame, data.paneBinding)) return;
|
|
95
|
+
}
|
|
96
|
+
receive(message, true);
|
|
97
|
+
});
|
|
98
|
+
document.addEventListener('clay-pane-ready', function () {
|
|
99
|
+
queueMicrotask(function () {
|
|
100
|
+
if (!current() && ensureBrowserPaneSource()) sendBrowserPaneCommand('state_request', {});
|
|
101
|
+
});
|
|
102
|
+
}, true);
|
|
103
|
+
store.subscribe(function () {
|
|
104
|
+
var source = browserSource();
|
|
105
|
+
if (source && !current()) {
|
|
106
|
+
store.set({ paneBrowserSource: null, paneBrowserTarget: null });
|
|
107
|
+
if (changed) changed(false);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -6,17 +6,17 @@ export function isPlantUmlFile(path) {
|
|
|
6
6
|
return /\.(puml|plantuml|pu|uml|salt)$/i.test(String(path || ""));
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export function cancelPlantUmlPreview() {
|
|
10
|
-
var job = store.get('plantUmlPreviewJob');
|
|
9
|
+
export function cancelPlantUmlPreview(instanceId) {
|
|
10
|
+
var job = store.get(instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob');
|
|
11
11
|
if (!job) return;
|
|
12
12
|
clearTimeout(job.timer);
|
|
13
13
|
job.observer.disconnect();
|
|
14
14
|
job.controller.abort();
|
|
15
|
-
|
|
15
|
+
var cleared = {}; cleared[instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob'] = null; store.set(cleared);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export function renderPlantUmlPreview(body, source, path) {
|
|
19
|
-
cancelPlantUmlPreview();
|
|
18
|
+
export function renderPlantUmlPreview(body, source, path, instanceId) {
|
|
19
|
+
cancelPlantUmlPreview(instanceId);
|
|
20
20
|
body.innerHTML = "";
|
|
21
21
|
var preview = document.createElement("div");
|
|
22
22
|
preview.className = "file-viewer-plantuml-preview";
|
|
@@ -36,22 +36,22 @@ export function renderPlantUmlPreview(body, source, path) {
|
|
|
36
36
|
|
|
37
37
|
var job = { controller: new AbortController(), observer: null, timer: null };
|
|
38
38
|
job.observer = new MutationObserver(function () {
|
|
39
|
-
if (!preview.isConnected && store.get('plantUmlPreviewJob') === job) cancelPlantUmlPreview();
|
|
39
|
+
if (!preview.isConnected && store.get(instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob') === job) cancelPlantUmlPreview(instanceId);
|
|
40
40
|
});
|
|
41
41
|
job.observer.observe(body, { childList: true });
|
|
42
42
|
job.timer = setTimeout(function () {
|
|
43
|
-
if (store.get('plantUmlPreviewJob') !== job) return;
|
|
43
|
+
if (store.get(instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob') !== job) return;
|
|
44
44
|
showError("The diagram preview timed out.");
|
|
45
|
-
cancelPlantUmlPreview();
|
|
45
|
+
cancelPlantUmlPreview(instanceId);
|
|
46
46
|
}, 20000);
|
|
47
|
-
|
|
47
|
+
var patch = {}; patch[instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob'] = job; store.set(patch);
|
|
48
48
|
fetch("api/file/plantuml?path=" + encodeURIComponent(path), { signal: job.controller.signal, cache: "no-store" }).then(function (response) {
|
|
49
49
|
return response.text().then(function (text) {
|
|
50
50
|
if (!response.ok) throw new Error(text || "Unable to render this diagram.");
|
|
51
51
|
return text;
|
|
52
52
|
});
|
|
53
53
|
}).then(function (svg) {
|
|
54
|
-
if (store.get('plantUmlPreviewJob') !== job || !preview.isConnected) return;
|
|
54
|
+
if (store.get(instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob') !== job || !preview.isConnected) return;
|
|
55
55
|
if (svg.length > 8 * 1024 * 1024 || !/<svg[\s>]/i.test(svg)) throw new Error("The renderer returned an invalid diagram.");
|
|
56
56
|
var image = document.createElement("img");
|
|
57
57
|
image.alt = "Diagram preview of " + path;
|
|
@@ -69,8 +69,8 @@ export function renderPlantUmlPreview(body, source, path) {
|
|
|
69
69
|
mountWireframeViewport(viewport, image, toolbar);
|
|
70
70
|
|
|
71
71
|
}).catch(function (error) {
|
|
72
|
-
if (store.get('plantUmlPreviewJob') === job && error.name !== "AbortError") showError(error.message);
|
|
72
|
+
if (store.get(instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob') === job && error.name !== "AbortError") showError(error.message);
|
|
73
73
|
}).finally(function () {
|
|
74
|
-
if (store.get('plantUmlPreviewJob') === job) cancelPlantUmlPreview();
|
|
74
|
+
if (store.get(instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob') === job) cancelPlantUmlPreview(instanceId);
|
|
75
75
|
});
|
|
76
76
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
import { WORKBENCH_PANELS } from './workbench-panels.js';
|
|
2
|
+
import { activeSavedView, workspaceChanged } from './view-workspace-state.js';
|
|
1
3
|
// Shared arbitration for docked background panes and one foreground overlay.
|
|
2
4
|
|
|
5
|
+
import { renderToolPaneChrome } from './tool-pane-chrome.js';
|
|
3
6
|
import { store } from './store.js';
|
|
4
7
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
5
8
|
import { showToast } from './utils.js';
|
|
6
9
|
|
|
7
10
|
var closers = {};
|
|
8
|
-
var panelIds = { terminal: 'terminal-container', browser: 'shared-browser-panel', 'file-viewer': 'file-viewer'
|
|
9
|
-
|
|
10
|
-
'notes-browser': 'notes-browser', 'scheduled-tasks': 'scheduled-tasks-panel', debates: 'debates-workbench',
|
|
11
|
-
'mcp-skills': 'mcp-skills-workbench', 'mate-knowledge': 'mate-knowledge-workbench', wireframe: 'wireframe-panel' };
|
|
11
|
+
var panelIds = { terminal: 'terminal-container', browser: 'shared-browser-panel', 'file-viewer': 'file-viewer' };
|
|
12
|
+
Object.keys(WORKBENCH_PANELS).forEach(function (name) { panelIds[name] = WORKBENCH_PANELS[name].id; });
|
|
12
13
|
|
|
13
14
|
// Pane startup does not need account settings for a closed workbench.
|
|
14
15
|
function ensureLayoutPreference() {
|
|
@@ -34,6 +35,7 @@ export function rightWorkbenchRevisionFor(name) {
|
|
|
34
35
|
function publish(owner, docked, revisions) {
|
|
35
36
|
store.set({ rightWorkbenchOwner: owner, rightWorkbenchDockedOwners: docked,
|
|
36
37
|
rightWorkbenchPanelRevisions: revisions, rightWorkbenchRevision: (store.get('rightWorkbenchRevision') || 0) + 1 });
|
|
38
|
+
workspaceChanged();
|
|
37
39
|
}
|
|
38
40
|
function setDocked(name, docked) {
|
|
39
41
|
if (!openOwner(name)) return;
|
|
@@ -53,16 +55,18 @@ function renderLayoutControls() {
|
|
|
53
55
|
var panel = document.getElementById(panelIds[name]);
|
|
54
56
|
if (!panel) return;
|
|
55
57
|
var docked = owners.indexOf(name) !== -1;
|
|
58
|
+
renderToolPaneChrome(panel, name);
|
|
56
59
|
panel.dataset.workbenchPlacement = docked ? 'docked' : 'overlay';
|
|
57
60
|
panel.dataset.workbenchActive = store.get('rightWorkbenchOwner') === name ? 'true' : 'false';
|
|
58
61
|
if (!openOwner(name)) return;
|
|
59
|
-
var header = panel.querySelector(':scope > header, :scope > .file-viewer-header .file-viewer-tabbar, :scope > .terminal-header');
|
|
62
|
+
var header = panel.querySelector(':scope > .tool-pane-header, :scope > header, :scope > .file-viewer-header .file-viewer-tabbar, :scope > .terminal-header');
|
|
60
63
|
if (!header) return;
|
|
61
64
|
var button = panel.querySelector('.workbench-layout-toggle');
|
|
62
65
|
if (!button) {
|
|
63
|
-
var actions = header.querySelector('[class*="window-actions"], .terminal-header-actions, .shared-browser-window, :scope.mcp-skills-header > div') || header;
|
|
66
|
+
var actions = header.querySelector('[class*="window-actions"], .terminal-header-actions, .shared-browser-window, .file-viewer-window-actions, .workbench-pane-actions, :scope.mcp-skills-header > div') || header;
|
|
64
67
|
button = document.createElement('button'); button.type = 'button'; button.className = 'workbench-layout-toggle';
|
|
65
68
|
button.addEventListener('click', function () {
|
|
69
|
+
if (activeSavedView()) { setDocked(name, dockedOwners().indexOf(name) < 0); return; }
|
|
66
70
|
var current = store.get('workbenchLayoutPreference') || {};
|
|
67
71
|
if (!current.ready || current.loading || current.saving) { ensureLayoutPreference(); return; }
|
|
68
72
|
var owner = store.get('myUserId'), project = store.get('currentSlug'), revision = rightWorkbenchRevisionFor(name);
|
|
@@ -83,6 +87,7 @@ function renderLayoutControls() {
|
|
|
83
87
|
});
|
|
84
88
|
});
|
|
85
89
|
var anchor = actions === header ? header.querySelector('[data-knowledge-wide]') : actions.firstElementChild;
|
|
90
|
+
if (panel.dataset.fileInstance) { var pin = actions.querySelector('[data-action="pin"]'); anchor = pin && pin.nextSibling; }
|
|
86
91
|
actions.insertBefore(button, anchor || null);
|
|
87
92
|
}
|
|
88
93
|
if (button.dataset.mode !== (docked ? 'docked' : 'overlay')) {
|
|
@@ -92,20 +97,26 @@ function renderLayoutControls() {
|
|
|
92
97
|
button.setAttribute('aria-label', docked ? 'Float workbench over sessions' : 'Dock workbench beside sessions');
|
|
93
98
|
button.title = state.error || button.getAttribute('aria-label');
|
|
94
99
|
button.setAttribute('aria-pressed', docked ? 'true' : 'false');
|
|
95
|
-
|
|
100
|
+
// View edits are queued by the workspace controller. Disabling this button
|
|
101
|
+
// for an unrelated in-flight save can swallow a click between down and up.
|
|
102
|
+
button.disabled = activeSavedView() ? false : !!state.loading || !!state.saving;
|
|
96
103
|
button.setAttribute('aria-busy', state.loading || state.saving ? 'true' : 'false');
|
|
97
104
|
});
|
|
98
105
|
}
|
|
99
106
|
|
|
100
|
-
export function registerRightWorkbench(name, close) {
|
|
107
|
+
export function registerRightWorkbench(name, close, panelId) {
|
|
108
|
+
if (panelId) panelIds[name] = panelId;
|
|
101
109
|
if (!name || typeof close !== "function") return;
|
|
102
110
|
closers[name] = close;
|
|
103
111
|
}
|
|
104
112
|
|
|
113
|
+
export function unregisterRightWorkbench(name) {
|
|
114
|
+
delete closers[name];
|
|
115
|
+
if (name.indexOf('file-') === 0 && name !== 'file-viewer') delete panelIds[name];
|
|
116
|
+
}
|
|
105
117
|
export function claimRightWorkbench(name) {
|
|
106
118
|
if (typeof document !== 'undefined') ensureLayoutPreference();
|
|
107
|
-
var owner = store.get('rightWorkbenchOwner'), docked = dockedOwners().slice()
|
|
108
|
-
if (!owner && !docked.length && preference.ready && preference.mode === 'docked') docked.push(name);
|
|
119
|
+
var owner = store.get('rightWorkbenchOwner'), docked = dockedOwners().slice();
|
|
109
120
|
var retained = docked.indexOf(name) !== -1 && owner && docked.indexOf(owner) === -1 ? owner : name;
|
|
110
121
|
var revision = (store.get('rightWorkbenchRevision') || 0) + 1, revisions = Object.assign({}, store.get('rightWorkbenchPanelRevisions') || {});
|
|
111
122
|
Object.keys(revisions).forEach(function (key) { if (key !== retained && docked.indexOf(key) === -1) delete revisions[key]; });
|
|
@@ -133,10 +144,18 @@ export function resetRightWorkbench() {
|
|
|
133
144
|
}
|
|
134
145
|
store.subscribe(function (state, previous) {
|
|
135
146
|
if (state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId) { resetRightWorkbench(); return; }
|
|
136
|
-
|
|
137
|
-
if (!before.ready && preference.ready && preference.mode === 'docked' && !dockedOwners().length && state.rightWorkbenchOwner) {
|
|
138
|
-
setDocked(state.rightWorkbenchOwner, true); return;
|
|
139
|
-
}
|
|
140
|
-
if (state.workbenchLayoutPreference !== previous.workbenchLayoutPreference || state.rightWorkbenchOwner !== previous.rightWorkbenchOwner ||
|
|
147
|
+
if (state.projectSplitViewPending !== previous.projectSplitViewPending || state.workbenchLayoutPreference !== previous.workbenchLayoutPreference || state.rightWorkbenchOwner !== previous.rightWorkbenchOwner ||
|
|
141
148
|
state.rightWorkbenchDockedOwners !== previous.rightWorkbenchDockedOwners) renderLayoutControls();
|
|
142
149
|
});
|
|
150
|
+
|
|
151
|
+
export function restoreWorkbenchDock(name) { setDocked(name, true); }
|
|
152
|
+
|
|
153
|
+
// Document-level shortcuts belong to the focused pane, then the foreground overlay.
|
|
154
|
+
export function handlesWorkbenchKey(name, event) {
|
|
155
|
+
if (event.defaultPrevented) return false;
|
|
156
|
+
var target = event.target, focused = Object.keys(panelIds).find(function (owner) {
|
|
157
|
+
var panel = document.getElementById(panelIds[owner]);
|
|
158
|
+
return panel && target && panel.contains(target);
|
|
159
|
+
});
|
|
160
|
+
return (focused || store.get('rightWorkbenchOwner')) === name;
|
|
161
|
+
}
|
|
@@ -65,18 +65,19 @@ function ensurePanel() {
|
|
|
65
65
|
panel.innerHTML =
|
|
66
66
|
'<header class="scheduled-tasks-topbar">' +
|
|
67
67
|
'<span class="scheduled-tasks-title">' + iconHtml("calendar-clock") + 'Scheduled</span>' +
|
|
68
|
-
'<div class="scheduled-tasks-create-group">' +
|
|
69
|
-
'<button class="scheduled-tasks-new" type="button" data-action="new">' + iconHtml("plus") + 'New</button>' +
|
|
70
|
-
'<button id="scheduled-tasks-create-menu-trigger" class="scheduled-tasks-new-menu" type="button" data-action="new-menu" aria-label="More task creation options" aria-haspopup="menu" aria-expanded="false" aria-controls="scheduled-tasks-create-menu">' + iconHtml("chevron-down") + '</button>' +
|
|
71
|
-
'<div id="scheduled-tasks-create-menu" class="scheduled-tasks-create-menu hidden" role="menu" aria-labelledby="scheduled-tasks-create-menu-trigger"><button type="button" role="menuitem" data-action="manual">Manual creation</button></div>' +
|
|
72
|
-
'</div>' +
|
|
73
68
|
'<div class="scheduled-tasks-window-actions">' +
|
|
74
69
|
'<button class="scheduled-tasks-icon-btn" type="button" data-action="wide" title="Widen panel" aria-label="Widen Scheduled panel" aria-pressed="false">' + iconHtml("chevrons-left-right") + '</button>' +
|
|
75
70
|
'<button class="scheduled-tasks-icon-btn" type="button" data-action="fullscreen" title="Toggle fullscreen" aria-label="Toggle Scheduled fullscreen" aria-pressed="false">' + iconHtml("maximize-2") + '</button>' +
|
|
76
71
|
'<button class="scheduled-tasks-icon-btn" type="button" data-action="close" title="Close" aria-label="Close Scheduled">' + iconHtml("x") + '</button>' +
|
|
77
72
|
'</div>' +
|
|
78
73
|
'</header>' +
|
|
79
|
-
'<div class="scheduled-
|
|
74
|
+
'<div class="scheduled-tasks-toolbar" role="toolbar" aria-label="Scheduled task actions">' +
|
|
75
|
+
'<div class="scheduled-tasks-create-group">' +
|
|
76
|
+
'<button class="scheduled-tasks-new" type="button" data-action="new">' + iconHtml("plus") + 'New</button>' +
|
|
77
|
+
'<button id="scheduled-tasks-create-menu-trigger" class="scheduled-tasks-new-menu" type="button" data-action="new-menu" aria-label="More task creation options" aria-haspopup="menu" aria-expanded="false" aria-controls="scheduled-tasks-create-menu">' + iconHtml("chevron-down") + '</button>' +
|
|
78
|
+
'<div id="scheduled-tasks-create-menu" class="scheduled-tasks-create-menu hidden" role="menu" aria-labelledby="scheduled-tasks-create-menu-trigger"><button type="button" role="menuitem" data-action="manual">Manual creation</button></div>' +
|
|
79
|
+
'</div>' +
|
|
80
|
+
'<div class="scheduled-task-default-ai"><span>New interviews use your shared Default AI.</span><button type="button" data-action="default-ai">Change default</button></div></div>' +
|
|
80
81
|
'<div class="scheduled-task-draft-slot"></div>' +
|
|
81
82
|
'<div class="scheduled-tasks-list" role="list"></div>';
|
|
82
83
|
host.appendChild(panel);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { recordToolExpansion } from './view-presentation-state.js';
|
|
1
2
|
import { registerStudioPaint } from './clay-studio-render.js';
|
|
3
|
+
import { hasBrowserPaneHost, initBrowserPaneTransport, forwardBrowserPaneMessage, sendBrowserPaneCommand, ensureBrowserPaneSource, browserPaneSessionId, browserPaneStamp, browserPaneBound } from './pane-browser-transport.js';
|
|
2
4
|
import { browserLinkTarget, initBrowserLinks } from './clay-browser-links.js';
|
|
3
5
|
import { browserSettingsHtml, bindBrowserSettings, renderBrowserSettings, closeBrowserSettings, handleBrowserSettingsMessage } from './shared-browser-settings.js';
|
|
4
6
|
import { renderBrowserTabs } from './shared-browser-tabs.js';
|
|
@@ -19,9 +21,11 @@ function eligible() { return visibleEntry() && !!store.get('activeSessionId'); }
|
|
|
19
21
|
function panel() { return document.getElementById('shared-browser-panel'); }
|
|
20
22
|
function ui(patch) { store.set({ sharedBrowserUi: Object.assign({}, store.get('sharedBrowserUi'), patch) }); }
|
|
21
23
|
function send(type, fields) {
|
|
24
|
+
var browser = store.get('sharedBrowser');
|
|
25
|
+
var paneSent = sendBrowserPaneCommand(type, Object.assign({ browserId: browser && browser.id, epoch: browser && browser.epoch }, fields || {}));
|
|
26
|
+
if (paneSent !== null) return paneSent;
|
|
22
27
|
var ws = getWs();
|
|
23
28
|
if (!ws || ws.readyState !== 1 || !store.get('activeSessionId')) return false;
|
|
24
|
-
var browser = store.get('sharedBrowser');
|
|
25
29
|
ws.send(JSON.stringify(Object.assign({ type: 'shared_browser_' + type, sessionId: store.get('activeSessionId'),
|
|
26
30
|
browserId: browser && browser.id, epoch: browser && browser.epoch }, fields || {})));
|
|
27
31
|
return true;
|
|
@@ -65,7 +69,7 @@ function ensurePanel() {
|
|
|
65
69
|
root.appendChild(el);
|
|
66
70
|
el.querySelector('[data-action="hide"]').onclick = hideSharedBrowser;
|
|
67
71
|
el.querySelector('[data-action="wide"]').onclick = function () { var wide = !(store.get('sharedBrowserUi') || {}).wide; ui({ wide: wide }); render(); };
|
|
68
|
-
el.querySelector('[data-action="fullscreen"]').onclick = function () { var full = !(store.get('sharedBrowserUi') || {}).fullscreen;
|
|
72
|
+
el.querySelector('[data-action="fullscreen"]').onclick = function () { var full = !(store.get('sharedBrowserUi') || {}).fullscreen; setBrowserExpanded(full); recordToolExpansion('browser', full); };
|
|
69
73
|
['back', 'forward', 'reload'].forEach(function (action) { el.querySelector('[data-action="' + action + '"]').onclick = function () { sendBrowserInput({ kind: action }); }; });
|
|
70
74
|
el.querySelector('form').onsubmit = function (event) { event.preventDefault(); sendBrowserInput({ kind: 'navigate', url: el.querySelector('form input').value }); };
|
|
71
75
|
el.querySelector('[data-action="control"]').onclick = function () {
|
|
@@ -178,7 +182,28 @@ function selectBrowserTab(id) {
|
|
|
178
182
|
setBrowserTab(browser);
|
|
179
183
|
send('view', { visible: desktop() && !document.hidden }); render();
|
|
180
184
|
}
|
|
185
|
+
function browserOpenContext() {
|
|
186
|
+
return JSON.stringify([store.get('myUserId'), store.get('currentSlug'), store.get('projectSplitViewActiveId'), store.get('sessionOriginId'), store.get('projectSplitViewSocketGeneration')]);
|
|
187
|
+
}
|
|
188
|
+
function sendOrQueueBrowserOpen(fields) {
|
|
189
|
+
store.set({ pendingBrowserOpen: null });
|
|
190
|
+
if (send('open', fields)) return;
|
|
191
|
+
// A false transport result means nothing was sent. Only this fresh human
|
|
192
|
+
// opening can wait for pane readiness; restoration never enters this path.
|
|
193
|
+
store.set({ pendingBrowserOpen: { context: browserOpenContext(), socket: getWs(), fields: fields || {}, expires: Date.now() + 15000 } });
|
|
194
|
+
}
|
|
195
|
+
function resumePendingBrowserOpen() {
|
|
196
|
+
var pending = store.get('pendingBrowserOpen');
|
|
197
|
+
if (!pending) return;
|
|
198
|
+
if (pending.context !== browserOpenContext() || pending.socket !== getWs() || pending.expires < Date.now() || !store.get('connected') || !store.get('sharedBrowserOpen')) {
|
|
199
|
+
store.set({ pendingBrowserOpen: null }); return;
|
|
200
|
+
}
|
|
201
|
+
if (!ensureBrowserPaneSource()) return;
|
|
202
|
+
store.set({ pendingBrowserOpen: null });
|
|
203
|
+
if (!send('open', pending.fields)) store.set({ pendingBrowserOpen: pending });
|
|
204
|
+
}
|
|
181
205
|
export function openSharedBrowserAt(value) {
|
|
206
|
+
ensureBrowserPaneSource();
|
|
182
207
|
var url = browserLinkTarget('browser:' + value);
|
|
183
208
|
if (!url) throw new Error('Enter a valid HTTP or HTTPS browser address.');
|
|
184
209
|
if (!desktop()) throw new Error('Clay Browser is available on desktop screens.');
|
|
@@ -186,19 +211,22 @@ export function openSharedBrowserAt(value) {
|
|
|
186
211
|
if (!store.get('connected')) throw new Error('Reconnect before opening this browser link.');
|
|
187
212
|
ensurePanel(); closeBrowserSettings(false);
|
|
188
213
|
claimRightWorkbench('browser'); store.set({ sharedBrowserOpen: true });
|
|
189
|
-
ui({ error: '' });
|
|
214
|
+
ui({ error: '' }); sendOrQueueBrowserOpen({ url: url }); render();
|
|
190
215
|
}
|
|
191
216
|
export function openSharedBrowser() {
|
|
217
|
+
ensureBrowserPaneSource();
|
|
192
218
|
if (!eligible() || !store.get('connected')) return;
|
|
193
219
|
ensurePanel();
|
|
194
220
|
claimRightWorkbench('browser');
|
|
195
221
|
store.set({ sharedBrowserOpen: true });
|
|
196
222
|
var browser = store.get('sharedBrowser');
|
|
197
|
-
if (!browser)
|
|
223
|
+
if (!browser) sendOrQueueBrowserOpen();
|
|
198
224
|
else send('view', { visible: true });
|
|
199
225
|
render();
|
|
200
226
|
}
|
|
201
227
|
export function hideSharedBrowser() {
|
|
228
|
+
setBrowserExpanded(false); recordToolExpansion('browser', false);
|
|
229
|
+
if (!store.get('savedWorkspaceApplying')) store.set({ pendingBrowserOpen: null });
|
|
202
230
|
closeViewportMenu();
|
|
203
231
|
closeBrowserSettings();
|
|
204
232
|
clearBrowserInput();
|
|
@@ -207,9 +235,11 @@ export function hideSharedBrowser() {
|
|
|
207
235
|
releaseRightWorkbench('browser');
|
|
208
236
|
render();
|
|
209
237
|
}
|
|
210
|
-
export function handleSharedBrowserMessage(msg) {
|
|
238
|
+
export function handleSharedBrowserMessage(msg, fromPane) {
|
|
211
239
|
if (!msg.type || msg.type.indexOf('shared_browser_') !== 0) return false;
|
|
212
|
-
if (
|
|
240
|
+
if (forwardBrowserPaneMessage(msg)) return true;
|
|
241
|
+
if (browserPaneBound() && !fromPane) return true;
|
|
242
|
+
if (String(msg.sessionId) !== String(browserPaneSessionId() || store.get('activeSessionId'))) return true;
|
|
213
243
|
if (handleBrowserSettingsMessage(msg)) return true;
|
|
214
244
|
if (msg.type === 'shared_browser_state') {
|
|
215
245
|
var old = store.get('sharedBrowser');
|
|
@@ -218,6 +248,7 @@ export function handleSharedBrowserMessage(msg) {
|
|
|
218
248
|
var selected = !msg.focus && old && tabs.find(function (tab) { return tab.id === old.id; });
|
|
219
249
|
selected = selected || msg.browser || tabs[0] || null;
|
|
220
250
|
setBrowserTab(selected);
|
|
251
|
+
store.set({ savedViewBrowserListReady: true });
|
|
221
252
|
if (msg.present && eligible()) {
|
|
222
253
|
ensurePanel(); claimRightWorkbench('browser'); store.set({ sharedBrowserOpen: true });
|
|
223
254
|
}
|
|
@@ -228,23 +259,35 @@ export function handleSharedBrowserMessage(msg) {
|
|
|
228
259
|
var current = store.get('sharedBrowser');
|
|
229
260
|
if (!desktop() || !store.get('sharedBrowserOpen') || !current || current.id !== msg.browserId || (store.get('sharedBrowserUi') || {}).decoding) return true;
|
|
230
261
|
var sessionId = store.get('activeSessionId');
|
|
262
|
+
var paneStamp = browserPaneStamp();
|
|
231
263
|
ui({ decoding: true });
|
|
232
264
|
var image = new Image();
|
|
233
265
|
image.onload = function () {
|
|
234
266
|
var latest = store.get('sharedBrowser');
|
|
235
|
-
if (latest && latest.id === msg.browserId && store.get('activeSessionId') === sessionId && panel()) {
|
|
267
|
+
if (latest && latest.id === msg.browserId && browserPaneStamp() === paneStamp && store.get('activeSessionId') === sessionId && panel()) {
|
|
236
268
|
var canvas = panel().querySelector('canvas'); canvas.width = image.naturalWidth; canvas.height = image.naturalHeight;
|
|
237
269
|
canvas.getContext('2d').drawImage(image, 0, 0);
|
|
238
270
|
ui({ decoding: false, hasFrame: true }); render();
|
|
239
271
|
registerStudioPaint(msg, canvas, null, canvas);
|
|
240
272
|
}
|
|
241
273
|
};
|
|
242
|
-
image.onerror = function () { if (store.get('activeSessionId') === sessionId) ui({ decoding: false }); };
|
|
274
|
+
image.onerror = function () { if (browserPaneStamp() === paneStamp && store.get('activeSessionId') === sessionId) ui({ decoding: false }); };
|
|
243
275
|
image.src = 'data:image/jpeg;base64,' + msg.data;
|
|
244
276
|
}
|
|
245
277
|
return true;
|
|
246
278
|
}
|
|
247
279
|
export function initSharedBrowser() {
|
|
280
|
+
initBrowserPaneTransport(handleSharedBrowserMessage, function (available) {
|
|
281
|
+
var applying = store.get('savedWorkspaceApplying');
|
|
282
|
+
store.set({ savedWorkspaceApplying: true, savedViewBrowserListReady: false });
|
|
283
|
+
try {
|
|
284
|
+
clearBrowserInput(); setBrowserTab(null);
|
|
285
|
+
store.set({ sharedBrowsers: [], sharedBrowserUi: {}, sharedBrowserSettings: {} });
|
|
286
|
+
if (!available) hideSharedBrowser();
|
|
287
|
+
render();
|
|
288
|
+
} finally { store.set({ savedWorkspaceApplying: applying }); }
|
|
289
|
+
});
|
|
290
|
+
document.addEventListener('clay-pane-ready', function () { queueMicrotask(resumePendingBrowserOpen); }, true);
|
|
248
291
|
initBrowserLinks();
|
|
249
292
|
var button = document.getElementById('shared-browser-btn');
|
|
250
293
|
if (button) button.onclick = openSharedBrowser;
|
|
@@ -261,14 +304,31 @@ export function initSharedBrowser() {
|
|
|
261
304
|
registerRightWorkbench('browser', hideSharedBrowser);
|
|
262
305
|
store.subscribe(function (state, previous) {
|
|
263
306
|
if (state.currentSlug !== previous.currentSlug || state.activeSessionId !== previous.activeSessionId || state.myUserId !== previous.myUserId) {
|
|
307
|
+
if (browserPaneSessionId() !== null) { render(); return; }
|
|
264
308
|
clearBrowserInput(); releaseRightWorkbench('browser');
|
|
265
|
-
store.set({ sharedBrowser: null, sharedBrowsers: [], sharedBrowserOpen: false, sharedBrowserUi: {}, sharedBrowserSettings: {} });
|
|
309
|
+
store.set({ sharedBrowser: null, sharedBrowsers: [], savedViewBrowserListReady: false, sharedBrowserOpen: false, sharedBrowserUi: {}, sharedBrowserSettings: {} });
|
|
266
310
|
if (state.connected) send('state_request');
|
|
267
311
|
render();
|
|
268
312
|
} else if (state.connected !== previous.connected) {
|
|
313
|
+
store.set({ savedViewBrowserListReady: false });
|
|
269
314
|
if (state.connected) send('state_request');
|
|
270
315
|
render();
|
|
271
316
|
} else if (state.permissions !== previous.permissions || state.dmMode !== previous.dmMode) {
|
|
272
317
|
if (!eligible()) hideSharedBrowser(); renderEntry();
|
|
273
318
|
}
|
|
274
319
|
});
|
|
320
|
+
|
|
321
|
+
export function restoreExistingBrowser(resource) {
|
|
322
|
+
if (hasBrowserPaneHost() && !ensureBrowserPaneSource()) return 'pending';
|
|
323
|
+
if (!store.get('savedViewBrowserListReady')) { send('state_request'); return 'pending'; }
|
|
324
|
+
if (!store.get('connected') || !store.get('controlRuntimeId')) return 'pending';
|
|
325
|
+
ensurePanel();
|
|
326
|
+
var browser = (store.get('sharedBrowsers') || []).find(function (item) { return item.id === resource.id && item.phase !== 'ended'; });
|
|
327
|
+
var exact = browser && resource.runtimeId === store.get('controlRuntimeId') && resource.sessionOriginId === store.get('sessionOriginId');
|
|
328
|
+
claimRightWorkbench('browser'); store.set({ sharedBrowserOpen: true });
|
|
329
|
+
if (exact) { setBrowserTab(browser); send('view', { visible: true }); ui({ error: '' }); render(); return 'restored'; }
|
|
330
|
+
ui({ error: 'This saved browser is unavailable in the current conversation. No replacement browser was opened.' }); render();
|
|
331
|
+
return 'unavailable';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function setBrowserExpanded(enabled) { ui({ fullscreen: !!enabled }); if (document.getElementById('shared-browser-panel')) render(); }
|
|
@@ -296,6 +296,7 @@ export function createSplitPane(pane, closePane) {
|
|
|
296
296
|
if (!vendorIcon.hidden) vendorIcon.src = VENDOR_AVATARS[vendor]; vendorIcon.alt = ""; vendorIcon.title = VENDOR_NAMES[vendor] || vendor; header.appendChild(vendorIcon);
|
|
297
297
|
var title = document.createElement("span"); title.className = "split-pane-title"; title.textContent = pane.title; title.title = "Rename session";
|
|
298
298
|
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);
|
|
299
|
+
var dragSpace = document.createElement("span"); dragSpace.className = "split-pane-drag-space"; dragSpace.title = "Drag to rearrange panes"; dragSpace.setAttribute("aria-hidden", "true"); header.appendChild(dragSpace);
|
|
299
300
|
var frame = document.createElement("iframe"); frame.className = "split-pane-frame"; frame.dataset.projectSlug = pane.slug;
|
|
300
301
|
frame.dataset.sessionId = String(pane.sessionId); frame.src = paneUrl(pane); frame.title = pane.title;
|
|
301
302
|
frame.dataset.sessionOriginId = session && session.sessionOriginId || '';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { livePaneBinding, samePaneBinding } from './pane-binding.js';
|
|
4
|
+
import { layoutScope, currentLayoutScope } from './split-view-layout-scope.js';
|
|
5
|
+
import { presentationDropTree } from './split-view-presentation-drop.js';
|
|
6
|
+
import { rectangles } from './split-view-layout-client.js';
|
|
7
|
+
|
|
8
|
+
function activeView() {
|
|
9
|
+
return (store.get('projectSplitViews') || []).find(function (view) { return view.id === store.get('projectSplitViewActiveId'); });
|
|
10
|
+
}
|
|
11
|
+
function reducedMotion() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
|
|
12
|
+
export function captureDragPreview(drag, host) {
|
|
13
|
+
var settling = store.get('projectSplitViewDragSettling');
|
|
14
|
+
if (settling && settling.timer) clearTimeout(settling.timer);
|
|
15
|
+
store.set({ projectSplitViewDragSettling: null });
|
|
16
|
+
var view = activeView();
|
|
17
|
+
drag.preview = { host: host, socket: getWs(), slug: store.get('currentSlug'), owner: store.get('myUserId'), projectOwner: store.get('currentProjectOwnerId'),
|
|
18
|
+
scope: view && layoutScope(view), width: host.clientWidth, height: host.clientHeight, tree: null,
|
|
19
|
+
panes: Array.from(host.querySelectorAll(':scope > .split-pane:not(.split-pane-cached)')).filter(function (pane) {
|
|
20
|
+
return !pane.hidden && pane.getBoundingClientRect().width > 0;
|
|
21
|
+
}).map(function (pane) {
|
|
22
|
+
var frame = pane.querySelector('.split-pane-frame'), live = frame && frame.clayPane && frame.clayPane.identity();
|
|
23
|
+
return { pane: pane, frame: frame, document: frame && frame.contentDocument, window: frame && frame.contentWindow,
|
|
24
|
+
binding: live && live.binding, rect: pane.getBoundingClientRect() };
|
|
25
|
+
}) };
|
|
26
|
+
}
|
|
27
|
+
export function dragPreviewCurrent(drag) {
|
|
28
|
+
var preview = drag.preview;
|
|
29
|
+
return !!preview && preview.socket === getWs() && preview.slug === store.get('currentSlug') && preview.owner === store.get('myUserId') &&
|
|
30
|
+
preview.projectOwner === store.get('currentProjectOwnerId') && store.get('connected') && !store.get('projectSplitViewDividerDrag') && (!preview.scope || currentLayoutScope(preview.scope)) &&
|
|
31
|
+
preview.width === preview.host.clientWidth && preview.height === preview.host.clientHeight && preview.panes.every(function (entry) {
|
|
32
|
+
var live = entry.frame && entry.frame.clayPane && entry.frame.clayPane.identity();
|
|
33
|
+
return entry.pane.isConnected && entry.frame.contentDocument === entry.document && entry.frame.contentWindow === entry.window &&
|
|
34
|
+
live && samePaneBinding(live.binding, entry.binding) && livePaneBinding(entry.frame);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// Hit-test the starting layout, never panes that are travelling under the hand.
|
|
38
|
+
export function dragPreviewPaneAt(drag, x, y) {
|
|
39
|
+
var entries = drag && drag.preview && drag.preview.panes || [];
|
|
40
|
+
return entries.find(function (entry) {
|
|
41
|
+
var r = entry.rect; return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export function renderDragPreview(drag, target) {
|
|
45
|
+
var preview = drag.preview, view = activeView();
|
|
46
|
+
if (!preview || !view || !dragPreviewCurrent(drag)) return null;
|
|
47
|
+
var sourceView = (store.get('projectSplitViews') || []).find(function (item) { return item.id === drag.sourceViewId; });
|
|
48
|
+
var tree = target && !target.sidebar && target.viewId === view.id && sourceView && sourceView.id === view.id && !store.get('projectSplitViewFocusedOrigin') ?
|
|
49
|
+
presentationDropTree(drag, target, sourceView, view) : null;
|
|
50
|
+
if (JSON.stringify(tree) === JSON.stringify(preview.tree)) return preview.dropRect || null;
|
|
51
|
+
preview.tree = tree;
|
|
52
|
+
preview.host.classList.add('split-view-drag-preview');
|
|
53
|
+
store.set({ projectSplitViewPreviewTree: tree, projectSplitViewPreviewScope: preview.scope });
|
|
54
|
+
preview.dropRect = null;
|
|
55
|
+
if (tree) {
|
|
56
|
+
var style = window.getComputedStyle(preview.host), left = parseFloat(style.paddingLeft) || 0, top = parseFloat(style.paddingTop) || 0;
|
|
57
|
+
var geometry = rectangles(tree, preview.width - left - (parseFloat(style.paddingRight) || 0), preview.height - top - (parseFloat(style.paddingBottom) || 0), 7);
|
|
58
|
+
var rect = geometry.panes[drag.origin];
|
|
59
|
+
if (rect) preview.dropRect = { left: left + rect.x, top: top + rect.y, width: rect.width, height: rect.height };
|
|
60
|
+
}
|
|
61
|
+
return preview.dropRect;
|
|
62
|
+
}
|
|
63
|
+
function settleMotion() {
|
|
64
|
+
var settling = store.get('projectSplitViewDragSettling');
|
|
65
|
+
if (!settling || settling.timer || store.get('projectSplitViewDrag') || store.get('projectSplitViewPending')) return;
|
|
66
|
+
settling.timer = setTimeout(function () {
|
|
67
|
+
if (store.get('projectSplitViewDragSettling') !== settling) return;
|
|
68
|
+
settling.host.classList.remove('split-view-drag-preview');
|
|
69
|
+
store.set({ projectSplitViewDragSettling: null });
|
|
70
|
+
}, reducedMotion() ? 0 : 320);
|
|
71
|
+
}
|
|
72
|
+
export function restoreDragPreview(drag, keep) {
|
|
73
|
+
var preview = drag && drag.preview;
|
|
74
|
+
if (!preview) return;
|
|
75
|
+
if (!keep && preview.tree && store.get('projectSplitViewPreviewTree') === preview.tree) {
|
|
76
|
+
store.set({ projectSplitViewPreviewTree: null, projectSplitViewPreviewScope: null });
|
|
77
|
+
}
|
|
78
|
+
var previous = store.get('projectSplitViewDragSettling');
|
|
79
|
+
if (previous && previous.timer) clearTimeout(previous.timer);
|
|
80
|
+
store.set({ projectSplitViewDragSettling: { host: preview.host, timer: null } });
|
|
81
|
+
settleMotion();
|
|
82
|
+
}
|
|
83
|
+
// Preserve transcript anchors until animated width/height changes have settled.
|
|
84
|
+
export function afterPaneMotion(host, callback) {
|
|
85
|
+
window.requestAnimationFrame(function () {
|
|
86
|
+
if (host.classList.contains('split-view-drag-preview') && !reducedMotion()) setTimeout(callback, 300);
|
|
87
|
+
else callback();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
store.subscribe(function () { settleMotion(); });
|
|
@@ -24,7 +24,7 @@ export function validateTree(tree, expectedIds) {
|
|
|
24
24
|
!Number.isFinite(node.ratio) || node.ratio < MIN_RATIO || node.ratio > MAX_RATIO) return false;
|
|
25
25
|
return walk(node.left, depth + 1) && walk(node.right, depth + 1);
|
|
26
26
|
}
|
|
27
|
-
valid = walk(tree, 0) && ids.length >=
|
|
27
|
+
valid = (tree === null || walk(tree, 0)) && ids.length >= 0 && ids.length <= MAX_SESSIONS && new Set(ids).size === ids.length;
|
|
28
28
|
if (valid && expectedIds) {
|
|
29
29
|
valid = Array.isArray(expectedIds) && expectedIds.length === ids.length &&
|
|
30
30
|
expectedIds.every(function (id) { return ids.indexOf(id) !== -1; });
|