Compare commits
5 Commits
Author | SHA1 | Date |
---|---|---|
Rick Carlino | be38ad830a | |
Rick Carlino | 4f03626d5f | |
Rick Carlino | 9f1cf4eedd | |
Rick Carlino | afceaf25e0 | |
Rick Carlino | e5389b747e |
2
Gemfile
2
Gemfile
|
@ -35,7 +35,7 @@ group :development, :test do
|
|||
gem "hashdiff"
|
||||
gem "pry-rails"
|
||||
gem "pry"
|
||||
gem "rspec-rails"
|
||||
gem "rspec-rails", "4.0.0.beta3"
|
||||
gem "rspec"
|
||||
gem 'rspec_junit_formatter'
|
||||
gem "simplecov"
|
||||
|
|
174
Gemfile.lock
174
Gemfile.lock
|
@ -7,38 +7,38 @@ GIT
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (6.0.2.2)
|
||||
actionpack (= 6.0.2.2)
|
||||
actioncable (6.0.2.1)
|
||||
actionpack (= 6.0.2.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.0.2.2)
|
||||
actionpack (= 6.0.2.2)
|
||||
activejob (= 6.0.2.2)
|
||||
activerecord (= 6.0.2.2)
|
||||
activestorage (= 6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
actionmailbox (6.0.2.1)
|
||||
actionpack (= 6.0.2.1)
|
||||
activejob (= 6.0.2.1)
|
||||
activerecord (= 6.0.2.1)
|
||||
activestorage (= 6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.0.2.2)
|
||||
actionpack (= 6.0.2.2)
|
||||
actionview (= 6.0.2.2)
|
||||
activejob (= 6.0.2.2)
|
||||
actionmailer (6.0.2.1)
|
||||
actionpack (= 6.0.2.1)
|
||||
actionview (= 6.0.2.1)
|
||||
activejob (= 6.0.2.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.0.2.2)
|
||||
actionview (= 6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
actionpack (6.0.2.1)
|
||||
actionview (= 6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
rack (~> 2.0, >= 2.0.8)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.0.2.2)
|
||||
actionpack (= 6.0.2.2)
|
||||
activerecord (= 6.0.2.2)
|
||||
activestorage (= 6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
actiontext (6.0.2.1)
|
||||
actionpack (= 6.0.2.1)
|
||||
activerecord (= 6.0.2.1)
|
||||
activestorage (= 6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
actionview (6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
|
@ -48,20 +48,20 @@ GEM
|
|||
activemodel (>= 4.1, < 6.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
activejob (6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
activerecord (6.0.2.2)
|
||||
activemodel (= 6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
activestorage (6.0.2.2)
|
||||
actionpack (= 6.0.2.2)
|
||||
activejob (= 6.0.2.2)
|
||||
activerecord (= 6.0.2.2)
|
||||
activemodel (6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
activerecord (6.0.2.1)
|
||||
activemodel (= 6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
activestorage (6.0.2.1)
|
||||
actionpack (= 6.0.2.1)
|
||||
activejob (= 6.0.2.1)
|
||||
activerecord (= 6.0.2.1)
|
||||
marcel (~> 0.3.1)
|
||||
activesupport (6.0.2.2)
|
||||
activesupport (6.0.2.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
|
@ -69,11 +69,11 @@ GEM
|
|||
zeitwerk (~> 2.2)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
amq-protocol (2.3.1)
|
||||
amq-protocol (2.3.0)
|
||||
bcrypt (3.1.13)
|
||||
builder (3.2.4)
|
||||
bunny (2.15.0)
|
||||
amq-protocol (~> 2.3, >= 2.3.1)
|
||||
bunny (2.14.4)
|
||||
amq-protocol (~> 2.3, >= 2.3.0)
|
||||
case_transform (0.2)
|
||||
activesupport
|
||||
climate_control (0.2.0)
|
||||
|
@ -99,27 +99,27 @@ GEM
|
|||
responders
|
||||
warden (~> 1.2.3)
|
||||
diff-lcs (1.3)
|
||||
digest-crc (0.5.1)
|
||||
digest-crc (0.4.1)
|
||||
discard (1.2.0)
|
||||
activerecord (>= 4.2, < 7)
|
||||
docile (1.3.2)
|
||||
erubi (1.9.0)
|
||||
factory_bot (5.1.2)
|
||||
factory_bot (5.1.1)
|
||||
activesupport (>= 4.2.0)
|
||||
factory_bot_rails (5.1.1)
|
||||
factory_bot (~> 5.1.0)
|
||||
railties (>= 4.2.0)
|
||||
faker (2.11.0)
|
||||
faker (2.10.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
faraday (0.17.3)
|
||||
faraday (0.15.4)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday_middleware (0.14.0)
|
||||
faraday_middleware (0.13.1)
|
||||
faraday (>= 0.7.4, < 1.0)
|
||||
font-awesome-rails (4.7.0.5)
|
||||
railties (>= 3.2, < 6.1)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
google-api-client (0.37.2)
|
||||
google-api-client (0.37.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 0.9)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
|
@ -130,8 +130,8 @@ GEM
|
|||
google-cloud-core (1.5.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.3.1)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-env (1.3.0)
|
||||
faraday (~> 0.11)
|
||||
google-cloud-errors (1.0.0)
|
||||
google-cloud-storage (1.25.1)
|
||||
addressable (~> 2.5)
|
||||
|
@ -140,14 +140,14 @@ GEM
|
|||
google-cloud-core (~> 1.2)
|
||||
googleauth (~> 0.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (0.11.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
googleauth (0.10.0)
|
||||
faraday (~> 0.12)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.12)
|
||||
hashdiff (1.0.1)
|
||||
hashdiff (1.0.0)
|
||||
hashie (3.6.0)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.8.2)
|
||||
|
@ -167,7 +167,7 @@ GEM
|
|||
activerecord
|
||||
kaminari-core (= 1.2.0)
|
||||
kaminari-core (1.2.0)
|
||||
loofah (2.5.0)
|
||||
loofah (2.4.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
|
@ -175,33 +175,33 @@ GEM
|
|||
marcel (0.3.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
memoist (0.16.2)
|
||||
method_source (1.0.0)
|
||||
method_source (0.9.2)
|
||||
mimemagic (0.3.4)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.14.0)
|
||||
multi_json (1.13.1)
|
||||
multipart-post (2.1.1)
|
||||
mutations (0.9.1)
|
||||
mutations (0.9.0)
|
||||
activesupport
|
||||
nio4r (2.5.2)
|
||||
nokogiri (1.10.9)
|
||||
nokogiri (1.10.8)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
orm_adapter (0.5.0)
|
||||
os (1.0.1)
|
||||
passenger (6.0.4)
|
||||
rack
|
||||
rake (>= 0.8.1)
|
||||
pg (1.2.3)
|
||||
pry (0.13.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
pg (1.2.2)
|
||||
pry (0.12.2)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (4.0.3)
|
||||
rabbitmq_http_api_client (1.13.0)
|
||||
faraday (>= 0.15, < 1)
|
||||
faraday_middleware (>= 0.13.0, < 1)
|
||||
rabbitmq_http_api_client (1.12.0)
|
||||
faraday (~> 0.15.4)
|
||||
faraday_middleware (~> 0.13.0)
|
||||
hashie (~> 3.6)
|
||||
multi_json (~> 1.13.1)
|
||||
rack (2.2.2)
|
||||
|
@ -211,20 +211,20 @@ GEM
|
|||
rack (>= 2.0.0)
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (6.0.2.2)
|
||||
actioncable (= 6.0.2.2)
|
||||
actionmailbox (= 6.0.2.2)
|
||||
actionmailer (= 6.0.2.2)
|
||||
actionpack (= 6.0.2.2)
|
||||
actiontext (= 6.0.2.2)
|
||||
actionview (= 6.0.2.2)
|
||||
activejob (= 6.0.2.2)
|
||||
activemodel (= 6.0.2.2)
|
||||
activerecord (= 6.0.2.2)
|
||||
activestorage (= 6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
rails (6.0.2.1)
|
||||
actioncable (= 6.0.2.1)
|
||||
actionmailbox (= 6.0.2.1)
|
||||
actionmailer (= 6.0.2.1)
|
||||
actionpack (= 6.0.2.1)
|
||||
actiontext (= 6.0.2.1)
|
||||
actionview (= 6.0.2.1)
|
||||
activejob (= 6.0.2.1)
|
||||
activemodel (= 6.0.2.1)
|
||||
activerecord (= 6.0.2.1)
|
||||
activestorage (= 6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 6.0.2.2)
|
||||
railties (= 6.0.2.1)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
|
@ -236,9 +236,9 @@ GEM
|
|||
rails_stdout_logging
|
||||
rails_serve_static_assets (0.0.5)
|
||||
rails_stdout_logging (0.0.5)
|
||||
railties (6.0.2.2)
|
||||
actionpack (= 6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
railties (6.0.2.1)
|
||||
actionpack (= 6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.20.3, < 2.0)
|
||||
|
@ -261,20 +261,20 @@ GEM
|
|||
rspec-mocks (~> 3.9.0)
|
||||
rspec-core (3.9.1)
|
||||
rspec-support (~> 3.9.1)
|
||||
rspec-expectations (3.9.1)
|
||||
rspec-expectations (3.9.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-mocks (3.9.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-rails (4.0.0)
|
||||
rspec-rails (4.0.0.beta3)
|
||||
actionpack (>= 4.2)
|
||||
activesupport (>= 4.2)
|
||||
railties (>= 4.2)
|
||||
rspec-core (~> 3.9)
|
||||
rspec-expectations (~> 3.9)
|
||||
rspec-mocks (~> 3.9)
|
||||
rspec-support (~> 3.9)
|
||||
rspec-core (~> 3.8)
|
||||
rspec-expectations (~> 3.8)
|
||||
rspec-mocks (~> 3.8)
|
||||
rspec-support (~> 3.8)
|
||||
rspec-support (3.9.2)
|
||||
rspec_junit_formatter (0.4.1)
|
||||
rspec-core (>= 2, < 4, != 2.12.0)
|
||||
|
@ -282,15 +282,15 @@ GEM
|
|||
activerecord (>= 4.0.0)
|
||||
railties (>= 4.0.0)
|
||||
secure_headers (6.3.0)
|
||||
signet (0.13.0)
|
||||
signet (0.12.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
faraday (~> 0.9)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simplecov (0.18.5)
|
||||
docile (~> 1.1)
|
||||
simplecov-html (~> 0.11)
|
||||
simplecov-html (0.12.2)
|
||||
simplecov-html (0.12.1)
|
||||
sprockets (4.0.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
|
@ -300,7 +300,7 @@ GEM
|
|||
sprockets (>= 3.0.0)
|
||||
thor (1.0.1)
|
||||
thread_safe (0.3.6)
|
||||
tzinfo (1.2.7)
|
||||
tzinfo (1.2.6)
|
||||
thread_safe (~> 0.1)
|
||||
uber (0.1.0)
|
||||
url (0.3.2)
|
||||
|
@ -312,7 +312,7 @@ GEM
|
|||
websocket-driver (0.7.1)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.4)
|
||||
zeitwerk (2.3.0)
|
||||
zeitwerk (2.2.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
@ -348,7 +348,7 @@ DEPENDENCIES
|
|||
request_store
|
||||
rollbar
|
||||
rspec
|
||||
rspec-rails
|
||||
rspec-rails (= 4.0.0.beta3)
|
||||
rspec_junit_formatter
|
||||
scenic
|
||||
secure_headers
|
||||
|
|
|
@ -46,6 +46,5 @@ module Resources
|
|||
Plant => Points,
|
||||
Point => Points,
|
||||
ToolSlot => Points,
|
||||
Weed => Points,
|
||||
}
|
||||
end # Resources
|
||||
|
|
|
@ -30,19 +30,19 @@ module CeleryScriptSettingsBag
|
|||
ALLOWED_PACKAGES = %w(farmbot_os arduino_firmware)
|
||||
ALLOWED_PIN_MODES = [DIGITAL = 0, ANALOG = 1]
|
||||
ALLOWED_PIN_TYPES = PIN_TYPE_MAP.keys
|
||||
ALLOWED_POINTER_TYPE = %w(GenericPointer ToolSlot Plant Weed)
|
||||
ALLOWED_RESOURCE_TYPE = %w(Device Point Plant ToolSlot Weed GenericPointer)
|
||||
ALLOWED_POINTER_TYPE = %w(GenericPointer ToolSlot Plant)
|
||||
ALLOWED_RESOURCE_TYPE = %w(Device Point Plant ToolSlot GenericPointer)
|
||||
ALLOWED_RPC_NODES = %w(assertion calibrate change_ownership
|
||||
check_updates emergency_lock
|
||||
check_updates dump_info emergency_lock
|
||||
emergency_unlock execute execute_script
|
||||
factory_reset find_home flash_firmware home
|
||||
install_farmware install_first_party_farmware _if
|
||||
move_absolute move_relative power_off read_pin
|
||||
read_status reboot remove_farmware update_resource
|
||||
read_status reboot remove_farmware resource_update
|
||||
send_message set_servo_angle set_user_env sync
|
||||
take_photo toggle_pin update_farmware wait
|
||||
write_pin zero)
|
||||
ALLOWED_SPEC_ACTION = %w(emergency_lock emergency_unlock power_off
|
||||
ALLOWED_SPEC_ACTION = %w(dump_info emergency_lock emergency_unlock power_off
|
||||
read_status reboot sync take_photo)
|
||||
ANY_VARIABLE = %i(tool coordinate point identifier)
|
||||
BAD_ALLOWED_PIN_MODES = '"%s" is not a valid pin_mode. Allowed values: %s'
|
||||
|
@ -73,7 +73,8 @@ module CeleryScriptSettingsBag
|
|||
ONLY_ONE_COORD = "Move Absolute does not accept a group of locations " \
|
||||
"as input. Please change your selection to a single" \
|
||||
" location."
|
||||
PLANT_STAGES = %w(planned planted harvested sprouted removed)
|
||||
PLANT_STAGES = %w(planned planted harvested sprouted)
|
||||
RESOURCE_UPDATE_ARGS = [:resource_type, :resource_id, :label, :value]
|
||||
SCOPE_DECLARATIONS = [:variable_declaration, :parameter_declaration]
|
||||
MISC_ENUM_ERR = '"%s" is not valid. Allowed values: %s'
|
||||
MAX_WAIT_MS = 1000 * 60 * 3 # Three Minutes
|
||||
|
@ -81,13 +82,6 @@ module CeleryScriptSettingsBag
|
|||
"A single wait node cannot exceed #{MAX_WAIT_MS / 1000 / 60} minutes. " +
|
||||
"Consider lowering the wait time or using multiple WAIT blocks."
|
||||
Corpus = CeleryScript::Corpus.new
|
||||
THIS_IS_DEPRECATED = {
|
||||
args: [:resource_type, :resource_id, :label, :value],
|
||||
tags: [:function, :api_writer, :network_user],
|
||||
blk: ->(n) do
|
||||
n.invalidate!("Deprecated `mark_as` detected. Delete it and re-add")
|
||||
end,
|
||||
}
|
||||
|
||||
CORPUS_VALUES = {
|
||||
boolean: [TrueClass, FalseClass],
|
||||
|
@ -257,7 +251,9 @@ module CeleryScriptSettingsBag
|
|||
# outside of the API. If `package` _was_ declared as a native enum (rather
|
||||
# than a string), it would cause false type errors in FE/FBJS.
|
||||
blk: ->(node) do
|
||||
manual_enum(ALLOWED_PACKAGES, node, BAD_PACKAGE)
|
||||
unless node.parent.kind.to_s == "flash_firmware"
|
||||
manual_enum(ALLOWED_PACKAGES, node, BAD_PACKAGE)
|
||||
end
|
||||
end,
|
||||
},
|
||||
axis: {
|
||||
|
@ -284,9 +280,6 @@ module CeleryScriptSettingsBag
|
|||
lua: {
|
||||
defn: [v(:string)],
|
||||
},
|
||||
resource: {
|
||||
defn: [n(:identifier), n(:resource)],
|
||||
},
|
||||
}.map do |(name, conf)|
|
||||
blk = conf[:blk]
|
||||
defn = conf.fetch(:defn)
|
||||
|
@ -326,6 +319,10 @@ module CeleryScriptSettingsBag
|
|||
args: [:x, :y, :z],
|
||||
tags: [:data, :location_like],
|
||||
},
|
||||
dump_info: {
|
||||
tags: [:function, :network_user, :disk_user, :api_writer],
|
||||
docs: "Sends an info dump to server administrators for troubleshooting.",
|
||||
},
|
||||
emergency_lock: {
|
||||
tags: [:function, :firmware_user, :control_flow],
|
||||
},
|
||||
|
@ -518,22 +515,15 @@ module CeleryScriptSettingsBag
|
|||
tags: [:function, :firmware_user, :rpi_user],
|
||||
blk: ->(n) { no_rpi_analog(n) },
|
||||
},
|
||||
# DEPRECATED- Get rid of this node ASAP -RC 15 APR 2020
|
||||
resource_update: THIS_IS_DEPRECATED,
|
||||
resource: {
|
||||
args: [:resource_type, :resource_id],
|
||||
tags: [:network_user],
|
||||
resource_update: {
|
||||
args: RESOURCE_UPDATE_ARGS,
|
||||
tags: [:function, :api_writer, :network_user],
|
||||
blk: ->(n) do
|
||||
resource_type = n.args.fetch(:resource_type).value
|
||||
resource_id = n.args.fetch(:resource_id).value
|
||||
check_resource_type(n, resource_type, resource_id, Device.current)
|
||||
end,
|
||||
},
|
||||
update_resource: {
|
||||
args: [:resource],
|
||||
body: [:pair],
|
||||
tags: [:function, :api_writer, :network_user],
|
||||
},
|
||||
point_group: {
|
||||
args: [:point_group_id],
|
||||
tags: [:data, :list_like],
|
||||
|
|
|
@ -8,7 +8,6 @@ class InUsePoint < ApplicationRecord
|
|||
GenericPointer.name => DEFAULT_NAME,
|
||||
ToolSlot.name => "slot",
|
||||
Plant.name => "plant",
|
||||
Weed.name => "weed"
|
||||
}
|
||||
|
||||
def readonly?
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
class PinBinding < ApplicationRecord
|
||||
OFF_LIMITS = [
|
||||
2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 19, 21, 23, 24, 25, 27,
|
||||
OFF_LIMITS = [
|
||||
2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 19, 21, 23, 24, 25, 27
|
||||
]
|
||||
BAD_PIN_NUM = "The following pin numbers cannot be used: %s" % OFF_LIMITS.join(", ")
|
||||
BAD_PIN_NUM = \
|
||||
"The following pin numbers cannot be used: %s" % OFF_LIMITS.join(", ")
|
||||
|
||||
belongs_to :device
|
||||
belongs_to :sequence
|
||||
enum special_action: { emergency_lock: "emergency_lock",
|
||||
enum special_action: { dump_info: "dump_info",
|
||||
emergency_lock: "emergency_lock",
|
||||
emergency_unlock: "emergency_unlock",
|
||||
power_off: "power_off",
|
||||
read_status: "read_status",
|
||||
reboot: "reboot",
|
||||
sync: "sync",
|
||||
take_photo: "take_photo" }
|
||||
power_off: "power_off",
|
||||
read_status: "read_status",
|
||||
reboot: "reboot",
|
||||
sync: "sync",
|
||||
take_photo: "take_photo" }
|
||||
validates :pin_num, uniqueness: { scope: :device }
|
||||
|
||||
def fancy_name
|
||||
|
|
|
@ -4,7 +4,7 @@ class Point < ApplicationRecord
|
|||
# axis value > 21k right now - RC
|
||||
# Using real constants instead of strings results
|
||||
# in circular dep. errors.
|
||||
POINTER_KINDS = ["GenericPointer", "Plant", "ToolSlot", "Weed"]
|
||||
POINTER_KINDS = ["GenericPointer", "Plant", "ToolSlot"]
|
||||
self.inheritance_column = "pointer_type"
|
||||
|
||||
belongs_to :device
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
class Weed < Point
|
||||
end
|
|
@ -27,19 +27,11 @@ module Points
|
|||
end
|
||||
|
||||
def execute
|
||||
Point.transaction { point.update!(update_params) && point }
|
||||
Point.transaction { point.update!(inputs.except(:point)) && point }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def merged_meta_fields
|
||||
@merged_meta_fields ||= (point.meta || {}).merge(meta || {})
|
||||
end
|
||||
|
||||
def update_params
|
||||
@update_params ||= inputs.except(:point).merge(meta: merged_meta_fields)
|
||||
end
|
||||
|
||||
def new_tool_id?
|
||||
raw_inputs.key?("tool_id")
|
||||
end
|
||||
|
|
|
@ -1,24 +1,6 @@
|
|||
class BasePointSerializer < ApplicationSerializer
|
||||
attributes :device_id, :name, :pointer_type, :meta, :x, :y, :z
|
||||
|
||||
# PROBLEM:
|
||||
# * Users need a mutable way to mark a plant's creation time => `planted_at`
|
||||
# * DB Admin needs to know the _real_ created_at time.
|
||||
# * We can't change field names (or destroy data) that is in use by legacy devices
|
||||
#
|
||||
# SOLUTION:
|
||||
# * Don't allow users to modify `created_at`
|
||||
# * Provide `planted_at` if possible.
|
||||
# * Always provide `planted_at` if it is available
|
||||
# * Provide a read-only view of `created_at` if `planted_at` is `nil`
|
||||
def planted_at
|
||||
object.planted_at || object.created_at
|
||||
end
|
||||
|
||||
def created_at
|
||||
planted_at
|
||||
end
|
||||
|
||||
def meta
|
||||
object.meta || {}
|
||||
end
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
class WeedSerializer < BasePointSerializer
|
||||
attributes :radius, :discarded_at, :plant_stage
|
||||
end
|
|
@ -1,4 +1,5 @@
|
|||
class MakeDefaulDeviceNameFarmbot < ActiveRecord::Migration[5.1]
|
||||
|
||||
def change
|
||||
change_column_default(:devices, :name, "Farmbot")
|
||||
end
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
class AddShowWeedsToWebAppConfig < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :web_app_configs,
|
||||
:show_weeds,
|
||||
:boolean,
|
||||
default: false
|
||||
end
|
||||
end
|
|
@ -1,5 +0,0 @@
|
|||
class UpdateMaxImageCount < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
change_column_default(:devices, :max_images_count, 450)
|
||||
end
|
||||
end
|
|
@ -150,8 +150,8 @@ ALTER SEQUENCE public.alerts_id_seq OWNED BY public.alerts.id;
|
|||
CREATE TABLE public.ar_internal_metadata (
|
||||
key character varying NOT NULL,
|
||||
value character varying,
|
||||
created_at timestamp(6) without time zone NOT NULL,
|
||||
updated_at timestamp(6) without time zone NOT NULL
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
|
@ -262,7 +262,7 @@ CREATE TABLE public.devices (
|
|||
id integer NOT NULL,
|
||||
name character varying DEFAULT 'FarmBot'::character varying,
|
||||
max_log_count integer DEFAULT 1000,
|
||||
max_images_count integer DEFAULT 450,
|
||||
max_images_count integer DEFAULT 100,
|
||||
timezone character varying(280),
|
||||
last_saw_api timestamp without time zone,
|
||||
last_saw_mq timestamp without time zone,
|
||||
|
@ -1364,7 +1364,7 @@ CREATE VIEW public.resource_update_steps AS
|
|||
edge_nodes.kind,
|
||||
edge_nodes.value
|
||||
FROM public.edge_nodes
|
||||
WHERE (((edge_nodes.kind)::text = 'resource_type'::text) AND ((edge_nodes.value)::text = ANY ((ARRAY['"GenericPointer"'::character varying, '"ToolSlot"'::character varying, '"Plant"'::character varying])::text[])))
|
||||
WHERE (((edge_nodes.kind)::text = 'resource_type'::text) AND ((edge_nodes.value)::text = ANY (ARRAY[('"GenericPointer"'::character varying)::text, ('"ToolSlot"'::character varying)::text, ('"Plant"'::character varying)::text])))
|
||||
), resource_id AS (
|
||||
SELECT edge_nodes.primary_node_id,
|
||||
edge_nodes.kind,
|
||||
|
@ -1644,7 +1644,8 @@ CREATE TABLE public.users (
|
|||
agreed_to_terms_at timestamp without time zone,
|
||||
confirmation_sent_at timestamp without time zone,
|
||||
unconfirmed_email character varying,
|
||||
inactivity_warning_sent_at timestamp without time zone
|
||||
inactivity_warning_sent_at timestamp without time zone,
|
||||
inactivity_warning_count integer
|
||||
);
|
||||
|
||||
|
||||
|
@ -1728,9 +1729,7 @@ CREATE TABLE public.web_app_configs (
|
|||
confirm_sequence_deletion boolean DEFAULT true,
|
||||
discard_unsaved_sequences boolean DEFAULT false,
|
||||
user_interface_read_only_mode boolean DEFAULT false,
|
||||
assertion_log integer DEFAULT 1,
|
||||
show_zones boolean DEFAULT false,
|
||||
show_weeds boolean DEFAULT false
|
||||
assertion_log integer DEFAULT 1
|
||||
);
|
||||
|
||||
|
||||
|
@ -3377,9 +3376,6 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||
('20191219212755'),
|
||||
('20191220010646'),
|
||||
('20200116140201'),
|
||||
('20200204192005'),
|
||||
('20200204230135'),
|
||||
('20200323235926'),
|
||||
('20200412152208');
|
||||
('20200204192005');
|
||||
|
||||
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
# How to install FarmBot Web API on a Debian Buster (10) Machine
|
||||
|
||||
# IMPORTANT NOTE: Resources are limited and Farmbot, inc. cannot provide
|
||||
# longterm support to self-hosted users. If you have never administered a
|
||||
# Ruby on Rails application, we highly advise stopping now. this presents an
|
||||
# extremely high risk of data loss. Free hosting is provided at
|
||||
# https://my.farm.bot and eliminates the risks and troubles of self-hosting.
|
||||
#
|
||||
# You are highly encouraged to use the my.farm.bot servers. Self hosted
|
||||
# documentation is provided with the assumption that you have experience with
|
||||
# Ruby/Javascript development.
|
||||
#
|
||||
# Self-hosting a Farmbot server is not a simple task.
|
||||
|
||||
# Remove old (possibly broke) docker versions
|
||||
sudo apt-get remove docker docker-engine docker.io
|
||||
|
||||
# Install docker
|
||||
sudo apt-get install apt-transport-https ca-certificates curl software-properties-common gnupg2 --yes
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
|
||||
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian buster stable" --yes
|
||||
sudo apt-get update --yes
|
||||
sudo apt-get install docker-ce --yes
|
||||
sudo docker run hello-world # Should run!
|
||||
# Install docker-compose
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/1.22.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
# Install FarmBot Web App
|
||||
# âš SKIP THIS STEP IF UPGRADING!
|
||||
git clone https://github.com/FarmBot/Farmbot-Web-App --depth=5 --branch=master
|
||||
|
||||
cd Farmbot-Web-App
|
||||
|
||||
#snap install micro --classic # Don't like `micro`? vim, nano, etc are fine, too.
|
||||
cp example.env .env # âš SKIP THIS STEP IF UPGRADING!
|
||||
|
||||
# == This is a very important step!!! ==
|
||||
#
|
||||
# Open `.env` in a text editor and change all the values.
|
||||
#
|
||||
# == Nothing will work if you skip this step!!! ==
|
||||
|
||||
vim .env # âš SKIP THIS STEP IF UPGRADING!
|
||||
# ^ This is the most important step
|
||||
# READ NOTE ABOVE. Very important!
|
||||
|
||||
# Install the correct version of bundler for the project
|
||||
sudo docker-compose run web gem install bundler:2.1.4
|
||||
# Install application specific Ruby dependencies
|
||||
sudo docker-compose run web bundle install
|
||||
# Install application specific Javascript deps
|
||||
sudo docker-compose run web npm install
|
||||
# Create a database in PostgreSQL
|
||||
sudo docker-compose run web bundle exec rails db:create db:migrate
|
||||
# Generate a set of *.pem files for data encryption
|
||||
sudo docker-compose run web rake keys:generate # âš SKIP THIS STEP IF UPGRADING!
|
||||
# Build the UI assets via ParcelJS
|
||||
sudo docker-compose run web rake assets:precompile
|
||||
# Run the server! ٩(^‿^)۶
|
||||
# NOTE: DONT TRY TO LOGIN until you see a message similar to this:
|
||||
# "✨ Built in 44.92s"
|
||||
# THIS MAY TAKE A VERY LONG TIME ON SLOW MACHINES (~3 minutes on DigitalOcean)
|
||||
# You will just get an empty screen otherwise.
|
||||
# This only happens during initialization
|
||||
sudo docker-compose up
|
||||
|
||||
# At this point, setup is complete. Content should be visible at ===============
|
||||
# http://YOUR_HOST:3000/.
|
||||
|
||||
# You can optionally verify installation by running unit tests.
|
||||
|
||||
# Create the database for the app to use:
|
||||
sudo docker-compose run -e RAILS_ENV=test web bundle exec rails db:setup
|
||||
# Run the tests in the "test" RAILS_ENV:
|
||||
sudo docker-compose run -e RAILS_ENV=test web rspec spec
|
||||
# Run user-interface unit tests REQUIRES AT LEAST 4 GB OF RAM:
|
||||
sudo docker-compose run web npm run test
|
||||
|
||||
# === BEGIN OPTIONAL UPGRADES
|
||||
# To update to later versions of FarmBot,
|
||||
# shut down the server, create a database backup
|
||||
# and run commands below.
|
||||
git pull https://github.com/FarmBot/Farmbot-Web-App.git master
|
||||
sudo docker-compose build
|
||||
sudo docker-compose run web bundle install # <== âš UPGRADE USERS ONLY
|
||||
sudo docker-compose run web npm install # <== âš UPGRADE USERS ONLY
|
||||
sudo docker-compose run web rails db:migrate # <== âš UPGRADE USERS ONLY
|
||||
# === END OPTIONAL UPGRADES ^
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
import { DesignerState } from "../farm_designer/interfaces";
|
||||
|
||||
export const fakeDesignerState = (): DesignerState => ({
|
||||
selectedPoints: undefined,
|
||||
selectionPointType: undefined,
|
||||
selectedPlants: undefined,
|
||||
hoveredPlant: {
|
||||
plantUUID: undefined,
|
||||
icon: ""
|
||||
|
@ -14,10 +13,8 @@ export const fakeDesignerState = (): DesignerState => ({
|
|||
cropSearchResults: [],
|
||||
cropSearchInProgress: false,
|
||||
chosenLocation: { x: undefined, y: undefined, z: undefined },
|
||||
drawnPoint: undefined,
|
||||
drawnWeed: undefined,
|
||||
currentPoint: undefined,
|
||||
openedSavedGarden: undefined,
|
||||
tryGroupSortType: undefined,
|
||||
editGroupAreaInMap: false,
|
||||
settingsSearchTerm: "",
|
||||
});
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
export const mockDispatch = (innerDispatch = jest.fn()) =>
|
||||
jest.fn(x => typeof x === "function" && x(innerDispatch));
|
|
@ -26,7 +26,6 @@ import {
|
|||
TaggedAlert,
|
||||
TaggedPointGroup,
|
||||
TaggedFolder,
|
||||
TaggedWeedPointer,
|
||||
} from "farmbot";
|
||||
import { fakeResource } from "../fake_resource";
|
||||
import {
|
||||
|
@ -134,6 +133,7 @@ export function fakeToolSlot(): TaggedToolSlotPointer {
|
|||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
radius: 25,
|
||||
pointer_type: "ToolSlot",
|
||||
meta: {},
|
||||
tool_id: undefined,
|
||||
|
@ -171,20 +171,6 @@ export function fakePoint(): TaggedGenericPointer {
|
|||
});
|
||||
}
|
||||
|
||||
export function fakeWeed(): TaggedWeedPointer {
|
||||
return fakeResource("Point", {
|
||||
id: idCounter++,
|
||||
name: "Weed 1",
|
||||
pointer_type: "Weed",
|
||||
x: 200,
|
||||
y: 400,
|
||||
z: 0,
|
||||
radius: 100,
|
||||
plant_stage: "planned",
|
||||
meta: { created_by: "plant-detection", color: "red" }
|
||||
});
|
||||
}
|
||||
|
||||
export function fakeSavedGarden(): TaggedSavedGarden {
|
||||
return fakeResource("SavedGarden", {
|
||||
id: idCounter++,
|
||||
|
@ -303,7 +289,6 @@ export function fakeWebAppConfig(): TaggedWebAppConfig {
|
|||
show_sensor_readings: false,
|
||||
show_plants: true,
|
||||
show_points: true,
|
||||
show_weeds: true,
|
||||
x_axis_inverted: false,
|
||||
y_axis_inverted: false,
|
||||
z_axis_inverted: true,
|
||||
|
|
|
@ -7,5 +7,4 @@ jest.mock("../toast/toast", () => ({
|
|||
error: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
busy: jest.fn(),
|
||||
removeToast: jest.fn(),
|
||||
}));
|
||||
|
|
|
@ -265,6 +265,7 @@ const tr11: TaggedPoint = {
|
|||
"pointer_type": "ToolSlot",
|
||||
"pullout_direction": 0,
|
||||
"gantry_mounted": false,
|
||||
"radius": 25,
|
||||
"x": 10,
|
||||
"y": 10,
|
||||
"z": 10,
|
||||
|
@ -315,28 +316,6 @@ const tr15: TaggedResource = {
|
|||
"uuid": "Tool.15.50"
|
||||
};
|
||||
|
||||
const tr16: TaggedPoint = {
|
||||
specialStatus: SpecialStatus.SAVED,
|
||||
kind: "Point",
|
||||
body: {
|
||||
id: 1395,
|
||||
created_at: "2017-05-24T20:41:19.889Z",
|
||||
updated_at: "2017-05-24T20:41:19.889Z",
|
||||
meta: {
|
||||
color: "gray",
|
||||
created_by: "plant-detection"
|
||||
},
|
||||
name: "untitled",
|
||||
pointer_type: "Weed",
|
||||
plant_stage: "planned",
|
||||
radius: 10,
|
||||
x: 490,
|
||||
y: 421,
|
||||
z: 5
|
||||
},
|
||||
uuid: "Point.1397.11"
|
||||
};
|
||||
|
||||
const log: TaggedLog = {
|
||||
kind: "Log",
|
||||
specialStatus: SpecialStatus.SAVED,
|
||||
|
@ -366,7 +345,6 @@ export const FAKE_RESOURCES: TaggedResource[] = [
|
|||
tr0,
|
||||
tr14,
|
||||
tr15,
|
||||
tr16,
|
||||
log,
|
||||
];
|
||||
const KIND: keyof TaggedResource = "kind"; // Safety first, kids.
|
||||
|
|
|
@ -334,7 +334,6 @@ const MUST_CONFIRM_LIST: ResourceName[] = [
|
|||
"Regimen",
|
||||
"Image",
|
||||
"SavedGarden",
|
||||
"PointGroup",
|
||||
];
|
||||
|
||||
const confirmationChecker = (resourceName: ResourceName, force = false) =>
|
||||
|
|
|
@ -37,9 +37,7 @@ import { getDevice } from "../../../device";
|
|||
import { talk } from "browser-speech";
|
||||
import { MessageType } from "../../../sequences/interfaces";
|
||||
import { FbjsEventName } from "farmbot/dist/constants";
|
||||
import {
|
||||
info, error, success, warning, fun, busy, removeToast,
|
||||
} from "../../../toast/toast";
|
||||
import { info, error, success, warning, fun, busy } from "../../../toast/toast";
|
||||
import { onLogs } from "../../log_handlers";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import { globalQueue } from "../../batch_queue";
|
||||
|
@ -179,8 +177,7 @@ describe("onOffline", () => {
|
|||
jest.resetAllMocks();
|
||||
onOffline();
|
||||
expect(dispatchNetworkDown).toHaveBeenCalledWith("user.mqtt", ANY_NUMBER);
|
||||
expect(error).toHaveBeenCalledWith(
|
||||
Content.MQTT_DISCONNECTED, "Error", "red", "offline");
|
||||
expect(error).toHaveBeenCalledWith(Content.MQTT_DISCONNECTED);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -189,17 +186,13 @@ describe("onOnline", () => {
|
|||
jest.resetAllMocks();
|
||||
onOnline();
|
||||
expect(dispatchNetworkUp).toHaveBeenCalledWith("user.mqtt", ANY_NUMBER);
|
||||
expect(removeToast).toHaveBeenCalledWith("offline");
|
||||
});
|
||||
});
|
||||
|
||||
describe("onReconnect()", () => {
|
||||
it("sends reconnect toast", () => {
|
||||
onReconnect();
|
||||
expect(warning).toHaveBeenCalledWith(
|
||||
"Attempting to reconnect to the message broker",
|
||||
"Offline", "yellow", "offline");
|
||||
});
|
||||
describe("onReconnect", () => {
|
||||
onReconnect();
|
||||
expect(warning).toHaveBeenCalledWith(
|
||||
"Attempting to reconnect to the message broker", "Offline", "yellow");
|
||||
});
|
||||
|
||||
describe("changeLastClientConnected", () => {
|
||||
|
@ -275,8 +268,7 @@ describe("onPublicBroadcast", () => {
|
|||
console.log = jest.fn();
|
||||
onPublicBroadcast({});
|
||||
expectBroadcastLog();
|
||||
expect(window.alert).toHaveBeenCalledWith(
|
||||
Content.FORCE_REFRESH_CANCEL_WARNING);
|
||||
expect(window.alert).toHaveBeenCalledWith(Content.FORCE_REFRESH_CANCEL_WARNING);
|
||||
expect(location.assign).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,9 +4,7 @@ import { Log } from "farmbot/dist/resources/api_resources";
|
|||
import { Farmbot, BotStateTree, TaggedResource } from "farmbot";
|
||||
import { FbjsEventName } from "farmbot/dist/constants";
|
||||
import { noop } from "lodash";
|
||||
import {
|
||||
success, error, info, warning, fun, busy, removeToast,
|
||||
} from "../toast/toast";
|
||||
import { success, error, info, warning, fun, busy } from "../toast/toast";
|
||||
import { HardwareState } from "../devices/interfaces";
|
||||
import { GetState, ReduxAction } from "../redux/interfaces";
|
||||
import { Content, Actions } from "../constants";
|
||||
|
@ -104,6 +102,11 @@ export function readStatus() {
|
|||
.then(() => { commandOK(noun); }, commandErr(noun));
|
||||
}
|
||||
|
||||
export const onOffline = () => {
|
||||
dispatchNetworkDown("user.mqtt", now());
|
||||
error(t(Content.MQTT_DISCONNECTED));
|
||||
};
|
||||
|
||||
export const changeLastClientConnected = (bot: Farmbot) => () => {
|
||||
bot.setUserEnv({
|
||||
"LAST_CLIENT_CONNECTED": JSON.stringify(new Date())
|
||||
|
@ -154,20 +157,14 @@ export function onMalformed() {
|
|||
}
|
||||
}
|
||||
|
||||
export const onOnline = () => {
|
||||
removeToast("offline");
|
||||
success(t("Reconnected to the message broker."), t("Online"));
|
||||
dispatchNetworkUp("user.mqtt", now());
|
||||
};
|
||||
|
||||
export const onReconnect = () =>
|
||||
warning(t("Attempting to reconnect to the message broker"),
|
||||
t("Offline"), "yellow", "offline");
|
||||
|
||||
export const onOffline = () => {
|
||||
dispatchNetworkDown("user.mqtt", now());
|
||||
error(t(Content.MQTT_DISCONNECTED), t("Error"), "red", "offline");
|
||||
};
|
||||
export const onOnline =
|
||||
() => {
|
||||
success(t("Reconnected to the message broker."), t("Online"));
|
||||
dispatchNetworkUp("user.mqtt", now());
|
||||
};
|
||||
export const onReconnect =
|
||||
() => warning(t("Attempting to reconnect to the message broker"),
|
||||
t("Offline"), "yellow");
|
||||
|
||||
export function onPublicBroadcast(payl: unknown) {
|
||||
console.log(FbjsEventName.publicBroadcast, payl);
|
||||
|
|
|
@ -2,27 +2,6 @@ import { trim } from "./util";
|
|||
|
||||
export namespace ToolTips {
|
||||
|
||||
// Farm Designer: Groups
|
||||
export const SORT_DESCRIPTION =
|
||||
trim(`When executing a sequence over a Group of locations, FarmBot will
|
||||
travel to each group member in the order of the chosen sort method.
|
||||
If the random option is chosen, FarmBot will travel in a random order
|
||||
every time, so the ordering shown below will only be representative.`);
|
||||
|
||||
export const CRITERIA_SELECTION_COUNT =
|
||||
trim(`Filter additions can only be removed by changing filters.
|
||||
Click and drag in the map to modify selection filters.
|
||||
Filters will be applied at the time of sequence execution. The final
|
||||
selection at that time may differ from the selection currently
|
||||
displayed.`);
|
||||
|
||||
export const CRITERIA_ALPHA_FEATURE =
|
||||
trim(`Group filters is a new feature under active development.
|
||||
Use with caution.`);
|
||||
|
||||
export const DOT_NOTATION_TIP =
|
||||
trim(`Tip: Use dot notation (i.e., 'meta.color') to access meta fields.`);
|
||||
|
||||
// Controls
|
||||
export const MOVE =
|
||||
trim(`Use these manual control buttons to move FarmBot in realtime.
|
||||
|
@ -33,12 +12,7 @@ export namespace ToolTips {
|
|||
|
||||
export const WEBCAM =
|
||||
trim(`If you have a webcam, you can view the video stream in this widget.
|
||||
Press the edit button to update and save your webcam URL.
|
||||
Note: Some webcam services do not allow webcam feeds to be embedded in
|
||||
other sites. If you see a web browser error after adding a webcam feed,
|
||||
there is unfortunately nothing FarmBot can do to fix the problem.
|
||||
Please contact your webcam's customer support to see if the security
|
||||
policy for embedding feeds into other sites can be changed.`);
|
||||
Press the edit button to update and save your webcam URL.`);
|
||||
|
||||
export const PERIPHERALS =
|
||||
trim(`Use these toggle switches to control FarmBot's peripherals in
|
||||
|
@ -52,19 +26,10 @@ export namespace ToolTips {
|
|||
export const SENSOR_HISTORY =
|
||||
trim(`View and filter historical sensor reading data.`);
|
||||
|
||||
// FarmBot OS Settings: Firmware
|
||||
export const FIRMWARE_VALUE_API =
|
||||
trim(`Firmware value from your choice in the dropdown to the left, as
|
||||
understood by the Web App.`);
|
||||
// Device
|
||||
export const OS_SETTINGS =
|
||||
trim(`View and change device settings.`);
|
||||
|
||||
export const FIRMWARE_VALUE_FBOS =
|
||||
trim(`Firmware value reported from the firmware, as understood by
|
||||
FarmBot OS.`);
|
||||
|
||||
export const FIRMWARE_VALUE_MCU =
|
||||
trim(`Firmware value reported from the firmware.`);
|
||||
|
||||
// Hardware Settings
|
||||
export const HW_SETTINGS =
|
||||
trim(`Change settings of your FarmBot hardware with the fields below.
|
||||
Caution: Changing these settings to extreme values can cause hardware
|
||||
|
@ -73,6 +38,18 @@ export namespace ToolTips {
|
|||
Tip: Recalibrate FarmBot after changing settings and test a
|
||||
few sequences to verify that everything works as expected.`);
|
||||
|
||||
export const PIN_BINDINGS =
|
||||
trim(`Assign an action or sequence to execute when a Raspberry Pi
|
||||
GPIO pin is activated.`);
|
||||
|
||||
export const PIN_BINDING_WARNING =
|
||||
trim(`Warning: Binding to a pin without a physical button and
|
||||
pull-down resistor connected may put FarmBot into an unstable state.`);
|
||||
|
||||
// Connectivity
|
||||
export const CONNECTIVITY =
|
||||
trim(`Diagnose connectivity issues with FarmBot and the browser.`);
|
||||
|
||||
// Hardware Settings: Homing and Calibration
|
||||
export const HOMING_ENCODERS =
|
||||
trim(`If encoders or end-stops are enabled, home axis (find zero).`);
|
||||
|
@ -225,16 +202,13 @@ export namespace ToolTips {
|
|||
trim(`The number of the pin to guard. This pin will be set to the specified
|
||||
state after the duration specified by TIMEOUT.`);
|
||||
|
||||
// Hardware Settings: Pin Bindings
|
||||
export const PIN_BINDINGS =
|
||||
trim(`Assign an action or sequence to execute when a Raspberry Pi
|
||||
GPIO pin is activated.`);
|
||||
|
||||
export const PIN_BINDING_WARNING =
|
||||
trim(`Warning: Binding to a pin without a physical button and
|
||||
pull-down resistor connected may put FarmBot into an unstable state.`);
|
||||
|
||||
// Farmware
|
||||
export const FARMWARE =
|
||||
trim(`Manage Farmware (plugins).`);
|
||||
|
||||
export const FARMWARE_LIST =
|
||||
trim(`View, select, and install new Farmware.`);
|
||||
|
||||
export const PHOTOS =
|
||||
trim(`Take and view photos with your FarmBot's camera.`);
|
||||
|
||||
|
@ -258,6 +232,9 @@ export namespace ToolTips {
|
|||
You can also edit, copy, and delete existing sequences;
|
||||
assign a color; and give your commands custom names.`);
|
||||
|
||||
export const SEQUENCE_LIST =
|
||||
trim(`Here is the list of all of your sequences. Click one to edit.`);
|
||||
|
||||
export const DEFAULT_VALUE =
|
||||
trim(`Select a location to be used as the default value for this variable.
|
||||
If the sequence is ever run without the variable explicitly set to
|
||||
|
@ -335,7 +312,6 @@ export namespace ToolTips {
|
|||
export const TAKE_PHOTO =
|
||||
trim(`Snaps a photo using the device camera. Select the camera type
|
||||
on the Device page.`);
|
||||
|
||||
export const EMERGENCY_LOCK =
|
||||
trim(`Stops a device from moving until it is unlocked by a user.`);
|
||||
|
||||
|
@ -346,7 +322,10 @@ export namespace ToolTips {
|
|||
trim(`The Mark As step allows FarmBot to programmatically edit the
|
||||
properties of the UTM, plants, and weeds from within a sequence.
|
||||
For example, you can mark a plant as "planted" during a seeding
|
||||
sequence or mark a weed as "removed" after removing it.`);
|
||||
sequence or delete a weed after removing it.`);
|
||||
|
||||
export const REBOOT =
|
||||
trim(`Power cycle FarmBot's onboard computer.`);
|
||||
|
||||
export const SET_SERVO_ANGLE =
|
||||
trim(`Move a servo to the provided angle. An angle of 90 degrees
|
||||
|
@ -359,9 +338,6 @@ export namespace ToolTips {
|
|||
export const MOVE_TO_HOME =
|
||||
trim(`Move FarmBot to home for the provided axis.`);
|
||||
|
||||
export const ASSERTION =
|
||||
trim(`Evaluate Lua commands. For power users and software developers.`);
|
||||
|
||||
export const FIRMWARE_ACTION =
|
||||
trim(`FarmBot OS or micro-controller firmware action.`);
|
||||
|
||||
|
@ -387,6 +363,20 @@ export namespace ToolTips {
|
|||
growing at the same or different times. Multiple regimens can be
|
||||
applied to any one plant.`);
|
||||
|
||||
export const REGIMEN_LIST =
|
||||
trim(`This is a list of all of your regimens. Click one to begin
|
||||
editing it.`);
|
||||
|
||||
// Tools
|
||||
export const TOOL_LIST =
|
||||
trim(`This is a list of all your FarmBot tools and seed containers.
|
||||
Click the Edit button to add, edit, or delete tools or seed containers.`);
|
||||
|
||||
export const TOOLBAY_LIST =
|
||||
trim(`Tool slots are where you store your FarmBot tools and seed
|
||||
containers, which should be reflective of your real FarmBot hardware
|
||||
configuration.`);
|
||||
|
||||
// Logs
|
||||
export const LOGS =
|
||||
trim(`View and filter log messages.`);
|
||||
|
@ -409,6 +399,16 @@ export namespace ToolTips {
|
|||
|
||||
export const FIRMWARE_DEBUG_MESSAGES =
|
||||
trim(`Log all debug received from firmware (clears after refresh).`);
|
||||
|
||||
export const MESSAGES =
|
||||
trim(`View messages.`);
|
||||
|
||||
// App
|
||||
export const LABS =
|
||||
trim(`Customize your web app experience.`);
|
||||
|
||||
export const TOURS =
|
||||
trim(`Take a guided tour of the Web App.`);
|
||||
}
|
||||
|
||||
export namespace Content {
|
||||
|
@ -512,9 +512,11 @@ export namespace Content {
|
|||
real account at`);
|
||||
|
||||
// App Settings
|
||||
export const TIME_FORMAT_24_HOUR =
|
||||
trim(`Display time using the 24-hour notation,
|
||||
i.e., 23:00 instead of 11:00pm`);
|
||||
export const CONFIRM_STEP_DELETION =
|
||||
trim(`Show a confirmation dialog when deleting a sequence step.`);
|
||||
|
||||
export const CONFIRM_SEQUENCE_DELETION =
|
||||
trim(`Show a confirmation dialog when deleting a sequence.`);
|
||||
|
||||
export const HIDE_WEBCAM_WIDGET =
|
||||
trim(`If not using a webcam, use this setting to remove the
|
||||
|
@ -524,6 +526,14 @@ export namespace Content {
|
|||
trim(`If not using sensors, use this setting to remove the
|
||||
widget from the Controls page.`);
|
||||
|
||||
export const DYNAMIC_MAP_SIZE =
|
||||
trim(`Change the garden map size based on axis length.
|
||||
A value must be input in AXIS LENGTH and STOP AT MAX must be enabled in
|
||||
the HARDWARE widget. Overrides MAP SIZE values.`);
|
||||
|
||||
export const PLANT_ANIMATIONS =
|
||||
trim(`Enable plant animations in the garden map.`);
|
||||
|
||||
export const BROWSER_SPEAK_LOGS =
|
||||
trim(`Have the browser also read aloud log messages on the
|
||||
"Speak" channel that are spoken by FarmBot.`);
|
||||
|
@ -536,33 +546,6 @@ export namespace Content {
|
|||
trim(`Warning! When enabled, any unsaved changes
|
||||
will be discarded when refreshing or closing the page. Are you sure?`);
|
||||
|
||||
export const EMERGENCY_UNLOCK_CONFIRM_CONFIG =
|
||||
trim(`Confirm when unlocking FarmBot after an emergency stop.`);
|
||||
|
||||
export const CONFIRM_EMERGENCY_UNLOCK_CONFIRM_DISABLE =
|
||||
trim(`Warning! When disabled, clicking the UNLOCK button will immediately
|
||||
unlock FarmBot instead of confirming that it is safe to do so.
|
||||
As a result, double-clicking the E-STOP button may not stop FarmBot.
|
||||
Are you sure you want to disable this feature?`);
|
||||
|
||||
export const USER_INTERFACE_READ_ONLY_MODE =
|
||||
trim(`Disallow account data changes. This does
|
||||
not prevent Farmwares or FarmBot OS from changing settings.`);
|
||||
|
||||
// Sequence Settings
|
||||
export const CONFIRM_STEP_DELETION =
|
||||
trim(`Show a confirmation dialog when deleting a sequence step.`);
|
||||
|
||||
export const CONFIRM_SEQUENCE_DELETION =
|
||||
trim(`Show a confirmation dialog when deleting a sequence.`);
|
||||
|
||||
export const SHOW_PINS =
|
||||
trim(`Show raw pin lists in Read Sensor, Control Peripheral, and
|
||||
If Statement steps.`);
|
||||
|
||||
export const EXPAND_STEP_OPTIONS =
|
||||
trim(`Choose whether advanced step options are open or closed by default.`);
|
||||
|
||||
export const DISCARD_UNSAVED_SEQUENCE_CHANGES =
|
||||
trim(`Don't ask about saving sequence work before
|
||||
closing browser tab. Warning: may cause loss of data.`);
|
||||
|
@ -571,19 +554,34 @@ export namespace Content {
|
|||
trim(`Warning! When enabled, any unsaved changes to sequences
|
||||
will be discarded when refreshing or closing the page. Are you sure?`);
|
||||
|
||||
// Farm Designer Settings
|
||||
export const PLANT_ANIMATIONS =
|
||||
trim(`Enable plant animations in the garden map.`);
|
||||
|
||||
export const VIRTUAL_TRAIL =
|
||||
trim(`Display a virtual trail for FarmBot in the garden map to show
|
||||
movement and watering history while the map is open. Toggling this setting
|
||||
will clear data for the current trail.`);
|
||||
|
||||
export const DYNAMIC_MAP_SIZE =
|
||||
trim(`Change the garden map size based on axis length.
|
||||
A value must be input in AXIS LENGTH and STOP AT MAX must be enabled in
|
||||
the HARDWARE widget. Overrides MAP SIZE values.`);
|
||||
export const TIME_FORMAT_24_HOUR =
|
||||
trim(`Display time using the 24-hour notation,
|
||||
i.e., 23:00 instead of 11:00pm`);
|
||||
|
||||
export const SHOW_PINS =
|
||||
trim(`Show raw pin lists in Read Sensor, Control Peripheral, and
|
||||
If Statement steps.`);
|
||||
|
||||
export const EXPAND_STEP_OPTIONS =
|
||||
trim(`Choose whether advanced step options are open or closed by default.`);
|
||||
|
||||
export const EMERGENCY_UNLOCK_CONFIRM_CONFIG =
|
||||
trim(`Confirm when unlocking FarmBot after an emergency stop.`);
|
||||
|
||||
export const USER_INTERFACE_READ_ONLY_MODE =
|
||||
trim(`Disallow account data changes. This does
|
||||
not prevent Farmwares or FarmBot OS from changing settings.`);
|
||||
|
||||
export const CONFIRM_EMERGENCY_UNLOCK_CONFIRM_DISABLE =
|
||||
trim(`Warning! When disabled, clicking the UNLOCK button will immediately
|
||||
unlock FarmBot instead of confirming that it is safe to do so.
|
||||
As a result, double-clicking the E-STOP button may not stop FarmBot.
|
||||
Are you sure you want to disable this feature?`);
|
||||
|
||||
export const MAP_SIZE =
|
||||
trim(`Specify custom map dimensions (in millimeters).
|
||||
|
@ -602,41 +600,13 @@ export namespace Content {
|
|||
export const CONFIRM_PLANT_DELETION =
|
||||
trim(`Show a confirmation dialog when deleting a plant.`);
|
||||
|
||||
// FarmBot OS Settings
|
||||
export const DIFFERENT_TZ_WARNING =
|
||||
trim(`Note: The selected timezone for your FarmBot is different than
|
||||
your local browser time.`);
|
||||
// Device
|
||||
export const NOT_HTTPS =
|
||||
trim(`WARNING: Sending passwords via HTTP:// is not secure.`);
|
||||
|
||||
export const OS_BETA_RELEASES =
|
||||
trim(`Warning! Leaving the stable FarmBot OS release channel may reduce
|
||||
FarmBot system stability. Are you sure?`);
|
||||
|
||||
export const DEVICE_NEVER_SEEN =
|
||||
trim(`The device has never been seen. Most likely,
|
||||
there is a network connectivity issue on the device's end.`);
|
||||
|
||||
export const TOO_OLD_TO_UPDATE =
|
||||
trim(`Please re-flash your FarmBot's SD card.`);
|
||||
|
||||
export const OS_AUTO_UPDATE =
|
||||
trim(`When enabled, FarmBot OS will automatically download and install
|
||||
software updates at the chosen time.`);
|
||||
|
||||
export const AUTO_SYNC =
|
||||
trim(`When enabled, device resources such as sequences and regimens
|
||||
will be sent to the device automatically. This removes the need to push
|
||||
"SYNC" after making changes in the web app. Changes to running
|
||||
sequences and regimens while auto sync is enabled will result in
|
||||
instantaneous change.`);
|
||||
|
||||
// FarmBot OS Settings: Power and Reset
|
||||
export const RESTART_FARMBOT =
|
||||
trim(`This will restart FarmBot's Raspberry Pi and controller
|
||||
software.`);
|
||||
|
||||
export const SHUTDOWN_FARMBOT =
|
||||
trim(`This will shutdown FarmBot's Raspberry Pi. To turn it
|
||||
back on, unplug FarmBot and plug it back in.`);
|
||||
export const CONTACT_SYSADMIN =
|
||||
trim(`Please contact the system(s) administrator(s) and ask them to enable
|
||||
HTTPS://`);
|
||||
|
||||
export const FACTORY_RESET_WARNING =
|
||||
trim(`Factory resetting your FarmBot will destroy all data on the device,
|
||||
|
@ -654,6 +624,10 @@ export namespace Content {
|
|||
not delete data stored in your web app account. Are you sure you wish
|
||||
to continue?`);
|
||||
|
||||
export const MCU_RESET_ALERT =
|
||||
trim(`Warning: This will reset all hardware settings to the default values.
|
||||
Are you sure you wish to continue?`);
|
||||
|
||||
export const AUTO_FACTORY_RESET =
|
||||
trim(`Automatically factory reset when the WiFi network cannot be
|
||||
detected. Useful for network changes.`);
|
||||
|
@ -662,26 +636,54 @@ export namespace Content {
|
|||
trim(`Time in minutes to attempt connecting to WiFi before a factory
|
||||
reset.`);
|
||||
|
||||
export const NOT_HTTPS =
|
||||
trim(`WARNING: Sending passwords via HTTP:// is not secure.`);
|
||||
export const DIFFERENT_TZ_WARNING =
|
||||
trim(`Note: The selected timezone for your FarmBot is different than
|
||||
your local browser time.`);
|
||||
|
||||
export const CONTACT_SYSADMIN =
|
||||
trim(`Please contact the system(s) administrator(s) and ask them to enable
|
||||
HTTPS://`);
|
||||
export const RESTART_FARMBOT =
|
||||
trim(`This will restart FarmBot's Raspberry Pi and controller
|
||||
software.`);
|
||||
|
||||
// FarmBot OS Settings: Firmware
|
||||
export const RESTART_FIRMWARE =
|
||||
trim(`Restart the Farmduino or Arduino firmware.`);
|
||||
|
||||
// Hardware Settings: Danger Zone
|
||||
export const OS_AUTO_UPDATE =
|
||||
trim(`When enabled, FarmBot OS will automatically download and install
|
||||
software updates at the chosen time.`);
|
||||
|
||||
export const AUTO_SYNC =
|
||||
trim(`When enabled, device resources such as sequences and regimens
|
||||
will be sent to the device automatically. This removes the need to push
|
||||
"SYNC" after making changes in the web app. Changes to running
|
||||
sequences and regimens while auto sync is enabled will result in
|
||||
instantaneous change.`);
|
||||
|
||||
export const SHUTDOWN_FARMBOT =
|
||||
trim(`This will shutdown FarmBot's Raspberry Pi. To turn it
|
||||
back on, unplug FarmBot and plug it back in.`);
|
||||
|
||||
export const OS_BETA_RELEASES =
|
||||
trim(`Warning! Leaving the stable FarmBot OS release channel may reduce
|
||||
FarmBot system stability. Are you sure?`);
|
||||
|
||||
export const DIAGNOSTIC_CHECK =
|
||||
trim(`Save snapshot of FarmBot OS system information, including
|
||||
user and device identity, to the database. A code will be returned
|
||||
that you can provide in support requests to allow FarmBot to look up
|
||||
data relevant to the issue to help us identify the problem.`);
|
||||
|
||||
export const DEVICE_NEVER_SEEN =
|
||||
trim(`The device has never been seen. Most likely,
|
||||
there is a network connectivity issue on the device's end.`);
|
||||
|
||||
export const TOO_OLD_TO_UPDATE =
|
||||
trim(`Please re-flash your FarmBot's SD card.`);
|
||||
|
||||
// Hardware Settings
|
||||
export const RESTORE_DEFAULT_HARDWARE_SETTINGS =
|
||||
trim(`Restoring hardware parameter defaults will destroy the
|
||||
current settings, resetting them to default values.`);
|
||||
|
||||
export const MCU_RESET_ALERT =
|
||||
trim(`Warning: This will reset all hardware settings to the default values.
|
||||
Are you sure you wish to continue?`);
|
||||
|
||||
// App
|
||||
export const APP_LOAD_TIMEOUT_MESSAGE =
|
||||
trim(`App could not be fully loaded, we recommend you try
|
||||
|
@ -709,6 +711,10 @@ export namespace Content {
|
|||
broken and may break or otherwise hinder your usage of the rest of the
|
||||
app. This feature may disappear or break at any time.`);
|
||||
|
||||
export const NEW_TOS =
|
||||
trim(`Before logging in, you must agree to our latest Terms of Service and
|
||||
Privacy Policy`);
|
||||
|
||||
export const FORCE_REFRESH_CONFIRM =
|
||||
trim(`A new version of the FarmBot web app has been released.
|
||||
Refresh page?`);
|
||||
|
@ -740,15 +746,6 @@ export namespace Content {
|
|||
encoders, stall detection, or endstops enabled for the chosen axis.
|
||||
Enable endstops, encoders, or stall detection from the Device page for: `);
|
||||
|
||||
export const REBOOT_STEP =
|
||||
trim(`Power cycle FarmBot's onboard computer.`);
|
||||
|
||||
export const SHUTDOWN_STEP =
|
||||
trim(`Power down FarmBot's onboard computer.`);
|
||||
|
||||
export const ESTOP_STEP =
|
||||
trim(`Unlocking a device requires user intervention.`);
|
||||
|
||||
export const IN_USE =
|
||||
trim(`Used in another resource. Protected from deletion.`);
|
||||
|
||||
|
@ -776,7 +773,7 @@ export namespace Content {
|
|||
trim(`Click and drag or use the inputs to draw a weed.`);
|
||||
|
||||
export const BOX_SELECT_DESCRIPTION =
|
||||
trim(`Drag a box around the items you would like to select.
|
||||
trim(`Drag a box around the plants you would like to select.
|
||||
Press the back arrow to exit.`);
|
||||
|
||||
export const SAVED_GARDENS =
|
||||
|
@ -822,8 +819,7 @@ export namespace Content {
|
|||
|
||||
export const MOUNTED_TOOL =
|
||||
trim(`The tool currently mounted to the UTM can be set here or by using
|
||||
a MARK AS step in a sequence. Use the verify button or read the tool
|
||||
verification pin in a sequence to verify that a tool is attached.`);
|
||||
a MARK AS step in a sequence.`);
|
||||
|
||||
// Farm Events
|
||||
export const NOTHING_SCHEDULED =
|
||||
|
@ -835,6 +831,10 @@ export namespace Content {
|
|||
regimen tasks. Consider rescheduling this event to tomorrow if
|
||||
this is a concern.`);
|
||||
|
||||
export const INVALID_RUN_TIME =
|
||||
trim(`This event does not appear to have a valid run time.
|
||||
Perhaps you entered bad dates?`);
|
||||
|
||||
export const FARM_EVENT_TZ_WARNING =
|
||||
trim(`Note: Times displayed according to FarmBot's local time, which
|
||||
is currently different from your browser's time. Timezone data is
|
||||
|
@ -849,14 +849,27 @@ export namespace Content {
|
|||
trim(`You haven't made any sequences or regimens yet. To add an event,
|
||||
first create a sequence or regimen.`);
|
||||
|
||||
// Groups
|
||||
export const SORT_DESCRIPTION =
|
||||
trim(`When executing a sequence over a Group of locations, FarmBot will
|
||||
travel to each group member in the order of the chosen sort method.
|
||||
If the random option is chosen, FarmBot will travel in a random order
|
||||
every time, so the ordering shown below will only be representative.`);
|
||||
|
||||
export const CRITERIA_SELECTION_COUNT =
|
||||
trim(`Criteria additions can only be removed by changing criteria.
|
||||
Click and drag in the map to modify selection criteria.
|
||||
Criteria will be applied at the time of sequence execution. The final
|
||||
selection at that time may differ from the selection currently
|
||||
displayed.`);
|
||||
|
||||
// Farmware
|
||||
export const NO_IMAGES_YET =
|
||||
trim(`You haven't yet taken any photos with your FarmBot.
|
||||
Once you do, they will show up here.`);
|
||||
|
||||
export const PROCESSING_PHOTO =
|
||||
trim(`Processing now. Results usually available in one minute.
|
||||
Check log messages for result status.`);
|
||||
trim(`Processing now. Results usually available in one minute.`);
|
||||
|
||||
export const NOT_AVAILABLE_WHEN_OFFLINE =
|
||||
trim(`Not available when device is offline.`);
|
||||
|
@ -932,8 +945,6 @@ export namespace TourContent {
|
|||
}
|
||||
|
||||
export enum DeviceSetting {
|
||||
axisHeadingLabels = ``,
|
||||
|
||||
// Homing and calibration
|
||||
homingAndCalibration = `Homing and Calibration`,
|
||||
homing = `Homing`,
|
||||
|
@ -985,11 +996,6 @@ export enum DeviceSetting {
|
|||
|
||||
// Pin Guard
|
||||
pinGuard = `Pin Guard`,
|
||||
pinGuard1 = `Pin Guard 1`,
|
||||
pinGuard2 = `Pin Guard 2`,
|
||||
pinGuard3 = `Pin Guard 3`,
|
||||
pinGuard4 = `Pin Guard 4`,
|
||||
pinGuard5 = `Pin Guard 5`,
|
||||
|
||||
// Danger Zone
|
||||
dangerZone = `Danger Zone`,
|
||||
|
@ -997,8 +1003,6 @@ export enum DeviceSetting {
|
|||
|
||||
// Pin Bindings
|
||||
pinBindings = `Pin Bindings`,
|
||||
savedPinBindings = `Saved pin bindings`,
|
||||
addNewPinBinding = `Add new pin binding`,
|
||||
|
||||
// FarmBot OS
|
||||
farmbot = `FarmBot`,
|
||||
|
@ -1016,6 +1020,7 @@ export enum DeviceSetting {
|
|||
powerAndReset = `Power and Reset`,
|
||||
restartFarmbot = `Restart Farmbot`,
|
||||
shutdownFarmbot = `Shutdown Farmbot`,
|
||||
restartFirmware = `Restart Firmware`,
|
||||
factoryReset = `Factory Reset`,
|
||||
autoFactoryReset = `Automatic Factory Reset`,
|
||||
connectionAttemptPeriod = `Connection Attempt Period`,
|
||||
|
@ -1033,7 +1038,6 @@ export enum DeviceSetting {
|
|||
|
||||
// Firmware
|
||||
firmwareSection = `Firmware`,
|
||||
restartFirmware = `Restart Firmware`,
|
||||
flashFirmware = `Flash firmware`,
|
||||
}
|
||||
|
||||
|
@ -1135,8 +1139,7 @@ export enum Actions {
|
|||
|
||||
// Designer
|
||||
SEARCH_QUERY_CHANGE = "SEARCH_QUERY_CHANGE",
|
||||
SELECT_POINT = "SELECT_POINT",
|
||||
SET_SELECTION_POINT_TYPE = "SET_SELECTION_POINT_TYPE",
|
||||
SELECT_PLANT = "SELECT_PLANT",
|
||||
TOGGLE_HOVERED_PLANT = "TOGGLE_HOVERED_PLANT",
|
||||
TOGGLE_HOVERED_POINT = "TOGGLE_HOVERED_POINT",
|
||||
HOVER_PLANT_LIST_ITEM = "HOVER_PLANT_LIST_ITEM",
|
||||
|
@ -1145,11 +1148,9 @@ export enum Actions {
|
|||
OF_SEARCH_RESULTS_OK = "OF_SEARCH_RESULTS_OK",
|
||||
OF_SEARCH_RESULTS_NO = "OF_SEARCH_RESULTS_NO",
|
||||
CHOOSE_LOCATION = "CHOOSE_LOCATION",
|
||||
SET_DRAWN_POINT_DATA = "SET_DRAWN_POINT_DATA",
|
||||
SET_DRAWN_WEED_DATA = "SET_DRAWN_WEED_DATA",
|
||||
SET_CURRENT_POINT_DATA = "SET_CURRENT_POINT_DATA",
|
||||
CHOOSE_SAVED_GARDEN = "CHOOSE_SAVED_GARDEN",
|
||||
TRY_SORT_TYPE = "TRY_SORT_TYPE",
|
||||
SET_SETTINGS_SEARCH_TERM = "SET_SETTINGS_SEARCH_TERM",
|
||||
EDIT_GROUP_AREA_IN_MAP = "EDIT_GROUP_AREA_IN_MAP",
|
||||
|
||||
// Regimens
|
||||
|
|
|
@ -38,7 +38,6 @@ $pink: #ebb;
|
|||
$light_red: #e99;
|
||||
$red: #e66;
|
||||
$dark_red: #f00;
|
||||
$medium_dark_red: #c00;
|
||||
$darkest_red: #900;
|
||||
$panel_green: #35761b;
|
||||
$panel_light_green: #f3f9f1;
|
||||
|
|
|
@ -120,68 +120,46 @@
|
|||
|
||||
.thin-search-wrapper {
|
||||
width: 100%;
|
||||
.thin-search {
|
||||
.spinner-container {
|
||||
.text-input-wrapper {
|
||||
position: relative;
|
||||
margin: 1rem;
|
||||
border-bottom: 1px solid $dark_gray;
|
||||
&:before,
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: $dark_gray;
|
||||
width: 1px;
|
||||
height: 3px;
|
||||
}
|
||||
&:before {
|
||||
left: 0;
|
||||
}
|
||||
&:after {
|
||||
right: 0;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
.text-input-wrapper {
|
||||
position: relative;
|
||||
margin: 1rem;
|
||||
border-bottom: 1px solid $dark_gray;
|
||||
&:before,
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
background: $dark_gray;
|
||||
width: 1px;
|
||||
height: 3px;
|
||||
}
|
||||
&:before {
|
||||
left: 0;
|
||||
}
|
||||
&:after {
|
||||
right: 0;
|
||||
}
|
||||
i {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.fa-search {
|
||||
position: absolute;
|
||||
top: 0.8rem;
|
||||
left: 1rem;
|
||||
cursor: default !important;
|
||||
}
|
||||
.fa-times {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 0.5rem;
|
||||
color: $darkest_red;
|
||||
font-size: 1.3rem;
|
||||
&:hover {
|
||||
color: $medium_dark_red;
|
||||
}
|
||||
}
|
||||
i {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
input {
|
||||
background: transparent;
|
||||
box-shadow: none !important;
|
||||
padding-left: 3rem !important;
|
||||
font-size: 1.4rem !important;
|
||||
&:active,
|
||||
&:focus {
|
||||
background: transparent !important;
|
||||
}
|
||||
&::-webkit-input-placeholder {
|
||||
color: $placeholder_gray;
|
||||
}
|
||||
.fa-search {
|
||||
position: absolute;
|
||||
top: 0.8rem;
|
||||
left: 1rem;
|
||||
cursor: default !important;
|
||||
}
|
||||
}
|
||||
input {
|
||||
background: transparent;
|
||||
box-shadow: none !important;
|
||||
padding-left: 3rem !important;
|
||||
font-size: 1.4rem !important;
|
||||
&:active,
|
||||
&:focus {
|
||||
background: transparent !important;
|
||||
}
|
||||
&::-webkit-input-placeholder {
|
||||
color: $placeholder_gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,41 +185,30 @@
|
|||
}
|
||||
}
|
||||
|
||||
%panel-item-base {
|
||||
text-align: right;
|
||||
font-size: 1rem;
|
||||
padding-right: 1rem;
|
||||
line-height: 3rem;
|
||||
float: right;
|
||||
}
|
||||
.plant-search-item,
|
||||
.group-search-item {
|
||||
cursor: pointer;
|
||||
padding: 0.5rem 1rem;
|
||||
img {
|
||||
margin-right: 0.5rem;
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
}
|
||||
&.quick-del {
|
||||
&:hover {
|
||||
background: lighten($red, 10%) !important;
|
||||
&:after {
|
||||
content: "x";
|
||||
margin-left: 1rem;
|
||||
color: $darkest_red;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
margin: 0 1rem 0 0;
|
||||
height: 4rem;
|
||||
width: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
%panel-item-base {
|
||||
text-align: right;
|
||||
font-size: 1rem;
|
||||
padding-top: 1.4rem;
|
||||
padding-right: 1rem;
|
||||
float: right;
|
||||
}
|
||||
.plant-search-item-age {
|
||||
@extend %panel-item-base;
|
||||
}
|
||||
.group-item-count {
|
||||
@extend %panel-item-base;
|
||||
padding-top: 0.6rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
.plant-search-item-name {
|
||||
display: inline-block;
|
||||
|
@ -252,27 +219,24 @@
|
|||
text-overflow: ellipsis;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.weed-search-item,
|
||||
.point-search-item {
|
||||
cursor: pointer;
|
||||
padding: 0.5rem 1rem;
|
||||
.saucer {
|
||||
display: inline-block;
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
margin: 0 1rem 0 0;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
}
|
||||
.weed-search-item-info,
|
||||
.point-search-item-info {
|
||||
text-align: right;
|
||||
font-size: 1rem;
|
||||
padding-top: 0.6rem;
|
||||
padding-right: 1rem;
|
||||
line-height: 3rem;
|
||||
float: right;
|
||||
}
|
||||
.weed-search-item-name,
|
||||
.point-search-item-name {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
@ -280,34 +244,19 @@
|
|||
width: 40%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-left: 1.25rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.tool-search-item,
|
||||
.tool-slot-search-item {
|
||||
line-height: 4rem;
|
||||
cursor: pointer;
|
||||
.row {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
.tool-slot-search-item-name {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
p {
|
||||
font-size: 1rem;
|
||||
line-height: 4rem;
|
||||
&.tool-status,
|
||||
&.tool-slot-position {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.tool-slot-position-info {
|
||||
padding: 0;
|
||||
padding-right: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.thin-search {
|
||||
.spinner-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,17 +284,11 @@
|
|||
}
|
||||
|
||||
.map-point {
|
||||
cursor: pointer !important;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 0.3;
|
||||
fill-opacity: 0.1;
|
||||
}
|
||||
|
||||
.map-weed {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.weed-image,
|
||||
.plant-image {
|
||||
transform-origin: bottom;
|
||||
transform-box: fill-box;
|
||||
|
@ -394,9 +337,6 @@
|
|||
fill: $white;
|
||||
stroke: $white;
|
||||
}
|
||||
&:hover {
|
||||
opacity: 0.15;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -564,10 +504,10 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
.more-bugs,
|
||||
.select-mode,
|
||||
.move-to-mode {
|
||||
margin: auto;
|
||||
margin-top: 1rem;
|
||||
button {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
p {
|
||||
text-align: center;
|
||||
padding-top: 2rem;
|
||||
|
|
|
@ -291,19 +291,9 @@
|
|||
.panel-action-buttons {
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
height: 16rem;
|
||||
width: 100%;
|
||||
background: $panel_medium_light_gray;
|
||||
padding: 0.5rem;
|
||||
&.status {
|
||||
height: 20rem;
|
||||
}
|
||||
&.more {
|
||||
height: 23rem;
|
||||
}
|
||||
&.more.status {
|
||||
height: 26rem;
|
||||
}
|
||||
button {
|
||||
margin: 0.5rem;
|
||||
float: left;
|
||||
|
@ -312,15 +302,10 @@
|
|||
min-width: -webkit-fill-available;
|
||||
margin-bottom: 0px;
|
||||
margin-left: .5rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
.button-row {
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.filter-search {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
.plant-status-bulk-update {
|
||||
display: inline-flex;
|
||||
|
@ -334,35 +319,17 @@
|
|||
line-height: 4.1rem;
|
||||
}
|
||||
}
|
||||
.more {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
margin-right: 1rem;
|
||||
line-height: 2.5rem;
|
||||
p {
|
||||
display: inline;
|
||||
font-size: 1.4rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.panel-content {
|
||||
padding-top: 16rem;
|
||||
padding-top: 15rem;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
padding-bottom: 5rem;
|
||||
max-height: calc(100vh - 13rem);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
&.status {
|
||||
padding-top: 20rem;
|
||||
}
|
||||
&.more {
|
||||
padding-top: 23rem;
|
||||
}
|
||||
&.more.status {
|
||||
padding-top: 26rem;
|
||||
}
|
||||
.plant-search-item,
|
||||
.group-search-item { pointer-events: none; }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -398,13 +365,9 @@
|
|||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
.point-color-input {
|
||||
div[class*=col-] {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
.saucer {
|
||||
margin-top: 2.75rem;
|
||||
}
|
||||
.saucer {
|
||||
margin: 1rem;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
.delete-row {
|
||||
margin: 1.5rem;
|
||||
|
@ -414,39 +377,16 @@
|
|||
|
||||
.weed-info-panel-content,
|
||||
.point-info-panel-content {
|
||||
.point-color-input {
|
||||
div[class*=col-] {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
.saucer {
|
||||
margin-top: 4.5rem;
|
||||
}
|
||||
.saucer {
|
||||
margin: 1rem;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
.fb-button & .red {
|
||||
display: block;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
font-size: 1.4rem;
|
||||
p {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 0.5rem !important;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.weed-removal-method-section {
|
||||
.weed-removal-method {
|
||||
display: flex;
|
||||
input {
|
||||
margin: 0;
|
||||
width: 10%;
|
||||
box-shadow: none;
|
||||
}
|
||||
label {
|
||||
margin: 0;
|
||||
margin-top: auto;
|
||||
font-size: 1.25rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -571,7 +511,6 @@
|
|||
margin-top: 1rem;
|
||||
p {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 0.5rem !important;
|
||||
}
|
||||
}
|
||||
input {
|
||||
|
@ -618,8 +557,22 @@
|
|||
overflow-x: hidden;
|
||||
.tool-search-item,
|
||||
.tool-slot-search-item {
|
||||
line-height: 4rem;
|
||||
cursor: pointer;
|
||||
margin-left: -15px;
|
||||
margin-right: -15px;
|
||||
.row {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
line-height: 4rem;
|
||||
&.tool-status,
|
||||
&.tool-slot-position {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
.filter-search {
|
||||
.bp3-button {
|
||||
min-height: 2.5rem;
|
||||
|
@ -632,6 +585,13 @@
|
|||
line-height: 2rem;
|
||||
}
|
||||
}
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.tool-slot-position-info {
|
||||
padding: 0;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
}
|
||||
.mounted-tool-header {
|
||||
display: flex;
|
||||
|
@ -690,7 +650,6 @@
|
|||
margin-top: 1rem;
|
||||
&.red {
|
||||
float: left;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
svg {
|
||||
|
@ -700,19 +659,6 @@
|
|||
height: 10rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.edit-tool,
|
||||
.add-new-tool {
|
||||
margin-bottom: 3rem;
|
||||
.name-error {
|
||||
margin-top: 1.2rem;
|
||||
margin-right: 1rem;
|
||||
color: $dark_red;
|
||||
float: right;
|
||||
}
|
||||
.save-btn {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
.add-stock-tools {
|
||||
.filter-search {
|
||||
margin-bottom: 1rem;
|
||||
|
@ -804,101 +750,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
.no-pad {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.settings-panel-content {
|
||||
padding: 0;
|
||||
margin-top: 6rem;
|
||||
max-height: calc(100vh - 15rem);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
margin-top: 5rem;
|
||||
padding-bottom: 5rem;
|
||||
.section {
|
||||
margin-bottom: 2rem;
|
||||
button {
|
||||
margin-top: 1.75rem;
|
||||
}
|
||||
.bulk-expand-controls {
|
||||
p {
|
||||
padding: 0.5rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.row:first-child {
|
||||
margin-right: 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.row:nth-child(2) {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 3rem;
|
||||
}
|
||||
.label-headings {
|
||||
margin-right: 2rem;
|
||||
label {
|
||||
line-height: 1rem;
|
||||
}
|
||||
}
|
||||
.release-notes-wrapper {
|
||||
float: right !important;
|
||||
}
|
||||
.network-not-found-timer {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.pin-guard-input-row {
|
||||
.row {
|
||||
margin-left: -15px;
|
||||
margin-right: -15px;
|
||||
padding-left: 0;
|
||||
padding-right: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
.pin-bindings {
|
||||
margin-right: 1rem;
|
||||
.row {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
margin-left: 1rem;
|
||||
margin-right: 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
div[class*=col-] {
|
||||
padding: 0;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
.bindings-list {
|
||||
margin-left: -5px;
|
||||
.binding-action {
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
.pin-binding-input-rows {
|
||||
margin-right: 1rem;
|
||||
margin-left: -15px;
|
||||
label {
|
||||
margin-left: 1rem !important;
|
||||
}
|
||||
.green {
|
||||
float: left;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.row:last-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
.stock-pin-bindings-button {
|
||||
display: inline;
|
||||
button {
|
||||
margin: 0;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.fb-button {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
label {
|
||||
margin: 0 !important;
|
||||
line-height: 3rem;
|
||||
}
|
||||
.bp3-popover-wrapper {
|
||||
display: inline;
|
||||
float: none;
|
||||
}
|
||||
.map-size-inputs {
|
||||
.row {
|
||||
|
@ -908,31 +772,6 @@
|
|||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
.help-icon {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.all-settings-content {
|
||||
max-height: calc(100vh - 22rem);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
margin-top: 1rem;
|
||||
padding-left: 1rem;
|
||||
.expandable-header {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.designer-settings {
|
||||
max-height: calc(100vh - 14rem);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
margin-right: -10px;
|
||||
padding-right: 1rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
.designer-setting {
|
||||
&.disabled {
|
||||
input {
|
||||
|
@ -968,19 +807,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.weed-item-icon,
|
||||
.group-item-icon {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
.weed-icon {
|
||||
position: absolute;
|
||||
top: 13%;
|
||||
left: 12%;
|
||||
width: 70%;
|
||||
height: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
.weeds-inventory-panel,
|
||||
.zones-inventory-panel,
|
||||
.groups-panel {
|
||||
|
@ -998,11 +824,6 @@
|
|||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 5rem;
|
||||
.clear-day-criteria,
|
||||
.clear-point-ids,
|
||||
.clear-criteria {
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
.group-member-display {
|
||||
i[class*=fa-caret-] {
|
||||
float: right;
|
||||
|
@ -1029,47 +850,20 @@
|
|||
.criteria-heading {
|
||||
margin-top: 0;
|
||||
}
|
||||
.alpha-icon {
|
||||
display: inline;
|
||||
float: none !important;
|
||||
margin-left: 1rem;
|
||||
color: $orange;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
p {
|
||||
&.category {
|
||||
display: block;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
text-transform: none;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.bp3-popover-wrapper {
|
||||
float: right;
|
||||
}
|
||||
.fb-button {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.point-type-section,
|
||||
.criteria-checkbox-list-item {
|
||||
.fb-checkbox {
|
||||
display: inline;
|
||||
margin-right: 1rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
p {
|
||||
display: inline;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
input[type="text"] {
|
||||
width: 50%;
|
||||
height: 2rem;
|
||||
}
|
||||
}
|
||||
.point-type-checkboxes {
|
||||
.point-type-section {
|
||||
.fb-checkbox {
|
||||
display: inline;
|
||||
margin-right: 1rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
p {
|
||||
display: inline;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.point-type-checkbox {
|
||||
position: relative;
|
||||
height: 2rem;
|
||||
|
@ -1088,9 +882,19 @@
|
|||
}
|
||||
}
|
||||
.plant-criteria-options,
|
||||
.weed-criteria-options,
|
||||
.point-criteria-options,
|
||||
.tool-criteria-options {
|
||||
margin-left: 3rem;
|
||||
p {
|
||||
&.category {
|
||||
display: block;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
text-transform: none;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
hr {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
@ -1120,13 +924,7 @@
|
|||
margin-top: 1rem;
|
||||
}
|
||||
.day-criteria {
|
||||
.criteria-checkbox-list-item {
|
||||
margin-bottom: 1rem;
|
||||
p {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
.days-old-text {
|
||||
p {
|
||||
display: inline;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
@ -1134,7 +932,6 @@
|
|||
line-height: 1.75rem;
|
||||
}
|
||||
}
|
||||
.number-eq-criteria,
|
||||
.string-eq-criteria {
|
||||
margin-top: 1rem;
|
||||
.row {
|
||||
|
@ -1161,13 +958,19 @@
|
|||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
.fb-toggle-button {
|
||||
width: 85px;
|
||||
margin-top: 0;
|
||||
&.red {
|
||||
background: $dark_gray !important;
|
||||
}
|
||||
}
|
||||
.clear-criteria {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.basic,
|
||||
.advanced {
|
||||
margin-left: 1rem;
|
||||
.filter-search {
|
||||
height: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.day-criteria {
|
||||
.row {
|
||||
margin-left: 0;
|
||||
|
@ -1179,17 +982,6 @@
|
|||
}
|
||||
}
|
||||
.advanced {
|
||||
.bp3-popover-wrapper {
|
||||
display: inline;
|
||||
float: none;
|
||||
margin-left: 1rem;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.filter-search {
|
||||
.bp3-popover-wrapper {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
.row {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
@ -1207,28 +999,29 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.group-member-count-breakdown {
|
||||
margin-bottom: 1rem;
|
||||
.manual-group-member-count,
|
||||
.criteria-group-member-count {
|
||||
div {
|
||||
display: inline;
|
||||
padding: 0.25rem;
|
||||
font-size: 1.2rem;
|
||||
.criteria-point-count-breakdown {
|
||||
margin-bottom: 1rem;
|
||||
.manual-group-member-count,
|
||||
.criteria-group-member-count {
|
||||
margin-left: 2rem;
|
||||
div {
|
||||
display: inline;
|
||||
padding: 0.25rem;
|
||||
font-size: 1.2rem;
|
||||
border: 1px solid $panel_light_blue;
|
||||
}
|
||||
p {
|
||||
display: inline;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
.criteria-group-member-count {
|
||||
div {
|
||||
border: 1px solid gray;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
p {
|
||||
display: inline;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.criteria-options-menu {
|
||||
label {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1237,7 +1030,6 @@
|
|||
display: inline-block;
|
||||
.row {
|
||||
margin-left: 0;
|
||||
margin-right: -2.5rem;
|
||||
div[class*=col-] {
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
|
@ -1253,28 +1045,16 @@
|
|||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
button {
|
||||
margin-top: 2rem !important;
|
||||
}
|
||||
.edit-in-map {
|
||||
float: right;
|
||||
button {
|
||||
margin: 1rem !important;
|
||||
width: 5rem !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
label {
|
||||
margin-top: 1.1rem !important;
|
||||
}
|
||||
}
|
||||
.location-selection-warning {
|
||||
i,
|
||||
p {
|
||||
display: inline;
|
||||
margin-right: 1rem;
|
||||
color: $darkest_red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.weeds-inventory-panel,
|
||||
|
|
|
@ -1314,50 +1314,6 @@ ul {
|
|||
}
|
||||
}
|
||||
|
||||
.update-resource-step {
|
||||
.update-resource-step-resource {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.update-resource-pair {
|
||||
margin-top: 0;
|
||||
margin-right: -2rem;
|
||||
div[class*=col-] {
|
||||
padding: 0;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
.custom-meta-field {
|
||||
position: relative;
|
||||
input {
|
||||
height: 3rem;
|
||||
}
|
||||
.fa-undo {
|
||||
position: absolute;
|
||||
top: 0.65rem;
|
||||
right: 0.5rem;
|
||||
color: $medium_light_gray;
|
||||
&:hover {
|
||||
color: $dark_gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
.custom-field-warning {
|
||||
display: inline-block;
|
||||
margin-top: 0.5rem;
|
||||
i,
|
||||
p {
|
||||
display: inline;
|
||||
cursor: default !important;
|
||||
margin-right: 0.5rem;
|
||||
color: $darkest_red;
|
||||
}
|
||||
.did-you-mean {
|
||||
cursor: pointer !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.farmware-name-manual-input {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
@ -1468,11 +1424,6 @@ ul {
|
|||
button {
|
||||
float: none !important;
|
||||
}
|
||||
.bp3-popover-wrapper {
|
||||
display: inline;
|
||||
margin-left: 0.5rem;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.problem-alert {
|
||||
|
|
|
@ -127,16 +127,6 @@ select {
|
|||
background: $white;
|
||||
margin-top: 0;
|
||||
cursor: pointer;
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: $white;
|
||||
opacity: 0.5;
|
||||
}
|
||||
&:checked:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
|
|
@ -106,7 +106,7 @@
|
|||
&.take-photo-step {
|
||||
background: $brown;
|
||||
}
|
||||
&.update-resource-step {
|
||||
&.resource-update-step {
|
||||
background: $brown;
|
||||
}
|
||||
&.set-servo-angle-step {
|
||||
|
@ -136,9 +136,6 @@
|
|||
&.reboot-step {
|
||||
background: $brown;
|
||||
}
|
||||
&.shutdown-step {
|
||||
background: $brown;
|
||||
}
|
||||
&.unknown-step {
|
||||
background: $gray;
|
||||
}
|
||||
|
@ -229,7 +226,7 @@
|
|||
&.take-photo-step a {
|
||||
color: $dark_brown;
|
||||
}
|
||||
&.update-resource-step {
|
||||
&.resource-update-step {
|
||||
background: $light_brown;
|
||||
}
|
||||
&.set-servo-angle-step {
|
||||
|
@ -256,9 +253,6 @@
|
|||
&.emergency-stop-step {
|
||||
background: $light_red;
|
||||
}
|
||||
&.shutdown-step {
|
||||
background: $light_brown;
|
||||
}
|
||||
&.reboot-step {
|
||||
background: $light_brown;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
jest.mock("../../redux/store", () => ({ store: jest.fn() }));
|
||||
|
||||
import { botReducer, initialState } from "../reducer";
|
||||
import { Actions } from "../../constants";
|
||||
import { ControlPanelState, BotState } from "../interfaces";
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
jest.mock("../../actions", () => ({
|
||||
toggleControlPanel: jest.fn(),
|
||||
bulkToggleControlPanel: jest.fn(),
|
||||
}));
|
||||
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
const mockState = fakeState();
|
||||
jest.mock("../../../redux/store", () => ({
|
||||
store: { getState: () => mockState },
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
|
@ -16,7 +9,7 @@ import {
|
|||
} from "../maybe_highlight";
|
||||
import { DeviceSetting } from "../../../constants";
|
||||
import { panelState } from "../../../__test_support__/control_panel_state";
|
||||
import { toggleControlPanel, bulkToggleControlPanel } from "../../actions";
|
||||
import { toggleControlPanel } from "../../actions";
|
||||
|
||||
describe("<Highlight />", () => {
|
||||
const fakeProps = (): HighlightProps => ({
|
||||
|
@ -32,24 +25,6 @@ describe("<Highlight />", () => {
|
|||
wrapper.instance().componentDidMount();
|
||||
expect(wrapper.state().className).toEqual("unhighlight");
|
||||
});
|
||||
|
||||
it("doesn't hide: no search term", () => {
|
||||
mockState.resources.consumers.farm_designer.settingsSearchTerm = "";
|
||||
const wrapper = mount(<Highlight {...fakeProps()} />);
|
||||
expect(wrapper.find("div").first().props().hidden).toEqual(false);
|
||||
});
|
||||
|
||||
it("doesn't hide: matches search term", () => {
|
||||
mockState.resources.consumers.farm_designer.settingsSearchTerm = "motor";
|
||||
const wrapper = mount(<Highlight {...fakeProps()} />);
|
||||
expect(wrapper.find("div").first().props().hidden).toEqual(false);
|
||||
});
|
||||
|
||||
it("hides", () => {
|
||||
mockState.resources.consumers.farm_designer.settingsSearchTerm = "encoder";
|
||||
const wrapper = mount(<Highlight {...fakeProps()} />);
|
||||
expect(wrapper.find("div").first().props().hidden).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("maybeHighlight()", () => {
|
||||
|
@ -103,11 +78,4 @@ describe("maybeOpenPanel()", () => {
|
|||
maybeOpenPanel(panelState())(jest.fn());
|
||||
expect(toggleControlPanel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("closes other panels", () => {
|
||||
location.search = "?highlight=motors";
|
||||
maybeOpenPanel(panelState(), true)(jest.fn());
|
||||
expect(toggleControlPanel).toHaveBeenCalledWith("motors");
|
||||
expect(bulkToggleControlPanel).toHaveBeenCalledWith(false, true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,12 +9,11 @@ import { settingToggle } from "../../actions";
|
|||
import {
|
||||
buildResourceIndex,
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
import { DeviceSetting } from "../../../constants";
|
||||
|
||||
describe("<PinGuardMCUInputGroup/>", () => {
|
||||
const fakeProps = (): PinGuardMCUInputGroupProps => {
|
||||
return {
|
||||
label: DeviceSetting.pinGuard1,
|
||||
label: "Pin Guard 1",
|
||||
pinNumKey: "pin_guard_1_pin_nr",
|
||||
timeoutKey: "pin_guard_1_time_out",
|
||||
activeStateKey: "pin_guard_1_active_state",
|
||||
|
|
|
@ -13,12 +13,11 @@ import {
|
|||
import { TaggedFirmwareConfig } from "farmbot";
|
||||
import { FBSelect } from "../../../ui";
|
||||
import { updateMCU } from "../../actions";
|
||||
import { DeviceSetting } from "../../../constants";
|
||||
|
||||
describe("<PinNumberDropdown />", () => {
|
||||
const fakeProps =
|
||||
(firmwareConfig?: TaggedFirmwareConfig): PinGuardMCUInputGroupProps => ({
|
||||
label: DeviceSetting.pinGuard1,
|
||||
label: "Pin Guard 1",
|
||||
pinNumKey: "pin_guard_1_pin_nr",
|
||||
timeoutKey: "pin_guard_1_time_out",
|
||||
activeStateKey: "pin_guard_1_active_state",
|
||||
|
|
|
@ -43,6 +43,16 @@ describe("<BoardType/>", () => {
|
|||
expect(wrapper.text()).toContain("Farmduino");
|
||||
});
|
||||
|
||||
it("sets sending status", () => {
|
||||
const wrapper = mount<BoardType>(<BoardType {...fakeProps()} />);
|
||||
expect(wrapper.state().sending).toBeFalsy();
|
||||
const p = fakeProps();
|
||||
p.sourceFbosConfig = () => ({ value: true, consistent: false });
|
||||
wrapper.setProps(p);
|
||||
wrapper.mount();
|
||||
expect(wrapper.state().sending).toBeTruthy();
|
||||
});
|
||||
|
||||
it("calls updateConfig", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount<BoardType>(<BoardType {...p} />);
|
||||
|
|
|
@ -11,15 +11,12 @@ import { OsUpdateButton } from "../os_update_button";
|
|||
import { OsUpdateButtonProps } from "../interfaces";
|
||||
import { ShouldDisplay } from "../../../interfaces";
|
||||
import { Content } from "../../../../constants";
|
||||
import { ConfigurationName } from "farmbot";
|
||||
|
||||
const UPDATE_CHANNEL = "update_channel" as ConfigurationName;
|
||||
|
||||
describe("<OsUpdateButton/>", () => {
|
||||
beforeEach(() => {
|
||||
bot.currentOSVersion = "6.1.6";
|
||||
bot.hardware.informational_settings.controller_version = "6.1.6";
|
||||
(bot.hardware.configuration[UPDATE_CHANNEL] as string) = "stable";
|
||||
bot.hardware.configuration.beta_opt_in = false;
|
||||
});
|
||||
|
||||
const fakeProps = (): OsUpdateButtonProps => ({
|
||||
|
@ -36,6 +33,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
availableVersion: string | undefined;
|
||||
availableBetaVersion: string | undefined;
|
||||
availableBetaCommit: string | undefined;
|
||||
betaOptIn: boolean | undefined;
|
||||
onBeta: boolean | undefined;
|
||||
update_available?: boolean | undefined;
|
||||
shouldDisplay: ShouldDisplay;
|
||||
|
@ -48,6 +46,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
availableVersion: "6.1.6",
|
||||
availableBetaVersion: undefined,
|
||||
availableBetaCommit: undefined,
|
||||
betaOptIn: false,
|
||||
onBeta: false,
|
||||
shouldDisplay: () => false,
|
||||
update_channel: "stable",
|
||||
|
@ -105,7 +104,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
expected: Results) => {
|
||||
const {
|
||||
installedVersion, installedCommit, onBeta, update_available,
|
||||
availableVersion, availableBetaVersion, availableBetaCommit,
|
||||
availableVersion, availableBetaVersion, availableBetaCommit, betaOptIn,
|
||||
shouldDisplay, update_channel,
|
||||
} = testProps;
|
||||
bot.hardware.informational_settings.controller_version = installedVersion;
|
||||
|
@ -116,7 +115,9 @@ describe("<OsUpdateButton/>", () => {
|
|||
bot.currentOSVersion = availableVersion;
|
||||
bot.currentBetaOSVersion = availableBetaVersion;
|
||||
bot.currentBetaOSCommit = availableBetaCommit;
|
||||
(bot.hardware.configuration[UPDATE_CHANNEL] as string) = update_channel;
|
||||
bot.hardware.configuration.beta_opt_in = betaOptIn;
|
||||
// tslint:disable-next-line:no-any
|
||||
(bot.hardware.configuration as any).update_channel = update_channel;
|
||||
|
||||
const p = fakeProps();
|
||||
p.shouldDisplay = shouldDisplay;
|
||||
|
@ -155,7 +156,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.6";
|
||||
testProps.availableVersion = undefined;
|
||||
testProps.update_channel = "beta";
|
||||
testProps.betaOptIn = true;
|
||||
const expectedResults = cantConnect("release server");
|
||||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
@ -165,7 +166,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
testProps.installedVersion = "6.1.6";
|
||||
testProps.availableVersion = undefined;
|
||||
testProps.availableBetaVersion = "6.1.7-beta";
|
||||
testProps.update_channel = "beta";
|
||||
testProps.betaOptIn = true;
|
||||
const expectedResults = updateNeeded("6.1.7-beta");
|
||||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
@ -174,7 +175,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.6";
|
||||
testProps.availableBetaVersion = undefined;
|
||||
testProps.update_channel = "beta";
|
||||
testProps.betaOptIn = true;
|
||||
const expectedResults = upToDate("6.1.6");
|
||||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
@ -204,7 +205,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.5";
|
||||
testProps.availableBetaVersion = "7.0.0-beta";
|
||||
testProps.update_channel = "beta";
|
||||
testProps.betaOptIn = true;
|
||||
const expectedResults = updateNeeded("7.0.0-beta");
|
||||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
@ -213,7 +214,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.6";
|
||||
testProps.availableBetaVersion = "6.1.6-beta";
|
||||
testProps.update_channel = "beta";
|
||||
testProps.betaOptIn = true;
|
||||
const expectedResults = upToDate("6.1.6");
|
||||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
@ -222,7 +223,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.6";
|
||||
testProps.availableBetaVersion = "6.1.6-beta";
|
||||
testProps.update_channel = "beta";
|
||||
testProps.betaOptIn = true;
|
||||
testProps.onBeta = true;
|
||||
const expectedResults = updateNeeded("6.1.6");
|
||||
testButtonState(testProps, expectedResults);
|
||||
|
@ -232,7 +233,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.6";
|
||||
testProps.availableBetaVersion = "6.1.6-beta";
|
||||
testProps.update_channel = "stable";
|
||||
testProps.betaOptIn = false;
|
||||
testProps.onBeta = true;
|
||||
const expectedResults = updateNeeded("6.1.6");
|
||||
testButtonState(testProps, expectedResults);
|
||||
|
@ -242,7 +243,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.7";
|
||||
testProps.availableBetaVersion = "6.1.7-beta";
|
||||
testProps.update_channel = "beta";
|
||||
testProps.betaOptIn = true;
|
||||
testProps.onBeta = true;
|
||||
const expectedResults = upToDate("6.1.7-beta");
|
||||
testButtonState(testProps, expectedResults);
|
||||
|
@ -252,7 +253,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.7-beta";
|
||||
testProps.availableBetaVersion = "6.1.7-beta";
|
||||
testProps.update_channel = "beta";
|
||||
testProps.betaOptIn = true;
|
||||
const expectedResults = upToDate("6.1.7-beta");
|
||||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
@ -263,7 +264,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
testProps.installedCommit = "old commit";
|
||||
testProps.availableBetaVersion = "7.0.0-beta";
|
||||
testProps.availableBetaCommit = "new commit";
|
||||
testProps.update_channel = "beta";
|
||||
testProps.betaOptIn = true;
|
||||
testProps.onBeta = true;
|
||||
const expectedResults = updateNeeded("7.0.0-beta");
|
||||
testButtonState(testProps, expectedResults);
|
||||
|
@ -272,7 +273,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
it("handles installed version newer than available (beta enabled)", () => {
|
||||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.7";
|
||||
testProps.update_channel = "beta";
|
||||
testProps.betaOptIn = true;
|
||||
testProps.onBeta = false;
|
||||
testProps.availableBetaVersion = "6.1.7-beta";
|
||||
const expectedResults = upToDate("6.1.7-beta");
|
||||
|
@ -307,6 +308,16 @@ describe("<OsUpdateButton/>", () => {
|
|||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
||||
it("doesn't use update_channel value", () => {
|
||||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.6";
|
||||
testProps.shouldDisplay = () => false;
|
||||
testProps.update_channel = "beta";
|
||||
testProps.availableBetaVersion = "6.1.7-beta";
|
||||
const expectedResults = upToDate("6.1.6");
|
||||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
||||
it("compares release candidates: newer", () => {
|
||||
const testProps = defaultTestProps();
|
||||
testProps.availableVersion = "6.1.5";
|
||||
|
|
|
@ -13,7 +13,17 @@ import { Highlight } from "../maybe_highlight";
|
|||
import { DeviceSetting } from "../../../constants";
|
||||
import { DevSettings } from "../../../account/dev/dev_support";
|
||||
|
||||
export class BoardType extends React.Component<BoardTypeProps, {}> {
|
||||
interface BoardTypeState { sending: boolean }
|
||||
|
||||
export class BoardType extends React.Component<BoardTypeProps, BoardTypeState> {
|
||||
state = {
|
||||
sending: this.sending
|
||||
};
|
||||
|
||||
UNSAFE_componentWillReceiveProps() {
|
||||
this.setState({ sending: this.sending });
|
||||
}
|
||||
|
||||
get sending() {
|
||||
return !this.props.sourceFbosConfig("firmware_hardware").consistent;
|
||||
}
|
||||
|
@ -29,14 +39,15 @@ export class BoardType extends React.Component<BoardTypeProps, {}> {
|
|||
if (selectedItem && isFwHardwareValue(firmware_hardware)) {
|
||||
info(t("Sending firmware configuration..."), t("Sending"));
|
||||
this.props.dispatch(updateConfig({ firmware_hardware }));
|
||||
this.setState({ sending: true });
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
FirmwareSelection = () =>
|
||||
<FBSelect
|
||||
key={this.props.firmwareHardware + "" + this.sending}
|
||||
extraClass={this.sending ? "dim" : ""}
|
||||
key={this.props.firmwareHardware}
|
||||
extraClass={this.state.sending ? "dim" : ""}
|
||||
list={getFirmwareChoices()}
|
||||
selectedItem={this.selectedBoard}
|
||||
onChange={this.sendOffConfig} />
|
||||
|
|
|
@ -263,7 +263,7 @@ export function FbosDetails(props: FbosDetailsProps) {
|
|||
soc_temp, wifi_level, uptime, memory_usage, disk_usage, throttled,
|
||||
wifi_level_percent, cpu_usage, private_ip,
|
||||
} = props.botInfoSettings;
|
||||
const { last_ota, last_ota_checkup, fbos_version } = props.deviceAccount.body;
|
||||
const { last_ota, last_ota_checkup } = props.deviceAccount.body;
|
||||
const infoFwCommit = firmware_version?.includes(".") ? firmware_commit : "---";
|
||||
const firmwareCommit = firmware_version?.split("-")[1] || infoFwCommit;
|
||||
|
||||
|
@ -273,7 +273,6 @@ export function FbosDetails(props: FbosDetailsProps) {
|
|||
botToMqttLastSeen={props.botToMqttLastSeen}
|
||||
timeSettings={props.timeSettings}
|
||||
device={props.deviceAccount} />
|
||||
<p><b>{t("Version last seen")}: </b>{fbos_version}</p>
|
||||
<p><b>{t("Environment")}: </b>{env}</p>
|
||||
<CommitDisplay title={t("Commit")}
|
||||
repo={FarmBotRepo.FarmBotOS} commit={commit} />
|
||||
|
|
|
@ -8,8 +8,6 @@ import { FirmwareAlerts } from "../../../messages/alerts";
|
|||
import { TimeSettings } from "../../../interfaces";
|
||||
import { Alert } from "farmbot";
|
||||
import { isFwHardwareValue, boardType } from "../firmware_hardware_support";
|
||||
import { Help } from "../../../ui";
|
||||
import { ToolTips } from "../../../constants";
|
||||
|
||||
export interface FirmwareHardwareStatusIconProps {
|
||||
firmwareHardware: string | undefined;
|
||||
|
@ -61,13 +59,10 @@ export const FirmwareHardwareStatusDetails =
|
|||
(props: FirmwareHardwareStatusDetailsProps) => {
|
||||
return <div className="firmware-hardware-status-details">
|
||||
<label>{t("Web App")}</label>
|
||||
<Help text={ToolTips.FIRMWARE_VALUE_API} />
|
||||
<p>{lookup(props.apiFirmwareValue) || t("unknown")}</p>
|
||||
<label>{t("FarmBot OS")}</label>
|
||||
<Help text={ToolTips.FIRMWARE_VALUE_FBOS} />
|
||||
<p>{lookup(props.botFirmwareValue) || t("unknown")}</p>
|
||||
<label>{t("Arduino/Farmduino")}</label>
|
||||
<Help text={ToolTips.FIRMWARE_VALUE_MCU} />
|
||||
<p>{lookup(props.mcuFirmwareValue) || t("unknown")}</p>
|
||||
<FirmwareAlerts
|
||||
alerts={props.alerts}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { SemverResult, semverCompare } from "../../../util";
|
|||
import { OsUpdateButtonProps } from "./interfaces";
|
||||
import { checkControllerUpdates } from "../../actions";
|
||||
import { isString } from "lodash";
|
||||
import { BotState } from "../../interfaces";
|
||||
import { BotState, Feature } from "../../interfaces";
|
||||
import { Content } from "../../../constants";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
|
||||
|
@ -154,8 +154,9 @@ export const OsUpdateButton = (props: OsUpdateButtonProps) => {
|
|||
const { controller_version } = bot.hardware.informational_settings;
|
||||
|
||||
/** FBOS beta release opt-in setting. */
|
||||
const betaOptIn =
|
||||
sourceFbosConfig("update_channel" as ConfigurationName).value !== "stable";
|
||||
const betaOptIn = props.shouldDisplay(Feature.use_update_channel)
|
||||
? sourceFbosConfig("update_channel" as ConfigurationName).value !== "stable"
|
||||
: !!sourceFbosConfig("beta_opt_in").value;
|
||||
/** FBOS update availability. */
|
||||
const buttonStatusProps = buttonVersionStatus({ bot, betaOptIn });
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ export class HardwareSettings extends
|
|||
bot, dispatch, sourceFwConfig, controlPanelState, firmwareConfig,
|
||||
firmwareHardware, resources
|
||||
} = this.props;
|
||||
const botOnline = isBotOnlineFromState(bot);
|
||||
const botOnline = !isBotOnlineFromState(bot);
|
||||
const commonProps = { dispatch, controlPanelState };
|
||||
return <Widget className="hardware-widget">
|
||||
<WidgetHeader title={t("Hardware")} helpText={ToolTips.HW_SETTINGS}>
|
||||
|
|
|
@ -24,7 +24,7 @@ export function DangerZone(props: DangerZoneProps) {
|
|||
<Highlight settingName={DeviceSetting.resetHardwareParams}>
|
||||
<Row>
|
||||
<Col xs={newFormat ? 8 : 4}>
|
||||
<label style={{ lineHeight: "1.5rem" }}>
|
||||
<label>
|
||||
{t(DeviceSetting.resetHardwareParams)}
|
||||
</label>
|
||||
</Col>
|
||||
|
|
|
@ -44,7 +44,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
</Col>
|
||||
</Row>}
|
||||
<PinGuardMCUInputGroup
|
||||
label={DeviceSetting.pinGuard1}
|
||||
label={t("Pin Guard {{ num }}", { num: 1 })}
|
||||
pinNumKey={"pin_guard_1_pin_nr"}
|
||||
timeoutKey={"pin_guard_1_time_out"}
|
||||
activeStateKey={"pin_guard_1_active_state"}
|
||||
|
@ -52,7 +52,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
resources={resources}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<PinGuardMCUInputGroup
|
||||
label={DeviceSetting.pinGuard2}
|
||||
label={t("Pin Guard {{ num }}", { num: 2 })}
|
||||
pinNumKey={"pin_guard_2_pin_nr"}
|
||||
timeoutKey={"pin_guard_2_time_out"}
|
||||
activeStateKey={"pin_guard_2_active_state"}
|
||||
|
@ -60,7 +60,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
resources={resources}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<PinGuardMCUInputGroup
|
||||
label={DeviceSetting.pinGuard3}
|
||||
label={t("Pin Guard {{ num }}", { num: 3 })}
|
||||
pinNumKey={"pin_guard_3_pin_nr"}
|
||||
timeoutKey={"pin_guard_3_time_out"}
|
||||
activeStateKey={"pin_guard_3_active_state"}
|
||||
|
@ -68,7 +68,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
resources={resources}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<PinGuardMCUInputGroup
|
||||
label={DeviceSetting.pinGuard4}
|
||||
label={t("Pin Guard {{ num }}", { num: 4 })}
|
||||
pinNumKey={"pin_guard_4_pin_nr"}
|
||||
timeoutKey={"pin_guard_4_time_out"}
|
||||
activeStateKey={"pin_guard_4_active_state"}
|
||||
|
@ -76,7 +76,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
resources={resources}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<PinGuardMCUInputGroup
|
||||
label={DeviceSetting.pinGuard5}
|
||||
label={t("Pin Guard {{ num }}", { num: 5 })}
|
||||
pinNumKey={"pin_guard_5_pin_nr"}
|
||||
timeoutKey={"pin_guard_5_time_out"}
|
||||
activeStateKey={"pin_guard_5_active_state"}
|
||||
|
|
|
@ -2,32 +2,28 @@ import * as React from "react";
|
|||
import { Row, Col } from "../../../ui/index";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { DevSettings } from "../../../account/dev/dev_support";
|
||||
import { Highlight } from "../maybe_highlight";
|
||||
import { DeviceSetting } from "../../../constants";
|
||||
|
||||
export function SpacePanelHeader() {
|
||||
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||
const width = newFormat ? 4 : 2;
|
||||
const offset = newFormat ? 0 : 6;
|
||||
return <div className="label-headings">
|
||||
<Highlight settingName={DeviceSetting.axisHeadingLabels}>
|
||||
<Row>
|
||||
<Col xs={width} xsOffset={offset} className={"centered-button-div"}>
|
||||
<label>
|
||||
{t("X AXIS")}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={width} className={"centered-button-div"}>
|
||||
<label>
|
||||
{t("Y AXIS")}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={width} className={"centered-button-div"}>
|
||||
<label>
|
||||
{t("Z AXIS")}
|
||||
</label>
|
||||
</Col>
|
||||
</Row>
|
||||
</Highlight>
|
||||
<Row>
|
||||
<Col xs={width} xsOffset={offset} className={"centered-button-div"}>
|
||||
<label>
|
||||
{t("X AXIS")}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={width} className={"centered-button-div"}>
|
||||
<label>
|
||||
{t("Y AXIS")}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={width} className={"centered-button-div"}>
|
||||
<label>
|
||||
{t("Z AXIS")}
|
||||
</label>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ export interface NumericMCUInputGroupProps {
|
|||
export interface PinGuardMCUInputGroupProps {
|
||||
sourceFwConfig: SourceFwConfig;
|
||||
dispatch: Function;
|
||||
label: DeviceSetting;
|
||||
label: string;
|
||||
pinNumKey: McuParamName;
|
||||
timeoutKey: McuParamName;
|
||||
activeStateKey: McuParamName;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import * as React from "react";
|
||||
import { store } from "../../redux/store";
|
||||
import { ControlPanelState } from "../interfaces";
|
||||
import { toggleControlPanel, bulkToggleControlPanel } from "../actions";
|
||||
import { urlFriendly } from "../../util";
|
||||
|
@ -57,11 +56,6 @@ const ERROR_HANDLING_PANEL = [
|
|||
];
|
||||
const PIN_GUARD_PANEL = [
|
||||
DeviceSetting.pinGuard,
|
||||
DeviceSetting.pinGuard1,
|
||||
DeviceSetting.pinGuard2,
|
||||
DeviceSetting.pinGuard3,
|
||||
DeviceSetting.pinGuard4,
|
||||
DeviceSetting.pinGuard5,
|
||||
];
|
||||
const DANGER_ZONE_PANEL = [
|
||||
DeviceSetting.dangerZone,
|
||||
|
@ -69,8 +63,6 @@ const DANGER_ZONE_PANEL = [
|
|||
];
|
||||
const PIN_BINDINGS_PANEL = [
|
||||
DeviceSetting.pinBindings,
|
||||
DeviceSetting.savedPinBindings,
|
||||
DeviceSetting.addNewPinBinding,
|
||||
];
|
||||
const POWER_AND_RESET_PANEL = [
|
||||
DeviceSetting.powerAndReset,
|
||||
|
@ -191,7 +183,6 @@ export interface HighlightProps {
|
|||
| (React.ReactChild | false)[]
|
||||
| (React.ReactChild | React.ReactChild[])[];
|
||||
className?: string;
|
||||
searchTerm?: string;
|
||||
}
|
||||
|
||||
interface HighlightState {
|
||||
|
@ -209,19 +200,11 @@ export class Highlight extends React.Component<HighlightProps, HighlightState> {
|
|||
}
|
||||
}
|
||||
|
||||
get searchTerm() {
|
||||
const { resources } = store.getState();
|
||||
return resources.consumers.farm_designer.settingsSearchTerm;
|
||||
}
|
||||
|
||||
render() {
|
||||
const show = !this.searchTerm ||
|
||||
this.props.settingName.toLowerCase().includes(this.searchTerm);
|
||||
return <div className={[
|
||||
this.props.className,
|
||||
this.state.className,
|
||||
].join(" ")}
|
||||
hidden={!show}>
|
||||
].join(" ")}>
|
||||
{this.props.children}
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import { PinNumberDropdown } from "./pin_number_dropdown";
|
|||
import { DevSettings } from "../../account/dev/dev_support";
|
||||
import { ToolTips } from "../../constants";
|
||||
import { Position } from "@blueprintjs/core";
|
||||
import { Highlight } from "./maybe_highlight";
|
||||
|
||||
export class PinGuardMCUInputGroup
|
||||
extends React.Component<PinGuardMCUInputGroupProps> {
|
||||
|
@ -51,7 +50,7 @@ export class PinGuardMCUInputGroup
|
|||
? <Row>
|
||||
<Col xs={3}>
|
||||
<label>
|
||||
{t(label)}
|
||||
{label}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={3}>
|
||||
|
@ -64,48 +63,46 @@ export class PinGuardMCUInputGroup
|
|||
<this.State />
|
||||
</Col>
|
||||
</Row>
|
||||
: <Highlight settingName={label}>
|
||||
<div className={"pin-guard-input-row"}>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<label>
|
||||
{t(label)}
|
||||
</label>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col xs={5} xsOffset={1} className="no-pad">
|
||||
<label>
|
||||
{t("Pin Number")}
|
||||
</label>
|
||||
<Help text={ToolTips.PIN_GUARD_PIN_NUMBER}
|
||||
position={Position.TOP_RIGHT} />
|
||||
</Col>
|
||||
<Col xs={5} className="no-pad">
|
||||
<this.Number />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col xs={5} xsOffset={1} className="no-pad">
|
||||
<label>
|
||||
{t("Timeout (sec)")}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={5} className="no-pad">
|
||||
<this.Timeout />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col xs={5} xsOffset={1} className="no-pad">
|
||||
<label>
|
||||
{t("To State")}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={5} className="no-pad">
|
||||
<this.State />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</Highlight>;
|
||||
: <div className={"pin-guard-input-row"}>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<label>
|
||||
{label}
|
||||
</label>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col xs={5} xsOffset={1} className="no-pad">
|
||||
<label>
|
||||
{t("Pin Number")}
|
||||
</label>
|
||||
<Help text={ToolTips.PIN_GUARD_PIN_NUMBER}
|
||||
position={Position.TOP_RIGHT} />
|
||||
</Col>
|
||||
<Col xs={5} className="no-pad">
|
||||
<this.Number />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col xs={5} xsOffset={1} className="no-pad">
|
||||
<label>
|
||||
{t("Timeout (sec)")}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={5} className="no-pad">
|
||||
<this.Timeout />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col xs={5} xsOffset={1} className="no-pad">
|
||||
<label>
|
||||
{t("To State")}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={5} className="no-pad">
|
||||
<this.State />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,13 +41,6 @@ export function Diagnosis(props: DiagnosisProps) {
|
|||
<div className={"saucer-connector last " + diagnosisColor} />
|
||||
</Col>
|
||||
<Col xs={10} className={"connectivity-diagnosis"}>
|
||||
<p className="blinking">
|
||||
{t("Always")}
|
||||
<a className="blinking" href="/app/device?highlight=farmbot_os">
|
||||
<u>{t("upgrade FarmBot OS")}</u>
|
||||
</a>
|
||||
{t("before troubleshooting.")}
|
||||
</p>
|
||||
<p>
|
||||
{diagnose(props)}
|
||||
</p>
|
||||
|
|
|
@ -81,7 +81,6 @@ export enum Feature {
|
|||
ota_update_hour = "ota_update_hour",
|
||||
rpi_led_control = "rpi_led_control",
|
||||
sensors = "sensors",
|
||||
update_resource = "update_resource",
|
||||
use_update_channel = "use_update_channel",
|
||||
variables = "variables",
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ export const specialActionLabelLookup: { [x: string]: string } = {
|
|||
|
||||
export const specialActionList: DropDownItem[] =
|
||||
Object.values(PinBindingSpecialAction)
|
||||
.filter(action => action != PinBindingSpecialAction.dump_info)
|
||||
.map((action: PinBindingSpecialAction) =>
|
||||
({ label: specialActionLabelLookup[action], value: action }));
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ import {
|
|||
} from "farmbot/dist/resources/api_resources";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { DevSettings } from "../../account/dev/dev_support";
|
||||
import { DeviceSetting } from "../../constants";
|
||||
|
||||
export class PinBindingInputGroup
|
||||
extends React.Component<PinBindingInputGroupProps, PinBindingInputGroupState> {
|
||||
|
@ -130,7 +129,7 @@ export class PinBindingInputGroup
|
|||
render() {
|
||||
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||
return <div className="pin-binding-input-rows">
|
||||
{newFormat && <Row><label>{t(DeviceSetting.addNewPinBinding)}</label></Row>}
|
||||
{newFormat && <Row><label>{t("add new pin binding")}</label></Row>}
|
||||
{newFormat && <this.Number />}
|
||||
{newFormat && <Row>
|
||||
<Col xs={5}>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from "react";
|
||||
import { Row, Col, Help } from "../../ui";
|
||||
import { ToolTips, DeviceSetting } from "../../constants";
|
||||
import { ToolTips } from "../../constants";
|
||||
import { selectAllPinBindings } from "../../resources/selectors";
|
||||
import { PinBindingsContentProps, PinBindingListItems } from "./interfaces";
|
||||
import { PinBindingsList } from "./pin_bindings_list";
|
||||
|
@ -17,7 +17,6 @@ import {
|
|||
} from "farmbot/dist/resources/api_resources";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { DevSettings } from "../../account/dev/dev_support";
|
||||
import { Highlight } from "../components/maybe_highlight";
|
||||
|
||||
/** Width of UI columns in Pin Bindings widget. */
|
||||
export enum PinBindingColWidth {
|
||||
|
@ -74,38 +73,32 @@ export const PinBindingsContent = (props: PinBindingsContentProps) => {
|
|||
const pinBindings = apiPinBindings(resources);
|
||||
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||
return <div className="pin-bindings">
|
||||
<Highlight settingName={DeviceSetting.pinBindings}>
|
||||
<Row>
|
||||
{newFormat && <Help text={ToolTips.PIN_BINDINGS}
|
||||
position={Position.TOP_RIGHT} />}
|
||||
<StockPinBindingsButton
|
||||
dispatch={dispatch} firmwareHardware={firmwareHardware} />
|
||||
<Popover
|
||||
position={Position.TOP_RIGHT}
|
||||
interactionKind={PopoverInteractionKind.HOVER}
|
||||
portalClassName={"bindings-warning-icon"}
|
||||
popoverClassName={"help"}>
|
||||
<i className="fa fa-exclamation-triangle" />
|
||||
<div className={"pin-binding-warning"}>
|
||||
{t(ToolTips.PIN_BINDING_WARNING)}
|
||||
</div>
|
||||
</Popover>
|
||||
</Row>
|
||||
</Highlight>
|
||||
<Row>
|
||||
{newFormat && <Help text={ToolTips.PIN_BINDINGS}
|
||||
position={Position.TOP_RIGHT} />}
|
||||
<StockPinBindingsButton
|
||||
dispatch={dispatch} firmwareHardware={firmwareHardware} />
|
||||
<Popover
|
||||
position={Position.TOP_RIGHT}
|
||||
interactionKind={PopoverInteractionKind.HOVER}
|
||||
portalClassName={"bindings-warning-icon"}
|
||||
popoverClassName={"help"}>
|
||||
<i className="fa fa-exclamation-triangle" />
|
||||
<div className={"pin-binding-warning"}>
|
||||
{t(ToolTips.PIN_BINDING_WARNING)}
|
||||
</div>
|
||||
</Popover>
|
||||
</Row>
|
||||
<div className={"pin-bindings-list-and-input"}>
|
||||
{!newFormat && <PinBindingsListHeader />}
|
||||
<Highlight settingName={DeviceSetting.savedPinBindings}>
|
||||
<PinBindingsList
|
||||
pinBindings={pinBindings}
|
||||
dispatch={dispatch}
|
||||
resources={resources} />
|
||||
</Highlight>
|
||||
<Highlight settingName={DeviceSetting.addNewPinBinding}>
|
||||
<PinBindingInputGroup
|
||||
pinBindings={pinBindings}
|
||||
dispatch={dispatch}
|
||||
resources={resources} />
|
||||
</Highlight>
|
||||
<PinBindingsList
|
||||
pinBindings={pinBindings}
|
||||
dispatch={dispatch}
|
||||
resources={resources} />
|
||||
<PinBindingInputGroup
|
||||
pinBindings={pinBindings}
|
||||
dispatch={dispatch}
|
||||
resources={resources} />
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
|
|
@ -15,7 +15,6 @@ import { DevSettings } from "../../account/dev/dev_support";
|
|||
import {
|
||||
PinBindingType, PinBindingSpecialAction,
|
||||
} from "farmbot/dist/resources/api_resources";
|
||||
import { DeviceSetting } from "../../constants";
|
||||
|
||||
export const PinBindingsList = (props: PinBindingsListProps) => {
|
||||
const { pinBindings, resources, dispatch } = props;
|
||||
|
@ -42,7 +41,7 @@ export const PinBindingsList = (props: PinBindingsListProps) => {
|
|||
|
||||
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||
return <div className={"bindings-list"}>
|
||||
{newFormat && <Row><label>{t(DeviceSetting.savedPinBindings)}</label></Row>}
|
||||
{newFormat && <Row><label>{t("saved pin bindings")}</label></Row>}
|
||||
{pinBindings
|
||||
.sort((a, b) => sortByNameAndPin(a.pin_number, b.pin_number))
|
||||
.map(x => {
|
||||
|
|
|
@ -15,6 +15,7 @@ import * as React from "react";
|
|||
import { RawFarmDesigner as FarmDesigner } from "../index";
|
||||
import { mount } from "enzyme";
|
||||
import { Props } from "../interfaces";
|
||||
import { GardenMapLegendProps } from "../map/interfaces";
|
||||
import { bot } from "../../__test_support__/fake_state/bot";
|
||||
import {
|
||||
fakeImage, fakeWebAppConfig,
|
||||
|
@ -27,8 +28,6 @@ import {
|
|||
import { fakeState } from "../../__test_support__/fake_state";
|
||||
import { edit } from "../../api/crud";
|
||||
import { BooleanSetting } from "../../session_keys";
|
||||
import { GardenMapLegend } from "../map/legend/garden_map_legend";
|
||||
import { GardenMap } from "../map/garden_map";
|
||||
|
||||
describe("<FarmDesigner/>", () => {
|
||||
const fakeProps = (): Props => ({
|
||||
|
@ -37,7 +36,6 @@ describe("<FarmDesigner/>", () => {
|
|||
designer: fakeDesignerState(),
|
||||
hoveredPlant: undefined,
|
||||
genericPoints: [],
|
||||
weeds: [],
|
||||
allPoints: [],
|
||||
plants: [],
|
||||
toolSlots: [],
|
||||
|
@ -48,10 +46,7 @@ describe("<FarmDesigner/>", () => {
|
|||
raw_encoders: { x: undefined, y: undefined, z: undefined },
|
||||
},
|
||||
botMcuParams: bot.hardware.mcu_params,
|
||||
botSize: {
|
||||
x: { value: 3000, isDefault: true },
|
||||
y: { value: 1500, isDefault: true },
|
||||
},
|
||||
stepsPerMmXY: { x: undefined, y: undefined },
|
||||
peripherals: [],
|
||||
eStopStatus: false,
|
||||
latestImages: [],
|
||||
|
@ -72,7 +67,8 @@ describe("<FarmDesigner/>", () => {
|
|||
|
||||
it("loads default map settings", () => {
|
||||
const wrapper = mount(<FarmDesigner {...fakeProps()} />);
|
||||
const legendProps = wrapper.find(GardenMapLegend).props();
|
||||
const legendProps =
|
||||
wrapper.find("GardenMapLegend").props() as GardenMapLegendProps;
|
||||
expect(legendProps.legendMenuOpen).toBeFalsy();
|
||||
expect(legendProps.showPlants).toBeTruthy();
|
||||
expect(legendProps.showPoints).toBeTruthy();
|
||||
|
@ -80,7 +76,8 @@ describe("<FarmDesigner/>", () => {
|
|||
expect(legendProps.showFarmbot).toBeTruthy();
|
||||
expect(legendProps.showImages).toBeFalsy();
|
||||
expect(legendProps.imageAgeInfo).toEqual({ newestDate: "", toOldest: 1 });
|
||||
const gardenMapProps = wrapper.find(GardenMap).props();
|
||||
// tslint:disable-next-line:no-any
|
||||
const gardenMapProps = wrapper.find("GardenMap").props() as any;
|
||||
expect(gardenMapProps.gridSize.x).toEqual(2900);
|
||||
expect(gardenMapProps.gridSize.y).toEqual(1400);
|
||||
});
|
||||
|
@ -93,7 +90,8 @@ describe("<FarmDesigner/>", () => {
|
|||
image2.body.created_at = "2001-01-01T00:00:00.000Z";
|
||||
p.latestImages = [image1, image2];
|
||||
const wrapper = mount(<FarmDesigner {...p} />);
|
||||
const legendProps = wrapper.find(GardenMapLegend).props();
|
||||
const legendProps =
|
||||
wrapper.find("GardenMapLegend").props() as GardenMapLegendProps;
|
||||
expect(legendProps.imageAgeInfo)
|
||||
.toEqual({ newestDate: "2001-01-03T00:00:00.000Z", toOldest: 2 });
|
||||
});
|
||||
|
@ -139,18 +137,4 @@ describe("<FarmDesigner/>", () => {
|
|||
bot_origin_quadrant: 2
|
||||
});
|
||||
});
|
||||
|
||||
it("initializes setting", () => {
|
||||
const p = fakeProps();
|
||||
p.getConfigValue = () => false;
|
||||
const i = new FarmDesigner(p);
|
||||
expect(i.initializeSetting(BooleanSetting.show_farmbot, true)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("gets bot origin quadrant", () => {
|
||||
const p = fakeProps();
|
||||
p.getConfigValue = () => 1;
|
||||
const i = new FarmDesigner(p);
|
||||
expect(i.getBotOriginQuadrant()).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -33,7 +33,7 @@ describe("<MoveTo />", () => {
|
|||
it("moves to location: bot's current z value", () => {
|
||||
const wrapper = mount(<MoveTo {...fakeProps()} />);
|
||||
wrapper.find("button").simulate("click");
|
||||
expect(mockDevice.moveAbsolute).toHaveBeenCalledWith({ x: 1, y: 2, z: 3 });
|
||||
expect(mockDevice.moveAbsolute).toHaveBeenCalledWith({ x: 1, y: 2, z: 30 });
|
||||
});
|
||||
|
||||
it("goes back", () => {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { designer } from "../reducer";
|
|||
import { Actions } from "../../constants";
|
||||
import { ReduxAction } from "../../redux/interfaces";
|
||||
import {
|
||||
HoveredPlantPayl, DrawnPointPayl, CropLiveSearchResult, DrawnWeedPayl,
|
||||
HoveredPlantPayl, CurrentPointPayl, CropLiveSearchResult,
|
||||
} from "../interfaces";
|
||||
import { BotPosition } from "../../devices/interfaces";
|
||||
import {
|
||||
|
@ -10,7 +10,6 @@ import {
|
|||
} from "../../__test_support__/fake_crop_search_result";
|
||||
import { fakeDesignerState } from "../../__test_support__/fake_designer_state";
|
||||
import { PointGroupSortType } from "farmbot/dist/resources/api_resources";
|
||||
import { PointType } from "farmbot";
|
||||
|
||||
describe("designer reducer", () => {
|
||||
const oldState = fakeDesignerState;
|
||||
|
@ -25,22 +24,13 @@ describe("designer reducer", () => {
|
|||
expect(newState.cropSearchInProgress).toEqual(true);
|
||||
});
|
||||
|
||||
it("selects points", () => {
|
||||
it("selects plants", () => {
|
||||
const action: ReduxAction<string[]> = {
|
||||
type: Actions.SELECT_POINT,
|
||||
payload: ["pointUuid"]
|
||||
type: Actions.SELECT_PLANT,
|
||||
payload: ["plantUuid"]
|
||||
};
|
||||
const newState = designer(oldState(), action);
|
||||
expect(newState.selectedPoints).toEqual(["pointUuid"]);
|
||||
});
|
||||
|
||||
it("sets selection point type", () => {
|
||||
const action: ReduxAction<PointType[] | undefined> = {
|
||||
type: Actions.SET_SELECTION_POINT_TYPE,
|
||||
payload: ["Plant"],
|
||||
};
|
||||
const newState = designer(oldState(), action);
|
||||
expect(newState.selectionPointType).toEqual(["Plant"]);
|
||||
expect(newState.selectedPlants).toEqual(["plantUuid"]);
|
||||
});
|
||||
|
||||
it("sets hovered plant", () => {
|
||||
|
@ -94,49 +84,25 @@ describe("designer reducer", () => {
|
|||
});
|
||||
|
||||
it("sets current point data", () => {
|
||||
const action: ReduxAction<DrawnPointPayl> = {
|
||||
type: Actions.SET_DRAWN_POINT_DATA,
|
||||
const action: ReduxAction<CurrentPointPayl> = {
|
||||
type: Actions.SET_CURRENT_POINT_DATA,
|
||||
payload: { cx: 10, cy: 20, r: 30, color: "red" }
|
||||
};
|
||||
const newState = designer(oldState(), action);
|
||||
expect(newState.drawnPoint).toEqual({
|
||||
expect(newState.currentPoint).toEqual({
|
||||
cx: 10, cy: 20, r: 30, color: "red"
|
||||
});
|
||||
});
|
||||
|
||||
it("uses current point color", () => {
|
||||
const action: ReduxAction<DrawnPointPayl> = {
|
||||
type: Actions.SET_DRAWN_POINT_DATA,
|
||||
const action: ReduxAction<CurrentPointPayl> = {
|
||||
type: Actions.SET_CURRENT_POINT_DATA,
|
||||
payload: { cx: 10, cy: 20, r: 30 }
|
||||
};
|
||||
const state = oldState();
|
||||
state.drawnPoint = { cx: 0, cy: 0, r: 0, color: "red" };
|
||||
state.currentPoint = { cx: 0, cy: 0, r: 0, color: "red" };
|
||||
const newState = designer(state, action);
|
||||
expect(newState.drawnPoint).toEqual({
|
||||
cx: 10, cy: 20, r: 30, color: "red"
|
||||
});
|
||||
});
|
||||
|
||||
it("sets current weed data", () => {
|
||||
const action: ReduxAction<DrawnWeedPayl> = {
|
||||
type: Actions.SET_DRAWN_WEED_DATA,
|
||||
payload: { cx: 10, cy: 20, r: 30, color: "red" }
|
||||
};
|
||||
const newState = designer(oldState(), action);
|
||||
expect(newState.drawnWeed).toEqual({
|
||||
cx: 10, cy: 20, r: 30, color: "red"
|
||||
});
|
||||
});
|
||||
|
||||
it("uses current weed color", () => {
|
||||
const action: ReduxAction<DrawnWeedPayl> = {
|
||||
type: Actions.SET_DRAWN_WEED_DATA,
|
||||
payload: { cx: 10, cy: 20, r: 30 }
|
||||
};
|
||||
const state = oldState();
|
||||
state.drawnWeed = { cx: 0, cy: 0, r: 0, color: "red" };
|
||||
const newState = designer(state, action);
|
||||
expect(newState.drawnWeed).toEqual({
|
||||
expect(newState.currentPoint).toEqual({
|
||||
cx: 10, cy: 20, r: 30, color: "red"
|
||||
});
|
||||
});
|
||||
|
@ -190,24 +156,4 @@ describe("designer reducer", () => {
|
|||
const newState = designer(state, action);
|
||||
expect(newState.tryGroupSortType).toEqual("random");
|
||||
});
|
||||
|
||||
it("sets settings search term", () => {
|
||||
const state = oldState();
|
||||
state.settingsSearchTerm = "";
|
||||
const action: ReduxAction<string> = {
|
||||
type: Actions.SET_SETTINGS_SEARCH_TERM, payload: "random"
|
||||
};
|
||||
const newState = designer(state, action);
|
||||
expect(newState.settingsSearchTerm).toEqual("random");
|
||||
});
|
||||
|
||||
it("enables edit group area in map mode", () => {
|
||||
const state = oldState();
|
||||
state.editGroupAreaInMap = false;
|
||||
const action: ReduxAction<boolean> = {
|
||||
type: Actions.EDIT_GROUP_AREA_IN_MAP, payload: true
|
||||
};
|
||||
const newState = designer(state, action);
|
||||
expect(newState.editGroupAreaInMap).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
jest.mock("../../config_storage/actions", () => ({
|
||||
getWebAppConfigValue: jest.fn(x => { x(); return jest.fn(() => true); }),
|
||||
setWebAppConfigValue: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount, ReactWrapper } from "enzyme";
|
||||
import {
|
||||
RawDesignerSettings as DesignerSettings, DesignerSettingsProps,
|
||||
mapStateToProps,
|
||||
} from "../settings";
|
||||
import { fakeState } from "../../__test_support__/fake_state";
|
||||
import { BooleanSetting, NumericSetting } from "../../session_keys";
|
||||
import { setWebAppConfigValue } from "../../config_storage/actions";
|
||||
|
||||
const getSetting =
|
||||
(wrapper: ReactWrapper, position: number, containsString: string) => {
|
||||
const setting = wrapper.find(".designer-setting").at(position);
|
||||
expect(setting.text().toLowerCase())
|
||||
.toContain(containsString.toLowerCase());
|
||||
return setting;
|
||||
};
|
||||
|
||||
describe("<DesignerSettings />", () => {
|
||||
const fakeProps = (): DesignerSettingsProps => ({
|
||||
dispatch: jest.fn(),
|
||||
getConfigValue: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders settings", () => {
|
||||
const wrapper = mount(<DesignerSettings {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("size");
|
||||
const settings = wrapper.find(".designer-setting");
|
||||
expect(settings.length).toEqual(7);
|
||||
});
|
||||
|
||||
it("renders defaultOn setting", () => {
|
||||
const p = fakeProps();
|
||||
p.getConfigValue = () => undefined;
|
||||
const wrapper = mount(<DesignerSettings {...p} />);
|
||||
const confirmDeletion = getSetting(wrapper, 6, "confirm plant");
|
||||
expect(confirmDeletion.find("button").text()).toEqual("on");
|
||||
});
|
||||
|
||||
it("toggles setting", () => {
|
||||
const wrapper = mount(<DesignerSettings {...fakeProps()} />);
|
||||
const trailSetting = getSetting(wrapper, 1, "trail");
|
||||
trailSetting.find("button").simulate("click");
|
||||
expect(setWebAppConfigValue)
|
||||
.toHaveBeenCalledWith(BooleanSetting.display_trail, true);
|
||||
});
|
||||
|
||||
it("changes origin", () => {
|
||||
const p = fakeProps();
|
||||
p.getConfigValue = () => 2;
|
||||
const wrapper = mount(<DesignerSettings {...p} />);
|
||||
const originSetting = getSetting(wrapper, 5, "origin");
|
||||
originSetting.find("div").last().simulate("click");
|
||||
expect(setWebAppConfigValue).toHaveBeenCalledWith(
|
||||
NumericSetting.bot_origin_quadrant, 4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const props = mapStateToProps(fakeState());
|
||||
const value = props.getConfigValue(BooleanSetting.show_plants);
|
||||
expect(value).toEqual(true);
|
||||
});
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
import { mapStateToProps, getPlants, botSize } from "../state_to_props";
|
||||
import { mapStateToProps, getPlants } from "../state_to_props";
|
||||
import { fakeState } from "../../__test_support__/fake_state";
|
||||
import {
|
||||
buildResourceIndex, fakeDevice,
|
||||
|
@ -11,13 +11,14 @@ import {
|
|||
fakeWebAppConfig,
|
||||
fakeFarmwareEnv,
|
||||
fakeSensorReading,
|
||||
fakeFirmwareConfig,
|
||||
} from "../../__test_support__/fake_state/resources";
|
||||
import { WebAppConfig } from "farmbot/dist/resources/configs/web_app";
|
||||
import { generateUuid } from "../../resources/util";
|
||||
import { DevSettings } from "../../account/dev/dev_support";
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
const DISCARDED_AT = "2018-01-01T00:00:00.000Z";
|
||||
|
||||
it("hovered plantUUID is undefined", () => {
|
||||
const state = fakeState();
|
||||
state.resources.consumers.farm_designer.hoveredPlant = {
|
||||
|
@ -39,11 +40,18 @@ describe("mapStateToProps()", () => {
|
|||
checkValue(2, true);
|
||||
});
|
||||
|
||||
it("stepsPerMm is defined", () => {
|
||||
const state = fakeState();
|
||||
state.bot.hardware.mcu_params.movement_step_per_mm_x = 3;
|
||||
state.bot.hardware.mcu_params.movement_step_per_mm_y = 4;
|
||||
expect(mapStateToProps(state).stepsPerMmXY).toEqual({ x: 3, y: 4 });
|
||||
});
|
||||
|
||||
it("returns selected plant", () => {
|
||||
const state = fakeState();
|
||||
state.resources = buildResourceIndex([fakePlant(), fakeDevice()]);
|
||||
const plantUuid = Object.keys(state.resources.index.byKind["Point"])[0];
|
||||
state.resources.consumers.farm_designer.selectedPoints = [plantUuid];
|
||||
state.resources.consumers.farm_designer.selectedPlants = [plantUuid];
|
||||
expect(mapStateToProps(state).selectedPlant).toEqual(
|
||||
expect.objectContaining({ uuid: plantUuid }));
|
||||
});
|
||||
|
@ -53,8 +61,11 @@ describe("mapStateToProps()", () => {
|
|||
const webAppConfig = fakeWebAppConfig();
|
||||
(webAppConfig.body as WebAppConfig).show_historic_points = true;
|
||||
const point1 = fakePoint();
|
||||
point1.body.discarded_at = undefined;
|
||||
const point2 = fakePoint();
|
||||
point2.body.discarded_at = DISCARDED_AT;
|
||||
const point3 = fakePoint();
|
||||
point3.body.discarded_at = DISCARDED_AT;
|
||||
state.resources = buildResourceIndex([
|
||||
webAppConfig, point1, point2, point3, fakeDevice(),
|
||||
]);
|
||||
|
@ -66,12 +77,15 @@ describe("mapStateToProps()", () => {
|
|||
const webAppConfig = fakeWebAppConfig();
|
||||
(webAppConfig.body as WebAppConfig).show_historic_points = false;
|
||||
const point1 = fakePoint();
|
||||
point1.body.discarded_at = undefined;
|
||||
const point2 = fakePoint();
|
||||
point2.body.discarded_at = DISCARDED_AT;
|
||||
const point3 = fakePoint();
|
||||
point3.body.discarded_at = DISCARDED_AT;
|
||||
state.resources = buildResourceIndex([
|
||||
webAppConfig, point1, point2, point3, fakeDevice(),
|
||||
]);
|
||||
expect(mapStateToProps(state).genericPoints.length).toEqual(3);
|
||||
expect(mapStateToProps(state).genericPoints.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("returns sensor readings", () => {
|
||||
|
@ -130,45 +144,3 @@ describe("getPlants()", () => {
|
|||
expect.objectContaining({ rotation: "15" }));
|
||||
});
|
||||
});
|
||||
|
||||
describe("botSize()", () => {
|
||||
it("returns default bot size", () => {
|
||||
const state = fakeState();
|
||||
expect(botSize(state)).toEqual({
|
||||
x: { value: 2900, isDefault: true },
|
||||
y: { value: 1400, isDefault: true },
|
||||
});
|
||||
});
|
||||
|
||||
it("returns map setting bot size", () => {
|
||||
const state = fakeState();
|
||||
const webAppConfig = fakeWebAppConfig();
|
||||
webAppConfig.body.map_size_x = 1000;
|
||||
webAppConfig.body.map_size_y = 1000;
|
||||
state.resources = buildResourceIndex([fakeDevice(), webAppConfig]);
|
||||
expect(botSize(state)).toEqual({
|
||||
x: { value: 1000, isDefault: true },
|
||||
y: { value: 1000, isDefault: true },
|
||||
});
|
||||
});
|
||||
|
||||
it("returns axis length setting bot size", () => {
|
||||
const state = fakeState();
|
||||
const firmwareConfig = fakeFirmwareConfig();
|
||||
firmwareConfig.body.movement_step_per_mm_x = 2;
|
||||
firmwareConfig.body.movement_step_per_mm_y = 4;
|
||||
firmwareConfig.body.movement_stop_at_max_x = 1;
|
||||
firmwareConfig.body.movement_stop_at_max_y = 1;
|
||||
firmwareConfig.body.movement_axis_nr_steps_x = 100;
|
||||
firmwareConfig.body.movement_axis_nr_steps_y = 100;
|
||||
const webAppConfig = fakeWebAppConfig();
|
||||
webAppConfig.body.map_size_x = 1000;
|
||||
webAppConfig.body.map_size_y = 1000;
|
||||
state.resources = buildResourceIndex([
|
||||
fakeDevice(), firmwareConfig, webAppConfig]);
|
||||
expect(mapStateToProps(state).botSize).toEqual({
|
||||
x: { value: 50, isDefault: false },
|
||||
y: { value: 25, isDefault: false },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -81,12 +81,21 @@ interface DesignerPanelTopProps {
|
|||
onClick?(): void;
|
||||
title?: string;
|
||||
children?: React.ReactNode;
|
||||
noIcon?: boolean;
|
||||
}
|
||||
|
||||
export const DesignerPanelTop = (props: DesignerPanelTopProps) => {
|
||||
const withBtn = !!props.linkTo || !!props.onClick;
|
||||
return <div className={`panel-top ${withBtn ? "with-button" : ""}`}>
|
||||
{props.children}
|
||||
<div className="thin-search-wrapper">
|
||||
<div className="text-input-wrapper">
|
||||
{!props.noIcon &&
|
||||
<i className="fa fa-search" />}
|
||||
<ErrorBoundary>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
{props.onClick &&
|
||||
<a>
|
||||
<div className={`fb-button panel-${TAB_COLOR[props.panel]}`}
|
||||
|
|
|
@ -223,7 +223,7 @@ describe("<EditFEForm />", () => {
|
|||
]}
|
||||
findExecutable={jest.fn(() => seq)}
|
||||
dispatch={jest.fn()}
|
||||
repeatOptions={repeatOptions()}
|
||||
repeatOptions={repeatOptions}
|
||||
timeSettings={fakeTimeSettings()}
|
||||
autoSyncEnabled={false}
|
||||
resources={buildResourceIndex([]).index}
|
||||
|
@ -545,10 +545,7 @@ describe("<RepeatForm />", () => {
|
|||
const fakeProps = (): RepeatFormProps => ({
|
||||
isRegimen: false,
|
||||
fieldGet: jest.fn(key =>
|
||||
"" + ({
|
||||
endDate: "2017-07-26", endTime: "08:57",
|
||||
startDate: "2017-07-25", startTime: "08:57"
|
||||
} as FarmEventViewModel)[key]),
|
||||
"" + ({ endDate: "2017-07-26" } as FarmEventViewModel)[key]),
|
||||
fieldSet: jest.fn(),
|
||||
timeSettings: fakeTimeSettings(),
|
||||
});
|
||||
|
|
|
@ -26,7 +26,7 @@ export interface FarmEventRepeatFormProps {
|
|||
}
|
||||
|
||||
const indexKey: keyof DropDownItem = "value";
|
||||
const OPTN_LOOKUP = () => keyBy(repeatOptions(), indexKey);
|
||||
const OPTN_LOOKUP = keyBy(repeatOptions, indexKey);
|
||||
|
||||
export function FarmEventRepeatForm(props: FarmEventRepeatFormProps) {
|
||||
const { disabled, fieldSet, repeat, endDate, endTime, timeUnit } = props;
|
||||
|
@ -50,9 +50,9 @@ export function FarmEventRepeatForm(props: FarmEventRepeatFormProps) {
|
|||
</Col>
|
||||
<Col xs={8}>
|
||||
<FBSelect
|
||||
list={repeatOptions()}
|
||||
list={repeatOptions}
|
||||
onChange={ddi => fieldSet("timeUnit", "" + ddi.value)}
|
||||
selectedItem={OPTN_LOOKUP()[timeUnit] || OPTN_LOOKUP()["daily"]} />
|
||||
selectedItem={OPTN_LOOKUP[timeUnit] || OPTN_LOOKUP["daily"]} />
|
||||
</Col>
|
||||
</Row>
|
||||
<label>
|
||||
|
|
|
@ -16,7 +16,6 @@ import {
|
|||
} from "../../ui/empty_state_wrapper";
|
||||
import { some, uniq, map, sortBy } from "lodash";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { SearchField } from "../../ui/search_field";
|
||||
|
||||
const filterSearch = (term: string) => (item: CalendarOccurrence) =>
|
||||
item.heading.toLowerCase().includes(term)
|
||||
|
@ -106,12 +105,14 @@ export class PureFarmEvents
|
|||
<DesignerPanelTop
|
||||
panel={Panel.FarmEvents}
|
||||
linkTo={"/app/designer/events/add"}
|
||||
title={t("Add event")}>
|
||||
<SearchField searchTerm={this.state.searchTerm}
|
||||
customLeftIcon={
|
||||
<i className="fa fa-calendar" onClick={this.resetCalendar} />}
|
||||
placeholder={t("Search your events...")}
|
||||
onChange={searchTerm => this.setState({ searchTerm })} />
|
||||
title={t("Add event")}
|
||||
noIcon={true}>
|
||||
<i className="fa fa-calendar" onClick={this.resetCalendar} />
|
||||
<input
|
||||
name="searchTerm"
|
||||
value={this.state.searchTerm}
|
||||
onChange={e => this.setState({ searchTerm: e.currentTarget.value })}
|
||||
placeholder={t("Search your events...")} />
|
||||
</DesignerPanelTop>
|
||||
<DesignerPanelContent panelName={"farm-event"}>
|
||||
<div className="farm-events">
|
||||
|
|
|
@ -41,7 +41,7 @@ export const formatDate = (input: string, timeSettings: TimeSettings) => {
|
|||
return moment(iso).utcOffset(timeSettings.utcOffset).format("YYYY-MM-DD");
|
||||
};
|
||||
|
||||
export const repeatOptions = () => [
|
||||
export const repeatOptions = [
|
||||
{ label: t("Minutes"), value: "minutely", name: "time_unit" },
|
||||
{ label: t("Hours"), value: "hourly", name: "time_unit" },
|
||||
{ label: t("Days"), value: "daily", name: "time_unit" },
|
||||
|
@ -147,7 +147,7 @@ export function mapStateToPropsAddEdit(props: Everything): AddEditFarmEventProps
|
|||
sequencesById,
|
||||
farmEventsById,
|
||||
executableOptions: executableList,
|
||||
repeatOptions: repeatOptions(),
|
||||
repeatOptions,
|
||||
handleTime,
|
||||
farmEvents,
|
||||
getFarmEvent,
|
||||
|
|
|
@ -11,7 +11,8 @@ import { NumericSetting, BooleanSetting } from "../session_keys";
|
|||
import { isUndefined, last, isFinite } from "lodash";
|
||||
import { AxisNumberProperty, BotSize } from "./map/interfaces";
|
||||
import {
|
||||
round, getPanelStatus, MapPanelStatus, mapPanelClassName, getMapPadding,
|
||||
getBotSize, round, getPanelStatus, MapPanelStatus, mapPanelClassName,
|
||||
getMapPadding,
|
||||
} from "./map/util";
|
||||
import {
|
||||
calcZoomLevel, getZoomLevelIndex, saveZoomLevelIndex,
|
||||
|
@ -69,7 +70,6 @@ export class RawFarmDesigner extends React.Component<Props, Partial<State>> {
|
|||
legend_menu_open: init(BooleanSetting.legend_menu_open, false),
|
||||
show_plants: init(BooleanSetting.show_plants, true),
|
||||
show_points: init(BooleanSetting.show_points, true),
|
||||
show_weeds: init(BooleanSetting.show_weeds, true),
|
||||
show_spread: init(BooleanSetting.show_spread, false),
|
||||
show_farmbot: init(BooleanSetting.show_farmbot, true),
|
||||
show_images: init(BooleanSetting.show_images, false),
|
||||
|
@ -116,7 +116,6 @@ export class RawFarmDesigner extends React.Component<Props, Partial<State>> {
|
|||
legend_menu_open,
|
||||
show_plants,
|
||||
show_points,
|
||||
show_weeds,
|
||||
show_spread,
|
||||
show_farmbot,
|
||||
show_images,
|
||||
|
@ -125,6 +124,11 @@ export class RawFarmDesigner extends React.Component<Props, Partial<State>> {
|
|||
zoom_level
|
||||
} = this.state;
|
||||
|
||||
const botSize = getBotSize(
|
||||
this.props.botMcuParams,
|
||||
this.props.stepsPerMmXY,
|
||||
getDefaultAxisLength(this.props.getConfigValue));
|
||||
|
||||
const stopAtHome = {
|
||||
x: !!this.props.botMcuParams.movement_stop_at_home_x,
|
||||
y: !!this.props.botMcuParams.movement_stop_at_home_y
|
||||
|
@ -151,7 +155,6 @@ export class RawFarmDesigner extends React.Component<Props, Partial<State>> {
|
|||
legendMenuOpen={legend_menu_open}
|
||||
showPlants={show_plants}
|
||||
showPoints={show_points}
|
||||
showWeeds={show_weeds}
|
||||
showSpread={show_spread}
|
||||
showFarmbot={show_farmbot}
|
||||
showImages={show_images}
|
||||
|
@ -161,7 +164,6 @@ export class RawFarmDesigner extends React.Component<Props, Partial<State>> {
|
|||
dispatch={this.props.dispatch}
|
||||
timeSettings={this.props.timeSettings}
|
||||
getConfigValue={this.props.getConfigValue}
|
||||
shouldDisplay={this.props.shouldDisplay}
|
||||
imageAgeInfo={imageAgeInfo} />
|
||||
|
||||
<DesignerNavTabs hidden={!(getPanelStatus() === MapPanelStatus.closed)} />
|
||||
|
@ -179,7 +181,6 @@ export class RawFarmDesigner extends React.Component<Props, Partial<State>> {
|
|||
<GardenMap
|
||||
showPoints={show_points}
|
||||
showPlants={show_plants}
|
||||
showWeeds={show_weeds}
|
||||
showSpread={show_spread}
|
||||
showFarmbot={show_farmbot}
|
||||
showImages={show_images}
|
||||
|
@ -191,16 +192,15 @@ export class RawFarmDesigner extends React.Component<Props, Partial<State>> {
|
|||
designer={this.props.designer}
|
||||
plants={this.props.plants}
|
||||
genericPoints={this.props.genericPoints}
|
||||
weeds={this.props.weeds}
|
||||
allPoints={this.props.allPoints}
|
||||
toolSlots={this.props.toolSlots}
|
||||
botLocationData={this.props.botLocationData}
|
||||
botSize={this.props.botSize}
|
||||
botSize={botSize}
|
||||
stopAtHome={stopAtHome}
|
||||
hoveredPlant={this.props.hoveredPlant}
|
||||
zoomLvl={zoom_level}
|
||||
botOriginQuadrant={this.getBotOriginQuadrant()}
|
||||
gridSize={getGridSize(this.props.getConfigValue, this.props.botSize)}
|
||||
gridSize={getGridSize(this.props.getConfigValue, botSize)}
|
||||
gridOffset={gridOffset}
|
||||
peripherals={this.props.peripherals}
|
||||
eStopStatus={this.props.eStopStatus}
|
||||
|
|
|
@ -11,12 +11,10 @@ import {
|
|||
TaggedSensor,
|
||||
TaggedPoint,
|
||||
TaggedPointGroup,
|
||||
TaggedWeedPointer,
|
||||
PointType,
|
||||
} from "farmbot";
|
||||
import { SlotWithTool, ResourceIndex, UUID } from "../resources/interfaces";
|
||||
import { SlotWithTool, ResourceIndex } from "../resources/interfaces";
|
||||
import {
|
||||
BotPosition, BotLocationData, ShouldDisplay,
|
||||
BotPosition, StepsPerMmXY, BotLocationData, ShouldDisplay,
|
||||
} from "../devices/interfaces";
|
||||
import { isNumber } from "lodash";
|
||||
import { McuParams, TaggedCrop } from "farmbot";
|
||||
|
@ -50,7 +48,6 @@ export interface State extends TypeCheckerHint {
|
|||
legend_menu_open: boolean;
|
||||
show_plants: boolean;
|
||||
show_points: boolean;
|
||||
show_weeds: boolean;
|
||||
show_spread: boolean;
|
||||
show_farmbot: boolean;
|
||||
show_images: boolean;
|
||||
|
@ -66,14 +63,13 @@ export interface Props {
|
|||
designer: DesignerState;
|
||||
hoveredPlant: TaggedPlant | undefined;
|
||||
genericPoints: TaggedGenericPointer[];
|
||||
weeds: TaggedWeedPointer[];
|
||||
allPoints: TaggedPoint[];
|
||||
plants: TaggedPlant[];
|
||||
toolSlots: SlotWithTool[];
|
||||
crops: TaggedCrop[];
|
||||
botLocationData: BotLocationData;
|
||||
botMcuParams: McuParams;
|
||||
botSize: BotSize;
|
||||
stepsPerMmXY: StepsPerMmXY;
|
||||
peripherals: { label: string, value: boolean }[];
|
||||
eStopStatus: boolean;
|
||||
latestImages: TaggedImage[];
|
||||
|
@ -110,8 +106,7 @@ export interface Crop {
|
|||
}
|
||||
|
||||
export interface DesignerState {
|
||||
selectedPoints: UUID[] | undefined;
|
||||
selectionPointType: PointType[] | undefined;
|
||||
selectedPlants: string[] | undefined;
|
||||
hoveredPlant: HoveredPlantPayl;
|
||||
hoveredPoint: string | undefined;
|
||||
hoveredPlantListItem: string | undefined;
|
||||
|
@ -120,12 +115,10 @@ export interface DesignerState {
|
|||
cropSearchResults: CropLiveSearchResult[];
|
||||
cropSearchInProgress: boolean;
|
||||
chosenLocation: BotPosition;
|
||||
drawnPoint: DrawnPointPayl | undefined;
|
||||
drawnWeed: DrawnWeedPayl | undefined;
|
||||
currentPoint: CurrentPointPayl | undefined;
|
||||
openedSavedGarden: string | undefined;
|
||||
tryGroupSortType: PointGroupSortType | "nn" | undefined;
|
||||
editGroupAreaInMap: boolean;
|
||||
settingsSearchTerm: string;
|
||||
}
|
||||
|
||||
export type TaggedExecutable = TaggedSequence | TaggedRegimen;
|
||||
|
@ -188,7 +181,6 @@ export interface FarmEventState {
|
|||
export interface GardenMapProps {
|
||||
showPlants: boolean | undefined;
|
||||
showPoints: boolean | undefined;
|
||||
showWeeds: boolean | undefined;
|
||||
showSpread: boolean | undefined;
|
||||
showFarmbot: boolean | undefined;
|
||||
showImages: boolean | undefined;
|
||||
|
@ -197,7 +189,6 @@ export interface GardenMapProps {
|
|||
dispatch: Function;
|
||||
designer: DesignerState;
|
||||
genericPoints: TaggedGenericPointer[];
|
||||
weeds: TaggedWeedPointer[];
|
||||
allPoints: TaggedPoint[];
|
||||
plants: TaggedPlant[];
|
||||
toolSlots: SlotWithTool[];
|
||||
|
@ -288,15 +279,7 @@ export interface CameraCalibrationData {
|
|||
calibrationZ: string | undefined;
|
||||
}
|
||||
|
||||
export interface DrawnPointPayl {
|
||||
name?: string;
|
||||
cx: number;
|
||||
cy: number;
|
||||
r: number;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export interface DrawnWeedPayl {
|
||||
export interface CurrentPointPayl {
|
||||
name?: string;
|
||||
cx: number;
|
||||
cy: number;
|
||||
|
|
|
@ -4,9 +4,10 @@ jest.mock("../../../history", () => ({
|
|||
getPathArray: jest.fn(() => mockPath.split("/")),
|
||||
}));
|
||||
|
||||
jest.mock("../../../api/crud", () => ({ edit: jest.fn() }));
|
||||
|
||||
jest.mock("../../point_groups/actions", () => ({ overwriteGroup: jest.fn() }));
|
||||
jest.mock("../../../api/crud", () => ({
|
||||
edit: jest.fn(),
|
||||
overwrite: jest.fn(),
|
||||
}));
|
||||
|
||||
import { fakePointGroup } from "../../../__test_support__/fake_state/resources";
|
||||
const mockGroup = fakePointGroup();
|
||||
|
@ -15,13 +16,12 @@ jest.mock("../../point_groups/group_detail", () => ({
|
|||
}));
|
||||
|
||||
import {
|
||||
movePlant, closePlantInfo, setDragIcon, clickMapPlant, selectPoint,
|
||||
movePlant, closePlantInfo, setDragIcon, clickMapPlant, selectPlant,
|
||||
setHoveredPlant,
|
||||
mapPointClickAction,
|
||||
} from "../actions";
|
||||
import { MovePlantProps } from "../../interfaces";
|
||||
import { fakePlant } from "../../../__test_support__/fake_state/resources";
|
||||
import { edit } from "../../../api/crud";
|
||||
import { edit, overwrite } from "../../../api/crud";
|
||||
import { Actions } from "../../../constants";
|
||||
import { DEFAULT_ICON, svgToUrl } from "../../../open_farm/icons";
|
||||
import { history } from "../../../history";
|
||||
|
@ -30,8 +30,6 @@ import { GetState } from "../../../redux/interfaces";
|
|||
import {
|
||||
buildResourceIndex,
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
import { overwriteGroup } from "../../point_groups/actions";
|
||||
import { mockDispatch } from "../../../__test_support__/fake_dispatch";
|
||||
|
||||
describe("movePlant", () => {
|
||||
it.each<[string, Record<"x" | "y", number>, Record<"x" | "y", number>]>([
|
||||
|
@ -76,7 +74,7 @@ describe("closePlantInfo()", () => {
|
|||
closePlantInfo(dispatch)();
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/plants");
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
payload: undefined, type: Actions.SELECT_POINT
|
||||
payload: undefined, type: Actions.SELECT_PLANT
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -86,7 +84,7 @@ describe("closePlantInfo()", () => {
|
|||
closePlantInfo(dispatch)();
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/plants");
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
payload: undefined, type: Actions.SELECT_POINT
|
||||
payload: undefined, type: Actions.SELECT_PLANT
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -117,7 +115,7 @@ describe("clickMapPlant", () => {
|
|||
const dispatch = jest.fn();
|
||||
const getState: GetState = jest.fn(() => state);
|
||||
clickMapPlant("fakeUuid", "fakeIcon")(dispatch, getState);
|
||||
expect(dispatch).toHaveBeenCalledWith(selectPoint(["fakeUuid"]));
|
||||
expect(dispatch).toHaveBeenCalledWith(selectPlant(["fakeUuid"]));
|
||||
expect(dispatch).toHaveBeenCalledWith(setHoveredPlant("fakeUuid", "fakeIcon"));
|
||||
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
@ -129,25 +127,12 @@ describe("clickMapPlant", () => {
|
|||
const plant = fakePlant();
|
||||
plant.body.id = 23;
|
||||
state.resources = buildResourceIndex([plant]);
|
||||
const dispatch = mockDispatch();
|
||||
const dispatch = jest.fn();
|
||||
const getState: GetState = jest.fn(() => state);
|
||||
clickMapPlant(plant.uuid, "fakeIcon")(dispatch, getState);
|
||||
expect(overwriteGroup).toHaveBeenCalledWith(mockGroup,
|
||||
expect.objectContaining({
|
||||
name: "Fake", point_ids: [1, 23]
|
||||
}));
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("doesn't add a point to current group", () => {
|
||||
mockPath = "/app/designer/groups/1";
|
||||
mockGroup.body.point_ids = [1];
|
||||
const state = fakeState();
|
||||
state.resources = buildResourceIndex([]);
|
||||
const dispatch = mockDispatch();
|
||||
const getState: GetState = jest.fn(() => state);
|
||||
clickMapPlant("missing plant uuid", "fakeIcon")(dispatch, getState);
|
||||
expect(overwriteGroup).not.toHaveBeenCalled();
|
||||
expect(overwrite).toHaveBeenCalledWith(mockGroup, expect.objectContaining({
|
||||
name: "Fake", point_ids: [1, 23]
|
||||
}));
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
@ -158,13 +143,12 @@ describe("clickMapPlant", () => {
|
|||
const plant = fakePlant();
|
||||
plant.body.id = 2;
|
||||
state.resources = buildResourceIndex([plant]);
|
||||
const dispatch = mockDispatch();
|
||||
const dispatch = jest.fn();
|
||||
const getState: GetState = jest.fn(() => state);
|
||||
clickMapPlant(plant.uuid, "fakeIcon")(dispatch, getState);
|
||||
expect(overwriteGroup).toHaveBeenCalledWith(mockGroup,
|
||||
expect.objectContaining({
|
||||
name: "Fake", point_ids: [1]
|
||||
}));
|
||||
expect(overwrite).toHaveBeenCalledWith(mockGroup, expect.objectContaining({
|
||||
name: "Fake", point_ids: [1]
|
||||
}));
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
@ -178,7 +162,7 @@ describe("clickMapPlant", () => {
|
|||
const getState: GetState = jest.fn(() => state);
|
||||
clickMapPlant(plant.uuid, "fakeIcon")(dispatch, getState);
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SELECT_POINT, payload: [plant.uuid]
|
||||
type: Actions.SELECT_PLANT, payload: [plant.uuid]
|
||||
});
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
@ -189,39 +173,13 @@ describe("clickMapPlant", () => {
|
|||
const plant = fakePlant();
|
||||
plant.uuid = "fakePlantUuid";
|
||||
state.resources = buildResourceIndex([plant]);
|
||||
state.resources.consumers.farm_designer.selectedPoints = [plant.uuid];
|
||||
state.resources.consumers.farm_designer.selectedPlants = [plant.uuid];
|
||||
const dispatch = jest.fn();
|
||||
const getState: GetState = jest.fn(() => state);
|
||||
clickMapPlant(plant.uuid, "fakeIcon")(dispatch, getState);
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SELECT_POINT, payload: []
|
||||
type: Actions.SELECT_PLANT, payload: []
|
||||
});
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapPointClickAction()", () => {
|
||||
it("navigates", () => {
|
||||
mockPath = "/app/designer/plants";
|
||||
const dispatch = jest.fn();
|
||||
mapPointClickAction(dispatch, "uuid", "fake path")();
|
||||
expect(history.push).toHaveBeenCalledWith("fake path");
|
||||
expect(dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("doesn't navigate: box select", () => {
|
||||
mockPath = "/app/designer/plants/select";
|
||||
const dispatch = jest.fn();
|
||||
mapPointClickAction(dispatch, "uuid", "fake path")();
|
||||
expect(history.push).not.toHaveBeenCalled();
|
||||
expect(dispatch).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("doesn't navigate: group edit", () => {
|
||||
mockPath = "/app/designer/groups/edit/1";
|
||||
const dispatch = jest.fn();
|
||||
mapPointClickAction(dispatch, "uuid", "fake path")();
|
||||
expect(history.push).not.toHaveBeenCalled();
|
||||
expect(dispatch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,17 +6,15 @@ jest.mock("../actions", () => ({
|
|||
import { Mode } from "../interfaces";
|
||||
let mockMode = Mode.none;
|
||||
let mockAtPlant = true;
|
||||
let mockInteractionAllow = true;
|
||||
jest.mock("../util", () => ({
|
||||
getMode: () => mockMode,
|
||||
getMapSize: () => ({ h: 100, w: 100 }),
|
||||
getGardenCoordinates: jest.fn(),
|
||||
transformXY: jest.fn(() => ({ qx: 0, qy: 0 })),
|
||||
transformForQuadrant: jest.fn(),
|
||||
maybeNoPointer: jest.fn(),
|
||||
round: jest.fn(),
|
||||
cursorAtPlant: () => mockAtPlant,
|
||||
allowInteraction: () => mockInteractionAllow,
|
||||
allowGroupAreaInteraction: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../layers/plants/plant_actions", () => ({
|
||||
|
@ -83,7 +81,6 @@ const DEFAULT_EVENT = { preventDefault: jest.fn(), pageX: NaN, pageY: NaN };
|
|||
const fakeProps = (): GardenMapProps => ({
|
||||
showPoints: true,
|
||||
showPlants: true,
|
||||
showWeeds: true,
|
||||
showSpread: false,
|
||||
showFarmbot: false,
|
||||
showImages: false,
|
||||
|
@ -95,7 +92,6 @@ const fakeProps = (): GardenMapProps => ({
|
|||
designer: fakeDesignerState(),
|
||||
plants: [],
|
||||
genericPoints: [],
|
||||
weeds: [],
|
||||
allPoints: [],
|
||||
toolSlots: [],
|
||||
botLocationData: {
|
||||
|
@ -290,22 +286,7 @@ describe("<GardenMap/>", () => {
|
|||
wrapper.find(".drop-area-svg").simulate("mouseDown", {
|
||||
pageX: 1, pageY: 2
|
||||
});
|
||||
expect(startNewPoint).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: "point"
|
||||
}));
|
||||
expect(getGardenCoordinates).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ pageX: 1, pageY: 2 }));
|
||||
});
|
||||
|
||||
it("starts drawing weed", () => {
|
||||
const wrapper = shallow(<GardenMap {...fakeProps()} />);
|
||||
mockMode = Mode.createWeed;
|
||||
wrapper.find(".drop-area-svg").simulate("mouseDown", {
|
||||
pageX: 1, pageY: 2
|
||||
});
|
||||
expect(startNewPoint).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: "weed"
|
||||
}));
|
||||
expect(startNewPoint).toHaveBeenCalled();
|
||||
expect(getGardenCoordinates).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ pageX: 1, pageY: 2 }));
|
||||
});
|
||||
|
@ -316,20 +297,7 @@ describe("<GardenMap/>", () => {
|
|||
wrapper.find(".drop-area-svg").simulate("mouseMove", {
|
||||
pageX: 10, pageY: 20
|
||||
});
|
||||
expect(resizePoint).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: "point"
|
||||
}));
|
||||
});
|
||||
|
||||
it("sets drawn weed radius", () => {
|
||||
const wrapper = shallow(<GardenMap {...fakeProps()} />);
|
||||
mockMode = Mode.createWeed;
|
||||
wrapper.find(".drop-area-svg").simulate("mouseMove", {
|
||||
pageX: 10, pageY: 20
|
||||
});
|
||||
expect(resizePoint).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: "weed"
|
||||
}));
|
||||
expect(resizePoint).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("lays eggs", () => {
|
||||
|
@ -382,7 +350,7 @@ describe("<GardenMap/>", () => {
|
|||
it("closes panel", () => {
|
||||
mockMode = Mode.boxSelect;
|
||||
const p = fakeProps();
|
||||
p.designer.selectedPoints = undefined;
|
||||
p.designer.selectedPlants = undefined;
|
||||
const wrapper = mount<GardenMap>(<GardenMap {...p} />);
|
||||
wrapper.instance().closePanel()();
|
||||
expect(closePlantInfo).toHaveBeenCalled();
|
||||
|
@ -398,7 +366,7 @@ describe("<GardenMap/>", () => {
|
|||
it("doesn't close panel: box select", () => {
|
||||
mockMode = Mode.boxSelect;
|
||||
const p = fakeProps();
|
||||
p.designer.selectedPoints = [fakePlant().uuid];
|
||||
p.designer.selectedPlants = [fakePlant().uuid];
|
||||
const wrapper = mount<GardenMap>(<GardenMap {...p} />);
|
||||
wrapper.instance().closePanel()();
|
||||
expect(closePlantInfo).not.toHaveBeenCalled();
|
||||
|
@ -407,7 +375,7 @@ describe("<GardenMap/>", () => {
|
|||
it("doesn't close panel: move mode", () => {
|
||||
mockMode = Mode.moveTo;
|
||||
const p = fakeProps();
|
||||
p.designer.selectedPoints = [fakePlant().uuid];
|
||||
p.designer.selectedPlants = [fakePlant().uuid];
|
||||
const wrapper = mount<GardenMap>(<GardenMap {...p} />);
|
||||
wrapper.instance().closePanel()();
|
||||
expect(closePlantInfo).not.toHaveBeenCalled();
|
||||
|
@ -436,56 +404,6 @@ describe("<GardenMap/>", () => {
|
|||
expect(wrapper.instance().state.isDragging).toBe(true);
|
||||
});
|
||||
|
||||
it("allows interactions: default", () => {
|
||||
mockMode = Mode.none;
|
||||
mockInteractionAllow = true;
|
||||
const p = fakeProps();
|
||||
p.designer.selectionPointType = undefined;
|
||||
const wrapper = mount<GardenMap>(<GardenMap {...p} />);
|
||||
const allowed = wrapper.instance().interactions("Plant");
|
||||
expect(allowed).toBeTruthy();
|
||||
});
|
||||
|
||||
it("allows interactions: box select", () => {
|
||||
mockMode = Mode.boxSelect;
|
||||
mockInteractionAllow = true;
|
||||
const p = fakeProps();
|
||||
p.designer.selectionPointType = undefined;
|
||||
const wrapper = mount<GardenMap>(<GardenMap {...p} />);
|
||||
const allowed = wrapper.instance().interactions("Plant");
|
||||
expect(allowed).toBeTruthy();
|
||||
});
|
||||
|
||||
it("allows interactions: group edit", () => {
|
||||
mockMode = Mode.editGroup;
|
||||
mockInteractionAllow = true;
|
||||
const p = fakeProps();
|
||||
p.designer.selectionPointType = undefined;
|
||||
const wrapper = mount<GardenMap>(<GardenMap {...p} />);
|
||||
const allowed = wrapper.instance().interactions("Plant");
|
||||
expect(allowed).toBeTruthy();
|
||||
});
|
||||
|
||||
it("disallows interactions: default", () => {
|
||||
mockMode = Mode.none;
|
||||
mockInteractionAllow = false;
|
||||
const p = fakeProps();
|
||||
p.designer.selectionPointType = undefined;
|
||||
const wrapper = mount<GardenMap>(<GardenMap {...p} />);
|
||||
const allowed = wrapper.instance().interactions("Plant");
|
||||
expect(allowed).toBeFalsy();
|
||||
});
|
||||
|
||||
it("disallows interactions: box select", () => {
|
||||
mockMode = Mode.boxSelect;
|
||||
mockInteractionAllow = true;
|
||||
const p = fakeProps();
|
||||
p.designer.selectionPointType = ["Plant"];
|
||||
const wrapper = mount<GardenMap>(<GardenMap {...p} />);
|
||||
const allowed = wrapper.instance().interactions("Weed");
|
||||
expect(allowed).toBeFalsy();
|
||||
});
|
||||
|
||||
it("unswapped height and width", () => {
|
||||
const p = fakeProps();
|
||||
p.getConfigValue = () => false;
|
||||
|
|
|
@ -4,6 +4,11 @@ jest.mock("../../../history", () => ({
|
|||
history: { getCurrentLocation: () => ({ pathname: mockPath }) }
|
||||
}));
|
||||
|
||||
let mockGardenOpen = true;
|
||||
jest.mock("../../saved_gardens/saved_gardens", () => ({
|
||||
savedGardenOpen: () => mockGardenOpen,
|
||||
}));
|
||||
|
||||
import {
|
||||
round,
|
||||
translateScreenToGarden,
|
||||
|
@ -16,9 +21,6 @@ import {
|
|||
mapPanelClassName,
|
||||
getMode,
|
||||
cursorAtPlant,
|
||||
allowInteraction,
|
||||
allowGroupAreaInteraction,
|
||||
savedGardenOpen,
|
||||
} from "../util";
|
||||
import { McuParams } from "farmbot";
|
||||
import {
|
||||
|
@ -30,37 +32,13 @@ import {
|
|||
} from "../../../__test_support__/map_transform_props";
|
||||
import { fakePlant } from "../../../__test_support__/fake_state/resources";
|
||||
|
||||
describe("round()", () => {
|
||||
describe("Utils", () => {
|
||||
it("rounds a number", () => {
|
||||
expect(round(44)).toEqual(40);
|
||||
expect(round(98)).toEqual(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapPanelClassName()", () => {
|
||||
it("returns correct panel status: short panel", () => {
|
||||
Object.defineProperty(window, "innerWidth", {
|
||||
value: 400,
|
||||
configurable: true
|
||||
});
|
||||
mockPath = "/app/designer/move_to";
|
||||
expect(mapPanelClassName()).toEqual("short-panel");
|
||||
mockPath = "/app/designer/plants/crop_search/mint/add";
|
||||
expect(mapPanelClassName()).toEqual("short-panel");
|
||||
});
|
||||
|
||||
it("returns correct panel status: panel open", () => {
|
||||
Object.defineProperty(window, "innerWidth", {
|
||||
value: 500,
|
||||
configurable: true
|
||||
});
|
||||
mockPath = "/app/designer/move_to";
|
||||
expect(mapPanelClassName()).toEqual("panel-open");
|
||||
mockPath = "/app/designer/plants/crop_search/mint/add";
|
||||
expect(mapPanelClassName()).toEqual("panel-open");
|
||||
});
|
||||
});
|
||||
|
||||
describe("translateScreenToGarden()", () => {
|
||||
it("translates screen coords to garden coords: zoomLvl = 1", () => {
|
||||
const result = translateScreenToGarden({
|
||||
|
@ -366,26 +344,17 @@ describe("getMode()", () => {
|
|||
expect(getMode()).toEqual(Mode.points);
|
||||
mockPath = "/app/designer/points/add";
|
||||
expect(getMode()).toEqual(Mode.createPoint);
|
||||
mockPath = "/app/designer/weeds";
|
||||
expect(getMode()).toEqual(Mode.weeds);
|
||||
mockPath = "/app/designer/weeds/add";
|
||||
expect(getMode()).toEqual(Mode.createWeed);
|
||||
mockPath = "/app/designer/gardens/1";
|
||||
mockPath = "/app/designer/gardens";
|
||||
mockGardenOpen = true;
|
||||
expect(getMode()).toEqual(Mode.templateView);
|
||||
mockPath = "/app/designer/groups/1";
|
||||
expect(getMode()).toEqual(Mode.editGroup);
|
||||
mockPath = "";
|
||||
mockGardenOpen = false;
|
||||
expect(getMode()).toEqual(Mode.none);
|
||||
});
|
||||
});
|
||||
|
||||
describe("savedGardenOpen", () => {
|
||||
it("is open", () => {
|
||||
const result = savedGardenOpen(["", "", "", "gardens", "4", ""]);
|
||||
expect(result).toEqual(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getGardenCoordinates()", () => {
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(document, "querySelector", {
|
||||
|
@ -427,37 +396,27 @@ describe("getGardenCoordinates()", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("allowInteraction()", () => {
|
||||
it("allows interaction", () => {
|
||||
mockPath = "/app/designer/plants";
|
||||
expect(allowInteraction()).toBeTruthy();
|
||||
});
|
||||
|
||||
it("disallows interaction", () => {
|
||||
describe("mapPanelClassName()", () => {
|
||||
it("returns correct panel status: short panel", () => {
|
||||
Object.defineProperty(window, "innerWidth", {
|
||||
value: 400,
|
||||
configurable: true
|
||||
});
|
||||
mockPath = "/app/designer/move_to";
|
||||
expect(mapPanelClassName()).toEqual("short-panel");
|
||||
mockPath = "/app/designer/plants/crop_search/mint/add";
|
||||
expect(allowInteraction()).toBeFalsy();
|
||||
mockPath = "/app/designer/move_to";
|
||||
expect(allowInteraction()).toBeFalsy();
|
||||
mockPath = "/app/designer/points/add";
|
||||
expect(allowInteraction()).toBeFalsy();
|
||||
mockPath = "/app/designer/weeds/add";
|
||||
expect(allowInteraction()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("allowGroupAreaInteraction()", () => {
|
||||
it("allows interaction", () => {
|
||||
mockPath = "/app/designer/plants";
|
||||
expect(allowGroupAreaInteraction()).toBeTruthy();
|
||||
expect(mapPanelClassName()).toEqual("short-panel");
|
||||
});
|
||||
|
||||
it("disallows interaction", () => {
|
||||
mockPath = "/app/designer/plants/select";
|
||||
expect(allowGroupAreaInteraction()).toBeFalsy();
|
||||
it("returns correct panel status: panel open", () => {
|
||||
Object.defineProperty(window, "innerWidth", {
|
||||
value: 500,
|
||||
configurable: true
|
||||
});
|
||||
mockPath = "/app/designer/move_to";
|
||||
expect(allowGroupAreaInteraction()).toBeFalsy();
|
||||
mockPath = "/app/designer/groups/1";
|
||||
expect(allowGroupAreaInteraction()).toBeFalsy();
|
||||
expect(mapPanelClassName()).toEqual("panel-open");
|
||||
mockPath = "/app/designer/plants/crop_search/mint/add";
|
||||
expect(mapPanelClassName()).toEqual("panel-open");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { MovePlantProps, DraggableEvent } from "../interfaces";
|
||||
import { defensiveClone } from "../../util";
|
||||
import { edit } from "../../api/crud";
|
||||
import { edit, overwrite } from "../../api/crud";
|
||||
import { history } from "../../history";
|
||||
import { Actions } from "../../constants";
|
||||
import { svgToUrl, DEFAULT_ICON } from "../../open_farm/icons";
|
||||
|
@ -12,7 +12,6 @@ import { TaggedPoint } from "farmbot";
|
|||
import { getMode } from "../map/util";
|
||||
import { ResourceIndex, UUID } from "../../resources/interfaces";
|
||||
import { selectAllPointGroups } from "../../resources/selectors";
|
||||
import { overwriteGroup } from "../point_groups/actions";
|
||||
|
||||
export function movePlant(payload: MovePlantProps) {
|
||||
const tr = payload.plant;
|
||||
|
@ -24,8 +23,8 @@ export function movePlant(payload: MovePlantProps) {
|
|||
return edit(tr, update);
|
||||
}
|
||||
|
||||
export const selectPoint = (payload: string[] | undefined) => {
|
||||
return { type: Actions.SELECT_POINT, payload };
|
||||
export const selectPlant = (payload: string[] | undefined) => {
|
||||
return { type: Actions.SELECT_PLANT, payload };
|
||||
};
|
||||
|
||||
export const setHoveredPlant = (plantUUID: string | undefined, icon = "") => ({
|
||||
|
@ -34,36 +33,35 @@ export const setHoveredPlant = (plantUUID: string | undefined, icon = "") => ({
|
|||
});
|
||||
|
||||
const addOrRemoveFromGroup =
|
||||
(clickedPlantUuid: UUID, resources: ResourceIndex) =>
|
||||
(dispatch: Function) => {
|
||||
const group = findGroupFromUrl(selectAllPointGroups(resources));
|
||||
const point =
|
||||
resources.references[clickedPlantUuid] as TaggedPoint | undefined;
|
||||
if (group && point?.body.id) {
|
||||
type Body = (typeof group)["body"];
|
||||
const nextGroup: Body = ({
|
||||
...group.body,
|
||||
point_ids: [...group.body.point_ids.filter(p => p != point.body.id)]
|
||||
});
|
||||
if (!group.body.point_ids.includes(point.body.id)) {
|
||||
nextGroup.point_ids.push(point.body.id);
|
||||
}
|
||||
nextGroup.point_ids = uniq(nextGroup.point_ids);
|
||||
dispatch(overwriteGroup(group, nextGroup));
|
||||
(clickedPlantUuid: UUID, resources: ResourceIndex) => {
|
||||
const group = findGroupFromUrl(selectAllPointGroups(resources));
|
||||
const point =
|
||||
resources.references[clickedPlantUuid] as TaggedPoint | undefined;
|
||||
if (group && point?.body.id) {
|
||||
type Body = (typeof group)["body"];
|
||||
const nextGroup: Body = ({
|
||||
...group.body,
|
||||
point_ids: [...group.body.point_ids.filter(p => p != point.body.id)]
|
||||
});
|
||||
if (!group.body.point_ids.includes(point.body.id)) {
|
||||
nextGroup.point_ids.push(point.body.id);
|
||||
}
|
||||
};
|
||||
|
||||
const addOrRemoveFromSelection =
|
||||
(clickedPointUuid: UUID, selectedPoints: UUID[] | undefined) => {
|
||||
const nextSelected =
|
||||
(selectedPoints || []).filter(uuid => uuid !== clickedPointUuid);
|
||||
if (!(selectedPoints?.includes(clickedPointUuid))) {
|
||||
nextSelected.push(clickedPointUuid);
|
||||
nextGroup.point_ids = uniq(nextGroup.point_ids);
|
||||
return overwrite(group, nextGroup);
|
||||
}
|
||||
return selectPoint(nextSelected);
|
||||
};
|
||||
|
||||
export const clickMapPlant = (clickedPlantUuid: UUID, icon: string) => {
|
||||
const addOrRemoveFromSelection =
|
||||
(clickedPlantUuid: UUID, selectedPlants: UUID[] | undefined) => {
|
||||
const nextSelected =
|
||||
(selectedPlants || []).filter(uuid => uuid !== clickedPlantUuid);
|
||||
if (!(selectedPlants?.includes(clickedPlantUuid))) {
|
||||
nextSelected.push(clickedPlantUuid);
|
||||
}
|
||||
return selectPlant(nextSelected);
|
||||
};
|
||||
|
||||
export const clickMapPlant = (clickedPlantUuid: string, icon: string) => {
|
||||
return (dispatch: Function, getState: GetState) => {
|
||||
switch (getMode()) {
|
||||
case Mode.editGroup:
|
||||
|
@ -71,11 +69,11 @@ export const clickMapPlant = (clickedPlantUuid: UUID, icon: string) => {
|
|||
dispatch(addOrRemoveFromGroup(clickedPlantUuid, resources.index));
|
||||
break;
|
||||
case Mode.boxSelect:
|
||||
const { selectedPoints } = getState().resources.consumers.farm_designer;
|
||||
dispatch(addOrRemoveFromSelection(clickedPlantUuid, selectedPoints));
|
||||
const { selectedPlants } = getState().resources.consumers.farm_designer;
|
||||
dispatch(addOrRemoveFromSelection(clickedPlantUuid, selectedPlants));
|
||||
break;
|
||||
default:
|
||||
dispatch(selectPoint([clickedPlantUuid]));
|
||||
dispatch(selectPlant([clickedPlantUuid]));
|
||||
dispatch(setHoveredPlant(clickedPlantUuid, icon));
|
||||
break;
|
||||
}
|
||||
|
@ -83,7 +81,7 @@ export const clickMapPlant = (clickedPlantUuid: UUID, icon: string) => {
|
|||
};
|
||||
|
||||
export const unselectPlant = (dispatch: Function) => () => {
|
||||
dispatch(selectPoint(undefined));
|
||||
dispatch(selectPlant(undefined));
|
||||
dispatch(setHoveredPlant(undefined));
|
||||
dispatch({ type: Actions.HOVER_PLANT_LIST_ITEM, payload: undefined });
|
||||
};
|
||||
|
@ -106,14 +104,3 @@ export const setDragIcon =
|
|||
e.dataTransfer.setDragImage
|
||||
&& e.dataTransfer.setDragImage(dragImg, width / 2, height / 2);
|
||||
};
|
||||
|
||||
export const mapPointClickAction =
|
||||
(dispatch: Function, uuid: UUID, path?: string) => () => {
|
||||
switch (getMode()) {
|
||||
case Mode.editGroup:
|
||||
case Mode.boxSelect:
|
||||
return dispatch(clickMapPlant(uuid, ""));
|
||||
default:
|
||||
return path && history.push(path);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,8 +8,9 @@ jest.mock("../../../point_groups/criteria", () => ({
|
|||
editGtLtCriteria: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../point_groups/actions", () => ({
|
||||
overwriteGroup: jest.fn(),
|
||||
jest.mock("../../../../api/crud", () => ({
|
||||
overwrite: jest.fn(),
|
||||
save: jest.fn(),
|
||||
}));
|
||||
|
||||
import {
|
||||
|
@ -24,8 +25,8 @@ import {
|
|||
import { Actions } from "../../../../constants";
|
||||
import { history } from "../../../../history";
|
||||
import { editGtLtCriteria } from "../../../point_groups/criteria";
|
||||
import { overwrite, save } from "../../../../api/crud";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { overwriteGroup } from "../../../point_groups/actions";
|
||||
|
||||
describe("getSelected", () => {
|
||||
it("returns some", () => {
|
||||
|
@ -54,9 +55,6 @@ describe("resizeBox", () => {
|
|||
const fakeProps = (): ResizeSelectionBoxProps => ({
|
||||
selectionBox: { x0: 0, y0: 0, x1: undefined, y1: undefined },
|
||||
plants: [],
|
||||
allPoints: [],
|
||||
selectionPointType: undefined,
|
||||
getConfigValue: () => true,
|
||||
gardenCoords: { x: 100, y: 200 },
|
||||
setMapState: jest.fn(),
|
||||
dispatch: jest.fn(),
|
||||
|
@ -70,7 +68,7 @@ describe("resizeBox", () => {
|
|||
selectionBox: { x0: 0, y0: 0, x1: 100, y1: 200 }
|
||||
});
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SELECT_POINT,
|
||||
type: Actions.SELECT_PLANT,
|
||||
payload: undefined
|
||||
});
|
||||
});
|
||||
|
@ -115,7 +113,7 @@ describe("resizeBox", () => {
|
|||
selectionBox: { x0: 0, y0: 0, x1: 100, y1: 200 }
|
||||
});
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SELECT_POINT,
|
||||
type: Actions.SELECT_PLANT,
|
||||
payload: [plant.uuid]
|
||||
});
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/plants/select");
|
||||
|
@ -137,7 +135,7 @@ describe("startNewSelectionBox", () => {
|
|||
selectionBox: { x0: 100, y0: 200, x1: undefined, y1: undefined }
|
||||
});
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SELECT_POINT,
|
||||
type: Actions.SELECT_PLANT,
|
||||
payload: undefined
|
||||
});
|
||||
});
|
||||
|
@ -159,7 +157,7 @@ describe("startNewSelectionBox", () => {
|
|||
startNewSelectionBox(p);
|
||||
expect(p.setMapState).not.toHaveBeenCalled();
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SELECT_POINT,
|
||||
type: Actions.SELECT_PLANT,
|
||||
payload: undefined
|
||||
});
|
||||
});
|
||||
|
@ -188,7 +186,8 @@ describe("maybeUpdateGroup()", () => {
|
|||
expectedBody && (expectedBody.point_ids = [
|
||||
plant1.body.id || 0, plant2.body.id || 0,
|
||||
]);
|
||||
expect(overwriteGroup).toHaveBeenCalledWith(p.group, expectedBody);
|
||||
expect(overwrite).toHaveBeenCalledWith(p.group, expectedBody);
|
||||
expect(save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates criteria", () => {
|
||||
|
@ -212,6 +211,7 @@ describe("maybeUpdateGroup()", () => {
|
|||
maybeUpdateGroup(p);
|
||||
expect(p.dispatch).not.toHaveBeenCalled();
|
||||
expect(editGtLtCriteria).not.toHaveBeenCalled();
|
||||
expect(overwriteGroup).not.toHaveBeenCalled();
|
||||
expect(overwrite).not.toHaveBeenCalled();
|
||||
expect(save).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,20 +3,18 @@ import { TaggedPlant, AxisNumberProperty, Mode } from "../interfaces";
|
|||
import { SelectionBoxData } from "./selection_box";
|
||||
import { GardenMapState } from "../../interfaces";
|
||||
import { history } from "../../../history";
|
||||
import { selectPoint } from "../actions";
|
||||
import { selectPlant } from "../actions";
|
||||
import { getMode } from "../util";
|
||||
import { editGtLtCriteria } from "../../point_groups/criteria";
|
||||
import { TaggedPointGroup, TaggedPoint, PointType } from "farmbot";
|
||||
import { TaggedPointGroup } from "farmbot";
|
||||
import { ShouldDisplay, Feature } from "../../../devices/interfaces";
|
||||
import { overwrite } from "../../../api/crud";
|
||||
import { unpackUUID } from "../../../util";
|
||||
import { UUID } from "../../../resources/interfaces";
|
||||
import { getFilteredPoints } from "../../plants/select_plants";
|
||||
import { GetWebAppConfigValue } from "../../../config_storage/actions";
|
||||
import { overwriteGroup } from "../../point_groups/actions";
|
||||
|
||||
/** Return all plants within the selection box. */
|
||||
export const getSelected = (
|
||||
plants: (TaggedPlant | TaggedPoint)[],
|
||||
plants: TaggedPlant[],
|
||||
box: SelectionBoxData | undefined,
|
||||
): string[] | undefined => {
|
||||
const arraySelected = plants.filter(p => {
|
||||
|
@ -37,9 +35,6 @@ export const getSelected = (
|
|||
export interface ResizeSelectionBoxProps {
|
||||
selectionBox: SelectionBoxData | undefined;
|
||||
plants: TaggedPlant[];
|
||||
allPoints: TaggedPoint[];
|
||||
selectionPointType: PointType[] | undefined;
|
||||
getConfigValue: GetWebAppConfigValue;
|
||||
gardenCoords: AxisNumberProperty | undefined;
|
||||
setMapState: (x: Partial<GardenMapState>) => void;
|
||||
dispatch: Function;
|
||||
|
@ -59,16 +54,11 @@ export const resizeBox = (props: ResizeSelectionBoxProps) => {
|
|||
props.setMapState({ selectionBox: newSelectionBox });
|
||||
if (props.plantActions) {
|
||||
// Select all plants within the updated selection box
|
||||
const { plants, allPoints, selectionPointType, getConfigValue } = props;
|
||||
const points =
|
||||
getFilteredPoints({
|
||||
plants, allPoints, selectionPointType, getConfigValue
|
||||
});
|
||||
const payload = getSelected(points, newSelectionBox);
|
||||
const payload = getSelected(props.plants, newSelectionBox);
|
||||
if (payload && getMode() === Mode.none) {
|
||||
history.push("/app/designer/plants/select");
|
||||
}
|
||||
props.dispatch(selectPoint(payload));
|
||||
props.dispatch(selectPlant(payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +84,7 @@ export const startNewSelectionBox = (props: StartNewSelectionBoxProps) => {
|
|||
}
|
||||
if (props.plantActions) {
|
||||
// Clear the previous plant selection when starting a new selection box
|
||||
props.dispatch(selectPoint(undefined));
|
||||
props.dispatch(selectPlant(undefined));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -109,21 +99,20 @@ export interface MaybeUpdateGroupProps {
|
|||
|
||||
export const maybeUpdateGroup =
|
||||
(props: MaybeUpdateGroupProps) => {
|
||||
const { group } = props;
|
||||
if (props.selectionBox && group) {
|
||||
if (props.selectionBox && props.group) {
|
||||
if (props.editGroupAreaInMap
|
||||
&& props.shouldDisplay(Feature.criteria_groups)) {
|
||||
props.dispatch(editGtLtCriteria(group, props.selectionBox));
|
||||
props.dispatch(editGtLtCriteria(props.group, props.selectionBox));
|
||||
} else {
|
||||
const nextGroupBody = cloneDeep(group.body);
|
||||
const nextGroupBody = cloneDeep(props.group.body);
|
||||
props.boxSelected?.map(uuid => {
|
||||
const { kind, remoteId } = unpackUUID(uuid);
|
||||
remoteId && kind == "Point" && nextGroupBody.point_ids.push(remoteId);
|
||||
});
|
||||
nextGroupBody.point_ids = uniq(nextGroupBody.point_ids);
|
||||
if (!isEqual(group.body.point_ids, nextGroupBody.point_ids)) {
|
||||
props.dispatch(overwriteGroup(group, nextGroupBody));
|
||||
props.dispatch(selectPoint(undefined));
|
||||
if (!isEqual(props.group.body.point_ids, nextGroupBody.point_ids)) {
|
||||
props.dispatch(overwrite(props.group, nextGroupBody));
|
||||
props.dispatch(selectPlant(undefined));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import {
|
||||
startNewPoint, resizePoint, StartNewPointProps, ResizePointProps,
|
||||
} from "../drawn_point_actions";
|
||||
import { startNewPoint, resizePoint } from "../drawn_point_actions";
|
||||
import { Actions } from "../../../../constants";
|
||||
|
||||
describe("startNewPoint", () => {
|
||||
const fakeProps = (): StartNewPointProps => ({
|
||||
const fakeProps = () => ({
|
||||
gardenCoords: { x: 100, y: 200 },
|
||||
dispatch: jest.fn(),
|
||||
setMapState: jest.fn(),
|
||||
type: "point",
|
||||
});
|
||||
|
||||
it("starts point", () => {
|
||||
|
@ -16,25 +13,15 @@ describe("startNewPoint", () => {
|
|||
startNewPoint(p);
|
||||
expect(p.setMapState).toHaveBeenCalledWith({ isDragging: true });
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SET_DRAWN_POINT_DATA,
|
||||
payload: { cx: 100, cy: 200, r: 0 }
|
||||
});
|
||||
});
|
||||
|
||||
it("starts weed", () => {
|
||||
const p = fakeProps();
|
||||
p.type = "weed";
|
||||
startNewPoint(p);
|
||||
expect(p.setMapState).toHaveBeenCalledWith({ isDragging: true });
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SET_DRAWN_WEED_DATA,
|
||||
type: Actions.SET_CURRENT_POINT_DATA,
|
||||
payload: { cx: 100, cy: 200, r: 0 }
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't start point", () => {
|
||||
const p = fakeProps();
|
||||
p.gardenCoords = undefined;
|
||||
// tslint:disable-next-line:no-any
|
||||
p.gardenCoords = undefined as any;
|
||||
startNewPoint(p);
|
||||
expect(p.setMapState).toHaveBeenCalledWith({ isDragging: true });
|
||||
expect(p.dispatch).not.toHaveBeenCalled();
|
||||
|
@ -42,29 +29,18 @@ describe("startNewPoint", () => {
|
|||
});
|
||||
|
||||
describe("resizePoint", () => {
|
||||
const fakeProps = (): ResizePointProps => ({
|
||||
const fakeProps = () => ({
|
||||
gardenCoords: { x: 100, y: 200 },
|
||||
drawnPoint: { cx: 100, cy: 200, r: 0 },
|
||||
currentPoint: { cx: 100, cy: 200, r: 0 },
|
||||
dispatch: jest.fn(),
|
||||
isDragging: true,
|
||||
type: "point",
|
||||
});
|
||||
|
||||
it("resizes point", () => {
|
||||
const p = fakeProps();
|
||||
resizePoint(p);
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SET_DRAWN_POINT_DATA,
|
||||
payload: { cx: 100, cy: 200, r: 0 }
|
||||
});
|
||||
});
|
||||
|
||||
it("resizes weed", () => {
|
||||
const p = fakeProps();
|
||||
p.type = "weed";
|
||||
resizePoint(p);
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SET_DRAWN_WEED_DATA,
|
||||
type: Actions.SET_CURRENT_POINT_DATA,
|
||||
payload: { cx: 100, cy: 200, r: 0 }
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,12 +12,13 @@ describe("<DrawnPoint/>", () => {
|
|||
cx: 10,
|
||||
cy: 20,
|
||||
r: 30,
|
||||
color: "red"
|
||||
}
|
||||
});
|
||||
|
||||
it("renders point", () => {
|
||||
const wrapper = svgMount(<DrawnPoint {...fakeProps()} />);
|
||||
expect(wrapper.find("g").props().stroke).toEqual("green");
|
||||
expect(wrapper.find("g").props().stroke).toEqual("red");
|
||||
expect(wrapper.find("circle").first().props()).toEqual({
|
||||
id: "point-radius", strokeDasharray: "4 5",
|
||||
cx: 10, cy: 20, r: 30,
|
||||
|
@ -27,11 +28,4 @@ describe("<DrawnPoint/>", () => {
|
|||
cx: 10, cy: 20, r: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it("renders point with chosen color", () => {
|
||||
const p = fakeProps();
|
||||
p.data = { cx: 0, cy: 0, r: 1, color: "red" };
|
||||
const wrapper = svgMount(<DrawnPoint {...p} />);
|
||||
expect(wrapper.find("g").props().stroke).toEqual("red");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { DrawnWeed, DrawnWeedProps } from "../drawn_weed";
|
||||
import {
|
||||
fakeMapTransformProps,
|
||||
} from "../../../../__test_support__/map_transform_props";
|
||||
import { svgMount } from "../../../../__test_support__/svg_mount";
|
||||
|
||||
describe("<DrawnWeed />", () => {
|
||||
const fakeProps = (): DrawnWeedProps => ({
|
||||
mapTransformProps: fakeMapTransformProps(),
|
||||
data: {
|
||||
cx: 10,
|
||||
cy: 20,
|
||||
r: 30,
|
||||
}
|
||||
});
|
||||
|
||||
it("renders weed", () => {
|
||||
const wrapper = svgMount(<DrawnWeed {...fakeProps()} />);
|
||||
const stop = wrapper.find("stop").first().props();
|
||||
expect(stop.stopColor).toEqual("red");
|
||||
expect(stop.stopOpacity).toEqual(0.25);
|
||||
expect(wrapper.find("circle").first().props()).toEqual({
|
||||
id: "weed-radius", cx: 10, cy: 20, r: 30, fill: "url(#DrawnWeedGradient)",
|
||||
});
|
||||
});
|
||||
|
||||
it("renders point with chosen color", () => {
|
||||
const p = fakeProps();
|
||||
p.data = { cx: 0, cy: 0, r: 1, color: "orange" };
|
||||
const wrapper = svgMount(<DrawnWeed {...p} />);
|
||||
const stop = wrapper.find("stop").first().props();
|
||||
expect(stop.stopColor).toEqual("orange");
|
||||
expect(stop.stopOpacity).toEqual(0.5);
|
||||
});
|
||||
});
|
|
@ -1,11 +1,11 @@
|
|||
import * as React from "react";
|
||||
import { MapTransformProps } from "../interfaces";
|
||||
import { transformXY } from "../util";
|
||||
import { DrawnPointPayl } from "../../interfaces";
|
||||
import { CurrentPointPayl } from "../../interfaces";
|
||||
|
||||
export interface DrawnPointProps {
|
||||
mapTransformProps: MapTransformProps;
|
||||
data: DrawnPointPayl | undefined;
|
||||
data: CurrentPointPayl | undefined;
|
||||
}
|
||||
|
||||
export function DrawnPoint(props: DrawnPointProps) {
|
||||
|
|
|
@ -1,47 +1,37 @@
|
|||
import { Actions } from "../../../constants";
|
||||
import { AxisNumberProperty } from "../interfaces";
|
||||
import { DrawnPointPayl } from "../../interfaces";
|
||||
|
||||
export interface StartNewPointProps {
|
||||
gardenCoords: AxisNumberProperty | undefined;
|
||||
dispatch: Function;
|
||||
setMapState: Function;
|
||||
type: "point" | "weed";
|
||||
}
|
||||
import { CurrentPointPayl } from "../../interfaces";
|
||||
|
||||
/** Create a new point. */
|
||||
export const startNewPoint = (props: StartNewPointProps) => {
|
||||
export const startNewPoint = (props: {
|
||||
gardenCoords: AxisNumberProperty | undefined,
|
||||
dispatch: Function,
|
||||
setMapState: Function,
|
||||
}) => {
|
||||
props.setMapState({ isDragging: true });
|
||||
const center = props.gardenCoords;
|
||||
if (center) {
|
||||
// Set the center of a new point
|
||||
props.dispatch({
|
||||
type: props.type == "weed"
|
||||
? Actions.SET_DRAWN_WEED_DATA
|
||||
: Actions.SET_DRAWN_POINT_DATA,
|
||||
type: Actions.SET_CURRENT_POINT_DATA,
|
||||
payload: { cx: center.x, cy: center.y, r: 0 }
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export interface ResizePointProps {
|
||||
gardenCoords: AxisNumberProperty | undefined;
|
||||
drawnPoint: DrawnPointPayl | undefined;
|
||||
dispatch: Function;
|
||||
isDragging: boolean | undefined;
|
||||
type: "point" | "weed";
|
||||
}
|
||||
|
||||
/** Resize a point. */
|
||||
export const resizePoint = (props: ResizePointProps) => {
|
||||
export const resizePoint = (props: {
|
||||
gardenCoords: AxisNumberProperty | undefined,
|
||||
currentPoint: CurrentPointPayl | undefined,
|
||||
dispatch: Function,
|
||||
isDragging: boolean | undefined,
|
||||
}) => {
|
||||
const edge = props.gardenCoords;
|
||||
if (edge && props.drawnPoint && !!props.isDragging) {
|
||||
const { cx, cy } = props.drawnPoint;
|
||||
if (edge && props.currentPoint && !!props.isDragging) {
|
||||
const { cx, cy } = props.currentPoint;
|
||||
// Adjust the radius of the point being created
|
||||
props.dispatch({
|
||||
type: props.type == "weed"
|
||||
? Actions.SET_DRAWN_WEED_DATA
|
||||
: Actions.SET_DRAWN_POINT_DATA,
|
||||
type: Actions.SET_CURRENT_POINT_DATA,
|
||||
payload: {
|
||||
cx, cy, // Center was set by click, radius is adjusted by drag
|
||||
r: Math.round(Math.sqrt(
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { MapTransformProps } from "../interfaces";
|
||||
import { transformXY } from "../util";
|
||||
import { DrawnWeedPayl } from "../../interfaces";
|
||||
|
||||
export interface DrawnWeedProps {
|
||||
mapTransformProps: MapTransformProps;
|
||||
data: DrawnWeedPayl | undefined;
|
||||
}
|
||||
|
||||
export function DrawnWeed(props: DrawnWeedProps) {
|
||||
const ID = "current-weed";
|
||||
const { data, mapTransformProps } = props;
|
||||
if (!data) { return <g id={ID} />; }
|
||||
const { cx, cy, r } = data;
|
||||
const color = data.color || "red";
|
||||
const { qx, qy } = transformXY(cx, cy, mapTransformProps);
|
||||
const stopOpacity = ["gray", "pink", "orange"].includes(color) ? 0.5 : 0.25;
|
||||
return <g id={ID}>
|
||||
<defs>
|
||||
<radialGradient id={"DrawnWeedGradient"}>
|
||||
<stop offset="90%" stopColor={color} stopOpacity={stopOpacity} />
|
||||
<stop offset="100%" stopColor={color} stopOpacity={0} />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<circle
|
||||
id={"weed-radius"}
|
||||
cx={qx}
|
||||
cy={qy}
|
||||
r={r}
|
||||
fill={"url(#DrawnWeedGradient)"} />
|
||||
</g>;
|
||||
}
|
|
@ -6,7 +6,7 @@ import {
|
|||
} from "./interfaces";
|
||||
import { GardenMapProps, GardenMapState } from "../interfaces";
|
||||
import {
|
||||
getMapSize, getGardenCoordinates, getMode, cursorAtPlant, allowInteraction,
|
||||
getMapSize, getGardenCoordinates, getMode, cursorAtPlant,
|
||||
} from "./util";
|
||||
import {
|
||||
Grid, MapBackground,
|
||||
|
@ -17,7 +17,6 @@ import {
|
|||
PlantLayer,
|
||||
SpreadLayer,
|
||||
PointLayer,
|
||||
WeedLayer,
|
||||
ToolSlotLayer,
|
||||
FarmBotLayer,
|
||||
ImageLayer,
|
||||
|
@ -35,12 +34,9 @@ import { NNPath } from "../point_groups/paths";
|
|||
import { history } from "../../history";
|
||||
import { ZonesLayer } from "./layers/zones/zones_layer";
|
||||
import { ErrorBoundary } from "../../error_boundary";
|
||||
import { TaggedPoint, TaggedPointGroup, PointType } from "farmbot";
|
||||
import { TaggedPoint, TaggedPointGroup } from "farmbot";
|
||||
import { findGroupFromUrl } from "../point_groups/group_detail";
|
||||
import { pointsSelectedByGroup } from "../point_groups/criteria";
|
||||
import { DrawnWeed } from "./drawn_point/drawn_weed";
|
||||
import { UUID } from "../../resources/interfaces";
|
||||
import { throttle } from "lodash";
|
||||
|
||||
export class GardenMap extends
|
||||
React.Component<GardenMapProps, Partial<GardenMapState>> {
|
||||
|
@ -85,12 +81,8 @@ export class GardenMap extends
|
|||
pointsSelectedByGroup(this.group, this.props.allPoints) : [];
|
||||
}
|
||||
|
||||
get groupSelected(): UUID[] {
|
||||
return this.pointsSelectedByGroup.map(point => point.uuid);
|
||||
}
|
||||
|
||||
/** Save the current plant (if needed) and reset drag state. */
|
||||
endDrag = throttle(() => {
|
||||
endDrag = () => {
|
||||
maybeSavePlantLocation({
|
||||
plant: this.getPlant(),
|
||||
isDragging: this.state.isDragging,
|
||||
|
@ -102,7 +94,7 @@ export class GardenMap extends
|
|||
dispatch: this.props.dispatch,
|
||||
shouldDisplay: this.props.shouldDisplay,
|
||||
editGroupAreaInMap: this.props.designer.editGroupAreaInMap,
|
||||
boxSelected: this.props.designer.selectedPoints,
|
||||
boxSelected: this.props.designer.selectedPlants,
|
||||
});
|
||||
this.setState({
|
||||
isDragging: false, qPageX: 0, qPageY: 0,
|
||||
|
@ -110,7 +102,7 @@ export class GardenMap extends
|
|||
activeDragSpread: undefined,
|
||||
selectionBox: undefined
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
|
||||
getGardenCoordinates =
|
||||
(e: React.DragEvent<HTMLElement> | React.MouseEvent<SVGElement>):
|
||||
|
@ -160,15 +152,6 @@ export class GardenMap extends
|
|||
gardenCoords: this.getGardenCoordinates(e),
|
||||
dispatch: this.props.dispatch,
|
||||
setMapState: this.setMapState,
|
||||
type: "point",
|
||||
});
|
||||
break;
|
||||
case Mode.createWeed:
|
||||
startNewPoint({
|
||||
gardenCoords: this.getGardenCoordinates(e),
|
||||
dispatch: this.props.dispatch,
|
||||
setMapState: this.setMapState,
|
||||
type: "weed",
|
||||
});
|
||||
break;
|
||||
case Mode.clickToAdd:
|
||||
|
@ -180,8 +163,8 @@ export class GardenMap extends
|
|||
startDragOnBackground = (e: React.MouseEvent<SVGElement>): void => {
|
||||
switch (getMode()) {
|
||||
case Mode.moveTo:
|
||||
break;
|
||||
case Mode.createPoint:
|
||||
case Mode.createWeed:
|
||||
case Mode.clickToAdd:
|
||||
case Mode.editPlant:
|
||||
break;
|
||||
|
@ -213,27 +196,17 @@ export class GardenMap extends
|
|||
}
|
||||
}
|
||||
|
||||
interactions = (pointerType: PointType): boolean => {
|
||||
if (allowInteraction()) {
|
||||
switch (getMode()) {
|
||||
case Mode.editGroup:
|
||||
case Mode.boxSelect:
|
||||
return (this.props.designer.selectionPointType || ["Plant"])
|
||||
.includes(pointerType);
|
||||
}
|
||||
}
|
||||
return allowInteraction();
|
||||
};
|
||||
|
||||
/** Return the selected plant, mode-allowing. */
|
||||
getPlant = (): TaggedPlant | undefined => {
|
||||
return allowInteraction()
|
||||
? this.props.selectedPlant
|
||||
: undefined;
|
||||
}
|
||||
|
||||
get currentPoint(): UUID | undefined {
|
||||
return this.props.designer.selectedPoints?.[0];
|
||||
switch (getMode()) {
|
||||
case Mode.boxSelect:
|
||||
case Mode.moveTo:
|
||||
case Mode.points:
|
||||
case Mode.createPoint:
|
||||
return undefined; // For modes without plant interaction
|
||||
default:
|
||||
return this.props.selectedPlant;
|
||||
}
|
||||
}
|
||||
|
||||
handleDragOver = (e: React.DragEvent<HTMLElement>) => {
|
||||
|
@ -300,28 +273,15 @@ export class GardenMap extends
|
|||
case Mode.createPoint:
|
||||
resizePoint({
|
||||
gardenCoords: this.getGardenCoordinates(e),
|
||||
drawnPoint: this.props.designer.drawnPoint,
|
||||
currentPoint: this.props.designer.currentPoint,
|
||||
dispatch: this.props.dispatch,
|
||||
isDragging: this.state.isDragging,
|
||||
type: "point",
|
||||
});
|
||||
break;
|
||||
case Mode.createWeed:
|
||||
resizePoint({
|
||||
gardenCoords: this.getGardenCoordinates(e),
|
||||
drawnPoint: this.props.designer.drawnWeed,
|
||||
dispatch: this.props.dispatch,
|
||||
isDragging: this.state.isDragging,
|
||||
type: "weed",
|
||||
});
|
||||
break;
|
||||
case Mode.editGroup:
|
||||
resizeBox({
|
||||
selectionBox: this.state.selectionBox,
|
||||
plants: this.props.plants,
|
||||
allPoints: this.props.allPoints,
|
||||
selectionPointType: this.props.designer.selectionPointType,
|
||||
getConfigValue: this.props.getConfigValue,
|
||||
gardenCoords: this.getGardenCoordinates(e),
|
||||
setMapState: this.setMapState,
|
||||
dispatch: this.props.dispatch,
|
||||
|
@ -333,9 +293,6 @@ export class GardenMap extends
|
|||
resizeBox({
|
||||
selectionBox: this.state.selectionBox,
|
||||
plants: this.props.plants,
|
||||
allPoints: this.props.allPoints,
|
||||
selectionPointType: this.props.designer.selectionPointType,
|
||||
getConfigValue: this.props.getConfigValue,
|
||||
gardenCoords: this.getGardenCoordinates(e),
|
||||
setMapState: this.setMapState,
|
||||
dispatch: this.props.dispatch,
|
||||
|
@ -351,7 +308,7 @@ export class GardenMap extends
|
|||
case Mode.moveTo:
|
||||
return () => { };
|
||||
case Mode.boxSelect:
|
||||
return this.props.designer.selectedPoints
|
||||
return this.props.designer.selectedPlants
|
||||
? () => { }
|
||||
: closePlantInfo(this.props.dispatch);
|
||||
default:
|
||||
|
@ -405,7 +362,6 @@ export class GardenMap extends
|
|||
botSize={this.props.botSize}
|
||||
mapTransformProps={this.mapTransformProps}
|
||||
groups={this.props.groups}
|
||||
startDrag={this.startDragOnBackground}
|
||||
currentGroup={this.group?.uuid} />
|
||||
SensorReadingsLayer = () => <SensorReadingsLayer
|
||||
visible={!!this.props.showSensorReadings}
|
||||
|
@ -429,20 +385,7 @@ export class GardenMap extends
|
|||
dispatch={this.props.dispatch}
|
||||
hoveredPoint={this.props.designer.hoveredPoint}
|
||||
visible={!!this.props.showPoints}
|
||||
interactions={this.interactions("GenericPointer")}
|
||||
genericPoints={this.props.genericPoints} />
|
||||
WeedLayer = () => <WeedLayer
|
||||
mapTransformProps={this.mapTransformProps}
|
||||
dispatch={this.props.dispatch}
|
||||
hoveredPoint={this.props.designer.hoveredPoint}
|
||||
visible={!!this.props.showWeeds}
|
||||
spreadVisible={!!this.props.showSpread}
|
||||
currentPoint={this.currentPoint}
|
||||
boxSelected={this.props.designer.selectedPoints}
|
||||
groupSelected={this.groupSelected}
|
||||
interactions={this.interactions("Weed")}
|
||||
weeds={this.props.weeds}
|
||||
animate={this.animate} />
|
||||
PlantLayer = () => <PlantLayer
|
||||
mapTransformProps={this.mapTransformProps}
|
||||
dispatch={this.props.dispatch}
|
||||
|
@ -452,11 +395,10 @@ export class GardenMap extends
|
|||
hoveredPlant={this.props.hoveredPlant}
|
||||
dragging={!!this.state.isDragging}
|
||||
editing={this.isEditing}
|
||||
boxSelected={this.props.designer.selectedPoints}
|
||||
groupSelected={this.groupSelected}
|
||||
boxSelected={this.props.designer.selectedPlants}
|
||||
groupSelected={this.pointsSelectedByGroup.map(point => point.uuid)}
|
||||
zoomLvl={this.props.zoomLvl}
|
||||
activeDragXY={this.state.activeDragXY}
|
||||
interactions={this.interactions("Plant")}
|
||||
animate={this.animate} />
|
||||
ToolSlotLayer = () => <ToolSlotLayer
|
||||
mapTransformProps={this.mapTransformProps}
|
||||
|
@ -464,7 +406,6 @@ export class GardenMap extends
|
|||
dispatch={this.props.dispatch}
|
||||
hoveredToolSlot={this.props.designer.hoveredToolSlot}
|
||||
botPositionX={this.props.botLocationData.position.x}
|
||||
interactions={this.interactions("ToolSlot")}
|
||||
slots={this.props.toolSlots} />
|
||||
FarmBotLayer = () => <FarmBotLayer
|
||||
mapTransformProps={this.mapTransformProps}
|
||||
|
@ -502,10 +443,8 @@ export class GardenMap extends
|
|||
chosenLocation={this.props.designer.chosenLocation}
|
||||
mapTransformProps={this.mapTransformProps} />
|
||||
DrawnPoint = () => <DrawnPoint
|
||||
data={this.props.designer.drawnPoint}
|
||||
mapTransformProps={this.mapTransformProps} />
|
||||
DrawnWeed = () => <DrawnWeed
|
||||
data={this.props.designer.drawnWeed}
|
||||
data={this.props.designer.currentPoint}
|
||||
key={"currentPoint"}
|
||||
mapTransformProps={this.mapTransformProps} />
|
||||
GroupOrder = () => <GroupOrder
|
||||
group={this.group}
|
||||
|
@ -529,7 +468,6 @@ export class GardenMap extends
|
|||
<this.SensorReadingsLayer />
|
||||
<this.SpreadLayer />
|
||||
<this.PointLayer />
|
||||
<this.WeedLayer />
|
||||
<this.PlantLayer />
|
||||
<this.ToolSlotLayer />
|
||||
<this.FarmBotLayer />
|
||||
|
@ -538,7 +476,6 @@ export class GardenMap extends
|
|||
<this.SelectionBox />
|
||||
<this.TargetCoordinate />
|
||||
<this.DrawnPoint />
|
||||
<this.DrawnWeed />
|
||||
<this.GroupOrder />
|
||||
<this.NNPath />
|
||||
<this.Bugs />
|
||||
|
|
|
@ -2,12 +2,9 @@ import {
|
|||
TaggedPlantPointer,
|
||||
TaggedGenericPointer,
|
||||
TaggedPlantTemplate,
|
||||
TaggedWeedPointer,
|
||||
} from "farmbot";
|
||||
import { State, BotOriginQuadrant } from "../interfaces";
|
||||
import {
|
||||
BotPosition, BotLocationData, ShouldDisplay,
|
||||
} from "../../devices/interfaces";
|
||||
import { BotPosition, BotLocationData } from "../../devices/interfaces";
|
||||
import { GetWebAppConfigValue } from "../../config_storage/actions";
|
||||
import { TimeSettings } from "../../interfaces";
|
||||
import { UUID } from "../../resources/interfaces";
|
||||
|
@ -25,10 +22,9 @@ export interface PlantLayerProps {
|
|||
mapTransformProps: MapTransformProps;
|
||||
zoomLvl: number;
|
||||
activeDragXY: BotPosition | undefined;
|
||||
boxSelected: UUID[] | undefined;
|
||||
boxSelected: string[] | undefined;
|
||||
groupSelected: UUID[];
|
||||
animate: boolean;
|
||||
interactions: boolean;
|
||||
}
|
||||
|
||||
export interface GardenMapLegendProps {
|
||||
|
@ -37,7 +33,6 @@ export interface GardenMapLegendProps {
|
|||
legendMenuOpen: boolean;
|
||||
showPlants: boolean;
|
||||
showPoints: boolean;
|
||||
showWeeds: boolean;
|
||||
showSpread: boolean;
|
||||
showFarmbot: boolean;
|
||||
showImages: boolean;
|
||||
|
@ -50,7 +45,6 @@ export interface GardenMapLegendProps {
|
|||
imageAgeInfo: { newestDate: string, toOldest: number };
|
||||
gardenId?: number;
|
||||
className?: string;
|
||||
shouldDisplay: ShouldDisplay;
|
||||
}
|
||||
|
||||
export type MapTransformProps = {
|
||||
|
@ -86,17 +80,6 @@ export interface GardenPointProps {
|
|||
dispatch: Function;
|
||||
}
|
||||
|
||||
export interface GardenWeedProps {
|
||||
mapTransformProps: MapTransformProps;
|
||||
weed: TaggedWeedPointer;
|
||||
hovered: boolean;
|
||||
current: boolean;
|
||||
selected: boolean;
|
||||
animate: boolean;
|
||||
spreadVisible: boolean;
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
interface DragHelpersBaseProps {
|
||||
dragging: boolean;
|
||||
mapTransformProps: MapTransformProps;
|
||||
|
@ -169,9 +152,7 @@ export enum Mode {
|
|||
addPlant = "addPlant",
|
||||
moveTo = "moveTo",
|
||||
points = "points",
|
||||
weeds = "weeds",
|
||||
createPoint = "createPoint",
|
||||
createWeed = "createWeed",
|
||||
templateView = "templateView",
|
||||
editGroup = "editGroup",
|
||||
}
|
||||
|
|
|
@ -1,30 +1,24 @@
|
|||
jest.mock("../../../../../api/crud", () => ({
|
||||
edit: jest.fn(),
|
||||
save: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { ImageFilterMenu, ImageFilterMenuProps } from "../image_filter_menu";
|
||||
import { shallow, mount } from "enzyme";
|
||||
import {
|
||||
fakeWebAppConfig,
|
||||
} from "../../../../../__test_support__/fake_state/resources";
|
||||
import { StringConfigKey } from "farmbot/dist/resources/configs/web_app";
|
||||
import { setWebAppConfigValue } from "../../../../../config_storage/actions";
|
||||
import {
|
||||
fakeTimeSettings,
|
||||
} from "../../../../../__test_support__/fake_time_settings";
|
||||
|
||||
const mockConfig = fakeWebAppConfig();
|
||||
jest.mock("../../../../../resources/selectors", () => ({
|
||||
getWebAppConfig: () => mockConfig,
|
||||
assertUuid: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { ImageFilterMenu, ImageFilterMenuProps } from "../image_filter_menu";
|
||||
import { shallow, mount } from "enzyme";
|
||||
|
||||
import { StringConfigKey } from "farmbot/dist/resources/configs/web_app";
|
||||
import {
|
||||
fakeTimeSettings,
|
||||
} from "../../../../../__test_support__/fake_time_settings";
|
||||
import { edit, save } from "../../../../../api/crud";
|
||||
import { fakeState } from "../../../../../__test_support__/fake_state";
|
||||
import {
|
||||
buildResourceIndex,
|
||||
} from "../../../../../__test_support__/resource_index_builder";
|
||||
jest.mock("../../../../../config_storage/actions", () => ({
|
||||
setWebAppConfigValue: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("<ImageFilterMenu />", () => {
|
||||
mockConfig.body.photo_filter_begin = "";
|
||||
|
@ -51,19 +45,13 @@ describe("<ImageFilterMenu />", () => {
|
|||
["endDate", "photo_filter_end", 2],
|
||||
])("sets filter: %s", (filter, key, i) => {
|
||||
const p = fakeProps();
|
||||
const state = fakeState();
|
||||
const config = fakeWebAppConfig();
|
||||
state.resources = buildResourceIndex([config]);
|
||||
p.dispatch = jest.fn(x => x(jest.fn(), () => state));
|
||||
const wrapper = shallow<ImageFilterMenu>(<ImageFilterMenu {...p} />);
|
||||
wrapper.find("BlurableInput").at(i).simulate("commit", {
|
||||
currentTarget: { value: "2001-01-03" }
|
||||
});
|
||||
expect(wrapper.instance().state[filter]).toEqual("2001-01-03");
|
||||
expect(edit).toHaveBeenCalledWith(config, {
|
||||
[key]: "2001-01-03T00:00:00.000Z"
|
||||
});
|
||||
expect(save).toHaveBeenCalledWith(config.uuid);
|
||||
expect(setWebAppConfigValue)
|
||||
.toHaveBeenCalledWith(key, "2001-01-03T00:00:00.000Z");
|
||||
});
|
||||
|
||||
it.each<[
|
||||
|
@ -73,64 +61,14 @@ describe("<ImageFilterMenu />", () => {
|
|||
["endTime", "photo_filter_end", 3],
|
||||
])("sets filter: %s", (filter, key, i) => {
|
||||
const p = fakeProps();
|
||||
const state = fakeState();
|
||||
const config = fakeWebAppConfig();
|
||||
state.resources = buildResourceIndex([config]);
|
||||
p.dispatch = jest.fn(x => x(jest.fn(), () => state));
|
||||
const wrapper = shallow<ImageFilterMenu>(<ImageFilterMenu {...p} />);
|
||||
wrapper.setState({ beginDate: "2001-01-03", endDate: "2001-01-03" });
|
||||
wrapper.find("BlurableInput").at(i).simulate("commit", {
|
||||
currentTarget: { value: "05:00" }
|
||||
});
|
||||
expect(wrapper.instance().state[filter]).toEqual("05:00");
|
||||
expect(edit).toHaveBeenCalledWith(config, {
|
||||
[key]: "2001-01-03T05:00:00.000Z"
|
||||
});
|
||||
expect(save).toHaveBeenCalledWith(config.uuid);
|
||||
});
|
||||
|
||||
it.each<[
|
||||
"beginDate" | "endDate",
|
||||
"photo_filter_begin" | "photo_filter_end",
|
||||
number
|
||||
]>([
|
||||
["beginDate", "photo_filter_begin", 0],
|
||||
["endDate", "photo_filter_end", 2],
|
||||
])("unsets filter: %s", (filter, key, i) => {
|
||||
const p = fakeProps();
|
||||
const state = fakeState();
|
||||
const config = fakeWebAppConfig();
|
||||
state.resources = buildResourceIndex([config]);
|
||||
p.dispatch = jest.fn(x => x(jest.fn(), () => state));
|
||||
const wrapper = shallow<ImageFilterMenu>(<ImageFilterMenu {...p} />);
|
||||
wrapper.setState({ beginDate: "2001-01-03", endDate: "2001-01-03" });
|
||||
wrapper.find("BlurableInput").at(i).simulate("commit", {
|
||||
currentTarget: { value: "" }
|
||||
});
|
||||
expect(wrapper.instance().state[filter]).toEqual(undefined);
|
||||
// tslint:disable-next-line:no-null-keyword
|
||||
expect(edit).toHaveBeenCalledWith(config, { [key]: null });
|
||||
expect(save).toHaveBeenCalledWith(config.uuid);
|
||||
});
|
||||
|
||||
it.each<[
|
||||
"beginTime" | "endTime", number
|
||||
]>([
|
||||
["beginTime", 1],
|
||||
["endTime", 3],
|
||||
])("doesn't set filter: %s", (filter, i) => {
|
||||
const p = fakeProps();
|
||||
const state = fakeState();
|
||||
const config = fakeWebAppConfig();
|
||||
state.resources = buildResourceIndex([config]);
|
||||
p.dispatch = jest.fn(x => x(jest.fn(), () => state));
|
||||
const wrapper = shallow<ImageFilterMenu>(<ImageFilterMenu {...p} />);
|
||||
wrapper.find("BlurableInput").at(i).simulate("commit", {
|
||||
currentTarget: { value: "05:00" }
|
||||
});
|
||||
expect(wrapper.instance().state[filter]).toEqual("05:00");
|
||||
expect(edit).not.toHaveBeenCalled();
|
||||
expect(save).not.toHaveBeenCalled();
|
||||
expect(setWebAppConfigValue)
|
||||
.toHaveBeenCalledWith(key, "2001-01-03T05:00:00.000Z");
|
||||
});
|
||||
|
||||
it("loads values from config", () => {
|
||||
|
@ -145,34 +83,14 @@ describe("<ImageFilterMenu />", () => {
|
|||
|
||||
it("changes slider", () => {
|
||||
const p = fakeProps();
|
||||
const state = fakeState();
|
||||
const config = fakeWebAppConfig();
|
||||
state.resources = buildResourceIndex([config]);
|
||||
p.dispatch = jest.fn(x => x(jest.fn(), () => state));
|
||||
p.getConfigValue = () => undefined;
|
||||
p.imageAgeInfo.newestDate = "2001-01-03T05:00:00.000Z";
|
||||
const wrapper = shallow<ImageFilterMenu>(<ImageFilterMenu {...p} />);
|
||||
wrapper.instance().sliderChange(1);
|
||||
expect(wrapper.instance().state.slider).toEqual(1);
|
||||
expect(edit).toHaveBeenCalledWith(config, {
|
||||
photo_filter_begin: "2001-01-02T00:00:00.000Z",
|
||||
photo_filter_end: "2001-01-03T00:00:00.000Z",
|
||||
});
|
||||
expect(save).toHaveBeenCalledWith(config.uuid);
|
||||
});
|
||||
|
||||
it("doesn't update config", () => {
|
||||
const p = fakeProps();
|
||||
const state = fakeState();
|
||||
state.resources = buildResourceIndex([]);
|
||||
p.dispatch = jest.fn(x => x(jest.fn(), () => state));
|
||||
p.getConfigValue = () => 1;
|
||||
p.imageAgeInfo.newestDate = "2001-01-03T05:00:00.000Z";
|
||||
const wrapper = shallow<ImageFilterMenu>(<ImageFilterMenu {...p} />);
|
||||
wrapper.instance().sliderChange(1);
|
||||
expect(wrapper.instance().state.slider).toEqual(1);
|
||||
expect(edit).not.toHaveBeenCalled();
|
||||
expect(save).not.toHaveBeenCalled();
|
||||
expect(setWebAppConfigValue)
|
||||
.toHaveBeenCalledWith("photo_filter_begin", "2001-01-02T00:00:00.000Z");
|
||||
expect(setWebAppConfigValue)
|
||||
.toHaveBeenCalledWith("photo_filter_end", "2001-01-03T00:00:00.000Z");
|
||||
});
|
||||
|
||||
it("displays slider labels", () => {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import * as React from "react";
|
||||
import { BlurableInput } from "../../../../ui/index";
|
||||
import { offsetTime } from "../../../farm_events/edit_fe_form";
|
||||
import { GetWebAppConfigValue } from "../../../../config_storage/actions";
|
||||
import {
|
||||
setWebAppConfigValue, GetWebAppConfigValue,
|
||||
} from "../../../../config_storage/actions";
|
||||
import moment from "moment";
|
||||
import {
|
||||
formatDate, formatTime,
|
||||
|
@ -9,13 +11,8 @@ import {
|
|||
import { Slider } from "@blueprintjs/core";
|
||||
import { t } from "../../../../i18next_wrapper";
|
||||
import { TimeSettings } from "../../../../interfaces";
|
||||
import { StringConfigKey } from "farmbot/dist/resources/configs/web_app";
|
||||
import { GetState } from "../../../../redux/interfaces";
|
||||
import { getWebAppConfig } from "../../../../resources/getters";
|
||||
import { edit, save } from "../../../../api/crud";
|
||||
import { isString, isUndefined } from "lodash";
|
||||
|
||||
interface FullImageFilterMenuState {
|
||||
interface ImageFilterMenuState {
|
||||
beginDate: string | undefined;
|
||||
beginTime: string | undefined;
|
||||
endDate: string | undefined;
|
||||
|
@ -23,8 +20,6 @@ interface FullImageFilterMenuState {
|
|||
slider: number;
|
||||
}
|
||||
|
||||
type ImageFilterMenuState = Partial<FullImageFilterMenuState>;
|
||||
|
||||
export interface ImageFilterMenuProps {
|
||||
timeSettings: TimeSettings;
|
||||
dispatch: Function;
|
||||
|
@ -33,48 +28,26 @@ export interface ImageFilterMenuProps {
|
|||
}
|
||||
|
||||
export class ImageFilterMenu
|
||||
extends React.Component<ImageFilterMenuProps, ImageFilterMenuState> {
|
||||
state: ImageFilterMenuState = {};
|
||||
extends React.Component<ImageFilterMenuProps, Partial<ImageFilterMenuState>> {
|
||||
constructor(props: ImageFilterMenuProps) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
UNSAFE_componentWillMount() {
|
||||
const { newestDate, toOldest } = this.props.imageAgeInfo;
|
||||
const beginDatetime = this.props.getConfigValue("photo_filter_begin");
|
||||
if (isString(beginDatetime) || isUndefined(beginDatetime)) {
|
||||
this.updateSliderState(beginDatetime);
|
||||
}
|
||||
this.setState({
|
||||
slider: toOldest + 1 - (beginDatetime
|
||||
? Math.abs(moment(beginDatetime.toString())
|
||||
.diff(moment(newestDate).clone(), "days")) : 0)
|
||||
});
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
updateSliderState = (begin: string | undefined) => {
|
||||
const { newestDate, toOldest } = this.props.imageAgeInfo;
|
||||
const offset = begin ? Math.abs(moment(begin.toString())
|
||||
.diff(moment(newestDate).clone(), "days")) : 0;
|
||||
this.setState({ slider: toOldest + 1 - offset });
|
||||
};
|
||||
|
||||
setValues = (update: StringValueUpdate) => {
|
||||
Object.entries(update).map(([key, value]) => {
|
||||
switch (key) {
|
||||
case "photo_filter_begin":
|
||||
this.updateSliderState(value);
|
||||
value
|
||||
? this.setState({
|
||||
beginDate: formatDate(value.toString(), this.props.timeSettings),
|
||||
beginTime: formatTime(value.toString(), this.props.timeSettings),
|
||||
})
|
||||
: this.setState({ beginDate: undefined, beginTime: undefined });
|
||||
break;
|
||||
case "photo_filter_end":
|
||||
value
|
||||
? this.setState({
|
||||
endDate: formatDate(value.toString(), this.props.timeSettings),
|
||||
endTime: formatTime(value.toString(), this.props.timeSettings),
|
||||
})
|
||||
: this.setState({ endDate: undefined, endTime: undefined });
|
||||
break;
|
||||
}
|
||||
});
|
||||
this.props.dispatch(setWebAppConfigValues(update));
|
||||
};
|
||||
UNSAFE_componentWillReceiveProps() {
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
updateState = () => {
|
||||
const beginDatetime = this.props.getConfigValue("photo_filter_begin");
|
||||
|
@ -97,27 +70,27 @@ export class ImageFilterMenu
|
|||
const input = e.currentTarget.value;
|
||||
this.setState({ [datetime]: input });
|
||||
const { beginDate, beginTime, endDate, endTime } = this.state;
|
||||
const { timeSettings } = this.props;
|
||||
const { dispatch, timeSettings } = this.props;
|
||||
let value = undefined;
|
||||
switch (datetime) {
|
||||
case "beginDate":
|
||||
value = offsetTime(input, beginTime || "00:00", timeSettings);
|
||||
this.setValues({ photo_filter_begin: value });
|
||||
dispatch(setWebAppConfigValue("photo_filter_begin", value));
|
||||
break;
|
||||
case "beginTime":
|
||||
if (beginDate) {
|
||||
value = offsetTime(beginDate, input, timeSettings);
|
||||
this.setValues({ photo_filter_begin: value });
|
||||
dispatch(setWebAppConfigValue("photo_filter_begin", value));
|
||||
}
|
||||
break;
|
||||
case "endDate":
|
||||
value = offsetTime(input, endTime || "00:00", timeSettings);
|
||||
this.setValues({ photo_filter_end: value });
|
||||
dispatch(setWebAppConfigValue("photo_filter_end", value));
|
||||
break;
|
||||
case "endTime":
|
||||
if (endDate) {
|
||||
value = offsetTime(endDate, input, timeSettings);
|
||||
this.setValues({ photo_filter_end: value });
|
||||
dispatch(setWebAppConfigValue("photo_filter_end", value));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -127,12 +100,13 @@ export class ImageFilterMenu
|
|||
sliderChange = (slider: number) => {
|
||||
const { newestDate, toOldest } = this.props.imageAgeInfo;
|
||||
this.setState({ slider });
|
||||
const { timeSettings } = this.props;
|
||||
const { dispatch, timeSettings } = this.props;
|
||||
const calcDate = (day: number) =>
|
||||
moment(newestDate).subtract(toOldest - day, "days").toISOString();
|
||||
const begin = offsetTime(calcDate(slider - 1), "00:00", timeSettings);
|
||||
const end = offsetTime(calcDate(slider), "00:00", timeSettings);
|
||||
this.setValues({ photo_filter_begin: begin, photo_filter_end: end });
|
||||
dispatch(setWebAppConfigValue("photo_filter_begin", begin));
|
||||
dispatch(setWebAppConfigValue("photo_filter_end", end));
|
||||
}
|
||||
|
||||
renderLabel = (day: number) => {
|
||||
|
@ -217,14 +191,3 @@ export class ImageFilterMenu
|
|||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
type StringValueUpdate = Partial<Record<StringConfigKey, string | undefined>>;
|
||||
|
||||
const setWebAppConfigValues = (update: StringValueUpdate) =>
|
||||
(dispatch: Function, getState: GetState) => {
|
||||
const webAppConfig = getWebAppConfig(getState().resources.index);
|
||||
if (webAppConfig) {
|
||||
dispatch(edit(webAppConfig, update));
|
||||
dispatch(save(webAppConfig.uuid));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
export * from "./farmbot/farmbot_layer";
|
||||
export * from "./plants/plant_layer";
|
||||
export * from "./points/point_layer";
|
||||
export * from "./weeds/weed_layer";
|
||||
export * from "./spread/spread_layer";
|
||||
export * from "./tool_slots/tool_slot_layer";
|
||||
export * from "./images/image_layer";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
let mockPath = "/app/designer/plants";
|
||||
jest.mock("../../../../../history", () => ({
|
||||
getPathArray: jest.fn(() => mockPath.split("/"))
|
||||
getPathArray: jest.fn(() => { return mockPath.split("/"); })
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
|
@ -31,7 +31,6 @@ describe("<PlantLayer/>", () => {
|
|||
activeDragXY: { x: undefined, y: undefined, z: undefined },
|
||||
animate: true,
|
||||
hoveredPlant: undefined,
|
||||
interactions: true,
|
||||
});
|
||||
|
||||
it("shows plants", () => {
|
||||
|
@ -60,19 +59,14 @@ describe("<PlantLayer/>", () => {
|
|||
it("is in clickable mode", () => {
|
||||
mockPath = "/app/designer/plants";
|
||||
const p = fakeProps();
|
||||
p.interactions = true;
|
||||
p.plants[0].body.id = 1;
|
||||
const wrapper = svgMount(<PlantLayer {...p} />);
|
||||
expect(wrapper.find("Link").props().style).toEqual({
|
||||
cursor: "pointer"
|
||||
});
|
||||
expect(wrapper.find("Link").props().style).toEqual({});
|
||||
});
|
||||
|
||||
it("is in non-clickable mode", () => {
|
||||
mockPath = "/app/designer/plants/crop_search/mint/add";
|
||||
const p = fakeProps();
|
||||
p.interactions = false;
|
||||
p.plants[0].body.id = 1;
|
||||
|
||||
const wrapper = svgMount(<PlantLayer {...p} />);
|
||||
expect(wrapper.find("Link").props().style)
|
||||
.toEqual({ pointerEvents: "none" });
|
||||
|
@ -117,12 +111,22 @@ describe("<PlantLayer/>", () => {
|
|||
expect(wrapper.find("GardenPlant").props().selected).toEqual(true);
|
||||
});
|
||||
|
||||
it("allows clicking of unsaved plants", () => {
|
||||
const p = fakeProps();
|
||||
const plant = fakePlant();
|
||||
plant.body.id = 1;
|
||||
p.plants = [plant];
|
||||
const wrapper = svgMount(<PlantLayer {...p} />);
|
||||
expect((wrapper.find("Link").props()).style).toEqual({});
|
||||
});
|
||||
|
||||
it("doesn't allow clicking of unsaved plants", () => {
|
||||
const p = fakeProps();
|
||||
p.interactions = false;
|
||||
p.plants[0].body.id = 0;
|
||||
const plant = fakePlant();
|
||||
plant.body.id = 0;
|
||||
p.plants = [plant];
|
||||
const wrapper = svgMount(<PlantLayer {...p} />);
|
||||
expect(wrapper.find("Link").props().style)
|
||||
expect((wrapper.find("Link").props()).style)
|
||||
.toEqual({ pointerEvents: "none" });
|
||||
});
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as React from "react";
|
|||
import { GardenPlant } from "./garden_plant";
|
||||
import { PlantLayerProps, Mode } from "../../interfaces";
|
||||
import { unpackUUID } from "../../../../util";
|
||||
import { getMode } from "../../util";
|
||||
import { maybeNoPointer, getMode } from "../../util";
|
||||
import { Link } from "../../../../link";
|
||||
|
||||
export function PlantLayer(props: PlantLayerProps) {
|
||||
|
@ -44,12 +44,9 @@ export function PlantLayer(props: PlantLayerProps) {
|
|||
activeDragXY={activeDragXY}
|
||||
hovered={hovered}
|
||||
animate={animate} />;
|
||||
const style: React.SVGProps<SVGGElement>["style"] =
|
||||
(props.interactions && p.body.id)
|
||||
? { cursor: "pointer" } : { pointerEvents: "none" };
|
||||
const wrapperProps = {
|
||||
className: "plant-link-wrapper",
|
||||
style,
|
||||
style: maybeNoPointer(p.body.id ? {} : { pointerEvents: "none" }),
|
||||
key: p.uuid,
|
||||
};
|
||||
return (getMode() === Mode.editGroup || getMode() === Mode.boxSelect)
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
jest.mock("../../../../../history", () => ({
|
||||
history: { push: jest.fn() },
|
||||
getPathArray: jest.fn(),
|
||||
}));
|
||||
jest.mock("../../../../../history", () => ({ history: { push: jest.fn() } }));
|
||||
|
||||
import * as React from "react";
|
||||
import { GardenPoint } from "../garden_point";
|
||||
|
@ -58,9 +55,10 @@ describe("<GardenPoint/>", () => {
|
|||
|
||||
it("opens point info", () => {
|
||||
const p = fakeProps();
|
||||
p.point.body.name = "weed";
|
||||
const wrapper = svgMount(<GardenPoint {...p} />);
|
||||
wrapper.find("g").simulate("click");
|
||||
expect(history.push).toHaveBeenCalledWith(
|
||||
`/app/designer/points/${p.point.body.id}`);
|
||||
`/app/designer/weeds/${p.point.body.id}`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,12 +19,10 @@ describe("<PointLayer/>", () => {
|
|||
mapTransformProps: fakeMapTransformProps(),
|
||||
hoveredPoint: undefined,
|
||||
dispatch: jest.fn(),
|
||||
interactions: true,
|
||||
});
|
||||
|
||||
it("shows points", () => {
|
||||
const p = fakeProps();
|
||||
p.interactions = false;
|
||||
const wrapper = svgMount(<PointLayer {...p} />);
|
||||
const layer = wrapper.find("#point-layer");
|
||||
expect(layer.find(GardenPoint).html()).toContain("r=\"100\"");
|
||||
|
@ -42,7 +40,6 @@ describe("<PointLayer/>", () => {
|
|||
it("allows point mode interaction", () => {
|
||||
mockPath = "/app/designer/points";
|
||||
const p = fakeProps();
|
||||
p.interactions = true;
|
||||
const wrapper = svgMount(<PointLayer {...p} />);
|
||||
const layer = wrapper.find("#point-layer");
|
||||
expect(layer.props().style).toEqual({});
|
||||
|
|
|
@ -2,7 +2,8 @@ import * as React from "react";
|
|||
import { GardenPointProps } from "../../interfaces";
|
||||
import { transformXY } from "../../util";
|
||||
import { Actions } from "../../../../constants";
|
||||
import { mapPointClickAction } from "../../actions";
|
||||
import { history } from "../../../../history";
|
||||
import { isAWeed } from "../../../points/weeds_inventory";
|
||||
|
||||
export const GardenPoint = (props: GardenPointProps) => {
|
||||
|
||||
|
@ -18,11 +19,11 @@ export const GardenPoint = (props: GardenPointProps) => {
|
|||
const { id, x, y, meta } = point.body;
|
||||
const { qx, qy } = transformXY(x, y, mapTransformProps);
|
||||
const color = meta.color || "green";
|
||||
return <g id={`point-${id}`} className={"map-point"} stroke={color}
|
||||
const panel = isAWeed(point.body.name, meta.type) ? "weeds" : "points";
|
||||
return <g id={"point-" + id} className={"map-point"} stroke={color}
|
||||
onMouseEnter={iconHover("start")}
|
||||
onMouseLeave={iconHover("end")}
|
||||
onClick={mapPointClickAction(props.dispatch, point.uuid,
|
||||
`/app/designer/points/${id}`)}>
|
||||
onClick={() => history.push(`/app/designer/${panel}/${id}`)}>
|
||||
<circle id="point-radius" cx={qx} cy={qy} r={point.body.radius}
|
||||
fill={hovered ? color : "transparent"} />
|
||||
<circle id="point-center" cx={qx} cy={qy} r={2} />
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import * as React from "react";
|
||||
import { TaggedGenericPointer } from "farmbot";
|
||||
import { GardenPoint } from "./garden_point";
|
||||
import { MapTransformProps } from "../../interfaces";
|
||||
import { MapTransformProps, Mode } from "../../interfaces";
|
||||
import { getMode } from "../../util";
|
||||
|
||||
export interface PointLayerProps {
|
||||
visible: boolean;
|
||||
|
@ -9,14 +10,13 @@ export interface PointLayerProps {
|
|||
mapTransformProps: MapTransformProps;
|
||||
hoveredPoint: string | undefined;
|
||||
dispatch: Function;
|
||||
interactions: boolean;
|
||||
}
|
||||
|
||||
export function PointLayer(props: PointLayerProps) {
|
||||
const { visible, genericPoints, mapTransformProps, hoveredPoint } = props;
|
||||
const style: React.CSSProperties =
|
||||
props.interactions ? {} : { pointerEvents: "none" };
|
||||
return <g id={"point-layer"} style={style}>
|
||||
getMode() === Mode.points ? {} : { pointerEvents: "none" };
|
||||
return <g id="point-layer" style={style}>
|
||||
{visible &&
|
||||
genericPoints.map(p =>
|
||||
<GardenPoint
|
||||
|
|
|
@ -73,7 +73,7 @@ export class SpreadCircle extends
|
|||
React.Component<SpreadCircleProps, SpreadCircleState> {
|
||||
state: SpreadCircleState = { spread: undefined };
|
||||
|
||||
componentDidMount = () => {
|
||||
UNSAFE_componentWillMount = () => {
|
||||
cachedCrop(this.props.plant.body.openfarm_slug)
|
||||
.then(({ spread }) => this.setState({ spread }));
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ describe("<ToolSlotLayer/>", () => {
|
|||
pointer_type: "ToolSlot",
|
||||
tool_id: undefined,
|
||||
name: "Name",
|
||||
radius: 50,
|
||||
x: 1,
|
||||
y: 2,
|
||||
z: 3,
|
||||
|
@ -37,7 +38,6 @@ describe("<ToolSlotLayer/>", () => {
|
|||
mapTransformProps: fakeMapTransformProps(),
|
||||
dispatch: jest.fn(),
|
||||
hoveredToolSlot: undefined,
|
||||
interactions: true,
|
||||
};
|
||||
}
|
||||
it("toggles visibility off", () => {
|
||||
|
@ -61,19 +61,9 @@ describe("<ToolSlotLayer/>", () => {
|
|||
expect(history.push).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("is in clickable mode", () => {
|
||||
mockPath = "/app/designer/plants/crop_search/mint/add";
|
||||
const p = fakeProps();
|
||||
p.interactions = true;
|
||||
const wrapper = shallow(<ToolSlotLayer {...p} />);
|
||||
expect(wrapper.find("g").props().style)
|
||||
.toEqual({ cursor: "pointer" });
|
||||
});
|
||||
|
||||
it("is in non-clickable mode", () => {
|
||||
mockPath = "/app/designer/plants/crop_search/mint/add";
|
||||
const p = fakeProps();
|
||||
p.interactions = false;
|
||||
const wrapper = shallow(<ToolSlotLayer {...p} />);
|
||||
expect(wrapper.find("g").props().style)
|
||||
.toEqual({ pointerEvents: "none" });
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
jest.mock("../../../../../history", () => ({
|
||||
history: { push: jest.fn() },
|
||||
getPathArray: jest.fn(),
|
||||
}));
|
||||
jest.mock("../../../../../history", () => ({ history: { push: jest.fn() } }));
|
||||
|
||||
import * as React from "react";
|
||||
import { ToolSlotPoint, TSPProps } from "../tool_slot_point";
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as React from "react";
|
|||
import { SlotWithTool, UUID } from "../../../../resources/interfaces";
|
||||
import { ToolSlotPoint } from "./tool_slot_point";
|
||||
import { MapTransformProps } from "../../interfaces";
|
||||
import { maybeNoPointer } from "../../util";
|
||||
|
||||
export interface ToolSlotLayerProps {
|
||||
visible: boolean;
|
||||
|
@ -10,7 +11,6 @@ export interface ToolSlotLayerProps {
|
|||
mapTransformProps: MapTransformProps;
|
||||
dispatch: Function;
|
||||
hoveredToolSlot: UUID | undefined;
|
||||
interactions: boolean;
|
||||
}
|
||||
|
||||
export function ToolSlotLayer(props: ToolSlotLayerProps) {
|
||||
|
@ -18,9 +18,7 @@ export function ToolSlotLayer(props: ToolSlotLayerProps) {
|
|||
|
||||
return <g
|
||||
id="toolslot-layer"
|
||||
style={props.interactions
|
||||
? { cursor: "pointer" }
|
||||
: { pointerEvents: "none" }}>
|
||||
style={maybeNoPointer({ cursor: "pointer" })}>
|
||||
{visible &&
|
||||
slots.map(slot =>
|
||||
<ToolSlotPoint
|
||||
|
|
|
@ -5,8 +5,8 @@ import { MapTransformProps } from "../../interfaces";
|
|||
import { ToolbaySlot, ToolNames, Tool, GantryToolSlot } from "./tool_graphics";
|
||||
import { ToolLabel } from "./tool_label";
|
||||
import { includes } from "lodash";
|
||||
import { history } from "../../../../history";
|
||||
import { t } from "../../../../i18next_wrapper";
|
||||
import { mapPointClickAction } from "../../actions";
|
||||
|
||||
export interface TSPProps {
|
||||
slot: SlotWithTool;
|
||||
|
@ -30,27 +30,25 @@ export const reduceToolName = (raw: string | undefined) => {
|
|||
};
|
||||
|
||||
export const ToolSlotPoint = (props: TSPProps) => {
|
||||
const { tool, toolSlot } = props.slot;
|
||||
const {
|
||||
id, x, y, pullout_direction, gantry_mounted
|
||||
} = toolSlot.body;
|
||||
} = props.slot.toolSlot.body;
|
||||
const { mapTransformProps, botPositionX } = props;
|
||||
const { quadrant, xySwap } = mapTransformProps;
|
||||
const xPosition = gantry_mounted ? (botPositionX || 0) : x;
|
||||
const { qx, qy } = transformXY(xPosition, y, props.mapTransformProps);
|
||||
const toolName = tool ? tool.body.name : t("Empty");
|
||||
const hovered = toolSlot.uuid === props.hoveredToolSlot;
|
||||
const toolName = props.slot.tool ? props.slot.tool.body.name : t("Empty");
|
||||
const hovered = props.slot.toolSlot.uuid === props.hoveredToolSlot;
|
||||
const toolProps = {
|
||||
x: qx,
|
||||
y: qy,
|
||||
hovered,
|
||||
dispatch: props.dispatch,
|
||||
uuid: toolSlot.uuid,
|
||||
uuid: props.slot.toolSlot.uuid,
|
||||
xySwap,
|
||||
};
|
||||
return <g id={"toolslot-" + id}
|
||||
onClick={mapPointClickAction(props.dispatch, toolSlot.uuid,
|
||||
`/app/designer/tool-slots/${id}`)}>
|
||||
onClick={() => history.push(`/app/designer/tool-slots/${id}`)}>
|
||||
{pullout_direction && !gantry_mounted &&
|
||||
<ToolbaySlot
|
||||
id={id}
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
jest.mock("../../../../../history", () => ({
|
||||
history: { push: jest.fn() },
|
||||
getPathArray: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { GardenWeed } from "../garden_weed";
|
||||
import { GardenWeedProps } from "../../../interfaces";
|
||||
import { fakeWeed } from "../../../../../__test_support__/fake_state/resources";
|
||||
import {
|
||||
fakeMapTransformProps,
|
||||
} from "../../../../../__test_support__/map_transform_props";
|
||||
import { Actions } from "../../../../../constants";
|
||||
import { history } from "../../../../../history";
|
||||
import { svgMount } from "../../../../../__test_support__/svg_mount";
|
||||
|
||||
describe("<GardenWeed />", () => {
|
||||
const fakeProps = (): GardenWeedProps => ({
|
||||
mapTransformProps: fakeMapTransformProps(),
|
||||
weed: fakeWeed(),
|
||||
hovered: false,
|
||||
dispatch: jest.fn(),
|
||||
current: false,
|
||||
selected: false,
|
||||
animate: false,
|
||||
spreadVisible: true,
|
||||
});
|
||||
|
||||
it("renders weed", () => {
|
||||
const p = fakeProps();
|
||||
p.weed.body.meta.color = undefined;
|
||||
const wrapper = svgMount(<GardenWeed {...p} />);
|
||||
expect(wrapper.find("#weed-radius").props().r).toEqual(100);
|
||||
expect(wrapper.find("#weed-radius").props().opacity).toEqual(0.5);
|
||||
expect(wrapper.find("stop").first().props().stopColor).toEqual("red");
|
||||
});
|
||||
|
||||
it("renders weed color", () => {
|
||||
const p = fakeProps();
|
||||
p.weed.body.meta.color = "orange";
|
||||
const wrapper = svgMount(<GardenWeed {...p} />);
|
||||
expect(wrapper.find("#weed-radius").props().r).toEqual(100);
|
||||
expect(wrapper.find("#weed-radius").props().opacity).toEqual(0.5);
|
||||
expect(wrapper.find("stop").first().props().stopColor).toEqual("orange");
|
||||
});
|
||||
|
||||
it("animates", () => {
|
||||
const p = fakeProps();
|
||||
p.animate = true;
|
||||
const wrapper = svgMount(<GardenWeed {...p} />);
|
||||
expect(wrapper.find(".soil-cloud").length).toEqual(1);
|
||||
expect(wrapper.find("image").hasClass("animate")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("hovers weed", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = svgMount(<GardenWeed {...p} />);
|
||||
wrapper.find("g").first().simulate("mouseEnter");
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.TOGGLE_HOVERED_POINT,
|
||||
payload: p.weed.uuid
|
||||
});
|
||||
});
|
||||
|
||||
it("is hovered", () => {
|
||||
const p = fakeProps();
|
||||
p.hovered = true;
|
||||
const wrapper = svgMount(<GardenWeed {...p} />);
|
||||
expect(wrapper.find("#weed-radius").props().opacity).toEqual(1);
|
||||
});
|
||||
|
||||
it("un-hovers weed", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = svgMount(<GardenWeed {...p} />);
|
||||
wrapper.find("g").first().simulate("mouseLeave");
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.TOGGLE_HOVERED_POINT,
|
||||
payload: undefined
|
||||
});
|
||||
});
|
||||
|
||||
it("opens weed info", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = svgMount(<GardenWeed {...p} />);
|
||||
wrapper.find("g").first().simulate("click");
|
||||
expect(history.push).toHaveBeenCalledWith(
|
||||
`/app/designer/weeds/${p.weed.body.id}`);
|
||||
});
|
||||
});
|
|
@ -1,66 +0,0 @@
|
|||
let mockPath = "/app/designer/plants";
|
||||
jest.mock("../../../../../history", () => ({
|
||||
getPathArray: jest.fn(() => mockPath.split("/")),
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { WeedLayer, WeedLayerProps } from "../weed_layer";
|
||||
import { fakeWeed } from "../../../../../__test_support__/fake_state/resources";
|
||||
import {
|
||||
fakeMapTransformProps,
|
||||
} from "../../../../../__test_support__/map_transform_props";
|
||||
import { GardenWeed } from "../garden_weed";
|
||||
import { svgMount } from "../../../../../__test_support__/svg_mount";
|
||||
|
||||
describe("<WeedLayer/>", () => {
|
||||
const fakeProps = (): WeedLayerProps => ({
|
||||
visible: true,
|
||||
spreadVisible: true,
|
||||
weeds: [fakeWeed()],
|
||||
mapTransformProps: fakeMapTransformProps(),
|
||||
hoveredPoint: undefined,
|
||||
dispatch: jest.fn(),
|
||||
currentPoint: undefined,
|
||||
boxSelected: undefined,
|
||||
groupSelected: [],
|
||||
animate: false,
|
||||
interactions: true,
|
||||
});
|
||||
|
||||
it("shows weeds", () => {
|
||||
const p = fakeProps();
|
||||
p.interactions = false;
|
||||
const wrapper = svgMount(<WeedLayer {...p} />);
|
||||
const layer = wrapper.find("#weeds-layer");
|
||||
expect(layer.find(GardenWeed).html()).toContain("r=\"100\"");
|
||||
expect(layer.props().style).toEqual({ pointerEvents: "none" });
|
||||
});
|
||||
|
||||
it("toggles visibility off", () => {
|
||||
const p = fakeProps();
|
||||
p.visible = false;
|
||||
const wrapper = svgMount(<WeedLayer {...p} />);
|
||||
const layer = wrapper.find("#weeds-layer");
|
||||
expect(layer.find(GardenWeed).length).toEqual(0);
|
||||
});
|
||||
|
||||
it("allows weed mode interaction", () => {
|
||||
mockPath = "/app/designer/weeds";
|
||||
const p = fakeProps();
|
||||
p.interactions = true;
|
||||
const wrapper = svgMount(<WeedLayer {...p} />);
|
||||
const layer = wrapper.find("#weeds-layer");
|
||||
expect(layer.props().style).toEqual({ cursor: "pointer" });
|
||||
});
|
||||
|
||||
it("is selected", () => {
|
||||
mockPath = "/app/designer/weeds";
|
||||
const p = fakeProps();
|
||||
const weed = fakeWeed();
|
||||
p.weeds = [weed];
|
||||
p.boxSelected = [weed.uuid];
|
||||
const wrapper = svgMount(<WeedLayer {...p} />);
|
||||
const layer = wrapper.find("#weeds-layer");
|
||||
expect(layer.find(GardenWeed).props().selected).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -1,69 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { GardenWeedProps } from "../../interfaces";
|
||||
import { transformXY } from "../../util";
|
||||
import { Actions } from "../../../../constants";
|
||||
import { Color } from "../../../../ui";
|
||||
import { mapPointClickAction } from "../../actions";
|
||||
|
||||
export const DEFAULT_WEED_ICON = "/app-resources/img/generic-weed.svg";
|
||||
|
||||
export const GardenWeed = (props: GardenWeedProps) => {
|
||||
|
||||
const iconHover = (action: "start" | "end") => () => {
|
||||
const hover = action === "start";
|
||||
props.dispatch({
|
||||
type: Actions.TOGGLE_HOVERED_POINT,
|
||||
payload: hover ? props.weed.uuid : undefined
|
||||
});
|
||||
};
|
||||
|
||||
const { weed, mapTransformProps, hovered, current, selected, animate } = props;
|
||||
const { id, x, y, meta, radius } = weed.body;
|
||||
const { qx, qy } = transformXY(x, y, mapTransformProps);
|
||||
const color = meta.color || "red";
|
||||
const stopOpacity = ["gray", "pink", "orange"].includes(color) ? 0.5 : 0.25;
|
||||
const className = [
|
||||
"weed-image", `is-chosen-${current || selected}`, animate ? "animate" : "",
|
||||
].join(" ");
|
||||
const iconRadius = hovered ? radius * 0.88 : radius * 0.8;
|
||||
return <g id={`weed-${id}`} className={`map-weed ${color}`}
|
||||
onMouseEnter={iconHover("start")}
|
||||
onMouseLeave={iconHover("end")}
|
||||
onClick={mapPointClickAction(props.dispatch, weed.uuid,
|
||||
`/app/designer/weeds/${id}`)}>
|
||||
<defs>
|
||||
<radialGradient id={`Weed${id}Gradient`}>
|
||||
<stop offset="90%" stopColor={color} stopOpacity={stopOpacity} />
|
||||
<stop offset="100%" stopColor={color} stopOpacity={0} />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
{animate &&
|
||||
<circle
|
||||
className="soil-cloud"
|
||||
cx={qx}
|
||||
cy={qy}
|
||||
r={radius}
|
||||
fill={Color.soilCloud}
|
||||
fillOpacity={0} />}
|
||||
|
||||
{props.spreadVisible &&
|
||||
<circle
|
||||
id={"weed-radius"}
|
||||
cx={qx}
|
||||
cy={qy}
|
||||
r={radius}
|
||||
fill={`url(#Weed${id}Gradient)`}
|
||||
opacity={hovered ? 1 : 0.5} />}
|
||||
|
||||
<g id="weed-icon">
|
||||
<image
|
||||
className={className}
|
||||
xlinkHref={DEFAULT_WEED_ICON}
|
||||
height={iconRadius * 2}
|
||||
width={iconRadius * 2}
|
||||
x={qx - iconRadius}
|
||||
y={qy - iconRadius} />
|
||||
</g>
|
||||
</g>;
|
||||
};
|
|
@ -1,43 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { TaggedWeedPointer } from "farmbot";
|
||||
import { GardenWeed } from "./garden_weed";
|
||||
import { MapTransformProps } from "../../interfaces";
|
||||
import { UUID } from "../../../../resources/interfaces";
|
||||
|
||||
export interface WeedLayerProps {
|
||||
visible: boolean;
|
||||
spreadVisible: boolean;
|
||||
weeds: TaggedWeedPointer[];
|
||||
mapTransformProps: MapTransformProps;
|
||||
hoveredPoint: UUID | undefined;
|
||||
currentPoint: UUID | undefined;
|
||||
boxSelected: UUID[] | undefined;
|
||||
groupSelected: UUID[];
|
||||
dispatch: Function;
|
||||
animate: boolean;
|
||||
interactions: boolean;
|
||||
}
|
||||
|
||||
export function WeedLayer(props: WeedLayerProps) {
|
||||
const { visible, weeds, mapTransformProps } = props;
|
||||
return <g id={"weeds-layer"} style={props.interactions
|
||||
? { cursor: "pointer" } : { pointerEvents: "none" }}>
|
||||
{visible &&
|
||||
weeds.map(p => {
|
||||
const current = p.uuid === props.currentPoint;
|
||||
const hovered = p.uuid === props.hoveredPoint;
|
||||
const selectedByBox = !!props.boxSelected?.includes(p.uuid);
|
||||
const selectedByGroup = props.groupSelected.includes(p.uuid);
|
||||
return <GardenWeed
|
||||
weed={p}
|
||||
key={p.uuid}
|
||||
hovered={hovered}
|
||||
current={current}
|
||||
selected={selectedByBox || selectedByGroup}
|
||||
animate={props.animate}
|
||||
spreadVisible={props.spreadVisible}
|
||||
dispatch={props.dispatch}
|
||||
mapTransformProps={mapTransformProps} />;
|
||||
})}
|
||||
</g>;
|
||||
}
|
|
@ -7,7 +7,6 @@ import {
|
|||
import {
|
||||
fakeMapTransformProps,
|
||||
} from "../../../../../__test_support__/map_transform_props";
|
||||
import { ReactWrapper } from "enzyme";
|
||||
|
||||
describe("<ZonesLayer />", () => {
|
||||
const fakeProps = (): ZonesLayerProps => ({
|
||||
|
@ -19,7 +18,6 @@ describe("<ZonesLayer />", () => {
|
|||
y: { value: 1500, isDefault: true }
|
||||
},
|
||||
mapTransformProps: fakeMapTransformProps(),
|
||||
startDrag: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
|
@ -27,27 +25,6 @@ describe("<ZonesLayer />", () => {
|
|||
expect(wrapper.find(".zones-layer").length).toEqual(1);
|
||||
});
|
||||
|
||||
const expectSolid = (zone2D: ReactWrapper) => {
|
||||
const zoneProps = zone2D.find("rect").props();
|
||||
expect(zoneProps.fill).toEqual(undefined);
|
||||
expect(zoneProps.stroke).toEqual(undefined);
|
||||
expect(zoneProps.strokeDasharray).toEqual(undefined);
|
||||
expect(zoneProps.strokeWidth).toEqual(undefined);
|
||||
};
|
||||
|
||||
const expectOutline = (zone2D: ReactWrapper) => {
|
||||
const zoneProps = zone2D.find("rect").props();
|
||||
expect(zoneProps.fill).toEqual("none");
|
||||
expect(zoneProps.stroke).toEqual("white");
|
||||
expect(zoneProps.strokeDasharray).toEqual(15);
|
||||
expect(zoneProps.strokeWidth).toEqual(4);
|
||||
};
|
||||
|
||||
const expectNone = (zone2D: ReactWrapper) => {
|
||||
expect(zone2D.html()).toEqual(
|
||||
"<g id=\"zones-2D-1\" class=\"current\"></g>");
|
||||
};
|
||||
|
||||
it("renders current group's zones: 2D", () => {
|
||||
const p = fakeProps();
|
||||
p.visible = false;
|
||||
|
@ -60,7 +37,6 @@ describe("<ZonesLayer />", () => {
|
|||
expect(wrapper.find("#zones-0D-1").length).toEqual(0);
|
||||
expect(wrapper.find("#zones-1D-1").length).toEqual(0);
|
||||
expect(wrapper.find("#zones-2D-1").length).toEqual(1);
|
||||
expectSolid(wrapper.find("#zones-2D-1"));
|
||||
expect(wrapper.find("#zones-2D-2").length).toEqual(0);
|
||||
});
|
||||
|
||||
|
@ -73,22 +49,19 @@ describe("<ZonesLayer />", () => {
|
|||
const wrapper = svgMount(<ZonesLayer {...p} />);
|
||||
expect(wrapper.find("#zones-0D-1").length).toEqual(0);
|
||||
expect(wrapper.find("#zones-1D-1").length).toEqual(1);
|
||||
expect(wrapper.find("#zones-2D-1").length).toEqual(1);
|
||||
expectNone(wrapper.find("#zones-2D-1"));
|
||||
expect(wrapper.find("#zones-2D-1").length).toEqual(0);
|
||||
});
|
||||
|
||||
it("renders current group's zones: 0D", () => {
|
||||
const p = fakeProps();
|
||||
p.visible = false;
|
||||
p.groups[0].body.id = 1;
|
||||
p.groups[0].body.criteria.number_gt = { x: 10 };
|
||||
p.groups[0].body.criteria.number_eq = { x: [100], y: [100] };
|
||||
p.currentGroup = p.groups[0].uuid;
|
||||
const wrapper = svgMount(<ZonesLayer {...p} />);
|
||||
expect(wrapper.find("#zones-0D-1").length).toEqual(1);
|
||||
expect(wrapper.find("#zones-1D-1").length).toEqual(0);
|
||||
expect(wrapper.find("#zones-2D-1").length).toEqual(1);
|
||||
expectOutline(wrapper.find("#zones-2D-1"));
|
||||
expect(wrapper.find("#zones-2D-1").length).toEqual(0);
|
||||
});
|
||||
|
||||
it("renders current group's zones: none", () => {
|
||||
|
@ -97,20 +70,15 @@ describe("<ZonesLayer />", () => {
|
|||
p.groups[0].body.id = 1;
|
||||
p.currentGroup = p.groups[0].uuid;
|
||||
const wrapper = svgMount(<ZonesLayer {...p} />);
|
||||
expect(wrapper.html()).toEqual(
|
||||
`<svg>
|
||||
<g class=\"zones-layer\" style=\"cursor: pointer;\">
|
||||
<g id=\"zones-2D-1\" class=\"current\">
|
||||
</g>
|
||||
</g>
|
||||
</svg>`.replace(/[ ]{2,}/g, "").replace(/[^\S ]/g, ""));
|
||||
expect(wrapper.html())
|
||||
.toEqual("<svg><g class=\"zones-layer\"></g></svg>");
|
||||
});
|
||||
|
||||
it("doesn't render current group's zones", () => {
|
||||
const p = fakeProps();
|
||||
p.visible = false;
|
||||
const wrapper = svgMount(<ZonesLayer {...p} />);
|
||||
expect(wrapper.html()).toEqual(
|
||||
"<svg><g class=\"zones-layer\" style=\"cursor: pointer;\"></g></svg>");
|
||||
expect(wrapper.html())
|
||||
.toEqual("<svg><g class=\"zones-layer\"></g></svg>");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
jest.mock("../../../../../history", () => ({ history: { push: jest.fn() } }));
|
||||
|
||||
import * as React from "react";
|
||||
import { svgMount } from "../../../../../__test_support__/svg_mount";
|
||||
import {
|
||||
Zones0D, ZonesProps, Zones1D, Zones2D, getZoneType, ZoneType, spaceSelected,
|
||||
Zones0D, ZonesProps, Zones1D, Zones2D, getZoneType, ZoneType,
|
||||
} from "../zones";
|
||||
import {
|
||||
fakePointGroup,
|
||||
|
@ -12,7 +10,6 @@ import {
|
|||
fakeMapTransformProps,
|
||||
} from "../../../../../__test_support__/map_transform_props";
|
||||
import { DEFAULT_CRITERIA } from "../../../../point_groups/criteria/interfaces";
|
||||
import { history } from "../../../../../history";
|
||||
|
||||
const fakeProps = (): ZonesProps => ({
|
||||
group: fakePointGroup(),
|
||||
|
@ -60,15 +57,6 @@ describe("<Zones0D />", () => {
|
|||
expect(wrapper.find("#zones-0D-1").length).toEqual(1);
|
||||
expect(wrapper.find("circle").length).toEqual(2);
|
||||
});
|
||||
|
||||
it("opens group", () => {
|
||||
const p = fakeProps();
|
||||
p.group.body.id = 1;
|
||||
p.group.body.criteria.number_eq = { x: [100], y: [200, 300] };
|
||||
const wrapper = svgMount(<Zones0D {...p} />);
|
||||
wrapper.find("#zones-0D-1").simulate("click");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/groups/1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("<Zones1D />", () => {
|
||||
|
@ -116,15 +104,6 @@ describe("<Zones1D />", () => {
|
|||
expect(wrapper.find("#zones-1D-1").length).toEqual(1);
|
||||
expect(wrapper.find("line").length).toEqual(2);
|
||||
});
|
||||
|
||||
it("opens group", () => {
|
||||
const p = fakeProps();
|
||||
p.group.body.id = 1;
|
||||
p.group.body.criteria.number_eq = { x: [], y: [200, 300] };
|
||||
const wrapper = svgMount(<Zones1D {...p} />);
|
||||
wrapper.find("#zones-1D-1").simulate("click");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/groups/1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("<Zones2D />", () => {
|
||||
|
@ -158,16 +137,6 @@ describe("<Zones2D />", () => {
|
|||
expect(wrapper.find("#zones-2D-1").length).toEqual(1);
|
||||
expect(wrapper.find("rect").length).toEqual(1);
|
||||
});
|
||||
|
||||
it("opens group", () => {
|
||||
const p = fakeProps();
|
||||
p.group.body.id = 1;
|
||||
p.group.body.criteria.number_gt = { x: 100, y: 200 };
|
||||
p.group.body.criteria.number_lt = { x: 300, y: 400 };
|
||||
const wrapper = svgMount(<Zones2D {...p} />);
|
||||
wrapper.find("#zones-2D-1").simulate("click");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/groups/1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getZoneType()", () => {
|
||||
|
@ -194,58 +163,3 @@ describe("getZoneType()", () => {
|
|||
expect(getZoneType(group)).toEqual(ZoneType.points);
|
||||
});
|
||||
});
|
||||
|
||||
describe("spaceSelected()", () => {
|
||||
const botSize = {
|
||||
x: { value: 3000, isDefault: true },
|
||||
y: { value: 1500, isDefault: true }
|
||||
};
|
||||
|
||||
it("is selected: area", () => {
|
||||
const group = fakePointGroup();
|
||||
group.body.criteria.number_eq = {};
|
||||
group.body.criteria.number_lt = {};
|
||||
group.body.criteria.number_gt = {};
|
||||
expect(spaceSelected(group, botSize)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("isn't selected: area", () => {
|
||||
const group = fakePointGroup();
|
||||
group.body.criteria.number_eq = {};
|
||||
group.body.criteria.number_lt = { x: 100 };
|
||||
group.body.criteria.number_gt = { x: 200 };
|
||||
expect(spaceSelected(group, botSize)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("is selected: lines", () => {
|
||||
const group = fakePointGroup();
|
||||
group.body.criteria.number_eq = { x: [0] };
|
||||
group.body.criteria.number_lt = {};
|
||||
group.body.criteria.number_gt = {};
|
||||
expect(spaceSelected(group, botSize)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("isn't selected: lines", () => {
|
||||
const group = fakePointGroup();
|
||||
group.body.criteria.number_eq = { x: [0] };
|
||||
group.body.criteria.number_lt = {};
|
||||
group.body.criteria.number_gt = { x: 100 };
|
||||
expect(spaceSelected(group, botSize)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("is selected: points", () => {
|
||||
const group = fakePointGroup();
|
||||
group.body.criteria.number_eq = { x: [0], y: [0] };
|
||||
group.body.criteria.number_lt = {};
|
||||
group.body.criteria.number_gt = {};
|
||||
expect(spaceSelected(group, botSize)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("isn't selected: points", () => {
|
||||
const group = fakePointGroup();
|
||||
group.body.criteria.number_eq = { x: [0], y: [0] };
|
||||
group.body.criteria.number_lt = { x: 0 };
|
||||
group.body.criteria.number_gt = {};
|
||||
expect(spaceSelected(group, botSize)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue