rsdeps

Cargo.lock visualizer (mirror)
Log | Files | Refs | README | LICENSE

commit 01c992395c26ace79d4dc2160c30090da6d7e43f
parent 7f48eba36823231f96fdc606f025caa35219c816
Author: Andy Khramtsov <>
Date:   Sat, 30 May 2026 06:08:40 +0300

feat: add about page

Diffstat:
Msrc/deps/dash_app.py | 4++--
Asrc/deps/pages/about.py | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/deps/pages/home.py | 56++++++++++++++++++++++++++++----------------------------
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