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
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { recordToolExpansion } from './view-presentation-state.js';
|
|
2
|
+
import { workspaceChanged } from './view-workspace-state.js';
|
|
1
3
|
import { store } from './store.js';
|
|
2
4
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
3
5
|
import { closeSidebar } from './sidebar.js';
|
|
4
6
|
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
|
|
5
7
|
import { copyToClipboard } from './utils.js';
|
|
6
|
-
import { getTerminalTheme
|
|
8
|
+
import { getTerminalTheme } from './theme.js';
|
|
7
9
|
import { getTerminalFontFamily, getTerminalFontSize, onTerminalFontChange } from './terminal-prefs.js';
|
|
8
10
|
import { createKeyToolbar } from './terminal-toolbar.js';
|
|
9
11
|
|
|
@@ -145,13 +147,8 @@ export function initTerminal(_ctx) {
|
|
|
145
147
|
|
|
146
148
|
// Fullscreen toggle
|
|
147
149
|
document.getElementById("terminal-fullscreen").addEventListener("click", function () {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (icon) {
|
|
151
|
-
icon.setAttribute("data-lucide", isFs ? "minimize-2" : "maximize-2");
|
|
152
|
-
refreshIcons();
|
|
153
|
-
}
|
|
154
|
-
fitTerminal();
|
|
150
|
+
setTerminalExpanded(!ctx.terminalContainerEl.classList.contains('panel-fullscreen'));
|
|
151
|
+
recordToolExpansion('terminal', ctx.terminalContainerEl.classList.contains('panel-fullscreen'));
|
|
155
152
|
});
|
|
156
153
|
|
|
157
154
|
// Header toggle button
|
|
@@ -224,6 +221,7 @@ export function openTerminal() {
|
|
|
224
221
|
|
|
225
222
|
// --- Close terminal panel (hide, detach, but keep PTYs alive) ---
|
|
226
223
|
export function closeTerminal() {
|
|
224
|
+
recordToolExpansion('terminal', false);
|
|
227
225
|
releaseRightWorkbench("terminal");
|
|
228
226
|
var container = ctx.terminalContainerEl;
|
|
229
227
|
container.classList.remove("panel-fullscreen");
|
|
@@ -279,7 +277,7 @@ function closeTab(termId) {
|
|
|
279
277
|
}
|
|
280
278
|
|
|
281
279
|
// --- Activate a tab (show xterm, attach) ---
|
|
282
|
-
function activateTab(termId) {
|
|
280
|
+
function activateTab(termId, savedBinding) {
|
|
283
281
|
var tab = tabs.get(termId);
|
|
284
282
|
if (!tab) return;
|
|
285
283
|
|
|
@@ -294,6 +292,10 @@ function activateTab(termId) {
|
|
|
294
292
|
}
|
|
295
293
|
|
|
296
294
|
activeTabId = termId;
|
|
295
|
+
var binding = (store.get("clayControlTerminalViews") || []).find(function (item) { return item.id === termId; });
|
|
296
|
+
if (binding) store.set({ savedViewTerminalResource: { id: binding.id, identity: binding.identity, runtimeId: binding.runtimeId } });
|
|
297
|
+
var unavailable = document.getElementById("terminal-saved-unavailable"); if (unavailable) unavailable.remove();
|
|
298
|
+
workspaceChanged();
|
|
297
299
|
|
|
298
300
|
// Lazy-create xterm instance
|
|
299
301
|
if (!tab.xterm) {
|
|
@@ -305,7 +307,7 @@ function activateTab(termId) {
|
|
|
305
307
|
|
|
306
308
|
// Attach to server
|
|
307
309
|
if (ctx.ws && ctx.connected) {
|
|
308
|
-
ctx.ws.send(JSON.stringify({ type: "term_attach", id: termId }));
|
|
310
|
+
ctx.ws.send(JSON.stringify({ type: "term_attach", id: termId, savedBinding: savedBinding }));
|
|
309
311
|
}
|
|
310
312
|
|
|
311
313
|
// Fit and focus
|
|
@@ -358,7 +360,7 @@ function createXtermForTab(tab) {
|
|
|
358
360
|
cursorBlink: true,
|
|
359
361
|
fontSize: getTerminalFontSize(),
|
|
360
362
|
fontFamily: getTerminalFontFamily(),
|
|
361
|
-
theme:
|
|
363
|
+
theme: getTerminalTheme(),
|
|
362
364
|
});
|
|
363
365
|
|
|
364
366
|
var fitAddon = null;
|
|
@@ -557,11 +559,13 @@ function renderTabBar() {
|
|
|
557
559
|
|
|
558
560
|
var moreBtn = document.createElement("button");
|
|
559
561
|
moreBtn.className = "terminal-tab-more";
|
|
560
|
-
moreBtn.innerHTML = '
|
|
562
|
+
moreBtn.innerHTML = iconHtml('x');
|
|
563
|
+
moreBtn.setAttribute('aria-label', 'Close terminal tab ' + t.title);
|
|
561
564
|
moreBtn.addEventListener("click", function (e) {
|
|
562
565
|
e.stopPropagation();
|
|
563
|
-
|
|
566
|
+
closeTab(t.id);
|
|
564
567
|
});
|
|
568
|
+
el.addEventListener('contextmenu', function (event) { event.preventDefault(); showTabContextMenu(event, t, label); });
|
|
565
569
|
el.appendChild(moreBtn);
|
|
566
570
|
|
|
567
571
|
el.addEventListener("click", function () {
|
|
@@ -699,7 +703,7 @@ function updateTerminalBadge() {
|
|
|
699
703
|
// --- Handle server messages ---
|
|
700
704
|
|
|
701
705
|
export function handleTermList(msg) {
|
|
702
|
-
store.set({ clayControlTerminalViews: (msg.terminals || []).map(function (item) { return { id: item.id, identity: item.controlIdentity, revision: item.controlRevision, runtimeId: store.get('controlRuntimeId') }; }) });
|
|
706
|
+
store.set({ clayControlTerminalViews: (msg.terminals || []).map(function (item) { return { id: item.id, identity: item.controlIdentity, revision: item.controlRevision, exited: item.exited, runtimeId: store.get('controlRuntimeId') }; }) });
|
|
703
707
|
// TUI session terminals are owned by session-tui-view.js and must not
|
|
704
708
|
// appear in the bottom terminal panel. Filter them out before any tab
|
|
705
709
|
// bookkeeping so the panel never tries to render or auto-attach them.
|
|
@@ -744,6 +748,7 @@ export function handleTermList(msg) {
|
|
|
744
748
|
}
|
|
745
749
|
|
|
746
750
|
renderTabBar();
|
|
751
|
+
store.set({ savedViewTerminalListReady: true });
|
|
747
752
|
|
|
748
753
|
// If panel is open and we have tabs, re-attach
|
|
749
754
|
if (isOpen && tabs.size > 0) {
|
|
@@ -825,6 +830,7 @@ export function handleTermClosed(msg) {
|
|
|
825
830
|
|
|
826
831
|
// --- Reset on reconnect ---
|
|
827
832
|
export function resetTerminals() {
|
|
833
|
+
store.set({ savedViewTerminalListReady: false, savedViewTerminalResource: null });
|
|
828
834
|
// Dispose all xterm instances (server state survives, client re-syncs via term_list)
|
|
829
835
|
for (var tab of tabs.values()) {
|
|
830
836
|
disposeTab(tab);
|
|
@@ -842,8 +848,8 @@ export function sendTerminalCommand(command) {
|
|
|
842
848
|
}
|
|
843
849
|
}
|
|
844
850
|
|
|
845
|
-
export function setTerminalTheme(
|
|
846
|
-
|
|
851
|
+
export function setTerminalTheme() {
|
|
852
|
+
var xtermTheme = getTerminalTheme();
|
|
847
853
|
for (var tab of tabs.values()) {
|
|
848
854
|
if (tab.xterm) {
|
|
849
855
|
tab.xterm.options.theme = xtermTheme;
|
|
@@ -851,9 +857,6 @@ export function setTerminalTheme(xtermTheme) {
|
|
|
851
857
|
}
|
|
852
858
|
}
|
|
853
859
|
|
|
854
|
-
store.subscribe(function (state, previous) {
|
|
855
|
-
if ((state.rightWorkbenchDockedOwners || []).includes('terminal') !== (previous.rightWorkbenchDockedOwners || []).includes('terminal')) setTerminalTheme(getTerminalTheme());
|
|
856
|
-
});
|
|
857
860
|
|
|
858
861
|
// Live font update: applies family/size to every open shell terminal
|
|
859
862
|
// and refits each (cell dimensions change with font size, so cols/rows
|
|
@@ -983,3 +986,27 @@ function showTermCtxMenu(e, tab) {
|
|
|
983
986
|
document.addEventListener("click", closeTermCtxMenu, { once: true });
|
|
984
987
|
}, 0);
|
|
985
988
|
}
|
|
989
|
+
|
|
990
|
+
// Restoration must never enter openTerminal's create-new branch.
|
|
991
|
+
export function restoreExistingTerminal(resource) {
|
|
992
|
+
if (!store.get('connected') || !store.get('controlRuntimeId')) return 'pending';
|
|
993
|
+
if (!store.get('savedViewTerminalListReady')) return 'pending';
|
|
994
|
+
var live = (store.get('clayControlTerminalViews') || []).find(function (item) { return item.id === resource.id && item.identity === resource.identity && item.runtimeId === resource.runtimeId && !item.exited; });
|
|
995
|
+
claimRightWorkbench('terminal');
|
|
996
|
+
var container = document.getElementById('terminal-container'); container.classList.remove('hidden');
|
|
997
|
+
if (live && tabs.has(resource.id)) { isOpen = true; activateTab(resource.id, resource); return 'restored'; }
|
|
998
|
+
isOpen = false;
|
|
999
|
+
tabs.forEach(function (tab) { if (tab.bodyEl) tab.bodyEl.style.display = 'none'; });
|
|
1000
|
+
var notice = document.getElementById('terminal-saved-unavailable');
|
|
1001
|
+
if (!notice) { notice = document.createElement('p'); notice.id = 'terminal-saved-unavailable'; notice.className = 'saved-view-unavailable'; notice.setAttribute('role', 'status'); document.getElementById('terminal-body').appendChild(notice); }
|
|
1002
|
+
notice.textContent = 'This saved terminal is unavailable. Close this pane and open Terminal to choose an available shell.';
|
|
1003
|
+
return 'unavailable';
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
export function setTerminalExpanded(enabled) {
|
|
1007
|
+
var panel = document.getElementById('terminal-container'); if (!panel) return;
|
|
1008
|
+
panel.classList.toggle('panel-fullscreen', !!enabled);
|
|
1009
|
+
var icon = panel.querySelector('#terminal-fullscreen [data-lucide]');
|
|
1010
|
+
if (icon) { icon.setAttribute('data-lucide', enabled ? 'minimize-2' : 'maximize-2'); refreshIcons(); }
|
|
1011
|
+
if (!panel.classList.contains('hidden')) fitTerminal();
|
|
1012
|
+
}
|
|
@@ -267,11 +267,6 @@ export function getTerminalTheme() {
|
|
|
267
267
|
return computeTerminalTheme(getTheme(DEFAULT_DARK_THEME_ID) || defaultDarkFallback);
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
-
export function getDockedTerminalTheme() {
|
|
271
|
-
var theme = getCurrentTheme();
|
|
272
|
-
return Object.assign({}, computeTerminalTheme(theme), { background: '#' + theme.base00 });
|
|
273
|
-
}
|
|
274
|
-
|
|
275
270
|
export function getMermaidThemeVars() {
|
|
276
271
|
return computeMermaidVars(getCurrentTheme());
|
|
277
272
|
}
|
|
@@ -332,7 +327,7 @@ export function applyTheme(themeId, fromPicker) {
|
|
|
332
327
|
// Terminals deliberately keep Clay Studio Dark for predictable ANSI
|
|
333
328
|
// contrast, even while the surrounding workspace uses a light theme.
|
|
334
329
|
var termTheme = getTerminalTheme();
|
|
335
|
-
try { setTerminalTheme(
|
|
330
|
+
try { setTerminalTheme(); } catch (e) {}
|
|
336
331
|
try { setTuiSessionTheme(termTheme); } catch (e) {}
|
|
337
332
|
try { setTuiAttentionTheme(termTheme); } catch (e) {}
|
|
338
333
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { WORKBENCH_PANELS } from './workbench-panels.js';
|
|
2
|
+
import { recordToolExpansion } from './view-presentation-state.js';
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
5
|
+
import { TOOL_PANES } from './tool-pane-tree.js';
|
|
6
|
+
|
|
7
|
+
function titleFor(panel, name) {
|
|
8
|
+
if (name === 'terminal') {
|
|
9
|
+
var state = store.snap(), project = (state.projectsHubList || []).find(function (item) { return item && item.slug === state.currentSlug; });
|
|
10
|
+
var projectName = project && (project.title || project.project || project.name) || state.projectName || state.currentSlug;
|
|
11
|
+
return projectName ? projectName + ' · Terminal' : 'Terminal';
|
|
12
|
+
}
|
|
13
|
+
if (name === 'file-viewer') { var tab = panel.querySelector('.file-viewer-tab.active .file-viewer-tab-label'); return tab && tab.textContent || 'Files'; }
|
|
14
|
+
var browser = store.get('sharedBrowser');
|
|
15
|
+
try { return browser && browser.url ? new URL(browser.url).hostname : 'Browser'; } catch (error) { return 'Browser'; }
|
|
16
|
+
}
|
|
17
|
+
export function renderToolPaneChrome(panel, name) {
|
|
18
|
+
if (panel.dataset.fileInstance) return;
|
|
19
|
+
if (WORKBENCH_PANELS[name]) { renderWorkbenchChrome(panel, name); return; }
|
|
20
|
+
var definition = TOOL_PANES[name];
|
|
21
|
+
if (!definition) return;
|
|
22
|
+
var header = panel.querySelector(':scope > .tool-pane-header');
|
|
23
|
+
if (!header) {
|
|
24
|
+
var original = panel.querySelector(name === 'terminal' ? '.terminal-header' : name === 'browser' ? '.shared-browser-header' : '.file-viewer-tabbar');
|
|
25
|
+
if (!original) return;
|
|
26
|
+
header = document.createElement('header'); header.className = 'tool-pane-header split-pane-header'; header.dataset.toolPane = name;
|
|
27
|
+
var kind = document.createElement('span'); kind.className = 'tool-pane-kind' + (name === 'terminal' ? '' : ' split-view-runtime');
|
|
28
|
+
kind.innerHTML = iconHtml(definition.icon) + (name === 'terminal' ? '' : '<span class="split-view-model">' + definition.label + '</span>');
|
|
29
|
+
var title = document.createElement('span'); title.className = 'tool-pane-title split-pane-title';
|
|
30
|
+
var space = document.createElement('span'); space.className = 'split-pane-drag-space'; space.title = 'Drag to rearrange panes'; space.setAttribute('aria-hidden', 'true');
|
|
31
|
+
var actions = original.querySelector('.terminal-header-actions, .shared-browser-window, .file-viewer-window-actions');
|
|
32
|
+
header.append(kind, title, space); if (actions) header.appendChild(actions);
|
|
33
|
+
panel.insertBefore(header, panel.firstChild);
|
|
34
|
+
if (name === 'browser') { original.hidden = true; var close = header.querySelector('[data-action="hide"]'); if (close) { close.title = 'Close browser pane'; close.setAttribute('aria-label', 'Close browser pane'); } }
|
|
35
|
+
else original.classList.add('tool-pane-tab-row');
|
|
36
|
+
var observer = new MutationObserver(function () { update(); });
|
|
37
|
+
observer.observe(panel, { childList: true, characterData: true, subtree: true, attributes: true, attributeFilter: ['aria-selected'] });
|
|
38
|
+
panel.addEventListener('clay-tool-pane-dispose', function () { observer.disconnect(); }, { once: true });
|
|
39
|
+
refreshIcons();
|
|
40
|
+
}
|
|
41
|
+
function update() {
|
|
42
|
+
var label = titleFor(panel, name), title = header.querySelector('.tool-pane-title');
|
|
43
|
+
if (title.textContent !== label) { title.textContent = label; title.title = label; }
|
|
44
|
+
}
|
|
45
|
+
update();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
store.subscribe(function (state, previous) {
|
|
49
|
+
if (state.projectName === previous.projectName && state.projectsHubList === previous.projectsHubList && state.currentSlug === previous.currentSlug) return;
|
|
50
|
+
var panel = document.getElementById('terminal-container');
|
|
51
|
+
if (panel && panel.querySelector(':scope > .tool-pane-header')) renderToolPaneChrome(panel, 'terminal');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
function renderWorkbenchChrome(panel, name) {
|
|
55
|
+
panel.classList.add('workbench-pane-shell');
|
|
56
|
+
var header = panel.querySelector(':scope > header');
|
|
57
|
+
if (!header || header.dataset.toolPane === name) return;
|
|
58
|
+
header.dataset.toolPane = name;
|
|
59
|
+
header.classList.add('tool-pane-header', 'split-pane-header');
|
|
60
|
+
var actions = header.querySelector('[class*="window-actions"], :scope > div:last-child');
|
|
61
|
+
if (!actions) {
|
|
62
|
+
actions = document.createElement('div');
|
|
63
|
+
Array.from(header.querySelectorAll(':scope > button')).forEach(function (button) {
|
|
64
|
+
var label = button.getAttribute('aria-label') || '';
|
|
65
|
+
if (/^(Widen|Close)|fullscreen/i.test(label)) actions.appendChild(button);
|
|
66
|
+
});
|
|
67
|
+
header.appendChild(actions);
|
|
68
|
+
}
|
|
69
|
+
actions.classList.add('workbench-pane-actions');
|
|
70
|
+
var title = header.querySelector('strong, [class$="-title"]');
|
|
71
|
+
if (title) title.classList.add('tool-pane-title');
|
|
72
|
+
var space = document.createElement('span'); space.className = 'split-pane-drag-space';
|
|
73
|
+
space.title = 'Drag to rearrange panes'; space.setAttribute('aria-hidden', 'true');
|
|
74
|
+
header.insertBefore(space, actions);
|
|
75
|
+
var expanded = panel.classList.contains('panel-fullscreen');
|
|
76
|
+
var observer = new MutationObserver(function () {
|
|
77
|
+
var next = panel.classList.contains('panel-fullscreen');
|
|
78
|
+
if (next !== expanded) { expanded = next; recordToolExpansion(name, next); }
|
|
79
|
+
});
|
|
80
|
+
observer.observe(panel, { attributes: true, attributeFilter: ['class'] });
|
|
81
|
+
panel.addEventListener('clay-tool-pane-dispose', function () { observer.disconnect(); }, { once: true });
|
|
82
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { livePaneBinding, samePaneBinding } from './pane-binding.js';
|
|
4
|
+
import { insertWindow } from './tool-pane-tree.js';
|
|
5
|
+
import { resize } from './split-view-layout-client.js';
|
|
6
|
+
import { repaintToolPanes, saveToolPaneTree, toolPaneContext } from './tool-pane-layout.js';
|
|
7
|
+
|
|
8
|
+
function receiptCurrent(drag) {
|
|
9
|
+
var current = store.get('toolPaneWindowGeometry');
|
|
10
|
+
return current && current.key === toolPaneContext() && store.get('connected') && current.key === drag.receipt.key && current.socket === getWs() && current.socket === drag.receipt.socket &&
|
|
11
|
+
current.scope === drag.receipt.scope && current.scope === JSON.stringify([store.get('myUserId'), store.get('currentSlug'), store.get('currentProjectOwnerId'), store.get('projectSplitViewSocketGeneration')]) && current.revision === drag.receipt.revision &&
|
|
12
|
+
JSON.stringify(current.windows.map(function (entry) { return entry.id; }).sort()) === JSON.stringify(drag.windows.map(function (entry) { return entry.id; }).sort()) && JSON.stringify(current.base) === JSON.stringify(drag.receipt.base) &&
|
|
13
|
+
current.host.clientWidth === drag.width && current.host.clientHeight === drag.height &&
|
|
14
|
+
drag.windows.every(function (entry) {
|
|
15
|
+
if (!entry.element.isConnected || entry.element.classList.contains('hidden')) return false;
|
|
16
|
+
if (!entry.frame) return entry.element.dataset.toolWindowId === entry.id;
|
|
17
|
+
var live = entry.frame.clayPane && entry.frame.clayPane.identity();
|
|
18
|
+
return entry.frame.contentDocument === entry.document && entry.frame.contentWindow === entry.window &&
|
|
19
|
+
live && samePaneBinding(live.binding, entry.binding) && livePaneBinding(entry.frame);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function capture(event, source, divider) {
|
|
23
|
+
var receipt = store.get('toolPaneWindowGeometry'), preference = store.get('workbenchLayoutPreference') || {};
|
|
24
|
+
if (!receipt || (!store.get('projectSplitViewActiveId') && (!preference.ready || preference.loading || preference.saving)) || store.get('projectSplitViewDrag') || store.get('projectSplitViewPending')) return null;
|
|
25
|
+
var drag = { pointerId: event.pointerId, receipt: receipt, source: source, divider: divider, startX: event.clientX, startY: event.clientY,
|
|
26
|
+
width: receipt.host.clientWidth, height: receipt.host.clientHeight, tree: receipt.tree, moved: false, windows: receipt.windows.map(function (item) {
|
|
27
|
+
var live = item.frame && item.frame.clayPane && item.frame.clayPane.identity();
|
|
28
|
+
return Object.assign({}, item, { rect: item.element.getBoundingClientRect(), document: item.frame && item.frame.contentDocument,
|
|
29
|
+
window: item.frame && item.frame.contentWindow, binding: live && live.binding, pointerEvents: item.frame && item.frame.style.pointerEvents });
|
|
30
|
+
}) };
|
|
31
|
+
return receiptCurrent(drag) ? drag : null;
|
|
32
|
+
}
|
|
33
|
+
function begin(drag) {
|
|
34
|
+
drag.moved = true;
|
|
35
|
+
drag.windows.forEach(function (item) { if (item.frame) item.frame.style.pointerEvents = 'none'; });
|
|
36
|
+
if (drag.source) drag.source.element.classList.add('tool-pane-drag-source');
|
|
37
|
+
document.body.classList.add('tool-pane-dragging'); drag.receipt.host.classList.add('split-view-drag-preview');
|
|
38
|
+
try { document.documentElement.setPointerCapture(drag.pointerId); } catch (error) {}
|
|
39
|
+
}
|
|
40
|
+
function pointerDown(event) {
|
|
41
|
+
if (event.button !== 0 || event.isPrimary === false || store.get('toolPaneDrag')) return;
|
|
42
|
+
var header = event.target.closest && event.target.closest('.tool-pane-window > .tool-pane-header, #split-host > .split-pane > .split-pane-header');
|
|
43
|
+
if (!header || event.target.closest('button, input, a, .tool-pane-title, .tool-pane-kind, .split-pane-title, .split-view-runtime, .split-worker-runtime')) return;
|
|
44
|
+
var receipt = store.get('toolPaneWindowGeometry');
|
|
45
|
+
if (!receipt || !receipt.windows.some(function (item) { return !!item.tool; })) return;
|
|
46
|
+
var source = receipt.windows.find(function (item) { return item.element === header.parentNode; });
|
|
47
|
+
var drag = source && capture(event, source);
|
|
48
|
+
if (!drag) return;
|
|
49
|
+
store.set({ toolPaneDrag: drag }); event.preventDefault(); event.stopImmediatePropagation();
|
|
50
|
+
}
|
|
51
|
+
function preview(drag, tree) {
|
|
52
|
+
drag.tree = tree;
|
|
53
|
+
store.set({ toolPaneLayoutPreview: { key: drag.receipt.key, base: drag.receipt.base, tree: tree, socket: drag.receipt.socket } });
|
|
54
|
+
repaintToolPanes();
|
|
55
|
+
}
|
|
56
|
+
function move(event) {
|
|
57
|
+
var drag = store.get('toolPaneDrag');
|
|
58
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
59
|
+
if (!receiptCurrent(drag)) { finish(false); return; }
|
|
60
|
+
if (!drag.moved && Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) < 6) return;
|
|
61
|
+
if (!drag.moved) begin(drag);
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
if (drag.divider) {
|
|
64
|
+
var entry = drag.divider, horizontal = entry.axis === 'horizontal';
|
|
65
|
+
var delta = horizontal ? event.clientX - drag.startX : event.clientY - drag.startY;
|
|
66
|
+
var next = resize(drag.receipt.tree, entry.path, Math.max(.05, Math.min(.95, entry.ratio + delta / Math.max(1, entry.available))));
|
|
67
|
+
if (next) preview(drag, next);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
var target = drag.windows.find(function (entry) { var r = entry.rect; return event.clientX >= r.left && event.clientX <= r.right && event.clientY >= r.top && event.clientY <= r.bottom; });
|
|
71
|
+
var nextTree = drag.receipt.tree;
|
|
72
|
+
if (target && target.id !== drag.source.id) {
|
|
73
|
+
var r = target.rect, x = (event.clientX - r.left) / r.width, y = (event.clientY - r.top) / r.height;
|
|
74
|
+
var edge = Math.min(x, 1 - x, y, 1 - y), side = 'center';
|
|
75
|
+
if (edge < .25) side = edge === x ? 'left' : edge === 1 - x ? 'right' : edge === y ? 'top' : 'bottom';
|
|
76
|
+
nextTree = insertWindow(drag.receipt.tree, drag.source.id, target.id, side);
|
|
77
|
+
}
|
|
78
|
+
preview(drag, nextTree);
|
|
79
|
+
var ghost = document.getElementById('tool-pane-drag-ghost');
|
|
80
|
+
if (!ghost) { ghost = document.createElement('div'); ghost.id = 'tool-pane-drag-ghost'; ghost.className = 'split-view-drag-ghost'; document.body.appendChild(ghost); }
|
|
81
|
+
ghost.textContent = drag.source.element.querySelector('.tool-pane-title, .split-pane-title').textContent;
|
|
82
|
+
ghost.style.left = event.clientX + 12 + 'px'; ghost.style.top = event.clientY + 12 + 'px'; ghost.hidden = false;
|
|
83
|
+
}
|
|
84
|
+
async function finish(commit) {
|
|
85
|
+
var drag = store.get('toolPaneDrag');
|
|
86
|
+
if (!drag) return;
|
|
87
|
+
var allowed = commit && drag.moved && receiptCurrent(drag) && JSON.stringify(drag.tree) !== JSON.stringify(drag.receipt.tree);
|
|
88
|
+
store.set({ toolPaneDrag: null });
|
|
89
|
+
drag.windows.forEach(function (entry) { if (entry.frame && entry.frame.isConnected) entry.frame.style.pointerEvents = entry.pointerEvents; });
|
|
90
|
+
if (drag.source) drag.source.element.classList.remove('tool-pane-drag-source');
|
|
91
|
+
document.body.classList.remove('tool-pane-dragging');
|
|
92
|
+
var ghost = document.getElementById('tool-pane-drag-ghost'); if (ghost) ghost.hidden = true;
|
|
93
|
+
try { if (document.documentElement.hasPointerCapture(drag.pointerId)) document.documentElement.releasePointerCapture(drag.pointerId); } catch (error) {}
|
|
94
|
+
var saved = allowed ? await saveToolPaneTree(drag.receipt, drag.tree) : false;
|
|
95
|
+
if (!store.get('toolPaneDrag')) { store.set({ toolPaneLayoutPreview: null }); repaintToolPanes(); }
|
|
96
|
+
setTimeout(function () { if (!store.get('toolPaneDrag')) drag.receipt.host.classList.remove('split-view-drag-preview'); }, 320);
|
|
97
|
+
return saved;
|
|
98
|
+
}
|
|
99
|
+
function up(event) {
|
|
100
|
+
var drag = store.get('toolPaneDrag');
|
|
101
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
102
|
+
if (drag.moved) move(event);
|
|
103
|
+
finish(true);
|
|
104
|
+
}
|
|
105
|
+
export function bindToolPaneDivider(divider, entry, tree) {
|
|
106
|
+
divider.addEventListener('pointerdown', function (event) {
|
|
107
|
+
if (event.button !== 0 || store.get('toolPaneDrag')) return;
|
|
108
|
+
var drag = capture(event, null, entry);
|
|
109
|
+
if (!drag) return;
|
|
110
|
+
store.set({ toolPaneDrag: drag }); begin(drag); event.preventDefault(); event.stopImmediatePropagation();
|
|
111
|
+
});
|
|
112
|
+
divider.addEventListener('keydown', function (event) {
|
|
113
|
+
var direction = ['ArrowLeft', 'ArrowUp'].indexOf(event.key) >= 0 ? -1 : ['ArrowRight', 'ArrowDown'].indexOf(event.key) >= 0 ? 1 : 0;
|
|
114
|
+
if (!direction) return;
|
|
115
|
+
var receipt = store.get('toolPaneWindowGeometry'), next = resize(tree, entry.path, Math.max(.05, Math.min(.95, entry.ratio + direction * .03)));
|
|
116
|
+
if (receipt && next) { event.preventDefault(); saveToolPaneTree(receipt, next); }
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
export function initToolPaneDrag() {
|
|
120
|
+
if (store.get('toolPaneDragInitialized')) return;
|
|
121
|
+
store.set({ toolPaneDragInitialized: true });
|
|
122
|
+
document.addEventListener('pointerdown', pointerDown, true);
|
|
123
|
+
document.addEventListener('pointermove', move, true); document.addEventListener('pointerup', up, true);
|
|
124
|
+
document.addEventListener('pointercancel', function () { finish(false); }, true);
|
|
125
|
+
document.addEventListener('keydown', function (event) { if (event.key === 'Escape' && store.get('toolPaneDrag')) { event.preventDefault(); finish(false); } }, true);
|
|
126
|
+
window.addEventListener('blur', function () { finish(false); });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
store.subscribe(function (state, previous) {
|
|
130
|
+
if (state.toolPaneDrag && (state.myUserId !== previous.myUserId || state.currentSlug !== previous.currentSlug ||
|
|
131
|
+
state.currentProjectOwnerId !== previous.currentProjectOwnerId || state.splitPanes !== previous.splitPanes ||
|
|
132
|
+
state.connected !== previous.connected || state.projectSplitViewActiveId !== previous.projectSplitViewActiveId ||
|
|
133
|
+
state.projectSplitViewSocketGeneration !== previous.projectSplitViewSocketGeneration)) finish(false);
|
|
134
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { boundLegacyCoordinates } from './view-legacy-coordinates.js';
|
|
2
|
+
import { pairDisplayTree, pairBase } from './view-pair-presentation.js';
|
|
3
|
+
import { activeSavedView, displayTree, fromDisplayTree, copy, workspaceKey } from './view-workspace-state.js';
|
|
4
|
+
import { persistWorkspace } from './view-workspace-controller.js';
|
|
5
|
+
import { store } from './store.js';
|
|
6
|
+
import { getWs } from './ws-ref.js';
|
|
7
|
+
import { TOOL_PANES, toolWindowId, workspaceWindowTree } from './tool-pane-tree.js';
|
|
8
|
+
import { renderPresentationSplitView } from './split-view-presentations.js';
|
|
9
|
+
import { ensureWorkbenchLayoutPreference } from './workbench-layout-preference.js';
|
|
10
|
+
import { showToast } from './utils.js';
|
|
11
|
+
import { initToolPaneDrag, bindToolPaneDivider } from './tool-pane-drag.js';
|
|
12
|
+
|
|
13
|
+
export function toolPaneContext() {
|
|
14
|
+
var state = store.snap(), view = (state.projectSplitViews || []).find(function (item) { return item.id === state.projectSplitViewActiveId; });
|
|
15
|
+
var split = state.splitPanes;
|
|
16
|
+
var origin = view ? view.id : split && split.panes ? 'legacy-' + (split.groupId || split.panes.map(function (pane) { return pane.sessionId; }).join('-')) : state.projectSplitViewSingletonOrigin;
|
|
17
|
+
return origin && state.currentSlug && JSON.stringify([state.currentSlug, state.currentProjectOwnerId || null, origin]);
|
|
18
|
+
}
|
|
19
|
+
export function dockedToolPanes() {
|
|
20
|
+
if (window.innerWidth < 1024 || store.get('paneMode')) return [];
|
|
21
|
+
return (store.get('rightWorkbenchDockedOwners') || []).filter(function (name) {
|
|
22
|
+
var definition = TOOL_PANES[name], panel = definition && document.getElementById(definition.id);
|
|
23
|
+
return panel && !panel.classList.contains('hidden') && !panel.classList.contains('panel-fullscreen');
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function toolPaneLayoutTree(view, base) {
|
|
27
|
+
initToolPaneDrag();
|
|
28
|
+
var owners = dockedToolPanes(), key = toolPaneContext(), preference = store.get('workbenchLayoutPreference') || {};
|
|
29
|
+
var draft = store.get('toolPaneLayoutPreview'), saved = preference.layouts && preference.layouts[key];
|
|
30
|
+
if (view && view.workspace) {
|
|
31
|
+
var legacy = store.get('savedWorkspaceLegacyLayout');
|
|
32
|
+
saved = legacy && legacy.key === workspaceKey(view) ? boundLegacyCoordinates(legacy.record, view.workspace) : { base: view.workspace.linkedPair ? pairBase(view.workspace.linkedPair, view.tree) : view.tree, tree: pairDisplayTree(view.workspace) };
|
|
33
|
+
}
|
|
34
|
+
if (draft && draft.key === key && JSON.stringify(draft.base) === JSON.stringify(base) && draft.socket === getWs()) return draft.tree;
|
|
35
|
+
return owners.length || saved ? workspaceWindowTree(base, saved, owners) : base;
|
|
36
|
+
}
|
|
37
|
+
export function paintToolPaneWindows(host, view, base, tree, geometry, inset) {
|
|
38
|
+
var owners = dockedToolPanes(), key = toolPaneContext(), main = document.getElementById('main-panels');
|
|
39
|
+
if (!main) return;
|
|
40
|
+
main.classList.toggle('view-session-focused', !!store.get('projectSplitViewFocusedOrigin'));
|
|
41
|
+
var hostRect = host.getBoundingClientRect(), mainRect = main.getBoundingClientRect();
|
|
42
|
+
var windows = Array.from(host.querySelectorAll(':scope > .split-pane:not(.split-pane-cached)')).map(function (pane) {
|
|
43
|
+
var frame = pane.querySelector('.split-pane-frame'); return { id: frame && frame.dataset.sessionOriginId, element: pane, frame: frame };
|
|
44
|
+
});
|
|
45
|
+
Object.keys(TOOL_PANES).forEach(function (name) {
|
|
46
|
+
var panel = document.getElementById(TOOL_PANES[name].id), rect = geometry.panes[toolWindowId(name)];
|
|
47
|
+
if (!panel) return;
|
|
48
|
+
if (!panel.dataset.toolPaneFullscreenObserved) {
|
|
49
|
+
panel.dataset.toolPaneFullscreenObserved = 'true';
|
|
50
|
+
var observer = new MutationObserver(function (records) {
|
|
51
|
+
if (records.some(function (record) { return (String(record.oldValue || '').split(/\s+/).indexOf('panel-fullscreen') >= 0) !== panel.classList.contains('panel-fullscreen'); })) requestAnimationFrame(repaintToolPanes);
|
|
52
|
+
});
|
|
53
|
+
observer.observe(panel, { attributes: true, attributeOldValue: true, attributeFilter: ['class'] });
|
|
54
|
+
panel.addEventListener('clay-tool-pane-dispose', function () { observer.disconnect(); }, { once: true });
|
|
55
|
+
}
|
|
56
|
+
var mounted = owners.indexOf(name) >= 0 && !!rect;
|
|
57
|
+
var wasMounted = panel.classList.contains('tool-pane-window');
|
|
58
|
+
panel.classList.toggle('tool-pane-window', mounted);
|
|
59
|
+
if (!mounted) {
|
|
60
|
+
if (wasMounted) ['left', 'top', 'width', 'height', 'right', 'maxHeight'].forEach(function (field) { panel.style[field] = ''; });
|
|
61
|
+
delete panel.dataset.toolWindowId; return;
|
|
62
|
+
}
|
|
63
|
+
panel.dataset.toolWindowId = toolWindowId(name);
|
|
64
|
+
panel.style.left = hostRect.left - mainRect.left + inset.left + rect.x + 'px';
|
|
65
|
+
panel.style.top = hostRect.top - mainRect.top + inset.top + rect.y + 'px';
|
|
66
|
+
panel.style.width = rect.width + 'px'; panel.style.height = rect.height + 'px'; panel.style.right = 'auto'; panel.style.maxHeight = 'none';
|
|
67
|
+
windows.push({ id: toolWindowId(name), element: panel, tool: name });
|
|
68
|
+
});
|
|
69
|
+
main.classList.toggle('has-tool-pane-windows', owners.length > 0);
|
|
70
|
+
if (Math.abs(host.clientWidth - hostRect.width) > 1) requestAnimationFrame(repaintToolPanes);
|
|
71
|
+
store.set({ toolPaneWindowGeometry: { key: key, base: base, tree: tree, view: view, windows: windows,
|
|
72
|
+
host: host, socket: getWs(), revision: view && view.workspace ? view.revision : (store.get('workbenchLayoutPreference') || {}).revision,
|
|
73
|
+
scope: JSON.stringify([store.get('myUserId'), store.get('currentSlug'), store.get('currentProjectOwnerId'), store.get('projectSplitViewSocketGeneration')]) } });
|
|
74
|
+
}
|
|
75
|
+
export function bindWorkspaceDivider(divider, entry, tree) { bindToolPaneDivider(divider, entry, tree); }
|
|
76
|
+
export function repaintToolPanes() { renderPresentationSplitView(); }
|
|
77
|
+
export async function saveToolPaneTree(receipt, tree) {
|
|
78
|
+
var live = store.get('toolPaneWindowGeometry');
|
|
79
|
+
if (!live || live.key !== toolPaneContext() || live.key !== receipt.key || live.socket !== receipt.socket || live.scope !== receipt.scope || live.revision !== receipt.revision || !store.get('connected')) return false;
|
|
80
|
+
var view = activeSavedView();
|
|
81
|
+
if (view) { var value = copy(view.workspace); value.tree = fromDisplayTree(value, tree); if (value.linkedPair) value.linkedPair.tree = copy(tree); return persistWorkspace(value); }
|
|
82
|
+
showToast('Wait for the saved View before changing pane positions.', 'warn');
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
store.subscribe(function (state, previous) {
|
|
86
|
+
if (state.paneMode) return;
|
|
87
|
+
var changed = state.rightWorkbenchDockedOwners !== previous.rightWorkbenchDockedOwners || state.workbenchLayoutPreference !== previous.workbenchLayoutPreference ||
|
|
88
|
+
state.toolPaneLayoutPreview !== previous.toolPaneLayoutPreview;
|
|
89
|
+
if (!changed) return;
|
|
90
|
+
if (!state.toolPaneRepaintQueued) {
|
|
91
|
+
store.set({ toolPaneRepaintQueued: true });
|
|
92
|
+
requestAnimationFrame(function () { store.set({ toolPaneRepaintQueued: false }); renderPresentationSplitView(); });
|
|
93
|
+
}
|
|
94
|
+
if ((state.rightWorkbenchDockedOwners || []).some(function (name) { return !!TOOL_PANES[name]; }) && !(state.workbenchLayoutPreference || {}).ready && !(state.workbenchLayoutPreference || {}).loading && !(state.workbenchLayoutPreference || {}).saving && !(state.workbenchLayoutPreference || {}).error) ensureWorkbenchLayoutPreference();
|
|
95
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ensureArrangeTrigger } from './split-view-presentation-renderer.js';
|
|
2
|
+
import { pairBase } from './view-pair-presentation.js';
|
|
3
|
+
import { activeSavedView } from './view-workspace-state.js';
|
|
4
|
+
// Legacy Driver/Worker pairs use the same display-only window geometry.
|
|
5
|
+
import { store } from './store.js';
|
|
6
|
+
import { rectangles } from './split-view-layout-client.js';
|
|
7
|
+
import { toolPaneLayoutTree, paintToolPaneWindows, bindWorkspaceDivider, dockedToolPanes } from './tool-pane-layout.js';
|
|
8
|
+
|
|
9
|
+
export function clearToolPaneWindows() {
|
|
10
|
+
document.querySelectorAll('.tool-pane-window').forEach(function (panel) {
|
|
11
|
+
panel.classList.remove('tool-pane-window'); delete panel.dataset.toolWindowId;
|
|
12
|
+
['left', 'top', 'width', 'height', 'right', 'maxHeight'].forEach(function (field) { panel.style[field] = ''; });
|
|
13
|
+
});
|
|
14
|
+
var main = document.getElementById('main-panels');
|
|
15
|
+
if (main) main.classList.remove('has-tool-pane-windows');
|
|
16
|
+
store.set({ toolPaneWindowGeometry: null });
|
|
17
|
+
}
|
|
18
|
+
export function paintLegacyToolPanes(host) {
|
|
19
|
+
var split = store.get('splitPanes');
|
|
20
|
+
if (!host || !split || !split.panes || split.panes.length < 2) return false;
|
|
21
|
+
var panes = Array.from(host.querySelectorAll(':scope > .split-pane:not(.split-pane-cached)'));
|
|
22
|
+
var ids = panes.map(function (pane) {
|
|
23
|
+
var frame = pane.querySelector('.split-pane-frame');
|
|
24
|
+
if (frame && !frame.dataset.toolPaneGeometryReadyBound) {
|
|
25
|
+
frame.dataset.toolPaneGeometryReadyBound = 'true';
|
|
26
|
+
frame.addEventListener('clay-pane-ready', function () { queueMicrotask(function () { if (frame.isConnected) paintLegacyToolPanes(host); }); });
|
|
27
|
+
}
|
|
28
|
+
return frame && frame.dataset.sessionOriginId;
|
|
29
|
+
});
|
|
30
|
+
if (ids.length < split.panes.length || ids.some(function (id) { return !id; }) || new Set(ids).size !== ids.length) return false;
|
|
31
|
+
var base = { id: ids[0] };
|
|
32
|
+
ids.slice(1).forEach(function (id) { base = { axis: 'horizontal', ratio: .5, left: base, right: { id: id } }; });
|
|
33
|
+
var view = activeSavedView() || { id: 'legacy-' + (split.groupId || ids.join('-')), activeSessionOriginId: ids[0] };
|
|
34
|
+
if (view.workspace && view.workspace.linkedPair) base = pairBase(view.workspace.linkedPair, view.tree);
|
|
35
|
+
host.classList.add('split-view-canvas'); host.dataset.positioned = 'true'; delete host.dataset.singlePane;
|
|
36
|
+
var style = getComputedStyle(host), left = parseFloat(style.paddingLeft) || 0, top = parseFloat(style.paddingTop) || 0;
|
|
37
|
+
var width = host.clientWidth - left - (parseFloat(style.paddingRight) || 0), height = host.clientHeight - top - (parseFloat(style.paddingBottom) || 0);
|
|
38
|
+
var tree = toolPaneLayoutTree(view, base), geometry = rectangles(tree, Math.max(0, width), Math.max(0, height), 7);
|
|
39
|
+
paintToolPaneWindows(host, view, base, tree, geometry, { left: left, top: top });
|
|
40
|
+
var focused = store.get('projectSplitViewFocusedOrigin');
|
|
41
|
+
panes.forEach(function (pane, index) {
|
|
42
|
+
var rect = geometry.panes[ids[index]];
|
|
43
|
+
pane.hidden = !!focused && ids[index] !== focused; pane.style.display = pane.hidden ? 'none' : '';
|
|
44
|
+
if (focused === ids[index]) rect = { x: 0, y: 0, width: width, height: height };
|
|
45
|
+
if (!rect) return;
|
|
46
|
+
if (view.sessions && view.sessions.some(function (s) { return s.sessionOriginId === ids[index]; })) ensureArrangeTrigger(pane.querySelector('.split-pane-header'));
|
|
47
|
+
pane.style.left = left + rect.x + 'px'; pane.style.top = top + rect.y + 'px';
|
|
48
|
+
pane.style.width = rect.width + 'px'; pane.style.height = rect.height + 'px';
|
|
49
|
+
});
|
|
50
|
+
host.querySelectorAll(':scope > .split-view-divider').forEach(function (divider) { divider.remove(); });
|
|
51
|
+
(focused ? [] : geometry.dividers).forEach(function (entry) {
|
|
52
|
+
var divider = document.createElement('div'); divider.className = 'split-view-divider'; divider.dataset.axis = entry.axis;
|
|
53
|
+
divider.setAttribute('role', 'separator'); divider.setAttribute('tabindex', '0'); divider.setAttribute('aria-label', 'Resize pane windows');
|
|
54
|
+
divider.setAttribute('aria-orientation', entry.axis === 'horizontal' ? 'vertical' : 'horizontal');
|
|
55
|
+
divider.style.left = left + entry.rect.x + 'px'; divider.style.top = top + entry.rect.y + 'px';
|
|
56
|
+
divider.style.width = entry.rect.width + 'px'; divider.style.height = entry.rect.height + 'px';
|
|
57
|
+
bindWorkspaceDivider(divider, entry, tree); host.appendChild(divider);
|
|
58
|
+
});
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { WORKBENCH_PANELS } from './workbench-panels.js';
|
|
2
|
+
import { listIds, remove, pairTree, swap } from './split-view-layout-client.js';
|
|
3
|
+
|
|
4
|
+
export var TOOL_PANES = { terminal: { id: 'terminal-container', label: 'Terminal', icon: 'terminal' },
|
|
5
|
+
browser: { id: 'shared-browser-panel', label: 'Browser', icon: 'globe' }, 'file-viewer': { id: 'file-viewer', label: 'Files', icon: 'file-text' } };
|
|
6
|
+
Object.assign(TOOL_PANES, WORKBENCH_PANELS);
|
|
7
|
+
export function toolWindowId(name) { return 'tool-' + name; }
|
|
8
|
+
export function isToolWindow(id) { return /^tool-file-[A-Za-z0-9_-]+$/.test(id) || Object.keys(TOOL_PANES).some(function (name) { return toolWindowId(name) === id; }); }
|
|
9
|
+
export function insertWindow(tree, source, target, side, ratio) {
|
|
10
|
+
if (source === target) return tree;
|
|
11
|
+
if (side === 'center' && isToolWindow(source) && isToolWindow(target)) return swap(tree, source, target).tree;
|
|
12
|
+
side = side === 'center' ? 'right' : side;
|
|
13
|
+
var base = remove(tree, source), found = false;
|
|
14
|
+
function walk(node) {
|
|
15
|
+
if (!node) return node;
|
|
16
|
+
if (node.id) {
|
|
17
|
+
if (node.id !== target) return node;
|
|
18
|
+
found = true;
|
|
19
|
+
var result = pairTree(target, source, side);
|
|
20
|
+
result.ratio = ratio || .5;
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
return { axis: node.axis, ratio: node.ratio, left: walk(node.left), right: walk(node.right) };
|
|
24
|
+
}
|
|
25
|
+
var next = walk(base);
|
|
26
|
+
return found && listIds(next).length <= 24 ? next : tree;
|
|
27
|
+
}
|
|
28
|
+
function stripUnknown(tree, allowed) {
|
|
29
|
+
var result = tree;
|
|
30
|
+
listIds(tree).forEach(function (id) { if (allowed.indexOf(id) < 0) result = remove(result, id); });
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
function placements(tree, output) {
|
|
34
|
+
if (!tree || tree.id) return;
|
|
35
|
+
var left = listIds(tree.left), right = listIds(tree.right);
|
|
36
|
+
if (tree.left.id && isToolWindow(tree.left.id)) output.push({ id: tree.left.id, target: right[0], side: tree.axis === 'horizontal' ? 'left' : 'top', ratio: tree.ratio });
|
|
37
|
+
else if (tree.right.id && isToolWindow(tree.right.id)) output.push({ id: tree.right.id, target: left[0], side: tree.axis === 'horizontal' ? 'right' : 'bottom', ratio: tree.ratio });
|
|
38
|
+
placements(tree.left, output); placements(tree.right, output);
|
|
39
|
+
}
|
|
40
|
+
export function workspaceWindowTree(base, saved, owners) {
|
|
41
|
+
var sessions = listIds(base), tools = owners.map(toolWindowId), allowed = sessions.concat(tools), tree = base;
|
|
42
|
+
if (saved && saved.tree && JSON.stringify(saved.base) === JSON.stringify(base)) tree = stripUnknown(saved.tree, allowed);
|
|
43
|
+
else if (saved && saved.tree) {
|
|
44
|
+
var attached = []; placements(saved.tree, attached);
|
|
45
|
+
attached.reverse().forEach(function (item) { if (tools.indexOf(item.id) >= 0 && listIds(tree).indexOf(item.target) >= 0) tree = insertWindow(tree, item.id, item.target, item.side, item.ratio); });
|
|
46
|
+
}
|
|
47
|
+
tools.forEach(function (id) {
|
|
48
|
+
if (listIds(tree).indexOf(id) < 0) tree = tree ? { axis: 'horizontal', ratio: .66, left: tree, right: { id: id } } : { id: id };
|
|
49
|
+
});
|
|
50
|
+
return tree;
|
|
51
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { copy, mapWorkspaceTree, paneOwner } from './view-workspace-state.js';
|
|
2
|
+
import { listIds } from './split-view-layout-client.js';
|
|
3
|
+
// Coordinates never create resources. Resolve the old singleton Files slot only
|
|
4
|
+
// after a real file instance exists; leave other delayed slots intact.
|
|
5
|
+
export function boundLegacyCoordinates(record, value) {
|
|
6
|
+
var next = copy(record), mapping = {};
|
|
7
|
+
listIds(record.tree).forEach(function (id) { mapping[id] = id; });
|
|
8
|
+
var files = value.panes.filter(function (pane) { return pane.kind === 'file' && pane.open && pane.placement === 'docked'; });
|
|
9
|
+
if (files.length === 1) mapping['tool-file-viewer'] = 'tool-' + paneOwner(files[0]);
|
|
10
|
+
next.tree = mapWorkspaceTree(next.tree, mapping);
|
|
11
|
+
return next;
|
|
12
|
+
}
|
|
13
|
+
export function legacyCoordinatesReady(record, value) {
|
|
14
|
+
var ids = value.panes.filter(function (p) { return p.open && p.placement === 'docked'; }).map(function (p) { return 'tool-' + paneOwner(p); });
|
|
15
|
+
return listIds(boundLegacyCoordinates(record, value).tree).filter(function (id) { return id.indexOf('tool-') === 0; }).every(function (id) { return ids.indexOf(id) >= 0; });
|
|
16
|
+
}
|