mirror of
https://github.com/mathuo/dockview
synced 2025-02-01 22:15:44 +00:00
code
This commit is contained in:
parent
9fc0603d61
commit
50e483d170
@ -1,6 +1,6 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"semi": false,
|
||||
"semi": true,
|
||||
"singleQuote": true
|
||||
}
|
||||
|
16
.vscode/extensions.json
vendored
16
.vscode/extensions.json
vendored
@ -1,13 +1,9 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": [
|
||||
"esbenp.prettier-vscode"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": [
|
||||
|
||||
]
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": ["esbenp.prettier-vscode"],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": []
|
||||
}
|
@ -13,4 +13,4 @@ module.exports = {
|
||||
'<rootDir>/src/__tests__/**/*.spec.tsx',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setupTests.ts'],
|
||||
}
|
||||
};
|
||||
|
@ -4,7 +4,8 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,27 +1,27 @@
|
||||
import * as React from 'react'
|
||||
import * as React from 'react';
|
||||
// import { LoadFromConfig } from "./loadFromConfig";
|
||||
// import { FromApi } from "./fromApi";
|
||||
// import { PaneDemo } from "./pane";
|
||||
import { TestGrid } from './layout-grid/reactgrid'
|
||||
import { Application } from './layout-grid/application'
|
||||
import { TestGrid } from './layout-grid/reactgrid';
|
||||
import { Application } from './layout-grid/application';
|
||||
|
||||
const options = [
|
||||
// { id: "config", component: LoadFromConfig },
|
||||
// { id: "api", component: FromApi },
|
||||
// { id: "pane", component: PaneDemo },
|
||||
{ id: 'grid', component: Application },
|
||||
]
|
||||
];
|
||||
|
||||
export const App = () => {
|
||||
const [value, setValue] = React.useState<string>(options[0].id)
|
||||
const [value, setValue] = React.useState<string>(options[0].id);
|
||||
|
||||
const onChange = (event: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
setValue(event.target.value)
|
||||
setValue(event.target.value);
|
||||
|
||||
const Component = React.useMemo(
|
||||
() => options.find((o) => o.id === value)?.component,
|
||||
[value]
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -48,5 +48,5 @@ export const App = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react'
|
||||
import * as ReactDOM from 'react-dom'
|
||||
import { App } from './app'
|
||||
import './index.scss'
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { App } from './app';
|
||||
import './index.scss';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('app'))
|
||||
ReactDOM.render(<App />, document.getElementById('app'));
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as React from 'react'
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Orientation,
|
||||
GridviewComponent,
|
||||
@ -6,18 +6,18 @@ import {
|
||||
GridviewReadyEvent,
|
||||
ComponentGridview,
|
||||
IGridviewPanelProps,
|
||||
} from 'splitview'
|
||||
import { TestGrid } from './reactgrid'
|
||||
} from 'splitview';
|
||||
import { TestGrid } from './reactgrid';
|
||||
|
||||
const rootcomponents: {
|
||||
[index: string]: React.FunctionComponent<IGridviewPanelProps>
|
||||
[index: string]: React.FunctionComponent<IGridviewPanelProps>;
|
||||
} = {
|
||||
sidebar: (props: IGridviewPanelProps) => {
|
||||
return (
|
||||
<div style={{ backgroundColor: 'rgb(37,37,38)', height: '100%' }}>
|
||||
sidebar
|
||||
</div>
|
||||
)
|
||||
);
|
||||
},
|
||||
editor: TestGrid,
|
||||
panel: () => {
|
||||
@ -25,12 +25,12 @@ const rootcomponents: {
|
||||
<div style={{ backgroundColor: 'rgb(30,30,30)', height: '100%' }}>
|
||||
panel
|
||||
</div>
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export const Application = () => {
|
||||
const api = React.useRef<ComponentGridview>()
|
||||
const api = React.useRef<ComponentGridview>();
|
||||
|
||||
const onReady = (event: GridviewReadyEvent) => {
|
||||
// event.api.deserialize(rootLayout);
|
||||
@ -38,27 +38,27 @@ export const Application = () => {
|
||||
id: '1',
|
||||
component: 'sidebar',
|
||||
snap: true,
|
||||
})
|
||||
});
|
||||
event.api.addComponent({
|
||||
id: '2',
|
||||
component: 'editor',
|
||||
snap: true,
|
||||
position: { reference: '1', direction: 'right' },
|
||||
priority: LayoutPriority.High,
|
||||
})
|
||||
});
|
||||
|
||||
api.current = event.api as ComponentGridview
|
||||
}
|
||||
api.current = event.api as ComponentGridview;
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const callback = (ev: UIEvent) => {
|
||||
const height = window.innerHeight - 20
|
||||
const width = window.innerWidth
|
||||
const height = window.innerHeight - 20;
|
||||
const width = window.innerWidth;
|
||||
|
||||
api.current?.layout(width, height)
|
||||
}
|
||||
window.addEventListener('resize', callback)
|
||||
callback(undefined)
|
||||
api.current?.layout(width, height);
|
||||
};
|
||||
window.addEventListener('resize', callback);
|
||||
callback(undefined);
|
||||
|
||||
api.current.addComponent({
|
||||
id: '3',
|
||||
@ -66,12 +66,12 @@ export const Application = () => {
|
||||
position: { reference: '2', direction: 'below' },
|
||||
size: 200,
|
||||
snap: true,
|
||||
})
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', callback)
|
||||
}
|
||||
}, [])
|
||||
window.removeEventListener('resize', callback);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<GridviewComponent
|
||||
@ -79,5 +79,5 @@ export const Application = () => {
|
||||
onReady={onReady}
|
||||
orientation={Orientation.HORIZONTAL}
|
||||
/>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react'
|
||||
import { IPanelProps } from 'splitview'
|
||||
import * as React from 'react';
|
||||
import { IPanelProps } from 'splitview';
|
||||
|
||||
export const CustomTab = (props: IPanelProps) => {
|
||||
return <div>hello</div>
|
||||
}
|
||||
return <div>hello</div>;
|
||||
};
|
||||
|
@ -1,25 +1,25 @@
|
||||
import * as React from 'react'
|
||||
import { Api, IPanelProps } from 'splitview'
|
||||
import * as React from 'react';
|
||||
import { Api, IPanelProps } from 'splitview';
|
||||
|
||||
export const Editor = (props: IPanelProps & { layoutApi: Api }) => {
|
||||
const [tabHeight, setTabHeight] = React.useState<number>(0)
|
||||
const [tabHeight, setTabHeight] = React.useState<number>(0);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (props.layoutApi) {
|
||||
setTabHeight(props.layoutApi.getTabHeight())
|
||||
setTabHeight(props.layoutApi.getTabHeight());
|
||||
}
|
||||
}, [props.layoutApi])
|
||||
}, [props.layoutApi]);
|
||||
|
||||
const onTabHeightChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = Number(event.target.value)
|
||||
const value = Number(event.target.value);
|
||||
if (!Number.isNaN(value)) {
|
||||
setTabHeight(value)
|
||||
setTabHeight(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onClick = () => {
|
||||
props.layoutApi.setTabHeight(tabHeight)
|
||||
}
|
||||
props.layoutApi.setTabHeight(tabHeight);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -35,5 +35,5 @@ export const Editor = (props: IPanelProps & { layoutApi: Api }) => {
|
||||
<button onClick={onClick}>Apply</button>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as React from 'react'
|
||||
import * as React from 'react';
|
||||
import {
|
||||
ReactGrid,
|
||||
OnReadyEvent,
|
||||
@ -9,33 +9,33 @@ import {
|
||||
GroupChangeKind,
|
||||
IGridviewPanelProps,
|
||||
TabContextMenuEvent,
|
||||
} from 'splitview'
|
||||
import { CustomTab } from './customTab'
|
||||
import { Editor } from './editorPanel'
|
||||
import { SplitPanel } from './splitPanel'
|
||||
} from 'splitview';
|
||||
import { CustomTab } from './customTab';
|
||||
import { Editor } from './editorPanel';
|
||||
import { SplitPanel } from './splitPanel';
|
||||
|
||||
const components = {
|
||||
inner_component: (props: IPanelProps) => {
|
||||
const _api = React.useRef<Api>()
|
||||
const [api, setApi] = React.useState<Api>()
|
||||
const _api = React.useRef<Api>();
|
||||
const [api, setApi] = React.useState<Api>();
|
||||
|
||||
const onReady = (event: OnReadyEvent) => {
|
||||
_api.current = event.api
|
||||
_api.current = event.api;
|
||||
|
||||
const layout = props.api.getStateKey<object>('layout')
|
||||
const layout = props.api.getStateKey<object>('layout');
|
||||
if (layout) {
|
||||
event.api.deserialize(layout)
|
||||
event.api.deserialize(layout);
|
||||
} else {
|
||||
event.api.addPanelFromComponent({
|
||||
componentName: 'test_component',
|
||||
id: 'inner-1',
|
||||
title: 'inner-1',
|
||||
})
|
||||
});
|
||||
event.api.addPanelFromComponent({
|
||||
componentName: 'test_component',
|
||||
id: 'inner-2',
|
||||
title: 'inner-2',
|
||||
})
|
||||
});
|
||||
event.api.addPanelFromComponent({
|
||||
componentName: 'test_component',
|
||||
id: nextGuid(),
|
||||
@ -44,7 +44,7 @@ const components = {
|
||||
direction: 'within',
|
||||
referencePanel: 'inner-1',
|
||||
},
|
||||
})
|
||||
});
|
||||
event.api.addPanelFromComponent({
|
||||
componentName: 'test_component',
|
||||
id: nextGuid(),
|
||||
@ -53,37 +53,37 @@ const components = {
|
||||
direction: 'within',
|
||||
referencePanel: 'inner-2',
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
setApi(event.api)
|
||||
}
|
||||
setApi(event.api);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const compDis = new CompositeDisposable(
|
||||
props.api.onDidDimensionsChange((event) => {
|
||||
_api.current?.layout(event.width, event.height)
|
||||
_api.current?.layout(event.width, event.height);
|
||||
}),
|
||||
_api.current.onDidLayoutChange((event) => {
|
||||
if (event.kind === GroupChangeKind.LAYOUT_CONFIG_UPDATED) {
|
||||
props.api.setState('layout', _api.current.toJSON())
|
||||
props.api.setState('layout', _api.current.toJSON());
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return () => {
|
||||
compDis.dispose()
|
||||
}
|
||||
}, [])
|
||||
compDis.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!api) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
api.onDidLayoutChange((event) => {
|
||||
// on inner grid changes
|
||||
})
|
||||
}, [api])
|
||||
});
|
||||
}, [api]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -99,16 +99,16 @@ const components = {
|
||||
debug={true}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
},
|
||||
test_component: (props: IPanelProps & { [key: string]: any }) => {
|
||||
const [panelState, setPanelState] = React.useState<{
|
||||
isGroupActive: boolean
|
||||
isPanelVisible: boolean
|
||||
isGroupActive: boolean;
|
||||
isPanelVisible: boolean;
|
||||
}>({
|
||||
isGroupActive: false,
|
||||
isPanelVisible: false,
|
||||
})
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = new CompositeDisposable(
|
||||
@ -116,31 +116,31 @@ const components = {
|
||||
setPanelState((_) => ({
|
||||
..._,
|
||||
isGroupActive: event.isFocused,
|
||||
}))
|
||||
}));
|
||||
}),
|
||||
props.api.onDidChangeVisibility((x) => {
|
||||
setPanelState((_) => ({
|
||||
..._,
|
||||
isPanelVisible: x.isVisible,
|
||||
}))
|
||||
}));
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
props.api.setClosePanelHook(() => {
|
||||
if (confirm('close?')) {
|
||||
return Promise.resolve(ClosePanelResult.CLOSE)
|
||||
return Promise.resolve(ClosePanelResult.CLOSE);
|
||||
}
|
||||
return Promise.resolve(ClosePanelResult.DONT_CLOSE)
|
||||
})
|
||||
return Promise.resolve(ClosePanelResult.DONT_CLOSE);
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposable.dispose()
|
||||
}
|
||||
}, [])
|
||||
disposable.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onClick = () => {
|
||||
props.api.setState('test_key', 'hello')
|
||||
}
|
||||
props.api.setState('test_key', 'hello');
|
||||
};
|
||||
|
||||
const backgroundColor = React.useMemo(
|
||||
() =>
|
||||
@ -149,7 +149,7 @@ const components = {
|
||||
Math.random() * 256
|
||||
)},${Math.floor(Math.random() * 256)})`,
|
||||
[]
|
||||
)
|
||||
);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@ -164,33 +164,33 @@ const components = {
|
||||
<div>{`G:${panelState.isGroupActive} P:${panelState.isPanelVisible}`}</div>
|
||||
<div>{props.text || '-'}</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
},
|
||||
editor: Editor,
|
||||
split_panel: SplitPanel,
|
||||
}
|
||||
};
|
||||
|
||||
const tabComponents = {
|
||||
default: CustomTab,
|
||||
}
|
||||
};
|
||||
|
||||
const nextGuid = (() => {
|
||||
let counter = 0
|
||||
return () => 'panel_' + (counter++).toString()
|
||||
})()
|
||||
let counter = 0;
|
||||
return () => 'panel_' + (counter++).toString();
|
||||
})();
|
||||
|
||||
export const TestGrid = (props: IGridviewPanelProps) => {
|
||||
const _api = React.useRef<Api>()
|
||||
const [api, setApi] = React.useState<Api>()
|
||||
const _api = React.useRef<Api>();
|
||||
const [api, setApi] = React.useState<Api>();
|
||||
|
||||
const onReady = (event: OnReadyEvent) => {
|
||||
_api.current = event.api
|
||||
setApi(event.api)
|
||||
}
|
||||
_api.current = event.api;
|
||||
setApi(event.api);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!api) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const panelReference = api.addPanelFromComponent({
|
||||
@ -198,24 +198,24 @@ export const TestGrid = (props: IGridviewPanelProps) => {
|
||||
id: nextGuid(),
|
||||
title: 'Item 1',
|
||||
params: { text: 'how low?' },
|
||||
})
|
||||
});
|
||||
api.addPanelFromComponent({
|
||||
componentName: 'test_component',
|
||||
id: 'item2',
|
||||
title: 'Item 2',
|
||||
})
|
||||
});
|
||||
api.addPanelFromComponent({
|
||||
componentName: 'split_panel',
|
||||
id: nextGuid(),
|
||||
title: 'Item 3 with a long title',
|
||||
})
|
||||
});
|
||||
api.addPanelFromComponent({
|
||||
componentName: 'test_component',
|
||||
id: nextGuid(),
|
||||
title: 'Item 3',
|
||||
position: { direction: 'below', referencePanel: 'item2' },
|
||||
suppressClosable: true,
|
||||
})
|
||||
});
|
||||
|
||||
// setInterval(() => {
|
||||
// panelReference.update({ params: { text: `Tick ${Date.now()}` } });
|
||||
@ -223,38 +223,38 @@ export const TestGrid = (props: IGridviewPanelProps) => {
|
||||
// }, 1000);
|
||||
|
||||
api.addDndHandle('text/plain', (ev) => {
|
||||
const { event } = ev
|
||||
const { event } = ev;
|
||||
|
||||
return {
|
||||
id: 'yellow',
|
||||
componentName: 'test_component',
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
api.addDndHandle('Files', (ev) => {
|
||||
const { event } = ev
|
||||
const { event } = ev;
|
||||
|
||||
ev.event.event.preventDefault()
|
||||
ev.event.event.preventDefault();
|
||||
|
||||
return {
|
||||
id: Date.now().toString(),
|
||||
title: event.event.dataTransfer.files[0].name,
|
||||
componentName: 'test_component',
|
||||
}
|
||||
})
|
||||
}, [api])
|
||||
};
|
||||
});
|
||||
}, [api]);
|
||||
|
||||
const onAdd = () => {
|
||||
const id = nextGuid()
|
||||
const id = nextGuid();
|
||||
api.addPanelFromComponent({
|
||||
componentName: 'test_component',
|
||||
id,
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onAddEmpty = () => {
|
||||
api.addEmptyGroup()
|
||||
}
|
||||
api.addEmptyGroup();
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
// const callback = (ev: UIEvent) => {
|
||||
@ -269,68 +269,68 @@ export const TestGrid = (props: IGridviewPanelProps) => {
|
||||
props.api.setConstraints({
|
||||
minimumWidth: () => _api.current.minimumWidth,
|
||||
minimumHeight: () => _api.current.minimumHeight,
|
||||
})
|
||||
});
|
||||
|
||||
const disposable = new CompositeDisposable(
|
||||
_api.current.onDidLayoutChange((event) => {
|
||||
console.log(event.kind)
|
||||
console.log(event.kind);
|
||||
}),
|
||||
props.api.onDidDimensionsChange((event) => {
|
||||
const { width, height } = event
|
||||
_api.current.layout(width, height - 20)
|
||||
const { width, height } = event;
|
||||
_api.current.layout(width, height - 20);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return () => {
|
||||
disposable.dispose()
|
||||
disposable.dispose();
|
||||
// window.removeEventListener("resize", callback);
|
||||
}
|
||||
}, [])
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onConfig = () => {
|
||||
const data = api.toJSON()
|
||||
const stringData = JSON.stringify(data, null, 4)
|
||||
console.log(stringData)
|
||||
localStorage.setItem('layout', stringData)
|
||||
}
|
||||
const data = api.toJSON();
|
||||
const stringData = JSON.stringify(data, null, 4);
|
||||
console.log(stringData);
|
||||
localStorage.setItem('layout', stringData);
|
||||
};
|
||||
|
||||
const onLoad = async () => {
|
||||
const didClose = await api.closeAllGroups()
|
||||
const didClose = await api.closeAllGroups();
|
||||
if (!didClose) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const data = localStorage.getItem('layout')
|
||||
const data = localStorage.getItem('layout');
|
||||
if (data) {
|
||||
const jsonData = JSON.parse(data)
|
||||
api.deserialize(jsonData)
|
||||
const jsonData = JSON.parse(data);
|
||||
api.deserialize(jsonData);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onClear = () => {
|
||||
api.closeAllGroups()
|
||||
}
|
||||
api.closeAllGroups();
|
||||
};
|
||||
|
||||
const onNextGroup = () => {
|
||||
api.moveToNext({ includePanel: true })
|
||||
}
|
||||
api.moveToNext({ includePanel: true });
|
||||
};
|
||||
|
||||
const onPreviousGroup = () => {
|
||||
api.moveToPrevious({ includePanel: true })
|
||||
}
|
||||
api.moveToPrevious({ includePanel: true });
|
||||
};
|
||||
|
||||
const onNextPanel = () => {
|
||||
api.activeGroup?.moveToNext()
|
||||
}
|
||||
api.activeGroup?.moveToNext();
|
||||
};
|
||||
|
||||
const onPreviousPanel = () => {
|
||||
api.activeGroup?.moveToPrevious()
|
||||
}
|
||||
api.activeGroup?.moveToPrevious();
|
||||
};
|
||||
|
||||
const dragRef = React.useRef<HTMLDivElement>()
|
||||
const dragRef = React.useRef<HTMLDivElement>();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!api) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
api.createDragTarget(
|
||||
{ element: dragRef.current, content: 'drag me' },
|
||||
@ -338,12 +338,12 @@ export const TestGrid = (props: IGridviewPanelProps) => {
|
||||
id: 'yellow',
|
||||
componentName: 'test_component',
|
||||
})
|
||||
)
|
||||
}, [api])
|
||||
);
|
||||
}, [api]);
|
||||
|
||||
const onDragStart = (event: React.DragEvent) => {
|
||||
event.dataTransfer.setData('text/plain', 'Panel2')
|
||||
}
|
||||
event.dataTransfer.setData('text/plain', 'Panel2');
|
||||
};
|
||||
|
||||
const onAddEditor = () => {
|
||||
api.addPanelFromComponent({
|
||||
@ -351,15 +351,15 @@ export const TestGrid = (props: IGridviewPanelProps) => {
|
||||
componentName: 'editor',
|
||||
tabComponentName: 'default',
|
||||
params: { layoutApi: api },
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onTabContextMenu = React.useMemo(
|
||||
() => (event: TabContextMenuEvent) => {
|
||||
console.log(event)
|
||||
console.log(event);
|
||||
},
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -421,5 +421,5 @@ export const TestGrid = (props: IGridviewPanelProps) => {
|
||||
onTabContextMenu={onTabContextMenu}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as React from 'react'
|
||||
import * as React from 'react';
|
||||
import {
|
||||
CompositeDisposable,
|
||||
IPanelProps,
|
||||
@ -6,65 +6,65 @@ import {
|
||||
Orientation,
|
||||
SplitviewFacade,
|
||||
SplitviewReadyEvent,
|
||||
} from 'splitview'
|
||||
import { SplitViewComponent } from 'splitview'
|
||||
} from 'splitview';
|
||||
import { SplitViewComponent } from 'splitview';
|
||||
|
||||
const components = {
|
||||
default1: (props: ISplitviewPanelProps) => {
|
||||
const [focused, setFocused] = React.useState<boolean>(false)
|
||||
const [focused, setFocused] = React.useState<boolean>(false);
|
||||
React.useEffect(() => {
|
||||
const disposable = new CompositeDisposable(
|
||||
props.api.onDidFocusChange((event) => {
|
||||
setFocused(event.isFocused)
|
||||
setFocused(event.isFocused);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return () => {
|
||||
disposable.dispose()
|
||||
}
|
||||
}, [])
|
||||
disposable.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
>{`component [isFocused: ${focused}]`}</div>
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export const SplitPanel = (props: IPanelProps) => {
|
||||
const api = React.useRef<SplitviewFacade>()
|
||||
const api = React.useRef<SplitviewFacade>();
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = new CompositeDisposable(
|
||||
props.api.onDidDimensionsChange((event) => {
|
||||
api.current?.layout(event.width, event.height - 20)
|
||||
api.current?.layout(event.width, event.height - 20);
|
||||
}),
|
||||
api.current.onChange((event) => {
|
||||
props.api.setState('sview_layout', api.current.toJSON())
|
||||
props.api.setState('sview_layout', api.current.toJSON());
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return () => {
|
||||
disposable.dispose()
|
||||
}
|
||||
}, [])
|
||||
disposable.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onReady = (event: SplitviewReadyEvent) => {
|
||||
const existingLayout = props.api.getStateKey('sview_layout')
|
||||
const existingLayout = props.api.getStateKey('sview_layout');
|
||||
|
||||
if (existingLayout) {
|
||||
event.api.deserialize(existingLayout)
|
||||
event.api.deserialize(existingLayout);
|
||||
} else {
|
||||
event.api.addFromComponent({ id: '1', component: 'default1' })
|
||||
event.api.addFromComponent({ id: '2', component: 'default1' })
|
||||
event.api.addFromComponent({ id: '1', component: 'default1' });
|
||||
event.api.addFromComponent({ id: '2', component: 'default1' });
|
||||
}
|
||||
api.current = event.api
|
||||
}
|
||||
api.current = event.api;
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
props.api.setState('sview_layout', api.current.toJSON())
|
||||
}
|
||||
props.api.setState('sview_layout', api.current.toJSON());
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -84,5 +84,5 @@ export const SplitPanel = (props: IPanelProps) => {
|
||||
orientation={Orientation.VERTICAL}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
var path = require('path')
|
||||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: path.resolve(__dirname, 'src/index.tsx'),
|
||||
@ -50,4 +50,4 @@ module.exports = {
|
||||
contentBase: path.resolve(__dirname, 'public'),
|
||||
publicPath: '/dist',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
const gulp = require('gulp')
|
||||
const buildfile = require('../../scripts/build')
|
||||
const package = require('./package')
|
||||
const gulp = require('gulp');
|
||||
const buildfile = require('../../scripts/build');
|
||||
const package = require('./package');
|
||||
|
||||
buildfile.build({ tsconfig: './tsconfig.build.json', package })
|
||||
buildfile.build({ tsconfig: './tsconfig.build.json', package });
|
||||
|
||||
gulp.task('run', gulp.series(['clean', 'esm', 'sass']))
|
||||
gulp.task('run', gulp.series(['clean', 'esm', 'sass']));
|
||||
|
@ -1,121 +1,121 @@
|
||||
import * as React from 'react'
|
||||
import { Orientation } from 'splitview'
|
||||
import { IViewWithReactComponent } from '../splitview'
|
||||
import * as React from 'react';
|
||||
import { Orientation } from 'splitview';
|
||||
import { IViewWithReactComponent } from '../splitview';
|
||||
|
||||
// component view
|
||||
|
||||
export interface IPaneComponentProps extends IViewWithReactComponent {
|
||||
setExpanded(expanded: boolean): void
|
||||
orientation: Orientation
|
||||
size: number
|
||||
orthogonalSize: number
|
||||
userprops?: { [index: string]: any }
|
||||
setExpanded(expanded: boolean): void;
|
||||
orientation: Orientation;
|
||||
size: number;
|
||||
orthogonalSize: number;
|
||||
userprops?: { [index: string]: any };
|
||||
}
|
||||
|
||||
export interface IPaneComponentRef {
|
||||
layout: (size: number, orthogonalSize: number) => void
|
||||
layout: (size: number, orthogonalSize: number) => void;
|
||||
}
|
||||
|
||||
export type PaneComponent = React.ForwardRefRenderFunction<
|
||||
IPaneComponentRef,
|
||||
IPaneComponentProps
|
||||
>
|
||||
>;
|
||||
|
||||
export interface IPaneHeaderComponentProps extends IViewWithReactComponent {
|
||||
setExpanded(expanded: boolean): void
|
||||
isExpanded: boolean
|
||||
userprops?: { [index: string]: any }
|
||||
setExpanded(expanded: boolean): void;
|
||||
isExpanded: boolean;
|
||||
userprops?: { [index: string]: any };
|
||||
}
|
||||
|
||||
export type PaneHeaderComponent = React.ForwardRefRenderFunction<
|
||||
{},
|
||||
IPaneHeaderComponentProps
|
||||
>
|
||||
>;
|
||||
|
||||
// component view facade
|
||||
|
||||
export interface IPaneRootProps {
|
||||
component: PaneComponent
|
||||
props: {}
|
||||
component: PaneComponent;
|
||||
props: {};
|
||||
}
|
||||
|
||||
export interface IPaneHeaderRootProps {
|
||||
component: PaneHeaderComponent
|
||||
props: {}
|
||||
component: PaneHeaderComponent;
|
||||
props: {};
|
||||
}
|
||||
|
||||
export interface IPaneRootRef extends IPaneComponentRef {
|
||||
updateProps: (props: Partial<IPaneComponentProps>) => void
|
||||
updateProps: (props: Partial<IPaneComponentProps>) => void;
|
||||
}
|
||||
|
||||
export interface IPaneHeaderRootRef {
|
||||
updateProps: (props: Partial<IPaneHeaderComponentProps>) => void
|
||||
updateProps: (props: Partial<IPaneHeaderComponentProps>) => void;
|
||||
}
|
||||
|
||||
export const PaneRoot = React.forwardRef(
|
||||
(props: IPaneRootProps, facadeRef: React.Ref<IPaneRootRef>) => {
|
||||
const ref = React.useRef<IPaneComponentRef>()
|
||||
const ref = React.useRef<IPaneComponentRef>();
|
||||
const [facadeProps, setFacadeProps] = React.useState<
|
||||
IPaneComponentProps
|
||||
>()
|
||||
>();
|
||||
|
||||
React.useImperativeHandle(
|
||||
facadeRef,
|
||||
() => {
|
||||
return {
|
||||
updateProps: (props) => {
|
||||
setFacadeProps((_props) => ({ ..._props, ...props }))
|
||||
setFacadeProps((_props) => ({ ..._props, ...props }));
|
||||
},
|
||||
layout: (size, orthogonalSize) => {
|
||||
ref.current?.layout(size, orthogonalSize)
|
||||
ref.current?.layout(size, orthogonalSize);
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
[ref]
|
||||
)
|
||||
);
|
||||
|
||||
const Component = React.useMemo(
|
||||
() => React.forwardRef(props.component),
|
||||
[props.component]
|
||||
)
|
||||
);
|
||||
|
||||
const _props = React.useMemo(
|
||||
() => ({ ...props.props, ...facadeProps, ref }),
|
||||
[props.props, facadeProps]
|
||||
)
|
||||
);
|
||||
|
||||
return React.createElement(Component, _props)
|
||||
return React.createElement(Component, _props);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export const PaneHeaderRoot = React.forwardRef(
|
||||
(props: IPaneHeaderRootProps, facadeRef: React.Ref<IPaneHeaderRootRef>) => {
|
||||
const [facadeProps, setFacadeProps] = React.useState<
|
||||
IPaneHeaderComponentProps
|
||||
>()
|
||||
>();
|
||||
|
||||
React.useImperativeHandle(
|
||||
facadeRef,
|
||||
() => {
|
||||
return {
|
||||
updateProps: (props) => {
|
||||
setFacadeProps((_props) => ({ ..._props, ...props }))
|
||||
setFacadeProps((_props) => ({ ..._props, ...props }));
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
const Component = React.useMemo(
|
||||
() => React.forwardRef(props.component),
|
||||
[props.component]
|
||||
)
|
||||
);
|
||||
|
||||
const _props = React.useMemo(
|
||||
() => ({ ...props.props, ...facadeProps }),
|
||||
[props.props, facadeProps]
|
||||
)
|
||||
);
|
||||
|
||||
return React.createElement(Component, _props)
|
||||
return React.createElement(Component, _props);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -1,67 +1,67 @@
|
||||
import * as React from 'react'
|
||||
import { IViewWithReactComponent } from '../splitview'
|
||||
import * as React from 'react';
|
||||
import { IViewWithReactComponent } from '../splitview';
|
||||
|
||||
// component view
|
||||
|
||||
export interface IViewComponentProps
|
||||
extends Omit<IViewWithReactComponent, 'component'> {
|
||||
userprops?: { [index: string]: any }
|
||||
userprops?: { [index: string]: any };
|
||||
}
|
||||
|
||||
export interface IViewComponentRef {
|
||||
layout: (size: number, orthogonalSize: number) => void
|
||||
layout: (size: number, orthogonalSize: number) => void;
|
||||
}
|
||||
|
||||
export type ViewComponent = React.ForwardRefRenderFunction<
|
||||
IViewComponentRef,
|
||||
IViewComponentProps
|
||||
>
|
||||
>;
|
||||
|
||||
// component view facade
|
||||
|
||||
export interface IViewRootProps {
|
||||
component: ViewComponent
|
||||
props: {}
|
||||
component: ViewComponent;
|
||||
props: {};
|
||||
}
|
||||
|
||||
export interface IViewRootRef extends IViewComponentRef {
|
||||
updateProps: (props: Partial<IViewComponentProps>) => void
|
||||
updateProps: (props: Partial<IViewComponentProps>) => void;
|
||||
}
|
||||
|
||||
export const ViewRoot = React.forwardRef(
|
||||
(props: IViewRootProps, facadeRef: React.Ref<IViewRootRef>) => {
|
||||
const ref = React.useRef<IViewComponentRef>()
|
||||
const ref = React.useRef<IViewComponentRef>();
|
||||
const [facadeProps, setFacadeProps] = React.useState<
|
||||
IViewComponentProps
|
||||
>()
|
||||
>();
|
||||
|
||||
React.useImperativeHandle(
|
||||
facadeRef,
|
||||
() => {
|
||||
return {
|
||||
updateProps: (props) => {
|
||||
setFacadeProps((_props) => ({ ..._props, ...props }))
|
||||
setFacadeProps((_props) => ({ ..._props, ...props }));
|
||||
},
|
||||
layout: (size, orthogonalSize) => {
|
||||
ref.current?.layout(size, orthogonalSize)
|
||||
ref.current?.layout(size, orthogonalSize);
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
[ref]
|
||||
)
|
||||
);
|
||||
|
||||
const Component = React.useMemo(
|
||||
() => React.forwardRef(props.component),
|
||||
[props.component]
|
||||
)
|
||||
);
|
||||
|
||||
const _props = React.useMemo(
|
||||
() => ({ ...props.props, ...facadeProps, ref }),
|
||||
[props.props, facadeProps]
|
||||
)
|
||||
);
|
||||
|
||||
return React.createElement(Component, _props)
|
||||
return React.createElement(Component, _props);
|
||||
|
||||
// return <Component ref={ref} {...props.props} {...facadeProps} />;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
export * from './splitview'
|
||||
export * from './paneview'
|
||||
export * from './bridge/view'
|
||||
export * from './panel/view'
|
||||
export * from './bridge/pane'
|
||||
export * from './panel/pane'
|
||||
export * from './splitview';
|
||||
export * from './paneview';
|
||||
export * from './bridge/view';
|
||||
export * from './panel/view';
|
||||
export * from './bridge/pane';
|
||||
export * from './panel/pane';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react'
|
||||
import * as ReactDOM from 'react-dom'
|
||||
import { Pane, IDisposable } from 'splitview'
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { Pane, IDisposable } from 'splitview';
|
||||
|
||||
import {
|
||||
PaneComponent,
|
||||
@ -9,47 +9,47 @@ import {
|
||||
PaneHeaderComponent,
|
||||
PaneHeaderRoot,
|
||||
IPaneHeaderRootRef,
|
||||
} from '../bridge/pane'
|
||||
import { IViewWithReactComponent } from '../splitview'
|
||||
import { IPaneWithReactComponent } from '../paneview'
|
||||
} from '../bridge/pane';
|
||||
import { IViewWithReactComponent } from '../splitview';
|
||||
import { IPaneWithReactComponent } from '../paneview';
|
||||
|
||||
export class PaneReact extends Pane {
|
||||
public readonly id: string
|
||||
public readonly id: string;
|
||||
|
||||
private bodyDisposable: IDisposable
|
||||
private headerDisposable: IDisposable
|
||||
private bodyRef: IPaneRootRef
|
||||
private headerRef: IPaneHeaderRootRef
|
||||
private disposable: IDisposable
|
||||
private bodyDisposable: IDisposable;
|
||||
private headerDisposable: IDisposable;
|
||||
private bodyRef: IPaneRootRef;
|
||||
private headerRef: IPaneHeaderRootRef;
|
||||
private disposable: IDisposable;
|
||||
|
||||
constructor(
|
||||
private readonly view: IPaneWithReactComponent,
|
||||
private readonly bodyComponent: PaneComponent,
|
||||
private readonly options: {
|
||||
headerName: string
|
||||
addPortal: (portal: React.ReactPortal) => IDisposable
|
||||
headerComponent?: PaneHeaderComponent
|
||||
headerName: string;
|
||||
addPortal: (portal: React.ReactPortal) => IDisposable;
|
||||
headerComponent?: PaneHeaderComponent;
|
||||
}
|
||||
) {
|
||||
super({ isExpanded: view.isExpanded })
|
||||
this.layout = this.layout.bind(this)
|
||||
this.onDidChange = this.onDidChange.bind(this)
|
||||
this.setRef = this.setRef.bind(this)
|
||||
this.setHeaderRef = this.setHeaderRef.bind(this)
|
||||
this.setExpanded = this.setExpanded.bind(this)
|
||||
super({ isExpanded: view.isExpanded });
|
||||
this.layout = this.layout.bind(this);
|
||||
this.onDidChange = this.onDidChange.bind(this);
|
||||
this.setRef = this.setRef.bind(this);
|
||||
this.setHeaderRef = this.setHeaderRef.bind(this);
|
||||
this.setExpanded = this.setExpanded.bind(this);
|
||||
|
||||
this.id = view.id
|
||||
this.id = view.id;
|
||||
|
||||
this.minimumSize = view.minimumSize
|
||||
this.maximumSize = view.maximumSize
|
||||
this.minimumSize = view.minimumSize;
|
||||
this.maximumSize = view.maximumSize;
|
||||
|
||||
this.render()
|
||||
this.render();
|
||||
}
|
||||
|
||||
public renderBody(element: HTMLElement) {
|
||||
if (this.bodyDisposable) {
|
||||
this.bodyDisposable.dispose()
|
||||
this.bodyDisposable = undefined
|
||||
this.bodyDisposable.dispose();
|
||||
this.bodyDisposable = undefined;
|
||||
}
|
||||
|
||||
const bodyPortal = ReactDOM.createPortal(
|
||||
@ -65,21 +65,21 @@ export class PaneReact extends Pane {
|
||||
}}
|
||||
/>,
|
||||
element
|
||||
)
|
||||
this.bodyDisposable = this.options.addPortal(bodyPortal)
|
||||
);
|
||||
this.bodyDisposable = this.options.addPortal(bodyPortal);
|
||||
}
|
||||
|
||||
public renderHeader(element: HTMLElement) {
|
||||
if (this.headerDisposable) {
|
||||
this.headerDisposable.dispose()
|
||||
this.disposable?.dispose()
|
||||
this.headerDisposable = undefined
|
||||
this.headerDisposable.dispose();
|
||||
this.disposable?.dispose();
|
||||
this.headerDisposable = undefined;
|
||||
}
|
||||
|
||||
if (this.options.headerComponent) {
|
||||
this.disposable = this.onDidChangeExpansionState((isExpanded) => {
|
||||
this.headerRef?.updateProps({ isExpanded })
|
||||
})
|
||||
this.headerRef?.updateProps({ isExpanded });
|
||||
});
|
||||
|
||||
const headerPortal = ReactDOM.createPortal(
|
||||
<PaneHeaderRoot
|
||||
@ -94,50 +94,50 @@ export class PaneReact extends Pane {
|
||||
}}
|
||||
/>,
|
||||
element
|
||||
)
|
||||
this.headerDisposable = this.options.addPortal(headerPortal)
|
||||
);
|
||||
this.headerDisposable = this.options.addPortal(headerPortal);
|
||||
} else {
|
||||
element.textContent = this.options.headerName
|
||||
element.textContent = this.options.headerName;
|
||||
element.onclick = () => {
|
||||
this.setExpanded(!this.isExpanded())
|
||||
}
|
||||
this.setExpanded(!this.isExpanded());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public update(view: IViewWithReactComponent) {
|
||||
this.minimumSize = view.minimumSize
|
||||
this.maximumSize = view.maximumSize
|
||||
this.minimumSize = view.minimumSize;
|
||||
this.maximumSize = view.maximumSize;
|
||||
|
||||
this.render()
|
||||
this.render();
|
||||
|
||||
this.bodyRef?.updateProps({
|
||||
minimumSize: this.minimumSize,
|
||||
maximumSize: this.maximumSize,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
public layout(size: number, orthogonalSize: number) {
|
||||
super.layout(size, orthogonalSize)
|
||||
this.orthogonalSize = orthogonalSize
|
||||
this.bodyRef?.layout(size, orthogonalSize)
|
||||
this.bodyRef?.updateProps({ size, orthogonalSize })
|
||||
super.layout(size, orthogonalSize);
|
||||
this.orthogonalSize = orthogonalSize;
|
||||
this.bodyRef?.layout(size, orthogonalSize);
|
||||
this.bodyRef?.updateProps({ size, orthogonalSize });
|
||||
}
|
||||
|
||||
private setRef(ref: IPaneRootRef) {
|
||||
this.bodyRef = ref
|
||||
this.bodyRef = ref;
|
||||
}
|
||||
|
||||
private setHeaderRef(ref: IPaneRootRef) {
|
||||
this.headerRef = ref
|
||||
this.headerRef = ref;
|
||||
this.headerRef?.updateProps({
|
||||
isExpanded: this.isExpanded(),
|
||||
setExpanded: this.setExpanded,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.bodyDisposable?.dispose()
|
||||
this.headerDisposable?.dispose()
|
||||
this.disposable?.dispose()
|
||||
this.bodyDisposable?.dispose();
|
||||
this.headerDisposable?.dispose();
|
||||
this.disposable?.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,30 @@
|
||||
import * as React from 'react'
|
||||
import * as ReactDOM from 'react-dom'
|
||||
import { IView, Emitter } from 'splitview'
|
||||
import { IViewRootRef, ViewComponent, ViewRoot } from '../bridge/view'
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { IView, Emitter } from 'splitview';
|
||||
import { IViewRootRef, ViewComponent, ViewRoot } from '../bridge/view';
|
||||
|
||||
import { IViewWithReactComponent } from '../splitview'
|
||||
import { IViewWithReactComponent } from '../splitview';
|
||||
|
||||
export class ReactRenderView implements IView {
|
||||
private ref: IViewRootRef
|
||||
private disposable: { dispose: () => void }
|
||||
private ref: IViewRootRef;
|
||||
private disposable: { dispose: () => void };
|
||||
|
||||
public readonly id: string
|
||||
private readonly component: ViewComponent
|
||||
public readonly props: {}
|
||||
public readonly id: string;
|
||||
private readonly component: ViewComponent;
|
||||
public readonly props: {};
|
||||
|
||||
public element: HTMLElement
|
||||
public minimumSize: number
|
||||
public maximumSize: number
|
||||
public snapSize: number
|
||||
public size: number
|
||||
public element: HTMLElement;
|
||||
public minimumSize: number;
|
||||
public maximumSize: number;
|
||||
public snapSize: number;
|
||||
public size: number;
|
||||
|
||||
private readonly _onDidChange = new Emitter<number | undefined>()
|
||||
public readonly onDidChange = this._onDidChange.event
|
||||
private readonly _onDidChange = new Emitter<number | undefined>();
|
||||
public readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
private _rendered = false
|
||||
private _size: number
|
||||
private _orthogonalSize: number
|
||||
private _rendered = false;
|
||||
private _size: number;
|
||||
private _orthogonalSize: number;
|
||||
|
||||
constructor(
|
||||
view: IViewWithReactComponent,
|
||||
@ -32,52 +32,52 @@ export class ReactRenderView implements IView {
|
||||
portal: React.ReactPortal
|
||||
) => { dispose: () => void }
|
||||
) {
|
||||
this.layout = this.layout.bind(this)
|
||||
this.onDidChange = this.onDidChange.bind(this)
|
||||
this.setRef = this.setRef.bind(this)
|
||||
this.layout = this.layout.bind(this);
|
||||
this.onDidChange = this.onDidChange.bind(this);
|
||||
this.setRef = this.setRef.bind(this);
|
||||
|
||||
this.id = view.id
|
||||
this.component = view.component
|
||||
this.props = view.props
|
||||
this.id = view.id;
|
||||
this.component = view.component;
|
||||
this.props = view.props;
|
||||
|
||||
this.minimumSize = view.minimumSize
|
||||
this.maximumSize = view.maximumSize
|
||||
this.snapSize = view.snapSize
|
||||
this.minimumSize = view.minimumSize;
|
||||
this.maximumSize = view.maximumSize;
|
||||
this.snapSize = view.snapSize;
|
||||
|
||||
this.element = document.createElement('div')
|
||||
this.element.id = 'react-attachable-view'
|
||||
this.element = document.createElement('div');
|
||||
this.element.id = 'react-attachable-view';
|
||||
}
|
||||
|
||||
public update(view: IView) {
|
||||
this.minimumSize = view.minimumSize
|
||||
this.maximumSize = view.maximumSize
|
||||
this.snapSize = view.snapSize
|
||||
this.minimumSize = view.minimumSize;
|
||||
this.maximumSize = view.maximumSize;
|
||||
this.snapSize = view.snapSize;
|
||||
|
||||
this.ref?.updateProps({
|
||||
minimumSize: this.minimumSize,
|
||||
maximumSize: this.maximumSize,
|
||||
snapSize: this.snapSize,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
public layout(size: number, orthogonalSize: number) {
|
||||
if (!this._rendered) {
|
||||
this.attachReactComponent()
|
||||
this._rendered = true
|
||||
this.attachReactComponent();
|
||||
this._rendered = true;
|
||||
}
|
||||
|
||||
this._size = size
|
||||
this._orthogonalSize = orthogonalSize
|
||||
this.ref?.layout(size, orthogonalSize)
|
||||
this._size = size;
|
||||
this._orthogonalSize = orthogonalSize;
|
||||
this.ref?.layout(size, orthogonalSize);
|
||||
}
|
||||
|
||||
private attachReactComponent() {
|
||||
const portal = this.createReactElement()
|
||||
const portal = this.createReactElement();
|
||||
if (this.disposable) {
|
||||
this.disposable.dispose()
|
||||
this.disposable = undefined
|
||||
this.disposable.dispose();
|
||||
this.disposable = undefined;
|
||||
}
|
||||
this.disposable = this.addPortal(portal)
|
||||
this.disposable = this.addPortal(portal);
|
||||
}
|
||||
|
||||
private createReactElement() {
|
||||
@ -94,15 +94,15 @@ export class ReactRenderView implements IView {
|
||||
}}
|
||||
/>,
|
||||
this.element
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private setRef(ref: IViewRootRef) {
|
||||
this.ref = ref
|
||||
this.ref?.layout(this._size, this._orthogonalSize)
|
||||
this.ref = ref;
|
||||
this.ref?.layout(this._size, this._orthogonalSize);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.disposable?.dispose()
|
||||
this.disposable?.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,39 +1,39 @@
|
||||
import * as React from 'react'
|
||||
import { Orientation, IBaseView, PaneView } from 'splitview'
|
||||
import * as React from 'react';
|
||||
import { Orientation, IBaseView, PaneView } from 'splitview';
|
||||
|
||||
import { PaneReact } from './panel/pane'
|
||||
import { PaneComponent, PaneHeaderComponent } from './bridge/pane'
|
||||
import { PaneReact } from './panel/pane';
|
||||
import { PaneComponent, PaneHeaderComponent } from './bridge/pane';
|
||||
|
||||
export interface IPaneWithReactComponent extends IBaseView {
|
||||
id: string
|
||||
headerId: string
|
||||
component: PaneComponent
|
||||
headerComponent: PaneHeaderComponent
|
||||
isExpanded: boolean
|
||||
componentProps: {}
|
||||
headerProps: {}
|
||||
id: string;
|
||||
headerId: string;
|
||||
component: PaneComponent;
|
||||
headerComponent: PaneHeaderComponent;
|
||||
isExpanded: boolean;
|
||||
componentProps: {};
|
||||
headerProps: {};
|
||||
}
|
||||
|
||||
export interface IPaneViewReactProps {
|
||||
orientation: Orientation
|
||||
onReady?: (event: PaneViewReadyEvent) => void
|
||||
components?: { [index: string]: PaneComponent }
|
||||
headerComponents?: { [index: string]: PaneHeaderComponent }
|
||||
size: number
|
||||
orthogonalSize: number
|
||||
initialLayout?: PaneViewSerializedConfig
|
||||
orientation: Orientation;
|
||||
onReady?: (event: PaneViewReadyEvent) => void;
|
||||
components?: { [index: string]: PaneComponent };
|
||||
headerComponents?: { [index: string]: PaneHeaderComponent };
|
||||
size: number;
|
||||
orthogonalSize: number;
|
||||
initialLayout?: PaneViewSerializedConfig;
|
||||
}
|
||||
|
||||
export interface PaneViewReadyEvent {
|
||||
api: PaneviewApi
|
||||
api: PaneviewApi;
|
||||
}
|
||||
|
||||
export interface PaneViewSerializedConfig {
|
||||
views: Array<
|
||||
Omit<IPaneWithReactComponent, 'component' | 'headerComponent'> & {
|
||||
size?: number
|
||||
size?: number;
|
||||
}
|
||||
>
|
||||
>;
|
||||
}
|
||||
|
||||
export interface PaneviewApi {
|
||||
@ -42,27 +42,27 @@ export interface PaneviewApi {
|
||||
IPaneWithReactComponent,
|
||||
'component' | 'headerComponent'
|
||||
> & {
|
||||
size?: number
|
||||
index?: number
|
||||
size?: number;
|
||||
index?: number;
|
||||
}
|
||||
) => void
|
||||
moveView: (from: number, to: number) => void
|
||||
toJSON: () => {}
|
||||
) => void;
|
||||
moveView: (from: number, to: number) => void;
|
||||
toJSON: () => {};
|
||||
}
|
||||
|
||||
export interface IPaneViewComponentRef {
|
||||
layout: (size: number, orthogonalSize: number) => void
|
||||
layout: (size: number, orthogonalSize: number) => void;
|
||||
}
|
||||
|
||||
export const PaneViewComponent = React.forwardRef(
|
||||
(props: IPaneViewReactProps, _ref: React.Ref<IPaneViewComponentRef>) => {
|
||||
const ref = React.useRef<HTMLDivElement>()
|
||||
const ref = React.useRef<HTMLDivElement>();
|
||||
const dimension = React.useRef<{
|
||||
size: number
|
||||
orthogonalSize: number
|
||||
}>()
|
||||
const paneview = React.useRef<PaneView>()
|
||||
const [portals, setPortals] = React.useState<React.ReactPortal[]>([])
|
||||
size: number;
|
||||
orthogonalSize: number;
|
||||
}>();
|
||||
const paneview = React.useRef<PaneView>();
|
||||
const [portals, setPortals] = React.useState<React.ReactPortal[]>([]);
|
||||
|
||||
const createView = React.useCallback(
|
||||
(_view: IPaneWithReactComponent) => {
|
||||
@ -70,47 +70,47 @@ export const PaneViewComponent = React.forwardRef(
|
||||
headerName: 'header',
|
||||
headerComponent: _view.headerComponent,
|
||||
addPortal: (portal) => {
|
||||
setPortals((portals) => [...portals, portal])
|
||||
setPortals((portals) => [...portals, portal]);
|
||||
return {
|
||||
dispose: () => {
|
||||
setPortals((portals) =>
|
||||
portals.filter((_) => _ !== portal)
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
})
|
||||
});
|
||||
},
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
const hydrate = React.useCallback(() => {
|
||||
if (!props.initialLayout || !paneview.current) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const serializedConfig = props.initialLayout
|
||||
const serializedConfig = props.initialLayout;
|
||||
|
||||
serializedConfig.views.forEach((view) => {
|
||||
const component = props.components[view.id]
|
||||
const headerComponent = props.headerComponents[view.headerId]
|
||||
const component = props.components[view.id];
|
||||
const headerComponent = props.headerComponents[view.headerId];
|
||||
paneview.current.addPane(
|
||||
createView({ ...view, component, headerComponent }),
|
||||
view.size
|
||||
)
|
||||
})
|
||||
paneview.current.layout(props.size, props.orthogonalSize)
|
||||
}, [props.initialLayout])
|
||||
);
|
||||
});
|
||||
paneview.current.layout(props.size, props.orthogonalSize);
|
||||
}, [props.initialLayout]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (paneview.current && dimension?.current) {
|
||||
paneview.current?.layout(
|
||||
dimension.current.size,
|
||||
dimension.current.orthogonalSize
|
||||
)
|
||||
dimension.current = undefined
|
||||
);
|
||||
dimension.current = undefined;
|
||||
}
|
||||
}, [paneview.current])
|
||||
}, [paneview.current]);
|
||||
|
||||
// if you put this in a hook it's laggy
|
||||
// paneview.current?.layout(props.size, props.orthogonalSize);
|
||||
@ -122,20 +122,20 @@ export const PaneViewComponent = React.forwardRef(
|
||||
if (!paneview.current) {
|
||||
// handle the case when layout is called and paneview doesn't exist yet
|
||||
// we cache the values and use them at the first opportunity
|
||||
dimension.current = { size, orthogonalSize }
|
||||
dimension.current = { size, orthogonalSize };
|
||||
}
|
||||
paneview.current?.layout(size, orthogonalSize)
|
||||
paneview.current?.layout(size, orthogonalSize);
|
||||
},
|
||||
}),
|
||||
[paneview]
|
||||
)
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
paneview.current = new PaneView(ref.current, {
|
||||
orientation: props.orientation,
|
||||
})
|
||||
});
|
||||
|
||||
hydrate()
|
||||
hydrate();
|
||||
|
||||
if (props.onReady) {
|
||||
props.onReady({
|
||||
@ -145,14 +145,14 @@ export const PaneViewComponent = React.forwardRef(
|
||||
IPaneWithReactComponent,
|
||||
'component' | 'headerComponent'
|
||||
> & {
|
||||
props?: {}
|
||||
size?: number
|
||||
index?: number
|
||||
props?: {};
|
||||
size?: number;
|
||||
index?: number;
|
||||
}
|
||||
) => {
|
||||
const component = props.components[options.id]
|
||||
const component = props.components[options.id];
|
||||
const headerComponent =
|
||||
props.headerComponents[options.headerId]
|
||||
props.headerComponents[options.headerId];
|
||||
paneview.current.addPane(
|
||||
createView({
|
||||
...options,
|
||||
@ -161,14 +161,14 @@ export const PaneViewComponent = React.forwardRef(
|
||||
}),
|
||||
options.size,
|
||||
options.index
|
||||
)
|
||||
);
|
||||
paneview.current.layout(
|
||||
props.size,
|
||||
props.orthogonalSize
|
||||
)
|
||||
);
|
||||
},
|
||||
moveView: (from: number, to: number) => {
|
||||
paneview.current.moveView(from, to)
|
||||
paneview.current.moveView(from, to);
|
||||
},
|
||||
toJSON: () => {
|
||||
return {
|
||||
@ -188,28 +188,28 @@ export const PaneViewComponent = React.forwardRef(
|
||||
// {}
|
||||
// )
|
||||
// ),
|
||||
}
|
||||
};
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
paneview.current.layout(props.size, props.orthogonalSize)
|
||||
paneview.current.layout(props.size, props.orthogonalSize);
|
||||
|
||||
return () => {
|
||||
paneview.current?.dispose()
|
||||
paneview.current = undefined
|
||||
}
|
||||
}, [])
|
||||
paneview.current?.dispose();
|
||||
paneview.current = undefined;
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
paneview.current?.setOrientation(props.orientation)
|
||||
}, [props.orientation])
|
||||
paneview.current?.setOrientation(props.orientation);
|
||||
}, [props.orientation]);
|
||||
|
||||
return (
|
||||
<div ref={ref} className="split-view-react-wrapper">
|
||||
{portals}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -1,90 +1,92 @@
|
||||
import * as React from 'react'
|
||||
import { SplitView, Orientation, IBaseView } from 'splitview'
|
||||
import { ReactRenderView } from './panel/view'
|
||||
import { ViewComponent } from './bridge/view'
|
||||
import * as React from 'react';
|
||||
import { SplitView, Orientation, IBaseView } from 'splitview';
|
||||
import { ReactRenderView } from './panel/view';
|
||||
import { ViewComponent } from './bridge/view';
|
||||
|
||||
export interface IViewWithReactComponent extends IBaseView {
|
||||
id: string
|
||||
props?: {}
|
||||
component: ViewComponent
|
||||
id: string;
|
||||
props?: {};
|
||||
component: ViewComponent;
|
||||
}
|
||||
|
||||
export interface OnReadyEvent {
|
||||
api: SplitviewApi
|
||||
api: SplitviewApi;
|
||||
}
|
||||
|
||||
export interface SerializedConfig {
|
||||
views: Array<Omit<IViewWithReactComponent, 'component'> & { size?: number }>
|
||||
views: Array<
|
||||
Omit<IViewWithReactComponent, 'component'> & { size?: number }
|
||||
>;
|
||||
}
|
||||
|
||||
export interface SplitviewApi {
|
||||
add: (
|
||||
options: Omit<IViewWithReactComponent, 'component'> & {
|
||||
size?: number
|
||||
index?: number
|
||||
size?: number;
|
||||
index?: number;
|
||||
}
|
||||
) => void
|
||||
moveView: (from: number, to: number) => void
|
||||
toJSON: () => {}
|
||||
) => void;
|
||||
moveView: (from: number, to: number) => void;
|
||||
toJSON: () => {};
|
||||
}
|
||||
|
||||
export interface ISplitViewReactProps {
|
||||
orientation: Orientation
|
||||
size: number
|
||||
orthogonalSize: number
|
||||
onReady?: (event: OnReadyEvent) => void
|
||||
components?: { [index: string]: ViewComponent }
|
||||
initialLayout?: SerializedConfig
|
||||
orientation: Orientation;
|
||||
size: number;
|
||||
orthogonalSize: number;
|
||||
onReady?: (event: OnReadyEvent) => void;
|
||||
components?: { [index: string]: ViewComponent };
|
||||
initialLayout?: SerializedConfig;
|
||||
}
|
||||
|
||||
export interface ISplitViewComponentRef {
|
||||
layout: (size: number, orthogonalSize: number) => void
|
||||
layout: (size: number, orthogonalSize: number) => void;
|
||||
}
|
||||
|
||||
export const SplitViewComponent = React.forwardRef(
|
||||
(props: ISplitViewReactProps, ref: React.Ref<ISplitViewComponentRef>) => {
|
||||
const containerRef = React.useRef<HTMLDivElement>()
|
||||
const splitview = React.useRef<SplitView>()
|
||||
const [portals, setPortals] = React.useState<React.ReactPortal[]>([])
|
||||
const containerRef = React.useRef<HTMLDivElement>();
|
||||
const splitview = React.useRef<SplitView>();
|
||||
const [portals, setPortals] = React.useState<React.ReactPortal[]>([]);
|
||||
|
||||
const hydrate = React.useCallback(() => {
|
||||
if (!props.initialLayout || !splitview.current) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const serializedConfig = props.initialLayout
|
||||
const serializedConfig = props.initialLayout;
|
||||
|
||||
serializedConfig.views.forEach((view) => {
|
||||
const component = props.components[view.id]
|
||||
const component = props.components[view.id];
|
||||
splitview.current.addView(
|
||||
createView({ ...view, component }),
|
||||
view.size
|
||||
)
|
||||
})
|
||||
splitview.current.layout(props.size, props.orthogonalSize)
|
||||
}, [props.initialLayout])
|
||||
);
|
||||
});
|
||||
splitview.current.layout(props.size, props.orthogonalSize);
|
||||
}, [props.initialLayout]);
|
||||
|
||||
React.useEffect(() => {
|
||||
splitview.current?.setOrientation(props.orientation)
|
||||
splitview.current?.layout(props.size, props.orthogonalSize)
|
||||
}, [props.orientation])
|
||||
splitview.current?.setOrientation(props.orientation);
|
||||
splitview.current?.layout(props.size, props.orthogonalSize);
|
||||
}, [props.orientation]);
|
||||
|
||||
React.useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
layout: (size, orthogonalSize) => {
|
||||
splitview.current?.layout(size, orthogonalSize)
|
||||
splitview.current?.layout(size, orthogonalSize);
|
||||
},
|
||||
}),
|
||||
[splitview]
|
||||
)
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
splitview.current = new SplitView(containerRef.current, {
|
||||
orientation: props.orientation,
|
||||
})
|
||||
});
|
||||
|
||||
hydrate()
|
||||
hydrate();
|
||||
|
||||
if (props.onReady) {
|
||||
props.onReady({
|
||||
@ -94,24 +96,24 @@ export const SplitViewComponent = React.forwardRef(
|
||||
IViewWithReactComponent,
|
||||
'component'
|
||||
> & {
|
||||
props?: {}
|
||||
size?: number
|
||||
index?: number
|
||||
props?: {};
|
||||
size?: number;
|
||||
index?: number;
|
||||
}
|
||||
) => {
|
||||
const component = props.components[options.id]
|
||||
const component = props.components[options.id];
|
||||
splitview.current.addView(
|
||||
createView({ ...options, component }),
|
||||
options.size,
|
||||
options.index
|
||||
)
|
||||
);
|
||||
splitview.current.layout(
|
||||
props.size,
|
||||
props.orthogonalSize
|
||||
)
|
||||
);
|
||||
},
|
||||
moveView: (from: number, to: number) => {
|
||||
splitview.current.moveView(from, to)
|
||||
splitview.current.moveView(from, to);
|
||||
},
|
||||
toJSON: () => {
|
||||
return {
|
||||
@ -132,37 +134,37 @@ export const SplitViewComponent = React.forwardRef(
|
||||
{}
|
||||
)
|
||||
),
|
||||
}
|
||||
};
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
splitview.current.layout(props.size, props.orthogonalSize)
|
||||
splitview.current.layout(props.size, props.orthogonalSize);
|
||||
|
||||
return () => {
|
||||
splitview.current.dispose()
|
||||
}
|
||||
}, [])
|
||||
splitview.current.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const createView = React.useCallback(
|
||||
(view: IViewWithReactComponent) =>
|
||||
new ReactRenderView(view, (portal) => {
|
||||
setPortals((portals) => [...portals, portal])
|
||||
setPortals((portals) => [...portals, portal]);
|
||||
return {
|
||||
dispose: () =>
|
||||
void setPortals((portals) =>
|
||||
portals.filter((_) => _ !== portal)
|
||||
),
|
||||
}
|
||||
};
|
||||
}),
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="split-view-container-react">
|
||||
{portals}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
const gulp = require('gulp')
|
||||
const buildfile = require('../../scripts/build')
|
||||
const package = require('./package')
|
||||
const gulp = require('gulp');
|
||||
const buildfile = require('../../scripts/build');
|
||||
const package = require('./package');
|
||||
|
||||
buildfile.build({ tsconfig: './tsconfig.build.json', package })
|
||||
buildfile.build({ tsconfig: './tsconfig.build.json', package });
|
||||
|
||||
gulp.task('run', gulp.series(['esm', 'sass']))
|
||||
gulp.task('run', gulp.series(['esm', 'sass']));
|
||||
|
@ -1,37 +1,37 @@
|
||||
export function tail<T>(arr: T[]): [T[], T] {
|
||||
if (arr.length === 0) {
|
||||
throw new Error('Invalid tail call')
|
||||
throw new Error('Invalid tail call');
|
||||
}
|
||||
|
||||
return [arr.slice(0, arr.length - 1), arr[arr.length - 1]]
|
||||
return [arr.slice(0, arr.length - 1), arr[arr.length - 1]];
|
||||
}
|
||||
|
||||
export function last<T>(arr: T[]): T {
|
||||
return arr.length > 0 ? arr[arr.length - 1] : undefined
|
||||
return arr.length > 0 ? arr[arr.length - 1] : undefined;
|
||||
}
|
||||
|
||||
export function sequenceEquals<T>(arr1: T[], arr2: T[]) {
|
||||
if (arr1.length !== arr2.length) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < arr1.length; i++) {
|
||||
if (arr1[i] !== arr2[i]) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes an element to the start of the array, if found.
|
||||
*/
|
||||
export function pushToStart<T>(arr: T[], value: T): void {
|
||||
const index = arr.indexOf(value)
|
||||
const index = arr.indexOf(value);
|
||||
|
||||
if (index > -1) {
|
||||
arr.splice(index, 1)
|
||||
arr.unshift(value)
|
||||
arr.splice(index, 1);
|
||||
arr.unshift(value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,31 +39,31 @@ export function pushToStart<T>(arr: T[], value: T): void {
|
||||
* Pushes an element to the end of the array, if found.
|
||||
*/
|
||||
export function pushToEnd<T>(arr: T[], value: T): void {
|
||||
const index = arr.indexOf(value)
|
||||
const index = arr.indexOf(value);
|
||||
|
||||
if (index > -1) {
|
||||
arr.splice(index, 1)
|
||||
arr.push(value)
|
||||
arr.splice(index, 1);
|
||||
arr.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
export const range = (from: number, to: number = undefined) => {
|
||||
const result: number[] = []
|
||||
const result: number[] = [];
|
||||
|
||||
if (to === undefined) {
|
||||
to = from
|
||||
from = 0
|
||||
to = from;
|
||||
from = 0;
|
||||
}
|
||||
|
||||
if (from <= to) {
|
||||
for (let i = from; i < to; i++) {
|
||||
result.push(i)
|
||||
result.push(i);
|
||||
}
|
||||
} else {
|
||||
for (let i = from; i > to; i--) {
|
||||
result.push(i)
|
||||
result.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
export function timeoutPromise(timeout: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, timeout)
|
||||
})
|
||||
resolve();
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { Event, Emitter, addDisposableListener } from './events'
|
||||
import { IDisposable, CompositeDisposable } from './lifecycle'
|
||||
import { Event, Emitter, addDisposableListener } from './events';
|
||||
import { IDisposable, CompositeDisposable } from './lifecycle';
|
||||
|
||||
export function getDomNodePagePosition(domNode: HTMLElement) {
|
||||
const bb = domNode.getBoundingClientRect()
|
||||
const bb = domNode.getBoundingClientRect();
|
||||
return {
|
||||
left: bb.left + window.scrollX,
|
||||
top: bb.top + window.scrollY,
|
||||
width: bb.width,
|
||||
height: bb.height,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -19,10 +19,10 @@ export const scrollIntoView = (
|
||||
element: HTMLElement,
|
||||
container: HTMLElement
|
||||
) => {
|
||||
const { inView, breachPoint } = isElementInView(element, container, true)
|
||||
const { inView, breachPoint } = isElementInView(element, container, true);
|
||||
if (!inView) {
|
||||
const adder = -container.offsetTop
|
||||
const isUp = breachPoint === 'top'
|
||||
const adder = -container.offsetTop;
|
||||
const isUp = breachPoint === 'top';
|
||||
container.scrollTo({
|
||||
top: isUp
|
||||
? adder + element.offsetTop
|
||||
@ -30,108 +30,108 @@ export const scrollIntoView = (
|
||||
element.offsetTop -
|
||||
container.clientHeight +
|
||||
element.clientHeight,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const isElementInView = (
|
||||
element: HTMLElement,
|
||||
container: HTMLElement,
|
||||
fullyInView: boolean
|
||||
): { inView: boolean; breachPoint?: 'top' | 'bottom' } => {
|
||||
const containerOfftsetTop = container.offsetTop
|
||||
const containerTop = containerOfftsetTop + container.scrollTop
|
||||
const containerOfftsetTop = container.offsetTop;
|
||||
const containerTop = containerOfftsetTop + container.scrollTop;
|
||||
const containerBottom =
|
||||
containerTop + container.getBoundingClientRect().height
|
||||
const elementTop = element.offsetTop
|
||||
const elementBottom = elementTop + element.getBoundingClientRect().height
|
||||
containerTop + container.getBoundingClientRect().height;
|
||||
const elementTop = element.offsetTop;
|
||||
const elementBottom = elementTop + element.getBoundingClientRect().height;
|
||||
|
||||
const isAbove = fullyInView
|
||||
? containerTop >= elementTop
|
||||
: elementTop > containerBottom
|
||||
: elementTop > containerBottom;
|
||||
const isBelow = fullyInView
|
||||
? containerBottom <= elementBottom
|
||||
: elementBottom < containerTop
|
||||
: elementBottom < containerTop;
|
||||
|
||||
if (isAbove) {
|
||||
return { inView: false, breachPoint: 'top' }
|
||||
return { inView: false, breachPoint: 'top' };
|
||||
}
|
||||
|
||||
if (isBelow) {
|
||||
return { inView: false, breachPoint: 'bottom' }
|
||||
return { inView: false, breachPoint: 'bottom' };
|
||||
}
|
||||
|
||||
return { inView: true }
|
||||
}
|
||||
return { inView: true };
|
||||
};
|
||||
|
||||
export function isHTMLElement(o: any): o is HTMLElement {
|
||||
if (typeof HTMLElement === 'object') {
|
||||
return o instanceof HTMLElement
|
||||
return o instanceof HTMLElement;
|
||||
}
|
||||
return (
|
||||
o &&
|
||||
typeof o === 'object' &&
|
||||
o.nodeType === 1 &&
|
||||
typeof o.nodeName === 'string'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export const isInTree = (element: HTMLElement, className: string) => {
|
||||
let _element = element
|
||||
let _element = element;
|
||||
|
||||
while (_element) {
|
||||
if (_element.classList.contains(className)) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
_element = _element.parentElement
|
||||
_element = _element.parentElement;
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const removeClasses = (element: HTMLElement, ...classes: string[]) => {
|
||||
for (const classname of classes) {
|
||||
if (element.classList.contains(classname)) {
|
||||
element.classList.remove(classname)
|
||||
element.classList.remove(classname);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const addClasses = (element: HTMLElement, ...classes: string[]) => {
|
||||
for (const classname of classes) {
|
||||
if (!element.classList.contains(classname)) {
|
||||
element.classList.add(classname)
|
||||
element.classList.add(classname);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleClass = (
|
||||
element: HTMLElement,
|
||||
className: string,
|
||||
isToggled: boolean
|
||||
) => {
|
||||
const hasClass = element.classList.contains(className)
|
||||
const hasClass = element.classList.contains(className);
|
||||
if (isToggled && !hasClass) {
|
||||
element.classList.add(className)
|
||||
element.classList.add(className);
|
||||
}
|
||||
if (!isToggled && hasClass) {
|
||||
element.classList.remove(className)
|
||||
element.classList.remove(className);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function firstIndex<T>(
|
||||
array: T[] | ReadonlyArray<T>,
|
||||
fn: (item: T) => boolean
|
||||
): number {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const element = array[i]
|
||||
const element = array[i];
|
||||
|
||||
if (fn(element)) {
|
||||
return i
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function isAncestor(
|
||||
@ -140,93 +140,93 @@ export function isAncestor(
|
||||
): boolean {
|
||||
while (testChild) {
|
||||
if (testChild === testAncestor) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
testChild = testChild.parentNode
|
||||
testChild = testChild.parentNode;
|
||||
}
|
||||
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
export interface IFocusTracker extends IDisposable {
|
||||
onDidFocus: Event<void>
|
||||
onDidBlur: Event<void>
|
||||
refreshState?(): void
|
||||
onDidFocus: Event<void>;
|
||||
onDidBlur: Event<void>;
|
||||
refreshState?(): void;
|
||||
}
|
||||
|
||||
export function trackFocus(element: HTMLElement | Window): IFocusTracker {
|
||||
return new FocusTracker(element)
|
||||
return new FocusTracker(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Track focus on an element. Ensure tabIndex is set when an HTMLElement is not focusable by default
|
||||
*/
|
||||
class FocusTracker extends CompositeDisposable implements IFocusTracker {
|
||||
private readonly _onDidFocus = new Emitter<void>()
|
||||
public readonly onDidFocus: Event<void> = this._onDidFocus.event
|
||||
private readonly _onDidFocus = new Emitter<void>();
|
||||
public readonly onDidFocus: Event<void> = this._onDidFocus.event;
|
||||
|
||||
private readonly _onDidBlur = new Emitter<void>()
|
||||
public readonly onDidBlur: Event<void> = this._onDidBlur.event
|
||||
private readonly _onDidBlur = new Emitter<void>();
|
||||
public readonly onDidBlur: Event<void> = this._onDidBlur.event;
|
||||
|
||||
private _refreshStateHandler: () => void
|
||||
private _refreshStateHandler: () => void;
|
||||
|
||||
constructor(element: HTMLElement | Window) {
|
||||
super()
|
||||
super();
|
||||
|
||||
let hasFocus = isAncestor(document.activeElement, <HTMLElement>element)
|
||||
let loosingFocus = false
|
||||
let hasFocus = isAncestor(document.activeElement, <HTMLElement>element);
|
||||
let loosingFocus = false;
|
||||
|
||||
const onFocus = () => {
|
||||
loosingFocus = false
|
||||
loosingFocus = false;
|
||||
if (!hasFocus) {
|
||||
hasFocus = true
|
||||
this._onDidFocus.fire()
|
||||
hasFocus = true;
|
||||
this._onDidFocus.fire();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onBlur = () => {
|
||||
if (hasFocus) {
|
||||
loosingFocus = true
|
||||
loosingFocus = true;
|
||||
window.setTimeout(() => {
|
||||
if (loosingFocus) {
|
||||
loosingFocus = false
|
||||
hasFocus = false
|
||||
this._onDidBlur.fire()
|
||||
loosingFocus = false;
|
||||
hasFocus = false;
|
||||
this._onDidBlur.fire();
|
||||
}
|
||||
}, 0)
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this._refreshStateHandler = () => {
|
||||
let currentNodeHasFocus = isAncestor(
|
||||
document.activeElement,
|
||||
<HTMLElement>element
|
||||
)
|
||||
);
|
||||
if (currentNodeHasFocus !== hasFocus) {
|
||||
if (hasFocus) {
|
||||
onBlur()
|
||||
onBlur();
|
||||
} else {
|
||||
onFocus()
|
||||
onFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(element, 'focus', onFocus, true)
|
||||
)
|
||||
);
|
||||
this.addDisposables(
|
||||
addDisposableListener(element, 'blur', onBlur, true)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
refreshState() {
|
||||
this._refreshStateHandler()
|
||||
this._refreshStateHandler();
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose()
|
||||
super.dispose();
|
||||
|
||||
this._onDidBlur.dispose()
|
||||
this._onDidFocus.dispose()
|
||||
this._onDidBlur.dispose();
|
||||
this._onDidFocus.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,37 @@
|
||||
import { IDisposable } from './lifecycle'
|
||||
import { IDisposable } from './lifecycle';
|
||||
|
||||
export interface Event<T> {
|
||||
(listener: (e: T) => any): IDisposable
|
||||
(listener: (e: T) => any): IDisposable;
|
||||
}
|
||||
|
||||
export interface EmitterOptions {
|
||||
emitLastValue?: boolean
|
||||
emitLastValue?: boolean;
|
||||
}
|
||||
|
||||
export namespace Event {
|
||||
export const any = <T>(...children: Event<T>[]): Event<T> => {
|
||||
return (listener: (e: T) => void) => {
|
||||
const disposables = children.map((child) => child(listener))
|
||||
const disposables = children.map((child) => child(listener));
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
disposables.forEach((d) => {
|
||||
d.dispose()
|
||||
})
|
||||
d.dispose();
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// dumb event emitter with better typings than nodes event emitter
|
||||
// https://github.com/microsoft/vscode/blob/master/src/vs/base/common/event.ts
|
||||
export class Emitter<T> implements IDisposable {
|
||||
private _event: Event<T>
|
||||
private _event: Event<T>;
|
||||
|
||||
private _last: T
|
||||
private _listeners: Array<(e: T) => any> = []
|
||||
private _disposed: boolean = false
|
||||
private _last: T;
|
||||
private _listeners: Array<(e: T) => any> = [];
|
||||
private _disposed: boolean = false;
|
||||
|
||||
constructor(private readonly options?: EmitterOptions) {}
|
||||
|
||||
@ -39,38 +39,38 @@ export class Emitter<T> implements IDisposable {
|
||||
if (!this._event) {
|
||||
this._event = (listener: (e: T) => void): IDisposable => {
|
||||
if (this.options?.emitLastValue && this._last !== undefined) {
|
||||
listener(this._last)
|
||||
listener(this._last);
|
||||
}
|
||||
|
||||
this._listeners.push(listener)
|
||||
this._listeners.push(listener);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
const index = this._listeners.indexOf(listener)
|
||||
const index = this._listeners.indexOf(listener);
|
||||
if (index > -1) {
|
||||
this._listeners.splice(index, 1)
|
||||
this._listeners.splice(index, 1);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
return this._event
|
||||
return this._event;
|
||||
}
|
||||
|
||||
public fire(e: T) {
|
||||
this._last = e
|
||||
this._last = e;
|
||||
this._listeners.forEach((listener) => {
|
||||
listener(e)
|
||||
})
|
||||
listener(e);
|
||||
});
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this._listeners = []
|
||||
this._disposed = true
|
||||
this._listeners = [];
|
||||
this._disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
export type EventHandler = HTMLElement | HTMLDocument | Window
|
||||
export type EventHandler = HTMLElement | HTMLDocument | Window;
|
||||
|
||||
export const addDisposableListener = <K extends keyof HTMLElementEventMap>(
|
||||
element: EventHandler,
|
||||
@ -78,11 +78,11 @@ export const addDisposableListener = <K extends keyof HTMLElementEventMap>(
|
||||
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
): IDisposable => {
|
||||
element.addEventListener(type, listener, options)
|
||||
element.addEventListener(type, listener, options);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
element.removeEventListener(type, listener)
|
||||
element.removeEventListener(type, listener);
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -1,9 +1,9 @@
|
||||
export function debounce<T extends Function>(cb: T, wait: number) {
|
||||
let timeout: NodeJS.Timeout
|
||||
let timeout: NodeJS.Timeout;
|
||||
|
||||
const callable = (...args: any) => {
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(() => cb(...args), wait)
|
||||
}
|
||||
return <T>(<any>callable)
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => cb(...args), wait);
|
||||
};
|
||||
return <T>(<any>callable);
|
||||
}
|
||||
|
@ -4,106 +4,106 @@ import {
|
||||
Orientation,
|
||||
Sizing,
|
||||
LayoutPriority,
|
||||
} from '../splitview/splitview'
|
||||
import { Emitter, Event } from '../events'
|
||||
import { INodeDescriptor } from './gridview'
|
||||
import { Node } from './types'
|
||||
import { CompositeDisposable, IDisposable, Disposable } from '../lifecycle'
|
||||
} from '../splitview/splitview';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { INodeDescriptor } from './gridview';
|
||||
import { Node } from './types';
|
||||
import { CompositeDisposable, IDisposable, Disposable } from '../lifecycle';
|
||||
|
||||
export class BranchNode extends CompositeDisposable implements IView {
|
||||
readonly element: HTMLElement
|
||||
private splitview: SplitView
|
||||
private _orthogonalSize: number
|
||||
private _size: number
|
||||
public readonly children: Node[] = []
|
||||
readonly element: HTMLElement;
|
||||
private splitview: SplitView;
|
||||
private _orthogonalSize: number;
|
||||
private _size: number;
|
||||
public readonly children: Node[] = [];
|
||||
|
||||
private readonly _onDidChange = new Emitter<number | undefined>()
|
||||
readonly onDidChange: Event<number | undefined> = this._onDidChange.event
|
||||
private readonly _onDidChange = new Emitter<number | undefined>();
|
||||
readonly onDidChange: Event<number | undefined> = this._onDidChange.event;
|
||||
|
||||
get width(): number {
|
||||
return this.orientation === Orientation.HORIZONTAL
|
||||
? this.size
|
||||
: this.orthogonalSize
|
||||
: this.orthogonalSize;
|
||||
}
|
||||
|
||||
get height(): number {
|
||||
return this.orientation === Orientation.HORIZONTAL
|
||||
? this.orthogonalSize
|
||||
: this.size
|
||||
: this.size;
|
||||
}
|
||||
|
||||
get minimumSize(): number {
|
||||
return this.children.length === 0
|
||||
? 0
|
||||
: Math.max(...this.children.map((c) => c.minimumOrthogonalSize))
|
||||
: Math.max(...this.children.map((c) => c.minimumOrthogonalSize));
|
||||
}
|
||||
|
||||
get maximumSize(): number {
|
||||
return Math.min(...this.children.map((c) => c.maximumOrthogonalSize))
|
||||
return Math.min(...this.children.map((c) => c.maximumOrthogonalSize));
|
||||
}
|
||||
|
||||
get minimumOrthogonalSize(): number {
|
||||
return this.splitview.minimumSize
|
||||
return this.splitview.minimumSize;
|
||||
}
|
||||
|
||||
get maximumOrthogonalSize(): number {
|
||||
return this.splitview.maximumSize
|
||||
return this.splitview.maximumSize;
|
||||
}
|
||||
|
||||
get orthogonalSize() {
|
||||
return this._orthogonalSize
|
||||
return this._orthogonalSize;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this._size
|
||||
return this._size;
|
||||
}
|
||||
|
||||
get minimumWidth(): number {
|
||||
return this.orientation === Orientation.HORIZONTAL
|
||||
? this.minimumOrthogonalSize
|
||||
: this.minimumSize
|
||||
: this.minimumSize;
|
||||
}
|
||||
|
||||
get snapSize() {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get minimumHeight(): number {
|
||||
return this.orientation === Orientation.HORIZONTAL
|
||||
? this.minimumSize
|
||||
: this.minimumOrthogonalSize
|
||||
: this.minimumOrthogonalSize;
|
||||
}
|
||||
|
||||
get maximumWidth(): number {
|
||||
return this.orientation === Orientation.HORIZONTAL
|
||||
? this.maximumOrthogonalSize
|
||||
: this.maximumSize
|
||||
: this.maximumSize;
|
||||
}
|
||||
|
||||
get maximumHeight(): number {
|
||||
return this.orientation === Orientation.HORIZONTAL
|
||||
? this.maximumSize
|
||||
: this.maximumOrthogonalSize
|
||||
: this.maximumOrthogonalSize;
|
||||
}
|
||||
|
||||
get priority(): LayoutPriority {
|
||||
if (this.children.length === 0) {
|
||||
return LayoutPriority.Normal
|
||||
return LayoutPriority.Normal;
|
||||
}
|
||||
|
||||
const priorities = this.children.map((c) =>
|
||||
typeof c.priority === 'undefined'
|
||||
? LayoutPriority.Normal
|
||||
: c.priority
|
||||
)
|
||||
);
|
||||
|
||||
if (priorities.some((p) => p === LayoutPriority.High)) {
|
||||
return LayoutPriority.High
|
||||
return LayoutPriority.High;
|
||||
} else if (priorities.some((p) => p === LayoutPriority.Low)) {
|
||||
return LayoutPriority.Low
|
||||
return LayoutPriority.Low;
|
||||
}
|
||||
|
||||
return LayoutPriority.Normal
|
||||
return LayoutPriority.Normal;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ -114,133 +114,133 @@ export class BranchNode extends CompositeDisposable implements IView {
|
||||
|
||||
childDescriptors?: INodeDescriptor[]
|
||||
) {
|
||||
super()
|
||||
this._orthogonalSize = orthogonalSize
|
||||
this._size = size
|
||||
this.element = document.createElement('div')
|
||||
this.element.className = 'branch-node'
|
||||
super();
|
||||
this._orthogonalSize = orthogonalSize;
|
||||
this._size = size;
|
||||
this.element = document.createElement('div');
|
||||
this.element.className = 'branch-node';
|
||||
|
||||
if (!childDescriptors) {
|
||||
this.splitview = new SplitView(this.element, {
|
||||
orientation: this.orientation,
|
||||
proportionalLayout,
|
||||
})
|
||||
this.splitview.layout(this.size, this.orthogonalSize)
|
||||
});
|
||||
this.splitview.layout(this.size, this.orthogonalSize);
|
||||
} else {
|
||||
const descriptor = {
|
||||
views: childDescriptors.map((childDescriptor) => {
|
||||
return {
|
||||
view: childDescriptor.node,
|
||||
size: childDescriptor.node.size,
|
||||
}
|
||||
};
|
||||
}),
|
||||
size: this.orthogonalSize,
|
||||
}
|
||||
};
|
||||
|
||||
this.children = childDescriptors.map((c) => c.node)
|
||||
this.children = childDescriptors.map((c) => c.node);
|
||||
this.splitview = new SplitView(this.element, {
|
||||
orientation: this.orientation,
|
||||
descriptor,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
this.addDisposables(
|
||||
this.splitview.onDidSashEnd(() => {
|
||||
this._onDidChange.fire(undefined)
|
||||
this._onDidChange.fire(undefined);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
moveChild(from: number, to: number): void {
|
||||
if (from === to) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (from < 0 || from >= this.children.length) {
|
||||
throw new Error('Invalid from index')
|
||||
throw new Error('Invalid from index');
|
||||
}
|
||||
|
||||
if (from < to) {
|
||||
to--
|
||||
to--;
|
||||
}
|
||||
|
||||
this.splitview.moveView(from, to)
|
||||
this.splitview.moveView(from, to);
|
||||
|
||||
const child = this._removeChild(from)
|
||||
this._addChild(child, to)
|
||||
const child = this._removeChild(from);
|
||||
this._addChild(child, to);
|
||||
}
|
||||
|
||||
getChildSize(index: number): number {
|
||||
if (index < 0 || index >= this.children.length) {
|
||||
throw new Error('Invalid index')
|
||||
throw new Error('Invalid index');
|
||||
}
|
||||
|
||||
return this.splitview.getViewSize(index)
|
||||
return this.splitview.getViewSize(index);
|
||||
}
|
||||
|
||||
resizeChild(index: number, size: number): void {
|
||||
if (index < 0 || index >= this.children.length) {
|
||||
throw new Error('Invalid index')
|
||||
throw new Error('Invalid index');
|
||||
}
|
||||
|
||||
this.splitview.resizeView(index, size)
|
||||
this.splitview.resizeView(index, size);
|
||||
}
|
||||
|
||||
public layout(size: number, orthogonalSize: number) {
|
||||
this._size = orthogonalSize
|
||||
this._orthogonalSize = size
|
||||
this._size = orthogonalSize;
|
||||
this._orthogonalSize = size;
|
||||
|
||||
this.splitview.layout(this.size, this.orthogonalSize)
|
||||
this.splitview.layout(this.size, this.orthogonalSize);
|
||||
}
|
||||
|
||||
public addChild(node: Node, size: number | Sizing, index: number): void {
|
||||
if (index < 0 || index > this.children.length) {
|
||||
throw new Error('Invalid index')
|
||||
throw new Error('Invalid index');
|
||||
}
|
||||
|
||||
this.splitview.addView(node, size, index)
|
||||
this._addChild(node, index)
|
||||
this.splitview.addView(node, size, index);
|
||||
this._addChild(node, index);
|
||||
}
|
||||
|
||||
public removeChild(index: number, sizing?: Sizing) {
|
||||
if (index < 0 || index >= this.children.length) {
|
||||
throw new Error('Invalid index')
|
||||
throw new Error('Invalid index');
|
||||
}
|
||||
|
||||
this.splitview.removeView(index, sizing)
|
||||
this._removeChild(index)
|
||||
this.splitview.removeView(index, sizing);
|
||||
this._removeChild(index);
|
||||
}
|
||||
|
||||
private _addChild(node: Node, index: number): void {
|
||||
this.children.splice(index, 0, node)
|
||||
this.setupChildrenEvents()
|
||||
this.children.splice(index, 0, node);
|
||||
this.setupChildrenEvents();
|
||||
}
|
||||
|
||||
private _removeChild(index: number): Node {
|
||||
const first = index === 0
|
||||
const last = index === this.children.length - 1
|
||||
const [child] = this.children.splice(index, 1)
|
||||
this.setupChildrenEvents()
|
||||
const first = index === 0;
|
||||
const last = index === this.children.length - 1;
|
||||
const [child] = this.children.splice(index, 1);
|
||||
this.setupChildrenEvents();
|
||||
|
||||
return child
|
||||
return child;
|
||||
}
|
||||
|
||||
private _childrenDisposable: IDisposable = Disposable.NONE
|
||||
private _childrenDisposable: IDisposable = Disposable.NONE;
|
||||
|
||||
private setupChildrenEvents() {
|
||||
this._childrenDisposable.dispose()
|
||||
this._childrenDisposable.dispose();
|
||||
|
||||
this._childrenDisposable = Event.any(
|
||||
...this.children.map((c) => c.onDidChange)
|
||||
)((e) => {
|
||||
this._onDidChange.fire(e)
|
||||
})
|
||||
this._onDidChange.fire(e);
|
||||
});
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose()
|
||||
this._childrenDisposable.dispose()
|
||||
this.splitview.dispose()
|
||||
this.children.forEach((child) => child.dispose())
|
||||
super.dispose();
|
||||
this._childrenDisposable.dispose();
|
||||
this.splitview.dispose();
|
||||
this.children.forEach((child) => child.dispose());
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { LayoutPriority, Orientation, Sizing } from '../splitview/splitview'
|
||||
import { Position } from '../groupview/droptarget/droptarget'
|
||||
import { tail } from '../array'
|
||||
import { LeafNode } from './leafNode'
|
||||
import { BranchNode } from './branchNode'
|
||||
import { Node } from './types'
|
||||
import { Emitter, Event } from '../events'
|
||||
import { IDisposable } from '../lifecycle'
|
||||
import { LayoutPriority, Orientation, Sizing } from '../splitview/splitview';
|
||||
import { Position } from '../groupview/droptarget/droptarget';
|
||||
import { tail } from '../array';
|
||||
import { LeafNode } from './leafNode';
|
||||
import { BranchNode } from './branchNode';
|
||||
import { Node } from './types';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
|
||||
function flipNode<T extends Node>(
|
||||
node: T,
|
||||
@ -18,57 +18,59 @@ function flipNode<T extends Node>(
|
||||
node.proportionalLayout,
|
||||
size,
|
||||
orthogonalSize
|
||||
)
|
||||
);
|
||||
|
||||
let totalSize = 0
|
||||
let totalSize = 0;
|
||||
|
||||
for (let i = node.children.length - 1; i >= 0; i--) {
|
||||
const child = node.children[i]
|
||||
const child = node.children[i];
|
||||
const childSize =
|
||||
child instanceof BranchNode ? child.orthogonalSize : child.size
|
||||
child instanceof BranchNode ? child.orthogonalSize : child.size;
|
||||
|
||||
let newSize =
|
||||
node.size === 0 ? 0 : Math.round((size * childSize) / node.size)
|
||||
totalSize += newSize
|
||||
node.size === 0
|
||||
? 0
|
||||
: Math.round((size * childSize) / node.size);
|
||||
totalSize += newSize;
|
||||
|
||||
// The last view to add should adjust to rounding errors
|
||||
if (i === 0) {
|
||||
newSize += size - totalSize
|
||||
newSize += size - totalSize;
|
||||
}
|
||||
|
||||
result.addChild(
|
||||
flipNode(child, orthogonalSize, newSize),
|
||||
newSize,
|
||||
0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return result as T
|
||||
return result as T;
|
||||
} else {
|
||||
return new LeafNode(
|
||||
(node as LeafNode).view,
|
||||
orthogonal(node.orientation),
|
||||
orthogonalSize
|
||||
) as T
|
||||
) as T;
|
||||
}
|
||||
}
|
||||
|
||||
export function indexInParent(element: HTMLElement): number {
|
||||
const parentElement = element.parentElement
|
||||
const parentElement = element.parentElement;
|
||||
|
||||
if (!parentElement) {
|
||||
throw new Error('Invalid grid element')
|
||||
throw new Error('Invalid grid element');
|
||||
}
|
||||
|
||||
let el = parentElement.firstElementChild
|
||||
let index = 0
|
||||
let el = parentElement.firstElementChild;
|
||||
let index = 0;
|
||||
|
||||
while (el !== element && el !== parentElement.lastElementChild && el) {
|
||||
el = el.nextElementSibling
|
||||
index++
|
||||
el = el.nextElementSibling;
|
||||
index++;
|
||||
}
|
||||
|
||||
return index
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,19 +80,19 @@ export function indexInParent(element: HTMLElement): number {
|
||||
* This will break as soon as DOM structures of the Splitview or Gridview change.
|
||||
*/
|
||||
export function getGridLocation(element: HTMLElement): number[] {
|
||||
const parentElement = element.parentElement
|
||||
const parentElement = element.parentElement;
|
||||
|
||||
if (!parentElement) {
|
||||
throw new Error('Invalid grid element')
|
||||
throw new Error('Invalid grid element');
|
||||
}
|
||||
|
||||
if (/\bgrid-view\b/.test(parentElement.className)) {
|
||||
return []
|
||||
return [];
|
||||
}
|
||||
|
||||
const index = indexInParent(parentElement)
|
||||
const ancestor = parentElement.parentElement!.parentElement!.parentElement!
|
||||
return [...getGridLocation(ancestor), index]
|
||||
const index = indexInParent(parentElement);
|
||||
const ancestor = parentElement.parentElement!.parentElement!.parentElement!;
|
||||
return [...getGridLocation(ancestor), index];
|
||||
}
|
||||
|
||||
export function getRelativeLocation(
|
||||
@ -98,30 +100,30 @@ export function getRelativeLocation(
|
||||
location: number[],
|
||||
direction: Position
|
||||
): number[] {
|
||||
const orientation = getLocationOrientation(rootOrientation, location)
|
||||
const directionOrientation = getDirectionOrientation(direction)
|
||||
const orientation = getLocationOrientation(rootOrientation, location);
|
||||
const directionOrientation = getDirectionOrientation(direction);
|
||||
|
||||
if (orientation === directionOrientation) {
|
||||
let [rest, index] = tail(location)
|
||||
let [rest, index] = tail(location);
|
||||
|
||||
if (direction === Position.Right || direction === Position.Bottom) {
|
||||
index += 1
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return [...rest, index]
|
||||
return [...rest, index];
|
||||
} else {
|
||||
const index =
|
||||
direction === Position.Right || direction === Position.Bottom
|
||||
? 1
|
||||
: 0
|
||||
return [...location, index]
|
||||
: 0;
|
||||
return [...location, index];
|
||||
}
|
||||
}
|
||||
|
||||
export function getDirectionOrientation(direction: Position): Orientation {
|
||||
return direction === Position.Top || direction === Position.Bottom
|
||||
? Orientation.VERTICAL
|
||||
: Orientation.HORIZONTAL
|
||||
: Orientation.HORIZONTAL;
|
||||
}
|
||||
|
||||
export function getLocationOrientation(
|
||||
@ -130,88 +132,88 @@ export function getLocationOrientation(
|
||||
): Orientation {
|
||||
return location.length % 2 === 0
|
||||
? orthogonal(rootOrientation)
|
||||
: rootOrientation
|
||||
: rootOrientation;
|
||||
}
|
||||
|
||||
export interface IGridView {
|
||||
readonly element: HTMLElement
|
||||
readonly minimumWidth: number
|
||||
readonly maximumWidth: number
|
||||
readonly minimumHeight: number
|
||||
readonly maximumHeight: number
|
||||
readonly priority?: LayoutPriority
|
||||
layout(width: number, height: number, top: number, left: number): void
|
||||
toJSON?(): object
|
||||
fromJSON?(json: object): void
|
||||
snap?: boolean
|
||||
readonly element: HTMLElement;
|
||||
readonly minimumWidth: number;
|
||||
readonly maximumWidth: number;
|
||||
readonly minimumHeight: number;
|
||||
readonly maximumHeight: number;
|
||||
readonly priority?: LayoutPriority;
|
||||
layout(width: number, height: number, top: number, left: number): void;
|
||||
toJSON?(): object;
|
||||
fromJSON?(json: object): void;
|
||||
snap?: boolean;
|
||||
}
|
||||
|
||||
const orthogonal = (orientation: Orientation) =>
|
||||
orientation === Orientation.HORIZONTAL
|
||||
? Orientation.VERTICAL
|
||||
: Orientation.HORIZONTAL
|
||||
: Orientation.HORIZONTAL;
|
||||
|
||||
const serializeLeafNode = (node: LeafNode) => {
|
||||
const size =
|
||||
node.orientation === Orientation.HORIZONTAL
|
||||
? node.size
|
||||
: node.orthogonalSize
|
||||
: node.orthogonalSize;
|
||||
return {
|
||||
size: node.size,
|
||||
data: node.view.toJSON ? node.view.toJSON() : {},
|
||||
type: 'leaf',
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const serializeBranchNode = (node: BranchNode) => {
|
||||
const size =
|
||||
node.orientation === Orientation.HORIZONTAL
|
||||
? node.size
|
||||
: node.orthogonalSize
|
||||
: node.orthogonalSize;
|
||||
|
||||
return {
|
||||
orientation: node.orientation,
|
||||
size,
|
||||
data: node.children.map((child) => {
|
||||
if (child instanceof LeafNode) {
|
||||
return serializeLeafNode(child)
|
||||
return serializeLeafNode(child);
|
||||
}
|
||||
return serializeBranchNode(child as BranchNode)
|
||||
return serializeBranchNode(child as BranchNode);
|
||||
}),
|
||||
type: 'branch',
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export interface ISerializedLeafNode {
|
||||
type: 'leaf'
|
||||
data: any
|
||||
size: number
|
||||
visible?: boolean
|
||||
type: 'leaf';
|
||||
data: any;
|
||||
size: number;
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
export interface ISerializedBranchNode {
|
||||
type: 'branch'
|
||||
data: ISerializedNode[]
|
||||
size: number
|
||||
type: 'branch';
|
||||
data: ISerializedNode[];
|
||||
size: number;
|
||||
}
|
||||
|
||||
export type ISerializedNode = ISerializedLeafNode | ISerializedBranchNode
|
||||
export type ISerializedNode = ISerializedLeafNode | ISerializedBranchNode;
|
||||
|
||||
export interface INodeDescriptor {
|
||||
node: Node
|
||||
visible?: boolean
|
||||
node: Node;
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
export interface IViewDeserializer {
|
||||
fromJSON: (data: {}) => IGridView
|
||||
fromJSON: (data: {}) => IGridView;
|
||||
}
|
||||
|
||||
export class Gridview {
|
||||
private _root: BranchNode
|
||||
public readonly element: HTMLElement
|
||||
private _root: BranchNode;
|
||||
public readonly element: HTMLElement;
|
||||
|
||||
private readonly _onDidChange = new Emitter<number | undefined>()
|
||||
readonly onDidChange: Event<number | undefined> = this._onDidChange.event
|
||||
private readonly _onDidChange = new Emitter<number | undefined>();
|
||||
readonly onDidChange: Event<number | undefined> = this._onDidChange.event;
|
||||
|
||||
public serialize() {
|
||||
return {
|
||||
@ -219,34 +221,34 @@ export class Gridview {
|
||||
height: this.height,
|
||||
width: this.width,
|
||||
orientation: this.orientation,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.root.dispose()
|
||||
this.root.dispose();
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.root.dispose()
|
||||
this.root.dispose();
|
||||
this.root = new BranchNode(
|
||||
Orientation.HORIZONTAL,
|
||||
this.proportionalLayout,
|
||||
0,
|
||||
0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public deserialize(json: any, deserializer: IViewDeserializer) {
|
||||
const orientation = json.orientation
|
||||
const height = json.height
|
||||
const orientation = json.orientation;
|
||||
const height = json.height;
|
||||
|
||||
this.orientation = orientation
|
||||
this.orientation = orientation;
|
||||
this._deserialize(
|
||||
json.root as ISerializedBranchNode,
|
||||
orientation,
|
||||
deserializer,
|
||||
height
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _deserialize(
|
||||
@ -260,7 +262,7 @@ export class Gridview {
|
||||
orientation,
|
||||
deserializer,
|
||||
orthogonalSize
|
||||
) as BranchNode
|
||||
) as BranchNode;
|
||||
}
|
||||
|
||||
private _deserializeNode(
|
||||
@ -269,9 +271,9 @@ export class Gridview {
|
||||
deserializer: IViewDeserializer,
|
||||
orthogonalSize: number
|
||||
): Node {
|
||||
let result: Node
|
||||
let result: Node;
|
||||
if (node.type === 'branch') {
|
||||
const serializedChildren = node.data as ISerializedNode[]
|
||||
const serializedChildren = node.data as ISerializedNode[];
|
||||
const children = serializedChildren.map((serializedChild) => {
|
||||
return {
|
||||
node: this._deserializeNode(
|
||||
@ -280,8 +282,8 @@ export class Gridview {
|
||||
deserializer,
|
||||
node.size
|
||||
),
|
||||
} as INodeDescriptor
|
||||
})
|
||||
} as INodeDescriptor;
|
||||
});
|
||||
|
||||
result = new BranchNode(
|
||||
orientation,
|
||||
@ -289,276 +291,280 @@ export class Gridview {
|
||||
node.size,
|
||||
orthogonalSize,
|
||||
children
|
||||
)
|
||||
);
|
||||
} else {
|
||||
result = new LeafNode(
|
||||
deserializer.fromJSON(node.data),
|
||||
orientation,
|
||||
orthogonalSize,
|
||||
node.size
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
public get orientation() {
|
||||
return this.root.orientation
|
||||
return this.root.orientation;
|
||||
}
|
||||
|
||||
public set orientation(orientation: Orientation) {
|
||||
if (this._root.orientation === orientation) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const { size, orthogonalSize } = this._root
|
||||
this.root = flipNode(this._root, orthogonalSize, size)
|
||||
this.root.layout(size, orthogonalSize)
|
||||
const { size, orthogonalSize } = this._root;
|
||||
this.root = flipNode(this._root, orthogonalSize, size);
|
||||
this.root.layout(size, orthogonalSize);
|
||||
}
|
||||
|
||||
private get root(): BranchNode {
|
||||
return this._root
|
||||
return this._root;
|
||||
}
|
||||
|
||||
private disposable: IDisposable
|
||||
private disposable: IDisposable;
|
||||
|
||||
private set root(root: BranchNode) {
|
||||
const oldRoot = this._root
|
||||
const oldRoot = this._root;
|
||||
|
||||
if (oldRoot) {
|
||||
this.disposable?.dispose()
|
||||
oldRoot.dispose()
|
||||
this.element.removeChild(oldRoot.element)
|
||||
this.disposable?.dispose();
|
||||
oldRoot.dispose();
|
||||
this.element.removeChild(oldRoot.element);
|
||||
}
|
||||
|
||||
this._root = root
|
||||
this._root = root;
|
||||
this.disposable = this._root.onDidChange((e) => {
|
||||
this._onDidChange.fire(e)
|
||||
})
|
||||
this.element.appendChild(root.element)
|
||||
this._onDidChange.fire(e);
|
||||
});
|
||||
this.element.appendChild(root.element);
|
||||
}
|
||||
|
||||
public next(location: number[]) {
|
||||
return this.progmaticSelect(location)
|
||||
return this.progmaticSelect(location);
|
||||
}
|
||||
|
||||
public preivous(location: number[]) {
|
||||
return this.progmaticSelect(location, true)
|
||||
return this.progmaticSelect(location, true);
|
||||
}
|
||||
|
||||
private progmaticSelect(location: number[], reverse = false) {
|
||||
const [rest, index] = tail(location)
|
||||
const [path, node] = this.getNode(location)
|
||||
const [rest, index] = tail(location);
|
||||
const [path, node] = this.getNode(location);
|
||||
|
||||
if (!(node instanceof LeafNode)) {
|
||||
throw new Error('invalid location')
|
||||
throw new Error('invalid location');
|
||||
}
|
||||
|
||||
const findLeaf = (node: Node, last: boolean): LeafNode => {
|
||||
if (node instanceof LeafNode) {
|
||||
return node
|
||||
return node;
|
||||
}
|
||||
if (node instanceof BranchNode) {
|
||||
return findLeaf(
|
||||
node.children[last ? node.children.length - 1 : 0],
|
||||
last
|
||||
)
|
||||
);
|
||||
}
|
||||
throw new Error('invalid node')
|
||||
}
|
||||
throw new Error('invalid node');
|
||||
};
|
||||
|
||||
for (let i = path.length - 1; i > -1; i--) {
|
||||
const n = path[i]
|
||||
const l = location[i] || 0
|
||||
const n = path[i];
|
||||
const l = location[i] || 0;
|
||||
const canProgressInCurrentLevel = reverse
|
||||
? l - 1 > -1
|
||||
: l + 1 < n.children.length
|
||||
: l + 1 < n.children.length;
|
||||
if (canProgressInCurrentLevel) {
|
||||
return findLeaf(n.children[reverse ? l - 1 : l + 1], reverse)
|
||||
return findLeaf(n.children[reverse ? l - 1 : l + 1], reverse);
|
||||
}
|
||||
}
|
||||
|
||||
return findLeaf(this.root, reverse)
|
||||
return findLeaf(this.root, reverse);
|
||||
}
|
||||
|
||||
get width(): number {
|
||||
return this.root.width
|
||||
return this.root.width;
|
||||
}
|
||||
get height(): number {
|
||||
return this.root.height
|
||||
return this.root.height;
|
||||
}
|
||||
|
||||
get minimumWidth(): number {
|
||||
return this.root.minimumWidth
|
||||
return this.root.minimumWidth;
|
||||
}
|
||||
get minimumHeight(): number {
|
||||
return this.root.minimumHeight
|
||||
return this.root.minimumHeight;
|
||||
}
|
||||
get maximumWidth(): number {
|
||||
return this.root.maximumHeight
|
||||
return this.root.maximumHeight;
|
||||
}
|
||||
get maximumHeight(): number {
|
||||
return this.root.maximumHeight
|
||||
return this.root.maximumHeight;
|
||||
}
|
||||
|
||||
constructor(readonly proportionalLayout: boolean) {
|
||||
this.element = document.createElement('div')
|
||||
this.element.className = 'grid-view'
|
||||
this.element = document.createElement('div');
|
||||
this.element.className = 'grid-view';
|
||||
this.root = new BranchNode(
|
||||
Orientation.HORIZONTAL,
|
||||
proportionalLayout,
|
||||
0,
|
||||
0
|
||||
)
|
||||
);
|
||||
|
||||
this.element.appendChild(this.root.element)
|
||||
this.element.appendChild(this.root.element);
|
||||
}
|
||||
|
||||
public moveView(parentLocation: number[], from: number, to: number): void {
|
||||
const [, parent] = this.getNode(parentLocation)
|
||||
const [, parent] = this.getNode(parentLocation);
|
||||
|
||||
if (!(parent instanceof BranchNode)) {
|
||||
throw new Error('Invalid location')
|
||||
throw new Error('Invalid location');
|
||||
}
|
||||
|
||||
parent.moveChild(from, to)
|
||||
parent.moveChild(from, to);
|
||||
}
|
||||
|
||||
public addView(view: IGridView, size: number | Sizing, location: number[]) {
|
||||
const [rest, index] = tail(location)
|
||||
const [rest, index] = tail(location);
|
||||
|
||||
const [pathToParent, parent] = this.getNode(rest)
|
||||
const [pathToParent, parent] = this.getNode(rest);
|
||||
|
||||
if (parent instanceof BranchNode) {
|
||||
const node = new LeafNode(
|
||||
view,
|
||||
orthogonal(parent.orientation),
|
||||
parent.orthogonalSize
|
||||
)
|
||||
parent.addChild(node, size, index)
|
||||
);
|
||||
parent.addChild(node, size, index);
|
||||
} else {
|
||||
const [grandParent, ..._] = [...pathToParent].reverse()
|
||||
const [parentIndex, ...__] = [...rest].reverse()
|
||||
const [grandParent, ..._] = [...pathToParent].reverse();
|
||||
const [parentIndex, ...__] = [...rest].reverse();
|
||||
|
||||
let newSiblingSize: number | Sizing = 0
|
||||
let newSiblingSize: number | Sizing = 0;
|
||||
|
||||
grandParent.removeChild(parentIndex)
|
||||
grandParent.removeChild(parentIndex);
|
||||
|
||||
const newParent = new BranchNode(
|
||||
parent.orientation,
|
||||
this.proportionalLayout,
|
||||
parent.size,
|
||||
parent.orthogonalSize
|
||||
)
|
||||
grandParent.addChild(newParent, parent.size, parentIndex)
|
||||
);
|
||||
grandParent.addChild(newParent, parent.size, parentIndex);
|
||||
|
||||
const newSibling = new LeafNode(
|
||||
parent.view,
|
||||
grandParent.orientation,
|
||||
parent.size
|
||||
)
|
||||
newParent.addChild(newSibling, newSiblingSize, 0)
|
||||
);
|
||||
newParent.addChild(newSibling, newSiblingSize, 0);
|
||||
|
||||
if (typeof size !== 'number' && size.type === 'split') {
|
||||
size = { type: 'split', index: 0 }
|
||||
size = { type: 'split', index: 0 };
|
||||
}
|
||||
|
||||
const node = new LeafNode(
|
||||
view,
|
||||
grandParent.orientation,
|
||||
parent.size
|
||||
)
|
||||
newParent.addChild(node, size, index)
|
||||
);
|
||||
newParent.addChild(node, size, index);
|
||||
}
|
||||
}
|
||||
|
||||
public remove(view: IGridView, sizing?: Sizing) {
|
||||
const location = getGridLocation(view.element)
|
||||
return this.removeView(location, sizing)
|
||||
const location = getGridLocation(view.element);
|
||||
return this.removeView(location, sizing);
|
||||
}
|
||||
|
||||
removeView(location: number[], sizing?: Sizing): IGridView {
|
||||
const [rest, index] = tail(location)
|
||||
const [pathToParent, parent] = this.getNode(rest)
|
||||
const [rest, index] = tail(location);
|
||||
const [pathToParent, parent] = this.getNode(rest);
|
||||
|
||||
if (!(parent instanceof BranchNode)) {
|
||||
throw new Error('Invalid location')
|
||||
throw new Error('Invalid location');
|
||||
}
|
||||
|
||||
const node = parent.children[index]
|
||||
const node = parent.children[index];
|
||||
|
||||
if (!(node instanceof LeafNode)) {
|
||||
throw new Error('Invalid location')
|
||||
throw new Error('Invalid location');
|
||||
}
|
||||
|
||||
parent.removeChild(index, sizing)
|
||||
parent.removeChild(index, sizing);
|
||||
|
||||
if (parent.children.length === 0) {
|
||||
throw new Error('Invalid grid state')
|
||||
throw new Error('Invalid grid state');
|
||||
}
|
||||
|
||||
if (parent.children.length > 1) {
|
||||
return node.view
|
||||
return node.view;
|
||||
}
|
||||
|
||||
if (pathToParent.length === 0) {
|
||||
// parent is root
|
||||
const sibling = parent.children[0]
|
||||
const sibling = parent.children[0];
|
||||
|
||||
if (sibling instanceof LeafNode) {
|
||||
return node.view
|
||||
return node.view;
|
||||
}
|
||||
|
||||
// we must promote sibling to be the new root
|
||||
parent.removeChild(0, sizing)
|
||||
this.root = sibling
|
||||
return node.view
|
||||
parent.removeChild(0, sizing);
|
||||
this.root = sibling;
|
||||
return node.view;
|
||||
}
|
||||
|
||||
const [grandParent, ..._] = [...pathToParent].reverse()
|
||||
const [parentIndex, ...__] = [...rest].reverse()
|
||||
const [grandParent, ..._] = [...pathToParent].reverse();
|
||||
const [parentIndex, ...__] = [...rest].reverse();
|
||||
|
||||
const sibling = parent.children[0]
|
||||
parent.removeChild(0, sizing)
|
||||
const sibling = parent.children[0];
|
||||
parent.removeChild(0, sizing);
|
||||
|
||||
const sizes = grandParent.children.map((_, i) =>
|
||||
grandParent.getChildSize(i)
|
||||
)
|
||||
grandParent.removeChild(parentIndex, sizing)
|
||||
);
|
||||
grandParent.removeChild(parentIndex, sizing);
|
||||
|
||||
if (sibling instanceof BranchNode) {
|
||||
sizes.splice(parentIndex, 1, ...sibling.children.map((c) => c.size))
|
||||
sizes.splice(
|
||||
parentIndex,
|
||||
1,
|
||||
...sibling.children.map((c) => c.size)
|
||||
);
|
||||
|
||||
for (let i = 0; i < sibling.children.length; i++) {
|
||||
const child = sibling.children[i]
|
||||
grandParent.addChild(child, child.size, parentIndex + i)
|
||||
const child = sibling.children[i];
|
||||
grandParent.addChild(child, child.size, parentIndex + i);
|
||||
}
|
||||
} else {
|
||||
const newSibling = new LeafNode(
|
||||
sibling.view,
|
||||
orthogonal(sibling.orientation),
|
||||
sibling.size
|
||||
)
|
||||
);
|
||||
grandParent.addChild(
|
||||
newSibling,
|
||||
sibling.orthogonalSize,
|
||||
parentIndex
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < sizes.length; i++) {
|
||||
grandParent.resizeChild(i, sizes[i])
|
||||
grandParent.resizeChild(i, sizes[i]);
|
||||
}
|
||||
|
||||
return node.view
|
||||
return node.view;
|
||||
}
|
||||
|
||||
public layout(width: number, height: number) {
|
||||
const [size, orthogonalSize] =
|
||||
this.root.orientation === Orientation.HORIZONTAL
|
||||
? [height, width]
|
||||
: [width, height]
|
||||
this.root.layout(size, orthogonalSize)
|
||||
: [width, height];
|
||||
this.root.layout(size, orthogonalSize);
|
||||
}
|
||||
|
||||
private getNode(
|
||||
@ -567,22 +573,22 @@ export class Gridview {
|
||||
path: BranchNode[] = []
|
||||
): [BranchNode[], Node] {
|
||||
if (location.length === 0) {
|
||||
return [path, node]
|
||||
return [path, node];
|
||||
}
|
||||
|
||||
if (!(node instanceof BranchNode)) {
|
||||
throw new Error('Invalid location')
|
||||
throw new Error('Invalid location');
|
||||
}
|
||||
|
||||
const [index, ...rest] = location
|
||||
const [index, ...rest] = location;
|
||||
|
||||
if (index < 0 || index >= node.children.length) {
|
||||
throw new Error('Invalid location')
|
||||
throw new Error('Invalid location');
|
||||
}
|
||||
|
||||
const child = node.children[index]
|
||||
path.push(node)
|
||||
const child = node.children[index];
|
||||
path.push(node);
|
||||
|
||||
return this.getNode(rest, child, path)
|
||||
return this.getNode(rest, child, path);
|
||||
}
|
||||
}
|
||||
|
@ -1,73 +1,73 @@
|
||||
import { IView, LayoutPriority, Orientation } from '../splitview/splitview'
|
||||
import { Emitter, Event } from '../events'
|
||||
import { IGridView } from './gridview'
|
||||
import { IView, LayoutPriority, Orientation } from '../splitview/splitview';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { IGridView } from './gridview';
|
||||
|
||||
export class LeafNode implements IView {
|
||||
private readonly _onDidChange = new Emitter<number | undefined>()
|
||||
readonly onDidChange: Event<number | undefined> = this._onDidChange.event
|
||||
private _size: number
|
||||
private _orthogonalSize: number
|
||||
private readonly _onDidChange = new Emitter<number | undefined>();
|
||||
readonly onDidChange: Event<number | undefined> = this._onDidChange.event;
|
||||
private _size: number;
|
||||
private _orthogonalSize: number;
|
||||
|
||||
public dispose() {}
|
||||
|
||||
private get minimumWidth(): number {
|
||||
return this.view.minimumWidth
|
||||
return this.view.minimumWidth;
|
||||
}
|
||||
|
||||
private get maximumWidth(): number {
|
||||
return this.view.maximumWidth
|
||||
return this.view.maximumWidth;
|
||||
}
|
||||
|
||||
private get minimumHeight(): number {
|
||||
return this.view.minimumHeight
|
||||
return this.view.minimumHeight;
|
||||
}
|
||||
|
||||
private get maximumHeight(): number {
|
||||
return this.view.maximumHeight
|
||||
return this.view.maximumHeight;
|
||||
}
|
||||
|
||||
get priority(): LayoutPriority | undefined {
|
||||
return this.view.priority
|
||||
return this.view.priority;
|
||||
}
|
||||
|
||||
get snapSize() {
|
||||
return this.view.snap ? this.minimumSize / 2 : undefined
|
||||
return this.view.snap ? this.minimumSize / 2 : undefined;
|
||||
}
|
||||
|
||||
get minimumSize(): number {
|
||||
return this.orientation === Orientation.HORIZONTAL
|
||||
? this.minimumHeight
|
||||
: this.minimumWidth
|
||||
: this.minimumWidth;
|
||||
}
|
||||
|
||||
get maximumSize(): number {
|
||||
return this.orientation === Orientation.HORIZONTAL
|
||||
? this.maximumHeight
|
||||
: this.maximumWidth
|
||||
: this.maximumWidth;
|
||||
}
|
||||
|
||||
get minimumOrthogonalSize(): number {
|
||||
return this.orientation === Orientation.HORIZONTAL
|
||||
? this.minimumWidth
|
||||
: this.minimumHeight
|
||||
: this.minimumHeight;
|
||||
}
|
||||
|
||||
get maximumOrthogonalSize(): number {
|
||||
return this.orientation === Orientation.HORIZONTAL
|
||||
? this.maximumWidth
|
||||
: this.maximumHeight
|
||||
: this.maximumHeight;
|
||||
}
|
||||
|
||||
get orthogonalSize() {
|
||||
return this._orthogonalSize
|
||||
return this._orthogonalSize;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this._size
|
||||
return this._size;
|
||||
}
|
||||
|
||||
get element() {
|
||||
return this.view.element
|
||||
return this.view.element;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ -76,19 +76,19 @@ export class LeafNode implements IView {
|
||||
orthogonalSize: number,
|
||||
size: number = 0
|
||||
) {
|
||||
this._orthogonalSize = orthogonalSize
|
||||
this._size = size
|
||||
this._orthogonalSize = orthogonalSize;
|
||||
this._size = size;
|
||||
}
|
||||
|
||||
public layout(size: number, orthogonalSize: number) {
|
||||
this._size = size
|
||||
this._orthogonalSize = orthogonalSize
|
||||
this._size = size;
|
||||
this._orthogonalSize = orthogonalSize;
|
||||
|
||||
const [width, height] =
|
||||
this.orientation === Orientation.HORIZONTAL
|
||||
? [orthogonalSize, size]
|
||||
: [size, orthogonalSize]
|
||||
: [size, orthogonalSize];
|
||||
|
||||
this.view.layout(width, height, 0, 0)
|
||||
this.view.layout(width, height, 0, 0);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { BranchNode } from './branchNode'
|
||||
import { LeafNode } from './leafNode'
|
||||
import { BranchNode } from './branchNode';
|
||||
import { LeafNode } from './leafNode';
|
||||
|
||||
export type Node = BranchNode | LeafNode
|
||||
export type Node = BranchNode | LeafNode;
|
||||
|
@ -1,22 +1,22 @@
|
||||
export class ActionContainer {
|
||||
private _element: HTMLElement
|
||||
private _list: HTMLElement
|
||||
private _element: HTMLElement;
|
||||
private _list: HTMLElement;
|
||||
|
||||
get element() {
|
||||
return this._element
|
||||
return this._element;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this._element = document.createElement('div')
|
||||
this._element.className = 'actions-bar'
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'actions-bar';
|
||||
|
||||
this._list = document.createElement('ul')
|
||||
this._list.className = 'actions-container'
|
||||
this._list = document.createElement('ul');
|
||||
this._list.className = 'actions-container';
|
||||
|
||||
this._element.appendChild(this._list)
|
||||
this._element.appendChild(this._list);
|
||||
}
|
||||
|
||||
public add(element: HTMLElement) {
|
||||
this._list.appendChild(element)
|
||||
this._list.appendChild(element);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { PanelOptions } from '../../layout/options'
|
||||
import { PanelOptions } from '../../layout/options';
|
||||
|
||||
export const DATA_KEY = 'splitview/transfer'
|
||||
export const DATA_KEY = 'splitview/transfer';
|
||||
|
||||
export const isPanelTransferEvent = (event: DragEvent) => {
|
||||
return event.dataTransfer.types.includes(DATA_KEY)
|
||||
}
|
||||
return event.dataTransfer.types.includes(DATA_KEY);
|
||||
};
|
||||
|
||||
export enum DragType {
|
||||
ITEM = 'group_drag',
|
||||
@ -12,113 +12,113 @@ export enum DragType {
|
||||
}
|
||||
|
||||
export interface DragItem {
|
||||
itemId: string
|
||||
groupId: string
|
||||
itemId: string;
|
||||
groupId: string;
|
||||
}
|
||||
|
||||
export interface ExternalDragItem extends PanelOptions {}
|
||||
|
||||
export type DataObject = DragItem | ExternalDragItem
|
||||
export type DataObject = DragItem | ExternalDragItem;
|
||||
|
||||
/**
|
||||
* Determine whether this data belong to that of an event that was started by
|
||||
* dragging a tab component
|
||||
*/
|
||||
export const isTabDragEvent = (data: any): data is DragItem => {
|
||||
return data.type === DragType.ITEM
|
||||
}
|
||||
return data.type === DragType.ITEM;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine whether this data belong to that of an event that was started by
|
||||
* a custom drag-enable component
|
||||
*/
|
||||
export const isCustomDragEvent = (data: any): data is ExternalDragItem => {
|
||||
return data.type === DragType.EXTERNAL
|
||||
}
|
||||
return data.type === DragType.EXTERNAL;
|
||||
};
|
||||
|
||||
export const extractData = (event: DragEvent): DataObject => {
|
||||
const data = JSON.parse(event.dataTransfer.getData(DATA_KEY))
|
||||
const data = JSON.parse(event.dataTransfer.getData(DATA_KEY));
|
||||
|
||||
if (!data) {
|
||||
console.warn(`[dragEvent] ${DATA_KEY} data is missing`)
|
||||
console.warn(`[dragEvent] ${DATA_KEY} data is missing`);
|
||||
}
|
||||
|
||||
if (typeof data.type !== 'string') {
|
||||
console.warn(`[dragEvent] invalid type ${data.type}`)
|
||||
console.warn(`[dragEvent] invalid type ${data.type}`);
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
class DataTransfer {
|
||||
private map = new Map<string, string>()
|
||||
private map = new Map<string, string>();
|
||||
|
||||
public setData(format: string, data: string) {
|
||||
this.map.set(format, data)
|
||||
this.map.set(format, data);
|
||||
}
|
||||
|
||||
public getData(format: string) {
|
||||
const data = this.map.get(format)
|
||||
return data
|
||||
const data = this.map.get(format);
|
||||
return data;
|
||||
}
|
||||
|
||||
public has(format: string) {
|
||||
return this.map.has(format)
|
||||
return this.map.has(format);
|
||||
}
|
||||
|
||||
public removeData(format: string) {
|
||||
const data = this.getData(format)
|
||||
this.map.delete(format)
|
||||
return data
|
||||
const data = this.getData(format);
|
||||
this.map.delete(format);
|
||||
return data;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.map.size
|
||||
return this.map.size;
|
||||
}
|
||||
}
|
||||
|
||||
export const DataTransferSingleton = new DataTransfer()
|
||||
export const DataTransferSingleton = new DataTransfer();
|
||||
|
||||
/**
|
||||
* A singleton to store transfer data during drag & drop operations that are only valid within the application.
|
||||
*/
|
||||
export class LocalSelectionTransfer<T> {
|
||||
private static readonly INSTANCE = new LocalSelectionTransfer()
|
||||
private static readonly INSTANCE = new LocalSelectionTransfer();
|
||||
|
||||
private data?: T[]
|
||||
private proto?: T
|
||||
private data?: T[];
|
||||
private proto?: T;
|
||||
|
||||
private constructor() {
|
||||
// protect against external instantiation
|
||||
}
|
||||
|
||||
static getInstance<T>(): LocalSelectionTransfer<T> {
|
||||
return LocalSelectionTransfer.INSTANCE as LocalSelectionTransfer<T>
|
||||
return LocalSelectionTransfer.INSTANCE as LocalSelectionTransfer<T>;
|
||||
}
|
||||
|
||||
hasData(proto: T): boolean {
|
||||
return proto && proto === this.proto
|
||||
return proto && proto === this.proto;
|
||||
}
|
||||
|
||||
clearData(proto: T): void {
|
||||
if (this.hasData(proto)) {
|
||||
this.proto = undefined
|
||||
this.data = undefined
|
||||
this.proto = undefined;
|
||||
this.data = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
getData(proto: T): T[] | undefined {
|
||||
if (this.hasData(proto)) {
|
||||
return this.data
|
||||
return this.data;
|
||||
}
|
||||
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setData(data: T[], proto: T): void {
|
||||
if (proto) {
|
||||
this.data = data
|
||||
this.proto = proto
|
||||
this.data = data;
|
||||
this.proto = proto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Emitter, Event } from '../../events'
|
||||
import { DataTransferSingleton } from './dataTransfer'
|
||||
import { Emitter, Event } from '../../events';
|
||||
import { DataTransferSingleton } from './dataTransfer';
|
||||
|
||||
export enum Position {
|
||||
Top = 'Top',
|
||||
@ -10,19 +10,19 @@ export enum Position {
|
||||
}
|
||||
|
||||
export interface DroptargetEvent {
|
||||
position: Position
|
||||
event: DragEvent
|
||||
position: Position;
|
||||
event: DragEvent;
|
||||
}
|
||||
|
||||
const HAS_PROCESSED_KEY = '__drop_target_processed__'
|
||||
const HAS_PROCESSED_KEY = '__drop_target_processed__';
|
||||
|
||||
export const hasProcessed = (event: DragEvent) =>
|
||||
!!(event as any)[HAS_PROCESSED_KEY]
|
||||
!!(event as any)[HAS_PROCESSED_KEY];
|
||||
|
||||
// tagging events as processed is better than calling .stopPropagation() which is the root of all evil
|
||||
const setEventAsProcessed = (event: DragEvent) => {
|
||||
event[HAS_PROCESSED_KEY] = true
|
||||
}
|
||||
event[HAS_PROCESSED_KEY] = true;
|
||||
};
|
||||
|
||||
const toggleClassName = (
|
||||
element: HTMLElement,
|
||||
@ -30,36 +30,36 @@ const toggleClassName = (
|
||||
addOrRemove: boolean
|
||||
) => {
|
||||
if (addOrRemove && !element.classList.contains(className)) {
|
||||
element.classList.add(className)
|
||||
element.classList.add(className);
|
||||
} else if (!addOrRemove && element.classList.contains(className)) {
|
||||
element.classList.remove(className)
|
||||
element.classList.remove(className);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export class Droptarget {
|
||||
private target: HTMLElement
|
||||
private overlay: HTMLElement
|
||||
private state: Position | undefined
|
||||
private target: HTMLElement;
|
||||
private overlay: HTMLElement;
|
||||
private state: Position | undefined;
|
||||
|
||||
private readonly _onDidChange = new Emitter<DroptargetEvent>()
|
||||
readonly onDidChange: Event<DroptargetEvent> = this._onDidChange.event
|
||||
private readonly _onDidChange = new Emitter<DroptargetEvent>();
|
||||
readonly onDidChange: Event<DroptargetEvent> = this._onDidChange.event;
|
||||
|
||||
constructor(
|
||||
private element: HTMLElement,
|
||||
private options: {
|
||||
isDisabled: () => boolean
|
||||
isDirectional: boolean
|
||||
id: string
|
||||
enableExternalDragEvents?: boolean
|
||||
isDisabled: () => boolean;
|
||||
isDirectional: boolean;
|
||||
id: string;
|
||||
enableExternalDragEvents?: boolean;
|
||||
}
|
||||
) {
|
||||
this.element.addEventListener('dragenter', this.onDragEnter)
|
||||
this.element.addEventListener('dragenter', this.onDragEnter);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this._onDidChange.dispose()
|
||||
this.removeDropTarget()
|
||||
this.element.removeEventListener('dragenter', this.onDragEnter)
|
||||
this._onDidChange.dispose();
|
||||
this.removeDropTarget();
|
||||
this.element.removeEventListener('dragenter', this.onDragEnter);
|
||||
}
|
||||
|
||||
private onDragEnter = (event: DragEvent) => {
|
||||
@ -67,104 +67,104 @@ export class Droptarget {
|
||||
!this.options.enableExternalDragEvents &&
|
||||
!DataTransferSingleton.has(this.options.id)
|
||||
) {
|
||||
console.debug('[droptarget] invalid event')
|
||||
return
|
||||
console.debug('[droptarget] invalid event');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.options.isDisabled()) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
if (!this.target) {
|
||||
console.debug('[droptarget] created')
|
||||
this.target = document.createElement('div')
|
||||
this.target.className = 'drop-target-dropzone'
|
||||
this.overlay = document.createElement('div')
|
||||
this.overlay.className = 'drop-target-selection'
|
||||
console.debug('[droptarget] created');
|
||||
this.target = document.createElement('div');
|
||||
this.target.className = 'drop-target-dropzone';
|
||||
this.overlay = document.createElement('div');
|
||||
this.overlay.className = 'drop-target-selection';
|
||||
//
|
||||
this.target.addEventListener('dragover', this.onDragOver)
|
||||
this.target.addEventListener('dragleave', this.onDragLeave)
|
||||
this.target.addEventListener('drop', this.onDrop)
|
||||
this.target.appendChild(this.overlay)
|
||||
this.target.addEventListener('dragover', this.onDragOver);
|
||||
this.target.addEventListener('dragleave', this.onDragLeave);
|
||||
this.target.addEventListener('drop', this.onDrop);
|
||||
this.target.appendChild(this.overlay);
|
||||
|
||||
this.element.classList.add('drop-target')
|
||||
this.element.append(this.target)
|
||||
this.element.classList.add('drop-target');
|
||||
this.element.append(this.target);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private onDrop = (event: DragEvent) => {
|
||||
if (
|
||||
!this.options.enableExternalDragEvents &&
|
||||
!DataTransferSingleton.has(this.options.id)
|
||||
) {
|
||||
console.debug('[dragtarget] invalid')
|
||||
return
|
||||
console.debug('[dragtarget] invalid');
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug('[dragtarget] drop')
|
||||
this.removeDropTarget()
|
||||
console.debug('[dragtarget] drop');
|
||||
this.removeDropTarget();
|
||||
|
||||
if (!hasProcessed(event)) {
|
||||
this._onDidChange.fire({ position: this.state, event })
|
||||
this._onDidChange.fire({ position: this.state, event });
|
||||
} else {
|
||||
console.debug('[dragtarget] already processed')
|
||||
console.debug('[dragtarget] already processed');
|
||||
}
|
||||
this.state = undefined
|
||||
this.state = undefined;
|
||||
|
||||
setEventAsProcessed(event)
|
||||
}
|
||||
setEventAsProcessed(event);
|
||||
};
|
||||
|
||||
private onDragOver = (event: DragEvent) => {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
if (!this.options.isDirectional) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const width = this.target.clientWidth
|
||||
const height = this.target.clientHeight
|
||||
const x = event.offsetX
|
||||
const y = event.offsetY
|
||||
const xp = (100 * x) / width
|
||||
const yp = (100 * y) / height
|
||||
const width = this.target.clientWidth;
|
||||
const height = this.target.clientHeight;
|
||||
const x = event.offsetX;
|
||||
const y = event.offsetY;
|
||||
const xp = (100 * x) / width;
|
||||
const yp = (100 * y) / height;
|
||||
|
||||
const isRight = xp > 80
|
||||
const isLeft = xp < 20
|
||||
const isTop = !isRight && !isLeft && yp < 20
|
||||
const isBottom = !isRight && !isLeft && yp > 80
|
||||
const isRight = xp > 80;
|
||||
const isLeft = xp < 20;
|
||||
const isTop = !isRight && !isLeft && yp < 20;
|
||||
const isBottom = !isRight && !isLeft && yp > 80;
|
||||
|
||||
toggleClassName(this.overlay, 'right', isRight)
|
||||
toggleClassName(this.overlay, 'left', isLeft)
|
||||
toggleClassName(this.overlay, 'top', isTop)
|
||||
toggleClassName(this.overlay, 'bottom', isBottom)
|
||||
toggleClassName(this.overlay, 'right', isRight);
|
||||
toggleClassName(this.overlay, 'left', isLeft);
|
||||
toggleClassName(this.overlay, 'top', isTop);
|
||||
toggleClassName(this.overlay, 'bottom', isBottom);
|
||||
|
||||
if (isRight) {
|
||||
this.state = Position.Right
|
||||
this.state = Position.Right;
|
||||
} else if (isLeft) {
|
||||
this.state = Position.Left
|
||||
this.state = Position.Left;
|
||||
} else if (isTop) {
|
||||
this.state = Position.Top
|
||||
this.state = Position.Top;
|
||||
} else if (isBottom) {
|
||||
this.state = Position.Bottom
|
||||
this.state = Position.Bottom;
|
||||
} else {
|
||||
this.state = Position.Center
|
||||
this.state = Position.Center;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private onDragLeave = (event: DragEvent) => {
|
||||
console.debug('[droptarget] leave')
|
||||
this.removeDropTarget()
|
||||
}
|
||||
console.debug('[droptarget] leave');
|
||||
this.removeDropTarget();
|
||||
};
|
||||
|
||||
private removeDropTarget() {
|
||||
if (this.target) {
|
||||
this.target.removeEventListener('dragover', this.onDragOver)
|
||||
this.target.removeEventListener('dragleave', this.onDragLeave)
|
||||
this.target.removeEventListener('drop', this.onDrop)
|
||||
this.element.removeChild(this.target)
|
||||
this.target = undefined
|
||||
this.element.classList.remove('drop-target')
|
||||
this.target.removeEventListener('dragover', this.onDragOver);
|
||||
this.target.removeEventListener('dragleave', this.onDragLeave);
|
||||
this.target.removeEventListener('drop', this.onDrop);
|
||||
this.element.removeChild(this.target);
|
||||
this.target = undefined;
|
||||
this.element.classList.remove('drop-target');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { DroptargetEvent } from './droptarget/droptarget'
|
||||
import { IGroupPanel } from './panel/types'
|
||||
import { DroptargetEvent } from './droptarget/droptarget';
|
||||
import { IGroupPanel } from './panel/types';
|
||||
|
||||
export interface TabDropEvent {
|
||||
event: DroptargetEvent
|
||||
index?: number
|
||||
event: DroptargetEvent;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export enum MouseEventKind {
|
||||
@ -12,8 +12,8 @@ export enum MouseEventKind {
|
||||
}
|
||||
|
||||
export interface LayoutMouseEvent {
|
||||
kind: MouseEventKind
|
||||
event: MouseEvent
|
||||
panel?: IGroupPanel
|
||||
tab?: boolean
|
||||
kind: MouseEventKind;
|
||||
event: MouseEvent;
|
||||
panel?: IGroupPanel;
|
||||
tab?: boolean;
|
||||
}
|
||||
|
@ -1,20 +1,20 @@
|
||||
import { IDisposable, CompositeDisposable, Disposable } from '../lifecycle'
|
||||
import { ITabContainer, TabContainer } from './titlebar/tabContainer'
|
||||
import { IContentContainer, ContentContainer } from './panel/content/content'
|
||||
import { IGridView } from '../gridview/gridview'
|
||||
import { Position, Droptarget, DroptargetEvent } from './droptarget/droptarget'
|
||||
import { Event, Emitter, addDisposableListener } from '../events'
|
||||
import { IGroupAccessor, Layout } from '../layout'
|
||||
import { toggleClass } from '../dom'
|
||||
import { ClosePanelResult, WatermarkPart } from './panel/parts'
|
||||
import { IGroupPanel } from './panel/types'
|
||||
import { timeoutPromise } from '../async'
|
||||
import { IDisposable, CompositeDisposable, Disposable } from '../lifecycle';
|
||||
import { ITabContainer, TabContainer } from './titlebar/tabContainer';
|
||||
import { IContentContainer, ContentContainer } from './panel/content/content';
|
||||
import { Position, Droptarget, DroptargetEvent } from './droptarget/droptarget';
|
||||
import { Event, Emitter, addDisposableListener } from '../events';
|
||||
import { IComponentGridview, IGroupAccessor, Layout } from '../layout';
|
||||
import { toggleClass } from '../dom';
|
||||
import { ClosePanelResult, WatermarkPart } from './panel/parts';
|
||||
import { IGroupPanel } from './panel/types';
|
||||
import { timeoutPromise } from '../async';
|
||||
import {
|
||||
extractData,
|
||||
isTabDragEvent,
|
||||
isCustomDragEvent,
|
||||
isPanelTransferEvent,
|
||||
} from './droptarget/dataTransfer'
|
||||
} from './droptarget/dataTransfer';
|
||||
import { IBaseGridView } from '../layout/baseGrid';
|
||||
|
||||
export const enum GroupChangeKind {
|
||||
GROUP_ACTIVE = 'GROUP_ACTIVE',
|
||||
@ -39,221 +39,219 @@ export const enum GroupChangeKind {
|
||||
}
|
||||
|
||||
export interface IGroupItem {
|
||||
id: string
|
||||
header: { element: HTMLElement }
|
||||
body: { element: HTMLElement }
|
||||
id: string;
|
||||
header: { element: HTMLElement };
|
||||
body: { element: HTMLElement };
|
||||
}
|
||||
|
||||
interface GroupMoveEvent {
|
||||
groupId: string
|
||||
itemId: string
|
||||
target: Position
|
||||
index?: number
|
||||
groupId: string;
|
||||
itemId: string;
|
||||
target: Position;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export interface GroupOptions {
|
||||
panels: IGroupPanel[]
|
||||
activePanel?: IGroupPanel
|
||||
panels: IGroupPanel[];
|
||||
activePanel?: IGroupPanel;
|
||||
}
|
||||
|
||||
export interface GroupChangeEvent {
|
||||
kind: GroupChangeKind
|
||||
panel?: IGroupPanel
|
||||
kind: GroupChangeKind;
|
||||
panel?: IGroupPanel;
|
||||
}
|
||||
|
||||
export interface IGroupview extends IDisposable, IGridView {
|
||||
id: string
|
||||
size: number
|
||||
panels: IGroupPanel[]
|
||||
tabHeight: number
|
||||
setActive: (isActive: boolean) => void
|
||||
export interface IGroupview extends IDisposable, IBaseGridView {
|
||||
size: number;
|
||||
panels: IGroupPanel[];
|
||||
tabHeight: number;
|
||||
// state
|
||||
isPanelActive: (panel: IGroupPanel) => boolean
|
||||
isActive: boolean
|
||||
activePanel: IGroupPanel
|
||||
indexOf(panel: IGroupPanel): number
|
||||
isPanelActive: (panel: IGroupPanel) => boolean;
|
||||
isActive: boolean;
|
||||
activePanel: IGroupPanel;
|
||||
indexOf(panel: IGroupPanel): number;
|
||||
// panel lifecycle
|
||||
openPanel(panel: IGroupPanel, index?: number): void
|
||||
closePanel(panel: IGroupPanel): Promise<boolean>
|
||||
closeAllPanels(): Promise<boolean>
|
||||
containsPanel(panel: IGroupPanel): boolean
|
||||
removePanel: (panelOrId: IGroupPanel | string) => IGroupPanel
|
||||
openPanel(panel: IGroupPanel, index?: number): void;
|
||||
closePanel(panel: IGroupPanel): Promise<boolean>;
|
||||
closeAllPanels(): Promise<boolean>;
|
||||
containsPanel(panel: IGroupPanel): boolean;
|
||||
removePanel: (panelOrId: IGroupPanel | string) => IGroupPanel;
|
||||
// events
|
||||
onDidGroupChange: Event<{ kind: GroupChangeKind }>
|
||||
onMove: Event<GroupMoveEvent>
|
||||
onDidGroupChange: Event<{ kind: GroupChangeKind }>;
|
||||
onMove: Event<GroupMoveEvent>;
|
||||
//
|
||||
startActiveDrag(panel: IGroupPanel): IDisposable
|
||||
startActiveDrag(panel: IGroupPanel): IDisposable;
|
||||
//
|
||||
moveToNext(options?: { panel?: IGroupPanel; suppressRoll?: boolean }): void
|
||||
moveToNext(options?: { panel?: IGroupPanel; suppressRoll?: boolean }): void;
|
||||
moveToPrevious(options?: {
|
||||
panel?: IGroupPanel
|
||||
suppressRoll?: boolean
|
||||
}): void
|
||||
panel?: IGroupPanel;
|
||||
suppressRoll?: boolean;
|
||||
}): void;
|
||||
}
|
||||
|
||||
export interface GroupDropEvent {
|
||||
event: DragEvent
|
||||
target: Position
|
||||
index?: number
|
||||
event: DragEvent;
|
||||
target: Position;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
private _element: HTMLElement
|
||||
private _element: HTMLElement;
|
||||
|
||||
private tabContainer: ITabContainer
|
||||
private contentContainer: IContentContainer
|
||||
private _active: boolean
|
||||
private _activePanel: IGroupPanel
|
||||
private dropTarget: Droptarget
|
||||
private watermark: WatermarkPart
|
||||
private tabContainer: ITabContainer;
|
||||
private contentContainer: IContentContainer;
|
||||
private _active: boolean;
|
||||
private _activePanel: IGroupPanel;
|
||||
private dropTarget: Droptarget;
|
||||
private watermark: WatermarkPart;
|
||||
|
||||
private _width: number
|
||||
private _height: number
|
||||
private _width: number;
|
||||
private _height: number;
|
||||
|
||||
private _panels: IGroupPanel[] = []
|
||||
private _panels: IGroupPanel[] = [];
|
||||
|
||||
private readonly _onMove = new Emitter<GroupMoveEvent>()
|
||||
readonly onMove: Event<GroupMoveEvent> = this._onMove.event
|
||||
private readonly _onMove = new Emitter<GroupMoveEvent>();
|
||||
readonly onMove: Event<GroupMoveEvent> = this._onMove.event;
|
||||
|
||||
private readonly _onDrop = new Emitter<GroupDropEvent>()
|
||||
readonly onDrop: Event<GroupDropEvent> = this._onDrop.event
|
||||
private readonly _onDrop = new Emitter<GroupDropEvent>();
|
||||
readonly onDrop: Event<GroupDropEvent> = this._onDrop.event;
|
||||
|
||||
private readonly _onDidGroupChange = new Emitter<GroupChangeEvent>()
|
||||
private readonly _onDidGroupChange = new Emitter<GroupChangeEvent>();
|
||||
readonly onDidGroupChange: Event<{ kind: GroupChangeKind }> = this
|
||||
._onDidGroupChange.event
|
||||
._onDidGroupChange.event;
|
||||
|
||||
get activePanel() {
|
||||
return this._activePanel
|
||||
return this._activePanel;
|
||||
}
|
||||
|
||||
get tabHeight() {
|
||||
return this.tabContainer.height
|
||||
return this.tabContainer.height;
|
||||
}
|
||||
|
||||
set tabHeight(height: number) {
|
||||
this.tabContainer.height = height
|
||||
this.layout(this._width, this._height)
|
||||
this.tabContainer.height = height;
|
||||
this.layout(this._width, this._height);
|
||||
}
|
||||
|
||||
get isActive() {
|
||||
return this._active
|
||||
return this._active;
|
||||
}
|
||||
|
||||
get panels() {
|
||||
return this._panels
|
||||
return this._panels;
|
||||
}
|
||||
|
||||
get element() {
|
||||
return this._element
|
||||
return this._element;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this._panels.length
|
||||
return this._panels.length;
|
||||
}
|
||||
|
||||
get isEmpty() {
|
||||
return this._panels.length === 0
|
||||
return this._panels.length === 0;
|
||||
}
|
||||
|
||||
get minimumHeight() {
|
||||
return 100
|
||||
return 100;
|
||||
}
|
||||
|
||||
get maximumHeight() {
|
||||
return Number.MAX_SAFE_INTEGER
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
get minimumWidth() {
|
||||
return 100
|
||||
return 100;
|
||||
}
|
||||
|
||||
get maximumWidth() {
|
||||
return Number.MAX_SAFE_INTEGER
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
public indexOf(panel: IGroupPanel) {
|
||||
return this.tabContainer.indexOf(panel.id)
|
||||
return this.tabContainer.indexOf(panel.id);
|
||||
}
|
||||
|
||||
public toJSON(): object {
|
||||
return {
|
||||
views: this.panels.map((panel) => panel.id),
|
||||
activeView: this._activePanel?.id,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public startActiveDrag(panel: IGroupPanel): IDisposable {
|
||||
const index = this.tabContainer.indexOf(panel.id)
|
||||
const index = this.tabContainer.indexOf(panel.id);
|
||||
if (index > -1) {
|
||||
const tab = this.tabContainer.at(index)
|
||||
tab.startDragEvent()
|
||||
const tab = this.tabContainer.at(index);
|
||||
tab.startDragEvent();
|
||||
return {
|
||||
dispose: () => {
|
||||
tab.stopDragEvent()
|
||||
tab.stopDragEvent();
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
return Disposable.NONE
|
||||
return Disposable.NONE;
|
||||
}
|
||||
|
||||
public moveToNext(options?: {
|
||||
panel?: IGroupPanel
|
||||
suppressRoll?: boolean
|
||||
panel?: IGroupPanel;
|
||||
suppressRoll?: boolean;
|
||||
}) {
|
||||
if (!options) {
|
||||
options = {}
|
||||
options = {};
|
||||
}
|
||||
if (!options.panel) {
|
||||
options.panel = this.activePanel
|
||||
options.panel = this.activePanel;
|
||||
}
|
||||
|
||||
const index = this.panels.indexOf(options.panel)
|
||||
const index = this.panels.indexOf(options.panel);
|
||||
|
||||
let normalizedIndex: number = undefined
|
||||
let normalizedIndex: number = undefined;
|
||||
|
||||
if (index < this.panels.length - 1) {
|
||||
normalizedIndex = index + 1
|
||||
normalizedIndex = index + 1;
|
||||
} else if (!options.suppressRoll) {
|
||||
normalizedIndex = 0
|
||||
normalizedIndex = 0;
|
||||
}
|
||||
|
||||
if (normalizedIndex === undefined) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
this.openPanel(this.panels[normalizedIndex])
|
||||
this.openPanel(this.panels[normalizedIndex]);
|
||||
}
|
||||
|
||||
public moveToPrevious(options?: {
|
||||
panel?: IGroupPanel
|
||||
suppressRoll?: boolean
|
||||
panel?: IGroupPanel;
|
||||
suppressRoll?: boolean;
|
||||
}) {
|
||||
if (!options) {
|
||||
options = {}
|
||||
options = {};
|
||||
}
|
||||
if (!options.panel) {
|
||||
options.panel = this.activePanel
|
||||
options.panel = this.activePanel;
|
||||
}
|
||||
|
||||
const index = this.panels.indexOf(options.panel)
|
||||
const index = this.panels.indexOf(options.panel);
|
||||
|
||||
let normalizedIndex: number = undefined
|
||||
let normalizedIndex: number = undefined;
|
||||
|
||||
if (index > 0) {
|
||||
normalizedIndex = index - 1
|
||||
normalizedIndex = index - 1;
|
||||
} else if (!options.suppressRoll) {
|
||||
normalizedIndex = this.panels.length - 1
|
||||
normalizedIndex = this.panels.length - 1;
|
||||
}
|
||||
|
||||
if (normalizedIndex === undefined) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
this.openPanel(this.panels[normalizedIndex])
|
||||
this.openPanel(this.panels[normalizedIndex]);
|
||||
}
|
||||
|
||||
public containsPanel(panel: IGroupPanel) {
|
||||
return this.panels.includes(panel)
|
||||
return this.panels.includes(panel);
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ -261,16 +259,16 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
public id: string,
|
||||
private options?: GroupOptions
|
||||
) {
|
||||
super()
|
||||
super();
|
||||
|
||||
this.addDisposables(this._onMove, this._onDidGroupChange, this._onDrop)
|
||||
this.addDisposables(this._onMove, this._onDidGroupChange, this._onDrop);
|
||||
|
||||
this._element = document.createElement('div')
|
||||
this._element.className = 'groupview'
|
||||
this._element.tabIndex = -1
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'groupview';
|
||||
this._element.tabIndex = -1;
|
||||
|
||||
this.tabContainer = new TabContainer(this.accessor, this)
|
||||
this.contentContainer = new ContentContainer()
|
||||
this.tabContainer = new TabContainer(this.accessor, this);
|
||||
this.contentContainer = new ContentContainer();
|
||||
this.dropTarget = new Droptarget(this.contentContainer.element, {
|
||||
isDirectional: true,
|
||||
id: this.accessor.id,
|
||||
@ -279,16 +277,16 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
return (
|
||||
this._panels.length === 1 &&
|
||||
this.tabContainer.hasActiveDragEvent
|
||||
)
|
||||
);
|
||||
},
|
||||
enableExternalDragEvents: this.accessor.options
|
||||
.enableExternalDragEvents,
|
||||
})
|
||||
});
|
||||
|
||||
this._element.append(
|
||||
this.tabContainer.element,
|
||||
this.contentContainer.element
|
||||
)
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
this._onMove,
|
||||
@ -297,7 +295,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
this.handleDropEvent(event.event, event.index)
|
||||
),
|
||||
this.contentContainer.onDidFocus(() => {
|
||||
this.accessor.doSetGroupActive(this)
|
||||
this.accessor.doSetGroupActive(this);
|
||||
}),
|
||||
this.dropTarget.onDidChange((event) => {
|
||||
// if we've center dropped on ourself then ignore
|
||||
@ -305,97 +303,99 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
event.position === Position.Center &&
|
||||
this.tabContainer.hasActiveDragEvent
|
||||
) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
this.handleDropEvent(event)
|
||||
this.handleDropEvent(event);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
if (options?.panels) {
|
||||
options.panels.forEach((panel) => {
|
||||
this.openPanel(panel)
|
||||
})
|
||||
this.openPanel(panel);
|
||||
});
|
||||
}
|
||||
if (options?.activePanel) {
|
||||
this.openPanel(options?.activePanel)
|
||||
this.openPanel(options?.activePanel);
|
||||
}
|
||||
|
||||
this.updateContainer()
|
||||
this.updateContainer();
|
||||
}
|
||||
|
||||
public openPanel(panel: IGroupPanel, index: number = this.panels.length) {
|
||||
if (this._activePanel === panel) {
|
||||
this.accessor.doSetGroupActive(this)
|
||||
return
|
||||
this.accessor.doSetGroupActive(this);
|
||||
return;
|
||||
}
|
||||
|
||||
this.doAddPanel(panel, index)
|
||||
this.doAddPanel(panel, index);
|
||||
|
||||
this.tabContainer.openPanel(panel, index)
|
||||
this.contentContainer.openPanel(panel.content.element)
|
||||
this.tabContainer.openPanel(panel, index);
|
||||
this.contentContainer.openPanel(panel.content.element);
|
||||
|
||||
this.doSetActivePanel(panel)
|
||||
this.accessor.doSetGroupActive(this)
|
||||
this.doSetActivePanel(panel);
|
||||
this.accessor.doSetGroupActive(this);
|
||||
|
||||
this.updateContainer()
|
||||
this.updateContainer();
|
||||
}
|
||||
|
||||
public removePanel(groupItemOrId: IGroupPanel | string): IGroupPanel {
|
||||
const id =
|
||||
typeof groupItemOrId === 'string' ? groupItemOrId : groupItemOrId.id
|
||||
typeof groupItemOrId === 'string'
|
||||
? groupItemOrId
|
||||
: groupItemOrId.id;
|
||||
|
||||
const panel = this._panels.find((panel) => panel.id === id)
|
||||
const panel = this._panels.find((panel) => panel.id === id);
|
||||
|
||||
if (!panel) {
|
||||
throw new Error('invalid operation')
|
||||
throw new Error('invalid operation');
|
||||
}
|
||||
|
||||
return this._removePanel(panel)
|
||||
return this._removePanel(panel);
|
||||
}
|
||||
|
||||
public async closeAllPanels() {
|
||||
const index = this.panels.indexOf(this._activePanel)
|
||||
const index = this.panels.indexOf(this._activePanel);
|
||||
|
||||
if (index > -1) {
|
||||
if (this.panels.indexOf(this._activePanel) < 0) {
|
||||
console.warn('active panel not tracked')
|
||||
console.warn('active panel not tracked');
|
||||
}
|
||||
|
||||
const canClose =
|
||||
!this._activePanel.close ||
|
||||
(await this._activePanel.close()) === ClosePanelResult.CLOSE
|
||||
(await this._activePanel.close()) === ClosePanelResult.CLOSE;
|
||||
if (!canClose) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.panels.length; i++) {
|
||||
if (i === index) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
const panel = this.panels[i]
|
||||
this.openPanel(panel)
|
||||
const panel = this.panels[i];
|
||||
this.openPanel(panel);
|
||||
|
||||
if (panel.close) {
|
||||
await timeoutPromise(0)
|
||||
await timeoutPromise(0);
|
||||
const canClose =
|
||||
(await panel.close()) === ClosePanelResult.CLOSE
|
||||
(await panel.close()) === ClosePanelResult.CLOSE;
|
||||
if (!canClose) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.panels.length > 0) {
|
||||
// take a copy since we will be edting the array as we iterate through
|
||||
const arrPanelCpy = [...this.panels]
|
||||
await Promise.all(arrPanelCpy.map((p) => this.doClose(p)))
|
||||
const arrPanelCpy = [...this.panels];
|
||||
await Promise.all(arrPanelCpy.map((p) => this.doClose(p)));
|
||||
} else {
|
||||
this.accessor.removeGroup(this)
|
||||
this.accessor.removeGroup(this);
|
||||
}
|
||||
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
public closePanel = async (panel: IGroupPanel) => {
|
||||
@ -403,159 +403,163 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
panel.close &&
|
||||
(await panel.close()) === ClosePanelResult.DONT_CLOSE
|
||||
) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
this.doClose(panel)
|
||||
return true
|
||||
}
|
||||
this.doClose(panel);
|
||||
return true;
|
||||
};
|
||||
|
||||
private doClose(panel: IGroupPanel) {
|
||||
this._removePanel(panel)
|
||||
;(this.accessor as Layout).unregisterPanel(panel)
|
||||
this._removePanel(panel);
|
||||
(this.accessor as Layout).unregisterPanel(panel);
|
||||
|
||||
panel.dispose()
|
||||
panel.dispose();
|
||||
|
||||
if (this.panels.length === 0) {
|
||||
this.accessor.removeGroup(this)
|
||||
this.accessor.removeGroup(this);
|
||||
}
|
||||
}
|
||||
|
||||
public isPanelActive(panel: IGroupPanel) {
|
||||
return this._activePanel === panel
|
||||
return this._activePanel === panel;
|
||||
}
|
||||
|
||||
public setActive(isActive: boolean) {
|
||||
if (this._active === isActive) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
this._active = isActive
|
||||
this._active = isActive;
|
||||
|
||||
toggleClass(this.element, 'active-group', isActive)
|
||||
toggleClass(this.element, 'inactive-group', !isActive)
|
||||
toggleClass(this.element, 'active-group', isActive);
|
||||
toggleClass(this.element, 'inactive-group', !isActive);
|
||||
|
||||
this.tabContainer.setActive(this._active)
|
||||
this.tabContainer.setActive(this._active);
|
||||
|
||||
if (!this._activePanel && this.panels.length > 0) {
|
||||
this.doSetActivePanel(this.panels[0])
|
||||
this.doSetActivePanel(this.panels[0]);
|
||||
}
|
||||
|
||||
this.panels.forEach((panel) => panel.setVisible(this._active, this))
|
||||
this.panels.forEach((panel) => panel.setVisible(this._active, this));
|
||||
|
||||
if (this.watermark?.setVisible) {
|
||||
this.watermark.setVisible(this._active, this)
|
||||
this.watermark.setVisible(this._active, this);
|
||||
}
|
||||
|
||||
if (isActive) {
|
||||
this._onDidGroupChange.fire({ kind: GroupChangeKind.GROUP_ACTIVE })
|
||||
this._onDidGroupChange.fire({ kind: GroupChangeKind.GROUP_ACTIVE });
|
||||
}
|
||||
}
|
||||
|
||||
public layout(width: number, height: number) {
|
||||
this._width = width
|
||||
this._height = height
|
||||
this._width = width;
|
||||
this._height = height;
|
||||
|
||||
if (this._activePanel?.layout) {
|
||||
this._activePanel.layout(this._width, this._height)
|
||||
this._activePanel.layout(this._width, this._height);
|
||||
}
|
||||
}
|
||||
|
||||
private _removePanel(panel: IGroupPanel) {
|
||||
const index = this._panels.indexOf(panel)
|
||||
const index = this._panels.indexOf(panel);
|
||||
|
||||
const isActivePanel = this._activePanel === panel
|
||||
const isActivePanel = this._activePanel === panel;
|
||||
|
||||
this.doRemovePanel(panel)
|
||||
this.doRemovePanel(panel);
|
||||
|
||||
if (isActivePanel && this.panels.length > 0) {
|
||||
const nextPanel = this.panels[Math.max(0, index - 1)]
|
||||
this.openPanel(nextPanel)
|
||||
const nextPanel = this.panels[Math.max(0, index - 1)];
|
||||
this.openPanel(nextPanel);
|
||||
}
|
||||
|
||||
if (this._activePanel && this.panels.length === 0) {
|
||||
this._activePanel = undefined
|
||||
this._activePanel = undefined;
|
||||
}
|
||||
|
||||
this.updateContainer()
|
||||
return panel
|
||||
this.updateContainer();
|
||||
return panel;
|
||||
}
|
||||
|
||||
private doRemovePanel(panel: IGroupPanel) {
|
||||
const index = this.panels.indexOf(panel)
|
||||
const index = this.panels.indexOf(panel);
|
||||
|
||||
if (this._activePanel === panel) {
|
||||
this.contentContainer.closePanel()
|
||||
this.contentContainer.closePanel();
|
||||
}
|
||||
|
||||
this.tabContainer.delete(panel.id)
|
||||
this._panels.splice(index, 1)
|
||||
this.tabContainer.delete(panel.id);
|
||||
this._panels.splice(index, 1);
|
||||
|
||||
this._onDidGroupChange.fire({
|
||||
kind: GroupChangeKind.REMOVE_PANEL,
|
||||
panel,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
private doAddPanel(panel: IGroupPanel, index: number) {
|
||||
const existingPanel = this._panels.indexOf(panel)
|
||||
const hasExistingPabel = existingPanel > -1
|
||||
const existingPanel = this._panels.indexOf(panel);
|
||||
const hasExistingPabel = existingPanel > -1;
|
||||
|
||||
if (hasExistingPabel) {
|
||||
// TODO - need to ensure ordering hasn't changed and if it has need to re-order this.panels
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
this.panels.splice(index, 0, panel)
|
||||
this.panels.splice(index, 0, panel);
|
||||
|
||||
this._onDidGroupChange.fire({ kind: GroupChangeKind.ADD_PANEL })
|
||||
this._onDidGroupChange.fire({ kind: GroupChangeKind.ADD_PANEL });
|
||||
}
|
||||
|
||||
private doSetActivePanel(panel: IGroupPanel) {
|
||||
this._activePanel = panel
|
||||
this.tabContainer.setActivePanel(panel)
|
||||
panel.layout(this._width, this._height)
|
||||
this._onDidGroupChange.fire({ kind: GroupChangeKind.PANEL_ACTIVE })
|
||||
this._activePanel = panel;
|
||||
this.tabContainer.setActivePanel(panel);
|
||||
panel.layout(this._width, this._height);
|
||||
this._onDidGroupChange.fire({ kind: GroupChangeKind.PANEL_ACTIVE });
|
||||
}
|
||||
|
||||
private updateContainer() {
|
||||
toggleClass(this.element, 'empty', this.isEmpty)
|
||||
toggleClass(this.element, 'empty', this.isEmpty);
|
||||
|
||||
if (this.accessor.options.watermarkComponent && !this.watermark) {
|
||||
const WatermarkComponent = this.accessor.options.watermarkComponent
|
||||
this.watermark = new WatermarkComponent()
|
||||
this.watermark.init({ accessor: this.accessor })
|
||||
const WatermarkComponent = this.accessor.options.watermarkComponent;
|
||||
this.watermark = new WatermarkComponent();
|
||||
this.watermark.init({ accessor: this.accessor });
|
||||
}
|
||||
|
||||
this.panels.forEach((panel) => panel.setVisible(this._active, this))
|
||||
this.panels.forEach((panel) => panel.setVisible(this._active, this));
|
||||
|
||||
if (this.isEmpty && !this.watermark?.element.parentNode) {
|
||||
addDisposableListener(this.watermark.element, 'click', () => {
|
||||
if (!this._active) {
|
||||
this.accessor.doSetGroupActive(this)
|
||||
this.accessor.doSetGroupActive(this);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.contentContainer.openPanel(this.watermark.element)
|
||||
this.contentContainer.openPanel(this.watermark.element);
|
||||
|
||||
this.watermark.setVisible(true, this)
|
||||
this.watermark.setVisible(true, this);
|
||||
}
|
||||
if (!this.isEmpty && this.watermark.element.parentNode) {
|
||||
this.watermark.dispose()
|
||||
this.watermark = undefined
|
||||
this.contentContainer.closePanel()
|
||||
this.watermark.dispose();
|
||||
this.watermark = undefined;
|
||||
this.contentContainer.closePanel();
|
||||
}
|
||||
}
|
||||
|
||||
private handleDropEvent(event: DroptargetEvent, index?: number) {
|
||||
if (isPanelTransferEvent(event.event)) {
|
||||
this.handlePanelDropEvent(event.event, event.position, index)
|
||||
return
|
||||
this.handlePanelDropEvent(event.event, event.position, index);
|
||||
return;
|
||||
}
|
||||
|
||||
this._onDrop.fire({ event: event.event, target: event.position, index })
|
||||
this._onDrop.fire({
|
||||
event: event.event,
|
||||
target: event.position,
|
||||
index,
|
||||
});
|
||||
|
||||
console.debug('[customDropEvent]')
|
||||
console.debug('[customDropEvent]');
|
||||
}
|
||||
|
||||
private handlePanelDropEvent(
|
||||
@ -563,16 +567,18 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
target: Position,
|
||||
index?: number
|
||||
) {
|
||||
const dataObject = extractData(event)
|
||||
const dataObject = extractData(event);
|
||||
|
||||
if (isTabDragEvent(dataObject)) {
|
||||
const { groupId, itemId } = dataObject
|
||||
const isSameGroup = this.id === groupId
|
||||
const { groupId, itemId } = dataObject;
|
||||
const isSameGroup = this.id === groupId;
|
||||
if (isSameGroup && !target) {
|
||||
const oldIndex = this.tabContainer.indexOf(itemId)
|
||||
const oldIndex = this.tabContainer.indexOf(itemId);
|
||||
if (oldIndex === index) {
|
||||
console.debug('[tabs] drop indicates no change in position')
|
||||
return
|
||||
console.debug(
|
||||
'[tabs] drop indicates no change in position'
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -581,14 +587,14 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
groupId: dataObject.groupId,
|
||||
itemId: dataObject.itemId,
|
||||
index,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (isCustomDragEvent(dataObject)) {
|
||||
let panel = this.accessor.getPanel(dataObject.id)
|
||||
let panel = this.accessor.getPanel(dataObject.id);
|
||||
|
||||
if (!panel) {
|
||||
panel = this.accessor.addPanel(dataObject)
|
||||
panel = this.accessor.addPanel(dataObject);
|
||||
}
|
||||
|
||||
this._onMove.fire({
|
||||
@ -596,19 +602,19 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
groupId: panel.group?.id,
|
||||
itemId: panel.id,
|
||||
index,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
for (const panel of this.panels) {
|
||||
panel.dispose()
|
||||
panel.dispose();
|
||||
}
|
||||
|
||||
super.dispose()
|
||||
super.dispose();
|
||||
|
||||
this.dropTarget.dispose()
|
||||
this.tabContainer.dispose()
|
||||
this.contentContainer.dispose()
|
||||
this.dropTarget.dispose();
|
||||
this.tabContainer.dispose();
|
||||
this.contentContainer.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,76 +1,76 @@
|
||||
import { IGroupview } from '../groupview'
|
||||
import { Emitter, Event } from '../../events'
|
||||
import { ClosePanelResult } from './parts'
|
||||
import { IGroupPanel } from './types'
|
||||
import { IBaseViewApi, BaseViewApi } from '../../panel/api'
|
||||
import { IGroupview } from '../groupview';
|
||||
import { Emitter, Event } from '../../events';
|
||||
import { ClosePanelResult } from './parts';
|
||||
import { IGroupPanel } from './types';
|
||||
import { IBaseViewApi, BaseViewApi } from '../../panel/api';
|
||||
|
||||
interface ChangeVisibilityEvent {
|
||||
isVisible: boolean
|
||||
isVisible: boolean;
|
||||
}
|
||||
|
||||
export interface IGroupPanelApi extends IBaseViewApi {
|
||||
// events
|
||||
onDidDirtyChange: Event<boolean>
|
||||
onDidChangeVisibility: Event<ChangeVisibilityEvent>
|
||||
onDidDirtyChange: Event<boolean>;
|
||||
onDidChangeVisibility: Event<ChangeVisibilityEvent>;
|
||||
// misc
|
||||
readonly isVisible: boolean
|
||||
group: IGroupview
|
||||
close: () => Promise<boolean>
|
||||
canClose: () => Promise<ClosePanelResult>
|
||||
setClosePanelHook(callback: () => Promise<ClosePanelResult>): void
|
||||
readonly isVisible: boolean;
|
||||
group: IGroupview;
|
||||
close: () => Promise<boolean>;
|
||||
canClose: () => Promise<ClosePanelResult>;
|
||||
setClosePanelHook(callback: () => Promise<ClosePanelResult>): void;
|
||||
}
|
||||
|
||||
export class GroupPanelApi extends BaseViewApi implements IGroupPanelApi {
|
||||
private _isVisible: boolean
|
||||
private _group: IGroupview
|
||||
private _closePanelCallback: () => Promise<ClosePanelResult>
|
||||
private _isVisible: boolean;
|
||||
private _group: IGroupview;
|
||||
private _closePanelCallback: () => Promise<ClosePanelResult>;
|
||||
|
||||
readonly _onDidDirtyChange = new Emitter<boolean>()
|
||||
readonly onDidDirtyChange = this._onDidDirtyChange.event
|
||||
readonly _onDidDirtyChange = new Emitter<boolean>();
|
||||
readonly onDidDirtyChange = this._onDidDirtyChange.event;
|
||||
readonly _onDidChangeVisibility = new Emitter<ChangeVisibilityEvent>({
|
||||
emitLastValue: true,
|
||||
})
|
||||
});
|
||||
readonly onDidChangeVisibility: Event<ChangeVisibilityEvent> = this
|
||||
._onDidChangeVisibility.event
|
||||
._onDidChangeVisibility.event;
|
||||
|
||||
get isVisible() {
|
||||
return this._isVisible
|
||||
return this._isVisible;
|
||||
}
|
||||
|
||||
get canClose() {
|
||||
return this._closePanelCallback
|
||||
return this._closePanelCallback;
|
||||
}
|
||||
|
||||
set group(value: IGroupview) {
|
||||
this._group = value
|
||||
this._group = value;
|
||||
}
|
||||
|
||||
get group() {
|
||||
return this._group
|
||||
return this._group;
|
||||
}
|
||||
|
||||
constructor(private panel: IGroupPanel, group: IGroupview) {
|
||||
super()
|
||||
this._group = group
|
||||
super();
|
||||
this._group = group;
|
||||
|
||||
this.addDisposables(
|
||||
this._onDidChangeVisibility,
|
||||
this._onDidDirtyChange,
|
||||
this.onDidChangeVisibility((event) => {
|
||||
this._isVisible = event.isVisible
|
||||
this._isVisible = event.isVisible;
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public close() {
|
||||
return this.group.closePanel(this.panel)
|
||||
return this.group.closePanel(this.panel);
|
||||
}
|
||||
|
||||
public setClosePanelHook(callback: () => Promise<ClosePanelResult>) {
|
||||
this._closePanelCallback = callback
|
||||
this._closePanelCallback = callback;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose()
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,55 +1,55 @@
|
||||
import { CompositeDisposable, IDisposable } from '../../../lifecycle'
|
||||
import { Emitter, Event } from '../../../events'
|
||||
import { trackFocus } from '../../../dom'
|
||||
import { CompositeDisposable, IDisposable } from '../../../lifecycle';
|
||||
import { Emitter, Event } from '../../../events';
|
||||
import { trackFocus } from '../../../dom';
|
||||
|
||||
export interface IContentContainer extends IDisposable {
|
||||
onDidFocus: Event<void>
|
||||
element: HTMLElement
|
||||
openPanel: (panel: HTMLElement) => void
|
||||
closePanel: () => void
|
||||
onDidFocus: Event<void>;
|
||||
element: HTMLElement;
|
||||
openPanel: (panel: HTMLElement) => void;
|
||||
closePanel: () => void;
|
||||
}
|
||||
|
||||
export class ContentContainer
|
||||
extends CompositeDisposable
|
||||
implements IContentContainer {
|
||||
private _element: HTMLElement
|
||||
private content: HTMLElement
|
||||
private _element: HTMLElement;
|
||||
private content: HTMLElement;
|
||||
|
||||
private readonly _onDidFocus = new Emitter<void>()
|
||||
readonly onDidFocus: Event<void> = this._onDidFocus.event
|
||||
private readonly _onDidFocus = new Emitter<void>();
|
||||
readonly onDidFocus: Event<void> = this._onDidFocus.event;
|
||||
|
||||
get element() {
|
||||
return this._element
|
||||
return this._element;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this._element = document.createElement('div')
|
||||
this._element.className = 'content-container'
|
||||
this._element.tabIndex = -1
|
||||
super();
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'content-container';
|
||||
this._element.tabIndex = -1;
|
||||
|
||||
const { onDidBlur, onDidFocus } = trackFocus(this._element)
|
||||
const { onDidBlur, onDidFocus } = trackFocus(this._element);
|
||||
|
||||
this.addDisposables(onDidFocus(() => this._onDidFocus.fire()))
|
||||
this.addDisposables(onDidFocus(() => this._onDidFocus.fire()));
|
||||
}
|
||||
|
||||
public openPanel(panel: HTMLElement) {
|
||||
if (this.content) {
|
||||
this._element.removeChild(this.content)
|
||||
this.content = undefined
|
||||
this._element.removeChild(this.content);
|
||||
this.content = undefined;
|
||||
}
|
||||
this.content = panel
|
||||
this._element.appendChild(this.content)
|
||||
this.content = panel;
|
||||
this._element.appendChild(this.content);
|
||||
}
|
||||
|
||||
public closePanel() {
|
||||
if (this.content) {
|
||||
this._element.removeChild(this.content)
|
||||
this.content = undefined
|
||||
this._element.removeChild(this.content);
|
||||
this.content = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose()
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,30 @@
|
||||
import { IGroupPanel, PanelInitParameters } from './types'
|
||||
import { GroupPanelApi } from './api'
|
||||
import { Event } from '../../events'
|
||||
import { IGroupview, GroupChangeKind } from '../groupview'
|
||||
import { MutableDisposable, CompositeDisposable } from '../../lifecycle'
|
||||
import { PanelContentPart, PanelHeaderPart, ClosePanelResult } from './parts'
|
||||
import { PanelUpdateEvent } from '../../panel/types'
|
||||
import { IGroupPanel, PanelInitParameters } from './types';
|
||||
import { GroupPanelApi } from './api';
|
||||
import { Event } from '../../events';
|
||||
import { IGroupview, GroupChangeKind } from '../groupview';
|
||||
import { MutableDisposable, CompositeDisposable } from '../../lifecycle';
|
||||
import { PanelContentPart, PanelHeaderPart, ClosePanelResult } from './parts';
|
||||
import { PanelUpdateEvent } from '../../panel/types';
|
||||
|
||||
export class DefaultPanel extends CompositeDisposable implements IGroupPanel {
|
||||
private readonly mutableDisposable = new MutableDisposable()
|
||||
private readonly mutableDisposable = new MutableDisposable();
|
||||
|
||||
private readonly api: GroupPanelApi
|
||||
private _group: IGroupview
|
||||
private params: PanelInitParameters
|
||||
private readonly api: GroupPanelApi;
|
||||
private _group: IGroupview;
|
||||
private params: PanelInitParameters;
|
||||
|
||||
readonly onDidStateChange: Event<any>
|
||||
readonly onDidStateChange: Event<any>;
|
||||
|
||||
get group() {
|
||||
return this._group
|
||||
return this._group;
|
||||
}
|
||||
|
||||
get header() {
|
||||
return this.headerPart
|
||||
return this.headerPart;
|
||||
}
|
||||
|
||||
get content() {
|
||||
return this.contentPart
|
||||
return this.contentPart;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ -32,22 +32,22 @@ export class DefaultPanel extends CompositeDisposable implements IGroupPanel {
|
||||
private readonly headerPart: PanelHeaderPart,
|
||||
private readonly contentPart: PanelContentPart
|
||||
) {
|
||||
super()
|
||||
super();
|
||||
|
||||
this.api = new GroupPanelApi(this, this._group)
|
||||
this.onDidStateChange = this.api.onDidStateChange
|
||||
this.api = new GroupPanelApi(this, this._group);
|
||||
this.onDidStateChange = this.api.onDidStateChange;
|
||||
}
|
||||
|
||||
public setDirty(isDirty: boolean) {
|
||||
this.api._onDidDirtyChange.fire(isDirty)
|
||||
this.api._onDidDirtyChange.fire(isDirty);
|
||||
}
|
||||
|
||||
public close(): Promise<ClosePanelResult> {
|
||||
if (this.api.canClose) {
|
||||
return this.api.canClose()
|
||||
return this.api.canClose();
|
||||
}
|
||||
|
||||
return Promise.resolve(ClosePanelResult.CLOSE)
|
||||
return Promise.resolve(ClosePanelResult.CLOSE);
|
||||
}
|
||||
|
||||
public toJSON(): object {
|
||||
@ -59,7 +59,7 @@ export class DefaultPanel extends CompositeDisposable implements IGroupPanel {
|
||||
title: this.params.title,
|
||||
suppressClosable: this.params.suppressClosable,
|
||||
state: this.api.getState(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public fromJSON(data: object) {
|
||||
@ -67,20 +67,20 @@ export class DefaultPanel extends CompositeDisposable implements IGroupPanel {
|
||||
}
|
||||
|
||||
public update(params: PanelUpdateEvent): void {
|
||||
this.params.params = { ...this.params.params, ...params }
|
||||
this.params.params = { ...this.params.params, ...params };
|
||||
|
||||
this.contentPart.update(params.params)
|
||||
this.api._onDidStateChange.fire()
|
||||
this.contentPart.update(params.params);
|
||||
this.api._onDidStateChange.fire();
|
||||
}
|
||||
|
||||
public init(params: PanelInitParameters): void {
|
||||
this.params = params
|
||||
this.api.setState(this.params.state)
|
||||
this.params = params;
|
||||
this.api.setState(this.params.state);
|
||||
if (this.content.init) {
|
||||
this.content.init({ ...params, api: this.api })
|
||||
this.content.init({ ...params, api: this.api });
|
||||
}
|
||||
if (this.header.init) {
|
||||
this.header.init({ ...params, api: this.api })
|
||||
this.header.init({ ...params, api: this.api });
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,33 +93,33 @@ export class DefaultPanel extends CompositeDisposable implements IGroupPanel {
|
||||
}
|
||||
|
||||
public setVisible(isGroupActive: boolean, group: IGroupview) {
|
||||
this._group = group
|
||||
this.api.group = group
|
||||
this._group = group;
|
||||
this.api.group = group;
|
||||
|
||||
this.mutableDisposable.value = this._group.onDidGroupChange((ev) => {
|
||||
if (ev.kind === GroupChangeKind.GROUP_ACTIVE) {
|
||||
this.api._onDidChangeVisibility.fire({
|
||||
isVisible: this._group.isPanelActive(this),
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.api._onDidChangeFocus.fire({ isFocused: isGroupActive })
|
||||
this.api._onDidChangeFocus.fire({ isFocused: isGroupActive });
|
||||
this.api._onDidChangeVisibility.fire({
|
||||
isVisible: this._group.isPanelActive(this),
|
||||
})
|
||||
});
|
||||
|
||||
if (this.headerPart.setVisible) {
|
||||
this.headerPart.setVisible(
|
||||
this._group.isPanelActive(this),
|
||||
isGroupActive
|
||||
)
|
||||
);
|
||||
}
|
||||
if (this.contentPart.setVisible) {
|
||||
this.contentPart.setVisible(
|
||||
this._group.isPanelActive(this),
|
||||
isGroupActive
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,14 +128,14 @@ export class DefaultPanel extends CompositeDisposable implements IGroupPanel {
|
||||
this.api._onDidPanelDimensionChange.fire({
|
||||
width,
|
||||
height: height - (this.group?.tabHeight || 0),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.api.dispose()
|
||||
this.mutableDisposable.dispose()
|
||||
this.api.dispose();
|
||||
this.mutableDisposable.dispose();
|
||||
|
||||
this.headerPart.dispose()
|
||||
this.contentPart.dispose()
|
||||
this.headerPart.dispose();
|
||||
this.contentPart.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { IDisposable } from '../../lifecycle'
|
||||
import { IGroupview } from '../groupview'
|
||||
import { IGroupAccessor } from '../../layout'
|
||||
import { IGroupPanelApi } from './api'
|
||||
import { PanelInitParameters } from './types'
|
||||
import { Constructor } from '../../types'
|
||||
import { IDisposable } from '../../lifecycle';
|
||||
import { IGroupview } from '../groupview';
|
||||
import { IGroupAccessor } from '../../layout';
|
||||
import { IGroupPanelApi } from './api';
|
||||
import { PanelInitParameters } from './types';
|
||||
import { Constructor } from '../../types';
|
||||
|
||||
export enum ClosePanelResult {
|
||||
CLOSE = 'CLOSE',
|
||||
@ -11,40 +11,40 @@ export enum ClosePanelResult {
|
||||
}
|
||||
|
||||
interface BasePart extends IDisposable {
|
||||
init?(params: PartInitParameters): void
|
||||
setVisible(isPanelVisible: boolean, isGroupVisible: boolean): void
|
||||
init?(params: PartInitParameters): void;
|
||||
setVisible(isPanelVisible: boolean, isGroupVisible: boolean): void;
|
||||
}
|
||||
|
||||
export interface WatermarkPartInitParameters {
|
||||
accessor: IGroupAccessor
|
||||
accessor: IGroupAccessor;
|
||||
}
|
||||
|
||||
export interface PartInitParameters extends PanelInitParameters {
|
||||
api: IGroupPanelApi
|
||||
api: IGroupPanelApi;
|
||||
}
|
||||
|
||||
export interface PanelHeaderPart extends BasePart {
|
||||
id: string
|
||||
element: HTMLElement
|
||||
layout?(height: string): void
|
||||
toJSON(): {}
|
||||
id: string;
|
||||
element: HTMLElement;
|
||||
layout?(height: string): void;
|
||||
toJSON(): {};
|
||||
}
|
||||
|
||||
export interface PanelContentPart extends BasePart {
|
||||
id: string
|
||||
element: HTMLElement
|
||||
layout?(width: number, height: number): void
|
||||
close?(): Promise<ClosePanelResult>
|
||||
focus(): void
|
||||
onHide(): void
|
||||
update(params: {}): void
|
||||
toJSON(): {}
|
||||
id: string;
|
||||
element: HTMLElement;
|
||||
layout?(width: number, height: number): void;
|
||||
close?(): Promise<ClosePanelResult>;
|
||||
focus(): void;
|
||||
onHide(): void;
|
||||
update(params: {}): void;
|
||||
toJSON(): {};
|
||||
}
|
||||
|
||||
export interface WatermarkPart extends IDisposable {
|
||||
init?: (params: WatermarkPartInitParameters) => void
|
||||
setVisible?(visible: boolean, group: IGroupview): void
|
||||
element: HTMLElement
|
||||
init?: (params: WatermarkPartInitParameters) => void;
|
||||
setVisible?(visible: boolean, group: IGroupview): void;
|
||||
element: HTMLElement;
|
||||
}
|
||||
|
||||
// constructors
|
||||
|
@ -1,56 +1,56 @@
|
||||
import { addDisposableListener, Emitter, Event } from '../../../events'
|
||||
import { Droptarget, DroptargetEvent } from '../../droptarget/droptarget'
|
||||
import { CompositeDisposable } from '../../../lifecycle'
|
||||
import { IGroupview } from '../../groupview'
|
||||
import { addDisposableListener, Emitter, Event } from '../../../events';
|
||||
import { Droptarget, DroptargetEvent } from '../../droptarget/droptarget';
|
||||
import { CompositeDisposable } from '../../../lifecycle';
|
||||
import { IGroupview } from '../../groupview';
|
||||
import {
|
||||
DataTransferSingleton,
|
||||
DATA_KEY,
|
||||
DragType,
|
||||
} from '../../droptarget/dataTransfer'
|
||||
import { toggleClass } from '../../../dom'
|
||||
import { IGroupAccessor } from '../../../layout'
|
||||
import { LayoutMouseEvent, MouseEventKind } from '../../events'
|
||||
} from '../../droptarget/dataTransfer';
|
||||
import { toggleClass } from '../../../dom';
|
||||
import { IGroupAccessor } from '../../../layout';
|
||||
import { LayoutMouseEvent, MouseEventKind } from '../../events';
|
||||
|
||||
export interface ITab {
|
||||
id: string
|
||||
element: HTMLElement
|
||||
hasActiveDragEvent: boolean
|
||||
setContent: (element: HTMLElement) => void
|
||||
onChanged: Event<LayoutMouseEvent>
|
||||
onDropped: Event<DroptargetEvent>
|
||||
setActive(isActive: boolean): void
|
||||
startDragEvent(): void
|
||||
stopDragEvent(): void
|
||||
id: string;
|
||||
element: HTMLElement;
|
||||
hasActiveDragEvent: boolean;
|
||||
setContent: (element: HTMLElement) => void;
|
||||
onChanged: Event<LayoutMouseEvent>;
|
||||
onDropped: Event<DroptargetEvent>;
|
||||
setActive(isActive: boolean): void;
|
||||
startDragEvent(): void;
|
||||
stopDragEvent(): void;
|
||||
}
|
||||
|
||||
export class Tab extends CompositeDisposable implements ITab {
|
||||
private _element: HTMLElement
|
||||
private _element: HTMLElement;
|
||||
private dragInPlayDetails: { id?: string; isDragging: boolean } = {
|
||||
isDragging: false,
|
||||
}
|
||||
private droptarget: Droptarget
|
||||
private content: HTMLElement
|
||||
};
|
||||
private droptarget: Droptarget;
|
||||
private content: HTMLElement;
|
||||
|
||||
private readonly _onChanged = new Emitter<LayoutMouseEvent>()
|
||||
readonly onChanged: Event<LayoutMouseEvent> = this._onChanged.event
|
||||
private readonly _onChanged = new Emitter<LayoutMouseEvent>();
|
||||
readonly onChanged: Event<LayoutMouseEvent> = this._onChanged.event;
|
||||
|
||||
private readonly _onDropped = new Emitter<DroptargetEvent>()
|
||||
readonly onDropped: Event<DroptargetEvent> = this._onDropped.event
|
||||
private readonly _onDropped = new Emitter<DroptargetEvent>();
|
||||
readonly onDropped: Event<DroptargetEvent> = this._onDropped.event;
|
||||
|
||||
public get element() {
|
||||
return this._element
|
||||
return this._element;
|
||||
}
|
||||
|
||||
public get hasActiveDragEvent() {
|
||||
return this.dragInPlayDetails?.isDragging
|
||||
return this.dragInPlayDetails?.isDragging;
|
||||
}
|
||||
|
||||
public startDragEvent() {
|
||||
this.dragInPlayDetails = { isDragging: true, id: this.accessor.id }
|
||||
this.dragInPlayDetails = { isDragging: true, id: this.accessor.id };
|
||||
}
|
||||
|
||||
public stopDragEvent() {
|
||||
this.dragInPlayDetails = { isDragging: false, id: undefined }
|
||||
this.dragInPlayDetails = { isDragging: false, id: undefined };
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ -58,73 +58,73 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
private readonly accessor: IGroupAccessor,
|
||||
private group: IGroupview
|
||||
) {
|
||||
super()
|
||||
super();
|
||||
|
||||
this.addDisposables(this._onChanged, this._onDropped)
|
||||
this.addDisposables(this._onChanged, this._onDropped);
|
||||
|
||||
this._element = document.createElement('div')
|
||||
this._element.className = 'tab'
|
||||
this._element.draggable = true
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'tab';
|
||||
this._element.draggable = true;
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this._element, 'mousedown', (event) => {
|
||||
if (event.defaultPrevented) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this._onChanged.fire({ kind: MouseEventKind.CLICK, event })
|
||||
this._onChanged.fire({ kind: MouseEventKind.CLICK, event });
|
||||
}),
|
||||
addDisposableListener(this._element, 'contextmenu', (event) => {
|
||||
this._onChanged.fire({
|
||||
kind: MouseEventKind.CONTEXT_MENU,
|
||||
event,
|
||||
})
|
||||
});
|
||||
}),
|
||||
addDisposableListener(this._element, 'dragstart', (event) => {
|
||||
this.dragInPlayDetails = {
|
||||
isDragging: true,
|
||||
id: this.accessor.id,
|
||||
}
|
||||
};
|
||||
|
||||
// set up a custom ghost image
|
||||
const dragImage = this._element.cloneNode(true) as HTMLElement
|
||||
const dragImage = this._element.cloneNode(true) as HTMLElement;
|
||||
|
||||
const box = this._element.getBoundingClientRect()
|
||||
const box = this._element.getBoundingClientRect();
|
||||
|
||||
// if the style of the tab is determined by CSS by a parent element that style will lost
|
||||
// therefore we must explicility re-add the style features that we know will be lost
|
||||
dragImage.style.height = `${box.height}px`
|
||||
dragImage.style.width = `${box.width}px`
|
||||
dragImage.style.position = 'absolute'
|
||||
dragImage.classList.add('dragging')
|
||||
dragImage.style.height = `${box.height}px`;
|
||||
dragImage.style.width = `${box.width}px`;
|
||||
dragImage.style.position = 'absolute';
|
||||
dragImage.classList.add('dragging');
|
||||
|
||||
document.body.appendChild(dragImage)
|
||||
document.body.appendChild(dragImage);
|
||||
event.dataTransfer.setDragImage(
|
||||
dragImage,
|
||||
event.offsetX,
|
||||
event.offsetY
|
||||
)
|
||||
setTimeout(() => document.body.removeChild(dragImage), 0)
|
||||
);
|
||||
setTimeout(() => document.body.removeChild(dragImage), 0);
|
||||
// configure the data-transfer object
|
||||
|
||||
const data = JSON.stringify({
|
||||
type: DragType.ITEM,
|
||||
itemId: this.id,
|
||||
groupId: this.group.id,
|
||||
})
|
||||
DataTransferSingleton.setData(this.dragInPlayDetails.id, data)
|
||||
});
|
||||
DataTransferSingleton.setData(this.dragInPlayDetails.id, data);
|
||||
|
||||
event.dataTransfer.setData(DATA_KEY, data)
|
||||
event.dataTransfer.effectAllowed = 'move'
|
||||
event.dataTransfer.setData(DATA_KEY, data);
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
}),
|
||||
addDisposableListener(this._element, 'dragend', (ev) => {
|
||||
// drop events fire before dragend so we can remove this safely
|
||||
DataTransferSingleton.removeData(this.dragInPlayDetails.id)
|
||||
DataTransferSingleton.removeData(this.dragInPlayDetails.id);
|
||||
this.dragInPlayDetails = {
|
||||
isDragging: false,
|
||||
id: undefined,
|
||||
}
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
this.droptarget = new Droptarget(this._element, {
|
||||
isDirectional: false,
|
||||
@ -132,30 +132,30 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
id: this.accessor.id,
|
||||
enableExternalDragEvents: this.accessor.options
|
||||
.enableExternalDragEvents,
|
||||
})
|
||||
});
|
||||
|
||||
this.addDisposables(
|
||||
this.droptarget.onDidChange((event) => {
|
||||
this._onDropped.fire(event)
|
||||
this._onDropped.fire(event);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public setActive(isActive: boolean) {
|
||||
toggleClass(this.element, 'active-tab', isActive)
|
||||
toggleClass(this.element, 'inactive-tab', !isActive)
|
||||
toggleClass(this.element, 'active-tab', isActive);
|
||||
toggleClass(this.element, 'inactive-tab', !isActive);
|
||||
}
|
||||
|
||||
public setContent(element: HTMLElement) {
|
||||
if (this.content) {
|
||||
this._element.removeChild(this.content)
|
||||
this._element.removeChild(this.content);
|
||||
}
|
||||
this.content = element
|
||||
this._element.appendChild(this.content)
|
||||
this.content = element;
|
||||
this._element.appendChild(this.content);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose()
|
||||
this.droptarget.dispose()
|
||||
super.dispose();
|
||||
this.droptarget.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { IGroupview } from '../groupview'
|
||||
import { IDisposable, ISerializable } from '../../lifecycle'
|
||||
import { Event } from '../../events'
|
||||
import { PanelHeaderPart, PanelContentPart, ClosePanelResult } from './parts'
|
||||
import { InitParameters, IPanel } from '../../panel/types'
|
||||
import { IGroupview } from '../groupview';
|
||||
import { IDisposable, ISerializable } from '../../lifecycle';
|
||||
import { Event } from '../../events';
|
||||
import { PanelHeaderPart, PanelContentPart, ClosePanelResult } from './parts';
|
||||
import { InitParameters, IPanel } from '../../panel/types';
|
||||
|
||||
// init parameters
|
||||
|
||||
export interface PanelInitParameters extends InitParameters {
|
||||
title: string
|
||||
suppressClosable?: boolean
|
||||
title: string;
|
||||
suppressClosable?: boolean;
|
||||
}
|
||||
|
||||
// constructors
|
||||
@ -16,15 +16,15 @@ export interface PanelInitParameters extends InitParameters {
|
||||
// panel
|
||||
|
||||
export interface IGroupPanel extends IDisposable, ISerializable, IPanel {
|
||||
id: string
|
||||
header: PanelHeaderPart
|
||||
content: PanelContentPart
|
||||
group: IGroupview
|
||||
focus(): void
|
||||
onHide(): void
|
||||
setVisible(isGroupActive: boolean, group: IGroupview): void
|
||||
setDirty(isDirty: boolean): void
|
||||
close?(): Promise<ClosePanelResult>
|
||||
init?(params: PanelInitParameters & { [index: string]: string }): void
|
||||
onDidStateChange: Event<any>
|
||||
id: string;
|
||||
header: PanelHeaderPart;
|
||||
content: PanelContentPart;
|
||||
group: IGroupview;
|
||||
focus(): void;
|
||||
onHide(): void;
|
||||
setVisible(isGroupActive: boolean, group: IGroupview): void;
|
||||
setDirty(isDirty: boolean): void;
|
||||
close?(): Promise<ClosePanelResult>;
|
||||
init?(params: PanelInitParameters & { [index: string]: string }): void;
|
||||
onDidStateChange: Event<any>;
|
||||
}
|
||||
|
@ -2,170 +2,170 @@ import {
|
||||
IDisposable,
|
||||
CompositeDisposable,
|
||||
IValueDisposable,
|
||||
} from '../../lifecycle'
|
||||
import { addDisposableListener, Emitter, Event } from '../../events'
|
||||
import { ITab, Tab } from '../panel/tab/tab'
|
||||
import { removeClasses, addClasses, toggleClass } from '../../dom'
|
||||
import { hasProcessed, Position } from '../droptarget/droptarget'
|
||||
import { TabDropEvent } from '../events'
|
||||
} from '../../lifecycle';
|
||||
import { addDisposableListener, Emitter, Event } from '../../events';
|
||||
import { ITab, Tab } from '../panel/tab/tab';
|
||||
import { removeClasses, addClasses, toggleClass } from '../../dom';
|
||||
import { hasProcessed, Position } from '../droptarget/droptarget';
|
||||
import { TabDropEvent } from '../events';
|
||||
|
||||
import { IGroupview } from '../groupview'
|
||||
import { IGroupAccessor } from '../../layout'
|
||||
import { last } from '../../array'
|
||||
import { DataTransferSingleton } from '../droptarget/dataTransfer'
|
||||
import { IGroupPanel } from '../panel/types'
|
||||
import { MouseEventKind } from '../events'
|
||||
import { IGroupview } from '../groupview';
|
||||
import { IGroupAccessor } from '../../layout';
|
||||
import { last } from '../../array';
|
||||
import { DataTransferSingleton } from '../droptarget/dataTransfer';
|
||||
import { IGroupPanel } from '../panel/types';
|
||||
import { MouseEventKind } from '../events';
|
||||
|
||||
export interface ITabContainer extends IDisposable {
|
||||
element: HTMLElement
|
||||
visible: boolean
|
||||
height: number
|
||||
hasActiveDragEvent: boolean
|
||||
delete: (id: string) => void
|
||||
indexOf: (tabOrId: ITab | string) => number
|
||||
at: (index: number) => ITab
|
||||
onDropEvent: Event<TabDropEvent>
|
||||
setActive: (isGroupActive: boolean) => void
|
||||
setActivePanel: (panel: IGroupPanel) => void
|
||||
isActive: (tab: ITab) => boolean
|
||||
closePanel: (panel: IGroupPanel) => void
|
||||
openPanel: (panel: IGroupPanel, index?: number) => void
|
||||
element: HTMLElement;
|
||||
visible: boolean;
|
||||
height: number;
|
||||
hasActiveDragEvent: boolean;
|
||||
delete: (id: string) => void;
|
||||
indexOf: (tabOrId: ITab | string) => number;
|
||||
at: (index: number) => ITab;
|
||||
onDropEvent: Event<TabDropEvent>;
|
||||
setActive: (isGroupActive: boolean) => void;
|
||||
setActivePanel: (panel: IGroupPanel) => void;
|
||||
isActive: (tab: ITab) => boolean;
|
||||
closePanel: (panel: IGroupPanel) => void;
|
||||
openPanel: (panel: IGroupPanel, index?: number) => void;
|
||||
}
|
||||
|
||||
export class TabContainer extends CompositeDisposable implements ITabContainer {
|
||||
private tabContainer: HTMLElement
|
||||
private _element: HTMLElement
|
||||
private actionContainer: HTMLElement
|
||||
private tabContainer: HTMLElement;
|
||||
private _element: HTMLElement;
|
||||
private actionContainer: HTMLElement;
|
||||
|
||||
private tabs: IValueDisposable<ITab>[] = []
|
||||
private selectedIndex: number = -1
|
||||
private active: boolean
|
||||
private activePanel: IGroupPanel
|
||||
private tabs: IValueDisposable<ITab>[] = [];
|
||||
private selectedIndex: number = -1;
|
||||
private active: boolean;
|
||||
private activePanel: IGroupPanel;
|
||||
|
||||
private _visible: boolean = true
|
||||
private _height: number
|
||||
private _visible: boolean = true;
|
||||
private _height: number;
|
||||
|
||||
private readonly _onDropped = new Emitter<TabDropEvent>()
|
||||
readonly onDropEvent: Event<TabDropEvent> = this._onDropped.event
|
||||
private readonly _onDropped = new Emitter<TabDropEvent>();
|
||||
readonly onDropEvent: Event<TabDropEvent> = this._onDropped.event;
|
||||
|
||||
get visible() {
|
||||
return this._visible
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
set visible(value: boolean) {
|
||||
this._visible = value
|
||||
this._visible = value;
|
||||
|
||||
toggleClass(this.element, 'hidden', !this._visible)
|
||||
toggleClass(this.element, 'hidden', !this._visible);
|
||||
}
|
||||
|
||||
get height() {
|
||||
return this._height
|
||||
return this._height;
|
||||
}
|
||||
|
||||
set height(value: number) {
|
||||
this._height = value
|
||||
this._element.style.height = `${this.height}px`
|
||||
this._height = value;
|
||||
this._element.style.height = `${this.height}px`;
|
||||
}
|
||||
|
||||
public get element() {
|
||||
return this._element
|
||||
return this._element;
|
||||
}
|
||||
|
||||
public isActive(tab: ITab) {
|
||||
return (
|
||||
this.selectedIndex > -1 &&
|
||||
this.tabs[this.selectedIndex].value === tab
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public get hasActiveDragEvent() {
|
||||
return !!this.tabs.find((tab) => tab.value.hasActiveDragEvent)
|
||||
return !!this.tabs.find((tab) => tab.value.hasActiveDragEvent);
|
||||
}
|
||||
|
||||
public at(index: number) {
|
||||
return this.tabs[index]?.value
|
||||
return this.tabs[index]?.value;
|
||||
}
|
||||
|
||||
public indexOf(tabOrId: ITab) {
|
||||
const id = typeof tabOrId === 'string' ? tabOrId : tabOrId.id
|
||||
return this.tabs.findIndex((tab) => tab.value.id === id)
|
||||
const id = typeof tabOrId === 'string' ? tabOrId : tabOrId.id;
|
||||
return this.tabs.findIndex((tab) => tab.value.id === id);
|
||||
}
|
||||
|
||||
constructor(private accessor: IGroupAccessor, private group: IGroupview) {
|
||||
super()
|
||||
super();
|
||||
|
||||
this.addDisposables(this._onDropped)
|
||||
this.addDisposables(this._onDropped);
|
||||
|
||||
this._element = document.createElement('div')
|
||||
this._element.className = 'title-container'
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'title-container';
|
||||
|
||||
this.height = 35
|
||||
this.height = 35;
|
||||
|
||||
this.actionContainer = document.createElement('div')
|
||||
this.actionContainer.className = 'action-container'
|
||||
this.actionContainer = document.createElement('div');
|
||||
this.actionContainer.className = 'action-container';
|
||||
|
||||
const list = document.createElement('ul')
|
||||
list.className = 'action-list'
|
||||
const list = document.createElement('ul');
|
||||
list.className = 'action-list';
|
||||
|
||||
this.tabContainer = document.createElement('div')
|
||||
this.tabContainer.className = 'tab-container'
|
||||
this.tabContainer = document.createElement('div');
|
||||
this.tabContainer.className = 'tab-container';
|
||||
|
||||
this._element.appendChild(this.tabContainer)
|
||||
this._element.appendChild(this.actionContainer)
|
||||
this._element.appendChild(this.tabContainer);
|
||||
this._element.appendChild(this.actionContainer);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.tabContainer, 'dragenter', (event) => {
|
||||
if (!DataTransferSingleton.has(this.accessor.id)) {
|
||||
console.debug('[tabs] invalid drop event')
|
||||
return
|
||||
console.debug('[tabs] invalid drop event');
|
||||
return;
|
||||
}
|
||||
if (!last(this.tabs).value.hasActiveDragEvent) {
|
||||
addClasses(this.tabContainer, 'drag-over-target')
|
||||
addClasses(this.tabContainer, 'drag-over-target');
|
||||
}
|
||||
}),
|
||||
addDisposableListener(this.tabContainer, 'dragover', (event) => {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
}),
|
||||
addDisposableListener(this.tabContainer, 'dragleave', (event) => {
|
||||
removeClasses(this.tabContainer, 'drag-over-target')
|
||||
removeClasses(this.tabContainer, 'drag-over-target');
|
||||
}),
|
||||
addDisposableListener(this.tabContainer, 'drop', (event) => {
|
||||
if (!DataTransferSingleton.has(this.accessor.id)) {
|
||||
console.debug('[tabs] invalid drop event')
|
||||
return
|
||||
console.debug('[tabs] invalid drop event');
|
||||
return;
|
||||
}
|
||||
if (hasProcessed(event)) {
|
||||
console.debug('[tab] drop event already processed')
|
||||
return
|
||||
console.debug('[tab] drop event already processed');
|
||||
return;
|
||||
}
|
||||
removeClasses(this.tabContainer, 'drag-over-target')
|
||||
removeClasses(this.tabContainer, 'drag-over-target');
|
||||
|
||||
const activetab = this.tabs.find(
|
||||
(tab) => tab.value.hasActiveDragEvent
|
||||
)
|
||||
);
|
||||
|
||||
const ignore = !!(
|
||||
activetab &&
|
||||
event
|
||||
.composedPath()
|
||||
.find((x) => activetab.value.element === x)
|
||||
)
|
||||
);
|
||||
|
||||
if (ignore) {
|
||||
console.debug('[tabs] ignore event')
|
||||
return
|
||||
console.debug('[tabs] ignore event');
|
||||
return;
|
||||
}
|
||||
|
||||
this._onDropped.fire({
|
||||
event: { event, position: Position.Center },
|
||||
index: this.tabs.length - 1,
|
||||
})
|
||||
});
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public setActive(isGroupActive: boolean) {
|
||||
this.active = isGroupActive
|
||||
this.active = isGroupActive;
|
||||
}
|
||||
|
||||
private addTab(
|
||||
@ -173,80 +173,80 @@ export class TabContainer extends CompositeDisposable implements ITabContainer {
|
||||
index: number = this.tabs.length
|
||||
) {
|
||||
if (index < 0 || index > this.tabs.length) {
|
||||
throw new Error('invalid location')
|
||||
throw new Error('invalid location');
|
||||
}
|
||||
|
||||
this.tabContainer.insertBefore(
|
||||
tab.value.element,
|
||||
this.tabContainer.children[index]
|
||||
)
|
||||
);
|
||||
|
||||
this.tabs = [
|
||||
...this.tabs.slice(0, index),
|
||||
tab,
|
||||
...this.tabs.slice(index),
|
||||
]
|
||||
];
|
||||
|
||||
if (this.selectedIndex < 0) {
|
||||
this.selectedIndex = index
|
||||
this.selectedIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
public delete(id: string) {
|
||||
const index = this.tabs.findIndex((tab) => tab.value.id === id)
|
||||
const index = this.tabs.findIndex((tab) => tab.value.id === id);
|
||||
|
||||
const tab = this.tabs.splice(index, 1)[0]
|
||||
const tab = this.tabs.splice(index, 1)[0];
|
||||
|
||||
const { value, disposable } = tab
|
||||
const { value, disposable } = tab;
|
||||
|
||||
disposable.dispose()
|
||||
value.element.remove()
|
||||
disposable.dispose();
|
||||
value.element.remove();
|
||||
}
|
||||
|
||||
public setActivePanel(panel: IGroupPanel) {
|
||||
this.tabs.forEach((tab) => {
|
||||
const isActivePanel = panel.id === tab.value.id
|
||||
tab.value.setActive(isActivePanel)
|
||||
})
|
||||
const isActivePanel = panel.id === tab.value.id;
|
||||
tab.value.setActive(isActivePanel);
|
||||
});
|
||||
}
|
||||
|
||||
public openPanel(panel: IGroupPanel, index: number = this.tabs.length) {
|
||||
if (this.tabs.find((tab) => tab.value.id === panel.id)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const tab = new Tab(panel.id, this.accessor, this.group)
|
||||
tab.setContent(panel.header.element)
|
||||
const tab = new Tab(panel.id, this.accessor, this.group);
|
||||
tab.setContent(panel.header.element);
|
||||
|
||||
const disposable = CompositeDisposable.from(
|
||||
tab.onChanged((event) => {
|
||||
switch (event.kind) {
|
||||
case MouseEventKind.CLICK:
|
||||
this.group.openPanel(panel)
|
||||
break
|
||||
this.group.openPanel(panel);
|
||||
break;
|
||||
}
|
||||
this.accessor.fireMouseEvent({ ...event, panel, tab: true })
|
||||
this.accessor.fireMouseEvent({ ...event, panel, tab: true });
|
||||
}),
|
||||
tab.onDropped((event) => {
|
||||
this._onDropped.fire({ event, index: this.indexOf(tab) })
|
||||
this._onDropped.fire({ event, index: this.indexOf(tab) });
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const value: IValueDisposable<ITab> = { value: tab, disposable }
|
||||
const value: IValueDisposable<ITab> = { value: tab, disposable };
|
||||
|
||||
this.addTab(value, index)
|
||||
this.activePanel = panel
|
||||
this.addTab(value, index);
|
||||
this.activePanel = panel;
|
||||
}
|
||||
|
||||
public closePanel(panel: IGroupPanel) {
|
||||
this.delete(panel.id)
|
||||
this.delete(panel.id);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose()
|
||||
super.dispose();
|
||||
|
||||
this.tabs.forEach((tab) => {
|
||||
tab.disposable.dispose()
|
||||
})
|
||||
this.tabs = []
|
||||
tab.disposable.dispose();
|
||||
});
|
||||
this.tabs = [];
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,21 @@
|
||||
export * from './splitview/splitview'
|
||||
export * from './paneview/paneview'
|
||||
export * from './gridview/gridview'
|
||||
export * from './groupview/groupview'
|
||||
export * from './groupview/panel/content/content'
|
||||
export * from './groupview/panel/tab/tab'
|
||||
export * from './events'
|
||||
export * from './lifecycle'
|
||||
export * from './groupview/panel/panel'
|
||||
export * from './groupview/panel/api'
|
||||
export * from './react/react'
|
||||
export * from './groupview/panel/types'
|
||||
export * from './groupview/panel/parts'
|
||||
export * from './react/layout'
|
||||
export * from './react/splitview'
|
||||
export * from './react/gridview'
|
||||
export * from './react/reactContentPart'
|
||||
export * from './react/reactHeaderPart'
|
||||
export * from './react/reactComponentGridView'
|
||||
export * from './splitview/splitview';
|
||||
export * from './paneview/paneview';
|
||||
export * from './gridview/gridview';
|
||||
export * from './groupview/groupview';
|
||||
export * from './groupview/panel/content/content';
|
||||
export * from './groupview/panel/tab/tab';
|
||||
export * from './events';
|
||||
export * from './lifecycle';
|
||||
export * from './groupview/panel/panel';
|
||||
export * from './groupview/panel/api';
|
||||
export * from './react/react';
|
||||
export * from './groupview/panel/types';
|
||||
export * from './groupview/panel/parts';
|
||||
export * from './react/layout';
|
||||
export * from './react/splitview';
|
||||
export * from './react/gridview';
|
||||
export * from './react/reactContentPart';
|
||||
export * from './react/reactHeaderPart';
|
||||
export * from './react/reactComponentGridView';
|
||||
|
||||
export * from './layout'
|
||||
export * from './layout';
|
||||
|
173
packages/splitview/src/layout/baseGrid.ts
Normal file
173
packages/splitview/src/layout/baseGrid.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import { MovementOptions2 } from '.';
|
||||
import { getGridLocation, Gridview, IGridView } from '../gridview/gridview';
|
||||
import { CompositeDisposable, IValueDisposable } from '../lifecycle';
|
||||
import { sequentialNumberGenerator } from '../math';
|
||||
|
||||
const nextLayoutId = sequentialNumberGenerator();
|
||||
|
||||
export interface BaseGridOptions {
|
||||
readonly proportionalLayout?: boolean;
|
||||
}
|
||||
|
||||
export interface IBaseGridView extends IGridView {
|
||||
id: string;
|
||||
setActive(isActive: boolean): void;
|
||||
}
|
||||
|
||||
export interface IBaseGrid<T extends IBaseGridView> {
|
||||
readonly element: HTMLElement;
|
||||
readonly id: string;
|
||||
readonly minimumHeight: number;
|
||||
readonly maximumHeight: number;
|
||||
readonly minimumWidth: number;
|
||||
readonly maximumWidth: number;
|
||||
readonly activeGroup: T;
|
||||
readonly size: number;
|
||||
getGroup(id: string): T | undefined;
|
||||
}
|
||||
|
||||
export class BaseGrid<T extends IBaseGridView>
|
||||
extends CompositeDisposable
|
||||
implements IBaseGrid<T> {
|
||||
private readonly _id = nextLayoutId.next();
|
||||
protected readonly groups = new Map<string, IValueDisposable<T>>();
|
||||
protected readonly gridview: Gridview;
|
||||
//
|
||||
private resizeTimer: NodeJS.Timer;
|
||||
protected _activeGroup: T;
|
||||
//
|
||||
protected _size: number;
|
||||
protected _orthogonalSize: number;
|
||||
|
||||
get id() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get element() {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.groups.size;
|
||||
}
|
||||
|
||||
get minimumHeight() {
|
||||
return this.gridview.minimumHeight;
|
||||
}
|
||||
get maximumHeight() {
|
||||
return this.gridview.maximumHeight;
|
||||
}
|
||||
get minimumWidth() {
|
||||
return this.gridview.maximumWidth;
|
||||
}
|
||||
get maximumWidth() {
|
||||
return this.gridview.maximumWidth;
|
||||
}
|
||||
|
||||
get activeGroup() {
|
||||
return this._activeGroup;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly _element: HTMLElement,
|
||||
options: BaseGridOptions
|
||||
) {
|
||||
super();
|
||||
|
||||
this.gridview = new Gridview(!!options.proportionalLayout);
|
||||
this.element.appendChild(this.gridview.element);
|
||||
}
|
||||
|
||||
public getGroup(id: string): T | undefined {
|
||||
return this.groups.get(id)?.value;
|
||||
}
|
||||
|
||||
public doSetGroupActive(group: T) {
|
||||
if (this._activeGroup && this._activeGroup !== group) {
|
||||
this._activeGroup.setActive(false);
|
||||
}
|
||||
group.setActive(true);
|
||||
this._activeGroup = group;
|
||||
}
|
||||
|
||||
public moveToNext(options?: MovementOptions2) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (!options.group) {
|
||||
options.group = this.activeGroup;
|
||||
}
|
||||
|
||||
const location = getGridLocation(options.group.element);
|
||||
const next = this.gridview.next(location)?.view;
|
||||
this.doSetGroupActive(next as T);
|
||||
}
|
||||
|
||||
public moveToPrevious(options?: MovementOptions2) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (!options.group) {
|
||||
options.group = this.activeGroup;
|
||||
}
|
||||
|
||||
const location = getGridLocation(options.group.element);
|
||||
const next = this.gridview.preivous(location)?.view;
|
||||
this.doSetGroupActive(next as T);
|
||||
}
|
||||
|
||||
public layout(
|
||||
size: number,
|
||||
orthogonalSize: number,
|
||||
forceResize?: boolean
|
||||
): void {
|
||||
const different =
|
||||
forceResize ||
|
||||
size !== this._size ||
|
||||
orthogonalSize !== this._orthogonalSize;
|
||||
|
||||
if (!different) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.element.style.height = `${orthogonalSize}px`;
|
||||
this.element.style.width = `${size}px`;
|
||||
|
||||
this._size = size;
|
||||
this._orthogonalSize = orthogonalSize;
|
||||
this.gridview.layout(size, orthogonalSize);
|
||||
}
|
||||
|
||||
public setAutoResizeToFit(enabled: boolean): void {
|
||||
if (this.resizeTimer) {
|
||||
clearInterval(this.resizeTimer);
|
||||
}
|
||||
if (enabled) {
|
||||
this.resizeTimer = setInterval(() => {
|
||||
this.resizeToFit();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the layout to fit the parent container
|
||||
*/
|
||||
public resizeToFit(): void {
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
} = this.element.parentElement.getBoundingClientRect();
|
||||
this.layout(width, height);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (this.resizeTimer) {
|
||||
clearInterval(this.resizeTimer);
|
||||
this.resizeTimer = undefined;
|
||||
}
|
||||
|
||||
this.gridview.dispose();
|
||||
}
|
||||
}
|
@ -3,87 +3,87 @@ import {
|
||||
PanelContentPartConstructor,
|
||||
PanelHeaderPart,
|
||||
PanelHeaderPartConstructor,
|
||||
} from '../groupview/panel/parts'
|
||||
import { FrameworkFactory } from '../types'
|
||||
import { DefaultTab } from './components/tab/defaultTab'
|
||||
} from '../groupview/panel/parts';
|
||||
import { FrameworkFactory } from '../types';
|
||||
import { DefaultTab } from './components/tab/defaultTab';
|
||||
|
||||
export function createContentComponent(
|
||||
componentName: string | PanelContentPartConstructor | any,
|
||||
components: {
|
||||
[componentName: string]: PanelContentPartConstructor
|
||||
[componentName: string]: PanelContentPartConstructor;
|
||||
},
|
||||
frameworkComponents: {
|
||||
[componentName: string]: any
|
||||
[componentName: string]: any;
|
||||
},
|
||||
createFrameworkComponent: FrameworkFactory<PanelContentPart>
|
||||
): PanelContentPart {
|
||||
const Component =
|
||||
typeof componentName === 'string'
|
||||
? components[componentName]
|
||||
: componentName
|
||||
: componentName;
|
||||
const FrameworkComponent =
|
||||
typeof componentName === 'string'
|
||||
? frameworkComponents[componentName]
|
||||
: componentName
|
||||
: componentName;
|
||||
if (Component && FrameworkComponent) {
|
||||
throw new Error(
|
||||
`cannot register component ${componentName} as both a component and frameworkComponent`
|
||||
)
|
||||
);
|
||||
}
|
||||
if (FrameworkComponent) {
|
||||
if (!createFrameworkComponent) {
|
||||
throw new Error(
|
||||
'you must register a frameworkPanelWrapper to use framework components'
|
||||
)
|
||||
);
|
||||
}
|
||||
const wrappedComponent = createFrameworkComponent.createComponent(
|
||||
componentName,
|
||||
FrameworkComponent
|
||||
)
|
||||
return wrappedComponent
|
||||
);
|
||||
return wrappedComponent;
|
||||
}
|
||||
return new Component() as PanelContentPart
|
||||
return new Component() as PanelContentPart;
|
||||
}
|
||||
|
||||
export function createTabComponent(
|
||||
componentName: string | PanelHeaderPartConstructor | any,
|
||||
components: {
|
||||
[componentName: string]: PanelHeaderPartConstructor
|
||||
[componentName: string]: PanelHeaderPartConstructor;
|
||||
},
|
||||
frameworkComponents: {
|
||||
[componentName: string]: any
|
||||
[componentName: string]: any;
|
||||
},
|
||||
createFrameworkComponent: FrameworkFactory<PanelHeaderPart>
|
||||
): PanelHeaderPart {
|
||||
const Component =
|
||||
typeof componentName === 'string'
|
||||
? components[componentName]
|
||||
: componentName
|
||||
: componentName;
|
||||
const FrameworkComponent =
|
||||
typeof componentName === 'string'
|
||||
? frameworkComponents[componentName]
|
||||
: componentName
|
||||
: componentName;
|
||||
if (Component && FrameworkComponent) {
|
||||
throw new Error(
|
||||
`cannot register component ${componentName} as both a component and frameworkComponent`
|
||||
)
|
||||
);
|
||||
}
|
||||
if (FrameworkComponent) {
|
||||
if (!createFrameworkComponent) {
|
||||
throw new Error(
|
||||
'you must register a frameworkPanelWrapper to use framework components'
|
||||
)
|
||||
);
|
||||
}
|
||||
const wrappedComponent = createFrameworkComponent.createComponent(
|
||||
componentName,
|
||||
FrameworkComponent
|
||||
)
|
||||
return wrappedComponent
|
||||
);
|
||||
return wrappedComponent;
|
||||
}
|
||||
|
||||
if (!Component) {
|
||||
return new DefaultTab()
|
||||
return new DefaultTab();
|
||||
}
|
||||
|
||||
return new Component() as PanelHeaderPart
|
||||
return new Component() as PanelHeaderPart;
|
||||
}
|
||||
|
@ -1,165 +1,85 @@
|
||||
import { Gridview, getRelativeLocation, IGridView } from '../gridview/gridview'
|
||||
import { Position } from '../groupview/droptarget/droptarget'
|
||||
import { getGridLocation } from '../gridview/gridview'
|
||||
import { tail, sequenceEquals } from '../array'
|
||||
import {
|
||||
GroupChangeKind,
|
||||
GroupChangeEvent,
|
||||
GroupDropEvent,
|
||||
} from '../groupview/groupview'
|
||||
import { CompositeDisposable, Disposable, IValueDisposable } from '../lifecycle'
|
||||
import { Event, Emitter } from '../events'
|
||||
import { getRelativeLocation, IGridView } from '../gridview/gridview';
|
||||
import { Position } from '../groupview/droptarget/droptarget';
|
||||
import { getGridLocation } from '../gridview/gridview';
|
||||
import { tail, sequenceEquals } from '../array';
|
||||
import { GroupChangeKind, GroupChangeEvent } from '../groupview/groupview';
|
||||
import { Disposable, IValueDisposable } from '../lifecycle';
|
||||
import { Event, Emitter } from '../events';
|
||||
|
||||
import { DebugWidget } from './components/debug/debug'
|
||||
import { DebugWidget } from './components/debug/debug';
|
||||
|
||||
import { sequentialNumberGenerator } from '../math'
|
||||
import { IPanelDeserializer } from './deserializer'
|
||||
import { sequentialNumberGenerator } from '../math';
|
||||
import { IPanelDeserializer } from './deserializer';
|
||||
|
||||
import { createComponent } from '../splitview/options'
|
||||
import { LayoutPriority, Orientation } from '../splitview/splitview'
|
||||
import { createComponent } from '../splitview/options';
|
||||
import { LayoutPriority, Orientation } from '../splitview/splitview';
|
||||
import { MovementOptions2 } from './options';
|
||||
import { GridComponentOptions } from '.';
|
||||
import { BaseGrid, IBaseGrid, IBaseGridView } from './baseGrid';
|
||||
|
||||
const nextLayoutId = sequentialNumberGenerator()
|
||||
const nextLayoutId = sequentialNumberGenerator();
|
||||
|
||||
export interface AddComponentOptions {
|
||||
component: string
|
||||
params?: { [key: string]: any }
|
||||
id: string
|
||||
component: string;
|
||||
params?: { [key: string]: any };
|
||||
id: string;
|
||||
position?: {
|
||||
direction?: 'left' | 'right' | 'above' | 'below' | 'within'
|
||||
reference: string
|
||||
}
|
||||
size?: number
|
||||
priority?: LayoutPriority
|
||||
snap?: boolean
|
||||
direction?: 'left' | 'right' | 'above' | 'below' | 'within';
|
||||
reference: string;
|
||||
};
|
||||
size?: number;
|
||||
priority?: LayoutPriority;
|
||||
snap?: boolean;
|
||||
}
|
||||
|
||||
export interface GridComponentOptions {
|
||||
orientation: Orientation
|
||||
components?: {
|
||||
[componentName: string]: IComponentGridview
|
||||
}
|
||||
frameworkComponents?: {
|
||||
[componentName: string]: any
|
||||
}
|
||||
frameworkComponentFactory: any
|
||||
tabHeight?: number
|
||||
export interface IComponentGridview extends IBaseGridView {
|
||||
init?: (params: { params: any }) => void;
|
||||
priority?: LayoutPriority;
|
||||
}
|
||||
|
||||
export interface IComponentGridview extends IGridView {
|
||||
id: string
|
||||
init: (params: { params: any }) => void
|
||||
priority?: LayoutPriority
|
||||
}
|
||||
|
||||
export interface MovementOptions2 {
|
||||
group?: IComponentGridview
|
||||
}
|
||||
|
||||
export interface IComponentGridviewLayout {
|
||||
addComponent(options: AddComponentOptions): void
|
||||
export interface IComponentGridviewLayout
|
||||
extends IBaseGrid<IComponentGridview> {
|
||||
addComponent(options: AddComponentOptions): void;
|
||||
}
|
||||
|
||||
export class ComponentGridview
|
||||
extends CompositeDisposable
|
||||
extends BaseGrid<IComponentGridview>
|
||||
implements IComponentGridviewLayout {
|
||||
private readonly _id = nextLayoutId.next()
|
||||
private readonly groups = new Map<
|
||||
string,
|
||||
IValueDisposable<IComponentGridview>
|
||||
>()
|
||||
private readonly gridview: Gridview = new Gridview(false)
|
||||
// events
|
||||
private readonly _onDidLayoutChange = new Emitter<GroupChangeEvent>()
|
||||
private readonly _onDidLayoutChange = new Emitter<GroupChangeEvent>();
|
||||
readonly onDidLayoutChange: Event<GroupChangeEvent> = this
|
||||
._onDidLayoutChange.event
|
||||
._onDidLayoutChange.event;
|
||||
// everything else
|
||||
private _size: number
|
||||
private _orthogonalSize: number
|
||||
private _activeGroup: IComponentGridview
|
||||
private _deserializer: IPanelDeserializer
|
||||
private resizeTimer: NodeJS.Timer
|
||||
private debugContainer: DebugWidget
|
||||
|
||||
private _deserializer: IPanelDeserializer;
|
||||
private debugContainer: DebugWidget;
|
||||
|
||||
constructor(
|
||||
private readonly element: HTMLElement,
|
||||
element: HTMLElement,
|
||||
public readonly options: GridComponentOptions
|
||||
) {
|
||||
super()
|
||||
|
||||
this.element.appendChild(this.gridview.element)
|
||||
super(element, { proportionalLayout: true });
|
||||
|
||||
if (!this.options.components) {
|
||||
this.options.components = {}
|
||||
this.options.components = {};
|
||||
}
|
||||
if (!this.options.frameworkComponents) {
|
||||
this.options.frameworkComponents = {}
|
||||
this.options.frameworkComponents = {};
|
||||
}
|
||||
|
||||
this.addDisposables(
|
||||
this.gridview.onDidChange((e) => {
|
||||
this._onDidLayoutChange.fire({ kind: GroupChangeKind.LAYOUT })
|
||||
this._onDidLayoutChange.fire({ kind: GroupChangeKind.LAYOUT });
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
get minimumHeight() {
|
||||
return this.gridview.minimumHeight
|
||||
}
|
||||
get maximumHeight() {
|
||||
return this.gridview.maximumHeight
|
||||
}
|
||||
get minimumWidth() {
|
||||
return this.gridview.maximumWidth
|
||||
}
|
||||
get maximumWidth() {
|
||||
return this.gridview.maximumWidth
|
||||
}
|
||||
|
||||
get activeGroup() {
|
||||
return this._activeGroup
|
||||
);
|
||||
}
|
||||
|
||||
get deserializer() {
|
||||
return this._deserializer
|
||||
return this._deserializer;
|
||||
}
|
||||
|
||||
set deserializer(value: IPanelDeserializer) {
|
||||
this._deserializer = value
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this._id
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.groups.size
|
||||
}
|
||||
|
||||
public moveToNext(options?: MovementOptions2) {
|
||||
if (!options) {
|
||||
options = {}
|
||||
}
|
||||
if (!options.group) {
|
||||
options.group = this.activeGroup
|
||||
}
|
||||
|
||||
const location = getGridLocation(options.group.element)
|
||||
const next = this.gridview.next(location)?.view as IComponentGridview
|
||||
this.doSetGroupActive(next)
|
||||
}
|
||||
|
||||
public moveToPrevious(options?: MovementOptions2) {
|
||||
if (!options) {
|
||||
options = {}
|
||||
}
|
||||
if (!options.group) {
|
||||
options.group = this.activeGroup
|
||||
}
|
||||
|
||||
const location = getGridLocation(options.group.element)
|
||||
const next = this.gridview.preivous(location)
|
||||
?.view as IComponentGridview
|
||||
this.doSetGroupActive(next)
|
||||
this._deserializer = value;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -168,21 +88,21 @@ export class ComponentGridview
|
||||
* @returns A JSON respresentation of the layout
|
||||
*/
|
||||
public toJSON() {
|
||||
const data = this.gridview.serialize()
|
||||
const data = this.gridview.serialize();
|
||||
|
||||
return { grid: data }
|
||||
return { grid: data };
|
||||
}
|
||||
|
||||
public deserialize(data: any) {
|
||||
this.gridview.clear()
|
||||
this.groups.clear()
|
||||
this.gridview.clear();
|
||||
this.groups.clear();
|
||||
|
||||
this.fromJSON(data, this.deserializer)
|
||||
this.gridview.layout(this._size, this._orthogonalSize)
|
||||
this.fromJSON(data, this.deserializer);
|
||||
this.gridview.layout(this._size, this._orthogonalSize);
|
||||
}
|
||||
|
||||
public fromJSON(data: any, deserializer: IPanelDeserializer) {
|
||||
const { grid, panels } = data
|
||||
const { grid, panels } = data;
|
||||
|
||||
// this.gridview.deserialize(
|
||||
// grid,
|
||||
@ -195,48 +115,26 @@ export class ComponentGridview
|
||||
// },
|
||||
// })
|
||||
// );
|
||||
this._onDidLayoutChange.fire({ kind: GroupChangeKind.NEW_LAYOUT })
|
||||
}
|
||||
|
||||
public setAutoResizeToFit(enabled: boolean) {
|
||||
if (this.resizeTimer) {
|
||||
clearInterval(this.resizeTimer)
|
||||
}
|
||||
if (enabled) {
|
||||
this.resizeTimer = setInterval(() => {
|
||||
this.resizeToFit()
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the layout to fit the parent container
|
||||
*/
|
||||
public resizeToFit() {
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
} = this.element.parentElement.getBoundingClientRect()
|
||||
this.layout(width, height)
|
||||
this._onDidLayoutChange.fire({ kind: GroupChangeKind.NEW_LAYOUT });
|
||||
}
|
||||
|
||||
public addComponent(options: AddComponentOptions) {
|
||||
let relativeLocation: number[] = [0]
|
||||
let relativeLocation: number[] = [0];
|
||||
|
||||
if (options.position?.reference) {
|
||||
const referenceGroup = this.groups.get(options.position.reference)
|
||||
.value
|
||||
.value;
|
||||
|
||||
const target = this.toTarget(options.position.direction)
|
||||
const target = this.toTarget(options.position.direction);
|
||||
if (target === Position.Center) {
|
||||
throw new Error(`${target} not supported as an option`)
|
||||
throw new Error(`${target} not supported as an option`);
|
||||
} else {
|
||||
const location = getGridLocation(referenceGroup.element)
|
||||
const location = getGridLocation(referenceGroup.element);
|
||||
relativeLocation = getRelativeLocation(
|
||||
this.gridview.orientation,
|
||||
location,
|
||||
target
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,28 +143,24 @@ export class ComponentGridview
|
||||
this.options.components,
|
||||
this.options.frameworkComponents,
|
||||
this.options.frameworkComponentFactory.createComponent
|
||||
)
|
||||
view.init({ params: {} })
|
||||
view.priority = options.priority
|
||||
view.snap = options.snap
|
||||
);
|
||||
view.init({ params: {} });
|
||||
view.priority = options.priority;
|
||||
view.snap = options.snap;
|
||||
|
||||
this.groups.set(options.id, {
|
||||
value: view,
|
||||
disposable: Disposable.NONE,
|
||||
})
|
||||
});
|
||||
|
||||
this.doAddGroup(view, relativeLocation, options.size)
|
||||
}
|
||||
|
||||
public getGroup(id: string) {
|
||||
return this.groups.get(id)?.value
|
||||
this.doAddGroup(view, relativeLocation, options.size);
|
||||
}
|
||||
|
||||
public removeGroup(group: IComponentGridview) {
|
||||
if (group === this._activeGroup) {
|
||||
this._activeGroup = undefined
|
||||
this._activeGroup = undefined;
|
||||
}
|
||||
this.doRemoveGroup(group)
|
||||
this.doRemoveGroup(group);
|
||||
}
|
||||
|
||||
private doAddGroup(
|
||||
@ -274,10 +168,10 @@ export class ComponentGridview
|
||||
location: number[],
|
||||
size?: number
|
||||
) {
|
||||
this.gridview.addView(group, size ?? { type: 'distribute' }, location)
|
||||
this.gridview.addView(group, size ?? { type: 'distribute' }, location);
|
||||
|
||||
this._onDidLayoutChange.fire({ kind: GroupChangeKind.ADD_GROUP })
|
||||
this.doSetGroupActive(group)
|
||||
this._onDidLayoutChange.fire({ kind: GroupChangeKind.ADD_GROUP });
|
||||
this.doSetGroupActive(group);
|
||||
}
|
||||
|
||||
private doRemoveGroup(
|
||||
@ -285,94 +179,71 @@ export class ComponentGridview
|
||||
options?: { skipActive?: boolean; skipDispose?: boolean }
|
||||
) {
|
||||
if (!this.groups.has(group.id)) {
|
||||
throw new Error('invalid operation')
|
||||
throw new Error('invalid operation');
|
||||
}
|
||||
|
||||
const { disposable } = this.groups.get(group.id)
|
||||
const { disposable } = this.groups.get(group.id);
|
||||
|
||||
if (!options?.skipDispose) {
|
||||
disposable.dispose()
|
||||
this.groups.delete(group.id)
|
||||
disposable.dispose();
|
||||
this.groups.delete(group.id);
|
||||
}
|
||||
|
||||
const view = this.gridview.remove(group, { type: 'distribute' })
|
||||
this._onDidLayoutChange.fire({ kind: GroupChangeKind.REMOVE_GROUP })
|
||||
const view = this.gridview.remove(group, { type: 'distribute' });
|
||||
this._onDidLayoutChange.fire({ kind: GroupChangeKind.REMOVE_GROUP });
|
||||
|
||||
if (!options?.skipActive && this.groups.size > 0) {
|
||||
this.doSetGroupActive(Array.from(this.groups.values())[0].value)
|
||||
this.doSetGroupActive(Array.from(this.groups.values())[0].value);
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
public doSetGroupActive(group: IComponentGridview) {
|
||||
if (this._activeGroup && this._activeGroup !== group) {
|
||||
// this._activeGroup.setActive(false);
|
||||
}
|
||||
// group.setActive(true);
|
||||
this._activeGroup = group
|
||||
return view;
|
||||
}
|
||||
|
||||
public moveGroup(
|
||||
referenceGroup: IComponentGridview,
|
||||
groupId: string,
|
||||
itemId: string,
|
||||
target: Position
|
||||
) {
|
||||
const sourceGroup = groupId ? this.groups.get(groupId).value : undefined
|
||||
const sourceGroup = groupId
|
||||
? this.groups.get(groupId).value
|
||||
: undefined;
|
||||
|
||||
const referenceLocation = getGridLocation(referenceGroup.element)
|
||||
const referenceLocation = getGridLocation(referenceGroup.element);
|
||||
const targetLocation = getRelativeLocation(
|
||||
this.gridview.orientation,
|
||||
referenceLocation,
|
||||
target
|
||||
)
|
||||
);
|
||||
|
||||
const [targetParentLocation, to] = tail(targetLocation)
|
||||
const sourceLocation = getGridLocation(sourceGroup.element)
|
||||
const [sourceParentLocation, from] = tail(sourceLocation)
|
||||
const [targetParentLocation, to] = tail(targetLocation);
|
||||
const sourceLocation = getGridLocation(sourceGroup.element);
|
||||
const [sourceParentLocation, from] = tail(sourceLocation);
|
||||
|
||||
if (sequenceEquals(sourceParentLocation, targetParentLocation)) {
|
||||
// special case when 'swapping' two views within same grid location
|
||||
// if a group has one tab - we are essentially moving the 'group'
|
||||
// which is equivalent to swapping two views in this case
|
||||
this.gridview.moveView(sourceParentLocation, from, to)
|
||||
this.gridview.moveView(sourceParentLocation, from, to);
|
||||
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// source group will become empty so delete the group
|
||||
const targetGroup = this.doRemoveGroup(sourceGroup, {
|
||||
skipActive: true,
|
||||
skipDispose: true,
|
||||
}) as IComponentGridview
|
||||
}) as IComponentGridview;
|
||||
|
||||
// after deleting the group we need to re-evaulate the ref location
|
||||
const updatedReferenceLocation = getGridLocation(referenceGroup.element)
|
||||
const updatedReferenceLocation = getGridLocation(
|
||||
referenceGroup.element
|
||||
);
|
||||
const location = getRelativeLocation(
|
||||
this.gridview.orientation,
|
||||
updatedReferenceLocation,
|
||||
target
|
||||
)
|
||||
this.doAddGroup(targetGroup, location)
|
||||
}
|
||||
|
||||
public layout(size: number, orthogonalSize: number, force?: boolean) {
|
||||
const different =
|
||||
force ||
|
||||
size !== this._size ||
|
||||
orthogonalSize !== this._orthogonalSize
|
||||
|
||||
if (!different) {
|
||||
return
|
||||
}
|
||||
|
||||
this.element.style.height = `${orthogonalSize}px`
|
||||
this.element.style.width = `${size}px`
|
||||
|
||||
this._size = size
|
||||
this._orthogonalSize = orthogonalSize
|
||||
this.gridview.layout(size, orthogonalSize)
|
||||
);
|
||||
this.doAddGroup(targetGroup, location);
|
||||
}
|
||||
|
||||
private toTarget(
|
||||
@ -380,31 +251,24 @@ export class ComponentGridview
|
||||
) {
|
||||
switch (direction) {
|
||||
case 'left':
|
||||
return Position.Left
|
||||
return Position.Left;
|
||||
case 'right':
|
||||
return Position.Right
|
||||
return Position.Right;
|
||||
case 'above':
|
||||
return Position.Top
|
||||
return Position.Top;
|
||||
case 'below':
|
||||
return Position.Bottom
|
||||
return Position.Bottom;
|
||||
case 'within':
|
||||
default:
|
||||
return Position.Center
|
||||
return Position.Center;
|
||||
}
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose()
|
||||
super.dispose();
|
||||
|
||||
this.gridview.dispose()
|
||||
this.debugContainer?.dispose();
|
||||
|
||||
this.debugContainer?.dispose()
|
||||
|
||||
if (this.resizeTimer) {
|
||||
clearInterval(this.resizeTimer)
|
||||
this.resizeTimer = undefined
|
||||
}
|
||||
|
||||
this._onDidLayoutChange.dispose()
|
||||
this._onDidLayoutChange.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,59 +1,59 @@
|
||||
import { CompositeDisposable } from '../../../lifecycle'
|
||||
import { Layout } from '../../layout'
|
||||
import { GroupChangeKind } from '../../../groupview/groupview'
|
||||
import { CompositeDisposable } from '../../../lifecycle';
|
||||
import { Layout } from '../../layout';
|
||||
import { GroupChangeKind } from '../../../groupview/groupview';
|
||||
|
||||
export class DebugWidget extends CompositeDisposable {
|
||||
private _element: HTMLElement
|
||||
private _element: HTMLElement;
|
||||
|
||||
constructor(private layout: Layout) {
|
||||
super()
|
||||
super();
|
||||
|
||||
let container = document.getElementById('layout-debug-container')
|
||||
let container = document.getElementById('layout-debug-container');
|
||||
|
||||
if (!container) {
|
||||
container = document.createElement('div')
|
||||
container.id = 'layout-debug-container'
|
||||
container.className = 'layout-debug-container'
|
||||
document.body.appendChild(container)
|
||||
container = document.createElement('div');
|
||||
container.id = 'layout-debug-container';
|
||||
container.className = 'layout-debug-container';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
this._element = document.createElement('div')
|
||||
this._element = document.createElement('div');
|
||||
this._element.innerHTML =
|
||||
`<div class='layout-debug-widget'>` +
|
||||
`<div class='layout-debug-widget-row'><span>Groups:</span><span id='group-count'>0</span></div>` +
|
||||
`<div class='layout-debug-widget-row'><span>Panels:</span><span id='panel-count'>0</span></div>` +
|
||||
`</div>`
|
||||
`</div>`;
|
||||
|
||||
container.appendChild(this._element)
|
||||
container.appendChild(this._element);
|
||||
|
||||
const gc = this._element.querySelector('#group-count')
|
||||
const pc = this._element.querySelector('#panel-count')
|
||||
const gc = this._element.querySelector('#group-count');
|
||||
const pc = this._element.querySelector('#panel-count');
|
||||
|
||||
const events = [
|
||||
GroupChangeKind.PANEL_CREATED,
|
||||
GroupChangeKind.PANEL_DESTROYED,
|
||||
GroupChangeKind.ADD_GROUP,
|
||||
GroupChangeKind.REMOVE_GROUP,
|
||||
]
|
||||
];
|
||||
|
||||
this.addDisposables(
|
||||
this.layout.onDidLayoutChange((event) => {
|
||||
if (events.includes(event.kind)) {
|
||||
gc.textContent = this.layout.size.toString()
|
||||
pc.textContent = this.layout.totalPanels.toString()
|
||||
gc.textContent = this.layout.size.toString();
|
||||
pc.textContent = this.layout.totalPanels.toString();
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose()
|
||||
super.dispose();
|
||||
|
||||
this._element.remove()
|
||||
this._element.remove();
|
||||
|
||||
const container = document.getElementById('layout-debug-container')
|
||||
const container = document.getElementById('layout-debug-container');
|
||||
if (container && container.children.length === 0) {
|
||||
container.remove()
|
||||
container.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,96 +1,96 @@
|
||||
import { CompositeDisposable, MutableDisposable } from '../../../lifecycle'
|
||||
import { CompositeDisposable, MutableDisposable } from '../../../lifecycle';
|
||||
import {
|
||||
PanelHeaderPart,
|
||||
PartInitParameters,
|
||||
} from '../../../groupview/panel/parts'
|
||||
import { addDisposableListener } from '../../../events'
|
||||
import { toggleClass } from '../../../dom'
|
||||
} from '../../../groupview/panel/parts';
|
||||
import { addDisposableListener } from '../../../events';
|
||||
import { toggleClass } from '../../../dom';
|
||||
|
||||
export class DefaultTab extends CompositeDisposable implements PanelHeaderPart {
|
||||
private _element: HTMLElement
|
||||
private _isGroupActive: boolean
|
||||
private _isPanelVisible: boolean
|
||||
private _element: HTMLElement;
|
||||
private _isGroupActive: boolean;
|
||||
private _isPanelVisible: boolean;
|
||||
|
||||
//
|
||||
private _content: HTMLElement
|
||||
private _actionContainer: HTMLElement
|
||||
private _list: HTMLElement
|
||||
private action: HTMLElement
|
||||
private _content: HTMLElement;
|
||||
private _actionContainer: HTMLElement;
|
||||
private _list: HTMLElement;
|
||||
private action: HTMLElement;
|
||||
//
|
||||
private params: PartInitParameters
|
||||
private params: PartInitParameters;
|
||||
//
|
||||
private isDirtyDisposable = new MutableDisposable()
|
||||
private isDirtyDisposable = new MutableDisposable();
|
||||
|
||||
get element() {
|
||||
return this._element
|
||||
return this._element;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return '__DEFAULT_TAB__'
|
||||
return '__DEFAULT_TAB__';
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
super();
|
||||
|
||||
this._element = document.createElement('div')
|
||||
this._element.className = 'default-tab'
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'default-tab';
|
||||
//
|
||||
this._content = document.createElement('div')
|
||||
this._content.className = 'tab-content'
|
||||
this._content = document.createElement('div');
|
||||
this._content.className = 'tab-content';
|
||||
//
|
||||
this._actionContainer = document.createElement('div')
|
||||
this._actionContainer.className = 'action-container'
|
||||
this._actionContainer = document.createElement('div');
|
||||
this._actionContainer.className = 'action-container';
|
||||
//
|
||||
this._list = document.createElement('ul')
|
||||
this._list.className = 'tab-list'
|
||||
this._list = document.createElement('ul');
|
||||
this._list.className = 'tab-list';
|
||||
//
|
||||
this.action = document.createElement('a')
|
||||
this.action.className = 'tab-action'
|
||||
this.action = document.createElement('a');
|
||||
this.action.className = 'tab-action';
|
||||
//
|
||||
this._element.appendChild(this._content)
|
||||
this._element.appendChild(this._actionContainer)
|
||||
this._actionContainer.appendChild(this._list)
|
||||
this._list.appendChild(this.action)
|
||||
this._element.appendChild(this._content);
|
||||
this._element.appendChild(this._actionContainer);
|
||||
this._actionContainer.appendChild(this._list);
|
||||
this._list.appendChild(this.action);
|
||||
//
|
||||
this.addDisposables(
|
||||
addDisposableListener(this._actionContainer, 'mousedown', (ev) => {
|
||||
ev.preventDefault()
|
||||
ev.preventDefault();
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
this.render()
|
||||
this.render();
|
||||
}
|
||||
|
||||
public toJSON() {
|
||||
return { id: this.id }
|
||||
return { id: this.id };
|
||||
}
|
||||
|
||||
public init(params: PartInitParameters) {
|
||||
this.params = params
|
||||
this._content.textContent = params.title
|
||||
this.params = params;
|
||||
this._content.textContent = params.title;
|
||||
|
||||
this.isDirtyDisposable.value = this.params.api.onDidDirtyChange(
|
||||
(event) => {
|
||||
const isDirty = event
|
||||
toggleClass(this.action, 'dirty', isDirty)
|
||||
const isDirty = event;
|
||||
toggleClass(this.action, 'dirty', isDirty);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if (!this.params.suppressClosable) {
|
||||
addDisposableListener(this.action, 'click', (ev) => {
|
||||
ev.preventDefault() //
|
||||
this.params.api.close()
|
||||
})
|
||||
ev.preventDefault(); //
|
||||
this.params.api.close();
|
||||
});
|
||||
} else {
|
||||
this.action.classList.add('disable-close')
|
||||
this.action.classList.add('disable-close');
|
||||
}
|
||||
}
|
||||
|
||||
public setVisible(isPanelVisible: boolean, isGroupVisible: boolean) {
|
||||
this._isPanelVisible = isPanelVisible
|
||||
this._isGroupActive = isGroupVisible
|
||||
this._isPanelVisible = isPanelVisible;
|
||||
this._isGroupActive = isGroupVisible;
|
||||
|
||||
this.render()
|
||||
this.render();
|
||||
}
|
||||
|
||||
private render() {
|
||||
|
@ -1,81 +1,81 @@
|
||||
import {
|
||||
WatermarkPart,
|
||||
WatermarkPartInitParameters,
|
||||
} from '../../../groupview/panel/parts'
|
||||
import { IGroupAccessor } from '../../layout'
|
||||
import { IGroupview } from '../../../groupview/groupview'
|
||||
import { ActionContainer } from '../../../groupview/actions/actionsContainer'
|
||||
import { addDisposableListener } from '../../../events'
|
||||
import { toggleClass } from '../../../dom'
|
||||
import { CompositeDisposable } from '../../../lifecycle'
|
||||
} from '../../../groupview/panel/parts';
|
||||
import { IGroupAccessor } from '../../layout';
|
||||
import { IGroupview } from '../../../groupview/groupview';
|
||||
import { ActionContainer } from '../../../groupview/actions/actionsContainer';
|
||||
import { addDisposableListener } from '../../../events';
|
||||
import { toggleClass } from '../../../dom';
|
||||
import { CompositeDisposable } from '../../../lifecycle';
|
||||
|
||||
export class Watermark extends CompositeDisposable implements WatermarkPart {
|
||||
private _element: HTMLElement
|
||||
private accessor: IGroupAccessor
|
||||
private _element: HTMLElement;
|
||||
private accessor: IGroupAccessor;
|
||||
|
||||
private _visible: boolean
|
||||
private _group: IGroupview
|
||||
private _visible: boolean;
|
||||
private _group: IGroupview;
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this._element = document.createElement('div')
|
||||
this._element.className = 'watermark'
|
||||
super();
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'watermark';
|
||||
|
||||
const title = document.createElement('div')
|
||||
title.className = 'watermark-title'
|
||||
const title = document.createElement('div');
|
||||
title.className = 'watermark-title';
|
||||
|
||||
const emptySpace = document.createElement('span')
|
||||
emptySpace.style.flexGrow = '1'
|
||||
const emptySpace = document.createElement('span');
|
||||
emptySpace.style.flexGrow = '1';
|
||||
|
||||
const content = document.createElement('div')
|
||||
content.className = 'watermark-content'
|
||||
const content = document.createElement('div');
|
||||
content.className = 'watermark-content';
|
||||
|
||||
this._element.appendChild(title)
|
||||
this._element.appendChild(content)
|
||||
this._element.appendChild(title);
|
||||
this._element.appendChild(content);
|
||||
|
||||
const actions = new ActionContainer()
|
||||
title.appendChild(emptySpace)
|
||||
title.appendChild(actions.element)
|
||||
const actions = new ActionContainer();
|
||||
title.appendChild(emptySpace);
|
||||
title.appendChild(actions.element);
|
||||
|
||||
const closeAnchor = document.createElement('a')
|
||||
closeAnchor.className = 'close-action'
|
||||
const closeAnchor = document.createElement('a');
|
||||
closeAnchor.className = 'close-action';
|
||||
|
||||
actions.add(closeAnchor)
|
||||
actions.add(closeAnchor);
|
||||
|
||||
addDisposableListener(closeAnchor, 'click', (ev) => {
|
||||
ev.preventDefault() //
|
||||
this.accessor.removeGroup(this._group)
|
||||
})
|
||||
ev.preventDefault(); //
|
||||
this.accessor.removeGroup(this._group);
|
||||
});
|
||||
}
|
||||
|
||||
public init(params: WatermarkPartInitParameters) {
|
||||
this.accessor = params.accessor
|
||||
this.accessor = params.accessor;
|
||||
|
||||
this.addDisposables(
|
||||
this.accessor.onDidLayoutChange((event) => {
|
||||
this.render()
|
||||
this.render();
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
this.render()
|
||||
this.render();
|
||||
}
|
||||
|
||||
public setVisible(visible: boolean, group: IGroupview): void {
|
||||
this._visible = visible
|
||||
this._group = group
|
||||
this.render()
|
||||
this._visible = visible;
|
||||
this._group = group;
|
||||
this.render();
|
||||
}
|
||||
|
||||
get element() {
|
||||
return this._element
|
||||
return this._element;
|
||||
}
|
||||
|
||||
private render() {
|
||||
const isOneGroup = this.accessor.size <= 1
|
||||
toggleClass(this.element, 'has-actions', isOneGroup)
|
||||
const isOneGroup = this.accessor.size <= 1;
|
||||
toggleClass(this.element, 'has-actions', isOneGroup);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose()
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { IGridView, IViewDeserializer } from '../gridview/gridview'
|
||||
import { IGroupPanel } from '../groupview/panel/types'
|
||||
import { Layout } from './layout'
|
||||
import { IGridView, IViewDeserializer } from '../gridview/gridview';
|
||||
import { IGroupPanel } from '../groupview/panel/types';
|
||||
import { Layout } from './layout';
|
||||
|
||||
export interface IPanelDeserializer {
|
||||
fromJSON(panelData: { [index: string]: any }): IGroupPanel
|
||||
fromJSON(panelData: { [index: string]: any }): IGroupPanel;
|
||||
}
|
||||
|
||||
export class DefaultDeserializer implements IViewDeserializer {
|
||||
@ -13,22 +13,22 @@ export class DefaultDeserializer implements IViewDeserializer {
|
||||
) {}
|
||||
|
||||
public fromJSON(data: { [key: string]: any }): IGridView {
|
||||
const children = data.views
|
||||
const active = data.activeView
|
||||
const children = data.views;
|
||||
const active = data.activeView;
|
||||
|
||||
const panels: IGroupPanel[] = []
|
||||
const panels: IGroupPanel[] = [];
|
||||
|
||||
for (const child of children) {
|
||||
const panel = this.panelDeserializer.createPanel(child)
|
||||
const panel = this.panelDeserializer.createPanel(child);
|
||||
|
||||
panels.push(panel)
|
||||
panels.push(panel);
|
||||
}
|
||||
|
||||
const group = this.layout.createGroup({
|
||||
panels,
|
||||
activePanel: panels.find((p) => p.id === active),
|
||||
})
|
||||
});
|
||||
|
||||
return group
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
export * from './layout'
|
||||
export * from './componentGridview'
|
||||
export * from './options'
|
||||
export * from './layout';
|
||||
export * from './componentGridview';
|
||||
export * from './options';
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,72 +1,91 @@
|
||||
import { IGroupview } from '../groupview/groupview'
|
||||
import { IGridView } from '../gridview/gridview';
|
||||
import { IGroupview } from '../groupview/groupview';
|
||||
import {
|
||||
PanelContentPart,
|
||||
PanelContentPartConstructor,
|
||||
PanelHeaderPart,
|
||||
PanelHeaderPartConstructor,
|
||||
WatermarkConstructor,
|
||||
} from '../groupview/panel/parts'
|
||||
import { IGroupPanel } from '../groupview/panel/types'
|
||||
import { FrameworkFactory } from '../types'
|
||||
import { Api } from './layout'
|
||||
} from '../groupview/panel/parts';
|
||||
import { IGroupPanel } from '../groupview/panel/types';
|
||||
import { Orientation } from '../splitview/splitview';
|
||||
import { FrameworkFactory } from '../types';
|
||||
import { IComponentGridview } from './componentGridview';
|
||||
import { Api } from './layout';
|
||||
|
||||
export interface GroupPanelFrameworkComponentFactory {
|
||||
content: FrameworkFactory<PanelContentPart>
|
||||
tab: FrameworkFactory<PanelHeaderPart>
|
||||
content: FrameworkFactory<PanelContentPart>;
|
||||
tab: FrameworkFactory<PanelHeaderPart>;
|
||||
}
|
||||
|
||||
export interface TabContextMenuEvent {
|
||||
event: MouseEvent
|
||||
api: Api
|
||||
panel: IGroupPanel
|
||||
event: MouseEvent;
|
||||
api: Api;
|
||||
panel: IGroupPanel;
|
||||
}
|
||||
|
||||
export interface GridComponentOptions {
|
||||
orientation: Orientation;
|
||||
components?: {
|
||||
[componentName: string]: IComponentGridview;
|
||||
};
|
||||
frameworkComponents?: {
|
||||
[componentName: string]: any;
|
||||
};
|
||||
frameworkComponentFactory: any;
|
||||
tabHeight?: number;
|
||||
}
|
||||
|
||||
export interface LayoutOptions {
|
||||
tabComponents?: {
|
||||
[componentName: string]: PanelHeaderPartConstructor
|
||||
}
|
||||
[componentName: string]: PanelHeaderPartConstructor;
|
||||
};
|
||||
components?: {
|
||||
[componentName: string]: PanelContentPartConstructor
|
||||
}
|
||||
[componentName: string]: PanelContentPartConstructor;
|
||||
};
|
||||
frameworkTabComponents?: {
|
||||
[componentName: string]: any
|
||||
}
|
||||
[componentName: string]: any;
|
||||
};
|
||||
frameworkComponents?: {
|
||||
[componentName: string]: any
|
||||
}
|
||||
watermarkComponent?: WatermarkConstructor
|
||||
watermarkFrameworkComponent?: any
|
||||
frameworkComponentFactory: GroupPanelFrameworkComponentFactory
|
||||
tabHeight?: number
|
||||
debug?: boolean
|
||||
enableExternalDragEvents?: boolean
|
||||
[componentName: string]: any;
|
||||
};
|
||||
watermarkComponent?: WatermarkConstructor;
|
||||
watermarkFrameworkComponent?: any;
|
||||
frameworkComponentFactory: GroupPanelFrameworkComponentFactory;
|
||||
tabHeight?: number;
|
||||
debug?: boolean;
|
||||
enableExternalDragEvents?: boolean;
|
||||
}
|
||||
|
||||
export interface PanelOptions {
|
||||
componentName: string
|
||||
tabComponentName?: string
|
||||
params?: { [key: string]: any }
|
||||
id: string
|
||||
title?: string
|
||||
suppressClosable?: boolean
|
||||
componentName: string;
|
||||
tabComponentName?: string;
|
||||
params?: { [key: string]: any };
|
||||
id: string;
|
||||
title?: string;
|
||||
suppressClosable?: boolean;
|
||||
}
|
||||
|
||||
export interface AddPanelOptions
|
||||
extends Omit<PanelOptions, 'componentName' | 'tabComponentName'> {
|
||||
componentName: string | PanelContentPartConstructor
|
||||
tabComponentName?: string | PanelHeaderPartConstructor
|
||||
componentName: string | PanelContentPartConstructor;
|
||||
tabComponentName?: string | PanelHeaderPartConstructor;
|
||||
position?: {
|
||||
direction?: 'left' | 'right' | 'above' | 'below' | 'within'
|
||||
referencePanel: string
|
||||
}
|
||||
direction?: 'left' | 'right' | 'above' | 'below' | 'within';
|
||||
referencePanel: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AddGroupOptions {
|
||||
direction?: 'left' | 'right' | 'above' | 'below'
|
||||
referencePanel: string
|
||||
direction?: 'left' | 'right' | 'above' | 'below';
|
||||
referencePanel: string;
|
||||
}
|
||||
|
||||
export interface MovementOptions {
|
||||
group?: IGroupview
|
||||
includePanel?: boolean
|
||||
export interface MovementOptions2 {
|
||||
group?: IGridView;
|
||||
}
|
||||
|
||||
export interface MovementOptions extends MovementOptions2 {
|
||||
includePanel?: boolean;
|
||||
group?: IGroupview;
|
||||
}
|
||||
|
@ -1,56 +1,56 @@
|
||||
export interface IDisposable {
|
||||
dispose: () => void
|
||||
dispose: () => void;
|
||||
}
|
||||
|
||||
export interface IValueDisposable<T> {
|
||||
value: T
|
||||
disposable: IDisposable
|
||||
value: T;
|
||||
disposable: IDisposable;
|
||||
}
|
||||
|
||||
export interface ISerializable {
|
||||
toJSON(): object
|
||||
fromJSON(data: object): void
|
||||
toJSON(): object;
|
||||
fromJSON(data: object): void;
|
||||
}
|
||||
|
||||
export namespace Disposable {
|
||||
export const NONE: IDisposable = { dispose: () => {} }
|
||||
export const NONE: IDisposable = { dispose: () => {} };
|
||||
}
|
||||
|
||||
export class CompositeDisposable {
|
||||
private disposables: IDisposable[]
|
||||
private disposables: IDisposable[];
|
||||
|
||||
public static from(...args: IDisposable[]) {
|
||||
return new CompositeDisposable(...args)
|
||||
return new CompositeDisposable(...args);
|
||||
}
|
||||
|
||||
constructor(...args: IDisposable[]) {
|
||||
this.disposables = args
|
||||
this.disposables = args;
|
||||
}
|
||||
|
||||
public addDisposables(...args: IDisposable[]) {
|
||||
args?.forEach((arg) => this.disposables.push(arg))
|
||||
args?.forEach((arg) => this.disposables.push(arg));
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.disposables.forEach((arg) => arg.dispose())
|
||||
this.disposables.forEach((arg) => arg.dispose());
|
||||
}
|
||||
}
|
||||
|
||||
export class MutableDisposable implements IDisposable {
|
||||
private _disposable: IDisposable
|
||||
private _disposable: IDisposable;
|
||||
|
||||
constructor() {}
|
||||
|
||||
set value(disposable: IDisposable) {
|
||||
if (this._disposable) {
|
||||
this._disposable.dispose()
|
||||
this._disposable.dispose();
|
||||
}
|
||||
this._disposable = disposable
|
||||
this._disposable = disposable;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
if (this._disposable) {
|
||||
this._disposable.dispose()
|
||||
this._disposable.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
export const clamp = (value: number, min: number, max: number) => {
|
||||
return Math.min(max, Math.max(value, min))
|
||||
}
|
||||
return Math.min(max, Math.max(value, min));
|
||||
};
|
||||
|
||||
export const sequentialNumberGenerator = () => {
|
||||
let value = 1
|
||||
return { next: () => (value++).toString() }
|
||||
}
|
||||
let value = 1;
|
||||
return { next: () => (value++).toString() };
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { PanelDimensionChangeEvent } from './types'
|
||||
import { Emitter, Event } from '../events'
|
||||
import { CompositeDisposable, IDisposable } from '../lifecycle'
|
||||
import { FunctionOrValue } from '../types'
|
||||
import { PanelDimensionChangeEvent } from './types';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { CompositeDisposable, IDisposable } from '../lifecycle';
|
||||
import { FunctionOrValue } from '../types';
|
||||
|
||||
// we've tried to do a bit better than the 'any' type.
|
||||
// anything that is serializable JSON should be valid here
|
||||
@ -13,74 +13,74 @@ type StateObject =
|
||||
| null
|
||||
| object
|
||||
| StateObject[]
|
||||
| { [key: string]: StateObject }
|
||||
| { [key: string]: StateObject };
|
||||
|
||||
interface State {
|
||||
[key: string]: StateObject
|
||||
[key: string]: StateObject;
|
||||
}
|
||||
|
||||
interface ChangeFocusEvent {
|
||||
isFocused: boolean
|
||||
isFocused: boolean;
|
||||
}
|
||||
|
||||
interface PanelConstraintChangeEvent {
|
||||
minimumSize?: number | (() => number)
|
||||
maximumSize?: number | (() => number)
|
||||
minimumSize?: number | (() => number);
|
||||
maximumSize?: number | (() => number);
|
||||
}
|
||||
|
||||
export interface IBaseViewApi extends IDisposable {
|
||||
// events
|
||||
onDidDimensionsChange: Event<PanelDimensionChangeEvent>
|
||||
onDidStateChange: Event<void>
|
||||
onDidFocusChange: Event<ChangeFocusEvent>
|
||||
onDidDimensionsChange: Event<PanelDimensionChangeEvent>;
|
||||
onDidStateChange: Event<void>;
|
||||
onDidFocusChange: Event<ChangeFocusEvent>;
|
||||
// state
|
||||
setState(key: string, value: StateObject): void
|
||||
setState(state: State): void
|
||||
getState: () => State
|
||||
getStateKey: <T extends StateObject>(key: string) => T
|
||||
setState(key: string, value: StateObject): void;
|
||||
setState(state: State): void;
|
||||
getState: () => State;
|
||||
getStateKey: <T extends StateObject>(key: string) => T;
|
||||
//
|
||||
readonly isFocused: boolean
|
||||
readonly isFocused: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A core api implementation that should be used across all panel-like objects
|
||||
*/
|
||||
export class BaseViewApi extends CompositeDisposable implements IBaseViewApi {
|
||||
private _state: State = {}
|
||||
private _isFocused: boolean
|
||||
private _state: State = {};
|
||||
private _isFocused: boolean;
|
||||
|
||||
readonly _onDidStateChange = new Emitter<void>()
|
||||
readonly onDidStateChange: Event<void> = this._onDidStateChange.event
|
||||
readonly _onDidStateChange = new Emitter<void>();
|
||||
readonly onDidStateChange: Event<void> = this._onDidStateChange.event;
|
||||
//
|
||||
readonly _onDidPanelDimensionChange = new Emitter<
|
||||
PanelDimensionChangeEvent
|
||||
>({
|
||||
emitLastValue: true,
|
||||
})
|
||||
readonly onDidDimensionsChange = this._onDidPanelDimensionChange.event
|
||||
});
|
||||
readonly onDidDimensionsChange = this._onDidPanelDimensionChange.event;
|
||||
//
|
||||
readonly _onDidChangeFocus = new Emitter<ChangeFocusEvent>({
|
||||
emitLastValue: true,
|
||||
})
|
||||
});
|
||||
readonly onDidFocusChange: Event<ChangeFocusEvent> = this._onDidChangeFocus
|
||||
.event
|
||||
.event;
|
||||
//
|
||||
|
||||
get isFocused() {
|
||||
return this._isFocused
|
||||
return this._isFocused;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
super();
|
||||
|
||||
this.addDisposables(
|
||||
this._onDidStateChange,
|
||||
this._onDidChangeFocus,
|
||||
this._onDidPanelDimensionChange,
|
||||
this.onDidFocusChange((event) => {
|
||||
this._isFocused = event.isFocused
|
||||
this._isFocused = event.isFocused;
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public setState(
|
||||
@ -88,76 +88,76 @@ export class BaseViewApi extends CompositeDisposable implements IBaseViewApi {
|
||||
value?: StateObject
|
||||
) {
|
||||
if (typeof key === 'object') {
|
||||
this._state = key
|
||||
this._state = key;
|
||||
} else {
|
||||
this._state[key] = value
|
||||
this._state[key] = value;
|
||||
}
|
||||
this._onDidStateChange.fire(undefined)
|
||||
this._onDidStateChange.fire(undefined);
|
||||
}
|
||||
|
||||
public getState(): State {
|
||||
return this._state
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public getStateKey<T extends StateObject>(key: string): T {
|
||||
return this._state[key] as T
|
||||
return this._state[key] as T;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose()
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
interface PanelConstraintChangeEvent {
|
||||
minimumSize?: FunctionOrValue<number>
|
||||
maximumSize?: FunctionOrValue<number>
|
||||
minimumSize?: FunctionOrValue<number>;
|
||||
maximumSize?: FunctionOrValue<number>;
|
||||
}
|
||||
|
||||
export interface IPanelApi extends IBaseViewApi {
|
||||
onDidConstraintsChange: Event<PanelConstraintChangeEvent>
|
||||
setConstraints(value: PanelConstraintChangeEvent): void
|
||||
onDidConstraintsChange: Event<PanelConstraintChangeEvent>;
|
||||
setConstraints(value: PanelConstraintChangeEvent): void;
|
||||
}
|
||||
|
||||
export class PanelApi extends BaseViewApi implements IBaseViewApi {
|
||||
readonly _onDidConstraintsChange = new Emitter<PanelConstraintChangeEvent>({
|
||||
emitLastValue: true,
|
||||
})
|
||||
});
|
||||
readonly onDidConstraintsChange: Event<PanelConstraintChangeEvent> = this
|
||||
._onDidConstraintsChange.event
|
||||
._onDidConstraintsChange.event;
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
super();
|
||||
}
|
||||
|
||||
public setConstraints(value: PanelConstraintChangeEvent) {
|
||||
this._onDidConstraintsChange.fire(value)
|
||||
this._onDidConstraintsChange.fire(value);
|
||||
}
|
||||
}
|
||||
|
||||
interface GridConstraintChangeEvent {
|
||||
minimumWidth?: FunctionOrValue<number>
|
||||
minimumHeight?: FunctionOrValue<number>
|
||||
maximumWidth?: FunctionOrValue<number>
|
||||
maximumHeight?: FunctionOrValue<number>
|
||||
minimumWidth?: FunctionOrValue<number>;
|
||||
minimumHeight?: FunctionOrValue<number>;
|
||||
maximumWidth?: FunctionOrValue<number>;
|
||||
maximumHeight?: FunctionOrValue<number>;
|
||||
}
|
||||
|
||||
export interface IGridApi extends IBaseViewApi {
|
||||
onDidConstraintsChange: Event<GridConstraintChangeEvent>
|
||||
setConstraints(value: GridConstraintChangeEvent): void
|
||||
onDidConstraintsChange: Event<GridConstraintChangeEvent>;
|
||||
setConstraints(value: GridConstraintChangeEvent): void;
|
||||
}
|
||||
|
||||
export class GridApi extends BaseViewApi implements IBaseViewApi {
|
||||
readonly _onDidConstraintsChange = new Emitter<GridConstraintChangeEvent>({
|
||||
emitLastValue: true,
|
||||
})
|
||||
});
|
||||
readonly onDidConstraintsChange: Event<GridConstraintChangeEvent> = this
|
||||
._onDidConstraintsChange.event
|
||||
._onDidConstraintsChange.event;
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
super();
|
||||
}
|
||||
|
||||
public setConstraints(value: GridConstraintChangeEvent) {
|
||||
this._onDidConstraintsChange.fire(value)
|
||||
this._onDidConstraintsChange.fire(value);
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
export interface InitParameters {
|
||||
params: { [index: string]: any }
|
||||
state?: { [index: string]: any }
|
||||
params: { [index: string]: any };
|
||||
state?: { [index: string]: any };
|
||||
}
|
||||
|
||||
export interface PanelUpdateEvent {
|
||||
params: { [index: string]: any }
|
||||
params: { [index: string]: any };
|
||||
}
|
||||
|
||||
export interface IPanel {
|
||||
init?(params: InitParameters): void
|
||||
layout?(width: number, height: number): void
|
||||
update?(event: PanelUpdateEvent): void
|
||||
init?(params: InitParameters): void;
|
||||
layout?(width: number, height: number): void;
|
||||
update?(event: PanelUpdateEvent): void;
|
||||
}
|
||||
|
||||
export interface PanelDimensionChangeEvent {
|
||||
width: number
|
||||
height: number
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
@ -1,151 +1,151 @@
|
||||
import { SplitView, IView, Orientation } from '../splitview/splitview'
|
||||
import { IDisposable } from '../lifecycle'
|
||||
import { Emitter } from '../events'
|
||||
import { addClasses, removeClasses } from '../dom'
|
||||
import { SplitView, IView, Orientation } from '../splitview/splitview';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { Emitter } from '../events';
|
||||
import { addClasses, removeClasses } from '../dom';
|
||||
|
||||
export interface IPaneOptions {
|
||||
minimumBodySize?: number
|
||||
maximumBodySize?: number
|
||||
orientation?: Orientation
|
||||
isExpanded?: boolean
|
||||
minimumBodySize?: number;
|
||||
maximumBodySize?: number;
|
||||
orientation?: Orientation;
|
||||
isExpanded?: boolean;
|
||||
}
|
||||
|
||||
export abstract class Pane implements IView {
|
||||
public element: HTMLElement
|
||||
private header: HTMLElement
|
||||
private body: HTMLElement
|
||||
public element: HTMLElement;
|
||||
private header: HTMLElement;
|
||||
private body: HTMLElement;
|
||||
|
||||
private _onDidChangeExpansionState: Emitter<boolean> = new Emitter<
|
||||
boolean
|
||||
>()
|
||||
public onDidChangeExpansionState = this._onDidChangeExpansionState.event
|
||||
>();
|
||||
public onDidChangeExpansionState = this._onDidChangeExpansionState.event;
|
||||
|
||||
private _onDidChange: Emitter<number | undefined> = new Emitter<
|
||||
number | undefined
|
||||
>()
|
||||
public onDidChange = this._onDidChange.event
|
||||
>();
|
||||
public onDidChange = this._onDidChange.event;
|
||||
|
||||
private _minimumBodySize: number
|
||||
private _maximumBodySize: number
|
||||
private _minimumBodySize: number;
|
||||
private _maximumBodySize: number;
|
||||
|
||||
private _minimumSize: number
|
||||
private _maximumSize: number
|
||||
private _isExpanded: boolean
|
||||
private _orientation: Orientation
|
||||
private _orthogonalSize: number
|
||||
private animationTimer: NodeJS.Timeout
|
||||
private expandedSize: number
|
||||
private headerSize = 22
|
||||
private _minimumSize: number;
|
||||
private _maximumSize: number;
|
||||
private _isExpanded: boolean;
|
||||
private _orientation: Orientation;
|
||||
private _orthogonalSize: number;
|
||||
private animationTimer: NodeJS.Timeout;
|
||||
private expandedSize: number;
|
||||
private headerSize = 22;
|
||||
|
||||
constructor(options: IPaneOptions) {
|
||||
this.element = document.createElement('div')
|
||||
this.element.className = 'pane'
|
||||
this.element = document.createElement('div');
|
||||
this.element.className = 'pane';
|
||||
|
||||
this._minimumBodySize =
|
||||
typeof options.minimumBodySize === 'number'
|
||||
? options.minimumBodySize
|
||||
: 120
|
||||
: 120;
|
||||
this._maximumBodySize =
|
||||
typeof options.maximumBodySize === 'number'
|
||||
? options.maximumBodySize
|
||||
: Number.POSITIVE_INFINITY
|
||||
: Number.POSITIVE_INFINITY;
|
||||
|
||||
this._isExpanded = options.isExpanded
|
||||
this.orientation = options.orientation
|
||||
this._isExpanded = options.isExpanded;
|
||||
this.orientation = options.orientation;
|
||||
}
|
||||
|
||||
public get minimumSize(): number {
|
||||
const headerSize = this.headerSize
|
||||
const expanded = this.isExpanded()
|
||||
const headerSize = this.headerSize;
|
||||
const expanded = this.isExpanded();
|
||||
const minimumBodySize = expanded
|
||||
? this._minimumBodySize
|
||||
: this._orientation === Orientation.HORIZONTAL
|
||||
? 50
|
||||
: 0
|
||||
: 0;
|
||||
|
||||
return headerSize + minimumBodySize
|
||||
return headerSize + minimumBodySize;
|
||||
}
|
||||
|
||||
public get maximumSize(): number {
|
||||
const headerSize = this.headerSize
|
||||
const expanded = this.isExpanded()
|
||||
const headerSize = this.headerSize;
|
||||
const expanded = this.isExpanded();
|
||||
const maximumBodySize = expanded
|
||||
? this._maximumBodySize
|
||||
: this._orientation === Orientation.HORIZONTAL
|
||||
? 50
|
||||
: 0
|
||||
: 0;
|
||||
|
||||
return headerSize + maximumBodySize
|
||||
return headerSize + maximumBodySize;
|
||||
}
|
||||
|
||||
public isExpanded() {
|
||||
return this._isExpanded
|
||||
return this._isExpanded;
|
||||
}
|
||||
|
||||
public get orientation() {
|
||||
return this._orientation
|
||||
return this._orientation;
|
||||
}
|
||||
|
||||
public get orthogonalSize() {
|
||||
return this._orthogonalSize
|
||||
return this._orthogonalSize;
|
||||
}
|
||||
|
||||
public set minimumSize(size: number) {
|
||||
this._minimumSize = size
|
||||
this._onDidChange.fire(undefined)
|
||||
this._minimumSize = size;
|
||||
this._onDidChange.fire(undefined);
|
||||
}
|
||||
|
||||
public set maximumSize(size: number) {
|
||||
this._maximumSize = size
|
||||
this._onDidChange.fire(undefined)
|
||||
this._maximumSize = size;
|
||||
this._onDidChange.fire(undefined);
|
||||
}
|
||||
|
||||
public setExpanded(expanded: boolean) {
|
||||
this._isExpanded = expanded
|
||||
this._isExpanded = expanded;
|
||||
|
||||
if (expanded) {
|
||||
if (this.animationTimer) {
|
||||
clearTimeout(this.animationTimer)
|
||||
clearTimeout(this.animationTimer);
|
||||
}
|
||||
this.element.appendChild(this.body)
|
||||
this.element.appendChild(this.body);
|
||||
} else {
|
||||
this.animationTimer = setTimeout(() => {
|
||||
this.body.remove()
|
||||
}, 200)
|
||||
this.body.remove();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
this._onDidChangeExpansionState.fire(expanded)
|
||||
this._onDidChange.fire(expanded ? this.expandedSize : undefined)
|
||||
this._onDidChangeExpansionState.fire(expanded);
|
||||
this._onDidChange.fire(expanded ? this.expandedSize : undefined);
|
||||
}
|
||||
|
||||
public set orientation(orientation: Orientation) {
|
||||
this._orientation = orientation
|
||||
this._orientation = orientation;
|
||||
}
|
||||
|
||||
public set orthogonalSize(size: number) {
|
||||
this._orthogonalSize = size
|
||||
this._orthogonalSize = size;
|
||||
}
|
||||
|
||||
public layout(size: number, orthogonalSize: number) {
|
||||
if (this.isExpanded()) {
|
||||
this.expandedSize = size
|
||||
this.expandedSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
this.header = document.createElement('div')
|
||||
this.header.className = 'pane-header'
|
||||
this.header.style.height = `${this.headerSize}px`
|
||||
this.header.style.lineHeight = `${this.headerSize}px`
|
||||
this.element.appendChild(this.header)
|
||||
this.renderHeader(this.header)
|
||||
this.header = document.createElement('div');
|
||||
this.header.className = 'pane-header';
|
||||
this.header.style.height = `${this.headerSize}px`;
|
||||
this.header.style.lineHeight = `${this.headerSize}px`;
|
||||
this.element.appendChild(this.header);
|
||||
this.renderHeader(this.header);
|
||||
|
||||
// this.updateHeader();
|
||||
|
||||
this.body = document.createElement('div')
|
||||
this.body.className = 'pane-body'
|
||||
this.element.appendChild(this.body)
|
||||
this.renderBody(this.body)
|
||||
this.body = document.createElement('div');
|
||||
this.body.className = 'pane-body';
|
||||
this.element.appendChild(this.body);
|
||||
this.renderBody(this.body);
|
||||
|
||||
// if (!this.isExpanded()) {
|
||||
// this.body.remove();
|
||||
@ -174,103 +174,103 @@ export abstract class Pane implements IView {
|
||||
// this._dropBackground = this.styles.dropBackground;
|
||||
// }
|
||||
|
||||
protected abstract renderHeader(container: HTMLElement): void
|
||||
protected abstract renderBody(container: HTMLElement): void
|
||||
protected abstract renderHeader(container: HTMLElement): void;
|
||||
protected abstract renderBody(container: HTMLElement): void;
|
||||
}
|
||||
|
||||
interface PaneItem {
|
||||
pane: Pane
|
||||
disposable: IDisposable
|
||||
pane: Pane;
|
||||
disposable: IDisposable;
|
||||
}
|
||||
|
||||
export class PaneView implements IDisposable {
|
||||
private element: HTMLElement
|
||||
private splitview: SplitView
|
||||
private paneItems: PaneItem[] = []
|
||||
private _orientation: Orientation
|
||||
private animationTimer: NodeJS.Timeout
|
||||
private orthogonalSize: number
|
||||
private size: number
|
||||
private element: HTMLElement;
|
||||
private splitview: SplitView;
|
||||
private paneItems: PaneItem[] = [];
|
||||
private _orientation: Orientation;
|
||||
private animationTimer: NodeJS.Timeout;
|
||||
private orthogonalSize: number;
|
||||
private size: number;
|
||||
|
||||
constructor(container: HTMLElement, options: { orientation: Orientation }) {
|
||||
this._orientation = options.orientation ?? Orientation.VERTICAL
|
||||
this._orientation = options.orientation ?? Orientation.VERTICAL;
|
||||
|
||||
this.element = document.createElement('div')
|
||||
this.element.className = 'pane-container'
|
||||
this.element = document.createElement('div');
|
||||
this.element.className = 'pane-container';
|
||||
|
||||
this.setupAnimation = this.setupAnimation.bind(this)
|
||||
this.setupAnimation = this.setupAnimation.bind(this);
|
||||
|
||||
container.appendChild(this.element)
|
||||
container.appendChild(this.element);
|
||||
this.splitview = new SplitView(this.element, {
|
||||
orientation: this._orientation,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
public setOrientation(orientation: Orientation) {
|
||||
this._orientation = orientation
|
||||
this._orientation = orientation;
|
||||
}
|
||||
|
||||
public addPane(pane: Pane, size?: number, index = this.splitview.length) {
|
||||
const disposable = pane.onDidChangeExpansionState(this.setupAnimation)
|
||||
const disposable = pane.onDidChangeExpansionState(this.setupAnimation);
|
||||
|
||||
const paneItem: PaneItem = {
|
||||
pane,
|
||||
disposable: {
|
||||
dispose: () => {
|
||||
disposable.dispose()
|
||||
disposable.dispose();
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
this.paneItems.splice(index, 0, paneItem)
|
||||
pane.orientation = this._orientation
|
||||
pane.orthogonalSize = this.orthogonalSize
|
||||
this.splitview.addView(pane, size, index)
|
||||
this.paneItems.splice(index, 0, paneItem);
|
||||
pane.orientation = this._orientation;
|
||||
pane.orthogonalSize = this.orthogonalSize;
|
||||
this.splitview.addView(pane, size, index);
|
||||
}
|
||||
|
||||
public getPanes() {
|
||||
return this.splitview.getViews() as Pane[]
|
||||
return this.splitview.getViews() as Pane[];
|
||||
}
|
||||
|
||||
public removePane(index: number) {
|
||||
this.splitview.removeView(index)
|
||||
const paneItem = this.paneItems.splice(index, 1)[0]
|
||||
paneItem.disposable.dispose()
|
||||
return paneItem
|
||||
this.splitview.removeView(index);
|
||||
const paneItem = this.paneItems.splice(index, 1)[0];
|
||||
paneItem.disposable.dispose();
|
||||
return paneItem;
|
||||
}
|
||||
|
||||
public moveView(from: number, to: number) {
|
||||
const view = this.removePane(from)
|
||||
this.addPane(view.pane, to)
|
||||
const view = this.removePane(from);
|
||||
this.addPane(view.pane, to);
|
||||
}
|
||||
|
||||
public layout(size: number, orthogonalSize: number): void {
|
||||
this.orthogonalSize = orthogonalSize
|
||||
this.size = size
|
||||
this.orthogonalSize = orthogonalSize;
|
||||
this.size = size;
|
||||
|
||||
for (const paneItem of this.paneItems) {
|
||||
paneItem.pane.orthogonalSize = this.orthogonalSize
|
||||
paneItem.pane.orthogonalSize = this.orthogonalSize;
|
||||
}
|
||||
|
||||
this.splitview.layout(this.size, this.orthogonalSize)
|
||||
this.splitview.layout(this.size, this.orthogonalSize);
|
||||
}
|
||||
|
||||
private setupAnimation() {
|
||||
if (this.animationTimer) {
|
||||
clearTimeout(this.animationTimer)
|
||||
clearTimeout(this.animationTimer);
|
||||
}
|
||||
|
||||
addClasses(this.element, 'animated')
|
||||
addClasses(this.element, 'animated');
|
||||
|
||||
this.animationTimer = setTimeout(() => {
|
||||
this.animationTimer = undefined
|
||||
removeClasses(this.element, 'animated')
|
||||
}, 200)
|
||||
this.animationTimer = undefined;
|
||||
removeClasses(this.element, 'animated');
|
||||
}, 200);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.paneItems.forEach((paneItem) => {
|
||||
paneItem.disposable.dispose()
|
||||
})
|
||||
paneItem.disposable.dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,48 +1,48 @@
|
||||
import { IGroupPanel } from '../groupview/panel/types'
|
||||
import { Layout } from '../layout/layout'
|
||||
import { DefaultPanel } from '../groupview/panel/panel'
|
||||
import { PanelContentPart, PanelHeaderPart } from '../groupview/panel/parts'
|
||||
import { IPanelDeserializer } from '../layout/deserializer'
|
||||
import { IGroupPanel } from '../groupview/panel/types';
|
||||
import { Layout } from '../layout/layout';
|
||||
import { DefaultPanel } from '../groupview/panel/panel';
|
||||
import { PanelContentPart, PanelHeaderPart } from '../groupview/panel/parts';
|
||||
import { IPanelDeserializer } from '../layout/deserializer';
|
||||
import {
|
||||
createContentComponent,
|
||||
createTabComponent,
|
||||
} from '../layout/componentFactory'
|
||||
} from '../layout/componentFactory';
|
||||
|
||||
export class ReactPanelDeserialzier implements IPanelDeserializer {
|
||||
constructor(private readonly layout: Layout) {}
|
||||
|
||||
public fromJSON(panelData: { [index: string]: any }): IGroupPanel {
|
||||
const panelId = panelData.id
|
||||
const content = panelData.content
|
||||
const tab = panelData.tab
|
||||
const props = panelData.props
|
||||
const title = panelData.title
|
||||
const state = panelData.state
|
||||
const suppressClosable = panelData.suppressClosable
|
||||
const panelId = panelData.id;
|
||||
const content = panelData.content;
|
||||
const tab = panelData.tab;
|
||||
const props = panelData.props;
|
||||
const title = panelData.title;
|
||||
const state = panelData.state;
|
||||
const suppressClosable = panelData.suppressClosable;
|
||||
|
||||
const contentPart = createContentComponent(
|
||||
content.id,
|
||||
this.layout.options.components,
|
||||
this.layout.options.frameworkComponents,
|
||||
this.layout.options.frameworkComponentFactory.content
|
||||
) as PanelContentPart
|
||||
) as PanelContentPart;
|
||||
|
||||
const headerPart = createTabComponent(
|
||||
tab.id,
|
||||
this.layout.options.tabComponents,
|
||||
this.layout.options.frameworkComponentFactory,
|
||||
this.layout.options.frameworkComponentFactory.tab
|
||||
) as PanelHeaderPart
|
||||
) as PanelHeaderPart;
|
||||
|
||||
const panel = new DefaultPanel(panelId, headerPart, contentPart)
|
||||
const panel = new DefaultPanel(panelId, headerPart, contentPart);
|
||||
|
||||
panel.init({
|
||||
title,
|
||||
suppressClosable,
|
||||
params: props || {},
|
||||
state: state || {},
|
||||
})
|
||||
});
|
||||
|
||||
return panel
|
||||
return panel;
|
||||
}
|
||||
}
|
||||
|
@ -1,43 +1,43 @@
|
||||
import * as React from 'react'
|
||||
import * as React from 'react';
|
||||
import {
|
||||
ComponentGridview,
|
||||
IComponentGridviewLayout,
|
||||
} from '../layout/componentGridview'
|
||||
import { IGridApi } from '../panel/api'
|
||||
import { Orientation } from '../splitview/splitview'
|
||||
import { ReactComponentGridView } from './reactComponentGridView'
|
||||
} from '../layout/componentGridview';
|
||||
import { IGridApi } from '../panel/api';
|
||||
import { Orientation } from '../splitview/splitview';
|
||||
import { ReactComponentGridView } from './reactComponentGridView';
|
||||
|
||||
export interface GridviewReadyEvent {
|
||||
api: IComponentGridviewLayout
|
||||
api: IComponentGridviewLayout;
|
||||
}
|
||||
|
||||
export interface IGridviewPanelProps {
|
||||
api: IGridApi
|
||||
api: IGridApi;
|
||||
}
|
||||
|
||||
export interface IGridviewComponentProps {
|
||||
orientation: Orientation
|
||||
onReady?: (event: GridviewReadyEvent) => void
|
||||
orientation: Orientation;
|
||||
onReady?: (event: GridviewReadyEvent) => void;
|
||||
components: {
|
||||
[index: string]: React.FunctionComponent<IGridviewPanelProps>
|
||||
}
|
||||
[index: string]: React.FunctionComponent<IGridviewPanelProps>;
|
||||
};
|
||||
}
|
||||
|
||||
export const GridviewComponent = (props: IGridviewComponentProps) => {
|
||||
const domReference = React.useRef<HTMLDivElement>()
|
||||
const gridview = React.useRef<IComponentGridviewLayout>()
|
||||
const [portals, setPortals] = React.useState<React.ReactPortal[]>([])
|
||||
const domReference = React.useRef<HTMLDivElement>();
|
||||
const gridview = React.useRef<IComponentGridviewLayout>();
|
||||
const [portals, setPortals] = React.useState<React.ReactPortal[]>([]);
|
||||
|
||||
const addPortal = React.useCallback((p: React.ReactPortal) => {
|
||||
setPortals((portals) => [...portals, p])
|
||||
setPortals((portals) => [...portals, p]);
|
||||
return {
|
||||
dispose: () => {
|
||||
setPortals((portals) =>
|
||||
portals.filter((portal) => portal !== p)
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
}, [])
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
gridview.current = new ComponentGridview(domReference.current, {
|
||||
@ -47,15 +47,15 @@ export const GridviewComponent = (props: IGridviewComponentProps) => {
|
||||
createComponent: (id: string, component: any) => {
|
||||
return new ReactComponentGridView(id, id, component, {
|
||||
addPortal,
|
||||
})
|
||||
});
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
if (props.onReady) {
|
||||
props.onReady({ api: gridview.current })
|
||||
props.onReady({ api: gridview.current });
|
||||
}
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -67,5 +67,5 @@ export const GridviewComponent = (props: IGridviewComponentProps) => {
|
||||
>
|
||||
{portals}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,66 +1,66 @@
|
||||
import * as React from 'react'
|
||||
import { IDisposable } from '../lifecycle'
|
||||
import { Layout, Api } from '../layout/layout'
|
||||
import { ReactPanelContentPart } from './reactContentPart'
|
||||
import { ReactPanelHeaderPart } from './reactHeaderPart'
|
||||
import { IPanelProps } from './react'
|
||||
import { ReactPanelDeserialzier } from './deserializer'
|
||||
import * as React from 'react';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { Layout, Api } from '../layout/layout';
|
||||
import { ReactPanelContentPart } from './reactContentPart';
|
||||
import { ReactPanelHeaderPart } from './reactHeaderPart';
|
||||
import { IPanelProps } from './react';
|
||||
import { ReactPanelDeserialzier } from './deserializer';
|
||||
import {
|
||||
GroupPanelFrameworkComponentFactory,
|
||||
TabContextMenuEvent,
|
||||
} from '../layout/options'
|
||||
} from '../layout/options';
|
||||
|
||||
export interface OnReadyEvent {
|
||||
api: Api
|
||||
api: Api;
|
||||
}
|
||||
|
||||
export interface ReactLayout {
|
||||
addPortal: (portal: React.ReactPortal) => IDisposable
|
||||
addPortal: (portal: React.ReactPortal) => IDisposable;
|
||||
}
|
||||
|
||||
export interface IReactGridProps {
|
||||
components?: {
|
||||
[componentName: string]: React.FunctionComponent<IPanelProps>
|
||||
}
|
||||
[componentName: string]: React.FunctionComponent<IPanelProps>;
|
||||
};
|
||||
tabComponents?: {
|
||||
[componentName: string]: React.FunctionComponent<IPanelProps>
|
||||
}
|
||||
watermarkComponent?: React.FunctionComponent
|
||||
onReady?: (event: OnReadyEvent) => void
|
||||
autoSizeToFitContainer?: boolean
|
||||
serializedLayout?: {}
|
||||
[componentName: string]: React.FunctionComponent<IPanelProps>;
|
||||
};
|
||||
watermarkComponent?: React.FunctionComponent;
|
||||
onReady?: (event: OnReadyEvent) => void;
|
||||
autoSizeToFitContainer?: boolean;
|
||||
serializedLayout?: {};
|
||||
deserializer?: {
|
||||
fromJSON: (
|
||||
data: any
|
||||
) => {
|
||||
component: React.FunctionComponent<IPanelProps>
|
||||
tabComponent?: React.FunctionComponent<IPanelProps>
|
||||
props?: { [key: string]: any }
|
||||
}
|
||||
}
|
||||
debug?: boolean
|
||||
tabHeight?: number
|
||||
enableExternalDragEvents?: boolean
|
||||
onTabContextMenu?: (event: TabContextMenuEvent) => void
|
||||
component: React.FunctionComponent<IPanelProps>;
|
||||
tabComponent?: React.FunctionComponent<IPanelProps>;
|
||||
props?: { [key: string]: any };
|
||||
};
|
||||
};
|
||||
debug?: boolean;
|
||||
tabHeight?: number;
|
||||
enableExternalDragEvents?: boolean;
|
||||
onTabContextMenu?: (event: TabContextMenuEvent) => void;
|
||||
}
|
||||
|
||||
export const ReactGrid = (props: IReactGridProps) => {
|
||||
const domReference = React.useRef<HTMLDivElement>()
|
||||
const layoutReference = React.useRef<Layout>()
|
||||
const domReference = React.useRef<HTMLDivElement>();
|
||||
const layoutReference = React.useRef<Layout>();
|
||||
|
||||
const [portals, setPortals] = React.useState<React.ReactPortal[]>([])
|
||||
const [portals, setPortals] = React.useState<React.ReactPortal[]>([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const addPortal = (p: React.ReactPortal) => {
|
||||
setPortals((portals) => [...portals, p])
|
||||
setPortals((portals) => [...portals, p]);
|
||||
return {
|
||||
dispose: () => {
|
||||
setPortals((portals) =>
|
||||
portals.filter((portal) => portal !== p)
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const factory: GroupPanelFrameworkComponentFactory = {
|
||||
content: {
|
||||
@ -70,7 +70,7 @@ export const ReactGrid = (props: IReactGridProps) => {
|
||||
) => {
|
||||
return new ReactPanelContentPart(id, component, {
|
||||
addPortal,
|
||||
})
|
||||
});
|
||||
},
|
||||
},
|
||||
tab: {
|
||||
@ -80,53 +80,57 @@ export const ReactGrid = (props: IReactGridProps) => {
|
||||
) => {
|
||||
return new ReactPanelHeaderPart(id, component, {
|
||||
addPortal,
|
||||
})
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const layout = new Layout({
|
||||
const element = document.createElement('div');
|
||||
|
||||
const layout = new Layout(element, {
|
||||
frameworkComponentFactory: factory,
|
||||
frameworkComponents: props.components,
|
||||
frameworkTabComponents: props.tabComponents,
|
||||
tabHeight: props.tabHeight,
|
||||
debug: props.debug,
|
||||
enableExternalDragEvents: props.enableExternalDragEvents,
|
||||
})
|
||||
});
|
||||
|
||||
layoutReference.current = layout
|
||||
domReference.current.appendChild(layoutReference.current.element)
|
||||
layoutReference.current = layout;
|
||||
domReference.current.appendChild(layoutReference.current.element);
|
||||
|
||||
layout.deserializer = new ReactPanelDeserialzier(layout)
|
||||
layout.deserializer = new ReactPanelDeserialzier(layout);
|
||||
|
||||
layout.resizeToFit()
|
||||
layout.resizeToFit();
|
||||
|
||||
if (props.serializedLayout) {
|
||||
layout.deserialize(props.serializedLayout)
|
||||
layout.deserialize(props.serializedLayout);
|
||||
}
|
||||
|
||||
if (props.onReady) {
|
||||
props.onReady({ api: layout })
|
||||
props.onReady({ api: layout });
|
||||
}
|
||||
|
||||
return () => {
|
||||
layout.dispose()
|
||||
}
|
||||
}, [])
|
||||
layout.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = layoutReference.current.onTabContextMenu((event) => {
|
||||
props.onTabContextMenu(event)
|
||||
})
|
||||
props.onTabContextMenu(event);
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposable.dispose()
|
||||
}
|
||||
}, [props.onTabContextMenu])
|
||||
disposable.dispose();
|
||||
};
|
||||
}, [props.onTabContextMenu]);
|
||||
|
||||
React.useEffect(() => {
|
||||
layoutReference.current.setAutoResizeToFit(props.autoSizeToFitContainer)
|
||||
}, [props.autoSizeToFitContainer])
|
||||
layoutReference.current.setAutoResizeToFit(
|
||||
props.autoSizeToFitContainer
|
||||
);
|
||||
}, [props.autoSizeToFitContainer]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -138,5 +142,5 @@ export const ReactGrid = (props: IReactGridProps) => {
|
||||
>
|
||||
{portals}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,61 +1,61 @@
|
||||
import * as React from 'react'
|
||||
import * as ReactDOM from 'react-dom'
|
||||
import { IDisposable } from '../lifecycle'
|
||||
import { IGroupPanelApi } from '../groupview/panel/api'
|
||||
import { sequentialNumberGenerator } from '../math'
|
||||
import { IBaseViewApi } from '../panel/api'
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { IGroupPanelApi } from '../groupview/panel/api';
|
||||
import { sequentialNumberGenerator } from '../math';
|
||||
import { IBaseViewApi } from '../panel/api';
|
||||
|
||||
export interface IPanelProps {
|
||||
api: IGroupPanelApi
|
||||
api: IGroupPanelApi;
|
||||
}
|
||||
|
||||
interface IPanelWrapperProps {
|
||||
component: React.FunctionComponent<IPanelProps>
|
||||
componentProps: any
|
||||
component: React.FunctionComponent<IPanelProps>;
|
||||
componentProps: any;
|
||||
}
|
||||
|
||||
interface IPanelWrapperRef {
|
||||
update: (props: { [key: string]: any }) => void
|
||||
update: (props: { [key: string]: any }) => void;
|
||||
}
|
||||
|
||||
const PanelWrapper = React.forwardRef(
|
||||
(props: IPanelWrapperProps, ref: React.RefObject<IPanelWrapperRef>) => {
|
||||
const [_, triggerRender] = React.useState<number>()
|
||||
const [_, triggerRender] = React.useState<number>();
|
||||
const _props = React.useRef<{ [key: string]: any }>(
|
||||
props.componentProps
|
||||
)
|
||||
);
|
||||
|
||||
React.useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
update: (props: { [key: string]: any }) => {
|
||||
_props.current = { ..._props.current, ...props }
|
||||
triggerRender(Date.now())
|
||||
_props.current = { ..._props.current, ...props };
|
||||
triggerRender(Date.now());
|
||||
},
|
||||
}),
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
console.debug('[reactwrapper] component mounted ')
|
||||
console.debug('[reactwrapper] component mounted ');
|
||||
return () => {
|
||||
console.debug('[reactwrapper] component unmounted ')
|
||||
}
|
||||
}, [])
|
||||
console.debug('[reactwrapper] component unmounted ');
|
||||
};
|
||||
}, []);
|
||||
|
||||
return React.createElement(
|
||||
props.component,
|
||||
_props.current as IPanelProps
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const counter = sequentialNumberGenerator()
|
||||
const counter = sequentialNumberGenerator();
|
||||
|
||||
export class ReactPart implements IDisposable {
|
||||
private componentInstance: IPanelWrapperRef
|
||||
private ref: { portal: React.ReactPortal; disposable: IDisposable }
|
||||
private disposed: boolean
|
||||
private componentInstance: IPanelWrapperRef;
|
||||
private ref: { portal: React.ReactPortal; disposable: IDisposable };
|
||||
private disposed: boolean;
|
||||
|
||||
constructor(
|
||||
private readonly parent: HTMLElement,
|
||||
@ -64,49 +64,49 @@ export class ReactPart implements IDisposable {
|
||||
private readonly component: React.FunctionComponent<{}>,
|
||||
private readonly parameters: { [key: string]: any }
|
||||
) {
|
||||
this.createPortal()
|
||||
this.createPortal();
|
||||
}
|
||||
|
||||
public update(props: {}) {
|
||||
if (this.disposed) {
|
||||
throw new Error('invalid operation')
|
||||
throw new Error('invalid operation');
|
||||
}
|
||||
|
||||
this.componentInstance?.update(props)
|
||||
this.componentInstance?.update(props);
|
||||
}
|
||||
|
||||
private createPortal() {
|
||||
if (this.disposed) {
|
||||
throw new Error('invalid operation')
|
||||
throw new Error('invalid operation');
|
||||
}
|
||||
|
||||
let props = {
|
||||
api: this.api,
|
||||
...this.parameters,
|
||||
} as any
|
||||
} as any;
|
||||
|
||||
const wrapper = React.createElement(PanelWrapper, {
|
||||
component: this.component,
|
||||
componentProps: props,
|
||||
ref: (element: any) => {
|
||||
this.componentInstance = element
|
||||
this.componentInstance = element;
|
||||
},
|
||||
})
|
||||
});
|
||||
const portal = ReactDOM.createPortal(
|
||||
wrapper,
|
||||
this.parent,
|
||||
counter.next()
|
||||
)
|
||||
);
|
||||
|
||||
this.ref = {
|
||||
portal,
|
||||
disposable: this.addPortal(portal),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.ref?.disposable?.dispose()
|
||||
this.ref = undefined
|
||||
this.disposed = true
|
||||
this.ref?.disposable?.dispose();
|
||||
this.ref = undefined;
|
||||
this.disposed = true;
|
||||
}
|
||||
}
|
||||
|
@ -1,55 +1,55 @@
|
||||
import { trackFocus } from '../dom'
|
||||
import { Emitter } from '../events'
|
||||
import { GridApi } from '../panel/api'
|
||||
import { CompositeDisposable } from '../lifecycle'
|
||||
import { ReactLayout } from './layout'
|
||||
import { ReactPart } from './react'
|
||||
import { ISplitviewPanelProps } from './splitview'
|
||||
import { PanelUpdateEvent, InitParameters, IPanel } from '../panel/types'
|
||||
import { IComponentGridview } from '../layout/componentGridview'
|
||||
import { FunctionOrValue } from '../types'
|
||||
import { trackFocus } from '../dom';
|
||||
import { Emitter } from '../events';
|
||||
import { GridApi } from '../panel/api';
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import { ReactLayout } from './layout';
|
||||
import { ReactPart } from './react';
|
||||
import { ISplitviewPanelProps } from './splitview';
|
||||
import { PanelUpdateEvent, InitParameters, IPanel } from '../panel/types';
|
||||
import { IComponentGridview } from '../layout/componentGridview';
|
||||
import { FunctionOrValue } from '../types';
|
||||
|
||||
export class ReactComponentGridView
|
||||
extends CompositeDisposable
|
||||
implements IComponentGridview, IPanel {
|
||||
private _element: HTMLElement
|
||||
private part: ReactPart
|
||||
private params: { params: any }
|
||||
private api: GridApi
|
||||
private _element: HTMLElement;
|
||||
private part: ReactPart;
|
||||
private params: { params: any };
|
||||
private api: GridApi;
|
||||
|
||||
private _onDidChange: Emitter<number | undefined> = new Emitter<
|
||||
number | undefined
|
||||
>()
|
||||
public onDidChange = this._onDidChange.event
|
||||
>();
|
||||
public onDidChange = this._onDidChange.event;
|
||||
|
||||
get element() {
|
||||
return this._element
|
||||
return this._element;
|
||||
}
|
||||
|
||||
private _minimumWidth: FunctionOrValue<number> = 200
|
||||
private _minimumHeight: FunctionOrValue<number> = 200
|
||||
private _maximumWidth: FunctionOrValue<number> = Number.MAX_SAFE_INTEGER
|
||||
private _maximumHeight: FunctionOrValue<number> = Number.MAX_SAFE_INTEGER
|
||||
private _minimumWidth: FunctionOrValue<number> = 200;
|
||||
private _minimumHeight: FunctionOrValue<number> = 200;
|
||||
private _maximumWidth: FunctionOrValue<number> = Number.MAX_SAFE_INTEGER;
|
||||
private _maximumHeight: FunctionOrValue<number> = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
get minimumWidth() {
|
||||
return typeof this._minimumWidth === 'function'
|
||||
? this._minimumWidth()
|
||||
: this._minimumWidth
|
||||
: this._minimumWidth;
|
||||
}
|
||||
get minimumHeight() {
|
||||
return typeof this._minimumHeight === 'function'
|
||||
? this._minimumHeight()
|
||||
: this._minimumHeight
|
||||
: this._minimumHeight;
|
||||
}
|
||||
get maximumHeight() {
|
||||
return typeof this._maximumHeight === 'function'
|
||||
? this._maximumHeight()
|
||||
: this._maximumHeight
|
||||
: this._maximumHeight;
|
||||
}
|
||||
get maximumWidth() {
|
||||
return typeof this._maximumWidth === 'function'
|
||||
? this._maximumWidth()
|
||||
: this._maximumWidth
|
||||
: this._maximumWidth;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ -60,17 +60,17 @@ export class ReactComponentGridView
|
||||
>,
|
||||
private readonly parent: ReactLayout
|
||||
) {
|
||||
super()
|
||||
this.api = new GridApi()
|
||||
super();
|
||||
this.api = new GridApi();
|
||||
if (!this.component) {
|
||||
throw new Error('React.FunctionalComponent cannot be undefined')
|
||||
throw new Error('React.FunctionalComponent cannot be undefined');
|
||||
}
|
||||
|
||||
this._element = document.createElement('div')
|
||||
this._element.tabIndex = -1
|
||||
this._element.style.outline = 'none'
|
||||
this._element = document.createElement('div');
|
||||
this._element.tabIndex = -1;
|
||||
this._element.style.outline = 'none';
|
||||
|
||||
const { onDidFocus, onDidBlur } = trackFocus(this._element)
|
||||
const { onDidFocus, onDidBlur } = trackFocus(this._element);
|
||||
|
||||
this.addDisposables(
|
||||
this.api.onDidConstraintsChange((event) => {
|
||||
@ -78,54 +78,58 @@ export class ReactComponentGridView
|
||||
typeof event.minimumWidth === 'number' ||
|
||||
typeof event.minimumWidth === 'function'
|
||||
) {
|
||||
this._minimumWidth = event.minimumWidth
|
||||
this._minimumWidth = event.minimumWidth;
|
||||
}
|
||||
if (
|
||||
typeof event.minimumHeight === 'number' ||
|
||||
typeof event.minimumHeight === 'function'
|
||||
) {
|
||||
this._minimumHeight = event.minimumHeight
|
||||
this._minimumHeight = event.minimumHeight;
|
||||
}
|
||||
if (
|
||||
typeof event.maximumWidth === 'number' ||
|
||||
typeof event.maximumWidth === 'function'
|
||||
) {
|
||||
this._maximumWidth = event.maximumWidth
|
||||
this._maximumWidth = event.maximumWidth;
|
||||
}
|
||||
if (
|
||||
typeof event.maximumHeight === 'number' ||
|
||||
typeof event.maximumHeight === 'function'
|
||||
) {
|
||||
this._maximumHeight = event.maximumHeight
|
||||
this._maximumHeight = event.maximumHeight;
|
||||
}
|
||||
}),
|
||||
onDidFocus(() => {
|
||||
this.api._onDidChangeFocus.fire({ isFocused: true })
|
||||
this.api._onDidChangeFocus.fire({ isFocused: true });
|
||||
}),
|
||||
onDidBlur(() => {
|
||||
this.api._onDidChangeFocus.fire({ isFocused: false })
|
||||
this.api._onDidChangeFocus.fire({ isFocused: false });
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
setActive(isActive: boolean) {
|
||||
// noop
|
||||
}
|
||||
|
||||
layout(width: number, height: number) {
|
||||
this.api._onDidPanelDimensionChange.fire({ width, height })
|
||||
this.api._onDidPanelDimensionChange.fire({ width, height });
|
||||
}
|
||||
|
||||
init(parameters: InitParameters): void {
|
||||
this.params = parameters
|
||||
this.params = parameters;
|
||||
this.part = new ReactPart(
|
||||
this.element,
|
||||
this.api,
|
||||
this.parent.addPortal,
|
||||
this.component,
|
||||
parameters.params
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
update(params: PanelUpdateEvent) {
|
||||
this.params = { ...this.params.params, ...params }
|
||||
this.part.update(params)
|
||||
this.params = { ...this.params.params, ...params };
|
||||
this.part.update(params);
|
||||
}
|
||||
|
||||
toJSON(): object {
|
||||
@ -134,11 +138,11 @@ export class ReactComponentGridView
|
||||
component: this.componentName,
|
||||
props: this.params.params,
|
||||
state: this.api.getState(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose()
|
||||
this.api.dispose()
|
||||
super.dispose();
|
||||
this.api.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { trackFocus } from '../dom'
|
||||
import { Emitter } from '../events'
|
||||
import { PanelApi } from '../panel/api'
|
||||
import { PanelDimensionChangeEvent } from '../panel/types'
|
||||
import { CompositeDisposable } from '../lifecycle'
|
||||
import { IView } from '../splitview/splitview'
|
||||
import { ReactLayout } from './layout'
|
||||
import { ReactPart } from './react'
|
||||
import { ISplitviewPanelProps } from './splitview'
|
||||
import { PanelUpdateEvent, InitParameters, IPanel } from '../panel/types'
|
||||
import { trackFocus } from '../dom';
|
||||
import { Emitter } from '../events';
|
||||
import { PanelApi } from '../panel/api';
|
||||
import { PanelDimensionChangeEvent } from '../panel/types';
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import { IView } from '../splitview/splitview';
|
||||
import { ReactLayout } from './layout';
|
||||
import { ReactPart } from './react';
|
||||
import { ISplitviewPanelProps } from './splitview';
|
||||
import { PanelUpdateEvent, InitParameters, IPanel } from '../panel/types';
|
||||
|
||||
/**
|
||||
* A no-thrills implementation of IView that renders a React component
|
||||
@ -15,43 +15,43 @@ import { PanelUpdateEvent, InitParameters, IPanel } from '../panel/types'
|
||||
export class ReactComponentView
|
||||
extends CompositeDisposable
|
||||
implements IView, IPanel {
|
||||
private _element: HTMLElement
|
||||
private part: ReactPart
|
||||
private params: { params: any }
|
||||
private api: PanelApi
|
||||
private _element: HTMLElement;
|
||||
private part: ReactPart;
|
||||
private params: { params: any };
|
||||
private api: PanelApi;
|
||||
|
||||
private _onDidChange: Emitter<number | undefined> = new Emitter<
|
||||
number | undefined
|
||||
>()
|
||||
public onDidChange = this._onDidChange.event
|
||||
>();
|
||||
public onDidChange = this._onDidChange.event;
|
||||
|
||||
get element() {
|
||||
return this._element
|
||||
return this._element;
|
||||
}
|
||||
|
||||
private _minimumSize: number = 200
|
||||
private _maximumSize: number = Number.MAX_SAFE_INTEGER
|
||||
private _snapSize: number
|
||||
private _minimumSize: number = 200;
|
||||
private _maximumSize: number = Number.MAX_SAFE_INTEGER;
|
||||
private _snapSize: number;
|
||||
|
||||
get minimumSize() {
|
||||
return this._minimumSize
|
||||
return this._minimumSize;
|
||||
}
|
||||
set minimumSize(value: number) {
|
||||
this._minimumSize = value
|
||||
this._minimumSize = value;
|
||||
}
|
||||
|
||||
get snapSize() {
|
||||
return this._snapSize
|
||||
return this._snapSize;
|
||||
}
|
||||
set snapSize(value: number) {
|
||||
this._snapSize = value
|
||||
this._snapSize = value;
|
||||
}
|
||||
|
||||
get maximumSize() {
|
||||
return this._maximumSize
|
||||
return this._maximumSize;
|
||||
}
|
||||
set maximumSize(value: number) {
|
||||
this._maximumSize = value
|
||||
this._maximumSize = value;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ -62,46 +62,46 @@ export class ReactComponentView
|
||||
>,
|
||||
private readonly parent: ReactLayout
|
||||
) {
|
||||
super()
|
||||
this.api = new PanelApi()
|
||||
super();
|
||||
this.api = new PanelApi();
|
||||
if (!this.component) {
|
||||
throw new Error('React.FunctionalComponent cannot be undefined')
|
||||
throw new Error('React.FunctionalComponent cannot be undefined');
|
||||
}
|
||||
|
||||
this._element = document.createElement('div')
|
||||
this._element.tabIndex = -1
|
||||
this._element.style.outline = 'none'
|
||||
this._element = document.createElement('div');
|
||||
this._element.tabIndex = -1;
|
||||
this._element.style.outline = 'none';
|
||||
|
||||
const { onDidFocus, onDidBlur } = trackFocus(this._element)
|
||||
const { onDidFocus, onDidBlur } = trackFocus(this._element);
|
||||
|
||||
this.addDisposables(
|
||||
onDidFocus(() => {
|
||||
this.api._onDidChangeFocus.fire({ isFocused: true })
|
||||
this.api._onDidChangeFocus.fire({ isFocused: true });
|
||||
}),
|
||||
onDidBlur(() => {
|
||||
this.api._onDidChangeFocus.fire({ isFocused: false })
|
||||
this.api._onDidChangeFocus.fire({ isFocused: false });
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
layout(width: number, height: number) {
|
||||
this.api._onDidPanelDimensionChange.fire({ width, height })
|
||||
this.api._onDidPanelDimensionChange.fire({ width, height });
|
||||
}
|
||||
|
||||
init(parameters: InitParameters): void {
|
||||
this.params = parameters
|
||||
this.params = parameters;
|
||||
this.part = new ReactPart(
|
||||
this.element,
|
||||
this.api,
|
||||
this.parent.addPortal,
|
||||
this.component,
|
||||
parameters.params
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
update(params: PanelUpdateEvent) {
|
||||
this.params = { ...this.params.params, ...params }
|
||||
this.part.update(params)
|
||||
this.params = { ...this.params.params, ...params };
|
||||
this.part.update(params);
|
||||
}
|
||||
|
||||
toJSON(): object {
|
||||
@ -110,11 +110,11 @@ export class ReactComponentView
|
||||
component: this.componentName,
|
||||
props: this.params.params,
|
||||
state: this.api.getState(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose()
|
||||
this.api.dispose()
|
||||
super.dispose();
|
||||
this.api.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import * as React from 'react'
|
||||
import * as React from 'react';
|
||||
import {
|
||||
PanelContentPart,
|
||||
PartInitParameters,
|
||||
ClosePanelResult,
|
||||
} from '../groupview/panel/parts'
|
||||
import { ReactPart, IPanelProps } from './react'
|
||||
import { ReactLayout } from './layout'
|
||||
} from '../groupview/panel/parts';
|
||||
import { ReactPart, IPanelProps } from './react';
|
||||
import { ReactLayout } from './layout';
|
||||
|
||||
export class ReactPanelContentPart implements PanelContentPart {
|
||||
private _element: HTMLElement
|
||||
private part: ReactPart
|
||||
private _element: HTMLElement;
|
||||
private part: ReactPart;
|
||||
|
||||
get element() {
|
||||
return this._element
|
||||
return this._element;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ -20,7 +20,7 @@ export class ReactPanelContentPart implements PanelContentPart {
|
||||
private readonly component: React.FunctionComponent<IPanelProps>,
|
||||
private readonly parent: ReactLayout
|
||||
) {
|
||||
this._element = document.createElement('div')
|
||||
this._element = document.createElement('div');
|
||||
}
|
||||
|
||||
public init(parameters: PartInitParameters): void {
|
||||
@ -30,17 +30,17 @@ export class ReactPanelContentPart implements PanelContentPart {
|
||||
this.parent.addPortal,
|
||||
this.component,
|
||||
parameters.params
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public update(params: {}) {
|
||||
this.part.update(params)
|
||||
this.part.update(params);
|
||||
}
|
||||
|
||||
public setVisible(isPanelVisible: boolean, isGroupVisible: boolean): void {
|
||||
@ -50,13 +50,13 @@ export class ReactPanelContentPart implements PanelContentPart {
|
||||
public layout(width: number, height: number): void {}
|
||||
|
||||
public close(): Promise<ClosePanelResult> {
|
||||
return Promise.resolve(ClosePanelResult.CLOSE)
|
||||
return Promise.resolve(ClosePanelResult.CLOSE);
|
||||
}
|
||||
|
||||
public focus(): void {}
|
||||
public onHide(): void {}
|
||||
|
||||
public dispose() {
|
||||
this.part?.dispose()
|
||||
this.part?.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import * as React from 'react'
|
||||
import { PanelHeaderPart, PartInitParameters } from '../groupview/panel/parts'
|
||||
import { ReactPart, IPanelProps } from './react'
|
||||
import { ReactLayout } from './layout'
|
||||
import * as React from 'react';
|
||||
import { PanelHeaderPart, PartInitParameters } from '../groupview/panel/parts';
|
||||
import { ReactPart, IPanelProps } from './react';
|
||||
import { ReactLayout } from './layout';
|
||||
|
||||
export class ReactPanelHeaderPart implements PanelHeaderPart {
|
||||
private _element: HTMLElement
|
||||
private part: ReactPart
|
||||
private _element: HTMLElement;
|
||||
private part: ReactPart;
|
||||
|
||||
get element() {
|
||||
return this._element
|
||||
return this._element;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ -16,7 +16,7 @@ export class ReactPanelHeaderPart implements PanelHeaderPart {
|
||||
private readonly component: React.FunctionComponent<IPanelProps>,
|
||||
private readonly parent: ReactLayout
|
||||
) {
|
||||
this._element = document.createElement('div')
|
||||
this._element = document.createElement('div');
|
||||
}
|
||||
|
||||
public init(parameters: PartInitParameters): void {
|
||||
@ -26,13 +26,13 @@ export class ReactPanelHeaderPart implements PanelHeaderPart {
|
||||
this.parent.addPortal,
|
||||
this.component,
|
||||
parameters.params
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public layout(height: string) {
|
||||
@ -44,6 +44,6 @@ export class ReactPanelHeaderPart implements PanelHeaderPart {
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.part?.dispose()
|
||||
this.part?.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,57 +1,57 @@
|
||||
import * as React from 'react'
|
||||
import { IPanelApi } from '../panel/api'
|
||||
import { IDisposable } from '../lifecycle'
|
||||
import * as React from 'react';
|
||||
import { IPanelApi } from '../panel/api';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import {
|
||||
IComponentSplitview,
|
||||
ComponentSplitview,
|
||||
} from '../splitview/componentSplitview'
|
||||
import { Orientation } from '../splitview/splitview'
|
||||
import { ReactComponentView } from './reactComponentView'
|
||||
} from '../splitview/componentSplitview';
|
||||
import { Orientation } from '../splitview/splitview';
|
||||
import { ReactComponentView } from './reactComponentView';
|
||||
|
||||
export interface SplitviewFacade {
|
||||
addFromComponent(options: {
|
||||
id: string
|
||||
component: string
|
||||
params?: { [index: string]: any }
|
||||
}): void
|
||||
layout(size: number, orthogonalSize: number): void
|
||||
onChange: (cb: (event: { proportions: number[] }) => void) => IDisposable
|
||||
toJSON: () => any
|
||||
deserialize: (data: any) => void
|
||||
minimumSize: number
|
||||
id: string;
|
||||
component: string;
|
||||
params?: { [index: string]: any };
|
||||
}): void;
|
||||
layout(size: number, orthogonalSize: number): void;
|
||||
onChange: (cb: (event: { proportions: number[] }) => void) => IDisposable;
|
||||
toJSON: () => any;
|
||||
deserialize: (data: any) => void;
|
||||
minimumSize: number;
|
||||
}
|
||||
|
||||
export interface SplitviewReadyEvent {
|
||||
api: IComponentSplitview
|
||||
api: IComponentSplitview;
|
||||
}
|
||||
|
||||
export interface ISplitviewPanelProps {
|
||||
api: IPanelApi
|
||||
api: IPanelApi;
|
||||
}
|
||||
|
||||
export interface ISplitviewComponentProps {
|
||||
orientation: Orientation
|
||||
onReady?: (event: SplitviewReadyEvent) => void
|
||||
orientation: Orientation;
|
||||
onReady?: (event: SplitviewReadyEvent) => void;
|
||||
components: {
|
||||
[index: string]: React.FunctionComponent<ISplitviewPanelProps>
|
||||
}
|
||||
[index: string]: React.FunctionComponent<ISplitviewPanelProps>;
|
||||
};
|
||||
}
|
||||
|
||||
export const SplitViewComponent = (props: ISplitviewComponentProps) => {
|
||||
const domReference = React.useRef<HTMLDivElement>()
|
||||
const splitpanel = React.useRef<IComponentSplitview>()
|
||||
const [portals, setPortals] = React.useState<React.ReactPortal[]>([])
|
||||
const domReference = React.useRef<HTMLDivElement>();
|
||||
const splitpanel = React.useRef<IComponentSplitview>();
|
||||
const [portals, setPortals] = React.useState<React.ReactPortal[]>([]);
|
||||
|
||||
const addPortal = React.useCallback((p: React.ReactPortal) => {
|
||||
setPortals((portals) => [...portals, p])
|
||||
setPortals((portals) => [...portals, p]);
|
||||
return {
|
||||
dispose: () => {
|
||||
setPortals((portals) =>
|
||||
portals.filter((portal) => portal !== p)
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
}, [])
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
splitpanel.current = new ComponentSplitview(domReference.current, {
|
||||
@ -61,27 +61,27 @@ export const SplitViewComponent = (props: ISplitviewComponentProps) => {
|
||||
createComponent: (id: string, component: any) => {
|
||||
return new ReactComponentView(id, id, component, {
|
||||
addPortal,
|
||||
})
|
||||
});
|
||||
},
|
||||
},
|
||||
proportionalLayout: false,
|
||||
})
|
||||
});
|
||||
|
||||
const { width, height } = domReference.current.getBoundingClientRect()
|
||||
const { width, height } = domReference.current.getBoundingClientRect();
|
||||
const [size, orthogonalSize] =
|
||||
props.orientation === Orientation.HORIZONTAL
|
||||
? [width, height]
|
||||
: [height, width]
|
||||
splitpanel.current.layout(size, orthogonalSize)
|
||||
: [height, width];
|
||||
splitpanel.current.layout(size, orthogonalSize);
|
||||
|
||||
if (props.onReady) {
|
||||
props.onReady({ api: splitpanel.current })
|
||||
props.onReady({ api: splitpanel.current });
|
||||
}
|
||||
|
||||
return () => {
|
||||
splitpanel.current.dispose()
|
||||
}
|
||||
}, [])
|
||||
splitpanel.current.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -93,5 +93,5 @@ export const SplitViewComponent = (props: ISplitviewComponentProps) => {
|
||||
>
|
||||
{portals}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,77 +1,77 @@
|
||||
import { IDisposable } from '../lifecycle'
|
||||
import { LayoutPriority, Orientation, SplitView } from './splitview'
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { LayoutPriority, Orientation, SplitView } from './splitview';
|
||||
import {
|
||||
createComponent,
|
||||
ISerializableView,
|
||||
SplitPanelOptions,
|
||||
} from './options'
|
||||
} from './options';
|
||||
|
||||
export interface IComponentSplitview extends IDisposable {
|
||||
addFromComponent(options: {
|
||||
id: string
|
||||
component: string
|
||||
id: string;
|
||||
component: string;
|
||||
params?: {
|
||||
[index: string]: any
|
||||
}
|
||||
priority?: LayoutPriority
|
||||
}): IDisposable
|
||||
layout(width: number, height: number): void
|
||||
onChange(cb: (event: { proportions: number[] }) => void): IDisposable
|
||||
toJSON(): object
|
||||
deserialize(data: any): void
|
||||
minimumSize: number
|
||||
[index: string]: any;
|
||||
};
|
||||
priority?: LayoutPriority;
|
||||
}): IDisposable;
|
||||
layout(width: number, height: number): void;
|
||||
onChange(cb: (event: { proportions: number[] }) => void): IDisposable;
|
||||
toJSON(): object;
|
||||
deserialize(data: any): void;
|
||||
minimumSize: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A high-level implementation of splitview that works using 'panels'
|
||||
*/
|
||||
export class ComponentSplitview implements IComponentSplitview {
|
||||
private splitview: SplitView
|
||||
private splitview: SplitView;
|
||||
|
||||
constructor(
|
||||
private readonly element: HTMLElement,
|
||||
private readonly options: SplitPanelOptions
|
||||
) {
|
||||
if (!options.components) {
|
||||
options.components = {}
|
||||
options.components = {};
|
||||
}
|
||||
if (!options.frameworkComponents) {
|
||||
options.frameworkComponents = {}
|
||||
options.frameworkComponents = {};
|
||||
}
|
||||
|
||||
this.splitview = new SplitView(this.element, options)
|
||||
this.splitview = new SplitView(this.element, options);
|
||||
}
|
||||
|
||||
get minimumSize() {
|
||||
return this.splitview.minimumSize
|
||||
return this.splitview.minimumSize;
|
||||
}
|
||||
|
||||
addFromComponent(options: {
|
||||
id: string
|
||||
component: string
|
||||
id: string;
|
||||
component: string;
|
||||
params?: {
|
||||
[index: string]: any
|
||||
}
|
||||
priority?: LayoutPriority
|
||||
[index: string]: any;
|
||||
};
|
||||
priority?: LayoutPriority;
|
||||
}): IDisposable {
|
||||
const view = createComponent(
|
||||
options.component,
|
||||
this.options.components,
|
||||
this.options.frameworkComponents,
|
||||
this.options.frameworkWrapper.createComponent
|
||||
)
|
||||
);
|
||||
|
||||
this.registerView(view)
|
||||
this.registerView(view);
|
||||
|
||||
this.splitview.addView(view, { type: 'distribute' })
|
||||
view.init({ params: options.params })
|
||||
view.priority = options.priority
|
||||
this.splitview.addView(view, { type: 'distribute' });
|
||||
view.init({ params: options.params });
|
||||
view.priority = options.priority;
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
//
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private registerView(view: ISerializableView) {
|
||||
@ -82,77 +82,77 @@ export class ComponentSplitview implements IComponentSplitview {
|
||||
const [size, orthogonalSize] =
|
||||
this.splitview.orientation === Orientation.HORIZONTAL
|
||||
? [width, height]
|
||||
: [height, width]
|
||||
this.splitview.layout(size, orthogonalSize)
|
||||
: [height, width];
|
||||
this.splitview.layout(size, orthogonalSize);
|
||||
}
|
||||
|
||||
onChange(cb: (event: { proportions: number[] }) => void): IDisposable {
|
||||
return this.splitview.onDidSashEnd(() => {
|
||||
cb({ proportions: this.splitview.proportions })
|
||||
})
|
||||
cb({ proportions: this.splitview.proportions });
|
||||
});
|
||||
}
|
||||
toJSON(): object {
|
||||
const views = this.splitview
|
||||
.getViews()
|
||||
.map((v: ISerializableView, i) => {
|
||||
const size = this.splitview.getViewSize(i)
|
||||
const size = this.splitview.getViewSize(i);
|
||||
return {
|
||||
size,
|
||||
data: v.toJSON ? v.toJSON() : {},
|
||||
minimumSize: v.minimumSize,
|
||||
maximumSize: v.maximumSize,
|
||||
snapSize: v.snapSize,
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
views,
|
||||
size: this.splitview.size,
|
||||
orientation: this.splitview.orientation,
|
||||
}
|
||||
};
|
||||
}
|
||||
deserialize(data: any): void {
|
||||
const { views, orientation, size } = data
|
||||
const { views, orientation, size } = data;
|
||||
|
||||
this.splitview.dispose()
|
||||
this.splitview.dispose();
|
||||
this.splitview = new SplitView(this.element, {
|
||||
orientation,
|
||||
proportionalLayout: false,
|
||||
descriptor: {
|
||||
size,
|
||||
views: views.map((v) => {
|
||||
const data = v.data
|
||||
const data = v.data;
|
||||
|
||||
const view = createComponent(
|
||||
data.component,
|
||||
this.options.components,
|
||||
this.options.frameworkComponents,
|
||||
this.options.frameworkWrapper.createComponent
|
||||
)
|
||||
);
|
||||
|
||||
if (typeof v.minimumSize === 'number') {
|
||||
view.minimumSize = v.minimumSize
|
||||
view.minimumSize = v.minimumSize;
|
||||
}
|
||||
if (typeof v.maximumSize === 'number') {
|
||||
view.maximumSize = v.maximumSize
|
||||
view.maximumSize = v.maximumSize;
|
||||
}
|
||||
if (typeof v.snapSize === 'number') {
|
||||
view.snapSize = v.snapSize
|
||||
view.snapSize = v.snapSize;
|
||||
}
|
||||
|
||||
view.init({ params: v.props })
|
||||
view.init({ params: v.props });
|
||||
|
||||
view.priority = v.priority
|
||||
view.priority = v.priority;
|
||||
|
||||
return { size: v.size, view }
|
||||
return { size: v.size, view };
|
||||
}),
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
this.splitview.orientation = orientation
|
||||
this.splitview.orientation = orientation;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.splitview.dispose()
|
||||
this.splitview.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { IView, ISplitViewOptions } from '../splitview/splitview'
|
||||
import { Constructor, FrameworkFactory } from '../types'
|
||||
import { IView, ISplitViewOptions } from '../splitview/splitview';
|
||||
import { Constructor, FrameworkFactory } from '../types';
|
||||
|
||||
export interface ISerializableView extends IView {
|
||||
toJSON: () => object
|
||||
init: (params: { params: any }) => void
|
||||
toJSON: () => object;
|
||||
init: (params: { params: any }) => void;
|
||||
}
|
||||
|
||||
export interface SplitPanelOptions extends ISplitViewOptions {
|
||||
components?: {
|
||||
[componentName: string]: ISerializableView
|
||||
}
|
||||
[componentName: string]: ISerializableView;
|
||||
};
|
||||
frameworkComponents?: {
|
||||
[componentName: string]: any
|
||||
}
|
||||
frameworkWrapper?: FrameworkFactory<ISerializableView>
|
||||
[componentName: string]: any;
|
||||
};
|
||||
frameworkWrapper?: FrameworkFactory<ISerializableView>;
|
||||
}
|
||||
|
||||
export interface ISerializableViewConstructor
|
||||
@ -22,37 +22,37 @@ export interface ISerializableViewConstructor
|
||||
export function createComponent<T>(
|
||||
componentName: string | Constructor<T> | any,
|
||||
components: {
|
||||
[componentName: string]: T
|
||||
[componentName: string]: T;
|
||||
},
|
||||
frameworkComponents: {
|
||||
[componentName: string]: any
|
||||
[componentName: string]: any;
|
||||
},
|
||||
createFrameworkComponent: (id: string, component: any) => T
|
||||
): T {
|
||||
const Component =
|
||||
typeof componentName === 'string'
|
||||
? components[componentName]
|
||||
: componentName
|
||||
: componentName;
|
||||
const FrameworkComponent =
|
||||
typeof componentName === 'string'
|
||||
? frameworkComponents[componentName]
|
||||
: componentName
|
||||
: componentName;
|
||||
if (Component && FrameworkComponent) {
|
||||
throw new Error(
|
||||
`cannot register component ${componentName} as both a component and frameworkComponent`
|
||||
)
|
||||
);
|
||||
}
|
||||
if (FrameworkComponent) {
|
||||
if (!createFrameworkComponent) {
|
||||
throw new Error(
|
||||
'you must register a frameworkPanelWrapper to use framework components'
|
||||
)
|
||||
);
|
||||
}
|
||||
const wrappedComponent = createFrameworkComponent(
|
||||
componentName,
|
||||
FrameworkComponent
|
||||
)
|
||||
return wrappedComponent
|
||||
);
|
||||
return wrappedComponent;
|
||||
}
|
||||
return new Component() as T
|
||||
return new Component() as T;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,9 @@
|
||||
export interface Constructor<T> {
|
||||
new (): T
|
||||
new (): T;
|
||||
}
|
||||
|
||||
export interface FrameworkFactory<T> {
|
||||
createComponent: (id: string, component: any) => T
|
||||
createComponent: (id: string, component: any) => T;
|
||||
}
|
||||
|
||||
export type FunctionOrValue<T> = (() => T) | T
|
||||
export type FunctionOrValue<T> = (() => T) | T;
|
||||
|
@ -1,11 +1,11 @@
|
||||
const gulp = require('gulp')
|
||||
const gulpClean = require('gulp-clean')
|
||||
const gulpTypescript = require('gulp-typescript')
|
||||
const merge = require('merge2')
|
||||
const header = require('gulp-header')
|
||||
const gulpSass = require('gulp-sass')
|
||||
const concat = require('gulp-concat')
|
||||
const sourcemaps = require('gulp-sourcemaps')
|
||||
const gulp = require('gulp');
|
||||
const gulpClean = require('gulp-clean');
|
||||
const gulpTypescript = require('gulp-typescript');
|
||||
const merge = require('merge2');
|
||||
const header = require('gulp-header');
|
||||
const gulpSass = require('gulp-sass');
|
||||
const concat = require('gulp-concat');
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
|
||||
const headerTemplate = [
|
||||
'/**',
|
||||
@ -14,25 +14,25 @@ const headerTemplate = [
|
||||
' * @link <%= pkg.homepage %>',
|
||||
' * @licence <%= pkg.licence %>',
|
||||
' */\n',
|
||||
].join('\n')
|
||||
].join('\n');
|
||||
|
||||
const dtsHeaderTemplate = [
|
||||
'// Type definitions for <%= pkg.name %> v <%= pkg.version %>',
|
||||
'// Project <%= pkg.homepage %>\n',
|
||||
].join('\n')
|
||||
].join('\n');
|
||||
|
||||
const build = (options) => {
|
||||
const { tsconfig, package } = options
|
||||
const { tsconfig, package } = options;
|
||||
gulp.task('clean', () =>
|
||||
gulp.src('dist', { read: false, allowEmpty: true }).pipe(gulpClean())
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task('esm', () => {
|
||||
const ts = gulpTypescript.createProject(tsconfig)
|
||||
const ts = gulpTypescript.createProject(tsconfig);
|
||||
const tsResult = gulp
|
||||
.src(['src/**/*.ts', 'src/**/*.tsx'])
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(ts())
|
||||
.pipe(ts());
|
||||
return merge([
|
||||
tsResult.dts
|
||||
.pipe(header(dtsHeaderTemplate, { pkg: package }))
|
||||
@ -43,8 +43,8 @@ const build = (options) => {
|
||||
tsResult
|
||||
.pipe(sourcemaps.write('.', { includeContent: false }))
|
||||
.pipe(gulp.dest('./dist/esm')),
|
||||
])
|
||||
})
|
||||
]);
|
||||
});
|
||||
|
||||
gulp.task('sass', () => {
|
||||
return (
|
||||
@ -56,8 +56,8 @@ const build = (options) => {
|
||||
// )
|
||||
.pipe(concat('styles.css'))
|
||||
.pipe(gulp.dest('./dist'))
|
||||
)
|
||||
})
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = { build }
|
||||
module.exports = { build };
|
||||
|
Loading…
Reference in New Issue
Block a user