diff --git a/.buildpacks b/.buildpacks deleted file mode 100644 index c799b60ab1..0000000000 --- a/.buildpacks +++ /dev/null @@ -1,2 +0,0 @@ -https://github.com/heroku/heroku-buildpack-nodejs.git -https://github.com/heroku/heroku-buildpack-ruby.git#v142 diff --git a/.ruby-version b/.ruby-version index fa7adc7ac7..9c25013dbb 100755 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.5 +3.3.6 diff --git a/.sass-lint.yml b/.sass-lint.yml index 2a3ebf8418..b950346e17 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -27,20 +27,6 @@ rules: - 2 - size: 2 - property-sort-order: - - 1 - - - order: - - content - - display - - position - - top - - left - - bottom - - right - - z-index - - margin - ignore-custom-properties: true variable-for-property: - 2 - diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 0278dac0ff..5d97025f6f 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -24,7 +24,7 @@ If you want to run a server on a LAN for personal use, this is the easiest and c **Affordability:** :broken_heart: - 1. Deploy as you would normally [deploy to Heroku](https://devcenter.heroku.com/articles/getting-started-with-rails4#deploy-your-application-to-heroku) + 1. Deploy as you would normally [deploy to Heroku](https://devcenter.heroku.com/articles/getting-started-with-rails6#deploy-the-app-to-heroku) (buildpacks: _heroku/nodejs_, _heroku/ruby_) 2. Enable Dyno metadata: `heroku labs:enable runtime-dyno-metadata --app ` (we need this to know the version number of the web app). 3. (If emails are enabled) Enable the [Heroku scheduler](https://elements.heroku.com/addons/scheduler) and configure it to run `rake api:log_digest` every 10 minutes. This is required for Device log digests via email. diff --git a/Gemfile b/Gemfile index e03f343896..de4a70b004 100755 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,5 @@ source "https://rubygems.org" -ruby "~> 3.3.5" +ruby "~> 3.3.6" gem "rails", "~> 6" gem "active_model_serializers" diff --git a/Gemfile.lock b/Gemfile.lock index 2bfa6a17db..7c4c4b97b3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,65 +1,65 @@ GEM remote: https://rubygems.org/ specs: - actioncable (6.1.7.8) - actionpack (= 6.1.7.8) - activesupport (= 6.1.7.8) + actioncable (6.1.7.10) + actionpack (= 6.1.7.10) + activesupport (= 6.1.7.10) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.8) - actionpack (= 6.1.7.8) - activejob (= 6.1.7.8) - activerecord (= 6.1.7.8) - activestorage (= 6.1.7.8) - activesupport (= 6.1.7.8) + actionmailbox (6.1.7.10) + actionpack (= 6.1.7.10) + activejob (= 6.1.7.10) + activerecord (= 6.1.7.10) + activestorage (= 6.1.7.10) + activesupport (= 6.1.7.10) mail (>= 2.7.1) - actionmailer (6.1.7.8) - actionpack (= 6.1.7.8) - actionview (= 6.1.7.8) - activejob (= 6.1.7.8) - activesupport (= 6.1.7.8) + actionmailer (6.1.7.10) + actionpack (= 6.1.7.10) + actionview (= 6.1.7.10) + activejob (= 6.1.7.10) + activesupport (= 6.1.7.10) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.7.8) - actionview (= 6.1.7.8) - activesupport (= 6.1.7.8) + actionpack (6.1.7.10) + actionview (= 6.1.7.10) + activesupport (= 6.1.7.10) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.8) - actionpack (= 6.1.7.8) - activerecord (= 6.1.7.8) - activestorage (= 6.1.7.8) - activesupport (= 6.1.7.8) + actiontext (6.1.7.10) + actionpack (= 6.1.7.10) + activerecord (= 6.1.7.10) + activestorage (= 6.1.7.10) + activesupport (= 6.1.7.10) nokogiri (>= 1.8.5) - actionview (6.1.7.8) - activesupport (= 6.1.7.8) + actionview (6.1.7.10) + activesupport (= 6.1.7.10) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - active_model_serializers (0.10.14) + active_model_serializers (0.10.15) actionpack (>= 4.1) activemodel (>= 4.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (6.1.7.8) - activesupport (= 6.1.7.8) + activejob (6.1.7.10) + activesupport (= 6.1.7.10) globalid (>= 0.3.6) - activemodel (6.1.7.8) - activesupport (= 6.1.7.8) - activerecord (6.1.7.8) - activemodel (= 6.1.7.8) - activesupport (= 6.1.7.8) - activestorage (6.1.7.8) - actionpack (= 6.1.7.8) - activejob (= 6.1.7.8) - activerecord (= 6.1.7.8) - activesupport (= 6.1.7.8) + activemodel (6.1.7.10) + activesupport (= 6.1.7.10) + activerecord (6.1.7.10) + activemodel (= 6.1.7.10) + activesupport (= 6.1.7.10) + activestorage (6.1.7.10) + actionpack (= 6.1.7.10) + activejob (= 6.1.7.10) + activerecord (= 6.1.7.10) + activesupport (= 6.1.7.10) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.8) + activesupport (6.1.7.10) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -85,18 +85,18 @@ GEM bigdecimal rexml crass (1.0.6) - database_cleaner (2.0.2) + database_cleaner (2.1.0) database_cleaner-active_record (>= 2, < 3) database_cleaner-active_record (2.2.0) activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - date (3.3.4) + date (3.4.1) declarative (0.0.20) - delayed_job (4.1.12) - activesupport (>= 3.0, < 8.0) - delayed_job_active_record (4.1.10) - activerecord (>= 3.0, < 8.0) + delayed_job (4.1.13) + activesupport (>= 3.0, < 9.0) + delayed_job_active_record (4.1.11) + activerecord (>= 3.0, < 9.0) delayed_job (>= 3.0, < 5) devise (4.9.4) bcrypt (~> 3.0) @@ -107,26 +107,26 @@ GEM diff-lcs (1.5.1) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - discard (1.3.0) - activerecord (>= 4.2, < 8) + discard (1.4.0) + activerecord (>= 4.2, < 9.0) docile (1.4.1) e2mmap (0.1.0) erubi (1.13.0) factory_bot (6.5.0) activesupport (>= 5.0.0) - factory_bot_rails (6.4.3) - factory_bot (~> 6.4) + factory_bot_rails (6.4.4) + factory_bot (~> 6.5) railties (>= 5.0.0) - faker (3.4.2) + faker (3.5.1) i18n (>= 1.8.11, < 2) - faraday (2.12.0) - faraday-net_http (>= 2.0, < 3.4) + faraday (2.12.1) + faraday-net_http (>= 2.0, < 3.5) json logger faraday-follow_redirects (0.3.0) faraday (>= 1, < 3) - faraday-net_http (3.3.0) - net-http + faraday-net_http (3.4.0) + net-http (>= 0.5.0) globalid (1.2.1) activesupport (>= 6.1) google-apis-core (0.15.1) @@ -137,17 +137,17 @@ GEM mutex_m representable (~> 3.0) retriable (>= 2.0, < 4.a) - google-apis-iamcredentials_v1 (0.21.0) + google-apis-iamcredentials_v1 (0.22.0) google-apis-core (>= 0.15.0, < 2.a) - google-apis-storage_v1 (0.45.0) + google-apis-storage_v1 (0.48.0) google-apis-core (>= 0.15.0, < 2.a) google-cloud-core (1.7.1) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) - google-cloud-env (2.2.0) + google-cloud-env (2.2.1) faraday (>= 1.0, < 3.a) google-cloud-errors (1.4.0) - google-cloud-storage (1.52.0) + google-cloud-storage (1.53.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-core (~> 0.13) @@ -156,21 +156,21 @@ GEM google-cloud-core (~> 1.6) googleauth (~> 1.9) mini_mime (~> 1.0) - googleauth (1.11.0) + googleauth (1.11.2) faraday (>= 1.0, < 3.a) google-cloud-env (~> 2.1) jwt (>= 1.4, < 3.0) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) - hashdiff (1.1.1) + hashdiff (1.1.2) hashie (4.1.0) httpclient (2.8.3) i18n (1.14.6) concurrent-ruby (~> 1.0) - json (2.7.2) + json (2.9.0) jsonapi-renderer (0.2.2) - jwt (2.9.1) + jwt (2.9.3) base64 kaminari (1.2.2) activesupport (>= 4.1.0) @@ -184,13 +184,13 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) - logger (1.6.1) + logger (1.6.2) lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.22.0) + loofah (2.23.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -201,14 +201,14 @@ GEM marcel (1.0.4) method_source (1.1.0) mini_mime (1.1.5) - minitest (5.25.1) + minitest (5.25.4) multi_json (1.15.0) mutations (0.9.1) activesupport - mutex_m (0.2.0) - net-http (0.4.1) + mutex_m (0.3.0) + net-http (0.6.0) uri - net-imap (0.4.16) + net-imap (0.5.1) date net-protocol net-pop (0.1.2) @@ -217,22 +217,22 @@ GEM timeout net-smtp (0.5.0) net-protocol - nio4r (2.7.3) - nokogiri (1.16.7-aarch64-linux) + nio4r (2.7.4) + nokogiri (1.16.8-aarch64-linux) racc (~> 1.4) - nokogiri (1.16.7-x86_64-linux) + nokogiri (1.16.8-x86_64-linux) racc (~> 1.4) orm_adapter (0.5.0) os (1.1.4) - parser (3.3.5.0) + parser (3.3.6.0) ast (~> 2.4.1) racc passenger (6.0.23) rack (>= 1.6.13) rackup rake (>= 12.3.3) - pg (1.5.8) - pry (0.14.2) + pg (1.5.9) + pry (0.15.0) coderay (~> 1.1) method_source (~> 1.0) pry-rails (0.3.11) @@ -245,46 +245,46 @@ GEM hashie (~> 4.1) multi_json (~> 1.15) racc (1.8.1) - rack (2.2.9) + rack (2.2.10) rack-attack (6.7.0) rack (>= 1.0, < 4) rack-cors (2.0.2) rack (>= 2.0.0) rack-test (2.1.0) rack (>= 1.3) - rackup (1.0.0) + rackup (1.0.1) rack (< 3) webrick - rails (6.1.7.8) - actioncable (= 6.1.7.8) - actionmailbox (= 6.1.7.8) - actionmailer (= 6.1.7.8) - actionpack (= 6.1.7.8) - actiontext (= 6.1.7.8) - actionview (= 6.1.7.8) - activejob (= 6.1.7.8) - activemodel (= 6.1.7.8) - activerecord (= 6.1.7.8) - activestorage (= 6.1.7.8) - activesupport (= 6.1.7.8) + rails (6.1.7.10) + actioncable (= 6.1.7.10) + actionmailbox (= 6.1.7.10) + actionmailer (= 6.1.7.10) + actionpack (= 6.1.7.10) + actiontext (= 6.1.7.10) + actionview (= 6.1.7.10) + activejob (= 6.1.7.10) + activemodel (= 6.1.7.10) + activerecord (= 6.1.7.10) + activestorage (= 6.1.7.10) + activesupport (= 6.1.7.10) bundler (>= 1.15.0) - railties (= 6.1.7.8) + railties (= 6.1.7.10) sprockets-rails (>= 2.0.0) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) + rails-html-sanitizer (1.6.1) loofah (~> 2.21) - nokogiri (~> 1.14) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) rails_12factor (0.0.3) rails_serve_static_assets rails_stdout_logging rails_serve_static_assets (0.0.5) rails_stdout_logging (0.0.5) - railties (6.1.7.8) - actionpack (= 6.1.7.8) - activesupport (= 6.1.7.8) + railties (6.1.7.10) + actionpack (= 6.1.7.10) + activesupport (= 6.1.7.10) method_source rake (>= 12.2) thor (~> 1.0) @@ -301,18 +301,18 @@ GEM actionpack (>= 5.2) railties (>= 5.2) retriable (3.1.2) - rexml (3.3.7) + rexml (3.3.9) rollbar (3.6.0) rspec (3.13.0) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.1) + rspec-core (3.13.2) rspec-support (~> 3.13.0) rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.1) + rspec-mocks (3.13.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-rails (6.1.5) @@ -323,7 +323,7 @@ GEM rspec-expectations (~> 3.13) rspec-mocks (~> 3.13) rspec-support (~> 3.13) - rspec-support (3.13.1) + rspec-support (3.13.2) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) scenic (1.8.0) @@ -331,8 +331,8 @@ GEM railties (>= 4.0.0) scout_apm (5.4.0) parser - secure_headers (6.7.0) - set (1.1.0) + secure_headers (7.0.0) + set (1.1.1) signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) @@ -360,28 +360,28 @@ GEM thor (1.3.2) thwait (0.2.0) e2mmap - timeout (0.4.1) + timeout (0.4.2) trailblazer-option (0.1.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) tzinfo-data (1.2024.2) tzinfo (>= 1.0.0) uber (0.1.0) - uri (0.13.1) + uri (1.0.2) valid_url (0.0.4) addressable rails warden (1.2.9) rack (>= 2.0.9) - webmock (3.23.1) + webmock (3.24.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.8.2) + webrick (1.9.1) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - zeitwerk (2.6.18) + zeitwerk (2.7.1) PLATFORMS aarch64-linux @@ -431,7 +431,7 @@ DEPENDENCIES webmock RUBY VERSION - ruby 3.3.5p100 + ruby 3.3.6p108 BUNDLED WITH - 2.5.19 + 2.5.23 diff --git a/app/controllers/api/ai_feedbacks_controller.rb b/app/controllers/api/ai_feedbacks_controller.rb index 071c2628aa..ab5b327553 100644 --- a/app/controllers/api/ai_feedbacks_controller.rb +++ b/app/controllers/api/ai_feedbacks_controller.rb @@ -1,7 +1,10 @@ module Api class AiFeedbacksController < Api::AbstractController def create - render json: AiFeedback.create!(update_params) + render json: AiFeedback.create!(update_params.merge( + model: ENV["OPENAI_MODEL_LUA"], + temperature: ENV["OPENAI_API_TEMPERATURE"] + )) end private diff --git a/app/controllers/api/ais_controller.rb b/app/controllers/api/ais_controller.rb index 901094c2fb..844e597257 100644 --- a/app/controllers/api/ais_controller.rb +++ b/app/controllers/api/ais_controller.rb @@ -111,7 +111,7 @@ def make_request(system_prompt, user_prompt, stream) url = "https://api.openai.com/v1/chat/completions" context_key = raw_json[:context_key] lua_request = context_key == "lua" - model_lua = ENV["OPENAI_MODEL_LUA"] || "gpt-3.5-turbo-16k" + model_lua = ENV["OPENAI_MODEL_LUA"] || "gpt-3.5-turbo" model_other = ENV["OPENAI_MODEL_OTHER"] || "gpt-3.5-turbo" payload = { "model" => lua_request ? model_lua : model_other, diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 4140ba75fd..62a145ef8d 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -21,7 +21,6 @@ class DashboardController < ApplicationController CACHE_DIR = File.join(".cache") CSS_INPUTS = { - front_page: "/css/laptop_splash.scss", default: "/css/_index.scss", terminal: "/css/xterm.css", }.with_indifferent_access @@ -35,7 +34,6 @@ class DashboardController < ApplicationController try_farmbot: "/try_farmbot/index.tsx", promo: "/promo/index.tsx", os_download: "/os_download/index.tsx", - featured: "/featured/index.tsx", terminal: "/terminal/index.tsx", }.with_indifferent_access diff --git a/app/views/dashboard/promo.html.erb b/app/views/dashboard/promo.html.erb index 1701cf51d0..97f76e5473 100644 --- a/app/views/dashboard/promo.html.erb +++ b/app/views/dashboard/promo.html.erb @@ -5,7 +5,9 @@ } body { margin: 0; - background-image: url("/placeholder_farmbot.jpg"); } -

Loading...

+
+ Loading 3D interactive experience... +

Loading interactive 3D FarmBot...

+
diff --git a/app/views/layouts/dashboard.html.erb b/app/views/layouts/dashboard.html.erb index 17c69ca3ce..8527eba3db 100644 --- a/app/views/layouts/dashboard.html.erb +++ b/app/views/layouts/dashboard.html.erb @@ -13,13 +13,7 @@ href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha512-SfTiTlX6kk+qitfevl/7LibUOeJWlt9rbyDn92a1DqWOw9vWG2MFoays0sgObmWazO5BQPiFucnnEAjpAB+/Sw==" crossorigin="anonymous"> - diff --git a/config/application.rb b/config/application.rb index a4638908cf..472b9206ca 100755 --- a/config/application.rb +++ b/config/application.rb @@ -93,7 +93,6 @@ class Application < Rails::Application base_uri: %w('self'), connect_src: connect_src, font_src: %w( - maxcdn.bootstrapcdn.com fonts.gstatic.com fonts.googleapis.com data: @@ -102,7 +101,7 @@ class Application < Rails::Application ), form_action: %w('self'), frame_src: %w(*), # We need "*" to support webcam users. - frame_ancestors: %w('self' https://farm.bot), + frame_ancestors: %w('self' https://farm.bot https://*.shopify.com https://*.shopifypreview.com), img_src: %w(* data:), # We need "*" to support webcam users. manifest_src: %w('self'), media_src: %w(), @@ -114,6 +113,7 @@ class Application < Rails::Application allow-modals allow-popups allow-downloads + allow-top-navigation ), plugin_types: %w(), script_src: [ @@ -129,7 +129,6 @@ class Application < Rails::Application "blob:", # 3D ], style_src: %w( - maxcdn.bootstrapcdn.com fonts.gstatic.com fonts.googleapis.com cdnjs.cloudflare.com diff --git a/config/routes.rb b/config/routes.rb index b623130ce8..13cc92e274 100755 --- a/config/routes.rb +++ b/config/routes.rb @@ -132,7 +132,6 @@ get "/try_farmbot" => "dashboard#try_farmbot", as: :try_farmbot_main get "/promo" => "dashboard#promo", as: :promo_main get "/os" => "dashboard#os_download", as: :os_download - get "/featured" => "dashboard#featured", as: :featured get "/password_reset/*token" => "dashboard#password_reset", as: :password_reset get "/tos_update" => "dashboard#tos_update", as: :tos_update get "/verify/:token" => "dashboard#confirmation_page", as: :confirmation_page diff --git a/db/migrate/20241203194030_add_dark_mode_to_web_app_config.rb b/db/migrate/20241203194030_add_dark_mode_to_web_app_config.rb new file mode 100644 index 0000000000..a47a75fed5 --- /dev/null +++ b/db/migrate/20241203194030_add_dark_mode_to_web_app_config.rb @@ -0,0 +1,9 @@ +class AddDarkModeToWebAppConfig < ActiveRecord::Migration[6.1] + def up + add_column :web_app_configs, :dark_mode, :boolean, default: false + end + + def down + remove_column :web_app_configs, :dark_mode + end +end diff --git a/db/migrate/20241203211516_add_model_to_ai_feedbacks.rb b/db/migrate/20241203211516_add_model_to_ai_feedbacks.rb new file mode 100644 index 0000000000..4cb3489e61 --- /dev/null +++ b/db/migrate/20241203211516_add_model_to_ai_feedbacks.rb @@ -0,0 +1,11 @@ +class AddModelToAiFeedbacks < ActiveRecord::Migration[6.1] + def up + add_column :ai_feedbacks, :model, :string, limit: 30 + add_column :ai_feedbacks, :temperature, :string, limit: 5 + end + + def down + remove_column :ai_feedbacks, :model + remove_column :ai_feedbacks, :temperature + end +end diff --git a/db/structure.sql b/db/structure.sql index 7c33c62e9d..c0f963f558 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -9,13 +9,6 @@ SET xmloption = content; SET client_min_messages = warning; SET row_security = off; --- --- Name: public; Type: SCHEMA; Schema: -; Owner: - --- - --- *not* creating schema, since initdb creates it - - -- -- Name: hstore; Type: EXTENSION; Schema: -; Owner: - -- @@ -159,7 +152,9 @@ CREATE TABLE public.ai_feedbacks ( prompt character varying(500), reaction character varying(25), created_at timestamp(6) without time zone NOT NULL, - updated_at timestamp(6) without time zone NOT NULL + updated_at timestamp(6) without time zone NOT NULL, + model character varying(30), + temperature character varying(5) ); @@ -1540,7 +1535,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, @@ -1715,7 +1710,7 @@ ALTER SEQUENCE public.sequence_publications_id_seq OWNED BY public.sequence_publ -- CREATE VIEW public.sequence_usage_reports AS - SELECT sequences.id AS sequence_id, + SELECT id AS sequence_id, ( SELECT count(*) AS count FROM public.edge_nodes WHERE (((edge_nodes.kind)::text = 'sequence_id'::text) AND ((edge_nodes.value)::integer = sequences.id))) AS edge_node_count, @@ -2037,7 +2032,8 @@ CREATE TABLE public.web_app_configs ( default_plant_depth integer DEFAULT 5, show_missed_step_plot boolean DEFAULT false, enable_3d_electronics_box_top boolean DEFAULT true, - three_d_garden boolean DEFAULT false + three_d_garden boolean DEFAULT false, + dark_mode boolean DEFAULT false ); @@ -3708,14 +3704,6 @@ ALTER TABLE ONLY public.plant_templates ADD CONSTRAINT plant_templates_device_id_fk FOREIGN KEY (device_id) REFERENCES public.devices(id); --- --- Name: plant_templates plant_templates_saved_garden_id_fk; Type: FK CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.plant_templates - ADD CONSTRAINT plant_templates_saved_garden_id_fk FOREIGN KEY (saved_garden_id) REFERENCES public.saved_gardens(id); - - -- -- Name: point_group_items point_group_items_point_group_id_fk; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -3989,6 +3977,8 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240202171922'), ('20240207234421'), ('20240405171128'), -('20240625195838'); +('20240625195838'), +('20241203194030'), +('20241203211516'); diff --git a/docker_configs/api.Dockerfile b/docker_configs/api.Dockerfile index 6a517dfc64..6399440e39 100644 --- a/docker_configs/api.Dockerfile +++ b/docker_configs/api.Dockerfile @@ -1,10 +1,10 @@ -FROM ruby:3.3.5 +FROM ruby:3.3.6 RUN curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg > /dev/null && \ sh -c '. /etc/os-release; echo $VERSION_CODENAME; echo "deb http://apt.postgresql.org/pub/repos/apt/ $VERSION_CODENAME-pgdg main" >> /etc/apt/sources.list.d/pgdg.list' && \ apt-get update -qq && apt-get install -y build-essential libpq-dev postgresql postgresql-contrib && \ mkdir -p /etc/apt/keyrings && \ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \ - sh -c 'echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" >> /etc/apt/sources.list.d/nodesource.list' && \ + sh -c 'echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" >> /etc/apt/sources.list.d/nodesource.list' && \ apt-get update -qq && \ sh -c 'echo "\nPackage: *\nPin: origin deb.nodesource.com\nPin-Priority: 700\n" >> /etc/apt/preferences' && \ apt-get install -y nodejs && \ diff --git a/docker_configs/rabbitmq.Dockerfile b/docker_configs/rabbitmq.Dockerfile index 4e6d1bcc77..5bbb2fb2cf 100755 --- a/docker_configs/rabbitmq.Dockerfile +++ b/docker_configs/rabbitmq.Dockerfile @@ -1,4 +1,4 @@ -FROM rabbitmq:3.13.6 +FROM rabbitmq:4.0.4 RUN rabbitmq-plugins enable --offline \ rabbitmq_auth_backend_http \ rabbitmq_management \ diff --git a/frontend/404.tsx b/frontend/404.tsx index 0b9eebbd84..36fc0bbd27 100644 --- a/frontend/404.tsx +++ b/frontend/404.tsx @@ -15,3 +15,6 @@ export const FourOhFour = (_: {}) => ; + +// eslint-disable-next-line import/no-default-export +export default FourOhFour; diff --git a/frontend/__test_support__/additional_mocks.tsx b/frontend/__test_support__/additional_mocks.tsx index b530a4e478..690f9f4663 100644 --- a/frontend/__test_support__/additional_mocks.tsx +++ b/frontend/__test_support__/additional_mocks.tsx @@ -28,12 +28,7 @@ window.TextDecoder = jest.fn(() => ({ })); jest.mock("../error_boundary", () => ({ - ErrorBoundary: (p: { children: React.ReactChild }) =>
{p.children}
, -})); - -jest.mock("../history", () => ({ - push: jest.fn(), - getPathArray: () => [], + ErrorBoundary: (p: { children: React.ReactNode }) =>
{p.children}
, })); window.ResizeObserver = (() => ({ @@ -47,3 +42,14 @@ jest.mock("@rollbar/react", () => ({ Provider: ({ children }: { children: React.ReactNode }) =>
{children}
, })); + +global.mockNavigate = jest.fn(() => jest.fn()); + +jest.mock("react-router", () => ({ + BrowserRouter: jest.fn(({ children }) =>
{children}
), + Route: jest.fn(({ children }) =>
{children}
), + Routes: jest.fn(({ children }) =>
{children}
), + useNavigate: () => mockNavigate, + Navigate: ({ to }: { to: string }) =>
{mockNavigate(to)}
, + Outlet: jest.fn(() =>
), +})); diff --git a/frontend/__test_support__/fake_designer_state.ts b/frontend/__test_support__/fake_designer_state.ts index 957f5b70b2..7ba45401b0 100644 --- a/frontend/__test_support__/fake_designer_state.ts +++ b/frontend/__test_support__/fake_designer_state.ts @@ -52,6 +52,8 @@ export const fakeDesignerState = (): DesignerState => ({ cropHeightCurveId: undefined, cropStage: undefined, cropPlantedAt: undefined, + distanceIndicator: "", + panelOpen: true, }); export const fakeHelpState = (): HelpState => ({ diff --git a/frontend/__test_support__/fake_state/resources.ts b/frontend/__test_support__/fake_state/resources.ts index 6e031ce6dd..d3be6a923e 100644 --- a/frontend/__test_support__/fake_state/resources.ts +++ b/frontend/__test_support__/fake_state/resources.ts @@ -36,6 +36,7 @@ import { } from "farmbot/dist/resources/api_resources"; import { MessageType } from "../../sequences/interfaces"; import { TaggedPointGroup } from "../../resources/interfaces"; +import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app"; export const resources: Everything["resources"] = buildResourceIndex(); let idCounter = 1; @@ -390,6 +391,8 @@ export function fakeWebAppConfig(): TaggedWebAppConfig { map_size_x: 2900, map_size_y: 1400, user_interface_read_only_mode: false, + ["three_d_garden" as BooleanConfigKey]: false, + ["dark_mode" as BooleanConfigKey]: false, view_celery_script: false, }); } diff --git a/frontend/__test_support__/helpers.ts b/frontend/__test_support__/helpers.ts index 26017ece44..97cf210966 100644 --- a/frontend/__test_support__/helpers.ts +++ b/frontend/__test_support__/helpers.ts @@ -1,3 +1,4 @@ +import { fireEvent } from "@testing-library/react"; import { ReactWrapper, shallow, ShallowWrapper } from "enzyme"; import { range } from "lodash"; @@ -42,6 +43,16 @@ export function changeBlurableInput( input.simulate("blur", { currentTarget: { value } }); } +/** Simulate BlurableInput commit. */ +export function changeBlurableInputRTL( + input: HTMLElement, + value: string, +) { + fireEvent.focus(input); + fireEvent.change(input, { target: { value } }); + fireEvent.blur(input); +} + export const fetchResponse = ( read: () => Promise>, response?: Partial, diff --git a/frontend/__test_support__/mount_with_context.tsx b/frontend/__test_support__/mount_with_context.tsx new file mode 100644 index 0000000000..3baa173426 --- /dev/null +++ b/frontend/__test_support__/mount_with_context.tsx @@ -0,0 +1,6 @@ +import React from "react"; +import { mount } from "enzyme"; +import { NavigationProvider } from "../routes_helpers"; + +export const mountWithContext = (element: React.ReactElement) => + mount({element}); diff --git a/frontend/__test_support__/panel_state.ts b/frontend/__test_support__/panel_state.ts index 11d6f9e521..6a213d317f 100644 --- a/frontend/__test_support__/panel_state.ts +++ b/frontend/__test_support__/panel_state.ts @@ -27,6 +27,7 @@ export const settingsPanelState = (): SettingsPanelState => { parameter_management: false, custom_settings: false, farm_designer: false, + three_d: false, account: false, other_settings: false, }; diff --git a/frontend/__test_support__/setup_tests.js b/frontend/__test_support__/setup_tests.js new file mode 100644 index 0000000000..15dd9b291b --- /dev/null +++ b/frontend/__test_support__/setup_tests.js @@ -0,0 +1,2 @@ +import "@testing-library/jest-dom"; +import "./customMatchers"; diff --git a/frontend/__test_support__/svg_mount.tsx b/frontend/__test_support__/svg_mount.tsx index 52494e083b..b9d9184f4f 100644 --- a/frontend/__test_support__/svg_mount.tsx +++ b/frontend/__test_support__/svg_mount.tsx @@ -1,6 +1,6 @@ import React from "react"; import { mount } from "enzyme"; -export function svgMount(element: React.ReactElement) { - return mount({element}); +export function svgMount(element: React.ReactNode) { + return mount({element}); } diff --git a/frontend/__test_support__/three_d_mocks.tsx b/frontend/__test_support__/three_d_mocks.tsx index f903e43577..c5f3f2593d 100644 --- a/frontend/__test_support__/three_d_mocks.tsx +++ b/frontend/__test_support__/three_d_mocks.tsx @@ -29,14 +29,18 @@ jest.mock("@react-spring/three", () => ({ (props.to as TransitionFn)?.(next); return { ...props, ...props.from }; }, - animated: { - mesh: ({ children }: { children: ReactNode }) => -
{children}
, - meshPhongMaterial: () =>
, - group: ({ children }: { children: ReactNode }) => -
{children}
, - pointLight: () =>
, - } + // mocks for ` + //
{children}
, + // meshPhongMaterial: () =>
, + // group: ({ children }: { children: ReactNode }) => + //
{children}
, + // pointLight: () =>
, + // }, + // mocks for `const AnimatedMesh = animated(Mesh); ... ({ children }: { children?: ReactNode }) => +
{children}
, })); jest.mock("@react-three/drei", () => { @@ -556,7 +560,9 @@ jest.mock("@react-three/drei", () => {
{name}
, Tube: ({ name, children }: { name: string, children: ReactNode }) =>
{children}
, - Text: ({ children }: { children: ReactNode }) => + Center: ({ children }: { children: ReactNode }) => +
{children}
, + Text3D: ({ children }: { children: ReactNode }) =>
{children}
, Detailed: ({ children }: { children: ReactNode }) =>
{children}
, @@ -580,8 +586,8 @@ jest.mock("@react-three/drei", () => {
{children}
, Stats: ({ name }: { name: string }) =>
{name}
, - Billboard: ({ name }: { name: string }) => -
{name}
, + Billboard: ({ name, children }: { name: string, children: ReactNode }) => +
{children}
, Image: ({ name }: { name: string }) =>
{name}
, Clouds: ({ name }: { name: string }) => diff --git a/frontend/__tests__/apology_test.tsx b/frontend/__tests__/apology_test.tsx index 82e55e2d39..c7e01b99b3 100644 --- a/frontend/__tests__/apology_test.tsx +++ b/frontend/__tests__/apology_test.tsx @@ -1,6 +1,6 @@ jest.mock("../session", () => ({ Session: { clear: jest.fn() } })); -import * as React from "react"; +import React from "react"; import { mount } from "enzyme"; import { Apology } from "../apology"; import { Session } from "../session"; diff --git a/frontend/__tests__/app_test.tsx b/frontend/__tests__/app_test.tsx index 10cafdeb56..c6e4128281 100644 --- a/frontend/__tests__/app_test.tsx +++ b/frontend/__tests__/app_test.tsx @@ -3,12 +3,6 @@ jest.mock("bowser", () => ({ getParser: () => ({ satisfies: () => mockSatisfies }), })); -let mockPath = ""; -jest.mock("../history", () => ({ - getPathArray: () => mockPath.split("/"), - push: jest.fn(), -})); - jest.mock("../hotkeys", () => ({ HotKeys: () =>
, })); @@ -30,10 +24,10 @@ import { error, warning } from "../toast/toast"; import { fakePings } from "../__test_support__/fake_state/pings"; import { auth } from "../__test_support__/fake_state/token"; import { + fakeDesignerState, fakeHelpState, fakeMenuOpenState, } from "../__test_support__/fake_designer_state"; import { Path } from "../internal_urls"; -import { push } from "../history"; import { app } from "../__test_support__/fake_state/app"; const FULLY_LOADED: ResourceName[] = [ @@ -68,9 +62,14 @@ const fakeProps = (): AppProps => ({ peripherals: [], sequences: [], menuOpen: fakeMenuOpenState(), + designer: fakeDesignerState(), }); describe(": Loading", () => { + beforeEach(() => { + location.pathname = Path.mock(Path.app()); + }); + it("MUST_LOADs not loaded", () => { const wrapper = mount(); expect(wrapper.text()).toContain("Loading..."); @@ -126,19 +125,33 @@ describe(": Loading", () => { }); it("navigates to landing page", () => { - mockPath = Path.mock(Path.app()); + location.pathname = Path.mock(Path.app()); const p = fakeProps(); p.getConfigValue = () => "controls"; mount(); - expect(push).toHaveBeenCalledWith(Path.controls()); + expect(mockNavigate).toHaveBeenCalledWith(Path.controls()); }); it("doesn't navigate to landing page", () => { - mockPath = Path.mock(Path.controls()); + location.pathname = Path.mock(Path.controls()); const p = fakeProps(); p.getConfigValue = () => "controls"; mount(); - expect(push).not.toHaveBeenCalled(); + expect(mockNavigate).not.toHaveBeenCalled(); + }); + + it("enables the dark theme", () => { + const p = fakeProps(); + p.getConfigValue = () => true; + const wrapper = mount(); + expect(wrapper.find(".app").hasClass("dark")).toBeTruthy(); + }); + + it("enables the light theme", () => { + const p = fakeProps(); + p.getConfigValue = () => false; + const wrapper = mount(); + expect(wrapper.find(".app").hasClass("light")).toBeTruthy(); }); }); diff --git a/frontend/__tests__/history_test.ts b/frontend/__tests__/history_test.ts deleted file mode 100644 index cfe6b127bd..0000000000 --- a/frontend/__tests__/history_test.ts +++ /dev/null @@ -1,24 +0,0 @@ -jest.mock("takeme", () => ({ navigate: jest.fn() })); -jest.unmock("../history"); - -import { navigate } from "takeme"; -import { getPathArray, push } from "../history"; - -describe("push()", () => { - it("calls history with a URL", () => { - push("/wow.html"); - expect(navigate).toHaveBeenCalledWith("/wow.html"); - }); - - it("calls history, stripping /app", () => { - push("/app/wow"); - expect(navigate).toHaveBeenCalledWith("/wow"); - }); -}); - -describe("getPathArray()", () => { - it("returns path array", () => { - location.pathname = "/1/2/3"; - expect(getPathArray()).toEqual(["", "1", "2", "3"]); - }); -}); diff --git a/frontend/__tests__/hotkeys_test.tsx b/frontend/__tests__/hotkeys_test.tsx index ada7afdf74..2d38712ebc 100644 --- a/frontend/__tests__/hotkeys_test.tsx +++ b/frontend/__tests__/hotkeys_test.tsx @@ -1,9 +1,3 @@ -import { Path } from "../internal_urls"; -let mockPath = Path.mock(Path.designer()); -jest.mock("../history", () => ({ - push: jest.fn(), - getPathArray: () => mockPath.split("/"), -})); const mockSyncThunk = jest.fn(); jest.mock("../devices/actions", () => ({ sync: () => mockSyncThunk })); jest.mock("../farm_designer/map/actions", () => ({ unselectPlant: jest.fn() })); @@ -21,26 +15,32 @@ import { shallow } from "enzyme"; import { HotKey, HotKeys, HotKeysProps, hotkeysWithActions, toggleHotkeyHelpOverlay, } from "../hotkeys"; -import { push } from "../history"; import { sync } from "../devices/actions"; import { unselectPlant } from "../farm_designer/map/actions"; import { save } from "../api/crud"; import { Actions } from "../constants"; +import { Path } from "../internal_urls"; describe("hotkeysWithActions()", () => { + beforeEach(() => { + location.pathname = Path.mock(Path.designer()); + }); + it("has key bindings", () => { const dispatch = jest.fn(); - const hotkeys = hotkeysWithActions(dispatch, ""); + const navigate = jest.fn(); + const hotkeys = hotkeysWithActions(navigate, dispatch, ""); expect(Object.values(hotkeys).length).toBe(8); const e = {} as KeyboardEvent; hotkeys[HotKey.save].onKeyDown?.(e); expect(save).not.toHaveBeenCalled(); mockState.resources.consumers.sequences.current = "uuid"; - const hotkeysSettingsPath = hotkeysWithActions(dispatch, "settings"); + const hotkeysSettingsPath = hotkeysWithActions(navigate, dispatch, "settings"); hotkeysSettingsPath[HotKey.save].onKeyDown?.(e); expect(save).not.toHaveBeenCalled(); - const hotkeysSequencesPath = hotkeysWithActions(dispatch, "sequences"); + const hotkeysSequencesPath = hotkeysWithActions( + navigate, dispatch, "sequences"); hotkeysSequencesPath[HotKey.save].onKeyDown?.(e); expect(save).toHaveBeenCalledWith("uuid"); @@ -48,24 +48,24 @@ describe("hotkeysWithActions()", () => { expect(dispatch).toHaveBeenCalledWith(sync()); hotkeys[HotKey.navigateRight].onKeyDown?.(e); - expect(push).toHaveBeenCalledWith(Path.plants()); + expect(navigate).toHaveBeenCalledWith(Path.plants()); hotkeys[HotKey.navigateLeft].onKeyDown?.(e); - expect(push).toHaveBeenCalledWith(Path.settings()); + expect(navigate).toHaveBeenCalledWith(Path.settings()); hotkeys[HotKey.addPlant].onKeyDown?.(e); - expect(push).toHaveBeenCalledWith(Path.cropSearch()); + expect(navigate).toHaveBeenCalledWith(Path.cropSearch()); hotkeys[HotKey.addEvent].onKeyDown?.(e); - expect(push).toHaveBeenCalledWith(Path.farmEvents("add")); + expect(navigate).toHaveBeenCalledWith(Path.farmEvents("add")); hotkeysSettingsPath[HotKey.backToPlantOverview].onKeyDown?.(e); - expect(push).toHaveBeenCalledWith(Path.plants()); + expect(navigate).toHaveBeenCalledWith(Path.plants()); expect(unselectPlant).toHaveBeenCalled(); jest.clearAllMocks(); - const hotkeysPhotosPath = hotkeysWithActions(dispatch, "photos"); + const hotkeysPhotosPath = hotkeysWithActions(navigate, dispatch, "photos"); hotkeysPhotosPath[HotKey.backToPlantOverview].onKeyDown?.(e); - expect(push).not.toHaveBeenCalled(); + expect(navigate).not.toHaveBeenCalled(); expect(unselectPlant).not.toHaveBeenCalled(); }); }); @@ -87,13 +87,13 @@ describe("", () => { }); it("renders", () => { - mockPath = Path.mock(Path.designer("nope")); + location.pathname = Path.mock(Path.designer("nope")); const wrapper = shallow(); expect(wrapper.html()).toEqual("
"); }); it("renders default", () => { - mockPath = Path.mock(Path.designer()); + location.pathname = Path.mock(Path.designer()); const wrapper = shallow(); expect(wrapper.html()).toEqual("
"); }); diff --git a/frontend/__tests__/internal_urls_test.ts b/frontend/__tests__/internal_urls_test.ts index 57de342741..6010dffb31 100644 --- a/frontend/__tests__/internal_urls_test.ts +++ b/frontend/__tests__/internal_urls_test.ts @@ -1,31 +1,35 @@ import { FilePath, Icon, landingPagePath, PAGE_SLUGS, Path, } from "../internal_urls"; -let mockPath = Path.mock(Path.designer()); -jest.mock("../history", () => ({ - getPathArray: () => mockPath.split("/"), -})); describe("Path()", () => { + beforeEach(() => { + location.pathname = Path.mock(Path.designer()); + }); + it("returns path", () => { expect(Path.plants()).toEqual("/app/designer/plants"); expect(Path.plants(1)).toEqual("/app/designer/plants/1"); - mockPath = Path.mock(Path.designerSequences("sequence")); + location.pathname = Path.mock(Path.designerSequences("sequence")); expect(Path.sequences("sequence")).toEqual("/app/designer/sequences/sequence"); - mockPath = Path.mock(Path.sequencePage("sequence")); + location.pathname = Path.mock(Path.sequencePage("sequence")); expect(Path.sequences("sequence")).toEqual("/app/sequences/sequence"); }); it("returns path start result", () => { - mockPath = Path.mock(Path.plants(1)); + location.pathname = Path.mock(Path.plants(1)); expect(Path.startsWith(Path.plants())).toEqual(true); - mockPath = Path.mock(Path.weeds(1)); + location.pathname = Path.mock(Path.weeds(1)); expect(Path.startsWith(Path.plants())).toEqual(false); }); it("modifies path", () => { expect(Path.mock(Path.plants())).toEqual("/app/designer/plants"); - expect(Path.route(Path.plants())).toEqual("/designer/plants"); + }); + + it("returns last chunk", () => { + location.pathname = Path.mock(Path.plants(3)); + expect(Path.getLastChunk()).toEqual("3"); }); it("returns index", () => { @@ -39,7 +43,7 @@ describe("Path()", () => { }); it("returns slug", () => { - mockPath = Path.mock(Path.cropSearch("slug")); + location.pathname = Path.mock(Path.cropSearch("slug")); expect(Path.getSlug(Path.cropSearch())).toEqual("slug"); }); diff --git a/frontend/__tests__/link_test.tsx b/frontend/__tests__/link_test.tsx index 2c87171ba0..faceea840e 100644 --- a/frontend/__tests__/link_test.tsx +++ b/frontend/__tests__/link_test.tsx @@ -1,30 +1,6 @@ -jest.mock("../history", () => ({ push: jest.fn() })); - import React from "react"; import { shallow } from "enzyme"; -import { clickHandler, Link } from "../link"; -import { push } from "../history"; - -describe("clickHandler", () => { - function setupClickTest(to: string) { - const onClick = jest.fn(); - const handler = clickHandler({ to, onClick }); - type ClickEvent = React.MouseEvent; - const e: Partial = { preventDefault: jest.fn() }; - return { - e: e as ClickEvent, - handler, - onClick - }; - } - - it("handles clicks", () => { - const { e, handler } = setupClickTest("/app/foo/bar"); - handler(e); - expect(e.preventDefault).toHaveBeenCalled(); - expect(push).toHaveBeenCalledWith("/app/foo/bar"); - }); -}); +import { Link } from "../link"; describe("", () => { it("renders child elements", () => { @@ -37,12 +13,12 @@ describe("", () => { it("navigates", () => { const wrapper = shallow(); wrapper.simulate("click", { preventDefault: jest.fn() }); - expect(push).toHaveBeenCalledWith("/tools"); + expect(mockNavigate).toHaveBeenCalledWith("/tools"); }); it("doesn't navigate when disabled", () => { const wrapper = shallow(); wrapper.simulate("click", { preventDefault: jest.fn() }); - expect(push).not.toHaveBeenCalled(); + expect(mockNavigate).not.toHaveBeenCalled(); }); }); diff --git a/frontend/__tests__/route_config_test.tsx b/frontend/__tests__/route_config_test.tsx index 524b4b1b09..ee63e47fe9 100644 --- a/frontend/__tests__/route_config_test.tsx +++ b/frontend/__tests__/route_config_test.tsx @@ -1,48 +1,16 @@ -jest.mock("react-redux", () => ({ - connect: jest.fn(() => jest.fn(x => x)), +jest.mock("react", () => ({ + ...jest.requireActual("react"), + lazy: jest.fn(x => x()), })); -import { UNBOUND_ROUTES } from "../route_config"; -import { RouteEnterEvent } from "takeme"; -import { ChangeRoute } from "../routes"; +import { last } from "lodash"; +import { ROUTE_DATA } from "../route_config"; -const fakeChangeRoute: ChangeRoute = (component, info, child) => { - if (info?.$ == "*") { - if (info?.children) { - expect(child?.name.split("Raw").join("")).toEqual("FourOhFour"); - } else { - expect(component.name).toEqual("FourOhFour"); +describe("ROUTE_DATA", () => { + it("has 404", () => { + const lastRoute = last(ROUTE_DATA); + if (lastRoute) { + expect(lastRoute.path).toEqual("*"); } - } - expect(component?.name.split("Raw").join("")).toEqual(info?.key); - if (info?.children) { - expect(child?.name.split("Raw").join("")).toEqual(info?.childKey); - } -}; - -const fakeRouteEnterEvent: RouteEnterEvent = { - params: { splat: "????" }, - oldPath: "??", - newPath: "??" -}; - -describe("UNBOUND_ROUTES", () => { - it("generates correct routes", () => { - UNBOUND_ROUTES - .map(r => r(fakeChangeRoute)) - .map(r => r.enter && r.enter(fakeRouteEnterEvent)); - }); - - it("generates crash route", async () => { - console.error = jest.fn(); - const fakeError = new Error("fake callback error"); - const changeRouteError = jest.fn() - // try block call - .mockImplementationOnce(() => { throw fakeError; }) - // catch block call - .mockImplementationOnce(x => { expect(x.name).toEqual("Apology"); }); - const r = UNBOUND_ROUTES[0](changeRouteError); - r.enter && await r.enter(fakeRouteEnterEvent); - expect(console.error).toHaveBeenCalledWith(fakeError); }); }); diff --git a/frontend/__tests__/routes_test.tsx b/frontend/__tests__/routes_test.tsx index a6005740e9..dbbca4f629 100644 --- a/frontend/__tests__/routes_test.tsx +++ b/frontend/__tests__/routes_test.tsx @@ -7,18 +7,13 @@ jest.mock("../session", () => ({ } })); -jest.mock("takeme", () => ({ - Router: jest.fn(() => ({ enableHtml5Routing: () => ({ init: jest.fn() }) })), -})); - import React from "react"; -import { mount, shallow } from "enzyme"; -import { AnyConnectedComponent, RootComponent } from "../routes"; +import { mount } from "enzyme"; +import { RootComponent } from "../routes"; import { store } from "../redux/store"; import { AuthState } from "../auth/interfaces"; import { auth } from "../__test_support__/fake_state/token"; import { Session } from "../session"; -import { FourOhFour } from "../404"; import { Path } from "../internal_urls"; describe("", () => { @@ -50,25 +45,4 @@ describe("", () => { expect(wrapper.html()).not.toContain("rollbar"); wrapper.unmount(); }); - - it("changes route", () => { - const wrapper = shallow(); - wrapper.instance().changeRoute(FourOhFour); - expect(wrapper.state().Route).toEqual(FourOhFour); - }); - - it("initializes", () => { - const wrapper = shallow(); - const Component = wrapper.state().Route; - const route = shallow(); - expect(route.text()).toEqual("Loading..."); - }); - - it("renders child route", () => { - const wrapper = shallow(); - wrapper.setState({ - ChildRoute: jest.fn(() => -

child

) as unknown as AnyConnectedComponent - }); - }); }); diff --git a/frontend/apology.tsx b/frontend/apology.tsx index 1c97844703..621a92d273 100644 --- a/frontend/apology.tsx +++ b/frontend/apology.tsx @@ -31,7 +31,7 @@ export function Apology(_: {}) {
  1. - Refresh the page. + Click back and then refresh the page.
  2. {"Perform a \"hard refresh\""} diff --git a/frontend/app.tsx b/frontend/app.tsx index 13c9341954..3c198dfbe2 100644 --- a/frontend/app.tsx +++ b/frontend/app.tsx @@ -50,12 +50,14 @@ import { TourStepContainer } from "./help/tours"; import { Toasts } from "./toast/fb_toast"; import Bowser from "bowser"; import { landingPagePath, Path } from "./internal_urls"; -import { push } from "./history"; import { AppState } from "./reducer"; import { sourceFbosConfigValue, sourceFwConfigValue, } from "./settings/source_config_value"; import { RunButtonMenuOpen } from "./sequences/interfaces"; +import { Navigate, Outlet } from "react-router"; +import { ErrorBoundary } from "./error_boundary"; +import { DesignerState } from "./farm_designer/interfaces"; export interface AppProps { dispatch: Function; @@ -86,6 +88,7 @@ export interface AppProps { sequences: TaggedSequence[]; menuOpen: RunButtonMenuOpen; appState: AppState; + designer: DesignerState; children?: React.ReactNode; } @@ -127,6 +130,7 @@ export function mapStateToProps(props: Everything): AppProps { peripherals: uniq(selectAllPeripherals(props.resources.index)), sequences: selectAllSequences(props.resources.index), menuOpen: props.resources.consumers.sequences.menuOpen, + designer: props.resources.consumers.farm_designer, }; } /** Time at which the app gives up and asks the user to refresh */ @@ -176,13 +180,14 @@ export class RawApp extends React.Component { const syncLoaded = this.isLoaded; const { bot, dispatch, getConfigValue } = this.props; const landingPage = getConfigValue(StringSetting.landing_page); - if (Path.equals("") && isString(landingPage)) { - push(landingPagePath(landingPage)); - } - return
    + const themeClass = getConfigValue(BooleanSetting.dark_mode) ? "dark" : "light"; + return
    + {(Path.equals("") || Path.equals(Path.app())) && isString(landingPage) && + } {!syncLoaded && } {syncLoaded && { menuOpen={this.props.menuOpen} pings={this.props.pings} />} {syncLoaded && this.props.children} + + + {syncLoaded && } + +
    { export const App = connect(mapStateToProps)( RawApp) as ConnectedComponent; +// eslint-disable-next-line import/no-default-export +export default App; diff --git a/frontend/constants.ts b/frontend/constants.ts index 9c9a2ef8a5..e81a111ea5 100644 --- a/frontend/constants.ts +++ b/frontend/constants.ts @@ -606,6 +606,43 @@ export namespace ToolTips { export const UNKNOWN_STEP = trim(`Unable to properly display this step.`); + // 3D + export const THREE_D_BED_WALL_THICKNESS = + trim(`Bed wood thickness. (default: {{ defaultConfigValue }}mm)`); + + export const THREE_D_BED_HEIGHT = + trim(`Bed height. (default: {{ defaultConfigValue }}mm)`); + + export const THREE_D_CC_SUPPORT_SIZE = + trim(`Cable carrier support size. (default: {{ defaultConfigValue }}mm)`); + + export const THREE_D_BEAM_LENGTH = + trim(`Gantry beam extrusion length. (default: {{ defaultConfigValue }}mm)`); + + export const THREE_D_COLUMN_LENGTH = + trim(`Gantry column extrusion length. (default: {{ defaultConfigValue }}mm)`); + + export const THREE_D_Z_AXIS_LENGTH = + trim(`Z axis extrusion length. (default: {{ defaultConfigValue }}mm)`); + + export const THREE_D_BED_X_OFFSET = + trim(`Bed X offset. (default: {{ defaultConfigValue }}mm)`); + + export const THREE_D_BED_Y_OFFSET = + trim(`Bed Y offset. (default: {{ defaultConfigValue }}mm)`); + + export const THREE_D_BED_Z_OFFSET = + trim(`Bed Z offset. (default: {{ defaultConfigValue }}mm)`); + + export const THREE_D_LEG_SIZE = + trim(`Leg size. (default: {{ defaultConfigValue }}mm)`); + + export const THREE_D_BOUNDS = + trim(`Display bounds. (default: 0)`); + + export const THREE_D_GRID = + trim(`Display grid. (default: 0)`); + // Tools export const WATER_FLOW_RATE = trim(`To calculate **WATER FLOW RATE**, hold a measuring cup below the @@ -819,6 +856,9 @@ export namespace Content { trim(`Disallow account data changes. This does not prevent Farmwares or FarmBot OS from changing settings.`); + export const DARK_MODE = + trim(`Enable a dark interface theme.`); + // Sequence Settings export const CONFIRM_STEP_DELETION = trim(`Show a confirmation dialog when deleting a sequence step.`); @@ -1154,22 +1194,22 @@ export namespace Content { Try leaving the saved garden first.`); export const NO_PLANTS = - trim(`Press "+" to add a plant to your garden.`); + trim(`Press + to add a plant to your garden`); export const NO_GARDENS = - trim(`Press "+" to add a garden.`); + trim(`Press + to add a garden`); export const NO_POINTS = - trim(`Press "+" to add a point to your garden.`); + trim(`Press + to add a point to your garden`); export const NO_GROUPS = - trim(`Press "+" to add a group.`); + trim(`Press + to add a group`); export const NO_WEEDS = - trim(`Press "+" to add a weed.`); + trim(`Press + to add a weed`); export const NO_FARMWARE = - trim(`Press "+" to add a Farmware.`); + trim(`Press + to add a Farmware`); export const NO_PERIPHERALS = trim(`Press "edit" to add a peripheral.`); @@ -1178,10 +1218,10 @@ export namespace Content { trim(`Press "edit" to add a sensor.`); export const NO_ZONES = - trim(`Press "+" to add a zone.`); + trim(`Press + to add a zone`); export const NO_CURVES = - trim(`Press "+" to add a curve.`); + trim(`Press + to add a curve`); export const ENTER_CROP_SEARCH_TERM = trim(`Search for a crop to add to your garden.`); @@ -1193,10 +1233,10 @@ export namespace Content { trim(`add this crop on OpenFarm?`); export const NO_TOOLS = - trim(`Press "+" to add a new tool or seed container.`); + trim(`Press + to add a new tool or seed container`); export const NO_SEED_CONTAINERS = - trim(`Press "+" to add a seed container.`); + trim(`Press + to add a seed container`); export const MOUNTED_TOOL = trim(`The tool currently mounted to the UTM can be set here or by using @@ -1205,7 +1245,7 @@ export namespace Content { // Farm Events export const NOTHING_SCHEDULED = - trim(`Press "+" to schedule an event.`); + trim(`Press + to schedule an event`); export const REGIMEN_TODAY_SKIPPED_ITEM_RISK = trim(`You are scheduling a regimen to run today. Be aware that @@ -2089,7 +2129,7 @@ export enum DeviceSetting { addNewPinBinding = `Add new pin binding`, // Pin Guard - pinGuardLabels = ``, + pinGuardLabels = `Pin Guard`, pinGuardTitles = `pin timeout (sec) to state`, pinGuard = `Pin Guard`, pinGuard1 = `Pin Guard 1`, @@ -2143,6 +2183,21 @@ export enum DeviceSetting { customSettings = `Custom Settings`, envEditor = `ENV Editor`, + // 3D + threeDGarden = `3D Garden`, + bedWallThickness = `Bed Wall Thickness`, + bedHeight = `Bed Height`, + ccSupportSize = `Cable Carrier Support Size`, + beamLength = `Beam Length`, + columnLength = `Column Length`, + zAxisLength = `Z Axis Length`, + bedXOffset = `Bed X Offset`, + bedYOffset = `Bed Y Offset`, + bedZOffset = `Bed Z Offset`, + legSize = `Leg Size`, + bounds = `Bounds`, + grid = `Grid`, + // Map showPlants = `Plants`, showPlantsMapLayer = `Show Plants Map Layer`, @@ -2221,6 +2276,7 @@ export enum DeviceSetting { discardUnsavedChanges = `Discard Unsaved Changes`, confirmEmergencyUnlock = `Confirm emergency unlock`, userInterfaceReadOnlyMode = `User Interface Read Only Mode`, + darkMode = `Dark Mode`, // Photos: Filters alwaysHighlightCurrentPhotoInMap = `always highlight current photo in map`, @@ -2411,6 +2467,7 @@ export enum Actions { TOGGLE_GRID_ID = "TOGGLE_GRID_ID", SET_GRID_START = "SET_GRID_START", TOGGLE_SOIL_HEIGHT_LABELS = "TOGGLE_SOIL_HEIGHT_LABELS", + SET_PANEL_OPEN = "SET_PANEL_OPEN", SET_PROFILE_OPEN = "SET_PROFILE_OPEN", SET_PROFILE_AXIS = "SET_PROFILE_AXIS", SET_PROFILE_POSITION = "SET_PROFILE_POSITION", @@ -2422,6 +2479,9 @@ export enum Actions { SET_CROP_STAGE = "SET_CROP_STAGE", SET_CROP_PLANTED_AT = "SET_CROP_PLANTED_AT", + // 3D + SET_DISTANCE_INDICATOR = "SET_DISTANCE_INDICATOR", + // Regimens PUSH_WEEK = "PUSH_WEEK", POP_WEEK = "POP_WEEK", diff --git a/frontend/controls/__tests__/controls_page_test.tsx b/frontend/controls/__tests__/controls_page_test.tsx deleted file mode 100644 index b3ef107c14..0000000000 --- a/frontend/controls/__tests__/controls_page_test.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; -import { mount } from "enzyme"; -import { Controls } from "../controls_page"; -import { push } from "../../history"; -import { Path } from "../../internal_urls"; - -describe("", () => { - it("redirects", () => { - const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("redirecting"); - expect(push).toHaveBeenCalledWith(Path.controls()); - }); -}); diff --git a/frontend/controls/__tests__/pin_form_fields_test.tsx b/frontend/controls/__tests__/pin_form_fields_test.tsx index 01e3f8b6d1..4c6b59213c 100644 --- a/frontend/controls/__tests__/pin_form_fields_test.tsx +++ b/frontend/controls/__tests__/pin_form_fields_test.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import React from "react"; import { shallow } from "enzyme"; import { NameInputBox, PinDropdown, ModeDropdown } from "../pin_form_fields"; import { fakeSensor } from "../../__test_support__/fake_state/resources"; diff --git a/frontend/controls/axis_display_group.tsx b/frontend/controls/axis_display_group.tsx index 51d2c3b654..71d273f98e 100644 --- a/frontend/controls/axis_display_group.tsx +++ b/frontend/controls/axis_display_group.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Row, Col } from "../ui"; +import { Row } from "../ui"; import { AxisDisplayGroupProps, AxisProps } from "./interfaces"; import { isNumber, isUndefined } from "lodash"; import { t } from "../i18next_wrapper"; @@ -8,7 +8,7 @@ import { DevSettings } from "../settings/dev/dev_support"; const Axis = (props: AxisProps) => { const { axis, val, missedSteps, axisState, index, detectionEnabled } = props; - return + return
    {isNumber(missedSteps) && missedSteps >= 0 && detectionEnabled && { value={isNumber(val) ? val : "---"} />} {!isUndefined(axisState) && DevSettings.futureFeaturesEnabled() &&

    {t(axisState)}

    } - ; +
    ; }; export const AxisDisplayGroup = (props: AxisDisplayGroupProps) => { @@ -30,7 +30,7 @@ export const AxisDisplayGroup = (props: AxisDisplayGroupProps) => { z: !!props.firmwareSettings?.encoder_enabled_z, }; const common = { noValues: props.noValues }; - return + return { highlight={props.highlightAxis == "z"} axisState={props.axisStates?.z} /> {!props.noValues && - - - } + } ; }; diff --git a/frontend/controls/axis_input_box.tsx b/frontend/controls/axis_input_box.tsx index da59dfab6b..31c05a185d 100644 --- a/frontend/controls/axis_input_box.tsx +++ b/frontend/controls/axis_input_box.tsx @@ -1,16 +1,14 @@ -import * as React from "react"; +import React from "react"; import { AxisInputBoxProps } from "./interfaces"; -import { Col, BlurableInput } from "../ui/index"; +import { BlurableInput } from "../ui"; export const AxisInputBox = ({ onChange, value, axis }: AxisInputBoxProps) => { - return - { - const val = parseFloat(e.currentTarget.value); - onChange(axis, isNaN(val) ? undefined : val); - }} /> - ; + return { + const val = parseFloat(e.currentTarget.value); + onChange(axis, isNaN(val) ? undefined : val); + }} />; }; diff --git a/frontend/controls/axis_input_box_group.tsx b/frontend/controls/axis_input_box_group.tsx index a0913c95ed..f17e7bcd55 100644 --- a/frontend/controls/axis_input_box_group.tsx +++ b/frontend/controls/axis_input_box_group.tsx @@ -1,6 +1,6 @@ import React from "react"; import { AxisInputBox } from "./axis_input_box"; -import { Row, Col } from "../ui"; +import { Row } from "../ui"; import { AxisInputBoxGroupProps, AxisInputBoxGroupState } from "./interfaces"; import { isNumber } from "lodash"; import { Vector3 } from "farmbot"; @@ -44,7 +44,7 @@ export class AxisInputBoxGroup extends render() { const { x, y, z } = this.state; - return + return - - - + ; } } diff --git a/frontend/controls/controls.tsx b/frontend/controls/controls.tsx index f1d8fe1b92..efd37ac694 100644 --- a/frontend/controls/controls.tsx +++ b/frontend/controls/controls.tsx @@ -3,7 +3,7 @@ import { connect } from "react-redux"; import { DesignerPanel, DesignerPanelContent, } from "../farm_designer/designer_panel"; -import { DesignerNavTabs, Panel } from "../farm_designer/panel_header"; +import { Panel } from "../farm_designer/panel_header"; import { Peripherals } from "./peripherals"; import { WebcamPanel } from "./webcam"; import { PinnedSequences } from "./pinned_sequence_list"; @@ -20,25 +20,24 @@ import { } from "farmbot"; import { ResourceIndex } from "../resources/interfaces"; import { t } from "../i18next_wrapper"; -import { push } from "../history"; import { Path } from "../internal_urls"; import { RunButtonMenuOpen } from "../sequences/interfaces"; +import { Navigate } from "react-router"; +import { mapStateToProps } from "./state_to_props"; -export class RawDesignerControls - extends React.Component { - render() { - this.props.dispatch({ type: Actions.OPEN_POPUP, payload: "controls" }); - push(Path.plants()); - return - - -

    Controls have moved to the navigation bar.

    -
    -
    ; - } -} +export const RawDesignerControls = (props: DesignerControlsProps) => { + props.dispatch({ type: Actions.OPEN_POPUP, payload: "controls" }); + return + + +

    Controls have moved to the navigation bar.

    +
    +
    ; +}; -export const DesignerControls = connect()(RawDesignerControls); +export const DesignerControls = connect(mapStateToProps)(RawDesignerControls); +// eslint-disable-next-line import/no-default-export +export default DesignerControls; export interface ControlsPanelProps { dispatch: Function; @@ -81,7 +80,7 @@ export class ControlsPanel extends React.Component { }; Peripherals = () => { - return
    + return
    -

    {t("This page has moved. Redirecting...")}

    - ; - } -} diff --git a/frontend/controls/move/__tests__/bot_position_rows_test.tsx b/frontend/controls/move/__tests__/bot_position_rows_test.tsx index ce946ad82d..f2c44ebe98 100644 --- a/frontend/controls/move/__tests__/bot_position_rows_test.tsx +++ b/frontend/controls/move/__tests__/bot_position_rows_test.tsx @@ -19,7 +19,6 @@ import { bot } from "../../../__test_support__/fake_state/bot"; import { Dictionary } from "farmbot"; import { BooleanSetting } from "../../../session_keys"; import { clickButton } from "../../../__test_support__/helpers"; -import { push } from "../../../history"; import { Path } from "../../../internal_urls"; describe("", () => { @@ -100,6 +99,6 @@ describe("", () => { const wrapper = mount(); wrapper.find(".fa-ellipsis-v").first().simulate("click"); wrapper.find("a").simulate("click"); - expect(push).toHaveBeenCalledWith(Path.settings("axes")); + expect(mockNavigate).toHaveBeenCalledWith(Path.settings("axes")); }); }); diff --git a/frontend/controls/move/bot_position_rows.tsx b/frontend/controls/move/bot_position_rows.tsx index 2b0052b4be..d3a4d07e24 100644 --- a/frontend/controls/move/bot_position_rows.tsx +++ b/frontend/controls/move/bot_position_rows.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Row, Col, Popover } from "../../ui"; +import { Row, Popover } from "../../ui"; import { findAxisLength, findHome, moveAbsolute, moveToHome, setHome, updateMCU, } from "../../devices/actions"; @@ -13,7 +13,7 @@ import { Position } from "@blueprintjs/core"; import { disabledAxisMap, } from "../../settings/hardware_settings/axis_tracking_status"; -import { push } from "../../history"; +import { useNavigate } from "react-router"; import { AxisActionsProps, BotPositionRowsProps, SetAxisLengthProps, } from "./interfaces"; @@ -43,27 +43,27 @@ export const BotPositionRows = (props: BotPositionRowsProps) => { || (hasEncoders(props.firmwareHardware) && (getConfigValue(BooleanSetting.scaled_encoders) || getConfigValue(BooleanSetting.raw_encoders))); - return
    + return
    - - + +
    - - +
    +
    - - +
    +
    - +
    { axisStates={locationData.axis_states} busy={arduinoBusy} style={{ overflowWrap: "break-word" }} - label={t("Current position (mm)")} /> + label={t("Position (mm)")} /> {hasEncoders(props.firmwareHardware) && getConfigValue(BooleanSetting.scaled_encoders) && } + label={t("Encoder (mm)")} />} {hasEncoders(props.firmwareHardware) && getConfigValue(BooleanSetting.raw_encoders) && } + label={t("Encoder (raw)")} />} { dispatch, botPosition, sourceFwConfig, } = props; const className = lockedClass(locked); + const navigate = useNavigate(); return } content={
    @@ -140,7 +141,7 @@ export const AxisActions = (props: AxisActionsProps) => { onClick={setAxisLength({ axis, dispatch, botPosition, sourceFwConfig })}> {t("SET LENGTH")} - push(Path.settings("axes"))}> + { navigate(Path.settings("axes")); }}> {t("Settings")} diff --git a/frontend/controls/move/jog_buttons.tsx b/frontend/controls/move/jog_buttons.tsx index 8e070dfe85..cc16ff2726 100644 --- a/frontend/controls/move/jog_buttons.tsx +++ b/frontend/controls/move/jog_buttons.tsx @@ -152,9 +152,6 @@ export class JogButtons directionAxisProps={directionAxesProps.z} /> - - - ; } diff --git a/frontend/controls/move/motor_position_plot.tsx b/frontend/controls/move/motor_position_plot.tsx index 87adf3f8a3..e0ca4cde18 100644 --- a/frontend/controls/move/motor_position_plot.tsx +++ b/frontend/controls/move/motor_position_plot.tsx @@ -246,9 +246,6 @@ export const MotorPositionPlot = (props: MotorPositionPlotProps) => { const plotContentProps = { load, encoders }; return diff --git a/frontend/controls/move/move_controls.tsx b/frontend/controls/move/move_controls.tsx index 5d5383307d..d522900953 100644 --- a/frontend/controls/move/move_controls.tsx +++ b/frontend/controls/move/move_controls.tsx @@ -18,9 +18,9 @@ export const MoveControls = (props: MoveControlsProps) => { const locationData = validBotLocationData(location_data); const botOnline = isBotOnlineFromState(props.bot); const { busy, locked } = props.bot.hardware.informational_settings; - return
    + return
    } + target={} content={ + return
    {!this.props.hidePinBindings && -
    +
    {sortResourcesById(props.peripherals).map(peripheral => - - - - - - - - - - - - - + + + + + )}
    ; diff --git a/frontend/controls/peripherals/peripheral_list.tsx b/frontend/controls/peripherals/peripheral_list.tsx index e56433b6d6..0766730517 100644 --- a/frontend/controls/peripherals/peripheral_list.tsx +++ b/frontend/controls/peripherals/peripheral_list.tsx @@ -2,40 +2,34 @@ import React from "react"; import { pinToggle, writePin } from "../../devices/actions"; import { PeripheralListProps } from "./interfaces"; import { sortResourcesById } from "../../util"; -import { Row, Col, ToggleButton } from "../../ui"; +import { Row, ToggleButton } from "../../ui"; import { t } from "../../i18next_wrapper"; import { Slider } from "@blueprintjs/core"; import { ANALOG } from "farmbot"; import { lockedClass } from "../locked_class"; export const PeripheralList = (props: PeripheralListProps) => -
    +
    {sortResourcesById(props.peripherals).map(peripheral => { const toggleValue = (props.pins[peripheral.body.pin || -1] || { value: undefined }).value; - return - - - - -

    {"" + peripheral.body.pin}

    - - - {peripheral.body.mode == 1 - ? - : { - peripheral.body.pin && pinToggle(peripheral.body.pin); - }} - title={t(`Toggle ${peripheral.body.label}`)} - customText={{ textFalse: t("off"), textTrue: t("on") }} - className={lockedClass(props.locked)} - disabled={!!props.disabled} />} - + return + +

    {"" + peripheral.body.pin}

    + {peripheral.body.mode == 1 + ? + : { + peripheral.body.pin && pinToggle(peripheral.body.pin); + }} + title={t(`Toggle ${peripheral.body.label}`)} + customText={{ textFalse: t("off"), textTrue: t("on") }} + className={lockedClass(props.locked)} + disabled={!!props.disabled} />}
    ; })}
    ; diff --git a/frontend/controls/pinned_sequence_list.tsx b/frontend/controls/pinned_sequence_list.tsx index 3d9f6ba109..71b1807138 100644 --- a/frontend/controls/pinned_sequence_list.tsx +++ b/frontend/controls/pinned_sequence_list.tsx @@ -2,34 +2,32 @@ import React from "react"; import { t } from "../i18next_wrapper"; import { ToolTips } from "../constants"; import { TestButton } from "../sequences/test_button"; -import { Widget, WidgetHeader, WidgetBody, Row, Col } from "../ui"; +import { Widget, WidgetHeader, WidgetBody, Row } from "../ui"; import { PinnedSequencesProps } from "./interfaces"; export const PinnedSequences = (props: PinnedSequencesProps) => { const pinnedSequences = props.sequences.filter(x => x.body.pinned); return
    {pinnedSequences.length > 0 && - + - {pinnedSequences.map(sequence => - - +
    + {pinnedSequences.map(sequence => + - - - - )} + )} +
    }
    ; diff --git a/frontend/controls/webcam/__tests__/index_test.tsx b/frontend/controls/webcam/__tests__/index_test.tsx index 59ad507b19..2f13903918 100644 --- a/frontend/controls/webcam/__tests__/index_test.tsx +++ b/frontend/controls/webcam/__tests__/index_test.tsx @@ -24,7 +24,7 @@ describe("", () => { expect(wrapper.instance().state.activeMenu).toEqual("show"); const text = allButtonText(wrapper); expect(text.toLowerCase()).not.toContain("view"); - clickButton(wrapper, 2, "edit"); + clickButton(wrapper, 0, "edit"); expect(wrapper.instance().state.activeMenu).toEqual("edit"); }); diff --git a/frontend/controls/webcam/__tests__/show_test.tsx b/frontend/controls/webcam/__tests__/show_test.tsx index 2cce9cba38..e445abda32 100644 --- a/frontend/controls/webcam/__tests__/show_test.tsx +++ b/frontend/controls/webcam/__tests__/show_test.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import React from "react"; import { fakeWebcamFeed } from "../../../__test_support__/fake_state/resources"; import { mount } from "enzyme"; import { Show, IndexIndicator } from "../show"; @@ -54,13 +54,13 @@ describe("", () => { it("renders index indicator: position 1", () => { const wrapper = mount(); expect(wrapper.find("div").props().style) - .toEqual({ left: "calc(-10px + 0 * 50%)", width: "50%" }); + .toEqual({ left: "calc(0 * 50%)", width: "50%" }); }); it("renders index indicator: position 2", () => { const wrapper = mount(); expect(wrapper.find("div").props().style) - .toEqual({ left: "calc(-10px + 1 * 25%)", width: "25%" }); + .toEqual({ left: "calc(1 * 25%)", width: "25%" }); }); it("doesn't render index indicator", () => { diff --git a/frontend/controls/webcam/edit.tsx b/frontend/controls/webcam/edit.tsx index 1c9a82e71a..635215d24f 100644 --- a/frontend/controls/webcam/edit.tsx +++ b/frontend/controls/webcam/edit.tsx @@ -25,8 +25,8 @@ export function Edit(props: WebcamPanelProps) { const unsaved = props .feeds .filter(x => x.specialStatus === SpecialStatus.DIRTY); - return
    -
    + return
    +
    {rows}
    diff --git a/frontend/controls/webcam/key_val_edit_row.tsx b/frontend/controls/webcam/key_val_edit_row.tsx index 72ff3e55a1..bd74875017 100644 --- a/frontend/controls/webcam/key_val_edit_row.tsx +++ b/frontend/controls/webcam/key_val_edit_row.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Row, Col } from "../../ui"; +import { Row } from "../../ui"; import { t } from "../../i18next_wrapper"; export interface KeyValEditRowProps { @@ -19,28 +19,22 @@ export interface KeyValEditRowProps { /** A row containing two textboxes and a delete button. Useful for maintaining * lists of things (peripherals, feeds, tools etc). */ export function KeyValEditRow(p: KeyValEditRowProps) { - return - - - - - - - - - + return + + + ; } diff --git a/frontend/controls/webcam/show.tsx b/frontend/controls/webcam/show.tsx index b2e98a63ac..69c8f1ad04 100644 --- a/frontend/controls/webcam/show.tsx +++ b/frontend/controls/webcam/show.tsx @@ -19,16 +19,17 @@ type State = { const FALLBACK_FEED = { name: "", url: PLACEHOLDER_FARMBOT }; -export function IndexIndicator(props: { i: number, total: number }): JSX.Element { - const percentWidth = 100 / props.total; - return props.total > 1 - ?
    - :
    ; -} +export const IndexIndicator = + (props: { i: number, total: number }): React.ReactNode => { + const percentWidth = 100 / props.total; + return props.total > 1 + ?
    + :
    ; + }; export class Show extends React.Component { NO_FEED = t("No webcams yet. Click the edit button to add a feed URL."); @@ -54,7 +55,22 @@ export class Show extends React.Component { const flipper = new Flipper(feeds, FALLBACK_FEED, this.state.current); const msg = this.getMessage(flipper.current.url); const imageClass = msg.length > 0 ? "no-flipper-image-container" : ""; - return
    + return
    +
    +
    + + + +
    +
    + +
    +

    {msg}

    @@ -77,17 +93,6 @@ export class Show extends React.Component { {t("Next")}
    -
    - -

    {flipper.current.name}

    - - -
    ; } } diff --git a/frontend/css/_blueprint_overrides.scss b/frontend/css/_blueprint_overrides.scss index 8cd1d73d7f..70f8e94c2c 100644 --- a/frontend/css/_blueprint_overrides.scss +++ b/frontend/css/_blueprint_overrides.scss @@ -1,14 +1,20 @@ +@use "variables" as *; +@use "sass:color"; + // Padding for the popups. .bp5-popover-content { z-index: 999; padding: 1rem; + color: var(--text-color) !important; + background: var(--main-bg) !important; + box-shadow: var(--box-shadow) !important; } .bp5-popover { - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2); - .bp5-popover-content { - border-radius: 5px; - } + box-shadow: var(--box-shadow); + font-size: 1.3rem; + border-radius: 0.5rem; + overflow: hidden; .bp5-popover-arrow { display: none !important; } @@ -32,26 +38,50 @@ overflow-y: scroll; } +.bp5-input { + box-shadow: none; + border-radius: 0; +} + .bp5-input-group { + border-bottom: 1.5px solid var(--border-color); .bp5-icon { top: 0.6rem; } } +.bp5-popover.help { + .bp5-popover-content { + width: 32rem; + } +} + +.bp5-slider { + margin: 0 2.5rem 0 1.5rem; + width: initial; +} + +.bp5-slider-progress { + background: var(--secondary-bg); +} + +.bp5-control { + margin: 0; +} + .bp5-button:not(.bp5-minimal) { font-weight: normal; text-transform: none; - background: $white; - border-radius: 0; + border-radius: 3px; font-size: 1.2rem !important; color: $black !important; height: 27px !important; width: 100% !important; border: 0 !important; - box-shadow: 0px 0px 10px #ddd !important; - background: $white !important; + box-shadow: none !important; + background: $translucent9_white !important; &:hover { - background: $white !important; + background: $translucent9_white !important; } :first-child { display: inline-block; @@ -59,15 +89,7 @@ width: 73%; overflow: hidden; text-overflow: ellipsis; - padding-right: 1rem; - } -} - -.bp5-popover-wrapper { - display: block; - position: relative; - * { - text-align: left; + padding-right: 1.5rem; } } diff --git a/frontend/css/_index.scss b/frontend/css/_index.scss index c120a66489..d564ca4285 100644 --- a/frontend/css/_index.scss +++ b/frontend/css/_index.scss @@ -1,38 +1,58 @@ -@use "sass:color"; -// Blueprint -@import "~/node_modules/@blueprintjs/core/lib/css/blueprint.css"; -@import "~/node_modules/@blueprintjs/icons/lib/css/blueprint-icons.css"; -// Partials -@import "animations"; -@import "colors"; -@import "fonts"; -@import "numbers"; // Global -@import "global"; -@import "mobile"; -@import "blueprint_overrides"; -// Designer -@import "farm_designer/farm_designer_panels"; -@import "farm_designer/farm_designer"; -@import "farm_designer/farm_events"; -// Sequences and Regimens -@import "sequences"; -@import "regimens"; -// App loading animations -@import "spinner"; -@import "loading_plant"; +@use "global/imports"; +@use "global/fonts"; +@use "global/global"; +@use "global/buttons"; +@use "global/inputs"; +@use "global/labels"; +@use "global/saucers"; +@use "global/sliders"; +@use "global/tables"; +@use "global/tooltips"; +// Animations +@use "animations/animations"; +@use "animations/spinner"; +@use "animations/loading_plant"; +// App +@use "app/navbar"; +@use "app/static_pages"; +@use "app/status_ticker"; +@use "app/toastr"; // Components -@import "buttons"; -@import "image_flipper"; -@import "inputs"; -@import "steps"; // must be imported after "inputs" -@import "labels"; -@import "navbar"; -@import "static_pages"; -@import "status_ticker"; -@import "tables"; -@import "toastr"; -@import "tooltips"; -@import "widget_move"; -@import "widgets"; -@import "scrollbar"; +@use "components/go_button"; +@use "components/image_flipper"; +@use "components/widgets"; +// Farm Designer +@use "farm_designer/farm_designer_panels"; +@use "farm_designer/farm_designer"; +@use "farm_designer/profile_viewer"; +@use "farm_designer/three_d_garden"; +// Panels +@use "panels/connectivity"; +@use "panels/controls"; +@use "panels/curves"; +@use "panels/events"; +@use "panels/farmware"; +@use "panels/gardens"; +@use "panels/help"; +@use "panels/jobs"; +@use "panels/location_info"; +@use "panels/logs"; +@use "panels/messages"; +@use "panels/peripherals"; +@use "panels/photos"; +@use "panels/plants"; +@use "panels/points"; +@use "panels/regimens"; +@use "panels/select"; +@use "panels/sensors"; +@use "panels/sequence_steps"; +@use "panels/sequences"; +@use "panels/settings"; +@use "panels/setup_wizard"; +@use "panels/tools"; +@use "panels/webcams"; +// Other +@use "mobile"; +@use "not_bootstrap"; +@use "blueprint_overrides"; diff --git a/frontend/css/_mobile.scss b/frontend/css/_mobile.scss index 25778ca031..0bc51c4d75 100644 --- a/frontend/css/_mobile.scss +++ b/frontend/css/_mobile.scss @@ -1,3 +1,6 @@ +@use "variables" as *; +@use "sass:color"; + @media screen and (max-width: 1075px) { .all-content-wrapper { padding: 11rem 0 0; diff --git a/frontend/css/_not_bootstrap.scss b/frontend/css/_not_bootstrap.scss new file mode 100644 index 0000000000..4e568b07a0 --- /dev/null +++ b/frontend/css/_not_bootstrap.scss @@ -0,0 +1,129 @@ +@use "variables" as *; +@use "sass:color"; + +.grid { + display: grid; + gap: 1rem; +} + +.row { + display: grid; + grid-auto-flow: column; + gap: 1rem; + align-items: center; + // background: #f6ff8d; + .fb-button { + margin: 0; + } +} + +.half-gap { + gap: 0.5rem; +} + +.double-gap { + gap: 2rem; +} + +.no-gap { + gap: 0; +} + +.align-baseline { + align-items: baseline; +} + +.grid-exp-1 { + grid-template-columns: 1fr; +} + +.grid-exp-2 { + grid-template-columns: auto 1fr; +} + +.grid-exp-3 { + grid-template-columns: auto auto 1fr; +} + +.grid-2-col { + grid-template-columns: 1fr 1fr; +} + +.grid-3-col { + grid-template-columns: 1fr 1fr 1fr; +} + +.grid-4-col { + grid-template-columns: 1fr 1fr 1fr 1fr; +} + +.key-value-edit-row { + grid-template-columns: 1fr 1fr auto; +} + +.speed-grid { + grid-template-columns: 25% 1fr; + align-items: start; +} + +.change-ownership-grid { + grid-auto-flow: unset; + grid-template-columns: auto 1fr; + row-gap: 0.25rem; +} + +fieldset { + margin: 0; + padding: 0; +} + +.advanced-group-criteria { + display: grid; + grid-template-columns: 1fr auto 1fr auto; + gap: 1rem; +} + +.weed-detection-grid { + gap: 3rem; +} + +.panel-title { + display: grid; + font-family: 'Inknut Antiqua', serif; + font-weight: bold; + grid-template-columns: auto 1fr auto; + align-items: center; +} + +.add-point-grid { + grid-template-columns: 2fr 1fr 1fr 1fr auto; +} + +.criteria-operators-grid { + gap: 0; + text-align: center; +} + +.grid-planting-grid { + grid-template-columns: 1fr 50px 50px; +} + +.axes-grid { + grid-template-columns: 22% 1fr; +} + +.axes-grid-header { + text-align: center; + grid-template-columns: 24% 1fr 1fr 1fr; +} + +.mcu-inputs, .calibration-row-axes { + justify-items: center; +} + +.z-setting-grid, .single-setting-grid { + grid-template-columns: 1fr 23.6%; + button { + justify-self: center; + } +} diff --git a/frontend/css/animations.scss b/frontend/css/animations/animations.scss similarity index 84% rename from frontend/css/animations.scss rename to frontend/css/animations/animations.scss index 44ec72f457..0cf9218299 100644 --- a/frontend/css/animations.scss +++ b/frontend/css/animations/animations.scss @@ -1,3 +1,6 @@ +@use "../variables" as *; +@use "sass:color"; + @keyframes rotate { 0% { transform: rotate(0deg); @@ -85,7 +88,7 @@ @keyframes flash { 0% { - text-shadow: white 0px 0px 10px; + text-shadow: $white 0px 0px 10px; } 100% { text-shadow: none; @@ -130,3 +133,21 @@ opacity: 0; } } + +@keyframes panel-show { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(0); + } +} + +@keyframes panel-hide { + 0% { + transform: translateX(0); + } + 100% { + transform: translateX(-120%); + } +} diff --git a/frontend/css/loading_plant.scss b/frontend/css/animations/loading_plant.scss similarity index 96% rename from frontend/css/loading_plant.scss rename to frontend/css/animations/loading_plant.scss index 2b9032e7f7..2bb3054ed0 100644 --- a/frontend/css/loading_plant.scss +++ b/frontend/css/animations/loading_plant.scss @@ -1,3 +1,6 @@ +@use "../variables" as *; +@use "sass:color"; + $duration: 2; .loading-plant-div-container { display: flex; @@ -107,6 +110,7 @@ $duration: 2; } .loading-plant-text { + fill: $off_white; opacity: 1; } diff --git a/frontend/css/spinner.scss b/frontend/css/animations/spinner.scss similarity index 95% rename from frontend/css/spinner.scss rename to frontend/css/animations/spinner.scss index a5e02ccf29..03d20bdb90 100644 --- a/frontend/css/spinner.scss +++ b/frontend/css/animations/spinner.scss @@ -1,6 +1,10 @@ +@use "../variables" as *; +@use "sass:color"; + $offset: 187; $quarterOffset: 46.75; $duration: 1.3s; + .spinner-container { display: flex; position: fixed; diff --git a/frontend/css/navbar.scss b/frontend/css/app/navbar.scss similarity index 81% rename from frontend/css/navbar.scss rename to frontend/css/app/navbar.scss index 40dab303f7..f89c1e6845 100644 --- a/frontend/css/navbar.scss +++ b/frontend/css/app/navbar.scss @@ -1,15 +1,17 @@ +@use "../variables" as *; +@use "sass:color"; + .nav-wrapper { position: fixed; left: 0; right: 0; z-index: 99; height: 8.9rem; - background: $dark_gray; - box-shadow: 0 4px 10px rgba(0, 0, 0, .2); + background: linear-gradient(0deg, transparent, $translucent2 30%, $black); } nav { - margin: 3.4rem auto 0; + margin-top: 3rem; button { margin: 1.8rem 1.8rem 0 0; font-size: 1.3rem !important; @@ -83,7 +85,7 @@ nav { a { display: inline-block; position: relative; - height: 5.5rem; + height: 5rem; font-size: 1.2rem; white-space: nowrap; padding: 1.5rem 0.5rem; @@ -104,13 +106,10 @@ nav { &.active { font-weight: bold; color: $white; + border-bottom: 3px solid $white; img { opacity: 1; } - &:after { - transition: all 0.4s ease; - transform: translateY(-3px); - } } &.beacon { &.active { @@ -119,24 +118,6 @@ nav { } } } - &:before { - content: ""; - position: absolute; - left: 0; - bottom: 0; - right: 0; - height: 3px; - background: $white; - } - &:after { - content: ""; - position: absolute; - left: 0; - bottom: 0; - right: 0; - height: 3px; - background: $dark_gray; - } div .saucer, .external-icon { display: inline-flex; margin-left: 1rem; @@ -169,7 +150,8 @@ nav { .nav-coordinates, .jobs-button, .setup-button, - .connectivity-button { + .connectivity-button, + .nav-name { display: flex; position: relative; gap: 1rem; @@ -186,6 +168,7 @@ nav { line-height: 3rem; color: $light_gray; background: $medium_gray; + user-select: none; &.hover, &:hover { color: $white; @@ -273,21 +256,7 @@ nav { } } .nav-name { - padding: 0.75rem; - border-radius: 5px; margin-top: -0.75rem; - font-weight: normal; - &:hover { - background: rgba(255, 255, 255, 0.2); - } - &:after{ - content: attr(data-title); - display: block; - font-weight: bold; - height: 0; - overflow: hidden; - visibility: hidden; - } } } @@ -306,10 +275,12 @@ nav { .bp5-popover-content { position: relative; width: 22rem; - background: $dark_gray; + background: var(--main-bg); + backdrop-filter: blur(10px); padding: 0; font-size: 1.2rem; letter-spacing: 1.2px; + color: var(--text-color); i { margin-right: 0.8rem; } @@ -317,32 +288,31 @@ nav { display: inline-block; width: 100%; text-transform: uppercase; - color: $off_white; padding: 0.5rem 1rem; } img { margin-right: 0.8rem; - margin-left: -0.1rem; - margin-bottom: 0.25rem; - filter: invert(1); } .app-version { - background: $dark_gray; - color: $white; - padding: 0.5rem 0 0 1rem; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; + padding: 0.5rem 1rem 0; + margin-top: 0.5rem; + border-top: 1px solid var(--border-color); label { - color: $white; + font-weight: normal; } a { - width: auto; - color: $white; + text-align: right; + border-radius: 0.5rem; } - p { - display: inline; - color: $gray; - font-size: 1.2rem; + } + .dark-mode-toggle { + padding: 0.5rem 1rem; + line-height: 1.8rem; + label { + font-weight: normal; + } + .fb-button { + margin: 0; } } } @@ -361,6 +331,16 @@ nav { } } +body:has(.app.dark) { + .menu-popover { + .bp5-popover-content { + img { + filter: invert(0.75); + } + } + } +} + .mobile-menu { padding: 0; .links { @@ -370,13 +350,9 @@ nav { overflow-x: hidden; padding-bottom: 3rem; &::-webkit-scrollbar { - background-color: $medium_gray; - } - &::-webkit-scrollbar-track { - background-color: $medium_gray; - } - &::-webkit-scrollbar-thumb { - background-color: $light_gray; + display: none !important; + width: 0px !important; + background-color: transparent !important; } } .nav-links { @@ -412,6 +388,22 @@ nav { } } +.nav-additional-menu { + padding: 0.5rem 0; + a:hover { + background: var(--secondary-bg); + } +} + +.read-only-icon { + margin: 9px 0px 0px 9px; + float: right; + box-sizing: inherit; + .fa-pencil { + color: $white; + } +} + @media screen and (max-width: 1075px) { .top-menu-container .nav-links { display: none !important; diff --git a/frontend/css/app/static_pages.scss b/frontend/css/app/static_pages.scss new file mode 100644 index 0000000000..6896423708 --- /dev/null +++ b/frontend/css/app/static_pages.scss @@ -0,0 +1,362 @@ +@use "../variables" as *; +@use "sass:color"; + +.static-page { + min-height: 100vh; + max-height: 100%; + background: url(/public/app-resources/img/plant-icon-background.png), linear-gradient(#00b685, #003f53); + background-size: 600px; + padding: 8rem 2rem; + h1, + h2 { + font-family: "Cabin", Arial, Helvetica, sans-serif !important; + font-weight: 100 !important; + color: $white; + text-shadow: 0 0 25px rgba(0, 0, 0, 0.1), 0 0 25px rgba(0, 0, 0, 0.1); + } + h1 { + font-family: "Inknut Antiqua" !important; + font-weight: bold !important; + font-size: 3.4rem; + line-height: 3.6rem; + } + h2 { + margin: 0; + font-size: 2.2rem; + } + form { + display: grid; + gap: 1rem; + } + input { + font-family: revert; + font-size: revert; + } + .all-content-wrapper { + max-width: 60rem; + } + .widget-wrapper { + box-shadow: 0 0 20px rgba(0, 0, 0, 0.2); + margin: 0 auto; + max-width: 40rem; + text-align: start; + width: 40rem; + } + .or-divider { + color: $white; + display: flex; + font-style: italic; + gap: 1rem; + justify-self: center; + text-align: center; + width: 10rem; + hr { + width: 100%; + border: 1.5px solid rgb(255 255 255 / 50%) + } + } + .front-page-container { + display: grid; + grid-auto-flow: row; + gap: 3rem; + justify-items: stretch; + } + .forgot-password-login-row { + display: grid; + grid-template-columns: 1fr auto; + gap: 1rem; + align-items: center; + } + .forgot-password { + display: inline-block; + color: $blue; + } + .create-account-button, .reset-password-button { + justify-self: right; + } + input[type="checkbox"] { + float: left; + margin-right: 0.5rem; + margin-top: 0.25rem; + } + a:link { + font-weight: 300; + color: $dark_gray; + } + a:visited { + color: $medium_gray; + } + a:hover { + font-weight: 500; + } + a:active { + color: $medium_gray; + font-weight: 500; + } + .fa-gear { + float: right; + } + .fa-external-link { + margin-left: 1rem; + margin-bottom: 0.5rem; + } + .pull-right { + justify-self: right; + } +} + +.os-download-page { + table { + a { + text-transform: none; + } + } +} + +.featured-sequences-page, +.os-download-page { + text-align: center; + .all-content-wrapper { + padding-top: 0 !important; + min-height: 30rem; + } + h1 { + margin-top: 5rem; + font-size: 2rem !important; + text-shadow: 0 0 5px rgba(0, 0, 0, 0.1), 0 0 25px rgba(0, 0, 0, 0.1) !important; + } + p { + margin: auto; + width: 70%; + color: $off_white; + } + a { + white-space: nowrap; + font-weight: bold !important; + color: $off_white !important; + &:hover { + color: $white !important; + } + &:visited { + color: $off_white; + } + &:link { + color: $off_white; + } + &:active { + color: $white !important; + } + } + table { + font-size: 1.1rem; + color: $gray; + text-align: left; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(10px); + border-radius: 2rem; + thead, + tr:not(:last-child) { + border-bottom: 2px solid $translucent2_white; + } + th { + padding: 1rem 1.5rem; + } + td { + padding: 1rem 1.5rem; + white-space: pre; + span { + display: block; + white-space: nowrap; + } + } + } + button { + cursor: pointer; + } + .wizard-btn { + margin: 1rem; + float: none; + color: $white; + margin-top: 7rem; + } + .os-download-wizard { + background: rgba(0, 0, 0, 0.5); + border-radius: 2rem; + backdrop-filter: blur(10px); + padding: 2rem; + .transparent-button { + margin: 1rem; + float: none; + color: $white; + height: 5rem; + width: 15rem; + } + .back { + position: absolute; + top: 1rem; + left: 1rem; + height: unset; + width: unset; + } + .start { + height: unset; + width: unset; + } + p { + font-size: 1.4rem; + } + .os-download-wizard-note { + font-size: 1.4rem; + font-weight: bold; + } + .os-download-wizard-run { + img { + width: 100%; + border-radius: 1rem; + } + } + .buttons { + button { + border-radius: 0; + padding: 0; + border: 3px solid $white; + img { + width: 100%; + } + &:hover { + border-color: $off_white; + .btn-text { + background: color.adjust($cyan, $lightness: -10%); + } + } + } + } + .download-wizard-button { + .os-wizard-content-button { + border-radius: 8px; + .btn-text { + background: $cyan; + border-radius: 5px 5px 0 0; + .btn-title { + font-family: "Inknut Antiqua"; + font-weight: bold; + margin-bottom: 0.5rem !important; + padding-top: 0.75rem; + font-size: 1.7rem; + } + .os-download-wizard-btn-label { + width: 75%; + font-size: 1.2rem; + padding-bottom: 0.5rem; + line-height: 1.5rem; + } + } + img { + border-radius: 0 0 5px 5px; + } + &.white { + background: $white !important; + border-color: $off_white; + .btn-text { + background: $off_white; + .btn-title { + color: $dark_gray; + } + } + &:hover { + background: $off_white !important; + border-color: $white; + .btn-text { + background: $white; + } + } + } + &.black { + background: $white !important; + border-color: $dark_gray; + .btn-text { + background: $dark_gray; + .btn-title { + color: $white; + } + } + &:hover { + background: $off_white !important; + border-color: $darker_gray; + .btn-text { + background: $darker_gray; + } + } + } + } + } + .download-link { + justify-items: center; + a { + text-transform: none; + height: unset; + } + } + .buttons-with-image { + display: flex; + .buttons { + margin: auto; + margin-right: 0; + } + img { + margin: auto; + margin-left: 3rem; + width: 12rem; + border-radius: 5px; + border: 3px solid $lighter_gray; + } + } + } +} + +.static-page { + &.os-download-page { + .os-download-description { + width: 80%; + } + p { + font-size: 1.5rem; + line-height: 2rem; + } + h1 { + margin: 0; + font-family: "Inknut Antiqua" !important; + font-weight: 600 !important; + } + } +} + +.featured-sequences-page { + summary { + color: $light_gray; + cursor: pointer; + } + .sequence-description { + margin-left: -1.5rem; + margin-right: -5rem; + } + .markdown { + p { + color: $white !important; + } + } + details { + max-width: calc(min(70vw, 370px)); + } + summary { + margin-bottom: 0.5rem; + } + h1, + h2 { + margin-bottom: 0; + } + li, + p { + white-space: pre-wrap; + } +} diff --git a/frontend/css/status_ticker.scss b/frontend/css/app/status_ticker.scss similarity index 82% rename from frontend/css/status_ticker.scss rename to frontend/css/app/status_ticker.scss index f8049a0ffb..bf5d8524a0 100644 --- a/frontend/css/status_ticker.scss +++ b/frontend/css/app/status_ticker.scss @@ -1,12 +1,15 @@ +@use "../variables" as *; +@use "sass:color"; + .ticker-list { position: fixed; top: 0; left: 0; right: 0; z-index: 3; - background: $black; + background: $dark_bg; opacity: 0.9; - min-height: 3.25rem; + min-height: 3rem; cursor: pointer; label { cursor: pointer; @@ -52,14 +55,8 @@ } .status-ticker-wrapper { - padding: 0.6rem 1rem 0.4rem 1rem; - transition: all 0.2s ease; - &:hover { - background: rgba(255, 255, 255, 0.2); - } + padding: 0.7rem 1rem; .saucer { - margin: 0.2rem 0.6rem 0rem 0; - float: left; width: 1.6rem; height: 1.6rem; border-radius: 50%; @@ -70,6 +67,7 @@ .first-ticker { .markdown p { font-weight: 600; + display: block; code { background: $dark_gray; } @@ -79,8 +77,6 @@ .first-ticker { height: 3rem; overflow: hidden; - padding-bottom: 0.5rem; - background: $black; .status-ticker-created-at { font-size: 1.2rem; font-weight: 600; @@ -108,13 +104,6 @@ -webkit-animation: flash 1.5s ease-in; animation: flash 1.5s ease-in; } - &.prefix { - .markdown { - p { - width: calc(100% - 21rem); - } - } - } } .logs-page-link { diff --git a/frontend/css/toastr.scss b/frontend/css/app/toastr.scss similarity index 68% rename from frontend/css/toastr.scss rename to frontend/css/app/toastr.scss index b1d7725589..742de0bebe 100644 --- a/frontend/css/toastr.scss +++ b/frontend/css/app/toastr.scss @@ -1,3 +1,6 @@ +@use "../variables" as *; +@use "sass:color"; + .toast-container { display: flex; position: fixed; @@ -11,7 +14,7 @@ .toast { position: relative; width: 100%; - box-shadow: 0px 1px 4px #555; + box-shadow: 0px 0 1rem $translucent2; pointer-events: all; padding: 1.8rem; cursor: pointer; @@ -24,7 +27,7 @@ font-size: 1.2rem; opacity: 0; border: none; - border-radius: 3px; + border-radius: 0.5rem; -webkit-font-smoothing: antialiased; transition: all 0.2s ease; &:hover { @@ -91,6 +94,7 @@ pointer-events: none; font-size: 1.6rem; font-weight: bold; + font-family: 'Inknut Antiqua', serif; } .toast-message { @@ -114,6 +118,12 @@ height: 1.6rem; overflow: hidden; transform: rotate(180deg); + .fb-icon-button { + color: $off_white !important; + &.disabled { + opacity: 0.25; + } + } } .toast-loader-left, @@ -169,3 +179,55 @@ opacity: 1; } } + +.tour-toast { + cursor: default !important; + .toast-title, + .toast-message { + color: $white !important; + i { + margin-left: 0.5rem; + margin-right: 0.5rem; + } + .extra-content { + margin-top: 1rem; + } + } + .toast-loader { + display: flex; + width: unset; + height: unset; + transform: none; + .previous, + .next { + &.disabled { + pointer-events: none; + color: $dark_gray; + } + } + } + .toast-message { + margin-top: 1.5rem; + } + .toast-title, + .message-contents { + transition: height 0.4s, opacity 0.2s; + } + .message-contents { + &.height-hidden { + position: absolute; + visibility: hidden; + padding-right: 2rem; + } + } + .progress-indicator { + position: absolute; + left: 0; + bottom: 0; + height: 0.5rem; + background: $white; + border-bottom-right-radius: 0; + border-bottom-left-radius: 5px; + transition: width 0.4s 0.2s, border-bottom-right-radius 0.4s 0.2s; + } +} diff --git a/frontend/css/components/go_button.scss b/frontend/css/components/go_button.scss new file mode 100644 index 0000000000..d31a31c242 --- /dev/null +++ b/frontend/css/components/go_button.scss @@ -0,0 +1,80 @@ +@use "../variables" as *; +@use "sass:color"; + +.go-button-axes-wrapper { + justify-content: left; + button { + margin: 0; + float: none; + } + .go-button-axes-text { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + p { + font-weight: bold; + color: $off_white; + margin: 0 !important; + } + } + .go-button-axes-dropdown { + border-left: 1px solid $dark_gray!important; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + line-height: 1.5rem; + &.pseudo-disabled { + border-left: 1px solid $gray; + } + } +} + +.go-button-axes-popover { + .bp5-popover-content { + color: $off_white; + background: $dark_gray; + .go-axes { + display: grid; + gap: 0.75rem; + button { + margin: 0; + padding: 0.5rem 1rem; + float: none; + &.x, + &.y, + &.z { + grid-row: 1; + grid-column: span 4; + } + &.xy, + &.xyz { + grid-row: 2; + grid-column: span 6; + } + } + a, + p { + display: inline; + color: $off_white; + font-size: 1.2rem; + text-decoration: none !important; + font-weight: normal; + } + a { + grid-column: span 12; + i { + margin-left: 0.5rem; + } + &:hover { + text-decoration: underline !important; + } + } + input { + float: right; + box-shadow: none; + cursor: pointer; + } + .save-as-default-wrapper { + grid-column: span 12; + } + } + } +} diff --git a/frontend/css/image_flipper.scss b/frontend/css/components/image_flipper.scss similarity index 64% rename from frontend/css/image_flipper.scss rename to frontend/css/components/image_flipper.scss index 1c76aeaba8..6a9684e4ae 100644 --- a/frontend/css/image_flipper.scss +++ b/frontend/css/components/image_flipper.scss @@ -1,25 +1,25 @@ +@use "../variables" as *; +@use "sass:color"; + .image-flipper { position: relative; - margin-top: 1rem; + border-radius: 0.5rem; + overflow: hidden; } .bp5-overlay { .image-flipper { z-index: 20; - margin: auto; - width: fit-content; - margin-top: 3%; + width: 100%; + display: grid; + justify-content: center; + align-content: center; .no-flipper-image-container, .flipper-image { - height: 80vh; - width: 80vw; text-align: center; .image-flipper-image, svg { - height: 100%; - max-width: unset; - max-height: unset; - min-height: unset; + filter: none; } } .image-flipper-left, @@ -30,7 +30,7 @@ } } .no-flipper-image-container { - background: $black; + background: var(--main-bg); } .placeholder { opacity: 1 !important; @@ -41,6 +41,14 @@ } } +body:has(.app.dark) { + .image-flipper-image { + &.placeholder { + filter: invert(1); + } + } +} + .desktop-only { @media screen and (max-width: 767px) { display: none; @@ -52,25 +60,26 @@ } .image-flipper-image { + display: block; margin: auto; - max-width: 100%; max-height: 650px; + max-width: 100%; min-height: 200px; - border-top-left-radius: 5px; - border-top-right-radius: 5px; } .image-flipper-left { - left: -5px; + left: 0; background: linear-gradient(to left, transparent, $translucent3); + box-shadow: none !important; &:hover { background: linear-gradient(to left, transparent, $translucent4); } } .image-flipper-right { - right: -5px; + right: 0; background: linear-gradient(to right, transparent, $translucent3); + box-shadow: none !important; &:hover { background: linear-gradient(to right, transparent, $translucent4); } @@ -78,11 +87,12 @@ .image-flipper-left, .image-flipper-right { + bottom: 0; + margin: 0; + padding: 0; position: absolute; - top: -5px; - bottom: -5px; + top: 0; width: 4rem; - text-align: center; i { color: $white; font-size: 2rem; @@ -91,9 +101,9 @@ } .no-flipper-image-container { - background: $panel_light_gray; img { - opacity: 0.4; + border-radius: 3px; + overflow: hidden; } p { position: absolute; @@ -109,17 +119,25 @@ } } +body:has(.app.darl) { + .no-flipper-image-container { + img { + filter: invert(1); + } + } +} + .photos-footer { display: flex; position: relative; justify-content: space-between; - background: $black; + background: var(--secondary-bg); border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; height: 3.5rem; padding-top: 0.5rem; .footer-text { - margin-top: -1rem; + margin-top: -0.5rem; } label { font-weight: normal; @@ -131,16 +149,6 @@ .bp5-popover-wrapper { display: inline; } - i { - color: $white; - } - .gradient { - position: absolute; - top: -3rem; - width: 100%; - height: 3rem; - background: linear-gradient(to top, $black 0%, transparent 30%); - } } .image-show-menu-target { @@ -167,7 +175,6 @@ } .shown-in-map-details { padding: 1rem; - background: $lighter_gray; &.shown { cursor: pointer; &:hover { @@ -274,14 +281,11 @@ margin-left: 1rem; margin-right: 0.5rem; } - p { - color: $off_white !important; - } } .image-created-at { - color: $white; margin-left: 1rem; + line-height: 1.25rem; span { white-space: nowrap; } @@ -292,16 +296,6 @@ } .photos { - .new-photo-button { - position: absolute; - top: 1rem; - right: 1rem; - p { - float: left; - margin-top: 0.75rem; - margin-right: 1rem; - } - } .photo-action-buttons { display: flex; gap: 1rem; @@ -337,110 +331,16 @@ } } -.camera-calibration, -.weed-detector { - .farmware-button { - position: relative; - top: 0rem; - z-index: 9; - height: 0px; - float: right; - } - .simple-camera-calibration-checkbox { - display: inline-block; - p { - display: inline-block; - margin: 0; - margin-top: 5rem; - } - svg { - position: absolute; - top: 0; - background: $black; - } - input { - float: left; - margin-right: 1rem; - } - } - .image-workspace { - .fb-button.green { - margin-top: 1rem; - } - .bp5-slider { - width: 95%; - margin-left: 1rem; - } - } - p { - margin: 1.5rem; - font-size: 1.2rem; - font-style: italic; - } - .bp5-popover-wrapper { - display: inline; - margin-left: 0.5rem; - &.input-error-wrapper { - display: block; - } - } - .filter-search { - .bp5-popover-wrapper { - display: inline-block; - margin-left: 0; - } - } - .camera-calibration-config { - label { - display: inline; - } - .camera-config-number-box { - margin-top: 1.5rem; - .input { - float: right; - margin-top: -0.25rem; - } - } - .dropdown-camera-calibration-config { - margin-top: 1.5rem; - .filter-search { - float: right; - margin-top: -0.25rem; - min-width: 16.5rem; - } - &.narrow { - .filter-search { - min-width: 8rem; - } - } - } - } -} - -.boolean-camera-calibration-config { - input[type=checkbox] { - display: block; - float: left; - margin-right: 1rem; - } - .bp5-popover-wrapper { - display: inline; - margin: 0.5rem; - } -} - .farmware-button { - div { - float: right; - } p { - float: right; - margin-top: 0.75rem; - margin-right: 1rem; color: $medium_gray; } } +.index-indicator-wrapper { + position: relative; +} + .index-indicator { position: absolute; bottom: 0; diff --git a/frontend/css/widgets.scss b/frontend/css/components/widgets.scss similarity index 88% rename from frontend/css/widgets.scss rename to frontend/css/components/widgets.scss index 3ed37a33e9..10f4bbfa1e 100644 --- a/frontend/css/widgets.scss +++ b/frontend/css/components/widgets.scss @@ -1,3 +1,6 @@ +@use "../variables" as *; +@use "sass:color"; + /** * Styling for the component. * Example output (w = widget): @@ -17,19 +20,22 @@ .widget-wrapper { position: relative; - box-shadow: 0px 0px 10px $gray; - margin-bottom: 3rem; + box-shadow: 0px 0px 1rem $translucent2; + border-radius: 0.5rem; + overflow: hidden; } .widget-header { - min-height: 3.3rem; background: $dark_gray; letter-spacing: .05rem; - padding: .75rem 1rem; - border-top-left-radius: 5px; - border-top-right-radius: 5px; + padding: 0 1.25rem; + display: grid; + grid-template-columns: 1fr auto; + gap: 1rem; + align-items: center; + height: 3.5rem; >*:not(h5):not(.title-help-icon):not(.title-help) { - margin-left: 1rem; + margin: 0; } .title-help{ display: inline; @@ -87,6 +93,7 @@ display: inline; color: $gray; font-size: 1.2rem; + margin: 0; text-transform: uppercase; } .bp5-popover-wrapper { @@ -108,11 +115,6 @@ border-color: $light_gray; color: $dark_gray; padding: 1.25rem; - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; - >.row:not(:first-of-type) { - margin-top: 1rem; - } } .widget-footer { diff --git a/frontend/css/farm_designer/farm_designer.scss b/frontend/css/farm_designer/farm_designer.scss index 8b92083898..a9f5649faf 100644 --- a/frontend/css/farm_designer/farm_designer.scss +++ b/frontend/css/farm_designer/farm_designer.scss @@ -1,14 +1,16 @@ +@use "../variables" as *; +@use "sass:color"; + .farm-designer { position: relative; height: 100vh; overflow: scroll; + scrollbar-width: none; .garden-map-legend { @media screen and (max-width: $mobile_max_width) { - &.panel-open { - display: none; - } + &.panel-open, &.short-panel { - top: 35rem; + display: none; } &.panel-closed-mobile, &.panel-closed { @@ -21,18 +23,21 @@ .farm-designer-map { display: inline-block; min-width: 100%; - padding: 11rem 2rem 2rem 2rem; // at zoom = 1.0: 110px 20px 20px 20px + padding: 9rem 2rem 2rem 2rem; height: 100%; overflow: visible; - transition: 0.2s ease; + transition: 0.3s ease; &.panel-open { - padding: 11rem 2rem 2rem 46.8rem; // at zoom = 1.0: 110px 20px 20px 468px + padding: 9rem 2rem 2rem 47.5rem; + @media screen and (max-width: $mobile_max_width) { + padding: 18rem 2rem 2rem 47.5rem; + } } &.panel-closed-mobile { - padding: 16rem 2rem 2rem 2rem; // at zoom = 1.0: 160px 20px 20px 20px + padding: 16rem 1rem 1rem 1rem; } &.short-panel { - padding: 35rem 2rem 2rem 2rem; // at zoom = 1.0: 350px 20px 20px 20px + padding: 35rem 1rem 1rem; } &::-webkit-scrollbar { display: none; @@ -72,131 +77,64 @@ .plant-catalog-image { width: 100%; background-position: center center !important; - background-size: 158% !important; + background-size: cover !important; background-color: $translucent; - height: 12rem; + aspect-ratio: 3 / 2; border-radius: 5px; -} - -.crop-search-result-wrapper { - max-height: calc(100vh - 19rem); - overflow-y: auto; - overflow-x: hidden; - margin-right: -10px; - padding-bottom: inherit; - .openfarm-search-results-wrapper { - display: grid; - gap: 2rem; - grid-template-columns: repeat(2, 1fr); - margin-left: 2.5rem; - margin-right: 2rem; - } - .plant-catalog-tile { - position: relative; - cursor: pointer; - text-align: center; - border-radius: 5px; - box-shadow: 0px 4px 7px 2px $translucent; - transition: transform 0.2s ease; - padding: 0; - &:hover { - box-shadow: 0px 5px 8px 2px rgba(0, 0, 0, 0.25); - transform: translateY(-1px); - } - label { - position: absolute; - left: 0; - bottom: -5px; - right: 0; - margin-top: 0 !important; - padding: 0.4rem 0.6rem 0.2rem; - background: linear-gradient(transparent, $translucent5); - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; - color: $white; - font-size: 1.2rem !important; - } + &.fallback-image { + background-size: 50% !important; + background-color: $translucent15 !important; } } .thin-search-wrapper { - width: 100%; - .thin-search { - .spinner-container { - position: absolute; - top: 0; - right: 0; - width: 2rem; - height: 2rem; - padding: 0; - margin-right: 1rem; + .spinner-container { + position: absolute; + top: 0; + right: 0; + width: 2rem; + height: 2rem; + padding: 0; + margin-right: 1rem; + } + .text-input-wrapper { + align-items: center; + border-bottom: 1.5px solid var(--border-color); + display: flex; + padding: 0 0.75rem; + i { + font-size: 1.5rem; } - .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; + .fa-search { + cursor: default !important; + } + .fa-times { + color: $medium_dark_red; + font-size: 1.3rem; + &:hover { + color: $red; } - &:after { - right: 0; + } + .point-sort-menu { + width: 110px; + label { + margin-top: 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; + cursor: pointer; padding: 0.5rem; - color: $darkest_red; - font-size: 1.3rem; - &:hover { - color: $medium_dark_red; - } - } - .bp5-popover-wrapper { - position: absolute; - .bp5-popover-target { - width: 3rem; - } - } - .point-sort-menu { - width: 110px; - label { - margin-top: 0; - } - i { - cursor: pointer; - padding: 0.5rem; - color: $medium_light_gray; - &.selected { - font-weight: bold; - color: $dark_gray; - } + color: $medium_light_gray; + &.selected { + font-weight: bold; + color: $dark_gray; } } } input { background: transparent; box-shadow: none !important; - padding-left: 3rem !important; font-size: 1.4rem !important; + color: inherit; &:active, &:focus { background: transparent !important; @@ -210,18 +148,21 @@ .panel-content { %panel-item-base { - text-align: right; font-size: 1rem; - padding-right: 1rem; - line-height: 3rem; - float: right; } - .plant-search-item, .group-search-item { + grid-template-columns: 1fr auto!important; + } + .plant-search-item, + .group-search-item, + .curve-search-item { + display: grid; + gap: 1rem; + grid-template-columns: auto 1fr auto; + align-items: center; cursor: pointer; padding: 0.5rem 1rem; img { - margin-right: 0.5rem; height: 3rem; width: 3rem; } @@ -239,21 +180,14 @@ } .curve-search-item-name, .plant-search-item-name, - .group-search-item-name, .regimen-search-item-name, .saved-garden-search-item-name, .weed-search-item-name, .point-search-item-name { display: inline-block; white-space: nowrap; - max-width: 60%; text-overflow: ellipsis; overflow: hidden; - vertical-align: middle; - margin-left: 1rem; - } - .group-search-item-name { - margin-left: 0; } .plant-search-item-age { @extend %panel-item-base; @@ -290,25 +224,13 @@ line-height: 3rem; float: right; } - .regimen-search-item-name, - .weed-search-item-name, - .point-search-item-name { - width: 40%; - margin-left: 1.25rem; - } .tool-search-item, .tool-slot-search-item { - line-height: 4rem; + margin: 0 -1rem; + padding: 0 1rem; cursor: pointer; - .row { - margin-left: 0.5rem; - margin-right: -25px; - } - .tool-search-item-icon, + .tool-svg, .tool-slot-search-item-icon { - width: fit-content; - padding-left: 10px; - padding-right: 0; svg { vertical-align: middle; } @@ -329,36 +251,32 @@ line-height: 4rem; &.tool-status, &.tool-slot-position { - float: right; + min-width: 10rem; + text-align: right; } } } .regimen-search-item { - position: relative; + display: grid; + gap: 1rem; + grid-template-columns: auto 1fr auto; + align-items: center; cursor: pointer; height: 3.5rem; + padding: 1rem; .regimen-search-item-name { - font-size: 1.2rem; - color: $dark_gray; - line-height: 3.5rem; - width: 80%; + font-size: 1.4rem; } .regimen-color { - display: inline-block; - vertical-align: middle; .color-picker { display: inline; .saucer { - margin-left: 3rem; width: 1.2rem; height: 1.2rem; } } } } - .padding { - height: 6rem; - } &.scrolled { box-shadow: inset 0 5px 10px rgba(0, 0, 0, 0.1); } @@ -616,98 +534,37 @@ } } -.saved-garden-indicator { - position: fixed; - top: 80px; - left: 50%; - z-index: 3; - padding: 2rem; - background: rgba(255, 255, 255, .75); - border-radius: 5px; - box-shadow: 0px 1px 4px #555; - text-align: center; - label { - display: block; - } - button { - margin: 0.5rem; - float: unset; - } -} - -.garden-snapshot { - button { - &.pseudo-disabled { - cursor: not-allowed; - } - } - textarea { - font-size: 1.2rem; - } -} - -.saved-garden-list { - .saved-garden-search-item { - padding: 0.25rem; - button { - margin-bottom: 1rem; - } - .saved-garden-info div { - height: 3rem; - line-height: 3rem; - cursor: pointer; - padding-right: 0; - span { - margin: 0; - pointer-events: none; - margin-left: 1rem; - } - p { - float: right; - line-height: 3rem; - text-align: center; - margin-right: 1rem; - } - } - } -} - .garden-map-legend { position: fixed; - top: 110px; + top: 90px; right: -155px; z-index: 3; transition: all 0.4s ease; &.active { - transform: translateX(-165px); + transform: translateX(-175px); } .content { display: flex; flex-direction: row-reverse; + gap: 1.25rem; + .menu-content, .z-display { + background-color: var(--main-bg); + border-radius: 1rem; + box-shadow: 0px 1px 5px rgba(0, 0, 0, .3); + backdrop-filter: blur(5px); + } .menu-content { - display: flex; - flex-direction: column; - flex-wrap: wrap; + display: grid; max-width: 155px; - padding: 1rem; - background: rgba(255, 255, 255, .75); - border-radius: 5px; - box-shadow: 0px 1px 4px #555; - >*+* { - margin: auto; - margin-top: 0.75rem; - } + padding: 1.25rem; + gap: 0.75rem; + justify-items: center; .zoom-buttons { - display: grid; - margin: auto; - width: 90%; + display: flex; + flex-direction: row-reverse; + gap: 1rem; button { margin: auto; - grid-row: 1; - grid-column: 2; - &.zoom-out { - grid-column: 1; - } } } } @@ -715,10 +572,6 @@ position: relative; padding: 2rem; padding-left: 3rem; - margin-right: 1.25rem; - background: rgba(255, 255, 255, .75); - border-radius: 5px; - box-shadow: 0px 1px 4px #555; label { position: absolute; top: 0.5rem; @@ -777,26 +630,6 @@ width: 5rem; } } - .caret-menu-button { - display: inline; - font-weight: bold; - font-size: medium; - cursor: pointer; - span { - margin-left: 0.25rem; - } - } - .more-bugs, - .select-mode, - .map-settings, - .move-to-mode { - margin: auto; - margin-top: 1rem; - p { - text-align: center; - padding-top: 2rem; - } - } .move-to-mode { display: none; @media screen and (max-width: $mobile_max_width) { @@ -806,10 +639,11 @@ .menu-pullout { position: absolute; left: -4.5rem; - color: $white; + color: $off_white; cursor: pointer; - transition: all 0.4s ease; + transition: all 0.3s ease; text-shadow: 0px 1px 1px #555; + font-size: 1.5rem; &.active { left: -4.5rem; transform: rotate(180deg); @@ -836,24 +670,6 @@ opacity: 0; } } - .map-rotate-button { - text-align: center; - .fb-button { - float: none; - } - } -} - -.map-plants-submenu, -.map-settings-submenu, -.farmbot-layer-submenu { - .bp5-popover-wrapper { - display: inline; - margin-left: 0.5rem; - } - .fb-toggle-button { - margin-left: 1rem; - } } .farmbot-origin { @@ -868,7 +684,7 @@ .quadrants { display: flex; flex-wrap: wrap; - border: 1px solid $dark_gray; + border: 1px solid var(--border-color); } .quadrant { display: inline-block; @@ -876,7 +692,7 @@ background-image: linear-gradient(rgba(0, 0, 0, 0.05) 1px, transparent 1px), linear-gradient(90deg, rgba(0, 0, 0, 0.05) 1px, transparent 1px), linear-gradient(rgba(0, 0, 0, 0.05) 2px, transparent 2px), linear-gradient(90deg, rgba(0, 0, 0, 0.05) 2px, transparent 2px); background-size: 4px 4px, 4px 4px, 100px 100px, 100px 100px; cursor: pointer; - border: 1px solid $dark_gray; + border: 1px solid var(--border-color); width: 50%; height: 24px; transition: all 0.2s ease-in-out; @@ -884,7 +700,7 @@ background-color: rgba(0, 0, 0, 0.1); } &.selected { - box-shadow: inset 0 0 8px $dark_gray; + box-shadow: inset 0 0 8px var(--border-color); } // Quadrant 1 &:nth-child(2) { &:before { @@ -945,48 +761,13 @@ } } -.map-plants-submenu, -.map-settings-submenu, -.map-points-submenu { - display: flex; - flex-direction: column; - fieldset { - display: flex; - margin-top: 0; - flex-direction: column; - align-items: center; - } - label { - margin-top: 0; - } - button { - margin: auto; - } - .fb-toggle-button { - margin-left: 1rem; - } +.map-size-grid { + grid-template-columns: auto 6rem !important; } .image-filter-menu { - label { - margin-top: 0 !important; - } - th { - text-align: center; - label { - margin: 0; - line-height: unset !important; - } - } - input { - background: $white; - } - .fa-step-backward, - .fa-caret-left, - .fa-caret-right, - .fa-step-forward { + .fa { font-weight: bold; - vertical-align: middle; } .fa-caret-left, .fa-caret-right { @@ -996,715 +777,31 @@ .fa-step-forward { font-size: 1rem; } - .fa-step-backward, - .fa-caret-left { - margin-right: 0.1rem; - } - .fa-caret-right, - .fa-step-forward { - margin-left: 0.1rem; - } } -.image-options { - .image-filter-menu { - margin-bottom: 1rem; - } - .non-layer-config-toggle { - margin-left: 1rem; - margin-right: 1rem; +@media screen and (min-width: $mobile_max_width) { + .farm-designer-panels.panel-closed ~ .three-d-garden .garden-bed-3d-model { + animation: panel-hide-shift-3d 0.3s ease forwards; } - .bp5-popover-wrapper { - display: inline; - margin-left: 0.5rem; - } - .fb-toggle-button { - margin-left: 1rem; + .farm-designer-panels.panel-open ~ .three-d-garden .garden-bed-3d-model { + animation: panel-show-shift-3d 0.3s ease forwards; } } -.sliders { - display: inline-block; - position: relative; - width: 100%; - margin-bottom: 3rem; - margin-top: 1rem; - .bp5-slider { - margin-left: 3rem; - margin-top: 1rem; - width: 80%; - } - &.vertical { - .bp5-slider { - margin-top: 0; - } - } - .bp5-slider-label { - white-space: nowrap; - text-align: center; - &:empty { - display: none; - } - } - .data-slider { - pointer-events: none; - .bp5-slider-axis, - .bp5-slider-track { - display: none; - } - .bp5-slider-label { - box-shadow: none; - } - .bp5-start { - top: 0.53rem; - width: 0.5px; - background: color.adjust($dark_gray, $alpha: -0.75); - box-shadow: none; - height: 0.55rem; - border-radius: 0; - &:first-of-type { - display: none; - } - } - &.vertical { - .bp5-start { - top: unset; - left: 0.53rem; - height: 0.5px; - width: 0.55rem; - border-radius: 0; - } - } - } - .input-slider { - position: absolute; - top: 0; - } -} - -.profile-viewer { - position: fixed; - bottom: 0; - z-index: 2; - width: calc(100% - $mobile_max_width); - background: #e5e5e5; - transition: transform 0.5s ease-out; - box-shadow: 0 0 1px rgba(0, 0, 0, .3); - transform: translateY(10rem) translateX($mobile_max_width); - &.panel-closed-mobile, - &.panel-closed { - width: 100%; - transform: translateY(10rem); - } - &.none-chosen { - transform: translateY(21rem) translateX($mobile_max_width); - &.panel-closed-mobile, - &.panel-closed { - transform: translateY(21rem); - } - } - @media screen and (max-width: $mobile_max_width) { - &.panel-open { - display: none; - } - } - &.open { - transform: translateY(0) translateX($mobile_max_width); - &.panel-closed-mobile, - &.panel-closed { - transform: translateY(0); - } - .profile-button { - i { - line-height: 6rem; - } - } - } - .profile-button { - margin: auto; - cursor: pointer; - margin-top: -3rem; - border-radius: 50%; - width: 6rem; - height: 6rem; - background: $magenta; - transition: background-color 0.3s ease-out; - text-align: center; - box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, .3); - &:hover { - background: color.adjust($magenta, $lightness: 5%); - } - i { - color: $off_white; - line-height: 3.25rem; - font-size: 2rem; - transition: line-height 0.5s ease-out; - } +@keyframes panel-show-shift-3d { + 0% { + transform: translateX(-22.5rem); } - .profile-content { - label { - display: block; - margin: auto; - margin-top: 1rem; - margin-bottom: 0.5rem; - text-align: center; - } - .no-profile { - height: 12.5rem; - text-align: center; - margin-top: 1.5rem; - } - .left-label, - .right-label { - position: absolute; - top: 50%; - font-weight: bold; - } - .left-label { - left: 2rem; - } - .right-label { - right: 2rem; - } - svg { - display: block; - margin: auto; - height: 10rem; - width: 90%; - border-radius: 5px; - padding: 1rem; - margin-bottom: 1rem; - background: #fafafa; - box-shadow: 0 0 4px rgba(0, 0, 0, .1); - &.expand { - height: unset; - max-height: 60vh; - } - } - .profile-options { - width: 100%; - height: 4rem; - text-align: center; - padding-top: 1rem; - padding-bottom: 1rem; - background: #f2f2f2; - box-shadow: 0px 0px 1px 0px #666666; - button { - display: inline; - float: none; - margin-right: 3rem; - } - label { - display: inline; - margin-right: 0.5rem; - line-height: 2rem; - vertical-align: text-top; - } - input { - height: 2rem !important; - width: 5rem; - margin-right: 3rem; - } - } + 100% { + transform: translateX(0); } } -.three-d-garden { - position: relative; - height: 100vh; - width: 100vw; - cursor: grab; - - body { - margin: 0; - } - - .garden-bed-3d-model { - position: relative; - height: 100vh; - width: 100vw; - cursor: grab; - - &:active { - cursor: grabbing; - } - } - - .gear { - position: absolute; - top: 1rem; - right: 1rem; - width: 1rem; - cursor: pointer; - background: rgba(255, 255, 255, 0.4); - box-shadow: $translucent2 0px 0px 5px; - padding: 7px; - border-radius: 5px; - backdrop-filter: blur(5px); - opacity: 0; - - &:hover { - opacity: 1; - } - } - - .overlay { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - pointer-events: none; - user-select: none; - - .settings-bar { - display: flex; - position: absolute; - left: 0; - bottom: 0; - right: 0; - justify-content: center; - gap: 1.25rem; - padding: 1rem; - overflow-x: scroll; - background: linear-gradient(0deg, $translucent5, transparent); - scrollbar-width: none; - - @media screen and (max-width: 768px) { - justify-content: left; - pointer-events: all; - } - - .setting-section { - pointer-events: all; - } - - .setting-title { - color: $off_white; - font-family: 'Inknut Antiqua'; - text-shadow: 0 0 10px black; - line-height: 1.8rem; - text-align: center; - } - - .row { - display: flex; - margin: 0; - background: rgba(255, 255, 255, 0.6); - box-shadow: $translucent2 0px 0px 1rem; - border-radius: 2.5rem; - height: 3.1rem; - padding: 0.3rem; - justify-content: space-evenly; - backdrop-filter: blur(5px); - gap: 0.5rem; - &:after, - &:before { - content: unset; - display: unset; - clear: unset; - } - } - - button { - padding: 0 0.85rem; - border-radius: 2.5rem; - font-weight: bold; - border: none; - background: none; - white-space: nowrap; - color: $off_black; - - &.outdoor.active, - &.lab.active, - &.genesis.active, - &.standard.active, - &.mobile.active { - background: rgba(255, 255, 255, 0.6); - } - - &.genesis-xl.active { - background: linear-gradient(315deg, #22a36d, #4ea3ed); - color: $off_white; - } - - &.winter.active { - background: linear-gradient(-15deg, #152e40, #3e8dc2); - color: $off_white; - } - - &.spring.active { - background: linear-gradient(15deg, #055b08, #49cc78); - color: $off_white; - } - - &.summer.active { - background: linear-gradient(-15deg, #d87f09, #f5e40a); - } - - &.fall.active { - background: linear-gradient(15deg, #502402, #c69075); - color: $off_white - } - - &:hover { - cursor: pointer; - background: $translucent3_white; - } - - &.disabled { - color: $placeholder_gray; - - &:hover { - cursor: not-allowed; - background: none; - } - } - } - } - } - - .tool-tip { - position: absolute; - left: 0; - bottom: 6.5rem; - right: 0; - margin: auto; - width: fit-content; - color: $off_white; - background: rgba(120, 0, 0, 0.65); - padding: 0.75rem 1rem; - border-radius: 5px; - text-align: center; - pointer-events: none; - backdrop-filter: blur(5px); - box-shadow: $translucent2 0px 0px 5px; - - @media screen and (max-width: 768px) { - margin: 0 1rem; - } - } - - .all-configs { - position: absolute; - top: 1rem; - right: 1rem; - width: 22rem; - background: $dark_gray; - text-align: left; - padding: 1rem; - border-radius: 5px; - max-height: 30rem; - overflow-y: scroll; - - .close { - position: absolute; - top: 0.5rem; - right: 1rem; - margin: 0; - cursor: pointer; - font-size: 1rem; - padding: 0.5rem 0.75rem; - background: none; - border-radius: 5px; - - &:hover { - background: $medium_gray; - } - } - - .spacer { - margin-top: 1rem; - } - - details { - color: $off_white; - - label { - font-weight: bold; - color: $off_white; - text-transform: none; - font-size: 0.9rem; - } - } - - summary { - margin: -1rem; - cursor: pointer; - padding: 1rem; - - &:hover { - background: $dark_gray; - } - } - - .config-row { - display: grid; - grid-template-columns: min-content minmax(30%, 200px) auto; - margin-bottom: 0.25rem; - - span { - width: 8rem; - padding-left: 1rem; - color: $gray; - } - - input[type="checkbox"] { - width: 1.1rem; - height: 1.1rem; - justify-self: left; - cursor: pointer; - box-shadow: none; - } - - input[type="number"] { - width: 100%; - max-width: 4rem; - height: 1rem; - font-size: 0.9rem; - box-shadow: none; - } - - input[type="radio"] { - width: 1.1rem; - height: 1.1rem; - cursor: pointer; - box-shadow: none; - } - - .options { - width: 100%; - justify-content: space-between; - } - - input[type="range"] { - width: 4rem; - margin-left: -2rem; - cursor: pointer; - box-shadow: none; - height: 1rem; - } - } - } - - .promo-info { - display: grid; - position: absolute; - top: 3rem; - right: 4rem; - color: $off_white; - text-align: right; - pointer-events: none; - text-shadow: 0 0 3.5rem $black, 0 0 1rem $black; - gap: 1rem; - justify-items: right; - - .title { - margin: 0; - font-family: 'Inknut Antiqua'; - font-size: 3.5rem; - line-height: 5rem; - font-weight: bold; - } - - .description { - margin: 0; - max-width: 40vw; - - p { - color: $off_white; - font-size: 1.25rem; - line-height: 1.7rem; - font-weight: bold; - } - - .short { - display: none; - } - - .full { - display: inline; - } - } - - .buy-button { - display: flex; - pointer-events: all; - background: #00a579e0; - border-radius: 7px; - box-shadow: $translucent2 0px 0px 10px; - backdrop-filter: blur(5px); - padding: 0.1rem 1.75rem; - text-shadow: none; - text-transform: uppercase; - text-decoration: none; - gap: 0.4rem; - align-items: center; - - p { - margin: 0; - color: $off_white; - font-size: 1.5rem; - line-height: 4rem; - font-weight: bold; - } - - .genesis-xl { - background: linear-gradient(315deg, #22a36d, #4ea3ed); - box-shadow: 0 0 8px rgba(0, 0, 0, 0.25); - color: $off_white; - border-radius: 50%; - height: 2.5rem; - width: 2.5rem; - line-height: 2.5rem; - text-align: center; - font-size: 1.2rem; - vertical-align: middle; - } - - &:hover { - background: #00bb89e5; - } - } - } - - .beacon-info { - width: 34rem; - background: $white; - background: rgba(255, 255, 255, 0.8); - box-shadow: $translucent2 0px 0px 10px; - border-radius: 0.5rem; - padding: 1.5rem; - backdrop-filter: blur(5px); - text-align: left; - user-select: none; - p { - margin-bottom: 0; - margin-top: 1rem; - font-weight: bold; - font-size: 1.25rem; - color: $black; - line-height: 1.6rem; - } - a { - text-decoration: underline !important; - text-underline-offset: 0.1rem; - text-underline-position: from-font; - font-weight: bold; - color: $dark_blue; - } - iframe { - margin-top: 1rem; - width: 100%; - height: auto; - border-radius: 0.35rem; - aspect-ratio: 16 / 9; - } - .header { - display: flex; - justify-content: space-between; - h2 { - margin: 0; - font-family: 'Inknut Antiqua'; - line-height: 100%; - font-weight: bold; - } - .exit-button { - width: 2rem; - height: 2rem; - aspect-ratio: 1 / 1; - border-radius: 50%; - filter: grayscale(100%); - font-size: 0.75rem; - line-height: 2rem; - text-align: center; - background: rgba(255, 255, 255, 0.4); - box-shadow: $translucent2 0px 0px 0.75rem; - &:hover { - cursor: pointer; - background: rgba(255, 255, 255, 0.6); - } - } - } +@keyframes panel-hide-shift-3d { + 0% { + transform: translateX(0); } - - @media screen and (max-width: 768px) { - .beacon-info-wrapper { - display: grid; - position: absolute; - left: 0; - bottom: 0; - right: 0; - align-items: end; - transform: none!important; - * { - transform: none!important; - } - .beacon-info { - width: initial; - border-bottom-left-radius: 0px; - border-bottom-right-radius: 0px; - overflow: scroll; - height: 33vh; - font-size: 100%; - - .header { - h2 { - font-size: 1.3rem; - } - } - - p { - font-size: 1rem; - line-height: 1.2rem; - } - } - } - .promo-info { - top: 1rem; - right: 1rem; - gap: 0.5rem; - - .title { - font-size: 1.5rem; - line-height: 2rem; - } - - .description { - max-width: 80vw; - - p { - font-size: 1rem; - line-height: 1.5rem; - } - - .short { - display: inline; - } - - .full { - display: none; - } - } - - .buy-button { - font-size: 0.85rem; - line-height: 2.2rem; - padding: 0.1rem 0.8rem; - border-radius: 5px; - - p { - font-size: 0.85rem; - line-height: 2rem; - } - - .genesis-xl { - height: 1.5rem; - width: 1.5rem; - line-height: 1.5rem; - font-size: 0.7rem; - } - } - } + 100% { + transform: translateX(-22.5rem); } } diff --git a/frontend/css/farm_designer/farm_designer_panels.scss b/frontend/css/farm_designer/farm_designer_panels.scss index bcfb8cbb6c..62a66c0c04 100644 --- a/frontend/css/farm_designer/farm_designer_panels.scss +++ b/frontend/css/farm_designer/farm_designer_panels.scss @@ -1,258 +1,119 @@ -.panel-nav, +@use "../variables" as *; +@use "sass:color"; + .farm-designer-panels { position: fixed; - top: 8.9rem; + top: 9rem; width: 45rem; - box-shadow: 0 0 10px $translucent; + margin: 1.25rem; + margin-top: 0; + overflow-y: scroll; + border-radius: 1rem; @media screen and (max-width: $mobile_max_width) { - width: 100%; + top: 15rem; + width: 100vw; + margin: 0; + } + &.short-panel { + max-height: 19rem; } } .panel-nav { + position: fixed; + top: 10rem; + width: 100vw; display: none; + overflow-x: scroll; + z-index: 11; @media screen and (max-width: $mobile_max_width) { display: block; } } -.panel-top { - margin-top: 0; - @media screen and (max-width: $mobile_max_width) { - margin-top: 5rem; +.panel-content { + padding: 1rem; + a { + color: inherit; + text-decoration: underline !important; } } -.controls-panel, -.sensors-panel, -.photos-panel, -.messages-panel, -.help-panel { +.group-detail-panel { .panel-content { - padding-top: 1rem; - max-height: calc(100vh - 3rem); - @media screen and (max-width: $mobile_max_width) { - padding-top: 6rem; - max-height: calc(100vh - 9rem); - } + overflow-y: auto; + overflow-x: hidden; + padding: 1rem; } } -.controls-panel, -.photos-panel { +.point-inventory-panel, +.plant-inventory-panel { .panel-content { - max-height: calc(100vh - 9rem); + overflow-y: auto; + overflow-x: hidden; + padding-bottom: inherit; } } -.controls-panel { +.curve-info-panel, +.weed-info-panel, +.point-info-panel, +.plant-info-panel { .panel-content { - padding-top: 0; - @media screen and (max-width: $mobile_max_width) { - padding-top: 4rem; - } + overflow-y: auto; + overflow-x: hidden; } } -.move { - position: relative; - padding-bottom: 1rem; - padding-top: 1rem; - .move { - .unavailable, - .bot-is-online-wrapper { - display: inline-block; - } - } - .move-settings.bp5-popover-wrapper { - position: absolute; - top: 6rem; - right: 1rem; - .fa-gear { - color: $dark_gray; - } - } - .jog-controls-group { - label { - margin-top: 0; - } - } - .bot-position-rows { - padding-left: 1rem; - padding-right: 1rem; - label { - margin-top: 0; - font-size: 1.2rem; - } - .axis-titles { - margin-bottom: 1rem; - } +.weed-info-panel { + .panel-title { + color: $dark_gray; } } -.plant-inventory-panel, -.groups-panel, -.designer-regimen-list-panel, -.events-panel, -.point-inventory-panel, .weeds-inventory-panel, .zones-inventory-panel, -.tools-panel, -.tool-slots-panel-content, -.designer-farmware-list-panel, -.settings-panel { +.groups-panel { .panel-content { - max-height: calc(100vh - 14rem); - @media screen and (max-width: $mobile_max_width) { - max-height: calc(100vh - 19rem); - } - } -} - -@keyframes panel-pullout { - 0% { - transform: translateY(-100%); - } - 100% { - transform: translateY(0); + overflow-y: auto; + overflow-x: hidden; } } .farm-designer-panels { bottom: 0; z-index: 1; + animation: panel-show 0.3s ease forwards; + pointer-events: none; &.panel-closed-mobile, &.panel-closed { - display: none !important; - } - &.short-panel { - height: 24rem; - .location-info-panel { - .panel-content { - max-height: 19rem; - } - } + animation: panel-hide 0.3s ease forwards; } .panel-container { width: 100%; - height: 100%; + border-radius: 1rem; overflow-x: hidden; font-size: 1.6rem; - padding-bottom: 6rem; - label { - font-size: 1.3rem; - margin-top: 2rem; - } - } - div { - animation: panel-pullout 0s ease; - } -} - -.panel-container { - overflow: hidden; - div[class*="search-item"] { - &:hover, - &.hovered { - transition: background 0.2s ease; - } - } - &.green-panel { - background-color: $panel_light_green; - div[class*="search-item"] { - &:hover, - &.hovered { - background: color.adjust($panel_light_green, $lightness: -10%); - } - } - } - &.cyan-panel { - background-color: $light_cyan; - div[class*="search-item"] { - &:hover, - &.hovered { - background: color.adjust($light_cyan, $lightness: -10%); - } - } - } - &.brown-panel { - background-color: $panel_light_brown; - div[class*="search-item"] { - &:hover, - &.hovered { - background: color.adjust($panel_light_brown, $lightness: -10%); - } - } - } - &.magenta-panel { - background-color: $light_magenta; - div[class*="search-item"] { - &:hover, - &.hovered { - background: color.adjust($light_magenta, $lightness: -10%); - } - } - } - &.light-gray-panel, - &.gray-panel { - background-color: $panel_light_gray; - div[class*="search-item"] { - &:hover, - &.hovered { - background: color.adjust($panel_light_gray, $lightness: -10%); - } - } - } - &.yellow-panel { - background-color: $panel_light_yellow; - div[class*="search-item"] { - &:hover, - &.hovered { - background: color.adjust($panel_light_yellow, $lightness: -10%); - } - } - } - &.blue-panel { - background-color: $panel_light_blue; - div[class*="search-item"] { - &:hover, - &.hovered { - background: color.adjust($panel_light_blue, $lightness: -10%); - } - } - } - &.navy-panel { - background-color: $panel_light_navy; - div[class*="search-item"] { - &:hover, - &.hovered { - background: color.adjust($panel_light_navy, $lightness: -10%); - } - } - } - &.teal-panel { - background-color: $panel_light_teal; + pointer-events: auto; + overflow: hidden; + box-shadow: 0 0 1rem $translucent; + background-color: var(--main-bg); + color: var(--text-color); div[class*="search-item"] { &:hover, &.hovered { - background: color.adjust($panel_light_teal, $lightness: -10%); + background: $translucent2_white; } } - } - &.red-panel { - background-color: $panel_light_red; - div[class*="search-item"] { - &:hover, - &.hovered { - background: color.adjust($panel_light_red, $lightness: -10%); - } + label { + font-size: 1.3rem; } } } .panel-nav, .panel-header { - color: $light_gray; &.green-panel { background-color: $panel_green; } @@ -266,7 +127,7 @@ background-color: $magenta; } &.gray-panel { - background-color: $panel_gray; + background-color: $translucent3; } &.light-gray-panel{ background-color: $panel_medium_light_gray; @@ -286,24 +147,6 @@ &.red-panel { background-color: $panel_red; } - &.blue, - &.yellow, - &.orange, - &.purple, - &.pink, - &.gray { - .right-button, - .back-arrow, - .title { - color: $dark_gray; - } - .right-button, - .back-arrow { - &:hover { - color: $black !important; - } - } - } } .panel-tabs { @@ -331,8 +174,8 @@ line-height: 5rem; height: 5rem; color: $light_gray; - padding-left: 0.1rem; - padding-right: 0.1rem; + padding-left: 0.2rem; + padding-right: 0.2rem; &.active { border-bottom: 3px solid $white; font-weight: bold; @@ -355,7 +198,6 @@ } .panel-title { - height: 50px; overflow: hidden; .back-arrow { float: left; @@ -363,26 +205,12 @@ font-size: 1.8rem; width: 50px; line-height: 50px; - &.black-text{ - color: $medium_gray; - } - &:hover { - &.black-text{ - color: $darker_gray !important; - } - &.white-text{ - color: $white; - } - } } .title { - float: left; font-size: 1.8rem; white-space: nowrap; - width: 60%; overflow: hidden; text-overflow: ellipsis; - line-height: 50px; } .right-button { position: absolute; @@ -407,460 +235,54 @@ } } -.white-text{ - color: $off_white; -} -.black-text{ - color: $black; -} - -.point-inventory-panel, -.plant-inventory-panel { - .panel-content { - overflow-y: auto; - overflow-x: hidden; - padding-bottom: inherit; - } -} - -.plant-selection-panel { - .panel-action-buttons { - position: absolute; - z-index: 9; - height: 16rem; - width: 100%; - background: $panel_medium_light_gray; - padding: 0.5rem; - .selection-type { - display: flex; - label { - line-height: 4rem; - } - .filter-search { - position: absolute; - right: 1rem; - width: 70%; - .bp5-button { - margin-left: 0.5rem; - } - i { - margin-right: -0.5rem; - } - } - } - &.more-select { - height: 20rem; - } - &.more-action { - height: 35rem; - } - &.more-select.more-action { - height: 38rem; - } - .fb-button { - margin: 0.5rem; - float: left; - } - label { - min-width: -webkit-fill-available; - margin-bottom: 0px; - margin-left: .5rem; - margin-top: 0; - } - .button-row { - float: left; - width: 100%; - margin-bottom: 1rem; - &.group-select { - .filter-search { - margin-top: 0.5rem; - } - } - } - .filter-search { - padding-right: 1rem; - } - .plant-curves-bulk-update, - .point-size-bulk-update, - .plant-depth-bulk-update, - .plant-date-bulk-update, - .point-color-bulk-update, - .plant-slug-bulk-update, - .plant-status-bulk-update { - display: inline-block; - width: 100%; - line-height: 3.5rem; +.weed-info-panel-content, +.point-info-panel-content { + font-size: 1.4rem; + .point-color-input { + div[class*=col-] { + padding-left: 0.5rem; } - .plant-curves-bulk-update, - .plant-status-bulk-update { - .filter-search { - display: inline-block; - width: 50% !important; - margin-top: 0.5rem; - } - p { - display: inline-block; - font-size: 1.2rem; - vertical-align: top; - } + .saucer { + margin-top: 4.5rem; } - .plant-depth-bulk-update, - .point-size-bulk-update { - p { - display: inline-block; - font-size: 1.2rem; - vertical-align: middle; - } + } + .fb-button & .red { + display: block; + margin-top: 3rem; + } + 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: 48%; - height: 3rem; - } - } - .plant-date-bulk-update { - .input { - display: inline-block; - width: 48%; - input { - height: 3rem; - padding: 0; - padding-left: 1rem; - padding-right: 1rem; - } - } - } - .point-color-bulk-update { - .color-picker { - display: inline-block; - margin-bottom: -0.5rem; - } - } - .plant-slug-bulk-update { - .fa-pencil { - margin-left: 0.25rem; - } - .fb-button { - float: none; - height: 2rem; - line-height: 0; - margin-left: 1rem; - vertical-align: top; - } - } - .more { - cursor: pointer; - margin-left: 0.5rem; - line-height: 2.5rem; - p { - display: inline; - font-size: 1.4rem; - margin-right: 1rem; + width: 10%; + box-shadow: none; } - } - .more-button { - float: right; - width: max-content; - margin-right: 1rem; - } - .more-content { - margin-top: 0.5rem; - max-height: 20.5rem; - overflow: scroll; - width: 330px; label { - width: 100%; - } - .filter-search { - display: inline-block; - width: 50.75% !important; - line-height: 1rem; - } - p { - display: inline-block; - width: 150px; - line-height: 3.5rem; + margin: 0; + margin-top: auto; + font-size: 1.25rem; + font-weight: normal; } } } - .panel-content { - padding-top: 16rem; - padding-right: 0; - padding-left: 0; - max-height: calc(100vh - 13rem); - overflow-y: auto; - overflow-x: hidden; - &.more-select { - padding-top: 20rem; - } - &.more-action { - padding-top: 35rem; - } - &.more-select.more-action { - padding-top: 38rem; - } - .tool-slot-search-item-icon { - padding-left: 5px !important; - } - } -} - -.group-detail-panel, -.plant-info-panel { - .panel-header-icon-group { - i { - color: $white; - } - } -} - -.curve-info-panel, -.weed-info-panel, -.point-info-panel, -.plant-info-panel { - .panel-title { - input { - margin: 1rem; - width: 75%; - } - } - .panel-content { - max-height: calc(100vh - 14rem); - overflow-y: auto; - overflow-x: hidden; - } -} - -.point-creation-panel { - .panel-content { - max-height: calc(100vh - 17rem); - overflow-y: auto; - overflow-x: hidden; - padding-top: 1rem; - ul { - margin-bottom: 0; - } - label { - margin-top: 0 !important; - } - .plant-info-field-data { - margin-top: 1rem; - } - p { - margin-top: 1rem; - } - .fb-button { - &.save { - margin-right: 1.5rem; - } - &.delete { - float: left; - margin-top: 1rem; - } - } - .point-color-input { - div[class*=col-] { - padding-left: 0.5rem; - } - .saucer { - margin-top: 2.75rem; - } - } - .delete-row { - margin: 1.5rem; - } - hr { - border-color: $medium_gray; - } - .grid-and-row-planting { - label { - color: $dark_gray; - } - input { - box-shadow: 0 0 10px #ddd; - } - .update-button, - .preview-button, - .cancel-button, - .save-button { - text-decoration: none !important; - color: $dark_gray; - &:hover { - color: $black; - background: $off_white; - } - } - } - .blue { - float: none; - } - .use-current-location { - button { - .fa { - font-size: 1.5rem; - } - } - } - } -} - -.weed-info-panel-content, -.point-info-panel-content { - font-size: 1.4rem; - .point-color-input { - div[class*=col-] { - padding-left: 0.5rem; - } - .saucer { - margin-top: 4.5rem; - } - } - .fb-button & .red { - display: block; - margin-top: 3rem; - } - 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; - } - } - } - .go-button-axes-wrapper { - display: inline-block; - margin-top: 1rem; - margin-bottom: 1rem; - margin-left: 1.5rem; - } -} - -.soil-height-checkbox { - margin-top: 1rem; - input { - float: left; - width: 2rem !important; - box-shadow: none !important; - margin-right: 0.5rem; - cursor: pointer; - } - label { - margin-top: 0 !important; - } -} - -.crop-catalog-panel { - .panel-top { - margin-top: 0; - } - .panel-content { - padding: 1rem 1rem 6rem; - padding-bottom: inherit; - } } .panel-top { + padding: 1rem; &.with-button { - display: flex; - .fb-button { - margin: 1rem; - margin-left: 0; - } - a { - margin-top: 0.5rem; - } - i:not(.fa-stack-2x) { - font-size: 1.5rem; - } - } -} - -.panel-content { - padding: 0 1rem; - padding-bottom: inherit; - a { - color: $black; - text-decoration: underline !important; - } -} - -.edit-farm-event-panel, -.add-farm-event-panel { - .panel-content { - max-height: calc(100vh - 14rem); - overflow-y: auto; - overflow-x: hidden; - } - input { - background: $white; - } - .save-btn { - margin: 1rem; - } - .location-form { - width: 100% !important; - } - .bp5-popover-wrapper { - display: inline-block; - margin-left: 0.5rem; - &.input-error-wrapper { - display: block; - } - } -} - -.farm-event-form { - .farm-event-repeat-options { - input[type=checkbox] { - margin-right: 0.5rem; - margin-top: 0; - vertical-align: middle; - } - .farm-event-repeat-form { - .add-event-repeat-frequency { - min-height: 34px; - } - } - } - .locals-list { - margin-top: 1rem; - margin-left: 1rem; - margin-right: 1rem; - } - .save-btn { - position: absolute; - top: 0.5rem; - right: 0; + display: grid; + grid-template-columns: 1fr auto; + gap: 1rem; + align-items: center; } } -.add-farm-event-panel, -.edit-farm-event-panel { - .fa-trash { - position: absolute; - top: 1.25rem; - right: 8rem; - color: $white; - } -} - -.panel-nav { - position: fixed; - z-index: 11; -} - .scroll-indicator { position: absolute; top: 0; @@ -880,7 +302,7 @@ height: 8rem; overflow-y: auto; overflow-x: hidden; - width: 75%; + width: calc(100% - 75px); font-family: "Cabin", sans-serif; } .saving-indicator { @@ -895,6 +317,7 @@ } .crop-drag-info-tile { position: absolute; + top: 0; right: 0; margin: 1rem; border: 2px solid $white; @@ -914,12 +337,6 @@ } .grid-input { - .row { - margin-top: 0.5rem; - .grid-axis-label { - font-weight: bold; - } - } .use-current-location { display: inline; float: none; @@ -935,177 +352,16 @@ } } -.crop-info-panel { - .panel-header { - position: inherit; - background-size: 144% !important; - background-repeat: no-repeat !important; - background-position: top center !important; - min-height: 125px; - .panel-title { - overflow: visible; - .title { - font-family: "Inknut Antiqua"; - font-weight: bold; - } - .bp5-popover-wrapper { - position: absolute; - right: 0; - } - .transparent-button { - position: absolute; - bottom: -9.5rem; - right: 1.5rem; - margin: 0; - color: $white; - white-space: nowrap; - } - .fa-th-large, - .fa-plus, - .fa-arrows { - position: absolute; - background: $white; - color: $dark_gray; - border-radius: 50%; - height: 1.5rem; - width: 1.5rem; - line-height: 1.5rem; - text-align: center; - font-size: 1rem; - padding-left: 0.15rem; - } - .fa-th-large { - top: 6rem; - right: 1.25rem; - } - .fa-plus { - top: 3.8rem; - right: 0.25rem; - } - .fa-arrows { - top: 1.5rem; - right: 1.25rem; - } - } - } - .panel-content { - max-height: calc(100vh - 19rem); - overflow-y: auto; - overflow-x: hidden; - padding: 2rem 1rem 6rem; - padding-bottom: 12rem; - li { - margin-bottom: 1rem; - p { - font-size: 1.25rem; - } - } - .grid-and-row-planting { - padding-bottom: 2rem; - margin-left: -1rem; - margin-right: -1rem; - padding-left: 1rem; - padding-right: 1rem; - hr { - border-color: $medium_gray; - } - label { - color: $dark_gray; - } - .update-button, - .preview-button, - .cancel-button, - .save-button { - text-decoration: none !important; - color: $dark_gray; - &:hover { - color: $black; - background: $off_white; - } - } - .use-current-location { - margin-top: 1.5rem; - } - } - .companion { - display: inline-block; - text-decoration: none !important; - background: $light_gray; - border-radius: 2rem; - margin-right: 1rem; - margin-bottom: 0.5rem; - padding: 0.15rem; - padding-left: 0.5rem; - &:hover { - background: $gray; - } - p { - display: inline; - margin-left: 0.25rem; - margin-right: 1rem; - font-weight: bold; - text-transform: none; - } - } - } - .edit-on-openfarm { - margin-bottom: 1rem; - font-size: 1.3rem; - } - .object-list { - li { - position: relative; - margin-left: 2rem; - } - li { - p, - div { - display: inline; - font-size: 1.3rem; - color: $dark_gray; - } - p { - margin-right: 0.25rem; - text-transform: uppercase; - font-weight: bold; - } - span { - position: absolute; - left: -2rem; - } - } - } - .plant-stage-selection, - .planted-at-selection { - display: flex; - label { - margin-right: 0.5rem; - line-height: 1rem; - white-space: nowrap; - } - .filter-search, - .input { - width: 100%; - } - } -} - .weeds-inventory-panel-content, .plant-panel-content, .points-panel-content { - padding: 0; - .empty-state { - margin-bottom: 4rem; - } + padding: 0!important; .non-empty-state { margin-bottom: 1rem; + gap: 0; } } -.edit-point-location { - margin-top: 1rem; -} - .point-name-input { label { margin-top: 2rem !important; @@ -1120,146 +376,62 @@ li { .additional-weed-properties { li { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + align-items: center; margin-top: 1rem; } } -.plants-panel-content { - li { - margin-top: 1rem; - p { - margin-bottom: 0.5rem !important; - } - } - .plant-info-field-data { - .bp5-popover-wrapper { - display: inline-block; - margin-left: 0.5rem; - } - .filter-search { - .bp5-popover-wrapper { - margin-left: 0; - } - } - } - input { - background: $white; - } - .fa-pencil { - margin-left: 0.5rem; - } -} - .panel-section { .delete { height: 2rem; } - .garden-indicator { - display: inline; - margin-left: 2rem; - font-size: 1rem; - } } .section-header { - display: inline-block; + display: grid; + grid-template-columns: 1fr auto; + align-items: center; height: 4rem; - width: 100%; - line-height: 3.75rem; + padding: 0 1rem; cursor: pointer; - padding-left: 1rem; &:hover { - background: $light_gray; + background: $translucent2_white; } label { - margin: 0 !important; cursor: pointer; } .fb-button { - line-height: 1rem; - margin-top: 0.5rem; - margin-right: 1rem; - padding: 0.25rem 0.75rem; - i { - margin: 0 !important; - font-size: 1.5rem; - } + line-height: 1.2rem; } .fa-caret-up, .fa-caret-down { - float: right; - line-height: 0; font-size: 2rem; - margin-right: 2rem; - margin-top: 2rem !important; - color: $dark_gray; - } -} - -.add-plant-panel, -.location-info-panel { - padding-bottom: 0 !important; -} - -.add-plant-panel { - .panel-header { - height: 100%; } } .grid-and-row-planting { + display: grid; + gap: 1rem; h3 { width: 100%; text-align: center; + margin: 0; } - .row { - margin-left: 0; - margin-right: 0; - div[class*=col-] { - padding-left: 0; - padding-right: 1rem; - } - } - i { + .fa-arrows-h, .fa-arrows-v { margin-left: 0.5rem; - margin-right: 0.5rem; - } - label { - color: $off_white; - margin-top: 0.75rem; } .use-current-location { - padding: 0; - margin-left: 1rem; + display: flex; + align-items: center; button { .fa { font-size: 1.5rem; } } } - .grid-input { - i[class*=fa-arrow] { - margin-right: 0.35rem; - cursor: default !important; - } - input { - box-shadow: none; - &.numPlantsH, - &.numPlantsV { - -moz-appearance: unset !important; - } - } - } - .grid-planting-toggle { - margin: 0.5rem; - margin-left: 0; - label { - margin-top: 0.5rem; - } - button { - float: right; - } - } .fb-toggle-button { float: none; margin-left: 1rem; @@ -1268,2761 +440,331 @@ li { .preview-button, .cancel-button, .save-button { - text-transform: uppercase; - font-size: 1rem; - border: 1px solid; - padding: 0.4rem 1.2rem; - font-weight: bold; - letter-spacing: 1px; - border-radius: 4px; - color: $off_white; - margin-top: 1.25rem; - margin-right: 1.5rem; - &:hover { color: $white; } + text-decoration: none !important; } } -.planted-at-selection, -.plant-stage-selection { - label { - margin-top: 1rem !important; - } - .filter-search { - position: relative; +.move-to-form { + display: grid; + gap: 1rem; + .bp5-popover-wrapper { + display: inline; + margin-left: 0.5rem; } } -.plant-stage-selection { - margin-bottom: 1rem; +.no-pad { + padding: 0; } -.move-to-form { - .bp5-popover-wrapper { - display: inline; - margin-left: 0.5rem; +.low-pad { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.factory-reset-options { + a { + color: $white; + text-decoration: none !important; } - .speed { - margin: 2rem; - margin-left: -15px; + .fa-external-link { + margin-left: 0.5rem; } } -.go-button-axes-wrapper { - margin-top: 1rem; - button { - margin: 0; - float: none; - } - .go-button-axes { - display: inline; +.weed-item-icon, +.group-item-icon { + display: inline-block; + position: relative; + .weed-icon { + position: absolute; + top: 11%; + left: 14%; + width: 70%; + height: 70%; } - .go-button-axes-text { - border-top-right-radius: 0; - border-bottom-right-radius: 0; + &.more-indicator { + height: 20px; + width: 20px; + cursor: pointer; p { - display: inline-block; - position: relative; - z-index: 1; - margin: 0 !important; - font-weight: bold; - color: $off_white; - height: 0; - line-height: 0; - font-size: 1.1rem; - } - } - .go-button-axes-dropdown { - border-left: 1px solid $dark_gray; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - &.pseudo-disabled { - border-left: 1px solid $gray; + display: inline; + text-align: center; } } } -.go-button-axes-popover { - .bp5-popover-arrow { - &::before { - position: relative; - background: $dark_gray; - } - } - .bp5-popover-content { - color: $off_white; - background: $dark_gray; - width: 200px; - .go-axes { - display: grid; - gap: 0.75rem; - button { - margin: 0; - padding: 0.5rem 1rem; - float: none; - &.x, - &.y, - &.z { - grid-row: 1; - grid-column: span 4; +.panel-header-icon-group { + margin-left: 1rem; + margin-right: 1.5rem; +} + +.group-detail-panel { + .panel-content { + .group-criteria { + .basic, .plant-criteria-options { + display: grid; + gap: 1rem; + } + .plant-stage-criteria, + .plant-type-criteria, + .radius-criteria, + .day-criteria, + .location-criteria, + .removal-method-criteria, + .color-criteria, + .point-source-criteria { + background: var(--secondary-bg); + border-radius: 0.5rem; + padding: 1rem; + display: grid; + gap: 1rem; + } + .criteria-heading { + margin-top: 0; + } + .alpha-icon { + display: inline; + float: none !important; + margin-left: 1rem; + color: $orange; + font-size: 1.4rem; + } + p { + &.category { + font-size: 1.2rem; + font-weight: bold; + } + } + .bp5-popover-wrapper { + float: right; + } + .criteria-checkbox-list { + display: flex; + gap: 1rem; + flex-wrap: wrap; + } + .point-type-section, + .criteria-checkbox-list-item { + height: 3rem; + border-radius: 3rem; + display: flex; + align-items: center; + padding: 0 1rem; + gap: 1rem; + background: $translucent2_white; + .fb-checkbox { + display: contents; + } + p { + text-transform: uppercase; } - &.xy, - &.xyz { - grid-row: 2; - grid-column: span 6; + input[type="text"] { + width: 50%; + height: 2rem; } } - a, - p { - display: inline; - color: $off_white; - font-size: 1.2rem; - text-decoration: none !important; - font-weight: normal; + .point-type-checkboxes { + .point-type-section { + .point-type-checkbox { + position: relative; + height: 2rem; + margin-top: 0.75rem; + cursor: pointer; + .fb-checkbox { + display: inline-block; + height: 2rem; + } + } + .plant-criteria-options, + .weed-criteria-options, + .point-criteria-options, + .tool-criteria-options { + .lt-gt-criteria { + margin-bottom: 1rem; + .row { + margin-left: 0 !important; + } + } + } + } } - a { - grid-column: span 12; - i { - margin-left: 0.5rem; + .criteria-radio-presets { + input[type="radio"] { + width: auto; + margin-right: 1rem; } - &:hover { - text-decoration: underline !important; + p { + display: inline; + text-transform: uppercase; } } - input { - float: right; - width: auto; - box-shadow: none; - cursor: pointer; + .criteria-string, + .criteria-pointer-type, + .criteria-plant-status, + .criteria-slug { + margin-top: 1rem; } - .save-as-default-wrapper { - display: inline-block; - grid-column: span 12; - width: 100%; + .number-eq-criteria, + .string-eq-criteria { + code { + display: inline-block; + font-size: 1.2rem; + font-weight: bold; + color: $black; + } + } + .number-eq-criteria, + .number-gt-lt-criteria { + p { + text-align: center; + font-size: 1.2rem; + } + } + .advanced { + .bp5-popover-wrapper { + display: inline-block; + float: none; + margin-left: 1rem; + font-size: 1.4rem; + } + .filter-search { + .bp5-popover-wrapper { + margin-left: 0; + } + } } } } } -.location-info-panel { - .panel-content { - max-height: calc(100vh - 14rem); - overflow-y: auto; - overflow-x: hidden; - padding-top: 1rem; - .location-actions { - p { - margin-top: 1rem; - } - } - h1 { - text-align: center; - font-size: 2rem; - margin-top: 4rem; - margin-bottom: 0; - } - .expandable-header { - text-transform: uppercase; - font-weight: bold; +.group-member-count-breakdown { + margin-bottom: 1rem; + .manual-group-member-count, + .criteria-group-member-count { + margin-bottom: 0.5rem; + div { + display: inline; + padding: 0.25rem; font-size: 1.2rem; } - label { - margin-top: 0; - &.no-items { - display: block; - } - } - button { - float: none; - } - .add-point { - margin-top: 2rem; + p { + display: inline; + margin-left: 0.5rem; } - .point-search-item, - .plant-search-item{ - margin-left: -15px; - margin-right: -15px; - padding-left: 2rem; - padding-right: 2rem; + .fb-button { + margin: 0; } - .sensor-history-table { - margin-left: -15px; - margin-right: -15px; - .table-row { - height: 4rem; - td:first-of-type { - padding-left: 2rem; - } - } + } +} +.lt-gt-criteria, +.location-criteria { + .row { + p { + text-transform: uppercase; + font-size: 1.1rem; } - .interpolated-soil-height { - .title { - display: inline; - font-weight: bold; - } - p { - display: inline; - margin-left: 1rem; - } - } - .photos-footer { - margin-top: 1rem; - .bp5-popover-wrapper { - margin-top: 3px; - } - } - hr { - margin: 1rem; - margin-bottom: 0.5rem; + } + .location-selection-warning { + i, + p { + display: inline; + margin-right: 1rem; + color: $darkest_red; } } } -.tool-slots-panel, -.tools-panel { - .panel-top { - display: flex; - } - .tool-slots-panel-content, - .tools-panel-content { - overflow-y: auto; - overflow-x: hidden; - .tool-search-item, - .tool-slot-search-item { - margin-left: -15px; - margin-right: -15px; - width: calc(100% + 25px); - padding-right: 3.5rem; - .filter-search { - .bp5-button { - min-height: 2.5rem; - max-height: 2.5rem; - span { - line-height: 1.5rem; - } - } - i { - top: 0.75rem; - } - } - p { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - } - .mounted-tool { - .filter-search { - width: 50%; - float: right; - } - .utm-and-mounted-tool-graphic { - width: 30%; - margin-left: 32%; - } - } - .mounted-tool-header { - display: inline; - margin-top: 1rem; - label { - margin: 0; - } - .bp5-popover-wrapper { - display: inline; - } - .help-icon { - margin-left: 1rem; - vertical-align: top; - font-size: 1.4rem; - } - } - .tools-header, - .tool-slots-header { - display: flex; - margin-top: 1rem; - margin-bottom: 1rem; - label { - margin: 0; - line-height: 2.1rem; - } - a { - margin-left: auto; - } - .fa-plus { - font-size: 1.5rem; - } - } - button:not(.bp5-button) { - display: block; - margin-left: auto; - float: none; - margin-top: 1rem; - } - .tool-verification-status { - display: flex; - margin-top: 1rem; - margin-bottom: 2rem; - button { - margin-top: 0; - } - } - .panel-section { - margin-left: -10px; - margin-right: -10px; - } - } -} - -.add-tool-slot-panel, -.edit-tool-slot-panel, -.add-tool-panel, -.edit-tool-panel { - .tool-action-btn-group { - margin: 1.5rem; - float: right; - .fb-icon-button, - .save-btn { - float: right; - color: $white; - margin-left: 1rem; - } - } -} - -.add-tool-panel-content, -.edit-tool-panel-content { - max-height: calc(100vh - 14rem); - overflow-y: auto; - overflow-x: hidden; - button { - display: block; - margin-left: auto; - float: none; - margin-top: 1rem; - &.red { - float: left; - margin-bottom: 1rem; - } - } - svg { - display: block; - margin: auto; - width: 10rem; - 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; - } - details { - padding: 2rem; - .graphics-input { - input { - width: 97%; - } - } - } - } - .add-stock-tools { - .filter-search { - margin-bottom: 1rem; - button { - margin-top: 0.2rem; - } - } - ul { - font-size: 1.2rem; - padding-left: 1rem; - li { - margin-top: 0.5rem; - line-height: 2rem; - cursor: pointer; - width: 50%; - &:hover { - font-weight: bold; - } - .fb-checkbox { - display: inline; - } - p { - display: inline; - line-height: 2.25rem; - font-size: 1.2rem; - vertical-align: top; - margin-left: 1rem; - } - } - } - button { - margin-bottom: 2rem; - .fa-plus { - margin-right: 0.5rem; - } - } - } -} - -.flow-rate-input { - .bp5-popover-wrapper { - display: inline; - margin-left: 0.5rem; - } - .fb-button { - float: right; - margin-top: 1.5rem; - } -} - -.edit-tool-slot-panel { - .save-error { - position: absolute; - top: 1.5rem; - right: 1rem; - color: $darkest_red; - } -} - -.add-tool-slot-panel-content, -.edit-tool-slot-panel-content { - max-height: calc(100vh - 14rem); - overflow-y: auto; - overflow-x: hidden; - svg { - display: block; - margin: auto; - width: 10rem; - height: 10rem; - margin-top: 2rem; - } - label { - margin-top: 0 !important; - } - .row, fieldset { - margin-top: 2rem; - } - fieldset button { - margin: 0; - } - .direction-icon { - margin-left: 1rem; - } - .help-icon { - color: $dark_gray; - } - .tool-slot-location-input { - .axis-inputs { - padding-left: 0; - } - .use-current-location { - padding: 0; - margin-left: -1rem; - } - button.blue { - margin-top: 0.5rem; - margin-right: 0.5rem; - height: 2.5rem; - .fa { - font-size: 1.5rem; - } - } - button.gray { - margin-top: 1rem; - } - } - .tool-direction-input, - .gantry-mounted-input { - label { - margin-top: 0; - } - input[type="checkbox"] { - float: left; - margin-right: 1rem; - } - } - .fb-button.green { - margin-top: 1rem; - } - .tool-direction-input { - margin-top: 0; - } -} - -.tool-svg { - display: flex; - div { - margin: auto; - text-align: center; - p { - font-style: italic; - } - } -} - -.no-pad { - padding: 0; -} - -.low-pad { - padding-left: 0.5rem; - padding-right: 0.5rem; - &.z-param-input { - padding-left: 4.25rem; - } -} - -.plant-inventory-panel, -.settings-panel { - .panel-top { - .fa-chevron-right, - .fa-chevron-down { - font-size: 1.25rem !important; - padding: 0.1rem; - } - .fa-gear { - color: $dark_gray; - margin-right: 1rem; - margin-top: 1.5rem; - } - } -} - -.settings-panel-settings-menu { - button { - margin-left: 1rem; - } -} - -.plants-panel-settings-menu { - label { - line-height: 3rem; - vertical-align: bottom; - margin-bottom: 0; - } - .bp5-popover-wrapper { - display: inline; - margin-left: 0.5rem; - line-height: 3rem; - } -} - -.settings-panel-content { - max-height: calc(100vh - 22rem); - overflow-y: auto; - overflow-x: hidden; - padding-bottom: inherit; - .expandable-header { - margin-top: 1.5rem; - margin-bottom: 0; - } - .section { - position: relative; - margin-bottom: 0; - .hw-warn { - position: absolute; - top: -0.35rem; - right: 2rem; - i { - font-size: 1.4rem; - } - } - } - .section { - margin-bottom: 2rem; - } - .row:first-child { - margin-right: 0; - margin-top: 1rem; - } - .row:nth-child(2) { - padding-left: 1.5rem; - padding-right: 3rem; - } - .row.zero-side-margins { - margin-left: 0; - margin-right: 0; - } - .export-data, - .change-password { - .old-password, - .new-password { - margin-top: 1.5rem; - input { - width: 50%; - float: right; - } - } - form { - margin-left: 2rem; - } - } - .label-headings { - label { - line-height: 1rem; - } - } - .release-notes-wrapper { - margin-top: 0.75rem; - float: right !important; - } - .settings-warning-banner { - margin-bottom: 1rem !important; - p { - display: inline; - } - } - .credentials-change-warning-banner, - .settings-warning-banner, - .stall-detection-note, - .limit-switch-warning { - margin: 1rem -15px -1rem -15px; - padding: 1rem 1.5rem 1rem 2rem; - background: color.adjust($orange, $alpha: -0.6); - p { - line-height: 1.75rem; - font-size: 1.3rem; - font-weight: bold; - } - } - .pin-reporting-input-row, - .pin-guard-input-row { - .row { - margin-left: -15px; - margin-right: -15px; - padding-left: 0; - padding-right: 1rem; - margin-bottom: 1rem; - } - button { - float: none; - } - } - .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; - } - } - } - .show-advanced-toggle, - .highlight-modified-toggle { - margin-right: 4.5rem; - margin-top: 1rem; - } - .fb-button { - margin-top: 0.5rem; - } - label { - margin: auto 0 !important; - line-height: 1; - font-size: 1.2rem !important; - } - .bp5-popover-wrapper { - display: inline-block; - margin: auto; - float: none; - } - .help-icon { - margin-left: 0.5rem; - } - .designer-setting { - &.disabled { - input { - background: $gray; - } - button { - background: $medium_light_gray !important; - } - } - } -} - -.non-layer-config-toggle { - margin-top: 0.1rem; - &.disabled { - input { - background: $gray !important; - } - button { - background: $medium_light_gray !important; - &:hover { - background: $medium_light_gray !important; - } - } - } -} - -.map-size-inputs { - .row { - margin: 0; - padding: 0; - margin-top: 1rem; - margin-bottom: 1rem; - } - label { - margin-top: 0.5rem; - } -} - -.factory-reset-options { - a { - color: $white; - font-weight: bold; - text-decoration: none !important; - } - .fa-external-link { - margin-left: 0.5rem; - } -} - -.saved-garden-panel-content { - padding: 0; - padding-bottom: inherit; - .non-empty-state { - max-height: calc(100vh - 14rem); - overflow-y: auto; - overflow-x: hidden; - @media screen and (max-width: $mobile_max_width) { - max-height: calc(100vh - 19rem); - } - } - .row { - margin: 0; - margin-top: 1rem; - margin-left: 1rem; - margin-right: 1rem; - } - hr { - width: 100%; - padding-top: 1rem; - } - button { - margin-left: 0.5rem; - &.wide { - margin: 1rem; - margin-left: 2rem; - width: 88%; - float: left; - } - } - input { - min-width: 7rem; - } -} - -.weed-item-icon, -.group-item-icon { - display: inline-block; - position: relative; - .weed-icon { - position: absolute; - top: 13%; - left: 12%; - width: 70%; - height: 70%; - } - &.more-indicator { - height: 20px; - width: 20px; - cursor: pointer; - p { - display: inline; - text-align: center; - } - } -} - -.weeds-inventory-panel, -.zones-inventory-panel, -.groups-panel { - .panel-content { - overflow-y: auto; - overflow-x: hidden; - } -} - -.panel-header-icon-group { - display: flex; - float: right; - padding: 1.5rem; -} - -.group-detail-panel { - .panel-content { - max-height: calc(100vh - 14rem); - overflow-y: auto; - overflow-x: hidden; - .clear-day-criteria, - .clear-point-ids, - .clear-criteria { - margin-top: 0.2rem; - } - .group-member-display { - i[class*=fa-caret-] { - float: right; - font-size: 2rem; - } - .point-list-wrapper { - padding: 0.5em 0em; - } - } - .group-name-input, - .group-member-display, - .group-sort-section { - margin-top: 1rem; - label { - margin-top: 0; - } - .bp5-popover-wrapper { - display: inline; - margin-left: 1rem; - } - } - .group-delete-btn { - float: left; - margin-top: 1em; - } - .group-criteria { - margin-top: 1rem; - .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; - } - } - .bp5-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 { - .point-type-checkbox { - position: relative; - height: 2rem; - margin-top: 0.75rem; - cursor: pointer; - .fb-checkbox { - display: inline-block; - height: 2rem; - } - i[class*=fa-caret-] { - position: absolute; - right: -0.5rem; - width: 3rem; - font-size: 2rem; - padding-left: 1rem; - } - } - .plant-criteria-options, - .weed-criteria-options, - .point-criteria-options, - .tool-criteria-options { - hr { - margin: 0.5rem; - } - .lt-gt-criteria { - margin-bottom: 1rem; - .row { - margin-left: 0 !important; - } - } - } - } - } - .criteria-radio-presets { - input[type="radio"] { - width: auto; - margin-right: 1rem; - } - p { - display: inline; - text-transform: uppercase; - } - } - .criteria-string, - .criteria-pointer-type, - .criteria-plant-status, - .criteria-slug { - margin-top: 1rem; - } - .day-criteria { - .criteria-checkbox-list-item { - margin-bottom: 1rem; - p { - vertical-align: middle; - } - } - .days-old-text { - display: inline; - vertical-align: bottom; - } - input { - line-height: 1.75rem; - } - } - .number-eq-criteria, - .string-eq-criteria { - margin-top: 1rem; - .row { - margin-top: 1rem; - } - code { - display: inline-block; - margin-top: 2rem; - font-size: 1.2rem; - font-weight: bold; - color: $black; - background: none; - } - } - .number-eq-criteria, - .number-gt-lt-criteria { - margin-top: 1rem; - .row { - margin-top: 1rem; - } - p { - text-align: center; - line-height: 2.75rem; - font-size: 1.2rem; - } - } - .basic, - .advanced { - margin-left: 1rem; - .filter-search { - height: 3rem; - margin-bottom: 1rem; - } - .day-criteria { - .row { - margin-left: 0; - } - div[class*=col-] { - padding: 0; - padding-right: 0.75rem; - } - } - } - .advanced { - .bp5-popover-wrapper { - display: inline-block; - float: none; - margin-left: 1rem; - font-size: 1.4rem; - } - .filter-search { - .bp5-popover-wrapper { - margin-left: 0; - } - } - .row { - margin-left: 0; - } - div[class*=col-] { - padding: 0; - } - .col-xs-9 { - margin-right: 0.5rem; - } - .col-xs-1 { - margin-left: 0.25rem; - margin-right: 0.25rem; - margin-top: 0.4rem; - text-align: center; - } - } - } - } -} - -.group-member-count-breakdown { - margin-bottom: 1rem; - .manual-group-member-count, - .criteria-group-member-count { - margin-bottom: 0.5rem; - div { - display: inline; - padding: 0.25rem; - font-size: 1.2rem; - } - p { - display: inline; - margin-left: 0.5rem; - } - .fb-button { - margin: 0; - } - } -} - -.criteria-options-menu { - label { - margin-right: 1rem; - } -} - -.lt-gt-criteria, -.location-criteria { - display: inline-block; - position: relative; - .row { - margin-left: 0; - margin-right: -2.5rem; - margin-top: 1rem; - div[class*=col-] { - padding: 0; - text-align: center; - } - p { - display: block !important; - text-transform: uppercase; - font-size: 1.1rem; - margin-top: 0.75rem; - } - label { - margin-top: 0.5rem; - } - } - button { - margin-top: 2rem !important; - } - .edit-in-map { - position: absolute; - top: 0; - right: 0; - 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, -.zones-inventory-panel, -.point-inventory-panel, -.groups-panel { - .panel-content { - padding: 0; - padding-bottom: inherit; - .points-section-header { - cursor: pointer; - height: 4rem; - line-height: 2.75rem; - label { - cursor: pointer; - } - .saucer { - display: inline-block; - height: 3rem; - width: 3rem; - vertical-align: middle; - margin-right: 0.25rem; - margin-left: 1rem; - } - &:hover { - background: $light_gray; - } - i { - margin-top: 0.5rem; - } - } - .section-header { - padding-top: 0.5rem; - padding-bottom: 0.5rem; - line-height: 2.75rem; - .fb-button { - margin-top: 0.3rem; - margin-right: 0.75rem; - } - .fa-caret-down, - .fa-caret-up { - margin-top: 1.5rem !important; - } - } - .points-section-header, - .pending-weeds-header, - .active-weeds-header, - .removed-weeds-header { - padding-top: 0.5rem; - padding-bottom: 0.5rem; - label { - margin: 0; - width: 44%; - margin-left: 1rem; - line-height: 2.1rem; - } - .fb-toggle-button { - margin-right: 2rem; - margin-top: 0.6rem; - float: right; - line-height: 0; - } - } - .section-action-btn-group { - float: right; - margin-top: 0.3rem; - line-height: 1.5rem; - .delete { - line-height: 1.5rem; - } - } - .points-section { - &.open { - background: rgba(0, 0, 0, 0.05); - margin-bottom: 1rem; - } - .row { - margin-left: -5px; - margin-right: 0; - label { - line-height: 3.5rem; - margin-top: 0; - margin-bottom: 0; - } - .input { - height: 3rem; - } - button { - margin-top: 0.85rem; - float: right; - } - .bp5-popover-wrapper { - display: none; - } - } - } - .points-section, - .panel-section { - .delete { - float: right; - line-height: revert; - } - } - .no-points, - .no-weeds { - margin-left: 2rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - } - .fa-caret-down, - .fa-caret-up { - float: right; - font-size: 2rem; - margin-right: 2rem; - margin-top: 0; - color: $dark_gray; - } - i { - margin: 0.25rem; - } - button { - margin: 0.5rem; - float: none; - } - .approval-buttons { - display: inline; - button { - height: 2.5rem; - line-height: 0 !important; - } - i { - padding-right: 0.5rem; - } - } - .active-weeds { - .empty-state-graphic { - margin-top: 1rem; - } - } - } -} - -.plant-inventory-panel { - .section-header { - .fb-button { - margin-top: 0.75rem; - } - } -} - -.weeds-inventory-panel { - .active-weeds-header { - .fb-button { - line-height: 1rem; - margin-top: 0.5rem; - margin-right: 1rem; - i { - margin: 0 !important; - font-size: 1.5rem; - } - } - } -} - -.curve-svg-wrapper { - .bp5-popover-target { - width: 100%; - } -} - -.warning-line-text-popover { - border-radius: 5px; - .bp5-popover-content { - background: $dark_gray; - border-radius: 5px; - } - .bp5-popover-arrow { - display: none; - } - p { - color: $white; - font-size: 1.3rem; - margin-bottom: 0.5rem !important; - } - .warning-text { - max-width: 200px; - .top { - font-weight: bold; - } - } -} - -.curve-action-popover { - .bp5-popover-content { - background: $dark_gray; - } - .bp5-popover-arrow-fill { - fill: $dark_gray; - } - .curve-action-menu { - .transparent-button { - position: absolute; - right: 0; - width: 50%; - color: $off_white - } - label { - width: 100%; - color: $off_white; - margin-top: 0.75rem; - margin-right: 1rem; - } - .filter-search { - width: 100%; - .bp5-popover-wrapper { - width: 120px; - } - button { - box-shadow: none !important; - } - } - input { - box-shadow: none; - width: 120px; - } - .curve-menu-row { - display: flex; - position: relative; - height: 3rem; - margin-top: 0.5rem; - &.last { - height: 2rem; - } - } - } -} - -.curve-svg { - .data-labels, - .y-axis-line, - .warning-line { - pointer-events: none; - } -} - -.curve-info-panel { - .panel-title { - .white-text { - color: $dark_gray; - &:hover { - color: $dark_gray; - } - } - } - .panel-header-icon-group { - i { - color: $dark_gray; - } - } -} - -.curve-info-panel-content-wrapper { - .bp5-popover-wrapper { - float: right; - margin-left: 1rem; - margin-top: 1rem; - } - .full-indicator { - height: 2rem; - text-align: center; - color: $darkest_red; - } - .input-error-wrapper { - position: absolute; - margin: 0; - } - table { - th { - text-transform: uppercase; - color: $dark_gray; - font-size: 1.3rem; - } - td, - th { - background: $lighter_gray; - border: 1px solid $light_gray; - padding-left: 1rem; - p { - display: inline; - font-size: 1.4rem; - color: $dark_gray; - } - &.active { - background: $white; - } - &.active-input { - padding: 0; - } - .percent-green { - color: $green; - } - .percent-red { - color: $red; - } - input { - font-size: 1.4rem; - padding-left: 1rem; - } - } - tr { - &.hovered { - border: 2px solid $gray; - border-top-width: 0; - border-bottom-width: 0; - } - } - } - .row-radio { - width: 1.5rem; - height: 1.5rem; - border-radius: 50%; - vertical-align: middle; - margin-left: 1rem; - margin-top: -0.25rem; - &.active { - &.full { - background: revert; - border: revert; - cursor: pointer; - } - } - &.full { - background: $light_gray; - border: $gray; - cursor: not-allowed; - } - } -} - -.curves-inventory-panel-content { - padding: 0; - .curve-search-item { - height: 4rem; - line-height: 4rem; - cursor: pointer; - .curve-search-item-info { - text-align: right; - font-size: 1rem; - padding-right: 1rem; - line-height: 4rem; - float: right; - } - .curve-search-item-name { - width: 40%; - margin-left: 1.25rem; - } - } - .section-header { - .fb-button { - margin-top: 0.75rem; - } - .fa-caret-up, - .fa-caret-down { - line-height: 3.75rem; - margin-top: 0 !important; - } - } -} - -.crop-curve-info { - .bp5-collapse { - padding-top: 0.5rem; - } - p { - line-height: 4rem; - font-size: 1.3rem; - } - label { - margin-top: 0 !important; - } - .fa-external-link { - position: absolute; - top: 1rem; - right: 0; - } - .active-curve-name { - position: relative; - height: 4rem; - line-height: 3rem; - p, - .filter-search { - display: inline-block; - position: absolute; - right: 3rem; - width: 75%; - margin-top: 0.5rem; - } - p { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - } - label { - line-height: 4rem; - } - } - .curve-svg-wrapper { - background: $white; - box-shadow: 0 0 10px $light_gray; - border-radius: 5px; - } -} - -.curve-info-panel-content-wrapper, -.all-curve-info { - svg { - text { - user-select: none; - } - } -} - -.curve-icon { - margin-left: 1rem; - vertical-align: middle; -} - -.curve-usage-display { - margin-bottom: 2rem; - i[class*=fa-caret-] { - float: right; - font-size: 2rem; - } - label { - margin-top: 0 !important; - } - .bp5-popover-wrapper { - display: inline; - margin-left: 1rem; - } - img { - cursor: pointer; - } -} - -.sensors-panel, -.controls-panel { - .panel-content { - overflow-y: auto; - overflow-x: hidden; - .widget-wrapper { - box-shadow: none; - .widget-header { - background: none; - h5 { - color: $dark_gray; - } - } - .widget-body { - background: none; - border: none; - } - .widget-footer { - background: none; - * { - color: $dark_gray; - } - } - } - .title-help-icon:hover { - color: $dark_gray !important; - } - .title-help-text.open { - color: $dark_gray !important; - font-size: 1.2rem; - } - } -} - -.controls-panel { - .move-settings.bp5-popover-wrapper { - padding-top: 2rem; - } - .index-indicator { - background: $dark_gray; - } -} - -.sensors-panel { - .panel-content { - label { - margin-top: 0; - } - } -} - -.farmware-panel, -.designer-farmware-list-panel, -.designer-farmware-info-panel, -.farmware-add-panel { - .panel-content { - max-height: calc(100vh - 9rem); - overflow-y: auto; - overflow-x: hidden; - a { - text-decoration: none !important; - p { - font-size: 1.4rem; - font-weight: normal !important; - line-height: 2rem; - } - } - } -} - -.designer-farmware-info-panel { - .panel-content { - position: relative; - padding-top: 2rem; - padding-bottom: 12rem; - button { - float: none; - } - .reset-configs { - margin-right: 1rem; - margin-top: 2rem; - } - .farmware-button { - position: absolute; - top: 1rem; - right: 1rem; - } - .farmware-form { - margin-top: 1rem; - } - .farmware-input-group { - margin-left: 0; - } - .advanced-configs { - .expandable-header { - margin-top: 2rem; - } - } - } -} - -.photos-panel { - .panel-content { - position: relative; - overflow-y: auto; - overflow-x: hidden; - padding-bottom: 12rem; - label { - margin-top: 0 !important; - } - a, - button { - margin: 0.5rem; - } - .filter-search { - button { - margin: 0; - } - } - fieldset { - label { - margin-top: 0.7rem; - } - button { - float: left; - height: 2rem; - } - } - .expandable-header { - margin-top: 2rem; - } - .filters-enabled-warning { - float: right; - } - .fa-exclamation-triangle { - font-size: 1.3rem; - color: $orange; - } - .toggle-label { - margin-top: 0 !important; - margin-bottom: 1rem; - } - .fb-toggle-button { - margin-top: 0.5rem !important; - } - .photo-filter-settings { - .filter-controls { - .banner { - display: none; - } - &.single-image-mode, - &.image-layer-disabled { - opacity: 0.40; - * { - pointer-events: none; - } - .banner { - display: inline-block; - position: absolute; - top: 25%; - left: -2.5%; - z-index: 10; - width: 105%; - padding: 0.5rem; - background-color: $dark_gray; - opacity: 0.90; - color: $off_white; - font-size: 1.8rem; - vertical-align: middle; - text-align: center; - } - } - } - } - .capture-settings { - .help-icon { - margin-left: 1rem; - font-size: 1.2rem; - } - .row { - margin-right: 0; - margin-top: 0.5rem; - } - label { - margin: 0; - line-height: 3rem; - font-style: normal; - } - .filter-search { - margin-right: -1rem; - } - .bp5-popover-wrapper { - display: inline-block; - } - .fb-toggle-button { - margin-top: 2rem; - } - .image-size-inputs { - margin-top: 1rem; - .size-inputs { - margin-top: 1rem; - } - .input { - margin-left: 0.25rem; - margin-right: -0.75rem; - input { - height: 3rem; - } - } - .resolution-change-warning { - i { - color: $darkest_red; - margin-right: 1rem; - } - p { - display: inline; - margin-right: 0.5rem; - font-size: 1.2rem; - } - .click { - cursor: pointer; - font-weight: bold; - &:hover { - color: $black; - } - } - } - } - .update-take-photo { - margin-top: 1rem; - .version-string { - display: inline; - margin-left: 1rem; - } - } - .capture-rotate-setting { - margin-top: 1rem; - margin-bottom: 1rem; - } - } - .imaging-data-management { - label { - line-height: 3rem; - } - .highlight-modified-toggle { - label { - margin-top: 0.5rem; - margin-left: 0.5rem; - } - } - } - .farmware-form { - button { - float: none; - } - } - .farmware-input-group { - margin-left: 0; - } - .farmware-config-fields { - margin-left: 1rem; - .config-field { - margin-top: 1rem; - } - } - .advanced-configs { - margin-left: 1rem; - .farmware-config-fields { - margin-left: 0; - } - h4 { - font-size: 1.5rem; - .icon-toggle { - font-size: 1.1rem; - i { - font-size: 0.9rem; - } - } - } - } - } - .title-help { - font-size: 1.3rem; - .fa-question-circle { - position: absolute; - top: -3rem; - right: 0; - font-size: 1.4rem; - } - a { - text-decoration: none !important; - } - .fa-external-link { - margin-left: 1rem; - } - .update { - display: inline; - p { - display: inline; - margin-left: 1rem; - } - .fa-refresh { - display: inline; - margin-left: 1rem; - color: $dark_gray; - } - } - .title-help-text.open { - margin-bottom: 0; - } - } -} - -.farmware-env-editor { - .row { - margin-bottom: 1rem; - &.no-margin { - margin: 0; - } - } - .env-hide-toggle { - margin-top: 1rem; - margin-bottom: 1rem; - } - .env-editor-warning { - margin: 0 -15px 2rem -15px; - padding: 1rem 1.5rem 1rem 2rem; - background: color.adjust($orange, $alpha: -0.6); - p { - line-height: 1.75rem; - font-size: 1.2rem; - font-weight: bold; - } - } - .bp5-popover-wrapper { - display: inline; - margin-left: 1rem; - } -} - -.env-editor-lua { - margin: 0 -15px 0 -15px; - padding: 1rem 1.5rem 1rem 2rem; - background: color.adjust($blue, $alpha: -0.6); - p { - line-height: 1.75rem; - font-size: 1.2rem; - font-weight: bold; - } -} - -.designer-sequence-list-panel { - .panel-content { - height: calc(100vh - 14rem); - overflow-y: scroll; - overflow-x: hidden; - padding-bottom: 6rem; - @media screen and (max-width: $mobile_max_width) { - max-height: calc(100vh - 19rem); - } - .panel-top { - margin-left: 0 !important; - } - a { - text-decoration: none !important; - p { - font-size: 1.4rem; - font-weight: normal !important; - line-height: 2rem; - } - } - .folders-panel { - margin-left: -1rem; - margin-right: -1rem; - } - } - .fullscreen { - white-space: nowrap; - height: 3rem; - margin-top: 0.75rem; - margin-right: 1rem; - i { - margin-left: 1rem; - } - } -} - -.group-detail-panel, -.designer-regimen-editor-panel, -.designer-sequence-editor-panel { - .panel-header { - input { - position: relative; - z-index: 1; - margin: 1rem; - width: 84%; - @media screen and (max-width: $mobile_max_width) { - width: 65%; - } - } - } -} - -.designer-sequence-editor-panel { - .panel-content { - .sequence-editor-content { - hr { - margin-top: 1rem; - } - @media screen and (max-width: 767px) { - margin-left: 5px; - padding-right: 0; - } - } - .add-command-button-container { - display: block; - } - .drag-drop-area { - display: none; - } - label { - margin-top: 0; - } - @media screen and (max-width: 767px) { - padding: 0; - } - } -} - -.weed-info-panel, -.point-info-panel, -.designer-regimen-editor-panel, -.designer-sequence-editor-panel { - .green, - .red { - .panel-title { - .fa-paint-brush, - .fa-expand, - .fa-trash, - .fa-clone, - .fa-magic, - .fa-spinner { - color: $white; - } - } - } -} - -.designer-regimen-scheduler-panel { - .panel-content { - display: inline-block; - max-height: calc(100vh - 9rem); - overflow-y: auto; - overflow-x: hidden; - width: 100%; - } -} - -.designer-regimen-editor-panel { - .title { - width: 45%; - } - .panel-title { - input { - margin: 1rem; - width: 75%; - } - .fa-clone, - .fa-trash, - .fa-paint-brush { - float: right; - margin-top: 1.5rem; - } - .button-group { - margin: 0; - float: right; - width: unset; - } - .color-picker { - float: right; - } - .fb-button { - margin: 1.5rem; - } - } - .green, - .red { - .panel-title { - .color-picker { - .icon-saucer { - color: $white; - } - } - } - } - .panel-content { - max-height: calc(100vh - 14rem); - overflow-y: auto; - overflow-x: hidden; - label { - margin-top: 0; - } - } -} - -.messages-panel { - .panel-content { - overflow-y: auto; - overflow-x: hidden; - label { - margin-top: 0; - } - } -} - -.logs-panel { - .panel-content { - margin-top: 6rem; - max-height: calc(100vh - 9rem); - overflow-y: auto; - overflow-x: hidden; - } -} - -.documentation-panel, -.support-panel, -.tours-panel, -.help-panel { - @media screen and (max-width: $mobile_max_width) { - .help-panel-header { - position: relative; - margin-top: 5rem; - } - .panel-content { - padding: 1rem; - padding-top: 0; - } - } - .panel-content { - max-height: calc(100vh - 12rem); - overflow-y: auto; - overflow-x: hidden; - padding-bottom: 12rem; - label { - margin-top: 0 !important; - } - } -} - -.documentation-panel { - .panel-content { - height: 102.5%; - padding: 0; - overflow: hidden; - iframe { - width: 100%; - height: 100%; - border: none; - } - } -} - -.help-panel-header { - background: $lighter_gray; - height: 4rem; - padding: 1rem; - padding-top: 0.5rem; - i { - font-size: 2rem; - margin-left: 0.25rem; - width: 2.25rem; - } - i, - img { - margin-right: 1rem; - margin-bottom: 0.5rem; - vertical-align: middle; - filter: brightness(0) opacity(0.75); - text-align: center; - } - a { - display: block; - font-weight: bold; - line-height: 3.5rem; - } - .fa-chevron-down, - .fa-chevron-up { - position: absolute; - top: 0.25rem; - right: 0; - padding: 1rem; - font-size: 1.25rem; - color: $medium_gray; - } - .bp5-collapse { - overflow: visible; - } - .bp5-collapse-body { - position: relative; - z-index: 1; - margin: -1rem; - margin-top: 0; - background: $lighter_gray; - a { - padding: 1rem; - line-height: 0; - &:hover { - background: $light_gray; - } - } - } -} - -.support-panel-content { - padding: 2rem; - padding-top: 0; - h1 { - font-size: 1.2rem; - font-weight: bold; - } - .row { - margin-bottom: 3rem; - } - a { - &.button { - display: block; - margin-top: 1rem; - text-decoration: none !important; - text-align: center; - border: 1.5px solid $dark_gray; - border-radius: 5px; - padding: 0.75rem; - line-height: 1.3rem; - b { - display: block; - font-size: 1.1rem; - text-transform: uppercase; - } - i { - font-size: 0.9rem; - color: $medium_gray; - font-weight: normal; - } - &:hover { - background: $lighter_gray; - } - } - &.inline { - text-decoration: underline; - margin-left: 0.25rem; - } - } -} - -.feedback { - p { - color: $dark_gray !important; - margin-bottom: 1rem !important; - font-weight: normal !important; - font-style: italic; - } - textarea { - font-size: 1.1rem; - height: 7.5rem; - } - button { - margin-top: 1rem; - float: none; - &:hover { - font-size: 1rem !important; - } - } - .bp5-popover-wrapper { - display: inline; - line-height: 0; - vertical-align: bottom; - margin-left: 1rem; - } -} - -.jobs-panel { - .panel-content { - display: inline-block; - overflow-y: auto; - overflow-x: auto; - width: 100%; - padding: 0; - } -} - -.jobs-tab { - overflow-y: scroll; - max-height: 26rem; - &.bp5-popover { - margin-top: 1.5rem; - } - table { - p { - padding: 1rem; - } - .job-name { - max-width: 20rem; - overflow: hidden; - text-overflow: ellipsis; - } - thead { - position: sticky; - top: 0; - z-index: 999; - background: $white; - } - tr { - transform: scale(1); - } - th, - td { - white-space: nowrap; - font-size: 1.2rem; - padding: 0.75rem; - } - th { - text-transform: uppercase; - } - .right-align { - text-align: right; - } - .progress { - position: absolute; - top: 0; - left: 0; - height: 99%; - opacity: 0.5; - border-radius: 0; - pointer-events: none; - } - .fa-clock-o { - cursor: default !important; - } - } -} - -.jobs-panel-portal { - .bp5-popover-content { - padding: 0; - width: min(500px, 100vw - 1rem); - max-height: calc(100vh - 10rem); - overflow: hidden; - padding-top: 1rem; - } -} - -.saved-garden-edit-panel { - .buttons { - position: absolute; - top: 1.25rem; - right: 0.5rem; - .fa-trash { - color: $white; - } - } -} - -.saved-garden-edit-panel-content { - button { - margin-left: 0.5rem; - margin-top: 1rem; - } - .point-list-wrapper { - margin-top: 2rem; - } - .row { - margin: 0; - } - textarea { - font-size: 1.2rem; - } -} - -.desktop-hide { - display: none !important; - @media screen and (max-width: 1075px) { - display: block !important; - } -} - -.setup-panel { - label { - margin-top: 0 !important; - } +.weeds-inventory-panel, +.zones-inventory-panel, +.point-inventory-panel, +.groups-panel { .panel-content { - max-height: calc(100vh - 9rem); - overflow-y: auto; - overflow-x: hidden; - h1 { - display: inline-block; - cursor: default; - } - .progress-meter { - display: inline; - margin-left: 2rem; - font-weight: bold; - color: $medium_light_gray; - } - .start-over { - margin-top: 2rem; - } - h1, - h2, - h3 { - margin-bottom: 0; + .points-section-header { cursor: pointer; - .fa-caret-up, - .fa-caret-down { - font-size: 2.5rem; - float: right; - line-height: 1rem; - margin-right: 1rem; - margin-top: 0.25rem; + height: 4rem; + line-height: 2.75rem; + label { + cursor: pointer; } .saucer { display: inline-block; - float: none; + height: 3rem; + width: 3rem; + vertical-align: middle; + margin-right: 0.25rem; margin-left: 1rem; - margin-top: 0; - vertical-align: top; - } - } - .saucer { - float: right; - margin-right: 0.5rem; - margin-top: 0.5rem; - height: 1.5rem; - width: 1.5rem; - i { - font-size: 1rem; - color: $white; - vertical-align: top; - width: 1.5rem; - line-height: 1.5rem; - text-align: center; - } - } - h1 { - font-weight: bold; - font-size: 2rem; - cursor: default; - } - h2 { - font-weight: bold; - font-size: 1.4rem; - padding: 1rem 1.5rem 1rem 1.5rem; - } - h3 { - font-size: 1.4rem; - margin-bottom: 1rem; - } - .prerequisites { - margin-top: 1rem; - p { - font-size: 1.3rem; - } - } - .prerequisites, - .prereq-not-met { - font-size: 1.3rem; - padding: 1rem; - background: $panel_light_red; - border: 1px solid $dark_red; - border-radius: 5px; - } - .wizard-header { - margin-bottom: 2rem; - } - .wizard-section { - h2 { - margin-top: 0; - } - .bp5-collapse-body { - margin-bottom: 2rem; - } - } - .wizard-step { - margin-left: -15px; - margin-right: -15px; - padding-left: 2rem; - padding-right: 2rem; - background: $lighter_gray; - .bp5-collapse-body { - margin-bottom: 0; - } - img { - width: 100%; - border-radius: 5px; - margin-bottom: 1rem; - } - } - .wizard-step-info { - display: inline; - } - .warning-banner { - margin: 0 -15px 1rem -15px; - padding: 1rem 1.5rem 1rem 2rem; - background: color.adjust($orange, $alpha: -0.6); - p { - line-height: 1.75rem; - font-size: 1.3rem; - font-weight: bold; } } - h2, - .wizard-step-header { - margin-left: -15px; - margin-right: -15px; - background: $panel_light_gray; - &:hover { - background: $light_gray; - } - } - .wizard-step-header { - padding: 0.5rem 1.5rem 0 1.5rem; - cursor: pointer; + .points-section { &.open { - background: $lighter_gray; - h3 { - font-weight: bold; - } - } - p { - display: inline; - color: $medium_light_gray; - margin-left: 1rem; + margin-bottom: 1rem; } - } - .wizard-answer { - button { - margin: 1rem; - float: none; - padding: 1rem 2rem 1rem 2rem; + .row { + padding: 0 1rem; } } - iframe { - aspect-ratio: 16/9; - border-radius: 5px; - box-shadow: 0px 0px 7px 0px $translucent; + .no-points, + .no-weeds { + margin-left: 1rem; + padding-bottom: 1rem; } - .wizard-components { - margin: auto; - width: fit-content; - border-radius: 5px; - box-shadow: 0 0 7px $translucent; - padding: 1rem; - background: $panel_light_gray; - &.no-border { - border: none; - width: 100%; - padding: 0; - } - &.no-background { - border: none; - width: max-content; - padding: 0; - } - &.full-width { - width: 100%; - } - &:empty { - display: none; - } - .widget-wrapper { - margin-bottom: 0; - } - .tool-verification-status { - text-align: center; - button { - margin: 1rem; - float: none; - } - } - .connectivity { - .diagnosis-indicator { - position: absolute; - top: -3rem; - height: 2rem; - width: 2rem; - .fa { - margin-left: -0.25rem; - margin-top: -0.1rem; - } - } - .saucer-connector { - display: none; - } - } - .flash-firmware { - display: block; - float: none; - } - .tour-start { - display: block; - margin: auto; - float: none; - padding: 1rem; - } - .setting { - .no-pad { - padding-left: 1.5rem; - padding-right: 1.5rem; - } - } - .pin-binding-input-rows { - .row { - margin-bottom: 0.5rem; - } - label { - margin-left: 1.5rem; - } - } - .flow-rate-input { - .fb-button { - margin-top: 0; - } - } + .fa-caret-down, + .fa-caret-up { + margin: 1rem; } - .wizard-step-question { - font-style: italic; + button { + float: none; } - .markdown { - display: block; - p { - width: 100%; - overflow: visible; - color: $dark_gray; - font-size: 1.4rem; - white-space: pre-wrap; - line-height: 2rem; - padding-top: 1rem; - padding-bottom: 1rem; + .approval-buttons { + i { + padding-right: 0.5rem; } } - .troubleshooting { - padding-bottom: 1rem; - a { - float: none; - padding: 0; - padding-left: 0.25rem; - } - .feedback { - textarea { - margin-top: 0.5rem; - } - p { - margin-bottom: 0 !important; - padding-bottom: 0 !important; - } + .pending-weeds { + .fa-check { + margin: 0 !important; } } - .troubleshooting-tip { - margin: 1rem 0; - border: 2px solid $medium_light_gray; - border-radius: 5px; - padding: 1rem; - cursor: pointer; - background: $panel_light_gray; - &:hover { - background: $white; - box-shadow: 0 1px 5px 0 $translucent; - } - p { - font-size: 1.4rem; - line-height: 2rem; - } - a { - font-size: 1.4rem; - text-decoration: underline; - } - &.selected { - border-color: $dark_gray; - background: $white; - p:first-of-type { - font-weight: bold; - padding-bottom: 0.5rem; - } - fieldset { - p { - font-weight: normal !important; - } - } - } - .fb-button { - float: none; - } - .arrow-button { - p { - font-size: 0.9rem; - } - } - .farmbot-origin { - margin-top: 1rem; - } - .motor-settings { - .row { - margin-top: 1rem; - } - } - .dropdown-camera-calibration-config, - .camera-config-number-box { - .bp5-popover-wrapper { - display: inline-block; - margin-left: 1rem; + } +} + +.sensors-panel, +.controls-panel { + .panel-content { + .widget-wrapper { + box-shadow: none; + .widget-header { + background: none; + h5 { + color: $dark_gray; } } - .filter-search { - .bp5-popover-wrapper { - display: unset; - margin-left: unset; - } + .widget-body { + background: none; + border: none; } - .yellow { - float: none; - &:hover { - font-size: 1rem; + .widget-footer { + background: none; + * { + color: $dark_gray; } } - iframe { - margin-top: 1rem; - } - .wizard-find-home-btn { - float: none; - } - } - .setup-complete { - margin-top: 2rem; - text-align: center; - p, - .saucer { - display: inline; - font-size: 1.5rem; - font-weight: bold; - margin-left: 1rem; - float: none; - } - .saucer { - display: inline-block; - } } - } -} - -.camera-check { - text-align: center; - p { - margin-top: 1rem; - } - button { - float: none; - } - img { - width: 100%; - padding: 1rem; - } -} - -.farmbot-model-selection { - width: 27rem; - padding: 1rem; - .fb-button { - margin: 1rem; - } - p { - margin-top: 0.5rem; - } - .seed-checkbox { - .fb-checkbox { - display: inline-block; - margin-top: 1rem; + .title-help-icon:hover { + color: $dark_gray !important; } - p { - display: inline; - margin-left: 0.5rem; - vertical-align: bottom; - line-height: 3rem; + .title-help-text.open { + color: $dark_gray !important; + font-size: 1.2rem; } } } -.peripherals-check { - margin: 1rem; -} - -.camera-calibration-card { - padding: 1rem; - width: 15rem; - svg { - width: 100%; - height: 100%; - background: $dark_gray; +.desktop-hide { + display: none !important; + @media screen and (max-width: 1075px) { + display: block !important; } } diff --git a/frontend/css/farm_designer/farm_events.scss b/frontend/css/farm_designer/farm_events.scss deleted file mode 100644 index ac32557986..0000000000 --- a/frontend/css/farm_designer/farm_events.scss +++ /dev/null @@ -1,122 +0,0 @@ -.farm-event-panel { - .fa-calendar { - position: absolute; - top: 0.5rem; - left: 1rem; - } - .panel-content { - padding: 0 1rem 6rem 0; - padding-bottom: inherit; - } -} - -.farm-events { - max-height: calc(100vh - 15rem); - overflow-y: auto; - overflow-x: hidden; - margin-right: -10px; - padding-bottom: inherit; - @media screen and (max-width: $mobile_max_width) { - max-height: calc(100vh - 20rem); - } -} - -.farm-event { - display: flex; - margin-top: 1rem; - margin-right: 10px; -} - -.farm-event-year { - text-align: center; - margin-top: 1rem; - font-size: 1.1rem; - font-weight: bold; -} - -.farm-event-date { - flex: 1; - text-align: center; -} - -.farm-event-date-day { - font-size: 2rem; -} - -.farm-event-data { - flex: 5; - max-width: 83%; -} - -.farm-event-variable { - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 125%; - font-size: 1rem; - line-height: 1.25rem; -} - -.farm-event-data-block { - display: flex; - position: relative; - padding: 0.8rem; - font-size: 1.2rem; - align-items: center; - background: $white; - border-bottom: 1px solid $gray; - p, - span { - color: $medium_gray; - } - .farm-event-data-time { - z-index: 1; - width: 6rem; - } - .farm-event-data-executable { - z-index: 1; - margin-left: 1rem; - max-width: 65%; - overflow-wrap: anywhere; - p { - margin-top: 0.2rem; - margin-bottom: 0.75rem !important; - } - } - &:hover { - i { - opacity: 1; - } - } - .edit-link { - position: absolute; - right: 0.8rem; - } - .fa-external-link { - margin-left: 0.5rem; - } - i { - opacity: 0; - } - &:before { - content: ""; - position: absolute; - top: 0; - left: 0; - z-index: 0; - width: 100%; - height: 100%; - background: $white; - opacity: 0.7; - } - &:after { - content: ""; - position: absolute; - top: 0; - left: 7rem; - bottom: 0; - width: 1px; - background: $gray; - } -} diff --git a/frontend/css/farm_designer/profile_viewer.scss b/frontend/css/farm_designer/profile_viewer.scss new file mode 100644 index 0000000000..e74fe4130d --- /dev/null +++ b/frontend/css/farm_designer/profile_viewer.scss @@ -0,0 +1,129 @@ +@use "../variables" as *; +@use "sass:color"; + +.profile-viewer { + position: fixed; + bottom: 0; + z-index: 2; + width: calc(100% - $mobile_max_width); + background: #e5e5e5; + transition: transform 0.5s ease-out; + box-shadow: 0 0 1px rgba(0, 0, 0, .3); + transform: translateY(10rem) translateX($mobile_max_width); + &.panel-closed-mobile, + &.panel-closed { + width: 100%; + transform: translateY(10rem); + } + &.none-chosen { + transform: translateY(21rem) translateX($mobile_max_width); + &.panel-closed-mobile, + &.panel-closed { + transform: translateY(21rem); + } + } + @media screen and (max-width: $mobile_max_width) { + &.panel-open { + display: none; + } + } + &.open { + transform: translateY(0) translateX($mobile_max_width); + &.panel-closed-mobile, + &.panel-closed { + transform: translateY(0); + } + .profile-button { + i { + line-height: 6rem; + } + } + } + .profile-button { + margin: auto; + cursor: pointer; + margin-top: -3rem; + border-radius: 50%; + width: 6rem; + height: 6rem; + background: $magenta; + transition: background-color 0.3s ease-out; + text-align: center; + box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, .3); + &:hover { + background: color.adjust($magenta, $lightness: 5%); + } + i { + color: $off_white; + line-height: 3.25rem; + font-size: 2rem; + transition: line-height 0.5s ease-out; + } + } + .profile-content { + label { + display: block; + margin: auto; + margin-top: 1rem; + margin-bottom: 0.5rem; + text-align: center; + } + .no-profile { + height: 12.5rem; + text-align: center; + margin-top: 1.5rem; + } + .left-label, + .right-label { + position: absolute; + top: 50%; + font-weight: bold; + } + .left-label { + left: 2rem; + } + .right-label { + right: 2rem; + } + svg { + display: block; + margin: auto; + height: 10rem; + width: 90%; + border-radius: 5px; + padding: 1rem; + margin-bottom: 1rem; + background: #fafafa; + box-shadow: 0 0 4px rgba(0, 0, 0, .1); + &.expand { + height: unset; + max-height: 60vh; + } + } + .profile-options { + width: 100%; + height: 4rem; + text-align: center; + padding-top: 1rem; + padding-bottom: 1rem; + background: #f2f2f2; + box-shadow: 0px 0px 1px 0px #666666; + button { + display: inline; + float: none; + margin-right: 3rem; + } + label { + display: inline; + margin-right: 0.5rem; + line-height: 2rem; + vertical-align: text-top; + } + input { + height: 2rem !important; + width: 5rem; + margin-right: 3rem; + } + } + } +} diff --git a/frontend/css/farm_designer/three_d_garden.scss b/frontend/css/farm_designer/three_d_garden.scss new file mode 100644 index 0000000000..4c31e5f76b --- /dev/null +++ b/frontend/css/farm_designer/three_d_garden.scss @@ -0,0 +1,555 @@ +@use "../variables" as *; +@use "sass:color"; + +.promo-loading-container { + display: grid; + align-content: center; + justify-content: center; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: -9999999; +} + +.promo-loading-image { + height: 100vh; +} + +.promo-loading-text { + font-family: 'Inknut Antiqua', sans-serif; + font-size: 2rem; + font-weight: bold; + color: $off_white; + text-shadow: 0 0 3.5rem #000, 0 0 1rem #000; + text-align: center; + margin: 0; + position: absolute; + width: 100vw; + height: 100vh; + align-content: center; +} + +.promo { + &.three-d-garden { + .garden-bed-3d-model { + width: 100vw; + } + + .overlay { + .settings-bar { + button { + height: 100%; + } + } + } + } +} + +.three-d-garden { + position: relative; + height: 100vh; + width: 100vw; + cursor: grab; + + body { + margin: 0; + } + + .garden-bed-3d-model { + width: calc(100vw + 45rem); + position: relative; + height: 100vh; + cursor: grab; + + &:active { + cursor: grabbing; + } + + @media screen and (max-width: 768px) { + width: 100vw; + } + } + + .gear { + position: absolute; + top: 1rem; + right: 1rem; + width: 1rem; + cursor: pointer; + background: rgba(255, 255, 255, 0.4); + box-shadow: $translucent2 0px 0px 5px; + padding: 7px; + border-radius: 5px; + backdrop-filter: blur(5px); + opacity: 0; + + &:hover { + opacity: 1; + } + } + + .overlay { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + pointer-events: none; + user-select: none; + + .settings-bar { + display: flex; + position: absolute; + left: 0; + bottom: 0; + right: 0; + justify-content: center; + gap: 1.25rem; + padding: 1rem; + overflow-x: scroll; + background: linear-gradient(0deg, $translucent5, transparent); + scrollbar-width: none; + + @media screen and (max-width: 768px) { + justify-content: left; + pointer-events: all; + } + + .setting-section { + pointer-events: all; + } + + .setting-title { + color: $off_white; + font-family: 'Inknut Antiqua'; + text-shadow: 0 0 10px black; + line-height: 1.8rem; + text-align: center; + font-size: 1rem; + } + + .row { + display: flex; + margin: 0; + background: rgba(255, 255, 255, 0.6); + box-shadow: $translucent2 0px 0px 1rem; + border-radius: 2.5rem; + height: 3.1rem; + padding: 0.3rem; + justify-content: space-evenly; + backdrop-filter: blur(5px); + gap: 0.5rem; + &:after, + &:before { + content: unset; + display: unset; + clear: unset; + } + } + + button { + padding: 0 0.85rem; + border-radius: 2.5rem; + font-weight: bold; + border: none; + background: none; + white-space: nowrap; + color: $off_black; + + &.outdoor.active, + &.lab.active, + &.genesis.active, + &.standard.active, + &.mobile.active { + background: rgba(255, 255, 255, 0.6); + } + + &.genesis-xl.active { + background: linear-gradient(315deg, #22a36d, #4ea3ed); + color: $off_white; + } + + &.winter.active { + background: linear-gradient(-15deg, #152e40, #3e8dc2); + color: $off_white; + } + + &.spring.active { + background: linear-gradient(15deg, #055b08, #49cc78); + color: $off_white; + } + + &.summer.active { + background: linear-gradient(-15deg, #d87f09, #f5e40a); + } + + &.fall.active { + background: linear-gradient(15deg, #502402, #c69075); + color: $off_white + } + + &:hover { + cursor: pointer; + background: $translucent3_white; + } + + &.disabled { + color: $placeholder_gray; + + &:hover { + cursor: not-allowed; + background: none; + } + } + } + } + } + + .tool-tip { + position: absolute; + left: 0; + bottom: 6.5rem; + right: 0; + margin: auto; + width: fit-content; + color: $off_white; + background: rgba(120, 0, 0, 0.65); + padding: 0.75rem 1rem; + border-radius: 5px; + text-align: center; + pointer-events: none; + backdrop-filter: blur(5px); + box-shadow: $translucent2 0px 0px 5px; + + @media screen and (max-width: 768px) { + margin: 0 1rem; + } + } + + .all-configs { + position: absolute; + top: 1rem; + right: 1rem; + width: 22rem; + background: $dark_gray; + text-align: left; + padding: 1rem; + border-radius: 5px; + max-height: 30rem; + overflow-y: scroll; + + .close { + position: absolute; + top: 0.5rem; + right: 1rem; + margin: 0; + cursor: pointer; + font-size: 1rem; + padding: 0.5rem 0.75rem; + background: none; + border-radius: 5px; + + &:hover { + background: $medium_gray; + } + } + + .spacer { + margin-top: 1rem; + } + + details { + color: $off_white; + + label { + font-weight: bold; + color: $off_white; + text-transform: none; + font-size: 0.9rem; + } + } + + summary { + margin: -1rem; + cursor: pointer; + padding: 1rem; + + &:hover { + background: $dark_gray; + } + } + + .config-row { + display: grid; + grid-template-columns: min-content minmax(30%, 200px) auto; + margin-bottom: 0.25rem; + + span { + width: 8rem; + padding-left: 1rem; + color: $gray; + } + + input[type="checkbox"] { + width: 1.1rem; + height: 1.1rem; + justify-self: left; + cursor: pointer; + box-shadow: none; + } + + input[type="number"] { + width: 100%; + max-width: 4rem; + height: 1rem; + font-size: 0.9rem; + box-shadow: none; + } + + input[type="radio"] { + width: 1.1rem; + height: 1.1rem; + cursor: pointer; + box-shadow: none; + } + + .options { + width: 100%; + justify-content: space-between; + } + + input[type="range"] { + width: 4rem; + margin-left: -2rem; + cursor: pointer; + box-shadow: none; + height: 1rem; + } + } + } + + .promo-info { + display: grid; + position: absolute; + top: 3rem; + right: 4rem; + color: $off_white; + text-align: right; + pointer-events: none; + text-shadow: 0 0 3.5rem $black, 0 0 1rem $black; + gap: 1rem; + justify-items: right; + + .title { + margin: 0; + font-family: 'Inknut Antiqua'; + font-size: 3.5rem; + line-height: 5rem; + font-weight: bold; + } + + .description { + margin: 0; + max-width: 40vw; + + p { + color: $off_white; + font-size: 1.25rem; + line-height: 1.7rem; + font-weight: bold; + } + + .short { + display: none; + } + + .full { + display: inline; + } + } + + .buy-button { + display: flex; + pointer-events: all; + background: #00a579e0; + border-radius: 7px; + box-shadow: $translucent2 0px 0px 10px; + backdrop-filter: blur(5px); + padding: 0.1rem 1.75rem; + text-shadow: none; + text-transform: uppercase; + text-decoration: none; + gap: 0.4rem; + align-items: center; + + p { + margin: 0; + color: $off_white; + font-size: 1.5rem; + line-height: 4rem; + font-weight: bold; + } + + .genesis-xl { + background: linear-gradient(315deg, #22a36d, #4ea3ed); + box-shadow: 0 0 8px rgba(0, 0, 0, 0.25); + color: $off_white; + border-radius: 50%; + height: 2.5rem; + width: 2.5rem; + line-height: 2.5rem; + text-align: center; + font-size: 1.2rem; + vertical-align: middle; + } + + &:hover { + background: #00bb89e5; + } + } + } + + .beacon-info { + width: 34rem; + background: $white; + background: $translucent8_white; + color: $black; + box-shadow: $translucent2 0px 0px 10px; + border-radius: 0.5rem; + padding: 1.5rem; + backdrop-filter: blur(5px); + text-align: left; + user-select: none; + p { + margin-bottom: 0; + margin-top: 1rem; + font-weight: bold; + font-size: 1.25rem; + color: $black; + line-height: 1.6rem; + } + a { + text-decoration: underline !important; + text-underline-offset: 0.1rem; + text-underline-position: from-font; + font-weight: bold; + color: $dark_blue; + } + iframe { + margin-top: 1rem; + width: 100%; + height: auto; + border-radius: 0.35rem; + aspect-ratio: 16 / 9; + } + .header { + display: flex; + justify-content: space-between; + font-size: 1rem; + h2 { + margin: 0; + font-family: 'Inknut Antiqua'; + line-height: 100%; + font-weight: bold; + } + .exit-button { + width: 2rem; + height: 2rem; + aspect-ratio: 1 / 1; + border-radius: 50%; + filter: grayscale(100%); + font-size: 0.75rem; + line-height: 2rem; + text-align: center; + background: rgba(255, 255, 255, 0.4); + box-shadow: $translucent2 0px 0px 0.75rem; + &:hover { + cursor: pointer; + background: rgba(255, 255, 255, 0.6); + } + } + } + } + + @media screen and (max-width: 768px) { + .beacon-info-wrapper { + display: grid; + position: absolute; + left: 0; + bottom: 0; + right: 0; + align-items: end; + transform: none!important; + * { + transform: none!important; + } + .beacon-info { + width: initial; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + overflow: scroll; + height: 33vh; + font-size: 100%; + + .header { + h2 { + font-size: 1.3rem; + } + } + + p { + font-size: 1rem; + line-height: 1.2rem; + } + } + } + .promo-info { + top: 1rem; + right: 1rem; + gap: 0.5rem; + + .title { + font-size: 1.5rem; + line-height: 2rem; + } + + .description { + max-width: 80vw; + + p { + font-size: 1rem; + line-height: 1.5rem; + } + + .short { + display: inline; + } + + .full { + display: none; + } + } + + .buy-button { + font-size: 0.85rem; + line-height: 2.2rem; + padding: 0.1rem 0.8rem; + border-radius: 5px; + + p { + font-size: 0.85rem; + line-height: 2rem; + } + + .genesis-xl { + height: 1.5rem; + width: 1.5rem; + line-height: 1.5rem; + font-size: 0.7rem; + } + } + } + } +} diff --git a/frontend/css/fonts.scss b/frontend/css/fonts.scss deleted file mode 100644 index d26e931211..0000000000 --- a/frontend/css/fonts.scss +++ /dev/null @@ -1,33 +0,0 @@ -// Google Fonts -@import url("https://fonts.googleapis.com/css?family=Cabin:400,400italic,700,700italic,100,100italic"); -@import url("https://fonts.googleapis.com/css?family=Inknut+Antiqua:400,400italic,700,700italic,100,100italic"); -@import "colors"; -// Font Variables -$cabin: 'Cabin', -Arial, -Helvetica, -sans-serif; -body, -html { - font-family: $cabin; - font-weight: 400; -} - -h1, -h2, -h3, -h4, -h5, -h6, -p, -fieldset { - font-family: $cabin; - font-weight: 400; -} - -p { - font-size: 1.1rem; - color: $dark_gray; - line-height: 1.4rem; - margin-bottom: 0 !important; -} diff --git a/frontend/css/global.scss b/frontend/css/global.scss deleted file mode 100644 index 05c73cecb5..0000000000 --- a/frontend/css/global.scss +++ /dev/null @@ -1,3109 +0,0 @@ -/* Styles used throughout the frontend */ - -body { - background: $gray; -} - -.initial-loading-text { - position: absolute; - top: 385px; - z-index: -1; - text-align: center; - width: 100%; - padding-top: 10%; - color: $dark_gray; -} - -.widget-body .row:not(:last-child) { - margin-bottom: 1rem; -} - -.colorpicker-menu { - padding: 0; - .bp5-popover-arrow-fill { - fill: $dark_gray; - } - .bp5-popover-content { - width: 13rem; - background: $dark_gray; - } - .bp5-popover-content, - .color-picker-cluster, - .color-picker-item-wrapper, - .saucer { - display: inline-block; - padding: 0.4rem; - } -} - -.bp5-popover.help { - .bp5-popover-arrow { - display: none !important; - } - .bp5-popover-arrow-fill { - fill: $dark_gray; - } - .bp5-popover-content { - width: 32rem; - color: $white; - background: $dark_gray; - } -} - -input[type=number]::-webkit-inner-spin-button, -input[type=number]::-webkit-outer-spin-button, -input[type=date]::-webkit-inner-spin-button, -input[type=date]::-webkit-outer-spin-button, -input[type=time]::-webkit-inner-spin-button, -input[type=time]::-webkit-outer-spin-button, -input[type=time] { - -webkit-appearance: none; - -moz-appearance: textfield; - margin: 0; - border-radius: 0; -} - -.char-limit { - float: right; - font-size: 1.4rem; - &.over { - color: $darkest_red; - font-weight: bold; - } -} - -.markdown { - p { - display: inline-block; - color: $white; - text-overflow: ellipsis; - overflow: hidden; - width: calc(100% - 14rem); - white-space: nowrap; - text-transform: none; - margin-top: 0.3rem; - } -} - -fieldset { - button { - margin: 0.6rem 0 0 0.6rem; - } -} - -.saucer { - position: relative; - z-index: 2; - height: 2rem; - width: 2rem; - background: $dark_gray; - border-radius: 50%; - cursor: pointer; - &.active { - border: 2px solid white; - } - &.hover { - border: 2px solid $dark_gray; - } - p { - font-weight: 900; - color: $white; - margin-left: 0.7rem; - margin-top: 0.3rem; - } -} - -.icon-saucer { - position: relative; - z-index: 2; - height: 2rem; - width: 2rem; - color: $dark_gray; - cursor: pointer; - &.active { - border: 2px solid white; - } - &.hover { - border: 2px solid $dark_gray; - } -} - -.saucer-connector { - position: absolute; - z-index: 1; - height: 3rem; - width: 1rem; - margin-left: 0.5rem; - margin-top: -1rem; - &.last { - margin-top: -9rem; - height: 8rem; - } -} - -.connector-hover-area { - visibility: hidden; - stroke: white; - pointer-events: all; -} - -.diagnosis-indicator { - text-align: center !important; - &.nav { - display: inline-block; - border-color: transparent; - } - i { - display: block; - position: absolute; - top: 2.5px; - left: 2.5px; - color: $white; - font-size: 11px; - &.fa-times { - left: 0.35rem; - } - &.fa-question { - left: 0.45rem; - } - } -} - -.jobs-and-logs { - margin-top: 1rem; -} - -.jobs-and-logs, -.controls-content, -.connectivity-content { - margin-bottom: 0; - padding-top: 2rem !important; - .tabs { - display: flex; - position: absolute; - top: 1rem; - left: 0; - z-index: 3; - width: 100%; - background: $white; - padding-bottom: 0.5rem; - :first-child { - margin-left: 1rem; - margin-right: 0.2rem; - border-bottom-left-radius: 7px; - border-top-left-radius: 7px; - } - :nth-child(2) { - margin-right: 0.2rem; - } - :last-child { - margin-right: 1rem; - border-bottom-right-radius: 7px; - border-top-right-radius: 7px; - } - label { - margin: 0; - padding: 0.5rem; - background: $light_gray; - color: $medium_gray; - width: 50%; - text-align: center; - margin-top: 0; - cursor: pointer; - &.selected { - background: $medium_gray; - color: $off_white; - } - &:hover { - background-color: color.adjust($white, $lightness: -40%); - color: $off_white; - } - } - } -} - -.controls-popover-portal { - .bp5-transition-container { - z-index: 999; - } - .controls-popover { - margin-top: 1.5rem; - max-width: 100vw; - .bp5-popover-content { - padding-bottom: 0; - } - .controls-content { - padding: 1rem; - padding-bottom: 0; - width: 500px; - max-width: calc(100vw - 3rem); - overflow-x: hidden; - max-height: calc(100vh - 10rem); - overflow-y: auto; - .row { - margin-bottom: 1rem; - } - } - } -} - -.connectivity-popover-portal { - .bp5-transition-container { - z-index: 999; - } - .connectivity-popover { - margin-top: -1rem; - max-width: 100vw; - .bp5-popover-content { - padding-bottom: 0; - } - .connectivity { - padding: 1rem; - padding-bottom: 0; - width: 600px; - max-width: calc(100vw - 3rem); - overflow-x: hidden; - max-height: calc(100vh - 10rem); - overflow-y: auto; - .row { - margin-bottom: 1rem; - } - .connectivity-content { - table { - font-size: 1.3rem; - } - } - .connectivity-left-column, - .connectivity-right-column { - padding-bottom: 2rem; - } - .connectivity-left-column { - padding-left: 0; - } - .connectivity-right-column { - margin-top: 1rem; - } - .connectivity-diagram svg { - max-height: 200px !important; - } - .port-info, - .network-info, - .fbos-info { - @media (max-width:767px) { - display: block; - } - } - .network-info { - margin-bottom: 0; - } - } - } -} - -.connectivity-diagnosis { - h4 { - margin-left: 3rem; - } - p { - padding-bottom: 1rem; - } - a { - display: block; - font-size: 1.2rem; - } - .blinking { - a { - display: inline; - } - } - .fa-external-link { - margin-right: 0.5rem; - } -} - -.camera-connection-indicator, -.memory-usage-display, -.chip-temp-display { - position: relative; - .saucer { - position: absolute; - top: 2px; - right: 0.5rem; - height: 1rem; - width: 1rem; - cursor: default; - } -} - -.voltage-display { - position: relative; - height: 1.4rem; - .voltage-saucer { - position: absolute; - top: 0; - right: 0.5rem; - height: 1rem; - width: 1rem; - cursor: default; - } -} - -.voltage-display { - display: flex; - .saucer { - height: 1rem; - width: 1rem; - cursor: default; - margin-left: 0.5rem; - margin-top: 0.2rem; - } - .help-icon { - margin: 0 0 0.5rem 0.25rem; - color: $dark_gray; - font-size: 1rem; - vertical-align: middle; - } -} - -.throttle-display { - .throttle-row { - display: flex; - white-space: nowrap; - .saucer { - margin-right: 1rem; - } - } -} - -.wifi-strength-display { - position: relative; - .percent-bar { - position: absolute; - top: 2px; - right: 0; - height: 1rem; - width: 25%; - clip-path: polygon(0 85%, 100% 0, 100% 100%, 0% 100%); - background-color: $light_gray; - .percent-bar-fill { - height: 100%; - background-color: $green; - } - } -} - -.mac-address { - font-size: 1rem; - b { - font-size: 1.1rem; - } -} - -.port-info, -.network-info, -.fbos-info { - margin: auto; - margin-bottom: 1rem; - margin-top: 1rem; - margin-left: 1rem; - background: $white; - padding: 1rem; - box-shadow: 0px 0px 10px $light_gray; - border-radius: 5px; - width: 20rem; - .ip-address { - word-break: break-all; - } - a { - display: block; - margin-top: 1rem; - font-size: 1.1rem; - text-decoration: underline !important; - i { - margin-right: 0.5rem; - } - } -} - -@keyframes adjust-width { - 100% { - width: 100.01%; - } -} - -.connectivity-diagram { - margin: auto; - width: 20rem; - background: $white; - svg { - animation: adjust-width 0.01s linear forwards; - } -} - -.port-info, -.qos-display { - .saucer { - float: right; - width: 1rem; - height: 1rem; - cursor: unset; - } -} - -.port-info { - i { - display: block; - font-size: 1.2rem; - } - .fa { - display: inline; - } -} - -.last-seen-row { - i { - margin-right: 0.5rem; - } -} - -.farmbot-os-details { - max-width: 350px; -} - -.os-release-channel { - margin-top: 0.5rem; - label { - margin: 0; - margin-right: 1rem; - line-height: 3rem; - } - .filter-search { - display: inline-block; - width: 50%; - button { - margin: 0; - } - } -} - -.garden-location { - .fa-map { - margin: 0.5rem; - float: right; - margin-bottom: 0; - color: $medium_gray; - } - .latitude { - padding-right: 0; - } - .longitude { - padding-left: 0; - } -} - -.all-content-wrapper { - margin: 0 auto; - padding: 11rem 3rem 0; - width: 100%; - max-width: 160rem; - animation: page-transition 0.2s ease-in-out; -} - -a { - cursor: pointer !important; - outline: none !important; -} - -.input-group { - width: 100% !important; - input[type=checkbox] { - box-shadow: none; - } -} - -.fa { - cursor: pointer !important; - &.fa-gear { - color: $white; - &.dark { - color: $dark_gray; - } - } -} - -.caution-icon { - pointer-events: none; - margin-left: 1rem; -} - -.drag-drop-area { - &.visible { - margin: 0.75rem 0; - margin-right: 25px; - margin-left: 10px; - border-style: dashed; - border-width: 2px; - border-color: $light_gray; - color: $gray; - font-weight: bold; - padding: 1.25rem; - background: $off_white; - text-align: center; - color: $gray; - font-weight: bold; - } -} - -.expandable-header { - cursor: pointer; - .icon-toggle { - font-size: 1.2rem; - font-weight: bold; - vertical-align: middle; - .fa { - font-size: 1rem; - } - } -} - -.centered-button-div { - text-align: center; - padding: 0.25rem; - .fb-button { - float: none !important; - } - label { - padding: 0; - } -} - -.single-setting-row { - .col-xs-5 { - padding-left: 4.75rem; - &.centered-button-div { - padding-left: 6rem; - } - } -} - -.fb-toggle-button, -.mcu-input-box { - position: relative; - .setting-status-indicator { - position: absolute; - z-index: 1; - } - .fa-exclamation-triangle { - color: $orange; - } - .fa-spinner { - color: $dark_gray; - } - .fa-check { - color: $green; - animation: fade-out 1s 0.4s forwards; - } - .save-error { - .bp5-popover-content { - background: $dark_gray; - min-width: 200px; - p { - text-transform: none; - color: $off_white; - } - } - .bp5-popover-arrow-fill { - fill: $dark_gray; - } - } -} - -.mcu-input-box { - .setting-status-indicator { - top: 0.5rem; - } -} - -.fb-toggle-button { - .setting-status-indicator { - top: 0.25rem; - } -} - -.mcu-input-box { - .setting-status-indicator { - right: 0.5rem; - } -} - -.fb-toggle-button { - &.yellow { - .setting-status-indicator { - display: none; - } - } - &.green { - .setting-status-indicator { - right: 0.5rem; - } - } - &.red { - .setting-status-indicator { - left: 0.5rem; - } - } -} - -.toggle-group { - display: inline-block; - width: 100%; - label { - padding-top: 0.5rem; - max-width: 75%; - } -} - -.load-progress-bar-wrapper { - position: relative; - margin: 0.5rem; - margin-top: 0; - margin-left: 0; - width: 90%; - height: 1.5rem; - border: 1px solid $dark_gray; - .load-progress-bar { - height: 100%; - background: $dark_gray !important; - p { - position: absolute; - color: $gray; - font-weight: bold; - font-size: 1rem; - } - } -} - -.firmware-setting-export-menu { - button { - margin-bottom: 1rem; - } - ul { - font-size: 0.75rem; - } -} - -.change-ownership-form { - p { - padding: 1rem; - margin-left: 0.5rem; - } - .row { - margin-bottom: 1rem; - &:empty { - margin-bottom: 0; - } - } - label { - margin-top: 0.5rem; - } - button { - margin-top: 1rem; - margin-right: 1rem; - } -} - -.pin-bindings { - .fa-exclamation-triangle { - color: $orange; - margin-left: 1rem; - margin-top: 0.75rem; - } - .fa-circle-o-notch, - .fa-th-large { - position: absolute; - top: 0.75rem; - left: 0.5rem; - color: $dark_gray; - } - .fb-button { - &.green { - margin-top: 0.5rem; - } - } - .bindings-list { - margin-bottom: 1rem; - margin-left: 1rem; - font-size: 1.2rem; - } - .binding-type-dropdown { - margin-bottom: 1.5rem; - } - .stock-pin-bindings-button { - button { - margin: 1rem; - float: left; - margin-left: 2rem; - } - i { - margin-right: 0.5rem; - } - } - .bp5-popover-wrapper { - display: inline; - float: none !important; - margin-left: 1rem; - } -} - -.sensor-history-widget { - .sensor-selection, - .sensor-history-time-selection, - .sensor-history-location-selection { - margin-bottom: 1rem !important; - } - .row { - margin-bottom: 0 !important; - } - label { - margin-bottom: 5px; - } - .bp5-popover-wrapper { - float: right; - } -} - -.add-sensor-reading-menu { - width: 300px; - .green { - position: absolute; - bottom: 1rem; - right: 1rem; - } - .reading-location, - .add-reading-value-form { - margin-top: 1rem; - } -} - -.sensor-selection { - margin-bottom: 4rem !important; -} - -.sensor-readings-plot { - max-height: 300px; - stroke: $black; - font-size: 60px; - font-weight: 100; -} - -.sensor-history-table { - font-size: 1.2rem; - th, - td { - width: 1%; - } - tr { - color: $black; - &.previous { - color: $medium_gray; - } - &.selected { - background: $gray; - } - } - .sensor-history-table-contents { - max-height: 20rem; - overflow-y: auto; - } -} - -.sensor-history-footer { - display: flex; - justify-content: space-between; - .date { - span { - white-space: nowrap; - } - label { - margin-right: 0.5rem; - } - } - .location { - display: flex; - label { - margin-left: 1rem; - margin-right: 0.5rem; - } - } -} - -.full-width { - width: 100%; -} - -fieldset { - border: none; - legend { - background: transparent; - } -} - -.move-tab, -.peripherals-tab, -.webcams-tab { - margin-top: 1rem; -} - -.webcams-tab { - margin-top: 2rem; -} - -.webcam-stream-unavailable { - position: relative; - width: 100%; -} - -.webcam-stream-unavailable img { - width: 100%; - max-width: 100%; - opacity: 0.40; -} - -.webcam-stream-valid { - img, iframe { - display: flex; - margin: auto; - max-height: 650px; - } - img { - max-width: 100%; - min-height: 100px; - } - iframe { - width: 100%; - border: none; - min-height: 300px; - } -} - -.webcam-stream-unavailable p { - position: absolute; - top: 50%; - left: 50%; - width: 100%; - transform: translate(-50%, -50%); - vertical-align: middle; - text-align: center; - font-size: 1.5rem; -} - -.webcam-rows { - height: 40rem; - overflow-y: scroll; - overflow-x: hidden; - padding-left: 3rem; -} - -.webcam-buttons { - position: relative; -} - -.webcam-buttons, -.peripherals-buttons { - display: flex; - margin-top: 1rem; - .bp5-popover-wrapper { - margin-top: 0.5rem; - } - p { - width: 100%; - margin-left: 1rem; - margin-top: 0.5rem; - overflow: hidden; - text-overflow: ellipsis; - } - button { - margin: 0.25rem; - margin-bottom: 1rem; - } -} - -h3 { - display: inline-block; - margin-top: 0; - font-size: 2.2rem; -} - -h4 { - line-height: 1.1rem; - font-size: 1.8rem; - color: $darker_gray; -} - -hr { - border-top: 1px solid rgba(16, 22, 26, 0.15); -} - -.e-stop { - white-space: nowrap; - &.yellow { - animation: bright-flash 1s infinite alternate; - } -} - -.e-stop-btn { - float: right; - margin-top: 1.5rem; - margin-left: 0.5rem; - border-radius: 1rem; - .e-stop { - margin: 0; - box-shadow: none !important; - height: 2.9rem; - font-size: 1.1rem !important; - &.pseudo-disabled { - box-shadow: none !important; - } - &:hover { - box-shadow: 0 0 10px $translucent5, 0 0 5px inset rgba(255, 255, 255, 0.2) !important; - &.red { - color: $white; - } - &.yellow { - color: $black; - } - } - } - &.hard { - border-radius: 5px; - } -} - -a { - text-decoration: none !important; - outline: none !important; - cursor: pointer !important; - color: $dark_gray; - &:link { - font-weight: 500; - color: $dark_gray; - } - &:visited { - color: $dark_gray; - } - &:hover { - font-weight: bold; - color: $dark_gray; - } - &:active { - color: $dark_gray; - } -} - -button, -select, -summary, -input { - outline: none !important; -} - -input:disabled { - background: rgba(0, 0, 0, 0.1) !important; -} - -ul { - list-style-type: none !important; - padding: 0; -} - -.coming-soon { - position: relative; - opacity: 0.50; - width: 100%; - height: 100%; -} - -.coming-soon:after { - content: "Coming Soon!"; - position: absolute; - top: 25%; - left: 0; - background-color: $red; - width: 100%; - vertical-align: middle; - text-align: center; - font-size: 2.5rem; -} - -.unavailable { - display: inline-block; - position: relative; - z-index: 10; - opacity: 0.40; - * { - pointer-events: none; - } - &.banner { - &:after { - content: "Not available when device is offline."; - position: absolute; - top: 25%; - left: -2.5%; - z-index: 10; - width: 105%; - padding: 0.5rem; - background-color: $dark_gray; - opacity: 0.90; - color: $off_white; - font-size: 1.8rem; - vertical-align: middle; - text-align: center; - } - } -} - -.button-group { - display: inline-block; - margin: 1.5rem 0 -1rem; - width: 100%; - button:not(:first-of-type) { - margin-right: 1rem; - } - .run-btn { - margin-right: 0.5rem; - } - .fb-button { - margin: 0; - } - .settings-menu-button { - margin-top: 0.15rem; - margin-right: 0.5rem; - .bp5-popover-wrapper { - margin-left: 0; - } - } - .fa { - float: right; - } - .fa-code { - font-weight: bold; - color: $gray; - &.enabled { - color: $dark_gray; - } - } - .inactive { - color: $gray; - } - .fa-eye-slash { - color: $gray; - } - .color-picker { - float: right; - vertical-align: middle; - .saucer { - height: 1.5rem; - width: 1.5rem; - } - } -} - -.sequence-description-wrapper, -.button-group, -.panel-header-icon-group { - .fa-spinner { - background: none !important; - box-shadow: none !important; - } -} - -.fb-button-popover-wrapper { - float: right; - margin-right: 1rem; -} - -.parameter-assignment-menu { - .test-button-div { - text-align: center; - } - .fb-button { - float: none; - } - .locals-list { - margin-top: 1rem; - margin-left: 1rem; - margin-right: 1rem; - } -} - -.parameter-assignment-menu-popover { - max-width: 400px; -} - -.note { - margin-top: 1rem; - font-style: italic; - font-size: 1.2rem; -} - -.bot-position-rows { - .missed-step-indicator-wrapper { - position: absolute; - top: -1.5rem; - left: 1.5rem; - right: 1.5rem; - height: 1rem; - .missed-step-details { - min-width: 9rem; - padding-bottom: 1rem; - table tr, td { - padding: unset; - } - label, p { - color: $white; - text-align: right; - font-style: normal; - } - } - .bp5-popover-wrapper { - .bp5-popover-target { - display: block; - } - } - .bp5-popover { - .bp5-popover-arrow { - svg { - transform: rotate(-90deg) translate(1px) !important; - .bp5-popover-arrow-fill { - fill: $dark_gray; - } - } - } - .bp5-popover-content { - background: $dark_gray; - } - } - .missed-step-indicator { - position: absolute; - top: 0.2rem; - height: 0.75rem; - width: 100%; - background: $white; - box-shadow: 0 1px 1px rgba(0, 0, 0, .05); - cursor: pointer; - .instant { - position: absolute; - height: 100%; - opacity: 0.5; - &.yellow { - opacity: 0.75; - } - } - .peak { - position: absolute; - height: 100%; - width: 2px; - opacity: 0.5; - &.yellow { - opacity: 0.75; - } - } - } - } -} - -.move-settings-menu { - label { - margin-top: 7px; - } - p { - margin-top: 0.7rem; - font-size: 1.4rem; - color: $medium_gray; - font-weight: 400; - } -} - -.bot-position-rows { - label { - margin-top: 0 !important; - } - .axis-titles { - margin-bottom: 1rem; - .bp5-popover-wrapper { - display: inline; - float: right; - } - } - p { - text-align: center; - font-style: italic; - } - .axis-info { - .index-1 { - z-index: 1; - } - .index-2 { - z-index: 2; - } - .index-3 { - z-index: 3; - } - } - .fa-ellipsis-v { - font-size: 1.2rem; - width: 1rem; - text-align: center; - } - .axis-actions { - width: 10rem; - .fb-button { - width: 100%; - float: none; - margin-bottom: 0.25rem; - } - a { - display: block; - padding: 1rem 0.5rem 0.75rem 0.5rem; - text-decoration: none !important; - font-size: 1.2rem; - .fa-external-link { - margin-top: 0.25rem; - float: right; - } - &:hover { - .fa-external-link { - font-weight: bold; - } - } - } - } -} - -.fbos-metric-history { - .fbos-metric-history-table-wrapper { - overflow-y: scroll; - height: 25rem; - padding-bottom: 1rem; - thead { - position: sticky; - top: 0; - z-index: 3; - background: $white; - } - .saucer { - margin: 0 auto; - height: 1.5rem; - width: 1.5rem; - cursor: default; - } - } -} - -.fbos-metric-history-plot-border, -.motor-position-plot-border { - text { - text-anchor: middle; - dominant-baseline: middle; - } -} - -.fbos-metric-history-plot-border { - margin: 1rem; - width: calc(100% - 2rem) !important; - border-radius: 5px; - box-shadow: 0 0 10px $light_gray; - animation: adjust-width 0.01s linear forwards; - text { - font-size: 0.6rem; - } -} - -.motor-position-plot-border { - box-shadow: 0 0 10px $light_gray; - border-radius: 5px; - text { - font-size: 0.4rem; - } -} - -.controls-popup, -.controls-popup-menu-outer { - position: fixed; - bottom: 3rem; - right: 1rem; - z-index: 3; - background: $dark_gray; - border-radius: 3rem; - height: 6rem; - width: 6rem; -} - -.controls-popup { - color: $off_white; - @media screen and (max-width: $mobile_max_width) { - &.panel-open { - display: none; - } - } - img { - position: fixed; - bottom: 3rem; - z-index: 4; - width: 6rem; - height: 6rem; - border-radius: 3rem; - padding: 18px 20px; - font-size: 2.4rem; - transition: all 0.25s ease-in-out; - filter: invert(1); - &:hover { - background-color: rgba(0, 0, 0, 0.2); - } - } - .move-amount-wrapper, - .jog-table { - display: none; - } - &.open { - img { - transform: rotate(-135deg); - &:hover { - background-color: rgba(0, 0, 0, 0); - } - } - .controls-popup-menu-outer { - transition: all 0.1s 0s ease-in-out; - width: 36rem; - height: 14rem; - padding: 0.6rem 5rem 0rem 0rem; - } - .controls-popup-menu-inner { - transition-delay: 0.25s !important; - opacity: 1; - } - .jog-table { - display: block; - margin: 0; - float: right; - td { - padding: 0; - } - } - .move-amount-wrapper { - display: inline-block; - width: 100%; - box-shadow: none; - margin-left: 2rem; - padding-top: 0.5rem; - .move-amount { - width: 18%; - height: 2rem; - } - } - } - .filter-search { - display: inline-block; - width: 9rem; - padding: 1rem; - margin-left: 1rem; - } - .arrow-button { - margin: 5px; - box-shadow: none !important; - &.pseudo-disabled { - box-shadow: none !important; - } - } -} - -.controls-popup-menu-inner { - border-radius: 2rem; - transition: all 0.1s 0s ease-in-out; - opacity: 0; -} - -.controls-popup-menu-outer { - transition: all 0.1s ease-in-out; - transition-delay: 0.2s !important; -} - -@keyframes page-transition { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -.empty-state-graphic { - display: flex; - margin: auto; - margin-top: 25%; - width: 50%; -} - -.empty-state { - text-align: center; - h5 { - font-weight: bold; - } - &.plants { - p, - h5, - a { - color: $panel_green; - } - } - &.curves { - .empty-state-graphic { - filter: grayscale(1); - } - } - &.gardens { - p, - h5, - a { - color: $panel_navy; - } - .empty-state-graphic { - filter: hue-rotate(135deg) brightness(0.5); - color: $panel_navy; - } - } - &.events { - p, - h5, - a { - color: $panel_yellow; - } - } - &.farmware { - p, - h5, - a { - color: $panel_gray; - } - } - &.peripherals { - img { - margin-top: 0; - max-width: 20rem; - } - p, - h5, - a { - color: $dark_gray; - } - } - &.sensors { - img { - margin-top: 0; - max-width: 20rem; - filter: grayscale(1); - padding: 1rem; - } - p, - h5, - a { - color: $dark_gray; - } - } - &.points { - p, - h5, - a { - color: $panel_teal; - } - } - &.tools { - p, - h5, - a { - color: $panel_gray; - } - } - &.groups { - p, - h5, - a { - color: $panel_blue; - } - } - &.weeds { - p, - h5, - a { - color: $panel_red; - } - } - &.zones { - p, - h5, - a { - color: $panel_brown; - } - } - &.location { - img { - filter: grayscale(1); - } - p, - h5, - a { - color: $dark_gray; - } - } -} - -.farmware-info { - margin-top: 1rem; -} - -.farmware-list-items { - margin-left: -30px; - margin-right: -20px; - padding: 0.5rem; - padding-left: 1.5rem; - padding-top: 0.75rem; - cursor: pointer; - label { - cursor: pointer; - } - &:hover { - background: $medium_light_gray; - p { - font-weight: bold; - } - } -} - -.farmware-url { - font-size: 1rem; - width: 100px; - word-wrap: break-word; -} - -.settings-menu-button { - float: right; - .fa-gear { - margin-top: -0.25rem; - color: $dark_gray; - } -} - -.publish-button { - float: right; -} - -.sequence-settings-menu { - .bp5-popover-wrapper { - display: inline; - margin-right: 1rem; - margin-left: 1rem; - } - .fb-button { - margin-top: 0; - } -} - -.sequence-publish-menu, -.sequence-share-menu { - max-width: 300px; - button { - display: block; - margin: auto; - float: none; - margin-top: 1rem; - } -} - -.sequence-publish-menu { - .sequence-publish-message { - text-align: left; - p { - padding-bottom: 1rem; - line-height: 1.5rem; - } - a { - display: inline; - text-decoration: underline !important; - } - ul { - list-style-type: "- " !important; - padding: revert; - padding-left: 1rem; - color: $dark_gray; - font-size: 1.1rem; - line-height: 1.5rem; - } - label { - font-size: 1rem; - margin-right: 1rem; - } - input { - width: 50%; - } - .republish-warning { - display: flex; - margin: 0.75rem -10px 1rem -10px; - padding: 1rem 1rem 0 1rem; - background: color.adjust($orange, $alpha: -0.6); - i { - margin-right: 1rem; - margin-top: 0.25rem; - } - p { - font-weight: bold; - padding-bottom: 0.5rem; - } - } - } - button { - margin: 0; - margin-top: 1rem; - .fa-spinner { - margin-left: 0.5rem; - } - } -} - -.sequence-share-menu { - text-align: center; - a { - display: block; - white-space: normal; - word-wrap: break-word; - word-break: break-word; - overflow: hidden; - text-overflow: ellipsis; - max-width: 100%; - &:hover { - color: $black; - font-weight: normal; - } - } - .versions-table { - margin-top: 2rem; - text-align: left; - .bp5-popover-wrapper { - display: inline-block; - margin-left: 1rem; - } - .fb-button.gray { - margin: 0; - float: right; - } - .row { - margin-top: 0.5rem; - } - p { - width: max-content; - padding: 5px; - border-radius: 3px; - color: $white; - background: $panel_blue; - } - .fa-link { - float: right; - margin-top: 0.75rem; - } - } - .fb-button.white { - color: $darkest_red; - text-transform: none; - font-weight: normal; - background: $white; - .fa-spinner { - margin-left: 0.5rem; - } - &:hover { - background: $off_white; - } - } -} - -.logs-page { - .row { - @media screen and (max-width: 974px) { - margin-left: 0; - margin-right: 0; - } - } - .bp5-popover-target { - float: right; - } - @media screen and (max-width: 1075px) { - padding-left: 15px !important; - } - @media screen and (max-width: 974px) { - padding-left: 0 !important; - } - .fa-2x { - float: right; - font-size: 1rem; - color: $dark_gray; - } - .fa-ban { - color: $medium_gray; - } - details { - display: inline; - margin-top: 1rem; - } -} - -.logs-table-wrapper { - border: none; - .fa-trash { - display: none; - } - tr { - vertical-align: top; - &:hover { - background: #eee; - .fa-trash { - display: inline; - color: $darkest_red; - margin-left: 6px; - padding: 0.5rem; - line-height: 2rem; - } - .log-verbosity-saucer { - display: none; - } - } - } - .fa-filter { - border: 1px black solid; - border-radius: 50%; - padding: 0.25rem; - width: 2rem; - height: 2rem; - text-align: center; - line-height: 1.3rem; - margin-left: 5px; - } - .markdown { - p { - margin-top: 0; - } - a { - text-decoration: underline !important; - &:hover { - font-weight: normal; - } - } - h1, - h2, - h3, - h4, - h5, - h6 { - margin: 0; - font-size: 1.4rem; - font-weight: bold; - } - } - .notice { - font-style: italic; - text-align: center; - font-size: 1.4rem; - padding: 1rem; - } -} - -.logs-filter-menu { - position: relative; - padding-right: 1rem; - fieldset { - display: flex; - label { - width: 100%; - padding-right: 1rem; - } - } - .lines { - padding-bottom: 2rem; - } - .line { - position: absolute; - width: 0.5rem; - height: 90%; - border-right: 1px $light_gray solid; - .line-label { - position: absolute; - background: $white; - } - } -} - -.logs-filter-menu, -.logs-settings-menu { - label { - margin-top: 7px; - } - .saucer { - float: left; - margin-right: 10px; - } - .bp5-popover-wrapper { - display: inline-block; - margin-left: 1rem; - margin-top: 0.5rem; - } - .log-stream-link { - margin-top: 1rem; - text-align: center; - .fa-external-link { - margin-left: 0.5rem; - } - } - .delete-all { - text-align: center; - button { - float: none; - } - } -} - -.logs-tab { - .thin-search-wrapper { - display: inline-block; - width: 94%; - background: $white; - } - .logs-settings-menu-button { - display: inline-block; - vertical-align: middle; - .fa-gear { - color: $dark_gray; - } - } -} - -.logs-table { - display: block; - overflow: scroll; - max-height: 42rem; - background: $white; - .log-verbosity-saucer, .saucer { - float: left; - margin-right: 5px; - margin-left: 3px; - } - button { - float: none; - } - thead { - width: 100%; - } - thead, - th { - position: sticky; - top: 0; - z-index: 999; - background: $white; - } - td { - color: $dark_gray !important; - word-break: break-word; - .markdown { - p { - display: block; - color: $dark_gray; - font: inherit; - font-size: inherit; - text-overflow: inherit; - overflow: inherit; - width: inherit; - white-space: inherit; - } - } - } - td:nth-child(1), - td:nth-child(3), - td:nth-child(4) { - white-space: nowrap; - } -} - -.link-to-logs { - margin-top: 2rem; - margin-bottom: 4rem; - text-align: center; - font-style: italic; -} - -.map-size-setting { - margin-top: 1rem; -} - -.release-notes-button { - font-weight: bold; - cursor: pointer; -} - -.release-notes { - max-width: 250px; - h1 { - font-weight: 300; - font-size: 1.4rem; - line-height: 2rem; - margin-top: 0; - } - li { - font-weight: 300; - font-size: 1.1rem; - line-height: 1.75rem; - margin-bottom: 1rem; - } - p { - display: block; - color: $dark_gray; - text-overflow: inherit; - overflow: inherit; - width: inherit; - white-space: inherit; - } -} - -.sensors-widget { - p { - margin-top: 0.75rem; - } - .sensor-list { - min-width: 33rem; - .fb-button { - white-space: unset; - } - } - .sensor-reading-display { - border-style: solid; - border-color: $dark_gray; - border-width: 0.1px; - height: 2rem; - width: 100%; - margin-top: 0.5rem; - &.moisture-sensor { - background: linear-gradient(to right, rgba($blue, 0) 20%, $blue 80%, rgba($blue, 0) 85%); - } - &.digital { - .indicator { - text-align: center; - } - } - .indicator { - position: relative; - background: $dark_gray; - height: 2rem; - span { - position: relative; - top: -0.1rem; - font-size: 1.3rem; - } - } - } -} - -.sensors-widget, -.webcam-widget, -.peripherals-widget { - .del-button { - margin-top: 0.5rem; - } -} - -.electronics-box-3d-model { - margin-bottom: 1rem; - .led-label, - .btn-label { - display: block; - margin: auto; - width: max-content; - color: $off_white; - font-size: 1rem; - background: $dark_gray; - padding: 0.25rem 0.5rem; - border-radius: 0.5rem; - white-space: normal; - max-height: 3.4rem; - overflow-y: hidden; - font-weight: 700; - line-height: 1; - text-align: center; - user-select: none; - &.hovered { - background: $black; - } - &.unbound { - background: $placeholder_gray; - } - } - .led-label { - max-width: 7rem; - } - .btn-label { - max-width: 5.8rem; - } - .filter-search { - max-width: 5.5rem; - .fa-caret-down { - bottom: -0.5rem; - right: 0.25rem; - color: $off_white; - } - button { - padding: 0.25rem; - background: $dark_gray !important; - border-radius: 0.5rem; - height: max-content !important; - min-height: 0; - &:hover { - background: $dark_gray !important; - } - } - span { - color: $off_white; - white-space: normal !important; - text-overflow: revert !important; - font-weight: 700; - font-size: 1rem; - text-align: center; - margin-right: 0.5rem; - } - } - div { - z-index: 0 !important; - } - canvas { - height: 23rem; - } -} - -.webcam-widget { - .no-flipper-image-container { - background: none !important; - width: 100% !important; - height: auto !important; - border-radius: 5px; - img { - max-width: 100% !important; - } - } - .image-flipper { - p, - button { - color: $dark_gray !important; - } - } -} - -.peripheral-list { - label { - margin-top: 0 !important; - } - .slider-container { - padding-left: 0.5rem; - padding-right: 1rem; - .bp5-slider { - min-width: 100%; - } - } -} - -.peripheral-form, -.peripheral-list { - padding-top: 1rem; -} - -.box-top-2d-wrapper { - margin-top: 2rem; - .box-top-leds, - .box-top-buttons { - display: grid; - text-align: center; - } - .box-top-buttons { - grid-template-columns: repeat(5, minmax(0, 1fr)); - } - .box-top-leds { - margin: auto; - width: 80%; - grid-template-columns: repeat(4, minmax(0, 1fr)); - } - .fast-blink { - animation: fill-blink 0.2s linear infinite; - } - .slow-blink { - animation: fill-blink 2s linear infinite; - } -} - -.farmware-step-input-fields { - label { - padding-top: 1rem; - } - fieldset { - width: 95%; - padding-left: 1rem; - padding-right: 1rem; - } - button { - position: absolute; - } -} - -.update-resource-step { - &.step-content { - .row { - margin: 0; - } - } - .update-resource-step-resource { - margin-bottom: 1rem; - } - .update-resource-pair { - margin-top: 1rem; - margin-right: -2rem; - &.first { - margin-top: 0; - } - 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; -} - -.write-pin-step { - .filter-search { - margin-bottom: 1rem; - } - .slider-container { - padding-left: 0.5rem; - padding-right: 1rem; - .bp5-slider { - min-width: 100%; - } - } -} - -.read-pin-step { - &.step-content { - div[class*=col-] { - padding-left: 1rem; - padding-right: 1rem; - } - } -} - -.computed-move-step { - &.step-content { - padding-left: 2.5rem !important; - padding-right: 2.5rem !important; - input { - height: 3rem; - &:disabled { - background: $light_gray !important; - } - } - .help-icon { - display: inline; - margin-left: 0.5rem; - } - div[class*=col-] { - padding: 0; - } - .row { - margin-top: 0.25rem; - margin-bottom: 0.25rem; - } - label { - padding-top: 1rem; - &.safe-z { - padding-top: 0.65rem; - } - } - .expandable-header { - text-align: center; - } - span { - color: $dark_gray; - } - .input { - position: relative; - border: 3px solid $transparent; - &.lua { - border-color: $yellow; - &:after { - content: "lua"; - position: absolute; - top: -0.25rem; - right: -0.3rem; - color: $dark_gray; - background: $yellow; - padding: 0.2rem 0.4rem; - font-size: 0.65rem; - font-weight: bold; - text-transform: uppercase; - } - } - } - .filter-search, - .fb-checkbox { - border: 3px solid $transparent; - } - .bp5-checkbox { - padding-top: 0.75rem; - margin-left: 0.25rem; - } - } -} - -.lua-input { - .lua-editor { - height: 20rem; - &.full { - height: 40rem; - } - &.expanded { - height: 70vh; - } - } - .char-limit { - padding: 0.5rem; - } - textarea { - height: 100% !important; - font-family: monospace; - pointer-events: revert !important; - font-size: 1.4rem; - padding: 0 3rem 0 2.6rem; - line-height: 1.9rem; - } -} - -.modified { - box-shadow: 0 0 0px 3px $yellow !important; - &.bp5-slider { - box-shadow: none !important; - .bp5-slider-handle { - box-shadow: 0 0 0px 3px $yellow; - } - } -} - -.modified-start { - box-shadow: none !important; - .bp5-start { - box-shadow: 0 0 0px 3px $yellow; - } -} - -.modified-end { - box-shadow: none !important; - .bp5-end { - box-shadow: 0 0 0px 3px $yellow; - } -} - -.input { - .fa-undo { - position: absolute; - top: 1rem; - right: 0.25rem; - font-size: 1rem; - } -} - -.checkbox-row { - display: flex; - margin-top: 1rem; - label { - padding: 0; - line-height: 2rem; - margin-bottom: 0; - } - .fb-checkbox { - display: inline; - position: relative; - margin-right: 1rem; - } - .bp5-popover-wrapper, - .bp5-popover-target { - margin-left: 1rem; - } -} - -.farmware-input-group { - position: relative; - margin-left: 3rem; - .fa-times-circle, - .fa-refresh { - position: absolute; - top: 0.85rem; - color: $light_gray; - &:hover { - color: $dark_gray; - } - } - .fa-times-circle { - right: 1rem; - } - .fa-refresh { - right: 3rem; - } - &.dropdown { - .filter-search { - border: 3px solid $transparent; - } - .fa-times-circle { - right: 3rem; - } - .fa-refresh { - right: 5rem; - } - } -} - -.tour-list { - margin: auto; - max-width: 300px; - margin-top: 1rem; - .fb-button { - margin: 0; - } -} - -.location-form { - .row { - margin-bottom: 1rem; - margin-top: 0.5rem; - } -} - -.default-value-form { - position: relative; -} - -.custom-coordinate-form { - margin-left: -3rem; - margin-right: -1rem; - .x, - .y, - .z { - padding-left: 2.5rem; - margin-right: -0.25rem; - } -} - -.error-with-button { - margin-top: 1rem; - background: $pink; - border: 1px solid $red; - border-radius: 5px; - label, - p { - margin: 0.5rem; - color: $red; - } - button { - margin: 0.5rem !important; - } -} - -.status-icon { - &.ok { - color: $green; - } - &.no { - color: $red; - } - &.unknown { - color: $orange; - } -} - -.status-details-target { - margin-left: -1rem; - margin-top: 0.25rem; -} - -.status-details { - max-width: 30rem; - button { - float: none !important; - } - .bp5-popover-wrapper { - display: inline; - margin-left: 0.5rem; - font-size: 1.3rem; - } -} - -.problem-alert { - position: relative; - margin: 1rem; - margin-bottom: 3rem; - padding: 2rem; - border-radius: 4px; - box-shadow: 0px 2px 5px $medium_gray; - background: $off_white; - .fa-exclamation-triangle, - .fa-check-square, - .fa-info-circle { - position: absolute; - top: 2rem; - left: 2rem; - font-size: 1.6rem; - padding-right: 0.5rem; - } - .fa-exclamation-triangle { - color: $orange; - } - .fa-check-square { - color: $green; - } - .fa-info-circle { - color: $blue; - } - &.bulletin-alert { - img, iframe { - margin: 1rem auto; - width: 100%; - border-radius: 5px; - } - } - .problem-alert-title { - margin-top: 0.15rem; - margin-bottom: 1rem; - margin-left: 2rem; - margin-right: 2rem; - line-height: 1rem; - h3 { - display: inline; - color: $dark_gray; - font-size: 1.5rem; - font-weight: bold; - } - p { - display: inline; - color: $medium_gray; - font-size: 1.2rem; - white-space: nowrap; - margin-left: 0.5rem; - } - } - .fa-times { - position: absolute; - top: 2rem; - right: 2rem; - color: $medium_light_gray; - &:hover { - color: $dark_gray; - } - } - .problem-alert-content { - .markdown { - p { - display: block; - color: $dark_gray; - text-overflow: inherit; - overflow: inherit; - width: inherit; - white-space: inherit; - } - ul { - list-style-type: disc !important; - padding-inline-start: 40px; - } - } - p { - margin-bottom: 0.75rem !important; - font-size: 1.4rem; - line-height: 2rem; - } - label { - margin-top: 0.5rem; - } - .row { - margin-top: 2rem; - } - .tour-list { - margin: 0; - label { - max-width: 57%; - } - } - .link-button { - color: $off_white !important; - font-weight: bold !important; - float: none; - margin-bottom: 2rem; - } - .fb-button { - margin-top: 0.5rem; - } - a { - text-decoration: none !important; - outline: none !important; - cursor: pointer !important; - color: $dark_gray; - &:link { - font-weight: 500; - color: $dark_gray; - } - &:visited { - color: $dark_gray; - } - &:hover { - font-weight: bold; - color: $dark_gray; - } - &:active { - color: $dark_gray; - } - } - } - .documentation-card { - .fa-question { - cursor: default !important; - margin-left: 0.25rem; - margin-right: 0.25rem; - } - } -} - -.firmware-alerts { - max-width: 600px; -} - -.firmware-hardware-choice-table { - margin: 2rem; - margin-top: 1rem; - width: 93%; - border: 1px solid $gray; - font-size: 1.2rem; - th { - background: $light_gray; - font-weight: normal; - } - td { - background: $off_white; - color: $medium_gray; - } - code { - background: $light_gray; - color: $dark_gray; - } -} - -.firmware-path-selection { - .manual-selection { - margin: 1rem -15px; - p { - padding: 0rem 1.5rem 1rem; - } - } -} - -.move-absolute-form { - display: flex; - .location-form, - .expandable-header { - padding-left: 15px; - } - .expandable-header { - padding-right: 15px; - } - .input-line { - flex: auto; - } - .more-options { - min-width: 110px; - margin-top: 0.5rem; - } - .custom-coordinate-form { - margin-left: -2.5rem; - margin-right: -11.25rem; - } -} - -textarea { - width: 100%; - border: 0; - padding: 6px 8px; - box-shadow: 0 0 10px #ddd; - outline: none!important; - resize: vertical; -} - -textarea:focus { - box-shadow: 0 0 10px rgba(0,0,0,.2); -} - -.sort-option-bar { - cursor: pointer; - margin-top: 0.25rem; - margin-bottom: 0.25rem; - border: 2px solid color.adjust($panel_light_blue, $lightness: -30%); - border-radius: 5px; - &:hover, &.selected { - .sort-path-info-bar { - background: color.adjust($panel_light_blue, $lightness: -40%); - } - } - &:hover { - border: 2px solid color.adjust($panel_light_blue, $lightness: -40%); - } - &.selected { - border: 2px solid $medium_gray; - } - .sort-path-info-bar { - background: color.adjust($panel_light_blue, $lightness: -30%); - font-size: 1.2rem; - padding-left: 0.5rem; - white-space: nowrap; - line-height: 2.5rem; - } -} - -.os-download-page { - table { - a { - text-transform: none; - } - } -} - -.featured-sequences-page, -.os-download-page { - text-align: center; - .all-content-wrapper { - padding-top: 0 !important; - min-height: 30rem; - } - h1 { - margin-top: 5rem; - font-size: 2rem !important; - text-shadow: 0 0 5px rgba(0, 0, 0, 0.1), 0 0 25px rgba(0, 0, 0, 0.1) !important; - } - p { - margin: auto; - width: 70%; - color: $off_white; - } - a { - white-space: nowrap; - font-weight: bold !important; - color: $off_white !important; - &:hover { - color: $white !important; - } - &:visited { - color: $off_white; - } - &:link { - color: $off_white; - } - &:active { - color: $white !important; - } - } - table { - margin: auto; - margin-top: 3rem; - width: 93%; - font-size: 1.2rem; - color: $off_white; - text-align: left; - thead { - border-bottom: 2px solid $off_white; - } - tr:not(:last-child) { - border-bottom: 1px solid $gray; - } - td { - padding-top: 1rem; - padding-bottom: 1rem; - white-space: pre-wrap; - span { - display: block; - white-space: nowrap; - } - } - } - button { - cursor: pointer; - } - .wizard-btn { - margin: 1rem; - float: none; - color: $white; - margin-top: 7rem; - } - .os-download-wizard { - position: relative; - margin-top: 2rem; - .transparent-button { - margin: 1rem; - float: none; - color: $white; - height: 5rem; - width: 15rem; - } - .back { - display: block; - position: absolute; - top: -1rem; - float: left; - height: unset; - width: unset; - } - .start { - height: unset; - width: unset; - } - p { - font-size: 1.4rem; - margin-bottom: 1rem !important; - } - .os-download-wizard-note { - font-size: 1.4rem; - font-weight: bold; - } - .buttons { - display: flex; - button { - width: 75%; - border-radius: 0; - padding: 0; - border: 3px solid $white; - width: 75%; - img { - margin: 0; - width: 100%; - margin-top: -1rem; - } - &:hover { - border-color: $off_white; - .btn-text { - background: color.adjust($cyan, $lightness: -10%); - } - } - } - } - .download-wizard-button { - .os-wizard-content-button { - border-radius: 8px; - .btn-text { - background: $cyan; - border-radius: 5px 5px 0 0; - .btn-title { - font-family: "Inknut Antiqua"; - font-weight: bold; - margin-bottom: 0.5rem !important; - padding-top: 0.75rem; - font-size: 1.7rem; - } - .os-download-wizard-btn-label { - width: 75%; - font-size: 1.2rem; - padding-bottom: 0.5rem; - line-height: 1.5rem; - } - } - img { - border-radius: 0 0 5px 5px; - } - &.white { - background: $white !important; - border-color: $off_white; - .btn-text { - background: $off_white; - .btn-title { - color: $dark_gray; - } - } - &:hover { - background: $off_white !important; - border-color: $white; - .btn-text { - background: $white; - } - } - } - &.black { - background: $white !important; - border-color: $dark_gray; - .btn-text { - background: $dark_gray; - .btn-title { - color: $white; - } - } - &:hover { - background: $off_white !important; - border-color: $darker_gray; - .btn-text { - background: $darker_gray; - } - } - } - } - } - .download-link { - display: inline-block; - a { - text-transform: none; - } - } - .buttons-with-image { - display: flex; - .buttons { - margin: auto; - margin-right: 0; - } - img { - margin: auto; - margin-left: 3rem; - width: 12rem; - border-radius: 5px; - border: 3px solid $lighter_gray; - } - } - } -} - -.static-page { - &.os-download-page { - .os-download-description { - width: 80%; - } - p { - font-size: 1.5rem; - line-height: 2rem; - } - h1 { - margin-top: 0; - font-family: "Inknut Antiqua" !important; - font-weight: 600 !important; - } - } -} - -.featured-sequences-page { - summary { - color: $light_gray; - cursor: pointer; - } - .sequence-description { - margin-left: -1.5rem; - margin-right: -5rem; - } - .markdown { - p { - color: $white !important; - } - } - details { - max-width: calc(min(70vw, 370px)); - } - summary { - margin-bottom: 0.5rem; - } - h1, - h2 { - margin-bottom: 0; - } - li, - p { - white-space: pre-wrap; - } -} - -.setting { - position: relative; - &.section { - .bp5-collapse { - padding-top: 1rem; - } - } - &.highlight { - background-color: $light_yellow; - box-shadow: 0px 0px 7px 4px $light_yellow; - } - &.unhighlight { - transition: background-color 10s linear, box-shadow 10s linear; - background-color: transparent; - box-shadow: none; - } - &.advanced { - border-left: 3px solid $blue; - padding-left: 0.7rem; - margin-left: -1rem; - } - .fa-anchor { - position: absolute; - top: 1.1rem; - left: -0.8rem; - color: $gray; - font-size: 0.8rem; - visibility: hidden; - &.hovered { - visibility: visible; - } - &.section { - position: absolute !important; - top: 0.1rem; - } - } - .landing-page-setting { - input { - margin-top: 1rem; - } - } - .activity-beep-setting { - margin-left: 0; - margin-right: 1.5rem !important; - .bp5-slider { - width: 0; - margin-left: 1rem; - } - } -} - -.beacon-transition { - transition: border 1s, border-radius 1s, background 1s; -} - -.beacon { - &.hard { - box-shadow: 0 0 0 4px $yellow !important; - } - &.soft { - background: #ffe53e52 !important; - } -} - -.tour-toast { - cursor: default !important; - .toast-title, - .toast-message { - color: $white !important; - i { - margin-left: 0.5rem; - margin-right: 0.5rem; - } - .extra-content { - margin-top: 1rem; - } - } - .toast-loader { - display: flex; - width: unset; - height: unset; - transform: none; - .previous, - .next, - .exit { - padding: 0.5rem; - &:hover { - font-weight: bold; - } - } - .previous, - .next { - margin-right: 0.5rem; - &.disabled { - pointer-events: none; - color: $dark_gray; - } - } - } - .toast-message { - margin-top: 1.5rem; - } - .toast-title, - .message-contents { - transition: height 0.4s, opacity 0.2s; - } - .message-contents { - &.height-hidden { - position: absolute; - visibility: hidden; - padding-right: 2rem; - } - } - .progress-indicator { - position: absolute; - left: 0; - bottom: 0; - height: 0.5rem; - background: $white; - border-bottom-right-radius: 0; - border-bottom-left-radius: 5px; - transition: width 0.4s 0.2s, border-bottom-right-radius 0.4s 0.2s; - } -} - -.widget-body-tooltips { - display: flex; - padding-right: 0.5rem; - .bp5-popover-wrapper { - line-height: 3rem; - } -} - -.z-param-label { - margin-top: 0.2rem; - padding-right: 0; - label { - font-size: 1.2rem !important; - } -} - -.read-only-icon { - margin: 9px 0px 0px 9px; - float: right; - box-sizing: inherit; - .fa-pencil { - color: $white; - } -} diff --git a/frontend/css/buttons.scss b/frontend/css/global/buttons.scss similarity index 75% rename from frontend/css/buttons.scss rename to frontend/css/global/buttons.scss index 59712ebe44..8134154e36 100644 --- a/frontend/css/buttons.scss +++ b/frontend/css/global/buttons.scss @@ -1,3 +1,6 @@ +@use "../variables" as *; +@use "sass:color"; + .fb-button { margin: 0.25em 0.5em; float: right; @@ -81,6 +84,14 @@ &.pink { background-color: $pink; } + &.clear { + background-color: transparent; + box-shadow: inset 0 0 0 1.5px var(--text-color); + color: var(--text-color); + &:hover { + box-shadow: inset 0 0 0 1.5px var(--text-color),0 0 5px var(--secondary-bg),inset 0 0 5px var(--secondary-bg); + } + } &.panel-green { background-color: $panel_green; } @@ -132,10 +143,8 @@ } .block { - height: 3.5rem; font-size: 1.2rem; color: $dark_gray; - text-align: left; &.active { box-shadow: none !important; border: 1px solid $white; @@ -149,30 +158,12 @@ background-color: color.adjust($medium_light_gray, $lightness: -5%) !important; } } - &:hover { - .block-control { - transition: all 0.2s ease-out; - opacity: 1; - } - } .step-block { display: flex; padding: 0; box-shadow: none; .step-block-drag { - margin: auto; - width: 100%; - padding: 0.2rem 0.8rem; - label { - display: -webkit-box; - margin: 0; - width: 90%; - overflow: hidden; - text-overflow: ellipsis; - word-wrap: break-word; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - } + padding: 0.5rem 0.8rem; } } &.quick-del { @@ -191,18 +182,6 @@ } } -.block-control { - position: absolute; - top: 30%; - right: 0.75rem; - transition: all 0.2s ease-out; - opacity: 0; - float: right; - font-size: 1.4rem !important; - pointer-events: none; - -} - .fb-toggle-button { position: relative; height: 1.8rem !important; @@ -213,46 +192,29 @@ width: 5rem; border-bottom: none; transition: all 0.4s ease; - &.yellow { + &.yellow, &.green, &.red { &:after { content: ""; position: absolute; - top: 0.1rem; - left: 0; - right: 0; - margin: 0 auto; - height: 1.6rem; - width: 1.6rem; - background: $white; + top: 0.2rem;height: 1.4rem; + width: 1.4rem; + background: $off_white; border-radius: 50%; - box-shadow: inset rgba(0, 0, 0, 0.03) 0px 0px 3px 3px; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 4px 0px 0 rgba(0, 0, 0, 0.04), 0 4px 9px rgba(0, 0, 0, 0.13), 0 3px 3px rgba(0, 0, 0, 0.05); } - &:before { - content: ""; - position: absolute; - top: 0.1rem; + } + &.yellow { + &:after { left: 0; right: 0; margin: 0 auto; - height: 1.6rem; - width: 1.6rem; - border-radius: 50%; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 4px 0px 0 rgba(0, 0, 0, 0.04), 0 4px 9px rgba(0, 0, 0, 0.13), 0 3px 3px rgba(0, 0, 0, 0.05); } } &.green { text-align: left; padding-left: 0.6rem; &:after { - content: ""; - position: absolute; - top: 0.1rem; right: 0.2rem; - height: 1.6rem; - width: 1.6rem; - background: $white; - border-radius: 50%; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 4px 0px 0 rgba(0, 0, 0, 0.04), 0 4px 9px rgba(0, 0, 0, 0.13), 0 3px 3px rgba(0, 0, 0, 0.05); } &:hover { background: $green; @@ -265,15 +227,7 @@ text-align: right !important; padding-right: 0.8rem; &:after { - content: ""; - position: absolute; - top: 0.1rem; left: 0.2rem; - height: 1.6rem; - width: 1.6rem; - background: white; - border-radius: 50%; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 4px 0px 0 rgba(0, 0, 0, 0.04), 0 4px 9px rgba(0, 0, 0, 0.13), 0 3px 3px rgba(0, 0, 0, 0.05); } &.dim { background: color.adjust($red, $lightness: 10%) !important; @@ -300,13 +254,6 @@ } } -.front-page-button { - margin-top: "1rem"; - padding: ".5rem 1.6rem"; - font-size: "1.2rem"; - border-bottom: "none"; -} - .fb-icon-button { width: 2.5rem; height: 2.5rem; @@ -317,10 +264,10 @@ background: transparent; color: $dark_gray; text-align: center; - &.light { - color: $off_white; + &.invert { + color: var(--text-color); &:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.12); + background: var(--secondary-bg); } &:disabled { color: $off_white; @@ -339,3 +286,24 @@ cursor: not-allowed; } } + +.header-button-group { + margin-right: 1.5rem; +} + +.button-group { + justify-content: right; + .fa-code { + font-weight: bold; + color: $gray; + &.enabled { + color: $dark_gray; + } + } + .inactive { + color: $gray; + } + .fa-eye-slash { + color: $gray; + } +} diff --git a/frontend/css/global/fonts.scss b/frontend/css/global/fonts.scss new file mode 100644 index 0000000000..3cf3c01a5c --- /dev/null +++ b/frontend/css/global/fonts.scss @@ -0,0 +1,75 @@ +@use "../variables" as *; +@use "sass:color"; + +$cabin: 'Cabin', +Arial, +Helvetica, +sans-serif; + +body, +html { + font-family: $cabin; + font-weight: 400; + font-size: 10px; +} + +h1, +h2, +h3, +h4, +h5, +h6, +p, +fieldset { + font-family: $cabin; + font-weight: 400; +} + +a { + text-decoration: none !important; + outline: none !important; + cursor: pointer !important; + color: inherit; + &:link { + font-weight: 500; + color: inherit; + } + &:visited { + color: inherit; + } + &:hover { + font-weight: bold; + color: inherit; + } + &:active { + color: inherit; + } +} + +p { + font-size: 1.1rem; + color: inherit; + line-height: 1.4rem; + margin-bottom: 0 !important; +} + +h3 { + display: inline-block; + margin-top: 0; + font-size: 2.2rem; +} + +h4 { + line-height: 1.1rem; + font-size: 1.8rem; + color: inherit; +} + +hr { + border: 1px solid var(--secondary-bg); +} + +ul { + list-style-type: none !important; + padding: 0; +} diff --git a/frontend/css/global/global.scss b/frontend/css/global/global.scss new file mode 100644 index 0000000000..d36c845b8c --- /dev/null +++ b/frontend/css/global/global.scss @@ -0,0 +1,468 @@ +@use "../variables" as *; +@use "sass:color"; + +body { + background: $dark_bg; + margin: 0; +} + +.app, +.bp5-portal { + background: var(--main-bg); + color: var(--text-color); +} + +.app.dark { + background: hsl(135 10% 20% / 1); +} + +.app.light { + background: $gray; +} + +.initial-loading-text { + position: absolute; + top: 385px; + z-index: -1; + text-align: center; + width: 100%; + padding-top: 10%; + color: $gray; +} + +.char-limit { + float: right; + font-size: 1.4rem; + &.over { + color: $darkest_red; + font-weight: bold; + } +} + +.markdown { + p { + display: inline-block; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + text-transform: none; + } +} + +.controls-popover, +.jobs-panel, +.connectivity-popover { + .bp5-popover-content { + background: var(--main-bg); + } +} + +.jobs-and-logs, +.controls-content, +.connectivity-content { + display: grid; + gap: 2rem; + color: var(--text-color); + .tabs { + border-radius: 0.5rem; + display: grid; + gap: 0.2rem; + grid-auto-flow: column; + justify-content: center; + margin: 0 auto; + overflow: hidden; + label { + padding: 0.5rem 3rem; + background: $light_gray; + color: $medium_gray; + cursor: pointer; + &.selected { + background: $medium_gray; + color: $off_white; + } + &:hover { + background-color: color.adjust($white, $lightness: -40%); + color: $off_white; + } + } + } +} + +.all-content-wrapper { + margin: 0 auto; + padding: 11rem 3rem 0; + width: 100%; + max-width: 160rem; + animation: page-transition 0.2s ease-in-out; +} + +a { + cursor: pointer !important; + outline: none !important; +} + +.fa { + cursor: pointer !important; +} + +.caution-icon { + pointer-events: none; + margin-left: 1rem; +} + +.drag-drop-area { + &.visible { + margin: 0.75rem 0; + margin-right: 25px; + margin-left: 10px; + border-style: dashed; + border-width: 2px; + border-color: $light_gray; + color: $gray; + font-weight: bold; + padding: 1.25rem; + background: $off_white; + text-align: center; + color: $gray; + font-weight: bold; + } +} + +.expandable-header { + cursor: pointer; + .icon-toggle { + font-size: 1.2rem; + font-weight: bold; + vertical-align: middle; + .fa { + font-size: 1rem; + } + } +} + +.fa-exclamation-triangle { + color: $orange; +} + +.fb-toggle-button { + .setting-status-indicator { + top: 0.25rem; + } +} + +.fb-toggle-button { + &.yellow { + .setting-status-indicator { + display: none; + } + } + &.green { + .setting-status-indicator { + right: 0.4rem; + } + } + &.red { + .setting-status-indicator { + left: 0.4rem; + } + } +} + +.full-width { + width: 100%; +} + +.e-stop { + white-space: nowrap; + &.yellow { + animation: bright-flash 1s infinite alternate; + } +} + +.e-stop-btn { + float: right; + margin-top: 1.5rem; + margin-left: 0.5rem; + border-radius: 1rem; + .e-stop { + margin: 0; + box-shadow: none !important; + height: 2.9rem; + font-size: 1.1rem !important; + &.pseudo-disabled { + box-shadow: none !important; + } + &:hover { + box-shadow: 0 0 10px $translucent5, 0 0 5px inset rgba(255, 255, 255, 0.2) !important; + &.red { + color: $white; + } + &.yellow { + color: $black; + } + } + } + &.hard { + border-radius: 5px; + } +} + +.unavailable { + display: inline-block; + position: relative; + z-index: 10; + opacity: 0.40; + * { + pointer-events: none; + } + &.banner { + &:after { + content: "Not available when device is offline."; + position: absolute; + top: 25%; + left: -2.5%; + z-index: 10; + width: 105%; + padding: 0.5rem; + background-color: $dark_gray; + opacity: 0.90; + color: $off_white; + font-size: 1.8rem; + vertical-align: middle; + text-align: center; + } + } +} + +.sequence-description-wrapper, +.button-group, +.panel-header-icon-group { + .fa-spinner { + background: none !important; + box-shadow: none !important; + } +} + +.fb-button-popover-wrapper { + float: right; +} + +.parameter-assignment-menu { + .test-button-div { + text-align: center; + } + .fb-button { + float: none; + } +} + +.parameter-assignment-menu-popover { + max-width: 400px; +} + +@keyframes page-transition { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +.empty-state-graphic { + width: 40%; + filter: grayscale(1) +} + +.empty-state { + margin: 2rem 0; + text-align: center; + h5 { + font-weight: bold; + } + &.location { + img { + filter: grayscale(1); + } + } +} + +.update-resource-step { + .update-resource-pair { + .custom-meta-field { + position: relative; + .fa-undo { + position: absolute; + top: 0.65rem; + right: 0.5rem; + color: $medium_light_gray; + &:hover { + color: $dark_gray; + } + } + } + .custom-field-warning { + i, + p { + display: inline; + cursor: default !important; + margin-right: 0.5rem; + color: $darkest_red; + } + .did-you-mean { + cursor: pointer !important; + font-weight: bold; + } + } + } +} + +.computed-move-step { + &.step-content { + display: grid; + gap: 1rem; + .move-location-grid { + grid-template-columns: calc(25% - 1rem) 1fr; + } + input { + &:disabled { + background: $light_gray !important; + } + } + .expandable-header { + margin: 0; + } + span { + color: $dark_gray; + } + .input { + &.lua { + border-color: $yellow; + &:after { + content: "lua"; + position: absolute; + top: -0.25rem; + right: -0.3rem; + color: $dark_gray; + background: $yellow; + padding: 0.2rem 0.4rem; + font-size: 0.65rem; + font-weight: bold; + text-transform: uppercase; + } + } + } + } +} + +.checkbox-row { + display: flex; + margin-top: 1rem; + label { + padding: 0; + line-height: 2rem; + margin-bottom: 0; + } + .fb-checkbox { + display: inline; + position: relative; + margin-right: 1rem; + } + .bp5-popover-wrapper, + .bp5-popover-target { + margin-left: 1rem; + } +} + +.default-value-form { + position: relative; +} + +.status-icon { + &.ok { + color: $green; + } + &.no { + color: $red; + } + &.unknown { + color: $orange; + } +} + +.status-details { + max-width: 30rem; + button { + float: none !important; + } + .bp5-popover-wrapper { + display: inline; + margin-left: 0.5rem; + font-size: 1.3rem; + } +} + +.location-form-content { + display: grid; + gap: 1rem; +} + +textarea:focus { + box-shadow: 0 0 10px rgba(0,0,0,.2); +} + +.sort-option-bar { + cursor: pointer; + margin-top: 0.25rem; + margin-bottom: 0.25rem; + border: 2px solid color.adjust($panel_light_blue, $lightness: -30%); + border-radius: 5px; + &:hover, &.selected { + .sort-path-info-bar { + background: color.adjust($panel_light_blue, $lightness: -40%); + } + } + &:hover { + border: 2px solid color.adjust($panel_light_blue, $lightness: -40%); + } + &.selected { + border: 2px solid $medium_gray; + } + .sort-path-info-bar { + background: color.adjust($panel_light_blue, $lightness: -30%); + font-size: 1.2rem; + padding-left: 0.5rem; + white-space: nowrap; + line-height: 2.5rem; + } +} + +.beacon-transition { + transition: border 1s, border-radius 1s; +} + +.beacon { + &.hard { + box-shadow: 0 0 0 4px $yellow !important; + } + &.soft { + background: #ffe53e52 !important; + } +} + +ul { + margin: 0; + li { + margin: 0; + } +} + +*::-webkit-scrollbar { + display: none !important; + width: 0px !important; + background-color: transparent !important; +} + +svg { + fill: var(--text-color); +} diff --git a/frontend/css/global/imports.scss b/frontend/css/global/imports.scss new file mode 100644 index 0000000000..72cfc098c1 --- /dev/null +++ b/frontend/css/global/imports.scss @@ -0,0 +1,6 @@ +// Google Fonts +@import url("https://fonts.googleapis.com/css?family=Cabin:400,400italic,700,700italic,100,100italic"); +@import url("https://fonts.googleapis.com/css?family=Inknut+Antiqua:400,400italic,700,700italic,100,100italic"); +// Blueprint +@import "~/node_modules/@blueprintjs/core/lib/css/blueprint.css"; +@import "~/node_modules/@blueprintjs/icons/lib/css/blueprint-icons.css"; diff --git a/frontend/css/inputs.scss b/frontend/css/global/inputs.scss similarity index 70% rename from frontend/css/inputs.scss rename to frontend/css/global/inputs.scss index 99c42624ad..297f13a72f 100644 --- a/frontend/css/inputs.scss +++ b/frontend/css/global/inputs.scss @@ -1,18 +1,46 @@ -// Styles for INPUTS +@use "../variables" as *; +@use "sass:color"; + +button, +select, +summary, +input { + outline: none !important; +} + +input { + border-radius: 3px; + &:disabled { + background: $translucent5_white !important; + cursor: not-allowed; + } +} + +input[type=number]::-webkit-inner-spin-button, +input[type=number]::-webkit-outer-spin-button, +input[type=date]::-webkit-inner-spin-button, +input[type=date]::-webkit-outer-spin-button, +input[type=time]::-webkit-inner-spin-button, +input[type=time]::-webkit-outer-spin-button, +input[type=time] { + -webkit-appearance: none; + -moz-appearance: textfield; + margin: 0; +} + input:not([role="combobox"]) { font-size: 1.2rem; - color: $dark_gray; height: auto; width: 100%; border: 0; padding: 6px 8px; - box-shadow: 0px 0px 10px $light_gray; + background: $translucent9_white; &[type="checkbox"] { width: inherit; } &:focus { background-color: $white; - box-shadow: 0px 0px 10px $translucent; + box-shadow: 0px 0px 10px $translucent1_white; } &.bulk-day-selector { width: 10%; @@ -48,6 +76,16 @@ input:not([role="combobox"]):not([type="checkbox"]):not([type="radio"]) { height: 3rem; } +input[type="radio"] { + box-shadow: none !important; + margin: 0; + cursor: pointer; +} + +input[type="checkbox"] { + padding: 0; +} + .input { position: relative; .bp5-popover-wrapper { @@ -84,15 +122,15 @@ input:not([role="combobox"]):not([type="checkbox"]):not([type="radio"]) { } .week-row { - height: 30.5px; - width: 108%; - margin-left: -1rem; + display: grid; + grid-template-columns: 1fr repeat(7, 4.5rem); + align-items: center; } select { border: none; padding: 7px; - background: $white; + background: $translucent9_white; width: 100%; } @@ -176,14 +214,15 @@ select { .fb-checkbox { input[type="checkbox"] { - position: relative; - border-radius: 0; -webkit-appearance: none; - border: 0.5px solid $gray; - width: 2rem; + background: $translucent9_white; + border-radius: 0; + border-radius: 3px; height: 2rem; - background: $white; - margin-top: 0; + margin: 0; + overflow: hidden; + position: relative; + width: 2rem; cursor: pointer; &:before { content: ""; @@ -192,7 +231,7 @@ select { left: 0; width: 100%; height: 100%; - background: $white; + background: $translucent9_white; opacity: 0.5; } &:checked:after { @@ -216,6 +255,7 @@ select { padding: 0.6rem 0.3rem; } &.large { + height: 3rem; input[type="checkbox"] { width: 3rem; height: 3rem; @@ -242,3 +282,47 @@ select { } } } + +textarea { + border-radius: 0.5rem; + width: 100%; + border: 0; + outline: none!important; + resize: vertical; + font-size: 1.2rem; + padding: 1rem; +} + +.input-group { + width: 100% !important; + input[type=checkbox] { + box-shadow: none; + } +} + +fieldset { + border: none; + legend { + background: transparent; + } +} + +.modified { + box-shadow: 0 0 0px 3px $yellow !important; + border-radius: 3px; + &.bp5-slider { + box-shadow: none !important; + .bp5-slider-handle { + box-shadow: 0 0 0px 3px $yellow; + } + } +} + +.input { + .fa-undo { + position: absolute; + top: 1rem; + right: 0.25rem; + font-size: 1rem; + } +} diff --git a/frontend/css/global/labels.scss b/frontend/css/global/labels.scss new file mode 100644 index 0000000000..ba88e89484 --- /dev/null +++ b/frontend/css/global/labels.scss @@ -0,0 +1,22 @@ +@use "../variables" as *; +@use "sass:color"; + +label { + font-size: 1.2rem; + font-weight: bold; + text-transform: uppercase; + &.day-label { + position: absolute; + text-align: center; + width: 10%; + margin-bottom: 0px; + line-height: 3rem; + border: solid var(--border-color); + border-width: 1px; + } +} + +input[type=checkbox]:checked+label.day-label { + color: var(--main-bg); + background: var(--text-color); +} diff --git a/frontend/css/global/saucers.scss b/frontend/css/global/saucers.scss new file mode 100644 index 0000000000..9d5c75b714 --- /dev/null +++ b/frontend/css/global/saucers.scss @@ -0,0 +1,70 @@ +@use "../variables" as *; +@use "sass:color"; + +.saucer { + position: relative; + z-index: 2; + height: 2rem; + width: 2rem; + background: $dark_gray; + border-radius: 50%; + cursor: pointer; + &.active { + border: 2px solid $dark_gray; + } + &.hover { + border: 2px solid $off_white; + } + p { + font-weight: 900; + color: $white; + margin-left: 0.7rem; + margin-top: 0.3rem; + } +} + +.icon-saucer { + position: relative; + z-index: 2; + height: 2rem; + width: 2rem; + color: $dark_gray; + cursor: pointer; + &.active { + border: 2px solid white; + } + &.hover { + border: 2px solid $dark_gray; + } +} + +.saucer-connector { + position: absolute; + z-index: 1; + height: 6rem; + width: 1rem; + margin-left: 0.5rem; + margin-top: -1rem; + &.last { + margin-top: -9rem; + height: 8rem; + } +} + +.colorpicker-menu { + padding: 0; + .bp5-popover-arrow-fill { + fill: $dark_gray; + } + .bp5-popover-content { + width: 13rem; + background: $dark_gray; + } + .bp5-popover-content, + .color-picker-cluster, + .color-picker-item-wrapper, + .saucer { + display: inline-block; + padding: 0.4rem; + } +} diff --git a/frontend/css/global/sliders.scss b/frontend/css/global/sliders.scss new file mode 100644 index 0000000000..ef0980b951 --- /dev/null +++ b/frontend/css/global/sliders.scss @@ -0,0 +1,61 @@ +@use "../variables" as *; +@use "sass:color"; + +.sliders { + display: inline-block; + position: relative; + width: 100%; + margin-bottom: 3rem; + margin-top: 1rem; + .bp5-slider { + margin-left: 3rem; + margin-top: 1rem; + width: 80%; + } + &.vertical { + .bp5-slider { + margin-top: 0; + } + } + .bp5-slider-label { + white-space: nowrap; + text-align: center; + &:empty { + display: none; + } + } + .data-slider { + pointer-events: none; + .bp5-slider-axis, + .bp5-slider-track { + display: none; + } + .bp5-slider-label { + box-shadow: none; + } + .bp5-start { + top: 0.53rem; + width: 0.5px; + background: color.adjust($dark_gray, $alpha: -0.75); + box-shadow: none; + height: 0.55rem; + border-radius: 0; + &:first-of-type { + display: none; + } + } + &.vertical { + .bp5-start { + top: unset; + left: 0.53rem; + height: 0.5px; + width: 0.55rem; + border-radius: 0; + } + } + } + .input-slider { + position: absolute; + top: 0; + } +} diff --git a/frontend/css/tables.scss b/frontend/css/global/tables.scss similarity index 64% rename from frontend/css/tables.scss rename to frontend/css/global/tables.scss index d94e84ecb6..823d644f3a 100644 --- a/frontend/css/tables.scss +++ b/frontend/css/global/tables.scss @@ -1,7 +1,12 @@ -// Styles for TABLES +@use "../variables" as *; +@use "sass:color"; + table { width: 100%; margin-bottom: 0; + border-collapse: collapse; + border-radius: 1rem; + overflow: hidden; } table.plain tr:nth-of-type(1n) { diff --git a/frontend/css/tooltips.scss b/frontend/css/global/tooltips.scss similarity index 93% rename from frontend/css/tooltips.scss rename to frontend/css/global/tooltips.scss index 12d9b390b0..0b30b95ce0 100644 --- a/frontend/css/tooltips.scss +++ b/frontend/css/global/tooltips.scss @@ -1,3 +1,10 @@ +@use "../variables" as *; +@use "sass:color"; + +.help-icon { + font-size: 1.3rem; +} + .help { display: inline; margin-left: 2rem; @@ -28,6 +35,7 @@ } .help-text-content { + font-size: 1.4rem; a { color: $off_white; &:hover { diff --git a/frontend/css/labels.scss b/frontend/css/labels.scss deleted file mode 100644 index 97df079162..0000000000 --- a/frontend/css/labels.scss +++ /dev/null @@ -1,34 +0,0 @@ -// Styles for LABELS -label { - color: $dark_gray; - font-size: 1.2rem; - font-weight: bold; - text-transform: uppercase; - &.day-label { - position: absolute; - text-align: center; - width: 10%; - margin-bottom: 0px; - line-height: 3rem; - border: solid $gray; - background: $white; - border-width: 1px 0.5px; - &.left-most { - border-left-width: 1px; - } - &.right-most { - border-right-width: 1px; - } - } - &.week-label { - width: 25%; - text-align: center; - vertical-align: middle; - margin-top: -1.75rem; - } -} - -input[type=checkbox]:checked+label.day-label { - color: $off_white; - background: $dark_gray; -} diff --git a/frontend/css/laptop_splash.scss b/frontend/css/laptop_splash.scss deleted file mode 100644 index 4673b62d54..0000000000 --- a/frontend/css/laptop_splash.scss +++ /dev/null @@ -1,95 +0,0 @@ -.perspective-container { - margin-top: 5rem; - perspective: 1500px; -} - -.laptop { - width: 80%; - margin-left: 0px; - transform: rotateX(6deg) rotateY(30deg) rotateZ(-6deg); - transform-style: preserve-3d; - transition-duration: 6s; -} - -@media only screen and (max-width: 600px) { - .laptop, - .laptop:hover { - transform: rotateX(6deg) rotateY(30deg) rotateZ(-6deg) scale3D(.4, .4, .4) translateX(-75%) !important; - margin-top: -60%; - margin-bottom: -80%; - } - span.laptop-shine { - opacity: 0; - } -} - -.laptop:hover { - transform: rotateX(6deg) rotateY(25deg) rotateZ(-5deg) translateY(-5px) scale3D(1.03, 1.03, 1.03); - transform-style: preserve-3d; -} - -.laptop-screen { - padding: 13px 10px 20px; - width: 100%; - border-radius: 15px; - background: white; - box-shadow: inset 0 4px 7px 1px #fff, inset 0 -5px 20px rgba(173, 186, 204, .25), 0 2px 6px rgba(0, 21, 64, .14), -16px 20px 40px 0px rgba(0, 0, 0, .15); - border-left: 2px solid #ddd; -} - -.laptop-screen video { - width: 100%; - border-radius: 3px; - box-shadow: 0px 0px 4px 2px #eee; -} - -span.laptop-shine { - display: none; - position: absolute; - top: 10px; - left: 12px; - bottom: 200px; - right: 160px; - background: linear-gradient(to top right, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 250, .3) 77%, rgba(255, 255, 255, 0) 90%); - transition-duration: 6s; -} - -.laptop:hover span.laptop-shine { - opacity: 0; -} - -.laptop-keyboard { - border-left: 1px solid #ddd; - border-top: 2px solid #eee; - border-bottom: 12px solid #eee; - border-right: 21px solid white; - padding-left: 10px; - padding-top: 15px; - border-radius: 30px; - margin-top: -15px; - width: 100%; - height: 220px; - background: white; - transform: perspective(1500px) rotateX(70deg); - transform-origin: 50% 0; - box-shadow: -20px 30px 40px 0px rgba(0, 0, 0, .1); -} - -.laptop-keys { - background: linear-gradient(45deg, #cfd7e1, #dce3eb); - width: 92%; - height: 100px; - margin-left: auto; - margin-right: auto; - opacity: 0.7; -} - -.laptop-trackpad { - background: linear-gradient(45deg, #cfd7e1, #dce3eb); - width: 200px; - height: 70px; - margin-top: 10px; - margin-left: auto; - margin-right: auto; - opacity: 0.7; -} diff --git a/frontend/css/numbers.scss b/frontend/css/numbers.scss deleted file mode 100644 index 72eb47e0ca..0000000000 --- a/frontend/css/numbers.scss +++ /dev/null @@ -1 +0,0 @@ -$mobile_max_width: 500px; diff --git a/frontend/css/panels/connectivity.scss b/frontend/css/panels/connectivity.scss new file mode 100644 index 0000000000..f6e7e7b4ed --- /dev/null +++ b/frontend/css/panels/connectivity.scss @@ -0,0 +1,292 @@ +@use "../variables" as *; +@use "sass:color"; + +.connector-hover-area { + visibility: hidden; + stroke: white; + pointer-events: all; +} + +.diagnosis-indicator { + text-align: center !important; + &.nav { + display: inline-block; + border-color: transparent; + } + i { + display: block; + position: absolute; + top: 2.5px; + left: 2.5px; + color: $white; + font-size: 11px; + &.fa-times { + left: 0.35rem; + } + &.fa-question { + left: 0.45rem; + } + } +} + +.connectivity-popover-portal { + .bp5-transition-container { + z-index: 999; + } + .connectivity-popover { + margin-top: -1rem; + max-width: 100vw; + .connectivity { + width: 600px; + max-width: calc(100vw - 2rem); + max-height: calc(100vh - 10rem); + padding: 1rem; + .connectivity-content { + table { + font-size: 1.3rem; + } + } + .connectivity-left-column { + display: grid; + gap: 1rem; + } + .connectivity-right-column { + display: grid; + gap: 1rem; + } + .connectivity-diagram svg { + max-height: 200px !important; + } + .port-info, + .network-info, + .fbos-info { + @media (max-width:767px) { + display: block; + } + } + .network-info { + margin-bottom: 0; + } + } + } +} + +.diagnosis-section { + align-items: start!important; +} + +.connectivity-diagnosis { + align-items: start; + h4 { + margin: 0.4rem 0 1.5rem; + } + p { + padding-bottom: 1rem; + } + a { + display: block; + font-size: 1.2rem; + } + .blinking { + a { + display: inline; + } + } + .fa-external-link { + margin-right: 0.5rem; + } +} + +.camera-connection-indicator, +.memory-usage-display, +.chip-temp-display { + position: relative; + .saucer { + position: absolute; + top: 2px; + right: 0.5rem; + height: 1rem; + width: 1rem; + cursor: default; + } +} + +.voltage-display { + position: relative; + height: 1.4rem; + .voltage-saucer { + position: absolute; + top: 0; + right: 0.5rem; + height: 1rem; + width: 1rem; + cursor: default; + } +} + +.voltage-display { + display: flex; + .saucer { + height: 1rem; + width: 1rem; + cursor: default; + margin-left: 0.5rem; + margin-top: 0.2rem; + } + .help-icon { + margin: 0 0 0.5rem 0.25rem; + font-size: 1rem; + vertical-align: middle; + } +} + +.throttle-display { + .throttle-row { + display: flex; + white-space: nowrap; + .saucer { + margin-right: 1rem; + } + } +} + +.wifi-strength-display { + position: relative; + .percent-bar { + position: absolute; + top: 2px; + right: 0; + height: 1rem; + width: 25%; + clip-path: polygon(0 85%, 100% 0, 100% 100%, 0% 100%); + background-color: $light_gray; + .percent-bar-fill { + height: 100%; + background-color: $green; + } + } +} + +.mac-address { + font-size: 1rem; + b { + font-size: 1.1rem; + } +} + +.port-info, +.network-info, +.fbos-info { + background: var(--secondary-bg); + padding: 1rem; + border-radius: 5px; + .ip-address { + word-break: break-all; + } + a { + display: block; + margin-top: 1rem; + font-size: 1.1rem; + text-decoration: underline !important; + i { + margin-right: 0.5rem; + } + } +} + +.connectivity-diagram { + margin: auto; + width: 20rem; + fill: $gray; +} + +.port-info, +.qos-display { + .saucer { + float: right; + width: 1rem; + height: 1rem; + cursor: unset; + } +} + +.port-info { + i { + display: block; + font-size: 1.2rem; + } + .fa { + display: inline; + } +} + +.last-seen-row { + i { + margin-right: 0.5rem; + } +} + +.farmbot-os-details { + max-width: 350px; +} + +.fbos-metric-history { + .fbos-metric-history-table-wrapper { + display: block; + overflow-y: scroll; + max-height: 25rem; + margin: 0 -2rem -2rem; + table { + overflow: unset; + thead, th { + text-align: left; + position: sticky; + top: 0; + z-index: 3; + background: var(--main-bg); + } + .saucer { + margin: 0 auto; + height: 1.5rem; + width: 1.5rem; + cursor: default; + } + } + } +} + +.fbos-metric-history-plot-border { + width: 100%; + border-radius: 5px; + background: var(--secondary-bg); + max-height: 30rem; + text { + font-size: 0.6rem; + text-anchor: middle; + dominant-baseline: middle; + } +} + +.connectivity-grid { + grid-template-columns: auto 1fr 1fr 1fr; +} + +.realtime-wrapper { + display: grid; + gap: 1rem; + grid-template-columns: auto 1fr; + @media screen and (max-width: $mobile_max_width) { + grid-template-columns: 1fr; + grid-auto-flow: dense; + } +} + +.network-wrapper { + display: grid; + grid-auto-flow: column; + gap: 1rem; + @media screen and (max-width: $mobile_max_width) { + grid-template-columns: 1fr !important; + grid-auto-flow: dense; + } +} diff --git a/frontend/css/panels/controls.scss b/frontend/css/panels/controls.scss new file mode 100644 index 0000000000..5ed17fc032 --- /dev/null +++ b/frontend/css/panels/controls.scss @@ -0,0 +1,463 @@ +@use "../variables" as *; +@use "sass:color"; + +.controls-popover-portal { + .bp5-transition-container { + z-index: 999; + } + .controls-popover { + max-width: 100vw; + .controls-content { + padding: 1rem; + width: 500px; + max-width: calc(100vw - 2rem); + max-height: calc(100vh - 10rem); + overflow-y: auto; + overflow-x: hidden; + } + } +} + +.text-center { + text-align: center; + width: 100%; +} + +.move { + .move { + .unavailable, + .bot-is-online-wrapper { + display: inline-block; + } + } + .move-settings.bp5-popover-wrapper { + position: absolute; + top: 10rem; + right: 2rem; + } +} + +.move-amount-wrapper { + box-shadow: 0px 0px 1rem $translucent1; + height: 2.5rem; + border-radius: 7px; +} + +.move-amount { + margin: 0; + background-color: $white; + border-right: 2px solid $off-white; + color: $medium_gray; + font-weight: bold; + height: 2.5rem; + padding-top: auto; + padding-bottom: auto; + padding-left: 0; + padding-right: 0; + text-align: center; + width: 20%; + float: left; + &:hover { + background-color: color.adjust($white, $lightness: -40%); + color: $off_white; + } + &:nth-child(n+2) { + border-left: 0; + } + &.leftmost { + border-bottom-left-radius: 7px; + border-top-left-radius: 7px; + } + &.rightmost { + border-bottom-right-radius: 7px; + border-top-right-radius: 7px; + border-right: 0px none; + } + &.move-amount-selected { + background-color: $medium_gray !important; + color: $off_white; + } +} + +.no-radius { + border-radius: 0; +} + +// JOG CONTROLS +.jog-table { + margin: auto; + margin-top: 15px; + width: auto; + border: 0; + .bp5-popover-wrapper { + line-height: 0; + } + .fa-camera { + .bp5-popover-wrapper { + z-index: 1; + width: 4rem; + height: 4rem; + margin-top: -3rem; + margin-left: -1rem; + .bp5-popover-target { + width: 100%; + height: 100%; + } + } + } +} + +.in-progress { + background: $gray !important; + border-bottom-color: $medium_gray !important; +} + +.progress-percent { + width: 3rem; + height: 3rem; + margin-top: -0.75rem; + margin-left: -0.75rem; + border-radius: 50%; + padding: 0.25rem; + p { + display: inline-block; + position: relative !important; + margin: auto !important; + color: $dark_gray !important; + background: $gray; + border-radius: 50%; + width: 100%; + height: 100%; + font-size: 0.7rem; + font-weight: bold; + line-height: 2.25rem; + vertical-align: middle; + } +} + +.power-btn-popover { + label { + color: $off_white; + } + .bp5-popover-wrapper { + display: inline; + margin-left: 0.5rem; + color: $off_white; + } + .bp5-popover-content { + width: 310px; + background: $dark_gray; + } + .bp5-popover-arrow-fill { + fill: $dark_gray; + } + .fa-anchor { + display: none; + } +} + +.arrow-button { + position: relative; + margin: 0; + background-color: $medium_gray; + border-bottom: 2px solid $dark_gray; + color: $off_white; + font-size: 16px !important; + height: 40px; + padding: 12px; + text-align: center; + width: 40px; + box-shadow: rgba(0, 0, 0, 0.15) 0px 2px 7px 0px; + &:hover { + background-color: color.adjust($medium_gray, $lightness: -5%); + } + &:disabled { + border-bottom: none; + box-shadow: none !important; + } + p { + position: absolute; + top: 0; + left: 0; + z-index: 1; + color: $white; + margin-top: -2px; + padding-left: 1px; + font-size: 0.9rem; + } + &:before { + position: relative; + z-index: 1; + } +} + +.movement-progress { + position: absolute; + width: 100%; + height: 100%; + border-radius: 3px; + background: $medium_gray; +} + +.camera-message, +.movement-message { + .bp5-popover-content { + width: unset !important; + } +} + +.home-button { + i { + margin-top: -1rem; + font-size: 2rem; + } + .fa-stack { + position: absolute; + top: 0; + left: 0; + margin: 0.5rem; + padding: 0.5rem; + .fa-stack-1x { + top: -1rem; + left: -1rem; + margin-top: 0; + font-size: 1rem; + } + .fa-stack-2x { + margin-top: 0; + font-size: 2rem; + } + } +} + +.bot-position-rows { + .missed-step-indicator-wrapper { + position: absolute; + top: -1.5rem; + height: 1rem; + .missed-step-details { + min-width: 9rem; + padding-bottom: 1rem; + table tr, td { + padding: unset; + } + label, p { + color: $white; + text-align: right; + font-style: normal; + } + } + .bp5-popover-wrapper { + .bp5-popover-target { + display: block; + } + } + .bp5-popover { + .bp5-popover-arrow { + svg { + transform: rotate(-90deg) translate(1px) !important; + .bp5-popover-arrow-fill { + fill: $dark_gray; + } + } + } + .bp5-popover-content { + background: $dark_gray; + } + } + .missed-step-indicator { + position: absolute; + top: 0.2rem; + height: 0.75rem; + width: 100%; + background: $white; + box-shadow: 0 1px 1px rgba(0, 0, 0, .05); + cursor: pointer; + .instant { + position: absolute; + height: 100%; + opacity: 0.5; + &.yellow { + opacity: 0.75; + } + } + .peak { + position: absolute; + height: 100%; + width: 2px; + opacity: 0.5; + &.yellow { + opacity: 0.75; + } + } + } + } +} + +.move-settings-menu { + label { + margin-top: 7px; + } + p { + margin-top: 0.7rem; + font-size: 1.4rem; + font-weight: 400; + } +} + +.bot-position-rows { + p { + text-align: center; + font-style: italic; + } + .axis-info { + .index-1 { + z-index: 1; + } + .index-2 { + z-index: 2; + } + .index-3 { + z-index: 3; + } + } + .fa-ellipsis-v { + font-size: 1.2rem; + width: 1rem; + text-align: center; + } + .axis-actions { + width: 10rem; + .fb-button { + width: 100%; + float: none; + margin-bottom: 0.25rem; + } + a { + display: block; + padding: 1rem 0.5rem 0.75rem 0.5rem; + text-decoration: none !important; + font-size: 1.2rem; + .fa-external-link { + margin-top: 0.25rem; + float: right; + } + &:hover { + .fa-external-link { + font-weight: bold; + } + } + } + } +} + +.motor-position-plot-border { + background: var(--secondary-bg); + border-radius: 5px; + text { + font-size: 0.4rem; + text-anchor: middle; + dominant-baseline: middle; + } +} + +.controls-popup, +.controls-popup-menu-outer { + position: fixed; + bottom: 3rem; + right: 1rem; + z-index: 3; + background: $dark_gray; + border-radius: 3rem; + height: 6rem; + width: 6rem; +} + +.controls-popup { + color: $off_white; + @media screen and (max-width: $mobile_max_width) { + &.panel-open { + display: none; + } + } + img { + position: fixed; + bottom: 3rem; + z-index: 4; + width: 6rem; + height: 6rem; + border-radius: 3rem; + padding: 18px 20px; + font-size: 2.4rem; + transition: all 0.25s ease-in-out; + filter: invert(1); + &:hover { + background-color: rgba(0, 0, 0, 0.2); + } + } + .move-amount-wrapper, + .jog-table { + display: none; + } + &.open { + img { + transform: rotate(-135deg); + &:hover { + background-color: rgba(0, 0, 0, 0); + } + } + .controls-popup-menu-outer { + transition: all 0.1s 0s ease-in-out; + width: 36rem; + height: 14rem; + padding: 0.6rem 5rem 0rem 0rem; + } + .controls-popup-menu-inner { + transition-delay: 0.25s !important; + opacity: 1; + } + .jog-table { + display: block; + margin: 0; + float: right; + td { + padding: 0; + } + } + .move-amount-wrapper { + display: inline-block; + width: 100%; + box-shadow: none; + margin-left: 2rem; + padding-top: 0.5rem; + .move-amount { + width: 18%; + height: 2rem; + } + } + } + .arrow-button { + margin: 5px; + box-shadow: none !important; + &.pseudo-disabled { + box-shadow: none !important; + } + } +} + +.controls-popup-menu-inner { + border-radius: 2rem; + transition: all 0.1s 0s ease-in-out; + opacity: 0; +} + +.controls-popup-menu-outer { + transition: all 0.1s ease-in-out; + transition-delay: 0.2s !important; +} + +.move-to-grid { + grid-auto-flow: unset !important; + grid-template-columns: 1fr 1fr 1fr auto; + row-gap: 0.25rem; +} diff --git a/frontend/css/panels/curves.scss b/frontend/css/panels/curves.scss new file mode 100644 index 0000000000..055d403bb9 --- /dev/null +++ b/frontend/css/panels/curves.scss @@ -0,0 +1,187 @@ +@use "../variables" as *; +@use "sass:color"; + +.curve-svg-wrapper { + .bp5-popover-target { + width: 100%; + } +} + +.warning-line-text-popover { + border-radius: 5px; + .bp5-popover-content { + background: $dark_gray; + border-radius: 5px; + } + .bp5-popover-arrow { + display: none; + } + p { + color: $white; + font-size: 1.3rem; + margin-bottom: 0.5rem !important; + } + .warning-text { + max-width: 200px; + .top { + font-weight: bold; + } + } +} + +.curve-action-popover { + max-width: 25rem; +} + +.curve-svg { + .data-labels, + .y-axis-line, + .warning-line { + pointer-events: none; + } +} + +.curve-info-panel-content-wrapper { + .full-indicator { + height: 2rem; + text-align: center; + color: $darkest_red; + } + .input-error-wrapper { + position: absolute; + margin: 0; + } + table { + th { + text-transform: uppercase; + color: $dark_gray; + font-size: 1.3rem; + } + td, + th { + background: $lighter_gray; + border: 1px solid $light_gray; + padding-left: 1rem; + p { + display: inline; + font-size: 1.4rem; + color: $dark_gray; + } + &.active { + background: $white; + } + &.active-input { + padding: 0; + } + .percent-green { + color: $green; + } + .percent-red { + color: $red; + } + input { + font-size: 1.4rem; + padding-left: 1rem; + } + } + tr { + &.hovered { + border: 2px solid $gray; + border-top-width: 0; + border-bottom-width: 0; + } + } + } + .row-radio { + width: 1.5rem; + height: 1.5rem; + border-radius: 50%; + vertical-align: middle; + margin-left: 1rem; + margin-top: -0.25rem; + &.active { + &.full { + background: revert; + border: revert; + cursor: pointer; + } + } + &.full { + background: $light_gray; + border: $gray; + cursor: not-allowed; + } + } +} + +.curves-inventory-panel-content { + padding: 0; + cursor: pointer; + .curve-search-item-info { + font-size: 1rem; + } + .section-header { + .fa-caret-up, + .fa-caret-down { + line-height: 3.75rem; + margin-top: 0 !important; + } + } +} + +.crop-curve-info { + .bp5-collapse { + padding-top: 0.5rem; + } + p { + line-height: 4rem; + font-size: 1.3rem; + } + label { + margin-top: 0 !important; + } + .fa-external-link { + position: absolute; + top: 1rem; + right: 0; + } + .active-curve-name { + p { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } + } + .curve-svg-wrapper { + background: $white; + box-shadow: 0 0 10px $light_gray; + border-radius: 5px; + } +} + +.curve-info-panel-content-wrapper, +.all-curve-info { + svg { + text { + user-select: none; + } + } +} + +.curve-usage-display { + margin-bottom: 2rem; + i[class*=fa-caret-] { + float: right; + font-size: 2rem; + } + label { + margin-top: 0 !important; + } + .bp5-popover-wrapper { + display: inline; + margin-left: 1rem; + } + img { + cursor: pointer; + } +} diff --git a/frontend/css/panels/events.scss b/frontend/css/panels/events.scss new file mode 100644 index 0000000000..f6502f820e --- /dev/null +++ b/frontend/css/panels/events.scss @@ -0,0 +1,75 @@ +@use "../variables" as *; +@use "sass:color"; + +.farm-event { + align-items: start!important; +} + +.farm-event-calendar-rows { + margin-bottom: 2rem; +} + +.farm-event-year { + text-align: center; + font-size: 2rem; + font-family: 'Inknut Antiqua', serif; + font-weight: bold; +} + +.farm-event-date { + text-align: center; +} + +.farm-event-date-month { + font-size: 1.2rem; +} + +.farm-event-date-day { + font-size: 1.5rem; +} + +.farm-event-variable { + font-size: 1rem; + border-radius: 0.5rem; + background: $translucent8_white; + padding: 0.25rem 0.5rem; + display: inline-block; + margin: 0.25rem; +} + +.farm-event-data-block { + grid-template-columns: 6rem 1fr auto; + padding: 0.5rem 1rem; + font-size: 1.2rem; + align-items: baseline!important; + border-radius: 0.5rem; + overflow: hidden; + box-shadow: 0 0 1rem $translucent1; + font-weight: bold; + color: $dark_gray; + .fa { + margin-left: 0.5rem; + opacity: 0; + } + &:hover { + i { + opacity: 1; + } + } +} + +.edit-farm-event-panel, +.add-farm-event-panel { + .panel-content { + overflow-y: auto; + overflow-x: hidden; + padding: 1rem 1rem 0; + } + .bp5-popover-wrapper { + display: inline-block; + margin-left: 0.5rem; + &.input-error-wrapper { + display: block; + } + } +} diff --git a/frontend/css/panels/farmware.scss b/frontend/css/panels/farmware.scss new file mode 100644 index 0000000000..6349642152 --- /dev/null +++ b/frontend/css/panels/farmware.scss @@ -0,0 +1,140 @@ +@use "../variables" as *; +@use "sass:color"; + +.farmware-panel, +.designer-farmware-list-panel, +.designer-farmware-info-panel, +.farmware-add-panel { + .panel-content { + a { + text-decoration: none !important; + p { + font-size: 1.4rem; + font-weight: normal !important; + line-height: 2rem; + } + } + } +} + +.designer-farmware-info-panel { + .panel-content { + position: relative; + padding-top: 2rem; + padding-bottom: 12rem; + button { + float: none; + } + .reset-configs { + margin-right: 1rem; + margin-top: 2rem; + } + .farmware-button { + position: absolute; + top: 1rem; + right: 1rem; + } + .farmware-form { + margin-top: 1rem; + } + .farmware-input-group { + margin-left: 0; + } + } +} + +.custom-setting-grid { + grid-template-columns: 1fr 1fr auto; +} + +.farmware-info { + margin-top: 1rem; +} + +.farmware-list-items { + margin-left: -30px; + margin-right: -20px; + padding: 0.5rem; + padding-left: 1.5rem; + padding-top: 0.75rem; + cursor: pointer; + label { + cursor: pointer; + } + &:hover { + background: $medium_light_gray; + p { + font-weight: bold; + } + } +} + +.farmware-url { + font-size: 1rem; + width: 100px; + word-wrap: break-word; +} + +.farmware-step-input-fields { + label { + padding-top: 1rem; + } + fieldset { + width: 95%; + padding-left: 1rem; + padding-right: 1rem; + } + button { + position: absolute; + } +} + +.farmware-name-manual-input { + margin-top: 1rem; +} + +.farmware-input-group { + position: relative; + margin-left: 3rem; + .fa-times-circle, + .fa-refresh { + position: absolute; + top: 0.85rem; + color: $light_gray; + &:hover { + color: $dark_gray; + } + } + .fa-times-circle { + right: 1rem; + } + .fa-refresh { + right: 3rem; + } + &.dropdown { + .filter-search { + border: 3px solid $transparent; + } + .fa-times-circle { + right: 3rem; + } + .fa-refresh { + right: 5rem; + } + } +} + +.error-with-button { + margin-top: 1rem; + background: $pink; + border: 1px solid $red; + border-radius: 5px; + label, + p { + margin: 0.5rem; + color: $red; + } + button { + margin: 0.5rem !important; + } +} diff --git a/frontend/css/panels/gardens.scss b/frontend/css/panels/gardens.scss new file mode 100644 index 0000000000..97e4cf1798 --- /dev/null +++ b/frontend/css/panels/gardens.scss @@ -0,0 +1,65 @@ +@use "../variables" as *; +@use "sass:color"; + +.saved-garden-edit-panel-content { + display: grid; + gap: 1rem; +} + +.saved-garden-grid { + grid-template-columns: auto 1fr; + align-items: baseline; +} + +.saved-garden-indicator { + position: fixed; + top: 80px; + left: 50%; + z-index: 3; + padding: 1rem 2rem; + background: var(--main-bg); + border-radius: 5px; + box-shadow: 0px 1px 5px $translucent3; + text-align: center; + label { + display: block; + } + button { + margin: 0.5rem; + float: unset; + } +} + +.saved-garden-list { + .saved-garden-search-item { + padding: 0.25rem; + button { + margin-bottom: 1rem; + } + .saved-garden-info div { + height: 3rem; + line-height: 3rem; + cursor: pointer; + padding-right: 0; + span { + margin: 0; + pointer-events: none; + margin-left: 1rem; + } + p { + float: right; + line-height: 3rem; + text-align: center; + margin-right: 1rem; + } + } + } +} + +.add-garden-grid { + grid-template-columns: auto 1fr; +} + +.add-new-garden-buttons { + justify-content: right; +} diff --git a/frontend/css/panels/help.scss b/frontend/css/panels/help.scss new file mode 100644 index 0000000000..b7ac6caad3 --- /dev/null +++ b/frontend/css/panels/help.scss @@ -0,0 +1,125 @@ +@use "../variables" as *; +@use "sass:color"; + +.documentation-panel { + .panel-content { + height: 100vh; + padding: 0; + overflow: hidden; + iframe { + width: 100%; + height: 100%; + border: none; + } + } +} + +body:has(.app.dark) { + .help-panel-header { + img { + filter: invert(0.75); + } + } +} + +.help-panel-header { + height: 4rem; + padding: 0.5rem 1rem; + display: grid; + grid-template-columns: 1fr auto; + i { + font-size: 2rem; + } + a { + font-weight: bold; + display: flex; + gap: 1rem; + align-items: center; + } + .fa-chevron-down, + .fa-chevron-up { + padding: 1rem; + font-size: 1.25rem; + } + .bp5-collapse { + overflow: visible; + grid-column: span 2; + } + .bp5-collapse-body { + position: relative; + z-index: 1; + margin: -1rem; + margin-top: 0; + background: var(--main-bg); + a { + padding: 1rem; + line-height: 0; + &:hover { + background: var(--secondary-bg); + } + } + } +} + +.support-panel-content { + padding: 2rem; + padding-top: 0; + h1 { + font-size: 1.2rem; + font-weight: bold; + } + .row { + margin-bottom: 3rem; + } + a { + &.button { + display: block; + margin-top: 1rem; + text-decoration: none !important; + text-align: center; + border: 1.5px solid $dark_gray; + border-radius: 5px; + padding: 0.75rem; + line-height: 1.3rem; + b { + display: block; + font-size: 1.1rem; + text-transform: uppercase; + } + i { + font-size: 0.9rem; + color: $medium_gray; + font-weight: normal; + } + &:hover { + background: var(--secondary-bg); + } + } + &.inline { + text-decoration: underline; + margin-left: 0.25rem; + } + } +} + +.feedback { + p { + color: $dark_gray !important; + margin-bottom: 1rem !important; + font-weight: normal !important; + font-style: italic; + } + textarea { + height: 7.5rem; + } + button { + float: none; + } + .bp5-popover-wrapper { + margin-left: 1rem; + } +} + +.tours-panel-content { + min-height: 35rem; +} diff --git a/frontend/css/panels/jobs.scss b/frontend/css/panels/jobs.scss new file mode 100644 index 0000000000..9d79d1d65e --- /dev/null +++ b/frontend/css/panels/jobs.scss @@ -0,0 +1,78 @@ +@use "../variables" as *; +@use "sass:color"; + +.jobs-and-logs { + margin-top: 1rem; +} + +.jobs-panel { + .panel-content { + display: inline-block; + overflow-y: auto; + overflow-x: auto; + width: 100%; + padding: 0; + } +} + +.jobs-tab { + overflow-y: scroll; + max-height: 26rem; + &.bp5-popover { + margin-top: 1.5rem; + } + table { + text-align: left; + p { + padding: 1rem; + } + .job-name { + max-width: 20rem; + overflow: hidden; + text-overflow: ellipsis; + } + thead { + position: sticky; + top: 0; + z-index: 999; + background: var(--main-bg); + } + tr { + transform: scale(1); + } + th, + td { + white-space: nowrap; + font-size: 1.2rem; + padding: 0.75rem; + } + th { + text-transform: uppercase; + } + .right-align { + text-align: right; + } + .progress { + position: absolute; + top: 0; + left: 0; + height: 99%; + opacity: 0.5; + border-radius: 0; + pointer-events: none; + } + .fa-clock-o { + cursor: default !important; + } + } +} + +.jobs-panel-portal { + .bp5-popover-content { + padding: 0; + width: min(500px, 100vw - 1rem); + max-height: calc(100vh - 10rem); + overflow: hidden; + padding-top: 1rem; + } +} diff --git a/frontend/css/panels/location_info.scss b/frontend/css/panels/location_info.scss new file mode 100644 index 0000000000..447abb962b --- /dev/null +++ b/frontend/css/panels/location_info.scss @@ -0,0 +1,80 @@ +@use "../variables" as *; +@use "sass:color"; + +.location-info-panel { + .panel-content { + max-height: calc(100vh - 14rem); + overflow-y: auto; + overflow-x: hidden; + padding-top: 1rem; + padding-bottom: 0 !important; + .location-info-content { + display: grid; + gap: 1rem; + } + .location-actions { + display: grid; + gap: 1rem; + p { + margin-top: 1rem; + } + } + h2 { + font-family: 'Inknut Antiqua'; + font-weight: bold; + margin: 0; + font-size: 2rem; + } + .expandable-header { + text-transform: uppercase; + font-weight: bold; + font-size: 1.2rem; + } + label { + margin-top: 0; + &.no-items { + display: block; + } + } + button { + float: none; + } + .add-point { + margin-top: 2rem; + } + .point-search-item, + .plant-search-item{ + margin-left: -15px; + margin-right: -15px; + padding-left: 2rem; + padding-right: 2rem; + } + .sensor-history-table { + margin-left: -15px; + margin-right: -15px; + .table-row { + height: 4rem; + td:first-of-type { + padding-left: 2rem; + } + } + + } + .interpolated-soil-height { + .title { + display: inline; + font-weight: bold; + } + p { + display: inline; + margin-left: 1rem; + } + } + .photos-footer { + margin-top: 1rem; + .bp5-popover-wrapper { + margin-top: 3px; + } + } + } +} diff --git a/frontend/css/panels/logs.scss b/frontend/css/panels/logs.scss new file mode 100644 index 0000000000..13ccdb5744 --- /dev/null +++ b/frontend/css/panels/logs.scss @@ -0,0 +1,142 @@ +@use "../variables" as *; +@use "sass:color"; + +.logs-table-wrapper { + border: none; + .fa-trash { + display: none; + } + tr { + vertical-align: top; + &:hover { + background: $translucent2_white; + .fa-trash { + display: inline; + margin-left: 6px; + padding: 0.5rem; + line-height: 2rem; + } + .log-verbosity-saucer { + display: none; + } + } + } + .fa-filter { + border: 1px black solid; + border-radius: 50%; + padding: 0.25rem; + width: 2rem; + height: 2rem; + text-align: center; + line-height: 1.3rem; + margin-left: 5px; + } + .markdown { + p { + margin-top: 0; + } + a { + text-decoration: underline !important; + &:hover { + font-weight: normal; + } + } + h1, + h2, + h3, + h4, + h5, + h6 { + margin: 0; + font-size: 1.4rem; + font-weight: bold; + } + } + .notice { + font-style: italic; + text-align: center; + font-size: 1.4rem; + padding: 1rem; + } +} + +.logs-filter-menu { + .log-filters-grid { + grid-template-columns: 10rem 17rem; + } + .lines { + padding-bottom: 2rem; + } + .line { + position: absolute; + width: 0.5rem; + height: 86.5%; + border-right: 1.5px $light_gray solid; + .line-label { + position: absolute; + background: $white; + font-family: monospace; + } + } + .bp5-slider-unlabeled { + margin: 0 1rem; + } +} + +.logs-tab { + .search-row { + margin: 0 1rem; + } +} + +.logs-table { + display: block; + overflow: scroll; + max-height: 42rem; + .log-verbosity-saucer .saucer { + text-align: center; + margin-left: 6px; + p { + margin: 0; + } + } + button { + float: none; + } + thead { + text-align: left; + background: var(--main-bg); + } + thead, + th { + position: sticky; + top: 0; + z-index: 999; + } + td { + word-break: break-word; + .markdown { + p { + display: block; + font: inherit; + font-size: inherit; + text-overflow: inherit; + overflow: inherit; + width: inherit; + white-space: inherit; + } + } + } + td:nth-child(1), + td:nth-child(3), + td:nth-child(4) { + white-space: nowrap; + } +} + +.link-to-logs { + margin-top: 2rem; + margin-bottom: 4rem; + text-align: center; + font-style: italic; +} diff --git a/frontend/css/panels/messages.scss b/frontend/css/panels/messages.scss new file mode 100644 index 0000000000..238429e18b --- /dev/null +++ b/frontend/css/panels/messages.scss @@ -0,0 +1,128 @@ +@use "../variables" as *; +@use "sass:color"; + +.problem-alerts { + padding: 1rem; +} + +.problem-alert { + padding: 1.5rem 2rem; + border-radius: 1rem; + box-shadow: 0px 2px 8px $translucent2; + background: $translucent2_white; + display: grid; + gap: 0.5rem; + .fa-check-square { + color: $green; + } + .fa-info-circle { + color: $blue; + } + &.bulletin-alert { + img, iframe { + margin: 1rem auto; + width: 100%; + border-radius: 5px; + } + } + .problem-alert-title { + align-items: baseline; + h3 { + display: inline; + font-size: 1.5rem; + font-weight: bold; + margin: 0; + font-family: 'Inknut Antiqua'; + } + p { + display: inline; + font-size: 1.2rem; + white-space: nowrap; + } + } + .problem-alert-content { + display: grid; + gap: 0.5rem; + .markdown { + p { + display: block; + text-overflow: inherit; + overflow: inherit; + width: inherit; + white-space: inherit; + } + ul { + list-style-type: disc !important; + padding-inline-start: 40px; + } + } + p { + margin-bottom: 0.75rem !important; + font-size: 1.4rem; + line-height: 2rem; + } + label { + margin-top: 0.5rem; + } + .row { + margin-top: 2rem; + } + .tour-list { + margin: 0; + label { + max-width: 57%; + } + } + .link-button { + color: $off_white !important; + font-weight: bold !important; + justify-self: left; + margin: 0; + } + a { + text-decoration: underline; + outline: none !important; + cursor: pointer !important; + &:link { + font-weight: 500; + } + &:hover { + color: $off_white; + } + &.fb-button { + text-decoration: none !important; + } + } + } + .documentation-card { + .fa-question { + cursor: default !important; + margin-left: 0.25rem; + margin-right: 0.25rem; + } + } +} + +.firmware-alerts { + max-width: 600px; +} + +.firmware-hardware-choice-table { + margin: 2rem; + margin-top: 1rem; + width: 93%; + border: 1px solid $gray; + font-size: 1.2rem; + th { + background: $light_gray; + font-weight: normal; + } + td { + background: $off_white; + color: $medium_gray; + } + code { + background: $light_gray; + color: $dark_gray; + } +} diff --git a/frontend/css/panels/peripherals.scss b/frontend/css/panels/peripherals.scss new file mode 100644 index 0000000000..e1737d6d5d --- /dev/null +++ b/frontend/css/panels/peripherals.scss @@ -0,0 +1,116 @@ +@use "../variables" as *; +@use "sass:color"; + +.electronics-box-3d-model { + max-width: calc(100vw - 4rem); + width: 100%; + .led-label, + .btn-label { + display: block; + margin: auto; + width: max-content; + color: $off_white; + font-size: 1rem; + background: $dark_gray; + padding: 0.25rem 0.5rem; + border-radius: 0.5rem; + white-space: normal; + max-height: 3.4rem; + overflow-y: hidden; + font-weight: 700; + line-height: 1; + text-align: center; + user-select: none; + &.hovered { + background: $black; + } + &.unbound { + background: $placeholder_gray; + } + } + .led-label { + max-width: 7rem; + } + .btn-label { + max-width: 5.8rem; + } + .filter-search { + max-width: 5.5rem; + .fa-caret-down { + bottom: -0.5rem; + right: 0.25rem; + color: $off_white; + } + button { + padding: 0.25rem; + background: $dark_gray !important; + border-radius: 0.5rem; + height: max-content !important; + min-height: 0; + &:hover { + background: $dark_gray !important; + } + } + span { + color: $off_white; + white-space: normal !important; + text-overflow: revert !important; + font-weight: 700; + font-size: 1rem; + text-align: center; + margin-right: 0.5rem; + } + } + div { + z-index: 0 !important; + } + canvas { + height: 23rem; + } +} + +.peripheral-list { + label { + margin-top: 0 !important; + } + .slider-container { + padding-left: 0.5rem; + padding-right: 1rem; + .bp5-slider { + min-width: 100%; + } + } +} + +.peripheral-edit-grid { + grid-template-columns: 1fr 20% 20% auto; +} + +.box-top-2d-wrapper { + margin-top: 2rem; + .box-top-leds, + .box-top-buttons { + display: grid; + text-align: center; + } + .box-top-buttons { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + .box-top-leds { + margin: auto; + width: 80%; + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + .fast-blink { + animation: fill-blink 0.2s linear infinite; + } + .slow-blink { + animation: fill-blink 2s linear infinite; + } +} + +.pinned-sequence-list { + .help-icon { + color: $gray !important; + } +} diff --git a/frontend/css/panels/photos.scss b/frontend/css/panels/photos.scss new file mode 100644 index 0000000000..39fc688c85 --- /dev/null +++ b/frontend/css/panels/photos.scss @@ -0,0 +1,148 @@ +@use "../variables" as *; +@use "sass:color"; + +.photos-panel { + .panel-title { + margin: 0; + } + .expandable-header { + margin: 0; + } + .panel-content { + display: grid; + gap: 1rem; + padding: 1.5rem; + .filters-enabled-warning { + float: right; + } + .fa-exclamation-triangle { + font-size: 1.3rem; + } + .photo-filter-settings { + .filter-controls { + .banner { + display: none; + } + &.single-image-mode, + &.image-layer-disabled { + opacity: 0.40; + * { + pointer-events: none; + } + .banner { + display: inline-block; + position: absolute; + top: 25%; + left: -2.5%; + z-index: 10; + width: 105%; + padding: 0.5rem; + background-color: $dark_gray; + opacity: 0.90; + color: $off_white; + font-size: 1.8rem; + vertical-align: middle; + text-align: center; + } + } + } + } + .capture-settings { + .image-size-inputs { + .resolution-change-warning { + i { + color: $darkest_red; + margin-right: 1rem; + } + p { + display: inline; + margin-right: 0.5rem; + font-size: 1.2rem; + } + .click { + cursor: pointer; + font-weight: bold; + &:hover { + color: $black; + } + } + } + } + .update-take-photo { + margin-top: 1rem; + .version-string { + display: inline; + margin-left: 1rem; + } + } + } + .imaging-data-management { + label { + line-height: 3rem; + } + .highlight-modified-toggle { + label { + margin-top: 0.5rem; + margin-left: 0.5rem; + } + } + } + .farmware-form { + button { + float: none; + } + } + .weed-detection-grid .bp5-slider { + grid-column: span 2; + } + } + .setting { + display: grid; + grid-template-columns: auto 1fr; + gap: 0.5rem; + } + .title-help { + font-size: 1.3rem; + .fa-question-circle { + position: absolute; + top: -3rem; + right: 0; + font-size: 1.4rem; + } + a { + text-decoration: none !important; + } + .fa-external-link { + margin-left: 1rem; + } + .update { + display: inline; + p { + display: inline; + margin-left: 1rem; + } + .fa-refresh { + display: inline; + margin-left: 1rem; + color: $dark_gray; + } + } + .title-help-text.open { + margin-bottom: 0; + } + } +} + +.camera-calibration, +.weed-detector { + svg { + background: $black; + } + p { + font-size: 1.2rem; + font-style: italic; + } + .camera-calibration-setting-grid { + grid-template-columns: 65% auto; + } +} diff --git a/frontend/css/panels/plants.scss b/frontend/css/panels/plants.scss new file mode 100644 index 0000000000..de0a1f7c67 --- /dev/null +++ b/frontend/css/panels/plants.scss @@ -0,0 +1,109 @@ +@use "../variables" as *; +@use "sass:color"; + +.plants-panel-settings-menu { + label { + line-height: 3rem; + vertical-align: bottom; + margin-bottom: 0; + } + .bp5-popover-wrapper { + display: inline; + margin-left: 0.5rem; + line-height: 3rem; + } +} + +.crop-search-result-wrapper { + .openfarm-search-results-wrapper { + display: grid; + gap: 1.5rem 1rem; + grid-template-columns: repeat(2, 1fr); + } + .plant-catalog-tile { + position: relative; + cursor: pointer; + text-align: center; + border-radius: 5px; + box-shadow: 0px 4px 7px 2px $translucent; + background: var(--secondary-bg); + transition: transform 0.2s ease; + padding: 0; + &:hover { + box-shadow: 0px 5px 8px 2px rgba(0, 0, 0, 0.25); + transform: translateY(-1px); + } + label { + position: absolute; + cursor: pointer; + left: 0; + bottom: 0; + right: 0; + padding: 3rem 0.6rem 0.2rem; + background: linear-gradient(transparent, $translucent15 50%, $translucent4); + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + color: $white; + font-size: 1.2rem !important; + } + } +} + +.crop-info-panel { + .panel-header { + position: inherit; + background-size: 144% !important; + background-repeat: no-repeat !important; + background-position: top center !important; + min-height: 125px; + color: $off_white; + .panel-title { + overflow: visible; + .title { + font-family: "Inknut Antiqua"; + font-weight: bold; + } + .bp5-popover-wrapper { + position: absolute; + right: 0; + } + .plus-grid-btn { + position: absolute; + bottom: -8.5rem; + right: 1.5rem; + box-shadow: inset 0 0 0 1.5px $off_white !important; + color: $off_white !important; + } + } + } + .panel-content { + overflow-y: auto; + overflow-x: hidden; + padding: 1rem; + .non-empty-state { + gap: 1rem; + } + .crop-companions { + display: flex; + gap: 0.75rem 1rem; + flex-wrap: wrap; + } + .companion { + display: inline-block; + text-decoration: none !important; + background: var(--secondary-bg); + border-radius: 2rem; + display: flex; + align-items: center; + gap: 0.5rem; + padding: .25rem .75rem .25rem .5rem; + p { + font-weight: bold; + text-transform: none; + } + } + } + .crop-info-field-data { + font-size: 1.3rem; + } +} diff --git a/frontend/css/panels/points.scss b/frontend/css/panels/points.scss new file mode 100644 index 0000000000..c4519a2641 --- /dev/null +++ b/frontend/css/panels/points.scss @@ -0,0 +1,13 @@ +@use "../variables" as *; +@use "sass:color"; + +.point-info-panel { + .panel-title { + color: $dark_gray; + } + .panel-content { + .delete-row { + margin: 1.5rem; + } + } +} diff --git a/frontend/css/panels/regimens.scss b/frontend/css/panels/regimens.scss new file mode 100644 index 0000000000..0453bb7e9f --- /dev/null +++ b/frontend/css/panels/regimens.scss @@ -0,0 +1,99 @@ +@use "../variables" as *; +@use "sass:color"; + +// Bulk Scheduler +.bulk-scheduler { + .regimen-days-label { + text-align: center; + } + .week-grid-meta-buttons { + text-align: right; + justify-content: end; + } + @media screen and (max-width: 974px) { + margin-left: 15px; + margin-right: 15px; + } +} + +.bulk-scheduler-add { + margin: 1.5rem !important; +} + +.schedule-regimen-item { + justify-self: center; +} + +.regimen-event { + background: $gray; + border-radius: 0.5rem; + overflow: hidden; + box-shadow: 0 0 1rem $translucent2; + color: $dark_gray; +} + +.regimen-event-titlebar { + padding: 0.5rem 0.75rem; + font-size: 1.2rem; + font-weight: bold; + text-transform: uppercase; +} + +.regimen-item-variables { + background: $translucent8_white; + padding: 1rem; +} + +.regimen-event-variable { + align-items: baseline; + i { + cursor: default !important; + } +} + +.designer-regimen-editor-panel-content { + padding: 0; +} + +.regimen-editor-content { + padding: 0 1rem 1rem; + .location-form-content { + .row { + white-space: nowrap; + .trash { + display: inline; + float: none; + } + } + } + .sequence-section-header { + &:hover { + background: $translucent!important; + } + } +} + +.designer-regimen-scheduler-panel { + .panel-content { + display: inline-block; + max-height: calc(100vh - 16.5rem); + overflow-y: auto; + overflow-x: hidden; + width: 100%; + } +} + +.designer-regimen-editor-panel { + .panel-header { + color: $dark_gray; + } + .panel-content { + max-height: calc(100vh - 14rem); + overflow-y: auto; + overflow-x: hidden; + scrollbar-width: none; + label { + margin-top: 0; + } + } +} diff --git a/frontend/css/panels/select.scss b/frontend/css/panels/select.scss new file mode 100644 index 0000000000..9a95927d2d --- /dev/null +++ b/frontend/css/panels/select.scss @@ -0,0 +1,17 @@ +@use "../variables" as *; +@use "sass:color"; + +.plant-selection-panel { + .panel-action-buttons { + padding: 1rem; + .quick-select { + align-items: start; + } + } + .panel-content { + padding: 0; + .tool-slot-search-item { + padding: 0 2rem !important; + } + } +} diff --git a/frontend/css/panels/sensors.scss b/frontend/css/panels/sensors.scss new file mode 100644 index 0000000000..f6c63c499e --- /dev/null +++ b/frontend/css/panels/sensors.scss @@ -0,0 +1,123 @@ +@use "../variables" as *; +@use "sass:color"; + +.add-sensor-reading-menu { + width: 25rem; + button { + margin-left: auto; + } +} + +.sensor-readings-plot { + max-height: 300px; + stroke: var(--text-color); + font-size: 60px; + font-weight: 100; +} + +.sensor-history-table { + text-align: left; + font-size: 1.2rem; + background: $translucent1_white; + border-radius: 0.5rem; + th, + td { + width: 1%; + } + tr { + &.previous { + color: $medium_gray; + } + &.selected { + background: $translucent1_white; + } + } + .sensor-history-table-contents { + max-height: 20rem; + overflow-y: auto; + } +} + +.sensor-history-footer { + display: flex; + justify-content: space-between; + .date { + span { + white-space: nowrap; + } + label { + margin-right: 0.5rem; + } + } + .location { + display: flex; + label { + margin-left: 1rem; + margin-right: 0.5rem; + } + } +} + +.sensors-widget { + p { + margin-top: 0.75rem; + } + .sensor-reading-display { + border-style: solid; + border-color: $dark_gray; + border-width: 0.1px; + height: 2rem; + width: 100%; + margin-top: 0.5rem; + &.moisture-sensor { + background: linear-gradient(to right, rgba($blue, 0) 20%, $blue 80%, rgba($blue, 0) 85%); + } + &.digital { + .indicator { + text-align: center; + } + } + .indicator { + position: relative; + background: $dark_gray; + height: 2rem; + span { + position: relative; + top: -0.1rem; + font-size: 1.3rem; + } + } + } +} + +.sensor-form-grid { + grid-template-columns: 2fr 1fr 1fr auto; +} + +.sensors-panel-content { + padding: 1.5rem; +} + +.sensors-panel { + display: grid; + gap: 1rem; + .panel-header { + display: grid; + grid-template-columns: 1fr auto; + gap: 1rem; + align-items: center; + .panel-title { + margin: 0; + height: unset; + } + } + .panel-content { + label { + margin-top: 0; + } + } + .sensor-history-widget { + display: grid; + gap: 1rem; + } +} diff --git a/frontend/css/steps.scss b/frontend/css/panels/sequence_steps.scss similarity index 80% rename from frontend/css/steps.scss rename to frontend/css/panels/sequence_steps.scss index fa49e4a97f..9a3ffb4117 100644 --- a/frontend/css/steps.scss +++ b/frontend/css/panels/sequence_steps.scss @@ -1,44 +1,13 @@ +@use "../variables" as *; +@use "sass:color"; + // Styles for ALL STEPS .step-wrapper { - box-shadow: 0px 0px 10px $gray; - border-radius: 3px; - .bottom-content { - display: flex; - fieldset { - text-align: right; - } - .channel-options { - flex: 1; - margin-top: 2rem; - } - .channel-fields { - flex: 1; - input, - label { - display: inline-block; - width: auto; - cursor: pointer; - } - input { - margin-left: 1rem; - box-shadow: none; - } - input[type="radio"] { - margin-right: 0.25rem; - margin-bottom: 0.25rem; - margin-top: 0; - vertical-align: middle; - } - form { - padding-top: 0.5rem; - .options { - display: inline; - label { - margin-left: 0.25rem; - } - } - } - } + box-shadow: 0px 0px 1rem $translucent1; + border-radius: 0.5rem; + overflow: hidden; + fieldset { + text-align: right; } pre { border: none; @@ -46,6 +15,10 @@ } } +.sequence-step-radio-grid { + justify-content: left; +} + .step-header { letter-spacing: .05rem; padding: .75rem 1rem; @@ -61,7 +34,6 @@ .step-comment { display: inline; width: 100%; - overflow: hidden; ::-webkit-input-placeholder { color: $dark_gray; font-weight: bold; @@ -86,7 +58,8 @@ } input { width: 100%; - padding: 0rem; + margin-left: -0.5rem; + padding-left: 0.5rem; background-color: transparent; box-shadow: none; color: $dark_gray; @@ -209,32 +182,9 @@ display: inline-block; } .prompt-wrapper { - margin-top: 1rem; + margin: 0.5rem 0; textarea { - font-size: 1rem; - height: 8rem; - } - .generate-code { - display: flex; - height: 3rem; - .feedback { - width: 7rem; - margin-top: 0.75rem; - transition: opacity 2s ease; - i:first-of-type { - margin-right: 0.5rem; - } - } - p { - display: inline-block; - width: 100%; - line-height: 1rem; - color: $medium_gray; - padding: 0.75rem; - } - button { - margin-top: 0.5rem; - } + height: 10rem; } } } @@ -316,7 +266,8 @@ &.assertion-step { background: $light_purple; .lua-input { - margin-bottom: 1rem; + border-radius: 0.5rem; + overflow: hidden; } } &.lua-step { @@ -325,21 +276,8 @@ } &.execute-step { background: $light_gray; - padding-bottom: 1rem; - .row { - margin: 0; - } .locals-list { - margin-left: -1rem; - margin-right: 0; - } - .location-form:not(:first-of-type) { - margin-top: 1rem; - } - .location-form { - .row:not(:first-of-type) { - margin-top: 1rem; - } + margin: 0; } &.pinned-view { .location-form { @@ -457,3 +395,46 @@ font-size: 1.3rem; } } + +.lua-input { + .lua-editor { + height: 20rem; + &.full { + height: 40rem; + } + &.expanded { + height: 70vh; + } + } + .char-limit { + padding: 0.5rem; + } + textarea { + height: 100% !important; + font-family: monospace; + pointer-events: revert !important; + font-size: 1.4rem; + padding: 0 3rem 0 2.6rem; + line-height: 1.9rem; + } +} + +.move-absolute-form { + display: flex; + .location-form, + .expandable-header { + padding-left: 15px; + padding-right: 15px; + } + .input-line { + flex: auto; + } + .more-options { + min-width: 110px; + margin-top: 0.5rem; + } + .custom-coordinate-form { + margin-left: -2.5rem; + margin-right: -11.25rem; + } +} diff --git a/frontend/css/sequences.scss b/frontend/css/panels/sequences.scss similarity index 73% rename from frontend/css/sequences.scss rename to frontend/css/panels/sequences.scss index cf30bb2897..bea94c7d36 100644 --- a/frontend/css/sequences.scss +++ b/frontend/css/panels/sequences.scss @@ -1,16 +1,70 @@ +@use "../variables" as *; +@use "sass:color"; + .sequence-page { max-width: unset; padding: 8.6rem 0rem 0; - background: $dark_gray; + .sequences-page-grid { + grid-template-columns: 1fr 2fr 1fr; + align-items: stretch; + gap: 0; + } +} + +.designer-sequence-list-panel { + .panel-content { + max-height: calc(100vh - 16.5rem); + overflow-y: scroll; + overflow-x: hidden; + @media screen and (max-width: $mobile_max_width) { + max-height: calc(100vh - 19rem); + } + a { + text-decoration: none !important; + p { + font-size: 1.4rem; + font-weight: normal !important; + } + } + .folders-panel { + margin-left: -1rem; + margin-right: -1rem; + } + } +} + +.designer-sequence-editor-panel { + .panel-header { + color: $dark_gray; + } + .panel-content { + .sequence-editor-content { + overflow: hidden; + @media screen and (max-width: 767px) { + margin-left: 5px; + padding-right: 0; + } + } + .add-command-button-container { + display: inline; + } + .drag-drop-area { + display: none; + } + label { + margin-top: 0; + } + @media screen and (max-width: 767px) { + padding: 0; + } + } } .sequence-editor-panel { - margin-left: -3.5rem; - margin-top: 0.4rem; - height: calc(100vh - 8.95rem); background: $light_gray; - padding: 0 2rem; - overflow: hidden; + padding: 0 1rem; + height: calc(100vh - 8.5rem); + overflow: scroll; @media screen and (max-width: 767px) { display: none; &.open { @@ -24,14 +78,6 @@ h3 { margin-top: 1rem; } - .sequence-editor-tools { - .row { - flex: 1; - } - .button-group { - width: unset; - } - } .bp5-popover-target .saucer { float: left; } @@ -48,17 +94,12 @@ margin-bottom: 2.5rem; padding-left: 0 !important; } - .button-group { - margin-right: 15px; - margin-top: -1rem; - } .title-help-text { padding-left: 15px; padding-right: 15px; } } .sequence-editor-content { - margin-right: -15px; .sequence-description { .description-input { padding-right: 1rem; @@ -80,16 +121,7 @@ .sequence-editor-panel { .sequence-section-header { &:hover { - background: $lighter_gray !important; - } - } -} - -.designer-sequence-editor-panel, -.sequence-editor-panel { - .assertion-step { - .locals-list { - margin: 1rem; + background: $translucent1!important; } } } @@ -114,37 +146,14 @@ .sequence-editor-content, .regimen-editor-content { - margin-right: -10px; - margin-left: -10px; - .regimen { - padding-left: 1rem; - } - .sequence { - margin-bottom: 1rem; - } @media screen and (max-width: 767px) { margin-left: 5px; margin-right: 0; padding-right: 2rem; - .regimen { - padding-left: 0; - } - } -} - -.regimen-editor-content { - @media screen and (max-width: 767px) { - padding-right: 0; } } .sequence-editor-content { - hr { - margin-right: 15px; - margin-bottom: 0; - margin-top: 0.5rem; - margin-left: 15px; - } .import-banners { margin-bottom: 1rem; } @@ -239,44 +248,22 @@ } } } - .padding { - height: 12rem; - } } .sequence-editor-sections { - overflow-y: auto; - overflow-x: hidden; - max-height: calc(100vh - 17.5rem); - pre { - margin-top: 2rem; - } - .sequence { - pre { - margin-top: 0; - } - } .sequence-description { - position: relative; - width: 100%; - padding-right: 3rem; + background: var(--secondary-bg); + border-radius: 0.5rem; textarea { - margin-left: 2rem; - height: 100%; - box-shadow: 0px 0px 10px $gray; + height: 10rem; padding-bottom: 2rem; } + .markdown { + padding: 1rem; + } .description-editor-tools { - position: absolute; - bottom: 0; - right: 3.5rem; - .fa-pencil { - margin-bottom: 1rem; - } .char-limit { font-size: 1rem; - line-height: 1.75rem; - margin-left: 1rem; color: $medium_gray; &.over { color: $darkest_red; @@ -286,37 +273,17 @@ } } -.sequence-description-wrapper { - position: relative; - .fa-magic, - .fa-spinner { - position: absolute; - top: 1rem; - right: 6.5rem; - } - .fa-pencil, - .fa-eye { - position: absolute; - top: 1rem; - right: 4rem; - } -} - .sequence-description { .markdown { display: block; min-height: 10rem; max-height: 20rem; overflow-y: auto; - border: 2px solid $gray; - padding: 0.5rem; - margin-left: 1.5rem; - margin-right: -1.5rem; + border-radius: 0.5rem; p { display: revert; margin: revert; width: 100%; - color: $dark_gray; overflow: revert; white-space: revert; text-transform: revert; @@ -399,50 +366,16 @@ } } -.sequence-description { - h1, - h2, - h3, - h4, - h5, - h6 { - color: $black; - } -} - -.sequence-item-help { - h1, - h2, - h3, - h4, - h5, - h6 { - color: $white; - } -} - .designer-sequence-list-panel-content { .panel-top { display: none; } .sequence-section-header { - margin-left: -15px; - margin-right: -10px; - padding-right: 0.85rem; - .fa-caret-up, - .fa-caret-down { - margin: 1rem !important; - margin-top: 0.5rem !important; - margin-left: 1rem !important; - margin-right: 1.2rem !important; - } - &.featured-sequence-header { - .fa-caret-up, - .fa-caret-down { - position: absolute; - right: 0.75rem; - } - } + display: grid; + gap: 1rem; + grid-template-columns: 1fr auto; + grid-auto-flow: column; + align-items: center; label { margin: 0; font-size: 1.3rem; @@ -458,6 +391,7 @@ } .sequence-list-item { &:hover { + background: var(--secondary-bg); .show-on-hover { display: flex !important; } @@ -472,19 +406,6 @@ } } -.folder-icon-wrapper { - display: inline; - button { - height: 2rem; - margin-left: 1rem; - margin-top: 0.75rem; - padding: 0 0.75rem; - i { - display: block; - } - } -} - .farm-event-form-content, .regimen-editor-content, .designer-sequence-list-panel-content, @@ -492,11 +413,12 @@ .sequence-section-header { position: relative; cursor: pointer; - padding-left: 1.5rem; + margin: 0 -1rem; + padding: 0 1rem; height: 4rem; line-height: 3.75rem; &:hover { - background: $light_gray; + background: $translucent1!important; } label { cursor: pointer; @@ -504,85 +426,11 @@ } .fa-caret-up, .fa-caret-down { - float: right; - line-height: 3rem; font-size: 2rem; - margin-right: 2rem; - margin-top: 0.5rem; - color: $dark_gray; - } - .add-variable-btn { - position: absolute; - right: 4rem; - width: 2.8rem; - height: 2rem; - line-height: 1rem; - margin-top: 0.75rem; - margin-right: 0.25rem; - padding: 0.25rem; - .fa-plus { - vertical-align: top; - padding-top: 0.1rem; - padding-left: 0.1rem; - font-size: 1.5rem; - } + margin-right: 0.5rem; } .bp5-popover-wrapper { - position: absolute; - top: 0; - right: 4rem; - .add-variable-btn { - position: relative; - right: unset; - text-align: center; - } - .bp5-popover-content { - background: $off_white; - height: 100%; - align-content: center; - .add-variable-options { - text-align: center; - line-height: 0; - } - .fb-button { - float: none; - height: 2rem; - } - } - } - } -} - -.designer-regimen-editor-panel-content { - padding: 0; -} - -.regimen-editor-content { - margin-left: -1rem; - .regimen { - margin-left: 2rem; - padding-right: 3rem; - } - .button-group { - padding-right: 1rem; - } - .regimen-editor-tools { - margin-left: 1rem; - margin-right: 1rem; - } - .location-form-content { - .row { - white-space: nowrap; - padding-right: 1rem; - .trash { - display: inline; - float: none; - } - } - } - .sequence-section-header { - &:hover { - background: $light_gray !important; + display: contents; } } } @@ -596,57 +444,19 @@ } .sequence-editor-tools { - display: flex; - margin-left: -1rem; - margin-right: -1rem !important; - padding-right: 2rem; - .row { - margin-left: 0; - } - .title, - input { - flex: 1; - } input { margin: 0.75rem; - margin-left: 2.75rem; } .title { padding: 1.25rem; font-size: 1.6rem; font-weight: bold; - margin-left: 1.5rem; } &.page { i { - color: $white; + color: $dark_gray; &.inactive { - color: $gray; - } - } - .publish-button { - span { - margin: 0; - } - } - .color-picker { - span, - i { - margin: 0; - } - } - &.blue, - &.yellow, - &.orange, - &.purple, - &.pink, - &.gray { - i, - .title { - color: $dark_gray; - &.inactive { - color: $placeholder_gray; - } + color: $placeholder_gray; } } &.blue { @@ -678,23 +488,7 @@ .sequence-editor-tools, .regimen-editor-tools { - margin-right: 15px; - @media screen and (max-width: 767px) { - margin-right: 10px; - } - hr { - margin: 0.5rem 0; - } - .sequence-name { - margin-top: 1rem; - } - &.preview { - .filter-search, - .input, - input { - pointer-events: none; - } - } + border-bottom: 1.5px solid $translucent1; } .preview-variables { @@ -703,34 +497,26 @@ } } -.regimen-editor-tools { - .button-group { - margin-bottom: 1rem; - .save-btn { - margin-left: 0.5rem; - } - } -} - .locals-list { - margin-left: 1.5rem; - margin-right: 2rem; - .location-form { - label { - margin-top: 0; - } + display: grid; + gap: 1rem; + .location-form, .custom-coordinate-form { + background: var(--secondary-bg); + padding: 1rem; + border-radius: 0.5rem; + } + .default-value-form, .custom-coordinate-form { + margin: 0 3.5rem; } .location-form-content { - .row { - display: flex; - } .bp5-popover-wrapper { display: inline-block; margin-left: 1rem; } .variable-icon { + width: 2.5rem; + text-align: center; cursor: default !important; - margin-top: 0.5rem; } p { display: inline; @@ -739,24 +525,15 @@ .variable-label { font-weight: bold; } - .fa-trash { - color: $darkest_red; - margin-top: 0.5rem; - margin-left: -1rem; - } - .numeric-variable-input, - .text-variable-input { - padding-right: 0.5rem; - padding-left: 2.5rem; + .no-location-coordinate-input-boxes, + .no-default-value-form { + display: none; } } -} - -.regimen { - width: 100%; - height: calc(100vh - 25rem); - overflow-y: auto; - overflow-x: hidden; + .numeric-variable-input, + .text-variable-input { + margin-right: 3.5rem; + } } .license { @@ -769,9 +546,7 @@ } .sequence-steps { - margin-right: 30px; - margin-left: 10px; - margin-top: 10px; + margin-right: 2.5rem; .sequence-step { &.hovered { box-shadow: 0px 0px 15px #ff7700; @@ -791,37 +566,22 @@ } .step-button-cluster { - margin-right: -15px; overflow-y: auto; overflow-x: hidden; max-height: calc(100vh - 8rem); - .text-input-wrapper { - input { - padding-left: 0.5rem !important; - } - .fa-times { - color: $red !important; - &:hover { - color: $medium_dark_red !important; - } - } + .commands, .pinned-sequences { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; } .step-button { label { cursor: pointer; } - .step-block-wrapper { - margin-bottom: 1rem; - } .clustered { - margin: 0.5rem; float: left; - margin-bottom: 0.25rem; box-shadow: none; - padding-bottom: 0; - label { - margin-bottom: 3px; - } + color: $dark_gray; &.gray { background: $light_gray !important; } @@ -837,7 +597,6 @@ background: $dark_gray; margin-bottom: 1rem; border-radius: 5px; - box-shadow: 0px 0px 10px $gray; overflow: unset; padding: 0.75rem; padding-top: 0.5rem; @@ -846,12 +605,9 @@ height: 10rem; overflow-y: auto; overflow-x: hidden; - padding-top: 0.5rem; - padding-bottom: 0.5rem; } .text-input-wrapper { - margin: 0; - border-bottom-color: $white !important; + border-bottom-color: $translucent8_white !important; input { color: $white; } @@ -859,14 +615,13 @@ &:after { background: $white !important; } - .fa-times { - color: $red; - } } } } .step-button-cluster-panel { + padding: 1rem; + background: $dark_gray; @media screen and (max-width: 767px) { display: none; margin-left: 15px; @@ -875,10 +630,6 @@ display: block; } } - .row { - margin-left: -2.5rem; - margin-right: 3rem; - } h3 { margin-top: 1rem; margin-left: -1rem; @@ -931,10 +682,6 @@ cursor: pointer; } } - i { - float: right; - margin-top: 0.75rem; - } } .folders-panel { @@ -966,31 +713,25 @@ font-size: 1.5rem; line-height: 1.5rem; margin-left: 0.75rem; - filter: drop-shadow(0 0 0.2rem $dark_green); + filter: drop-shadow(0 0 0.25rem var(--main-bg)); text-align: center; } } .folder-button-cluster { display: flex; - .cluster-icon { - padding: 0.5rem; - border-radius: 3px; - line-height: 2rem !important; - color: $off_white; - &:hover { - background: $medium_gray; + .fb-icon-button { + .fa-stack-1x { + line-height: unset; + margin-top: -1.25rem; } .fa-stack-2x { width: unset; line-height: unset; } - .fa-stack-1x { - width: unset; - } } } ul { - margin-bottom: 0; + margin: 0; } .folder-drop-area { height: 0; @@ -1023,18 +764,18 @@ } .folders { .folder > div:not(:first-child), ul { - margin-left: 1rem; + margin-left: 2rem; } .folder-list-item { &.moving { .drop-visual { position: absolute; - width: 75%; - height: 75%; - margin-top: 0.5rem; - margin-left: 3rem; - border: 2px dashed $gray; - background: $white; + top: 0.5rem; + bottom: 0.5rem; + left: 2.75rem; + right: 3rem; + border: 1px dashed $gray; + border-radius: 3px; } } &.hovered { @@ -1057,9 +798,7 @@ position: relative; width: 100%; height: 3.5rem; - border-bottom: 1px solid $light_gray; cursor: pointer; - background: $lighter_gray; border-left: 4px solid transparent; &.active { border-left: 4px solid $dark_gray; @@ -1073,19 +812,10 @@ width: 3rem; font-size: 1.1rem; } - .folder-settings-icon { - position: absolute; - right: 0; - .bp5-popover-target { - i { - color: $dark_gray; - } - } - } .fb-button-popover-wrapper { margin-right: 0.25rem; button { - margin-top: 0.65rem; + margin-top: 0.75rem; } } .fa-question-circle, @@ -1115,7 +845,7 @@ display: block; } } - i { + i:not(.fb-icon-button) { margin: 0; line-height: 3.5rem; width: 3rem; @@ -1204,7 +934,7 @@ .folder-list-item { padding-left: 0; .bp5-popover-wrapper.color-picker { - margin-left: 3rem; + margin-left: 2.5rem; } .color-picker { .icon-saucer { @@ -1215,7 +945,7 @@ } } .matched { - background: color.adjust($yellow, $alpha: -0.5); + background: $translucent2_white; } .non-empty-state { width: 100%; @@ -1239,27 +969,15 @@ } p { line-height: 1.75rem; - color: $off_white; - } - .fa-exclamation-triangle { - color: $orange; } } - hr { - border-color: $medium_gray; - margin-left: -1rem; - margin-top: 1rem; - margin-bottom: 1rem; - width: 106.5%; - } label { display: block; - color: $off_white; } img { max-width: 100%; border-radius: 5px; - border: 2px solid $medium_gray; + border: 2px solid var(--border-color); } } @@ -1270,24 +988,11 @@ } } -.folder-settings-icon, -.sequence-item-description, -.sequence-item-action-menu { - .bp5-popover-arrow-fill { - fill: $dark_gray; - } - .bp5-popover-content { - background: $dark_gray; - } -} - .sequence-item-description { .bp5-popover-content { max-height: 20rem; overflow-y: auto; width: 32rem; - color: $white; - background: $dark_gray; } } @@ -1301,16 +1006,11 @@ } .sequence-list-panel { - padding-top: 0.4rem; - margin-right: 5px; @media screen and (max-width: 1075px) { margin-left: 15px; } .empty-state { display: none; - .empty-state-graphic { - margin-top: 25%; - } } @media screen and (max-width: 767px) { margin-left: 15px; @@ -1322,9 +1022,6 @@ display: block; } } - .panel-top { - margin: 0; - } } .designer-regimen-list-panel { @@ -1337,7 +1034,6 @@ .panel-content { overflow-y: auto; overflow-x: hidden; - margin-left: -15px; padding: 0; } } @@ -1379,6 +1075,9 @@ z-index: 9; width: 100%; height: 0; + button { + margin: 0; + } &.first, &.middle, &.last { @@ -1400,7 +1099,6 @@ .add-command { transform: rotate(-45deg); margin-top: -1.5rem; - margin-right: 1.5rem; border-top-left-radius: 0; i { transform: rotate(45deg); @@ -1409,22 +1107,26 @@ } &.first { .add-command { - margin-top: -0.25rem; - margin-right: 1.5rem; border-top-left-radius: 0; } } &.last { .bp5-collapse { - margin-left: 1rem; - margin-right: 3rem; + margin-top: 1rem; + margin-right: 2.5rem; } .add-command { - margin-top: -1.75rem; - margin-right: 0.5rem; + margin-top: -2rem; border-bottom-left-radius: 0; } } + &.last { + &.open { + .add-command { + margin-top: -1.5rem; + } + } + } &.only { position: relative; margin: auto; @@ -1435,7 +1137,7 @@ padding-right: 3rem; } .add-command { - float: none; + display: none; } } @media screen and (max-width: 767px) { @@ -1446,11 +1148,9 @@ } &.open { position: relative; - height: 100%; .add-command { position: absolute; - top: 50%; - right: -4rem; + right: -2.5rem; transform: rotate(-45deg); i { transform: rotate(0); @@ -1525,3 +1225,128 @@ $border_width: 1.4px; } } } + +.sequence-publish-menu, +.sequence-share-menu { + max-width: 300px; + button { + display: block; + margin: auto; + float: none; + margin-top: 1rem; + } +} + +.sequence-publish-menu { + .sequence-publish-message { + text-align: left; + p { + padding-bottom: 1rem; + line-height: 1.5rem; + } + a { + display: inline; + text-decoration: underline !important; + } + ul { + list-style-type: "- " !important; + padding: revert; + padding-left: 1rem; + font-size: 1.1rem; + line-height: 1.5rem; + } + label { + font-size: 1rem; + margin-right: 1rem; + } + input { + width: 50%; + } + .republish-warning { + display: flex; + margin: 0.75rem -10px 1rem -10px; + padding: 1rem 1rem 0 1rem; + background: color.adjust($orange, $alpha: -0.6); + i { + margin-right: 1rem; + margin-top: 0.25rem; + } + p { + font-weight: bold; + padding-bottom: 0.5rem; + } + } + } + button { + margin: 0; + margin-top: 1rem; + .fa-spinner { + margin-left: 0.5rem; + } + } +} + +.sequence-share-menu { + text-align: center; + a { + display: block; + white-space: normal; + word-wrap: break-word; + word-break: break-word; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + &:hover { + color: $black; + font-weight: normal; + } + } + .versions-table { + margin-top: 2rem; + text-align: left; + .bp5-popover-wrapper { + display: inline-block; + margin-left: 1rem; + } + .fb-button.gray { + margin: 0; + float: right; + } + .row { + margin-top: 0.5rem; + } + p { + width: max-content; + padding: 5px; + border-radius: 3px; + color: $white; + background: $panel_blue; + } + .fa-link { + float: right; + margin-top: 0.75rem; + } + } + .fb-button.white { + color: $darkest_red; + text-transform: none; + font-weight: normal; + background: $white; + .fa-spinner { + margin-left: 0.5rem; + } + &:hover { + background: $off_white; + } + } +} + +.celeryscript { + font-size: 1.25rem; + margin: 0; + background: $dark_gray; + color: $off_white; + padding: 1rem; + font-size: 1.2rem; + white-space: pre-wrap; +} diff --git a/frontend/css/panels/settings.scss b/frontend/css/panels/settings.scss new file mode 100644 index 0000000000..7d1cfab18e --- /dev/null +++ b/frontend/css/panels/settings.scss @@ -0,0 +1,261 @@ +@use "../variables" as *; +@use "sass:color"; + +.settings-panel-content { + overflow-y: auto; + overflow-x: hidden; + padding-bottom: 1.5rem; + display: grid; + gap: 2rem; + .expandable-header { + margin: 0; + } + .section { + display: grid; + grid-template-columns: auto 1fr; + align-items: center; + &.setting { + gap: 0.5rem; + } + .bp5-collapse { + grid-column: span 2; + } + .bp5-collapse-body { + display: grid; + gap: 1rem; + } + } + .export-data, + .change-password { + form { + margin-left: 2rem; + } + } + .label-headings { + label { + line-height: 1rem; + } + } + .release-notes-wrapper { + float: right !important; + } + .settings-warning-banner { + margin: 0 -2.5rem; + padding: 1rem 2.5rem; + background: color.adjust($orange, $alpha: -0.6); + p { + font-size: 1.3rem; + font-weight: bold; + } + &.env-editor-lua { + background: color.adjust($blue, $alpha: -0.6); + } + } + .pin-guard-grid { + display: grid; + gap: 1rem; + grid-template-columns: 1fr 1fr auto; + grid-auto-flow: column; + } + .pin-bindings { + .stock-pin-bindings-button { + display: block; + justify-self: right; + i { + margin-right: 0.5rem; + } + } + .pin-binding-grid { + grid-template-columns: 1fr 30% auto; + } + .binding-action { + font-weight: bold; + font-size: 1.2rem; + } + } + .fb-button { + margin-top: 0.5rem; + } + label { + margin: auto 0 !important; + line-height: 1; + font-size: 1.2rem !important; + } + .help-icon { + margin-left: 0.5rem; + } + .designer-setting { + &.disabled { + input { + background: $gray; + } + button { + background: $medium_light_gray !important; + } + } + } +} + +.setting { + &[hidden] { + display: none !important; + } + &.highlight { + background-color: $translucent2_white; + box-shadow: 0px 0px 7px 4px $translucent2_white; + } + &.unhighlight { + transition: background-color 10s linear, box-shadow 10s linear; + background-color: transparent; + box-shadow: none; + } + &.advanced { + border-left: 3px solid $blue; + padding-left: 0.7rem; + margin-left: -1rem; + } + .fa-anchor { + font-size: 0.8rem; + visibility: hidden; + &.hovered { + visibility: visible; + } + } + .setting { + display: grid; + grid-template-columns: auto 1fr; + gap: 0.5rem; + align-items: baseline; + .farmbot-os-setting { + text-align: left; + } + } +} + +.load-progress-bar-wrapper { + position: relative; + margin: 0.5rem; + margin-top: 0; + margin-left: 0; + width: 90%; + height: 1.5rem; + border: 1px solid $dark_gray; + .load-progress-bar { + height: 100%; + background: $dark_gray !important; + p { + position: absolute; + color: $gray; + font-weight: bold; + font-size: 1rem; + } + } +} + +.firmware-setting-export-menu { + button { + margin-bottom: 1rem; + } + ul { + font-size: 0.75rem; + } +} + +.change-ownership-form { + p { + padding: 1rem; + margin-left: 0.5rem; + } + .row { + margin-bottom: 1rem; + &:empty { + margin-bottom: 0; + } + } + label { + margin-top: 0.5rem; + } + button { + margin-top: 1rem; + margin-right: 1rem; + } +} + +.mcu-input-box { + width: 100%; +} + +.fb-toggle-button, +.mcu-input-box { + position: relative; + .setting-status-indicator { + position: absolute; + z-index: 1; + } + .fa-spinner { + color: $dark_gray; + } + .fa-check { + color: $green; + animation: fade-out 1s 0.4s forwards; + } + .save-error { + .bp5-popover-content { + background: $dark_gray; + min-width: 200px; + p { + text-transform: none; + color: $off_white; + } + } + .bp5-popover-arrow-fill { + fill: $dark_gray; + } + } +} + +.mcu-input-box { + .setting-status-indicator { + top: 0.5rem; + right: 0.5rem; + } +} + +.release-notes-button { + font-weight: bold; + cursor: pointer; +} + +.release-notes { + max-width: 250px; + h1 { + font-weight: 300; + font-size: 1.4rem; + line-height: 2rem; + margin-top: 0; + } + li { + font-weight: 300; + font-size: 1.1rem; + line-height: 1.75rem; + margin-bottom: 1rem; + } + p { + display: block; + color: $dark_gray; + text-overflow: inherit; + overflow: inherit; + width: inherit; + white-space: inherit; + } +} + +.timezone-grid { + grid-auto-flow: dense!important; +} + +.note { + font-style: italic; + grid-column: span 2; + margin-bottom: 1rem; +} diff --git a/frontend/css/panels/setup_wizard.scss b/frontend/css/panels/setup_wizard.scss new file mode 100644 index 0000000000..006faff2ec --- /dev/null +++ b/frontend/css/panels/setup_wizard.scss @@ -0,0 +1,333 @@ +@use "../variables" as *; +@use "sass:color"; + +.setup-panel { + .panel-top { + padding: 0; + } + .panel-content { + overflow-y: auto; + overflow-x: hidden; + padding: 0; + .progress-meter { + font-weight: bold; + color: $medium_gray; + } + h1, + h2, + h3 { + margin-bottom: 0; + cursor: pointer; + .fa-caret-up, + .fa-caret-down { + font-size: 2rem; + } + } + .saucer { + height: 1.5rem; + width: 1.5rem; + i { + font-size: 1rem; + color: $white; + vertical-align: top; + width: 1.5rem; + line-height: 1.5rem; + text-align: center; + } + } + h2 { + font-weight: bold; + font-size: 1.4rem; + padding: 1rem 1.5rem 1rem 1.5rem; + } + h3 { + font-size: 1.4rem; + } + .prerequisites, + .prereq-not-met { + font-size: 1.3rem; + padding: 1rem; + background: var(--secondary-bg); + border: 1px solid $dark_red; + border-radius: 5px; + } + .wizard-header { + padding: 1.5rem; + h1 { + font-family: 'Inknut Antiqua', serif; + font-weight: bold; + font-size: 2rem; + margin: 0; + } + } + .wizard-section { + h2 { + margin-top: 0; + } + .bp5-collapse-body { + margin-bottom: 2rem; + } + } + .wizard-step { + .bp5-collapse-body { + margin-bottom: 0; + } + img { + width: 100%; + border-radius: 5px; + } + } + .warning-banner { + margin: 0 -15px 1rem -15px; + padding: 1rem 1.5rem 1rem 2rem; + background: color.adjust($orange, $alpha: -0.6); + p { + line-height: 1.75rem; + font-size: 1.3rem; + font-weight: bold; + } + } + h2, + .wizard-step-header { + padding: 0.5rem 1.5rem; + cursor: pointer; + &.open { + background: var(--secondary-bg); + h3 { + font-weight: bold; + } + } + &:hover { + background: var(--secondary-bg); + } + } + .wizard-step-content { + padding: 0.5rem 1.5rem 2rem; + background: var(--secondary-bg); + .markdown { + display: contents; + p { + font-size: 1.4rem; + white-space: pre-wrap; + line-height: 1.8rem; + text-align: justify; + } + } + } + .wizard-step-q-and-a { + background-color: $dark_gray; + box-shadow: 0 0 1rem $translucent; + border-radius: 1rem; + display: grid; + gap: 1rem; + justify-content: center; + padding: 2rem; + .wizard-step-question { + font-style: italic; + text-align: center; + &.markdown p { + color: $off_white!important; + text-align: center; + font-size: 1.5rem; + } + } + .wizard-answer { + justify-content: center; + button { + padding: 1rem 2rem; + } + } + } + iframe { + aspect-ratio: 16/9; + border-radius: 5px; + box-shadow: 0px 0px 7px 0px $translucent; + } + .wizard-components { + margin: 0 auto; + width: fit-content; + border-radius: 5px; + padding: 1rem 1.5rem; + background: var(--secondary-bg); + &.no-border { + border: none; + width: 100%; + padding: 0; + } + &.no-background { + border: none; + width: max-content; + padding: 0; + } + &.full-width { + width: 100%; + } + &:empty { + display: none; + } + .widget-wrapper { + margin-bottom: 0; + } + .tool-verification-status { + text-align: center; + button { + margin: 1rem; + float: none; + } + } + .connectivity { + .diagnosis-indicator { + height: 2rem; + width: 2rem; + .fa { + margin-left: -0.25rem; + margin-top: -0.3rem; + } + } + .saucer-connector { + display: none; + } + } + .flash-firmware { + display: block; + float: none; + } + .tour-start { + display: block; + margin: auto; + float: none; + padding: 1rem; + } + .setting { + .no-pad { + padding-left: 1.5rem; + padding-right: 1.5rem; + } + } + .flow-rate-input { + .fb-button { + margin-top: 0; + } + } + } + .troubleshooting { + a { + float: none; + padding: 0; + padding-left: 0.25rem; + } + } + .troubleshooting-tip { + border: 2px solid $medium_light_gray; + border-radius: 5px; + padding: 1rem; + cursor: pointer; + background: $panel_light_gray; + &:hover { + background: $white; + box-shadow: 0 1px 5px 0 $translucent; + } + p { + font-size: 1.4rem; + line-height: 2rem; + } + a { + font-size: 1.4rem; + text-decoration: underline; + } + &.selected { + border-color: $dark_gray; + background: $white; + p:first-of-type { + font-weight: bold; + } + fieldset { + p { + font-weight: normal !important; + } + } + } + .fb-button { + float: none; + } + .arrow-button { + p { + font-size: 0.9rem; + } + } + .farmbot-origin { + margin-top: 1rem; + } + .motor-settings { + .row { + margin-top: 1rem; + } + } + .filter-search { + .bp5-popover-wrapper { + display: unset; + margin-left: unset; + } + } + .yellow { + float: none; + &:hover { + font-size: 1rem; + } + } + iframe { + margin-top: 1rem; + } + .wizard-find-home-btn { + float: none; + } + } + .setup-complete { + margin: 2rem; + justify-content: center; + p { + font-size: 1.5rem; + font-weight: bold; + } + } + } +} + +.camera-check { + text-align: center; + justify-items: center; + img { + width: 100%; + padding: 1rem; + } +} + +.farmbot-model-selection { + width: 27rem; + .seed-checkbox { + .fb-checkbox { + display: inline-block; + margin-top: 1rem; + } + p { + display: inline; + margin-left: 0.5rem; + vertical-align: bottom; + line-height: 3rem; + } + } +} + +.peripherals-check { + margin: 1rem; +} + +.camera-calibration-card { + padding: 1rem; + width: 15rem; + svg { + width: 100%; + height: 100%; + background: $dark_gray; + } +} diff --git a/frontend/css/panels/tools.scss b/frontend/css/panels/tools.scss new file mode 100644 index 0000000000..8662fce24d --- /dev/null +++ b/frontend/css/panels/tools.scss @@ -0,0 +1,209 @@ +@use "../variables" as *; +@use "sass:color"; + +.tool-slots-panel, +.tools-panel { + .tool-slots-panel-content, + .tools-panel-content { + overflow-y: auto; + overflow-x: hidden; + .tool-search-item, + .tool-slot-search-item { + .filter-search { + .bp5-button { + min-height: 2.5rem; + max-height: 2.5rem; + span { + line-height: 1.5rem; + } + } + i { + line-height: 2.5rem; + } + } + p { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + .mounted-tool { + .filter-search { + width: 50%; + float: right; + } + .utm-and-mounted-tool-graphic { + width: 30%; + margin-left: 32%; + } + } + .mounted-tool-header { + display: inline; + margin-top: 1rem; + label { + margin: 0; + } + .bp5-popover-wrapper { + display: inline; + } + .help-icon { + margin-left: 1rem; + vertical-align: top; + font-size: 1.4rem; + } + } + button:not(.bp5-button) { + display: block; + margin-left: auto; + float: none; + margin-top: 1rem; + } + .tool-verification-status { + display: flex; + margin-top: 1rem; + margin-bottom: 2rem; + button { + margin-top: 0; + } + } + .panel-section { + margin-left: -10px; + margin-right: -10px; + } + } +} + +.add-tool-slot-panel, +.edit-tool-slot-panel, +.add-tool-panel, +.edit-tool-panel { + .tool-action-btn-group { + margin: 1.5rem; + } +} + +.add-tool-panel-content, +.edit-tool-panel-content { + max-height: calc(100vh - 14rem); + overflow-y: auto; + overflow-x: hidden; + button { + display: block; + margin-left: auto; + float: none; + margin-top: 1rem; + &.red { + float: left; + margin-bottom: 1rem; + } + } + svg { + display: block; + margin: auto; + width: 10rem; + height: 10rem; + margin-top: 2rem; + } + .edit-tool, + .add-new-tool { + .name-error { + justify-self: right; + color: $dark_red; + } + .save-btn { + float: right; + } + details { + padding: 2rem; + .graphics-input { + input { + width: 97%; + } + } + } + } + .add-stock-tools { + .filter-search { + margin-bottom: 1rem; + button { + margin-top: 0.2rem; + } + } + ul { + font-size: 1.2rem; + li { + margin-top: 0.5rem; + line-height: 2rem; + cursor: pointer; + width: 50%; + &:hover { + font-weight: bold; + } + .fb-checkbox { + display: inline; + } + p { + display: inline; + line-height: 2.25rem; + font-size: 1.2rem; + vertical-align: top; + margin-left: 1rem; + } + } + } + button { + .fa-plus { + margin-right: 0.5rem; + } + } + } +} + +.flow-rate-input { + .bp5-popover-wrapper { + display: inline; + margin-left: 0.5rem; + } + .fb-button { + float: right; + margin-top: 1.5rem; + } +} + +.edit-tool-slot-panel { + .save-error { + position: absolute; + top: 1.5rem; + right: 1rem; + color: $darkest_red; + } +} + +.add-tool-slot-panel-content, +.edit-tool-slot-panel-content { + max-height: calc(100vh - 14rem); + overflow-y: auto; + overflow-x: hidden; + svg { + display: block; + margin: auto; + width: 10rem; + height: 10rem; + margin-top: 2rem; + } +} + +.tool-svg { + display: flex; + div { + margin: auto; + text-align: center; + p { + font-style: italic; + } + } +} + +.tool-slot-location-grid { + align-items: end !important; +} diff --git a/frontend/css/panels/webcams.scss b/frontend/css/panels/webcams.scss new file mode 100644 index 0000000000..6ae9cb3d62 --- /dev/null +++ b/frontend/css/panels/webcams.scss @@ -0,0 +1,59 @@ +@use "../variables" as *; +@use "sass:color"; + +.webcam-stream-unavailable p { + position: absolute; + top: 50%; + left: 50%; + width: 100%; + transform: translate(-50%, -50%); + vertical-align: middle; + text-align: center; + font-size: 1.5rem; +} + +.webcam-widget { + .no-flipper-image-container { + background: none !important; + width: 100% !important; + height: auto !important; + border-radius: 5px; + img { + max-width: 100% !important; + } + } + .image-flipper { + p, + button { + color: $white; + } + } +} + +.webcam-stream-unavailable { + position: relative; + width: 100%; +} + +.webcam-stream-unavailable img { + width: 100%; + max-width: 100%; + opacity: 0.40; +} + +.webcam-stream-valid { + img, iframe { + display: flex; + margin: auto; + max-height: 650px; + } + img { + max-width: 100%; + min-height: 100px; + } + iframe { + width: 100%; + border: none; + min-height: 300px; + } +} diff --git a/frontend/css/regimens.scss b/frontend/css/regimens.scss deleted file mode 100644 index 6562e06e8c..0000000000 --- a/frontend/css/regimens.scss +++ /dev/null @@ -1,169 +0,0 @@ -// Bulk Scheduler -.bulk-scheduler { - &.page { - @media screen and (max-width: 768px) { - display: none; - margin-bottom: 3rem; - &.open { - &.inserting-item { - display: block; - } - } - } - } - label { - margin-top: 0 !important; - } - .week-grid { - margin-top: 2rem; - } - .week-grid-meta-buttons { - margin-top: 1rem; - text-align: right; - button { - float: none; - margin-left: 1rem; - margin-bottom: 1rem; - } - } - @media screen and (max-width: 974px) { - margin-left: 15px; - margin-right: 15px; - } - .fa-clock-o { - margin-left: 0.75rem; - color: $dark_gray; - } - input { - background-color: $white !important; - } -} - -.bulk-scheduler-add { - margin: 1.5rem !important; -} - -.bulk-scheduler-content { - margin-top: 1rem; -} - -// Regimen Editor -.regimen-day { - margin: 1.5rem 0; - margin-right: 10px; -} - -.regimen-event { - position: relative; - background: $gray; - border-radius: 3px; - min-height: 3.5rem; - margin-bottom: 1rem; - &.blue { - .regimen-item-variables { - background: $panel_light_blue; - } - } - &.green { - .regimen-item-variables { - background: $light_green; - } - } - &.yellow { - .regimen-item-variables { - background: $light_yellow; - } - } - &.orange { - .regimen-item-variables { - background: $light_orange; - } - } - &.purple { - .regimen-item-variables { - background: $light_purple; - } - } - &.pink { - .regimen-item-variables { - background: $light_pink; - } - } - &.gray { - .regimen-item-variables { - background: $light_gray; - } - } - &.red { - .regimen-item-variables { - background: $light_red; - } - } -} - -.regimen-event-titlebar { - padding: 0.5rem; - padding-left: 1rem; -} - -.regimen-event-title { - color: $dark_gray; - font-size: 1.2rem; - font-weight: bold; - text-transform: uppercase; - .fa-external-link { - margin-left: 0.5rem; - } -} - -.regimen-event-time { - margin-left: 1.5rem; - margin-right: 1.5rem; -} - -.regimen-item-variables { - padding: 1rem; - border-bottom-left-radius: 3px; - border-bottom-right-radius: 3px; - .variable-icon { - margin-right: 1rem; - } -} - -.regimen-event-variable { - display: block; - margin-right: 1.5rem; - i { - cursor: default !important; - } -} - -.regimen-control { - position: absolute; - right: 1rem; -} - -.regimen-days-label { - margin-left: 50%; - margin-top: 2rem; -} - -.open-bulk-scheduler-btn-wrapper { - margin: auto; - width: fit-content; - text-align: center; - button { - float: none; - margin-bottom: 1rem; - } - .open-bulk-scheduler-btn { - display: none; - @media screen and (max-width: 768px) { - display: block; - margin: auto; - float: none !important; - } - } -} - -// Regimen List styles in sequences.scss diff --git a/frontend/css/scrollbar.scss b/frontend/css/scrollbar.scss deleted file mode 100644 index fd1fc73189..0000000000 --- a/frontend/css/scrollbar.scss +++ /dev/null @@ -1,13 +0,0 @@ -*::-webkit-scrollbar { - width: 4px; - height: 4px; - background-color: $light_gray; -} - -*::-webkit-scrollbar-track { - background-color: $light_gray; -} - -*::-webkit-scrollbar-thumb { - background-color: $medium_gray; -} diff --git a/frontend/css/static_pages.scss b/frontend/css/static_pages.scss deleted file mode 100644 index 4f3b8a411c..0000000000 --- a/frontend/css/static_pages.scss +++ /dev/null @@ -1,85 +0,0 @@ -.static-page { - min-height: 100vh; - max-height: 100%; - background: linear-gradient(-135deg, #6db1ec, #35a274); - padding-top: 4rem; - .row { - margin: 0 auto; - } - h1, - h2 { - font-family: "Cabin", Arial, Helvetica, sans-serif !important; - font-weight: 100 !important; - color: $white; - text-shadow: 0 0 25px rgba(0, 0, 0, 0.1), 0 0 25px rgba(0, 0, 0, 0.1); - } - h1 { - font-family: "Inknut Antiqua" !important; - font-weight: bold !important; - font-size: 3.4rem; - line-height: 3.6rem; - } - h2 { - margin-top: 1rem; - margin-bottom: 8rem; - font-size: 2.2rem; - line-height: 2.6rem; - } - input { - margin-bottom: 1rem; - font-family: revert; - font-size: revert; - } - .all-content-wrapper { - max-width: 50rem; - padding-left: 1rem; - padding-right: 1rem; - } - .widget-wrapper { - margin-top: 1.5rem; - text-align: start; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.2); - } - .inner-width { - margin: auto; - max-width: 1024px; - text-align: center; - } - .forgot-password { - display: inline-block; - color: $blue; - } - .tos { - margin-bottom: 1.5rem; - } - input[type="checkbox"] { - float: left; - margin-right: 0.5rem; - margin-top: 0.25rem; - } - a:link { - font-weight: 300; - color: $dark_gray; - } - a:visited { - color: $medium_gray; - } - a:hover { - font-weight: 500; - } - a:active { - color: $medium_gray; - font-weight: 500; - } - .fa-gear { - float: right; - } - .fa-external-link { - margin-left: 1rem; - margin-bottom: 0.5rem; - } - img { - width: 55%; - margin-bottom: 2rem; - } -} diff --git a/frontend/css/variables.scss b/frontend/css/variables.scss new file mode 100644 index 0000000000..2e7fa04f28 --- /dev/null +++ b/frontend/css/variables.scss @@ -0,0 +1,3 @@ +@forward "variables/colors"; + +$mobile_max_width: 500px; diff --git a/frontend/css/colors.scss b/frontend/css/variables/colors.scss similarity index 62% rename from frontend/css/colors.scss rename to frontend/css/variables/colors.scss index ebe5283ba5..1fdd008b52 100644 --- a/frontend/css/colors.scss +++ b/frontend/css/variables/colors.scss @@ -1,4 +1,5 @@ // COLORS used throughout the web app +$alpha90: 0.90; $transparent: #00000000; $translucent: rgba(0, 0, 0, 0.2); $translucent1: rgba(0, 0, 0, 0.1); @@ -7,8 +8,15 @@ $translucent2: rgba(0, 0, 0, 0.2); $translucent3: rgba(0, 0, 0, 0.3); $translucent4: rgba(0, 0, 0, 0.4); $translucent5: rgba(0, 0, 0, 0.5); +$translucent6: rgba(0, 0, 0, 0.6); +$translucent7: rgba(0, 0, 0, 0.7); +$translucent8: rgba(0, 0, 0, 0.8); $translucent1_white: rgba(255, 255, 255, 0.1); +$translucent2_white: rgba(255, 255, 255, 0.2); $translucent3_white: rgba(255, 255, 255, 0.3); +$translucent5_white: rgba(255, 255, 255, 0.5); +$translucent8_white: rgba(255, 255, 255, 0.8); +$translucent9_white: rgba(255, 255, 255, 0.9); $white: #fff; $off_white: #f4f4f4; $lighter_gray: #eee; @@ -19,6 +27,7 @@ $medium_gray: #666; $placeholder_gray: #999; $dark_gray: #434343; $darker_gray: #182026; +$dark_bg: hsl(135 30% 7% / 1); $black: #000; $off_black: #222; $light_blue: #cdf; @@ -50,23 +59,40 @@ $red: #e66; $dark_red: #f00; $medium_dark_red: #c00; $darkest_red: #900; -$panel_green: #35761b; -$panel_light_green: #f3f9f1; -$panel_yellow: #e99d18; -$panel_light_yellow: #fffaf0; -$panel_gray: #92a7b3; -$panel_medium_light_gray: #e6e6e6; -$panel_light_gray: #f9fbfc; -$panel_blue: #026365; -$panel_light_blue: #f0f8f8; -$panel_navy: #334970; -$panel_light_navy: #f3f5f9; -$panel_brown: #9e630a; -$panel_light_brown: #fbf7f0; -$panel_teal: #1eb287; -$panel_light_teal: #f1fcf9; -$panel_red: #ff4f37; -$panel_light_red: #fff7f6; +$panel_green: rgb(53, 118, 27, $alpha90); +$panel_light_green: rgb(243, 249, 241, $alpha90); +$panel_yellow: rgb(233, 157, 24, $alpha90); +$panel_light_yellow: rgb(255, 250, 240, $alpha90); +$panel_gray: rgb(20, 92, 128, 0.3); +$panel_medium_light_gray: rgb(230, 230, 230, $alpha90); +$panel_light_gray: rgb(249, 251, 252, $alpha90); +$panel_blue: rgb(2, 99, 101, $alpha90); +$panel_light_blue: rgb(240, 248, 248, $alpha90); +$panel_navy: rgb(51, 73, 112, $alpha90); +$panel_light_navy: rgb(243, 245, 249, $alpha90); +$panel_brown: rgb(158, 99, 10, $alpha90); +$panel_light_brown: rgb(251, 247, 240, $alpha90); +$panel_teal: rgb(30, 178, 135, $alpha90); +$panel_light_teal: rgb(241, 252, 249, $alpha90); +$panel_red: rgb(255, 79, 55, $alpha90); +$panel_light_red: rgb(255, 247, 246, $alpha90); + +body:has(.app.light) { + --main-bg: #{$off_white}; + --secondary-bg: #{$translucent1}; + --text-color: #{$dark_gray}; + --border-color: #{$translucent2}; + --box-shadow: 0 0 5px 2px #{$translucent1}; +} + +body:has(.app.dark) { + --main-bg: #{$dark_bg}; + --secondary-bg: #{$translucent1_white}; + --text-color: #{$gray}; + --border-color: #{$translucent3_white}; + --box-shadow: 0 0 5px 2px #{$translucent1_white}; +} + .dark-gray { background-color: $dark_gray !important; } diff --git a/frontend/css/widget_move.scss b/frontend/css/widget_move.scss deleted file mode 100644 index ed054772eb..0000000000 --- a/frontend/css/widget_move.scss +++ /dev/null @@ -1,206 +0,0 @@ -// MOVE AMOUNT (mm) selector -.text-center { - text-align: center; - width: 100%; -} - -.move-amount-wrapper { - box-shadow: 0px 0px 10px $light_gray; - height: 2.5rem; - border-radius: 7px; -} - -.move-amount { - margin: 0; - background-color: $white; - border-right: 2px solid $off-white; - color: $medium_gray; - font-weight: bold; - height: 2.5rem; - padding-top: auto; - padding-bottom: auto; - padding-left: 0; - padding-right: 0; - text-align: center; - width: 20%; - float: left; - &:hover { - background-color: color.adjust($white, $lightness: -40%); - color: $off_white; - } - &:nth-child(n+2) { - border-left: 0; - } - &.leftmost { - border-bottom-left-radius: 7px; - border-top-left-radius: 7px; - } - &.rightmost { - border-bottom-right-radius: 7px; - border-top-right-radius: 7px; - border-right: 0px none; - } - &.move-amount-selected { - background-color: $medium_gray !important; - color: $off_white; - } -} - -.no-radius { - border-radius: 0; -} - -// JOG CONTROLS -.jog-table { - margin: auto; - margin-top: 15px; - width: auto; - border: 0; - .bp5-popover-wrapper { - line-height: 0; - } - .fa-camera { - .bp5-popover-wrapper { - z-index: 1; - width: 4rem; - height: 4rem; - margin-top: -3rem; - margin-left: -1rem; - .bp5-popover-target { - width: 100%; - height: 100%; - } - } - } -} - -.in-progress { - background: $gray !important; - border-bottom-color: $medium_gray !important; -} - -.progress-percent { - width: 3rem; - height: 3rem; - margin-top: -0.75rem; - margin-left: -0.75rem; - border-radius: 50%; - padding: 0.25rem; - p { - display: inline-block; - position: relative !important; - margin: auto !important; - color: $dark_gray !important; - background: $gray; - border-radius: 50%; - width: 100%; - height: 100%; - font-size: 0.7rem; - font-weight: bold; - line-height: 2.25rem; - vertical-align: middle; - } -} - -.power-btn-popover { - label { - color: $off_white; - } - .bp5-popover-wrapper { - display: inline; - margin-left: 0.5rem; - color: $off_white; - } - .bp5-popover-content { - width: 310px; - background: $dark_gray; - } - .bp5-popover-arrow-fill { - fill: $dark_gray; - } - .fa-anchor { - display: none; - } -} - -.arrow-button { - position: relative; - margin: 0; - background-color: $medium_gray; - border-bottom: 2px solid $dark_gray; - color: $off_white; - font-size: 16px !important; - height: 40px; - padding: 12px; - text-align: center; - width: 40px; - box-shadow: rgba(0, 0, 0, 0.15) 0px 2px 7px 0px; - &:hover { - background-color: color.adjust($medium_gray, $lightness: -5%); - } - &:disabled { - border-bottom: none; - box-shadow: none !important; - } - p { - position: absolute; - top: 0; - left: 0; - z-index: 1; - color: $white; - margin-top: -2px; - padding-left: 1px; - font-size: 0.9rem; - } - &:before { - position: relative; - z-index: 1; - } -} - -.movement-progress { - position: absolute; - width: 100%; - height: 100%; - border-radius: 3px; - background: $medium_gray; -} - -.camera-message, -.movement-message { - .bp5-popover-content { - width: unset !important; - } -} - -.home-button { - i { - margin-top: -1rem; - font-size: 2rem; - } - .fa-stack { - position: absolute; - top: 0; - left: 0; - margin: 0.5rem; - padding: 0.5rem; - .fa-stack-1x { - top: -1rem; - left: -1rem; - margin-top: 0; - font-size: 1rem; - } - .fa-stack-2x { - margin-top: 0; - font-size: 2rem; - } - } -} - -// ABSOLUTE MOVES -input { - &.move-input { - font-size: 1rem; - margin-bottom: 1.5rem; - } -} diff --git a/frontend/curves/__tests__/chart_test.tsx b/frontend/curves/__tests__/chart_test.tsx index 43bcaac850..50346286c0 100644 --- a/frontend/curves/__tests__/chart_test.tsx +++ b/frontend/curves/__tests__/chart_test.tsx @@ -2,13 +2,6 @@ jest.mock("../edit_curve", () => ({ editCurve: jest.fn(), })); -import { Path } from "../../internal_urls"; -let mockPath = Path.mock(Path.curves(1)); -jest.mock("../../history", () => ({ - getPathArray: jest.fn(() => mockPath.split("/")), - push: jest.fn(), -})); - import { mount } from "enzyme"; import React from "react"; import { Actions } from "../../constants"; @@ -18,6 +11,7 @@ import { fakeCurve, fakePoint } from "../../__test_support__/fake_state/resource import { CurveIcon, CurveSvg, getWarningLinesContent } from "../chart"; import { editCurve } from "../edit_curve"; import { CurveIconProps, CurveSvgProps } from "../interfaces"; +import { Path } from "../../internal_urls"; const TEST_DATA = { 1: 0, 10: 10, 50: 500, 100: 1000 }; @@ -217,7 +211,7 @@ describe("", () => { }); it("shows warning lines: height in plants panels", () => { - mockPath = Path.mock(Path.cropSearch()); + location.pathname = Path.mock(Path.cropSearch()); const p = fakeProps(); p.curve.body.type = "height"; p.sourceFbosConfig = () => ({ value: 100, consistent: true }); diff --git a/frontend/curves/__tests__/curves_inventory_test.tsx b/frontend/curves/__tests__/curves_inventory_test.tsx index 151a50d1b9..03e320cc94 100644 --- a/frontend/curves/__tests__/curves_inventory_test.tsx +++ b/frontend/curves/__tests__/curves_inventory_test.tsx @@ -8,7 +8,6 @@ import { mount } from "enzyme"; import { RawCurves as Curves, mapStateToProps } from "../curves_inventory"; import { fakeState } from "../../__test_support__/fake_state"; import { fakeCurve } from "../../__test_support__/fake_state/resources"; -import { push } from "../../history"; import { init, save } from "../../api/crud"; import { SearchField } from "../../ui/search_field"; import { Path } from "../../internal_urls"; @@ -54,9 +53,11 @@ describe(" />", () => { p.curves = [fakeCurve()]; p.curves[0].body.id = 1; p.curvesPanelState.water = true; - const wrapper = mount(); + const wrapper = mount(); + const navigate = jest.fn(); + wrapper.instance().navigate = navigate; wrapper.find(".curve-search-item").first().simulate("click"); - expect(push).toHaveBeenCalledWith(Path.curves(1)); + expect(navigate).toHaveBeenCalledWith(Path.curves(1)); }); it("navigates to unsaved curve", () => { @@ -64,9 +65,11 @@ describe(" />", () => { p.curves = [fakeCurve()]; p.curves[0].body.id = 0; p.curvesPanelState.water = true; - const wrapper = mount(); + const wrapper = mount(); + const navigate = jest.fn(); + wrapper.instance().navigate = navigate; wrapper.find(".curve-search-item").first().simulate("click"); - expect(push).toHaveBeenCalledWith(Path.curves(0)); + expect(navigate).toHaveBeenCalledWith(Path.curves(0)); }); it("filters curves", () => { @@ -97,13 +100,15 @@ describe(" />", () => { p.curves = [curve]; p.dispatch = jest.fn(() => Promise.resolve()); const wrapper = mount(); + const navigate = jest.fn(); + wrapper.instance().navigate = navigate; await wrapper.instance().addNew("water")(); expect(init).toHaveBeenCalledWith("Curve", { name: "Water curve 2", type: "water", data: { 1: 1, 30: 500, 45: 500, 60: 250 }, }); expect(save).toHaveBeenCalled(); - expect(push).toHaveBeenCalledWith(Path.curves(1)); + expect(navigate).toHaveBeenCalledWith(Path.curves(1)); }); it("creates new curve: missing curve", async () => { @@ -115,13 +120,15 @@ describe(" />", () => { p.curves = [curve]; p.dispatch = jest.fn(() => Promise.resolve()); const wrapper = mount(); + const navigate = jest.fn(); + wrapper.instance().navigate = navigate; await wrapper.instance().addNew("water")(); expect(init).toHaveBeenCalledWith("Curve", { name: "Water curve 2", type: "water", data: { 1: 1, 30: 500, 45: 500, 60: 250 }, }); expect(save).toHaveBeenCalled(); - expect(push).not.toHaveBeenCalled(); + expect(navigate).not.toHaveBeenCalled(); }); it("creates new curve: spread", async () => { @@ -132,13 +139,15 @@ describe(" />", () => { p.curves = [curve]; p.dispatch = jest.fn(() => Promise.resolve()); const wrapper = mount(); + const navigate = jest.fn(); + wrapper.instance().navigate = navigate; await wrapper.instance().addNew("spread")(); expect(init).toHaveBeenCalledWith("Curve", { name: "Spread curve 1", type: "spread", data: { 1: 1, 30: 300, 45: 300, 60: 150 }, }); expect(save).toHaveBeenCalled(); - expect(push).toHaveBeenCalledWith(Path.curves(1)); + expect(navigate).toHaveBeenCalledWith(Path.curves(1)); }); it("handles curve creation error", async () => { @@ -147,13 +156,15 @@ describe(" />", () => { .mockImplementationOnce(jest.fn()) .mockImplementationOnce(() => Promise.reject()); const wrapper = mount(); + const navigate = jest.fn(); + wrapper.instance().navigate = navigate; await wrapper.instance().addNew("water")(); expect(init).toHaveBeenCalledWith("Curve", { name: "Water curve 1", type: "water", data: { 1: 1, 30: 500, 45: 500, 60: 250 }, }); expect(save).toHaveBeenCalled(); - expect(push).not.toHaveBeenCalled(); + expect(navigate).not.toHaveBeenCalled(); }); }); diff --git a/frontend/curves/__tests__/edit_curve_test.tsx b/frontend/curves/__tests__/edit_curve_test.tsx index fc2afc0dc9..aeadd4c3a6 100644 --- a/frontend/curves/__tests__/edit_curve_test.tsx +++ b/frontend/curves/__tests__/edit_curve_test.tsx @@ -1,10 +1,3 @@ -import { Path } from "../../internal_urls"; -let mockPath = Path.mock(Path.curves(1)); -jest.mock("../../history", () => ({ - getPathArray: jest.fn(() => mockPath.split("/")), - push: jest.fn(), -})); - jest.mock("../../api/crud", () => ({ overwrite: jest.fn(), init: jest.fn(() => ({ payload: { uuid: "uuid" } })), @@ -31,14 +24,18 @@ import { buildResourceIndex, } from "../../__test_support__/resource_index_builder"; import { destroy, overwrite, init, save } from "../../api/crud"; -import { push } from "../../history"; import { mockDispatch } from "../../__test_support__/fake_dispatch"; import { fakeBotSize } from "../../__test_support__/fake_bot_data"; import { changeBlurableInput } from "../../__test_support__/helpers"; import { error } from "../../toast/toast"; import { SpecialStatus } from "farmbot"; +import { Path } from "../../internal_urls"; describe("", () => { + beforeEach(() => { + location.pathname = Path.mock(Path.curves(1)); + }); + const fakeProps = (): EditCurveProps => ({ dispatch: mockDispatch(), findCurve: () => undefined, @@ -50,21 +47,20 @@ describe("", () => { }); it("redirects", () => { - mockPath = Path.mock(Path.curves("nope")); + location.pathname = Path.mock(Path.curves("nope")); const wrapper = mount(); - expect(wrapper.text()).toContain("Redirecting..."); - expect(push).toHaveBeenCalledWith(Path.curves()); + expect(wrapper.text().toLowerCase()).toContain("redirecting"); + expect(mockNavigate).toHaveBeenCalledWith(Path.curves()); }); it("doesn't redirect", () => { - mockPath = Path.mock(Path.logs()); + location.pathname = Path.mock(Path.logs()); const wrapper = mount(); - expect(wrapper.text()).toContain("Redirecting..."); - expect(push).not.toHaveBeenCalled(); + expect(wrapper.text().toLowerCase()).toContain("redirecting"); + expect(mockNavigate).not.toHaveBeenCalled(); }); it("renders", () => { - mockPath = Path.mock(Path.curves(1)); const p = fakeProps(); p.findCurve = () => fakeCurve(); const wrapper = mount(); @@ -74,7 +70,6 @@ describe("", () => { }); it("renders: data full", () => { - mockPath = Path.mock(Path.curves(1)); const p = fakeProps(); const curve = fakeCurve(); curve.body.data = { @@ -152,7 +147,6 @@ describe("", () => { }); it("toggles state", () => { - mockPath = Path.mock(Path.curves(1)); const p = fakeProps(); p.findCurve = () => fakeCurve(); const wrapper = mount(); @@ -162,7 +156,6 @@ describe("", () => { }); it("sets hovered state", () => { - mockPath = Path.mock(Path.curves(1)); const p = fakeProps(); p.findCurve = () => fakeCurve(); const wrapper = mount(); @@ -172,7 +165,6 @@ describe("", () => { }); it("sets maxCount state high", () => { - mockPath = Path.mock(Path.curves(1)); const p = fakeProps(); p.findCurve = () => fakeCurve(); const wrapper = mount(); @@ -182,7 +174,6 @@ describe("", () => { }); it("sets maxCount state low", () => { - mockPath = Path.mock(Path.curves(1)); const p = fakeProps(); p.findCurve = () => fakeCurve(); const wrapper = mount(); @@ -193,7 +184,6 @@ describe("", () => { }); it("sets iconDisplay state", () => { - mockPath = Path.mock(Path.curves(1)); const p = fakeProps(); p.findCurve = () => fakeCurve(); const wrapper = mount(); @@ -203,7 +193,6 @@ describe("", () => { }); it("renders no icons", () => { - mockPath = Path.mock(Path.curves(1)); const p = fakeProps(); p.findCurve = () => undefined; const wrapper = mount(); @@ -212,7 +201,6 @@ describe("", () => { }); it("renders icons", () => { - mockPath = Path.mock(Path.curves(1)); const p = fakeProps(); const curve = fakeCurve(); curve.body.id = 1; @@ -231,7 +219,6 @@ describe("", () => { }); it("hides icons", () => { - mockPath = Path.mock(Path.curves(1)); const p = fakeProps(); const curve = fakeCurve(); curve.body.id = 1; @@ -247,7 +234,6 @@ describe("", () => { }); it("deletes curve", () => { - mockPath = Path.mock(Path.curves(1)); const p = fakeProps(); const curve = fakeCurve(); p.findCurve = () => curve; @@ -257,7 +243,6 @@ describe("", () => { }); it("handles curve in use", () => { - mockPath = Path.mock(Path.curves(1)); const p = fakeProps(); const curve = fakeCurve(); p.findCurve = () => curve; @@ -269,7 +254,6 @@ describe("", () => { }); it("renders spread", () => { - mockPath = Path.mock(Path.curves(1)); const p = fakeProps(); const curve = fakeCurve(); curve.body.type = "spread"; @@ -280,7 +264,6 @@ describe("", () => { }); it("renders height", () => { - mockPath = Path.mock(Path.curves(1)); const p = fakeProps(); const curve = fakeCurve(); curve.body.type = "height"; @@ -297,23 +280,28 @@ describe("copyCurve()", () => { existingCurve.body.name = "Fake copy 1"; const curves = [existingCurve]; const curve = fakeCurve(); - await copyCurve(curves, curve)(jest.fn(() => Promise.resolve()), jest.fn())(); + const navigate = jest.fn(); + await copyCurve(curves, curve, navigate)( + jest.fn(() => Promise.resolve()), + jest.fn(), + )(); expect(init).toHaveBeenCalledWith("Curve", { ...curve.body, name: "Fake copy 2", id: undefined, }); expect(save).toHaveBeenCalled(); - expect(push).not.toHaveBeenCalled(); + expect(navigate).not.toHaveBeenCalled(); }); it("handles promise rejection", async () => { const dispatch = jest.fn() .mockImplementationOnce(jest.fn()) .mockImplementationOnce(() => Promise.reject()); - await copyCurve([], fakeCurve())(dispatch, jest.fn())(); + const navigate = jest.fn(); + await copyCurve([], fakeCurve(), navigate)(dispatch, jest.fn())(); expect(save).toHaveBeenCalled(); - expect(push).not.toHaveBeenCalled(); + expect(navigate).not.toHaveBeenCalled(); }); it("copies curve and navigates", async () => { @@ -325,14 +313,18 @@ describe("copyCurve()", () => { curve.body.id = 1; const state = fakeState(); state.resources = buildResourceIndex([curve]); - await copyCurve(curves, curve)(jest.fn(() => Promise.resolve()), () => state)(); + const navigate = jest.fn(); + await copyCurve(curves, curve, navigate)( + jest.fn(() => Promise.resolve()), + () => state, + )(); expect(init).toHaveBeenCalledWith("Curve", { ...curve.body, name: "Fake copy 2", id: undefined, }); expect(save).toHaveBeenCalled(); - expect(push).toHaveBeenCalledWith(Path.curves(1)); + expect(navigate).toHaveBeenCalledWith(Path.curves(1)); }); it("copies curve and doesn't navigate", async () => { @@ -344,14 +336,18 @@ describe("copyCurve()", () => { curve.body.id = 1; const state = fakeState(); state.resources = buildResourceIndex([curve]); - await copyCurve(curves, curve)(jest.fn(() => Promise.resolve()), () => state)(); + const navigate = jest.fn(); + await copyCurve(curves, curve, navigate)( + jest.fn(() => Promise.resolve()), + () => state, + )(); expect(init).toHaveBeenCalledWith("Curve", { ...curve.body, name: "Fake copy 2", id: undefined, }); expect(save).toHaveBeenCalled(); - expect(push).not.toHaveBeenCalled(); + expect(navigate).not.toHaveBeenCalled(); }); }); diff --git a/frontend/curves/chart.tsx b/frontend/curves/chart.tsx index 0e553f5a81..6708c30526 100644 --- a/frontend/curves/chart.tsx +++ b/frontend/curves/chart.tsx @@ -146,7 +146,7 @@ const Data = (props: DataProps) => { stroke={last || showHoverEffect(day) ? curveColor(curve) : "none"} strokeWidth={0.5} strokeDasharray={last ? 0.5 : undefined} x={x(fullWidth * 0.75)} y={y} - fill={last ? Color.white : undefined} + fill={last ? "transparent" : undefined} width={fullWidth * 0.75} height={height} /> { @@ -269,16 +269,16 @@ const XAxis = (props: XAxisProps) => { const step = maxDay(data) > 100 ? 20 : 10; const dayLabels = [1].concat(range(step, lastLabel + 1, step)); return - + {dayLabels.map(day => {day})} {t("DAY")} @@ -296,19 +296,19 @@ const YAxis = (props: YAxisProps) => { const y = normY(value); return {y > -1 && - {value} } ; })} {props.curve.body.type == CurveType.water ? t("mL") : t("mm")} diff --git a/frontend/curves/curves_inventory.tsx b/frontend/curves/curves_inventory.tsx index 467d80dbc5..fe307a5151 100644 --- a/frontend/curves/curves_inventory.tsx +++ b/frontend/curves/curves_inventory.tsx @@ -1,7 +1,7 @@ import React from "react"; import { connect } from "react-redux"; import { CurvesPanelState, Everything } from "../interfaces"; -import { DesignerNavTabs, Panel } from "../farm_designer/panel_header"; +import { Panel } from "../farm_designer/panel_header"; import { EmptyStateWrapper, EmptyStateGraphic, } from "../ui/empty_state_wrapper"; @@ -11,7 +11,6 @@ import { } from "../farm_designer/designer_panel"; import { t } from "../i18next_wrapper"; import { selectAllCurves } from "../resources/selectors"; -import { push } from "../history"; import { init, save } from "../api/crud"; import { SearchField } from "../ui/search_field"; import { Path } from "../internal_urls"; @@ -25,6 +24,7 @@ import { } from "./templates"; import { Curve } from "farmbot/dist/resources/api_resources"; import { CurveIcon } from "./chart"; +import { NavigationContext } from "../routes_helpers"; export const mapStateToProps = (props: Everything): CurvesProps => ({ dispatch: props.dispatch, @@ -46,7 +46,13 @@ export class RawCurves extends React.Component { type: Actions.TOGGLE_CURVES_PANEL_OPTION, payload: section, }); - navigate = (id: number) => push(Path.curves(id)); + static contextType = NavigationContext; + context!: React.ContextType; + navigate = this.context; + + navigateById = (id: number) => { + this.navigate(Path.curves(id)); + }; addNew = (type: Curve["type"]) => () => { let num = 1; @@ -68,7 +74,7 @@ export class RawCurves extends React.Component { .then(() => { const id = this.props.curves.filter(curve => curve.uuid == action.payload.uuid)[0]?.body.id; - id && this.navigate(id); + id && this.navigateById(id); }) .catch(() => { }); }; @@ -77,7 +83,7 @@ export class RawCurves extends React.Component { this.navigate(curve.body.id || 0)} />; + onClick={() => this.navigateById(curve.body.id || 0)} />; render() { const { curves } = this.props; @@ -91,7 +97,6 @@ export class RawCurves extends React.Component { const heightCurves = filteredCurves .filter(curve => curve.body.type == CurveType.height); return - { } export const Curves = connect(mapStateToProps)(RawCurves); +// eslint-disable-next-line import/no-default-export +export default Curves; const CurveInventoryItem = (props: CurveInventoryItemProps) => { return
    { switch (curve.body.type) { @@ -129,16 +129,20 @@ export class RawEditCurve extends React.Component; }; + static contextType = NavigationContext; + context!: React.ContextType; + navigate = this.context; + render() { const { curve, setHovered } = this; const { dispatch } = this.props; const { hovered } = this.state; const curvesPath = Path.curves(); - !curve && Path.startsWith(curvesPath) && push(curvesPath); return + {!curve && Path.startsWith(curvesPath) && }
    {curve && - } + onClick={dispatch(copyCurve( + this.props.curves, + curve, + this.navigate))} />} {curve && - this.props.resourceUsage[curve.uuid] ? error(t("Curve in use.")) @@ -165,7 +172,7 @@ export class RawEditCurve extends React.Component {t("scale")} } @@ -174,7 +181,7 @@ export class RawEditCurve extends React.Component {t("templates")} } @@ -214,13 +221,15 @@ export class RawEditCurve extends React.Component { const { data } = props.curve.body; const [maxDayNum, setMaxDay] = React.useState(maxDay(data)); const [maxValueNum, setMaxValue] = React.useState(maxValue(data)); - return
    -
    + return
    +
    { isFinite(value) && value > 0 && setMaxValue(value); }} />
    -
    +
    { isFinite(day) && day > 0 && day < 201 && setMaxDay(day); }} />
    -
    - - -
    } - + + + + } +
    ; }; @@ -142,42 +137,38 @@ export class Connectivity wifi_level, wifi_level_percent, private_ip, node_name, target, } = informational_settings; const wifi = isWifi(wifi_level, wifi_level_percent); - return
    - -
    - -

    {t("Connection type")}: {wifi ? "WiFi" : t("Unknown")}

    - - - -
    - - - -
    - - {isMobile() ? t("This phone") : t("This computer")} - - - - {t("FarmBot")} - - - - - - {t("Learn more about ports")} - -
    - + return
    +
    + +

    {t("Connection type")}: {wifi ? "WiFi" : t("Unknown")}

    + + + +
    + +
    + + {isMobile() ? t("This phone") : t("This computer")} + + + + {t("FarmBot")} + + + + + + {t("Learn more about ports")} + +
    ; }; render() { const { realtime, network, history } = this.props.metricPanelState; return
    - +
    @@ -190,7 +181,7 @@ export class Connectivity {network && } {history && } - +
    {firmwareAlerts(this.props.alerts).length > 0 && - -
    - {!props.header && } -
    - {!props.header && -
    } - - -

    - {props.from == "browser" ? browserFrom : props.from} -

    - - -

    - {props.to} -

    - - -

    - {props.header ? t("last message seen ") : props.connectionMsg} -

    - + return +
    + {!props.header && } +
    + {!props.header && +
    } +

    + {props.from == "browser" ? browserFrom : props.from} +

    +

    + {props.to} +

    +

    + {props.header ? t("last message seen ") : props.connectionMsg} +

    ; } diff --git a/frontend/devices/connectivity/diagnosis.tsx b/frontend/devices/connectivity/diagnosis.tsx index 5d7b8a9aee..b86b6a0116 100644 --- a/frontend/devices/connectivity/diagnosis.tsx +++ b/frontend/devices/connectivity/diagnosis.tsx @@ -1,12 +1,13 @@ import React from "react"; -import { DiagnosticMessages } from "../../constants"; -import { Col, Row, docLinkClick } from "../../ui"; +import { DeviceSetting, DiagnosticMessages } from "../../constants"; +import { Row, docLinkClick } from "../../ui"; import { bitArray } from "../../util"; import { TRUTH_TABLE } from "./truth_table"; import { t } from "../../i18next_wrapper"; -import { goToFbosSettings } from "../../settings/maybe_highlight"; import { SyncStatus } from "farmbot"; import { syncText } from "../../nav/sync_text"; +import { useNavigate } from "react-router"; +import { linkToSetting } from "../../settings/maybe_highlight"; export type ConnectionName = | "userAPI" @@ -49,37 +50,35 @@ export const DiagnosisSaucer = (props: DiagnosisSaucerProps) => { export function Diagnosis(props: DiagnosisProps) { const diagnosisBoolean = diagnosisStatus(props.statusFlags); const diagnosisColor = diagnosisBoolean ? "green" : "red"; - return
    + const navigate = useNavigate(); + return + + ; } export function getDiagnosisCode(statusFlags: ConnectionStatusFlags) { diff --git a/frontend/devices/connectivity/diagram.tsx b/frontend/devices/connectivity/diagram.tsx index e94aa74aa9..f0a58d1de3 100644 --- a/frontend/devices/connectivity/diagram.tsx +++ b/frontend/devices/connectivity/diagram.tsx @@ -62,7 +62,7 @@ export function getTextPosition( } export function nodeLabel( - label: string, node: DiagramNodes, anchor = "middle"): JSX.Element { + label: string, node: DiagramNodes, anchor = "middle"): React.ReactNode { const position = getTextPosition(node); return {label} @@ -99,15 +99,15 @@ export function getLineProps( return { x1: 0, y1: 0, x2: 0, y2: 0 }; // fallback } -export function Connector(props: ConnectorProps): JSX.Element { +export function Connector(props: ConnectorProps): React.ReactNode { const { connectionData, from, to, hover, hoveredConnection, customLineProps } = props; const lineProps = customLineProps ? customLineProps : getLineProps(from, to); const hoverIndicatorColor = hoveredConnection === connectionData.connectionName - ? Color.darkGray - : Color.white; + ? Color.white + : Color.darkGray; return diff --git a/frontend/devices/connectivity/fbos_metric_history_plot.tsx b/frontend/devices/connectivity/fbos_metric_history_plot.tsx index 9ac4623f9a..8701ebe8a9 100644 --- a/frontend/devices/connectivity/fbos_metric_history_plot.tsx +++ b/frontend/devices/connectivity/fbos_metric_history_plot.tsx @@ -171,7 +171,7 @@ const XAxisLabels = () => /** plot background color and top and bottom lines */ const PlotBackground = () => - + diff --git a/frontend/devices/connectivity/fbos_metric_history_table.tsx b/frontend/devices/connectivity/fbos_metric_history_table.tsx index be54100d0d..c3d62fc9c1 100644 --- a/frontend/devices/connectivity/fbos_metric_history_table.tsx +++ b/frontend/devices/connectivity/fbos_metric_history_table.tsx @@ -49,7 +49,7 @@ const TableHeaderCell = (props: TableHeaderCellProps) => { const selected = props.recordSelected || props.hoveredMetric == props.metricName; return + return
    @@ -142,7 +142,7 @@ export class FbosMetricHistoryTable .map(m => { const recordSelected = this.state.hoveredTime == m.body.created_at; const recordProps = { - style: { background: recordSelected ? "#eee" : undefined }, + style: { background: recordSelected ? "rgba(255,255,255,0.2)" : undefined }, }; const cellProps = { ...commonProps, recordSelected }; const rightCellProps = { ...cellProps, rightAlign: true }; diff --git a/frontend/devices/jobs.tsx b/frontend/devices/jobs.tsx index 0897badcc5..afbfea5d9c 100644 --- a/frontend/devices/jobs.tsx +++ b/frontend/devices/jobs.tsx @@ -3,7 +3,7 @@ import { connect } from "react-redux"; import { DesignerPanel, DesignerPanelContent, DesignerPanelTop, } from "../farm_designer/designer_panel"; -import { Panel, DesignerNavTabs } from "../farm_designer/panel_header"; +import { Panel } from "../farm_designer/panel_header"; import { Everything, JobsAndLogsState, TimeSettings } from "../interfaces"; import { BytesProgress, Dictionary, JobProgress, PercentageProgress, TaggedDevice, @@ -33,7 +33,6 @@ export const mapStateToProps = (props: Everything): JobsPanelProps => ({ export class RawJobsPanel extends React.Component { render() { return -

    {t("Job count")}: {Object.values(this.props.jobs).length}

    @@ -44,6 +43,8 @@ export class RawJobsPanel extends React.Component { } export const JobsPanel = connect(mapStateToProps)(RawJobsPanel); +// eslint-disable-next-line import/no-default-export +export default JobsPanel; export interface JobsAndLogsProps { dispatch: Function; @@ -75,23 +76,21 @@ export class JobsAndLogs }; Logs = () => { - return
    - -
    ; + return ; }; render() { const { jobs, logs } = this.props.jobsPanelState; - return
    + return
    diff --git a/frontend/devices/timezones/__tests__/timezone_selector_test.tsx b/frontend/devices/timezones/__tests__/timezone_selector_test.tsx index 073f520636..169ae48a65 100644 --- a/frontend/devices/timezones/__tests__/timezone_selector_test.tsx +++ b/frontend/devices/timezones/__tests__/timezone_selector_test.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import React from "react"; import { mount } from "enzyme"; import { TimezoneSelector } from "../timezone_selector"; import { inferTimezone } from "../guess_timezone"; diff --git a/frontend/draggable/actions.ts b/frontend/draggable/actions.ts index b360126b49..687e05f113 100644 --- a/frontend/draggable/actions.ts +++ b/frontend/draggable/actions.ts @@ -2,7 +2,7 @@ import { DataXfer, DataXferIntent, DataXferBase } from "./interfaces"; import { SequenceBodyItem as Step, uuid as id } from "farmbot"; import { Everything } from "../interfaces"; import { ReduxAction } from "../redux/interfaces"; -import * as React from "react"; +import React from "react"; import { Actions } from "../constants"; import { UUID } from "../resources/interfaces"; export const STEP_DATATRANSFER_IDENTIFER = "farmbot/sequence-step"; diff --git a/frontend/error_boundary.tsx b/frontend/error_boundary.tsx index 05a80d3aac..52cd1c0cd8 100644 --- a/frontend/error_boundary.tsx +++ b/frontend/error_boundary.tsx @@ -22,7 +22,7 @@ export class ErrorBoundary extends React.Component { no = () => this.props.fallback || ; - ok = () => this.props.children ||
    ; + ok = () => this.props.children; render() { return (this.state.hasError ? this.no : this.ok)(); } } diff --git a/frontend/extras/__tests__/fallback_widget_test.tsx b/frontend/extras/__tests__/fallback_widget_test.tsx index c8bfc0a462..df6eebe707 100644 --- a/frontend/extras/__tests__/fallback_widget_test.tsx +++ b/frontend/extras/__tests__/fallback_widget_test.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import React from "react"; import { mount, shallow } from "enzyme"; import { FallbackWidget, FallbackWidgetProps } from "../fallback_widget"; @@ -21,6 +21,6 @@ describe("", () => { p.helpText = "This is a fake widget."; const wrapper = shallow(); expect(wrapper.html()) - .toContain(" ({ - getPathArray: jest.fn(() => mockPath.split("/")), -})); - jest.mock("../../api/crud", () => ({ edit: jest.fn(), save: jest.fn(), @@ -39,6 +33,7 @@ import { fakeBotLocationData, fakeBotSize, } from "../../__test_support__/fake_bot_data"; import { WebAppConfig } from "farmbot/dist/resources/configs/web_app"; +import { Path } from "../../internal_urls"; describe("", () => { const fakeProps = (): FarmDesignerProps => ({ @@ -69,7 +64,7 @@ describe("", () => { visualizedSequenceBody: [], logs: [], deviceTarget: "", - sourceFbosConfig: jest.fn(), + sourceFbosConfig: () => ({ value: 1, consistent: true }), farmwareEnvs: [], curves: [], }); @@ -103,7 +98,7 @@ describe("", () => { }); it("renders nav titles", () => { - mockPath = Path.mock(Path.plants()); + location.pathname = Path.mock(Path.plants()); const wrapper = mount(); expect(wrapper.find(".panel-nav").first().hasClass("hidden")) .toBeTruthy(); @@ -114,8 +109,10 @@ describe("", () => { }); it("hides panel", () => { - mockPath = Path.mock(Path.designer()); - const wrapper = mount(); + location.pathname = Path.mock(Path.plants()); + const p = fakeProps(); + p.designer.panelOpen = false; + const wrapper = mount(); expect(wrapper.find(".panel-nav").first().hasClass("hidden")) .toBeFalsy(); expect(wrapper.find(".farm-designer-panels").hasClass("panel-open")) diff --git a/frontend/farm_designer/__tests__/location_info_test.tsx b/frontend/farm_designer/__tests__/location_info_test.tsx index 15c79d9f77..e29f625331 100644 --- a/frontend/farm_designer/__tests__/location_info_test.tsx +++ b/frontend/farm_designer/__tests__/location_info_test.tsx @@ -13,10 +13,10 @@ import { } from "../../__test_support__/fake_state/resources"; import { tagAsSoilHeight } from "../../points/soil_height"; import { Actions } from "../../constants"; -import { push } from "../../history"; import { ImageFlipper } from "../../photos/images/image_flipper"; import { Path } from "../../internal_urls"; import { fakeMovementState } from "../../__test_support__/fake_bot_data"; +import { mountWithContext } from "../../__test_support__/mount_with_context"; describe("", () => { const fakeProps = (): LocationInfoProps => ({ @@ -145,7 +145,7 @@ describe("", () => { const p = fakeProps(); p.chosenLocation = { x: 1, y: 1, z: 0 }; p.currentBotLocation = { x: 10, y: 1, z: 0 }; - const wrapper = mount(); + const wrapper = mountWithContext(); expect(wrapper.text().toLowerCase()).toContain("9mm from farmbot"); jest.clearAllMocks(); wrapper.find(".add-point").simulate("click"); @@ -153,7 +153,7 @@ describe("", () => { type: Actions.SET_DRAWN_POINT_DATA, payload: { cx: 1, cy: 1 } }); - expect(push).toHaveBeenCalledWith(Path.points("add")); + expect(mockNavigate).toHaveBeenCalledWith(Path.points("add")); }); }); diff --git a/frontend/farm_designer/__tests__/move_to_test.tsx b/frontend/farm_designer/__tests__/move_to_test.tsx index 7b71104ed4..304ac21879 100644 --- a/frontend/farm_designer/__tests__/move_to_test.tsx +++ b/frontend/farm_designer/__tests__/move_to_test.tsx @@ -1,11 +1,5 @@ jest.mock("../../devices/actions", () => ({ move: jest.fn() })); -const mockPath = ""; -jest.mock("../../history", () => ({ - getPathArray: jest.fn(() => mockPath.split("/")), - push: jest.fn(), -})); - jest.mock("../../config_storage/actions", () => ({ setWebAppConfigValue: jest.fn(), })); @@ -17,18 +11,19 @@ jest.mock("../../ui/popover", () => ({ import React from "react"; import { mount, shallow } from "enzyme"; +import { render, screen, fireEvent } from "@testing-library/react"; import { MoveToForm, MoveToFormProps, MoveModeLink, chooseLocation, GoToThisLocationButtonProps, GoToThisLocationButton, movementPercentRemaining, + MoveModeLinkProps, } from "../move_to"; -import { push } from "../../history"; import { Actions } from "../../constants"; -import { clickButton } from "../../__test_support__/helpers"; import { move } from "../../devices/actions"; import { Path } from "../../internal_urls"; import { setWebAppConfigValue } from "../../config_storage/actions"; import { StringSetting } from "../../session_keys"; import { fakeMovementState } from "../../__test_support__/fake_bot_data"; +import { mockDispatch } from "../../__test_support__/fake_dispatch"; describe("", () => { const fakeProps = (): MoveToFormProps => ({ @@ -101,27 +96,69 @@ describe("", () => { }); describe("", () => { + const fakeProps = (): MoveModeLinkProps => ({ + dispatch: jest.fn(), + }); + it("enters 'move to' mode", () => { - const wrapper = shallow(); - clickButton(wrapper, 0, "move mode"); - expect(push).toHaveBeenCalledWith(Path.location()); + const p = fakeProps(); + const dispatch = jest.fn(); + p.dispatch = mockDispatch(dispatch); + render(); + const button = screen.getByTitle("open move mode panel"); + fireEvent.click(button); + expect(mockNavigate).toHaveBeenCalledWith(Path.location()); + expect(dispatch).toHaveBeenCalledWith({ + type: Actions.SET_PANEL_OPEN, + payload: true, + }); }); }); describe("chooseLocation()", () => { it("updates chosen coordinates", () => { + location.pathname = Path.mock(Path.location()); + const navigate = jest.fn(); const dispatch = jest.fn(); - chooseLocation({ dispatch, gardenCoords: { x: 1, y: 2 } }); + chooseLocation({ navigate, dispatch, gardenCoords: { x: 1, y: 2 } }); expect(dispatch).toHaveBeenCalledWith({ type: Actions.CHOOSE_LOCATION, - payload: { x: 1, y: 2, z: 0 } + payload: { x: 1, y: 2, z: 0 }, }); + expect(navigate).toHaveBeenCalledWith(Path.location({ x: 1, y: 2 })); }); - it("doesn't update coordinates", () => { + it("doesn't update coordinates or navigate", () => { + location.pathname = Path.mock(Path.location()); + const navigate = jest.fn(); const dispatch = jest.fn(); - chooseLocation({ dispatch, gardenCoords: undefined }); + chooseLocation({ navigate, dispatch, gardenCoords: undefined }); expect(dispatch).not.toHaveBeenCalled(); + expect(navigate).not.toHaveBeenCalled(); + }); + + it("doesn't navigate: same location", () => { + location.pathname = Path.mock(Path.location({ x: 1, y: 2 })); + const navigate = jest.fn(); + const dispatch = jest.fn(); + chooseLocation({ navigate, dispatch, gardenCoords: { x: 1, y: 2 } }); + expect(dispatch).toHaveBeenCalledWith({ + type: Actions.CHOOSE_LOCATION, + payload: { x: 1, y: 2, z: 0 }, + }); + expect(navigate).not.toHaveBeenCalled(); + }); + + it("doesn't navigate: not in location panel", () => { + location.pathname = Path.mock(Path.plants()); + const navigate = jest.fn(); + const dispatch = jest.fn(); + chooseLocation({ navigate, dispatch, gardenCoords: { x: 1, y: 2 } }); + expect(dispatch).toHaveBeenCalledWith({ + type: Actions.CHOOSE_LOCATION, + payload: { x: 1, y: 2, z: 0 }, + }); + expect(navigate).not.toHaveBeenCalled(); }); }); diff --git a/frontend/farm_designer/__tests__/panel_header_test.tsx b/frontend/farm_designer/__tests__/panel_header_test.tsx index 59328c26ae..82708ba68f 100644 --- a/frontend/farm_designer/__tests__/panel_header_test.tsx +++ b/frontend/farm_designer/__tests__/panel_header_test.tsx @@ -1,9 +1,3 @@ -import { Path } from "../../internal_urls"; -let mockPath = Path.mock(Path.plants()); -jest.mock("../../history", () => ({ - getPathArray: jest.fn(() => mockPath.split("/")), -})); - let mockDev = false; jest.mock("../../settings/dev/dev_support", () => ({ DevSettings: { @@ -17,50 +11,80 @@ jest.mock("../../redux/store", () => ({ store: { getState: () => mockState } })) import React from "react"; import { shallow, mount, ReactWrapper } from "enzyme"; -import { DesignerNavTabs } from "../panel_header"; +import { DesignerNavTabs, DesignerNavTabsProps } from "../panel_header"; import { buildResourceIndex } from "../../__test_support__/resource_index_builder"; import { fakeFarmwareInstallation, fakeWebAppConfig, } from "../../__test_support__/fake_state/resources"; +import { Path } from "../../internal_urls"; +import { fakeDesignerState } from "../../__test_support__/fake_designer_state"; +import { Actions } from "../../constants"; +import { mockDispatch } from "../../__test_support__/fake_dispatch"; // eslint-disable-next-line @typescript-eslint/no-explicit-any const expectOnlyOneActiveIcon = (wrapper: ReactWrapper) => expect(wrapper.html().match(/active/)?.length).toEqual(1); -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const expectColor = (wrapper: ReactWrapper, color: string) => - expect(wrapper.find(".panel-nav").hasClass(`${color}-panel`)).toBeTruthy(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any const expectActive = (wrapper: ReactWrapper, slug: string) => expect(wrapper.find(`#${slug}`).first().hasClass("active")).toBeTruthy(); describe("", () => { - it.each<[string, string, string]>([ - ["map", Path.designer(), "gray"], - ["plants", Path.plants(), "green"], - ["plants", Path.plantTemplates(1), "green"], - ["tools", Path.toolSlots(), "gray"], - ])("shows active %s icon", (slug, path, color) => { - mockPath = Path.mock(path); - const wrapper = mount(); + const fakeProps = (): DesignerNavTabsProps => ({ + dispatch: jest.fn(), + designer: fakeDesignerState(), + hidden: false, + }); + + it.each<[string, string]>([ + ["sequences", Path.sequencePage()], + ["plants", Path.plants()], + ["plants", Path.plantTemplates(1)], + ["tools", Path.toolSlots()], + ])("shows active %s icon", (slug, path) => { + location.pathname = Path.mock(path); + const p = fakeProps(); + const dispatch = jest.fn(); + p.dispatch = mockDispatch(dispatch); + const wrapper = mount(); expectOnlyOneActiveIcon(wrapper); - expectColor(wrapper, color); expectActive(wrapper, slug); + wrapper.find("#" + slug).first().simulate("click"); + expect(dispatch).toHaveBeenCalledWith({ + type: Actions.SET_PANEL_OPEN, payload: true, + }); + }); + + it("closes panel", () => { + location.pathname = Path.mock(Path.plants()); + const p = fakeProps(); + const dispatch = jest.fn(); + p.dispatch = mockDispatch(dispatch); + p.designer.panelOpen = true; + const wrapper = mount(); + expectOnlyOneActiveIcon(wrapper); + expectActive(wrapper, "plants"); + wrapper.find("a").first().simulate("click"); + expect(dispatch).toHaveBeenCalledWith({ + type: Actions.SET_PANEL_OPEN, payload: false, + }); + p.designer.panelOpen = false; + wrapper.setProps(p); + expectOnlyOneActiveIcon(wrapper); + expect(wrapper.find("a").first().hasClass("active")).toBeTruthy(); }); it("shows inactive icons for logs page", () => { - mockPath = Path.mock(Path.logs()); - const wrapper = mount(); + location.pathname = Path.mock(Path.logs()); + const wrapper = mount(); expect(wrapper.find(".active").length).toEqual(0); }); it("shows active zones icon", () => { - mockPath = Path.mock(Path.zones()); + location.pathname = Path.mock(Path.zones()); mockDev = true; - const wrapper = mount(); + const wrapper = mount(); expectOnlyOneActiveIcon(wrapper); - expectColor(wrapper, "brown"); expectActive(wrapper, "zones"); }); @@ -68,7 +92,7 @@ describe("", () => { const config = fakeWebAppConfig(); config.body.hide_sensors = false; mockState.resources = buildResourceIndex([config]); - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.html()).toContain("sensors"); }); @@ -76,7 +100,7 @@ describe("", () => { const config = fakeWebAppConfig(); config.body.hide_sensors = true; mockState.resources = buildResourceIndex([config]); - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.html()).not.toContain("sensors"); }); @@ -85,7 +109,7 @@ describe("", () => { value: () => [{}, { scrollWidth: 100, scrollLeft: 0, clientWidth: 75 }], configurable: true }); - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.html()).toContain("scroll-indicator"); }); @@ -94,7 +118,7 @@ describe("", () => { value: () => [{}, { scrollWidth: 500, scrollLeft: 0, clientWidth: 750 }], configurable: true }); - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.html()).not.toContain("scroll-indicator"); }); @@ -103,12 +127,12 @@ describe("", () => { value: () => [{}, { scrollWidth: 100, scrollLeft: 25, clientWidth: 75 }], configurable: true }); - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.html()).not.toContain("scroll-indicator"); }); it("calls onScroll", () => { - const wrapper = shallow(); + const wrapper = shallow(); wrapper.setState({ atEnd: false }); wrapper.find(".panel-tabs").simulate("scroll"); expect(wrapper.state().atEnd).toEqual(true); @@ -116,7 +140,7 @@ describe("", () => { it("shows farmware tab", () => { mockState.resources = buildResourceIndex([fakeFarmwareInstallation()]); - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.html()).toContain("farmware"); }); }); diff --git a/frontend/farm_designer/__tests__/reducer_test.ts b/frontend/farm_designer/__tests__/reducer_test.ts index 7c7558d688..e0b6970b1d 100644 --- a/frontend/farm_designer/__tests__/reducer_test.ts +++ b/frontend/farm_designer/__tests__/reducer_test.ts @@ -1,10 +1,3 @@ -import { Path } from "../../internal_urls"; -let mockPath = Path.mock(Path.designer()); -jest.mock("../../history", () => ({ - getPathArray: jest.fn(() => mockPath.split("/")), - push: jest.fn(), -})); - import { designer } from "../reducer"; import { Actions } from "../../constants"; import { ReduxAction } from "../../redux/interfaces"; @@ -19,7 +12,7 @@ import { fakeDesignerState } from "../../__test_support__/fake_designer_state"; import { PointGroupSortType } from "farmbot/dist/resources/api_resources"; import { PlantStage, PointType } from "farmbot"; import { UUID } from "../../resources/interfaces"; -import { push } from "../../history"; +import { Path } from "../../internal_urls"; describe("designer reducer", () => { const oldState = fakeDesignerState; @@ -120,6 +113,24 @@ describe("designer reducer", () => { expect(newState.cropPlantedAt).toEqual("2020-01-20T20:00:00.000Z"); }); + it("sets distance indicator", () => { + const action: ReduxAction = { + type: Actions.SET_DISTANCE_INDICATOR, + payload: "setting", + }; + const newState = designer(oldState(), action); + expect(newState.distanceIndicator).toEqual("setting"); + }); + + it("sets panel open state", () => { + const action: ReduxAction = { + type: Actions.SET_PANEL_OPEN, + payload: false, + }; + const newState = designer(oldState(), action); + expect(newState.panelOpen).toEqual(false); + }); + it("sets hovered plant list item", () => { const action: ReduxAction = { type: Actions.HOVER_PLANT_LIST_ITEM, @@ -175,14 +186,13 @@ describe("designer reducer", () => { }); it("sets query upon chosen location", () => { - mockPath = Path.mock(Path.location()); + location.pathname = Path.mock(Path.location()); const action: ReduxAction = { type: Actions.CHOOSE_LOCATION, payload: { x: 0, y: 0, z: 0 }, }; const newState = designer(oldState(), action); expect(newState.chosenLocation).toEqual({ x: 0, y: 0, z: 0 }); - expect(push).toHaveBeenCalledWith(Path.location({ x: 0, y: 0 })); }); it("sets current point data", () => { diff --git a/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx b/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx index 0e95cadd65..87107d89c8 100644 --- a/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx +++ b/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx @@ -1,18 +1,82 @@ +jest.mock("../../three_d_garden", () => ({ + ThreeDGarden: jest.fn(), +})); + import React from "react"; -import { mount } from "enzyme"; import { ThreeDGardenMapProps, ThreeDGardenMap } from "../three_d_garden_map"; import { fakeMapTransformProps } from "../../__test_support__/map_transform_props"; import { fakeBotSize } from "../../__test_support__/fake_bot_data"; +import { fakeDesignerState } from "../../__test_support__/fake_designer_state"; +import { fakePlant } from "../../__test_support__/fake_state/resources"; +import { render } from "@testing-library/react"; +import { ThreeDGarden } from "../../three_d_garden"; +import { clone } from "lodash"; +import { INITIAL } from "../../three_d_garden/config"; +import { ASSETS } from "../../three_d_garden/constants"; describe("", () => { const fakeProps = (): ThreeDGardenMapProps => ({ mapTransformProps: fakeMapTransformProps(), botSize: fakeBotSize(), gridOffset: { x: 10, y: 10 }, + get3DConfigValue: () => 1, + sourceFbosConfig: () => ({ value: 0, consistent: true }), + designer: fakeDesignerState(), + plants: [fakePlant()], }); - it("renders", () => { - const wrapper = mount(); - expect(wrapper.html()).toContain("three-d-garden"); + it("converts props", () => { + const p = fakeProps(); + const plant0 = fakePlant(); + plant0.body.name = "Spinach"; + plant0.body.openfarm_slug = "spinach"; + const plant1 = fakePlant(); + plant1.body.name = "Unknown"; + plant1.body.openfarm_slug = "not-set"; + p.plants = [plant0, plant1]; + render(); + const expectedConfig = clone(INITIAL); + expectedConfig.bedWallThickness = 1; + expectedConfig.bedHeight = 1; + expectedConfig.bedLengthOuter = 3160; + expectedConfig.bedWidthOuter = 1660; + expectedConfig.botSizeX = 3000; + expectedConfig.botSizeY = 1500; + expectedConfig.ccSupportSize = 1; + expectedConfig.beamLength = 1; + expectedConfig.columnLength = 1; + expectedConfig.zAxisLength = 1; + expectedConfig.bedXOffset = 1; + expectedConfig.bedYOffset = 1; + expectedConfig.bedZOffset = 1; + expectedConfig.legSize = 1; + expectedConfig.bounds = true; + expectedConfig.grid = true; + expectedConfig.pan = true; + expectedConfig.zoom = true; + expectedConfig.soilHeight = 0; + expectedConfig.zGantryOffset = 0; + expectedConfig.zoomBeacons = false; + expect(ThreeDGarden).toHaveBeenCalledWith({ + config: expectedConfig, + plants: [ + { + icon: ASSETS.icons.spinach, + label: "Spinach", + size: 50, + spread: 0, + x: 101, + y: 201, + }, + { + icon: ASSETS.icons.arugula, + label: "Unknown", + size: 50, + spread: 0, + x: 101, + y: 201, + }, + ], + }, {}); }); }); diff --git a/frontend/farm_designer/designer_panel.tsx b/frontend/farm_designer/designer_panel.tsx index 8aeacd1818..325fc3d7f2 100644 --- a/frontend/farm_designer/designer_panel.tsx +++ b/frontend/farm_designer/designer_panel.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { push } from "../history"; +import { useNavigate } from "react-router"; import { last, trim } from "lodash"; import { Link } from "../link"; import { Panel, TAB_COLOR, PanelColor } from "./panel_header"; @@ -38,10 +38,10 @@ interface DesignerPanelHeaderProps { panel?: Panel; panelColor?: PanelColor; title?: string; - titleElement?: JSX.Element; + titleElement?: React.ReactNode; blackText?: boolean; description?: string; - descriptionElement?: JSX.Element; + descriptionElement?: React.ReactNode; backTo?: string; onBack?: () => void; specialStatus?: SpecialStatus; @@ -60,18 +60,18 @@ const backToText = (to: string | undefined): string => { export const DesignerPanelHeader = (props: DesignerPanelHeaderProps) => { const panelColor = props.panel ? TAB_COLOR[props.panel] : props.panelColor; const colorClass = props.colorClass || `${panelColor || PanelColor.gray}-panel`; - const textColor = props.blackText ? "black" : "white"; + const navigate = useNavigate(); return
    - { - props.backTo ? push(props.backTo) : history.back(); + props.backTo ? navigate(props.backTo) : history.back(); props.onBack?.(); }} /> {props.title && - + {t(props.title)} } {props.titleElement} @@ -87,7 +87,6 @@ export const DesignerPanelHeader = (props: DesignerPanelHeaderProps) => {
    {props.description && t(props.description)} {props.descriptionElement} @@ -113,14 +112,14 @@ export const DesignerPanelTop = (props: DesignerPanelTopProps) => { {props.children} {props.onClick && -
    } {props.linkTo && -
    +
    } @@ -145,6 +144,5 @@ export const DesignerPanelContent = (props: DesignerPanelContentProps) => { {props.children} -
    ; }; diff --git a/frontend/farm_designer/index.tsx b/frontend/farm_designer/index.tsx index ee99d84b99..9b7ca0b59b 100755 --- a/frontend/farm_designer/index.tsx +++ b/frontend/farm_designer/index.tsx @@ -5,7 +5,6 @@ import { FarmDesignerProps, State, BotOriginQuadrant, isBotOriginQuadrant, } from "./interfaces"; import { mapStateToProps } from "./state_to_props"; -import { Plants } from "../plants/plant_inventory"; import { GardenMapLegend } from "./map/legend/garden_map_legend"; import { NumericSetting, BooleanSetting } from "../session_keys"; import { isUndefined, isFinite, isEqual, filter } from "lodash"; @@ -25,6 +24,9 @@ import { calculateImageAgeInfo } from "../photos/photo_filter_settings/util"; import { Xyz } from "farmbot"; import { ProfileViewer } from "./map/profile"; import { ThreeDGardenMap } from "./three_d_garden_map"; +import { Outlet } from "react-router"; +import { ErrorBoundary } from "../error_boundary"; +import { get3DConfigValueFunction } from "../settings/three_d_settings"; export const getDefaultAxisLength = (getConfigValue: GetWebAppConfigValue): Record => { @@ -134,7 +136,7 @@ export class RawFarmDesigner }; } - get mapPanelClassName() { return mapPanelClassName(); } + get mapPanelClassName() { return mapPanelClassName(this.props.designer); } render() { const { @@ -157,7 +159,7 @@ export class RawFarmDesigner y: !!this.props.botMcuParams.movement_stop_at_home_y }; - const mapPadding = getMapPadding(getPanelStatus()); + const mapPadding = getMapPadding(getPanelStatus(this.props.designer)); const padHeightOffset = mapPadding.top - mapPadding.top / zoom_level; return
    @@ -188,16 +190,27 @@ export class RawFarmDesigner botSize={this.props.botSize} imageAgeInfo={calculateImageAgeInfo(this.props.latestImages)} /> -
    - - - - localStorage.setItem(key, on ? "" : value)} /> - - - ; + return + + localStorage.setItem(key, on ? "" : value)} /> + ; }; export const ExtraSettings = (searchTerm: string) => { diff --git a/frontend/farm_designer/map/garden_map.tsx b/frontend/farm_designer/map/garden_map.tsx index 6a8750626b..09e9d3ee8f 100644 --- a/frontend/farm_designer/map/garden_map.tsx +++ b/frontend/farm_designer/map/garden_map.tsx @@ -36,7 +36,6 @@ import { } from "./layers/plants/plant_actions"; import { chooseLocation } from "../move_to"; import { GroupOrder } from "./group_order_visual"; -import { push } from "../../history"; import { ErrorBoundary } from "../../error_boundary"; import { TaggedPoint, TaggedPointGroup, PointType } from "farmbot"; import { findGroupFromUrl } from "../../point_groups/group_detail"; @@ -49,6 +48,9 @@ import { chooseProfile, ProfileLine } from "./profile"; import { betterCompact } from "../../util"; import { Path } from "../../internal_urls"; import { AddPlantIcon } from "./active_plant/add_plant_icon"; +import { NavigationContext } from "../../routes_helpers"; +import { NavigateFunction } from "react-router"; +import { setPanelOpen } from "../panel_header"; const BOUND_KEYS = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"]; @@ -60,6 +62,10 @@ export class GardenMap extends this.state = {}; } + static contextType = NavigationContext; + context!: React.ContextType; + navigate: NavigateFunction = url => { this.context(url as string); }; + componentDidMount = () => { document.onkeydown = this.onKeyDown as never; document.onkeyup = this.onKeyUp as never; @@ -136,6 +142,7 @@ export class GardenMap extends gridOffset: this.props.gridOffset, pageX: e.pageX, pageY: e.pageY, + designer: this.props.designer, }); }; @@ -234,7 +241,7 @@ export class GardenMap extends return true; } }; - openLocationInfo(e) && push(Path.plants()); + openLocationInfo(e) && this.navigate(Path.plants()); startNewSelectionBox({ gardenCoords: this.getGardenCoordinates(e), setMapState: this.setMapState, @@ -314,7 +321,9 @@ export class GardenMap extends break; case Mode.locationInfo: e.preventDefault(); + this.props.dispatch(setPanelOpen(true)); !this.state.toLocation && chooseLocation({ + navigate: this.navigate, gardenCoords: this.getGardenCoordinates(e), dispatch: this.props.dispatch }); @@ -363,6 +372,7 @@ export class GardenMap extends break; case Mode.editGroup: resizeBox({ + navigate: this.navigate, selectionBox: this.state.selectionBox, plants: this.props.plants, allPoints: this.props.allPoints, @@ -380,6 +390,7 @@ export class GardenMap extends case Mode.boxSelect: default: resizeBox({ + navigate: this.navigate, selectionBox: this.state.selectionBox, plants: this.props.plants, allPoints: this.props.allPoints, @@ -452,19 +463,19 @@ export class GardenMap extends case Mode.boxSelect: return this.props.designer.selectedPoints ? () => { } - : closePlantInfo(this.props.dispatch); + : closePlantInfo(this.navigate, this.props.dispatch); default: return () => { const area = this.state.previousSelectionBoxArea; const box = area && area > 10; if (this.state.toLocation && [Mode.none, Mode.points, Mode.weeds].includes(getMode())) { - !box && push(Path.location(this.state.toLocation)); + !box && this.navigate(Path.location(this.state.toLocation)); } this.setState({ toLocation: undefined, previousSelectionBoxArea: undefined, }); - closePlantInfo(this.props.dispatch)(); + closePlantInfo(this.navigate, this.props.dispatch)(); }; } }; diff --git a/frontend/farm_designer/map/layers/farmbot/__tests__/negative_position_labels_test.tsx b/frontend/farm_designer/map/layers/farmbot/__tests__/negative_position_labels_test.tsx index 22a0453226..e2d0bb356e 100644 --- a/frontend/farm_designer/map/layers/farmbot/__tests__/negative_position_labels_test.tsx +++ b/frontend/farm_designer/map/layers/farmbot/__tests__/negative_position_labels_test.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import React from "react"; import { shallow } from "enzyme"; import { NegativePositionLabel, NegativePositionLabelProps, diff --git a/frontend/farm_designer/map/layers/farmbot/bot_extents.tsx b/frontend/farm_designer/map/layers/farmbot/bot_extents.tsx index 143079254b..2cb1ae8cff 100644 --- a/frontend/farm_designer/map/layers/farmbot/bot_extents.tsx +++ b/frontend/farm_designer/map/layers/farmbot/bot_extents.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import React from "react"; import { transformXY } from "../../util"; import { BotExtentsProps } from "../../interfaces"; diff --git a/frontend/farm_designer/map/layers/farmbot/negative_position_labels.tsx b/frontend/farm_designer/map/layers/farmbot/negative_position_labels.tsx index 6382c90a3b..e49aac4954 100644 --- a/frontend/farm_designer/map/layers/farmbot/negative_position_labels.tsx +++ b/frontend/farm_designer/map/layers/farmbot/negative_position_labels.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import React from "react"; import { BotPosition } from "../../../../devices/interfaces"; import { MapTransformProps, AxisNumberProperty } from "../../interfaces"; import { transformXY } from "../../util"; diff --git a/frontend/farm_designer/map/layers/logs/__tests__/logs_layer_test.tsx b/frontend/farm_designer/map/layers/logs/__tests__/logs_layer_test.tsx index 087f84aa86..c0f67609d1 100644 --- a/frontend/farm_designer/map/layers/logs/__tests__/logs_layer_test.tsx +++ b/frontend/farm_designer/map/layers/logs/__tests__/logs_layer_test.tsx @@ -1,5 +1,4 @@ -import React from "react"; -import { act } from "react-dom/test-utils"; +import React, { act } from "react"; import { svgMount } from "../../../../../__test_support__/svg_mount"; import { awayFromHome, LogsLayer, positionDifferent } from "../logs_layer"; import { LogsLayerProps } from "../interfaces"; diff --git a/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts b/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts index ef84ef6444..088be097bd 100644 --- a/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts +++ b/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts @@ -14,12 +14,6 @@ jest.mock("../../../actions", () => ({ movePointTo: jest.fn(), })); -import { Path } from "../../../../../internal_urls"; -let mockPath = Path.mock(Path.cropSearch("mint")); -jest.mock("../../../../../history", () => ({ - getPathArray: () => mockPath.split("/"), -})); - import { newPlantKindAndBody, NewPlantKindAndBodyProps, maybeSavePlantLocation, MaybeSavePlantLocationProps, @@ -46,6 +40,7 @@ import { BotOriginQuadrant } from "../../../../interfaces"; import { fakeDesignerState, } from "../../../../../__test_support__/fake_designer_state"; +import { Path } from "../../../../../internal_urls"; describe("newPlantKindAndBody()", () => { it("returns new PlantTemplate", () => { @@ -101,6 +96,10 @@ describe("createPlant()", () => { }); describe("dropPlant()", () => { + beforeEach(() => { + location.pathname = Path.mock(Path.cropSearch("mint")); + }); + const fakeProps = (): DropPlantProps => { const designer = fakeDesignerState(); designer.cropSearchResults = [fakeCropLiveSearchResult()]; @@ -131,7 +130,7 @@ describe("dropPlant()", () => { it("doesn't drop plant", () => { console.log = jest.fn(); - mockPath = Path.mock(Path.cropSearch()) + "/"; + location.pathname = Path.mock(Path.cropSearch()) + "/"; dropPlant(fakeProps()); expect(initSave).not.toHaveBeenCalled(); expect(console.log).toHaveBeenCalledWith("Missing slug."); @@ -139,7 +138,7 @@ describe("dropPlant()", () => { it("doesn't drop plant: no crop", () => { console.log = jest.fn(); - mockPath = Path.mock(Path.cropSearch("mint")); + location.pathname = Path.mock(Path.cropSearch("mint")); const p = fakeProps(); p.designer.companionIndex = 1; p.designer.cropSearchResults = []; @@ -202,7 +201,7 @@ describe("dropPlant()", () => { }); it("throws error", () => { - mockPath = Path.mock(Path.cropSearch("mint")); + location.pathname = Path.mock(Path.cropSearch("mint")); const p = fakeProps(); // eslint-disable-next-line @typescript-eslint/no-explicit-any p.gardenCoords = undefined as any; diff --git a/frontend/farm_designer/map/layers/plants/__tests__/plant_layer_test.tsx b/frontend/farm_designer/map/layers/plants/__tests__/plant_layer_test.tsx index 5e8e9eec36..21de92d57b 100644 --- a/frontend/farm_designer/map/layers/plants/__tests__/plant_layer_test.tsx +++ b/frontend/farm_designer/map/layers/plants/__tests__/plant_layer_test.tsx @@ -1,9 +1,3 @@ -import { FilePath, Path } from "../../../../../internal_urls"; -let mockPath = Path.mock(Path.plants()); -jest.mock("../../../../../history", () => ({ - getPathArray: jest.fn(() => mockPath.split("/")) -})); - import React from "react"; import { PlantLayer } from "../plant_layer"; import { @@ -16,6 +10,9 @@ import { import { svgMount } from "../../../../../__test_support__/svg_mount"; import { shallow } from "enzyme"; import { GardenPlant } from "../garden_plant"; +import { FilePath, Path } from "../../../../../internal_urls"; +import { Actions } from "../../../../../constants"; +import { mockDispatch } from "../../../../../__test_support__/fake_dispatch"; describe("", () => { const fakeProps = (): PlantLayerProps => ({ @@ -60,7 +57,7 @@ describe("", () => { }); it("is in clickable mode", () => { - mockPath = Path.mock(Path.plants()); + location.pathname = Path.mock(Path.plants()); const p = fakeProps(); p.interactions = true; p.plants[0].body.id = 1; @@ -71,7 +68,7 @@ describe("", () => { }); it("is in non-clickable mode", () => { - mockPath = Path.mock(Path.cropSearch("mint/add")); + location.pathname = Path.mock(Path.cropSearch("mint/add")); const p = fakeProps(); p.interactions = false; p.plants[0].body.id = 1; @@ -81,7 +78,7 @@ describe("", () => { }); it("has link to plant", () => { - mockPath = Path.mock(Path.plants()); + location.pathname = Path.mock(Path.plants()); const p = fakeProps(); p.plants[0].body.id = 5; const wrapper = svgMount(); @@ -89,8 +86,22 @@ describe("", () => { .toEqual(Path.plants(5)); }); + it("clicks plant", () => { + location.pathname = Path.mock(Path.plants()); + const p = fakeProps(); + const dispatch = jest.fn(); + p.dispatch = mockDispatch(dispatch); + p.plants[0].body.id = 5; + const wrapper = svgMount(); + wrapper.find("Link").first().simulate("click"); + expect(dispatch).toHaveBeenCalledWith({ + type: Actions.SET_PANEL_OPEN, + payload: true, + }); + }); + it("has link to plant template", () => { - mockPath = Path.mock(Path.plants()); + location.pathname = Path.mock(Path.plants()); const p = fakeProps(); p.plants = [fakePlantTemplate()]; p.plants[0].body.id = 5; @@ -100,7 +111,7 @@ describe("", () => { }); it("has hovered plant", () => { - mockPath = Path.mock(Path.plants()); + location.pathname = Path.mock(Path.plants()); const p = fakeProps(); const plant = fakePlant(); p.plants = [plant]; @@ -110,7 +121,7 @@ describe("", () => { }); it("has plant selected by selection box", () => { - mockPath = Path.mock(Path.plants()); + location.pathname = Path.mock(Path.plants()); const p = fakeProps(); const plant = fakePlant(); p.plants = [plant]; @@ -129,7 +140,7 @@ describe("", () => { }); it("wraps the component in (instead of ", () => { - mockPath = Path.mock(Path.groups(15)); + location.pathname = Path.mock(Path.groups(15)); const p = fakeProps(); const wrapper = svgMount(); expect(wrapper.find("a").length).toBe(0); diff --git a/frontend/farm_designer/map/layers/plants/circle.tsx b/frontend/farm_designer/map/layers/plants/circle.tsx index 06e7eb1da8..9e5b28df6d 100644 --- a/frontend/farm_designer/map/layers/plants/circle.tsx +++ b/frontend/farm_designer/map/layers/plants/circle.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import React from "react"; import { Color } from "../../../../ui"; export interface CircleProps { diff --git a/frontend/farm_designer/map/layers/plants/plant_layer.tsx b/frontend/farm_designer/map/layers/plants/plant_layer.tsx index e3badec076..e4a032bd5b 100644 --- a/frontend/farm_designer/map/layers/plants/plant_layer.tsx +++ b/frontend/farm_designer/map/layers/plants/plant_layer.tsx @@ -5,6 +5,7 @@ import { unpackUUID } from "../../../../util"; import { getMode } from "../../util"; import { Link } from "../../../../link"; import { Path } from "../../../../internal_urls"; +import { setPanelOpen } from "../../../panel_header"; export function PlantLayer(props: PlantLayerProps) { const { @@ -59,6 +60,7 @@ export function PlantLayer(props: PlantLayerProps) { return (getMode() === Mode.editGroup || getMode() === Mode.boxSelect) ? {plant} : dispatch(setPanelOpen(true))} to={path(p.body.id)}> {plant} ; diff --git a/frontend/farm_designer/map/layers/points/__tests__/garden_point_test.tsx b/frontend/farm_designer/map/layers/points/__tests__/garden_point_test.tsx index 2b77cdbf0e..80ccaff177 100644 --- a/frontend/farm_designer/map/layers/points/__tests__/garden_point_test.tsx +++ b/frontend/farm_designer/map/layers/points/__tests__/garden_point_test.tsx @@ -1,10 +1,3 @@ -import { Path } from "../../../../../internal_urls"; -const mockPath = Path.mock(Path.points()); -jest.mock("../../../../../history", () => ({ - push: jest.fn(), - getPathArray: () => mockPath.split("/"), -})); - import React from "react"; import { GardenPoint } from "../garden_point"; import { GardenPointProps } from "../../../interfaces"; @@ -13,7 +6,6 @@ import { fakeMapTransformProps, } from "../../../../../__test_support__/map_transform_props"; import { Actions } from "../../../../../constants"; -import { push } from "../../../../../history"; import { svgMount } from "../../../../../__test_support__/svg_mount"; import { fakeCameraCalibrationData, fakeCameraCalibrationDataFull, @@ -23,6 +15,7 @@ import { CameraViewArea } from "../../farmbot/bot_figure"; import { Color } from "../../../../../ui"; import { tagAsSoilHeight } from "../../../../../points/soil_height"; import { SpecialStatus } from "farmbot"; +import { Path } from "../../../../../internal_urls"; describe("", () => { const fakeProps = (): GardenPointProps => ({ @@ -101,7 +94,7 @@ describe("", () => { const p = fakeProps(); const wrapper = svgMount(); wrapper.find("g").simulate("click"); - expect(push).toHaveBeenCalledWith(Path.points(p.point.body.id)); + expect(mockNavigate).toHaveBeenCalledWith(Path.points(p.point.body.id)); }); it("shows camera view area", () => { diff --git a/frontend/farm_designer/map/layers/points/__tests__/point_layer_test.tsx b/frontend/farm_designer/map/layers/points/__tests__/point_layer_test.tsx index 37dacfcf06..c738eddf5a 100644 --- a/frontend/farm_designer/map/layers/points/__tests__/point_layer_test.tsx +++ b/frontend/farm_designer/map/layers/points/__tests__/point_layer_test.tsx @@ -1,9 +1,3 @@ -import { Path } from "../../../../../internal_urls"; -let mockPath = Path.mock(Path.plants()); -jest.mock("../../../../../history", () => ({ - getPathArray: jest.fn(() => mockPath.split("/")), -})); - import React from "react"; import { PointLayer, PointLayerProps } from "../point_layer"; import { fakePoint } from "../../../../../__test_support__/fake_state/resources"; @@ -19,6 +13,7 @@ import { fakeDesignerState, } from "../../../../../__test_support__/fake_designer_state"; import { tagAsSoilHeight } from "../../../../../points/soil_height"; +import { Path } from "../../../../../internal_urls"; describe("", () => { const fakeProps = (): PointLayerProps => ({ @@ -55,7 +50,7 @@ describe("", () => { }); it("allows point mode interaction", () => { - mockPath = Path.mock(Path.points()); + location.pathname = Path.mock(Path.points()); const p = fakeProps(); p.interactions = true; const wrapper = svgMount(); diff --git a/frontend/farm_designer/map/layers/points/garden_point.tsx b/frontend/farm_designer/map/layers/points/garden_point.tsx index ae02f8059c..1e5a28b4fc 100644 --- a/frontend/farm_designer/map/layers/points/garden_point.tsx +++ b/frontend/farm_designer/map/layers/points/garden_point.tsx @@ -8,6 +8,7 @@ import { Color } from "../../../../ui"; import { soilHeightPoint } from "../../../../points/soil_height"; import { SpecialStatus } from "farmbot"; import { Path } from "../../../../internal_urls"; +import { useNavigate } from "react-router"; // eslint-disable-next-line complexity export const GardenPoint = (props: GardenPointProps) => { @@ -26,6 +27,7 @@ export const GardenPoint = (props: GardenPointProps) => { const color = meta.color || "green"; const unsaved = point.specialStatus !== SpecialStatus.SAVED; const selected = current || hovered; + const navigate = useNavigate(); return { onClick={() => { props.dispatch(selectPoint([point.uuid])); props.dispatch(setHoveredPlant(undefined)); - mapPointClickAction(props.dispatch, point.uuid, Path.points(id))(); + mapPointClickAction(navigate, props.dispatch, point.uuid, Path.points(id))(); }}> ", () => { function fakeProps(): SpreadOverlapHelperProps { @@ -167,14 +168,14 @@ describe("SpreadOverlapHelper functions", () => { it("overlapText()", () => { const spreadData = { active: 100, inactive: 200 }; - const svgText = shallow(overlapText(100, 100, 150, spreadData)); + const svgText = svgMount(overlapText(100, 100, 150, spreadData)); ["Active: 80%", "Inactive: 40%", "orange"].map(string => expect(svgText.text()).toContain(string)); }); it("overlapText(): no overlap", () => { const spreadData = { active: 100, inactive: 200 }; - const svgText = shallow(overlapText(100, 100, 0, spreadData)); + const svgText = svgMount(overlapText(100, 100, 0, spreadData)); expect(svgText.text()).toEqual(""); }); }); diff --git a/frontend/farm_designer/map/layers/spread/spread_overlap_helper.tsx b/frontend/farm_designer/map/layers/spread/spread_overlap_helper.tsx index 2204e8b739..75089b6ac8 100644 --- a/frontend/farm_designer/map/layers/spread/spread_overlap_helper.tsx +++ b/frontend/farm_designer/map/layers/spread/spread_overlap_helper.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import React from "react"; import { SpreadOverlapHelperProps } from "../../interfaces"; import { round, transformXY, defaultSpreadCmDia } from "../../util"; import { BotPosition } from "../../../../devices/interfaces"; @@ -104,7 +104,7 @@ export function overlapText( qy: number, overlap: number, spreadData: SpreadRadii, -): JSX.Element { +): React.ReactNode { // Display spread overlap percentages for debugging purposes. const activeSpreadDia = spreadData.active * 2; const inactiveSpreadDia = spreadData.inactive * 2; diff --git a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_layer_test.tsx b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_layer_test.tsx index f023bbcaa2..168cf76f96 100644 --- a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_layer_test.tsx +++ b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_layer_test.tsx @@ -1,10 +1,3 @@ -import { Path } from "../../../../../internal_urls"; -let mockPath = Path.mock(Path.plants()); -jest.mock("../../../../../history", () => ({ - push: jest.fn(), - getPathArray: jest.fn(() => { return mockPath.split("/"); }) -})); - import React from "react"; import { ToolSlotLayer, ToolSlotLayerProps } from "../tool_slot_layer"; import { @@ -12,10 +5,10 @@ import { } from "../../../../../__test_support__/map_transform_props"; import { fakeResource } from "../../../../../__test_support__/fake_resource"; import { shallow } from "enzyme"; -import { push } from "../../../../../history"; import { ToolSlotPointer } from "farmbot/dist/resources/api_resources"; import { TaggedToolSlotPointer } from "farmbot"; import { ToolSlotPoint } from "../tool_slot_point"; +import { Path } from "../../../../../internal_urls"; describe("", () => { function fakeProps(): ToolSlotLayerProps { @@ -56,16 +49,16 @@ describe("", () => { }); it("doesn't navigate to tools page", async () => { - mockPath = Path.mock(Path.plants(1)); + location.pathname = Path.mock(Path.plants(1)); const p = fakeProps(); const wrapper = shallow(); const tools = wrapper.find("g").first(); await tools.simulate("click"); - expect(push).not.toHaveBeenCalled(); + expect(mockNavigate).not.toHaveBeenCalled(); }); it("is in clickable mode", () => { - mockPath = Path.mock(Path.cropSearch("mint/add")); + location.pathname = Path.mock(Path.cropSearch("mint/add")); const p = fakeProps(); p.interactions = true; const wrapper = shallow(); @@ -74,7 +67,7 @@ describe("", () => { }); it("is in non-clickable mode", () => { - mockPath = Path.mock(Path.cropSearch("mint/add")); + location.pathname = Path.mock(Path.cropSearch("mint/add")); const p = fakeProps(); p.interactions = false; const wrapper = shallow(); diff --git a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx index d7d5a33c0c..2fdb9ca1b0 100644 --- a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx +++ b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx @@ -1,10 +1,3 @@ -import { Path } from "../../../../../internal_urls"; -const mockPath = Path.mock(Path.toolSlots()); -jest.mock("../../../../../history", () => ({ - push: jest.fn(), - getPathArray: () => mockPath.split("/"), -})); - import React from "react"; import { ToolSlotPoint, TSPProps } from "../tool_slot_point"; import { @@ -14,9 +7,9 @@ import { fakeMapTransformProps, } from "../../../../../__test_support__/map_transform_props"; import { svgMount } from "../../../../../__test_support__/svg_mount"; -import { push } from "../../../../../history"; import { shallow } from "enzyme"; import { Actions } from "../../../../../constants"; +import { Path } from "../../../../../internal_urls"; describe("", () => { const fakeProps = (): TSPProps => ({ @@ -48,7 +41,7 @@ describe("", () => { p.slot.toolSlot.body.id = 1; const wrapper = svgMount(); wrapper.find("g").first().simulate("click"); - expect(push).toHaveBeenCalledWith(Path.toolSlots(1)); + expect(mockNavigate).toHaveBeenCalledWith(Path.toolSlots(1)); }); it("displays tool name", () => { diff --git a/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx b/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx index f280a46738..1b4bcccc3b 100644 --- a/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx +++ b/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Color } from "../../../../ui/index"; +import { Color } from "../../../../ui"; import { BotOriginQuadrant } from "../../../interfaces"; import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources"; diff --git a/frontend/farm_designer/map/layers/tool_slots/tool_slot_point.tsx b/frontend/farm_designer/map/layers/tool_slots/tool_slot_point.tsx index ce1f46eb9a..e85312a5fa 100644 --- a/frontend/farm_designer/map/layers/tool_slots/tool_slot_point.tsx +++ b/frontend/farm_designer/map/layers/tool_slots/tool_slot_point.tsx @@ -13,6 +13,7 @@ import { reduceToolName } from "../../tool_graphics/all_tools"; import { Path } from "../../../../internal_urls"; import { Actions } from "../../../../constants"; import { Circle } from "../plants/circle"; +import { useNavigate } from "react-router"; export interface TSPProps { slot: SlotWithTool; @@ -54,12 +55,13 @@ export const ToolSlotPoint = (props: TSPProps) => { payload: hover ? toolSlot.uuid : undefined }); }; + const navigate = useNavigate(); return { props.dispatch(selectPoint([toolSlot.uuid])); - mapPointClickAction(props.dispatch, toolSlot.uuid, + mapPointClickAction(navigate, props.dispatch, toolSlot.uuid, Path.toolSlots(id))(); props.dispatch(setHoveredPlant(undefined)); }}> diff --git a/frontend/farm_designer/map/layers/weeds/__tests__/garden_weed_test.tsx b/frontend/farm_designer/map/layers/weeds/__tests__/garden_weed_test.tsx index 7c37fec1e1..d0bec4e7cd 100644 --- a/frontend/farm_designer/map/layers/weeds/__tests__/garden_weed_test.tsx +++ b/frontend/farm_designer/map/layers/weeds/__tests__/garden_weed_test.tsx @@ -1,10 +1,3 @@ -import { Path } from "../../../../../internal_urls"; -const mockPath = Path.mock(Path.weeds()); -jest.mock("../../../../../history", () => ({ - push: jest.fn(), - getPathArray: () => mockPath.split("/"), -})); - import React from "react"; import { GardenWeed } from "../garden_weed"; import { GardenWeedProps } from "../../../interfaces"; @@ -13,8 +6,8 @@ import { fakeMapTransformProps, } from "../../../../../__test_support__/map_transform_props"; import { Actions } from "../../../../../constants"; -import { push } from "../../../../../history"; import { svgMount } from "../../../../../__test_support__/svg_mount"; +import { Path } from "../../../../../internal_urls"; describe("", () => { const fakeProps = (): GardenWeedProps => ({ @@ -97,7 +90,7 @@ describe("", () => { const p = fakeProps(); const wrapper = svgMount(); wrapper.find("g").first().simulate("click"); - expect(push).toHaveBeenCalledWith(Path.weeds(p.weed.body.id)); + expect(mockNavigate).toHaveBeenCalledWith(Path.weeds(p.weed.body.id)); expect(p.dispatch).toHaveBeenCalledWith({ type: Actions.SELECT_POINT, payload: [p.weed.uuid], @@ -126,7 +119,7 @@ describe("", () => { p.selected = false; p.current = false; const wrapper = svgMount(); - wrapper.find(GardenWeed).setState({ iconHovered: true }); + wrapper.find(GardenWeed).simulate("mouseEnter"); expect(wrapper.html()).not.toContain("weed-indicator"); }); }); diff --git a/frontend/farm_designer/map/layers/weeds/__tests__/weed_layer_test.tsx b/frontend/farm_designer/map/layers/weeds/__tests__/weed_layer_test.tsx index 791ac7968d..0320a0771c 100644 --- a/frontend/farm_designer/map/layers/weeds/__tests__/weed_layer_test.tsx +++ b/frontend/farm_designer/map/layers/weeds/__tests__/weed_layer_test.tsx @@ -1,9 +1,3 @@ -import { Path } from "../../../../../internal_urls"; -let mockPath = Path.mock(Path.plants()); -jest.mock("../../../../../history", () => ({ - getPathArray: jest.fn(() => mockPath.split("/")), -})); - import React from "react"; import { WeedLayer, WeedLayerProps } from "../weed_layer"; import { fakeWeed } from "../../../../../__test_support__/fake_state/resources"; @@ -12,6 +6,7 @@ import { } from "../../../../../__test_support__/map_transform_props"; import { GardenWeed } from "../garden_weed"; import { svgMount } from "../../../../../__test_support__/svg_mount"; +import { Path } from "../../../../../internal_urls"; describe("", () => { const fakeProps = (): WeedLayerProps => ({ @@ -46,7 +41,7 @@ describe("", () => { }); it("allows weed mode interaction", () => { - mockPath = Path.mock(Path.weeds()); + location.pathname = Path.mock(Path.weeds()); const p = fakeProps(); p.interactions = true; const wrapper = svgMount(); @@ -55,7 +50,7 @@ describe("", () => { }); it("is selected", () => { - mockPath = Path.mock(Path.weeds()); + location.pathname = Path.mock(Path.weeds()); const p = fakeProps(); const weed = fakeWeed(); p.weeds = [weed]; diff --git a/frontend/farm_designer/map/layers/weeds/garden_weed.tsx b/frontend/farm_designer/map/layers/weeds/garden_weed.tsx index f383b2a4e8..d86e540f6a 100644 --- a/frontend/farm_designer/map/layers/weeds/garden_weed.tsx +++ b/frontend/farm_designer/map/layers/weeds/garden_weed.tsx @@ -6,95 +6,90 @@ import { Color } from "../../../../ui"; import { mapPointClickAction, selectPoint, setHoveredPlant } from "../../actions"; import { Circle } from "../plants/circle"; import { FilePath, Path } from "../../../../internal_urls"; +import { useNavigate } from "react-router"; -interface GardenWeedState { - iconHovered: boolean; -} +// eslint-disable-next-line complexity +export const GardenWeed = (props: GardenWeedProps) => { + const [iconHovered, setIconHovered] = React.useState(false); -export class GardenWeed - extends React.Component { - state: GardenWeedState = { iconHovered: false }; - - iconHover = (action: "start" | "end") => () => { + const iconHover = (action: "start" | "end") => () => { const startHover = action === "start"; - this.setState({ iconHovered: startHover }); - this.props.dispatch({ + setIconHovered(startHover); + props.dispatch({ type: Actions.TOGGLE_HOVERED_POINT, - payload: startHover ? this.props.weed.uuid : undefined + payload: startHover ? props.weed.uuid : undefined }); }; - // eslint-disable-next-line complexity - render() { - const { - weed, mapTransformProps, hovered, current, selected, animate, - radiusVisible, dispatch, - } = this.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 newClass = id ? "" : "new"; - const className = [ - "weed-image", - newClass, - `is-chosen-${current || selected}`, - animate ? "animate" : "", - ].join(" "); - const plantIconSize = scaleIcon(radius); - const iconRadius = hovered ? plantIconSize * 1.1 : plantIconSize; - return { - dispatch(selectPoint([weed.uuid])); - dispatch(setHoveredPlant(undefined)); - mapPointClickAction(dispatch, weed.uuid, Path.weeds(id))(); - }}> - - - - - - + const { + weed, mapTransformProps, hovered, current, selected, animate, + radiusVisible, dispatch, + } = 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 newClass = id ? "" : "new"; + const className = [ + "weed-image", + newClass, + `is-chosen-${current || selected}`, + animate ? "animate" : "", + ].join(" "); + const plantIconSize = scaleIcon(radius); + const iconRadius = hovered ? plantIconSize * 1.1 : plantIconSize; + const navigate = useNavigate(); + return { + dispatch(selectPoint([weed.uuid])); + dispatch(setHoveredPlant(undefined)); + mapPointClickAction(navigate, dispatch, weed.uuid, Path.weeds(id))(); + }}> + + + + + + - {animate && - } + {animate && + } - {radiusVisible && - } + {radiusVisible && + } - {(current || selected || (hovered && !this.state.iconHovered)) && - - - } + {(current || selected || (hovered && !iconHovered)) && + + + } - - - - ; - } -} + + + + ; +}; diff --git a/frontend/farm_designer/map/layers/zones/__tests__/zones_test.tsx b/frontend/farm_designer/map/layers/zones/__tests__/zones_test.tsx index e7c62bfe31..71bdaf9e4a 100644 --- a/frontend/farm_designer/map/layers/zones/__tests__/zones_test.tsx +++ b/frontend/farm_designer/map/layers/zones/__tests__/zones_test.tsx @@ -10,7 +10,6 @@ import { fakeMapTransformProps, } from "../../../../../__test_support__/map_transform_props"; import { DEFAULT_CRITERIA } from "../../../../../point_groups/criteria/interfaces"; -import { push } from "../../../../../history"; import { Path } from "../../../../../internal_urls"; const fakeProps = (): ZonesProps => ({ @@ -67,7 +66,7 @@ describe("", () => { p.group.body.criteria.number_eq = { x: [100], y: [200, 300] }; const wrapper = svgMount(); wrapper.find("#zones-0D-1").simulate("click"); - expect(push).toHaveBeenCalledWith(Path.groups(1)); + expect(mockNavigate).toHaveBeenCalledWith(Path.groups(1)); }); }); @@ -123,7 +122,7 @@ describe("", () => { p.group.body.criteria.number_eq = { x: [], y: [200, 300] }; const wrapper = svgMount(); wrapper.find("#zones-1D-1").simulate("click"); - expect(push).toHaveBeenCalledWith(Path.groups(1)); + expect(mockNavigate).toHaveBeenCalledWith(Path.groups(1)); }); }); @@ -166,7 +165,7 @@ describe("", () => { p.group.body.criteria.number_lt = { x: 300, y: 400 }; const wrapper = svgMount(); wrapper.find("#zones-2D-1").simulate("click"); - expect(push).toHaveBeenCalledWith(Path.groups(1)); + expect(mockNavigate).toHaveBeenCalledWith(Path.groups(1)); }); }); diff --git a/frontend/farm_designer/map/layers/zones/zones.tsx b/frontend/farm_designer/map/layers/zones/zones.tsx index 89245bd5e6..2eb4799b30 100644 --- a/frontend/farm_designer/map/layers/zones/zones.tsx +++ b/frontend/farm_designer/map/layers/zones/zones.tsx @@ -4,7 +4,7 @@ import { MapTransformProps, BotSize } from "../../interfaces"; import { transformXY } from "../../util"; import { isUndefined } from "lodash"; import { UUID } from "../../../../resources/interfaces"; -import { push } from "../../../../history"; +import { useNavigate } from "react-router"; import { Path } from "../../../../internal_urls"; export interface ZonesProps { @@ -45,9 +45,6 @@ export const getZoneType = (group: TaggedPointGroup): ZoneType => { return ZoneType.none; }; -const openGroup = (id: number | undefined) => - () => push(Path.groups(id)); - /** Bounds for area selected by criteria or bot extents. */ const getBoundary = (props: GetBoundaryProps): Boundary => { const { criteria } = props.group.body; @@ -91,7 +88,11 @@ const zone0D = (props: ZonesProps) => export const Zones0D = (props: ZonesProps) => { const current = props.group.uuid == props.currentGroup; const { id } = props.group.body; - return { + navigate(Path.groups(id)); + }} className={current ? "current" : ""}> {zone0D(props).map((point, i) => )} @@ -133,7 +134,11 @@ const zone1D = (props: ZonesProps) => { export const Zones1D = (props: ZonesProps) => { const current = props.group.uuid == props.currentGroup; const { id } = props.group.body; - return { + navigate(Path.groups(id)); + }} className={current ? "current" : ""}> {zone1D(props).map((line, i) => { } : {}; const { id } = props.group.body; - return { + navigate(Path.groups(id)); + }} className={current ? "current" : ""}> {!zone.selectsAll && { const { setting, getConfigValue } = props; const value = !!(setting ? getConfigValue(setting) : undefined); return
    + className={`row grid-exp-1 align-baseline ${props.disabled ? "disabled" : ""}`}> {props.helpText && } {setting &&
    ; export const PlantsSubMenu = (props: SettingsSubMenuProps) => -
    +
    ; export const FarmbotSubMenu = (props: SettingsSubMenuProps) => -
    +
    { label={DeviceSetting.showPhotos} onClick={toggle(BooleanSetting.show_images)} submenuTitle={t("filter")} - popover={
    + popover={
    @@ -209,7 +209,7 @@ const LayerToggles = (props: GardenMapLegendProps) => { }; export const MapSettingsContent = (props: SettingsSubMenuProps) => -
    +
    - + - +
    diff --git a/frontend/farm_designer/map/legend/layer_toggle.tsx b/frontend/farm_designer/map/legend/layer_toggle.tsx index 174e360daa..caac1d6b7c 100644 --- a/frontend/farm_designer/map/legend/layer_toggle.tsx +++ b/frontend/farm_designer/map/legend/layer_toggle.tsx @@ -11,7 +11,7 @@ export interface LayerToggleProps { label: DeviceSetting; value: boolean | undefined; onClick(): void; - popover?: JSX.Element | undefined; + popover?: React.ReactElement; submenuTitle?: string; } @@ -34,7 +34,7 @@ export function LayerToggle(props: LayerToggleProps) { } content={popover} />} diff --git a/frontend/farm_designer/map/legend/z_display.tsx b/frontend/farm_designer/map/legend/z_display.tsx index 5970b0e3c6..8f7ed5a947 100644 --- a/frontend/farm_designer/map/legend/z_display.tsx +++ b/frontend/farm_designer/map/legend/z_display.tsx @@ -1,10 +1,9 @@ import React from "react"; import { t } from "../../../i18next_wrapper"; import { ConfigurationName, McuParams, TaggedPoint } from "farmbot"; -import { ToggleButton } from "../../../ui/toggle_button"; import { BotLocationData, SourceFbosConfig } from "../../../devices/interfaces"; import { BotSize } from "../interfaces"; -import { MarkedSlider } from "../../../ui"; +import { MarkedSlider, ToggleButton } from "../../../ui"; import { ceil, round } from "lodash"; import { soilHeightPoint } from "../../../points/soil_height"; diff --git a/frontend/farm_designer/map/profile/__tests__/content_test.tsx b/frontend/farm_designer/map/profile/__tests__/content_test.tsx index e2933152f1..be44ca4220 100644 --- a/frontend/farm_designer/map/profile/__tests__/content_test.tsx +++ b/frontend/farm_designer/map/profile/__tests__/content_test.tsx @@ -1,9 +1,3 @@ -import { Path } from "../../../../internal_urls"; -let mockPath = Path.mock(Path.designer()); -jest.mock("../../../../history", () => ({ - getPathArray: jest.fn(() => mockPath.split("/")), -})); - jest.mock("../../layers/points/interpolation_map", () => ({ getInterpolationData: () => [{ x: 111, y: 112, z: 113 }], fetchInterpolationOptions: () => ({ stepSize: 100 }), @@ -33,6 +27,7 @@ import { SlotDimensions } from "../../tool_graphics/slot"; import { fakeDesignerState, } from "../../../../__test_support__/fake_designer_state"; +import { Path } from "../../../../internal_urls"; describe("", () => { const fakeProps = (): ProfileSvgProps => ({ @@ -416,7 +411,7 @@ describe("", () => { }); it("renders interpolated soil", () => { - mockPath = Path.mock(Path.location()); + location.pathname = Path.mock(Path.location()); const p = fakeProps(); p.expanded = true; p.designer.profileAxis = "y"; diff --git a/frontend/farm_designer/map/profile/tools.tsx b/frontend/farm_designer/map/profile/tools.tsx index 9fb37dad3b..351d492bfb 100644 --- a/frontend/farm_designer/map/profile/tools.tsx +++ b/frontend/farm_designer/map/profile/tools.tsx @@ -89,8 +89,7 @@ export const UTMProfile = (props: ProfileUtmProps) => { x={profileUtmH - 2} y={profileUtmV - 2} width={4} height={4} />} + xlinkHref={FilePath.image("farmbot")} opacity={1} /> {toolInfo.name ? { const { dispatch } = props; const { profileOpen, profileFollowBot } = props.designer; const axis = props.designer.profileAxis; - const panelStatus = getPanelStatus(); + const panelStatus = getPanelStatus(props.designer); const { x, y } = profileFollowBot ? props.botLocationData.position : props.designer.profilePosition; diff --git a/frontend/farm_designer/map/util.ts b/frontend/farm_designer/map/util.ts index 62ec4cd934..cb42aa3923 100644 --- a/frontend/farm_designer/map/util.ts +++ b/frontend/farm_designer/map/util.ts @@ -1,4 +1,4 @@ -import { BotOriginQuadrant } from "../interfaces"; +import { BotOriginQuadrant, DesignerState } from "../interfaces"; import { McuParams, Xyz } from "farmbot"; import { StepsPerMm } from "../../devices/interfaces"; import { @@ -61,8 +61,8 @@ export enum MapPanelStatus { } /** Get farm designer side panel status. */ -export const getPanelStatus = (): MapPanelStatus => { - if (Path.equals(Path.designer())) { +export const getPanelStatus = (designer: DesignerState): MapPanelStatus => { + if (!designer.panelOpen) { return isMobile() ? MapPanelStatus.mobileClosed : MapPanelStatus.closed; } const mode = getMode(); @@ -75,8 +75,8 @@ export const getPanelStatus = (): MapPanelStatus => { }; /** Get panel status class name for farm designer. */ -export const mapPanelClassName = () => { - switch (getPanelStatus()) { +export const mapPanelClassName = (designer: DesignerState) => { + switch (getPanelStatus(designer)) { case MapPanelStatus.short: return "short-panel"; case MapPanelStatus.mobileClosed: return "panel-closed-mobile"; case MapPanelStatus.closed: return "panel-closed"; @@ -90,12 +90,12 @@ export const mapPanelClassName = () => { export const getMapPadding = (panelStatus: MapPanelStatus): { left: number, top: number } => { switch (panelStatus) { - case MapPanelStatus.short: return { left: 20, top: 350 }; - case MapPanelStatus.mobileClosed: return { left: 20, top: 160 }; - case MapPanelStatus.closed: return { left: 20, top: 110 }; + case MapPanelStatus.short: return { left: 10, top: 350 }; + case MapPanelStatus.mobileClosed: return { left: 10, top: 160 }; + case MapPanelStatus.closed: return { left: 10, top: 90 }; case MapPanelStatus.open: default: - return { left: 468, top: 110 }; + return { left: 475, top: 90 }; } }; @@ -330,13 +330,18 @@ export const savedGardenOpen = () => const getZoomLevelFromMap = (map: Element) => parseFloat((window.getComputedStyle(map).transform || "(1").split("(")[1]); +export interface GetGardenCoordinatesProps { + mapTransformProps: MapTransformProps; + gridOffset: AxisNumberProperty; + pageX: number; + pageY: number; + designer: DesignerState; +} + /** Get the garden map coordinate of a cursor or screen interaction. */ -export const getGardenCoordinates = (props: { - mapTransformProps: MapTransformProps, - gridOffset: AxisNumberProperty, - pageX: number, - pageY: number, -}): AxisNumberProperty | undefined => { +export const getGardenCoordinates = ( + props: GetGardenCoordinatesProps, +): AxisNumberProperty | undefined => { const el = document.querySelector(".drop-area-svg"); const map = document.querySelector(".farm-designer-map"); const page = document.querySelector(".farm-designer"); @@ -348,7 +353,7 @@ export const getGardenCoordinates = (props: { mapTransformProps: props.mapTransformProps, gridOffset: props.gridOffset, zoomLvl, - panelStatus: getPanelStatus(), + panelStatus: getPanelStatus(props.designer), }; return translateScreenToGarden(params); } else { diff --git a/frontend/farm_designer/map_size_setting.tsx b/frontend/farm_designer/map_size_setting.tsx index 8aac841557..3b107f52d9 100644 --- a/frontend/farm_designer/map_size_setting.tsx +++ b/frontend/farm_designer/map_size_setting.tsx @@ -3,7 +3,7 @@ import { GetWebAppConfigValue, setWebAppConfigValue, } from "../config_storage/actions"; import { t } from "../i18next_wrapper"; -import { Row, Col } from "../ui"; +import { Row } from "../ui"; import { NumericSetting } from "../session_keys"; import { NumberConfigKey as WebAppNumberConfigKey, @@ -18,19 +18,15 @@ interface LengthInputProps { } const LengthInput = (props: LengthInputProps) => - -
    - - - - props.dispatch(setWebAppConfigValue( - props.setting, e.currentTarget.value))} /> - + + + props.dispatch(setWebAppConfigValue( + props.setting, e.currentTarget.value))} /> ; export interface MapSizeInputsProps { @@ -39,7 +35,7 @@ export interface MapSizeInputsProps { } export const MapSizeInputs = (props: MapSizeInputsProps) => -
    +
    - -
    - - - - - - - - - - - - - - - - + + + + +
    + + this.setState({ z: val })} axis={"z"} value={this.state.z} /> -
    - - + - - - - - - `${value}%`} - value={this.state.speed} - onChange={speed => this.setState({ speed })} /> - + + + `${value}%`} + value={this.state.speed} + onChange={speed => this.setState({ speed })} /> this.setState({ safeZ: !this.state.safeZ })} /> @@ -114,27 +99,39 @@ export class MoveToForm extends React.Component -
    +export interface MoveModeLinkProps { + dispatch: Function; +} + +export const MoveModeLink = (props: MoveModeLinkProps) => { + const navigate = useNavigate(); + return
    ; +}; /** Mark a new bot target location on the map. */ export const chooseLocation = (props: { + navigate: NavigateFunction, gardenCoords: AxisNumberProperty | undefined, dispatch: Function, }) => { if (props.gardenCoords) { - props.dispatch(chooseLocationAction({ + const loc = { x: Math.max(0, props.gardenCoords.x), y: Math.max(0, props.gardenCoords.y), z: 0, - })); + }; + props.dispatch(chooseLocationAction(loc)); + navigateToLocation(props.navigate, loc); } }; @@ -181,7 +178,7 @@ export class GoToThisLocationButton ].join(" "); const defaultDestination = coordinateFromAxes(target, current, defaultAxes); const remaining = movementPercentRemaining(current, this.props.movementState); - return
    + return
    - props.fieldSet("startDate", e.currentTarget.value)} /> - - - props.fieldSet("startTime", e.currentTarget.value)} - disabled={props.disabled || forceMidnight} - hidden={forceMidnight} /> - + props.fieldSet("startDate", e.currentTarget.value)} /> + props.fieldSet("startTime", e.currentTarget.value)} + disabled={props.disabled || forceMidnight} + hidden={forceMidnight} /> ; }; @@ -470,7 +458,7 @@ export interface RepeatFormProps { export const RepeatForm = (props: RepeatFormProps) => { const allowRepeat = !props.isRegimen && props.fieldGet("timeUnit") !== NEVER; - return
    + return
    {!props.isRegimen ?
    - {content.name} -
    - {t("Description")} -
    - {content.description || ""} -
    -
    -
    - - {t("View")} - -
    {t("Name")}{t("Link")}