Packages
phoenix_kit
1.7.36
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/phoenix_kit_web/dashboard/context_provider.ex
defmodule PhoenixKitWeb.Dashboard.ContextProvider do
@moduledoc """
LiveView on_mount hook for loading dashboard contexts.
This module provides an on_mount hook that loads available contexts for the
current user and sets socket assigns for the context selector.
## Usage
Add to your live_session:
live_session :dashboard,
on_mount: [
{PhoenixKitWeb.Users.Auth, :phoenix_kit_ensure_authenticated_scope},
{PhoenixKitWeb.Dashboard.ContextProvider, :default}
]
## Assigns Set (Single Selector - Legacy)
- `@dashboard_contexts` - List of all contexts available to the user
- `@current_context` - The currently selected context (or nil)
- `@show_context_selector` - Boolean, true only if user has 2+ contexts
- `@context_selector_config` - The ContextSelector config struct
- `@current_contexts_map` - Map with single entry `%{key => current_context}` for badge compatibility
- `@dashboard_contexts_map` - Map with single entry `%{key => contexts}` for consistency
- `@show_context_selectors_map` - Map with single entry `%{key => show_selector}`
- `@dashboard_tabs` - (Optional) List of Tab structs when `tab_loader` is configured
Note: The `key` used in maps is `config.key` if set, otherwise `:default`.
This ensures context-aware badges work correctly with legacy single-selector configs.
## Assigns Set (Multiple Selectors)
- `@dashboard_contexts_map` - Map of key => list of contexts
- `@current_contexts_map` - Map of key => current context item
- `@show_context_selectors_map` - Map of key => boolean
- `@context_selector_configs` - List of all ContextSelector configs
Note: Legacy assigns are also set for backward compatibility when using
multiple selectors. The first selector's data populates the legacy assigns.
## Accessing in LiveViews
def mount(_params, _session, socket) do
# Single selector (legacy)
context = socket.assigns.current_context
# Multiple selectors
org = socket.assigns.current_contexts_map[:organization]
project = socket.assigns.current_contexts_map[:project]
if context do
items = MyApp.Items.list_for_context(context.id)
{:ok, assign(socket, items: items)}
else
{:ok, assign(socket, items: [])}
end
end
"""
import Phoenix.Component, only: [assign: 3]
alias PhoenixKit.Dashboard.ContextSelector
alias PhoenixKit.Dashboard.Tab
alias PhoenixKit.Users.Auth.Scope
@doc """
On mount hook that loads contexts and sets assigns.
## Options
- `:default` - Standard behavior, loads contexts for authenticated user
- `:optional` - Same as default, but doesn't require authentication
"""
def on_mount(:default, _params, session, socket) do
# Check if multi-selector is configured
if ContextSelector.multi_selector_enabled?() do
load_multi_selectors(socket, session)
else
# Legacy single selector
config = ContextSelector.get_config()
if config.enabled do
load_and_assign_contexts(socket, session, config)
else
{:cont, assign_disabled(socket)}
end
end
end
def on_mount(:optional, params, session, socket) do
on_mount(:default, params, session, socket)
end
# Private functions
defp load_and_assign_contexts(socket, session, config) do
user_id = get_user_id(socket)
if user_id do
contexts = ContextSelector.load_contexts(user_id)
current_context = resolve_current_context(contexts, session, config)
show_selector = length(contexts) > 1
# Load context-specific tabs if tab_loader is configured
context_tabs = load_context_tabs(current_context, config)
# Determine the context key for current_contexts_map
# Use config.key if available, otherwise :default
context_key = config.key || :default
socket =
socket
|> assign(:dashboard_contexts, contexts)
|> assign(:current_context, current_context)
|> assign(:show_context_selector, show_selector)
|> assign(:context_selector_config, config)
# Also set current_contexts_map for context-aware badge compatibility
# This makes legacy single-selector mode consistent with multi-selector mode
|> assign(:current_contexts_map, build_contexts_map(context_key, current_context))
|> assign(:dashboard_contexts_map, %{context_key => contexts})
|> assign(:show_context_selectors_map, %{context_key => show_selector})
|> maybe_assign_tabs(context_tabs)
handle_empty_contexts(socket, contexts, config)
else
{:cont, assign_disabled(socket)}
end
end
defp build_contexts_map(_key, nil), do: %{}
defp build_contexts_map(key, context), do: %{key => context}
defp load_context_tabs(context, config) when config.tab_loader != nil do
ContextSelector.load_tabs(context)
end
defp load_context_tabs(_context, _config), do: nil
defp maybe_assign_tabs(socket, nil), do: socket
defp maybe_assign_tabs(socket, []), do: socket
defp maybe_assign_tabs(socket, tabs) when is_list(tabs) do
# Convert raw maps to Tab structs if needed
parsed_tabs =
Enum.map(tabs, fn
%Tab{} = tab -> tab
attrs when is_map(attrs) -> Tab.new!(attrs)
end)
assign(socket, :dashboard_tabs, parsed_tabs)
end
defp resolve_current_context(contexts, session, config) when is_list(contexts) do
session_id = session[config.session_key]
cond do
# No contexts available
contexts == [] ->
nil
# Try to find by session ID
session_id != nil ->
ContextSelector.find_by_id(contexts, session_id) || List.first(contexts)
# Fall back to first context
true ->
List.first(contexts)
end
end
defp handle_empty_contexts(socket, [], %{empty_behavior: {:redirect, path}}) do
{:halt, Phoenix.LiveView.redirect(socket, to: path)}
end
defp handle_empty_contexts(socket, [], %{empty_behavior: :hide}) do
socket = assign(socket, :show_context_selector, false)
{:cont, socket}
end
defp handle_empty_contexts(socket, [], %{empty_behavior: :show_empty}) do
{:cont, socket}
end
defp handle_empty_contexts(socket, _contexts, _config) do
{:cont, socket}
end
defp assign_disabled(socket) do
socket
|> assign(:dashboard_contexts, [])
|> assign(:current_context, nil)
|> assign(:show_context_selector, false)
|> assign(:context_selector_config, %ContextSelector{enabled: false})
# Multi-selector assigns (also disabled)
|> assign(:dashboard_contexts_map, %{})
|> assign(:current_contexts_map, %{})
|> assign(:show_context_selectors_map, %{})
|> assign(:context_selector_configs, [])
end
# ============================================================================
# Multi-Selector Support
# ============================================================================
defp load_multi_selectors(socket, session) do
user_id = get_user_id(socket)
if user_id do
configs = ContextSelector.get_all_configs()
ordered_configs = ContextSelector.order_by_dependencies(configs)
# Get stored context IDs from session
stored_ids = get_stored_context_ids(session)
# Load contexts for each selector in dependency order
{contexts_map, current_map} =
load_all_contexts(ordered_configs, user_id, stored_ids)
# Build show map
show_map =
Map.new(contexts_map, fn {key, contexts} ->
{key, length(contexts) > 1}
end)
# Set legacy assigns using first selector for backward compatibility
first_config = List.first(ordered_configs)
socket =
socket
# Multi-selector assigns
|> assign(:dashboard_contexts_map, contexts_map)
|> assign(:current_contexts_map, current_map)
|> assign(:show_context_selectors_map, show_map)
|> assign(:context_selector_configs, ordered_configs)
# Legacy assigns for backward compatibility
|> assign_legacy_from_first(first_config, contexts_map, current_map, show_map)
{:cont, socket}
else
{:cont, assign_disabled(socket)}
end
end
defp get_stored_context_ids(session) do
# Try multi-selector session key first
case session[ContextSelector.multi_session_key()] do
ids when is_map(ids) -> ids
_ -> %{}
end
end
defp load_all_contexts(configs, user_id, stored_ids) do
# Accumulator: {contexts_map, current_map}
Enum.reduce(configs, {%{}, %{}}, fn config, {ctx_map, cur_map} ->
# Get parent context if this is a dependent selector
parent_context =
if config.depends_on do
Map.get(cur_map, config.depends_on)
else
nil
end
# Load contexts for this selector
contexts = ContextSelector.load_contexts_for_config(config, user_id, parent_context)
# Resolve current context from session or default to first
current = resolve_context_for_key(contexts, config.key, stored_ids, config)
{
Map.put(ctx_map, config.key, contexts),
Map.put(cur_map, config.key, current)
}
end)
end
defp resolve_context_for_key(contexts, key, stored_ids, _config) do
stored_id = Map.get(stored_ids, to_string(key))
cond do
contexts == [] ->
nil
stored_id != nil ->
ContextSelector.find_by_id(contexts, stored_id) || List.first(contexts)
true ->
List.first(contexts)
end
end
defp assign_legacy_from_first(socket, nil, _contexts_map, _current_map, _show_map) do
# No configs at all - use disabled defaults
socket
|> assign(:dashboard_contexts, [])
|> assign(:current_context, nil)
|> assign(:show_context_selector, false)
|> assign(:context_selector_config, %ContextSelector{enabled: false})
end
defp assign_legacy_from_first(socket, first_config, contexts_map, current_map, show_map) do
key = first_config.key
contexts = Map.get(contexts_map, key, [])
current = Map.get(current_map, key)
show = Map.get(show_map, key, false)
socket
|> assign(:dashboard_contexts, contexts)
|> assign(:current_context, current)
|> assign(:show_context_selector, show)
|> assign(:context_selector_config, first_config)
end
defp get_user_id(socket) do
cond do
# Try phoenix_kit_current_scope first
scope = socket.assigns[:phoenix_kit_current_scope] ->
user = Scope.user(scope)
user && user.id
# Try current_user
user = socket.assigns[:current_user] ->
user.id
# Try phoenix_kit_current_user
user = socket.assigns[:phoenix_kit_current_user] ->
user.id
true ->
nil
end
end
end