update saved gardens panel
parent
212fa65a4e
commit
8215abc978
|
@ -743,15 +743,18 @@ export namespace Content {
|
|||
Press the back arrow to exit.`);
|
||||
|
||||
export const SAVED_GARDENS =
|
||||
trim(`Create new gardens from scratch or by copying plants from the
|
||||
current garden. View and edit saved gardens, and, when ready, apply them
|
||||
to the main garden.`);
|
||||
trim(`Create a new garden from scratch or by copying plants from the
|
||||
current garden.`);
|
||||
|
||||
export const ERROR_PLANT_TEMPLATE_GROUP =
|
||||
trim(`Cannot create a group with these plants.
|
||||
Try leaving the saved garden first.`);
|
||||
|
||||
export const NO_PLANTS =
|
||||
trim(`Press "+" to add a plant to your garden.`);
|
||||
|
||||
export const NO_GARDENS =
|
||||
trim(`Press "CREATE NEW GARDEN" to add a garden.`);
|
||||
trim(`Press "+" to add a garden.`);
|
||||
|
||||
export const NO_POINTS =
|
||||
trim(`Press "+" to add a point to your garden.`);
|
||||
|
|
|
@ -406,6 +406,8 @@
|
|||
}
|
||||
|
||||
.garden-snapshot {
|
||||
margin-left: 2rem;
|
||||
margin-right: 2rem;
|
||||
button {
|
||||
&.pseudo-disabled {
|
||||
cursor: not-allowed;
|
||||
|
@ -414,22 +416,26 @@
|
|||
}
|
||||
|
||||
.saved-garden-list {
|
||||
margin: -15px;
|
||||
.saved-garden-row {
|
||||
padding: 0.25rem;
|
||||
button {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.saved-garden-info div {
|
||||
height: 3rem;
|
||||
line-height: 3rem;
|
||||
cursor: pointer;
|
||||
padding-right: 0;
|
||||
input {
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
label {
|
||||
margin: 0;
|
||||
pointer-events: none;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
p {
|
||||
margin-top: 0.25rem;
|
||||
float: right;
|
||||
line-height: 3rem;
|
||||
text-align: center;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
|
|
|
@ -472,9 +472,7 @@
|
|||
}
|
||||
|
||||
.move-to-panel-content {
|
||||
&.with-nav {
|
||||
margin-top: 6rem;
|
||||
}
|
||||
margin-top: 6rem;
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
button {
|
||||
|
@ -526,11 +524,7 @@
|
|||
}
|
||||
|
||||
.saved-garden-panel-content {
|
||||
&.with-nav {
|
||||
margin-top: 6rem;
|
||||
}
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
padding: 0;
|
||||
.row {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
@ -571,3 +565,17 @@
|
|||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.saved-garden-edit-panel-content {
|
||||
margin-left: 3rem;
|
||||
margin-right: 3rem;
|
||||
button {
|
||||
margin-left: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ describe("<DesignerNavTabs />", () => {
|
|||
});
|
||||
|
||||
it("renders for saved gardens", () => {
|
||||
mockPath = "/app/designer/saved_gardens";
|
||||
mockPath = "/app/designer/gardens";
|
||||
mockDev = true;
|
||||
const wrapper = shallow(<DesignerNavTabs />);
|
||||
expect(wrapper.hasClass("green-panel")).toBeTruthy();
|
||||
|
|
|
@ -326,11 +326,11 @@ describe("getMode()", () => {
|
|||
expect(getMode()).toEqual(Mode.clickToAdd);
|
||||
mockPath = "/app/designer/plants/1/edit";
|
||||
expect(getMode()).toEqual(Mode.editPlant);
|
||||
mockPath = "/app/designer/saved_gardens/templates/1/edit";
|
||||
mockPath = "/app/designer/gardens/templates/1/edit";
|
||||
expect(getMode()).toEqual(Mode.editPlant);
|
||||
mockPath = "/app/designer/plants/1";
|
||||
expect(getMode()).toEqual(Mode.editPlant);
|
||||
mockPath = "/app/designer/saved_gardens/templates/1";
|
||||
mockPath = "/app/designer/gardens/templates/1";
|
||||
expect(getMode()).toEqual(Mode.editPlant);
|
||||
mockPath = "/app/designer/plants/select";
|
||||
expect(getMode()).toEqual(Mode.boxSelect);
|
||||
|
@ -342,7 +342,7 @@ describe("getMode()", () => {
|
|||
expect(getMode()).toEqual(Mode.points);
|
||||
mockPath = "/app/designer/points/add";
|
||||
expect(getMode()).toEqual(Mode.createPoint);
|
||||
mockPath = "/app/designer/saved_gardens";
|
||||
mockPath = "/app/designer/gardens";
|
||||
mockGardenOpen = true;
|
||||
expect(getMode()).toEqual(Mode.templateView);
|
||||
mockPath = "/app/designer/groups/1";
|
||||
|
|
|
@ -84,7 +84,7 @@ describe("<PlantLayer/>", () => {
|
|||
p.plants[0].body.id = 5;
|
||||
const wrapper = svgMount(<PlantLayer {...p} />);
|
||||
expect(wrapper.find("Link").props().to)
|
||||
.toEqual("/app/designer/saved_gardens/templates/5");
|
||||
.toEqual("/app/designer/gardens/templates/5");
|
||||
});
|
||||
|
||||
it("has selected plant", () => {
|
||||
|
|
|
@ -25,7 +25,7 @@ export function PlantLayer(props: PlantLayerProps) {
|
|||
const selected = !!(currentPlant && (p.uuid === currentPlant.uuid));
|
||||
const multiselected = !!(selectedForDel && (selectedForDel.includes(p.uuid)));
|
||||
const plantCategory = unpackUUID(p.uuid).kind === "PlantTemplate"
|
||||
? "saved_gardens/templates"
|
||||
? "gardens/templates"
|
||||
: "plants";
|
||||
const plant = <GardenPlant
|
||||
uuid={p.uuid}
|
||||
|
|
|
@ -66,7 +66,7 @@ const getCurrentTab = (): Tabs => {
|
|||
return Panel.Map;
|
||||
} else if (pathArray.includes("events")) {
|
||||
return Panel.FarmEvents;
|
||||
} else if (pathArray.includes("saved_gardens")) {
|
||||
} else if (pathArray.includes("gardens")) {
|
||||
return Panel.SavedGardens;
|
||||
} else if (pathArray.includes("tools")) {
|
||||
return Panel.Tools;
|
||||
|
@ -120,7 +120,7 @@ export function DesignerNavTabs(props: { hidden?: boolean }) {
|
|||
{DevSettings.futureFeaturesEnabled() &&
|
||||
<NavTab
|
||||
panel={Panel.SavedGardens}
|
||||
linkTo={"/app/designer/saved_gardens"}
|
||||
linkTo={"/app/designer/gardens"}
|
||||
title={t("Gardens")} />}
|
||||
<NavTab
|
||||
panel={Panel.FarmEvents}
|
||||
|
|
|
@ -63,7 +63,7 @@ describe("<PlantInfo />", () => {
|
|||
});
|
||||
|
||||
it("gets template id", () => {
|
||||
mockPath = "/app/designer/saved_gardens/templates/2";
|
||||
mockPath = "/app/designer/gardens/templates/2";
|
||||
const p = fakeProps();
|
||||
p.openedSavedGarden = "uuid";
|
||||
const wrapper = mount<PlantInfo>(<PlantInfo {...p} />);
|
||||
|
|
|
@ -85,7 +85,7 @@ describe("<PlantInventoryItem />", () => {
|
|||
type: Actions.SELECT_PLANT
|
||||
});
|
||||
expect(push).toHaveBeenCalledWith(
|
||||
"/app/designer/saved_gardens/templates/" + p.tpp.body.id);
|
||||
"/app/designer/gardens/templates/" + p.tpp.body.id);
|
||||
});
|
||||
|
||||
it("gets cached icon", async () => {
|
||||
|
|
|
@ -19,11 +19,12 @@ import {
|
|||
RawSelectPlants as SelectPlants, SelectPlantsProps, mapStateToProps
|
||||
} from "../select_plants";
|
||||
import { fakePlant } from "../../../__test_support__/fake_state/resources";
|
||||
import { Actions } from "../../../constants";
|
||||
import { Actions, Content } from "../../../constants";
|
||||
import { clickButton } from "../../../__test_support__/helpers";
|
||||
import { destroy } from "../../../api/crud";
|
||||
import { createGroup } from "../../point_groups/actions";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import { error } from "../../../toast/toast";
|
||||
|
||||
describe("<SelectPlants />", () => {
|
||||
beforeEach(function () {
|
||||
|
@ -41,6 +42,7 @@ describe("<SelectPlants />", () => {
|
|||
selected: ["plant.1"],
|
||||
plants: [plant1, plant2],
|
||||
dispatch: jest.fn(),
|
||||
gardenOpen: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -147,6 +149,15 @@ describe("<SelectPlants />", () => {
|
|||
wrapper.find(".dark-blue").simulate("click");
|
||||
expect(createGroup).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("doesn't create group", () => {
|
||||
const p = fakeProps();
|
||||
p.gardenOpen = "uuid";
|
||||
const wrapper = mount(<SelectPlants {...p} />);
|
||||
wrapper.find(".dark-blue").simulate("click");
|
||||
expect(createGroup).not.toHaveBeenCalled();
|
||||
expect(error).toHaveBeenCalledWith(Content.ERROR_PLANT_TEMPLATE_GROUP);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps", () => {
|
||||
|
|
|
@ -42,7 +42,7 @@ export class PlantInventoryItem extends
|
|||
const click = () => {
|
||||
const plantCategory =
|
||||
unpackUUID(this.props.tpp.uuid).kind === "PlantTemplate"
|
||||
? "saved_gardens/templates"
|
||||
? "gardens/templates"
|
||||
: "plants";
|
||||
push(`/app/designer/${plantCategory}/${plantId}`);
|
||||
dispatch(selectPlant([tpp.uuid]));
|
||||
|
|
|
@ -14,19 +14,20 @@ import {
|
|||
import { t } from "../../i18next_wrapper";
|
||||
import { createGroup } from "../point_groups/actions";
|
||||
import { PanelColor } from "../panel_header";
|
||||
import { error } from "../../toast/toast";
|
||||
|
||||
export function mapStateToProps(props: Everything) {
|
||||
return {
|
||||
selected: props.resources.consumers.farm_designer.selectedPlants,
|
||||
plants: getPlants(props.resources),
|
||||
dispatch: props.dispatch,
|
||||
};
|
||||
}
|
||||
export const mapStateToProps = (props: Everything): SelectPlantsProps => ({
|
||||
selected: props.resources.consumers.farm_designer.selectedPlants,
|
||||
plants: getPlants(props.resources),
|
||||
dispatch: props.dispatch,
|
||||
gardenOpen: props.resources.consumers.farm_designer.openedSavedGarden,
|
||||
});
|
||||
|
||||
export interface SelectPlantsProps {
|
||||
plants: TaggedPlant[];
|
||||
dispatch: Function;
|
||||
selected: string[] | undefined;
|
||||
gardenOpen: string | undefined;
|
||||
}
|
||||
|
||||
export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
||||
|
@ -74,9 +75,9 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
|||
{t("Delete")}
|
||||
</button>
|
||||
<button className="fb-button dark-blue"
|
||||
onClick={() => this.props.dispatch(createGroup({
|
||||
points: this.selected
|
||||
}))}>
|
||||
onClick={() => !this.props.gardenOpen
|
||||
? this.props.dispatch(createGroup({ points: this.selected }))
|
||||
: error(t(Content.ERROR_PLANT_TEMPLATE_GROUP))}>
|
||||
{t("Create group")}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -56,8 +56,9 @@ describe("destroySavedGarden", () => {
|
|||
it("deletes garden", () => {
|
||||
const dispatch = jest.fn(() => Promise.resolve());
|
||||
destroySavedGarden("SavedGardenUuid")(dispatch);
|
||||
expect(dispatch).toHaveBeenCalledWith(unselectSavedGarden);
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/gardens");
|
||||
expect(destroy).toHaveBeenCalledWith("SavedGardenUuid");
|
||||
expect(dispatch).toHaveBeenLastCalledWith(unselectSavedGarden);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -65,7 +66,7 @@ describe("closeSavedGarden", () => {
|
|||
it("closes garden", () => {
|
||||
const dispatch = jest.fn();
|
||||
closeSavedGarden()(dispatch);
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/saved_gardens");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/gardens");
|
||||
expect(dispatch).toHaveBeenCalledWith(unselectSavedGarden);
|
||||
});
|
||||
});
|
||||
|
@ -75,7 +76,7 @@ describe("openSavedGarden", () => {
|
|||
const dispatch = jest.fn();
|
||||
const uuid = "SavedGardenUuid.1.0";
|
||||
openSavedGarden(uuid)(dispatch);
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/saved_gardens/1");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/gardens/1");
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.CHOOSE_SAVED_GARDEN,
|
||||
payload: uuid
|
||||
|
@ -91,7 +92,7 @@ describe("openOrCloseGarden", () => {
|
|||
gardenIsOpen: false,
|
||||
};
|
||||
openOrCloseGarden(props)();
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/saved_gardens/1");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/gardens/1");
|
||||
});
|
||||
|
||||
it("closes garden", () => {
|
||||
|
@ -101,19 +102,19 @@ describe("openOrCloseGarden", () => {
|
|||
gardenIsOpen: true,
|
||||
};
|
||||
openOrCloseGarden(props)();
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/saved_gardens");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/gardens");
|
||||
});
|
||||
});
|
||||
|
||||
describe("newSavedGarden", () => {
|
||||
it("creates a new saved garden", () => {
|
||||
newSavedGarden("my saved garden")(jest.fn());
|
||||
newSavedGarden("my saved garden")(jest.fn(() => Promise.resolve()));
|
||||
expect(initSave).toHaveBeenCalledWith(
|
||||
"SavedGarden", { name: "my saved garden" });
|
||||
});
|
||||
|
||||
it("creates a new saved garden with default name", () => {
|
||||
newSavedGarden("")(jest.fn());
|
||||
newSavedGarden("")(jest.fn(() => Promise.resolve()));
|
||||
expect(initSave).toHaveBeenCalledWith(
|
||||
"SavedGarden", { name: "Untitled Garden" });
|
||||
});
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { RawAddGarden as AddGarden, mapStateToProps } from "../garden_add";
|
||||
import { GardenSnapshotProps } from "../garden_snapshot";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
|
||||
describe("<AddGarden />", () => {
|
||||
const fakeProps = (): GardenSnapshotProps => ({
|
||||
currentSavedGarden: undefined,
|
||||
plantTemplates: [],
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders add garden panel", () => {
|
||||
const wrapper = mount(<AddGarden {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("create new garden");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const props = mapStateToProps(fakeState());
|
||||
expect(props.currentSavedGarden).toEqual(undefined);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,103 @@
|
|||
jest.mock("../actions", () => ({
|
||||
applyGarden: jest.fn(),
|
||||
destroySavedGarden: jest.fn(),
|
||||
openOrCloseGarden: jest.fn(),
|
||||
closeSavedGarden: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../api/crud", () => ({
|
||||
edit: jest.fn(),
|
||||
save: jest.fn(),
|
||||
}));
|
||||
|
||||
let mockPath = "";
|
||||
jest.mock("../../../history", () => ({
|
||||
getPathArray: jest.fn(() => mockPath.split("/")),
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { RawEditGarden as EditGarden, mapStateToProps } from "../garden_edit";
|
||||
import { EditGardenProps } from "../interfaces";
|
||||
import { fakeSavedGarden } from "../../../__test_support__/fake_state/resources";
|
||||
import { clickButton } from "../../../__test_support__/helpers";
|
||||
import { applyGarden, destroySavedGarden } from "../actions";
|
||||
import { error } from "../../../toast/toast";
|
||||
import { edit } from "../../../api/crud";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import {
|
||||
buildResourceIndex
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
|
||||
describe("<EditGarden />", () => {
|
||||
const fakeProps = (): EditGardenProps => ({
|
||||
savedGarden: undefined,
|
||||
gardenIsOpen: false,
|
||||
dispatch: jest.fn(),
|
||||
plantPointerCount: 0,
|
||||
});
|
||||
|
||||
it("edits garden name", () => {
|
||||
const p = fakeProps();
|
||||
p.savedGarden = fakeSavedGarden();
|
||||
const wrapper = shallow(<EditGarden {...p} />);
|
||||
wrapper.find("BlurableInput").simulate("commit",
|
||||
{ currentTarget: { value: "new name" } });
|
||||
expect(edit).toHaveBeenCalledWith(expect.any(Object), { name: "new name" });
|
||||
});
|
||||
|
||||
it("applies garden", () => {
|
||||
const p = fakeProps();
|
||||
p.savedGarden = fakeSavedGarden();
|
||||
p.savedGarden.body.id = 1;
|
||||
p.plantPointerCount = 0;
|
||||
const wrapper = mount(<EditGarden {...p} />);
|
||||
clickButton(wrapper, 0, "apply");
|
||||
expect(applyGarden).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("plants still in garden", () => {
|
||||
const p = fakeProps();
|
||||
p.savedGarden = fakeSavedGarden();
|
||||
p.plantPointerCount = 1;
|
||||
const wrapper = mount(<EditGarden {...p} />);
|
||||
clickButton(wrapper, 0, "apply");
|
||||
expect(error).toHaveBeenCalledWith(expect.stringContaining(
|
||||
"Please clear current garden first"));
|
||||
});
|
||||
|
||||
it("destroys garden", () => {
|
||||
const p = fakeProps();
|
||||
p.savedGarden = fakeSavedGarden();
|
||||
const wrapper = mount(<EditGarden {...p} />);
|
||||
clickButton(wrapper, 1, "delete");
|
||||
expect(destroySavedGarden).toHaveBeenCalledWith(p.savedGarden.uuid);
|
||||
});
|
||||
|
||||
it("shows garden not found", () => {
|
||||
const wrapper = mount(<EditGarden {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("not found");
|
||||
});
|
||||
|
||||
it("show when garden is open", () => {
|
||||
const p = fakeProps();
|
||||
p.savedGarden = fakeSavedGarden();
|
||||
p.gardenIsOpen = true;
|
||||
const wrapper = mount(<EditGarden {...p} />);
|
||||
expect(wrapper.text()).toContain("exit");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const sg = fakeSavedGarden();
|
||||
sg.body.id = 1;
|
||||
mockPath = "/app/designer/gardens/1";
|
||||
const state = fakeState();
|
||||
state.resources = buildResourceIndex([sg]);
|
||||
state.resources.consumers.farm_designer.openedSavedGarden = sg.uuid;
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.gardenIsOpen).toEqual(true);
|
||||
expect(props.savedGarden).toEqual(sg);
|
||||
});
|
||||
});
|
|
@ -1,58 +1,23 @@
|
|||
jest.mock("../actions", () => ({
|
||||
openOrCloseGarden: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../api/crud", () => ({
|
||||
edit: jest.fn(),
|
||||
save: jest.fn(),
|
||||
}));
|
||||
jest.mock("../actions", () => ({ openSavedGarden: jest.fn() }));
|
||||
|
||||
import * as React from "react";
|
||||
import { shallow, mount } from "enzyme";
|
||||
import {
|
||||
fakeSavedGarden, fakePlantTemplate
|
||||
} from "../../../__test_support__/fake_state/resources";
|
||||
import { edit } from "../../../api/crud";
|
||||
import { GardenInfo, SavedGardenList } from "../garden_list";
|
||||
import { SavedGardenInfoProps, SavedGardensProps } from "../interfaces";
|
||||
import { shallow } from "enzyme";
|
||||
import { GardenInfo } from "../garden_list";
|
||||
import { fakeSavedGarden } from "../../../__test_support__/fake_state/resources";
|
||||
import { SavedGardenInfoProps } from "../interfaces";
|
||||
import { openSavedGarden } from "../actions";
|
||||
|
||||
describe("<GardenInfo />", () => {
|
||||
const fakeProps = (): SavedGardenInfoProps => ({
|
||||
dispatch: jest.fn(),
|
||||
savedGarden: fakeSavedGarden(),
|
||||
gardenIsOpen: false,
|
||||
plantTemplateCount: 0,
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
||||
it("edits garden name", () => {
|
||||
const wrapper = shallow(<GardenInfo {...fakeProps()} />);
|
||||
wrapper.find("BlurableInput").simulate("commit",
|
||||
{ currentTarget: { value: "new name" } });
|
||||
expect(edit).toHaveBeenCalledWith(expect.any(Object), { name: "new name" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("<SavedGardenList />", () => {
|
||||
const fakeProps = (): SavedGardensProps => {
|
||||
const fakeSG = fakeSavedGarden();
|
||||
return {
|
||||
dispatch: jest.fn(),
|
||||
plantPointerCount: 1,
|
||||
savedGardens: [fakeSG],
|
||||
plantTemplates: [fakePlantTemplate(), fakePlantTemplate()],
|
||||
openedSavedGarden: fakeSG.uuid,
|
||||
};
|
||||
};
|
||||
|
||||
it("renders open garden", () => {
|
||||
const wrapper = mount(<SavedGardenList {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("exit");
|
||||
});
|
||||
|
||||
it("renders gardens closed", () => {
|
||||
it("opens garden", () => {
|
||||
const p = fakeProps();
|
||||
p.openedSavedGarden = undefined;
|
||||
const wrapper = mount(<SavedGardenList {...p} />);
|
||||
expect(wrapper.text()).not.toContain("exit");
|
||||
const wrapper = shallow(<GardenInfo {...p} />);
|
||||
wrapper.simulate("click");
|
||||
expect(openSavedGarden).toHaveBeenCalledWith(p.savedGarden.uuid);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -36,9 +36,8 @@ import {
|
|||
buildResourceIndex
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
import { SavedGardensProps } from "../interfaces";
|
||||
import { applyGarden, destroySavedGarden, closeSavedGarden } from "../actions";
|
||||
import { closeSavedGarden } from "../actions";
|
||||
import { Actions } from "../../../constants";
|
||||
import { error } from "../../../toast/toast";
|
||||
|
||||
describe("<SavedGardens />", () => {
|
||||
const fakeProps = (): SavedGardensProps => ({
|
||||
|
@ -50,36 +49,14 @@ describe("<SavedGardens />", () => {
|
|||
});
|
||||
|
||||
it("renders saved gardens", () => {
|
||||
const wrapper = mount(<SavedGardens {...fakeProps()} />);
|
||||
["saved garden 1", "2", "apply"].map(string =>
|
||||
const p = fakeProps();
|
||||
p.plantTemplates[0].body.saved_garden_id = p.savedGardens[0].body.id || 0;
|
||||
p.plantTemplates[1].body.saved_garden_id = p.savedGardens[0].body.id || 0;
|
||||
const wrapper = mount(<SavedGardens {...p} />);
|
||||
["saved garden 1", "2 plants"].map(string =>
|
||||
expect(wrapper.html().toLowerCase()).toContain(string));
|
||||
});
|
||||
|
||||
it("applies garden", () => {
|
||||
const p = fakeProps();
|
||||
p.savedGardens[0].uuid = "SavedGarden.1.0";
|
||||
p.savedGardens[0].body.id = 1;
|
||||
p.plantPointerCount = 0;
|
||||
const wrapper = mount(<SavedGardens {...p} />);
|
||||
clickButton(wrapper, 3, "apply");
|
||||
expect(applyGarden).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("plants still in garden", () => {
|
||||
const wrapper = mount(<SavedGardens {...fakeProps()} />);
|
||||
wrapper.find("button").first().simulate("click");
|
||||
clickButton(wrapper, 3, "apply");
|
||||
expect(error).toHaveBeenCalledWith(expect.stringContaining(
|
||||
"Please clear current garden first"));
|
||||
});
|
||||
|
||||
it("destroys garden", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<SavedGardens {...p} />);
|
||||
clickButton(wrapper, 2, "");
|
||||
expect(destroySavedGarden).toHaveBeenCalledWith(p.savedGardens[0].uuid);
|
||||
});
|
||||
|
||||
it("has no saved gardens yet", () => {
|
||||
const p = fakeProps();
|
||||
p.savedGardens = [];
|
||||
|
@ -87,11 +64,31 @@ describe("<SavedGardens />", () => {
|
|||
expect(wrapper.text().toLowerCase()).toContain("no saved gardens yet");
|
||||
});
|
||||
|
||||
it("shows alt display", () => {
|
||||
mockDev = true;
|
||||
const wrapper = mount(<SavedGardens {...fakeProps()} />);
|
||||
expect(wrapper.html()).toContain("-nav");
|
||||
mockDev = false;
|
||||
it("changes search term", () => {
|
||||
const wrapper = shallow<SavedGardens>(<SavedGardens {...fakeProps()} />);
|
||||
expect(wrapper.state().searchTerm).toEqual("");
|
||||
wrapper.find("input").first().simulate("change",
|
||||
{ currentTarget: { value: "spring" } });
|
||||
expect(wrapper.state().searchTerm).toEqual("spring");
|
||||
});
|
||||
|
||||
it("shows filtered gardens", () => {
|
||||
const p = fakeProps();
|
||||
p.savedGardens = [fakeSavedGarden(), fakeSavedGarden()];
|
||||
p.savedGardens[0].body.name = "winter";
|
||||
p.savedGardens[1].body.name = "spring";
|
||||
const wrapper = mount(<SavedGardens {...p} />);
|
||||
wrapper.setState({ searchTerm: "winter" });
|
||||
expect(wrapper.text()).toContain("winter");
|
||||
expect(wrapper.text()).not.toContain("spring");
|
||||
});
|
||||
|
||||
it("shows when garden is open", () => {
|
||||
const p = fakeProps();
|
||||
p.savedGardens = [fakeSavedGarden(), fakeSavedGarden()];
|
||||
p.openedSavedGarden = p.savedGardens[0].uuid;
|
||||
const wrapper = mount(<SavedGardens {...p} />);
|
||||
expect(wrapper.html()).toContain("selected");
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -115,7 +112,7 @@ describe("<SavedGardensLink />", () => {
|
|||
const wrapper = shallow(<SavedGardensLink />);
|
||||
clickButton(wrapper, 0, "saved gardens");
|
||||
expect(history.push).toHaveBeenCalledWith(
|
||||
"/app/designer/saved_gardens");
|
||||
"/app/designer/gardens");
|
||||
mockDev = false;
|
||||
});
|
||||
|
||||
|
@ -130,7 +127,7 @@ describe("<SavedGardensLink />", () => {
|
|||
|
||||
describe("savedGardenOpen", () => {
|
||||
it("is open", () => {
|
||||
const result = savedGardenOpen(["", "", "", "saved_gardens", "4", ""]);
|
||||
const result = savedGardenOpen(["", "", "", "gardens", "4", ""]);
|
||||
expect(result).toEqual(4);
|
||||
});
|
||||
});
|
||||
|
@ -145,7 +142,7 @@ describe("<SavedGardenHUD />", () => {
|
|||
it("opens menu", () => {
|
||||
const wrapper = mount(<SavedGardenHUD dispatch={jest.fn()} />);
|
||||
clickButton(wrapper, 0, "menu");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/saved_gardens");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/gardens");
|
||||
});
|
||||
|
||||
it("navigates to plants", () => {
|
||||
|
|
|
@ -13,7 +13,10 @@ import { stopTracking } from "../../connectivity/data_consistency";
|
|||
/** Save all Plant to PlantTemplates in a new SavedGarden. */
|
||||
export const snapshotGarden = (name?: string | undefined) =>
|
||||
axios.post<void>(API.current.snapshotPath, name ? { name } : {})
|
||||
.then(() => success(t("Garden Saved.")));
|
||||
.then(() => {
|
||||
success(t("Garden Saved."));
|
||||
history.push("/app/designer/gardens");
|
||||
});
|
||||
|
||||
export const unselectSavedGarden = {
|
||||
type: Actions.CHOOSE_SAVED_GARDEN,
|
||||
|
@ -32,19 +35,19 @@ export const applyGarden = (gardenId: number) => (dispatch: Function) => axios
|
|||
});
|
||||
|
||||
export const destroySavedGarden = (uuid: string) => (dispatch: Function) => {
|
||||
dispatch(destroy(uuid))
|
||||
.then(dispatch(unselectSavedGarden))
|
||||
.catch(() => { });
|
||||
dispatch(unselectSavedGarden);
|
||||
history.push("/app/designer/gardens");
|
||||
dispatch(destroy(uuid));
|
||||
};
|
||||
|
||||
export const closeSavedGarden = () => {
|
||||
history.push("/app/designer/saved_gardens");
|
||||
history.push("/app/designer/gardens");
|
||||
return (dispatch: Function) =>
|
||||
dispatch(unselectSavedGarden);
|
||||
};
|
||||
|
||||
export const openSavedGarden = (savedGarden: string) => {
|
||||
history.push("/app/designer/saved_gardens/" + unpackUUID(savedGarden).remoteId);
|
||||
history.push("/app/designer/gardens/" + unpackUUID(savedGarden).remoteId);
|
||||
return (dispatch: Function) =>
|
||||
dispatch({ type: Actions.CHOOSE_SAVED_GARDEN, payload: savedGarden });
|
||||
};
|
||||
|
@ -62,7 +65,11 @@ export const openOrCloseGarden = (props: {
|
|||
/** Create a new SavedGarden with the chosen name. */
|
||||
export const newSavedGarden = (name: string) =>
|
||||
(dispatch: Function) => {
|
||||
dispatch(initSave("SavedGarden", { name: name || "Untitled Garden" }));
|
||||
dispatch(initSave("SavedGarden", { name: name || "Untitled Garden" }))
|
||||
.then(() => {
|
||||
success(t("Garden Saved."));
|
||||
history.push("/app/designer/gardens");
|
||||
});
|
||||
};
|
||||
|
||||
/** Create a copy of a PlantTemplate body and assign it a new SavedGarden. */
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { GardenSnapshotProps, GardenSnapshot } from "./garden_snapshot";
|
||||
import { findSavedGarden } from "./garden_edit";
|
||||
import { selectAllPlantTemplates } from "../../resources/selectors";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelHeader, DesignerPanelContent
|
||||
} from "../plants/designer_panel";
|
||||
import { Content } from "../../constants";
|
||||
import { Row } from "../../ui";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { Panel } from "../panel_header";
|
||||
|
||||
export const mapStateToProps = (props: Everything): GardenSnapshotProps => ({
|
||||
currentSavedGarden: findSavedGarden(props.resources.index),
|
||||
dispatch: props.dispatch,
|
||||
plantTemplates: selectAllPlantTemplates(props.resources.index),
|
||||
});
|
||||
|
||||
export class RawAddGarden extends React.Component<GardenSnapshotProps, {}> {
|
||||
render() {
|
||||
return <DesignerPanel panelName={"saved-garden"} panel={Panel.SavedGardens}>
|
||||
<DesignerPanelHeader
|
||||
panelName={"saved-garden"}
|
||||
panel={Panel.SavedGardens}
|
||||
title={t("Add Garden")}
|
||||
description={Content.SAVED_GARDENS}
|
||||
backTo={"/app/designer/gardens"} />
|
||||
<DesignerPanelContent panelName={"saved-garden"}>
|
||||
<Row>
|
||||
<GardenSnapshot
|
||||
currentSavedGarden={this.props.currentSavedGarden}
|
||||
plantTemplates={this.props.plantTemplates}
|
||||
dispatch={this.props.dispatch} />
|
||||
</Row>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
}
|
||||
|
||||
export const AddGarden = connect(mapStateToProps)(RawAddGarden);
|
|
@ -0,0 +1,120 @@
|
|||
import * as React from "react";
|
||||
import { GardenViewButtonProps, EditGardenProps } from "./interfaces";
|
||||
import { openOrCloseGarden, applyGarden, destroySavedGarden } from "./actions";
|
||||
import { error } from "../../toast/toast";
|
||||
import { trim } from "../../util";
|
||||
import { BlurableInput, Row } from "../../ui";
|
||||
import { edit, save } from "../../api/crud";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
selectAllSavedGardens, selectAllPlantPointers, findResourceById
|
||||
} from "../../resources/selectors";
|
||||
import { Everything } from "../../interfaces";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelHeader, DesignerPanelContent
|
||||
} from "../plants/designer_panel";
|
||||
import { getPathArray } from "../../history";
|
||||
import { isNumber } from "lodash";
|
||||
import { ResourceIndex } from "../../resources/interfaces";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { Panel } from "../panel_header";
|
||||
|
||||
/** Open or close a SavedGarden. */
|
||||
const GardenViewButton = (props: GardenViewButtonProps) => {
|
||||
const { dispatch, savedGarden, gardenIsOpen } = props;
|
||||
const onClick = openOrCloseGarden({ savedGarden, gardenIsOpen, dispatch });
|
||||
const btnText = gardenIsOpen
|
||||
? t("exit")
|
||||
: t("view");
|
||||
return <button
|
||||
className={`fb-button ${gardenIsOpen ? "gray" : "yellow"}`}
|
||||
onClick={onClick}>
|
||||
{btnText}
|
||||
</button>;
|
||||
};
|
||||
|
||||
/** Apply a SavedGarden after checking that the current garden is empty. */
|
||||
const ApplyGardenButton =
|
||||
(props: { plantPointerCount: number, gardenId: number, dispatch: Function }) =>
|
||||
<button
|
||||
className="fb-button green"
|
||||
onClick={() => props.plantPointerCount > 0
|
||||
? error(trim(`${t("Please clear current garden first.")}
|
||||
(${props.plantPointerCount} ${t("plants")})`))
|
||||
: props.dispatch(applyGarden(props.gardenId))}>
|
||||
{t("apply")}
|
||||
</button>;
|
||||
|
||||
const DestroyGardenButton =
|
||||
(props: { dispatch: Function, gardenUuid: string }) =>
|
||||
<button
|
||||
className="fb-button red"
|
||||
onClick={() => props.dispatch(destroySavedGarden(props.gardenUuid))}>
|
||||
{t("delete")}
|
||||
</button>;
|
||||
|
||||
export const findSavedGarden = (ri: ResourceIndex) => {
|
||||
const id = getPathArray()[4];
|
||||
const num = parseInt(id || "NOPE", 10);
|
||||
if (isNumber(num) && !isNaN(num)) {
|
||||
const uuid = findResourceById(ri, "SavedGarden", num);
|
||||
return selectAllSavedGardens(ri).filter(x => x.uuid === uuid)[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const mapStateToProps = (props: Everything): EditGardenProps => {
|
||||
const { openedSavedGarden } = props.resources.consumers.farm_designer;
|
||||
const savedGarden = findSavedGarden(props.resources.index);
|
||||
return {
|
||||
savedGarden,
|
||||
gardenIsOpen: !!(savedGarden && savedGarden.uuid === openedSavedGarden),
|
||||
dispatch: props.dispatch,
|
||||
plantPointerCount: selectAllPlantPointers(props.resources.index).length,
|
||||
};
|
||||
};
|
||||
|
||||
export class RawEditGarden extends React.Component<EditGardenProps, {}> {
|
||||
render() {
|
||||
const { savedGarden } = this.props;
|
||||
return <DesignerPanel panelName={"saved-garden-edit"}
|
||||
panel={Panel.SavedGardens}>
|
||||
<DesignerPanelHeader
|
||||
panelName={"saved-garden"}
|
||||
panel={Panel.SavedGardens}
|
||||
title={t("Edit Garden")}
|
||||
backTo={"/app/designer/gardens"} />
|
||||
<DesignerPanelContent panelName={"saved-garden-edit"}>
|
||||
{savedGarden
|
||||
? <div>
|
||||
<Row>
|
||||
<label>{t("Garden name")}</label>
|
||||
<BlurableInput
|
||||
value={savedGarden.body.name || ""}
|
||||
onCommit={e => {
|
||||
this.props.dispatch(edit(savedGarden, {
|
||||
name: e.currentTarget.value
|
||||
}));
|
||||
this.props.dispatch(save(savedGarden.uuid));
|
||||
}} />
|
||||
</Row>
|
||||
<Row>
|
||||
<ApplyGardenButton
|
||||
dispatch={this.props.dispatch}
|
||||
plantPointerCount={this.props.plantPointerCount}
|
||||
gardenId={savedGarden.body.id || -1} />
|
||||
<DestroyGardenButton
|
||||
dispatch={this.props.dispatch}
|
||||
gardenUuid={savedGarden.uuid} />
|
||||
<GardenViewButton
|
||||
dispatch={this.props.dispatch}
|
||||
savedGarden={savedGarden.uuid}
|
||||
gardenIsOpen={this.props.gardenIsOpen} />
|
||||
</Row>
|
||||
</div>
|
||||
: <p>{t("Garden not found.")}</p>}
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
}
|
||||
|
||||
export const EditGarden = connect(mapStateToProps)(RawEditGarden);
|
|
@ -1,115 +1,43 @@
|
|||
import * as React from "react";
|
||||
import { Row, Col, BlurableInput } from "../../ui";
|
||||
import { error } from "../../toast/toast";
|
||||
import { Col } from "../../ui";
|
||||
import { isNumber, isString } from "lodash";
|
||||
import { openOrCloseGarden, applyGarden, destroySavedGarden } from "./actions";
|
||||
import { openSavedGarden } from "./actions";
|
||||
import {
|
||||
SavedGardensProps, GardenViewButtonProps, SavedGardenItemProps,
|
||||
SavedGardenInfoProps
|
||||
SavedGardenItemProps, SavedGardenInfoProps, SavedGardensListProps
|
||||
} from "./interfaces";
|
||||
import { edit, save } from "../../api/crud";
|
||||
import { trim } from "../../util";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
/** Name input and PlantTemplate count for a single SavedGarden. */
|
||||
export const GardenInfo = (props: SavedGardenInfoProps) => {
|
||||
const { savedGarden, gardenIsOpen, dispatch } = props;
|
||||
const { savedGarden, dispatch } = props;
|
||||
return <div className="saved-garden-info"
|
||||
onClick={openOrCloseGarden({
|
||||
savedGarden: savedGarden.uuid, gardenIsOpen, dispatch
|
||||
})}>
|
||||
<Col xs={4}>
|
||||
<BlurableInput
|
||||
value={savedGarden.body.name || ""}
|
||||
onCommit={e => {
|
||||
dispatch(edit(savedGarden, { name: e.currentTarget.value }));
|
||||
dispatch(save(savedGarden.uuid));
|
||||
}} />
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<p style={{ textAlign: "center" }}>{props.plantTemplateCount}</p>
|
||||
onClick={() => dispatch(openSavedGarden(savedGarden.uuid))}>
|
||||
<Col>
|
||||
<label>{savedGarden.body.name}</label>
|
||||
<p>{props.plantTemplateCount} {t("plants")}</p>
|
||||
</Col>
|
||||
</div>;
|
||||
};
|
||||
|
||||
/** Open or close a SavedGarden. */
|
||||
const GardenViewButton = (props: GardenViewButtonProps) => {
|
||||
const { dispatch, savedGarden, gardenIsOpen } = props;
|
||||
const onClick = openOrCloseGarden({ savedGarden, gardenIsOpen, dispatch });
|
||||
const btnText = gardenIsOpen
|
||||
? t("exit")
|
||||
: t("view");
|
||||
return <button
|
||||
className={`fb-button ${gardenIsOpen ? "gray" : "yellow"}`}
|
||||
onClick={onClick}>
|
||||
{btnText}
|
||||
</button>;
|
||||
};
|
||||
|
||||
/** Apply a SavedGarden after checking that the current garden is empty. */
|
||||
const ApplyGardenButton =
|
||||
(props: { plantPointerCount: number, gardenId: number, dispatch: Function }) =>
|
||||
<button
|
||||
className="fb-button green"
|
||||
onClick={() => props.plantPointerCount > 0
|
||||
? error(trim(`${t("Please clear current garden first.")}
|
||||
(${props.plantPointerCount} ${t("plants")})`))
|
||||
: props.dispatch(applyGarden(props.gardenId))}>
|
||||
{t("apply")}
|
||||
</button>;
|
||||
|
||||
const DestroyGardenButton =
|
||||
(props: { dispatch: Function, gardenUuid: string }) =>
|
||||
<button
|
||||
className="fb-button red del-button"
|
||||
title={t("Delete")}
|
||||
onClick={() => props.dispatch(destroySavedGarden(props.gardenUuid))}>
|
||||
<i className="fa fa-times" />
|
||||
</button>;
|
||||
|
||||
/** Info and actions for a single SavedGarden. */
|
||||
const SavedGardenItem = (props: SavedGardenItemProps) => {
|
||||
return <div
|
||||
className={`saved-garden-row ${props.gardenIsOpen ? "selected" : ""}`}>
|
||||
<Row>
|
||||
<GardenInfo
|
||||
savedGarden={props.savedGarden}
|
||||
gardenIsOpen={props.gardenIsOpen}
|
||||
plantTemplateCount={props.plantTemplateCount}
|
||||
dispatch={props.dispatch} />
|
||||
<Col xs={6}>
|
||||
<DestroyGardenButton
|
||||
dispatch={props.dispatch}
|
||||
gardenUuid={props.savedGarden.uuid} />
|
||||
<ApplyGardenButton
|
||||
dispatch={props.dispatch}
|
||||
plantPointerCount={props.plantPointerCount}
|
||||
gardenId={props.savedGarden.body.id || -1} />
|
||||
<GardenViewButton
|
||||
dispatch={props.dispatch}
|
||||
savedGarden={props.savedGarden.uuid}
|
||||
gardenIsOpen={props.gardenIsOpen} />
|
||||
</Col>
|
||||
</Row>
|
||||
<GardenInfo
|
||||
savedGarden={props.savedGarden}
|
||||
plantTemplateCount={props.plantTemplateCount}
|
||||
dispatch={props.dispatch} />
|
||||
</div>;
|
||||
};
|
||||
|
||||
/** Info and action list for all SavedGardens. */
|
||||
export const SavedGardenList = (props: SavedGardensProps) =>
|
||||
export const SavedGardenList = (props: SavedGardensListProps) =>
|
||||
<div className="saved-garden-list">
|
||||
<Row>
|
||||
<Col xs={4}>
|
||||
<label>{t("name")}</label>
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<label>{t("plants")}</label>
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<label style={{ float: "right" }}>{t("actions")}</label>
|
||||
</Col>
|
||||
</Row>
|
||||
{props.savedGardens.map(sg => {
|
||||
if (isString(sg.uuid) && isNumber(sg.body.id) && isString(sg.body.name)) {
|
||||
const validSavedGarden =
|
||||
isString(sg.uuid) && isNumber(sg.body.id) && isString(sg.body.name);
|
||||
if (validSavedGarden && (sg.body.name || "").toLowerCase()
|
||||
.includes(props.searchTerm.toLowerCase())) {
|
||||
return <SavedGardenItem
|
||||
key={sg.uuid}
|
||||
savedGarden={sg}
|
||||
|
|
|
@ -8,6 +8,14 @@ export interface SavedGardensProps {
|
|||
openedSavedGarden: string | undefined;
|
||||
}
|
||||
|
||||
export interface SavedGardensListProps extends SavedGardensProps {
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
export interface SavedGardensState {
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
export interface GardenViewButtonProps {
|
||||
dispatch: Function;
|
||||
savedGarden: string | undefined;
|
||||
|
@ -24,7 +32,13 @@ export interface SavedGardenItemProps {
|
|||
|
||||
export interface SavedGardenInfoProps {
|
||||
savedGarden: TaggedSavedGarden;
|
||||
gardenIsOpen: boolean;
|
||||
plantTemplateCount: number;
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
export interface EditGardenProps {
|
||||
savedGarden: TaggedSavedGarden | undefined;
|
||||
gardenIsOpen: boolean;
|
||||
dispatch: Function;
|
||||
plantPointerCount: number;
|
||||
}
|
||||
|
|
|
@ -6,19 +6,18 @@ import { unselectPlant } from "../actions";
|
|||
import {
|
||||
selectAllSavedGardens, selectAllPlantTemplates, selectAllPlantPointers
|
||||
} from "../../resources/selectors";
|
||||
import { GardenSnapshot } from "./garden_snapshot";
|
||||
import { SavedGardenList } from "./garden_list";
|
||||
import { SavedGardensProps } from "./interfaces";
|
||||
import { SavedGardensProps, SavedGardensState } from "./interfaces";
|
||||
import { closeSavedGarden } from "./actions";
|
||||
import { TaggedSavedGarden } from "farmbot";
|
||||
import { Content } from "../../constants";
|
||||
import {
|
||||
DesignerPanel,
|
||||
DesignerPanelContent
|
||||
DesignerPanel, DesignerPanelContent, DesignerPanelTop
|
||||
} from "../plants/designer_panel";
|
||||
import { DesignerNavTabs, Panel } from "../panel_header";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { EmptyStateWrapper, EmptyStateGraphic } from "../../ui/empty_state_wrapper";
|
||||
import {
|
||||
EmptyStateWrapper, EmptyStateGraphic
|
||||
} from "../../ui/empty_state_wrapper";
|
||||
import { Content } from "../../constants";
|
||||
|
||||
export const mapStateToProps = (props: Everything): SavedGardensProps => ({
|
||||
savedGardens: selectAllSavedGardens(props.resources.index),
|
||||
|
@ -28,35 +27,35 @@ export const mapStateToProps = (props: Everything): SavedGardensProps => ({
|
|||
openedSavedGarden: props.resources.consumers.farm_designer.openedSavedGarden,
|
||||
});
|
||||
|
||||
export class RawSavedGardens extends React.Component<SavedGardensProps, {}> {
|
||||
export class RawSavedGardens
|
||||
extends React.Component<SavedGardensProps, SavedGardensState> {
|
||||
state: SavedGardensState = { searchTerm: "" };
|
||||
|
||||
componentDidMount() {
|
||||
unselectPlant(this.props.dispatch)();
|
||||
}
|
||||
|
||||
get currentSavedGarden(): TaggedSavedGarden | undefined {
|
||||
return this.props.savedGardens
|
||||
.filter(x => x.uuid === this.props.openedSavedGarden)[0];
|
||||
}
|
||||
onChange = (e: React.SyntheticEvent<HTMLInputElement>) =>
|
||||
this.setState({ searchTerm: e.currentTarget.value });
|
||||
|
||||
render() {
|
||||
return <DesignerPanel panelName={"saved-garden"} panel={Panel.SavedGardens}>
|
||||
<DesignerNavTabs />
|
||||
<DesignerPanelContent panelName={"saved-garden"}
|
||||
className={"with-nav"}>
|
||||
<p>{t(Content.SAVED_GARDENS)}</p>
|
||||
<GardenSnapshot
|
||||
currentSavedGarden={this.currentSavedGarden}
|
||||
plantTemplates={this.props.plantTemplates}
|
||||
dispatch={this.props.dispatch} />
|
||||
<hr />
|
||||
<DesignerPanelContent panelName={"saved-garden"}>
|
||||
<DesignerPanelTop
|
||||
panel={Panel.SavedGardens}
|
||||
linkTo={"/app/designer/gardens/add"}
|
||||
title={t("Add garden")}>
|
||||
<input type="text" onChange={this.onChange}
|
||||
placeholder={t("Search your gardens...")} />
|
||||
</DesignerPanelTop>
|
||||
<EmptyStateWrapper
|
||||
notEmpty={this.props.savedGardens.length > 0}
|
||||
title={t("No saved gardens yet.")}
|
||||
// text={t(Content.NO_GARDENS)}
|
||||
text={t(Content.NO_GARDENS)}
|
||||
colorScheme="gardens"
|
||||
graphic={EmptyStateGraphic.plants}>
|
||||
<SavedGardenList {...this.props} />
|
||||
<SavedGardenList {...this.props} searchTerm={this.state.searchTerm} />
|
||||
</EmptyStateWrapper>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
|
@ -67,13 +66,13 @@ export class RawSavedGardens extends React.Component<SavedGardensProps, {}> {
|
|||
export const SavedGardensLink = () =>
|
||||
<button className="fb-button green"
|
||||
hidden={true}
|
||||
onClick={() => history.push("/app/designer/saved_gardens")}>
|
||||
onClick={() => history.push("/app/designer/gardens")}>
|
||||
{t("Saved Gardens")}
|
||||
</button>;
|
||||
|
||||
/** Check if a SavedGarden is currently open (URL approach). */
|
||||
export const savedGardenOpen = (pathArray: string[]) =>
|
||||
pathArray[3] === "saved_gardens" && parseInt(pathArray[4]) > 0
|
||||
pathArray[3] === "gardens" && parseInt(pathArray[4]) > 0
|
||||
? parseInt(pathArray[4]) : false;
|
||||
|
||||
/** Sticky an indicator and actions menu when a SavedGarden is open. */
|
||||
|
@ -81,7 +80,7 @@ export const SavedGardenHUD = (props: { dispatch: Function }) =>
|
|||
<div className="saved-garden-indicator">
|
||||
<label>{t("Viewing saved garden")}</label>
|
||||
<button className="fb-button gray"
|
||||
onClick={() => history.push("/app/designer/saved_gardens")}>
|
||||
onClick={() => history.push("/app/designer/gardens")}>
|
||||
{t("Menu")}
|
||||
</button>
|
||||
<button className="fb-button green"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ResourceIndex } from "./interfaces";
|
||||
import { ResourceIndex, UUID } from "./interfaces";
|
||||
import {
|
||||
ResourceName,
|
||||
TaggedGenericPointer,
|
||||
|
@ -50,7 +50,7 @@ export let maybeDetermineUuid =
|
|||
}
|
||||
};
|
||||
|
||||
export let findId = (index: ResourceIndex, kind: ResourceName, id: number) => {
|
||||
export let findId = (index: ResourceIndex, kind: ResourceName, id: number): UUID => {
|
||||
const uuid = maybeDetermineUuid(index, kind, id);
|
||||
if (uuid) {
|
||||
return uuid;
|
||||
|
|
|
@ -203,7 +203,7 @@ export const UNBOUND_ROUTES = [
|
|||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/plants/saved_gardens",
|
||||
$: "/designer/plants/gardens",
|
||||
getModule,
|
||||
key,
|
||||
getChild: () => import("./farm_designer/saved_gardens/saved_gardens"),
|
||||
|
@ -275,7 +275,7 @@ export const UNBOUND_ROUTES = [
|
|||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/saved_gardens",
|
||||
$: "/designer/gardens",
|
||||
getModule,
|
||||
key,
|
||||
getChild: () => import("./farm_designer/saved_gardens/saved_gardens"),
|
||||
|
@ -283,7 +283,7 @@ export const UNBOUND_ROUTES = [
|
|||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/saved_gardens/templates",
|
||||
$: "/designer/gardens/templates",
|
||||
getModule,
|
||||
key,
|
||||
getChild: () => import("./farm_designer/plants/plant_inventory"),
|
||||
|
@ -291,7 +291,7 @@ export const UNBOUND_ROUTES = [
|
|||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/saved_gardens/templates/:plant_template_id",
|
||||
$: "/designer/gardens/templates/:plant_template_id",
|
||||
getModule,
|
||||
key,
|
||||
getChild: () => import("./farm_designer/plants/plant_info"),
|
||||
|
@ -299,11 +299,19 @@ export const UNBOUND_ROUTES = [
|
|||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/saved_gardens/:saved_garden_id",
|
||||
$: "/designer/gardens/add",
|
||||
getModule,
|
||||
key,
|
||||
getChild: () => import("./farm_designer/saved_gardens/saved_gardens"),
|
||||
childKey: "SavedGardens"
|
||||
getChild: () => import("./farm_designer/saved_gardens/garden_add"),
|
||||
childKey: "AddGarden"
|
||||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/gardens/:saved_garden_id",
|
||||
getModule,
|
||||
key,
|
||||
getChild: () => import("./farm_designer/saved_gardens/garden_edit"),
|
||||
childKey: "EditGarden"
|
||||
}),
|
||||
route({
|
||||
children: true,
|
||||
|
|
Loading…
Reference in New Issue