Skip to content

Commit 29240bc

Browse files
committed
Support chat tab reordering in UI
1 parent c82867a commit 29240bc

2 files changed

Lines changed: 78 additions & 29 deletions

File tree

Core/Sources/SuggestionWidget/ChatWindowView.swift

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ struct ChatTabBar: View {
160160
}
161161

162162
@Environment(\.chatTabPool) var chatTabPool
163+
@State var draggingTabId: String?
163164

164165
var body: some View {
165166
WithViewStore(
@@ -186,6 +187,21 @@ struct ChatTabBar: View {
186187
tab.menu
187188
}
188189
.id(info.id)
190+
.onDrag {
191+
print("dragging tab \(info.id)")
192+
draggingTabId = info.id
193+
return NSItemProvider(object: info.id as NSString)
194+
}
195+
.onDrop(
196+
of: [.text],
197+
delegate: ChatTabBarDropDelegate(
198+
store: store,
199+
tabs: viewStore.state.tabInfo,
200+
itemId: info.id,
201+
draggingTabId: $draggingTabId
202+
)
203+
)
204+
189205
} else {
190206
EmptyView()
191207
}
@@ -264,6 +280,30 @@ struct ChatTabBar: View {
264280
}
265281
}
266282

283+
struct ChatTabBarDropDelegate: DropDelegate {
284+
let store: StoreOf<ChatPanelFeature>
285+
let tabs: IdentifiedArray<String, ChatTabInfo>
286+
let itemId: String
287+
@Binding var draggingTabId: String?
288+
289+
func dropUpdated(info: DropInfo) -> DropProposal? {
290+
return DropProposal(operation: .move)
291+
}
292+
293+
func performDrop(info: DropInfo) -> Bool {
294+
draggingTabId = nil
295+
return true
296+
}
297+
298+
func dropEntered(info: DropInfo) {
299+
guard itemId != draggingTabId else { return }
300+
let from = tabs.firstIndex { $0.id == draggingTabId }
301+
let to = tabs.firstIndex { $0.id == itemId }
302+
guard let from, let to, from != to else { return }
303+
store.send(.moveChatTab(from: from, to: to))
304+
}
305+
}
306+
267307
struct ChatTabBarButton<Content: View>: View {
268308
let store: StoreOf<ChatPanelFeature>
269309
let info: ChatTabInfo
@@ -273,33 +313,30 @@ struct ChatTabBarButton<Content: View>: View {
273313

274314
var body: some View {
275315
HStack(spacing: 0) {
276-
Button(action: {
277-
store.send(.tabClicked(id: info.id))
278-
}) {
279-
content()
280-
.font(.callout)
281-
.lineLimit(1)
282-
.frame(maxWidth: 120)
283-
.padding(.horizontal, 32)
284-
.contentShape(Rectangle())
285-
}
286-
.buttonStyle(PlainButtonStyle())
287-
288-
.overlay(alignment: .leading) {
289-
Button(action: {
290-
store.send(.closeTabButtonClicked(id: info.id))
291-
}) {
292-
Image(systemName: "xmark")
293-
.foregroundColor(.secondary)
316+
content()
317+
.font(.callout)
318+
.lineLimit(1)
319+
.frame(maxWidth: 120)
320+
.padding(.horizontal, 32)
321+
.contentShape(Rectangle())
322+
.onTapGesture {
323+
store.send(.tabClicked(id: info.id))
294324
}
295-
.buttonStyle(.plain)
296-
.padding(2)
297-
.padding(.leading, 8)
298-
.opacity(isHovered ? 1 : 0)
299-
}
300-
.onHover { isHovered = $0 }
301-
.animation(.linear(duration: 0.1), value: isHovered)
302-
.animation(.linear(duration: 0.1), value: isSelected)
325+
.overlay(alignment: .leading) {
326+
Button(action: {
327+
store.send(.closeTabButtonClicked(id: info.id))
328+
}) {
329+
Image(systemName: "xmark")
330+
.foregroundColor(.secondary)
331+
}
332+
.buttonStyle(.plain)
333+
.padding(2)
334+
.padding(.leading, 8)
335+
.opacity(isHovered ? 1 : 0)
336+
}
337+
.onHover { isHovered = $0 }
338+
.animation(.linear(duration: 0.1), value: isHovered)
339+
.animation(.linear(duration: 0.1), value: isSelected)
303340

304341
Divider().padding(.vertical, 6)
305342
}

Core/Sources/SuggestionWidget/FeatureReducers/ChatPanelFeature.swift

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public struct ChatPanelFeature: ReducerProtocol {
2727
public var tabInfo: IdentifiedArray<String, ChatTabInfo>
2828
public var tabCollection: [ChatTabBuilderCollection]
2929
public var selectedTabId: String?
30-
30+
3131
public var selectedTabInfo: ChatTabInfo? {
3232
guard let id = selectedTabId else { return tabInfo.first }
3333
return tabInfo[id: id]
@@ -69,7 +69,8 @@ public struct ChatPanelFeature: ReducerProtocol {
6969
case appendAndSelectTab(ChatTabInfo)
7070
case switchToNextTab
7171
case switchToPreviousTab
72-
72+
case moveChatTab(from: Int, to: Int)
73+
7374
case chatTab(id: String, action: ChatTabItem.Action)
7475
}
7576

@@ -205,7 +206,18 @@ public struct ChatPanelFeature: ReducerProtocol {
205206
let targetId = state.chatTabGroup.tabInfo[previousIndex].id
206207
state.chatTabGroup.selectedTabId = targetId
207208
return .none
208-
209+
210+
case let .moveChatTab(from, to):
211+
guard from >= 0, from < state.chatTabGroup.tabInfo.endIndex, to >= 0,
212+
to <= state.chatTabGroup.tabInfo.endIndex
213+
else {
214+
return .none
215+
}
216+
let tab = state.chatTabGroup.tabInfo[from]
217+
state.chatTabGroup.tabInfo.remove(at: from)
218+
state.chatTabGroup.tabInfo.insert(tab, at: to)
219+
return .none
220+
209221
case .chatTab:
210222
return .none
211223
}

0 commit comments

Comments
 (0)