2024-02-28 17:37:01 +03:00
|
|
|
//
|
|
|
|
// ShareOptionsView.swift
|
|
|
|
// FileProviderUIExt
|
|
|
|
//
|
|
|
|
// Created by Claudio Cambra on 28/2/24.
|
|
|
|
//
|
|
|
|
|
|
|
|
import AppKit
|
2024-03-04 16:02:35 +03:00
|
|
|
import Combine
|
2024-03-04 14:16:57 +03:00
|
|
|
import NextcloudKit
|
2024-03-06 15:09:54 +03:00
|
|
|
import OSLog
|
2024-04-06 17:14:52 +03:00
|
|
|
import SuggestionsTextFieldKit
|
2024-02-28 17:37:01 +03:00
|
|
|
|
|
|
|
class ShareOptionsView: NSView {
|
2024-03-06 18:44:34 +03:00
|
|
|
@IBOutlet private weak var optionsTitleTextField: NSTextField!
|
2024-03-06 20:10:43 +03:00
|
|
|
@IBOutlet private weak var shareRecipientTextField: NSTextField! // Hide if public link share
|
2024-03-04 14:16:57 +03:00
|
|
|
@IBOutlet private weak var labelTextField: NSTextField!
|
|
|
|
@IBOutlet private weak var uploadEditPermissionCheckbox: NSButton!
|
|
|
|
@IBOutlet private weak var hideDownloadCheckbox: NSButton!
|
|
|
|
@IBOutlet private weak var passwordProtectCheckbox: NSButton!
|
|
|
|
@IBOutlet private weak var passwordSecureField: NSSecureTextField!
|
|
|
|
@IBOutlet private weak var expirationDateCheckbox: NSButton!
|
|
|
|
@IBOutlet private weak var expirationDatePicker: NSDatePicker!
|
|
|
|
@IBOutlet private weak var noteForRecipientCheckbox: NSButton!
|
|
|
|
@IBOutlet private weak var noteTextField: NSTextField!
|
|
|
|
@IBOutlet private weak var saveButton: NSButton!
|
|
|
|
@IBOutlet private weak var deleteButton: NSButton!
|
2024-03-06 15:09:16 +03:00
|
|
|
@IBOutlet private weak var shareTypePicker: NSPopUpButton!
|
|
|
|
@IBOutlet private weak var publicLinkShareMenuItem: NSMenuItem!
|
|
|
|
@IBOutlet private weak var userShareMenuItem: NSMenuItem!
|
|
|
|
@IBOutlet private weak var groupShareMenuItem: NSMenuItem!
|
|
|
|
@IBOutlet private weak var emailShareMenuItem: NSMenuItem!
|
|
|
|
@IBOutlet private weak var federatedCloudShareMenuItem: NSMenuItem!
|
|
|
|
@IBOutlet private weak var circleShare: NSMenuItem!
|
|
|
|
@IBOutlet private weak var talkConversationShare: NSMenuItem!
|
2024-03-04 14:16:57 +03:00
|
|
|
|
2024-04-03 15:46:01 +03:00
|
|
|
var kit: NextcloudKit? {
|
|
|
|
didSet {
|
|
|
|
Logger.shareOptionsView.info("Setting up the kit.")
|
|
|
|
guard let kit = kit else {
|
|
|
|
Logger.shareOptionsView.error("Could not configure suggestions data source.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-04-06 17:11:29 +03:00
|
|
|
suggestionsTextFieldDelegate.suggestionsDataSource = ShareeSuggestionsDataSource(
|
|
|
|
kit: kit
|
|
|
|
)
|
|
|
|
suggestionsTextFieldDelegate.confirmationHandler = { suggestion in
|
|
|
|
guard let sharee = suggestion?.data as? NKSharee else { return }
|
|
|
|
self.shareRecipientTextField.stringValue = sharee.shareWith
|
|
|
|
Logger.shareOptionsView.debug("Chose sharee \(sharee.shareWith, privacy: .public)")
|
|
|
|
}
|
|
|
|
suggestionsTextFieldDelegate.targetTextField = shareRecipientTextField
|
2024-04-03 15:46:01 +03:00
|
|
|
}
|
|
|
|
}
|
2024-03-04 16:51:37 +03:00
|
|
|
var dataSource: ShareTableViewDataSource?
|
2024-03-04 15:42:52 +03:00
|
|
|
var controller: ShareController? {
|
2024-03-04 14:16:57 +03:00
|
|
|
didSet {
|
2024-03-06 15:08:06 +03:00
|
|
|
guard controller != nil else { return }
|
2024-03-06 18:44:34 +03:00
|
|
|
optionsTitleTextField.stringValue = "Share options"
|
2024-03-06 19:39:56 +03:00
|
|
|
deleteButton.title = "Delete"
|
|
|
|
deleteButton.image = NSImage(
|
|
|
|
systemSymbolName: "trash", accessibilityDescription: "Delete trash icon"
|
|
|
|
)
|
|
|
|
deleteButton.bezelColor = NSColor.systemRed
|
2024-03-04 16:02:35 +03:00
|
|
|
cancellable?.cancel()
|
2024-03-05 13:16:17 +03:00
|
|
|
createMode = false
|
2024-03-04 15:42:52 +03:00
|
|
|
update()
|
2024-03-04 16:02:35 +03:00
|
|
|
cancellable = controller.publisher.sink { _ in self.update() }
|
2024-03-04 15:42:52 +03:00
|
|
|
}
|
|
|
|
}
|
2024-03-06 15:08:06 +03:00
|
|
|
var createMode = false {
|
|
|
|
didSet {
|
2024-04-17 20:58:38 +03:00
|
|
|
Logger.shareOptionsView.info("Create mode set: \(self.createMode, privacy: .public)")
|
2024-03-06 15:08:06 +03:00
|
|
|
shareTypePicker.isHidden = !createMode
|
2024-03-06 20:11:42 +03:00
|
|
|
shareRecipientTextField.isHidden = !createMode
|
2024-03-06 15:08:06 +03:00
|
|
|
labelTextField.isHidden = createMode // Cannot set label on create API call
|
|
|
|
guard createMode else { return }
|
2024-03-06 18:44:34 +03:00
|
|
|
optionsTitleTextField.stringValue = "Create new share"
|
2024-03-06 19:39:56 +03:00
|
|
|
deleteButton.title = "Cancel"
|
|
|
|
deleteButton.image = NSImage(
|
|
|
|
systemSymbolName: "xmark.bin", accessibilityDescription: "Cancel create icon"
|
|
|
|
)
|
|
|
|
deleteButton.bezelColor = NSColor.controlColor
|
2024-03-06 15:08:06 +03:00
|
|
|
cancellable?.cancel()
|
|
|
|
cancellable = nil
|
|
|
|
controller = nil
|
|
|
|
reset()
|
2024-03-18 14:16:29 +03:00
|
|
|
setupCreateForm()
|
2024-03-06 15:08:06 +03:00
|
|
|
}
|
|
|
|
}
|
2024-03-04 16:02:35 +03:00
|
|
|
private var cancellable: AnyCancellable?
|
2024-04-03 15:46:01 +03:00
|
|
|
private var suggestionsWindowController = SuggestionsWindowController()
|
|
|
|
private var suggestionsTextFieldDelegate = SuggestionsTextFieldDelegate()
|
2024-03-04 15:42:52 +03:00
|
|
|
|
|
|
|
private func update() {
|
|
|
|
guard let share = controller?.share else {
|
|
|
|
reset()
|
2024-03-04 16:35:40 +03:00
|
|
|
setAllFields(enabled: false)
|
2024-03-04 15:42:52 +03:00
|
|
|
saveButton.isEnabled = false
|
|
|
|
deleteButton.isEnabled = false
|
|
|
|
return
|
2024-03-04 14:16:57 +03:00
|
|
|
}
|
2024-03-04 16:35:40 +03:00
|
|
|
|
2024-03-04 15:42:52 +03:00
|
|
|
deleteButton.isEnabled = share.canDelete
|
2024-03-04 16:35:40 +03:00
|
|
|
saveButton.isEnabled = share.canEdit
|
|
|
|
|
2024-04-23 18:48:53 +03:00
|
|
|
setAllFields(enabled: share.canEdit)
|
|
|
|
reset()
|
|
|
|
|
|
|
|
shareRecipientTextField.stringValue = share.shareWithDisplayname
|
|
|
|
labelTextField.stringValue = share.label
|
|
|
|
uploadEditPermissionCheckbox.state = share.shareesCanEdit ? .on : .off
|
|
|
|
hideDownloadCheckbox.state = share.hideDownload ? .on : .off
|
|
|
|
passwordProtectCheckbox.state = share.password.isEmpty ? .off : .on
|
|
|
|
passwordSecureField.isHidden = passwordProtectCheckbox.state == .off
|
|
|
|
passwordSecureField.stringValue = share.password
|
|
|
|
expirationDateCheckbox.state = share.expirationDate == nil ? .off : .on
|
|
|
|
expirationDatePicker.isHidden = expirationDateCheckbox.state == .off
|
|
|
|
expirationDatePicker.dateValue = share.expirationDate as? Date ?? Date()
|
|
|
|
noteForRecipientCheckbox.state = share.note.isEmpty ? .off : .on
|
|
|
|
noteTextField.isHidden = noteForRecipientCheckbox.state == .off
|
|
|
|
noteForRecipientCheckbox.stringValue = share.note
|
2024-03-04 15:42:52 +03:00
|
|
|
}
|
|
|
|
|
2024-03-18 14:15:10 +03:00
|
|
|
private func reset() {
|
2024-03-06 20:20:58 +03:00
|
|
|
shareRecipientTextField.stringValue = ""
|
2024-03-04 15:42:52 +03:00
|
|
|
labelTextField.stringValue = ""
|
|
|
|
uploadEditPermissionCheckbox.state = .off
|
|
|
|
hideDownloadCheckbox.state = .off
|
|
|
|
passwordProtectCheckbox.state = .off
|
|
|
|
passwordSecureField.isHidden = true
|
2024-03-06 20:20:58 +03:00
|
|
|
passwordSecureField.stringValue = ""
|
2024-03-04 15:42:52 +03:00
|
|
|
expirationDateCheckbox.state = .off
|
|
|
|
expirationDatePicker.isHidden = true
|
2024-03-06 20:20:58 +03:00
|
|
|
expirationDatePicker.dateValue = NSDate.now
|
2024-03-18 14:15:10 +03:00
|
|
|
expirationDatePicker.minDate = NSDate.now
|
|
|
|
expirationDatePicker.maxDate = nil
|
2024-03-04 15:42:52 +03:00
|
|
|
noteForRecipientCheckbox.state = .off
|
|
|
|
noteTextField.isHidden = true
|
2024-03-06 20:20:58 +03:00
|
|
|
noteTextField.stringValue = ""
|
2024-03-04 14:16:57 +03:00
|
|
|
}
|
2024-03-04 16:35:25 +03:00
|
|
|
|
2024-03-18 14:16:29 +03:00
|
|
|
private func setupCreateForm() {
|
|
|
|
guard createMode else { return }
|
|
|
|
|
|
|
|
setAllFields(enabled: true)
|
|
|
|
|
|
|
|
let type = pickedShareType()
|
2024-03-18 15:14:22 +03:00
|
|
|
shareRecipientTextField.isHidden = type == .publicLink
|
2024-03-18 14:16:29 +03:00
|
|
|
|
2024-03-19 21:26:35 +03:00
|
|
|
if let caps = dataSource?.capabilities?.filesSharing {
|
2024-03-18 14:16:29 +03:00
|
|
|
uploadEditPermissionCheckbox.state =
|
|
|
|
caps.defaultPermissions & NKShare.PermissionValues.updateShare.rawValue != 0
|
|
|
|
? .on : .off
|
|
|
|
|
|
|
|
switch type {
|
|
|
|
case .publicLink:
|
|
|
|
passwordProtectCheckbox.isHidden = false
|
2024-03-19 21:26:35 +03:00
|
|
|
passwordProtectCheckbox.state = caps.publicLink?.passwordEnforced == true ? .on : .off
|
|
|
|
passwordProtectCheckbox.isEnabled = caps.publicLink?.passwordEnforced == false
|
|
|
|
expirationDateCheckbox.state = caps.publicLink?.expireDateEnforced == true ? .on : .off
|
|
|
|
expirationDateCheckbox.isEnabled = caps.publicLink?.expireDateEnforced == false
|
2024-03-18 14:16:29 +03:00
|
|
|
expirationDatePicker.dateValue = Date(
|
|
|
|
timeIntervalSinceNow:
|
2024-03-19 21:26:35 +03:00
|
|
|
TimeInterval((caps.publicLink?.expireDateDays ?? 1) * 24 * 60 * 60)
|
2024-03-18 14:16:29 +03:00
|
|
|
)
|
2024-03-19 21:26:35 +03:00
|
|
|
if caps.publicLink?.expireDateEnforced == true {
|
2024-03-18 14:16:29 +03:00
|
|
|
expirationDatePicker.maxDate = expirationDatePicker.dateValue
|
|
|
|
}
|
|
|
|
case .email:
|
2024-03-19 21:26:35 +03:00
|
|
|
passwordProtectCheckbox.isHidden = caps.email?.passwordEnabled == false
|
|
|
|
passwordProtectCheckbox.state = caps.email?.passwordEnforced == true ? .on : .off
|
2024-03-18 14:16:29 +03:00
|
|
|
default:
|
2024-03-18 15:14:22 +03:00
|
|
|
passwordProtectCheckbox.isHidden = true
|
|
|
|
passwordProtectCheckbox.state = .off
|
2024-03-18 14:16:29 +03:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
passwordSecureField.isHidden = passwordProtectCheckbox.state == .off
|
|
|
|
expirationDatePicker.isHidden = expirationDateCheckbox.state == .off
|
|
|
|
}
|
|
|
|
|
2024-04-03 15:46:01 +03:00
|
|
|
private func setAllFields(enabled: Bool) {
|
2024-03-06 20:11:42 +03:00
|
|
|
shareTypePicker.isEnabled = enabled
|
|
|
|
shareRecipientTextField.isEnabled = enabled
|
2024-03-04 16:35:25 +03:00
|
|
|
labelTextField.isEnabled = enabled
|
|
|
|
uploadEditPermissionCheckbox.isEnabled = enabled
|
|
|
|
hideDownloadCheckbox.isEnabled = enabled
|
|
|
|
passwordProtectCheckbox.isEnabled = enabled
|
|
|
|
passwordSecureField.isEnabled = enabled
|
|
|
|
expirationDateCheckbox.isEnabled = enabled
|
|
|
|
expirationDatePicker.isEnabled = enabled
|
|
|
|
noteForRecipientCheckbox.isEnabled = enabled
|
|
|
|
noteTextField.isEnabled = enabled
|
|
|
|
saveButton.isEnabled = enabled
|
|
|
|
deleteButton.isEnabled = enabled
|
|
|
|
}
|
2024-03-04 16:40:01 +03:00
|
|
|
|
2024-03-18 14:16:29 +03:00
|
|
|
private func pickedShareType() -> NKShare.ShareType {
|
2024-03-18 13:52:39 +03:00
|
|
|
let selectedShareTypeItem = shareTypePicker.selectedItem
|
|
|
|
var selectedShareType = NKShare.ShareType.publicLink
|
|
|
|
if selectedShareTypeItem == publicLinkShareMenuItem {
|
|
|
|
selectedShareType = .publicLink
|
|
|
|
} else if selectedShareTypeItem == userShareMenuItem {
|
|
|
|
selectedShareType = .user
|
|
|
|
} else if selectedShareTypeItem == groupShareMenuItem {
|
|
|
|
selectedShareType = .group
|
|
|
|
} else if selectedShareTypeItem == emailShareMenuItem {
|
|
|
|
selectedShareType = .email
|
|
|
|
} else if selectedShareTypeItem == federatedCloudShareMenuItem {
|
|
|
|
selectedShareType = .federatedCloud
|
|
|
|
} else if selectedShareTypeItem == circleShare {
|
|
|
|
selectedShareType = .circle
|
|
|
|
} else if selectedShareTypeItem == talkConversationShare {
|
|
|
|
selectedShareType = .talkConversation
|
|
|
|
}
|
|
|
|
return selectedShareType
|
|
|
|
}
|
|
|
|
|
2024-03-18 14:17:13 +03:00
|
|
|
@IBAction func shareTypePickerAction(_ sender: Any) {
|
|
|
|
if createMode {
|
|
|
|
setupCreateForm()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-06 20:30:57 +03:00
|
|
|
@IBAction func passwordCheckboxAction(_ sender: Any) {
|
|
|
|
passwordSecureField.isHidden = passwordProtectCheckbox.state == .off
|
|
|
|
}
|
|
|
|
|
2024-03-06 21:00:41 +03:00
|
|
|
@IBAction func expirationDateCheckboxAction(_ sender: Any) {
|
|
|
|
expirationDatePicker.isHidden = expirationDateCheckbox.state == .off
|
|
|
|
}
|
|
|
|
|
2024-03-12 09:27:35 +03:00
|
|
|
@IBAction func noteForRecipientCheckboxAction(_ sender: Any) {
|
|
|
|
noteTextField.isHidden = noteForRecipientCheckbox.state == .off
|
|
|
|
}
|
|
|
|
|
2024-03-04 16:40:01 +03:00
|
|
|
@IBAction func save(_ sender: Any) {
|
|
|
|
Task { @MainActor in
|
|
|
|
let password = passwordProtectCheckbox.state == .on
|
|
|
|
? passwordSecureField.stringValue
|
|
|
|
: ""
|
|
|
|
let expireDate = expirationDateCheckbox.state == .on
|
|
|
|
? NKShare.formattedDateString(date: expirationDatePicker.dateValue)
|
|
|
|
: ""
|
|
|
|
let note = noteForRecipientCheckbox.state == .on
|
|
|
|
? noteTextField.stringValue
|
|
|
|
: ""
|
|
|
|
let label = labelTextField.stringValue
|
|
|
|
let hideDownload = hideDownloadCheckbox.state == .on
|
2024-03-05 12:15:57 +03:00
|
|
|
let uploadAndEdit = uploadEditPermissionCheckbox.state == .on
|
2024-03-06 15:09:54 +03:00
|
|
|
|
|
|
|
guard !createMode else {
|
|
|
|
Logger.shareOptionsView.info("Creating new share!")
|
|
|
|
|
|
|
|
guard let dataSource = dataSource,
|
2024-04-03 15:46:01 +03:00
|
|
|
let kit = kit,
|
2024-03-06 15:09:54 +03:00
|
|
|
let itemServerRelativePath = dataSource.itemServerRelativePath
|
|
|
|
else {
|
|
|
|
Logger.shareOptionsView.error("Cannot create new share due to missing data.")
|
2024-04-17 20:58:38 +03:00
|
|
|
Logger.shareOptionsView.error("dataSource: \(self.dataSource, privacy: .public)")
|
|
|
|
Logger.shareOptionsView.error("kit: \(self.kit, privacy: .public)")
|
2024-03-06 15:09:54 +03:00
|
|
|
Logger.shareOptionsView.error(
|
2024-04-17 20:58:38 +03:00
|
|
|
"path: \(self.dataSource?.itemServerRelativePath ?? "", privacy: .public)"
|
2024-03-06 15:09:54 +03:00
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-03-18 13:52:39 +03:00
|
|
|
let selectedShareType = pickedShareType()
|
2024-03-06 20:11:42 +03:00
|
|
|
let shareWith = shareRecipientTextField.stringValue
|
2024-03-06 15:09:54 +03:00
|
|
|
|
|
|
|
var permissions = NKShare.PermissionValues.all.rawValue
|
|
|
|
permissions = uploadAndEdit
|
|
|
|
? permissions | NKShare.PermissionValues.updateShare.rawValue
|
|
|
|
: permissions & ~NKShare.PermissionValues.updateShare.rawValue
|
|
|
|
|
|
|
|
setAllFields(enabled: false)
|
|
|
|
deleteButton.isEnabled = false
|
|
|
|
saveButton.isEnabled = false
|
|
|
|
let error = await ShareController.create(
|
|
|
|
kit: kit,
|
|
|
|
shareType: selectedShareType,
|
|
|
|
itemServerRelativePath: itemServerRelativePath,
|
2024-03-06 20:11:42 +03:00
|
|
|
shareWith: shareWith,
|
2024-03-06 15:09:54 +03:00
|
|
|
password: password,
|
|
|
|
expireDate: expireDate,
|
|
|
|
permissions: permissions,
|
|
|
|
note: note,
|
|
|
|
label: label,
|
|
|
|
hideDownload: hideDownload
|
|
|
|
)
|
|
|
|
if let error = error, error != .success {
|
|
|
|
dataSource.uiDelegate?.showError("Error creating: \(error.errorDescription)")
|
2024-03-06 20:12:19 +03:00
|
|
|
setAllFields(enabled: true)
|
|
|
|
} else {
|
2024-03-19 15:08:14 +03:00
|
|
|
dataSource.uiDelegate?.hideOptions(self)
|
2024-03-06 20:12:19 +03:00
|
|
|
await dataSource.reload()
|
2024-03-06 15:09:54 +03:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
Logger.shareOptionsView.info("Editing existing share!")
|
|
|
|
|
|
|
|
guard let controller = controller else {
|
|
|
|
Logger.shareOptionsView.error("No valid share controller, cannot edit share.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
let share = controller.share
|
2024-03-05 12:15:57 +03:00
|
|
|
let permissions = uploadAndEdit
|
|
|
|
? share.permissions | NKShare.PermissionValues.updateShare.rawValue
|
|
|
|
: share.permissions & ~NKShare.PermissionValues.updateShare.rawValue
|
2024-03-04 16:40:01 +03:00
|
|
|
|
|
|
|
setAllFields(enabled: false)
|
|
|
|
deleteButton.isEnabled = false
|
|
|
|
saveButton.isEnabled = false
|
2024-03-05 12:15:57 +03:00
|
|
|
let error = await controller.save(
|
2024-03-04 16:40:01 +03:00
|
|
|
password: password,
|
|
|
|
expireDate: expireDate,
|
2024-03-05 12:15:57 +03:00
|
|
|
permissions: permissions,
|
2024-03-04 16:40:01 +03:00
|
|
|
note: note,
|
|
|
|
label: label,
|
|
|
|
hideDownload: hideDownload
|
|
|
|
)
|
2024-03-04 20:29:39 +03:00
|
|
|
if let error = error, error != .success {
|
|
|
|
dataSource?.uiDelegate?.showError("Error updating share: \(error.errorDescription)")
|
2024-03-06 15:09:54 +03:00
|
|
|
setAllFields(enabled: true)
|
|
|
|
} else {
|
2024-03-19 15:08:14 +03:00
|
|
|
dataSource?.uiDelegate?.hideOptions(self)
|
2024-03-06 15:09:54 +03:00
|
|
|
await dataSource?.reload()
|
2024-03-04 20:29:39 +03:00
|
|
|
}
|
2024-03-04 16:40:01 +03:00
|
|
|
}
|
|
|
|
}
|
2024-03-05 12:50:40 +03:00
|
|
|
|
|
|
|
@IBAction func delete(_ sender: Any) {
|
|
|
|
Task { @MainActor in
|
2024-03-06 19:50:45 +03:00
|
|
|
guard !createMode else {
|
2024-03-19 15:08:14 +03:00
|
|
|
dataSource?.uiDelegate?.hideOptions(self)
|
2024-03-06 19:50:45 +03:00
|
|
|
reset()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-03-05 12:50:40 +03:00
|
|
|
setAllFields(enabled: false)
|
|
|
|
deleteButton.isEnabled = false
|
|
|
|
saveButton.isEnabled = false
|
|
|
|
let error = await controller?.delete()
|
|
|
|
if let error = error, error != .success {
|
|
|
|
dataSource?.uiDelegate?.showError("Error deleting share: \(error.errorDescription)")
|
|
|
|
}
|
|
|
|
await dataSource?.reload()
|
|
|
|
}
|
|
|
|
}
|
2024-02-28 17:37:01 +03:00
|
|
|
}
|