Improve the task editing panes, swap the backend from an md file to a yaml file, add column editing functionality

feature/make_board_screen
Alex Selimov 1 year ago
parent b19fe10a0d
commit 779ddca11f

@ -1,18 +0,0 @@
#
## To Do
- Update the task information
- Task 2
## Review
-  Task 3
## Done
- Task 4

@ -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 == '#': # Assign the data to board variables
# If sprint has already been defined we should exit the loop self.columns = data['columns']
if self.sprint: self.tasks = [[] for col in self.columns]
break for task in data['tasks']:
self.tasks[self.columns.index(task['column'])].append(
# Otherwise assign it Task(task['summary'], task['score'], task['description']))
try:
self.sprint = ' '.join(line.split(' ')[1:])
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%;

@ -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):