WIP: add Quick Look preview groundwork
This commit is contained in:
@@ -18,6 +18,7 @@ let package = Package(
|
|||||||
.linkedFramework("AppKit"),
|
.linkedFramework("AppKit"),
|
||||||
.linkedFramework("Carbon"),
|
.linkedFramework("Carbon"),
|
||||||
.linkedFramework("LocalAuthentication"),
|
.linkedFramework("LocalAuthentication"),
|
||||||
|
.linkedFramework("QuickLook"),
|
||||||
.linkedFramework("Security"),
|
.linkedFramework("Security"),
|
||||||
.linkedFramework("Vision"),
|
.linkedFramework("Vision"),
|
||||||
.linkedLibrary("sqlite3")
|
.linkedLibrary("sqlite3")
|
||||||
|
|||||||
@@ -182,6 +182,23 @@ final class ClipboardCacheService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func temporaryPreviewURL(for item: ClipboardItem) -> URL? {
|
||||||
|
switch item.kind {
|
||||||
|
case .file:
|
||||||
|
let urls = FilePayload.urls(from: item.payload)
|
||||||
|
return urls.first { fileManager.fileExists(atPath: $0.path) }
|
||||||
|
case .text, .unknown:
|
||||||
|
let text = item.payload.clipboardTrimmed.isEmpty ? item.displayText : item.payload
|
||||||
|
guard !text.clipboardTrimmed.isEmpty else { return nil }
|
||||||
|
return writeTemporaryCopy(data: Data(text.utf8), id: item.id, fileExtension: "txt")
|
||||||
|
case .url:
|
||||||
|
guard let data = webLocationData(for: item.payload) else { return nil }
|
||||||
|
return writeTemporaryCopy(data: data, id: item.id, fileExtension: "webloc")
|
||||||
|
case .image, .pdf, .audio, .richText:
|
||||||
|
return temporaryReadableURL(for: item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func encryptCachedReferencesIfNeeded(for items: [ClipboardItem]) {
|
func encryptCachedReferencesIfNeeded(for items: [ClipboardItem]) {
|
||||||
queue.async { [weak self] in
|
queue.async { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
@@ -320,6 +337,16 @@ final class ClipboardCacheService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func webLocationData(for value: String) -> Data? {
|
||||||
|
let trimmed = value.clipboardTrimmed
|
||||||
|
guard !trimmed.isEmpty else { return nil }
|
||||||
|
return try? PropertyListSerialization.data(
|
||||||
|
fromPropertyList: ["URL": trimmed],
|
||||||
|
format: .xml,
|
||||||
|
options: 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private func removeTemporaryPreviewFiles() {
|
private func removeTemporaryPreviewFiles() {
|
||||||
guard fileManager.fileExists(atPath: temporaryPreviewDirectory.path) else {
|
guard fileManager.fileExists(atPath: temporaryPreviewDirectory.path) else {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import AppKit
|
import AppKit
|
||||||
|
import QuickLook
|
||||||
|
|
||||||
struct ClipboardPanelAnimationProfile {
|
struct ClipboardPanelAnimationProfile {
|
||||||
let showDuration: TimeInterval
|
let showDuration: TimeInterval
|
||||||
@@ -15,10 +16,11 @@ struct ClipboardPanelReflowPlan {
|
|||||||
enum ClipboardPanelShortcutAction: Equatable {
|
enum ClipboardPanelShortcutAction: Equatable {
|
||||||
case copy
|
case copy
|
||||||
case open
|
case open
|
||||||
|
case preview
|
||||||
case reveal
|
case reveal
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ClipboardPanelController: NSObject, NSWindowDelegate {
|
final class ClipboardPanelController: NSObject, NSWindowDelegate, QLPreviewPanelDataSource, QLPreviewPanelDelegate {
|
||||||
private enum Animation {
|
private enum Animation {
|
||||||
static let showDuration: TimeInterval = 0.16
|
static let showDuration: TimeInterval = 0.16
|
||||||
static let hideDuration: TimeInterval = 0.12
|
static let hideDuration: TimeInterval = 0.12
|
||||||
@@ -44,6 +46,7 @@ final class ClipboardPanelController: NSObject, NSWindowDelegate {
|
|||||||
private let preferredScreenProvider: () -> NSScreen?
|
private let preferredScreenProvider: () -> NSScreen?
|
||||||
private let openSettings: () -> Void
|
private let openSettings: () -> Void
|
||||||
private var isAnimating = false
|
private var isAnimating = false
|
||||||
|
private var quickLookURL: URL?
|
||||||
private var screenParametersObserver: NSObjectProtocol?
|
private var screenParametersObserver: NSObjectProtocol?
|
||||||
private static let collectionShortcuts: [UInt16: ClipboardSortMode] = [
|
private static let collectionShortcuts: [UInt16: ClipboardSortMode] = [
|
||||||
18: .mostRecent,
|
18: .mostRecent,
|
||||||
@@ -82,7 +85,8 @@ final class ClipboardPanelController: NSObject, NSWindowDelegate {
|
|||||||
panelView = ClipboardPanelView(
|
panelView = ClipboardPanelView(
|
||||||
viewModel: viewModel,
|
viewModel: viewModel,
|
||||||
onClose: { [weak self] in self?.hide() },
|
onClose: { [weak self] in self?.hide() },
|
||||||
onSettings: { [weak self] in self?.openSettings() }
|
onSettings: { [weak self] in self?.openSettings() },
|
||||||
|
onPreview: { [weak self] in self?.previewSelected() }
|
||||||
)
|
)
|
||||||
|
|
||||||
let contentSize = NSSize(width: 1200, height: 420)
|
let contentSize = NSSize(width: 1200, height: 420)
|
||||||
@@ -333,6 +337,9 @@ final class ClipboardPanelController: NSObject, NSWindowDelegate {
|
|||||||
case 53:
|
case 53:
|
||||||
self.hide()
|
self.hide()
|
||||||
return nil
|
return nil
|
||||||
|
case 49:
|
||||||
|
self.previewSelected()
|
||||||
|
return nil
|
||||||
case 36:
|
case 36:
|
||||||
self.viewModel.pasteSelected()
|
self.viewModel.pasteSelected()
|
||||||
return nil
|
return nil
|
||||||
@@ -360,11 +367,26 @@ final class ClipboardPanelController: NSObject, NSWindowDelegate {
|
|||||||
viewModel.copySelected()
|
viewModel.copySelected()
|
||||||
case .open:
|
case .open:
|
||||||
viewModel.openSelected()
|
viewModel.openSelected()
|
||||||
|
case .preview:
|
||||||
|
previewSelected()
|
||||||
case .reveal:
|
case .reveal:
|
||||||
viewModel.revealSelected()
|
viewModel.revealSelected()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func previewSelected() {
|
||||||
|
guard let url = viewModel.previewURLForSelected() else { return }
|
||||||
|
quickLookURL = url
|
||||||
|
guard let previewPanel = QLPreviewPanel.shared() else {
|
||||||
|
NSWorkspace.shared.open(url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
previewPanel.dataSource = self
|
||||||
|
previewPanel.delegate = self
|
||||||
|
previewPanel.currentPreviewItemIndex = 0
|
||||||
|
previewPanel.makeKeyAndOrderFront(nil)
|
||||||
|
}
|
||||||
|
|
||||||
private func shouldHandlePanelKeyEvent(_ event: NSEvent) -> Bool {
|
private func shouldHandlePanelKeyEvent(_ event: NSEvent) -> Bool {
|
||||||
shouldHandlePanelKeyEvent(event, allowSearchFieldEditing: false)
|
shouldHandlePanelKeyEvent(event, allowSearchFieldEditing: false)
|
||||||
}
|
}
|
||||||
@@ -399,6 +421,8 @@ final class ClipboardPanelController: NSObject, NSWindowDelegate {
|
|||||||
return .copy
|
return .copy
|
||||||
case 31:
|
case 31:
|
||||||
return .open
|
return .open
|
||||||
|
case 16:
|
||||||
|
return .preview
|
||||||
case 15:
|
case 15:
|
||||||
return .reveal
|
return .reveal
|
||||||
default:
|
default:
|
||||||
@@ -406,6 +430,14 @@ final class ClipboardPanelController: NSObject, NSWindowDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int {
|
||||||
|
quickLookURL == nil ? 0 : 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem! {
|
||||||
|
quickLookURL as NSURL?
|
||||||
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
var debugPanelFrame: NSRect {
|
var debugPanelFrame: NSRect {
|
||||||
panel.frame
|
panel.frame
|
||||||
|
|||||||
@@ -168,6 +168,15 @@ final class ClipboardPanelViewModel {
|
|||||||
return pasteService.pasteboardWriters(for: visibleItems[index])
|
return pasteService.pasteboardWriters(for: visibleItems[index])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func previewURLForSelected() -> URL? {
|
||||||
|
guard let item = selectedItem else { return nil }
|
||||||
|
return previewURL(for: item)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal func previewURL(for item: ClipboardItem) -> URL? {
|
||||||
|
cacheService.temporaryPreviewURL(for: item)
|
||||||
|
}
|
||||||
|
|
||||||
func openSelected() {
|
func openSelected() {
|
||||||
guard let item = selectedItem else { return }
|
guard let item = selectedItem else { return }
|
||||||
switch item.kind {
|
switch item.kind {
|
||||||
|
|||||||
Reference in New Issue
Block a user