cncjs
255 строк · 7.7 Кб
1import chainedFunction from 'chained-function';2import classNames from 'classnames';3import ensureArray from 'ensure-array';4import get from 'lodash/get';5import includes from 'lodash/includes';6import isEqual from 'lodash/isEqual';7import pubsub from 'pubsub-js';8import PropTypes from 'prop-types';9import React, { Component } from 'react';10import Sortable from 'react-sortablejs';11import uuid from 'uuid';12import { GRBL, MARLIN, SMOOTHIE, TINYG } from 'app/constants';13import { Button } from 'app/components/Buttons';14import Modal from 'app/components/Modal';15import controller from 'app/lib/controller';16import i18n from 'app/lib/i18n';17import log from 'app/lib/log';18import portal from 'app/lib/portal';19import store from 'app/store';20import Widget from './Widget';21import styles from './widgets.styl';22
23class SecondaryWidgets extends Component {24static propTypes = {25onForkWidget: PropTypes.func.isRequired,26onRemoveWidget: PropTypes.func.isRequired,27onDragStart: PropTypes.func.isRequired,28onDragEnd: PropTypes.func.isRequired29};30
31state = {32widgets: store.get('workspace.container.secondary.widgets')33};34
35forkWidget = (widgetId) => () => {36portal(({ onClose }) => (37<Modal size="xs" onClose={onClose}>38<Modal.Header>39<Modal.Title>40{i18n._('Fork Widget')}41</Modal.Title>42</Modal.Header>43<Modal.Body>44{i18n._('Are you sure you want to fork this widget?')}45</Modal.Body>46<Modal.Footer>47<Button48onClick={onClose}49>50{i18n._('Cancel')}51</Button>52<Button53btnStyle="primary"54onClick={chainedFunction(55() => {56const name = widgetId.split(':')[0];57if (!name) {58log.error(`Failed to fork widget: widgetId=${widgetId}`);59return;60}61
62// Use the same widget settings in a new widget63const forkedWidgetId = `${name}:${uuid.v4()}`;64const defaultSettings = store.get(`widgets["${name}"]`);65const clonedSettings = store.get(`widgets["${widgetId}"]`, defaultSettings);66store.set(`widgets["${forkedWidgetId}"]`, clonedSettings);67
68const widgets = [...this.state.widgets, forkedWidgetId];69this.setState({ widgets: widgets });70
71this.props.onForkWidget(widgetId);72},73onClose
74)}75>76{i18n._('OK')}77</Button>78</Modal.Footer>79</Modal>80));81};82
83removeWidget = (widgetId) => () => {84portal(({ onClose }) => (85<Modal size="xs" onClose={onClose}>86<Modal.Header>87<Modal.Title>88{i18n._('Remove Widget')}89</Modal.Title>90</Modal.Header>91<Modal.Body>92{i18n._('Are you sure you want to remove this widget?')}93</Modal.Body>94<Modal.Footer>95<Button96onClick={onClose}97>98{i18n._('Cancel')}99</Button>100<Button101btnStyle="primary"102onClick={chainedFunction(103() => {104const widgets = this.state.widgets.filter(n => n !== widgetId);105this.setState({ widgets: widgets });106
107if (widgetId.match(/\w+:[\w\-]+/)) {108// Remove forked widget settings109store.unset(`widgets["${widgetId}"]`);110}111
112this.props.onRemoveWidget(widgetId);113},114onClose
115)}116>117{i18n._('OK')}118</Button>119</Modal.Footer>120</Modal>121));122};123
124pubsubTokens = [];125
126widgetMap = {};127
128componentDidMount() {129this.subscribe();130}131
132componentWillUnmount() {133this.unsubscribe();134}135
136shouldComponentUpdate(nextProps, nextState) {137// Do not compare props for performance considerations138return !isEqual(nextState, this.state);139}140
141componentDidUpdate() {142const { widgets } = this.state;143
144// Calling store.set() will merge two different arrays into one.145// Remove the property first to avoid duplication.146store.replace('workspace.container.secondary.widgets', widgets);147}148
149subscribe() {150{ // updateSecondaryWidgets151const token = pubsub.subscribe('updateSecondaryWidgets', (msg, widgets) => {152this.setState({ widgets: widgets });153});154this.pubsubTokens.push(token);155}156}157
158unsubscribe() {159this.pubsubTokens.forEach((token) => {160pubsub.unsubscribe(token);161});162this.pubsubTokens = [];163}164
165expandAll() {166const len = this.state.widgets.length;167for (let i = 0; i < len; ++i) {168const widget = this.widgetMap[this.state.widgets[i]];169const expand = get(widget, 'expand');170if (typeof expand === 'function') {171expand();172}173}174}175
176collapseAll() {177const len = this.state.widgets.length;178for (let i = 0; i < len; ++i) {179const widget = this.widgetMap[this.state.widgets[i]];180const collapse = get(widget, 'collapse');181if (typeof collapse === 'function') {182collapse();183}184}185}186
187render() {188const { className } = this.props;189const widgets = this.state.widgets190.filter(widgetId => {191// e.g. "webcam" or "webcam:d8e6352f-80a9-475f-a4f5-3e9197a48a23"192const name = widgetId.split(':')[0];193if (name === 'grbl' && !includes(controller.loadedControllers, GRBL)) {194return false;195}196if (name === 'marlin' && !includes(controller.loadedControllers, MARLIN)) {197return false;198}199if (name === 'smoothie' && !includes(controller.loadedControllers, SMOOTHIE)) {200return false;201}202if (name === 'tinyg' && !includes(controller.loadedControllers, TINYG)) {203return false;204}205return true;206})207.map(widgetId => (208<div data-widget-id={widgetId} key={widgetId}>209<Widget210ref={node => {211if (node && node.widget) {212this.widgetMap[widgetId] = node.widget;213}214}}215widgetId={widgetId}216onFork={this.forkWidget(widgetId)}217onRemove={this.removeWidget(widgetId)}218sortable={{219handleClassName: 'sortable-handle',220filterClassName: 'sortable-filter'221}}222/>223</div>224));225
226return (227<Sortable228className={classNames(className, styles.widgets)}229options={{230animation: 150,231delay: 0, // Touch and hold delay232group: {233name: 'secondary',234pull: true,235put: ['primary']236},237handle: '.sortable-handle', // Drag handle selector within list items238filter: '.sortable-filter', // Selectors that do not lead to dragging239chosenClass: 'sortable-chosen', // Class name for the chosen item240ghostClass: 'sortable-ghost', // Class name for the drop placeholder241dataIdAttr: 'data-widget-id',242onStart: this.props.onDragStart,243onEnd: this.props.onDragEnd244}}245onChange={(order) => {246this.setState({ widgets: ensureArray(order) });247}}248>249{widgets}250</Sortable>251);252}253}
254
255export default SecondaryWidgets;256