styling, edge selection

This commit is contained in:
Kaushik Narayan R 2025-01-07 22:49:10 -07:00
parent f54d8ccee6
commit c9938aca35
6 changed files with 96 additions and 46 deletions

View File

@ -4,6 +4,7 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-around;
gap: var(--mb-1);
height: 100vh; height: 100vh;
width: 10vw; width: 10vw;
position: sticky; position: sticky;

View File

@ -9,7 +9,7 @@ const Navbar = () => {
const auth = useContext(AuthContext); const auth = useContext(AuthContext);
return ( return (
<nav className={styles.navbar_wrapper}> <nav className={`${styles.navbar_wrapper} custom_scrollbar`}>
<StyledNavLink path="/" text="About" /> <StyledNavLink path="/" text="About" />
<StyledNavLink path="/graph" text="Graph" /> <StyledNavLink path="/graph" text="Graph" />
{auth === true ? ( {auth === true ? (

View File

@ -1,10 +1,11 @@
a { a {
padding: var(--mb-3); padding: var(--mb-3) var(--mb-1);
border-radius: 2%; border-radius: 2%;
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;
color: var(--text); color: var(--text);
text-align: center;
} }
.active_link { .active_link {

View File

@ -89,3 +89,20 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace; monospace;
} }
/* Custom scrollbar */
.custom_scrollbar::-webkit-scrollbar-track {
border-radius: 1rem;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
background-color: var(--text);
}
.custom_scrollbar::-webkit-scrollbar {
width: 10px;
}
.custom_scrollbar::-webkit-scrollbar-thumb {
border-radius: 1rem;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
background-color: var(--bg);
}

View File

@ -14,6 +14,11 @@
gap: var(--mb-1); gap: var(--mb-1);
height: 100vh; height: 100vh;
width: 10vw; width: 10vw;
position: sticky;
position: -webkit-sticky;
top: 0;
right: 0;
overflow: auto;
padding: var(--mb-3); padding: var(--mb-3);
} }

View File

@ -7,10 +7,10 @@ import {
addEdge, addEdge,
applyNodeChanges, applyNodeChanges,
applyEdgeChanges, applyEdgeChanges,
useOnSelectionChange,
useReactFlow, useReactFlow,
MarkerType, MarkerType,
BackgroundVariant, BackgroundVariant,
ConnectionLineType,
type DefaultEdgeOptions, type DefaultEdgeOptions,
type ProOptions, type ProOptions,
type ReactFlowInstance, type ReactFlowInstance,
@ -18,8 +18,8 @@ import {
type Edge, type Edge,
type OnNodesChange, type OnNodesChange,
type OnEdgesChange, type OnEdgesChange,
type OnSelectionChangeFunc,
type OnConnect, type OnConnect,
type OnDelete,
type OnBeforeDelete, type OnBeforeDelete,
} from "@xyflow/react"; } from "@xyflow/react";
import Dagre from "@dagrejs/dagre"; import Dagre from "@dagrejs/dagre";
@ -91,7 +91,7 @@ const initialInteractive: Interactive = {
}; };
const edgeOptions: DefaultEdgeOptions = { const edgeOptions: DefaultEdgeOptions = {
animated: true, animated: false,
style: { style: {
stroke: "white", stroke: "white",
strokeWidth: 2, strokeWidth: 2,
@ -105,6 +105,21 @@ const edgeOptions: DefaultEdgeOptions = {
}, },
}; };
const selectedEdgeOptions: DefaultEdgeOptions = {
animated: true,
style: {
stroke: "red",
strokeWidth: 2,
},
markerEnd: {
type: MarkerType.ArrowClosed,
color: "red",
width: 16,
height: 16,
orient: "auto",
},
};
const proOptions: ProOptions = { hideAttribution: true }; const proOptions: ProOptions = { hideAttribution: true };
const Graph = () => { const Graph = () => {
@ -119,39 +134,6 @@ const Graph = () => {
console.debug("flow loaded"); console.debug("flow loaded");
}; };
const onFlowBeforeDelete: OnBeforeDelete = useCallback(
async ({ nodes, edges }) => {
// can't delete playlists
if (nodes.length > 0) {
showErrorToastNotification("Can't delete playlists!");
return false;
}
return { nodes, edges };
},
[]
);
const onFlowAfterDelete: OnDelete = useCallback(
async ({ nodes, edges }) => {
console.debug(
`deleted connection: ${edges[0].source} -> ${edges[0].target}`
);
// call API to delete link
const spotifyPlaylistLinkPrefix = "https://open.spotify.com/playlist/";
const resp = await APIWrapper({
apiFn: apiDeleteLink,
data: {
from: spotifyPlaylistLinkPrefix + edges[0].source,
to: spotifyPlaylistLinkPrefix + edges[0].target,
},
refreshAuth,
});
if (resp?.status === 200)
showSuccessToastNotification(resp?.data.message);
},
[refreshAuth]
);
// base event handling // base event handling
const onNodesChange: OnNodesChange = useCallback( const onNodesChange: OnNodesChange = useCallback(
(changes) => setPlaylistNodes((nds) => applyNodeChanges(changes, nds)), (changes) => setPlaylistNodes((nds) => applyNodeChanges(changes, nds)),
@ -162,7 +144,25 @@ const Graph = () => {
[setLinkEdges] [setLinkEdges]
); );
const onConnect: OnConnect = useCallback( const onFlowSelectionChange: OnSelectionChangeFunc = useCallback(
({ nodes, edges }) => {
const newSelectedID = edges[0]?.id ?? "";
setLinkEdges((eds) =>
eds.map((ed) =>
ed.id === newSelectedID
? { ...ed, ...selectedEdgeOptions }
: { ...ed, ...edgeOptions }
)
);
},
[setLinkEdges]
);
useOnSelectionChange({
onChange: onFlowSelectionChange,
});
// new edge
const onFlowConnect: OnConnect = useCallback(
async (connection) => { async (connection) => {
console.debug( console.debug(
`new connection: ${connection.source} -> ${connection.target}` `new connection: ${connection.source} -> ${connection.target}`
@ -185,6 +185,36 @@ const Graph = () => {
[setLinkEdges, refreshAuth] [setLinkEdges, refreshAuth]
); );
// remove edge
const onFlowBeforeDelete: OnBeforeDelete = useCallback(
async ({ nodes, edges }) => {
// can't delete playlists
if (nodes.length > 0) {
showErrorToastNotification("Can't delete playlists!");
return false;
}
console.debug(
`deleted connection: ${edges[0].source} -> ${edges[0].target}`
);
// call API to delete link
const spotifyPlaylistLinkPrefix = "https://open.spotify.com/playlist/";
const resp = await APIWrapper({
apiFn: apiDeleteLink,
data: {
from: spotifyPlaylistLinkPrefix + edges[0].source,
to: spotifyPlaylistLinkPrefix + edges[0].target,
},
refreshAuth,
});
if (resp?.status === 200) {
showSuccessToastNotification(resp?.data.message);
return { nodes, edges };
}
return false;
},
[refreshAuth]
);
type getLayoutedElementsOpts = { type getLayoutedElementsOpts = {
direction: rankdirType; direction: rankdirType;
}; };
@ -335,8 +365,6 @@ const Graph = () => {
useEffect(() => { useEffect(() => {
fetchGraph(); fetchGraph();
// TODO: how to invoke async and sync fns in order correctly inside useEffect?
// refreshGraph();
}, [fetchGraph]); }, [fetchGraph]);
const disableInteractive = () => { const disableInteractive = () => {
@ -369,10 +397,9 @@ const Graph = () => {
edges={linkEdges} edges={linkEdges}
defaultEdgeOptions={edgeOptions} defaultEdgeOptions={edgeOptions}
proOptions={proOptions} proOptions={proOptions}
connectionLineType={ConnectionLineType.SmoothStep}
connectOnClick={false}
fitView fitView
colorMode={"light"} colorMode={"light"}
elevateEdgesOnSelect
edgesReconnectable={false} edgesReconnectable={false}
nodesFocusable={false} nodesFocusable={false}
nodesDraggable={interactive.ndDrag} nodesDraggable={interactive.ndDrag}
@ -382,16 +409,15 @@ const Graph = () => {
multiSelectionKeyCode={null} multiSelectionKeyCode={null}
onInit={onFlowInit} onInit={onFlowInit}
onBeforeDelete={onFlowBeforeDelete} onBeforeDelete={onFlowBeforeDelete}
onDelete={onFlowAfterDelete}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onConnect={onConnect} onConnect={onFlowConnect}
> >
<Controls onInteractiveChange={toggleInteractive} /> <Controls onInteractiveChange={toggleInteractive} />
<MiniMap pannable zoomable /> <MiniMap pannable zoomable />
<Background variant={BackgroundVariant.Dots} gap={36} size={3} /> <Background variant={BackgroundVariant.Dots} gap={36} size={3} />
</ReactFlow> </ReactFlow>
<div className={styles.operations_wrapper}> <div className={`${styles.operations_wrapper} custom_scrollbar`}>
<Button onClickMethod={refreshGraph}> <Button onClickMethod={refreshGraph}>
<WiCloudRefresh size={36} /> <WiCloudRefresh size={36} />
Refresh Graph Refresh Graph