├── 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 | [](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 |
--------------------------------------------------------------------------------