Improve the task editing panes, swap the backend from an md file to a yaml file, add column editing functionality
This commit is contained in:
parent
b19fe10a0d
commit
779ddca11f
@ -1,18 +0,0 @@
|
|||||||
#
|
|
||||||
|
|
||||||
## To Do
|
|
||||||
|
|
||||||
|
|
||||||
- Update the task information
|
|
||||||
- Task 2
|
|
||||||
|
|
||||||
## Review
|
|
||||||
|
|
||||||
|
|
||||||
- Task 3
|
|
||||||
|
|
||||||
## Done
|
|
||||||
|
|
||||||
|
|
||||||
- Task 4
|
|
||||||
|
|
78
src/board.py
78
src/board.py
@ -1,5 +1,20 @@
|
|||||||
""" This module contains classes and functions to contain the kanban board information """
|
""" This module contains classes and functions to contain the kanban board information """
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
class Task:
|
||||||
|
""" This class represents each task,
|
||||||
|
"""
|
||||||
|
def __init__(self, summary, score, description):
|
||||||
|
""" Initialize the task class
|
||||||
|
"""
|
||||||
|
# Each task has the following properties
|
||||||
|
self.summary = summary # Summary of the task
|
||||||
|
self.score = score # Score for ticket
|
||||||
|
self.description = description # Description of ticket
|
||||||
|
|
||||||
|
|
||||||
class Board:
|
class Board:
|
||||||
def __init__(self, file = None):
|
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.
|
||||||
@ -13,59 +28,30 @@ class Board:
|
|||||||
self.columns = list()
|
self.columns = list()
|
||||||
self.tasks = list()
|
self.tasks = list()
|
||||||
|
|
||||||
self.file = ''
|
|
||||||
self.file = file
|
self.file = file
|
||||||
|
|
||||||
if file:
|
if file:
|
||||||
self.parse_md(file)
|
self.read_yaml(file)
|
||||||
|
|
||||||
|
|
||||||
def parse_md(self, file):
|
def read_yaml(self, file='.board.yaml'):
|
||||||
""" Upon starting the code we need to parse the markdown file which contains our board
|
""" Read the yaml file in and set up the data
|
||||||
information
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
file - the path to the markdown file containing the board information
|
file - yaml file to read in
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with open(file,'r') as f:
|
# Read in the data
|
||||||
for line in f:
|
with open(file, 'r') as f:
|
||||||
item_type = line.split(' ')[0]
|
data = yaml.safe_load(f)
|
||||||
# Assign sprint
|
|
||||||
if item_type == '#':
|
|
||||||
# If sprint has already been defined we should exit the loop
|
|
||||||
if self.sprint:
|
|
||||||
break
|
|
||||||
|
|
||||||
# Otherwise assign it
|
# Assign the data to board variables
|
||||||
try:
|
self.columns = data['columns']
|
||||||
self.sprint = ' '.join(line.split(' ')[1:])
|
self.tasks = [[] for col in self.columns]
|
||||||
|
for task in data['tasks']:
|
||||||
|
self.tasks[self.columns.index(task['column'])].append(
|
||||||
|
Task(task['summary'], task['score'], task['description']))
|
||||||
|
|
||||||
except IndexError:
|
|
||||||
# If a sprint title is not defined we default it to ' ' which we process
|
|
||||||
# later
|
|
||||||
self.sprint=' '
|
|
||||||
|
|
||||||
# Define a new column and add a list to the tasks variable that corresponds to that
|
|
||||||
# column
|
|
||||||
elif item_type == "##":
|
|
||||||
self.columns.append(' '.join(line.split(' ')[1:]))
|
|
||||||
self.tasks.append(list())
|
|
||||||
|
|
||||||
# Now add the task to the list structures
|
|
||||||
elif item_type == "-":
|
|
||||||
# The tasks are a list of [col index, task name]
|
|
||||||
self.tasks[-1].append(' '.join(line.split(' ')[1:]).strip())
|
|
||||||
|
|
||||||
|
|
||||||
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))
|
|
||||||
f.write('\n')
|
|
||||||
|
|
||||||
|
|
||||||
def move_task(self, col_index, task_index, direction):
|
def move_task(self, col_index, task_index, direction):
|
||||||
@ -108,11 +94,11 @@ class Board:
|
|||||||
""" Return a task based on column and task index"""
|
""" Return a task based on column and task index"""
|
||||||
return self.tasks[icol][itask]
|
return self.tasks[icol][itask]
|
||||||
|
|
||||||
def update_task( self, icol, itask, text):
|
def update_task( self, icol, itask, task):
|
||||||
""" Update the task based on text """
|
""" Update the task based on text """
|
||||||
self.tasks[icol][itask] = text
|
self.tasks[icol][itask] = task
|
||||||
|
|
||||||
def add_task( self, icol, text):
|
def add_task( self, icol, task):
|
||||||
"""Add a task to icol"""
|
"""Add a task to icol"""
|
||||||
self.tasks[icol].append(text)
|
self.tasks[icol].append(task)
|
||||||
|
|
||||||
|
@ -6,13 +6,19 @@ Screen {
|
|||||||
background: $bg;
|
background: $bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
EditScreen {
|
EditTaskScreen {
|
||||||
align: center middle;
|
align: center middle;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
layout: vertical;
|
layout: vertical;
|
||||||
background: #000000 25%;
|
background: #000000 25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EditColScreen {
|
||||||
|
align: center middle;
|
||||||
|
overflow-x: hidden;
|
||||||
|
layout: vertical;
|
||||||
|
background: #000000 25%;
|
||||||
|
}
|
||||||
.column {
|
.column {
|
||||||
width: 1fr;
|
width: 1fr;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
118
src/tui.py
118
src/tui.py
@ -3,7 +3,7 @@ from textual.widgets import Static, Label, ListItem, ListView, TextArea, Input
|
|||||||
from textual.containers import Horizontal, Vertical
|
from textual.containers import Horizontal, Vertical
|
||||||
from textual.screen import Screen
|
from textual.screen import Screen
|
||||||
from textual.binding import Binding
|
from textual.binding import Binding
|
||||||
from board import Board
|
from board import Board, Task
|
||||||
|
|
||||||
|
|
||||||
class TaskList(ListView):
|
class TaskList(ListView):
|
||||||
@ -16,20 +16,38 @@ class TaskList(ListView):
|
|||||||
Binding("j", "cursor_down", "Cursor Down", show=False, priority=True),
|
Binding("j", "cursor_down", "Cursor Down", show=False, priority=True),
|
||||||
]
|
]
|
||||||
|
|
||||||
class EditScreen(Screen):
|
class EditTaskScreen(Screen):
|
||||||
"""
|
"""
|
||||||
This is a screen used to edit the name of a task
|
This is a screen used to edit the name of a task
|
||||||
"""
|
"""
|
||||||
CSS="""
|
CSS="""
|
||||||
Label{
|
Label{
|
||||||
width:50%;
|
width:50%;
|
||||||
|
background: #282828;
|
||||||
|
padding: 1;
|
||||||
}
|
}
|
||||||
Input{
|
Input{
|
||||||
width:50%;
|
width:50%;
|
||||||
|
background: #282828;
|
||||||
|
padding: 0 0;
|
||||||
|
border: #ebdbb9;
|
||||||
|
}
|
||||||
|
|
||||||
|
Input:focus{
|
||||||
|
border: #458588;
|
||||||
|
}
|
||||||
|
TextArea{
|
||||||
|
width: 50%;
|
||||||
|
height: 25%;
|
||||||
|
background: #282828;
|
||||||
|
border: #ebdbb9;
|
||||||
|
}
|
||||||
|
TextArea:focus{
|
||||||
|
border: #458588;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
BINDINGS = [
|
BINDINGS = [
|
||||||
Binding('enter', 'save', 'Save Changes', priority=True)
|
Binding('ctrl+s', 'save', 'Save Changes', priority=True),
|
||||||
]
|
]
|
||||||
def __init__(self,text):
|
def __init__(self,text):
|
||||||
"""
|
"""
|
||||||
@ -43,6 +61,60 @@ class EditScreen(Screen):
|
|||||||
Compose the widgets on the screen, this screen doesn't need dynamic layout changes
|
Compose the widgets on the screen, this screen doesn't need dynamic layout changes
|
||||||
"""
|
"""
|
||||||
yield Label('Task Name:')
|
yield Label('Task Name:')
|
||||||
|
yield Input(value=self.text.summary)
|
||||||
|
yield Label('Score:')
|
||||||
|
if self.text.score:
|
||||||
|
yield Input(value=self.text.score)
|
||||||
|
else:
|
||||||
|
yield Input(value="")
|
||||||
|
yield Label('Description:')
|
||||||
|
if self.text.description:
|
||||||
|
yield TextArea(self.text.description, language='markdown')
|
||||||
|
else:
|
||||||
|
yield TextArea(language='markdown')
|
||||||
|
|
||||||
|
|
||||||
|
def action_save(self):
|
||||||
|
query = self.query(selector=Input)
|
||||||
|
self.text.summary = query.nodes[0].value
|
||||||
|
self.text.score = query.nodes[1].value
|
||||||
|
query = self.query(selector=TextArea)
|
||||||
|
self.text.description = query.nodes[0].text
|
||||||
|
self.dismiss(self.text)
|
||||||
|
|
||||||
|
class EditColScreen(Screen):
|
||||||
|
"""
|
||||||
|
This is a screen used to edit the name of a task
|
||||||
|
"""
|
||||||
|
CSS="""
|
||||||
|
Label{
|
||||||
|
width:50%;
|
||||||
|
background: #282828;
|
||||||
|
padding: 1;
|
||||||
|
}
|
||||||
|
Input{
|
||||||
|
width:50%;
|
||||||
|
background: #282828;
|
||||||
|
padding: 0 0;
|
||||||
|
border: #ebdbb9;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
BINDINGS = [
|
||||||
|
Binding('ctrl+s', 'save', 'Save Changes', priority=True),
|
||||||
|
Binding('enter', 'save', 'Save Changes', priority=True),
|
||||||
|
]
|
||||||
|
def __init__(self,text):
|
||||||
|
"""
|
||||||
|
Initialize the screen
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
def compose(self):
|
||||||
|
"""
|
||||||
|
Compose the widgets on the screen, this screen doesn't need dynamic layout changes
|
||||||
|
"""
|
||||||
|
yield Label('Column Name:')
|
||||||
yield Input(value=self.text)
|
yield Input(value=self.text)
|
||||||
|
|
||||||
|
|
||||||
@ -60,6 +132,7 @@ class KanbanForm(App):
|
|||||||
Binding("L", "move_up", "Focus Next", show=False),
|
Binding("L", "move_up", "Focus Next", show=False),
|
||||||
Binding("H", "move_down", "Focus Prev", show=False),
|
Binding("H", "move_down", "Focus Prev", show=False),
|
||||||
Binding("e", "edit_task", "Edit Task", show=False,),
|
Binding("e", "edit_task", "Edit Task", show=False,),
|
||||||
|
Binding("r", "edit_column", "Edit Column Name", show=False,),
|
||||||
Binding('q', 'exit', "Exit")
|
Binding('q', 'exit', "Exit")
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -68,7 +141,7 @@ class KanbanForm(App):
|
|||||||
Initialization function for form
|
Initialization function for form
|
||||||
"""
|
"""
|
||||||
# Initialize our board class
|
# Initialize our board class
|
||||||
self.board = Board(file = '.board.md')
|
self.board = Board(file = '.board.yaml')
|
||||||
self.cols = list()
|
self.cols = list()
|
||||||
|
|
||||||
self.col_widgets = list()
|
self.col_widgets = list()
|
||||||
@ -83,7 +156,7 @@ class KanbanForm(App):
|
|||||||
with Vertical(classes=col_class):
|
with Vertical(classes=col_class):
|
||||||
yield Static(col, classes='header')
|
yield Static(col, classes='header')
|
||||||
yield TaskList(
|
yield TaskList(
|
||||||
*[ListItem(Label(task)) for task in self.board.get_tasks()[i]])
|
*[ListItem(Label(task.summary)) for task in self.board.get_tasks()[i]])
|
||||||
|
|
||||||
# Now make all TaskLists except the first have no highlights
|
# Now make all TaskLists except the first have no highlights
|
||||||
def action_fnext(self):
|
def action_fnext(self):
|
||||||
@ -92,7 +165,7 @@ class KanbanForm(App):
|
|||||||
|
|
||||||
def action_move_up(self):
|
def action_move_up(self):
|
||||||
icol, itask = self.get_col_task()
|
icol, itask = self.get_col_task()
|
||||||
text = self.board.get_task(icol, itask)
|
text = self.board.get_task(icol, itask).summary
|
||||||
moved = self.board.move_task(icol, itask, 1)
|
moved = self.board.move_task(icol, itask, 1)
|
||||||
if moved:
|
if moved:
|
||||||
query = self.query(selector=TaskList)
|
query = self.query(selector=TaskList)
|
||||||
@ -109,7 +182,7 @@ class KanbanForm(App):
|
|||||||
|
|
||||||
def action_move_down(self):
|
def action_move_down(self):
|
||||||
icol, itask = self.get_col_task()
|
icol, itask = self.get_col_task()
|
||||||
text = self.board.get_task(icol, itask)
|
text = self.board.get_task(icol, itask).summary
|
||||||
moved = self.board.move_task(icol, itask, -1)
|
moved = self.board.move_task(icol, itask, -1)
|
||||||
if moved:
|
if moved:
|
||||||
query = self.query(selector=TaskList)
|
query = self.query(selector=TaskList)
|
||||||
@ -122,10 +195,25 @@ class KanbanForm(App):
|
|||||||
def action_edit_task(self):
|
def action_edit_task(self):
|
||||||
icol, itask = self.get_col_task()
|
icol, itask = self.get_col_task()
|
||||||
task = self.board.get_task(icol, itask)
|
task = self.board.get_task(icol, itask)
|
||||||
self.push_screen(EditScreen(task), self.update_task)
|
self.push_screen(EditTaskScreen(task), self.update_task)
|
||||||
|
|
||||||
def action_new_task(self):
|
def action_new_task(self):
|
||||||
self.push_screen(EditScreen(""), self.new_task)
|
self.push_screen(EditTaskScreen(Task(None,None,None)), self.new_task)
|
||||||
|
|
||||||
|
def action_edit_column(self):
|
||||||
|
icol, itask = self.get_col_task()
|
||||||
|
text = self.board.get_columns()[icol]
|
||||||
|
self.push_screen(EditColScreen(text), self.update_col)
|
||||||
|
|
||||||
|
def update_col(self, text):
|
||||||
|
""" Update the column
|
||||||
|
"""
|
||||||
|
icol, itask = self.get_col_task()
|
||||||
|
query = self.query(selector=Static)
|
||||||
|
query = [node for node in query.nodes if str(node) == 'Static()']
|
||||||
|
query[icol].update(text)
|
||||||
|
self.board.get_columns()[icol] = text
|
||||||
|
|
||||||
|
|
||||||
def action_exit(self):
|
def action_exit(self):
|
||||||
""" Exit the application """
|
""" Exit the application """
|
||||||
@ -153,21 +241,21 @@ class KanbanForm(App):
|
|||||||
|
|
||||||
return col_index, task_index
|
return col_index, task_index
|
||||||
|
|
||||||
def update_task(self, text):
|
def update_task(self, task):
|
||||||
""" This function gets the text inputted in the edit screen and updates the underlying
|
""" This function gets the text inputted in the edit screen and updates the underlying
|
||||||
task and the board class
|
task and the board class
|
||||||
|
|
||||||
"""
|
"""
|
||||||
icol, itask = self.get_col_task()
|
icol, itask = self.get_col_task()
|
||||||
self.focused.highlighted_child.children[0].update(text)
|
self.focused.highlighted_child.children[0].update(task.summary)
|
||||||
self.board.update_task(icol, itask, text)
|
self.board.update_task(icol, itask, task)
|
||||||
|
|
||||||
def new_task(self, text):
|
def new_task(self, task):
|
||||||
""" This function adds a new task to our board
|
""" This function adds a new task to our board
|
||||||
"""
|
"""
|
||||||
icol,_ = self.get_col_task()
|
icol,_ = self.get_col_task()
|
||||||
self.focused.mount(ListItem(Label(text)))
|
self.focused.mount(ListItem(Label(task.summary)))
|
||||||
self.board.add_task(icol, text)
|
self.board.add_task(icol, task)
|
||||||
self.focused.highlighted_child
|
self.focused.highlighted_child
|
||||||
|
|
||||||
# def on_key(self):
|
# def on_key(self):
|
||||||
|
Reference in New Issue
Block a user