folder ui updates
parent
f9ac3e659f
commit
a2ae2ea38e
|
@ -3,6 +3,7 @@ $translucent: rgba(0, 0, 0, 0.2);
|
|||
$translucent2: rgba(0, 0, 0, 0.6);
|
||||
$white: #fff;
|
||||
$off_white: #f4f4f4;
|
||||
$lighter_gray: #eee;
|
||||
$light_gray: #ddd;
|
||||
$gray: #ccc;
|
||||
$medium_light_gray: #bcbcbc;
|
||||
|
@ -129,9 +130,36 @@ $panel_light_red: #fff7f6;
|
|||
background: $blue !important;
|
||||
}
|
||||
|
||||
|
||||
.dark-blue,
|
||||
.fun,
|
||||
.saucer-fun {
|
||||
background: $dark_blue !important;
|
||||
}
|
||||
|
||||
.icon-saucer {
|
||||
background: none !important;
|
||||
&.blue {
|
||||
color: $blue;
|
||||
}
|
||||
&.green {
|
||||
color: $green;
|
||||
}
|
||||
&.yellow {
|
||||
color: $yellow;
|
||||
}
|
||||
&.orange {
|
||||
color: $orange;
|
||||
}
|
||||
&.purple {
|
||||
color: $purple;
|
||||
}
|
||||
&.pink {
|
||||
color: $pink;
|
||||
}
|
||||
&.gray {
|
||||
color: $gray;
|
||||
}
|
||||
&.red {
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -394,7 +394,7 @@
|
|||
a {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
i {
|
||||
i &:not(.fa-stack-2x) {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,10 +34,29 @@ body {
|
|||
width: 13rem;
|
||||
background: $dark_gray;
|
||||
}
|
||||
div {
|
||||
.bp3-popover-content,
|
||||
.color-picker-cluster,
|
||||
.color-picker-item-wrapper,
|
||||
.saucer {
|
||||
display: inline-block;
|
||||
padding: 0.4rem;
|
||||
}
|
||||
.color-picker-item {
|
||||
position: relative;
|
||||
.active-border {
|
||||
display: none;
|
||||
}
|
||||
&.active {
|
||||
.active-border {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 1px;
|
||||
transform: scale(1.5);
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bp3-popover.help {
|
||||
|
@ -113,6 +132,21 @@ fieldset {
|
|||
}
|
||||
}
|
||||
|
||||
.icon-saucer {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
color: $dark_gray;
|
||||
cursor: pointer;
|
||||
&.active {
|
||||
border: 2px solid white;
|
||||
}
|
||||
&.hover {
|
||||
border: 2px solid $dark_gray;
|
||||
}
|
||||
}
|
||||
|
||||
.saucer-connector {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
|
|
|
@ -229,8 +229,145 @@
|
|||
}
|
||||
}
|
||||
|
||||
.sequence-list-item {
|
||||
margin-right: 15px;
|
||||
.folders-panel {
|
||||
.panel-top,
|
||||
.folder-button-cluster {
|
||||
i {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.fa-stack {
|
||||
font-size: 1rem;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
.fa-stack-2x {
|
||||
font-size: 2rem;
|
||||
}
|
||||
.fa-stack-1x {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.5rem;
|
||||
margin-left: 0.75rem;
|
||||
filter: drop-shadow(0 0 0.2rem $dark_green);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.non-empty-state {
|
||||
margin-left: -30px;
|
||||
margin-right: -20px;
|
||||
}
|
||||
@media screen and (max-width: 767px) {
|
||||
.non-empty-state {
|
||||
margin-left: -15px;
|
||||
}
|
||||
}
|
||||
.folder-button-cluster {
|
||||
display: flex;
|
||||
i {
|
||||
width: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.folders {
|
||||
.folder > div:not(:first-child), ul {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
.folder-list-item,
|
||||
.sequence-list-item {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid $light_gray;
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
background: $lighter_gray;
|
||||
&.active {
|
||||
border-left: 3px solid $dark_gray;
|
||||
}
|
||||
.fa-chevron-down, .fa-chevron-right {
|
||||
z-index: 2;
|
||||
width: 2rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.folder-settings-icon,
|
||||
.fa-arrows-v {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
.fa-arrows-v, .fa-ellipsis-v {
|
||||
display: none;
|
||||
}
|
||||
&:hover {
|
||||
.fa-arrows-v, .fa-ellipsis-v {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
i {
|
||||
margin: 0;
|
||||
line-height: 2.5rem;
|
||||
width: 3rem;
|
||||
text-align: center;
|
||||
}
|
||||
.saucer, .icon-saucer {
|
||||
position: absolute;
|
||||
margin: 0.5rem;
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
}
|
||||
p {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
.folder-name {
|
||||
width: 100%;
|
||||
margin-right: 3rem;
|
||||
.input {
|
||||
width: 90%;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
}
|
||||
.sequence-list-item-icons {
|
||||
display: flex;
|
||||
margin-right: 3rem;
|
||||
}
|
||||
button {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
&.move-source {
|
||||
border: 1px solid $dark_gray;
|
||||
}
|
||||
&.move-target {
|
||||
background: $white;
|
||||
}
|
||||
}
|
||||
.sequence-list-item {
|
||||
padding-left: 2rem;
|
||||
.saucer {
|
||||
top: 0.55rem;
|
||||
}
|
||||
}
|
||||
.folder-list-item {
|
||||
.bp3-popover-wrapper {
|
||||
position: absolute;
|
||||
}
|
||||
.color-picker {
|
||||
.bp3-popover-target {
|
||||
margin-left: 2.5rem;
|
||||
}
|
||||
.icon-saucer {
|
||||
top: 0;
|
||||
left: 1.5rem;
|
||||
line-height: 1.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.farmware-list-panel,
|
||||
|
|
|
@ -32,7 +32,7 @@ export function StepDragger({ dispatch,
|
|||
children,
|
||||
intent,
|
||||
draggerId }: StepDraggerProps) {
|
||||
return <div
|
||||
return <div className="step-dragger"
|
||||
onDragStart={stepDragEventHandler(dispatch,
|
||||
step,
|
||||
intent,
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
import { mapStateToFolderProps } from "../map_state_to_props";
|
||||
import { buildResourceIndex } from "../../__test_support__/resource_index_builder";
|
||||
import { fakeFolder, fakeSequence } from "../../__test_support__/fake_state/resources";
|
||||
import { fakeState } from "../../__test_support__/fake_state";
|
||||
|
||||
describe("mapStateToFolderProps", () => {
|
||||
it("maps state to props", () => {
|
||||
const f1 = fakeFolder({ name: "@" });
|
||||
const f2 = fakeFolder({ name: "#", parent_id: f1.body.id });
|
||||
const f3 = fakeFolder({ name: "$", parent_id: f2.body.id });
|
||||
const props = mapStateToFolderProps(buildResourceIndex([f1,
|
||||
const state = fakeState();
|
||||
state.resources = buildResourceIndex([f1,
|
||||
f2,
|
||||
f3,
|
||||
fakeSequence({ name: "%", folder_id: f1.body.id }),
|
||||
fakeSequence({ name: "^", folder_id: f2.body.id }),
|
||||
fakeSequence({ name: "&", folder_id: f3.body.id }),
|
||||
fakeSequence({ name: "*", folder_id: undefined }),
|
||||
fakeSequence({ name: "!", folder_id: undefined })]));
|
||||
fakeSequence({ name: "!", folder_id: undefined })]);
|
||||
const props = mapStateToFolderProps(state);
|
||||
expect(props).toBeDefined();
|
||||
expect(props.rootFolder.folders.length).toBe(1);
|
||||
expect(props.rootFolder.noFolder.length).toBe(2);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from "react";
|
||||
import {
|
||||
BlurableInput,
|
||||
Row,
|
||||
Col,
|
||||
ColorPickerCluster,
|
||||
Saucer
|
||||
EmptyStateWrapper,
|
||||
EmptyStateGraphic,
|
||||
Saucer,
|
||||
ColorPicker
|
||||
} from "../ui";
|
||||
import {
|
||||
FolderUnion,
|
||||
|
@ -29,40 +29,44 @@ import {
|
|||
setFolderColor
|
||||
} from "./actions";
|
||||
import { Link } from "../link";
|
||||
import { urlFriendly } from "../util";
|
||||
import { urlFriendly, lastUrlChunk } from "../util";
|
||||
import {
|
||||
setActiveSequenceByName
|
||||
} from "../sequences/set_active_sequence_by_name";
|
||||
import { Popover, Position } from "@blueprintjs/core";
|
||||
import { Popover } from "@blueprintjs/core";
|
||||
import { t } from "../i18next_wrapper";
|
||||
|
||||
type Style = React.StyleHTMLAttributes<HTMLDivElement>["style"];
|
||||
|
||||
const FOLDER_LIST_ITEM: Style = {
|
||||
backgroundColor: "#ddd",
|
||||
borderBottom: "1px solid #aaa",
|
||||
padding: "0.5rem",
|
||||
cursor: "pointer",
|
||||
height: "3.5rem"
|
||||
};
|
||||
const UL_STYLE = { marginBottom: "0px" };
|
||||
const FLEX = { display: "flex" };
|
||||
const FOLDER_NODE_WRAPPER = { marginLeft: 10 };
|
||||
const FOLDER_PANEL_WRAPPER = { marginTop: 0 };
|
||||
import { Content } from "../constants";
|
||||
import { StepDragger, NULL_DRAGGER_ID } from "../draggable/step_dragger";
|
||||
import { variableList } from "../sequences/locals_list/variable_support";
|
||||
|
||||
const FolderListItem = (props: FolderItemProps) => {
|
||||
const { sequence, onClick } = props;
|
||||
const url = `/app/sequences/${urlFriendly(sequence.body.name) || ""}`;
|
||||
const style = props.isMoveTarget ? { border: "1px solid red" } : {};
|
||||
return <li style={{ ...style, ...FOLDER_LIST_ITEM }}>
|
||||
<i onClick={() => onClick(sequence.uuid)} className="fa fa-arrows-v float-right" />
|
||||
<div className={"float-left"}>
|
||||
<Saucer color={sequence.body.color || "gray"} active={false} />
|
||||
</div>
|
||||
const seqName = sequence.body.name;
|
||||
const url = `/app/sequences/${urlFriendly(seqName) || ""}`;
|
||||
const moveTarget = props.isMoveTarget ? "move-source" : "";
|
||||
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"
|
||||
draggerId={NULL_DRAGGER_ID}>
|
||||
<Link to={url} key={sequence.uuid} onClick={setActiveSequenceByName}>
|
||||
{sequence.body.name}
|
||||
<li className={`sequence-list-item ${active} ${moveTarget}`} draggable={true}>
|
||||
<Saucer color={sequence.body.color || "gray"} active={false} />
|
||||
<p>{nameWithSaveIndicator}</p>
|
||||
<div className="sequence-list-item-icons">
|
||||
{props.inUse &&
|
||||
<i className="in-use fa fa-hdd-o" title={t(Content.IN_USE)} />}
|
||||
<i className="fa fa-arrows-v" onClick={() => onClick(sequence.uuid)} />
|
||||
</div>
|
||||
</li>
|
||||
</Link>
|
||||
</li>;
|
||||
</StepDragger>;
|
||||
};
|
||||
|
||||
const ToggleFolderBtn = (p: ToggleFolderBtnProps) => {
|
||||
|
@ -76,9 +80,10 @@ const AddFolderBtn = ({ folder }: AddFolderBtn) => {
|
|||
return <button
|
||||
className="fb-button green"
|
||||
onClick={() => createFolder(folder || {})}>
|
||||
<i
|
||||
title={"Create Subfolder"}
|
||||
className="fa fa-folder" />
|
||||
<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>
|
||||
</button>;
|
||||
};
|
||||
|
||||
|
@ -86,12 +91,15 @@ const AddSequenceBtn = ({ folderId }: AddSequenceProps) => {
|
|||
return <button
|
||||
className="fb-button green"
|
||||
onClick={() => addNewSequenceToFolder(folderId)}>
|
||||
<i className="fa fa-server" />
|
||||
<div className="fa-stack fa-2x">
|
||||
<i className="fa fa-server fa-stack-2x" />
|
||||
<i className="fa fa-plus fa-stack-1x" />
|
||||
</div>
|
||||
</button>;
|
||||
};
|
||||
|
||||
const FolderButtonCluster = ({ node }: FolderNodeProps) => {
|
||||
return <div style={FLEX}>
|
||||
return <div className="folder-button-cluster">
|
||||
<button
|
||||
className="fb-button red"
|
||||
onClick={() => deleteFolder(node.id)}>
|
||||
|
@ -110,51 +118,29 @@ const FolderButtonCluster = ({ node }: FolderNodeProps) => {
|
|||
|
||||
const FolderNameEditor = (props: FolderNodeProps) => {
|
||||
const { node } = props;
|
||||
|
||||
const onCommit = (e: React.SyntheticEvent<HTMLInputElement, Event>) => {
|
||||
const { currentTarget } = e;
|
||||
return setFolderName(node.id, currentTarget.value)
|
||||
.then(() => toggleFolderEditState(node.id));
|
||||
};
|
||||
let namePart: JSX.Element;
|
||||
const toggle = () => toggleFolderOpenState(node.id);
|
||||
const nodeName = props.movedSequenceUuid ?
|
||||
t("CLICK TO MOVE HERE") : node.name;
|
||||
if (node.editing) {
|
||||
namePart = <BlurableInput value={nodeName} onCommit={onCommit} />;
|
||||
} else {
|
||||
namePart = <span onClick={toggle}> {nodeName}</span>;
|
||||
}
|
||||
|
||||
const faIcon = ` fa fa-chevron-${node.open ? "down" : "right"}`;
|
||||
const style = {
|
||||
...FOLDER_LIST_ITEM,
|
||||
...(props.movedSequenceUuid ? { backgroundColor: "#bbb" } : {})
|
||||
};
|
||||
const onClick =
|
||||
props.movedSequenceUuid ? () => props.onMoveEnd(node.id) : () => { };
|
||||
return <div style={style} onClick={onClick}>
|
||||
<i
|
||||
className={"float-left" + faIcon}
|
||||
const moveModeTarget = props.movedSequenceUuid ? "move-target" : "";
|
||||
const nodeName = moveModeTarget ? t("CLICK TO MOVE HERE") : node.name;
|
||||
const onClick = moveModeTarget ? () => props.onMoveEnd(node.id) : () => { };
|
||||
return <div className={`folder-list-item ${moveModeTarget}`}
|
||||
onClick={onClick}>
|
||||
<i className={`fa fa-chevron-${node.open ? "down" : "right"}`}
|
||||
title={"Open/Close Folder"}
|
||||
onClick={toggle} />
|
||||
<div className={"float-left"}>
|
||||
<Popover
|
||||
position={Position.BOTTOM}
|
||||
popoverClassName="colorpicker-menu gray">
|
||||
<i className="fa fa-folder" style={{ color: node.color }} />
|
||||
<ColorPickerCluster
|
||||
current={node.color}
|
||||
onChange={(color) => setFolderColor(node.id, color)} />
|
||||
</Popover>
|
||||
</div>
|
||||
{namePart}
|
||||
<div className={"float-right"}>
|
||||
<Popover>
|
||||
<i className={"fa fa-ellipsis-v"} />
|
||||
<FolderButtonCluster {...props} />
|
||||
</Popover>
|
||||
onClick={() => toggleFolderOpenState(node.id)} />
|
||||
<ColorPicker
|
||||
saucerIcon={"fa-folder"}
|
||||
current={node.color}
|
||||
onChange={color => setFolderColor(node.id, color)} />
|
||||
<div className="folder-name" onClick={() => toggleFolderOpenState(node.id)}>
|
||||
{node.editing
|
||||
? <BlurableInput value={nodeName} onCommit={e =>
|
||||
setFolderName(node.id, e.currentTarget.value)
|
||||
.then(() => toggleFolderEditState(node.id))} />
|
||||
: <p>{nodeName}</p>}
|
||||
</div>
|
||||
<Popover className="folder-settings-icon" usePortal={false}>
|
||||
<i className={"fa fa-ellipsis-v"} />
|
||||
<FolderButtonCluster {...props} />
|
||||
</Popover>
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
@ -163,28 +149,29 @@ const FolderNode = (props: FolderNodeProps) => {
|
|||
|
||||
const names = node
|
||||
.content
|
||||
.map(x => <FolderListItem
|
||||
sequence={sequences[x]}
|
||||
key={"F" + x}
|
||||
.map(seqUuid => <FolderListItem
|
||||
sequence={sequences[seqUuid]}
|
||||
key={"F" + seqUuid}
|
||||
dispatch={props.dispatch}
|
||||
variableData={props.sequenceMetas[seqUuid]}
|
||||
inUse={!!props.resourceUsage[seqUuid]}
|
||||
onClick={props.onMoveStart}
|
||||
isMoveTarget={props.movedSequenceUuid === x} />);
|
||||
isMoveTarget={props.movedSequenceUuid === seqUuid} />);
|
||||
|
||||
const children = <ul style={UL_STYLE}> {names} </ul>;
|
||||
const mapper = (n2: FolderUnion) => <FolderNode
|
||||
node={n2}
|
||||
key={n2.id}
|
||||
sequences={sequences}
|
||||
dispatch={props.dispatch}
|
||||
sequenceMetas={props.sequenceMetas}
|
||||
resourceUsage={props.resourceUsage}
|
||||
movedSequenceUuid={props.movedSequenceUuid}
|
||||
onMoveStart={props.onMoveStart}
|
||||
onMoveEnd={props.onMoveEnd} />;
|
||||
const array: FolderUnion[] = node.children || [];
|
||||
return <div style={FOLDER_NODE_WRAPPER}>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<FolderNameEditor {...props} />
|
||||
</Col>
|
||||
</Row>
|
||||
{!!node.open && children}
|
||||
return <div className="folder">
|
||||
<FolderNameEditor {...props} />
|
||||
{!!node.open && <ul className="in-folder-sequences">{names}</ul>}
|
||||
{!!node.open && array.map(mapper)}
|
||||
</div>;
|
||||
};
|
||||
|
@ -194,11 +181,14 @@ export class Folders extends React.Component<FolderProps, FolderState> {
|
|||
|
||||
Graph = (_props: {}) => {
|
||||
|
||||
return <div>
|
||||
return <div className="folders">
|
||||
{this.props.rootFolder.folders.map(grandparent => {
|
||||
return <FolderNode
|
||||
node={grandparent}
|
||||
key={grandparent.id}
|
||||
dispatch={this.props.dispatch}
|
||||
sequenceMetas={this.props.sequenceMetas}
|
||||
resourceUsage={this.props.resourceUsage}
|
||||
movedSequenceUuid={this.state.movedSequenceUuid}
|
||||
onMoveStart={this.startSequenceMove}
|
||||
onMoveEnd={this.endSequenceMove}
|
||||
|
@ -225,15 +215,18 @@ export class Folders extends React.Component<FolderProps, FolderState> {
|
|||
.props
|
||||
.rootFolder
|
||||
.noFolder
|
||||
.map(x => <FolderListItem
|
||||
key={x}
|
||||
sequence={this.props.sequences[x]}
|
||||
.map(seqUuid => <FolderListItem
|
||||
key={seqUuid}
|
||||
dispatch={this.props.dispatch}
|
||||
variableData={this.props.sequenceMetas[seqUuid]}
|
||||
inUse={!!this.props.resourceUsage[seqUuid]}
|
||||
sequence={this.props.sequences[seqUuid]}
|
||||
onClick={this.startSequenceMove}
|
||||
isMoveTarget={this.state.movedSequenceUuid === x} />);
|
||||
isMoveTarget={this.state.movedSequenceUuid === seqUuid} />);
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<div className="panel-top with-button" style={FOLDER_PANEL_WRAPPER}>
|
||||
return <div className="folders-panel">
|
||||
<div className="panel-top with-button">
|
||||
<div className="thin-search-wrapper">
|
||||
<div className="text-input-wrapper">
|
||||
<i className="fa fa-search" />
|
||||
|
@ -252,10 +245,17 @@ export class Folders extends React.Component<FolderProps, FolderState> {
|
|||
<AddFolderBtn />
|
||||
<AddSequenceBtn />
|
||||
</div>
|
||||
<ul style={UL_STYLE}>
|
||||
{this.rootSequences()}
|
||||
</ul>
|
||||
<this.Graph />
|
||||
<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>
|
||||
<this.Graph />
|
||||
</EmptyStateWrapper>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Color } from "farmbot/dist/corpus";
|
|||
import { TaggedSequence } from "farmbot";
|
||||
import { DeepPartial } from "redux";
|
||||
import { Folder } from "farmbot/dist/resources/api_resources";
|
||||
import { VariableNameSet, UUID } from "../resources/interfaces";
|
||||
|
||||
export interface FolderMeta {
|
||||
open: boolean;
|
||||
|
@ -60,6 +61,9 @@ export interface FolderProps {
|
|||
rootFolder: RootFolderNode;
|
||||
sequences: Record<string, TaggedSequence>;
|
||||
searchTerm: string | undefined;
|
||||
dispatch: Function;
|
||||
resourceUsage: Record<UUID, boolean | undefined>;
|
||||
sequenceMetas: Record<UUID, VariableNameSet | undefined>;
|
||||
}
|
||||
|
||||
export interface FolderState {
|
||||
|
@ -73,12 +77,18 @@ export interface FolderNodeProps {
|
|||
movedSequenceUuid: string | undefined;
|
||||
onMoveStart(sequenceUuid: string): void;
|
||||
onMoveEnd(folderId: number): void;
|
||||
dispatch: Function;
|
||||
resourceUsage: Record<UUID, boolean | undefined>;
|
||||
sequenceMetas: Record<UUID, VariableNameSet | undefined>;
|
||||
}
|
||||
|
||||
export interface FolderItemProps {
|
||||
onClick(sequenceUuid: string): void;
|
||||
sequence: TaggedSequence;
|
||||
isMoveTarget: boolean;
|
||||
dispatch: Function;
|
||||
variableData: VariableNameSet | undefined;
|
||||
inUse: boolean;
|
||||
}
|
||||
|
||||
export interface AddFolderBtn {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { FolderProps } from "./constants";
|
||||
import { selectAllSequences } from "../resources/selectors";
|
||||
import { TaggedSequence } from "farmbot";
|
||||
import { RestResources } from "../resources/interfaces";
|
||||
import { resourceUsageList } from "../resources/in_use";
|
||||
import { Everything } from "../interfaces";
|
||||
type SequenceDict = Record<string, TaggedSequence>;
|
||||
type Reducer = (a: FolderProps["sequences"], b: TaggedSequence) => SequenceDict;
|
||||
|
||||
|
@ -10,12 +11,15 @@ const reduce: Reducer = (a, b) => {
|
|||
return a;
|
||||
};
|
||||
|
||||
export function mapStateToFolderProps(props: RestResources): FolderProps {
|
||||
const x = props.index.sequenceFolders;
|
||||
export function mapStateToFolderProps(props: Everything): FolderProps {
|
||||
const x = props.resources.index.sequenceFolders;
|
||||
|
||||
return {
|
||||
rootFolder: x.filteredFolders ? x.filteredFolders : x.folders,
|
||||
sequences: selectAllSequences(props.index).reduce(reduce, {}),
|
||||
searchTerm: x.searchTerm
|
||||
sequences: selectAllSequences(props.resources.index).reduce(reduce, {}),
|
||||
searchTerm: x.searchTerm,
|
||||
dispatch: props.dispatch,
|
||||
sequenceMetas: props.resources.index.sequenceMetas,
|
||||
resourceUsage: resourceUsageList(props.resources.index.inUse),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ describe("<Sequences/>", () => {
|
|||
getWebAppConfigValue: jest.fn(),
|
||||
menuOpen: false,
|
||||
stepIndex: undefined,
|
||||
folderData: mapStateToFolderProps(fakeState().resources)
|
||||
folderData: mapStateToFolderProps(fakeState())
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
|
|
|
@ -2,12 +2,10 @@ import * as React from "react";
|
|||
import { connect } from "react-redux";
|
||||
import { StepButtonCluster } from "./step_button_cluster";
|
||||
import { SequenceEditorMiddle } from "./sequence_editor_middle";
|
||||
import {
|
||||
Page, Row, LeftPanel, EmptyStateWrapper, EmptyStateGraphic
|
||||
} from "../ui";
|
||||
import { Page, Row, LeftPanel } from "../ui";
|
||||
import { Props } from "./interfaces";
|
||||
import { mapStateToProps } from "./state_to_props";
|
||||
import { ToolTips, Content } from "../constants";
|
||||
import { ToolTips } from "../constants";
|
||||
import { isTaggedSequence } from "../resources/tagged_resources";
|
||||
import { setActiveSequenceByName } from "./set_active_sequence_by_name";
|
||||
import { CenterPanel, RightPanel } from "../ui";
|
||||
|
@ -47,14 +45,7 @@ export class RawSequences extends React.Component<Props, {}> {
|
|||
className={`sequence-list-panel ${activeClasses}`}
|
||||
title={t("Sequences")}
|
||||
helpText={t(ToolTips.SEQUENCE_LIST)}>
|
||||
<EmptyStateWrapper
|
||||
notEmpty={this.props.sequences.length > 0
|
||||
|| this.props.folderData.rootFolder.folders.length > 0}
|
||||
graphic={EmptyStateGraphic.sequences}
|
||||
title={t("No Sequences.")}
|
||||
text={Content.NO_SEQUENCES}>
|
||||
<Folders {...this.props.folderData} />
|
||||
</EmptyStateWrapper>
|
||||
<Folders {...this.props.folderData} dispatch={this.props.dispatch} />
|
||||
</LeftPanel>
|
||||
<CenterPanel
|
||||
className={`sequence-editor-panel ${activeClasses}`}
|
||||
|
|
|
@ -89,6 +89,6 @@ export function mapStateToProps(props: Everything): Props {
|
|||
getWebAppConfigValue: getConfig,
|
||||
menuOpen: props.resources.consumers.sequences.menuOpen,
|
||||
stepIndex: props.resources.consumers.sequences.stepIndex,
|
||||
folderData: mapStateToFolderProps(props.resources)
|
||||
folderData: mapStateToFolderProps(props)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,11 +8,13 @@ interface PickerProps {
|
|||
position?: Position;
|
||||
current: ResourceColor;
|
||||
onChange?: (color: ResourceColor) => void;
|
||||
saucerIcon?: string;
|
||||
}
|
||||
|
||||
interface ColorPickerClusterProps {
|
||||
onChange: (color: ResourceColor) => void;
|
||||
current: ResourceColor;
|
||||
saucerIcon?: string;
|
||||
}
|
||||
|
||||
interface ColorPickerItemProps extends ColorPickerClusterProps {
|
||||
|
@ -21,17 +23,24 @@ interface ColorPickerItemProps extends ColorPickerClusterProps {
|
|||
|
||||
const ColorPickerItem = (props: ColorPickerItemProps) => {
|
||||
const isActive = props.color === props.current;
|
||||
return <div onClick={() => props.onChange(props.color)}>
|
||||
<Saucer color={props.color} active={isActive} />
|
||||
return <div className="color-picker-item-wrapper"
|
||||
onClick={() => props.onChange(props.color)}>
|
||||
{props.saucerIcon
|
||||
? <div className={`color-picker-item ${isActive ? "active" : ""}`}>
|
||||
<i className={`icon-saucer active-border fa ${props.saucerIcon}`} />
|
||||
<i className={`icon-saucer fa ${props.saucerIcon} ${props.color}`} />
|
||||
</div>
|
||||
: <Saucer color={props.color} active={isActive} />}
|
||||
</div>;
|
||||
};
|
||||
|
||||
export const ColorPickerCluster = (props: ColorPickerClusterProps) => {
|
||||
return <div>
|
||||
return <div className="color-picker-cluster">
|
||||
{colors.map((color) => {
|
||||
return <ColorPickerItem
|
||||
key={color}
|
||||
onChange={props.onChange}
|
||||
saucerIcon={props.saucerIcon}
|
||||
current={props.current}
|
||||
color={color} />;
|
||||
})}
|
||||
|
@ -41,11 +50,17 @@ export class ColorPicker extends React.Component<PickerProps, {}> {
|
|||
|
||||
public render() {
|
||||
const cb = this.props.onChange || function () { };
|
||||
return <Popover
|
||||
return <Popover className="color-picker"
|
||||
position={this.props.position || Position.BOTTOM}
|
||||
popoverClassName="colorpicker-menu gray">
|
||||
<Saucer color={this.props.current} />
|
||||
<ColorPickerCluster onChange={cb} current={this.props.current} />
|
||||
{this.props.saucerIcon
|
||||
? <i className={`icon-saucer fa ${this.props.saucerIcon} ${
|
||||
this.props.current}`} />
|
||||
: <Saucer color={this.props.current} />}
|
||||
<ColorPickerCluster
|
||||
onChange={cb}
|
||||
current={this.props.current}
|
||||
saucerIcon={this.props.saucerIcon} />
|
||||
</Popover>;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue