// @flow import * as React from 'react'; import autobind from 'autobind-decorator'; import classnames from 'classnames'; import { SortableContainer, SortableElement, arrayMove } from 'react-sortable-hoc/dist/es6'; import { Dropdown, DropdownButton, DropdownItem } from '../base/dropdown'; import PromptButton from '../base/prompt-button'; import Button from '../base/button'; import Link from '../base/link'; import EnvironmentEditor from '../editors/environment-editor'; import Editable from '../base/editable'; import Modal from '../base/modal'; import ModalBody from '../base/modal-body'; import ModalHeader from '../base/modal-header'; import ModalFooter from '../base/modal-footer'; import * as models from '../../../models'; import { DEBOUNCE_MILLIS } from '../../../common/constants'; import type { Workspace } from '../../../models/workspace'; import type { Environment } from '../../../models/environment'; import * as db from '../../../common/database'; import HelpTooltip from '../help-tooltip'; import Tooltip from '../tooltip'; const ROOT_ENVIRONMENT_NAME = 'Base Environment'; type Props = { activeEnvironmentId: string | null, editorFontSize: number, editorIndentSize: number, editorKeyMap: string, lineWrapping: boolean, render: Function, getRenderContext: Function, nunjucksPowerUserMode: boolean, isVariableUncovered: boolean, }; type State = { workspace: Workspace | null, isValid: boolean, subEnvironments: Array, rootEnvironment: Environment | null, selectedEnvironmentId: string | null, }; const SidebarListItem = SortableElement( ({ environment, activeEnvironment, showEnvironment, changeEnvironmentName }) => { const classes = classnames({ 'env-modal__sidebar-item': true, 'env-modal__sidebar-item--active': activeEnvironment === environment, }); return (
  • ); }, ); const SidebarList = SortableContainer( ({ environments, activeEnvironment, showEnvironment, changeEnvironmentName }) => (
      {environments.map((e, i) => ( ))}
    ), ); @autobind class WorkspaceEnvironmentsEditModal extends React.PureComponent { environmentEditorRef: ?EnvironmentEditor; colorChangeTimeout: any; saveTimeout: any; modal: Modal; editorKey: number; constructor(props: Props) { super(props); this.state = { workspace: null, isValid: true, subEnvironments: [], rootEnvironment: null, selectedEnvironmentId: null, }; this.colorChangeTimeout = null; this.editorKey = 0; } hide() { this.modal && this.modal.hide(); } _setEditorRef(n: ?EnvironmentEditor) { this.environmentEditorRef = n; } _setModalRef(n: Modal | null) { this.modal = n; } async show(workspace: Workspace) { const { activeEnvironmentId } = this.props; // Default to showing the currently active environment if (activeEnvironmentId) { this.setState({ selectedEnvironmentId: activeEnvironmentId }); } await this._load(workspace); this.modal && this.modal.show(); } async _load(workspace: Workspace | null, environmentToSelect: Environment | null = null) { if (!workspace) { console.warn('Failed to reload environment editor without Workspace'); return; } const rootEnvironment = await models.environment.getOrCreateForWorkspace(workspace); const subEnvironments = await models.environment.findByParentId(rootEnvironment._id); let selectedEnvironmentId; if (environmentToSelect) { selectedEnvironmentId = environmentToSelect._id; } else if (this.state.workspace && workspace._id !== this.state.workspace._id) { // We've changed workspaces, so load the root one selectedEnvironmentId = rootEnvironment._id; } else { // We haven't changed workspaces, so try loading the last environment, and fall back // to the root one selectedEnvironmentId = this.state.selectedEnvironmentId || rootEnvironment._id; } this.setState({ workspace, rootEnvironment, subEnvironments, selectedEnvironmentId, }); } async _handleAddEnvironment(isPrivate: boolean = false) { const { rootEnvironment, workspace } = this.state; if (!rootEnvironment) { console.warn('Failed to add environment. Unknown root environment'); return; } const parentId = rootEnvironment._id; const environment = await models.environment.create({ parentId, isPrivate, }); await this._load(workspace, environment); } async _handleShowEnvironment(environment: Environment) { // Don't allow switching if the current one has errors if (this.environmentEditorRef && !this.environmentEditorRef.isValid()) { return; } if (environment === this._getActiveEnvironment()) { return; } const { workspace } = this.state; await this._load(workspace, environment); } async _handleDeleteEnvironment(environment: Environment) { const { rootEnvironment, workspace } = this.state; // Don't delete the root environment if (environment === rootEnvironment) { return; } // Delete the current one, then activate the root environment await models.environment.remove(environment); await this._load(workspace, rootEnvironment); } async _handleChangeEnvironmentName(environment: Environment, name: string) { const { workspace } = this.state; // NOTE: Fetch the environment first because it might not be up to date. // For example, editing the body updates silently. const realEnvironment = await models.environment.getById(environment._id); if (!realEnvironment) { return; } await models.environment.update(realEnvironment, { name }); await this._load(workspace); } _handleChangeEnvironmentColor(environment: Environment, color: string | null) { clearTimeout(this.colorChangeTimeout); this.colorChangeTimeout = setTimeout(async () => { const { workspace } = this.state; await models.environment.update(environment, { color }); await this._load(workspace); }, DEBOUNCE_MILLIS); } _didChange() { this._saveChanges(); // Call this last in case component unmounted const isValid = this.environmentEditorRef ? this.environmentEditorRef.isValid() : false; if (this.state.isValid !== isValid) { this.setState({ isValid }); } } _getActiveEnvironment(): Environment | null { const { selectedEnvironmentId, subEnvironments, rootEnvironment } = this.state; if (rootEnvironment && rootEnvironment._id === selectedEnvironmentId) { return rootEnvironment; } else { return subEnvironments.find(e => e._id === selectedEnvironmentId) || null; } } _handleUnsetColor(environment: Environment) { this._handleChangeEnvironmentColor(environment, null); } componentDidMount() { db.onChange(async changes => { const { selectedEnvironmentId } = this.state; for (const change of changes) { const [ _, // eslint-disable-line no-unused-vars doc, fromSync, ] = change; // Force an editor refresh if any changes from sync come in if (doc._id === selectedEnvironmentId && fromSync) { this.editorKey = doc.modified; await this._load(this.state.workspace); } } }); } async _handleSortEnd(results: { oldIndex: number, newIndex: number, collection: Array, }) { const { oldIndex, newIndex } = results; if (newIndex === oldIndex) { return; } const { subEnvironments } = this.state; const newSubEnvironments = arrayMove(subEnvironments, oldIndex, newIndex); this.setState({ subEnvironments: newSubEnvironments }); // Do this last so we don't block the sorting db.bufferChanges(); for (let i = 0; i < newSubEnvironments.length; i++) { const environment = newSubEnvironments[i]; await models.environment.update(environment, { metaSortKey: i }); } db.flushChanges(); } async _handleClickColorChange(environment: Environment) { let el = document.querySelector('#env-color-picker'); // Remove existing child so we reset the event handlers. This // was easier than trying to clean them up later. if (el && el.parentNode) { el.parentNode.removeChild(el); } el = document.createElement('input'); el.id = 'env-color-picker'; el.type = 'color'; document.body && document.body.appendChild(el); let color = environment.color || '#7d69cb'; if (!environment.color) { await this._handleChangeEnvironmentColor(environment, color); } el.setAttribute('value', color); el.addEventListener('input', (e: Event) => { if (e.target instanceof HTMLInputElement) { this._handleChangeEnvironmentColor(environment, e.target && e.target.value); } }); el.click(); } _saveChanges() { // Only save if it's valid if (!this.environmentEditorRef || !this.environmentEditorRef.isValid()) { return; } let data; try { data = this.environmentEditorRef.getValue(); } catch (err) { // Invalid JSON probably return; } const activeEnvironment = this._getActiveEnvironment(); if (activeEnvironment) { clearTimeout(this.saveTimeout); this.saveTimeout = setTimeout(() => { models.environment.update(activeEnvironment, { data }); }, DEBOUNCE_MILLIS * 4); } } render() { const { editorFontSize, editorIndentSize, editorKeyMap, lineWrapping, render, getRenderContext, nunjucksPowerUserMode, isVariableUncovered, } = this.props; const { subEnvironments, rootEnvironment, isValid } = this.state; const activeEnvironment = this._getActiveEnvironment(); return ( Manage Environments
  • Sub Environments

    Environment Private Environment

    {rootEnvironment === activeEnvironment ? ( ROOT_ENVIRONMENT_NAME ) : ( activeEnvironment && this._handleChangeEnvironmentName(activeEnvironment, name) } value={activeEnvironment ? activeEnvironment.name : ''} /> )}

    {activeEnvironment && rootEnvironment !== activeEnvironment ? ( {activeEnvironment.color && ( )} Color {activeEnvironment.color ? 'Change Color' : 'Assign Color'} Unset Color ) : null} {activeEnvironment && rootEnvironment !== activeEnvironment ? ( ) : null}
    * Environment data can be used for  Nunjucks Templating {' '} in your requests
    ); } } export default WorkspaceEnvironmentsEditModal;