diff --git a/assets/css/components.css b/assets/css/components.css index 05c952664..d653390db 100644 --- a/assets/css/components.css +++ b/assets/css/components.css @@ -36,6 +36,14 @@ @apply px-2 py-1 bg-gray-50 border-gray-200 text-gray-600 hover:bg-gray-100 focus:bg-gray-100; } +.button-square-icon { + @apply p-0 flex items-center justify-center h-10 w-10; +} + +.button-square-icon i { + @apply text-xl leading-none; +} + .choice-button { @apply px-5 py-2 rounded-lg border text-gray-700 bg-white border-gray-200; } diff --git a/assets/css/tooltips.css b/assets/css/tooltips.css index 77e69a1e4..5631dd01d 100644 --- a/assets/css/tooltips.css +++ b/assets/css/tooltips.css @@ -55,14 +55,12 @@ Example usage: transition-delay: 0s; } -.tooltip:hover:before, -.tooltip:focus-within:before { +.tooltip:hover:before { visibility: visible; transition-delay: var(--show-delay); } -.tooltip:hover:after, -.tooltip:focus-within:after { +.tooltip:hover:after { visibility: visible; transition-delay: var(--show-delay); } diff --git a/lib/livebook/evaluator/string_formatter.ex b/lib/livebook/evaluator/string_formatter.ex index 8a6bce9f3..ce57cd9fd 100644 --- a/lib/livebook/evaluator/string_formatter.ex +++ b/lib/livebook/evaluator/string_formatter.ex @@ -13,8 +13,13 @@ defmodule Livebook.Evaluator.StringFormatter do :ignored end + def format({:ok, {:module, _, _, _} = value}) do + inspected = inspect(value, inspect_opts(limit: 10)) + {:inspect, inspected} + end + def format({:ok, value}) do - inspected = inspect(value, pretty: true, width: 100, syntax_colors: syntax_colors()) + inspected = inspect(value, inspect_opts()) {:inspect, inspected} end @@ -23,6 +28,11 @@ defmodule Livebook.Evaluator.StringFormatter do {:error, formatted} end + defp inspect_opts(opts \\ []) do + default_opts = [pretty: true, width: 100, syntax_colors: syntax_colors()] + Keyword.merge(default_opts, opts) + end + defp syntax_colors() do # Note: we intentionally don't specify colors # for `:binary`, `:list`, `:map` and `:tuple` diff --git a/lib/livebook/notebook/hello_livebook.ex b/lib/livebook/notebook/hello_livebook.ex new file mode 100644 index 000000000..dda3bf6f3 --- /dev/null +++ b/lib/livebook/notebook/hello_livebook.ex @@ -0,0 +1,191 @@ +defmodule Livebook.Notebook.HelloLivebook do + livemd = ~s''' + # Hello Livebook + + ## Introduction + + We are happy you decided to give Livebook a try, hopefully it empowers + you to build great stuff 🚀 + + Livebook is a tool for crafting **interactive** and **collaborative** code notebooks. + It is primarily meant as a tool rapid prototyping - think of it as an IEx session + combined with your editor. + You can also use it for authoring shareable articles that people can easily + run and play around with. + Package authors can write notebooks as interactive tutorials + and include them in their repository, so that users can easily download + and run them locally. + + ## Basic usage + + Each notebook consists of a number of cells, which serve as primary building blocks. + There are **Markdown** cells (such as this one) that allow you to describe your work + and **Elixir** cells where the magic takes place! + + To insert a new cell move your between cells and click one of the revealed buttons. 👇 + + ```elixir + # This is an Elixir cell - as the name suggests that's where the code goes. + # To evaluate this cell, you can either press the "Evaluate" button above + # or use `ctrl + enter` like a boss! + + message = "hey, grab yourself a cup of 🍵" + ``` + + Subsequent cells have access to the bindings you defined! + + ```elixir + String.replace(message, "🍵", "☕") + ``` + + Note however that bindings are not global, so each cell *sees* only stuff that goes + above itself. This approach helps to keep the notebook clean and predictable + as you keep working on it! + + ## Sections + + You can leverage so called **sections** to nicely group related cells together. + Click on the book icon in the left sidebar to reveal a list of all sections. + As you can see, this approach helps to easily jump around the notebook, + especially once it gets grows. + + Let's make use of this section to see how output is captured! + + ```elixir + cats = ~w(😼 😹 😻 😺 😸 😽) + + for _ <- 1..3 do + cats + |> Enum.take_random(3) + |> Enum.join(" ") + |> IO.puts() + end + ``` + + ## Notebook files + + My default notebooks are kept in memory, which is fine for interactive hacking, + but oftentimes you will want to save your work for later. Fortunately notebooks + can be persisted by clicking on the "Settings" icon in the left sidebar + and selecting the file location. + + Notebooks are stored in **live markdown** format, which is essentially the markdown you know, + with just a few assumptions on how particular elements are represented. Thanks to this + approach you can easily keep notebooks under version control and get readable diffs. + You can also easily preview those files, reuse for blog posts and even edit in a text editor. + + ## Modules + + As we already saw, Elixir cells can be used for working on tiny snippets, + but you may as well define a module! + + ```elixir + defmodule Utils do + @doc """ + Generates a random binary id. + """ + @spec random_id() :: binary() + def random_id() do + :crypto.strong_rand_bytes(20) |> Base.encode32(case: :lower) + end + end + ``` + + If you're surprised by the above output, keep in mind that + every Elixir expression evaluates to some value and as so does module compilation! + + Having the module defined, let's take it for a spin. + + ```elixir + Utils.random_id() + ``` + + ## Imports + + You can import modules as normally to make the imported functions visible + to all subsequent cells. Usually you want to keep `import`, `alias` and `require` + in the first section, as part of the notebook setup. + + ```elixir + import IEx.Helpers + ``` + + ```elixir + h(Enum.map()) + ``` + + ```elixir + # Sidenote: http://www.numbat.org.au/thenumbat + i("I ❤️ Numbats") + ``` + + ## Runtimes + + Livebook has a concept of **runtime**, which in practice is an Elixir node responsible + for evaluating your code. + + By default a new Elixir node is started (similarly to starting `iex`), + but you can also choose to run inside a Mix project (as you would with `iex -S mix`) + or even manually attach to an existing distributed node! + You can configure the runtime under "Notebook settings". + + ## Using packages + + Sometimes you need a dependency or two and notebooks are no exception to this. + + One way to work with packages is to create a Mix project and configure the notebook + to run its context (as pointed out above). This approach makes sense if you already have + a Mix project you are working on, especially because this makes all project's + modules available as well. + + But there are cases when you just want to play around with a new package + or quickly prototype some code that relies on such. Fortunately, starting + version `v1.12` Elixir ships with `Mix.install/2` that allows for installing + dependencies into Elixir runtime! This approach is especially useful for sharing notebooks, + because everyone will be able to get the same dependencies. Let's try this out: + + ```elixir + # Note: this requires Elixir version >= 1.12 + Mix.install([ + {:jason, "~> 1.2"} + ]) + ``` + + ```elixir + %{elixir: "rulez"} + |> Jason.encode!() + |> IO.puts() + ``` + + It is a good idea to specify versions of the installed packages, + so that the notebook is easily reproducible later on. + + Also keep in mind that `Mix.install/2` can be called only once + per runtime, so if you need to modify the dependencies, you should + go to the notebook runtime configuration and **reconnect** the current runtime. + + ## Stepping up your workflow + + Once you start using notebooks more, it's gonna be beneficial + to optimise how you move around. Livebook leverages the concept of + **navigation**/**insert** modes and offers many shortcuts for common operations. + Make sure to check out the shortcuts by clicking the "Keyboard" icon in + the sidebar panel or by typing `?`. + + ## Final notes + + Livebook is an open source project, so feel free to look into + [the repository](https://github.com/elixir-nx/livebook) + to contribute, report bugs, suggest features or just skim over the codebase. + + Now go ahead and build something cool! 🚢 + ''' + + {notebook, []} = Livebook.LiveMarkdown.Import.notebook_from_markdown(livemd) + + @notebook notebook + + def new() do + @notebook + end +end diff --git a/lib/livebook_web/live/home_live.ex b/lib/livebook_web/live/home_live.ex index e31184d8d..e2c138b08 100644 --- a/lib/livebook_web/live/home_live.ex +++ b/lib/livebook_web/live/home_live.ex @@ -36,10 +36,18 @@ defmodule LivebookWeb.HomeLive do