diff --git a/.ruby-version b/.ruby-version index 9c25013dbb..86fb650440 100755 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.6 +3.3.7 diff --git a/Gemfile b/Gemfile index de4a70b004..8019878b75 100755 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,5 @@ source "https://rubygems.org" -ruby "~> 3.3.6" +ruby "~> 3.3.7" gem "rails", "~> 6" gem "active_model_serializers" @@ -11,6 +11,7 @@ gem "discard" gem "google-cloud-storage", "~> 1.11" gem "jwt" gem "kaminari" +gem "logger" gem "mutations" gem "pg" gem "rabbitmq_http_api_client" diff --git a/Gemfile.lock b/Gemfile.lock index 7c4c4b97b3..9c3325c3e8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -71,7 +71,7 @@ GEM ast (2.4.2) base64 (0.2.0) bcrypt (3.1.20) - bigdecimal (3.1.8) + bigdecimal (3.1.9) builder (3.3.0) bunny (2.23.0) amq-protocol (~> 2.3, >= 2.3.1) @@ -80,7 +80,7 @@ GEM activesupport climate_control (1.2.0) coderay (1.1.3) - concurrent-ruby (1.3.4) + concurrent-ruby (1.3.5) crack (1.0.0) bigdecimal rexml @@ -105,13 +105,13 @@ GEM responders warden (~> 1.2.3) diff-lcs (1.5.1) - digest-crc (0.6.5) + digest-crc (0.7.0) rake (>= 12.0.0, < 14.0.0) discard (1.4.0) activerecord (>= 4.2, < 9.0) docile (1.4.1) e2mmap (0.1.0) - erubi (1.13.0) + erubi (1.13.1) factory_bot (6.5.0) activesupport (>= 5.0.0) factory_bot_rails (6.4.4) @@ -119,7 +119,7 @@ GEM railties (>= 5.0.0) faker (3.5.1) i18n (>= 1.8.11, < 2) - faraday (2.12.1) + faraday (2.12.2) faraday-net_http (>= 2.0, < 3.5) json logger @@ -129,7 +129,7 @@ GEM net-http (>= 0.5.0) globalid (1.2.1) activesupport (>= 6.1) - google-apis-core (0.15.1) + google-apis-core (0.16.0) addressable (~> 2.5, >= 2.5.1) googleauth (~> 1.9) httpclient (>= 2.8.3, < 3.a) @@ -139,7 +139,7 @@ GEM retriable (>= 2.0, < 4.a) google-apis-iamcredentials_v1 (0.22.0) google-apis-core (>= 0.15.0, < 2.a) - google-apis-storage_v1 (0.48.0) + google-apis-storage_v1 (0.49.0) google-apis-core (>= 0.15.0, < 2.a) google-cloud-core (1.7.1) google-cloud-env (>= 1.0, < 3.a) @@ -147,7 +147,7 @@ GEM google-cloud-env (2.2.1) faraday (>= 1.0, < 3.a) google-cloud-errors (1.4.0) - google-cloud-storage (1.53.0) + google-cloud-storage (1.54.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-core (~> 0.13) @@ -156,9 +156,11 @@ GEM google-cloud-core (~> 1.6) googleauth (~> 1.9) mini_mime (~> 1.0) - googleauth (1.11.2) + google-logging-utils (0.1.0) + googleauth (1.13.1) faraday (>= 1.0, < 3.a) - google-cloud-env (~> 2.1) + google-cloud-env (~> 2.2) + google-logging-utils (~> 0.1) jwt (>= 1.4, < 3.0) multi_json (~> 1.11) os (>= 0.9, < 2.0) @@ -166,11 +168,11 @@ GEM hashdiff (1.1.2) hashie (4.1.0) httpclient (2.8.3) - i18n (1.14.6) + i18n (1.14.7) concurrent-ruby (~> 1.0) - json (2.9.0) + json (2.9.1) jsonapi-renderer (0.2.2) - jwt (2.9.3) + jwt (2.10.1) base64 kaminari (1.2.2) activesupport (>= 4.1.0) @@ -184,13 +186,13 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) - logger (1.6.2) + logger (1.6.5) lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.23.1) + loofah (2.24.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -208,7 +210,7 @@ GEM mutex_m (0.3.0) net-http (0.6.0) uri - net-imap (0.5.1) + net-imap (0.5.5) date net-protocol net-pop (0.1.2) @@ -218,13 +220,13 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.4) - nokogiri (1.16.8-aarch64-linux) + nokogiri (1.18.2-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.16.8-x86_64-linux) + nokogiri (1.18.2-x86_64-linux-gnu) racc (~> 1.4) orm_adapter (0.5.0) os (1.1.4) - parser (3.3.6.0) + parser (3.3.7.0) ast (~> 2.4.1) racc passenger (6.0.23) @@ -232,7 +234,7 @@ GEM rackup rake (>= 12.3.3) pg (1.5.9) - pry (0.15.0) + pry (0.15.2) coderay (~> 1.1) method_source (~> 1.0) pry-rails (0.3.11) @@ -250,7 +252,7 @@ GEM rack (>= 1.0, < 4) rack-cors (2.0.2) rack (>= 2.0.0) - rack-test (2.1.0) + rack-test (2.2.0) rack (>= 1.3) rackup (1.0.1) rack (< 3) @@ -274,7 +276,7 @@ GEM activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.1) + rails-html-sanitizer (1.6.2) loofah (~> 2.21) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) rails_12factor (0.0.3) @@ -301,7 +303,7 @@ GEM actionpack (>= 5.2) railties (>= 5.2) retriable (3.1.2) - rexml (3.3.9) + rexml (3.4.0) rollbar (3.6.0) rspec (3.13.0) rspec-core (~> 3.13.0) @@ -329,9 +331,9 @@ GEM scenic (1.8.0) activerecord (>= 4.0.0) railties (>= 4.0.0) - scout_apm (5.4.0) + scout_apm (5.6.0) parser - secure_headers (7.0.0) + secure_headers (7.1.0) set (1.1.1) signet (0.19.0) addressable (~> 2.8) @@ -360,11 +362,11 @@ GEM thor (1.3.2) thwait (0.2.0) e2mmap - timeout (0.4.2) + timeout (0.4.3) trailblazer-option (0.1.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - tzinfo-data (1.2024.2) + tzinfo-data (1.2025.1) tzinfo (>= 1.0.0) uber (0.1.0) uri (1.0.2) @@ -378,7 +380,8 @@ GEM crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) webrick (1.9.1) - websocket-driver (0.7.6) + websocket-driver (0.7.7) + base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) zeitwerk (2.7.1) @@ -402,6 +405,7 @@ DEPENDENCIES hashdiff jwt kaminari + logger lograge mutations passenger @@ -431,7 +435,7 @@ DEPENDENCIES webmock RUBY VERSION - ruby 3.3.6p108 + ruby 3.3.7p123 BUNDLED WITH - 2.5.23 + 2.6.2 diff --git a/config/application.rb b/config/application.rb index 472b9206ca..4372fcfbba 100755 --- a/config/application.rb +++ b/config/application.rb @@ -77,7 +77,6 @@ class Application < Rails::Application ENV["MQTT_HOST"], "api.github.com", "raw.githubusercontent.com", - "openfarm.cc", "api.rollbar.com", PARCELJS_URL, ENV["FORCE_SSL"] ? "wss:" : "ws:", diff --git a/config/boot.rb b/config/boot.rb index 5e5f0c1fac..f50e8b2877 100755 --- a/config/boot.rb +++ b/config/boot.rb @@ -2,3 +2,4 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) +require 'logger' diff --git a/docker_configs/api.Dockerfile b/docker_configs/api.Dockerfile index 6399440e39..741be04228 100644 --- a/docker_configs/api.Dockerfile +++ b/docker_configs/api.Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:3.3.6 +FROM ruby:3.3.7 RUN curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg > /dev/null && \ sh -c '. /etc/os-release; echo $VERSION_CODENAME; echo "deb http://apt.postgresql.org/pub/repos/apt/ $VERSION_CODENAME-pgdg main" >> /etc/apt/sources.list.d/pgdg.list' && \ apt-get update -qq && apt-get install -y build-essential libpq-dev postgresql postgresql-contrib && \ diff --git a/frontend/__test_support__/additional_mocks.tsx b/frontend/__test_support__/additional_mocks.tsx index 690f9f4663..7ecb5b793f 100644 --- a/frontend/__test_support__/additional_mocks.tsx +++ b/frontend/__test_support__/additional_mocks.tsx @@ -4,10 +4,6 @@ jest.mock("browser-speech", () => ({ talk: jest.fn(), })); -jest.mock("../open_farm/cached_crop", () => ({ - cachedCrop: jest.fn(() => Promise.resolve({ svg_icon: "icon" })), -})); - const { ancestorOrigins } = window.location; delete (window as { location: Location | undefined }).location; window.location = { diff --git a/frontend/__test_support__/fake_crop_search_result.ts b/frontend/__test_support__/fake_crop_search_result.ts deleted file mode 100644 index b5a2980ea4..0000000000 --- a/frontend/__test_support__/fake_crop_search_result.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { CropLiveSearchResult } from "../farm_designer/interfaces"; - -export const fakeCropLiveSearchResult = (): CropLiveSearchResult => ({ - crop: { - name: "Mint", - slug: "mint", - binomial_name: "Mentha spicata", - common_names: ["Mint", "spearmint"], - description: "Mint is a perennial herb with a distinctive taste.", - sun_requirements: "Partial sun", - sowing_method: "Direct seed indoors or outside", - processing_pictures: 0, - main_image_path: "", - spread: 25, - row_spacing: 35, - height: 60, - growing_degree_days: 100, - }, - images: ["fake-mint-svg"], - companions: [ - { name: "Strawberry", slug: "strawberry", svg_icon: "fake-strawberry-svg" }, - ], -}); diff --git a/frontend/__test_support__/fake_crops.ts b/frontend/__test_support__/fake_crops.ts new file mode 100644 index 0000000000..a199e82e0c --- /dev/null +++ b/frontend/__test_support__/fake_crops.ts @@ -0,0 +1,49 @@ +import { Crops } from "../crops/interfaces"; + +export const FAKE_CROPS: Crops = { + "mint": { + name: "Mint", + binomial_name: "Mentha spicata", + common_names: ["Mint", "spearmint"], + description: "Mint is a perennial herb with a distinctive taste.", + sun_requirements: "Partial sun", + sowing_method: "Direct seed indoors or outside", + spread: 100, + row_spacing: 100, + height: 60, + growing_degree_days: 100, + companions: ["strawberry"], + image: "/crops/images/mint.jpg", + icon: "/crops/icons/mint.avif" + }, + "strawberry": { + name: "Strawberry", + binomial_name: "", + common_names: [], + description: "", + sun_requirements: "", + sowing_method: "", + spread: 25, + row_spacing: 35, + height: 60, + growing_degree_days: 100, + companions: [], + icon: "", + image: "", + }, + "generic-plant": { + name: "", + binomial_name: "", + common_names: [], + description: "", + sun_requirements: "", + sowing_method: "", + spread: 0, + row_spacing: 0, + height: 0, + growing_degree_days: 0, + companions: [], + icon: "", + image: "", + }, +}; diff --git a/frontend/__test_support__/fake_designer_state.ts b/frontend/__test_support__/fake_designer_state.ts index 7ba45401b0..d4953a1dbf 100644 --- a/frontend/__test_support__/fake_designer_state.ts +++ b/frontend/__test_support__/fake_designer_state.ts @@ -7,7 +7,6 @@ export const fakeDesignerState = (): DesignerState => ({ selectionPointType: undefined, hoveredPlant: { plantUUID: undefined, - icon: "" }, hoveredPoint: undefined, hoveredPlantListItem: undefined, @@ -16,8 +15,6 @@ export const fakeDesignerState = (): DesignerState => ({ hoveredImage: undefined, hoveredSpread: undefined, cropSearchQuery: "", - cropSearchResults: [], - cropSearchInProgress: false, companionIndex: undefined, plantTypeChangeId: undefined, bulkPlantSlug: undefined, @@ -52,6 +49,7 @@ export const fakeDesignerState = (): DesignerState => ({ cropHeightCurveId: undefined, cropStage: undefined, cropPlantedAt: undefined, + cropRadius: undefined, distanceIndicator: "", panelOpen: true, }); diff --git a/frontend/__test_support__/fake_props.ts b/frontend/__test_support__/fake_props.ts new file mode 100644 index 0000000000..6020edd1ab --- /dev/null +++ b/frontend/__test_support__/fake_props.ts @@ -0,0 +1,13 @@ +import { TaggedPlant } from "../farm_designer/map/interfaces"; +import { AddPlantProps } from "../three_d_garden/bed"; +import { fakeDesignerState } from "./fake_designer_state"; + +export const fakeAddPlantProps = + (plants: TaggedPlant[]): AddPlantProps => ({ + gridSize: { x: 1000, y: 2000 }, + dispatch: jest.fn(), + getConfigValue: jest.fn(), + plants, + curves: [], + designer: fakeDesignerState(), + }); diff --git a/frontend/__test_support__/three_d_mocks.tsx b/frontend/__test_support__/three_d_mocks.tsx index c5f3f2593d..ad1a153a8d 100644 --- a/frontend/__test_support__/three_d_mocks.tsx +++ b/frontend/__test_support__/three_d_mocks.tsx @@ -21,6 +21,10 @@ jest.mock("@react-three/fiber", () => ({ Canvas: ({ children }: { children: ReactNode }) =>
{children}
, addEffect: jest.fn(), useFrame: jest.fn(x => x({ clock: { getElapsedTime: jest.fn(() => 0) } })), + useThree: jest.fn(() => ({ + pointer: { x: 0, y: 0 }, + camera: new THREE.PerspectiveCamera(), + })), })); jest.mock("@react-spring/three", () => ({ @@ -43,6 +47,8 @@ jest.mock("@react-spring/three", () => ({
{children}
, })); +type Event = React.MouseEvent; + jest.mock("@react-three/drei", () => { const useGLTF = jest.fn((key: string) => ({ [ASSETS.models.crossSlide]: { @@ -552,8 +558,26 @@ jest.mock("@react-three/drei", () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any Box: (props: any) =>
{props.children}
, - Extrude: ({ name }: { name: string }) => -
{name}
, + Extrude: ({ name, onClick, onPointerMove }: { + name: string, + onClick: (event: Event) => void, + onPointerMove: (event: Event) => void, + }) => +
+ onPointerMove({ + point: { x: 0, y: 0 }, + ...e, + } as unknown as Event)} + onClick={e => + onClick({ + // @ts-expect-error: This spread always overwrites this property. + stopPropagation: jest.fn(), + point: { x: 0, y: 0 }, + ...e, + } as unknown as Event)}> + {name} +
, Line: ({ name }: { name: string }) =>
{name}
, Trail: ({ name }: { name: string }) => diff --git a/frontend/__tests__/external_urls_test.ts b/frontend/__tests__/external_urls_test.ts index a4791e67c5..b885b81d7c 100644 --- a/frontend/__tests__/external_urls_test.ts +++ b/frontend/__tests__/external_urls_test.ts @@ -17,12 +17,6 @@ describe("ExternalUrl", () => { .toEqual("https://software.farm.bot/docs"); expect(ExternalUrl.softwareForum) .toEqual("https://forum.farmbot.org/c/software"); - expect(ExternalUrl.OpenFarm.cropApi) - .toEqual("https://openfarm.cc/api/v1/crops/"); - expect(ExternalUrl.OpenFarm.cropBrowse) - .toEqual("https://openfarm.cc/crops/"); - expect(ExternalUrl.OpenFarm.newCrop) - .toEqual("https://openfarm.cc/en/crops/new"); expect(ExternalUrl.Video.desktop) .toEqual("https://cdn.shopify.com/s/files/1/2040/0289/files/Farm_Designer_Loop.mp4?9552037556691879018"); expect(ExternalUrl.Video.mobile) diff --git a/frontend/__tests__/interceptors_test.ts b/frontend/__tests__/interceptors_test.ts index f58a7cdcc7..b4dca074e1 100644 --- a/frontend/__tests__/interceptors_test.ts +++ b/frontend/__tests__/interceptors_test.ts @@ -141,11 +141,11 @@ const fake = (responseURL: string): Partial => ({ request: { responseURL } }); describe("isLocalRequest", () => { - it("determines if the URL is local vs. Github, Openfarm, etc...", () => { + it("determines if the URL is local vs. Github, etc...", () => { API.setBaseUrl("http://localhost:3000"); - const openfarm = fake("http://openfarm.cc/foo/bar") as SafeError; - expect(isLocalRequest(openfarm)).toBe(false); + const github = fake("http://github.com/foo/bar") as SafeError; + expect(isLocalRequest(github)).toBe(false); const api = fake("http://localhost:3000/api/tools/1") as SafeError; expect(isLocalRequest(api)).toBe(true); diff --git a/frontend/__tests__/internal_urls_test.ts b/frontend/__tests__/internal_urls_test.ts index 6010dffb31..46dd3890f8 100644 --- a/frontend/__tests__/internal_urls_test.ts +++ b/frontend/__tests__/internal_urls_test.ts @@ -43,8 +43,8 @@ describe("Path()", () => { }); it("returns slug", () => { - location.pathname = Path.mock(Path.cropSearch("slug")); - expect(Path.getSlug(Path.cropSearch())).toEqual("slug"); + location.pathname = Path.mock(Path.cropSearch("green_mint")); + expect(Path.getCropSlug()).toEqual("green-mint"); }); it("returns path with query", () => { diff --git a/frontend/connectivity/__tests__/connect_device/index_test.ts b/frontend/connectivity/__tests__/connect_device/index_test.ts index 00cdafe853..7c93ca9f8d 100644 --- a/frontend/connectivity/__tests__/connect_device/index_test.ts +++ b/frontend/connectivity/__tests__/connect_device/index_test.ts @@ -12,6 +12,11 @@ jest.mock("../../../util/beep", () => ({ beep: jest.fn(), })); +let mockOnline = false; +jest.mock("../../../devices/must_be_online", () => ({ + forceOnline: () => mockOnline, +})); + import { HardwareState } from "../../../devices/interfaces"; import { incomingStatus, @@ -200,30 +205,54 @@ describe("bothUp()", () => { describe("onOffline", () => { it("tells the app MQTT is down", () => { + mockOnline = false; jest.resetAllMocks(); onOffline(); expect(dispatchNetworkDown).toHaveBeenCalledWith("user.mqtt", ANY_NUMBER); expect(error).toHaveBeenCalledWith( Content.MQTT_DISCONNECTED, { idPrefix: "offline" }); }); + + it("doesn't show toast", () => { + mockOnline = true; + jest.resetAllMocks(); + onOffline(); + expect(error).not.toHaveBeenCalled(); + }); }); describe("onOnline", () => { it("tells the app MQTT is up", () => { + mockOnline = false; jest.resetAllMocks(); onOnline(); expect(dispatchNetworkUp).toHaveBeenCalledWith("user.mqtt", ANY_NUMBER); expect(removeToast).toHaveBeenCalledWith("offline"); + expect(success).toHaveBeenCalled(); + }); + + it("doesn't show toast", () => { + mockOnline = true; + jest.resetAllMocks(); + onOnline(); + expect(success).not.toHaveBeenCalled(); }); }); describe("onReconnect()", () => { it("sends reconnect toast", () => { + mockOnline = false; onReconnect(); expect(warning).toHaveBeenCalledWith( "Attempting to reconnect to the message broker", { title: "Offline", color: "yellow", idPrefix: "offline" }); }); + + it("doesn't show toast", () => { + mockOnline = true; + onReconnect(); + expect(warning).not.toHaveBeenCalled(); + }); }); describe("changeLastClientConnected", () => { diff --git a/frontend/connectivity/connect_device.ts b/frontend/connectivity/connect_device.ts index 9345f2fa82..3c1af379f6 100644 --- a/frontend/connectivity/connect_device.ts +++ b/frontend/connectivity/connect_device.ts @@ -24,6 +24,7 @@ import { ChannelName, MessageType } from "../sequences/interfaces"; import { slowDown } from "./slow_down"; import { t } from "../i18next_wrapper"; import { now } from "../devices/connectivity/qos"; +import { forceOnline } from "../devices/must_be_online"; export const TITLE = () => t("New message from bot"); @@ -140,17 +141,24 @@ export const onMalformed = (dispatch: Function, getState: GetState) => () => { export const onOnline = () => { removeToast("offline"); - success(t("Reconnected to the message broker."), { title: t("Online") }); + if (!forceOnline()) { + success(t("Reconnected to the message broker."), { title: t("Online") }); + } dispatchNetworkUp("user.mqtt", now()); }; -export const onReconnect = () => - warning(t("Attempting to reconnect to the message broker"), - { title: t("Offline"), color: "yellow", idPrefix: "offline" }); +export const onReconnect = () => { + if (!forceOnline()) { + warning(t("Attempting to reconnect to the message broker"), + { title: t("Offline"), color: "yellow", idPrefix: "offline" }); + } +}; export const onOffline = () => { dispatchNetworkDown("user.mqtt", now()); - error(t(Content.MQTT_DISCONNECTED), { idPrefix: "offline" }); + if (!forceOnline()) { + error(t(Content.MQTT_DISCONNECTED), { idPrefix: "offline" }); + } }; export function onPublicBroadcast(payl: unknown) { diff --git a/frontend/constants.ts b/frontend/constants.ts index e81a111ea5..df9aa59991 100644 --- a/frontend/constants.ts +++ b/frontend/constants.ts @@ -1226,12 +1226,6 @@ export namespace Content { export const ENTER_CROP_SEARCH_TERM = trim(`Search for a crop to add to your garden.`); - export const CROP_NOT_FOUND_INTRO = - trim(`Would you like to`); - - export const CROP_NOT_FOUND_LINK = - trim(`add this crop on OpenFarm?`); - export const NO_TOOLS = trim(`Press + to add a new tool or seed container`); @@ -2478,6 +2472,7 @@ export enum Actions { SET_CROP_HEIGHT_CURVE_ID = "SET_CROP_HEIGHT_CURVE_ID", SET_CROP_STAGE = "SET_CROP_STAGE", SET_CROP_PLANTED_AT = "SET_CROP_PLANTED_AT", + SET_CROP_RADIUS = "SET_CROP_RADIUS", // 3D SET_DISTANCE_INDICATOR = "SET_DISTANCE_INDICATOR", diff --git a/frontend/crops/__tests__/find_test.ts b/frontend/crops/__tests__/find_test.ts new file mode 100644 index 0000000000..0e7f4a4103 --- /dev/null +++ b/frontend/crops/__tests__/find_test.ts @@ -0,0 +1,49 @@ +import { FAKE_CROPS } from "../../__test_support__/fake_crops"; +jest.mock("../constants", () => ({ + CROPS: FAKE_CROPS, +})); + +import { findCrop, findCrops, findIcon, findImage } from "../find"; + +describe("findCrop()", () => { + it("finds crop", () => { + const result = findCrop("mint"); + expect(result.name).toEqual("Mint"); + }); + + it("finds custom crop", () => { + const result = findCrop("foo-bar"); + expect(result.name).toEqual("Foo Bar"); + }); +}); + +describe("findCrops()", () => { + it("finds crops", () => { + const result = findCrops("mint"); + expect(Object.keys(result)).toEqual(["mint"]); + }); + + it("finds custom crop", () => { + const result = findCrops("foo-bar"); + expect(Object.keys(result)).toEqual(["foo-bar"]); + }); +}); + +describe("findIcon()", () => { + it("finds crop icon", () => { + const result = findIcon("mint"); + expect(result).toEqual("/crops/icons/mint.avif"); + }); + + it("returns fallback icon", () => { + const result = findIcon("foo-bar"); + expect(result).toEqual("/crops/icons/generic-plant.avif"); + }); +}); + +describe("findImage()", () => { + it("finds crop image", () => { + const result = findImage("mint"); + expect(result).toEqual("/crops/images/mint.jpg"); + }); +}); diff --git a/frontend/crops/aliases.json b/frontend/crops/aliases.json new file mode 100644 index 0000000000..c2186d07fe --- /dev/null +++ b/frontend/crops/aliases.json @@ -0,0 +1,125 @@ +{ + "looseleaf-lettuce": [ + "lettuce", + "lambs-lettuce", + "salad-bowl-lettuce", + "black-seeded-simpson-lettuce", + "red-butterhead-lettuce", + "lettuce-igloo", + "lettuce-freedom-mix", + "lechuga-crespa", + "celtuce", + "miners-lettuce-1" + ], + "white-onion": [ + "onion", + "onion-1", + "onion-2", + "onion-3" + ], + "spring-onion": [ + "onion-white-lisbon" + ], + "cherry-belle-radish": [ + "radish", + "wild-radish" + ], + "green-cabbage": [ + "cabbage", + "cabbage-1", + "chinese-cabbage" + ], + "beet": [ + "beet-detroit-dark-red-medium-top", + "beets" + ], + "blue-lake-bean": [ + "green-bean" + ], + "swiss-chard": [ + "swiss-chard-3", + "fordhook-giant-swiss-chard" + ], + "carrot": [ + "carrot-scarlet-nantes", + "rainbow-carrot" + ], + "red-russian-kale": [ + "kale", + "kale-1" + ], + "onion-chive": [ + "chives" + ], + "cucumber": [ + "cucumber-1", + "wild-cucumber" + ], + "snap-pea": [ + "pea" + ], + "basil": [ + "sweet-basil", + "thai-basil" + ], + "russet-potato": [ + "potato" + ], + "parsley": [ + "curled-parsley" + ], + "cayenne-pepper": [ + "chili-pepper" + ], + "garlic": [ + "garlic-1", + "hardneck-garlic", + "elephant-garlic" + ], + "jalapeno": [ + "jalapeno-pepper" + ], + "purple-potato": [ + "blue-potato" + ], + "red-bell-pepper": [ + "bell-pepper" + ], + "icicle-radish": [ + "daikon" + ], + "hemp": [ + "hemp-1" + ], + "oregano": [ + "italian-oregano", + "greek-oregano" + ], + "zucchini": [ + "green-zucchini" + ], + "dill": [ + "dill-1" + ], + "tomato": [ + "tiny-tim-tomato" + ], + "cherry-tomato": [ + "cherry-tomato-husky-red" + ], + "watercress": [ + "cress-1" + ], + "shallot": [ + "shallot-1" + ], + "bok-choy": [ + "pak-choy-bok-choy" + ], + "kohlrabi": [ + "kohlrabi-1" + ], + "asparagus": [ + "asparagus-1" + ] +} diff --git a/frontend/crops/constants.ts b/frontend/crops/constants.ts new file mode 100644 index 0000000000..623d20cbb8 --- /dev/null +++ b/frontend/crops/constants.ts @@ -0,0 +1,522 @@ +import acorn_squash from "./data/acorn-squash.json"; +import adjuma_pepper from "./data/adjuma-pepper.json"; +import aji_dulce_pepper from "./data/aji-dulce-pepper.json"; +import aleppo_pepper from "./data/aleppo-pepper.json"; +import almond from "./data/almond.json"; +import anaheim_pepper from "./data/anaheim-pepper.json"; +import apple from "./data/apple.json"; +import apricot from "./data/apricot.json"; +import artichoke from "./data/artichoke.json"; +import arugula from "./data/arugula.json"; +import asian_pear from "./data/asian-pear.json"; +import asparagus from "./data/asparagus.json"; +import banana_pepper from "./data/banana-pepper.json"; +import barley from "./data/barley.json"; +import basil from "./data/basil.json"; +import beet from "./data/beet.json"; +import belgian_endive from "./data/belgian-endive.json"; +import bibb_lettuce from "./data/bibb-lettuce.json"; +import bing_cherry from "./data/bing-cherry.json"; +import birds_eye_chili from "./data/birds-eye-chili.json"; +import bishops_crown_pepper from "./data/bishops-crown-pepper.json"; +import black_habanero from "./data/black-habanero.json"; +import black_prince_tomato from "./data/black-prince-tomato.json"; +import black_salsify from "./data/black-salsify.json"; +import blackberry from "./data/blackberry.json"; +import blue_lake_bean from "./data/blue-lake-bean.json"; +import blue_oyster_mushroom from "./data/blue-oyster-mushroom.json"; +import blueberry from "./data/blueberry.json"; +import bok_choy from "./data/bok-choy.json"; +import borage from "./data/borage.json"; +import brazilian_starfish_pepper from "./data/brazilian-starfish-pepper.json"; +import broccoli from "./data/broccoli.json"; +import brussels_sprout from "./data/brussels-sprout.json"; +import butternut_squash from "./data/butternut-squash.json"; +import california_poppy from "./data/california-poppy.json"; +import cannabis from "./data/cannabis.json"; +import cantaloupe from "./data/cantaloupe.json"; +import carrot from "./data/carrot.json"; +import cascabel_chili from "./data/cascabel-chili.json"; +import cauliflower from "./data/cauliflower.json"; +import cayenne_pepper from "./data/cayenne-pepper.json"; +import celeriac_root from "./data/celeriac-root.json"; +import celery from "./data/celery.json"; +import chamomile from "./data/chamomile.json"; +import chantrelle_mushroom from "./data/chantrelle-mushroom.json"; +import cheongyang_pepper from "./data/cheongyang-pepper.json"; +import cherokee_purple_tomato from "./data/cherokee-purple-tomato.json"; +import cherry_belle_radish from "./data/cherry-belle-radish.json"; +import cherry_stuffer_pepper from "./data/cherry-stuffer-pepper.json"; +import cherry_tomato from "./data/cherry-tomato.json"; +import chile_de_aborol_pepper from "./data/chile-de-aborol-pepper.json"; +import cilantro from "./data/cilantro.json"; +import cinnamon_cap_mushroom from "./data/cinnamon-cap-mushroom.json"; +import claytonia from "./data/claytonia.json"; +import collard_greens from "./data/collard-greens.json"; +import corn from "./data/corn.json"; +import cotton from "./data/cotton.json"; +import cremini_mushroom from "./data/cremini-mushroom.json"; +import cubanelle_pepper from "./data/cubanelle-pepper.json"; +import cucumber from "./data/cucumber.json"; +import curly_endive from "./data/curly-endive.json"; +import curly_kale from "./data/curly-kale.json"; +import dandelion from "./data/dandelion.json"; +import dark_opal_basil from "./data/dark-opal-basil.json"; +import datil_pepper from "./data/datil-pepper.json"; +import delicata_squash from "./data/delicata-squash.json"; +import diablito_pepper from "./data/diablito-pepper.json"; +import dill from "./data/dill.json"; +import dundicut_pepper from "./data/dundicut-pepper.json"; +import echinacea from "./data/echinacea.json"; +import eggplant from "./data/eggplant.json"; +import facing_heaven_pepper from "./data/facing-heaven-pepper.json"; +import fatilii_pepper from "./data/fatilii-pepper.json"; +import fava_bean from "./data/fava-bean.json"; +import fennel from "./data/fennel.json"; +import flower from "./data/flower.json"; +import french_breakfast_radish from "./data/french-breakfast-radish.json"; +import fresno_pepper from "./data/fresno-pepper.json"; +import fuji_apple from "./data/fuji-apple.json"; +import gala_apple from "./data/gala-apple.json"; +import galangal from "./data/galangal.json"; +import garlic_chives from "./data/garlic-chives.json"; +import garlic from "./data/garlic.json"; +import generic_plant from "./data/generic-plant.json"; +import german_chamomile from "./data/german-chamomile.json"; +import ghost_pepper from "./data/ghost-pepper.json"; +import ginger from "./data/ginger.json"; +import golden_beet from "./data/golden-beet.json"; +import golden_delicious_apple from "./data/golden-delicious-apple.json"; +import granny_smith_apple from "./data/granny-smith-apple.json"; +import green_anjou_pear from "./data/green-anjou-pear.json"; +import green_bartlett_pear from "./data/green-bartlett-pear.json"; +import green_bell_pepper from "./data/green-bell-pepper.json"; +import green_birds_eye_chili from "./data/green-birds-eye-chili.json"; +import green_cabbage from "./data/green-cabbage.json"; +import green_onion from "./data/green-onion.json"; +import green_serrano_pepper from "./data/green-serrano-pepper.json"; +import green_tabasco_pepper from "./data/green-tabasco-pepper.json"; +import green_zebra_tomato from "./data/green-zebra-tomato.json"; +import guajillo_pepper from "./data/guajillo-pepper.json"; +import habanero_pepper from "./data/habanero-pepper.json"; +import hemp from "./data/hemp.json"; +import hillbilly_tomato from "./data/hillbilly-tomato.json"; +import honeydew_melon from "./data/honeydew-melon.json"; +import hop from "./data/hop.json"; +import hungarian_wax_pepper from "./data/hungarian-wax-pepper.json"; +import iceberg_lettuce from "./data/iceberg-lettuce.json"; +import icicle_radish from "./data/icicle-radish.json"; +import indigo from "./data/indigo.json"; +import jalapeno from "./data/jalapeno.json"; +import japanese_yam from "./data/japanese-yam.json"; +import kabocha_squash from "./data/kabocha-squash.json"; +import kohlrabi from "./data/kohlrabi.json"; +import lacinato_kale from "./data/lacinato-kale.json"; +import lavender from "./data/lavender.json"; +import leek from "./data/leek.json"; +import lemon_balm from "./data/lemon-balm.json"; +import lemon_drop_pepper from "./data/lemon-drop-pepper.json"; +import lemon_verbena from "./data/lemon-verbena.json"; +import lemongrass from "./data/lemongrass.json"; +import lima_bean from "./data/lima-bean.json"; +import lions_mane_mushroom from "./data/lions-mane-mushroom.json"; +import looseleaf_lettuce from "./data/looseleaf-lettuce.json"; +import lovage from "./data/lovage.json"; +import madame_jeanette_pepper from "./data/madame-jeanette-pepper.json"; +import malagueta_pepper from "./data/malagueta-pepper.json"; +import marjoram from "./data/marjoram.json"; +import marshmallow from "./data/marshmallow.json"; +import medusa_pepper from "./data/medusa-pepper.json"; +import melon from "./data/melon.json"; +import millet from "./data/millet.json"; +import mint from "./data/mint.json"; +import money_tree from "./data/money-tree.json"; +import mugwort from "./data/mugwort.json"; +import mushroom from "./data/mushroom.json"; +import napa_cabbage from "./data/napa-cabbage.json"; +import nectarine from "./data/nectarine.json"; +import oat from "./data/oat.json"; +import okra from "./data/okra.json"; +import onion_chive from "./data/onion-chive.json"; +import orange_bell_pepper from "./data/orange-bell-pepper.json"; +import orange_scotch_bonnet from "./data/orange-scotch-bonnet.json"; +import oregano from "./data/oregano.json"; +import ornamental_gourd from "./data/ornamental-gourd.json"; +import parsley from "./data/parsley.json"; +import parsnip from "./data/parsnip.json"; +import pattypan_squash from "./data/pattypan-squash.json"; +import peach from "./data/peach.json"; +import peanut from "./data/peanut.json"; +import peppadew_pepper from "./data/peppadew-pepper.json"; +import pequin_pepper from "./data/pequin-pepper.json"; +import peter_pepper from "./data/peter-pepper.json"; +import pimento_pepper from "./data/pimento-pepper.json"; +import pineapple_tomato from "./data/pineapple-tomato.json"; +import pink_oyster_mushroom from "./data/pink-oyster-mushroom.json"; +import plantain from "./data/plantain.json"; +import plum from "./data/plum.json"; +import poblano_pepper from "./data/poblano-pepper.json"; +import pointed_cabbage from "./data/pointed-cabbage.json"; +import porcini_mushroom from "./data/porcini-mushroom.json"; +import portabello_mushroom from "./data/portabello-mushroom.json"; +import psilocybin_mushroom from "./data/psilocybin-mushroom.json"; +import pumpkin from "./data/pumpkin.json"; +import purple_carrot from "./data/purple-carrot.json"; +import purple_cauliflower from "./data/purple-cauliflower.json"; +import purple_pod_bean from "./data/purple-pod-bean.json"; +import purple_potato from "./data/purple-potato.json"; +import radicchio from "./data/radicchio.json"; +import rainbow_chard from "./data/rainbow-chard.json"; +import rainier_cherry from "./data/rainier-cherry.json"; +import raspberry from "./data/raspberry.json"; +import red_anjou_pear from "./data/red-anjou-pear.json"; +import red_bartlett_pear from "./data/red-bartlett-pear.json"; +import red_bell_pepper from "./data/red-bell-pepper.json"; +import red_cabbage from "./data/red-cabbage.json"; +import red_carrot from "./data/red-carrot.json"; +import red_chard from "./data/red-chard.json"; +import red_curly_kale from "./data/red-curly-kale.json"; +import red_giant_mustard from "./data/red-giant-mustard.json"; +import red_gold_potato from "./data/red-gold-potato.json"; +import red_kuri_squash from "./data/red-kuri-squash.json"; +import red_onion from "./data/red-onion.json"; +import red_pointed_cabbage from "./data/red-pointed-cabbage.json"; +import red_russian_kale from "./data/red-russian-kale.json"; +import red_savina_pepper from "./data/red-savina-pepper.json"; +import red_scotch_bonnet from "./data/red-scotch-bonnet.json"; +import red_serrano_pepper from "./data/red-serrano-pepper.json"; +import red_tabasco_pepper from "./data/red-tabasco-pepper.json"; +import rice from "./data/rice.json"; +import rocoto_pepper from "./data/rocoto-pepper.json"; +import romaine_lettuce from "./data/romaine-lettuce.json"; +import rosemary from "./data/rosemary.json"; +import runner_bean from "./data/runner-bean.json"; +import russet_potato from "./data/russet-potato.json"; +import rutabaga from "./data/rutabaga.json"; +import rye from "./data/rye.json"; +import sage from "./data/sage.json"; +import salsify from "./data/salsify.json"; +import santa_fe_grande_pepper from "./data/santa-fe-grande-pepper.json"; +import savoy_cabbage from "./data/savoy-cabbage.json"; +import shallot from "./data/shallot.json"; +import shiitake_mushroom from "./data/shiitake-mushroom.json"; +import shishito_pepper from "./data/shishito-pepper.json"; +import siling_labuyo_pepper from "./data/siling-labuyo-pepper.json"; +import snap_pea from "./data/snap-pea.json"; +import soybean from "./data/soybean.json"; +import spaghetti_squash from "./data/spaghetti-squash.json"; +import spinach from "./data/spinach.json"; +import spring_onion from "./data/spring-onion.json"; +import st_johns_wort from "./data/st-johns-wort.json"; +import stevia from "./data/stevia.json"; +import strawberry from "./data/strawberry.json"; +import striped_cavern_tomato from "./data/striped-cavern-tomato.json"; +import sugarcane from "./data/sugarcane.json"; +import sunflower from "./data/sunflower.json"; +import sunray_tomato from "./data/sunray-tomato.json"; +import sweet_italian_pepper from "./data/sweet-italian-pepper.json"; +import sweet_potato from "./data/sweet-potato.json"; +import swiss_chard from "./data/swiss-chard.json"; +import taro from "./data/taro.json"; +import thyme from "./data/thyme.json"; +import tomatillo from "./data/tomatillo.json"; +import tomato from "./data/tomato.json"; +import trinidad_moruga_scorpion_pepper from "./data/trinidad-moruga-scorpion-pepper.json"; +import trinidad_scorpion_butch_t_pepper from "./data/trinidad-scorpion-butch-t-pepper.json"; +import trumpet_mushroom from "./data/trumpet-mushroom.json"; +import tulsi from "./data/tulsi.json"; +import turmeric from "./data/turmeric.json"; +import turnip from "./data/turnip.json"; +import watercress from "./data/watercress.json"; +import watermelon_radish from "./data/watermelon-radish.json"; +import watermelon from "./data/watermelon.json"; +import wax_bean from "./data/wax-bean.json"; +import wheat from "./data/wheat.json"; +import white_button_mushroom from "./data/white-button-mushroom.json"; +import white_carrot from "./data/white-carrot.json"; +import white_onion from "./data/white-onion.json"; +import white_oyster_mushroom from "./data/white-oyster-mushroom.json"; +import wormwood from "./data/wormwood.json"; +import yellow_bell_pepper from "./data/yellow-bell-pepper.json"; +import yellow_carrot from "./data/yellow-carrot.json"; +import yellow_cauliflower from "./data/yellow-cauliflower.json"; +import yellow_onion from "./data/yellow-onion.json"; +import yellow_oyster_mushroom from "./data/yellow-oyster-mushroom.json"; +import yellow_scotch_bonnet from "./data/yellow-scotch-bonnet.json"; +import yellow_squash from "./data/yellow-squash.json"; +import yukon_gold_potato from "./data/yukon-gold-potato.json"; +import zucchini from "./data/zucchini.json"; + +import { Crops } from "./interfaces"; +import { kebabCase } from "lodash"; +import aliases from "./aliases.json"; + +const SNAKE_CASE_CROPS: Crops = { + acorn_squash, + adjuma_pepper, + aji_dulce_pepper, + aleppo_pepper, + almond, + anaheim_pepper, + apple, + apricot, + artichoke, + arugula, + asian_pear, + asparagus, + banana_pepper, + barley, + basil, + beet, + belgian_endive, + bibb_lettuce, + bing_cherry, + birds_eye_chili, + bishops_crown_pepper, + black_habanero, + black_prince_tomato, + black_salsify, + blackberry, + blue_lake_bean, + blue_oyster_mushroom, + blueberry, + bok_choy, + borage, + brazilian_starfish_pepper, + broccoli, + brussels_sprout, + butternut_squash, + california_poppy, + cannabis, + cantaloupe, + carrot, + cascabel_chili, + cauliflower, + cayenne_pepper, + celeriac_root, + celery, + chamomile, + chantrelle_mushroom, + cheongyang_pepper, + cherokee_purple_tomato, + cherry_belle_radish, + cherry_stuffer_pepper, + cherry_tomato, + chile_de_aborol_pepper, + cilantro, + cinnamon_cap_mushroom, + claytonia, + collard_greens, + corn, + cotton, + cremini_mushroom, + cubanelle_pepper, + cucumber, + curly_endive, + curly_kale, + dandelion, + dark_opal_basil, + datil_pepper, + delicata_squash, + diablito_pepper, + dill, + dundicut_pepper, + echinacea, + eggplant, + facing_heaven_pepper, + fatilii_pepper, + fava_bean, + fennel, + flower, + french_breakfast_radish, + fresno_pepper, + fuji_apple, + gala_apple, + galangal, + garlic_chives, + garlic, + generic_plant, + german_chamomile, + ghost_pepper, + ginger, + golden_beet, + golden_delicious_apple, + granny_smith_apple, + green_anjou_pear, + green_bartlett_pear, + green_bell_pepper, + green_birds_eye_chili, + green_cabbage, + green_onion, + green_serrano_pepper, + green_tabasco_pepper, + green_zebra_tomato, + guajillo_pepper, + habanero_pepper, + hemp, + hillbilly_tomato, + honeydew_melon, + hop, + hungarian_wax_pepper, + iceberg_lettuce, + icicle_radish, + indigo, + jalapeno, + japanese_yam, + kabocha_squash, + kohlrabi, + lacinato_kale, + lavender, + leek, + lemon_balm, + lemon_drop_pepper, + lemon_verbena, + lemongrass, + lima_bean, + lions_mane_mushroom, + looseleaf_lettuce, + lovage, + madame_jeanette_pepper, + malagueta_pepper, + marjoram, + marshmallow, + medusa_pepper, + melon, + millet, + mint, + money_tree, + mugwort, + mushroom, + napa_cabbage, + nectarine, + oat, + okra, + onion_chive, + orange_bell_pepper, + orange_scotch_bonnet, + oregano, + ornamental_gourd, + parsley, + parsnip, + pattypan_squash, + peach, + peanut, + peppadew_pepper, + pequin_pepper, + peter_pepper, + pimento_pepper, + pineapple_tomato, + pink_oyster_mushroom, + plantain, + plum, + poblano_pepper, + pointed_cabbage, + porcini_mushroom, + portabello_mushroom, + psilocybin_mushroom, + pumpkin, + purple_carrot, + purple_cauliflower, + purple_pod_bean, + purple_potato, + radicchio, + rainbow_chard, + rainier_cherry, + raspberry, + red_anjou_pear, + red_bartlett_pear, + red_bell_pepper, + red_cabbage, + red_carrot, + red_chard, + red_curly_kale, + red_giant_mustard, + red_gold_potato, + red_kuri_squash, + red_onion, + red_pointed_cabbage, + red_russian_kale, + red_savina_pepper, + red_scotch_bonnet, + red_serrano_pepper, + red_tabasco_pepper, + rice, + rocoto_pepper, + romaine_lettuce, + rosemary, + runner_bean, + russet_potato, + rutabaga, + rye, + sage, + salsify, + santa_fe_grande_pepper, + savoy_cabbage, + shallot, + shiitake_mushroom, + shishito_pepper, + siling_labuyo_pepper, + snap_pea, + soybean, + spaghetti_squash, + spinach, + spring_onion, + st_johns_wort, + stevia, + strawberry, + striped_cavern_tomato, + sugarcane, + sunflower, + sunray_tomato, + sweet_italian_pepper, + sweet_potato, + swiss_chard, + taro, + thyme, + tomatillo, + tomato, + trinidad_moruga_scorpion_pepper, + trinidad_scorpion_butch_t_pepper, + trumpet_mushroom, + tulsi, + turmeric, + turnip, + watercress, + watermelon_radish, + watermelon, + wax_bean, + wheat, + white_button_mushroom, + white_carrot, + white_onion, + white_oyster_mushroom, + wormwood, + yellow_bell_pepper, + yellow_carrot, + yellow_cauliflower, + yellow_onion, + yellow_oyster_mushroom, + yellow_scotch_bonnet, + yellow_squash, + yukon_gold_potato, + zucchini, +}; + +export const CROPS: Crops = Object.entries(SNAKE_CASE_CROPS) + .reduce((crops, [slug, crop]) => { + crops[kebabCase(slug)] = crop; + return crops; + }, {} as Crops); +export const SLUGS: string[] = Object.keys(CROPS).map(slug => kebabCase(slug)); +export const ICON_URLS: string[] = Object.values(CROPS).map(crop => crop.icon); + +export const ALIASED_SLUG_LOOKUP: Record = Object.entries(aliases) + .reduce(( + slug_lookup: Record, + [slug, slug_aliases]: [string, string[]], + ) => { + slug_aliases.map(slug_alias => { + slug_lookup[slug_alias] = slug; + }); + return slug_lookup; + }, {} as Record); diff --git a/frontend/crops/data/acorn-squash.json b/frontend/crops/data/acorn-squash.json new file mode 100644 index 0000000000..b874f3e9a2 --- /dev/null +++ b/frontend/crops/data/acorn-squash.json @@ -0,0 +1,18 @@ +{ + "name": "Acorn Squash", + "binomial_name": "Curcurbita pepo", + "common_names": [], + "description": "Dark green and/or orange to yellow patterned rind with longitudinal ridges. Acorn-shaped, generally 4-7 inches long. Sweet, yellow-orange flesh inside. Belongs to the same species as summer squashes like zucchini and crookneck squash.", + "sun_requirements": "Full Sun", + "sowing_method": "seeds", + "spread": 150, + "row_spacing": 60, + "height": 30, + "growing_degree_days": 0, + "companions": [ + "borage", + "corn" + ], + "image": "/crops/images/acorn-squash.jpg", + "icon": "/crops/icons/acorn-squash.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/adjuma-pepper.json b/frontend/crops/data/adjuma-pepper.json new file mode 100644 index 0000000000..e3a67a7f7c --- /dev/null +++ b/frontend/crops/data/adjuma-pepper.json @@ -0,0 +1,19 @@ +{ + "name": "Adjuma Pepper", + "binomial_name": "Capsicum chinense", + "common_names": [ + "Adjoema", + "aji umba", + "ojemma" + ], + "description": "Adjuma, Aji Umba, or Adjoema peppers are a variety of Capsicum chinense chili pepper originally from Suriname. The peppers are similar in heat, aroma, and flavor to Habanero, but have a boxier shape. The peppers ripen from lime-green to red or yellow on the plant. They are often mistaken for Habanero or Scotch Bonnets.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, harden off seedlings before transplanting outside", + "spread": 40, + "row_spacing": 45, + "height": 75, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/adjuma-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/aji-dulce-pepper.json b/frontend/crops/data/aji-dulce-pepper.json new file mode 100644 index 0000000000..ed9ba2dd2e --- /dev/null +++ b/frontend/crops/data/aji-dulce-pepper.json @@ -0,0 +1,19 @@ +{ + "name": "Aj\u00ed Dulce Pepper", + "binomial_name": "Capsicum chinense", + "common_names": [ + "ajicito", + "aji dulce", + "sweet chili" + ], + "description": "Aj\u00ed dulce (or sweet chili) peppers are a specific Venezuelan variety of Capsicum chinense peppers that are related to the habanero but have a much milder, smoky flavor. Their 5cm long fruits start green and turn red as they ripen. The plants are small and compact well-suited to container gardening.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off.", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/aji-dulce-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/aleppo-pepper.json b/frontend/crops/data/aleppo-pepper.json new file mode 100644 index 0000000000..3d764e7609 --- /dev/null +++ b/frontend/crops/data/aleppo-pepper.json @@ -0,0 +1,19 @@ +{ + "name": "Aleppo Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "Aleppo Pepper", + "Halaby Pepper", + "pul biber" + ], + "description": "Aleppo, or Halaby, pepper is a variety of Capsicum annuum used as a spice in Middle Eastern and Mediterranean cuisine. The plant's pods are semi-dried, de-seeded, and then coarsely ground. The resulting spice is slightly oilier and milder than red pepper flakes and has notes of raisin.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off.", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/aleppo-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/almond.json b/frontend/crops/data/almond.json new file mode 100644 index 0000000000..f0bfad52a6 --- /dev/null +++ b/frontend/crops/data/almond.json @@ -0,0 +1,17 @@ +{ + "name": "Almond", + "binomial_name": "Prunus dulcis", + "common_names": [ + "almond" + ], + "description": "The almond is a deciduous tree that grows well in Mediterranean climates. It has white to pale flowers that appear before the leaves in early spring. Trees begin producing fruit within 2 to 4 years of being transplanted.", + "sun_requirements": "Full Sun", + "sowing_method": "Bare root tree", + "spread": 0, + "row_spacing": 460, + "height": 400, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/almond.jpg", + "icon": "/crops/icons/almond.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/anaheim-pepper.json b/frontend/crops/data/anaheim-pepper.json new file mode 100644 index 0000000000..f9eceab58c --- /dev/null +++ b/frontend/crops/data/anaheim-pepper.json @@ -0,0 +1,22 @@ +{ + "name": "Anaheim Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "anaheim pepper", + "California chile", + "Magdalena pepper", + "chile seco del norte" + ], + "description": "Anaheim peppers are a mild variety of the New Mexico chile pepper cultivar No. 9. When dried, they are known as \"chile seco del norte\".", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off.", + "spread": 40, + "row_spacing": 40, + "height": 50, + "growing_degree_days": 0, + "companions": [ + "tomato" + ], + "image": "/crops/images/anaheim-pepper.jpg", + "icon": "/crops/icons/anaheim-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/apple.json b/frontend/crops/data/apple.json new file mode 100644 index 0000000000..8f3caf30b1 --- /dev/null +++ b/frontend/crops/data/apple.json @@ -0,0 +1,15 @@ +{ + "name": "Apple", + "binomial_name": "Malus pumila", + "common_names": [], + "description": "The apple is a deciduous tree in the Rose family grown for it's sweet fruit. The apple originated in Central Asia and has spread across the world. There are now over 7,500 cultivars bred for a variety of climates and characteristics. Apples are propagated through grafting because seeds do not breed true.", + "sun_requirements": "Full Sun", + "sowing_method": "Bare root tree", + "spread": 500, + "row_spacing": 0, + "height": 300, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/apple.jpg", + "icon": "/crops/icons/apple.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/apricot.json b/frontend/crops/data/apricot.json new file mode 100644 index 0000000000..dcd0a5af60 --- /dev/null +++ b/frontend/crops/data/apricot.json @@ -0,0 +1,17 @@ +{ + "name": "Apricot", + "binomial_name": "Prunus armeniaca", + "common_names": [ + "apricot" + ], + "description": "The apricot is a small fruiting tree in the genus Prunus (of which other stone fruits like peaches, plums and cherries are also members). It is a section of the Prunus (Plum) subgenus. The apricot tree has a dense, spreading canopy with white to pinkish flowers. It's fruit are yellow to orange, smaller than peaches, and have smooth or velvety skin. The apricot can tolerate winter temperatures down to \u221230 \u00b0C, making it slightly more cold-hardy than peach trees. However, it is susceptible to spring frosts killing the blooms because it tends to flower very early. Some varieties are self-pollinating, but all apricot trees benefit from a pollination partner with the same bloom time within 15 meters. Standard and dwarf rootstocks are available. Dwarf trees can grow to 3 meters, standard to 4.5 meters. Depending on the size chosen, the tree will bear fruit within 2-4 years of planting.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant bare-root plant", + "spread": 450, + "row_spacing": 450, + "height": 450, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/apricot.jpg", + "icon": "/crops/icons/apricot.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/artichoke.json b/frontend/crops/data/artichoke.json new file mode 100644 index 0000000000..cd5bf09159 --- /dev/null +++ b/frontend/crops/data/artichoke.json @@ -0,0 +1,18 @@ +{ + "name": "Artichoke", + "binomial_name": "Cynara cardunculus var. scolymus", + "common_names": [ + "Globe Artichoke", + "Annual Artichoke" + ], + "description": "The globe artichoke is a variety of a species of thistle cultivated as a food. The budding artichoke flower-head is the edible part of the plant. It is a cluster of many budding small flowers (known as an \"inflorescence\") and bracts on an edible base. Once the buds bloom the head becomes coarse and barely edible. Artichokes are perennials in Zone 7 and warmer. They normally produce edible flower-heads during their second year, but recent cultivars such as 'Imperial Star' have been bred to produce in the first year. Other cultivars, such as 'Northern Star', have been bred to overwinter in more northern climates. There are green and purple varieties of artichoke. They are often steamed, saut\u00e9ed or braised, but can also be eaten raw.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors. In warmer zones, propagate \"pups.\"", + "spread": 120, + "row_spacing": 90, + "height": 90, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/artichoke.jpg", + "icon": "/crops/icons/artichoke.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/arugula.json b/frontend/crops/data/arugula.json new file mode 100644 index 0000000000..a140fefdb2 --- /dev/null +++ b/frontend/crops/data/arugula.json @@ -0,0 +1,25 @@ +{ + "name": "Arugula", + "binomial_name": "Eruca sativa", + "common_names": [ + "Arugula", + "Rocket Salad", + "Rucola", + "Rucoli", + "Rugula", + "Colewort", + "Roquette", + "Garden Rocket", + "Eruca" + ], + "description": "Mildly bitter, sometimes peppery, salad green. Fast-growing with deeply lobed leaves. Prefers cooler weather, bolts in the heat.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed or transplant in wide rows or raised beds", + "spread": 20, + "row_spacing": 25, + "height": 30, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/arugula.jpg", + "icon": "/crops/icons/arugula.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/asian-pear.json b/frontend/crops/data/asian-pear.json new file mode 100644 index 0000000000..8d7c0a86da --- /dev/null +++ b/frontend/crops/data/asian-pear.json @@ -0,0 +1,25 @@ +{ + "name": "Asian Pear", + "binomial_name": "Pyrus pyrifolia", + "common_names": [ + "Asian pear", + "Chinese pear", + "Korean pear", + "Japanese pear", + "Taiwanese pear", + "sand pear", + "nashi pear" + ], + "description": "The Asian Pear tree is species of deciduous tree native to East Asia. It is also grown in India, Australia, and the United States. They can be divided into two main cultivar groups: 1) the Akanashi, or 'Russet pears,' group, which has yellowish-brown rinds, and 2) the Aonashi, or 'Green pears' group, which has yellow-green rinds. \n\nThe fruit ranges from 3-10cm in diameter and has a high water content and a crisp, grainy texture. It is often eaten raw. Unlike European pears, which can be harvested before fully ripened, Asian Pears must tree-ripen for optimum flavor and sweetness. \n\nThe trees produce white hermaphrodite flowers in the spring. Although partially self-pollinating, Asian Pears benefit from having a cultivar planted nearby. Trees produce fruit by their second or third year. Fruit should be thinned to 1 fruit per cluster or every 15cm.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant bare-root plant", + "spread": 600, + "row_spacing": 600, + "height": 750, + "growing_degree_days": 0, + "companions": [ + "borage" + ], + "image": "/crops/images/asian-pear.jpg", + "icon": "/crops/icons/asian-pear.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/asparagus.json b/frontend/crops/data/asparagus.json new file mode 100644 index 0000000000..d33624260a --- /dev/null +++ b/frontend/crops/data/asparagus.json @@ -0,0 +1,19 @@ +{ + "name": "Asparagus", + "binomial_name": "Asparagus officinalis", + "common_names": [ + "Asparagus", + "Sparrow Grass", + "Sperage" + ], + "description": "Perennial spring vegetable often sown from crowns. The vegetable has a mild flavor with earthy undertones. When mature and reproducing, the plant creates tall, stout stems with feathery foliage and small red berries.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant crowns outside, or start seeds indoors", + "spread": 42, + "row_spacing": 30, + "height": 140, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/asparagus.jpg", + "icon": "/crops/icons/asparagus.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/banana-pepper.json b/frontend/crops/data/banana-pepper.json new file mode 100644 index 0000000000..e10ae5b8fb --- /dev/null +++ b/frontend/crops/data/banana-pepper.json @@ -0,0 +1,18 @@ +{ + "name": "Banana Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "yellow wax pepper", + "banana chili" + ], + "description": "Banana peppers are Capsicum annuum cultivars that are curved like a banana and have a mild, tangy taste. They are often bright yellow, but can also be green, red, or orange. Mature, ripe peppers are sweeter than younger ones. Spicy varieties are called Hungarian wax peppers. Banana peppers are often confused with Pepperoncini, which are a separate cultivar of Capiscum annuum and Capsicum frutescens. Banana peppers can be eaten raw, stuffed, or pickled.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off.", + "spread": 30, + "row_spacing": 45, + "height": 60, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/banana-pepper.jpg", + "icon": "/crops/icons/banana-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/barley.json b/frontend/crops/data/barley.json new file mode 100644 index 0000000000..159a40c81e --- /dev/null +++ b/frontend/crops/data/barley.json @@ -0,0 +1,15 @@ +{ + "name": "Barley", + "binomial_name": "Hordeum vulgare", + "common_names": [], + "description": "Barley is a member of the grass family that resembles wheat, but with more compact seeds. It is a major cereal grain that can be pearled, malted, or used as animal feed. Barley is often grown as a cover crop, and can be sown in spring or winter, depending on climate and desired use.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed using 15cm spacing, or broadcast and rake in", + "spread": 0, + "row_spacing": 15, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/barley.jpg", + "icon": "/crops/icons/barley.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/basil.json b/frontend/crops/data/basil.json new file mode 100644 index 0000000000..e0835c685a --- /dev/null +++ b/frontend/crops/data/basil.json @@ -0,0 +1,18 @@ +{ + "name": "Basil", + "binomial_name": "Ocimum basilicum", + "common_names": [ + "Basil", + "Basilikum" + ], + "description": "", + "sun_requirements": "Full Sun", + "sowing_method": "", + "spread": 40, + "row_spacing": 30, + "height": 60, + "growing_degree_days": 650, + "companions": [], + "image": "/crops/images/basil.jpg", + "icon": "/crops/icons/basil.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/beet.json b/frontend/crops/data/beet.json new file mode 100644 index 0000000000..5dc77a133c --- /dev/null +++ b/frontend/crops/data/beet.json @@ -0,0 +1,17 @@ +{ + "name": "Beet", + "binomial_name": "Beta vulgaris", + "common_names": [ + "Beet" + ], + "description": "Extremely sweet, perfectly round beets, 8cm across, have deep red skin and dark red flesh.", + "sun_requirements": "Full Sun", + "sowing_method": "Sow in well-worked soil after danger of frost in spring. In frost-free areas, sow in fall. Sow thinly in rows 30cm apart and cover with 1.5cm of fine soil. Firm lightly and keep evenly moist. Seedlings emerge in 14-21 days. Thin to stand about 8cm apart when seedlings are 3cm to 5cm tall.", + "spread": 8, + "row_spacing": 30, + "height": 30, + "growing_degree_days": 59, + "companions": [], + "image": "/crops/images/beet.jpg", + "icon": "/crops/icons/beet.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/belgian-endive.json b/frontend/crops/data/belgian-endive.json new file mode 100644 index 0000000000..40edc05a1f --- /dev/null +++ b/frontend/crops/data/belgian-endive.json @@ -0,0 +1,19 @@ +{ + "name": "Belgian Endive", + "binomial_name": "Cichorium intybus", + "common_names": [ + "Endive", + "Belgium Endive", + "witloof chicory" + ], + "description": "Belgian Endive (not to be confused with Endive) is a chicory cultivar that is grown for its small head of tender, cream-colored, slightly bitter leaves. The plant is first grown outside, then the tops are cut and the thick roots are transplanted indoors where they are grown without any sunlight in a process known as \"forcing.\" The lack of sunlight prevents the new leaf growth from turning green or opening up. The paler the leaves, the less bitter the taste. Belgian Endive can be roasted, tossed in salads, or baked into various dishes. The harder inner part of the stem at the bottom of the head can be removed before cooking to reduce bitterness.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed, thin to 15cm apart", + "spread": 0, + "row_spacing": 15, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/belgian-endive.jpg", + "icon": "/crops/icons/belgian-endive.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/bibb-lettuce.json b/frontend/crops/data/bibb-lettuce.json new file mode 100644 index 0000000000..8599d140e5 --- /dev/null +++ b/frontend/crops/data/bibb-lettuce.json @@ -0,0 +1,23 @@ +{ + "name": "Bibb Lettuce", + "binomial_name": "Lactuca Sativa", + "common_names": [ + "Butterhead", + "Bibb", + "Boston lettuce", + "Kropsla", + "Laitue", + "Kopfsalat", + "Appia" + ], + "description": "Buttercrunch (also known as Butterhead, Bibb, and Boston lettuce) is a lettuce cultivar that produces tightly clustered leaves with a buttery flavor and smooth texture. Lettuce is a cool weather crop and high temperatures will impede germination and/or cause the plant to bolt (go to seed quickly). Some hybrid cultivars have been bred to be more heat-resistant.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed outdoors, thin to 20cm when seedlings are 3cm tall", + "spread": 30, + "row_spacing": 40, + "height": 30, + "growing_degree_days": 60, + "companions": [], + "image": "/crops/images/bibb-lettuce.jpg", + "icon": "/crops/icons/bibb-lettuce.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/bing-cherry.json b/frontend/crops/data/bing-cherry.json new file mode 100644 index 0000000000..44ddd8b763 --- /dev/null +++ b/frontend/crops/data/bing-cherry.json @@ -0,0 +1,19 @@ +{ + "name": "Bing Cherry", + "binomial_name": "Prunus avium 'Bing'", + "common_names": [ + "cherry" + ], + "description": "The Bing Cherry is a cultivar of the wild sweet cherry, Prunus avium, that produces large, heart-shaped, dark red fruit with firm, meaty, purplish-red flesh. The trees are available in dwarf and standard sizes. Standard trees bear fruit 5-6 years after planting, dwarf trees will bear fruit 1-3 years earlier. Plant 2-3 trees with the same bloom time for cross-pollination. Bing Cherries require a dry-summer climate for optimum harvest. Thinning is not necessary. Trees should be pruned in late winter.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant sapling", + "spread": 760, + "row_spacing": 1050, + "height": 1050, + "growing_degree_days": 0, + "companions": [ + "dill" + ], + "image": "/crops/images/bing-cherry.jpg", + "icon": "/crops/icons/bing-cherry.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/birds-eye-chili.json b/frontend/crops/data/birds-eye-chili.json new file mode 100644 index 0000000000..816f9145cf --- /dev/null +++ b/frontend/crops/data/birds-eye-chili.json @@ -0,0 +1,24 @@ +{ + "name": "Bird's Eye Chili", + "binomial_name": "Capsicum annuum var. glabriusculum", + "common_names": [ + "Bird's eye chili", + "bird eye chili", + "bird's chili", + "chile de arbol", + "Thai chili", + "chiltepin", + "piri piri", + "pili pili" + ], + "description": "Bird's Eye Chili, Chili de Arbol, or Piri Piri, is a Capsicum annuum cultivar grown in Ethiopia, Southeast Asia, and parts of India. It produces small, conical fruits that are much hotter than the japaleno: their Scoville rating is between 100,000\u2013225,000 units. It is used in Vietnamese and Thai cuisine. The seeds can be difficult to germinate because of their tough coating, which is designed to be passed through birds' digestive systems. For best results, soak the seeds in plain water for 6 hours before planting and keep the germination temperature between 70-90\u00baF.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off.", + "spread": 0, + "row_spacing": 0, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/birds-eye-chili.jpg", + "icon": "/crops/icons/birds-eye-chili.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/bishops-crown-pepper.json b/frontend/crops/data/bishops-crown-pepper.json new file mode 100644 index 0000000000..8f0f5cd561 --- /dev/null +++ b/frontend/crops/data/bishops-crown-pepper.json @@ -0,0 +1,26 @@ +{ + "name": "Bishop's Crown Pepper", + "binomial_name": "Capsicum baccatum var. pendulum", + "common_names": [ + "bishop's crown", + "Christmas bell", + "peri peri", + "joker's hatAji flor", + "Balloon pepper", + "Campane", + "Orchid", + "Pimenta cambuci", + "Ubatuba cambuci", + "Atomic Starfish" + ], + "description": "Bishop's Crown, or Peri Peri, is a Capsicum baccatum cultivar that produces peppers shaped like flying saucers or bishop's hats. Each plant produces 30-50 fruits that grow to about 3.5cm wide. The peppers have thin, crisp flesh and can be sweet and mild or much spicier than jalapenos. They mature from pale green to red.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off.", + "spread": 0, + "row_spacing": 60, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/bishops-crown-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/black-habanero.json b/frontend/crops/data/black-habanero.json new file mode 100644 index 0000000000..443b7971d0 --- /dev/null +++ b/frontend/crops/data/black-habanero.json @@ -0,0 +1,20 @@ +{ + "name": "Black Habanero", + "binomial_name": "Capsicum chinense", + "common_names": [ + "habanero negro" + ], + "description": "Black Habaneros are a habanero cultivar that produces brown to black peppers that rate between 400,000-450,000 Scoville units (making them hotter than regular habaneros). The plant grows well in containers or directly in soil, but takes longer to grow than other habanero varieties. The fruits are green when young and turn brown to black as they mature.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, harden off seedlings before transplanting outside", + "spread": 40, + "row_spacing": 45, + "height": 75, + "growing_degree_days": 0, + "companions": [ + "spinach", + "tomato" + ], + "image": "", + "icon": "/crops/icons/black-habanero.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/black-prince-tomato.json b/frontend/crops/data/black-prince-tomato.json new file mode 100644 index 0000000000..632f937167 --- /dev/null +++ b/frontend/crops/data/black-prince-tomato.json @@ -0,0 +1,18 @@ +{ + "name": "Black Prince Tomato", + "binomial_name": "Solanum lycopersicum", + "common_names": [ + "black prince", + "Czerno Prinz" + ], + "description": "Black Prince tomatoes are an heirloom tomato cultivar from Siberia. They are well suited for cooler climates with shorter growing seasons. They produce garnet to mahogany brown fruits that can be round, plum, or heart-shaped and about 7cm in diameter. The fruit has a deep, sweet, rich flavor. Stake/trellis/cage and prune plants for best results. Fruit can be eaten raw or cooked into sauce.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant outside after last frost", + "spread": 0, + "row_spacing": 90, + "height": 150, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/black-prince-tomato.jpg", + "icon": "/crops/icons/black-prince-tomato.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/black-salsify.json b/frontend/crops/data/black-salsify.json new file mode 100644 index 0000000000..d32a06e5e8 --- /dev/null +++ b/frontend/crops/data/black-salsify.json @@ -0,0 +1,24 @@ +{ + "name": "Black Salsify", + "binomial_name": "Scorzonera hispanica", + "common_names": [ + "Spanish salsify", + "black oyster plant", + "serpent root", + "viper's herb", + "viper's grass", + "scorzonera" + ], + "description": "Black Salsify is a perennial member of the genus Scorzonera in the Asteraceae family that is grown as an annual. It is native to Southern Europe and the Near East. It is similar to Salsify, but they are in different genera. Both plants are grown for their edible roots and cultivated like carrots and parsnips. Black Salsify's leaves look like a clump of coarse grass with yellow ray flowers. Greens can be used in salads. The thin taproot grows up to 1 meter long and 2cm in diameter. It has a striking black skin that is inedible and removed after boiling. The white flesh tastes like a cross between oysters and asparagus. Roots can be cooked into soup, mashed, or roasted and used as a coffee substitute. Black Salsify is a cool weather crop - it's roots become stringy at temperatures above 29C. Harvest when roots are 30cm long, taking care not to break the roots. Roots can be left in the ground until a hard frost, and then stored in a root cellar through the winter.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, thin to 15cm", + "spread": 0, + "row_spacing": 15, + "height": 0, + "growing_degree_days": 0, + "companions": [ + "carrot" + ], + "image": "/crops/images/black-salsify.jpg", + "icon": "/crops/icons/black-salsify.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/blackberry.json b/frontend/crops/data/blackberry.json new file mode 100644 index 0000000000..4488fb4926 --- /dev/null +++ b/frontend/crops/data/blackberry.json @@ -0,0 +1,18 @@ +{ + "name": "Blackberry", + "binomial_name": "Rubus fruticosus", + "common_names": [ + "Blackberry", + "bramble fruit" + ], + "description": "Deeply colored, sweet, clustered berries. The blackberry plant is a perennial plant that bears biennial stems or canes. The plant often has thorns, though some cultivars do not. Most varieties have greater berry yields from the second year on. In peak season, expect to harvest twice a week.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant canes into soil or pot", + "spread": 75, + "row_spacing": 100, + "height": 175, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/blackberry.jpg", + "icon": "/crops/icons/blackberry.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/blue-lake-bean.json b/frontend/crops/data/blue-lake-bean.json new file mode 100644 index 0000000000..726e23335b --- /dev/null +++ b/frontend/crops/data/blue-lake-bean.json @@ -0,0 +1,19 @@ +{ + "name": "Blue Lake Bean", + "binomial_name": "Phaseolus vulgaris var. nanus", + "common_names": [ + "Blue Lake", + "Bush Bean", + "Green Bean" + ], + "description": "Blue Lake is an upright bush bean with medium-thick green pods that are plump, tasty, and best when harvested at 15cm long. The plant has a high yield, long season, and benefits from inoculant.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors or out. Transplant seedlings after hardening off.", + "spread": 25, + "row_spacing": 25, + "height": 55, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/blue-lake-bean.jpg", + "icon": "/crops/icons/blue-lake-bean.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/blue-oyster-mushroom.json b/frontend/crops/data/blue-oyster-mushroom.json new file mode 100644 index 0000000000..fafc2d3a1a --- /dev/null +++ b/frontend/crops/data/blue-oyster-mushroom.json @@ -0,0 +1,18 @@ +{ + "name": "Blue Oyster Mushroom", + "binomial_name": "Pleurotus columbinus", + "common_names": [ + "blue oyster mushroom", + "blue oyster" + ], + "description": "Blue Oysters grow in large clusters and have the typical shelf-like shape of the Pleurotus species. They start out blue and turn gray as they mature. The mushroom's size and shape varies depending on growing conditions. They can grow rapidly, sometimes doubling in size daily. Blue Oysters require a very high level of fresh air exchange and grow well outdoors. They can also be grown indoors, but will have long and thick stems with small caps if the environment is high in CO2. They have a thick, meaty flesh that is milder in flavor than shiitake. Blue Oysters can be grown with mushroom kits, or by obtaining spores and inoculating a substrate such as pasteurized cardboard, straw, sawdust, or hardwood logs.", + "sun_requirements": "", + "sowing_method": "Inoculate substrate with spores", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/blue-oyster-mushroom.jpg", + "icon": "/crops/icons/blue-oyster-mushroom.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/blueberry.json b/frontend/crops/data/blueberry.json new file mode 100644 index 0000000000..c1279e353f --- /dev/null +++ b/frontend/crops/data/blueberry.json @@ -0,0 +1,15 @@ +{ + "name": "Blueberry", + "binomial_name": "Vaccinium sect. Cyanococcus", + "common_names": [], + "description": "Perennial flowering plants with sweet, indigo-colored berries. Blueberry plants are usually erect, prostrate shrubs that range in height from 10cm to 4m high, depending on the cultivar.", + "sun_requirements": "Full Sun", + "sowing_method": "Plant plants into soil or pots.", + "spread": 150, + "row_spacing": 240, + "height": 240, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/blueberry.jpg", + "icon": "/crops/icons/blueberry.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/bok-choy.json b/frontend/crops/data/bok-choy.json new file mode 100644 index 0000000000..9307c49d87 --- /dev/null +++ b/frontend/crops/data/bok-choy.json @@ -0,0 +1,21 @@ +{ + "name": "Bok Choy", + "binomial_name": "Brassica rapa subsp. Chinensis", + "common_names": [ + "Bok Choy", + "Bok Choi", + "Pak Choi", + "Toy Choi", + "Bok Choi Hybrid" + ], + "description": "A type of Chinese cabbage that does not form a head. It has smooth, dark or light green leaf blades and crisp white stalks that form a cluster similar to mustard greens or celery. It is often harvested while still small and tender, when it is around 10cm high. Winter hardy. Row spacing depends on desired harvest size.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, or sow indoors and harden off before transplanting", + "spread": 20, + "row_spacing": 15, + "height": 20, + "growing_degree_days": 50, + "companions": [], + "image": "/crops/images/bok-choy.jpg", + "icon": "/crops/icons/bok-choy.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/borage.json b/frontend/crops/data/borage.json new file mode 100644 index 0000000000..f681162d6e --- /dev/null +++ b/frontend/crops/data/borage.json @@ -0,0 +1,27 @@ +{ + "name": "Borage", + "binomial_name": "Borago officinalis", + "common_names": [ + "borage", + "borag", + "stoff\u00e9rblomma", + "agurkurt", + "hjulkrone", + "purasruoho", + "borretsch", + "boraginis semen", + "boraginis herba", + "oleum boraginis", + "starflower" + ], + "description": "Borage, or Starflower, is an annual flowering herb with blue, pink, or white flowers and bristly stems and leaves. It is a popular companion plant, particularly for tomatoes, as it is thought to confuse mother moths of tomato hornworms looking for a place to lay their eggs. It also attracts many pollinators. It's edible leaves can be used as a fresh vegetable or dried herb. When fresh, it has a cucumber-like taste and is used in salads, lemonade, or as a garnish. It's flowers have a sweet honey-like taste and can be used to decorate desserts or cocktails or candied. It is also cultivated for the borage seed oil extracted from it's seeds.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed outdoors, thin to 45cm apart when seedlings are 4cm tall", + "spread": 50, + "row_spacing": 30, + "height": 100, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/borage.jpg", + "icon": "/crops/icons/borage.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/brazilian-starfish-pepper.json b/frontend/crops/data/brazilian-starfish-pepper.json new file mode 100644 index 0000000000..da54e5f05f --- /dev/null +++ b/frontend/crops/data/brazilian-starfish-pepper.json @@ -0,0 +1,15 @@ +{ + "name": "Brazilian Starfish Pepper", + "binomial_name": "Capsicum baccatum", + "common_names": [], + "description": "Brazilian Starfish is a Capsicum baccatum cultivar hot pepper. It produces squat, 2cm wide, starfish-shaped fruits that have a rating of 10,000 to 30,000 Scoville units (close to or hotter than a serrano). The plant is a late, but prolific, producer and has an elegant weeping, vine-like growing habit. The peppers are green when young and turn bright red as they mature. They have a sweet, juicy, fruity taste and can be eaten fresh, dried, or pickled.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings after hardening off.", + "spread": 0, + "row_spacing": 50, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/brazilian-starfish-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/broccoli.json b/frontend/crops/data/broccoli.json new file mode 100644 index 0000000000..2752824d99 --- /dev/null +++ b/frontend/crops/data/broccoli.json @@ -0,0 +1,17 @@ +{ + "name": "Broccoli", + "binomial_name": "Brassica oleracea", + "common_names": [ + "broccoli" + ], + "description": "Broccoli has large flower heads known as \"crowns\" that are green to blue-green in color, grouped tightly together atop a thick stem, and surrounded by leaves. Broccoli resembles cauliflower, a different cultivar in its species. It thrives in cool weather.", + "sun_requirements": "Full Sun", + "sowing_method": "Sow seeds indoors and transplant outside", + "spread": 60, + "row_spacing": 40, + "height": 75, + "growing_degree_days": 80, + "companions": [], + "image": "/crops/images/broccoli.jpg", + "icon": "/crops/icons/broccoli.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/brussels-sprout.json b/frontend/crops/data/brussels-sprout.json new file mode 100644 index 0000000000..7587b4709b --- /dev/null +++ b/frontend/crops/data/brussels-sprout.json @@ -0,0 +1,17 @@ +{ + "name": "Brussels Sprouts", + "binomial_name": "brassica oleracea cv. gemmifera", + "common_names": [ + "brussels sprouts" + ], + "description": "Brussels sprouts grow on the sides of stalks up to 91cm tall. The stalks are covered with leaves and the sprouts look like miniature cabbages. They are a cool weather crop and are delicious roasted.", + "sun_requirements": "Full Sun", + "sowing_method": "Sow seeds indoors, harden off seedlings before transplanting outdoors", + "spread": 80, + "row_spacing": 60, + "height": 90, + "growing_degree_days": 110, + "companions": [], + "image": "/crops/images/brussels-sprout.jpg", + "icon": "/crops/icons/brussels-sprout.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/butternut-squash.json b/frontend/crops/data/butternut-squash.json new file mode 100644 index 0000000000..5cd40db557 --- /dev/null +++ b/frontend/crops/data/butternut-squash.json @@ -0,0 +1,22 @@ +{ + "name": "Butternut Squash", + "binomial_name": "Cucurbita moschata", + "common_names": [ + "Butternut Squash", + "Butternut pumpkin", + "Gramma" + ], + "description": "Butternut squash is a winter squash cultivar that produces long squash with smooth, tan-yellow skin and a rounded compartment of seeds at the bottom. Flesh is deep orange and firm with a sweet, nutty taste. Butternut squash becomes deeper orange and sweeter and richer as it ripens. Like other squash, it grows on sprawling vines. If starting seeds indoors, use peat pots and directly transplant them into the soil to reduce root disturbance. Harvest squash before any hard frosts and cure. Winter squash can be stored for up to 3 months in a cool, dry, well-ventilated place.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed. If planting indoors, harden off before transplanting seedlings outside.", + "spread": 150, + "row_spacing": 90, + "height": 30, + "growing_degree_days": 0, + "companions": [ + "borage", + "carrot" + ], + "image": "/crops/images/butternut-squash.jpg", + "icon": "/crops/icons/butternut-squash.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/california-poppy.json b/frontend/crops/data/california-poppy.json new file mode 100644 index 0000000000..531e72cb15 --- /dev/null +++ b/frontend/crops/data/california-poppy.json @@ -0,0 +1,21 @@ +{ + "name": "California Poppy", + "binomial_name": "Eschscholzia californica", + "common_names": [ + "California poppy", + "Californian poppy", + "golden poppy", + "California sunlight", + "cup of gold" + ], + "description": "The California Poppy is a perennial or annual flowering plant with blue-green foliage and yellow, orange, or red flowers with 4 silky petals. The petals close at night (or in cold, windy weather) and reopen in the morning. The California Poppy is native to the Western United States and Mexico. It is drought-tolerant, self-seeding, and grows well in poor, sandy soil. All parts of the plant have medicinal uses. California Poppies should be direct-seeded outdoors: their long taproots prevent them from transplanting well. Temperatures below -4\u00b0 C will kill plants.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors after temperatures are above 20\u00b0 C", + "spread": 0, + "row_spacing": 30, + "height": 150, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/california-poppy.jpg", + "icon": "/crops/icons/california-poppy.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cannabis.json b/frontend/crops/data/cannabis.json new file mode 100644 index 0000000000..640f2d932e --- /dev/null +++ b/frontend/crops/data/cannabis.json @@ -0,0 +1,21 @@ +{ + "name": "Cannabis", + "binomial_name": "Cannabis sativa / Cannabis indica / Cannabis ruderalis", + "common_names": [ + "Marijuana", + "Mary Jane", + "cannabis" + ], + "description": "Cannabis is a genus of a flowering plant in the family Cannabaceae. Three species may be recognized: Cannabis sativa, Cannabis indica and Cannabis ruderalis, or all three may be treated as subspecies of a single species, C. sativa. Cannabis is used for hemp, medicinal purposes, and as a recreational drug (tetrahydrocannabinol, or THC, is the principal psychoactive compound). As of 2017, growing cannabis is legal in 14 states in the United States. After harvest, the buds of cannabis are known as marijuana. \n\nThere are a few types of Cannabis seeds to consider if growing the plant for it's buds. Autoflowering strains do not rely on daylight hours and light schedules to enter the flowering stage. They are best for outdoor use, create smaller plants, and have a life cycle of 10 weeks from start to finish. Non-autoflowering strains depend light cycles to determine whether to grow or whether to flower, create bigger plants, and are best for indoor growing. Feminized cannabis seeds are bred to produce only female plants. Indica-dominant strains have thinner leaves and are better suited to tough weather conditions. Sativa-dominant strains prefer more light and warmer temperatures.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors or transplant seedlings outside, depending on growing zone", + "spread": 0, + "row_spacing": 0, + "height": 150, + "growing_degree_days": 0, + "companions": [ + "lemon-balm" + ], + "image": "/crops/images/cannabis.jpg", + "icon": "/crops/icons/cannabis.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cantaloupe.json b/frontend/crops/data/cantaloupe.json new file mode 100644 index 0000000000..5a8ddbd544 --- /dev/null +++ b/frontend/crops/data/cantaloupe.json @@ -0,0 +1,18 @@ +{ + "name": "Cantaloupe", + "binomial_name": "Cucumis melo var. cantalupo", + "common_names": [ + "Cantaloupe", + "Muskmelon" + ], + "description": "Cantaloupes are round melons that grow on vines and have sweet, firm, juicy, orange flesh. The rind is light-grey and reticulated (or covered in a \"net-like\" pattern).", + "sun_requirements": "Full Sun", + "sowing_method": "Sow 2-3 seeds per peat pot indoors and transplant peat pots directly into soil when soil is warm. Thin to 1 seedling.", + "spread": 150, + "row_spacing": 45, + "height": 40, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/cantaloupe.jpg", + "icon": "/crops/icons/cantaloupe.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/carrot.json b/frontend/crops/data/carrot.json new file mode 100644 index 0000000000..f550f9044f --- /dev/null +++ b/frontend/crops/data/carrot.json @@ -0,0 +1,19 @@ +{ + "name": "Carrot", + "binomial_name": "Daucus Carota", + "common_names": [ + "Carrot", + "Karotte", + "M\u00f6hre" + ], + "description": "The carrot is a root vegetable. It is usually orange in color, but some cultivars are purple, black, red, white, and yellow. The most commonly eaten part of the plant is the taproot, but the greens are sometimes eaten as well. The leaves appear first, and the taproot grows more slowly beneath the soil. Fast-growing cultivars mature within three months of sowing the seed. Slower-maturing cultivars are harvested four months after sowing.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct Seed, thin to 3cm apart when seedlings are 8cm high", + "spread": 10, + "row_spacing": 20, + "height": 35, + "growing_degree_days": 75, + "companions": [], + "image": "/crops/images/carrot.jpg", + "icon": "/crops/icons/carrot.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cascabel-chili.json b/frontend/crops/data/cascabel-chili.json new file mode 100644 index 0000000000..8e62be846c --- /dev/null +++ b/frontend/crops/data/cascabel-chili.json @@ -0,0 +1,25 @@ +{ + "name": "Cascabel Chili", + "binomial_name": "Capsicum annuum 'Cascabel'", + "common_names": [ + "cascabel", + "little bell", + "rattle chili", + "ball chili", + "bola chili", + "chile bola", + "jingebell" + ], + "description": "Cascabel, or Rattle Chiles, are a Mirsaol Capiscum annum cultivar that produces small, nearly round peppers 2-3cm in diameter. \"Mirasol\" means \"looking at the sun\" in Spanish - the peppers borne by a Mirasol cultivar are erect and point to the sun. The peppers ripen from green to red and have a thin skin that makes them well-suited for drying. When dry, the loose seeds rattle inside. They are known as Bola Chili when fresh, and Cascabel, Guajones or Coras when dried. The peppers are mildly spicy with a Scoville rating of 1,500-2,500 SHU and a nutty, slightly smoky flavor.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 0, + "height": 75, + "growing_degree_days": 0, + "companions": [ + "spinach" + ], + "image": "/crops/images/cascabel-chili.jpg", + "icon": "/crops/icons/cascabel-chili.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cauliflower.json b/frontend/crops/data/cauliflower.json new file mode 100644 index 0000000000..61327897d8 --- /dev/null +++ b/frontend/crops/data/cauliflower.json @@ -0,0 +1,19 @@ +{ + "name": "Cauliflower", + "binomial_name": "Brassica oleracea", + "common_names": [ + "Cauliflower" + ], + "description": "Cauliflower is a vegetable in the Brassicaceae family. The solid, firm head resembles that of broccoli and is usually white, but can also be yellow, purple, or green in color. Like broccoli, it sits atop a stalk. The head is wrapped in thick leaves that begin to open when the plant is ready for harvest. All cauliflower does best in cool weather.", + "sun_requirements": "Full Sun", + "sowing_method": "Sow seeds indoors, harden seedlings off before transplanting", + "spread": 30, + "row_spacing": 60, + "height": 75, + "growing_degree_days": 80, + "companions": [ + "dill" + ], + "image": "/crops/images/cauliflower.jpg", + "icon": "/crops/icons/cauliflower.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cayenne-pepper.json b/frontend/crops/data/cayenne-pepper.json new file mode 100644 index 0000000000..8a9e693f34 --- /dev/null +++ b/frontend/crops/data/cayenne-pepper.json @@ -0,0 +1,22 @@ +{ + "name": "Cayenne Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "Guinea spice", + "cow-horn pepper", + "red hot chili pepper", + "aleva", + "bird pepper", + "red pepper" + ], + "description": "Cayenne peppers are a cultivar of Capsicum annuum. They are members of the nightshade family, Solanaceae, and related to tomatoes and eggplant. The upright plants produce long, thin, red fruit that can be up to 25cm long. Cayenne peppers have a Scoville rating of 30,000 to 50,000 units and are slightly hotter than jalapenos. They are generally dried and ground to make cayenne spice, but they can also be used whole or in a thin, vinegar-based sauce.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 60, + "row_spacing": 45, + "height": 90, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/cayenne-pepper.jpg", + "icon": "/crops/icons/cayenne-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/celeriac-root.json b/frontend/crops/data/celeriac-root.json new file mode 100644 index 0000000000..647420b140 --- /dev/null +++ b/frontend/crops/data/celeriac-root.json @@ -0,0 +1,18 @@ +{ + "name": "Celeriac", + "binomial_name": "Apium graveolens var. rapaceum", + "common_names": [ + "Celeriac Root", + "Celeriac" + ], + "description": "Celeriac is a large root that develops at soil level and looks like a textured turnip. It is topped with curly leaves. Celeriac root has a flavor similar to celery (they are in the same family) and is delicious in soups, stews, and casseroles. Because it is a root vegetable, it can be stored for months.", + "sun_requirements": "Full Sun", + "sowing_method": "Seed indoors, then transplant outside into soil", + "spread": 60, + "row_spacing": 20, + "height": 50, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/celeriac-root.jpg", + "icon": "/crops/icons/celeriac-root.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/celery.json b/frontend/crops/data/celery.json new file mode 100644 index 0000000000..82a9b9c302 --- /dev/null +++ b/frontend/crops/data/celery.json @@ -0,0 +1,19 @@ +{ + "name": "Celery", + "binomial_name": "Apium graveolens", + "common_names": [ + "celery" + ], + "description": "The celery plant has long fibrous stalks that taper into leaves. The stalks and leaves can both be eaten. Celery seed is also used as a spice. Celery seed extracts are used in medicines.", + "sun_requirements": "Partial Sun", + "sowing_method": "Sow seeds indoors 10-12 weeks before transplanting outdoors", + "spread": 25, + "row_spacing": 15, + "height": 40, + "growing_degree_days": 105, + "companions": [ + "red-cabbage" + ], + "image": "/crops/images/celery.jpg", + "icon": "/crops/icons/celery.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/chamomile.json b/frontend/crops/data/chamomile.json new file mode 100644 index 0000000000..df547aa802 --- /dev/null +++ b/frontend/crops/data/chamomile.json @@ -0,0 +1,17 @@ +{ + "name": "Chamomile", + "binomial_name": "chamaemelum nobile", + "common_names": [ + "chamomile" + ], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/chamomile.jpg", + "icon": "/crops/icons/chamomile.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/chantrelle-mushroom.json b/frontend/crops/data/chantrelle-mushroom.json new file mode 100644 index 0000000000..3646130594 --- /dev/null +++ b/frontend/crops/data/chantrelle-mushroom.json @@ -0,0 +1,19 @@ +{ + "name": "Chantrelle Mushroom", + "binomial_name": "Cantharellus cibarius", + "common_names": [ + "chanterelles", + "girolle", + "Pfifferling" + ], + "description": "Chantrelle mushrooms are orange or yellow and funnel shaped, with gill-like ridges beneath the smooth cap. They are one of the most desirable edible mushrooms. Their flavor and aroma range from fruity to woody, earthy, or spicy. Chantrelles are a mycorrhizal fungi, growing on trees in a symbiotic relationship. Their tree of choice varies depending on what region, but they often grow on hemlock, Douglas fir, white pine, beech, spruce, and birch. They are difficult to cultivate and are usually foraged for in the wild. To cultivate, find a tree appropriate for the mushroom in your region, and test and amend soil pH to get it between 4-5.5. Rake the soil to loosen it and break up whole chanterelles, spreading mushroom bits around the loose soil to distribute the spores contained in the mushroom. Leave the area undisturbed. It can take years for the mycelium to develop and bear fruit.", + "sun_requirements": "", + "sowing_method": "Direct seed mushroom pieces", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/chantrelle-mushroom.jpg", + "icon": "/crops/icons/chantrelle-mushroom.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cheongyang-pepper.json b/frontend/crops/data/cheongyang-pepper.json new file mode 100644 index 0000000000..3b19136f97 --- /dev/null +++ b/frontend/crops/data/cheongyang-pepper.json @@ -0,0 +1,19 @@ +{ + "name": "Cheongyang Chili Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "Cheongyang Chili", + "chungyang red pepper", + "Cheongyang gochu" + ], + "description": "The Cheongyang chili pepper is a Capsicum annuum cultivar from the Cheongyang region of Korea. The pepper was created by crossing the local Jejudo chilli with Bird's eye chilli. It is a bit sweeter and tangier than the Bird's Eye and has a Scoville rating of 10,000 units. The fruit turns red as it ripens and is 4-5cm long and 1cm in diameter.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/cheongyang-pepper.jpg", + "icon": "/crops/icons/cheongyang-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cherokee-purple-tomato.json b/frontend/crops/data/cherokee-purple-tomato.json new file mode 100644 index 0000000000..e7c67fae47 --- /dev/null +++ b/frontend/crops/data/cherokee-purple-tomato.json @@ -0,0 +1,17 @@ +{ + "name": "Cherokee Purple Tomato", + "binomial_name": "Solanum lycopersicum", + "common_names": [ + "Cherokee Purple" + ], + "description": "Cherokee Purple is an heirloom tomato variety that produces flattened globe-shaped fruits that are dusky pink with dark green shoulders. Fruits are medium to large sized with a deep, rich flavor. Best eaten raw. For larger yield and improved plant health, prune and stake tomatoes.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors and transplant seedlings outside after hardening off", + "spread": 45, + "row_spacing": 90, + "height": 200, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/cherokee-purple-tomato.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cherry-belle-radish.json b/frontend/crops/data/cherry-belle-radish.json new file mode 100644 index 0000000000..1b4b4fab32 --- /dev/null +++ b/frontend/crops/data/cherry-belle-radish.json @@ -0,0 +1,17 @@ +{ + "name": "Cherry Belle Radish", + "binomial_name": "Raphanus sativus", + "common_names": [ + "Cherry Bell" + ], + "description": "Cherry Belle radishes are small, fast-growing radishes that are smooth and round with bright red skin and crisp white flesh. They are harvested when they are 2cm across. Like all radishes, they prefer cooler weather. Slice and toss into salads.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed, thin to 5cm apart", + "spread": 15, + "row_spacing": 5, + "height": 13, + "growing_degree_days": 36, + "companions": [], + "image": "/crops/images/cherry-belle-radish.jpg", + "icon": "/crops/icons/cherry-belle-radish.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cherry-stuffer-pepper.json b/frontend/crops/data/cherry-stuffer-pepper.json new file mode 100644 index 0000000000..f5af4fc39a --- /dev/null +++ b/frontend/crops/data/cherry-stuffer-pepper.json @@ -0,0 +1,17 @@ +{ + "name": "Cherry Stuffer Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "Cherry Stuffer" + ], + "description": "Cherry Stuffer peppers are small sweet, round peppers that can be eaten fresh, stuffed, or grilled. They resemble the spicier Cherry Bomb pepper.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 70, + "row_spacing": 60, + "height": 70, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/cherry-stuffer-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cherry-tomato.json b/frontend/crops/data/cherry-tomato.json new file mode 100644 index 0000000000..9b5e3c3da7 --- /dev/null +++ b/frontend/crops/data/cherry-tomato.json @@ -0,0 +1,18 @@ +{ + "name": "Cherry Tomato", + "binomial_name": "Solanum lycopersicum", + "common_names": [ + "Cherry tomato", + "red robin" + ], + "description": "This variety can be grown in pots.", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 25, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/cherry-tomato.jpg", + "icon": "/crops/icons/cherry-tomato.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/chile-de-aborol-pepper.json b/frontend/crops/data/chile-de-aborol-pepper.json new file mode 100644 index 0000000000..d74b5fd6af --- /dev/null +++ b/frontend/crops/data/chile-de-aborol-pepper.json @@ -0,0 +1,19 @@ +{ + "name": "Chile De Aborol", + "binomial_name": "Capsicum annuum", + "common_names": [ + "Chile de \u00e1rbol", + "bird's beak chile", + "rat's tail chile" + ], + "description": "Chile de \u00e1rbol, or Bird's Beak chiles, are a Capsicum annuum cultivar with a Scoville heat index of 15,000-30,000 units (slightly less spicy than Cayenne). They are members of the nightshade family, Solanaceae, and are related to tomatoes and eggplant. The upright plants produce slender, curved peppers 5-7.5 cm long and 1cm in diameter. The peppers turn red as they ripen, and are notable for keeping their red color after being dried. Chile de \u00e1rbol can be used fresh, dried, or powdered. They need 80-100 frost-free days for fruit to set and mature.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 60, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/chile-de-aborol-pepper.jpg", + "icon": "/crops/icons/chile-de-aborol-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cilantro.json b/frontend/crops/data/cilantro.json new file mode 100644 index 0000000000..761451f932 --- /dev/null +++ b/frontend/crops/data/cilantro.json @@ -0,0 +1,19 @@ +{ + "name": "Cilantro", + "binomial_name": "Coriandrum sativum", + "common_names": [ + "cilantro", + "Chinese parsley", + "coriander" + ], + "description": "Cilantro is a soft leafy green plant. It's leaves are variable in shape - they are broadly lobed at the base of the plant and become slender and feathery higher up the stems. All of the plant is edible, but the fresh leaves and dried seeds (coriander) are most commonly used. Cilantro is great in salads and as a garnish on stews, casseroles, and tacos, but tastes \"soapy\" to some people.", + "sun_requirements": "Partial Sun", + "sowing_method": "Sow seeds indoors 6-8 weeks before last frost of the season, harden off before transplanting outdoors", + "spread": 30, + "row_spacing": 30, + "height": 50, + "growing_degree_days": 45, + "companions": [], + "image": "/crops/images/cilantro.jpg", + "icon": "/crops/icons/cilantro.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cinnamon-cap-mushroom.json b/frontend/crops/data/cinnamon-cap-mushroom.json new file mode 100644 index 0000000000..fbbc2b2b4f --- /dev/null +++ b/frontend/crops/data/cinnamon-cap-mushroom.json @@ -0,0 +1,18 @@ +{ + "name": "Cinnamon Cap Mushroom", + "binomial_name": "Hypholoma sublateritium", + "common_names": [ + "Cinnamon cap", + "brick top" + ], + "description": "Cinnamon Caps are small, orange to pinkish mushrooms with flaky caps. They have a nutty, earthy flavor and crunchy texture. They are delicious saut\u00e9ed, grilled, baked or roasted. Cinnamon Caps grow quickly and abundantly in clusters on large wood substrates or inoculated logs.", + "sun_requirements": "", + "sowing_method": "Inoculate log or wood substrate with spores", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/cinnamon-cap-mushroom.jpg", + "icon": "/crops/icons/cinnamon-cap-mushroom.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/claytonia.json b/frontend/crops/data/claytonia.json new file mode 100644 index 0000000000..3d7586238a --- /dev/null +++ b/frontend/crops/data/claytonia.json @@ -0,0 +1,15 @@ +{ + "name": "Claytonia", + "binomial_name": "", + "common_names": [], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/claytonia.jpg", + "icon": "/crops/icons/claytonia.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/collard-greens.json b/frontend/crops/data/collard-greens.json new file mode 100644 index 0000000000..f5b258ca69 --- /dev/null +++ b/frontend/crops/data/collard-greens.json @@ -0,0 +1,18 @@ +{ + "name": "Collard Greens", + "binomial_name": "Brassica oleracea", + "common_names": [ + "collard greens", + "collards" + ], + "description": "Collards are a loose-leafed cultivar of Brassica oleracea. They are members of the Acephala Group of the species, which includes kale and spring greens. Collards have big, thick, slightly bitter leaves that are high in vitamins and minerals. They are a staple of Southern Cuisine and can be eaten boiled, saut\u00e9ed, baked, or raw.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors or out. Transplant seedlings outside after hardening off.", + "spread": 30, + "row_spacing": 15, + "height": 90, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/collard-greens.jpg", + "icon": "/crops/icons/collard-greens.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/corn.json b/frontend/crops/data/corn.json new file mode 100644 index 0000000000..3bbc475528 --- /dev/null +++ b/frontend/crops/data/corn.json @@ -0,0 +1,18 @@ +{ + "name": "Corn", + "binomial_name": "Zea mays", + "common_names": [ + "Maize", + "Corn" + ], + "description": "Corn is a large grain plant, or tall grass, first domesticated about 10,000 years ago by indigenous peoples in Southern Mexico. The leafy stalk produces ears after pollination. Depending on the variety, the corn can be eaten fresh, or dried and ground into cornmeal.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors", + "spread": 50, + "row_spacing": 76, + "height": 290, + "growing_degree_days": 100, + "companions": [], + "image": "/crops/images/corn.jpg", + "icon": "/crops/icons/corn.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cotton.json b/frontend/crops/data/cotton.json new file mode 100644 index 0000000000..380c42e8a9 --- /dev/null +++ b/frontend/crops/data/cotton.json @@ -0,0 +1,18 @@ +{ + "name": "Cotton", + "binomial_name": "Gossypium hirsutum", + "common_names": [ + "Cotton", + "upland cotton" + ], + "description": "Cotton is grown for the cotton boll, or protective case of soft, fluffy fibers around it's seeds. The boll is harvested, spun, and used to make textiles. Cotton is a member of the genus Gossypium in the mallow family Malvaceae. There are four main cultivars of cotton. Gossypium hirsutum, or upland cotton, accounts for 90% of world production. While white cotton is the most common type, different varieties produce cotton bolls in a range of colors from green to brown to red, eliminating the need for dye. \n\nCotton plants have large flowers that resemble hibiscus. After the flowers fade, the boll grows around the seeds. The boll is designed to aid in seed dispersal. The cotton plant is a shrub native to tropical and subtropical areas around the world, including the Americas, Africa, and India. It requires a long, sunny growing season with moderate rainfall. The plants need 65-75 days above 15C to flower, and another 50 days for the seed pods to mature and open. Growers outside of this climate should start seeds indoors and transplant outside. Cotton is ready to be picked when the bolls crack open and the fluffy white cotton is exposed. Wear gloves - cotton bolls are sharp. Lay out to dry, then separate seeds from cotton.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed. Seed indoors and transplant out in areas with shorter growing seasons.", + "spread": 0, + "row_spacing": 0, + "height": 150, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/cotton.jpg", + "icon": "/crops/icons/cotton.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cremini-mushroom.json b/frontend/crops/data/cremini-mushroom.json new file mode 100644 index 0000000000..4f16c21260 --- /dev/null +++ b/frontend/crops/data/cremini-mushroom.json @@ -0,0 +1,18 @@ +{ + "name": "Cremini Mushroom", + "binomial_name": "Agaricus bisporus", + "common_names": [ + "Cremini mushroom", + "cremini" + ], + "description": "Cremini mushroom are the same species (Agaricus bisporus) as White Button and Portobello mushrooms. White Button are harvested earliest, making them the youngest version of the mushroom. Portobello are the most mature version of the mushroom, making Cremini the adolescent stage. Cremini have a browner color, denser texture, and deeper flavor than White Button. They hold up better in stews and soups than White Buttons. Growing Agaricus bisporus requires spores and partially decomposed compost and peat moss. These mushrooms can be grown indoors year-round because temperature, moisture, and light can be controlled. They can also be grown outdoors.", + "sun_requirements": "Full Shade", + "sowing_method": "Sprinkle spores on surface of compost and peat moss, gently mix in, and press down.", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/cremini-mushroom.jpg", + "icon": "/crops/icons/cremini-mushroom.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cubanelle-pepper.json b/frontend/crops/data/cubanelle-pepper.json new file mode 100644 index 0000000000..d5abe2cf86 --- /dev/null +++ b/frontend/crops/data/cubanelle-pepper.json @@ -0,0 +1,21 @@ +{ + "name": "Cubanelle Pepper", + "binomial_name": "Capsicum annuum 'Cubanelle'", + "common_names": [ + "cubanelle", + "cuban pepper", + "italian frying pepper" + ], + "description": "The Cuban, Cubanelle, or Italian Frying pepper, is a sweet Capsicum annuum cultivar popular in Cuban, Dominican, Puerto Rican, and Italian cuisine. Cubanelles have 2-3 lobes and are 12-18cm long. They have more flavor, thinner flesh, and a lower water content than bell peppers. Cubanelles ripen from light yellow-green to orange-red, but are commonly harvested at yellow-green to be roasted or fried. They have a Scoville rating of 1,000 units.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 75, + "row_spacing": 45, + "height": 75, + "growing_degree_days": 0, + "companions": [ + "parsley" + ], + "image": "/crops/images/cubanelle-pepper.jpg", + "icon": "/crops/icons/cubanelle-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/cucumber.json b/frontend/crops/data/cucumber.json new file mode 100644 index 0000000000..b2a3ec5861 --- /dev/null +++ b/frontend/crops/data/cucumber.json @@ -0,0 +1,26 @@ +{ + "name": "Cucumber", + "binomial_name": "cucumis sativus", + "common_names": [ + "cucumber", + " gherkin", + " concombre", + " huanggua", + " kiukaba", + " kukama (tuvalu)", + " khira", + " kiukamupa", + " kukamba", + " cetriolo" + ], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/cucumber.jpg", + "icon": "/crops/icons/cucumber.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/curly-endive.json b/frontend/crops/data/curly-endive.json new file mode 100644 index 0000000000..5d78dd2864 --- /dev/null +++ b/frontend/crops/data/curly-endive.json @@ -0,0 +1,20 @@ +{ + "name": "Endive", + "binomial_name": "Cichorium endivia var. crispum", + "common_names": [ + "curly endive", + "endive", + "chicor\u00e9e fris\u00e9e", + "frisee" + ], + "description": "Curly Endive is a narrow-leaved member of the Chicory family (versus Escarole, which is broad-leaved). It grows in loose heads of narrow, green, lacey leaves with curled tips. The head is often tied together when it is 25cm tall to blanch the inner leaves. The outer leaves have a slightly bitter flavor, and the inner leaves have a subtle sweetness and more delicate texture. All of the leaves sweeten slightly with frost or cooking. Curly Endive can be eaten raw in salads or cooked in soups, stews, and other dishes.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant outside after hardening off seedlings", + "spread": 25, + "row_spacing": 40, + "height": 25, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/curly-endive.jpg", + "icon": "/crops/icons/curly-endive.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/curly-kale.json b/frontend/crops/data/curly-kale.json new file mode 100644 index 0000000000..cc326d6925 --- /dev/null +++ b/frontend/crops/data/curly-kale.json @@ -0,0 +1,19 @@ +{ + "name": "Curly Kale", + "binomial_name": "brassica oleracea var. fimbriata", + "common_names": [ + "curly kale", + " borecole", + " scotch kale" + ], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/curly-kale.jpg", + "icon": "/crops/icons/curly-kale.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/dandelion.json b/frontend/crops/data/dandelion.json new file mode 100644 index 0000000000..362ed2dac9 --- /dev/null +++ b/frontend/crops/data/dandelion.json @@ -0,0 +1,18 @@ +{ + "name": "Dandelion", + "binomial_name": "Taraxacum officinale", + "common_names": [ + "dandelion", + "lion's tooth" + ], + "description": "Dandelions are a large genus of flowering plants in the Asteraceae family native to Eurasia and North America. The two most common species, T. officinale and T. erythrospermum, propagate as wildflowers, but they can also be cultivated for their greens, flowers, and roots. Dandelions are perennial, herbaceous plants with long taproots, lobed leaves that grow in rosettes, and yellow flower heads composed of florets. The young leaves can be used raw in salads. They are high in vitamins and minerals. Older, larger leaves are more bitter and are best saut\u00e9ed or added to soups and stews. The flower petals are used to make dandelion wine. The roots take a year to harvest, and can be brewed into a detoxifying, mildly diuretic tea or coffee substitute. Dandelions are considered a beneficial weed and have a number of uses as a companion plant. They combat fusarium wilt, a soil-borne fungal disease that afflicts tomatoes, because their roots produce an acid that \"starves\" the disease of one of its essential nutrients, iron. Their taproots loosen compacted soils and bring up nutrients for shallower-rooted plants. Dandelions attract pollinating insects and release ethylene gas, helping fruit to ripen. Deadhead flowers to prevent the plants from becoming invasive.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, thin to 15cm", + "spread": 45, + "row_spacing": 15, + "height": 45, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/dandelion.jpg", + "icon": "/crops/icons/dandelion.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/dark-opal-basil.json b/frontend/crops/data/dark-opal-basil.json new file mode 100644 index 0000000000..f67851d285 --- /dev/null +++ b/frontend/crops/data/dark-opal-basil.json @@ -0,0 +1,18 @@ +{ + "name": "Dark Opal Basil", + "binomial_name": "Ocimum basilicum", + "common_names": [ + "Basil", + "Basilikum" + ], + "description": "", + "sun_requirements": "Full Sun", + "sowing_method": "", + "spread": 40, + "row_spacing": 30, + "height": 60, + "growing_degree_days": 650, + "companions": [], + "image": "/crops/images/dark-opal-basil.jpg", + "icon": "/crops/icons/dark-opal-basil.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/datil-pepper.json b/frontend/crops/data/datil-pepper.json new file mode 100644 index 0000000000..db93573b76 --- /dev/null +++ b/frontend/crops/data/datil-pepper.json @@ -0,0 +1,19 @@ +{ + "name": "Datil Pepper", + "binomial_name": "Capsicum chinense", + "common_names": [ + "Datil pepper" + ], + "description": "Datil peppers are a variety of the Capsicum chinense species that have a rating of 100,000-300,000 Scoville heat units and a sweeter, more complex, fruitier flavor than habaneros. Peppers ripen from olive green to lime green with a dark purple stripe to brilliant orange yellow and grow to 8cm in length. Like most hot peppers, they need at least 90 days of dry, warm weather. They can be grown in containers in regions with shorter, cooler growing seasons. Datil peppers prefer direct sun in the morning and filtered sun in the afternoon.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 90, + "height": 90, + "growing_degree_days": 0, + "companions": [ + "carrot" + ], + "image": "", + "icon": "/crops/icons/datil-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/delicata-squash.json b/frontend/crops/data/delicata-squash.json new file mode 100644 index 0000000000..7287c4fe05 --- /dev/null +++ b/frontend/crops/data/delicata-squash.json @@ -0,0 +1,22 @@ +{ + "name": "Delicata Squash", + "binomial_name": "Cucurbita pepo var. pepo", + "common_names": [ + "Delicata Squash", + "Peanut squash", + "Bohemian squash", + "Sweet potato squash" + ], + "description": "Delicata is an oblong, cylindrical, cream-colored squash with green and sometimes orange stripes. Like all squash, it grows on vines. It has deep orange flesh that tastes like a sweet potato, but sweeter. Delicata is consumed as a winter squash, but is actually in the same species as summer squash: Cucurbita pepo. \n\nDelicata is delicious roasted, baked, sauteed, or steamed. When roasted or baked, the rind can be eaten as well.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed in groups of 2, thin to 1 plant when seedling have 2 sets of adult leaves", + "spread": 80, + "row_spacing": 90, + "height": 30, + "growing_degree_days": 0, + "companions": [ + "icicle-radish" + ], + "image": "", + "icon": "/crops/icons/delicata-squash.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/diablito-pepper.json b/frontend/crops/data/diablito-pepper.json new file mode 100644 index 0000000000..25cae62499 --- /dev/null +++ b/frontend/crops/data/diablito-pepper.json @@ -0,0 +1,18 @@ +{ + "name": "Diablito Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "Diablito", + "diablito pepper" + ], + "description": "The Diablito Pepper is a hot Thai pepper that produces 5cm long conical fruit that are red when ripe. It has a rating of 5,000 to 30,000 Scoville Units.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 60, + "row_spacing": 40, + "height": 60, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/diablito-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/dill.json b/frontend/crops/data/dill.json new file mode 100644 index 0000000000..16053a3464 --- /dev/null +++ b/frontend/crops/data/dill.json @@ -0,0 +1,26 @@ +{ + "name": "Dill", + "binomial_name": "anethum graveolens", + "common_names": [ + "dill", + " garden dill", + " east indian dill", + " aneth", + " eneldo", + " dill", + " dill", + " shibith", + " dille", + " aneto" + ], + "description": "Dill is a fragrant herb commonly used in cooking, especially for its fresh leaves and seeds. It has feathery, delicate green leaves and produces yellow, umbrella-like flower clusters. The plant is known for its unique flavor, often used in pickling, salad dressings, and as a seasoning for fish and potatoes. It attracts beneficial insects, such as pollinators, and can be grown in both home gardens and containers.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors", + "spread": 30, + "row_spacing": 30, + "height": 75, + "growing_degree_days": 50, + "companions": [], + "image": "/crops/images/dill.jpg", + "icon": "/crops/icons/dill.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/dundicut-pepper.json b/frontend/crops/data/dundicut-pepper.json new file mode 100644 index 0000000000..fb60b5a4cc --- /dev/null +++ b/frontend/crops/data/dundicut-pepper.json @@ -0,0 +1,20 @@ +{ + "name": "Dundicut", + "binomial_name": "Capsicum annuum", + "common_names": [ + "dundicut", + "lal mirch" + ], + "description": "Dundicut peppers are a dark red chili pepper native to Pakistan. They are a cultivar of either Capsicum frutescens or Capsicum annuum. The plants produce 2-3cm, upright, teardrop-shaped fruits that ripen from green to dark red. The fruit is similar in size and flavor to Scotch Bonnet peppers, but, with a Scoville rating of 30,000 to 65,000, not as hot. Dundicut are typically used dried in curries, chutneys, and sauces, and have a mildly fruity aroma.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 0, + "height": 85, + "growing_degree_days": 0, + "companions": [ + "eggplant" + ], + "image": "", + "icon": "/crops/icons/dundicut-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/echinacea.json b/frontend/crops/data/echinacea.json new file mode 100644 index 0000000000..5b6bfc4c71 --- /dev/null +++ b/frontend/crops/data/echinacea.json @@ -0,0 +1,19 @@ +{ + "name": "Coneflower", + "binomial_name": "Echinacea", + "common_names": [ + "Echinacea", + "coneflower", + "purple coneflower" + ], + "description": "Coneflowers, or Echinacea, are a genus of perennial flower. The genus is comprised of 9 species native to Eastern and Central North America's prairies and open wooded areas. Coneflowers grow from taproots and produce erect, unbranched stems with showy, long-lasting flowers that resemble daisies with their raised centers. Flowers are usually purple or white, but recent breeding has expanded the color selection widely. \"Deadhead,\" or remove spent flower heads, to encourage new flowers. Coneflowers attract pollinators and are drought and heat tolerant and deer resistant. They will self-seed in the fall. If starting from seed, some species benefit from stratification. The species Echinacea purpurea is used in herbal medicine. Native Americans used Echinacea purpurea externally for wounds and insect bites and internally for pains, coughs, stomach cramps, and snake bites. Contemporary herbal medicine uses Echinacea to strengthen the immune system, but scientific research is inconclusive as to its effectiveness.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed or cuttings", + "spread": 40, + "row_spacing": 45, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/echinacea.jpg", + "icon": "/crops/icons/echinacea.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/eggplant.json b/frontend/crops/data/eggplant.json new file mode 100644 index 0000000000..2932f37e47 --- /dev/null +++ b/frontend/crops/data/eggplant.json @@ -0,0 +1,22 @@ +{ + "name": "Eggplant", + "binomial_name": "Solanum melongena", + "common_names": [ + "Brinjal", + "aubergine", + "eggplant", + "terong", + "mad apple", + "melongene" + ], + "description": "Eggplants commonly are egg-shaped with glossy black skin, but can come in a variety of other shapes and colors. They can be white, yellow, and pale to deep purple. Some are as small as goose eggs. The 'Rosa Bianca' cultivar is squat and round, while Asian cultivars can be long and thin. Eggplant stems are often spiny and their flowers range from white to purple. \n\nTheir flesh is generally white with a meaty texture and small seeds in the center. They are delicious grilled, roasted, in soups and stews, and breaded and fried.", + "sun_requirements": "Full Sun", + "sowing_method": "Sow seeds indoors and transplant out, or plant nursery seedlings", + "spread": 55, + "row_spacing": 60, + "height": 150, + "growing_degree_days": 90, + "companions": [], + "image": "/crops/images/eggplant.jpg", + "icon": "/crops/icons/eggplant.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/facing-heaven-pepper.json b/frontend/crops/data/facing-heaven-pepper.json new file mode 100644 index 0000000000..1805b53daa --- /dev/null +++ b/frontend/crops/data/facing-heaven-pepper.json @@ -0,0 +1,19 @@ +{ + "name": "Facing Heaven Pepper", + "binomial_name": "Capsicum annuum var. conoides", + "common_names": [ + "Heaven Chili", + "Heaven Chile", + "skyward-pointing chili pepper" + ], + "description": "The Facing Heaven Pepper is a Capsicum annuum variety widely grown in the Sichuan Province of southwestern China. The plant produces small, cone-shaped, medium-hot chili peppers that are 3-6cm long and 1-2cm in diameter. The peppers have very thin skin and grow with the fruit pointing upwards instead of hanging down. Facing Heaven peppers are fragrant and moderately spicy, with a Scoville heating rating of 10,000 to 50,000 units and a citrus-y flavor. The peppers ripen to a bright red and are lightly cooked in oil or dried and added to dishes whole.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 40, + "height": 100, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/facing-heaven-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/fatilii-pepper.json b/frontend/crops/data/fatilii-pepper.json new file mode 100644 index 0000000000..dd6191db1b --- /dev/null +++ b/frontend/crops/data/fatilii-pepper.json @@ -0,0 +1,15 @@ +{ + "name": "Fatilii Pepper", + "binomial_name": "", + "common_names": [], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/fatilii-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/fava-bean.json b/frontend/crops/data/fava-bean.json new file mode 100644 index 0000000000..962b3bb392 --- /dev/null +++ b/frontend/crops/data/fava-bean.json @@ -0,0 +1,27 @@ +{ + "name": "Fava Bean", + "binomial_name": "Vicia faba", + "common_names": [ + "broad bean", + "fava bean", + "faba bean", + "field bean", + "bell bean", + "English bean", + "horse bean", + "Windsor bean", + "pigeon bean", + "tic bean", + "tick bean" + ], + "description": "The fava, or broad bean, is a species of flowering plant in the vetch and pea family Fabaceae. Unlike most beans, fava beans thrive in cooler weather and do not require trellising. They should be planted in early spring as soon as the soil can be worked. The broad, leathery pods contain 3-8 beans and grow on stiff stems that can reach heights of 1.8m. Fava are planted for their edible beans, or as a cover crop to reduce winter erosion and increase nitrogen in the soil. Inoculants can increase yield and nitrogen production. The pods are commonly harvested while young, when they are about 15cm long. Fava beans are eaten in a myriad of ways: raw in salads, grilled, roasted, fried, pureed, and more.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors", + "spread": 0, + "row_spacing": 12, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/fava-bean.jpg", + "icon": "/crops/icons/fava-bean.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/fennel.json b/frontend/crops/data/fennel.json new file mode 100644 index 0000000000..89aed56a28 --- /dev/null +++ b/frontend/crops/data/fennel.json @@ -0,0 +1,19 @@ +{ + "name": "Fennel", + "binomial_name": "Foeniculum vulgare", + "common_names": [ + "Fennel", + "finocchio", + "finuke" + ], + "description": "Fennel is a flowering plant species with yellow flowers and feathery leaves. It is grown primarily for its bulbous white base and green leaf stalks, which have a mild, fresh licorice flavor. It's base and stalks are delicious roasted, grilled, or eaten fresh. \n\nDo not let fennel go to seed: it can become invasive.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, or sow indoors and harden off before transplanting outside", + "spread": 40, + "row_spacing": 15, + "height": 135, + "growing_degree_days": 90, + "companions": [], + "image": "/crops/images/fennel.jpg", + "icon": "/crops/icons/fennel.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/flower.json b/frontend/crops/data/flower.json new file mode 100644 index 0000000000..39eb94d447 --- /dev/null +++ b/frontend/crops/data/flower.json @@ -0,0 +1,15 @@ +{ + "name": "Flower", + "binomial_name": "", + "common_names": [], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/flower.jpg", + "icon": "/crops/icons/flower.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/french-breakfast-radish.json b/frontend/crops/data/french-breakfast-radish.json new file mode 100644 index 0000000000..774af2eb17 --- /dev/null +++ b/frontend/crops/data/french-breakfast-radish.json @@ -0,0 +1,18 @@ +{ + "name": "French Breakfast Radish", + "binomial_name": "R. raphanistrum subsp. sativus", + "common_names": [ + "French Breakfast Radish", + "radish" + ], + "description": "Radishes are edible root vegetable of the Brassicaceae family. They are usually eaten raw in salads. Their sharp flavor comes from various chemical compounds produced by the plants, including glucosinolate, myrosinase, and isothiocyanate. Radishes are sometimes grown as companion plants because few pests and diseases affect them.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, thin to 2cm apart", + "spread": 15, + "row_spacing": 3, + "height": 20, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/french-breakfast-radish.jpg", + "icon": "/crops/icons/french-breakfast-radish.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/fresno-pepper.json b/frontend/crops/data/fresno-pepper.json new file mode 100644 index 0000000000..70bd0b4cae --- /dev/null +++ b/frontend/crops/data/fresno-pepper.json @@ -0,0 +1,18 @@ +{ + "name": "Fresno Pepper", + "binomial_name": "Capsicum annuum 'Fresno Chili'", + "common_names": [ + "fresno pepper", + "fresno chili" + ], + "description": "The Fresno Chili pepper is a Capiscum annuum cultivar created by Clarence Brown Hamlin in 1952. The plant produces 5-7cm long conical fruit that grow pointed upwards. As they mature, they turn from green to orange to red and the fruit droops downwards. The peppers have a Scoville rating of 2,500-10,000 units and can be substituted for jalape\u00f1o and serrano peppers in sauces, salsas, chutneys, and stews. They have a more complex flavor than jalape\u00f1os and mature faster. Fresno Chilis do not dry well and are best used fresh. Young green peppers are less hot than fully mature red ones.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 45, + "height": 75, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/fresno-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/fuji-apple.json b/frontend/crops/data/fuji-apple.json new file mode 100644 index 0000000000..64b7e15117 --- /dev/null +++ b/frontend/crops/data/fuji-apple.json @@ -0,0 +1,18 @@ +{ + "name": "Fuji Apple", + "binomial_name": "Malus pumila", + "common_names": [ + "Fuji", + "Fuji apple" + ], + "description": "Fuji apples are a hybrid developed in Fujisaki, Japan in the late 1930s by crossing Red Delicious and Virginia Ralls Genet apples. The tree produces round, medium to large, thick-skinned fruits that are red with a yellow blush and faint stripes. The apples have a crisp texture and sweetness, somewhat reminiscent of Asian pears. They can be eaten fresh, baked, roasted, or made into cider. Fuji apples have a remarkably long shelf life of nearly 12 months. The trees have a lower winter-chill requirement than most other apple varieties. Blossoms are white to pinkish. Tree requires pollination by a tree of another variety with the same bloom period within 50 feet (Gala, Golden Delicious, Jonathan, Red Delicious, Lodi, Braeburn). Apples are propagated through grafting because seeds do not breed true. Semi-dwarf and standard rootstocks are available.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant bare-root plant", + "spread": 360, + "row_spacing": 900, + "height": 360, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/fuji-apple.jpg", + "icon": "/crops/icons/fuji-apple.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/gala-apple.json b/frontend/crops/data/gala-apple.json new file mode 100644 index 0000000000..da312bc93c --- /dev/null +++ b/frontend/crops/data/gala-apple.json @@ -0,0 +1,17 @@ +{ + "name": "Gala Apple", + "binomial_name": "Malus pumila", + "common_names": [ + "Gala" + ], + "description": "Gala Apples are apple cultivars that were developed in New Zealand around 1985 by crossing Kidd's Orange Red and Golden Delicious. They produce shiny, red-striped fruit that is firm, sweet, aromatic, and juicy. They can be eaten fresh or baked. Harvest sooner for a storage life of 4 months, or let them fully ripen on the tree for a sweeter apple that won't store as long. Blossoms are white or pink. Gala apples require pollination by a tree of another variety with the same bloom period within 50 feet (Akane, Cortland, Empire, Enterprise, Fuji, Honeycrisp, Jonathan, Macoun, Red Delicious, Golden Delicious). Apples are propagated through grafting because seeds do not breed true. Semi-dwarf and standard sizes are available. Depending on the size chosen, the tree will bear fruit within 2-5 years of planting.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant bare-root plant", + "spread": 300, + "row_spacing": 300, + "height": 300, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/gala-apple.jpg", + "icon": "/crops/icons/gala-apple.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/galangal.json b/frontend/crops/data/galangal.json new file mode 100644 index 0000000000..1264d1db9a --- /dev/null +++ b/frontend/crops/data/galangal.json @@ -0,0 +1,17 @@ +{ + "name": "Galangal", + "binomial_name": "alpinia galanga", + "common_names": [ + "galangal" + ], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/galangal.jpg", + "icon": "/crops/icons/galangal.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/garlic-chives.json b/frontend/crops/data/garlic-chives.json new file mode 100644 index 0000000000..689780d36d --- /dev/null +++ b/frontend/crops/data/garlic-chives.json @@ -0,0 +1,21 @@ +{ + "name": "Garlic Chives", + "binomial_name": "Allium tuberosum", + "common_names": [ + "Garlic Chives", + "Asian chives", + "Chinese chives", + "Chinese leek", + "Oriental garlic" + ], + "description": "Garlic chives are a member of the Allium family and related to garlic and onions. They taste more like garlic than chives. Their long, thin, strap-shaped leaves grow from a small, elongated bulb. Their bulbous roots are rhizomes and the plant grows in slowly expanding clumps. Garlic chives are sometimes grown for ornamental purposes, and also have medicinal uses. The plant's leaves, stalks, and immature, unopened flower buds are used as seasoning in salads, spreads, flavored vinegars, and other dishes. They make great companion plants because they aid in pollination and can deter pests and disease. Their strong scent confuses the carrot root fly, which can normally smell carrots from up to a mile away.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct sow outdoors or sow indoors and transplant seedlings after hardening off", + "spread": 8, + "row_spacing": 8, + "height": 45, + "growing_degree_days": 75, + "companions": [], + "image": "/crops/images/garlic-chives.jpg", + "icon": "/crops/icons/garlic-chives.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/garlic.json b/frontend/crops/data/garlic.json new file mode 100644 index 0000000000..5bca3a393b --- /dev/null +++ b/frontend/crops/data/garlic.json @@ -0,0 +1,26 @@ +{ + "name": "Garlic", + "binomial_name": "allium sativum", + "common_names": [ + "garlic", + " cultivated garlic", + " ail", + " a\u00efl", + " aglio", + " ajo", + " kaliki", + " knoblauch", + " vitl\u00f6k", + " hvidl\u00f8g" + ], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/garlic.jpg", + "icon": "/crops/icons/garlic.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/generic-plant.json b/frontend/crops/data/generic-plant.json new file mode 100644 index 0000000000..e1d1c64966 --- /dev/null +++ b/frontend/crops/data/generic-plant.json @@ -0,0 +1,15 @@ +{ + "name": "Generic Plant", + "binomial_name": "", + "common_names": [], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/generic-plant.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/german-chamomile.json b/frontend/crops/data/german-chamomile.json new file mode 100644 index 0000000000..5c0ec5587b --- /dev/null +++ b/frontend/crops/data/german-chamomile.json @@ -0,0 +1,24 @@ +{ + "name": "German Chamomile", + "binomial_name": "Matricaria chamomilla, Matricaria recutita", + "common_names": [ + "German Chamomile", + "Common Chamomile", + "camomile", + "Italian camomilla", + "Hungarian chamomile", + "kamilla", + "wild chamomile", + "scented mayweed" + ], + "description": "German, or Common Chamomile is an annual plant with daisy-like flowers. It is in the Daisy family (Asteraceae) along with Roman Chamomile (Chamaemelum nobile) and the two plants have similar apple-scented flowers, but they are in separate genera. German Chamomile is taller than Roman Chamomile, growing to heights of 60cm, and produces larger flowers that are 3-5cm in diameter. It has hairless stems and is more compact than it's Roman counterpart. It's essential oil is dark blue, whereas Roman Chamomile's essential oil is pale yellow to clear. German Chamomile is the most popular source of herbal chamomile and is used to calm and nourish the nervous system and support good digestion. Chamomile attracts beneficial insects like hoverflies (which eat aphids and mites) and ladybugs. Chamomile tea can be sprayed on seedlings to prevent damping-off, a fungal infection that can kill young plants.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed, do not cover. Seeds need light to germinate.", + "spread": 30, + "row_spacing": 30, + "height": 60, + "growing_degree_days": 75, + "companions": [], + "image": "/crops/images/german-chamomile.jpg", + "icon": "/crops/icons/german-chamomile.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/ghost-pepper.json b/frontend/crops/data/ghost-pepper.json new file mode 100644 index 0000000000..fd139a7452 --- /dev/null +++ b/frontend/crops/data/ghost-pepper.json @@ -0,0 +1,15 @@ +{ + "name": "Ghost Pepper", + "binomial_name": "", + "common_names": [], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/ghost-pepper.jpg", + "icon": "/crops/icons/ghost-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/ginger.json b/frontend/crops/data/ginger.json new file mode 100644 index 0000000000..ec75af152a --- /dev/null +++ b/frontend/crops/data/ginger.json @@ -0,0 +1,17 @@ +{ + "name": "Ginger", + "binomial_name": "Zingiber officinale", + "common_names": [ + "Ginger" + ], + "description": "Ginger is a flowering, perennial, herbaceous plant native to Southern Asia\u2019s tropical rainforests. It is in the Zingiberaceae family, which also includes turmeric. Ginger is grown for its root, which is also called Ginger, and as an ornamental. The ginger root is a rhizome that produces thin annual stems with narrow green leaves and clusters of white and pink flower buds that open into yellow flowers. Ginger needs ample moisture and fertilizer and temperatures between 21.6 and 25\u00b0C. Temperatures below 9\u00b0C can kill the plant, and it will not germinate in cold soil. Ginger prefers sun in the morning and shade in the afternoon. It can be grown in containers or greenhouses in colder climates to extend the growing season. Propagate ginger by planting rhizomes or roots. It can be harvested after 4-6 months as \u201cBaby Ginger.\u201d Baby Ginger does not have a skin on it yet - instead the rhizome is pink and cream-colored, very tender and juicy, and less spicy. Mature ginger has fibrous, dry, yellow flesh with a tan skin and spicier flavor. Ginger takes 10-12 months to mature and is harvested when the stalk has withered. Ginger can be used to season dishes, steeped into a tea, or candied. Ayurveda and other traditional medicines use ginger to reduce pain and inflammation, soothe upset stomachs, and boost metabolism.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed roots outdoors", + "spread": 25, + "row_spacing": 20, + "height": 100, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/ginger.jpg", + "icon": "/crops/icons/ginger.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/golden-beet.json b/frontend/crops/data/golden-beet.json new file mode 100644 index 0000000000..7b8ac7d329 --- /dev/null +++ b/frontend/crops/data/golden-beet.json @@ -0,0 +1,19 @@ +{ + "name": "Golden Beet", + "binomial_name": "Beta vulgaris", + "common_names": [ + "Golden Beet", + "Yellow Beet", + "Beet" + ], + "description": "Golden beets have gold rather than red taproots. The veins of their leaves are yellow rather than red. They have a sweeter, milder flavor than red beets. Like red beets, their leaves can be eaten and the roots are delicious shaved raw into salads or roasted.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct sow, thin to 8cm apart when seedlings are 5cm tall", + "spread": 30, + "row_spacing": 8, + "height": 20, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/golden-beet.jpg", + "icon": "/crops/icons/golden-beet.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/golden-delicious-apple.json b/frontend/crops/data/golden-delicious-apple.json new file mode 100644 index 0000000000..018ac9c66c --- /dev/null +++ b/frontend/crops/data/golden-delicious-apple.json @@ -0,0 +1,19 @@ +{ + "name": "Golden Delicious Apple", + "binomial_name": "Malus pumila", + "common_names": [ + "golden delicious", + "golden delicious apple", + "yellow delicious" + ], + "description": "Golden Delicious apples are a milder flavored apple cultivar with thin yellow skin and crispy, sweet, pale white flesh. They can be eaten fresh or baked. Blossoms are white or pink. Trees have a chilling requirement of 600-700 hours. Golden Delicious apples are self-pollinating, but can benefit from nearby trees with the same bloom period, and are great pollinators for apple trees that don't self-pollinate. Apples are propagated through grafting because seeds do not breed true. Semi-dwarf and dwarf sizes are available. Depending on the size chosen, the tree will bear fruit within 2-5 years of planting.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant bare-root plant", + "spread": 450, + "row_spacing": 450, + "height": 450, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/golden-delicious-apple.jpg", + "icon": "/crops/icons/golden-delicious-apple.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/granny-smith-apple.json b/frontend/crops/data/granny-smith-apple.json new file mode 100644 index 0000000000..4367d51a51 --- /dev/null +++ b/frontend/crops/data/granny-smith-apple.json @@ -0,0 +1,17 @@ +{ + "name": "Granny Smith Apple", + "binomial_name": "Malus domestica \u00d7 M. sylvestris", + "common_names": [ + "Granny Smith Apple" + ], + "description": "Granny Smith apples are tip-bearing apple cultivars that were developed in Australia in 1868 by Maria Anna Smith. They are thought to be a hybrid of Malus sylvestris, the European Wild Apple, with the domestic apple M. domestica. The fruit has hard, light green skin and crisp, tart juicy flesh.They can be eaten fresh and are a very popular apple for baking. They have a longer storage life than other apples due to their low ethylene content. Blossoms are white. Trees require 400 hours below 7\u00b0C each winter. Granny Smith apples are self-pollinating but benefit from having a tree of another variety with the same bloom period within 50 feet (Rome, Akane, Cripps Pink, Golden Delicious). Apples are propagated through grafting because they are genetic hybrids that produce new genetic combinations in their seedlings. Semi-dwarf and dwarf rootstocks are available. Depending on the size chosen, the tree will bear fruit within 2-5 years of planting.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant bare-root plant", + "spread": 450, + "row_spacing": 450, + "height": 450, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/granny-smith-apple.jpg", + "icon": "/crops/icons/granny-smith-apple.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/green-anjou-pear.json b/frontend/crops/data/green-anjou-pear.json new file mode 100644 index 0000000000..bd9ed6e8bd --- /dev/null +++ b/frontend/crops/data/green-anjou-pear.json @@ -0,0 +1,20 @@ +{ + "name": "Green D'anjou Pear", + "binomial_name": "Pyrus communis 'D'Anjou'", + "common_names": [ + "anjou pear", + "green anjou", + "Nec Plus Meuris", + "Beurr\u00e9 d'Anjou" + ], + "description": "Green D'Anjou pears are the green cultivar of D'Anjou pears, which are a short-necked cultivar of European pear. D'Anjou pears are medium to large, around 80mm in diameter. They have a wide base that tapers at the stem and pale green skin that does not change color as the pear ripens. The flesh is firm, sweet, and juicy. They can be eaten fresh or baked. The tree has white blooms in the spring. \nD'Anjou pears are not self-fertile. They require pollination by a pear tree of another variety with the same bloom period within 50 feet (Bartlett, Bosc, Seckel). Standard and dwarf rootstocks are available. Depending on the size chosen, the tree will bear fruit within 4-6 years of planting. Test ripeness by pressing the top of the pear near the stem. If it gives slightly, the pear is ripe and ready for harvest.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant bare-root plant", + "spread": 300, + "row_spacing": 360, + "height": 300, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/green-anjou-pear.jpg", + "icon": "/crops/icons/green-anjou-pear.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/green-bartlett-pear.json b/frontend/crops/data/green-bartlett-pear.json new file mode 100644 index 0000000000..f7625e7dc3 --- /dev/null +++ b/frontend/crops/data/green-bartlett-pear.json @@ -0,0 +1,19 @@ +{ + "name": "Green Bartlett Pear", + "binomial_name": "Pyrus communis 'Williams'", + "common_names": [ + "Williams pear", + "Williams' Bon Chr\u00e9tien pear", + "bartlett pear" + ], + "description": "Green Bartlett pears are a cultivar of the species Pyrus communis or European Pear. Bartlett pears have a bell shape and green skin that turns yellow as it ripens after harvest. Both Green and Red Bartlett pears are medium-sized and juicy with soft flesh. They can be eaten fresh, baked, or canned. Bartlett pears are not as cold tolerant as other varieties. Their white blooms open early in the season and they are ready to harvest in late summer or early fall. The pear should still be green and relatively hard when picked. After harvest, pears take 7-10 days to ripen, during which time their flesh will soften and the skin will change to a soft yellow. Bartlett pears are not self-fertile. They require pollination by a pear tree of another variety with the same bloom period within 50 feet, such as Bosc. Standard and dwarf rootstocks are available. Standard sized trees can grow to 6 meters. Depending on the size chosen, the tree will bear fruit within 4-6 years of planting.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant bare-root plant", + "spread": 85, + "row_spacing": 300, + "height": 300, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/green-bartlett-pear.jpg", + "icon": "/crops/icons/green-bartlett-pear.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/green-bell-pepper.json b/frontend/crops/data/green-bell-pepper.json new file mode 100644 index 0000000000..55793ab7c8 --- /dev/null +++ b/frontend/crops/data/green-bell-pepper.json @@ -0,0 +1,20 @@ +{ + "name": "Green Bell Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "bell pepper", + "green bell pepper", + "sweet pepper", + "capiscum" + ], + "description": "The bell pepper is a cultivar group of the species Capsicum annuum. The fruit is often mildly sweet, because this specific cultivar does not produce capsaicin, the chemical responsible for other peppers' spiciness. Green bell peppers are unripened peppers - if left on the plant, they will turn red, orange, or yellow depending on the variety. The \"Permagreen\" variety will be green even when fully ripe. Green peppers are less sweet and slightly more bitter than yellow or orange peppers. Red bell peppers are the sweetest.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 30, + "row_spacing": 45, + "height": 50, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/green-bell-pepper.jpg", + "icon": "/crops/icons/green-bell-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/green-birds-eye-chili.json b/frontend/crops/data/green-birds-eye-chili.json new file mode 100644 index 0000000000..d4ed25e679 --- /dev/null +++ b/frontend/crops/data/green-birds-eye-chili.json @@ -0,0 +1,19 @@ +{ + "name": "Green Bird's Eye Chili", + "binomial_name": "Capsicum annuum glabriusculum", + "common_names": [ + "birds eye chili", + "chili de arbol", + "thai chili" + ], + "description": "Bird's Eye Chili, or Chili de Arbol, is a Capsicum annuum cultivar grown in Ethiopia, Southeast Asia, and parts of India. It produces small, conical fruits that are much hotter than the japaleno: their Scoville rating is between 100,000\u2013225,000 units. It is used in Vietnamese and Thai cuisine. The seeds can be difficult to germinate because of their tough coating, which is designed to be passed through birds' digestive systems. For best results, soak the seeds in plain water for 6 hours before planting and keep the germination temperature between 70-90\u00baF. Green Bird's Eye Chilis are unripened peppers. If left on the plant, they will turn red. They have an underripe, metallic, grassy flavor as compared to the red chili's more rounded and fruity flavor.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off.", + "spread": 0, + "row_spacing": 0, + "height": 200, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/green-birds-eye-chili.jpg", + "icon": "/crops/icons/green-birds-eye-chili.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/green-cabbage.json b/frontend/crops/data/green-cabbage.json new file mode 100644 index 0000000000..189019a57b --- /dev/null +++ b/frontend/crops/data/green-cabbage.json @@ -0,0 +1,20 @@ +{ + "name": "Cabbage", + "binomial_name": "Brassica oleracea", + "common_names": [ + "cabbage", + "headed cabbage" + ], + "description": "Cabbage is a member of the Brassica family and related to kale, broccoli, brussels sprouts, and cauliflower. It's dense, layered heads grow on stalks and are surrounded by looser outer leaves. It's leaves can be green, white, or purple in color, and smooth or crinkly in texture. Depending on the variety, the head can be round, oblong, or flat. Cabbage prefers cooler temperatures and is best planted in the spring or fall.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off.", + "spread": 50, + "row_spacing": 60, + "height": 45, + "growing_degree_days": 105, + "companions": [ + "blue-lake-bean" + ], + "image": "/crops/images/green-cabbage.jpg", + "icon": "/crops/icons/green-cabbage.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/green-onion.json b/frontend/crops/data/green-onion.json new file mode 100644 index 0000000000..cf43beb34c --- /dev/null +++ b/frontend/crops/data/green-onion.json @@ -0,0 +1,22 @@ +{ + "name": "Green Onion", + "binomial_name": "Allium fistulosum", + "common_names": [ + "Green Onion", + "scallion", + "bunching onion", + "spring onion", + "salad onion", + "welsh onion" + ], + "description": "Green onions have a small bulb and tall, hollow, tubular leaves. The leaves and bulbs can be eaten raw or cooked in salads, stews, soups, and other dishes. They have a milder taste than most onions. Green onions are members of the Allium family and are related to garlic, shallots, leeks, and chives.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed, thin to 8cm apart when seedlings are 5cm tall.", + "spread": 10, + "row_spacing": 8, + "height": 25, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/green-onion.jpg", + "icon": "/crops/icons/green-onion.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/green-serrano-pepper.json b/frontend/crops/data/green-serrano-pepper.json new file mode 100644 index 0000000000..4c7c4c0b41 --- /dev/null +++ b/frontend/crops/data/green-serrano-pepper.json @@ -0,0 +1,17 @@ +{ + "name": "Green Serrano Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "serrano pepper" + ], + "description": "Serrano peppers are a type of chili pepper native to Mexico. They have a bright, biting flavor and are spicier than jalapenos with a Scoville rating of 10,000 to 25,000. They are often eaten raw, or used in pico de gallo, salsa, and soups. A plant can produce up to 50 peppers 3-8cm long. Like many other peppers, Serranos can be harvested when they are green and unripe, or red and ripe.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 75, + "row_spacing": 45, + "height": 75, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/green-serrano-pepper.jpg", + "icon": "/crops/icons/green-serrano-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/green-tabasco-pepper.json b/frontend/crops/data/green-tabasco-pepper.json new file mode 100644 index 0000000000..2a8e1a6050 --- /dev/null +++ b/frontend/crops/data/green-tabasco-pepper.json @@ -0,0 +1,17 @@ +{ + "name": "Tabasco Pepper", + "binomial_name": "Capsicum frutescens", + "common_names": [ + "tabasco" + ], + "description": "Tabasco peppers are unripe, small, thin chili peppers about 5cm long. They originated in Mexico and have a Scoville rating of 30,000 to 50,000. Like other Capsicum frutescens varieties, the peppers remain upright when ripe rather than hanging (like a bell pepper). They are the only Capsicum frutescens variety that is juicy (the fruit is not dry inside like other peppers) and are famous for their use in Tabasco sauce. Like other peppers, Tabasco peppers ripen from green to red and can be harvested while green and unripe or red and ripe.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 70, + "row_spacing": 45, + "height": 75, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/green-tabasco-pepper.jpg", + "icon": "/crops/icons/green-tabasco-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/green-zebra-tomato.json b/frontend/crops/data/green-zebra-tomato.json new file mode 100644 index 0000000000..ebfb8a114f --- /dev/null +++ b/frontend/crops/data/green-zebra-tomato.json @@ -0,0 +1,17 @@ +{ + "name": "Green Zebra Tomato", + "binomial_name": "Solanum lycopersicum", + "common_names": [ + "green zebra" + ], + "description": "Green Zebra tomatoes are a tomato cultivar created by cross-breeding 4 different heirloom tomato varieties. The fruits are 4-6cm in diameter and have emerald skin with dark green vertical stripes. When ripe, the skin develops a yellow blush. Their flesh is bright green, tangy, and sweet. Green Zebras are great eaten raw or in salads. For larger yield and improved plant health, prune and stake tomatoes.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 45, + "row_spacing": 120, + "height": 100, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/green-zebra-tomato.jpg", + "icon": "/crops/icons/green-zebra-tomato.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/guajillo-pepper.json b/frontend/crops/data/guajillo-pepper.json new file mode 100644 index 0000000000..1ce57e2adf --- /dev/null +++ b/frontend/crops/data/guajillo-pepper.json @@ -0,0 +1,20 @@ +{ + "name": "Guajillo Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "Guajillo pepper", + "Guajillo chili", + "Guajillo chile", + "chile guajillo" + ], + "description": "Guajillo chiles are a Capsicum annuum variety of chili pepper. The upright plants produce 7-15mm tapered peppers that ripen from green to orange, red, or brown when mature. The peppers are mildly spicy and sweet, with a Scoville rating of 2,500-5,000 units. Guajillo means \"little gourd\" in Spanish and the peppers get their name from the rattling sound the seeds make in dried pods. Guajillo chiles are in the \"Holy Trinity of Chiles\" with ancho and pasilla peppers - the three are used to make Mexican mole sauces. Guajillo chiles can also be added to salsa or dried and made into a paste for flavoring meat.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 75, + "height": 90, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/guajillo-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/habanero-pepper.json b/frontend/crops/data/habanero-pepper.json new file mode 100644 index 0000000000..ed0055f85f --- /dev/null +++ b/frontend/crops/data/habanero-pepper.json @@ -0,0 +1,17 @@ +{ + "name": "Habanero Pepper", + "binomial_name": "Capsicum chinense", + "common_names": [ + "habanero pepper" + ], + "description": "Habaneros are a very hot chili pepper variety that have a rating of 100,000-350,000 Scoville units and a floral aroma. The fruit is green when unripe. Various cultivars turn orange, red, white, brown, yellow, or purple as they ripen. Ripe fruit are typically 2-6 cm long.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 40, + "row_spacing": 45, + "height": 50, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/habanero-pepper.jpg", + "icon": "/crops/icons/habanero-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/hemp.json b/frontend/crops/data/hemp.json new file mode 100644 index 0000000000..4fa85324a6 --- /dev/null +++ b/frontend/crops/data/hemp.json @@ -0,0 +1,18 @@ +{ + "name": "Hemp", + "binomial_name": "Cannabis sativa", + "common_names": [ + "hemp", + "industrial hemp" + ], + "description": "Hemp is a variety of Cannabis sativa that is grown for industrial uses. It can be made into paper, textiles, clothing, rope, food, animal feed, biofuel, and biodegradable plastics. Hemp has a lower concentration of THC and a higher concentration of CBD than Marijuana (the other dominant variety of Cannabis sativa), which means it has little to no psychoactive effects. Despite this, the legality of hemp cultivation varies throughout the U.S. Hemp is low maintenance, fast growing, and an extremely efficient weed suppressor. It uses significantly less water and land than cotton to produce similar textile yields.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors", + "spread": 450, + "row_spacing": 450, + "height": 350, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/hemp.jpg", + "icon": "/crops/icons/hemp.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/hillbilly-tomato.json b/frontend/crops/data/hillbilly-tomato.json new file mode 100644 index 0000000000..df291b1619 --- /dev/null +++ b/frontend/crops/data/hillbilly-tomato.json @@ -0,0 +1,23 @@ +{ + "name": "Hillbilly Tomato", + "binomial_name": "Solanum lycopersicum", + "common_names": [ + "hillbilly tomato", + "flame tomato", + "hillbilly potato leaf tomato" + ], + "description": "Hillbilly, or Flame, tomatoes are large heirloom cultivars. The fruits are 1-2 pounds, round, heavily ribbed, and have brilliant yellow to orange skin with red marbling. They are juicy and have a rich, sweet flavor that is low in acid. They are usually eaten raw - sliced or added to sandwiches. For larger yield and improved plant health, prune and stake tomatoes.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 120, + "row_spacing": 90, + "height": 120, + "growing_degree_days": 0, + "companions": [ + "borage", + "carrot", + "mint" + ], + "image": "", + "icon": "/crops/icons/hillbilly-tomato.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/honeydew-melon.json b/frontend/crops/data/honeydew-melon.json new file mode 100644 index 0000000000..522ee1d64b --- /dev/null +++ b/frontend/crops/data/honeydew-melon.json @@ -0,0 +1,19 @@ +{ + "name": "Honeydew Melon", + "binomial_name": "Cucumis melo", + "common_names": [ + "Honeydew", + "honeymelon", + "Bailan melon" + ], + "description": "Honeydew melons have a round to slightly oval shape. Like other melons, they grow on vines. Their sweet, tender flesh is pale green or orange in color, and the smooth peel ranges from greenish to yellow.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed", + "spread": 150, + "row_spacing": 90, + "height": 30, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/honeydew-melon.jpg", + "icon": "/crops/icons/honeydew-melon.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/hop.json b/frontend/crops/data/hop.json new file mode 100644 index 0000000000..5d4992eddb --- /dev/null +++ b/frontend/crops/data/hop.json @@ -0,0 +1,17 @@ +{ + "name": "Common Hop", + "binomial_name": "Humulus lupulus", + "common_names": [ + "common hop" + ], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/hop.jpg", + "icon": "/crops/icons/hop.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/hungarian-wax-pepper.json b/frontend/crops/data/hungarian-wax-pepper.json new file mode 100644 index 0000000000..4ecb262f32 --- /dev/null +++ b/frontend/crops/data/hungarian-wax-pepper.json @@ -0,0 +1,19 @@ +{ + "name": "Hungarian Wax Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "Hungarian Wax pepper" + ], + "description": "Hungarian Wax Peppers are a Capsicum annuum cultivar that produce 10-15cm long, tapered peppers with a broad Scoville rating of 1,000-15,000 units. Peppers have smooth, waxy skin and mature from green to orange to red. They resemble banana peppers when immature and are often harvested before maturity. Peppers are frequently pickled, stuffed, roasted, and fried.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 40, + "row_spacing": 45, + "height": 60, + "growing_degree_days": 0, + "companions": [ + "parsley" + ], + "image": "", + "icon": "/crops/icons/hungarian-wax-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/iceberg-lettuce.json b/frontend/crops/data/iceberg-lettuce.json new file mode 100644 index 0000000000..d43f57f750 --- /dev/null +++ b/frontend/crops/data/iceberg-lettuce.json @@ -0,0 +1,15 @@ +{ + "name": "Iceberg Lettuce", + "binomial_name": "Lactuca sativa", + "common_names": [], + "description": "Iceberg lettuce grows in firm, round, glossy green heads. The interior is white and has a juicy, mild flavor. It is most often used in salads. Lettuce is cold-hardy. High temperatures will impede germination and/or cause the plant to bolt (go to seed quickly). Some hybrid cultivars have been bred to be more heat-resistant.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed", + "spread": 15, + "row_spacing": 25, + "height": 30, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/iceberg-lettuce.jpg", + "icon": "/crops/icons/iceberg-lettuce.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/icicle-radish.json b/frontend/crops/data/icicle-radish.json new file mode 100644 index 0000000000..724d168dbd --- /dev/null +++ b/frontend/crops/data/icicle-radish.json @@ -0,0 +1,17 @@ +{ + "name": "Icicle Radish", + "binomial_name": "Raphanus sativus", + "common_names": [ + "Icicle Radish" + ], + "description": "Icicle radishes have long, slender, snow-white roots and green leaves. The roots have a crisp flesh with a mild flavor. Radishes grow best in cool weather. Often eaten raw in salads.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed", + "spread": 10, + "row_spacing": 2, + "height": 20, + "growing_degree_days": 25, + "companions": [], + "image": "/crops/images/icicle-radish.jpg", + "icon": "/crops/icons/icicle-radish.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/indigo.json b/frontend/crops/data/indigo.json new file mode 100644 index 0000000000..8cf58e1ca3 --- /dev/null +++ b/frontend/crops/data/indigo.json @@ -0,0 +1,18 @@ +{ + "name": "Indigo", + "binomial_name": "Indigofera tinctoria / Indigofera suffruticosa", + "common_names": [ + "Indigo", + "true indigo" + ], + "description": "Indigo is a species of plant from the bean family that was one of the original sources of indigo dye. It is a shrub that grows 1-2 meters high. Depending on the climate, it can be an annual, biennial, or perennial. Because it is a legume, Indigo can also be grown to improve soil conditions. If growing the plant for dye, harvest before the flowers open. The dye is obtained by soaking and fermenting the plant's leaves.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors", + "spread": 0, + "row_spacing": 0, + "height": 150, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/indigo.jpg", + "icon": "/crops/icons/indigo.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/jalapeno.json b/frontend/crops/data/jalapeno.json new file mode 100644 index 0000000000..73f4cfaf44 --- /dev/null +++ b/frontend/crops/data/jalapeno.json @@ -0,0 +1,21 @@ +{ + "name": "Jalapeno Pepper", + "binomial_name": "Capsicum annum", + "common_names": [ + "Jalapeno", + "huachinango", + "chile gordo", + "cuaresme\u00f1o", + "xalapa" + ], + "description": "Small hot pepper, about 2.5 x 7.5cm, medium spiciness. Can be harvested green (when the fruit is younger and milder) or left on the plant longer until it turns red and becomes hotter and sweeter.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, harden seedlings off before transplanting outside.", + "spread": 45, + "row_spacing": 45, + "height": 40, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/jalapeno.jpg", + "icon": "/crops/icons/jalapeno.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/japanese-yam.json b/frontend/crops/data/japanese-yam.json new file mode 100644 index 0000000000..fb4598b883 --- /dev/null +++ b/frontend/crops/data/japanese-yam.json @@ -0,0 +1,22 @@ +{ + "name": "Japanese Yam", + "binomial_name": "Dioscorea japonica", + "common_names": [ + "Japanese Yam", + "mountain yam", + "satsuma imo", + "kotobuki", + "East Asian mountain yam", + "yamaimo" + ], + "description": "The Japanese yam is a roughly oblong tuber. It has a thin, rusted red colored skin and cream colored, densely textured flesh. It's flesh is dry, starchy, and mildly sweet. The skin is bitter like the roots and should be peeled before eating. The rest of the plant is a climbing vine that has opposite, smooth, shiny leaves and white flowers. \n\nYams should not be confused with sweet potatoes, even though the two words are often used as synonyms. Yams belong to the Dioscoreaceae family, while sweet potatoes are in the Convolvulaceae family.", + "sun_requirements": "Full Sun", + "sowing_method": "Slips", + "spread": 0, + "row_spacing": 30, + "height": 20, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/japanese-yam.jpg", + "icon": "/crops/icons/japanese-yam.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/kabocha-squash.json b/frontend/crops/data/kabocha-squash.json new file mode 100644 index 0000000000..35c50999a2 --- /dev/null +++ b/frontend/crops/data/kabocha-squash.json @@ -0,0 +1,21 @@ +{ + "name": "Kabocha Squash", + "binomial_name": "Cucurbita maxima", + "common_names": [ + "Kabocha Squash", + "Squash" + ], + "description": "Kabocha squash has a hard, knobbly skin that is deep green or orange with some light green to white stripes. It's shaped like a squat pumpkin and has wonderfully sweet, deep orange flesh. The taste and texture of it's flesh is like a combination of pumpkin and sweet potato. It is delicious roasted, and in curries, soups, and other dishes.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed. For shorter seasons, direct seed indoors in peat pots and transplant pots outdoors when soil is warm.", + "spread": 140, + "row_spacing": 90, + "height": 25, + "growing_degree_days": 120, + "companions": [ + "borage", + "corn" + ], + "image": "/crops/images/kabocha-squash.jpg", + "icon": "/crops/icons/kabocha-squash.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/kohlrabi.json b/frontend/crops/data/kohlrabi.json new file mode 100644 index 0000000000..db77bad703 --- /dev/null +++ b/frontend/crops/data/kohlrabi.json @@ -0,0 +1,21 @@ +{ + "name": "Kohlrabi", + "binomial_name": "Brassica oleracea var. gongylodes", + "common_names": [ + "Kohlrabi", + "German turnip", + "turnip cabbage" + ], + "description": "Kohlrabi is a low, stout cultivar of cabbage that has a swollen, almost spherical stem with leaves sprouting off the top. The stem can be light green, white, or purple. Both the stem and leaves are edible. The stem's taste and texture is similar to a broccoli stem or cabbage heart, but milder and sweeter. Younger stems can be as crisp and juicy as apples, but much less sweet. Older stems tend to be woody. Like other members of the Brassica family, kohlrabi prefers mild, cool weather.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed, thin to 10cm apart when seedlings are 2-5cm tall", + "spread": 30, + "row_spacing": 10, + "height": 20, + "growing_degree_days": 700, + "companions": [ + "german-chamomile" + ], + "image": "/crops/images/kohlrabi.jpg", + "icon": "/crops/icons/kohlrabi.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/lacinato-kale.json b/frontend/crops/data/lacinato-kale.json new file mode 100644 index 0000000000..20fba964d3 --- /dev/null +++ b/frontend/crops/data/lacinato-kale.json @@ -0,0 +1,32 @@ +{ + "name": "Lacinato Kale", + "binomial_name": "Brassica oleracea", + "common_names": [ + "Lacinato", + "Dinosaur kale", + "Italian kale", + "Laminate kale", + "Tuscan kale", + "Cavolo Nero", + "Italian Kale", + "Dino Kale", + "laminate kale", + "Lacinato Kale", + "Tuscan Kale", + "Black Kale", + "nero de toscano" + ], + "description": "Lacinato, Dinosaur, Tuscan, or Italian Kale has very dark green or blue-green leaves that are heavily crinkled or blistered (savoyed) instead of curled. The leaves have a rich, tender taste and are softer than curly green kales. Can be harvested at baby or adult stage. Delicious raw or cooked. Light frosts increase the sweetness of the leaves.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors or transplant seedlings", + "spread": 75, + "row_spacing": 45, + "height": 75, + "growing_degree_days": 0, + "companions": [ + "dill", + "thyme" + ], + "image": "/crops/images/lacinato-kale.jpg", + "icon": "/crops/icons/lacinato-kale.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/lavender.json b/frontend/crops/data/lavender.json new file mode 100644 index 0000000000..97dd145822 --- /dev/null +++ b/frontend/crops/data/lavender.json @@ -0,0 +1,19 @@ +{ + "name": "Lavender", + "binomial_name": "Lavandula angustifolia", + "common_names": [ + "Lavender" + ], + "description": "Lavender has an easily recognizable scent and purple to blue flowers that grow in whorls. Leaf shape varies across the genus. It is a natural repellant of moths, slugs, and deer, and a great companion for any plant that suffers from these pests (such as kale, cabbage, and other members of the Brassicaceae family).", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors", + "spread": 50, + "row_spacing": 30, + "height": 70, + "growing_degree_days": 110, + "companions": [ + "purple-cauliflower" + ], + "image": "/crops/images/lavender.jpg", + "icon": "/crops/icons/lavender.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/leek.json b/frontend/crops/data/leek.json new file mode 100644 index 0000000000..1c8f3132e7 --- /dev/null +++ b/frontend/crops/data/leek.json @@ -0,0 +1,23 @@ +{ + "name": "Leek", + "binomial_name": "Allium porrum", + "common_names": [ + "Leek", + "Prei", + "Poireau", + "Porree", + "Blauwgroene winter" + ], + "description": "The leek is a vegetable, a cultivar of Allium ampeloprasum, the broadleaf wild leek. The edible part of the plant is a bundle of leaf sheaths that is sometimes erroneously called a stem or stalk. Historically, many scientific names were used for leeks, but they are now all treated as cultivars of A. ampeloprasum. The name 'leek' developed from the Anglo-Saxon word \"leac\". Two closely related vegetables, elephant garlic and kurrat, are also cultivars of A. ampeloprasum, although different in their uses as food. The onion and garlic are also related, being other species of the genus Allium.\nLeeks have thick blue-green foliage. The bundle of white leaf sheaths has a mild onion taste and can be blanched, steamed, braised, or grilled and used in soups, stews, omelet fillings, and more. The leaves can be used to make stock.\n\nPlant their leeks in autumn, and they should fatten up in time for winter picking. Plant them early to ensure they have enough time to grow before winter. Leeks take up to six months to mature after transplanting.\n\nNewer cultivars have quicker maturity \u2013 three to four months. Maturity is often affected by temperature, available nutrients and water. \n\nLeeks need a soil that is rich in organic matter. Dig in compost or manure two weeks before planting. Add fertiliser every few weeks\nLeeks like aged manure, especially chicken manure, and worm castings, another excellent source of nutrients.\n\nPlant seedlings in full sun, with moist but well-drained deep soil. Raised beds are ideal. Water young plants frequently.\n\nThe white part of the leek is edible; the green is not. You can blanch the stems to increase the proportion of stem that\u2019s edible and to sweeten the taste. Do this to fully grown leeks. Tie a paper collar around each stem and gently hill the earth up around the stem. Be careful not to get soil between the paper collar and stem as the leek may rot.\nAs the plants grow, add another collar above the first one and hill up more soil. Be aware that slugs and snails can hide in the paper collars\n\nHarvest the leeks when stems are around 2.5cm in diameter. Dig carefully around the leek and lift with a garden fork. Do not pull, as the leek is likely to break.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed or transplant seedlings", + "spread": 30, + "row_spacing": 15, + "height": 60, + "growing_degree_days": 140, + "companions": [ + "spinach" + ], + "image": "/crops/images/leek.jpg", + "icon": "/crops/icons/leek.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/lemon-balm.json b/frontend/crops/data/lemon-balm.json new file mode 100644 index 0000000000..9ad8399f25 --- /dev/null +++ b/frontend/crops/data/lemon-balm.json @@ -0,0 +1,21 @@ +{ + "name": "Lemon Balm", + "binomial_name": "Melissa officinalis", + "common_names": [ + "Lemon balm", + "mint balm", + "common balm" + ], + "description": "Lemon Balm is a perennial herb in the mint family that flowers during the summer and gives off a strong lemon scent. In hot climates, a little shade can be beneficial, helping the plant to produce larger and more flavorful leaves. Lemon Balm is used in teas, salads, as an ornamental, and in aromatherapy. Like mint, Lemon Balm can aggressively spread throughout the garden - planting it in a container can keep this in check.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off.", + "spread": 50, + "row_spacing": 30, + "height": 70, + "growing_degree_days": 70, + "companions": [ + "rosemary" + ], + "image": "/crops/images/lemon-balm.jpg", + "icon": "/crops/icons/lemon-balm.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/lemon-drop-pepper.json b/frontend/crops/data/lemon-drop-pepper.json new file mode 100644 index 0000000000..53598f5bfd --- /dev/null +++ b/frontend/crops/data/lemon-drop-pepper.json @@ -0,0 +1,19 @@ +{ + "name": "Lemon Drop Pepper", + "binomial_name": "Capsicum baccatum 'Lemon drop'", + "common_names": [ + "aj\u00ed limo", + "lemon drop pepper", + "qillu uchu" + ], + "description": "The Lemon Drop pepper is a Capsicum baccatum cultivar that produces high yields of 60mm long, cone-shaped, slightly crinkled peppers that are yellow when ripe. Peppers have a spicy, citrus-like, lemon flavor and a Scoville rating of 15,000-30,000 units. They are popular in Peruvian cuisine.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 0, + "height": 200, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/lemon-drop-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/lemon-verbena.json b/frontend/crops/data/lemon-verbena.json new file mode 100644 index 0000000000..d93fce54d6 --- /dev/null +++ b/frontend/crops/data/lemon-verbena.json @@ -0,0 +1,19 @@ +{ + "name": "Lemon Verbena", + "binomial_name": "Aloysia citriodora", + "common_names": [ + "lemon verbena", + "vervain", + "verbena" + ], + "description": "Lemon verbena is a deciduous, perennial plant or shrub grown for it's culinary, medicinal, and cosmetic uses. The plant has glossy, pointed leaves that have a strong lemon fragrance when bruised. In the late spring or early summer, Lemon Verbena produces tiny purple or white flowers. It is cold-sensitive and will lose leaves below 4.4\u00b0 C and go dormant. Wood is hardy to -10\u00b0 C. The plant can be grown in containers and brought indoors in regions with colder winters. The leaves can be used fresh or dried to add a lemon flavor to fish, poultry, salad dressings, puddings, yogurt, and beverages. The flavor is less intense when the leaves are dried. Leaves are steeped into teas and used in perfumes. Lemon Verbena has also been used to treat Candida yeast infections.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors (transplant seedlings outside after hardening off) or use plugs or cuttings", + "spread": 120, + "row_spacing": 45, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/lemon-verbena.jpg", + "icon": "/crops/icons/lemon-verbena.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/lemongrass.json b/frontend/crops/data/lemongrass.json new file mode 100644 index 0000000000..49f2ecc35a --- /dev/null +++ b/frontend/crops/data/lemongrass.json @@ -0,0 +1,25 @@ +{ + "name": "Lemongrass", + "binomial_name": "Cymbopogon citratus", + "common_names": [ + "Lemongrass", + "barbed wire grass", + "silky heads", + "citronella grass", + "cha de Dartigalongue", + "fever grass", + "tanglad", + "hierba Luisa", + "gavati chahapati" + ], + "description": "Lemongrass is a grass-like plant with long thin leaves and stalks with bulbous bases. It is used as a culinary herb in Asian cuisines and as a medicinal herb in India. It has a subtle citrus flavor and can be used fresh or dried and powdered. It is often used in teas, soups, and curries, and to season poultry, fish, beef, and seafood. It's oil has anti-fungal properties and is used as a pesticide and preservative.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed or transplant seedlings", + "spread": 140, + "row_spacing": 30, + "height": 100, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/lemongrass.jpg", + "icon": "/crops/icons/lemongrass.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/lima-bean.json b/frontend/crops/data/lima-bean.json new file mode 100644 index 0000000000..f2ad33fa38 --- /dev/null +++ b/frontend/crops/data/lima-bean.json @@ -0,0 +1,20 @@ +{ + "name": "Lima Bean", + "binomial_name": "Phaseolus lunatus", + "common_names": [ + "Lima Bean", + "butter bean", + "baby lima", + "sieva bean" + ], + "description": "Lima beans have bush and pole varieties. Bush types produce more quickly and create smaller beans. Pole types can grow to 3m high and produce larger seeds. Like all legumes, lima beans can benefit from inoculants. Lima beans are shelled and cooked before eating.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors after soil is at least 24\u00b0C, thin to 30cm apart when seedlings are 5cm tall", + "spread": 25, + "row_spacing": 30, + "height": 40, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/lima-bean.jpg", + "icon": "/crops/icons/lima-bean.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/lions-mane-mushroom.json b/frontend/crops/data/lions-mane-mushroom.json new file mode 100644 index 0000000000..a8f8e0efef --- /dev/null +++ b/frontend/crops/data/lions-mane-mushroom.json @@ -0,0 +1,22 @@ +{ + "name": "Lion's Mane", + "binomial_name": "Hericium erinaceus", + "common_names": [ + "lion's mane mushroom", + "bearded tooth mushroom", + "satyr's beard", + "bearded hedgehog mushroom", + "pom pom mushroom", + "bearded tooth fungus" + ], + "description": "Lion's Mane is an edible and medicinal mushroom in the Tooth Fungus group. It is Native to North America, Europe and Asia, and grows in a single clump of cascading long white spines. Lion's Mane can be grown by obtaining spores and inoculating a substrate like sawdust or hardwood logs. If using for culinary purposes, harvest the mushroom when young. The texture of the cooked mushroom is often compared to seafood, and some feel it's taste resembles lobster. Lion's Mane is used in Traditional Chinese Medicine, and it's anti-dementia properties are currently being researched.", + "sun_requirements": "", + "sowing_method": "Inoculate fresh logs or sawdust with spores", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/lions-mane-mushroom.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/looseleaf-lettuce.json b/frontend/crops/data/looseleaf-lettuce.json new file mode 100644 index 0000000000..61bea1ba49 --- /dev/null +++ b/frontend/crops/data/looseleaf-lettuce.json @@ -0,0 +1,22 @@ +{ + "name": "Looseleaf Lettuce", + "binomial_name": "Lactuca sativa", + "common_names": [ + "looseleaf", + "loose leaf", + "salad mix", + "salad greens", + "lettuce mix", + "lettuce blend" + ], + "description": "Looseleaf lettuces are fast growing and don't form tight heads. This makes them ideal for growing as salad mix. Seed mixes often include a variety of lettuce types that have different colors and textures. Looseleaf lettuce is harvested by cutting the outer leaves or by cutting the entire plant in a \"cut-and-come-again\" method. Lettuce is generally a cool-weather crop, but some varieties are heat-tolerant.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed outdoors, thin to 15cm apart when seedlings are 3cm tall", + "spread": 25, + "row_spacing": 25, + "height": 25, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/looseleaf-lettuce.jpg", + "icon": "/crops/icons/looseleaf-lettuce.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/lovage.json b/frontend/crops/data/lovage.json new file mode 100644 index 0000000000..4fcd42666e --- /dev/null +++ b/frontend/crops/data/lovage.json @@ -0,0 +1,17 @@ +{ + "name": "Lovage", + "binomial_name": "Levisticum officinale", + "common_names": [ + "Lovage" + ], + "description": "Lovage is a perennial herb that tastes like an intensified cross between celery and parsley with a hint of anise. A member of Apiaceae family, it is related to carrots and celery. The leaves, roots, and young stems are edible. Leaves and hollow, thick stems can be added to salads, soups, and poultry dishes. The seeds can be used as a spice similar to fennel seeds. Lovage is also used as a companion crop because it's flowers attract beneficial insects like bees, hoverflies, lacewing larva, lady beetles, and parasitic wasps. Leaves can be harvested the first year, whole stems the second year.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed (indoors in early spring, outdoors in late spring or fall)", + "spread": 80, + "row_spacing": 60, + "height": 180, + "growing_degree_days": 90, + "companions": [], + "image": "/crops/images/lovage.jpg", + "icon": "/crops/icons/lovage.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/madame-jeanette-pepper.json b/frontend/crops/data/madame-jeanette-pepper.json new file mode 100644 index 0000000000..4b2f530bf5 --- /dev/null +++ b/frontend/crops/data/madame-jeanette-pepper.json @@ -0,0 +1,18 @@ +{ + "name": "Madame Jeannette Pepper", + "binomial_name": "Capsicum chinense", + "common_names": [ + "Suriname Yellow", + "madame jeannette" + ], + "description": "The Madame Jeannette Pepper is a chili pepper cultivar originally from Suriname. The compact plant produces peppers that are a bit larger than habaneros and have curvy, erratic shapes. They can be thin, curved, and slightly wrinkled, or pumpkin-shaped, or look like slightly elongated bell peppers. Peppers ripen from green to yellow. They have a Scoville rating of 125,000 - 325,000 and a fruity taste with hints of mango and pineapple beneath all the spiciness. Like other hot peppers, Madame Jeannettes dislike cool weather. They can be grown indoors in containers. Madame Jeannettes are sometimes confused with Adjuma pepper, which are a bit more round and habanero-shaped. They are used widely in Surinamese and Antillean cuisine.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside or to container after hardening off", + "spread": 0, + "row_spacing": 60, + "height": 60, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/madame-jeanette-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/malagueta-pepper.json b/frontend/crops/data/malagueta-pepper.json new file mode 100644 index 0000000000..4601366367 --- /dev/null +++ b/frontend/crops/data/malagueta-pepper.json @@ -0,0 +1,18 @@ +{ + "name": "Malagueta Pepper", + "binomial_name": "Capsicum frutescens 'Malagueta'", + "common_names": [ + "malaguetinha", + "piri piri" + ], + "description": "The Malagueta is a Capsicum frutescens cultivar widely grown in Brazil, Portugal, Mozambique, and the Caribbean. The plant produces small, tapered peppers 5cm in length that are green when immature, and yellow, orange, or red when ripe. Peppers grow upright instead of hanging like a jalapeno or bell pepper. Peppers are quite spicy with a Scoville rating of 60,000100,000 units. The peppers are often available in two different sizes: the smaller, younger ones are called malaguetinha or piri piri, and the larger, mature ones are called malagueta. Malaguetas require long, hot summers. They can be grown in containers and moved indoors before a frost in colder regions.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 45, + "height": 75, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/malagueta-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/marjoram.json b/frontend/crops/data/marjoram.json new file mode 100644 index 0000000000..5dea548080 --- /dev/null +++ b/frontend/crops/data/marjoram.json @@ -0,0 +1,20 @@ +{ + "name": "Marjoram", + "binomial_name": "Origanum majorana", + "common_names": [ + "marjoram", + "sweet marjoram", + "knotted marjoram", + "pot marjoram" + ], + "description": "Marjoram is a somewhat cold-sensitive annual or perennial herb that is a sub-species of oregano and a member of the Mint family (Lamiaceae). Marjoram is often confused with oregano, but it has slightly more pointed leaves that tend to fold inwards a bit. Marjoram has a mild, floral, woodsy flavor, while oregano is more pungent and spicy. The two herbs should not be substituted for one another in recipes. Marjoram can be grown in containers and brought indoors to protect it from frosts.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed. If planting indoors, harden off before transplanting seedlings outside.", + "spread": 25, + "row_spacing": 20, + "height": 40, + "growing_degree_days": 75, + "companions": [], + "image": "/crops/images/marjoram.jpg", + "icon": "/crops/icons/marjoram.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/marshmallow.json b/frontend/crops/data/marshmallow.json new file mode 100644 index 0000000000..e75ca4619b --- /dev/null +++ b/frontend/crops/data/marshmallow.json @@ -0,0 +1,21 @@ +{ + "name": "Marshmallow", + "binomial_name": "Althaea officinalis", + "common_names": [ + "marshmallow plant", + "marshmallow", + "marsh mallow", + "common marshmallow", + "marsh-mallow" + ], + "description": "Marshmallow is a perennial plant indigenous to Europe, Western Asia, and North Africa. It is used in herbal medicine and as an ornamental. The original marshmallow candy was made from it's roots. It likes rich, moist soil and grows well in marshes and grasses near lakes and rivers. It's tall stems that can grow to 1.2-2m, with a few lateral branches and soft, velvety leaves. Marshmallow will produce large, trumpet-shaped white to pink flowers in it's second year. The flowers resemble Hollyhocks, the plant's showier cousin. The flat, round fruit are known as \"cheeses.\" The flowers and young leaves can be eaten in salads, brewed as a tea for bronchitis and cough, or made into soothing poultices for skin irritations and bruising. The roots contain 30% mucilage that can soothe the mucous membranes and digestive tract. It may take a few years for the roots to grow large enough to be harvested.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed outdoors in early fall. In early spring, direct seed indoors, stratifying seeds.", + "spread": 0, + "row_spacing": 30, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/marshmallow.jpg", + "icon": "/crops/icons/marshmallow.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/medusa-pepper.json b/frontend/crops/data/medusa-pepper.json new file mode 100644 index 0000000000..ca67a912e3 --- /dev/null +++ b/frontend/crops/data/medusa-pepper.json @@ -0,0 +1,18 @@ +{ + "name": "Medusa Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "medusa pepper", + "christmas pepper" + ], + "description": "Medusa peppers are an ornamental Capsicum annuum cultivar that produces 40-50 upright, twisted fruit that look like Medusa's twisted hair of snakes. Fruit are 5.1cm long, slightly curved, and ripen from green to ivory, yellow, orange, and red. Unlike many other ornamental peppers, Medusa peppers are very mild and sweet, with a Scoville rating of 1-1000 units. Plants are compact and grow well in containers.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 90, + "height": 40, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/medusa-pepper.jpg", + "icon": "/crops/icons/medusa-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/melon.json b/frontend/crops/data/melon.json new file mode 100644 index 0000000000..dc0486cc07 --- /dev/null +++ b/frontend/crops/data/melon.json @@ -0,0 +1,18 @@ +{ + "name": "Melon", + "binomial_name": "Cucumis melo", + "common_names": [ + "melon", + "muskmelon" + ], + "description": "Melons are a group of species in the Cucurbitaceae family. They produce sweet fruits that grow on sprawling vines. Types of melons include: watermelons, cantaloupe, honeydew, casaba, and crenshaw. More growing information is available in individual species entries.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed. If planting indoors, use peat pots and harden off before transplanting seedlings outside.", + "spread": 130, + "row_spacing": 120, + "height": 40, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/melon.jpg", + "icon": "/crops/icons/melon.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/millet.json b/frontend/crops/data/millet.json new file mode 100644 index 0000000000..f6b4455ac9 --- /dev/null +++ b/frontend/crops/data/millet.json @@ -0,0 +1,20 @@ +{ + "name": "Millet", + "binomial_name": "Panicum miliaceum", + "common_names": [ + "Sorghum", + "Millet", + "proso", + "broomcorn" + ], + "description": "Millets are a group of small-seeded grasses grown as cereal grains. Millet has a mild, nutty flavor and contains more essential amino acids than wheat, oats, rice, barley, and rye. Millets are heat and drought-tolerant and can grow in relatively poor soils. They are quick growing (they can be harvested in as little as 30 days after planting), have good insect resistance, and are relatively free of disease. Millets are grown for human consumption, as cover crops, and for forage, feed, and hay for livestock. The most widely grown millet is Pearl Millet (Pennisetum glaucum). In the United States, Proso Millet (Panicum miliaceum) is most commonly grown for food. Proso produces seeds the size of peppercorns that can be red, yellow, or white. Millet can be cooked as a whole grain and added to salads or soups, baked, ground into flour, or eaten as porridge.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed: broadcast or drill", + "spread": 0, + "row_spacing": 0, + "height": 90, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/millet.jpg", + "icon": "/crops/icons/millet.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/mint.json b/frontend/crops/data/mint.json new file mode 100644 index 0000000000..8fc380efb5 --- /dev/null +++ b/frontend/crops/data/mint.json @@ -0,0 +1,20 @@ +{ + "name": "Mint", + "binomial_name": "Mentha spicata / Mentha viridis", + "common_names": [ + "Mint", + "spearmint" + ], + "description": "Mint is a perennial herb with a distinctive taste. It's stems are square-shaped and it has pink, purple, or white flowers. Peppermint, ginger mint, and large apple mint are hybrids of mint. Mint is often grown in pots to prevent it from overtaking the garden with its invasive, spreading rhizome root structure. It's leaves are most aromatic before the plant flowers and can be used fresh, dried, or frozen. Mint's strongly scented leaves confuse the pests of carrots, tomatoes, alliums, and brassicas, and deter flea beetles.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed indoors or outside", + "spread": 75, + "row_spacing": 35, + "height": 60, + "growing_degree_days": 80, + "companions": [ + "green-zebra-tomato" + ], + "image": "/crops/images/mint.jpg", + "icon": "/crops/icons/mint.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/money-tree.json b/frontend/crops/data/money-tree.json new file mode 100644 index 0000000000..f840475f8b --- /dev/null +++ b/frontend/crops/data/money-tree.json @@ -0,0 +1,26 @@ +{ + "name": "Money Tree", + "binomial_name": "Pachira aquatica", + "common_names": [ + "Money Tree", + "Skrilla", + "Malabar chestnut", + "French peanut", + "Guiana chestnut", + "provision tree", + "saba nut", + "monguba (Brazil)", + "pumpo (Guatemala)", + "money plant" + ], + "description": "The Money Tree is a tropical wetland tree of the mallow family Malvaceae. It is native to Central and South America where it grows in swamps to heights of up to 18m. The tree's flashy flowers have long petals that open like banana peels to reveal yellow-orange stamens. The nuts of the tree grow in a large, woody pod and are edible. They taste similar to peanuts and can be eaten raw, cooked, or ground into flour to make bread. A common houseplant, the tree is sometimes sold with a braided trunk. Money Tree houseplants are often a similar species, P. glabra, which has a smaller growth habit, less showy flowers, and smaller green seed pods. Money Trees grown in containers should be repotted every 2 years. The Money Tree may have gotten it's name from a story about a poor man who prayed for money and then encountered this plant, took it home, and made money selling plants grown from it's seeds.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed or cutting", + "spread": 0, + "row_spacing": 0, + "height": 1800, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/money-tree.jpg", + "icon": "/crops/icons/money-tree.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/mugwort.json b/frontend/crops/data/mugwort.json new file mode 100644 index 0000000000..a62e705b93 --- /dev/null +++ b/frontend/crops/data/mugwort.json @@ -0,0 +1,15 @@ +{ + "name": "Mugwort", + "binomial_name": "Artemisia", + "common_names": [], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/mugwort.jpg", + "icon": "/crops/icons/mugwort.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/mushroom.json b/frontend/crops/data/mushroom.json new file mode 100644 index 0000000000..f08253f4c6 --- /dev/null +++ b/frontend/crops/data/mushroom.json @@ -0,0 +1,15 @@ +{ + "name": "Mushroom", + "binomial_name": "", + "common_names": [], + "description": "Mushrooms are the fleshy, spore-bearing, fruiting body of a fungus. Mushrooms often have a stem, a cap, and gills on the underside of the cap - but not always. Commonly cultivated mushrooms include Oyster (the easiest and best for beginners), Shittake, King Stropharia or Wine Cap, Maitake, Lion's Mane, and Reishi. Mushrooms are generally grown by obtaining spores and inoculating a substrate (compost, cardboard, etc. Different types prefer different substrates) with the spores. Mushrooms can be grown indoors or outdoors, but it is often easier to cultivate them indoors because the light and moisture can be more thoroughly controlled. More growing information is available in individual species entries.", + "sun_requirements": "Full Shade", + "sowing_method": "Sprinke spores on surface of compost and peat moss, gently mix in, and press down.", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/mushroom.jpg", + "icon": "/crops/icons/mushroom.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/napa-cabbage.json b/frontend/crops/data/napa-cabbage.json new file mode 100644 index 0000000000..dac670b484 --- /dev/null +++ b/frontend/crops/data/napa-cabbage.json @@ -0,0 +1,18 @@ +{ + "name": "Napa Cabbage", + "binomial_name": "Brassica rapa subsp. Pekinensis", + "common_names": [ + "Napa cabbage", + "Chinese cabbage" + ], + "description": "Napa is one of two subgroups of Chinese Cabbage (the other being Bok choy) that creates a dense, oblong-shaped head with crinkly, thick, light-green leaves. Napa Cabbage can be eaten raw in salads, cooked in stir-fry, or used in kimchi. It is a cool weather crop and can also be used as a companion plant \"trap crop\" to attract pests away from other crops.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors or outside", + "spread": 45, + "row_spacing": 45, + "height": 45, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/napa-cabbage.jpg", + "icon": "/crops/icons/napa-cabbage.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/nectarine.json b/frontend/crops/data/nectarine.json new file mode 100644 index 0000000000..08122f4bf5 --- /dev/null +++ b/frontend/crops/data/nectarine.json @@ -0,0 +1,15 @@ +{ + "name": "Nectarine", + "binomial_name": "Prunus persica var. nucipersic", + "common_names": [], + "description": "The nectarine is a deciduous tree that produces stone, or drupe, fruits. It is actually the same species as a Peach, it just has a recessive gene that makes it's skin smooth rather than fuzzy. Both fruits belong to the genus Prunus which includes the cherry, apricot, almond, and plum. The peach and nectarine are classified with the almond in the subgenus Amygdalus because their stones are corrugated rather than smooth. Most cultivars require 500 hours of chilling at temperatures between 0 and 10 \u00b0C during the winter, and hot temperatures in the summer to ripen fruit. Some varieties are self-pollinating, while others require pollination by a peach or nectarine tree of another variety with the same bloom period within 50 feet. Standard and dwarf rootstocks are available. Dwarf trees can grow to 3 meters, standard to 4.5 meters. Depending on the size chosen, the tree will bear fruit within 2-4 years of planting.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant bare-root plant", + "spread": 365, + "row_spacing": 300, + "height": 1000, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/nectarine.jpg", + "icon": "/crops/icons/nectarine.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/oat.json b/frontend/crops/data/oat.json new file mode 100644 index 0000000000..a7f8167c04 --- /dev/null +++ b/frontend/crops/data/oat.json @@ -0,0 +1,19 @@ +{ + "name": "Oat", + "binomial_name": "Avena sativa", + "common_names": [ + "Oats", + "spring oats", + "common oat" + ], + "description": "Oats are a species of cereal grain grown for their seed. They are an annual, upright grass with a higher tolerance for rain than other cereal grains. Oats thrive in cool, moist conditions and can tolerate light frosts up to -15C. They do not do well in hot, dry weather. Oats can be eaten as rolled or steel cut oats, oat flour, muesli, or baked into granola. They can also grown as livestock feed or cover crops/green manure. Seeding rates, planting time, and spacing vary based on intended use.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed/broadcast", + "spread": 7, + "row_spacing": 7, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/oat.jpg", + "icon": "/crops/icons/oat.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/okra.json b/frontend/crops/data/okra.json new file mode 100644 index 0000000000..58f163c57b --- /dev/null +++ b/frontend/crops/data/okra.json @@ -0,0 +1,19 @@ +{ + "name": "Okra", + "binomial_name": "Abelmoschus esculentus", + "common_names": [ + "Okra", + "Gumbo", + "Ladies Fingers" + ], + "description": "Okra is a frost-tender annual grown for it's edible seed pods and, less commonly, it's leaves and stems. Plants can be green or red.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors or outside", + "spread": 25, + "row_spacing": 25, + "height": 250, + "growing_degree_days": 64, + "companions": [], + "image": "/crops/images/okra.jpg", + "icon": "/crops/icons/okra.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/onion-chive.json b/frontend/crops/data/onion-chive.json new file mode 100644 index 0000000000..15cae01e67 --- /dev/null +++ b/frontend/crops/data/onion-chive.json @@ -0,0 +1,24 @@ +{ + "name": "Onion Chive", + "binomial_name": "Allium schoenoprasum", + "common_names": [ + "Onion Chive", + "Chive" + ], + "description": "Chives are members of the Allium family along with garlic, onions, and leeks. They have waxy, tubular, grass-like leaves that have a mild onion taste and purple blooms. They grow in grass-like clumps and are frost tolerant. Chives are a great companion plant because they are believed to repel harmful insects including aphids, beetles, cabbage worms, slugs, and Japanese beetles. The stems and flowers are edible. Pull the florets apart and chop the stems and sprinkle them onto salads, soups, baked potatoes, egg dishes, and more.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed outdoors or transplant seedlings", + "spread": 8, + "row_spacing": 15, + "height": 30, + "growing_degree_days": 0, + "companions": [ + "broccoli", + "tomato", + "strawberry", + "eggplant", + "parsley" + ], + "image": "/crops/images/onion-chive.jpg", + "icon": "/crops/icons/onion-chive.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/orange-bell-pepper.json b/frontend/crops/data/orange-bell-pepper.json new file mode 100644 index 0000000000..0b3e9b923f --- /dev/null +++ b/frontend/crops/data/orange-bell-pepper.json @@ -0,0 +1,19 @@ +{ + "name": "Orange Bell Pepper", + "binomial_name": "Capiscum annum", + "common_names": [ + "bell pepper", + "orange pepper", + "sweet pepper" + ], + "description": "The bell pepper is a cultivar group of the species Capsicum annuum. Bell pepper cultivars produce fruits in colors including red, yellow, orange, green, brown, white, and purple. The fruit is often mildly sweet, because this specific cultivar does not produce capsaicin, the chemical responsible for other peppers' spiciness. Orange bell peppers are sweeter than green peppers, but not as sweet as red bell peppers.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 30, + "row_spacing": 45, + "height": 50, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/orange-bell-pepper.jpg", + "icon": "/crops/icons/orange-bell-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/orange-scotch-bonnet.json b/frontend/crops/data/orange-scotch-bonnet.json new file mode 100644 index 0000000000..3db0a87c9a --- /dev/null +++ b/frontend/crops/data/orange-scotch-bonnet.json @@ -0,0 +1,19 @@ +{ + "name": "Scotch Bonnet Pepper", + "binomial_name": "Capsicum chinense", + "common_names": [ + "bonney peppers", + "Caribbean red pepper", + "cachucha pepper" + ], + "description": "Scotch Bonnet Peppers are a hot pepper cultivar shaped like a hat. They have a rating of 100,000\u2013350,000 Scoville units, making them much spicier than jalapenos. Scotch Bonnets are sweeter and stouter than habaneros and used in Caribbean cuisine. Sweet varieties are known as Cachucha Peppers. The fruit is green when young and red when ripe. Scotch Bonnets are often harvested when yellow-orange. Plants grown with less water will produce hotter peppers.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed (and transplant after hardening off) or seedlings", + "spread": 0, + "row_spacing": 60, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/orange-scotch-bonnet.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/oregano.json b/frontend/crops/data/oregano.json new file mode 100644 index 0000000000..0529027871 --- /dev/null +++ b/frontend/crops/data/oregano.json @@ -0,0 +1,17 @@ +{ + "name": "Oregano", + "binomial_name": "Origanum vulgare", + "common_names": [ + "oregano" + ], + "description": "pungent, flavorful her for cooking", + "sun_requirements": "Full Sun", + "sowing_method": "direct or transplant", + "spread": 40, + "row_spacing": 30, + "height": 30, + "growing_degree_days": 85, + "companions": [], + "image": "/crops/images/oregano.jpg", + "icon": "/crops/icons/oregano.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/ornamental-gourd.json b/frontend/crops/data/ornamental-gourd.json new file mode 100644 index 0000000000..513f135a1d --- /dev/null +++ b/frontend/crops/data/ornamental-gourd.json @@ -0,0 +1,15 @@ +{ + "name": "Ornamental Gourds", + "binomial_name": "", + "common_names": [], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/ornamental-gourd.jpg", + "icon": "/crops/icons/ornamental-gourd.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/parsley.json b/frontend/crops/data/parsley.json new file mode 100644 index 0000000000..adc8d31f52 --- /dev/null +++ b/frontend/crops/data/parsley.json @@ -0,0 +1,18 @@ +{ + "name": "Parsley", + "binomial_name": "Petroselinum crispum", + "common_names": [ + "Parsley", + "garden parsley" + ], + "description": "Parsley is an herb in the Apiaceae family with two main cultivars: flat leafed (or Italian) and curly. Some gardeners feel flat leaf is easier to cultivate since it is more tolerant of rain and sunshine. Curly parsley is more decorative in appearance. Both cultivars can be used fresh or dried to season food.", + "sun_requirements": "Partial Sun", + "sowing_method": "direct seed indoors or outdoors", + "spread": 10, + "row_spacing": 30, + "height": 40, + "growing_degree_days": 80, + "companions": [], + "image": "/crops/images/parsley.jpg", + "icon": "/crops/icons/parsley.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/parsnip.json b/frontend/crops/data/parsnip.json new file mode 100644 index 0000000000..d03b614b04 --- /dev/null +++ b/frontend/crops/data/parsnip.json @@ -0,0 +1,17 @@ +{ + "name": "Parsnip", + "binomial_name": "Pastinaca sativa", + "common_names": [ + "Parsnip" + ], + "description": "Parsnips are a root vegetable closely related to carrots and celery. They look like thick white carrots with larger, slightly rough foliage. Parsnips taste similar to carrots, except that they are sweeter, especially when cooked. Like carrots, they can be broiled, pureed, roasted, steamed, and baked into cakes. They become even sweeter in flavor after winter frosts. Before cane sugar arrived in Europe, they were used as a sweetener. The stems and foliage of the parsnip can cause a skin rash - wear long sleeves, pants, and gloves when weeding and harvesting.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, thin seedlings to 5cm apart", + "spread": 20, + "row_spacing": 5, + "height": 45, + "growing_degree_days": 120, + "companions": [], + "image": "/crops/images/parsnip.jpg", + "icon": "/crops/icons/parsnip.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/pattypan-squash.json b/frontend/crops/data/pattypan-squash.json new file mode 100644 index 0000000000..523b05b15d --- /dev/null +++ b/frontend/crops/data/pattypan-squash.json @@ -0,0 +1,31 @@ +{ + "name": "Pattypan Squash", + "binomial_name": "Cucurbita pepo", + "common_names": [ + "Pattypan Squash", + "Patty Pan", + "scallop squash", + "peter pan squash", + "sunburst squash", + "granny squash", + "custard marrow", + "custard squash", + "cibl\u00e8me (Cajun French)", + "white squash", + "button squash", + "scallopini", + "schwoughksie squash (pronounced \"shwooxie squash", + "\" in the Poughkeepsie", + "New York area)" + ], + "description": "Pattypan squash is a variety of summer squash that has a bright yellow, green or white skin and is shaped like a flying saucer with scalloped edges. The squash's flesh is most tender when it is slightly immature - the squash is usually harvested when it is 5-8cm in diameter. It can be baked, grilled, fried, or boiled.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors or direct seed into peat pots and transplant pots into soil", + "spread": 100, + "row_spacing": 90, + "height": 80, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/pattypan-squash.jpg", + "icon": "/crops/icons/pattypan-squash.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/peach.json b/frontend/crops/data/peach.json new file mode 100644 index 0000000000..8ab6c15c2a --- /dev/null +++ b/frontend/crops/data/peach.json @@ -0,0 +1,17 @@ +{ + "name": "Peach", + "binomial_name": "Prunus persica", + "common_names": [ + "peach" + ], + "description": "The peach tree is a deciduous tree native to Northwest China that produces stone, or drupe, fruits. It belongs to the genus Prunus which includes the cherry, apricot, almond, and plum. The peach is classified with the almond in the subgenus Amygdalus because their stones are corrugated rather than smooth. Peaches and nectarines are the same species - nectarines have a recessive gene that makes their skin smooth rather than fuzzy. Cultivated peaches are divided into two groups: clingstones and freestones, depending on whether the flesh sticks to the stone or not. Peaches can have white or yellow fuzzy skin. Yellow peaches usually have an acidic tang coupled with sweetness. White peaches are very sweet with little acidity. Most cultivars require 500 hours of chilling around 0 to 10 \u00b0C during the winter, and hot temperatures in the summer to ripen fruit. Some varieties are self-pollinating, while others require pollination by a peach tree of another variety with the same bloom period within 50 feet. Peaches should be thinned to 7-12cm apart when fruit are 2-3cm in diameter to increase mature fruit size. Standard and dwarf rootstocks are available. Dwarf trees can grow to 3 meters, standard to 4.5 meters. Depending on the size chosen, the tree will bear fruit within 2-4 years of planting.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant bare-root plant", + "spread": 365, + "row_spacing": 300, + "height": 1000, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/peach.jpg", + "icon": "/crops/icons/peach.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/peanut.json b/frontend/crops/data/peanut.json new file mode 100644 index 0000000000..32fd36624f --- /dev/null +++ b/frontend/crops/data/peanut.json @@ -0,0 +1,21 @@ +{ + "name": "Peanut", + "binomial_name": "Arachis hypogaea", + "common_names": [ + "Peanut", + "groundnut", + "goober" + ], + "description": "Peanuts are annual herbaceous plants in the Legume family that are native to South America. They are grown for their edible seed pods, which are also called peanuts. Peanuts are unique in that they grow underground. Above ground, the plants have opposite, pinnate leaves, and small, yellowish orange flowers with red veins. After being pollinated, the flower's ovary elongates and forms a \"peg\" that grows down into the soil and develops into the peanut. There are two types of peanuts: runner and bunch. Runner peanuts have a vining growth habit and require slightly more space than Bunch peanuts, which do not spread out. Once plants are 30 cm tall, begin hilling soil around them to ensure the pegs and developing peanuts are underground. Peanuts are a tropical plant: they need a long, warm growing season of 90-130 days and can withstand only very light spring and fall frosts. Plants are ready to harvest when the leaves turn yellow and the peanuts' inner shells have gold-marked veins (check by pulling a few nuts out and shelling them). Peanuts should be harvested before the pegs become too brittle, because then the pods will break off in the ground. Peanuts also make a good cover crop because, like other legumes, they fix nitrogen.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed. If planting indoors, harden seedlings off and transplant once soil is 15-21\u00b0C", + "spread": 30, + "row_spacing": 25, + "height": 40, + "growing_degree_days": 0, + "companions": [ + "eggplant" + ], + "image": "/crops/images/peanut.jpg", + "icon": "/crops/icons/peanut.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/peppadew-pepper.json b/frontend/crops/data/peppadew-pepper.json new file mode 100644 index 0000000000..dd69e16e75 --- /dev/null +++ b/frontend/crops/data/peppadew-pepper.json @@ -0,0 +1,15 @@ +{ + "name": "Peppadew Pepper", + "binomial_name": "", + "common_names": [], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/peppadew-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/pequin-pepper.json b/frontend/crops/data/pequin-pepper.json new file mode 100644 index 0000000000..f017bd76bd --- /dev/null +++ b/frontend/crops/data/pequin-pepper.json @@ -0,0 +1,18 @@ +{ + "name": "Pequin Pepper", + "binomial_name": "Capsicum annuum 'Pequin'", + "common_names": [ + "pequin", + "piquin" + ], + "description": "The Pequin, or Piquin, Pepper is a hot chili pepper cultivar that produces small, 2cm long, peppers that grow upright rather than hanging. Peppers ripen from green to bright red and are very spicy, with a Scoville Heat rating of 30,000\u201360,000 units. They have a citrusy, smoky, nutty flavor, and are often pickled or added to salsas, hot sauces, and soups. Pequin peppers have a low germination rate (15%) and can take 2-4 months to germinate.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 60, + "height": 100, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/pequin-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/peter-pepper.json b/frontend/crops/data/peter-pepper.json new file mode 100644 index 0000000000..7a1ddd3fb6 --- /dev/null +++ b/frontend/crops/data/peter-pepper.json @@ -0,0 +1,18 @@ +{ + "name": "Peter Pepper", + "binomial_name": "Capsicum annuum 'annuum'", + "common_names": [ + "penis pepper", + "chilli willy pepper" + ], + "description": "The Peter Pepper is a rare heirloom chili pepper cultivar that has a strikingly phallic appearance. The red or orange peppers are most commonly grown in East Texas or Louisiana. Peter Peppers are 8-10 cm long and quite spicy with a Scoville heat rating of 10,000-23,000 units. They can be eaten fresh, added to salsa and other dishes, dried and ground into chili powder, or grown for ornamental purposes.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 60, + "height": 60, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/peter-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/pimento-pepper.json b/frontend/crops/data/pimento-pepper.json new file mode 100644 index 0000000000..34c0a12d1e --- /dev/null +++ b/frontend/crops/data/pimento-pepper.json @@ -0,0 +1,19 @@ +{ + "name": "Pimento Pepper", + "binomial_name": "Capsicum annuum 'Pimiento'", + "common_names": [ + "pimiento pepper", + "pimento pepper", + "cherry pepper" + ], + "description": "Pimento, Pimiento, or Cherry Peppers are a chili pepper cultivar that produces red, heart-shaped peppers 7-10 cm long and 5-7 cm wide. Pimentos have very thick flesh that is sweeter and more aromatic than bell peppers. They have a Scoville heat rating of 100-500 units, but there are some spicy varieties. Like other peppers, Pimentos are not frost tolerant and grow best at temperatures above 13\u00b0 C. They can be grown in containers and brought inside in cooler regions. Pimentos can be eaten fresh, stuffed, pickled, or added to Pimento cheese spread.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 60, + "row_spacing": 60, + "height": 60, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/pimento-pepper.jpg", + "icon": "/crops/icons/pimento-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/pineapple-tomato.json b/frontend/crops/data/pineapple-tomato.json new file mode 100644 index 0000000000..dced5ab75a --- /dev/null +++ b/frontend/crops/data/pineapple-tomato.json @@ -0,0 +1,17 @@ +{ + "name": "Pineapple Tomato", + "binomial_name": "Solanum lycopersicum", + "common_names": [ + "Pineapple tomato" + ], + "description": "Pineapple tomatoes are an heirloom variety that produce large fruit that can weigh 1-2lbs and measure at least 12cm in diameter. Tomatoes are bi-colored with a streaked red and yellow exterior. Flesh is swirled with red, pink, orange, and yellow and meaty with few seeds. They have a sweet fruity taste with a hint of citrus. For larger yield and improved plant health, prune and stake plants.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 45, + "row_spacing": 90, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/pineapple-tomato.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/pink-oyster-mushroom.json b/frontend/crops/data/pink-oyster-mushroom.json new file mode 100644 index 0000000000..88294438a6 --- /dev/null +++ b/frontend/crops/data/pink-oyster-mushroom.json @@ -0,0 +1,15 @@ +{ + "name": "Pink Oyster Mushroom", + "binomial_name": "Pleurotus djamor", + "common_names": [], + "description": "Pink Oyster mushrooms are a member of the Oyster family whose mycelium are more susceptible to infection and competition. This makes them more difficult to grow, and they benefit from sterilized substrate. They can grow at room temperature and anywhere warmer.", + "sun_requirements": "Partial Sun", + "sowing_method": "Inoculate substrate with spores", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/pink-oyster-mushroom.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/plantain.json b/frontend/crops/data/plantain.json new file mode 100644 index 0000000000..3b152d7a73 --- /dev/null +++ b/frontend/crops/data/plantain.json @@ -0,0 +1,19 @@ +{ + "name": "Plantain", + "binomial_name": "Musa paradisiaca", + "common_names": [ + "plantain", + "cooking banana", + "green banana" + ], + "description": "Plantains, or cooking bananas, are banana cultivars in the genus Musa whose fruits are generally used in cooking. Plantains are firmer, starchier, and less sweet than bananas. They can be eaten while ripe or unripe. Plantains grow from a long underground rhizome. Their giant leaves (up to 1m long and 60cm across) wrap around a central trunk. The plantain tree is very frost-sensitive. It needs 10-15 months without freezing temperatures to create flowers, and another 4-8 months to grow plantains.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant young tree", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/plantain.jpg", + "icon": "/crops/icons/plantain.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/plum.json b/frontend/crops/data/plum.json new file mode 100644 index 0000000000..978a1f65bd --- /dev/null +++ b/frontend/crops/data/plum.json @@ -0,0 +1,18 @@ +{ + "name": "Plum", + "binomial_name": "Prunus", + "common_names": [ + "plum", + "prune" + ], + "description": "The plum is a fruit of the subgenus Prunus of the genus Prunus. The Prunus genus also includes the cherry, apricot, almond, and peach. Within the subgenus Prunus, there are many species. The two largest species groups are European Plums (Prunus domestica) and Japanese Plums (Prunus salicina). Apricots are also classified as a section of the Prunus subgenus. The skin of plums can be coated with a waxy bloom or it can be shiny. Plums can be purple, green, yellow, or red. Shape varies from oval to globular. Plums can be dried to make prunes. European Plums require 800-900 hours of chilling during the winter, Japanese Plums require 300-500. Some varieties are self-pollinating, but all plum trees benefit from a pollination partner with the same bloom time within 15 meters. Standard and dwarf rootstocks are available. Dwarf trees can grow to 3 meters, standard to 4.5 meters. Depending on the size chosen, the tree will bear fruit within 3-6 years of planting.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant bare-root plant", + "spread": 500, + "row_spacing": 500, + "height": 500, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/plum.jpg", + "icon": "/crops/icons/plum.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/poblano-pepper.json b/frontend/crops/data/poblano-pepper.json new file mode 100644 index 0000000000..09195c0ef9 --- /dev/null +++ b/frontend/crops/data/poblano-pepper.json @@ -0,0 +1,20 @@ +{ + "name": "Poblano Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "Ancho chile", + "poblano pepper", + "mulato pepper", + "pasilla" + ], + "description": "Poblanos are mild chili peppers native to Mexico. The plants produce medium to large peppers that are 7-15cm long and deep green. They are commonly harvested while still green, but can also be left on the plant until they fully ripen and turn red. Red poblanos are significantly spicier and more flavorful than green ones. Ancho chiles are dried red poblano peppers. Green poblanos are frequently grilled, stuffed, or roasted. Green poblanos usually have a mild flavor and a Scoville rating of 1,000\u20131,500 units, but they can sometimes be hotter. Poblanos are sometimes sold as Pasilla Peppers, although Pasilla is the dried Chilaca chili pepper.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 40, + "row_spacing": 45, + "height": 50, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/poblano-pepper.jpg", + "icon": "/crops/icons/poblano-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/pointed-cabbage.json b/frontend/crops/data/pointed-cabbage.json new file mode 100644 index 0000000000..4fd9323b55 --- /dev/null +++ b/frontend/crops/data/pointed-cabbage.json @@ -0,0 +1,23 @@ +{ + "name": "Pointed Cabbage", + "binomial_name": "Brassica oleracea var. capitata", + "common_names": [ + "Pointed cabbage", + "hearted cabbage", + "sweetheart cabbage", + "hispi cabbage" + ], + "description": "Pointed, Sweetheart, Hearted, or Hispi Cabbage, is a type of green cabbage with a pointed head. It's leaves have a softer texture and sweeter taste and are more open than typical green cabbage. It can be added to stir-fries and casseroles or boiled or saut\u00e9ed.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors or outdoors", + "spread": 30, + "row_spacing": 30, + "height": 0, + "growing_degree_days": 0, + "companions": [ + "rosemary", + "thyme" + ], + "image": "/crops/images/pointed-cabbage.jpg", + "icon": "/crops/icons/pointed-cabbage.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/porcini-mushroom.json b/frontend/crops/data/porcini-mushroom.json new file mode 100644 index 0000000000..1c7a60076b --- /dev/null +++ b/frontend/crops/data/porcini-mushroom.json @@ -0,0 +1,21 @@ +{ + "name": "Porcini Mushroom", + "binomial_name": "Boletus edulis", + "common_names": [ + "Porcini", + "penny bun", + "cep", + "porcino", + "king bolete" + ], + "description": "Porcini mushrooms have large brown caps. They are in the genus Boletus, and like other boletes, have tubes extending downward from the underside of the cap instead of gills. Their stout stems are white or yellowish in color. Porcini are often eaten in soups, pasta, or risotto. They can be grown on cardboard indoors - or outside if temperatures are between 15-17C.", + "sun_requirements": "Partial Sun", + "sowing_method": "Inoculate substrate with spores", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/porcini-mushroom.jpg", + "icon": "/crops/icons/porcini-mushroom.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/portabello-mushroom.json b/frontend/crops/data/portabello-mushroom.json new file mode 100644 index 0000000000..d536cff36a --- /dev/null +++ b/frontend/crops/data/portabello-mushroom.json @@ -0,0 +1,26 @@ +{ + "name": "Portobello", + "binomial_name": "Agaricus bisporus", + "common_names": [ + "Portabello Mushroom", + "Swiss brown mushroom", + "Roman brown mushroom", + "Italian brown", + "Italian mushroom", + "cremini", + "crimini mushroom", + "baby bella", + "brown cap mushroom", + "chestnut mushroom" + ], + "description": "Portobello mushrooms are the mature version of white button mushrooms. They have a meaty flavor and are delicious grilled, saut\u00e9ed, stuffed, and in soups and stir frys. To grow Portobellos, you will need portobello spores and partially decomposed compost and peat moss. They can be grown outside or indoors. Portobellos can be grown indoors year-round because temperature, moisture, and light can be controlled.", + "sun_requirements": "Full Shade", + "sowing_method": "Sprinkle spores on surface of compost and peat moss, gently mix in, and press down", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/portabello-mushroom.jpg", + "icon": "/crops/icons/portabello-mushroom.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/psilocybin-mushroom.json b/frontend/crops/data/psilocybin-mushroom.json new file mode 100644 index 0000000000..fb821b6e35 --- /dev/null +++ b/frontend/crops/data/psilocybin-mushroom.json @@ -0,0 +1,19 @@ +{ + "name": "Psilocybin", + "binomial_name": "Psilocybe", + "common_names": [ + "Psilocybin", + "magic mushrooms", + "shrooms" + ], + "description": "Psilocybin mushrooms are a polyphyletic group of mushrooms that contain the psychedelic compounds psilocybin, psilocin and baeocystin.", + "sun_requirements": "", + "sowing_method": "Inoculate sterilized substrate", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/psilocybin-mushroom.jpg", + "icon": "/crops/icons/psilocybin-mushroom.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/pumpkin.json b/frontend/crops/data/pumpkin.json new file mode 100644 index 0000000000..bc345ef533 --- /dev/null +++ b/frontend/crops/data/pumpkin.json @@ -0,0 +1,17 @@ +{ + "name": "Pumpkin", + "binomial_name": "Cucurbita pepo", + "common_names": [ + "pumpkin" + ], + "description": "Pumpkins are squash cultivars that are round to oval in shape with thick, slightly ribbed skin that varies from deep yellow to orange in color. Their flesh ranges from yellow to gold and has large seeds. Like other members of the Cucurbitaceae family, they grow on sprawling vines. Different varieties of pumpkins are grown for food or decoration.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors (and transplant outside after seedlings are hardened off) or outdoors", + "spread": 250, + "row_spacing": 120, + "height": 70, + "growing_degree_days": 110, + "companions": [], + "image": "/crops/images/pumpkin.jpg", + "icon": "/crops/icons/pumpkin.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/purple-carrot.json b/frontend/crops/data/purple-carrot.json new file mode 100644 index 0000000000..6be09aa124 --- /dev/null +++ b/frontend/crops/data/purple-carrot.json @@ -0,0 +1,17 @@ +{ + "name": "Purple Carrot", + "binomial_name": "Daucus carota var. sativus", + "common_names": [ + "Purple Carrot" + ], + "description": "Purple carrots are a carrot cultivar that have purple skin and orange to purple flesh. They are a little less sweet than orange carrots and sometimes have a lower yield. They can be cooked the same way as carrots.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, thin to 3cm apart when seedlings are 7cm high", + "spread": 8, + "row_spacing": 3, + "height": 15, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/purple-carrot.jpg", + "icon": "/crops/icons/purple-carrot.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/purple-cauliflower.json b/frontend/crops/data/purple-cauliflower.json new file mode 100644 index 0000000000..0f3904ffc2 --- /dev/null +++ b/frontend/crops/data/purple-cauliflower.json @@ -0,0 +1,18 @@ +{ + "name": "Purple Cauliflower", + "binomial_name": "Brassica oleracea var. botrytis", + "common_names": [ + "Purple Cauliflower", + "Graffiti" + ], + "description": "Purple cauliflower is a cultivar of cauliflower that grows purple. Like regular cauliflower, it does not do well in hot weather. It's purple color comes from anthocyanins (the same antioxidants found in red wine). To retain the color, sprinkle with vinegar or lemon juice before cooking.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 65, + "row_spacing": 45, + "height": 50, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/purple-cauliflower.jpg", + "icon": "/crops/icons/purple-cauliflower.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/purple-pod-bean.json b/frontend/crops/data/purple-pod-bean.json new file mode 100644 index 0000000000..730317f698 --- /dev/null +++ b/frontend/crops/data/purple-pod-bean.json @@ -0,0 +1,20 @@ +{ + "name": "Purple Pod Bean", + "binomial_name": "Phaseolus vulgaris", + "common_names": [ + "Purple pod bean" + ], + "description": "Purple pod beans come in bush and pole varieties. They can be eaten raw or cooked, but often turn green when cooked. Like other legumes, they benefit from being treated with an inoculant.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, thin to 30cm when plants are 4cm tall", + "spread": 35, + "row_spacing": 30, + "height": 0, + "growing_degree_days": 0, + "companions": [ + "corn", + "eggplant" + ], + "image": "/crops/images/purple-pod-bean.jpg", + "icon": "/crops/icons/purple-pod-bean.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/purple-potato.json b/frontend/crops/data/purple-potato.json new file mode 100644 index 0000000000..aaac8829cf --- /dev/null +++ b/frontend/crops/data/purple-potato.json @@ -0,0 +1,17 @@ +{ + "name": "Purple Potato", + "binomial_name": "Solanum tuberosum", + "common_names": [ + "Purple Potato" + ], + "description": "Purple potatoes are a type of potato with purple skin and white to purple flesh. Common varieties include Magic Molly, Purple Majesty, Adirondack Blue, Peter Wilcox (yellow flesh), and Harvest Moon (purple and white swirled skin with white flesh). Flavor and texture vary based on variety.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed cut tubers with at least one \"eye\" per piece.", + "spread": 35, + "row_spacing": 30, + "height": 50, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/purple-potato.jpg", + "icon": "/crops/icons/purple-potato.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/radicchio.json b/frontend/crops/data/radicchio.json new file mode 100644 index 0000000000..63fad27675 --- /dev/null +++ b/frontend/crops/data/radicchio.json @@ -0,0 +1,18 @@ +{ + "name": "Radicchio", + "binomial_name": "Cichorium intybus", + "common_names": [ + "Radicchio", + "Italian chicory" + ], + "description": "Radicchio is a leaf vegetable that grows in compact, dense heads. It's leaves have a bitter, spicy taste that complement fresh salads in small doses. When saut\u00e9ed, grilled, or roasted, it's flavor deepens and it becomes less bitter. It is a cool weather crop.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 10, + "row_spacing": 20, + "height": 15, + "growing_degree_days": 85, + "companions": [], + "image": "/crops/images/radicchio.jpg", + "icon": "/crops/icons/radicchio.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/rainbow-chard.json b/frontend/crops/data/rainbow-chard.json new file mode 100644 index 0000000000..c4d0f79ef4 --- /dev/null +++ b/frontend/crops/data/rainbow-chard.json @@ -0,0 +1,18 @@ +{ + "name": "Rainbow Chard", + "binomial_name": "Beta vulgaris", + "common_names": [ + "Rainbow Chard", + "Bright Lights" + ], + "description": "Rainbow Chard is grown from a seed mix that contains chard cultivars that have pink, yellow, orange, red, and white stems. Both the leaves and stems are edible and are delicious in salads, saut\u00e9ed, and baked. Chard is related to beets.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors or outdoors. Transplant seedlings outside after hardening off.", + "spread": 20, + "row_spacing": 30, + "height": 35, + "growing_degree_days": 40, + "companions": [], + "image": "/crops/images/rainbow-chard.jpg", + "icon": "/crops/icons/rainbow-chard.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/rainier-cherry.json b/frontend/crops/data/rainier-cherry.json new file mode 100644 index 0000000000..ec51201314 --- /dev/null +++ b/frontend/crops/data/rainier-cherry.json @@ -0,0 +1,17 @@ +{ + "name": "Rainier Cherry", + "binomial_name": "Prunus avium 'Rainier'", + "common_names": [ + "rainier" + ], + "description": "The Rainier cherry is a cultivar developed in 1952 by crossing Bing and Van cherry cultivars. It has creamy yellow skin with a red blush. The Rainier is considered the sweetest of cherries. Like all cherries, it belongs to the genus Prunus which includes the peach, apricot, almond, and plum. Rainiers require 700 hours of chilling at temperatures below 7\u00b0C during the winter. They are not self-pollinating - plant another cherry variety like Bing, Van, Lapins, or Lambert within 15 meters. Trees will bear fruit within 2-5 years of planting.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant sapling", + "spread": 450, + "row_spacing": 750, + "height": 750, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/rainier-cherry.jpg", + "icon": "/crops/icons/rainier-cherry.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/raspberry.json b/frontend/crops/data/raspberry.json new file mode 100644 index 0000000000..4939df7ee4 --- /dev/null +++ b/frontend/crops/data/raspberry.json @@ -0,0 +1,17 @@ +{ + "name": "Raspberry", + "binomial_name": "Rubus idaeus", + "common_names": [ + "Cultivated Raspberry" + ], + "description": "Raspberries are a perennial plant with erect to trailing canes that often have spines or thorns. The plants produce fruit in their second year of growth, but some \"primocane\" varieties exist that flower and fruit their first year. Canes are light green to blue in hue with alternate, compound leaves. Fruits are sweet, many-seeded, and hollow.", + "sun_requirements": "Partial Sun", + "sowing_method": "Transplant from roots or juvenile plants.", + "spread": 75, + "row_spacing": 200, + "height": 150, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/raspberry.jpg", + "icon": "/crops/icons/raspberry.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-anjou-pear.json b/frontend/crops/data/red-anjou-pear.json new file mode 100644 index 0000000000..b80fad28da --- /dev/null +++ b/frontend/crops/data/red-anjou-pear.json @@ -0,0 +1,21 @@ +{ + "name": "Red D'anjou Pear", + "binomial_name": "Pyrus communis 'D'Anjou'", + "common_names": [ + "anjou pear", + "red anjou", + "Nec Plus Meuris", + "Beurr\u00e9 d'Anjou", + "red pear" + ], + "description": "Red D'Anjou pears are the red cultivar of D'Anjou pears, which are a short-necked cultivar of European pear. D'Anjou pears are medium to large, around 80mm in diameter. They have a wide base that tapers at the stem and deep red skin that changes color only slightly as they ripen. The flesh is firm, sweet, and juicy and very similar to Green D'Anjou pears. They can be eaten fresh or baked. The tree has white blooms in the spring. D'Anjou pears are not self-fertile. They require pollination by a pear tree of another variety with the same bloom period within 50 feet (Bartlett, Bosc, Seckel). Standard and dwarf rootstocks are available. Depending on the size chosen, the tree will bear fruit within 4-6 years of planting. Test ripeness by pressing the top of the pear near the stem. If it gives slightly, the pear is ripe and ready for harvest.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant bare-root plant", + "spread": 300, + "row_spacing": 360, + "height": 300, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/red-anjou-pear.jpg", + "icon": "/crops/icons/red-anjou-pear.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-bartlett-pear.json b/frontend/crops/data/red-bartlett-pear.json new file mode 100644 index 0000000000..91db5ee5de --- /dev/null +++ b/frontend/crops/data/red-bartlett-pear.json @@ -0,0 +1,21 @@ +{ + "name": "Red Bartlett Pear", + "binomial_name": "Pyrus communis 'Williams'", + "common_names": [ + "Williams pear", + "Williams' Bon Chr\u00e9tien pear", + "bartlett pear" + ], + "description": "Red Bartlett pears emerged as a naturally occurring variety (known as a 'sport') of the species Pyrus communis or European Pear. They have since been cultivated. Bartlett pears have a bell shape and skin that turns red as it ripens after harvest. Both Red and Green Bartlett pears are medium-sized and juicy with soft flesh. They can be eaten fresh, baked, or canned. Bartlett pears are not as cold tolerant as other varieties. Their white blooms open early in the season and they are ready to harvest in late summer or early fall. The pear should still be relatively hard when picked. After harvest, pears take 7-10 days to ripen, during which time their flesh will soften and the skin will change to a deep crimson. Bartlett pears are not self-fertile. They require pollination by a pear tree of another variety with the same bloom period within 50 feet, such as Bosc. Standard and dwarf rootstocks are available. Standard sized trees can grow to 6 meters. Depending on the size chosen, the tree will bear fruit within 4-6 years of planting.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant bare-root plant", + "spread": 85, + "row_spacing": 300, + "height": 300, + "growing_degree_days": 0, + "companions": [ + "german-chamomile" + ], + "image": "/crops/images/red-bartlett-pear.jpg", + "icon": "/crops/icons/red-bartlett-pear.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-bell-pepper.json b/frontend/crops/data/red-bell-pepper.json new file mode 100644 index 0000000000..70d76290ef --- /dev/null +++ b/frontend/crops/data/red-bell-pepper.json @@ -0,0 +1,19 @@ +{ + "name": "Red Bell Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "bell pepper", + "sweet pepper", + "red pepper" + ], + "description": "The bell pepper is a cultivar group of the species Capsicum annuum. Bell pepper cultivars produce fruits in colors including red, yellow, orange, green, brown, white, and purple. The fruit is often mildly sweet, because this specific cultivar does not produce capsaicin, the chemical responsible for other peppers' spiciness. Red bell peppers are the sweetest bell peppers.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 30, + "row_spacing": 45, + "height": 50, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/red-bell-pepper.jpg", + "icon": "/crops/icons/red-bell-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-cabbage.json b/frontend/crops/data/red-cabbage.json new file mode 100644 index 0000000000..9ff5a788a4 --- /dev/null +++ b/frontend/crops/data/red-cabbage.json @@ -0,0 +1,18 @@ +{ + "name": "Red Cabbage", + "binomial_name": "Brassica oleracea var. capitata", + "common_names": [ + "Red Cabbage", + "Red Jewel" + ], + "description": "Red Cabbage is a type of cabbage with red leaves. Like other cabbage, it prefers cool weather and forms round, dense heads on top of short stalks.\n\nBeautiful garnet color makes this early, delicious cabbage a standout at the table! Produces large, tightly packed hearts with crisp leaves. Size is 1.5 kilo (3 lb).\n\nSeed outdoors as soon as weather and soil conditions permit. Cover seed lightly with soil, 6 \u2013 7 seeds per 30 cm (1 ft) of row. Average garden soil and a sunny location are ideal. Thin to 30 cm (1 ft) apart when 7 cm (3 in) high. For earlier Cabbage start indoors 2 to 3 weeks in advance of intended date for moving the plants to the garden. Germinates best at 21 C (70 F) in 7 to 10 days. Reduce temperature after germination to 15 C (65 F) and provide additional light to help prevent stretching and to tone up plants before transplanting outside. Several sowings of Cabbage 10 days apart ensure a fresh supply into fall. Keep well watered during hot temperatures to avoid bolting.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors (and transplant out after hardening seedlings off) or outdoors", + "spread": 30, + "row_spacing": 60, + "height": 40, + "growing_degree_days": 75, + "companions": [], + "image": "/crops/images/red-cabbage.jpg", + "icon": "/crops/icons/red-cabbage.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-carrot.json b/frontend/crops/data/red-carrot.json new file mode 100644 index 0000000000..dc16602931 --- /dev/null +++ b/frontend/crops/data/red-carrot.json @@ -0,0 +1,17 @@ +{ + "name": "Red Carrot", + "binomial_name": "Daucus carota var. sativus", + "common_names": [ + "red carrot" + ], + "description": "Red carrots are a carrot cultivar with red skin and orange to red flesh. Some varieties become more red once they are cooked. Red Carrots can sometimes have a lower yield than orange carrots, but are high in lycopene.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, thin to 3cm apart when seedlings are 7cm high", + "spread": 8, + "row_spacing": 3, + "height": 15, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/red-carrot.jpg", + "icon": "/crops/icons/red-carrot.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-chard.json b/frontend/crops/data/red-chard.json new file mode 100644 index 0000000000..07afb361f5 --- /dev/null +++ b/frontend/crops/data/red-chard.json @@ -0,0 +1,18 @@ +{ + "name": "Red Chard", + "binomial_name": "Beta vulgaris subsp. vulgaris", + "common_names": [ + "swiss chard", + "chard" + ], + "description": "Swiss Chard is a leafy green vegetable in the Beet family. The thick stems come in a variety of colors including white, pink, red, yellow, orange, and pale green. Chard has large, crinkly leaves with distinctive veins. The leaves and stems are edible and high in nutrients. Swiss chard can be eaten raw, saut\u00e9ed, baked, or steamed.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors or outdoors. Transplant seedlings after hardening off.", + "spread": 20, + "row_spacing": 30, + "height": 35, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/red-chard.jpg", + "icon": "/crops/icons/red-chard.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-curly-kale.json b/frontend/crops/data/red-curly-kale.json new file mode 100644 index 0000000000..b225f64efa --- /dev/null +++ b/frontend/crops/data/red-curly-kale.json @@ -0,0 +1,18 @@ +{ + "name": "Red Curly Kale", + "binomial_name": "", + "common_names": [ + "redbor", + "curly red kale" + ], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/red-curly-kale.jpg", + "icon": "/crops/icons/red-curly-kale.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-giant-mustard.json b/frontend/crops/data/red-giant-mustard.json new file mode 100644 index 0000000000..830c985533 --- /dev/null +++ b/frontend/crops/data/red-giant-mustard.json @@ -0,0 +1,17 @@ +{ + "name": "Red Giant Mustard", + "binomial_name": "Brassica juncea", + "common_names": [], + "description": "Red Giant Mustard is a variety of mustard that has large purple leaves with a mild mustard flavor. Red Giant can be harvested at the baby or adult stage as individual leaves or whole plants. Adult leaves will have a deeper flavor. Like other mustards, Red Giant is cold-tolerant and does best in cool weather, with spring or autumn plantings. Heat will cause plants to set seed and become unpalatably bitter. Leaves are added to salad mixes and stir fry, braised, or steamed.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, thin to 30cm when seedlings are 5cm or taller", + "spread": 20, + "row_spacing": 60, + "height": 45, + "growing_degree_days": 0, + "companions": [ + "dill" + ], + "image": "/crops/images/red-giant-mustard.jpg", + "icon": "/crops/icons/red-giant-mustard.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-gold-potato.json b/frontend/crops/data/red-gold-potato.json new file mode 100644 index 0000000000..616ab3725d --- /dev/null +++ b/frontend/crops/data/red-gold-potato.json @@ -0,0 +1,17 @@ +{ + "name": "Red Potato", + "binomial_name": "", + "common_names": [ + "Red Potato" + ], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/red-gold-potato.jpg", + "icon": "/crops/icons/red-gold-potato.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-kuri-squash.json b/frontend/crops/data/red-kuri-squash.json new file mode 100644 index 0000000000..196d3ad184 --- /dev/null +++ b/frontend/crops/data/red-kuri-squash.json @@ -0,0 +1,23 @@ +{ + "name": "Red Kuri Squash", + "binomial_name": "Cucurbita maxima", + "common_names": [ + "Red Kuri", + "Orange Hokkaido", + "Baby Red Hubbard", + "Japanese squash", + "Uchiki kuri squash", + "potimarron", + "onion squash" + ], + "description": "Red Kuri is a teardrop-shaped winter squash with thick, smooth, deep-orange to red skin. It's firm flesh has a delicate chestnut-like flavor and is good baked, roasted, or in pies and purees. Like other members of the squash family, it grows on a vine. Each vine produces around three squash.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct sow inside or outdoors. Transplant seedlings after hardening off.", + "spread": 210, + "row_spacing": 90, + "height": 50, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/red-kuri-squash.jpg", + "icon": "/crops/icons/red-kuri-squash.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-onion.json b/frontend/crops/data/red-onion.json new file mode 100644 index 0000000000..06b63a3dc2 --- /dev/null +++ b/frontend/crops/data/red-onion.json @@ -0,0 +1,15 @@ +{ + "name": "Red Onion", + "binomial_name": "Allium cepa", + "common_names": [], + "description": "Red onions are onion cultivars that have purplish red skin and white flesh tinged with red. They are members of the Allium family along with garlic and leeks. They have a sweet to mild flavor and can be eaten raw (in salads and guacamole), grilled, saut\u00e9ed, or baked. Make sure to choose a variety suited to your day length.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors or plant sets (small bulbs) outside.", + "spread": 13, + "row_spacing": 10, + "height": 60, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/red-onion.jpg", + "icon": "/crops/icons/red-onion.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-pointed-cabbage.json b/frontend/crops/data/red-pointed-cabbage.json new file mode 100644 index 0000000000..2cb1abf274 --- /dev/null +++ b/frontend/crops/data/red-pointed-cabbage.json @@ -0,0 +1,15 @@ +{ + "name": "Red Pointed Cabbage", + "binomial_name": "Brassica oleracea var. capitata", + "common_names": [], + "description": "Pointed red cabbage is a type of red cabbage with a pointed head. It's leaves have a softer texture and sweeter taste and are more open than typical cabbage. It can be added to stir-fries and casseroles, boiled, saut\u00e9ed, or fermented into sauerkraut.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors (and transplant seedlings after hardening off) or outdoors", + "spread": 30, + "row_spacing": 30, + "height": 50, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/red-pointed-cabbage.jpg", + "icon": "/crops/icons/red-pointed-cabbage.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-russian-kale.json b/frontend/crops/data/red-russian-kale.json new file mode 100644 index 0000000000..bd4e28e677 --- /dev/null +++ b/frontend/crops/data/red-russian-kale.json @@ -0,0 +1,17 @@ +{ + "name": "Red Russian Kale", + "binomial_name": "Brassica napus pabularia", + "common_names": [ + "Red Russian Kale" + ], + "description": "Red Russian Kale is a type of kale with flat, toothed green leaves and purple stems. It can be harvested at the baby or adult stage. When harvested as a baby, it is often added to salad mixes. It is usually eaten raw or lightly cooked.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors (and transplant after hardening off seedlings) or outdoors", + "spread": 30, + "row_spacing": 45, + "height": 65, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/red-russian-kale.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-savina-pepper.json b/frontend/crops/data/red-savina-pepper.json new file mode 100644 index 0000000000..7fdb950df6 --- /dev/null +++ b/frontend/crops/data/red-savina-pepper.json @@ -0,0 +1,17 @@ +{ + "name": "Red Savina Pepper", + "binomial_name": "Capsicum chinense 'Red Savina'", + "common_names": [], + "description": "Red Savina peppers are a Habanero chili cultivar that produce hotter, larger, and heavier fruit. Fruit are 5cm long by 4cm wide and wrinkled. They mature from green to shiny red. Red Savina have a Scoville Heat rating of 200,000-577,000 units and are second only in heat to the Naga Jolokia pepper. The peppers have a fruity, mildly smoky flavor, but directly consuming them - and even touching the seeds - can be dangerous because of their extreme heat.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 40, + "row_spacing": 60, + "height": 120, + "growing_degree_days": 0, + "companions": [ + "carrot" + ], + "image": "", + "icon": "/crops/icons/red-savina-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-scotch-bonnet.json b/frontend/crops/data/red-scotch-bonnet.json new file mode 100644 index 0000000000..00b5a44249 --- /dev/null +++ b/frontend/crops/data/red-scotch-bonnet.json @@ -0,0 +1,19 @@ +{ + "name": "Red Scotch Bonnet Pepper", + "binomial_name": "Capsicum chinense", + "common_names": [ + "bonney peppers", + "Caribbean red pepper", + "cachucha pepper" + ], + "description": "Red Scotch Bonnet Peppers are ripe hot pepper cultivars shaped like a hat. They have a rating of 100,000\u2013350,000 Scoville units, making them much spicier than jalapenos. Scotch Bonnets are sweeter and stouter than habaneros and used in Caribbean cuisine. Sweet varieties are known as Cachucha Peppers. The fruit is green when young and red when ripe. Scotch Bonnets are often harvested when yellow-orange. Plants grown with less water will produce hotter peppers.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 60, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/red-scotch-bonnet.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-serrano-pepper.json b/frontend/crops/data/red-serrano-pepper.json new file mode 100644 index 0000000000..99c933cce4 --- /dev/null +++ b/frontend/crops/data/red-serrano-pepper.json @@ -0,0 +1,18 @@ +{ + "name": "Serrano Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "serrano", + "red serrano" + ], + "description": "Serrano peppers are a type of chili pepper native to Mexico. They have a bright, biting flavor and are spicier than jalapenos with a Scoville rating of 10,000 to 25,000. They are often eaten raw, or used in pico de gallo, salsa, and soups. A plant can produce up to 50 peppers 3-8cm long. Like many other peppers, Serranos can be harvested when they are green and unripe, or red and ripe.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 75, + "row_spacing": 45, + "height": 75, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/red-serrano-pepper.jpg", + "icon": "/crops/icons/red-serrano-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/red-tabasco-pepper.json b/frontend/crops/data/red-tabasco-pepper.json new file mode 100644 index 0000000000..3be1ed1a1e --- /dev/null +++ b/frontend/crops/data/red-tabasco-pepper.json @@ -0,0 +1,19 @@ +{ + "name": "Red Tabasco Peppers", + "binomial_name": "Capsicum frutescens", + "common_names": [ + "tabasco pepper" + ], + "description": "Red Tabasco peppers are small, thin chili peppers about 5cm long. They originated in Mexico and have a Scoville rating of 30,000 to 50,000 units. Like other Capsicum frutescens varieties, the peppers remain upright when ripe rather than hanging (like a bell pepper). They are the only Capsicum frutescens variety that is juicy (in other peppers the fruit is dry inside) and are famous for their use in Tabasco sauce. Like other peppers, Tabasco peppers ripen from green to red and can be harvested while green and unripe or red and ripe.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 70, + "row_spacing": 45, + "height": 75, + "growing_degree_days": 0, + "companions": [ + "eggplant" + ], + "image": "/crops/images/red-tabasco-pepper.jpg", + "icon": "/crops/icons/red-tabasco-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/rice.json b/frontend/crops/data/rice.json new file mode 100644 index 0000000000..c3c3a8dad3 --- /dev/null +++ b/frontend/crops/data/rice.json @@ -0,0 +1,18 @@ +{ + "name": "Rice", + "binomial_name": "Oryza sativa", + "common_names": [ + "Rice", + "Asian Rice" + ], + "description": "Rice is the seed of the grass species Oryza sativa. It is a cereal grain that can come in many shapes, colors, and sizes. Rice requires ample water and is labor-intensive to cultivate. Water-controlling terrace systems enable it to be grown almost anywhere. The traditional method of cultivating rice is to flood the fields during or after setting the young seedlings. Rice can be grown without flooding, but flooding simplifies weed and pest control.", + "sun_requirements": "Full Sun", + "sowing_method": "Seedlings", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/rice.jpg", + "icon": "/crops/icons/rice.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/rocoto-pepper.json b/frontend/crops/data/rocoto-pepper.json new file mode 100644 index 0000000000..208a46979a --- /dev/null +++ b/frontend/crops/data/rocoto-pepper.json @@ -0,0 +1,15 @@ +{ + "name": "Rocoto Pepper", + "binomial_name": "", + "common_names": [], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/rocoto-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/romaine-lettuce.json b/frontend/crops/data/romaine-lettuce.json new file mode 100644 index 0000000000..24e27918c4 --- /dev/null +++ b/frontend/crops/data/romaine-lettuce.json @@ -0,0 +1,17 @@ +{ + "name": "Romaine Lettuce", + "binomial_name": "Lactuca sativa", + "common_names": [ + "ist eine Deutsche Pflanze" + ], + "description": "Romaine is a variety of lettuce that grows into a dense, tall head. It's leaves are light to bright green and have firm ribs down their centers. It is more tolerant of heat than most lettuces.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed indoors or outside.", + "spread": 30, + "row_spacing": 40, + "height": 30, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/romaine-lettuce.jpg", + "icon": "/crops/icons/romaine-lettuce.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/rosemary.json b/frontend/crops/data/rosemary.json new file mode 100644 index 0000000000..85d6d7042d --- /dev/null +++ b/frontend/crops/data/rosemary.json @@ -0,0 +1,18 @@ +{ + "name": "Rosemary", + "binomial_name": "Rosmarinus officinalis", + "common_names": [], + "description": "Rosmarinus officinalis, commonly known as rosemary, is a woody, perennial herb with fragrant, evergreen, needle-like leaves and white, pink, purple, or blue flowers, native to the Mediterranean region.\nIt is a member of the mint family Lamiaceae, which includes many other herbs. The name \"rosemary\" derives from the Latin for \"dew\" (ros) and \"sea\" (marinus), or \"dew of the sea\". The plant is also sometimes called 'anthos', from the ancient Greek word \u1f04\u03bd\u03b8\u03bf\u03c2, meaning \"flower\". Rosemary has a fibrous root system.", + "sun_requirements": "Full Sun", + "sowing_method": "Purchased starter", + "spread": 60, + "row_spacing": 60, + "height": 90, + "growing_degree_days": 90, + "companions": [ + "lemon-balm", + "arugula" + ], + "image": "/crops/images/rosemary.jpg", + "icon": "/crops/icons/rosemary.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/runner-bean.json b/frontend/crops/data/runner-bean.json new file mode 100644 index 0000000000..9aa3e2b68c --- /dev/null +++ b/frontend/crops/data/runner-bean.json @@ -0,0 +1,25 @@ +{ + "name": "Runner Bean", + "binomial_name": "Phaseolus coccineus", + "common_names": [ + "scarlet runner bean", + "runner bean", + "butter bean", + "multiflora bean", + "Oregon lima bean", + "fire bean", + "mammoth", + "red giant", + "scarlet emperor" + ], + "description": "The Runner, or Multiflora, Bean is a vining plant in the Fabaceae (legume) family that is native to Central America. It is sometimes called the Butter Bean, which can also refer to the Lima Bean, a separate species. The Runner Bean differs from other beans in that it can be grown as a perennial where the ground does not freeze. Runner Beans are grown for their edible beans and as an ornamental: plants produce brilliant red flowers that attract hummingbirds and multicolored seeds. Some varieties have white flowers. The seed pods have a knife-like shape and are usually green, but some purple cultivars exist. Pods are 15-30cm long and contain 6-10 2.5cm seeds. Different varieties have different colored seeds. Seeds can eaten fresh or as dried beans, but must be cooked to remove toxins. Blooms can be added to salads. Young pods can be cooked like green beans. Roots are eaten by indigenous peoples in Central America. Runner Beans benefit from trellising.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors once soil temperatures are above 10\u00b0C", + "spread": 30, + "row_spacing": 20, + "height": 300, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/runner-bean.jpg", + "icon": "/crops/icons/runner-bean.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/russet-potato.json b/frontend/crops/data/russet-potato.json new file mode 100644 index 0000000000..83130f777c --- /dev/null +++ b/frontend/crops/data/russet-potato.json @@ -0,0 +1,21 @@ +{ + "name": "Potato", + "binomial_name": "Solanum tuberosum", + "common_names": [ + "Tatie", + "Tater", + "Spud" + ], + "description": "Potatoes are starchy root vegetables in the Solanaceae, or Nightshade, family, which also includes tomatoes, eggplants, and peppers. They originated in South America, and spread to become a worldwide staple. The leaves and fruit are usually poisonous and the stem tuber is the only edible part once it is cooked. The potato can be cooked in many ways, brewed into alcohol, and also used as the basis for creating bioplastics. More growing information is available in individual species entries.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors after last frost. Each piece must have one eye.", + "spread": 30, + "row_spacing": 90, + "height": 60, + "growing_degree_days": 100, + "companions": [ + "marjoram" + ], + "image": "/crops/images/russet-potato.jpg", + "icon": "/crops/icons/russet-potato.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/rutabaga.json b/frontend/crops/data/rutabaga.json new file mode 100644 index 0000000000..ce80508aeb --- /dev/null +++ b/frontend/crops/data/rutabaga.json @@ -0,0 +1,20 @@ +{ + "name": "Rutabaga", + "binomial_name": "Brassica napus", + "common_names": [ + "Rutabaga", + "swede", + "neep", + "turnip" + ], + "description": "Often confused with turnips, the rutabaga is a root vegetable that is a cross between the cabbage and the turnip. The root has a roughly globular shape, thick purple-tinged yellow skin (which is sometimes sealed in wax after harvest to extend storage life), and yellow flesh. The flesh is slightly sweet and bitter, similar to cabbage. Rutabagas are slightly larger than turnips and more cold-tolerant. Although edible, the tops are not often eaten. The roots can be baked, boiled, and roasted.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed, thin to 15cm when seedlings at 5cm tall", + "spread": 17, + "row_spacing": 15, + "height": 35, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/rutabaga.jpg", + "icon": "/crops/icons/rutabaga.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/rye.json b/frontend/crops/data/rye.json new file mode 100644 index 0000000000..45a50717e1 --- /dev/null +++ b/frontend/crops/data/rye.json @@ -0,0 +1,15 @@ +{ + "name": "Rye", + "binomial_name": "Secale cereale", + "common_names": [], + "description": "Rye is a grass grown as a cereal grain, a cover crop and a forage crop. A member of the wheat tribe (Triticeae), it is closely related to barley and wheat. It should not be confused with Ryegrass, which is in the Lolium genus, and used for lawns, pasture, and hay for livestock. Rye is more tolerant of poorer soils and cold temperatures than other grains. It is often sown in autumn and harvested for grain the following season. It's vigorous growth suppresses weeds, but can also invade winter wheat fields. When grown as a cover crop or \"green manure,\" it is often paired with hairy vetch and crimson clover, and mowed and turned in before it goes to seed to increase the amount of organic matter in the soil.", + "sun_requirements": "Full Sun", + "sowing_method": "Broadcast direct seed", + "spread": 25, + "row_spacing": 0, + "height": 125, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/rye.jpg", + "icon": "/crops/icons/rye.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/sage.json b/frontend/crops/data/sage.json new file mode 100644 index 0000000000..f796374662 --- /dev/null +++ b/frontend/crops/data/sage.json @@ -0,0 +1,25 @@ +{ + "name": "Sage", + "binomial_name": "Salvia officinalis", + "common_names": [ + "Sage", + "garden sage", + "common sage", + "culinary sage", + "golden sage", + "dalmatian sage", + "broadleaf sage" + ], + "description": "Sage is an evergreen shrub with woody stems, soft green-gray leaves, and blue to purplish flowers. It is in the Mint family (Lamiaceae), and is perennial in Zones 4-8. It can be used fresh or dried, and has culinary and medicinal uses.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, harden seedlings off before transplanting outside.", + "spread": 50, + "row_spacing": 30, + "height": 70, + "growing_degree_days": 85, + "companions": [ + "bok-choy" + ], + "image": "/crops/images/sage.jpg", + "icon": "/crops/icons/sage.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/salsify.json b/frontend/crops/data/salsify.json new file mode 100644 index 0000000000..639f11406c --- /dev/null +++ b/frontend/crops/data/salsify.json @@ -0,0 +1,21 @@ +{ + "name": "Salsify", + "binomial_name": "Tragopogon porrifolius", + "common_names": [ + "salsify", + "white salsify", + "oyster plant", + "vegetable oyster", + "goats beard" + ], + "description": "Salsify, or white salsify, is a perennial plant grown as an annual for it's edible root and leaves. It does well in cool weather and is cultivated similarly to carrots and parsnips. It is similar to Black Salsify, but they are in different genera. Both plants are grown for their edible roots and cultivated like carrots and parsnips. Salsify's leaves look like a clump of coarse grass with starry pink to purple flowers. The greens and flowers can be used in salads. Flowers should be dead-headed to prevent the plant from becoming invasive. Salsify is less hearty than Black Salsify. It requires sandy loam soil to grow straight roots and can be grown in containers. The taproot has dirty-beige skin and resembles a large white carrot. It has an oyster-like taste that some consider more subtle than Black Salsify. It can be eaten boiled or mashed; skin is inedible and should be removed. Plants can stay in the ground until just before the soil freezes and roots can be stored in a root cellar through the winter. Take extra care not to break the roots while harvesting - damaged roots have a much shorter shelf life.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed outdoors, thin to 7cm", + "spread": 0, + "row_spacing": 0, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/salsify.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/santa-fe-grande-pepper.json b/frontend/crops/data/santa-fe-grande-pepper.json new file mode 100644 index 0000000000..3d0038c6bb --- /dev/null +++ b/frontend/crops/data/santa-fe-grande-pepper.json @@ -0,0 +1,23 @@ +{ + "name": "Santa Fe Grande Pepper", + "binomial_name": "Capsicum annuum 'Santa Fe Grande'", + "common_names": [ + "yellow hot chili pepper", + "Guero chili pepper", + "Caribe" + ], + "description": "Santa Fe Grande peppers are a chili pepper cultivar that produces conical fruits 5-10cm long that resemble jalapeno peppers. Peppers ripen from pale yellow to bright orange or red and have a sweet, mildly spicy taste. They have a Scoville Heat rating of 0-999 units and are often pickled or added to salsas. Plants are resistant to the tobacco mosaic virus.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 60, + "row_spacing": 45, + "height": 60, + "growing_degree_days": 0, + "companions": [ + "tomato", + "eggplant", + "parsley" + ], + "image": "", + "icon": "/crops/icons/santa-fe-grande-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/savoy-cabbage.json b/frontend/crops/data/savoy-cabbage.json new file mode 100644 index 0000000000..b3a8b6f9f4 --- /dev/null +++ b/frontend/crops/data/savoy-cabbage.json @@ -0,0 +1,19 @@ +{ + "name": "Savoy Cabbage", + "binomial_name": "Brassica oleracea var. capitata", + "common_names": [ + "Cavolo Verza", + "Savoyer Kohl", + "Savoy King" + ], + "description": "Savoy is a variety of cabbage with deep green crinkled leaves. It's crunchy, tender leaves can be eaten raw in salads and coleslaw or cooked in stir-fry, casserole, and soup. Like all cabbages, it prefers cool weather.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors (and harden seedlings off before transplanting) or outdoors (and thin plants to 35cm when 4 cm high)", + "spread": 30, + "row_spacing": 35, + "height": 35, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/savoy-cabbage.jpg", + "icon": "/crops/icons/savoy-cabbage.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/shallot.json b/frontend/crops/data/shallot.json new file mode 100644 index 0000000000..972f407e7b --- /dev/null +++ b/frontend/crops/data/shallot.json @@ -0,0 +1,26 @@ +{ + "name": "Shallot", + "binomial_name": "Allium cepa var. aggregatum", + "common_names": [ + "shallot", + " scallion", + " cibol", + " thick-neck onion", + " chalota", + " echalota", + " ascalonia", + " alho de ascalao", + " echalote", + " schalotte" + ], + "description": "Shallots are a type of small onion with a subtle, distinguished flavor. The bulb is covered in papery red skin tinted rust, gold, or purple and has a tapered oblong shape with multiple cloves. They can be used fresh or dried in a variety of dishes.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed, thin to 5cm when seedlings are 3cm tall", + "spread": 15, + "row_spacing": 5, + "height": 30, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/shallot.jpg", + "icon": "/crops/icons/shallot.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/shiitake-mushroom.json b/frontend/crops/data/shiitake-mushroom.json new file mode 100644 index 0000000000..222fa08a6c --- /dev/null +++ b/frontend/crops/data/shiitake-mushroom.json @@ -0,0 +1,15 @@ +{ + "name": "Shiitake Mushroom", + "binomial_name": "Lentinula edodes", + "common_names": [], + "description": "Shiitake mushrooms have white stems with tightly-packed gills that are not directly attached to the stem. Their caps are brown to black. Shiitake have a long history of medicinal use, are high in Vitamins B and D, and are believed to have anti-tumor properties. Growing the mushrooms on logs outdoors (rather than the sawdust used for commercially grown mushrooms) increases their nutrient content. Spawn comes in three different forms, plugs are the easiest to use for beginners.", + "sun_requirements": "Partial Sun", + "sowing_method": "Inoculate logs with spawn", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/shiitake-mushroom.jpg", + "icon": "/crops/icons/shiitake-mushroom.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/shishito-pepper.json b/frontend/crops/data/shishito-pepper.json new file mode 100644 index 0000000000..13b694d889 --- /dev/null +++ b/frontend/crops/data/shishito-pepper.json @@ -0,0 +1,19 @@ +{ + "name": "Shishito Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "shishito", + "kkwari-gochu", + "groundcherry pepper" + ], + "description": "Shishito peppers are mild, thin-walled, slightly sweet peppers native to East Asia. They are small, slender peppers, usually 5-10cm long, with slightly wrinkled flesh. They have a Scoville rating of 50-200, but about 1 in 10 peppers can be significantly spicy. Shishitos are usually harvested while still green and unripe, but they can be left on the plant to ripen and turn orange and red. Shishitos are popular in Japan and Korea and are often grilled, roasted, saut\u00e9ed, or fried into tempura.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 50, + "height": 75, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/shishito-pepper.jpg", + "icon": "/crops/icons/shishito-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/siling-labuyo-pepper.json b/frontend/crops/data/siling-labuyo-pepper.json new file mode 100644 index 0000000000..31a0dec675 --- /dev/null +++ b/frontend/crops/data/siling-labuyo-pepper.json @@ -0,0 +1,18 @@ +{ + "name": "Siling Labuyo Pepper", + "binomial_name": "Capsicum frutescens 'Siling Labuyo'", + "common_names": [ + "tagalog", + "wild chili" + ], + "description": "Siling labuyo peppers are a small chili pepper cultivar native to the Philippines. They are related to Malagueta Peppers and often mistaken with Bird's Eye Chilis (which are a separate species, Capsicum annuum). The small, slightly wrinkled peppers are 1.5 to 2.5 cm long and grow pointing upwards in clusters of 2 or 3. Fruit ripen from green to red, yellow, orange, purple, white, or black. Peppers are very spicy, with a Scoville Heat rating of 80,000 - 100,000 units.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 60, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/siling-labuyo-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/snap-pea.json b/frontend/crops/data/snap-pea.json new file mode 100644 index 0000000000..da8b99ca3d --- /dev/null +++ b/frontend/crops/data/snap-pea.json @@ -0,0 +1,19 @@ +{ + "name": "Snap Pea", + "binomial_name": "Pisum sativum var. macrocarpon", + "common_names": [ + "sugar snap", + "dwarf sugar", + "mangetout" + ], + "description": "Snap peas are a cultivar group of edible-podded peas that have round pods instead of flat pods (like snow peas). They were developed in 1952 by cross-breeding snow pea with a mutant shell pea plant. The pods are edible and contain 3-8 peas. They are harvested when they are bright green, young, and tender. Snap peas are a cool weather crop and require trellising to support their climbing vines.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, thin to 12cm apart when seedlings are 3cm high", + "spread": 20, + "row_spacing": 12, + "height": 150, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/snap-pea.jpg", + "icon": "/crops/icons/snap-pea.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/soybean.json b/frontend/crops/data/soybean.json new file mode 100644 index 0000000000..de05d60956 --- /dev/null +++ b/frontend/crops/data/soybean.json @@ -0,0 +1,20 @@ +{ + "name": "Soybean", + "binomial_name": "Glycine max", + "common_names": [ + "Soy", + "Edamame", + "Soya", + "Soybean" + ], + "description": "Soybeans are a type of legume that can be grown for many uses. Soybeans are one of the major crops grown in the United States. Like all legumes, they benefit from an inoculant at planting. They can be harvested fresh or dried. Soybeans are used to make oil, feed livestock, grown as cover crops, or processed into foods like tofu and tempeh. When harvested in an immature state and boiled whole in their pods, soybeans are known as Edamame.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors after last frost, thin to 30cm when seedlings are 4cm high", + "spread": 55, + "row_spacing": 30, + "height": 65, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/soybean.jpg", + "icon": "/crops/icons/soybean.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/spaghetti-squash.json b/frontend/crops/data/spaghetti-squash.json new file mode 100644 index 0000000000..6a981ba71e --- /dev/null +++ b/frontend/crops/data/spaghetti-squash.json @@ -0,0 +1,18 @@ +{ + "name": "Spaghetti Squash", + "binomial_name": "Cucurbita pepo", + "common_names": [ + "Spaghetti Squash", + "vegetable spaghetti" + ], + "description": "Spaghetti Squash is a winter squash cultivar that produces oblong, ivory to yellow-skinned squash with flesh like strands of spaghetti. Like other squash, it grows on sprawling vines. If starting seeds indoors, use peat pots and directly transplant them into the soil to reduce root disturbance. Harvest squash before any hard frosts and cure. Spaghetti Squash can be stored for up to 3 months in a cool, dry, well-ventilated place.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors or indoors in peat pots (transplant pots outside after hardening off seedlings)", + "spread": 30, + "row_spacing": 30, + "height": 1800, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/spaghetti-squash.jpg", + "icon": "/crops/icons/spaghetti-squash.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/spinach.json b/frontend/crops/data/spinach.json new file mode 100644 index 0000000000..d54891c2e5 --- /dev/null +++ b/frontend/crops/data/spinach.json @@ -0,0 +1,17 @@ +{ + "name": "Spinach", + "binomial_name": "Spinacia oleracea", + "common_names": [ + "Spinach" + ], + "description": "Spinach is an annual plant whose deep green leaves are eaten as a vegetable. It grows best in cooler weather. It can be eaten raw or cooked.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, thin to 15cm when seedlings are 3cm high", + "spread": 20, + "row_spacing": 30, + "height": 15, + "growing_degree_days": 40, + "companions": [], + "image": "/crops/images/spinach.jpg", + "icon": "/crops/icons/spinach.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/spring-onion.json b/frontend/crops/data/spring-onion.json new file mode 100644 index 0000000000..4f7fafc8cd --- /dev/null +++ b/frontend/crops/data/spring-onion.json @@ -0,0 +1,18 @@ +{ + "name": "Spring Onion", + "binomial_name": "Allium cepa", + "common_names": [ + "Spring Onion", + "fresh onion" + ], + "description": "Spring onions resemble scallions in appearance and flavor, but they are actually very young storage onions. Yellow, red, or white storage onions are pulled out of the ground before the standard harvest date, when their skins are still thin and they are mild in flavor. Their bulbs and greens can be eaten fresh or cooked.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed or plant slips or sets", + "spread": 8, + "row_spacing": 8, + "height": 30, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/spring-onion.jpg", + "icon": "/crops/icons/spring-onion.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/st-johns-wort.json b/frontend/crops/data/st-johns-wort.json new file mode 100644 index 0000000000..387517c74c --- /dev/null +++ b/frontend/crops/data/st-johns-wort.json @@ -0,0 +1,20 @@ +{ + "name": "St. John's Wort", + "binomial_name": "Hypericum perforatum", + "common_names": [ + "St. John's wort", + "perforate St John's-wort", + "common Saint John's wort", + "St John's wort" + ], + "description": "St. John's Wort is a perennial, herbaceous to woody flowering plant native to Europe and Asia that is used as a medicinal herb to treat depression, inflammation, and wounds. Plants have erect stems with narrow, oblong leaves and yellow, star-shaped flowers. St. John's Wort will bloom the second year after planting, but can easily become invasive because it spreads through underground rhizomes and seeds. A single plant can produce 100,000 seeds that can remain viable for 10 years. Remove berries before they ripen to prevent seeds from forming. St. John's Wort should not be taken by pregnant women.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed, very lightly covering seeds. If planting indoors, harden off before transplanting seedlings outside. Can also use cuttings or divide plants.", + "spread": 60, + "row_spacing": 90, + "height": 100, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/st-johns-wort.jpg", + "icon": "/crops/icons/st-johns-wort.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/stevia.json b/frontend/crops/data/stevia.json new file mode 100644 index 0000000000..5e184622ee --- /dev/null +++ b/frontend/crops/data/stevia.json @@ -0,0 +1,20 @@ +{ + "name": "Stevia", + "binomial_name": "Stevia rebaudiana", + "common_names": [ + "sweetleaf", + "sweet leaf", + "sugarleaf", + "candyleaf" + ], + "description": "Stevia is tender, semi-tropical shrub in the Asteraceae (Sunflower) family that is native to Brazil and Paraguay. It is grown for it's sweet leaves, which can be used fresh or dried and ground into a powder also known as Stevia. Dried leaves are sweeter than fresh, and the powder is used as a sugar substitute. Stevia contains compounds that are 200-300 times sweeter than sugar, but pass through the digestive system without being broken down and absorbed. The plant has weak, floppy stems, slender, oblong leaves, and tubular, white, hermaphrodite flowers. Stevia does best in humid, wet environments. It is a perennial in regions where the temperature range is 21 to 43\u00b0 C, but can be grown as an annual or in containers in colder regions. Stevia cannot survive temperatures below 7\u00b0 C. The entire plant can be harvested in the fall once flower buds have appeared but not opened (the plant has a bitter aftertaste after flowers have opened). Cooler temperatures and shorter days increase the sweetness of the leaves.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, cuttings, or cloned seedlings", + "spread": 60, + "row_spacing": 25, + "height": 60, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/stevia.jpg", + "icon": "/crops/icons/stevia.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/strawberry.json b/frontend/crops/data/strawberry.json new file mode 100644 index 0000000000..81e1cb5742 --- /dev/null +++ b/frontend/crops/data/strawberry.json @@ -0,0 +1,20 @@ +{ + "name": "Strawberry", + "binomial_name": "Fragaria \u00d7 ananassa", + "common_names": [ + "Aardbei", + "Fraisier", + "Erdbeere", + "Grandian F1" + ], + "description": "Strawberries are a hybrid species of the genus Fragaria that produce sweet, bright red fruits. \n\nThere are three main types of strawberries: \n\n1) summer-fruiting: produce a single, large crop of fruit the year after planting. To grow, transplant plugs or crowns in early spring in rows spaced at least 120cm apart. Pinch off all flowers the first season and train the plant's runners, pressing them into the soil 15-22cm apart from the mother plant. Mulch with straw or pine needles in the fall when the plants have died back. When the plants start to grow back in the spring, move the mulch aside. After harvest the second season, set a lawnmower to about 10cm high and mow, being sure not to damage crowns.\n\n The other two types are \n\n2) Ever-bearing and 3) Day Neutral, both of which send out less runners and bear several crops of smaller fruit throughout the season. These two types can be grown using raised beds about 20cm high and 60cm wide. Transplant crowns or plugs in staggered double rows, about 30cm apart. Remove runners and flowers until July of the first year to give the roots time to develop, and then allow plants to produce fruit. All types of strawberries begin to produce fewer and less sweet fruit once they are two years or older. Because strawberries are a hybrid, seeds will not breed true. Strawberries are predominantly propagated using bare root plugs or crowns or dividing runners. Make sure not to bury the crown when transplanting plugs.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant bare root plants/plugs or divide runners", + "spread": 30, + "row_spacing": 35, + "height": 30, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/strawberry.jpg", + "icon": "/crops/icons/strawberry.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/striped-cavern-tomato.json b/frontend/crops/data/striped-cavern-tomato.json new file mode 100644 index 0000000000..cf28646773 --- /dev/null +++ b/frontend/crops/data/striped-cavern-tomato.json @@ -0,0 +1,18 @@ +{ + "name": "Striped Cavern Tomato", + "binomial_name": "Solanum lycopersicum", + "common_names": [ + "striped cavern", + "Schimmeig Stoo" + ], + "description": "Striped Cavern is an heirloom tomato variety that produces medium-sized, thick-walled, hollow tomatoes similar to bell peppers. The tomatoes are often stuffed or added to fresh salsas. They have red skin with yellow and orange stripes and a sweet, mild taste. For larger yield and improved plant health, prune and stake plants.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 0, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/striped-cavern-tomato.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/sugarcane.json b/frontend/crops/data/sugarcane.json new file mode 100644 index 0000000000..43f894e819 --- /dev/null +++ b/frontend/crops/data/sugarcane.json @@ -0,0 +1,18 @@ +{ + "name": "Sugarcane", + "binomial_name": "Saccharum officinarum", + "common_names": [ + "sugarcane", + "sugar cane" + ], + "description": "Sugarcane is a species of tall perennial true grasses. It is a member of the grass family Poaceae, which also includes maize, wheat, and rice. Sugarcane is native to the warm temperate and tropical regions of South Asia and Melanesia. It's thick, jointed, fibrous stalks are rich in the sugar sucrose and can grow to heights of 6 meters. Sugarcane is the world's largest crop and is grown for sugar and ethanol.", + "sun_requirements": "Full Sun", + "sowing_method": "Plant cuttings into trench", + "spread": 70, + "row_spacing": 15, + "height": 355, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/sugarcane.jpg", + "icon": "/crops/icons/sugarcane.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/sunflower.json b/frontend/crops/data/sunflower.json new file mode 100644 index 0000000000..b43ea536d3 --- /dev/null +++ b/frontend/crops/data/sunflower.json @@ -0,0 +1,18 @@ +{ + "name": "Sunflower", + "binomial_name": "Helianthus annuus", + "common_names": [ + "Sun Flower", + "Sunflower" + ], + "description": "Sunflowers are large flowers with bright to deep yellow ray florets surrounding a large circular grouping of disc florets that mature into seeds. Sunflowers are grown for ornamental purposes, cut flowers, or their edible seeds. They can reach heights of 300cm or more.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors after last frost in groups of 2-3, thin to 1 plant when true leaves appear", + "spread": 60, + "row_spacing": 30, + "height": 240, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/sunflower.jpg", + "icon": "/crops/icons/sunflower.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/sunray-tomato.json b/frontend/crops/data/sunray-tomato.json new file mode 100644 index 0000000000..17106c7adb --- /dev/null +++ b/frontend/crops/data/sunray-tomato.json @@ -0,0 +1,20 @@ +{ + "name": "Sunray Tomato", + "binomial_name": "Solanum lycopersicum", + "common_names": [ + "Sunray tomato", + "sungold tomato", + "sungold", + "sunsweet tomato" + ], + "description": "Sunrays or Sungolds are a cherry tomato variety that produce small, round golden to orange tomatoes. They have deeply sweet, delicious, fruity flavor. Harvest when they are at their deepest hue for the most intense flavor. Cherry tomatoes do not need to be pruned. Trellis and staking is optional.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 30, + "row_spacing": 90, + "height": 300, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/sunray-tomato.jpg", + "icon": "/crops/icons/sunray-tomato.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/sweet-italian-pepper.json b/frontend/crops/data/sweet-italian-pepper.json new file mode 100644 index 0000000000..0071130941 --- /dev/null +++ b/frontend/crops/data/sweet-italian-pepper.json @@ -0,0 +1,23 @@ +{ + "name": "Italian Sweet Pepper", + "binomial_name": "Capsicum annuum 'Friggitello'", + "common_names": [ + "friggitello", + "Golden Greek pepper", + "Sweet Italian pepper", + "Tuscan pepper", + "pepperoncini" + ], + "description": "The Italian Sweet Pepper (also known as Friggitello, Golden Greek Sweet Pepper, and Tuscan Pepper) is a Capiscum Annum cultivar that produces tapered green to red peppers. The peppers are mild with slight heat (they have a Scoville Heat rating of 0-500 units) and a touch of bitterness. Greek varieties are sweeter than Italian. Friggitello is sometimes known as Pepperoncini in America, but the Pepperoncini is a separate cultivar and a spicier pepper, with a Scoville rating of 15,000\u201330,000 units. The plants produce heavily and benefit from being staked. Peppers are harvested while still immature and green, when they are 5\u20137.5 cm long. They are eaten fresh, saut\u00e9ed, pickled, and stuffed.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 90, + "row_spacing": 45, + "height": 90, + "growing_degree_days": 0, + "companions": [ + "carrot" + ], + "image": "", + "icon": "/crops/icons/sweet-italian-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/sweet-potato.json b/frontend/crops/data/sweet-potato.json new file mode 100644 index 0000000000..63f75407d9 --- /dev/null +++ b/frontend/crops/data/sweet-potato.json @@ -0,0 +1,17 @@ +{ + "name": "Sweet Potato", + "binomial_name": "Ipomoea batata", + "common_names": [], + "description": "The sweet potato is a large, starchy, tuberous root. The tuber is long and tapered and has yellow, orange, red, brown, purple, or beige skin. It's flesh can be white, red, pink, violet, yellow, orange, or purple. Sweet potato cultivars with white or pale yellow flesh are not as sweet and moist as those with red, pink or orange flesh. The heart-shaped leaves and vines are sometimes eaten as greens when the plants are young, but eating the leaves interferes with the growth of the tuber underground. The sweet potato is not related to the potato or the yam.", + "sun_requirements": "Full Sun", + "sowing_method": "Slips", + "spread": 100, + "row_spacing": 35, + "height": 25, + "growing_degree_days": 0, + "companions": [ + "parsnip" + ], + "image": "/crops/images/sweet-potato.jpg", + "icon": "/crops/icons/sweet-potato.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/swiss-chard.json b/frontend/crops/data/swiss-chard.json new file mode 100644 index 0000000000..bb91df1d95 --- /dev/null +++ b/frontend/crops/data/swiss-chard.json @@ -0,0 +1,18 @@ +{ + "name": "Swiss Chard", + "binomial_name": "Beta vulgaris subsp. vulgaris", + "common_names": [ + "swiss chard", + "chard" + ], + "description": "Swiss Chard is a leafy green vegetable in the Beet family. The thick stems come in a variety of colors including white, pink, red, yellow, orange, and pale green. Chard has large, crinkly leaves with distinctive veins. The leaves and stems are edible and high in nutrients. Swiss chard can be eaten raw, saut\u00e9ed, baked, or steamed.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors or outdoors. Transplant seedlings after hardening off.", + "spread": 20, + "row_spacing": 30, + "height": 35, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/swiss-chard.jpg", + "icon": "/crops/icons/swiss-chard.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/taro.json b/frontend/crops/data/taro.json new file mode 100644 index 0000000000..639e497269 --- /dev/null +++ b/frontend/crops/data/taro.json @@ -0,0 +1,19 @@ +{ + "name": "Taro", + "binomial_name": "Colocasia esculenta", + "common_names": [ + "Taro", + "elephant ears", + "dasheen" + ], + "description": "Taro is a perennial, tropical plant grown for its edible starchy corm (underground plant stem) and leaves. It's big green leaves and corm must be cooked before consumption, as they are toxic when raw. When grown as an ornamental, taro is known as \"elephant ears.\" Taro is believed to have been one of the earliest cultivated plants and is a food staple in African, South Indian and Oceanic cultures. Taro comes in many varieties, from small to large and from white-fleshed to purple-flecked. It is commonly prepared like a potato, since it is starchy, but it has a nuttier, richer, and more complex taste.", + "sun_requirements": "Partial Sun", + "sowing_method": "Direct seed tubers outdoors", + "spread": 0, + "row_spacing": 45, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/taro.jpg", + "icon": "/crops/icons/taro.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/thyme.json b/frontend/crops/data/thyme.json new file mode 100644 index 0000000000..7a3c97b5c4 --- /dev/null +++ b/frontend/crops/data/thyme.json @@ -0,0 +1,20 @@ +{ + "name": "Thyme", + "binomial_name": "Thymus vulgaris", + "common_names": [ + "Thyme" + ], + "description": "Thyme is a perennial evergreen herb in the Lamiaceae (Mint) family. It is related to Oregano. Thyme is cold-hardy to -28.8 \u00b0C and a perennial in US Hardiness zones 5-8. Thyme can live for 5-6 years but needs to be divided or replaced after 3-4 years. It can be used fresh or dried to season dishes.", + "sun_requirements": "Full Sun", + "sowing_method": "Transplant cuttings or seedlings or direct seed indoors", + "spread": 30, + "row_spacing": 30, + "height": 25, + "growing_degree_days": 80, + "companions": [ + "lacinato-kale", + "red-pointed-cabbage" + ], + "image": "/crops/images/thyme.jpg", + "icon": "/crops/icons/thyme.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/tomatillo.json b/frontend/crops/data/tomatillo.json new file mode 100644 index 0000000000..dac7938320 --- /dev/null +++ b/frontend/crops/data/tomatillo.json @@ -0,0 +1,18 @@ +{ + "name": "Tomatillo", + "binomial_name": "Physalis philadelphica", + "common_names": [ + "Husk tomato", + "Mexican husk tomato" + ], + "description": "The tomatillo is a member of the Nightshade family. The plant bears a resemblance to tomato plants and benefits from pruning and trellising. The fruit of the tomatillo plant is small, round, and green or purple. It is covered in a paper husk that splits open when the fruit is ready for harvest. Tomatillos are native to Mexico and can be eaten raw or cooked. They have a citrusy, tart, and tangy lemon flavor.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 90, + "row_spacing": 60, + "height": 100, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/tomatillo.jpg", + "icon": "/crops/icons/tomatillo.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/tomato.json b/frontend/crops/data/tomato.json new file mode 100644 index 0000000000..ecfbe1edac --- /dev/null +++ b/frontend/crops/data/tomato.json @@ -0,0 +1,18 @@ +{ + "name": "Tomato", + "binomial_name": "Solanum lycopersicum", + "common_names": [ + "Tomato", + "Tomate" + ], + "description": "The tomato is the fruit of the tomato plant, a member of the Nightshade family (Solanaceae). The fruit grows on a small compact bush.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 45, + "row_spacing": 45, + "height": 45, + "growing_degree_days": 75, + "companions": [], + "image": "/crops/images/tomato.jpg", + "icon": "/crops/icons/tomato.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/trinidad-moruga-scorpion-pepper.json b/frontend/crops/data/trinidad-moruga-scorpion-pepper.json new file mode 100644 index 0000000000..36e5b28fb5 --- /dev/null +++ b/frontend/crops/data/trinidad-moruga-scorpion-pepper.json @@ -0,0 +1,17 @@ +{ + "name": "Trinidad Moruga Scorpion Pepper", + "binomial_name": "Capsicum chinense", + "common_names": [ + "Trinidad Morouga Scorpion Pepper" + ], + "description": "Trinidad Moruga Scorpion Pepper is a Capsicum chinense species that was bred in Trinidad and Tobago. It is currently the hottest chili pepper in the world, with an average Scoville Heat index rating of 1.2 million units. Peppers mature from green to red and look like miniature, squished, wrinkled red bell peppers. The pepper also has a tender fruity flavor beneath it's extreme heat.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/trinidad-moruga-scorpion-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/trinidad-scorpion-butch-t-pepper.json b/frontend/crops/data/trinidad-scorpion-butch-t-pepper.json new file mode 100644 index 0000000000..ce755a8c75 --- /dev/null +++ b/frontend/crops/data/trinidad-scorpion-butch-t-pepper.json @@ -0,0 +1,15 @@ +{ + "name": "Trinidad Scorpion 'butch T' Pepper", + "binomial_name": "Capsicum chinense", + "common_names": [], + "description": "The Trinidad Scorpion 'Butch T' Pepper is a Capiscum chinense cultivar derived from the Trinidad Moruga Scorpion. Fruit look like the Trinidad Moruga Scorpion, with a pointed tail similar to a Scorpion's. Peppers are extremely spicy, with a Scoville Heat Index rating of 500,000 - 1,463,700 units.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/trinidad-scorpion-butch-t-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/trumpet-mushroom.json b/frontend/crops/data/trumpet-mushroom.json new file mode 100644 index 0000000000..5db735269d --- /dev/null +++ b/frontend/crops/data/trumpet-mushroom.json @@ -0,0 +1,26 @@ +{ + "name": "Trumpet Mushroom", + "binomial_name": "Pleurotus eryngii", + "common_names": [ + "king trumpet", + "trumpet mushroom", + "king oyster mushroom", + "Eryngii", + "trumpet royale", + "French Horn Mushroom", + "King Brown Mushroom", + "Boletus Of The Steppes", + "Ali'i Oyster", + "Abalone mushroom" + ], + "description": "The Trumpet, King Oyster, or Trumpet Royale mushroom, is a thick, white, stumpy mushroom with a small, brown flat cap covering short gills. It is considered the tastiest member of the Oyster mushroom family, in addition to being the largest. All but the very bottom of the mushroom can be eaten. It has a rich flavor similar to umami when cooked, and a flavor and texture similar to abalone.", + "sun_requirements": "Partial Sun", + "sowing_method": "Inoculate substrate", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/trumpet-mushroom.jpg", + "icon": "/crops/icons/trumpet-mushroom.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/tulsi.json b/frontend/crops/data/tulsi.json new file mode 100644 index 0000000000..5c6e390c75 --- /dev/null +++ b/frontend/crops/data/tulsi.json @@ -0,0 +1,18 @@ +{ + "name": "Tulsi", + "binomial_name": "Ocimum tenuiflorum", + "common_names": [ + "Holy Basil", + "Thai Holy Basil" + ], + "description": "Ocimum tenuiflorum, also known as Ocimum sanctum, holy basil, or tulas\u012b, is an aromatic plant in the family Lamiaceae which is native to the Indian Subcontinent and widespread as a cultivated plant throughout the Southeast Asian tropics. It is an erect, many branched subshrub, 30\u201360 cm tall with hairy stems and simple opposite green or purple leaves that are strongly scented. Leaves have petioles and are ovate, up to 5 cm long, usually slightly toothed. The flowers are purplish in elongate racemes in close whorls. The two main morphotypes cultivated in India and Nepal are green-leaved (Sri or Lakshmi tulasi) and purple-leaved (Krishna tulasi).\r\n\r\nTulasi is cultivated for religious and medicinal purposes, and for its essential oil. It is widely known across the Indian Subcontinent as a medicinal plant and an herbal tea, commonly used in Ayurveda, and has an important role within the Vaishnavite tradition of Hinduism, in which devotees perform worship involving holy basil plants or leaves.\r\n\r\nThe variety of Ocimum tenuiflorum used in Thai cuisine is referred to as Thai holy basil (Thai language: \u0e01\u0e30\u0e40\u0e1e\u0e23\u0e32 kaphrao); it is not to be confused with Thai basil, which is a variety of Ocimum basilicum.", + "sun_requirements": "", + "sowing_method": "", + "spread": 40, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/tulsi.jpg", + "icon": "/crops/icons/tulsi.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/turmeric.json b/frontend/crops/data/turmeric.json new file mode 100644 index 0000000000..655421e0a8 --- /dev/null +++ b/frontend/crops/data/turmeric.json @@ -0,0 +1,17 @@ +{ + "name": "Turmeric", + "binomial_name": "curcuma longa", + "common_names": [ + "turmeric" + ], + "description": "", + "sun_requirements": "", + "sowing_method": "", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/turmeric.jpg", + "icon": "/crops/icons/turmeric.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/turnip.json b/frontend/crops/data/turnip.json new file mode 100644 index 0000000000..7c7aaa8eba --- /dev/null +++ b/frontend/crops/data/turnip.json @@ -0,0 +1,20 @@ +{ + "name": "Turnip", + "binomial_name": "Brassica rapa", + "common_names": [ + "turnip", + " winter oil seed turnip rape", + " summer oil seed", + " turnip rape" + ], + "description": "Turnips are smooth, round root vegetables 5-20 cm in diameter. They are often white below the soil line and bright purple or green above. Turnips usually have white flesh and a slightly sweet and bitter taste, similar to cabbage. The edible foliage tastes similar to mustard greens. Turnips are a cool-weather crop and do best as spring or fall plantings.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, thin to 10cm when seedlings are 3cm tall", + "spread": 30, + "row_spacing": 10, + "height": 35, + "growing_degree_days": 55, + "companions": [], + "image": "/crops/images/turnip.jpg", + "icon": "/crops/icons/turnip.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/watercress.json b/frontend/crops/data/watercress.json new file mode 100644 index 0000000000..ace0c8179b --- /dev/null +++ b/frontend/crops/data/watercress.json @@ -0,0 +1,17 @@ +{ + "name": "Watercress", + "binomial_name": "nasturtium officinale", + "common_names": [ + "Watercress" + ], + "description": "Watercress is a semi-aquatic to aquatic perennial plant in the Brassica family whose leaves and stems have a clean, slightly peppery taste. In the wild, Watercress favors moderately cool climates and grows partially submerged in running water and flooded areas. It is one of the oldest known leaf vegetables eaten by humans. Watercress can be cultivated hydroponically, in containers, or in soil that is kept constantly wet. It can be harvested as sprouts or leaves.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed, transplants, or cuttings", + "spread": 20, + "row_spacing": 20, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/watercress.jpg", + "icon": "/crops/icons/watercress.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/watermelon-radish.json b/frontend/crops/data/watermelon-radish.json new file mode 100644 index 0000000000..e3a1bb19ca --- /dev/null +++ b/frontend/crops/data/watermelon-radish.json @@ -0,0 +1,19 @@ +{ + "name": "Watermelon Radish", + "binomial_name": "Raphanus sativus", + "common_names": [ + "Watermelon Radish", + "Red Meat", + "White Glob" + ], + "description": "Watermelon radishes are round roots with white skin and bright red or deep pink flesh. They have a sweet, delicious taste and can be eaten raw in salads, pickled, or cooked. They are best sown in summer or fall, as they will bolt from spring sowings.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed, thin to 5cm apart when seedlings are 3cm tall", + "spread": 8, + "row_spacing": 5, + "height": 15, + "growing_degree_days": 50, + "companions": [], + "image": "/crops/images/watermelon-radish.jpg", + "icon": "/crops/icons/watermelon-radish.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/watermelon.json b/frontend/crops/data/watermelon.json new file mode 100644 index 0000000000..8c9001b81f --- /dev/null +++ b/frontend/crops/data/watermelon.json @@ -0,0 +1,17 @@ +{ + "name": "Watermelon", + "binomial_name": "Citrullus lanatus", + "common_names": [ + "watermelon" + ], + "description": "The watermelon is a species of melon that produces round or oblong fruits with thick skin and sweet, watery flesh. It is a special kind of berry with a hard rind and no internal division, botanically known as a \"pepo.\" The rind is usually dark green with light-green stripes. The flesh can be red or yellow. Like other melons and members of the Cucurbitaceae family, the watermelon grows on sprawling vines.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed into soil or peat pots (and transplant pots directly into soil after hardening off)", + "spread": 200, + "row_spacing": 300, + "height": 50, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/watermelon.jpg", + "icon": "/crops/icons/watermelon.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/wax-bean.json b/frontend/crops/data/wax-bean.json new file mode 100644 index 0000000000..7d65027348 --- /dev/null +++ b/frontend/crops/data/wax-bean.json @@ -0,0 +1,15 @@ +{ + "name": "Wax Bean", + "binomial_name": "Phaseolus vulgaris", + "common_names": [], + "description": "Wax beans are a type of snap bean with vibrant yellow, patterned, or dark purple skin and a slightly waxier texture. They can be eaten whole with pod and seeds and have a mild and fresh taste. Wax beans come in pole and bush varieties. Like other legumes, wax beans benefit from an inoculant.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, thin to 30cm when seedlings are 4cm tall", + "spread": 40, + "row_spacing": 7, + "height": 40, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/wax-bean.jpg", + "icon": "/crops/icons/wax-bean.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/wheat.json b/frontend/crops/data/wheat.json new file mode 100644 index 0000000000..429569d3ce --- /dev/null +++ b/frontend/crops/data/wheat.json @@ -0,0 +1,15 @@ +{ + "name": "Wheat", + "binomial_name": "Triticum aestivum", + "common_names": [], + "description": "Wheat is a grass that is grown for its seed. Many species of wheat make up the genus Triticum, with the most widely grown species being Common Wheat (Triticum aestivum). Wheat can be divided into two main groups: Winter (planted in fall and harvested in the spring or summer) and Spring (planted in spring and harvested in fall). Winter and Spring can then be divided into 1) Soft wheat (low in gluten, used for pastries and crackers), 2) Hard wheat (high in gluten, used for bread), and 3) Durum wheat (used for pasta). Winter wheat should be planted 6-8 weeks before the soil freezes to allow time for good root development. Spring wheat is planted as early as the ground can be worked in the spring.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed into furrows. Broadcast or use grain drill. Rake in, roll soil to firm the bed.", + "spread": 0, + "row_spacing": 0, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/wheat.jpg", + "icon": "/crops/icons/wheat.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/white-button-mushroom.json b/frontend/crops/data/white-button-mushroom.json new file mode 100644 index 0000000000..69d1d0f068 --- /dev/null +++ b/frontend/crops/data/white-button-mushroom.json @@ -0,0 +1,15 @@ +{ + "name": "White Button Mushroom", + "binomial_name": "Agaricus bisporus", + "common_names": [], + "description": "White button mushrooms are the immature version of portobello mushrooms. They are one of the easiest species of mushroom to grow. Growing white button mushrooms requires spores and partially decomposed compost and peat moss. Mushrooms can be grown indoors year-round because temperature, moisture, and light can be controlled. They can also be grown outdoors.", + "sun_requirements": "Full Shade", + "sowing_method": "Sprinkle spores on surface of compost and peat moss, gently mix in, and press down.", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/white-button-mushroom.jpg", + "icon": "/crops/icons/white-button-mushroom.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/white-carrot.json b/frontend/crops/data/white-carrot.json new file mode 100644 index 0000000000..f7743b831f --- /dev/null +++ b/frontend/crops/data/white-carrot.json @@ -0,0 +1,17 @@ +{ + "name": "White Carrot", + "binomial_name": "Daucus carota var. sativus", + "common_names": [ + "white carrot" + ], + "description": "White carrots are a carrot cultivar that have pale yellow to white skin. They sometimes have a lower yield than orange carrots. They have a mild flavor that is less earthy than other carrot varieties. They are grown and cooked the same way as standard orange carrots.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, thin to 3cm apart when seedlings are 7cm high", + "spread": 8, + "row_spacing": 3, + "height": 15, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/white-carrot.jpg", + "icon": "/crops/icons/white-carrot.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/white-onion.json b/frontend/crops/data/white-onion.json new file mode 100644 index 0000000000..1c96bb5415 --- /dev/null +++ b/frontend/crops/data/white-onion.json @@ -0,0 +1,17 @@ +{ + "name": "White Onion", + "binomial_name": "Allium cepa", + "common_names": [ + "White Onion" + ], + "description": "White onions are a type of onion with white skin and white flesh. They are members of the Allium family along with garlic and leeks. They tend to have a sharper, more pungent flavor than yellow onions, and thinner, papery skin. They can be eaten cooked or raw (mince and add to salsas and chutneys). Make sure to choose a variety suited to your day length.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors or plant sets (small bulbs) outside.", + "spread": 13, + "row_spacing": 10, + "height": 60, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/white-onion.jpg", + "icon": "/crops/icons/white-onion.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/white-oyster-mushroom.json b/frontend/crops/data/white-oyster-mushroom.json new file mode 100644 index 0000000000..e02441ce38 --- /dev/null +++ b/frontend/crops/data/white-oyster-mushroom.json @@ -0,0 +1,15 @@ +{ + "name": "White Oyster Mushroom", + "binomial_name": "Pleurotus ostreatus", + "common_names": [], + "description": "White Oyster mushrooms are easily cultivated and have vigorous mycelium growth. They grow in large clusters and have the typical shelf-like shape of the Pleurotus species. White Oysters have milky white caps with a silky surface, and grow best outdoors on beech, hornbeam, birch, or poplar tree wood. They have a thick, meaty flesh that is milder in flavor than shiitake.", + "sun_requirements": "Partial Sun", + "sowing_method": "Inoculate wood substrate with grain or plug spawn", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/white-oyster-mushroom.jpg", + "icon": "/crops/icons/white-oyster-mushroom.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/wormwood.json b/frontend/crops/data/wormwood.json new file mode 100644 index 0000000000..59ec5f0057 --- /dev/null +++ b/frontend/crops/data/wormwood.json @@ -0,0 +1,21 @@ +{ + "name": "Wormwood", + "binomial_name": "Artemisia absinthium", + "common_names": [ + "wormwood", + "common wormwood", + "absinthe", + "absinthium", + "absinthe wormwood" + ], + "description": "Wormwood is a species of Artemisia and a member of the Daisy (Asteraceae) family. Other Artemesia species include Tarragon and Mugwort. All parts of the plant are extremely bitter. Wormwood is drought, heat, and deer resistant. It is grown as an ornamental plant and is used to make traditional bitters and absinthe. Herbal medicine has historically used wormwood to treat liver, gallbladder and digestive system disorders.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors two weeks after last spring frost", + "spread": 0, + "row_spacing": 90, + "height": 150, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/wormwood.jpg", + "icon": "/crops/icons/wormwood.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/yellow-bell-pepper.json b/frontend/crops/data/yellow-bell-pepper.json new file mode 100644 index 0000000000..e57877cb0a --- /dev/null +++ b/frontend/crops/data/yellow-bell-pepper.json @@ -0,0 +1,22 @@ +{ + "name": "Yellow Bell Pepper", + "binomial_name": "Capsicum annuum", + "common_names": [ + "bell pepper", + "sweet pepper", + "yellow pepper" + ], + "description": "Bell peppers are a cultivar group of the species Capsicum annuum. The fruit is often mildly sweet, because this specific cultivar does not produce capsaicin, the chemical responsible for other peppers' spiciness. Yellow peppers are sweeter than green peppers, but not as sweet as red bell peppers.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 30, + "row_spacing": 45, + "height": 50, + "growing_degree_days": 0, + "companions": [ + "tomato", + "eggplant" + ], + "image": "/crops/images/yellow-bell-pepper.jpg", + "icon": "/crops/icons/yellow-bell-pepper.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/yellow-carrot.json b/frontend/crops/data/yellow-carrot.json new file mode 100644 index 0000000000..3f4f411ddf --- /dev/null +++ b/frontend/crops/data/yellow-carrot.json @@ -0,0 +1,17 @@ +{ + "name": "Yellow Carrot", + "binomial_name": "Daucus carota var. sativus", + "common_names": [ + "Yellow carrot" + ], + "description": "Yellow carrots are a carrot cultivar that have yellow skin and flesh. They are grown and cooked the same way as orange carrots, but can sometimes have a lower yield.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed outdoors, thin to 3cm apart when seedlings are 7cm high", + "spread": 8, + "row_spacing": 3, + "height": 15, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/yellow-carrot.jpg", + "icon": "/crops/icons/yellow-carrot.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/yellow-cauliflower.json b/frontend/crops/data/yellow-cauliflower.json new file mode 100644 index 0000000000..69507d8015 --- /dev/null +++ b/frontend/crops/data/yellow-cauliflower.json @@ -0,0 +1,18 @@ +{ + "name": "Yellow Cauliflower", + "binomial_name": "Brassica oleracea var. botrytis", + "common_names": [ + "Yellow Cauliflower", + "Cheddar" + ], + "description": "Yellow Cauliflower is a cauliflower cultivar that forms yellow heads. It is a member of the Brassicaceae family. The solid, firm head resembles that of broccoli and sits atop a stalk. The head is wrapped in thick leaves that begin to open when the plant is ready for harvest. All cauliflower does best in cool weather.", + "sun_requirements": "Full Sun", + "sowing_method": "Sow seeds indoors, harden seedlings off before transplanting", + "spread": 30, + "row_spacing": 60, + "height": 75, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/yellow-cauliflower.jpg", + "icon": "/crops/icons/yellow-cauliflower.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/yellow-onion.json b/frontend/crops/data/yellow-onion.json new file mode 100644 index 0000000000..2909176f73 --- /dev/null +++ b/frontend/crops/data/yellow-onion.json @@ -0,0 +1,17 @@ +{ + "name": "Yellow Onion", + "binomial_name": "Allium cepa", + "common_names": [ + "Yellow Onion" + ], + "description": "Yellow onions have with yellow skin and white to yellow flesh. They are members of the Allium family along with garlic and leeks. They have a good balance of sweet and astringency in their flavor, and become sweeter when cooked. Spanish onions are a type of yellow onion that is slightly sweeter and more delicate in flavor. Make sure to choose a variety suited to your day length.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors or plant sets (small bulbs) outside.", + "spread": 13, + "row_spacing": 10, + "height": 60, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/yellow-onion.jpg", + "icon": "/crops/icons/yellow-onion.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/yellow-oyster-mushroom.json b/frontend/crops/data/yellow-oyster-mushroom.json new file mode 100644 index 0000000000..2fff26234e --- /dev/null +++ b/frontend/crops/data/yellow-oyster-mushroom.json @@ -0,0 +1,18 @@ +{ + "name": "Yellow Oyster", + "binomial_name": "Pleurotus citrinopileatus", + "common_names": [ + "Yellow Oyster", + "Golden Oyster" + ], + "description": "Yellow, or Golden Oyster, mushrooms are members of the Oysters family with striking bright gold caps. They grow in large clusters and have the typical shelf-like shape of the Pleurotus species. The brightness of their golden caps is directly related to the intensity of light where they are growing and they thrive in high temperatures. Yellow Oysters have a thick, meaty flesh that is initially spicy and bitter, but becomes nutty after thorough cooking.", + "sun_requirements": "", + "sowing_method": "Inoculate substrate with spores", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/yellow-oyster-mushroom.jpg", + "icon": "/crops/icons/yellow-oyster-mushroom.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/yellow-scotch-bonnet.json b/frontend/crops/data/yellow-scotch-bonnet.json new file mode 100644 index 0000000000..18d40d7129 --- /dev/null +++ b/frontend/crops/data/yellow-scotch-bonnet.json @@ -0,0 +1,19 @@ +{ + "name": "Yellow Scotch Bonnet Pepper", + "binomial_name": "Capsicum chinense", + "common_names": [ + "bonney peppers", + "Caribbean red pepper", + "cachucha pepper" + ], + "description": "Yellow Scotch Bonnet Peppers are medium-ripe hot pepper cultivars shaped like a hat. They have a rating of 100,000\u2013350,000 Scoville units, making them much spicier than jalapenos. Scotch Bonnets are sweeter and stouter than habaneros and used in Caribbean cuisine. Sweet varieties are known as Cachucha Peppers. The fruit is green when young and red when ripe. Scotch Bonnets are often harvested when yellow-orange. Plants grown with less water will produce hotter peppers.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors, transplant seedlings outside after hardening off", + "spread": 0, + "row_spacing": 60, + "height": 120, + "growing_degree_days": 0, + "companions": [], + "image": "", + "icon": "/crops/icons/yellow-scotch-bonnet.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/yellow-squash.json b/frontend/crops/data/yellow-squash.json new file mode 100644 index 0000000000..a279d540a6 --- /dev/null +++ b/frontend/crops/data/yellow-squash.json @@ -0,0 +1,17 @@ +{ + "name": "Squash", + "binomial_name": "Cucurbita", + "common_names": [ + "squash" + ], + "description": "Squash is a genus of herbaceous vines that have large edible orange flowers that mature into gourds or cucurbits. Squash are commonly divided into two main groups: summer and winter. Summer squash have shorter growing times, a bushy growth habit, tender skin, and are quite prolific. Common types include cucumbers, zucchini, and pattypan squash. Winter squash take longer to mature, have a more sprawling growth habit, and produce gourds with thicker skins that can be stored for a few months. Common winter squash are pumpkins, butternut squash, acorn, and delicata. Most squash transplant poorly. If starting from seed indoors, use peat pots that can be directly transplanted into the soil to reduce root disturbance. More growing information is available in individual species entries.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed indoors or outdoors", + "spread": 0, + "row_spacing": 0, + "height": 0, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/yellow-squash.jpg", + "icon": "/crops/icons/yellow-squash.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/yukon-gold-potato.json b/frontend/crops/data/yukon-gold-potato.json new file mode 100644 index 0000000000..f221de5837 --- /dev/null +++ b/frontend/crops/data/yukon-gold-potato.json @@ -0,0 +1,18 @@ +{ + "name": "Yukon Gold Potato", + "binomial_name": "Solanum tuberosum", + "common_names": [ + "Yukon Gold Potato", + "Yellow potato" + ], + "description": "Yukon Gold is a potato cultivar that was developed in the 1960s. It has smooth skin and yellow flesh that is waxy and moist. It becomes flaky and a bit starchy when cooked. Yukon Gold potatoes can be mashed, shredded, or used in soups and stews.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed cut tubers with at least one \"eye\" per piece.", + "spread": 30, + "row_spacing": 30, + "height": 50, + "growing_degree_days": 0, + "companions": [], + "image": "/crops/images/yukon-gold-potato.jpg", + "icon": "/crops/icons/yukon-gold-potato.avif" +} \ No newline at end of file diff --git a/frontend/crops/data/zucchini.json b/frontend/crops/data/zucchini.json new file mode 100644 index 0000000000..b0b63d8716 --- /dev/null +++ b/frontend/crops/data/zucchini.json @@ -0,0 +1,20 @@ +{ + "name": "Green Zucchini", + "binomial_name": "Cucurbita pepo", + "common_names": [ + "Zucchini", + "courgette", + "long marrow", + "garden marrow" + ], + "description": "Green Griller Zucchini\nAs the name suggests, this variety of zucchini squash is practically made for the grill. Oblong zucchinis are fatter and blockier than other varieties, yielding broad slices that are easy to flip. Fruits grow on a low, open bush, so picking them is a cinch. Leaves boast a green and silver variegated pattern that looks really attractive in the garden. Fruits mature quite quickly!Resistant to Zucchini Yellow Mosaic Virus. Fruit is 6-7 in long.", + "sun_requirements": "Full Sun", + "sowing_method": "Direct seed 2 seeds, thin to 1 plant when seedlings have 2 sets of leaves", + "spread": 100, + "row_spacing": 90, + "height": 45, + "growing_degree_days": 50, + "companions": [], + "image": "/crops/images/zucchini.jpg", + "icon": "/crops/icons/zucchini.avif" +} \ No newline at end of file diff --git a/frontend/crops/find.ts b/frontend/crops/find.ts new file mode 100644 index 0000000000..cbb5f24ed7 --- /dev/null +++ b/frontend/crops/find.ts @@ -0,0 +1,44 @@ +import { clone, kebabCase, startCase } from "lodash"; +import { CROPS, ALIASED_SLUG_LOOKUP } from "./constants"; +import { Crop, Crops } from "./interfaces"; + +const GENERIC_PLANT_ICON = "/crops/icons/generic-plant.avif"; + +const customCrop = (slug: string) => { + const generic = clone(CROPS["generic-plant"]); + generic.name = startCase(slug); + return generic; +}; + +export const findCrop = (slug: string): Crop => { + const crop = CROPS[kebabCase(slug)]; + if (crop) { return crop; } + + return customCrop(slug); +}; + +export const findCrops = (searchTerm: string): Crops => { + const term = searchTerm.toLowerCase(); + const crops = Object.entries(CROPS).filter(([_slug, crop]) => + crop.name.toLowerCase().includes(term)) + .reduce((crops, [slug, crop]) => { + crops[kebabCase(slug)] = crop; + return crops; + }, {} as Crops); + if (Object.values(crops).length == 0) { + return { [kebabCase(searchTerm)]: customCrop(searchTerm) }; + } + return crops; +}; + +export const findIcon = (slug: string): string => { + let icon = findCrop(slug).icon; + if (icon == GENERIC_PLANT_ICON) { + icon = findCrop(ALIASED_SLUG_LOOKUP[slug]).icon; + } + return icon || GENERIC_PLANT_ICON; +}; + +export const findImage = (slug: string): string => { + return findCrop(slug).image; +}; diff --git a/frontend/crops/interfaces.ts b/frontend/crops/interfaces.ts new file mode 100644 index 0000000000..f5163e1790 --- /dev/null +++ b/frontend/crops/interfaces.ts @@ -0,0 +1,16 @@ +export interface Crop { + name: string; + binomial_name: string; + common_names: string[]; + description: string; + sun_requirements: string; + sowing_method: string; + spread: number; + row_spacing: number; + height: number; + growing_degree_days: number; + companions: string[]; + icon: string; + image: string; +} +export type Crops = Record; diff --git a/frontend/css/components/go_button.scss b/frontend/css/components/go_button.scss index d31a31c242..4a65e02a07 100644 --- a/frontend/css/components/go_button.scss +++ b/frontend/css/components/go_button.scss @@ -6,6 +6,8 @@ button { margin: 0; float: none; + height: 3rem !important; + padding: 0 1rem; } .go-button-axes-text { border-top-right-radius: 0; diff --git a/frontend/css/farm_designer/farm_designer_panels.scss b/frontend/css/farm_designer/farm_designer_panels.scss index 62a66c0c04..05b07cb3dc 100644 --- a/frontend/css/farm_designer/farm_designer_panels.scss +++ b/frontend/css/farm_designer/farm_designer_panels.scss @@ -362,18 +362,6 @@ } } -.point-name-input { - label { - margin-top: 2rem !important; - } -} - -li { - label { - margin-top: 0 !important; - } -} - .additional-weed-properties { li { display: grid; diff --git a/frontend/css/panels/curves.scss b/frontend/css/panels/curves.scss index 055d403bb9..f8ebd2ab83 100644 --- a/frontend/css/panels/curves.scss +++ b/frontend/css/panels/curves.scss @@ -130,6 +130,9 @@ } .crop-curve-info { + background: var(--secondary-bg); + border-radius: 0.5rem; + padding: 1rem; .bp5-collapse { padding-top: 0.5rem; } @@ -140,11 +143,6 @@ label { margin-top: 0 !important; } - .fa-external-link { - position: absolute; - top: 1rem; - right: 0; - } .active-curve-name { p { white-space: nowrap; @@ -152,11 +150,6 @@ overflow: hidden; } } - .curve-svg-wrapper { - background: $white; - box-shadow: 0 0 10px $light_gray; - border-radius: 5px; - } } .curve-info-panel-content-wrapper, diff --git a/frontend/css/panels/plants.scss b/frontend/css/panels/plants.scss index de0a1f7c67..858c3ab077 100644 --- a/frontend/css/panels/plants.scss +++ b/frontend/css/panels/plants.scss @@ -1,21 +1,8 @@ @use "../variables" as *; @use "sass:color"; -.plants-panel-settings-menu { - label { - line-height: 3rem; - vertical-align: bottom; - margin-bottom: 0; - } - .bp5-popover-wrapper { - display: inline; - margin-left: 0.5rem; - line-height: 3rem; - } -} - .crop-search-result-wrapper { - .openfarm-search-results-wrapper { + .crop-search-results-wrapper { display: grid; gap: 1.5rem 1rem; grid-template-columns: repeat(2, 1fr); @@ -46,6 +33,20 @@ color: $white; font-size: 1.2rem !important; } + img { + position: absolute; + top: 1rem; + left: 1rem; + height: 4rem; + width: 4rem; + filter: drop-shadow(0 0 15px $translucent5) drop-shadow(0 0 10px $translucent5) drop-shadow(0 0 3px $translucent5); + &.center { + top: calc(50% - 3rem); + left: calc(50% - 3rem); + width: 6rem; + height: 6rem; + } + } } } @@ -100,10 +101,29 @@ p { font-weight: bold; text-transform: none; + white-space: nowrap; } } + img { + width: 100%; + border-radius: 0.5rem; + } + } + .crop-info-grid { + grid-template-columns: auto auto; } .crop-info-field-data { font-size: 1.3rem; + margin-left: 2.5rem; + } +} + +.info-box { + background: var(--secondary-bg); + border-radius: 0.5rem; + padding: 1rem; + align-content: baseline; + &:has(.crop-companions) { + grid-column: span 2; } } diff --git a/frontend/css/panels/points.scss b/frontend/css/panels/points.scss index c4519a2641..fb3c45573a 100644 --- a/frontend/css/panels/points.scss +++ b/frontend/css/panels/points.scss @@ -11,3 +11,7 @@ } } } + +.row.edit-point-location { + align-items: end; +} diff --git a/frontend/curves/chart.tsx b/frontend/curves/chart.tsx index 6708c30526..1e68ceba62 100644 --- a/frontend/curves/chart.tsx +++ b/frontend/curves/chart.tsx @@ -274,7 +274,7 @@ const XAxis = (props: XAxisProps) => { {day})} { {value} } ; })} diff --git a/frontend/external_urls.ts b/frontend/external_urls.ts index e06154ff89..49d9d88a9a 100644 --- a/frontend/external_urls.ts +++ b/frontend/external_urls.ts @@ -19,7 +19,6 @@ enum FbosFile { export namespace ExternalUrl { const GITHUB = "https://github.com"; const GITHUB_RAW = "https://raw.githubusercontent.com"; - const OPENFARM = "https://openfarm.cc"; const DOCS_HUB = "https://docs.farm.bot"; const GENESIS_DOCS = "https://genesis.farm.bot"; const EXPRESS_DOCS = "https://express.farm.bot"; @@ -72,12 +71,6 @@ export namespace ExternalUrl { export const solar = SOLAR; export const raisedBed = RAISED_BED; - export namespace OpenFarm { - export const cropApi = `${OPENFARM}/api/v1/crops/`; - export const cropBrowse = `${OPENFARM}/crops/`; - export const newCrop = `${OPENFARM}/en/crops/new`; - } - export namespace Video { export const desktop = `${SHOPIFY_CDN}/Farm_Designer_Loop.mp4?9552037556691879018`; diff --git a/frontend/farm_designer/__tests__/location_info_test.tsx b/frontend/farm_designer/__tests__/location_info_test.tsx index e29f625331..f63b4794a7 100644 --- a/frontend/farm_designer/__tests__/location_info_test.tsx +++ b/frontend/farm_designer/__tests__/location_info_test.tsx @@ -122,7 +122,7 @@ describe("", () => { wrapper.find(".plant-search-item").simulate("mouseEnter"); expect(p.dispatch).toHaveBeenCalledWith({ type: Actions.TOGGLE_HOVERED_PLANT, - payload: { icon: "", plantUUID: "plantUuid" }, + payload: { plantUUID: "plantUuid" }, }); jest.clearAllMocks(); wrapper.find(".point-search-item").simulate("mouseEnter"); diff --git a/frontend/farm_designer/__tests__/plant_test.ts b/frontend/farm_designer/__tests__/plant_test.ts index 3684f271f1..9fb3d4f5df 100644 --- a/frontend/farm_designer/__tests__/plant_test.ts +++ b/frontend/farm_designer/__tests__/plant_test.ts @@ -1,4 +1,4 @@ -import { Plant } from "../plant"; +import { Plant, verifiedCropSlug } from "../plant"; describe("Plant()", () => { it("returns defaults", () => { @@ -21,3 +21,11 @@ describe("Plant()", () => { }); }); }); + +describe("verifiedCropSlug()", () => { + it("returns verified crop slug", () => { + expect(verifiedCropSlug(undefined)).toEqual("not-set"); + expect(verifiedCropSlug("mint")).toEqual("mint"); + expect(verifiedCropSlug("random")).not.toEqual("random"); + }); +}); diff --git a/frontend/farm_designer/__tests__/reducer_test.ts b/frontend/farm_designer/__tests__/reducer_test.ts index e0b6970b1d..e4bc45b2a9 100644 --- a/frontend/farm_designer/__tests__/reducer_test.ts +++ b/frontend/farm_designer/__tests__/reducer_test.ts @@ -1,13 +1,8 @@ import { designer } from "../reducer"; import { Actions } from "../../constants"; import { ReduxAction } from "../../redux/interfaces"; -import { - HoveredPlantPayl, DrawnPointPayl, CropLiveSearchResult, DrawnWeedPayl, -} from "../interfaces"; +import { HoveredPlantPayl, DrawnPointPayl, DrawnWeedPayl } from "../interfaces"; import { BotPosition } from "../../devices/interfaces"; -import { - fakeCropLiveSearchResult, -} from "../../__test_support__/fake_crop_search_result"; import { fakeDesignerState } from "../../__test_support__/fake_designer_state"; import { PointGroupSortType } from "farmbot/dist/resources/api_resources"; import { PlantStage, PointType } from "farmbot"; @@ -24,7 +19,6 @@ describe("designer reducer", () => { }; const newState = designer(oldState(), action); expect(newState.cropSearchQuery).toEqual("apple"); - expect(newState.cropSearchInProgress).toEqual(true); }); it("selects points", () => { @@ -49,13 +43,12 @@ describe("designer reducer", () => { const action: ReduxAction = { type: Actions.TOGGLE_HOVERED_PLANT, payload: { - icon: "icon", plantUUID: "plantUuid" } }; const newState = designer(oldState(), action); expect(newState.hoveredPlant).toEqual({ - icon: "icon", plantUUID: "plantUuid" + plantUUID: "plantUuid" }); }); @@ -113,6 +106,15 @@ describe("designer reducer", () => { expect(newState.cropPlantedAt).toEqual("2020-01-20T20:00:00.000Z"); }); + it("sets crop radius", () => { + const action: ReduxAction = { + type: Actions.SET_CROP_RADIUS, + payload: 100, + }; + const newState = designer(oldState(), action); + expect(newState.cropRadius).toEqual(100); + }); + it("sets distance indicator", () => { const action: ReduxAction = { type: Actions.SET_DISTANCE_INDICATOR, @@ -275,18 +277,6 @@ describe("designer reducer", () => { expect(newState.openedSavedGarden).toEqual(payload); }); - it("stores new OpenFarm assets", () => { - const payload: CropLiveSearchResult[] = [ - fakeCropLiveSearchResult(), - ]; - const action: ReduxAction = { - type: Actions.OF_SEARCH_RESULTS_OK, payload - }; - const newState = designer(oldState(), action); - expect(newState.cropSearchResults).toEqual(payload); - expect(newState.cropSearchInProgress).toEqual(false); - }); - it("sets companion index", () => { const action: ReduxAction = { type: Actions.SET_COMPANION_INDEX, payload: 1, @@ -295,24 +285,6 @@ describe("designer reducer", () => { expect(newState.companionIndex).toEqual(1); }); - it("starts search", () => { - const action: ReduxAction = { - type: Actions.OF_SEARCH_RESULTS_START, payload: undefined - }; - const newState = designer(oldState(), action); - expect(newState.cropSearchInProgress).toEqual(true); - }); - - it("ends search", () => { - const state = oldState(); - state.cropSearchInProgress = true; - const action: ReduxAction = { - type: Actions.OF_SEARCH_RESULTS_NO, payload: undefined - }; - const newState = designer(state, action); - expect(newState.cropSearchInProgress).toEqual(false); - }); - it("sets plant type change id", () => { const state = oldState(); state.plantTypeChangeId = undefined; diff --git a/frontend/farm_designer/__tests__/search_selectors_test.ts b/frontend/farm_designer/__tests__/search_selectors_test.ts deleted file mode 100644 index 5dca2a5b4b..0000000000 --- a/frontend/farm_designer/__tests__/search_selectors_test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { findBySlug } from "../search_selectors"; -import { - fakeCropLiveSearchResult, -} from "../../__test_support__/fake_crop_search_result"; -import { FilePath } from "../../internal_urls"; - -describe("findBySlug()", () => { - it("returns crop default result", () => { - const result = findBySlug([fakeCropLiveSearchResult()], "some-crop"); - expect(result).toEqual({ - crop: expect.objectContaining({ name: "Some Crop" }), - images: [FilePath.DEFAULT_ICON], - companions: [], - }); - }); - - it("returns crop default result: no slug provided", () => { - const result = findBySlug([fakeCropLiveSearchResult()]); - expect(result).toEqual({ - crop: expect.objectContaining({ name: "" }), - images: [FilePath.DEFAULT_ICON], - companions: [], - }); - }); - - it("returns matched result", () => { - const result = findBySlug([fakeCropLiveSearchResult()], "mint"); - expect(result).toEqual({ - crop: expect.objectContaining({ name: "Mint" }), - images: ["fake-mint-svg"], - companions: [ - { name: "Strawberry", slug: "strawberry", svg_icon: "fake-strawberry-svg" }, - ], - }); - }); -}); diff --git a/frontend/farm_designer/__tests__/state_to_props_test.ts b/frontend/farm_designer/__tests__/state_to_props_test.ts index ee1d4bf7dd..bd0be15333 100644 --- a/frontend/farm_designer/__tests__/state_to_props_test.ts +++ b/frontend/farm_designer/__tests__/state_to_props_test.ts @@ -23,7 +23,7 @@ describe("mapStateToProps()", () => { it("hovered plantUUID is undefined", () => { const state = fakeState(); state.resources.consumers.farm_designer.hoveredPlant = { - plantUUID: "x", icon: "" + plantUUID: "x" }; expect(mapStateToProps(state).hoveredPlant).toBeFalsy(); }); diff --git a/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx b/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx index 87107d89c8..ab47204a77 100644 --- a/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx +++ b/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx @@ -12,7 +12,6 @@ import { render } from "@testing-library/react"; import { ThreeDGarden } from "../../three_d_garden"; import { clone } from "lodash"; import { INITIAL } from "../../three_d_garden/config"; -import { ASSETS } from "../../three_d_garden/constants"; describe("", () => { const fakeProps = (): ThreeDGardenMapProps => ({ @@ -23,17 +22,13 @@ describe("", () => { sourceFbosConfig: () => ({ value: 0, consistent: true }), designer: fakeDesignerState(), plants: [fakePlant()], + dispatch: jest.fn(), + getWebAppConfigValue: jest.fn(), + curves: [], }); it("converts props", () => { const p = fakeProps(); - const plant0 = fakePlant(); - plant0.body.name = "Spinach"; - plant0.body.openfarm_slug = "spinach"; - const plant1 = fakePlant(); - plant1.body.name = "Unknown"; - plant1.body.openfarm_slug = "not-set"; - p.plants = [plant0, plant1]; render(); const expectedConfig = clone(INITIAL); expectedConfig.bedWallThickness = 1; @@ -59,24 +54,7 @@ describe("", () => { expectedConfig.zoomBeacons = false; expect(ThreeDGarden).toHaveBeenCalledWith({ config: expectedConfig, - plants: [ - { - icon: ASSETS.icons.spinach, - label: "Spinach", - size: 50, - spread: 0, - x: 101, - y: 201, - }, - { - icon: ASSETS.icons.arugula, - label: "Unknown", - size: 50, - spread: 0, - x: 101, - y: 201, - }, - ], + addPlantProps: expect.any(Object), }, {}); }); }); diff --git a/frontend/farm_designer/__tests__/util_test.ts b/frontend/farm_designer/__tests__/util_test.ts index 1b97123302..ecaa21e466 100644 --- a/frontend/farm_designer/__tests__/util_test.ts +++ b/frontend/farm_designer/__tests__/util_test.ts @@ -1,9 +1,4 @@ -let mockPromise: Promise<{} | void> = Promise.resolve(); -jest.mock("axios", () => ({ get: () => mockPromise })); - -import { executableType, OFCropFetch, OFSearch } from "../util"; -import { Actions } from "../../constants"; -import { FilePath } from "../../internal_urls"; +import { executableType } from "../util"; describe("executableType", () => { it("handles expected values", () => { @@ -15,101 +10,3 @@ describe("executableType", () => { expect(() => executableType("Nope")).toThrow(); }); }); - -describe("OFSearch()", () => { - const START = expect.objectContaining({ - type: Actions.OF_SEARCH_RESULTS_START - }); - const NO = expect.objectContaining({ type: Actions.OF_SEARCH_RESULTS_NO }); - - it("searches: no image", async () => { - mockPromise = Promise.resolve({ data: { data: [{ attributes: {} }] } }); - const dispatch = jest.fn(); - await OFSearch("mint")(dispatch); - expect(dispatch).toHaveBeenCalledWith(START); - await expect(dispatch).toHaveBeenCalledWith({ - type: Actions.OF_SEARCH_RESULTS_OK, payload: [ - { crop: {}, images: [FilePath.DEFAULT_ICON], companions: [] }] - }); - await expect(dispatch).not.toHaveBeenCalledWith(NO); - }); - - it("searches: image", async () => { - mockPromise = Promise.resolve({ - data: { - included: [ - { - id: 0, - type: "crops-pictures", - attributes: { thumbnail_url: "thumbnail_url" }, - }, - { - id: 0, - type: "crops", - attributes: { name: "name", slug: "slug", svg_icon: "svg_icon" }, - }, - ], - data: [{ - attributes: {}, - relationships: { pictures: { data: [{ id: 0 }] } } - }] - } - }); - const dispatch = jest.fn(); - await OFSearch("mint")(dispatch); - expect(dispatch).toHaveBeenCalledWith(START); - await expect(dispatch).toHaveBeenCalledWith({ - type: Actions.OF_SEARCH_RESULTS_OK, payload: [{ - crop: {}, - images: ["thumbnail_url"], - companions: [{ name: "name", slug: "slug", svg_icon: "svg_icon" }] - }] - }); - await expect(dispatch).not.toHaveBeenCalledWith(NO); - }); - - it("searches: image, specific", async () => { - mockPromise = Promise.resolve({ - data: { - included: [ - { - id: 0, - type: "crops-pictures", - attributes: { thumbnail_url: "thumbnail_url" }, - }, - { - id: 0, - type: "crops", - attributes: { name: "name", slug: "slug", svg_icon: "svg_icon" }, - }, - ], - data: { - attributes: {}, - relationships: { pictures: { data: [{ id: 0 }] } } - } - } - }); - const dispatch = jest.fn(); - await OFCropFetch("mint")(dispatch); - expect(dispatch).toHaveBeenCalledWith(START); - await expect(dispatch).toHaveBeenCalledWith({ - type: Actions.OF_SEARCH_RESULTS_OK, payload: [{ - crop: {}, - images: ["thumbnail_url"], - companions: [{ name: "name", slug: "slug", svg_icon: "svg_icon" }] - }] - }); - await expect(dispatch).not.toHaveBeenCalledWith(NO); - }); - - it("fails search", async () => { - mockPromise = Promise.reject(); - const dispatch = jest.fn(); - await OFCropFetch("mint")(dispatch); - expect(dispatch).toHaveBeenCalledWith(START); - await expect(dispatch).not.toHaveBeenCalledWith(expect.objectContaining({ - type: Actions.OF_SEARCH_RESULTS_OK - })); - await expect(dispatch).toHaveBeenCalledWith(NO); - }); -}); diff --git a/frontend/farm_designer/index.tsx b/frontend/farm_designer/index.tsx index 9b7ca0b59b..cb2194b5d0 100755 --- a/frontend/farm_designer/index.tsx +++ b/frontend/farm_designer/index.tsx @@ -213,7 +213,10 @@ export class RawFarmDesigner sourceFbosConfig={this.props.sourceFbosConfig} gridOffset={gridOffset} mapTransformProps={this.mapTransformProps} - botSize={this.props.botSize} /> + botSize={this.props.botSize} + dispatch={this.props.dispatch} + curves={this.props.curves} + getWebAppConfigValue={this.props.getConfigValue} /> :
(dispatch: Function) => void; - export interface CropCatalogProps { - cropSearchQuery: string | undefined; dispatch: Function; - cropSearchResults: CropLiveSearchResult[]; - openfarmSearch: OpenfarmSearch; - cropSearchInProgress: boolean; plant: TaggedPlantPointer | undefined; bulkPlantSlug: string | undefined; hoveredPlant: HoveredPlantPayl; + cropSearchQuery: string; } export interface CropInfoProps { dispatch: Function; designer: DesignerState; - openfarmCropFetch: OpenfarmSearch; botPosition: BotPosition; xySwap: boolean; getConfigValue: GetWebAppConfigValue; diff --git a/frontend/farm_designer/map/__tests__/actions_test.ts b/frontend/farm_designer/map/__tests__/actions_test.ts index cd8366c1a1..42c318409a 100644 --- a/frontend/farm_designer/map/__tests__/actions_test.ts +++ b/frontend/farm_designer/map/__tests__/actions_test.ts @@ -21,7 +21,6 @@ import { import { MovePointToProps, MovePointsProps } from "../../interfaces"; import { edit } from "../../../api/crud"; import { Actions } from "../../../constants"; -import { svgToUrl } from "../../../open_farm/icons"; import { fakeState } from "../../../__test_support__/fake_state"; import { GetState } from "../../../redux/interfaces"; import { @@ -29,7 +28,7 @@ import { } from "../../../__test_support__/resource_index_builder"; import { overwriteGroup } from "../../../point_groups/actions"; import { mockDispatch } from "../../../__test_support__/fake_dispatch"; -import { FilePath, Path } from "../../../internal_urls"; +import { Path } from "../../../internal_urls"; describe("movePoints", () => { it.each<[string, Record<"x" | "y", number>, Record<"x" | "y", number>]>([ @@ -113,18 +112,9 @@ describe("setDragIcon()", () => { it("sets the drag icon", () => { const setDragImage = jest.fn(); const e = { currentTarget: new Image(), dataTransfer: { setDragImage } }; - setDragIcon("icon")(e); + setDragIcon("mint")(e); const img = new Image(); - img.src = svgToUrl("icon"); - expect(setDragImage).toHaveBeenCalledWith(img, 0, 0); - }); - - it("sets a default drag icon", () => { - const setDragImage = jest.fn(); - const e = { currentTarget: new Image(), dataTransfer: { setDragImage } }; - setDragIcon(undefined)(e); - const img = new Image(); - img.src = FilePath.DEFAULT_ICON; + img.src = "/crops/icons/mint.avif"; expect(setDragImage).toHaveBeenCalledWith(img, 0, 0); }); }); @@ -134,9 +124,9 @@ describe("clickMapPlant", () => { const state = fakeState(); const dispatch = jest.fn(); const getState: GetState = jest.fn(() => state); - clickMapPlant("fakeUuid", "fakeIcon")(dispatch, getState); + clickMapPlant("fakeUuid")(dispatch, getState); expect(dispatch).toHaveBeenCalledWith(selectPoint(["fakeUuid"])); - expect(dispatch).toHaveBeenCalledWith(setHoveredPlant("fakeUuid", "fakeIcon")); + expect(dispatch).toHaveBeenCalledWith(setHoveredPlant("fakeUuid")); expect(dispatch).toHaveBeenCalledTimes(2); }); @@ -149,7 +139,7 @@ describe("clickMapPlant", () => { state.resources = buildResourceIndex([plant]); const dispatch = mockDispatch(); const getState: GetState = jest.fn(() => state); - clickMapPlant(plant.uuid, "fakeIcon")(dispatch, getState); + clickMapPlant(plant.uuid)(dispatch, getState); expect(overwriteGroup).toHaveBeenCalledWith(mockGroup, expect.objectContaining({ name: "Fake", point_ids: [1, 23] @@ -164,7 +154,7 @@ describe("clickMapPlant", () => { state.resources = buildResourceIndex([]); const dispatch = mockDispatch(); const getState: GetState = jest.fn(() => state); - clickMapPlant("missing plant uuid", "fakeIcon")(dispatch, getState); + clickMapPlant("missing plant uuid")(dispatch, getState); expect(overwriteGroup).not.toHaveBeenCalled(); expect(dispatch).toHaveBeenCalledTimes(1); }); @@ -178,7 +168,7 @@ describe("clickMapPlant", () => { state.resources = buildResourceIndex([plant]); const dispatch = mockDispatch(); const getState: GetState = jest.fn(() => state); - clickMapPlant(plant.uuid, "fakeIcon")(dispatch, getState); + clickMapPlant(plant.uuid)(dispatch, getState); expect(overwriteGroup).toHaveBeenCalledWith(mockGroup, expect.objectContaining({ name: "Fake", point_ids: [1] @@ -194,7 +184,7 @@ describe("clickMapPlant", () => { state.resources = buildResourceIndex([plant]); const dispatch = jest.fn(); const getState: GetState = jest.fn(() => state); - clickMapPlant(plant.uuid, "fakeIcon")(dispatch, getState); + clickMapPlant(plant.uuid)(dispatch, getState); expect(dispatch).toHaveBeenCalledWith({ type: Actions.SELECT_POINT, payload: [plant.uuid] }); @@ -210,7 +200,7 @@ describe("clickMapPlant", () => { state.resources.consumers.farm_designer.selectedPoints = [plant.uuid]; const dispatch = jest.fn(); const getState: GetState = jest.fn(() => state); - clickMapPlant(plant.uuid, "fakeIcon")(dispatch, getState); + clickMapPlant(plant.uuid)(dispatch, getState); expect(dispatch).toHaveBeenCalledWith({ type: Actions.SELECT_POINT, payload: [] }); diff --git a/frontend/farm_designer/map/__tests__/garden_map_test.tsx b/frontend/farm_designer/map/__tests__/garden_map_test.tsx index c59de2d539..dca07cda3c 100644 --- a/frontend/farm_designer/map/__tests__/garden_map_test.tsx +++ b/frontend/farm_designer/map/__tests__/garden_map_test.tsx @@ -319,7 +319,7 @@ describe("", () => { location.pathname = Path.mock(Path.plants()); const p = fakeProps(); p.designer.selectedPoints = []; - p.designer.hoveredPlant = { icon: "", plantUUID: undefined }; + p.designer.hoveredPlant = { plantUUID: undefined }; p.designer.hoveredPoint = undefined; p.designer.hoveredToolSlot = undefined; const wrapper = mount(); diff --git a/frontend/farm_designer/map/actions.ts b/frontend/farm_designer/map/actions.ts index 8b63c1cbdd..8014bffef6 100644 --- a/frontend/farm_designer/map/actions.ts +++ b/frontend/farm_designer/map/actions.ts @@ -2,7 +2,6 @@ import { MovePointsProps, DraggableEvent, MovePointToProps } from "../interfaces import { defensiveClone } from "../../util"; import { edit } from "../../api/crud"; import { Actions } from "../../constants"; -import { svgToUrl } from "../../open_farm/icons"; import { Mode } from "../map/interfaces"; import { clamp, uniq } from "lodash"; import { GetState } from "../../redux/interfaces"; @@ -12,9 +11,10 @@ import { getMode } from "../map/util"; import { ResourceIndex, UUID } from "../../resources/interfaces"; import { selectAllPointGroups } from "../../resources/selectors"; import { overwriteGroup } from "../../point_groups/actions"; -import { FilePath, Path } from "../../internal_urls"; +import { Path } from "../../internal_urls"; import { NavigateFunction } from "react-router"; import { setPanelOpen } from "../panel_header"; +import { findIcon } from "../../crops/find"; export const movePoints = (payload: MovePointsProps) => (dispatch: Function) => { payload.points.map(point => { @@ -40,9 +40,9 @@ export const selectPoint = (payload: string[] | undefined) => { return { type: Actions.SELECT_POINT, payload }; }; -export const setHoveredPlant = (plantUUID: string | undefined, icon = "") => ({ +export const setHoveredPlant = (plantUUID: string | undefined) => ({ type: Actions.TOGGLE_HOVERED_PLANT, - payload: { plantUUID, icon } + payload: { plantUUID } }); const addOrRemoveFromGroup = @@ -75,7 +75,7 @@ const addOrRemoveFromSelection = return selectPoint(nextSelected); }; -export const clickMapPlant = (clickedPlantUuid: UUID, icon: string) => { +export const clickMapPlant = (clickedPlantUuid: UUID) => { return (dispatch: Function, getState: GetState) => { switch (getMode()) { case Mode.editGroup: @@ -88,7 +88,7 @@ export const clickMapPlant = (clickedPlantUuid: UUID, icon: string) => { break; default: dispatch(selectPoint([clickedPlantUuid])); - dispatch(setHoveredPlant(clickedPlantUuid, icon)); + dispatch(setHoveredPlant(clickedPlantUuid)); break; } }; @@ -113,9 +113,9 @@ export const closePlantInfo = ( }; export const setDragIcon = - (icon: string | undefined) => (e: DraggableEvent) => { + (slug: string) => (e: DraggableEvent) => { const dragImg = new Image(); - dragImg.src = icon ? svgToUrl(icon) : FilePath.DEFAULT_ICON; + dragImg.src = findIcon(slug); const width = dragImg.naturalWidth; const height = dragImg.naturalHeight; e.dataTransfer.setDragImage @@ -131,7 +131,7 @@ export const mapPointClickAction = switch (getMode()) { case Mode.editGroup: case Mode.boxSelect: - return dispatch(clickMapPlant(uuid, "")); + return dispatch(clickMapPlant(uuid)); default: dispatch(setPanelOpen(true)); return path && navigate(path); diff --git a/frontend/farm_designer/map/active_plant/__tests__/add_plant_icon_test.tsx b/frontend/farm_designer/map/active_plant/__tests__/add_plant_icon_test.tsx index 3d8441dc4c..17d01ac762 100644 --- a/frontend/farm_designer/map/active_plant/__tests__/add_plant_icon_test.tsx +++ b/frontend/farm_designer/map/active_plant/__tests__/add_plant_icon_test.tsx @@ -4,23 +4,23 @@ import { AddPlantIcon, AddPlantIconProps } from "../add_plant_icon"; import { fakeMapTransformProps, } from "../../../../__test_support__/map_transform_props"; +import { Path } from "../../../../internal_urls"; import { - fakeCropLiveSearchResult, -} from "../../../../__test_support__/fake_crop_search_result"; -import { svgToUrl } from "../../../../open_farm/icons"; -import { FilePath, Path } from "../../../../internal_urls"; + fakeDesignerState, +} from "../../../../__test_support__/fake_designer_state"; describe("", () => { const fakeProps = (): AddPlantIconProps => ({ + designer: fakeDesignerState(), cursorPosition: { x: 1, y: 2 }, - cropSearchResults: [fakeCropLiveSearchResult()], mapTransformProps: fakeMapTransformProps(), }); it("returns icon", () => { const wrapper = mount(); expect(wrapper.find("image").length).toEqual(1); - expect(wrapper.find("image").props().xlinkHref).toEqual(FilePath.DEFAULT_ICON); + expect(wrapper.find("image").props().xlinkHref) + .toEqual("/crops/icons/generic-plant.avif"); }); it("doesn't return icon", () => { @@ -33,11 +33,9 @@ describe("", () => { it("returns specific icon", () => { location.pathname = Path.mock(Path.cropSearch("mint")); const p = fakeProps(); - const result = fakeCropLiveSearchResult(); - result.crop.svg_icon = "fake_icon"; - p.cropSearchResults = [result]; const wrapper = mount(); expect(wrapper.find("image").length).toEqual(1); - expect(wrapper.find("image").props().xlinkHref).toEqual(svgToUrl("fake_icon")); + expect(wrapper.find("image").props().xlinkHref) + .toEqual("/crops/icons/mint.avif"); }); }); diff --git a/frontend/farm_designer/map/active_plant/__tests__/hovered_plant_test.tsx b/frontend/farm_designer/map/active_plant/__tests__/hovered_plant_test.tsx index be83b5c89f..75bff5bf04 100644 --- a/frontend/farm_designer/map/active_plant/__tests__/hovered_plant_test.tsx +++ b/frontend/farm_designer/map/active_plant/__tests__/hovered_plant_test.tsx @@ -24,7 +24,7 @@ describe("", () => { it("shows hovered plant icon", () => { const p = fakeProps(); - p.designer.hoveredPlant.icon = "fake icon"; + p.designer.hoveredPlant = { plantUUID: "plant" }; const wrapper = shallow(); const icon = wrapper.find("image").props(); expect(icon.visibility).toBeTruthy(); @@ -39,7 +39,7 @@ describe("", () => { it("shows hovered plant icon with hovered spread size", () => { const p = fakeProps(); - p.designer.hoveredPlant.icon = "fake icon"; + p.designer.hoveredPlant = { plantUUID: "plant" }; p.designer.hoveredSpread = 1000; const wrapper = shallow(); const icon = wrapper.find("image").props(); @@ -48,9 +48,9 @@ describe("", () => { it("shows hovered plant icon while dragging", () => { const p = fakeProps(); + p.designer.hoveredPlant = { plantUUID: "plant" }; p.isEditing = true; p.dragging = true; - p.designer.hoveredPlant.icon = "fake icon"; const wrapper = shallow(); const icon = wrapper.find("image").props(); expect(icon.visibility).toBeTruthy(); @@ -60,7 +60,7 @@ describe("", () => { it("shows animated hovered plant indicator", () => { const p = fakeProps(); - p.designer.hoveredPlant.icon = "fake icon"; + p.designer.hoveredPlant = { plantUUID: "plant" }; p.animate = true; const wrapper = shallow(); expect(wrapper.find(".plant-indicator").length).toEqual(1); @@ -68,7 +68,7 @@ describe("", () => { it("shows selected plant indicators", () => { const p = fakeProps(); - p.designer.hoveredPlant.icon = "fake icon"; + p.designer.hoveredPlant = { plantUUID: "plant" }; p.currentPlant = fakePlant(); const wrapper = shallow(); expect(wrapper.find("#selected-plant-spread-indicator").length).toEqual(1); @@ -77,7 +77,7 @@ describe("", () => { expect(wrapper.find("Circle").props().selected).toBeTruthy(); expect(wrapper.find("SpreadCircle").length).toEqual(1); expect(wrapper.find("SpreadCircle").html()) - .toContain("cx=\"100\" cy=\"200\" r=\"0\""); + .toContain("cx=\"100\" cy=\"200\" r=\"150\""); }); it("doesn't show hovered plant icon", () => { diff --git a/frontend/farm_designer/map/active_plant/add_plant_icon.tsx b/frontend/farm_designer/map/active_plant/add_plant_icon.tsx index 88180fcc96..464f1b7282 100644 --- a/frontend/farm_designer/map/active_plant/add_plant_icon.tsx +++ b/frontend/farm_designer/map/active_plant/add_plant_icon.tsx @@ -2,25 +2,22 @@ import React from "react"; import { round, scaleIcon, transformXY } from "../util"; import { AxisNumberProperty, MapTransformProps } from "../interfaces"; import { Path } from "../../../internal_urls"; -import { findBySlug } from "../../search_selectors"; -import { CropLiveSearchResult } from "../../interfaces"; -import { svgToUrl } from "../../../open_farm/icons"; +import { findIcon } from "../../../crops/find"; +import { DesignerState } from "../../interfaces"; +import { DEFAULT_PLANT_RADIUS } from "../../plant"; export interface AddPlantIconProps { + designer: DesignerState; cursorPosition: AxisNumberProperty | undefined; - cropSearchResults: CropLiveSearchResult[]; mapTransformProps: MapTransformProps; } export const AddPlantIcon = (props: AddPlantIconProps) => { if (!props.cursorPosition) { return ; } const { x, y } = props.cursorPosition; - const radius = 25; const { qx, qy } = transformXY(round(x), round(y), props.mapTransformProps); - const iconRadius = scaleIcon(radius); - const crop = Path.getSlug(Path.cropSearch()); - const result = findBySlug(props.cropSearchResults, crop); - const icon = svgToUrl(result.crop.svg_icon); + const iconRadius = scaleIcon(props.designer.cropRadius || DEFAULT_PLANT_RADIUS); + const icon = findIcon(Path.getCropSlug()); return ; AddPlantIcon = () => getMode() == Mode.clickToAdd ? : ; diff --git a/frontend/farm_designer/map/interfaces.ts b/frontend/farm_designer/map/interfaces.ts index bb6093942c..b8fdd55d2b 100644 --- a/frontend/farm_designer/map/interfaces.ts +++ b/frontend/farm_designer/map/interfaces.ts @@ -89,7 +89,6 @@ export interface GardenPlantProps { } export interface GardenPlantState { - icon: string; hover: boolean; } @@ -183,6 +182,7 @@ export interface SpreadOverlapHelperProps { activeDragXY: BotPosition | undefined; activeDragSpread: number | undefined; showOverlapValues?: boolean; + inactiveSpread: number; } /** Garden map interaction modes. */ diff --git a/frontend/farm_designer/map/layers/plants/__tests__/garden_plant_test.tsx b/frontend/farm_designer/map/layers/plants/__tests__/garden_plant_test.tsx index 16a12b0728..f2a7bb50d8 100644 --- a/frontend/farm_designer/map/layers/plants/__tests__/garden_plant_test.tsx +++ b/frontend/farm_designer/map/layers/plants/__tests__/garden_plant_test.tsx @@ -8,7 +8,6 @@ import { fakeMapTransformProps, } from "../../../../../__test_support__/map_transform_props"; import { SpecialStatus } from "farmbot"; -import { cachedCrop } from "../../../../../open_farm/cached_crop"; describe("", () => { const fakeProps = (): GardenPlantProps => ({ @@ -133,15 +132,4 @@ describe("", () => { const wrapper = shallow(); expect(wrapper.find("image").props().filter).toEqual("url(#grayscale)"); }); - - it("fetches icon", () => { - const p = fakeProps(); - p.plant.body.openfarm_slug = "slug"; - const np = fakeProps(); - np.plant.body.openfarm_slug = "new-slug"; - const wrapper = shallow(); - wrapper.setProps(np); - expect(cachedCrop).toHaveBeenCalledWith("slug"); - expect(cachedCrop).toHaveBeenCalledWith("new-slug"); - }); }); diff --git a/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts b/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts index 088be097bd..da67f4eb92 100644 --- a/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts +++ b/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts @@ -4,16 +4,16 @@ jest.mock("../../../../../api/crud", () => ({ initSave: jest.fn(), })); -const mockSpreads: { [x: string]: number } = { mint: 100 }; -jest.mock("../../../../../open_farm/cached_crop", () => ({ - cachedCrop: jest.fn(p => Promise.resolve({ spread: mockSpreads[p] })), -})); - jest.mock("../../../actions", () => ({ movePoints: jest.fn(), movePointTo: jest.fn(), })); +import { FAKE_CROPS } from "../../../../../__test_support__/fake_crops"; +jest.mock("../../../../../crops/constants", () => ({ + CROPS: FAKE_CROPS, +})); + import { newPlantKindAndBody, NewPlantKindAndBodyProps, maybeSavePlantLocation, MaybeSavePlantLocationProps, @@ -27,14 +27,10 @@ import { fakeCurve, fakePlant, } from "../../../../../__test_support__/fake_state/resources"; import { edit, save, initSave } from "../../../../../api/crud"; -import { cachedCrop } from "../../../../../open_farm/cached_crop"; import { fakeMapTransformProps, } from "../../../../../__test_support__/map_transform_props"; import { movePointTo, movePoints } from "../../../actions"; -import { - fakeCropLiveSearchResult, -} from "../../../../../__test_support__/fake_crop_search_result"; import { error } from "../../../../../toast/toast"; import { BotOriginQuadrant } from "../../../../interfaces"; import { @@ -102,7 +98,6 @@ describe("dropPlant()", () => { const fakeProps = (): DropPlantProps => { const designer = fakeDesignerState(); - designer.cropSearchResults = [fakeCropLiveSearchResult()]; return { designer, gardenCoords: { x: 10, y: 20 }, @@ -136,22 +131,8 @@ describe("dropPlant()", () => { expect(console.log).toHaveBeenCalledWith("Missing slug."); }); - it("doesn't drop plant: no crop", () => { - console.log = jest.fn(); - location.pathname = Path.mock(Path.cropSearch("mint")); - const p = fakeProps(); - p.designer.companionIndex = 1; - p.designer.cropSearchResults = []; - dropPlant(p); - expect(initSave).not.toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith("Missing crop."); - }); - it("finds curves", () => { const p = fakeProps(); - const result = fakeCropLiveSearchResult(); - result.crop.slug = "mint"; - p.designer.cropSearchResults = [result]; const plant = fakePlant(); plant.body.openfarm_slug = "mint"; plant.body.water_curve_id = 1; @@ -184,9 +165,6 @@ describe("dropPlant()", () => { it("doesn't find curves", () => { const p = fakeProps(); - const result = fakeCropLiveSearchResult(); - result.crop.slug = "mint"; - p.designer.cropSearchResults = [result]; p.plants = []; p.curves = []; dropPlant(p); @@ -359,7 +337,6 @@ describe("beginPlantDrag()", () => { it("starts drag: plant", () => { beginPlantDrag(fakeProps()); - expect(cachedCrop).toHaveBeenCalled(); }); it("starts drag: not plant", () => { @@ -367,7 +344,6 @@ describe("beginPlantDrag()", () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any p.plant = undefined as any; beginPlantDrag(p); - expect(cachedCrop).not.toHaveBeenCalled(); }); }); diff --git a/frontend/farm_designer/map/layers/plants/__tests__/plant_layer_test.tsx b/frontend/farm_designer/map/layers/plants/__tests__/plant_layer_test.tsx index 21de92d57b..4a4c85b1ae 100644 --- a/frontend/farm_designer/map/layers/plants/__tests__/plant_layer_test.tsx +++ b/frontend/farm_designer/map/layers/plants/__tests__/plant_layer_test.tsx @@ -10,7 +10,7 @@ import { import { svgMount } from "../../../../../__test_support__/svg_mount"; import { shallow } from "enzyme"; import { GardenPlant } from "../garden_plant"; -import { FilePath, Path } from "../../../../../internal_urls"; +import { Path } from "../../../../../internal_urls"; import { Actions } from "../../../../../constants"; import { mockDispatch } from "../../../../../__test_support__/fake_dispatch"; @@ -41,7 +41,7 @@ describe("", () => { ["soil-cloud", "plant-icon", "image visibility=\"visible\"", - FilePath.DEFAULT_ICON, + "icon", "height=\"40\" width=\"40\" x=\"80\" y=\"180\"", "drag-helpers", "plant-icon", diff --git a/frontend/farm_designer/map/layers/plants/garden_plant.tsx b/frontend/farm_designer/map/layers/plants/garden_plant.tsx index e75efd227f..83079ba252 100644 --- a/frontend/farm_designer/map/layers/plants/garden_plant.tsx +++ b/frontend/farm_designer/map/layers/plants/garden_plant.tsx @@ -1,35 +1,21 @@ import React from "react"; import { GardenPlantProps, GardenPlantState } from "../../interfaces"; -import { svgToUrl } from "../../../../open_farm/icons"; import { transformXY, scaleIcon } from "../../util"; import { DragHelpers } from "../../active_plant/drag_helpers"; import { Color } from "../../../../ui"; import { Actions } from "../../../../constants"; -import { cachedCrop } from "../../../../open_farm/cached_crop"; import { clickMapPlant } from "../../actions"; import { Circle } from "./circle"; import { SpecialStatus } from "farmbot"; -import { FilePath } from "../../../../internal_urls"; +import { findIcon } from "../../../../crops/find"; export class GardenPlant extends React.Component> { - state: GardenPlantState = { icon: FilePath.DEFAULT_ICON, hover: false }; - - fetchIcon = () => { - cachedCrop(this.props.plant.body.openfarm_slug) - .then(({ svg_icon }) => { - this.setState({ icon: svgToUrl(svg_icon) }); - }); - }; - - componentDidMount = () => this.fetchIcon(); - componentDidUpdate = (prevProps: GardenPlantProps) => - this.props.plant.body.openfarm_slug != prevProps.plant.body.openfarm_slug && - this.fetchIcon(); + state: GardenPlantState = { hover: false }; click = () => { - this.props.dispatch(clickMapPlant(this.props.uuid, this.state.icon)); + this.props.dispatch(clickMapPlant(this.props.uuid)); }; iconHover = (action: "start" | "end") => { @@ -56,10 +42,11 @@ export class GardenPlant extends activeDragXY, zoomLvl, animate, editing, hovered, hoveredSpread, } = this.props; const { id, x, y } = plant.body; - const { icon, hover } = this.state; + const { hover } = this.state; const radius = (current || selected) && hoveredSpread ? hoveredSpread / 2 : plant.body.radius; + const icon = findIcon(plant.body.openfarm_slug); const plantIconSize = scaleIcon(radius); const iconRadius = hover ? plantIconSize * 1.1 : plantIconSize; const { qx, qy } = transformXY(x, y, mapTransformProps); diff --git a/frontend/farm_designer/map/layers/plants/plant_actions.ts b/frontend/farm_designer/map/layers/plants/plant_actions.ts index 184616c6e3..822b9a17ee 100644 --- a/frontend/farm_designer/map/layers/plants/plant_actions.ts +++ b/frontend/farm_designer/map/layers/plants/plant_actions.ts @@ -8,16 +8,15 @@ import { isNumber } from "lodash"; import { DesignerState, GardenMapState, MovePointsProps, } from "../../../interfaces"; -import { findBySlug } from "../../../search_selectors"; import { round, defaultSpreadCmDia } from "../../util"; import { movePointTo, movePoints } from "../../actions"; -import { cachedCrop } from "../../../../open_farm/cached_crop"; import { t } from "../../../../i18next_wrapper"; import { error } from "../../../../toast/toast"; import { TaggedCurve, TaggedPlantTemplate, TaggedPoint } from "farmbot"; import { Path } from "../../../../internal_urls"; import { GetWebAppConfigValue } from "../../../../config_storage/actions"; import { NumericSetting } from "../../../../session_keys"; +import { findCrop } from "../../../../crops/find"; export interface NewPlantKindAndBodyProps { x: number; @@ -55,7 +54,7 @@ export const newPlantKindAndBody = (props: NewPlantKindAndBodyProps): { y: props.y, openfarm_slug: props.slug, name: props.cropName, - radius: DEFAULT_PLANT_RADIUS, + radius: props.designer.cropRadius, depth: props.depth, plant_stage: props.designer.cropStage, planted_at: props.designer.cropPlantedAt, @@ -117,17 +116,17 @@ export const dropPlant = (props: DropPlantProps) => { const { gardenCoords, gridSize, dispatch, getConfigValue, } = props; - const { companionIndex, cropSearchResults, openedSavedGarden } = props.designer; + const { companionIndex, openedSavedGarden } = props.designer; if (gardenCoords) { - const slug = Path.getSlug(Path.plants(1)); + const slug = Path.getCropSlug(); if (!slug) { console.log("Missing slug."); return; } - const crop = isNumber(companionIndex) - ? cropSearchResults[0]?.companions[companionIndex] - : findBySlug(cropSearchResults, slug).crop; - if (!crop) { console.log("Missing crop."); return; } + const cropSlug = isNumber(companionIndex) + ? findCrop(slug).companions[companionIndex] + : slug; + const crop = findCrop(cropSlug); createPlant({ cropName: crop.name, - slug: crop.slug, + slug: cropSlug, gardenCoords, gridSize, dispatch, @@ -213,9 +212,8 @@ export const setActiveSpread = (props: SetActiveSpreadProps): void => { const defaultSpread = props.selectedPlant ? defaultSpreadCmDia(props.selectedPlant.body.radius) : 0; - cachedCrop(props.slug) - .then(({ spread }) => - props.setMapState({ activeDragSpread: spread || defaultSpread })); + const crop = findCrop(props.slug); + props.setMapState({ activeDragSpread: crop.spread || defaultSpread }); }; export interface BeginPlantDragProps { diff --git a/frontend/farm_designer/map/layers/spread/__tests__/spread_layer_test.tsx b/frontend/farm_designer/map/layers/spread/__tests__/spread_layer_test.tsx index 39444e1370..dc802edea3 100644 --- a/frontend/farm_designer/map/layers/spread/__tests__/spread_layer_test.tsx +++ b/frontend/farm_designer/map/layers/spread/__tests__/spread_layer_test.tsx @@ -8,7 +8,6 @@ import { fakeMapTransformProps, } from "../../../../../__test_support__/map_transform_props"; import { SpreadOverlapHelper } from "../spread_overlap_helper"; -import { cachedCrop } from "../../../../../open_farm/cached_crop"; describe("", () => { const fakeProps = (): SpreadLayerProps => ({ @@ -29,7 +28,7 @@ describe("", () => { const p = fakeProps(); const wrapper = shallow(); const layer = wrapper.find("#spread-layer"); - expect(layer.find("SpreadCircle").html()).toContain("r=\"0\""); + expect(layer.find("SpreadCircle").html()).toContain("r=\"150\""); }); it("toggles visibility off", () => { @@ -62,8 +61,7 @@ describe("", () => { it("uses spread value", () => { const wrapper = shallow(); - wrapper.setState({ spread: 20, loaded: true }); - expect(wrapper.find("circle").first().props().r).toEqual(100); + expect(wrapper.find("circle").first().props().r).toEqual(150); expect(wrapper.find("circle").first().hasClass("animate")).toBeTruthy(); expect(wrapper.find("circle").first().props().fill).toEqual("none"); }); @@ -73,7 +71,6 @@ describe("", () => { p.selected = true; p.hoveredSpread = 100; const wrapper = shallow(); - wrapper.setState({ spread: 1000, loaded: true }); expect(wrapper.find("circle").last().props().r).toEqual(50); }); @@ -84,7 +81,5 @@ describe("", () => { np.plant.body.openfarm_slug = "new-slug"; const wrapper = shallow(); wrapper.setProps(np); - expect(cachedCrop).toHaveBeenCalledWith("slug"); - expect(cachedCrop).toHaveBeenCalledWith("new-slug"); }); }); diff --git a/frontend/farm_designer/map/layers/spread/__tests__/spread_overlap_helper_test.tsx b/frontend/farm_designer/map/layers/spread/__tests__/spread_overlap_helper_test.tsx index 8c468419d0..633c624a95 100644 --- a/frontend/farm_designer/map/layers/spread/__tests__/spread_overlap_helper_test.tsx +++ b/frontend/farm_designer/map/layers/spread/__tests__/spread_overlap_helper_test.tsx @@ -28,7 +28,8 @@ describe("", () => { dragging: false, zoomLvl: 1, activeDragXY: { x: undefined, y: undefined, z: undefined }, - activeDragSpread: 25 + activeDragSpread: 25, + inactiveSpread: 25, }; } @@ -120,6 +121,7 @@ describe("", () => { it("shows overlap values", () => { const p = fakeProps(); + p.inactiveSpread = 0; p.activeDragXY = { x: 100, y: 100, z: 0 }; p.dragging = false; p.showOverlapValues = true; diff --git a/frontend/farm_designer/map/layers/spread/spread_layer.tsx b/frontend/farm_designer/map/layers/spread/spread_layer.tsx index 9665fdbdc8..cb8d73f297 100644 --- a/frontend/farm_designer/map/layers/spread/spread_layer.tsx +++ b/frontend/farm_designer/map/layers/spread/spread_layer.tsx @@ -1,10 +1,10 @@ import React from "react"; import { round, transformXY, defaultSpreadCmDia } from "../../util"; -import { cachedCrop } from "../../../../open_farm/cached_crop"; import { MapTransformProps, TaggedPlant } from "../../interfaces"; import { SpreadOverlapHelper } from "./spread_overlap_helper"; import { BotPosition } from "../../../../devices/interfaces"; import { Color } from "../../../../ui"; +import { findCrop } from "../../../../crops/find"; export interface SpreadLayerProps { visible: boolean; @@ -28,6 +28,7 @@ export function SpreadLayer(props: SpreadLayerProps) { return {plants.map(p => { const selected = p.uuid === currentPlant?.uuid; + const { spread } = findCrop(p.body.openfarm_slug); return {visible && ; @@ -60,54 +62,33 @@ export interface SpreadCircleProps { selected: boolean; } -interface SpreadCircleState { - spread: number | undefined; - loaded: boolean; -} - -export class SpreadCircle extends - React.Component { - state: SpreadCircleState = { spread: undefined, loaded: false }; - - fetchSpread = () => { - cachedCrop(this.props.plant.body.openfarm_slug) - .then(({ spread }) => this.setState({ spread, loaded: true })); - }; - - componentDidMount = () => this.fetchSpread(); - componentDidUpdate = (prevProps: SpreadCircleProps) => - this.props.plant.body.openfarm_slug != prevProps.plant.body.openfarm_slug && - this.fetchSpread(); - - render() { - const { radius, x, y, id } = this.props.plant.body; - const { visible, mapTransformProps, animate } = this.props; - const { qx, qy } = transformXY(round(x), round(y), mapTransformProps); - const spreadDiaCm = this.state.loaded - ? this.state.spread || defaultSpreadCmDia(radius) - : 0; - return - {visible && - } - {this.props.hoveredSpread && this.props.selected && - } - ; - } -} +export const SpreadCircle = (props: SpreadCircleProps) => { + const { radius, x, y, id } = props.plant.body; + const { visible, mapTransformProps, animate } = props; + const { qx, qy } = transformXY(round(x), round(y), mapTransformProps); + const { spread } = findCrop(props.plant.body.openfarm_slug); + const spreadDiaCm = spread || defaultSpreadCmDia(radius); + return + {visible && + } + {props.hoveredSpread && props.selected && + } + ; +}; diff --git a/frontend/farm_designer/map/layers/spread/spread_overlap_helper.tsx b/frontend/farm_designer/map/layers/spread/spread_overlap_helper.tsx index 75089b6ac8..ff4809ce6c 100644 --- a/frontend/farm_designer/map/layers/spread/spread_overlap_helper.tsx +++ b/frontend/farm_designer/map/layers/spread/spread_overlap_helper.tsx @@ -2,7 +2,6 @@ import React from "react"; import { SpreadOverlapHelperProps } from "../../interfaces"; import { round, transformXY, defaultSpreadCmDia } from "../../util"; import { BotPosition } from "../../../../devices/interfaces"; -import { cachedCrop } from "../../../../open_farm/cached_crop"; import { isUndefined } from "lodash"; enum OverlapColor { @@ -22,10 +21,6 @@ export enum SpreadOption { type SpreadRadii = { inactive: number, active: number }; -interface SpreadCircleState { - inactiveSpread: number | undefined; -} - export function getDiscreteColor( overlap: number, spreadRadius: number): OverlapColor { // Return overlap severity color based on discrete intervals. @@ -129,48 +124,37 @@ export function overlapText( } } -export class SpreadOverlapHelper extends - React.Component { - state: SpreadCircleState = { inactiveSpread: undefined }; - - componentDidMount() { - cachedCrop(this.props.plant.body.openfarm_slug) - .then(({ spread }) => this.setState({ inactiveSpread: spread })); - } - - render() { - const { dragging, plant, activeDragXY, activeDragSpread, - mapTransformProps } = this.props; - const { radius, x, y } = plant.body; - const { qx, qy } = transformXY(round(x), round(y), mapTransformProps); - const gardenCoord: BotPosition = { x: round(x), y: round(y), z: 0 }; - const { inactiveSpread } = this.state; - // Convert spread diameter in cm to radius in mm. - const spreadRadii = { - active: (activeDragSpread || 0) / 2 * 10, - inactive: (inactiveSpread || defaultSpreadCmDia(radius)) / 2 * 10, - }; +export const SpreadOverlapHelper = (props: SpreadOverlapHelperProps) => { + const { dragging, plant, activeDragXY, activeDragSpread, inactiveSpread, + mapTransformProps } = props; + const { radius, x, y } = plant.body; + const { qx, qy } = transformXY(round(x), round(y), mapTransformProps); + const gardenCoord: BotPosition = { x: round(x), y: round(y), z: 0 }; + // Convert spread diameter in cm to radius in mm. + const spreadRadii = { + active: (activeDragSpread || 0) / 2 * 10, + inactive: (inactiveSpread || defaultSpreadCmDia(radius)) / 2 * 10, + }; - const overlapValue = getOverlap(activeDragXY, gardenCoord, spreadRadii); - // Overlap is evaluated against the inactive plant since evaluating - // against the active plant would require keeping a list of all plants - // overlapping the active plant. Therefore, the spread overlap helper - // should be thought of as a tool checking the inactive plants, not - // the plant being edited. Dragging a plant with a small spread into - // the area of a plant with large spread will illustrate this point. - const color = getContinuousColor( - overlapValue, getRadius(SpreadOption.InactivePlant, spreadRadii)); + const overlapValue = getOverlap(activeDragXY, gardenCoord, spreadRadii); + // Overlap is evaluated against the inactive plant since evaluating + // against the active plant would require keeping a list of all plants + // overlapping the active plant. Therefore, the spread overlap helper + // should be thought of as a tool checking the inactive plants, not + // the plant being edited. Dragging a plant with a small spread into + // the area of a plant with large spread will illustrate this point. + const color = getContinuousColor( + overlapValue, getRadius(SpreadOption.InactivePlant, spreadRadii)); - return - {!dragging && // Non-active plants - } - {this.props.showOverlapValues && !dragging && - overlapText(qx, qy, overlapValue, spreadRadii)} - ; - } -} + return + {!dragging && // Non-active plants + } + {props.showOverlapValues && !dragging && + overlapText(qx, qy, overlapValue, spreadRadii)} + ; +}; diff --git a/frontend/farm_designer/map/profile/__tests__/plants_and_weeds_test.tsx b/frontend/farm_designer/map/profile/__tests__/plants_and_weeds_test.tsx index 183788829f..af6268a74b 100644 --- a/frontend/farm_designer/map/profile/__tests__/plants_and_weeds_test.tsx +++ b/frontend/farm_designer/map/profile/__tests__/plants_and_weeds_test.tsx @@ -1,8 +1,3 @@ -let mockSpread = 0; -jest.mock("../../../../open_farm/cached_crop", () => ({ - cachedCrop: jest.fn(() => Promise.resolve({ icon: "", spread: mockSpread })), -})); - import React from "react"; import { svgMount } from "../../../../__test_support__/svg_mount"; import { @@ -30,7 +25,6 @@ describe("", () => { }); it("renders plant point", () => { - mockSpread = 100; const p = fakeProps(); p.point.body.z = 0; p.soilHeight = 200; @@ -56,9 +50,9 @@ describe("", () => { }); it("renders default spread", () => { - mockSpread = 0; const p = fakeProps(); const plant = fakePlant(); + plant.body.openfarm_slug = "foo-bar"; plant.body.radius = 25; p.point = plant; const wrapper = svgMount(); @@ -68,7 +62,6 @@ describe("", () => { }); it("renders hovered spread", () => { - mockSpread = 0; const p = fakeProps(); const plant = fakePlant(); plant.body.radius = 25; diff --git a/frontend/farm_designer/map/profile/interfaces.ts b/frontend/farm_designer/map/profile/interfaces.ts index ea982ffdca..e19c1e3f7f 100644 --- a/frontend/farm_designer/map/profile/interfaces.ts +++ b/frontend/farm_designer/map/profile/interfaces.ts @@ -116,11 +116,6 @@ export interface ProfileUtmProps { gantryHeight: number; } -export interface PlantPointState { - icon: string; - spreadDiaCm: number; -} - export interface ProfilePointProps { point: T; tools: TaggedTool[]; diff --git a/frontend/farm_designer/map/profile/plants_and_weeds.tsx b/frontend/farm_designer/map/profile/plants_and_weeds.tsx index afbdde6be4..68d81e194e 100644 --- a/frontend/farm_designer/map/profile/plants_and_weeds.tsx +++ b/frontend/farm_designer/map/profile/plants_and_weeds.tsx @@ -1,92 +1,79 @@ import React from "react"; import { TaggedWeedPointer } from "farmbot"; -import { PlantPointState, ProfilePointProps } from "./interfaces"; +import { ProfilePointProps } from "./interfaces"; import { Color } from "../../../ui"; import { defaultSpreadCmDia, scaleIcon } from "../util"; -import { svgToUrl } from "../../../open_farm/icons"; import { TaggedPlant } from "../interfaces"; -import { cachedCrop } from "../../../open_farm/cached_crop"; import { BooleanSetting } from "../../../session_keys"; import { FilePath } from "../../../internal_urls"; +import { findCrop, findIcon } from "../../../crops/find"; /** Plant point profile. */ -export class PlantPoint - extends React.Component, PlantPointState> { - state: PlantPointState = { - icon: FilePath.DEFAULT_ICON, - spreadDiaCm: defaultSpreadCmDia(this.props.point.body.radius), - }; - - componentDidMount() { - cachedCrop(this.props.point.body.openfarm_slug) - .then(({ svg_icon, spread }) => - this.setState({ icon: svgToUrl(svg_icon), spreadDiaCm: spread || 0 })); - } - - render() { - const { point, getX, soilHeight, getConfigValue, designer } = this.props; - const { icon, spreadDiaCm } = this.state; - const currentPlantUuid = designer.selectedPoints?.[0]; - const radius = point.uuid == currentPlantUuid && designer.hoveredSpread - ? designer.hoveredSpread / 2 - : point.body.radius; - const plantIconSize = scaleIcon(radius) * 2; - const spreadRadius = (spreadDiaCm || defaultSpreadCmDia(radius)) / 2 * 10; - const profileX = getX(point.body); - const profileY = point.body.z == 0 ? soilHeight : point.body.z; - const depth = point.kind == "Point" - ? point.body.depth - : 0; - return - - - - - - - - - - - - - - - - - {getConfigValue(BooleanSetting.show_spread) && - } - - - - ; - } -} +export const PlantPoint = (props: ProfilePointProps) => { + const { point, getX, soilHeight, getConfigValue, designer } = props; + const currentPlantUuid = designer.selectedPoints?.[0]; + const radius = point.uuid == currentPlantUuid && designer.hoveredSpread + ? designer.hoveredSpread / 2 + : point.body.radius; + const plantIconSize = scaleIcon(radius) * 2; + const slug = point.body.openfarm_slug; + const spreadDiaCm = findCrop(slug).spread; + const icon = findIcon(slug); + const spreadRadius = (spreadDiaCm || defaultSpreadCmDia(radius)) / 2 * 10; + const profileX = getX(point.body); + const profileY = point.body.z == 0 ? soilHeight : point.body.z; + const depth = point.kind == "Point" + ? point.body.depth + : 0; + return + + + + + + + + + + + + + + + + + {getConfigValue(BooleanSetting.show_spread) && + } + + + + ; +}; /** Weed point profile. */ export const WeedPoint = (props: ProfilePointProps) => { diff --git a/frontend/farm_designer/plant.ts b/frontend/farm_designer/plant.ts index daac63694d..a30b44907c 100644 --- a/frontend/farm_designer/plant.ts +++ b/frontend/farm_designer/plant.ts @@ -1,11 +1,15 @@ +import { sample } from "lodash"; import { PlantOptions } from "./interfaces"; import { PlantPointer } from "farmbot/dist/resources/api_resources"; +import { SLUGS } from "../crops/constants"; export const DEFAULT_PLANT_RADIUS = 25; +export const verifiedCropSlug = (slug: string | undefined): string => + (slug == "random" ? sample(SLUGS) : slug) || "not-set"; + /** Factory function for Plant types. */ export function Plant(options: PlantOptions): PlantPointer { - const openfarm_slug = options.openfarm_slug || "not-set"; return { id: options.id, pointer_type: "Plant", @@ -16,7 +20,7 @@ export function Plant(options: PlantOptions): PlantPointer { z: 0, radius: (options.radius || DEFAULT_PLANT_RADIUS), depth: options.depth || 0, - openfarm_slug, + openfarm_slug: verifiedCropSlug(options.openfarm_slug), plant_stage: options.plant_stage || "planned", planted_at: options.planted_at, water_curve_id: options.water_curve_id, diff --git a/frontend/farm_designer/reducer.ts b/frontend/farm_designer/reducer.ts index e012b3c386..0ebc16e9b0 100644 --- a/frontend/farm_designer/reducer.ts +++ b/frontend/farm_designer/reducer.ts @@ -1,12 +1,10 @@ import { DesignerState, - CropLiveSearchResult, DrawnPointPayl, DrawnWeedPayl, HoveredPlantPayl, } from "./interfaces"; import { generateReducer } from "../redux/generate_reducer"; -import { cloneDeep } from "lodash"; import { TaggedResource, PointType, PlantStage } from "farmbot"; import { Actions } from "../constants"; import { BotPosition } from "../devices/interfaces"; @@ -18,7 +16,6 @@ export const initialState: DesignerState = { selectionPointType: undefined, hoveredPlant: { plantUUID: undefined, - icon: "" }, hoveredPoint: undefined, hoveredSpread: undefined, @@ -27,8 +24,6 @@ export const initialState: DesignerState = { hoveredSensorReading: undefined, hoveredImage: undefined, cropSearchQuery: "", - cropSearchResults: [], - cropSearchInProgress: false, companionIndex: undefined, plantTypeChangeId: undefined, bulkPlantSlug: undefined, @@ -63,23 +58,14 @@ export const initialState: DesignerState = { cropHeightCurveId: undefined, cropStage: undefined, cropPlantedAt: undefined, + cropRadius: undefined, distanceIndicator: "", panelOpen: true, }; export const designer = generateReducer(initialState) .add(Actions.SEARCH_QUERY_CHANGE, (s, { payload }) => { - s.cropSearchInProgress = true; - const state = cloneDeep(s); - state.cropSearchQuery = payload; - return state; - }) - .add(Actions.OF_SEARCH_RESULTS_START, (s) => { - s.cropSearchInProgress = true; - return s; - }) - .add(Actions.OF_SEARCH_RESULTS_NO, (s) => { - s.cropSearchInProgress = false; + s.cropSearchQuery = payload; return s; }) .add(Actions.SET_PLANT_TYPE_CHANGE_ID, (s, { payload }) => { @@ -127,6 +113,10 @@ export const designer = generateReducer(initialState) s.cropPlantedAt = payload; return s; }) + .add(Actions.SET_CROP_RADIUS, (s, { payload }) => { + s.cropRadius = payload; + return s; + }) .add(Actions.HOVER_PLANT_LIST_ITEM, (s, { payload }) => { s.hoveredPlantListItem = payload; return s; @@ -165,18 +155,13 @@ export const designer = generateReducer(initialState) s.drawnWeed && (s.drawnWeed.color = color); return s; }) - .add(Actions.OF_SEARCH_RESULTS_OK, (s, a) => { - s.cropSearchResults = a.payload; - s.cropSearchInProgress = false; - return s; - }) .add(Actions.SET_COMPANION_INDEX, (s, a) => { s.companionIndex = a.payload; return s; }) .add(Actions.DESTROY_RESOURCE_OK, (s) => { s.selectedPoints = undefined; - s.hoveredPlant = { plantUUID: undefined, icon: "" }; + s.hoveredPlant = { plantUUID: undefined }; return s; }) .add(Actions.CHOOSE_LOCATION, (s, { payload }) => { diff --git a/frontend/farm_designer/search_selectors.ts b/frontend/farm_designer/search_selectors.ts deleted file mode 100644 index bdca35d7c9..0000000000 --- a/frontend/farm_designer/search_selectors.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { CropLiveSearchResult } from "./interfaces"; -import { startCase, find } from "lodash"; -import { FilePath } from "../internal_urls"; - -export function findBySlug( - crops: CropLiveSearchResult[], slug?: string): CropLiveSearchResult { - const crop = find(crops, result => result.crop.slug === slug); - return crop || { - crop: { - name: startCase((slug || "").split("-").join(" ")), - slug: slug || "", - binomial_name: "", - common_names: [], - description: "", - sun_requirements: "", - sowing_method: "", - processing_pictures: 0, - main_image_path: "", - }, - images: [FilePath.DEFAULT_ICON], - companions: [], - }; -} diff --git a/frontend/farm_designer/three_d_garden_map.tsx b/frontend/farm_designer/three_d_garden_map.tsx index dd1bae4614..9f8bd7fe20 100644 --- a/frontend/farm_designer/three_d_garden_map.tsx +++ b/frontend/farm_designer/three_d_garden_map.tsx @@ -4,11 +4,11 @@ import { INITIAL } from "../three_d_garden/config"; import { BotSize, MapTransformProps, AxisNumberProperty, TaggedPlant, } from "./map/interfaces"; -import { camelCase, clone } from "lodash"; +import { clone } from "lodash"; import { SourceFbosConfig } from "../devices/interfaces"; -import { ConfigurationName } from "farmbot"; +import { ConfigurationName, TaggedCurve } from "farmbot"; import { DesignerState } from "./interfaces"; -import { ASSETS } from "../three_d_garden/constants"; +import { GetWebAppConfigValue } from "../config_storage/actions"; export interface ThreeDGardenMapProps { botSize: BotSize; @@ -18,6 +18,9 @@ export interface ThreeDGardenMapProps { sourceFbosConfig: SourceFbosConfig; designer: DesignerState; plants: TaggedPlant[]; + dispatch: Function; + getWebAppConfigValue: GetWebAppConfigValue; + curves: TaggedCurve[]; } export const ThreeDGardenMap = (props: ThreeDGardenMapProps) => { @@ -51,20 +54,17 @@ export const ThreeDGardenMap = (props: ThreeDGardenMapProps) => { config.bounds = !!getValue("bounds"); config.grid = !!getValue("grid"); - const plants = props.plants.map(plant => { - return { - label: plant.body.name, - icon: ASSETS.icons[camelCase(plant.body.openfarm_slug)] - || ASSETS.icons.arugula, - size: plant.body.radius * 2, - spread: 0, - x: plant.body.x + config.bedXOffset, - y: plant.body.y + config.bedYOffset, - }; - }); - config.zoom = true; config.pan = true; - return ; + return ; }; diff --git a/frontend/farm_designer/util.ts b/frontend/farm_designer/util.ts index 567e336855..64c41f524b 100644 --- a/frontend/farm_designer/util.ts +++ b/frontend/farm_designer/util.ts @@ -1,58 +1,4 @@ -import axios, { AxiosPromise } from "axios"; -import { - OpenFarm, CropSearchResult, CropSearchResultSpecific, -} from "../open_farm/openfarm"; -import { Actions } from "../constants"; import { ExecutableType } from "farmbot/dist/resources/api_resources"; -import { flatten, get } from "lodash"; -import { ExternalUrl } from "../external_urls"; -import { FilePath } from "../internal_urls"; - -const searchUrl = (q: string) => - `${ExternalUrl.OpenFarm.cropApi}?include=pictures&filter=${q}`; -const specificUrl = (q: string) => `${ExternalUrl.OpenFarm.cropApi}${q}`; -const openFarmSearchQuery = - (q: string): AxiosPromise => - axios.get(searchUrl(q)); -const openFarmSearchQuerySpecific = - (q: string): AxiosPromise => - axios.get(specificUrl(q)); - -const FALLBACK: OpenFarm.Included[] = []; -const OFSearchRaw = (specific: boolean) => (searchTerm: string) => - (dispatch: Function) => { - dispatch({ type: Actions.OF_SEARCH_RESULTS_START, payload: undefined }); - (specific - ? openFarmSearchQuerySpecific(searchTerm) - : openFarmSearchQuery(searchTerm)) - .then(resp => { - const imageUrls: string[] = []; - const companions: OpenFarm.CompanionsData[] = []; - get(resp, "data.included", FALLBACK) - .map((item: OpenFarm.Included) => { - switch (item.type) { - case "crops-pictures": - imageUrls.push(item.attributes.thumbnail_url); - break; - case "crops": - const { name, slug, svg_icon } = item.attributes; - companions.push({ name, slug, svg_icon }); - break; - } - }); - const payload = flatten([resp.data.data]).map(datum => { - const crop = datum.attributes; - const images = imageUrls.length > 0 ? imageUrls : [FilePath.DEFAULT_ICON]; - return { crop, images, companions }; - }); - dispatch({ type: Actions.OF_SEARCH_RESULTS_OK, payload }); - }) - .catch(() => - dispatch({ type: Actions.OF_SEARCH_RESULTS_NO, payload: undefined })); - }; - -export const OFCropFetch = OFSearchRaw(true); -export const OFSearch = OFSearchRaw(false); function isExecutableType(x?: string): x is ExecutableType { const EXECUTABLES: ExecutableType[] = ["Sequence", "Regimen"]; diff --git a/frontend/interceptors.ts b/frontend/interceptors.ts index 8286951a9b..4e74171809 100644 --- a/frontend/interceptors.ts +++ b/frontend/interceptors.ts @@ -29,10 +29,7 @@ export function responseRejected(x: SafeError | undefined) { dispatchNetworkUp("user.api", now()); const a = ![451, 401, 422].includes(x.response.status); const b = x.response.status > 399; - // Openfarm API was sending too many 404's. - const c = !(get(x, "response.config.url", "") as string) - .includes("openfarm.cc/"); - if (a && b && c) { + if (a && b) { setTimeout(() => { // Explicitly throw error so error reporting tool will save it. const respString = JSON.stringify(x.response); diff --git a/frontend/internal_urls.ts b/frontend/internal_urls.ts index 4f8aaace93..df9f00248e 100644 --- a/frontend/internal_urls.ts +++ b/frontend/internal_urls.ts @@ -1,4 +1,4 @@ -import { isUndefined, last } from "lodash"; +import { isUndefined, kebabCase, last } from "lodash"; import { t } from "./i18next_wrapper"; export namespace Path { @@ -92,6 +92,7 @@ export namespace Path { export const idIndex = (path: string) => path.split("/").length + 0; export const getSlug = (path: string): string => getPathArray()[Path.idIndex(path)] || ""; + export const getCropSlug = () => kebabCase(Path.getSlug(Path.cropSearch())); } export namespace FilePath { diff --git a/frontend/open_farm/__tests__/cached_crop_test.ts b/frontend/open_farm/__tests__/cached_crop_test.ts deleted file mode 100644 index 747a4e64d0..0000000000 --- a/frontend/open_farm/__tests__/cached_crop_test.ts +++ /dev/null @@ -1,89 +0,0 @@ -const mockIcon = "Wow"; -const mockResponse: { promise: Promise<{}> } = { - promise: Promise.resolve({ - data: { - id: 0, - data: { - attributes: { - svg_icon: "Wow", - slug: "lettuce" - } - } - } - }) -}; - -jest.mock("axios", () => ({ - get: jest.fn(() => mockResponse.promise) -})); - -jest.unmock("../cached_crop"); -import { cachedCrop, maybeGetCachedPlantIcon, promiseCache } from "../cached_crop"; -import axios from "axios"; -import { times } from "lodash"; -import { imgEvent } from "../../__test_support__/fake_html_events"; -import { svgToUrl } from "../icons"; - -describe("cachedIcon()", () => { - it("does an HTTP request if the icon can't be found locally", async () => { - times(10, () => cachedCrop("lettuce")); - const item1 = await cachedCrop("lettuce"); - expect(item1.svg_icon).toContain(mockIcon); - const item2 = await cachedCrop("lettuce"); - expect(item2.slug).toBe(item1.slug); - expect(item2.svg_icon).toBe(item1.svg_icon); - expect(item2.spread).toBe(undefined); - expect(axios.get).toHaveBeenCalledTimes(1); - }); - - it("handles unexpected responses from OpenFarm", async () => { - const old = mockResponse.promise; - mockResponse.promise = Promise.resolve({ data: {} }); - const radish = await cachedCrop("radish"); - expect(radish.spread).toBeUndefined(); - expect(radish.svg_icon).toBeUndefined(); - mockResponse.promise = old; - }); -}); - -describe("maybeGetCachedPlantIcon()", () => { - beforeEach(() => { - Object.keys(promiseCache).map(key => delete promiseCache[key]); - }); - - const expectedIcon = svgToUrl(mockIcon); - - it("sets src", async () => { - const img = imgEvent().currentTarget; - const cb = jest.fn(); - await maybeGetCachedPlantIcon("slug", img, cb); - await expect(axios.get).toHaveBeenCalledWith(expect.stringContaining( - "slug")); - expect(img.getAttribute).toHaveBeenCalledWith("src"); - expect(img.setAttribute).toHaveBeenCalledWith("src", expectedIcon); - expect(cb).toHaveBeenCalledWith(expectedIcon); - }); - - it("doesn't set icon twice", async () => { - const img = imgEvent().currentTarget; - img.getAttribute = jest.fn(() => expectedIcon); - const cb = jest.fn(); - await maybeGetCachedPlantIcon("slug", img, cb); - await expect(axios.get).toHaveBeenCalledWith(expect.stringContaining( - "slug")); - expect(img.getAttribute).toHaveBeenCalledWith("src"); - expect(img.setAttribute).not.toHaveBeenCalled(); - expect(cb).toHaveBeenCalledWith(expectedIcon); - }); - - it("doesn't set icon when undefined", async () => { - const img = imgEvent().currentTarget; - img.getAttribute = jest.fn(() => expectedIcon); - const cb = jest.fn(); - await maybeGetCachedPlantIcon("", img, cb); - await expect(axios.get).not.toHaveBeenCalled(); - expect(img.getAttribute).not.toHaveBeenCalled(); - expect(img.setAttribute).not.toHaveBeenCalled(); - expect(cb).not.toHaveBeenCalled(); - }); -}); diff --git a/frontend/open_farm/__tests__/icons_test.ts b/frontend/open_farm/__tests__/icons_test.ts deleted file mode 100644 index 153fd1e748..0000000000 --- a/frontend/open_farm/__tests__/icons_test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { svgToUrl } from "../icons"; - -describe("svgToUrl()", () => { - it("returns svg url", () => { - expect(svgToUrl("icon")).toEqual("data:image/svg+xml;utf8,icon"); - }); -}); diff --git a/frontend/open_farm/cached_crop.ts b/frontend/open_farm/cached_crop.ts deleted file mode 100644 index c8ba7fd0fe..0000000000 --- a/frontend/open_farm/cached_crop.ts +++ /dev/null @@ -1,95 +0,0 @@ -import axios, { AxiosResponse } from "axios"; -import { Dictionary } from "farmbot"; -import { isObject, isUndefined } from "lodash"; -import { OFCropAttrs, OFCropResponse, svgToUrl } from "./icons"; -import { ExternalUrl } from "../external_urls"; - -type OFIcon = Readonly; -type IconDictionary = Dictionary; - -const STORAGE_KEY = "openfarm_icons_with_spread"; - -function initLocalStorage() { - localStorage.setItem(STORAGE_KEY, "{}"); - return {}; -} - -function getAllIconsFromCache(): IconDictionary { - try { - const dictionary = JSON.parse(localStorage.getItem(STORAGE_KEY) || ""); - return isObject(dictionary) ? dictionary as IconDictionary : initLocalStorage(); - } catch (error) { - return initLocalStorage(); - } -} - -function localStorageIconFetch(slug: string): Promise | undefined { - const icon = getAllIconsFromCache()[slug]; - return icon ? Promise.resolve(icon) : undefined; -} - -function localStorageIconSet(icon: OFIcon): void { - const dictionary = getAllIconsFromCache(); - dictionary[icon.slug] = icon; - localStorage.setItem(STORAGE_KEY, JSON.stringify(dictionary)); -} - -/** PROBLEM: HTTP requests get fired too fast. If you have 10 garlic plants, - * and the garlic icon is not cached locally, and you try to render 10 garlic - * icons in the first 100ms, and HTTP requests take more than 100ms, you will - * end up performing 10 HTTP requests at application start time. Not very - * efficient. - * SOLUTION: Keep a record of open requests to avoid duplicate requests. */ -export const promiseCache: Dictionary>> = {}; - -const cacheTheIcon = (slug: string) => - (resp: AxiosResponse): OFIcon => { - if (resp?.data?.data?.attributes) { - const icon = { - slug: resp.data.data.attributes.slug, - spread: resp.data.data.attributes.spread, - svg_icon: resp.data.data.attributes.svg_icon - }; - localStorageIconSet(icon); - return icon; - } else { - return { slug, spread: undefined, svg_icon: undefined }; - } - }; - -function HTTPIconFetch(slug: string) { - const url = ExternalUrl.OpenFarm.cropApi + slug; - // Avoid duplicate requests. - if (!isUndefined(promiseCache[url])) { return promiseCache[url]; } - promiseCache[url] = axios - .get(url) - .then(cacheTheIcon(slug), cacheTheIcon(slug)); - return promiseCache[url]; -} - -/** PROBLEM: You have 100 lettuce plants. You don't want to download an SVG icon - * 100 times. - * SOLUTION: Cache stuff. */ -export function cachedCrop(slug: string): Promise { - return localStorageIconFetch(slug) || HTTPIconFetch(slug); -} - -export const maybeGetCachedPlantIcon = ( - slug: string | undefined, - target: React.SyntheticEvent["currentTarget"], - cb: (icon: string) => void) => { - slug && cachedCrop(slug) - .then(crop => { - const i = svgToUrl(crop.svg_icon); - setImgSrc(target, i); - cb(i); - }); -}; - -export const setImgSrc = ( - target: React.SyntheticEvent["currentTarget"], - icon: string) => { - if (icon !== target.getAttribute("src")) { - target.setAttribute("src", icon); - } -}; diff --git a/frontend/open_farm/icons.ts b/frontend/open_farm/icons.ts deleted file mode 100644 index 63555a6c61..0000000000 --- a/frontend/open_farm/icons.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { FilePath } from "../internal_urls"; - -const DATA_URI = "data:image/svg+xml;utf8,"; - -export interface OFCropAttrs { - svg_icon?: string | undefined; - spread?: number | undefined; - slug: string; -} - -export interface OFCropResponse { - id?: undefined; - // Attributes available, possibly not declared in the interface: - // binomial_name, common_names, description, - // growing_degree_days, guides_count, height, main_image_path, - // name, processing_pictures, row_spacing, slug, sowing_method, - // spread, sun_requirements, svg_icon, tags_array, taxon - data?: { - attributes?: OFCropAttrs | undefined; - }; -} - -export function svgToUrl(xml: string | undefined): string { - return xml - ? (DATA_URI + encodeURIComponent(xml)) - : FilePath.DEFAULT_ICON; -} diff --git a/frontend/open_farm/openfarm.ts b/frontend/open_farm/openfarm.ts deleted file mode 100644 index fd614367be..0000000000 --- a/frontend/open_farm/openfarm.ts +++ /dev/null @@ -1,108 +0,0 @@ -export namespace OpenFarm { - - /** An OpenFarm.cc crop entry. NOT a farmbot.cc Crop. */ - export interface OFCrop { - name: string; - slug: string; - binomial_name: string; - common_names: string[]; - description: string; - sun_requirements: string; - sowing_method: string; - svg_icon?: string | undefined; - // Unsure of this. Def not an object tho. - spread?: number | undefined; - row_spacing?: number; - height?: number; - processing_pictures: number; - guides_count?: number; - main_image_path: string; - taxon?: string; - tags_array?: string[]; - growing_degree_days?: number; - } - - export interface CompanionsData { - name: string; - slug: string; - svg_icon?: string | undefined; - } - - interface Self { - api: string; - website: string; - } - - export interface Links { - self: Self; - } - - interface Links2 { - related: string; - } - - interface Datum2 { - type: string; - id: string; - } - - export interface Companions { - links: Links2; - } - - interface Pictures { - links: Links2; - data: Datum2[]; - } - - interface Relationships { - companions: Companions; - pictures: Pictures; - } - - export interface Datum { - id: string; - type: string; - attributes: OFCrop; - links: Links; - relationships: Relationships; - } - - interface ImageAttrs { - id: string; - image_url: string; - small_url: string; - thumbnail_url: string; - medium_url: string; - large_url: string; - canopy_url: string; - } - - interface IncludedPictures { - id: string; - type: "crops-pictures"; - attributes: ImageAttrs; - } - - interface IncludedCompanion { - id: string; - type: "crops"; - attributes: OFCrop; - links: Links; - relationships: Relationships; - } - - export type Included = IncludedPictures | IncludedCompanion; -} - -/** Returned by https://openfarm.cc/api/v1/crops?filter=q */ -export interface CropSearchResult { - data: OpenFarm.Datum[]; - included: OpenFarm.Included[]; -} - -/** Returned by https://openfarm.cc/api/v1/q */ -export interface CropSearchResultSpecific { - data: OpenFarm.Datum; - included: OpenFarm.Included[]; -} diff --git a/frontend/plants/__tests__/add_plant_test.tsx b/frontend/plants/__tests__/add_plant_test.tsx index e2f54ac919..ca5d757f34 100644 --- a/frontend/plants/__tests__/add_plant_test.tsx +++ b/frontend/plants/__tests__/add_plant_test.tsx @@ -3,10 +3,6 @@ import { mount } from "enzyme"; import { RawAddPlant as AddPlant, AddPlantProps, mapStateToProps, } from "../add_plant"; -import { - fakeCropLiveSearchResult, -} from "../../__test_support__/fake_crop_search_result"; -import { svgToUrl } from "../../open_farm/icons"; import { fakeState } from "../../__test_support__/fake_state"; import { buildResourceIndex, @@ -20,14 +16,10 @@ import { fakeDesignerState } from "../../__test_support__/fake_designer_state"; describe("", () => { const fakeProps = (): AddPlantProps => { const designer = fakeDesignerState(); - const cropSearchResult = fakeCropLiveSearchResult(); - cropSearchResult.crop.svg_icon = "fake_mint_svg"; - designer.cropSearchResults = [cropSearchResult]; return { designer, dispatch: jest.fn(), xy_swap: false, - openfarmCropFetch: jest.fn(() => jest.fn()), botPosition: { x: undefined, y: undefined, z: undefined }, }; }; @@ -41,8 +33,7 @@ describe("", () => { expect(wrapper.text()).toContain("Preview"); const img = wrapper.find("img"); expect(img).toBeDefined(); - expect(img.props().src).toEqual(svgToUrl("fake_mint_svg")); - expect(p.openfarmCropFetch).toHaveBeenCalledWith("mint"); + expect(img.props().src).toEqual("/crops/icons/mint.avif"); }); }); @@ -50,7 +41,6 @@ describe("mapStateToProps", () => { it("maps state to props", () => { const state = fakeState(); const results = mapStateToProps(state); - expect(results.designer.cropSearchResults).toEqual([]); expect(results.xy_swap).toEqual(false); }); diff --git a/frontend/plants/__tests__/crop_catalog_test.tsx b/frontend/plants/__tests__/crop_catalog_test.tsx index 1e7623a45d..413e4c0b30 100644 --- a/frontend/plants/__tests__/crop_catalog_test.tsx +++ b/frontend/plants/__tests__/crop_catalog_test.tsx @@ -8,44 +8,36 @@ import { import { mount, shallow } from "enzyme"; import { CropCatalogProps } from "../../farm_designer/interfaces"; import { Actions } from "../../constants"; -import { - fakeCropLiveSearchResult, -} from "../../__test_support__/fake_crop_search_result"; -import { SearchField } from "../../ui/search_field"; import { fakeState } from "../../__test_support__/fake_state"; import { Path } from "../../internal_urls"; import { buildResourceIndex } from "../../__test_support__/resource_index_builder"; import { fakePlant } from "../../__test_support__/fake_state/resources"; +import { SearchField } from "../../ui/search_field"; describe("", () => { const fakeProps = (): CropCatalogProps => ({ dispatch: jest.fn(), - openfarmSearch: jest.fn(() => jest.fn()), - cropSearchResults: [], - cropSearchQuery: undefined, - cropSearchInProgress: false, plant: undefined, bulkPlantSlug: undefined, - hoveredPlant: { plantUUID: undefined, icon: "" }, + hoveredPlant: { plantUUID: undefined }, + cropSearchQuery: "", }); it("renders", () => { const wrapper = mount(); expect(wrapper.text()).toContain("Choose a crop"); expect(wrapper.find("input").props().placeholder) - .toEqual("Search OpenFarm..."); + .toEqual("Search crops..."); }); - it("handles search term change", () => { + it("changes search term", () => { const p = fakeProps(); const wrapper = shallow(); - wrapper.find(SearchField).simulate("change", "apple"); + wrapper.find(SearchField).simulate("change", "term"); expect(p.dispatch).toHaveBeenCalledWith({ - payload: "apple", - type: Actions.SEARCH_QUERY_CHANGE + type: Actions.SEARCH_QUERY_CHANGE, + payload: "term", }); - // Requires lodash.debouce to be mocked - expect(p.openfarmSearch).toHaveBeenCalledWith("apple"); }); it("goes back", () => { @@ -54,22 +46,6 @@ describe("", () => { expect(mockNavigate).toHaveBeenCalledWith(Path.plants()); }); - it("search term is too short", () => { - const p = fakeProps(); - p.cropSearchQuery = "ab"; - const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("too short"); - }); - - it("shows result update spinner", () => { - const p = fakeProps(); - p.cropSearchQuery = "abc"; - p.cropSearchInProgress = true; - p.cropSearchResults = [fakeCropLiveSearchResult()]; - const wrapper = mount(); - expect(wrapper.find(".spinner").length).toEqual(1); - }); - it("dispatches upon unmount", () => { const p = fakeProps(); const wrapper = mount(); @@ -83,7 +59,6 @@ describe("", () => { describe("mapStateToProps()", () => { it("returns props", () => { const props = mapStateToProps(fakeState()); - expect(props.cropSearchInProgress).toEqual(false); expect(props.plant).toEqual(undefined); }); diff --git a/frontend/plants/__tests__/crop_info_test.tsx b/frontend/plants/__tests__/crop_info_test.tsx index 2d8b5322fb..e1921a2ef2 100644 --- a/frontend/plants/__tests__/crop_info_test.tsx +++ b/frontend/plants/__tests__/crop_info_test.tsx @@ -5,28 +5,22 @@ jest.mock("../../farm_designer/map/actions", () => ({ setDragIcon: jest.fn(), })); -let mockDev = false; -jest.mock("../../settings/dev/dev_support", () => ({ - DevSettings: { futureFeaturesEnabled: () => mockDev } +import { FAKE_CROPS } from "../../__test_support__/fake_crops"; +jest.mock("../../crops/constants", () => ({ + CROPS: FAKE_CROPS, })); import React from "react"; import { - RawCropInfo as CropInfo, searchForCurrentCrop, mapStateToProps, - getCropHeaderProps, + RawCropInfo as CropInfo, mapStateToProps, } from "../crop_info"; import { mount, shallow } from "enzyme"; import { CropInfoProps } from "../../farm_designer/interfaces"; import { initSave } from "../../api/crud"; -import { - fakeCropLiveSearchResult, -} from "../../__test_support__/fake_crop_search_result"; -import { unselectPlant } from "../../farm_designer/map/actions"; -import { svgToUrl } from "../../open_farm/icons"; import { fakeState } from "../../__test_support__/fake_state"; import { Actions } from "../../constants"; import { clickButton } from "../../__test_support__/helpers"; -import { FilePath, Path } from "../../internal_urls"; +import { Path } from "../../internal_urls"; import { fakeCurve, fakePlant, fakeWebAppConfig, } from "../../__test_support__/fake_state/resources"; @@ -39,14 +33,8 @@ import { BlurableInput, FBSelect } from "../../ui"; describe("", () => { const fakeProps = (): CropInfoProps => { - const cropSearchResult = fakeCropLiveSearchResult(); - cropSearchResult.crop.svg_icon = "fake_mint_svg"; - cropSearchResult.crop.row_spacing = 100; - cropSearchResult.crop.sowing_method = ""; const designer = fakeDesignerState(); - designer.cropSearchResults = [cropSearchResult]; return { - openfarmCropFetch: jest.fn(), dispatch: jest.fn(), designer, botPosition: { x: undefined, y: undefined, z: undefined }, @@ -62,22 +50,11 @@ describe("", () => { it("renders", () => { location.pathname = Path.mock(Path.cropSearch("mint")); const p = fakeProps(); - p.designer.cropSearchResults[0].companions = []; const wrapper = mount(); expect(wrapper.text()).toContain("Mint"); - expect(wrapper.text()).toContain("Row Spacing:1000mm"); + expect(wrapper.text()).toContain("Row Spacing"); expect(wrapper.find("img").at(0).props().src) - .toEqual(svgToUrl("fake_mint_svg")); - expect(wrapper.text().toLowerCase()).not.toContain("edit on"); - }); - - it("renders openfarm link", () => { - mockDev = true; - location.pathname = Path.mock(Path.cropSearch("mint")); - const p = fakeProps(); - p.designer.cropSearchResults[0].companions = []; - const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("edit on"); + .toEqual("/crops/icons/mint.avif"); }); it("returns to crop search", () => { @@ -131,31 +108,32 @@ describe("", () => { }); }); - it("updates curves", () => { + it("renders radius", () => { + location.pathname = Path.mock(Path.cropSearch("mint")); + const p = fakeProps(); + p.designer.cropRadius = 100; + const wrapper = shallow(); + expect(wrapper.find(BlurableInput).at(1).props().value).toEqual(100); + }); + + it("updates radius", () => { location.pathname = Path.mock(Path.cropSearch("mint")); const p = fakeProps(); - const wrapper = mount(); - wrapper.setState({ crop: "strawberry" }); - wrapper.instance().componentDidUpdate(); - expect(wrapper.state().crop).toEqual("mint"); + const wrapper = shallow(); + wrapper.find("BlurableInput").at(1).simulate("commit", + { currentTarget: { value: "100" } }); expect(p.dispatch).toHaveBeenCalledWith({ - type: Actions.SET_CROP_WATER_CURVE_ID, payload: undefined, + type: Actions.SET_CROP_RADIUS, payload: 100, }); }); - it("updates current image", () => { + it("updates curves", () => { location.pathname = Path.mock(Path.cropSearch("mint")); - const images = [FilePath.DEFAULT_WEED_ICON]; const p = fakeProps(); - const cropSearchResult = fakeCropLiveSearchResult(); - cropSearchResult.images = []; - p.designer.cropSearchResults = [cropSearchResult]; - const wrapper = mount(); - expect(wrapper.state().currentImage).toEqual(undefined); - cropSearchResult.images = images; - p.designer.cropSearchResults = [cropSearchResult]; - wrapper.setProps(p); - expect(wrapper.state().currentImage?.body.attachment_url).toEqual(images[0]); + mount(); + expect(p.dispatch).toHaveBeenCalledWith({ + type: Actions.SET_CROP_WATER_CURVE_ID, payload: undefined, + }); }); it("doesn't change search query", () => { @@ -176,10 +154,11 @@ describe("", () => { }); it("adds a plant at the current bot position", () => { + location.pathname = Path.mock(Path.cropSearch("mint")); const p = fakeProps(); p.botPosition = { x: 100, y: 200, z: undefined }; const wrapper = mount(); - clickButton(wrapper, 5, "location (100, 200)", { partial_match: true }); + clickButton(wrapper, 1, "location (100, 200)", { partial_match: true }); expect(initSave).toHaveBeenCalledWith("Point", expect.objectContaining({ name: "Mint", @@ -193,52 +172,35 @@ describe("", () => { const p = fakeProps(); p.botPosition = { x: 100, y: undefined, z: undefined }; const wrapper = mount(); - clickButton(wrapper, 5, "location (unknown)", { partial_match: true }); + clickButton(wrapper, 1, "location (unknown)", { partial_match: true }); expect(initSave).not.toHaveBeenCalled(); }); it("renders cm in mm", () => { + location.pathname = Path.mock(Path.cropSearch("mint")); const p = fakeProps(); - p.designer.cropSearchResults[0].crop.spread = 1234; const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("12340mm"); + expect(wrapper.text().toLowerCase()).toContain("1000mm"); }); it("renders missing values", () => { + location.pathname = Path.mock(Path.cropSearch("x")); const p = fakeProps(); - p.designer.cropSearchResults[0].crop.svg_icon = undefined; - p.designer.cropSearchResults[0].crop.row_spacing = undefined; - p.designer.cropSearchResults[0].crop.common_names = []; - p.designer.cropSearchResults[0].images = []; const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("spacing:not available"); - expect(wrapper.text().toLowerCase()).toContain("common names:not available"); + expect(wrapper.text().toLowerCase()).toContain("sowingnot available"); + expect(wrapper.text().toLowerCase()).toContain("common namesnot available"); }); it("handles string of names", () => { + location.pathname = Path.mock(Path.cropSearch("mint")); const p = fakeProps(); - p.designer.cropSearchResults[0].crop.common_names = - "names" as unknown as string[]; const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("common names:names"); - }); - - it("sets current image", () => { - const images = ["/plant.jpg", FilePath.DEFAULT_WEED_ICON]; - const p = fakeProps(); - const cropSearchResult = fakeCropLiveSearchResult(); - cropSearchResult.images = images; - p.designer.cropSearchResults = [cropSearchResult]; - const wrapper = shallow(); - expect(wrapper.state().currentImage?.body.attachment_url).toEqual(images[0]); - wrapper.instance().setCurrentImage(1); - expect(wrapper.state().currentImage?.body.attachment_url).toEqual(images[1]); + expect(wrapper.text().toLowerCase()).toContain("common namesmint, spearmint"); }); it("navigates to companion plant", () => { location.pathname = Path.mock(Path.cropSearch("mint")); const p = fakeProps(); - p.openfarmCropFetch = jest.fn(() => jest.fn()); p.dispatch = jest.fn(x => { typeof x === "function" && x(); return Promise.resolve(); @@ -249,7 +211,6 @@ describe("", () => { const companion = wrapper.find("a").at(0); expect(companion.text()).toEqual("Strawberry"); companion.simulate("click"); - expect(p.openfarmCropFetch).toHaveBeenCalledWith("strawberry"); expect(mockNavigate).toHaveBeenCalledWith(Path.cropSearch("strawberry")); }); @@ -292,35 +253,14 @@ describe("", () => { }); }); -describe("searchForCurrentCrop()", () => { - it("searches", () => { - location.pathname = Path.mock(Path.cropSearch("mint")); - const dispatch = jest.fn(); - const fakeOFSearch = jest.fn((_) => jest.fn()); - searchForCurrentCrop(fakeOFSearch)(dispatch); - expect(fakeOFSearch).toHaveBeenCalledWith("mint"); - expect(unselectPlant).toHaveBeenCalled(); - }); -}); - -describe("getCropHeaderProps()", () => { - it("handles missing crop", () => { - location.pathname = Path.mock(Path.cropSearch()); - const result = getCropHeaderProps({ cropSearchResults: [] }); - expect(result.result.crop.name).toEqual(""); - }); -}); - describe("mapStateToProps()", () => { it("returns props", () => { const state = fakeState(); const webAppConfig = fakeWebAppConfig(); webAppConfig.body.show_plants = false; state.resources = buildResourceIndex([webAppConfig]); - state.resources.consumers.farm_designer.cropSearchInProgress = true; state.bot.hardware.location_data.position = { x: 1, y: 2, z: 3 }; const props = mapStateToProps(state); - expect(props.designer.cropSearchInProgress).toEqual(true); expect(props.botPosition).toEqual({ x: 1, y: 2, z: 3 }); expect(props.getConfigValue("show_plants")).toEqual(false); }); diff --git a/frontend/plants/__tests__/crop_search_results_test.tsx b/frontend/plants/__tests__/crop_search_results_test.tsx new file mode 100644 index 0000000000..b065bf8e6b --- /dev/null +++ b/frontend/plants/__tests__/crop_search_results_test.tsx @@ -0,0 +1,95 @@ +jest.mock("../../api/crud", () => ({ + edit: jest.fn(), + save: jest.fn(), +})); + +import React from "react"; +import { mount } from "enzyme"; +import { CropSearchResults, SearchResultProps } from "../crop_search_results"; +import { fakePlant } from "../../__test_support__/fake_state/resources"; +import { Path } from "../../internal_urls"; +import { Actions } from "../../constants"; +import { edit, save } from "../../api/crud"; + +describe("", () => { + const fakeProps = (): SearchResultProps => ({ + searchTerm: "mint", + plant: undefined, + dispatch: jest.fn(), + bulkPlantSlug: undefined, + hoveredPlant: { plantUUID: undefined }, + }); + + it("renders CropSearchResults", () => { + const p = fakeProps(); + const wrapper = mount(); + const text = wrapper.text(); + expect(text).toContain("Mint"); + expect(wrapper.find("Link").length).toEqual(1); + expect(wrapper.find("Link").first().prop("to")).toContain("mint"); + }); + + it("renders for plant type change", () => { + const p = fakeProps(); + p.plant = fakePlant(); + p.plant.body.id = 1; + const wrapper = mount(); + expect(wrapper.text()).toContain("Mint"); + expect(wrapper.find("Link").first().prop("to")) + .toEqual(Path.plants(1)); + const icon = wrapper.find("img"); + expect(icon.hasClass("center")).toBeFalsy(); + }); + + it("renders without image", () => { + const p = fakeProps(); + p.searchTerm = "foo-bar"; + const wrapper = mount(); + const icon = wrapper.find("img"); + expect(icon.hasClass("center")).toBeTruthy(); + }); + + it("changes plant type", () => { + const p = fakeProps(); + p.plant = fakePlant(); + p.plant.body.id = 1; + const wrapper = mount(); + wrapper.find("Link").first().simulate("click"); + expect(p.dispatch).toHaveBeenCalledWith({ + type: Actions.SET_PLANT_TYPE_CHANGE_ID, + payload: undefined, + }); + expect(edit).toHaveBeenCalledWith(p.plant, { + name: "Mint", + openfarm_slug: "mint", + }); + expect(save).toHaveBeenCalledWith(p.plant.uuid); + }); + + it("changes plant type and hover", () => { + const p = fakeProps(); + p.plant = fakePlant(); + p.plant.body.id = 1; + p.hoveredPlant = { plantUUID: p.plant.uuid }; + const wrapper = mount(); + wrapper.find("Link").first().simulate("click"); + expect(p.dispatch).toHaveBeenCalledWith({ + type: Actions.TOGGLE_HOVERED_PLANT, + payload: { plantUUID: p.plant.uuid }, + }); + }); + + it("sets bulk slug", () => { + const p = fakeProps(); + p.bulkPlantSlug = "slug"; + const wrapper = mount(); + const link = wrapper.find("Link").first(); + expect(link.prop("to")).toEqual(Path.plants("select")); + link.simulate("click"); + expect(p.dispatch).toHaveBeenCalledWith({ + type: Actions.SET_SLUG_BULK, + payload: "mint", + }); + expect(edit).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/plants/__tests__/openfarm_search_results_test.tsx b/frontend/plants/__tests__/openfarm_search_results_test.tsx deleted file mode 100644 index 6775084448..0000000000 --- a/frontend/plants/__tests__/openfarm_search_results_test.tsx +++ /dev/null @@ -1,122 +0,0 @@ -jest.mock("../../api/crud", () => ({ - edit: jest.fn(), - save: jest.fn(), -})); - -import React from "react"; -import { mount } from "enzyme"; -import { OpenFarmResults, SearchResultProps } from "../openfarm_search_results"; -import { fakePlant } from "../../__test_support__/fake_state/resources"; -import { Path } from "../../internal_urls"; -import { Actions } from "../../constants"; -import { edit, save } from "../../api/crud"; - -describe("", () => { - const fakeProps = (): SearchResultProps => ({ - cropSearchResults: [ - { - crop: { - slug: "potato", - name: "S. tuberosum", - svg_icon: "icon", - main_image_path: "image", - }, - images: ["potato.jpg"], - }, - { - crop: { - slug: "tomato", - name: "Solanum lycopersicum", - main_image_path: "https:image", - }, - images: ["tomato.jpg"], - }, - ], - cropSearchInProgress: false, - plant: undefined, - dispatch: jest.fn(), - bulkPlantSlug: undefined, - hoveredPlant: { plantUUID: undefined, icon: "" }, - }); - - it("renders OpenFarmSearchResults", () => { - const p = fakeProps(); - const wrapper = mount(); - const text = wrapper.text(); - expect(text).toContain(p.cropSearchResults[0].crop.name); - expect(text).toContain(p.cropSearchResults[1].crop.name); - expect(wrapper.find("Link").length).toEqual(p.cropSearchResults.length); - expect(wrapper.find("Link").first().prop("to")) - .toContain(p.cropSearchResults[0].crop.slug); - }); - - it("renders OpenFarmSearchResults for plant type change", () => { - const p = fakeProps(); - p.plant = fakePlant(); - p.plant.body.id = 1; - const wrapper = mount(); - expect(wrapper.text()).toContain(p.cropSearchResults[0].crop.name); - expect(wrapper.find("Link").first().prop("to")) - .toEqual(Path.plants(1)); - }); - - it("changes plant type", () => { - const p = fakeProps(); - p.plant = fakePlant(); - p.plant.body.id = 1; - const wrapper = mount(); - wrapper.find("Link").first().simulate("click"); - expect(p.dispatch).toHaveBeenCalledWith({ - type: Actions.SET_PLANT_TYPE_CHANGE_ID, - payload: undefined, - }); - expect(edit).toHaveBeenCalledWith(p.plant, { - name: "S. tuberosum", - openfarm_slug: "potato", - }); - expect(save).toHaveBeenCalledWith(p.plant.uuid); - }); - - it("changes plant type and hover icon", () => { - const p = fakeProps(); - p.plant = fakePlant(); - p.plant.body.id = 1; - p.hoveredPlant = { plantUUID: p.plant.uuid, icon: "old" }; - const wrapper = mount(); - wrapper.find("Link").first().simulate("click"); - expect(p.dispatch).toHaveBeenCalledWith({ - type: Actions.TOGGLE_HOVERED_PLANT, - payload: { plantUUID: p.plant.uuid, icon: "icon" }, - }); - }); - - it("sets bulk slug", () => { - const p = fakeProps(); - p.bulkPlantSlug = "slug"; - const wrapper = mount(); - const link = wrapper.find("Link").first(); - expect(link.prop("to")).toEqual(Path.plants("select")); - link.simulate("click"); - expect(p.dispatch).toHaveBeenCalledWith({ - type: Actions.SET_SLUG_BULK, - payload: "potato", - }); - expect(edit).not.toHaveBeenCalled(); - }); - - it("shows search in progress", () => { - const p = fakeProps(); - p.cropSearchResults = []; - p.cropSearchInProgress = true; - const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("searching"); - }); - - it("shows no results", () => { - const p = fakeProps(); - p.cropSearchResults = []; - p.cropSearchInProgress = false; - const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("no search results"); - }); -}); diff --git a/frontend/plants/__tests__/plant_info_test.tsx b/frontend/plants/__tests__/plant_info_test.tsx index 6f0846dd13..2bf4a12dea 100644 --- a/frontend/plants/__tests__/plant_info_test.tsx +++ b/frontend/plants/__tests__/plant_info_test.tsx @@ -41,8 +41,8 @@ describe("", () => { ["Strawberry Plant 1", "Plant Type", "Strawberry"].map(string => expect(wrapper.text().toLowerCase()).toContain(string.toLowerCase())); const buttons = wrapper.find("button"); - expect(buttons.at(0).text()).toEqual("GO (X, Y)"); - expect(buttons.at(2).text()).toEqual("Planned"); + expect(buttons.at(0).text()).toEqual("Planned"); + expect(buttons.at(1).text()).toEqual("GO (X, Y)"); }); it("renders: no plant", () => { diff --git a/frontend/plants/__tests__/plant_inventory_item_test.tsx b/frontend/plants/__tests__/plant_inventory_item_test.tsx index ae41ddf94e..69544acc3e 100644 --- a/frontend/plants/__tests__/plant_inventory_item_test.tsx +++ b/frontend/plants/__tests__/plant_inventory_item_test.tsx @@ -1,7 +1,3 @@ -jest.mock("../../open_farm/cached_crop", () => ({ - maybeGetCachedPlantIcon: jest.fn((_, __, x) => x("icon")), -})); - jest.mock("../../farm_designer/map/actions", () => ({ mapPointClickAction: jest.fn(() => jest.fn()), setHoveredPlant: jest.fn(), @@ -17,7 +13,6 @@ import { shallow, mount } from "enzyme"; import { fakePlant, fakePlantTemplate, } from "../../__test_support__/fake_state/resources"; -import { maybeGetCachedPlantIcon } from "../../open_farm/cached_crop"; import { mapPointClickAction, setHoveredPlant, selectPoint, } from "../../farm_designer/map/actions"; @@ -59,13 +54,13 @@ describe("", () => { const p = fakeProps(); const wrapper = shallow(); wrapper.simulate("mouseEnter"); - expect(setHoveredPlant).toHaveBeenCalledWith(p.plant.uuid, ""); + expect(setHoveredPlant).toHaveBeenCalledWith(p.plant.uuid); }); it("hover end", () => { const wrapper = shallow(); wrapper.simulate("mouseLeave"); - expect(setHoveredPlant).toHaveBeenCalledWith(undefined, ""); + expect(setHoveredPlant).toHaveBeenCalledWith(undefined); }); it("selects plant", () => { @@ -97,7 +92,7 @@ describe("", () => { expect.any(Function), p.plant.uuid); expect(mockNavigate).not.toHaveBeenCalled(); - expect(setHoveredPlant).toHaveBeenCalledWith(undefined, ""); + expect(setHoveredPlant).toHaveBeenCalledWith(undefined); }); it("selects plant template", () => { @@ -114,12 +109,8 @@ describe("", () => { it("gets and sets cached icon", () => { const p = fakeProps(); const wrapper = mount(); - const img = wrapper.find("img"); - img.simulate("load"); - expect(maybeGetCachedPlantIcon).toHaveBeenCalledWith("strawberry", - img.instance(), expect.any(Function)); wrapper.simulate("mouseEnter"); - expect(setHoveredPlant).toHaveBeenCalledWith(p.plant.uuid, "icon"); + expect(setHoveredPlant).toHaveBeenCalledWith(p.plant.uuid); }); }); diff --git a/frontend/plants/__tests__/plant_inventory_test.tsx b/frontend/plants/__tests__/plant_inventory_test.tsx index 009aa7e4bf..0f4b290d21 100644 --- a/frontend/plants/__tests__/plant_inventory_test.tsx +++ b/frontend/plants/__tests__/plant_inventory_test.tsx @@ -1,7 +1,3 @@ -jest.mock("../../open_farm/cached_crop", () => ({ - maybeGetCachedPlantIcon: jest.fn(), -})); - jest.mock("../../point_groups/actions", () => ({ createGroup: jest.fn(), })); diff --git a/frontend/plants/__tests__/plant_panel_test.tsx b/frontend/plants/__tests__/plant_panel_test.tsx index 6f8f25e280..9c66dbd19a 100644 --- a/frontend/plants/__tests__/plant_panel_test.tsx +++ b/frontend/plants/__tests__/plant_panel_test.tsx @@ -109,7 +109,7 @@ describe("", () => { it("moves to plant location", () => { const wrapper = mount(); - clickButton(wrapper, 0, "go (x, y)"); + clickButton(wrapper, 1, "go (x, y)"); expect(move).toHaveBeenCalledWith({ x: 12, y: 34, z: 0 }); }); diff --git a/frontend/plants/add_plant.tsx b/frontend/plants/add_plant.tsx index 49c0b78bab..10936e0a26 100644 --- a/frontend/plants/add_plant.tsx +++ b/frontend/plants/add_plant.tsx @@ -1,42 +1,40 @@ import React from "react"; import { Everything } from "../interfaces"; import { connect } from "react-redux"; -import { svgToUrl } from "../open_farm/icons"; -import { DesignerState, OpenfarmSearch } from "../farm_designer/interfaces"; +import { DesignerState } from "../farm_designer/interfaces"; import { setDragIcon } from "../farm_designer/map/actions"; -import { getCropHeaderProps, searchForCurrentCrop } from "./crop_info"; import { DesignerPanel, DesignerPanelHeader, } from "../farm_designer/designer_panel"; -import { OFCropFetch } from "../farm_designer/util"; import { t } from "../i18next_wrapper"; import { Panel } from "../farm_designer/panel_header"; import { PlantGrid } from "./grid/plant_grid"; import { getWebAppConfig } from "../resources/getters"; import { BotPosition } from "../devices/interfaces"; import { validBotLocationData } from "../util/location"; +import { Path } from "../internal_urls"; +import { findCrop, findIcon, findImage } from "../crops/find"; export const mapStateToProps = (props: Everything): AddPlantProps => ({ designer: props.resources.consumers.farm_designer, xy_swap: !!getWebAppConfig(props.resources.index)?.body.xy_swap, dispatch: props.dispatch, - openfarmCropFetch: OFCropFetch, botPosition: validBotLocationData(props.bot.hardware.location_data).position, }); interface APDProps { - svgIcon: string | undefined; + slug: string; children?: React.ReactNode; } -const AddPlantDescription = ({ svgIcon, children }: APDProps) => +const AddPlantDescription = ({ slug, children }: APDProps) =>
{t("plant + onDragStart={setDragIcon(slug)} />
{t("Drag and drop")} {t("the icon onto the map or ")} {t("CLICK anywhere within the grid")} {t(`to add the plant @@ -47,40 +45,33 @@ const AddPlantDescription = ({ svgIcon, children }: APDProps) => export interface AddPlantProps { dispatch: Function; - openfarmCropFetch: OpenfarmSearch; xy_swap: boolean; botPosition: BotPosition; designer: DesignerState; } export class RawAddPlant extends React.Component { - - componentDidMount() { - this.props.dispatch(searchForCurrentCrop(this.props.openfarmCropFetch)); - } - render() { - const { cropSearchResults } = this.props.designer; - const { result, backgroundURL } = - getCropHeaderProps({ cropSearchResults }); const panelName = "add-plant"; - const descElem = + const slug = Path.getCropSlug(); + const crop = findCrop(slug); + const descElem = + itemName={crop.name} /> ; return { - - debouncedOFSearch = debounce((searchTerm: string) => { - this.props.openfarmSearch(searchTerm)(this.props.dispatch); - }, 500); - - handleChange = (value: string) => { - this.props.dispatch({ type: Actions.SEARCH_QUERY_CHANGE, payload: value }); - this.debouncedOFSearch(value); - }; - - get cropSearchQuery() { return this.props.cropSearchQuery || ""; } - - get tooShort() { - const termLength = this.cropSearchQuery.length; - return !this.props.cropSearchInProgress && termLength > 0 && termLength < 3; - } - - get validSearchTerm() { - return this.cropSearchQuery.length > 2; - } - - get showResultChangeSpinner() { - return this.props.cropSearchInProgress && - this.props.cropSearchResults.length > 0 && - this.validSearchTerm; - } - - componentDidMount() { - this.props.openfarmSearch(this.cropSearchQuery)(this.props.dispatch); - } +export class RawCropCatalog + extends React.Component { componentWillUnmount = () => this.props.dispatch({ type: Actions.SET_PLANT_TYPE_CHANGE_ID, @@ -86,28 +51,24 @@ export class RawCropCatalog extends React.Component { backTo={Path.plants()} /> - : undefined} /> + searchTerm={this.props.cropSearchQuery} + placeholder={t("Search crops...")} + onChange={searchTerm => this.props.dispatch({ + type: Actions.SEARCH_QUERY_CHANGE, + payload: searchTerm, + })} + autoFocus={true} /> - { } }; -/** Basic field: value display for OpenFarm crop properties. */ +/** Basic field: value display for crop properties. */ const InfoField = (props: InfoFieldProps) => -
  • - {EMOJI[props.title]} - +
    +
    + {EMOJI[props.title]} + +
    {props.children}
    -
  • ; +
    ; const OMITTED_PROPERTIES = [ "name", - "slug", - "processing_pictures", "description", - "main_image_path", - "tags_array", - "guides_count", - "svg_icon", - "taxon", + "image", + "icon", + "companions", ]; const NO_VALUE = t("Not available"); +interface SummaryItemPropsBase { + key: string; + i: number; + field: string; +} + +interface CmPropertyProps extends SummaryItemPropsBase { + value: number; +} + +interface CommonNamesProps extends SummaryItemPropsBase { + value: string[]; +} + +interface DefaultPropertyDisplayProps extends SummaryItemPropsBase { + value: string; +} + /** - * Need to convert the `cm` provided by OpenFarm to `mm` + * Need to convert the `cm` provided by crop data to `mm` * to match the Farm Designer units. */ -const CmProperty = ({ i, field, value }: SummaryItemProps) => +const CmProperty = ({ i, field, value }: CmPropertyProps) => - {!isNaN(parseInt(value)) - ? (parseInt(value) * 10) + t("mm") + {value + ? (value * 10) + t("mm") : NO_VALUE} ; /** Comma-separated list of crop common names. */ -const CommonNames = ({ i, field, value }: SummaryItemProps) => +const CommonNames = ({ i, field, value }: CommonNamesProps) => - {(isArray(value) - ? value.join(", ") - : value) || NO_VALUE} + {value.join(", ") || NO_VALUE} ; /** Default behavior for all other properties. */ -const DefaultPropertyDisplay = ({ i, field, value }: SummaryItemProps) => +const DefaultPropertyDisplay = ({ i, field, value }: DefaultPropertyDisplayProps) => {value || NO_VALUE} ; /** Choose the appropriate display function for the crop property. */ -const handleDisplay = ([field, value]: string[], i: number) => { - const commonProps: SummaryItemProps = { key: field, i, field, value }; +const handleDisplay = ( + [field, value]: [string, string | string[] | number], + i: number, +) => { + const commonProps: SummaryItemPropsBase = { key: field, i, field }; switch (field) { case "spread": case "row_spacing": case "height": - return ; + return ; case "common_names": - return ; + return ; default: - return ; + return ; } }; interface CropInfoListProps { - result: CropLiveSearchResult; + crop: Crop; dispatch: Function; selectMostUsedCurves(slug: string): void; - openfarmCropFetch: OpenfarmSearch; } -/** Display crop properties from OpenFarm. */ +/** Display crop properties. */ const CropInfoList = (props: CropInfoListProps) => { - return
    -
      - {chain(props.result.crop) - .omit(OMITTED_PROPERTIES) - .toPairs() - .map(handleDisplay) - .value()} - -
    + return
    + {chain(props.crop) + .omit(OMITTED_PROPERTIES) + .toPairs() + .map(handleDisplay) + .value()} +
    ; }; -/** Display companion plant list from OpenFarm. */ +/** Display companion plant list. */ const Companions = (props: CropInfoListProps) => { - const { result, dispatch, openfarmCropFetch } = props; - if (result.companions.length == 0) { return
    ; } + const { crop, dispatch } = props; + const companions = crop.companions + .filter(slug => findCrop(slug).name != "Generic plant"); + if (companions.length == 0) { return; } return
    - {result.companions.map((companion, index) => - { + const companion = findCrop(companionSlug); + return { - openfarmCropFetch(companion.slug)(dispatch); unselectPlant(dispatch)(); - props.selectMostUsedCurves(companion.slug); + props.selectMostUsedCurves(companionSlug); }} onDragStart={() => { dispatch({ @@ -193,13 +195,14 @@ const Companions = (props: CropInfoListProps) => { payload: undefined, }), 500); }} - to={Path.cropSearch(companion.slug)}> + to={Path.cropSearch(companionSlug)}>

    {companion.name}

    - )} + ; + })}
    ; }; @@ -241,39 +244,15 @@ const AddPlantHereButton = (props: AddPlantHereButtonProps) => { /** Image of crop to drag into map. */ const CropDragInfoTile = - ({ svgIcon }: { svgIcon: string | undefined }) => + ({ slug }: { slug: string }) =>
    + src={findIcon(slug)} + onDragStart={setDragIcon(slug)} />
    ; -/** Text and link for crop editing. */ -const EditOnOpenFarm = ({ slug }: { slug: string }) => -
    - {t("Edit on")}  - - {"OpenFarm"} - -
    ; - -/** Get values common to crop panel headers. */ -export const getCropHeaderProps = (props: { - cropSearchResults: CropLiveSearchResult[] -}) => { - const crop = Path.getSlug(Path.cropSearch()); - const result = findBySlug(props.cropSearchResults, crop); - const basePath = Path.cropSearch(); - const backgroundURL = `linear-gradient( - rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), url(${result.images[0]})`; - return { crop, result, basePath, backgroundURL }; -}; - export function mapStateToProps(props: Everything): CropInfoProps { return { - openfarmCropFetch: OFCropFetch, dispatch: props.dispatch, botPosition: validBotLocationData(props.bot.hardware.location_data).position, xySwap: !!getWebAppConfigValue(() => props)(BooleanSetting.xy_swap), @@ -287,68 +266,17 @@ export function mapStateToProps(props: Everything): CropInfoProps { designer: props.resources.consumers.farm_designer, }; } -/** Get OpenFarm crop search results for crop info page contents. */ -export const searchForCurrentCrop = (openfarmCropFetch: OpenfarmSearch) => - (dispatch: Function) => { - const crop = Path.getSlug(Path.cropSearch()); - openfarmCropFetch(crop)(dispatch); - unselectPlant(dispatch)(); - }; - -interface CropInfoState { - crop: string; - currentImage: TaggedImage | undefined; -} - -const toTaggedImage = (url: string): TaggedImage => ({ - kind: "Image", - uuid: url, - specialStatus: SpecialStatus.SAVED, - body: { - created_at: "", - updated_at: "", - device_id: 0, - attachment_processed_at: "1", - attachment_url: url, - meta: { - x: undefined, - y: undefined, - z: undefined, - } - }, -}); - -export class RawCropInfo extends React.Component { - state: CropInfoState = { - crop: Path.getSlug(Path.cropSearch()), - currentImage: undefined, - }; +export class RawCropInfo extends React.Component { componentDidMount() { - this.clearCropSearchResults("")(); - this.setState({ currentImage: undefined }); - this.props.dispatch(searchForCurrentCrop(this.props.openfarmCropFetch)); - this.selectMostUsedCurves(Path.getSlug(Path.cropSearch())); - } - - componentDidUpdate() { - const crop = Path.getSlug(Path.cropSearch()); - if (crop != this.state.crop) { - this.selectMostUsedCurves(crop); - this.setState({ crop }); - this.clearCropSearchResults(crop)(); - this.setState({ currentImage: undefined }); - } - if (isUndefined(this.state.currentImage) && this.imageData.length > 0) { - this.setCurrentImage(0); - } + this.selectMostUsedCurves(Path.getCropSlug()); } selectMostUsedCurves = (slug: string) => { const findCurve = findMostUsedCurveForCrop({ plants: this.props.plants, curves: this.props.curves, - openfarmSlug: slug, + slug: slug, }); [CurveType.water, CurveType.spread, CurveType.height].map(curveType => { const id = findCurve(curveType)?.body.id; @@ -356,44 +284,28 @@ export class RawCropInfo extends React.Component { }); }; - /** Clear the current crop search results. */ - clearCropSearchResults = (crop: string) => () => { - const { dispatch } = this.props; - if (!this.props.designer.cropSearchQuery) { - dispatch({ type: Actions.SEARCH_QUERY_CHANGE, payload: crop }); - } - dispatch({ type: Actions.OF_SEARCH_RESULTS_OK, payload: [] }); - }; - - get images() { - const crop = getCropHeaderProps({ - cropSearchResults: this.props.designer.cropSearchResults, - }); - return crop.result.images - .filter(image => !image.includes(FilePath.DEFAULT_ICON)); - } - get imageData() { return this.images.map(toTaggedImage); } - - setCurrentImage = (index: number) => - this.setState({ currentImage: this.imageData[index] }); - render() { const { dispatch, designer } = this.props; - const { cropSearchResults, cropSearchInProgress } = designer; - const { crop, result, basePath, backgroundURL } = - getCropHeaderProps({ cropSearchResults }); + const slug = Path.getCropSlug(); + const crop = findCrop(slug); + const image = findImage(slug); const panelName = "crop-info"; return - + title={crop.name} + backTo={Path.cropSearch()} + onBack={() => !designer.cropSearchQuery && dispatch({ + type: Actions.SEARCH_QUERY_CHANGE, + payload: startCase(slug).toLowerCase(), + })} + style={{ + background: `linear-gradient( + rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), url(${image})` + }} + description={crop.description}> + @@ -403,81 +315,70 @@ export class RawCropInfo extends React.Component { + itemName={crop.name} />
    } /> - - - - {DevSettings.futureFeaturesEnabled() && - } -
    - - dispatch({ - type: Actions.SET_CROP_STAGE, - payload: ddi.value, - })} /> -
    -
    - - dispatch({ - type: Actions.SET_CROP_PLANTED_AT, - payload: e.currentTarget.value, - }) - } /> -
    - - - {this.images.length > 0 && - } - {this.images.length > 0 && - } -
    + + + +
    + + dispatch({ + type: Actions.SET_CROP_STAGE, + payload: ddi.value, + })} /> +
    +
    + + dispatch({ + type: Actions.SET_CROP_PLANTED_AT, + payload: e.currentTarget.value, + }) + } /> +
    +
    + + dispatch({ + type: Actions.SET_CROP_RADIUS, + payload: parseInt(e.currentTarget.value), + }) + } /> +
    + +
    ; } diff --git a/frontend/plants/crop_search_results.tsx b/frontend/plants/crop_search_results.tsx new file mode 100644 index 0000000000..035b4fa4af --- /dev/null +++ b/frontend/plants/crop_search_results.tsx @@ -0,0 +1,89 @@ +import React from "react"; +import { Link } from "../link"; +import { + EmptyStateWrapper, EmptyStateGraphic, +} from "../ui/empty_state_wrapper"; +import { Actions } from "../constants"; +import { t } from "../i18next_wrapper"; +import { Path } from "../internal_urls"; +import { edit, save } from "../api/crud"; +import { TaggedPlantPointer } from "farmbot"; +import { setHoveredPlant } from "../farm_designer/map/actions"; +import { HoveredPlantPayl } from "../farm_designer/interfaces"; +import { findCrops, findIcon, findImage } from "../crops/find"; +import { Crop } from "../crops/interfaces"; + +export interface SearchResultProps { + searchTerm: string; + plant: TaggedPlantPointer | undefined; + dispatch: Function; + bulkPlantSlug: string | undefined; + hoveredPlant: HoveredPlantPayl; +} + +export class CropSearchResults extends React.Component { + render() { + const { dispatch, plant } = this.props; + const { plantUUID } = this.props.hoveredPlant; + const to = (slug: string) => { + if (plant) { + return Path.plants(plant.body.id); + } + if (this.props.bulkPlantSlug) { + return Path.plants("select"); + } + return Path.cropSearch(slug); + }; + const click = (slug: string, crop: Crop) => () => { + if (plant) { + dispatch(edit(plant, { + name: crop.name, + openfarm_slug: slug, + })); + dispatch(save(plant.uuid)); + dispatch({ + type: Actions.SET_PLANT_TYPE_CHANGE_ID, + payload: undefined, + }); + if (plant.uuid == plantUUID) { + dispatch(setHoveredPlant(plantUUID)); + } + } + if (this.props.bulkPlantSlug) { + dispatch({ + type: Actions.SET_SLUG_BULK, + payload: slug, + }); + } + }; + const crops = findCrops(this.props.searchTerm); + return 0} + graphic={EmptyStateGraphic.no_crop_results} + title={t("No search results")} + colorScheme={"plants"}> +
    + {Object.entries(crops).map(([slug, crop]) => { + const image = findImage(slug); + return +
    + + +
    +
    + ; + })} +
    + ; + } +} diff --git a/frontend/plants/curve_info.tsx b/frontend/plants/curve_info.tsx index 777c8b78e6..cc017e7e08 100644 --- a/frontend/plants/curve_info.tsx +++ b/frontend/plants/curve_info.tsx @@ -37,19 +37,21 @@ export const AllCurveInfo = (props: AllCurveInfoProps) => { export const CurveInfo = (props: CurveInfoProps) => { const { plant, onChange, curve, curves, plants, curveType } = props; const [hovered, setHovered] = React.useState(undefined); - return
    -
    + return
    +
    - { - (ddi.headingId || ddi.isNull) - && onChange(ddi.isNull ? undefined : ddi.value, props.curveType); - }} /> - {curve && - - } +
    + {curve && + + } + { + (ddi.headingId || ddi.isNull) + && onChange(ddi.isNull ? undefined : ddi.value, props.curveType); + }} /> +
    {curve && { - const openfarmSlug = props.plant?.slug || Path.getSlug(Path.cropSearch()); + const slug = props.plant?.slug || Path.getCropSlug(); const list: (DropDownItem | undefined)[] = []; list.push(NULL_CHOICE); const usedIds = props.plants - .filter(plant => plant.body.openfarm_slug == openfarmSlug) + .filter(plant => plant.body.openfarm_slug == slug) .map(plant => plant.body[CURVE_KEY_LOOKUP[props.curveType]]) .filter(id => id); list.push({ @@ -107,16 +109,16 @@ export const curveToDdi = }; interface FindCurveProps { - openfarmSlug: string; + slug: string; plants: TaggedPlantPointer[]; curves: TaggedCurve[]; } export const findMostUsedCurveForCrop = (props: FindCurveProps) => (curveType: CurveType): TaggedCurve | undefined => { - const { openfarmSlug, plants, curves } = props; + const { slug, plants, curves } = props; const counts = countBy(plants - .filter(p => p.body.openfarm_slug == openfarmSlug) + .filter(p => p.body.openfarm_slug == slug) .map(p => p.body[CURVE_KEY_LOOKUP[curveType]] as number | undefined) .filter(x => x)); const maxCount = Math.max(...Object.values(counts)); diff --git a/frontend/plants/edit_plant_status.tsx b/frontend/plants/edit_plant_status.tsx index 7f363bffd6..fb07c874aa 100644 --- a/frontend/plants/edit_plant_status.tsx +++ b/frontend/plants/edit_plant_status.tsx @@ -13,7 +13,7 @@ import { t } from "../i18next_wrapper"; import { UUID } from "../resources/interfaces"; import { edit, save } from "../api/crud"; import { EditPlantStatusProps } from "./plant_panel"; -import { capitalize, mean, round, startCase } from "lodash"; +import { mean, round, startCase } from "lodash"; import { TimeSettings } from "../interfaces"; import { Link } from "../link"; import { Path } from "../internal_urls"; @@ -23,6 +23,7 @@ import { CurveType } from "../curves/templates"; import { curveToDdi, CURVE_KEY_LOOKUP } from "./curve_info"; import { CURVE_TYPES } from "../curves/curves_inventory"; import { betterCompact } from "../util"; +import { findCrop } from "../crops/find"; export const PLANT_STAGE_DDI_LOOKUP = (): { [x: string]: DropDownItem } => ({ planned: { label: t("Planned"), value: "planned" }, @@ -92,7 +93,7 @@ const getUpdateByPlantStage = (plant_stage: PlantStage): PlantOptions => { /** Select a `plant_stage` for a plant. */ export function EditPlantStatus(props: EditPlantStatusProps) { const { plantStatus, updatePlant, uuid } = props; - return
    + return
    { const plants = props.allPoints.filter(point => props.selected.includes(point.uuid) && point.kind === "Point" && @@ -335,7 +336,7 @@ export const PlantSlugBulkUpdate = (props: PlantSlugBulkUpdateProps) => { plants.map(plant => { props.dispatch(edit(plant, { openfarm_slug: slug, - name: capitalize(slug).replace(/-/g, " "), + name: findCrop(slug).name, })); props.dispatch(save(plant.uuid)); }); diff --git a/frontend/plants/grid/__tests__/plant_grid_test.tsx b/frontend/plants/grid/__tests__/plant_grid_test.tsx index 4e7e40961e..29379927df 100644 --- a/frontend/plants/grid/__tests__/plant_grid_test.tsx +++ b/frontend/plants/grid/__tests__/plant_grid_test.tsx @@ -142,6 +142,7 @@ describe("", () => { const p = fakeProps(); p.openfarm_slug = "beet"; const designer = fakeDesignerState(); + designer.cropRadius = 100; designer.cropStage = "planted"; designer.cropPlantedAt = "2020-01-20T20:00:00.000Z"; designer.cropWaterCurveId = 1; diff --git a/frontend/plants/grid/generate_grid.ts b/frontend/plants/grid/generate_grid.ts index 17d29733e1..111c1a0673 100644 --- a/frontend/plants/grid/generate_grid.ts +++ b/frontend/plants/grid/generate_grid.ts @@ -4,6 +4,7 @@ import { import { range } from "lodash"; import { PlantGridData, PlantGridInitOption } from "./interfaces"; import { DesignerState } from "../../farm_designer/interfaces"; +import { verifiedCropSlug, DEFAULT_PLANT_RADIUS } from "../../farm_designer/plant"; const generateXs = (start: number, count: number, spacing: number, offsetCol: boolean) => @@ -34,7 +35,7 @@ export function vectorGrid(params: PlantGridData, offsetPacking: boolean): } const createPlantGridMapper = ( - openfarm_slug: string, + cropSlug: string, cropName: string, meta: Record, designer: DesignerState | undefined, @@ -43,12 +44,12 @@ const createPlantGridMapper = ( const [x, y] = vec; return { name: cropName, - radius: 25, + radius: designer?.cropRadius || DEFAULT_PLANT_RADIUS, depth: 0, z: 0, x, y, - openfarm_slug, + openfarm_slug: verifiedCropSlug(cropSlug), pointer_type: "Plant", plant_stage: designer?.cropStage || "planned", planted_at: designer?.cropPlantedAt, diff --git a/frontend/plants/grid/plant_grid.tsx b/frontend/plants/grid/plant_grid.tsx index 09f7bdbea0..f005a98e29 100644 --- a/frontend/plants/grid/plant_grid.tsx +++ b/frontend/plants/grid/plant_grid.tsx @@ -64,7 +64,7 @@ export class PlantGrid extends React.Component { itemName: this.props.itemName, grid: this.state.grid, offsetPacking: this.state.offsetPacking, - radius: this.props.radius, + radius: this.props.radius || this.props.designer?.cropRadius, z: this.props.z, meta: this.props.meta, plantStage: this.props.designer?.cropStage, diff --git a/frontend/plants/openfarm_search_results.tsx b/frontend/plants/openfarm_search_results.tsx deleted file mode 100644 index 27808411e3..0000000000 --- a/frontend/plants/openfarm_search_results.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React from "react"; -import { Link } from "../link"; -import { - EmptyStateWrapper, EmptyStateGraphic, -} from "../ui/empty_state_wrapper"; -import { Actions, Content } from "../constants"; -import { t } from "../i18next_wrapper"; -import { ExternalUrl } from "../external_urls"; -import { FilePath, Path } from "../internal_urls"; -import { edit, save } from "../api/crud"; -import { TaggedPlantPointer } from "farmbot"; -import { setHoveredPlant } from "../farm_designer/map/actions"; -import { HoveredPlantPayl } from "../farm_designer/interfaces"; - -/** A stripped down version of OFSearchResult */ -interface Result { - crop: { - slug: string; - name: string; - svg_icon?: string | undefined; - main_image_path: string; - }; - images: string[]; -} - -export interface SearchResultProps { - cropSearchResults: Result[]; - cropSearchInProgress: boolean; - plant: TaggedPlantPointer | undefined; - dispatch: Function; - bulkPlantSlug: string | undefined; - hoveredPlant: HoveredPlantPayl; -} - -export class OpenFarmResults extends React.Component { - - get text(): React.ReactNode { - return

    {`${t(Content.CROP_NOT_FOUND_INTRO)} `} - - {t(Content.CROP_NOT_FOUND_LINK)} - -

    ; - } - - render() { - const { - cropSearchResults, cropSearchInProgress, dispatch, plant, - } = this.props; - const { plantUUID } = this.props.hoveredPlant; - const to = (slug: string) => { - if (plant) { - return Path.plants(plant.body.id); - } - if (this.props.bulkPlantSlug) { - return Path.plants("select"); - } - return Path.cropSearch(slug); - }; - const click = (crop: Result["crop"]) => () => { - if (plant) { - dispatch(edit(plant, { - name: crop.name, - openfarm_slug: crop.slug, - })); - dispatch(save(plant.uuid)); - dispatch({ - type: Actions.SET_PLANT_TYPE_CHANGE_ID, - payload: undefined, - }); - if (plant.uuid == plantUUID && crop.svg_icon) { - dispatch(setHoveredPlant(plantUUID, crop.svg_icon)); - } - } - if (this.props.bulkPlantSlug) { - dispatch({ - type: Actions.SET_SLUG_BULK, - payload: crop.slug, - }); - } - }; - return 0} - graphic={EmptyStateGraphic.no_crop_results} - title={cropSearchInProgress - ? t("Searching...") - : t("No search results")} - textElement={cropSearchInProgress ? undefined : this.text} - colorScheme={"plants"}> -
    - {cropSearchResults.map(resp => { - const { crop } = resp; - const image = crop.main_image_path.startsWith("https") - ? crop.main_image_path - : FilePath.DEFAULT_ICON; - const fallbackImageClass = crop.main_image_path.startsWith("https") - ? "" - : " fallback-image"; - return -
    - -
    -
    - ; - })} -
    - ; - } -} diff --git a/frontend/plants/plant_inventory_item.tsx b/frontend/plants/plant_inventory_item.tsx index 6740382e42..7b405d9aaf 100644 --- a/frontend/plants/plant_inventory_item.tsx +++ b/frontend/plants/plant_inventory_item.tsx @@ -2,15 +2,15 @@ import React from "react"; import { TaggedPlant, Mode } from "../farm_designer/map/interfaces"; import { unpackUUID } from "../util"; import { t } from "../i18next_wrapper"; -import { maybeGetCachedPlantIcon } from "../open_farm/cached_crop"; import { selectPoint, setHoveredPlant, mapPointClickAction, } from "../farm_designer/map/actions"; import { PlantStageAndAge, plantAgeAndStage } from "./map_state_to_props"; import { getMode } from "../farm_designer/map/util"; import { isUndefined, round } from "lodash"; -import { FilePath, Path } from "../internal_urls"; +import { Path } from "../internal_urls"; import { useNavigate } from "react-router"; +import { findIcon } from "../crops/find"; export interface PlantInventoryItemProps { plant: TaggedPlant; @@ -21,7 +21,6 @@ export interface PlantInventoryItemProps { // The individual plants that show up in the farm designer sub nav. export const PlantInventoryItem = (props: PlantInventoryItemProps) => { - const [iconState, setIconState] = React.useState(""); const navigate = useNavigate(); const { plant, dispatch } = props; @@ -30,8 +29,7 @@ export const PlantInventoryItem = (props: PlantInventoryItemProps) => { const toggle = (action: "enter" | "leave") => { const isEnter = action === "enter"; const plantUUID = isEnter ? plant.uuid : undefined; - const icon = isEnter ? iconState : ""; - dispatch(setHoveredPlant(plantUUID, icon)); + dispatch(setHoveredPlant(plantUUID)); }; const click = () => { @@ -48,12 +46,9 @@ export const PlantInventoryItem = (props: PlantInventoryItemProps) => { } }; - const onLoad = (e: React.SyntheticEvent) => - maybeGetCachedPlantIcon(slug, e.currentTarget, setIconState); - - // Name given from OpenFarm's API. const label = plant.body.name || "Unknown plant"; const slug = plant.body.openfarm_slug; + const icon = findIcon(slug); return
    { onClick={click}> + src={icon} /> {label} diff --git a/frontend/plants/plant_panel.tsx b/frontend/plants/plant_panel.tsx index 3d6c14e6ea..670ee038e2 100644 --- a/frontend/plants/plant_panel.tsx +++ b/frontend/plants/plant_panel.tsx @@ -1,7 +1,7 @@ import React from "react"; import { FormattedPlantInfo } from "./map_state_to_props"; import { useNavigate } from "react-router"; -import { BlurableInput, Row, Help } from "../ui"; +import { BlurableInput, Help } from "../ui"; import { PlantStage, TaggedCurve, TaggedFarmwareEnv, TaggedGenericPointer, TaggedPlantPointer, Xyz, @@ -25,6 +25,7 @@ import { BotPosition, SourceFbosConfig } from "../devices/interfaces"; import { AllCurveInfo, CURVE_KEY_LOOKUP } from "./curve_info"; import { BotSize } from "../farm_designer/map/interfaces"; import { UpdatePlant } from "./plant_info"; +import { ALIASED_SLUG_LOOKUP } from "../crops/constants"; export interface PlantPanelProps { info: FormattedPlantInfo; @@ -85,9 +86,9 @@ export const EditPlantLocation = (props: EditPlantLocationProps) => { points: props.soilHeightPoints, farmwareEnvs: props.farmwareEnvs, }); - return + return
    {["x", "y", "z"].map((axis: Xyz) => -
    +
    {axis == "z" && !isUndefined(soilZ) && @@ -101,7 +102,7 @@ export const EditPlantLocation = (props: EditPlantLocationProps) => { [axis]: parseIntInput(e.currentTarget.value) })} />
    )} - ; +
    ; }; export interface EditPlantRadiusProps extends EditPlantProperty { @@ -109,7 +110,7 @@ export interface EditPlantRadiusProps extends EditPlantProperty { } export const EditPlantRadius = (props: EditPlantRadiusProps) => - +
    onCommit={e => props.updatePlant(props.uuid, { radius: parseIntInput(e.currentTarget.value) })} /> - ; +
    ; export interface EditPlantDepthProps extends EditPlantProperty { depth: number; } export const EditPlantDepth = (props: EditPlantDepthProps) => - +
    onCommit={e => props.updatePlant(props.uuid, { depth: parseIntInput(e.currentTarget.value) })} /> - ; +
    ; interface ListItemProps { name?: string; @@ -144,7 +145,7 @@ interface ListItemProps { } export const ListItem = (props: ListItemProps) => -
  • +
    {props.name &&
  • ; +
    ; export function PlantPanel(props: PlantPanelProps) { const { @@ -162,13 +163,14 @@ export function PlantPanel(props: PlantPanelProps) { const { x, y, z } = info; const commonProps = { uuid, updatePlant }; const navigate = useNavigate(); + const slugForCropInfoLink = ALIASED_SLUG_LOOKUP[slug] || slug; return -
      +
      - {startCase(slug)} + to={Path.cropSearch(slugForCropInfoLink)}> + {startCase(slugForCropInfoLink)} { @@ -178,45 +180,40 @@ export function PlantPanel(props: PlantPanelProps) { }} /> {(timeSettings && !inSavedGarden) && - -
      - - - +
      +
      + +
      -
      - - {daysOldText({ age: daysOld, stage: plantStatus })} - + {(!inSavedGarden) + ? + : t(startCase(plantStatus))} +
      + + {daysOldText({ age: daysOld, stage: plantStatus })}
      - } - +
      } +
      - - - + +
      +
      - - {!isUndefined(info.depth) && - - } - - {(!inSavedGarden) - ? - : t(startCase(plantStatus))} - + {!isUndefined(info.depth) && } +
      {info.uuid.startsWith("Point") && {value || ""}; } })} -
    +
    ; } diff --git a/frontend/point_groups/__tests__/point_group_item_test.tsx b/frontend/point_groups/__tests__/point_group_item_test.tsx index f75293358e..9c4e8d773b 100644 --- a/frontend/point_groups/__tests__/point_group_item_test.tsx +++ b/frontend/point_groups/__tests__/point_group_item_test.tsx @@ -1,8 +1,3 @@ -jest.mock("../../open_farm/cached_crop", () => ({ - maybeGetCachedPlantIcon: jest.fn(), - setImgSrc: jest.fn(), -})); - jest.mock("../../farm_designer/map/actions", () => ({ setHoveredPlant: jest.fn(), })); @@ -12,20 +7,16 @@ import React from "react"; import { PointGroupItem, PointGroupItemProps, genericPointIcon, genericWeedIcon, + svgToUrl, } from "../point_group_item"; import { shallow, mount } from "enzyme"; import { fakePlant, fakePointGroup, fakePoint, fakeToolSlot, fakeWeed, fakeTool, fakePlantTemplate, } from "../../__test_support__/fake_state/resources"; -import { - maybeGetCachedPlantIcon, setImgSrc, -} from "../../open_farm/cached_crop"; import { setHoveredPlant } from "../../farm_designer/map/actions"; import { cloneDeep } from "lodash"; -import { imgEvent } from "../../__test_support__/fake_html_events"; import { error } from "../../toast/toast"; -import { svgToUrl } from "../../open_farm/icons"; import { overwriteGroup } from "../actions"; import { mockDispatch } from "../../__test_support__/fake_dispatch"; import { fakeToolTransformProps } from "../../__test_support__/fake_tool_info"; @@ -50,50 +41,18 @@ describe("", () => { expect(el.first().prop("onClick")).toEqual(i.click); }); - it("fetches plant icon", async () => { - const p = fakeProps(); - p.point = fakePlant(); - const i = new PointGroupItem(p); - const fakeImgEvent = imgEvent(); - await i.maybeGetCachedIcon(fakeImgEvent); - const slug = i.props.point.kind == "Point" && - i.props.point.body.pointer_type === "Plant" - ? i.props.point.body.openfarm_slug - : "slug"; - expect(maybeGetCachedPlantIcon) - .toHaveBeenCalledWith(slug, expect.any(Object), expect.any(Function)); - expect(setImgSrc).not.toHaveBeenCalled(); - }); - - it("doesn't fetch non-plant icon", async () => { - const p = fakeProps(); - p.point = fakeWeed(); - const i = new PointGroupItem(p); - const fakeImgEvent = imgEvent(); - await i.maybeGetCachedIcon(fakeImgEvent); - expect(maybeGetCachedPlantIcon).not.toHaveBeenCalled(); - expect(setImgSrc).not.toHaveBeenCalled(); - }); - - it("sets icon in state", () => { - const i = new PointGroupItem(fakeProps()); - i.setState = jest.fn(); - i.setIconState("fake icon"); - expect(i.setState).toHaveBeenCalledWith({ icon: "fake icon" }); - }); - it("displays default plant icon", () => { const p = fakeProps(); p.point = fakePlant(); const wrapper = mount(); - expect(wrapper.find("img").props().src).toEqual(FilePath.DEFAULT_ICON); + expect(wrapper.find("img").props().src).toEqual("/crops/icons/strawberry.avif"); }); it("displays default plant template icon", () => { const p = fakeProps(); p.point = fakePlantTemplate(); const wrapper = mount(); - expect(wrapper.find("img").props().src).toEqual(FilePath.DEFAULT_ICON); + expect(wrapper.find("img").props().src).toEqual("/crops/icons/mint.avif"); }); it("displays point icon", () => { @@ -142,24 +101,21 @@ describe("", () => { it("handles mouse enter", () => { const i = new PointGroupItem(fakeProps()); - i.state.icon = "X"; i.enter(); expect(i.props.dispatch).toHaveBeenCalledTimes(1); - expect(setHoveredPlant).toHaveBeenCalledWith(i.props.point.uuid, "X"); + expect(setHoveredPlant).toHaveBeenCalledWith(i.props.point.uuid); }); it("handles mouse enter: no action", () => { const p = fakeProps(); p.dispatch = undefined; const i = new PointGroupItem(p); - i.state.icon = "X"; i.enter(); expect(setHoveredPlant).not.toHaveBeenCalled(); }); it("handles mouse exit", () => { const i = new PointGroupItem(fakeProps()); - i.state.icon = "X"; i.leave(); expect(i.props.dispatch).toHaveBeenCalledTimes(1); expect(setHoveredPlant).toHaveBeenCalledWith(undefined); @@ -169,7 +125,6 @@ describe("", () => { const p = fakeProps(); p.dispatch = undefined; const i = new PointGroupItem(p); - i.state.icon = "X"; i.leave(); expect(setHoveredPlant).not.toHaveBeenCalled(); }); diff --git a/frontend/point_groups/point_group_item.tsx b/frontend/point_groups/point_group_item.tsx index 690f4a2285..e5e20bb884 100644 --- a/frontend/point_groups/point_group_item.tsx +++ b/frontend/point_groups/point_group_item.tsx @@ -1,11 +1,8 @@ import React from "react"; -import { svgToUrl } from "../open_farm/icons"; -import { maybeGetCachedPlantIcon } from "../open_farm/cached_crop"; import { setHoveredPlant } from "../farm_designer/map/actions"; import { TaggedPointGroup, uuid, TaggedPoint, TaggedToolSlotPointer, TaggedTool, TaggedPlantTemplate, - TaggedPlantPointer, } from "farmbot"; import { error } from "../toast/toast"; import { t } from "../i18next_wrapper"; @@ -15,6 +12,12 @@ import { ToolSlotSVG } from "../farm_designer/map/layers/tool_slots/tool_graphic import { ToolTransformProps } from "../tools/interfaces"; import { FilePath, Path } from "../internal_urls"; import { NavigationContext } from "../routes_helpers"; +import { findIcon } from "../crops/find"; + +export const svgToUrl = (xml: string): string => { + const DATA_URI = "data:image/svg+xml;utf8,"; + return DATA_URI + encodeURIComponent(xml); +}; export interface PointGroupItemProps { point: TaggedPoint | TaggedPlantTemplate; @@ -26,8 +29,6 @@ export interface PointGroupItemProps { navigate?: boolean; } -interface PointGroupItemState { icon: string; } - const removePoint = (group: TaggedPointGroup, pointId: number) => (dispatch: Function) => { type Body = (typeof group)["body"]; @@ -59,14 +60,12 @@ export const genericWeedIcon = (color: string | undefined) => // The individual plants in the point group detail page. export class PointGroupItem - extends React.Component { - - state: PointGroupItemState = { icon: "" }; + extends React.Component { key = uuid(); enter = () => this.props.dispatch?.( - setHoveredPlant(this.props.point.uuid, this.state.icon)); + setHoveredPlant(this.props.point.uuid)); leave = () => this.props.dispatch?.(setHoveredPlant(undefined)); @@ -88,28 +87,18 @@ export class PointGroupItem this.leave(); }; - setIconState = (icon: string) => this.setState({ icon }); - get criteriaIcon() { return this.props.group && !this.props.group.body.point_ids .includes(this.props.point.body.id || 0); } - maybeGetCachedIcon = (e: React.SyntheticEvent) => { - const img = e.currentTarget; - if (this.props.point.kind == "PlantTemplate" - || this.props.point.body.pointer_type == "Plant") { - const slug = (this.props.point as TaggedPlantPointer | TaggedPlantTemplate) - .body.openfarm_slug; - maybeGetCachedPlantIcon(slug, img, this.setIconState); - } - }; - get initIcon() { - if (this.props.point.kind == "PlantTemplate") { return FilePath.DEFAULT_ICON; } + if (this.props.point.kind == "PlantTemplate") { + return findIcon(this.props.point.body.openfarm_slug); + } switch (this.props.point.body.pointer_type) { case "Plant": - return FilePath.DEFAULT_ICON; + return findIcon(this.props.point.body.openfarm_slug); case "GenericPointer": const { color } = this.props.point.body.meta; return svgToUrl(genericPointIcon(color)); @@ -156,7 +145,6 @@ export class PointGroupItem ; diff --git a/frontend/points/create_points.tsx b/frontend/points/create_points.tsx index a2732382a3..88edbac24c 100644 --- a/frontend/points/create_points.tsx +++ b/frontend/points/create_points.tsx @@ -218,9 +218,9 @@ export class RawCreatePoints PointProperties = () =>
      -
    • - -
      +
      +
      +
      - -
    • +
    +
    diff --git a/frontend/points/point_edit_actions.tsx b/frontend/points/point_edit_actions.tsx index d349c1a0f7..87c8b9f3d6 100644 --- a/frontend/points/point_edit_actions.tsx +++ b/frontend/points/point_edit_actions.tsx @@ -64,11 +64,6 @@ export const EditPointProperties = (props: EditPointPropertiesProps) => defaultAxes={props.defaultAxes} updatePoint={props.updatePoint} /> - - - {props.point.body.pointer_type == "GenericPointer" && ; export const AdditionalWeedProperties = (props: AdditionalWeedPropertiesProps) => -
      - - - - - {daysOldText(plantAgeAndStage(props.point))} - +
        +
        +
        + + +
        + +
        + + {daysOldText(plantAgeAndStage(props.point))} +
        +
        {Object.entries(props.point.body.meta).map(([key, value]) => { switch (key) { case "color": case "type": return
        ; + className={`meta-${key}-not-displayed`} + style={{ display: "none" }} />; case "created_by": return {lookupPointSource(value)} @@ -185,7 +188,7 @@ export interface EditPointRadiusProps { } export const EditPointRadius = (props: EditPointRadiusProps) => - +
        onCommit={e => props.updatePoint({ radius: round(parseIntInput(e.currentTarget.value)) })} /> - ; +
        ; export interface EditPointColorProps { updatePoint(update: PointUpdate): void; diff --git a/frontend/points/point_info.tsx b/frontend/points/point_info.tsx index e0da900722..43d38ef9ae 100644 --- a/frontend/points/point_info.tsx +++ b/frontend/points/point_info.tsx @@ -97,7 +97,7 @@ export const RawEditPoint = (props: EditPointProps) => { {point - ?
        + ?
        ; export const DevSettingsRows = () => -
        - - +
        diff --git a/frontend/three_d_garden/__tests__/bed_test.tsx b/frontend/three_d_garden/__tests__/bed_test.tsx index 9bbf78e895..a4dbc2278b 100644 --- a/frontend/three_d_garden/__tests__/bed_test.tsx +++ b/frontend/three_d_garden/__tests__/bed_test.tsx @@ -1,8 +1,33 @@ +jest.mock("../../farm_designer/map/layers/plants/plant_actions", () => ({ + dropPlant: jest.fn(), +})); + +let mockIsMobile = false; +jest.mock("../../screen_size", () => ({ + isMobile: () => mockIsMobile, +})); + +const mockSetPosition = jest.fn(); +interface MockRefCurrent { + position: { set: Function; }; +} +interface MockRef { + current: MockRefCurrent | undefined; +} +const mockRef: MockRef = { current: undefined }; +jest.mock("react", () => ({ + ...jest.requireActual("react"), + useRef: () => mockRef, +})); + import React from "react"; -import { mount } from "enzyme"; import { INITIAL } from "../config"; import { Bed, BedProps } from "../bed"; import { clone } from "lodash"; +import { fireEvent, render, screen } from "@testing-library/react"; +import { dropPlant } from "../../farm_designer/map/layers/plants/plant_actions"; +import { Path } from "../../internal_urls"; +import { fakeAddPlantProps } from "../../__test_support__/fake_props"; describe("", () => { const fakeProps = (): BedProps => ({ @@ -13,8 +38,8 @@ describe("", () => { it("renders bed", () => { const p = fakeProps(); p.config.extraLegsX = 0; - const wrapper = mount(); - expect(wrapper.html()).toContain("bed-group"); + const { container } = render(); + expect(container).toContainHTML("bed-group"); }); it("renders bed with extra legs", () => { @@ -22,7 +47,55 @@ describe("", () => { p.config.extraLegsX = 2; p.config.extraLegsY = 2; p.config.legsFlush = false; - const wrapper = mount(); - expect(wrapper.html()).toContain("bed-group"); + const { container } = render(); + expect(container).toContainHTML("bed-group"); + }); + + it("adds a plant", () => { + location.pathname = Path.mock(Path.cropSearch("mint")); + const p = fakeProps(); + p.addPlantProps = fakeAddPlantProps([]); + render(); + const soil = screen.getAllByText("soil")[0]; + fireEvent.click(soil); + expect(dropPlant).toHaveBeenCalledWith(expect.objectContaining({ + gardenCoords: { x: 1360, y: 620 }, + })); + }); + + it("updates pointer plant position", () => { + location.pathname = Path.mock(Path.cropSearch("mint")); + mockIsMobile = false; + mockRef.current = { position: { set: mockSetPosition } }; + const p = fakeProps(); + p.addPlantProps = fakeAddPlantProps([]); + render(); + const soil = screen.getAllByText("soil")[0]; + fireEvent.pointerMove(soil); + expect(mockSetPosition).toHaveBeenCalledWith(0, 0, -75); + }); + + it("handles missing ref", () => { + location.pathname = Path.mock(Path.cropSearch("mint")); + mockIsMobile = false; + mockRef.current = undefined; + const p = fakeProps(); + p.addPlantProps = fakeAddPlantProps([]); + render(); + const soil = screen.getAllByText("soil")[0]; + fireEvent.pointerMove(soil); + expect(mockSetPosition).not.toHaveBeenCalled(); + }); + + it("doesn't update pointer plant position: mobile", () => { + location.pathname = Path.mock(Path.cropSearch("mint")); + mockIsMobile = true; + mockRef.current = { position: { set: mockSetPosition } }; + const p = fakeProps(); + p.addPlantProps = fakeAddPlantProps([]); + render(); + const soil = screen.getAllByText("soil")[0]; + fireEvent.pointerMove(soil); + expect(mockSetPosition).not.toHaveBeenCalled(); }); }); diff --git a/frontend/three_d_garden/__tests__/garden_test.tsx b/frontend/three_d_garden/__tests__/garden_test.tsx index 122e8ce72f..29415b2435 100644 --- a/frontend/three_d_garden/__tests__/garden_test.tsx +++ b/frontend/three_d_garden/__tests__/garden_test.tsx @@ -1,6 +1,7 @@ let mockIsDesktop = false; jest.mock("../../screen_size", () => ({ isDesktop: () => mockIsDesktop, + isMobile: jest.fn(), })); import React from "react"; @@ -9,25 +10,27 @@ import { GardenModelProps, GardenModel } from "../garden"; import { clone } from "lodash"; import { INITIAL } from "../config"; import { render, screen } from "@testing-library/react"; -import { ASSETS } from "../constants"; +import { fakePlant } from "../../__test_support__/fake_state/resources"; +import { fakeAddPlantProps } from "../../__test_support__/fake_props"; describe("", () => { const fakeProps = (): GardenModelProps => ({ config: clone(INITIAL), activeFocus: "", setActiveFocus: jest.fn(), + addPlantProps: fakeAddPlantProps([]), }); it("renders", () => { - const wrapper = mount(); - expect(wrapper.html()).toContain("zoom-beacons"); - expect(wrapper.html()).not.toContain("stats"); - expect(wrapper.html()).toContain("darkgreen"); + const { container } = render(); + expect(container).toContainHTML("zoom-beacons"); + expect(container).not.toContainHTML("stats"); + expect(container).toContainHTML("darkgreen"); }); it("renders no user plants", () => { const p = fakeProps(); - p.plants = []; + p.addPlantProps = fakeAddPlantProps([]); render(); const plantLabels = screen.queryAllByText("Beet"); expect(plantLabels.length).toEqual(0); @@ -35,16 +38,9 @@ describe("", () => { it("renders user plant", () => { const p = fakeProps(); - p.plants = [ - { - label: "Beet", - icon: ASSETS.icons.beet, - spread: 175, - size: 150, - x: 0, - y: 0, - }, - ]; + const plant = fakePlant(); + plant.body.name = "Beet"; + p.addPlantProps = fakeAddPlantProps([plant]); render(); const plantLabels = screen.queryAllByText("Beet"); expect(plantLabels.length).toEqual(1); @@ -52,7 +48,7 @@ describe("", () => { it("renders promo plants", () => { const p = fakeProps(); - p.plants = undefined; + p.addPlantProps = undefined; render(); const plantLabels = screen.queryAllByText("Beet"); expect(plantLabels.length).toEqual(7); @@ -70,9 +66,10 @@ describe("", () => { p.config.viewCube = true; p.config.lab = true; p.activeFocus = "plant"; - const wrapper = mount(); - expect(wrapper.html()).toContain("gray"); - expect(wrapper.html()).toContain("stats"); + p.addPlantProps = undefined; + const { container } = render(); + expect(container).toContainHTML("gray"); + expect(container).toContainHTML("stats"); }); it("sets hover", () => { diff --git a/frontend/three_d_garden/__tests__/index_test.tsx b/frontend/three_d_garden/__tests__/index_test.tsx index a910b5ec12..ee85f796cf 100644 --- a/frontend/three_d_garden/__tests__/index_test.tsx +++ b/frontend/three_d_garden/__tests__/index_test.tsx @@ -5,11 +5,12 @@ import { import React from "react"; import { INITIAL } from "../config"; import { clone } from "lodash"; +import { fakeAddPlantProps } from "../../__test_support__/fake_props"; describe("", () => { const fakeProps = (): ThreeDGardenProps => ({ config: clone(INITIAL), - plants: [], + addPlantProps: fakeAddPlantProps([]), }); it("renders", () => { diff --git a/frontend/three_d_garden/__tests__/plants_test.tsx b/frontend/three_d_garden/__tests__/plants_test.tsx new file mode 100644 index 0000000000..32bc15c344 --- /dev/null +++ b/frontend/three_d_garden/__tests__/plants_test.tsx @@ -0,0 +1,121 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { clone } from "lodash"; +import { fakePlant } from "../../__test_support__/fake_state/resources"; +import { INITIAL } from "../config"; +import { + calculatePlantPositions, + convertPlants, + ThreeDPlant, + ThreeDPlantProps, +} from "../plants"; +import { CROPS } from "../../crops/constants"; + +describe("calculatePlantPositions()", () => { + it("calculates plant positions", () => { + const config = clone(INITIAL); + config.plants = "Spring"; + const positions = calculatePlantPositions(config); + expect(positions).toContainEqual({ + icon: CROPS.beet.icon, + label: "Beet", + size: 150, + spread: 175, + x: 350, + y: 680, + }); + expect(positions.length).toEqual(65); + }); + + it("returns no plants", () => { + const config = clone(INITIAL); + config.plants = ""; + const positions = calculatePlantPositions(config); + expect(positions.length).toEqual(0); + }); +}); + +describe("convertPlants()", () => { + it("converts plants", () => { + const config = clone(INITIAL); + config.bedXOffset = 10; + config.bedYOffset = 1; + + const plant0 = fakePlant(); + plant0.body.name = "Spinach"; + plant0.body.openfarm_slug = "spinach"; + plant0.body.x = 100; + plant0.body.y = 200; + + const plant1 = fakePlant(); + plant1.body.name = "Unknown"; + plant1.body.openfarm_slug = "not-set"; + plant1.body.x = 1000; + plant1.body.y = 2000; + + const plants = [plant0, plant1]; + + const convertedPlants = convertPlants(config, plants); + + expect(convertedPlants).toEqual([{ + icon: CROPS.spinach.icon, + label: "Spinach", + size: 50, + spread: 0, + x: 110, + y: 201, + }, + { + icon: CROPS["generic-plant"].icon, + label: "Unknown", + size: 50, + spread: 0, + x: 1010, + y: 2001, + }, + ]); + }); +}); + +describe("", () => { + const fakeProps = (): ThreeDPlantProps => { + const config = clone(INITIAL); + const plant = fakePlant(); + plant.body.name = "Beet"; + return { + plant: convertPlants(config, [plant])[0], + i: 0, + config: config, + hoveredPlant: undefined, + }; + }; + + it("renders label", () => { + const p = fakeProps(); + p.config.labels = true; + p.config.labelsOnHover = false; + p.labelOnly = true; + render(); + expect(screen.getByText("Beet")).toBeInTheDocument(); + }); + + it("renders hovered label", () => { + const p = fakeProps(); + p.config.labels = true; + p.config.labelsOnHover = true; + p.hoveredPlant = 0; + p.labelOnly = true; + render(); + expect(screen.getByText("Beet")).toBeInTheDocument(); + }); + + it("renders plant", () => { + const p = fakeProps(); + p.config.labels = false; + p.config.labelsOnHover = false; + p.labelOnly = false; + render(); + const { container } = render(); + expect(container).toContainHTML("image"); + }); +}); diff --git a/frontend/three_d_garden/bed.tsx b/frontend/three_d_garden/bed.tsx index 5ab3ef5fcd..295aa883c8 100644 --- a/frontend/three_d_garden/bed.tsx +++ b/frontend/three_d_garden/bed.tsx @@ -1,6 +1,10 @@ import React from "react"; -import { Box, Detailed, Extrude, useTexture } from "@react-three/drei"; -import { DoubleSide, Path, Shape, RepeatWrapping } from "three"; +import { + Billboard, Box, Detailed, Extrude, useTexture, Image, +} from "@react-three/drei"; +import { + DoubleSide, Path as LinePath, Shape, RepeatWrapping, Group as GroupType, +} from "three"; import { range } from "lodash"; import { threeSpace, zZero, getColorFromBrightness } from "./helpers"; import { Config, detailLevels } from "./config"; @@ -11,11 +15,24 @@ import { Packaging } from "./packaging"; import { Caster } from "./caster"; import { UtilitiesPost } from "./utilities_post"; import { Group, MeshPhongMaterial } from "./components"; +import { getMode, round } from "../farm_designer/map/util"; +import { + AxisNumberProperty, Mode, TaggedPlant, +} from "../farm_designer/map/interfaces"; +import { dropPlant } from "../farm_designer/map/layers/plants/plant_actions"; +import { TaggedCurve } from "farmbot"; +import { GetWebAppConfigValue } from "../config_storage/actions"; +import { DesignerState } from "../farm_designer/interfaces"; +import { isMobile } from "../screen_size"; +import { ThreeEvent } from "@react-three/fiber"; +import { Path } from "../internal_urls"; +import { findIcon } from "../crops/find"; +import { DEFAULT_PLANT_RADIUS } from "../farm_designer/plant"; const soil = ( - Type: typeof Path | typeof Shape, + Type: typeof LinePath | typeof Shape, botSize: Record<"x" | "y" | "z" | "thickness", number>, -): Path | Shape => { +): LinePath | Shape => { const { x, y, thickness } = botSize; const hole = new Type(); @@ -41,19 +58,30 @@ const bedStructure2D = ( shape.lineTo(0, 0); // inner edge - shape.holes.push(soil(Path, botSize)); + shape.holes.push(soil(LinePath, botSize)); return shape; }; +export interface AddPlantProps { + gridSize: AxisNumberProperty; + dispatch: Function; + getConfigValue: GetWebAppConfigValue; + plants: TaggedPlant[]; + curves: TaggedCurve[]; + designer: DesignerState; +} + export interface BedProps { config: Config; activeFocus: string; + addPlantProps?: AddPlantProps; } export const Bed = (props: BedProps) => { const { - bedWidthOuter, bedLengthOuter, botSizeZ, bedHeight, bedZOffset, + bedWidthOuter, bedLengthOuter, botSizeZ, bedHeight, + bedXOffset, bedYOffset, bedZOffset, legSize, legsFlush, extraLegsX, extraLegsY, bedBrightness, soilBrightness, soilHeight, ccSupportSize, axes, xyDimensions, } = props.config; @@ -109,9 +137,58 @@ export const Bed = (props: BedProps) => { {children} ; - const Soil = ({ children }: { children: React.ReactElement }) => { + // eslint-disable-next-line no-null/no-null + const pointerPlantRef = React.useRef(null); + + type XY = AxisNumberProperty; + + const getGardenPosition = (e: ThreeEvent): XY => ({ + x: round(threeSpace(e.point.x, -bedLengthOuter) - bedXOffset), + y: round(threeSpace(e.point.y, -bedWidthOuter) - bedYOffset), + }); + + const get3DPosition = (gardenPosition: XY): XY => ({ + x: threeSpace(gardenPosition.x + bedXOffset, bedLengthOuter), + y: threeSpace(gardenPosition.y + bedYOffset, bedWidthOuter), + }); + + const iconSize = + (props.addPlantProps?.designer.cropRadius || DEFAULT_PLANT_RADIUS) * 2; + + interface SoilProps { + children: React.ReactElement; + addPlantProps?: AddPlantProps; + } + + const Soil = ({ children, addPlantProps }: SoilProps) => { const soilDepth = bedHeight + zZero(props.config) - soilHeight; return { + e.stopPropagation(); + if (addPlantProps && getMode() == Mode.clickToAdd) { + dropPlant({ + gardenCoords: getGardenPosition(e), + gridSize: addPlantProps.gridSize, + dispatch: addPlantProps.dispatch, + getConfigValue: addPlantProps.getConfigValue, + plants: addPlantProps.plants, + curves: addPlantProps.curves, + designer: addPlantProps.designer, + }); + } + }} + onPointerMove={e => { + if (addPlantProps + && getMode() == Mode.clickToAdd + && !isMobile() + && pointerPlantRef.current) { + const position = get3DPosition(getGardenPosition(e)); + pointerPlantRef.current.position.set( + position.x, + position.y, + zZero(props.config) - props.config.soilHeight + iconSize / 2); + } + }} castShadow={true} receiveShadow={true} args={[ @@ -130,7 +207,8 @@ export const Bed = (props: BedProps) => { return - + @@ -199,12 +277,22 @@ export const Bed = (props: BedProps) => { ]}> + {getMode() == Mode.clickToAdd && !isMobile() && + + + } - + - + diff --git a/frontend/three_d_garden/bot.tsx b/frontend/three_d_garden/bot.tsx index 4b399c00b2..9e193171aa 100644 --- a/frontend/three_d_garden/bot.tsx +++ b/frontend/three_d_garden/bot.tsx @@ -618,8 +618,7 @@ export const Bot = (props: FarmbotModelProps) => { depth: zAxisLength - 350, bevelEnabled: false, }, - )} - > + )}> @@ -841,8 +840,7 @@ export const Bot = (props: FarmbotModelProps) => { depth: botSizeY - 30, bevelEnabled: false, }, - )} - > + )}> diff --git a/frontend/three_d_garden/constants.ts b/frontend/three_d_garden/constants.ts index 470d4a7802..96889e1921 100644 --- a/frontend/three_d_garden/constants.ts +++ b/frontend/three_d_garden/constants.ts @@ -16,50 +16,6 @@ export const ASSETS: Record> = { concrete: "/3D/textures/concrete.avif", screen: "/3D/textures/screen.avif", }, - icons: { - anaheimPepper: "/3D/icons/anaheim_pepper.avif", - arugula: "/3D/icons/arugula.avif", - basil: "/3D/icons/basil.avif", - beet: "/3D/icons/beet.avif", - bibbLettuce: "/3D/icons/bibb_lettuce.avif", - bokChoy: "/3D/icons/bok_choy.avif", - broccoli: "/3D/icons/broccoli.avif", - brusselsSprout: "/3D/icons/brussels_sprout.avif", - carrot: "/3D/icons/carrot.avif", - cauliflower: "/3D/icons/cauliflower.avif", - celery: "/3D/icons/celery.avif", - chard: "/3D/icons/swiss_chard.avif", - cherryBelleRadish: "/3D/icons/cherry_belle_radish.avif", - cilantro: "/3D/icons/cilantro.avif", - collardGreens: "/3D/icons/collard_greens.avif", - cucumber: "/3D/icons/cucumber.avif", - eggplant: "/3D/icons/eggplant.avif", - frenchBreakfastRadish: "/3D/icons/french_breakfast_radish.avif", - garlic: "/3D/icons/garlic.avif", - goldenBeet: "/3D/icons/golden_beet.avif", - hillbillyTomato: "/3D/icons/hillbilly_tomato.avif", - icicleRadish: "/3D/icons/icicle_radish.avif", - lacinatoKale: "/3D/icons/lacinato_kale.avif", - leek: "/3D/icons/leek.avif", - napaCabbage: "/3D/icons/napa_cabbage.avif", - okra: "/3D/icons/okra.avif", - parsnip: "/3D/icons/parsnip.avif", - rainbowChard: "/3D/icons/rainbow_chard.avif", - redBellPepper: "/3D/icons/red_bell_pepper.avif", - redCurlyKale: "/3D/icons/red_curly_kale.avif", - redRussianKale: "/3D/icons/red_russian_kale.avif", - runnerBean: "/3D/icons/runner_bean.avif", - rutabaga: "/3D/icons/rutabaga.avif", - savoyCabbage: "/3D/icons/savoy_cabbage.avif", - shallot: "/3D/icons/shallot.avif", - snapPea: "/3D/icons/snap_pea.avif", - spinach: "/3D/icons/spinach.avif", - sweetPotato: "/3D/icons/sweet_potato.avif", - turmeric: "/3D/icons/turmeric.avif", - turnip: "/3D/icons/turnip.avif", - yellowOnion: "/3D/icons/yellow_onion.avif", - zucchini: "/3D/icons/zucchini.avif", - }, shapes: { track: "/3D/shapes/track.svg", column: "/3D/shapes/column.svg", @@ -111,7 +67,6 @@ export const ASSETS: Record> = { interface Plant { label: string; - icon: string; spread: number; size: number; } @@ -123,253 +78,211 @@ interface Gardens { export const PLANTS: Record = { anaheimPepper: { label: "Anaheim Pepper", - icon: ASSETS.icons.anaheimPepper, spread: 400, size: 150, }, arugula: { label: "Arugula", - icon: ASSETS.icons.arugula, spread: 250, size: 180, }, basil: { label: "Basil", - icon: ASSETS.icons.basil, spread: 250, size: 160, }, beet: { label: "Beet", - icon: ASSETS.icons.beet, spread: 175, size: 150, }, bibbLettuce: { label: "Bibb Lettuce", - icon: ASSETS.icons.bibbLettuce, spread: 250, size: 200, }, bokChoy: { label: "Bok Choy", - icon: ASSETS.icons.bokChoy, spread: 210, size: 160, }, broccoli: { label: "Broccoli", - icon: ASSETS.icons.broccoli, spread: 375, size: 250, }, brusselsSprout: { label: "Brussels Sprout", - icon: ASSETS.icons.brusselsSprout, spread: 300, size: 250, }, carrot: { label: "Carrot", - icon: ASSETS.icons.carrot, spread: 150, size: 125, }, cauliflower: { label: "Cauliflower", - icon: ASSETS.icons.cauliflower, spread: 400, size: 250, }, celery: { label: "Celery", - icon: ASSETS.icons.celery, spread: 350, size: 200, }, chard: { label: "Swiss Chard", - icon: ASSETS.icons.chard, spread: 300, size: 300, }, cherryBelleRadish: { - label: "Cherry Belle Radish", - icon: ASSETS.icons.cherryBelleRadish, + label: "Cherry Bell Radish", spread: 100, size: 100, }, cilantro: { label: "Cilantro", - icon: ASSETS.icons.cilantro, spread: 180, size: 150, }, collardGreens: { label: "Collard Greens", - icon: ASSETS.icons.collardGreens, spread: 230, size: 230, }, cucumber: { label: "Cucumber", - icon: ASSETS.icons.cucumber, spread: 400, size: 200, }, eggplant: { label: "Eggplant", - icon: ASSETS.icons.eggplant, spread: 400, size: 200, }, frenchBreakfastRadish: { label: "French Breakfast Radish", - icon: ASSETS.icons.frenchBreakfastRadish, spread: 100, size: 100, }, garlic: { label: "Garlic", - icon: ASSETS.icons.garlic, spread: 175, size: 100, }, goldenBeet: { label: "Golden Beet", - icon: ASSETS.icons.goldenBeet, spread: 175, size: 150, }, hillbillyTomato: { label: "Hillbilly Tomato", - icon: ASSETS.icons.hillbillyTomato, spread: 400, size: 200, }, icicleRadish: { label: "Icicle Radish", - icon: ASSETS.icons.icicleRadish, spread: 100, size: 100, }, - lacinatoKale: { - label: "Lacinato Kale", - icon: ASSETS.icons.lacinatoKale, + laciantoKale: { + label: "Lacianto Kale", spread: 250, size: 220, }, leek: { label: "Leek", - icon: ASSETS.icons.leek, spread: 200, size: 200, }, napaCabbage: { label: "Napa Cabbage", - icon: ASSETS.icons.napaCabbage, spread: 400, size: 220, }, okra: { label: "Okra", - icon: ASSETS.icons.okra, spread: 400, size: 200, }, parsnip: { label: "Parsnip", - icon: ASSETS.icons.parsnip, spread: 180, size: 120, }, rainbowChard: { label: "Rainbow Chard", - icon: ASSETS.icons.rainbowChard, spread: 250, size: 250, }, redBellPepper: { label: "Red Bell Pepper", - icon: ASSETS.icons.redBellPepper, spread: 350, size: 200, }, redCurlyKale: { label: "Red Curly Kale", - icon: ASSETS.icons.redCurlyKale, spread: 350, size: 220, }, redRussianKale: { label: "Red Russian Kale", - icon: ASSETS.icons.redRussianKale, spread: 250, size: 200, }, runnerBean: { label: "Runner Bean", - icon: ASSETS.icons.runnerBean, spread: 350, size: 200, }, rutabaga: { label: "Rutabaga", - icon: ASSETS.icons.rutabaga, spread: 200, size: 150, }, savoyCabbage: { label: "Savoy Cabbage", - icon: ASSETS.icons.savoyCabbage, spread: 400, size: 250, }, shallot: { label: "Shallot", - icon: ASSETS.icons.shallot, spread: 200, size: 140, }, snapPea: { label: "Snap Pea", - icon: ASSETS.icons.snapPea, spread: 200, size: 150, }, spinach: { label: "Spinach", - icon: ASSETS.icons.spinach, spread: 250, size: 200, }, sweetPotato: { label: "Sweet Potato", - icon: ASSETS.icons.sweetPotato, spread: 400, size: 180, }, turmeric: { label: "Turmeric", - icon: ASSETS.icons.turmeric, spread: 250, size: 150, }, turnip: { label: "Turnip", - icon: ASSETS.icons.turnip, spread: 175, size: 150, }, yellowOnion: { label: "Yellow Onion", - icon: ASSETS.icons.yellowOnion, spread: 200, size: 150, }, zucchini: { label: "Zucchini", - icon: ASSETS.icons.zucchini, spread: 400, size: 250, }, @@ -386,7 +299,7 @@ export const GARDENS: Gardens = { ], "Fall": [ "arugula", "cherryBelleRadish", "cilantro", "collardGreens", "garlic", - "goldenBeet", "leek", "lacinatoKale", "turnip", "yellowOnion", + "goldenBeet", "leek", "laciantoKale", "turnip", "yellowOnion", ], "Winter": [ "frenchBreakfastRadish", "napaCabbage", "parsnip", "redCurlyKale", diff --git a/frontend/three_d_garden/garden.tsx b/frontend/three_d_garden/garden.tsx index 40d2114e1b..aaccede1b6 100644 --- a/frontend/three_d_garden/garden.tsx +++ b/frontend/three_d_garden/garden.tsx @@ -3,23 +3,18 @@ import { ThreeEvent } from "@react-three/fiber"; import { GizmoHelper, GizmoViewcube, OrbitControls, PerspectiveCamera, - Circle, Stats, Billboard, Image, Clouds, Cloud, OrthographicCamera, + Circle, Stats, Image, Clouds, Cloud, OrthographicCamera, Detailed, Sphere, useTexture, Line, } from "@react-three/drei"; -import { RepeatWrapping, Vector3, BackSide } from "three"; +import { RepeatWrapping, BackSide } from "three"; import { Bot } from "./bot"; -import { Bed } from "./bed"; -import { - threeSpace, - zZero as zZeroFunc, - zero as zeroFunc, - extents as extentsFunc, -} from "./helpers"; +import { AddPlantProps, Bed } from "./bed"; +import { zero as zeroFunc, extents as extentsFunc } from "./helpers"; import { Sky } from "./sky"; import { Config, detailLevels, seasonProperties } from "./config"; -import { ASSETS, GARDENS, PLANTS } from "./constants"; +import { ASSETS } from "./constants"; import { useSpring, animated } from "@react-spring/three"; import { Solar } from "./solar"; import { Sun, sunPosition } from "./sun"; @@ -30,8 +25,11 @@ import { AmbientLight, AxesHelper, Group, MeshBasicMaterial, MeshPhongMaterial, } from "./components"; import { isDesktop } from "../screen_size"; -import { Text } from "./text"; import { isUndefined, range } from "lodash"; +import { + calculatePlantPositions, convertPlants, ThreeDPlant, +} from "./plants"; +import { ICON_URLS } from "../crops/constants"; const AnimatedGroup = animated(Group); @@ -39,69 +37,18 @@ export interface GardenModelProps { config: Config; activeFocus: string; setActiveFocus(focus: string): void; - plants?: Plant[]; -} - -interface Plant { - label: string; - icon: string; - size: number; - spread: number; - x: number; - y: number; + addPlantProps?: AddPlantProps; } -export interface ThreeDGardenPlant extends Plant { } - +// eslint-disable-next-line complexity export const GardenModel = (props: GardenModelProps) => { const { config } = props; const groundZ = config.bedZOffset + config.bedHeight; const Camera = config.perspective ? PerspectiveCamera : OrthographicCamera; - const gardenPlants = GARDENS[config.plants] || []; - const calculatePlantPositions = (): Plant[] => { - const positions: Plant[] = []; - const startX = 350; - let nextX = startX; - let index = 0; - while (nextX <= config.bedLengthOuter - 100) { - const plantKey = gardenPlants[index]; - const plant = PLANTS[plantKey]; - if (!plant) { return []; } - positions.push({ - ...plant, - x: nextX, - y: config.bedWidthOuter / 2, - }); - const plantsPerHalfRow = - Math.ceil((config.bedWidthOuter - plant.spread) / 2 / plant.spread); - for (let i = 1; i < plantsPerHalfRow; i++) { - positions.push({ - ...plant, - x: nextX, - y: config.bedWidthOuter / 2 + plant.spread * i, - }); - positions.push({ - ...plant, - x: nextX, - y: config.bedWidthOuter / 2 - plant.spread * i, - }); - } - if (index + 1 < gardenPlants.length) { - const nextPlant = PLANTS[gardenPlants[index + 1]]; - nextX += (plant.spread / 2) + (nextPlant.spread / 2); - index++; - } else { - index = 0; - const nextPlant = PLANTS[gardenPlants[0]]; - nextX += (plant.spread / 2) + (nextPlant.spread / 2); - } - } - return positions; - }; - const plants = isUndefined(props.plants) - ? calculatePlantPositions() - : props.plants; + const plants = isUndefined(props.addPlantProps) + ? calculatePlantPositions(config) + : convertPlants(config, props.addPlantProps.plants); const [hoveredPlant, setHoveredPlant] = React.useState(undefined); @@ -118,35 +65,6 @@ export const GardenModel = (props: GardenModelProps) => { : undefined; }; - interface PlantProps { - plant: Plant; - i: number; - labelOnly?: boolean; - } - - const Plant = (props: PlantProps) => { - const { i, plant, labelOnly } = props; - const alwaysShowLabels = config.labels && !config.labelsOnHover; - return - {labelOnly - ? - {plant.label} - - : } - ; - }; const isXL = config.sizePreset == "Genesis XL"; const { scale } = useSpring({ scale: isXL ? 1.75 : 1, @@ -181,7 +99,7 @@ export const GardenModel = (props: GardenModelProps) => { const camera = getCamera(config, props.activeFocus, initCamera); const zero = zeroFunc(config); - const gridZ = zero.z - config.soilHeight; + const gridZ = zero.z - config.soilHeight + 5; const extents = extentsFunc(config); // eslint-disable-next-line no-null/no-null @@ -250,15 +168,21 @@ export const GardenModel = (props: GardenModelProps) => { .cloudOpacity} fade={5000} /> - + - {Object.values(PLANTS).map((plant, i) => - )} + {ICON_URLS.map((url, i) => )} {plants.map((plant, i) => - )} + )} {range(0, config.botSizeX + 100, 100).map(x => @@ -282,7 +206,10 @@ export const GardenModel = (props: GardenModelProps) => { onPointerMove={setHover(true)} onPointerLeave={setHover(false)}> {plants.map((plant, i) => - )} + )} diff --git a/frontend/three_d_garden/index.tsx b/frontend/three_d_garden/index.tsx index d19150b03b..8c5e90b503 100644 --- a/frontend/three_d_garden/index.tsx +++ b/frontend/three_d_garden/index.tsx @@ -1,21 +1,24 @@ import { Canvas } from "@react-three/fiber"; import React from "react"; import { Config } from "./config"; -import { GardenModel, ThreeDGardenPlant } from "./garden"; +import { GardenModel } from "./garden"; import { noop } from "lodash"; +import { AddPlantProps } from "./bed"; export interface ThreeDGardenProps { config: Config; - plants: ThreeDGardenPlant[]; + addPlantProps: AddPlantProps; } export const ThreeDGarden = (props: ThreeDGardenProps) => { return
        - +
        ; diff --git a/frontend/three_d_garden/plants.tsx b/frontend/three_d_garden/plants.tsx new file mode 100644 index 0000000000..d251917c50 --- /dev/null +++ b/frontend/three_d_garden/plants.tsx @@ -0,0 +1,112 @@ +import { TaggedPlant } from "../farm_designer/map/interfaces"; +import { Config } from "./config"; +import { GARDENS, PLANTS } from "./constants"; +import { Billboard, Image } from "@react-three/drei"; +import React from "react"; +import { Vector3 } from "three"; +import { threeSpace, zZero as zZeroFunc } from "./helpers"; +import { Text } from "./text"; +import { findIcon } from "../crops/find"; +import { kebabCase } from "lodash"; + +interface Plant { + label: string; + icon: string; + size: number; + spread: number; + x: number; + y: number; +} + +export interface ThreeDGardenPlant extends Plant { } + +export const convertPlants = (config: Config, plants: TaggedPlant[]): Plant[] => { + return plants.map(plant => { + return { + label: plant.body.name, + icon: findIcon(plant.body.openfarm_slug), + size: plant.body.radius * 2, + spread: 0, + x: plant.body.x + config.bedXOffset, + y: plant.body.y + config.bedYOffset, + }; + }); +}; + +export const calculatePlantPositions = (config: Config): Plant[] => { + const gardenPlants = GARDENS[config.plants] || []; + const positions: Plant[] = []; + const startX = 350; + let nextX = startX; + let index = 0; + while (nextX <= config.bedLengthOuter - 100) { + const plantKey = gardenPlants[index]; + const plant = PLANTS[plantKey]; + if (!plant) { return []; } + const icon = findIcon(kebabCase(plant.label)); + positions.push({ + ...plant, + icon, + x: nextX, + y: config.bedWidthOuter / 2, + }); + const plantsPerHalfRow = + Math.ceil((config.bedWidthOuter - plant.spread) / 2 / plant.spread); + for (let i = 1; i < plantsPerHalfRow; i++) { + positions.push({ + ...plant, + icon, + x: nextX, + y: config.bedWidthOuter / 2 + plant.spread * i, + }); + positions.push({ + ...plant, + icon, + x: nextX, + y: config.bedWidthOuter / 2 - plant.spread * i, + }); + } + if (index + 1 < gardenPlants.length) { + const nextPlant = PLANTS[gardenPlants[index + 1]]; + nextX += (plant.spread / 2) + (nextPlant.spread / 2); + index++; + } else { + index = 0; + const nextPlant = PLANTS[gardenPlants[0]]; + nextX += (plant.spread / 2) + (nextPlant.spread / 2); + } + } + return positions; +}; + +export interface ThreeDPlantProps { + plant: Plant; + i: number; + labelOnly?: boolean; + config: Config; + hoveredPlant: number | undefined; +} + +export const ThreeDPlant = (props: ThreeDPlantProps) => { + const { i, plant, labelOnly, config, hoveredPlant } = props; + const alwaysShowLabels = config.labels && !config.labelsOnHover; + return + {labelOnly + ? + {plant.label} + + : } + ; +}; diff --git a/frontend/weeds/weed_inventory_item.tsx b/frontend/weeds/weed_inventory_item.tsx index 858ef3492f..d3da001ae9 100644 --- a/frontend/weeds/weed_inventory_item.tsx +++ b/frontend/weeds/weed_inventory_item.tsx @@ -3,8 +3,7 @@ import { TaggedWeedPointer } from "farmbot"; import { Actions } from "../constants"; import { useNavigate } from "react-router"; import { t } from "../i18next_wrapper"; -import { svgToUrl } from "../open_farm/icons"; -import { genericWeedIcon } from "../point_groups/point_group_item"; +import { genericWeedIcon, svgToUrl } from "../point_groups/point_group_item"; import { getMode } from "../farm_designer/map/util"; import { Mode } from "../farm_designer/map/interfaces"; import { mapPointClickAction, selectPoint } from "../farm_designer/map/actions"; diff --git a/frontend/weeds/weeds_edit.tsx b/frontend/weeds/weeds_edit.tsx index 40ac055a6b..fda1ded663 100644 --- a/frontend/weeds/weeds_edit.tsx +++ b/frontend/weeds/weeds_edit.tsx @@ -98,7 +98,7 @@ export const RawEditWeed = (props: EditWeedProps) => { {weed - ?
        + ?