Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 151 additions & 44 deletions packages/openscd/src/addons/menu-tabs/menu-tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,78 +5,185 @@ import {
property,
query,
state,
TemplateResult,
css
css,
} from 'lit-element';

import '@material/mwc-list';
import '@material/mwc-tab';
import '@material/mwc-tab-bar';
import '@material/mwc-menu';
import type { Menu } from '@material/mwc-menu';
import '@material/mwc-button';
import '@material/mwc-icon-button';
import '@material/mwc-icon';

import {
Plugin,
} from "../../plugin.js"

import { Plugin } from '../../plugin.js';

@customElement('oscd-menu-tabs')
export class OscdMenuTabs extends LitElement {

@property({ type: Array }) editors: Plugin[] = [];
_activeEditor: Plugin | undefined;
@property({ type: Object }) get activeEditor() { return this._activeEditor; }
@property({ type: Object }) get activeEditor() {
return this._activeEditor;
}
set activeEditor(editor: Plugin | undefined) {
this._activeEditor = editor;
const editorIndex = this.editors.findIndex(e => e.name === editor?.name && e.src === editor?.src);
const editorIndex = this.editors.findIndex(
e => e.name === editor?.name && e.src === editor?.src
);
this.activeTabIndex = editorIndex > -1 ? editorIndex : 0;
this.requestUpdate();
};
}

@state() private activeTabIndex = 0;
@state() private visibleTabs: Plugin[] = [];
@state() private hiddenTabs: Plugin[] = [];
@query('.app-bar-container') private appBarContainer!: HTMLElement;
@query('mwc-menu') private overflowMenu!: Menu;

firstUpdated() {
this.#calculateVisibleTabs();
window.addEventListener('resize', this.#calculateVisibleTabs);
}

disconnectedCallback() {
window.removeEventListener('resize', this.#calculateVisibleTabs);
super.disconnectedCallback();
}

#calculateVisibleTabs = async () => {
await this.updateComplete;

const visibleTabs: Plugin[] = [];
const hiddenTabs: Plugin[] = [];
let totalWidth = 0;

const measurer = document.createElement('div');
Object.assign(measurer.style, {
position: 'absolute',
visibility: 'hidden',
whiteSpace: 'nowrap',
fontSize: '14px',
padding: '0 20 px',
});
document.body.appendChild(measurer);

try {
for (let i = 0; i < this.editors.length; i++) {
measurer.textContent = this.editors[i].name;
const approximateWidthOtherThanText = 112;
const buttonWidth =
measurer.offsetWidth + approximateWidthOtherThanText;

var availableWidth = this.appBarContainer.offsetWidth;
const isMenuButtonVisible = this.hiddenTabs.length > 0;
if (isMenuButtonVisible) {
availableWidth -= 48;
}
if (totalWidth + buttonWidth <= availableWidth) {
totalWidth += buttonWidth;
visibleTabs.push(this.editors[i]);
} else {
hiddenTabs.push(this.editors[i]);
}
}

render(){
if(this.editors.length === 0){ return html``; }
this.visibleTabs = visibleTabs;
this.hiddenTabs = hiddenTabs;
} finally {
document.body.removeChild(measurer);
}
};

render() {
if (this.activeEditor === undefined && this.editors.length > 0) {
this.#activateTab(0);
}

return html`
<mwc-tab-bar
@MDCTabBar:activated=${this.handleActivatedEditorTab}
activeIndex=${this.activeTabIndex}
>
${ this.editors.map( EditorTab ) }
</mwc-tab-bar>
`
<div class="app-bar-container">
${this.visibleTabs.map(
(editor, index) => html`
<mwc-button
label=${editor.name}
icon=${editor.icon}
?active=${this.activeTabIndex === index}
@click=${() => this.#activateTab(index)}
></mwc-button>
`
)}
${this.hiddenTabs.length > 0
? html`
<mwc-icon-button
icon="more_vert"
@click=${() => this.overflowMenu.show()}
></mwc-icon-button>

<mwc-menu>
${this.hiddenTabs.map(
(editor, index) => html`
<mwc-list-item
graphic="icon"
@click=${() =>
this.#activateTab(this.visibleTabs.length + index)}
>
<span>${editor.name}</span>
<mwc-icon slot="graphic">${editor.icon}</mwc-icon>
</mwc-list-item>
`
)}
</mwc-menu>
`
: ''}
</div>
`;
}

#activateTab(index: number) {
this.activeTabIndex = index;
this._activeEditor = this.editors[index];
this.dispatchEvent(
new CustomEvent(TabActivatedEventKey, {
detail: { editor: this.editors[index] },
composed: true,
bubbles: true,
})
);
}

static styles = css`
mwc-tab {
background-color: var(--primary);
.app-bar-container {
display: flex;
align-items: center;
justify-content: space-between;
height: 36px;
background-color: var(--mdc-theme-primary, #6200ee);
position: relative;
}

mwc-button {
--mdc-theme-on-primary: #174b46;
--mdc-theme-primary: var(--mdc-theme-on-primary);
--mdc-shape-small: 0px;
--mdc-button-horizontal-padding: 24px;
--mdc-typography-button-font-size: 0.9rem;
white-space: nowrap;
}
`

private handleActivatedEditorTab(e: CustomEvent): void {
const tabIndex = e.detail.index;
const editor = this.editors[tabIndex];
this.activeTabIndex = tabIndex;
this.dispatchActivateEditor(editor);
}
mwc-button[active] {
--mdc-theme-on-primary: #d9e0ce;
}

private dispatchActivateEditor( editor: Plugin ){
const newEvent = new CustomEvent(TabActivatedEventKey, {
detail: { editor },
composed: true,
bubbles: true
})
this.dispatchEvent(newEvent)
}
}
mwc-icon-button {
color: #174b46;
}

function EditorTab({ name, icon }: Plugin): TemplateResult {
return html`
<mwc-tab label=${name} icon=${icon || 'edit'}> </mwc-tab>
mwc-menu {
position: absolute;
left: 0;
top: 100%;
}
`;
}

export const TabActivatedEventKey = 'oscd-editor-tab-activated'
export const TabActivatedEventKey = 'oscd-editor-tab-activated';
export type TabActivatedEvent = CustomEvent<TabActivatedEventDetail>;
export type TabActivatedEventDetail = { editor: Plugin }
export type TabActivatedEventDetail = { editor: Plugin };