Fix drag and drop UI on web

This commit is contained in:
Alex Benzer
2026-01-01 19:36:42 -08:00
parent 2e9f7f329f
commit f49065d421
+53 -8
View File
@@ -54,6 +54,7 @@ import * as FeedCard from '#/components/FeedCard'
import {SearchInput} from '#/components/forms/SearchInput' import {SearchInput} from '#/components/forms/SearchInput'
import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline' import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
import {Menu_Stroke2_Corner0_Rounded as DragHandleIcon} from '#/components/icons/Menu' 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 {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
import * as Layout from '#/components/Layout' import * as Layout from '#/components/Layout'
import * as ListCard from '#/components/ListCard' import * as ListCard from '#/components/ListCard'
@@ -90,6 +91,10 @@ type FlatlistSlice =
type: 'noFollowingFeed' type: 'noFollowingFeed'
key: string key: string
} }
| {
type: 'noPinnedFeeds'
key: string
}
export function FeedsScreen(_props: Props) { export function FeedsScreen(_props: Props) {
const { const {
@@ -221,14 +226,19 @@ const FeedsScreenInner = React.memo(
key: 'pinnedFeedsSection', key: 'pinnedFeedsSection',
type: 'pinnedFeedsSection', type: 'pinnedFeedsSection',
}) })
}
if (!hasFollowingFeed && pinnedCount > 0) { if (!hasFollowingFeed) {
slices.push({ slices.push({
key: 'noFollowingFeed', key: 'noFollowingFeed',
type: 'noFollowingFeed', type: 'noFollowingFeed',
}) })
} }
} else {
slices.push({
key: 'noPinnedFeeds',
type: 'noPinnedFeeds',
})
}
} else { } else {
slices.push({ slices.push({
key: 'savedFeedNoResults', key: 'savedFeedNoResults',
@@ -307,11 +317,33 @@ const FeedsScreenInner = React.memo(
<NoFollowingFeed /> <NoFollowingFeed />
</View> </View>
) )
} else if (item.type === 'noPinnedFeeds') {
return (
<View
style={[
pal.border,
a.flex_row,
a.align_center,
a.gap_sm,
{
borderBottomWidth: 1,
paddingHorizontal: 16,
paddingVertical: 16,
},
]}>
<PinIcon size="sm" fill={pal.colors.textLight} />
<Text type="md" style={pal.textLight}>
<Trans>You don't have any pinned feeds.</Trans>
</Text>
</View>
)
} }
return null return null
}, },
[ [
pal.border, pal.border,
pal.textLight,
pal.colors,
overlayY, overlayY,
overlayVisible, overlayVisible,
setOverlayFeed, setOverlayFeed,
@@ -422,14 +454,24 @@ const PinnedFeedsSection = React.memo(function PinnedFeedsSection({
const isDraggingRef = React.useRef(false) const isDraggingRef = React.useRef(false)
// Keep ref in sync with server feeds // 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(() => { React.useEffect(() => {
if (!isDraggingRef.current) { if (!isDraggingRef.current) {
// Check if feeds actually changed (different items or order from server) const currentIds = new Set(feedsRef.current.map(f => f.config.id))
const currentIds = feedsRef.current.map(f => f.config.id).join(',') const serverIds = new Set(serverFeeds.map(f => f.config.id))
const serverIds = serverFeeds.map(f => f.config.id).join(',')
if (currentIds !== serverIds) { // Find new feeds (in server but not local) and removed feeds (in local but not server)
feedsRef.current = serverFeeds 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) forceRender(n => n + 1)
} }
} }
@@ -1153,6 +1195,7 @@ function DragOverlay({
visible: Animated.SharedValue<number> visible: Animated.SharedValue<number>
}) { }) {
const t = useTheme() const t = useTheme()
const {isMobile} = useWebMediaQueries()
// Position overlay at finger position, offset by half item height (~30px) to center it // Position overlay at finger position, offset by half item height (~30px) to center it
const animatedStyle = useAnimatedStyle(() => { const animatedStyle = useAnimatedStyle(() => {
@@ -1168,11 +1211,13 @@ function DragOverlay({
<Animated.View <Animated.View
style={[ style={[
animatedStyle, animatedStyle,
a.mx_auto,
{ {
position: 'absolute', position: 'absolute',
left: 0, left: 0,
right: 0, right: 0,
zIndex: 1000, zIndex: 1000,
maxWidth: isMobile ? undefined : 600,
}, },
]} ]}
pointerEvents="none"> pointerEvents="none">