import { Content } from "../../../../constants"; import { initSave, edit, save } from "../../../../api/crud"; import { AxisNumberProperty, TaggedPlant, MapTransformProps } from "../../interfaces"; import { Plant, DEFAULT_PLANT_RADIUS } from "../../../plant"; import moment from "moment"; import { unpackUUID } from "../../../../util"; import { isNumber, isString } from "lodash"; import { CropLiveSearchResult, GardenMapState } from "../../../interfaces"; import { getPathArray } from "../../../../history"; import { findBySlug } from "../../../search_selectors"; import { transformXY, round } from "../../util"; import { movePlant } from "../../../actions"; import { cachedCrop } from "../../../../open_farm/cached_crop"; import { t } from "../../../../i18next_wrapper"; import { error } from "../../../../toast/toast"; /** Return a new plant or plantTemplate object. */ export const newPlantKindAndBody = (props: { x: number, y: number, slug: string, cropName: string, openedSavedGarden: string | undefined }): { kind: TaggedPlant["kind"], body: TaggedPlant["body"] } => { const savedGardenId = isString(props.openedSavedGarden) ? unpackUUID(props.openedSavedGarden).remoteId : undefined; return isNumber(savedGardenId) ? { kind: "PlantTemplate", body: { x: props.x, y: props.y, z: 0, openfarm_slug: props.slug, name: props.cropName, radius: DEFAULT_PLANT_RADIUS, saved_garden_id: savedGardenId, } } : { kind: "Point", body: Plant({ x: props.x, y: props.y, openfarm_slug: props.slug, name: props.cropName, created_at: moment().toISOString(), radius: DEFAULT_PLANT_RADIUS }) }; }; /** Create a new plant in the garden map. */ export const createPlant = (props: { cropName: string, slug: string, gardenCoords: AxisNumberProperty, gridSize: AxisNumberProperty | undefined, dispatch: Function, openedSavedGarden: string | undefined }): void => { const { cropName, slug, gardenCoords, gridSize, openedSavedGarden } = props; const { x, y } = gardenCoords; const tooLow = x < 0 || y < 0; // negative (beyond grid start) const tooHigh = gridSize ? x > gridSize.x || y > gridSize.y // beyond grid end : false; const outsideGrid = tooLow || tooHigh; if (outsideGrid) { error(t(Content.OUTSIDE_PLANTING_AREA)); } else { const p = newPlantKindAndBody({ x, y, slug, cropName, openedSavedGarden }); // Stop non-plant objects from creating generic plants in the map if (p.body.name != "name" && p.body.openfarm_slug != "slug") { // Create and save a new plant in the garden map props.dispatch(initSave(p.kind, p.body)); } } }; /** Create a plant upon drop. */ export const dropPlant = (props: { gardenCoords: AxisNumberProperty | undefined, cropSearchResults: CropLiveSearchResult[], openedSavedGarden: string | undefined, gridSize: AxisNumberProperty, dispatch: Function, }) => { const { gardenCoords, openedSavedGarden, gridSize, dispatch } = props; if (gardenCoords) { const slug = getPathArray()[5]; const { crop } = findBySlug(props.cropSearchResults || [], slug); createPlant({ cropName: crop.name, slug: crop.slug, gardenCoords, gridSize, dispatch, openedSavedGarden, }); } else { throw new Error(`Missing 'drop-area-svg', 'farm-designer-map', or 'farm-designer' while trying to add a plant.`); } }; /** Move a plant in the garden map. */ export const dragPlant = (props: { getPlant: () => TaggedPlant | undefined, mapTransformProps: MapTransformProps, isDragging: boolean | undefined, dispatch: Function, setMapState: (x: Partial) => void, gridSize: AxisNumberProperty, pageX: number, pageY: number, qPageX: number | undefined, qPageY: number | undefined, }) => { const plant = props.getPlant(); const map = document.querySelector(".farm-designer-map"); const { isDragging, gridSize, pageX, pageY, qPageX, qPageY } = props; const { quadrant, xySwap } = props.mapTransformProps; if (isDragging && plant && map) { const zoomLvl = parseFloat(window.getComputedStyle(map).zoom || "1"); const { qx, qy } = transformXY(pageX, pageY, props.mapTransformProps); const deltaX = Math.round((qx - (qPageX || qx)) / zoomLvl); const deltaY = Math.round((qy - (qPageY || qy)) / zoomLvl); const dX = xySwap && (quadrant % 2 === 1) ? -deltaX : deltaX; const dY = xySwap && (quadrant % 2 === 1) ? -deltaY : deltaY; props.setMapState({ qPageX: qx, qPageY: qy, activeDragXY: { x: plant.body.x + dX, y: plant.body.y + dY, z: 0 } }); props.dispatch(movePlant({ deltaX: dX, deltaY: dY, plant, gridSize })); } }; /** Fetch the current plant's spread. */ export const setActiveSpread = (props: { selectedPlant: TaggedPlant | undefined, slug: string, setMapState: (x: Partial) => void, }): void => { const { selectedPlant } = props; const defaultSpreadCm = selectedPlant ? selectedPlant.body.radius : 0; cachedCrop(props.slug) .then(({ spread }) => // Convert spread diameter from cm to mm. // `radius * 10` is the default value for spread diameter (in mm). props.setMapState({ activeDragSpread: (spread || defaultSpreadCm) * 10 }) ); }; /** Prepare for plant move. */ export const beginPlantDrag = (props: { plant: TaggedPlant | undefined, setMapState: (x: Partial) => void, selectedPlant: TaggedPlant | undefined, }): void => { props.setMapState({ isDragging: true }); if (props.plant) { setActiveSpread({ selectedPlant: props.selectedPlant, slug: props.plant.body.openfarm_slug, setMapState: props.setMapState, }); } }; /** If dragging a plant, save the new location. */ export const maybeSavePlantLocation = (props: { plant: TaggedPlant | undefined, isDragging: boolean | undefined, dispatch: Function, }) => { if (props.plant && props.isDragging) { props.dispatch(edit(props.plant, { x: round(props.plant.body.x), y: round(props.plant.body.y) })); props.dispatch(save(props.plant.uuid)); } };