Browse Source

Merge branch 'master' into branch 'Issue#174'

master
silberengel 11 months ago
parent
commit
f397df2d44
No known key found for this signature in database
GPG Key ID: 962BEC8725790894
  1. 126
      src/lib/navigator/EventNetwork/NodeTooltip.svelte
  2. 18
      src/lib/navigator/EventNetwork/index.svelte
  3. 67
      src/routes/visualize/+page.svelte

126
src/lib/navigator/EventNetwork/NodeTooltip.svelte

@ -1,38 +1,128 @@ @@ -1,38 +1,128 @@
<script lang="ts">
import type { NetworkNode } from "./types";
import { onMount, createEventDispatcher } from "svelte";
export let node: NetworkNode;
export let selected: boolean = false;
export let x: number;
export let y: number;
let { node, selected = false, x, y } = $props<{
node: NetworkNode;
selected?: boolean;
x: number;
y: number;
}>();
const dispatch = createEventDispatcher();
let tooltipElement: HTMLDivElement;
let tooltipX = $state(x + 10);
let tooltipY = $state(y - 10);
function getAuthorTag(node: NetworkNode): string {
if (node.event) {
const authorTags = node.event.getMatchingTags("author");
if (authorTags.length > 0) {
return authorTags[0][1];
}
}
return "Unknown";
}
function getSummaryTag(node: NetworkNode): string | null {
if (node.event) {
const summaryTags = node.event.getMatchingTags("summary");
if (summaryTags.length > 0) {
return summaryTags[0][1];
}
}
return null;
}
function getDTag(node: NetworkNode): string {
if (node.event) {
const dTags = node.event.getMatchingTags("d");
if (dTags.length > 0) {
return dTags[0][1];
}
}
return "View Publication";
}
function truncateContent(content: string, maxLength: number = 200): string {
if (content.length <= maxLength) return content;
return content.substring(0, maxLength) + "...";
}
function closeTooltip() {
dispatch('close');
}
// Ensure tooltip is fully visible on screen
onMount(() => {
if (tooltipElement) {
const rect = tooltipElement.getBoundingClientRect();
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
// Check if tooltip goes off the right edge
if (rect.right > windowWidth) {
tooltipX = windowWidth - rect.width - 10;
}
// Check if tooltip goes off the bottom edge
if (rect.bottom > windowHeight) {
tooltipY = windowHeight - rect.height - 10;
}
// Check if tooltip goes off the left edge
if (rect.left < 0) {
tooltipX = 10;
}
// Check if tooltip goes off the top edge
if (rect.top < 0) {
tooltipY = 10;
}
}
});
</script>
<div
bind:this={tooltipElement}
class="tooltip-leather fixed p-4 rounded shadow-lg bg-primary-0 dark:bg-primary-800
border border-gray-200 dark:border-gray-800 transition-colors duration-200"
style="left: {x + 10}px; top: {y - 10}px; z-index: 1000;"
style="left: {tooltipX}px; top: {tooltipY}px; z-index: 1000; max-width: 400px;"
>
<div class="space-y-2">
<div class="font-bold text-base">{node.title}</div>
<button
class="absolute top-2 left-2 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 rounded-full p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
onclick={closeTooltip}
aria-label="Close"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</button>
<div class="space-y-2 pl-6">
<div class="font-bold text-base">
<a href="/publication?id={node.id}" class="text-gray-800 hover:text-primary-400 dark:text-gray-300 dark:hover:text-primary-500">
{node.title}
</a>
</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
{node.type} ({node.kind})
</div>
<div
class="text-gray-600 dark:text-gray-400 text-sm overflow-hidden text-ellipsis"
>
ID: {node.id}
{#if node.naddr}
<div>{node.naddr}</div>
{/if}
{#if node.nevent}
<div>{node.nevent}</div>
{/if}
<div class="text-gray-600 dark:text-gray-400 text-sm">
Author: {getAuthorTag(node)}
</div>
{#if node.isContainer && getSummaryTag(node)}
<div class="mt-2 text-xs bg-gray-100 dark:bg-gray-800 p-2 rounded overflow-auto max-h-40">
<span class="font-semibold">Summary:</span> {truncateContent(getSummaryTag(node) || "", 200)}
</div>
{/if}
{#if node.content}
<div
class="mt-2 text-xs bg-gray-100 dark:bg-gray-800 p-2 rounded overflow-auto max-h-40"
>
{node.content}
{truncateContent(node.content)}
</div>
{/if}
{#if selected}

18
src/lib/navigator/EventNetwork/index.svelte

@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
import { createSimulation, setupDragHandlers, applyGlobalLogGravity, applyConnectedGravity } from "./utils/forceSimulation";
import Legend from "./Legend.svelte";
import NodeTooltip from "./NodeTooltip.svelte";
import type { NetworkNode, NetworkLink } from "./types";
let { events = [] } = $props<{ events?: NDKEvent[] }>();
@ -90,14 +91,14 @@ @@ -90,14 +91,14 @@
function updateGraph() {
if (!svg || !events?.length || !svgGroup) return;
const { nodes, links } = generateGraph(events, currentLevels);
const { nodes, links } = generateGraph(events, Number(currentLevels));
if (!nodes.length) return;
// Stop any existing simulation
if (simulation) simulation.stop();
// Create new simulation
simulation = createSimulation(nodes, links, nodeRadius, linkDistance);
simulation = createSimulation(nodes, links, Number(nodeRadius), Number(linkDistance));
const dragHandler = setupDragHandlers(simulation);
// Update links
@ -303,6 +304,11 @@ @@ -303,6 +304,11 @@
updateGraph();
}
});
function handleTooltipClose() {
tooltipVisible = false;
selectedNodeId = null;
}
</script>
<div
@ -321,15 +327,9 @@ @@ -321,15 +327,9 @@
selected={tooltipNode.id === selectedNodeId}
x={tooltipX}
y={tooltipY}
on:close={handleTooltipClose}
/>
{/if}
<Legend />
</div>
<style>
.tooltip {
max-width: 300px;
word-wrap: break-word;
}
</style>

67
src/routes/visualize/+page.svelte

@ -79,44 +79,45 @@ @@ -79,44 +79,45 @@
</script>
<div class="leather w-full p-4 relative">
<h1 class="h-leather text-2xl font-bold mb-4">Publication Network</h1>
<!-- Settings Toggle Button -->
<!-- Settings Button - Using Flowbite Components -->
{#if !loading && !error}
<Button
class="btn-leather fixed right-4 top-24 z-40 rounded-lg min-w-[150px]"
size="sm"
on:click={() => (showSettings = !showSettings)}
>
<CogSolid class="mr-2 h-5 w-5" />
Settings
</Button>
<div class="flex items-center gap-4 mb-4">
<h1 class="h-leather text-2xl font-bold">Publication Network</h1>
<!-- Settings Button - Using Flowbite Components -->
{#if !loading && !error}
<Button
class="btn-leather z-10 rounded-lg min-w-[120px]"
on:click={() => (showSettings = !showSettings)}
>
<CogSolid class="mr-2 h-5 w-5" />
Settings
</Button>
{/if}
</div>
{#if !loading && !error && showSettings}
<!-- Settings Panel -->
{#if showSettings}
<div
class="fixed right-0 top-[140px] h-auto w-80 bg-white dark:bg-gray-800 p-4 shadow-lg z-30
overflow-y-auto max-h-[calc(100vh-96px)] rounded-l-lg border-l border-t border-b
border-gray-200 dark:border-gray-700"
transition:fly={{ duration: 300, x: 320, opacity: 1, easing: quintOut }}
>
<div class="card space-y-4">
<h2 class="text-xl font-bold mb-4 h-leather">
Visualization Settings
</h2>
<div
class="absolute left-[220px] top-14 h-auto w-80 bg-white dark:bg-gray-800 p-4 shadow-lg z-10
overflow-y-auto max-h-[calc(100vh-96px)] rounded-lg border
border-gray-200 dark:border-gray-700"
transition:fly={{ duration: 300, y: -10, opacity: 1, easing: quintOut }}
>
<div class="card space-y-4">
<h2 class="text-xl font-bold mb-4 h-leather">
Visualization Settings
</h2>
<div class="space-y-4">
<span class="text-sm text-gray-600 dark:text-gray-400">
Showing {events.length} events from {$networkFetchLimit} headers
</span>
<EventLimitControl on:update={handleLimitUpdate} />
<EventRenderLevelLimit on:update={handleLimitUpdate} />
</div>
<div class="space-y-4">
<span class="text-sm text-gray-600 dark:text-gray-400">
Showing {events.length} events from {$networkFetchLimit} headers
</span>
<EventLimitControl on:update={handleLimitUpdate} />
<EventRenderLevelLimit on:update={handleLimitUpdate} />
</div>
</div>
{/if}
</div>
{/if}
{#if loading}
<div class="flex justify-center items-center h-64">
<div role="status">
@ -155,6 +156,6 @@ @@ -155,6 +156,6 @@
</div>
{:else}
<EventNetwork {events} />
<div class="mt-8 prose dark:prose-invert max-w-none" />
<div class="mt-8 prose dark:prose-invert max-w-none"></div>
{/if}
</div>

Loading…
Cancel
Save