Merge pull request #755 from mathuo/665-floating-group-setvisible-will-crash

bug: fix setVisible on floating groups
This commit is contained in:
mathuo 2024-11-10 14:29:57 +00:00 committed by GitHub
commit 24cc974a68
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 392 additions and 188 deletions

View File

@ -5354,169 +5354,378 @@ describe('dockviewComponent', () => {
});
});
test('that setVisible toggles visiblity', () => {
const container = document.createElement('div');
describe('panel visibility', () => {
test('that setVisible toggles visiblity', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent(container, {
createComponent(options) {
switch (options.name) {
case 'default':
return new PanelContentPartTest(
options.id,
options.name
);
default:
throw new Error(`unsupported`);
}
},
});
const api = new DockviewApi(dockview);
const dockview = new DockviewComponent(container, {
createComponent(options) {
switch (options.name) {
case 'default':
return new PanelContentPartTest(
options.id,
options.name
);
default:
throw new Error(`unsupported`);
}
},
});
const api = new DockviewApi(dockview);
dockview.layout(1000, 1000);
dockview.layout(1000, 1000);
const panel1 = api.addPanel({
id: 'panel1',
component: 'default',
});
const panel2 = api.addPanel({
id: 'panel2',
component: 'default',
position: { referencePanel: panel1, direction: 'within' },
const panel1 = api.addPanel({
id: 'panel1',
component: 'default',
});
const panel2 = api.addPanel({
id: 'panel2',
component: 'default',
position: { referencePanel: panel1, direction: 'within' },
});
const panel3 = api.addPanel({
id: 'panel3',
component: 'default',
position: { referencePanel: panel1, direction: 'right' },
});
const panel4 = api.addPanel({
id: 'panel4',
component: 'default',
position: { referencePanel: panel3, direction: 'within' },
});
expect(api.groups.length).toBe(2);
expect(panel1.group).toBe(panel2.group);
expect(panel3.group).toBe(panel4.group);
expect(panel1.group.api.isVisible).toBeTruthy();
expect(panel2.group.api.isVisible).toBeTruthy();
expect(panel3.group.api.isVisible).toBeTruthy();
expect(panel4.group.api.isVisible).toBeTruthy();
expect(panel1.api.isVisible).toBeFalsy();
expect(panel2.api.isVisible).toBeTruthy();
expect(panel3.api.isVisible).toBeFalsy();
expect(panel4.api.isVisible).toBeTruthy();
// case #1
panel1.group.api.setVisible(false);
expect(panel1.group.api.isVisible).toBeFalsy();
expect(panel2.group.api.isVisible).toBeFalsy();
expect(panel3.group.api.isVisible).toBeTruthy();
expect(panel4.group.api.isVisible).toBeTruthy();
expect(panel1.api.isVisible).toBeFalsy();
expect(panel2.api.isVisible).toBeFalsy();
expect(panel3.api.isVisible).toBeFalsy();
expect(panel4.api.isVisible).toBeTruthy();
// case #2
panel3.group.api.setVisible(false);
expect(panel1.group.api.isVisible).toBeFalsy();
expect(panel2.group.api.isVisible).toBeFalsy();
expect(panel3.group.api.isVisible).toBeFalsy();
expect(panel4.group.api.isVisible).toBeFalsy();
expect(panel1.api.isVisible).toBeFalsy();
expect(panel2.api.isVisible).toBeFalsy();
expect(panel3.api.isVisible).toBeFalsy();
expect(panel4.api.isVisible).toBeFalsy();
// case #2
panel3.group.api.setVisible(true);
expect(panel1.group.api.isVisible).toBeFalsy();
expect(panel2.group.api.isVisible).toBeFalsy();
expect(panel3.group.api.isVisible).toBeTruthy();
expect(panel4.group.api.isVisible).toBeTruthy();
expect(panel1.api.isVisible).toBeFalsy();
expect(panel2.api.isVisible).toBeFalsy();
expect(panel3.api.isVisible).toBeFalsy();
expect(panel4.api.isVisible).toBeTruthy();
// case #2
panel1.group.api.setVisible(true);
expect(panel1.group.api.isVisible).toBeTruthy();
expect(panel2.group.api.isVisible).toBeTruthy();
expect(panel3.group.api.isVisible).toBeTruthy();
expect(panel4.group.api.isVisible).toBeTruthy();
expect(panel1.api.isVisible).toBeFalsy();
expect(panel2.api.isVisible).toBeTruthy();
expect(panel3.api.isVisible).toBeFalsy();
expect(panel4.api.isVisible).toBeTruthy();
});
const panel3 = api.addPanel({
id: 'panel3',
component: 'default',
position: { referencePanel: panel1, direction: 'right' },
test('setVisible #1', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent(container, {
createComponent(options) {
switch (options.name) {
case 'default':
return new PanelContentPartTest(
options.id,
options.name
);
default:
throw new Error(`unsupported`);
}
},
});
const api = new DockviewApi(dockview);
dockview.layout(1000, 1000);
const panel1 = api.addPanel({
id: 'panel1',
component: 'default',
});
const panel2 = api.addPanel({
id: 'panel2',
component: 'default',
position: { referencePanel: panel1, direction: 'below' },
});
const panel3 = api.addPanel({
id: 'panel3',
component: 'default',
position: { referencePanel: panel1, direction: 'below' },
});
expect(api.groups.length).toBe(3);
panel1.group.api.setVisible(false);
panel2.group.api.setVisible(false);
panel3.group.api.setVisible(false);
expect(panel1.group.api.isVisible).toBeFalsy();
expect(panel2.group.api.isVisible).toBeFalsy();
expect(panel3.group.api.isVisible).toBeFalsy();
panel1.group.api.setVisible(true);
expect(panel1.group.api.isVisible).toBeTruthy();
expect(panel2.group.api.isVisible).toBeFalsy();
expect(panel3.group.api.isVisible).toBeFalsy();
});
const panel4 = api.addPanel({
id: 'panel4',
component: 'default',
position: { referencePanel: panel3, direction: 'within' },
test('that watermark appears when all views are not visible', () => {
jest.useFakeTimers();
const container = document.createElement('div');
const dockview = new DockviewComponent(container, {
createComponent(options) {
switch (options.name) {
case 'default':
return new PanelContentPartTest(
options.id,
options.name
);
default:
throw new Error(`unsupported`);
}
},
});
const api = new DockviewApi(dockview);
dockview.layout(1000, 1000);
const panel1 = api.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = api.addPanel({
id: 'panel_2',
component: 'default',
position: {
direction: 'right',
},
});
let query = queryByTestId(container, 'watermark-component');
expect(query).toBeFalsy();
panel1.group.api.setVisible(false);
jest.runAllTicks(); // visibility events check fires on microtask-queue
query = queryByTestId(container, 'watermark-component');
expect(query).toBeFalsy();
panel2.group.api.setVisible(false);
jest.runAllTicks(); // visibility events check fires on microtask-queue
query = queryByTestId(container, 'watermark-component');
expect(query).toBeTruthy();
panel1.group.api.setVisible(true);
jest.runAllTicks(); // visibility events check fires on microtask-queue
query = queryByTestId(container, 'watermark-component');
expect(query).toBeFalsy();
});
expect(api.groups.length).toBe(2);
expect(panel1.group).toBe(panel2.group);
expect(panel3.group).toBe(panel4.group);
test('setVisible on floating group', () => {
const container = document.createElement('div');
expect(panel1.group.api.isVisible).toBeTruthy();
expect(panel2.group.api.isVisible).toBeTruthy();
expect(panel3.group.api.isVisible).toBeTruthy();
expect(panel4.group.api.isVisible).toBeTruthy();
const dockview = new DockviewComponent(container, {
createComponent(options) {
switch (options.name) {
case 'default':
return new PanelContentPartTest(
options.id,
options.name
);
default:
throw new Error(`unsupported`);
}
},
});
const api = new DockviewApi(dockview);
expect(panel1.api.isVisible).toBeFalsy();
expect(panel2.api.isVisible).toBeTruthy();
expect(panel3.api.isVisible).toBeFalsy();
expect(panel4.api.isVisible).toBeTruthy();
dockview.layout(1000, 1000);
// case #1
panel1.group.api.setVisible(false);
const panel1 = api.addPanel({
id: 'panel1',
component: 'default',
});
const panel2 = api.addPanel({
id: 'panel2',
component: 'default',
position: { referencePanel: panel1, direction: 'below' },
});
expect(panel1.group.api.isVisible).toBeFalsy();
expect(panel2.group.api.isVisible).toBeFalsy();
expect(panel3.group.api.isVisible).toBeTruthy();
expect(panel4.group.api.isVisible).toBeTruthy();
const panel3 = api.addPanel({
id: 'panel3',
component: 'default',
position: { referencePanel: panel1, direction: 'below' },
});
expect(panel1.api.isVisible).toBeFalsy();
expect(panel2.api.isVisible).toBeFalsy();
expect(panel3.api.isVisible).toBeFalsy();
expect(panel4.api.isVisible).toBeTruthy();
api.addFloatingGroup(panel2);
expect(panel2.api.location.type).toBe('floating');
// case #2
panel2.api.group.setVisible(false);
expect(panel2.api.isVisible).toBeFalsy();
expect(panel2.api.group.api.isVisible).toBeFalsy();
panel3.group.api.setVisible(false);
panel2.api.group.setVisible(true);
expect(panel2.api.isVisible).toBeTruthy();
expect(panel2.api.group.api.isVisible).toBeTruthy();
expect(panel1.group.api.isVisible).toBeFalsy();
expect(panel2.group.api.isVisible).toBeFalsy();
expect(panel3.group.api.isVisible).toBeFalsy();
expect(panel4.group.api.isVisible).toBeFalsy();
panel2.api.group.setVisible(false);
expect(panel2.api.isVisible).toBeFalsy();
expect(panel2.api.group.api.isVisible).toBeFalsy();
expect(panel1.api.isVisible).toBeFalsy();
expect(panel2.api.isVisible).toBeFalsy();
expect(panel3.api.isVisible).toBeFalsy();
expect(panel4.api.isVisible).toBeFalsy();
panel2.api.group.api.moveTo({
group: panel1.group,
position: 'left',
});
expect(api.groups.length).toBe(3);
expect(panel2.api.isVisible).toBeFalsy();
expect(panel2.api.group.api.isVisible).toBeFalsy();
// case #2
panel3.group.api.setVisible(true);
expect(panel1.group.api.isVisible).toBeFalsy();
expect(panel2.group.api.isVisible).toBeFalsy();
expect(panel3.group.api.isVisible).toBeTruthy();
expect(panel4.group.api.isVisible).toBeTruthy();
expect(panel1.api.isVisible).toBeFalsy();
expect(panel2.api.isVisible).toBeFalsy();
expect(panel3.api.isVisible).toBeFalsy();
expect(panel4.api.isVisible).toBeTruthy();
// case #2
panel1.group.api.setVisible(true);
expect(panel1.group.api.isVisible).toBeTruthy();
expect(panel2.group.api.isVisible).toBeTruthy();
expect(panel3.group.api.isVisible).toBeTruthy();
expect(panel4.group.api.isVisible).toBeTruthy();
expect(panel1.api.isVisible).toBeFalsy();
expect(panel2.api.isVisible).toBeTruthy();
expect(panel3.api.isVisible).toBeFalsy();
expect(panel4.api.isVisible).toBeTruthy();
});
test('setVisible #1', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent(container, {
createComponent(options) {
switch (options.name) {
case 'default':
return new PanelContentPartTest(
options.id,
options.name
);
default:
throw new Error(`unsupported`);
}
},
});
const api = new DockviewApi(dockview);
dockview.layout(1000, 1000);
const panel1 = api.addPanel({
id: 'panel1',
component: 'default',
});
const panel2 = api.addPanel({
id: 'panel2',
component: 'default',
position: { referencePanel: panel1, direction: 'below' },
panel2.api.group.setVisible(true);
expect(panel2.api.isVisible).toBeTruthy();
expect(panel2.api.group.api.isVisible).toBeTruthy();
});
const panel3 = api.addPanel({
id: 'panel3',
component: 'default',
position: { referencePanel: panel1, direction: 'below' },
test('setVisible on popout group should have no effect', async () => {
window.open = () => setupMockWindow();
const container = document.createElement('div');
const dockview = new DockviewComponent(container, {
createComponent(options) {
switch (options.name) {
case 'default':
return new PanelContentPartTest(
options.id,
options.name
);
default:
throw new Error(`unsupported`);
}
},
});
const api = new DockviewApi(dockview);
dockview.layout(1000, 1000);
const panel1 = api.addPanel({
id: 'panel1',
component: 'default',
});
const panel2 = api.addPanel({
id: 'panel2',
component: 'default',
position: { referencePanel: panel1, direction: 'below' },
});
const panel3 = api.addPanel({
id: 'panel3',
component: 'default',
position: { referencePanel: panel1, direction: 'below' },
});
await api.addPopoutGroup(panel2);
expect(panel2.api.location.type).toBe('popout');
expect(panel2.api.group.api.isVisible).toBeTruthy();
panel2.api.group.api.setVisible(false);
expect(panel2.api.group.api.isVisible).toBeTruthy();
});
expect(api.groups.length).toBe(3);
test('opening a popout group from a group that is non visible should automatically make it visible', async () => {
window.open = () => setupMockWindow();
panel1.group.api.setVisible(false);
panel2.group.api.setVisible(false);
panel3.group.api.setVisible(false);
const container = document.createElement('div');
expect(panel1.group.api.isVisible).toBeFalsy();
expect(panel2.group.api.isVisible).toBeFalsy();
expect(panel3.group.api.isVisible).toBeFalsy();
const dockview = new DockviewComponent(container, {
createComponent(options) {
switch (options.name) {
case 'default':
return new PanelContentPartTest(
options.id,
options.name
);
default:
throw new Error(`unsupported`);
}
},
});
const api = new DockviewApi(dockview);
panel1.group.api.setVisible(true);
dockview.layout(1000, 1000);
expect(panel1.group.api.isVisible).toBeTruthy();
expect(panel2.group.api.isVisible).toBeFalsy();
expect(panel3.group.api.isVisible).toBeFalsy();
const panel1 = api.addPanel({
id: 'panel1',
component: 'default',
});
const panel2 = api.addPanel({
id: 'panel2',
component: 'default',
position: { referencePanel: panel1, direction: 'below' },
});
const panel3 = api.addPanel({
id: 'panel3',
component: 'default',
position: { referencePanel: panel1, direction: 'below' },
});
panel2.api.group.api.setVisible(false);
await api.addPopoutGroup(panel2);
expect(panel2.api.location.type).toBe('popout');
expect(panel2.api.group.api.isVisible).toBeTruthy();
});
});
describe('addPanel', () => {
@ -5939,58 +6148,6 @@ describe('dockviewComponent', () => {
expect(api.groups.length).toBe(3);
});
test('that watermark appears when all views are not visible', () => {
jest.useFakeTimers();
const container = document.createElement('div');
const dockview = new DockviewComponent(container, {
createComponent(options) {
switch (options.name) {
case 'default':
return new PanelContentPartTest(
options.id,
options.name
);
default:
throw new Error(`unsupported`);
}
},
});
const api = new DockviewApi(dockview);
dockview.layout(1000, 1000);
const panel1 = api.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = api.addPanel({
id: 'panel_2',
component: 'default',
position: {
direction: 'right',
},
});
let query = queryByTestId(container, 'watermark-component');
expect(query).toBeFalsy();
panel1.group.api.setVisible(false);
jest.runAllTicks(); // visibility events check fires on microtask-queue
query = queryByTestId(container, 'watermark-component');
expect(query).toBeFalsy();
panel2.group.api.setVisible(false);
jest.runAllTicks(); // visibility events check fires on microtask-queue
query = queryByTestId(container, 'watermark-component');
expect(query).toBeTruthy();
panel1.group.api.setVisible(true);
jest.runAllTicks(); // visibility events check fires on microtask-queue
query = queryByTestId(container, 'watermark-component');
expect(query).toBeFalsy();
});
describe('updateOptions', () => {
test('gap', () => {
const container = document.createElement('div');

View File

@ -538,6 +538,32 @@ export class DockviewComponent
this.updateWatermark();
}
override setVisible(panel: DockviewGroupPanel, visible: boolean): void {
switch (panel.api.location.type) {
case 'grid':
super.setVisible(panel, visible);
break;
case 'floating': {
const item = this.floatingGroups.find(
(floatingGroup) => floatingGroup.group === panel
);
if (item) {
item.overlay.setVisible(visible);
panel.api._onDidVisibilityChange.fire({
isVisible: visible,
});
}
break;
}
case 'popout':
console.warn(
'dockview: You cannot hide a group that is in a popout window'
);
break;
}
}
addPopoutGroup(
itemToPopout: DockviewPanel | DockviewGroupPanel,
options?: {

View File

@ -34,6 +34,10 @@
border: 1px solid var(--dv-tab-divider-color);
box-shadow: var(--dv-floating-box-shadow);
&.dv-hidden {
display: none;
}
&.dv-resize-container-dragging {
opacity: 0.5;
}

View File

@ -59,6 +59,8 @@ export class Overlay extends CompositeDisposable {
private verticalAlignment: 'top' | 'bottom' | undefined;
private horiziontalAlignment: 'left' | 'right' | undefined;
private _isVisible: boolean;
set minimumInViewportWidth(value: number | undefined) {
this.options.minimumInViewportWidth = value;
}
@ -71,6 +73,10 @@ export class Overlay extends CompositeDisposable {
return this._element;
}
get isVisible(): boolean {
return this._isVisible;
}
constructor(
private readonly options: AnchoredBox & {
container: HTMLElement;
@ -84,6 +90,7 @@ export class Overlay extends CompositeDisposable {
this.addDisposables(this._onDidChange, this._onDidChangeEnd);
this._element.className = 'dv-resize-container';
this._isVisible = true;
this.setupResize('top');
this.setupResize('bottom');
@ -110,6 +117,16 @@ export class Overlay extends CompositeDisposable {
arialLevelTracker.push(this._element);
}
setVisible(isVisible: boolean): void {
if (isVisible === this.isVisible) {
return;
}
this._isVisible = isVisible;
toggleClass(this.element, 'dv-hidden', !this.isVisible);
}
bringToFront(): void {
arialLevelTracker.push(this._element);
}