map historical image slider
parent
49c61134da
commit
49d5f4509f
|
@ -313,11 +313,16 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
fieldset {
|
||||
width: 100%;
|
||||
}
|
||||
label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
button {
|
||||
float: none;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
}
|
||||
.caret-menu-button {
|
||||
|
@ -452,7 +457,17 @@
|
|||
}
|
||||
|
||||
.image-filter-menu {
|
||||
width: 32rem;
|
||||
th {
|
||||
text-align: center;
|
||||
}
|
||||
.pt-slider {
|
||||
margin-left: 3rem;
|
||||
margin-top: 1rem;
|
||||
width: 25rem;
|
||||
}
|
||||
.pt-slider-label {
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,11 @@ import { mount } from "enzyme";
|
|||
import { Props } from "../interfaces";
|
||||
import { GardenMapLegendProps } from "../map/interfaces";
|
||||
import { bot } from "../../__test_support__/fake_state/bot";
|
||||
import { fakeImage } from "../../__test_support__/fake_state/resources";
|
||||
|
||||
describe("<FarmDesigner/>", () => {
|
||||
function fakeProps(): Props {
|
||||
|
||||
return {
|
||||
dispatch: jest.fn(),
|
||||
selectedPlant: undefined,
|
||||
|
@ -53,7 +55,7 @@ describe("<FarmDesigner/>", () => {
|
|||
|
||||
it("loads default map settings", () => {
|
||||
localStorage["showPoints"] = "false";
|
||||
const wrapper = mount(<FarmDesigner { ...fakeProps() } />);
|
||||
const wrapper = mount(<FarmDesigner {...fakeProps()} />);
|
||||
const legendProps = wrapper.find("GardenMapLegend").props() as GardenMapLegendProps;
|
||||
expect(legendProps.legendMenuOpen).toBeFalsy();
|
||||
expect(legendProps.showPlants).toBeTruthy();
|
||||
|
@ -62,14 +64,28 @@ describe("<FarmDesigner/>", () => {
|
|||
expect(legendProps.showFarmbot).toBeTruthy();
|
||||
expect(legendProps.showImages).toBeFalsy();
|
||||
expect(legendProps.botOriginQuadrant).toEqual(2);
|
||||
expect(legendProps.imageAgeInfo).toEqual({ newestDate: "", toOldest: 1 });
|
||||
// tslint:disable-next-line:no-any
|
||||
const gardenMapProps = wrapper.find("GardenMap").props() as any;
|
||||
expect(gardenMapProps.gridSize.x).toEqual(2900);
|
||||
expect(gardenMapProps.gridSize.y).toEqual(1400);
|
||||
});
|
||||
|
||||
it("loads image info", () => {
|
||||
const p = fakeProps();
|
||||
const image1 = fakeImage();
|
||||
const image2 = fakeImage();
|
||||
image1.body.created_at = "2001-01-03T00:00:00.000Z";
|
||||
image2.body.created_at = "2001-01-01T00:00:00.000Z";
|
||||
p.latestImages = [image1, image2];
|
||||
const wrapper = mount(<FarmDesigner {...p} />);
|
||||
const legendProps = wrapper.find("GardenMapLegend").props() as GardenMapLegendProps;
|
||||
expect(legendProps.imageAgeInfo)
|
||||
.toEqual({ newestDate: "2001-01-03T00:00:00.000Z", toOldest: 2 });
|
||||
});
|
||||
|
||||
it("renders nav titles", () => {
|
||||
const wrapper = mount(<FarmDesigner { ...fakeProps() } />);
|
||||
const wrapper = mount(<FarmDesigner {...fakeProps()} />);
|
||||
["Designer", "Plants", "Farm Events"].map(string =>
|
||||
expect(wrapper.text()).toContain(string));
|
||||
});
|
||||
|
|
|
@ -10,11 +10,12 @@ import { Plants } from "./plants/plant_inventory";
|
|||
import { GardenMapLegend } from "./map/garden_map_legend";
|
||||
import { Session, safeBooleanSettting } from "../session";
|
||||
import { NumericSetting, BooleanSetting } from "../session_keys";
|
||||
import { isUndefined } from "lodash";
|
||||
import { isUndefined, last } from "lodash";
|
||||
import { AxisNumberProperty, BotSize } from "./map/interfaces";
|
||||
import { getBotSize } from "./map/util";
|
||||
import { catchErrors } from "../util";
|
||||
import { calcZoomLevel, getZoomLevelIndex, saveZoomLevelIndex } from "./map/zoom";
|
||||
import * as moment from "moment";
|
||||
|
||||
export const getDefaultAxisLength = (): AxisNumberProperty => {
|
||||
if (Session.deprecatedGetBool(BooleanSetting.map_xl)) {
|
||||
|
@ -125,6 +126,15 @@ export class FarmDesigner extends React.Component<Props, Partial<State>> {
|
|||
y: !!this.props.botMcuParams.movement_stop_at_home_y
|
||||
};
|
||||
|
||||
const newestImage = this.props.latestImages[0];
|
||||
const oldestImage = last(this.props.latestImages);
|
||||
const newestDate = newestImage ? newestImage.body.created_at : "";
|
||||
const toOldest = oldestImage && newestDate
|
||||
? Math.abs(moment(oldestImage.body.created_at)
|
||||
.diff(moment(newestDate).clone(), "days"))
|
||||
: 1;
|
||||
const imageAgeInfo = { newestDate, toOldest };
|
||||
|
||||
return <div className="farm-designer">
|
||||
|
||||
<GardenMapLegend
|
||||
|
@ -140,7 +150,8 @@ export class FarmDesigner extends React.Component<Props, Partial<State>> {
|
|||
showImages={show_images}
|
||||
dispatch={this.props.dispatch}
|
||||
tzOffset={this.props.tzOffset}
|
||||
getConfigValue={this.props.getConfigValue} />
|
||||
getConfigValue={this.props.getConfigValue}
|
||||
imageAgeInfo={imageAgeInfo} />
|
||||
|
||||
<div className="panel-header gray-panel designer-nav">
|
||||
<div className="panel-tabs">
|
||||
|
|
|
@ -28,6 +28,7 @@ describe("<GardenMapLegend />", () => {
|
|||
dispatch: jest.fn(),
|
||||
tzOffset: 0,
|
||||
getConfigValue: jest.fn(),
|
||||
imageAgeInfo: { newestDate: "", toOldest: 1 },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
import * as React from "react";
|
||||
import { ImageFilterMenu, ImageFilterMenuProps } from "../image_filter_menu";
|
||||
import { shallow, mount } from "enzyme";
|
||||
import { fakeWebAppConfig } from "../../../__test_support__/fake_state/resources";
|
||||
import { StringConfigKey } from "../../../config_storage/web_app_configs";
|
||||
import { setWebAppConfigValue } from "../../../config_storage/actions";
|
||||
|
||||
const mockConfig = fakeWebAppConfig();
|
||||
jest.mock("../../../resources/selectors", () => {
|
||||
return {
|
||||
getWebAppConfig: () => mockConfig,
|
||||
assertUuid: jest.fn()
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock("../../../config_storage/actions", () => {
|
||||
return {
|
||||
setWebAppConfigValue: jest.fn()
|
||||
};
|
||||
});
|
||||
|
||||
describe("<ImageFilterMenu />", () => {
|
||||
beforeEach(function () {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
mockConfig.body.photo_filter_begin = "";
|
||||
mockConfig.body.photo_filter_end = "";
|
||||
|
||||
const fakeProps = (): ImageFilterMenuProps => {
|
||||
return {
|
||||
tzOffset: 0,
|
||||
dispatch: jest.fn(),
|
||||
getConfigValue: jest.fn(x => mockConfig.body[x as StringConfigKey]),
|
||||
imageAgeInfo: { newestDate: "", toOldest: 1 }
|
||||
};
|
||||
};
|
||||
|
||||
it("renders", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<ImageFilterMenu {...p} />);
|
||||
["Date", "Time", "Newer than", "Older than"].map(string =>
|
||||
expect(wrapper.text()).toContain(string));
|
||||
});
|
||||
|
||||
const testFilterSetDate =
|
||||
(filter: "beginDate" | "endDate",
|
||||
key: "photo_filter_begin" | "photo_filter_end",
|
||||
i: number) => {
|
||||
it(`sets filter: ${filter}`, () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<ImageFilterMenu {...p} />);
|
||||
wrapper.find("BlurableInput").at(i).simulate("commit", {
|
||||
currentTarget: { value: "2001-01-03" }
|
||||
});
|
||||
expect(wrapper.state()[filter]).toEqual("2001-01-03");
|
||||
expect(setWebAppConfigValue)
|
||||
.toHaveBeenCalledWith(key, "2001-01-03T00:00:00.000Z");
|
||||
});
|
||||
};
|
||||
|
||||
testFilterSetDate("beginDate", "photo_filter_begin", 0);
|
||||
testFilterSetDate("endDate", "photo_filter_end", 2);
|
||||
|
||||
const testFilterSetTime =
|
||||
(filter: "beginTime" | "endTime",
|
||||
key: "photo_filter_begin" | "photo_filter_end",
|
||||
i: number) => {
|
||||
it(`sets filter: ${filter}`, () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<ImageFilterMenu {...p} />);
|
||||
wrapper.setState({ beginDate: "2001-01-03", endDate: "2001-01-03" });
|
||||
wrapper.find("BlurableInput").at(i).simulate("commit", {
|
||||
currentTarget: { value: "05:00" }
|
||||
});
|
||||
expect(wrapper.state()[filter]).toEqual("05:00");
|
||||
expect(setWebAppConfigValue)
|
||||
.toHaveBeenCalledWith(key, "2001-01-03T05:00:00.000Z");
|
||||
});
|
||||
};
|
||||
|
||||
testFilterSetTime("beginTime", "photo_filter_begin", 1);
|
||||
testFilterSetTime("endTime", "photo_filter_end", 3);
|
||||
|
||||
it("loads values from config", () => {
|
||||
mockConfig.body.photo_filter_begin = "2001-01-03T05:00:00.000Z";
|
||||
mockConfig.body.photo_filter_end = "2001-01-03T06:00:00.000Z";
|
||||
const wrapper = shallow(<ImageFilterMenu {...fakeProps()} />);
|
||||
expect(wrapper.state()).toEqual({
|
||||
beginDate: "2001-01-03", beginTime: "05:00",
|
||||
endDate: "2001-01-03", endTime: "06:00", slider: NaN
|
||||
});
|
||||
});
|
||||
|
||||
it("changes slider", () => {
|
||||
const p = fakeProps();
|
||||
p.imageAgeInfo.newestDate = "2001-01-03T05:00:00.000Z";
|
||||
const wrapper = shallow(<ImageFilterMenu {...p} />);
|
||||
wrapper.find("Slider").simulate("change", 1);
|
||||
expect(wrapper.state().slider).toEqual(1);
|
||||
expect(setWebAppConfigValue)
|
||||
.toHaveBeenCalledWith("photo_filter_begin", "2001-01-02T00:00:00.000Z");
|
||||
expect(setWebAppConfigValue)
|
||||
.toHaveBeenCalledWith("photo_filter_end", "2001-01-03T00:00:00.000Z");
|
||||
});
|
||||
|
||||
it("displays slider labels", () => {
|
||||
const p = fakeProps();
|
||||
p.imageAgeInfo.newestDate = "2001-01-03T00:00:00.000Z";
|
||||
const wrapper = mount(<ImageFilterMenu {...p} />);
|
||||
["Jan-1", "Jan-2", "Jan-3"].map(date =>
|
||||
expect(wrapper.text()).toContain(date));
|
||||
});
|
||||
});
|
|
@ -4,7 +4,7 @@ import { LayerToggle } from "./layer_toggle";
|
|||
import { GardenMapLegendProps } from "./interfaces";
|
||||
import { history } from "../../history";
|
||||
import { atMaxZoom, atMinZoom } from "./zoom";
|
||||
import { ImageFilterMenu } from "./layers/image_layer";
|
||||
import { ImageFilterMenu } from "./image_filter_menu";
|
||||
|
||||
export function GardenMapLegend(props: GardenMapLegendProps) {
|
||||
|
||||
|
@ -22,6 +22,7 @@ export function GardenMapLegend(props: GardenMapLegendProps) {
|
|||
dispatch,
|
||||
tzOffset,
|
||||
getConfigValue,
|
||||
imageAgeInfo,
|
||||
} = props;
|
||||
|
||||
const plusBtnClass = atMaxZoom() ? "disabled" : "";
|
||||
|
@ -76,7 +77,8 @@ export function GardenMapLegend(props: GardenMapLegendProps) {
|
|||
popover={<ImageFilterMenu
|
||||
tzOffset={tzOffset}
|
||||
dispatch={dispatch}
|
||||
getConfigValue={getConfigValue} />} />
|
||||
getConfigValue={getConfigValue}
|
||||
imageAgeInfo={imageAgeInfo} />} />
|
||||
</div>
|
||||
<div className="farmbot-origin">
|
||||
<label>
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
import * as React from "react";
|
||||
import { BlurableInput } from "../../ui/index";
|
||||
import { t } from "i18next";
|
||||
import { offsetTime } from "../farm_events/edit_fe_form";
|
||||
import { setWebAppConfigValue, GetWebAppConfigValue } from "../../config_storage/actions";
|
||||
import * as moment from "moment";
|
||||
import { formatDate, formatTime } from "../farm_events/map_state_to_props_add_edit";
|
||||
import { Slider } from "@blueprintjs/core";
|
||||
|
||||
interface ImageFilterMenuState {
|
||||
beginDate: string | undefined;
|
||||
beginTime: string | undefined;
|
||||
endDate: string | undefined;
|
||||
endTime: string | undefined;
|
||||
slider: number;
|
||||
}
|
||||
|
||||
export interface ImageFilterMenuProps {
|
||||
tzOffset: number;
|
||||
dispatch: Function;
|
||||
getConfigValue: GetWebAppConfigValue;
|
||||
imageAgeInfo: { newestDate: string, toOldest: number };
|
||||
}
|
||||
|
||||
export class ImageFilterMenu
|
||||
extends React.Component<ImageFilterMenuProps, Partial<ImageFilterMenuState>> {
|
||||
constructor(props: ImageFilterMenuProps) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const { newestDate, toOldest } = this.props.imageAgeInfo;
|
||||
const beginDatetime = this.props.getConfigValue("photo_filter_begin");
|
||||
this.setState({
|
||||
slider: toOldest + 1 - (beginDatetime
|
||||
? Math.abs(moment(beginDatetime.toString())
|
||||
.diff(moment(newestDate).clone(), "days")) : 0)
|
||||
});
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
componentWillReceiveProps() {
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
updateState = () => {
|
||||
const beginDatetime = this.props.getConfigValue("photo_filter_begin");
|
||||
const endDatetime = this.props.getConfigValue("photo_filter_end");
|
||||
const { tzOffset } = this.props;
|
||||
this.setState({
|
||||
beginDate: beginDatetime
|
||||
? formatDate(beginDatetime.toString(), tzOffset) : undefined,
|
||||
beginTime: beginDatetime
|
||||
? formatTime(beginDatetime.toString(), tzOffset) : undefined,
|
||||
endDate: endDatetime
|
||||
? formatDate(endDatetime.toString(), tzOffset) : undefined,
|
||||
endTime: endDatetime
|
||||
? formatTime(endDatetime.toString(), tzOffset) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
setDatetime = (datetime: keyof ImageFilterMenuState) => {
|
||||
return (e: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
const input = e.currentTarget.value;
|
||||
this.setState({ [datetime]: input });
|
||||
const { beginDate, beginTime, endDate, endTime } = this.state;
|
||||
const { dispatch, tzOffset } = this.props;
|
||||
let value = undefined;
|
||||
switch (datetime) {
|
||||
case "beginDate":
|
||||
value = offsetTime(input, beginTime || "00:00", tzOffset);
|
||||
dispatch(setWebAppConfigValue("photo_filter_begin", value));
|
||||
break;
|
||||
case "beginTime":
|
||||
if (beginDate) {
|
||||
value = offsetTime(beginDate, input, tzOffset);
|
||||
dispatch(setWebAppConfigValue("photo_filter_begin", value));
|
||||
}
|
||||
break;
|
||||
case "endDate":
|
||||
value = offsetTime(input, endTime || "00:00", tzOffset);
|
||||
dispatch(setWebAppConfigValue("photo_filter_end", value));
|
||||
break;
|
||||
case "endTime":
|
||||
if (endDate) {
|
||||
value = offsetTime(endDate, input, tzOffset);
|
||||
dispatch(setWebAppConfigValue("photo_filter_end", value));
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
sliderChange = (slider: number) => {
|
||||
const { newestDate, toOldest } = this.props.imageAgeInfo;
|
||||
this.setState({ slider });
|
||||
const { dispatch, tzOffset } = this.props;
|
||||
const calcDate = (day: number) =>
|
||||
moment(newestDate).subtract(toOldest - day, "days").toISOString();
|
||||
const begin = offsetTime(calcDate(slider - 1), "00:00", tzOffset);
|
||||
const end = offsetTime(calcDate(slider), "00:00", tzOffset);
|
||||
dispatch(setWebAppConfigValue("photo_filter_begin", begin));
|
||||
dispatch(setWebAppConfigValue("photo_filter_end", end));
|
||||
}
|
||||
|
||||
renderLabel = (day: number) => {
|
||||
const { newestDate, toOldest } = this.props.imageAgeInfo;
|
||||
return moment(newestDate)
|
||||
.utcOffset(this.props.tzOffset)
|
||||
.subtract(toOldest + 1 - day, "days")
|
||||
.format("MMM-D");
|
||||
}
|
||||
|
||||
get labelStepSize() {
|
||||
return Math.max(Math.round(this.props.imageAgeInfo.toOldest / 5), 1);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { beginDate, beginTime, endDate, endTime, slider } = this.state;
|
||||
return <div className={"image-filter-menu"}>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th><label>{t("Date")}</label></th>
|
||||
<th><label>{t("Time")}</label></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<label>{t("Newer than")}</label>
|
||||
</td>
|
||||
<td>
|
||||
<BlurableInput
|
||||
type="date"
|
||||
name="beginDate"
|
||||
value={beginDate || ""}
|
||||
allowEmpty={true}
|
||||
onCommit={this.setDatetime("beginDate")} />
|
||||
</td>
|
||||
<td>
|
||||
<BlurableInput
|
||||
type="time"
|
||||
name="beginTime"
|
||||
value={beginTime || ""}
|
||||
allowEmpty={true}
|
||||
disabled={!beginDate}
|
||||
onCommit={this.setDatetime("beginTime")} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label>{t("Older than")}</label>
|
||||
</td>
|
||||
<td>
|
||||
<BlurableInput
|
||||
type="date"
|
||||
name="endDate"
|
||||
value={endDate || ""}
|
||||
allowEmpty={true}
|
||||
onCommit={this.setDatetime("endDate")} />
|
||||
</td>
|
||||
<td>
|
||||
<BlurableInput
|
||||
type="time"
|
||||
name="endTime"
|
||||
value={endTime || ""}
|
||||
allowEmpty={true}
|
||||
disabled={!endDate}
|
||||
onCommit={this.setDatetime("endTime")} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<Slider
|
||||
min={0}
|
||||
max={this.props.imageAgeInfo.toOldest + 1}
|
||||
labelStepSize={this.labelStepSize}
|
||||
value={slider}
|
||||
onChange={this.sliderChange}
|
||||
renderLabel={this.renderLabel}
|
||||
showTrackFill={false} />
|
||||
</div>;
|
||||
}
|
||||
}
|
|
@ -39,6 +39,7 @@ export interface GardenMapLegendProps {
|
|||
dispatch: Function;
|
||||
tzOffset: number;
|
||||
getConfigValue: GetWebAppConfigValue;
|
||||
imageAgeInfo: { newestDate: string, toOldest: number };
|
||||
}
|
||||
|
||||
export type MapTransformProps = {
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import * as React from "react";
|
||||
import {
|
||||
ImageLayer, ImageLayerProps, ImageFilterMenu, ImageFilterMenuProps
|
||||
} from "../image_layer";
|
||||
import { ImageLayer, ImageLayerProps } from "../image_layer";
|
||||
import { shallow } from "enzyme";
|
||||
import { fakeImage, fakeWebAppConfig } from "../../../../__test_support__/fake_state/resources";
|
||||
import { Actions } from "../../../../constants";
|
||||
import { StringConfigKey } from "../../../../config_storage/web_app_configs";
|
||||
|
||||
const mockConfig = fakeWebAppConfig();
|
||||
jest.mock("../../../../resources/selectors", () => {
|
||||
|
@ -40,7 +36,7 @@ describe("<ImageLayer/>", () => {
|
|||
|
||||
it("shows images", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<ImageLayer {...p } />);
|
||||
const wrapper = shallow(<ImageLayer {...p} />);
|
||||
const layer = wrapper.find("#image-layer");
|
||||
expect(layer.find("MapImage").html()).toContain("x=\"0\"");
|
||||
});
|
||||
|
@ -48,7 +44,7 @@ describe("<ImageLayer/>", () => {
|
|||
it("toggles visibility off", () => {
|
||||
const p = fakeProps();
|
||||
p.visible = false;
|
||||
const wrapper = shallow(<ImageLayer {...p } />);
|
||||
const wrapper = shallow(<ImageLayer {...p} />);
|
||||
const layer = wrapper.find("#image-layer");
|
||||
expect(layer.find("MapImage").length).toEqual(0);
|
||||
});
|
||||
|
@ -57,92 +53,8 @@ describe("<ImageLayer/>", () => {
|
|||
const p = fakeProps();
|
||||
p.images[0].body.created_at = "2018-01-22T05:00:00.000Z";
|
||||
p.getConfigValue = () => "2018-01-23T05:00:00.000Z";
|
||||
const wrapper = shallow(<ImageLayer {...p } />);
|
||||
const wrapper = shallow(<ImageLayer {...p} />);
|
||||
const layer = wrapper.find("#image-layer");
|
||||
expect(layer.find("MapImage").length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("<ImageFilterMenu />", () => {
|
||||
beforeEach(function () {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const getState = jest.fn(() => ({ resources: { index: {} } }));
|
||||
mockConfig.body.photo_filter_begin = "";
|
||||
mockConfig.body.photo_filter_end = "";
|
||||
|
||||
const fakeProps = (): ImageFilterMenuProps => {
|
||||
return {
|
||||
tzOffset: 0,
|
||||
dispatch: jest.fn(),
|
||||
getConfigValue: jest.fn(x => mockConfig.body[x as StringConfigKey])
|
||||
};
|
||||
};
|
||||
|
||||
it("renders", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<ImageFilterMenu {...p } />);
|
||||
["Date", "Time", "Newer than", "Older than"].map(string =>
|
||||
expect(wrapper.text()).toContain(string));
|
||||
});
|
||||
|
||||
const testFilterSetDate =
|
||||
(filter: "beginDate" | "endDate",
|
||||
key: "photo_filter_begin" | "photo_filter_end",
|
||||
i: number) => {
|
||||
it(`sets filter: ${filter}`, () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<ImageFilterMenu {...p } />);
|
||||
wrapper.find("BlurableInput").at(i).simulate("commit", {
|
||||
currentTarget: { value: "2001-01-03" }
|
||||
});
|
||||
expect(wrapper.state()[filter]).toEqual("2001-01-03");
|
||||
(p.dispatch as jest.Mock).mock.calls[0][0](p.dispatch, getState);
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.EDIT_RESOURCE,
|
||||
payload: expect.objectContaining({
|
||||
update: { [key]: "2001-01-03T00:00:00.000Z" }
|
||||
})
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
testFilterSetDate("beginDate", "photo_filter_begin", 0);
|
||||
testFilterSetDate("endDate", "photo_filter_end", 2);
|
||||
|
||||
const testFilterSetTime =
|
||||
(filter: "beginTime" | "endTime",
|
||||
key: "photo_filter_begin" | "photo_filter_end",
|
||||
i: number) => {
|
||||
it(`sets filter: ${filter}`, () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<ImageFilterMenu {...p } />);
|
||||
wrapper.setState({ beginDate: "2001-01-03", endDate: "2001-01-03" });
|
||||
wrapper.find("BlurableInput").at(i).simulate("commit", {
|
||||
currentTarget: { value: "05:00" }
|
||||
});
|
||||
expect(wrapper.state()[filter]).toEqual("05:00");
|
||||
(p.dispatch as jest.Mock).mock.calls[0][0](p.dispatch, getState);
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.EDIT_RESOURCE,
|
||||
payload: expect.objectContaining({
|
||||
update: { [key]: "2001-01-03T05:00:00.000Z" }
|
||||
})
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
testFilterSetTime("beginTime", "photo_filter_begin", 1);
|
||||
testFilterSetTime("endTime", "photo_filter_end", 3);
|
||||
|
||||
it("loads values from config", () => {
|
||||
mockConfig.body.photo_filter_begin = "2001-01-03T05:00:00.000Z";
|
||||
mockConfig.body.photo_filter_end = "2001-01-03T06:00:00.000Z";
|
||||
const wrapper = shallow(<ImageFilterMenu {...fakeProps() } />);
|
||||
expect(wrapper.state()).toEqual({
|
||||
beginDate: "2001-01-03", beginTime: "05:00",
|
||||
endDate: "2001-01-03", endTime: "06:00"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,12 +4,8 @@ import { CameraCalibrationData } from "../../interfaces";
|
|||
import { TaggedImage } from "../../../resources/tagged_resources";
|
||||
import { MapImage } from "../map_image";
|
||||
import { reverse, cloneDeep } from "lodash";
|
||||
import { BlurableInput } from "../../../ui/index";
|
||||
import { t } from "i18next";
|
||||
import { offsetTime } from "../../farm_events/edit_fe_form";
|
||||
import { setWebAppConfigValue, GetWebAppConfigValue } from "../../../config_storage/actions";
|
||||
import { GetWebAppConfigValue } from "../../../config_storage/actions";
|
||||
import * as moment from "moment";
|
||||
import { formatDate, formatTime } from "../../farm_events/map_state_to_props_add_edit";
|
||||
|
||||
export interface ImageLayerProps {
|
||||
visible: boolean;
|
||||
|
@ -24,7 +20,7 @@ export function ImageLayer(props: ImageLayerProps) {
|
|||
const {
|
||||
visible, images, mapTransformProps, cameraCalibrationData, sizeOverride,
|
||||
getConfigValue
|
||||
} = props;
|
||||
} = props;
|
||||
const imageFilterBegin = getConfigValue("photo_filter_begin");
|
||||
const imageFilterEnd = getConfigValue("photo_filter_end");
|
||||
return <g id="image-layer">
|
||||
|
@ -44,139 +40,3 @@ export function ImageLayer(props: ImageLayerProps) {
|
|||
)}
|
||||
</g>;
|
||||
}
|
||||
|
||||
interface ImageFilterMenuState {
|
||||
beginDate: string | undefined;
|
||||
beginTime: string | undefined;
|
||||
endDate: string | undefined;
|
||||
endTime: string | undefined;
|
||||
}
|
||||
|
||||
export interface ImageFilterMenuProps {
|
||||
tzOffset: number;
|
||||
dispatch: Function;
|
||||
getConfigValue: GetWebAppConfigValue;
|
||||
}
|
||||
|
||||
export class ImageFilterMenu
|
||||
extends React.Component<ImageFilterMenuProps, Partial<ImageFilterMenuState>> {
|
||||
constructor(props: ImageFilterMenuProps) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
componentWillReceiveProps() {
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
updateState = () => {
|
||||
const beginDatetime = this.props.getConfigValue("photo_filter_begin");
|
||||
const endDatetime = this.props.getConfigValue("photo_filter_end");
|
||||
const { tzOffset } = this.props;
|
||||
this.setState({
|
||||
beginDate: beginDatetime
|
||||
? formatDate(beginDatetime.toString(), tzOffset) : undefined,
|
||||
beginTime: beginDatetime
|
||||
? formatTime(beginDatetime.toString(), tzOffset) : undefined,
|
||||
endDate: endDatetime
|
||||
? formatDate(endDatetime.toString(), tzOffset) : undefined,
|
||||
endTime: endDatetime
|
||||
? formatTime(endDatetime.toString(), tzOffset) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
setDatetime = (datetime: keyof ImageFilterMenuState) => {
|
||||
return (e: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
const input = e.currentTarget.value;
|
||||
this.setState({ [datetime]: input });
|
||||
const { beginDate, beginTime, endDate, endTime } = this.state;
|
||||
const { dispatch, tzOffset } = this.props;
|
||||
let value = undefined;
|
||||
switch (datetime) {
|
||||
case "beginDate":
|
||||
value = offsetTime(input, beginTime || "00:00", tzOffset);
|
||||
dispatch(setWebAppConfigValue("photo_filter_begin", value));
|
||||
break;
|
||||
case "beginTime":
|
||||
if (beginDate) {
|
||||
value = offsetTime(beginDate, input, tzOffset);
|
||||
dispatch(setWebAppConfigValue("photo_filter_begin", value));
|
||||
}
|
||||
break;
|
||||
case "endDate":
|
||||
value = offsetTime(input, endTime || "00:00", tzOffset);
|
||||
dispatch(setWebAppConfigValue("photo_filter_end", value));
|
||||
break;
|
||||
case "endTime":
|
||||
if (endDate) {
|
||||
value = offsetTime(endDate, input, tzOffset);
|
||||
dispatch(setWebAppConfigValue("photo_filter_end", value));
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
render() {
|
||||
const { beginDate, beginTime, endDate, endTime } = this.state;
|
||||
return <table className={"image-filter-menu"}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th><label>{t("Date")}</label></th>
|
||||
<th><label>{t("Time")}</label></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<label>{t("Newer than")}</label>
|
||||
</td>
|
||||
<td>
|
||||
<BlurableInput
|
||||
type="date"
|
||||
name="beginDate"
|
||||
value={beginDate || ""}
|
||||
allowEmpty={true}
|
||||
onCommit={this.setDatetime("beginDate")} />
|
||||
</td>
|
||||
<td>
|
||||
<BlurableInput
|
||||
type="time"
|
||||
name="beginTime"
|
||||
value={beginTime || ""}
|
||||
allowEmpty={true}
|
||||
disabled={!beginDate}
|
||||
onCommit={this.setDatetime("beginTime")} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label>{t("Older than")}</label>
|
||||
</td>
|
||||
<td>
|
||||
<BlurableInput
|
||||
type="date"
|
||||
name="endDate"
|
||||
value={endDate || ""}
|
||||
allowEmpty={true}
|
||||
onCommit={this.setDatetime("endDate")} />
|
||||
</td>
|
||||
<td>
|
||||
<BlurableInput
|
||||
type="time"
|
||||
name="endTime"
|
||||
value={endTime || ""}
|
||||
allowEmpty={true}
|
||||
disabled={!endDate}
|
||||
onCommit={this.setDatetime("endTime")} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue