Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions artefact_kino/artefact_kino.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,11 @@ of how many labels either artefact contains. An `Agent` node will be the same sh
blue-green in a two-label artefact as it is in a ten-label one. You can follow a concept
by colour across panels without thinking about it.

**Stable header height.** When an artefact carries a long description — a Cypher query or
a chapter summary — the header expands and the graph viewports in the two panels no longer
align. Pass `max_description_lines:` to cap the description to a fixed number of lines with
overflow hidden. Both panels keep the same height and the graphs line up.
**Stable header height.** When artefacts have descriptions of different lengths — or when
one has no description at all — the headers expand differently and the graph viewports no
longer align. Pass `description_lines:` to reserve a fixed-height description area of
exactly that many lines. Content is clipped if it is longer; the space is held empty if
there is no description. Both panels share the same header height and the graphs line up.

```elixir
require Artefact
Expand Down Expand Up @@ -146,8 +147,8 @@ integrated =
)

Kino.Layout.grid([
ArtefactKino.new(section, max_description_lines: 2),
ArtefactKino.new(integrated, max_description_lines: 2)
ArtefactKino.new(section, description_lines: 2),
ArtefactKino.new(integrated, description_lines: 2)
], columns: 2)
```

Expand Down
32 changes: 19 additions & 13 deletions artefact_kino/lib/artefact_kino.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,19 @@ defmodule ArtefactKino do

Options:
- `default:` — `:create` (default) or `:merge`
- `max_description_lines:` — integer; caps the description to this many
lines with overflow hidden. Useful when placing widgets side by side so
that long descriptions do not cause misaligned graph viewports.
- `description_lines:` — integer; reserves a fixed-height description area
of exactly this many lines. Content is clipped if longer; an empty area
is reserved if there is no description. Keeps side-by-side graph viewports
at the same height regardless of description length or presence.
"""
def new(%Artefact{} = artefact, opts \\ []) do
Artefact.validate!(artefact)
default = Keyword.get(opts, :default, :create)
max_description_lines = Keyword.get(opts, :max_description_lines, nil)
Kino.JS.new(__MODULE__, build_data(artefact, default, max_description_lines))
description_lines = Keyword.get(opts, :description_lines, nil)
Kino.JS.new(__MODULE__, build_data(artefact, default, description_lines))
end

defp build_data(artefact, default, max_description_lines) do
defp build_data(artefact, default, description_lines) do
%{
nodes: vis_nodes(artefact),
edges: vis_edges(artefact),
Expand All @@ -50,7 +51,7 @@ defmodule ArtefactKino do
default: Atom.to_string(default),
title: artefact.title || artefact.base_label || "Artefact",
description: artefact.description,
max_description_lines: max_description_lines,
description_lines: description_lines,
artefact_rows: artefact_rows(artefact),
nodes_rows: nodes_rows(artefact),
rels_rows: rels_rows(artefact)
Expand Down Expand Up @@ -210,16 +211,21 @@ defmodule ArtefactKino do
.replace(/"/g, """)
.replace(/'/g, "'");

const descStyle = data.max_description_lines
? `font-size:11px;color:#888;margin-top:2px;font-style:italic;display:-webkit-box;-webkit-line-clamp:${data.max_description_lines};-webkit-box-orient:vertical;overflow:hidden;`
: `font-size:11px;color:#888;margin-top:2px;font-style:italic;white-space:pre-line;`;
const descHtml = (() => {
if (data.description_lines) {
const style = `font-size:11px;color:#888;margin-top:2px;font-style:italic;line-height:1.4;height:calc(${data.description_lines} * 1.4em);overflow:hidden;white-space:pre-line;`;
return `<div style="${style}">${data.description ? escapeHtml(data.description) : ""}</div>`;
}
if (data.description) {
return `<div style="font-size:11px;color:#888;margin-top:2px;font-style:italic;white-space:pre-line;">${escapeHtml(data.description)}</div>`;
}
return "";
})();

const headerHtml = `
<div style="padding:6px 8px;border-bottom:1px solid #333;">
<div style="font-size:13px;color:#aaa;">${escapeHtml(data.title)}</div>
${data.description
? `<div style="${descStyle}">${escapeHtml(data.description)}</div>`
: ""}
${descHtml}
</div>`;

ctx.root.innerHTML = `
Expand Down
Loading