Merge branch 'master' into search

This commit is contained in:
Aaron Gutierrez
2015-07-28 23:17:30 -07:00
2 changed files with 267 additions and 54 deletions

View File

@@ -3,6 +3,7 @@
import os import os
import sys import sys
import json import json
from threading import Thread
import urwid import urwid
import asana import asana
@@ -16,6 +17,8 @@ from secrets import CLIENT_ID, CLIENT_SECRET
PERSONAL = 498346170860 PERSONAL = 498346170860
class CmdAsana: class CmdAsana:
loop = None
def __init__(self): def __init__(self):
try: try:
f = open(".oauth", "r") f = open(".oauth", "r")
@@ -110,30 +113,73 @@ class CmdAsana:
self.client.tasks.update(task_id, completed=True) self.client.tasks.update(task_id, completed=True)
def newTask(self, task_after_id): def newTask(self, task_after_id):
if self.state['view'] == 'project': def runInThread():
task = self.client.tasks.create_in_workspace( if self.state['view'] == 'project':
self.state['workspace_id'], task = self.client.tasks.create_in_workspace(
projects=[self.state['id']] self.state['workspace_id'],
) projects=[self.state['id']]
if task_after_id != None: )
self.client.tasks.add_project(task['id'], if task_after_id != None:
project=self.state['id'], self.client.tasks.add_project(task['id'],
insert_after=task_after_id) project=self.state['id'],
else: insert_after=task_after_id)
task = self.client.tasks.create_in_workspace( else:
self.state['workspace_id'], task = self.client.tasks.create_in_workspace(
assignee=self.me['id'] self.state['workspace_id'],
) assignee=self.me['id']
)
task_list,_ = self.frame.contents[1] update(task)
task_list.insertNewTask(task)
def update(task):
task_list,_ = self.frame.contents[1]
task_list.insertNewTask(task)
thread = Thread(target=runInThread)
thread.start()
def updateTask(self, task_id, name): def updateTask(self, task_id, name):
self.client.tasks.update(task_id, name=name) def runInThread():
self.client.tasks.update(task_id, name=name)
thread = Thread(target=runInThread)
thread.start()
def updateDescription(self, task_id, description):
def runInThread():
self.client.tasks.update(task_id, notes=description)
thread = Thread(target=runInThread)
thread.start()
def assignTask(self, task_id, user_id):
def runInThread():
self.client.tasks.update(task_id, assignee=user_id)
thread = Thread(target=runInThread)
thread.start()
def addComment(self, task_id, comment): def addComment(self, task_id, comment):
self.client.stories.create_on_task(task_id, {"text": comment}) def runInThread():
self.showDetails(task_id) self.client.stories.create_on_task(task_id, {"text": comment})
self.showDetails(task_id, show_loading=False)
thread = Thread(target=runInThread)
thread.start()
def userTypeAhead(self, text, callback):
def runInThread():
users = self.client.workspaces.typeahead(self.state['workspace_id'],
{
'type': 'user',
'query': text,
'count': 5
})
callback(users)
self.loop.draw_screen()
thread = Thread(target=runInThread)
thread.start()
def replaceBody(self, widget): def replaceBody(self, widget):
old_widget,_ = self.frame.contents.pop() old_widget,_ = self.frame.contents.pop()
@@ -141,15 +187,31 @@ class CmdAsana:
self.clearSignals(old_widget) self.clearSignals(old_widget)
self.frame.contents.append((widget, self.frame.options())) self.frame.contents.append((widget, self.frame.options()))
self.frame.focus_position = 0 self.frame.focus_position = 0
if self.loop != None:
self.loop.draw_screen()
def showMainLoading(self):
text = urwid.Text(('loading', '[loading...]'))
self.replaceBody(urwid.Filler(text))
def showMyTasks(self, workspace_id): def showMyTasks(self, workspace_id):
self.state['view'] = 'atm' self.state['view'] = 'atm'
self.state['id'] = workspace_id self.state['id'] = workspace_id
self.state['workspace_id'] = workspace_id self.state['workspace_id'] = workspace_id
task_list = ui.TaskList(self.allMyTasks(workspace_id)) self.showMainLoading()
self.connectTaskListSignals(task_list)
self.replaceBody(task_list) def runInThread():
tasks = self.allMyTasks(workspace_id)
update(tasks)
def update(tasks):
task_list = ui.TaskList(tasks)
self.connectTaskListSignals(task_list)
self.replaceBody(task_list)
thread = Thread(target=runInThread)
thread.start()
def showProject(self, project_id): def showProject(self, project_id):
if project_id == None: if project_id == None:
@@ -157,60 +219,110 @@ class CmdAsana:
self.state['view'] = 'project' self.state['view'] = 'project'
self.state['id'] = project_id self.state['id'] = project_id
task_list = ui.TaskList(self.projectTasks(project_id)) self.showMainLoading()
self.connectTaskListSignals(task_list)
self.replaceBody(task_list) def runInThread():
tasks = self.projectTasks(project_id)
update(tasks)
def update(tasks):
task_list = ui.TaskList(tasks)
self.connectTaskListSignals(task_list)
self.replaceBody(task_list)
thread = Thread(target=runInThread)
thread.start()
def showProjectList(self, workspace_id): def showProjectList(self, workspace_id):
self.state['view'] = 'workspace' self.state['view'] = 'workspace'
self.state['id'] = workspace_id self.state['id'] = workspace_id
self.state['workspace_id'] = workspace_id self.state['workspace_id'] = workspace_id
self.workspace_id = workspace_id self.workspace_id = workspace_id
project_list = ui.ProjectList(self.allMyProjects())
urwid.connect_signal(project_list, 'loadproject', self.showProject) self.showMainLoading()
self.replaceBody(project_list)
def showDetails(self, task_id):
def runInThread():
projects = self.allMyProjects()
update(projects)
def update(projects):
project_list = ui.ProjectList(projects)
urwid.connect_signal(project_list, 'loadproject', self.showProject)
self.replaceBody(project_list)
thread = Thread(target=runInThread)
thread.start()
def loadProjectSearch(self): def loadProjectSearch(self):
#callback somehow? #callback somehow?
def showDetails(self, task_id):
def showDetails(self, task_id, show_loading=True):
self.state['view'] = 'details' self.state['view'] = 'details'
self.state['id'] = task_id self.state['id'] = task_id
task = self.client.tasks.find_by_id(task_id) if show_loading:
stories = self.client.stories.find_by_task(task_id) self.showMainLoading()
task_details = ui.TaskDetails(task, stories)
urwid.connect_signal(task_details, 'comment', self.addComment) def runInThread():
urwid.connect_signal(task_details, 'loadproject', self.showProject) task = self.client.tasks.find_by_id(task_id)
self.replaceBody(task_details) stories = self.client.stories.find_by_task(task_id)
update(task, stories)
def update(task, stories):
task_details = ui.TaskDetails(task, stories)
self.connectDetailsSignals(task_details)
self.replaceBody(task_details)
thread = Thread(target=runInThread)
thread.start()
def registerSignals(self): def registerSignals(self):
urwid.register_signal(ui.TaskList, [ urwid.register_signal(ui.TaskList, [
'complete', 'complete',
'newtask', 'newtask',
'updatetask', 'updatetask',
'details' 'details',
]) ])
urwid.register_signal(ui.TaskEdit, [ urwid.register_signal(ui.TaskEdit, [
'complete', 'complete',
'newtask', 'newtask',
'updatetask', 'updatetask',
'details' 'details',
])
urwid.register_signal(ui.TaskDetails, [
'comment',
'loadproject',
'updatedescription',
'updatetask',
'usertypeahead',
'assigntask',
])
urwid.register_signal(ui.AssigneeTypeAhead, [
'usertypeahead',
'assigntask',
]) ])
urwid.register_signal(ui.TaskDetails, ['comment', 'loadproject'])
urwid.register_signal(ui.CommentEdit, ['comment']) urwid.register_signal(ui.CommentEdit, ['comment'])
urwid.register_signal(ui.DescriptionEdit, ['updatedescription'])
urwid.register_signal(ui.TaskNameEdit, 'updatetask')
urwid.register_signal(ui.WorkspaceMenu, 'click') urwid.register_signal(ui.WorkspaceMenu, 'click')
urwid.register_signal(ui.ProjectList, 'loadproject') urwid.register_signal(ui.ProjectList, 'loadproject')
def clearSignals(self, widget): def clearSignals(self, widget):
urwid.disconnect_signal(widget, 'complete', self.completeTask) urwid.disconnect_signal(widget, 'complete', self.completeTask)
urwid.disconnect_signal(widget, 'newtask', self.newTask) urwid.disconnect_signal(widget, 'newtask', self.newTask)
urwid.disconnect_signal(widget, 'updatetask', self.updateTask) urwid.disconnect_signal(widget, 'updatetask', self.updateTask)
urwid.disconnect_signal(widget, 'details', self.showDetails) urwid.disconnect_signal(widget, 'details', self.showDetails)
urwid.disconnect_signal(widget, 'updatedescription',
self.updateDescription)
urwid.disconnect_signal(widget, 'updatetask', self.updateTask)
urwid.disconnect_signal(widget, 'usertypeahead', self.userTypeAhead)
def connectTaskListSignals(self, task_list): def connectTaskListSignals(self, task_list):
urwid.connect_signal(task_list, 'complete', self.completeTask) urwid.connect_signal(task_list, 'complete', self.completeTask)
@@ -218,6 +330,15 @@ class CmdAsana:
urwid.connect_signal(task_list, 'updatetask', self.updateTask) urwid.connect_signal(task_list, 'updatetask', self.updateTask)
urwid.connect_signal(task_list, 'details', self.showDetails) urwid.connect_signal(task_list, 'details', self.showDetails)
def connectDetailsSignals(self, task_details):
urwid.connect_signal(task_details, 'comment', self.addComment)
urwid.connect_signal(task_details, 'loadproject', self.showProject)
urwid.connect_signal(task_details, 'updatedescription',
self.updateDescription)
urwid.connect_signal(task_details, 'updatetask', self.updateTask)
urwid.connect_signal(task_details, 'usertypeahead', self.userTypeAhead)
urwid.connect_signal(task_details, 'assigntask', self.assignTask)
def handleInput(self, key): def handleInput(self, key):
if key in ('q', 'Q'): if key in ('q', 'Q'):
raise urwid.ExitMainLoop() raise urwid.ExitMainLoop()
@@ -244,11 +365,11 @@ class CmdAsana:
else: else:
raise KeyError raise KeyError
loop = urwid.MainLoop(self.frame, self.loop = urwid.MainLoop(self.frame,
unhandled_input=self.handleInput, unhandled_input=self.handleInput,
palette=ui.palette palette=ui.palette
) )
loop.run() self.loop.run()
def main(): def main():
cmdasana = CmdAsana() cmdasana = CmdAsana()

