commit 01c992395c26ace79d4dc2160c30090da6d7e43f
parent 7f48eba36823231f96fdc606f025caa35219c816
Author: Andy Khramtsov <>
Date: Sat, 30 May 2026 06:08:40 +0300
feat: add about page
Diffstat:
3 files changed, 95 insertions(+), 30 deletions(-)
diff --git a/src/deps/dash_app.py b/src/deps/dash_app.py
@@ -19,12 +19,12 @@ def main():
app.server.config["APP_STATE"] = state
app.layout = html.Div(
- className="vertical-content vertical-content_large-gap",
+ className="vertical-content vertical-content_medium-gap",
children=[
html.Div(
className=(
"padded-box horizontal-content horizontal-content_large-gap "
- "horizontal-content_spread horizontal-content_center"
+ "horizontal-content_spread horizontal-content_center shadow"
),
children=[
html.Div(
diff --git a/src/deps/pages/about.py b/src/deps/pages/about.py
@@ -0,0 +1,65 @@
+import dash
+from dash import html
+
+dash.register_page(__name__, path="/about", order=2)
+
+
+text = [
+ html.H2("Cargo dependency visualizer"),
+ html.P("This app represents Cargo.lock (from the Rust programming language) as a graph (or network)."),
+ html.P("You should read whatever is in the (?) boxes on the main page."),
+ html.H3("Why"),
+ html.P("I made this because I wanted to see visually what the dependency graph looks like on my Rust projects."),
+ html.P("And also because I knew I could make it in a couple of days in this horrible stack (explained further)."),
+ html.P(
+ "Well, I actually made it in one day, but it took another to make it into a presentable shape, "
+ "with all the helps and abouts and styling (believe it or not)."
+ ),
+ html.P("I am not sure how useful this app is in general, but it was to me."),
+ html.H3("The upload?"),
+ html.P(
+ 'The "upload" you see on the main page is not uploading anything to the server drive, it is just '
+ "a convenient way to put text from a file into a text box. "
+ ),
+ html.P("I don't want your stuff, what if you put some kind of malware in there, ew."),
+ html.H3("The stack"),
+ html.P("Python, Dash, Plotly, Polars, pydot."),
+ html.P("Yes, Python..."),
+ html.P(
+ "Dash makes heavy use of server callbacks, so there is a lot of back and forth happening when "
+ "using this app. Every little action is a server callback. This makes it not responsive."
+ ),
+ html.P(
+ "Also pydot is slow, but I don't know the network visualization stuff and whether there is anything "
+ "better out there. And frankly, I don't care."
+ ),
+ html.P(
+ "I mitigated this slowness as much as I could by only turning Cargo.lock into graph once "
+ "and caching the generated graph in browser memory. "
+ "So on each selection only the new visual representation (figure) is generated from whatever the "
+ "client sends, the whole graph is not being reordered again and again. "
+ "That is why the initial generation can take many seconds but selecting nodes is much faster."
+ ),
+ html.H3("It's bad"),
+ html.P("The container image for this thing is almost half a gigabyte. Draw your own conclusions."),
+ html.H2("Who am I"),
+ html.P("A programmer that writes code using a pair of hands and a keyboard, like a psycopath."),
+ html.A(
+ "https://andy.krmtsv.ru",
+ href="https://andy.krmtsv.ru",
+ target="_blank",
+ rel="noopener noreferrer",
+ ),
+]
+
+
+def layout():
+ return html.Div(
+ className="padded-box",
+ children=[
+ html.Div(
+ className="padded-box shadow",
+ children=text,
+ )
+ ],
+ )
diff --git a/src/deps/pages/home.py b/src/deps/pages/home.py
@@ -83,26 +83,28 @@ def layout():
dcc.Store(id=ids.cache_store, storage_type="memory"),
dcc.Store(id=ids.selected_node_store, storage_type="memory"),
html.Div(
- className="horizontal-content horizontal-content_center",
+ className="padded-box vertical-content shadow",
children=[
- html.H2("Cargo dependency visualisation"),
- html.Abbr(
- "?",
- title="Upload Cargo.lock and optionally Cargo.toml to see the full dependency graph. "
- "Multiple select avaliable for file upload. Or just paste the contents into the fields.\n\n"
- "It is possible to render the dependency graph of many projects because there is "
- "only one Cargo.lock.\n\n"
- "Note: Cargo.toml is only used for coloring [dependencies]. "
- "The implementation of Cargo.toml parsing is primitive, it will not work on many projects. "
- "But it is only the coloring feature, the graph is still fun to see.\n\n"
- "Text fields are used as storage and the source of truth, they persist data in local storage.",
- className="help-icon",
+ html.Div(
+ className="horizontal-content horizontal-content_center",
+ children=[
+ html.H3("Insert your dependencies"),
+ html.Abbr(
+ "?",
+ title="Upload Cargo.lock and optionally Cargo.toml to see the full dependency graph. "
+ "Multiple select avaliable for file upload. Or just paste the contents into the "
+ "fields.\n\n"
+ "It is possible to render the dependency graph of many projects because there is "
+ "only one Cargo.lock.\n\n"
+ "Note: Cargo.toml is only used for coloring [dependencies]. "
+ "The implementation of Cargo.toml parsing is primitive, it will not work on many "
+ "projects. But it is only the coloring feature, the graph is still fun to see.\n\n"
+ "Text fieldsare used as storage and the source of truth, they persist data in local "
+ "storage.",
+ className="help-icon",
+ ),
+ ],
),
- ],
- ),
- html.Div(
- className="vertical-content",
- children=[
dcc.Upload(
className="button",
id=ids.upload_files,
@@ -157,7 +159,7 @@ def layout():
),
content=[
html.Div(
- className="vertical-content",
+ className="padded-box vertical-content shadow",
children=[
html.Button(
className="button",
@@ -193,7 +195,7 @@ def layout():
],
),
html.Div(
- className="vertical-content",
+ className="padded-box vertical-content shadow",
children=[
html.Div(
className="horizontal-content horizontal-content_center",
@@ -697,18 +699,16 @@ def display_cargo_lock_packages(inputs, state):
prevent_initial_call=True,
)
def visualize(inputs, state):
- if (
- ctx.triggered_id
- and ctx.triggered_id == ids.generate_button
- or not state["cache"]
+ pressed_generate = bool(ctx.triggered_id and ctx.triggered_id == ids.generate_button)
+ invalid_cache = bool(
+ not state["cache"]
or state["cache"].get("positions") is None
or state["cache"].get("df_graph_nodes") is None
or state["cache"].get("df_graph_arcs") is None
or state["cache"].get("adjacency_list") is None
- ):
- clean = True
- else:
- clean = False
+ )
+
+ clean = pressed_generate or invalid_cache
if ctx.triggered_id and ctx.triggered_id == ids.reset_highlight_button:
selected = None