Initial version of pykanban allowing for tasks to be read in frm a .md file and for the tasks to be moved between columns
This commit is contained in:
parent
daa59af825
commit
9c2449f4b7
61
src/board.py
61
src/board.py
@ -1,18 +1,24 @@
|
|||||||
""" This module contains classes and functions to contain the kanban board information """
|
""" This module contains classes and functions to contain the kanban board information """
|
||||||
|
|
||||||
class Board:
|
class Board:
|
||||||
def __init__(self):
|
def __init__(self, file = None):
|
||||||
""" Initialize the Board class, this class has three important class variables.
|
""" Initialize the Board class, this class has three important class variables.
|
||||||
These are:
|
These are:
|
||||||
self.sprint | str - name of the current sprint
|
self.sprint | str - name of the current sprint
|
||||||
self.columns | list(str) - columns in kanban board
|
self.columns | list(str) - columns in kanban board
|
||||||
self.tasks | list(list(str)) - tasks in each column
|
self.tasks | list(list()) - tasks in each column
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.sprint = None
|
self.sprint = None
|
||||||
self.columns = list()
|
self.columns = list()
|
||||||
self.tasks = list()
|
self.tasks = list()
|
||||||
|
|
||||||
|
self.file = ''
|
||||||
|
self.file = file
|
||||||
|
|
||||||
|
if file:
|
||||||
|
self.parse_md(file)
|
||||||
|
|
||||||
|
|
||||||
def parse_md(self, file):
|
def parse_md(self, file):
|
||||||
""" Upon starting the code we need to parse the markdown file which contains our board
|
""" Upon starting the code we need to parse the markdown file which contains our board
|
||||||
@ -48,11 +54,56 @@ class Board:
|
|||||||
|
|
||||||
# Now add the task to the list structures
|
# Now add the task to the list structures
|
||||||
elif item_type == "-":
|
elif item_type == "-":
|
||||||
self.tasks[-1].append(' '.join(line.split(' ')[1:]))
|
# The tasks are a list of [col index, task name]
|
||||||
|
self.tasks[-1].append(" "+' '.join(line.split(' ')[1:]))
|
||||||
|
|
||||||
|
|
||||||
|
def write_md(self):
|
||||||
|
with open(self.file, 'w') as f:
|
||||||
|
f.write('#\n\n')
|
||||||
|
for i,col in enumerate(self.columns):
|
||||||
|
f.write('## {}\n\n'.format(col))
|
||||||
|
for task in self.tasks[i]:
|
||||||
|
f.write('- {}\n'.format(task[1][1:]))
|
||||||
|
f.write('\n')
|
||||||
|
|
||||||
|
|
||||||
|
def move_task(self, col_index, task_index, direction):
|
||||||
|
""" This class method moves tasks between columns by incrementing/decrementing the column
|
||||||
|
index
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
col_index - index of the column we are in
|
||||||
|
task_index - index of the task we are changing in the column
|
||||||
|
direction - direction to move the task
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
moved - True if a task was moved else false
|
||||||
|
"""
|
||||||
|
task = self.tasks[col_index][task_index]
|
||||||
|
if col_index+direction >= 0 and col_index+direction < len(self.columns):
|
||||||
|
self.tasks[col_index+direction].append(task)
|
||||||
|
del self.tasks[col_index][task_index]
|
||||||
|
return True
|
||||||
|
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def print_board_items(self):
|
def print_board_items(self):
|
||||||
for col, tasks in zip(self.columns, self.tasks):
|
for i, col in enumerate(self.columns):
|
||||||
print(col, tasks)
|
print(col)
|
||||||
|
print(self.tasks[i])
|
||||||
|
|
||||||
|
def get_columns(self):
|
||||||
|
""" Return columns"""
|
||||||
|
return self.columns
|
||||||
|
|
||||||
|
def get_tasks(self):
|
||||||
|
""" Return tasks"""
|
||||||
|
return self.tasks
|
||||||
|
|
||||||
|
def get_task(self, icol, itask):
|
||||||
|
""" Return a task based on column and task index"""
|
||||||
|
return self.tasks[icol][itask]
|
||||||
|
24
src/layout.tcss
Normal file
24
src/layout.tcss
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
Screen {
|
||||||
|
layout: horizontal;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
width: 1fr;
|
||||||
|
height: 100%;
|
||||||
|
padding: 2 2 2 2;
|
||||||
|
border-right: dashed #458588;
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-column{
|
||||||
|
width: 1fr;
|
||||||
|
height: 100%;
|
||||||
|
padding: 2 2 2 2;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
content-align: center top;
|
||||||
|
color: #458588;
|
||||||
|
text-style: bold underline;
|
||||||
|
}
|
119
src/tui.py
Normal file
119
src/tui.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.widgets import Static, Label, ListItem, ListView
|
||||||
|
from textual.containers import Horizontal, Vertical
|
||||||
|
from textual.binding import Binding
|
||||||
|
from board import Board
|
||||||
|
|
||||||
|
|
||||||
|
class TaskList(ListView):
|
||||||
|
"""
|
||||||
|
Inherited widget from Listview to use as the kanban board columns
|
||||||
|
"""
|
||||||
|
# Keybinds
|
||||||
|
BINDINGS = [
|
||||||
|
Binding("k", "cursor_up", "Cursor Up", show=False, priority=True),
|
||||||
|
Binding("j", "cursor_down", "Cursor Down", show=False, priority=True),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class KanbanForm(App):
|
||||||
|
CSS_PATH = 'layout.tcss'
|
||||||
|
BINDINGS = [
|
||||||
|
Binding("l", "fnext", "Focus Next", show=False, priority=True),
|
||||||
|
Binding("h", "fprev", "Focus Prev", show=False, priority=True),
|
||||||
|
Binding("L", "move_up", "Focus Next", show=False, priority=True),
|
||||||
|
Binding("H", "move_down", "Focus Prev", show=False, priority=True),
|
||||||
|
Binding('q', 'exit', "Exit", priority=True, show=False)
|
||||||
|
]
|
||||||
|
|
||||||
|
def compose(self):
|
||||||
|
"""
|
||||||
|
Initialization function for form
|
||||||
|
"""
|
||||||
|
# Initialize our board class
|
||||||
|
self.board = Board(file = '.board.md')
|
||||||
|
self.cols = list()
|
||||||
|
|
||||||
|
self.col_widgets = list()
|
||||||
|
|
||||||
|
|
||||||
|
with Horizontal():
|
||||||
|
for i,col in enumerate(self.board.get_columns()):
|
||||||
|
if i < len(self.board.get_columns())-1:
|
||||||
|
col_class = 'column'
|
||||||
|
else:
|
||||||
|
col_class = 'last-column'
|
||||||
|
with Vertical(classes=col_class):
|
||||||
|
yield Static(col, classes='header')
|
||||||
|
yield TaskList(
|
||||||
|
*[ListItem(Label(task)) for task in self.board.get_tasks()[i]])
|
||||||
|
|
||||||
|
# Now make all TaskLists except the first have no highlights
|
||||||
|
def action_fnext(self):
|
||||||
|
""" Focus next column"""
|
||||||
|
self.children[0].focus_next()
|
||||||
|
|
||||||
|
def action_move_up(self):
|
||||||
|
icol, itask = self.get_col_task()
|
||||||
|
text = self.board.get_task(icol, itask)
|
||||||
|
moved = self.board.move_task(icol, itask, 1)
|
||||||
|
if moved:
|
||||||
|
query = self.query(selector=TaskList)
|
||||||
|
self.focused.highlighted_child.remove()
|
||||||
|
query.nodes[icol+1].append(ListItem(Label(text)))
|
||||||
|
self.focused.action_cursor_down()
|
||||||
|
self.action_fnext()
|
||||||
|
self.focused.action_cursor_down()
|
||||||
|
|
||||||
|
|
||||||
|
def action_fprev(self):
|
||||||
|
""" Focus previous column """
|
||||||
|
self.children[0].focus_previous()
|
||||||
|
|
||||||
|
def action_move_down(self):
|
||||||
|
icol, itask = self.get_col_task()
|
||||||
|
text = self.board.get_task(icol, itask)
|
||||||
|
moved = self.board.move_task(icol, itask, -1)
|
||||||
|
if moved:
|
||||||
|
query = self.query(selector=TaskList)
|
||||||
|
self.focused.highlighted_child.remove()
|
||||||
|
query.nodes[icol-1].append(ListItem(Label(text)))
|
||||||
|
self.focused.action_cursor_down()
|
||||||
|
self.action_fprev()
|
||||||
|
self.focused.action_cursor_down()
|
||||||
|
|
||||||
|
def action_exit(self):
|
||||||
|
""" Exit the application """
|
||||||
|
self.exit()
|
||||||
|
|
||||||
|
def get_col_task(self):
|
||||||
|
"""
|
||||||
|
This function gets the relevant column and task from the Board object for the current
|
||||||
|
selected item in the tui.
|
||||||
|
"""
|
||||||
|
focused_col = self.focused
|
||||||
|
query = self.query(selector=TaskList)
|
||||||
|
|
||||||
|
# First get the column index
|
||||||
|
for i, child in enumerate(query.nodes):
|
||||||
|
if focused_col == child:
|
||||||
|
col_index = i
|
||||||
|
|
||||||
|
# Now get the indext of the item in the list
|
||||||
|
to_move = focused_col.highlighted_child
|
||||||
|
for i, child in enumerate(focused_col.children):
|
||||||
|
if to_move == child:
|
||||||
|
task_index = i
|
||||||
|
|
||||||
|
return col_index, task_index
|
||||||
|
|
||||||
|
|
||||||
|
# def on_key(self):
|
||||||
|
# with open('log','a') as f:
|
||||||
|
# f.write("{}".format(self.children[0].focus_next))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
kb = KanbanForm()
|
||||||
|
kb.run()
|
||||||
|
|
||||||
|
|
Reference in New Issue
Block a user