Compare commits

..

17 Commits

Author SHA1 Message Date
Shadowfacts 24ce95c764
Add inline editing for some value types 2020-08-12 22:49:19 -04:00
Shadowfacts ba45bc6f1b
Fix query status messages 2020-08-12 19:42:43 -04:00
Shadowfacts d93b29b274
Tweak edit window appearance 2020-08-12 19:41:46 -04:00
Shadowfacts 8e71077070
Add Copy Value as JSON 2020-08-12 19:37:11 -04:00
Shadowfacts c96e093c72
Fix JSON pretty printer string escaping 2020-08-12 19:17:17 -04:00
Shadowfacts f7310471bf
Use SPM for MongoSwift 2020-08-12 19:09:43 -04:00
Shadowfacts e40e8d662f
Add Copy Value command 2020-08-12 18:49:56 -04:00
Shadowfacts 5ae526934c
Highlight JS keywords 2020-08-12 17:17:32 -04:00
Shadowfacts 8e34e6071f
Fix window expanding on relaunch 2020-08-12 16:52:18 -04:00
Shadowfacts 45e45a3991
Add custom ExtJSON pretty printer to handle Mongo objects 2020-08-12 16:39:05 -04:00
Shadowfacts 54f8236332
Split DatabaseViewController into multiple VCs 2020-08-12 10:53:40 -04:00
Shadowfacts edb01fa6e2
Fix extended JSON prettification failing on escaped double-quotes 2020-08-12 10:08:05 -04:00
Shadowfacts 1ab9d53006
Fix window size resetting on new tab 2020-08-11 22:27:49 -04:00
Shadowfacts 31962b43a3
Improve status messages 2020-07-07 13:23:27 -04:00
Shadowfacts 967a2518b4
Use Combine for MongoController status changes 2020-07-06 10:50:56 -04:00
Shadowfacts bb695fbf53 Fix crash when MongoController connection fails 2020-07-06 10:36:44 -04:00
Shadowfacts 98179767ca
Fix missing outgoing network entitlement 2020-07-06 10:35:47 -04:00
27 changed files with 1052 additions and 536 deletions

