Skip to content

Commit

Permalink
Add note relation visualisation page (#6)
Browse files Browse the repository at this point in the history
* add safeguard for tag search

* add base visualisation

* add color to visualisation, and metadata changes
  • Loading branch information
bercivarga authored Mar 10, 2024
1 parent 6e3e937 commit f270915
Show file tree
Hide file tree
Showing 8 changed files with 1,292 additions and 49 deletions.
84 changes: 84 additions & 0 deletions app/(platform)/map/interactive-map.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"use client";

import { useEffect, useRef, useState } from "react";
import { ForceGraph3D } from "react-force-graph";

import { getAllNotes } from "@/helpers/notes/getAllNotes";
import { prepareNoteRelations } from "@/helpers/notes/prepareNoteRelations";

type Props = {
notes: NonNullable<Awaited<ReturnType<typeof getAllNotes>>>;
};

type GraphData = {
nodes: {
id: string;
name: string;
val: number;
}[];
links: {
source: string;
target: string;
}[];
};

function createLinksFromAllNotes(notes: Props["notes"]): GraphData["links"] {
return notes
.map((note) => {
return prepareNoteRelations(note).map((link) => ({
source: note.id,
target: link.id,
}));
})
.flat();
}

function prepareNotesForGraph(notes: Props["notes"]): GraphData {
const nodes = notes.map((note) => ({
id: note.id,
name: note.title,
val: 1,
}));

const links = createLinksFromAllNotes(notes);

return { nodes, links };
}

export default function InteractiveMap({ notes }: Props) {
const [data, setData] = useState<GraphData | null>(null);
const containerRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const graphData = prepareNotesForGraph(notes);
setData(graphData);
}, [notes]);

if (!data) {
return (
<div
className="absolute inset-0 flex items-center justify-center bg-slate-900 text-white"
ref={containerRef}
>
<div>Preparing your graph...</div>
</div>
);
}

return (
<div
className="absolute inset-0 hover:cursor-grab active:cursor-grabbing"
ref={containerRef}
>
<ForceGraph3D
width={containerRef.current?.clientWidth ?? 0}
height={containerRef.current?.clientHeight ?? 0}
graphData={data}
nodeAutoColorBy="name"
linkDirectionalParticles="value"
linkDirectionalParticleSpeed={(d) => d.value * 0.001}
linkDirectionalParticleResolution={3}
/>
</div>
);
}
37 changes: 3 additions & 34 deletions app/(platform)/map/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,14 @@
import { Metadata } from "next";

import {
Menubar,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarSeparator,
MenubarTrigger,
} from "@/components/ui/menubar";

export const metadata: Metadata = {
title: "Map",
description: "Map",
title: "Map of your notes",
description: "Explore your notes and their relationships.",
};

export default function MapLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<div className="relative h-screen w-full">
{children}
<div className="absolute bottom-6 left-1/2 -translate-x-1/2">
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem>Save raster output</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent>
<MenubarItem>2D</MenubarItem>
<MenubarSeparator />
<MenubarItem>3D</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
</div>
</div>
);
return <div className="relative h-screen w-full">{children}</div>;
}
15 changes: 12 additions & 3 deletions app/(platform)/map/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
export default function MapPage() {
import { getAllNotes } from "@/helpers/notes/getAllNotes";

import InteractiveMap from "./interactive-map";

export default async function MapPage() {
const notes = await getAllNotes();

return (
<main>
<h2>Map</h2>
<main className="relative h-screen w-full">
<InteractiveMap notes={notes ?? []} />
</main>
);
}

export const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";
6 changes: 5 additions & 1 deletion app/(platform)/notes/[noteId]/tag-manager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ export default function TagManager({ note, open, setOpen }: Props) {
setOpen(false);
}

const showTagCreationSuggestion = !!localSearch;
const showTagCreationSuggestion =
!!localSearch &&
!results.some(
(tag) => tag.name.toLowerCase() === localSearch.toLowerCase()
);

return (
<CommandDialog open={open} onOpenChange={handleClose}>
Expand Down
1 change: 1 addition & 0 deletions app/(platform)/notes/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,6 @@ export default async function AllNotesPage() {
</main>
);
}

export const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";
2 changes: 1 addition & 1 deletion helpers/notes/getAllNotes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export async function getAllNotes() {

const notes = await prisma.note.findMany({
where: { authorId: dbUser.id, deleted: false },
include: { tags: true },
include: { tags: true, relatedNotes: true, relatedTo: true },
orderBy: { updatedAt: "desc" },
take: 10,
});
Expand Down
Loading

0 comments on commit f270915

Please sign in to comment.