diff --git a/apps/remix-ide/src/app/components/right-side-panel.tsx b/apps/remix-ide/src/app/components/right-side-panel.tsx index 66eef3460a6..04ae3fb6f44 100644 --- a/apps/remix-ide/src/app/components/right-side-panel.tsx +++ b/apps/remix-ide/src/app/components/right-side-panel.tsx @@ -25,9 +25,13 @@ export class RightSidePanel extends AbstractPanel { highlightStamp: number hiddenPlugin: any isHidden: boolean + isMaximized: boolean + maximizedState: { leftPanelHidden: boolean, terminalPanelHidden: boolean } constructor() { super(rightSidePanel) + this.isMaximized = false + this.maximizedState = { leftPanelHidden: false, terminalPanelHidden: false } } onActivation() { @@ -40,6 +44,27 @@ export class RightSidePanel extends AbstractPanel { } }) + // Listen for terminal panel being shown - auto-restore right panel if maximized + this.on('terminal', 'terminalPanelShown', () => { + if (this.isMaximized) { + this.maximizePanel() // This will toggle and restore the panel + } + }) + + // Listen for file changes - auto-restore right panel if maximized when main panel is used + this.on('fileManager', 'currentFileChanged', () => { + if (this.isMaximized) { + this.maximizePanel() // This will toggle and restore the panel + } + }) + + // Listen for tab/app switches - auto-restore right panel if maximized (includes home tab, file tabs, etc.) + this.on('tabs', 'switchApp', () => { + if (this.isMaximized) { + this.maximizePanel() // This will toggle and restore the panel + } + }) + // Initialize isHidden state from panelStates in localStorage const panelStatesStr = window.localStorage.getItem('panelStates') const panelStates = panelStatesStr ? JSON.parse(panelStatesStr) : {} @@ -146,6 +171,30 @@ export class RightSidePanel extends AbstractPanel { const activePlugin = this.currentFocus() if (activePlugin !== profile.name) throw new Error(`Plugin ${profile.name} is not pinned`) + + // If the panel is maximized, restore left and main panels but not terminal + if (this.isMaximized) { + const leftPanelHidden = await this.call('sidePanel', 'isPanelHidden') + + // Restore left panel if it was visible before maximizing + if (!this.maximizedState.leftPanelHidden && leftPanelHidden) { + await this.call('sidePanel', 'togglePanel') + } + + // Show main panel + const mainPanel = document.querySelector('#main-panel') + mainPanel?.classList.remove('d-none') + + // Remove full width from right panel + const rightPanel = document.querySelector('#right-side-panel') + rightPanel?.classList.remove('right-panel-maximized') + + this.isMaximized = false + trackMatomoEvent(this, { category: 'topbar', action: 'rightSidePanel', name: 'restoredOnUnpin', isClick: false }) + this.emit('rightSidePanelRestored') + this.events.emit('rightSidePanelRestored') + } + await this.call('sidePanel', 'unPinView', profile, this.plugins[profile.name].view) super.remove(profile.name) // Clear hiddenPlugin and set panel to hidden state when no plugin is pinned @@ -171,7 +220,7 @@ export class RightSidePanel extends AbstractPanel { return this.hiddenPlugin } - togglePanel () { + async togglePanel () { const pinnedPanel = document.querySelector('#right-side-panel') // Persist the hidden state to panelStates, preserving pluginProfile const panelStates = JSON.parse(window.localStorage.getItem('panelStates') || '{}') @@ -205,6 +254,11 @@ export class RightSidePanel extends AbstractPanel { this.emit('rightSidePanelShown') this.events.emit('rightSidePanelShown') } else { + // If the panel is maximized, restore all panels before hiding + if (this.isMaximized) { + await this.maximizePanel() // This will toggle and restore the panels + } + this.isHidden = true this.hiddenPlugin = pluginProfile pinnedPanel?.classList.add('d-none') @@ -225,6 +279,68 @@ export class RightSidePanel extends AbstractPanel { return this.isHidden } + async maximizePanel() { + if (!this.isMaximized) { + // Store the current state of panels before maximizing + const leftPanelHidden = await this.call('sidePanel', 'isPanelHidden') + const terminalPanelHidden = await this.call('terminal', 'isPanelHidden') + + this.maximizedState = { leftPanelHidden, terminalPanelHidden } + + // Hide left panel if it's visible + if (!leftPanelHidden) { + await this.call('sidePanel', 'togglePanel') + } + + // Hide terminal panel if it's visible + if (!terminalPanelHidden) { + await this.call('terminal', 'togglePanel') + } + + // Hide main panel (center panel with editor) + const mainPanel = document.querySelector('#main-panel') + mainPanel?.classList.add('d-none') + + // Make right panel take full width + const rightPanel = document.querySelector('#right-side-panel') + rightPanel?.classList.add('right-panel-maximized') + + this.isMaximized = true + trackMatomoEvent(this, { category: 'topbar', action: 'rightSidePanel', name: 'maximized', isClick: false }) + this.emit('rightSidePanelMaximized') + this.events.emit('rightSidePanelMaximized') + } else { + // Restore panels to their previous state + const leftPanelHidden = await this.call('sidePanel', 'isPanelHidden') + const terminalPanelHidden = await this.call('terminal', 'isPanelHidden') + + // Restore left panel if it was visible before maximizing + if (!this.maximizedState.leftPanelHidden && leftPanelHidden) { + await this.call('sidePanel', 'togglePanel') + } + + // Restore terminal panel if it was visible before maximizing + if (!this.maximizedState.terminalPanelHidden && terminalPanelHidden) { + await this.call('terminal', 'togglePanel') + } + + // Show main panel + const mainPanel = document.querySelector('#main-panel') + mainPanel?.classList.remove('d-none') + + // Remove full width from right panel + const rightPanel = document.querySelector('#right-side-panel') + rightPanel?.classList.remove('right-panel-maximized') + + this.isMaximized = false + trackMatomoEvent(this, { category: 'topbar', action: 'rightSidePanel', name: 'restored', isClick: false }) + this.emit('rightSidePanelRestored') + this.events.emit('rightSidePanelRestored') + } + + this.renderComponent() + } + highlight () { // If the right side panel is hidden, unhide it when a pinned icon is clicked if (this.isHidden) { @@ -262,7 +378,7 @@ export class RightSidePanel extends AbstractPanel { } updateComponent(state: any) { - return } { ...state } /> + return } { ...state } /> } renderComponent() { diff --git a/apps/remix-ide/src/app/panels/terminal.tsx b/apps/remix-ide/src/app/panels/terminal.tsx index 24967b11b83..ac3d0b6d09d 100644 --- a/apps/remix-ide/src/app/panels/terminal.tsx +++ b/apps/remix-ide/src/app/panels/terminal.tsx @@ -19,7 +19,7 @@ function register(api) { KONSOLES.push(api) } const profile = { displayName: 'Terminal', name: 'terminal', - methods: ['log', 'logHtml', 'togglePanel', 'isPanelHidden'], + methods: ['log', 'logHtml', 'togglePanel', 'isPanelHidden', 'maximizePanel'], events: [], description: 'Remix IDE terminal', version: packageJson.version @@ -55,8 +55,10 @@ export default class Terminal extends Plugin { dispatch: any terminalApi: any isHidden: boolean + isMaximized: boolean constructor(opts, api) { super(profile) + this.isMaximized = false this.fileImport = new CompilerImports() this.event = new EventManager() this.globalRegistry = Registry.getInstance() @@ -116,6 +118,21 @@ export default class Terminal extends Plugin { onActivation() { this.renderComponent() + + // Listen for file changes - auto-restore terminal panel if maximized when main panel is used + this.on('fileManager', 'currentFileChanged', () => { + if (this.isMaximized) { + this.maximizePanel() // This will toggle and restore the panel + } + }) + + // Listen for tab/app switches - auto-restore terminal panel if maximized + this.on('tabs', 'switchApp', () => { + if (this.isMaximized) { + this.maximizePanel() // This will toggle and restore the panel + } + }) + // Initialize isHidden state from panelStates in localStorage const panelStatesStr = window.localStorage.getItem('panelStates') const panelStates = panelStatesStr ? JSON.parse(panelStatesStr) : {} @@ -187,6 +204,23 @@ export default class Terminal extends Plugin { this.emit('terminalPanelShown') } else { this.isHidden = true + + // If terminal was hidden when maximized, restore the main panel + if (this.isMaximized) { + const mainView = document.querySelector('.mainview') + if (mainView) { + const wraps = mainView.querySelectorAll('[class*="-wrap"]') + wraps.forEach((wrap: HTMLElement) => { + if (!wrap.classList.contains('terminal-wrap')) { + wrap.classList.remove('d-none') + } + }) + } + terminalPanel?.classList.remove('maximized') + this.isMaximized = false + this.renderComponent() + } + terminalPanel?.classList.add('d-none') trackMatomoEvent(this, { category: 'topbar', action: 'terminalPanel', name: 'hiddenOnToggleIconClick', isClick: false }) this.emit('terminalPanelHidden') @@ -204,6 +238,48 @@ export default class Terminal extends Plugin { return this.isHidden } + async maximizePanel() { + if (!this.isMaximized) { + // Hide all main panel content except terminal + const mainView = document.querySelector('.mainview') + if (mainView) { + // Find all child elements with -wrap class except terminal-wrap + const wraps = mainView.querySelectorAll('[class*="-wrap"]') + wraps.forEach((wrap: HTMLElement) => { + if (!wrap.classList.contains('terminal-wrap')) { + wrap.classList.add('d-none') + } else { + // Add maximized class to terminal-wrap + wrap.classList.add('maximized') + } + }) + } + + this.isMaximized = true + trackMatomoEvent(this, { category: 'topbar', action: 'terminalPanel', name: 'maximized', isClick: false }) + this.emit('terminalPanelMaximized') + } else { + // Show all main panel content + const mainView = document.querySelector('.mainview') + if (mainView) { + // Find all child elements with -wrap class and show them + const wraps = mainView.querySelectorAll('[class*="-wrap"]') + wraps.forEach((wrap: HTMLElement) => { + wrap.classList.remove('d-none') + // Remove maximized class from terminal-wrap + if (wrap.classList.contains('terminal-wrap')) { + wrap.classList.remove('maximized') + } + }) + } + + this.isMaximized = false + trackMatomoEvent(this, { category: 'topbar', action: 'terminalPanel', name: 'restored', isClick: false }) + this.emit('terminalPanelRestored') + } + this.renderComponent() + } + setDispatch(dispatch) { this.dispatch = dispatch } @@ -219,6 +295,8 @@ export default class Terminal extends Plugin { plugin={state.plugin} onReady={state.onReady} visible={true} + isMaximized={this.isMaximized} + maximizePanel={this.maximizePanel.bind(this)} /> ) } diff --git a/libs/remix-ui/app/src/lib/remix-app/style/remix-app.css b/libs/remix-ui/app/src/lib/remix-app/style/remix-app.css index 2b7675e3a4e..05b24b140a5 100644 --- a/libs/remix-ui/app/src/lib/remix-app/style/remix-app.css +++ b/libs/remix-ui/app/src/lib/remix-app/style/remix-app.css @@ -49,6 +49,12 @@ pre { transition : width 0.25s; padding-bottom : 1.4rem; } +.right-panel-maximized { + width : 50% !important; + max-width : 50%; + margin : 0 auto; + flex : none; +} .highlightcode { position : absolute; z-index : 20; diff --git a/libs/remix-ui/panel/src/lib/plugins/panel-header.tsx b/libs/remix-ui/panel/src/lib/plugins/panel-header.tsx index 6841ed4acdd..6c12ca7dac1 100644 --- a/libs/remix-ui/panel/src/lib/plugins/panel-header.tsx +++ b/libs/remix-ui/panel/src/lib/plugins/panel-header.tsx @@ -10,7 +10,9 @@ export interface RemixPanelProps { plugins: Record, pinView?: (profile: PluginRecord['profile'], view: PluginRecord['view']) => void, unPinView?: (profile: PluginRecord['profile']) => void, - togglePanel?: () => void + togglePanel?: () => void, + maximizePanel?: () => void, + isMaximized?: boolean } const RemixUIPanelHeader = (props: RemixPanelProps) => { const [plugin, setPlugin] = useState() @@ -45,6 +47,10 @@ const RemixUIPanelHeader = (props: RemixPanelProps) => { props.togglePanel && props.togglePanel() } + const maximizePanelHandler = () => { + props.maximizePanel && props.maximizePanel() + } + const tooltipChild = const FilePanelHeading = () => { @@ -99,6 +105,15 @@ const RemixUIPanelHeader = (props: RemixPanelProps) => {
+ +
+ {props.isMaximized ? '\ueb4d' : '\ueb4c' /* Actual icons were not being rendered, so used unicode for codicon-screen-full & codicon-screen-normal icons*/ } +
+
{ const { terminalState, xtermState } = useContext(TerminalContext) const platform = useContext(platformContext) - const intl = useIntl() const terminalMenu = useRef(null) useEffect(() => { @@ -31,10 +28,12 @@ export const RemixUITerminalBar = (props: RemixUiTerminalProps) => {
{xtermState.showOutput? : } +
:
+
} diff --git a/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-maximize.tsx b/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-maximize.tsx new file mode 100644 index 00000000000..4dce22b21bc --- /dev/null +++ b/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-maximize.tsx @@ -0,0 +1,32 @@ +import { CustomTooltip } from '@remix-ui/helper' +import React from 'react' // eslint-disable-line +import { RemixUiTerminalProps } from '../types/terminalTypes' + +export const RemixUITerminalMenuMaximize = (props: RemixUiTerminalProps) => { + + async function handleMaximizeTerminal(): Promise { + if (props.maximizePanel) { + await props.maximizePanel() + } + } + + return ( + <> + +
+ {props.isMaximized ? '\ueb4d' : '\ueb4c' /* Actual icons were not being rendered, so used unicode for codicon-screen-full & codicon-screen-normal icons*/ } +
+
+ + ) +} diff --git a/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-toggle.tsx b/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-toggle.tsx index d73b00cfb7a..113daad263e 100644 --- a/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-toggle.tsx +++ b/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-toggle.tsx @@ -5,6 +5,10 @@ import { RemixUiTerminalProps } from '../types/terminalTypes' export const RemixUITerminalMenuToggle = (props: RemixUiTerminalProps) => { async function handleToggleTerminal(): Promise { + // If panel is maximized, un-maximize it first to show main panel + if (props.isMaximized && props.maximizePanel) { + await props.maximizePanel() + } // Toggle the bottom terminal panel using terminal-wrap component await props.plugin.call('terminal', 'togglePanel') } diff --git a/libs/remix-ui/terminal/src/lib/types/terminalTypes.ts b/libs/remix-ui/terminal/src/lib/types/terminalTypes.ts index 0de59411173..f904716a804 100644 --- a/libs/remix-ui/terminal/src/lib/types/terminalTypes.ts +++ b/libs/remix-ui/terminal/src/lib/types/terminalTypes.ts @@ -35,4 +35,6 @@ export interface RemixUiTerminalProps { plugin: any, onReady: (api: any) => void, visible: boolean, + isMaximized?: boolean, + maximizePanel?: () => void, }