summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason Lee <changjin9792@gmail.com>2023-03-23 21:11:19 +0800
committerJason Lee <changjin9792@gmail.com>2023-03-23 21:11:19 +0800
commit653eb48cae27d63b44a876cf2a9dad328e43629d (patch)
treeeb0bef6fa811c5de461fdc067064702e60316085
parent6ae66af763c2afdd0a51c14a93fa8958bf98e353 (diff)
[Feat] Deployed PyPI pakcage v0.1.2feature/migrate-cli-codes
issue #1
-rw-r--r--README.md1
-rw-r--r--dist/girok-0.1.0-py3-none-any.whlbin0 -> 35642 bytes
-rw-r--r--dist/girok-0.1.0.tar.gzbin0 -> 28634 bytes
-rw-r--r--dist/girok-0.1.1-py3-none-any.whlbin0 -> 35713 bytes
-rw-r--r--dist/girok-0.1.1.tar.gzbin0 -> 28690 bytes
-rw-r--r--dist/girok-0.1.2-py3-none-any.whlbin0 -> 35877 bytes
-rw-r--r--dist/girok-0.1.2.tar.gzbin0 -> 28749 bytes
-rw-r--r--girok/__init__.py1
-rw-r--r--girok/__main__.py0
-rw-r--r--girok/api/auth.py34
-rw-r--r--girok/api/category.py131
-rw-r--r--girok/api/task.py146
-rw-r--r--girok/calendar_cli/calendar_app.py37
-rw-r--r--girok/calendar_cli/calendar_container.py324
-rw-r--r--girok/calendar_cli/calendar_main.css252
-rw-r--r--girok/calendar_cli/calendar_main.py181
-rw-r--r--girok/calendar_cli/sidebar.py201
-rw-r--r--girok/calendar_cli/textual_demo_2023-03-11T22_49_21_681307.svg246
-rw-r--r--girok/commands/auth.py89
-rw-r--r--girok/commands/calendar.py10
-rw-r--r--girok/commands/category.py124
-rw-r--r--girok/commands/task.py569
-rw-r--r--girok/config.json4
-rw-r--r--girok/config.py22
-rw-r--r--girok/constants.py53
-rw-r--r--girok/girok.py48
-rw-r--r--girok/styles/stopwatch03.css53
-rw-r--r--girok/utils/__init__.py0
-rw-r--r--girok/utils/auth.py54
-rw-r--r--girok/utils/calendar.py132
-rw-r--r--girok/utils/display.py253
-rw-r--r--girok/utils/general.py52
-rw-r--r--girok/utils/task.py161
-rw-r--r--new_requirements.txt43
-rw-r--r--poetry.lock1277
-rw-r--r--processreq.py22
-rw-r--r--pyproject.toml59
-rw-r--r--tests/__init__.py0
38 files changed, 4578 insertions, 1 deletions
diff --git a/README.md b/README.md
index b087646..e69de29 100644
--- a/README.md
+++ b/README.md
@@ -1 +0,0 @@
-# girok-cli \ No newline at end of file
diff --git a/dist/girok-0.1.0-py3-none-any.whl b/dist/girok-0.1.0-py3-none-any.whl
new file mode 100644
index 0000000..fb2c24f
--- /dev/null
+++ b/dist/girok-0.1.0-py3-none-any.whl
Binary files differ
diff --git a/dist/girok-0.1.0.tar.gz b/dist/girok-0.1.0.tar.gz
new file mode 100644
index 0000000..0851a9d
--- /dev/null
+++ b/dist/girok-0.1.0.tar.gz
Binary files differ
diff --git a/dist/girok-0.1.1-py3-none-any.whl b/dist/girok-0.1.1-py3-none-any.whl
new file mode 100644
index 0000000..52d4501
--- /dev/null
+++ b/dist/girok-0.1.1-py3-none-any.whl
Binary files differ
diff --git a/dist/girok-0.1.1.tar.gz b/dist/girok-0.1.1.tar.gz
new file mode 100644
index 0000000..2ea2e29
--- /dev/null
+++ b/dist/girok-0.1.1.tar.gz
Binary files differ
diff --git a/dist/girok-0.1.2-py3-none-any.whl b/dist/girok-0.1.2-py3-none-any.whl
new file mode 100644
index 0000000..43623d5
--- /dev/null
+++ b/dist/girok-0.1.2-py3-none-any.whl
Binary files differ
diff --git a/dist/girok-0.1.2.tar.gz b/dist/girok-0.1.2.tar.gz
new file mode 100644
index 0000000..fdebf13
--- /dev/null
+++ b/dist/girok-0.1.2.tar.gz
Binary files differ
diff --git a/girok/__init__.py b/girok/__init__.py
new file mode 100644
index 0000000..48fef32
--- /dev/null
+++ b/girok/__init__.py
@@ -0,0 +1 @@
+__version__ = "0.1.2" \ No newline at end of file
diff --git a/girok/__main__.py b/girok/__main__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/girok/__main__.py
diff --git a/girok/api/auth.py b/girok/api/auth.py
new file mode 100644
index 0000000..6d71a38
--- /dev/null
+++ b/girok/api/auth.py
@@ -0,0 +1,34 @@
+import requests
+from girok.config import get_config
+cfg = get_config()
+
+base_url = cfg.base_url
+
+def register(email, password):
+ resp = requests.post(base_url + "/register", json={
+ "email": email,
+ "password": password
+ })
+ return resp
+
+
+def login(email, password):
+ files = {
+ "username": (None, email),
+ "password": (None, password)
+ }
+
+ resp = requests.post(base_url + "/login", files=files)
+ return resp
+
+
+def validate_access_token(access_token):
+ options = {
+ "headers": {
+ "Authorization": "Bearer " + access_token,
+ }
+ }
+
+
+ resp = requests.get(base_url + "/validate-access-token", headers=options['headers'])
+ return resp \ No newline at end of file
diff --git a/girok/api/category.py b/girok/api/category.py
new file mode 100644
index 0000000..f01d274
--- /dev/null
+++ b/girok/api/category.py
@@ -0,0 +1,131 @@
+import requests
+from rich.console import Console
+
+from girok.config import get_config
+import girok.utils.auth as auth_utils
+import girok.utils.general as general_utils
+import girok.utils.display as display_utils
+import girok.constants as constants
+
+console = Console()
+cfg = get_config()
+
+def get_categories():
+ resp = requests.get(
+ cfg.base_url + "/categories",
+ headers=auth_utils.build_jwt_header(cfg.config_path)
+ )
+ if resp.status_code == 200:
+ return general_utils.bytes2dict(resp.content)
+
+
+
+def add_category(cat_str: str, color=None):
+ cats = cat_str.split('/')
+ resp = requests.post(
+ cfg.base_url + "/categories",
+ json={
+ "names": cats,
+ "color": color
+ },
+ headers=auth_utils.build_jwt_header(cfg.config_path)
+ )
+ return resp
+
+
+def remove_category(cat_str: str):
+ cats = cat_str.split('/')
+ resp = requests.delete(
+ cfg.base_url + "/categories",
+ json={
+ "cats": cats
+ },
+ headers=auth_utils.build_jwt_header(cfg.config_path)
+ )
+
+ return resp
+
+
+def rename_category(cat_str: str, new_name: str):
+ cats = cat_str.split('/')
+ resp = requests.patch(
+ cfg.base_url + "/categories/name",
+ json={
+ "cats": cats,
+ "new_name": new_name
+ },
+ headers=auth_utils.build_jwt_header(cfg.config_path)
+ )
+
+ return resp
+
+
+def move_category(cat_str: str, new_parent_cat_str: str):
+ if cat_str.endswith('/'):
+ cat_str = cat_str[:-1]
+ if new_parent_cat_str.endswith('/'):
+ new_parent_cat_str = new_parent_cat_str[:-1]
+
+ cats = cat_str.split('/') if cat_str else []
+ new_parent_cats = new_parent_cat_str.split('/') if new_parent_cat_str else []
+ resp = requests.patch(
+ cfg.base_url + "/categories/parent",
+ json={
+ "cats": cats,
+ "new_parent_cats": new_parent_cats
+ },
+ headers=auth_utils.build_jwt_header(cfg.config_path)
+ )
+ return resp
+
+
+def get_last_cat_id(cats: list):
+ resp = requests.get(
+ cfg.base_url + "/categories/last-cat-id",
+ json={
+ "cats": cats
+ },
+ headers=auth_utils.build_jwt_header(cfg.config_path)
+ )
+ if resp.status_code == 200:
+ return general_utils.bytes2dict(resp.content)['cat_id']
+ elif resp.status_code == 400:
+ err_msg = general_utils.bytes2dict(resp.content)['detail']
+ display_utils.center_print(err_msg, constants.DISPLAY_TERMINAL_COLOR_ERROR)
+ exit(0)
+ else:
+ display_utils.center_print(resp.content, constants.DISPLAY_TERMINAL_COLOR_ERROR)
+ exit(0)
+
+
+def get_category_color(cat_id: int):
+ resp = requests.get(
+ cfg.base_url + f"/categories/{cat_id}/color",
+ headers=auth_utils.build_jwt_header(cfg.config_path)
+ )
+
+ if resp.status_code == 200:
+ return general_utils.bytes2dict(resp.content)['color']
+ elif resp.status_code == 400:
+ err_msg = general_utils.bytes2dict(resp.content)['detail']
+ display_utils.center_print(err_msg, constants.DISPLAY_TERMINAL_COLOR_ERROR)
+ exit(0)
+ else:
+ display_utils.center_print(resp.content, constants.DISPLAY_TERMINAL_COLOR_ERROR)
+ exit(0)
+
+
+def get_color_dict():
+ resp = requests.get(
+ cfg.base_url + "/categories/color",
+ headers=auth_utils.build_jwt_header(cfg.config_path)
+ )
+ if resp.status_code == 200:
+ color_dict = general_utils.bytes2dict(resp.content)
+ return color_dict
+ elif resp.status_code == 400:
+ err_msg = general_utils.bytes2dict(resp.content)['detail']
+ display_utils.center_print(err_msg, constants.DISPLAY_TERMINAL_COLOR_ERROR)
+ else:
+ display_utils.center_print("Error occurred.", constants.DISPLAY_TERMINAL_COLOR_ERROR)
+ \ No newline at end of file
diff --git a/girok/api/task.py b/girok/api/task.py
new file mode 100644
index 0000000..04e972b
--- /dev/null
+++ b/girok/api/task.py
@@ -0,0 +1,146 @@
+from typing import Union
+from textual import log
+from datetime import datetime
+import requests
+
+from girok.config import get_config
+import girok.utils.auth as auth_utils
+import girok.utils.display as display_utils
+import girok.utils.general as general_utils
+import girok.utils.task as task_utils
+import girok.constants as constants
+
+cfg = get_config()
+
+def create_task(task_data: dict):
+ print(task_data)
+ resp = requests.post(
+ cfg.base_url + "/tasks",
+ json=task_data,
+ headers=auth_utils.build_jwt_header(cfg.config_path)
+ )
+ if resp.status_code == 201:
+ task = general_utils.bytes2dict(resp.content)
+ task_id = task['task_id']
+ return task_id
+ elif resp.status_code == 400:
+ err_msg = general_utils.bytes2dict(resp.content)['detail']
+ display_utils.center_print(err_msg, constants.DISPLAY_TERMINAL_COLOR_ERROR)
+ else:
+ display_utils.center_print("Error occurred.", constants.DISPLAY_TERMINAL_COLOR_ERROR)
+
+
+def get_single_task(task_id: int):
+ resp = requests.get(
+ cfg.base_url + f"/tasks/{task_id}",
+ headers=auth_utils.build_jwt_header(cfg.config_path),
+ )
+ if resp.status_code == 200:
+ task = general_utils.bytes2dict(resp.content)
+ return task
+ elif resp.status_code == 400:
+ err_msg = general_utils.bytes2dict(resp.content)['detail']
+ display_utils.center_print(err_msg, constants.DISPLAY_TERMINAL_COLOR_ERROR)
+ else:
+ display_utils.center_print(resp.content, constants.DISPLAY_TERMINAL_COLOR_ERROR)
+
+
+
+def get_tasks(
+ cats: Union[list, None] = None,
+ start_date: Union[str, None] = None,
+ end_date: Union[str, None] = None,
+ tag: Union[str, None] = None,
+ priority: Union[int, None] = None,
+ no_priority: bool = False,
+ view: str = "category"
+):
+ query_str_obj = {
+ "category": cats,
+ "start_date": start_date,
+ "end_date": end_date,
+ "tag": tag,
+ "priority": priority,
+ "no_priority": no_priority,
+ "view": view
+ }
+ resp = requests.get(
+ cfg.base_url + "/tasks",
+ headers=auth_utils.build_jwt_header(cfg.config_path),
+ params=query_str_obj
+ )
+ return resp
+
+
+def remove_task(task_id: int):
+ resp = requests.delete(
+ cfg.base_url + f"/tasks/{task_id}",
+ headers=auth_utils.build_jwt_header(cfg.config_path),
+ )
+ return resp
+
+
+def get_tags():
+ resp = requests.get(
+ cfg.base_url + "/tasks/tags",
+ headers=auth_utils.build_jwt_header(cfg.config_path)
+ )
+
+ return resp
+
+
+def change_task_tag(task_id: int, new_tag_name: str):
+ resp = requests.patch(
+ cfg.base_url + f"/tasks/{task_id}/tag",
+ headers=auth_utils.build_jwt_header(cfg.config_path),
+ json={
+ "new_tag_name": new_tag_name
+ }
+ )
+
+ if resp.status_code == 204:
+ display_utils.center_print(f"Successfully renamed [ID: {task_id}] tag to {new_tag_name}.", "black on green")
+ elif resp.status_code == 400:
+ err_msg = general_utils.bytes2dict(resp.content)['detail']
+ display_utils.center_print(err_msg, constants.DISPLAY_TERMINAL_COLOR_ERROR)
+ else:
+ display_utils.center_print(resp.content, constants.DISPLAY_TERMINAL_COLOR_ERROR)
+
+
+def change_task_priority(task_id: int, new_priority: int):
+ resp = requests.patch(
+ cfg.base_url + f"/tasks/{task_id}/priority",
+ headers=auth_utils.build_jwt_header(cfg.config_path),
+ json={
+ "new_priority": new_priority
+ }
+ )
+
+ if resp.status_code == 204:
+ display_utils.center_print(f"Successfully change [ID: {task_id}] priority to {new_priority}.", "black on green")
+ elif resp.status_code == 400:
+ err_msg = general_utils.bytes2dict(resp.content)['detail']
+ display_utils.center_print(err_msg, constants.DISPLAY_TERMINAL_COLOR_ERROR)
+ else:
+ display_utils.center_print(resp.content, constants.DISPLAY_TERMINAL_COLOR_ERROR)
+
+
+def change_task_date(task_id: int, new_date: str):
+ resp = requests.patch(
+ cfg.base_url + f"/tasks/{task_id}/date",
+ headers=auth_utils.build_jwt_header(cfg.config_path),
+ json={
+ "new_date": new_date
+ }
+ )
+
+ if resp.status_code == 204:
+ display_utils.center_print(f"Successfully change [ID: {task_id}] date to {new_date}.", "black on green")
+ elif resp.status_code == 400:
+ err_msg = general_utils.bytes2dict(resp.content)['detail']
+ display_utils.center_print(err_msg, constants.DISPLAY_TERMINAL_COLOR_ERROR)
+ else:
+ display_utils.center_print(resp.content, constants.DISPLAY_TERMINAL_COLOR_ERROR)
+
+
+
diff --git a/girok/calendar_cli/calendar_app.py b/girok/calendar_cli/calendar_app.py
new file mode 100644
index 0000000..443aba6
--- /dev/null
+++ b/girok/calendar_cli/calendar_app.py
@@ -0,0 +1,37 @@
+from textual.app import App, ComposeResult
+from textual.containers import Container, Horizontal, Vertical
+from textual.widgets import Button, Footer, Header, Static, Label, Placeholder, Tree
+from textual.messages import Message
+from textual.widget import Widget
+
+import girok.api.category as category_api
+import girok.utils.calendar as calendar_utils
+from rich.table import Table
+from rich.style import Style
+from textual import log
+
+from girok.calendar_cli.sidebar import SidebarContainer, CategoryTree
+from girok.calendar_cli.calendar_container import CalendarContainer
+import girok.constants as constants
+
+class CalendarApp(Horizontal):
+ CSS_PATH = "./demo_dock.css"
+
+ def compose(self):
+ yield SidebarContainer(id="sidebar-container")
+ yield CalendarContainer(id="calendar-container")
+
+ def on_category_tree_category_changed(self, event):
+ self.query_one(CalendarContainer).update_cat_path(event.cat_path)
+ cat_tree = self.query_one(CategoryTree)
+
+ def on_tag_tree_tag_changed(self, event):
+ tag = event.tag
+ if tag.endswith(" " + constants.LEFT_ARROW_EMOJI):
+ tag = tag[:-2]
+ if tag == "All Tags":
+ tag = ""
+ self.query_one(CalendarContainer).update_tag(tag)
+
+ def on_category_tree_custom_test_message(self, event):
+ self.refresh() \ No newline at end of file
diff --git a/girok/calendar_cli/calendar_container.py b/girok/calendar_cli/calendar_container.py
new file mode 100644
index 0000000..38d54c9
--- /dev/null
+++ b/girok/calendar_cli/calendar_container.py
@@ -0,0 +1,324 @@
+import asyncio
+from datetime import datetime, timedelta
+import calendar
+
+from textual.app import App, ComposeResult
+from textual.containers import Container, Horizontal, Vertical
+from textual.widgets import Button, Footer, Header, Static, Label, Placeholder, Tree, DataTable
+from textual.messages import Message
+from textual.widget import Widget
+from textual.reactive import var
+from rich.text import Text
+from rich.style import Style
+from rich.segment import Segment
+from rich.panel import Panel
+from rich.markdown import Markdown
+from textual import log
+
+import girok.api.task as task_api
+import girok.api.category as category_api
+import girok.utils.calendar as calendar_utils
+import girok.utils.general as general_utils
+import girok.utils.task as task_utils
+import girok.constants as constants
+from girok.calendar_cli.sidebar import CategoryTree, TagTree
+
+
+class WeekdayBarContainer(Horizontal):
+ pass
+
+class CalendarHeader(Vertical):
+ year = datetime.now().year
+ month = datetime.now().month
+ cat_path = ""
+ tag = ""
+
+ def on_mount(self):
+ self.display_date()
+
+ def compose(self):
+ month_name = task_utils.get_month_name_by_number(self.month)
+
+ with Horizontal():
+ with Container(id="calendar-header-category-container"):
+ yield Static(self.cat_path, id="calendar-header-category")
+ with Container(id="calendar-header-date-container"):
+ yield Static(Text(f"{month_name} {self.year}", style=Style(color=constants.CALENDAR_HEADER_DATE_COLOR, bold=True)), id="calendar-header-date")
+ with Container(id="calendar-header-tag-container"):
+ yield Static(f"{self.tag}", id="calendar-header-tag")
+ yield Horizontal()
+ with WeekdayBarContainer(id="weekday-bar"):
+ yield Static(Text("Monday", style=Style(color=constants.CALENDAR_WEEKDAY_NAME_COLOR, bold=True)), classes="calendar-weekday-name")
+ yield Static(Text("Tuesday", style=Style(color=constants.CALENDAR_WEEKDAY_NAME_COLOR, bold=True)), classes="calendar-weekday-name")
+ yield Static(Text("Wednesday", style=Style(color=constants.CALENDAR_WEEKDAY_NAME_COLOR, bold=True)), classes="calendar-weekday-name")
+ yield Static(Text("Thursday", style=Style(color=constants.CALENDAR_WEEKDAY_NAME_COLOR, bold=True)), classes="calendar-weekday-name")
+ yield Static(Text("Friday", style=Style(color=constants.CALENDAR_WEEKDAY_NAME_COLOR, bold=True)), classes="calendar-weekday-name")
+ yield Static(Text("Saturday", style=Style(color="#87C5FA", bold=True)), classes="calendar-weekday-name")
+ yield Static(Text("Sunday", style=Style(color="#DB4455", bold=True)), classes="calendar-weekday-name")
+
+ def update_year_and_month(self, year, month):
+ self.year, self.month = year, month
+ self.display_date()
+
+ def update_cat_path(self, new_cat_path):
+ self.cat_path = new_cat_path
+ self.display_date()
+
+ def update_tag(self, new_tag):
+ self.tag = new_tag
+ self.display_date()
+
+ def display_date(self):
+ month_name = task_utils.get_month_name_by_number(self.month, abbr=False)
+ calendar_header_category = self.query_one("#calendar-header-category")
+ calendar_header_date = self.query_one("#calendar-header-date")
+ calendar_header_tag = self.query_one("#calendar-header-tag")
+ calendar_weekday_bar = self.query_one("#weekday-bar")
+
+ calendar_header_category.update(Text(f"Category: /{self.cat_path}", style=Style(color=constants.CALENDAR_HEADER_DATE_COLOR)))
+ calendar_header_date.update(Text(f"{month_name} {self.year}", style=Style(color=constants.CALENDAR_HEADER_DATE_COLOR, bold=True)))
+ calendar_header_tag.update(Text(f"Tag: {self.tag}", style=Style(color=constants.CALENDAR_HEADER_DATE_COLOR)))
+
+
+class CalendarCell(Vertical):
+ pass
+
+
+class Calendar(Container):
+ year = datetime.now().year
+ month = datetime.now().month
+ cat_path = "" # If "", show all categories
+ tag = ""
+ tasks = []
+ can_focus = True
+ cur_month_first_day_cell_num = None
+ cur_focused_cell_cord = (None, None)
+ cur_focused_cell = None
+ m = 5
+ n = 7
+ grid = [[False for _ in range(7)] for _ in range(5)]
+ is_pop_up = False
+
+ class TaskCellSelected(Message):
+ def __init__(self, cell_tasks, year, month, day):
+ super().__init__()
+ self.cell_tasks = cell_tasks
+ self.year = year
+ self.month = month
+ self.day = day
+
+ def on_mount(self):
+ self.update_calendar()
+
+ def compose(self):
+ for i in range(35):
+ yield CalendarCell(classes="calendar-cell", id=f"cell{i}")
+
+ def on_key(self, event):
+ if self.is_pop_up:
+ return
+ x, y = self.cur_focused_cell_cord
+ if event.key == 'h': # left
+ next_cell_coord = (x, y - 1)
+ elif event.key == 'j': # down
+ next_cell_coord = (x + 1, y)
+ elif event.key == 'k': # up
+ next_cell_coord = (x - 1, y)
+ elif event.key == 'l': # right
+ next_cell_coord = (x, y + 1)
+ elif event.key == 'o':
+ pass
+ else:
+ return
+
+ if event.key in ['h', 'j', 'k', 'l']: # moving on cells
+ nx, ny = next_cell_coord
+ if nx < 0 or ny < 0 or nx >= self.m or ny >= self.n: # Out of matrix
+ return
+
+ if not self.grid[nx][ny]: # Out of boundary of the current month
+ return
+
+ prev_cell_num = calendar_utils.convert_coord_to_cell_num(*self.cur_focused_cell_cord)
+ prev_cell = self.query_one(f"#cell{prev_cell_num}")
+ calendar_utils.remove_left_arrow(prev_cell)
+
+ cur_cell_num = calendar_utils.convert_coord_to_cell_num(nx, ny)
+ next_cell = self.query_one(f"#cell{cur_cell_num}")
+ calendar_utils.add_left_arrow(next_cell)
+
+ self.cur_focused_cell_cord = (nx, ny)
+ self.cur_focused_cell = next_cell
+ elif event.key == 'o': # select a cell
+ cur_cell_num = calendar_utils.convert_coord_to_cell_num(x, y)
+ cur_cell = self.query_one(f"#cell{cur_cell_num}")
+ cell_tasks = cur_cell.children[1:] # task data
+
+ # Retrieve tasks for the selected day
+ selected_day = calendar_utils.convert_cell_num_to_day(self.year, self.month, cur_cell_num)
+ cell_tasks = list(filter(
+ lambda x: calendar_utils.get_date_obj_from_str_separated_by_T(x['deadline']).day == selected_day,
+ self.tasks
+ ))
+ self.post_message(self.TaskCellSelected(cell_tasks, self.year, self.month, selected_day))
+ self.is_pop_up = True
+
+ def on_focus(self):
+ x, y = calendar_utils.convert_cell_num_to_coord(self.cur_month_first_day_cell_num)
+ target_cell = self.query_one(f"#cell{self.cur_month_first_day_cell_num}")
+ # target_cell.add_class("focused-cell")
+ calendar_utils.add_left_arrow(target_cell)
+ self.cur_focused_cell_cord = (x, y)
+ self.cur_focused_cell = target_cell
+
+ def update_year_and_month(self, year, month):
+ self.year, self.month = year, month
+ self.update_calendar()
+
+ def update_cat_path(self, new_cat_path: str):
+ self.cat_path = new_cat_path
+ self.update_calendar(show_arrow=False)
+
+ def update_tag(self, new_tag: str):
+ self.tag = new_tag
+ self.update_calendar(show_arrow=False)
+
+ def refresh_cell_days(self):
+ self.grid = [[False for _ in range(7)] for _ in range(5)]
+ first_weekday, total_days = calendar.monthrange(self.year, self.month)
+ self.cur_month_first_day_cell_num = first_weekday
+ now = datetime.now()
+ for i in range(35):
+ cell = self.query_one(f"#cell{i}")
+ for child in cell.walk_children():
+ child.remove()
+ if i >= first_weekday and i <= first_weekday + total_days - 1:
+ x, y = calendar_utils.convert_cell_num_to_coord(i)
+ self.grid[x][y] = True
+ day = calendar_utils.convert_cell_num_to_day(self.year, self.month, i)
+ day_text = Text()
+ day_text.append(f"{day}")
+ if self.year == now.year and self.month == now.month and day == now.day:
+ day_text = Text(str(day_text), style=Style(bgcolor=constants.CALENDAR_TODAY_COLOR, color="black"))
+ # day_text.stylize(style=Style(bgcolor=constants.CALENDAR_TODAY_COLOR, color="black"))
+ cell.mount(Label(day_text, id=f"cell-header-{i}"))
+
+ def update_calendar(self, show_arrow=True):
+ """
+ If val == "", then "root category" is selected
+ """
+ if self.cat_path == "": # all categories
+ cat_list = None
+ elif self.cat_path == "No Category":
+ cat_list = ['']
+ else:
+ cat_list = self.cat_path[:-1].split('/')
+
+ if self.tag == "":
+ tag = None
+ else:
+ tag = self.tag
+
+ log("CATS", cat_list),
+ log("tag", tag)
+
+ start_date, end_date = task_utils.build_time_window_by_year_and_month(self.year, self.month)
+ resp = task_api.get_tasks(
+ cats=cat_list,
+ start_date=start_date,
+ end_date=end_date,
+ tag=tag,
+ view="list"
+ )
+
+ if resp.status_code == 200:
+ self.refresh_cell_days()
+ tasks = general_utils.bytes2dict(resp.content)['tasks']
+ self.tasks = tasks
+ first_weekday, last_day = calendar.monthrange(self.year, self.month)
+
+ if self.cur_focused_cell:
+ if show_arrow:
+ calendar_utils.remove_left_arrow(self.cur_focused_cell)
+ self.cur_focused_cell_cord = calendar_utils.convert_cell_num_to_coord(first_weekday)
+ self.cur_focused_cell = self.query_one(f"#cell{first_weekday}") # update
+ if show_arrow:
+ calendar_utils.add_left_arrow(self.cur_focused_cell)
+
+ for idx, task in enumerate(tasks):
+ full_date = task['deadline']
+ day = datetime.strptime(full_date, "%Y-%m-%dT%H:%M:%S").day
+ cell_num = calendar_utils.convert_day_to_cell_num(self.year, self.month, day)
+ cell = self.query_one(f"#cell{cell_num}")
+ color = task['color']
+ name = task['name']
+ # if len(name) > 13:
+ # name = name[:13] + ".."
+ task_item_name = Text()
+ task_item_name.append("●", style=constants.CIRCLE_COLOR[color])
+ task_item_name.append(" " + name)
+
+ task_item = Static(task_item_name, id=f"task-cell{cell_num}-{idx}", classes="task-item")
+ cell.mount(task_item)
+
+ task_item = self.query_one(f"#task-cell{cell_num}-{idx}")
+ task_item.styles.overflow_x = "hidden"
+ task_item.styles.overflow_y = "hidden"
+
+ elif resp.status_code == 400:
+ err_msg = general_utils.bytes2dict(resp.content)['detail']
+ exit(0)
+ else:
+ exit(0)
+
+
+class CalendarContainer(Vertical):
+ year = datetime.now().year
+ month = datetime.now().month
+ cat_path = None # If none, show all categories
+ tag = None
+
+ def update_month_by_offset(self, offset: int):
+ new_year, new_month = task_utils.get_year_and_month_by_month_offset(
+ month_offset=offset,
+ year=self.year,
+ month=self.month
+ )
+ self.year, self.month = new_year, new_month
+ calendar_header = self.query_one(CalendarHeader)
+ cal = self.query_one(Calendar)
+
+ calendar_header.update_year_and_month(self.year, self.month)
+ cal.update_year_and_month(self.year, self.month)
+
+ def update_year_and_month(self, year: int, month: int):
+ self.year, self.month = year, month
+ calendar_header = self.query_one(CalendarHeader)
+ cal = self.query_one(Calendar)
+
+ calendar_header.update_year_and_month(self.year, self.month)
+ cal.update_year_and_month(self.year, self.month)
+
+ def update_cat_path(self, cat_path: str):
+ """
+ cat_path: ex) HKU/COMP3230 or ""
+ """
+ self.cat_path = cat_path
+ cal = self.query_one(Calendar)
+ cal.update_cat_path(new_cat_path=cat_path)
+
+ cal_header = self.query_one(CalendarHeader)
+ cal_header.update_cat_path(new_cat_path=cat_path)
+
+
+ def update_tag(self, tag: str):
+ self.tag = tag
+ cal = self.query_one(Calendar)
+ cal.update_tag(new_tag=tag)
+ cal_header = self.query_one(CalendarHeader)
+ cal_header.update_tag(new_tag=tag)
+
+
+ def compose(self):
+ yield CalendarHeader(id="calendar-header")
+ yield Calendar(id="calendar")
diff --git a/girok/calendar_cli/calendar_main.css b/girok/calendar_cli/calendar_main.css
new file mode 100644
index 0000000..ee7300a
--- /dev/null
+++ b/girok/calendar_cli/calendar_main.css
@@ -0,0 +1,252 @@
+/* :root {
+ --task-color: #a8c1d1;
+ --default-font-color: #8f929b;
+ --calendar-date-color: #c4c7d0;
+ --category-color: #9bdfbb;
+ --border-color: grey;
+ --default-background-color: #171921;
+} */
+
+* {
+ padding: 0;
+ margin: 0;
+ color: #8f929b;
+ background: #171921;
+ overflow: hidden hidden;
+}
+
+Screen {
+ /* background: white; */
+ layers: below above;
+ layer: below;
+}
+
+#app-calendar {
+ layout: horizontal;
+}
+
+#sidebar-container {
+ /* display: none; */
+ height: 100%;
+ width: 2fr;
+ border: