1
+ import { DndContext , pointerWithin , useDroppable } from "@dnd-kit/core"
1
2
import { ActionButton } from "@follow/components/ui/button/index.js"
3
+ import type { FeedViewType } from "@follow/constants"
2
4
import { Routes , views } from "@follow/constants"
3
5
import { useTypeScriptHappyCallback } from "@follow/hooks"
4
6
import { useRegisterGlobalContext } from "@follow/shared/bridge"
@@ -19,13 +21,14 @@ import { shortcuts } from "~/constants/shortcuts"
19
21
import { useNavigateEntry } from "~/hooks/biz/useNavigateEntry"
20
22
import { useReduceMotion } from "~/hooks/biz/useReduceMotion"
21
23
import { getRouteParams } from "~/hooks/biz/useRouteParams"
24
+ import { useBatchUpdateSubscription } from "~/hooks/biz/useSubscriptionActions"
22
25
import { useAuthQuery } from "~/hooks/common"
23
26
import { Queries } from "~/queries"
24
27
import { useSubscriptionStore } from "~/store/subscription"
25
28
import { useFeedUnreadStore } from "~/store/unread"
26
29
27
30
import { WindowUnderBlur } from "../../components/ui/background"
28
- import { getSelectedFeedIds , setSelectedFeedIds } from "./atom"
31
+ import { getSelectedFeedIds , setSelectedFeedIds , useSelectedFeedIds } from "./atom"
29
32
import { FeedColumnHeader } from "./header"
30
33
import { FeedList } from "./list"
31
34
@@ -87,27 +90,6 @@ export function FeedColumn({ children, className }: PropsWithChildren<{ classNam
87
90
}
88
91
} , [ setActive_ ] )
89
92
90
- const [ useHotkeysSwitch , setUseHotkeysSwitch ] = useState < boolean > ( false )
91
- useHotkeys (
92
- shortcuts . feeds . switchBetweenViews . key ,
93
- ( e ) => {
94
- e . preventDefault ( )
95
- setUseHotkeysSwitch ( true )
96
- if ( isHotkeyPressed ( "Left" ) ) {
97
- setActive ( ( i ) => {
98
- if ( i === 0 ) {
99
- return views . length - 1
100
- } else {
101
- return i - 1
102
- }
103
- } )
104
- } else {
105
- setActive ( ( i ) => ( i + 1 ) % views . length )
106
- }
107
- } ,
108
- { scopes : HotKeyScopeMap . Home } ,
109
- )
110
-
111
93
useWheel (
112
94
( { event, last, memo : wait = false , direction : [ dx ] , delta : [ dex ] } ) => {
113
95
if ( ! last ) {
@@ -131,88 +113,150 @@ export function FeedColumn({ children, className }: PropsWithChildren<{ classNam
131
113
} ,
132
114
)
133
115
134
- const unreadByView = useUnreadByView ( )
135
- const { t } = useTranslation ( )
136
-
137
- const showSidebarUnreadCount = useUISettingKey ( "sidebarShowUnreadCount" )
138
-
139
116
useRegisterGlobalContext ( "goToDiscover" , ( ) => {
140
117
window . router . navigate ( Routes . Discover )
141
118
} )
142
119
120
+ const [ selectedIds , setSelectedIds ] = useSelectedFeedIds ( )
121
+
122
+ const { mutate } = useBatchUpdateSubscription ( )
123
+
143
124
return (
144
- < WindowUnderBlur
145
- className = { cn ( "relative flex h-full flex-col space-y-3 pt-2.5" , className ) }
146
- onClick = { useCallback ( ( ) => navigateBackHome ( ) , [ navigateBackHome ] ) }
147
- >
148
- < FeedColumnHeader />
125
+ < DndContext
126
+ collisionDetection = { pointerWithin }
127
+ onDragEnd = { ( event ) => {
128
+ if ( ! event . over ) {
129
+ return
130
+ }
149
131
150
- < div
151
- className = "flex w-full justify-between px-3 text-xl text-theme-vibrancyFg"
152
- onClick = { stopPropagation }
153
- >
154
- { views . map ( ( item , index ) => (
155
- < ActionButton
156
- key = { item . name }
157
- tooltip = { t ( item . name ) }
158
- shortcut = { `${ index + 1 } ` }
159
- className = { cn (
160
- active === index && item . className ,
161
- "flex h-11 flex-col items-center gap-1 text-xl" ,
162
- ELECTRON ? "hover:!bg-theme-item-hover" : "" ,
163
- active === index && useHotkeysSwitch ? "bg-theme-item-active" : "" ,
164
- ) }
165
- onClick = { ( e ) => {
166
- setActive ( index )
167
- setUseHotkeysSwitch ( false )
168
- e . stopPropagation ( )
169
- } }
170
- >
171
- { item . icon }
172
- { showSidebarUnreadCount ? (
173
- < div className = "text-[0.625rem] font-medium leading-none" >
174
- { unreadByView [ index ] > 99 ? (
175
- < span className = "-mr-0.5" > 99+</ span >
176
- ) : (
177
- unreadByView [ index ]
178
- ) }
179
- </ div >
180
- ) : (
181
- < i
182
- className = { cn (
183
- "i-mgc-round-cute-fi text-[0.25rem]" ,
184
- unreadByView [ index ]
185
- ? active === index
186
- ? "opacity-100"
187
- : "opacity-60"
188
- : "opacity-0" ,
189
- ) }
190
- />
191
- ) }
192
- </ ActionButton >
193
- ) ) }
194
- </ div >
195
- < div
196
- className = "relative flex size-full overflow-hidden"
197
- ref = { carouselRef }
198
- onPointerDown = { useTypeScriptHappyCallback ( ( e ) => {
199
- if ( ! ( e . target instanceof HTMLElement ) || ! e . target . closest ( "[data-feed-id]" ) ) {
200
- const nextSelectedFeedIds = getSelectedFeedIds ( )
201
- setSelectedFeedIds ( nextSelectedFeedIds . length === 0 ? nextSelectedFeedIds : [ ] )
202
- }
203
- } , [ ] ) }
132
+ const { category, view } = event . over . data . current as {
133
+ category : string
134
+ view : FeedViewType
135
+ }
136
+
137
+ mutate ( { category, view, feedIdList : selectedIds } )
138
+
139
+ setSelectedIds ( [ ] )
140
+ } }
141
+ >
142
+ < WindowUnderBlur
143
+ className = { cn ( "relative flex h-full flex-col space-y-3 pt-2.5" , className ) }
144
+ onClick = { useCallback ( ( ) => navigateBackHome ( ) , [ navigateBackHome ] ) }
204
145
>
205
- < SwipeWrapper active = { active } >
146
+ < FeedColumnHeader />
147
+
148
+ < div
149
+ className = "flex w-full justify-between px-3 text-xl text-theme-vibrancyFg"
150
+ onClick = { stopPropagation }
151
+ >
206
152
{ views . map ( ( item , index ) => (
207
- < section key = { item . name } className = "h-full w-feed-col shrink-0 snap-center" >
208
- < FeedList className = "flex size-full flex-col text-sm" view = { index } />
209
- </ section >
153
+ < ViewSwitchButton
154
+ key = { item . name }
155
+ item = { item }
156
+ index = { index }
157
+ active = { active }
158
+ setActive = { setActive }
159
+ />
210
160
) ) }
211
- </ SwipeWrapper >
212
- </ div >
161
+ </ div >
162
+ < div
163
+ className = "relative flex size-full"
164
+ ref = { carouselRef }
165
+ onPointerDown = { useTypeScriptHappyCallback ( ( e ) => {
166
+ if ( ! ( e . target instanceof HTMLElement ) || ! e . target . closest ( "[data-feed-id]" ) ) {
167
+ const nextSelectedFeedIds = getSelectedFeedIds ( )
168
+ setSelectedFeedIds ( nextSelectedFeedIds . length === 0 ? nextSelectedFeedIds : [ ] )
169
+ }
170
+ } , [ ] ) }
171
+ >
172
+ < SwipeWrapper active = { active } >
173
+ { views . map ( ( item , index ) => (
174
+ < section key = { item . name } className = "h-full w-feed-col shrink-0 snap-center" >
175
+ < FeedList className = "flex size-full flex-col text-sm" view = { index } />
176
+ </ section >
177
+ ) ) }
178
+ </ SwipeWrapper >
179
+ </ div >
180
+
181
+ { children }
182
+ </ WindowUnderBlur >
183
+ </ DndContext >
184
+ )
185
+ }
186
+
187
+ const ViewSwitchButton : FC < {
188
+ item : ( typeof views ) [ number ]
189
+ index : number
190
+
191
+ active : number
192
+ setActive : ( next : number | ( ( prev : number ) => number ) ) => void
193
+ } > = ( { item, index, active, setActive } ) => {
194
+ const [ useHotkeysSwitch , setUseHotkeysSwitch ] = useState < boolean > ( false )
195
+ useHotkeys (
196
+ shortcuts . feeds . switchBetweenViews . key ,
197
+ ( e ) => {
198
+ e . preventDefault ( )
199
+ setUseHotkeysSwitch ( true )
200
+ if ( isHotkeyPressed ( "Left" ) ) {
201
+ setActive ( ( i ) => {
202
+ if ( i === 0 ) {
203
+ return views . length - 1
204
+ } else {
205
+ return i - 1
206
+ }
207
+ } )
208
+ } else {
209
+ setActive ( ( i ) => ( i + 1 ) % views . length )
210
+ }
211
+ } ,
212
+ { scopes : HotKeyScopeMap . Home } ,
213
+ )
213
214
214
- { children }
215
- </ WindowUnderBlur >
215
+ const unreadByView = useUnreadByView ( )
216
+ const { t } = useTranslation ( )
217
+ const showSidebarUnreadCount = useUISettingKey ( "sidebarShowUnreadCount" )
218
+
219
+ const { isOver, setNodeRef } = useDroppable ( {
220
+ id : `view-${ item . name } ` ,
221
+ data : {
222
+ category : "" ,
223
+ view : item . view ,
224
+ } ,
225
+ } )
226
+
227
+ return (
228
+ < ActionButton
229
+ ref = { setNodeRef }
230
+ key = { item . name }
231
+ tooltip = { t ( item . name ) }
232
+ shortcut = { `${ index + 1 } ` }
233
+ className = { cn (
234
+ active === index && item . className ,
235
+ "flex h-11 flex-col items-center gap-1 text-xl" ,
236
+ ELECTRON ? "hover:!bg-theme-item-hover" : "" ,
237
+ active === index && useHotkeysSwitch ? "bg-theme-item-active" : "" ,
238
+ isOver && "border-theme-accent-400 bg-theme-accent-400/60" ,
239
+ ) }
240
+ onClick = { ( e ) => {
241
+ setActive ( index )
242
+ setUseHotkeysSwitch ( false )
243
+ e . stopPropagation ( )
244
+ } }
245
+ >
246
+ { item . icon }
247
+ { showSidebarUnreadCount ? (
248
+ < div className = "text-[0.625rem] font-medium leading-none" >
249
+ { unreadByView [ index ] > 99 ? < span className = "-mr-0.5" > 99+</ span > : unreadByView [ index ] }
250
+ </ div >
251
+ ) : (
252
+ < i
253
+ className = { cn (
254
+ "i-mgc-round-cute-fi text-[0.25rem]" ,
255
+ unreadByView [ index ] ? ( active === index ? "opacity-100" : "opacity-60" ) : "opacity-0" ,
256
+ ) }
257
+ />
258
+ ) }
259
+ </ ActionButton >
216
260
)
217
261
}
218
262
0 commit comments