mirror of
https://github.com/mathuo/dockview
synced 2025-02-02 14:35:46 +00:00
Merge branch 'master' of https://github.com/mathuo/dockview
This commit is contained in:
commit
5bd1f1e1c6
@ -5,7 +5,7 @@ describe('api', () => {
|
|||||||
let api: PanelApiImpl;
|
let api: PanelApiImpl;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
api = new PanelApiImpl('dummy_id');
|
api = new PanelApiImpl('dummy_id', 'fake-component');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updateParameters', () => {
|
test('updateParameters', () => {
|
||||||
|
@ -31,7 +31,8 @@ describe('groupPanelApi', () => {
|
|||||||
const cut = new DockviewPanelApiImpl(
|
const cut = new DockviewPanelApiImpl(
|
||||||
panel,
|
panel,
|
||||||
group,
|
group,
|
||||||
<DockviewComponent>accessor
|
<DockviewComponent>accessor,
|
||||||
|
'fake-component'
|
||||||
);
|
);
|
||||||
|
|
||||||
cut.setTitle('test_title');
|
cut.setTitle('test_title');
|
||||||
@ -59,7 +60,8 @@ describe('groupPanelApi', () => {
|
|||||||
const cut = new DockviewPanelApiImpl(
|
const cut = new DockviewPanelApiImpl(
|
||||||
<DockviewPanel>groupPanel,
|
<DockviewPanel>groupPanel,
|
||||||
<DockviewGroupPanel>groupViewPanel,
|
<DockviewGroupPanel>groupViewPanel,
|
||||||
<DockviewComponent>accessor
|
<DockviewComponent>accessor,
|
||||||
|
'fake-component'
|
||||||
);
|
);
|
||||||
|
|
||||||
cut.updateParameters({ keyA: 'valueA' });
|
cut.updateParameters({ keyA: 'valueA' });
|
||||||
@ -89,7 +91,8 @@ describe('groupPanelApi', () => {
|
|||||||
const cut = new DockviewPanelApiImpl(
|
const cut = new DockviewPanelApiImpl(
|
||||||
<DockviewPanel>groupPanel,
|
<DockviewPanel>groupPanel,
|
||||||
<DockviewGroupPanel>groupViewPanel,
|
<DockviewGroupPanel>groupViewPanel,
|
||||||
<DockviewComponent>accessor
|
<DockviewComponent>accessor,
|
||||||
|
'fake-component'
|
||||||
);
|
);
|
||||||
|
|
||||||
let events = 0;
|
let events = 0;
|
||||||
|
@ -8,7 +8,7 @@ import { PanelUpdateEvent } from '../../panel/types';
|
|||||||
import { Orientation } from '../../splitview/splitview';
|
import { Orientation } from '../../splitview/splitview';
|
||||||
import { CompositeDisposable } from '../../lifecycle';
|
import { CompositeDisposable } from '../../lifecycle';
|
||||||
import { Emitter } from '../../events';
|
import { Emitter } from '../../events';
|
||||||
import { IDockviewPanel } from '../../dockview/dockviewPanel';
|
import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||||
import { fireEvent } from '@testing-library/dom';
|
import { fireEvent } from '@testing-library/dom';
|
||||||
import { getPanelData } from '../../dnd/dataTransfer';
|
import { getPanelData } from '../../dnd/dataTransfer';
|
||||||
@ -4598,6 +4598,64 @@ describe('dockviewComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('that emits onDidLayoutChange', () => {
|
||||||
|
let dockview: DockviewComponent;
|
||||||
|
let panel1: DockviewPanel;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
|
dockview = new DockviewComponent({
|
||||||
|
parentElement: container,
|
||||||
|
components: {
|
||||||
|
default: PanelContentPartTest,
|
||||||
|
},
|
||||||
|
tabComponents: {
|
||||||
|
test_tab_id: PanelTabPartTest,
|
||||||
|
},
|
||||||
|
orientation: Orientation.HORIZONTAL,
|
||||||
|
});
|
||||||
|
|
||||||
|
panel1 = dockview.addPanel({
|
||||||
|
id: 'panel_1',
|
||||||
|
component: 'default',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('that emits onDidPanelTitleChange and onDidLayoutChange when the panel set a title', () => {
|
||||||
|
const didLayoutChangeHandler = jest.fn();
|
||||||
|
const { dispose: disposeDidLayoutChangeHandler } =
|
||||||
|
dockview.onDidLayoutChange(didLayoutChangeHandler);
|
||||||
|
|
||||||
|
panel1.setTitle('new title');
|
||||||
|
|
||||||
|
jest.runAllTimers();
|
||||||
|
|
||||||
|
expect(didLayoutChangeHandler).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
disposeDidLayoutChangeHandler();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('that emits onDidPanelParametersChange and onDidLayoutChange when the panel updates parameters', () => {
|
||||||
|
const didLayoutChangeHandler = jest.fn();
|
||||||
|
const { dispose: disposeDidLayoutChangeHandler } =
|
||||||
|
dockview.onDidLayoutChange(didLayoutChangeHandler);
|
||||||
|
|
||||||
|
panel1.api.updateParameters({ keyA: 'valueA' });
|
||||||
|
|
||||||
|
jest.runAllTimers();
|
||||||
|
|
||||||
|
expect(didLayoutChangeHandler).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
disposeDidLayoutChangeHandler();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('that setVisible toggles visiblity', () => {
|
test('that setVisible toggles visiblity', () => {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
|
|
||||||
|
@ -9,19 +9,40 @@ import {
|
|||||||
} from '../../dockview/types';
|
} from '../../dockview/types';
|
||||||
import { PanelUpdateEvent, Parameters } from '../../panel/types';
|
import { PanelUpdateEvent, Parameters } from '../../panel/types';
|
||||||
import {
|
import {
|
||||||
|
DockviewGroupLocation,
|
||||||
DockviewGroupPanelModel,
|
DockviewGroupPanelModel,
|
||||||
GroupOptions,
|
GroupOptions,
|
||||||
} from '../../dockview/dockviewGroupPanelModel';
|
} from '../../dockview/dockviewGroupPanelModel';
|
||||||
import { fireEvent } from '@testing-library/dom';
|
import { fireEvent } from '@testing-library/dom';
|
||||||
import { LocalSelectionTransfer, PanelTransfer } from '../../dnd/dataTransfer';
|
import { LocalSelectionTransfer, PanelTransfer } from '../../dnd/dataTransfer';
|
||||||
import { CompositeDisposable } from '../../lifecycle';
|
import { CompositeDisposable } from '../../lifecycle';
|
||||||
import { DockviewPanelApi } from '../../api/dockviewPanelApi';
|
import {
|
||||||
|
ActiveGroupEvent,
|
||||||
|
DockviewPanelApi,
|
||||||
|
GroupChangedEvent,
|
||||||
|
RendererChangedEvent,
|
||||||
|
} from '../../api/dockviewPanelApi';
|
||||||
import { IDockviewPanel } from '../../dockview/dockviewPanel';
|
import { IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||||
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
|
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
|
||||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||||
import { WatermarkRendererInitParameters } from '../../dockview/types';
|
import { WatermarkRendererInitParameters } from '../../dockview/types';
|
||||||
import { createOffsetDragOverEvent } from '../__test_utils__/utils';
|
import { createOffsetDragOverEvent } from '../__test_utils__/utils';
|
||||||
import { OverlayRenderContainer } from '../../overlayRenderContainer';
|
import {
|
||||||
|
DockviewPanelRenderer,
|
||||||
|
OverlayRenderContainer,
|
||||||
|
} from '../../overlayRenderContainer';
|
||||||
|
import { DockviewGroupPanelFloatingChangeEvent } from '../../api/dockviewGroupPanelApi';
|
||||||
|
import { SizeEvent } from '../../api/gridviewPanelApi';
|
||||||
|
import {
|
||||||
|
PanelDimensionChangeEvent,
|
||||||
|
FocusEvent,
|
||||||
|
VisibilityEvent,
|
||||||
|
ActiveEvent,
|
||||||
|
WillFocusEvent,
|
||||||
|
} from '../../api/panelApi';
|
||||||
|
import { Position } from '../../dnd/droptarget';
|
||||||
|
import { Emitter, Event } from '../../events';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
|
|
||||||
enum GroupChangeKind2 {
|
enum GroupChangeKind2 {
|
||||||
ADD_PANEL,
|
ADD_PANEL,
|
||||||
@ -240,12 +261,20 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
let removePanelMock: jest.Mock;
|
let removePanelMock: jest.Mock;
|
||||||
let removeGroupMock: jest.Mock;
|
let removeGroupMock: jest.Mock;
|
||||||
|
|
||||||
|
let panelApi: DockviewPanelApi;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
removePanelMock = jest.fn();
|
removePanelMock = jest.fn();
|
||||||
removeGroupMock = jest.fn();
|
removeGroupMock = jest.fn();
|
||||||
|
|
||||||
options = {};
|
options = {};
|
||||||
|
|
||||||
|
panelApi = fromPartial<DockviewPanelApi>({
|
||||||
|
renderer: 'onlyWhenVisibile',
|
||||||
|
onDidTitleChange: new Emitter().event,
|
||||||
|
onDidParametersChange: new Emitter().event,
|
||||||
|
});
|
||||||
|
|
||||||
dockview = (<Partial<DockviewComponent>>{
|
dockview = (<Partial<DockviewComponent>>{
|
||||||
options: { parentElement: document.createElement('div') },
|
options: { parentElement: document.createElement('div') },
|
||||||
createWatermarkComponent: () => new Watermark(),
|
createWatermarkComponent: () => new Watermark(),
|
||||||
@ -265,15 +294,9 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('panel events are captured during de-serialization', () => {
|
test('panel events are captured during de-serialization', () => {
|
||||||
const panel1 = new TestPanel('panel1', {
|
const panel1 = new TestPanel('panel1', panelApi);
|
||||||
renderer: 'onlyWhenVisibile',
|
const panel2 = new TestPanel('panel2', panelApi);
|
||||||
} as any);
|
const panel3 = new TestPanel('panel3', panelApi);
|
||||||
const panel2 = new TestPanel('panel2', {
|
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any);
|
|
||||||
const panel3 = new TestPanel('panel3', {
|
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
const groupview2 = new DockviewGroupPanel(dockview, 'groupview-2', {
|
const groupview2 = new DockviewGroupPanel(dockview, 'groupview-2', {
|
||||||
panels: [panel1, panel2, panel3],
|
panels: [panel1, panel2, panel3],
|
||||||
@ -357,15 +380,9 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const panel1 = new TestPanel('panel1', {
|
const panel1 = new TestPanel('panel1', panelApi);
|
||||||
renderer: 'onlyWhenVisibile',
|
const panel2 = new TestPanel('panel2', panelApi);
|
||||||
} as any);
|
const panel3 = new TestPanel('panel3', panelApi);
|
||||||
const panel2 = new TestPanel('panel2', {
|
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any);
|
|
||||||
const panel3 = new TestPanel('panel3', {
|
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
expect(events.length).toBe(0);
|
expect(events.length).toBe(0);
|
||||||
|
|
||||||
@ -443,15 +460,9 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('moveToPrevious and moveToNext', () => {
|
test('moveToPrevious and moveToNext', () => {
|
||||||
const panel1 = new TestPanel('panel1', {
|
const panel1 = new TestPanel('panel1', panelApi);
|
||||||
renderer: 'onlyWhenVisibile',
|
const panel2 = new TestPanel('panel2', panelApi);
|
||||||
} as any);
|
const panel3 = new TestPanel('panel3', panelApi);
|
||||||
const panel2 = new TestPanel('panel2', {
|
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any);
|
|
||||||
const panel3 = new TestPanel('panel3', {
|
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
groupview.model.openPanel(panel1);
|
groupview.model.openPanel(panel1);
|
||||||
groupview.model.openPanel(panel2);
|
groupview.model.openPanel(panel2);
|
||||||
@ -495,15 +506,9 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('closeAllPanels with panels', () => {
|
test('closeAllPanels with panels', () => {
|
||||||
const panel1 = new TestPanel('panel1', {
|
const panel1 = new TestPanel('panel1', panelApi);
|
||||||
renderer: 'onlyWhenVisibile',
|
const panel2 = new TestPanel('panel2', panelApi);
|
||||||
} as any);
|
const panel3 = new TestPanel('panel3', panelApi);
|
||||||
const panel2 = new TestPanel('panel2', {
|
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any);
|
|
||||||
const panel3 = new TestPanel('panel3', {
|
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
groupview.model.openPanel(panel1);
|
groupview.model.openPanel(panel1);
|
||||||
groupview.model.openPanel(panel2);
|
groupview.model.openPanel(panel2);
|
||||||
@ -608,25 +613,19 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
.getElementsByClassName('content-container')
|
.getElementsByClassName('content-container')
|
||||||
.item(0)!.childNodes;
|
.item(0)!.childNodes;
|
||||||
|
|
||||||
const panel1 = new TestPanel('id_1', {
|
const panel1 = new TestPanel('id_1', panelApi);
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
cut.openPanel(panel1);
|
cut.openPanel(panel1);
|
||||||
expect(contentContainer.length).toBe(1);
|
expect(contentContainer.length).toBe(1);
|
||||||
expect(contentContainer.item(0)).toBe(panel1.view.content.element);
|
expect(contentContainer.item(0)).toBe(panel1.view.content.element);
|
||||||
|
|
||||||
const panel2 = new TestPanel('id_2', {
|
const panel2 = new TestPanel('id_2', panelApi);
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
cut.openPanel(panel2);
|
cut.openPanel(panel2);
|
||||||
expect(contentContainer.length).toBe(1);
|
expect(contentContainer.length).toBe(1);
|
||||||
expect(contentContainer.item(0)).toBe(panel2.view.content.element);
|
expect(contentContainer.item(0)).toBe(panel2.view.content.element);
|
||||||
|
|
||||||
const panel3 = new TestPanel('id_2', {
|
const panel3 = new TestPanel('id_2', panelApi);
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
cut.openPanel(panel3, { skipSetActive: true });
|
cut.openPanel(panel3, { skipSetActive: true });
|
||||||
expect(contentContainer.length).toBe(1);
|
expect(contentContainer.length).toBe(1);
|
||||||
@ -834,11 +833,7 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
new groupPanelMock() as DockviewGroupPanel
|
new groupPanelMock() as DockviewGroupPanel
|
||||||
);
|
);
|
||||||
|
|
||||||
cut.openPanel(
|
cut.openPanel(new TestPanel('panel1', panelApi));
|
||||||
new TestPanel('panel1', {
|
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any)
|
|
||||||
);
|
|
||||||
|
|
||||||
const element = container
|
const element = container
|
||||||
.getElementsByClassName('content-container')
|
.getElementsByClassName('content-container')
|
||||||
@ -908,16 +903,8 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
new groupPanelMock() as DockviewGroupPanel
|
new groupPanelMock() as DockviewGroupPanel
|
||||||
);
|
);
|
||||||
|
|
||||||
cut.openPanel(
|
cut.openPanel(new TestPanel('panel1', panelApi));
|
||||||
new TestPanel('panel1', {
|
cut.openPanel(new TestPanel('panel2', panelApi));
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any)
|
|
||||||
);
|
|
||||||
cut.openPanel(
|
|
||||||
new TestPanel('panel2', {
|
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any)
|
|
||||||
);
|
|
||||||
|
|
||||||
const element = container
|
const element = container
|
||||||
.getElementsByClassName('content-container')
|
.getElementsByClassName('content-container')
|
||||||
@ -987,16 +974,8 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
new groupPanelMock() as DockviewGroupPanel
|
new groupPanelMock() as DockviewGroupPanel
|
||||||
);
|
);
|
||||||
|
|
||||||
cut.openPanel(
|
cut.openPanel(new TestPanel('panel1', panelApi));
|
||||||
new TestPanel('panel1', {
|
cut.openPanel(new TestPanel('panel2', panelApi));
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any)
|
|
||||||
);
|
|
||||||
cut.openPanel(
|
|
||||||
new TestPanel('panel2', {
|
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any)
|
|
||||||
);
|
|
||||||
|
|
||||||
const element = container
|
const element = container
|
||||||
.getElementsByClassName('content-container')
|
.getElementsByClassName('content-container')
|
||||||
@ -1097,11 +1076,7 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
container.getElementsByClassName('watermark-test-container').length
|
container.getElementsByClassName('watermark-test-container').length
|
||||||
).toBe(1);
|
).toBe(1);
|
||||||
|
|
||||||
cut.openPanel(
|
cut.openPanel(new TestPanel('panel1', panelApi));
|
||||||
new TestPanel('panel1', {
|
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any)
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
container.getElementsByClassName('watermark-test-container').length
|
container.getElementsByClassName('watermark-test-container').length
|
||||||
@ -1111,11 +1086,7 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
.length
|
.length
|
||||||
).toBe(1);
|
).toBe(1);
|
||||||
|
|
||||||
cut.openPanel(
|
cut.openPanel(new TestPanel('panel2', panelApi));
|
||||||
new TestPanel('panel2', {
|
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any)
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
container.getElementsByClassName('watermark-test-container').length
|
container.getElementsByClassName('watermark-test-container').length
|
||||||
@ -1133,11 +1104,7 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
container.getElementsByClassName('watermark-test-container').length
|
container.getElementsByClassName('watermark-test-container').length
|
||||||
).toBe(1);
|
).toBe(1);
|
||||||
|
|
||||||
cut.openPanel(
|
cut.openPanel(new TestPanel('panel1', panelApi));
|
||||||
new TestPanel('panel1', {
|
|
||||||
renderer: 'onlyWhenVisibile',
|
|
||||||
} as any)
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
container.getElementsByClassName('watermark-test-container').length
|
container.getElementsByClassName('watermark-test-container').length
|
||||||
|
@ -34,9 +34,18 @@ describe('dockviewPanel', () => {
|
|||||||
});
|
});
|
||||||
const model = <IDockviewPanelModel>new panelModelMock();
|
const model = <IDockviewPanelModel>new panelModelMock();
|
||||||
|
|
||||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model, {
|
const cut = new DockviewPanel(
|
||||||
|
'fake-id',
|
||||||
|
'fake-component',
|
||||||
|
undefined,
|
||||||
|
accessor,
|
||||||
|
api,
|
||||||
|
group,
|
||||||
|
model,
|
||||||
|
{
|
||||||
renderer: 'onlyWhenVisibile',
|
renderer: 'onlyWhenVisibile',
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
let latestTitle: string | undefined = undefined;
|
let latestTitle: string | undefined = undefined;
|
||||||
|
|
||||||
@ -84,9 +93,18 @@ describe('dockviewPanel', () => {
|
|||||||
});
|
});
|
||||||
const model = <IDockviewPanelModel>new panelModelMock();
|
const model = <IDockviewPanelModel>new panelModelMock();
|
||||||
|
|
||||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model, {
|
const cut = new DockviewPanel(
|
||||||
|
'fake-id',
|
||||||
|
'fake-component',
|
||||||
|
undefined,
|
||||||
|
accessor,
|
||||||
|
api,
|
||||||
|
group,
|
||||||
|
model,
|
||||||
|
{
|
||||||
renderer: 'onlyWhenVisibile',
|
renderer: 'onlyWhenVisibile',
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
cut.init({ title: 'myTitle', params: {} });
|
cut.init({ title: 'myTitle', params: {} });
|
||||||
expect(cut.title).toBe('myTitle');
|
expect(cut.title).toBe('myTitle');
|
||||||
@ -130,9 +148,18 @@ describe('dockviewPanel', () => {
|
|||||||
});
|
});
|
||||||
const model = <IDockviewPanelModel>new panelModelMock();
|
const model = <IDockviewPanelModel>new panelModelMock();
|
||||||
|
|
||||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model, {
|
const cut = new DockviewPanel(
|
||||||
|
'fake-id',
|
||||||
|
'fake-component',
|
||||||
|
undefined,
|
||||||
|
accessor,
|
||||||
|
api,
|
||||||
|
group,
|
||||||
|
model,
|
||||||
|
{
|
||||||
renderer: 'onlyWhenVisibile',
|
renderer: 'onlyWhenVisibile',
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
cut.init({ params: {}, title: 'title' });
|
cut.init({ params: {}, title: 'title' });
|
||||||
|
|
||||||
@ -167,9 +194,18 @@ describe('dockviewPanel', () => {
|
|||||||
});
|
});
|
||||||
const model = <IDockviewPanelModel>new panelModelMock();
|
const model = <IDockviewPanelModel>new panelModelMock();
|
||||||
|
|
||||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model, {
|
const cut = new DockviewPanel(
|
||||||
|
'fake-id',
|
||||||
|
'fake-component',
|
||||||
|
undefined,
|
||||||
|
accessor,
|
||||||
|
api,
|
||||||
|
group,
|
||||||
|
model,
|
||||||
|
{
|
||||||
renderer: 'onlyWhenVisibile',
|
renderer: 'onlyWhenVisibile',
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
expect(cut.params).toEqual(undefined);
|
expect(cut.params).toEqual(undefined);
|
||||||
|
|
||||||
@ -205,9 +241,18 @@ describe('dockviewPanel', () => {
|
|||||||
});
|
});
|
||||||
const model = <IDockviewPanelModel>new panelModelMock();
|
const model = <IDockviewPanelModel>new panelModelMock();
|
||||||
|
|
||||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model, {
|
const cut = new DockviewPanel(
|
||||||
|
'fake-id',
|
||||||
|
'fake-component',
|
||||||
|
undefined,
|
||||||
|
accessor,
|
||||||
|
api,
|
||||||
|
group,
|
||||||
|
model,
|
||||||
|
{
|
||||||
renderer: 'onlyWhenVisibile',
|
renderer: 'onlyWhenVisibile',
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
cut.api.setSize({ height: 123, width: 456 });
|
cut.api.setSize({ height: 123, width: 456 });
|
||||||
|
|
||||||
@ -241,9 +286,18 @@ describe('dockviewPanel', () => {
|
|||||||
});
|
});
|
||||||
const model = <IDockviewPanelModel>new panelModelMock();
|
const model = <IDockviewPanelModel>new panelModelMock();
|
||||||
|
|
||||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model, {
|
const cut = new DockviewPanel(
|
||||||
|
'fake-id',
|
||||||
|
'fake-component',
|
||||||
|
undefined,
|
||||||
|
accessor,
|
||||||
|
api,
|
||||||
|
group,
|
||||||
|
model,
|
||||||
|
{
|
||||||
renderer: 'onlyWhenVisibile',
|
renderer: 'onlyWhenVisibile',
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
cut.init({ params: { a: '1', b: '2' }, title: 'A title' });
|
cut.init({ params: { a: '1', b: '2' }, title: 'A title' });
|
||||||
expect(cut.params).toEqual({ a: '1', b: '2' });
|
expect(cut.params).toEqual({ a: '1', b: '2' });
|
||||||
|
@ -42,7 +42,7 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor(id: string, private readonly accessor: DockviewComponent) {
|
constructor(id: string, private readonly accessor: DockviewComponent) {
|
||||||
super(id);
|
super(id, '__dockviewgroup__');
|
||||||
|
|
||||||
this.addDisposables(this._onDidLocationChange);
|
this.addDisposables(this._onDidLocationChange);
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ export interface DockviewPanelApi
|
|||||||
readonly title: string | undefined;
|
readonly title: string | undefined;
|
||||||
readonly onDidActiveGroupChange: Event<ActiveGroupEvent>;
|
readonly onDidActiveGroupChange: Event<ActiveGroupEvent>;
|
||||||
readonly onDidGroupChange: Event<GroupChangedEvent>;
|
readonly onDidGroupChange: Event<GroupChangedEvent>;
|
||||||
|
readonly onDidTitleChange: Event<TitleEvent>;
|
||||||
readonly onDidRendererChange: Event<RendererChangedEvent>;
|
readonly onDidRendererChange: Event<RendererChangedEvent>;
|
||||||
readonly location: DockviewGroupLocation;
|
readonly location: DockviewGroupLocation;
|
||||||
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent>;
|
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent>;
|
||||||
@ -62,6 +63,7 @@ export class DockviewPanelApiImpl
|
|||||||
implements DockviewPanelApi
|
implements DockviewPanelApi
|
||||||
{
|
{
|
||||||
private _group: DockviewGroupPanel;
|
private _group: DockviewGroupPanel;
|
||||||
|
private _tabComponent: string | undefined;
|
||||||
|
|
||||||
readonly _onDidTitleChange = new Emitter<TitleEvent>();
|
readonly _onDidTitleChange = new Emitter<TitleEvent>();
|
||||||
readonly onDidTitleChange = this._onDidTitleChange.event;
|
readonly onDidTitleChange = this._onDidTitleChange.event;
|
||||||
@ -118,12 +120,20 @@ export class DockviewPanelApiImpl
|
|||||||
return this._group;
|
return this._group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get tabComponent(): string | undefined {
|
||||||
|
return this._tabComponent;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private panel: DockviewPanel,
|
private panel: DockviewPanel,
|
||||||
group: DockviewGroupPanel,
|
group: DockviewGroupPanel,
|
||||||
private readonly accessor: DockviewComponent
|
private readonly accessor: DockviewComponent,
|
||||||
|
component: string,
|
||||||
|
tabComponent?: string
|
||||||
) {
|
) {
|
||||||
super(panel.id);
|
super(panel.id, component);
|
||||||
|
|
||||||
|
this._tabComponent = tabComponent;
|
||||||
|
|
||||||
this.initialize(panel);
|
this.initialize(panel);
|
||||||
|
|
||||||
|
@ -44,8 +44,8 @@ export class GridviewPanelApiImpl
|
|||||||
private readonly _onDidSizeChange = new Emitter<SizeEvent>();
|
private readonly _onDidSizeChange = new Emitter<SizeEvent>();
|
||||||
readonly onDidSizeChange: Event<SizeEvent> = this._onDidSizeChange.event;
|
readonly onDidSizeChange: Event<SizeEvent> = this._onDidSizeChange.event;
|
||||||
|
|
||||||
constructor(id: string, panel?: IPanel) {
|
constructor(id: string, component: string, panel?: IPanel) {
|
||||||
super(id);
|
super(id, component);
|
||||||
|
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
this._onDidConstraintsChangeInternal,
|
this._onDidConstraintsChangeInternal,
|
||||||
|
@ -24,6 +24,7 @@ export interface PanelApi {
|
|||||||
readonly onDidFocusChange: Event<FocusEvent>;
|
readonly onDidFocusChange: Event<FocusEvent>;
|
||||||
readonly onDidVisibilityChange: Event<VisibilityEvent>;
|
readonly onDidVisibilityChange: Event<VisibilityEvent>;
|
||||||
readonly onDidActiveChange: Event<ActiveEvent>;
|
readonly onDidActiveChange: Event<ActiveEvent>;
|
||||||
|
readonly onDidParametersChange: Event<Parameters>;
|
||||||
setActive(): void;
|
setActive(): void;
|
||||||
setVisible(isVisible: boolean): void;
|
setVisible(isVisible: boolean): void;
|
||||||
updateParameters(parameters: Parameters): void;
|
updateParameters(parameters: Parameters): void;
|
||||||
@ -53,6 +54,8 @@ export interface PanelApi {
|
|||||||
readonly height: number;
|
readonly height: number;
|
||||||
|
|
||||||
readonly onWillFocus: Event<WillFocusEvent>;
|
readonly onWillFocus: Event<WillFocusEvent>;
|
||||||
|
|
||||||
|
getParameters<T extends Parameters = Parameters>(): T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WillFocusEvent extends DockviewEvent {
|
export class WillFocusEvent extends DockviewEvent {
|
||||||
@ -70,6 +73,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
|||||||
private _isVisible = true;
|
private _isVisible = true;
|
||||||
private _width = 0;
|
private _width = 0;
|
||||||
private _height = 0;
|
private _height = 0;
|
||||||
|
private _parameters: Parameters = {};
|
||||||
|
|
||||||
private readonly panelUpdatesDisposable = new MutableDisposable();
|
private readonly panelUpdatesDisposable = new MutableDisposable();
|
||||||
|
|
||||||
@ -97,9 +101,9 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
|||||||
readonly _onActiveChange = new Emitter<void>();
|
readonly _onActiveChange = new Emitter<void>();
|
||||||
readonly onActiveChange: Event<void> = this._onActiveChange.event;
|
readonly onActiveChange: Event<void> = this._onActiveChange.event;
|
||||||
|
|
||||||
readonly _onUpdateParameters = new Emitter<Parameters>();
|
readonly _onDidParametersChange = new Emitter<Parameters>();
|
||||||
readonly onUpdateParameters: Event<Parameters> =
|
readonly onDidParametersChange: Event<Parameters> =
|
||||||
this._onUpdateParameters.event;
|
this._onDidParametersChange.event;
|
||||||
|
|
||||||
get isFocused(): boolean {
|
get isFocused(): boolean {
|
||||||
return this._isFocused;
|
return this._isFocused;
|
||||||
@ -121,7 +125,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
|||||||
return this._height;
|
return this._height;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(readonly id: string) {
|
constructor(readonly id: string, readonly component: string) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
@ -145,16 +149,20 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
|||||||
this._onDidActiveChange,
|
this._onDidActiveChange,
|
||||||
this._onWillFocus,
|
this._onWillFocus,
|
||||||
this._onActiveChange,
|
this._onActiveChange,
|
||||||
this._onUpdateParameters,
|
|
||||||
this._onWillFocus,
|
this._onWillFocus,
|
||||||
this._onWillVisibilityChange,
|
this._onWillVisibilityChange,
|
||||||
this._onUpdateParameters
|
this._onDidParametersChange
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getParameters<T extends Parameters = Parameters>(): T {
|
||||||
|
return this._parameters as T;
|
||||||
|
}
|
||||||
|
|
||||||
public initialize(panel: IPanel): void {
|
public initialize(panel: IPanel): void {
|
||||||
this.panelUpdatesDisposable.value = this._onUpdateParameters.event(
|
this.panelUpdatesDisposable.value = this._onDidParametersChange.event(
|
||||||
(parameters) => {
|
(parameters) => {
|
||||||
|
this._parameters = parameters;
|
||||||
panel.update({
|
panel.update({
|
||||||
params: parameters,
|
params: parameters,
|
||||||
});
|
});
|
||||||
@ -171,6 +179,6 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateParameters(parameters: Parameters): void {
|
updateParameters(parameters: Parameters): void {
|
||||||
this._onUpdateParameters.fire(parameters);
|
this._onDidParametersChange.fire(parameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,8 @@ export class PaneviewPanelApiImpl
|
|||||||
this._pane = pane;
|
this._pane = pane;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(id: string) {
|
constructor(id: string, component: string) {
|
||||||
super(id);
|
super(id, component);
|
||||||
|
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
this._onDidExpansionChange,
|
this._onDidExpansionChange,
|
||||||
|
@ -45,8 +45,8 @@ export class SplitviewPanelApiImpl
|
|||||||
this._onDidSizeChange.event;
|
this._onDidSizeChange.event;
|
||||||
//
|
//
|
||||||
|
|
||||||
constructor(id: string) {
|
constructor(id: string, component: string) {
|
||||||
super(id);
|
super(id, component);
|
||||||
|
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
this._onDidConstraintsChangeInternal,
|
this._onDidConstraintsChangeInternal,
|
||||||
|
@ -49,6 +49,8 @@ export class DefaultDockviewDeserialzier implements IPanelDeserializer {
|
|||||||
|
|
||||||
const panel = new DockviewPanel(
|
const panel = new DockviewPanel(
|
||||||
panelId,
|
panelId,
|
||||||
|
contentComponent,
|
||||||
|
tabComponent,
|
||||||
this.accessor,
|
this.accessor,
|
||||||
new DockviewApi(this.accessor),
|
new DockviewApi(this.accessor),
|
||||||
group,
|
group,
|
||||||
|
@ -65,6 +65,7 @@ import {
|
|||||||
OverlayRenderContainer,
|
OverlayRenderContainer,
|
||||||
} from '../overlayRenderContainer';
|
} from '../overlayRenderContainer';
|
||||||
import { PopoutWindow } from '../popoutWindow';
|
import { PopoutWindow } from '../popoutWindow';
|
||||||
|
import { TitleEvent } from '../api/dockviewPanelApi';
|
||||||
|
|
||||||
const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = {
|
const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = {
|
||||||
activationSize: { type: 'pixels', value: 10 },
|
activationSize: { type: 'pixels', value: 10 },
|
||||||
@ -2175,6 +2176,12 @@ export class DockviewComponent
|
|||||||
if (this._onDidActivePanelChange.value !== event.panel) {
|
if (this._onDidActivePanelChange.value !== event.panel) {
|
||||||
this._onDidActivePanelChange.fire(event.panel);
|
this._onDidActivePanelChange.fire(event.panel);
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
Event.any(
|
||||||
|
view.model.onDidPanelTitleChange,
|
||||||
|
view.model.onDidPanelParametersChange
|
||||||
|
)(() => {
|
||||||
|
this._bufferOnDidLayoutChange.fire();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -2204,6 +2211,8 @@ export class DockviewComponent
|
|||||||
|
|
||||||
const panel = new DockviewPanel(
|
const panel = new DockviewPanel(
|
||||||
options.id,
|
options.id,
|
||||||
|
contentComponent,
|
||||||
|
tabComponent,
|
||||||
this,
|
this,
|
||||||
this._api,
|
this._api,
|
||||||
group,
|
group,
|
||||||
|
@ -11,8 +11,13 @@ import {
|
|||||||
IDockviewEvent,
|
IDockviewEvent,
|
||||||
} from '../events';
|
} from '../events';
|
||||||
import { IViewSize } from '../gridview/gridview';
|
import { IViewSize } from '../gridview/gridview';
|
||||||
import { CompositeDisposable } from '../lifecycle';
|
import { CompositeDisposable, IDisposable } from '../lifecycle';
|
||||||
import { IPanel, PanelInitParameters, PanelUpdateEvent } from '../panel/types';
|
import {
|
||||||
|
IPanel,
|
||||||
|
PanelInitParameters,
|
||||||
|
PanelUpdateEvent,
|
||||||
|
Parameters,
|
||||||
|
} from '../panel/types';
|
||||||
import {
|
import {
|
||||||
ContentContainer,
|
ContentContainer,
|
||||||
IContentContainer,
|
IContentContainer,
|
||||||
@ -28,6 +33,7 @@ import { DockviewGroupPanel } from './dockviewGroupPanel';
|
|||||||
import { IDockviewPanel } from './dockviewPanel';
|
import { IDockviewPanel } from './dockviewPanel';
|
||||||
import { IHeaderActionsRenderer } from './options';
|
import { IHeaderActionsRenderer } from './options';
|
||||||
import { OverlayRenderContainer } from '../overlayRenderContainer';
|
import { OverlayRenderContainer } from '../overlayRenderContainer';
|
||||||
|
import { TitleEvent } from '../api/dockviewPanelApi';
|
||||||
|
|
||||||
interface GroupMoveEvent {
|
interface GroupMoveEvent {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
@ -245,6 +251,7 @@ export class DockviewGroupPanelModel
|
|||||||
private _height = 0;
|
private _height = 0;
|
||||||
|
|
||||||
private _panels: IDockviewPanel[] = [];
|
private _panels: IDockviewPanel[] = [];
|
||||||
|
private readonly _panelDisposables = new Map<string, IDisposable>();
|
||||||
|
|
||||||
private readonly _onMove = new Emitter<GroupMoveEvent>();
|
private readonly _onMove = new Emitter<GroupMoveEvent>();
|
||||||
readonly onMove: Event<GroupMoveEvent> = this._onMove.event;
|
readonly onMove: Event<GroupMoveEvent> = this._onMove.event;
|
||||||
@ -271,6 +278,14 @@ export class DockviewGroupPanelModel
|
|||||||
readonly onDidAddPanel: Event<DockviewGroupChangeEvent> =
|
readonly onDidAddPanel: Event<DockviewGroupChangeEvent> =
|
||||||
this._onDidAddPanel.event;
|
this._onDidAddPanel.event;
|
||||||
|
|
||||||
|
private readonly _onDidPanelTitleChange = new Emitter<TitleEvent>();
|
||||||
|
readonly onDidPanelTitleChange: Event<TitleEvent> =
|
||||||
|
this._onDidPanelTitleChange.event;
|
||||||
|
|
||||||
|
private readonly _onDidPanelParametersChange = new Emitter<Parameters>();
|
||||||
|
readonly onDidPanelParametersChange: Event<Parameters> =
|
||||||
|
this._onDidPanelParametersChange.event;
|
||||||
|
|
||||||
private readonly _onDidRemovePanel =
|
private readonly _onDidRemovePanel =
|
||||||
new Emitter<DockviewGroupChangeEvent>();
|
new Emitter<DockviewGroupChangeEvent>();
|
||||||
readonly onDidRemovePanel: Event<DockviewGroupChangeEvent> =
|
readonly onDidRemovePanel: Event<DockviewGroupChangeEvent> =
|
||||||
@ -826,6 +841,12 @@ export class DockviewGroupPanelModel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const disposable = this._panelDisposables.get(panel.id);
|
||||||
|
if (disposable) {
|
||||||
|
disposable.dispose();
|
||||||
|
this._panelDisposables.delete(panel.id);
|
||||||
|
}
|
||||||
|
|
||||||
this._onDidRemovePanel.fire({ panel });
|
this._onDidRemovePanel.fire({ panel });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -856,6 +877,18 @@ export class DockviewGroupPanelModel
|
|||||||
this.updateMru(panel);
|
this.updateMru(panel);
|
||||||
this.panels.splice(index, 0, panel);
|
this.panels.splice(index, 0, panel);
|
||||||
|
|
||||||
|
this._panelDisposables.set(
|
||||||
|
panel.id,
|
||||||
|
new CompositeDisposable(
|
||||||
|
panel.api.onDidTitleChange((event) =>
|
||||||
|
this._onDidPanelTitleChange.fire(event)
|
||||||
|
),
|
||||||
|
panel.api.onDidParametersChange((event) =>
|
||||||
|
this._onDidPanelParametersChange.fire(event)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
this._onDidAddPanel.fire({ panel });
|
this._onDidAddPanel.fire({ panel });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +58,8 @@ export class DockviewPanel
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly id: string,
|
public readonly id: string,
|
||||||
|
component: string,
|
||||||
|
tabComponent: string | undefined,
|
||||||
private readonly accessor: DockviewComponent,
|
private readonly accessor: DockviewComponent,
|
||||||
private readonly containerApi: DockviewApi,
|
private readonly containerApi: DockviewApi,
|
||||||
group: DockviewGroupPanel,
|
group: DockviewGroupPanel,
|
||||||
@ -68,7 +70,13 @@ export class DockviewPanel
|
|||||||
this._renderer = options.renderer;
|
this._renderer = options.renderer;
|
||||||
this._group = group;
|
this._group = group;
|
||||||
|
|
||||||
this.api = new DockviewPanelApiImpl(this, this._group, accessor);
|
this.api = new DockviewPanelApiImpl(
|
||||||
|
this,
|
||||||
|
this._group,
|
||||||
|
accessor,
|
||||||
|
component,
|
||||||
|
tabComponent
|
||||||
|
);
|
||||||
|
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
this.api.onActiveChange(() => {
|
this.api.onActiveChange(() => {
|
||||||
|
@ -140,7 +140,7 @@ export abstract class GridviewPanel<
|
|||||||
},
|
},
|
||||||
api?: T
|
api?: T
|
||||||
) {
|
) {
|
||||||
super(id, component, api ?? <T>new GridviewPanelApiImpl(id));
|
super(id, component, api ?? <T>new GridviewPanelApiImpl(id, component));
|
||||||
|
|
||||||
if (typeof options?.minimumWidth === 'number') {
|
if (typeof options?.minimumWidth === 'number') {
|
||||||
this._minimumWidth = options.minimumWidth;
|
this._minimumWidth = options.minimumWidth;
|
||||||
|
@ -164,7 +164,7 @@ export abstract class PaneviewPanel
|
|||||||
isExpanded: boolean,
|
isExpanded: boolean,
|
||||||
isHeaderVisible: boolean
|
isHeaderVisible: boolean
|
||||||
) {
|
) {
|
||||||
super(id, component, new PaneviewPanelApiImpl(id));
|
super(id, component, new PaneviewPanelApiImpl(id, component));
|
||||||
this.api.pane = this; // TODO cannot use 'this' before 'super'
|
this.api.pane = this; // TODO cannot use 'this' before 'super'
|
||||||
this.api.initialize(this);
|
this.api.initialize(this);
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ export abstract class SplitviewPanel
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor(id: string, componentName: string) {
|
constructor(id: string, componentName: string) {
|
||||||
super(id, componentName, new SplitviewPanelApiImpl(id));
|
super(id, componentName, new SplitviewPanelApiImpl(id, componentName));
|
||||||
|
|
||||||
this.api.initialize(this);
|
this.api.initialize(this);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user