mirror of
https://github.com/dergigi/boris.git
synced 2025-12-30 13:04:57 +01:00
- Create reusable usePullToRefresh hook with touch gesture detection - Add PullToRefreshIndicator component with visual feedback - Implement pull-to-refresh on HighlightsPanel (right sidebar) - Implement pull-to-refresh on Explore page - Implement pull-to-refresh on Me pages (all tabs) - Implement pull-to-refresh on BookmarkList (left sidebar) - Only activates on touch devices for mobile-first experience - Shows rotating arrow icon that becomes refresh spinner - Displays contextual messages (pull/release/refreshing) - Integrates with existing refresh handlers and loading states
173 lines
5.4 KiB
TypeScript
173 lines
5.4 KiB
TypeScript
import React, { useState, useRef } from 'react'
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
import { faHighlighter } from '@fortawesome/free-solid-svg-icons'
|
|
import { Highlight } from '../types/highlights'
|
|
import { HighlightItem } from './HighlightItem'
|
|
import { useFilteredHighlights } from '../hooks/useFilteredHighlights'
|
|
import { usePullToRefresh } from '../hooks/usePullToRefresh'
|
|
import HighlightsPanelCollapsed from './HighlightsPanel/HighlightsPanelCollapsed'
|
|
import HighlightsPanelHeader from './HighlightsPanel/HighlightsPanelHeader'
|
|
import PullToRefreshIndicator from './PullToRefreshIndicator'
|
|
import { RelayPool } from 'applesauce-relay'
|
|
import { IEventStore } from 'applesauce-core'
|
|
import { UserSettings } from '../services/settingsService'
|
|
|
|
export interface HighlightVisibility {
|
|
nostrverse: boolean
|
|
friends: boolean
|
|
mine: boolean
|
|
}
|
|
|
|
interface HighlightsPanelProps {
|
|
highlights: Highlight[]
|
|
loading: boolean
|
|
isCollapsed: boolean
|
|
onToggleCollapse: () => void
|
|
onSelectUrl?: (url: string) => void
|
|
selectedUrl?: string
|
|
onToggleHighlights?: (show: boolean) => void
|
|
selectedHighlightId?: string
|
|
onRefresh?: () => void
|
|
onHighlightClick?: (highlightId: string) => void
|
|
currentUserPubkey?: string
|
|
highlightVisibility?: HighlightVisibility
|
|
onHighlightVisibilityChange?: (visibility: HighlightVisibility) => void
|
|
followedPubkeys?: Set<string>
|
|
relayPool?: RelayPool | null
|
|
eventStore?: IEventStore | null
|
|
settings?: UserSettings
|
|
}
|
|
|
|
export const HighlightsPanel: React.FC<HighlightsPanelProps> = ({
|
|
highlights,
|
|
loading,
|
|
isCollapsed,
|
|
onToggleCollapse,
|
|
onSelectUrl,
|
|
selectedUrl,
|
|
onToggleHighlights,
|
|
selectedHighlightId,
|
|
onRefresh,
|
|
onHighlightClick,
|
|
currentUserPubkey,
|
|
highlightVisibility = { nostrverse: true, friends: true, mine: true },
|
|
onHighlightVisibilityChange,
|
|
followedPubkeys = new Set(),
|
|
relayPool,
|
|
eventStore,
|
|
settings
|
|
}) => {
|
|
const [showHighlights, setShowHighlights] = useState(true)
|
|
const [localHighlights, setLocalHighlights] = useState(highlights)
|
|
const highlightsListRef = useRef<HTMLDivElement>(null)
|
|
|
|
const handleToggleHighlights = () => {
|
|
const newValue = !showHighlights
|
|
setShowHighlights(newValue)
|
|
onToggleHighlights?.(newValue)
|
|
}
|
|
|
|
// Pull-to-refresh for highlights
|
|
const pullToRefreshState = usePullToRefresh(highlightsListRef, {
|
|
onRefresh: () => {
|
|
if (onRefresh) {
|
|
onRefresh()
|
|
}
|
|
},
|
|
isRefreshing: loading,
|
|
disabled: !onRefresh
|
|
})
|
|
|
|
// Keep track of highlight updates
|
|
React.useEffect(() => {
|
|
setLocalHighlights(highlights)
|
|
}, [highlights])
|
|
|
|
const handleHighlightUpdate = (updatedHighlight: Highlight) => {
|
|
setLocalHighlights(prev =>
|
|
prev.map(h => h.id === updatedHighlight.id ? updatedHighlight : h)
|
|
)
|
|
}
|
|
|
|
const handleHighlightDelete = (highlightId: string) => {
|
|
// Remove highlight from local state
|
|
setLocalHighlights(prev => prev.filter(h => h.id !== highlightId))
|
|
}
|
|
|
|
const filteredHighlights = useFilteredHighlights({
|
|
highlights: localHighlights,
|
|
selectedUrl,
|
|
highlightVisibility,
|
|
currentUserPubkey,
|
|
followedPubkeys
|
|
})
|
|
|
|
if (isCollapsed) {
|
|
return (
|
|
<HighlightsPanelCollapsed
|
|
hasHighlights={filteredHighlights.length > 0}
|
|
onToggleCollapse={onToggleCollapse}
|
|
settings={settings}
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="highlights-container">
|
|
<HighlightsPanelHeader
|
|
loading={loading}
|
|
hasHighlights={filteredHighlights.length > 0}
|
|
showHighlights={showHighlights}
|
|
highlightVisibility={highlightVisibility}
|
|
currentUserPubkey={currentUserPubkey}
|
|
onToggleHighlights={handleToggleHighlights}
|
|
onRefresh={onRefresh}
|
|
onToggleCollapse={onToggleCollapse}
|
|
onHighlightVisibilityChange={onHighlightVisibilityChange}
|
|
/>
|
|
|
|
{loading && filteredHighlights.length === 0 ? (
|
|
<div className="highlights-loading">
|
|
<FontAwesomeIcon icon={faHighlighter} spin />
|
|
</div>
|
|
) : filteredHighlights.length === 0 ? (
|
|
<div className="highlights-empty">
|
|
<FontAwesomeIcon icon={faHighlighter} size="2x" />
|
|
<p>No highlights for this article.</p>
|
|
<p className="empty-hint">
|
|
{selectedUrl
|
|
? 'Create highlights for this article using a Nostr client that supports NIP-84.'
|
|
: 'Select an article to view its highlights.'}
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div
|
|
ref={highlightsListRef}
|
|
className={`highlights-list pull-to-refresh-container ${pullToRefreshState.isPulling ? 'is-pulling' : ''}`}
|
|
>
|
|
<PullToRefreshIndicator
|
|
isPulling={pullToRefreshState.isPulling}
|
|
pullDistance={pullToRefreshState.pullDistance}
|
|
canRefresh={pullToRefreshState.canRefresh}
|
|
isRefreshing={loading}
|
|
/>
|
|
{filteredHighlights.map((highlight) => (
|
|
<HighlightItem
|
|
key={highlight.id}
|
|
highlight={highlight}
|
|
onSelectUrl={onSelectUrl}
|
|
isSelected={highlight.id === selectedHighlightId}
|
|
onHighlightClick={onHighlightClick}
|
|
relayPool={relayPool}
|
|
eventStore={eventStore}
|
|
onHighlightUpdate={handleHighlightUpdate}
|
|
onHighlightDelete={handleHighlightDelete}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|