Compare commits
	
		
			5 commits
		
	
	
		
			74ed6516b4
			...
			fea33eb62e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fea33eb62e | |||
| be682f4754 | |||
| 83ae35006b | |||
| 1bc94bfd0a | |||
| 547a65e19f | 
					 10 changed files with 207 additions and 131 deletions
				
			
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -1,2 +1,2 @@
 | 
				
			||||||
*.pyc
 | 
					*.pyc
 | 
				
			||||||
*.db
 | 
					*.db*
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/tt/constants.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/tt/constants.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SPECIAL_DATES_PIECES = {
 | 
				
			||||||
 | 
					    "future": (3000,1,1),
 | 
				
			||||||
 | 
					    "unclassified": (1999,1,1),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					SPECIAL_DATES_DISPLAY = {
 | 
				
			||||||
 | 
					    "3000-01-01": "[#333333]future[/]",
 | 
				
			||||||
 | 
					    "1999-01-01": "[#cccccc]unclassified[/]",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ from peewee import fn
 | 
				
			||||||
from peewee import Case, Value
 | 
					from peewee import Case, Value
 | 
				
			||||||
from ..db import db, Task, SavedSearch
 | 
					from ..db import db, Task, SavedSearch
 | 
				
			||||||
from .. import config
 | 
					from .. import config
 | 
				
			||||||
 | 
					from ..constants import SPECIAL_DATES_PIECES
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_task(item_id: int) -> Task:
 | 
					def get_task(item_id: int) -> Task:
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ def get_task(item_id: int) -> Task:
 | 
				
			||||||
def add_task(
 | 
					def add_task(
 | 
				
			||||||
    text: str,
 | 
					    text: str,
 | 
				
			||||||
    status: str,
 | 
					    status: str,
 | 
				
			||||||
    due: datetime | None = None,
 | 
					    due: datetime | str = SPECIAL_DATES_PIECES["unclassified"],
 | 
				
			||||||
    type: str = "",
 | 
					    type: str = "",
 | 
				
			||||||
    project: str = "",
 | 
					    project: str = "",
 | 
				
			||||||
) -> Task:
 | 
					) -> Task:
 | 
				
			||||||
| 
						 | 
					@ -84,8 +84,8 @@ def get_tasks(
 | 
				
			||||||
        query = query.where(fn.Lower(Task.text).contains(search_text.lower()))
 | 
					        query = query.where(fn.Lower(Task.text).contains(search_text.lower()))
 | 
				
			||||||
    if statuses:
 | 
					    if statuses:
 | 
				
			||||||
        query = query.where(Task.status.in_(statuses))
 | 
					        query = query.where(Task.status.in_(statuses))
 | 
				
			||||||
    #if projects:
 | 
					    if projects:
 | 
				
			||||||
    #     query = query.where(Task.project.in_(projects))
 | 
					         query = query.where(Task.project.in_(projects))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sort_expressions = _parse_sort_string(sort, statuses)
 | 
					    sort_expressions = _parse_sort_string(sort, statuses)
 | 
				
			||||||
    query = query.order_by(*sort_expressions)
 | 
					    query = query.order_by(*sort_expressions)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										89
									
								
								src/tt/tui/columns.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/tt/tui/columns.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,89 @@
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    get_color_enum,
 | 
				
			||||||
 | 
					    get_colored_date,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from .modals import ChoiceModal, DateModal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NotifyValidationError(Exception):
 | 
				
			||||||
 | 
					    """will notify and continue if raised"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ELLIPSIS = "…"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TableColumnConfig:
 | 
				
			||||||
 | 
					    def __init__(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        field: str,
 | 
				
			||||||
 | 
					        display_name: str,
 | 
				
			||||||
 | 
					        *,
 | 
				
			||||||
 | 
					        default=None,
 | 
				
			||||||
 | 
					        enable_editor=False,
 | 
				
			||||||
 | 
					        filterable=True,
 | 
				
			||||||
 | 
					        read_only=False,
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        self.field = field
 | 
				
			||||||
 | 
					        self.display_name = display_name
 | 
				
			||||||
 | 
					        self.default = default
 | 
				
			||||||
 | 
					        self.enable_editor = enable_editor
 | 
				
			||||||
 | 
					        self.filterable = filterable
 | 
				
			||||||
 | 
					        self.read_only = read_only
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def preprocess(self, val):
 | 
				
			||||||
 | 
					        return val  # no-op
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def start_change(self, app, current_value):
 | 
				
			||||||
 | 
					        if current_value.endswith(ELLIPSIS):
 | 
				
			||||||
 | 
					            app.action_start_edit()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # default edit mode
 | 
				
			||||||
 | 
					            app._show_input("edit", current_value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def format_for_display(self, val):
 | 
				
			||||||
 | 
					        val = str(val)
 | 
				
			||||||
 | 
					        if "\n" in val:
 | 
				
			||||||
 | 
					            val = val.split("\n")[0] + ELLIPSIS
 | 
				
			||||||
 | 
					        return val
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EnumColumnConfig(TableColumnConfig):
 | 
				
			||||||
 | 
					    def __init__(self, field: str, display_name: str, enum, **kwargs):
 | 
				
			||||||
 | 
					        super().__init__(field, display_name, **kwargs)
 | 
				
			||||||
 | 
					        self.enum = enum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def preprocess(self, val):
 | 
				
			||||||
 | 
					        if val in self.enum:
 | 
				
			||||||
 | 
					            return val
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            raise NotifyValidationError(f"Invalid value {val}. Use: {list(self.enum)}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def format_for_display(self, val):
 | 
				
			||||||
 | 
					        return get_color_enum(val, self.enum)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def start_change(self, app, current_value):
 | 
				
			||||||
 | 
					        # a weird hack? pass app here and correct modal gets pushed
 | 
				
			||||||
 | 
					        app.push_screen(ChoiceModal(self.enum, current_value), app.apply_change)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DateColumnConfig(TableColumnConfig):
 | 
				
			||||||
 | 
					    def preprocess(self, val):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return datetime.datetime.strptime(val, "%Y-%m-%d")
 | 
				
			||||||
 | 
					        except ValueError:
 | 
				
			||||||
 | 
					            raise NotifyValidationError("Invalid date format. Use YYYY-MM-DD")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def format_for_display(self, val):
 | 
				
			||||||
 | 
					        return get_colored_date(val)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def start_change(self, app, current_value):
 | 
				
			||||||
 | 
					        app.push_screen(DateModal(current_value), app.apply_change)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_col_cls(field_type):
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        "text": TableColumnConfig,
 | 
				
			||||||
 | 
					        "enum": EnumColumnConfig,
 | 
				
			||||||
 | 
					        "date": DateColumnConfig,
 | 
				
			||||||
 | 
					    }[field_type]
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,3 @@
 | 
				
			||||||
import datetime
 | 
					 | 
				
			||||||
from textual.app import App
 | 
					from textual.app import App
 | 
				
			||||||
from textual.widgets import (
 | 
					from textual.widgets import (
 | 
				
			||||||
    DataTable,
 | 
					    DataTable,
 | 
				
			||||||
| 
						 | 
					@ -13,86 +12,10 @@ from ..utils import (
 | 
				
			||||||
    remove_rich_tag,
 | 
					    remove_rich_tag,
 | 
				
			||||||
    filter_to_string,
 | 
					    filter_to_string,
 | 
				
			||||||
    get_text_from_editor,
 | 
					    get_text_from_editor,
 | 
				
			||||||
    get_color_enum,
 | 
					 | 
				
			||||||
    get_colored_date,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .keymodal import KeyModal
 | 
					from .keymodal import KeyModal
 | 
				
			||||||
from .modals import ChoiceModal, DateModal, ConfirmModal
 | 
					from .modals import ConfirmModal
 | 
				
			||||||
 | 
					from .columns import get_col_cls
 | 
				
			||||||
 | 
					 | 
				
			||||||
class NotifyValidationError(Exception):
 | 
					 | 
				
			||||||
    """will notify and continue if raised"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ELLIPSIS = "…"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TableColumnConfig:
 | 
					 | 
				
			||||||
    def __init__(
 | 
					 | 
				
			||||||
        self,
 | 
					 | 
				
			||||||
        field: str,
 | 
					 | 
				
			||||||
        display_name: str,
 | 
					 | 
				
			||||||
        *,
 | 
					 | 
				
			||||||
        default=None,
 | 
					 | 
				
			||||||
        enable_editor=False,
 | 
					 | 
				
			||||||
        filterable=True,
 | 
					 | 
				
			||||||
        read_only=False,
 | 
					 | 
				
			||||||
    ):
 | 
					 | 
				
			||||||
        self.field = field
 | 
					 | 
				
			||||||
        self.display_name = display_name
 | 
					 | 
				
			||||||
        self.default = default
 | 
					 | 
				
			||||||
        self.enable_editor = enable_editor
 | 
					 | 
				
			||||||
        self.filterable = filterable
 | 
					 | 
				
			||||||
        self.read_only = read_only
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def preprocess(self, val):
 | 
					 | 
				
			||||||
        return val  # no-op
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def start_change(self, app, current_value):
 | 
					 | 
				
			||||||
        if current_value.endswith(ELLIPSIS):
 | 
					 | 
				
			||||||
            app.action_start_edit()
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            # default edit mode
 | 
					 | 
				
			||||||
            app._show_input("edit", current_value)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def format_for_display(self, val):
 | 
					 | 
				
			||||||
        val = str(val)
 | 
					 | 
				
			||||||
        if "\n" in val:
 | 
					 | 
				
			||||||
            val = val.split("\n")[0] + ELLIPSIS
 | 
					 | 
				
			||||||
        return val
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class EnumColumnConfig(TableColumnConfig):
 | 
					 | 
				
			||||||
    def __init__(self, field: str, display_name: str, enum, **kwargs):
 | 
					 | 
				
			||||||
        super().__init__(field, display_name, **kwargs)
 | 
					 | 
				
			||||||
        self.enum = enum
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def preprocess(self, val):
 | 
					 | 
				
			||||||
        if val in self.enum:
 | 
					 | 
				
			||||||
            return val
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            raise NotifyValidationError(f"Invalid value {val}. Use: {list(self.enum)}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def format_for_display(self, val):
 | 
					 | 
				
			||||||
        return get_color_enum(val, self.enum)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def start_change(self, app, current_value):
 | 
					 | 
				
			||||||
        # a weird hack? pass app here and correct modal gets pushed
 | 
					 | 
				
			||||||
        app.push_screen(ChoiceModal(self.enum, current_value), app.apply_change)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DateColumnConfig(TableColumnConfig):
 | 
					 | 
				
			||||||
    def preprocess(self, val):
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            return datetime.strptime(val, "%Y-%m-%d")
 | 
					 | 
				
			||||||
        except ValueError:
 | 
					 | 
				
			||||||
            raise NotifyValidationError("Invalid date format. Use YYYY-MM-DD")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def format_for_display(self, val):
 | 
					 | 
				
			||||||
        return get_colored_date(val)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def start_change(self, app, current_value):
 | 
					 | 
				
			||||||
        app.push_screen(DateModal(current_value), app.apply_change)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -187,11 +110,7 @@ class TableEditor(App):
 | 
				
			||||||
        # set up columns
 | 
					        # set up columns
 | 
				
			||||||
        for col in view["columns"]:
 | 
					        for col in view["columns"]:
 | 
				
			||||||
            field_type = col.get("field_type", "text")
 | 
					            field_type = col.get("field_type", "text")
 | 
				
			||||||
            field_cls = {
 | 
					            field_cls = get_col_cls(field_type)
 | 
				
			||||||
                "text": TableColumnConfig,
 | 
					 | 
				
			||||||
                "enum": EnumColumnConfig,
 | 
					 | 
				
			||||||
                "date": DateColumnConfig,
 | 
					 | 
				
			||||||
            }[field_type]
 | 
					 | 
				
			||||||
            field_name = col["field_name"]
 | 
					            field_name = col["field_name"]
 | 
				
			||||||
            display_name = col.get("display_name", field_name.title())
 | 
					            display_name = col.get("display_name", field_name.title())
 | 
				
			||||||
            default = col.get("default")
 | 
					            default = col.get("default")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,6 +37,7 @@ class KeyModal(ModalScreen):
 | 
				
			||||||
        for binding in self.app.BINDINGS:
 | 
					        for binding in self.app.BINDINGS:
 | 
				
			||||||
            if binding[0] not in ["h", "j", "k", "l", "g", "G", "escape"]:
 | 
					            if binding[0] not in ["h", "j", "k", "l", "g", "G", "escape"]:
 | 
				
			||||||
                table.add_row(binding[0], binding[2])
 | 
					                table.add_row(binding[0], binding[2])
 | 
				
			||||||
 | 
					        # TODO: MRO?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        yield Static("tt keybindings", classes="title")
 | 
					        yield Static("tt keybindings", classes="title")
 | 
				
			||||||
        yield Static(table)
 | 
					        yield Static(table)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,12 @@
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
from textual.screen import ModalScreen
 | 
					from textual.screen import ModalScreen
 | 
				
			||||||
from textual.binding import Binding
 | 
					from textual.binding import Binding
 | 
				
			||||||
from textual.widgets import RadioSet, RadioButton, Label
 | 
					from textual.widgets import Label
 | 
				
			||||||
from .. import config
 | 
					from textual.containers import Horizontal, Vertical
 | 
				
			||||||
 | 
					from textual.reactive import reactive
 | 
				
			||||||
from ..utils import get_color_enum
 | 
					from ..utils import get_color_enum
 | 
				
			||||||
 | 
					from ..constants import SPECIAL_DATES_PIECES
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ConfirmModal(ModalScreen):
 | 
					class ConfirmModal(ModalScreen):
 | 
				
			||||||
    CSS = """
 | 
					    CSS = """
 | 
				
			||||||
| 
						 | 
					@ -42,42 +46,56 @@ class ChoiceModal(ModalScreen):
 | 
				
			||||||
    ChoiceModal Label {
 | 
					    ChoiceModal Label {
 | 
				
			||||||
        height: 1;
 | 
					        height: 1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    ChoiceModal Label#selected {
 | 
				
			||||||
 | 
					        background: white;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    BINDINGS = [
 | 
					    BINDINGS = [
 | 
				
			||||||
        ("j,tab", "cursor_down", "Down"),
 | 
					        ("j,tab", "cursor_down", "Down"),
 | 
				
			||||||
        ("k", "cursor_up", "Up"),
 | 
					        ("k,shift+tab", "cursor_up", "Up"),
 | 
				
			||||||
        Binding("enter", "select", "Select", priority=False),
 | 
					        Binding("enter", "select", "Select", priority=True),
 | 
				
			||||||
        ("escape", "cancel", "cancel"),
 | 
					        ("escape", "cancel", "cancel"),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, enum, selected):
 | 
					    def __init__(self, enum, selected):
 | 
				
			||||||
        self._enum = enum
 | 
					        self._enum = enum
 | 
				
			||||||
        self.selected = selected
 | 
					        self.enum_by_idx = list(self._enum)
 | 
				
			||||||
 | 
					        # selection index
 | 
				
			||||||
 | 
					        self.sel_idx = 0
 | 
				
			||||||
 | 
					        # convert value back to index for initial selection
 | 
				
			||||||
 | 
					        for idx, e in enumerate(self._enum):
 | 
				
			||||||
 | 
					            if e.value == selected:
 | 
				
			||||||
 | 
					                self.sel_idx = idx
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
        super().__init__()
 | 
					        super().__init__()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def compose(self):
 | 
					    def compose(self):
 | 
				
			||||||
        yield RadioSet(
 | 
					        for idx, e in enumerate(self._enum):
 | 
				
			||||||
            *[
 | 
					            yield Label(
 | 
				
			||||||
                RadioButton(
 | 
					                ("> " if idx == self.sel_idx else "  ") +
 | 
				
			||||||
                    get_color_enum(e.value, config.STATUSES), value=self.selected == str(e.value)
 | 
					                     get_color_enum(e.value, self._enum),
 | 
				
			||||||
                )
 | 
					                classes="selected" if idx == self.sel_idx else "",
 | 
				
			||||||
                for e in self._enum
 | 
					            )
 | 
				
			||||||
            ]
 | 
					
 | 
				
			||||||
        )
 | 
					    def _move_cursor(self, dir):
 | 
				
			||||||
 | 
					        labels = self.query(Label)
 | 
				
			||||||
 | 
					        # reset old
 | 
				
			||||||
 | 
					        labels[self.sel_idx].update("  " + get_color_enum(self.enum_by_idx[self.sel_idx], self._enum))
 | 
				
			||||||
 | 
					        # move cursor
 | 
				
			||||||
 | 
					        self.sel_idx = (self.sel_idx + dir) % len(self._enum)
 | 
				
			||||||
 | 
					        # reset new
 | 
				
			||||||
 | 
					        labels[self.sel_idx].update("> " + get_color_enum(self.enum_by_idx[self.sel_idx], self._enum))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def action_cursor_down(self):
 | 
					    def action_cursor_down(self):
 | 
				
			||||||
        self.query_one(RadioSet).action_next_button()
 | 
					        self._move_cursor(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def action_cursor_up(self):
 | 
					    def action_cursor_up(self):
 | 
				
			||||||
        self.query_one(RadioSet).action_previous_button()
 | 
					        self._move_cursor(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def action_select(self):
 | 
					    async def action_select(self):
 | 
				
			||||||
        rs = self.query_one(RadioSet)
 | 
					        self.dismiss(self.enum_by_idx[self.sel_idx])
 | 
				
			||||||
        # TODO: this doesn't work
 | 
					 | 
				
			||||||
        #rs.action_toggle_button()
 | 
					 | 
				
			||||||
        pressed = rs.pressed_button
 | 
					 | 
				
			||||||
        self.dismiss(str(pressed.label))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def action_cancel(self):
 | 
					    def action_cancel(self):
 | 
				
			||||||
        self.app.pop_screen()
 | 
					        self.app.pop_screen()
 | 
				
			||||||
| 
						 | 
					@ -86,37 +104,71 @@ class ChoiceModal(ModalScreen):
 | 
				
			||||||
class DateModal(ModalScreen):
 | 
					class DateModal(ModalScreen):
 | 
				
			||||||
    CSS = """
 | 
					    CSS = """
 | 
				
			||||||
    DateModal {
 | 
					    DateModal {
 | 
				
			||||||
        layout: horizontal;
 | 
					 | 
				
			||||||
        align: center middle;
 | 
					        align: center middle;
 | 
				
			||||||
        background: $primary 30%;
 | 
					        background: $primary 30%;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    DateModal Vertical {
 | 
				
			||||||
 | 
					        border: double teal;
 | 
				
			||||||
 | 
					        height: 10;
 | 
				
			||||||
 | 
					        width: 50;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    DateModal Horizonal {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    DateModal Label {
 | 
					    DateModal Label {
 | 
				
			||||||
        border: solid grey;
 | 
					        border: solid white;
 | 
				
			||||||
 | 
					        align: center middle;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    DateModal Label.selected-date {
 | 
					    DateModal Label.selected-date {
 | 
				
			||||||
        border: solid green;
 | 
					        border: solid green;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    DateModal Label.hints {
 | 
				
			||||||
 | 
					        border: solid grey;
 | 
				
			||||||
 | 
					        height: 4;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    BINDINGS = [
 | 
					    BINDINGS = [
 | 
				
			||||||
        ("j", "cursor_down", "Down"),
 | 
					        ("j", "cursor_down", "Down"),
 | 
				
			||||||
        ("k", "cursor_up", "Up"),
 | 
					        ("k", "cursor_up", "Up"),
 | 
				
			||||||
        ("h,tab", "cursor_left", "Left"),
 | 
					        ("h,shift+tab", "cursor_left", "Left"),
 | 
				
			||||||
        ("l", "cursor_right", "Right"),
 | 
					        ("l,tab", "cursor_right", "Right"),
 | 
				
			||||||
 | 
					        ("f", "future", "Future"),
 | 
				
			||||||
 | 
					        ("t", "today", "Today"),
 | 
				
			||||||
 | 
					        ("u", "unclassified", "Unclassified"),
 | 
				
			||||||
        Binding("enter", "select", "Select", priority=True),
 | 
					        Binding("enter", "select", "Select", priority=True),
 | 
				
			||||||
        ("escape", "cancel", "cancel"),
 | 
					        ("escape", "cancel", "cancel"),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pieces = reactive([0, 0, 0], recompose=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, date):
 | 
					    def __init__(self, date):
 | 
				
			||||||
        self.pieces = [int(p) for p in date.split("-")]
 | 
					 | 
				
			||||||
        self.selected = 1  # start on month
 | 
					 | 
				
			||||||
        super().__init__()
 | 
					        super().__init__()
 | 
				
			||||||
 | 
					        if date in SPECIAL_DATES_PIECES:
 | 
				
			||||||
 | 
					            self.pieces = list(SPECIAL_DATES_PIECES[date])
 | 
				
			||||||
 | 
					        elif date:
 | 
				
			||||||
 | 
					            self.pieces = [int(p) for p in date.split("-")]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.action_today()
 | 
				
			||||||
 | 
					        self.selected = 1  # start on month
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def compose(self):
 | 
					    def compose(self):
 | 
				
			||||||
        for idx, piece in enumerate(self.pieces):
 | 
					        with Vertical():
 | 
				
			||||||
            yield Label(
 | 
					            with Horizontal():
 | 
				
			||||||
                str(piece), classes="selected-date" if idx == self.selected else ""
 | 
					                yield Label(f"{self.pieces[0]}")
 | 
				
			||||||
            )
 | 
					                yield Label(f"{self.pieces[1]}", classes="selected-date")
 | 
				
			||||||
 | 
					                yield Label(f"{self.pieces[2]}")
 | 
				
			||||||
 | 
					            yield Label("""(h/j/k/l) move (enter) confirm (esc) quit
 | 
				
			||||||
 | 
					(p)ast (t)oday (f)uture""", classes="hints")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def action_future(self):
 | 
				
			||||||
 | 
					        self.pieces = list(SPECIAL_DATES_PIECES["future"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def action_unclassified(self):
 | 
				
			||||||
 | 
					        self.pieces = list(SPECIAL_DATES_PIECES["unclassified"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def action_today(self):
 | 
				
			||||||
 | 
					        today = datetime.date.today()
 | 
				
			||||||
 | 
					        self.pieces = [today.year, today.month, today.day]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def action_cursor_left(self):
 | 
					    def action_cursor_left(self):
 | 
				
			||||||
        # cycle Y/M/D
 | 
					        # cycle Y/M/D
 | 
				
			||||||
| 
						 | 
					@ -152,8 +204,7 @@ class DateModal(ModalScreen):
 | 
				
			||||||
        if cur_value > self.max_for(self.selected):
 | 
					        if cur_value > self.max_for(self.selected):
 | 
				
			||||||
            cur_value = 1
 | 
					            cur_value = 1
 | 
				
			||||||
        self.pieces[self.selected] = cur_value
 | 
					        self.pieces[self.selected] = cur_value
 | 
				
			||||||
        cur_label = self.query("Label")[self.selected]
 | 
					        self.mutate_reactive(DateModal.pieces)
 | 
				
			||||||
        cur_label.update(str(cur_value))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def action_cursor_down(self):
 | 
					    def action_cursor_down(self):
 | 
				
			||||||
        self._move_piece(-1)
 | 
					        self._move_piece(-1)
 | 
				
			||||||
| 
						 | 
					@ -178,8 +229,7 @@ class DateModal(ModalScreen):
 | 
				
			||||||
            event.prevent_default()
 | 
					            event.prevent_default()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def action_select(self):
 | 
					    def action_select(self):
 | 
				
			||||||
        date = "-".join(str(p) for p in self.pieces)
 | 
					        self.dismiss("-".join(str(p) for p in self.pieces))
 | 
				
			||||||
        self.dismiss(date)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def action_cancel(self):
 | 
					    def action_cancel(self):
 | 
				
			||||||
        self.app.pop_screen()
 | 
					        self.app.pop_screen()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,10 +56,8 @@ class TT(TableEditor):
 | 
				
			||||||
    def refresh_items(self):
 | 
					    def refresh_items(self):
 | 
				
			||||||
        items = get_tasks(
 | 
					        items = get_tasks(
 | 
				
			||||||
            self.search_query,
 | 
					            self.search_query,
 | 
				
			||||||
            projects=self.filters.get("project", "").split(","),
 | 
					            projects=self._filters_to_list("project"),
 | 
				
			||||||
            statuses=self.filters.get("status", "").split(",")
 | 
					            statuses=self._filters_to_list("status"),
 | 
				
			||||||
            if "status" in self.filters
 | 
					 | 
				
			||||||
            else None,
 | 
					 | 
				
			||||||
            sort=self.sort_string,
 | 
					            sort=self.sort_string,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        for item in items:
 | 
					        for item in items:
 | 
				
			||||||
| 
						 | 
					@ -68,6 +66,12 @@ class TT(TableEditor):
 | 
				
			||||||
                key=str(item.id),
 | 
					                key=str(item.id),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _filters_to_list(self, key):
 | 
				
			||||||
 | 
					        filters = self.filters.get(key)
 | 
				
			||||||
 | 
					        if filters:
 | 
				
			||||||
 | 
					            return filters.split(",")
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def run(default_view):
 | 
					def run(default_view):
 | 
				
			||||||
    app = TT(default_view)
 | 
					    app = TT(default_view)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ import os
 | 
				
			||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
import tempfile
 | 
					import tempfile
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
 | 
					from .constants import SPECIAL_DATES_DISPLAY
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def filter_to_string(filters, search_query):
 | 
					def filter_to_string(filters, search_query):
 | 
				
			||||||
| 
						 | 
					@ -41,6 +42,8 @@ def get_colored_date(date: datetime.date) -> str:
 | 
				
			||||||
    if not isinstance(date, datetime.date):
 | 
					    if not isinstance(date, datetime.date):
 | 
				
			||||||
        return ""
 | 
					        return ""
 | 
				
			||||||
    as_str = date.strftime("%Y-%m-%d")
 | 
					    as_str = date.strftime("%Y-%m-%d")
 | 
				
			||||||
 | 
					    if as_str in SPECIAL_DATES_DISPLAY:
 | 
				
			||||||
 | 
					        return SPECIAL_DATES_DISPLAY[as_str]
 | 
				
			||||||
    today = datetime.date.today()
 | 
					    today = datetime.date.today()
 | 
				
			||||||
    if date.date() < today:
 | 
					    if date.date() < today:
 | 
				
			||||||
        return f"[#eeeeee on #dd1111]{as_str}[/]"
 | 
					        return f"[#eeeeee on #dd1111]{as_str}[/]"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										6
									
								
								tt.toml
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								tt.toml
									
									
									
									
									
								
							| 
						 | 
					@ -28,7 +28,7 @@ values = [
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[views]]
 | 
					[[views]]
 | 
				
			||||||
name = "tasks"
 | 
					name = "tasks"
 | 
				
			||||||
sort = "due"
 | 
					sort = "due,status"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[views.filters]
 | 
					[views.filters]
 | 
				
			||||||
status = "wip,blocked,zero"
 | 
					status = "wip,blocked,zero"
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@ read_only = true
 | 
				
			||||||
[[views.columns]]
 | 
					[[views.columns]]
 | 
				
			||||||
field_name = "text"
 | 
					field_name = "text"
 | 
				
			||||||
display_name = "Task"
 | 
					display_name = "Task"
 | 
				
			||||||
default = "new taskz"
 | 
					default = "new task"
 | 
				
			||||||
editor = true
 | 
					editor = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[views.columns]]
 | 
					[[views.columns]]
 | 
				
			||||||
| 
						 | 
					@ -56,7 +56,7 @@ default = ""
 | 
				
			||||||
[[views.columns]]
 | 
					[[views.columns]]
 | 
				
			||||||
field_name = "due"
 | 
					field_name = "due"
 | 
				
			||||||
field_type = "date"
 | 
					field_type = "date"
 | 
				
			||||||
default = ""
 | 
					default = "1999-01-01"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[views.columns]]
 | 
					[[views.columns]]
 | 
				
			||||||
field_name = "project"
 | 
					field_name = "project"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue