From f49065d421bd04dc46577ec3cb939cfca29b3a9a Mon Sep 17 00:00:00 2001 From: Alex Benzer Date: Thu, 1 Jan 2026 19:36:42 -0800 Subject: [PATCH] Fix drag and drop UI on web --- src/view/screens/Feeds.tsx | 65 ++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx index 58087e8e24..0c77d2ef43 100644 --- a/src/view/screens/Feeds.tsx +++ b/src/view/screens/Feeds.tsx @@ -54,6 +54,7 @@ import * as FeedCard from '#/components/FeedCard' import {SearchInput} from '#/components/forms/SearchInput' import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline' import {Menu_Stroke2_Corner0_Rounded as DragHandleIcon} from '#/components/icons/Menu' +import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' import * as Layout from '#/components/Layout' import * as ListCard from '#/components/ListCard' @@ -90,6 +91,10 @@ type FlatlistSlice = type: 'noFollowingFeed' key: string } + | { + type: 'noPinnedFeeds' + key: string + } export function FeedsScreen(_props: Props) { const { @@ -221,12 +226,17 @@ const FeedsScreenInner = React.memo( key: 'pinnedFeedsSection', type: 'pinnedFeedsSection', }) - } - if (!hasFollowingFeed && pinnedCount > 0) { + if (!hasFollowingFeed) { + slices.push({ + key: 'noFollowingFeed', + type: 'noFollowingFeed', + }) + } + } else { slices.push({ - key: 'noFollowingFeed', - type: 'noFollowingFeed', + key: 'noPinnedFeeds', + type: 'noPinnedFeeds', }) } } else { @@ -307,11 +317,33 @@ const FeedsScreenInner = React.memo( ) + } else if (item.type === 'noPinnedFeeds') { + return ( + + + + You don't have any pinned feeds. + + + ) } return null }, [ pal.border, + pal.textLight, + pal.colors, overlayY, overlayVisible, setOverlayFeed, @@ -422,14 +454,24 @@ const PinnedFeedsSection = React.memo(function PinnedFeedsSection({ const isDraggingRef = React.useRef(false) // Keep ref in sync with server feeds - // Always sync unless actively dragging, to handle pin/unpin updates + // Merge changes: keep local order for existing feeds, add new ones, remove deleted ones React.useEffect(() => { if (!isDraggingRef.current) { - // Check if feeds actually changed (different items or order from server) - const currentIds = feedsRef.current.map(f => f.config.id).join(',') - const serverIds = serverFeeds.map(f => f.config.id).join(',') - if (currentIds !== serverIds) { - feedsRef.current = serverFeeds + const currentIds = new Set(feedsRef.current.map(f => f.config.id)) + const serverIds = new Set(serverFeeds.map(f => f.config.id)) + + // Find new feeds (in server but not local) and removed feeds (in local but not server) + const newFeeds = serverFeeds.filter(f => !currentIds.has(f.config.id)) + const removedIds = new Set( + [...currentIds].filter(id => !serverIds.has(id)), + ) + + if (newFeeds.length > 0 || removedIds.size > 0) { + // Keep existing feeds in their current order, remove deleted, add new at end + const updatedFeeds = feedsRef.current + .filter(f => !removedIds.has(f.config.id)) + .concat(newFeeds) + feedsRef.current = updatedFeeds forceRender(n => n + 1) } } @@ -1153,6 +1195,7 @@ function DragOverlay({ visible: Animated.SharedValue }) { const t = useTheme() + const {isMobile} = useWebMediaQueries() // Position overlay at finger position, offset by half item height (~30px) to center it const animatedStyle = useAnimatedStyle(() => { @@ -1168,11 +1211,13 @@ function DragOverlay({