114
ui.py
View File

@@ -10,6 +10,7 @@ palette = [
('selected', 'standout', ''), ('selected', 'standout', ''),
('selected workspace', 'standout,bold', ''), ('selected workspace', 'standout,bold', ''),
('header', 'bold,light green', ''), ('header', 'bold,light green', ''),
('secondary', 'light gray', ''),
('task', 'light green', ''), ('task', 'light green', ''),
('section', 'white', 'dark green'), ('section', 'white', 'dark green'),
('workspace', 'white', 'dark blue'), ('workspace', 'white', 'dark blue'),
@@ -48,6 +49,11 @@ class PagerButton(urwid.Button):
super(PagerButton, self).__init__(('pager', 'load more')) super(PagerButton, self).__init__(('pager', 'load more'))
urwid.connect_signal(self, 'click', loadPage) urwid.connect_signal(self, 'click', loadPage)
class TypeAheadButton(urwid.Button):
def __init__(self, item, onClick):
super(TypeAheadButton, self).__init__(item['name'])
urwid.connect_signal(self, 'click', onClick, item)
class ProjectIcon(urwid.SelectableIcon): class ProjectIcon(urwid.SelectableIcon):
def __init__(self, project, onClick): def __init__(self, project, onClick):
self.project = project self.project = project
@@ -210,13 +216,71 @@ class TaskEdit(urwid.Edit):
class CommentEdit(urwid.Edit): class CommentEdit(urwid.Edit):
def __init__(self, task): def __init__(self, task):
self.task = task self.task = task
super(CommentEdit, self).__init__('Add a comment:\n') super(CommentEdit, self).__init__(('secondary', u'Add a comment:\n'))
def keypress(self, size, key): def keypress(self, size, key):
if key != 'enter': if key != 'enter':
return super(CommentEdit, self).keypress(size, key) return super(CommentEdit, self).keypress(size, key)
urwid.emit_signal(self, 'comment', self.task['id'], self.edit_text) urwid.emit_signal(self, 'comment', self.task['id'], self.edit_text)
class TaskNameEdit(urwid.Edit):
def __init__(self, task):
self.task = task
super(TaskNameEdit, self).__init__(('secondary',
u'#' + str(task['id']) + ' '),
task['name'])
def keypress(self, size, key):
if key in ('enter', 'esc', 'up', 'down'):
if (self.edit_text != self.task['name']):
urwid.emit_signal(self, 'updatetask', self.task['id'],
self.edit_text)
return super(TaskNameEdit, self).keypress(size, key)
class DescriptionEdit(urwid.Edit):
def __init__(self, task):
self.task = task
super(DescriptionEdit, self).__init__(('secondary', u'Description:\n'),
task['notes'],
multiline=True)
def keypress(self, size, key):
if key != 'esc':
return super(DescriptionEdit, self).keypress(size, key)
urwid.emit_signal(self, 'updatedescription', self.task['id'],
self.edit_text)
class AssigneeTypeAhead(urwid.Pile):
def __init__(self, task):
self.task = task
if task['assignee'] != None:
assignee = task['assignee']['name']
else:
assignee = ""
self.edit = urwid.Edit('Assignee: ', assignee)
urwid.connect_signal(self.edit, 'change', self.typeAhead)
body = [('pack', self.edit)]
super(AssigneeTypeAhead, self).__init__(body)
def typeAhead(self, widget, text):
urwid.emit_signal(self, 'usertypeahead', text, self.updateTypeAhead)
def updateTypeAhead(self, users):
users = [(TypeAheadButton(u, self.assign), ('pack', None)) for u in users]
users.insert(0, self.contents[0])
self.contents = users
def assign(self, widget, user):
urwid.emit_signal(self, 'assigntask', self.task['id'], user['id'])
self.contents = [self.contents[0]]
self.edit.set_edit_text(user['name'])
class TaskDetails(urwid.Pile): class TaskDetails(urwid.Pile):
def __init__(self, task, stories): def __init__(self, task, stories):
self.task = task self.task = task
@@ -225,23 +289,29 @@ class TaskDetails(urwid.Pile):
comment_edit = CommentEdit(task) comment_edit = CommentEdit(task)
urwid.connect_signal(comment_edit, 'comment', self.comment) urwid.connect_signal(comment_edit, 'comment', self.comment)
self.description_edit = DescriptionEdit(task)
urwid.connect_signal(self.description_edit, 'updatedescription',
self.updateDescription)
task_name_edit = TaskNameEdit(task)
urwid.connect_signal(task_name_edit, 'updatetask', self.updateTask)
assignee_type_ahead = AssigneeTypeAhead(task)
urwid.connect_signal(assignee_type_ahead, 'usertypeahead',
self.userTypeAhead)
urwid.connect_signal(assignee_type_ahead, 'assigntask', self.assignTask)
projects = [('pack', ProjectIcon(project, self.loadProject)) projects = [('pack', ProjectIcon(project, self.loadProject))
for project in task['projects']] for project in task['projects']]
if task['assignee']:
assignee = urwid.Text('Assigned to: ' + task['assignee']['name'])
else:
assignee = urwid.Text('(not assigned)')
body = projects + \ body = projects + \
[ [
('pack', urwid.Divider('=')), ('pack', urwid.Divider('=')),
('pack', urwid.Text(('header', task['name'] + \ ('pack', task_name_edit),
" #" + str(task['id'])))), ('pack', assignee_type_ahead),
('pack', assignee), ('pack', urwid.Divider('-')),
('pack', self.description_edit),
('pack', urwid.Divider('-')), ('pack', urwid.Divider('-')),
('pack', urwid.Text(task['notes'])),
] + \ ] + \
[('pack', urwid.Text('[' + story['created_by']['name'] + '] ' + \ [('pack', urwid.Text('[' + story['created_by']['name'] + '] ' + \
story['text'])) for story in stories] + \ story['text'])) for story in stories] + \
@@ -251,8 +321,30 @@ class TaskDetails(urwid.Pile):
super(TaskDetails, self).__init__(body) super(TaskDetails, self).__init__(body)
def keypress(self, size, key):
key = super(TaskDetails, self).keypress(size, key)
if self.focus != self.description_edit and \
self.description_edit.edit_text != self.task['notes']:
self.updateDescription(self.task['id'],
self.description_edit.edit_text)
return key
def comment(self, task_id, comment): def comment(self, task_id, comment):
urwid.emit_signal(self, 'comment', task_id, comment) urwid.emit_signal(self, 'comment', task_id, comment)
def updateDescription(self, task_id, description):
urwid.emit_signal(self, 'updatedescription', task_id, description)
def updateTask(self, task_id, name):
urwid.emit_signal(self, 'updatetask', task_id, name)
def loadProject(self, project_id): def loadProject(self, project_id):
urwid.emit_signal(self, 'loadproject', project_id) urwid.emit_signal(self, 'loadproject', project_id)
def userTypeAhead(self, text, callback):
urwid.emit_signal(self, 'usertypeahead', text, callback)
def assignTask(self, task_id, user_id):
urwid.emit_signal(self, 'assigntask', task_id, user_id)