Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make terminal draggable #4340

Merged
merged 15 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions packages/ai-native/src/browser/layout/layout.module.less
Original file line number Diff line number Diff line change
Expand Up @@ -119,20 +119,20 @@
border: none;
}

.header {
height: 36px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
background-color: var(--editorGroupHeader-tabsBackground);
}

.right_slot_container_wrap {
height: 100%;
display: flex;
flex-direction: column;

.header {
height: 36px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
background-color: var(--editorGroupHeader-tabsBackground);
}

.container {
flex: 1;
}
Expand Down
18 changes: 10 additions & 8 deletions packages/ai-native/src/browser/layout/tabbar.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,16 +174,18 @@ export const AIRightTabRenderer = ({
return (
<ContainerView
{...props}
customTitleBar={
<div className={styles.header}>
<span className={styles.title}>{options && options.title}</span>
<div className={styles.side}>
<EnhancePopover id={'ai_right_panel_header_close'} title={localize('editor.title.context.close')}>
<EnhanceIcon icon='close' onClick={handleClose} />
</EnhancePopover>
</div>
</div>
}
renderContainerWrap={({ children }) => (
<div className={styles.right_slot_container_wrap}>
<div className={styles.header}>
<span className={styles.title}>{options && options.title}</span>
<div className={styles.side}>
<EnhancePopover id={'ai_right_panel_header_close'} title={localize('editor.title.context.close')}>
<EnhanceIcon icon='close' onClick={handleClose} />
</EnhancePopover>
</div>
</div>
<div className={styles.container}>{children}</div>
</div>
)}
Expand Down
1 change: 1 addition & 0 deletions packages/core-browser/src/layout/layout.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export interface ExtViewContainerOptions {
// viewContainer 最小高度,默认 120
miniSize?: number;
alignment?: Layout.alignment;
draggable?: boolean;
}
export const ComponentRegistry = Symbol('ComponentRegistry');

Expand Down
25 changes: 23 additions & 2 deletions packages/main-layout/src/browser/accordion/titlebar.view.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,39 @@
import React from 'react';

import { useDesignStyles } from '@opensumi/ide-core-browser';
import { useDesignStyles, useInjectable } from '@opensumi/ide-core-browser';

import { TabbarService, TabbarServiceFactory } from '../tabbar/tabbar.service';

import styles from './styles.module.less';

export const TitleBar: React.FC<{
title: string;
menubar?: React.ReactNode;
height?: number;
draggable?: boolean;
side: string;
containerId: string;
}> = React.memo((props) => {
const styles_titlebar = useDesignStyles(styles.titlebar, 'titlebar');
const tabbarService: TabbarService = useInjectable(TabbarServiceFactory)(props.side);

return (
<div className={styles_titlebar} style={{ height: props.height }}>
<h1>{props.title}</h1>
{!props.draggable && <h1>{props.title}</h1>}
{!!props.draggable && (
<h1
draggable
style={{ cursor: 'pointer' }}
onDragStart={(e) => {
tabbarService.handleDragStart(e, props.containerId);
}}
onDragEnd={(e) => {
tabbarService.handleDragEnd(e);
}}
>
{props.title}
</h1>
)}
{props.menubar || null}
</div>
);
Expand Down
12 changes: 10 additions & 2 deletions packages/main-layout/src/browser/default-config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* istanbul ignore file */
import { LayoutConfig, SlotLocation } from '@opensumi/ide-core-browser';

import { DROP_BOTTOM_CONTAINER, DROP_RIGHT_CONTAINER } from '../common';

export const defaultConfig: LayoutConfig = {
[SlotLocation.top]: {
modules: ['@opensumi/ide-menu-bar'],
Expand All @@ -19,13 +21,19 @@ export const defaultConfig: LayoutConfig = {
],
},
[SlotLocation.right]: {
modules: [],
modules: [DROP_RIGHT_CONTAINER],
},
[SlotLocation.main]: {
modules: ['@opensumi/ide-editor'],
},
[SlotLocation.bottom]: {
modules: ['@opensumi/ide-terminal-next', '@opensumi/ide-output', 'debug-console', '@opensumi/ide-markers'],
modules: [
DROP_BOTTOM_CONTAINER,
'@opensumi/ide-terminal-next',
'@opensumi/ide-output',
'debug-console',
'@opensumi/ide-markers',
],
},
[SlotLocation.statusBar]: {
modules: ['@opensumi/ide-status-bar'],
Expand Down
34 changes: 34 additions & 0 deletions packages/main-layout/src/browser/drop-area/drop-area.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';

import { useInjectable } from '@opensumi/ide-core-browser';
import { IMainLayoutService } from '@opensumi/ide-main-layout';

import styles from './styles.module.less';

interface IDropAreaProps {
location: string;
}

const DropArea: React.FC<IDropAreaProps> = (props) => {
const { location } = props;
const layoutService = useInjectable<IMainLayoutService>(IMainLayoutService);

return (
<div
className={styles.drop_area}
onDrop={(e) => {
const containerId = e.dataTransfer?.getData('containerId');
layoutService.moveContainerTo(containerId, location);
}}
onDragOver={(e) => {
e.preventDefault();
}}
>
drop here
</div>
);
};

export const RightDropArea = () => <DropArea location='right' />;

export const BottomDropArea = () => <DropArea location='bottom' />;
7 changes: 7 additions & 0 deletions packages/main-layout/src/browser/drop-area/styles.module.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.drop_area {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
98 changes: 90 additions & 8 deletions packages/main-layout/src/browser/layout.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import { Deferred, getDebugLogger, isUndefined } from '@opensumi/ide-core-common
import { ThemeChangedEvent } from '@opensumi/ide-theme';

import {
DROP_BOTTOM_CONTAINER,
DROP_RIGHT_CONTAINER,
IMainLayoutService,
MainLayoutContribution,
SUPPORT_ACCORDION_LOCATION,
Expand Down Expand Up @@ -224,6 +226,70 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
}
};

findTabbarServiceByContainerId(containerId: string): TabbarService | undefined {
let tabbarService: undefined | TabbarService;
for (const value of this.tabbarServices.values()) {
if (value.containersMap.has(containerId)) {
tabbarService = value;
break;
}
}

return tabbarService;
}

moveContainerTo(containerId: string, to: string): void {
const fromTabbar = this.findTabbarServiceByContainerId(containerId);

if (!fromTabbar) {
this.logger.error(`cannot find container: ${containerId}`);
return;
}
const container = fromTabbar.getContainer(containerId);
if (!container) {
this.logger.error(`cannot find container: ${containerId}`);
return;
}

const toTabbar = this.getTabbarService(to);

fromTabbar.removeContainer(containerId);

if (!fromTabbar.visibleContainers.length || fromTabbar.currentContainerId.get() === containerId) {
this.toggleSlot(fromTabbar.location, false);
}
toTabbar.dynamicAddContainer(containerId, container);
const newHandler = this.injector.get(TabBarHandler, [containerId, this.getTabbarService(toTabbar.location)]);
this.handleMap.set(containerId, newHandler!);
}

showDropAreaForContainer(containerId: string): void {
const tabbarService = this.findTabbarServiceByContainerId(containerId);
const bottomService = this.tabbarServices.get('bottom');
const rightService = this.tabbarServices.get('right');
if (!tabbarService) {
this.logger.error(`cannot find container: ${containerId}`);
return;
}
if (tabbarService?.location === 'right') {
bottomService?.updateCurrentContainerId('drop-bottom');
}
if (tabbarService?.location === 'bottom') {
rightService?.updateCurrentContainerId('drop-right');
}
}

hideDropArea(): void {
const bottomService = this.tabbarServices.get('bottom');
const rightService = this.tabbarServices.get('right');
if (bottomService?.currentContainerId.get() === DROP_BOTTOM_CONTAINER) {
bottomService.updateCurrentContainerId(bottomService.previousContainerId || '');
}
if (rightService?.currentContainerId.get() === DROP_RIGHT_CONTAINER) {
rightService.updateCurrentContainerId(rightService.previousContainerId || '');
}
}

isVisible(location: string) {
const tabbarService = this.getTabbarService(location);
return !!tabbarService.currentContainerId.get();
Expand All @@ -245,25 +311,41 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
return;
}
if (show === true) {
tabbarService.updateCurrentContainerId(
tabbarService.currentContainerId.get() ||
tabbarService.previousContainerId ||
tabbarService.containersMap.keys().next().value!,
);
// 不允许通过该api展示drop面板
tabbarService.updateCurrentContainerId(this.findNonDropContainerId(tabbarService));
} else if (show === false) {
tabbarService.updateCurrentContainerId('');
} else {
tabbarService.updateCurrentContainerId(
tabbarService.currentContainerId.get()
? ''
: tabbarService.previousContainerId || tabbarService.containersMap.keys().next().value!,
tabbarService.currentContainerId.get() ? '' : this.findNonDropContainerId(tabbarService),
);
}
if (tabbarService.currentContainerId.get() && size) {
tabbarService.resizeHandle?.setSize(size);
}
}

private findNonDropContainerId(tabbarService: TabbarService): string {
const currentContainerId = tabbarService.currentContainerId.get();
if (currentContainerId && ![DROP_BOTTOM_CONTAINER, DROP_RIGHT_CONTAINER].includes(currentContainerId as string)) {
return currentContainerId;
}
if (
tabbarService.previousContainerId &&
![DROP_BOTTOM_CONTAINER, DROP_RIGHT_CONTAINER].includes(tabbarService.previousContainerId as string)
) {
return tabbarService.previousContainerId;
}

for (const key of tabbarService.containersMap.keys()) {
if (![DROP_BOTTOM_CONTAINER, DROP_RIGHT_CONTAINER].includes(key as string)) {
return key;
}
}

return '';
}

getTabbarService(location: string) {
const service = this.tabbarServices.get(location) || this.injector.get(TabbarService, [location]);
if (!this.tabbarServices.get(location)) {
Expand Down
26 changes: 24 additions & 2 deletions packages/main-layout/src/browser/main-layout.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ import {
import { ContributionProvider, Domain, IEventBus, WithEventBus, localize } from '@opensumi/ide-core-common';
import { Command, CommandContribution, CommandRegistry, CommandService } from '@opensumi/ide-core-common/lib/command';

import { IMainLayoutService } from '../common';
import { DROP_BOTTOM_CONTAINER, DROP_RIGHT_CONTAINER, IMainLayoutService } from '../common';

import { BottomDropArea, RightDropArea } from './drop-area/drop-area';
import { ViewQuickOpenHandler } from './quick-open-view';
import { BottomTabRenderer, LeftTabRenderer, RightTabRenderer } from './tabbar/renderer.view';

Expand Down Expand Up @@ -117,14 +118,22 @@ export const RETRACT_BOTTOM_PANEL: Command = {
iconClass: getIcon('shrink'),
};

@Domain(CommandContribution, ClientAppContribution, SlotRendererContribution, MenuContribution, QuickOpenContribution)
@Domain(
CommandContribution,
ClientAppContribution,
SlotRendererContribution,
MenuContribution,
QuickOpenContribution,
ComponentContribution,
)
export class MainLayoutModuleContribution
extends WithEventBus
implements
CommandContribution,
ClientAppContribution,
SlotRendererContribution,
MenuContribution,
ComponentContribution,
QuickOpenContribution
{
@Autowired(IMainLayoutService)
Expand Down Expand Up @@ -186,6 +195,19 @@ export class MainLayoutModuleContribution
}
}

registerComponent(registry: ComponentRegistry): void {
registry.register(DROP_RIGHT_CONTAINER, [], {
component: RightDropArea,
hideTab: true,
containerId: DROP_RIGHT_CONTAINER,
});
registry.register(DROP_BOTTOM_CONTAINER, [], {
component: BottomDropArea,
hideTab: true,
containerId: DROP_BOTTOM_CONTAINER,
});
}

async onStart() {
this.registerSideToggleKey();
}
Expand Down
3 changes: 3 additions & 0 deletions packages/main-layout/src/browser/tabbar/bar.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ export const TabbarViewBase: React.FC<ITabbarViewProps> = (props) => {
}
tabbarService.handleDrop(e, containerId);
}}
onDragEnd={(e) => {
tabbarService.handleDragEnd(e);
}}
key={containerId}
id={containerId}
onContextMenu={(e) => tabbarService.handleContextMenu(e, containerId)}
Expand Down
Loading