diff --git a/.github/workflows/eval_in_wasm_build_test.yml b/.github/workflows/eval_in_wasm_build_test.yml index 93c3d91d..41f93c8d 100644 --- a/.github/workflows/eval_in_wasm_build_test.yml +++ b/.github/workflows/eval_in_wasm_build_test.yml @@ -54,6 +54,8 @@ jobs: cache-dependency-path: pnpm-lock.yaml - name: Install JS dependencies + env: + CXXFLAGS: "-std=c++20" run: pnpm install working-directory: . diff --git a/.github/workflows/hello_popcorn_build_test.yml b/.github/workflows/hello_popcorn_build_test.yml index 27827076..3635dab4 100644 --- a/.github/workflows/hello_popcorn_build_test.yml +++ b/.github/workflows/hello_popcorn_build_test.yml @@ -56,6 +56,8 @@ jobs: cache-dependency-path: pnpm-lock.yaml - name: Install JS dependencies + env: + CXXFLAGS: "-std=c++20" run: pnpm install working-directory: . diff --git a/.github/workflows/langtour_e2e_test.yml b/.github/workflows/langtour_e2e_test.yml index 8f6c5a4f..9c7bf5c5 100644 --- a/.github/workflows/langtour_e2e_test.yml +++ b/.github/workflows/langtour_e2e_test.yml @@ -64,6 +64,8 @@ jobs: working-directory: language-tour/elixir_tour - name: Install pnpm dependencies + env: + CXXFLAGS: "-std=c++20" run: pnpm install --frozen-lockfile - name: Build popcorn JS package diff --git a/.github/workflows/local_live_view_build.yml b/.github/workflows/local_live_view_build.yml index 7c1b7969..d94a729c 100644 --- a/.github/workflows/local_live_view_build.yml +++ b/.github/workflows/local_live_view_build.yml @@ -57,6 +57,8 @@ jobs: cache-dependency-path: pnpm-lock.yaml - name: Install JS dependencies + env: + CXXFLAGS: "-std=c++20" run: pnpm install working-directory: . diff --git a/.github/workflows/otp_js_e2e_test.yml b/.github/workflows/otp_js_e2e_test.yml index 975a5d9e..c6d2c878 100644 --- a/.github/workflows/otp_js_e2e_test.yml +++ b/.github/workflows/otp_js_e2e_test.yml @@ -65,6 +65,8 @@ jobs: cache-dependency-path: pnpm-lock.yaml - name: Install JS dependencies + env: + CXXFLAGS: "-std=c++20" run: pnpm install - name: Setup Playwright diff --git a/.github/workflows/popcorn_build_test.yml b/.github/workflows/popcorn_build_test.yml index 3db78ea1..e26120f8 100644 --- a/.github/workflows/popcorn_build_test.yml +++ b/.github/workflows/popcorn_build_test.yml @@ -70,6 +70,8 @@ jobs: - name: Install JS dependencies working-directory: . + env: + CXXFLAGS: "-std=c++20" run: pnpm install - name: Setup Playwright diff --git a/.github/workflows/popcorn_js_e2e_test.yml b/.github/workflows/popcorn_js_e2e_test.yml index 6cff0a56..c2c14457 100644 --- a/.github/workflows/popcorn_js_e2e_test.yml +++ b/.github/workflows/popcorn_js_e2e_test.yml @@ -45,6 +45,8 @@ jobs: cache-dependency-path: pnpm-lock.yaml - name: Install JS dependencies + env: + CXXFLAGS: "-std=c++20" run: pnpm install - name: Build popcorn JS package diff --git a/examples/local-lv-forms/assets/css/app.css b/examples/local-lv-forms/assets/css/app.css index ba5f5973..9402e3e5 100644 --- a/examples/local-lv-forms/assets/css/app.css +++ b/examples/local-lv-forms/assets/css/app.css @@ -124,7 +124,7 @@ font-size: 15px; } -.title { +h1 { padding: 30px 25px; font-size: 30px; font-weight: bold; diff --git a/examples/local-lv-forms/lib/form_demo_web/controllers/page_html/home.html.heex b/examples/local-lv-forms/lib/form_demo_web/controllers/page_html/home.html.heex index 46600b51..72391af1 100644 --- a/examples/local-lv-forms/lib/form_demo_web/controllers/page_html/home.html.heex +++ b/examples/local-lv-forms/lib/form_demo_web/controllers/page_html/home.html.heex @@ -1,5 +1,5 @@
LocalLiveView & Phoenix Integration: Forms Demo
+Explore seamless form handling and LocalLiveView integration.
diff --git a/examples/local-lv-forms/lib/form_demo_web/live/form_demo_live.ex b/examples/local-lv-forms/lib/form_demo_web/live/form_demo_live.ex index 03c6c471..d0ba6697 100644 --- a/examples/local-lv-forms/lib/form_demo_web/live/form_demo_live.ex +++ b/examples/local-lv-forms/lib/form_demo_web/live/form_demo_live.ex @@ -6,7 +6,7 @@ defmodule FormDemoWeb.FormDemoLive do
{error}
- <% end %> -{error}
+ <% end %> +No users yet - save one above
+ <% end %>Current temperature: {@temperature}°C
-Current temperature
+{@temperature}°C
+Country: {@country}
Country: {@country}
""" end def mount(_params, _session, socket) do - temperature = 25 - country = "Poland" - - socket = - socket - |> assign(:temperature, temperature) - |> assign(:country, country) - - {:ok, socket} + {:ok, socket |> assign(:temperature, 25) |> assign(:country, "Poland")} end def handle_event("inc_temperature", _params, socket) do @@ -31,4 +26,5 @@ defmodule ThermostatLive do def handle_event("dec_temperature", _params, socket) do {:noreply, update(socket, :temperature, &(&1 - 1))} end + end diff --git a/examples/local-lv-thermostat/local/lib_landing/thermostat_live_presentation.ex b/examples/local-lv-thermostat/local/lib_landing/thermostat_live_presentation.ex new file mode 100644 index 00000000..af9c54d1 --- /dev/null +++ b/examples/local-lv-thermostat/local/lib_landing/thermostat_live_presentation.ex @@ -0,0 +1,78 @@ +defmodule ThermostatLivePresentation do + use LocalLiveView + + defdelegate render(assigns), to: ThermostatLive + + def mount(params, session, socket) do + result = ThermostatLive.mount(params, session, socket) + {:ok, new_socket} = result + + Popcorn.Wasm.send_event("llv_presentation", %{ + block: nil, + event: "mount", + assigns: presentation_assigns(new_socket) + }) + + result + end + + def handle_event(event, params, socket) + when event in ["inc_temperature", "dec_temperature"] do + + effective_socket = + case socket.assigns[:_pending_cur] do + nil -> socket + pending -> assign(socket, Map.to_list(pending)) + end + + {:noreply, updated} = ThermostatLive.handle_event(event, params, effective_socket) + + Popcorn.Wasm.send_event("llv_presentation", %{ + block: event, + event: event, + assigns: presentation_assigns(updated) + }) + + new_pending = %{ + temperature: updated.assigns.temperature, + country: updated.assigns.country + } + + {:noreply, + socket + |> assign(:_pending_prev, socket.assigns[:_pending_cur]) + |> assign(:_pending_cur, new_pending)} + end + + def handle_info({:js_push, "llv_ack", _}, socket) do + case {socket.assigns[:_pending_prev], socket.assigns[:_pending_cur]} do + {%{temperature: t, country: c}, _} -> + {:noreply, + socket + |> assign(:temperature, t) + |> assign(:country, c) + |> assign(:_pending_prev, nil)} + + {nil, %{temperature: t, country: c}} -> + {:noreply, + socket + |> assign(:temperature, t) + |> assign(:country, c) + |> assign(:_pending_cur, nil)} + + _ -> + {:noreply, socket} + end + end + + def handle_event(event, params, socket) do + ThermostatLive.handle_event(event, params, socket) + end + + defp presentation_assigns(socket) do + %{ + temperature: socket.assigns.temperature, + country: socket.assigns.country + } + end +end diff --git a/examples/local-lv-thermostat/local/mix.exs b/examples/local-lv-thermostat/local/mix.exs index 539d0e84..32501c5c 100644 --- a/examples/local-lv-thermostat/local/mix.exs +++ b/examples/local-lv-thermostat/local/mix.exs @@ -7,6 +7,7 @@ defmodule Local.MixProject do version: "0.1.0", elixir: "~> 1.17", start_permanent: Mix.env() == :prod, + elixirc_paths: elixirc_paths(), deps: deps(), compilers: Mix.compilers(), aliases: aliases() @@ -24,6 +25,14 @@ defmodule Local.MixProject do ] end + defp elixirc_paths do + if System.get_env("LLV_LANDING_PATCH") == "true" do + ["lib", "lib_landing"] + else + ["lib"] + end + end + defp deps do [ {:local_live_view, path: "../../../local-live-view"} diff --git a/landing-page/astro.config.mjs b/landing-page/astro.config.mjs index b6832cb6..432d5a02 100644 --- a/landing-page/astro.config.mjs +++ b/landing-page/astro.config.mjs @@ -54,12 +54,14 @@ export default defineConfig({ "../examples/local-lv-thermostat/priv/static/assets/js/wasm", dir: "../examples/local-lv-thermostat", newBundleName: "local_thermostat.avm", + extraEnv: { LLV_LANDING_PATCH: "true" }, }), buildBundle({ wasmSrcPathDefault: "../examples/local-lv-forms/priv/static/assets/js/wasm", dir: "../examples/local-lv-forms", newBundleName: "local_forms.avm", + extraEnv: { LLV_LANDING_PATCH: "true" }, }), ], markdown: { diff --git a/landing-page/build-wasm.js b/landing-page/build-wasm.js index 77ca2d71..9230c1f9 100644 --- a/landing-page/build-wasm.js +++ b/landing-page/build-wasm.js @@ -4,9 +4,9 @@ import { dirname, join } from "path"; import { fileURLToPath } from "url"; /** - * @param {{ dir: string, wasmSrcPathDefault?: string, newBundleName: string }} options + * @param {{ dir: string, wasmSrcPathDefault?: string, newBundleName: string, extraEnv?: Record
+ + {title} +
+ {children} +
-
-defmodule FormDemoLocal do
- use LocalLiveView
- import Local.CoreComponents
-
- @impl true
- def render(assigns) do
- ~H"""
- <div class="bordered">
- <.form for={@form} id="my-form" phx-change="validate" phx-submit="save">
- <label>USERNAME</label>
- <.input type="text" field={@form[:username]} />
- <label>EMAIL</label>
- <.input type="text" field={@form[:email]} />
- <div class="centered">
- <button class="ghost-button" disabled={@disabled}>SAVE</button>
- </div>
- </.form>
- <div class="centered">
- <button class="ghost-button" phx-click="generate_random">GENERATE RANDOM</button>
- </div>
- </div>
- <%= for error <- @errors do %>
- <p style="color:red;">{error}</p>
- <% end %>
- <div class="bordered">
- <h1>[Local Runtime] User List:</h1>
- <ul>
- <%= for user <- @users do %>
- <li>Username: {user["username"]}, Email: {user["email"]}</li>
- <% end %>
- </ul>
- </div>
- """
- end
-
- @impl true
- def mount(_params, _session, socket) do
- send(self(), :sync)
- user = %{"email" => "", "username" => ""}
- {:ok, assign(socket, users: [], form: to_form(user), errors: [], disabled: true)}
- end
-
- @impl true
- def handle_event("validate", params, socket) do
- errors = validate(params, socket.assigns.users)
- {:noreply, assign(socket, form: to_form(params), errors: errors, disabled: errors != [])}
- end
-
- def handle_event("save", user_params, socket) do
- users = socket.assigns.users
-
- case validate(user_params, users) do
- [] ->
- blank_user = %{"email" => "", "username" => ""}
- send_to_phoenix(%{"type" => "new_user", "user" => user_params})
-
- {:noreply,
- assign(socket,
- form: to_form(blank_user),
- users: users ++ [user_params],
- errors: [],
- disabled: true
- )}
-
- errors ->
- {:noreply, assign(socket, errors: errors, disabled: true)}
- end
- end
-
- def handle_event(
- "llv_server_message",
- %{"type" => "synchronize", "users" => server_users},
- socket
- ) do
- filtered_users =
- Enum.filter(socket.assigns.users, fn user ->
- case validate_already_existing(user, server_users) do
- [] ->
- send_to_phoenix(%{"type" => "new_user", "user" => user})
- true
-
- _ ->
- false
- end
- end)
- {:noreply, assign(socket, users: server_users ++ filtered_users)}
- end
-
- def handle_event("generate_random", _params, socket) do
- users = socket.assigns.users
- user = generate_random_user(users)
- handle_event("save", user, socket)
- end
-
- defp validate(user, existing_users) do
- (validate_correctness(user) ++ validate_already_existing(user, existing_users))
- |> Enum.filter(fn error -> error != "" end)
- end
-
- defp validate_already_existing(user, existing_users) do
- user
- |> Enum.filter(fn {key, value} ->
- Enum.any?(existing_users, fn user -> Map.get(user, key) == value end)
- end)
- |> Enum.map(fn {key, _value} ->
- String.capitalize("#{key} already in use")
- end)
- end
-
- defp validate_correctness(user) do
- Enum.map(user, fn {key, value} -> validate_correctness(key, value) end)
- end
-
- defp validate_correctness("username", value) do
- cond do
- String.length(value) < 4 -> "Username length must be greater than 3 characters"
- true -> ""
- end
- end
-
- defp validate_correctness("email", value) do
- with [name, server] <- String.split(value, "@"),
- true <- String.length(name) > 0 and String.contains?(server, ".") do
- ""
- else
- _err -> "Email must have an email format"
- end
- end
-end
-
-
-