├── Cartfile ├── Cartfile.resolved ├── GPM.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcshareddata │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── GPM.xcscheme ├── xcuserdata │ └── user.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── .travis.yml ├── GPMTests ├── github │ ├── response_repos_owner_repo_projects_project_number_columns.json │ ├── response_repos_owner_repo_projects_columns_column_id_card.json │ ├── response_repos_owner_repo_projects_columns_column_id_cards.json │ ├── GitHubTestsSupport.swift │ ├── response_repos_owner_repo_projects.json │ ├── response_user.json │ ├── GitHubServiceTests.swift │ └── response_repos_owner_repo_issues_number.json ├── ArrayUtilitiesTests.swift ├── Info.plist └── GPMTests.swift ├── GPM ├── GPM.entitlements ├── github │ ├── GitHubUser.swift │ ├── GitHubServer.swift │ ├── GitHubService+Users.swift │ ├── GitHubProject.swift │ ├── GitHubScope.swift │ ├── GitHubService+Issues.swift │ ├── GitHubIssue.swift │ ├── GitHubService.swift │ └── GitHubService+Projects.swift ├── HeaderCellView.swift ├── Column.swift ├── WindowController.swift ├── SplitViewController.swift ├── Card.swift ├── AppDelegate.swift ├── CardCellView.swift ├── Kanban.swift ├── AccessTokenService.swift ├── Info.plist ├── Array+Utilities.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── KanbanRegisterViewController.swift ├── KanbanColumnNoteRegisterViewController.swift ├── KanbanColumnNoteUpdateViewController.swift ├── KanbanViewController.swift ├── LoginViewController.swift ├── BookmarkViewController.swift ├── KanbanService.swift └── KanbanColumnTableViewController.swift ├── README.md ├── LICENSE └── .gitignore /Cartfile: -------------------------------------------------------------------------------- 1 | github "Alamofire/Alamofire" ~> 4.0 2 | github "kishikawakatsumi/KeychainAccess" ~> 3.0 3 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Alamofire/Alamofire" "4.0.1" 2 | github "kishikawakatsumi/KeychainAccess" "v3.0.0" 3 | -------------------------------------------------------------------------------- /GPM.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8 3 | before_install: 4 | - brew update 5 | - brew outdated carthage || brew upgrade carthage 6 | script: 7 | - carthage bootstrap --platform Mac 8 | - xcodebuild -project GPM.xcodeproj -scheme GPM build test 9 | -------------------------------------------------------------------------------- /GPMTests/github/response_repos_owner_repo_projects_project_number_columns.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 365, 4 | "name": "To Do", 5 | "project_url": "https://api.github.com/repos/project-management/projects/2", 6 | "created_at": "2016-09-05T14:18:44Z", 7 | "updated_at": "2016-09-05T14:22:28Z" 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /GPM.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SchemeUserState 5 | 6 | GPM.xcscheme 7 | 8 | 9 | SuppressBuildableAutocreation 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /GPM/GPM.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /GPM/github/GitHubUser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubUser.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/22. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | public class GitHubUser: NSObject { 12 | public let login: String 13 | 14 | init(login: String) { 15 | self.login = login 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GPM/HeaderCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeaderCellView.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/10/03. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class HeaderCellView: NSTableCellView { 12 | 13 | override func draw(_ dirtyRect: NSRect) { 14 | super.draw(dirtyRect) 15 | 16 | // Drawing code here. 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /GPMTests/github/response_repos_owner_repo_projects_columns_column_id_card.json: -------------------------------------------------------------------------------- 1 | { 2 | "column_url": "https://api.github.com/repos/api-playground/projects-test/projects/columns/366", 3 | "content_url": "https://api.github.com/repos/api-playground/projects-test/issues/3", 4 | "id": 1478, 5 | "note": "Add payload for delete Project column", 6 | "created_at": "2016-09-05T14:21:06Z", 7 | "updated_at": "2016-09-05T14:20:22Z" 8 | } -------------------------------------------------------------------------------- /GPM/Column.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Column.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/19. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | struct Column: Hashable, Equatable { 12 | let id: Int 13 | 14 | let name: String 15 | 16 | var hashValue: Int { 17 | return id 18 | } 19 | } 20 | 21 | func == (lhs: Column, rhs: Column) -> Bool { 22 | return lhs.id == rhs.id 23 | } 24 | -------------------------------------------------------------------------------- /GPM/WindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WindowController.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/19. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class WindowController: NSWindowController { 12 | override func windowDidLoad() { 13 | super.windowDidLoad() 14 | 15 | // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GPMTests/ArrayUtilitiesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArrayUtilitiesTests.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/10/01. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import GPM 11 | 12 | class ArrayUtilitiesTests: XCTestCase { 13 | 14 | func testMoveItems() { 15 | let array = [0,1,2,3,4,5,6,7,8,9] 16 | let array2 = array.moveItems(from: IndexSet(arrayLiteral: 0,2,4,6,8), to: 4) 17 | XCTAssertEqual([1,3,0,2,4,6,8,5,7,9], array2) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /GPM/github/GitHubServer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubServer.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/24. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct GitHubServer { 12 | /** 13 | * API endpoint URL. This url must end with a slash. 14 | * 15 | * example: https://api.github.com/ 16 | */ 17 | let apiBaseURL: NSURL 18 | 19 | /** 20 | * Web front URL. This url must end with a slash. 21 | * 22 | * example: https://github.com/ 23 | */ 24 | let webBaseURL: NSURL 25 | } 26 | -------------------------------------------------------------------------------- /GPM.xcodeproj/xcuserdata/user.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SuppressBuildableAutocreation 8 | 9 | CE891A8E1D8CC7EA000A9711 10 | 11 | primary 12 | 13 | 14 | CE891AA11D8CC7EB000A9711 15 | 16 | primary 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /GPMTests/github/response_repos_owner_repo_projects_columns_column_id_cards.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "column_url": "https://api.github.com/repos/api-playground/projects-test/projects/columns/366", 4 | "id": 1478, 5 | "note": "Delete Project column", 6 | "created_at": "2016-09-05T14:21:06Z", 7 | "updated_at": "2016-09-05T14:20:22Z" 8 | }, 9 | { 10 | "column_url": "https://api.github.com/repos/mtgto/GPM/projects/columns/80135", 11 | "id": 134958, 12 | "note": null, 13 | "created_at": "2016-09-18T03:04:23Z", 14 | "updated_at": "2016-09-18T03:04:23Z", 15 | "content_url": "https://api.github.com/repos/mtgto/GPM/issues/4" 16 | }, 17 | ] 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GPM 2 | ==== 3 | [![Build Status](https://travis-ci.org/mtgto/GPM.svg?branch=master)](https://travis-ci.org/mtgto/GPM) 4 | 5 | macOS application for easily operating GitHub Projects. 6 | 7 | **IMPORTANT: Currently, this application has a limitation. You can't see over 30 cards per each column. This limit comes from [API limits](https://developer.github.com/v3/repos/projects/#preview-period).** 8 | 9 | ## Requirements 10 | - Mac OS X 10.11 or later 11 | 12 | ## Builds 13 | ```sh 14 | $ carthage bootstrap --platform Mac 15 | $ open GPM.xcodeproj 16 | ``` 17 | 18 | ## License 19 | [MIT](/LICENSE) 20 | 21 | ## Author 22 | [mtgto](https://github.com/mtgto) 23 | 24 | -------------------------------------------------------------------------------- /GPM/SplitViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplitViewController.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/19. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class SplitViewController: NSSplitViewController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | // Do view setup here. 15 | if let bookmarkViewController = self.splitViewItems[0].viewController as? BookmarkViewController, let kanbanViewController = self.splitViewItems[1].viewController as? KanbanViewController { 16 | bookmarkViewController.delegate = kanbanViewController 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /GPM/github/GitHubService+Users.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubService+Users.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/22. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Alamofire 11 | 12 | public extension GitHubService { 13 | public func fetchUser(handler: @escaping (GitHubResponse) -> Void) { 14 | self.fetch(path: "user", parser: self.parseUser, handler: handler) 15 | } 16 | 17 | func parseUser(_ data: Any) -> GitHubUser? { 18 | if let user = data as? [String:Any] { 19 | if let login = user["login"] as? String { 20 | return GitHubUser(login: login) 21 | } 22 | } 23 | return nil 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /GPM/Card.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Card.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/18. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class Card: Hashable, Equatable { 12 | let id: Int 13 | 14 | let note: String? 15 | 16 | let issue: GitHubIssue? 17 | 18 | var title: String? { return note ?? issue?.title } 19 | 20 | var hashValue: Int { 21 | return self.id 22 | } 23 | 24 | init(id: Int, note: String?, issue: GitHubIssue?) { 25 | self.id = id 26 | self.note = note 27 | self.issue = issue 28 | } 29 | } 30 | 31 | func == (lhs: Card, rhs: Card) -> Bool { 32 | return lhs.id == rhs.id && lhs.note == rhs.note && lhs.issue == rhs.issue 33 | } 34 | -------------------------------------------------------------------------------- /GPM/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/17. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | 15 | 16 | func applicationDidFinishLaunching(_ aNotification: Notification) { 17 | // Insert code here to initialize your application 18 | if let accessToken = AccessTokenService.sharedInstance.accessToken() { 19 | GitHubService.sharedInstance.accessToken = accessToken 20 | } 21 | } 22 | 23 | func applicationWillTerminate(_ aNotification: Notification) { 24 | // Insert code here to tear down your application 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /GPMTests/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 | -------------------------------------------------------------------------------- /GPM/CardCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CardCellView.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/27. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class CardCellView: NSTableCellView { 12 | 13 | @IBOutlet weak var additionalTextField: NSTextField! 14 | 15 | override func awakeFromNib() { 16 | self.wantsLayer = true 17 | self.layer?.backgroundColor = NSColor.white.cgColor 18 | self.layer?.cornerRadius = 4.0 19 | self.layer?.borderWidth = 1.0 20 | self.layer?.borderColor = NSColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1.0).cgColor 21 | } 22 | 23 | override func draw(_ dirtyRect: NSRect) { 24 | super.draw(dirtyRect) 25 | 26 | // Drawing code here. 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /GPM/Kanban.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Kanban.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/18. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class Kanban: Hashable, Equatable { 12 | let owner: String 13 | let repo: String 14 | let number: Int 15 | var cards: [(GitHubProject.Column, [GitHubProject.Card])] = [] 16 | 17 | init(owner: String, repo: String, number: Int) { 18 | self.owner = owner 19 | self.repo = repo 20 | self.number = number 21 | } 22 | 23 | var hashValue: Int { 24 | return "\(owner)/\(repo)/\(number)".hashValue 25 | } 26 | } 27 | 28 | func ==(lhs: Kanban, rhs: Kanban) -> Bool { 29 | return lhs.owner == rhs.owner && lhs.repo == rhs.repo && lhs.number == rhs.number 30 | } 31 | -------------------------------------------------------------------------------- /GPM/AccessTokenService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessTokenService.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/21. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import KeychainAccess 11 | 12 | class AccessTokenService: NSObject { 13 | static let sharedInstance: AccessTokenService = AccessTokenService() 14 | 15 | private static let ServiceName = "net.mtgto.GPM.github-accesstoken" 16 | 17 | private static let AccessTokenKey = "api.github.com" 18 | 19 | func accessToken() -> String? { 20 | let keychain = Keychain(service: AccessTokenService.ServiceName) 21 | return try! keychain.get(AccessTokenService.AccessTokenKey) 22 | } 23 | 24 | func setAccessToken(_ accessToken: String) { 25 | let keychain = Keychain(service: AccessTokenService.ServiceName) 26 | keychain[AccessTokenService.AccessTokenKey] = accessToken 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /GPMTests/github/GitHubTestsSupport.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubTestsSupport.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/18. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | protocol GitHubTestsSupport { 12 | func dataFromResourceFile(_ filename: String) -> Data 13 | } 14 | 15 | extension GitHubTestsSupport where Self: XCTestCase { 16 | func URLForResourceFile(_ filename: String) -> URL { 17 | let baseName = (filename as NSString).deletingPathExtension 18 | let ext = (filename as NSString).pathExtension 19 | let url = Bundle(for: type(of: self)).url(forResource: baseName, withExtension: ext) 20 | XCTAssertNotNil(url, "Resource file is not found") 21 | return url! 22 | } 23 | 24 | func dataFromResourceFile(_ filename: String) -> Data { 25 | let data = try? Data(contentsOf: self.URLForResourceFile(filename)) 26 | XCTAssertNotNil(data, "Failed to load resource from filename \"\(filename)\"") 27 | return data! 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /GPMTests/GPMTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GPMTests.swift 3 | // GPMTests 4 | // 5 | // Created by mtgto on 2016/09/17.s 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import GPM 11 | 12 | class GPMTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /GPM/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 © 2016 mtgto. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /GPM/Array+Utilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Utilities.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/10/01. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array { 12 | mutating func remove(at: IndexSet) { 13 | for i: Int in at.sorted().reversed() { 14 | self.remove(at: i) 15 | } 16 | } 17 | 18 | /** 19 | * Move items to the new position. 20 | */ 21 | func moveItems(from indexSet: IndexSet, to position: Int) -> [Element] { 22 | var left: [Element] = [] 23 | var right: [Element] = [] 24 | var center: [Element] = [] 25 | self.enumerated().forEach { (offset, element) in 26 | if indexSet.contains(offset) { 27 | center.append(element) 28 | } else if offset < position { 29 | left.append(element) 30 | } else { 31 | right.append(element) 32 | } 33 | } 34 | return left + center + right 35 | } 36 | 37 | subscript(indexSet: IndexSet) -> [Element] { 38 | return indexSet.map({self[$0]}) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 mtgto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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 | -------------------------------------------------------------------------------- /GPM/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 | } -------------------------------------------------------------------------------- /GPM/github/GitHubProject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubProject.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/17. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | public class GitHubProject: NSObject { 12 | let number: Int 13 | 14 | let name: String 15 | 16 | let body: String 17 | 18 | public class Column: NSObject { 19 | let id: Int 20 | 21 | let name: String 22 | 23 | init(id: Int, name: String) { 24 | self.id = id 25 | self.name = name 26 | } 27 | } 28 | 29 | public class Card: NSObject { 30 | public enum Position { 31 | case Top 32 | case Bottom 33 | case After(Int) 34 | } 35 | 36 | let id: Int 37 | 38 | let note: String? 39 | 40 | let issueId: GitHubIssueId? 41 | 42 | init(id: Int, note: String?, issueId: GitHubIssueId?) { 43 | self.id = id 44 | self.note = note 45 | self.issueId = issueId 46 | } 47 | } 48 | 49 | init(number: Int, name: String, body: String) { 50 | self.number = number 51 | self.name = name 52 | self.body = body 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /GPM/github/GitHubScope.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubScope.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/22. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | // All definition is found in https://developer.github.com/v3/oauth/#scopes 12 | public enum GitHubScope: String { 13 | case User = "user" // also contains "user:email", "user:follow" 14 | case UserEmail = "user:email" 15 | case UserFollow = "user:follow" 16 | case PublicRepo = "public_repo" 17 | case Repo = "repo" 18 | case RepoDeployment = "repo_deployment" 19 | case RepoStatus = "repo:status" 20 | case DeleteRepo = "delete_repo" 21 | case Notifications = "notifications" 22 | case Gist = "gist" 23 | case ReadRepoHook = "read:repo_hook" 24 | case WriteRepoHook = "write:repo_hook" 25 | case AdminRepoHook = "admin:repo_hook" 26 | case AdminOrgHook = "admin:org_hook" 27 | case ReadOrg = "read:org" 28 | case WriteOrg = "write:org" 29 | case AdminOrg = "admin:org" 30 | case ReadPublicKey = "read:public_key" 31 | case WritePublicKey = "write:public_key" 32 | case AdminPublicKey = "admin:public_key" 33 | case ReadGpgKey = "read:gpg_key" 34 | case WriteGpgKey = "write:gpg_key" 35 | case AdminGpgKey = "admin:gpg_key" 36 | } 37 | -------------------------------------------------------------------------------- /GPM/github/GitHubService+Issues.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubService+Issues.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/18. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | public extension GitHubService { 13 | public func fetchIssue(owner: String, repo: String, number: Int, handler: @escaping (GitHubResponse) -> Void) { 14 | self.fetch( 15 | path: "repos/\(owner)/\(repo)/issues/\(number)", 16 | parser: { self.parseIssueResponse($0, owner: owner, repo: repo) }, 17 | handler: handler) 18 | } 19 | 20 | func parseIssueResponse(_ data: Any, owner: String, repo: String) -> GitHubIssue? { 21 | if let issue = data as? [String:Any] { 22 | if let number = issue["number"] as? Int, let title = issue["title"] as? String, let body = issue["body"] as? String { 23 | let issueType = issue["pull_request"] != nil ? GitHubIssueType.PullRequest : GitHubIssueType.Issue 24 | return GitHubIssue(id: GitHubIssueId(owner: owner, repo: repo, number: number), type: issueType, title: title, body: body) 25 | } else { 26 | return nil 27 | } 28 | } else { 29 | return nil 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /GPMTests/github/response_repos_owner_repo_projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "owner_url": "https://api.github.com/repos/project-management", 4 | "url": "https://api.github.com/repos/project-management/projects/2", 5 | "id": 1002604, 6 | "name": "Projects v1", 7 | "body": "Things to do", 8 | "number": 1, 9 | "creator": { 10 | "login": "octocat", 11 | "id": 1, 12 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 13 | "gravatar_id": "", 14 | "url": "https://api.github.com/users/octocat", 15 | "html_url": "https://github.com/octocat", 16 | "followers_url": "https://api.github.com/users/octocat/followers", 17 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 18 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 19 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 20 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 21 | "organizations_url": "https://api.github.com/users/octocat/orgs", 22 | "repos_url": "https://api.github.com/users/octocat/repos", 23 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 24 | "received_events_url": "https://api.github.com/users/octocat/received_events", 25 | "type": "User", 26 | "site_admin": false 27 | }, 28 | "created_at": "2011-04-10T20:09:31Z", 29 | "updated_at": "2014-03-03T18:58:10Z" 30 | } 31 | ] 32 | -------------------------------------------------------------------------------- /GPM/KanbanRegisterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KanbanRegisterViewController.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/20. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class KanbanRegisterViewController: NSViewController { 12 | @IBOutlet weak var textField: NSTextField! 13 | 14 | @IBOutlet weak var alertTextField: NSTextField! 15 | var bookmarkViewController: BookmarkViewController? = nil 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | // Do view setup here. 20 | } 21 | 22 | @IBAction func addKanban(_ sender: AnyObject) { 23 | if let url = NSURL(string: textField.stringValue), let kanban = self.parseKanbanURL(url) { 24 | NotificationCenter.default.post(name: BookmarkViewController.AddKanbanNotificationName, object: kanban) 25 | self.dismissViewController(self) 26 | } else { 27 | // TODO: l10n 28 | self.alertTextField.stringValue = "Invalid URL!" 29 | } 30 | } 31 | 32 | func parseKanbanURL(_ url: NSURL) -> Kanban? { 33 | // TODO: validate url 34 | if let pathComponents = url.pathComponents { 35 | let count = pathComponents.count 36 | if count >= 4 && pathComponents[count - 2] == "projects" { 37 | let owner = pathComponents[count - 4] 38 | let repo = pathComponents[count - 3] 39 | let number = Int(pathComponents[count - 1])! 40 | return Kanban(owner: owner, repo: repo, number: number) 41 | } 42 | } 43 | return nil 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.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 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | -------------------------------------------------------------------------------- /GPMTests/github/response_user.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": "octocat", 3 | "id": 1, 4 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 5 | "gravatar_id": "", 6 | "url": "https://api.github.com/users/octocat", 7 | "html_url": "https://github.com/octocat", 8 | "followers_url": "https://api.github.com/users/octocat/followers", 9 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 10 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 11 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 12 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 13 | "organizations_url": "https://api.github.com/users/octocat/orgs", 14 | "repos_url": "https://api.github.com/users/octocat/repos", 15 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 16 | "received_events_url": "https://api.github.com/users/octocat/received_events", 17 | "type": "User", 18 | "site_admin": false, 19 | "name": "monalisa octocat", 20 | "company": "GitHub", 21 | "blog": "https://github.com/blog", 22 | "location": "San Francisco", 23 | "email": "octocat@github.com", 24 | "hireable": false, 25 | "bio": "There once was...", 26 | "public_repos": 2, 27 | "public_gists": 1, 28 | "followers": 20, 29 | "following": 0, 30 | "created_at": "2008-01-14T04:33:35Z", 31 | "updated_at": "2008-01-14T04:33:35Z", 32 | "total_private_repos": 100, 33 | "owned_private_repos": 100, 34 | "private_gists": 81, 35 | "disk_usage": 10000, 36 | "collaborators": 8, 37 | "plan": { 38 | "name": "Medium", 39 | "space": 400, 40 | "private_repos": 20, 41 | "collaborators": 0 42 | } 43 | } -------------------------------------------------------------------------------- /GPM/KanbanColumnNoteRegisterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KanbanColumnNoteRegisterViewController.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/10/03. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class KanbanColumnNoteRegisterViewController: NSViewController { 12 | 13 | @IBOutlet weak var textField: NSTextField! 14 | 15 | @IBOutlet weak var alertTextField: NSTextField! 16 | 17 | var kanban: Kanban! = nil 18 | 19 | var column: Column! = nil 20 | 21 | weak var columnDelegate: ColumnDelegate! = nil 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | // Do view setup here. 26 | } 27 | 28 | @IBAction func addNote(_ sender: AnyObject) { 29 | let service = GitHubService.sharedInstance 30 | let note = self.textField.stringValue 31 | self.alertTextField.stringValue = "Adding..." 32 | service.addProjectCard(owner: kanban.owner, repo: kanban.repo, columnId: column.id, note: note, contentId: nil, contentType: nil) { (response) in 33 | switch response.result { 34 | case GitHubResult.Success(let githubCard): 35 | let card = Card(id: githubCard.id, note: githubCard.note, issue: nil) 36 | self.columnDelegate.addCard(card) 37 | self.dismissViewController(self) 38 | self.removeFromParentViewController() 39 | case GitHubResult.Failure(let error): 40 | // show reason 41 | switch error { 42 | case .NetworkError: 43 | // TODO: l10n 44 | self.alertTextField.stringValue = "Invalid URL!" 45 | case .ParseError: 46 | // TODO: l10n 47 | self.alertTextField.stringValue = "Unknown error!" 48 | case .NoTokenError: 49 | // TODO: l10n 50 | self.alertTextField.stringValue = "Token error!" 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /GPM/KanbanColumnNoteUpdateViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KanbanColumnNoteUpdateViewController.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/10/09. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class KanbanColumnNoteUpdateViewController: NSViewController { 12 | 13 | @IBOutlet weak var textField: NSTextField! 14 | 15 | @IBOutlet weak var alertTextField: NSTextField! 16 | 17 | var kanban: Kanban! = nil 18 | 19 | var column: Column! = nil 20 | 21 | var card: Card! = nil 22 | 23 | weak var columnDelegate: ColumnDelegate! = nil 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | // Do view setup here. 28 | self.textField.stringValue = self.card.note! 29 | } 30 | 31 | @IBAction func updateNote(_ sender: AnyObject) { 32 | let service = GitHubService.sharedInstance 33 | let note = self.textField.stringValue 34 | self.alertTextField.stringValue = "Updating..." 35 | service.updateProjectCard(owner: self.kanban.owner, repo: self.kanban.repo, cardId: self.card.id, note: note) { (response) in 36 | switch response.result { 37 | case GitHubResult.Success(let githubCard): 38 | let card = Card(id: githubCard.id, note: githubCard.note, issue: nil) 39 | self.columnDelegate.updateCard(card) 40 | self.dismissViewController(self) 41 | self.removeFromParentViewController() 42 | case GitHubResult.Failure(let error): 43 | // show reason 44 | switch error { 45 | case .NetworkError: 46 | // TODO: l10n 47 | self.alertTextField.stringValue = "Invalid URL!" 48 | case .ParseError: 49 | // TODO: l10n 50 | self.alertTextField.stringValue = "Unknown error!" 51 | case .NoTokenError: 52 | // TODO: l10n 53 | self.alertTextField.stringValue = "Token error!" 54 | } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /GPM/KanbanViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KanbanViewController.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/18. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | protocol KanbanDelegate: class { 12 | func kanbanDidSelected(_ kanban: Kanban) 13 | } 14 | 15 | class KanbanViewController: NSViewController, KanbanDelegate { 16 | private var kanban: Kanban? = nil 17 | private var columnCards: [(Column, [Card])] = [] 18 | 19 | @IBOutlet weak var stackView: NSStackView! 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | // Do view setup here. 24 | } 25 | 26 | func columnTableViewControllerAtIndex(_ columnIndex: Int) -> KanbanColumnTableViewController { 27 | return self.childViewControllers[columnIndex] as! KanbanColumnTableViewController 28 | } 29 | 30 | // MARK: - KanbanDelegate 31 | func kanbanDidSelected(_ kanban: Kanban) { 32 | self.kanban = kanban 33 | self.childViewControllers.forEach({ viewController in 34 | self.stackView.removeView(viewController.view) 35 | viewController.removeFromParentViewController() 36 | }) 37 | let storyboard = NSStoryboard(name: "Main", bundle: nil) 38 | KanbanService.sharedInstance.fetchKanban(kanban) { (columnCards) in 39 | self.columnCards = columnCards 40 | for (columnIndex, (column, cards)) in columnCards.enumerated() { 41 | if let viewController = storyboard.instantiateController(withIdentifier: "KanbanColumnTableViewController") as? KanbanColumnTableViewController { 42 | viewController.kanban = kanban 43 | viewController.columnIndex = columnIndex 44 | viewController.column = column 45 | viewController.cards = cards 46 | self.addChildViewController(viewController) 47 | self.stackView.addArrangedSubview(viewController.view) 48 | } 49 | } 50 | } 51 | } 52 | 53 | func tableViewColumnDidResize(_ notification: Notification) { 54 | debugPrint("tableViewColumnDidResize") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /GPMTests/github/GitHubServiceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubServiceTests.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/18. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import GPM 12 | 13 | class GitHubServiceTests: XCTestCase, GitHubTestsSupport { 14 | let service = GitHubService() 15 | 16 | func testParseProjectsResponse() { 17 | let data = self.dataFromResourceFile("response_repos_owner_repo_projects.json") 18 | let json = try! JSONSerialization.jsonObject(with: data, options: []) 19 | let projects = service.parseProjectsResponse(json) 20 | XCTAssertEqual(projects.map({ $0.count }), Optional(1)) 21 | } 22 | 23 | func testParseProjectColumnsResponse() { 24 | let data = self.dataFromResourceFile("response_repos_owner_repo_projects_project_number_columns.json") 25 | let json = try! JSONSerialization.jsonObject(with: data, options: []) 26 | let columns = service.parseProjectColumnsResponse(json) 27 | XCTAssertEqual(columns.map({ $0.count }), Optional(1)) 28 | } 29 | 30 | func testParseProjectCardsResponse() { 31 | let data = self.dataFromResourceFile("response_repos_owner_repo_projects_columns_column_id_cards.json") 32 | let json = try! JSONSerialization.jsonObject(with: data, options: []) 33 | let cards = service.parseProjectCardsResponse(json) 34 | XCTAssertEqual(cards.map({ $0.count }), Optional(2)) 35 | } 36 | 37 | func testParseProjectCardResponse() { 38 | let data = self.dataFromResourceFile("response_repos_owner_repo_projects_columns_column_id_card.json") 39 | let json = try! JSONSerialization.jsonObject(with: data, options: []) 40 | let card = service.parseProjectCardResponse(json) 41 | XCTAssertNotNil(card) 42 | } 43 | 44 | func testParseIssueResponse() { 45 | let data = self.dataFromResourceFile("response_repos_owner_repo_issues_number.json") 46 | let json = try! JSONSerialization.jsonObject(with: data, options: []) 47 | let issue = service.parseIssueResponse(json, owner: "octocat", repo: "Hello-World") 48 | XCTAssertNotNil(issue) 49 | } 50 | 51 | func testParseUserResponse() { 52 | let data = self.dataFromResourceFile("response_user.json") 53 | let json = try! JSONSerialization.jsonObject(with: data, options: []) 54 | let user = service.parseUser(json) 55 | XCTAssertEqual(user.map({ $0.login }), Optional("octocat")) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /GPM/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginViewController.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/22. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class LoginViewController: NSViewController, NSTextFieldDelegate { 12 | 13 | @IBOutlet weak var tokenTextField: NSTextField! 14 | @IBOutlet weak var alertTextField: NSTextField! 15 | @IBOutlet weak var okButton: NSButton! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | // Do view setup here. 20 | } 21 | 22 | // Notified while live editing. 23 | override func controlTextDidChange(_ obj: Notification) { 24 | if let textField = obj.object as? NSTextField { 25 | okButton.isEnabled = !textField.stringValue.isEmpty 26 | } 27 | } 28 | 29 | @IBAction func addAccessToken(_ sender: AnyObject) { 30 | self.alertTextField.stringValue = "Checking the access token..." 31 | let accessToken = self.tokenTextField.stringValue 32 | let service = GitHubService() 33 | service.accessToken = accessToken 34 | service.fetchUser { (response) in 35 | switch response.result { 36 | case GitHubResult.Success(_): 37 | if response.scopes.contains(GitHubScope.PublicRepo.rawValue) || response.scopes.contains(GitHubScope.Repo.rawValue) { 38 | AccessTokenService.sharedInstance.setAccessToken(accessToken) 39 | GitHubService.sharedInstance.accessToken = accessToken 40 | self.dismissViewController(self) 41 | } else { 42 | // TODO: l10n 43 | self.alertTextField.stringValue = "This token doesn't have repo or public_repo scope." 44 | } 45 | case GitHubResult.Failure(let error): 46 | // show reason 47 | switch error { 48 | case .NetworkError: 49 | // TODO: l10n 50 | self.alertTextField.stringValue = "Invalid URL!" 51 | case .ParseError: 52 | // TODO: l10n 53 | self.alertTextField.stringValue = "Unknown error!" 54 | case .NoTokenError: 55 | // TODO: l10n 56 | self.alertTextField.stringValue = "Unknown error!" 57 | } 58 | } 59 | } 60 | } 61 | 62 | @IBAction func quit(_ sender: AnyObject) { 63 | self.dismissViewController(self) 64 | NSRunningApplication.current().terminate() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /GPM/BookmarkViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookmarkViewController.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/18. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class BookmarkViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource { 12 | weak var delegate: KanbanDelegate! = nil 13 | 14 | @IBOutlet weak var tableView: NSTableView! 15 | 16 | static let AddKanbanNotificationName = Notification.Name("AddKanbanNotificationName") 17 | 18 | private static let LoginSegueName = "LoginSegue" 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | // Do view setup here. 23 | NotificationCenter.default.addObserver(self, selector: #selector(addKanbanNotification(_:)), name: BookmarkViewController.AddKanbanNotificationName, object: nil) 24 | } 25 | 26 | override func viewDidAppear() { 27 | if AccessTokenService.sharedInstance.accessToken() == nil { 28 | debugPrint("viewDidAppear") 29 | self.performSegue(withIdentifier: BookmarkViewController.LoginSegueName, sender: self) 30 | } 31 | } 32 | 33 | func addKanbanNotification(_ notification: Notification) { 34 | if let kanban = notification.object as? Kanban { 35 | if KanbanService.sharedInstance.addKanban(kanban) { 36 | self.tableView.reloadData() 37 | } 38 | } 39 | } 40 | 41 | // MARK: - NSTableViewDelegate 42 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { 43 | let view = tableView.make(withIdentifier: "BookmarkViewCell", owner: tableView) 44 | if let cell = view as? NSTableCellView { 45 | cell.textField?.stringValue = KanbanService.sharedInstance.kanbans[row].repo + "ほげほげほげほげほげほげほげほげほげ" 46 | return cell 47 | } else { 48 | debugPrint(view) 49 | } 50 | return nil 51 | } 52 | 53 | func tableViewSelectionDidChange(_ notification: Notification) { 54 | if let tableView = notification.object as? NSTableView, tableView.selectedRow >= 0 { 55 | let kanban = KanbanService.sharedInstance.kanbans[tableView.selectedRow] 56 | self.delegate.kanbanDidSelected(kanban) 57 | } 58 | } 59 | 60 | // MARK: - NSTableViewDataSource 61 | 62 | func numberOfRows(in tableView: NSTableView) -> Int { 63 | return KanbanService.sharedInstance.kanbans.count 64 | } 65 | 66 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { 67 | return KanbanService.sharedInstance.kanbans[row] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /GPM/github/GitHubIssue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubIssue.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/18. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | public enum GitHubIssueType: String { 12 | case Issue = "issue" 13 | case PullRequest = "pull_request" 14 | } 15 | 16 | public class GitHubIssueId: NSObject, NSCoding { 17 | let owner: String 18 | let repo: String 19 | let number: Int 20 | private let id: String 21 | 22 | init(owner: String, repo: String, number: Int) { 23 | self.owner = owner 24 | self.repo = repo 25 | self.number = number 26 | self.id = "\(owner)/\(repo)/\(number)" 27 | } 28 | 29 | public override var hashValue: Int { 30 | return self.id.hashValue 31 | } 32 | 33 | // MARK: - NSCoding 34 | required public init?(coder aDecoder: NSCoder) { 35 | self.owner = aDecoder.decodeObject(forKey: "owner") as! String 36 | self.repo = aDecoder.decodeObject(forKey: "repo") as! String 37 | self.number = aDecoder.decodeInteger(forKey: "number") 38 | self.id = "\(owner)/\(repo)/\(number)" 39 | } 40 | 41 | public func encode(with aCoder: NSCoder) { 42 | aCoder.encode(self.owner, forKey: "owner") 43 | aCoder.encode(self.repo, forKey: "repo") 44 | aCoder.encode(self.number, forKey: "number") 45 | } 46 | 47 | // MARK: - Equatable 48 | override public func isEqual(_ object: Any?) -> Bool { 49 | guard let issueId = object as? GitHubIssueId else { 50 | return false 51 | } 52 | return self == issueId 53 | } 54 | } 55 | 56 | public func ==(lhs: GitHubIssueId, rhs: GitHubIssueId) -> Bool { 57 | return lhs.owner == rhs.owner && lhs.repo == rhs.repo && lhs.number == rhs.number 58 | } 59 | 60 | public class GitHubIssue: NSObject, NSCoding { 61 | let id: GitHubIssueId 62 | 63 | let type: GitHubIssueType 64 | 65 | let title: String 66 | 67 | let body: String //! markdown 68 | 69 | init(id: GitHubIssueId, type: GitHubIssueType, title: String, body: String) { 70 | self.id = id 71 | self.type = type 72 | self.title = title 73 | self.body = body 74 | } 75 | 76 | // MARK: - NSCoding 77 | required public init?(coder aDecoder: NSCoder) { 78 | self.id = aDecoder.decodeObject(forKey: "id") as! GitHubIssueId 79 | self.type = GitHubIssueType(rawValue: aDecoder.decodeObject(forKey: "type") as! String)! 80 | self.title = aDecoder.decodeObject(forKey: "title") as! String 81 | self.body = aDecoder.decodeObject(forKey: "body") as! String 82 | } 83 | 84 | public func encode(with aCoder: NSCoder) { 85 | aCoder.encode(self.id, forKey: "id") 86 | aCoder.encode(self.type.rawValue, forKey: "type") 87 | aCoder.encode(self.title, forKey: "title") 88 | aCoder.encode(self.body, forKey: "body") 89 | } 90 | 91 | public override func isEqual(_ object: Any?) -> Bool { 92 | guard let issue = object as? GitHubIssue else { 93 | return false 94 | } 95 | return self == issue 96 | } 97 | } 98 | 99 | public func ==(lhs: GitHubIssue, rhs: GitHubIssue) -> Bool { 100 | return lhs.id == rhs.id && lhs.type == rhs.type && lhs.title == rhs.title && lhs.body == rhs.body 101 | } 102 | -------------------------------------------------------------------------------- /GPM/github/GitHubService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubService.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/17. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Alamofire 11 | 12 | public struct GitHubResponse { 13 | public let scopes: [String] 14 | public let result: GitHubResult 15 | } 16 | 17 | public enum GitHubResult { 18 | case Success(T) 19 | case Failure(GitHubError) 20 | } 21 | 22 | public enum GitHubError: Error { 23 | case NoTokenError 24 | case NetworkError 25 | case ParseError 26 | } 27 | 28 | public class GitHubService: NSObject { 29 | static let sharedInstance: GitHubService = GitHubService() 30 | 31 | var accessToken: String? = nil 32 | 33 | var server: GitHubServer = GitHubServer( 34 | apiBaseURL: NSURL(string: "https://api.github.com/")!, 35 | webBaseURL: NSURL(string: "https://github.com/")! 36 | ) 37 | 38 | internal func authenticateHeaders(_ accessToken: String) -> [String:String] { 39 | return [ 40 | "Authorization": "token \(accessToken)", 41 | "Accept": "application/vnd.github.inertia-preview+json" // Projects API is still in Early Access. See https://developer.github.com/v3/repos/projects/ 42 | ] 43 | } 44 | 45 | internal func fetch(path: String, parser: @escaping (Any) -> T?, handler: @escaping (GitHubResponse) -> Void) { 46 | self.request(path: path, method: .get, parser: parser, handler: handler) 47 | } 48 | 49 | internal func post(path: String, method: Alamofire.HTTPMethod = .post, parameters: [String:Any]? = nil, parser: @escaping (Any) -> T?, handler: @escaping (GitHubResponse) -> Void) { 50 | self.request(path: path, method: method, parameters: parameters, encoding: JSONEncoding.default, parser: parser, handler: handler) 51 | } 52 | 53 | fileprivate func request(path: String, method: Alamofire.HTTPMethod, parameters: [String:Any]? = nil, encoding: ParameterEncoding = URLEncoding.default, parser: @escaping (Any) -> T?, handler: @escaping (GitHubResponse) -> Void) { 54 | guard let accessToken = self.accessToken else { 55 | handler(GitHubResponse(scopes: [], result: GitHubResult.Failure(.NoTokenError))) 56 | return 57 | } 58 | Alamofire.request(self.server.apiBaseURL.appendingPathComponent(path)!, method: method, parameters: parameters, encoding: encoding, headers: self.authenticateHeaders(accessToken)) 59 | .responseJSON { response in 60 | let scopes = self.parseScopesFromResponse(response.response) 61 | switch response.result { 62 | case .success(let value): 63 | // TODO: Check status code (200 or other) 64 | if let parsedValue = parser(value) { 65 | handler(GitHubResponse(scopes: scopes, result: GitHubResult.Success(parsedValue))) 66 | } else { 67 | handler(GitHubResponse(scopes: scopes, result: GitHubResult.Failure(GitHubError.ParseError))) 68 | } 69 | case .failure(let error): 70 | print(error) 71 | handler(GitHubResponse(scopes: scopes, result: GitHubResult.Failure(GitHubError.NetworkError))) 72 | } 73 | } 74 | } 75 | 76 | internal func parseScopesFromResponse(_ response: HTTPURLResponse?) -> [String] { 77 | if let response = response, let scopesString = response.allHeaderFields["X-OAuth-Scopes"] as? String { 78 | return scopesString.components(separatedBy: ", ") 79 | } else { 80 | return [] 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /GPM.xcodeproj/xcshareddata/xcschemes/GPM.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 96 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /GPM/KanbanService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KanbanService.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/18. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Alamofire 11 | 12 | private let KanbansKey: String = "kanbans" 13 | 14 | class KanbanService: NSObject { 15 | static let sharedInstance: KanbanService = KanbanService() 16 | 17 | var kanbans: [Kanban] = [] 18 | 19 | override init() { 20 | super.init() 21 | let userDefaults = NSUserDefaultsController.shared().defaults 22 | userDefaults.register(defaults: [KanbansKey: [["owner": "mtgto", "repo": "GPM", "number": 1]]]) 23 | if let kanbans = userDefaults.array(forKey: KanbansKey) as? [[String:Any]] { 24 | self.kanbans = kanbans.map({ (kanbanDict) -> Kanban in 25 | Kanban(owner: kanbanDict["owner"]! as! String, repo: kanbanDict["repo"] as! String, number: kanbanDict["number"] as! Int) 26 | }) 27 | } 28 | } 29 | 30 | // Return whether success to add or already exists. 31 | func addKanban(_ kanban: Kanban) -> Bool { 32 | if self.kanbans.index(of: kanban) == nil { 33 | self.kanbans += kanbans 34 | return true 35 | } else { 36 | return false 37 | } 38 | } 39 | 40 | func fetchKanban(_ kanban: Kanban, handler: @escaping ([(Column, [Card])]) -> Void) { 41 | debugPrint("Start to fetch kanban: \(kanban).") 42 | var cards: [(Column, [Card])] = [] 43 | let group = DispatchGroup() 44 | 45 | group.enter() 46 | GitHubService.sharedInstance.fetchProjectColumnsForProject(owner: kanban.owner, repo: kanban.repo, projectNumber: kanban.number) { (response) in 47 | switch response.result { 48 | case GitHubResult.Success(let githubColumns): 49 | debugPrint("Succeeded to fetch columns: \(githubColumns)") 50 | let columns = githubColumns.map({Column(id: $0.id, name: $0.name)}) 51 | cards = columns.map { ($0, []) } 52 | for column in columns { 53 | group.enter() 54 | GitHubService.sharedInstance.fetchProjectCardsForProjectColumn(owner: kanban.owner, repo: kanban.repo, columnId: column.id) { (response) in 55 | switch response.result { 56 | case GitHubResult.Success(let githubCards): 57 | debugPrint("Succeeded to fetch cards: \(githubCards)") 58 | group.enter() 59 | self.fetchIssues(cards: githubCards, handler: { (cardIssues) in 60 | cards = cards.map({ (columnCards: (Column, [Card])) -> (Column, [Card]) in 61 | if column == columnCards.0 { 62 | return (column, cardIssues.map({Card(id: $0.0.id, note: $0.0.note, issue: $0.1)})) 63 | } else { 64 | return columnCards 65 | } 66 | }) 67 | group.leave() 68 | }) 69 | case GitHubResult.Failure(let error): 70 | print(error) 71 | } 72 | group.leave() 73 | } 74 | } 75 | case GitHubResult.Failure(let error): 76 | print(error) 77 | } 78 | group.leave() 79 | } 80 | 81 | group.notify(queue: DispatchQueue.main) { 82 | debugPrint("All download done. \(cards)") 83 | handler(cards) 84 | } 85 | } 86 | 87 | func fetchIssues(cards: [GitHubProject.Card], handler: @escaping (([(GitHubProject.Card, GitHubIssue?)]) -> Void)) { 88 | func loop(cards: [GitHubProject.Card], current: [(GitHubProject.Card, GitHubIssue?)]) { 89 | if cards.count == 0 { 90 | handler(current) 91 | } else { 92 | let card = cards[0] 93 | let last: [GitHubProject.Card] = Array(cards.dropFirst(1)) 94 | if let issueId = card.issueId { 95 | GitHubService.sharedInstance.fetchIssue(owner: issueId.owner, repo: issueId.repo, number: issueId.number) { (response) in 96 | switch response.result { 97 | case GitHubResult.Success(let issue): 98 | loop(cards: last, current: current + [(card, issue)]) 99 | case GitHubResult.Failure(let error): 100 | print(error) 101 | loop(cards: last, current: current + [(card, nil)]) 102 | } 103 | } 104 | } else { 105 | loop(cards: last, current: current + [(card, nil)]) 106 | } 107 | } 108 | } 109 | loop(cards: cards, current: []) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /GPMTests/github/response_repos_owner_repo_issues_number.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "url": "https://api.github.com/repos/octocat/Hello-World/issues/1347", 4 | "repository_url": "https://api.github.com/repos/octocat/Hello-World", 5 | "labels_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/labels{/name}", 6 | "comments_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments", 7 | "events_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/events", 8 | "html_url": "https://github.com/octocat/Hello-World/issues/1347", 9 | "number": 1347, 10 | "state": "open", 11 | "title": "Found a bug", 12 | "body": "I'm having a problem with this.", 13 | "user": { 14 | "login": "octocat", 15 | "id": 1, 16 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 17 | "gravatar_id": "", 18 | "url": "https://api.github.com/users/octocat", 19 | "html_url": "https://github.com/octocat", 20 | "followers_url": "https://api.github.com/users/octocat/followers", 21 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 22 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 23 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 24 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 25 | "organizations_url": "https://api.github.com/users/octocat/orgs", 26 | "repos_url": "https://api.github.com/users/octocat/repos", 27 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 28 | "received_events_url": "https://api.github.com/users/octocat/received_events", 29 | "type": "User", 30 | "site_admin": false 31 | }, 32 | "labels": [ 33 | { 34 | "url": "https://api.github.com/repos/octocat/Hello-World/labels/bug", 35 | "name": "bug", 36 | "color": "f29513" 37 | } 38 | ], 39 | "assignee": { 40 | "login": "octocat", 41 | "id": 1, 42 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 43 | "gravatar_id": "", 44 | "url": "https://api.github.com/users/octocat", 45 | "html_url": "https://github.com/octocat", 46 | "followers_url": "https://api.github.com/users/octocat/followers", 47 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 48 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 49 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 50 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 51 | "organizations_url": "https://api.github.com/users/octocat/orgs", 52 | "repos_url": "https://api.github.com/users/octocat/repos", 53 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 54 | "received_events_url": "https://api.github.com/users/octocat/received_events", 55 | "type": "User", 56 | "site_admin": false 57 | }, 58 | "milestone": { 59 | "url": "https://api.github.com/repos/octocat/Hello-World/milestones/1", 60 | "html_url": "https://github.com/octocat/Hello-World/milestones/v1.0", 61 | "labels_url": "https://api.github.com/repos/octocat/Hello-World/milestones/1/labels", 62 | "id": 1002604, 63 | "number": 1, 64 | "state": "open", 65 | "title": "v1.0", 66 | "description": "Tracking milestone for version 1.0", 67 | "creator": { 68 | "login": "octocat", 69 | "id": 1, 70 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 71 | "gravatar_id": "", 72 | "url": "https://api.github.com/users/octocat", 73 | "html_url": "https://github.com/octocat", 74 | "followers_url": "https://api.github.com/users/octocat/followers", 75 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 76 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 77 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 78 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 79 | "organizations_url": "https://api.github.com/users/octocat/orgs", 80 | "repos_url": "https://api.github.com/users/octocat/repos", 81 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 82 | "received_events_url": "https://api.github.com/users/octocat/received_events", 83 | "type": "User", 84 | "site_admin": false 85 | }, 86 | "open_issues": 4, 87 | "closed_issues": 8, 88 | "created_at": "2011-04-10T20:09:31Z", 89 | "updated_at": "2014-03-03T18:58:10Z", 90 | "closed_at": "2013-02-12T13:22:01Z", 91 | "due_on": "2012-10-09T23:39:01Z" 92 | }, 93 | "locked": false, 94 | "comments": 0, 95 | "pull_request": { 96 | "url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347", 97 | "html_url": "https://github.com/octocat/Hello-World/pull/1347", 98 | "diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff", 99 | "patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch" 100 | }, 101 | "closed_at": null, 102 | "created_at": "2011-04-22T13:33:48Z", 103 | "updated_at": "2011-04-22T13:33:48Z", 104 | "closed_by": { 105 | "login": "octocat", 106 | "id": 1, 107 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 108 | "gravatar_id": "", 109 | "url": "https://api.github.com/users/octocat", 110 | "html_url": "https://github.com/octocat", 111 | "followers_url": "https://api.github.com/users/octocat/followers", 112 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 113 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 114 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 115 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 116 | "organizations_url": "https://api.github.com/users/octocat/orgs", 117 | "repos_url": "https://api.github.com/users/octocat/repos", 118 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 119 | "received_events_url": "https://api.github.com/users/octocat/received_events", 120 | "type": "User", 121 | "site_admin": false 122 | } 123 | } -------------------------------------------------------------------------------- /GPM/github/GitHubService+Projects.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubService+Projects.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/17. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Alamofire 11 | 12 | public extension GitHubService { 13 | public func fetchProjectsForRepository(owner: String, repo: String, handler: @escaping (GitHubResponse<[GitHubProject]>) -> Void) { 14 | self.fetch(path: "repos/\(owner)/\(repo)/projects", parser: self.parseProjectsResponse, handler: handler) 15 | } 16 | 17 | public func fetchProject(owner: String, repo: String, projectNumber: Int, handler: @escaping (GitHubResponse) -> Void) { 18 | self.fetch(path: "repos/\(owner)/\(repo)/projects/\(projectNumber)", parser: self.parseProjectResponse, handler: handler) 19 | } 20 | 21 | public func fetchProjectColumnsForProject(owner: String, repo: String, projectNumber: Int, handler: @escaping (GitHubResponse<[GitHubProject.Column]>) -> Void) { 22 | self.fetch(path: "repos/\(owner)/\(repo)/projects/\(projectNumber)/columns", parser: self.parseProjectColumnsResponse, handler: handler) 23 | } 24 | 25 | public func fetchProjectCardsForProjectColumn(owner: String, repo: String, columnId: Int, handler: @escaping (GitHubResponse<[GitHubProject.Card]>) -> Void) { 26 | self.fetch(path: "repos/\(owner)/\(repo)/projects/columns/\(columnId)/cards", parser: self.parseProjectCardsResponse, handler: handler) 27 | } 28 | 29 | // See https://developer.github.com/v3/repos/projects/#move-a-project-card 30 | public func updateProjectCardPosition(owner: String, repo: String, cardId: Int, position: GitHubProject.Card.Position, columnId: Int?, handler: @escaping (GitHubResponse) -> Void) { 31 | var parameters: [String: Any] 32 | switch position { 33 | case .Top: 34 | parameters = ["position": "top"] 35 | case .Bottom: 36 | parameters = ["position": "bottom"] 37 | case .After(let cardId): 38 | parameters = ["position": "after:\(cardId)"] 39 | } 40 | if let columnId = columnId { 41 | parameters.updateValue(columnId, forKey: "column_id") 42 | } 43 | self.post(path: "repos/\(owner)/\(repo)/projects/columns/cards/\(cardId)/moves", parameters: parameters, parser: { _ -> Void in }, handler: handler) 44 | } 45 | 46 | public func addProjectCard(owner: String, repo: String, columnId: Int, note: String?, contentId: GitHubIssueId?, contentType: GitHubIssueType?, handler: @escaping (GitHubResponse) -> Void) { 47 | var parameters: [String: Any] = [:] 48 | if let note = note { 49 | parameters["note"] = note 50 | } else if let contentId = contentId, let contentType = contentType { 51 | parameters["content_id"] = contentId.number 52 | parameters["content_type"] = contentType.rawValue 53 | } 54 | self.post(path: "repos/\(owner)/\(repo)/projects/columns/\(columnId)/cards", parameters: parameters, parser: self.parseProjectCardResponse, handler: handler) 55 | } 56 | 57 | public func updateProjectCard(owner: String, repo: String, cardId: Int, note: String, handler: @escaping (GitHubResponse) -> Void) { 58 | let parameters = ["note": note] 59 | self.post(path: "repos/\(owner)/\(repo)/projects/columns/cards/\(cardId)", method: .patch, parameters: parameters, parser: self.parseProjectCardResponse, handler: handler) 60 | } 61 | 62 | func parseProjectsResponse(_ data: Any) -> [GitHubProject]? { 63 | if let array = data as? Array<[String:Any]> { 64 | return array.flatMap({ self.parseProjectResponse($0) }) 65 | } else { 66 | return [] 67 | } 68 | } 69 | 70 | func parseProjectResponse(_ data: Any) -> GitHubProject? { 71 | if let project = data as? [String:Any] { 72 | if let number = project["number"] as? Int, let name = project["name"] as? String, let body = project["body"] as? String { 73 | return GitHubProject(number: number, name: name, body: body) 74 | } else { 75 | return nil 76 | } 77 | } else { 78 | return nil 79 | } 80 | } 81 | 82 | func parseProjectColumnsResponse(_ data: Any) -> [GitHubProject.Column]? { 83 | if let array = data as? Array<[String:Any]> { 84 | return array.flatMap({ (column) -> GitHubProject.Column? in 85 | if let id = column["id"] as? Int, let name = column["name"] as? String { 86 | return GitHubProject.Column(id: id, name: name) 87 | } else { 88 | return nil 89 | } 90 | }) 91 | } 92 | return [] 93 | } 94 | 95 | func parseProjectCardsResponse(_ data: Any) -> [GitHubProject.Card]? { 96 | if let array = data as? Array<[String:Any]> { 97 | return array.flatMap({self.parseProjectCardResponse($0)}) 98 | } else { 99 | return [] 100 | } 101 | } 102 | 103 | func parseProjectCardResponse(_ data: Any) -> GitHubProject.Card? { 104 | if let card = data as? [String:Any], let id = card["id"] as? Int { 105 | let note = card["note"] as? String 106 | let contentURL = (card["content_url"] as? String).flatMap({NSURL(string: $0)}) 107 | let issueId = contentURL.flatMap({ (contentURL) -> GitHubIssueId? in 108 | if let pathComponents = contentURL.pathComponents { 109 | let count = pathComponents.count 110 | if count >= 5 && pathComponents[count - 5] == "repos" { 111 | let owner = pathComponents[count - 4] 112 | let repo = pathComponents[count - 3] 113 | let number = Int(pathComponents[count - 1])! 114 | return GitHubIssueId(owner: owner, repo: repo, number: number) 115 | } 116 | } 117 | return nil 118 | }) 119 | return GitHubProject.Card(id: id, note: note, issueId: issueId) 120 | } else { 121 | return nil 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /GPM/KanbanColumnTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KanbanColumnTableViewController.swift 3 | // GPM 4 | // 5 | // Created by mtgto on 2016/09/27. 6 | // Copyright © 2016 mtgto. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | protocol ColumnDelegate: class { 12 | func addCard(_ card: Card) 13 | 14 | func updateCard(_ newCard: Card) 15 | } 16 | 17 | class KanbanColumnTableViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate, ColumnDelegate { 18 | 19 | class CardPosition: NSObject, NSCoding { 20 | let columnIndex: Int 21 | let rowIndexes: IndexSet 22 | 23 | init(columnIndex: Int, rowIndexes: IndexSet) { 24 | self.columnIndex = columnIndex 25 | self.rowIndexes = rowIndexes 26 | } 27 | 28 | // MARK: - NSCoding 29 | required init?(coder aDecoder: NSCoder) { 30 | self.columnIndex = aDecoder.decodeInteger(forKey: "columnIndex") 31 | self.rowIndexes = aDecoder.decodeObject(forKey: "rowIndexes") as! IndexSet 32 | } 33 | 34 | func encode(with aCoder: NSCoder) { 35 | aCoder.encode(self.columnIndex, forKey: "columnIndex") 36 | aCoder.encode(self.rowIndexes, forKey: "rowIndexes") 37 | } 38 | } 39 | 40 | @IBOutlet weak var tableView: NSTableView! 41 | 42 | var kanban: Kanban! = nil 43 | 44 | var column: Column! = nil 45 | 46 | var columnIndex: Int = -1 47 | 48 | var cards: [Card] = [] 49 | 50 | static let CardsType = "net.mtgto.GPM.KanbanColumnTableViewController.cards" 51 | 52 | override func viewDidLoad() { 53 | super.viewDidLoad() 54 | // Do view setup here. 55 | self.tableView.draggingDestinationFeedbackStyle = .gap 56 | self.tableView.register(forDraggedTypes: [KanbanColumnTableViewController.CardsType]) 57 | } 58 | 59 | /** 60 | * Remove items from cards parameter and table rows. 61 | * 62 | * - rowIndexes: Set of the TableRow. 63 | */ 64 | func removeAtRowIndexes(_ rowIndexes: IndexSet) -> [Card] { 65 | let itemIndexes = IndexSet(rowIndexes.map({$0-1})) 66 | let removed = itemIndexes.map({self.cards[$0]}) 67 | self.tableView.removeRows(at: rowIndexes, withAnimation: NSTableViewAnimationOptions.slideUp) 68 | self.cards.remove(at: itemIndexes) 69 | return removed 70 | } 71 | 72 | @IBAction func addNote(_ sender: AnyObject) { 73 | let storyboard = NSStoryboard(name: "Main", bundle: nil) 74 | if let viewController = storyboard.instantiateController(withIdentifier: "KanbanColumnNoteRegisterViewController") as? KanbanColumnNoteRegisterViewController { 75 | viewController.kanban = self.kanban 76 | viewController.column = self.column 77 | viewController.columnDelegate = self 78 | debugPrint("self.view.frame: \(self.view.frame), self.view.bounds: \(self.view.bounds), sender.frame: \(sender.frame)") 79 | self.presentViewController(viewController, asPopoverRelativeTo: self.view.bounds, of: self.view, preferredEdge: NSRectEdge.maxY, behavior: NSPopoverBehavior.transient) 80 | } 81 | } 82 | 83 | @IBAction func doubleClicked(_ sender: AnyObject) { 84 | let clickedRow = self.tableView.clickedRow 85 | if clickedRow >= 1 { 86 | let card = self.cards[clickedRow - 1] 87 | if card.note != nil { 88 | let storyboard = NSStoryboard(name: "Main", bundle: nil) 89 | if let viewController = storyboard.instantiateController(withIdentifier: "KanbanColumnNoteUpdateViewController") as? KanbanColumnNoteUpdateViewController { 90 | viewController.kanban = self.kanban 91 | viewController.column = self.column 92 | viewController.card = card 93 | viewController.columnDelegate = self 94 | debugPrint("self.view.frame: \(self.view.frame), self.view.bounds: \(self.view.bounds), sender.frame: \(sender.frame)") 95 | self.presentViewController(viewController, asPopoverRelativeTo: self.view.bounds, of: self.view, preferredEdge: NSRectEdge.maxY, behavior: NSPopoverBehavior.transient) 96 | } 97 | } 98 | } 99 | } 100 | 101 | // MARK: - ColumnDelegate 102 | func addCard(_ card: Card) { 103 | self.cards.insert(card, at: 0) 104 | self.tableView.insertRows(at: IndexSet(integer: 1), withAnimation: .effectGap) 105 | } 106 | 107 | func updateCard(_ newCard: Card) { 108 | for (index, card) in self.cards.enumerated() { 109 | if card.id == newCard.id { 110 | self.cards[index] = newCard 111 | self.tableView.noteHeightOfRows(withIndexesChanged: IndexSet(integer: index + 1)) 112 | self.tableView.reloadData(forRowIndexes: IndexSet(integer: index + 1), columnIndexes: IndexSet(integer: 0)) 113 | break 114 | } 115 | } 116 | } 117 | 118 | // MARK: - NSTableViewDataSource 119 | func numberOfRows(in tableView: NSTableView) -> Int { 120 | return self.cards.count + 1 121 | } 122 | 123 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { 124 | if row == 0 { 125 | return self.column 126 | } else { 127 | let cardIndex = row - 1 128 | return self.cards[cardIndex] 129 | } 130 | } 131 | 132 | func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool { 133 | pboard.declareTypes([KanbanColumnTableViewController.CardsType], owner: nil) 134 | pboard.setData(NSKeyedArchiver.archivedData(withRootObject: CardPosition(columnIndex: self.columnIndex, rowIndexes: rowIndexes)), forType: KanbanColumnTableViewController.CardsType) 135 | return true 136 | } 137 | 138 | func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableViewDropOperation) -> NSDragOperation { 139 | if dropOperation == .above { 140 | return .move 141 | } else { 142 | return [] 143 | } 144 | } 145 | 146 | func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableViewDropOperation) -> Bool { 147 | let pboard = info.draggingPasteboard() 148 | let cardIndex = row - 1 149 | if let data = pboard.data(forType: KanbanColumnTableViewController.CardsType) { 150 | if let cardPosition = NSKeyedUnarchiver.unarchiveObject(with: data) as? CardPosition { 151 | let position = cardIndex == 0 ? GitHubProject.Card.Position.Top : GitHubProject.Card.Position.After(self.cards[cardIndex-1].id) 152 | if cardPosition.columnIndex == self.columnIndex { 153 | // Move from same tableView. 154 | self.tableView.beginUpdates() 155 | let itemIndexes = IndexSet(cardPosition.rowIndexes.map({$0-1})) 156 | let items = self.cards[itemIndexes] 157 | self.cards = self.cards.moveItems(from: itemIndexes, to: cardIndex) 158 | var oldOffset = 0 159 | var newOffset = 0 160 | for index in Array(cardPosition.rowIndexes).sorted().reversed() { 161 | if index < row { 162 | self.tableView.moveRow(at: index + oldOffset, to: row - 1) 163 | oldOffset -= 1 164 | } else { 165 | self.tableView.moveRow(at: index, to: row + newOffset) 166 | newOffset += 1 167 | } 168 | } 169 | self.tableView.endUpdates() 170 | for item in items.reversed() { 171 | GitHubService.sharedInstance.updateProjectCardPosition(owner: self.kanban.owner, repo: self.kanban.repo, cardId: item.id, position: position, columnId: nil) { response in 172 | } 173 | } 174 | return true 175 | } else { 176 | // Move from other tableView. 177 | self.tableView.beginUpdates() 178 | let parentViewController = self.parent as! KanbanViewController 179 | let sourceViewController = parentViewController.columnTableViewControllerAtIndex(cardPosition.columnIndex) 180 | let items = sourceViewController.removeAtRowIndexes(cardPosition.rowIndexes) 181 | self.cards.insert(contentsOf: items, at: cardIndex) 182 | let addedRowIndexes = IndexSet(integersIn: row.. NSView? { 200 | //debugPrint("tableView.width = \(tableView.frame.size.width)") 201 | if row == 0 { 202 | // header 203 | if let headerCellView = tableView.make(withIdentifier: "HeaderCellView", owner: self) as? HeaderCellView { 204 | headerCellView.textField?.stringValue = self.column.name 205 | return headerCellView 206 | } 207 | } else { 208 | // cards 209 | let cardIndex = row - 1 210 | if let cardCellView = tableView.make(withIdentifier: "CardCellView", owner: self) as? CardCellView { 211 | cardCellView.textField?.stringValue = self.cards[cardIndex].title! 212 | return cardCellView 213 | } 214 | } 215 | print("ERROR: no reachable.") 216 | return nil 217 | } 218 | 219 | func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { 220 | //debugPrint("heightOfRow:\(row)") 221 | if row == 0 { 222 | // header 223 | if let view = tableView.make(withIdentifier: "HeaderCellView", owner: self) as? HeaderCellView { 224 | view.textField?.stringValue = self.column!.name 225 | return view.fittingSize.height 226 | } 227 | } else { 228 | // cards 229 | let cardIndex = row - 1 230 | if let view = tableView.make(withIdentifier: "CardCellView", owner: self) as? CardCellView { 231 | view.textField?.stringValue = self.cards[cardIndex].title! 232 | //view.textField?.preferredMaxLayoutWidth = 50 233 | return view.fittingSize.height 234 | } 235 | } 236 | print("ERROR: no reachable.") 237 | return 0.0 238 | } 239 | 240 | func tableViewColumnDidResize(_ notification: Notification) { 241 | debugPrint("tableViewColumnDidResize") 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /GPM.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CE5901A91D998C1B00659FFC /* CardCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5901A81D998C1B00659FFC /* CardCellView.swift */; }; 11 | CE891A931D8CC7EB000A9711 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE891A921D8CC7EB000A9711 /* AppDelegate.swift */; }; 12 | CE891A991D8CC7EB000A9711 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE891A981D8CC7EB000A9711 /* Assets.xcassets */; }; 13 | CE891A9C1D8CC7EB000A9711 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE891A9A1D8CC7EB000A9711 /* Main.storyboard */; }; 14 | CE891AA71D8CC7EB000A9711 /* GPMTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE891AA61D8CC7EB000A9711 /* GPMTests.swift */; }; 15 | CE91FB4E1D92E9E000D5079F /* GitHubService+Users.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE91FB4D1D92E9E000D5079F /* GitHubService+Users.swift */; }; 16 | CE91FB501D92EB2300D5079F /* GitHubUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE91FB4F1D92EB2300D5079F /* GitHubUser.swift */; }; 17 | CE91FB521D92EF6C00D5079F /* response_user.json in Resources */ = {isa = PBXBuildFile; fileRef = CE91FB511D92EF6C00D5079F /* response_user.json */; }; 18 | CE91FB541D92F2A800D5079F /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE91FB531D92F2A800D5079F /* LoginViewController.swift */; }; 19 | CE91FB561D9418E400D5079F /* GitHubScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE91FB551D9418E400D5079F /* GitHubScope.swift */; }; 20 | CE91FB5C1D960CF900D5079F /* GitHubServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE91FB5B1D960CF900D5079F /* GitHubServer.swift */; }; 21 | CE91FB811D997C4600D5079F /* KanbanColumnTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE91FB801D997C4600D5079F /* KanbanColumnTableViewController.swift */; }; 22 | CE951D381D921743004D99F3 /* KeychainAccess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE951D371D921743004D99F3 /* KeychainAccess.framework */; }; 23 | CE951D391D921743004D99F3 /* KeychainAccess.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE951D371D921743004D99F3 /* KeychainAccess.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 24 | CE951D3B1D92191F004D99F3 /* AccessTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE951D3A1D92191F004D99F3 /* AccessTokenService.swift */; }; 25 | CEA02D841DA94CB30092DEE9 /* KanbanColumnNoteUpdateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA02D821DA94CB30092DEE9 /* KanbanColumnNoteUpdateViewController.swift */; }; 26 | CEB662951D9FF18D003D8458 /* Array+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB662941D9FF18D003D8458 /* Array+Utilities.swift */; }; 27 | CEB662971D9FF1CE003D8458 /* ArrayUtilitiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB662961D9FF1CE003D8458 /* ArrayUtilitiesTests.swift */; }; 28 | CEB6629C1DA15CAB003D8458 /* HeaderCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB6629B1DA15CAB003D8458 /* HeaderCellView.swift */; }; 29 | CEB6629E1DA16D4C003D8458 /* KanbanColumnNoteRegisterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB6629D1DA16D4C003D8458 /* KanbanColumnNoteRegisterViewController.swift */; }; 30 | CEB662A01DA17002003D8458 /* response_repos_owner_repo_projects_columns_column_id_card.json in Resources */ = {isa = PBXBuildFile; fileRef = CEB6629F1DA17002003D8458 /* response_repos_owner_repo_projects_columns_column_id_card.json */; }; 31 | CEC9C5ED1D8CF42B002C218F /* GitHubService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C5EC1D8CF42B002C218F /* GitHubService.swift */; }; 32 | CEC9C5EF1D8CF53C002C218F /* GitHubProject.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C5EE1D8CF53C002C218F /* GitHubProject.swift */; }; 33 | CEC9C5F21D8D7CFF002C218F /* GitHubService+Projects.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C5F11D8D7CFF002C218F /* GitHubService+Projects.swift */; }; 34 | CEC9C5F51D8D9635002C218F /* GitHubServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C5F41D8D9635002C218F /* GitHubServiceTests.swift */; }; 35 | CEC9C5F71D8D9708002C218F /* GitHubTestsSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C5F61D8D9708002C218F /* GitHubTestsSupport.swift */; }; 36 | CEC9C5F91D8D990F002C218F /* response_repos_owner_repo_projects.json in Resources */ = {isa = PBXBuildFile; fileRef = CEC9C5F81D8D990F002C218F /* response_repos_owner_repo_projects.json */; }; 37 | CEC9C5FF1D8D9D64002C218F /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE891AB71D8CD998000A9711 /* Alamofire.framework */; }; 38 | CEC9C6001D8D9D64002C218F /* Alamofire.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE891AB71D8CD998000A9711 /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 39 | CEC9C6031D8DA845002C218F /* response_repos_owner_repo_projects_project_number_columns.json in Resources */ = {isa = PBXBuildFile; fileRef = CEC9C6021D8DA845002C218F /* response_repos_owner_repo_projects_project_number_columns.json */; }; 40 | CEC9C6051D8DB441002C218F /* response_repos_owner_repo_projects_columns_column_id_cards.json in Resources */ = {isa = PBXBuildFile; fileRef = CEC9C6041D8DB441002C218F /* response_repos_owner_repo_projects_columns_column_id_cards.json */; }; 41 | CEC9C6071D8E316F002C218F /* GitHubService+Issues.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C6061D8E316F002C218F /* GitHubService+Issues.swift */; }; 42 | CEC9C6091D8E324A002C218F /* GitHubIssue.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C6081D8E324A002C218F /* GitHubIssue.swift */; }; 43 | CEC9C60B1D8E3373002C218F /* response_repos_owner_repo_issues_number.json in Resources */ = {isa = PBXBuildFile; fileRef = CEC9C60A1D8E3373002C218F /* response_repos_owner_repo_issues_number.json */; }; 44 | CEC9C60D1D8E3719002C218F /* BookmarkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C60C1D8E3719002C218F /* BookmarkViewController.swift */; }; 45 | CEC9C6101D8E372B002C218F /* KanbanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C60E1D8E372B002C218F /* KanbanViewController.swift */; }; 46 | CEC9C6131D8E3B2C002C218F /* Kanban.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C6121D8E3B2C002C218F /* Kanban.swift */; }; 47 | CEC9C6151D8E4154002C218F /* KanbanService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C6141D8E4154002C218F /* KanbanService.swift */; }; 48 | CEC9C61D1D8EC968002C218F /* Card.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C61C1D8EC968002C218F /* Card.swift */; }; 49 | CEC9C61F1D8EE758002C218F /* SplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C61E1D8EE758002C218F /* SplitViewController.swift */; }; 50 | CEC9C6211D8EF045002C218F /* Column.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C6201D8EF045002C218F /* Column.swift */; }; 51 | CEC9C6251D902E45002C218F /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C6241D902E45002C218F /* WindowController.swift */; }; 52 | CEC9C6271D903749002C218F /* KanbanRegisterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC9C6261D903749002C218F /* KanbanRegisterViewController.swift */; }; 53 | /* End PBXBuildFile section */ 54 | 55 | /* Begin PBXContainerItemProxy section */ 56 | CE891AA31D8CC7EB000A9711 /* PBXContainerItemProxy */ = { 57 | isa = PBXContainerItemProxy; 58 | containerPortal = CE891A871D8CC7EA000A9711 /* Project object */; 59 | proxyType = 1; 60 | remoteGlobalIDString = CE891A8E1D8CC7EA000A9711; 61 | remoteInfo = GPM; 62 | }; 63 | /* End PBXContainerItemProxy section */ 64 | 65 | /* Begin PBXCopyFilesBuildPhase section */ 66 | CEC9C6011D8D9D64002C218F /* Embed Frameworks */ = { 67 | isa = PBXCopyFilesBuildPhase; 68 | buildActionMask = 2147483647; 69 | dstPath = ""; 70 | dstSubfolderSpec = 10; 71 | files = ( 72 | CE951D391D921743004D99F3 /* KeychainAccess.framework in Embed Frameworks */, 73 | CEC9C6001D8D9D64002C218F /* Alamofire.framework in Embed Frameworks */, 74 | ); 75 | name = "Embed Frameworks"; 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | /* End PBXCopyFilesBuildPhase section */ 79 | 80 | /* Begin PBXFileReference section */ 81 | CE5901A81D998C1B00659FFC /* CardCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardCellView.swift; sourceTree = ""; }; 82 | CE891A8F1D8CC7EB000A9711 /* GPM.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GPM.app; sourceTree = BUILT_PRODUCTS_DIR; }; 83 | CE891A921D8CC7EB000A9711 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 84 | CE891A981D8CC7EB000A9711 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 85 | CE891A9B1D8CC7EB000A9711 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 86 | CE891A9D1D8CC7EB000A9711 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 87 | CE891AA21D8CC7EB000A9711 /* GPMTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GPMTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 88 | CE891AA61D8CC7EB000A9711 /* GPMTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPMTests.swift; sourceTree = ""; }; 89 | CE891AA81D8CC7EB000A9711 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 90 | CE891AB71D8CD998000A9711 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/Mac/Alamofire.framework; sourceTree = ""; }; 91 | CE91FB4D1D92E9E000D5079F /* GitHubService+Users.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GitHubService+Users.swift"; sourceTree = ""; }; 92 | CE91FB4F1D92EB2300D5079F /* GitHubUser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubUser.swift; sourceTree = ""; }; 93 | CE91FB511D92EF6C00D5079F /* response_user.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = response_user.json; sourceTree = ""; }; 94 | CE91FB531D92F2A800D5079F /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 95 | CE91FB551D9418E400D5079F /* GitHubScope.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubScope.swift; sourceTree = ""; }; 96 | CE91FB5B1D960CF900D5079F /* GitHubServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubServer.swift; sourceTree = ""; }; 97 | CE91FB801D997C4600D5079F /* KanbanColumnTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KanbanColumnTableViewController.swift; sourceTree = ""; }; 98 | CE951D371D921743004D99F3 /* KeychainAccess.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KeychainAccess.framework; path = Carthage/Build/Mac/KeychainAccess.framework; sourceTree = ""; }; 99 | CE951D3A1D92191F004D99F3 /* AccessTokenService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessTokenService.swift; sourceTree = ""; }; 100 | CEA02D821DA94CB30092DEE9 /* KanbanColumnNoteUpdateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KanbanColumnNoteUpdateViewController.swift; sourceTree = ""; }; 101 | CEB662941D9FF18D003D8458 /* Array+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Utilities.swift"; sourceTree = ""; }; 102 | CEB662961D9FF1CE003D8458 /* ArrayUtilitiesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayUtilitiesTests.swift; sourceTree = ""; }; 103 | CEB6629B1DA15CAB003D8458 /* HeaderCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HeaderCellView.swift; path = GPM/HeaderCellView.swift; sourceTree = SOURCE_ROOT; }; 104 | CEB6629D1DA16D4C003D8458 /* KanbanColumnNoteRegisterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = KanbanColumnNoteRegisterViewController.swift; path = GPM/KanbanColumnNoteRegisterViewController.swift; sourceTree = SOURCE_ROOT; }; 105 | CEB6629F1DA17002003D8458 /* response_repos_owner_repo_projects_columns_column_id_card.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = response_repos_owner_repo_projects_columns_column_id_card.json; sourceTree = ""; }; 106 | CEC9C5EC1D8CF42B002C218F /* GitHubService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubService.swift; sourceTree = ""; }; 107 | CEC9C5EE1D8CF53C002C218F /* GitHubProject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubProject.swift; sourceTree = ""; }; 108 | CEC9C5F11D8D7CFF002C218F /* GitHubService+Projects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GitHubService+Projects.swift"; sourceTree = ""; }; 109 | CEC9C5F41D8D9635002C218F /* GitHubServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubServiceTests.swift; sourceTree = ""; }; 110 | CEC9C5F61D8D9708002C218F /* GitHubTestsSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubTestsSupport.swift; sourceTree = ""; }; 111 | CEC9C5F81D8D990F002C218F /* response_repos_owner_repo_projects.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = response_repos_owner_repo_projects.json; sourceTree = ""; }; 112 | CEC9C6021D8DA845002C218F /* response_repos_owner_repo_projects_project_number_columns.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = response_repos_owner_repo_projects_project_number_columns.json; sourceTree = ""; }; 113 | CEC9C6041D8DB441002C218F /* response_repos_owner_repo_projects_columns_column_id_cards.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = response_repos_owner_repo_projects_columns_column_id_cards.json; sourceTree = ""; }; 114 | CEC9C6061D8E316F002C218F /* GitHubService+Issues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GitHubService+Issues.swift"; sourceTree = ""; }; 115 | CEC9C6081D8E324A002C218F /* GitHubIssue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubIssue.swift; sourceTree = ""; }; 116 | CEC9C60A1D8E3373002C218F /* response_repos_owner_repo_issues_number.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = response_repos_owner_repo_issues_number.json; sourceTree = ""; }; 117 | CEC9C60C1D8E3719002C218F /* BookmarkViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkViewController.swift; sourceTree = ""; }; 118 | CEC9C60E1D8E372B002C218F /* KanbanViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KanbanViewController.swift; sourceTree = ""; }; 119 | CEC9C6121D8E3B2C002C218F /* Kanban.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Kanban.swift; sourceTree = ""; }; 120 | CEC9C6141D8E4154002C218F /* KanbanService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KanbanService.swift; sourceTree = ""; }; 121 | CEC9C6161D8E4BE6002C218F /* GPM.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GPM.entitlements; sourceTree = ""; }; 122 | CEC9C61C1D8EC968002C218F /* Card.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Card.swift; sourceTree = ""; }; 123 | CEC9C61E1D8EE758002C218F /* SplitViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplitViewController.swift; sourceTree = ""; }; 124 | CEC9C6201D8EF045002C218F /* Column.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Column.swift; sourceTree = ""; }; 125 | CEC9C6241D902E45002C218F /* WindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = ""; }; 126 | CEC9C6261D903749002C218F /* KanbanRegisterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KanbanRegisterViewController.swift; sourceTree = ""; }; 127 | /* End PBXFileReference section */ 128 | 129 | /* Begin PBXFrameworksBuildPhase section */ 130 | CE891A8C1D8CC7EA000A9711 /* Frameworks */ = { 131 | isa = PBXFrameworksBuildPhase; 132 | buildActionMask = 2147483647; 133 | files = ( 134 | CE951D381D921743004D99F3 /* KeychainAccess.framework in Frameworks */, 135 | CEC9C5FF1D8D9D64002C218F /* Alamofire.framework in Frameworks */, 136 | ); 137 | runOnlyForDeploymentPostprocessing = 0; 138 | }; 139 | CE891A9F1D8CC7EB000A9711 /* Frameworks */ = { 140 | isa = PBXFrameworksBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | ); 144 | runOnlyForDeploymentPostprocessing = 0; 145 | }; 146 | /* End PBXFrameworksBuildPhase section */ 147 | 148 | /* Begin PBXGroup section */ 149 | CE891A861D8CC7EA000A9711 = { 150 | isa = PBXGroup; 151 | children = ( 152 | CE891A911D8CC7EB000A9711 /* GPM */, 153 | CE891AA51D8CC7EB000A9711 /* GPMTests */, 154 | CE891A901D8CC7EB000A9711 /* Products */, 155 | CE891AB61D8CD998000A9711 /* Frameworks */, 156 | ); 157 | sourceTree = ""; 158 | }; 159 | CE891A901D8CC7EB000A9711 /* Products */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | CE891A8F1D8CC7EB000A9711 /* GPM.app */, 163 | CE891AA21D8CC7EB000A9711 /* GPMTests.xctest */, 164 | ); 165 | name = Products; 166 | sourceTree = ""; 167 | }; 168 | CE891A911D8CC7EB000A9711 /* GPM */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | CEC9C5EB1D8CF40E002C218F /* github */, 172 | CE891A921D8CC7EB000A9711 /* AppDelegate.swift */, 173 | CEC9C6241D902E45002C218F /* WindowController.swift */, 174 | CEC9C61E1D8EE758002C218F /* SplitViewController.swift */, 175 | CEC9C60C1D8E3719002C218F /* BookmarkViewController.swift */, 176 | CEC9C60E1D8E372B002C218F /* KanbanViewController.swift */, 177 | CE91FB801D997C4600D5079F /* KanbanColumnTableViewController.swift */, 178 | CEB6629D1DA16D4C003D8458 /* KanbanColumnNoteRegisterViewController.swift */, 179 | CEA02D821DA94CB30092DEE9 /* KanbanColumnNoteUpdateViewController.swift */, 180 | CEC9C6261D903749002C218F /* KanbanRegisterViewController.swift */, 181 | CE91FB531D92F2A800D5079F /* LoginViewController.swift */, 182 | CE951D3A1D92191F004D99F3 /* AccessTokenService.swift */, 183 | CEC9C6201D8EF045002C218F /* Column.swift */, 184 | CEC9C61C1D8EC968002C218F /* Card.swift */, 185 | CEB6629B1DA15CAB003D8458 /* HeaderCellView.swift */, 186 | CE5901A81D998C1B00659FFC /* CardCellView.swift */, 187 | CEC9C6141D8E4154002C218F /* KanbanService.swift */, 188 | CEC9C6121D8E3B2C002C218F /* Kanban.swift */, 189 | CEB662941D9FF18D003D8458 /* Array+Utilities.swift */, 190 | CE891A981D8CC7EB000A9711 /* Assets.xcassets */, 191 | CE891A9A1D8CC7EB000A9711 /* Main.storyboard */, 192 | CE891A9D1D8CC7EB000A9711 /* Info.plist */, 193 | CEC9C6161D8E4BE6002C218F /* GPM.entitlements */, 194 | ); 195 | path = GPM; 196 | sourceTree = ""; 197 | }; 198 | CE891AA51D8CC7EB000A9711 /* GPMTests */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | CEC9C5F31D8D94C7002C218F /* github */, 202 | CE891AA61D8CC7EB000A9711 /* GPMTests.swift */, 203 | CEB662961D9FF1CE003D8458 /* ArrayUtilitiesTests.swift */, 204 | CE891AA81D8CC7EB000A9711 /* Info.plist */, 205 | ); 206 | path = GPMTests; 207 | sourceTree = ""; 208 | }; 209 | CE891AB61D8CD998000A9711 /* Frameworks */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | CE891AB71D8CD998000A9711 /* Alamofire.framework */, 213 | CE951D371D921743004D99F3 /* KeychainAccess.framework */, 214 | ); 215 | name = Frameworks; 216 | sourceTree = ""; 217 | }; 218 | CEC9C5EB1D8CF40E002C218F /* github */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | CE91FB5B1D960CF900D5079F /* GitHubServer.swift */, 222 | CEC9C5EC1D8CF42B002C218F /* GitHubService.swift */, 223 | CEC9C5F11D8D7CFF002C218F /* GitHubService+Projects.swift */, 224 | CEC9C6061D8E316F002C218F /* GitHubService+Issues.swift */, 225 | CE91FB4D1D92E9E000D5079F /* GitHubService+Users.swift */, 226 | CEC9C5EE1D8CF53C002C218F /* GitHubProject.swift */, 227 | CEC9C6081D8E324A002C218F /* GitHubIssue.swift */, 228 | CE91FB4F1D92EB2300D5079F /* GitHubUser.swift */, 229 | CE91FB551D9418E400D5079F /* GitHubScope.swift */, 230 | ); 231 | path = github; 232 | sourceTree = ""; 233 | }; 234 | CEC9C5F31D8D94C7002C218F /* github */ = { 235 | isa = PBXGroup; 236 | children = ( 237 | CEC9C5F61D8D9708002C218F /* GitHubTestsSupport.swift */, 238 | CEC9C5F41D8D9635002C218F /* GitHubServiceTests.swift */, 239 | CEC9C5F81D8D990F002C218F /* response_repos_owner_repo_projects.json */, 240 | CEC9C6021D8DA845002C218F /* response_repos_owner_repo_projects_project_number_columns.json */, 241 | CEC9C6041D8DB441002C218F /* response_repos_owner_repo_projects_columns_column_id_cards.json */, 242 | CEB6629F1DA17002003D8458 /* response_repos_owner_repo_projects_columns_column_id_card.json */, 243 | CEC9C60A1D8E3373002C218F /* response_repos_owner_repo_issues_number.json */, 244 | CE91FB511D92EF6C00D5079F /* response_user.json */, 245 | ); 246 | path = github; 247 | sourceTree = ""; 248 | }; 249 | /* End PBXGroup section */ 250 | 251 | /* Begin PBXNativeTarget section */ 252 | CE891A8E1D8CC7EA000A9711 /* GPM */ = { 253 | isa = PBXNativeTarget; 254 | buildConfigurationList = CE891AAB1D8CC7EB000A9711 /* Build configuration list for PBXNativeTarget "GPM" */; 255 | buildPhases = ( 256 | CE891A8B1D8CC7EA000A9711 /* Sources */, 257 | CE891A8C1D8CC7EA000A9711 /* Frameworks */, 258 | CE891A8D1D8CC7EA000A9711 /* Resources */, 259 | CEC9C6011D8D9D64002C218F /* Embed Frameworks */, 260 | ); 261 | buildRules = ( 262 | ); 263 | dependencies = ( 264 | ); 265 | name = GPM; 266 | productName = GPM; 267 | productReference = CE891A8F1D8CC7EB000A9711 /* GPM.app */; 268 | productType = "com.apple.product-type.application"; 269 | }; 270 | CE891AA11D8CC7EB000A9711 /* GPMTests */ = { 271 | isa = PBXNativeTarget; 272 | buildConfigurationList = CE891AAE1D8CC7EB000A9711 /* Build configuration list for PBXNativeTarget "GPMTests" */; 273 | buildPhases = ( 274 | CE891A9E1D8CC7EB000A9711 /* Sources */, 275 | CE891A9F1D8CC7EB000A9711 /* Frameworks */, 276 | CE891AA01D8CC7EB000A9711 /* Resources */, 277 | ); 278 | buildRules = ( 279 | ); 280 | dependencies = ( 281 | CE891AA41D8CC7EB000A9711 /* PBXTargetDependency */, 282 | ); 283 | name = GPMTests; 284 | productName = GPMTests; 285 | productReference = CE891AA21D8CC7EB000A9711 /* GPMTests.xctest */; 286 | productType = "com.apple.product-type.bundle.unit-test"; 287 | }; 288 | /* End PBXNativeTarget section */ 289 | 290 | /* Begin PBXProject section */ 291 | CE891A871D8CC7EA000A9711 /* Project object */ = { 292 | isa = PBXProject; 293 | attributes = { 294 | LastSwiftUpdateCheck = 0800; 295 | LastUpgradeCheck = 0800; 296 | ORGANIZATIONNAME = mtgto; 297 | TargetAttributes = { 298 | CE891A8E1D8CC7EA000A9711 = { 299 | CreatedOnToolsVersion = 8.0; 300 | DevelopmentTeam = W3A6B7FDC7; 301 | ProvisioningStyle = Automatic; 302 | SystemCapabilities = { 303 | com.apple.Keychain = { 304 | enabled = 0; 305 | }; 306 | com.apple.Sandbox = { 307 | enabled = 1; 308 | }; 309 | }; 310 | }; 311 | CE891AA11D8CC7EB000A9711 = { 312 | CreatedOnToolsVersion = 8.0; 313 | DevelopmentTeam = W3A6B7FDC7; 314 | ProvisioningStyle = Automatic; 315 | TestTargetID = CE891A8E1D8CC7EA000A9711; 316 | }; 317 | }; 318 | }; 319 | buildConfigurationList = CE891A8A1D8CC7EA000A9711 /* Build configuration list for PBXProject "GPM" */; 320 | compatibilityVersion = "Xcode 3.2"; 321 | developmentRegion = English; 322 | hasScannedForEncodings = 0; 323 | knownRegions = ( 324 | en, 325 | Base, 326 | ); 327 | mainGroup = CE891A861D8CC7EA000A9711; 328 | productRefGroup = CE891A901D8CC7EB000A9711 /* Products */; 329 | projectDirPath = ""; 330 | projectRoot = ""; 331 | targets = ( 332 | CE891A8E1D8CC7EA000A9711 /* GPM */, 333 | CE891AA11D8CC7EB000A9711 /* GPMTests */, 334 | ); 335 | }; 336 | /* End PBXProject section */ 337 | 338 | /* Begin PBXResourcesBuildPhase section */ 339 | CE891A8D1D8CC7EA000A9711 /* Resources */ = { 340 | isa = PBXResourcesBuildPhase; 341 | buildActionMask = 2147483647; 342 | files = ( 343 | CE891A991D8CC7EB000A9711 /* Assets.xcassets in Resources */, 344 | CE891A9C1D8CC7EB000A9711 /* Main.storyboard in Resources */, 345 | ); 346 | runOnlyForDeploymentPostprocessing = 0; 347 | }; 348 | CE891AA01D8CC7EB000A9711 /* Resources */ = { 349 | isa = PBXResourcesBuildPhase; 350 | buildActionMask = 2147483647; 351 | files = ( 352 | CEC9C6051D8DB441002C218F /* response_repos_owner_repo_projects_columns_column_id_cards.json in Resources */, 353 | CEC9C60B1D8E3373002C218F /* response_repos_owner_repo_issues_number.json in Resources */, 354 | CE91FB521D92EF6C00D5079F /* response_user.json in Resources */, 355 | CEC9C5F91D8D990F002C218F /* response_repos_owner_repo_projects.json in Resources */, 356 | CEC9C6031D8DA845002C218F /* response_repos_owner_repo_projects_project_number_columns.json in Resources */, 357 | CEB662A01DA17002003D8458 /* response_repos_owner_repo_projects_columns_column_id_card.json in Resources */, 358 | ); 359 | runOnlyForDeploymentPostprocessing = 0; 360 | }; 361 | /* End PBXResourcesBuildPhase section */ 362 | 363 | /* Begin PBXSourcesBuildPhase section */ 364 | CE891A8B1D8CC7EA000A9711 /* Sources */ = { 365 | isa = PBXSourcesBuildPhase; 366 | buildActionMask = 2147483647; 367 | files = ( 368 | CE91FB541D92F2A800D5079F /* LoginViewController.swift in Sources */, 369 | CE91FB5C1D960CF900D5079F /* GitHubServer.swift in Sources */, 370 | CE91FB4E1D92E9E000D5079F /* GitHubService+Users.swift in Sources */, 371 | CEA02D841DA94CB30092DEE9 /* KanbanColumnNoteUpdateViewController.swift in Sources */, 372 | CEC9C6091D8E324A002C218F /* GitHubIssue.swift in Sources */, 373 | CE951D3B1D92191F004D99F3 /* AccessTokenService.swift in Sources */, 374 | CE91FB501D92EB2300D5079F /* GitHubUser.swift in Sources */, 375 | CEB6629E1DA16D4C003D8458 /* KanbanColumnNoteRegisterViewController.swift in Sources */, 376 | CEC9C6071D8E316F002C218F /* GitHubService+Issues.swift in Sources */, 377 | CE891A931D8CC7EB000A9711 /* AppDelegate.swift in Sources */, 378 | CEC9C6211D8EF045002C218F /* Column.swift in Sources */, 379 | CEC9C5ED1D8CF42B002C218F /* GitHubService.swift in Sources */, 380 | CEC9C60D1D8E3719002C218F /* BookmarkViewController.swift in Sources */, 381 | CEB6629C1DA15CAB003D8458 /* HeaderCellView.swift in Sources */, 382 | CEC9C6271D903749002C218F /* KanbanRegisterViewController.swift in Sources */, 383 | CE5901A91D998C1B00659FFC /* CardCellView.swift in Sources */, 384 | CEC9C5EF1D8CF53C002C218F /* GitHubProject.swift in Sources */, 385 | CEB662951D9FF18D003D8458 /* Array+Utilities.swift in Sources */, 386 | CEC9C5F21D8D7CFF002C218F /* GitHubService+Projects.swift in Sources */, 387 | CEC9C61D1D8EC968002C218F /* Card.swift in Sources */, 388 | CE91FB811D997C4600D5079F /* KanbanColumnTableViewController.swift in Sources */, 389 | CEC9C6101D8E372B002C218F /* KanbanViewController.swift in Sources */, 390 | CEC9C61F1D8EE758002C218F /* SplitViewController.swift in Sources */, 391 | CE91FB561D9418E400D5079F /* GitHubScope.swift in Sources */, 392 | CEC9C6251D902E45002C218F /* WindowController.swift in Sources */, 393 | CEC9C6151D8E4154002C218F /* KanbanService.swift in Sources */, 394 | CEC9C6131D8E3B2C002C218F /* Kanban.swift in Sources */, 395 | ); 396 | runOnlyForDeploymentPostprocessing = 0; 397 | }; 398 | CE891A9E1D8CC7EB000A9711 /* Sources */ = { 399 | isa = PBXSourcesBuildPhase; 400 | buildActionMask = 2147483647; 401 | files = ( 402 | CEC9C5F51D8D9635002C218F /* GitHubServiceTests.swift in Sources */, 403 | CE891AA71D8CC7EB000A9711 /* GPMTests.swift in Sources */, 404 | CEC9C5F71D8D9708002C218F /* GitHubTestsSupport.swift in Sources */, 405 | CEB662971D9FF1CE003D8458 /* ArrayUtilitiesTests.swift in Sources */, 406 | ); 407 | runOnlyForDeploymentPostprocessing = 0; 408 | }; 409 | /* End PBXSourcesBuildPhase section */ 410 | 411 | /* Begin PBXTargetDependency section */ 412 | CE891AA41D8CC7EB000A9711 /* PBXTargetDependency */ = { 413 | isa = PBXTargetDependency; 414 | target = CE891A8E1D8CC7EA000A9711 /* GPM */; 415 | targetProxy = CE891AA31D8CC7EB000A9711 /* PBXContainerItemProxy */; 416 | }; 417 | /* End PBXTargetDependency section */ 418 | 419 | /* Begin PBXVariantGroup section */ 420 | CE891A9A1D8CC7EB000A9711 /* Main.storyboard */ = { 421 | isa = PBXVariantGroup; 422 | children = ( 423 | CE891A9B1D8CC7EB000A9711 /* Base */, 424 | ); 425 | name = Main.storyboard; 426 | sourceTree = ""; 427 | }; 428 | /* End PBXVariantGroup section */ 429 | 430 | /* Begin XCBuildConfiguration section */ 431 | CE891AA91D8CC7EB000A9711 /* Debug */ = { 432 | isa = XCBuildConfiguration; 433 | buildSettings = { 434 | ALWAYS_SEARCH_USER_PATHS = NO; 435 | CLANG_ANALYZER_NONNULL = YES; 436 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 437 | CLANG_CXX_LIBRARY = "libc++"; 438 | CLANG_ENABLE_MODULES = YES; 439 | CLANG_ENABLE_OBJC_ARC = YES; 440 | CLANG_WARN_BOOL_CONVERSION = YES; 441 | CLANG_WARN_CONSTANT_CONVERSION = YES; 442 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 443 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 444 | CLANG_WARN_EMPTY_BODY = YES; 445 | CLANG_WARN_ENUM_CONVERSION = YES; 446 | CLANG_WARN_INFINITE_RECURSION = YES; 447 | CLANG_WARN_INT_CONVERSION = YES; 448 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 449 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 450 | CLANG_WARN_UNREACHABLE_CODE = YES; 451 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 452 | CODE_SIGN_IDENTITY = "-"; 453 | COPY_PHASE_STRIP = NO; 454 | DEBUG_INFORMATION_FORMAT = dwarf; 455 | ENABLE_STRICT_OBJC_MSGSEND = YES; 456 | ENABLE_TESTABILITY = YES; 457 | GCC_C_LANGUAGE_STANDARD = gnu99; 458 | GCC_DYNAMIC_NO_PIC = NO; 459 | GCC_NO_COMMON_BLOCKS = YES; 460 | GCC_OPTIMIZATION_LEVEL = 0; 461 | GCC_PREPROCESSOR_DEFINITIONS = ( 462 | "DEBUG=1", 463 | "$(inherited)", 464 | ); 465 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 466 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 467 | GCC_WARN_UNDECLARED_SELECTOR = YES; 468 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 469 | GCC_WARN_UNUSED_FUNCTION = YES; 470 | GCC_WARN_UNUSED_VARIABLE = YES; 471 | MACOSX_DEPLOYMENT_TARGET = 10.11; 472 | MTL_ENABLE_DEBUG_INFO = YES; 473 | ONLY_ACTIVE_ARCH = YES; 474 | SDKROOT = macosx; 475 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 476 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 477 | }; 478 | name = Debug; 479 | }; 480 | CE891AAA1D8CC7EB000A9711 /* Release */ = { 481 | isa = XCBuildConfiguration; 482 | buildSettings = { 483 | ALWAYS_SEARCH_USER_PATHS = NO; 484 | CLANG_ANALYZER_NONNULL = YES; 485 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 486 | CLANG_CXX_LIBRARY = "libc++"; 487 | CLANG_ENABLE_MODULES = YES; 488 | CLANG_ENABLE_OBJC_ARC = YES; 489 | CLANG_WARN_BOOL_CONVERSION = YES; 490 | CLANG_WARN_CONSTANT_CONVERSION = YES; 491 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 492 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 493 | CLANG_WARN_EMPTY_BODY = YES; 494 | CLANG_WARN_ENUM_CONVERSION = YES; 495 | CLANG_WARN_INFINITE_RECURSION = YES; 496 | CLANG_WARN_INT_CONVERSION = YES; 497 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 498 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 499 | CLANG_WARN_UNREACHABLE_CODE = YES; 500 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 501 | CODE_SIGN_IDENTITY = "-"; 502 | COPY_PHASE_STRIP = NO; 503 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 504 | ENABLE_NS_ASSERTIONS = NO; 505 | ENABLE_STRICT_OBJC_MSGSEND = YES; 506 | GCC_C_LANGUAGE_STANDARD = gnu99; 507 | GCC_NO_COMMON_BLOCKS = YES; 508 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 509 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 510 | GCC_WARN_UNDECLARED_SELECTOR = YES; 511 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 512 | GCC_WARN_UNUSED_FUNCTION = YES; 513 | GCC_WARN_UNUSED_VARIABLE = YES; 514 | MACOSX_DEPLOYMENT_TARGET = 10.11; 515 | MTL_ENABLE_DEBUG_INFO = NO; 516 | SDKROOT = macosx; 517 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 518 | }; 519 | name = Release; 520 | }; 521 | CE891AAC1D8CC7EB000A9711 /* Debug */ = { 522 | isa = XCBuildConfiguration; 523 | buildSettings = { 524 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 525 | CODE_SIGN_ENTITLEMENTS = GPM/GPM.entitlements; 526 | COMBINE_HIDPI_IMAGES = YES; 527 | DEVELOPMENT_TEAM = W3A6B7FDC7; 528 | FRAMEWORK_SEARCH_PATHS = ( 529 | "$(inherited)", 530 | "$(PROJECT_DIR)/Carthage/Build/Mac", 531 | ); 532 | INFOPLIST_FILE = GPM/Info.plist; 533 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 534 | PRODUCT_BUNDLE_IDENTIFIER = net.mtgto.GPM; 535 | PRODUCT_NAME = "$(TARGET_NAME)"; 536 | SWIFT_VERSION = 3.0; 537 | }; 538 | name = Debug; 539 | }; 540 | CE891AAD1D8CC7EB000A9711 /* Release */ = { 541 | isa = XCBuildConfiguration; 542 | buildSettings = { 543 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 544 | CODE_SIGN_ENTITLEMENTS = GPM/GPM.entitlements; 545 | COMBINE_HIDPI_IMAGES = YES; 546 | DEVELOPMENT_TEAM = W3A6B7FDC7; 547 | FRAMEWORK_SEARCH_PATHS = ( 548 | "$(inherited)", 549 | "$(PROJECT_DIR)/Carthage/Build/Mac", 550 | ); 551 | INFOPLIST_FILE = GPM/Info.plist; 552 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 553 | PRODUCT_BUNDLE_IDENTIFIER = net.mtgto.GPM; 554 | PRODUCT_NAME = "$(TARGET_NAME)"; 555 | SWIFT_VERSION = 3.0; 556 | }; 557 | name = Release; 558 | }; 559 | CE891AAF1D8CC7EB000A9711 /* Debug */ = { 560 | isa = XCBuildConfiguration; 561 | buildSettings = { 562 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 563 | BUNDLE_LOADER = "$(TEST_HOST)"; 564 | COMBINE_HIDPI_IMAGES = YES; 565 | DEVELOPMENT_TEAM = W3A6B7FDC7; 566 | INFOPLIST_FILE = GPMTests/Info.plist; 567 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 568 | PRODUCT_BUNDLE_IDENTIFIER = net.mtgto.GPMTests; 569 | PRODUCT_NAME = "$(TARGET_NAME)"; 570 | SWIFT_VERSION = 3.0; 571 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GPM.app/Contents/MacOS/GPM"; 572 | }; 573 | name = Debug; 574 | }; 575 | CE891AB01D8CC7EB000A9711 /* Release */ = { 576 | isa = XCBuildConfiguration; 577 | buildSettings = { 578 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 579 | BUNDLE_LOADER = "$(TEST_HOST)"; 580 | COMBINE_HIDPI_IMAGES = YES; 581 | DEVELOPMENT_TEAM = W3A6B7FDC7; 582 | INFOPLIST_FILE = GPMTests/Info.plist; 583 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 584 | PRODUCT_BUNDLE_IDENTIFIER = net.mtgto.GPMTests; 585 | PRODUCT_NAME = "$(TARGET_NAME)"; 586 | SWIFT_VERSION = 3.0; 587 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GPM.app/Contents/MacOS/GPM"; 588 | }; 589 | name = Release; 590 | }; 591 | /* End XCBuildConfiguration section */ 592 | 593 | /* Begin XCConfigurationList section */ 594 | CE891A8A1D8CC7EA000A9711 /* Build configuration list for PBXProject "GPM" */ = { 595 | isa = XCConfigurationList; 596 | buildConfigurations = ( 597 | CE891AA91D8CC7EB000A9711 /* Debug */, 598 | CE891AAA1D8CC7EB000A9711 /* Release */, 599 | ); 600 | defaultConfigurationIsVisible = 0; 601 | defaultConfigurationName = Release; 602 | }; 603 | CE891AAB1D8CC7EB000A9711 /* Build configuration list for PBXNativeTarget "GPM" */ = { 604 | isa = XCConfigurationList; 605 | buildConfigurations = ( 606 | CE891AAC1D8CC7EB000A9711 /* Debug */, 607 | CE891AAD1D8CC7EB000A9711 /* Release */, 608 | ); 609 | defaultConfigurationIsVisible = 0; 610 | defaultConfigurationName = Release; 611 | }; 612 | CE891AAE1D8CC7EB000A9711 /* Build configuration list for PBXNativeTarget "GPMTests" */ = { 613 | isa = XCConfigurationList; 614 | buildConfigurations = ( 615 | CE891AAF1D8CC7EB000A9711 /* Debug */, 616 | CE891AB01D8CC7EB000A9711 /* Release */, 617 | ); 618 | defaultConfigurationIsVisible = 0; 619 | defaultConfigurationName = Release; 620 | }; 621 | /* End XCConfigurationList section */ 622 | }; 623 | rootObject = CE891A871D8CC7EA000A9711 /* Project object */; 624 | } 625 | --------------------------------------------------------------------------------