update
BIN
.playwright-mcp/both-actionbars-test.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
.playwright-mcp/editor-actionbar-test.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
.playwright-mcp/editor-actionbar-visible.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
.playwright-mcp/editor-actionbar-working.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
.playwright-mcp/terminal-actionbar-resize-issue.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
.playwright-mcp/terminal-resize-fix-verification.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
.playwright-mcp/terminal-with-actionbar-fix.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
.playwright-mcp/workspace-actionbar-layout.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
.playwright-mcp/workspace-file-open.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
@@ -16,6 +16,8 @@ import { themeDefaultStyles } from '../../00theme.js';
|
|||||||
import type { IExecutionEnvironment } from '../../00group-runtime/index.js';
|
import type { IExecutionEnvironment } from '../../00group-runtime/index.js';
|
||||||
import { WebContainerEnvironment } from '../../00group-runtime/index.js';
|
import { WebContainerEnvironment } from '../../00group-runtime/index.js';
|
||||||
import '../../dees-icon/dees-icon.js';
|
import '../../dees-icon/dees-icon.js';
|
||||||
|
import '../../dees-actionbar/dees-actionbar.js';
|
||||||
|
import type { DeesActionbar } from '../../dees-actionbar/dees-actionbar.js';
|
||||||
import { TerminalTabManager } from './terminal-tab-manager.js';
|
import { TerminalTabManager } from './terminal-tab-manager.js';
|
||||||
import type {
|
import type {
|
||||||
ITerminalTab,
|
ITerminalTab,
|
||||||
@@ -79,6 +81,9 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
private terminalThemeSubscription: any = null;
|
private terminalThemeSubscription: any = null;
|
||||||
private isBright: boolean = false;
|
private isBright: boolean = false;
|
||||||
|
|
||||||
|
// Actionbar reference for terminal-context notifications
|
||||||
|
private terminalActionbar: DeesActionbar | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Promise that resolves when the environment is ready.
|
* Promise that resolves when the environment is ready.
|
||||||
* @deprecated Use executionEnvironment directly
|
* @deprecated Use executionEnvironment directly
|
||||||
@@ -120,17 +125,21 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
|
|
||||||
.terminal-content {
|
.terminal-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: ${cssManager.bdTheme('#ffffff', '#000000')};
|
background: ${cssManager.bdTheme('#ffffff', '#000000')};
|
||||||
}
|
}
|
||||||
|
|
||||||
#active-terminal-container {
|
#active-terminal-container {
|
||||||
position: absolute;
|
flex: 1;
|
||||||
top: 20px;
|
position: relative;
|
||||||
left: 20px;
|
min-height: 0;
|
||||||
right: 20px;
|
margin: 20px;
|
||||||
bottom: 20px;
|
}
|
||||||
|
|
||||||
|
.terminal-content dees-actionbar {
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tab bar on the right side */
|
/* Tab bar on the right side */
|
||||||
@@ -426,6 +435,7 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
<span>No terminal open</span>
|
<span>No terminal open</span>
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
|
<dees-actionbar></dees-actionbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Vertical tab bar on the right -->
|
<!-- Vertical tab bar on the right -->
|
||||||
@@ -491,11 +501,15 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
|
|
||||||
async connectedCallback(): Promise<void> {
|
async connectedCallback(): Promise<void> {
|
||||||
await super.connectedCallback();
|
await super.connectedCallback();
|
||||||
this.resizeObserver.observe(this);
|
// ResizeObserver is set up in attachTerminalToContainer when the container exists
|
||||||
}
|
}
|
||||||
|
|
||||||
async disconnectedCallback(): Promise<void> {
|
async disconnectedCallback(): Promise<void> {
|
||||||
this.resizeObserver.unobserve(this);
|
// Unobserve the terminal container
|
||||||
|
const container = this.shadowRoot?.getElementById('active-terminal-container');
|
||||||
|
if (container) {
|
||||||
|
this.resizeObserver.unobserve(container);
|
||||||
|
}
|
||||||
if (this.terminalThemeSubscription) {
|
if (this.terminalThemeSubscription) {
|
||||||
this.terminalThemeSubscription.unsubscribe();
|
this.terminalThemeSubscription.unsubscribe();
|
||||||
this.terminalThemeSubscription = null;
|
this.terminalThemeSubscription = null;
|
||||||
@@ -558,6 +572,10 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
const container = this.shadowRoot?.getElementById('active-terminal-container');
|
const container = this.shadowRoot?.getElementById('active-terminal-container');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
|
// Observe container for resize (handles actionbar appearing/disappearing)
|
||||||
|
// ResizeObserver.observe() is idempotent - safe to call multiple times
|
||||||
|
this.resizeObserver.observe(container);
|
||||||
|
|
||||||
// Clear container
|
// Clear container
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
@@ -656,6 +674,36 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
detail: { tabId, exitCode },
|
detail: { tabId, exitCode },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Show actionbar to offer closing the tab (only if tab is closeable)
|
||||||
|
if (tab.closeable) {
|
||||||
|
this.showExitedTabActionbar(tabId, tab.label, exitCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show actionbar offering to close an exited tab
|
||||||
|
*/
|
||||||
|
private async showExitedTabActionbar(tabId: string, tabLabel: string, exitCode: number): Promise<void> {
|
||||||
|
const isSuccess = exitCode === 0;
|
||||||
|
const result = await this.showActionbar({
|
||||||
|
message: isSuccess
|
||||||
|
? `"${tabLabel}" completed. Close tab?`
|
||||||
|
: `"${tabLabel}" exited (code ${exitCode}). Close tab?`,
|
||||||
|
type: isSuccess ? 'info' : 'warning',
|
||||||
|
icon: isSuccess ? 'lucide:checkCircle' : 'lucide:alertTriangle',
|
||||||
|
actions: [
|
||||||
|
{ id: 'close', label: 'Close Tab', primary: true },
|
||||||
|
{ id: 'keep', label: 'Keep Open' },
|
||||||
|
],
|
||||||
|
timeout: { duration: 10000, defaultActionId: 'close' },
|
||||||
|
dismissible: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close tab if user clicked "Close Tab" or timeout triggered auto-close
|
||||||
|
if (result.actionId === 'close') {
|
||||||
|
this.closeTab(tabId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Public API ==========
|
// ========== Public API ==========
|
||||||
@@ -816,6 +864,19 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an actionbar notification in the terminal panel context.
|
||||||
|
* Use this for terminal-related decisions (e.g., retry failed process, kill process, etc.)
|
||||||
|
*/
|
||||||
|
public async showActionbar(
|
||||||
|
options: Parameters<DeesActionbar['show']>[0]
|
||||||
|
): Promise<ReturnType<DeesActionbar['show']>> {
|
||||||
|
if (!this.terminalActionbar) {
|
||||||
|
this.terminalActionbar = this.shadowRoot?.querySelector('dees-actionbar') as DeesActionbar;
|
||||||
|
}
|
||||||
|
return this.terminalActionbar?.show(options);
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Utility Methods ==========
|
// ========== Utility Methods ==========
|
||||||
|
|
||||||
public async waitForPrompt(term: Terminal, prompt: string): Promise<void> {
|
public async waitForPrompt(term: Terminal, prompt: string): Promise<void> {
|
||||||
|
|||||||
@@ -957,6 +957,7 @@ testSmartPromise();
|
|||||||
></dees-workspace-monaco>
|
></dees-workspace-monaco>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
|
<dees-actionbar></dees-actionbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Horizontal resize handle for terminal -->
|
<!-- Horizontal resize handle for terminal -->
|
||||||
@@ -1021,9 +1022,6 @@ testSmartPromise();
|
|||||||
.executionEnvironment=${this.executionEnvironment}
|
.executionEnvironment=${this.executionEnvironment}
|
||||||
@run-process=${this.handleRunProcess}
|
@run-process=${this.handleRunProcess}
|
||||||
></dees-workspace-bottombar>
|
></dees-workspace-bottombar>
|
||||||
|
|
||||||
<!-- Action Bar for notifications -->
|
|
||||||
<dees-actionbar></dees-actionbar>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -1056,9 +1054,6 @@ testSmartPromise();
|
|||||||
this.currentFileTreeWidth = this.fileTreeWidth;
|
this.currentFileTreeWidth = this.fileTreeWidth;
|
||||||
this.currentTerminalHeight = this.terminalHeight;
|
this.currentTerminalHeight = this.terminalHeight;
|
||||||
|
|
||||||
// Get actionbar reference for file change notifications
|
|
||||||
this.actionbarElement = this.shadowRoot?.querySelector('dees-actionbar') as DeesActionbar;
|
|
||||||
|
|
||||||
if (this.executionEnvironment) {
|
if (this.executionEnvironment) {
|
||||||
await this.initializeWorkspace();
|
await this.initializeWorkspace();
|
||||||
}
|
}
|
||||||
@@ -1068,6 +1063,11 @@ testSmartPromise();
|
|||||||
if (changedProperties.has('executionEnvironment') && this.executionEnvironment) {
|
if (changedProperties.has('executionEnvironment') && this.executionEnvironment) {
|
||||||
await this.initializeWorkspace();
|
await this.initializeWorkspace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Capture actionbar reference when it becomes available (after initialization completes)
|
||||||
|
if (!this.actionbarElement) {
|
||||||
|
this.actionbarElement = this.shadowRoot?.querySelector('.editor-panel dees-actionbar') as DeesActionbar;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initializeWorkspace() {
|
private async initializeWorkspace() {
|
||||||
|
|||||||
@@ -130,12 +130,12 @@ export class DeesActionbar extends DeesElement {
|
|||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 0;
|
max-height: 0;
|
||||||
transition: height 0.2s ease-out;
|
transition: max-height 0.2s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(.visible) {
|
:host(.visible) {
|
||||||
height: auto;
|
max-height: 100px; /* Enough for actionbar content */
|
||||||
}
|
}
|
||||||
|
|
||||||
.actionbar-item {
|
.actionbar-item {
|
||||||
|
|||||||