mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Somewhat working file tray
This commit is contained in:
parent
4c7e79b954
commit
30422c3d95
29 changed files with 4032 additions and 65 deletions
|
|
@ -0,0 +1,143 @@
|
|||
//
|
||||
// ShelfStateViewModel.swift
|
||||
// boringNotch
|
||||
//
|
||||
// Created by Alexander on 2025-10-09.
|
||||
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
@MainActor
|
||||
final class ShelfStateViewModel: ObservableObject {
|
||||
static let shared = ShelfStateViewModel()
|
||||
|
||||
@Published private(set) var items: [ShelfItem] = [] {
|
||||
didSet { ShelfPersistenceService.shared.save(items) }
|
||||
}
|
||||
|
||||
@Published var isLoading: Bool = false
|
||||
|
||||
var isEmpty: Bool { items.isEmpty }
|
||||
|
||||
// Queue for deferred bookmark updates to avoid publishing during view updates
|
||||
private var pendingBookmarkUpdates: [ShelfItem.ID: Data] = [:]
|
||||
private var updateTask: Task<Void, Never>?
|
||||
|
||||
private init() {
|
||||
items = ShelfPersistenceService.shared.load()
|
||||
}
|
||||
|
||||
|
||||
func add(_ newItems: [ShelfItem]) {
|
||||
guard !newItems.isEmpty else { return }
|
||||
var merged = items
|
||||
// Deduplicate by identityKey while preserving order (existing first)
|
||||
var seen: Set<String> = Set(merged.map { $0.identityKey })
|
||||
for it in newItems {
|
||||
let key = it.identityKey
|
||||
if !seen.contains(key) {
|
||||
merged.append(it)
|
||||
seen.insert(key)
|
||||
}
|
||||
}
|
||||
items = merged
|
||||
}
|
||||
|
||||
func remove(_ item: ShelfItem) {
|
||||
item.cleanupStoredData()
|
||||
items.removeAll { $0.id == item.id }
|
||||
}
|
||||
|
||||
func updateBookmark(for item: ShelfItem, bookmark: Data) {
|
||||
guard let idx = items.firstIndex(where: { $0.id == item.id }) else { return }
|
||||
if case .file = items[idx].kind {
|
||||
items[idx].kind = .file(bookmark: bookmark)
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduleDeferredBookmarkUpdate(for item: ShelfItem, bookmark: Data) {
|
||||
pendingBookmarkUpdates[item.id] = bookmark
|
||||
|
||||
// Cancel existing task and schedule a new one
|
||||
updateTask?.cancel()
|
||||
updateTask = Task { @MainActor [weak self] in
|
||||
await Task.yield()
|
||||
|
||||
guard let self = self else { return }
|
||||
|
||||
for (itemID, bookmarkData) in self.pendingBookmarkUpdates {
|
||||
if let idx = self.items.firstIndex(where: { $0.id == itemID }),
|
||||
case .file = self.items[idx].kind {
|
||||
self.items[idx].kind = .file(bookmark: bookmarkData)
|
||||
}
|
||||
}
|
||||
|
||||
self.pendingBookmarkUpdates.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func load(_ providers: [NSItemProvider]) {
|
||||
guard !providers.isEmpty else { return }
|
||||
isLoading = true
|
||||
Task { [weak self] in
|
||||
let dropped = await ShelfDropService.items(from: providers)
|
||||
await MainActor.run {
|
||||
self?.add(dropped)
|
||||
self?.isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cleanupInvalidItems() {
|
||||
Task { [weak self] in
|
||||
guard let self else { return }
|
||||
var keep: [ShelfItem] = []
|
||||
for item in self.items {
|
||||
switch item.kind {
|
||||
case .file(let data):
|
||||
let bookmark = Bookmark(data: data)
|
||||
if await bookmark.validate() {
|
||||
keep.append(item)
|
||||
} else {
|
||||
item.cleanupStoredData()
|
||||
}
|
||||
default:
|
||||
keep.append(item)
|
||||
}
|
||||
}
|
||||
await MainActor.run { self.items = keep }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func resolveFileURL(for item: ShelfItem) -> URL? {
|
||||
guard case .file(let bookmarkData) = item.kind else { return nil }
|
||||
let bookmark = Bookmark(data: bookmarkData)
|
||||
let result = bookmark.resolve()
|
||||
if let refreshed = result.refreshedData, refreshed != bookmarkData {
|
||||
NSLog("Bookmark for \(item) stale; refreshing")
|
||||
scheduleDeferredBookmarkUpdate(for: item, bookmark: refreshed)
|
||||
}
|
||||
return result.url
|
||||
}
|
||||
|
||||
func resolveAndUpdateBookmark(for item: ShelfItem) -> URL? {
|
||||
guard case .file(let bookmarkData) = item.kind else { return nil }
|
||||
let bookmark = Bookmark(data: bookmarkData)
|
||||
let result = bookmark.resolve()
|
||||
if let refreshed = result.refreshedData, refreshed != bookmarkData {
|
||||
NSLog("Bookmark for \(item) stale; refreshing")
|
||||
updateBookmark(for: item, bookmark: refreshed)
|
||||
}
|
||||
return result.url
|
||||
}
|
||||
|
||||
func resolveFileURLs(for items: [ShelfItem]) -> [URL] {
|
||||
var urls: [URL] = []
|
||||
for it in items {
|
||||
if let u = resolveFileURL(for: it) { urls.append(u) }
|
||||
}
|
||||
return urls
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue