123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
- import { ParamDefinition as PD } from '../mol-util/param-definition';
- import { PluginComponent } from './component';
- import { PluginContext } from './context';
- import { PluginCommands } from './commands';
- const regionStateOptions = [
- ['full', 'Full'] as const,
- ['collapsed', 'Collapsed'] as const,
- ['hidden', 'Hidden'] as const,
- ];
- export type PluginLayoutControlsDisplay = 'outside' | 'portrait' | 'landscape' | 'reactive'
- export const PluginLayoutStateParams = {
- isExpanded: PD.Boolean(false),
- showControls: PD.Boolean(true),
- regionState: PD.Group({
- left: PD.Select('full', regionStateOptions),
- // TODO: support other region states
- // right: PD.Select('full', regionStateOptions),
- // top: PD.Select('full', regionStateOptions),
- // bottom: PD.Select('full', regionStateOptions),
- }),
- controlsDisplay: PD.Value<PluginLayoutControlsDisplay>('outside', { isHidden: true })
- }
- export type PluginLayoutStateProps = PD.Values<typeof PluginLayoutStateParams>
- export type LeftPanelTabName = 'none' | 'root' | 'data' | 'states' | 'settings' | 'help'
- interface RootState {
- top: string | null,
- bottom: string | null,
- left: string | null,
- right: string | null,
- width: string | null,
- height: string | null,
- maxWidth: string | null,
- maxHeight: string | null,
- margin: string | null,
- marginLeft: string | null,
- marginRight: string | null,
- marginTop: string | null,
- marginBottom: string | null,
- scrollTop: number,
- scrollLeft: number,
- position: string | null,
- overflow: string | null,
- viewports: HTMLElement[],
- zIndex: string | null
- }
- export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
- readonly events = {
- updated: this.ev()
- }
- private updateProps(state: Partial<PluginLayoutStateProps>) {
- const prevExpanded = !!this.state.isExpanded;
- this.updateState(state);
- if (this.root && typeof state.isExpanded === 'boolean' && state.isExpanded !== prevExpanded) this.handleExpand();
- this.events.updated.next();
- }
- private root: HTMLElement | undefined;
- private rootState: RootState | undefined = void 0;
- private expandedViewport: HTMLMetaElement;
- setProps(props: Partial<PluginLayoutStateProps>) {
- this.updateState(props);
- }
- setRoot(root: HTMLElement) {
- this.root = root;
- if (this.state.isExpanded) this.handleExpand();
- }
- private getScrollElement() {
- if ((document as any).scrollingElement) return (document as any).scrollingElement;
- if (document.documentElement) return document.documentElement;
- return document.body;
- }
- private handleExpand() {
- try {
- const body = document.getElementsByTagName('body')[0];
- const head = document.getElementsByTagName('head')[0];
- if (!body || !head || !this.root) return;
- if (this.state.isExpanded) {
- const children = head.children;
- const viewports: HTMLElement[] = [];
- let hasExp = false;
- for (let i = 0; i < children.length; i++) {
- if (children[i] === this.expandedViewport) {
- hasExp = true;
- } else if (((children[i] as any).name || '').toLowerCase() === 'viewport') {
- viewports.push(children[i] as any);
- }
- }
- for (let v of viewports) {
- head.removeChild(v);
- }
- if (!hasExp) head.appendChild(this.expandedViewport);
- const s = body.style;
- const doc = this.getScrollElement();
- const scrollLeft = doc.scrollLeft;
- const scrollTop = doc.scrollTop;
- this.rootState = {
- top: s.top, bottom: s.bottom, right: s.right, left: s.left, scrollTop, scrollLeft, position: s.position, overflow: s.overflow, viewports, zIndex: this.root.style.zIndex,
- width: s.width, height: s.height,
- maxWidth: s.maxWidth, maxHeight: s.maxHeight,
- margin: s.margin, marginLeft: s.marginLeft, marginRight: s.marginRight, marginTop: s.marginTop, marginBottom: s.marginBottom
- };
- s.overflow = 'hidden';
- s.position = 'fixed';
- s.top = '0';
- s.bottom = '0';
- s.right = '0';
- s.left = '0';
- s.width = '100%';
- s.height = '100%';
- s.maxWidth = '100%';
- s.maxHeight = '100%';
- s.margin = '0';
- s.marginLeft = '0';
- s.marginRight = '0';
- s.marginTop = '0';
- s.marginBottom = '0';
- // TODO: setting this breaks viewport controls for some reason. Is there a fix?
- // this.root.style.zIndex = '100000';
- } else {
- const children = head.children;
- for (let i = 0; i < children.length; i++) {
- if (children[i] === this.expandedViewport) {
- head.removeChild(this.expandedViewport);
- break;
- }
- }
- if (this.rootState) {
- const t = this.rootState;
- for (let v of t.viewports) {
- head.appendChild(v);
- }
- const s = body.style
- s.top = t.top!;
- s.bottom = t.bottom!;
- s.left = t.left!;
- s.right = t.right!;
- s.width = t.width!;
- s.height = t.height!;
- s.maxWidth = t.maxWidth!;
- s.maxHeight = t.maxHeight!;
- s.margin = t.margin!;
- s.marginLeft = t.marginLeft!;
- s.marginRight = t.marginRight!;
- s.marginTop = t.marginTop!;
- s.marginBottom = t.marginBottom!;
- s.position = t.position!;
- s.overflow = t.overflow || '';
- const doc = this.getScrollElement();
- doc.scrollTop = t.scrollTop;
- doc.scrollLeft = t.scrollLeft;
- this.rootState = void 0;
- this.root.style.zIndex = t.zIndex!;
- }
- }
- } catch (e) {
- const msg = 'Layout change error, you might have to reload the page.'
- this.context.log.error(msg);
- console.error(msg, e);
- }
- }
- constructor(private context: PluginContext) {
- super({ ...PD.getDefaultValues(PluginLayoutStateParams), ...(context.spec.layout && context.spec.layout.initial) });
- PluginCommands.Layout.Update.subscribe(context, e => this.updateProps(e.state));
- // TODO how best make sure it runs on node.js as well as in the browser?
- if (typeof document !== 'undefined') {
- // <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' />
- this.expandedViewport = document.createElement('meta') as any;
- this.expandedViewport.name = 'viewport';
- this.expandedViewport.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0';
- }
- }
- }
|