Browse Source
This commit represents a checkpoint in implementing a sophisticated event configuration system with people tag anchors. The implementation has grown complex and needs reimplementation in a smarter way. ## Major Changes: ### Event Configuration System Overhaul - Replaced simple allowed/disabled kinds with EventKindConfig objects - Each event kind now has individual limits and type-specific settings: - Kind 0 (profiles): Controls max profiles to fetch - Kind 3 (follow lists): Has depth setting and complex fetch logic - Kind 30040: Has nestedLevels setting - Created new EventTypeConfig component replacing EventKindFilter ### Follow List Fetching Logic - Kind 3 limit=1: Fetches only user's follow list - Kind 3 limit>1: Fetches user's + (limit-1) follow lists from follows - Added depth traversal (0=direct, 1=2 degrees, 2=3 degrees) - Attempted to implement "addFollowLists" toggle (later simplified) ### Profile Fetching Changes - Modified to be more selective about which profiles to fetch - Attempted to limit based on follow lists and event authors - Added progress indicators for profile loading ### People Tag Anchors Implementation - Complex logic to create "p" tag anchors from follow lists - Synthetic event creation to connect people to visualization - "Only show people with publications" checkbox - Attempted to connect people to: - Events they authored (pubkey match) - Events where they're tagged with "p" - Display limiting based on kind 0 limit ### UI/UX Changes - Tag anchors legend now scrollable when >20 items - Auto-disable functionality when >20 tag anchors - Added various UI controls that proved confusing - Multiple iterations on settings panel layout ## Problems with Current Implementation: 1. **Overly Complex Logic**: The synthetic event creation and connection logic for people tag anchors became convoluted 2. **Confusing UI**: Too many interdependent settings that aren't intuitive: - Limit inputs control different things for different event types - The relationship between kind 3 and kind 0 limits is unclear - "addFollowLists" checkbox functionality was confusing 3. **Performance Concerns**: Fetching all profiles then limiting display may not be optimal 4. **Unclear Requirements**: The exact behavior for people tag anchors connections needs clarification ## Next Steps: Need to revert and reimplement with: - Clearer separation of concerns - Simpler UI that's more intuitive - Better defined behavior for people tag anchors - More efficient profile fetching strategy ## Files Changed: - EventTypeConfig.svelte: New component for event configuration - visualizationConfig.ts: Major overhaul for EventKindConfig - profileCache.ts: Added selective fetching logic - visualize/+page.svelte: Complex follow list and profile fetching - EventNetwork components: People tag anchor implementation - settings_panel.org: Documentation of intended behavior 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>master
12 changed files with 1263 additions and 187 deletions
@ -0,0 +1,125 @@
@@ -0,0 +1,125 @@
|
||||
* Settings Panel Documentation |
||||
|
||||
** Overview |
||||
The settings panel controls how events are fetched and displayed in the visualization. It has several sections that work together to create an efficient and user-friendly experience. |
||||
|
||||
** Event Types Configuration |
||||
|
||||
*** Purpose |
||||
Controls which types of Nostr events are fetched and how many of each type. |
||||
|
||||
*** Key Event Types |
||||
- *Kind 0* (Profiles/Metadata): User profile information (names, pictures, etc.) |
||||
- *Kind 3* (Follow Lists): Who each user follows |
||||
- *Kind 30040* (Index Events): Publication indices |
||||
- *Kind 30041* (Content Events): Publication content |
||||
- *Kind 30818* (Content Events): Alternative publication format |
||||
|
||||
*** How Limits Work |
||||
Each event kind has a limit number that controls different things: |
||||
|
||||
**** For Kind 0 (Profiles) |
||||
- Limit controls how many profiles to fetch from discovered pubkeys |
||||
- These profiles are used for: |
||||
- Displaying names instead of pubkeys |
||||
- Showing profile pictures in tooltips |
||||
- When "People" tag anchors are selected, this limit controls how many people anchors to display |
||||
|
||||
**** For Kind 3 (Follow Lists) |
||||
- =limit = 1=: Only fetch the current user's follow list |
||||
- =limit > 1=: Fetch the user's follow list PLUS (limit-1) follow lists from people they follow |
||||
- The depth selector controls traversal: |
||||
- =Direct= (0): Just the immediate follows |
||||
- =2 degrees= (1): Follows of follows |
||||
- =3 degrees= (2): Three levels deep |
||||
|
||||
**** For Kind 30040/30041/30818 |
||||
- Limit controls maximum number of these events to fetch |
||||
|
||||
** Tag Anchors |
||||
|
||||
*** What Are Tag Anchors? |
||||
Tag anchors are special nodes in the graph that act as gravity points for events sharing common attributes. They help organize the visualization by grouping related content. |
||||
|
||||
*** Tag Types Available |
||||
- *Hashtags* (t): Groups events by hashtag |
||||
- *Authors*: Groups events by author |
||||
- *People* (p): Shows people from follow lists as anchor points |
||||
- *Event References* (e): Groups events that reference each other |
||||
- *Titles*: Groups events by title |
||||
- *Summaries*: Groups events by summary |
||||
|
||||
*** How People Tag Anchors Work |
||||
When "People" is selected as the tag type: |
||||
|
||||
1. The system looks at all loaded follow lists (kind 3 events) |
||||
2. Extracts all pubkeys (people) from those follow lists |
||||
3. Creates tag anchors for those people (up to the kind 0 limit) |
||||
4. Connects each person anchor to: |
||||
- Events they authored (where pubkey matches) |
||||
- Events where they're mentioned in "p" tags |
||||
|
||||
*** Display Limiting and Auto-Disable |
||||
- Tag anchors are created for ALL discovered tags |
||||
- But only displayed up to the configured limit |
||||
- When > 20 tag anchors exist, they're all auto-disabled |
||||
- Users can selectively enable specific anchors |
||||
- The legend becomes scrollable for many anchors |
||||
|
||||
*** "Only show people with publications" Checkbox |
||||
When checked (default): |
||||
- Only shows people who have events in the current visualization |
||||
|
||||
When unchecked: |
||||
- Shows ALL people from follow lists, even if they have no events displayed |
||||
- Useful for seeing your complete social graph |
||||
|
||||
** Display Limits Section |
||||
|
||||
*** Max Publication Indices (30040) |
||||
Controls display filtering for publication indices after they're fetched. |
||||
|
||||
*** Max Events per Index |
||||
Limits how many content events to show per publication index. |
||||
|
||||
*** Fetch if not found |
||||
When enabled, automatically fetches missing referenced events. |
||||
|
||||
** Graph Traversal Section |
||||
|
||||
*** Search through already fetched |
||||
When enabled, tag expansion only searches through events already loaded (more efficient). |
||||
|
||||
*** Append mode |
||||
When enabled, new fetches add to the existing graph instead of replacing it. |
||||
|
||||
** Current Implementation Questions |
||||
|
||||
1. *Profile Fetching*: Should we fetch profiles for: |
||||
- Only event authors? |
||||
- All pubkeys in follow lists? |
||||
- All pubkeys mentioned anywhere? |
||||
|
||||
2. *People Tag Anchors*: Should they connect to: |
||||
- Only events where the person is tagged with "p"? |
||||
- Events they authored? |
||||
- Both? |
||||
|
||||
3. *Display Limits*: Should limits control: |
||||
- How many to fetch from relays? |
||||
- How many to display (fetch all, display subset)? |
||||
- Both with separate controls? |
||||
|
||||
4. *Auto-disable Threshold*: Is 20 the right number for auto-disabling tag anchors? |
||||
|
||||
** Ideal User Flow |
||||
|
||||
1. User loads the visualization |
||||
2. Their follow list is fetched (kind 3, limit 1) |
||||
3. Profiles are fetched for people they follow (kind 0, respecting limit) |
||||
4. Publications are fetched (kind 30040/30041/30818) |
||||
5. User enables "People" tag anchors |
||||
6. Sees their follows as anchor points |
||||
7. Can see which follows have authored content |
||||
8. Can selectively enable/disable specific people |
||||
9. Can increase limits to see more content/people |
||||
@ -0,0 +1,246 @@
@@ -0,0 +1,246 @@
|
||||
<script lang="ts"> |
||||
import { visualizationConfig } from '$lib/stores/visualizationConfig'; |
||||
import { Button, Input } from 'flowbite-svelte'; |
||||
import { CloseCircleOutline } from 'flowbite-svelte-icons'; |
||||
import { getEventKindName, getEventKindColor } from '$lib/utils/eventColors'; |
||||
|
||||
let { |
||||
onReload = () => {}, |
||||
eventCounts = {} |
||||
} = $props<{ |
||||
onReload?: () => void; |
||||
eventCounts?: { [kind: number]: number }; |
||||
}>(); |
||||
|
||||
let newKind = $state(''); |
||||
let showAddInput = $state(false); |
||||
let inputError = $state(''); |
||||
|
||||
function validateKind(value: string | number): number | null { |
||||
// Convert to string for consistent handling |
||||
const strValue = String(value); |
||||
if (strValue === null || strValue === undefined || strValue.trim() === '') { |
||||
inputError = ''; |
||||
return null; |
||||
} |
||||
const kind = parseInt(strValue.trim()); |
||||
if (isNaN(kind)) { |
||||
inputError = 'Must be a number'; |
||||
return null; |
||||
} |
||||
if (kind < 0) { |
||||
inputError = 'Must be non-negative'; |
||||
return null; |
||||
} |
||||
if ($visualizationConfig.eventConfigs.some(ec => ec.kind === kind)) { |
||||
inputError = 'Already added'; |
||||
return null; |
||||
} |
||||
inputError = ''; |
||||
return kind; |
||||
} |
||||
|
||||
function handleAddKind() { |
||||
console.log('[EventTypeConfig] handleAddKind called with:', newKind); |
||||
const kind = validateKind(newKind); |
||||
console.log('[EventTypeConfig] Validation result:', kind); |
||||
if (kind !== null) { |
||||
console.log('[EventTypeConfig] Adding event kind:', kind); |
||||
visualizationConfig.addEventKind(kind); |
||||
newKind = ''; |
||||
showAddInput = false; |
||||
inputError = ''; |
||||
} else { |
||||
console.log('[EventTypeConfig] Validation failed:', inputError); |
||||
} |
||||
} |
||||
|
||||
function handleKeydown(e: KeyboardEvent) { |
||||
if (e.key === 'Enter') { |
||||
handleAddKind(); |
||||
} else if (e.key === 'Escape') { |
||||
showAddInput = false; |
||||
newKind = ''; |
||||
inputError = ''; |
||||
} |
||||
} |
||||
|
||||
function handleLimitChange(kind: number, value: string) { |
||||
const limit = parseInt(value); |
||||
if (!isNaN(limit) && limit > 0) { |
||||
visualizationConfig.updateEventLimit(kind, limit); |
||||
} |
||||
} |
||||
|
||||
function handleNestedLevelsChange(value: string) { |
||||
const levels = parseInt(value); |
||||
if (!isNaN(levels) && levels >= 0) { |
||||
visualizationConfig.updateNestedLevels(levels); |
||||
} |
||||
} |
||||
|
||||
function handleFollowDepthChange(value: string) { |
||||
const depth = parseInt(value); |
||||
if (!isNaN(depth) && depth >= 0 && depth <= 2) { |
||||
visualizationConfig.updateFollowDepth(depth); |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<div class="space-y-3"> |
||||
<span class="text-xs text-gray-600 dark:text-gray-400"> |
||||
Showing {Object.values(eventCounts).reduce((a, b) => a + b, 0)} of {Object.values(eventCounts).reduce((a, b) => a + b, 0)} events |
||||
</span> |
||||
|
||||
<!-- Event configurations --> |
||||
<div class="space-y-2"> |
||||
{#each $visualizationConfig.eventConfigs as config} |
||||
{@const isLoaded = (eventCounts[config.kind] || 0) > 0} |
||||
{@const isDisabled = $visualizationConfig.disabledKinds?.includes(config.kind) || false} |
||||
{@const color = getEventKindColor(config.kind)} |
||||
{@const borderColor = isLoaded ? 'border-green-500' : 'border-red-500'} |
||||
<div class="flex items-center gap-2"> |
||||
<!-- Kind badge with color indicator and load status border --> |
||||
<button |
||||
class="flex items-center gap-1 min-w-[140px] px-2 py-1 border-2 rounded {borderColor} {isDisabled ? 'opacity-50' : ''} hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors cursor-pointer" |
||||
onclick={() => visualizationConfig.toggleKind(config.kind)} |
||||
title={isDisabled ? `Click to enable ${getEventKindName(config.kind)}` : `Click to disable ${getEventKindName(config.kind)}`} |
||||
> |
||||
<span |
||||
class="inline-block w-3 h-3 rounded-full flex-shrink-0" |
||||
style="background-color: {color}" |
||||
></span> |
||||
<span class="text-sm font-medium dark:text-white"> |
||||
{config.kind} |
||||
</span> |
||||
</button> |
||||
<button |
||||
onclick={() => visualizationConfig.removeEventKind(config.kind)} |
||||
class="text-red-500 hover:text-red-700 transition-colors" |
||||
title="Remove {getEventKindName(config.kind)}" |
||||
> |
||||
<CloseCircleOutline class="w-4 h-4" /> |
||||
</button> |
||||
|
||||
<!-- Limit input for all kinds --> |
||||
<input |
||||
type="number" |
||||
value={config.limit} |
||||
min="1" |
||||
max="1000" |
||||
class="w-16 px-2 py-1 text-xs border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white" |
||||
oninput={(e) => handleLimitChange(config.kind, e.currentTarget.value)} |
||||
title="Max to display" |
||||
/> |
||||
|
||||
<!-- Nested levels for 30040 --> |
||||
{#if config.kind === 30040} |
||||
<span class="text-xs text-gray-600 dark:text-gray-400">Nested Levels:</span> |
||||
<input |
||||
type="number" |
||||
value={config.nestedLevels || 1} |
||||
min="0" |
||||
max="10" |
||||
class="w-14 px-2 py-1 text-xs border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white" |
||||
oninput={(e) => handleNestedLevelsChange(e.currentTarget.value)} |
||||
title="Levels to traverse" |
||||
/> |
||||
{/if} |
||||
|
||||
<!-- Additional settings for kind 3 (follow lists) --> |
||||
{#if config.kind === 3} |
||||
<select |
||||
value={config.depth || 0} |
||||
onchange={(e) => handleFollowDepthChange(e.currentTarget.value)} |
||||
class="px-2 py-1 text-xs border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white" |
||||
title="How many degrees of separation to traverse" |
||||
> |
||||
<option value="0">Direct</option> |
||||
<option value="1">2 degrees</option> |
||||
<option value="2">3 degrees</option> |
||||
</select> |
||||
{/if} |
||||
|
||||
<!-- Load indicator --> |
||||
{#if isLoaded} |
||||
<span class="text-xs text-green-600 dark:text-green-400"> |
||||
({eventCounts[config.kind]}) |
||||
</span> |
||||
{:else} |
||||
<span class="text-xs text-red-600 dark:text-red-400"> |
||||
(not loaded) |
||||
</span> |
||||
{/if} |
||||
</div> |
||||
{/each} |
||||
</div> |
||||
|
||||
<!-- Add kind button/input --> |
||||
{#if showAddInput} |
||||
<div class="flex items-center gap-2"> |
||||
<input |
||||
bind:value={newKind} |
||||
type="number" |
||||
placeholder="Enter event kind number (e.g. 1)" |
||||
class="flex-1 px-3 py-1 text-sm border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500" |
||||
onkeydown={handleKeydown} |
||||
oninput={(e) => validateKind(e.currentTarget.value)} |
||||
/> |
||||
<Button size="xs" onclick={handleAddKind} disabled={newKind === '' || !!inputError}> |
||||
Add |
||||
</Button> |
||||
<Button |
||||
size="xs" |
||||
color="light" |
||||
onclick={() => { |
||||
showAddInput = false; |
||||
newKind = ''; |
||||
inputError = ''; |
||||
}} |
||||
> |
||||
Cancel |
||||
</Button> |
||||
</div> |
||||
{#if inputError} |
||||
<p class="text-xs text-red-500 -mt-2"> |
||||
{inputError} |
||||
</p> |
||||
{/if} |
||||
{:else} |
||||
<Button |
||||
size="xs" |
||||
color="light" |
||||
onclick={() => showAddInput = true} |
||||
class="gap-1" |
||||
> |
||||
<span>+</span> |
||||
<span>Add Event Type</span> |
||||
</Button> |
||||
{/if} |
||||
|
||||
<!-- Reload button --> |
||||
<Button |
||||
size="xs" |
||||
color="blue" |
||||
onclick={onReload} |
||||
class="gap-1" |
||||
title="Reload graph with current settings" |
||||
> |
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> |
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path> |
||||
</svg> |
||||
<span>Reload</span> |
||||
</Button> |
||||
|
||||
<!-- Border legend --> |
||||
<div class="text-xs text-gray-500 dark:text-gray-400 space-y-1 mt-2"> |
||||
<p class="flex items-center gap-2"> |
||||
<span class="inline-block w-3 h-3 border-2 border-green-500 rounded"></span> |
||||
<span>Green = Events loaded</span> |
||||
</p> |
||||
<p class="flex items-center gap-2"> |
||||
<span class="inline-block w-3 h-3 border-2 border-red-500 rounded"></span> |
||||
<span>Red = Not loaded (click Reload)</span> |
||||
</p> |
||||
</div> |
||||
</div> |
||||
@ -1,89 +1,234 @@
@@ -1,89 +1,234 @@
|
||||
import { writable, derived } from 'svelte/store'; |
||||
import { writable, derived, get } from "svelte/store"; |
||||
|
||||
export interface VisualizationConfig { |
||||
// Event filtering
|
||||
allowedKinds: number[]; // Using array for ordered display
|
||||
disabledKinds: number[]; // Kinds that are temporarily disabled but not removed
|
||||
allowFreeEvents: boolean; |
||||
export interface EventKindConfig { |
||||
kind: number; |
||||
limit: number; |
||||
nestedLevels?: number; // Only for kind 30040
|
||||
depth?: number; // Only for kind 3 (follow lists)
|
||||
} |
||||
|
||||
// Display limits (moving from displayLimits store)
|
||||
maxPublicationIndices: number; // -1 unlimited
|
||||
maxEventsPerIndex: number; // -1 unlimited
|
||||
export interface VisualizationConfig { |
||||
// Event configurations with per-kind limits
|
||||
eventConfigs: EventKindConfig[]; |
||||
|
||||
// Graph traversal
|
||||
searchThroughFetched: boolean; |
||||
|
||||
// Append mode - add new events to existing graph instead of replacing
|
||||
appendMode?: boolean; |
||||
|
||||
// Legacy properties for backward compatibility
|
||||
allowedKinds?: number[]; |
||||
disabledKinds?: number[]; |
||||
allowFreeEvents?: boolean; |
||||
maxPublicationIndices?: number; |
||||
maxEventsPerIndex?: number; |
||||
} |
||||
|
||||
// Default configurations for common event kinds
|
||||
const DEFAULT_EVENT_CONFIGS: EventKindConfig[] = [ |
||||
{ kind: 0, limit: 50 }, // Metadata events (profiles) - controls how many profiles to fetch
|
||||
{ kind: 3, limit: 1, depth: 0 }, // Follow lists - limit 1 = just user's, higher = user's + from follows
|
||||
{ kind: 30040, limit: 20, nestedLevels: 1 }, |
||||
{ kind: 30041, limit: 20 }, |
||||
{ kind: 30818, limit: 20 }, |
||||
]; |
||||
|
||||
function createVisualizationConfig() { |
||||
const { subscribe, set, update } = writable<VisualizationConfig>({ |
||||
allowedKinds: [30040, 30041, 30818], |
||||
disabledKinds: [30041, 30818], // 30041 and 30818 disabled by default
|
||||
// Initialize with both new and legacy properties
|
||||
const initialConfig: VisualizationConfig = { |
||||
eventConfigs: DEFAULT_EVENT_CONFIGS, |
||||
searchThroughFetched: true, |
||||
appendMode: false, |
||||
// Legacy properties
|
||||
allowedKinds: DEFAULT_EVENT_CONFIGS.map(ec => ec.kind), |
||||
disabledKinds: [30041, 30818], |
||||
allowFreeEvents: false, |
||||
maxPublicationIndices: -1, |
||||
maxEventsPerIndex: -1, |
||||
searchThroughFetched: true |
||||
}); |
||||
}; |
||||
|
||||
const { subscribe, set, update } = |
||||
writable<VisualizationConfig>(initialConfig); |
||||
|
||||
// Helper to sync legacy properties with eventConfigs
|
||||
const syncLegacyProperties = (config: VisualizationConfig) => { |
||||
config.allowedKinds = config.eventConfigs.map((ec) => ec.kind); |
||||
return config; |
||||
}; |
||||
|
||||
return { |
||||
subscribe, |
||||
update, |
||||
reset: () => set({ |
||||
allowedKinds: [30040, 30041, 30818], |
||||
disabledKinds: [30041, 30818], // 30041 and 30818 disabled by default
|
||||
allowFreeEvents: false, |
||||
maxPublicationIndices: -1, |
||||
maxEventsPerIndex: -1, |
||||
searchThroughFetched: true |
||||
}), |
||||
addKind: (kind: number) => update(config => { |
||||
if (!config.allowedKinds.includes(kind)) { |
||||
return { ...config, allowedKinds: [...config.allowedKinds, kind] }; |
||||
reset: () => set(initialConfig), |
||||
|
||||
// Add a new event kind with default limit
|
||||
addEventKind: (kind: number, limit: number = 10) => |
||||
update((config) => { |
||||
// Check if kind already exists
|
||||
if (config.eventConfigs.some((ec) => ec.kind === kind)) { |
||||
return config; |
||||
} |
||||
|
||||
const newConfig: EventKindConfig = { kind, limit }; |
||||
// Add nestedLevels for 30040
|
||||
if (kind === 30040) { |
||||
newConfig.nestedLevels = 1; |
||||
} |
||||
// Add depth for kind 3
|
||||
if (kind === 3) { |
||||
newConfig.depth = 0; |
||||
} |
||||
|
||||
const updated = { |
||||
...config, |
||||
eventConfigs: [...config.eventConfigs, newConfig], |
||||
}; |
||||
return syncLegacyProperties(updated); |
||||
}), |
||||
|
||||
// Legacy method for backward compatibility
|
||||
addKind: (kind: number) => |
||||
update((config) => { |
||||
if (config.eventConfigs.some((ec) => ec.kind === kind)) { |
||||
return config; |
||||
} |
||||
const updated = { |
||||
...config, |
||||
eventConfigs: [...config.eventConfigs, { kind, limit: 10 }], |
||||
}; |
||||
return syncLegacyProperties(updated); |
||||
}), |
||||
|
||||
// Remove an event kind
|
||||
removeEventKind: (kind: number) => |
||||
update((config) => { |
||||
const updated = { |
||||
...config, |
||||
eventConfigs: config.eventConfigs.filter((ec) => ec.kind !== kind), |
||||
}; |
||||
return syncLegacyProperties(updated); |
||||
}), |
||||
|
||||
// Legacy method for backward compatibility
|
||||
removeKind: (kind: number) => |
||||
update((config) => { |
||||
const updated = { |
||||
...config, |
||||
eventConfigs: config.eventConfigs.filter((ec) => ec.kind !== kind), |
||||
}; |
||||
return syncLegacyProperties(updated); |
||||
}), |
||||
removeKind: (kind: number) => update(config => ({ |
||||
|
||||
// Update limit for a specific kind
|
||||
updateEventLimit: (kind: number, limit: number) => |
||||
update((config) => ({ |
||||
...config, |
||||
allowedKinds: config.allowedKinds.filter(k => k !== kind) |
||||
eventConfigs: config.eventConfigs.map((ec) => |
||||
ec.kind === kind ? { ...ec, limit } : ec, |
||||
), |
||||
})), |
||||
toggleFreeEvents: () => update(config => ({ |
||||
|
||||
// Update nested levels for kind 30040
|
||||
updateNestedLevels: (levels: number) => |
||||
update((config) => ({ |
||||
...config, |
||||
allowFreeEvents: !config.allowFreeEvents |
||||
eventConfigs: config.eventConfigs.map((ec) => |
||||
ec.kind === 30040 ? { ...ec, nestedLevels: levels } : ec, |
||||
), |
||||
})), |
||||
setMaxPublicationIndices: (max: number) => update(config => ({ |
||||
|
||||
// Update depth for kind 3
|
||||
updateFollowDepth: (depth: number) => |
||||
update((config) => ({ |
||||
...config, |
||||
maxPublicationIndices: max |
||||
eventConfigs: config.eventConfigs.map((ec) => |
||||
ec.kind === 3 ? { ...ec, depth: depth } : ec, |
||||
), |
||||
})), |
||||
setMaxEventsPerIndex: (max: number) => update(config => ({ |
||||
|
||||
|
||||
// Get config for a specific kind
|
||||
getEventConfig: (kind: number) => { |
||||
let config: EventKindConfig | undefined; |
||||
subscribe((c) => { |
||||
config = c.eventConfigs.find((ec) => ec.kind === kind); |
||||
})(); |
||||
return config; |
||||
}, |
||||
|
||||
toggleSearchThroughFetched: () => |
||||
update((config) => ({ |
||||
...config, |
||||
maxEventsPerIndex: max |
||||
searchThroughFetched: !config.searchThroughFetched, |
||||
})), |
||||
toggleSearchThroughFetched: () => update(config => ({ |
||||
|
||||
toggleAppendMode: () => |
||||
update((config) => ({ |
||||
...config, |
||||
searchThroughFetched: !config.searchThroughFetched |
||||
appendMode: !config.appendMode, |
||||
})), |
||||
toggleKind: (kind: number) => update(config => { |
||||
const isDisabled = config.disabledKinds.includes(kind); |
||||
|
||||
// Legacy methods for backward compatibility
|
||||
toggleKind: (kind: number) => |
||||
update((config) => { |
||||
const isDisabled = config.disabledKinds?.includes(kind) || false; |
||||
if (isDisabled) { |
||||
// Re-enable it
|
||||
return { |
||||
...config, |
||||
disabledKinds: config.disabledKinds.filter(k => k !== kind) |
||||
disabledKinds: |
||||
config.disabledKinds?.filter((k) => k !== kind) || [], |
||||
}; |
||||
} else { |
||||
// Disable it
|
||||
return { |
||||
...config, |
||||
disabledKinds: [...config.disabledKinds, kind] |
||||
disabledKinds: [...(config.disabledKinds || []), kind], |
||||
}; |
||||
} |
||||
}) |
||||
}), |
||||
|
||||
toggleFreeEvents: () => |
||||
update((config) => ({ |
||||
...config, |
||||
allowFreeEvents: !config.allowFreeEvents, |
||||
})), |
||||
|
||||
setMaxPublicationIndices: (max: number) => |
||||
update((config) => ({ |
||||
...config, |
||||
maxPublicationIndices: max, |
||||
})), |
||||
|
||||
setMaxEventsPerIndex: (max: number) => |
||||
update((config) => ({ |
||||
...config, |
||||
maxEventsPerIndex: max, |
||||
})), |
||||
}; |
||||
} |
||||
|
||||
export const visualizationConfig = createVisualizationConfig(); |
||||
|
||||
// Helper to check if a kind is allowed and enabled
|
||||
// Helper to get all enabled event kinds
|
||||
export const enabledEventKinds = derived(visualizationConfig, ($config) => |
||||
$config.eventConfigs.map((ec) => ec.kind), |
||||
); |
||||
|
||||
// Helper to check if a kind is enabled
|
||||
export const isKindEnabled = derived( |
||||
visualizationConfig, |
||||
($config) => (kind: number) => |
||||
$config.eventConfigs.some((ec) => ec.kind === kind), |
||||
); |
||||
|
||||
// Legacy helper for backward compatibility
|
||||
export const isKindAllowed = derived( |
||||
visualizationConfig, |
||||
$config => (kind: number) => $config.allowedKinds.includes(kind) && !$config.disabledKinds.includes(kind) |
||||
($config) => (kind: number) => { |
||||
const inEventConfigs = $config.eventConfigs.some((ec) => ec.kind === kind); |
||||
const notDisabled = !($config.disabledKinds?.includes(kind) || false); |
||||
return inEventConfigs && notDisabled; |
||||
}, |
||||
); |
||||
Loading…
Reference in new issue