Add inline editing for some value types
This commit is contained in:
parent
ba45bc6f1b
commit
24ce95c764
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue