|
|
@ -3,23 +3,26 @@ 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, Task
|
|
|
|
from .board import Board, Task, BoardList
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TaskList(ListView):
|
|
|
|
class TaskList(ListView):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Inherited widget from Listview to use as the kanban board columns
|
|
|
|
Inherited widget from Listview to use as the kanban board columns
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
# Keybinds
|
|
|
|
# Keybinds
|
|
|
|
BINDINGS = [
|
|
|
|
BINDINGS = [
|
|
|
|
Binding("k", "cursor_up", "Cursor Up", show=False, priority=True),
|
|
|
|
Binding("k", "cursor_up", "Cursor Up", show=False, priority=True),
|
|
|
|
Binding("j", "cursor_down", "Cursor Down", show=False, priority=True),
|
|
|
|
Binding("j", "cursor_down", "Cursor Down", show=False, priority=True),
|
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class EditTaskScreen(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%;
|
|
|
@ -47,9 +50,10 @@ class EditTaskScreen(Screen):
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
BINDINGS = [
|
|
|
|
BINDINGS = [
|
|
|
|
Binding('ctrl+s', 'save', 'Save Changes', priority=True),
|
|
|
|
Binding("ctrl+s", "save", "Save Changes", priority=True),
|
|
|
|
Binding('escape', 'exit', 'Exit Without Changes', priority=True),
|
|
|
|
Binding("escape", "exit", "Exit Without Changes", priority=True),
|
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, text):
|
|
|
|
def __init__(self, text):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Initialize the screen
|
|
|
|
Initialize the screen
|
|
|
@ -61,19 +65,18 @@ class EditTaskScreen(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 Input(value=self.text.summary)
|
|
|
|
yield Label('Score:')
|
|
|
|
yield Label("Score:")
|
|
|
|
if self.text.score:
|
|
|
|
if self.text.score:
|
|
|
|
yield Input(value=self.text.score)
|
|
|
|
yield Input(value=self.text.score)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
yield Input(value="")
|
|
|
|
yield Input(value="")
|
|
|
|
yield Label('Description:')
|
|
|
|
yield Label("Description:")
|
|
|
|
if self.text.description:
|
|
|
|
if self.text.description:
|
|
|
|
yield TextArea(self.text.description, language='markdown')
|
|
|
|
yield TextArea(self.text.description, language="markdown")
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
yield TextArea(language='markdown')
|
|
|
|
yield TextArea(language="markdown")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def action_save(self):
|
|
|
|
def action_save(self):
|
|
|
|
query = self.query(selector=Input)
|
|
|
|
query = self.query(selector=Input)
|
|
|
@ -86,10 +89,12 @@ class EditTaskScreen(Screen):
|
|
|
|
def action_exit(self):
|
|
|
|
def action_exit(self):
|
|
|
|
self.dismiss(None)
|
|
|
|
self.dismiss(None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class EditColScreen(Screen):
|
|
|
|
class EditColScreen(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%;
|
|
|
@ -104,9 +109,10 @@ class EditColScreen(Screen):
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
BINDINGS = [
|
|
|
|
BINDINGS = [
|
|
|
|
Binding('ctrl+s', 'save', 'Save Changes', priority=True),
|
|
|
|
Binding("ctrl+s", "save", "Save Changes", priority=True),
|
|
|
|
Binding('enter', 'save', 'Save Changes', priority=True),
|
|
|
|
Binding("enter", "save", "Save Changes", priority=True),
|
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, text):
|
|
|
|
def __init__(self, text):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Initialize the screen
|
|
|
|
Initialize the screen
|
|
|
@ -118,58 +124,159 @@ class EditColScreen(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('Column Name:')
|
|
|
|
yield Label("Column Name:")
|
|
|
|
yield Input(value=self.text)
|
|
|
|
yield Input(value=self.text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def action_save(self):
|
|
|
|
def action_save(self):
|
|
|
|
query = self.query(selector=Input)
|
|
|
|
query = self.query(selector=Input)
|
|
|
|
self.dismiss(query.nodes[0].value)
|
|
|
|
self.dismiss(query.nodes[0].value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class KanbanForm(App):
|
|
|
|
class SelectBoardScreen(Screen):
|
|
|
|
CSS_PATH = 'layout.tcss'
|
|
|
|
"""This is a screen used to select a board"""
|
|
|
|
|
|
|
|
|
|
|
|
BINDINGS = [
|
|
|
|
BINDINGS = [
|
|
|
|
Binding("a", "new_task", "Add New Task", show=False, ),
|
|
|
|
Binding("enter", "pick_option", "Save Changes", priority=True),
|
|
|
|
Binding("l", "fnext", "Focus Next", show=False, ),
|
|
|
|
Binding("q", "exit", "Exit"),
|
|
|
|
Binding("h", "fprev", "Focus Prev", show=False, ),
|
|
|
|
]
|
|
|
|
|
|
|
|
CSS = """
|
|
|
|
|
|
|
|
$bg: #282828;
|
|
|
|
|
|
|
|
Label{
|
|
|
|
|
|
|
|
width:50%;
|
|
|
|
|
|
|
|
background: #282828;
|
|
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
TaskList{
|
|
|
|
|
|
|
|
width:50%;
|
|
|
|
|
|
|
|
background: #282828;
|
|
|
|
|
|
|
|
padding: 0 0;
|
|
|
|
|
|
|
|
border: #ebdbb9;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ListView{
|
|
|
|
|
|
|
|
width:50%;
|
|
|
|
|
|
|
|
background: #282828;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ListItem{
|
|
|
|
|
|
|
|
border: solid #ebdbb2 100%;
|
|
|
|
|
|
|
|
background: $bg;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ListView > ListItem.--highlight {
|
|
|
|
|
|
|
|
background: $bg;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ListView:focus > ListItem.--highlight {
|
|
|
|
|
|
|
|
background: #458588;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Label:focus{
|
|
|
|
|
|
|
|
background: #458588;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, logger):
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
Initialize the screen
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
self.board_list = BoardList()
|
|
|
|
|
|
|
|
self.logger = logger
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def compose(self):
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
Compose the widgets on the screen, this screen doesn't need dynamic layout changes
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
yield Label("Select a board:")
|
|
|
|
|
|
|
|
yield TaskList(
|
|
|
|
|
|
|
|
*[ListItem(Label(board)) for board in self.board_list.get_boards()],
|
|
|
|
|
|
|
|
ListItem(Label("Add a new board")),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def action_pick_option(self):
|
|
|
|
|
|
|
|
"""Pick a board from the ListItem"""
|
|
|
|
|
|
|
|
self.focused.highlighted_child
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MainBoardScreen(Screen):
|
|
|
|
|
|
|
|
CSS_PATH = "layout.tcss"
|
|
|
|
|
|
|
|
BINDINGS = [
|
|
|
|
|
|
|
|
Binding(
|
|
|
|
|
|
|
|
"a",
|
|
|
|
|
|
|
|
"new_task",
|
|
|
|
|
|
|
|
"Add New Task",
|
|
|
|
|
|
|
|
show=False,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
Binding(
|
|
|
|
|
|
|
|
"l",
|
|
|
|
|
|
|
|
"fnext",
|
|
|
|
|
|
|
|
"Focus Next",
|
|
|
|
|
|
|
|
show=False,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
Binding(
|
|
|
|
|
|
|
|
"h",
|
|
|
|
|
|
|
|
"fprev",
|
|
|
|
|
|
|
|
"Focus Prev",
|
|
|
|
|
|
|
|
show=False,
|
|
|
|
|
|
|
|
),
|
|
|
|
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(
|
|
|
|
Binding("r", "edit_column", "Edit Column Name", show=False,),
|
|
|
|
"e",
|
|
|
|
Binding("d", "delete_task", "Delete Task", show=False,),
|
|
|
|
"edit_task",
|
|
|
|
Binding('q', 'exit', "Exit")
|
|
|
|
"Edit Task",
|
|
|
|
|
|
|
|
show=False,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
Binding(
|
|
|
|
|
|
|
|
"r",
|
|
|
|
|
|
|
|
"edit_column",
|
|
|
|
|
|
|
|
"Edit Column Name",
|
|
|
|
|
|
|
|
show=False,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
Binding(
|
|
|
|
|
|
|
|
"d",
|
|
|
|
|
|
|
|
"delete_task",
|
|
|
|
|
|
|
|
"Delete Task",
|
|
|
|
|
|
|
|
show=False,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
Binding("q", "exit", "Exit"),
|
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
|
|
"""Initialize the Kanban Form App"""
|
|
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
|
|
|
|
def compose(self):
|
|
|
|
def compose(self):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Initialization function for form
|
|
|
|
Initialization function for form
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
# Initialize our board class
|
|
|
|
# Initialize our board class
|
|
|
|
self.board = Board(file = '.board.yaml')
|
|
|
|
self.board = Board(file=".board.yaml")
|
|
|
|
self.cols = list()
|
|
|
|
self.cols = list()
|
|
|
|
|
|
|
|
|
|
|
|
self.col_widgets = list()
|
|
|
|
self.col_widgets = list()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with Horizontal():
|
|
|
|
with Horizontal():
|
|
|
|
for i, col in enumerate(self.board.get_columns()):
|
|
|
|
for i, col in enumerate(self.board.get_columns()):
|
|
|
|
if i < len(self.board.get_columns()) - 1:
|
|
|
|
if i < len(self.board.get_columns()) - 1:
|
|
|
|
col_class = 'column'
|
|
|
|
col_class = "column"
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
col_class = 'last-column'
|
|
|
|
col_class = "last-column"
|
|
|
|
with Vertical(classes=col_class):
|
|
|
|
with Vertical(classes=col_class):
|
|
|
|
if i == 0:
|
|
|
|
if i == 0:
|
|
|
|
yield Static(col, classes='header-focused')
|
|
|
|
yield Static(col, classes="header-focused")
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
yield Static(col, classes='header')
|
|
|
|
yield Static(col, classes="header")
|
|
|
|
yield TaskList(
|
|
|
|
yield TaskList(
|
|
|
|
*[ListItem(Label(task.summary)) for task in self.board.get_tasks()[i]])
|
|
|
|
*[
|
|
|
|
|
|
|
|
ListItem(Label(task.summary))
|
|
|
|
|
|
|
|
for task in self.board.get_tasks()[i]
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def action_fnext(self):
|
|
|
|
def action_fnext(self):
|
|
|
|
"""Focus next column"""
|
|
|
|
"""Focus next column"""
|
|
|
|
query = self.query(selector=Static)
|
|
|
|
query = self.query(selector=Static)
|
|
|
|
query = [node for node in query.nodes if str(node) == 'Static()']
|
|
|
|
query = [node for node in query.nodes if str(node) == "Static()"]
|
|
|
|
icol, _ = self.get_col_task()
|
|
|
|
icol, _ = self.get_col_task()
|
|
|
|
query[icol].classes = "header"
|
|
|
|
query[icol].classes = "header"
|
|
|
|
self.children[0].focus_next()
|
|
|
|
self.children[0].focus_next()
|
|
|
@ -178,8 +285,6 @@ class KanbanForm(App):
|
|
|
|
except IndexError:
|
|
|
|
except IndexError:
|
|
|
|
query[0].classes = "header-focused"
|
|
|
|
query[0].classes = "header-focused"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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).summary
|
|
|
|
text = self.board.get_task(icol, itask).summary
|
|
|
@ -192,11 +297,10 @@ class KanbanForm(App):
|
|
|
|
self.action_fnext()
|
|
|
|
self.action_fnext()
|
|
|
|
self.focused.action_cursor_down()
|
|
|
|
self.focused.action_cursor_down()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def action_fprev(self):
|
|
|
|
def action_fprev(self):
|
|
|
|
"""Focus previous column"""
|
|
|
|
"""Focus previous column"""
|
|
|
|
query = self.query(selector=Static)
|
|
|
|
query = self.query(selector=Static)
|
|
|
|
query = [node for node in query.nodes if str(node) == 'Static()']
|
|
|
|
query = [node for node in query.nodes if str(node) == "Static()"]
|
|
|
|
icol, _ = self.get_col_task()
|
|
|
|
icol, _ = self.get_col_task()
|
|
|
|
query[icol].classes = "header"
|
|
|
|
query[icol].classes = "header"
|
|
|
|
self.children[0].focus_previous()
|
|
|
|
self.children[0].focus_previous()
|
|
|
@ -236,18 +340,16 @@ class KanbanForm(App):
|
|
|
|
self.board.del_task(icol, itask)
|
|
|
|
self.board.del_task(icol, itask)
|
|
|
|
|
|
|
|
|
|
|
|
def update_col(self, text):
|
|
|
|
def update_col(self, text):
|
|
|
|
""" Update the column
|
|
|
|
"""Update the column"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
icol, itask = self.get_col_task()
|
|
|
|
icol, itask = self.get_col_task()
|
|
|
|
query = self.query(selector=Static)
|
|
|
|
query = self.query(selector=Static)
|
|
|
|
query = [node for node in query.nodes if str(node) == 'Static()']
|
|
|
|
query = [node for node in query.nodes if str(node) == "Static()"]
|
|
|
|
query[icol].update(text)
|
|
|
|
query[icol].update(text)
|
|
|
|
self.board.get_columns()[icol] = text
|
|
|
|
self.board.get_columns()[icol] = text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def action_exit(self):
|
|
|
|
def action_exit(self):
|
|
|
|
"""Exit the application"""
|
|
|
|
"""Exit the application"""
|
|
|
|
self.board.write_yaml(file='.board.yaml')
|
|
|
|
self.board.write_yaml(file=".board.yaml")
|
|
|
|
self.exit()
|
|
|
|
self.exit()
|
|
|
|
|
|
|
|
|
|
|
|
def get_col_task(self):
|
|
|
|
def get_col_task(self):
|
|
|
@ -283,20 +385,33 @@ class KanbanForm(App):
|
|
|
|
self.board.update_task(icol, itask, task)
|
|
|
|
self.board.update_task(icol, itask, task)
|
|
|
|
|
|
|
|
|
|
|
|
def new_task(self, task):
|
|
|
|
def new_task(self, task):
|
|
|
|
""" This function adds a new task to our board
|
|
|
|
"""This function adds a new task to our board"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
if task:
|
|
|
|
if task:
|
|
|
|
icol, _ = self.get_col_task()
|
|
|
|
icol, _ = self.get_col_task()
|
|
|
|
self.focused.mount(ListItem(Label(task.summary)))
|
|
|
|
self.focused.mount(ListItem(Label(task.summary)))
|
|
|
|
self.board.add_task(icol, task)
|
|
|
|
self.board.add_task(icol, task)
|
|
|
|
self.focused.action_cursor_down()
|
|
|
|
self.focused.action_cursor_down()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class KanbanForm(App):
|
|
|
|
|
|
|
|
"""Main Kanban app"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CSS_PATH = "layout.tcss"
|
|
|
|
|
|
|
|
SCREENS = {"main": SelectBoardScreen()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def on_mount(self):
|
|
|
|
|
|
|
|
self.push_screen("main")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# def on_key(self):
|
|
|
|
# def on_key(self):
|
|
|
|
# with open('log','a') as f:
|
|
|
|
# with open('log','a') as f:
|
|
|
|
# f.write("{}".format(self.children[0].focus_next))
|
|
|
|
# f.write("{}".format(self.children[0].focus_next))
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
|
|
def run_tui():
|
|
|
|
kb = KanbanForm()
|
|
|
|
kb = KanbanForm()
|
|
|
|
kb.run()
|
|
|
|
kb.run()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
|
|
run_tui()
|