mirror of
https://github.com/20kaushik02/spotify-manager-web.git
synced 2025-12-06 10:34:07 +00:00
styling, edge selection
This commit is contained in:
parent
f54d8ccee6
commit
c9938aca35
@ -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;
|
||||||
|
|||||||
@ -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 ? (
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user