Skip to content
Snippets Groups Projects
Select Git revision
  • 75ccded612a57c3553af5f0e9e6ea6e4775d2559
  • master default protected
  • rednatco-v2
  • base-pairs-ladder
  • rednatco
  • test
  • ntc-tube-uniform-color
  • ntc-tube-missing-atoms
  • restore-vertex-array-per-program
  • watlas2
  • dnatco_new
  • cleanup-old-nodejs
  • webmmb
  • fix_auth_seq_id
  • update_deps
  • ext_dev
  • ntc_balls
  • nci-2
  • plugin
  • bugfix-0.4.5
  • nci
  • v0.5.0-dev.1
  • v0.4.5
  • v0.4.4
  • v0.4.3
  • v0.4.2
  • v0.4.1
  • v0.4.0
  • v0.3.12
  • v0.3.11
  • v0.3.10
  • v0.3.9
  • v0.3.8
  • v0.3.7
  • v0.3.6
  • v0.3.5
  • v0.3.4
  • v0.3.3
  • v0.3.2
  • v0.3.1
  • v0.3.0
41 results

base.tsx

Blame
  • base.tsx 4.76 KiB
    /**
     * Copyright (c) 2018-2020 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 * as React from 'react';
    import { Observable, Subscription } from 'rxjs';
    import { PluginContext } from '../mol-plugin/context';
    import { Button } from './controls/common';
    import { ArrowRight, ArrowDropDown } from '@material-ui/icons';
    import { Icon } from './controls/icons';
    
    export const PluginReactContext = React.createContext(void 0 as any as PluginContext);
    
    export abstract class PluginUIComponent<P = {}, S = {}, SS = {}> extends React.Component<P, S, SS> {
        static contextType = PluginReactContext;
        readonly plugin: PluginContext;
    
        private subs: Subscription[] | undefined = void 0;
    
        protected subscribe<T>(obs: Observable<T>, action: (v: T) => void) {
            if (typeof this.subs === 'undefined') this.subs = [];
            this.subs.push(obs.subscribe(action));
        }
    
        componentWillUnmount() {
            if (!this.subs) return;
            for (const s of this.subs) s.unsubscribe();
            this.subs = void 0;
        }
    
        protected init?(): void;
    
        constructor(props: P, context?: any) {
            super(props, context);
            this.plugin = context;
            if (this.init) this.init();
        }
    }
    
    export abstract class PurePluginUIComponent<P = {}, S = {}, SS = {}> extends React.PureComponent<P, S, SS> {
        static contextType = PluginReactContext;
        readonly plugin: PluginContext;
    
        private subs: Subscription[] | undefined = void 0;
    
        protected subscribe<T>(obs: Observable<T>, action: (v: T) => void) {
            if (typeof this.subs === 'undefined') this.subs = [];
            this.subs.push(obs.subscribe(action));
        }
    
        componentWillUnmount() {
            if (!this.subs) return;
            for (const s of this.subs) s.unsubscribe();
            this.subs = void 0;
        }
    
        protected init?(): void;
    
        constructor(props: P, context?: any) {
            super(props, context);
            this.plugin = context;
            if (this.init) this.init();
        }
    }
    
    export type _Props<C extends React.Component> = C extends React.Component<infer P> ? P : never
    export type _State<C extends React.Component> = C extends React.Component<any, infer S> ? S : never
    
    //
    
    export type CollapsableProps = { initiallyCollapsed?: boolean, header?: string }
    export type CollapsableState = {
        isCollapsed: boolean,
        header: string,
        description?: string,
        isHidden?: boolean,
        brand?: { svg?: React.FC, accent: 'cyan' | 'red' | 'gray' | 'green' | 'purple' | 'blue' | 'orange' }
    }
    
    export abstract class CollapsableControls<P = {}, S = {}, SS = {}> extends PluginUIComponent<P & CollapsableProps, S & CollapsableState, SS> {
        toggleCollapsed = () => {
            this.setState({ isCollapsed: !this.state.isCollapsed } as (S & CollapsableState));
        }
    
        componentDidUpdate(prevProps: P & CollapsableProps) {
            if(this.props.initiallyCollapsed !== undefined && prevProps.initiallyCollapsed !== this.props.initiallyCollapsed) {
                this.setState({ isCollapsed: this.props.initiallyCollapsed as any });
            }
        }
    
        protected abstract defaultState(): (S & CollapsableState)
        protected abstract renderControls(): JSX.Element | null
    
        render() {
            if (this.state.isHidden) return null;
    
            const wrapClass = this.state.isCollapsed
                ? 'msp-transform-wrapper msp-transform-wrapper-collapsed'
                : 'msp-transform-wrapper';
    
            return <div className={wrapClass}>
                <div className='msp-transform-header'>
                    <Button icon={this.state.brand ? void 0 : this.state.isCollapsed ? ArrowRight : ArrowDropDown} noOverflow onClick={this.toggleCollapsed}
                        className={this.state.brand ? `msp-transform-header-brand msp-transform-header-brand-${this.state.brand.accent}` : void 0} title={`Click to ${this.state.isCollapsed ? 'expand' : 'collapse'}`}>
                        {/* {this.state.brand && <div className={`msp-accent-bg-${this.state.brand.accent}`}>{this.state.brand.svg ? <Icon svg={this.state.brand.svg} /> : this.state.brand.name}</div>} */}
                        <Icon svg={this.state.brand?.svg} inline />
                        {this.state.header}
                        <small style={{ margin: '0 6px' }}>{this.state.isCollapsed ? '' : this.state.description}</small>
                    </Button>
                </div>
                {!this.state.isCollapsed && this.renderControls()}
            </div>;
        }
    
        constructor(props: P & CollapsableProps, context?: any) {
            super(props, context);
    
            const state = this.defaultState();
            if (props.initiallyCollapsed !== undefined) state.isCollapsed = props.initiallyCollapsed;
            if (props.header !== undefined) state.header = props.header;
            this.state = state;
        }
    }