- {links.map(link => {
+ {getLinks().map(link => {
const isActive = (currPageSlug === link.slug) ? "active" : "";
const childPath = link.slug === "designer" ? "/plants" : "";
const fn = link.computeHref || DEFAULT;
diff --git a/frontend/resources/selectors_by_id.ts b/frontend/resources/selectors_by_id.ts
index 7d2b2e214..3b81d7e78 100644
--- a/frontend/resources/selectors_by_id.ts
+++ b/frontend/resources/selectors_by_id.ts
@@ -8,6 +8,7 @@ import {
isTaggedToolSlotPointer,
sanityCheck,
isTaggedPlantTemplate,
+ isTaggedGenericPointer,
} from "./tagged_resources";
import {
ResourceName,
@@ -105,6 +106,13 @@ export function maybeFindPlantTemplateById(index: ResourceIndex, id: number) {
if (resource && isTaggedPlantTemplate(resource)) { return resource; }
}
+/** Unlike other findById methods, this one allows undefined (missed) values */
+export function maybeFindPointById(index: ResourceIndex, id: number) {
+ const uuid = index.byKindAndId[joinKindAndId("Point", id)];
+ const resource = index.references[uuid || "nope"];
+ if (resource && isTaggedGenericPointer(resource)) { return resource; }
+}
+
export let findRegimenById = (ri: ResourceIndex, regimen_id: number) => {
const regimen = byId("Regimen")(ri, regimen_id);
if (regimen && isTaggedRegimen(regimen) && sanityCheck(regimen)) {
diff --git a/frontend/route_config.tsx b/frontend/route_config.tsx
index 262817753..a7d5de1e2 100644
--- a/frontend/route_config.tsx
+++ b/frontend/route_config.tsx
@@ -91,7 +91,8 @@ const key = "FarmDesigner";
*
* DO NOT RE-ORDER ITEMS FOR READABILITY--they are order-dependent.
* Stuff will break if the route order is changed.
- * (e.g., must be "a" then "a/:b/c" then "a/:b", 404 must be last, etc.)
+ * (e.g., must be ["a", "a/b", "a/b/:c/d", "a/b/:c", "a/:e"],
+ * 404 must be last, etc.)
*/
export const UNBOUND_ROUTES = [
route({
@@ -218,12 +219,28 @@ export const UNBOUND_ROUTES = [
}),
route({
children: true,
- $: "/designer/plants/create_point",
+ $: "/designer/points",
+ getModule,
+ key,
+ getChild: () => import("./farm_designer/plants/point_inventory"),
+ childKey: "Points"
+ }),
+ route({
+ children: true,
+ $: "/designer/points/add",
getModule,
key,
getChild: () => import("./farm_designer/plants/create_points"),
childKey: "CreatePoints"
}),
+ route({
+ children: true,
+ $: "/designer/points/:point_id",
+ getModule,
+ key,
+ getChild: () => import("./farm_designer/plants/point_info"),
+ childKey: "EditPoint"
+ }),
route({
children: true,
$: "/designer/plants/crop_search",
@@ -296,6 +313,30 @@ export const UNBOUND_ROUTES = [
getChild: () => import("./farm_designer/settings"),
childKey: "DesignerSettings"
}),
+ route({
+ children: true,
+ $: "/designer/tools",
+ getModule,
+ key,
+ getChild: () => import("./farm_designer/tools"),
+ childKey: "Tools"
+ }),
+ route({
+ children: true,
+ $: "/designer/tools/add",
+ getModule,
+ key,
+ getChild: () => import("./farm_designer/tools/add_tool"),
+ childKey: "AddTool"
+ }),
+ route({
+ children: true,
+ $: "/designer/tools/:tool_id",
+ getModule,
+ key,
+ getChild: () => import("./farm_designer/tools/edit_tool"),
+ childKey: "EditTool"
+ }),
route({
children: true,
$: "/designer/groups",
diff --git a/frontend/toast/__tests__/fb_toast_test.ts b/frontend/toast/__tests__/fb_toast_test.ts
index 7d726432b..a0bd08c2e 100644
--- a/frontend/toast/__tests__/fb_toast_test.ts
+++ b/frontend/toast/__tests__/fb_toast_test.ts
@@ -100,4 +100,22 @@ describe("FBToast", () => {
i.doPolling();
expect(i.detach).toHaveBeenCalled();
});
+
+ it("does polling: large timeout value", () => {
+ const [i] = newToast();
+ i.isHovered = false;
+ i.timeout = 8;
+ i.detach = jest.fn();
+ i.doPolling();
+ expect(i.detach).not.toHaveBeenCalled();
+ });
+
+ it("does polling: hovered", () => {
+ const [i] = newToast();
+ i.isHovered = true;
+ i.timeout = 0;
+ i.detach = jest.fn();
+ i.doPolling();
+ expect(i.detach).not.toHaveBeenCalled();
+ });
});
diff --git a/frontend/toast/__tests__/toast_test.ts b/frontend/toast/__tests__/toast_test.ts
index f14fb2ec1..9822a399a 100644
--- a/frontend/toast/__tests__/toast_test.ts
+++ b/frontend/toast/__tests__/toast_test.ts
@@ -26,6 +26,13 @@ describe("toasts", () => {
console.warn);
});
+ it("pops a warning() toast with different title and color", () => {
+ warning("test suite msg", "new title", "purple");
+ expect(createToastOnce)
+ .toHaveBeenCalledWith("test suite msg", "new title", "purple",
+ console.warn);
+ });
+
it("pops a error() toast", () => {
error("test suite msg 2");
expect(createToastOnce).toHaveBeenCalledWith("test suite msg 2",
@@ -34,18 +41,37 @@ describe("toasts", () => {
console.error);
});
+ it("pops a error() toast with different title and color", () => {
+ error("test suite msg", "new title", "purple");
+ expect(createToastOnce)
+ .toHaveBeenCalledWith("test suite msg", "new title", "purple",
+ console.error);
+ });
+
it("pops a success() toast", () => {
success("test suite msg");
expect(createToast)
.toHaveBeenCalledWith("test suite msg", "Success", "green");
});
+ it("pops a success() toast with different title and color", () => {
+ success("test suite msg", "new title", "purple");
+ expect(createToast)
+ .toHaveBeenCalledWith("test suite msg", "new title", "purple");
+ });
+
it("pops a info() toast", () => {
info("test suite msg");
expect(createToast)
.toHaveBeenCalledWith("test suite msg", "FYI", "blue");
});
+ it("pops a info() toast with different title and color", () => {
+ info("test suite msg", "new title", "purple");
+ expect(createToast)
+ .toHaveBeenCalledWith("test suite msg", "new title", "purple");
+ });
+
it("pops a busy() toast", () => {
busy("test suite msg");
expect(createToast)
@@ -64,6 +90,12 @@ describe("toasts", () => {
.toHaveBeenCalledWith("test suite msg", "Did you know?", "dark-blue");
});
+ it("pops a fun() toast with different title and color", () => {
+ fun("test suite msg", "new title", "purple");
+ expect(createToast)
+ .toHaveBeenCalledWith("test suite msg", "new title", "purple");
+ });
+
it("adds the appropriate div to the DOM", () => {
const count1 = document.querySelectorAll(".toast-container").item.length;
expect(count1).toEqual(1);
diff --git a/frontend/ui/empty_state_wrapper.tsx b/frontend/ui/empty_state_wrapper.tsx
index c6bdb5913..30a133652 100644
--- a/frontend/ui/empty_state_wrapper.tsx
+++ b/frontend/ui/empty_state_wrapper.tsx
@@ -16,7 +16,7 @@ interface EmptyStateWrapperProps {
text?: string;
textElement?: JSX.Element;
graphic: string;
- colorScheme?: "plants" | "events";
+ colorScheme?: "plants" | "events" | "gardens" | "points" | "tools" | "groups";
children?: React.ReactNode;
}
diff --git a/public/app-resources/img/icons/point.svg b/public/app-resources/img/icons/point.svg
new file mode 100644
index 000000000..7d780d348
--- /dev/null
+++ b/public/app-resources/img/icons/point.svg
@@ -0,0 +1,140 @@
+
+
+
diff --git a/public/app-resources/img/icons/settings.svg b/public/app-resources/img/icons/settings.svg
new file mode 100644
index 000000000..56f3929dc
--- /dev/null
+++ b/public/app-resources/img/icons/settings.svg
@@ -0,0 +1,169 @@
+
+
+
diff --git a/public/app-resources/img/icons/tool.svg b/public/app-resources/img/icons/tool.svg
new file mode 100644
index 000000000..5e0f9eb58
--- /dev/null
+++ b/public/app-resources/img/icons/tool.svg
@@ -0,0 +1,144 @@
+
+
+