panel updates
parent
3ae676ef54
commit
538ef0f1e4
|
@ -40,4 +40,16 @@ describe("<Link/>", () => {
|
|||
expect(el.html()).toContain("Hey!");
|
||||
el.unmount();
|
||||
});
|
||||
|
||||
it("navigates", () => {
|
||||
const wrapper = shallow(<Link to="/tools" />);
|
||||
wrapper.simulate("click", { preventDefault: jest.fn() });
|
||||
expect(navigate).toHaveBeenCalledWith("/tools");
|
||||
});
|
||||
|
||||
it("doesn't navigate when disabled", () => {
|
||||
const wrapper = shallow(<Link to="/tools" disabled={true} />);
|
||||
wrapper.simulate("click", { preventDefault: jest.fn() });
|
||||
expect(navigate).not.toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -747,6 +747,15 @@ export namespace Content {
|
|||
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.`);
|
||||
|
||||
export const NO_POINTS =
|
||||
trim(`Press "+" to add a point to your garden.`);
|
||||
|
||||
export const NO_GROUPS =
|
||||
trim(`Press "+" to add a point group.`);
|
||||
|
||||
export const ENTER_CROP_SEARCH_TERM =
|
||||
trim(`Search for a crop to add to your garden.`);
|
||||
|
||||
|
@ -756,6 +765,9 @@ export namespace Content {
|
|||
export const CROP_NOT_FOUND_LINK =
|
||||
trim(`add this crop on OpenFarm?`);
|
||||
|
||||
export const NO_TOOLS =
|
||||
trim(`Press "+" to add a new tool.`);
|
||||
|
||||
// Farm Events
|
||||
export const NOTHING_SCHEDULED =
|
||||
trim(`Press "+" to schedule an event.`);
|
||||
|
@ -945,6 +957,7 @@ export enum Actions {
|
|||
SEARCH_QUERY_CHANGE = "SEARCH_QUERY_CHANGE",
|
||||
SELECT_PLANT = "SELECT_PLANT",
|
||||
TOGGLE_HOVERED_PLANT = "TOGGLE_HOVERED_PLANT",
|
||||
TOGGLE_HOVERED_POINT = "TOGGLE_HOVERED_POINT",
|
||||
HOVER_PLANT_LIST_ITEM = "HOVER_PLANT_LIST_ITEM",
|
||||
OF_SEARCH_RESULTS_START = "OF_SEARCH_RESULTS_START",
|
||||
OF_SEARCH_RESULTS_OK = "OF_SEARCH_RESULTS_OK",
|
||||
|
|
|
@ -211,6 +211,38 @@
|
|||
text-overflow: ellipsis;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.point-search-item {
|
||||
cursor: pointer;
|
||||
padding: 0.5rem 1rem;
|
||||
&:hover,
|
||||
&.hovered {
|
||||
background: darken($light_brown, 10%);
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
.saucer {
|
||||
display: inline-block;
|
||||
margin: 0 1rem 0 0;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
.point-search-item-info {
|
||||
text-align: right;
|
||||
font-size: 1rem;
|
||||
padding-top: 0.6rem;
|
||||
padding-right: 1rem;
|
||||
float: right;
|
||||
}
|
||||
.point-search-item-name {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
width: 8em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.thin-search {
|
||||
|
|
|
@ -187,6 +187,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.point-inventory-panel,
|
||||
.plant-inventory-panel {
|
||||
.panel-content {
|
||||
padding: 0;
|
||||
|
@ -197,6 +198,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
.points-panel-tabs {
|
||||
i {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
label {
|
||||
padding-left: 10rem;
|
||||
padding-right: 10rem;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.plant-selection-panel {
|
||||
.panel-action-buttons {
|
||||
position: absolute;
|
||||
|
@ -393,6 +406,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.tools-panel-content {
|
||||
button {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-panel-content {
|
||||
max-height: calc(100vh - 15rem);
|
||||
overflow-y: auto;
|
||||
|
@ -450,3 +469,9 @@
|
|||
min-width: 7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.point-panel-content {
|
||||
.point-search-item-name {
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -829,6 +829,13 @@ ul {
|
|||
color: $panel_green;
|
||||
}
|
||||
}
|
||||
&.gardens {
|
||||
p,
|
||||
h5,
|
||||
a {
|
||||
color: $panel_green;
|
||||
}
|
||||
}
|
||||
&.events {
|
||||
p,
|
||||
h5,
|
||||
|
@ -839,6 +846,33 @@ ul {
|
|||
filter: sepia(1) contrast(1.2) saturate(1.2);
|
||||
}
|
||||
}
|
||||
&.points {
|
||||
p,
|
||||
h5,
|
||||
a {
|
||||
color: $panel_gray;
|
||||
}
|
||||
.empty-state-graphic {
|
||||
filter: saturate(0);
|
||||
}
|
||||
}
|
||||
&.tools {
|
||||
p,
|
||||
h5,
|
||||
a {
|
||||
color: $panel_gray;
|
||||
}
|
||||
}
|
||||
&.groups {
|
||||
p,
|
||||
h5,
|
||||
a {
|
||||
color: $panel_blue;
|
||||
}
|
||||
.empty-state-graphic {
|
||||
filter: hue-rotate(60deg) saturate(0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.farmware-selection-panel {
|
||||
|
|
|
@ -2,28 +2,14 @@ import { designer } from "../reducer";
|
|||
import { Actions } from "../../constants";
|
||||
import { ReduxAction } from "../../redux/interfaces";
|
||||
import {
|
||||
DesignerState, HoveredPlantPayl, CurrentPointPayl, CropLiveSearchResult
|
||||
HoveredPlantPayl, CurrentPointPayl, CropLiveSearchResult
|
||||
} from "../interfaces";
|
||||
import { BotPosition } from "../../devices/interfaces";
|
||||
import { fakeCropLiveSearchResult } from "../../__test_support__/fake_crop_search_result";
|
||||
import { fakeDesignerState } from "../../__test_support__/fake_designer_state";
|
||||
|
||||
describe("designer reducer", () => {
|
||||
const oldState = (): DesignerState => {
|
||||
return {
|
||||
selectedPlants: undefined,
|
||||
hoveredPlant: {
|
||||
plantUUID: undefined,
|
||||
icon: ""
|
||||
},
|
||||
hoveredPlantListItem: undefined,
|
||||
cropSearchQuery: "",
|
||||
cropSearchResults: [],
|
||||
cropSearchInProgress: false,
|
||||
chosenLocation: { x: undefined, y: undefined, z: undefined },
|
||||
currentPoint: undefined,
|
||||
openedSavedGarden: undefined,
|
||||
};
|
||||
};
|
||||
const oldState = fakeDesignerState;
|
||||
|
||||
it("sets search query", () => {
|
||||
const action: ReduxAction<string> = {
|
||||
|
|
|
@ -264,6 +264,7 @@ export interface CameraCalibrationData {
|
|||
}
|
||||
|
||||
export interface CurrentPointPayl {
|
||||
name?: string;
|
||||
cx: number;
|
||||
cy: number;
|
||||
r: number;
|
||||
|
|
|
@ -337,10 +337,12 @@ describe("getMode()", () => {
|
|||
expect(getMode()).toEqual(Mode.addPlant);
|
||||
mockPath = "/app/designer/move_to";
|
||||
expect(getMode()).toEqual(Mode.moveTo);
|
||||
mockPath = "/app/designer/plants/create_point";
|
||||
mockPath = "/app/designer/points/add";
|
||||
expect(getMode()).toEqual(Mode.createPoint);
|
||||
mockPath = "/app/designer/saved_gardens";
|
||||
expect(getMode()).toEqual(Mode.templateView);
|
||||
mockPath = "/app/designer/groups/1";
|
||||
expect(getMode()).toEqual(Mode.addPointToGroup);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ export function DrawnPoint(props: DrawnPointProps) {
|
|||
stroke={color ? color : "green"}
|
||||
strokeOpacity={0.75}
|
||||
strokeWidth={3}
|
||||
fill="none">
|
||||
fill={"none"}>
|
||||
<circle id="point-radius" cx={qx} cy={qy} r={r} strokeDasharray="4 5" />
|
||||
<circle id="point-center" cx={qx} cy={qy} r={2} />
|
||||
</g>;
|
||||
|
|
|
@ -126,6 +126,7 @@ export class GardenMap extends
|
|||
switch (getMode()) {
|
||||
case Mode.boxSelect:
|
||||
case Mode.moveTo:
|
||||
case Mode.points:
|
||||
case Mode.createPoint:
|
||||
return undefined; // For modes without plant interaction
|
||||
default:
|
||||
|
|
|
@ -72,6 +72,9 @@ export interface GardenPointProps {
|
|||
point: TaggedGenericPointer;
|
||||
}
|
||||
|
||||
export interface GardenPointState {
|
||||
}
|
||||
|
||||
interface DragHelpersBaseProps {
|
||||
dragging: boolean;
|
||||
mapTransformProps: MapTransformProps;
|
||||
|
@ -140,6 +143,7 @@ export enum Mode {
|
|||
editPlant = "editPlant",
|
||||
addPlant = "addPlant",
|
||||
moveTo = "moveTo",
|
||||
points = "points",
|
||||
createPoint = "createPoint",
|
||||
templateView = "templateView",
|
||||
addPointToGroup = "addPointToGroup",
|
||||
|
|
|
@ -29,7 +29,9 @@ import {
|
|||
fakeMapTransformProps
|
||||
} from "../../../../../__test_support__/map_transform_props";
|
||||
import { movePlant } from "../../../../actions";
|
||||
import { fakeCropLiveSearchResult } from "../../../../../__test_support__/fake_crop_search_result";
|
||||
import {
|
||||
fakeCropLiveSearchResult
|
||||
} from "../../../../../__test_support__/fake_crop_search_result";
|
||||
import { error } from "../../../../../toast/toast";
|
||||
|
||||
describe("newPlantKindAndBody()", () => {
|
||||
|
|
|
@ -25,7 +25,8 @@ export function PlantLayer(props: PlantLayerProps) {
|
|||
const selected = !!(currentPlant && (p.uuid === currentPlant.uuid));
|
||||
const grayscale = !!(selectedForDel && (selectedForDel.includes(p.uuid)));
|
||||
const plantCategory = unpackUUID(p.uuid).kind === "PlantTemplate"
|
||||
? "saved_gardens/templates" : "plants";
|
||||
? "saved_gardens/templates"
|
||||
: "plants";
|
||||
const plant = <GardenPlant
|
||||
uuid={p.uuid}
|
||||
mapTransformProps={mapTransformProps}
|
||||
|
@ -42,16 +43,12 @@ export function PlantLayer(props: PlantLayerProps) {
|
|||
style: maybeNoPointer(p.body.id ? {} : { pointerEvents: "none" }),
|
||||
key: p.uuid,
|
||||
};
|
||||
if (getMode() === Mode.addPointToGroup) {
|
||||
return <g {...wrapperProps}>
|
||||
{plant}
|
||||
</g>;
|
||||
} else {
|
||||
return <Link {...wrapperProps}
|
||||
return getMode() === Mode.addPointToGroup
|
||||
? <g {...wrapperProps}>{plant}</g>
|
||||
: <Link {...wrapperProps}
|
||||
to={`/app/designer/${plantCategory}/${"" + p.body.id}`}>
|
||||
{plant}
|
||||
</Link>;
|
||||
}
|
||||
})}
|
||||
</g>;
|
||||
}
|
||||
|
|
|
@ -113,12 +113,12 @@ describe("<SpreadOverlapHelper/>", () => {
|
|||
describe("SpreadOverlapHelper functions", () => {
|
||||
|
||||
it("getDiscreteColor()", () => {
|
||||
expect(getDiscreteColor(0, 100)).toEqual("none");
|
||||
expect(getDiscreteColor(10, 100)).toEqual("green");
|
||||
expect(getDiscreteColor(20, 100)).toEqual("green");
|
||||
expect(getDiscreteColor(91, 100)).toEqual("red");
|
||||
expect(getDiscreteColor(61, 100)).toEqual("orange");
|
||||
expect(getDiscreteColor(31, 100)).toEqual("yellow");
|
||||
expect(getDiscreteColor(-2, 100)).toEqual("none");
|
||||
expect(getDiscreteColor(40, 100)).toEqual("yellow");
|
||||
expect(getDiscreteColor(70, 100)).toEqual("orange");
|
||||
expect(getDiscreteColor(100, 100)).toEqual("red");
|
||||
});
|
||||
|
||||
it("getContinuousColor()", () => {
|
||||
|
|
|
@ -101,7 +101,7 @@ describe("<PointsSubMenu />", () => {
|
|||
getConfigValue={jest.fn()} />);
|
||||
clickButton(wrapper, 0, "point creator");
|
||||
expect(history.push).toHaveBeenCalledWith(
|
||||
"/app/designer/plants/create_point");
|
||||
"/app/designer/points/add");
|
||||
});
|
||||
|
||||
it("shows historic points", () => {
|
||||
|
|
|
@ -40,10 +40,11 @@ export const PointsSubMenu = ({ toggle, getConfigValue }: {
|
|||
getConfigValue: GetWebAppConfigValue
|
||||
}) =>
|
||||
<div className="map-points-submenu">
|
||||
{!DevSettings.futureFeaturesEnabled() &&
|
||||
<button className={"fb-button green"}
|
||||
onClick={() => history.push("/app/designer/plants/create_point")}>
|
||||
onClick={() => history.push("/app/designer/points/add")}>
|
||||
{t("Point Creator")}
|
||||
</button>
|
||||
</button>}
|
||||
<LayerToggle
|
||||
value={!!getConfigValue(BooleanSetting.show_historic_points)}
|
||||
label={t("Historic Points?")}
|
||||
|
|
|
@ -299,7 +299,10 @@ export const getMode = (): Mode => {
|
|||
if (pathArray[4] === "select") { return Mode.boxSelect; }
|
||||
if (pathArray[4] === "crop_search") { return Mode.addPlant; }
|
||||
if (pathArray[3] === "move_to") { return Mode.moveTo; }
|
||||
if (pathArray[4] === "create_point") { return Mode.createPoint; }
|
||||
if (pathArray[3] === "points") {
|
||||
if (pathArray[4] === "add") { return Mode.createPoint; }
|
||||
return Mode.points;
|
||||
}
|
||||
if (savedGardenOpen(pathArray)) { return Mode.templateView; }
|
||||
}
|
||||
return Mode.none;
|
||||
|
@ -337,6 +340,7 @@ export const maybeNoPointer =
|
|||
case Mode.boxSelect:
|
||||
case Mode.clickToAdd:
|
||||
case Mode.moveTo:
|
||||
case Mode.points:
|
||||
case Mode.createPoint:
|
||||
return { "pointerEvents": "none" };
|
||||
default:
|
||||
|
|
|
@ -9,8 +9,10 @@ export enum Panel {
|
|||
Plants = "Plants",
|
||||
FarmEvents = "FarmEvents",
|
||||
SavedGardens = "SavedGardens",
|
||||
Tools = "Tools",
|
||||
Settings = "Settings",
|
||||
Groups = "Groups"
|
||||
Points = "Points",
|
||||
Groups = "Groups",
|
||||
}
|
||||
|
||||
type Tabs = keyof typeof Panel;
|
||||
|
@ -20,7 +22,9 @@ export const TAB_COLOR: { [key in Panel]: string } = {
|
|||
[Panel.Plants]: "green",
|
||||
[Panel.FarmEvents]: "yellow",
|
||||
[Panel.SavedGardens]: "green",
|
||||
[Panel.Tools]: "gray",
|
||||
[Panel.Settings]: "gray",
|
||||
[Panel.Points]: "gray",
|
||||
[Panel.Groups]: "blue",
|
||||
};
|
||||
|
||||
|
@ -31,8 +35,10 @@ export const TAB_ICON: { [key in Panel]: string } = {
|
|||
[Panel.Plants]: iconFile("plant"),
|
||||
[Panel.FarmEvents]: iconFile("calendar"),
|
||||
[Panel.SavedGardens]: iconFile("gardens"),
|
||||
[Panel.Settings]: iconFile("gardens"),
|
||||
[Panel.Groups]: iconFile("groups")
|
||||
[Panel.Tools]: iconFile("tool"),
|
||||
[Panel.Settings]: iconFile("settings"),
|
||||
[Panel.Points]: iconFile("point"),
|
||||
[Panel.Groups]: iconFile("groups"),
|
||||
};
|
||||
|
||||
const getCurrentTab = (): Tabs => {
|
||||
|
@ -43,8 +49,12 @@ const getCurrentTab = (): Tabs => {
|
|||
return Panel.FarmEvents;
|
||||
} else if (pathArray.includes("saved_gardens")) {
|
||||
return Panel.SavedGardens;
|
||||
} else if (pathArray.includes("tools")) {
|
||||
return Panel.Tools;
|
||||
} else if (pathArray.includes("settings")) {
|
||||
return Panel.Settings;
|
||||
} else if (pathArray.includes("points")) {
|
||||
return Panel.Points;
|
||||
} else if (pathArray.includes("groups")) {
|
||||
return Panel.Groups;
|
||||
} else {
|
||||
|
@ -84,9 +94,15 @@ export function DesignerNavTabs(props: { hidden?: boolean }) {
|
|||
{DevSettings.futureFeaturesEnabled() &&
|
||||
<NavTab panel={Panel.SavedGardens}
|
||||
linkTo={"/app/designer/saved_gardens"} title={t("Gardens")} />}
|
||||
{DevSettings.futureFeaturesEnabled() &&
|
||||
<NavTab panel={Panel.Points}
|
||||
linkTo={"/app/designer/points"} title={t("Points")} />}
|
||||
{DevSettings.futureFeaturesEnabled() &&
|
||||
<NavTab panel={Panel.Groups}
|
||||
linkTo={"/app/designer/groups"} title={t("Groups")} />}
|
||||
{DevSettings.futureFeaturesEnabled() &&
|
||||
<NavTab panel={Panel.Tools}
|
||||
linkTo={"/app/designer/tools"} title={t("Tools")} />}
|
||||
<NavTab panel={Panel.Settings} icon={"fa fa-gear"}
|
||||
linkTo={"/app/designer/settings"} title={t("Settings")} />
|
||||
</div>
|
||||
|
|
|
@ -103,7 +103,7 @@ describe("<CreatePoints />", () => {
|
|||
const wrapper = shallow<CreatePoints>(<CreatePoints {...fakeProps()} />);
|
||||
wrapper.instance().getPointData();
|
||||
expect(wrapper.instance().state).toEqual({
|
||||
color: "green", cx: 0, cy: 0, r: 1
|
||||
color: "green", cx: 0, cy: 0, r: 1, name: "Created Point"
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { mapStateToProps } from "../map_state_to_props";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import { buildResourceIndex } from "../../../__test_support__/resource_index_builder";
|
||||
import { fakePlant } from "../../../__test_support__/fake_state/resources";
|
||||
import {
|
||||
buildResourceIndex
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
import {
|
||||
fakePlant, fakePlantTemplate
|
||||
} from "../../../__test_support__/fake_state/resources";
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns findPlant()", () => {
|
||||
|
@ -15,4 +19,16 @@ describe("mapStateToProps()", () => {
|
|||
expect(result.findPlant("10")).toEqual(
|
||||
expect.objectContaining({ uuid }));
|
||||
});
|
||||
|
||||
it("finds plant template", () => {
|
||||
const state = fakeState();
|
||||
const template = fakePlantTemplate();
|
||||
template.body.id = 10;
|
||||
state.resources = buildResourceIndex([template]);
|
||||
const uuid = Object.keys(state.resources.index.all)[0];
|
||||
state.resources.consumers.farm_designer.openedSavedGarden = "uuid";
|
||||
const result = mapStateToProps(state);
|
||||
expect(result.findPlant("10")).toEqual(
|
||||
expect.objectContaining({ uuid }));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
jest.mock("react-redux", () => ({ connect: jest.fn() }));
|
||||
|
||||
let mockPath = "/app/designer/points/1";
|
||||
jest.mock("../../../history", () => ({
|
||||
getPathArray: jest.fn(() => mockPath.split("/")),
|
||||
history: { push: jest.fn() }
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { EditPoint, EditPointProps, mapStateToProps } from "../point_info";
|
||||
import { fakePoint } from "../../../__test_support__/fake_state/resources";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import { buildResourceIndex } from "../../../__test_support__/resource_index_builder";
|
||||
|
||||
describe("<EditPoint />", () => {
|
||||
const fakeProps = (): EditPointProps => ({
|
||||
findPoint: fakePoint,
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders redirect", () => {
|
||||
mockPath = "/app/designer/points";
|
||||
const wrapper = mount(<EditPoint {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("Redirecting...");
|
||||
});
|
||||
|
||||
it("renders with points", () => {
|
||||
mockPath = "/app/designer/points/1";
|
||||
const wrapper = mount(<EditPoint {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("Edit Point 1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const state = fakeState();
|
||||
const point = fakePoint();
|
||||
point.body.id = 1;
|
||||
state.resources = buildResourceIndex([point]);
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.findPoint(1)).toEqual(point);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,78 @@
|
|||
jest.mock("react-redux", () => ({ connect: jest.fn() }));
|
||||
|
||||
jest.mock("../../../history", () => ({
|
||||
push: jest.fn(),
|
||||
getPathArray: () => [],
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { Points, PointsProps } from "../point_inventory";
|
||||
import { fakePoint } from "../../../__test_support__/fake_state/resources";
|
||||
import { push } from "../../../history";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import {
|
||||
buildResourceIndex
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
import { mapStateToProps } from "../point_inventory";
|
||||
|
||||
describe("<Points />", () => {
|
||||
const fakeProps = (): PointsProps => ({
|
||||
points: [],
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders no points", () => {
|
||||
const wrapper = mount(<Points {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("No points yet.");
|
||||
});
|
||||
|
||||
it("renders points", () => {
|
||||
const p = fakeProps();
|
||||
p.points = [fakePoint()];
|
||||
const wrapper = mount(<Points {...p} />);
|
||||
expect(wrapper.text()).toContain("Point 1");
|
||||
});
|
||||
|
||||
it("navigates to point info", () => {
|
||||
const p = fakeProps();
|
||||
p.points = [fakePoint()];
|
||||
p.points[0].body.id = 1;
|
||||
const wrapper = mount(<Points {...p} />);
|
||||
wrapper.find(".point-search-item").first().simulate("click");
|
||||
expect(push).toHaveBeenCalledWith("/app/designer/points/1");
|
||||
});
|
||||
|
||||
it("changes search term", () => {
|
||||
const p = fakeProps();
|
||||
p.points = [fakePoint(), fakePoint()];
|
||||
p.points[0].body.name = "point 0";
|
||||
p.points[1].body.name = "point 1";
|
||||
const wrapper = shallow<Points>(<Points {...p} />);
|
||||
wrapper.find("input").first().simulate("change",
|
||||
{ currentTarget: { value: "0" } });
|
||||
expect(wrapper.state().searchTerm).toEqual("0");
|
||||
});
|
||||
|
||||
it("filters points", () => {
|
||||
const p = fakeProps();
|
||||
p.points = [fakePoint(), fakePoint()];
|
||||
p.points[0].body.name = "point 0";
|
||||
p.points[1].body.name = "point 1";
|
||||
const wrapper = mount(<Points {...p} />);
|
||||
wrapper.setState({ searchTerm: "0" });
|
||||
expect(wrapper.text()).not.toContain("point 1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const state = fakeState();
|
||||
const point = fakePoint();
|
||||
const discarded = fakePoint();
|
||||
discarded.body.discarded_at = "2016-05-22T05:00:00.000Z";
|
||||
state.resources = buildResourceIndex([point, discarded]);
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.points).toEqual([point]);
|
||||
});
|
||||
});
|
|
@ -1,5 +1,4 @@
|
|||
import * as React from "react";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { Everything, ResourceColor } from "../../interfaces";
|
||||
import { initSave } from "../../api/crud";
|
||||
|
@ -30,6 +29,7 @@ export interface CreatePointsProps {
|
|||
}
|
||||
|
||||
interface CreatePointsState {
|
||||
name: string;
|
||||
cx: number;
|
||||
cy: number;
|
||||
r: number;
|
||||
|
@ -51,6 +51,7 @@ export class CreatePoints
|
|||
getPointData = () => {
|
||||
const point = this.props.currentPoint;
|
||||
this.setState({
|
||||
name: point ? point.name : "Created Point",
|
||||
cx: point ? point.cx : 0,
|
||||
cy: point ? point.cy : 0,
|
||||
r: point ? point.r : 1,
|
||||
|
@ -63,7 +64,9 @@ export class CreatePoints
|
|||
type: Actions.SET_CURRENT_POINT_DATA,
|
||||
payload: undefined
|
||||
});
|
||||
this.setState({ cx: undefined, cy: undefined, r: undefined, color: undefined });
|
||||
this.setState({
|
||||
cx: undefined, cy: undefined, r: undefined, color: undefined
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -77,14 +80,21 @@ export class CreatePoints
|
|||
});
|
||||
}
|
||||
|
||||
/** Update number fields. */
|
||||
updateNumberValue = (key: keyof Omit<CreatePointsState, "color">) => {
|
||||
/** Update fields. */
|
||||
updateValue = (key: keyof CreatePointsState) => {
|
||||
return (e: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
const value = parseIntInput(e.currentTarget.value);
|
||||
const { value } = e.currentTarget;
|
||||
this.setState({ [key]: value });
|
||||
if (this.props.currentPoint) {
|
||||
const point = clone(this.props.currentPoint);
|
||||
switch (key) {
|
||||
case "name":
|
||||
case "color":
|
||||
point[key] = value;
|
||||
break;
|
||||
default:
|
||||
point[key] = parseIntInput(value);
|
||||
}
|
||||
this.props.dispatch({
|
||||
type: Actions.SET_CURRENT_POINT_DATA,
|
||||
payload: point
|
||||
|
@ -96,30 +106,42 @@ export class CreatePoints
|
|||
changeColor = (color: ResourceColor) => {
|
||||
this.setState({ color });
|
||||
if (this.props.currentPoint) {
|
||||
const { cx, cy, r } = this.props.currentPoint;
|
||||
const { cx, cy, r, name } = this.props.currentPoint;
|
||||
this.props.dispatch({
|
||||
type: Actions.SET_CURRENT_POINT_DATA,
|
||||
payload: { cx, cy, r, color }
|
||||
payload: { cx, cy, r, color, name }
|
||||
});
|
||||
}
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
createPoint = () => {
|
||||
const { cx, cy, r, color } = this.state;
|
||||
const { cx, cy, r, color, name } = this.state;
|
||||
const body: GenericPointer = {
|
||||
pointer_type: "GenericPointer",
|
||||
name: "Created Point",
|
||||
name: name || "Created Point",
|
||||
meta: { color, created_by: "farm-designer" },
|
||||
x: (cx || 0),
|
||||
y: (cy || 0),
|
||||
x: cx || 0,
|
||||
y: cy || 0,
|
||||
z: 0,
|
||||
radius: (r || 1),
|
||||
radius: r || 1,
|
||||
};
|
||||
this.props.dispatch(initSave("Point", body));
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
PointName = () =>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<label>{t("Name")}</label>
|
||||
<BlurableInput
|
||||
name="name"
|
||||
type="text"
|
||||
onCommit={this.updateValue("name")}
|
||||
value={this.state.name || "Created Point"} />
|
||||
</Col>
|
||||
</Row>;
|
||||
|
||||
PointProperties = () => {
|
||||
const { cx, cy, r, color } = this.state;
|
||||
return <Row>
|
||||
|
@ -128,7 +150,7 @@ export class CreatePoints
|
|||
<BlurableInput
|
||||
name="cx"
|
||||
type="number"
|
||||
onCommit={this.updateNumberValue("cx")}
|
||||
onCommit={this.updateValue("cx")}
|
||||
value={cx || 0} />
|
||||
</Col>
|
||||
<Col xs={3}>
|
||||
|
@ -136,7 +158,7 @@ export class CreatePoints
|
|||
<BlurableInput
|
||||
name="cy"
|
||||
type="number"
|
||||
onCommit={this.updateNumberValue("cy")}
|
||||
onCommit={this.updateValue("cy")}
|
||||
value={cy || 0} />
|
||||
</Col>
|
||||
<Col xs={3}>
|
||||
|
@ -144,7 +166,7 @@ export class CreatePoints
|
|||
<BlurableInput
|
||||
name="r"
|
||||
type="number"
|
||||
onCommit={this.updateNumberValue("r")}
|
||||
onCommit={this.updateValue("r")}
|
||||
value={r || 0}
|
||||
min={0} />
|
||||
</Col>
|
||||
|
@ -196,8 +218,10 @@ export class CreatePoints
|
|||
panelName={"point-creation"}
|
||||
panelColor={"brown"}
|
||||
title={t("Create point")}
|
||||
backTo={"/app/designer/points"}
|
||||
description={Content.CREATE_POINTS_DESCRIPTION} />
|
||||
<DesignerPanelContent panelName={"point-creation"}>
|
||||
<this.PointName />
|
||||
<this.PointProperties />
|
||||
<this.PointActions />
|
||||
<this.DeleteAllPoints />
|
||||
|
|
|
@ -167,7 +167,7 @@ interface ListItemProps {
|
|||
children: React.ReactChild;
|
||||
}
|
||||
|
||||
const ListItem = (props: ListItemProps) =>
|
||||
export const ListItem = (props: ListItemProps) =>
|
||||
<li>
|
||||
<p>
|
||||
{props.name}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelHeader, DesignerPanelContent
|
||||
} from "./designer_panel";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { history, getPathArray } from "../../history";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { TaggedPoint } from "farmbot";
|
||||
import { maybeFindPointById } from "../../resources/selectors";
|
||||
|
||||
export interface EditPointProps {
|
||||
dispatch: Function;
|
||||
findPoint(id: number): TaggedPoint | undefined;
|
||||
}
|
||||
|
||||
export const mapStateToProps = (props: Everything): EditPointProps => ({
|
||||
dispatch: props.dispatch,
|
||||
findPoint: id => maybeFindPointById(props.resources.index, id),
|
||||
});
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export class EditPoint extends React.Component<EditPointProps, {}> {
|
||||
get stringyID() { return getPathArray()[4] || ""; }
|
||||
get point() {
|
||||
if (this.stringyID) {
|
||||
return this.props.findPoint(parseInt(this.stringyID));
|
||||
}
|
||||
}
|
||||
|
||||
fallback = () => {
|
||||
history.push("/app/designer/points");
|
||||
return <span>{t("Redirecting...")}</span>;
|
||||
}
|
||||
|
||||
default = (point: TaggedPoint) => {
|
||||
return <DesignerPanel panelName={"plant-info"} panelColor={"green"}>
|
||||
<DesignerPanelHeader
|
||||
panelName={"plant-info"}
|
||||
panelColor={"gray"}
|
||||
title={`${t("Edit")} ${point.body.name}`}
|
||||
backTo={"/app/designer/points"}>
|
||||
</DesignerPanelHeader>
|
||||
<DesignerPanelContent panelName={"plants"}>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.point ? this.default(this.point) : this.fallback();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { PointInventoryItem } from "./point_inventory_item";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { DesignerNavTabs, Panel } from "../panel_header";
|
||||
import {
|
||||
EmptyStateWrapper, EmptyStateGraphic
|
||||
} from "../../ui/empty_state_wrapper";
|
||||
import { Content } from "../../constants";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelContent, DesignerPanelTop
|
||||
} from "./designer_panel";
|
||||
import { selectAllGenericPointers } from "../../resources/selectors";
|
||||
import { TaggedGenericPointer } from "farmbot";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
export interface PointsProps {
|
||||
points: TaggedGenericPointer[];
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
interface PointsState {
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
export function mapStateToProps(props: Everything): PointsProps {
|
||||
return {
|
||||
points: selectAllGenericPointers(props.resources.index)
|
||||
.filter(x => !x.body.discarded_at),
|
||||
dispatch: props.dispatch,
|
||||
};
|
||||
}
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export class Points extends React.Component<PointsProps, PointsState> {
|
||||
|
||||
state: PointsState = { searchTerm: "" };
|
||||
|
||||
update = ({ currentTarget }: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
this.setState({ searchTerm: currentTarget.value });
|
||||
}
|
||||
|
||||
render() {
|
||||
return <DesignerPanel panelName={"point-inventory"} panelColor={"brown"}>
|
||||
<DesignerNavTabs />
|
||||
<DesignerPanelTop
|
||||
panel={Panel.Points}
|
||||
linkTo={"/app/designer/points/add"}
|
||||
title={t("Add point")}>
|
||||
<input type="text" onChange={this.update}
|
||||
placeholder={t("Search your points...")} />
|
||||
</DesignerPanelTop>
|
||||
<DesignerPanelContent panelName={"point"}>
|
||||
<EmptyStateWrapper
|
||||
notEmpty={this.props.points.length > 0}
|
||||
graphic={EmptyStateGraphic.no_crop_results}
|
||||
title={t("No points yet.")}
|
||||
text={Content.NO_POINTS}
|
||||
colorScheme={"points"}>
|
||||
{this.props.points
|
||||
.filter(p => p.body.name.toLowerCase()
|
||||
.includes(this.state.searchTerm.toLowerCase()))
|
||||
.map(p => {
|
||||
return <PointInventoryItem
|
||||
key={p.uuid}
|
||||
tpp={p}
|
||||
dispatch={this.props.dispatch} />;
|
||||
})}
|
||||
</EmptyStateWrapper>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import * as React from "react";
|
||||
import { TaggedGenericPointer } from "farmbot";
|
||||
import { Saucer } from "../../ui";
|
||||
import { push } from "../../history";
|
||||
|
||||
export interface PointInventoryItemProps {
|
||||
tpp: TaggedGenericPointer;
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
// The individual points that show up in the farm designer sub nav.
|
||||
export class PointInventoryItem extends
|
||||
React.Component<PointInventoryItemProps, {}> {
|
||||
|
||||
render() {
|
||||
const point = this.props.tpp.body;
|
||||
const pointId = (point.id || "ERR_NO_POINT_ID").toString();
|
||||
|
||||
const click = () => {
|
||||
push(`/app/designer/points/${pointId}`);
|
||||
};
|
||||
|
||||
const label = point.name || "Unknown point";
|
||||
|
||||
return <div
|
||||
className={`point-search-item`}
|
||||
key={pointId}
|
||||
onClick={click}>
|
||||
<Saucer color={point.meta.color || "green"} />
|
||||
<span className="point-search-item-name">
|
||||
{label}
|
||||
</span>
|
||||
<p className="point-search-item-info">
|
||||
{`(${point.x}, ${point.y}) ⌀${point.radius * 2}`}
|
||||
</p>
|
||||
</div>;
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
jest.mock("../../../api/crud", () => {
|
||||
return {
|
||||
jest.mock("../../../api/crud", () => ({
|
||||
save: jest.fn(),
|
||||
overwrite: jest.fn()
|
||||
};
|
||||
});
|
||||
}));
|
||||
|
||||
jest.mock("../../actions", () => {
|
||||
return { toggleHoveredPlant: jest.fn() };
|
||||
});
|
||||
jest.mock("../../actions", () => ({
|
||||
toggleHoveredPlant: jest.fn()
|
||||
}));
|
||||
|
||||
import React from "react";
|
||||
import { GroupDetailActive, LittleIcon } from "../group_detail_active";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { fakePointGroup, fakePlant } from "../../../__test_support__/fake_state/resources";
|
||||
import {
|
||||
fakePointGroup, fakePlant
|
||||
} from "../../../__test_support__/fake_state/resources";
|
||||
import { save, overwrite } from "../../../api/crud";
|
||||
import { toggleHoveredPlant } from "../../actions";
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { fakePointGroup, fakePlant } from "../../../__test_support__/fake_state/resources";
|
||||
import {
|
||||
fakePointGroup, fakePlant
|
||||
} from "../../../__test_support__/fake_state/resources";
|
||||
const GOOD_ID = 9;
|
||||
|
||||
const mockPlant = fakePlant();
|
||||
|
@ -24,7 +26,9 @@ import { GroupDetailActive } from "../group_detail_active";
|
|||
import { GroupDetail } from "../group_detail";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import { createStore } from "redux";
|
||||
import { buildResourceIndex } from "../../../__test_support__/resource_index_builder";
|
||||
import {
|
||||
buildResourceIndex
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
import { push } from "../../../history";
|
||||
|
||||
describe("<GroupDetail />", () => {
|
||||
|
|
|
@ -1,24 +1,18 @@
|
|||
jest.mock("../../../history", () => {
|
||||
return {
|
||||
jest.mock("react-redux", () => ({ connect: jest.fn() }));
|
||||
|
||||
jest.mock("../../../history", () => ({
|
||||
getPathArray: jest.fn(() => ["L", "O", "L"]),
|
||||
history: {
|
||||
push: jest.fn(),
|
||||
}
|
||||
};
|
||||
});
|
||||
history: { push: jest.fn() }
|
||||
}));
|
||||
|
||||
import React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { GroupListPanel, newUpdater } from "../group_list_panel";
|
||||
import { Provider } from "react-redux";
|
||||
import { createStore, DeepPartial } from "redux";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import { buildResourceIndex } from "../../../__test_support__/resource_index_builder";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { GroupListPanel, GroupListPanelProps } from "../group_list_panel";
|
||||
import { fakePointGroup } from "../../../__test_support__/fake_state/resources";
|
||||
import { history } from "../../../history";
|
||||
|
||||
describe("<GroupListPanel />", () => {
|
||||
const setUpTests = () => {
|
||||
const fakeProps = (): GroupListPanelProps => {
|
||||
const fake1 = fakePointGroup();
|
||||
fake1.body.name = "one";
|
||||
fake1.body.id = 9;
|
||||
|
@ -27,35 +21,34 @@ describe("<GroupListPanel />", () => {
|
|||
const fake2 = fakePointGroup();
|
||||
fake2.body.name = "two";
|
||||
|
||||
const state = fakeState();
|
||||
state.resources = buildResourceIndex([fake1, fake2]);
|
||||
const store = createStore(s => s, state);
|
||||
|
||||
return { store, fake1, fake2 };
|
||||
return { dispatch: jest.fn(), groups: [fake1, fake2] };
|
||||
};
|
||||
|
||||
it("handles the `change` event", () => {
|
||||
const setState = jest.fn();
|
||||
const fn = newUpdater(setState, "searchTerm");
|
||||
type E = React.SyntheticEvent<HTMLInputElement>;
|
||||
const e: DeepPartial<E> = { currentTarget: { value: "X" } };
|
||||
fn(e as E);
|
||||
expect(setState).toHaveBeenCalledWith({ searchTerm: "X" });
|
||||
it("changes search term", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow<GroupListPanel>(<GroupListPanel {...p} />);
|
||||
wrapper.find("input").first().simulate("change",
|
||||
{ currentTarget: { value: "one" } });
|
||||
expect(wrapper.state().searchTerm).toEqual("one");
|
||||
});
|
||||
|
||||
it("renders relevant group data as a list", () => {
|
||||
const { store, fake1, fake2 } = setUpTests();
|
||||
|
||||
const el = mount(<Provider store={store}>
|
||||
<GroupListPanel {...({} as GroupListPanel["props"])} />
|
||||
</Provider>);
|
||||
el.find(".plant-search-item").first().simulate("click");
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<GroupListPanel {...p} />);
|
||||
wrapper.find(".plant-search-item").first().simulate("click");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/groups/9");
|
||||
|
||||
const text = el.text();
|
||||
expect(text).toContain("3 items");
|
||||
expect(text).toContain("0 items");
|
||||
expect(text).toContain(fake2.body.name);
|
||||
expect(text).toContain(fake1.body.name);
|
||||
["3 items",
|
||||
"0 items",
|
||||
p.groups[0].body.name,
|
||||
p.groups[1].body.name].map(string =>
|
||||
expect(wrapper.text()).toContain(string));
|
||||
});
|
||||
|
||||
it("renders no groups", () => {
|
||||
const p = fakeProps();
|
||||
p.groups = [];
|
||||
const wrapper = mount(<GroupListPanel {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("no groups yet");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,13 +3,17 @@ import { connect } from "react-redux";
|
|||
import { Everything } from "../../interfaces";
|
||||
import { Panel, DesignerNavTabs } from "../panel_header";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { DesignerPanel, DesignerPanelTop, DesignerPanelContent } from "../plants/designer_panel";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelTop, DesignerPanelContent
|
||||
} from "../plants/designer_panel";
|
||||
import { findAll } from "../../resources/find_all";
|
||||
import { TaggedPointGroup } from "farmbot";
|
||||
import { history } from "../../history";
|
||||
import { GroupInventoryItem } from "./group_inventory_item";
|
||||
import { EmptyStateWrapper, EmptyStateGraphic } from "../../ui/empty_state_wrapper";
|
||||
import { Content } from "../../constants";
|
||||
|
||||
interface GroupListPanelProps {
|
||||
export interface GroupListPanelProps {
|
||||
dispatch: Function;
|
||||
groups: TaggedPointGroup[];
|
||||
}
|
||||
|
@ -23,20 +27,16 @@ function mapStateToProps(props: Everything): GroupListPanelProps {
|
|||
findAll<TaggedPointGroup>(props.resources.index, "PointGroup");
|
||||
return { groups, dispatch: props.dispatch };
|
||||
}
|
||||
/** I wanted this to be a member method of <GroupListPanel/> but testing was
|
||||
* too wonky due to @connect(). If anyone knows a way to test this, feel free
|
||||
* to do a non-curried solution. -RC*/
|
||||
export const newUpdater =
|
||||
(cb: Function, key: keyof GroupListPanel["state"]) =>
|
||||
(e: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
cb({ [key]: e.currentTarget.value });
|
||||
};
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export class GroupListPanel extends React.Component<GroupListPanelProps, State> {
|
||||
|
||||
state: State = { searchTerm: "" };
|
||||
|
||||
update = ({ currentTarget }: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
this.setState({ searchTerm: currentTarget.value });
|
||||
}
|
||||
|
||||
navigate = (id: number) => history.push(`/app/designer/groups/${id}`);
|
||||
|
||||
render() {
|
||||
|
@ -47,14 +47,19 @@ export class GroupListPanel extends React.Component<GroupListPanelProps, State>
|
|||
linkTo={"/app/designer/plants/select"}
|
||||
title={t("Add Group")}>
|
||||
<input type="text"
|
||||
onChange={newUpdater(this.setState, "searchTerm")}
|
||||
onChange={this.update}
|
||||
placeholder={t("Search your groups...")} />
|
||||
</DesignerPanelTop>
|
||||
<DesignerPanelContent panelName={"groups"}>
|
||||
{this
|
||||
.props
|
||||
.groups
|
||||
.filter(p => p.body.name.toLowerCase().includes(this.state.searchTerm.toLowerCase()))
|
||||
<EmptyStateWrapper
|
||||
notEmpty={this.props.groups.length > 0}
|
||||
title={t("No groups yet.")}
|
||||
text={t(Content.NO_GROUPS)}
|
||||
colorScheme="groups"
|
||||
graphic={EmptyStateGraphic.plants}>
|
||||
{this.props.groups
|
||||
.filter(p => p.body.name.toLowerCase()
|
||||
.includes(this.state.searchTerm.toLowerCase()))
|
||||
.map(group => <GroupInventoryItem
|
||||
key={group.uuid}
|
||||
group={group}
|
||||
|
@ -62,6 +67,7 @@ export class GroupListPanel extends React.Component<GroupListPanelProps, State>
|
|||
dispatch={this.props.dispatch}
|
||||
onClick={() => this.navigate(group.body.id || 0)}
|
||||
/>)}
|
||||
</EmptyStateWrapper>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import * as React from "react";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { history } from "../../history";
|
||||
import { unselectPlant } from "../actions";
|
||||
import {
|
||||
|
@ -19,6 +18,7 @@ import {
|
|||
import { DevSettings } from "../../account/dev/dev_support";
|
||||
import { DesignerNavTabs } from "../panel_header";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { EmptyStateWrapper, EmptyStateGraphic } from "../../ui/empty_state_wrapper";
|
||||
|
||||
export const mapStateToProps = (props: Everything): SavedGardensProps => ({
|
||||
savedGardens: selectAllSavedGardens(props.resources.index),
|
||||
|
@ -58,9 +58,14 @@ export class SavedGardens extends React.Component<SavedGardensProps, {}> {
|
|||
plantTemplates={this.props.plantTemplates}
|
||||
dispatch={this.props.dispatch} />
|
||||
<hr />
|
||||
{this.props.savedGardens.length > 0
|
||||
? <SavedGardenList {...this.props} />
|
||||
: <p>{t("No saved gardens yet.")}</p>}
|
||||
<EmptyStateWrapper
|
||||
notEmpty={this.props.savedGardens.length > 0}
|
||||
title={t("No saved gardens yet.")}
|
||||
// text={t(Content.NO_GARDENS)}
|
||||
colorScheme="gardens"
|
||||
graphic={EmptyStateGraphic.plants}>
|
||||
<SavedGardenList {...this.props} />
|
||||
</EmptyStateWrapper>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
jest.mock("react-redux", () => ({ connect: jest.fn() }));
|
||||
|
||||
jest.mock("../../../api/crud", () => ({ initSave: jest.fn() }));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { AddTool, AddToolProps, mapStateToProps } from "../add_tool";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import { SaveBtn } from "../../../ui";
|
||||
import { initSave } from "../../../api/crud";
|
||||
|
||||
describe("<AddTool />", () => {
|
||||
const fakeProps = (): AddToolProps => ({
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<AddTool {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("Add new tool");
|
||||
});
|
||||
|
||||
it("edits tool name", () => {
|
||||
const wrapper = shallow<AddTool>(<AddTool {...fakeProps()} />);
|
||||
expect(wrapper.state().toolName).toEqual("");
|
||||
wrapper.find("input").simulate("change",
|
||||
{ currentTarget: { value: "new name" } });
|
||||
expect(wrapper.state().toolName).toEqual("new name");
|
||||
});
|
||||
|
||||
it("saves", () => {
|
||||
const wrapper = shallow(<AddTool {...fakeProps()} />);
|
||||
wrapper.setState({ toolName: "Foo" });
|
||||
wrapper.find(SaveBtn).simulate("click");
|
||||
expect(initSave).toHaveBeenCalledWith("Tool", { name: "Foo" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const props = mapStateToProps(fakeState());
|
||||
expect(props.dispatch).toEqual(expect.any(Function));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
jest.mock("react-redux", () => ({ connect: jest.fn() }));
|
||||
|
||||
jest.mock("../../../api/crud", () => ({ edit: jest.fn() }));
|
||||
|
||||
jest.mock("../../../history", () => ({
|
||||
history: { push: jest.fn() },
|
||||
getPathArray: () => "/app/designer/tools/1".split("/"),
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { EditTool, EditToolProps, mapStateToProps } from "../edit_tool";
|
||||
import { fakeTool } from "../../../__test_support__/fake_state/resources";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import {
|
||||
buildResourceIndex
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
import { SaveBtn } from "../../../ui";
|
||||
import { history } from "../../../history";
|
||||
import { edit } from "../../../api/crud";
|
||||
|
||||
describe("<EditTool />", () => {
|
||||
const fakeProps = (): EditToolProps => ({
|
||||
findTool: jest.fn(() => fakeTool()),
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<EditTool {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("Edit Foo");
|
||||
});
|
||||
|
||||
it("redirects", () => {
|
||||
const p = fakeProps();
|
||||
p.findTool = jest.fn(() => undefined);
|
||||
const wrapper = mount(<EditTool {...p} />);
|
||||
expect(wrapper.text()).toContain("Redirecting...");
|
||||
});
|
||||
|
||||
it("edits tool name", () => {
|
||||
const wrapper = shallow<EditTool>(<EditTool {...fakeProps()} />);
|
||||
wrapper.find("input").simulate("change",
|
||||
{ currentTarget: { value: "new name" } });
|
||||
expect(wrapper.state().toolName).toEqual("new name");
|
||||
});
|
||||
|
||||
it("saves", () => {
|
||||
const wrapper = shallow(<EditTool {...fakeProps()} />);
|
||||
wrapper.find(SaveBtn).simulate("click");
|
||||
expect(edit).toHaveBeenCalledWith(expect.any(Object), { name: "Foo" });
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/tools");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const state = fakeState();
|
||||
const tool = fakeTool();
|
||||
tool.body.id = 123;
|
||||
state.resources = buildResourceIndex([tool]);
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.findTool("" + tool.body.id)).toEqual(tool);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,86 @@
|
|||
jest.mock("react-redux", () => ({ connect: jest.fn() }));
|
||||
|
||||
jest.mock("../../../history", () => ({
|
||||
history: { push: jest.fn() },
|
||||
getPathArray: () => "/app/designer/tools".split("/"),
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { Tools, ToolsProps, mapStateToProps } from "../index";
|
||||
import {
|
||||
fakeTool, fakeToolSlot
|
||||
} from "../../../__test_support__/fake_state/resources";
|
||||
import { history } from "../../../history";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import { buildResourceIndex } from "../../../__test_support__/resource_index_builder";
|
||||
|
||||
describe("<Tools />", () => {
|
||||
const fakeProps = (): ToolsProps => ({
|
||||
tools: [],
|
||||
toolSlots: [],
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders with no tools", () => {
|
||||
const wrapper = mount(<Tools {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("Add a tool");
|
||||
});
|
||||
|
||||
it("renders with tools", () => {
|
||||
const p = fakeProps();
|
||||
p.tools = [fakeTool()];
|
||||
p.tools[0].body.id = 1;
|
||||
p.tools[0].body.status = "inactive";
|
||||
p.toolSlots = [fakeToolSlot()];
|
||||
p.toolSlots[0].body.x = 1;
|
||||
const wrapper = mount(<Tools {...p} />);
|
||||
expect(wrapper.text()).toContain("Foo");
|
||||
expect(wrapper.text()).toContain("(1, 0, 0)");
|
||||
});
|
||||
|
||||
it("navigates to tool", () => {
|
||||
const p = fakeProps();
|
||||
p.tools = [fakeTool()];
|
||||
p.tools[0].body.id = 1;
|
||||
p.tools[0].body.status = "inactive";
|
||||
p.toolSlots = [fakeToolSlot()];
|
||||
p.toolSlots[0].body.tool_id = 2;
|
||||
const wrapper = mount(<Tools {...p} />);
|
||||
wrapper.find("p").first().simulate("click");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/tools/2");
|
||||
wrapper.find("p").last().simulate("click");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/tools/1");
|
||||
});
|
||||
|
||||
it("changes search term", () => {
|
||||
const p = fakeProps();
|
||||
p.tools = [fakeTool(), fakeTool()];
|
||||
p.tools[0].body.name = "tool 0";
|
||||
p.tools[1].body.name = "tool 1";
|
||||
const wrapper = shallow<Tools>(<Tools {...p} />);
|
||||
wrapper.find("input").first().simulate("change",
|
||||
{ currentTarget: { value: "0" } });
|
||||
expect(wrapper.state().searchTerm).toEqual("0");
|
||||
});
|
||||
|
||||
it("filters tools", () => {
|
||||
const p = fakeProps();
|
||||
p.tools = [fakeTool(), fakeTool()];
|
||||
p.tools[0].body.name = "tool 0";
|
||||
p.tools[1].body.name = "tool 1";
|
||||
const wrapper = mount(<Tools {...p} />);
|
||||
wrapper.setState({ searchTerm: "0" });
|
||||
expect(wrapper.text()).not.toContain("tool 1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const state = fakeState();
|
||||
const tool = fakeTool();
|
||||
state.resources = buildResourceIndex([tool]);
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.tools).toEqual([tool]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelContent, DesignerPanelHeader
|
||||
} from "../plants/designer_panel";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { SaveBtn } from "../../ui";
|
||||
import { SpecialStatus } from "farmbot";
|
||||
import { initSave } from "../../api/crud";
|
||||
|
||||
export interface AddToolProps {
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
export interface AddToolState {
|
||||
toolName: string;
|
||||
}
|
||||
|
||||
export const mapStateToProps = (props: Everything): AddToolProps => ({
|
||||
dispatch: props.dispatch,
|
||||
});
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export class AddTool extends React.Component<AddToolProps, AddToolState> {
|
||||
state: AddToolState = { toolName: "" };
|
||||
render() {
|
||||
return <DesignerPanel panelName={"tool"} panelColor={"gray"}>
|
||||
<DesignerPanelHeader
|
||||
panelName={"tool"}
|
||||
title={t("Add new tool")}
|
||||
backTo={"/app/designer/tools"}
|
||||
panelColor={"gray"} />
|
||||
<DesignerPanelContent panelName={"tools"}>
|
||||
<label>{t("Tool Name")}</label>
|
||||
<input
|
||||
onChange={e => this.setState({ toolName: e.currentTarget.value })} />
|
||||
<SaveBtn
|
||||
onClick={() =>
|
||||
this.props.dispatch(initSave("Tool", { name: this.state.toolName }))}
|
||||
status={SpecialStatus.DIRTY} />
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelContent, DesignerPanelHeader
|
||||
} from "../plants/designer_panel";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { getPathArray } from "../../history";
|
||||
import { TaggedTool, SpecialStatus } from "farmbot";
|
||||
import { maybeFindToolById } from "../../resources/selectors";
|
||||
import { SaveBtn } from "../../ui";
|
||||
import { edit } from "../../api/crud";
|
||||
import { history } from "../../history";
|
||||
|
||||
export interface EditToolProps {
|
||||
findTool(id: string): TaggedTool | undefined;
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
export interface EditToolState {
|
||||
toolName: string;
|
||||
}
|
||||
|
||||
export const mapStateToProps = (props: Everything): EditToolProps => ({
|
||||
findTool: (id: string) =>
|
||||
maybeFindToolById(props.resources.index, parseInt(id)),
|
||||
dispatch: props.dispatch,
|
||||
});
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export class EditTool extends React.Component<EditToolProps, EditToolState> {
|
||||
state: EditToolState = { toolName: this.tool ? this.tool.body.name || "" : "" };
|
||||
|
||||
get stringyID() { return getPathArray()[4] || ""; }
|
||||
|
||||
get tool() { return this.props.findTool(this.stringyID); }
|
||||
|
||||
fallback = () => {
|
||||
history.push("/app/designer/tools");
|
||||
return <span>{t("Redirecting...")}</span>;
|
||||
}
|
||||
|
||||
default = (tool: TaggedTool) =>
|
||||
<DesignerPanel panelName={"tool"} panelColor={"gray"}>
|
||||
<DesignerPanelHeader
|
||||
panelName={"tool"}
|
||||
title={`${t("Edit")} ${tool.body.name}`}
|
||||
backTo={"/app/designer/tools"}
|
||||
panelColor={"gray"} />
|
||||
<DesignerPanelContent panelName={"tools"}>
|
||||
<label>{t("Tool Name")}</label>
|
||||
<input
|
||||
value={this.state.toolName}
|
||||
onChange={e => this.setState({ toolName: e.currentTarget.value })} />
|
||||
<SaveBtn
|
||||
onClick={() => {
|
||||
this.props.dispatch(edit(tool, { name: this.state.toolName }));
|
||||
history.push("/app/designer/tools");
|
||||
}}
|
||||
status={SpecialStatus.DIRTY} />
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
|
||||
render() {
|
||||
return this.tool ? this.default(this.tool) : this.fallback();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelTop, DesignerPanelContent
|
||||
} from "../plants/designer_panel";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { DesignerNavTabs, Panel } from "../panel_header";
|
||||
import {
|
||||
EmptyStateWrapper, EmptyStateGraphic
|
||||
} from "../../ui/empty_state_wrapper";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { TaggedTool, TaggedToolSlotPointer } from "farmbot";
|
||||
import {
|
||||
selectAllTools, selectAllToolSlotPointers
|
||||
} from "../../resources/selectors";
|
||||
import { Content } from "../../constants";
|
||||
import { history } from "../../history";
|
||||
import { Row, Col } from "../../ui";
|
||||
import { botPositionLabel } from "../map/layers/farmbot/bot_position_label";
|
||||
|
||||
export interface ToolsProps {
|
||||
tools: TaggedTool[];
|
||||
toolSlots: TaggedToolSlotPointer[];
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
export interface ToolsState {
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
export const mapStateToProps = (props: Everything): ToolsProps => ({
|
||||
tools: selectAllTools(props.resources.index),
|
||||
toolSlots: selectAllToolSlotPointers(props.resources.index),
|
||||
dispatch: props.dispatch,
|
||||
});
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export class Tools extends React.Component<ToolsProps, ToolsState> {
|
||||
state: ToolsState = { searchTerm: "" };
|
||||
|
||||
update = ({ currentTarget }: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
this.setState({ searchTerm: currentTarget.value });
|
||||
}
|
||||
|
||||
getToolName = (toolId: number | undefined): string | undefined => {
|
||||
const foundTool = this.props.tools.filter(tool => tool.body.id === toolId)[0];
|
||||
return foundTool ? foundTool.body.name : undefined;
|
||||
};
|
||||
|
||||
render() {
|
||||
const panelName = "tools";
|
||||
return <DesignerPanel
|
||||
panelName={panelName}
|
||||
panelColor={"gray"}>
|
||||
<DesignerNavTabs />
|
||||
<DesignerPanelTop
|
||||
panel={Panel.Tools}
|
||||
linkTo={"/app/designer/tools/add"}
|
||||
title={t("Add tool")}>
|
||||
<input type="text" onChange={this.update}
|
||||
placeholder={t("Search your tools...")} />
|
||||
</DesignerPanelTop>
|
||||
<DesignerPanelContent panelName={"tools"}>
|
||||
<EmptyStateWrapper
|
||||
notEmpty={this.props.tools.length > 0}
|
||||
graphic={EmptyStateGraphic.sequences}
|
||||
title={t("Add a tool")}
|
||||
text={Content.NO_TOOLS}
|
||||
colorScheme={"tools"}>
|
||||
<div>
|
||||
<label>{t("tool slots")}</label>
|
||||
{this.props.toolSlots
|
||||
.filter(p => (this.getToolName(p.body.tool_id) || "").toLowerCase()
|
||||
.includes(this.state.searchTerm.toLowerCase()))
|
||||
.map(toolSlot =>
|
||||
<ToolSlotInventoryItem key={toolSlot.uuid}
|
||||
toolSlot={toolSlot}
|
||||
getToolName={this.getToolName} />)}
|
||||
<br />
|
||||
<label>{t("inactive tools")}</label>
|
||||
{this.props.tools
|
||||
.filter(tool => tool.body.name && tool.body.name.toLowerCase()
|
||||
.includes(this.state.searchTerm.toLowerCase()))
|
||||
.filter(tool => tool.body.status === "inactive")
|
||||
.map(tool =>
|
||||
<ToolInventoryItem key={tool.uuid}
|
||||
toolId={tool.body.id}
|
||||
toolName={tool.body.name || t("Unnammed tool")} />)}
|
||||
</div>
|
||||
</EmptyStateWrapper>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
}
|
||||
|
||||
interface ToolSlotInventoryItemProps {
|
||||
toolSlot: TaggedToolSlotPointer;
|
||||
getToolName(toolId: number | undefined): string | undefined;
|
||||
}
|
||||
|
||||
const ToolSlotInventoryItem = (props: ToolSlotInventoryItemProps) => {
|
||||
const { x, y, z, tool_id } = props.toolSlot.body;
|
||||
return <Row>
|
||||
<Col xs={7}>
|
||||
<p onClick={() => history.push(`/app/designer/tools/${tool_id}`)}>
|
||||
{props.getToolName(tool_id) || t("No tool")}
|
||||
</p>
|
||||
</Col>
|
||||
<Col xs={5}>
|
||||
<p style={{ float: "right" }}>{botPositionLabel({ x, y, z })}</p>
|
||||
</Col>
|
||||
</Row>;
|
||||
};
|
||||
|
||||
interface ToolInventoryItemProps {
|
||||
toolName: string;
|
||||
toolId: number | undefined;
|
||||
}
|
||||
|
||||
const ToolInventoryItem = (props: ToolInventoryItemProps) =>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<p onClick={() => history.push(`/app/designer/tools/${props.toolId}`)}>
|
||||
{t(props.toolName)}
|
||||
</p>
|
||||
</Col>
|
||||
</Row>;
|
|
@ -1,6 +1,5 @@
|
|||
import * as React from "react";
|
||||
|
||||
import { links } from "./nav/nav_links";
|
||||
import { getLinks } from "./nav/nav_links";
|
||||
import { sync } from "./devices/actions";
|
||||
import { push, getPathArray } from "./history";
|
||||
import { Row, Col } from "./ui/index";
|
||||
|
@ -60,6 +59,7 @@ export class HotKeys extends React.Component<Props, Partial<State>> {
|
|||
this.setState({ [property]: !this.state[property] });
|
||||
|
||||
private hotkeys(dispatch: Function, slug: string) {
|
||||
const links = getLinks();
|
||||
const idx = findIndex(links, { slug });
|
||||
const right = "/app/" + (links[idx + 1] || links[0]).slug;
|
||||
const left = "/app/" + (links[idx - 1] || links[links.length - 1]).slug;
|
||||
|
|
|
@ -6,6 +6,7 @@ interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|||
children?: React.ReactChild | React.ReactChild[];
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const maybeStripLegacyUrl =
|
||||
|
@ -23,6 +24,8 @@ export const clickHandler =
|
|||
export class Link extends React.Component<LinkProps, {}> {
|
||||
render() {
|
||||
const { props } = this;
|
||||
return <a {...props} href={props.to} onClick={clickHandler(props)} />;
|
||||
return props.disabled
|
||||
? <a {...props} />
|
||||
: <a {...props} href={props.to} onClick={clickHandler(props)} />;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import {
|
|||
} from "./compute_editor_url_from_state";
|
||||
import { Link } from "../link";
|
||||
import { t } from "../i18next_wrapper";
|
||||
import { betterCompact } from "../util";
|
||||
import { DevSettings } from "../account/dev/dev_support";
|
||||
/** Uses a slug and a child path to compute the `href` of a navbar link. */
|
||||
export type LinkComputeFn = (slug: string, childPath: string) => string;
|
||||
|
||||
|
@ -24,7 +26,7 @@ interface NavLinkParams {
|
|||
computeHref?: LinkComputeFn
|
||||
}
|
||||
|
||||
export const links: NavLinkParams[] = [
|
||||
export const getLinks = (): NavLinkParams[] => betterCompact([
|
||||
{ name: "Farm Designer", icon: "leaf", slug: "designer" },
|
||||
{ name: "Controls", icon: "keyboard-o", slug: "controls" },
|
||||
{ name: "Device", icon: "cog", slug: "device" },
|
||||
|
@ -36,19 +38,21 @@ export const links: NavLinkParams[] = [
|
|||
name: "Regimens", icon: "calendar-check-o", slug: "regimens",
|
||||
computeHref: computeEditorUrlFromState("Regimen")
|
||||
},
|
||||
{ name: "Tools", icon: "wrench", slug: "tools" },
|
||||
DevSettings.futureFeaturesEnabled()
|
||||
? undefined
|
||||
: { name: "Tools", icon: "wrench", slug: "tools" },
|
||||
{
|
||||
name: "Farmware", icon: "crosshairs", slug: "farmware",
|
||||
computeHref: computeFarmwareUrlFromState
|
||||
},
|
||||
{ name: "Messages", icon: "list", slug: "messages" },
|
||||
];
|
||||
]);
|
||||
|
||||
export const NavLinks = (props: NavLinksProps) => {
|
||||
const currPageSlug = getPathArray()[2];
|
||||
return <div className="links">
|
||||
<div className="nav-links">
|
||||
{links.map(link => {
|
||||
{getLinks().map(link => {
|
||||
const isActive = (currPageSlug === link.slug) ? "active" : "";
|
||||
const childPath = link.slug === "designer" ? "/plants" : "";
|
||||
const fn = link.computeHref || DEFAULT;
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#000000;stroke-miterlimit:10;}
|
||||
.st1{fill:#C46F24;}
|
||||
.st2{opacity:0.5;fill:#C46F24;}
|
||||
.st3{fill:#FFFFFF;stroke:#000000;stroke-width:7;stroke-miterlimit:10;}
|
||||
.st4{fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st5{fill:#FFFFFF;stroke:#000000;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st6{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st7{fill:#F15A24;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st8{fill:none;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st9{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st10{fill:#FFFFFF;}
|
||||
.st11{fill:#FFFFFF;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st12{fill:none;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:4.088,10.22;}
|
||||
.st13{fill:#FFFFFF;stroke:#333333;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st14{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st15{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st16{fill:none;stroke:#969696;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st17{fill:#B3B3B3;stroke:#4D4D4D;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st18{fill:#B3B3B3;stroke:#4D4D4D;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st19{fill:none;stroke:#4D4D4D;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st20{fill:#FFFFFF;stroke:#000000;stroke-miterlimit:10;}
|
||||
.st21{fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st22{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st23{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:3.2727,9.8182;}
|
||||
.st24{fill:none;stroke:#333333;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st25{fill:none;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st26{fill:none;stroke:#333333;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st27{fill:none;stroke:#E6E6E6;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st28{fill:#1EB287;}
|
||||
.st29{fill:#DEEDCB;}
|
||||
.st30{fill:#505305;}
|
||||
.st31{fill:#186435;}
|
||||
.st32{fill:#A44F79;}
|
||||
.st33{fill:#2AB188;}
|
||||
.st34{fill:#A35915;}
|
||||
.st35{fill:#4D4D4D;}
|
||||
.st36{fill:#F6B330;}
|
||||
.st37{fill:#324872;}
|
||||
.st38{fill:#2BA270;}
|
||||
.st39{fill:#53A4EA;}
|
||||
.st40{fill:#3BA2A0;}
|
||||
.st41{fill:#1792CD;}
|
||||
.st42{fill:#0C2E3D;}
|
||||
.st43{fill:#35761B;}
|
||||
.st44{fill:#0C6364;}
|
||||
.st45{fill:#F4A519;}
|
||||
.st46{opacity:0.06;fill:#3B9910;}
|
||||
.st47{opacity:0.06;fill:#E56200;}
|
||||
.st48{opacity:0.06;fill:#2E5799;}
|
||||
.st49{opacity:0.06;fill:#007F7C;}
|
||||
.st50{opacity:0.06;fill:#00B7FF;}
|
||||
.st51{opacity:0.06;fill:#FF9D00;}
|
||||
.st52{opacity:0.06;fill:#00CC8D;}
|
||||
.st53{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st54{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st55{fill:#F15A24;}
|
||||
.st56{fill:#F15A24;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st57{fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st58{fill:#FFFFFF;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st59{opacity:0.8;enable-background:new ;}
|
||||
.st60{clip-path:url(#SVGID_2_);}
|
||||
.st61{clip-path:url(#SVGID_4_);}
|
||||
.st62{clip-path:url(#SVGID_6_);fill:#333333;}
|
||||
.st63{clip-path:url(#SVGID_8_);}
|
||||
.st64{clip-path:url(#SVGID_10_);fill:#333333;}
|
||||
.st65{clip-path:url(#SVGID_12_);}
|
||||
.st66{clip-path:url(#SVGID_14_);}
|
||||
.st67{clip-path:url(#SVGID_16_);fill:#333333;}
|
||||
.st68{clip-path:url(#SVGID_18_);}
|
||||
.st69{clip-path:url(#SVGID_20_);fill:#333333;}
|
||||
.st70{clip-path:url(#SVGID_22_);}
|
||||
.st71{clip-path:url(#SVGID_24_);fill:#333333;}
|
||||
.st72{clip-path:url(#SVGID_26_);}
|
||||
.st73{clip-path:url(#SVGID_28_);fill:#333333;}
|
||||
.st74{clip-path:url(#SVGID_30_);}
|
||||
.st75{clip-path:url(#SVGID_32_);fill:#333333;}
|
||||
.st76{clip-path:url(#SVGID_34_);}
|
||||
.st77{clip-path:url(#SVGID_36_);fill:#333333;}
|
||||
.st78{clip-path:url(#SVGID_38_);}
|
||||
.st79{clip-path:url(#SVGID_40_);fill:#333333;}
|
||||
.st80{clip-path:url(#SVGID_42_);}
|
||||
.st81{clip-path:url(#SVGID_44_);fill:#333333;}
|
||||
.st82{clip-path:url(#SVGID_46_);}
|
||||
.st83{clip-path:url(#SVGID_48_);fill:#333333;}
|
||||
.st84{clip-path:url(#SVGID_50_);}
|
||||
.st85{clip-path:url(#SVGID_52_);fill:#333333;}
|
||||
.st86{clip-path:url(#SVGID_54_);}
|
||||
.st87{clip-path:url(#SVGID_56_);fill:#333333;}
|
||||
.st88{clip-path:url(#SVGID_58_);}
|
||||
.st89{clip-path:url(#SVGID_60_);fill:#333333;}
|
||||
.st90{clip-path:url(#SVGID_62_);}
|
||||
.st91{clip-path:url(#SVGID_64_);}
|
||||
.st92{clip-path:url(#SVGID_66_);enable-background:new ;}
|
||||
.st93{clip-path:url(#SVGID_68_);}
|
||||
.st94{clip-path:url(#SVGID_70_);}
|
||||
.st95{clip-path:url(#SVGID_72_);fill:#333333;}
|
||||
.st96{clip-path:url(#SVGID_74_);}
|
||||
.st97{clip-path:url(#SVGID_76_);}
|
||||
.st98{clip-path:url(#SVGID_78_);fill:#333333;}
|
||||
.st99{clip-path:url(#SVGID_80_);}
|
||||
.st100{clip-path:url(#SVGID_82_);fill:#333333;}
|
||||
.st101{clip-path:url(#SVGID_84_);}
|
||||
.st102{clip-path:url(#SVGID_86_);}
|
||||
.st103{clip-path:url(#SVGID_88_);fill:#333333;}
|
||||
.st104{clip-path:url(#SVGID_90_);}
|
||||
.st105{clip-path:url(#SVGID_92_);fill:#333333;}
|
||||
.st106{clip-path:url(#SVGID_94_);}
|
||||
.st107{clip-path:url(#SVGID_96_);fill:#333333;}
|
||||
.st108{clip-path:url(#SVGID_98_);}
|
||||
.st109{clip-path:url(#SVGID_100_);fill:#333333;}
|
||||
.st110{opacity:0.8;clip-path:url(#SVGID_102_);fill:#333333;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_3">
|
||||
</g>
|
||||
<g id="Layer_4">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<g>
|
||||
<path d="M46.86,22.5l-3.1,0c-0.82,0-1.53-0.57-1.71-1.37c-1.47-6.45-6.53-11.65-13.14-13.17C28.09,7.78,27.5,7.05,27.5,6.2l0-3.06
|
||||
c0-1.31-0.94-2.5-2.24-2.63C23.76,0.36,22.5,1.53,22.5,3v3.24c0,0.82-0.57,1.52-1.37,1.71C14.68,9.42,9.48,14.48,7.97,21.09
|
||||
C7.78,21.91,7.05,22.5,6.2,22.5l-3.06,0c-1.31,0-2.5,0.94-2.63,2.24C0.36,26.24,1.53,27.5,3,27.5h3.19c0.84,0,1.58,0.58,1.76,1.4
|
||||
c0.61,2.69,1.84,5.21,3.67,7.37c2.48,2.94,5.79,4.93,9.48,5.76c0.82,0.19,1.4,0.92,1.4,1.76l0,3.06c0,1.31,0.94,2.5,2.24,2.63
|
||||
c1.5,0.15,2.76-1.02,2.76-2.49v-3.24c0-0.82,0.57-1.53,1.37-1.71c6.45-1.47,11.66-6.53,13.17-13.14c0.19-0.83,0.92-1.41,1.77-1.41
|
||||
H47c1.47,0,2.64-1.26,2.49-2.76C49.36,23.44,48.17,22.5,46.86,22.5z M28.31,37.05c-0.91,0.25-1.81-0.41-1.81-1.36v-3.91
|
||||
c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5v3.9c0,0.91-0.86,1.61-1.73,1.38c-2.46-0.65-4.65-2.03-6.32-4.01
|
||||
c-1.2-1.43-2.05-3.07-2.52-4.82c-0.23-0.88,0.47-1.73,1.38-1.73h3.92c0.83,0,1.5-0.67,1.5-1.5s-0.67-1.5-1.5-1.5h-3.9
|
||||
c-0.94,0-1.6-0.91-1.35-1.82c1.17-4.27,4.54-7.57,8.72-8.73c0.91-0.25,1.81,0.41,1.81,1.36v3.91c0,0.83,0.67,1.5,1.5,1.5
|
||||
s1.5-0.67,1.5-1.5v-3.9c0-0.94,0.91-1.6,1.82-1.35c4.27,1.17,7.57,4.54,8.73,8.72c0.25,0.91-0.41,1.81-1.36,1.81h-3.91
|
||||
c-0.83,0-1.5,0.67-1.5,1.5s0.67,1.5,1.5,1.5h3.9c0.95,0,1.6,0.91,1.35,1.82C35.86,32.59,32.5,35.89,28.31,37.05z"/>
|
||||
<circle cx="25" cy="25" r="1.75"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.5 KiB |
|
@ -0,0 +1,169 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#000000;stroke-miterlimit:10;}
|
||||
.st1{fill:#C46F24;}
|
||||
.st2{opacity:0.5;fill:#C46F24;}
|
||||
.st3{fill:#C1631E;}
|
||||
.st4{fill:#FFFFFF;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st5{fill:#FFFFFF;stroke:#000000;stroke-width:7;stroke-miterlimit:10;}
|
||||
.st6{fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st7{fill:#FFFFFF;stroke:#000000;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st8{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st9{fill:#F15A24;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st10{fill:none;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st11{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st12{fill:#FFFFFF;}
|
||||
.st13{fill:#FFFFFF;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st14{fill:none;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:4.088,10.22;}
|
||||
.st15{fill:#FFFFFF;stroke:#333333;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st16{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st17{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st18{fill:none;stroke:#969696;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st19{fill:#B3B3B3;stroke:#4D4D4D;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st20{fill:#B3B3B3;stroke:#4D4D4D;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st21{fill:none;stroke:#4D4D4D;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st22{fill:#FFFFFF;stroke:#000000;stroke-miterlimit:10;}
|
||||
.st23{fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st24{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st25{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:3.2727,9.8182;}
|
||||
.st26{fill:none;stroke:#333333;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st27{fill:none;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st28{fill:none;stroke:#333333;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st29{fill:none;stroke:#E6E6E6;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st30{fill:#1EB287;}
|
||||
.st31{fill:#DEEDCB;}
|
||||
.st32{fill:#505305;}
|
||||
.st33{fill:#186435;}
|
||||
.st34{fill:#A44F79;}
|
||||
.st35{fill:#2AB188;}
|
||||
.st36{fill:#A35915;}
|
||||
.st37{fill:#4D4D4D;}
|
||||
.st38{fill:#F6B330;}
|
||||
.st39{fill:#324872;}
|
||||
.st40{fill:#2BA270;}
|
||||
.st41{fill:#53A4EA;}
|
||||
.st42{fill:#3BA2A0;}
|
||||
.st43{fill:#1792CD;}
|
||||
.st44{fill:#0C2E3D;}
|
||||
.st45{fill:#35761B;}
|
||||
.st46{fill:#0C6364;}
|
||||
.st47{fill:#F4A519;}
|
||||
.st48{opacity:0.06;fill:#3B9910;}
|
||||
.st49{opacity:0.06;fill:#E56200;}
|
||||
.st50{opacity:0.06;fill:#2E5799;}
|
||||
.st51{opacity:0.06;fill:#007F7C;}
|
||||
.st52{opacity:0.06;fill:#00B7FF;}
|
||||
.st53{opacity:0.06;fill:#FF9D00;}
|
||||
.st54{opacity:0.06;fill:#00CC8D;}
|
||||
.st55{fill:#91A7B4;}
|
||||
.st56{fill:#9F6300;}
|
||||
.st57{fill:#07B386;}
|
||||
.st58{fill:#FF4D2D;}
|
||||
.st59{fill:#F9FBFC;}
|
||||
.st60{fill:#FBF7F0;}
|
||||
.st61{fill:#F0F8F8;}
|
||||
.st62{fill:#F1FCF9;}
|
||||
.st63{fill:#FFF7F6;}
|
||||
.st64{fill:#AF8761;}
|
||||
.st65{fill:#FFF8F3;}
|
||||
.st66{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st67{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st68{fill:#F15A24;}
|
||||
.st69{fill:#F15A24;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st70{fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st71{fill:#FFFFFF;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st72{opacity:0.8;enable-background:new ;}
|
||||
.st73{clip-path:url(#SVGID_2_);}
|
||||
.st74{clip-path:url(#SVGID_4_);}
|
||||
.st75{clip-path:url(#SVGID_6_);fill:#333333;}
|
||||
.st76{clip-path:url(#SVGID_8_);}
|
||||
.st77{clip-path:url(#SVGID_10_);fill:#333333;}
|
||||
.st78{clip-path:url(#SVGID_12_);}
|
||||
.st79{clip-path:url(#SVGID_14_);}
|
||||
.st80{clip-path:url(#SVGID_16_);fill:#333333;}
|
||||
.st81{clip-path:url(#SVGID_18_);}
|
||||
.st82{clip-path:url(#SVGID_20_);fill:#333333;}
|
||||
.st83{clip-path:url(#SVGID_22_);}
|
||||
.st84{clip-path:url(#SVGID_24_);fill:#333333;}
|
||||
.st85{clip-path:url(#SVGID_26_);}
|
||||
.st86{clip-path:url(#SVGID_28_);fill:#333333;}
|
||||
.st87{clip-path:url(#SVGID_30_);}
|
||||
.st88{clip-path:url(#SVGID_32_);fill:#333333;}
|
||||
.st89{clip-path:url(#SVGID_34_);}
|
||||
.st90{clip-path:url(#SVGID_36_);fill:#333333;}
|
||||
.st91{clip-path:url(#SVGID_38_);}
|
||||
.st92{clip-path:url(#SVGID_40_);fill:#333333;}
|
||||
.st93{clip-path:url(#SVGID_42_);}
|
||||
.st94{clip-path:url(#SVGID_44_);fill:#333333;}
|
||||
.st95{clip-path:url(#SVGID_46_);}
|
||||
.st96{clip-path:url(#SVGID_48_);fill:#333333;}
|
||||
.st97{clip-path:url(#SVGID_50_);}
|
||||
.st98{clip-path:url(#SVGID_52_);fill:#333333;}
|
||||
.st99{clip-path:url(#SVGID_54_);}
|
||||
.st100{clip-path:url(#SVGID_56_);fill:#333333;}
|
||||
.st101{clip-path:url(#SVGID_58_);}
|
||||
.st102{clip-path:url(#SVGID_60_);fill:#333333;}
|
||||
.st103{clip-path:url(#SVGID_62_);}
|
||||
.st104{clip-path:url(#SVGID_64_);}
|
||||
.st105{clip-path:url(#SVGID_66_);enable-background:new ;}
|
||||
.st106{clip-path:url(#SVGID_68_);}
|
||||
.st107{clip-path:url(#SVGID_70_);}
|
||||
.st108{clip-path:url(#SVGID_72_);fill:#333333;}
|
||||
.st109{clip-path:url(#SVGID_74_);}
|
||||
.st110{clip-path:url(#SVGID_76_);}
|
||||
.st111{clip-path:url(#SVGID_78_);fill:#333333;}
|
||||
.st112{clip-path:url(#SVGID_80_);}
|
||||
.st113{clip-path:url(#SVGID_82_);fill:#333333;}
|
||||
.st114{clip-path:url(#SVGID_84_);}
|
||||
.st115{clip-path:url(#SVGID_86_);}
|
||||
.st116{clip-path:url(#SVGID_88_);fill:#333333;}
|
||||
.st117{clip-path:url(#SVGID_90_);}
|
||||
.st118{clip-path:url(#SVGID_92_);fill:#333333;}
|
||||
.st119{clip-path:url(#SVGID_94_);}
|
||||
.st120{clip-path:url(#SVGID_96_);fill:#333333;}
|
||||
.st121{clip-path:url(#SVGID_98_);}
|
||||
.st122{clip-path:url(#SVGID_100_);fill:#333333;}
|
||||
.st123{opacity:0.8;clip-path:url(#SVGID_102_);fill:#333333;}
|
||||
.st124{fill:#FFFFFF;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st125{fill:#FFFFFF;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st126{fill:#FFFFFF;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st127{fill:#FFFFFF;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st128{opacity:0.8;}
|
||||
.st129{opacity:0.8;fill:#FFFFFF;}
|
||||
.st130{fill:none;stroke:#FFFFFF;stroke-width:6;stroke-miterlimit:10;}
|
||||
.st131{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st132{opacity:0.2;fill:url(#SVGID_103_);}
|
||||
.st133{fill:#D8EAD2;}
|
||||
.st134{opacity:0.2;fill:url(#SVGID_104_);}
|
||||
.st135{opacity:0.06;fill:#E07127;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_3">
|
||||
</g>
|
||||
<g id="Layer_4">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<path d="M41.87,27.98l-0.24-0.08c-0.87-0.28-1.42-1.12-1.37-2.03c0.03-0.5,0.03-1.01,0.01-1.51c-0.04-0.91,0.51-1.73,1.37-2.01
|
||||
l0.22-0.07c1.29-0.42,2.13-1.76,1.78-3.07c-0.38-1.39-1.84-2.17-3.19-1.73l-0.29,0.09c-0.87,0.28-1.81-0.07-2.31-0.85
|
||||
c-0.28-0.44-0.58-0.86-0.9-1.27c-0.56-0.71-0.6-1.7-0.07-2.43l0.07-0.1c0.78-1.08,0.72-2.59-0.25-3.46c-1.12-1-2.83-0.8-3.69,0.39
|
||||
l-0.14,0.2c-0.54,0.74-1.5,1.01-2.35,0.68c-0.5-0.19-1-0.36-1.51-0.49c-0.88-0.24-1.5-1.02-1.5-1.93V8.23
|
||||
c0-1.32-0.94-2.52-2.24-2.65c-1.5-0.15-2.76,1.02-2.76,2.49l0,0.26c0,0.91-0.62,1.69-1.5,1.93c-0.51,0.14-1.01,0.3-1.5,0.49
|
||||
c-0.85,0.33-1.81,0.06-2.35-0.68l-0.06-0.08c-0.78-1.07-2.24-1.48-3.37-0.83c-1.3,0.76-1.63,2.45-0.77,3.63l0.16,0.22
|
||||
c0.54,0.74,0.49,1.73-0.08,2.44c-0.25,0.31-0.57,0.8-0.87,1.27c-0.49,0.78-1.44,1.13-2.32,0.84l-0.17-0.06
|
||||
c-1.24-0.4-2.67,0.12-3.19,1.32c-0.61,1.38,0.12,2.94,1.51,3.39l0.36,0.12c0.86,0.28,1.41,1.1,1.37,2
|
||||
c-0.02,0.5-0.02,1.01,0.01,1.51c0.05,0.92-0.5,1.76-1.37,2.04l-0.24,0.08c-1.23,0.4-2.07,1.64-1.81,2.91
|
||||
c0.25,1.22,1.31,2.01,2.45,2.01c0.26,0,0.52-0.04,0.77-0.12l0.42-0.14c0.86-0.28,1.79,0.06,2.29,0.82c0.27,0.41,0.56,0.8,0.87,1.18
|
||||
c0.57,0.71,0.62,1.71,0.08,2.45l-0.15,0.21c-0.85,1.17-0.7,2.8,0.42,3.63c0.45,0.33,0.97,0.49,1.49,0.49
|
||||
c0.77,0,1.54-0.36,2.02-1.03l0.27-0.37c0.54-0.75,1.53-1.01,2.38-0.67c0.48,0.2,0.95,0.36,1.42,0.51c0.85,0.26,1.43,1.03,1.43,1.91
|
||||
l0,0.22c0,1.35,0.94,2.54,2.24,2.67c1.5,0.15,2.76-1.02,2.76-2.49V40.4c1.67-0.26,3.1-0.81,4.24-1.41
|
||||
c0.06-0.03,0.11-0.06,0.16-0.09L33,40.4c0.49,0.67,1.25,1.03,2.02,1.03c0.52,0,1.04-0.16,1.49-0.49c1.12-0.83,1.26-2.47,0.45-3.59
|
||||
l-0.18-0.24c-0.53-0.72-0.51-1.71,0.05-2.41c0.35-0.44,0.65-0.86,0.9-1.23c0.5-0.75,1.43-1.09,2.29-0.81l0.42,0.14
|
||||
c0.26,0.08,0.52,0.12,0.77,0.12c1.14,0,2.2-0.79,2.45-2.01C43.93,29.62,43.1,28.38,41.87,27.98z M29.41,34.56
|
||||
c-2.74,1.45-5.83,1.31-9.16-0.43c-5.04-2.62-7-8.85-4.38-13.89c1.83-3.52,5.43-5.54,9.15-5.54c1.6,0,3.22,0.37,4.74,1.16
|
||||
c5.04,2.62,7,8.85,4.38,13.89C33.33,31.3,31.67,33.37,29.41,34.56z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9.3 KiB |
|
@ -0,0 +1,144 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#000000;stroke-miterlimit:10;}
|
||||
.st1{fill:#C46F24;}
|
||||
.st2{opacity:0.5;fill:#C46F24;}
|
||||
.st3{fill:#FFFFFF;stroke:#000000;stroke-width:7;stroke-miterlimit:10;}
|
||||
.st4{fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st5{fill:#FFFFFF;stroke:#000000;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st6{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st7{fill:#F15A24;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st8{fill:none;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st9{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st10{fill:#FFFFFF;}
|
||||
.st11{fill:#FFFFFF;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st12{fill:none;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:4.088,10.22;}
|
||||
.st13{fill:#FFFFFF;stroke:#333333;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st14{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st15{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st16{fill:none;stroke:#969696;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st17{fill:#B3B3B3;stroke:#4D4D4D;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st18{fill:#B3B3B3;stroke:#4D4D4D;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st19{fill:none;stroke:#4D4D4D;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st20{fill:#FFFFFF;stroke:#000000;stroke-miterlimit:10;}
|
||||
.st21{fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st22{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st23{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:3.2727,9.8182;}
|
||||
.st24{fill:none;stroke:#333333;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st25{fill:none;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st26{fill:none;stroke:#333333;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st27{fill:none;stroke:#E6E6E6;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st28{fill:#1EB287;}
|
||||
.st29{fill:#DEEDCB;}
|
||||
.st30{fill:#505305;}
|
||||
.st31{fill:#186435;}
|
||||
.st32{fill:#A44F79;}
|
||||
.st33{fill:#2AB188;}
|
||||
.st34{fill:#A35915;}
|
||||
.st35{fill:#4D4D4D;}
|
||||
.st36{fill:#F6B330;}
|
||||
.st37{fill:#324872;}
|
||||
.st38{fill:#2BA270;}
|
||||
.st39{fill:#53A4EA;}
|
||||
.st40{fill:#3BA2A0;}
|
||||
.st41{fill:#1792CD;}
|
||||
.st42{fill:#0C2E3D;}
|
||||
.st43{fill:#35761B;}
|
||||
.st44{fill:#0C6364;}
|
||||
.st45{fill:#F4A519;}
|
||||
.st46{opacity:0.06;fill:#3B9910;}
|
||||
.st47{opacity:0.06;fill:#E56200;}
|
||||
.st48{opacity:0.06;fill:#2E5799;}
|
||||
.st49{opacity:0.06;fill:#007F7C;}
|
||||
.st50{opacity:0.06;fill:#00B7FF;}
|
||||
.st51{opacity:0.06;fill:#FF9D00;}
|
||||
.st52{opacity:0.06;fill:#00CC8D;}
|
||||
.st53{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st54{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st55{fill:#F15A24;}
|
||||
.st56{fill:#F15A24;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st57{fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st58{fill:#FFFFFF;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st59{opacity:0.8;enable-background:new ;}
|
||||
.st60{clip-path:url(#SVGID_2_);}
|
||||
.st61{clip-path:url(#SVGID_4_);}
|
||||
.st62{clip-path:url(#SVGID_6_);fill:#333333;}
|
||||
.st63{clip-path:url(#SVGID_8_);}
|
||||
.st64{clip-path:url(#SVGID_10_);fill:#333333;}
|
||||
.st65{clip-path:url(#SVGID_12_);}
|
||||
.st66{clip-path:url(#SVGID_14_);}
|
||||
.st67{clip-path:url(#SVGID_16_);fill:#333333;}
|
||||
.st68{clip-path:url(#SVGID_18_);}
|
||||
.st69{clip-path:url(#SVGID_20_);fill:#333333;}
|
||||
.st70{clip-path:url(#SVGID_22_);}
|
||||
.st71{clip-path:url(#SVGID_24_);fill:#333333;}
|
||||
.st72{clip-path:url(#SVGID_26_);}
|
||||
.st73{clip-path:url(#SVGID_28_);fill:#333333;}
|
||||
.st74{clip-path:url(#SVGID_30_);}
|
||||
.st75{clip-path:url(#SVGID_32_);fill:#333333;}
|
||||
.st76{clip-path:url(#SVGID_34_);}
|
||||
.st77{clip-path:url(#SVGID_36_);fill:#333333;}
|
||||
.st78{clip-path:url(#SVGID_38_);}
|
||||
.st79{clip-path:url(#SVGID_40_);fill:#333333;}
|
||||
.st80{clip-path:url(#SVGID_42_);}
|
||||
.st81{clip-path:url(#SVGID_44_);fill:#333333;}
|
||||
.st82{clip-path:url(#SVGID_46_);}
|
||||
.st83{clip-path:url(#SVGID_48_);fill:#333333;}
|
||||
.st84{clip-path:url(#SVGID_50_);}
|
||||
.st85{clip-path:url(#SVGID_52_);fill:#333333;}
|
||||
.st86{clip-path:url(#SVGID_54_);}
|
||||
.st87{clip-path:url(#SVGID_56_);fill:#333333;}
|
||||
.st88{clip-path:url(#SVGID_58_);}
|
||||
.st89{clip-path:url(#SVGID_60_);fill:#333333;}
|
||||
.st90{clip-path:url(#SVGID_62_);}
|
||||
.st91{clip-path:url(#SVGID_64_);}
|
||||
.st92{clip-path:url(#SVGID_66_);enable-background:new ;}
|
||||
.st93{clip-path:url(#SVGID_68_);}
|
||||
.st94{clip-path:url(#SVGID_70_);}
|
||||
.st95{clip-path:url(#SVGID_72_);fill:#333333;}
|
||||
.st96{clip-path:url(#SVGID_74_);}
|
||||
.st97{clip-path:url(#SVGID_76_);}
|
||||
.st98{clip-path:url(#SVGID_78_);fill:#333333;}
|
||||
.st99{clip-path:url(#SVGID_80_);}
|
||||
.st100{clip-path:url(#SVGID_82_);fill:#333333;}
|
||||
.st101{clip-path:url(#SVGID_84_);}
|
||||
.st102{clip-path:url(#SVGID_86_);}
|
||||
.st103{clip-path:url(#SVGID_88_);fill:#333333;}
|
||||
.st104{clip-path:url(#SVGID_90_);}
|
||||
.st105{clip-path:url(#SVGID_92_);fill:#333333;}
|
||||
.st106{clip-path:url(#SVGID_94_);}
|
||||
.st107{clip-path:url(#SVGID_96_);fill:#333333;}
|
||||
.st108{clip-path:url(#SVGID_98_);}
|
||||
.st109{clip-path:url(#SVGID_100_);fill:#333333;}
|
||||
.st110{opacity:0.8;clip-path:url(#SVGID_102_);fill:#333333;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_3">
|
||||
</g>
|
||||
<g id="Layer_4">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<g>
|
||||
<path d="M43.89,5.33c-4.71-1.42-11.55-2.24-18.76-2.24S11.07,3.91,6.37,5.33C5.32,5.65,4.63,6.63,4.63,7.72l0,13.76
|
||||
c0,1.11,0.73,2.08,1.8,2.4c0.83,0.25,1.73,0.48,2.68,0.69c0.59,0.13,1.02,0.65,1.02,1.25v2.55c0,0.83,0.67,1.5,1.5,1.5
|
||||
s1.5-0.67,1.5-1.5v-1.92c0-0.62,0.54-1.09,1.16-1.01c1.18,0.15,2.41,0.28,3.68,0.38c0.65,0.05,1.16,0.59,1.16,1.25v2.31
|
||||
c0,0.83,0.67,1.5,1.5,1.5c0.83,0,1.5-0.67,1.5-1.5v-2.09c0-0.68,0.56-1.22,1.23-1.2c0.59,0.01,1.17,0.02,1.77,0.02
|
||||
s1.18-0.01,1.77-0.02c0.68-0.01,1.23,0.53,1.23,1.2v2.09c0,0.83,0.67,1.5,1.5,1.5c0.83,0,1.5-0.67,1.5-1.5v-2.31
|
||||
c0-0.66,0.51-1.2,1.16-1.25c1.26-0.1,2.49-0.23,3.68-0.38c0.62-0.08,1.16,0.39,1.16,1.01v1.92c0,0.83,0.67,1.5,1.5,1.5
|
||||
s1.5-0.67,1.5-1.5v-2.55c0-0.61,0.43-1.12,1.02-1.25c0.95-0.21,1.85-0.44,2.68-0.69c1.06-0.32,1.8-1.29,1.8-2.4l0-13.76
|
||||
C45.63,6.63,44.93,5.65,43.89,5.33z M25.13,8.09c5.57,0,10.85,0.5,14.91,1.41c0.34,0.08,0.59,0.38,0.59,0.74v0
|
||||
c0,0.36-0.25,0.67-0.6,0.74c-4.17,0.85-9.47,1.34-15.05,1.34c-5.45,0-10.63-0.46-14.75-1.28c-0.35-0.07-0.6-0.38-0.6-0.74v-0.06
|
||||
c0-0.35,0.24-0.66,0.59-0.74C14.28,8.6,19.56,8.09,25.13,8.09z M25.13,21.09c-5.18,0-10.1-0.44-14.04-1.22
|
||||
c-0.85-0.17-1.46-0.92-1.46-1.79v-2.73c0-0.71,0.63-1.25,1.33-1.13c4.08,0.7,8.95,1.09,14.02,1.09c5.19,0,10.17-0.41,14.31-1.14
|
||||
c0.7-0.12,1.35,0.43,1.35,1.14v2.77c0,0.87-0.61,1.62-1.46,1.79C35.23,20.66,30.3,21.09,25.13,21.09z"/>
|
||||
<path d="M27.61,38.72c-0.37-0.6-0.66-1.08-0.87-1.44l0,0c-0.36-0.61-1.02-0.94-1.71-0.97c-0.7,0-1.35,0.39-1.7,1
|
||||
c-0.2,0.35-0.48,0.83-0.85,1.42c-0.07,0.11-0.17,0.25-0.28,0.42c-0.76,1.11-2.17,3.19-2.17,5.49c0,2.83,2.24,5.13,5,5.13
|
||||
s5-2.3,5-5.13c0-2.31-1.4-4.39-2.16-5.51C27.77,38.97,27.67,38.83,27.61,38.72z M25.58,41.1c0.59,0.89,1.44,2.29,1.44,3.54
|
||||
c0,1.17-0.9,2.13-2,2.13c-1.1,0-2-0.96-2-2.13c0-1.23,0.86-2.63,1.46-3.52l0.15-0.22c0.2-0.29,0.63-0.29,0.82,0L25.58,41.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.0 KiB |
Loading…
Reference in New Issue