Skip to content

Instantly share code, notes, and snippets.

@nmichaud
Created March 31, 2018 02:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nmichaud/10495747fe5b099926b81cf1373673ac to your computer and use it in GitHub Desktop.
Save nmichaud/10495747fe5b099926b81cf1373673ac to your computer and use it in GitHub Desktop.
DockPanel subclass with maximize/minimize button and menu button.>....
import {
Message
} from '@phosphor/messaging';
import {
VirtualElement, h
} from '@phosphor/virtualdom';
import {
ISignal, Signal
} from '@phosphor/signaling';
import {
ArrayExt, each
} from '@phosphor/algorithm';
import {
ElementExt
} from '@phosphor/domutils';
import {
CommandRegistry
} from '@phosphor/commands';
import {
DockPanel as DockPanel_, TabBar as TabBar_, Menu, Widget, Title,
} from '@phosphor/widgets';
function createMenu(commands: CommandRegistry): Menu {
let root = new Menu({ commands });
root.addItem({ command: 'example:lock' });
root.addItem({ type: 'separator' });
root.addItem({ command: 'example:new-tab' });
return root;
}
class TabBarRenderer extends TabBar_.Renderer {
/**
* A selector which matches the close icon node in a tab.
*/
readonly maximizeIconSelector = '.p-TabBar-tabMaximizeIcon';
/**
* A selector which matches the close icon node in a tab.
*/
readonly configureIconSelector = '.p-TabBar-tabConfigureIcon';
/**
* Render tabs with the default DOM structure, but additionally register a context
* menu listener.
*/
renderTab(data: TabBar_.IRenderData<any>): VirtualElement {
let title = data.title.caption;
let key = this.createTabKey(data);
let style = this.createTabStyle(data);
let className = this.createTabClass(data);
let dataset = this.createTabDataset(data);
return (
h.li({ key, className, title, style, dataset },
this.renderIcon(data),
this.renderLabel(data),
this.renderConfigureIcon(data),
this.renderMaximizeIcon(data),
this.renderCloseIcon(data)
)
);
}
/**
* Render the maximize icon element for a tab.
*
* @param data - The data to use for rendering the tab.
*
* @returns A virtual element representing the tab maximize icon.
*/
renderMaximizeIcon(data: TabBar_.IRenderData<any>): VirtualElement {
let name = 'p-TabBar-tabMaximizeIcon';
if (!this.maximized) {
name += ` p-mod-maximize`;
} else {
name += ` p-mod-minimize`;
}
return h.div({ className: name });
}
/**
* Render the configure icon element for a tab.
*
* @param data - The data to use for rendering the tab.
*
* @returns A virtual element representing the tab configure icon.
*/
renderConfigureIcon(data: TabBar_.IRenderData<any>): VirtualElement {
return h.div({ className: 'p-TabBar-tabConfigureIcon' });
}
maximized: boolean = false;
}
/**
* The arguments object for the `tabMaximizeRequested` signal.
*/
export
interface ITabMaximizeRequestedArgs<T> {
/**
* The index of the tab to maximize.
*/
readonly index: number;
/**
* The title for the tab.
*/
readonly title: Title<T>;
};
/**
* The arguments object for the `tabConfigureRequested` signal.
*/
export
interface ITabConfigureRequestedArgs<T> {
/**
* The index of the tab to maximize.
*/
readonly index: number;
/**
* The title for the tab.
*/
readonly title: Title<T>;
/**
* The current client X position of the mouse.
*/
readonly clientX: number;
/**
* The current client Y position of the mouse.
*/
readonly clientY: number;
};
export
class TabBar<T> extends TabBar_<T> {
/**
* A signal emitted when a tab maximize icon is clicked.
*
*/
get tabMaximizeRequested(): ISignal<this, ITabMaximizeRequestedArgs<T>> {
return this._tabMaximizeRequested;
}
private _tabMaximizeRequested = new Signal<this, ITabMaximizeRequestedArgs<T>>(this);
/**
* A signal emitted when a tab configure icon is clicked.
*
*/
get tabConfigureRequested(): ISignal<this, ITabConfigureRequestedArgs<T>> {
return this._tabConfigureRequested;
}
private _tabConfigureRequested = new Signal<this, ITabConfigureRequestedArgs<T>>(this);
/**
* Handle the DOM events for the tab bar.
*
* @param event - The DOM event sent to the tab bar.
*
* #### Notes
* This method implements the DOM `EventListener` interface and is
* called in response to events on the tab bar's DOM node.
*
* This should not be called directly by user code.
*/
handleEvent(event: Event): void {
switch (event.type) {
case 'mouseup':
// Lookup the tab nodes.
let tabs = this.contentNode.children;
let ev = event as MouseEvent;
// Note this is broken, since it only checks on mouse up so you could
// click outside the button and release inside and it would fire
// Find the index of the released tab.
let index = ArrayExt.findFirstIndex(tabs, tab => {
return ElementExt.hitTest(tab, ev.clientX, ev.clientY);
});
let title = this.titles[index];
// Emit the close requested signal if the maximize icon was released.
let icon = tabs[index].querySelector((this.renderer as TabBarRenderer).maximizeIconSelector);
if (icon && icon.contains(ev.target as HTMLElement)) {
this._tabMaximizeRequested.emit({ index, title });
}
icon = tabs[index].querySelector((this.renderer as TabBarRenderer).configureIconSelector);
if (icon && icon.contains(ev.target as HTMLElement)) {
let er = (ev.target as HTMLElement).getBoundingClientRect();
let clientX = er.left;
let clientY = er.bottom;
this._tabConfigureRequested.emit({ index, title, clientX, clientY });
}
}
super.handleEvent(event);
}
}
class DockPanelRenderer extends DockPanel_.Renderer {
createTabBar(): TabBar<Widget> {
let bar = new TabBar<Widget>({renderer: new TabBarRenderer()});
bar.addClass('p-DockPanel-tabBar');
return bar;
}
}
export
class DockPanel extends DockPanel_ {
constructor(commands: CommandRegistry, options: DockPanel_.IOptions = {}) {
super({...options,
renderer: new DockPanelRenderer()
});
this._commands = commands;
}
/**
* Get the locked state for the dock panel.
*/
get locked(): boolean {
return this._locked;
}
/**
* Set the locked state for the dock panel.
*
*/
set locked(value: boolean) {
// Bail early if the locked state does not change.
if (this._locked === value) {
return;
}
this._locked = value;
each(this.tabBars(), tabBar =>
{
tabBar.tabsMovable = !value;
each(tabBar.titles, title => { title.closable = !value;});
});
}
protected _createTabBar(): TabBar<Widget> {
let tabBar = super._createTabBar() as TabBar<Widget>;
tabBar.tabMaximizeRequested.connect(this._onTabMaximizeRequested, this);
tabBar.tabConfigureRequested.connect(this._onTabConfigureRequested, this);
tabBar.tabsMovable = !this._locked;
let renderer = tabBar.renderer as TabBarRenderer;
renderer.maximized = this._maximized;
return tabBar;
}
/**
* Handle the `tabMaximizeRequested` signal from a tab bar.
*/
private _onTabConfigureRequested(sender: TabBar<Widget>, args: ITabConfigureRequestedArgs<Widget>): void {
let menu = createMenu(this._commands);
menu.open(args.clientX, args.clientY);
}
/**
* Handle the `tabMaximizeRequested` signal from a tab bar.
*/
private _onTabMaximizeRequested(sender: TabBar<Widget>, args: ITabMaximizeRequestedArgs<Widget>): void {
this._maximized = !this._maximized;
if (this._maximized) {
this._minimizedLayout = this.saveLayout();
this.restoreLayout({
main: {
type: 'tab-area',
widgets: [args.title.owner],
currentIndex: 0
}
});
} else {
this.restoreLayout(this._minimizedLayout as DockPanel_.ILayoutConfig);
this._minimizedLayout = null;
}
}
private _locked: boolean = false;
private _maximized: boolean = false;
private _minimizedLayout: DockPanel_.ILayoutConfig | null = null;
private _commands: CommandRegistry;
}
export
class ContentWidget extends Widget {
static createNode(): HTMLElement {
let node = document.createElement('div');
let content = document.createElement('div');
let input = document.createElement('input');
input.placeholder = 'Placeholder...';
content.appendChild(input);
node.appendChild(content);
return node;
}
constructor(name: string) {
super({ node: ContentWidget.createNode() });
this.setFlag(Widget.Flag.DisallowLayout);
this.addClass('content');
this.title.label = name;
this.title.closable = true;
this.title.caption = `Long description for: ${name}`;
}
get inputNode(): HTMLInputElement {
return this.node.getElementsByTagName('input')[0] as HTMLInputElement;
}
protected onActivateRequest(msg: Message): void {
if (this.isAttached) {
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment