Skip to content

Commit

Permalink
Merge pull request #190 from mudkipme/feat/nested-tags
Browse files Browse the repository at this point in the history
feat: collapsible tags
  • Loading branch information
mudkipme authored Jun 23, 2024
2 parents 364a0cb + 7b4834e commit 7c5b02e
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 13 deletions.
4 changes: 4 additions & 0 deletions MoeMemos.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
194C4C372BC6FCBE00076DEE /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 194C4C362BC6FCBE00076DEE /* PrivacyInfo.xcprivacy */; };
194C4C382BC6FCBE00076DEE /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 194C4C362BC6FCBE00076DEE /* PrivacyInfo.xcprivacy */; };
194C4C392BC6FCBE00076DEE /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 194C4C362BC6FCBE00076DEE /* PrivacyInfo.xcprivacy */; };
1951055E2C287A0F009EA669 /* NestedTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1951055D2C287A0F009EA669 /* NestedTag.swift */; };
195E5D6B28C704AC00784EF5 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195E5D6A28C704AC00784EF5 /* Date.swift */; };
195E5D7128C70A3800784EF5 /* HeatmapStat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195E5D7028C70A3800784EF5 /* HeatmapStat.swift */; };
196C7DC02B087A0000EDD8B6 /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 196C7DBF2B087A0000EDD8B6 /* Account */; };
Expand Down Expand Up @@ -178,6 +179,7 @@
194B273428C3973600A48675 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
194B273728C3973600A48675 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
194C4C362BC6FCBE00076DEE /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
1951055D2C287A0F009EA669 /* NestedTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedTag.swift; sourceTree = "<group>"; };
19558A442912CEA60070CAF9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
195E5D6A28C704AC00784EF5 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
195E5D7028C70A3800784EF5 /* HeatmapStat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeatmapStat.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -429,6 +431,7 @@
children = (
19F31BFA28C3E9B2000C5207 /* Memo.swift */,
199A657C28C4761100C39994 /* Usage.swift */,
1951055D2C287A0F009EA669 /* NestedTag.swift */,
);
path = Model;
sourceTree = "<group>";
Expand Down Expand Up @@ -652,6 +655,7 @@
193AA8A728CD9ED700FF7EB6 /* URL.swift in Sources */,
19FC1C7C290DA47C0078A7F2 /* Route.swift in Sources */,
19FC1C7E290DA6E90078A7F2 /* Navigation.swift in Sources */,
1951055E2C287A0F009EA669 /* NestedTag.swift in Sources */,
1919FBE529CF738300FEF570 /* ExploreViewModel.swift in Sources */,
1911568A29802F200058F18B /* QuickLookRepresentable.swift in Sources */,
31EE786129B9F3A2005F62D0 /* Highlighter.swift in Sources */,
Expand Down
41 changes: 41 additions & 0 deletions MoeMemos/Model/NestedTag.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// NestedTag.swift
// MoeMemos
//
// Created by Mudkip on 2024/6/23.
//

import Foundation
import Models

struct NestedTag: Identifiable, Hashable {
var id: String { fullName }
var fullName: String
var name: String
var children: [NestedTag]? = nil

static func fromTagList(_ tagList: [String], prefix: String? = nil) -> [NestedTag] {
var tagDict = [String: [String]]()

for tag in tagList {
let parts = tag.split(separator: "/", maxSplits: 2).map(String.init)
if tagDict[parts[0]] == nil {
tagDict[parts[0]] = []
}

if parts.count > 1 {
tagDict[parts[0]]?.append(parts[1])
}
}

var nestedTags = [NestedTag]()
for (key, value) in tagDict {
let fullName = prefix != nil ? "\(prefix!)/\(key)" : key
let children = fromTagList(value, prefix: fullName)
let tag = NestedTag(fullName: fullName, name: key, children: children.isEmpty ? nil : children)
nestedTags.append(tag)
}

return nestedTags.sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending }
}
}
3 changes: 3 additions & 0 deletions MoeMemos/ViewModel/MemosViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import Factory
}
}
private(set) var tags: [Tag] = []
private(set) var nestedTags: [NestedTag] = []
private(set) var matrix: [DailyUsageStat] = DailyUsageStat.initialMatrix
private(set) var inited = false
private(set) var loading = false
Expand All @@ -43,6 +44,7 @@ import Factory
@MainActor
func loadTags() async throws {
tags = try await service.listTags()
nestedTags = NestedTag.fromTagList(tags.map { $0.name })
}

@MainActor
Expand Down Expand Up @@ -90,6 +92,7 @@ import Factory
tags.removeAll { tag in
tag.name == name
}
nestedTags = NestedTag.fromTagList(tags.map { $0.name })
}

@MainActor
Expand Down
20 changes: 11 additions & 9 deletions MoeMemos/Views/Sidebar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import SwiftUI
import Account
import Env
import Models

struct Sidebar: View {
@Environment(MemosViewModel.self) private var memosViewModel: MemosViewModel
Expand Down Expand Up @@ -40,15 +41,16 @@ struct Sidebar: View {
}

Section {
ForEach(memosViewModel.tags) { tag in
NavigationLink(value: Route.tag(tag)) {
Label(tag.name, systemImage: "number")
}
}.onDelete { indexSet in
let toDeleteTags = indexSet.map { memosViewModel.tags[$0].name }
Task {
for tag in toDeleteTags {
try await memosViewModel.deleteTag(name: tag)
OutlineGroup(memosViewModel.nestedTags, children: \.children) { item in
NavigationLink(value: Route.tag(Tag(name: item.fullName))) {
Label(item.name, systemImage: "number")
}.contextMenu {
Button(role: .destructive) {
Task {
try await memosViewModel.deleteTag(name: item.fullName)
}
} label: {
Label("memo.delete", systemImage: "trash")
}
}
}
Expand Down
6 changes: 2 additions & 4 deletions Packages/Models/Sources/Models/Tag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@ import Foundation
import SwiftData

public struct Tag: Hashable, Identifiable {
@Attribute(.unique)
public var id: UUID = UUID()
public var name: String
public var id: String { name }

public init(id: UUID = UUID(), name: String) {
self.id = id
public init(name: String) {
self.name = name
}
}

0 comments on commit 7c5b02e

Please sign in to comment.