211 lines
6.9 KiB
Swift
211 lines
6.9 KiB
Swift
//
|
|
// ViewController.swift
|
|
// MastoSearch
|
|
//
|
|
// Created by Shadowfacts on 12/14/21.
|
|
//
|
|
|
|
import Cocoa
|
|
import Combine
|
|
import HTMLStreamer
|
|
import MastoSearchCore
|
|
|
|
class ViewController: NSViewController {
|
|
|
|
private static let formatter: DateFormatter = {
|
|
let f = DateFormatter()
|
|
f.locale = .current
|
|
f.setLocalizedDateFormatFromTemplate("yyyy-MM-dd hh:mm a")
|
|
return f
|
|
}()
|
|
private static let searchThread = DispatchQueue(label: "Search", qos: .userInitiated)
|
|
|
|
private static let htmlConverter = TextConverter(configuration: .init(insertNewlines: false))
|
|
|
|
@IBOutlet weak var table: NSTableView!
|
|
@IBOutlet weak var progressIndicator: NSProgressIndicator!
|
|
|
|
private var dataSource: DataSource!
|
|
private var allStatusesSnapshot: NSDiffableDataSourceSnapshot<Section, Item>?
|
|
|
|
private var cancellables = Set<AnyCancellable>()
|
|
|
|
private var query: String?
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
dataSource = DataSource(owner: self) { tableView, tableColumn, row, item in
|
|
let cell = tableView.makeView(withIdentifier: tableColumn.identifier, owner: self) as! NSTableCellView
|
|
|
|
switch cell.identifier! {
|
|
case .date:
|
|
cell.textField!.font = .monospacedDigitSystemFont(ofSize: 13, weight: .regular)
|
|
cell.textField!.stringValue = ViewController.formatter.string(from: item.status.published)
|
|
|
|
case .contentWarning:
|
|
cell.textField!.stringValue = item.status.summary ?? ""
|
|
|
|
case .content:
|
|
cell.textField!.stringValue = Self.htmlConverter.convert(html: item.status.content)
|
|
|
|
default:
|
|
fatalError()
|
|
}
|
|
|
|
return cell
|
|
}
|
|
|
|
table.sortDescriptors = [NSSortDescriptor(key: "published", ascending: false)]
|
|
|
|
let menu = NSMenu()
|
|
menu.addItem(withTitle: "Open in Browser", action: #selector(openURL(_:)), keyEquivalent: "")
|
|
menu.addItem(withTitle: "Copy URL", action: #selector(copyURL(_:)), keyEquivalent: "c")
|
|
table.menu = menu
|
|
|
|
DatabaseController.shared.onInitialize
|
|
.sink { [unowned self] in self.loadAll() }
|
|
.store(in: &cancellables)
|
|
|
|
SyncController.shared.onSync
|
|
.sink { [unowned self] in
|
|
self.allStatusesSnapshot = nil
|
|
self.loadAll()
|
|
}
|
|
.store(in: &cancellables)
|
|
}
|
|
|
|
override func viewWillAppear() {
|
|
super.viewWillAppear()
|
|
|
|
progressIndicator.startAnimation(nil)
|
|
}
|
|
|
|
private func loadStatuses(_ statuses: StatusSequence) -> NSDiffableDataSourceSnapshot<Section, Item> {
|
|
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
|
snapshot.appendSections([.statuses])
|
|
snapshot.appendItems(statuses.map { Item(status: $0) }, toSection: .statuses)
|
|
DispatchQueue.main.async {
|
|
self.dataSource.apply(snapshot, animatingDifferences: false) {
|
|
self.progressIndicator.stopAnimation(nil)
|
|
}
|
|
}
|
|
return snapshot
|
|
}
|
|
|
|
private func loadAll() {
|
|
self.query = nil
|
|
|
|
if let snapshot = allStatusesSnapshot {
|
|
dispatchPrecondition(condition: .onQueue(.main))
|
|
self.dataSource.apply(snapshot, animatingDifferences: false)
|
|
} else {
|
|
let sortDescriptor = table.sortDescriptors.first
|
|
ViewController.searchThread.async {
|
|
DatabaseController.shared.getStatuses(sortDescriptor: sortDescriptor) { statuses in
|
|
let snapshot = self.loadStatuses(statuses)
|
|
self.allStatusesSnapshot = snapshot
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func search(_ query: String) {
|
|
let query = query.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
self.query = query
|
|
guard !query.isEmpty else {
|
|
loadAll()
|
|
return
|
|
}
|
|
|
|
let sortDescriptor = table.sortDescriptors.first
|
|
ViewController.searchThread.async {
|
|
DatabaseController.shared.getStatuses(query: query, sortDescriptor: sortDescriptor) { statuses in
|
|
_ = self.loadStatuses(statuses)
|
|
}
|
|
}
|
|
}
|
|
|
|
func sortDescriptorsChanged() {
|
|
guard DatabaseController.shared.isInitialized else {
|
|
return
|
|
}
|
|
allStatusesSnapshot = nil
|
|
self.dataSource.apply(NSDiffableDataSourceSnapshot(), animatingDifferences: false)
|
|
if let query = query {
|
|
search(query)
|
|
} else {
|
|
loadAll()
|
|
}
|
|
}
|
|
|
|
@objc func openURL(_ sender: Any) {
|
|
guard table.clickedRow != -1,
|
|
let item = dataSource.itemIdentifier(forRow: table.clickedRow) else {
|
|
return
|
|
}
|
|
NSWorkspace.shared.open(URL(string: item.status.url)!)
|
|
}
|
|
|
|
@objc func copyURL(_ sender: Any) {
|
|
guard table.clickedRow != -1,
|
|
let item = dataSource.itemIdentifier(forRow: table.clickedRow) else {
|
|
return
|
|
}
|
|
NSPasteboard.general.clearContents()
|
|
NSPasteboard.general.setString(item.status.url, forType: .string)
|
|
}
|
|
|
|
}
|
|
|
|
extension ViewController {
|
|
enum Section {
|
|
case statuses
|
|
}
|
|
struct Item: Hashable {
|
|
let status: Status
|
|
|
|
static func ==(lhs: Item, rhs: Item) -> Bool {
|
|
return lhs.status.url == rhs.status.url
|
|
}
|
|
|
|
func hash(into hasher: inout Hasher) {
|
|
hasher.combine(status.url)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension ViewController {
|
|
class DataSource: NSTableViewDiffableDataSource<Section, Item> {
|
|
unowned var owner: ViewController
|
|
|
|
init(owner: ViewController, cellProvider: @escaping NSTableViewDiffableDataSource<ViewController.Section, ViewController.Item>.CellProvider) {
|
|
self.owner = owner
|
|
super.init(tableView: owner.table, cellProvider: cellProvider)
|
|
}
|
|
|
|
// without @objc the table view doesn't detect it, even though it overrides an NSTableViewDataSource method and so should be exposed automatically
|
|
@objc func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldSortDescriptors: [NSSortDescriptor]) {
|
|
owner.sortDescriptorsChanged()
|
|
}
|
|
}
|
|
}
|
|
|
|
extension ViewController: NSMenuItemValidation {
|
|
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
|
|
if menuItem.action == #selector(copyURL) || menuItem.action == #selector(openURL) {
|
|
return table.clickedRow != -1
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension NSUserInterfaceItemIdentifier {
|
|
|
|
static let date = NSUserInterfaceItemIdentifier("date")
|
|
static let contentWarning = NSUserInterfaceItemIdentifier("contentWarning")
|
|
static let content = NSUserInterfaceItemIdentifier("content")
|
|
|
|
}
|