diff --git a/.formatter.exs b/.formatter.exs index 4761678..50d7a54 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,4 +1,5 @@ [ - import_deps: [:phoenix], - inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"] + import_deps: [:phoenix, :surface], + inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"], + surface_inputs: ["{lib,test}/**/*.{ex,exs,sface}"] ] diff --git a/config/dev.exs b/config/dev.exs index eec1ee8..83e21e0 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -51,7 +51,8 @@ config :elixir_console, ElixirConsoleWeb.Endpoint, patterns: [ ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", ~r"lib/elixir_console_web/(live|views)/.*(ex)$", - ~r"lib/elixir_console_web/templates/.*(eex)$" + ~r"lib/elixir_console_web/templates/.*(eex)$", + ~r"lib/elixir_console_web/live/.*(sface)$" ] ] diff --git a/lib/elixir_console_web/live/console_live.ex b/lib/elixir_console_web/live/console_live.ex index 6ef5f20..6125c72 100644 --- a/lib/elixir_console_web/live/console_live.ex +++ b/lib/elixir_console_web/live/console_live.ex @@ -36,6 +36,28 @@ defmodule ElixirConsoleWeb.ConsoleLive do Sandbox.terminate(sandbox) end + @impl true + def render(assigns) do + ~H""" +
+
+
+
+
+
+ <%= live_component(HistoryComponent, output: @output, id: :history) %> + <%= live_component(CommandInputComponent, history: @history, bindings: @sandbox.bindings, id: :command_input) %> +
+
+
+
+ <%= live_component(SidebarComponent, + sandbox: @sandbox, contextual_help: @contextual_help, suggestions: @suggestions) + %> +
+ """ + end + # This event comes from HistoryComponent @impl true def handle_info({:show_function_docs, contextual_help}, socket) do diff --git a/lib/elixir_console_web/live/console_live.html.heex b/lib/elixir_console_web/live/console_live.html.heex deleted file mode 100644 index a74f11c..0000000 --- a/lib/elixir_console_web/live/console_live.html.heex +++ /dev/null @@ -1,16 +0,0 @@ -
-
-
-
-
-
- <%= live_component(HistoryComponent, output: @output, id: :history) %> - <%= live_component(CommandInputComponent, history: @history, bindings: @sandbox.bindings, id: :command_input) %> -
-
-
-
- <%= live_component(SidebarComponent, - sandbox: @sandbox, contextual_help: @contextual_help, suggestions: @suggestions) - %> -
diff --git a/lib/elixir_console_web/live/console_live/bindings.ex b/lib/elixir_console_web/live/console_live/bindings.ex new file mode 100644 index 0000000..4f21907 --- /dev/null +++ b/lib/elixir_console_web/live/console_live/bindings.ex @@ -0,0 +1,21 @@ +defmodule ElixirConsoleWeb.ConsoleLive.Bindings do + use Surface.Component + + @doc "Keyword list of bindings" + prop bindings, :keyword + + def render(assigns) do + ~F""" +
+

Current Bindings

+ +
+ """ + end +end diff --git a/lib/elixir_console_web/live/console_live/command_input_component.ex b/lib/elixir_console_web/live/console_live/command_input_component.ex index 1284905..d7378f6 100644 --- a/lib/elixir_console_web/live/console_live/command_input_component.ex +++ b/lib/elixir_console_web/live/console_live/command_input_component.ex @@ -5,10 +5,13 @@ defmodule ElixirConsoleWeb.ConsoleLive.CommandInputComponent do user experience. """ - use Phoenix.LiveComponent + use Surface.Component import ElixirConsoleWeb.ConsoleLive.Helpers alias ElixirConsole.Autocomplete + prop input_value, :string + prop caret_position, :integer + def mount(socket) do {:ok, assign( @@ -19,6 +22,27 @@ defmodule ElixirConsoleWeb.ConsoleLive.CommandInputComponent do )} end + def render(assigns) do + ~F""" +
+
+ {print_prompt()} + +
+
+ """ + end + defp ensure_number(value) when is_number(value), do: value diff --git a/lib/elixir_console_web/live/console_live/command_input_component.html.heex b/lib/elixir_console_web/live/console_live/command_input_component.html.heex deleted file mode 100644 index 2e4e54a..0000000 --- a/lib/elixir_console_web/live/console_live/command_input_component.html.heex +++ /dev/null @@ -1,16 +0,0 @@ -
-
- <%= print_prompt() %> - -
-
diff --git a/lib/elixir_console_web/live/console_live/contextual_help.ex b/lib/elixir_console_web/live/console_live/contextual_help.ex new file mode 100644 index 0000000..b2421dc --- /dev/null +++ b/lib/elixir_console_web/live/console_live/contextual_help.ex @@ -0,0 +1,27 @@ +defmodule ElixirConsoleWeb.ConsoleLive.ContextualHelp do + use Surface.Component + + @doc "Link to documentation in hexdocs.pm" + prop link, :string + + @doc "Name of the function" + prop func_name, :string + + @doc "Header of the documentation" + prop header, :string + + @doc "Body of the documentation" + prop doc, :string + + def render(assigns) do + ~F""" +
+ + {@func_name} + + {@header} + {Phoenix.HTML.raw @doc} +
+ """ + end +end diff --git a/lib/elixir_console_web/live/console_live/history_component.ex b/lib/elixir_console_web/live/console_live/history_component.ex index 8e1205f..ba7c1cb 100644 --- a/lib/elixir_console_web/live/console_live/history_component.ex +++ b/lib/elixir_console_web/live/console_live/history_component.ex @@ -4,11 +4,38 @@ defmodule ElixirConsoleWeb.ConsoleLive.HistoryComponent do commands and results are displayed. """ - use Phoenix.LiveComponent + use Surface.LiveComponent import Phoenix.HTML, only: [sigil_e: 2] import ElixirConsoleWeb.ConsoleLive.Helpers alias ElixirConsole.ContextualHelp + prop output, :keyword + + @impl true + def render(assigns) do + ~F""" +
+
+

Elixir {System.version()}/OTP {System.otp_release()}

+
+ {#for output <- @output} +
+ {print_prompt()}{format_command(output.command)} +
+
+ {output.result} + {#if output.error} + + {output.error} + + {/if} +
+ {/for} +
+ """ + end + + @impl true def handle_event( "function_link_clicked", %{"func_name" => func_name, "header" => header, "doc" => doc, "link" => link}, diff --git a/lib/elixir_console_web/live/console_live/history_component.html.heex b/lib/elixir_console_web/live/console_live/history_component.html.heex deleted file mode 100644 index cdf668a..0000000 --- a/lib/elixir_console_web/live/console_live/history_component.html.heex +++ /dev/null @@ -1,18 +0,0 @@ -
-
-

Elixir <%= System.version() %>/OTP <%= System.otp_release() %>

-
- <%= for output <- @output do %> -
- <%= print_prompt() %><%= format_command(output.command) %> -
-
- <%= output.result %> - <%= if output.error do %> - - <%= output.error %> - - <% end %> -
- <% end %> -
diff --git a/lib/elixir_console_web/live/console_live/instructions.ex b/lib/elixir_console_web/live/console_live/instructions.ex new file mode 100644 index 0000000..4818fbe --- /dev/null +++ b/lib/elixir_console_web/live/console_live/instructions.ex @@ -0,0 +1,19 @@ +defmodule ElixirConsoleWeb.ConsoleLive.Instructions do + use Surface.Component + + def render(assigns) do + ~F""" +

INSTRUCTIONS

+

[UP] [DOWN]: Navigate through commands history

+

[TAB]: Get suggestions or autocomplete while typing

+

You can see the history panel that includes all your commands and their output. + Click on any Elixir function to see here the corresponding documentation.

+

ABOUT SECURITY

+

Please note some features of the language are not safe to run in a shared environment like this console. + If you are interested in knowing more about the limitations, you must read here.

+

Please report any security vulnerabilities to + elixir-console-security@wyeworks.com. +

+ """ + end +end diff --git a/lib/elixir_console_web/live/console_live/sidebar_component.ex b/lib/elixir_console_web/live/console_live/sidebar_component.ex index 3887016..b572b48 100644 --- a/lib/elixir_console_web/live/console_live/sidebar_component.ex +++ b/lib/elixir_console_web/live/console_live/sidebar_component.ex @@ -5,5 +5,30 @@ defmodule ElixirConsoleWeb.ConsoleLive.SidebarComponent do information. """ - use Phoenix.LiveComponent + use Surface.Component + + alias ElixirConsoleWeb.ConsoleLive.{Bindings, Suggestions, ContextualHelp, Instructions} + + prop sandbox, :map + + prop suggestions, :list + + prop contextual_help, :map + + def render(assigns) do + ~F""" +
+ +
+ {#if @suggestions != []} + + {#elseif @contextual_help} + + {#else} + + {/if} +
+
+ """ + end end diff --git a/lib/elixir_console_web/live/console_live/sidebar_component.html.heex b/lib/elixir_console_web/live/console_live/sidebar_component.html.heex deleted file mode 100644 index fbe80a7..0000000 --- a/lib/elixir_console_web/live/console_live/sidebar_component.html.heex +++ /dev/null @@ -1,46 +0,0 @@ -
-
-

Current Bindings

- -
-
- <%= if @suggestions != [] do %> -

Suggestions:

- <% else %> - <%= if @contextual_help do %> -
- - <%= @contextual_help[:func_name] %> - - <%= @contextual_help[:header] %> - <%= Phoenix.HTML.raw @contextual_help[:doc] %> -
- <% else %> -

INSTRUCTIONS

-

[UP] [DOWN]: Navigate through commands history

-

[TAB]: Get suggestions or autocomplete while typing

-

You can see the history panel that includes all your commands and their output. - Click on any Elixir function to see here the corresponding documentation.

-

ABOUT SECURITY

-

Please note some features of the language are not safe to run in a shared environment like this console. - If you are interested in knowing more about the limitations, you must read here.

-

Please report any security vulnerabilities to - elixir-console-security@wyeworks.com. -

- <% end %> - <% end %> - -
-
diff --git a/lib/elixir_console_web/live/console_live/suggestions.ex b/lib/elixir_console_web/live/console_live/suggestions.ex new file mode 100644 index 0000000..8b289f2 --- /dev/null +++ b/lib/elixir_console_web/live/console_live/suggestions.ex @@ -0,0 +1,17 @@ +defmodule ElixirConsoleWeb.ConsoleLive.Suggestions do + use Surface.Component + + @doc "List of suggestions" + prop suggestions, :list + + def render(assigns) do + ~F""" +

Suggestions:

+ + """ + end +end diff --git a/mix.exs b/mix.exs index 7927512..d0d5f97 100644 --- a/mix.exs +++ b/mix.exs @@ -41,6 +41,7 @@ defmodule ElixirConsole.MixProject do {:earmark, "~> 1.4.0"}, {:floki, "~> 0.31.0", only: :test}, {:sentry, "~> 7.0"}, + {:surface, "~> 0.5.2"}, {:wallaby, "~> 0.28", only: :test, runtime: false} ] end diff --git a/mix.lock b/mix.lock index e8f43f1..635493d 100644 --- a/mix.lock +++ b/mix.lock @@ -28,6 +28,7 @@ "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "sentry": {:hex, :sentry, "7.2.1", "ea6a5d26c4afef97bf21e09612ebe8d97b525b7822429092926377cd82c08f9e", [:mix], [{:hackney, "~> 1.8 or 1.6.5", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "8d1a9469095a83ff8575d16dc1b4d73f5444f3da1ded89a44ce8b85481180089"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "surface": {:hex, :surface, "0.5.2", "905e90a348eb788732adc474d88cce5627f8a202227492498b296d230b5f4f9c", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.15", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "c98562ee29323a906b2715059fbaf9bd566674fce19dd0ca71e2a51c457a80dd"}, "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"}, "tesla": {:hex, :tesla, "1.4.3", "f5a494e08fb1abe4fd9c28abb17f3d9b62b8f6fc492860baa91efb1aab61c8a0", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "e0755bb664bf4d664af72931f320c97adbf89da4586670f4864bf259b5750386"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},