Add inline editing for some value types

This commit is contained in:
Shadowfacts 2020-08-12 22:45:37 -04:00
parent ba45bc6f1b
commit 24ce95c764
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
2 changed files with 175 additions and 24 deletions

View File

@ -39,6 +39,14 @@ class Node: NSObject {
numberOfChildren > 0
}
var root: Node {
if let parent = parent {
return parent.root
} else {
return self
}
}
init(key: Key? = nil, value: BSON, parent: Node? = nil) {
self.value = value
self.parent = parent
@ -84,8 +92,7 @@ extension Node {
extension Node {
static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = .current
formatter.setLocalizedDateFormatFromTemplate("yyyy-MM-dd HH:mm:ss ZZ")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZ"
return formatter
}()
@ -136,7 +143,7 @@ extension Node {
case let .bool(value):
return value.description
case let .datetime(value):
return value.description
return Node.dateFormatter.string(from: value)
case .null:
return "null"
case let .regex(value):
@ -152,6 +159,7 @@ extension Node {
case let .int32(value):
return value.description
case let .timestamp(value):
// todo: this needs to include the timestamp increment
let date = Date(timeIntervalSince1970: TimeInterval(value.timestamp))
return Node.dateFormatter.string(from: date)
case let .int64(value):
@ -226,13 +234,4 @@ extension Node {
return "MaxKey"
}
}
var isValueCopyable: Bool {
switch value {
case .document(_), .array(_), .binary(_), .minKey, .maxKey:
return false
default:
return true
}
}
}

View File

@ -155,12 +155,25 @@ class QueryViewController: NSViewController {
}
}
private func openEditWindow(_ document: BSONDocument) {
let wc = EditDocumentWindowController(mongoController: mongoController, collection: collection, document: document)
wc.documentEdited = {
self.refresh()
self.mongoController.statusManager.set("Updated document", for: .document)
}
wc.showWindow(nil)
}
@objc func outlineCellDoubleClicked() {
if let item = outlineView.item(atRow: outlineView.clickedRow) {
if outlineView.isItemExpanded(item) {
outlineView.collapseItem(item)
} else {
outlineView.expandItem(item)
if let node = outlineView.item(atRow: outlineView.clickedRow) as? Node {
if node.hasChildren {
if outlineView.isItemExpanded(node) {
outlineView.collapseItem(node)
} else {
outlineView.expandItem(node)
}
} else if node.isValueInlineEditable {
outlineView.editColumn(1, row: outlineView.clickedRow, with: nil, select: false)
}
}
}
@ -181,12 +194,7 @@ class QueryViewController: NSViewController {
return
}
let wc = EditDocumentWindowController(mongoController: mongoController, collection: collection, document: document)
wc.documentEdited = {
self.refresh()
self.mongoController.statusManager.set("Updated document", for: .document)
}
wc.showWindow(nil)
openEditWindow(document)
}
@IBAction func copy(_ sender: Any) {
@ -205,6 +213,51 @@ class QueryViewController: NSViewController {
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(str, forType: .string)
}
@objc func editedValue(_ textField: NSTextField) {
guard let node = outlineView.item(atRow: outlineView.selectedRow) as? Node,
case let .document(rootDoc) = node.root.value else {
return
}
let proposedValue = textField.stringValue
if let newValue = node.coerceBSONValue(proposedValue) {
let updateDoc: BSONDocument = [
"$set": [
node.buildUpdateKey(): newValue
]
]
mongoController.collection(collection).updateOne(filter: rootDoc, update: updateDoc).whenComplete { (result) in
switch result {
case .success(nil):
fatalError()
case .success(_):
self.mongoController.statusManager.set("Updated document", for: .document)
case let .failure(error):
DispatchQueue.main.async {
let alert = NSAlert(error: error)
alert.beginSheetModal(for: self.view.window!, completionHandler: nil)
}
}
}
} else {
textField.stringValue = node.valueString
let alert = NSAlert()
alert.alertStyle = .critical
alert.messageText = "Invalid value format"
alert.informativeText = "The value '\(proposedValue)' is not valid for fields of type \(node.value.type).\nIf you want to change the value type, edit the JSON document."
alert.addButton(withTitle: "OK")
alert.addButton(withTitle: "Edit Document")
alert.beginSheetModal(for: self.view.window!) { (res) in
alert.window.close()
if res == .alertSecondButtonReturn {
self.openEditWindow(rootDoc)
}
}
}
}
}
extension QueryViewController: NSMenuItemValidation {
@ -273,7 +326,9 @@ extension QueryViewController: NSOutlineViewDelegate {
} else if tableColumn.identifier == .fieldValueColumn {
let cell = outlineView.makeView(withIdentifier: .fieldValueCell, owner: nil) as! NSTableCellView
cell.textField!.stringValue = node.valueString
cell.textField!.isEditable = false
cell.textField!.isEditable = node.isValueInlineEditable
cell.textField!.target = self
cell.textField!.action = #selector(editedValue(_:))
return cell
} else if tableColumn.identifier == .valueTypeColumn {
let cell = outlineView.makeView(withIdentifier: .valueTypeCell, owner: nil) as! NSTableCellView
@ -295,3 +350,100 @@ extension NSUserInterfaceItemIdentifier {
static let valueTypeColumn = NSUserInterfaceItemIdentifier(rawValue: "ValueTypeCol")
static let valueTypeCell = NSUserInterfaceItemIdentifier(rawValue: "ValueTypeCell")
}
fileprivate extension Node {
var isValueCopyable: Bool {
switch value.type {
case .document, .array, .binary, .minKey, .maxKey:
return false
default:
return true
}
}
var isValueInlineEditable: Bool {
switch value.type {
case .double, .string, .objectID, .bool, .datetime, .int32, .int64, .decimal128:
return true
default:
return false
}
}
func coerceBSONValue(_ str: String) -> BSON? {
guard isValueInlineEditable else { return false }
switch value.type {
case .double:
if let d = Double(str) {
return .double(d)
} else {
return nil
}
case .string:
return .string(str)
case .objectID:
if let id = try? BSONObjectID(str) {
return .objectID(id)
} else {
return nil
}
case .bool:
let lower = str.lowercased()
if lower == "true" {
return .bool(true)
} else if lower == "false" {
return .bool(false)
} else {
return nil
}
case .datetime:
if let date = Node.dateFormatter.date(from: str) {
return .datetime(date)
} else {
return nil
}
case .int32:
if let i = Int32(str) {
return .int32(i)
} else {
return nil
}
case .int64:
if let i = Int64(str) {
return .int64(i)
} else {
return nil
}
case .decimal128:
if let dec = try? BSONDecimal128(str) {
return .decimal128(dec)
} else {
return nil
}
default:
return nil
}
}
func buildUpdateKey() -> String {
let parentKey: String
if let parent = parent {
if case .objectID(_) = parent.key, parent.parent == nil {
parentKey = ""
} else {
parentKey = parent.buildUpdateKey() + "."
}
} else {
parentKey = ""
}
switch key {
case let .index(index):
return parentKey + index.description
case let .name(name):
return parentKey + name
default:
fatalError()
}
}
}