Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented drag-n-drop task reordering #8

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions ReSwift-Todo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@
507347901D7FFE8B00ACFD0D /* StreamReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5073478F1D7FFE8B00ACFD0D /* StreamReader.swift */; };
507347921D7FFFBD00ACFD0D /* ErrorHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507347911D7FFFBD00ACFD0D /* ErrorHelpers.swift */; };
507347941D8001A200ACFD0D /* String+ReSwiftTodo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507347931D8001A200ACFD0D /* String+ReSwiftTodo.swift */; };
6972332A240708A500E91FBA /* ToDoPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 697233282407084100E91FBA /* ToDoPasteboardWriter.swift */; };
6972332C24070EDE00E91FBA /* ToDoSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6972332B24070EDE00E91FBA /* ToDoSerializer.swift */; };
6972332E2407679800E91FBA /* Array+ReSwiftTodo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6972332D2407679800E91FBA /* Array+ReSwiftTodo.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -164,6 +167,9 @@
5073478F1D7FFE8B00ACFD0D /* StreamReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreamReader.swift; sourceTree = "<group>"; };
507347911D7FFFBD00ACFD0D /* ErrorHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorHelpers.swift; sourceTree = "<group>"; };
507347931D8001A200ACFD0D /* String+ReSwiftTodo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "String+ReSwiftTodo.swift"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
697233282407084100E91FBA /* ToDoPasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ToDoPasteboardWriter.swift; path = "ReSwift-Todo/ToDoPasteboardWriter.swift"; sourceTree = SOURCE_ROOT; };
6972332B24070EDE00E91FBA /* ToDoSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToDoSerializer.swift; sourceTree = "<group>"; };
6972332D2407679800E91FBA /* Array+ReSwiftTodo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+ReSwiftTodo.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -208,6 +214,7 @@
504CDFDD1D8A6DA800D16314 /* Util */ = {
isa = PBXGroup;
children = (
6972332D2407679800E91FBA /* Array+ReSwiftTodo.swift */,
50703B461D7EF83D001DAF45 /* CollectionType+ReSwiftTodo.swift */,
507347931D8001A200ACFD0D /* String+ReSwiftTodo.swift */,
);
Expand Down Expand Up @@ -350,6 +357,7 @@
50703B311D7EB736001DAF45 /* ToDoListWindowController.swift */,
50703B441D7EF590001DAF45 /* ToDoTableDataSource.swift */,
50703B3A1D7EE569001DAF45 /* ToDoListViewModel.swift */,
697233282407084100E91FBA /* ToDoPasteboardWriter.swift */,
504CDFFD1D8ADF3400D16314 /* KeyboardEventHandler.swift */,
50703B3C1D7EE581001DAF45 /* To-Do Item */,
504CDFFC1D8ADF2400D16314 /* Components */,
Expand Down Expand Up @@ -408,6 +416,7 @@
502C04841D8850D00032B7F3 /* ToDoLineTokenizer.swift */,
5073478F1D7FFE8B00ACFD0D /* StreamReader.swift */,
502C046E1D87D9580032B7F3 /* ToDoListSerializer.swift */,
6972332B24070EDE00E91FBA /* ToDoSerializer.swift */,
);
name = Persistence;
sourceTree = "<group>";
Expand Down Expand Up @@ -555,12 +564,14 @@
50703AE91D7EACB8001DAF45 /* ToDoDocument.swift in Sources */,
502C04801D880C2A0032B7F3 /* DateConverter.swift in Sources */,
50703B0A1D7EAD09001DAF45 /* ToDoList.swift in Sources */,
6972332E2407679800E91FBA /* Array+ReSwiftTodo.swift in Sources */,
507347901D7FFE8B00ACFD0D /* StreamReader.swift in Sources */,
50703B371D7EBC76001DAF45 /* ToDoCellView.swift in Sources */,
504CDFE71D8A721D00D16314 /* UndoActionContext.swift in Sources */,
502C04851D8850D00032B7F3 /* ToDoLineTokenizer.swift in Sources */,
50703B0D1D7EAD09001DAF45 /* ToDoID.swift in Sources */,
504CDFE31D8A6F2D00D16314 /* UndoCommand.swift in Sources */,
6972332C24070EDE00E91FBA /* ToDoSerializer.swift in Sources */,
50703B4E1D7F1407001DAF45 /* ToDoListImporter.swift in Sources */,
504CDFE11D8A6E7300D16314 /* NotUndoable.swift in Sources */,
504CDFF71D8ADB3D00D16314 /* ToDoTableView.swift in Sources */,
Expand All @@ -585,6 +596,7 @@
504CDFEE1D8A8E1300D16314 /* SelectionState.swift in Sources */,
502C046F1D87D9580032B7F3 /* ToDoListSerializer.swift in Sources */,
50703B2C1D7EB2E7001DAF45 /* LoggingMiddleware.swift in Sources */,
6972332A240708A500E91FBA /* ToDoPasteboardWriter.swift in Sources */,
50703B341D7EB838001DAF45 /* ToDoListPresenter.swift in Sources */,
50703B301D7EB60F001DAF45 /* ToDoListState.swift in Sources */,
502C04731D87DF040032B7F3 /* ErrorHandling.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
29 changes: 29 additions & 0 deletions ReSwift-Todo/Array+ReSwiftTodo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Array+ReSwiftTodo.swift
// ReSwift-Todo
//
// Created by Jay Koutavas on 2/26/20.
// Copyright © 2020 ReSwift. All rights reserved.
//

import Foundation

extension Array {
// These functions from sooop on GitHub
// https://gist.github.com/sooop/3c964900d429516ba48bd75050d0de0a
mutating func move(from start: Index, to end: Index) {
guard (0..<count) ~= start, (0...count) ~= end else { return }
if start == end { return }
let targetIndex = start < end ? end - 1 : end
insert(remove(at: start), at: targetIndex)
}

mutating func move(from indexes: IndexSet, to toIndex: Index) {
let movingData = indexes.map{ self[$0] }
let targetIndex = toIndex - indexes.filter{ $0 < toIndex }.count
for (i, e) in indexes.enumerated() {
remove(at: e - i)
}
insert(contentsOf: movingData, at: targetIndex)
}
}
6 changes: 6 additions & 0 deletions ReSwift-Todo/ToDoList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
// Copyright © 2016 ReSwift. All rights reserved.
//

import Foundation

struct ToDoList {

static var empty: ToDoList { return ToDoList(title: nil, items: []) }
Expand Down Expand Up @@ -39,6 +41,10 @@ struct ToDoList {
}
}

mutating func moveItems(from: Int, to: Int) {
items.move(from: from, to: to)
}

func indexOf(toDoID: ToDoID) -> Int? {

return items.firstIndex(where: { $0.toDoID == toDoID })
Expand Down
29 changes: 29 additions & 0 deletions ReSwift-Todo/ToDoListActions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,32 @@ struct RemoveTaskAction: UndoableAction, ToDoListAction {
return InsertTaskAction(toDo: removingToDo.toDo, index: removingToDo.index)
}
}

struct MoveTaskAction: UndoableAction, ToDoListAction {
let from: Int
let to: Int

init(from: Int, to: Int) {
self.from = from
self.to = to
}

func apply(oldToDoList: ToDoList) -> ToDoList {

var result = oldToDoList
result.moveItems(from: from, to: to)
return result
}

var name: String { return "Move Task" }
var isUndoable: Bool { return true }

func inverse(context: UndoActionContext) -> UndoableAction? {

let movedDown = self.to > self.from
let inversedFrom = movedDown ? self.to-1 : self.to
let inversedTo = movedDown ? self.from : self.from+1

return MoveTaskAction(from: inversedFrom, to: inversedTo)
}
}
27 changes: 3 additions & 24 deletions ReSwift-Todo/ToDoListSerializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ enum SerializationError: Error {
class ToDoListSerializer {

init() { }

lazy var dateConverter: DateConverter = DateConverter()
lazy var toDoSerializer = ToDoSerializer()

func data(toDoList: ToDoList, encoding: String.Encoding = String.Encoding.utf8) -> Data? {

Expand All @@ -47,35 +47,14 @@ class ToDoListSerializer {
guard !toDoList.isEmpty else { return "" }

let title = toDoList.title.map { $0.appended(":") } ?? ""
let items = toDoList.items.map(itemRepresentation)
let items = toDoList.items.map(toDoSerializer.itemRepresentation)

let lines = [title]
.appendedContentsOf(items)
.filter({ !$0.isEmpty }) // Remove empty title lines

return lines.joined(separator: "\n").appended("\n")
}

fileprivate func itemRepresentation(_ item: ToDo) -> String {

let body = "- \(item.title)"
let tags = item.tags.sorted().map { "@\($0)" }
let done: String? = {
switch item.completion {
case .unfinished: return nil
case .finished(when: let date):
guard let date = date else { return "@done" }

let dateString = dateConverter.string(date: date)
return "@done(\(dateString))"
}
}()

return [body]
.appendedContentsOf(tags)
.appendedContentsOf([done].compactMap(identity)) // remove nil
.joined(separator: " ")
}
}

func identity<T>(_ value: T?) -> T? {
Expand Down
9 changes: 9 additions & 0 deletions ReSwift-Todo/ToDoListWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ protocol ToDoTableDataSourceType {
var toDoCount: Int { get }

func updateContents(toDoListViewModel viewModel: ToDoListViewModel)
func setStore(toDoListStore: ToDoListStore?)
func toDoCellView(tableView: NSTableView, row: Int, owner: AnyObject) -> ToDoCellView?
}

Expand Down Expand Up @@ -64,6 +65,7 @@ class ToDoListWindowController: NSWindowController {

didSet {
keyboardEventHandler?.store = store
dataSource.setStore(toDoListStore: store)
}
}

Expand All @@ -86,6 +88,7 @@ class ToDoListWindowController: NSWindowController {

tableView.dataSource = self.dataSource.tableDataSource
tableView.delegate = self
tableView.registerForDraggedTypes([.todo, .tableViewIndex])

keyboardEventHandler?.dataSource = self.dataSource
keyboardEventHandler?.store = self.store
Expand Down Expand Up @@ -193,6 +196,12 @@ extension ToDoListWindowController: NSTableViewDelegate {

dispatchAction(action)
}

// Due to a bug with NSTableView, this method has to be implemented to get
// the draggingDestinationFeedbackStyle.gap animation to look right.
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
return 30
}
}

extension ToDoListWindowController: ToDoItemChangeDelegate {
Expand Down
50 changes: 50 additions & 0 deletions ReSwift-Todo/ToDoPasteboardWriter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// ToDoPasteboardWriter.swift
// ReSwift-TodoTests
//
// Created by Jay Koutavas on 2/26/20.
// Copyright © 2020 ReSwift. All rights reserved.
//

import Cocoa

class ToDoPasteboardWriter: NSObject, NSPasteboardWriting {
var todoViewModel: ToDoViewModel
var index: Int

init(todoViewModel: ToDoViewModel, at index: Int) {
self.todoViewModel = todoViewModel
self.index = index
}

func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] {
return [.todo, .tableViewIndex]
}

func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
switch type {
case .todo:
return todoViewModel.title
case .tableViewIndex:
return index
default:
return nil
}
}
}

extension NSPasteboard.PasteboardType {
static let todo = NSPasteboard.PasteboardType("com.heynow.todo")
static let tableViewIndex = NSPasteboard.PasteboardType("com.heynow.tableViewIndex")
}

extension NSPasteboardItem {
open func integer(forType type: NSPasteboard.PasteboardType) -> Int? {
guard let data = data(forType: type) else { return nil }
let plist = try? PropertyListSerialization.propertyList(
from: data,
options: .mutableContainers,
format: nil)
return plist as? Int
}
}
37 changes: 37 additions & 0 deletions ReSwift-Todo/ToDoSerializer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// ToDoSerializer.swift
// ReSwift-Todo
//
// Created by Jay Koutavas on 2/26/20.
// Copyright © 2020 ReSwift. All rights reserved.
//

import Foundation

class ToDoSerializer {

init() { }

lazy var dateConverter: DateConverter = DateConverter()

func itemRepresentation(_ item: ToDo) -> String {

let body = "- \(item.title)"
let tags = item.tags.sorted().map { "@\($0)" }
let done: String? = {
switch item.completion {
case .unfinished: return nil
case .finished(when: let date):
guard let date = date else { return "@done" }

let dateString = dateConverter.string(date: date)
return "@done(\(dateString))"
}
}()

return [body]
.appendedContentsOf(tags)
.appendedContentsOf([done].compactMap(identity)) // remove nil
.joined(separator: " ")
}
}
Loading