From a1093dd19033661b83db83702563b3b59ecb7d57 Mon Sep 17 00:00:00 2001 From: Mayel de Borniol Date: Tue, 8 Oct 2024 17:48:02 +0100 Subject: [PATCH] https://github.com/bonfire-networks/bonfire-app/issues/1043 --- lib/components/core_components.swiftui.ex | 98 +++---- lib/layout/layout_view.ex | 2 +- lib/static_generator/static_generator.ex | 2 +- lib/themes/default/app.swiftui.ex | 114 ++++---- lib/ui_common.ex | 10 +- lib/web.ex | 78 +----- lib/web_native.ex | 302 +++++++++++----------- lib/web_native_original.exs | 181 +++++++++++++ 8 files changed, 449 insertions(+), 338 deletions(-) create mode 100644 lib/web_native_original.exs diff --git a/lib/components/core_components.swiftui.ex b/lib/components/core_components.swiftui.ex index da16bc16..f49def6d 100644 --- a/lib/components/core_components.swiftui.ex +++ b/lib/components/core_components.swiftui.ex @@ -1,5 +1,4 @@ if Code.ensure_loaded?(LiveViewNative.Component) do - defmodule Bonfire.UI.Common.CoreComponents.SwiftUI do @moduledoc """ Provides core UI components built for SwiftUI. @@ -23,57 +22,64 @@ if Code.ensure_loaded?(LiveViewNative.Component) do import LiveViewNative.LiveForm.Component - - @doc """ - A special component that allows users to inject dynamic live components - """ - attr :module, :atom, required: true - attr :id, :string, required: true - attr :function, :atom, default: :render - slot :default - def stateful_component(assigns) do - # TEMP: until LVN supports live components - stateless_component(with true <- module_enabled?(assigns[:module]), - {:ok, assigns} <- assigns - |> assign(module_default_assigns(assigns[:module])) - |> assign(assigns) # again so we override defaults - |> assign_new(:myself, fn -> nil end) - |> assign_new(:streams, fn -> nil end) - |> apply(assigns[:module], :mount, [...]), - {:ok, assigns} <- apply(assigns[:module], :update, [assigns, assigns]) do - assign_new(assigns, :function, fn -> :render end) - |> debug("assi") - else e -> + @doc """ + A special component that allows users to inject dynamic live components + """ + attr :module, :atom, required: true + attr :id, :string, required: true + attr :function, :atom, default: :render + slot :default + + def stateful_component(assigns) do + # TEMP: until LVN supports live components + stateless_component( + with true <- module_enabled?(assigns[:module]), + {:ok, assigns} <- + assigns + |> assign(module_default_assigns(assigns[:module])) + # again so we override defaults + |> assign(assigns) + |> assign_new(:myself, fn -> nil end) + |> assign_new(:streams, fn -> nil end) + |> apply(assigns[:module], :mount, [...]), + {:ok, assigns} <- apply(assigns[:module], :update, [assigns, assigns]) do + assign_new(assigns, :function, fn -> :render end) + |> debug("assi") + else + e -> error(e) + assigns |> debug("erssi") |> assign(:module, __MODULE__) |> assign(:function, :error_msg) |> assign(:text, "Could not render component") - end - ) - # TODO: when LVN supports live components - # ~LVN""" - # <%= render_slot(@default) %> - # """ - end - - @doc """ - A special component that allows users to inject dynamic function components - """ - attr :module, :atom, default: nil - attr :function, :atom, default: :render - attr :id, :string, default: nil - slot :default - def stateless_component(assigns) do - ~LVN""" - <%= Phoenix.LiveView.TagEngine.component( - &apply(@module || __MODULE__, @function || :render, [&1]), - assigns, - {__ENV__.module, __ENV__.function, __ENV__.file, __ENV__.line} - ) %> - """ - end + end + ) + + # TODO: when LVN supports live components + # ~LVN""" + # <%= render_slot(@default) %> + # """ + end + + @doc """ + A special component that allows users to inject dynamic function components + """ + attr :module, :atom, default: nil + attr :function, :atom, default: :render + attr :id, :string, default: nil + slot :default + + def stateless_component(assigns) do + ~LVN""" + <%= Phoenix.LiveView.TagEngine.component( + &apply(@module || __MODULE__, @function || :render, [&1]), + assigns, + {__ENV__.module, __ENV__.function, __ENV__.file, __ENV__.line} + ) %> + """ + end @doc """ Renders an input with label and error messages. diff --git a/lib/layout/layout_view.ex b/lib/layout/layout_view.ex index 28ff4d4e..a0ed2161 100644 --- a/lib/layout/layout_view.ex +++ b/lib/layout/layout_view.ex @@ -1,7 +1,7 @@ defmodule Bonfire.UI.Common.LayoutView do use Bonfire.UI.Common.Web, :layout - embed_templates "*.html" + use_if_enabled(Bonfire.UI.Common.Web.Native, :layout) # def render("live.html", assigns) do # Bonfire.UI.Common.LayoutLive.render(assigns) diff --git a/lib/static_generator/static_generator.ex b/lib/static_generator/static_generator.ex index 2d08fa49..bb96a1e0 100644 --- a/lib/static_generator/static_generator.ex +++ b/lib/static_generator/static_generator.ex @@ -39,7 +39,7 @@ defmodule Bonfire.UI.Common.StaticGenerator do urls |> Enum.reject(fn url -> - full_path = Path.join([dest, url, "index.html"]) + full_path = Path.join([dest, url, "index.#{opts[:ext] || "html"}"]) file_exists_not_expired(full_path) end) |> debug("expired or doesn't exist") diff --git a/lib/themes/default/app.swiftui.ex b/lib/themes/default/app.swiftui.ex index 48fd3954..35ef14f6 100644 --- a/lib/themes/default/app.swiftui.ex +++ b/lib/themes/default/app.swiftui.ex @@ -1,70 +1,70 @@ -defmodule Bonfire.UI.Common.Themes.Default.App.SwiftUI do - use LiveViewNative.Stylesheet, :swiftui +if Bonfire.Common.Extend.module_enabled?(LiveViewNative) do + defmodule Bonfire.UI.Common.Themes.Default.App.SwiftUI do + use LiveViewNative.Stylesheet, :swiftui - # Add your styles here - # Refer to your client's documentation on what the proper syntax - # is for defining rules within classes - ~SHEET""" - - """ + # Add your styles here + # Refer to your client's documentation on what the proper syntax + # is for defining rules within classes + ~SHEET""" - def class("main_header") do - ~RULES""" - toolbar(content: :toolbar) - navigationTitle(:title) - toolbarTitleMenu(content: :content) - navigationBarTitleDisplayMode(.inline) - toolbarBackgroundVisibility(.visible, for: .navigationBar) - toolbarBackground(.ultraThinMaterial, for: .navigationBar) """ - end - def class("simple_header") do - ~RULES""" - toolbar(content: :toolbar) - navigationTitle(:title) - navigationBarTitleDisplayMode(.inline) - """ - end + def class("main_header") do + ~RULES""" + toolbar(content: :toolbar) + navigationTitle(:title) + toolbarTitleMenu(content: :content) + navigationBarTitleDisplayMode(.inline) + toolbarBackgroundVisibility(.visible, for: .navigationBar) + toolbarBackground(.ultraThinMaterial, for: .navigationBar) + """ + end - def class("detents:" <> props) do - [height, size] = String.split(props, ":") + def class("simple_header") do + ~RULES""" + toolbar(content: :toolbar) + navigationTitle(:title) + navigationBarTitleDisplayMode(.inline) + """ + end - # {height, _} = Integer.parse(height) + def class("detents:" <> props) do + [height, size] = String.split(props, ":") - ~RULES""" - presentationDetents([.{height}, .{size}]) - """ - end + # {height, _} = Integer.parse(height) - def class("dragindicator:" <> props) do - - ~RULES""" - presentationDragIndicator(.{props}) - """ - end + ~RULES""" + presentationDetents([.{height}, .{size}]) + """ + end + def class("dragindicator:" <> props) do + ~RULES""" + presentationDragIndicator(.{props}) + """ + end - def class("ultrathinmaterial") do - ~RULES""" - presentationBackground(.ultraThinMaterial) - """ - end + def class("ultrathinmaterial") do + ~RULES""" + presentationBackground(.ultraThinMaterial) + """ + end - # If you need to have greater control over how your style rules are created - # you can use the function defintion style which is more verbose but allows - # for more fine-grained controled - # - # This example shows what is not possible within the more concise ~SHEET - # use `` allows for a setting - # of both the `width` and `height` values. + # If you need to have greater control over how your style rules are created + # you can use the function defintion style which is more verbose but allows + # for more fine-grained controled + # + # This example shows what is not possible within the more concise ~SHEET + # use `` allows for a setting + # of both the `width` and `height` values. - # def class("frame:" <> dims) do - # [width] = Regex.run(~r/w(\d+)/, dims, capture: :all_but_first) - # [height] = Regex.run(~r/h(\d+)/, dims, capture: :all_but_first) + # def class("frame:" <> dims) do + # [width] = Regex.run(~r/w(\d+)/, dims, capture: :all_but_first) + # [height] = Regex.run(~r/h(\d+)/, dims, capture: :all_but_first) - # ~RULES""" - # frame(width: {width}, height: {height}) - # """ - # end -end \ No newline at end of file + # ~RULES""" + # frame(width: {width}, height: {height}) + # """ + # end + end +end diff --git a/lib/ui_common.ex b/lib/ui_common.ex index 029a8d4b..eca1d9aa 100644 --- a/lib/ui_common.ex +++ b/lib/ui_common.ex @@ -1334,7 +1334,8 @@ defmodule Bonfire.UI.Common do Inserts one or many items in an existing stream. See `Phoenix.LiveView.stream_insert/4` for opts. """ - def maybe_stream_insert(%{assigns: %{streams: streams}} = socket, name, items, _opts) when is_nil(streams) or streams==%{} do + def maybe_stream_insert(%{assigns: %{streams: streams}} = socket, name, items, _opts) + when is_nil(streams) or streams == %{} do error( assigns(socket), "Invalid stream '#{name}' to render data in. Will set as regular assign instead" @@ -1438,17 +1439,20 @@ defmodule Bonfire.UI.Common do def component_props(module) do component_attr(module, :prop) end + def component_data(module) do component_attr(module, :data) end + defp component_attr(module, key) do apply(Bonfire.UI.Social.FeedLive, :__info__, [:attributes]) |> Keyword.get_values(key) - |> Enum.flat_map(&(&1)) + |> Enum.flat_map(& &1) end def module_default_assigns(module) do - for %{name: name, opts: opts} <- component_props(module) ++ component_data(module), Keyword.has_key?(opts, :default) do + for %{name: name, opts: opts} <- component_props(module) ++ component_data(module), + Keyword.has_key?(opts, :default) do {name, opts[:default]} end end diff --git a/lib/web.ex b/lib/web.ex index 0c7dbe64..33a681b6 100755 --- a/lib/web.ex +++ b/lib/web.ex @@ -86,22 +86,6 @@ defmodule Bonfire.UI.Common.Web do # Include shared imports and aliases for views # import Surface unquote(live_view_helpers()) - - ## support LVN layout - # use_if_enabled(LiveViewNative.Layouts, env: Application.compile_env!(:bonfire, :env)) - # use_if_enabled(Bonfire.UI.Common.Web.Native.layout(opts)) - defmodule SwiftUI do - use LiveViewNative.Component, format: :swiftui - unquote(live_view_helpers()) - unquote(Bonfire.UI.Common.Web.Native.helpers(:swiftui)) - import LiveViewNative.Component, only: [csrf_token: 1] - import LiveViewNative.Renderer - "*.swiftui" - |> LiveViewNative.Renderer.embed_templates() - |> IO.inspect(label: "embed_templates layout") - end - - end end @@ -144,7 +128,6 @@ defmodule Bonfire.UI.Common.Web do # ) # end - def live_view(caller, opts \\ []) do # IO.inspect(live_view: opts) # maybe_put_layout(opts, :live) @@ -175,9 +158,6 @@ defmodule Bonfire.UI.Common.Web do embed_templates("#{template_name}.mjml", suffix: "_mjml") embed_templates("#{template_name}.text", suffix: "_text") - # use_if_enabled(LiveViewNative.LiveView, unquote(native_opts())) - # Bonfire.UI.Common.Web.Native.live_view(template_name: template_name) - # on_mount(PhoenixProfiler) end end @@ -198,9 +178,6 @@ defmodule Bonfire.UI.Common.Web do embed_templates("#{template_name}.mjml", suffix: "_mjml") embed_templates("#{template_name}.text", suffix: "_text") - # use_if_enabled(LiveViewNative.Component) # TEMP workaround for `Not yet implemented. Please convert to a LiveViewNative.Component for now` - # #use_if_enabled(LiveViewNative.LiveComponent) - # unquote(source_inspector()) end end @@ -218,8 +195,6 @@ defmodule Bonfire.UI.Common.Web do embed_templates("#{template_name}.mjml", suffix: "_mjml") embed_templates("#{template_name}.text", suffix: "_text") - # use_if_enabled(LiveViewNative.Component) - # unquote(source_inspector()) end end @@ -338,7 +313,7 @@ defmodule Bonfire.UI.Common.Web do end end - defp live_view_helpers do + def live_view_helpers do quote do unquote(live_view_basic_helpers()) @@ -553,8 +528,6 @@ defmodule Bonfire.UI.Common.Web do unquote(surface_helpers()) - # unquote(Bonfire.UI.Common.Web.Native.live_view(template_name: template_name)) - alias Bonfire.UI.Common.LivePlugs # on_mount(PhoenixProfiler) @@ -586,26 +559,6 @@ defmodule Bonfire.UI.Common.Web do embed_templates("#{template_name}.mjml", suffix: "_mjml") embed_templates("#{template_name}.text", suffix: "_text") - # use_if_enabled(LiveViewNative.LiveView, unquote(native_opts())) - # unquote(Bonfire.UI.Common.Web.Native.live_view(format: :swiftui)) - # Bonfire.UI.Common.Web.Native.live_view(format: :swiftui, template_name: template_name) - # if module_enabled?(LiveViewNative) do - use LiveViewNative.LiveView, format: :swiftui, formats: [:swiftui], layouts: [ - swiftui: {Bonfire.UI.Common.LayoutView.SwiftUI, :app} - ] - defmodule SwiftUI do - # use Phoenix.LiveView, unquote(opts) - use LiveViewNative.Component, format: :swiftui - unquote(live_view_helpers()) - unquote(Bonfire.UI.Common.Web.Native.helpers(:swiftui)) - import LiveViewNative.Renderer - unquote("#{Bonfire.UI.Common.filename_for_module_template(caller.module)}.swiftui") - # "*.swiftui" - |> LiveViewNative.Renderer.embed_templates(name: :render, root: unquote(Path.dirname(caller.file))) - |> IO.inspect(label: "embed_templates #{template_name}") - end - # end - unquote(surface_helpers()) alias Bonfire.UI.Common.LivePlugs @@ -645,23 +598,6 @@ defmodule Bonfire.UI.Common.Web do embed_templates("#{template_name}.mjml", suffix: "_mjml") embed_templates("#{template_name}.text", suffix: "_text") - - # use_if_enabled(LiveViewNative.Component) # TEMP workaround for `Not yet implemented. Please convert to a LiveViewNative.Component for now` - # #use_if_enabled(LiveViewNative.LiveComponent) - - # use_if_enabled LiveViewNative.Component, - # format: :swiftui, - # as: :render_native - - # LiveViewNative.Renderer.embed_templates("*.swiftui", format: :swiftui, name: :render_native) - - use LiveViewNative.Component, format: :swiftui - unquote(Bonfire.UI.Common.Web.Native.helpers(:swiftui)) - import LiveViewNative.Component, only: [csrf_token: 1] - import LiveViewNative.Renderer - unquote("#{Bonfire.UI.Common.filename_for_module_template(caller.module)}.swiftui") - |> LiveViewNative.Renderer.embed_templates(name: :render) - |> IO.inspect(label: "embed_templates layout") end end @@ -684,18 +620,6 @@ defmodule Bonfire.UI.Common.Web do embed_templates("#{template_name}.mjml", suffix: "_mjml") embed_templates("#{template_name}.text", suffix: "_text") - - use LiveViewNative.Component, format: :swiftui - unquote(Bonfire.UI.Common.Web.Native.helpers(:swiftui)) - import LiveViewNative.Component, only: [csrf_token: 1] - import LiveViewNative.Renderer - unquote("#{Bonfire.UI.Common.filename_for_module_template(caller.module)}.swiftui") - |> LiveViewNative.Renderer.embed_templates(name: :render) - |> IO.inspect(label: "embed_templates layout") - - # use_if_enabled LiveViewNative.Component, - # format: :swiftui, - # as: :render_native end end diff --git a/lib/web_native.ex b/lib/web_native.ex index c09145a5..16c277ba 100644 --- a/lib/web_native.ex +++ b/lib/web_native.ex @@ -1,193 +1,189 @@ -defmodule Bonfire.UI.Common.Web.Native do - @moduledoc """ - The entrypoint for defining your native interfaces, such - as components, render components, layouts, and live views. - - This can be used in your application as: - - use Bonfire.UI.Common.Web.Native, :live_view - - The definitions below will be executed for every - component, so keep them short and clean, focused - on imports, uses and aliases. - - Do NOT define functions inside the quoted expressions - below. Instead, define additional modules and import - those modules here. - """ - import Untangle - - def native_formats, - do: [ - # :swiftui - ] - - def native_opts, - do: [ - formats: native_formats(), - layouts: [ - swiftui: {Bonfire.UI.Common.LayoutView.SwiftUI, :app} +if Bonfire.Common.Extend.module_enabled?(LiveViewNative) do + defmodule Bonfire.UI.Common.Web.Native do + @moduledoc """ + The entrypoint for defining your native interfaces, such as components, render components, layouts, and live views. + + This is a highly modified version of the one generated by LiveViewNative (the original is kept around for reference in `web_native_original.exs`) + """ + import Untangle + alias Bonfire.UI.Common.Web + + def native_formats, + do: [ + # :swiftui ] - ] - @doc ~S''' - Set up an existing LiveView module for use with LiveView Native + def native_opts, + do: [ + formats: native_formats(), + layouts: [ + swiftui: {Bonfire.UI.Common.LayoutView.SwiftUI, :app} + ] + ] - defmodule MyAppWeb.HomeLive do - use Bonfire.UI.Common.Web, :live_view - use Bonfire.UI.Common.Web.Native, :live_view - end + @doc ~S''' + Set up an existing LiveView module for use with LiveView Native - An `on_mount` callback will be injected that will negotiate - the inbound connection content type. If it is a LiveView Native - type the `render/1` will be delegated to the format-specific - render component. - ''' - def live_view(opts \\ []) do - quote do - use LiveViewNative.LiveView, unquote(debug(opts ++ native_opts())) - # formats: unquote(Bonfire.UI.Common.Web.native_formats()) - # # layouts: [ - # # swiftui: {BonfireUmbrellaWeb.Layouts.SwiftUI, :app} - # # ] - - # require LiveViewNative.Renderer - # LiveViewNative.Renderer.embed_templates(IO.inspect("#{unquote(opts)[:template_name] || "*"}.*", label: "embed_templates_*")) - # |> IO.inspect(label: "embed_templates") - - # unquote(Bonfire.UI.Common.Web.verified_routes()) - end - end + defmodule MyAppWeb.HomeLive do + use Bonfire.UI.Common.Web, :live_view # or :surface_live_view + use_if_enabled Bonfire.UI.Common.Web.Native, :view + end - @doc ~S''' - Set up a module as a LiveView Native format-specific render component + An `on_mount` callback will be injected that will negotiate + the inbound connection content type. If it is a LiveView Native + type the `render/1` will be delegated to the format-specific + render component. + ''' + def view(caller, opts \\ []) do + opts = + opts + |> Keyword.put_new(:formats, [:swiftui]) + |> Keyword.put_new(:layouts, + swiftui: {Bonfire.UI.Common.LayoutView.SwiftUI, :app} + ) + |> Keyword.take([:formats, :layouts]) - defmodule MyAppWeb.HomeLive.SwiftUI do - use Bonfire.UI.Common.Web.Native, [:render_component, format: :swiftui] + quote do + # first define the on_mount delegation + use LiveViewNative.LiveView, unquote(opts) - def render(assigns, _interface) do - ~LVN""" - Hello, world! - """ + # then define a format-specific render component that uses `embed_templates` to render `*.swiftui.neex` file(s) + defmodule SwiftUI do + unquote(Web.live_view_helpers()) + + unquote(stateless_component(caller, format: :swiftui)) end end - ''' - def render_component(opts) do - opts = - opts - |> Keyword.take([:format]) - |> Keyword.put(:as, :render) - - quote do - use LiveViewNative.Component, unquote(opts) - - unquote(helpers(opts[:format])) end - end - @doc ~S''' - Set up a module as a LiveView Native Component + @doc ~S''' + Set up an existing module as a LiveView Native Component - defmodule MyAppWeb.Components.CustomSwiftUI do - use Bonfire.UI.Common.Web.Native, [:component, format: :swiftui] + defmodule MyApp.MyComponent do + use Bonfire.UI.Common.Web, :stateless_component + use_if_enabled Bonfire.UI.Common.Web.Native, :stateless_component - attr :msg, :string, :required - def home_text(assigns) do - ~LVN""" - @msg - """ + # then put your SwiftUI component code in `my_component.swiftui.neex` next to the module end - end - LiveView Native Components are identical to Phoenix Components. Please - refer to the `Phoenix.Component` documentation for more information. - ''' - def component(opts \\ []) do - opts = Keyword.take(opts, [:format, :root, :as]) + LiveView Native Components are identical to Phoenix Components. Please + refer to the `Phoenix.Component` documentation for more information. + ''' + def stateless_component(caller, opts \\ []) do + opts = + opts + |> Keyword.put_new(:format, :swiftui) + |> Keyword.take([:format]) + |> Keyword.put(:as, :render) - quote do - use LiveViewNative.Component, unquote(opts) - - unquote(helpers(opts[:format])) - end - end - - @doc ~S''' - Set up a module as a LiveView Native Layout Component + quote do + use LiveViewNative.Component, unquote(opts) - defmodule MyAppWeb.Layouts.SwiftUI do - use Bonfire.UI.Common.Web.Native, [:layout, format: :swiftui] + unquote(helpers(opts[:format])) + import LiveViewNative.Renderer - embed_templates "layouts_swiftui/*" + unquote( + "#{Bonfire.UI.Common.filename_for_module_template(caller.module)}.#{opts[:format]}" + ) + |> LiveViewNative.Renderer.embed_templates(name: :render) + |> IO.inspect(label: "embed_templates layout") end - ''' - def layout(opts \\ []) do - opts = Keyword.take(opts, [:format, :root]) + end - quote do - use LiveViewNative.Component, unquote(opts) + def stateful_component(caller, opts \\ []) do + # TODO: change when LVN supports live components + stateless_component(caller, opts) + end - import LiveViewNative.Component, only: [csrf_token: 1] + @doc ~S''' + Set up a module as a LiveView Native Layout Component - unquote(helpers(opts[:format])) - end - end + defmodule MyAppWeb.Layouts.SwiftUI do + use_if_enabled Bonfire.UI.Common.Web.Native, [:layout, format: :swiftui] + end + ''' + def layout(caller, opts \\ []) do + opts = + opts + |> Keyword.put_new(:format, :swiftui) + |> Keyword.take([:format, :root]) - def helpers(format) do - gettext_quoted = quote do - import Bonfire.Common.Localise.Gettext - end + defmodule SwiftUI do + use LiveViewNative.Component, unquote(opts) - plugin = LiveViewNative.fetch_plugin!(format) + unquote(Web.live_view_helpers()) + unquote(helpers(opts[:format])) + import LiveViewNative.Component, only: [csrf_token: 1] + import LiveViewNative.Renderer - plugin_component_quoted = - try do - Code.ensure_compiled!(plugin.component) + unquote("*.#{opts[:format]}") + |> LiveViewNative.Renderer.embed_templates() + |> IO.inspect(label: "embed_templates layout") + end + end + end + def helpers(format) do + gettext_quoted = quote do - import unquote(plugin.component) + import Bonfire.Common.Localise.Gettext end - rescue - _ -> nil - end - live_form_quoted = - quote do - import LiveViewNative.LiveForm.Component - end + plugin = LiveViewNative.fetch_plugin!(format) - core_component_module = - Module.concat([Bonfire.UI.Common, CoreComponents, plugin.module_suffix]) + plugin_component_quoted = + try do + Code.ensure_compiled!(plugin.component) - core_component_quoted = - try do - Code.ensure_compiled!(core_component_module) + quote do + import unquote(plugin.component) + end + rescue + _ -> nil + end + live_form_quoted = quote do - import unquote(core_component_module) + import LiveViewNative.LiveForm.Component end - rescue - _ -> nil - end - [ - gettext_quoted, - plugin_component_quoted, - live_form_quoted, - core_component_quoted, - Bonfire.UI.Common.Web.verified_routes() - ] - end + core_component_module = + Module.concat([Bonfire.UI.Common, CoreComponents, plugin.module_suffix]) - @doc """ - When used, dispatch to the appropriate controller/view/etc. - """ - defmacro __using__([which | opts]) when is_atom(which) do - apply(__MODULE__, which, [opts]) - end + core_component_quoted = + try do + Code.ensure_compiled!(core_component_module) - defmacro __using__(which) when is_atom(which) do - apply(__MODULE__, which, []) + quote do + import unquote(core_component_module) + end + rescue + _ -> nil + end + + [ + gettext_quoted, + plugin_component_quoted, + live_form_quoted, + core_component_quoted, + Bonfire.UI.Common.Web.verified_routes() + ] + end + + @doc """ + When used, dispatch to the appropriate controller/view/etc. + """ + defmacro __using__([which | opts]) when is_atom(which) do + apply(__MODULE__, which, [__CALLER__, opts]) + end + + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, [__CALLER__]) + end + + defmacro __using__({which, opts}) when is_atom(which) and is_list(opts) do + apply(__MODULE__, which, [__CALLER__, opts]) + end end end diff --git a/lib/web_native_original.exs b/lib/web_native_original.exs new file mode 100644 index 00000000..c86da8df --- /dev/null +++ b/lib/web_native_original.exs @@ -0,0 +1,181 @@ +if Bonfire.Common.Extend.module_enabled?(LiveViewNative) do + defmodule Bonfire.UI.Common.Web.Native.Original do + @moduledoc """ + The entrypoint for defining your native interfaces, such + as components, render components, layouts, and live views. + + This can be used in your application as: + + use Bonfire.UI.Common.Web.Native , :live_view + + The definitions below will be executed for every + component, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define additional modules and import + those modules here. + """ + + import Bonfire.UI.Common.Web, only: [verified_routes: 0] + + @doc ~S''' + Set up an existing LiveView module for use with LiveView Native + + defmodule MyAppWeb.HomeLive do + use MyAppWeb, :live_view + use MyAppNative, :live_view + end + + An `on_mount` callback will be injected that will negotiate + the inbound connection content type. If it is a LiveView Native + type the `render/1` will be delegated to the format-specific + render component. + ''' + def live_view() do + quote do + use LiveViewNative.LiveView, + formats: [ + :swiftui + ], + layouts: [ + swiftui: {Bonfire.UI.Common.Web.Layouts.SwiftUI, :app} + ] + + unquote(verified_routes()) + end + end + + @doc ~S''' + Set up a module as a LiveView Native format-specific render component + + defmodule MyAppWeb.HomeLive.SwiftUI do + use MyAppNative, [:render_component, format: :swiftui] + + def render(assigns, _interface) do + ~LVN""" + Hello, world! + """ + end + end + ''' + def render_component(opts) do + opts = + opts + |> Keyword.take([:format]) + |> Keyword.put(:as, :render) + + quote do + use LiveViewNative.Component, unquote(opts) + + unquote(helpers(opts[:format])) + end + end + + @doc ~S''' + Set up a module as a LiveView Native Component + + defmodule MyAppWeb.Components.CustomSwiftUI do + use MyAppNative, [:component, format: :swiftui] + + attr :msg, :string, :required + def home_textk(assigns) do + ~LVN""" + @msg + """ + end + end + + LiveView Native Components are identical to Phoenix Components. Please + refer to the `Phoenix.Component` documentation for more information. + ''' + def component(opts) do + opts = Keyword.take(opts, [:format, :root, :as]) + + quote do + use LiveViewNative.Component, unquote(opts) + + unquote(helpers(opts[:format])) + end + end + + @doc ~S''' + Set up a module as a LiveView Natve Layout Component + + defmodule MyAppWeb.Layouts.SwiftUI do + use MyAppNative, [:layout, format: :swiftui] + + embed_templates "layouts_swiftui/*" + end + ''' + def layout(opts) do + opts = Keyword.take(opts, [:format, :root]) + + quote do + use LiveViewNative.Component, unquote(opts) + + import LiveViewNative.Component, only: [csrf_token: 1] + + unquote(helpers(opts[:format])) + end + end + + defp helpers(format) do + gettext_quoted = + quote do + import Bonfire.UI.Common.Web.Gettext + end + + plugin = LiveViewNative.fetch_plugin!(format) + + plugin_component_quoted = + try do + Code.ensure_compiled!(plugin.component) + + quote do + import unquote(plugin.component) + end + rescue + _ -> nil + end + + live_form_quoted = + quote do + import LiveViewNative.LiveForm.Component + end + + core_component_module = + Module.concat([Bonfire.UI.Common.Web, CoreComponents, plugin.module_suffix]) + + core_component_quoted = + try do + Code.ensure_compiled!(core_component_module) + + quote do + import unquote(core_component_module) + end + rescue + _ -> nil + end + + [ + gettext_quoted, + plugin_component_quoted, + live_form_quoted, + core_component_quoted, + verified_routes() + ] + end + + @doc """ + When used, dispatch to the appropriate controller/view/etc. + """ + defmacro __using__([which | opts]) when is_atom(which) do + apply(__MODULE__, which, [opts]) + end + + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end + end +end