mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-09-04 20:14:57 +08:00
List database types when adding database connection for the first time (#1090)
* List database types when adding database connection for the first time * Rename classes
This commit is contained in:
parent
9d7c795f0a
commit
5117fd6e64
9 changed files with 128 additions and 67 deletions
|
@ -209,15 +209,19 @@
|
|||
/* Toggleable menu */
|
||||
|
||||
.menu {
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
.menu-content {
|
||||
@apply absolute z-30 rounded-lg bg-white flex flex-col py-2 mt-1;
|
||||
box-shadow: 0px 15px 99px rgba(13, 24, 41, 0.15);
|
||||
}
|
||||
|
||||
.menu.right {
|
||||
.menu-content.right {
|
||||
@apply right-0;
|
||||
}
|
||||
|
||||
.menu.left {
|
||||
.menu-content.left {
|
||||
@apply left-0;
|
||||
}
|
||||
|
||||
|
@ -233,6 +237,18 @@
|
|||
@apply pointer-events-none opacity-50;
|
||||
}
|
||||
|
||||
.submenu {
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
.submenu:not(:hover):not(:focus-within) .submenu-content {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.submenu-content {
|
||||
@apply absolute -top-2 right-0 translate-x-full pl-2;
|
||||
}
|
||||
|
||||
/* Boxes */
|
||||
|
||||
.error-box {
|
||||
|
|
|
@ -79,7 +79,6 @@ const Session = {
|
|||
|
||||
// DOM events
|
||||
|
||||
this._handleDocumentKeyDown = this.handleDocumentKeyDown.bind(this);
|
||||
this._handleDocumentKeyDown = this.handleDocumentKeyDown.bind(this);
|
||||
this._handleDocumentMouseDown = this.handleDocumentMouseDown.bind(this);
|
||||
this._handleDocumentFocus = this.handleDocumentFocus.bind(this);
|
||||
|
|
|
@ -195,7 +195,16 @@ defprotocol Livebook.Runtime do
|
|||
@type smart_cell_definition :: %{
|
||||
kind: String.t(),
|
||||
name: String.t(),
|
||||
requirement: nil | %{name: String.t(), dependencies: list(dependency())}
|
||||
requirement: nil | smart_cell_requirement()
|
||||
}
|
||||
|
||||
@type smart_cell_requirement :: %{
|
||||
name: String.t(),
|
||||
variants:
|
||||
list(%{
|
||||
name: String.t(),
|
||||
dependencies: list(dependency())
|
||||
})
|
||||
}
|
||||
|
||||
@type dependency :: term()
|
||||
|
|
|
@ -18,23 +18,38 @@ defmodule Livebook.Runtime.ElixirStandalone do
|
|||
}
|
||||
|
||||
kino_dep = {:kino, github: "livebook-dev/kino"}
|
||||
vega_lite_dep = {:vega_lite, "~> 0.1.3"}
|
||||
|
||||
@extra_smart_cell_definitions [
|
||||
%{
|
||||
kind: "Elixir.Kino.SmartCell.DBConnection",
|
||||
name: "Database connection",
|
||||
requirement: %{name: "Kino", dependencies: [kino_dep]}
|
||||
requirement: %{
|
||||
name: "Kino",
|
||||
variants: [
|
||||
%{name: "PostgreSQL", dependencies: [kino_dep, {:postgrex, "~> 0.16.1"}]},
|
||||
%{name: "MySQL", dependencies: [kino_dep, {:myxql, "~> 0.6.1"}]}
|
||||
]
|
||||
}
|
||||
},
|
||||
%{
|
||||
kind: "Elixir.Kino.SmartCell.SQL",
|
||||
name: "SQL query",
|
||||
requirement: %{name: "Kino", dependencies: [kino_dep]}
|
||||
requirement: %{
|
||||
name: "Kino",
|
||||
variants: [
|
||||
%{name: "Default", dependencies: [kino_dep]}
|
||||
]
|
||||
}
|
||||
},
|
||||
%{
|
||||
kind: "Elixir.Kino.SmartCell.ChartBuilder",
|
||||
name: "Chart builder",
|
||||
requirement: %{name: "Kino", dependencies: [kino_dep, vega_lite_dep]}
|
||||
requirement: %{
|
||||
name: "Kino",
|
||||
variants: [
|
||||
%{name: "Default", dependencies: [kino_dep, {:vega_lite, "~> 0.1.3"}]}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -310,14 +310,6 @@ defmodule Livebook.Session do
|
|||
GenServer.cast(pid, {:convert_smart_cell, self(), cell_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends smart cell dependencies addition request to the server.
|
||||
"""
|
||||
@spec add_smart_cell_dependencies(pid(), String.t()) :: :ok
|
||||
def add_smart_cell_dependencies(pid, kind) do
|
||||
GenServer.cast(pid, {:add_smart_cell_dependencies, kind})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends dependencies addition request to the server.
|
||||
"""
|
||||
|
@ -747,16 +739,6 @@ defmodule Livebook.Session do
|
|||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({:add_smart_cell_dependencies, kind}, state) do
|
||||
state =
|
||||
case Enum.find(state.data.smart_cell_definitions, &(&1.kind == kind)) do
|
||||
%{requirement: %{dependencies: dependencies}} -> do_add_dependencies(state, dependencies)
|
||||
_ -> state
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({:add_dependencies, dependencies}, state) do
|
||||
{:noreply, do_add_dependencies(state, dependencies)}
|
||||
end
|
||||
|
|
|
@ -446,8 +446,7 @@ defmodule LivebookWeb.LiveHelpers do
|
|||
|> assign_new(:secondary_click, fn -> false end)
|
||||
|
||||
~H"""
|
||||
<div class="relative"
|
||||
id={@id}>
|
||||
<div class="menu" id={@id}>
|
||||
<div
|
||||
phx-click={not @disabled && JS.toggle(to: "##{@id}-content")}
|
||||
phx-click-away={JS.hide(to: "##{@id}-content")}
|
||||
|
@ -456,13 +455,40 @@ defmodule LivebookWeb.LiveHelpers do
|
|||
phx-key="escape">
|
||||
<%= render_slot(@toggle) %>
|
||||
</div>
|
||||
<menu id={"#{@id}-content"} class={"hidden menu #{@position}"} role="menu">
|
||||
<menu id={"#{@id}-content"} class={"hidden menu-content #{@position}"} role="menu">
|
||||
<%= render_slot(@content) %>
|
||||
</menu>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
A menu item that shows a submenu on hover.
|
||||
|
||||
This component should be used within `menu/1` content.
|
||||
|
||||
## Example
|
||||
|
||||
<.submenu>
|
||||
<button class"menu-item" role="menuitem">Submenu</button>
|
||||
<:content>
|
||||
<button class"menu-item" role="menuitem">Option 1</button>
|
||||
<.:content>
|
||||
</.submenu>
|
||||
"""
|
||||
def submenu(assigns) do
|
||||
~H"""
|
||||
<div class="submenu">
|
||||
<%= render_slot(@inner_block) %>
|
||||
<div class="submenu-content">
|
||||
<menu class="menu-content relative mt-0">
|
||||
<%= render_slot(@content) %>
|
||||
</menu>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a live region with the given role.
|
||||
|
||||
|
|
|
@ -723,8 +723,16 @@ defmodule LivebookWeb.SessionLive do
|
|||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("add_smart_cell_dependencies", %{"kind" => kind}, socket) do
|
||||
Session.add_smart_cell_dependencies(socket.assigns.session.pid, kind)
|
||||
def handle_event(
|
||||
"add_smart_cell_dependencies",
|
||||
%{"kind" => kind, "variant_idx" => variant_idx},
|
||||
socket
|
||||
) do
|
||||
with %{requirement: %{variants: variants}} <-
|
||||
Enum.find(socket.private.data.smart_cell_definitions, &(&1.kind == kind)),
|
||||
{:ok, %{dependencies: dependencies}} <- Enum.fetch(variants, variant_idx) do
|
||||
Session.add_dependencies(socket.assigns.session.pid, dependencies)
|
||||
end
|
||||
|
||||
{status, socket} = maybe_reconnect_runtime(socket)
|
||||
|
||||
|
|
|
@ -67,11 +67,10 @@ defmodule LivebookWeb.SessionLive.InsertButtonsComponent do
|
|||
</:toggle>
|
||||
<:content>
|
||||
<%= for definition <- Enum.sort_by(@smart_cell_definitions, & &1.name) do %>
|
||||
<button class="menu-item text-gray-500"
|
||||
role="menuitem"
|
||||
phx-click={on_smart_cell_click(definition, @section_id, @cell_id)}>
|
||||
<span class="font-medium"><%= definition.name %></span>
|
||||
</button>
|
||||
<.smart_cell_insert_button
|
||||
definition={definition}
|
||||
section_id={@section_id}
|
||||
cell_id={@cell_id} />
|
||||
<% end %>
|
||||
</:content>
|
||||
</.menu>
|
||||
|
@ -81,13 +80,44 @@ defmodule LivebookWeb.SessionLive.InsertButtonsComponent do
|
|||
"""
|
||||
end
|
||||
|
||||
defp on_smart_cell_click(%{requirement: nil} = definition, section_id, cell_id) do
|
||||
defp smart_cell_insert_button(%{definition: %{requirement: %{variants: [_, _ | _]}}} = assigns) do
|
||||
~H"""
|
||||
<.submenu>
|
||||
<button class="menu-item text-gray-500" role="menuitem">
|
||||
<span class="font-medium"><%= @definition.name %></span>
|
||||
</button>
|
||||
<:content>
|
||||
<%= for {variant, idx} <- Enum.with_index(@definition.requirement.variants) do %>
|
||||
<button class="menu-item text-gray-500"
|
||||
role="menuitem"
|
||||
phx-click={on_smart_cell_click(@definition, idx, @section_id, @cell_id)}>
|
||||
<span class="font-medium"><%= variant.name %></span>
|
||||
</button>
|
||||
<% end %>
|
||||
</:content>
|
||||
</.submenu>
|
||||
"""
|
||||
end
|
||||
|
||||
defp smart_cell_insert_button(assigns) do
|
||||
~H"""
|
||||
<button class="menu-item text-gray-500"
|
||||
role="menuitem"
|
||||
phx-click={on_smart_cell_click(@definition, 0, @section_id, @cell_id)}>
|
||||
<span class="font-medium"><%= @definition.name %></span>
|
||||
</button>
|
||||
"""
|
||||
end
|
||||
|
||||
defp on_smart_cell_click(%{requirement: nil} = definition, _variant_idx, section_id, cell_id) do
|
||||
insert_smart_cell(definition, section_id, cell_id)
|
||||
end
|
||||
|
||||
defp on_smart_cell_click(%{requirement: %{}} = definition, section_id, cell_id) do
|
||||
defp on_smart_cell_click(%{requirement: %{}} = definition, variant_idx, section_id, cell_id) do
|
||||
with_confirm(
|
||||
JS.push("add_smart_cell_dependencies", value: %{kind: definition.kind})
|
||||
JS.push("add_smart_cell_dependencies",
|
||||
value: %{kind: definition.kind, variant_idx: variant_idx}
|
||||
)
|
||||
|> insert_smart_cell(definition, section_id, cell_id),
|
||||
title: "Add package",
|
||||
description: ~s'''
|
||||
|
|
|
@ -134,27 +134,15 @@ defmodule Livebook.SessionTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "add_smart_cell_dependencies/2" do
|
||||
test "applies source change to the setup cell to include the smart cell dependency",
|
||||
describe "add_dependencies/2" do
|
||||
test "applies source change to the setup cell to include the given dependencies",
|
||||
%{session: session} do
|
||||
runtime = connected_noop_runtime()
|
||||
Session.set_runtime(session.pid, runtime)
|
||||
|
||||
send(
|
||||
session.pid,
|
||||
{:runtime_smart_cell_definitions,
|
||||
[
|
||||
%{
|
||||
kind: "text",
|
||||
name: "Text",
|
||||
requirement: %{name: "Kino", dependencies: [{:kino, "~> 0.5.0"}]}
|
||||
}
|
||||
]}
|
||||
)
|
||||
|
||||
Phoenix.PubSub.subscribe(Livebook.PubSub, "sessions:#{session.id}")
|
||||
|
||||
Session.add_smart_cell_dependencies(session.pid, "text")
|
||||
Session.add_dependencies(session.pid, [{:kino, "~> 0.5.0"}])
|
||||
|
||||
session_pid = session.pid
|
||||
assert_receive {:operation, {:apply_cell_delta, ^session_pid, "setup", :primary, _delta, 1}}
|
||||
|
@ -183,21 +171,9 @@ defmodule Livebook.SessionTest do
|
|||
runtime = connected_noop_runtime()
|
||||
Session.set_runtime(session.pid, runtime)
|
||||
|
||||
send(
|
||||
session.pid,
|
||||
{:runtime_smart_cell_definitions,
|
||||
[
|
||||
%{
|
||||
kind: "text",
|
||||
name: "Text",
|
||||
requirement: %{name: "Kino", dependencies: [{:kino, "~> 0.5.0"}]}
|
||||
}
|
||||
]}
|
||||
)
|
||||
|
||||
Phoenix.PubSub.subscribe(Livebook.PubSub, "sessions:#{session.id}")
|
||||
|
||||
Session.add_smart_cell_dependencies(session.pid, "text")
|
||||
Session.add_dependencies(session.pid, [{:kino, "~> 0.5.0"}])
|
||||
|
||||
assert_receive {:error, "failed to add dependencies to the setup cell, reason:" <> _}
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue