mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-12-09 13:16:08 +08:00
Initial redesign (#75)
* Add Remix icons * Replace existing icons with Remix icons * Update fonts * Redesign homepage * Redesign shortcuts modal * Fix tests
This commit is contained in:
parent
266bf35bd0
commit
a2d1e2f934
30 changed files with 252 additions and 326 deletions
|
|
@ -1,7 +1,7 @@
|
|||
/* Buttons */
|
||||
|
||||
.button-base {
|
||||
@apply px-4 py-2 bg-white rounded-md border border-gray-300 font-medium text-gray-700;
|
||||
@apply px-5 py-2 bg-white rounded-lg border border-gray-200 font-medium text-sm text-gray-600;
|
||||
}
|
||||
|
||||
.button-base:not(:disabled) {
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
}
|
||||
|
||||
.button-primary {
|
||||
@apply border-0 bg-blue-400 text-white;
|
||||
@apply border-transparent bg-blue-600 text-white;
|
||||
}
|
||||
|
||||
.button-primary:not(:disabled) {
|
||||
|
|
@ -35,11 +35,11 @@
|
|||
/* Form fields */
|
||||
|
||||
.input-base {
|
||||
@apply w-full px-3 py-3 bg-white rounded-md placeholder-gray-400 text-gray-700;
|
||||
@apply w-full p-3 bg-white border border-gray-200 rounded-lg placeholder-gray-400 text-gray-600;
|
||||
}
|
||||
|
||||
.checkbox-base {
|
||||
@apply h-5 w-5 appearance-none border border-gray-300 rounded text-blue-400 cursor-pointer;
|
||||
@apply h-5 w-5 appearance-none border border-gray-300 rounded text-blue-600 cursor-pointer;
|
||||
}
|
||||
|
||||
.checkbox-base:checked {
|
||||
|
|
|
|||
|
|
@ -3,3 +3,7 @@
|
|||
button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Inter";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,14 +99,14 @@
|
|||
}
|
||||
|
||||
.markdown code {
|
||||
@apply py-1 px-2 rounded-md text-sm align-middle;
|
||||
@apply py-1 px-2 rounded-lg text-sm align-middle font-mono;
|
||||
/* Match the editor colors */
|
||||
background-color: #282c34;
|
||||
color: #abb2bf;
|
||||
}
|
||||
|
||||
.markdown pre > code {
|
||||
@apply block p-4 rounded-md text-sm align-middle;
|
||||
@apply block p-4 rounded-lg text-sm align-middle font-mono;
|
||||
/* Match the editor colors */
|
||||
background-color: #282c34;
|
||||
color: #abb2bf;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@
|
|||
}
|
||||
|
||||
.font-editor {
|
||||
font-family: "Droid Sans Mono", monospace, monospace, "Droid Sans Fallback";
|
||||
font-family: "JetBrains Mono";
|
||||
font-size: 14px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
import "../css/app.css";
|
||||
import 'remixicon/fonts/remixicon.css'
|
||||
|
||||
import "@fontsource/inter";
|
||||
import "@fontsource/inter/500.css";
|
||||
import "@fontsource/inter/600.css";
|
||||
import "@fontsource/jetbrains-mono";
|
||||
|
||||
import "phoenix_html";
|
||||
import { Socket } from "phoenix";
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ class LiveEditor {
|
|||
occurrencesHighlight: false,
|
||||
renderLineHighlight: "none",
|
||||
theme: "custom",
|
||||
fontFamily: "JetBrains Mono"
|
||||
});
|
||||
|
||||
this.editor.getModel().updateOptions({
|
||||
|
|
|
|||
35
assets/package-lock.json
generated
35
assets/package-lock.json
generated
|
|
@ -6,6 +6,8 @@
|
|||
"": {
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "^4.2.2",
|
||||
"@fontsource/jetbrains-mono": "^4.2.2",
|
||||
"dompurify": "^2.2.6",
|
||||
"hyperlist": "^1.0.0",
|
||||
"marked": "^1.2.8",
|
||||
|
|
@ -14,7 +16,8 @@
|
|||
"nprogress": "^0.2.0",
|
||||
"phoenix": "file:../deps/phoenix",
|
||||
"phoenix_html": "file:../deps/phoenix_html",
|
||||
"phoenix_live_view": "file:../deps/phoenix_live_view"
|
||||
"phoenix_live_view": "file:../deps/phoenix_live_view",
|
||||
"remixicon": "^2.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0",
|
||||
|
|
@ -1095,6 +1098,16 @@
|
|||
"node": ">=0.1.95"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/inter": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.2.2.tgz",
|
||||
"integrity": "sha512-lvR1PQe+8FTTd3YRW84KGcgUR8leZ7S3aY+51MQ90MQHI0VQe3cDH6T6jjs1qTm+wPmWfdSVjN8ugvNZpGUnvA=="
|
||||
},
|
||||
"node_modules/@fontsource/jetbrains-mono": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-4.2.2.tgz",
|
||||
"integrity": "sha512-aCwLIqfZZrZjy+cIx/9hSzxyOcz8YzMbd3VQ8QRkS2MB3+XsXAnMLEkELXLCfKYUkEysRECaq5s7+Qhi1hgZAA=="
|
||||
},
|
||||
"node_modules/@fullhuman/postcss-purgecss": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-3.1.3.tgz",
|
||||
|
|
@ -13059,6 +13072,11 @@
|
|||
"jsesc": "bin/jsesc"
|
||||
}
|
||||
},
|
||||
"node_modules/remixicon": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/remixicon/-/remixicon-2.5.0.tgz",
|
||||
"integrity": "sha512-q54ra2QutYDZpuSnFjmeagmEiN9IMo56/zz5dDNitzKD23oFRw77cWo4TsrAdmdkPiEn8mxlrTqxnkujDbEGww=="
|
||||
},
|
||||
"node_modules/remove-trailing-separator": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
|
||||
|
|
@ -16690,6 +16708,16 @@
|
|||
"minimist": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"@fontsource/inter": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.2.2.tgz",
|
||||
"integrity": "sha512-lvR1PQe+8FTTd3YRW84KGcgUR8leZ7S3aY+51MQ90MQHI0VQe3cDH6T6jjs1qTm+wPmWfdSVjN8ugvNZpGUnvA=="
|
||||
},
|
||||
"@fontsource/jetbrains-mono": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-4.2.2.tgz",
|
||||
"integrity": "sha512-aCwLIqfZZrZjy+cIx/9hSzxyOcz8YzMbd3VQ8QRkS2MB3+XsXAnMLEkELXLCfKYUkEysRECaq5s7+Qhi1hgZAA=="
|
||||
},
|
||||
"@fullhuman/postcss-purgecss": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-3.1.3.tgz",
|
||||
|
|
@ -26530,6 +26558,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"remixicon": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/remixicon/-/remixicon-2.5.0.tgz",
|
||||
"integrity": "sha512-q54ra2QutYDZpuSnFjmeagmEiN9IMo56/zz5dDNitzKD23oFRw77cWo4TsrAdmdkPiEn8mxlrTqxnkujDbEGww=="
|
||||
},
|
||||
"remove-trailing-separator": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@
|
|||
"test:watch": "jest --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "^4.2.2",
|
||||
"@fontsource/jetbrains-mono": "^4.2.2",
|
||||
"dompurify": "^2.2.6",
|
||||
"hyperlist": "^1.0.0",
|
||||
"marked": "^1.2.8",
|
||||
|
|
@ -18,7 +20,8 @@
|
|||
"nprogress": "^0.2.0",
|
||||
"phoenix": "file:../deps/phoenix",
|
||||
"phoenix_html": "file:../deps/phoenix_html",
|
||||
"phoenix_live_view": "file:../deps/phoenix_live_view"
|
||||
"phoenix_live_view": "file:../deps/phoenix_live_view",
|
||||
"remixicon": "^2.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,38 @@ module.exports = {
|
|||
],
|
||||
darkMode: false,
|
||||
theme: {
|
||||
extend: {},
|
||||
fontFamily: {
|
||||
'sans': ['Inter'],
|
||||
'mono': ['JetBrains Mono'],
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
blue: {
|
||||
50: '#F5F7FF',
|
||||
100: '#ECF0FF',
|
||||
200: '#D8E0FF',
|
||||
300: '#B2C1FF',
|
||||
400: '#8BA2FF',
|
||||
500: '#6583FF',
|
||||
600: '#3E64FF',
|
||||
700: '#2D4CDB',
|
||||
800: '#1F37B7',
|
||||
900: '#132593',
|
||||
},
|
||||
gray: {
|
||||
50: '#F8FAFC',
|
||||
100: '#F0F5F9',
|
||||
200: '#E1E8F0',
|
||||
300: '#CAD5E0',
|
||||
400: '#91A4B7',
|
||||
500: '#61758A',
|
||||
600: '#445668',
|
||||
700: '#304254',
|
||||
800: '#1C2A3A',
|
||||
900: '#0D1829',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
extend: {},
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ module.exports = (env, options) => {
|
|||
],
|
||||
},
|
||||
{
|
||||
test: /\.ttf$/,
|
||||
test: /\.(ttf|woff|woff2|eot|svg)$/,
|
||||
use: ['file-loader'],
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ defmodule LivebookWeb do
|
|||
|
||||
# Custom helpers
|
||||
import LivebookWeb.Helpers
|
||||
alias LivebookWeb.Icons
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
defmodule LivebookWeb.Helpers do
|
||||
import Phoenix.LiveView.Helpers
|
||||
import Phoenix.HTML.Tag
|
||||
|
||||
@doc """
|
||||
Renders a component inside the `Livebook.ModalComponent` component.
|
||||
|
|
@ -31,4 +32,13 @@ defmodule LivebookWeb.Helpers do
|
|||
defp windows?(user_agent), do: String.match?(user_agent, ~r/Windows/)
|
||||
|
||||
defdelegate ansi_string_to_html(string), to: LivebookWeb.ANSI
|
||||
|
||||
@doc """
|
||||
Returns [Remix](https://remixicon.com) icon tag.
|
||||
"""
|
||||
def remix_icon(name, attrs \\ []) do
|
||||
icon_class = "ri-#{name}"
|
||||
attrs = Keyword.update(attrs, :class, icon_class, fn class -> "#{icon_class} #{class}" end)
|
||||
content_tag(:i, "", attrs)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,25 +17,25 @@ defmodule LivebookWeb.CellComponent do
|
|||
def render_cell_content(%{cell: %{type: :markdown}} = assigns) do
|
||||
~L"""
|
||||
<div class="flex flex-col items-center space-y-2 absolute z-50 right-0 top-0 -mr-10" data-element="actions">
|
||||
<button class="text-gray-500 hover:text-current" data-element="enable-insert-mode-button">
|
||||
<%= Icons.svg(:pencil, class: "h-6") %>
|
||||
<button class="text-gray-400 hover:text-current" data-element="enable-insert-mode-button">
|
||||
<%= remix_icon("pencil-line", class: "text-2xl") %>
|
||||
</button>
|
||||
<button class="text-gray-500 hover:text-current"
|
||||
<button class="text-gray-400 hover:text-current"
|
||||
phx-click="delete_cell"
|
||||
phx-value-cell_id="<%= @cell.id %>">
|
||||
<%= Icons.svg(:trash, class: "h-6") %>
|
||||
<%= remix_icon("delete-bin-line", class: "text-2xl") %>
|
||||
</button>
|
||||
<button class="text-gray-500 hover:text-current"
|
||||
<button class="text-gray-400 hover:text-current"
|
||||
phx-click="move_cell"
|
||||
phx-value-cell_id="<%= @cell.id %>"
|
||||
phx-value-offset="-1">
|
||||
<%= Icons.svg(:chevron_up, class: "h-6") %>
|
||||
<%= remix_icon("arrow-up-s-line", class: "text-2xl") %>
|
||||
</button>
|
||||
<button class="text-gray-500 hover:text-current"
|
||||
<button class="text-gray-400 hover:text-current"
|
||||
phx-click="move_cell"
|
||||
phx-value-cell_id="<%= @cell.id %>"
|
||||
phx-value-offset="1">
|
||||
<%= Icons.svg(:chevron_down, class: "h-6") %>
|
||||
<%= remix_icon("arrow-down-s-line", class: "text-2xl") %>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -53,37 +53,37 @@ defmodule LivebookWeb.CellComponent do
|
|||
~L"""
|
||||
<div class="flex flex-col items-center space-y-2 absolute z-50 right-0 top-0 -mr-10" data-element="actions">
|
||||
<%= if @cell_info.evaluation_status == :ready do %>
|
||||
<button class="text-gray-500 hover:text-current"
|
||||
<button class="text-gray-400 hover:text-current"
|
||||
phx-click="queue_cell_evaluation"
|
||||
phx-value-cell_id="<%= @cell.id %>">
|
||||
<%= Icons.svg(:play, class: "h-6") %>
|
||||
<%= remix_icon("play-circle-line", class: "text-2xl") %>
|
||||
</button>
|
||||
<% else %>
|
||||
<button class="text-gray-500 hover:text-current"
|
||||
<button class="text-gray-400 hover:text-current"
|
||||
phx-click="cancel_cell_evaluation"
|
||||
phx-value-cell_id="<%= @cell.id %>">
|
||||
<%= Icons.svg(:stop, class: "h-6") %>
|
||||
<%= remix_icon("stop-circle-line", class: "text-2xl") %>
|
||||
</button>
|
||||
<% end %>
|
||||
<button class="text-gray-500 hover:text-current"
|
||||
<button class="text-gray-400 hover:text-current"
|
||||
phx-click="delete_cell"
|
||||
phx-value-cell_id="<%= @cell.id %>">
|
||||
<%= Icons.svg(:trash, class: "h-6") %>
|
||||
<%= remix_icon("delete-bin-line", class: "text-2xl") %>
|
||||
</button>
|
||||
<%= live_patch to: Routes.session_path(@socket, :cell_settings, @session_id, @cell.id), class: "text-gray-500 hover:text-current" do %>
|
||||
<%= Icons.svg(:adjustments, class: "h-6") %>
|
||||
<%= live_patch to: Routes.session_path(@socket, :cell_settings, @session_id, @cell.id), class: "text-gray-400 hover:text-current" do %>
|
||||
<%= remix_icon("list-settings-line", class: "text-2xl") %>
|
||||
<% end %>
|
||||
<button class="text-gray-500 hover:text-current"
|
||||
<button class="text-gray-400 hover:text-current"
|
||||
phx-click="move_cell"
|
||||
phx-value-cell_id="<%= @cell.id %>"
|
||||
phx-value-offset="-1">
|
||||
<%= Icons.svg(:chevron_up, class: "h-6") %>
|
||||
<%= remix_icon("arrow-up-s-line", class: "text-2xl") %>
|
||||
</button>
|
||||
<button class="text-gray-500 hover:text-current"
|
||||
<button class="text-gray-400 hover:text-current"
|
||||
phx-click="move_cell"
|
||||
phx-value-cell_id="<%= @cell.id %>"
|
||||
phx-value-offset="1">
|
||||
<%= Icons.svg(:chevron_down, class: "h-6") %>
|
||||
<%= remix_icon("arrow-down-s-line", class: "text-2xl") %>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ defmodule LivebookWeb.CellComponent do
|
|||
assigns = %{cell: cell, cell_info: cell_info, show_status: show_status}
|
||||
|
||||
~L"""
|
||||
<div class="py-3 rounded-md overflow-hidden bg-editor relative">
|
||||
<div class="py-3 rounded-lg overflow-hidden bg-editor relative">
|
||||
<div
|
||||
id="editor-container-<%= @cell.id %>"
|
||||
data-element="editor-container"
|
||||
|
|
@ -137,9 +137,9 @@ defmodule LivebookWeb.CellComponent do
|
|||
~L"""
|
||||
<div class="max-w-2xl w-full animate-pulse">
|
||||
<div class="flex-1 space-y-4">
|
||||
<div class="h-4 bg-gray-200 rounded-md w-3/4"></div>
|
||||
<div class="h-4 bg-gray-200 rounded-md"></div>
|
||||
<div class="h-4 bg-gray-200 rounded-md w-5/6"></div>
|
||||
<div class="h-4 bg-gray-200 rounded-lg w-3/4"></div>
|
||||
<div class="h-4 bg-gray-200 rounded-lg"></div>
|
||||
<div class="h-4 bg-gray-200 rounded-lg w-5/6"></div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
|
@ -159,9 +159,9 @@ defmodule LivebookWeb.CellComponent do
|
|||
~L"""
|
||||
<div class="px-8 max-w-2xl w-full animate-pulse">
|
||||
<div class="flex-1 space-y-4 py-1">
|
||||
<div class="h-4 bg-gray-500 rounded-md w-3/4"></div>
|
||||
<div class="h-4 bg-gray-500 rounded-md"></div>
|
||||
<div class="h-4 bg-gray-500 rounded-md w-5/6"></div>
|
||||
<div class="h-4 bg-gray-500 rounded-lg w-3/4"></div>
|
||||
<div class="h-4 bg-gray-500 rounded-lg"></div>
|
||||
<div class="h-4 bg-gray-500 rounded-lg w-5/6"></div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
|
@ -171,7 +171,7 @@ defmodule LivebookWeb.CellComponent do
|
|||
assigns = %{outputs: outputs, cell_id: cell_id}
|
||||
|
||||
~L"""
|
||||
<div class="flex flex-col rounded-md border border-gray-200 divide-y divide-gray-200 font-editor">
|
||||
<div class="flex flex-col rounded-lg border border-gray-200 divide-y divide-gray-200 font-editor">
|
||||
<%= for {output, index} <- @outputs |> Enum.reverse() |> Enum.with_index() do %>
|
||||
<div class="p-4">
|
||||
<div class="">
|
||||
|
|
@ -230,7 +230,7 @@ defmodule LivebookWeb.CellComponent do
|
|||
<div class="text-xs text-gray-400">Evaluating</div>
|
||||
<span class="flex relative h-3 w-3">
|
||||
<span class="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-blue-300 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-3 w-3 bg-blue-400"></span>
|
||||
<span class="relative inline-flex rounded-full h-3 w-3 bg-blue-600"></span>
|
||||
</span>
|
||||
</div>
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -17,17 +17,14 @@ defmodule LivebookWeb.HomeLive do
|
|||
@impl true
|
||||
def render(assigns) do
|
||||
~L"""
|
||||
<header class="flex justify-center p-4 border-b">
|
||||
<h1 class="text-2xl font-medium">Livebook</h1>
|
||||
</header>
|
||||
<div class="container max-w-5xl w-full mx-auto p-4 pb-8 flex flex-col items-center space-y-4">
|
||||
<div class="container max-w-4xl w-full mx-auto p-4 pb-8 flex flex-col items-center space-y-4">
|
||||
<div class="w-full flex justify-end">
|
||||
<button class="button-base button-sm"
|
||||
<button class="button-base button-primary"
|
||||
phx-click="new">
|
||||
New notebook
|
||||
New Notebook
|
||||
</button>
|
||||
</div>
|
||||
<div class="container flex flex-col space-y-4">
|
||||
<div class="w-full flex flex-col space-y-4">
|
||||
<%= live_component @socket, LivebookWeb.PathSelectComponent,
|
||||
id: "path_select",
|
||||
path: @path,
|
||||
|
|
@ -35,27 +32,30 @@ defmodule LivebookWeb.HomeLive do
|
|||
running_paths: paths(@session_summaries),
|
||||
target: nil %>
|
||||
<div class="flex justify-end space-x-2">
|
||||
<%= content_tag :button, "Fork",
|
||||
class: "button-base button-sm",
|
||||
<%= content_tag :button,
|
||||
class: "button-base",
|
||||
phx_click: "fork",
|
||||
disabled: not path_forkable?(@path) %>
|
||||
disabled: not path_forkable?(@path) do %>
|
||||
<%= remix_icon("git-branch-line", class: "align-middle mr-1") %>
|
||||
<span>Fork</span>
|
||||
<% end %>
|
||||
<%= if path_running?(@path, @session_summaries) do %>
|
||||
<%= live_patch "Join session", to: Routes.session_path(@socket, :page, session_id_by_path(@path, @session_summaries)),
|
||||
class: "button-base button-sm button-primary" %>
|
||||
class: "button-base button-primary" %>
|
||||
<% else %>
|
||||
<%= content_tag :button, "Open",
|
||||
class: "button-base button-sm button-primary",
|
||||
class: "button-base button-primary",
|
||||
phx_click: "open",
|
||||
disabled: not path_openable?(@path, @session_summaries) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full pt-24">
|
||||
<h3 class="text-xl font-medium text-gray-900">
|
||||
Running sessions
|
||||
<h3 class="text-xl font-semibold text-gray-800 mb-5">
|
||||
Running Sessions
|
||||
</h3>
|
||||
<%= if @session_summaries == [] do %>
|
||||
<div class="mt-3 text-gray-500 text-medium">
|
||||
<div class="text-gray-500 text-medium">
|
||||
No sessions currently running, you can create one above.
|
||||
</div>
|
||||
<% else %>
|
||||
|
|
|
|||
|
|
@ -1,193 +0,0 @@
|
|||
defmodule LivebookWeb.Icons do
|
||||
import Phoenix.HTML.Tag
|
||||
import Phoenix.LiveView.Helpers
|
||||
|
||||
@doc """
|
||||
Returns icon svg tag.
|
||||
"""
|
||||
def svg(name, attrs \\ [])
|
||||
|
||||
def svg(:play, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:plus, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:trash, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:chip, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:information_circle, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:exclamation_circle, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:question_mark_circle, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:pencil, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:folder, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:document_text, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:check_circle, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:dots_circle_horizontal, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:home, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:stop, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:chevron_up, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:chevron_down, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
def svg(:adjustments, attrs) do
|
||||
assigns = %{attrs: heroicon_svg_attrs(attrs)}
|
||||
|
||||
~L"""
|
||||
<%= tag(:svg, @attrs) %>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
# https://heroicons.com
|
||||
defp heroicon_svg_attrs(attrs) do
|
||||
heroicon_svg_attrs = [
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
fill: "none",
|
||||
viewBox: "0 0 24 24",
|
||||
stroke: "currentColor"
|
||||
]
|
||||
|
||||
Keyword.merge(attrs, heroicon_svg_attrs)
|
||||
end
|
||||
end
|
||||
|
|
@ -5,14 +5,14 @@ defmodule LivebookWeb.InsertCellComponent do
|
|||
~L"""
|
||||
<div class="<%= if(@persistent, do: "opacity-100", else: "opacity-0") %> hover:opacity-100 flex space-x-2 justify-center items-center">
|
||||
<%= line() %>
|
||||
<button class="py-1 px-2 rounded-md text-sm hover:bg-gray-50 border border-gray-200"
|
||||
<button class="py-1 px-2 rounded-lg text-sm hover:bg-gray-50 border border-gray-200"
|
||||
phx-click="insert_cell"
|
||||
phx-value-type="markdown"
|
||||
phx-value-section_id="<%= @section_id %>"
|
||||
phx-value-index="<%= @index %>">
|
||||
+ Markdown
|
||||
</button>
|
||||
<button class="py-1 px-2 rounded-md text-sm hover:bg-gray-50 border border-gray-200"
|
||||
<button class="py-1 px-2 rounded-lg text-sm hover:bg-gray-50 border border-gray-200"
|
||||
phx-click="insert_cell"
|
||||
phx-value-type="elixir"
|
||||
phx-value-section_id="<%= @section_id %>"
|
||||
|
|
|
|||
|
|
@ -19,10 +19,15 @@ defmodule LivebookWeb.ModalComponent do
|
|||
phx-page-loading></div>
|
||||
|
||||
<!-- Modal box -->
|
||||
<div class="relative max-h-full overflow-y-auto bg-white rounded-md shadow-xl"
|
||||
<div class="relative max-h-full overflow-y-auto bg-white rounded-lg shadow-xl"
|
||||
role="dialog"
|
||||
aria-modal="true">
|
||||
|
||||
<%= live_patch to: @return_to, class: "absolute top-6 right-6 text-gray-400 flex space-x-1 items-center" do %>
|
||||
<span class="text-sm">(esc)</span>
|
||||
<%= remix_icon("close-line", class: "text-2xl") %>
|
||||
<% end %>
|
||||
|
||||
<%= live_component @socket, @component, @opts %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ defmodule LivebookWeb.PathSelectComponent do
|
|||
def render(assigns) do
|
||||
~L"""
|
||||
<form phx-change="set_path" phx-submit="set_path" <%= if @target, do: "phx-target=#{@target}" %>>
|
||||
<input class="input-base shadow"
|
||||
<input class="input-base"
|
||||
id="input-path"
|
||||
phx-hook="FocusOnUpdate"
|
||||
type="text"
|
||||
|
|
@ -37,22 +37,22 @@ defmodule LivebookWeb.PathSelectComponent do
|
|||
defp render_file(file, target) do
|
||||
icon =
|
||||
case file do
|
||||
%{is_running: true} -> :play
|
||||
%{is_dir: true} -> :folder
|
||||
_ -> :document_text
|
||||
%{is_running: true} -> "play-circle-line"
|
||||
%{is_dir: true} -> "folder-fill"
|
||||
_ -> "file-code-line"
|
||||
end
|
||||
|
||||
assigns = %{file: file, icon: icon}
|
||||
|
||||
~L"""
|
||||
<button class="flex space-x-2 items-center p-2 rounded-md hover:bg-gray-100 focus:ring-1 focus:ring-blue-400 <%= if(@file.is_running, do: "text-green-400 opacity-75", else: "text-gray-700") %>"
|
||||
<button class="flex space-x-2 items-center p-2 rounded-lg hover:bg-gray-100 focus:ring-1 focus:ring-gray-400"
|
||||
phx-click="set_path"
|
||||
phx-value-path="<%= file.path %>"
|
||||
<%= if target, do: "phx-target=#{target}" %>>
|
||||
<span class="block">
|
||||
<%= Icons.svg(@icon, class: "h-5") %>
|
||||
<%= remix_icon(@icon, class: "text-xl align-middle #{if(@file.is_running, do: "text-green-300", else: "text-gray-400")}") %>
|
||||
</span>
|
||||
<span class="block overflow-hidden overflow-ellipsis whitespace-nowrap">
|
||||
<span class="block font-medium overflow-hidden overflow-ellipsis whitespace-nowrap <%= if(@file.is_running, do: "text-green-300", else: "text-gray-500") %>">
|
||||
<%= file.name %>
|
||||
</span>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ defmodule LivebookWeb.SectionComponent do
|
|||
</div>
|
||||
<div class="flex space-x-2 items-center">
|
||||
<button phx-click="delete_section" phx-value-section_id="<%= @section.id %>" class="text-gray-600 hover:text-current" tabindex="-1">
|
||||
<%= Icons.svg(:trash, class: "h-6") %>
|
||||
<%= remix_icon("delete-bin-line", class: "text-2xl") %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -91,14 +91,14 @@ defmodule LivebookWeb.SessionLive do
|
|||
<% end %>
|
||||
<button phx-click="add_section" class="py-2 px-4 rounded-l-md cursor-pointer text-gray-300 hover:text-gray-400">
|
||||
<div class="flex items-center space-x-2">
|
||||
<%= Icons.svg(:plus, class: "h-6") %>
|
||||
<%= remix_icon("add-line", class: "text-2xl") %>
|
||||
<span>New section</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<%= live_patch to: Routes.session_path(@socket, :runtime, @session_id) do %>
|
||||
<div class="text-sm text-gray-500 text-medium px-4 py-2 border-b border-gray-200 flex space-x-2 items-center hover:bg-gray-200">
|
||||
<%= Icons.svg(:chip, class: "h-5 text-gray-400") %>
|
||||
<%= remix_icon("cpu-line", class: "text-xl text-gray-400") %>
|
||||
<span><%= runtime_description(@data.runtime) %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -106,15 +106,15 @@ defmodule LivebookWeb.SessionLive do
|
|||
<div class="text-sm text-gray-500 text-medium px-4 py-2 border-b border-gray-200 flex space-x-2 items-center hover:bg-gray-200">
|
||||
<%= if @data.path do %>
|
||||
<%= if @data.dirty do %>
|
||||
<%= Icons.svg(:dots_circle_horizontal, class: "h-5 text-blue-400") %>
|
||||
<%= remix_icon("refresh-line", class: "text-xl text-blue-600") %>
|
||||
<% else %>
|
||||
<%= Icons.svg(:check_circle, class: "h-5 text-green-400") %>
|
||||
<%= remix_icon("checkbox-circle-line", class: "text-xl text-green-400") %>
|
||||
<% end %>
|
||||
<span>
|
||||
<%= Path.basename(@data.path) %>
|
||||
</span>
|
||||
<% else %>
|
||||
<%= Icons.svg(:document_text, class: "h-5 text-gray-400") %>
|
||||
<%= remix_icon("file-code-line", class: "text-xl text-gray-400") %>
|
||||
<span>
|
||||
No file choosen
|
||||
</span>
|
||||
|
|
@ -123,10 +123,10 @@ defmodule LivebookWeb.SessionLive do
|
|||
<% end %>
|
||||
<div class="p-4 flex space-x-2">
|
||||
<%= live_patch to: Routes.home_path(@socket, :page) do %>
|
||||
<%= Icons.svg(:home, class: "h-6 w-6 text-gray-600 hover:text-current") %>
|
||||
<%= remix_icon("home-2-line", class: "text-2xl text-gray-600 hover:text-current") %>
|
||||
<% end %>
|
||||
<%= live_patch to: Routes.session_path(@socket, :shortcuts, @session_id) do %>
|
||||
<%= Icons.svg(:question_mark_circle, class: "h-6 w-6 text-gray-600 hover:text-current") %>
|
||||
<%= remix_icon("question-line", class: "text-2xl text-gray-600 hover:text-current") %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ defmodule LivebookWeb.SessionLive.AttachedLive do
|
|||
~L"""
|
||||
<div class="flex-col space-y-3">
|
||||
<%= if @error_message do %>
|
||||
<div class="mb-3 rounded-md px-4 py-2 bg-red-100 text-red-400 font-medium">
|
||||
<div class="mb-3 rounded-lg px-4 py-2 bg-red-100 text-red-400 font-medium">
|
||||
<%= @error_message %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -35,10 +35,10 @@ defmodule LivebookWeb.SessionLive.AttachedLive do
|
|||
Then enter the name of the node below:
|
||||
</p>
|
||||
<%= f = form_for :node, "#", phx_submit: "init" %>
|
||||
<%= text_input f, :name, class: "input-base shadow",
|
||||
<%= text_input f, :name, class: "input-base",
|
||||
placeholder: if(Livebook.Config.shortnames?, do: "test", else: "test@127.0.0.1") %>
|
||||
|
||||
<%= submit "Connect", class: "mt-3 button-base button-sm" %>
|
||||
<%= submit "Connect", class: "mt-3 button-base" %>
|
||||
</form>
|
||||
</div>
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ defmodule LivebookWeb.SessionLive.CellSettingsComponent do
|
|||
</label>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-end space-x-2">
|
||||
<%= live_patch "Cancel", to: @return_to, class: "button-base button-sm" %>
|
||||
<button class="button-base button-primary button-sm" type="submit">
|
||||
<%= live_patch "Cancel", to: @return_to, class: "button-base" %>
|
||||
<button class="button-base button-primary" type="submit">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ defmodule LivebookWeb.SessionLive.ElixirStandaloneLive do
|
|||
This is the default runtime and is started automatically
|
||||
as soon as you evaluate the first cell.
|
||||
</p>
|
||||
<button class="button-base button-sm" phx-click="init">
|
||||
<button class="button-base" phx-click="init">
|
||||
Connect
|
||||
</button>
|
||||
<%= if @output do %>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ defmodule LivebookWeb.SessionLive.MixStandaloneLive do
|
|||
extnames: [],
|
||||
running_paths: [],
|
||||
target: nil %>
|
||||
<%= content_tag :button, "Connect", class: "button-base button-sm", phx_click: "init", disabled: not mix_project_root?(@path) %>
|
||||
<%= content_tag :button, "Connect", class: "button-base", phx_click: "init", disabled: not mix_project_root?(@path) %>
|
||||
<% end %>
|
||||
<%= if @status != :initial do %>
|
||||
<div class="markdown">
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ defmodule LivebookWeb.SessionLive.PersistenceComponent do
|
|||
</div>
|
||||
<div class="flex justify-end">
|
||||
<%= content_tag :button, "Done",
|
||||
class: "button-base button-primary button-sm",
|
||||
class: "button-base button-primary",
|
||||
phx_click: "done",
|
||||
phx_target: @myself,
|
||||
disabled: not path_savable?(normalize_path(@path), @session_summaries) %>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ defmodule LivebookWeb.SessionLive.RuntimeComponent do
|
|||
The code is evaluated in a separate Elixir runtime (node),
|
||||
which you can configure yourself here.
|
||||
</p>
|
||||
<div class="shadow rounded-md p-2">
|
||||
<div class="border border-gray-200 rounded-lg p-2">
|
||||
<%= if @runtime do %>
|
||||
<table class="w-full text-center text-sm">
|
||||
<thead>
|
||||
|
|
@ -35,7 +35,7 @@ defmodule LivebookWeb.SessionLive.RuntimeComponent do
|
|||
<td><%= runtime_type_label(@runtime) %></td>
|
||||
<td><%= @runtime.node %></td>
|
||||
<td>
|
||||
<button class="button-base text-sm button-sm button-danger"
|
||||
<button class="button-base text-sm button-danger"
|
||||
type="button"
|
||||
phx-click="disconnect"
|
||||
phx-target="<%= @myself %>">
|
||||
|
|
|
|||
|
|
@ -3,26 +3,26 @@ defmodule LivebookWeb.SessionLive.ShortcutsComponent do
|
|||
|
||||
@shortcuts %{
|
||||
insert_mode: [
|
||||
%{seq: "esc", desc: "Switch back to navigation mode"},
|
||||
%{seq: "ctrl + enter", desc: "Evaluate cell and stay in insert mode"}
|
||||
%{seq: ["esc"], desc: "Switch back to navigation mode"},
|
||||
%{seq: ["ctrl", "↵"], desc: "Evaluate cell and stay in insert mode"}
|
||||
],
|
||||
navigation_mode: [
|
||||
%{seq: "?", desc: "Open this help modal"},
|
||||
%{seq: "j", desc: "Focus next cell"},
|
||||
%{seq: "k", desc: "Focus previous cell"},
|
||||
%{seq: "J", desc: "Move cell down"},
|
||||
%{seq: "K", desc: "Move cell up"},
|
||||
%{seq: "i", desc: "Switch to insert mode"},
|
||||
%{seq: "n", desc: "Insert Elixir cell below"},
|
||||
%{seq: "m", desc: "Insert Markdown cell below"},
|
||||
%{seq: "N", desc: "Insert Elixir cell above"},
|
||||
%{seq: "M", desc: "Insert Markdown cell above"},
|
||||
%{seq: "dd", desc: "Delete cell"},
|
||||
%{seq: "ee", desc: "Evaluate cell"},
|
||||
%{seq: "es", desc: "Evaluate section"},
|
||||
%{seq: "ea", desc: "Evaluate all stale/new cells"},
|
||||
%{seq: "ej", desc: "Evaluate cells below"},
|
||||
%{seq: "ex", desc: "Cancel cell evaluation"}
|
||||
%{seq: ["?"], desc: "Open this help modal"},
|
||||
%{seq: ["j"], desc: "Focus next cell"},
|
||||
%{seq: ["k"], desc: "Focus previous cell"},
|
||||
%{seq: ["J"], desc: "Move cell down"},
|
||||
%{seq: ["K"], desc: "Move cell up"},
|
||||
%{seq: ["i"], desc: "Switch to insert mode"},
|
||||
%{seq: ["n"], desc: "Insert Elixir cell below"},
|
||||
%{seq: ["m"], desc: "Insert Markdown cell below"},
|
||||
%{seq: ["N"], desc: "Insert Elixir cell above"},
|
||||
%{seq: ["M"], desc: "Insert Markdown cell above"},
|
||||
%{seq: ["dd"], desc: "Delete cell"},
|
||||
%{seq: ["ee"], desc: "Evaluate cell"},
|
||||
%{seq: ["es"], desc: "Evaluate section"},
|
||||
%{seq: ["ea"], desc: "Evaluate all stale/new cells"},
|
||||
%{seq: ["ej"], desc: "Evaluate cells below"},
|
||||
%{seq: ["ex"], desc: "Cancel cell evaluation"}
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -35,10 +35,10 @@ defmodule LivebookWeb.SessionLive.ShortcutsComponent do
|
|||
def render(assigns) do
|
||||
~L"""
|
||||
<div class="p-6 sm:max-w-4xl sm:w-full flex flex-col space-y-3">
|
||||
<h3 class="text-lg font-medium text-gray-900">
|
||||
<h3 class="text-2xl font-semibold text-gray-800">
|
||||
Keyboard shortcuts
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
<p class="text-gray-700">
|
||||
Livebook highly embraces keyboard navigation to improve your productivity.
|
||||
It operates in one of two modes similarly to the Vim text editor.
|
||||
In <span class="font-semibold">navigation mode</span> you move around
|
||||
|
|
@ -78,10 +78,8 @@ defmodule LivebookWeb.SessionLive.ShortcutsComponent do
|
|||
<tbody>
|
||||
<%= for shortcut <- @shortcuts do %>
|
||||
<tr>
|
||||
<td class="py-1 pr-4">
|
||||
<span class="bg-editor text-editor py-0.5 px-2 rounded-md inline-flex items-center">
|
||||
<%= if(@platform == :mac, do: seq_for_mac(shortcut.seq), else: shortcut.seq) %>
|
||||
</span>
|
||||
<td class="py-2 pr-3">
|
||||
<%= render_seq(shortcut.seq, @platform) %>
|
||||
</td>
|
||||
<td>
|
||||
<%= shortcut.desc %>
|
||||
|
|
@ -93,10 +91,40 @@ defmodule LivebookWeb.SessionLive.ShortcutsComponent do
|
|||
"""
|
||||
end
|
||||
|
||||
defp render_seq(seq, platform) do
|
||||
seq = if(platform == :mac, do: seq_for_mac(seq), else: seq)
|
||||
|
||||
joiner = remix_icon("add-line", class: "text-xl text-gray-600")
|
||||
|
||||
elements =
|
||||
seq
|
||||
|> Enum.map(&render_key/1)
|
||||
|> Enum.intersperse(joiner)
|
||||
|
||||
assigns = %{elements: elements}
|
||||
|
||||
~L"""
|
||||
<div class="flex space-x-1 items-center">
|
||||
<%= for element <- @elements do %>
|
||||
<%= element %>
|
||||
<% end %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_key(key) do
|
||||
content_tag("span", key,
|
||||
class:
|
||||
"bg-editor text-gray-200 text-sm font-semibold h-8 w-8 flex items-center justify-center rounded-lg inline-flex items-center"
|
||||
)
|
||||
end
|
||||
|
||||
defp seq_for_mac(seq) do
|
||||
seq
|
||||
|> String.replace("ctrl", "cmd")
|
||||
|> String.replace("alt", "option")
|
||||
Enum.map(seq, fn
|
||||
"ctrl" -> "⌘"
|
||||
"alt" -> "⌥"
|
||||
key -> key
|
||||
end)
|
||||
end
|
||||
|
||||
defp split_in_half(list) do
|
||||
|
|
|
|||
|
|
@ -6,24 +6,23 @@ defmodule LivebookWeb.SessionsComponent do
|
|||
@impl true
|
||||
def render(assigns) do
|
||||
~L"""
|
||||
<div class="mt-3 flex flex-col space-y-2">
|
||||
<div class="flex flex-col space-y-4">
|
||||
<%= for summary <- @session_summaries do %>
|
||||
<div class="shadow rounded-md p-2">
|
||||
<div class="p-3 flex items-center">
|
||||
<div class="flex-grow flex flex-col space-y-1 text-gray-700 text-lg hover:text-gray-900">
|
||||
<%= live_redirect summary.notebook_name, to: Routes.session_path(@socket, :page, summary.session_id) %>
|
||||
<div class="text-gray-500 text-sm">
|
||||
<%= summary.path || "No file" %>
|
||||
</div>
|
||||
<div class="p-5 flex items-center border border-gray-200 rounded-lg">
|
||||
<div class="flex-grow flex flex-col space-y-1">
|
||||
<%= live_redirect summary.notebook_name, to: Routes.session_path(@socket, :page, summary.session_id),
|
||||
class: "font-semibold text-gray-800 hover:text-gray-900" %>
|
||||
<div class="text-gray-600 text-sm">
|
||||
<%= summary.path || "No file" %>
|
||||
</div>
|
||||
<button class="text-gray-500 hover:text-current"
|
||||
phx-click="delete_session"
|
||||
phx-value-id="<%= summary.session_id %>"
|
||||
phx-target="<%= @myself %>"
|
||||
aria-label="delete">
|
||||
<%= Icons.svg(:trash, class: "h-6") %>
|
||||
</button>
|
||||
</div>
|
||||
<button class="text-gray-400 hover:text-current"
|
||||
phx-click="delete_session"
|
||||
phx-value-id="<%= summary.session_id %>"
|
||||
phx-target="<%= @myself %>"
|
||||
aria-label="delete">
|
||||
<%= remix_icon("delete-bin-line", class: "text-xl") %>
|
||||
</button>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
<main role="main" class="flex-grow flex flex-col h-screen">
|
||||
<div class="fixed right-5 bottom-5 z-50 flex flex-col space-y-3">
|
||||
<%= if live_flash(@flash, :info) do %>
|
||||
<div class="flex items-center space-x-2 rounded-md px-4 py-2 bg-blue-100 text-blue-400 hover:opacity-75 cursor-pointer" role="alert"
|
||||
<div class="flex items-center space-x-2 rounded-lg px-4 py-2 bg-blue-100 text-blue-600 hover:opacity-75 cursor-pointer" role="alert"
|
||||
phx-click="lv:clear-flash"
|
||||
phx-value-key="info">
|
||||
<%= Icons.svg(:information_circle, class: "h-6 w-6") %>
|
||||
<%= remix_icon("information-line", class: "text-2xl") %>
|
||||
<span class="whitespace-pre"><%= live_flash(@flash, :info) %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= if live_flash(@flash, :error) do %>
|
||||
<div class="flex items-center space-x-2 rounded-md px-4 py-2 bg-red-100 text-red-400 hover:opacity-75 cursor-pointer" role="alert"
|
||||
<div class="flex items-center space-x-2 rounded-lg px-4 py-2 bg-red-100 text-red-400 hover:opacity-75 cursor-pointer" role="alert"
|
||||
phx-click="lv:clear-flash"
|
||||
phx-value-key="error">
|
||||
<%= Icons.svg(:exclamation_circle, class: "h-6 w-6") %>
|
||||
<%= remix_icon("error-warning-line", class: "text-2xl") %>
|
||||
<span class="whitespace-pre"><%= live_flash(@flash, :error) %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ defmodule LivebookWeb.HomeLiveTest do
|
|||
|
||||
test "disconnected and connected render", %{conn: conn} do
|
||||
{:ok, view, disconnected_html} = live(conn, "/")
|
||||
assert disconnected_html =~ "Livebook"
|
||||
assert render(view) =~ "Livebook"
|
||||
assert disconnected_html =~ "Running Sessions"
|
||||
assert render(view) =~ "Running Sessions"
|
||||
end
|
||||
|
||||
test "redirects to session upon creation", %{conn: conn} do
|
||||
|
|
@ -16,7 +16,7 @@ defmodule LivebookWeb.HomeLiveTest do
|
|||
|
||||
assert {:error, {:live_redirect, %{to: to}}} =
|
||||
view
|
||||
|> element("button", "New notebook")
|
||||
|> element("button", "New Notebook")
|
||||
|> render_click()
|
||||
|
||||
assert to =~ "/sessions/"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue