Browse Source

refactor: legend now using inline tailwind

master
limina1 8 months ago
parent
commit
0003ebccd4
  1. 277
      src/lib/navigator/EventNetwork/Legend.svelte
  2. 6
      src/lib/navigator/EventNetwork/index.svelte

277
src/lib/navigator/EventNetwork/Legend.svelte

@ -89,11 +89,14 @@
</div> </div>
{#if expanded} {#if expanded}
<div class="legend-content"> <div class="space-y-4">
<!-- Node Types Section --> <!-- Node Types Section -->
<div class="legend-section"> <div class="border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0">
<div class="legend-section-header" onclick={toggleNodeTypes}> <div
<h4 class="legend-section-title">Node Types</h4> class="flex justify-between items-center cursor-pointer py-2 mb-3 hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-md px-2 -mx-2 transition-colors"
onclick={toggleNodeTypes}
>
<h4 class="font-semibold text-gray-700 dark:text-gray-300 m-0 text-sm">Node Types</h4>
<div class="pointer-events-none"> <div class="pointer-events-none">
{#if nodeTypesExpanded} {#if nodeTypesExpanded}
<CaretUpOutline class="w-3 h-3" /> <CaretUpOutline class="w-3 h-3" />
@ -104,22 +107,22 @@
</div> </div>
{#if nodeTypesExpanded} {#if nodeTypesExpanded}
<ul class="legend-list"> <ul class="space-y-2">
<!-- Dynamic event kinds --> <!-- Dynamic event kinds -->
{#each Object.entries(eventCounts).sort(([a], [b]) => Number(a) - Number(b)) as [kindStr, count]} {#each Object.entries(eventCounts).sort(([a], [b]) => Number(a) - Number(b)) as [kindStr, count]}
{@const kind = Number(kindStr)} {@const kind = Number(kindStr)}
{@const color = getEventKindColor(kind)} {@const color = getEventKindColor(kind)}
{@const name = getEventKindName(kind)} {@const name = getEventKindName(kind)}
{#if count > 0} {#if (count as number) > 0}
<li class="legend-item"> <li class="flex items-center mb-2 last:mb-0">
<div class="legend-icon"> <div class="flex items-center mr-2">
<span <span
class="legend-circle" class="w-4 h-4 rounded-full"
style="background-color: {color}" style="background-color: {color}"
> >
</span> </span>
</div> </div>
<span class="legend-text"> <span class="text-sm">
{kind} - {name} ({count}) {kind} - {name} ({count})
</span> </span>
</li> </li>
@ -127,7 +130,7 @@
{/each} {/each}
<!-- Connection lines --> <!-- Connection lines -->
<li class="legend-item"> <li class="flex items-center mb-2 last:mb-0">
<svg class="w-6 h-6 mr-2" viewBox="0 0 24 24"> <svg class="w-6 h-6 mr-2" viewBox="0 0 24 24">
<path <path
d="M4 12h16M16 6l6 6-6 6" d="M4 12h16M16 6l6 6-6 6"
@ -136,7 +139,7 @@
fill="none" fill="none"
/> />
</svg> </svg>
<span class="legend-text"> <span class="text-sm">
{#if starMode} {#if starMode}
Radial connections from centers to related events Radial connections from centers to related events
{:else} {:else}
@ -147,7 +150,7 @@
<!-- Edge colors for person connections --> <!-- Edge colors for person connections -->
{#if showPersonNodes && personAnchors.length > 0} {#if showPersonNodes && personAnchors.length > 0}
<li class="legend-item"> <li class="flex items-center mb-2 last:mb-0">
<svg class="w-6 h-6 mr-2" viewBox="0 0 24 24"> <svg class="w-6 h-6 mr-2" viewBox="0 0 24 24">
<path <path
d="M4 12h16" d="M4 12h16"
@ -156,11 +159,11 @@
fill="none" fill="none"
/> />
</svg> </svg>
<span class="legend-text text-xs"> <span class="text-xs">
Authored by person Authored by person
</span> </span>
</li> </li>
<li class="legend-item"> <li class="flex items-center mb-2 last:mb-0">
<svg class="w-6 h-6 mr-2" viewBox="0 0 24 24"> <svg class="w-6 h-6 mr-2" viewBox="0 0 24 24">
<path <path
d="M4 12h16" d="M4 12h16"
@ -169,7 +172,7 @@
fill="none" fill="none"
/> />
</svg> </svg>
<span class="legend-text text-xs"> <span class="text-xs">
References person References person
</span> </span>
</li> </li>
@ -179,9 +182,12 @@
</div> </div>
<!-- Tag Anchor Controls Section --> <!-- Tag Anchor Controls Section -->
<div class="legend-section"> <div class="border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0">
<div class="legend-section-header" onclick={() => tagControlsExpanded = !tagControlsExpanded}> <div
<h4 class="legend-section-title">Tag Anchor Controls</h4> class="flex justify-between items-center cursor-pointer py-2 mb-3 hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-md px-2 -mx-2 transition-colors"
onclick={() => tagControlsExpanded = !tagControlsExpanded}
>
<h4 class="font-semibold text-gray-700 dark:text-gray-300 m-0 text-sm">Tag Anchor Controls</h4>
<div class="pointer-events-none"> <div class="pointer-events-none">
{#if tagControlsExpanded} {#if tagControlsExpanded}
<CaretUpOutline class="w-3 h-3" /> <CaretUpOutline class="w-3 h-3" />
@ -200,7 +206,7 @@
showTagAnchors = !showTagAnchors; showTagAnchors = !showTagAnchors;
onTagSettingsChange(); onTagSettingsChange();
}} }}
class="toggle-button {showTagAnchors ? 'active' : ''}" class="px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 text-xs font-medium cursor-pointer transition-all min-w-12 hover:bg-gray-200 dark:hover:bg-gray-600 {showTagAnchors ? 'bg-blue-500 text-white border-blue-500 hover:bg-blue-600' : ''}"
> >
{showTagAnchors ? 'ON' : 'OFF'} {showTagAnchors ? 'ON' : 'OFF'}
</button> </button>
@ -231,9 +237,12 @@
<!-- Tag Anchors section --> <!-- Tag Anchors section -->
{#if showTags && tagAnchors.length > 0} {#if showTags && tagAnchors.length > 0}
<div class="legend-section"> <div class="border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0">
<div class="legend-section-header" onclick={toggleTagAnchors}> <div
<h4 class="legend-section-title">Active Tag Anchors: {tagAnchors[0].type}</h4> class="flex justify-between items-center cursor-pointer py-2 mb-3 hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-md px-2 -mx-2 transition-colors"
onclick={toggleTagAnchors}
>
<h4 class="font-semibold text-gray-700 dark:text-gray-300 m-0 text-sm">Active Tag Anchors: {tagAnchors[0].type}</h4>
<div class="pointer-events-none"> <div class="pointer-events-none">
{#if tagAnchorsExpanded} {#if tagAnchorsExpanded}
<CaretUpOutline class="w-3 h-3" /> <CaretUpOutline class="w-3 h-3" />
@ -285,10 +294,10 @@
type="checkbox" type="checkbox"
onclick={() => { onclick={() => {
// Invert selection - toggle all tags one by one // Invert selection - toggle all tags one by one
const allTagIds = tagAnchors.map(anchor => `${anchor.type}-${anchor.label}`); const allTagIds = tagAnchors.map((anchor: any) => `${anchor.type}-${anchor.label}`);
// Process all tags // Process all tags
allTagIds.forEach(tagId => { allTagIds.forEach((tagId: string) => {
onTagToggle(tagId); onTagToggle(tagId);
}); });
}} }}
@ -299,23 +308,23 @@
</div> </div>
<div <div
class="tag-grid {tagAnchors.length > 20 ? 'scrollable' : ''}" class="grid gap-1 {tagAnchors.length > 20 ? 'max-h-96 overflow-y-auto pr-2' : ''}"
style="grid-template-columns: repeat({TAG_LEGEND_COLUMNS}, 1fr);" style="grid-template-columns: repeat({TAG_LEGEND_COLUMNS}, 1fr);"
> >
{#each sortedAnchors as anchor} {#each sortedAnchors as anchor}
{@const tagId = `${anchor.type}-${anchor.label}`} {@const tagId = `${anchor.type}-${anchor.label}`}
{@const isDisabled = disabledTags.has(tagId)} {@const isDisabled = disabledTags.has(tagId)}
<button <button
class="tag-grid-item {isDisabled ? 'disabled' : ''}" class="flex items-center gap-1 p-1 border-none bg-transparent cursor-pointer transition-all rounded w-full text-left hover:bg-black/5 dark:hover:bg-white/5 {isDisabled ? 'opacity-50' : ''}"
onclick={() => onTagToggle(tagId)} onclick={() => onTagToggle(tagId)}
title={isDisabled ? `Click to show ${anchor.label}` : `Click to hide ${anchor.label}`} title={isDisabled ? `Click to show ${anchor.label}` : `Click to hide ${anchor.label}`}
> >
<div class="legend-icon"> <div class="flex items-center mr-1">
<span <span
class="legend-circle" class="w-4 h-4 rounded-full border-2 border-white"
style="background-color: {anchor.color}; width: 18px; height: 18px; border: 2px solid white; opacity: {isDisabled ? 0.3 : 1};" style="background-color: {anchor.color}; opacity: {isDisabled ? 0.3 : 1};"
> >
<span class="legend-letter text-xs text-white font-bold"> <span class="text-xs text-white font-bold flex items-center justify-center w-full h-full">
{anchor.type === "t" {anchor.type === "t"
? "#" ? "#"
: anchor.type === "author" : anchor.type === "author"
@ -324,7 +333,7 @@
</span> </span>
</span> </span>
</div> </div>
<span class="legend-text text-xs" style="opacity: {isDisabled ? 0.5 : 1};"> <span class="text-xs" style="opacity: {isDisabled ? 0.5 : 1};">
{anchor.label} {anchor.label}
{#if !isDisabled} {#if !isDisabled}
<span class="text-gray-500">({anchor.count})</span> <span class="text-gray-500">({anchor.count})</span>
@ -338,9 +347,12 @@
{/if} {/if}
<!-- Person Visualizer Section --> <!-- Person Visualizer Section -->
<div class="legend-section"> <div class="border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0">
<div class="legend-section-header" onclick={() => personVisualizerExpanded = !personVisualizerExpanded}> <div
<h4 class="legend-section-title">Person Visualizer</h4> class="flex justify-between items-center cursor-pointer py-2 mb-3 hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-md px-2 -mx-2 transition-colors"
onclick={() => personVisualizerExpanded = !personVisualizerExpanded}
>
<h4 class="font-semibold text-gray-700 dark:text-gray-300 m-0 text-sm">Person Visualizer</h4>
<div class="pointer-events-none"> <div class="pointer-events-none">
{#if personVisualizerExpanded} {#if personVisualizerExpanded}
<CaretUpOutline class="w-3 h-3" /> <CaretUpOutline class="w-3 h-3" />
@ -360,7 +372,7 @@
showPersonNodes = !showPersonNodes; showPersonNodes = !showPersonNodes;
onPersonSettingsChange(); onPersonSettingsChange();
}} }}
class="toggle-button {showPersonNodes ? 'active' : ''}" class="px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 text-xs font-medium cursor-pointer transition-all min-w-12 hover:bg-gray-200 dark:hover:bg-gray-600 {showPersonNodes ? 'bg-blue-500 text-white border-blue-500 hover:bg-blue-600' : ''}"
> >
{showPersonNodes ? 'ON' : 'OFF'} {showPersonNodes ? 'ON' : 'OFF'}
</button> </button>
@ -406,10 +418,10 @@
type="checkbox" type="checkbox"
onclick={() => { onclick={() => {
// Invert selection - toggle all person nodes // Invert selection - toggle all person nodes
const allPubkeys = personAnchors.map(person => person.pubkey); const allPubkeys = personAnchors.map((person: any) => person.pubkey);
// Process all persons // Process all persons
allPubkeys.forEach(pubkey => { allPubkeys.forEach((pubkey: string) => {
onPersonToggle(pubkey); onPersonToggle(pubkey);
}); });
}} }}
@ -420,13 +432,13 @@
</div> </div>
<div <div
class="tag-grid {personAnchors.length > 20 ? 'scrollable' : ''}" class="grid gap-1 {personAnchors.length > 20 ? 'max-h-96 overflow-y-auto pr-2' : ''}"
style="grid-template-columns: repeat(2, 1fr);" style="grid-template-columns: repeat(2, 1fr);"
> >
{#each personAnchors as person} {#each personAnchors as person}
{@const isDisabled = disabledPersons.has(person.pubkey)} {@const isDisabled = disabledPersons.has(person.pubkey)}
<button <button
class="tag-grid-item {isDisabled ? 'disabled' : ''}" class="flex items-center gap-1 p-1 border-none bg-transparent cursor-pointer transition-all rounded w-full text-left hover:bg-black/5 dark:hover:bg-white/5 {isDisabled ? 'opacity-50' : ''} {!showPersonNodes ? 'cursor-not-allowed' : ''}"
onclick={() => { onclick={() => {
if (showPersonNodes) { if (showPersonNodes) {
onPersonToggle(person.pubkey); onPersonToggle(person.pubkey);
@ -435,13 +447,13 @@
disabled={!showPersonNodes} disabled={!showPersonNodes}
title={!showPersonNodes ? 'Enable "Show Person Nodes" first' : isDisabled ? `Click to show ${person.displayName || person.pubkey}` : `Click to hide ${person.displayName || person.pubkey}`} title={!showPersonNodes ? 'Enable "Show Person Nodes" first' : isDisabled ? `Click to show ${person.displayName || person.pubkey}` : `Click to hide ${person.displayName || person.pubkey}`}
> >
<div class="legend-icon"> <div class="flex items-center mr-1">
<span <span
class="legend-diamond" class="w-3.5 h-3.5 border-2 border-white"
style="background-color: {person.isFromFollowList ? getEventKindColor(3) : '#10B981'}; opacity: {isDisabled ? 0.3 : 1};" style="background-color: {person.isFromFollowList ? getEventKindColor(3) : '#10B981'}; opacity: {isDisabled ? 0.3 : 1}; transform: rotate(45deg);"
/> />
</div> </div>
<span class="legend-text text-xs" style="opacity: {isDisabled ? 0.5 : 1};"> <span class="text-xs" style="opacity: {isDisabled ? 0.5 : 1};">
{person.displayName || person.pubkey.slice(0, 8) + '...'} {person.displayName || person.pubkey.slice(0, 8) + '...'}
{#if !isDisabled} {#if !isDisabled}
<span class="text-gray-500"> <span class="text-gray-500">
@ -465,195 +477,34 @@
</div> </div>
<style> <style>
.legend-section { /* Custom scrollbar styles that are difficult to express with Tailwind */
border-bottom: 1px solid #e5e7eb; .grid.max-h-96::-webkit-scrollbar {
padding-bottom: 1rem;
margin-bottom: 1rem;
}
.legend-section:last-child {
border-bottom: none;
margin-bottom: 0;
}
.legend-section-header {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
padding: 0.5rem 0;
margin-bottom: 0.75rem;
}
.legend-section-header:hover {
background-color: #f9fafb;
border-radius: 0.375rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.legend-section-title {
font-weight: 600;
color: #374151;
margin: 0;
font-size: 0.875rem;
}
.legend-item {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
}
.legend-item:last-child {
margin-bottom: 0;
}
.tag-grid-item {
display: flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem;
border: none;
background: none;
cursor: pointer;
transition: all 0.2s ease;
border-radius: 0.25rem;
width: 100%;
text-align: left;
}
.tag-grid-item:hover:not(.disabled) {
background-color: rgba(0, 0, 0, 0.05);
}
.tag-grid-item.disabled {
cursor: pointer;
}
.tag-grid-item:hover.disabled {
background-color: rgba(0, 0, 0, 0.02);
}
.tag-grid {
display: grid;
gap: 0.25rem;
}
.tag-grid.scrollable {
max-height: 400px;
overflow-y: auto;
padding-right: 0.5rem;
}
.tag-grid.scrollable::-webkit-scrollbar {
width: 6px; width: 6px;
} }
.tag-grid.scrollable::-webkit-scrollbar-track { .grid.max-h-96::-webkit-scrollbar-track {
background: #f1f1f1; background: #f1f1f1;
border-radius: 3px; border-radius: 3px;
} }
.tag-grid.scrollable::-webkit-scrollbar-thumb { .grid.max-h-96::-webkit-scrollbar-thumb {
background: #888; background: #888;
border-radius: 3px; border-radius: 3px;
} }
.tag-grid.scrollable::-webkit-scrollbar-thumb:hover { .grid.max-h-96::-webkit-scrollbar-thumb:hover {
background: #555; background: #555;
} }
:global(.dark) .legend-section-header:hover { :global(.dark) .grid.max-h-96::-webkit-scrollbar-track {
background-color: rgba(255, 255, 255, 0.05);
}
:global(.dark) .legend-section-title {
color: #d1d5db;
}
:global(.dark) .legend-section {
border-bottom-color: #374151;
}
:global(.dark) .tag-grid-item:hover:not(.disabled) {
background-color: rgba(255, 255, 255, 0.05);
}
:global(.dark) .tag-grid-item:hover.disabled {
background-color: rgba(255, 255, 255, 0.02);
}
:global(.dark) .tag-grid.scrollable::-webkit-scrollbar-track {
background: #374151; background: #374151;
} }
:global(.dark) .tag-grid.scrollable::-webkit-scrollbar-thumb { :global(.dark) .grid.max-h-96::-webkit-scrollbar-thumb {
background: #6b7280; background: #6b7280;
} }
:global(.dark) .tag-grid.scrollable::-webkit-scrollbar-thumb:hover { :global(.dark) .grid.max-h-96::-webkit-scrollbar-thumb:hover {
background: #9ca3af; background: #9ca3af;
} }
.toggle-button {
padding: 0.25rem 0.5rem;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
background-color: #f3f4f6;
color: #6b7280;
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
min-width: 3rem;
}
.toggle-button.small {
padding: 0.125rem 0.375rem;
font-size: 0.625rem;
min-width: 2.5rem;
}
.toggle-button:hover {
background-color: #e5e7eb;
}
.toggle-button.active {
background-color: #3b82f6;
color: white;
border-color: #3b82f6;
}
.toggle-button.active:hover {
background-color: #2563eb;
}
:global(.dark) .toggle-button {
background-color: #374151;
border-color: #4b5563;
color: #9ca3af;
}
:global(.dark) .toggle-button:hover {
background-color: #4b5563;
}
:global(.dark) .toggle-button.active {
background-color: #3b82f6;
border-color: #3b82f6;
color: white;
}
:global(.dark) .toggle-button.active:hover {
background-color: #2563eb;
}
.legend-diamond {
display: inline-block;
width: 14px;
height: 14px;
transform: rotate(45deg);
border: 2px solid white;
}
</style> </style>

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

@ -1042,7 +1042,7 @@
}); });
// Track previous values to avoid unnecessary calls // Track previous values to avoid unnecessary calls
let previousTagType = $state(selectedTagType); let previousTagType = $state<string | undefined>(undefined);
let isInitialized = $state(false); let isInitialized = $state(false);
// Mark as initialized after first render // Mark as initialized after first render
@ -1068,9 +1068,9 @@
// Extract unique tags from current events // Extract unique tags from current events
const tags = new Set<string>(); const tags = new Set<string>();
events.forEach(event => { events.forEach((event: NDKEvent) => {
const eventTags = event.getMatchingTags(selectedTagType); const eventTags = event.getMatchingTags(selectedTagType);
eventTags.forEach(tag => { eventTags.forEach((tag: string[]) => {
if (tag[1]) tags.add(tag[1]); if (tag[1]) tags.add(tag[1]);
}); });
}); });

Loading…
Cancel
Save