Skip to content
Snippets Groups Projects
action.tsx 5.16 KiB
/**
 * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
 *
 * @author David Sehnal <david.sehnal@gmail.com>
 */

import * as React from 'react';
import { Transform, State, Transformer } from 'mol-state';
import { StateAction } from 'mol-state/action';
import { PluginCommands } from 'mol-plugin/command';
import { PluginComponent } from './base';
import { ParameterControls, createChangeSubject, ParamChanges } from './controls/parameters';
import { Subject } from 'rxjs';
import { shallowEqual } from 'mol-util/object';

export { ActionContol }

namespace ActionContol {
    export interface Props {
        nodeRef: Transform.Ref,
        state: State,
        action?: StateAction
    }
}

class ActionContol extends PluginComponent<ActionContol.Props, { params: any, initialParams: any, error?: string, busy: boolean, canApply: boolean }> {
    private changes: ParamChanges;
    private busy: Subject<boolean>;

    cell = this.props.state.cells.get(this.props.nodeRef)!;
    parentCell = (this.cell.sourceRef && this.props.state.cells.get(this.cell.sourceRef)) || void 0;

    action: StateAction | Transformer = !this.props.action ? this.cell.transform.transformer : this.props.action
    isUpdate = !this.props.action

    getDefaultParams() {
        if (this.isUpdate) {
            return this.cell.transform.params;
        } else {
            const p = this.action.definition.params;
            if (!p || !p.default) return {};
            const obj = this.cell;
            if (!obj.obj) return {};
            return p.default(obj.obj, this.plugin);
        }
    }

    getParamDefinitions() {
        if (this.isUpdate) {
            const cell = this.cell;
            const def = cell.transform.transformer.definition;

            if (!cell.sourceRef || !def.params || !def.params.controls) return { };
            const src = this.parentCell;
            if (!src || !src.obj) return { };

            return def.params.controls(src.obj, this.plugin);
        } else {
            const p = this.action.definition.params;
            if (!p || !p.controls) return {};
            const cell = this.cell;
            if (!cell.obj) return {};
            return p.controls(cell.obj, this.plugin);
        }
    }

    defaultState() {
        const params = this.getDefaultParams();
        return { error: void 0, params, initialParams: params, busy: false, canApply: !this.isUpdate };
    }

    apply = async () => {
        this.setState({ busy: true, initialParams: this.state.params, canApply: !this.isUpdate });

        try {
            if (Transformer.is(this.action)) {
                await this.plugin.updateTransform(this.props.state, this.props.nodeRef, this.state.params);
            } else {
                await PluginCommands.State.ApplyAction.dispatch(this.plugin, {
                    state: this.props.state,
                    action: this.action.create(this.state.params),
                    ref: this.props.nodeRef
                });
            }
        } finally {
            this.busy.next(false);
        }
    }

    validate(params: any) {
        const def = this.isUpdate ? this.cell.transform.transformer.definition.params : this.action.definition.params;
        if (!def || !def.validate) return;
        const cell = this.cell;
        const error = def.validate(params, this.isUpdate ? this.parentCell!.obj! : cell.obj!, this.plugin);
        return error && error[0];
    }

    init() {
        this.changes = createChangeSubject();
        this.subscribe(this.changes, ({ name, value }) => {
            const params = { ...this.state.params, [name]: value };
            const canApply = this.isUpdate ? !shallowEqual(params, this.state.initialParams) : true;
            this.setState({ params, error: this.validate(params), canApply });
        });

        this.busy = new Subject();
        this.subscribe(this.busy, busy => this.setState({ busy }));
    }

    onEnter = () => {
        if (this.state.error) return;
        this.apply();
    }

    refresh = () => {
        this.setState({ params: this.state.initialParams, canApply: !this.isUpdate });
    }

    state = this.defaultState()

    render() {
        console.log('render', this.props.nodeRef, this.action.id);
        const cell = this.cell;
        if (cell.status !== 'ok' || (this.isUpdate && cell.transform.ref === Transform.RootRef)) return null;

        const action = this.action;

        return <div>
            <div style={{ borderBottom: '1px solid #999', marginBottom: '5px' }}><h3>{(action.definition.display && action.definition.display.name) || action.id}</h3></div>

            <ParameterControls params={this.getParamDefinitions()} values={this.state.params} changes={this.changes} onEnter={this.onEnter} isEnabled={!this.state.busy} />

            <div style={{ textAlign: 'right' }}>
                <span style={{ color: 'red' }}>{this.state.error}</span>
                <button onClick={this.apply} disabled={!this.state.canApply || !!this.state.error || this.state.busy}>{this.isUpdate ? 'Update' : 'Create'}</button>
                <button title='Refresh Params' onClick={this.refresh} disabled={this.state.busy}></button>
            </div>
        </div>
    }
}