View File

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 50; objectVersion = 52;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -17,63 +17,33 @@
D626BF83243BD2EE0075117B /* EditDocumentWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626BF81243BD2EE0075117B /* EditDocumentWindowController.xib */; }; D626BF83243BD2EE0075117B /* EditDocumentWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626BF81243BD2EE0075117B /* EditDocumentWindowController.xib */; };
D626BF86243BE19A0075117B /* EditDocumentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626BF84243BE19A0075117B /* EditDocumentViewController.swift */; }; D626BF86243BE19A0075117B /* EditDocumentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626BF84243BE19A0075117B /* EditDocumentViewController.swift */; };
D626BF87243BE19A0075117B /* EditDocumentViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626BF85243BE19A0075117B /* EditDocumentViewController.xib */; }; D626BF87243BE19A0075117B /* EditDocumentViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626BF85243BE19A0075117B /* EditDocumentViewController.xib */; };
D627CE7E24E38F3B00C39FE5 /* MainSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627CE7C24E38F3A00C39FE5 /* MainSplitViewController.swift */; };
D627CE8224E38FBE00C39FE5 /* DatabaseCollectionListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627CE8024E38FBE00C39FE5 /* DatabaseCollectionListViewController.swift */; };
D627CE8324E38FBE00C39FE5 /* DatabaseCollectionListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627CE8124E38FBE00C39FE5 /* DatabaseCollectionListViewController.xib */; };
D627CE8824E399F100C39FE5 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627CE8624E399F100C39FE5 /* DetailViewController.swift */; };
D627CE8924E399F100C39FE5 /* DetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627CE8724E399F100C39FE5 /* DetailViewController.xib */; };
D627CE8B24E438EE00C39FE5 /* DatabaseCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627CE8A24E438EE00C39FE5 /* DatabaseCollection.swift */; };
D627CE8D24E4478800C39FE5 /* JSONPrettyPrinter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627CE8C24E4478800C39FE5 /* JSONPrettyPrinter.swift */; };
D627CE9024E4A9F100C39FE5 /* MongoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = D627CE8F24E4A9F100C39FE5 /* MongoSwift */; };
D63CDEBE23C837DC0012D658 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDEBD23C837DC0012D658 /* AppDelegate.swift */; }; D63CDEBE23C837DC0012D658 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDEBD23C837DC0012D658 /* AppDelegate.swift */; };
D63CDEC023C837DD0012D658 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D63CDEBF23C837DD0012D658 /* Assets.xcassets */; }; D63CDEC023C837DD0012D658 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D63CDEBF23C837DD0012D658 /* Assets.xcassets */; };
D63CDEC323C837DD0012D658 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63CDEC123C837DD0012D658 /* MainMenu.xib */; }; D63CDEC323C837DD0012D658 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63CDEC123C837DD0012D658 /* MainMenu.xib */; };
D63CDF1F23C837F10012D658 /* CLibMongoC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1623C837F10012D658 /* CLibMongoC.framework */; };
D63CDF2023C837F10012D658 /* CLibMongoC.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1623C837F10012D658 /* CLibMongoC.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D63CDF2123C837F10012D658 /* CNIOAtomics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1723C837F10012D658 /* CNIOAtomics.framework */; };
D63CDF2223C837F10012D658 /* CNIOAtomics.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1723C837F10012D658 /* CNIOAtomics.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D63CDF2323C837F10012D658 /* CNIODarwin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1823C837F10012D658 /* CNIODarwin.framework */; };
D63CDF2423C837F10012D658 /* CNIODarwin.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1823C837F10012D658 /* CNIODarwin.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D63CDF2523C837F10012D658 /* CNIOLinux.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1923C837F10012D658 /* CNIOLinux.framework */; };
D63CDF2623C837F10012D658 /* CNIOLinux.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1923C837F10012D658 /* CNIOLinux.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D63CDF2723C837F10012D658 /* CNIOSHA1.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1A23C837F10012D658 /* CNIOSHA1.framework */; };
D63CDF2823C837F10012D658 /* CNIOSHA1.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1A23C837F10012D658 /* CNIOSHA1.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D63CDF2923C837F10012D658 /* MongoSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1B23C837F10012D658 /* MongoSwift.framework */; };
D63CDF2A23C837F10012D658 /* MongoSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1B23C837F10012D658 /* MongoSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D63CDF2B23C837F10012D658 /* MongoSwiftSync.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1C23C837F10012D658 /* MongoSwiftSync.framework */; };
D63CDF2C23C837F10012D658 /* MongoSwiftSync.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1C23C837F10012D658 /* MongoSwiftSync.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D63CDF2D23C837F10012D658 /* NIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1D23C837F10012D658 /* NIO.framework */; };
D63CDF2E23C837F10012D658 /* NIO.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1D23C837F10012D658 /* NIO.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D63CDF2F23C837F10012D658 /* NIOConcurrencyHelpers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1E23C837F10012D658 /* NIOConcurrencyHelpers.framework */; };
D63CDF3023C837F10012D658 /* NIOConcurrencyHelpers.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D63CDF1E23C837F10012D658 /* NIOConcurrencyHelpers.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D63CDF3723C8381A0012D658 /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDF3323C838190012D658 /* Node.swift */; }; D63CDF3723C8381A0012D658 /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDF3323C838190012D658 /* Node.swift */; };
D63CDF3823C8381A0012D658 /* MongoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDF3423C838190012D658 /* MongoController.swift */; }; D63CDF3823C8381A0012D658 /* MongoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDF3423C838190012D658 /* MongoController.swift */; };
D63CDF3C23C838470012D658 /* DatabaseWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDF3A23C838470012D658 /* DatabaseWindowController.swift */; }; D63CDF3C23C838470012D658 /* DatabaseWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDF3A23C838470012D658 /* DatabaseWindowController.swift */; };
D63CDF3D23C838470012D658 /* DatabaseWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63CDF3B23C838470012D658 /* DatabaseWindowController.xib */; }; D63CDF3D23C838470012D658 /* DatabaseWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63CDF3B23C838470012D658 /* DatabaseWindowController.xib */; };
D63CDF4023C839010012D658 /* QueryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDF3E23C839010012D658 /* QueryViewController.swift */; }; D63CDF4023C839010012D658 /* QueryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDF3E23C839010012D658 /* QueryViewController.swift */; };
D63CDF4123C839010012D658 /* QueryViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63CDF3F23C839010012D658 /* QueryViewController.xib */; }; D63CDF4123C839010012D658 /* QueryViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63CDF3F23C839010012D658 /* QueryViewController.xib */; };
D63CDF4423C970C50012D658 /* DatabaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDF4223C970C50012D658 /* DatabaseViewController.swift */; };
D63CDF4523C970C50012D658 /* DatabaseViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63CDF4323C970C50012D658 /* DatabaseViewController.xib */; };
D6A7D096243541A400B46857 /* WindowStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A7D095243541A400B46857 /* WindowStatusView.swift */; }; D6A7D096243541A400B46857 /* WindowStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A7D095243541A400B46857 /* WindowStatusView.swift */; };
D6A7D09A243546B500B46857 /* WindowStatusView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A7D099243546B500B46857 /* WindowStatusView.xib */; }; D6A7D09A243546B500B46857 /* WindowStatusView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A7D099243546B500B46857 /* WindowStatusView.xib */; };
D6A7D0A42435885B00B46857 /* JavaScriptHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A7D0A32435885B00B46857 /* JavaScriptHighlighter.swift */; }; D6A7D0A42435885B00B46857 /* JavaScriptHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A7D0A32435885B00B46857 /* JavaScriptHighlighter.swift */; };
D6ABB01424B4DE7600F79DA8 /* StatusManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ABB01324B4DE7600F79DA8 /* StatusManager.swift */; };
D6D13B042436C33D00493D97 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D13B032436C33D00493D97 /* main.swift */; }; D6D13B042436C33D00493D97 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D13B032436C33D00493D97 /* main.swift */; };
D6D13B082436C34200493D97 /* JavaScriptHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A7D0A32435885B00B46857 /* JavaScriptHighlighter.swift */; }; D6D13B082436C34200493D97 /* JavaScriptHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A7D0A32435885B00B46857 /* JavaScriptHighlighter.swift */; };
D6D4665323CB730C00F13B1B /* MongoEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */; }; D6D4665323CB730C00F13B1B /* MongoEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
D63CDF3123C837F10012D658 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
D63CDF2823C837F10012D658 /* CNIOSHA1.framework in Embed Frameworks */,
D63CDF2623C837F10012D658 /* CNIOLinux.framework in Embed Frameworks */,
D63CDF2423C837F10012D658 /* CNIODarwin.framework in Embed Frameworks */,
D63CDF2A23C837F10012D658 /* MongoSwift.framework in Embed Frameworks */,
D63CDF2223C837F10012D658 /* CNIOAtomics.framework in Embed Frameworks */,
D63CDF2023C837F10012D658 /* CLibMongoC.framework in Embed Frameworks */,
D63CDF2C23C837F10012D658 /* MongoSwiftSync.framework in Embed Frameworks */,
D63CDF3023C837F10012D658 /* NIOConcurrencyHelpers.framework in Embed Frameworks */,
D63CDF2E23C837F10012D658 /* NIO.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
D6D13AFF2436C33D00493D97 /* CopyFiles */ = { D6D13AFF2436C33D00493D97 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -96,6 +66,13 @@
D626BF81243BD2EE0075117B /* EditDocumentWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EditDocumentWindowController.xib; sourceTree = "<group>"; }; D626BF81243BD2EE0075117B /* EditDocumentWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EditDocumentWindowController.xib; sourceTree = "<group>"; };
D626BF84243BE19A0075117B /* EditDocumentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditDocumentViewController.swift; sourceTree = "<group>"; }; D626BF84243BE19A0075117B /* EditDocumentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditDocumentViewController.swift; sourceTree = "<group>"; };
D626BF85243BE19A0075117B /* EditDocumentViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EditDocumentViewController.xib; sourceTree = "<group>"; }; D626BF85243BE19A0075117B /* EditDocumentViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EditDocumentViewController.xib; sourceTree = "<group>"; };
D627CE7C24E38F3A00C39FE5 /* MainSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSplitViewController.swift; sourceTree = "<group>"; };
D627CE8024E38FBE00C39FE5 /* DatabaseCollectionListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseCollectionListViewController.swift; sourceTree = "<group>"; };
D627CE8124E38FBE00C39FE5 /* DatabaseCollectionListViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DatabaseCollectionListViewController.xib; sourceTree = "<group>"; };
D627CE8624E399F100C39FE5 /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; };
D627CE8724E399F100C39FE5 /* DetailViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DetailViewController.xib; sourceTree = "<group>"; };
D627CE8A24E438EE00C39FE5 /* DatabaseCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseCollection.swift; sourceTree = "<group>"; };
D627CE8C24E4478800C39FE5 /* JSONPrettyPrinter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONPrettyPrinter.swift; sourceTree = "<group>"; };
D63CDEBA23C837DC0012D658 /* MongoView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MongoView.app; sourceTree = BUILT_PRODUCTS_DIR; }; D63CDEBA23C837DC0012D658 /* MongoView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MongoView.app; sourceTree = BUILT_PRODUCTS_DIR; };
D63CDEBD23C837DC0012D658 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; D63CDEBD23C837DC0012D658 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
D63CDEBF23C837DD0012D658 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; D63CDEBF23C837DD0012D658 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -117,11 +94,10 @@
D63CDF3B23C838470012D658 /* DatabaseWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DatabaseWindowController.xib; sourceTree = "<group>"; }; D63CDF3B23C838470012D658 /* DatabaseWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DatabaseWindowController.xib; sourceTree = "<group>"; };
D63CDF3E23C839010012D658 /* QueryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryViewController.swift; sourceTree = "<group>"; }; D63CDF3E23C839010012D658 /* QueryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryViewController.swift; sourceTree = "<group>"; };
D63CDF3F23C839010012D658 /* QueryViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QueryViewController.xib; sourceTree = "<group>"; }; D63CDF3F23C839010012D658 /* QueryViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QueryViewController.xib; sourceTree = "<group>"; };
D63CDF4223C970C50012D658 /* DatabaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseViewController.swift; sourceTree = "<group>"; };
D63CDF4323C970C50012D658 /* DatabaseViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DatabaseViewController.xib; sourceTree = "<group>"; };
D6A7D095243541A400B46857 /* WindowStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowStatusView.swift; sourceTree = "<group>"; }; D6A7D095243541A400B46857 /* WindowStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowStatusView.swift; sourceTree = "<group>"; };
D6A7D099243546B500B46857 /* WindowStatusView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WindowStatusView.xib; sourceTree = "<group>"; }; D6A7D099243546B500B46857 /* WindowStatusView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WindowStatusView.xib; sourceTree = "<group>"; };
D6A7D0A32435885B00B46857 /* JavaScriptHighlighter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JavaScriptHighlighter.swift; sourceTree = "<group>"; }; D6A7D0A32435885B00B46857 /* JavaScriptHighlighter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JavaScriptHighlighter.swift; sourceTree = "<group>"; };
D6ABB01324B4DE7600F79DA8 /* StatusManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusManager.swift; sourceTree = "<group>"; };
D6D13B012436C33D00493D97 /* jstest */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jstest; sourceTree = BUILT_PRODUCTS_DIR; }; D6D13B012436C33D00493D97 /* jstest */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jstest; sourceTree = BUILT_PRODUCTS_DIR; };
D6D13B032436C33D00493D97 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; }; D6D13B032436C33D00493D97 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MongoEvaluator.swift; sourceTree = "<group>"; }; D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MongoEvaluator.swift; sourceTree = "<group>"; };
@ -132,15 +108,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D63CDF2723C837F10012D658 /* CNIOSHA1.framework in Frameworks */, D627CE9024E4A9F100C39FE5 /* MongoSwift in Frameworks */,
D63CDF2523C837F10012D658 /* CNIOLinux.framework in Frameworks */,
D63CDF2323C837F10012D658 /* CNIODarwin.framework in Frameworks */,
D63CDF2923C837F10012D658 /* MongoSwift.framework in Frameworks */,
D63CDF2123C837F10012D658 /* CNIOAtomics.framework in Frameworks */,
D63CDF1F23C837F10012D658 /* CLibMongoC.framework in Frameworks */,
D63CDF2B23C837F10012D658 /* MongoSwiftSync.framework in Frameworks */,
D63CDF2F23C837F10012D658 /* NIOConcurrencyHelpers.framework in Frameworks */,
D63CDF2D23C837F10012D658 /* NIO.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -172,8 +140,11 @@
children = ( children = (
D60C863D23CA2E2100C9DB8E /* ServerConnectViewController.swift */, D60C863D23CA2E2100C9DB8E /* ServerConnectViewController.swift */,
D60C863E23CA2E2100C9DB8E /* ServerConnectViewController.xib */, D60C863E23CA2E2100C9DB8E /* ServerConnectViewController.xib */,
D63CDF4223C970C50012D658 /* DatabaseViewController.swift */, D627CE7C24E38F3A00C39FE5 /* MainSplitViewController.swift */,
D63CDF4323C970C50012D658 /* DatabaseViewController.xib */, D627CE8024E38FBE00C39FE5 /* DatabaseCollectionListViewController.swift */,
D627CE8124E38FBE00C39FE5 /* DatabaseCollectionListViewController.xib */,
D627CE8624E399F100C39FE5 /* DetailViewController.swift */,
D627CE8724E399F100C39FE5 /* DetailViewController.xib */,
D63CDF3E23C839010012D658 /* QueryViewController.swift */, D63CDF3E23C839010012D658 /* QueryViewController.swift */,
D63CDF3F23C839010012D658 /* QueryViewController.xib */, D63CDF3F23C839010012D658 /* QueryViewController.xib */,
D626BF84243BE19A0075117B /* EditDocumentViewController.swift */, D626BF84243BE19A0075117B /* EditDocumentViewController.swift */,
@ -216,9 +187,12 @@
children = ( children = (
D63CDEBD23C837DC0012D658 /* AppDelegate.swift */, D63CDEBD23C837DC0012D658 /* AppDelegate.swift */,
D63CDF3423C838190012D658 /* MongoController.swift */, D63CDF3423C838190012D658 /* MongoController.swift */,
D6ABB01324B4DE7600F79DA8 /* StatusManager.swift */,
D63CDF3323C838190012D658 /* Node.swift */, D63CDF3323C838190012D658 /* Node.swift */,
D627CE8A24E438EE00C39FE5 /* DatabaseCollection.swift */,
D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */, D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */,
D624090E243903E90020E09F /* ExtendedJSON.swift */, D624090E243903E90020E09F /* ExtendedJSON.swift */,
D627CE8C24E4478800C39FE5 /* JSONPrettyPrinter.swift */,
D6A7D0A22435880700B46857 /* Synax Highlighting */, D6A7D0A22435880700B46857 /* Synax Highlighting */,
D60C863B23CA2DD600C9DB8E /* Windows */, D60C863B23CA2DD600C9DB8E /* Windows */,
D60C863C23CA2DDD00C9DB8E /* View Controllers */, D60C863C23CA2DDD00C9DB8E /* View Controllers */,
@ -273,13 +247,15 @@
D63CDEB623C837DC0012D658 /* Sources */, D63CDEB623C837DC0012D658 /* Sources */,
D63CDEB723C837DC0012D658 /* Frameworks */, D63CDEB723C837DC0012D658 /* Frameworks */,
D63CDEB823C837DC0012D658 /* Resources */, D63CDEB823C837DC0012D658 /* Resources */,
D63CDF3123C837F10012D658 /* Embed Frameworks */,
); );
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
); );
name = MongoView; name = MongoView;
packageProductDependencies = (
D627CE8F24E4A9F100C39FE5 /* MongoSwift */,
);
productName = MongoView; productName = MongoView;
productReference = D63CDEBA23C837DC0012D658 /* MongoView.app */; productReference = D63CDEBA23C837DC0012D658 /* MongoView.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
@ -328,6 +304,9 @@
Base, Base,
); );
mainGroup = D63CDEB123C837DC0012D658; mainGroup = D63CDEB123C837DC0012D658;
packageReferences = (
D627CE8E24E4A9F100C39FE5 /* XCRemoteSwiftPackageReference "mongo-swift-driver" */,
);
productRefGroup = D63CDEBB23C837DC0012D658 /* Products */; productRefGroup = D63CDEBB23C837DC0012D658 /* Products */;
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
@ -347,9 +326,10 @@
D63CDEC023C837DD0012D658 /* Assets.xcassets in Resources */, D63CDEC023C837DD0012D658 /* Assets.xcassets in Resources */,
D60C863A23CA2DD100C9DB8E /* ServerConnectWindowController.xib in Resources */, D60C863A23CA2DD100C9DB8E /* ServerConnectWindowController.xib in Resources */,
D626BF87243BE19A0075117B /* EditDocumentViewController.xib in Resources */, D626BF87243BE19A0075117B /* EditDocumentViewController.xib in Resources */,
D63CDF4523C970C50012D658 /* DatabaseViewController.xib in Resources */,
D6A7D09A243546B500B46857 /* WindowStatusView.xib in Resources */, D6A7D09A243546B500B46857 /* WindowStatusView.xib in Resources */,
D63CDF3D23C838470012D658 /* DatabaseWindowController.xib in Resources */, D63CDF3D23C838470012D658 /* DatabaseWindowController.xib in Resources */,
D627CE8924E399F100C39FE5 /* DetailViewController.xib in Resources */,
D627CE8324E38FBE00C39FE5 /* DatabaseCollectionListViewController.xib in Resources */,
D63CDEC323C837DD0012D658 /* MainMenu.xib in Resources */, D63CDEC323C837DD0012D658 /* MainMenu.xib in Resources */,
D626BF83243BD2EE0075117B /* EditDocumentWindowController.xib in Resources */, D626BF83243BD2EE0075117B /* EditDocumentWindowController.xib in Resources */,
D63CDF4123C839010012D658 /* QueryViewController.xib in Resources */, D63CDF4123C839010012D658 /* QueryViewController.xib in Resources */,
@ -363,19 +343,24 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D627CE8B24E438EE00C39FE5 /* DatabaseCollection.swift in Sources */,
D63CDEBE23C837DC0012D658 /* AppDelegate.swift in Sources */, D63CDEBE23C837DC0012D658 /* AppDelegate.swift in Sources */,
D6D4665323CB730C00F13B1B /* MongoEvaluator.swift in Sources */, D6D4665323CB730C00F13B1B /* MongoEvaluator.swift in Sources */,
D624090F243903E90020E09F /* ExtendedJSON.swift in Sources */, D624090F243903E90020E09F /* ExtendedJSON.swift in Sources */,
D627CE8D24E4478800C39FE5 /* JSONPrettyPrinter.swift in Sources */,
D63CDF3823C8381A0012D658 /* MongoController.swift in Sources */, D63CDF3823C8381A0012D658 /* MongoController.swift in Sources */,
D60C863923CA2DD100C9DB8E /* ServerConnectWindowController.swift in Sources */, D60C863923CA2DD100C9DB8E /* ServerConnectWindowController.swift in Sources */,
D63CDF3723C8381A0012D658 /* Node.swift in Sources */, D63CDF3723C8381A0012D658 /* Node.swift in Sources */,
D626BF86243BE19A0075117B /* EditDocumentViewController.swift in Sources */, D626BF86243BE19A0075117B /* EditDocumentViewController.swift in Sources */,
D627CE8224E38FBE00C39FE5 /* DatabaseCollectionListViewController.swift in Sources */,
D60C863F23CA2E2100C9DB8E /* ServerConnectViewController.swift in Sources */, D60C863F23CA2E2100C9DB8E /* ServerConnectViewController.swift in Sources */,
D63CDF4423C970C50012D658 /* DatabaseViewController.swift in Sources */, D627CE8824E399F100C39FE5 /* DetailViewController.swift in Sources */,
D63CDF3C23C838470012D658 /* DatabaseWindowController.swift in Sources */, D63CDF3C23C838470012D658 /* DatabaseWindowController.swift in Sources */,
D6A7D096243541A400B46857 /* WindowStatusView.swift in Sources */, D6A7D096243541A400B46857 /* WindowStatusView.swift in Sources */,
D626BF82243BD2EE0075117B /* EditDocumentWindowController.swift in Sources */, D626BF82243BD2EE0075117B /* EditDocumentWindowController.swift in Sources */,
D6A7D0A42435885B00B46857 /* JavaScriptHighlighter.swift in Sources */, D6A7D0A42435885B00B46857 /* JavaScriptHighlighter.swift in Sources */,
D6ABB01424B4DE7600F79DA8 /* StatusManager.swift in Sources */,
D627CE7E24E38F3B00C39FE5 /* MainSplitViewController.swift in Sources */,
D62408C12438CF550020E09F /* JavaScriptEditorView.swift in Sources */, D62408C12438CF550020E09F /* JavaScriptEditorView.swift in Sources */,
D63CDF4023C839010012D658 /* QueryViewController.swift in Sources */, D63CDF4023C839010012D658 /* QueryViewController.swift in Sources */,
); );
@ -526,7 +511,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 3; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = V4WK9KR9U2; DEVELOPMENT_TEAM = HGYVAQA9FW;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = MongoView/Info.plist; INFOPLIST_FILE = MongoView/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@ -548,7 +533,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 3; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = V4WK9KR9U2; DEVELOPMENT_TEAM = HGYVAQA9FW;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = MongoView/Info.plist; INFOPLIST_FILE = MongoView/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@ -614,6 +599,25 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
D627CE8E24E4A9F100C39FE5 /* XCRemoteSwiftPackageReference "mongo-swift-driver" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mongodb/mongo-swift-driver.git";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 1.0.1;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
D627CE8F24E4A9F100C39FE5 /* MongoSwift */ = {
isa = XCSwiftPackageProductDependency;
package = D627CE8E24E4A9F100C39FE5 /* XCRemoteSwiftPackageReference "mongo-swift-driver" */;
productName = MongoSwift;
};
/* End XCSwiftPackageProductDependency section */
}; };
rootObject = D63CDEB223C837DC0012D658 /* Project object */; rootObject = D63CDEB223C837DC0012D658 /* Project object */;
} }

View File

@ -4,7 +4,4 @@
<FileRef <FileRef
location = "container:MongoView.xcodeproj"> location = "container:MongoView.xcodeproj">
</FileRef> </FileRef>
<FileRef
location = "group:../mongo-swift-driver/MongoSwift.xcodeproj">
</FileRef>
</Workspace> </Workspace>

View File

@ -0,0 +1,34 @@
{
"object": {
"pins": [
{
"package": "mongo-swift-driver",
"repositoryURL": "https://github.com/mongodb/mongo-swift-driver.git",
"state": {
"branch": null,
"revision": "ec67468132743919e90a34a76073afcc4a13355e",
"version": "1.0.1"
}
},
{
"package": "Nimble",
"repositoryURL": "https://github.com/Quick/Nimble.git",
"state": {
"branch": null,
"revision": "2b1809051b4a65c1d7f5233331daa24572cd7fca",
"version": "8.1.1"
}
},
{
"package": "swift-nio",
"repositoryURL": "https://github.com/apple/swift-nio",
"state": {
"branch": null,
"revision": "acf5465b5e7fb9aeda54a34d16fb44c31a399715",
"version": "2.20.2"
}
}
]
},
"version": 1
}

View File

@ -16,9 +16,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
var serverConnectWindowController: ServerConnectWindowController? var serverConnectWindowController: ServerConnectWindowController?
func applicationDidFinishLaunching(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
let wc = DatabaseWindowController() newWindow(mongoController: nil)
windowControllers.append(wc)
wc.showWindow(nil)
} }
func applicationWillTerminate(_ aNotification: Notification) { func applicationWillTerminate(_ aNotification: Notification) {
@ -31,7 +29,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
windowControllers.append(wc) windowControllers.append(wc)
if addToTabGroup, if addToTabGroup,
let tabGroup = windowControllers.first(where: { $0.window!.isKeyWindow })?.window?.tabGroup { let existing = windowControllers.first(where: { $0.window!.isKeyWindow })?.window,
let tabGroup = existing.tabGroup {
tabGroup.addWindow(wc.window!) tabGroup.addWindow(wc.window!)
tabGroup.selectedWindow = wc.window! tabGroup.selectedWindow = wc.window!
} else { } else {

View File

@ -0,0 +1,23 @@
//
// DatabaseCollection.swift
// MongoView
//
// Created by Shadowfacts on 8/12/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import Foundation
struct DatabaseCollections {
let database: String
let collections: [String]
}
struct DatabaseCollection: CustomStringConvertible {
let database: String
let name: String
var description: String {
return "\(database).\(name)"
}
}

View File

@ -19,6 +19,10 @@ struct ExtendedJSON {
return ["$oid": id] return ["$oid": id]
} }
context.setObject(objectId, forKeyedSubscript: "ObjectId" as NSString) context.setObject(objectId, forKeyedSubscript: "ObjectId" as NSString)
let date: @convention(block) (String) -> [String: String] = { (date) in
return ["$date": date]
}
context.setObject(date, forKeyedSubscript: "Date" as NSString)
return context return context
}() }()
@ -26,9 +30,9 @@ struct ExtendedJSON {
return context.evaluateScript("JSON.stringify(\(string))")?.toString() return context.evaluateScript("JSON.stringify(\(string))")?.toString()
} }
private static func fromExtJSON(_ string: String) -> Document? { private static func fromExtJSON(_ string: String) -> BSONDocument? {
do { do {
let doc = try Document(fromJSON: string) let doc = try BSONDocument(fromJSON: string)
return doc return doc
} catch { } catch {
print("Unable to create document from extended JSON: \(error)") print("Unable to create document from extended JSON: \(error)")
@ -36,7 +40,7 @@ struct ExtendedJSON {
} }
} }
static func toDocument(_ string: String) -> Document? { static func toDocument(_ string: String) -> BSONDocument? {
guard let normalized = normalize(string), guard let normalized = normalize(string),
let doc = fromExtJSON(normalized) else { let doc = fromExtJSON(normalized) else {
return nil return nil
@ -44,9 +48,4 @@ struct ExtendedJSON {
return doc return doc
} }
static func prettify(_ string: String) -> String? {
let command = "JSON.stringify(JSON.parse(`\(string)`), null, 4)"
return context.evaluateScript(command)?.toString()
}
} }

View File

@ -0,0 +1,118 @@
//
// JSONPrettyPrinter.swift
// MongoView
//
// Created by Shadowfacts on 8/12/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import Foundation
class JSONPrettyPrinter {
let options: Options
private var strings = [String]()
private var currentIndent = ""
init(options: Options) {
self.options = options
}
func prettify(_ string: String) throws -> String {
let object = try JSONSerialization.jsonObject(with: string.data(using: .utf8)!, options: [])
doPrettify(object)
return strings.joined(separator: "")
}
private func indent() {
currentIndent += "\t"
}
private func outdent() {
currentIndent = String(currentIndent.dropLast())
}
private func doPrettify(_ object: Any) {
if let dict = object as? [String: Any] {
if options.contains(.convertMongoObjects) && tryPrettifyMongoObject(dict) {
return
}
indent()
strings.append("{\n")
for (index, k) in dict.keys.sorted().enumerated() {
strings.append("\(currentIndent)\"\(escape(k))\"")
strings.append(": ")
doPrettify(dict[k]!)
if index != dict.count - 1 {
strings.append(",\n")
}
}
outdent()
strings.append("\n\(currentIndent)}")
} else if let arr = object as? [Any] {
indent()
strings.append("[\n")
for (index, v) in arr.enumerated() {
strings.append(currentIndent)
doPrettify(v)
if index != arr.count - 1 {
strings.append(",\n")
}
}
outdent()
strings.append("\n\(currentIndent)]")
} else if let str = object as? String {
strings.append("\"\(escape(str))\"")
} else if let bool = object as? Bool {
strings.append(bool.description)
} else if let num = object as? NSNumber {
strings.append(num.description)
} else if object is NSNull {
strings.append("null")
} else {
fatalError("unhandled object type: \(String(describing: object))")
}
}
private func tryPrettifyMongoObject(_ dict: [String: Any]) -> Bool {
guard dict.count == 1 else { return false }
if let value = dict["$oid"] as? String {
strings.append("ObjectId(\"\(value)\")")
return true
}
if let value = dict["$date"] as? String {
strings.append("Date(\"\(value)\")")
return true
}
return false
}
private func escape(_ str: String) -> String {
var str = str
var index = str.startIndex
while index < str.endIndex {
let c = str[index]
if c == "\\" || c == "\"" {
str.replaceSubrange(index..<str.index(after: index), with: "\\\(c)")
index = str.index(after: index)
} else if c == "\n" {
str.replaceSubrange(index..<str.index(after: index), with: "\\n")
index = str.index(after: index)
} else if c == "\r" {
str.replaceSubrange(index..<str.index(after: index), with: "\\r")
index = str.index(after: index)
}
index = str.index(after: index)
}
return str
}
}
extension JSONPrettyPrinter {
struct Options: OptionSet {
let rawValue: Int
static let convertMongoObjects = Options(rawValue: 1 << 0)
}
}

View File

@ -9,6 +9,7 @@
import Foundation import Foundation
import MongoSwift import MongoSwift
import NIO import NIO
import Combine
class MongoController { class MongoController {
let connectionString: String let connectionString: String
@ -17,15 +18,17 @@ class MongoController {
var client: MongoClient! var client: MongoClient!
var status: Status = .connecting { @Published var statusManager = StatusManager()
@Published private(set) var status: ConnectionStatus {
didSet { didSet {
statusDidChange.forEach { $0(status) } statusManager.set(messageFor(status: status), for: .connection)
} }
} }
var statusDidChange = [(Status) -> Void]()
init(connectionString: String) { init(connectionString: String) {
self.connectionString = connectionString self.connectionString = connectionString
self.status = .connecting
self.statusManager.set(messageFor(status: .connecting), for: .connection)
} }
deinit { deinit {
@ -40,7 +43,7 @@ class MongoController {
do { do {
client = try MongoClient(connectionString, using: group) client = try MongoClient(connectionString, using: group)
status = .success status = .connected
} catch { } catch {
status = .failed status = .failed
print("Failed to connect to Mongo: \(error)") print("Failed to connect to Mongo: \(error)")
@ -51,14 +54,25 @@ class MongoController {
return client.db(collection.database) return client.db(collection.database)
} }
func collection(_ collection: DatabaseCollection) -> MongoCollection<Document> { func collection(_ collection: DatabaseCollection) -> MongoCollection<BSONDocument> {
return db(for: collection).collection(collection.name) return db(for: collection).collection(collection.name)
} }
private func messageFor(status: ConnectionStatus) -> String {
switch status {
case .connecting:
return "Connecting..."
case .connected:
return "Connected to \(connectionString)"
case .failed:
return "Failed to connect"
}
}
} }
extension MongoController { extension MongoController {
enum Status { enum ConnectionStatus {
case connecting, success, failed case connecting, connected, failed
} }
} }

View File

@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict/> <dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist> </plist>

View File

@ -39,22 +39,30 @@ class Node: NSObject {
numberOfChildren > 0 numberOfChildren > 0
} }
var root: Node {
if let parent = parent {
return parent.root
} else {
return self
}
}
init(key: Key? = nil, value: BSON, parent: Node? = nil) { init(key: Key? = nil, value: BSON, parent: Node? = nil) {
self.value = value self.value = value
self.parent = parent self.parent = parent
if key == nil, if key == nil,
case let .document(doc) = value, case let .document(doc) = value,
case let .objectId(id) = doc["_id"] { case let .objectID(id) = doc["_id"] {
self.key = .objectId(id) self.key = .objectID(id)
} else { } else {
self.key = key self.key = key
} }
} }
convenience init(document: Document) { convenience init(document: BSONDocument) {
if case let .objectId(id) = document["_id"] { if case let .objectID(id) = document["_id"] {
self.init(key: .objectId(id), value: .document(document)) self.init(key: .objectID(id), value: .document(document))
} else { } else {
self.init(key: nil, value: .document(document)) self.init(key: nil, value: .document(document))
} }
@ -77,15 +85,14 @@ extension Node {
enum Key: Equatable, Hashable { enum Key: Equatable, Hashable {
case index(Int) case index(Int)
case name(String) case name(String)
case objectId(ObjectId) case objectID(BSONObjectID)
} }
} }
extension Node { extension Node {
static let dateFormatter: DateFormatter = { static let dateFormatter: DateFormatter = {
let formatter = DateFormatter() let formatter = DateFormatter()
formatter.locale = .current formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZ"
formatter.setLocalizedDateFormatFromTemplate("yyyy-MM-dd HH:mm:ss ZZ")
return formatter return formatter
}() }()
@ -97,7 +104,7 @@ extension Node {
return index.description return index.description
case let .name(name): case let .name(name):
return name return name
case let .objectId(id): case let .objectID(id):
return id.description return id.description
} }
} }
@ -113,9 +120,7 @@ extension Node {
case let .array(array): case let .array(array):
return "(\(array.count) element\(array.count == 1 ? "" : "s"))" return "(\(array.count) element\(array.count == 1 ? "" : "s"))"
case let .binary(value): case let .binary(value):
switch Binary.Subtype(rawValue: value.subtype) { switch value.subtype {
case nil:
return "(unknown binary data)"
case .generic: case .generic:
return "(generic binary data)" return "(generic binary data)"
case .function: case .function:
@ -125,24 +130,24 @@ extension Node {
case .uuidDeprecated: case .uuidDeprecated:
fallthrough fallthrough
case .uuid: case .uuid:
return try! UUID(from: value).description return try! value.toUUID().description
case .md5: case .md5:
return "(MD5 binary data)" return "(MD5 binary data)"
case .userDefined: default:
return "(user defined binary data)" return "(unknown binary data))"
} }
case .undefined: case .undefined:
return "undefined" return "undefined"
case let .objectId(value): case let .objectID(value):
return value.description return value.description
case let .bool(value): case let .bool(value):
return value.description return value.description
case let .datetime(value): case let .datetime(value):
return value.description return Node.dateFormatter.string(from: value)
case .null: case .null:
return "null" return "null"
case let .regex(value): case let .regex(value):
return try! NSRegularExpression(from: value).description return value.pattern
case let .dbPointer(value): case let .dbPointer(value):
return "\(value.ref)(\(value.id))" return "\(value.ref)(\(value.id))"
case let .symbol(value): case let .symbol(value):
@ -154,6 +159,7 @@ extension Node {
case let .int32(value): case let .int32(value):
return value.description return value.description
case let .timestamp(value): case let .timestamp(value):
// todo: this needs to include the timestamp increment
let date = Date(timeIntervalSince1970: TimeInterval(value.timestamp)) let date = Date(timeIntervalSince1970: TimeInterval(value.timestamp))
return Node.dateFormatter.string(from: date) return Node.dateFormatter.string(from: date)
case let .int64(value): case let .int64(value):
@ -178,9 +184,7 @@ extension Node {
case .array(_): case .array(_):
return "Array" return "Array"
case let .binary(value): case let .binary(value):
switch Binary.Subtype(rawValue: value.subtype) { switch value.subtype {
case nil:
return "Unknown binary data"
case .generic: case .generic:
return "Generic binary data" return "Generic binary data"
case .function: case .function:
@ -193,12 +197,12 @@ extension Node {
return "UUID" return "UUID"
case .md5: case .md5:
return "MD5 hash" return "MD5 hash"
case .userDefined: default:
return "User defined binary data" return "Unknown binary data"
} }
case .undefined: case .undefined:
return "Undefined" return "Undefined"
case .objectId(_): case .objectID(_):
return "ObjectId" return "ObjectId"
case .bool(_): case .bool(_):
return "Bool" return "Bool"

View File

@ -0,0 +1,54 @@
//
// StatusManager.swift
// MongoView
//
// Created by Shadowfacts on 7/7/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import Foundation
struct StatusManager {
private(set) var statuses = [Category: Status]()
var mostRelevant: Status? {
for category in Category.sortedByPriority {
if let status = statuses[category] {
return status
}
}
return nil
}
mutating func set(_ message: String, for category: Category, override: Bool = false) {
statuses[category] = Status(message: message, category: category)
if override {
for other in Category.allCases where other < category {
remove(for: other)
}
}
}
mutating func remove(for category: Category) {
statuses.removeValue(forKey: category)
}
enum Category: Int, Comparable, CaseIterable {
case document
case query
case connection
static let sortedByPriority = allCases.sorted()
static func < (lhs: StatusManager.Category, rhs: StatusManager.Category) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}
struct Status {
let message: String
let category: Category
let timestamp = Date()
}
}

View File

@ -23,6 +23,7 @@ fileprivate let identifierStarts: CharacterSet = {
}() }()
fileprivate let operators = CharacterSet(charactersIn: "+-*/<>=") fileprivate let operators = CharacterSet(charactersIn: "+-*/<>=")
fileprivate let expressionEnds = CharacterSet(charactersIn: ",]});") fileprivate let expressionEnds = CharacterSet(charactersIn: ",]});")
fileprivate let keywords = ["null", "true", "false"]
class JavaScriptHighlighter { class JavaScriptHighlighter {
private var text: String! private var text: String!
@ -244,6 +245,9 @@ class JavaScriptHighlighter {
while let char = peek(), identifiers.contains(char) { while let char = peek(), identifiers.contains(char) {
consume() consume()
} }
let identifier = text[identifierStart..<currentIndex]
let token: TokenType = keywords.contains(String(identifier)) ? .keyword : .identifier
emit(token: token, range: range(from: identifierStart, to: currentIndex))
print("Identifier: '\(text[identifierStart..<currentIndex])'") print("Identifier: '\(text[identifierStart..<currentIndex])'")
} }
@ -452,6 +456,7 @@ class JavaScriptHighlighter {
extension JavaScriptHighlighter { extension JavaScriptHighlighter {
enum TokenType { enum TokenType {
case identifier case identifier
case keyword
case punctuation case punctuation
case number case number
case string(Unicode.Scalar) case string(Unicode.Scalar)
@ -463,9 +468,9 @@ extension JavaScriptHighlighter {
case .number: case .number:
return .systemBlue return .systemBlue
case .punctuation: case .punctuation:
return .systemTeal
case .identifier:
return nil return nil
case .keyword, .identifier:
return .systemTeal
} }
} }
} }

View File

@ -0,0 +1,127 @@
//
// DatabaseCollectionListViewController.swift
// MongoView
//
// Created by Shadowfacts on 8/11/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import Cocoa
import NIO
import Combine
class DatabaseCollectionListViewController: NSViewController {
@IBOutlet weak var outlineView: NSOutlineView!
let mongoController: MongoController
let selectedCollection = PassthroughSubject<DatabaseCollection, Never>()
private var databaseCollections: [DatabaseCollections] = []
init(mongoController: MongoController) {
self.mongoController = mongoController
super.init(nibName: "DatabaseCollectionListViewController", bundle: .main)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
mongoController.client.listDatabaseNames().flatMap { (databaseNames) -> EventLoopFuture<[DatabaseCollections]> in
let futures = databaseNames.map { (name: String) -> EventLoopFuture<DatabaseCollections> in
let db = self.mongoController.client.db(name)
return db.listCollectionNames().map { (collectionNames: [String]) -> DatabaseCollections in
let sortedNames = collectionNames.sorted()
return DatabaseCollections(database: name, collections: sortedNames)
}
}
return EventLoopFuture.whenAllSucceed(futures, on: futures.first!.eventLoop)
}.whenComplete { [weak self] (res) in
guard let self = self else { return }
switch res {
case let .success(databaseCollections):
let sortedCollections = databaseCollections.sorted(by: { $0.database < $1.database })
self.databaseCollections = sortedCollections
DispatchQueue.main.async {
self.outlineView.reloadData()
}
case let .failure(error):
fatalError("error getting database names: \(error)")
}
}
outlineView.dataSource = self
outlineView.delegate = self
}
@IBAction func outlineCellDoubleClicked(_ sender: Any) {
let item = outlineView.item(atRow: outlineView.clickedRow)!
if item is DatabaseCollections {
if outlineView.isItemExpanded(item) {
outlineView.collapseItem(item)
} else {
outlineView.expandItem(item)
}
} else if let collection = item as? DatabaseCollection {
selectedCollection.send(collection)
}
}
}
extension DatabaseCollectionListViewController: NSOutlineViewDataSource {
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if item == nil {
return databaseCollections.count
} else if let database = item as? DatabaseCollections {
return database.collections.count
} else {
return 0
}
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
return item is DatabaseCollections
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if item == nil {
return databaseCollections[index]
} else if let databaseCollections = item as? DatabaseCollections {
let collection = databaseCollections.collections[index]
return DatabaseCollection(database: databaseCollections.database, name: collection)
} else {
fatalError()
}
}
}
extension DatabaseCollectionListViewController: NSOutlineViewDelegate {
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
if let database = item as? DatabaseCollections {
let cell = outlineView.makeView(withIdentifier: .databaseNameCell, owner: nil) as! NSTableCellView
cell.textField!.stringValue = database.database
cell.textField!.isEditable = false
return cell
} else if let collection = item as? DatabaseCollection {
let cell = outlineView.makeView(withIdentifier: .collectionNameCell, owner: nil) as! NSTableCellView
cell.textField!.stringValue = collection.name
cell.textField!.isEditable = false
return cell
} else {
fatalError()
}
}
}
extension NSUserInterfaceItemIdentifier {
static let databaseNameCell = NSUserInterfaceItemIdentifier(rawValue: "DatabaseNameCell")
static let collectionNameCell = NSUserInterfaceItemIdentifier(rawValue: "CollectionNameCell")
}

View File

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16097" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16097"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="DatabaseCollectionListViewController" customModule="MongoView" customModuleProvider="target">
<connections>
<outlet property="outlineView" destination="PYy-rp-1sY" id="Ag4-pd-TtB"/>
<outlet property="view" destination="CSW-JG-jb6" id="wPe-4V-JEk"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<scrollView autohidesScrollers="YES" horizontalLineScroll="26" horizontalPageScroll="10" verticalLineScroll="26" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="CSW-JG-jb6">
<rect key="frame" x="0.0" y="0.0" width="150" height="400"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" drawsBackground="NO" id="uX8-zh-NZd">
<rect key="frame" x="1" y="1" width="148" height="398"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" multipleSelection="NO" autosaveColumns="NO" rowHeight="24" rowSizeStyle="automatic" viewBased="YES" indentationPerLevel="16" outlineTableColumn="nUE-Qx-Fdi" id="PYy-rp-1sY">
<rect key="frame" x="0.0" y="0.0" width="148" height="398"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn width="145" minWidth="16" maxWidth="1000" id="nUE-Qx-Fdi">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="qEZ-z5-ScP">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="DatabaseNameCell" id="qxw-S5-5TO">
<rect key="frame" x="1" y="1" width="145" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Rdr-4s-WEp">
<rect key="frame" x="0.0" y="1" width="145" height="14"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="HEADER CELL" id="m7l-6x-d1Y">
<font key="font" metaFont="smallSystemBold"/>
<color key="textColor" name="headerColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="Rdr-4s-WEp" id="uxa-OG-78l"/>
</connections>
</tableCellView>
<tableCellView identifier="CollectionNameCell" id="Jfx-os-pKH">
<rect key="frame" x="1" y="20" width="145" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3XC-L1-Mmo">
<rect key="frame" x="3" y="0.0" width="17" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" imageScaling="proportionallyDown" image="NSActionTemplate" id="w8x-qJ-eUa"/>
</imageView>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="22A-k5-6kG">
<rect key="frame" x="25" y="0.0" width="120" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="MvS-zU-uSK">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="imageView" destination="3XC-L1-Mmo" id="2te-tg-hnE"/>
<outlet property="textField" destination="22A-k5-6kG" id="opL-jf-aEN"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
<connections>
<action trigger="doubleAction" selector="outlineCellDoubleClicked:" target="-2" id="tK6-yr-CRA"/>
</connections>
</outlineView>
</subviews>
<nil key="backgroundColor"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="b0d-if-wLj">
<rect key="frame" x="1" y="119" width="238" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="vsz-HX-7re">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<point key="canvasLocation" x="-15" y="177"/>
</scrollView>
</objects>
<resources>
<image name="NSActionTemplate" width="14" height="14"/>
</resources>
</document>

View File

@ -1,214 +0,0 @@
//
// DatabaseViewController.swift
// MongoView
//
// Created by Shadowfacts on 1/10/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import Cocoa
import MongoSwift
import NIO
struct DatabaseCollections {
let database: String
let collections: [String]
}
struct DatabaseCollection {
let database: String
let name: String
}
class DatabaseViewController: NSViewController {
@IBOutlet weak var collectionsOutlineView: NSOutlineView!
@IBOutlet weak var detailContainerView: NSView!
let mongoController: MongoController
private var databaseCollections: [DatabaseCollections] = []
private var selectedCollection: DatabaseCollection?
private var placeholderLabel: NSTextField?
private var queryViewController: QueryViewController?
init(mongoController: MongoController) {
self.mongoController = mongoController
super.init(nibName: "DatabaseViewController", bundle: .main)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
mongoController.client.listDatabaseNames().flatMap { (databaseNames) -> EventLoopFuture<[DatabaseCollections]> in
let futures = databaseNames.map { (name: String) -> EventLoopFuture<DatabaseCollections> in
let db = self.mongoController.client.db(name)
return db.listCollectionNames().map { (collectionNames: [String]) -> DatabaseCollections in
let sortedNames = collectionNames.sorted()
return DatabaseCollections(database: name, collections: sortedNames)
}
}
return EventLoopFuture.whenAllSucceed(futures, on: futures.first!.eventLoop)
}.whenComplete { [weak self] (res) in
guard let self = self else { return }
switch res {
case let .success(databaseCollections):
let sortedCollections = databaseCollections.sorted(by: { $0.database < $1.database })
self.databaseCollections = sortedCollections
DispatchQueue.main.async {
self.collectionsOutlineView.reloadData()
}
case let .failure(error):
fatalError("error getting database names: \(error)")
}
}
collectionsOutlineView.dataSource = self
collectionsOutlineView.delegate = self
collectionsOutlineView.target = self
collectionsOutlineView.doubleAction = #selector(cellDoubleClicked)
updateDetailView()
}
func showCollection(_ collection: DatabaseCollection) {
selectedCollection = collection
updateDetailView()
}
private func updateDetailView() {
if let collection = selectedCollection {
if let placeholderLabel = placeholderLabel {
placeholderLabel.removeFromSuperview()
}
if let oldQueryViewController = self.queryViewController {
oldQueryViewController.view.removeFromSuperview()
oldQueryViewController.removeFromParent()
}
let queryViewController = QueryViewController(mongoController: mongoController, collection: collection)
self.queryViewController = queryViewController
queryViewController.view.translatesAutoresizingMaskIntoConstraints = false
addChild(queryViewController)
detailContainerView.addSubview(queryViewController.view)
NSLayoutConstraint.activate([
queryViewController.view.leadingAnchor.constraint(equalTo: detailContainerView.leadingAnchor),
queryViewController.view.trailingAnchor.constraint(equalTo: detailContainerView.trailingAnchor),
queryViewController.view.topAnchor.constraint(equalTo: detailContainerView.topAnchor),
queryViewController.view.bottomAnchor.constraint(equalTo: detailContainerView.bottomAnchor)
])
self.title = queryViewController.title
} else {
if let queryViewController = queryViewController {
queryViewController.view.removeFromSuperview()
queryViewController.removeFromParent()
}
if self.placeholderLabel == nil {
let label = NSTextField(labelWithString: "Select a collection to begin")
self.placeholderLabel = label
label.translatesAutoresizingMaskIntoConstraints = false
detailContainerView.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: detailContainerView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: detailContainerView.centerYAnchor)
])
self.title = "No Query"
}
}
}
@objc func cellDoubleClicked() {
let item = collectionsOutlineView.item(atRow: collectionsOutlineView.clickedRow)!
if item is DatabaseCollections {
if collectionsOutlineView.isItemExpanded(item) {
collectionsOutlineView.collapseItem(item)
} else {
collectionsOutlineView.expandItem(item)
}
} else if let collection = item as? DatabaseCollection {
// only open a new window/tab if our own query has changed from the default, otherwise replace our query controller
if let queryViewController = queryViewController, queryViewController.hasFilterChanged {
(NSApplication.shared.delegate as! AppDelegate).newWindow(mongoController: mongoController, collection: collection)
} else {
self.selectedCollection = collection
updateDetailView()
}
}
}
@IBAction func refresh(_ sender: Any) {
queryViewController?.refresh()
}
}
extension DatabaseViewController: NSMenuItemValidation {
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
if menuItem.action == #selector(refresh(_:)) {
return queryViewController != nil
}
return true
}
}
extension DatabaseViewController: NSOutlineViewDataSource {
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if item == nil {
return databaseCollections.count
} else if let database = item as? DatabaseCollections {
return database.collections.count
} else {
return 0
}
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
return item is DatabaseCollections
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if item == nil {
return databaseCollections[index]
} else if let databaseCollections = item as? DatabaseCollections {
let collection = databaseCollections.collections[index]
return DatabaseCollection(database: databaseCollections.database, name: collection)
} else {
fatalError()
}
}
}
extension DatabaseViewController: NSOutlineViewDelegate {
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
if let database = item as? DatabaseCollections {
let cell = outlineView.makeView(withIdentifier: .databaseNameCell, owner: nil) as! NSTableCellView
cell.textField!.stringValue = database.database
cell.textField!.isEditable = false
return cell
} else if let collection = item as? DatabaseCollection {
let cell = outlineView.makeView(withIdentifier: .collectionNameCell, owner: nil) as! NSTableCellView
cell.textField!.stringValue = collection.name
cell.textField!.isEditable = false
return cell
} else {
fatalError()
}
}
}
extension NSUserInterfaceItemIdentifier {
static let databaseNameCell = NSUserInterfaceItemIdentifier(rawValue: "DatabaseNameCell")
static let collectionNameCell = NSUserInterfaceItemIdentifier(rawValue: "CollectionNameCell")
}

View File

@ -1,143 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15702" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15702"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="DatabaseViewController" customModule="MongoView" customModuleProvider="target">
<connections>
<outlet property="collectionsOutlineView" destination="V8b-zo-g9y" id="MHN-98-SLo"/>
<outlet property="detailContainerView" destination="XEU-4D-Mdl" id="TwG-BX-2Gv"/>
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="Hz6-mo-xeY">
<rect key="frame" x="0.0" y="0.0" width="963" height="618"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<splitView arrangesAllSubviews="NO" dividerStyle="thin" vertical="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5Fg-Gm-qda">
<rect key="frame" x="0.0" y="0.0" width="963" height="618"/>
<subviews>
<customView id="oWj-Ph-gg6">
<rect key="frame" x="0.0" y="0.0" width="258" height="618"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-Xn-ruZ">
<rect key="frame" x="0.0" y="0.0" width="258" height="618"/>
<clipView key="contentView" drawsBackground="NO" id="x7X-dI-jkR">
<rect key="frame" x="1" y="1" width="256" height="616"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" viewBased="YES" indentationPerLevel="16" outlineTableColumn="jbA-H8-mVM" id="V8b-zo-g9y">
<rect key="frame" x="0.0" y="0.0" width="256" height="616"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn width="253" minWidth="16" maxWidth="1000" id="jbA-H8-mVM">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<font key="font" metaFont="label" size="11"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="xnp-1R-i8D">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="DatabaseNameCell" id="rKw-cV-n1d">
<rect key="frame" x="1" y="1" width="253" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FFr-3D-mui">
<rect key="frame" x="0.0" y="1" width="253" height="14"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="HEADER CELL" id="cEl-mJ-9oh">
<font key="font" metaFont="smallSystemBold"/>
<color key="textColor" name="headerColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="FFr-3D-mui" id="5i3-fV-eYZ"/>
</connections>
</tableCellView>
<tableCellView identifier="CollectionNameCell" id="hIt-06-qWa">
<rect key="frame" x="1" y="20" width="253" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ccM-b1-d7j">
<rect key="frame" x="3" y="0.0" width="17" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" imageScaling="proportionallyDown" image="NSActionTemplate" id="kCZ-pa-Nqs"/>
</imageView>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Lxc-CX-ZCN">
<rect key="frame" x="25" y="0.0" width="228" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="5CJ-WG-qTe">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="imageView" destination="ccM-b1-d7j" id="otw-h0-Zyz"/>
<outlet property="textField" destination="Lxc-CX-ZCN" id="cW2-4u-Tl2"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
</outlineView>
</subviews>
<nil key="backgroundColor"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="all-QE-bCD">
<rect key="frame" x="1" y="119" width="238" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="x72-Cx-DrL">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="h6f-Xn-ruZ" secondAttribute="trailing" id="CYf-mb-0kx"/>
<constraint firstItem="h6f-Xn-ruZ" firstAttribute="top" secondItem="oWj-Ph-gg6" secondAttribute="top" id="OHe-aL-LhC"/>
<constraint firstItem="h6f-Xn-ruZ" firstAttribute="leading" secondItem="oWj-Ph-gg6" secondAttribute="leading" id="d9N-28-6qQ"/>
<constraint firstAttribute="bottom" secondItem="h6f-Xn-ruZ" secondAttribute="bottom" id="fmp-A8-teG"/>
</constraints>
</customView>
<customView fixedFrame="YES" id="XEU-4D-Mdl">
<rect key="frame" x="259" y="0.0" width="704" height="618"/>
<autoresizingMask key="autoresizingMask"/>
</customView>
</subviews>
<holdingPriorities>
<real value="250"/>
<real value="250"/>
</holdingPriorities>
</splitView>
</subviews>
<constraints>
<constraint firstItem="5Fg-Gm-qda" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="HRn-8t-EI3"/>
<constraint firstItem="5Fg-Gm-qda" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" id="Tco-te-GLJ"/>
<constraint firstAttribute="trailing" secondItem="5Fg-Gm-qda" secondAttribute="trailing" id="f5N-wp-15r"/>
<constraint firstAttribute="bottom" secondItem="5Fg-Gm-qda" secondAttribute="bottom" id="l7G-Jh-PBe"/>
</constraints>
<point key="canvasLocation" x="-8.5" y="361"/>
</customView>
</objects>
<resources>
<image name="NSActionTemplate" width="14" height="14"/>
</resources>
</document>

View File

@ -0,0 +1,17 @@
//
// DetailViewController.swift
// MongoView
//
// Created by Shadowfacts on 8/11/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import Cocoa
class DetailViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16097" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16097"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="DetailViewController" customModule="MongoView" customModuleProvider="target">
<connections>
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="Hz6-mo-xeY">
<rect key="frame" x="0.0" y="0.0" width="480" height="272"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PWc-n0-PoD">
<rect key="frame" x="156" y="128" width="169" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Select a collection to begin" id="8Vw-6z-chU">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="PWc-n0-PoD" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" id="0RT-Qd-Ehi"/>
<constraint firstItem="PWc-n0-PoD" firstAttribute="centerY" secondItem="Hz6-mo-xeY" secondAttribute="centerY" id="i20-B9-H29"/>
</constraints>
<point key="canvasLocation" x="140" y="154"/>
</customView>
</objects>
</document>

View File

@ -13,7 +13,7 @@ class EditDocumentViewController: NSViewController {
private(set) var mongoController: MongoController! private(set) var mongoController: MongoController!
private(set) var collection: DatabaseCollection! private(set) var collection: DatabaseCollection!
private(set) var document: Document! private(set) var document: BSONDocument!
var documentEdited: (() -> Void)? var documentEdited: (() -> Void)?
@ -22,7 +22,7 @@ class EditDocumentViewController: NSViewController {
@IBOutlet weak var cancelButton: NSButton! @IBOutlet weak var cancelButton: NSButton!
@IBOutlet weak var validateButton: NSButton! @IBOutlet weak var validateButton: NSButton!
init(mongoController: MongoController, collection: DatabaseCollection, document: Document) { init(mongoController: MongoController, collection: DatabaseCollection, document: BSONDocument) {
self.mongoController = mongoController self.mongoController = mongoController
self.collection = collection self.collection = collection
self.document = document self.document = document
@ -37,7 +37,10 @@ class EditDocumentViewController: NSViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
editorTextView.string = ExtendedJSON.prettify(document.extendedJSON) ?? document.extendedJSON let printer = JSONPrettyPrinter(options: .convertMongoObjects)
let extended = document.toExtendedJSONString()
editorTextView.string = (try? printer.prettify(extended)) ?? extended
editorTextView.isAutomaticQuoteSubstitutionEnabled = false editorTextView.isAutomaticQuoteSubstitutionEnabled = false
} }

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16097" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16097"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -57,17 +57,17 @@ Gw
</customSpacing> </customSpacing>
</stackView> </stackView>
<scrollView borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MlX-0S-w82"> <scrollView borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MlX-0S-w82">
<rect key="frame" x="8" y="37" width="464" height="227"/> <rect key="frame" x="0.0" y="37" width="480" height="235"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="egN-c7-XBK"> <clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="egN-c7-XBK">
<rect key="frame" x="0.0" y="0.0" width="449" height="227"/> <rect key="frame" x="0.0" y="0.0" width="465" height="235"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<textView importsGraphics="NO" richText="NO" verticallyResizable="YES" id="1aa-Vo-yPS" customClass="JavaScriptEditorView" customModule="MongoView" customModuleProvider="target"> <textView importsGraphics="NO" richText="NO" verticallyResizable="YES" id="1aa-Vo-yPS" customClass="JavaScriptEditorView" customModule="MongoView" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="449" height="227"/> <rect key="frame" x="0.0" y="0.0" width="465" height="235"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="449" height="227"/> <size key="minSize" width="465" height="235"/>
<size key="maxSize" width="465" height="10000000"/> <size key="maxSize" width="465" height="10000000"/>
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/> <color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
</textView> </textView>
@ -78,7 +78,7 @@ Gw
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</scroller> </scroller>
<scroller key="verticalScroller" verticalHuggingPriority="750" horizontal="NO" id="IDr-mZ-kqd"> <scroller key="verticalScroller" verticalHuggingPriority="750" horizontal="NO" id="IDr-mZ-kqd">
<rect key="frame" x="449" y="0.0" width="15" height="227"/> <rect key="frame" x="465" y="0.0" width="15" height="235"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</scroller> </scroller>
</scrollView> </scrollView>
@ -94,15 +94,15 @@ Gw
</button> </button>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstItem="MlX-0S-w82" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="8" id="BdG-le-KXB"/> <constraint firstItem="MlX-0S-w82" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" id="BdG-le-KXB"/>
<constraint firstItem="J9n-en-a5p" firstAttribute="top" secondItem="MlX-0S-w82" secondAttribute="bottom" constant="8" id="FvY-Be-Tdx"/> <constraint firstItem="J9n-en-a5p" firstAttribute="top" secondItem="MlX-0S-w82" secondAttribute="bottom" constant="8" id="FvY-Be-Tdx"/>
<constraint firstItem="J9n-en-a5p" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="8" id="HzC-6Y-gZG"/> <constraint firstItem="J9n-en-a5p" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="8" id="HzC-6Y-gZG"/>
<constraint firstAttribute="bottom" secondItem="xa0-UE-ywz" secondAttribute="bottom" constant="8" id="Ssc-Oa-bTe"/> <constraint firstAttribute="bottom" secondItem="xa0-UE-ywz" secondAttribute="bottom" constant="8" id="Ssc-Oa-bTe"/>
<constraint firstItem="xa0-UE-ywz" firstAttribute="top" secondItem="MlX-0S-w82" secondAttribute="bottom" constant="8" id="Uga-ZA-1b4"/> <constraint firstItem="xa0-UE-ywz" firstAttribute="top" secondItem="MlX-0S-w82" secondAttribute="bottom" constant="8" id="Uga-ZA-1b4"/>
<constraint firstAttribute="trailing" secondItem="xa0-UE-ywz" secondAttribute="trailing" constant="8" id="i4O-th-zRP"/> <constraint firstAttribute="trailing" secondItem="xa0-UE-ywz" secondAttribute="trailing" constant="8" id="i4O-th-zRP"/>
<constraint firstAttribute="bottom" secondItem="J9n-en-a5p" secondAttribute="bottom" constant="8" id="lZs-Qs-8gq"/> <constraint firstAttribute="bottom" secondItem="J9n-en-a5p" secondAttribute="bottom" constant="8" id="lZs-Qs-8gq"/>
<constraint firstItem="MlX-0S-w82" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="8" id="m99-4c-vcx"/> <constraint firstItem="MlX-0S-w82" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="m99-4c-vcx"/>
<constraint firstAttribute="trailing" secondItem="MlX-0S-w82" secondAttribute="trailing" constant="8" id="rYV-TB-qaV"/> <constraint firstAttribute="trailing" secondItem="MlX-0S-w82" secondAttribute="trailing" id="rYV-TB-qaV"/>
</constraints> </constraints>
<point key="canvasLocation" x="140" y="154"/> <point key="canvasLocation" x="140" y="154"/>
</customView> </customView>

View File

@ -0,0 +1,105 @@
//
// MainSplitViewController.swift
// MongoView
//
// Created by Shadowfacts on 8/11/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import Cocoa
import Combine
class MainSplitViewController: NSSplitViewController {
let mongoController: MongoController
let initialCollection: DatabaseCollection?
private var detailViewController = DetailViewController()
private var currentQueryViewController: QueryViewController? {
detailViewController.children.first as? QueryViewController
}
private var collectionSubscriber: Cancellable?
private var titleObservation: NSKeyValueObservation?
init(mongoController: MongoController, initialCollection: DatabaseCollection?) {
self.mongoController = mongoController
self.initialCollection = initialCollection
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let listVC = DatabaseCollectionListViewController(mongoController: mongoController)
let sourceListItem = NSSplitViewItem(sidebarWithViewController: listVC)
let detailItem = NSSplitViewItem(viewController: detailViewController)
detailItem.maximumThickness = NSSplitViewItem.unspecifiedDimension
addSplitViewItem(sourceListItem)
addSplitViewItem(detailItem)
NSLayoutConstraint.activate([
sourceListItem.viewController.view.widthAnchor.constraint(greaterThanOrEqualToConstant: 200),
sourceListItem.viewController.view.widthAnchor.constraint(lessThanOrEqualToConstant: 500),
detailItem.viewController.view.widthAnchor.constraint(greaterThanOrEqualToConstant: 200),
])
collectionSubscriber = listVC.selectedCollection.sink(receiveValue: self.selectCollection)
if let initialCollection = initialCollection {
selectCollection(initialCollection)
}
}
private func selectCollection(_ collection: DatabaseCollection) {
if currentQueryViewController?.hasFilterChanged ?? false {
(NSApp.delegate as! AppDelegate).newWindow(mongoController: mongoController, collection: collection)
} else {
let queryVC = QueryViewController(mongoController: mongoController, collection: collection)
setDetailChild(queryVC)
}
}
private func setDetailChild(_ vc: NSViewController) {
if let child = detailViewController.children.first {
child.removeFromParent()
child.view.removeFromSuperview()
}
vc.view.translatesAutoresizingMaskIntoConstraints = false
detailViewController.addChild(vc)
detailViewController.view.addSubview(vc.view)
NSLayoutConstraint.activate([
detailViewController.view.leadingAnchor.constraint(equalTo: vc.view.leadingAnchor),
detailViewController.view.trailingAnchor.constraint(equalTo: vc.view.trailingAnchor),
detailViewController.view.topAnchor.constraint(equalTo: vc.view.topAnchor),
detailViewController.view.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor),
])
titleObservation = vc.observe(\.title, options: .initial, changeHandler: { [unowned self] (_, _) in
self.title = vc.title
})
}
@IBAction func refresh(_ sender: Any) {
currentQueryViewController?.refresh()
}
}
extension MainSplitViewController: NSMenuItemValidation {
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
if menuItem.action == #selector(refresh(_:)) {
return currentQueryViewController != nil
}
return true
}
}

View File

@ -8,6 +8,7 @@
import Cocoa import Cocoa
import MongoSwift import MongoSwift
import NIO
class QueryViewController: NSViewController { class QueryViewController: NSViewController {
@ -68,16 +69,9 @@ class QueryViewController: NSViewController {
} }
override func viewDidAppear() {
super.viewDidAppear()
view.window!.makeFirstResponder(outlineView)
}
func refresh(reload: Bool = true) { func refresh(reload: Bool = true) {
let filterText = filterTextView.string.trimmingCharacters(in: .whitespacesAndNewlines) let filterText = filterTextView.string.trimmingCharacters(in: .whitespacesAndNewlines)
let filter: Document let filter: BSONDocument
if !filterText.isEmpty, if !filterText.isEmpty,
let doc = ExtendedJSON.toDocument(filterText) { let doc = ExtendedJSON.toDocument(filterText) {
filter = doc filter = doc
@ -85,14 +79,24 @@ class QueryViewController: NSViewController {
filter = [:] filter = [:]
} }
let documents = try! mongoController.collection(collection).find(filter).all() mongoController.statusManager.set("Querying \(collection)...", for: .query, override: true)
rootNodes = documents.map { Node(document: $0) }
title = "\(self.collection.database).\(self.collection.name)" let collection = mongoController.collection(self.collection)
documentCountLabel.stringValue = "\(documents.count) document\(documents.count == 1 ? "" : "s")" collection.find(filter).flatMap { (cursor: MongoCursor) -> EventLoopFuture<[BSONDocument]> in
return cursor.toArray()
}.whenSuccess { (documents) in
DispatchQueue.main.async {
self.rootNodes = documents.map { Node(document: $0) }
self.title = self.collection.description
self.documentCountLabel.stringValue = "\(documents.count) document\(documents.count == 1 ? "" : "s")"
if reload { if reload {
outlineView.reloadData() self.outlineView.reloadData()
}
self.mongoController.statusManager.set("Queried \(self.collection)", for: .query, override: true)
}
} }
} }
@ -102,8 +106,8 @@ class QueryViewController: NSViewController {
alert.alertStyle = .warning alert.alertStyle = .warning
alert.messageText = "Confirm deletion" alert.messageText = "Confirm deletion"
alert.informativeText = "Are you sure you want to delete the document" alert.informativeText = "Are you sure you want to delete the document"
let id: ObjectId? let id: BSONObjectID?
if case let .objectId(docId) = doc["_id"] { if case let .objectID(docId) = doc["_id"] {
id = docId id = docId
alert.informativeText += " with id \(docId)" alert.informativeText += " with id \(docId)"
} else { } else {
@ -133,6 +137,7 @@ class QueryViewController: NSViewController {
return return
} }
self.refresh() self.refresh()
self.mongoController.statusManager.set("Deleted document", for: .document)
case let .failure(error): case let .failure(error):
let alert = NSAlert(error: error) let alert = NSAlert(error: error)
alert.beginSheetModal(for: self.view.window!, completionHandler: nil) alert.beginSheetModal(for: self.view.window!, completionHandler: nil)
@ -142,12 +147,33 @@ class QueryViewController: NSViewController {
} }
} }
@objc func outlineCellDoubleClicked() { private func nodeForCopying() -> Node? {
if let item = outlineView.item(atRow: outlineView.clickedRow) { if outlineView.clickedRow >= 0 {
if outlineView.isItemExpanded(item) { return outlineView.item(atRow: outlineView.clickedRow) as? Node
outlineView.collapseItem(item)
} else { } else {
outlineView.expandItem(item) return outlineView.item(atRow: outlineView.selectedRow) as? Node
}
}
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 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)
} }
} }
} }
@ -168,11 +194,69 @@ class QueryViewController: NSViewController {
return return
} }
let wc = EditDocumentWindowController(mongoController: mongoController, collection: collection, document: document) openEditWindow(document)
wc.documentEdited = { }
self.refresh()
@IBAction func copy(_ sender: Any) {
guard let node = nodeForCopying() else { return }
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(node.valueString, forType: .string)
// todo: support copying more specific types?
}
@IBAction func copyAsJSON(_ sender: Any) {
guard let node = nodeForCopying() else { return }
let doc: BSONDocument = ["value": node.value]
let ext = doc.toExtendedJSONString()
// toExtendedJSON returns `{ "value": <whatever> }`, drop the object wrapper
let str = String(ext.dropFirst(12).dropLast(2))
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)
}
}
} }
wc.showWindow(nil)
} }
} }
@ -184,6 +268,9 @@ extension QueryViewController: NSMenuItemValidation {
} else { } else {
return false return false
} }
} else if menuItem.action == #selector(copy(_:)) || menuItem.action == #selector(copyAsJSON(_:)) {
let node = nodeForCopying()
return node != nil && node!.isValueCopyable
} }
return true return true
} }
@ -239,7 +326,9 @@ extension QueryViewController: NSOutlineViewDelegate {
} else if tableColumn.identifier == .fieldValueColumn { } else if tableColumn.identifier == .fieldValueColumn {
let cell = outlineView.makeView(withIdentifier: .fieldValueCell, owner: nil) as! NSTableCellView let cell = outlineView.makeView(withIdentifier: .fieldValueCell, owner: nil) as! NSTableCellView
cell.textField!.stringValue = node.valueString 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 return cell
} else if tableColumn.identifier == .valueTypeColumn { } else if tableColumn.identifier == .valueTypeColumn {
let cell = outlineView.makeView(withIdentifier: .valueTypeCell, owner: nil) as! NSTableCellView let cell = outlineView.makeView(withIdentifier: .valueTypeCell, owner: nil) as! NSTableCellView
@ -261,3 +350,100 @@ extension NSUserInterfaceItemIdentifier {
static let valueTypeColumn = NSUserInterfaceItemIdentifier(rawValue: "ValueTypeCol") static let valueTypeColumn = NSUserInterfaceItemIdentifier(rawValue: "ValueTypeCol")
static let valueTypeCell = NSUserInterfaceItemIdentifier(rawValue: "ValueTypeCell") 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()
}
}
}

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16097" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16097"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -1139,18 +1139,31 @@
</customView> </customView>
<menu id="nL5-kg-dty"> <menu id="nL5-kg-dty">
<items> <items>
<menuItem title="Edit Document" id="0gS-XH-YDt"> <menuItem title="Edit Document" id="0gS-XH-YDt">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="editDocument:" target="-2" id="DP4-Tq-o5M"/> <action selector="editDocument:" target="-2" id="DP4-Tq-o5M"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Delete" id="nn0-ZF-eDZ"> <menuItem isSeparatorItem="YES" id="xG5-Uo-1aO"/>
<menuItem title="Delete…" id="nn0-ZF-eDZ">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="deleteNode:" target="-2" id="dgm-SC-hn5"/> <action selector="deleteNode:" target="-2" id="dgm-SC-hn5"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="giL-0Q-HTl"/>
<menuItem title="Copy Value" keyEquivalent="c" id="UuQ-79-VL3">
<connections>
<action selector="copy:" target="-2" id="HsR-MZ-ayB"/>
</connections>
</menuItem>
<menuItem title="Copy Value as JSON" id="5lo-Mc-kwv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="copyAsJSON:" target="-2" id="rb1-6U-LO5"/>
</connections>
</menuItem>
</items> </items>
<point key="canvasLocation" x="887" y="210"/> <point key="canvasLocation" x="887" y="210"/>
</menu> </menu>

