mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-28 15:44:32 +00:00
Somewhat working file tray
This commit is contained in:
parent
4c7e79b954
commit
30422c3d95
29 changed files with 4032 additions and 65 deletions
174
DynamicIsland/extensions/NSItemProvider+LoadHelpers.swift
Normal file
174
DynamicIsland/extensions/NSItemProvider+LoadHelpers.swift
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
//
|
||||
// NSItemProvider+LoadHelpers.swift
|
||||
// DynamicIsland
|
||||
//
|
||||
// Ported shelf drop helpers from boringNotch.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Foundation
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
extension NSItemProvider {
|
||||
func extractItem() async -> URL? {
|
||||
await loadFileURL(typeIdentifier: UTType.item.identifier)
|
||||
}
|
||||
|
||||
/// Detects if this is a file dragged from the filesystem
|
||||
func extractFileURL() async -> URL? {
|
||||
if hasItemConformingToTypeIdentifier(UTType.fileURL.identifier) {
|
||||
return await loadFileURL(typeIdentifier: UTType.fileURL.identifier)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Loads raw data for the given type identifier
|
||||
func loadData() async -> Data? {
|
||||
guard hasItemConformingToTypeIdentifier(UTType.data.identifier) else { return nil }
|
||||
return await withCheckedContinuation { (cont: CheckedContinuation<Data?, Never>) in
|
||||
loadItem(forTypeIdentifier: UTType.data.identifier, options: nil) { item, error in
|
||||
if let error {
|
||||
print("Error loading data for type \(UTType.data.identifier): \(error.localizedDescription)")
|
||||
cont.resume(returning: nil)
|
||||
return
|
||||
}
|
||||
if let url = item as? URL, let data = try? Data(contentsOf: url) {
|
||||
if !url.absoluteString.contains("com.apple.SwiftUI.filePromises") {
|
||||
cont.resume(returning: nil)
|
||||
return
|
||||
}
|
||||
self.suggestedName = self.suggestedName ?? url.lastPathComponent
|
||||
let fileManager = FileManager.default
|
||||
let folderURL = url.deletingLastPathComponent()
|
||||
do {
|
||||
try fileManager.removeItem(at: url)
|
||||
let contents = try fileManager.contentsOfDirectory(atPath: folderURL.path)
|
||||
if contents.isEmpty {
|
||||
try fileManager.removeItem(at: folderURL)
|
||||
}
|
||||
} catch {
|
||||
print("Error: \(error.localizedDescription)")
|
||||
}
|
||||
cont.resume(returning: data)
|
||||
} else if let data = item as? Data {
|
||||
cont.resume(returning: data)
|
||||
} else {
|
||||
cont.resume(returning: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to extract a URL (web link) from the provider
|
||||
func extractURL() async -> URL? {
|
||||
if hasItemConformingToTypeIdentifier(UTType.url.identifier) {
|
||||
if let url = await loadURL(typeIdentifier: UTType.url.identifier) {
|
||||
guard url.scheme != nil else { return nil }
|
||||
return url
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractText() async -> String? {
|
||||
let textTypes = [UTType.utf8PlainText.identifier, UTType.plainText.identifier]
|
||||
for typeIdentifier in textTypes where hasItemConformingToTypeIdentifier(typeIdentifier) {
|
||||
if let text = await loadText(typeIdentifier: typeIdentifier) {
|
||||
return text
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Loads a file URL from the provider for the given type identifier.
|
||||
func loadFileURL(typeIdentifier: String) async -> URL? {
|
||||
await withCheckedContinuation { (cont: CheckedContinuation<URL?, Never>) in
|
||||
loadItem(forTypeIdentifier: typeIdentifier, options: nil) { item, error in
|
||||
if let error {
|
||||
print("❌ Error loading item for type \(typeIdentifier): \(error.localizedDescription)")
|
||||
cont.resume(returning: nil)
|
||||
return
|
||||
}
|
||||
var resolvedURL: URL?
|
||||
if let url = item as? URL {
|
||||
resolvedURL = url
|
||||
} else if let data = item as? Data {
|
||||
if let string = String(data: data, encoding: .utf8) {
|
||||
if let url = URL(string: string) {
|
||||
resolvedURL = url
|
||||
} else if string.hasPrefix("/") {
|
||||
resolvedURL = URL(fileURLWithPath: string)
|
||||
}
|
||||
}
|
||||
if resolvedURL == nil {
|
||||
let bookmark = Bookmark(data: data)
|
||||
resolvedURL = bookmark.resolveURL()
|
||||
}
|
||||
} else if let string = item as? String {
|
||||
if let url = URL(string: string) {
|
||||
resolvedURL = url
|
||||
} else if string.hasPrefix("/") {
|
||||
resolvedURL = URL(fileURLWithPath: string)
|
||||
}
|
||||
}
|
||||
cont.resume(returning: resolvedURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads a URL from the provider for the given type identifier.
|
||||
func loadURL(typeIdentifier: String) async -> URL? {
|
||||
await withCheckedContinuation { (cont: CheckedContinuation<URL?, Never>) in
|
||||
loadItem(forTypeIdentifier: typeIdentifier, options: nil) { item, error in
|
||||
if error != nil {
|
||||
cont.resume(returning: nil)
|
||||
return
|
||||
}
|
||||
if let url = item as? URL {
|
||||
cont.resume(returning: url)
|
||||
} else if let data = item as? Data {
|
||||
if let string = String(data: data, encoding: .utf8) {
|
||||
if let url = URL(string: string) {
|
||||
cont.resume(returning: url)
|
||||
return
|
||||
} else if string.hasPrefix("/") {
|
||||
cont.resume(returning: URL(fileURLWithPath: string))
|
||||
return
|
||||
}
|
||||
}
|
||||
cont.resume(returning: nil)
|
||||
} else if let string = item as? String {
|
||||
if let url = URL(string: string) {
|
||||
cont.resume(returning: url)
|
||||
} else if string.hasPrefix("/") {
|
||||
cont.resume(returning: URL(fileURLWithPath: string))
|
||||
} else {
|
||||
cont.resume(returning: nil)
|
||||
}
|
||||
} else {
|
||||
cont.resume(returning: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads text from the provider for the given type identifier.
|
||||
func loadText(typeIdentifier: String) async -> String? {
|
||||
await withCheckedContinuation { (cont: CheckedContinuation<String?, Never>) in
|
||||
loadItem(forTypeIdentifier: typeIdentifier, options: nil) { item, error in
|
||||
if error != nil {
|
||||
cont.resume(returning: nil)
|
||||
return
|
||||
}
|
||||
if let string = item as? String {
|
||||
cont.resume(returning: string)
|
||||
} else if let data = item as? Data,
|
||||
let string = String(data: data, encoding: .utf8) {
|
||||
cont.resume(returning: string)
|
||||
} else {
|
||||
cont.resume(returning: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
DynamicIsland/extensions/NSMenu+AssociatedObject.swift
Normal file
21
DynamicIsland/extensions/NSMenu+AssociatedObject.swift
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// NSMenu+AssociatedObject.swift
|
||||
// DynamicIsland
|
||||
//
|
||||
// Retains menu action targets using associated objects.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
private final class MenuActionBox: NSObject {
|
||||
let target: AnyObject
|
||||
init(target: AnyObject) { self.target = target }
|
||||
}
|
||||
|
||||
extension NSMenu {
|
||||
private static let retainedAction = AssociatedObject<MenuActionBox>()
|
||||
|
||||
func retainActionTarget(_ target: AnyObject) {
|
||||
NSMenu.retainedAction[self] = MenuActionBox(target: target)
|
||||
}
|
||||
}
|
||||
44
DynamicIsland/extensions/URL+SecurityScoped.swift
Normal file
44
DynamicIsland/extensions/URL+SecurityScoped.swift
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// URL+SecurityScoped.swift
|
||||
// DynamicIsland
|
||||
//
|
||||
// Ported from boringNotch shelf utilities.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
extension URL {
|
||||
func accessSecurityScopedResource<Value>(accessor: (URL) throws -> Value) rethrows -> Value {
|
||||
let didStartAccessing = startAccessingSecurityScopedResource()
|
||||
defer {
|
||||
if didStartAccessing {
|
||||
stopAccessingSecurityScopedResource()
|
||||
}
|
||||
}
|
||||
return try accessor(self)
|
||||
}
|
||||
|
||||
/// Async version of accessSecurityScopedResource
|
||||
func accessSecurityScopedResource<Value>(accessor: (URL) async throws -> Value) async rethrows -> Value {
|
||||
let didStartAccessing = startAccessingSecurityScopedResource()
|
||||
defer {
|
||||
if didStartAccessing {
|
||||
stopAccessingSecurityScopedResource()
|
||||
}
|
||||
}
|
||||
return try await accessor(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == URL {
|
||||
func accessSecurityScopedResources<Value>(accessor: ([URL]) async throws -> Value) async rethrows -> Value {
|
||||
let didStart = map { $0.startAccessingSecurityScopedResource() }
|
||||
defer {
|
||||
for (url, started) in zip(self, didStart) where started {
|
||||
url.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
}
|
||||
return try await accessor(self)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue