Skip to content

Commit

Permalink
create algebras for pasteboard and notification center
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelangel-dev committed Oct 5, 2020
1 parent 59a0c93 commit a6a6d5c
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 116 deletions.
33 changes: 32 additions & 1 deletion nef-plugin.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
8B497A7D239A91EF00CEC130 /* nef in Frameworks */ = {isa = PBXBuildFile; productRef = 8B497A7C239A91EF00CEC130 /* nef */; settings = {ATTRIBUTES = (Required, ); }; };
8B4D1E1522D7A37E004432CF /* PickerOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B4D1E1422D7A37E004432CF /* PickerOptionView.swift */; };
8B4D1E1722D88A5E004432CF /* OptionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B4D1E1622D88A5E004432CF /* OptionItem.swift */; };
8B7583AC247278DA00C98A08 /* AppDelegate+Clipboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B7583AB247278DA00C98A08 /* AppDelegate+Clipboard.swift */; };
8B53BB952507D8B400C2B874 /* Pasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B53BB942507D8B400C2B874 /* Pasteboard.swift */; };
8B53BB972507D8C000C2B874 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B53BB962507D8C000C2B874 /* Notifications.swift */; };
8B53BB992507D8F100C2B874 /* MacPasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B53BB982507D8F100C2B874 /* MacPasteboard.swift */; };
8B53BB9B2507D93300C2B874 /* MacNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B53BB9A2507D93300C2B874 /* MacNotifications.swift */; };
8B75AF99238061FF00388B1C /* NSView+Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75AF98238061FF00388B1C /* NSView+Layout.swift */; };
8B78F13F24EE7A3900869EF7 /* Browser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B3B21A922F8329900E92BC9 /* Browser.swift */; };
8B78F14024EE7AC600869EF7 /* libSourceEditorUtils.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B78F13624EE7A1900869EF7 /* libSourceEditorUtils.a */; };
Expand Down Expand Up @@ -132,6 +135,10 @@
8B41588722D8E43A00588921 /* CarbonStyle+Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CarbonStyle+Color.swift"; sourceTree = "<group>"; };
8B4D1E1422D7A37E004432CF /* PickerOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerOptionView.swift; sourceTree = "<group>"; };
8B4D1E1622D88A5E004432CF /* OptionItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionItem.swift; sourceTree = "<group>"; };
8B53BB942507D8B400C2B874 /* Pasteboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pasteboard.swift; sourceTree = "<group>"; };
8B53BB962507D8C000C2B874 /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
8B53BB982507D8F100C2B874 /* MacPasteboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacPasteboard.swift; sourceTree = "<group>"; };
8B53BB9A2507D93300C2B874 /* MacNotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacNotifications.swift; sourceTree = "<group>"; };
8B75AF98238061FF00388B1C /* NSView+Layout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSView+Layout.swift"; sourceTree = "<group>"; };
8B78F13624EE7A1900869EF7 /* libSourceEditorUtils.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSourceEditorUtils.a; sourceTree = BUILT_PRODUCTS_DIR; };
8B78F33324EE801A00869EF7 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -312,6 +319,24 @@
name = Frameworks;
sourceTree = "<group>";
};
8B53BB922507D89A00C2B874 /* Algebras */ = {
isa = PBXGroup;
children = (
8B53BB942507D8B400C2B874 /* Pasteboard.swift */,
8B53BB962507D8C000C2B874 /* Notifications.swift */,
);
path = Algebras;
sourceTree = "<group>";
};
8B53BB932507D8A500C2B874 /* Instances */ = {
isa = PBXGroup;
children = (
8B53BB982507D8F100C2B874 /* MacPasteboard.swift */,
8B53BB9A2507D93300C2B874 /* MacNotifications.swift */,
);
path = Instances;
sourceTree = "<group>";
};
8B78F13724EE7A1900869EF7 /* nef-utils */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -398,6 +423,8 @@
8BBB49AA2523469E005FD43B /* Command.swift */,
8BB2893622CB6EFD006E4CCB /* Main.storyboard */,
8B78F34924EEA7E800869EF7 /* App */,
8B53BB922507D89A00C2B874 /* Algebras */,
8B53BB932507D8A500C2B874 /* Instances */,
8B78F34024EEA0FE00869EF7 /* Controllers */,
8B78F33724EE943C00869EF7 /* Models */,
8BB2896422CB6FF0006E4CCB /* Support Files */,
Expand Down Expand Up @@ -690,6 +717,7 @@
buildActionMask = 2147483647;
files = (
8B2CE74622D89C8900571F24 /* CheckOptionView.swift in Sources */,
8B53BB972507D8C000C2B874 /* Notifications.swift in Sources */,
8B91B1932360A3690041F79B /* DispatchGroup.swift in Sources */,
8B7E0622235493F7003ED8E2 /* NSWindow+Extension.swift in Sources */,
8B78F33B24EE948F00869EF7 /* NefController.swift in Sources */,
Expand All @@ -699,6 +727,7 @@
AF73C327248C1A4200168A64 /* URL+Extension.swift in Sources */,
8B3B21AC22F8644000E92BC9 /* FixedToggle.swift in Sources */,
8B3B21A722F82AAE00E92BC9 /* AboutView.swift in Sources */,
8B53BB952507D8B400C2B874 /* Pasteboard.swift in Sources */,
8B7F5C85239807EF0040575D /* ProgressView.swift in Sources */,
8B230C83241C1DD500C31191 /* Data+IO.swift in Sources */,
8B7F5C832397DC820040575D /* PlaygroundBookProgressReport.swift in Sources */,
Expand All @@ -707,6 +736,7 @@
8B78F34624EEA25100869EF7 /* ImageNotificationController.swift in Sources */,
8BF68FB122DE0ED4009E57A9 /* PreferencesModel.swift in Sources */,
8BF68FA322DDE268009E57A9 /* ActionViewModel.swift in Sources */,
8B53BB992507D8F100C2B874 /* MacPasteboard.swift in Sources */,
8B2CE74822D8A35400571F24 /* ColorOptionView.swift in Sources */,
8BF68FA122DDD8BD009E57A9 /* Assembler.swift in Sources */,
8BBB49BA25234946005FD43B /* Command.swift in Sources */,
Expand All @@ -718,6 +748,7 @@
8BF68FB522DE2403009E57A9 /* CarbonStyle+Model.swift in Sources */,
8B78F33F24EE999900869EF7 /* Result+terminate.swift in Sources */,
8BAAB73D22D4DB2A00FD3636 /* Date+Extension.swift in Sources */,
8B53BB9B2507D93300C2B874 /* MacNotifications.swift in Sources */,
8B78F34424EEA24500869EF7 /* MarkdownPageController.swift in Sources */,
8B78F33D24EE950300869EF7 /* i18n.swift in Sources */,
8BEAFB12235C96F700118F31 /* Bookmark.swift in Sources */,
Expand Down
23 changes: 23 additions & 0 deletions nef/Algebras/Notifications.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright © 2020 The nef Authors.

import Foundation
import AppKit
import BowEffects

protocol Notifications {
func removeAllDelivered<D, E: Swift.Error>() -> EnvIO<D, E, Void>
func show<D, E: Swift.Error>(title: String, body: String, options: NotificationOptions) -> EnvIO<D, E, Void>
}


struct NotificationOptions {
let imageData: Data?
let actions: [NefNotification.Action]
let identifier: String

init(imageData: Data? = nil, actions: [NefNotification.Action] = [], identifier: String = UUID().uuidString) {
self.imageData = imageData
self.actions = actions
self.identifier = identifier
}
}
13 changes: 13 additions & 0 deletions nef/Algebras/Pasteboard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright © 2020 The nef Authors.

import Foundation
import AppKit
import BowEffects

enum PasteboardError: Swift.Error {
case writeToClipboard
}

protocol Pasteboard {
func write<D>(_ image: NSImage) -> EnvIO<D, PasteboardError, Void>
}
146 changes: 35 additions & 111 deletions nef/Controllers/ImageNotificationController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,78 +43,30 @@ import BowEffects

extension ImageNotificationController {

func removeOldNotifications() -> EnvIO<Clipboard.Config, Clipboard.Error, Void> {
EnvIO.invoke { config in
config.notificationCenter.removeAllDeliveredNotifications()
}^
}

func showNotification(title: String, body: String, imageData: Data? = nil, actions: [NefNotification.Action] = [], id: String = UUID().uuidString) -> EnvIO<Clipboard.Config, Clipboard.Error, Void> {
EnvIO.invoke { config in
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.categoryIdentifier = id

if let data = imageData {
content.userInfo = [NefNotification.UserInfoKey.imageData: data]
}

let category = UNNotificationCategory(identifier: id,
actions: actions.map(\.unNotificationAction),
intentIdentifiers: [],
hiddenPreviewsBodyPlaceholder: "",
options: .customDismissAction)

let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.5, repeats: false)
let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)

config.notificationCenter.setNotificationCategories([category])
config.notificationCenter.add(request)
}^
}

func processNotification(_ userInfo: [String: Any], action: String) -> EnvIO<NotificationConfig, NefNotification.Error, NefNotification.Response> {
guard let image = userInfo[NefNotification.UserInfoKey.imageData] as? Data else {
return EnvIO.raiseError(.noImageData)^
}

switch action {
case NefNotification.Action.saveImage.identifier:
return image
.persist(command: .exportSnippetToClipboard(selection: ""))
.mapError { _ in .persistImage }
.contramap(\.openPanel)
.map { .saveImage($0) }^
case UNNotificationDismissActionIdentifier:
return EnvIO.pure(.dismiss)^
default:
return EnvIO.raiseError(.unsupportedAction)^
}
}
}

enum Clipboard {
enum Error: Swift.Error {
case invalidData
case writeToClipboard
case carbon
}

struct Config {
let clipboard: NSPasteboard
let notificationCenter: UNUserNotificationCenter
}
// func processNotification(_ userInfo: [String: Any], action: String) -> EnvIO<NotificationConfig, NefNotification.Error, NefNotification.Response> {
// guard let image = userInfo[NefNotification.UserInfoKey.imageData] as? Data else {
// return EnvIO.raiseError(.noImageData)^
// }
//
// switch action {
// case NefNotification.Action.saveImage.identifier:
// return image
// .persist(command: .exportSnippetToClipboard(selection: ""))
// .mapError { _ in .persistImage }
// .contramap(\.openPanel)
// .map { .saveImage($0) }^
// case UNNotificationDismissActionIdentifier:
// return EnvIO.pure(.dismiss)^
// default:
// return EnvIO.raiseError(.unsupportedAction)^
// }
// }
}

// MARK: - Helpers
private extension NefNotification.Action {
var unNotificationAction: UNNotificationAction {
.init(identifier: identifier,
title: title,
options: .foreground)
}
}




/// -------------- AppDelegate+Clipboard
Expand All @@ -123,46 +75,18 @@ import AppKit
import Bow
import BowEffects

extension ImageNotificationController {
func clipboardCarbonIO(data: Data) -> EnvIO<Clipboard.Config, Clipboard.Error, NSImage> {
func makeImage(_ data: Data) -> IO<Clipboard.Error, NSImage> {
data.makeImage().mapError { _ in .invalidData }
}

let image = EnvIO<Clipboard.Config, Clipboard.Error, NSImage>.var()

return binding(
image <- makeImage(data).env(),
|<-self.writeToClipboard(image.get),
|<-self.removeOldNotifications(),
|<-self.showNotification(title: "nef",
body: "Image copied to clipboard!",
imageData: data,
actions: [.cancel, .saveImage]),
yield:image.get)^
}

func showClipboardFile(response: NefNotification.Response) -> EnvIO<NotificationConfig, NefNotification.Error, Void> {
guard case let .saveImage(url) = response else { return EnvIO.pure(())^ }

return EnvIO.invoke { config in
config.workspace.activateFileViewerSelecting([url])
}^
}
}

private extension ImageNotificationController {
func writeToClipboard(_ image: NSImage) -> EnvIO<Clipboard.Config, Clipboard.Error, Void> {
EnvIO.invoke { config in
config.clipboard.clearContents()
if !config.clipboard.writeObjects([image]) {
throw Clipboard.Error.writeToClipboard
}
}^
}
}

struct NotificationConfig {
let workspace: NSWorkspace
let openPanel: OpenPanel
}
//extension ImageNotificationController {
// func showClipboardFile(response: NefNotification.Response) -> EnvIO<NotificationConfig, NefNotification.Error, Void> {
// guard case let .saveImage(url) = response else { return EnvIO.pure(())^ }
//
// return EnvIO.invoke { config in
// config.workspace.activateFileViewerSelecting([url])
// }^
// }
//}


//struct NotificationConfig {
// let workspace: NSWorkspace
// let openPanel: OpenPanel
//}
2 changes: 1 addition & 1 deletion nef/Controllers/MarkdownPageController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class MarkdownPageController: NefController {
.writableFolder(create: true)
.use { folder in
markdownIO(folder: folder, page: page).provide(env)^
.mapError { _ in .unknown } // MOVERLO A BOW: extension ResourcePartial: MonadError {}
.mapError { _ in .unknown }
}

}^
Expand Down
4 changes: 2 additions & 2 deletions nef/Extensions/Data+IO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ extension Data {
}
}

func makeImage() -> IO<ImageError, NSImage> {
IO.invoke {
func makeImage<D>() -> EnvIO<D, ImageError, NSImage> {
EnvIO.invoke { _ in
guard let image = NSImage(data: self) else {
throw ImageError.invalidData
}
Expand Down
56 changes: 56 additions & 0 deletions nef/Instances/MacNotifications.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright © 2020 The nef Authors.

import Foundation
import AppKit
import UserNotifications

import Bow
import BowEffects

class MacNotificationController: Notifications {
let notificationCenter: UNUserNotificationCenter

init(notificationCenter: UNUserNotificationCenter) {
self.notificationCenter = notificationCenter
}

func removeAllDelivered<D, E: Swift.Error>() -> EnvIO<D, E, Void> {
EnvIO.invoke { _ in
self.notificationCenter.removeAllDeliveredNotifications()
}^
}

func show<D, E: Swift.Error>(title: String, body: String, options: NotificationOptions) -> EnvIO<D, E, Void> {
EnvIO.invoke { _ in
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.categoryIdentifier = options.identifier

if let imageData = options.imageData {
content.userInfo = [NefNotification.UserInfoKey.imageData: imageData]
}

let category = UNNotificationCategory(identifier: options.identifier,
actions: options.actions.map(\.unNotificationAction),
intentIdentifiers: [],
hiddenPreviewsBodyPlaceholder: "",
options: .customDismissAction)

let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.5, repeats: false)
let request = UNNotificationRequest(identifier: options.identifier, content: content, trigger: trigger)

self.notificationCenter.setNotificationCategories([category])
self.notificationCenter.add(request)
}^
}
}

// MARK: - Helpers
private extension NefNotification.Action {
var unNotificationAction: UNNotificationAction {
.init(identifier: identifier,
title: title,
options: .foreground)
}
}
25 changes: 25 additions & 0 deletions nef/Instances/MacPasteboard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright © 2020 The nef Authors.

import Foundation
import AppKit
import UserNotifications

import Bow
import BowEffects

class MacPasteboard: Pasteboard {
let pasteboard: NSPasteboard

init(pasteboard: NSPasteboard) {
self.pasteboard = pasteboard
}

func write<D>(_ image: NSImage) -> EnvIO<D, PasteboardError, Void> {
EnvIO.invoke { _ in
self.pasteboard.clearContents()
if !self.pasteboard.writeObjects([image]) {
throw PasteboardError.writeToClipboard
}
}^
}
}
2 changes: 1 addition & 1 deletion nef/Utils/OpenPanel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import AppKit
import BowEffects

typealias BookmarkResource = Resource<IOPartial<OpenPanelError>, URL>
typealias BookmarkResource = IOResource<OpenPanelError, URL>

enum OpenPanelError: Error {
case denied
Expand Down

0 comments on commit a6a6d5c

Please sign in to comment.