diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 6e0bb974e..574e46d51 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -27,7 +27,7 @@ jobs:
- id: setup-elixir
uses: erlef/setup-elixir@v1
with:
- otp-version: "25.0.4"
+ otp-version: "25.3.2.7"
elixir-version: "1.14.0"
- name: Setup the Elixir project
diff --git a/.tool-versions b/.tool-versions
index b39e1c180..3fa6184d2 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,3 +1,3 @@
-erlang 25.0.4
+erlang 25.3.2.7
elixir 1.14.0-otp-25
nodejs 18.19.0
diff --git a/.vscode/settings.json b/.vscode/settings.json
index d0f43fd34..a44241c74 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -23,5 +23,6 @@
"elixir": "html",
"phoenix-heex": "html"
},
- "editor.tabCompletion": "on"
+ "editor.tabCompletion": "on",
+ "elixirLS.mixEnv": "dev"
}
diff --git a/core/assets/js/app.js b/core/assets/js/app.js
index 6e0bbafc4..3b895b357 100644
--- a/core/assets/js/app.js
+++ b/core/assets/js/app.js
@@ -18,7 +18,7 @@ import { decode } from "blurhash";
import { urlBase64ToUint8Array } from "./tools";
import { registerAPNSDeviceToken } from "./apns";
import "./100vh-fix";
-import { ViewportResize } from "./viewport_resize";
+import { Viewport } from "./viewport";
import { SidePanel } from "./side_panel";
import { Toggle } from "./toggle";
import { Cell } from "./cell";
@@ -108,7 +108,7 @@ let Hooks = {
Tabbar,
TabbarItem,
TabbarFooterItem,
- ViewportResize,
+ Viewport,
Wysiwyg,
AutoSubmit,
Sticky,
diff --git a/core/assets/js/viewport_resize.js b/core/assets/js/viewport.js
similarity index 60%
rename from core/assets/js/viewport_resize.js
rename to core/assets/js/viewport.js
index 25470bb95..19ee3b494 100644
--- a/core/assets/js/viewport_resize.js
+++ b/core/assets/js/viewport.js
@@ -2,19 +2,24 @@ import _ from "lodash";
let resizeHandler;
-export const ViewportResize = {
+export const Viewport = {
mounted() {
// Direct push of current window size to properly update view
this.pushResizeEvent();
window.addEventListener("resize", (event) => {
- this.pushResizeEvent();
+ this.pushChangeEvent();
});
},
- pushResizeEvent() {
- console.log("pushResizeEvent");
- this.pushEvent("viewport_resize", {
+ updated() {
+ console.log("[Viewport] updated");
+ this.pushChangeEvent();
+ },
+
+ pushChangeEvent() {
+ console.log("[Viewport] push update event");
+ this.pushEvent("viewport_changed", {
width: window.innerWidth,
height: window.innerHeight,
});
diff --git a/core/bundles/next/lib/account/signin_page.ex b/core/bundles/next/lib/account/signin_page.ex
index c7eea8369..dd1636c5b 100644
--- a/core/bundles/next/lib/account/signin_page.ex
+++ b/core/bundles/next/lib/account/signin_page.ex
@@ -1,11 +1,19 @@
defmodule Next.Account.SigninPage do
use CoreWeb, :live_view
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.User, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Uri, __MODULE__})
+ on_mount({Frameworks.GreenLight.LiveHook, __MODULE__})
+ on_mount({Frameworks.Fabric.LiveHook, __MODULE__})
+
import CoreWeb.Layouts.Stripped.Html
import CoreWeb.Layouts.Stripped.Composer
+ import CoreWeb.Menus
+
import Frameworks.Pixel.Line
alias Frameworks.Pixel.Tabbar
-
alias Next.Account.SigninPageBuilder
@impl true
@@ -29,11 +37,16 @@ defmodule Next.Account.SigninPage do
}
end
- defp update_view_model(socket) do
+ def update_view_model(socket) do
vm = SigninPageBuilder.view_model(nil, socket.assigns)
assign(socket, vm: vm)
end
+ def update_menus(%{assigns: %{current_user: user, uri: uri}} = socket) do
+ menus = build_menus(stripped_menus_config(), user, uri)
+ assign(socket, menus: menus)
+ end
+
@impl true
def render(assigns) do
~H"""
diff --git a/core/bundles/self/lib/account/signin_page.ex b/core/bundles/self/lib/account/signin_page.ex
index a0df80394..f91024e9e 100644
--- a/core/bundles/self/lib/account/signin_page.ex
+++ b/core/bundles/self/lib/account/signin_page.ex
@@ -1,11 +1,20 @@
defmodule Self.Account.SigninPage do
use CoreWeb, :live_view
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.User, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Uri, __MODULE__})
+ on_mount({Frameworks.GreenLight.LiveHook, __MODULE__})
+ on_mount({Frameworks.Fabric.LiveHook, __MODULE__})
+
import CoreWeb.Layouts.Stripped.Html
import CoreWeb.Layouts.Stripped.Composer
+ import CoreWeb.Menus
alias Systems.Account.User
alias Systems.Account.UserForm
+ @impl true
def mount(params, _session, socket) do
require_feature(:password_sign_in)
@@ -18,6 +27,11 @@ defmodule Self.Account.SigninPage do
}
end
+ def update_menus(%{assigns: %{current_user: user, uri: uri}} = socket) do
+ menus = build_menus(stripped_menus_config(), user, uri)
+ assign(socket, menus: menus)
+ end
+
defp update_form(%{assigns: %{email: nil}} = socket) do
assign(socket, :form, to_form(%{}))
end
diff --git a/core/config/config.exs b/core/config/config.exs
index 239bf135e..1f1d5aa59 100644
--- a/core/config/config.exs
+++ b/core/config/config.exs
@@ -42,11 +42,10 @@ config :plug, :statuses, %{
404 => "Page not found"
}
-config :core, :branch, factory: Systems.Project.Public
-
config :core, CoreWeb.FileUploader, max_file_size: 100_000_000
config :core,
+ greenlight_auth_module: Core.Authorization,
image_catalog: Core.ImageCatalog.Unsplash,
banking_backend: Systems.Banking.Dummy
diff --git a/core/frameworks/concept/branch.ex b/core/frameworks/concept/branch.ex
index f14ebf678..c7161a7d6 100644
--- a/core/frameworks/concept/branch.ex
+++ b/core/frameworks/concept/branch.ex
@@ -1,31 +1,11 @@
-defmodule Frameworks.Concept.Branch do
- defmodule Factory do
- @type scope :: :self | :parent
- @type leaf :: struct()
- @callback name(leaf, scope) :: {:ok, binary()} | {:error, atom()}
- @callback hierarchy(leaf) :: {:ok, list()} | {:error, atom()}
- end
+defprotocol Frameworks.Concept.Branch do
+ # FIXME: add possibility to resolve dependencies to leafs (siblings)
- require Logger
+ @type scope :: :self | :parent
- def name(leaf, scope, default) when is_struct(leaf) and is_binary(default) do
- case factory().name(leaf, scope) do
- {:ok, name} ->
- name
+ @spec name(t, scope) :: binary
+ def name(_t, _scope)
- {:error, error} ->
- Logger.warn(
- "[Branch] Error while fetching name: #{inspect(error)} for leaf #{inspect(leaf)}"
- )
-
- default
- end
- end
-
- def hierarchy(leaf) when is_struct(leaf) do
- factory().hierarchy(leaf)
- end
-
- defp factory, do: Access.get(settings(), :factory, nil)
- defp settings, do: Application.fetch_env!(:core, :branch)
+ @spec hierarchy(t) :: list
+ def hierarchy(_t)
end
diff --git a/core/frameworks/concept/live_hook.ex b/core/frameworks/concept/live_hook.ex
new file mode 100644
index 000000000..42910d522
--- /dev/null
+++ b/core/frameworks/concept/live_hook.ex
@@ -0,0 +1,28 @@
+defmodule Frameworks.Concept.LiveHook do
+ @type live_view_module :: atom()
+ @type params :: map()
+ @type session :: map()
+ @type socket :: Phoenix.LiveView.Socket.t()
+
+ @callback on_mount(live_view_module(), params(), session(), socket()) ::
+ {:cont | :halt, socket()}
+
+ defmacro __using__(_opts) do
+ quote do
+ @behaviour Frameworks.Concept.LiveHook
+
+ import Phoenix.LiveView,
+ only: [attach_hook: 4, connected?: 1, get_connect_params: 1, redirect: 2]
+
+ import Phoenix.Component, only: [assign: 2]
+
+ def optional_apply(socket, live_view_module, function) do
+ Frameworks.Utility.Module.optional_apply(live_view_module, function, [socket], socket)
+ end
+
+ def optional_apply(socket, live_view_module, function, args) when is_list(args) do
+ Frameworks.Utility.Module.optional_apply(live_view_module, function, args, socket)
+ end
+ end
+ end
+end
diff --git a/core/frameworks/fabric/live_hook.ex b/core/frameworks/fabric/live_hook.ex
new file mode 100644
index 000000000..fd0af06ea
--- /dev/null
+++ b/core/frameworks/fabric/live_hook.ex
@@ -0,0 +1,9 @@
+defmodule Frameworks.Fabric.LiveHook do
+ import Phoenix.Component, only: [assign: 2]
+
+ def on_mount(_live_view_module, _params, _session, socket) do
+ self = %Fabric.LiveView.RefModel{pid: self()}
+ fabric = %Fabric.Model{parent: nil, self: self, children: nil}
+ {:cont, socket |> assign(fabric: fabric)}
+ end
+end
diff --git a/core/frameworks/fabric/live_view_mount_plug.ex b/core/frameworks/fabric/live_view_mount_plug.ex
deleted file mode 100644
index aabe2fad5..000000000
--- a/core/frameworks/fabric/live_view_mount_plug.ex
+++ /dev/null
@@ -1,22 +0,0 @@
-defmodule Frameworks.Fabric.LiveViewMountPlug do
- defmacro __using__(_) do
- quote do
- @before_compile Frameworks.Fabric.LiveViewMountPlug
- end
- end
-
- defmacro __before_compile__(_env) do
- quote do
- defoverridable mount: 3
-
- @doc """
- Automatically assigns Fabric to the socket on mount
- """
- def mount(params, session, socket) do
- self = %Fabric.LiveView.RefModel{pid: self()}
- fabric = %Fabric.Model{parent: nil, self: self, children: nil}
- super(params, session, socket |> Phoenix.Component.assign(:fabric, fabric))
- end
- end
- end
-end
diff --git a/core/frameworks/green_light/_live_feature.ex b/core/frameworks/green_light/_live_feature.ex
new file mode 100644
index 000000000..725e599cc
--- /dev/null
+++ b/core/frameworks/green_light/_live_feature.ex
@@ -0,0 +1,23 @@
+defmodule Frameworks.GreenLight.LiveFeature do
+ @callback get_authorization_context(
+ Phoenix.LiveView.unsigned_params() | :not_mounted_at_router,
+ session :: map,
+ socket :: Phoenix.Socket.t()
+ ) :: integer | struct
+
+ @optional_callbacks get_authorization_context: 3
+
+ defmacro __using__(_opts) do
+ quote do
+ @behaviour Frameworks.GreenLight.LiveFeature
+
+ def mount(params, session, %{assigns: %{authorization_failed: true}} = socket) do
+ {:ok, socket}
+ end
+
+ def render(%{authorization_failed: true}) do
+ raise Frameworks.GreenLight.AccessDeniedError, "Authorization failed for #{__MODULE__}"
+ end
+ end
+ end
+end
diff --git a/core/frameworks/green_light/_live_hook.ex b/core/frameworks/green_light/_live_hook.ex
new file mode 100644
index 000000000..0e289d05a
--- /dev/null
+++ b/core/frameworks/green_light/_live_hook.ex
@@ -0,0 +1,40 @@
+defmodule Frameworks.GreenLight.LiveHook do
+ @moduledoc """
+ Live Hook that enables automatic authorization checks.
+ """
+ use Frameworks.Concept.LiveHook
+ use CoreWeb, :verified_routes
+ require Logger
+
+ @impl true
+ def on_mount(live_view_module, params, session, socket) do
+ if access_allowed?(live_view_module, params, session, socket) do
+ {:cont, socket}
+ else
+ {:halt, redirect(socket, to: ~p"/access_denied")}
+ end
+ end
+
+ defp access_allowed?(live_view_module, params, session, socket) do
+ user = Map.get(socket.assigns, :current_user)
+
+ if function_exported?(live_view_module, :get_authorization_context, 3) do
+ can_access? =
+ auth_module().can_access?(
+ user,
+ live_view_module.get_authorization_context(params, session, socket)
+ |> Core.Authorization.print_roles(),
+ live_view_module
+ )
+
+ Logger.notice("User #{user.id} can_access? #{live_view_module}: #{can_access?}")
+ can_access?
+ else
+ auth_module().can_access?(user, live_view_module)
+ end
+ end
+
+ defp auth_module() do
+ Application.get_env(:core, :greenlight_auth_module)
+ end
+end
diff --git a/core/frameworks/green_light/live.ex b/core/frameworks/green_light/live.ex
deleted file mode 100644
index 95d8fda5f..000000000
--- a/core/frameworks/green_light/live.ex
+++ /dev/null
@@ -1,61 +0,0 @@
-defmodule Frameworks.GreenLight.Live do
- require Logger
-
- @moduledoc """
- The Live module enables automatic authorization checks for LiveViews.
- """
- @callback get_authorization_context(
- Phoenix.LiveView.unsigned_params() | :not_mounted_at_router,
- session :: map,
- socket :: Phoenix.Socket.t()
- ) :: integer | struct
- @optional_callbacks get_authorization_context: 3
-
- defmacro __using__(auth_module) do
- quote do
- @greenlight_authmodule unquote(auth_module)
- @behaviour Frameworks.GreenLight.Live
- @before_compile Frameworks.GreenLight.Live
- import Phoenix.LiveView.Helpers
-
- def render(%{authorization_failed: true}) do
- raise Frameworks.GreenLight.AccessDeniedError, "Authorization failed for #{__MODULE__}"
- end
- end
- end
-
- defmacro __before_compile__(_env) do
- quote do
- if Module.defines?(__MODULE__, {:get_authorization_context, 3}) do
- defp access_allowed?(params, session, socket) do
- user = Map.get(socket.assigns, :current_user)
-
- can_access? =
- @greenlight_authmodule.can_access?(
- socket,
- get_authorization_context(params, session, socket)
- |> Core.Authorization.print_roles(),
- __MODULE__
- )
-
- Logger.notice("User #{user.id} can_access? #{__MODULE__}: #{can_access?}")
- can_access?
- end
- else
- defp access_allowed?(_params, session, socket) do
- @greenlight_authmodule.can_access?(socket, __MODULE__)
- end
- end
-
- defoverridable mount: 3
-
- def mount(params, session, socket) do
- if access_allowed?(params, session, socket) do
- super(params, session, socket)
- else
- {:ok, assign(socket, authorization_failed: true)}
- end
- end
- end
- end
-end
diff --git a/core/frameworks/pixel/components/tabbar.ex b/core/frameworks/pixel/components/tabbar.ex
index f50ca4077..06aaf4415 100644
--- a/core/frameworks/pixel/components/tabbar.ex
+++ b/core/frameworks/pixel/components/tabbar.ex
@@ -28,17 +28,19 @@ defmodule Frameworks.Pixel.Tabbar do
def container(assigns) do
~H"""
-
- <%= if @size == :full do %>
- <.container_full type={@type} tabs={@tabs} />
- <% end %>
- <%= if @size == :wide do %>
- <.container_wide type={@type} tabs={@tabs} />
- <% end %>
- <%= if @size == :narrow do %>
- <.container_narrow tabs={@tabs} />
- <% end %>
-
+ <%= if Enum.count(@tabs) > 0 do %>
+
+ <%= if @size == :full do %>
+ <.container_full type={@type} tabs={@tabs} />
+ <% end %>
+ <%= if @size == :wide do %>
+ <.container_wide type={@type} tabs={@tabs} />
+ <% end %>
+ <%= if @size == :narrow do %>
+ <.container_narrow tabs={@tabs} />
+ <% end %>
+
+ <% end %>
"""
end
diff --git a/core/frameworks/utility/list.ex b/core/frameworks/utility/list.ex
index 247c5731c..2b3b3ad98 100644
--- a/core/frameworks/utility/list.ex
+++ b/core/frameworks/utility/list.ex
@@ -1,9 +1,10 @@
defmodule Frameworks.Utility.List do
- def append_if(list, element, true), do: append(list, element)
+ def append_if(list, term, true), do: append(list, term)
def append_if(list, _element, _), do: list
def append_if(list, nil), do: list
- def append_if(list, element), do: append(list, element)
+ def append_if(list, term), do: append(list, term)
+ def append(list, closure) when is_function(closure, 0), do: list ++ [closure.()]
def append(list, element), do: list ++ [element]
def insert_at_every(list, every, fun) do
diff --git a/core/frameworks/utility/module.ex b/core/frameworks/utility/module.ex
index 636441cf6..f102f942a 100644
--- a/core/frameworks/utility/module.ex
+++ b/core/frameworks/utility/module.ex
@@ -1,4 +1,15 @@
defmodule Frameworks.Utility.Module do
+ def optional_apply(module, function, params, default \\ nil)
+ when is_atom(module) and is_atom(function) and is_list(params) do
+ if function_exported?(module, function, Enum.count(params)) do
+ "#{module}.#{function}/#{Enum.count(params)} called"
+ apply(module, function, params)
+ else
+ "#{module}.#{function}/#{Enum.count(params)} skipped"
+ default
+ end
+ end
+
def get(nil, _name), do: nil
def get(system, name) when is_atom(system),
diff --git a/core/lib/core/authorization.ex b/core/lib/core/authorization.ex
index eea8da9ee..cb30098f9 100644
--- a/core/lib/core/authorization.ex
+++ b/core/lib/core/authorization.ex
@@ -62,7 +62,7 @@ defmodule Core.Authorization do
grant_access(Systems.Pool.LandingPage, [:visitor, :member, :owner])
grant_access(Systems.Pool.ParticipantPage, [:creator])
grant_access(Systems.Pool.SubmissionPage, [:creator])
- grant_access(Systems.Project.NodePage, [:creator, :owner])
+ grant_access(Systems.Project.NodePage, [:owner])
grant_access(Systems.Project.OverviewPage, [:admin, :creator])
grant_access(Systems.Promotion.LandingPage, [:visitor, :member])
grant_access(Systems.Storage.EndpointContentPage, [:owner])
diff --git a/core/lib/core_web.ex b/core/lib/core_web.ex
index 944d72459..bbd2a389a 100644
--- a/core/lib/core_web.ex
+++ b/core/lib/core_web.ex
@@ -42,6 +42,8 @@ defmodule CoreWeb do
import Phoenix.LiveView.Controller
+ plug(Systems.Project.BranchPlug)
+
# Routes generation with the ~p sigil
unquote(verified_routes())
end
@@ -95,11 +97,7 @@ defmodule CoreWeb do
end
end
- def live_view() do
- live_view(:base)
- end
-
- def live_view(mount_plug_type) do
+ def live_view do
quote do
use Fabric.LiveView, CoreWeb.Layouts
@@ -112,33 +110,20 @@ defmodule CoreWeb do
unquote(component_helpers())
unquote(verified_routes())
-
- unquote do
- if mount_plug_type == :extended do
- live_mount_plugs_extended()
- else
- live_mount_plugs()
- end
- end
- end
- end
-
- def live_mount_plugs do
- quote do
- use CoreWeb.LiveLocale
- use CoreWeb.LiveTimezone
- use CoreWeb.LiveRemoteIp
- use Frameworks.Fabric.LiveViewMountPlug
- use Frameworks.GreenLight.Live, Core.Authorization
- use CoreWeb.LiveUser
+ unquote(live_features())
end
end
- def live_mount_plugs_extended do
+ def live_features do
quote do
- use Systems.Observatory.Public
- use CoreWeb.LiveUri
- unquote(live_mount_plugs())
+ use Frameworks.GreenLight.LiveFeature
+ use Systems.Observatory.LiveFeature
+ use CoreWeb.Live.Feature.Viewport
+ use CoreWeb.Live.Feature.Uri
+ use CoreWeb.Live.Feature.Model
+ use CoreWeb.Live.Feature.Menus
+ use CoreWeb.Live.Feature.Tabbar
+ use CoreWeb.Live.Feature.Actions
end
end
diff --git a/core/lib/core_web/controllers/error_controller.ex b/core/lib/core_web/controllers/error_controller.ex
new file mode 100644
index 000000000..cefc74957
--- /dev/null
+++ b/core/lib/core_web/controllers/error_controller.ex
@@ -0,0 +1,10 @@
+defmodule CoreWeb.ErrorController do
+ use CoreWeb, :controller
+
+ def access_denied(conn, _params) do
+ conn
+ |> put_status(:forbidden)
+ |> put_view(CoreWeb.ErrorHTML)
+ |> render(:"403")
+ end
+end
diff --git a/core/lib/core_web/controllers/error_html.ex b/core/lib/core_web/controllers/error_html.ex
index e1eceadc4..ae60f88c9 100644
--- a/core/lib/core_web/controllers/error_html.ex
+++ b/core/lib/core_web/controllers/error_html.ex
@@ -2,6 +2,7 @@ defmodule CoreWeb.ErrorHTML do
use CoreWeb, :html
import CoreWeb.Layouts.Stripped.Html
import CoreWeb.Layouts.Stripped.Composer
+ import CoreWeb.Menus
defp body("403.html", status), do: dgettext("eyra-error", "403.body", status: status)
defp body("404.html", status), do: dgettext("eyra-error", "404.body", status: status)
@@ -19,7 +20,7 @@ defmodule CoreWeb.ErrorHTML do
def render(template, assigns) do
status = status(template)
- menus = build_menus(Map.put(assigns, :active_menu_item, nil))
+ menus = build_menus(stripped_menus_config(), nil, nil)
assigns =
Map.merge(assigns, %{
diff --git a/core/lib/core_web/controllers/layouts/error.html.heex b/core/lib/core_web/controllers/layouts/error.html.heex
index ed6c40b45..8fb3531f0 100644
--- a/core/lib/core_web/controllers/layouts/error.html.heex
+++ b/core/lib/core_web/controllers/layouts/error.html.heex
@@ -7,7 +7,7 @@
-
+
-
+
diff --git a/core/lib/core_web/controllers/layouts/stripped/composer.ex b/core/lib/core_web/controllers/layouts/stripped/composer.ex
index 717ba2cda..59671b21b 100644
--- a/core/lib/core_web/controllers/layouts/stripped/composer.ex
+++ b/core/lib/core_web/controllers/layouts/stripped/composer.ex
@@ -1,83 +1,35 @@
defmodule CoreWeb.Layouts.Stripped.Composer do
- import Phoenix.Component
-
- @menus [
- :mobile_navbar,
- :desktop_navbar
- ]
-
- def builder, do: Application.fetch_env!(:core, :stripped_menu_builder)
-
- def build_menu(%{active_menu_item: active_menu_item} = assigns, type) do
- builder().build_menu(assigns, type, active_menu_item)
- end
-
- def build_menu(%{vm: %{active_menu_item: active_menu_item}} = assigns, type) do
- builder().build_menu(assigns, type, active_menu_item)
- end
-
- def build_menus(%{assigns: assigns} = socket) do
- menus = build_menus(assigns)
- socket |> assign(menus: menus)
- end
-
- def build_menus(assigns) do
- Enum.reduce(@menus, %{}, fn menu_id, acc ->
- Map.put(acc, menu_id, build_menu(assigns, menu_id))
- end)
- end
-
- def update_menus(socket) do
- socket
- |> build_menus()
- end
+ def stripped_menus_config(),
+ do: {
+ :stripped_menu_builder,
+ [
+ :mobile_navbar,
+ :desktop_navbar
+ ],
+ nil
+ }
defmacro __using__(_) do
quote do
- @before_compile CoreWeb.Layouts.Stripped.Composer
+ def get_menus_config(), do: CoreWeb.Layouts.Stripped.Composer.stripped_menus_config()
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.User, __MODULE__})
+ on_mount({Frameworks.GreenLight.LiveHook, __MODULE__})
+ on_mount({Frameworks.Fabric.LiveHook, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.RemoteIp, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Timezone, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Locale, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Uri, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Model, __MODULE__})
+ on_mount({Systems.Project.LiveHook, __MODULE__})
+ on_mount({Systems.Observatory.LiveHook, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Menus, __MODULE__})
+
use CoreWeb.UI.PlainDialog
- import CoreWeb.Layouts.Stripped.Composer
import CoreWeb.Layouts.Stripped.Html
import Systems.Content.Html, only: [live_stripped: 1]
end
end
-
- defmacro __before_compile__(_env) do
- quote do
- defoverridable mount: 3
-
- @impl true
- def mount(params, session, %{assigns: %{authorization_failed: true}} = socket) do
- {:ok, socket}
- end
-
- @impl true
- def mount(params, session, socket) do
- {:ok, socket} = super(params, session, socket)
-
- {
- :ok,
- socket
- |> assign(active_menu_item: nil)
- |> update_menus()
- }
- end
-
- defoverridable handle_uri: 1
-
- def handle_uri(socket) do
- super(socket)
- |> update_menus()
- end
-
- defoverridable handle_view_model_updated: 1
-
- @impl true
- def handle_view_model_updated(socket) do
- super(socket)
- |> update_menus()
- end
- end
- end
end
diff --git a/core/lib/core_web/controllers/layouts/website/composer.ex b/core/lib/core_web/controllers/layouts/website/composer.ex
index e25941356..56a783d8f 100644
--- a/core/lib/core_web/controllers/layouts/website/composer.ex
+++ b/core/lib/core_web/controllers/layouts/website/composer.ex
@@ -1,18 +1,31 @@
defmodule CoreWeb.Layouts.Website.Composer do
defmacro __using__(_) do
quote do
- use CoreWeb.LiveMenus, {
- :website_menu_builder,
- [
- :mobile_menu,
- :mobile_navbar,
- :desktop_navbar
- ]
- }
+ def get_menus_config(),
+ do: {
+ :website_menu_builder,
+ [
+ :mobile_menu,
+ :mobile_navbar,
+ :desktop_navbar
+ ]
+ }
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.User, __MODULE__})
+ on_mount({Frameworks.GreenLight.LiveHook, __MODULE__})
+ on_mount({Frameworks.Fabric.LiveHook, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.RemoteIp, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Timezone, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Locale, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Uri, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Model, __MODULE__})
+ on_mount({Systems.Project.LiveHook, __MODULE__})
+ on_mount({Systems.Observatory.LiveHook, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Menus, __MODULE__})
use CoreWeb.UI.PlainDialog
- import CoreWeb.Layouts.Website.Composer
import CoreWeb.Layouts.Website.Html
end
end
diff --git a/core/lib/core_web/controllers/layouts/workspace/composer.ex b/core/lib/core_web/controllers/layouts/workspace/composer.ex
index 4eacd988d..8316ea346 100644
--- a/core/lib/core_web/controllers/layouts/workspace/composer.ex
+++ b/core/lib/core_web/controllers/layouts/workspace/composer.ex
@@ -1,19 +1,35 @@
defmodule CoreWeb.Layouts.Workspace.Composer do
defmacro __using__(_opts) do
quote do
- use CoreWeb.LiveMenus, {
- :workspace_menu_builder,
- [
- :mobile_menu,
- :mobile_navbar,
- :desktop_menu,
- :tablet_menu
- ]
- }
+ def get_menus_config(),
+ do: {
+ :workspace_menu_builder,
+ [
+ :mobile_menu,
+ :mobile_navbar,
+ :desktop_menu,
+ :tablet_menu
+ ]
+ }
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.User, __MODULE__})
+ on_mount({Frameworks.GreenLight.LiveHook, __MODULE__})
+ on_mount({Frameworks.Fabric.LiveHook, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Viewport, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.RemoteIp, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Timezone, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Locale, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Uri, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Model, __MODULE__})
+ on_mount({Systems.Project.LiveHook, __MODULE__})
+ on_mount({Systems.Observatory.LiveHook, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Menus, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Tabbar, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Actions, __MODULE__})
use CoreWeb.UI.PlainDialog
- import CoreWeb.Layouts.Workspace.Composer
import CoreWeb.Layouts.Workspace.Html
@impl true
diff --git a/core/lib/core_web/endpoint.ex b/core/lib/core_web/endpoint.ex
index 0b8bcd2a7..a8adcbeec 100644
--- a/core/lib/core_web/endpoint.ex
+++ b/core/lib/core_web/endpoint.ex
@@ -1,5 +1,6 @@
defmodule CoreWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :core
+
require Systems.Content.Plug
require Systems.Feldspar.Plug
diff --git a/core/lib/core_web/live/fake_qualtrics.ex b/core/lib/core_web/live/fake_qualtrics.ex
index 4d6218a6b..42dd8df46 100644
--- a/core/lib/core_web/live/fake_qualtrics.ex
+++ b/core/lib/core_web/live/fake_qualtrics.ex
@@ -6,6 +6,7 @@ defmodule CoreWeb.FakeQualtrics do
alias Frameworks.Pixel.Text
alias Frameworks.Pixel.Button
+ @impl true
def mount(%{"re" => redirect_url}, _session, socket) do
socket =
socket
diff --git a/core/lib/core_web/live/feature/actions.ex b/core/lib/core_web/live/feature/actions.ex
new file mode 100644
index 000000000..5a30745a1
--- /dev/null
+++ b/core/lib/core_web/live/feature/actions.ex
@@ -0,0 +1,36 @@
+defmodule CoreWeb.Live.Feature.Actions do
+ alias CoreWeb.UI.Responsive.Breakpoint
+
+ def create_actions(%{assigns: %{breakpoint: {:unknown, _}}} = _socket), do: []
+
+ def create_actions(%{assigns: %{vm: %{actions: actions}}} = socket) do
+ actions
+ |> Keyword.keys()
+ |> Enum.map(&create_action(Keyword.get(actions, &1), socket))
+ |> Enum.filter(&(not is_nil(&1)))
+ end
+
+ def create_actions(_socket), do: []
+
+ def create_action(action, %{assigns: %{breakpoint: breakpoint}}) do
+ Breakpoint.value(breakpoint, nil,
+ xs: %{0 => action.icon},
+ md: %{40 => action.label, 100 => action.icon},
+ lg: %{50 => action.label}
+ )
+ end
+
+ defmacro __using__(_opts \\ nil) do
+ quote do
+ import CoreWeb.Live.Feature.Actions
+
+ # stubs, handled by Live Hooks
+ def handle_event("action_click", _, socket), do: {:noreply, socket}
+ def handle_info(:action_clicked, socket), do: {:noreply, socket}
+
+ def update_actions(socket) do
+ assign(socket, actions: create_actions(socket))
+ end
+ end
+ end
+end
diff --git a/core/lib/core_web/live/feature/menus.ex b/core/lib/core_web/live/feature/menus.ex
new file mode 100644
index 000000000..14fef9b16
--- /dev/null
+++ b/core/lib/core_web/live/feature/menus.ex
@@ -0,0 +1,42 @@
+defmodule CoreWeb.Live.Feature.Menus do
+ @type menu_builder :: atom()
+ @type menus :: list(atom())
+ @type active_menu_item :: atom()
+
+ @callback get_menus_config() ::
+ {menu_builder(), menus()} | {menu_builder(), menus(), active_menu_item()} | nil
+
+ defmacro __using__(_opts) do
+ quote do
+ @behaviour CoreWeb.Live.Feature.Menus
+ import Phoenix.Component, only: [assign: 2]
+
+ @impl true
+ def get_menus_config(), do: nil
+
+ defoverridable get_menus_config: 0
+
+ import Phoenix.Component
+
+ def update_menus(%{assigns: %{authorization_failed: true}} = socket), do: socket
+
+ def update_menus(%{assigns: %{menus_config: nil}} = socket),
+ do: socket |> assign(menus: nil)
+
+ def update_menus(
+ %{assigns: %{menus_config: {menu_builder, menus}, active_menu_item: active_menu_item}} =
+ socket
+ ) do
+ update_menus(socket, menu_builder, menus, active_menu_item)
+ end
+
+ def update_menus(socket, menu_builder, menus, active_menu_item) do
+ user = Map.get(socket.assigns, :current_user)
+ uri = Map.get(socket.assigns, :uri)
+
+ menus = CoreWeb.Menus.build_menus(menu_builder, menus, active_menu_item, user, uri)
+ socket |> assign(menus: menus)
+ end
+ end
+ end
+end
diff --git a/core/lib/core_web/live/feature/model.ex b/core/lib/core_web/live/feature/model.ex
new file mode 100644
index 000000000..58dc9bb64
--- /dev/null
+++ b/core/lib/core_web/live/feature/model.ex
@@ -0,0 +1,14 @@
+defmodule CoreWeb.Live.Feature.Model do
+ @callback get_model(map(), map(), Socket.t()) :: map | struct | nil
+
+ defmacro __using__(_opts \\ nil) do
+ quote do
+ @behaviour CoreWeb.Live.Feature.Model
+
+ @impl true
+ def get_model(_params, _session, _socket), do: nil
+
+ defoverridable get_model: 3
+ end
+ end
+end
diff --git a/core/lib/core_web/live/feature/tabbar.ex b/core/lib/core_web/live/feature/tabbar.ex
new file mode 100644
index 000000000..c9e20cb17
--- /dev/null
+++ b/core/lib/core_web/live/feature/tabbar.ex
@@ -0,0 +1,17 @@
+defmodule CoreWeb.Live.Feature.Tabbar do
+ defmacro __using__(_opts \\ nil) do
+ quote do
+ alias CoreWeb.UI.Responsive.Breakpoint
+
+ def tabbar_size({:unknown, _}), do: :unknown
+ def tabbar_size(bp), do: Breakpoint.value(bp, :narrow, sm: %{30 => :wide})
+
+ defoverridable tabbar_size: 1
+
+ def update_tabbar_size(%{assigns: %{breakpoint: breakpoint}} = socket) do
+ tabbar_size = tabbar_size(breakpoint)
+ socket |> assign(tabbar_size: tabbar_size)
+ end
+ end
+ end
+end
diff --git a/core/lib/core_web/live/feature/uri.ex b/core/lib/core_web/live/feature/uri.ex
new file mode 100644
index 000000000..7e6967d86
--- /dev/null
+++ b/core/lib/core_web/live/feature/uri.ex
@@ -0,0 +1,14 @@
+defmodule CoreWeb.Live.Feature.Uri do
+ @callback handle_uri(Socket.t()) :: Socket.t()
+
+ defmacro __using__(_opts \\ nil) do
+ quote do
+ @behaviour CoreWeb.Live.Feature.Uri
+
+ @impl true
+ def handle_uri(socket), do: socket
+
+ defoverridable handle_uri: 1
+ end
+ end
+end
diff --git a/core/lib/core_web/live/feature/viewport.ex b/core/lib/core_web/live/feature/viewport.ex
new file mode 100644
index 000000000..2e7b4e92c
--- /dev/null
+++ b/core/lib/core_web/live/feature/viewport.ex
@@ -0,0 +1,20 @@
+defmodule CoreWeb.Live.Feature.Viewport do
+ @callback handle_resize(socket :: Socket.t()) :: Socket.t()
+
+ defmacro __using__(_) do
+ quote do
+ @behaviour CoreWeb.Live.Feature.Viewport
+
+ alias CoreWeb.UI.Responsive.Viewport
+ alias CoreWeb.UI.Responsive.Breakpoint
+
+ # stubs, handled by Live Hook
+ def handle_event("viewport_changed", _, socket), do: {:noreply, socket}
+ def handle_info(:viewport_updated, socket), do: {:noreply, socket}
+
+ # optional feature
+ def handle_resize(socket), do: socket
+ defoverridable handle_resize: 1
+ end
+ end
+end
diff --git a/core/lib/core_web/live/hook/actions.ex b/core/lib/core_web/live/hook/actions.ex
new file mode 100644
index 000000000..94dceb025
--- /dev/null
+++ b/core/lib/core_web/live/hook/actions.ex
@@ -0,0 +1,77 @@
+defmodule CoreWeb.Live.Hook.Actions do
+ use Frameworks.Concept.LiveHook
+
+ @impl true
+ def on_mount(live_view_module, _params, _session, socket) do
+ {
+ :cont,
+ socket
+ |> update_actions(live_view_module)
+ |> handle_action_click(live_view_module)
+ |> handle_uri(live_view_module)
+ |> handle_view_model_updated(live_view_module)
+ |> handle_viewport_updated(live_view_module)
+ }
+ end
+
+ defp handle_action_click(socket, live_view_module) do
+ attach_hook(socket, :tabbar_handle_action_click, :handle_event, fn
+ "action_click", %{"item" => action_id}, socket ->
+ {:cont, socket |> handle_action_click(live_view_module, action_id)}
+
+ _, _, socket ->
+ {:cont, socket}
+ end)
+ end
+
+ defp handle_uri(socket, live_view_module) do
+ attach_hook(socket, :actions_handle_uri, :handle_params, fn _params, _uri, socket ->
+ {:cont, socket |> update_actions(live_view_module)}
+ end)
+ end
+
+ defp handle_view_model_updated(socket, live_view_module) do
+ attach_hook(socket, :actions_handle_view_model_updated, :handle_info, fn
+ :view_model_updated, socket ->
+ {:cont, socket |> update_actions(live_view_module)}
+
+ _, socket ->
+ {:cont, socket}
+ end)
+ end
+
+ defp handle_viewport_updated(socket, live_view_module) do
+ attach_hook(socket, :actions_viewport_updated, :handle_info, fn
+ :viewport_updated, socket ->
+ {:cont, socket |> update_actions(live_view_module)}
+
+ _, socket ->
+ {:cont, socket}
+ end)
+ end
+
+ def handle_action_click(
+ %{assigns: %{vm: %{actions: actions}}} = socket,
+ live_view_module,
+ action_id
+ )
+ when is_binary(action_id) do
+ action_id = String.to_existing_atom(action_id)
+ action = Keyword.get(actions, action_id)
+
+ socket
+ |> action.handle_click.()
+ |> update_view_model(live_view_module)
+ |> update_actions(live_view_module)
+ end
+
+ def update_view_model(socket, live_view_module) do
+ socket
+ |> optional_apply(live_view_module, :update_view_model)
+ |> optional_apply(live_view_module, :handle_update_view_model)
+ end
+
+ def update_actions(socket, live_view_module) do
+ optional_apply(socket, live_view_module, :update_actions)
+ end
+end
diff --git a/core/lib/core_web/live/hook/base.ex b/core/lib/core_web/live/hook/base.ex
new file mode 100644
index 000000000..614a05148
--- /dev/null
+++ b/core/lib/core_web/live/hook/base.ex
@@ -0,0 +1,17 @@
+defmodule CoreWeb.Live.Hook.Base do
+ use Frameworks.Concept.LiveHook
+
+ @impl true
+ def on_mount(live_view_module, _params, _session, socket) do
+ {
+ :cont,
+ socket
+ |> assign(
+ live_view_module: live_view_module,
+ popup: nil,
+ dialog: nil,
+ modal: nil
+ )
+ }
+ end
+end
diff --git a/core/lib/core_web/live/hook/locale.ex b/core/lib/core_web/live/hook/locale.ex
new file mode 100644
index 000000000..7bdf75994
--- /dev/null
+++ b/core/lib/core_web/live/hook/locale.ex
@@ -0,0 +1,23 @@
+defmodule CoreWeb.Live.Hook.Locale do
+ @moduledoc "A Live Hook that changes the locale of the current process"
+
+ use Frameworks.Concept.LiveHook
+
+ @impl true
+ def on_mount(_live_view_module, _params, _session, socket) do
+ {:cont, socket |> Phoenix.Component.assign(locale: CoreWeb.Live.Hook.Locale.get_locale())}
+ end
+
+ def put_locale(locale) when is_atom(locale), do: put_locale(Atom.to_string(locale))
+
+ def put_locale(locale) do
+ CoreWeb.Cldr.put_locale(locale)
+ Gettext.put_locale(locale)
+ Gettext.put_locale(CoreWeb.Gettext, locale)
+ Gettext.put_locale(Timex.Gettext, locale)
+ end
+
+ def get_locale() do
+ Gettext.get_locale()
+ end
+end
diff --git a/core/lib/core_web/live/hook/menus.ex b/core/lib/core_web/live/hook/menus.ex
new file mode 100644
index 000000000..005ba2b20
--- /dev/null
+++ b/core/lib/core_web/live/hook/menus.ex
@@ -0,0 +1,70 @@
+defmodule CoreWeb.Live.Hook.Menus do
+ use Frameworks.Concept.LiveHook
+
+ @impl true
+ def on_mount(live_view_module, _params, _session, socket) do
+ {
+ :cont,
+ socket
+ |> update_menu_config(live_view_module)
+ |> ensure_active_menu_item()
+ |> update_menus(live_view_module)
+ |> handle_uri(live_view_module)
+ |> handle_view_model_updated(live_view_module)
+ |> handle_viewport_updated(live_view_module)
+ }
+ end
+
+ defp update_menu_config(socket, live_view_module) do
+ menus_config = live_view_module.get_menus_config()
+ assign(socket, menus_config: menus_config)
+ end
+
+ def update_menus(socket, live_view_module) do
+ live_view_module.update_menus(socket)
+ end
+
+ defp handle_uri(socket, live_view_module) do
+ attach_hook(socket, :menus_handle_uri, :handle_params, fn _params, _uri, socket ->
+ {:cont, socket |> update_menus(live_view_module)}
+ end)
+ end
+
+ defp handle_view_model_updated(socket, live_view_module) do
+ attach_hook(socket, :menus_handle_view_model_updated, :handle_info, fn
+ :view_model_updated, socket ->
+ {:cont, socket |> update_menus(live_view_module)}
+
+ _, socket ->
+ {:cont, socket}
+ end)
+ end
+
+ defp handle_viewport_updated(socket, live_view_module) do
+ attach_hook(socket, :menus_viewport_updated, :handle_info, fn
+ :viewport_updated, socket ->
+ {:cont, socket |> update_menus(live_view_module)}
+
+ _, socket ->
+ {:cont, socket}
+ end)
+ end
+
+ defp ensure_active_menu_item(
+ %{assigns: %{menus_config: {menu_builder, menus, active_menu_item}}} = socket
+ ) do
+ socket |> assign(menus_config: {menu_builder, menus}, active_menu_item: active_menu_item)
+ end
+
+ defp ensure_active_menu_item(%{assigns: %{vm: %{active_menu_item: active_menu_item}}} = socket) do
+ socket |> assign(active_menu_item: active_menu_item)
+ end
+
+ defp ensure_active_menu_item(%{assigns: %{active_menu_item: _}} = socket) do
+ socket
+ end
+
+ defp ensure_active_menu_item(socket) do
+ socket |> assign(active_menu_item: nil)
+ end
+end
diff --git a/core/lib/core_web/live/hook/model.ex b/core/lib/core_web/live/hook/model.ex
new file mode 100644
index 000000000..ffb225e54
--- /dev/null
+++ b/core/lib/core_web/live/hook/model.ex
@@ -0,0 +1,16 @@
+defmodule CoreWeb.Live.Hook.Model do
+ @moduledoc "A Live Hook that injects the LiveView data model"
+ use Frameworks.Concept.LiveHook
+
+ @impl true
+ def on_mount(live_view_module, params, session, socket) do
+ model =
+ Frameworks.Utility.Module.optional_apply(live_view_module, :get_model, [
+ params,
+ session,
+ socket
+ ])
+
+ {:cont, socket |> assign(model: model)}
+ end
+end
diff --git a/core/lib/core_web/live/hook/remote_ip.ex b/core/lib/core_web/live/hook/remote_ip.ex
new file mode 100644
index 000000000..6316a04d2
--- /dev/null
+++ b/core/lib/core_web/live/hook/remote_ip.ex
@@ -0,0 +1,23 @@
+defmodule CoreWeb.Live.Hook.RemoteIp do
+ @moduledoc "A Live Hook that injects the remote_ip from a session variable."
+ use Frameworks.Concept.LiveHook
+
+ @impl true
+ def on_mount(_live_view_module, _params, %{"remote_ip" => remote_ip}, socket) do
+ {:cont, socket |> assign(remote_ip: remote_ip)}
+ end
+end
+
+defmodule CoreWeb.Plug.RemoteIp do
+ @moduledoc "A Plug that sets a session variable to the current remote ip."
+ import Plug.Conn, only: [put_session: 3]
+
+ def init(options) do
+ options
+ end
+
+ def call(%{remote_ip: remote_ip} = conn, _opts) do
+ remote_ip = to_string(:inet_parse.ntoa(remote_ip))
+ put_session(conn, :remote_ip, remote_ip)
+ end
+end
diff --git a/core/lib/core_web/live/hook/tabbar.ex b/core/lib/core_web/live/hook/tabbar.ex
new file mode 100644
index 000000000..1572dec63
--- /dev/null
+++ b/core/lib/core_web/live/hook/tabbar.ex
@@ -0,0 +1,27 @@
+defmodule CoreWeb.Live.Hook.Tabbar do
+ use Frameworks.Concept.LiveHook
+
+ @impl true
+ def on_mount(live_view_module, _params, _session, socket) do
+ {
+ :cont,
+ socket
+ |> update_tabbar_size(live_view_module)
+ |> handle_viewport_updated(live_view_module)
+ }
+ end
+
+ defp handle_viewport_updated(socket, live_view_module) do
+ attach_hook(socket, :tabbar_viewport_updated, :handle_info, fn
+ :viewport_updated, socket ->
+ {:cont, socket |> update_tabbar_size(live_view_module)}
+
+ _, socket ->
+ {:cont, socket}
+ end)
+ end
+
+ defp update_tabbar_size(socket, live_view_module) do
+ optional_apply(socket, live_view_module, :update_tabbar_size)
+ end
+end
diff --git a/core/lib/core_web/live/hook/timezone.ex b/core/lib/core_web/live/hook/timezone.ex
new file mode 100644
index 000000000..50b31b22c
--- /dev/null
+++ b/core/lib/core_web/live/hook/timezone.ex
@@ -0,0 +1,14 @@
+defmodule CoreWeb.Live.Hook.Timezone do
+ use Frameworks.Concept.LiveHook
+
+ @impl true
+ def on_mount(_live_view_module, _params, _session, socket) do
+ timezone =
+ case {connected?(socket), get_connect_params(socket)} do
+ {true, %{"timezone" => timezone}} -> timezone
+ _ -> "Europe/Amsterdam"
+ end
+
+ {:cont, assign(socket, timezone: timezone)}
+ end
+end
diff --git a/core/lib/core_web/live/hook/uri.ex b/core/lib/core_web/live/hook/uri.ex
new file mode 100644
index 000000000..371bd6c3c
--- /dev/null
+++ b/core/lib/core_web/live/hook/uri.ex
@@ -0,0 +1,43 @@
+defmodule CoreWeb.Live.Hook.Uri do
+ @moduledoc "A LiveView helper that automatically sets the current locale from a session variable."
+ use Frameworks.Concept.LiveHook
+
+ @impl true
+ def on_mount(live_view_module, _params, _session, socket) do
+ {
+ :cont,
+ socket
+ |> assign(
+ uri: nil,
+ uri_origin: nil,
+ uri_path: nil
+ )
+ |> handle_uri(live_view_module)
+ }
+ end
+
+ defp handle_uri(socket, live_view_module) do
+ attach_hook(socket, :handle_uri, :handle_params, fn params, uri, socket ->
+ parsed_uri = URI.parse(uri)
+ uri_origin = "#{parsed_uri.scheme}://#{parsed_uri.authority}"
+
+ uri_path =
+ case parsed_uri.query do
+ nil -> parsed_uri.path
+ query -> "#{parsed_uri.path}?#{query}"
+ end
+
+ socket =
+ assign(socket,
+ params: params,
+ uri: uri,
+ uri_origin: uri_origin,
+ uri_path: uri_path
+ )
+
+ socket = optional_apply(socket, live_view_module, :handle_uri)
+
+ {:cont, socket}
+ end)
+ end
+end
diff --git a/core/lib/core_web/live/hook/user.ex b/core/lib/core_web/live/hook/user.ex
new file mode 100644
index 000000000..4bcb8ffc7
--- /dev/null
+++ b/core/lib/core_web/live/hook/user.ex
@@ -0,0 +1,20 @@
+defmodule CoreWeb.Live.Hook.User do
+ @moduledoc """
+ Live Hook that injects the current user.
+ """
+ use Frameworks.Concept.LiveHook
+ alias Systems.Account
+
+ @impl true
+ def on_mount(_live_view_module, _params, session, socket) do
+ {:cont, socket |> assign(current_user: current_user(session))}
+ end
+
+ defp current_user(%{assigns: %{current_user: current_user}}), do: current_user
+
+ defp current_user(%{"user_token" => user_token}) do
+ Account.Public.get_user_by_session_token(user_token)
+ end
+
+ defp current_user(_), do: nil
+end
diff --git a/core/lib/core_web/live/hook/viewport.ex b/core/lib/core_web/live/hook/viewport.ex
new file mode 100644
index 000000000..0079e9430
--- /dev/null
+++ b/core/lib/core_web/live/hook/viewport.ex
@@ -0,0 +1,41 @@
+defmodule CoreWeb.Live.Hook.Viewport do
+ @moduledoc """
+ Live Hook that injects the current viewport and breakpoint.
+ """
+ use Frameworks.Concept.LiveHook
+
+ import CoreWeb.UI.Responsive.Breakpoint
+ import CoreWeb.UI.Responsive.Viewport
+
+ @impl true
+ def on_mount(live_view_module, _params, _session, socket) do
+ {
+ :cont,
+ socket
+ |> assign_breakpoint()
+ |> handle_viewport_changed(live_view_module)
+ }
+ end
+
+ defp handle_viewport_changed(socket, live_view_module) do
+ attach_hook(socket, :handle_viewport_changed, :handle_event, fn
+ "viewport_changed", new_viewport, socket ->
+ {:cont, socket |> update_viewport(live_view_module, new_viewport)}
+
+ _, _, socket ->
+ {:cont, socket}
+ end)
+ end
+
+ defp update_viewport(socket, live_view_module, new_viewport) do
+ new_breakpoint = breakpoint(new_viewport)
+
+ send(self(), :viewport_updated)
+
+ socket
+ |> assign(viewport: new_viewport)
+ |> assign(breakpoint: new_breakpoint)
+ |> optional_apply(live_view_module, :update_view_model)
+ |> optional_apply(live_view_module, :handle_resize)
+ end
+end
diff --git a/core/lib/core_web/live/menu/builder.ex b/core/lib/core_web/live/menu/builder.ex
index 2c029fcd2..b65e42418 100644
--- a/core/lib/core_web/live/menu/builder.ex
+++ b/core/lib/core_web/live/menu/builder.ex
@@ -4,9 +4,10 @@ defmodule CoreWeb.Menu.Builder do
@type active_item :: atom()
@type menu :: map()
@type user :: map() | nil
+ @type uri :: binary() | nil
@type item :: atom()
- @callback build_menu(socket, type, active_item) :: menu
+ @callback build_menu(type, active_item, user, uri) :: menu
@callback include_map(user) :: map()
alias Systems.Admin
@@ -29,22 +30,20 @@ defmodule CoreWeb.Menu.Builder do
import CoreWeb.Menu.Helpers
@impl true
- def build_menu(assigns, menu_id, active_item) do
- builder = &build_item(assigns, menu_id, &1, active_item, @item_flags)
+ def build_menu(menu_id, active_item, user, uri) do
+ builder = &build_item(menu_id, &1, active_item, @item_flags, user, uri)
primary = select_items(menu_id, @primary)
secondary = select_items(menu_id, @secondary)
%{
- home: build_home(assigns, menu_id, unquote(home), @home_flags),
- primary: build(assigns, primary, builder),
- secondary: build(assigns, secondary, builder)
+ home: build_home(menu_id, unquote(home), @home_flags, uri),
+ primary: build(primary, builder, user),
+ secondary: build(secondary, builder, user)
}
end
- defp build(assigns, items, builder) do
- user = Map.get(assigns, :current_user)
-
+ defp build(items, builder, user) do
include_map =
Map.merge(
unquote(__MODULE__).include_map(user),
diff --git a/core/lib/core_web/live/menu/helpers.ex b/core/lib/core_web/live/menu/helpers.ex
index 854e57399..f998e0821 100644
--- a/core/lib/core_web/live/menu/helpers.ex
+++ b/core/lib/core_web/live/menu/helpers.ex
@@ -10,24 +10,9 @@ defmodule CoreWeb.Menu.Helpers do
require CoreWeb.Gettext
- def home_item(menu_id, id, action, size) when is_atom(id) do
- face = %{
- type: :menu_home,
- icon: id,
- size: size
- }
-
- %{
- id: id,
- menu_id: menu_id,
- action: action,
- face: face
- }
- end
-
- def build_home(assigns, menu_id, id, config) do
+ def build_home(menu_id, id, config, uri) do
if Keyword.has_key?(config, menu_id) do
- action = action(assigns, id)
+ action = action(id, uri)
flags = select_flags(menu_id, id, config)
size =
@@ -43,6 +28,21 @@ defmodule CoreWeb.Menu.Helpers do
end
end
+ defp home_item(menu_id, id, action, size) when is_atom(id) do
+ face = %{
+ type: :menu_home,
+ icon: id,
+ size: size
+ }
+
+ %{
+ id: id,
+ menu_id: menu_id,
+ action: action,
+ face: face
+ }
+ end
+
def menu_item(menu_id, id, active?, action, %{} = opts) when is_atom(id) do
face = %{
type: :menu_item,
@@ -60,22 +60,20 @@ defmodule CoreWeb.Menu.Helpers do
}
end
- def menu_item(assigns, menu_id, id, active_item, flags) when is_list(flags) do
+ def menu_item(menu_id, id, active_item, flags, user, uri) when is_list(flags) do
active? = id == active_item
- action = action(assigns, id)
- opts = opts(assigns, id, flags)
+ action = action(id, uri)
+ opts = opts(id, flags, user)
menu_item(menu_id, id, active?, action, opts)
end
- def build_item(assigns, menu_id, id, active_item, config) do
+ def build_item(menu_id, id, active_item, config, user, uri) do
flags = select_flags(menu_id, id, config)
- menu_item(assigns, menu_id, id, active_item, flags)
+ menu_item(menu_id, id, active_item, flags, user, uri)
end
- defp opts(assigns, id, flags) when is_list(flags) do
- user = Map.get(assigns, :current_user)
-
+ defp opts(id, flags, user) when is_list(flags) do
icon =
if Enum.member?(flags, :icon) do
icon(id)
@@ -104,7 +102,9 @@ defmodule CoreWeb.Menu.Helpers do
}
end
- def action(%{uri: uri} = assigns, :language) do
+ def action(:language, nil), do: action(:language, nil, "\\")
+
+ def action(:language, uri) do
parsed_uri = URI.parse(uri)
redirect_url =
@@ -113,14 +113,12 @@ defmodule CoreWeb.Menu.Helpers do
query -> "#{parsed_uri.path}?#{query}"
end
- action(assigns, :language, redirect_url)
+ action(:language, uri, redirect_url)
end
- def action(assigns, :language), do: action(assigns, :language, "\\")
-
- def action(_assigns, id) when is_atom(id), do: ItemsProvider.action(id)
+ def action(id, _uri) when is_atom(id), do: ItemsProvider.action(id)
- def action(_assigns, :language, redirect_url) when is_binary(redirect_url) do
+ def action(:language, _uri, redirect_url) when is_binary(redirect_url) do
[locale] = supported_languages()
%{
diff --git a/core/lib/core_web/live_locale.ex b/core/lib/core_web/live_locale.ex
deleted file mode 100644
index 41a5c7f54..000000000
--- a/core/lib/core_web/live_locale.ex
+++ /dev/null
@@ -1,34 +0,0 @@
-defmodule CoreWeb.LiveLocale do
- @moduledoc "A LiveView helper that changes the locale of the current process"
-
- def put_locale(locale) when is_atom(locale), do: put_locale(Atom.to_string(locale))
-
- def put_locale(locale) do
- CoreWeb.Cldr.put_locale(locale)
- Gettext.put_locale(locale)
- Gettext.put_locale(CoreWeb.Gettext, locale)
- Gettext.put_locale(Timex.Gettext, locale)
- end
-
- def get_locale() do
- Gettext.get_locale()
- end
-
- defmacro __using__(_opts \\ nil) do
- quote do
- @before_compile CoreWeb.LiveLocale
- import CoreWeb.LiveLocale
- end
- end
-
- defmacro __before_compile__(_env) do
- quote do
- defoverridable mount: 3
-
- @impl true
- def mount(params, session, socket) do
- super(params, session, socket |> assign(locale: CoreWeb.LiveLocale.get_locale()))
- end
- end
- end
-end
diff --git a/core/lib/core_web/live_menus.ex b/core/lib/core_web/live_menus.ex
deleted file mode 100644
index 1a6539a76..000000000
--- a/core/lib/core_web/live_menus.ex
+++ /dev/null
@@ -1,63 +0,0 @@
-defmodule CoreWeb.LiveMenus do
- defmacro __using__({menu_builder, menus}) do
- quote do
- @before_compile CoreWeb.LiveMenus
- import Phoenix.Component
-
- def builder, do: Application.fetch_env!(:core, unquote(menu_builder))
-
- def build_menu(%{active_menu_item: active_menu_item} = assigns, type) do
- builder().build_menu(assigns, type, active_menu_item)
- end
-
- def build_menu(%{vm: %{active_menu_item: active_menu_item}} = assigns, type) do
- builder().build_menu(assigns, type, active_menu_item)
- end
-
- def build_menus(%{assigns: assigns} = socket) do
- menus = build_menus(assigns)
- socket |> assign(menus: menus)
- end
-
- def build_menus(%{authorization_failed: true}), do: nil
-
- def build_menus(assigns) do
- Enum.reduce(unquote(menus), %{}, fn menu_id, acc ->
- Map.put(acc, menu_id, build_menu(assigns, menu_id))
- end)
- end
-
- def update_menus(socket) do
- socket
- |> build_menus()
- end
- end
- end
-
- defmacro __before_compile__(_env) do
- quote do
- defoverridable mount: 3
-
- @impl true
- def mount(params, session, socket) do
- {:ok, socket} = super(params, session, socket)
- {:ok, socket |> update_menus()}
- end
-
- defoverridable handle_uri: 1
-
- def handle_uri(socket) do
- super(socket)
- |> update_menus()
- end
-
- defoverridable handle_view_model_updated: 1
-
- @impl true
- def handle_view_model_updated(socket) do
- super(socket)
- |> update_menus()
- end
- end
- end
-end
diff --git a/core/lib/core_web/live_remote_ip.ex b/core/lib/core_web/live_remote_ip.ex
deleted file mode 100644
index fe0d8d3b5..000000000
--- a/core/lib/core_web/live_remote_ip.ex
+++ /dev/null
@@ -1,33 +0,0 @@
-defmodule CoreWeb.Plug.LiveRemoteIp do
- @moduledoc "A Plug that sets a session variable to the current remote ip."
- import Plug.Conn, only: [put_session: 3]
-
- def init(options) do
- options
- end
-
- def call(%{remote_ip: remote_ip} = conn, _opts) do
- remote_ip = to_string(:inet_parse.ntoa(remote_ip))
- put_session(conn, :remote_ip, remote_ip)
- end
-end
-
-defmodule CoreWeb.LiveRemoteIp do
- @moduledoc "A LiveView helper that automatically sets the current remote_ip from a session variable."
-
- defmacro __using__(_opts \\ nil) do
- quote do
- @before_compile CoreWeb.LiveRemoteIp
- end
- end
-
- defmacro __before_compile__(_env) do
- quote do
- defoverridable mount: 3
-
- def mount(params, %{"remote_ip" => remote_ip} = session, socket) do
- super(params, session, socket |> assign(remote_ip: remote_ip))
- end
- end
- end
-end
diff --git a/core/lib/core_web/live_timezone.ex b/core/lib/core_web/live_timezone.ex
deleted file mode 100644
index f19057daf..000000000
--- a/core/lib/core_web/live_timezone.ex
+++ /dev/null
@@ -1,31 +0,0 @@
-defmodule CoreWeb.LiveTimezone do
- import Phoenix.LiveView, only: [connected?: 1, get_connect_params: 1]
-
- def update_timezone(socket, _session) do
- timezone =
- case {connected?(socket), get_connect_params(socket)} do
- {true, %{"timezone" => timezone}} -> timezone
- _ -> "Europe/Amsterdam"
- end
-
- Phoenix.Component.assign(socket, timezone: timezone)
- end
-
- defmacro __using__(_opts \\ nil) do
- quote do
- @before_compile CoreWeb.LiveTimezone
- import CoreWeb.LiveTimezone
- end
- end
-
- defmacro __before_compile__(_env) do
- quote do
- defoverridable mount: 3
-
- @impl true
- def mount(params, session, socket) do
- super(params, session, socket |> update_timezone(session))
- end
- end
- end
-end
diff --git a/core/lib/core_web/live_uri.ex b/core/lib/core_web/live_uri.ex
deleted file mode 100644
index ccca92ae5..000000000
--- a/core/lib/core_web/live_uri.ex
+++ /dev/null
@@ -1,34 +0,0 @@
-defmodule CoreWeb.LiveUri do
- @moduledoc "A LiveView helper that automatically sets the current locale from a session variable."
-
- @callback handle_uri(Socket.t()) :: Socket.t()
-
- defmacro __using__(_opts \\ nil) do
- quote do
- @behaviour CoreWeb.LiveUri
-
- import Phoenix.LiveView
-
- def handle_params(unsigned_params, uri, socket) do
- parsed_uri = URI.parse(uri)
- uri_origin = "#{parsed_uri.scheme}://#{parsed_uri.authority}"
-
- uri_path =
- case parsed_uri.query do
- nil -> parsed_uri.path
- query -> "#{parsed_uri.path}?#{query}"
- end
-
- {
- :noreply,
- socket
- |> assign(:params, unsigned_params)
- |> assign(:uri, uri)
- |> assign(:uri_origin, uri_origin)
- |> assign(:uri_path, uri_path)
- |> handle_uri()
- }
- end
- end
- end
-end
diff --git a/core/lib/core_web/live_user.ex b/core/lib/core_web/live_user.ex
deleted file mode 100644
index f061c49bd..000000000
--- a/core/lib/core_web/live_user.ex
+++ /dev/null
@@ -1,36 +0,0 @@
-defmodule CoreWeb.LiveUser do
- alias Systems.Account
-
- @moduledoc """
- Automatically setup the current user in LiveViews.
- """
- def current_user(%{assigns: %{current_user: current_user}}), do: current_user
-
- def current_user(%{"user_token" => user_token}) do
- Account.Public.get_user_by_session_token(user_token)
- end
-
- def current_user(_), do: nil
-
- defmacro __using__(_opts \\ nil) do
- quote do
- @before_compile CoreWeb.LiveUser
- import CoreWeb.LiveUser
- end
- end
-
- defmacro __before_compile__(_env) do
- quote do
- defoverridable mount: 3
-
- def mount(params, session, socket) do
- super(
- params,
- session,
- socket
- |> Phoenix.Component.assign(current_user: current_user(session))
- )
- end
- end
- end
-end
diff --git a/core/lib/core_web/menus.ex b/core/lib/core_web/menus.ex
new file mode 100644
index 000000000..ea65d67b8
--- /dev/null
+++ b/core/lib/core_web/menus.ex
@@ -0,0 +1,18 @@
+defmodule CoreWeb.Menus do
+ def build_menus({menu_builder, menus, active_menu_item}, user, uri),
+ do: build_menus(menu_builder, menus, active_menu_item, user, uri)
+
+ def build_menus(menu_builder, menus, active_menu_item, user, uri) do
+ Enum.reduce(menus, %{}, fn menu_item, acc ->
+ Map.put(acc, menu_item, build_menu(menu_builder, menu_item, active_menu_item, user, uri))
+ end)
+ end
+
+ defp build_menu(menu_builder, menu_item, active_menu_item, user, uri) do
+ menu_builder_module(menu_builder).build_menu(menu_item, active_menu_item, user, uri)
+ end
+
+ def menu_builder_module(menu_builder) do
+ Application.fetch_env!(:core, menu_builder)
+ end
+end
diff --git a/core/lib/core_web/routes.ex b/core/lib/core_web/routes.ex
index ed7da0390..f1571c7cb 100644
--- a/core/lib/core_web/routes.ex
+++ b/core/lib/core_web/routes.ex
@@ -24,7 +24,7 @@ defmodule CoreWeb.Routes do
)
plug(RemoteIp)
- plug(CoreWeb.Plug.LiveRemoteIp)
+ plug(CoreWeb.Plug.RemoteIp)
plug(:fetch_live_flash)
plug(:fetch_meta_info)
@@ -70,6 +70,11 @@ defmodule CoreWeb.Routes do
require Systems.Routes
Systems.Routes.routes()
+ scope "/", CoreWeb do
+ pipe_through(:browser_unprotected)
+ get("/access_denied", ErrorController, :access_denied)
+ end
+
scope "/", CoreWeb do
pipe_through(:api)
diff --git a/core/lib/core_web/ui/responsive/viewport.ex b/core/lib/core_web/ui/responsive/viewport.ex
index 3dc30ca9c..9702571e9 100644
--- a/core/lib/core_web/ui/responsive/viewport.ex
+++ b/core/lib/core_web/ui/responsive/viewport.ex
@@ -1,31 +1,6 @@
defmodule CoreWeb.UI.Responsive.Viewport do
- import Phoenix.Component
- import CoreWeb.UI.Responsive.Breakpoint
-
- @callback handle_resize(socket :: Socket.t()) :: Socket.t()
-
- defmacro __using__(_) do
- quote do
- @behaviour CoreWeb.UI.Responsive.Viewport
-
- import CoreWeb.UI.Responsive.Viewport
- import CoreWeb.UI.Responsive.Breakpoint
-
- alias CoreWeb.UI.Responsive.Breakpoint
-
- def handle_event("viewport_resize", new_viewport, socket) do
- new_breakpoint = breakpoint(new_viewport)
-
- {
- :noreply,
- socket
- |> assign(viewport: new_viewport)
- |> assign(breakpoint: new_breakpoint)
- |> handle_resize()
- }
- end
- end
- end
+ import Phoenix.Component, only: [assign: 2]
+ import CoreWeb.UI.Responsive.Breakpoint, only: [breakpoint: 1]
def assign_viewport(%{private: %{connect_params: %{"viewport" => viewport}}} = socket) do
assign(socket, viewport: viewport)
diff --git a/core/mix.exs b/core/mix.exs
index 149ba9d04..ea32d763b 100644
--- a/core/mix.exs
+++ b/core/mix.exs
@@ -10,6 +10,7 @@ defmodule Core.MixProject do
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
start_permanent: Mix.env() == :prod,
+ consolidate_protocols: Mix.env() != :test,
aliases: aliases(),
deps: deps(),
# The main page in the docs
@@ -119,6 +120,8 @@ defmodule Core.MixProject do
{:file_system, "~> 0.2", only: [:dev, :test]},
{:bypass, "~> 2.1", only: :test},
{:mox, "~> 1.0", only: :test},
+ {:promox, "~> 0.1.0", only: :test},
+ {:mock, "~> 0.3.0", only: :test},
{:progress_bar, "~> 2.0.1", only: [:dev, :test]},
{:phoenix_live_reload, "~> 1.3", only: :dev},
{:credo, "~> 1.6", only: [:dev, :test], runtime: false},
diff --git a/core/mix.lock b/core/mix.lock
index 25a86613c..8c1412c14 100644
--- a/core/mix.lock
+++ b/core/mix.lock
@@ -68,10 +68,12 @@
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
+ "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
"mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"},
+ "mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"},
"mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"},
"nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
@@ -96,6 +98,7 @@
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
"progress_bar": {:hex, :progress_bar, "2.0.1", "7b40200112ae533d5adceb80ff75fbe66dc753bca5f6c55c073bfc122d71896d", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "2519eb58a2f149a3a094e729378256d8cb6d96a259ec94841bd69fdc71f18f87"},
+ "promox": {:hex, :promox, "0.1.4", "6a833b8b954ecf534ad5ccc9c74550b636e996f433f75ced2cadfd1a15e10d0c", [:mix], [], "hexpm", "7e0a54fb183fb705f8711255d3f5beb3519284b69ccf468bed3dc9c1e661f455"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"},
"sentry": {:hex, :sentry, "8.1.0", "8d235b62fce5f8e067ea1644e30939405b71a5e1599d9529ff82899d11d03f2b", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.3", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "f9fc7641ef61e885510f5e5963c2948b9de1de597c63f781e9d3d6c9c8681ab4"},
diff --git a/core/systems/account/await_confirmation.ex b/core/systems/account/await_confirmation.ex
index 491dd6b3d..63792fa19 100644
--- a/core/systems/account/await_confirmation.ex
+++ b/core/systems/account/await_confirmation.ex
@@ -1,10 +1,19 @@
defmodule Systems.Account.AwaitConfirmation do
use CoreWeb, :live_view
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.User, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Uri, __MODULE__})
+ on_mount({Frameworks.GreenLight.LiveHook, __MODULE__})
+ on_mount({Frameworks.Fabric.LiveHook, __MODULE__})
+
import CoreWeb.Layouts.Stripped.Html
import CoreWeb.Layouts.Stripped.Composer
+ import CoreWeb.Menus
alias Frameworks.Pixel.Text
+ @impl true
def mount(_params, _session, socket) do
require_feature(:password_sign_in)
@@ -16,6 +25,11 @@ defmodule Systems.Account.AwaitConfirmation do
}
end
+ def update_menus(%{assigns: %{current_user: user, uri: uri}} = socket) do
+ menus = build_menus(stripped_menus_config(), user, uri)
+ assign(socket, menus: menus)
+ end
+
# data(changeset, :any)
@impl true
def render(assigns) do
diff --git a/core/systems/account/confirm_token.ex b/core/systems/account/confirm_token.ex
index adbedefc6..85678ea16 100644
--- a/core/systems/account/confirm_token.ex
+++ b/core/systems/account/confirm_token.ex
@@ -1,19 +1,24 @@
defmodule Systems.Account.ConfirmToken do
- @moduledoc """
- The home screen.
- """
use CoreWeb, :live_view
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.User, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Uri, __MODULE__})
+ on_mount({Frameworks.GreenLight.LiveHook, __MODULE__})
+ on_mount({Frameworks.Fabric.LiveHook, __MODULE__})
+
import CoreWeb.Layouts.Stripped.Html
import CoreWeb.Layouts.Stripped.Composer
-
+ import CoreWeb.Menus
import Frameworks.Pixel.Form
- alias Frameworks.Pixel.Button
+ alias Frameworks.Pixel.Button
alias Systems.Account
alias Systems.Account.User
require Logger
+ @impl true
def mount(%{"token" => token}, _session, socket) do
require_feature(:password_sign_in)
@@ -30,6 +35,11 @@ defmodule Systems.Account.ConfirmToken do
}
end
+ def update_menus(%{assigns: %{current_user: user, uri: uri}} = socket) do
+ menus = build_menus(stripped_menus_config(), user, uri)
+ assign(socket, menus: menus)
+ end
+
defp update_confirm_button(socket) do
confirm_button = %{
action: %{type: :send, event: "confirm"},
diff --git a/core/systems/account/reset_password.ex b/core/systems/account/reset_password.ex
index 2099aecc4..24a4d60b8 100644
--- a/core/systems/account/reset_password.ex
+++ b/core/systems/account/reset_password.ex
@@ -3,9 +3,14 @@ defmodule Systems.Account.ResetPassword do
The home screen.
"""
use CoreWeb, :live_view
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.User, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Uri, __MODULE__})
+
import CoreWeb.Layouts.Stripped.Html
import CoreWeb.Layouts.Stripped.Composer
-
+ import CoreWeb.Menus
import Frameworks.Pixel.Form
alias Systems.Account
@@ -13,6 +18,7 @@ defmodule Systems.Account.ResetPassword do
alias Frameworks.Pixel.Text
alias Frameworks.Pixel.Button
+ @impl true
def mount(_params, _session, socket) do
{
:ok,
@@ -22,6 +28,11 @@ defmodule Systems.Account.ResetPassword do
}
end
+ def update_menus(%{assigns: %{current_user: user, uri: uri}} = socket) do
+ menus = build_menus(stripped_menus_config(), user, uri)
+ assign(socket, menus: menus)
+ end
+
@impl true
def handle_event("reset-password", %{"user" => %{"email" => email}}, socket) do
case User.valid_email_changeset(email) do
diff --git a/core/systems/account/reset_password_token.ex b/core/systems/account/reset_password_token.ex
index ce1a9dd84..720e57356 100644
--- a/core/systems/account/reset_password_token.ex
+++ b/core/systems/account/reset_password_token.ex
@@ -3,14 +3,22 @@ defmodule Systems.Account.ResetPasswordToken do
The password reset token.
"""
use CoreWeb, :live_view
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.User, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Uri, __MODULE__})
+ on_mount({Frameworks.GreenLight.LiveHook, __MODULE__})
+ on_mount({Frameworks.Fabric.LiveHook, __MODULE__})
+
import CoreWeb.Layouts.Stripped.Html
import CoreWeb.Layouts.Stripped.Composer
-
+ import CoreWeb.Menus
import Frameworks.Pixel.Form
alias Systems.Account
alias Frameworks.Pixel.Button
+ @impl true
def mount(%{"token" => token}, _session, socket) do
if user = Account.Public.get_user_by_reset_password_token(token) do
{
@@ -31,6 +39,11 @@ defmodule Systems.Account.ResetPasswordToken do
end
end
+ def update_menus(%{assigns: %{current_user: user, uri: uri}} = socket) do
+ menus = build_menus(stripped_menus_config(), user, uri)
+ assign(socket, menus: menus)
+ end
+
@impl true
def handle_event(
"reset-password",
diff --git a/core/systems/account/signup_page.ex b/core/systems/account/signup_page.ex
index 2308b8e39..60bfaa3cf 100644
--- a/core/systems/account/signup_page.ex
+++ b/core/systems/account/signup_page.ex
@@ -3,13 +3,22 @@ defmodule Systems.Account.SignupPage do
The home screen.
"""
use CoreWeb, :live_view
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.User, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Uri, __MODULE__})
+ on_mount({Frameworks.GreenLight.LiveHook, __MODULE__})
+ on_mount({Frameworks.Fabric.LiveHook, __MODULE__})
+
import CoreWeb.Layouts.Stripped.Html
import CoreWeb.Layouts.Stripped.Composer
+ import CoreWeb.Menus
alias Systems.Account
alias Systems.Account.UserForm
alias Systems.Account.User
+ @impl true
def mount(%{"user_type" => user_type}, _session, socket) do
require_feature(:password_sign_in)
creator? = user_type == "creator"
@@ -27,6 +36,11 @@ defmodule Systems.Account.SignupPage do
}
end
+ def update_menus(%{assigns: %{current_user: user, uri: uri}} = socket) do
+ menus = build_menus(stripped_menus_config(), user, uri)
+ assign(socket, menus: menus)
+ end
+
@impl true
def handle_event("signup", %{"user" => user_params}, %{assigns: %{creator?: creator?}} = socket) do
user_params = Map.put(user_params, "creator", creator?)
diff --git a/core/systems/account/user_profile_page.ex b/core/systems/account/user_profile_page.ex
index 9e5d60a32..313c5d698 100644
--- a/core/systems/account/user_profile_page.ex
+++ b/core/systems/account/user_profile_page.ex
@@ -20,12 +20,6 @@ defmodule Systems.Account.UserProfilePage do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def handle_event(_, _payload, socket) do
{:noreply, socket}
diff --git a/core/systems/admin/config_page.ex b/core/systems/admin/config_page.ex
index 6aa7264dc..aac46e5cd 100644
--- a/core/systems/admin/config_page.ex
+++ b/core/systems/admin/config_page.ex
@@ -12,14 +12,6 @@ defmodule Systems.Admin.ConfigPage do
{:ok, socket |> assign(initial_tab: initial_tab)}
end
- @impl true
- def handle_view_model_updated(socket) do
- socket
- end
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def handle_event("change", _payload, socket) do
{:noreply, socket}
diff --git a/core/systems/admin/import_rewards_page.ex b/core/systems/admin/import_rewards_page.ex
index f77d2744d..c37ac1900 100644
--- a/core/systems/admin/import_rewards_page.ex
+++ b/core/systems/admin/import_rewards_page.ex
@@ -38,6 +38,7 @@ defmodule Systems.Admin.ImportRewardsPage do
Systems.Observatory.SingletonModel.instance()
end
+ @impl true
def mount(%{"back" => back}, _session, socket) do
entity = %Admin.ImportRewardsModel{}
@@ -94,12 +95,6 @@ defmodule Systems.Admin.ImportRewardsPage do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def compose(:currency, %{currency_labels: items}) do
%{
diff --git a/core/systems/admin/login_page.ex b/core/systems/admin/login_page.ex
index 85f613964..2350f825a 100644
--- a/core/systems/admin/login_page.ex
+++ b/core/systems/admin/login_page.ex
@@ -1,12 +1,21 @@
defmodule Systems.Admin.LoginPage do
use CoreWeb, :live_view
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.User, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Uri, __MODULE__})
+ on_mount({Frameworks.GreenLight.LiveHook, __MODULE__})
+ on_mount({Frameworks.Fabric.LiveHook, __MODULE__})
+
import CoreWeb.Layouts.Stripped.Html
import CoreWeb.Layouts.Stripped.Composer
+ import CoreWeb.Menus
import Ecto.Query
alias Core.Repo
alias Systems.Account.User
+ @impl true
def mount(_params, _session, socket) do
{
:ok,
@@ -16,6 +25,11 @@ defmodule Systems.Admin.LoginPage do
}
end
+ def update_menus(%{assigns: %{current_user: user, uri: uri}} = socket) do
+ menus = build_menus(stripped_menus_config(), user, uri)
+ assign(socket, menus: menus)
+ end
+
# FIXME: Move this to Accounts
defp list_users do
from(u in User, order_by: {:asc, :email})
diff --git a/core/systems/advert/_switch.ex b/core/systems/advert/_switch.ex
index a2bba122f..9b29c0774 100644
--- a/core/systems/advert/_switch.ex
+++ b/core/systems/advert/_switch.ex
@@ -4,7 +4,8 @@ defmodule Systems.Advert.Switch do
alias Systems.{
Advert,
Promotion,
- Assignment
+ Assignment,
+ Home
}
@impl true
@@ -56,6 +57,7 @@ defmodule Systems.Advert.Switch do
update(Promotion.LandingPage, promotion_id, promotion, from_pid)
update(Assignment.LandingPage, assignment_id, advert, from_pid)
update(Advert.ContentPage, id, advert, from_pid)
+ update(Home.Page, :singleton, %{id: :singleton}, from_pid)
end
defp handle({_, _}, _), do: nil
diff --git a/core/systems/advert/content_page.ex b/core/systems/advert/content_page.ex
index b4c09cab1..abcaa2dbe 100644
--- a/core/systems/advert/content_page.ex
+++ b/core/systems/advert/content_page.ex
@@ -28,15 +28,6 @@ defmodule Systems.Advert.ContentPage do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_resize(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def render(assigns) do
~H"""
diff --git a/core/systems/advert/content_page_builder.ex b/core/systems/advert/content_page_builder.ex
index bb9d49e23..fe270a59c 100644
--- a/core/systems/advert/content_page_builder.ex
+++ b/core/systems/advert/content_page_builder.ex
@@ -17,13 +17,13 @@ defmodule Systems.Advert.ContentPageBuilder do
submission: submission,
promotion: promotion
} = advert,
- assigns
+ %{branch: branch} = assigns
) do
submitted? = Pool.SubmissionModel.submitted?(submission)
show_errors = submitted?
tabs = create_tabs(advert, show_errors, assigns)
- breadcrumbs = create_breadcrumbs(advert)
+ breadcrumbs = Concept.Branch.hierarchy(branch)
action_map = action_map(advert, assigns)
actions = actions(advert, action_map)
@@ -41,13 +41,6 @@ defmodule Systems.Advert.ContentPageBuilder do
}
end
- defp create_breadcrumbs(advert) do
- case Concept.Branch.hierarchy(advert) do
- {:ok, hierarchy} -> hierarchy
- {:error, _} -> nil
- end
- end
-
defp create_tabs(advert, show_errors, assigns) do
advert
|> get_tab_keys()
diff --git a/core/systems/advert/funding_view.ex b/core/systems/advert/funding_view.ex
index 26181c884..57f02473a 100644
--- a/core/systems/advert/funding_view.ex
+++ b/core/systems/advert/funding_view.ex
@@ -58,7 +58,7 @@ defmodule Systems.Advert.FundingView do
changeset: changeset,
selected_budget: budget,
user: user,
- locale: CoreWeb.LiveLocale.get_locale()
+ locale: CoreWeb.Live.Hook.Locale.get_locale()
)
|> update_state()
|> update_reward()
diff --git a/core/systems/advert/promotion_landing_page_builder.ex b/core/systems/advert/promotion_landing_page_builder.ex
index 853203ead..7a5d3e48d 100644
--- a/core/systems/advert/promotion_landing_page_builder.ex
+++ b/core/systems/advert/promotion_landing_page_builder.ex
@@ -26,7 +26,7 @@ defmodule Systems.Advert.PromotionLandingPageBuilder do
) do
assignment
|> Assignment.Model.language()
- |> CoreWeb.LiveLocale.put_locale()
+ |> CoreWeb.Live.Hook.Locale.put_locale()
extra = Map.take(promotion, [:image_id | Promotion.Model.plain_fields()])
icon_url = "/images/#{pool_name |> String.downcase()}-wide-dark.svg"
diff --git a/core/systems/alliance/callback_page.ex b/core/systems/alliance/callback_page.ex
index 5ee2ce3b3..1338fab11 100644
--- a/core/systems/alliance/callback_page.ex
+++ b/core/systems/alliance/callback_page.ex
@@ -34,12 +34,6 @@ defmodule Systems.Alliance.CallbackPage do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
defp activate_participant_task(
%{assigns: %{vm: %{state: :participant}, model: model, current_user: user}} = socket
) do
diff --git a/core/systems/alliance/content_page.ex b/core/systems/alliance/content_page.ex
index 422adfec2..2d4f17a60 100644
--- a/core/systems/alliance/content_page.ex
+++ b/core/systems/alliance/content_page.ex
@@ -25,15 +25,6 @@ defmodule Systems.Alliance.ContentPage do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_resize(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def render(assigns) do
~H"""
diff --git a/core/systems/assignment/content_page.ex b/core/systems/assignment/content_page.ex
index a63c4ef93..ed94d9798 100644
--- a/core/systems/assignment/content_page.ex
+++ b/core/systems/assignment/content_page.ex
@@ -36,13 +36,9 @@ defmodule Systems.Assignment.ContentPage do
end
@impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_resize(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: update_view_model(socket)
+ def handle_uri(socket) do
+ update_view_model(socket)
+ end
@impl true
def render(assigns) do
diff --git a/core/systems/assignment/content_page_builder.ex b/core/systems/assignment/content_page_builder.ex
index eb3d8aa68..4e2c7c698 100644
--- a/core/systems/assignment/content_page_builder.ex
+++ b/core/systems/assignment/content_page_builder.ex
@@ -26,12 +26,12 @@ defmodule Systems.Assignment.ContentPageBuilder do
"""
def view_model(
%{id: id} = assignment,
- assigns
+ %{branch: branch} = assigns
) do
show_errors = false
template = Assignment.Private.get_template(assignment)
- breadcrumbs = create_breadcrumbs(assignment)
+ breadcrumbs = Concept.Branch.hierarchy(branch)
tabs = create_tabs(assignment, template, show_errors, assigns)
action_map = action_map(assignment)
actions = actions(assignment, action_map)
@@ -47,13 +47,6 @@ defmodule Systems.Assignment.ContentPageBuilder do
}
end
- defp create_breadcrumbs(assignment) do
- case Concept.Branch.hierarchy(assignment) do
- {:ok, hierarchy} -> hierarchy
- {:error, _} -> nil
- end
- end
-
defp action_map(assignment) do
preview_url = Assignment.Private.get_preview_url(assignment)
diff --git a/core/systems/assignment/crew_page.ex b/core/systems/assignment/crew_page.ex
index 00d3e4b3b..ffc441e8b 100644
--- a/core/systems/assignment/crew_page.ex
+++ b/core/systems/assignment/crew_page.ex
@@ -1,7 +1,9 @@
defmodule Systems.Assignment.CrewPage do
- use CoreWeb, {:live_view, :extended}
+ use CoreWeb, :live_view
use CoreWeb.Layouts.Stripped.Composer
- use CoreWeb.UI.Responsive.Viewport
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Viewport, __MODULE__})
require Logger
@@ -30,7 +32,7 @@ defmodule Systems.Assignment.CrewPage do
String.to_integer(id)
|> Assignment.Public.get!([:info])
|> Assignment.Model.language()
- |> CoreWeb.LiveLocale.put_locale()
+ |> CoreWeb.Live.Hook.Locale.put_locale()
{
:ok,
@@ -41,7 +43,6 @@ defmodule Systems.Assignment.CrewPage do
modal: nil,
panel_form: nil
)
- |> update_timezone(session)
|> update_panel_info(session)
|> update_image_info()
|> signal_started()
@@ -49,22 +50,17 @@ defmodule Systems.Assignment.CrewPage do
}
end
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def handle_view_model_updated(socket) do
socket
|> update_flow()
|> update_image_info()
- |> update_menus()
end
@impl true
def handle_resize(socket) do
socket
|> update_image_info()
- |> update_menus()
end
def signal_started(%{assigns: %{vm: %{crew_member: crew_member}}} = socket) do
@@ -235,7 +231,7 @@ defmodule Systems.Assignment.CrewPage do
<% end %>
-
+
<.flow fabric={@fabric} />
diff --git a/core/systems/budget/funding_page.ex b/core/systems/budget/funding_page.ex
index 0544da5a1..a2a287c59 100644
--- a/core/systems/budget/funding_page.ex
+++ b/core/systems/budget/funding_page.ex
@@ -32,12 +32,6 @@ defmodule Systems.Budget.FundingPage do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def compose(:create_budget_form, %{user: user, locale: locale}) do
%{
diff --git a/core/systems/content/plug.ex b/core/systems/content/_plug.ex
similarity index 100%
rename from core/systems/content/plug.ex
rename to core/systems/content/_plug.ex
diff --git a/core/systems/content/composer.ex b/core/systems/content/composer.ex
index e13122c28..13ec9a802 100644
--- a/core/systems/content/composer.ex
+++ b/core/systems/content/composer.ex
@@ -5,8 +5,7 @@ defmodule Systems.Content.Composer do
def __using_live_website__ do
quote do
- @before_compile {Systems.Content.Composer, :__before_compile_live_website__}
- use CoreWeb, {:live_view, :extended}
+ use CoreWeb, :live_view
use CoreWeb.Layouts.Website.Composer
use CoreWeb.LiveDefaults
@@ -19,22 +18,9 @@ defmodule Systems.Content.Composer do
end
end
- defmacro __before_compile_live_website__(_) do
- quote do
- defoverridable mount: 3
-
- @impl true
- def mount(params, session, socket) do
- {:ok, socket} = super(params, session, socket)
- {:ok, socket |> assign(popup: nil, dialog: nil, modal: nil)}
- end
- end
- end
-
def __using_live_workspace__ do
quote do
- @before_compile {Systems.Content.Composer, :__before_compile_live_workspace__}
- use CoreWeb, {:live_view, :extended}
+ use CoreWeb, :live_view
use CoreWeb.Layouts.Workspace.Composer
use CoreWeb.LiveDefaults
@@ -57,18 +43,6 @@ defmodule Systems.Content.Composer do
end
end
- defmacro __before_compile_live_workspace__(_) do
- quote do
- defoverridable mount: 3
-
- @impl true
- def mount(params, session, socket) do
- {:ok, socket} = super(params, session, socket)
- {:ok, socket |> assign(popup: nil, dialog: nil, modal: nil)}
- end
- end
- end
-
def __using_tabbar_page__ do
quote do
use Systems.Content.Composer, :live_workspace
@@ -77,104 +51,7 @@ defmodule Systems.Content.Composer do
def __using_management_page__ do
quote do
- @before_compile {Systems.Content.Composer, :__before_compile_management_page__}
-
use Systems.Content.Composer, :live_workspace
- use CoreWeb.UI.Responsive.Viewport
- alias CoreWeb.UI.Responsive.Breakpoint
-
- def tabbar_size({:unknown, _}), do: :unknown
- def tabbar_size(bp), do: Breakpoint.value(bp, :narrow, sm: %{30 => :wide})
-
- def create_actions(%{assigns: %{breakpoint: {:unknown, _}}} = _socket), do: []
-
- def create_actions(%{assigns: %{vm: %{actions: actions}}} = socket) do
- actions
- |> Keyword.keys()
- |> Enum.map(&create_action(Keyword.get(actions, &1), socket))
- |> Enum.filter(&(not is_nil(&1)))
- end
-
- def create_action(action, %{assigns: %{breakpoint: breakpoint}}) do
- Breakpoint.value(breakpoint, nil,
- xs: %{0 => action.icon},
- md: %{40 => action.label, 100 => action.icon},
- lg: %{50 => action.label}
- )
- end
-
- def update_tabbar_size(%{assigns: %{breakpoint: breakpoint}} = socket) do
- tabbar_size = tabbar_size(breakpoint)
- socket |> assign(tabbar_size: tabbar_size)
- end
-
- def update_actions(socket) do
- assign(socket, actions: create_actions(socket))
- end
-
- @impl true
- def handle_event(
- "action_click",
- %{"item" => action_id},
- %{assigns: %{vm: %{actions: actions}}} = socket
- ) do
- action_id = String.to_existing_atom(action_id)
- action = Keyword.get(actions, action_id)
-
- {
- :noreply,
- socket
- |> action.handle_click.()
- |> update_view_model()
- |> update_actions()
- }
- end
- end
- end
-
- defmacro __before_compile_management_page__(_) do
- quote do
- defoverridable mount: 3
-
- @imple true
- def mount(params, session, socket) do
- {:ok, socket} = super(params, session, socket)
-
- {
- :ok,
- socket
- |> assign_viewport()
- |> assign_breakpoint()
- |> update_actions()
- |> update_tabbar_size()
- }
- end
-
- defoverridable handle_uri: 1
-
- @impl true
- def handle_uri(socket) do
- super(socket)
- |> update_actions()
- end
-
- defoverridable handle_view_model_updated: 1
-
- @impl true
- def handle_view_model_updated(socket) do
- super(socket)
- |> update_actions()
- end
-
- defoverridable handle_resize: 1
-
- @impl true
- def handle_resize(socket) do
- super(socket)
- |> update_tabbar_size()
- |> update_actions()
- |> update_menus()
- end
end
end
end
diff --git a/core/systems/content/html.ex b/core/systems/content/html.ex
index 542ba75e1..d3fb75559 100644
--- a/core/systems/content/html.ex
+++ b/core/systems/content/html.ex
@@ -130,7 +130,7 @@ defmodule Systems.Content.Html do
def management_page(assigns) do
~H"""
-
+
<.live_workspace title={@title} menus={@menus} modal={@modal} popup={@popup} dialog={@dialog}>
<:top_bar>
diff --git a/core/systems/desktop/page.ex b/core/systems/desktop/page.ex
index 916747fe2..775c2018b 100644
--- a/core/systems/desktop/page.ex
+++ b/core/systems/desktop/page.ex
@@ -8,6 +8,7 @@ defmodule Systems.Desktop.Page do
user
end
+ @impl true
def mount(_params, _session, socket) do
{
:ok,
@@ -24,12 +25,6 @@ defmodule Systems.Desktop.Page do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def render(assigns) do
~H"""
diff --git a/core/systems/document/content_page.ex b/core/systems/document/content_page.ex
index 4b04b52f8..70344b71e 100644
--- a/core/systems/document/content_page.ex
+++ b/core/systems/document/content_page.ex
@@ -25,15 +25,6 @@ defmodule Systems.Document.ContentPage do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_resize(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def render(assigns) do
~H"""
diff --git a/core/systems/feldspar/plug.ex b/core/systems/feldspar/_plug.ex
similarity index 100%
rename from core/systems/feldspar/plug.ex
rename to core/systems/feldspar/_plug.ex
diff --git a/core/systems/feldspar/app_page.ex b/core/systems/feldspar/app_page.ex
index 3dabfcc74..88c99de60 100644
--- a/core/systems/feldspar/app_page.ex
+++ b/core/systems/feldspar/app_page.ex
@@ -1,7 +1,15 @@
defmodule Systems.Feldspar.AppPage do
use CoreWeb, :live_view
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.User, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Uri, __MODULE__})
+ on_mount({Frameworks.GreenLight.LiveHook, __MODULE__})
+ on_mount({Frameworks.Fabric.LiveHook, __MODULE__})
+
import CoreWeb.Layouts.Stripped.Html
import CoreWeb.Layouts.Stripped.Composer
+ import CoreWeb.Menus
require Logger
@@ -26,6 +34,11 @@ defmodule Systems.Feldspar.AppPage do
}
end
+ def update_menus(%{assigns: %{current_user: user, uri: uri}} = socket) do
+ menus = build_menus(stripped_menus_config(), user, uri)
+ assign(socket, menus: menus)
+ end
+
@impl true
def compose(:app_view, %{app_id: app_id, app_url: app_url}) do
%{
@@ -33,7 +46,7 @@ defmodule Systems.Feldspar.AppPage do
params: %{
key: "app_#{app_id}",
url: app_url,
- locale: CoreWeb.LiveLocale.get_locale()
+ locale: CoreWeb.Live.Hook.Locale.get_locale()
}
}
end
diff --git a/core/systems/feldspar/content_page.ex b/core/systems/feldspar/content_page.ex
index a9fcf037a..22ffb2215 100644
--- a/core/systems/feldspar/content_page.ex
+++ b/core/systems/feldspar/content_page.ex
@@ -25,15 +25,6 @@ defmodule Systems.Feldspar.ContentPage do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_resize(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def render(assigns) do
~H"""
diff --git a/core/systems/graphite/leaderboard_content_page.ex b/core/systems/graphite/leaderboard_content_page.ex
index 66857b089..0a7903d76 100644
--- a/core/systems/graphite/leaderboard_content_page.ex
+++ b/core/systems/graphite/leaderboard_content_page.ex
@@ -28,15 +28,6 @@ defmodule Systems.Graphite.LeaderboardContentPage do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_resize(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def render(assigns) do
~H"""
diff --git a/core/systems/graphite/leaderboard_content_page_builder.ex b/core/systems/graphite/leaderboard_content_page_builder.ex
index b5bc01c07..0f3d948c3 100644
--- a/core/systems/graphite/leaderboard_content_page_builder.ex
+++ b/core/systems/graphite/leaderboard_content_page_builder.ex
@@ -7,10 +7,10 @@ defmodule Systems.Graphite.LeaderboardContentPageBuilder do
alias Systems.Content
alias Systems.Graphite
- def view_model(%Graphite.LeaderboardModel{id: id} = leaderboard, assigns) do
+ def view_model(%Graphite.LeaderboardModel{id: id} = leaderboard, %{branch: branch} = assigns) do
action_map = action_map(leaderboard, assigns)
- breadcrumbs = create_breadcrumbs(leaderboard)
+ breadcrumbs = Concept.Branch.hierarchy(branch)
tabs = create_tabs(leaderboard, false, assigns)
%{
@@ -26,13 +26,6 @@ defmodule Systems.Graphite.LeaderboardContentPageBuilder do
}
end
- defp create_breadcrumbs(leaderboard) do
- case Concept.Branch.hierarchy(leaderboard) do
- {:ok, hierarchy} -> hierarchy
- {:error, _} -> nil
- end
- end
-
defp actions(%{status: :concept}, %{preview: preview, publish: publish}) do
[preview: preview, publish: publish]
end
diff --git a/core/systems/graphite/leaderboard_page.ex b/core/systems/graphite/leaderboard_page.ex
index edc3fd08d..e1a59d822 100644
--- a/core/systems/graphite/leaderboard_page.ex
+++ b/core/systems/graphite/leaderboard_page.ex
@@ -1,7 +1,9 @@
defmodule Systems.Graphite.LeaderboardPage do
- use CoreWeb, {:live_view, :extended}
+ use CoreWeb, :live_view
use CoreWeb.Layouts.Stripped.Composer
- use CoreWeb.UI.Responsive.Viewport
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Viewport, __MODULE__})
alias Core.ImageHelpers
alias Frameworks.Pixel.Card
@@ -37,14 +39,10 @@ defmodule Systems.Graphite.LeaderboardPage do
}
end
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def handle_resize(socket) do
socket
|> update_image_info()
- |> update_menus()
end
@impl true
@@ -87,7 +85,7 @@ defmodule Systems.Graphite.LeaderboardPage do
@impl true
def render(assigns) do
~H"""
-
+
<.stripped menus={@menus}>
<:header>
diff --git a/core/systems/graphite/tool_controller.ex b/core/systems/graphite/tool_controller.ex
index 65662bf0e..e2416b743 100644
--- a/core/systems/graphite/tool_controller.ex
+++ b/core/systems/graphite/tool_controller.ex
@@ -2,7 +2,7 @@ defmodule Systems.Graphite.ToolController do
use CoreWeb, :controller
def ensure_spot(%{assigns: %{current_user: _user}} = conn, %{"id" => _id}) do
- # TODO: PreRef
+ # FIXME: PreRef
# id = String.to_integer(id)
diff --git a/core/systems/home/_presenter.ex b/core/systems/home/_presenter.ex
index e98d64e0c..118171308 100644
--- a/core/systems/home/_presenter.ex
+++ b/core/systems/home/_presenter.ex
@@ -2,7 +2,7 @@ defmodule Systems.Home.Presenter do
@behaviour Frameworks.Concept.Presenter
@impl true
- def view_model(Systems.Home.Page, user, assigns) do
- Systems.Home.PageBuilder.view_model(user, assigns)
+ def view_model(Systems.Home.Page, _, assigns) do
+ Systems.Home.PageBuilder.view_model(nil, assigns)
end
end
diff --git a/core/systems/home/page.ex b/core/systems/home/page.ex
index 408780c6a..d07226df6 100644
--- a/core/systems/home/page.ex
+++ b/core/systems/home/page.ex
@@ -4,17 +4,12 @@ defmodule Systems.Home.Page do
alias Systems.Home
alias Frameworks.Pixel.Hero
- @impl true
- def get_model(_params, _session, %{assigns: %{current_user: user}} = _socket)
- when not is_nil(user) do
- user
- end
-
@impl true
def get_model(_params, _session, _socket) do
Systems.Observatory.SingletonModel.instance()
end
+ @impl true
def mount(_params, _session, socket) do
{
:ok,
@@ -32,10 +27,10 @@ defmodule Systems.Home.Page do
end
@impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
+ def handle_view_model_updated(socket) do
+ # FIXME: consider to move updates of childs to Fabric.LiveHook
+ socket |> update_child(:home_view)
+ end
@impl true
def render(assigns) do
diff --git a/core/systems/home/page_builder.ex b/core/systems/home/page_builder.ex
index 1dfd5c834..b396e2d0c 100644
--- a/core/systems/home/page_builder.ex
+++ b/core/systems/home/page_builder.ex
@@ -16,7 +16,7 @@ defmodule Systems.Home.PageBuilder do
alias Systems.Pool
alias Systems.Crew
- def view_model(%Account.User{} = user, assigns) do
+ def view_model(_, %{current_user: user} = assigns) do
panl? = panl_participant?(user)
put_locale(user, panl?)
@@ -58,11 +58,11 @@ defmodule Systems.Home.PageBuilder do
end
defp put_locale(%Systems.Account.User{creator: false}, true) do
- CoreWeb.LiveLocale.put_locale("nl")
+ CoreWeb.Live.Hook.Locale.put_locale("nl")
end
defp put_locale(_, _) do
- CoreWeb.LiveLocale.put_locale("en")
+ CoreWeb.Live.Hook.Locale.put_locale("en")
end
defp block_keys(%Account.User{}, opts) do
diff --git a/core/systems/lab/content_page.ex b/core/systems/lab/content_page.ex
index 5f96479cf..e55c7aea1 100644
--- a/core/systems/lab/content_page.ex
+++ b/core/systems/lab/content_page.ex
@@ -25,15 +25,6 @@ defmodule Systems.Lab.ContentPage do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_resize(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def render(assigns) do
~H"""
diff --git a/core/systems/lab/public_page.ex b/core/systems/lab/public_page.ex
index 06607646a..4da9f9a2a 100644
--- a/core/systems/lab/public_page.ex
+++ b/core/systems/lab/public_page.ex
@@ -1,13 +1,15 @@
defmodule Systems.Lab.PublicPage do
@moduledoc """
- The public promotion screen.
+ The public lab screen.
"""
use CoreWeb, :live_view
- alias Systems.{
- Lab
- }
+ alias Systems.Lab
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.User, __MODULE__})
+
+ @impl true
def mount(%{"id" => id}, _session, %{assigns: %{current_user: user}} = socket) do
tool = Lab.Public.get_tool!(id, [:time_slots])
diff --git a/core/systems/next_action/overview_page.ex b/core/systems/next_action/overview_page.ex
index 71e0a14e6..80552dccd 100644
--- a/core/systems/next_action/overview_page.ex
+++ b/core/systems/next_action/overview_page.ex
@@ -20,14 +20,10 @@ defmodule Systems.NextAction.OverviewPage do
}
end
- @impl true
def handle_view_model_updated(socket) do
refresh_next_actions(socket)
end
- @impl true
- def handle_uri(socket), do: socket
-
def refresh_next_actions(%{assigns: %{current_user: user}} = socket) do
assign(
socket,
diff --git a/core/systems/notification/overview_page.ex b/core/systems/notification/overview_page.ex
index d9b3a24e0..4a60a82f6 100644
--- a/core/systems/notification/overview_page.ex
+++ b/core/systems/notification/overview_page.ex
@@ -2,6 +2,10 @@ defmodule Systems.Notification.OverviewPage do
use CoreWeb, :live_view
alias Systems.Notification
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.User, __MODULE__})
+
+ @impl true
def mount(_params, _session, %{assigns: %{current_user: user}} = socket) do
{:ok, socket |> assign(:notifications, Notification.Public.list(user))}
end
diff --git a/core/systems/observatory/_live_feature.ex b/core/systems/observatory/_live_feature.ex
new file mode 100644
index 000000000..902aeec63
--- /dev/null
+++ b/core/systems/observatory/_live_feature.ex
@@ -0,0 +1,54 @@
+defmodule Systems.Observatory.LiveFeature do
+ @callback handle_view_model_updated(Socket.t()) :: Socket.t()
+
+ defmacro __using__(_opts \\ []) do
+ quote do
+ @behaviour Systems.Observatory.LiveFeature
+
+ @impl true
+ def handle_view_model_updated(socket), do: socket
+
+ defoverridable handle_view_model_updated: 1
+
+ @presenter Frameworks.Concept.System.presenter(__MODULE__)
+
+ import CoreWeb.Gettext
+ alias Systems.Observatory
+
+ # Stubs for messages that are handled in Live Hooks
+ def handle_info(%Phoenix.Socket.Broadcast{}, socket), do: {:noreply, socket}
+ def handle_info(:view_model_updated, socket), do: {:noreply, socket}
+
+ def observe_view_model(%{assigns: %{authorization_failed: true}} = socket) do
+ socket
+ end
+
+ def observe_view_model(%{assigns: %{model: %{id: id} = model}} = socket) do
+ socket
+ |> Observatory.Public.observe([{__MODULE__, [id]}])
+ |> Observatory.Public.update_view_model(__MODULE__, model, @presenter)
+ end
+
+ def update_view_model(%{assigns: %{model: model}} = socket) do
+ socket
+ |> Observatory.Public.update_view_model(__MODULE__, model, @presenter)
+ end
+
+ def put_info_flash(socket, from_pid) do
+ if from_pid == self() do
+ socket |> put_saved_info_flash()
+ else
+ socket |> put_updated_info_flash()
+ end
+ end
+
+ def put_updated_info_flash(socket) do
+ socket |> Frameworks.Pixel.Flash.put_info("Updated")
+ end
+
+ def put_saved_info_flash(socket) do
+ socket |> Frameworks.Pixel.Flash.put_info("Saved")
+ end
+ end
+ end
+end
diff --git a/core/systems/observatory/_live_hook.ex b/core/systems/observatory/_live_hook.ex
new file mode 100644
index 000000000..8c48c578f
--- /dev/null
+++ b/core/systems/observatory/_live_hook.ex
@@ -0,0 +1,56 @@
+defmodule Systems.Observatory.LiveHook do
+ use Frameworks.Concept.LiveHook
+
+ require Logger
+
+ @impl true
+ def on_mount(live_view_module, _params, session, socket) do
+ {
+ :cont,
+ socket
+ |> assign(session: session)
+ |> observe_view_model(live_view_module)
+ |> handle_auto_save_status(live_view_module)
+ |> handle_model_update(live_view_module)
+ }
+ end
+
+ defp observe_view_model(socket, live_view_module) do
+ live_view_module.observe_view_model(socket)
+ end
+
+ defp handle_auto_save_status(socket, _live_view_module) do
+ attach_hook(socket, :handle_auto_save_status, :handle_info, fn
+ %{auto_save: status}, socket ->
+ {:cont, socket |> assign(auto_save_status: status)}
+
+ _, socket ->
+ {:cont, socket}
+ end)
+ end
+
+ defp handle_model_update(socket, live_view_module) do
+ attach_hook(socket, :handle_model_update, :handle_info, fn
+ %{topic: _topic, payload: {_signal, %{model: model, from_pid: from_pid}}}, socket ->
+ {:cont, socket |> handle_model_update(live_view_module, model, from_pid)}
+
+ %{topic: _topic, payload: {_signal, %{model: model}}}, socket ->
+ Logger.warn("Unknown sender, no from_pid provided")
+ {:cont, socket |> handle_model_update(live_view_module, model, nil)}
+
+ _, socket ->
+ {:cont, socket}
+ end)
+ end
+
+ defp handle_model_update(socket, live_view_module, model, from_pid) do
+ # Send message to other Live Hooks
+ send(self(), :view_model_updated)
+
+ socket
+ |> assign(model: model)
+ |> optional_apply(live_view_module, :update_view_model)
+ |> optional_apply(live_view_module, :handle_view_model_updated)
+ |> optional_apply(live_view_module, :put_info_flash, [from_pid])
+ end
+end
diff --git a/core/systems/observatory/_public.ex b/core/systems/observatory/_public.ex
index 15c9283ab..a8e2635e3 100644
--- a/core/systems/observatory/_public.ex
+++ b/core/systems/observatory/_public.ex
@@ -1,9 +1,6 @@
defmodule Systems.Observatory.Public do
alias CoreWeb.Endpoint
- @callback get_model(map(), map(), Socket.t()) :: Socket.t()
- @callback handle_view_model_updated(Socket.t()) :: Socket.t()
-
def subscribe(signal, key \\ []) do
Endpoint.subscribe(topic_key(signal, key))
end
@@ -43,121 +40,19 @@ defmodule Systems.Observatory.Public do
def update_view_model(socket, page, model, presenter) do
vm = get_view_model(socket, page, model, presenter)
- socket
- |> Phoenix.Component.assign(vm: vm)
+ Phoenix.Component.assign(socket, vm: vm)
end
- defp get_view_model(_socket, page, _model, nil) do
+ def get_view_model(_socket, page, _model, nil) do
raise "No presenter available for #{page}"
end
- defp get_view_model(
- %{assigns: assigns} = _socket,
- page,
- model,
- presenter
- ) do
+ def get_view_model(
+ %{assigns: assigns} = _socket,
+ page,
+ model,
+ presenter
+ ) do
apply(presenter, :view_model, [page, model, assigns])
end
-
- defmacro __using__(_opts \\ []) do
- quote do
- @behaviour Systems.Observatory.Public
- @before_compile Systems.Observatory.Public
- @presenter Frameworks.Concept.System.presenter(__MODULE__)
-
- import CoreWeb.Gettext
- alias Systems.Observatory.Public
-
- require Logger
-
- def handle_info(%{auto_save: status}, socket) do
- {
- :noreply,
- socket |> assign(auto_save_status: status)
- }
- end
-
- def handle_info(
- %{topic: _topic, payload: {signal, %{model: model, from_pid: from_pid}}} = payload,
- socket
- ) do
- {
- :noreply,
- socket
- |> assign(model: model)
- |> update_view_model()
- |> handle_view_model_updated()
- |> put_info_flash(from_pid)
- }
- end
-
- def handle_info(%{topic: topic, payload: {signal, %{model: model}}} = payload, socket) do
- Logger.warn("Unknown sender, no from_pid provided")
- handle_info(%{topic: topic, payload: {signal, %{model: model, from_pid: nil}}}, socket)
- end
-
- def observe_view_model(%{assigns: %{authorization_failed: true}} = socket) do
- socket
- end
-
- def observe_view_model(%{assigns: %{model: %{id: id} = model}} = socket) do
- socket
- |> Public.observe([{__MODULE__, [id]}])
- |> Public.update_view_model(__MODULE__, model, @presenter)
- end
-
- def observe_event(%{assigns: %{model: %{id: id} = model}} = socket) do
- socket
- |> Public.observe([{__MODULE__, [id]}])
- |> Public.update_view_model(__MODULE__, model, @presenter)
- end
-
- def update_view_model(%{assigns: %{model: model}} = socket) do
- socket
- |> Public.update_view_model(__MODULE__, model, @presenter)
- end
-
- def put_info_flash(socket, from_pid) do
- if from_pid == self() do
- socket |> put_saved_info_flash()
- else
- socket |> put_updated_info_flash()
- end
- end
-
- def put_updated_info_flash(socket) do
- socket |> Frameworks.Pixel.Flash.put_info("Updated")
- end
-
- def put_saved_info_flash(socket) do
- socket |> Frameworks.Pixel.Flash.put_info("Saved")
- end
- end
- end
-
- defmacro __before_compile__(_env) do
- quote do
- defoverridable mount: 3
-
- @impl true
- def mount(params, session, socket) do
- model = get_model(params, session, socket)
-
- super(
- params,
- session,
- socket
- |> assign(model: model, session: session)
- |> observe_view_model()
- )
- end
-
- defoverridable handle_view_model_updated: 1
-
- def handle_view_model_updated(socket) do
- super(socket)
- end
- end
- end
end
diff --git a/core/systems/org/content_page.ex b/core/systems/org/content_page.ex
index 338cbd722..99a4f13f1 100644
--- a/core/systems/org/content_page.ex
+++ b/core/systems/org/content_page.ex
@@ -23,15 +23,6 @@ defmodule Systems.Org.ContentPage do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_resize(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def render(assigns) do
~H"""
diff --git a/core/systems/pool/detail_page.ex b/core/systems/pool/detail_page.ex
index a5cd7d58e..ebc95067a 100644
--- a/core/systems/pool/detail_page.ex
+++ b/core/systems/pool/detail_page.ex
@@ -3,7 +3,6 @@ defmodule Systems.Pool.DetailPage do
The pool details screen.
"""
use Systems.Content.Composer, :live_workspace
- use CoreWeb.UI.Responsive.Viewport
alias Frameworks.Pixel.Tabbar
alias Frameworks.Pixel.Navigation
@@ -39,12 +38,6 @@ defmodule Systems.Pool.DetailPage do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def handle_event("close_email_dialog", _, socket) do
{
@@ -94,12 +87,6 @@ defmodule Systems.Pool.DetailPage do
{:noreply, socket}
end
- @impl true
- def handle_resize(socket) do
- socket
- |> update_menus()
- end
-
defp close_email_dialog(socket) do
socket
|> assign(email_dialog: nil)
@@ -120,7 +107,7 @@ defmodule Systems.Pool.DetailPage do
<% end %>
-
+
diff --git a/core/systems/pool/landing_page.ex b/core/systems/pool/landing_page.ex
index bfa30e309..cb18b87db 100644
--- a/core/systems/pool/landing_page.ex
+++ b/core/systems/pool/landing_page.ex
@@ -17,12 +17,6 @@ defmodule Systems.Pool.LandingPage do
{:ok, socket}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def handle_event("register", _, socket) do
{
diff --git a/core/systems/pool/participant_page.ex b/core/systems/pool/participant_page.ex
index c40001468..aa61bff5b 100644
--- a/core/systems/pool/participant_page.ex
+++ b/core/systems/pool/participant_page.ex
@@ -18,12 +18,6 @@ defmodule Systems.Pool.ParticipantPage do
{:ok, socket}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def render(assigns) do
~H"""
diff --git a/core/systems/pool/submission_page.ex b/core/systems/pool/submission_page.ex
index 537edc24b..e22436d60 100644
--- a/core/systems/pool/submission_page.ex
+++ b/core/systems/pool/submission_page.ex
@@ -25,12 +25,6 @@ defmodule Systems.Pool.SubmissionPage do
{:ok, socket}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def handle_event("publish", _params, %{assigns: %{vm: %{submission: submission}}} = socket) do
socket =
diff --git a/core/systems/project/_live_hook.ex b/core/systems/project/_live_hook.ex
new file mode 100644
index 000000000..384cab62a
--- /dev/null
+++ b/core/systems/project/_live_hook.ex
@@ -0,0 +1,24 @@
+defmodule Systems.Project.LiveHook do
+ @moduledoc "A Live Hook that injects the correct Branch (Project.NodeModel) for each Leaf"
+ use Frameworks.Concept.LiveHook
+
+ alias Frameworks.Concept
+ alias Systems.Project
+
+ @impl true
+ def on_mount(_live_view_module, _params, _session, socket) do
+ branch =
+ with model <- Map.get(socket.assigns, :model, nil),
+ false <- model == nil,
+ false <- Concept.Leaf.impl_for(model) == nil,
+ item <- Project.Public.get_item_by(model),
+ false <- item == nil,
+ node <- Project.Public.get_node_by_item!(item) do
+ %Project.Branch{node_id: node.id, item_id: item.id}
+ else
+ _ -> nil
+ end
+
+ {:cont, socket |> assign(branch: branch)}
+ end
+end
diff --git a/core/systems/project/_public.ex b/core/systems/project/_public.ex
index 27cd64665..1e1314aac 100644
--- a/core/systems/project/_public.ex
+++ b/core/systems/project/_public.ex
@@ -1,6 +1,5 @@
defmodule Systems.Project.Public do
use CoreWeb, :verified_routes
- @behaviour Frameworks.Concept.Branch.Factory
import CoreWeb.Gettext
import Ecto.Query, warn: false
@@ -11,7 +10,6 @@ defmodule Systems.Project.Public do
alias Ecto.Multi
alias Frameworks.Signal
- alias Frameworks.Concept
alias Systems.Account.User
alias Systems.Advert
@@ -21,57 +19,6 @@ defmodule Systems.Project.Public do
alias Systems.Storage
alias Systems.Workflow
- @impl true
- def name(:parent, %Systems.Storage.EndpointModel{} = model) do
- case get_by_item_special(model) do
- %{name: name} -> {:ok, name}
- _ -> {:error, :not_found}
- end
- end
-
- @impl true
- def name(:self, %Systems.Storage.EndpointModel{} = model) do
- case get_item_by(model) do
- %{name: name} -> {:ok, name}
- _ -> {:error, :not_found}
- end
- end
-
- @impl true
- def name(_, _), do: {:error, :not_supported}
-
- @impl true
- def hierarchy(atom) do
- if item = get_item_by(atom) do
- breadcrumbs(item)
- else
- {:error, :unknown}
- end
- end
-
- def breadcrumbs(%Project.ItemModel{name: name} = item) do
- special_path = "/#{Concept.Leaf.resource_id(item)}/content"
- special_breadcrumb = %{label: name, path: special_path}
-
- {:ok, node_breadcrumbs} =
- item
- |> get_node_by_item!()
- |> breadcrumbs()
-
- {:ok, node_breadcrumbs ++ [special_breadcrumb]}
- end
-
- def breadcrumbs(%Project.NodeModel{} = node) do
- project = get_by_root(node)
- project_breadcrumb = %{label: project.name, path: "/project/node/#{node.id}"}
-
- {:ok, [projects_breadcrumb(), project_breadcrumb]}
- end
-
- defp projects_breadcrumb() do
- %{label: dgettext("eyra-project", "first.breadcrumb.label"), path: ~p"/project"}
- end
-
def get!(id, preload \\ []) do
from(project in Project.Model,
where: project.id == ^id,
diff --git a/core/systems/project/branch.ex b/core/systems/project/branch.ex
new file mode 100644
index 000000000..c8e2fcef8
--- /dev/null
+++ b/core/systems/project/branch.ex
@@ -0,0 +1,58 @@
+defmodule Systems.Project.Branch do
+ use Ecto.Schema
+ @primary_key false
+
+ embedded_schema do
+ field(:node_id, :integer)
+ field(:item_id, :integer)
+ end
+end
+
+defimpl Frameworks.Concept.Branch, for: Systems.Project.Branch do
+ use CoreWeb, :verified_routes
+ import CoreWeb.Gettext
+ import Frameworks.Utility.List
+ alias Frameworks.Concept
+ alias Systems.Project
+
+ def name(%Project.Branch{node_id: node_id}, :parent) do
+ %Project.Model{name: name} =
+ node_id
+ |> Project.Public.get_node!()
+ |> Project.Public.get_by_root()
+
+ name
+ end
+
+ def name(%Project.Branch{item_id: item_id}, :self) do
+ %Project.ItemModel{name: name} = Project.Public.get_item!(item_id)
+ name
+ end
+
+ def hierarchy(%Project.Branch{node_id: node_id, item_id: item_id}) do
+ node_breadcrumb = fn ->
+ Project.Public.get_node!(node_id) |> breadcrumb()
+ end
+
+ item_breadcrumb = fn ->
+ Project.Public.get_item!(item_id, Project.ItemModel.preload_graph(:down)) |> breadcrumb()
+ end
+
+ [root_breadcrumb()]
+ |> append_if(node_breadcrumb, not is_nil(node_id))
+ |> append_if(item_breadcrumb, not is_nil(item_id))
+ end
+
+ defp breadcrumb(%Project.ItemModel{name: name} = item) do
+ %{label: name, path: "/#{Concept.Leaf.resource_id(item)}/content"}
+ end
+
+ defp breadcrumb(%Project.NodeModel{} = node) do
+ %Project.Model{name: name} = Project.Public.get_by_root(node)
+ %{label: name, path: "/project/node/#{node.id}"}
+ end
+
+ defp root_breadcrumb() do
+ %{label: dgettext("eyra-project", "first.breadcrumb.label"), path: ~p"/project"}
+ end
+end
diff --git a/core/systems/project/branch_plug.ex b/core/systems/project/branch_plug.ex
new file mode 100644
index 000000000..b689589b1
--- /dev/null
+++ b/core/systems/project/branch_plug.ex
@@ -0,0 +1,30 @@
+defmodule Systems.Project.BranchPlug do
+ @behaviour Plug
+ alias Frameworks.Concept
+ alias Systems.Project
+ alias Systems.Storage
+
+ @impl true
+ def init(opts), do: opts
+
+ @impl true
+ def call(%{request_path: request_path} = conn, _opts) do
+ branch = branch(Path.split(request_path))
+ conn |> Plug.Conn.assign(:branch, branch)
+ end
+
+ defp branch(["/", "storage", "endpoint", id | _]), do: branch(Storage.Public.get_endpoint!(id))
+
+ defp branch(%{} = leaf) do
+ with false <- Concept.Leaf.impl_for(leaf) == nil,
+ item <- Project.Public.get_item_by(leaf),
+ false <- item == nil,
+ node <- Project.Public.get_node_by_item!(item) do
+ %Project.Branch{node_id: node.id, item_id: item.id}
+ else
+ _ -> nil
+ end
+ end
+
+ defp branch(_), do: nil
+end
diff --git a/core/systems/project/node_page.ex b/core/systems/project/node_page.ex
index 13579f085..c41e4c469 100644
--- a/core/systems/project/node_page.ex
+++ b/core/systems/project/node_page.ex
@@ -7,6 +7,11 @@ defmodule Systems.Project.NodePage do
alias Frameworks.Pixel.Breadcrumbs
alias Systems.Project
+ @impl true
+ def get_authorization_context(params, session, socket) do
+ get_model(params, session, socket)
+ end
+
@impl true
def get_model(%{"id" => id}, _session, _socket) do
Project.Public.get_node!(String.to_integer(id), Project.NodeModel.preload_graph(:down))
@@ -88,12 +93,6 @@ defmodule Systems.Project.NodePage do
{:noreply, socket |> hide_modal(modal_view)}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def render(assigns) do
~H"""
diff --git a/core/systems/project/node_page_builder.ex b/core/systems/project/node_page_builder.ex
index a4fa586bb..bf1e17ef4 100644
--- a/core/systems/project/node_page_builder.ex
+++ b/core/systems/project/node_page_builder.ex
@@ -2,7 +2,7 @@ defmodule Systems.Project.NodePageBuilder do
use Core.FeatureFlags
alias Frameworks.Utility.ViewModelBuilder
-
+ alias Frameworks.Concept
alias Systems.Project
def view_model(
@@ -11,7 +11,8 @@ defmodule Systems.Project.NodePageBuilder do
} = node,
assigns
) do
- {:ok, breadcrumbs} = Project.Public.breadcrumbs(node)
+ branch = %Project.Branch{node_id: node.id}
+ breadcrumbs = Concept.Branch.hierarchy(branch)
item_cards = to_item_cards(node, assigns)
node_cards = to_node_cards(node, assigns)
diff --git a/core/systems/project/overview_page.ex b/core/systems/project/overview_page.ex
index 9f91cd134..71cdfb809 100644
--- a/core/systems/project/overview_page.ex
+++ b/core/systems/project/overview_page.ex
@@ -21,12 +21,8 @@ defmodule Systems.Project.OverviewPage do
{:ok, socket}
end
- @impl true
def handle_view_model_updated(socket), do: socket |> update_child(:people_page)
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def compose(:project_form, %{active_project: project_id, vm: %{projects: projects}}) do
project = Enum.find(projects, &(&1.id == String.to_integer(project_id)))
diff --git a/core/systems/promotion/landing_page.ex b/core/systems/promotion/landing_page.ex
index 490b0993e..ede471136 100644
--- a/core/systems/promotion/landing_page.ex
+++ b/core/systems/promotion/landing_page.ex
@@ -3,7 +3,9 @@ defmodule Systems.Promotion.LandingPage do
The public promotion screen.
"""
use Systems.Content.Composer, :live_website
- use CoreWeb.UI.Responsive.Viewport
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Viewport, __MODULE__})
import Systems.Promotion.BannerView
@@ -42,14 +44,10 @@ defmodule Systems.Promotion.LandingPage do
}
end
- @impl true
def handle_view_model_updated(socket) do
update_image_info(socket)
end
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def handle_resize(socket) do
update_image_info(socket)
@@ -117,7 +115,7 @@ defmodule Systems.Promotion.LandingPage do
@impl true
def render(assigns) do
~H"""
-
+
<.live_website user={@current_user} user_agent={Browser.Ua.to_ua(@socket)} menus={@menus} modal={@modal} popup={@popup} dialog={@dialog}>
<:hero>
diff --git a/core/systems/storage/controller.ex b/core/systems/storage/controller.ex
index 4813fbc40..8166e0b71 100644
--- a/core/systems/storage/controller.ex
+++ b/core/systems/storage/controller.ex
@@ -2,19 +2,19 @@ defmodule Systems.Storage.Controller do
alias CoreWeb.UI.Timestamp
use CoreWeb, :controller
- alias Frameworks.Concept.Branch
+ alias Frameworks.Concept
alias Systems.Storage
alias Systems.Rate
- def export(conn, %{"id" => id}) do
+ def export(%{assigns: %{branch: branch}} = conn, %{"id" => id}) do
if endpoint =
Storage.Public.get_endpoint!(
String.to_integer(id),
Storage.EndpointModel.preload_graph(:down)
) do
special = Storage.EndpointModel.special(endpoint)
- branch_name = Branch.name(endpoint, :parent, "export")
+ branch_name = Concept.Branch.name(branch, :parent)
export(conn, special, branch_name)
else
diff --git a/core/systems/storage/endpoint_content_page.ex b/core/systems/storage/endpoint_content_page.ex
index ffbae673e..d906d1b24 100644
--- a/core/systems/storage/endpoint_content_page.ex
+++ b/core/systems/storage/endpoint_content_page.ex
@@ -28,7 +28,6 @@ defmodule Systems.Storage.EndpointContentPage do
}
end
- @impl true
def handle_view_model_updated(%{assigns: %{vm: vm}} = socket) do
if tab = Enum.find(vm.tabs, &(&1.id == :data_view)) do
Fabric.send_event(tab.child.ref, %{name: "update_files", payload: %{}})
@@ -37,9 +36,6 @@ defmodule Systems.Storage.EndpointContentPage do
socket
end
- @impl true
- def handle_resize(socket), do: socket
-
@impl true
def handle_uri(socket), do: update_view_model(socket)
diff --git a/core/systems/storage/endpoint_content_page_builder.ex b/core/systems/storage/endpoint_content_page_builder.ex
index 25e507c45..b0c6cc467 100644
--- a/core/systems/storage/endpoint_content_page_builder.ex
+++ b/core/systems/storage/endpoint_content_page_builder.ex
@@ -8,12 +8,12 @@ defmodule Systems.Storage.EndpointContentPageBuilder do
def view_model(
%{id: id} = endpoint,
- assigns
+ %{branch: branch} = assigns
) do
show_errors = true
- breadcrumbs = create_breadcrumbs(endpoint)
+ breadcrumbs = Concept.Branch.hierarchy(branch)
tabs = create_tabs(endpoint, show_errors, assigns)
- title = Concept.Branch.name(endpoint, :self, "Data")
+ title = Concept.Branch.name(branch, :self)
%{
id: id,
@@ -26,13 +26,6 @@ defmodule Systems.Storage.EndpointContentPageBuilder do
}
end
- defp create_breadcrumbs(endpoint) do
- case Concept.Branch.hierarchy(endpoint) do
- {:ok, hierarchy} -> hierarchy
- {:error, _} -> nil
- end
- end
-
defp get_tab_keys(endpoint) do
special = Storage.EndpointModel.special_field(endpoint)
@@ -79,10 +72,10 @@ defmodule Systems.Storage.EndpointContentPageBuilder do
:data,
endpoint,
show_errors,
- %{fabric: fabric, timezone: timezone} = _assigns
+ %{branch: branch, fabric: fabric, timezone: timezone} = _assigns
) do
ready? = true
- branch_name = Concept.Branch.name(endpoint, :parent, "Current")
+ branch_name = Concept.Branch.name(branch, :parent)
child =
Fabric.prepare_child(fabric, :data_view, Storage.EndpointDataView, %{
diff --git a/core/systems/support/helpdesk_page.ex b/core/systems/support/helpdesk_page.ex
index cafa8cea0..b84b74b46 100644
--- a/core/systems/support/helpdesk_page.ex
+++ b/core/systems/support/helpdesk_page.ex
@@ -6,6 +6,7 @@ defmodule Systems.Support.HelpdeskPage do
user
end
+ @impl true
def mount(_params, _session, socket) do
{:ok, socket |> compose_child(:helpdesk_form)}
end
@@ -18,12 +19,6 @@ defmodule Systems.Support.HelpdeskPage do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def render(assigns) do
~H"""
diff --git a/core/systems/support/overview_page.ex b/core/systems/support/overview_page.ex
index 3452594eb..36daaac01 100644
--- a/core/systems/support/overview_page.ex
+++ b/core/systems/support/overview_page.ex
@@ -17,12 +17,6 @@ defmodule Systems.Support.OverviewPage do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def render(assigns) do
~H"""
diff --git a/core/systems/support/ticket_page.ex b/core/systems/support/ticket_page.ex
index 738e53d36..1c901416d 100644
--- a/core/systems/support/ticket_page.ex
+++ b/core/systems/support/ticket_page.ex
@@ -12,6 +12,7 @@ defmodule Systems.Support.TicketPage do
Support.Public.get_ticket!(id)
end
+ @impl true
def mount(%{"id" => id}, _session, socket) do
{
:ok,
@@ -20,12 +21,6 @@ defmodule Systems.Support.TicketPage do
}
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
-
@impl true
def handle_event("close_ticket", _params, %{assigns: %{id: id}} = socket) do
Support.Public.close_ticket_by_id(id)
diff --git a/core/systems/test/page.ex b/core/systems/test/page.ex
index 8d9db4fb8..b410dfc59 100644
--- a/core/systems/test/page.ex
+++ b/core/systems/test/page.ex
@@ -2,7 +2,11 @@ defmodule Systems.Test.Page do
@moduledoc """
The page for testing the view model observations
"""
- use Systems.Content.Composer, :live_workspace
+ use CoreWeb, :live_view
+
+ on_mount({CoreWeb.Live.Hook.Base, __MODULE__})
+ on_mount({CoreWeb.Live.Hook.Model, __MODULE__})
+ on_mount({Systems.Observatory.LiveHook, __MODULE__})
alias Systems.Test
@@ -13,14 +17,19 @@ defmodule Systems.Test.Page do
@impl true
def mount(_params, _session, socket) do
- {:ok, socket |> assign(active_menu_item: nil)}
+ {
+ :ok,
+ socket
+ |> assign(
+ view_model_updated: 0,
+ active_menu_item: nil
+ )
+ }
end
- @impl true
- def handle_view_model_updated(socket), do: socket
-
- @impl true
- def handle_uri(socket), do: socket
+ def handle_view_model_updated(%{assigns: %{view_model_updated: view_model_updated}} = socket) do
+ socket |> assign(view_model_updated: "#{view_model_updated + 1}")
+ end
# data(model, :map)
@impl true
@@ -28,6 +37,7 @@ defmodule Systems.Test.Page do
~H"""
<%= @vm.title %>
<%= @vm.subtitle %>
+
view_model_updated: <%= @view_model_updated %>
"""
end
end
diff --git a/core/test/systems/advert/content_page_test.exs b/core/test/systems/advert/content_page_test.exs
index 001c726f6..c2879e248 100644
--- a/core/test/systems/advert/content_page_test.exs
+++ b/core/test/systems/advert/content_page_test.exs
@@ -2,8 +2,10 @@ defmodule Systems.Advert.ContentPageTest do
use CoreWeb.ConnCase, async: true
import Phoenix.ConnTest
import Phoenix.LiveViewTest
+ import Phoenix.Component, only: [assign: 2]
import ExUnit.Assertions
+ import Mock
alias Systems.Advert
@@ -11,18 +13,27 @@ defmodule Systems.Advert.ContentPageTest do
setup [:login_as_creator]
test "Default", %{conn: %{assigns: %{current_user: researcher}} = conn} do
- %{id: id} = Advert.Factories.create_advert(researcher, :accepted, 1)
+ branch =
+ Promox.new()
+ |> Promox.stub(Frameworks.Concept.Branch, :hierarchy, fn _ -> [] end)
- {:ok, _view, html} = live(conn, ~p"/advert/#{id}/content")
+ with_mock Systems.Project.LiveHook,
+ on_mount: fn _live_view_module, _params, _session, socket ->
+ {:cont, socket |> assign(branch: branch)}
+ end do
+ %{id: id} = Advert.Factories.create_advert(researcher, :accepted, 1)
- assert html =~ "
"
- assert html =~ "Settings"
+ {:ok, _view, html} = live(conn, ~p"/advert/#{id}/content")
- assert html =~ "
"
- assert html =~ "Criteria"
+ assert html =~ "
"
+ assert html =~ "Settings"
- assert html =~ "
"
- assert html =~ "Monitor"
+ assert html =~ "
"
+ assert html =~ "Criteria"
+
+ assert html =~ "
"
+ assert html =~ "Monitor"
+ end
end
end
end
diff --git a/core/test/systems/observatory/view_model_observe_test.exs b/core/test/systems/observatory/view_model_observe_test.exs
index 669191568..4b485b4ab 100644
--- a/core/test/systems/observatory/view_model_observe_test.exs
+++ b/core/test/systems/observatory/view_model_observe_test.exs
@@ -4,9 +4,7 @@ defmodule Systems.Observatory.ViewModelObserveTest do
import Phoenix.LiveViewTest
import Core.AuthTestHelpers
- alias Systems.{
- Test
- }
+ alias Systems.Test
describe "View model roundtrip" do
setup [:login_as_member]
@@ -16,6 +14,7 @@ defmodule Systems.Observatory.ViewModelObserveTest do
assert html =~ "John Doe"
assert html =~ "Age: 56 - Works at: The Basement"
+ assert html =~ "view_model_updated: 0"
end
test "View model update", %{conn: conn} do
@@ -24,7 +23,10 @@ defmodule Systems.Observatory.ViewModelObserveTest do
model = Test.Public.get(1)
Test.Public.update(model, %{age: 57})
- assert render(view) =~ "Age: 57 - Works at: The Basement"
+ html = render(view)
+
+ assert html =~ "Age: 57 - Works at: The Basement"
+ assert html =~ "view_model_updated: 1"
end
end
end
diff --git a/core/test/systems/support/overview_page_test.exs b/core/test/systems/support/overview_page_test.exs
index 4b394585b..d99cbb195 100644
--- a/core/test/systems/support/overview_page_test.exs
+++ b/core/test/systems/support/overview_page_test.exs
@@ -7,9 +7,7 @@ defmodule Systems.Support.OverviewPageTest do
setup [:login_as_member]
test "deny access to non-admin", %{conn: conn} do
- assert_error_sent(403, fn ->
- live(conn, ~p"/support/ticket")
- end)
+ assert {:error, {:redirect, %{to: "/access_denied"}}} = live(conn, ~p"/support/ticket")
end
end
diff --git a/core/test/systems/support/ticket_page_test.exs b/core/test/systems/support/ticket_page_test.exs
index b3d427371..73ce53102 100644
--- a/core/test/systems/support/ticket_page_test.exs
+++ b/core/test/systems/support/ticket_page_test.exs
@@ -9,9 +9,8 @@ defmodule Systems.Support.TicketPageTest do
test "deny access to non-admin", %{conn: conn} do
ticket = Factories.insert!(:helpdesk_ticket)
- assert_error_sent(403, fn ->
- live(conn, ~p"/support/ticket/#{ticket.id}")
- end)
+ assert {:error, {:redirect, %{to: "/access_denied"}}} =
+ live(conn, ~p"/support/ticket/#{ticket.id}")
end
end
diff --git a/core/test/test_helper.exs b/core/test/test_helper.exs
index 43a493d6b..0345f6488 100644
--- a/core/test/test_helper.exs
+++ b/core/test/test_helper.exs
@@ -1,3 +1,7 @@
+require Promox
+
+Promox.defmock(for: Frameworks.Concept.Branch)
+
ExUnit.start()
Ecto.Adapters.SQL.Sandbox.mode(Core.Repo, :manual)