Skip to content

Commit

Permalink
GenericLeftPanelControls: bind to leftPanelTabName and regionState
Browse files Browse the repository at this point in the history
  • Loading branch information
midlik committed Jul 9, 2024
1 parent 0185569 commit c7ebdb1
Showing 1 changed file with 39 additions and 13 deletions.
52 changes: 39 additions & 13 deletions src/app/ui/pdbe-left-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import { PluginCommands } from 'Molstar/mol-plugin/commands';
import { PluginContext } from 'Molstar/mol-plugin/context';
import { StateTransform } from 'Molstar/mol-state';
import { ParamDefinition as PD } from 'Molstar/mol-util/param-definition';
import { sleep } from 'Molstar/mol-util/sleep';
import * as React from 'react';
import { Subject } from 'rxjs';
import { BehaviorSubject, Subject } from 'rxjs';
import { PluginCustomState } from '../plugin-custom-state';
import { SegmentTree } from './segment-tree';

Expand Down Expand Up @@ -45,7 +46,7 @@ export class PDBeLeftPanelControls extends PluginUIComponent<{}, { tab: PDBeLeft
}

set = (tab: PDBeLeftPanelTabName) => {
if (this.state.tab === tab) {
if (tab === this.state.tab) {
tab = 'none'; // clicking on active tab should collapse panel
}
this.plugin.behaviors.layout.leftPanelTabName.next(tab as any); // will update state via subscription
Expand Down Expand Up @@ -242,21 +243,30 @@ function resolveIcon(Icon: React.JSXElementConstructor<{}> | string): React.FC {

const NO_TAB = 'none';

function GenericLeftPanelControls(tabs: TabSpec[], defaultTab: string = NO_TAB): React.ComponentClass<{}> {
export function GenericLeftPanelControls(tabs: TabSpec[], options?: { defaultTab?: string, boundBehavior?: (plugin: PluginContext) => BehaviorSubject<string>, onTabChange?: (plugin: PluginContext, tab: string) => any }): React.ComponentClass<{}> {
if (tabs.some(tab => tab.id === NO_TAB)) throw new Error(`Cannot use '${NO_TAB}' as tab id because it is reserved.`);

return class _GenericLeftPanelControls extends PluginUIComponent<{}, { tab: string, dirtyTabs: string[] }> {
readonly boundBehavior = options?.boundBehavior?.(this.plugin);
readonly state = {
tab: defaultTab,
tab: options?.defaultTab ?? this.boundBehavior?.value ?? NO_TAB,
dirtyTabs: [] as string[],
};

setTab = async (tab: string) => {
if (tab === this.state.tab) return; // important to avoid infinite loop when state is bound to `boundBehavior`
this.setDirtyTab(tab, false);
this.setState({ tab }, () => {
this.boundBehavior?.next(tab);
options?.onTabChange?.(this.plugin, tab);
});
};
toggleTab = (tab: string) => {
if (this.state.tab === tab) {
tab = NO_TAB; // clicking on active tab should collapse panel
if (tab === this.state.tab) {
this.setTab(NO_TAB);
} else {
this.setTab(tab);
}
this.setState({ tab });
// this.plugin.behaviors.layout.leftPanelTabName.next(tab as any); // will update state via subscription
};
setDirtyTab = (tab: string, dirty: boolean) => {
if (dirty && !this.state.dirtyTabs.includes(tab)) {
Expand All @@ -265,8 +275,13 @@ function GenericLeftPanelControls(tabs: TabSpec[], defaultTab: string = NO_TAB):
this.setState({ dirtyTabs: this.state.dirtyTabs.filter(t => t !== tab) }); // Remove from dirty tabs
}
};

componentDidMount(): void {
if (this.boundBehavior) {
if (this.boundBehavior.value !== this.state.tab) {
this.boundBehavior.next(this.state.tab);
}
this.subscribe(this.boundBehavior, tab => this.setTab(tab));
}
for (const tab of tabs) {
const dirtySubject = tab.dirtyOn?.(this.plugin);
if (dirtySubject) {
Expand All @@ -275,18 +290,18 @@ function GenericLeftPanelControls(tabs: TabSpec[], defaultTab: string = NO_TAB):
});
}
}
options?.onTabChange?.(this.plugin, this.state.tab);
}

render() {
const currentTabId = this.state.tab;
this.setDirtyTab(currentTabId as string, false);
const currentTab = currentTabId ? tabs.find(t => t.id === currentTabId) : undefined;
const CurrentTabHeader = currentTab?.header;
const CurrentTabBody = currentTab?.body;

const iconForTab = (tab: TabSpec) => {
if (tab.showWhen && !tab.showWhen(this.plugin)) return null;
return <IconButton key={tab.id} title={tab.title} svg={resolveIcon(tab.icon)} toggleState={currentTabId === tab.id}
return <IconButton key={tab.id} title={tab.title} svg={resolveIcon(tab.icon)} toggleState={tab.id === currentTabId}
onClick={() => this.toggleTab(tab.id)} transparent style={{ position: 'relative' }}
extraContent={this.state.dirtyTabs.includes(tab.id) ? <div className='msp-left-panel-controls-button-data-dirty' /> : undefined} />;
};
Expand All @@ -311,6 +326,16 @@ function GenericLeftPanelControls(tabs: TabSpec[], defaultTab: string = NO_TAB):
};
}

async function adjustLeftPanelState(plugin: PluginContext, expanded: boolean) {
await sleep(0); // this ensures PluginCommands.Layout.Update runs after componentDidMount, without this the panel will not collapse when defaultTab is none (not sure why)
if (expanded && plugin.layout.state.regionState.left === 'collapsed') {
await PluginCommands.Layout.Update(plugin, { state: { regionState: { ...plugin.layout.state.regionState, left: 'full' } } });
}
if (!expanded && plugin.layout.state.regionState.left === 'full') {
await PluginCommands.Layout.Update(plugin, { state: { regionState: { ...plugin.layout.state.regionState, left: 'collapsed' } } });
}
}

export const PDBeLeftPanelControls2 = GenericLeftPanelControls([
{
id: 'root',
Expand Down Expand Up @@ -354,5 +379,6 @@ export const PDBeLeftPanelControls2 = GenericLeftPanelControls([
body: FullSettings,
position: 'bottom',
},
]);
// TODO: ensure binding with this.plugin.behaviors.layout.leftPanelTabName, binding with collapsed state, create tab registry and allow only using tab names
], { defaultTab: 'none', boundBehavior: plugin => plugin.behaviors.layout.leftPanelTabName as BehaviorSubject<string>, onTabChange: (plugin, tab) => adjustLeftPanelState(plugin, tab !== NO_TAB) });

// TODO: binding with collapsed state - collapsed when tab not found, create tab registry and allow only using tab names

0 comments on commit c7ebdb1

Please sign in to comment.