diff --git a/.gitignore b/.gitignore index 0371853..cf9faf4 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,7 @@ artefactory-*.tar # Temporary files, for example, from tests. /tmp/ -/drafts +/.drafts /.elixir_ls .DS_Store diff --git a/artefact_kino/CHANGELOG.md b/artefact_kino/CHANGELOG.md index 41b18e7..fc2038c 100644 --- a/artefact_kino/CHANGELOG.md +++ b/artefact_kino/CHANGELOG.md @@ -5,6 +5,10 @@ SPDX-License-Identifier: MIT # Changelog +## 0.1.4 — 2026-05-05 + +- Inspector panel collapsible (matching the Export panel); both default collapsed to give the graph more room on bigger artefacts; selecting a node or relationship in the graph auto-expands the Inspector. Bumps `artefact` requirement to `~> 0.1.4` for convenience. + ## 0.1.3 — 2026-04-30 - Compatible with `artefact ~> 0.1.3` diff --git a/artefact_kino/README.md b/artefact_kino/README.md index 53d2bb6..781bf50 100644 --- a/artefact_kino/README.md +++ b/artefact_kino/README.md @@ -18,7 +18,9 @@ ArtefactKino is a viewer, not an editor. It renders three panels side by side: - **Graph** (heartside) — an interactive vis-network graph. Nodes are colour-coded by label, with colours blended for multi-label nodes using circular hue averaging in linear RGB space. Layout strategies: Physics, Hierarchical, Radial. - **Inspector** — tabbed Elixir view of the artefact struct, nodes table, and relationships table. Clicking a node or relationship in the graph navigates to and highlights the corresponding row. -- **Export** — CREATE Cypher, MERGE Cypher, and Arrows JSON. Click any panel to select all text for easy copying. The export panel is collapsible to give more space to the graph and inspector. +- **Export** — CREATE Cypher, MERGE Cypher, Arrows JSON, and Mermaid source. Click any panel to select all text for easy copying. + +The Inspector and Export panels are both collapsible and start collapsed by default to give the graph room on bigger artefacts; selecting a node or relationship in the graph auto-expands the Inspector. MERGE Cypher upserts nodes by uuid — safe to run repeatedly. CREATE always makes new nodes. See the [CreateMerge artefact](https://github.com/diffo-dev/artefactory) for a visual explanation of the difference. diff --git a/artefact_kino/lib/artefact_kino.ex b/artefact_kino/lib/artefact_kino.ex index 42b4b16..0075e27 100644 --- a/artefact_kino/lib/artefact_kino.ex +++ b/artefact_kino/lib/artefact_kino.ex @@ -31,30 +31,35 @@ defmodule ArtefactKino do defp build_data(artefact, default) do %{ - nodes: vis_nodes(artefact), - edges: vis_edges(artefact), + nodes: vis_nodes(artefact), + edges: vis_edges(artefact), create_cypher: Artefact.Cypher.create(artefact), - merge_cypher: Artefact.Cypher.merge(artefact), - arrows_json: Artefact.Arrows.to_json(artefact), - mermaid: Artefact.Mermaid.export(artefact), - default: Atom.to_string(default), - title: artefact.title || artefact.base_label || "Artefact", - description: artefact.description, + merge_cypher: Artefact.Cypher.merge(artefact), + arrows_json: Artefact.Arrows.to_json(artefact), + mermaid: Artefact.Mermaid.export(artefact), + default: Atom.to_string(default), + title: artefact.title || artefact.base_label || "Artefact", + description: artefact.description, artefact_rows: artefact_rows(artefact), - nodes_rows: nodes_rows(artefact), - rels_rows: rels_rows(artefact) + nodes_rows: nodes_rows(artefact), + rels_rows: rels_rows(artefact) } end defp vis_nodes(%Artefact{graph: graph, base_label: base_label}) do Enum.map(graph.nodes, fn node -> - all_labels = Enum.uniq(node.labels ++ if(base_label, do: [base_label], else: [])) + all_labels = Enum.uniq(node.labels ++ if(base_label, do: [base_label], else: [])) semantic_labels = Enum.reject(node.labels, &(&1 == base_label)) - name = Map.get(node.properties, "name", node.id) - label = if semantic_labels == [], do: name, else: "#{name}\n#{Enum.join(semantic_labels, " ")}" - tooltip = node.properties + name = Map.get(node.properties, "name", node.id) + + label = + if semantic_labels == [], do: name, else: "#{name}\n#{Enum.join(semantic_labels, " ")}" + + tooltip = + node.properties |> Enum.map(fn {k, v} -> "#{k}: #{v}" end) |> Enum.join("\n") + %{id: node.id, label: label, labels: all_labels, title: "#{node.uuid}\n#{tooltip}"} end) end @@ -69,21 +74,21 @@ defmodule ArtefactKino do defp artefact_rows(%Artefact{} = a) do [ - %{key: "id", value: a.id}, - %{key: "uuid", value: a.uuid}, - %{key: "title", value: inspect(a.title)}, + %{key: "id", value: a.id}, + %{key: "uuid", value: a.uuid}, + %{key: "title", value: inspect(a.title)}, %{key: "description", value: inspect(a.description)}, - %{key: "base_label", value: inspect(a.base_label)}, - %{key: "metadata", value: inspect(a.metadata, pretty: true)} + %{key: "base_label", value: inspect(a.base_label)}, + %{key: "metadata", value: inspect(a.metadata, pretty: true)} ] end defp nodes_rows(%Artefact{graph: graph}) do Enum.map(graph.nodes, fn n -> %{ - id: n.id, - uuid: n.uuid, - labels: Enum.join(n.labels, ", "), + id: n.id, + uuid: n.uuid, + labels: Enum.join(n.labels, ", "), properties: inspect(n.properties) } end) @@ -94,10 +99,10 @@ defmodule ArtefactKino do |> Enum.with_index() |> Enum.map(fn {r, idx} -> %{ - idx: idx, - from: r.from_id, - type: r.type, - to: r.to_id, + idx: idx, + from: r.from_id, + type: r.type, + to: r.to_id, properties: inspect(r.properties) } end) @@ -219,11 +224,12 @@ defmodule ArtefactKino do -
-
+
+
+
@@ -235,7 +241,7 @@ defmodule ArtefactKino do - +

           
@@ -266,21 +272,41 @@ defmodule ArtefactKino do cypherBtns.forEach(b => b.addEventListener("click", () => { currentCypher = b.dataset.cypher; renderCypher(); })); renderCypher(); - // -- collapse export panel -- - const exportPanel = ctx.root.querySelector("#export-panel"); - const collapseBtn = ctx.root.querySelector("#collapse-btn"); - let exportCollapsed = false; - - btnStyle(collapseBtn, false); - collapseBtn.addEventListener("click", () => { - exportCollapsed = !exportCollapsed; - exportPanel.style.flex = exportCollapsed ? "0 0 32px" : "1"; - exportPanel.style.overflow = "hidden"; - collapseBtn.textContent = exportCollapsed ? "▶" : "◀"; - btnStyle(collapseBtn, exportCollapsed); - ctx.root.querySelector("#cypher").style.display = exportCollapsed ? "none" : ""; - ctx.root.querySelectorAll(".cbtn").forEach(b => b.style.display = exportCollapsed ? "none" : ""); - }); + // -- collapse helper (shared by inspector and export) -- + function setupCollapse(panel, button, hideSelectors, defaultCollapsed) { + let collapsed; + function setState(state) { + collapsed = state; + panel.style.flex = collapsed ? "0 0 32px" : "1"; + panel.style.overflow = "hidden"; + button.textContent = collapsed ? "▶" : "◀"; + btnStyle(button, collapsed); + // btnStyle uses cssText which wipes inline styles — reapply margin-left:auto + // so the button stays pushed to the right of the header strip when it is + // the only visible child (the panel-is-collapsed case). + button.style.marginLeft = "auto"; + hideSelectors.forEach(sel => { + ctx.root.querySelectorAll(sel).forEach(el => el.style.display = collapsed ? "none" : ""); + }); + } + setState(defaultCollapsed); + button.addEventListener("click", () => setState(!collapsed)); + return { expand: () => { if (collapsed) setState(false); } }; + } + + const inspectorControl = setupCollapse( + ctx.root.querySelector("#inspector-panel"), + ctx.root.querySelector("#inspector-collapse-btn"), + ["#tab-content", ".tbtn"], + true + ); + + const exportControl = setupCollapse( + ctx.root.querySelector("#export-panel"), + ctx.root.querySelector("#export-collapse-btn"), + ["#cypher", ".cbtn"], + true + ); // -- click to select all in pre elements -- function selectAll(el) { @@ -369,6 +395,7 @@ defmodule ArtefactKino do network.on("selectNode", ({ nodes: selected }) => { if (!selected.length) return; + inspectorControl.expand(); pendingHighlight = selected[0]; renderTab("nodes"); }); @@ -376,6 +403,7 @@ defmodule ArtefactKino do network.on("selectEdge", ({ edges: selected, nodes: selectedNodes }) => { if (!selected.length) return; if (selectedNodes && selectedNodes.length > 0) return; + inspectorControl.expand(); pendingHighlight = selected[0]; renderTab("rels"); }); diff --git a/artefact_kino/mix.exs b/artefact_kino/mix.exs index 63a73e4..ee0af6a 100644 --- a/artefact_kino/mix.exs +++ b/artefact_kino/mix.exs @@ -5,7 +5,7 @@ defmodule ArtefactKino.MixProject do @moduledoc false use Mix.Project - @version "0.1.3" + @version "0.1.4" @github_url "https://github.com/diffo-dev/artefactory" def project do @@ -17,7 +17,8 @@ defmodule ArtefactKino.MixProject do deps: deps(), package: package(), name: "ArtefactKino", - description: "Livebook Kino widget for rendering Artefactory knowledge graph fragments (Artefacts)", + description: + "Livebook Kino widget for rendering Artefactory knowledge graph fragments (Artefacts)", source_url: @github_url, docs: docs() ] @@ -41,13 +42,13 @@ defmodule ArtefactKino.MixProject do defp artefact_dep do cond do System.get_env("HEX_PUBLISH") == "1" -> - {:artefact, "~> 0.1.3"} + {:artefact, "~> 0.1.4"} File.exists?(Path.join(__DIR__, "../artefact/mix.exs")) -> {:artefact, path: "../artefact"} true -> - {:artefact, "~> 0.1.3"} + {:artefact, "~> 0.1.4"} end end @@ -64,7 +65,12 @@ defmodule ArtefactKino.MixProject do main: "ArtefactKino", source_url: @github_url, source_ref: "v#{@version}", - extras: ["README.md", "CHANGELOG.md"] + extras: [ + "README.md", + "CHANGELOG.md", + {"artefact_kino.livemd", title: "Livebook"}, + {"LICENSES/MIT.txt", title: "License (MIT)"} + ] ] end end