2019-12-18 10:22:44 -07:00
|
|
|
import React from "react";
|
2019-12-18 17:31:52 -07:00
|
|
|
import { BlurableInput, Row, Col, ColorPickerCluster, Saucer } 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,
|
2019-12-18 13:44:11 -07:00
|
|
|
AddSequenceProps,
|
|
|
|
ToggleFolderBtnProps
|
2019-12-13 14:26:57 -07:00
|
|
|
} from "./constants";
|
2019-12-05 15:40:06 -07:00
|
|
|
import {
|
|
|
|
createFolder,
|
|
|
|
deleteFolder,
|
|
|
|
setFolderName,
|
|
|
|
toggleFolderOpenState,
|
|
|
|
toggleFolderEditState,
|
2019-12-06 08:47:52 -07:00
|
|
|
toggleAll,
|
2019-12-11 08:52:40 -07:00
|
|
|
updateSearchTerm,
|
|
|
|
addNewSequenceToFolder,
|
2019-12-11 12:22:48 -07:00
|
|
|
moveSequence,
|
|
|
|
setFolderColor
|
2019-12-05 15:40:06 -07:00
|
|
|
} from "./actions";
|
2019-12-11 08:52:40 -07:00
|
|
|
import { Link } from "../link";
|
|
|
|
import { urlFriendly } from "../util";
|
|
|
|
import { setActiveSequenceByName } from "../sequences/set_active_sequence_by_name";
|
2019-12-18 15:46:38 -07:00
|
|
|
import { Popover } from "@blueprintjs/core";
|
2019-12-12 15:56:57 -07:00
|
|
|
import { t } from "../i18next_wrapper";
|
2019-12-04 15:42:27 -07:00
|
|
|
|
2019-12-18 17:04:07 -07:00
|
|
|
const FOLDER_LIST_ITEM: React.StyleHTMLAttributes<HTMLDivElement>["style"] = {
|
2019-12-18 16:02:50 -07:00
|
|
|
backgroundColor: "#ddd",
|
|
|
|
borderBottom: "1px solid #aaa",
|
|
|
|
padding: "0.5rem",
|
2019-12-18 16:12:12 -07:00
|
|
|
cursor: "pointer",
|
2019-12-18 16:02:50 -07:00
|
|
|
height: "3.5rem"
|
|
|
|
};
|
2019-12-18 17:04:07 -07:00
|
|
|
const UL_STYLE = { marginBottom: "0px" };
|
|
|
|
const FLEX = { display: "flex" };
|
|
|
|
const FOLDER_NODE_WRAPPER = { marginLeft: 10 };
|
|
|
|
const FOLDER_PANEL_WRAPPER = { marginTop: 0 };
|
2019-12-18 16:42:16 -07:00
|
|
|
|
2019-12-17 16:37:05 -07:00
|
|
|
const FolderListItem = (props: FolderItemProps) => {
|
2019-12-11 08:52:40 -07:00
|
|
|
const { sequence, onClick } = props;
|
2019-12-17 13:19:17 -07:00
|
|
|
const url = `/app/sequences/${urlFriendly(sequence.body.name) || ""}`;
|
2019-12-18 10:33:23 -07:00
|
|
|
const style = props.isMoveTarget ? { border: "1px solid red" } : {};
|
2019-12-18 17:04:07 -07:00
|
|
|
return <li style={{ ...style, ...FOLDER_LIST_ITEM }}>
|
2019-12-18 16:55:01 -07:00
|
|
|
<i onClick={() => onClick(sequence.uuid)} className="fa fa-arrows-v float-right" />
|
2019-12-18 17:28:24 -07:00
|
|
|
<div className={"float-left"}>
|
2019-12-18 17:31:52 -07:00
|
|
|
<Saucer color={sequence.body.color || "gray"} active={false} />
|
2019-12-18 17:28:24 -07:00
|
|
|
</div>
|
2019-12-17 13:19:17 -07:00
|
|
|
<Link to={url} key={sequence.uuid} onClick={setActiveSequenceByName}>
|
2019-12-18 10:33:23 -07:00
|
|
|
{sequence.body.name}
|
2019-12-11 08:52:40 -07:00
|
|
|
</Link>
|
|
|
|
</li>;
|
2019-12-09 07:48:52 -07:00
|
|
|
};
|
2019-12-04 13:01:49 -07:00
|
|
|
|
2019-12-18 12:48:48 -07:00
|
|
|
const ToggleFolderBtn = (p: ToggleFolderBtnProps) => {
|
|
|
|
const klass = `fa fa-${p.expanded ? "plus" : "minus"}-square`;
|
2019-12-18 17:47:51 -07:00
|
|
|
return <button className="fb-button gray" onClick={p.onClick}>
|
|
|
|
<i className={klass} />
|
2019-12-18 13:14:37 -07:00
|
|
|
</button>;
|
2019-12-18 12:48:48 -07:00
|
|
|
};
|
2019-12-18 13:44:11 -07:00
|
|
|
|
2019-12-17 15:25:08 -07:00
|
|
|
const AddFolderBtn = ({ folder }: AddFolderBtn) => {
|
2019-12-18 17:47:51 -07:00
|
|
|
return <button
|
|
|
|
className="fb-button green"
|
|
|
|
onClick={() => createFolder(folder || {})}>
|
2019-12-18 13:14:37 -07:00
|
|
|
<i
|
|
|
|
title={"Create Subfolder"}
|
|
|
|
className="fa fa-folder" />
|
|
|
|
</button>;
|
2019-12-17 15:25:08 -07:00
|
|
|
};
|
|
|
|
|
2019-12-18 15:46:38 -07:00
|
|
|
const AddSequenceBtn = ({ folderId }: AddSequenceProps) => {
|
2019-12-18 13:14:37 -07:00
|
|
|
return <button className="fb-button green">
|
|
|
|
<i className="fa fa-server" onClick={() => addNewSequenceToFolder(folderId)} />
|
|
|
|
</button>;
|
|
|
|
};
|
2019-12-17 15:25:08 -07:00
|
|
|
|
2019-12-18 10:22:44 -07:00
|
|
|
const FolderButtonCluster = ({ node }: FolderNodeProps) => {
|
2019-12-18 17:04:07 -07:00
|
|
|
return <div style={FLEX}>
|
2019-12-18 17:47:51 -07:00
|
|
|
<button className="fb-button red" onClick={() => deleteFolder(node.id)}>
|
|
|
|
<i className="fa fa-trash" />
|
2019-12-18 13:14:37 -07:00
|
|
|
</button>
|
2019-12-18 17:47:51 -07:00
|
|
|
<button className="fb-button gray" onClick={() => toggleFolderEditState(node.id)}>
|
|
|
|
<i className="fa fa-pencil" />
|
2019-12-18 13:14:37 -07:00
|
|
|
</button>
|
2019-12-18 15:46:38 -07:00
|
|
|
{node.kind !== "terminal" && <AddFolderBtn folder={{ parent_id: node.id }} />}
|
|
|
|
<AddSequenceBtn folderId={node.id} />
|
2019-12-17 16:37:05 -07:00
|
|
|
</div>;
|
|
|
|
};
|
|
|
|
|
2019-12-18 10:22:44 -07:00
|
|
|
const FolderNameEditor = (props: FolderNodeProps) => {
|
2019-12-18 10:33:23 -07:00
|
|
|
const { node } = props;
|
|
|
|
|
2019-12-17 17:06:36 -07:00
|
|
|
const onCommit = (e: React.SyntheticEvent<HTMLInputElement, Event>) => {
|
|
|
|
const { currentTarget } = e;
|
|
|
|
return setFolderName(node.id, currentTarget.value)
|
|
|
|
.then(() => toggleFolderEditState(node.id));
|
|
|
|
};
|
|
|
|
let namePart: JSX.Element;
|
2019-12-18 12:48:48 -07:00
|
|
|
const toggle = () => toggleFolderOpenState(node.id);
|
2019-12-18 17:14:56 -07:00
|
|
|
const nodeName = props.movedSequenceUuid ?
|
|
|
|
t("CLICK TO MOVE HERE") : node.name;
|
2019-12-17 17:06:36 -07:00
|
|
|
if (node.editing) {
|
2019-12-18 17:14:56 -07:00
|
|
|
namePart = <BlurableInput value={nodeName} onCommit={onCommit} />;
|
2019-12-17 17:06:36 -07:00
|
|
|
} else {
|
2019-12-18 17:14:56 -07:00
|
|
|
namePart = <span onClick={toggle}> {nodeName}</span>;
|
2019-12-17 17:06:36 -07:00
|
|
|
}
|
2019-12-18 13:56:55 -07:00
|
|
|
|
2019-12-18 12:48:48 -07:00
|
|
|
const faIcon = ` fa fa-chevron-${node.open ? "down" : "right"}`;
|
2019-12-18 17:14:56 -07:00
|
|
|
const style = {
|
|
|
|
...FOLDER_LIST_ITEM,
|
|
|
|
...(props.movedSequenceUuid ? { backgroundColor: "#bbb" } : {})
|
|
|
|
};
|
|
|
|
const onClick =
|
|
|
|
props.movedSequenceUuid ? () => props.onMoveEnd(node.id) : () => { };
|
|
|
|
return <div style={style} onClick={onClick}>
|
2019-12-18 15:46:38 -07:00
|
|
|
<i
|
2019-12-18 17:04:07 -07:00
|
|
|
className={"float-left" + faIcon}
|
2019-12-18 15:46:38 -07:00
|
|
|
title={"Open/Close Folder"}
|
|
|
|
onClick={toggle} />
|
2019-12-18 16:55:01 -07:00
|
|
|
<div className={"float-left"}>
|
2019-12-18 15:46:38 -07:00
|
|
|
<Popover>
|
|
|
|
<i className="fa fa-folder" style={{ color: node.color }} />
|
|
|
|
<ColorPickerCluster
|
|
|
|
current={node.color}
|
|
|
|
onChange={(color) => setFolderColor(node.id, color)} />
|
|
|
|
</Popover>
|
|
|
|
</div>
|
2019-12-17 17:32:30 -07:00
|
|
|
{namePart}
|
2019-12-18 16:55:01 -07:00
|
|
|
<div className={"float-right"}>
|
2019-12-18 15:46:38 -07:00
|
|
|
<Popover>
|
|
|
|
<i className={"fa fa-ellipsis-v"} />
|
|
|
|
<FolderButtonCluster {...props} />
|
|
|
|
</Popover>
|
|
|
|
</div>
|
2019-12-17 17:06:36 -07:00
|
|
|
</div>;
|
2019-12-17 16:37:05 -07:00
|
|
|
};
|
2019-12-17 17:06:36 -07:00
|
|
|
|
2019-12-11 08:52:40 -07:00
|
|
|
const FolderNode = (props: FolderNodeProps) => {
|
|
|
|
const { node, sequences } = props;
|
2019-12-09 07:48:52 -07:00
|
|
|
|
2019-12-11 07:03:21 -07:00
|
|
|
const names = node
|
|
|
|
.content
|
2019-12-17 16:37:05 -07:00
|
|
|
.map(x => <FolderListItem
|
2019-12-11 08:52:40 -07:00
|
|
|
sequence={sequences[x]}
|
|
|
|
key={"F" + x}
|
|
|
|
onClick={props.onMoveStart}
|
|
|
|
isMoveTarget={props.movedSequenceUuid === x} />);
|
2019-12-04 15:42:27 -07:00
|
|
|
|
2019-12-18 16:42:16 -07:00
|
|
|
const children = <ul style={UL_STYLE}> {names} </ul>;
|
2019-12-11 08:52:40 -07:00
|
|
|
const mapper = (n2: FolderUnion) => <FolderNode
|
|
|
|
node={n2}
|
|
|
|
key={n2.id}
|
|
|
|
sequences={sequences}
|
|
|
|
movedSequenceUuid={props.movedSequenceUuid}
|
|
|
|
onMoveStart={props.onMoveStart}
|
2019-12-17 15:25:08 -07:00
|
|
|
onMoveEnd={props.onMoveEnd} />;
|
2019-12-09 07:48:52 -07:00
|
|
|
const array: FolderUnion[] = node.children || [];
|
2019-12-18 17:04:07 -07:00
|
|
|
return <div style={FOLDER_NODE_WRAPPER}>
|
2019-12-17 13:19:17 -07:00
|
|
|
<Row>
|
2019-12-18 16:42:16 -07:00
|
|
|
<Col xs={12}>
|
2019-12-17 17:06:36 -07:00
|
|
|
<FolderNameEditor {...props} />
|
2019-12-17 13:19:17 -07:00
|
|
|
</Col>
|
|
|
|
</Row>
|
2019-12-05 14:52:01 -07:00
|
|
|
{!!node.open && children}
|
2019-12-17 17:32:30 -07:00
|
|
|
{!!node.open && array.map(mapper)}
|
2019-12-04 16:26:16 -07:00
|
|
|
</div>;
|
2019-12-04 15:42:27 -07:00
|
|
|
};
|
2019-12-03 09:32:27 -07:00
|
|
|
|
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-03 09:32:27 -07:00
|
|
|
Graph = (_props: {}) => {
|
2019-12-12 15:56:57 -07:00
|
|
|
|
2019-11-22 10:43:30 -07:00
|
|
|
return <div>
|
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-11 08:52:40 -07:00
|
|
|
movedSequenceUuid={this.state.movedSequenceUuid}
|
|
|
|
onMoveStart={this.startSequenceMove}
|
|
|
|
onMoveEnd={this.endSequenceMove}
|
2019-12-04 15:42:27 -07:00
|
|
|
sequences={this.props.sequences} />;
|
2019-12-03 09:32:27 -07:00
|
|
|
})}
|
2019-11-22 10:43:30 -07:00
|
|
|
</div>;
|
|
|
|
}
|
2019-12-03 09:32:27 -07:00
|
|
|
|
2019-12-05 15:40:06 -07:00
|
|
|
toggleAll = () => {
|
|
|
|
toggleAll(this.state.toggleDirection);
|
|
|
|
this.setState({ toggleDirection: !this.state.toggleDirection });
|
|
|
|
}
|
|
|
|
|
2019-12-11 08:52:40 -07:00
|
|
|
startSequenceMove = (seqUuid: string) => {
|
|
|
|
this.setState({ movedSequenceUuid: seqUuid });
|
|
|
|
}
|
|
|
|
|
|
|
|
endSequenceMove = (folderId: number) => {
|
|
|
|
moveSequence(this.state.movedSequenceUuid || "", folderId);
|
2019-12-11 09:08:49 -07:00
|
|
|
this.setState({ movedSequenceUuid: undefined });
|
2019-12-11 08:52:40 -07:00
|
|
|
}
|
2019-12-17 15:25:08 -07:00
|
|
|
|
2019-12-13 14:26:57 -07:00
|
|
|
rootSequences = () => this
|
|
|
|
.props
|
|
|
|
.rootFolder
|
|
|
|
.noFolder
|
2019-12-17 16:37:05 -07:00
|
|
|
.map(x => <FolderListItem
|
2019-12-13 14:26:57 -07:00
|
|
|
key={x}
|
|
|
|
sequence={this.props.sequences[x]}
|
|
|
|
onClick={this.startSequenceMove}
|
|
|
|
isMoveTarget={this.state.movedSequenceUuid === x} />);
|
2019-12-11 08:52:40 -07:00
|
|
|
|
2019-12-03 09:32:27 -07:00
|
|
|
render() {
|
2019-12-10 14:46:53 -07:00
|
|
|
return <div>
|
2019-12-18 17:04:07 -07:00
|
|
|
<div className="panel-top with-button" style={FOLDER_PANEL_WRAPPER}>
|
2019-12-18 12:48:48 -07:00
|
|
|
<div className="thin-search-wrapper">
|
|
|
|
<div className="text-input-wrapper">
|
|
|
|
<i className="fa fa-search" />
|
|
|
|
<input
|
|
|
|
value={this.props.searchTerm || ""}
|
2019-12-18 13:14:37 -07:00
|
|
|
onChange={({ currentTarget }) => {
|
|
|
|
updateSearchTerm(currentTarget.value);
|
|
|
|
}}
|
2019-12-18 12:48:48 -07:00
|
|
|
type="text"
|
|
|
|
placeholder={t("Search sequences")} />
|
|
|
|
</div>
|
2019-12-18 11:48:48 -07:00
|
|
|
</div>
|
2019-12-18 12:48:48 -07:00
|
|
|
<ToggleFolderBtn
|
|
|
|
expanded={this.state.toggleDirection}
|
|
|
|
onClick={this.toggleAll} />
|
2019-12-18 15:46:38 -07:00
|
|
|
<AddFolderBtn />
|
|
|
|
<AddSequenceBtn />
|
2019-12-18 11:48:48 -07:00
|
|
|
</div>
|
2019-12-18 17:14:56 -07:00
|
|
|
{/* <DropFolderHereBtn
|
2019-12-17 15:25:08 -07:00
|
|
|
onClick={() => this.endSequenceMove(0)}
|
2019-12-18 17:14:56 -07:00
|
|
|
active={!!this.state.movedSequenceUuid} /> */}
|
2019-12-18 16:42:16 -07:00
|
|
|
<ul style={UL_STYLE}>
|
2019-12-18 16:22:55 -07:00
|
|
|
{this.rootSequences()}
|
|
|
|
</ul>
|
2019-12-18 16:42:16 -07:00
|
|
|
<this.Graph />
|
2019-12-10 14:46:53 -07:00
|
|
|
</div>;
|
2019-12-03 09:32:27 -07:00
|
|
|
}
|
2019-11-22 10:43:30 -07:00
|
|
|
}
|