├── .github └── workflows │ └── build_and_test.yml ├── .gitignore ├── .jazzy.json ├── DocumentationBuilder └── main.swift ├── Examples ├── PLRelationalBasics.playground │ ├── Contents.swift │ ├── contents.xcplayground │ ├── playground.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── timeline.xctimeline ├── PLRelationalExamples.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ ├── TodoApp-SwiftUI-iOS.xcscheme │ │ └── TodoApp-SwiftUI-macOS.xcscheme └── TodoApp-SwiftUI │ ├── Resources │ ├── iOS │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ └── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ └── macOS │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── Trash.imageset │ │ │ ├── Contents.json │ │ │ ├── trash.png │ │ │ └── trash@2x.png │ │ ├── Base.lproj │ │ └── Main.storyboard │ │ └── Preview Content │ │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── Sources │ ├── Shared │ │ ├── Identifiers.swift │ │ └── Model.swift │ ├── iOS │ │ ├── AppDelegate.swift │ │ ├── CheckButton.swift │ │ ├── ChecklistItemView.swift │ │ ├── ChecklistItemViewModel.swift │ │ ├── ChecklistView.swift │ │ ├── ChecklistViewModel.swift │ │ ├── ContentView.swift │ │ ├── ContentViewModel.swift │ │ ├── DetailView.swift │ │ ├── DetailViewModel.swift │ │ ├── SceneDelegate.swift │ │ ├── TagItemView.swift │ │ ├── TagItemViewModel.swift │ │ ├── TagsView.swift │ │ ├── TagsViewModel.swift │ │ └── TextView.swift │ └── macOS │ │ ├── AppDelegate.swift │ │ ├── ChecklistItemView.swift │ │ ├── ChecklistItemViewModel.swift │ │ ├── ChecklistView.swift │ │ ├── ChecklistViewModel.swift │ │ ├── ContentView.swift │ │ ├── ContentViewModel.swift │ │ ├── DetailView.swift │ │ ├── DetailViewModel.swift │ │ ├── EphemeralComboBox.swift │ │ └── TextView.swift │ └── Support │ ├── iOS │ └── Info.plist │ └── macOS │ ├── App.entitlements │ └── Info.plist ├── LICENSE ├── Legacy ├── Examples │ ├── BindableControlsApp │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── MainMenu.xib │ │ └── Info.plist │ ├── BindableControlsAppUITests │ │ ├── BindableControlsAppUITests.swift │ │ └── Info.plist │ ├── HelloWorldApp │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── MainMenu.xib │ │ ├── Info.plist │ │ └── ViewModel.swift │ ├── HelloWorldAppTests │ │ ├── HelloWorldAppTests.swift │ │ └── Info.plist │ ├── HelloWorldAppUITests │ │ ├── HelloWorldAppUITests.swift │ │ └── Info.plist │ ├── MiniVisualizer │ │ ├── AppDelegate.swift │ │ ├── ArrowView.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── MainMenu.xib │ │ ├── Info.plist │ │ ├── NSBezierPath+CGPath.swift │ │ ├── RelationView.swift │ │ ├── StageView.swift │ │ └── ViewModel.swift │ ├── RelationChangeApp │ │ ├── AppDelegate.swift │ │ ├── ArrowView.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── MainMenu.xib │ │ ├── Info.plist │ │ ├── NSBezierPath+CGPath.swift │ │ ├── RelationView.swift │ │ ├── ScrollingTextView.swift │ │ ├── TextView.swift │ │ └── ViewModel.swift │ ├── SearchApp │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── MainMenu.xib │ │ ├── Info.plist │ │ ├── ResultsListView.swift │ │ ├── SearchResult.swift │ │ └── ViewModel.swift │ ├── SearchAppTests │ │ ├── Info.plist │ │ └── SearchAppTests.swift │ ├── SearchAppUITests │ │ ├── Info.plist │ │ └── SearchAppUITests.swift │ ├── TodoApp │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ ├── ChecklistView.xib │ │ │ ├── DetailView.xib │ │ │ └── MainMenu.xib │ │ ├── ChecklistView.swift │ │ ├── ChecklistViewModel.swift │ │ ├── DetailView.swift │ │ ├── DetailViewModel.swift │ │ ├── Identifiers.swift │ │ ├── Info.plist │ │ ├── Model.swift │ │ ├── README.markdown │ │ ├── trash.png │ │ └── trash@2x.png │ ├── TodoClientApp │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ ├── ChecklistView.xib │ │ │ ├── DetailView.xib │ │ │ └── MainMenu.xib │ │ ├── ChecklistView.swift │ │ ├── ChecklistViewModel.swift │ │ ├── DetailView.swift │ │ ├── DetailViewModel.swift │ │ ├── Identifiers.swift │ │ ├── Info.plist │ │ ├── Model.swift │ │ ├── trash.png │ │ └── trash@2x.png │ ├── Visualizer │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── AsyncState.swift │ │ ├── Base.lproj │ │ │ ├── Document.xib │ │ │ └── MainMenu.xib │ │ ├── Box.swift │ │ ├── DocDatabase.swift │ │ ├── DocDatabaseError.swift │ │ ├── DocModel.swift │ │ ├── DocObject.swift │ │ ├── DocOutlineModel.swift │ │ ├── DocOutlineView.swift │ │ ├── Document.swift │ │ ├── EditorView.swift │ │ ├── Environment.swift │ │ ├── HistoryItem.swift │ │ ├── Identifiers.swift │ │ ├── Info.plist │ │ ├── ItemType.swift │ │ ├── NSColor255.swift │ │ ├── NSControlSwiftAction.swift │ │ ├── ObjectAttachment.swift │ │ ├── OutlineRowView.swift │ │ ├── RelationModel.swift │ │ ├── RelationViewModel.swift │ │ ├── ScrollView.swift │ │ ├── SidebarModel.swift │ │ ├── SidebarView.swift │ │ └── VisualizerColors.swift │ ├── VisualizerTests │ │ ├── Info.plist │ │ └── VisualizerTests.swift │ └── VisualizerUITests │ │ ├── Info.plist │ │ └── VisualizerUITests.swift ├── PLBindableControls │ └── Sources │ │ ├── AppKit │ │ ├── BackgroundView.swift │ │ ├── Button.swift │ │ ├── Checkbox.swift │ │ ├── ColorPanel.swift │ │ ├── ColorPickerView.swift │ │ ├── ComboBox.swift │ │ ├── ContextMenu.swift │ │ ├── EmphemeralComboBox.swift │ │ ├── EphemeralTextField.swift │ │ ├── ExtOutlineView.swift │ │ ├── ImageView.swift │ │ ├── Label.swift │ │ ├── ListView.swift │ │ ├── MenuItem.swift │ │ ├── PopUpButton.swift │ │ ├── ProgressIndicator.swift │ │ ├── SectionedTreeView.swift │ │ ├── SegmentedControl.swift │ │ ├── StepperView.swift │ │ ├── TableView.swift │ │ ├── TextField.swift │ │ ├── TextView.swift │ │ └── TreeView.swift │ │ ├── Info.plist │ │ ├── Shared │ │ ├── Bindable.swift │ │ ├── CheckState.swift │ │ ├── Color.swift │ │ ├── Image.swift │ │ └── TextProperty.swift │ │ └── UIKit │ │ ├── BarButtonItem.swift │ │ ├── ImageView.swift │ │ ├── Label.swift │ │ ├── ListView.swift │ │ ├── SectionedTreeView.swift │ │ ├── Slider.swift │ │ ├── Switch.swift │ │ ├── TextField.swift │ │ └── TreeView.swift ├── PLRelationalBinding │ ├── Sources │ │ ├── ArrayBinarySearch.swift │ │ ├── ArrayProperty.swift │ │ ├── ArraySafeIndex.swift │ │ ├── AsyncProperty.swift │ │ ├── AsyncPropertyOperations.swift │ │ ├── ChangeHandler.swift │ │ ├── CollectionElement.swift │ │ ├── CommonValue.swift │ │ ├── Info.plist │ │ ├── Property.swift │ │ ├── PropertyOperations.swift │ │ ├── RelationArrayProperty.swift │ │ ├── RelationAsyncProperty.swift │ │ ├── RelationChangeParts.swift │ │ ├── RelationExtractTyped.swift │ │ ├── RelationMutation.swift │ │ ├── RelationSignal.swift │ │ ├── RelationTreeProperty.swift │ │ ├── RelationValuePlistable.swift │ │ ├── RowCollectionElement.swift │ │ ├── Signal.swift │ │ ├── SignalOperations.swift │ │ ├── TreeProperty.swift │ │ ├── UndoManager.swift │ │ └── UndoableDatabase.swift │ └── Tests │ │ ├── ArrayBinarySearchTests.swift │ │ ├── AsyncPropertyOperationsTests.swift │ │ ├── AsyncPropertyTests.swift │ │ ├── BindingTestCase.swift │ │ ├── ChangeHandlerTests.swift │ │ ├── Info.plist │ │ ├── PropertyOperationsTests.swift │ │ ├── PropertyTests.swift │ │ ├── RelationArrayPropertyTests.swift │ │ ├── RelationAsyncPropertyTests.swift │ │ ├── RelationChangePartsTests.swift │ │ ├── RelationExtractTypedTests.swift │ │ ├── RelationMutationTests.swift │ │ ├── RelationSignalTests.swift │ │ ├── RelationTreePropertyTests.swift │ │ ├── SignalOperationsTests.swift │ │ └── UndoableDatabaseTests.swift └── PLRelationalLegacy.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ └── xcschemes │ ├── BindableControlsApp.xcscheme │ ├── HelloWorldApp.xcscheme │ ├── MiniVisualizer.xcscheme │ ├── PLBindableControls-iOS.xcscheme │ ├── PLBindableControls-macOS.xcscheme │ ├── PLRelationalBinding-iOS.xcscheme │ ├── PLRelationalBinding-macOS.xcscheme │ ├── RelationChangeApp.xcscheme │ ├── SearchApp.xcscheme │ ├── TodoApp.xcscheme │ └── TodoClientApp.xcscheme ├── PLRelational ├── Modules │ ├── SQLiteTokenizerAPI.h │ ├── module-sqlite3-include.h │ └── module.modulemap ├── PLRelational.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ ├── Documentation.xcscheme │ │ ├── DocumentationBuilder.xcscheme │ │ ├── PLRelational-iOS.xcscheme │ │ └── PLRelational-macOS.xcscheme ├── Sources │ ├── Async.swift │ ├── AsyncManager.swift │ ├── AsyncManagerObservation.swift │ ├── AsyncRunloopHack.swift │ ├── BinaryOperator.swift │ ├── BookmarkableGraph.swift │ ├── CachingRelation.swift │ ├── ChangeLoggingDatabase.swift │ ├── ChangeLoggingRelation.swift │ ├── CollectionConvenience.swift │ ├── ConcreteRelation.swift │ ├── ConsistentPRNG.swift │ ├── Crypto.swift │ ├── DJBHash.swift │ ├── DataCodec.swift │ ├── DeallocObservation.swift │ ├── Debug.swift │ ├── DictionaryConvenience.swift │ ├── DictionaryCreation.swift │ ├── DispatchContext.swift │ ├── ErrorConvenience.swift │ ├── GeneratorConcat.swift │ ├── IndexedSet.swift │ ├── Info.plist │ ├── InlineMutableData.swift │ ├── IntermediateRelation.swift │ ├── InternedUTF8String.swift │ ├── MemoryTableDatabase.swift │ ├── MemoryTableRelation.swift │ ├── MirrorChildren.swift │ ├── MutableBox.swift │ ├── MutableRelation.swift │ ├── MutableRelationCascadingDelete.swift │ ├── Mutex.swift │ ├── NSURLConvenience.swift │ ├── NegativeSet.swift │ ├── ObjectCasting.swift │ ├── ObjectDictionary.swift │ ├── ObjectMap.swift │ ├── ObjectSet.swift │ ├── ObserverSet.swift │ ├── OptionalFlatten.swift │ ├── PerThreadInstance.swift │ ├── PlistDatabase.swift │ ├── PlistDirectoryRelation.swift │ ├── PlistFileRelation.swift │ ├── Promise.swift │ ├── QueryOptimizer.swift │ ├── QueryPlanner.swift │ ├── QueryPlannerDump.swift │ ├── QueryRunner.swift │ ├── RWLock.swift │ ├── Relation.swift │ ├── RelationActivityLogging.swift │ ├── RelationChange.swift │ ├── RelationDefaultChangeObserverImplementation.swift │ ├── RelationDerivative.swift │ ├── RelationDifferentiator.swift │ ├── RelationDump.swift │ ├── RelationObserver.swift │ ├── RelationOperators.swift │ ├── RelationParts.swift │ ├── RelationRecursiveSelect.swift │ ├── RelationTextIndex.swift │ ├── RelationValue.swift │ ├── RemovableSet.swift │ ├── Result.swift │ ├── Row.swift │ ├── RowPlist.swift │ ├── SQLiteDatabase.swift │ ├── SQLiteRelation.swift │ ├── SelectExpression.swift │ ├── SelectExpressionAnalysis.swift │ ├── SelectExpressionOperators.swift │ ├── SequenceFind.swift │ ├── SetPowerSet.swift │ ├── SetSubtraction.swift │ ├── SimpleDatabase.swift │ ├── SmallInlineArray.swift │ ├── StoredDatabase.swift │ ├── StoredRelation.swift │ ├── StringConvenience.swift │ ├── StringPad.swift │ ├── TransactionalDatabase.swift │ ├── UnaryOperator.swift │ ├── UnsafePointerReadWrite.swift │ └── ValueWithDestructor.swift ├── Tests │ ├── AsyncManagerTests.swift │ ├── BookmarkableGraphTests.swift │ ├── ChangeLoggingDatabaseTests.swift │ ├── ChangeLoggingRelationTests.swift │ ├── DBTestCase.swift │ ├── Info.plist │ ├── InlineMutableDataTests.swift │ ├── InternedUTF8StringTests.swift │ ├── ObjectMapTests.swift │ ├── PlistDatabaseTests.swift │ ├── PlistDirectoryRelationTests.swift │ ├── PlistFileRelationTests.swift │ ├── QueryOptimizerTests.swift │ ├── QueryRunnerTests.swift │ ├── RelationDifferentiatorTests.swift │ ├── RelationObservationTests.swift │ ├── RelationRecursiveSelectTests.swift │ ├── RelationTests.swift │ ├── RelationTextIndexTests.swift │ ├── SQLiteDatabaseTests.swift │ └── TransactionalDatabaseTests.swift └── samplesummarize.swift ├── PLRelationalCombine ├── PLRelationalCombine.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ ├── PLRelationalCombine-iOS.xcscheme │ │ └── PLRelationalCombine-macOS.xcscheme ├── Sources │ ├── ArrayBinarySearch.swift │ ├── CancellableBag.swift │ ├── Info.plist │ ├── LogError.swift │ ├── RelationArrayReduce.swift │ ├── RelationChangePublisher.swift │ ├── RelationChangeSummary.swift │ ├── RelationMutation.swift │ ├── RelationValuePublisher.swift │ ├── TwoWay.swift │ ├── UndoManager.swift │ ├── UndoableDatabase.swift │ ├── WeakBind.swift │ └── WeakTwoWayBind.swift └── Tests │ ├── ArrayBinarySearchTests.swift │ ├── CombineTestCase.swift │ ├── Info.plist │ ├── RelationArrayReduceTests.swift │ ├── RelationChangePublisherTests.swift │ ├── RelationChangeSummaryTests.swift │ ├── RelationMutationTests.swift │ ├── RelationValuePublisherTests.swift │ ├── TwoWayTests.swift │ ├── UndoableDatabaseTests.swift │ ├── WeakBindTests.swift │ └── WeakTwoWayBindTests.swift └── README.md /.github/workflows/build_and_test.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | # Run this on pushes to `master`, or when a pull request is opened against `master` 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | test: 14 | 15 | name: Test on ${{ matrix.name }} 16 | runs-on: macOS-latest 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | # GitHub Actions requires a single row to be added to the build matrix. 22 | # For more info: 23 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions 24 | # This approach based on: 25 | # https://github.com/DaanDeMeyer/reproc/blob/master/.github/workflows/main.yml 26 | name: [ 27 | macOS, 28 | iOS 29 | ] 30 | 31 | include: 32 | - name: macOS 33 | destination: 'platform=macOS' 34 | 35 | - name: iOS 36 | destination: 'platform=iOS Simulator,name=iPhone 11,OS=13.1' 37 | 38 | steps: 39 | - name: Checkout 40 | uses: actions/checkout@v1 41 | 42 | - name: Select Xcode 11.1 43 | run: sudo xcode-select -switch /Applications/Xcode_11.1.app 44 | 45 | - name: PLRelational 46 | run: | 47 | cd PLRelational 48 | set -o pipefail && xcodebuild clean test -scheme PLRelational-${{ matrix.name }} -destination "${{ matrix.destination }}" | xcpretty 49 | 50 | - name: PLRelationalCombine 51 | run: | 52 | cd PLRelationalCombine 53 | set -o pipefail && xcodebuild clean test -scheme PLRelationalCombine-${{ matrix.name }} -destination "${{ matrix.destination }}" | xcpretty 54 | 55 | - name: Examples > TodoApp-SwiftUI 56 | run: | 57 | cd Examples 58 | set -o pipefail && xcodebuild clean build -scheme TodoApp-SwiftUI-${{ matrix.name }} -destination "${{ matrix.destination }}" | xcpretty 59 | 60 | - name: Legacy > PLRelationalBinding 61 | run: | 62 | cd Legacy 63 | set -o pipefail && xcodebuild clean test -scheme PLRelationalBinding-${{ matrix.name }} -destination "${{ matrix.destination }}" | xcpretty 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint.DS_Store 24 | .DS_Store 25 | -------------------------------------------------------------------------------- /Examples/PLRelationalBasics.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Examples/PLRelationalBasics.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/PLRelationalBasics.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/PLRelationalExamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/PLRelationalExamples.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Resources/iOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Resources/iOS/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Resources/iOS/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Resources/macOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Resources/macOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Resources/macOS/Assets.xcassets/Trash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "trash.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "trash@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Resources/macOS/Assets.xcassets/Trash.imageset/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plausiblelabs/plrelational/d904ea052b4e2b2db720733d3e63089c7d99e49b/Examples/TodoApp-SwiftUI/Resources/macOS/Assets.xcassets/Trash.imageset/trash.png -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Resources/macOS/Assets.xcassets/Trash.imageset/trash@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plausiblelabs/plrelational/d904ea052b4e2b2db720733d3e63089c7d99e49b/Examples/TodoApp-SwiftUI/Resources/macOS/Assets.xcassets/Trash.imageset/trash@2x.png -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Resources/macOS/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/Shared/Identifiers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import PLRelational 8 | 9 | /// Base class for our identifier value types. All of our identifiers use UUIDs 10 | /// under the hood and we have conveniences for conversion to/from `RelationValue`. 11 | class BaseID { 12 | fileprivate let uuid: String 13 | 14 | init() { 15 | self.uuid = UUID().uuidString 16 | } 17 | 18 | init(_ stringValue: String) { 19 | self.uuid = stringValue 20 | } 21 | 22 | init(_ relationValue: RelationValue) { 23 | self.uuid = relationValue.get()! 24 | } 25 | 26 | var relationValue: RelationValue { 27 | return uuid.relationValue 28 | } 29 | } 30 | 31 | /// Identifier type for rows in the `Item` relation. 32 | class ItemID: BaseID, Equatable, Hashable { 33 | /// Shorthand for extracting an `ItemID` from an `Item` row. 34 | convenience init(_ row: Row) { 35 | self.init(row[Item.id]) 36 | } 37 | 38 | static func ==(lhs: ItemID, rhs: ItemID) -> Bool { 39 | return lhs.uuid == rhs.uuid 40 | } 41 | 42 | func hash(into hasher: inout Hasher) { 43 | hasher.combine(uuid) 44 | } 45 | } 46 | 47 | /// Identifier type for rows in the `Tag` relation. 48 | class TagID: BaseID, Equatable, Hashable { 49 | /// Shorthand for extracting a `TagID` from an `Tag` row. 50 | convenience init(_ row: Row) { 51 | self.init(row[Tag.id]) 52 | } 53 | 54 | static func ==(lhs: TagID, rhs: TagID) -> Bool { 55 | return lhs.uuid == rhs.uuid 56 | } 57 | 58 | func hash(into hasher: inout Hasher) { 59 | hasher.combine(uuid) 60 | } 61 | } 62 | 63 | /// Conforming to this protocol allows us to use `ItemID` and `TagID` directly 64 | /// in row initializers and in select expressions without having to explicitly 65 | /// convert to `RelationValue`. 66 | extension BaseID: SelectExpressionConstantValue {} 67 | 68 | extension BaseID: CustomStringConvertible { 69 | var description: String { 70 | return "\(String(describing: type(of: self)))(\(uuid))" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/iOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | @UIApplicationMain 9 | class AppDelegate: UIResponder, UIApplicationDelegate { 10 | 11 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 12 | return true 13 | } 14 | 15 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 16 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 17 | } 18 | 19 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/iOS/CheckButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct CheckButton: View { 9 | 10 | let isOn: Binding 11 | 12 | var body: some View { 13 | let checkName = isOn.wrappedValue ? 14 | "checkmark.circle" : "circle" 15 | return Image(systemName: checkName) 16 | .imageScale(.large) 17 | .frame(minWidth: 28, maxHeight: .infinity) 18 | .onTapGesture { self.isOn.wrappedValue.toggle() } 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/iOS/ChecklistItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct ChecklistItemView: View { 9 | 10 | static let rowHeight: CGFloat = 34 11 | 12 | @ObservedObject private var model: ChecklistItemViewModel 13 | 14 | init(model: ChecklistItemViewModel) { 15 | self.model = model 16 | } 17 | 18 | var body: some View { 19 | HStack(alignment: .center) { 20 | CheckButton(isOn: $model.completed) 21 | VStack(alignment: .leading) { 22 | Text(model.title) 23 | .lineLimit(1) 24 | .frame(maxWidth: .infinity, alignment: .leading) 25 | 26 | if !model.tags.isEmpty { 27 | Text(model.tags) 28 | .lineLimit(1) 29 | .font(.system(size: 11)) 30 | .foregroundColor(Color.secondary) 31 | .frame(maxWidth: .infinity, alignment: .leading) 32 | } 33 | } 34 | .frame(maxWidth: .infinity, alignment: .topLeading) 35 | } 36 | .frame(minHeight: ChecklistItemView.rowHeight) 37 | } 38 | } 39 | 40 | struct ChecklistItemView_Previews: PreviewProvider { 41 | static var previews: some View { 42 | let (model, itemIds) = modelForPreviewWithIds() 43 | let viewModel = ChecklistItemViewModel(model: model, item: ChecklistItem(id: itemIds[0], title: "Item 1", created: "", completed: "")) 44 | return ChecklistItemView(model: viewModel) 45 | .padding() 46 | .previewLayout(.fixed(width: 300, height: ChecklistItemView.rowHeight)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/iOS/ChecklistItemViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Combine 7 | import SwiftUI 8 | import PLRelational 9 | import PLRelationalCombine 10 | 11 | final class ChecklistItemViewModel: ElementViewModel, Identifiable, ObservableObject { 12 | 13 | private let model: Model 14 | 15 | var item: ChecklistItem { 16 | didSet { 17 | // REQ-4 18 | // Keep the list item title up to date. Note that we could have set up 19 | // a binding in `init` below similar to what we do for the tags label, 20 | // but using `didSet` here demonstrates another approach. Since the 21 | // `reduce(to:)` will see an update every time the title gets changed 22 | // by the detail view, and since it already sets the updated view model's 23 | // `item`, we can just update `title` here. 24 | self.title = item.title 25 | } 26 | } 27 | var element: ChecklistItem { item } 28 | var id: ItemID { item.id } 29 | 30 | @TwoWay(onSet: .commit) var completed: Bool = false 31 | @Published var title: String 32 | @Published var tags: String 33 | 34 | private var cancellableBag = CancellableBag() 35 | 36 | init(model: Model, item: ChecklistItem) { 37 | self.model = model 38 | self.item = item 39 | self.title = item.title 40 | self.tags = "" 41 | 42 | // REQ-3 43 | // Each to-do item should have a checkbox showing its completion status. 44 | // This is a two-way property that is backed by UndoableDatabase. 45 | model.items 46 | .select(Item.id *== id) 47 | .project(Item.completed) 48 | .bind(to: \._completed, on: self, strategy: model.itemCompleted()) 49 | .store(in: &cancellableBag) 50 | 51 | // REQ-5 52 | // Each to-do item should have a string containing the 53 | // list of tags for that item. 54 | self.model 55 | .tagsString(for: id) 56 | .bind(to: \.tags, on: self) 57 | .store(in: &cancellableBag) 58 | } 59 | 60 | deinit { 61 | cancellableBag.cancel() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/iOS/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct ContentView: View { 9 | 10 | @ObservedObject private var model: ContentViewModel 11 | 12 | init(model: ContentViewModel) { 13 | self.model = model 14 | } 15 | 16 | var body: some View { 17 | NavigationView { 18 | ChecklistView(model: model.checklistViewModel) 19 | .navigationBarTitle("To Do", displayMode: .inline) 20 | // TODO: On iPad, the following would be the preferred way 21 | // of having a split master/detail view, but for now we'll 22 | // focus on iPhone 23 | // DetailView(model: model.checklistViewModel.detailViewModel) 24 | } 25 | } 26 | } 27 | 28 | struct ContentView_Previews: PreviewProvider { 29 | static var previews: some View { 30 | let model = modelForPreview() 31 | let viewModel = ContentViewModel(model: model) 32 | return Group { 33 | ContentView(model: viewModel) 34 | .previewDevice(PreviewDevice(rawValue: "iPhone 8")) 35 | .previewDisplayName("iPhone 8") 36 | 37 | // ContentView(model: viewModel) 38 | // .previewDevice(PreviewDevice(rawValue: "iPad Pro (9.7-inch)")) 39 | // .previewDisplayName("iPad Pro") 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/iOS/ContentViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Combine 7 | import SwiftUI 8 | import PLRelational 9 | import PLRelationalCombine 10 | 11 | final class ContentViewModel: ObservableObject { 12 | 13 | private let model: Model 14 | 15 | let checklistViewModel: ChecklistViewModel 16 | 17 | private var cancellableBag = CancellableBag() 18 | 19 | init(model: Model) { 20 | self.model = model 21 | self.checklistViewModel = ChecklistViewModel(model: model) 22 | } 23 | 24 | deinit { 25 | cancellableBag.cancel() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/iOS/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import UIKit 7 | import SwiftUI 8 | import PLRelationalCombine 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | var contentViewModel: ContentViewModel! 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Create the SwiftUI view that provides the window contents 17 | // TODO: Specify path for persistence 18 | let undoManager = PLRelationalCombine.UndoManager() 19 | let model = Model(undoManager: undoManager, path: nil) 20 | if !model.dbAlreadyExisted { 21 | _ = model.addDefaultData() 22 | } 23 | contentViewModel = ContentViewModel(model: model) 24 | let contentView = ContentView(model: contentViewModel) 25 | 26 | // Use a UIHostingController as window root view controller 27 | if let windowScene = scene as? UIWindowScene { 28 | let window = UIWindow(windowScene: windowScene) 29 | window.rootViewController = UIHostingController(rootView: contentView) 30 | self.window = window 31 | window.makeKeyAndVisible() 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/iOS/TagItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct TagItemView: View { 9 | 10 | static let rowHeight: CGFloat = 34 11 | 12 | @ObservedObject private var model: TagItemViewModel 13 | private let action: () -> Void 14 | 15 | init(model: TagItemViewModel, action: @escaping () -> Void) { 16 | self.model = model 17 | self.action = action 18 | } 19 | 20 | var body: some View { 21 | HStack(alignment: .center) { 22 | ZStack { 23 | if model.tagItem.itemID != nil { 24 | Image(systemName: "checkmark") 25 | } else { 26 | EmptyView() 27 | } 28 | } 29 | .frame(width: 20, height: 20) 30 | 31 | Button(action: self.action) { 32 | Text(model.tagItem.name) 33 | .lineLimit(1) 34 | .frame(maxWidth: .infinity, alignment: .leading) 35 | } 36 | } 37 | .frame(minHeight: ChecklistItemView.rowHeight, alignment: .leading) 38 | } 39 | } 40 | 41 | struct TagItemView_Previews: PreviewProvider { 42 | static var previews: some View { 43 | return List { 44 | TagItemView(model: TagItemViewModel(tagItem: TagItem(id: TagID("1"), name: String(repeating: "Tag 1 ", count: 20), itemID: ItemID("1"))), action: {}) 45 | TagItemView(model: TagItemViewModel(tagItem: TagItem(id: TagID("2"), name: "Tag 2", itemID: nil)), action: {}) 46 | } 47 | .environment(\.defaultMinListRowHeight, TagItemView.rowHeight) 48 | .padding() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/iOS/TagItemViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Combine 7 | import SwiftUI 8 | import PLRelational 9 | import PLRelationalCombine 10 | 11 | struct TagItem: Identifiable { 12 | let id: TagID 13 | let name: String 14 | let itemID: ItemID? 15 | 16 | init(id: TagID, name: String, itemID: ItemID?) { 17 | self.id = id 18 | self.name = name 19 | self.itemID = itemID 20 | } 21 | 22 | init(row: Row) { 23 | self.id = TagID(row[Tag.id]) 24 | self.name = row[Tag.name].get()! 25 | if let itemID = row[Item.id].get() as String? { 26 | self.itemID = ItemID(itemID) 27 | } else { 28 | self.itemID = nil 29 | } 30 | } 31 | } 32 | 33 | final class TagItemViewModel: ElementViewModel, Identifiable, ObservableObject { 34 | 35 | var tagItem: TagItem 36 | var element: TagItem { tagItem } 37 | var id: TagID { tagItem.id } 38 | 39 | init(tagItem: TagItem) { 40 | self.tagItem = tagItem 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/iOS/TagsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct TagsView: View { 9 | 10 | @ObservedObject private var model: TagsViewModel 11 | 12 | init(model: TagsViewModel) { 13 | self.model = model 14 | } 15 | 16 | var body: some View { 17 | VStack(spacing: 0) { 18 | TextField("Add a tag", text: $model.newTagName, onCommit: { 19 | self.model.addNewTagToSelectedItem() 20 | }) 21 | .autocapitalization(.none) 22 | .padding() 23 | 24 | Divider() 25 | 26 | List { 27 | ForEach(self.model.tagItemViewModels) { tagItemViewModel in 28 | TagItemView(model: tagItemViewModel, action: { 29 | self.model.toggleApplied(tagItemViewModel.tagItem) 30 | }) 31 | .frame(minWidth: 0, maxWidth: .infinity) 32 | } 33 | } 34 | .environment(\.defaultMinListRowHeight, TagItemView.rowHeight) 35 | } 36 | } 37 | } 38 | 39 | struct TagsView_Previews: PreviewProvider { 40 | static var previews: some View { 41 | let model = modelForPreview() 42 | return TagsView(model: TagsViewModel(model: model)) 43 | .padding() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/iOS/TextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | // Based on: 7 | // https://stackoverflow.com/a/57853937 8 | 9 | import SwiftUI 10 | 11 | /// A wrapper around UITextView to allow it to be used in SwiftUI. 12 | struct TextView: UIViewRepresentable { 13 | 14 | @Binding var text: String 15 | 16 | var onCommit: () -> Void = {} 17 | 18 | func makeCoordinator() -> Coordinator { 19 | Coordinator(self) 20 | } 21 | 22 | func makeUIView(context: Context) -> UITextView { 23 | let uiTextView = UITextView() 24 | uiTextView.delegate = context.coordinator 25 | 26 | uiTextView.font = UIFont.systemFont(ofSize: 16) 27 | uiTextView.isScrollEnabled = true 28 | uiTextView.isEditable = true 29 | uiTextView.isUserInteractionEnabled = true 30 | 31 | // XXX: Remove all padding 32 | uiTextView.textContainerInset = .zero 33 | uiTextView.textContainer.lineFragmentPadding = 0 34 | 35 | return uiTextView 36 | } 37 | 38 | func updateUIView(_ uiView: UITextView, context: Context) { 39 | uiView.text = text 40 | } 41 | 42 | class Coordinator : NSObject, UITextViewDelegate { 43 | var parent: TextView 44 | 45 | init(_ uiTextView: TextView) { 46 | self.parent = uiTextView 47 | } 48 | 49 | func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { 50 | return true 51 | } 52 | 53 | func textViewShouldEndEditing(_ textView: UITextView) -> Bool { 54 | self.parent.text = textView.text 55 | self.parent.onCommit() 56 | return true 57 | } 58 | } 59 | } 60 | 61 | #if DEBUG 62 | struct TextView_Previews: PreviewProvider { 63 | static var previews: some View { 64 | VStack { 65 | TextView(text: .constant("hello\nthere")) 66 | } 67 | .padding() 68 | .previewLayout(.fixed(width: 300, height: 200)) 69 | } 70 | } 71 | #endif 72 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/macOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | import SwiftUI 8 | import PLRelational 9 | import PLRelationalCombine 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | var window: NSWindow! 15 | 16 | private var undoManager: PLRelationalCombine.UndoManager! 17 | private var model: Model! 18 | private var contentViewModel: ContentViewModel! 19 | 20 | func applicationDidFinishLaunching(_ aNotification: Notification) { 21 | // Prepare the undo manager 22 | undoManager = UndoManager() 23 | 24 | // Initialize our model 25 | model = Model(undoManager: undoManager, path: "/tmp/TodoApp-SwiftUI.db") 26 | if !model.dbAlreadyExisted { 27 | _ = model.addDefaultData() 28 | } 29 | 30 | // Create the SwiftUI view that provides the window contents 31 | contentViewModel = ContentViewModel(model: model) 32 | let contentView = ContentView(model: contentViewModel) 33 | 34 | // Create the window and set the content view 35 | window = NSWindow( 36 | contentRect: NSRect(x: 0, y: 0, width: 640, height: 440), 37 | styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], 38 | backing: .buffered, defer: false) 39 | window.delegate = self 40 | window.center() 41 | window.setFrameAutosaveName("Main Window") 42 | window.contentView = NSHostingView(rootView: contentView) 43 | window.makeKeyAndOrderFront(nil) 44 | } 45 | 46 | func applicationWillTerminate(_ aNotification: Notification) { 47 | } 48 | } 49 | 50 | extension AppDelegate: NSWindowDelegate { 51 | func windowWillReturnUndoManager(_ window: NSWindow) -> Foundation.UndoManager? { 52 | return undoManager.native 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/macOS/ChecklistItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct ChecklistItemView: View { 9 | 10 | @ObservedObject private var model: ChecklistItemViewModel 11 | 12 | init(model: ChecklistItemViewModel) { 13 | self.model = model 14 | } 15 | 16 | var body: some View { 17 | HStack(alignment: .center) { 18 | Toggle(isOn: $model.completed) { 19 | Text(" ") 20 | } 21 | Text(model.title) 22 | Spacer() 23 | Text(model.tags) 24 | .font(.system(size: 11)) 25 | .foregroundColor(Color.secondary) 26 | } 27 | } 28 | } 29 | 30 | struct ChecklistItemView_Previews: PreviewProvider { 31 | static var previews: some View { 32 | let model = modelForPreview() 33 | let viewModel = ChecklistItemViewModel(model: model, item: ChecklistItem(id: ItemID("1"), title: "Item 1", created: "", completed: "")) 34 | return ChecklistItemView(model: viewModel) 35 | .padding() 36 | .previewLayout(.fixed(width: 300, height: 30)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/macOS/ChecklistItemViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Combine 7 | import SwiftUI 8 | import PLRelational 9 | import PLRelationalCombine 10 | 11 | final class ChecklistItemViewModel: ElementViewModel, Identifiable, ObservableObject { 12 | 13 | private let model: Model 14 | 15 | var item: ChecklistItem { 16 | didSet { 17 | // REQ-4 18 | // Keep the list item title up to date. Note that we could have set up 19 | // a binding in `init` below similar to what we do for the tags label, 20 | // but using `didSet` here demonstrates another approach. Since the 21 | // `reduce(to:)` will see an update every time the title gets changed 22 | // by the detail view, and since it already sets the updated view model's 23 | // `item`, we can just update `title` here. 24 | self.title = item.title 25 | } 26 | } 27 | var element: ChecklistItem { item } 28 | var id: ItemID { item.id } 29 | 30 | @TwoWay(onSet: .commit) var completed: Bool = false 31 | @Published var title: String 32 | @Published var tags: String 33 | 34 | private var cancellableBag = CancellableBag() 35 | 36 | init(model: Model, item: ChecklistItem) { 37 | self.model = model 38 | self.item = item 39 | self.title = item.title 40 | self.tags = "" 41 | 42 | // REQ-3 43 | // Each to-do item should have a checkbox showing its completion status. 44 | // This is a two-way property that is backed by UndoableDatabase. 45 | model.items 46 | .select(Item.id *== id) 47 | .project(Item.completed) 48 | .bind(to: \._completed, on: self, strategy: model.itemCompleted()) 49 | .store(in: &cancellableBag) 50 | 51 | // REQ-5 52 | // Each to-do item should have a string containing the 53 | // list of tags for that item. 54 | self.model 55 | .tagsString(for: id) 56 | .bind(to: \.tags, on: self) 57 | .store(in: &cancellableBag) 58 | } 59 | 60 | deinit { 61 | cancellableBag.cancel() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/macOS/ChecklistView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct ChecklistView: View { 9 | 10 | @ObservedObject private var model: ChecklistViewModel 11 | 12 | init(model: ChecklistViewModel) { 13 | self.model = model 14 | } 15 | 16 | var body: some View { 17 | // Disable animation for initial load 18 | let animation: Animation? 19 | if model.hasDisplayedItems { 20 | animation = .default 21 | } else { 22 | if model.itemViewModels.count > 0 { 23 | model.hasDisplayedItems = true 24 | } 25 | animation = .none 26 | } 27 | 28 | return VStack { 29 | TextField("Add a to-do", text: $model.newItemTitle, onCommit: { 30 | self.model.addNewItem() 31 | }) 32 | .padding(.bottom, 10) 33 | 34 | List(selection: $model.selectedItem) { 35 | ForEach(self.model.itemViewModels) { itemViewModel in 36 | ChecklistItemView(model: itemViewModel) 37 | .frame(minWidth: 0, maxWidth: .infinity) 38 | .animation(.none) 39 | } 40 | } 41 | .animation(animation) 42 | .environment(\.defaultMinListRowHeight, 30) 43 | } 44 | } 45 | } 46 | 47 | struct ChecklistView_Previews: PreviewProvider { 48 | static var previews: some View { 49 | let model = modelForPreview() 50 | let viewModel = ChecklistViewModel(model: model) 51 | return ChecklistView(model: viewModel) 52 | .padding() 53 | .previewLayout(.fixed(width: 300, height: 400)) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/macOS/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct ContentView: View { 9 | 10 | @ObservedObject private var model: ContentViewModel 11 | 12 | init(model: ContentViewModel) { 13 | self.model = model 14 | } 15 | 16 | var body: some View { 17 | HStack(spacing: 20) { 18 | ChecklistView(model: model.checklistViewModel) 19 | .frame(minWidth: 290, minHeight: 400) 20 | 21 | ZStack { 22 | if model.hasSelection { 23 | DetailView(model: model.detailViewModel) 24 | .padding() 25 | .background(Color(white: 0.25)) 26 | .cornerRadius(8) 27 | } else { 28 | Text("No Selection") 29 | .font(.system(size: 19)) 30 | .foregroundColor(Color.secondary) 31 | .frame(maxWidth: .infinity) 32 | } 33 | } 34 | .frame(minWidth: 290, minHeight: 400) 35 | } 36 | .padding() 37 | } 38 | } 39 | 40 | struct ContentView_Previews: PreviewProvider { 41 | static var previews: some View { 42 | let model = modelForPreview() 43 | let viewModel = ContentViewModel(model: model) 44 | return ContentView(model: viewModel) 45 | .previewLayout(.fixed(width: 600, height: 440)) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/macOS/ContentViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Combine 7 | import SwiftUI 8 | import PLRelational 9 | import PLRelationalCombine 10 | 11 | final class ContentViewModel: ObservableObject { 12 | 13 | private let model: Model 14 | 15 | let checklistViewModel: ChecklistViewModel 16 | let detailViewModel: DetailViewModel 17 | 18 | @Published var hasSelection: Bool = false 19 | 20 | private var cancellableBag = CancellableBag() 21 | 22 | init(model: Model) { 23 | self.model = model 24 | self.checklistViewModel = ChecklistViewModel(model: model) 25 | self.detailViewModel = DetailViewModel(model: model) 26 | 27 | // Set a flag when an item is selected in the list of to-do items. 28 | model.selectedItems 29 | .nonEmpty() 30 | .replaceError(with: false) 31 | .bind(to: \.hasSelection, on: self) 32 | .store(in: &cancellableBag) 33 | } 34 | 35 | deinit { 36 | cancellableBag.cancel() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Sources/macOS/DetailView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct DetailView: View { 9 | 10 | @ObservedObject private var model: DetailViewModel 11 | 12 | init(model: DetailViewModel) { 13 | self.model = model 14 | } 15 | 16 | var body: some View { 17 | VStack(alignment: .leading) { 18 | HStack(alignment: .center) { 19 | Toggle(isOn: $model.itemCompleted) { 20 | Text(" ") 21 | } 22 | TextField("", text: $model.itemTitle, onCommit: { self.model.commitItemTitle() }) 23 | } 24 | .padding(.bottom) 25 | 26 | EphemeralComboBox( 27 | placeholder: "Assign a tag", 28 | items: $model.availableTags, 29 | onCommitString: { self.model.addNewTagToSelectedItem(name: $0) }, 30 | onItemSelected: { self.model.addExistingTagToSelectedItem(tagID: $0.id) } 31 | ) 32 | 33 | List { 34 | ForEach(model.itemTags, id: \.self) { tag in 35 | Text(tag) 36 | } 37 | } 38 | .padding(.bottom) 39 | 40 | Text("Notes") 41 | TextView(text: $model.itemNotes, onCommit: { self.model.commitItemNotes() }) 42 | .padding(.bottom) 43 | 44 | HStack { 45 | Text(model.createdOn) 46 | Spacer() 47 | Button(action: model.deleteSelectedItem) { 48 | Image("Trash") 49 | }.buttonStyle(BorderlessButtonStyle()) 50 | } 51 | } 52 | } 53 | } 54 | 55 | struct DetailView_Previews: PreviewProvider { 56 | static var previews: some View { 57 | let model = modelForPreview() 58 | let viewModel = DetailViewModel(model: model) 59 | return DetailView(model: viewModel) 60 | .padding() 61 | .previewLayout(.fixed(width: 300, height: 400)) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Support/iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Support/macOS/App.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/TodoApp-SwiftUI/Support/macOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2019 Plausible Labs Cooperative, Inc. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | NSSupportsAutomaticTermination 32 | 33 | NSSupportsSuddenTermination 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016-2017 Plausible Labs Cooperative, Inc. 2 | All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, 5 | to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to permit 9 | persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Legacy/Examples/BindableControlsApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Legacy/Examples/BindableControlsApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2016 Plausible Labs Cooperative, Inc. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /Legacy/Examples/BindableControlsAppUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Legacy/Examples/HelloWorldApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | import PLRelational 8 | import PLRelationalBinding 9 | import PLBindableControls 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { 13 | 14 | @IBOutlet weak var window: NSWindow! 15 | @IBOutlet var outlineView: ExtOutlineView! 16 | @IBOutlet var empNameLabel: Label! 17 | @IBOutlet var empDeptLabel: Label! 18 | 19 | private var undoManager: PLRelationalBinding.UndoManager! 20 | private var model: ViewModel! 21 | private var listView: ListView! 22 | 23 | func applicationDidFinishLaunching(_ aNotification: Notification) { 24 | window.delegate = self 25 | 26 | // Prepare the undo manager 27 | undoManager = UndoManager() 28 | 29 | // Bind the views to the view model 30 | model = ViewModel(undoManager: undoManager) 31 | 32 | listView = ListView(model: model.employeesListModel, outlineView: outlineView) 33 | listView.selection <~> model.employeesListSelection 34 | listView.configureCell = { view, row in 35 | let rowID: Int64 = row["id"].get()! 36 | let initialValue: String? = row["first_name"].get() 37 | let nameProperty = self.model.employeeName(for: rowID, initialValue: initialValue) 38 | let textField = view.textField as! TextField 39 | textField.string <~> nameProperty 40 | } 41 | 42 | empNameLabel.string <~ model.selectedEmployeeName 43 | empDeptLabel.string <~ model.selectedEmployeeDepartment 44 | } 45 | 46 | func windowWillReturnUndoManager(_ window: NSWindow) -> Foundation.UndoManager? { 47 | return undoManager.native 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Legacy/Examples/HelloWorldApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Legacy/Examples/HelloWorldApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2017 Plausible Labs Cooperative, Inc. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /Legacy/Examples/HelloWorldAppTests/HelloWorldAppTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import XCTest 7 | @testable import HelloWorldApp 8 | 9 | class HelloWorldAppTests: XCTestCase { 10 | } 11 | -------------------------------------------------------------------------------- /Legacy/Examples/HelloWorldAppTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Legacy/Examples/HelloWorldAppUITests/HelloWorldAppUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import XCTest 7 | 8 | class HelloWorldAppUITests: XCTestCase { 9 | } 10 | -------------------------------------------------------------------------------- /Legacy/Examples/HelloWorldAppUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Legacy/Examples/MiniVisualizer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Legacy/Examples/MiniVisualizer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2017 Plausible Labs Cooperative, Inc. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /Legacy/Examples/MiniVisualizer/NSBezierPath+CGPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import AppKit 7 | 8 | extension NSBezierPath { 9 | 10 | public var cgPath: CGPath { 11 | let path = CGMutablePath() 12 | var points = [CGPoint](repeating: .zero, count: 3) 13 | 14 | for i in 0 ..< self.elementCount { 15 | let type = self.element(at: i, associatedPoints: &points) 16 | switch type { 17 | case .moveToBezierPathElement: 18 | path.move(to: points[0]) 19 | case .lineToBezierPathElement: 20 | path.addLine(to: points[0]) 21 | case .curveToBezierPathElement: 22 | path.addCurve(to: points[2], control1: points[0], control2: points[1]) 23 | case .closePathBezierPathElement: 24 | path.closeSubpath() 25 | } 26 | } 27 | 28 | return path 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Legacy/Examples/MiniVisualizer/ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import PLRelational 8 | import PLRelationalBinding 9 | import PLBindableControls 10 | 11 | enum Fruit { 12 | static let id = Attribute("id") 13 | static let name = Attribute("name") 14 | static let quantity = Attribute("quantity") 15 | } 16 | 17 | class ViewModel { 18 | 19 | private let db: TransactionalDatabase 20 | let fruits: TransactionalRelation 21 | 22 | private var observerRemovals: [ObserverRemoval] = [] 23 | 24 | init() { 25 | // Prepare the stored relations 26 | let memoryDB = MemoryTableDatabase() 27 | let db = TransactionalDatabase(memoryDB) 28 | func createRelation(_ name: String, _ scheme: Scheme) -> TransactionalRelation { 29 | _ = memoryDB.createRelation(name, scheme: scheme) 30 | return db[name] 31 | } 32 | 33 | fruits = createRelation( 34 | "fruit", 35 | [Fruit.id, Fruit.name, Fruit.quantity]) 36 | 37 | self.db = db 38 | 39 | fruits.asyncAdd([Fruit.id: 1, Fruit.name: "Apple", Fruit.quantity: 5]) 40 | fruits.asyncAdd([Fruit.id: 2, Fruit.name: "Banana", Fruit.quantity: 7]) 41 | fruits.asyncAdd([Fruit.id: 3, Fruit.name: "Cherry", Fruit.quantity: 42]) 42 | } 43 | 44 | deinit { 45 | observerRemovals.forEach{ $0() } 46 | } 47 | 48 | lazy var fruitsProperty: ArrayProperty = { 49 | return self.fruits.arrayProperty(idAttr: Fruit.id, orderAttr: Fruit.id) 50 | }() 51 | } 52 | -------------------------------------------------------------------------------- /Legacy/Examples/RelationChangeApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Legacy/Examples/RelationChangeApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2017 Plausible Labs Cooperative, Inc. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /Legacy/Examples/RelationChangeApp/NSBezierPath+CGPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import AppKit 7 | 8 | extension NSBezierPath { 9 | 10 | public var cgPath: CGPath { 11 | let path = CGMutablePath() 12 | var points = [CGPoint](repeating: .zero, count: 3) 13 | 14 | for i in 0 ..< self.elementCount { 15 | let type = self.element(at: i, associatedPoints: &points) 16 | switch type { 17 | case .moveTo: 18 | path.move(to: points[0]) 19 | case .lineTo: 20 | path.addLine(to: points[0]) 21 | case .curveTo: 22 | path.addCurve(to: points[2], control1: points[0], control2: points[1]) 23 | case .closePath: 24 | path.closeSubpath() 25 | } 26 | } 27 | 28 | return path 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Legacy/Examples/RelationChangeApp/TextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | import PLRelationalBinding 8 | import PLBindableControls 9 | 10 | class TextView: NSTextView { 11 | 12 | lazy var text: BindableProperty = WriteOnlyProperty(set: { [weak self] value, _ in 13 | self?.string = value 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /Legacy/Examples/SearchApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | import PLRelational 8 | import PLRelationalBinding 9 | import PLBindableControls 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { 13 | 14 | @IBOutlet weak var window: NSWindow! 15 | @IBOutlet var queryField: TextField! 16 | @IBOutlet var outlineView: ExtOutlineView! 17 | @IBOutlet var noResultsLabel: Label! 18 | @IBOutlet var personNameLabel: Label! 19 | @IBOutlet var personBioLabel: Label! 20 | 21 | private var undoManager: PLRelationalBinding.UndoManager! 22 | private var model: ViewModel! 23 | private var resultsListView: ListView! 24 | 25 | func applicationDidFinishLaunching(_ aNotification: Notification) { 26 | // XXX: We're not ready for dark mode yet 27 | if #available(macOS 10.14, *) { 28 | NSApp.appearance = NSAppearance(named: .aqua) 29 | } 30 | 31 | window.delegate = self 32 | queryField.deliverTransientChanges = true 33 | 34 | // Prepare the undo manager 35 | undoManager = UndoManager() 36 | 37 | // Bind the views to the view model 38 | model = ViewModel(undoManager: undoManager) 39 | model.queryString <~ queryField.string 40 | 41 | resultsListView = ResultsListView(model: model.resultsListModel, outlineView: outlineView) 42 | resultsListView.reloadCellOnUpdate = true 43 | resultsListView.selection <~> model.resultsListSelection 44 | resultsListView.configureCell = { view, row in 45 | view.textField?.attributedStringValue = SearchResult.highlightedString(from: row) 46 | } 47 | 48 | noResultsLabel.visible <~ not(model.hasResults) 49 | personNameLabel.string <~ model.selectedPersonName 50 | personBioLabel.string <~ model.selectedPersonBio 51 | } 52 | 53 | func windowWillReturnUndoManager(_ window: NSWindow) -> Foundation.UndoManager? { 54 | return undoManager.native 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Legacy/Examples/SearchApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Legacy/Examples/SearchApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2016 Plausible Labs Cooperative, Inc. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /Legacy/Examples/SearchApp/ResultsListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import AppKit 7 | import PLRelationalBinding 8 | import PLBindableControls 9 | 10 | /// Normally it would not be necessary to subclass ListView, but we do that here just to customize the row selection 11 | /// color to make the search results a bit more readable. 12 | class ResultsListView: ListView { 13 | 14 | override func outlineView(_ outlineView: NSOutlineView, rowViewForItem item: Any) -> NSTableRowView? { 15 | let identifier = NSUserInterfaceItemIdentifier("RowView") 16 | if let rowView = outlineView.makeView(withIdentifier: identifier, owner: self) { 17 | return rowView as? NSTableRowView 18 | } else { 19 | let rowView = OutlineRowView(frame: NSZeroRect, rowHeight: outlineView.rowHeight) 20 | rowView.identifier = identifier 21 | return rowView 22 | } 23 | } 24 | } 25 | 26 | private class OutlineRowView: NSTableRowView { 27 | let rowHeight: CGFloat 28 | 29 | init(frame: NSRect, rowHeight: CGFloat) { 30 | self.rowHeight = rowHeight 31 | super.init(frame: frame) 32 | } 33 | 34 | required init?(coder: NSCoder) { 35 | fatalError("init(coder:) has not been implemented") 36 | } 37 | 38 | override func drawSelection(in dirtyRect: NSRect) { 39 | var selectionRect = self.bounds 40 | selectionRect.origin.y = self.bounds.height - rowHeight 41 | selectionRect.size.height = rowHeight 42 | let color = NSColor(red: 178.0/255.0, green: 223.0/255.0, blue: 255.0/255.0, alpha: 1.0) 43 | color.setFill() 44 | selectionRect.fill() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Legacy/Examples/SearchAppTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Legacy/Examples/SearchAppTests/SearchAppTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import XCTest 7 | @testable import SearchApp 8 | 9 | class SearchAppTests: XCTestCase { 10 | 11 | override func setUp() { 12 | super.setUp() 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | } 15 | 16 | override func tearDown() { 17 | // Put teardown code here. This method is called after the invocation of each test method in the class. 18 | super.tearDown() 19 | } 20 | 21 | func testExample() { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | } 25 | 26 | // func testPerformanceExample() { 27 | // // This is an example of a performance test case. 28 | // self.measure { 29 | // // Put the code you want to measure the time of here. 30 | // } 31 | // } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Legacy/Examples/SearchAppUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Legacy/Examples/SearchAppUITests/SearchAppUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import XCTest 7 | 8 | class SearchAppUITests: XCTestCase { 9 | 10 | override func setUp() { 11 | super.setUp() 12 | 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 18 | XCUIApplication().launch() 19 | 20 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 21 | } 22 | 23 | override func tearDown() { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | super.tearDown() 26 | } 27 | 28 | func testExample() { 29 | // Use recording to get started writing UI tests. 30 | // Use XCTAssert and related functions to verify your tests produce the correct results. 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Legacy/Examples/TodoApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | import PLRelational 8 | import PLRelationalBinding 9 | import PLBindableControls 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { 13 | 14 | @IBOutlet weak var window: NSWindow! 15 | @IBOutlet weak var listContainer: NSView! 16 | @IBOutlet weak var detailContainer: NSView! 17 | @IBOutlet weak var noSelectionLabel: Label! 18 | 19 | private var undoManager: PLRelationalBinding.UndoManager! 20 | 21 | private var checklistView: ChecklistView! 22 | private var detailView: DetailView! 23 | 24 | private var model: Model! 25 | 26 | func applicationDidFinishLaunching(_ aNotification: Notification) { 27 | // XXX: We're not ready for dark mode yet 28 | if #available(macOS 10.14, *) { 29 | NSApp.appearance = NSAppearance(named: .aqua) 30 | } 31 | 32 | window.delegate = self 33 | 34 | // Prepare the undo manager 35 | undoManager = UndoManager() 36 | 37 | // Initialize our model 38 | model = Model(undoManager: undoManager) 39 | 40 | // Add the checklist view to the left side 41 | let checklistViewModel = ChecklistViewModel(model: model) 42 | checklistView = ChecklistView(frame: listContainer.bounds, model: checklistViewModel) 43 | listContainer.addSubview(checklistView) 44 | 45 | // Add the detail view to the left side 46 | let detailViewModel = DetailViewModel(model: model) 47 | detailView = DetailView(frame: detailContainer.bounds, model: detailViewModel) 48 | detailContainer.addSubview(detailView) 49 | 50 | // REQ-6 51 | // Toggle the "No Selection" label and detail view depending 52 | // on the selection state 53 | detailView.visible <~ model.hasSelection 54 | noSelectionLabel.visible <~ not(model.hasSelection) 55 | } 56 | 57 | func windowWillReturnUndoManager(_ window: NSWindow) -> Foundation.UndoManager? { 58 | return undoManager.native 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Legacy/Examples/TodoApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Legacy/Examples/TodoApp/Identifiers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import PLRelational 8 | 9 | /// Base class for our identifier value types. All of our identifiers use UUIDs 10 | /// under the hood and we have conveniences for conversion to/from `RelationValue`. 11 | class BaseID { 12 | private let uuid: String 13 | 14 | init() { 15 | self.uuid = UUID().uuidString 16 | } 17 | 18 | init(_ stringValue: String) { 19 | self.uuid = stringValue 20 | } 21 | 22 | init(_ relationValue: RelationValue) { 23 | self.uuid = relationValue.get()! 24 | } 25 | 26 | var relationValue: RelationValue { 27 | return uuid.relationValue 28 | } 29 | } 30 | 31 | /// Identifier type for rows in the `Item` relation. 32 | class ItemID: BaseID { 33 | /// Shorthand for extracting an `ItemID` from an `Item` row. 34 | convenience init(_ row: Row) { 35 | self.init(row[Item.id]) 36 | } 37 | } 38 | 39 | /// Identifier type for rows in the `Tag` relation. 40 | class TagID: BaseID { 41 | /// Shorthand for extracting a `TagID` from an `Tag` row. 42 | convenience init(_ row: Row) { 43 | self.init(row[Tag.id]) 44 | } 45 | } 46 | 47 | /// Conforming to this protocol allows us to use `ItemID` and `TagID` directly 48 | /// in row initializers and in select expressions without having to explicitly 49 | /// convert to `RelationValue`. 50 | extension BaseID: SelectExpressionConstantValue {} 51 | -------------------------------------------------------------------------------- /Legacy/Examples/TodoApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2017 Plausible Labs Cooperative, Inc. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /Legacy/Examples/TodoApp/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plausiblelabs/plrelational/d904ea052b4e2b2db720733d3e63089c7d99e49b/Legacy/Examples/TodoApp/trash.png -------------------------------------------------------------------------------- /Legacy/Examples/TodoApp/trash@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plausiblelabs/plrelational/d904ea052b4e2b2db720733d3e63089c7d99e49b/Legacy/Examples/TodoApp/trash@2x.png -------------------------------------------------------------------------------- /Legacy/Examples/TodoClientApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | import PLRelational 8 | import PLRelationalBinding 9 | import PLBindableControls 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { 13 | 14 | @IBOutlet weak var window: NSWindow! 15 | @IBOutlet weak var listContainer: NSView! 16 | @IBOutlet weak var detailContainer: NSView! 17 | @IBOutlet weak var noSelectionLabel: Label! 18 | 19 | private var undoManager: PLRelationalBinding.UndoManager! 20 | 21 | private var checklistView: ChecklistView! 22 | private var detailView: DetailView! 23 | 24 | private var model: Model! 25 | 26 | func applicationDidFinishLaunching(_ aNotification: Notification) { 27 | window.delegate = self 28 | 29 | // Prepare the undo manager 30 | undoManager = UndoManager() 31 | 32 | // Initialize our model 33 | model = Model(undoManager: undoManager) 34 | 35 | // Add the checklist view to the left side 36 | let checklistViewModel = ChecklistViewModel(model: model) 37 | checklistView = ChecklistView(frame: listContainer.bounds, model: checklistViewModel) 38 | listContainer.addSubview(checklistView) 39 | 40 | // Add the detail view to the left side 41 | let detailViewModel = DetailViewModel(model: model) 42 | detailView = DetailView(frame: detailContainer.bounds, model: detailViewModel) 43 | detailContainer.addSubview(detailView) 44 | 45 | // Toggle the "No Selection" label and detail view depending 46 | // on the selection state 47 | detailView.visible <~ model.hasSelection 48 | noSelectionLabel.visible <~ not(model.hasSelection) 49 | } 50 | 51 | func windowWillReturnUndoManager(_ window: NSWindow) -> Foundation.UndoManager? { 52 | return undoManager.native 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Legacy/Examples/TodoClientApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Legacy/Examples/TodoClientApp/Identifiers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import PLRelational 8 | 9 | /// Base class for our identifier value types. All of our identifiers use UUIDs 10 | /// under the hood and we have conveniences for conversion to/from `RelationValue`. 11 | class BaseID { 12 | private let uuid: String 13 | 14 | init() { 15 | self.uuid = UUID().uuidString 16 | } 17 | 18 | init(_ stringValue: String) { 19 | self.uuid = stringValue 20 | } 21 | 22 | init(_ relationValue: RelationValue) { 23 | self.uuid = relationValue.get()! 24 | } 25 | 26 | var relationValue: RelationValue { 27 | return uuid.relationValue 28 | } 29 | } 30 | 31 | /// Identifier type for rows in the `Item` relation. 32 | class ItemID: BaseID { 33 | /// Shorthand for extracting an `ItemID` from an `Item` row. 34 | convenience init(_ row: Row) { 35 | self.init(row[Item.id]) 36 | } 37 | } 38 | 39 | /// Identifier type for rows in the `Tag` relation. 40 | class TagID: BaseID { 41 | /// Shorthand for extracting a `TagID` from an `Tag` row. 42 | convenience init(_ row: Row) { 43 | self.init(row[Tag.id]) 44 | } 45 | } 46 | 47 | /// Conforming to this protocol allows us to use `ItemID` and `TagID` directly 48 | /// in row initializers and in select expressions without having to explicitly 49 | /// convert to `RelationValue`. 50 | extension BaseID: SelectExpressionConstantValue {} 51 | -------------------------------------------------------------------------------- /Legacy/Examples/TodoClientApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2017 Plausible Labs Cooperative, Inc. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /Legacy/Examples/TodoClientApp/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plausiblelabs/plrelational/d904ea052b4e2b2db720733d3e63089c7d99e49b/Legacy/Examples/TodoClientApp/trash.png -------------------------------------------------------------------------------- /Legacy/Examples/TodoClientApp/trash@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plausiblelabs/plrelational/d904ea052b4e2b2db720733d3e63089c7d99e49b/Legacy/Examples/TodoClientApp/trash@2x.png -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | 8 | @NSApplicationMain 9 | class AppDelegate: NSObject, NSApplicationDelegate { 10 | 11 | func applicationDidFinishLaunching(_ aNotification: Notification) { 12 | } 13 | 14 | func applicationWillTerminate(_ aNotification: Notification) { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/AsyncState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | enum AsyncState { 7 | case idle(T) 8 | case loading 9 | } 10 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/Box.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | class Box { 7 | let value: T 8 | 9 | init(_ value: T) { 10 | self.value = value 11 | } 12 | 13 | static func open(_ obj: AnyObject?) -> T? { 14 | return (obj as? Box)?.value 15 | } 16 | } 17 | 18 | extension Box: CustomStringConvertible { 19 | var description: String { 20 | return "Box<\(T.self)>(\(value))" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/DocDatabaseError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import PLRelational 8 | 9 | enum DocDatabaseError: Error, CustomStringConvertible { case 10 | createFailed(underlying: RelationError), 11 | openFailed(underlying: RelationError), 12 | saveFailed(underlying: RelationError), 13 | replaceFailed(underlying: NSError) 14 | 15 | var description: String { 16 | return "DocDatabaseError(\(reason))" 17 | } 18 | 19 | var reason: String { 20 | switch self { 21 | case let .createFailed(e): 22 | return "Creation failed: \(e)" 23 | case let .openFailed(e): 24 | return "Open failed: \(e)" 25 | case let .saveFailed(e): 26 | return "Save failed: \(e)" 27 | case let .replaceFailed(e): 28 | return "Failed to replace file: \(e)" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/DocObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | /// Represents an item that exists in the document outline. 7 | struct DocObject { 8 | let docItemID: DocItemID 9 | let objectID: ObjectID 10 | let type: ItemType 11 | 12 | /// Create a DocObject whose docItemID and objectID have the same underlying value. 13 | init(_ type: ItemType) { 14 | self.docItemID = DocItemID() 15 | self.objectID = ObjectID(docItemID.stringValue) 16 | self.type = type 17 | } 18 | 19 | /// Create a DocObject that uses the given identifiers. 20 | init(docItemID: DocItemID, objectID: ObjectID, type: ItemType) { 21 | self.docItemID = docItemID 22 | self.objectID = objectID 23 | self.type = type 24 | } 25 | } 26 | 27 | extension DocObject: Equatable {} 28 | func ==(a: DocObject, b: DocObject) -> Bool { 29 | return a.docItemID == b.docItemID 30 | } 31 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/DocOutlineView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | import PLRelationalBinding 8 | import PLBindableControls 9 | 10 | class DocOutlineView { 11 | 12 | private let treeView: SectionedTreeView 13 | 14 | init(model: DocOutlineModel, outlineView: NSOutlineView) { 15 | treeView = SectionedTreeView(model: model, outlineView: outlineView) 16 | treeView.animateChanges = true 17 | treeView.autoExpand = true 18 | treeView.rowView = { frame, rowHeight in 19 | return OutlineRowView(frame: frame, rowHeight: rowHeight) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/Environment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import SystemConfiguration 8 | 9 | final class Environment { 10 | 11 | static func computerName() -> String { 12 | if let cfstring = SCDynamicStoreCopyComputerName(nil, nil) { 13 | return (cfstring as NSString) as String 14 | } else { 15 | return "Unknown" 16 | } 17 | } 18 | 19 | static func fullUserName() -> String { 20 | return NSFullUserName() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/HistoryItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import PLRelational 8 | 9 | struct HistoryItem { 10 | let id: HistoryItemID 11 | let tabID: TabID 12 | let outlinePath: DocOutlinePath 13 | let position: Int64 14 | } 15 | 16 | extension HistoryItem: Equatable {} 17 | func ==(a: HistoryItem, b: HistoryItem) -> Bool { 18 | return a.id == b.id 19 | } 20 | 21 | extension HistoryItem: Hashable { 22 | func hash(into hasher: inout Hasher) { 23 | hasher.combine(id) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeExtensions 11 | 12 | plrv 13 | 14 | CFBundleTypeIconFile 15 | 16 | CFBundleTypeName 17 | DocumentType 18 | CFBundleTypeOSTypes 19 | 20 | ???? 21 | 22 | CFBundleTypeRole 23 | Editor 24 | NSDocumentClass 25 | $(PRODUCT_MODULE_NAME).Document 26 | 27 | 28 | CFBundleExecutable 29 | $(EXECUTABLE_NAME) 30 | CFBundleIconFile 31 | 32 | CFBundleIdentifier 33 | $(PRODUCT_BUNDLE_IDENTIFIER) 34 | CFBundleInfoDictionaryVersion 35 | 6.0 36 | CFBundleName 37 | $(PRODUCT_NAME) 38 | CFBundlePackageType 39 | APPL 40 | CFBundleShortVersionString 41 | 1.0 42 | CFBundleVersion 43 | 1 44 | LSMinimumSystemVersion 45 | $(MACOSX_DEPLOYMENT_TARGET) 46 | NSHumanReadableCopyright 47 | Copyright © 2016 Plausible Labs Cooperative, Inc. All rights reserved. 48 | NSMainNibFile 49 | MainMenu 50 | NSPrincipalClass 51 | NSApplication 52 | 53 | 54 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/ItemType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import PLRelational 8 | 9 | enum ItemType: Int64 { case 10 | section = 0, 11 | group = 1, 12 | storedRelation = 2, 13 | sharedRelation = 3, 14 | privateRelation = 4 15 | 16 | init?(_ value: RelationValue) { 17 | self.init(rawValue: value.get()!)! 18 | } 19 | 20 | init?(_ row: Row) { 21 | self.init(row["type"]) 22 | } 23 | 24 | var name: String { 25 | switch self { 26 | case .section: return "Section" 27 | case .group: return "Group" 28 | case .storedRelation: return "Stored Relation" 29 | case .sharedRelation: return "Shared Relation" 30 | case .privateRelation: return "Private Relation" 31 | } 32 | } 33 | 34 | var isGroupType: Bool { 35 | switch self { 36 | case .section, .group: 37 | return true 38 | default: 39 | return false 40 | } 41 | } 42 | 43 | var isObjectType: Bool { 44 | return !isGroupType 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/NSColor255.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import AppKit 7 | 8 | extension NSColor { 9 | /// Initialize a color with 255-based component values. 10 | convenience init(r: Int, g: Int, b: Int, a: Int = 255) { 11 | func convert(_ component: Int) -> CGFloat { 12 | return CGFloat(component) / 255.0 13 | } 14 | self.init(calibratedRed: convert(r), green: convert(g), blue: convert(b), alpha: convert(a)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/NSControlSwiftAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import AppKit 7 | 8 | 9 | private class ActionTrampoline: NSObject { 10 | var actionFunc: (NSControl) -> Void 11 | 12 | init(action: @escaping (NSControl) -> Void) { 13 | self.actionFunc = action 14 | } 15 | 16 | @objc func action(_ sender: NSControl) { 17 | actionFunc(sender) 18 | } 19 | } 20 | 21 | protocol NSActionSettingExtensionProtocol: class { 22 | var target: AnyObject? { get set } 23 | var action: Selector? { get set } 24 | } 25 | 26 | extension NSActionSettingExtensionProtocol { 27 | @discardableResult func setAction(_ action: @escaping (Self) -> Void) -> Self { 28 | let trampoline = ActionTrampoline(action: { sender in 29 | action(sender as! Self) 30 | }) 31 | _ = attach(trampoline, to: self) 32 | self.target = trampoline 33 | self.action = #selector(ActionTrampoline.action(_:)) 34 | 35 | return self 36 | } 37 | } 38 | 39 | extension NSControl: NSActionSettingExtensionProtocol {} 40 | extension NSMenuItem: NSActionSettingExtensionProtocol {} 41 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/ObjectAttachment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | private var associatedObjectKey = UnsafeMutablePointer.allocate(capacity: 1) 9 | 10 | /// Attach an object to another object. This can be used for turning weak/unowned 11 | /// properties into de-facto strong properties by attaching the target of the 12 | /// property to the object that contains the property. Good for standalone delegates 13 | /// and action targets. 14 | /// 15 | /// - parameter object: The target object whose lifetime will be extended. 16 | /// - parameter to: The source object to attach to. 17 | /// - returns: `object`, for easier chaining of calls. 18 | func attach(_ object: T, to: AnyObject) -> T{ 19 | objc_sync_enter(to) 20 | 21 | let array = objc_getAssociatedObject(to, associatedObjectKey) as? NSArray ?? [] 22 | let newArray = array.adding(object) 23 | objc_setAssociatedObject(to, associatedObjectKey, newArray, .OBJC_ASSOCIATION_RETAIN) 24 | 25 | objc_sync_exit(to) 26 | 27 | return object 28 | } 29 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/OutlineRowView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | 8 | class OutlineRowView: NSTableRowView { 9 | let rowHeight: CGFloat 10 | 11 | init(frame: NSRect, rowHeight: CGFloat) { 12 | self.rowHeight = rowHeight 13 | super.init(frame: frame) 14 | } 15 | 16 | required init?(coder: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | 20 | override func drawSelection(in dirtyRect: NSRect) { 21 | var selectionRect = self.bounds 22 | selectionRect.origin.y = self.bounds.height - rowHeight 23 | selectionRect.size.height = rowHeight 24 | let color = self.isEmphasized ? VisualizerColors.strongHighlight : VisualizerColors.weakHighlight 25 | color.setFill() 26 | selectionRect.fill() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/RelationViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import PLRelational 8 | 9 | struct RelationViewModel { 10 | 11 | let rootID: ObjectID 12 | let models: [ObjectID: RelationModel] 13 | 14 | // init(rootID: ObjectID, models: [ObjectID: RelationModel]) { 15 | // // TODO 16 | // } 17 | } 18 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/ScrollView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | import PLRelationalBinding 8 | 9 | open class ScrollView: NSScrollView { 10 | 11 | open lazy var visible: BindableProperty = WriteOnlyProperty(set: { [weak self] value, _ in 12 | self?.isHidden = !value 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/SidebarModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import PLRelational 8 | import PLRelationalBinding 9 | 10 | class SidebarModel { 11 | 12 | let db: DocDatabase 13 | private let selectedObjects: Relation 14 | 15 | /// - Parameters: 16 | /// - db: The database. 17 | /// - selectedObjects: Relation with scheme [id, type, name]. 18 | init(db: DocDatabase, selectedObjects: Relation) { 19 | precondition(selectedObjects.scheme == DB.Object.scheme) 20 | 21 | self.db = db 22 | self.selectedObjects = selectedObjects 23 | } 24 | 25 | lazy var itemSelected: AsyncReadableProperty = { 26 | return self.selectedObjects.nonEmpty.property() 27 | }() 28 | 29 | lazy var itemNotSelected: AsyncReadableProperty = { 30 | return self.selectedObjects.empty.property() 31 | }() 32 | } 33 | -------------------------------------------------------------------------------- /Legacy/Examples/Visualizer/VisualizerColors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | 8 | class VisualizerColors { 9 | static let strongHighlight = NSColor(srgbRed: 113.0/255.0, green: 130.0/255.0, blue: 226.0/255.0, alpha: 1.0) 10 | static let weakHighlight = NSColor(srgbRed: 213.0/255.0, green: 230.0/255.0, blue: 249.0/255.0, alpha: 1.0) 11 | } 12 | -------------------------------------------------------------------------------- /Legacy/Examples/VisualizerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Legacy/Examples/VisualizerTests/VisualizerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import XCTest 7 | @testable import Visualizer 8 | 9 | class VisualizerTests: XCTestCase { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Legacy/Examples/VisualizerUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Legacy/Examples/VisualizerUITests/VisualizerUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import XCTest 7 | 8 | class VisualizerUITests: XCTestCase { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/AppKit/BackgroundView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | import PLRelationalBinding 8 | 9 | open class BackgroundView: NSView { 10 | 11 | public lazy var visible: BindableProperty = WriteOnlyProperty(set: { [weak self] value, _ in 12 | self?.isHidden = !value 13 | }) 14 | 15 | public var backgroundColor: NSColor? 16 | 17 | open override func draw(_ dirtyRect: NSRect) { 18 | super.draw(dirtyRect) 19 | 20 | if let bg = backgroundColor { 21 | bg.setFill() 22 | NSBezierPath.fill(dirtyRect) 23 | } 24 | } 25 | 26 | open override var isFlipped: Bool { 27 | return true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/AppKit/Button.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | import PLRelationalBinding 8 | 9 | open class Button: NSButton { 10 | 11 | public private(set) lazy var visible: BindableProperty = WriteOnlyProperty(set: { [weak self] value, _ in 12 | self?.isHidden = !value 13 | }) 14 | 15 | public private(set) lazy var disabled: BindableProperty = WriteOnlyProperty(set: { [unowned self] value, _ in 16 | self.isEnabled = !value 17 | }) 18 | 19 | public private(set) lazy var string: BindableProperty = WriteOnlyProperty(set: { [weak self] value, _ in 20 | self?.title = value 21 | }) 22 | 23 | private let _clicks = SourceSignal<()>() 24 | public var clicks: Signal<()> { return _clicks } 25 | 26 | public override init(frame: NSRect) { 27 | super.init(frame: frame) 28 | target = self 29 | action = #selector(buttonClicked(_:)) 30 | } 31 | 32 | public required init?(coder: NSCoder) { 33 | super.init(coder: coder) 34 | target = self 35 | action = #selector(buttonClicked(_:)) 36 | } 37 | 38 | @objc func buttonClicked(_ sender: Button) { 39 | _clicks.notifyAction() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/AppKit/ContextMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | 8 | public struct ContextMenu { 9 | 10 | // TODO: Remove this in favor of MenuItem 11 | public enum Item { case 12 | titled(title: String, enabled: Bool, action: () -> Void), 13 | separator 14 | } 15 | 16 | public let items: [Item] 17 | 18 | public init(items: [Item]) { 19 | self.items = items 20 | } 21 | } 22 | 23 | extension ContextMenu { 24 | 25 | public var nsmenu: NSMenu { 26 | let menu = NSMenu() 27 | menu.autoenablesItems = false 28 | 29 | for item in items { 30 | let nsitem: NSMenuItem 31 | switch item { 32 | case let .titled(title, enabled, action): 33 | nsitem = ClosureMenuItem(title: title, actionClosure: action, keyEquivalent: "") 34 | nsitem.isEnabled = enabled 35 | break 36 | case .separator: 37 | nsitem = NSMenuItem.separator() 38 | } 39 | menu.addItem(nsitem) 40 | } 41 | 42 | return menu 43 | } 44 | } 45 | 46 | private class ClosureMenuItem: NSMenuItem { 47 | 48 | private var actionClosure: () -> Void 49 | 50 | init(title: String, actionClosure: @escaping () -> Void, keyEquivalent: String) { 51 | self.actionClosure = actionClosure 52 | super.init(title: title, action: #selector(ClosureMenuItem.action(_:)), keyEquivalent: keyEquivalent) 53 | self.target = self 54 | } 55 | 56 | required init(coder aDecoder: NSCoder) { 57 | fatalError("init(coder:) has not been implemented") 58 | } 59 | 60 | @objc func action(_ sender: NSMenuItem) { 61 | self.actionClosure() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/AppKit/EphemeralTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | import PLRelationalBinding 8 | 9 | /// A simplified variant of `TextField` that clears the text field whenever new text is committed 10 | /// with the enter key. 11 | open class EphemeralTextField: NSTextField { 12 | 13 | private let _strings = SourceSignal() 14 | public var strings: Signal { return _strings } 15 | 16 | public override init(frame: NSRect) { 17 | super.init(frame: frame) 18 | 19 | target = self 20 | action = #selector(stringCommitted(_:)) 21 | } 22 | 23 | public required init?(coder: NSCoder) { 24 | super.init(coder: coder) 25 | 26 | target = self 27 | action = #selector(stringCommitted(_:)) 28 | } 29 | 30 | @objc func stringCommitted(_ sender: NSTextField) { 31 | if !self.stringValue.isEmpty { 32 | self._strings.notifyValueChanging(self.stringValue) 33 | self.stringValue = "" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/AppKit/ExtOutlineView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | 8 | public protocol ExtOutlineViewDelegate: NSOutlineViewDelegate { 9 | func outlineView(_ outlineView: NSOutlineView, menuForItem item: Any) -> NSMenu? 10 | } 11 | 12 | open class ExtOutlineView: NSOutlineView { 13 | 14 | open override func validateProposedFirstResponder(_ responder: NSResponder, for event: NSEvent?) -> Bool { 15 | // XXX: The following prevents the text field from becoming first responder if it is right-clicked 16 | // (which should instead cause the context menu to be shown) 17 | if let event = event { 18 | if event.type == .rightMouseDown || (event.type == .leftMouseDown && event.modifierFlags.contains(.control)) { 19 | return false 20 | } else { 21 | return super.validateProposedFirstResponder(responder, for: event) 22 | } 23 | } else { 24 | return super.validateProposedFirstResponder(responder, for: event) 25 | } 26 | } 27 | 28 | open override func menu(for event: NSEvent) -> NSMenu? { 29 | // Notify the delegate if a context menu is requested for an item 30 | let point = self.convert(event.locationInWindow, from: nil) 31 | let row = self.row(at: point) 32 | let item = self.item(atRow: row) 33 | if item == nil { 34 | return nil 35 | } 36 | return (self.delegate as! ExtOutlineViewDelegate).outlineView(self, menuForItem: item!) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/AppKit/ImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | import PLRelationalBinding 8 | 9 | open class ImageView: NSImageView { 10 | 11 | public lazy var img: BindableProperty = WriteOnlyProperty(set: { [unowned self] value, _ in 12 | self.image = value.nsimage 13 | }) 14 | 15 | public override init(frame: NSRect) { 16 | super.init(frame: frame) 17 | } 18 | 19 | public required init?(coder: NSCoder) { 20 | super.init(coder: coder) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/AppKit/Label.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | import PLRelationalBinding 8 | 9 | open class Label: NSTextField { 10 | public override init(frame frameRect: NSRect) { 11 | super.init(frame: frameRect) 12 | setup() 13 | } 14 | 15 | public required init?(coder: NSCoder) { 16 | super.init(coder: coder) 17 | setup() 18 | } 19 | 20 | public private(set) lazy var string: BindableProperty = WriteOnlyProperty(set: { [weak self] value, _ in 21 | self?.stringValue = value 22 | }) 23 | 24 | public private(set) lazy var attributedString: BindableProperty = WriteOnlyProperty(set: { [weak self] value, _ in 25 | self?.attributedStringValue = value 26 | }) 27 | 28 | public private(set) lazy var bindable_textColor: BindableProperty = WriteOnlyProperty(set: { [weak self] value, _ in 29 | self?.textColor = value 30 | }) 31 | 32 | public private(set) lazy var visible: BindableProperty = WriteOnlyProperty(set: { [weak self] value, _ in 33 | self?.isHidden = !value 34 | }) 35 | } 36 | 37 | extension Label { 38 | fileprivate func setup() { 39 | self.drawsBackground = false 40 | self.isBezeled = false 41 | self.isEditable = false 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/AppKit/ProgressIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | import PLRelationalBinding 8 | 9 | open class ProgressIndicator: NSProgressIndicator { 10 | 11 | public lazy var visible: BindableProperty = WriteOnlyProperty(set: { [weak self] value, _ in 12 | self?.isHidden = !value 13 | if value { 14 | self?.startAnimation(nil) 15 | } else { 16 | self?.stopAnimation(nil) 17 | } 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/AppKit/TextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | import PLRelationalBinding 8 | 9 | open class TextView: NSTextView, NSTextViewDelegate { 10 | 11 | private lazy var _text: ExternalValueProperty = ExternalValueProperty( 12 | get: { [unowned self] in 13 | self.string 14 | }, 15 | set: { [unowned self] value, _ in 16 | self.string = value 17 | // XXX: Without the following sometimes part of the text will disappear, not sure why yet 18 | self.layoutManager?.invalidateLayout(forCharacterRange: NSMakeRange(0, value.count), actualCharacterRange: nil) 19 | } 20 | ) 21 | public var text: ReadWriteProperty { return _text } 22 | 23 | private var previousString: String? 24 | 25 | public override init(frame: NSRect) { 26 | super.init(frame: frame) 27 | configure() 28 | } 29 | 30 | public required init?(coder: NSCoder) { 31 | super.init(coder: coder) 32 | configure() 33 | } 34 | 35 | private func configure() { 36 | // XXX: Keep built-in undo support disabled until we decide how to make it play 37 | // nicely with our own undo stuff 38 | self.allowsUndo = false 39 | self.delegate = self 40 | } 41 | 42 | open func textDidBeginEditing(_ notification: Notification) { 43 | previousString = self.string 44 | } 45 | 46 | open func textDidEndEditing(_ notification: Notification) { 47 | if let previousString = previousString { 48 | if self.string != previousString { 49 | _text.changed(transient: false) 50 | } 51 | } 52 | previousString = nil 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/Shared/Bindable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | // The following is based on ReactiveSwift's ReactiveExtensionProvider protocol and Reactive proxy concepts. 9 | 10 | /// A provider of binding extensions. 11 | public protocol BindingExtensionsProvider: class { 12 | } 13 | 14 | /// A proxy that hosts binding extensions of `Base`. 15 | public struct Bindable { 16 | /// The `Base` instance used to invoke the extensions. 17 | public let base: Base 18 | 19 | fileprivate init(_ base: Base) { 20 | self.base = base 21 | } 22 | } 23 | 24 | extension BindingExtensionsProvider { 25 | /// A proxy that hosts binding extensions for `self`. 26 | public var bindable: Bindable { 27 | return Bindable(self) 28 | } 29 | 30 | /// A proxy that hosts static binding extensions for the type of `self`. 31 | public static var bindable: Bindable.Type { 32 | return Bindable.self 33 | } 34 | } 35 | 36 | // TODO: Eventually we should move Bindable and BindingExtensionsProvider to PLRelationalBinding, and then define 37 | // this NSObject extension in PLBindableControls. 38 | extension NSObject: BindingExtensionsProvider { 39 | } 40 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/Shared/CheckState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | #if os(macOS) 7 | import Cocoa 8 | #endif 9 | import PLRelationalBinding 10 | 11 | public enum CheckState: String { case 12 | on = "On", 13 | off = "Off", 14 | mixed = "Mixed" 15 | 16 | public init(_ boolValue: Bool?) { 17 | switch boolValue { 18 | case nil: 19 | self = .mixed 20 | case .some(false): 21 | self = .off 22 | case .some(true): 23 | self = .on 24 | } 25 | } 26 | 27 | public init(commonValue: CommonValue) { 28 | switch commonValue { 29 | case .none: 30 | self = .off 31 | case .one(let b): 32 | self = b ? .on : .off 33 | case .multi: 34 | self = .mixed 35 | } 36 | } 37 | 38 | #if os(macOS) 39 | public init(_ nsValue: NSControl.StateValue) { 40 | switch nsValue { 41 | case .mixed: 42 | self = .mixed 43 | case .off: 44 | self = .off 45 | case .on: 46 | self = .on 47 | default: 48 | preconditionFailure("Must be one of NSControl.StateValue.{mixed, off, on}") 49 | } 50 | } 51 | #endif 52 | 53 | public var boolValue: Bool { 54 | switch self { 55 | case .on: 56 | return true 57 | case .off: 58 | return false 59 | case .mixed: 60 | preconditionFailure("Cannot represent mixed state as a boolean") 61 | } 62 | } 63 | 64 | public var commonValue: CommonValue { 65 | switch self { 66 | case .on: 67 | return .one(true) 68 | case .off: 69 | return .one(false) 70 | case .mixed: 71 | return .multi 72 | } 73 | } 74 | 75 | #if os(macOS) 76 | /// The Cocoa-defined value that corresponds to this CheckState (NSOnState, NSOffState, or NSMixedState). 77 | public var nsValue: NSControl.StateValue { 78 | switch self { 79 | case .on: 80 | return .on 81 | case .off: 82 | return .off 83 | case .mixed: 84 | return .mixed 85 | } 86 | } 87 | #endif 88 | } 89 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/Shared/Image.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | #if os(macOS) 7 | import AppKit 8 | #else 9 | import UIKit 10 | #endif 11 | 12 | /// This serves as an insulation layer to avoid having platform-specific (i.e., AppKit or UIKit) 13 | /// dependencies in the model, which should be platform-independent. 14 | public class Image { 15 | 16 | #if os(macOS) 17 | public let nsimage: NSImage 18 | 19 | public init(_ nsimage: NSImage) { 20 | self.nsimage = nsimage 21 | } 22 | 23 | public init?(named name: String) { 24 | guard let nsimage = NSImage(named: name) else { return nil } 25 | self.nsimage = nsimage 26 | } 27 | #else 28 | public let uiimage: UIImage 29 | 30 | public init(_ uiimage: UIImage) { 31 | self.uiimage = uiimage 32 | } 33 | 34 | public init?(named name: String) { 35 | guard let uiimage = UIImage(named: name) else { return nil } 36 | self.uiimage = uiimage 37 | } 38 | #endif 39 | } 40 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/Shared/TextProperty.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import PLRelationalBinding 7 | 8 | /// A sum type that holds either a read-only or read-write String-typed property, used for 9 | /// binding the content of a TextField or similar control. 10 | public enum TextProperty { case 11 | readOnly(ReadableProperty), 12 | readWrite(ReadWriteProperty), 13 | asyncReadOnly(AsyncReadableProperty), 14 | asyncReadWrite(AsyncReadWriteProperty), 15 | readOnlyOpt(ReadableProperty), 16 | readWriteOpt(ReadWriteProperty), 17 | asyncReadOnlyOpt(AsyncReadableProperty), 18 | asyncReadWriteOpt(AsyncReadWriteProperty) 19 | 20 | var editable: Bool { 21 | switch self { 22 | case .readOnly, .asyncReadOnly, .readOnlyOpt, .asyncReadOnlyOpt: 23 | return false 24 | case .readWrite, .asyncReadWrite, .readWriteOpt, .asyncReadWriteOpt: 25 | return true 26 | } 27 | } 28 | } 29 | 30 | #if os(macOS) 31 | extension TextField { 32 | public func bind(_ property: TextProperty?) { 33 | string.unbindAll() 34 | optString.unbindAll() 35 | if let property = property { 36 | switch property { 37 | case .readOnly(let text): 38 | string <~ text 39 | case .readWrite(let text): 40 | string <~> text 41 | case .asyncReadOnly(let text): 42 | string <~ text 43 | case .asyncReadWrite(let text): 44 | string <~> text 45 | case .readOnlyOpt(let text): 46 | optString <~ text 47 | case .readWriteOpt(let text): 48 | optString <~> text 49 | case .asyncReadOnlyOpt(let text): 50 | optString <~ text 51 | case .asyncReadWriteOpt(let text): 52 | optString <~> text 53 | } 54 | isEditable = property.editable 55 | } else { 56 | isEditable = false 57 | } 58 | } 59 | } 60 | #endif 61 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/UIKit/BarButtonItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import UIKit 7 | import PLRelationalBinding 8 | 9 | // TODO: Convert this to Bindable extension 10 | open class BarButtonItem: UIBarButtonItem { 11 | 12 | public lazy var bindable_enabled: BindableProperty = { 13 | return WriteOnlyProperty(set: { [weak self] value, _ in 14 | self?.isEnabled = value 15 | }) 16 | }() 17 | 18 | private let _bindable_clicks = SourceSignal<()>() 19 | public var bindable_clicks: Signal<()> { return _bindable_clicks } 20 | 21 | public override init() { 22 | super.init() 23 | target = self 24 | action = #selector(buttonClicked(_:)) 25 | } 26 | 27 | public required init?(coder: NSCoder) { 28 | super.init(coder: coder) 29 | target = self 30 | action = #selector(buttonClicked(_:)) 31 | } 32 | 33 | @objc func buttonClicked(_ sender: BarButtonItem) { 34 | _bindable_clicks.notifyAction() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/UIKit/ImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import UIKit 7 | import PLRelationalBinding 8 | 9 | extension Bindable where Base: UIImageView { 10 | 11 | public var image: BindableProperty { 12 | return WriteOnlyProperty(set: { [weak base = self.base] value, _ in 13 | base?.image = value.uiimage 14 | }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/UIKit/Slider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import UIKit 7 | import PLRelationalBinding 8 | 9 | open class Slider: UISlider { 10 | 11 | private lazy var changeHandler: ChangeHandler = ChangeHandler( 12 | onLock: { [weak self] in 13 | guard let strongSelf = self else { return } 14 | strongSelf.isUserInteractionEnabled = false 15 | }, 16 | onUnlock: { [weak self] in 17 | guard let strongSelf = self else { return } 18 | strongSelf.isUserInteractionEnabled = true 19 | } 20 | ) 21 | 22 | private lazy var _bindable_value: ExternalValueProperty = ExternalValueProperty( 23 | get: { [weak self] in 24 | self?.value ?? 0.0 25 | }, 26 | set: { [weak self] newValue, _ in 27 | self?.value = newValue 28 | }, 29 | changeHandler: self.changeHandler 30 | ) 31 | public var bindable_value: ReadWriteProperty { return _bindable_value } 32 | 33 | public override init(frame: CGRect) { 34 | super.init(frame: frame) 35 | self.addTarget(self, action: #selector(updatedState(_:)), for: .valueChanged) 36 | } 37 | 38 | public required init?(coder: NSCoder) { 39 | super.init(coder: coder) 40 | self.addTarget(self, action: #selector(updatedState(_:)), for: .valueChanged) 41 | } 42 | 43 | @objc func updatedState(_ sender: Slider) { 44 | // TODO: Use transient: true if isContinuous==true and the value is still changing? 45 | _bindable_value.changed(transient: false) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/UIKit/Switch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import UIKit 7 | import PLRelationalBinding 8 | 9 | open class Switch: UISwitch { 10 | 11 | private lazy var changeHandler: ChangeHandler = ChangeHandler( 12 | onLock: { [weak self] in 13 | guard let strongSelf = self else { return } 14 | strongSelf.isUserInteractionEnabled = false 15 | }, 16 | onUnlock: { [weak self] in 17 | guard let strongSelf = self else { return } 18 | strongSelf.isUserInteractionEnabled = true 19 | } 20 | ) 21 | 22 | private lazy var _bindable_on: ExternalValueProperty = ExternalValueProperty( 23 | get: { [weak self] in 24 | self?.isOn ?? false 25 | }, 26 | set: { [weak self] newValue, _ in 27 | self?.setOn(newValue, animated: false) 28 | }, 29 | changeHandler: self.changeHandler 30 | ) 31 | public var bindable_on: ReadWriteProperty { return _bindable_on } 32 | 33 | public override init(frame: CGRect) { 34 | super.init(frame: frame) 35 | self.addTarget(self, action: #selector(updatedState(_:)), for: .valueChanged) 36 | } 37 | 38 | public required init?(coder: NSCoder) { 39 | super.init(coder: coder) 40 | self.addTarget(self, action: #selector(updatedState(_:)), for: .valueChanged) 41 | } 42 | 43 | @objc func updatedState(_ sender: Switch) { 44 | _bindable_on.changed(transient: false) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Legacy/PLBindableControls/Sources/UIKit/TextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import UIKit 7 | import PLRelationalBinding 8 | 9 | open class TextField: UITextField, UITextFieldDelegate { 10 | 11 | private lazy var _bindable_text: ExternalValueProperty = ExternalValueProperty( 12 | get: { 13 | self.text ?? "" 14 | }, 15 | set: { value, _ in 16 | self.text = value 17 | } 18 | ) 19 | public var bindable_text: ReadWriteProperty { return _bindable_text } 20 | 21 | /// Whether to deliver transient changes. If `true` a transient change will be delivered via the 22 | /// `string` property on each keystroke. If `false` (the default), no transient changes will be 23 | /// delivered, and only a single commit change will be delivered when the user is done editing. 24 | public var deliverTransientChanges: Bool = false 25 | 26 | private var previousCommittedValue: String? 27 | 28 | public override init(frame: CGRect) { 29 | super.init(frame: frame) 30 | self.configure() 31 | } 32 | 33 | public required init?(coder: NSCoder) { 34 | super.init(coder: coder) 35 | self.configure() 36 | } 37 | 38 | private func configure() { 39 | self.delegate = self 40 | self.addTarget(self, action: #selector(textFieldChanged(_:)), for: .editingChanged) 41 | } 42 | 43 | open func textFieldDidBeginEditing(_ textField: UITextField) { 44 | _bindable_text.exclusiveMode = true 45 | previousCommittedValue = self.text 46 | } 47 | 48 | @objc func textFieldChanged(_ sender: UITextField) { 49 | if deliverTransientChanges { 50 | _bindable_text.changed(transient: true) 51 | } 52 | } 53 | 54 | open func textFieldShouldReturn(_ textField: UITextField) -> Bool { 55 | return true 56 | } 57 | 58 | open func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) { 59 | if self.text != previousCommittedValue { 60 | _bindable_text.changed(transient: false) 61 | } 62 | _bindable_text.exclusiveMode = false 63 | previousCommittedValue = nil 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Legacy/PLRelationalBinding/Sources/ArrayBinarySearch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | extension Array { 7 | func binarySearch(_ lessThan: (Element) -> Bool) -> Int { 8 | return self[0..(_ element: Element, _ f: (Element) -> T, _ compare: (T, T) -> Bool) -> Int where T: Comparable { 12 | let val = f(element) 13 | 14 | let index = binarySearch({ compare(f($0), val) }) 15 | 16 | if index < count { 17 | insert(element, at: index) 18 | } else { 19 | append(element) 20 | } 21 | 22 | return index 23 | } 24 | } 25 | 26 | extension Array where Element: Comparable { 27 | func binarySearch(_ element: Element) -> Int { 28 | return self.binarySearch({ $0 < element }) 29 | } 30 | } 31 | 32 | extension ArraySlice { 33 | func binarySearch(_ lessThan: (Element) -> Bool) -> Int { 34 | if count == 0 { return startIndex } 35 | let mid = startIndex + ((endIndex - startIndex) / 2) 36 | if lessThan(self[mid]) { 37 | return self[(mid + 1).. Element? { 8 | return (index >= 0 && index < self.count) 9 | ? self[index] 10 | : nil 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Legacy/PLRelationalBinding/Sources/CollectionElement.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// :nodoc: Elided from docs to reduce clutter for now; part of "official" API but may be reworked in the near future 9 | public protocol CollectionElement: class { 10 | associatedtype ID: Hashable, Plistable 11 | associatedtype Data 12 | 13 | var id: ID { get } 14 | var data: Data { get set } 15 | } 16 | -------------------------------------------------------------------------------- /Legacy/PLRelationalBinding/Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Legacy/PLRelationalBinding/Sources/RelationAsyncProperty.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import PLRelational 7 | 8 | public struct RelationMutationConfig { 9 | public let snapshot: () -> TransactionalDatabaseSnapshot 10 | public let update: (_ newValue: T) -> Void 11 | public let commit: (_ before: TransactionalDatabaseSnapshot, _ newValue: T) -> Void 12 | 13 | public init( 14 | snapshot: @escaping () -> TransactionalDatabaseSnapshot, 15 | update: @escaping (_ newValue: T) -> Void, 16 | commit: @escaping (_ before: TransactionalDatabaseSnapshot, _ newValue: T) -> Void) 17 | { 18 | self.snapshot = snapshot 19 | self.update = update 20 | self.commit = commit 21 | } 22 | } 23 | 24 | private class RelationAsyncReadWriteProperty: AsyncReadWriteProperty { 25 | private let mutator: RelationMutationConfig 26 | private var before: TransactionalDatabaseSnapshot? 27 | 28 | init(signal: Signal, mutator: RelationMutationConfig) { 29 | self.mutator = mutator 30 | 31 | super.init(signal: signal) 32 | } 33 | 34 | fileprivate override func setValue(_ value: T, _ metadata: ChangeMetadata) { 35 | if before == nil { 36 | before = mutator.snapshot() 37 | } 38 | 39 | // Note: We don't set `mutableValue` here; instead we wait to receive the change from the 40 | // relation in our signal observer and then update `mutableValue` there 41 | if metadata.transient { 42 | mutator.update(value) 43 | } else { 44 | mutator.commit(before!, value) 45 | before = nil 46 | } 47 | } 48 | } 49 | 50 | extension SignalType { 51 | 52 | // MARK: Relation / AsyncReadWriteProperty convenience 53 | 54 | /// Lifts this signal into an AsyncReadWriteProperty that writes values back to a relation via the given mutator. 55 | public func property(mutator: RelationMutationConfig) -> AsyncReadWriteProperty { 56 | return RelationAsyncReadWriteProperty(signal: signal, mutator: mutator) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Legacy/PLRelationalBinding/Sources/RelationValuePlistable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import PLRelational 8 | 9 | /// :nodoc: Elided from docs to reduce clutter for now; part of "official" API but may be reworked in the near future 10 | public protocol Plistable { 11 | func toPlist() -> AnyObject 12 | static func fromPlist(_ obj: AnyObject) -> Self? 13 | } 14 | 15 | /// :nodoc: Elided from docs to reduce clutter for now; part of "official" API but may be reworked in the near future 16 | extension RelationValue: Plistable { 17 | public func toPlist() -> AnyObject { 18 | switch self { 19 | case let .integer(value): 20 | return ["type": "integer", "value": value.description] as NSDictionary 21 | case let .text(value): 22 | return ["type": "text", "value": value] as NSDictionary 23 | default: 24 | // TODO: Support other types 25 | return [:] as NSDictionary 26 | } 27 | } 28 | 29 | public static func fromPlist(_ obj: AnyObject) -> RelationValue? { 30 | if let plist = obj as? [String: String] { 31 | if let type = plist["type"], let stringValue = plist["value"] { 32 | switch type { 33 | case "integer": 34 | if let v = Int64(stringValue) { 35 | return RelationValue(v) 36 | } else { 37 | return nil 38 | } 39 | case "text": 40 | return RelationValue(stringValue) 41 | default: 42 | return nil 43 | } 44 | } else { 45 | return nil 46 | } 47 | } else { 48 | return nil 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Legacy/PLRelationalBinding/Sources/RowCollectionElement.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import PLRelational 8 | 9 | /// :nodoc: Elided from docs to reduce clutter for now; part of "official" API but may be reworked in the near future 10 | public class RowCollectionElement: CollectionElement { 11 | public typealias ID = RelationValue 12 | public typealias Data = Row 13 | 14 | public let id: RelationValue 15 | public var data: Row 16 | public let tag: AnyObject? 17 | 18 | init(id: RelationValue, data: Row, tag: AnyObject?) { 19 | self.id = id 20 | self.data = data 21 | self.tag = tag 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Legacy/PLRelationalBinding/Tests/ArrayBinarySearchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import XCTest 7 | @testable import PLRelationalBinding 8 | 9 | class ArrayBinarySearchTests: XCTestCase { 10 | 11 | func testBinarySearch() { 12 | XCTAssertEqual([].binarySearch(42), 0) 13 | XCTAssertEqual([0, 2, 4, 6].binarySearch(-1), 0) 14 | XCTAssertEqual([0, 2, 4, 6].binarySearch(0), 0) 15 | XCTAssertEqual([0, 2, 4, 6].binarySearch(1), 1) 16 | XCTAssertEqual([0, 2, 4, 6].binarySearch(2), 1) 17 | XCTAssertEqual([0, 2, 4, 6].binarySearch(3), 2) 18 | XCTAssertEqual([0, 2, 4, 6].binarySearch(4), 2) 19 | XCTAssertEqual([0, 2, 4, 6].binarySearch(5), 3) 20 | XCTAssertEqual([0, 2, 4, 6].binarySearch(6), 3) 21 | XCTAssertEqual([0, 2, 4, 6].binarySearch(7), 4) 22 | XCTAssertEqual([0, 2, 4, 6].binarySearch(8), 4) 23 | } 24 | 25 | func testInsertSorted() { 26 | 27 | func insert(_ v: Int, _ vs: [Int], _ expected: [Int], _ expectedIndex: Int) { 28 | var mutvs = vs 29 | let index = mutvs.insertSorted(v, { $0 }, <) 30 | XCTAssertEqual(mutvs, expected) 31 | XCTAssertEqual(index, expectedIndex) 32 | } 33 | 34 | insert(42, [], [42], 0) 35 | insert(-1, [0, 2, 4, 6], [-1, 0, 2, 4, 6], 0) 36 | insert( 0, [0, 2, 4, 6], [0, 0, 2, 4, 6], 0) 37 | insert( 1, [0, 2, 4, 6], [0, 1, 2, 4, 6], 1) 38 | insert( 2, [0, 2, 4, 6], [0, 2, 2, 4, 6], 1) 39 | insert( 3, [0, 2, 4, 6], [0, 2, 3, 4, 6], 2) 40 | insert( 4, [0, 2, 4, 6], [0, 2, 4, 4, 6], 2) 41 | insert( 5, [0, 2, 4, 6], [0, 2, 4, 5, 6], 3) 42 | insert( 6, [0, 2, 4, 6], [0, 2, 4, 6, 6], 3) 43 | insert( 7, [0, 2, 4, 6], [0, 2, 4, 6, 7], 4) 44 | insert( 8, [0, 2, 4, 6], [0, 2, 4, 6, 8], 4) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Legacy/PLRelationalBinding/Tests/ChangeHandlerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import XCTest 7 | @testable import PLRelationalBinding 8 | 9 | class ChangeHandlerTests: BindingTestCase { 10 | 11 | func testBasic() { 12 | var lockCount = 0 13 | var unlockCount = 0 14 | 15 | let handler = ChangeHandler(onLock: { lockCount += 1 }, onUnlock: { unlockCount += 1 }) 16 | XCTAssertEqual(lockCount, 0) 17 | XCTAssertEqual(unlockCount, 0) 18 | 19 | handler.willChange() 20 | XCTAssertEqual(lockCount, 1) 21 | XCTAssertEqual(unlockCount, 0) 22 | 23 | handler.willChange() 24 | XCTAssertEqual(lockCount, 1) 25 | XCTAssertEqual(unlockCount, 0) 26 | 27 | handler.didChange() 28 | XCTAssertEqual(lockCount, 1) 29 | XCTAssertEqual(unlockCount, 0) 30 | 31 | handler.didChange() 32 | XCTAssertEqual(lockCount, 1) 33 | XCTAssertEqual(unlockCount, 1) 34 | 35 | handler.incrementCount(3) 36 | XCTAssertEqual(lockCount, 2) 37 | XCTAssertEqual(unlockCount, 1) 38 | 39 | handler.didChange() 40 | XCTAssertEqual(lockCount, 2) 41 | XCTAssertEqual(unlockCount, 1) 42 | 43 | handler.decrementCount(2) 44 | XCTAssertEqual(lockCount, 2) 45 | XCTAssertEqual(unlockCount, 2) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Legacy/PLRelationalBinding/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Legacy/PLRelationalLegacy.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Legacy/PLRelationalLegacy.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Legacy/PLRelationalLegacy.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PLRelational/Modules/module-sqlite3-include.h: -------------------------------------------------------------------------------- 1 | 2 | // modulemaps don't automatically pull in SDK-relative system headers. 3 | // But if we indirect through a header file, they do! 4 | // So have the modulemap include this, which includes the system header. 5 | #include 6 | 7 | // We also need SQLite's tokenizer declarations, which the standard header does not include. 8 | #include "SQLiteTokenizerAPI.h" 9 | -------------------------------------------------------------------------------- /PLRelational/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | module sqlite3 [system] { 2 | header "module-sqlite3-include.h" 3 | link "sqlite3" 4 | export * 5 | } 6 | -------------------------------------------------------------------------------- /PLRelational/PLRelational.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PLRelational/PLRelational.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PLRelational/PLRelational.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PLRelational/Sources/ConsistentPRNG.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Darwin 7 | 8 | /// :nodoc: Implementation detail (will be made non-public eventually) 9 | /// A PRNG that produces consistent output on each run. Obviously, don't use this for anything 10 | /// where randomness is relevant to security. Intended for pseudorandom test cases that can be 11 | /// replicated on each run. 12 | public struct ConsistentPRNG { 13 | private var state: [UInt16] = Array(repeating: 0, count: 3) 14 | 15 | public mutating func next() -> Int { 16 | return nrand48(&state) 17 | } 18 | 19 | public mutating func next(_ limit: Int) -> Int { 20 | return next() % limit 21 | } 22 | 23 | var max: Int { 24 | return 0x7fffffff 25 | } 26 | } 27 | 28 | private let nrand48: @convention(c) (UnsafeMutablePointer) -> Int = loadFunc("nrand48") 29 | 30 | private func loadFunc(_ name: String) -> T { 31 | let RTLD_DEFAULT = UnsafeMutableRawPointer(bitPattern: -2) 32 | let address = dlsym(RTLD_DEFAULT, name) 33 | return unsafeBitCast(address, to: T.self) 34 | } 35 | -------------------------------------------------------------------------------- /PLRelational/Sources/Crypto.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import CommonCrypto 7 | 8 | /// :nodoc: Implementation detail (will be made non-public eventually) 9 | public func SHA256(_ data: [UInt8]) -> [UInt8] { 10 | var output: [UInt8] = Array(repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) 11 | CC_SHA256(data, CC_LONG(data.count), &output) 12 | return output 13 | } 14 | 15 | /// :nodoc: Implementation detail (will be made non-public eventually) 16 | public func hexString(_ data: [UInt8], uppercase: Bool) -> String { 17 | struct Static { 18 | static let hexCharsUpper: [Character] = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"] 19 | static let hexCharsLower: [Character] = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"] 20 | } 21 | 22 | let chars = uppercase ? Static.hexCharsUpper : Static.hexCharsLower 23 | 24 | var output = String() 25 | output.reserveCapacity(data.count * 2) 26 | 27 | for byte in data { 28 | let upperNibble = byte >> 4 29 | let lowerNibble = byte & 0xf 30 | 31 | for nibble in [upperNibble, lowerNibble] { 32 | output.append(chars[Int(nibble)]) 33 | } 34 | } 35 | 36 | return output 37 | } 38 | -------------------------------------------------------------------------------- /PLRelational/Sources/DJBHash.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | /// :nodoc: Implementation detail (will be made non-public eventually) 7 | /// An implementation of the DJB hash function, adapted from http://stackoverflow.com/questions/31438210/how-to-implement-the-hashable-protocol-in-swift-for-an-int-array-a-custom-strin 8 | public struct DJBHash { 9 | public private(set) var value: Int = 5381 10 | 11 | public mutating func combine(_ new: Int) { 12 | value = (value << 5) &+ value &+ new 13 | } 14 | 15 | public init() {} 16 | 17 | public static func hash(values: S) -> Int where S.Iterator.Element == Int { 18 | var hash = DJBHash() 19 | for value in values { 20 | hash.combine(value) 21 | } 22 | return hash.value 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PLRelational/Sources/DataCodec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | 9 | public protocol DataCodec { 10 | func encode(_ data: Data) -> Result 11 | func decode(_ data: Data) -> Result 12 | } 13 | -------------------------------------------------------------------------------- /PLRelational/Sources/DeallocObservation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// :nodoc: Implementation detail (will be made non-public eventually) 9 | /// Observe the deallocation of an object. 10 | /// 11 | /// - parameter target: The object to observe. 12 | /// - parameter f: The function to call when `target` is deallocated. 13 | /// - returns: A removal function. Call this to remove the observation before `target` has been deallocated. 14 | public func ObserveDeallocation(_ target: AnyObject, _ f: @escaping () -> Void) -> (() -> Void) { 15 | return mutex.locked({ 16 | let callOnDeinit: CallOnDeinit 17 | if let obj = objc_getAssociatedObject(target, key!) as? CallOnDeinit { 18 | callOnDeinit = obj 19 | } else { 20 | callOnDeinit = CallOnDeinit() 21 | objc_setAssociatedObject(target, key!, callOnDeinit, .OBJC_ASSOCIATION_RETAIN) 22 | } 23 | 24 | let counter = callOnDeinit.counter 25 | callOnDeinit.counter += 1 26 | 27 | callOnDeinit.calls[counter] = f 28 | 29 | return { [weak callOnDeinit] in 30 | mutex.locked({ 31 | _ = callOnDeinit?.calls.removeValue(forKey: counter) 32 | }) 33 | } 34 | }) 35 | } 36 | 37 | private class CallOnDeinit { 38 | var counter: UInt64 = 0 39 | var calls: [UInt64: () -> Void] = [:] 40 | 41 | deinit { 42 | for (_, call) in calls { 43 | call() 44 | } 45 | } 46 | } 47 | 48 | private let mutex = Mutex() 49 | private let key = malloc(1) 50 | -------------------------------------------------------------------------------- /PLRelational/Sources/Debug.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | 9 | /// :nodoc: Implementation detail (will be made non-public eventually) 10 | public func debugLog(_ items: Any..., file: String = #file, line: Int = #line) { 11 | let strings = items.map({ String(describing: $0) }) 12 | let fullString = strings.joined(separator: " ") 13 | let filename = URL(fileURLWithPath: file).lastPathComponent 14 | NSLog("%@:%ld: %@", filename, line, fullString) 15 | } 16 | 17 | /// :nodoc: Implementation detail (will be made non-public eventually) 18 | public func pointerString(_ obj: AnyObject) -> String { 19 | return String(format: "%p", unsafeBitCast(obj, to: Int.self)) 20 | } 21 | 22 | /// :nodoc: Implementation detail (will be made non-public eventually) 23 | public func pointerAndClassString(_ obj: AnyObject) -> String { 24 | return "<\(type(of: obj)): \(pointerString(obj))>" 25 | } 26 | -------------------------------------------------------------------------------- /PLRelational/Sources/DictionaryConvenience.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | /// :nodoc: Implementation detail (will be made non-public eventually) 7 | extension Dictionary where Value: Hashable { 8 | public var inverted: [Value: Key] { 9 | return Dictionary(self.map({ ($1, $0) })) 10 | } 11 | } 12 | 13 | /// :nodoc: Implementation detail (will be made non-public eventually) 14 | extension Dictionary { 15 | public mutating func getOrCreate(_ key: Key, defaultValue: @autoclosure () -> Value) -> Value { 16 | if let value = self[key] { 17 | return value 18 | } else { 19 | let new = defaultValue() 20 | self[key] = new 21 | return new 22 | } 23 | } 24 | 25 | public subscript(key: Key, defaultValue defaultValue: @autoclosure () -> Value) -> Value { 26 | mutating get { 27 | return getOrCreate(key, defaultValue: defaultValue()) 28 | } 29 | set { 30 | self[key] = newValue 31 | } 32 | } 33 | } 34 | 35 | /// :nodoc: Implementation detail (will be made non-public eventually) 36 | /// Combine a dictionary and some collection of key/value pairs, which may be a second dictionary. 37 | /// Any keys that exist in both will have the value from the second parameter in the result. 38 | public func +(a: [K: V], b: Seq) -> [K: V] where Seq.Iterator.Element == (K, V) { 39 | var result = a 40 | for (k, v) in b { 41 | result[k] = v 42 | } 43 | return result 44 | } 45 | 46 | /// :nodoc: Implementation detail (will be made non-public eventually) 47 | extension Dictionary { 48 | /// Initialize a dictionary with an array of key/value pairs. 49 | public init(_ pairs: [(Key, Value)]) { 50 | self.init(minimumCapacity: pairs.count) 51 | 52 | for (k, v) in pairs { 53 | self[k] = v 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PLRelational/Sources/DictionaryCreation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | /// :nodoc: Implementation detail (will be made non-public eventually) 7 | public extension Dictionary { 8 | init(_ seq: S) where S.Iterator.Element == (Key, Value) { 9 | self.init() 10 | for (k, v) in seq { 11 | self[k] = v 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PLRelational/Sources/ErrorConvenience.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | extension Error { 9 | var isFileNotFound: Bool { 10 | // NSData throws NSFileReadNoSuchFileError when the file doesn't exist. It doesn't seem to be documented 11 | // but given that it's an official Cocoa constant it seems safe enough. 12 | let codes = [NSFileNoSuchFileError, NSFileReadNoSuchFileError] 13 | return (self as NSError).domain == NSCocoaErrorDomain && codes.contains((self as NSError).code) 14 | } 15 | } 16 | 17 | extension NSError { 18 | static var fileNotFound: NSError { 19 | return NSError(domain: NSCocoaErrorDomain, code: NSFileReadNoSuchFileError, userInfo: nil) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PLRelational/Sources/GeneratorConcat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | /// :nodoc: Implementation detail (will be made non-public eventually) 7 | extension IteratorProtocol { 8 | public func concat(_ other: OtherGen) -> ConcatGenerator { 9 | return ConcatGenerator(self, other) 10 | } 11 | } 12 | 13 | /// :nodoc: Implementation detail (will be made non-public eventually) 14 | /// A GeneratorType which concatenates two other generators. It will produce all elements from 15 | /// the first generator, then all elements from the second. 16 | public struct ConcatGenerator: IteratorProtocol where G1.Element == G2.Element { 17 | var g1: G1? 18 | var g2: G2? 19 | 20 | public init(_ g1: G1, _ g2: G2) { 21 | self.g1 = g1 22 | self.g2 = g2 23 | } 24 | 25 | public mutating func next() -> G1.Element? { 26 | if g1 != nil { 27 | if let element = g1?.next() { 28 | return element 29 | } else { 30 | g1 = nil 31 | } 32 | } 33 | // NOTE: not else if, because we want to fall through after the g1 = nil above. 34 | if g2 != nil { 35 | if let element = g2?.next() { 36 | return element 37 | } else { 38 | g2 = nil 39 | } 40 | } 41 | 42 | return nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /PLRelational/Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /PLRelational/Sources/MemoryTableDatabase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// A minimal implementation of the `StoredDatabase` protocol that allows `MemoryTableRelation` to be used 9 | /// with `TransactionalDatabase`. 10 | public class MemoryTableDatabase: StoredDatabase { 11 | 12 | private var relations: Mutexed<[String: MemoryTableRelation]> 13 | 14 | public init(relations: [String: MemoryTableRelation] = [:]) { 15 | relations.forEach({ _ = $1.setDebugName($0) }) 16 | self.relations = Mutexed(relations) 17 | } 18 | 19 | public subscript(name: String) -> StoredRelation? { 20 | return storedRelation(forName: name) 21 | } 22 | 23 | public func storedRelation(forName name: String) -> StoredRelation? { 24 | return relations.withValue({ $0[name] }) 25 | } 26 | 27 | public func createRelation(_ name: String, scheme: Scheme) -> MemoryTableRelation { 28 | let relation = MemoryTableRelation(scheme: scheme) 29 | _ = relation.setDebugName(name) 30 | relations.withMutableValue({ $0[name] = relation }) 31 | return relation 32 | } 33 | 34 | public func transaction(_ transactionFunction: () -> (Return, TransactionResult)) -> Result { 35 | return .Ok(transactionFunction().0) 36 | } 37 | 38 | public func resultNeedsRetry(_ result: Result) -> Bool { 39 | return false 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /PLRelational/Sources/MirrorChildren.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | extension Mirror { 7 | var mirrorsIncludingSupertypes: [Mirror] { 8 | var result = [self] 9 | while let next = result.last?.superclassMirror { 10 | result.append(next) 11 | } 12 | return result 13 | } 14 | 15 | var childrenIncludingSupertypes: Children { 16 | let all = mirrorsIncludingSupertypes.flatMap({ $0.children }) 17 | return AnyCollection(all) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PLRelational/Sources/MutableBox.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | 7 | /// A really simple generic class that boxes a mutable value. 8 | /// The intent is to allow for in-place mutations of value-typed collections in situations 9 | /// where Swift isn't smart enough to figure out how otherwise. 10 | class MutableBox { 11 | var value: T 12 | 13 | init(_ initialValue: T) { 14 | value = initialValue 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PLRelational/Sources/MutableRelation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | public protocol MutableRelation: class, Relation { 7 | func add(_ row: Row) -> Result 8 | func delete(_ query: SelectExpression) -> Result 9 | } 10 | 11 | public protocol MutableSelectRelation: class, Relation { 12 | var selectExpression: SelectExpression { get set } 13 | } 14 | -------------------------------------------------------------------------------- /PLRelational/Sources/Mutex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | 7 | import Foundation 8 | 9 | /// :nodoc: Implementation detail (will be made non-public eventually) 10 | /// A simple mutex with a Swifty API. Note: despite being a struct, it acts like a reference type. 11 | public struct Mutex { 12 | fileprivate let lock = NSLock() 13 | 14 | /// Call the given function with the lock locked, automatically unlocking before returning or throwing. 15 | public func locked(_ f: () throws -> T) rethrows -> T { 16 | lock.lock() 17 | defer { lock.unlock() } 18 | return try f() 19 | } 20 | } 21 | 22 | /// :nodoc: Implementation detail (will be made non-public eventually) 23 | /// A wrapper that holds a value and a mutex, and allows accessing that value with the mutex held. 24 | /// When the wrapped type is a reference type, this will act like a reference type. When the 25 | /// wrapped type is a value type, this will act weirdly, with the mutex being shared among copies 26 | /// but the value being separate in each copy. This struct really shouldn't be copied, just have one. 27 | public struct Mutexed { 28 | fileprivate let mutex = Mutex() 29 | 30 | fileprivate var value: T 31 | 32 | public init(_ value: T) { 33 | self.value = value 34 | } 35 | 36 | /// Call the given function with the lock locked, passing in the value so it can be used while locked. 37 | public func withValue(_ f: (T) throws -> Result) rethrows -> Result { 38 | return try mutex.locked({ 39 | return try f(value) 40 | }) 41 | } 42 | 43 | /// Call the given function with the lock locked, passing in the value as inout so it can be used or 44 | /// mutated while locked. 45 | public mutating func withMutableValue(_ f: (inout T) throws -> Result) rethrows -> Result { 46 | return try mutex.locked({ 47 | return try f(&value) 48 | }) 49 | } 50 | 51 | /// Fetch the wrapped value. Obviously, only use this when it's safe to use (but not necessarily fetch) 52 | /// the value without locking, like with value types. 53 | public func get() -> T { 54 | return withValue({ $0 }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PLRelational/Sources/NSURLConvenience.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | 9 | /// :nodoc: Implementation detail (will be made non-public eventually) 10 | extension URL { 11 | public var isDirectory: Result { 12 | do { 13 | let values = try resourceValues(forKeys: [.isDirectoryKey]) 14 | return .Ok(values.isDirectory ?? false) 15 | } catch let error as NSError { 16 | return .Err(error) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PLRelational/Sources/NegativeSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | /// :nodoc: Implementation detail (will be made non-public eventually) 7 | /// A Set which tracks objects removed from an empty set, conceptually as an object 8 | /// with a count of -1. Removing an object and then adding it results in the set 9 | /// not containing that object. It only supports added and removed, not arbitrary 10 | /// counts. 11 | public struct NegativeSet { 12 | /// The values which have been added to the set. 13 | public fileprivate(set) var added: Set = [] 14 | 15 | /// The values which have been removed from the set. 16 | public fileprivate(set) var removed: Set = [] 17 | 18 | /// Create a new set. 19 | public init() {} 20 | 21 | /// Add all elements in `set` to this set. 22 | public mutating func unionInPlace(_ set: Set) { 23 | let new = set.fastSubtracting(removed) 24 | added.formUnion(new) 25 | removed.fastSubtract(set) 26 | } 27 | 28 | /// Remove all elements in `set` from this set. 29 | public mutating func subtractInPlace(_ set: Set) { 30 | let gone = set.fastSubtracting(added) 31 | removed.formUnion(gone) 32 | added.fastSubtract(set) 33 | } 34 | 35 | /// Clear this set by resetting `added` and `removed` to empty sets. 36 | public mutating func removeAll() { 37 | added.removeAll() 38 | removed.removeAll() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /PLRelational/Sources/ObjectCasting.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | 7 | func asObject(_ value: Any) -> AnyObject? { 8 | if type(of: value) is AnyObject.Type { 9 | return value as AnyObject 10 | } else { 11 | return nil 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /PLRelational/Sources/ObjectDictionary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | /// Like Dictionary, but the keys use object identity rather than value equality. 7 | struct ObjectDictionary: Sequence, ExpressibleByDictionaryLiteral { 8 | fileprivate var dict: Dictionary, Value> 9 | 10 | init(_ seq: S) where S.Iterator.Element == (Key, Value) { 11 | dict = Dictionary(seq.map({ (ObjectSetWrapper(object: $0), $1) })) 12 | } 13 | 14 | init(dictionaryLiteral elements: (Key, Value)...) { 15 | self.init(elements) 16 | } 17 | 18 | func makeIterator() -> AnyIterator<(Key, Value)> { 19 | let gen = dict.lazy.map({ ($0.object, $1) }).makeIterator() 20 | return AnyIterator(gen) 21 | } 22 | 23 | subscript(key: Key) -> Value? { 24 | get { 25 | return dict[ObjectSetWrapper(object: key)] 26 | } 27 | set { 28 | dict[ObjectSetWrapper(object: key)] = newValue 29 | } 30 | } 31 | 32 | subscript(key: Key, defaultValue defaultValue: @autoclosure () -> Value) -> Value { 33 | mutating get { 34 | return getOrCreate(key, defaultValue: defaultValue()) 35 | } 36 | set { 37 | self[key] = newValue 38 | } 39 | } 40 | 41 | mutating func getOrCreate(_ key: Key, defaultValue: @autoclosure () -> Value) -> Value { 42 | if let value = self[key] { 43 | return value 44 | } else { 45 | let new = defaultValue() 46 | self[key] = new 47 | return new 48 | } 49 | } 50 | 51 | var keys: [Key] { 52 | return dict.keys.map({ $0.object }) 53 | } 54 | 55 | var isEmpty: Bool { 56 | return dict.isEmpty 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /PLRelational/Sources/ObjectSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | /// :nodoc: Implementation detail (will be made non-public eventually) 7 | /// Like a Set, but based on object identity rather than value equality. 8 | public struct ObjectSet: Sequence { 9 | fileprivate var set: Set> 10 | 11 | public init(_ elements: [T]) { 12 | set = Set(elements.map(ObjectSetWrapper.init)) 13 | } 14 | 15 | public func makeIterator() -> AnyIterator { 16 | let gen = set.lazy.map({ $0.object }).makeIterator() 17 | return AnyIterator(gen) 18 | } 19 | 20 | public var isEmpty: Bool { 21 | return set.isEmpty 22 | } 23 | 24 | public mutating func insert(_ obj: T) { 25 | set.insert(ObjectSetWrapper(object: obj)) 26 | } 27 | 28 | public mutating func remove(_ obj: T) { 29 | set.remove(ObjectSetWrapper(object: obj)) 30 | } 31 | 32 | public func contains(_ obj: T) -> Bool { 33 | return set.contains(ObjectSetWrapper(object: obj)) 34 | } 35 | 36 | public var any: T? { 37 | return set.first?.object 38 | } 39 | 40 | public mutating func removeFirst() -> T { 41 | return set.removeFirst().object 42 | } 43 | } 44 | 45 | struct ObjectSetWrapper: Hashable { 46 | var object: T 47 | 48 | func hash(into hasher: inout Hasher) { 49 | hasher.combine(ObjectIdentifier(object)) 50 | } 51 | } 52 | 53 | func ==(a: ObjectSetWrapper, b: ObjectSetWrapper) -> Bool { 54 | return a.object === b.object 55 | } 56 | 57 | /// :nodoc: Implementation detail (will be made non-public eventually) 58 | extension ObjectSet: ExpressibleByArrayLiteral { 59 | public init(arrayLiteral elements: T...) { 60 | self.init(elements) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /PLRelational/Sources/OptionalFlatten.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | 7 | /// :nodoc: Implementation detail (will be made non-public eventually) 8 | /// Collapse a double optional into a single optional, transforming .some(nil) into nil. 9 | public func flatten(_ doubleOptional: T??) -> T? { 10 | switch doubleOptional { 11 | case .none, .some(.none): return nil 12 | case .some(.some(let wrapped)): return wrapped 13 | } 14 | } 15 | 16 | /// :nodoc: Implementation detail (will be made non-public eventually) 17 | /// Collapse two optionals into an optional tuple with non-optional components. 18 | /// If either parameter is nil, the return value is nil. 19 | public func flatten(_ t: T?, _ u: U?) -> (T, U)? { 20 | if let t = t, let u = u { 21 | return (t, u) 22 | } else { 23 | return nil 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PLRelational/Sources/PerThreadInstance.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | 9 | /// :nodoc: Implementation detail (will be made non-public eventually) 10 | public protocol PerThreadInstance: class { 11 | static var currentInstance: Self { get } 12 | 13 | init() 14 | } 15 | 16 | /// :nodoc: Implementation detail (will be made non-public eventually) 17 | extension PerThreadInstance { 18 | public static var currentInstance: Self { 19 | let key = NSValue(nonretainedObject: self) 20 | if let instance = Thread.current.threadDictionary[key] as? Self { 21 | return instance 22 | } else { 23 | let instance = self.init() 24 | Thread.current.threadDictionary[key] = instance 25 | return instance 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /PLRelational/Sources/Promise.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// :nodoc: Implementation detail (will be made non-public eventually) 9 | /// A simple implementation of a promise. You can set a value and wait for a value to be set. 10 | /// Thread safe, obviously. 11 | public class Promise { 12 | private let condition = NSCondition() 13 | private var value: T? = nil 14 | 15 | public init() {} 16 | 17 | public func fulfill(_ value: T) { 18 | condition.lock() 19 | precondition(self.value == nil) 20 | self.value = value 21 | condition.broadcast() 22 | condition.unlock() 23 | } 24 | 25 | public func get() -> T { 26 | condition.lock() 27 | while true { 28 | if let value = self.value { 29 | condition.unlock() 30 | return value 31 | } else { 32 | condition.wait() 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /PLRelational/Sources/RWLock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Darwin 7 | 8 | 9 | /// A reader-writer lock. Wraps pthread_rwlock. 10 | class RWLock { 11 | var lock: UnsafeMutablePointer 12 | 13 | init() { 14 | lock = UnsafeMutablePointer.allocate(capacity: 1) 15 | let err = pthread_rwlock_init(lock, nil) 16 | if err != 0 { 17 | fatalError("pthread_rwlock_init returned error \(err): \(String(cString: strerror(err)) )") 18 | } 19 | } 20 | 21 | deinit { 22 | let err = pthread_rwlock_destroy(lock) 23 | if err != 0 { 24 | fatalError("pthread_rwlock_destroy returned error \(err): \(String(cString: strerror(err)) )") 25 | } 26 | lock.deallocate() 27 | } 28 | 29 | func readLock() { 30 | pthread_rwlock_rdlock(lock) 31 | } 32 | 33 | func writeLock() { 34 | pthread_rwlock_wrlock(lock) 35 | } 36 | 37 | func unlock() { 38 | pthread_rwlock_unlock(lock) 39 | } 40 | 41 | func read(_ f: () -> T) -> T { 42 | readLock() 43 | defer { unlock() } 44 | return f() 45 | } 46 | 47 | func write(_ f: () -> T) -> T { 48 | writeLock() 49 | defer { unlock() } 50 | return f() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /PLRelational/Sources/RelationChange.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | public struct RelationChange { 7 | public let added: Relation? 8 | public let removed: Relation? 9 | 10 | public init(added: Relation?, removed: Relation?) { 11 | self.added = added 12 | self.removed = removed 13 | } 14 | } 15 | 16 | extension RelationChange { 17 | public func copy() -> Result { 18 | let copiedAdded = added.map(ConcreteRelation.copyRelation) 19 | let copiedRemoved = removed.map(ConcreteRelation.copyRelation) 20 | 21 | return 22 | hoistOptional(copiedAdded) 23 | .combine(hoistOptional(copiedRemoved)) 24 | .map({ RelationChange(added: $0, removed: $1) }) 25 | } 26 | } 27 | 28 | extension RelationChange: CustomStringConvertible { 29 | public var description: String { 30 | func stringForRelation(_ r: Relation?) -> String { 31 | guard let r = r else { return "∅" } 32 | if r.isEmpty.ok ?? false { return "∅" } 33 | return "\n\(r.description)\n" 34 | } 35 | return "RelationChange added: \(stringForRelation(added)) removed: \(stringForRelation(removed))" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /PLRelational/Sources/RelationObserver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | /// :nodoc: Implementation detail (will be made non-public eventually); also related to synchronous APIs, which are de-emphasized 7 | public protocol RelationObserver { 8 | func transactionBegan() 9 | func relationChanged(_ relation: Relation, change: RelationChange) 10 | func transactionEnded() 11 | } 12 | 13 | class WeakRelationObserverProxy: RelationObserver { 14 | fileprivate weak var target: (RelationObserver & AnyObject)? 15 | fileprivate var targetRemoval: () -> Void = { fatalError("Proxy deallocated, but target removal function never set.") } 16 | fileprivate var relationRemoval: () -> Void = { fatalError("Observer method called, but relation removal function never set.") } 17 | 18 | init(target: RelationObserver & AnyObject) { 19 | self.target = target 20 | targetRemoval = ObserveDeallocation(target, { [weak self] in 21 | self?.relationRemoval() 22 | }) 23 | } 24 | 25 | func registerOn(_ observee: Relation, kinds: [RelationObservationKind]) { 26 | relationRemoval = observee.addChangeObserver(self, kinds: kinds) 27 | } 28 | 29 | func transactionBegan() { 30 | getTargetOrRemove()?.transactionBegan() 31 | } 32 | 33 | func relationChanged(_ relation: Relation, change: RelationChange) { 34 | getTargetOrRemove()?.relationChanged(relation, change: change) 35 | } 36 | 37 | func transactionEnded() { 38 | getTargetOrRemove()?.transactionEnded() 39 | } 40 | 41 | fileprivate func getTargetOrRemove() -> RelationObserver? { 42 | if let target = target { 43 | return target 44 | } else { 45 | relationRemoval() 46 | return nil 47 | } 48 | } 49 | } 50 | 51 | struct SimpleRelationObserverProxy: RelationObserver { 52 | var f: (RelationChange) -> Void 53 | func transactionBegan() {} 54 | func relationChanged(_ relation: Relation, change: RelationChange) { 55 | f(change) 56 | } 57 | func transactionEnded() {} 58 | } 59 | -------------------------------------------------------------------------------- /PLRelational/Sources/RelationOperators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | infix operator ∩ : AdditionPrecedence 7 | 8 | /// Union two possibly-nil relations together. The result is nil if both inputs are nil. 9 | /// We use + instead of ∪ because it's easier to type and can still be understood. 10 | public func +(lhs: Relation?, rhs: Relation?) -> Relation? { 11 | switch (lhs, rhs) { 12 | case let (.some(lhs), .some(rhs)): 13 | return lhs.union(rhs) 14 | case let (.some(lhs), .none): 15 | return lhs 16 | case let (.none, .some(rhs)): 17 | return rhs 18 | case (.none, .none): 19 | return nil 20 | } 21 | } 22 | 23 | /// Subtract two possibly-nil relations. The result is nil if the lhs is nil, the lhs 24 | /// is returned if the rhs is nil, and the difference is returned if both exist. 25 | public func -(lhs: Relation?, rhs: Relation?) -> Relation? { 26 | if let lhs = lhs, let rhs = rhs { 27 | return lhs.difference(rhs) 28 | } else if let lhs = lhs { 29 | return lhs 30 | } else { 31 | return nil 32 | } 33 | } 34 | 35 | /// Intersect two possibly-nil relations. The result is nil if either operand is nil. 36 | /// I couldn't come up with a nice ASCII version of this operator, so we get the real 37 | /// untypeable thing. Use copy/paste or the character viewer. 38 | public func ∩(lhs: Relation?, rhs: Relation?) -> Relation? { 39 | if let lhs = lhs, let rhs = rhs { 40 | return lhs.intersection(rhs) 41 | } else { 42 | return nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /PLRelational/Sources/RemovableSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | /// :nodoc: Implementation detail (will be made non-public eventually) 7 | /// A set of arbitrary objects which can be iterated and can later be removed. 8 | /// Ideal for observer callbacks. 9 | public class RemovableSet: Sequence { 10 | private var nextNumber: UInt64 = 0 11 | private var contents: [UInt64: T] = [:] 12 | 13 | public init() {} 14 | 15 | /// Add a value to the set. Returns a remover which, when called, removes the value from the set. 16 | public func add(_ value: T) -> RemovableSetRemover { 17 | let number = nextNumber 18 | nextNumber += 1 19 | 20 | contents[number] = value 21 | return { 22 | self.contents.removeValue(forKey: number) 23 | } 24 | } 25 | 26 | public func makeIterator() -> Dictionary.Values.Iterator { 27 | return contents.values.makeIterator() 28 | } 29 | } 30 | 31 | /// :nodoc: Implementation detail (will be made non-public eventually) 32 | /// A set of observer functions which take some parameter and return Void. 33 | /// The same as RemovableSet aside from constraining the content type and 34 | /// providing a notify method to make it easier to call the observers. 35 | public class RemovableFunctionSet: RemovableSet<(Params) -> Void> { 36 | public func notify(_ params: Params) { 37 | for f in self { 38 | f(params) 39 | } 40 | } 41 | } 42 | 43 | public typealias RemovableSetRemover = () -> Void 44 | -------------------------------------------------------------------------------- /PLRelational/Sources/SelectExpressionAnalysis.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | extension SelectExpression { 7 | /// Perform some basic simplifications of the expression. This short-circuits logical operators 8 | /// with a constant on one side and immediately evaluates operators with constant operands. 9 | func shallowSimplify() -> SelectExpression { 10 | switch self { 11 | case let op as SelectExpressionUnaryOperator: 12 | if let expr = op.expr as? Bool { 13 | return !expr 14 | } 15 | return self 16 | 17 | case let op as SelectExpressionBinaryOperator: 18 | switch (op.op, op.lhs, op.rhs) { 19 | case let (op, lhs as SelectExpressionConstantValue, rhs as SelectExpressionConstantValue): 20 | return op.evaluate(lhs.relationValue, rhs.relationValue) 21 | 22 | case let (_ as AndComparator, true, rhs): 23 | return rhs 24 | case (_ as AndComparator, false, _): 25 | return false 26 | case let (_ as AndComparator, lhs, true): 27 | return lhs 28 | case (_ as AndComparator, _, false): 29 | return false 30 | 31 | case let (_ as OrComparator, false, rhs): 32 | return rhs 33 | case (_ as OrComparator, true, _): 34 | return true 35 | case let (_ as OrComparator, lhs, false): 36 | return lhs 37 | case (_ as OrComparator, _, true): 38 | return true 39 | 40 | default: 41 | return self 42 | } 43 | 44 | default: 45 | return self 46 | } 47 | } 48 | 49 | /// Walk the entire expression, calling shallowSimplify as we go to simplify as much as possible. 50 | func deepSimplify() -> SelectExpression { 51 | return mapTree({ $0.shallowSimplify() }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /PLRelational/Sources/SequenceFind.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | extension Sequence { 7 | func find(_ predicate: (Iterator.Element) -> Bool) -> Iterator.Element? { 8 | for element in self { 9 | if predicate(element) { 10 | return element 11 | } 12 | } 13 | return nil 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /PLRelational/Sources/SetPowerSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | extension Set { 7 | var powerSet: Set> { 8 | if count == 0 { return [[]] } 9 | let first = self.first! 10 | let rest = self.subtracting([first]) 11 | let restPowerSet = rest.powerSet 12 | return restPowerSet.union(restPowerSet.map({ $0.union([first]) })) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PLRelational/Sources/SetSubtraction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | /// :nodoc: Implementation detail (will be made non-public eventually) 7 | extension Set { 8 | /// As of Xcode 8.3/Swift 3.1, the implementation of Set's `subtract` is kind of dumb: 9 | /// 10 | /// for item in other { 11 | /// remove(item) 12 | /// } 13 | /// 14 | /// That means that subtracting a large set from a small set is unnecessarily slow. 15 | /// This reimplements subtraction doing the same thing, but iterating over the smaller 16 | /// of the two sets. 17 | public mutating func fastSubtract(_ other: Set) { 18 | if other.count > self.count { 19 | self = Set(self.lazy.filter({ !other.contains($0) })) 20 | } else { 21 | for item in other { 22 | self.remove(item) 23 | } 24 | } 25 | } 26 | 27 | /// A non-mutating wrapper around fastSubtract. Just creates a mutable copy and mutates it 28 | /// using fastSubtract. 29 | public func fastSubtracting(_ other: Set) -> Set { 30 | var result = self 31 | result.fastSubtract(other) 32 | return result 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /PLRelational/Sources/StoredDatabase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | public enum TransactionResult { 9 | case commit 10 | case rollback 11 | case retry 12 | } 13 | 14 | public protocol StoredDatabase { 15 | func storedRelation(forName name: String) -> StoredRelation? 16 | 17 | func transaction(_ transactionFunction: () -> (Return, TransactionResult)) -> Result 18 | func resultNeedsRetry(_ result: Result) -> Bool 19 | } 20 | -------------------------------------------------------------------------------- /PLRelational/Sources/StoredRelation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | public protocol StoredRelation: MutableRelation { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /PLRelational/Sources/StringConvenience.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | extension String { 9 | func numericLessThan(_ other: String) -> Bool { 10 | return compare(other, options: .numeric, range: nil, locale: nil) == .orderedAscending 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /PLRelational/Sources/StringPad.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | extension String { 7 | func pad(to length: Int, with character: Character, after: Bool = true) -> String { 8 | var new = self 9 | while new.count < length { 10 | new.insert(character, at: after ? new.endIndex : new.startIndex) 11 | } 12 | return new 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PLRelational/Sources/UnaryOperator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | /// :nodoc: Elided from docs to reduce clutter for now; part of "official" API but may be reworked in the near future 7 | public protocol UnaryOperator { 8 | func evaluate(_ value: RelationValue) -> RelationValue 9 | } 10 | 11 | /// :nodoc: Elided from docs to reduce clutter for now; part of "official" API but may be reworked in the near future 12 | public struct NotOperator: UnaryOperator { 13 | public init() {} 14 | 15 | public func evaluate(_ value: RelationValue) -> RelationValue { 16 | return .integer(value.boolValue ? 0 : 1) 17 | } 18 | } 19 | 20 | /// :nodoc: Elided from docs to reduce clutter for now; part of "official" API but may be reworked in the near future 21 | extension NotOperator: CustomStringConvertible { 22 | public var description: String { 23 | return "!" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PLRelational/Sources/UnsafePointerReadWrite.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | 7 | import Darwin 8 | 9 | extension UnsafeRawPointer { 10 | func unalignedLoad(fromByteOffset: Int) -> T { 11 | var output: T = 0 12 | memcpy(&output, self + fromByteOffset, MemoryLayout.size) 13 | return output 14 | } 15 | } 16 | 17 | extension UnsafePointer { 18 | func unalignedLoad(fromByteOffset: Int) -> T { 19 | return UnsafeRawPointer(self).unalignedLoad(fromByteOffset: fromByteOffset) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PLRelational/Sources/ValueWithDestructor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | class ValueWithDestructor { 7 | var value: T 8 | let destructor: (T) -> Void 9 | 10 | init(value: T, destructor: @escaping (T) -> Void) { 11 | self.value = value 12 | self.destructor = destructor 13 | } 14 | 15 | deinit { 16 | destructor(value) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /PLRelational/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /PLRelational/Tests/InlineMutableDataTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import XCTest 7 | @testable import PLRelational 8 | 9 | class InlineMutableDataTests: XCTestCase { 10 | func testBigData() { 11 | var bigArray: [UInt8] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 12 | bigArray += bigArray 13 | bigArray += bigArray 14 | bigArray += bigArray 15 | bigArray += bigArray 16 | bigArray += bigArray 17 | bigArray += bigArray 18 | bigArray += bigArray 19 | bigArray += bigArray 20 | bigArray += bigArray 21 | bigArray += bigArray 22 | 23 | func assertContents(_ data: InlineMutableData) { 24 | XCTAssertEqual(data.length, bigArray.count) 25 | data.withUnsafeMutablePointerToElements({ 26 | XCTAssertTrue(memcmp($0, bigArray, data.length) == 0) 27 | }) 28 | } 29 | 30 | var a = InlineMutableData.make(10) 31 | a = InlineMutableData.append(a, pointer: bigArray, length: bigArray.count) 32 | assertContents(a) 33 | 34 | var b = InlineMutableData.make(bigArray.count) 35 | b = InlineMutableData.append(b, pointer: bigArray, length: bigArray.count) 36 | assertContents(b) 37 | 38 | var c = InlineMutableData.make(10) 39 | for elt in bigArray { 40 | var elt = elt 41 | c = InlineMutableData.append(c, pointer: &elt, length: 1) 42 | } 43 | assertContents(c) 44 | 45 | var d = InlineMutableData.make(10) 46 | for elt in bigArray { 47 | var elt = elt 48 | d = InlineMutableData.append(d, pointer: &elt, length: 1) 49 | } 50 | assertContents(d) 51 | 52 | XCTAssertEqual(a, b) 53 | XCTAssertEqual(a, c) 54 | XCTAssertEqual(a, d) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PLRelational/Tests/InternedUTF8StringTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import XCTest 7 | import PLRelational 8 | 9 | class InternedUTF8StringTests: XCTestCase { 10 | func testAll() { 11 | let abc = InternedUTF8String.get("abc") 12 | XCTAssertEqual(abc, InternedUTF8String.get("abc")) 13 | 14 | let def = InternedUTF8String.get("def") 15 | XCTAssertEqual(def, InternedUTF8String.get("def")) 16 | 17 | XCTAssertNotEqual(abc, def) 18 | XCTAssertTrue(abc < def) 19 | XCTAssertFalse(abc > def) 20 | XCTAssertEqual(abc.string, "abc") 21 | XCTAssertEqual(def.string, "def") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /PLRelational/Tests/QueryRunnerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import XCTest 7 | @testable import PLRelational 8 | 9 | class QueryRunnerTests: XCTestCase { 10 | func testLargerTree() { 11 | let a = MakeRelation(["number", "pilot", "equipment"]) 12 | let b = MakeRelation(["number", "pilot", "equipment"], [1, "Jones", "777"]) 13 | let c = MakeRelation(["number", "pilot", "equipment"]) 14 | let d = MakeRelation(["number", "pilot", "equipment"], [2, "Smith", "787"]) 15 | let e = MakeRelation(["number", "pilot", "equipment"]) 16 | let f = MakeRelation(["number", "pilot", "equipment"], [1, "Jones", "777"]) 17 | let g = MakeRelation(["number", "pilot", "equipment"], [3, "Johnson", "797"]) 18 | let h = MakeRelation(["number", "pilot", "equipment"]) 19 | let i = MakeRelation(["number", "pilot", "equipment"], [1, "Jones", "777"]) 20 | let j = MakeRelation(["number", "pilot", "equipment"], [2, "Smith", "787"]) 21 | 22 | let bc = b.difference(c) 23 | let abc = a.union(bc) 24 | let ef = e.difference(f) 25 | let def = d.difference(ef) 26 | let abcdef = abc.union(def) 27 | let hi = h.difference(i) 28 | let hij = hi.difference(j) 29 | let ghij = g.difference(hij) 30 | let abcdefghij = abcdef.union(ghij) 31 | 32 | AssertEqual(abcdefghij, 33 | MakeRelation( 34 | ["number", "pilot", "equipment"], 35 | [1, "Jones", "777"], 36 | [2, "Smith", "787"], 37 | [3, "Johnson", "797"])) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /PLRelational/Tests/RelationDifferentiatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import XCTest 7 | @testable import PLRelational 8 | 9 | class RelationDifferentiatorTests: XCTestCase { 10 | func testEquijoinDerivative() { 11 | let a = MakeRelation(["id", "text"], 12 | [1, "new"], 13 | [2, "blah blah blah"]) 14 | .setDebugName("a") 15 | let b = MakeRelation(["id", "title"], 16 | [1, "NEW"], 17 | [2, "Blah Blah Blah"]) 18 | .setDebugName("b") 19 | 20 | let joined = a.equijoin(b, matching: ["id": "id"]) 21 | .setDebugName("joined") 22 | 23 | let differentiator = RelationDifferentiator(relation: joined) 24 | let derivative = differentiator.computeDerivative() 25 | 26 | derivative.addChange(RelationChange( 27 | added: MakeRelation(["id", "text"], [1, "new"]), 28 | removed: MakeRelation(["id", "text"], [1, "old"])), toVariable: a) 29 | 30 | derivative.addChange(RelationChange( 31 | added: MakeRelation(["id", "title"], [1, "NEW"]), 32 | removed: MakeRelation(["id", "title"], [1, "OLD"])), toVariable: b) 33 | 34 | AssertEqual(derivative.change.added, MakeRelation(["id", "text", "title"], [1, "new", "NEW"])) 35 | AssertEqual(derivative.change.removed, MakeRelation(["id", "text", "title"], [1, "old", "OLD"])) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /PLRelationalCombine/PLRelationalCombine.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PLRelationalCombine/PLRelationalCombine.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PLRelationalCombine/PLRelationalCombine.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PLRelationalCombine/Sources/CancellableBag.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Combine 7 | 8 | /// Alias for a set of cancellables, with a `cancel` convenience method that 9 | /// allows for one-shot cancellation. 10 | public typealias CancellableBag = Set 11 | 12 | extension Set where Element: Cancellable { 13 | /// Cancel each cancellable in this set. 14 | public func cancel() { 15 | self.forEach{ $0.cancel() } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /PLRelationalCombine/Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /PLRelationalCombine/Sources/RelationChangeSummary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import PLRelational 7 | 8 | /// A container that summarizes a change made to a `Relation`, breaking down the change 9 | /// into distinct added, updated, and deleted items. 10 | public struct RelationChangeSummary { 11 | public let added: [T] 12 | public let updated: [T] 13 | public let deleted: [T] 14 | 15 | public var isEmpty: Bool { 16 | return added.isEmpty && updated.isEmpty && deleted.isEmpty 17 | } 18 | } 19 | 20 | extension NegativeSet where T == Row { 21 | 22 | func summary(idAttr: Attribute, _ mapFunc: (Row) -> V) -> RelationChangeSummary { 23 | // First gather the rows that are being deleted (or updated) 24 | var deletedRows = Set(self.removed) 25 | 26 | var addedItems: [V] = [] 27 | var updatedItems: [V] = [] 28 | for row in self.added { 29 | let id = row[idAttr] 30 | if let index = deletedRows.firstIndex(where: { $0[idAttr] == id }) { 31 | // A row with this identifier appears in both sets, so it must've been updated; the added set 32 | // will contain the new row contents 33 | updatedItems.append(mapFunc(row)) 34 | 35 | // We can be sure this item isn't being removed, so remove it from the set of deleted items 36 | deletedRows.remove(at: index) 37 | } else { 38 | // This is a newly added row 39 | addedItems.append(mapFunc(row)) 40 | } 41 | } 42 | 43 | let deletedItems: [V] = Array(deletedRows.map(mapFunc)) 44 | 45 | return RelationChangeSummary(added: addedItems, updated: updatedItems, deleted: deletedItems) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /PLRelationalCombine/Sources/UndoableDatabase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import PLRelational 8 | 9 | /// TODO: Docs 10 | public class UndoableDatabase { 11 | 12 | private let db: TransactionalDatabase 13 | private let undoManager: UndoManager 14 | 15 | public init(db: TransactionalDatabase, undoManager: UndoManager) { 16 | self.db = db 17 | self.undoManager = undoManager 18 | } 19 | 20 | /// TODO: Docs 21 | public func performUndoableAction(_ name: String, before: TransactionalDatabaseSnapshot? = nil, _ transactionFunc: @escaping () -> Void) { 22 | let deltaPromise = Promise() 23 | 24 | var actualBefore: TransactionalDatabaseSnapshot! 25 | if let before = before { 26 | actualBefore = before 27 | } else { 28 | AsyncManager.currentInstance.registerCheckpoint({ 29 | actualBefore = self.db.takeSnapshot() 30 | }) 31 | } 32 | transactionFunc() 33 | AsyncManager.currentInstance.registerCheckpoint({ 34 | let after = self.db.takeSnapshot() 35 | let delta = self.db.computeDelta(from: actualBefore, to: after) 36 | deltaPromise.fulfill(delta) 37 | }) 38 | 39 | undoManager.registerChange( 40 | name: name, 41 | perform: false, 42 | forward: { 43 | self.db.asyncApply(delta: deltaPromise.get()) 44 | }, 45 | backward: { 46 | self.db.asyncApply(delta: deltaPromise.get().reversed) 47 | } 48 | ) 49 | } 50 | 51 | /// TODO: Docs 52 | public func takeSnapshot() -> TransactionalDatabaseSnapshot { 53 | return self.db.takeSnapshot() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /PLRelationalCombine/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /PLRelationalCombine/Tests/WeakBindTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import XCTest 7 | import Combine 8 | import PLRelational 9 | @testable import PLRelationalCombine 10 | 11 | class WeakBindTests: CombineTestCase { 12 | 13 | func testLifetime() { 14 | 15 | class TestObject { 16 | var value: String = "" 17 | private var cancellableBag = CancellableBag() 18 | 19 | init(names: Relation) { 20 | // This is a long-lived publisher that will continue to observe 21 | // the underlying relation until it is cancelled (and it will be 22 | // cancelled once there are no more references to this 23 | // TestObject instance) 24 | names 25 | .oneString() 26 | .replaceError(with: "") 27 | .bind(to: \.value, on: self) 28 | .store(in: &cancellableBag) 29 | } 30 | 31 | deinit { 32 | cancellableBag.cancel() 33 | } 34 | } 35 | 36 | let names = MakeRelation(["name"], ["fred"]) 37 | var obj: TestObject? = TestObject(names: names) 38 | weak var weakObj = obj 39 | 40 | awaitIdle() 41 | XCTAssertEqual(weakObj?.value, "fred") 42 | 43 | // Verify that `bind` holds the given object weakly 44 | obj = nil 45 | XCTAssertNil(weakObj) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /PLRelationalCombine/Tests/WeakTwoWayBindTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | import XCTest 7 | import Combine 8 | import PLRelational 9 | @testable import PLRelationalCombine 10 | 11 | class WeakTwoWayBindTests: CombineTestCase { 12 | 13 | func testLifetime() { 14 | 15 | class TestObject: ObservableObject { 16 | @TwoWay var value: String = "" 17 | private var cancellableBag = CancellableBag() 18 | 19 | init(names: Relation) { 20 | // This is a long-lived publisher that will continue to observe 21 | // the underlying relation until it is cancelled (and it will be 22 | // cancelled once there are no more references to this 23 | // TestObject instance) 24 | names 25 | .bind(to: \._value, on: self, strategy: oneString) 26 | .store(in: &cancellableBag) 27 | } 28 | 29 | deinit { 30 | cancellableBag.cancel() 31 | } 32 | } 33 | 34 | let names = MakeRelation(["name"], ["fred"]) 35 | var obj: TestObject? = TestObject(names: names) 36 | weak var weakObj = obj 37 | 38 | awaitIdle() 39 | XCTAssertEqual(weakObj?.value, "fred") 40 | 41 | // Verify that `bind` holds the given object weakly 42 | obj = nil 43 | XCTAssertNil(weakObj) 44 | } 45 | } 46 | --------------------------------------------------------------------------------