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

fix: layout state restore not work #3941

Merged
merged 6 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,8 @@ export class FileAndContentUpdateTimeContribution extends WithEventBus {
// additional edits for file-participants
this._traceConfig = !!this.preferenceService.get<boolean>(TRACE_LOG_FLAG);
this.addDispose(
this.preferenceService.onPreferenceChanged((e) => {
if (e.preferenceName === TRACE_LOG_FLAG) {
this._traceConfig = !!e.newValue;
}
this.preferenceService.onSpecificPreferenceChange(TRACE_LOG_FLAG, (e) => {
this._traceConfig = !!e.newValue;
}),
);
}
Expand Down
19 changes: 11 additions & 8 deletions packages/core-browser/src/bootstrap/app.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,25 @@ export type IAppRenderer = (app: (props: any) => JSX.Element) => void;

const defaultAppRender =
(dom: HTMLElement): IAppRenderer =>
(IDEApp: (props) => JSX.Element) => {
(IDEApp: (props: any) => JSX.Element) => {
bytemain marked this conversation as resolved.
Show resolved Hide resolved
const root = ReactDom.createRoot(dom);
root.render(<IDEApp />);
};

const debugLogger = getDebugLogger();

export function renderClientApp(app: IClientApp, container: HTMLElement | IAppRenderer) {
const Layout = app.config.layoutComponent || DefaultLayout;
const overlayComponents = app.browserModules
.filter((module) => module.isOverlay)
.map((module) => {
if (!module.component) {
getDebugLogger().warn('Overlay module does not have component', module);
return () => <></>;
.filter((mod) => mod.isOverlay)
.map((mod) => {
if (!mod.component) {
debugLogger.warn('Overlay module does not have component', mod);
return null;
}
return module.component;
});
return mod.component;
})
.filter(Boolean) as React.ComponentType[];
bytemain marked this conversation as resolved.
Show resolved Hide resolved

const IdeApp = (props) => <App {...props} app={app} main={Layout} overlays={overlayComponents} />;

Expand Down
22 changes: 20 additions & 2 deletions packages/core-browser/src/components/layout/default-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { BoxPanel } from './box-panel';
import { SplitPanel } from './split-panel';

export interface ILayoutConfigCache {
[key: string]: { size: number; currentId: string };
[key: string]: { size?: number; currentId?: string };
}

export const getStorageValue = () => {
Expand All @@ -26,7 +26,7 @@ export const getStorageValue = () => {
} catch (err) {}

return {
layout: savedLayout,
layout: fixLayout(savedLayout),
colors: savedColors,
};
};
Expand Down Expand Up @@ -75,3 +75,21 @@ export function ToolbarActionBasedLayout(
</BoxPanel>
);
}

/**
* if layout has currentId, but its size is zero
* we cannot acknowledge the currentId, so we should remove it
*/
export function fixLayout(layout: ILayoutConfigCache) {
const newLayout = { ...layout };
for (const key in layout) {
if (!layout[key]) {
continue;
}

if (!layout[key].size) {
newLayout[key].currentId = '';
}
}
return newLayout;
}
6 changes: 2 additions & 4 deletions packages/core-browser/src/layout/layout-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ export class LayoutState {
await this.preferenceService.ready;
this.saveLayoutWithWorkspace = this.preferenceService.get<boolean>('view.saveLayoutWithWorkspace') || false;
this.disposableCollection.push(
this.preferenceService.onPreferenceChanged((e) => {
if (e.preferenceName === 'view.saveLayoutWithWorkspace') {
this.saveLayoutWithWorkspace = e.newValue;
}
this.preferenceService.onSpecificPreferenceChange('view.saveLayoutWithWorkspace', (e) => {
this.saveLayoutWithWorkspace = e.newValue;
}),
);
}
Expand Down
15 changes: 9 additions & 6 deletions packages/main-layout/src/browser/accordion/accordion.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import debounce from 'lodash/debounce';
import isEqual from 'lodash/isEqual';
import { action, makeObservable, observable, runInAction } from 'mobx';

import { Autowired, Injectable } from '@opensumi/di';
Expand Down Expand Up @@ -83,7 +84,7 @@ export class AccordionService extends WithEventBus {
@Autowired(IContextKeyService)
private contextKeyService: IContextKeyService;

@Autowired()
@Autowired(LayoutState)
private layoutState: LayoutState;

@Autowired(IProgressService)
Expand Down Expand Up @@ -119,16 +120,18 @@ export class AccordionService extends WithEventBus {
private topViewKey: IContextKey<string>;
private scopedCtxKeyService: IScopedContextKeyService;

private didChangeViewTitleEmitter: Emitter<AccordionViewChangeEvent> = new Emitter<AccordionViewChangeEvent>();
private didChangeViewTitleEmitter: Emitter<AccordionViewChangeEvent> = this.registerDispose(
new Emitter<AccordionViewChangeEvent>(),
);
public onDidChangeViewTitle: Event<AccordionViewChangeEvent> = this.didChangeViewTitleEmitter.event;

private beforeAppendViewEmitter = new Emitter<string>();
private beforeAppendViewEmitter = this.registerDispose(new Emitter<string>());
public onBeforeAppendViewEvent = this.beforeAppendViewEmitter.event;

private afterAppendViewEmitter = new Emitter<string>();
private afterAppendViewEmitter = this.registerDispose(new Emitter<string>());
public onAfterAppendViewEvent = this.afterAppendViewEmitter.event;

private afterDisposeViewEmitter = new Emitter<string>();
private afterDisposeViewEmitter = this.registerDispose(new Emitter<string>());
public onAfterDisposeViewEvent = this.afterDisposeViewEmitter.event;
bytemain marked this conversation as resolved.
Show resolved Hide resolved

constructor(public containerId: string, private noRestore?: boolean) {
Expand Down Expand Up @@ -222,7 +225,7 @@ export class AccordionService extends WithEventBus {
const defaultState: { [containerId: string]: SectionState } = {};
this.visibleViews.forEach((view) => (defaultState[view.id] = { collapsed: false, hidden: false }));
const restoredState = this.layoutState.getState(LAYOUT_STATE.getContainerSpace(this.containerId), defaultState);
if (restoredState !== defaultState) {
if (!isEqual(restoredState, defaultState)) {
bytemain marked this conversation as resolved.
Show resolved Hide resolved
this.state = restoredState;
}
this.popViewKeyIfOnlyOneViewVisible();
Expand Down
7 changes: 2 additions & 5 deletions packages/main-layout/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { IMainLayoutService, IViewsRegistry, MainLayoutContribution } from '../c
import { AccordionServiceFactory } from './accordion/accordion.service';
import { LayoutService } from './layout.service';
import { MainLayoutModuleContribution } from './main-layout.contribution';
import { TabbarServiceFactory } from './tabbar/tabbar.service';
import { TabbarServiceFactory, TabbarServiceFactoryFn } from './tabbar/tabbar.service';
import { ViewsRegistry } from './views-registry';

@Injectable()
Expand All @@ -23,10 +23,7 @@ export class MainLayoutModule extends BrowserModule {
},
{
token: TabbarServiceFactory,
useFactory: (injector: Injector) => (location: string) => {
const manager: IMainLayoutService = injector.get(IMainLayoutService);
return manager.getTabbarService(location);
},
useFactory: TabbarServiceFactoryFn,
},
{
token: AccordionServiceFactory,
Expand Down
101 changes: 56 additions & 45 deletions packages/main-layout/src/browser/layout.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
WithEventBus,
slotRendererRegistry,
} from '@opensumi/ide-core-browser';
import { fixLayout } from '@opensumi/ide-core-browser/lib/components';
import { LAYOUT_STATE, LayoutState } from '@opensumi/ide-core-browser/lib/layout/layout-state';
import { ComponentRegistryInfo } from '@opensumi/ide-core-browser/lib/layout/layout.interface';
import {
Expand All @@ -41,6 +42,22 @@ import { AccordionService } from './accordion/accordion.service';
import { TabbarService } from './tabbar/tabbar.service';
import { TabBarHandler } from './tabbar-handler';

const defaultLayoutState = {
[SlotLocation.left]: {
currentId: undefined,
size: undefined,
},
[SlotLocation.right]: {
// 依照下面的恢复逻辑,这里设置为 `''` 时,就不会恢复右侧的 TabBar 的状态(即选中相应的 viewContainer)
currentId: '',
size: undefined,
},
[SlotLocation.bottom]: {
currentId: undefined,
size: undefined,
},
};

@Injectable()
export class LayoutService extends WithEventBus implements IMainLayoutService {
@Autowired(INJECTOR_TOKEN)
Expand Down Expand Up @@ -154,21 +171,7 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
}

restoreTabbarService = async (service: TabbarService) => {
this.state = this.layoutState.getState(LAYOUT_STATE.MAIN, {
[SlotLocation.left]: {
currentId: undefined,
size: undefined,
},
[SlotLocation.right]: {
// 依照下面的恢复逻辑,这里设置为 `''` 时,就不会恢复右侧的 TabBar 的状态(即选中相应的 viewContainer)
currentId: '',
size: undefined,
},
[SlotLocation.bottom]: {
currentId: undefined,
size: undefined,
},
});
this.state = fixLayout(this.layoutState.getState(LAYOUT_STATE.MAIN, defaultLayoutState));

const { currentId, size } = this.state[service.location] || {};
service.prevSize = size;
Expand Down Expand Up @@ -264,30 +267,32 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
getTabbarService(location: string) {
const service = this.tabbarServices.get(location) || this.injector.get(TabbarService, [location]);
if (!this.tabbarServices.get(location)) {
service.onCurrentChange(({ currentId }) => {
this.storeState(service, currentId);
// onView 也支持监听 containerId
this.eventBus.fire(new ExtensionActivateEvent({ topic: 'onView', data: currentId }));
if (currentId && SUPPORT_ACCORDION_LOCATION.has(service.location)) {
const accordionService = this.getAccordionService(currentId);
accordionService?.tryUpdateResize();
accordionService?.expandedViews.forEach((view) => {
this.eventBus.fire(new ExtensionActivateEvent({ topic: 'onView', data: view.id }));
});
}
});
service.addDispose(
service.onCurrentChange(({ currentId }) => {
this.storeState(service, currentId);
// onView 也支持监听 containerId
this.eventBus.fire(new ExtensionActivateEvent({ topic: 'onView', data: currentId }));
if (currentId && SUPPORT_ACCORDION_LOCATION.has(service.location)) {
const accordionService = this.getAccordionService(currentId);
accordionService?.tryUpdateResize();
accordionService?.expandedViews.forEach((view) => {
this.eventBus.fire(new ExtensionActivateEvent({ topic: 'onView', data: view.id }));
});
}
}),
);
service.viewReady.promise
.then(() => service.restoreState())
.then(() => this.restoreTabbarService(service))
.catch((err) => {
this.logger.error(`[TabbarService:${location}] restore state error`, err);
});
const debouncedStoreState = debounce(() => this.storeState(service, service.currentContainerId), 100);
service.onSizeChange(debouncedStoreState);
service.addDispose(service.onSizeChange(debouncedStoreState));
if (location === SlotLocation.bottom) {
// use this getter's side effect to set bottomExpanded contextKey
const debouncedUpdate = debounce(() => void this.bottomExpanded, 100);
service.onSizeChange(() => debouncedUpdate);
service.addDispose(service.onSizeChange(() => debouncedUpdate));
}
this.tabbarServices.set(location, service);
}
Expand Down Expand Up @@ -372,17 +377,21 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
}
const service = this.getAccordionService(options.containerId);
// 如果 append view 时尝试注册 holdTabbarComponent
service.onBeforeAppendViewEvent(() => {
this.tryUpdateTabbar(options.containerId);
});
service.onAfterDisposeViewEvent(() => {
// 如果没有其他 view ,则 remove 掉 container
if (service.views.length === 0) {
this.disposeContainer(options.containerId);
// 重新注册到 holdTabbarComponent ,以便再次 append 时能注册传上去
this.holdTabbarComponent.set(options.containerId, { views, options, side });
}
});
service.addDispose(
bytemain marked this conversation as resolved.
Show resolved Hide resolved
service.onBeforeAppendViewEvent(() => {
this.tryUpdateTabbar(options.containerId);
}),
);
service.addDispose(
service.onAfterDisposeViewEvent(() => {
// 如果没有其他 view ,则 remove 掉 container
if (service.views.length === 0) {
this.disposeContainer(options.containerId);
// 重新注册到 holdTabbarComponent ,以便再次 append 时能注册传上去
this.holdTabbarComponent.set(options.containerId, { views, options, side });
}
}),
);
return options.containerId;
}
const tabbarService = this.getTabbarService(side);
Expand Down Expand Up @@ -443,11 +452,13 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
const viewReady = new Deferred<void>();
const accordionService = this.getAccordionService(containerId);
if (!accordionService.visibleViews.find((view) => view.id === viewId)) {
accordionService.onAfterAppendViewEvent((id) => {
if (id === viewId) {
viewReady.resolve();
}
});
accordionService.addDispose(
bytemain marked this conversation as resolved.
Show resolved Hide resolved
accordionService.onAfterAppendViewEvent((id) => {
if (id === viewId) {
viewReady.resolve();
}
}),
);
} else {
viewReady.resolve();
}
Expand Down
7 changes: 6 additions & 1 deletion packages/main-layout/src/browser/tabbar/tabbar.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import debounce from 'lodash/debounce';
import { action, makeObservable, observable, reaction, runInAction } from 'mobx';

import { Autowired, Injectable } from '@opensumi/di';
import { Autowired, Injectable, Injector } from '@opensumi/di';
import {
CommandRegistry,
ComponentRegistryInfo,
Expand Down Expand Up @@ -47,6 +47,11 @@ import { IMainLayoutService, SUPPORT_ACCORDION_LOCATION, TabBarRegistrationEvent
import { EXPAND_BOTTOM_PANEL, RETRACT_BOTTOM_PANEL, TOGGLE_BOTTOM_PANEL_COMMAND } from '../main-layout.contribution';

export const TabbarServiceFactory = Symbol('TabbarServiceFactory');
export const TabbarServiceFactoryFn = (injector: Injector) => (location: string) => {
const manager: IMainLayoutService = injector.get(IMainLayoutService);
return manager.getTabbarService(location);
};

export interface TabState {
hidden: boolean;
// 排序位置,数字越小优先级越高
Expand Down
Loading