Farmbot-Web-App/frontend/folders/component.tsx

342 lines
11 KiB
TypeScript
Raw Normal View History

import React from "react";
2019-12-19 09:26:40 -07:00
import {
BlurableInput,
2019-12-19 18:30:02 -07:00
EmptyStateWrapper,
EmptyStateGraphic,
2019-12-20 09:17:55 -07:00
ColorPicker,
2019-12-19 09:26:40 -07:00
} from "../ui";
2019-12-13 14:26:57 -07:00
import {
FolderUnion,
FolderItemProps,
FolderNodeProps,
FolderProps,
2019-12-18 12:28:15 -07:00
FolderState,
AddFolderBtn,
AddSequenceProps,
2019-12-20 09:17:55 -07:00
ToggleFolderBtnProps,
FolderNodeState,
FolderPanelTopProps,
2019-12-21 12:47:19 -07:00
SequenceDropAreaProps,
FolderButtonClusterProps,
FolderNameInputProps,
SequenceDropAreaState,
2020-01-03 13:13:49 -07:00
} from "./interfaces";
2019-12-05 15:40:06 -07:00
import {
createFolder,
deleteFolder,
setFolderName,
toggleFolderOpenState,
toggleFolderEditState,
toggleAll,
updateSearchTerm,
addNewSequenceToFolder,
2019-12-11 12:22:48 -07:00
moveSequence,
2019-12-20 09:17:55 -07:00
setFolderColor,
2019-12-21 12:47:19 -07:00
dropSequence,
sequenceEditMaybeSave,
2019-12-05 15:40:06 -07:00
} from "./actions";
import { Link } from "../link";
2019-12-19 18:30:02 -07:00
import { urlFriendly, lastUrlChunk } from "../util";
2019-12-19 09:26:40 -07:00
import {
2020-02-28 09:35:32 -07:00
setActiveSequenceByName,
2019-12-19 09:26:40 -07:00
} from "../sequences/set_active_sequence_by_name";
2019-12-19 18:30:02 -07:00
import { Popover } from "@blueprintjs/core";
2019-12-12 15:56:57 -07:00
import { t } from "../i18next_wrapper";
2019-12-19 18:30:02 -07:00
import { Content } from "../constants";
import { StepDragger, NULL_DRAGGER_ID } from "../draggable/step_dragger";
import { variableList } from "../sequences/locals_list/variable_support";
2019-12-21 12:47:19 -07:00
import { UUID } from "../resources/interfaces";
2020-04-13 19:15:11 -06:00
import { SearchField } from "../ui/search_field";
2019-12-18 16:42:16 -07:00
2019-12-20 09:17:55 -07:00
export const FolderListItem = (props: FolderItemProps) => {
2019-12-21 12:47:19 -07:00
const { sequence, movedSequenceUuid } = props;
2019-12-19 18:30:02 -07:00
const seqName = sequence.body.name;
const url = `/app/sequences/${urlFriendly(seqName) || ""}`;
2019-12-21 12:47:19 -07:00
const moveSource = movedSequenceUuid === sequence.uuid ? "move-source" : "";
2019-12-19 18:30:02 -07:00
const nameWithSaveIndicator = seqName + (sequence.specialStatus ? "*" : "");
const active = lastUrlChunk() === urlFriendly(seqName) ? "active" : "";
return <StepDragger
dispatch={props.dispatch}
step={{
kind: "execute",
args: { sequence_id: props.sequence.body.id || 0 },
body: variableList(props.variableData)
}}
intent="step_splice"
2019-12-23 15:38:48 -07:00
draggerId={NULL_DRAGGER_ID}
resourceUuid={sequence.uuid}>
2019-12-21 12:47:19 -07:00
<li className={`sequence-list-item ${active} ${moveSource}`}
draggable={true}>
<ColorPicker
current={sequence.body.color || "gray"}
onChange={color => sequenceEditMaybeSave(sequence, { color })} />
<Link to={url} key={sequence.uuid} onClick={setActiveSequenceByName}>
2019-12-19 18:30:02 -07:00
<p>{nameWithSaveIndicator}</p>
2019-12-21 12:47:19 -07:00
</Link>
<div className="sequence-list-item-icons">
{props.inUse &&
<i className="in-use fa fa-hdd-o" title={t(Content.IN_USE)} />}
2020-02-26 11:10:59 -07:00
<i className="fa fa-arrows-v"
2019-12-21 12:47:19 -07:00
onMouseDown={() => props.startSequenceMove(sequence.uuid)}
onMouseUp={() => props.toggleSequenceMove(sequence.uuid)} />
</div>
</li>
2020-01-03 13:04:45 -07:00
</StepDragger>;
};
2019-12-20 09:17:55 -07:00
const ToggleFolderBtn = (props: ToggleFolderBtnProps) => {
2020-02-28 09:34:28 -07:00
return <button className="fb-button gray"
title={t("toggle folder open")}
onClick={props.onClick}>
2019-12-21 12:47:19 -07:00
<i className={`fa fa-chevron-${props.expanded ? "right" : "down"}`} />
2019-12-18 13:14:37 -07:00
</button>;
2019-12-18 12:48:48 -07:00
};
2019-12-21 12:47:19 -07:00
const AddFolderBtn = ({ folder, close }: AddFolderBtn) => {
return <button
className="fb-button green"
2020-02-28 09:34:28 -07:00
title={t("Create subfolder")}
2019-12-23 15:38:48 -07:00
onClick={() => { close?.(); createFolder(folder); }}>
2019-12-19 18:30:02 -07:00
<div className="fa-stack fa-2x" title={"Create Subfolder"}>
<i className="fa fa-folder fa-stack-2x" />
<i className="fa fa-plus fa-stack-1x" />
</div>
2019-12-18 13:14:37 -07:00
</button>;
};
2019-12-21 12:47:19 -07:00
const AddSequenceBtn = ({ folderId, close }: AddSequenceProps) => {
2019-12-19 09:26:40 -07:00
return <button
className="fb-button green"
2020-02-28 09:34:28 -07:00
title={t("add new sequence")}
2019-12-21 12:47:19 -07:00
onClick={() => { close?.(); addNewSequenceToFolder(folderId); }}>
2019-12-19 18:30:02 -07:00
<div className="fa-stack fa-2x">
<i className="fa fa-server fa-stack-2x" />
<i className="fa fa-plus fa-stack-1x" />
</div>
2019-12-18 13:14:37 -07:00
</button>;
};
2019-12-21 12:47:19 -07:00
export const FolderButtonCluster =
({ node, close }: FolderButtonClusterProps) => {
return <div className="folder-button-cluster">
<button
className="fb-button red"
2020-02-28 09:34:28 -07:00
title={t("delete folder")}
2019-12-21 12:47:19 -07:00
onClick={() => deleteFolder(node.id)}>
<i className="fa fa-trash" />
</button>
<button
className="fb-button gray"
2020-02-28 09:34:28 -07:00
title={t("edit folder")}
2019-12-21 12:47:19 -07:00
onClick={() => { close(); toggleFolderEditState(node.id); }}>
<i className="fa fa-pencil" />
</button>
{node.kind !== "terminal" &&
<AddFolderBtn folder={{ parent_id: node.id }} close={close} />}
<AddSequenceBtn folderId={node.id} close={close} />
</div>;
};
2019-12-23 15:38:48 -07:00
export const FolderNameInput = ({ node }: FolderNameInputProps) =>
2019-12-21 12:47:19 -07:00
<div className="folder-name-input">
2019-12-27 11:38:29 -07:00
<BlurableInput value={node.name} autoFocus={true} autoSelect={true}
onCommit={e => {
setFolderName(node.id, e.currentTarget.value);
toggleFolderEditState(node.id);
}} />
2019-12-19 09:26:40 -07:00
<button
2019-12-21 12:47:19 -07:00
className="fb-button green"
2020-02-28 09:34:28 -07:00
title={t("save folder name")}
2019-12-19 09:26:40 -07:00
onClick={() => toggleFolderEditState(node.id)}>
2019-12-21 12:47:19 -07:00
<i className="fa fa-check" />
2019-12-18 13:14:37 -07:00
</button>
2019-12-17 16:37:05 -07:00
</div>;
2019-12-20 09:17:55 -07:00
export class FolderNameEditor
extends React.Component<FolderNodeProps, FolderNodeState> {
state: FolderNodeState = { settingsOpen: false };
2019-12-23 15:38:48 -07:00
close = () => this.setState({ settingsOpen: false });
2019-12-20 09:17:55 -07:00
render() {
const { node } = this.props;
const settingsOpenClass = this.state.settingsOpen ? "open" : "";
2019-12-21 12:47:19 -07:00
return <div className={"folder-list-item"}>
2019-12-20 09:17:55 -07:00
<i className={`fa fa-chevron-${node.open ? "down" : "right"}`}
title={"Open/Close Folder"}
2019-12-21 12:47:19 -07:00
onClick={() => toggleFolderOpenState(node.id)} />
2019-12-20 09:17:55 -07:00
<ColorPicker
saucerIcon={"fa-folder"}
current={node.color}
onChange={color => setFolderColor(node.id, color)} />
2019-12-21 12:47:19 -07:00
<div className="folder-name">
2019-12-20 09:17:55 -07:00
{node.editing
2019-12-21 12:47:19 -07:00
? <FolderNameInput node={node} />
: <p>{node.name}</p>}
2019-12-20 09:17:55 -07:00
</div>
<Popover className="folder-settings-icon" usePortal={false}
isOpen={this.state.settingsOpen}>
<i className={`fa fa-ellipsis-v ${settingsOpenClass}`}
onClick={() =>
this.setState({ settingsOpen: !this.state.settingsOpen })} />
2019-12-23 15:38:48 -07:00
<FolderButtonCluster {...this.props} close={this.close} />
2019-12-20 09:17:55 -07:00
</Popover>
</div>;
}
}
const FolderNode = (props: FolderNodeProps) => {
const { node, sequences } = props;
2019-12-20 09:17:55 -07:00
const sequenceItems = node.content.map(seqUuid =>
<FolderListItem
2019-12-19 18:30:02 -07:00
sequence={sequences[seqUuid]}
key={"F" + seqUuid}
dispatch={props.dispatch}
variableData={props.sequenceMetas[seqUuid]}
inUse={!!props.resourceUsage[seqUuid]}
2019-12-21 12:47:19 -07:00
toggleSequenceMove={props.toggleSequenceMove}
startSequenceMove={props.startSequenceMove}
movedSequenceUuid={props.movedSequenceUuid} />);
2019-12-04 15:42:27 -07:00
2019-12-20 09:17:55 -07:00
const childFolders: FolderUnion[] = node.children || [];
const folderNodes = childFolders.map(folder =>
<FolderNode
node={folder}
key={folder.id}
sequences={sequences}
dispatch={props.dispatch}
sequenceMetas={props.sequenceMetas}
resourceUsage={props.resourceUsage}
movedSequenceUuid={props.movedSequenceUuid}
2019-12-21 12:47:19 -07:00
toggleSequenceMove={props.toggleSequenceMove}
startSequenceMove={props.startSequenceMove}
2019-12-20 09:17:55 -07:00
onMoveEnd={props.onMoveEnd} />);
2019-12-19 18:30:02 -07:00
return <div className="folder">
<FolderNameEditor {...props} />
2019-12-20 09:17:55 -07:00
{!!node.open && <ul className="in-folder-sequences">{sequenceItems}</ul>}
2019-12-21 12:47:19 -07:00
<SequenceDropArea
dropAreaVisible={!!props.movedSequenceUuid}
onMoveEnd={props.onMoveEnd}
toggleSequenceMove={props.toggleSequenceMove}
folderId={node.id}
folderName={node.name} />
2019-12-20 09:17:55 -07:00
{!!node.open && folderNodes}
2019-12-04 16:26:16 -07:00
</div>;
2019-12-04 15:42:27 -07:00
};
2019-12-21 12:47:19 -07:00
export class SequenceDropArea
extends React.Component<SequenceDropAreaProps, SequenceDropAreaState> {
state: SequenceDropAreaState = { hovered: false };
render() {
const { dropAreaVisible, folderId, onMoveEnd, folderName } = this.props;
const visible = dropAreaVisible ? "visible" : "";
const hovered = this.state.hovered ? "hovered" : "";
return <div
className={`folder-drop-area ${visible} ${hovered}`}
onClick={() => onMoveEnd(folderId)}
onDrop={e => {
this.setState({ hovered: false });
dropSequence(folderId)(e);
this.props.toggleSequenceMove();
}}
onDragOver={e => e.preventDefault()}
onDragEnter={() => this.setState({ hovered: true })}
onDragLeave={() => this.setState({ hovered: false })}>
{folderId
? `${t("Move into")} ${folderName}`
: t("Move out of folders")}
</div>;
}
}
2019-12-13 14:26:57 -07:00
export class Folders extends React.Component<FolderProps, FolderState> {
2019-12-18 13:14:37 -07:00
state: FolderState = { toggleDirection: false };
2019-12-05 15:40:06 -07:00
2019-12-20 09:17:55 -07:00
Graph = () => {
2019-12-19 18:30:02 -07:00
return <div className="folders">
2019-12-12 15:56:57 -07:00
{this.props.rootFolder.folders.map(grandparent => {
2019-12-04 15:42:27 -07:00
return <FolderNode
node={grandparent}
key={grandparent.id}
2019-12-19 18:30:02 -07:00
dispatch={this.props.dispatch}
sequenceMetas={this.props.sequenceMetas}
resourceUsage={this.props.resourceUsage}
movedSequenceUuid={this.state.movedSequenceUuid}
2019-12-21 12:47:19 -07:00
toggleSequenceMove={this.toggleSequenceMove}
startSequenceMove={this.startSequenceMove}
onMoveEnd={this.endSequenceMove}
2019-12-04 15:42:27 -07:00
sequences={this.props.sequences} />;
})}
2019-11-22 10:43:30 -07:00
</div>;
}
2019-12-05 15:40:06 -07:00
toggleAll = () => {
toggleAll(this.state.toggleDirection);
this.setState({ toggleDirection: !this.state.toggleDirection });
}
2019-12-21 12:47:19 -07:00
startSequenceMove = (seqUuid: UUID) => this.setState({
movedSequenceUuid: seqUuid,
stashedUuid: this.state.movedSequenceUuid,
})
toggleSequenceMove = (seqUuid?: UUID) => this.setState({
movedSequenceUuid: this.state.stashedUuid ? undefined : seqUuid,
})
endSequenceMove = (folderId: number) => {
moveSequence(this.state.movedSequenceUuid || "", folderId);
this.setState({ movedSequenceUuid: undefined });
}
2019-12-20 09:17:55 -07:00
rootSequences = () => this.props.rootFolder.noFolder.map(seqUuid =>
<FolderListItem
2019-12-19 18:30:02 -07:00
key={seqUuid}
dispatch={this.props.dispatch}
variableData={this.props.sequenceMetas[seqUuid]}
inUse={!!this.props.resourceUsage[seqUuid]}
sequence={this.props.sequences[seqUuid]}
2019-12-21 12:47:19 -07:00
toggleSequenceMove={this.toggleSequenceMove}
startSequenceMove={this.startSequenceMove}
movedSequenceUuid={this.state.movedSequenceUuid} />);
render() {
2019-12-19 18:30:02 -07:00
return <div className="folders-panel">
2019-12-20 09:17:55 -07:00
<FolderPanelTop
searchTerm={this.props.searchTerm}
toggleDirection={this.state.toggleDirection}
toggleAll={this.toggleAll} />
2019-12-19 18:30:02 -07:00
<EmptyStateWrapper
notEmpty={Object.values(this.props.sequences).length > 0
|| this.props.rootFolder.folders.length > 0}
graphic={EmptyStateGraphic.sequences}
title={t("No Sequences.")}
text={Content.NO_SEQUENCES}>
<ul className="sequences-not-in-folders">
{this.rootSequences()}
</ul>
2019-12-21 12:47:19 -07:00
<SequenceDropArea
dropAreaVisible={!!this.state.movedSequenceUuid}
onMoveEnd={this.endSequenceMove}
toggleSequenceMove={this.toggleSequenceMove}
folderId={0}
folderName={"none"} />
2019-12-19 18:30:02 -07:00
<this.Graph />
</EmptyStateWrapper>
</div>;
}
2019-11-22 10:43:30 -07:00
}
2019-12-20 09:17:55 -07:00
export const FolderPanelTop = (props: FolderPanelTopProps) =>
<div className="panel-top with-button">
2020-04-13 19:15:11 -06:00
<SearchField
placeholder={t("Search sequences...")}
searchTerm={props.searchTerm || ""}
onChange={updateSearchTerm} />
2019-12-20 09:17:55 -07:00
<ToggleFolderBtn
expanded={props.toggleDirection}
onClick={props.toggleAll} />
<AddFolderBtn />
<AddSequenceBtn />
</div>;