View File

@ -19,7 +19,6 @@ class JavaScriptEditorView: NSTextView {
super.string super.string
} }
set { set {
isRehighlighting = true
super.string = newValue super.string = newValue
rehighlight() rehighlight()
} }

View File

@ -23,6 +23,7 @@ class WindowStatusView: NSView {
super.awakeFromNib() super.awakeFromNib()
(button.cell as! NSButtonCell).imageDimsWhenDisabled = false (button.cell as! NSButtonCell).imageDimsWhenDisabled = false
button.font = .monospacedDigitSystemFont(ofSize: 13, weight: .regular)
} }
func setText(_ text: String) { func setText(_ text: String) {

View File

@ -7,6 +7,7 @@
// //
import Cocoa import Cocoa
import Combine
class DatabaseWindowController: NSWindowController { class DatabaseWindowController: NSWindowController {
@ -19,7 +20,10 @@ class DatabaseWindowController: NSWindowController {
var mongoController: MongoController! var mongoController: MongoController!
var initialCollection: DatabaseCollection? var initialCollection: DatabaseCollection?
private var databaseViewController: DatabaseViewController! private var mainViewController: NSViewController!
private var statusChangeHandler: Cancellable?
private var connectionStatusChangeHandler: Cancellable?
convenience init() { convenience init() {
self.init(windowNibName: "DatabaseWindowController") self.init(windowNibName: "DatabaseWindowController")
@ -28,21 +32,26 @@ class DatabaseWindowController: NSWindowController {
override func windowDidLoad() { override func windowDidLoad() {
super.windowDidLoad() super.windowDidLoad()
if mongoController == nil { let setupMongo = mongoController == nil
if setupMongo {
mongoController = MongoController(connectionString: "mongodb://localhost:27017") mongoController = MongoController(connectionString: "mongodb://localhost:27017")
mongoController.setup()
} }
mongoController.statusDidChange.append({ [weak self] (status) in
guard let self = self else { return }
self.updateStatusText(status)
})
self.updateStatusText(mongoController.status)
databaseViewController = DatabaseViewController(mongoController: mongoController) statusChangeHandler = mongoController.$statusManager
contentViewController = databaseViewController .receive(on: DispatchQueue.main)
.sink { self.updateStatusText(manager: $0) }
if let initialCollection = initialCollection { if mongoController.client != nil {
databaseViewController.showCollection(initialCollection) initializeUI()
} else {
connectionStatusChangeHandler = mongoController.$status
.receive(on: DispatchQueue.main)
.filter { $0 == .connected }
.sink { (_) in self.initializeUI() }
}
if setupMongo {
mongoController.setup()
} }
titleObservation = observe(\.contentViewController?.title) { [unowned self] (_, _) in titleObservation = observe(\.contentViewController?.title) { [unowned self] (_, _) in
@ -60,19 +69,33 @@ class DatabaseWindowController: NSWindowController {
} }
private func updateWindowTitle() { private func updateWindowTitle() {
window?.title = databaseViewController.title ?? "MongoView" window?.title = mainViewController?.title ?? "MongoView"
} }
private func updateStatusText(_ status: MongoController.Status) { private func updateStatusText(manager: StatusManager) {
guard let statusView = self.statusView else { return } guard let statusView = self.statusView,
switch status { let mostRelevant = manager.mostRelevant else { return }
case .connecting:
statusView.setText("Connecting...") let dayComparisonResult = Calendar.current.compare(mostRelevant.timestamp, to: Date(), toGranularity: .day)
case .failed:
statusView.setText("Failed to connect") let formatter = DateFormatter()
case .success: if dayComparisonResult == .orderedSame {
statusView.setText("Connected to \(self.mongoController.connectionString)") formatter.dateStyle = .none
} else {
formatter.dateStyle = .short
} }
formatter.timeStyle = .medium
let timestamp = formatter.string(from: mostRelevant.timestamp)
let string = "\(mostRelevant.message) | \(timestamp)"
statusView.setText(string)
}
private func initializeUI() {
mainViewController = MainSplitViewController(mongoController: mongoController, initialCollection: initialCollection)
// otherwise the VC size uses the size from the nib, potentially changing the window size
mainViewController.view.frame = window!.contentLayoutRect
contentViewController = mainViewController
} }
} }
@ -126,7 +149,7 @@ extension DatabaseWindowController: NSToolbarDelegate {
item.label = "Refresh" item.label = "Refresh"
item.paletteLabel = "Refresh" item.paletteLabel = "Refresh"
item.target = self item.target = self
let button = NSButton(image: NSImage(named: NSImage.refreshTemplateName)!, target: nil, action: #selector(DatabaseViewController.refresh(_:))) let button = NSButton(image: NSImage(named: NSImage.refreshTemplateName)!, target: nil, action: #selector(MainSplitViewController.refresh(_:)))
button.bezelStyle = .texturedRounded button.bezelStyle = .texturedRounded
item.view = button item.view = button
return item return item

View File

@ -13,11 +13,11 @@ class EditDocumentWindowController: NSWindowController {
private(set) var mongoController: MongoController! private(set) var mongoController: MongoController!
private(set) var collection: DatabaseCollection! private(set) var collection: DatabaseCollection!
private(set) var mongoDocument: Document! private(set) var mongoDocument: BSONDocument!
var documentEdited: (() -> Void)? var documentEdited: (() -> Void)?
convenience init(mongoController: MongoController, collection: DatabaseCollection, document: Document) { convenience init(mongoController: MongoController, collection: DatabaseCollection, document: BSONDocument) {
self.init(windowNibName: "EditDocumentWindowController") self.init(windowNibName: "EditDocumentWindowController")
self.mongoController = mongoController self.mongoController = mongoController