diff --git a/README.md b/README.md index d2023f3d5..0b5c68d5e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Eyra Mono -Primary collection of Eyra projects +Primary collection of Eyra projects ## Projects @@ -19,7 +19,7 @@ Project implementing a SaaS platform based on interlinked modules called Systems * Campaign * Assignment * Lab -* Survey +* Questionnaire * Pool * Data Donation * .. @@ -28,12 +28,12 @@ Project implementing a SaaS platform based on interlinked modules called Systems * Next -Primary bundle with all features available except Link specific features. +Primary bundle with all features available except Link specific features. Next is hosted on: https://eyra.co * Link -Secundary bundle with only Panl specific features. +Secundary bundle with only Panl specific features. Link is hosted on: https://researchpanl.eu ## Banking Proxy diff --git a/core/assets/js/app.js b/core/assets/js/app.js index c66003216..751d48c6e 100644 --- a/core/assets/js/app.js +++ b/core/assets/js/app.js @@ -26,6 +26,7 @@ import "./100vh-fix"; import { ViewportResize } from "./viewport_resize"; import { SidePanel } from "./side_panel"; import { Toggle } from "./toggle"; +import { Cell } from "./cell"; import { LiveContent, LiveField } from "./live_content"; import { Tabbar, TabbarItem, TabbarFooterItem } from "./tabbar"; import { PythonUploader } from "./python_uploader"; @@ -96,6 +97,7 @@ let Hooks = { ViewportResize, SidePanel, Toggle, + Cell, LiveContent, LiveField, Tabbar, diff --git a/core/assets/js/cell.js b/core/assets/js/cell.js new file mode 100644 index 000000000..532ae64ac --- /dev/null +++ b/core/assets/js/cell.js @@ -0,0 +1,82 @@ +const COLLAPSED = "collapsed"; +const EXPANDED = "expanded"; + +const COLLAPSED_VIEW = "cell-collapsed-view"; +const EXPANDED_VIEW = "cell-expanded-view"; + +const COLLAPSE_BUTTON = "cell-collapse-button"; +const EXPAND_BUTTON = "cell-expand-button"; + +export const Cell = { + mounted() { + this.collapseButton = this.el.getElementsByClassName(COLLAPSE_BUTTON)[0]; + this.collapsedView = this.el.getElementsByClassName(COLLAPSED_VIEW)[0]; + + this.expandButton = this.el.getElementsByClassName(EXPAND_BUTTON)[0]; + this.expandedView = this.el.getElementsByClassName(EXPANDED_VIEW)[0]; + + this.collapseButton.addEventListener("click", (event) => { + event.stopPropagation(); + this.updateStatus(COLLAPSED); + }); + + this.expandButton.addEventListener("click", (event) => { + event.stopPropagation(); + this.updateStatus(EXPANDED); + }); + + var initialStatus = this.el.dataset.initialStatus + ? this.el.dataset.initialTab + : COLLAPSED; + + var savedStatus = this.loadStatus(); + this.status = savedStatus ? savedStatus : initialStatus; + this.updateUI(); + }, + + updated() { + this.updateUI(); + }, + + loadStatus() { + const key = this.getStatusKey(); + const status = window.localStorage.getItem(key); + if (typeof status === "string") { + return status; + } + return undefined; + }, + + saveStatus() { + console.info("saveStatus ", this.status); + window.localStorage.setItem(this.getStatusKey(), this.status); + }, + + getStatusKey() { + return "cell://" + this.el.id + "/status"; + }, + + updateStatus(status) { + this.status = status; + this.saveStatus(); + this.updateUI(); + }, + + updateUI() { + if (this.status == EXPANDED) { + this.hide(this.collapsedView); + this.show(this.expandedView); + } else { + this.show(this.collapsedView); + this.hide(this.expandedView); + } + }, + hide(element) { + if (!element.classList.contains("hidden")) { + element.classList.add("hidden"); + } + }, + show(element) { + element.classList.remove("hidden"); + }, +}; diff --git a/core/assets/js/side_panel.js b/core/assets/js/side_panel.js index e3f3a3a70..9cb5cac3a 100644 --- a/core/assets/js/side_panel.js +++ b/core/assets/js/side_panel.js @@ -1,54 +1,42 @@ -const maxBottomMargin = 62; +const maxBottomMargin = 63; export const SidePanel = { mounted() { + this.mainContent = document.getElementById("main-content"); this.parent = document.getElementById(this.el.dataset.parent); this.panel = this.el.getElementsByClassName("panel")[0]; - this.panel.style = `height: 0px;`; - this.make_absolute(); + this.panel.style = `position: fixed; height: 0px; top: 0px`; this.updateFrame(); - window.addEventListener("tab-activated", (event) => { + new ResizeObserver(() => { this.updateFrame(); - }); + }).observe(this.parent); - window.addEventListener("scroll", (event) => { + this.mainContent.addEventListener("scroll", (event) => { this.updateFrame(); }); window.addEventListener("resize", (event) => { this.updateFrame(); }); - }, - make_absolute() { - this.el.classList.remove("relative"); - this.el.classList.add("absolute"); + + window.addEventListener("tab-activated", (event) => { + this.updateFrame(); + }); }, updated() { - this.make_absolute(); this.updateFrame(); }, updateFrame() { - this.updateHeight(); - this.updatePosition(); - }, - updateHeight() { - const bottomMarginDelta = Math.min( - maxBottomMargin, - document.documentElement.scrollHeight - - window.scrollY - - window.innerHeight - ); - const bottomMargin = maxBottomMargin - bottomMarginDelta; + const bottomDistance = + this.mainContent.scrollHeight - + this.mainContent.scrollTop - + window.innerHeight; + const bottomMargin = + maxBottomMargin - Math.min(maxBottomMargin, bottomDistance); + const topMargin = Math.max(0, this.parent.getBoundingClientRect().top); + const height = window.innerHeight - (topMargin + bottomMargin); - const height = - window.innerHeight - - (this.parent.getBoundingClientRect().top + bottomMargin); - this.panel.style = `height: ${height}px;`; - }, - updatePosition() { - const top = - Math.max(0, this.parent.getBoundingClientRect().top) + window.scrollY; - this.el.style = `top: ${top}px; right: 0px`; + this.panel.style = `position: fixed; height: ${height}px; top: ${topMargin}px`; }, }; diff --git a/core/assets/js/tabbar.js b/core/assets/js/tabbar.js index 18af8d362..eff8758db 100644 --- a/core/assets/js/tabbar.js +++ b/core/assets/js/tabbar.js @@ -63,7 +63,7 @@ export const Tabbar = { var isVisible = tab.id === nextTabId; setVisible(tab, isVisible); if (isVisible) { - tab.dispatchEvent(new Event("tab-activated", { bubbles: true})); + tab.dispatchEvent(new Event("tab-activated", { bubbles: true })); } }); diff --git a/core/assets/static/images/icons/ready.svg b/core/assets/static/images/icons/ready.svg new file mode 100644 index 000000000..a5f0811bb --- /dev/null +++ b/core/assets/static/images/icons/ready.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/core/assets/tailwind.config.js b/core/assets/tailwind.config.js index d7deef92b..8f02ec07a 100644 --- a/core/assets/tailwind.config.js +++ b/core/assets/tailwind.config.js @@ -239,7 +239,7 @@ module.exports = { paddingBottom: "constant(safe-area-inset-bottom)", paddingBottom: "env(safe-area-inset-bottom)", }, - ".scrollbar-hide": { + ".scrollbar-hidden": { /* Firefox */ "scrollbar-width": "thin", diff --git a/core/bundles/link/seeds.exs b/core/bundles/link/seeds.exs index 14e2ce202..fd5e82c21 100644 --- a/core/bundles/link/seeds.exs +++ b/core/bundles/link/seeds.exs @@ -15,11 +15,11 @@ student_count = 1500 researcher_count = 100 researchers_per_campaign = 5 lab_count = 200 -survey_count = 600 +questionnaire_count = 600 time_slots_per_lab = 20 seats_per_time_slot = 20 -survey_url = "https://vuamsterdam.eu.qualtrics.com/jfe/form/SV_4Po8iTxbvcxtuaW" +questionnaire_url = "https://vuamsterdam.eu.qualtrics.com/jfe/form/SV_4Po8iTxbvcxtuaW" images = [ "raw_url=https%3A%2F%2Fimages.unsplash.com%2Fphoto-1600614908054-57142d1eec2b%3Fixid%3DMnwyMTY0MzZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2Mjc4MDc5MzY%26ixlib%3Drb-1.2.1&username=seonghojang95&name=Seongho+Jang&blur_hash=LKLy%5B%2AMd0L%3FG%3B0XSE2xDyC%25f%24zI%3B", @@ -184,9 +184,9 @@ researcher = {:ok, surveys} = Core.Repo.transaction(fn -> - for _ <- 1..survey_count do + for _ <- 1..questionnaire_count do %{ - type: :survey_tool, + type: :questionnaire_tool, promotion: %{ title: Faker.Lorem.sentence() <> " (survey)", subtitle: Faker.Lorem.sentence(), @@ -195,8 +195,8 @@ researcher = marks: ["vu"], plugin: "survey" }, - survey_tool: %{ - survey_url: Faker.Internet.url(), + questionnaire_tool: %{ + questionnaire_url: Faker.Internet.url(), # desktop_enabled: true, # phone_enabled: true, # tablet_enabled: true, @@ -250,7 +250,7 @@ Core.Repo.transaction( ) ) - if tool_type == :survey_tool do + if tool_type == :questionnaire_tool do participant_count = :random.uniform(tool.subject_count) for student <- Enum.take_random(students, participant_count) do diff --git a/core/frameworks/pixel/components/button.ex b/core/frameworks/pixel/components/button.ex index af713a4e5..ea1f4e81d 100644 --- a/core/frameworks/pixel/components/button.ex +++ b/core/frameworks/pixel/components/button.ex @@ -32,6 +32,7 @@ defmodule Frameworks.Pixel.Button do defp action_function(type) do case type do + :fake -> &Action.fake/1 :toggle -> &Action.toggle/1 :click -> &Action.click/1 :redirect -> &Action.redirect/1 diff --git a/core/frameworks/pixel/components/button_action.ex b/core/frameworks/pixel/components/button_action.ex index dda6999fe..2df1e98f3 100644 --- a/core/frameworks/pixel/components/button_action.ex +++ b/core/frameworks/pixel/components/button_action.ex @@ -1,6 +1,16 @@ defmodule Frameworks.Pixel.Button.Action do use CoreWeb, :html + slot(:inner_block, required: true) + + def fake(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + attr(:code, :string, required: true) slot(:inner_block, required: true) diff --git a/core/frameworks/pixel/components/side_panel.ex b/core/frameworks/pixel/components/side_panel.ex index e1bec621c..6e6020362 100644 --- a/core/frameworks/pixel/components/side_panel.ex +++ b/core/frameworks/pixel/components/side_panel.ex @@ -9,8 +9,8 @@ defmodule Frameworks.Pixel.SidePanel do def side_panel(assigns) do ~H""" -
-
+
+
<%= render_slot(@inner_block) %>
diff --git a/core/frameworks/pixel/components/square_container.ex b/core/frameworks/pixel/components/square_container.ex index 5a3450c67..0e844c3b5 100644 --- a/core/frameworks/pixel/components/square_container.ex +++ b/core/frameworks/pixel/components/square_container.ex @@ -8,7 +8,7 @@ defmodule Frameworks.Pixel.Square do def container(assigns) do ~H"""
-
+
<%= render_slot(@inner_block) %>
diff --git a/core/frameworks/signal/_public.ex b/core/frameworks/signal/_public.ex index 21f746fd6..45f694d7f 100644 --- a/core/frameworks/signal/_public.ex +++ b/core/frameworks/signal/_public.ex @@ -5,6 +5,7 @@ defmodule Frameworks.Signal.Public do Core.WebPush.SignalHandlers, Core.APNS.SignalHandlers, Systems.Observatory.Switch, + Systems.Project.Switch, Systems.Assignment.Switch, Systems.Pool.Switch, Systems.Student.Switch, diff --git a/core/lib/core/authorization.ex b/core/lib/core/authorization.ex index 6083e2979..dfb52e859 100644 --- a/core/lib/core/authorization.ex +++ b/core/lib/core/authorization.ex @@ -24,7 +24,7 @@ defmodule Core.Authorization do # Models grant_access(Systems.Campaign.Model, [:visitor, :member]) - grant_access(Systems.Survey.ToolModel, [:owner, :coordinator, :participant]) + grant_access(Systems.Questionnaire.ToolModel, [:owner, :coordinator, :participant]) grant_access(Systems.Lab.ToolModel, [:owner, :coordinator, :participant]) grant_access(Systems.DataDonation.ToolModel, [:owner, :coordinator, :participant]) grant_access(Systems.Benchmark.SpotModel, [:owner]) @@ -71,9 +71,9 @@ defmodule Core.Authorization do grant_access(CoreWeb.User.Profile, [:member]) grant_access(CoreWeb.User.Settings, [:member]) grant_access(CoreWeb.User.SecuritySettings, [:member]) - grant_access(CoreWeb.FakeSurvey, [:member]) + grant_access(CoreWeb.FakeQuestionnaire, [:member]) - grant_actions(CoreWeb.FakeSurveyController, %{ + grant_actions(CoreWeb.FakeQuestionnaireController, %{ index: [:visitor, :member] }) diff --git a/core/lib/core/enums/base.ex b/core/lib/core/enums/base.ex index f070d8c93..052b0db88 100644 --- a/core/lib/core/enums/base.ex +++ b/core/lib/core/enums/base.ex @@ -9,8 +9,22 @@ defmodule Core.Enums.Base do unquote(values) end + def contains(atom) when is_atom(atom) do + contains(Atom.to_string(atom)) + end + + def contains(binary) when is_binary(binary) do + values() + |> Enum.map(&Atom.to_string/1) + |> Enum.member?(binary) + end + def translate(value) do - Gettext.dgettext(CoreWeb.Gettext, "eyra-enums", "#{unquote(name)}.#{value}") + if contains(value) do + Gettext.dgettext(CoreWeb.Gettext, "eyra-enums", "#{unquote(name)}.#{value}") + else + value + end end def labels() do diff --git a/core/lib/core/factories.ex b/core/lib/core/factories.ex index 5f247163f..6259204de 100644 --- a/core/lib/core/factories.ex +++ b/core/lib/core/factories.ex @@ -22,7 +22,7 @@ defmodule Core.Factories do Assignment, Crew, Support, - Survey, + Questionnaire, Lab, DataDonation, Benchmark, @@ -142,14 +142,26 @@ defmodule Core.Factories do } end - def build(:survey_tool) do - build(:survey_tool, %{}) + def build(:questionnaire_tool) do + build(:questionnaire_tool, %{}) end def build(:lab_tool) do build(:lab_tool, %{}) end + def build(:data_donation_tool) do + build(:data_donation_tool, %{}) + end + + def build(:data_donation_task) do + build(:data_donation_task, %{}) + end + + def build(:data_donation_document_task) do + build(:data_donation_document_task, %{}) + end + def build(:time_slot) do build(:time_slot, %{}) end @@ -400,14 +412,14 @@ defmodule Core.Factories do def build(:tool_ref, %{} = attributes) do {item, attributes} = Map.pop(attributes, :item, build(:project_item)) - {survey_tool, attributes} = Map.pop(attributes, :survey_tool, nil) + {questionnaire_tool, attributes} = Map.pop(attributes, :questionnaire_tool, nil) {lab_tool, attributes} = Map.pop(attributes, :lab_tool, nil) {data_donation_tool, attributes} = Map.pop(attributes, :data_donation_tool, nil) {benchmark_tool, attributes} = Map.pop(attributes, :benchmark_tool, nil) %Project.ToolRefModel{ item: item, - survey_tool: survey_tool, + questionnaire_tool: questionnaire_tool, lab_tool: lab_tool, data_donation_tool: data_donation_tool, benchmark_tool: benchmark_tool @@ -450,13 +462,16 @@ defmodule Core.Factories do {nil, attributes} end - {survey_tool, attributes} = + {questionnaire_tool, attributes} = if lab_tool == nil do tool_auth_node = build(:auth_node, %{parent: auth_node}) - case Map.pop(attributes, :survey_tool, nil) do - {nil, attributes} -> {build(:survey_tool, %{auth_node: tool_auth_node}), attributes} - {survey_tool, attributes} -> {survey_tool, attributes} + case Map.pop(attributes, :questionnaire_tool, nil) do + {nil, attributes} -> + {build(:questionnaire_tool, %{auth_node: tool_auth_node}), attributes} + + {questionnaire_tool, attributes} -> + {questionnaire_tool, attributes} end else {nil, attributes} @@ -464,7 +479,7 @@ defmodule Core.Factories do %Assignment.ExperimentModel{ auth_node: auth_node, - survey_tool: survey_tool, + questionnaire_tool: questionnaire_tool, lab_tool: lab_tool } |> struct!(attributes) @@ -579,10 +594,28 @@ defmodule Core.Factories do |> struct!(attributes) end - def build(:survey_tool, %{} = attributes) do + def build(:data_donation_task, %{} = attributes) do + {tool, attributes} = Map.pop(attributes, :tool, build(:data_donation_tool)) + + {document_task, attributes} = + Map.pop(attributes, :document_task, build(:data_donation_document_task)) + + %DataDonation.TaskModel{ + tool: tool, + download_task: document_task + } + |> struct!(attributes) + end + + def build(:data_donation_document_task, %{} = attributes) do + %DataDonation.DocumentTaskModel{} + |> struct!(attributes) + end + + def build(:questionnaire_tool, %{} = attributes) do {auth_node, attributes} = Map.pop(attributes, :auth_node, build(:auth_node)) - %Survey.ToolModel{ + %Questionnaire.ToolModel{ auth_node: auth_node } |> struct!(attributes) diff --git a/core/lib/core_web/controllers/fake_survey_controller.ex b/core/lib/core_web/controllers/fake_questionnaire_controller.ex similarity index 79% rename from core/lib/core_web/controllers/fake_survey_controller.ex rename to core/lib/core_web/controllers/fake_questionnaire_controller.ex index 18648ce8b..ba0ae797b 100644 --- a/core/lib/core_web/controllers/fake_survey_controller.ex +++ b/core/lib/core_web/controllers/fake_questionnaire_controller.ex @@ -1,4 +1,4 @@ -defmodule CoreWeb.FakeSurveyController do +defmodule CoreWeb.FakeQuestionnaireController do use CoreWeb, :controller def index(conn, _params) do diff --git a/core/lib/core_web/controllers/fake_questionnaire_html.ex b/core/lib/core_web/controllers/fake_questionnaire_html.ex new file mode 100644 index 000000000..16b75320c --- /dev/null +++ b/core/lib/core_web/controllers/fake_questionnaire_html.ex @@ -0,0 +1,5 @@ +defmodule CoreWeb.FakeQuestionnaireHTML do + use CoreWeb, :html + + embed_templates("fake_questionnaire_html/*") +end diff --git a/core/lib/core_web/controllers/fake_survey_html/index.html.eex b/core/lib/core_web/controllers/fake_questionnaire_html/index.html.eex similarity index 100% rename from core/lib/core_web/controllers/fake_survey_html/index.html.eex rename to core/lib/core_web/controllers/fake_questionnaire_html/index.html.eex diff --git a/core/lib/core_web/controllers/fake_survey_html.ex b/core/lib/core_web/controllers/fake_survey_html.ex deleted file mode 100644 index a0739651c..000000000 --- a/core/lib/core_web/controllers/fake_survey_html.ex +++ /dev/null @@ -1,5 +0,0 @@ -defmodule CoreWeb.FakeSurveyHTML do - use CoreWeb, :html - - embed_templates("fake_survey_html/*") -end diff --git a/core/lib/core_web/controllers/layouts/error.html.heex b/core/lib/core_web/controllers/layouts/error.html.heex index b6dc33471..d9ca0ff32 100644 --- a/core/lib/core_web/controllers/layouts/error.html.heex +++ b/core/lib/core_web/controllers/layouts/error.html.heex @@ -15,7 +15,7 @@ /> - +
diff --git a/core/lib/core_web/controllers/layouts/root.html.leex b/core/lib/core_web/controllers/layouts/root.html.leex index 414b112a6..fa1a4af2f 100644 --- a/core/lib/core_web/controllers/layouts/root.html.leex +++ b/core/lib/core_web/controllers/layouts/root.html.leex @@ -21,7 +21,7 @@ /> - +
- - -
-
-
-
- -
-
-
-
-
- <%= if @title do %> -
- -
- <% end %> - <%= if @top_bar do %> -
- <%= render_slot(@top_bar) %> +
+
+ + +
+
+
+
+
+ +
+
+
+
+
+ <%= if @title do %> +
+ +
+ <% end %> + <%= if @top_bar do %> +
+ <%= render_slot(@top_bar) %> +
+ <% end %> +
+ <%= render_slot(@inner_block) %> +
- <% end %> -
- <%= render_slot(@inner_block) %> -
+ <%= if @footer do %> +
+ <.content_footer /> +
+ <% end %>
- <%= if @footer do %> -
- <.content_footer /> -
- <% end %>
-
-
- <.platform_footer /> +
+ <.platform_footer /> +
diff --git a/core/lib/core_web/live/fake_survey.ex b/core/lib/core_web/live/fake_questionnaire.ex similarity index 61% rename from core/lib/core_web/live/fake_survey.ex rename to core/lib/core_web/live/fake_questionnaire.ex index 2520728f0..1d7861883 100644 --- a/core/lib/core_web/live/fake_survey.ex +++ b/core/lib/core_web/live/fake_questionnaire.ex @@ -1,4 +1,4 @@ -defmodule CoreWeb.FakeSurvey do +defmodule CoreWeb.FakeQuestionnaire do @moduledoc """ The home screen. """ @@ -25,14 +25,14 @@ defmodule CoreWeb.FakeSurvey do def render(assigns) do ~H"""
- + - Fake survey - This fake survey is used to validate the survey tool flow with an external tool. + Fake questionnaire + This fake questionnaire is used to validate the survey tool flow with an external tool. <.spacing value="M" /> - +
""" diff --git a/core/lib/core_web/live/routes.ex b/core/lib/core_web/live/routes.ex index 96c9ecfba..6383887bc 100644 --- a/core/lib/core_web/live/routes.ex +++ b/core/lib/core_web/live/routes.ex @@ -8,7 +8,7 @@ defmodule CoreWeb.Live.Routes do scope "/", CoreWeb do pipe_through(:browser) get("/switch-language/:locale", LanguageSwitchController, :index) - live("/fake_survey/:id", FakeSurvey) + live("/fake_survey/:id", FakeQuestionnaire) end if Mix.env() in [:test] do diff --git a/core/lib/core_web/loaders.ex b/core/lib/core_web/loaders.ex index ef6943aa7..1ad833e4d 100644 --- a/core/lib/core_web/loaders.ex +++ b/core/lib/core_web/loaders.ex @@ -8,6 +8,6 @@ defmodule CoreWeb.Loaders do defloader(:campaign, &Systems.Campaign.Public.get!/1) defloader(:promotion, &Systems.Promotion.Public.get!/1) defloader(:assignment, &Systems.Assignment.Public.get!/1) - defloader(:survey_tool, &Systems.Survey.Public.get_survey_tool!/1) + defloader(:questionnaire_tool, &Systems.Questionnaire.Public.get_questionnaire_tool!/1) defloader(:user_profile, &Core.Accounts.get_profile/1) end diff --git a/core/lib/core_web/ui/navigation.ex b/core/lib/core_web/ui/navigation.ex index 0de5602cb..c9c6671cf 100644 --- a/core/lib/core_web/ui/navigation.ex +++ b/core/lib/core_web/ui/navigation.ex @@ -68,7 +68,7 @@ defmodule CoreWeb.UI.Navigation do
-
+
<%= if @centralize do %>
@@ -136,7 +136,7 @@ defmodule CoreWeb.UI.Navigation do def desktop_menu(assigns) do ~H""" -