| | |
| | | var bubbleDraftNote = "" |
| | | var tagNavigationHoveredGroupName: String? = nil |
| | | var tagNavigationAppDropTargetName: String? = nil |
| | | var tagNavigationLastHoverScrollID: String? = nil |
| | | var tagNavigationLastHoverScrollAt: Date? = nil |
| | | } |
| | | |
| | | private struct EditActionFeedback: Identifiable { |
| | |
| | | @State private var allApps: [AppInfo] = [] |
| | | @State private var displayGroups: [TagGroup] = [] |
| | | @State private var tagColors: [String: Int] = [:] |
| | | @State private var tagDefinitions: [String: TagDatabase.TagDef] = [:] |
| | | @State private var containerAppOrder: [String: [String]] = [:] |
| | | @State private var groupLayoutVersion = 0 |
| | | @State private var appGridScrollTargetID: String? = nil |
| | | @State private var appGridScrollRequestToken = 0 |
| | |
| | | ?? (isColorlessContainerMode ? filledColorlessContainer : nil) |
| | | } |
| | | private let rightSidebarFloatingClearance: CGFloat = 44 |
| | | private let tagNavigationHoverScrollInterval: TimeInterval = 0.22 |
| | | |
| | | private var floatingButtonSurfaceColor: Color { |
| | | colorScheme == .dark |
| | | ? Color.white.opacity(0.10) |
| | |
| | | .onChange(of: appGridInteraction.pendingTagRemovalDrop != nil) { _, _ in |
| | | publishModalInteractionState() |
| | | } |
| | | .onChange(of: smartStartNotice != nil) { _, _ in |
| | | publishModalInteractionState() |
| | | } |
| | | .onDisappear { |
| | | NotificationCenter.default.post( |
| | | name: .tagLauncherModalInteractionChanged, |
| | |
| | | NotificationCenter.default.post( |
| | | name: .tagLauncherModalInteractionChanged, |
| | | object: nil, |
| | | userInfo: ["active": appGridInteraction.pendingUncategorizedDrop != nil || appGridInteraction.pendingTagRemovalDrop != nil] |
| | | userInfo: [ |
| | | "active": smartStartNotice != nil |
| | | || appGridInteraction.pendingUncategorizedDrop != nil |
| | | || appGridInteraction.pendingTagRemovalDrop != nil |
| | | ] |
| | | ) |
| | | } |
| | | |
| | |
| | | }, |
| | | onDropOutsideGroup: { path, source, copy in |
| | | dropAppOutsideGroup(path: path, sourceTag: source, copy: copy) |
| | | }, |
| | | onReorderApps: { containerID, orderedPaths in |
| | | reorderApps(inContainer: containerID, orderedPaths: orderedPaths) |
| | | }, |
| | | onGroupActivate: { groupName in |
| | | if isColorlessContainerMode { |
| | |
| | | |
| | | private func makeDisplayGroups(apps: [AppInfo], tagOrder: [String]) -> [TagGroup] { |
| | | let order = tagOrder.isEmpty ? TagEditor.orderedTagNames() : tagOrder |
| | | let rawGroups = AppIndexer.group(apps: apps, defaultGroupName: defaultGroupName, tagOrder: order) |
| | | let rawGroups = AppIndexer.group( |
| | | apps: apps, |
| | | defaultGroupName: defaultGroupName, |
| | | tagOrder: order, |
| | | tagDefinitions: tagDefinitions, |
| | | containerAppOrder: containerAppOrder |
| | | ) |
| | | return rawGroups.map { group in |
| | | if group.name == defaultGroupName { |
| | | return TagGroup(name: tr("group.uncategorized"), apps: group.apps) |
| | | return TagGroup( |
| | | containerID: group.containerID, |
| | | name: tr("group.uncategorized"), |
| | | apps: group.apps |
| | | ) |
| | | } |
| | | if group.name == "Mac自带" { |
| | | return TagGroup(name: tr("group.appleBuiltIn"), apps: group.apps) |
| | | return TagGroup( |
| | | containerID: group.containerID, |
| | | name: tr("group.appleBuiltIn"), |
| | | apps: group.apps |
| | | ) |
| | | } |
| | | return group |
| | | } |
| | |
| | | |
| | | appGridInteraction.tagNavigationHoveredGroupName = id |
| | | fillColorlessContainer(id) |
| | | scrollToTagFromHover(id) |
| | | } |
| | | |
| | | private func handleTagNavigationAppDropHover(_ id: String, active: Bool) { |
| | |
| | | } |
| | | |
| | | appGridInteraction.tagNavigationAppDropTargetName = id |
| | | } |
| | | |
| | | private func scrollToTagFromHover(_ id: String) { |
| | | let now = Date() |
| | | if appGridInteraction.tagNavigationLastHoverScrollID == id, |
| | | let lastScrollAt = appGridInteraction.tagNavigationLastHoverScrollAt, |
| | | now.timeIntervalSince(lastScrollAt) < tagNavigationHoverScrollInterval { |
| | | return |
| | | } |
| | | appGridInteraction.tagNavigationLastHoverScrollID = id |
| | | appGridInteraction.tagNavigationLastHoverScrollAt = now |
| | | scrollTo(id) |
| | | } |
| | | |
| | | private func fillColorlessContainer(_ id: String) { |
| | |
| | | private func dropApp(path: String, sourceTag: String, targetTag: String, copy: Bool) { |
| | | resetTransientDragState(keepingPendingUncategorizedDrop: true) |
| | | |
| | | guard sourceTag != targetTag else { return } |
| | | |
| | | if isUncategorizedDropTarget(targetTag) { |
| | | confirmAndMoveAppToUncategorized(path: path) |
| | | return |
| | |
| | | } |
| | | |
| | | guard tagColors[targetTag] != nil else { return } |
| | | guard sourceTag != targetTag || copy else { return } |
| | | TagEditor.moveApp( |
| | | path: path, |
| | | from: sourceTag, |
| | |
| | | guard !appHasTag(app, tagName: targetTag) else { return } |
| | | TagEditor.appendTags([targetTag], to: [path]) |
| | | showDropRefresh() |
| | | refreshApps(forceLayoutRefresh: true) |
| | | } |
| | | |
| | | private func reorderApps(inContainer containerID: String, orderedPaths: [String]) { |
| | | resetTransientDragState() |
| | | let key = TagDatabase.normalizedContainerID(containerID, tags: tagDefinitions) |
| | | let paths = TagDatabase.normalizedAppOrderPaths(orderedPaths) |
| | | guard !key.isEmpty, !paths.isEmpty else { return } |
| | | |
| | | containerAppOrder[key] = paths |
| | | rebuildDisplayGroups(apps: allApps, tagOrder: draggedTagNames) |
| | | TagEditor.reorderApps(inContainer: key, orderedPaths: paths) |
| | | refreshApps(forceLayoutRefresh: true) |
| | | } |
| | | |
| | |
| | | allApps = snapshot.apps |
| | | quickSearchDocuments = snapshot.quickSearchDocuments |
| | | tagColors = snapshot.tagColors |
| | | tagDefinitions = snapshot.tagDefinitions |
| | | containerAppOrder = snapshot.containerAppOrder |
| | | draggedTagNames = snapshot.tagOrder |
| | | rebuildDisplayGroups(apps: snapshot.apps, tagOrder: snapshot.tagOrder) |
| | | if quickSearchVisible { |