diff --git a/.gitignore b/.gitignore index d523ab6..0ea3240 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ todo.md .cache .nvimrc CMakeLists.txt.user +.slint.lua diff --git a/external/mirai-core/include/mirai-core/BaseSource.h b/external/mirai-core/include/mirai-core/BaseSource.h index f6f2204..0b7ae5b 100644 --- a/external/mirai-core/include/mirai-core/BaseSource.h +++ b/external/mirai-core/include/mirai-core/BaseSource.h @@ -39,7 +39,9 @@ class BaseSource const std::string &getName() const; void addDay(const DayData &dayData); + void addUnscheduledTask(const TaskItemData &taskData); Day *day(const Date &date); + std::vector> *unscheduledTasks(); std::vector> *days(); TaskItem *getTaskById(int taskId); Event *getEventById(int eventId); @@ -55,6 +57,7 @@ class BaseSource int id_ = nextId_++; std::string name_; std::vector> days_; + std::vector> unscheduledTasks_; bool isDirty_ = false; }; diff --git a/external/mirai-core/include/mirai-core/TasksView.h b/external/mirai-core/include/mirai-core/TasksView.h index b9cc47a..8cb57d1 100644 --- a/external/mirai-core/include/mirai-core/TasksView.h +++ b/external/mirai-core/include/mirai-core/TasksView.h @@ -33,6 +33,7 @@ class TasksView TasksView(Mirai *mirai); FilteredDay &operator[](int index); + std::vector &filteredUnscheduledTasks(); size_t count() const; void update(); @@ -49,6 +50,7 @@ class TasksView private: std::vector filteredDays; + std::vector filteredUnscheduledTasks_; Mirai *mirai; Tags tagsFilter; diff --git a/external/mirai-core/include/mirai-core/TodoMd.h b/external/mirai-core/include/mirai-core/TodoMd.h index 5f95c0e..de4297f 100644 --- a/external/mirai-core/include/mirai-core/TodoMd.h +++ b/external/mirai-core/include/mirai-core/TodoMd.h @@ -6,6 +6,7 @@ #pragma once +#include "BaseSource.h" #include "DateTime.h" #include "Day.h" #include "Event.h" @@ -23,13 +24,13 @@ namespace mirai struct MiraiMarkdownFormatParseResult { std::string name; std::vector days; + std::vector unscheduledTasks; }; class TodoMdFormat { public: - static std::string - stringify(const std::string &name, const std::vector> &days); + static std::string stringify(BaseSource &source); static MiraiMarkdownFormatParseResult parse(const std::string &content); diff --git a/external/mirai-core/src/BaseSource.cpp b/external/mirai-core/src/BaseSource.cpp index 5e80d8a..6feae25 100644 --- a/external/mirai-core/src/BaseSource.cpp +++ b/external/mirai-core/src/BaseSource.cpp @@ -46,12 +46,23 @@ std::vector> *BaseSource::days() return &days_; } +std::vector> *BaseSource::unscheduledTasks() +{ + return &unscheduledTasks_; +} + void BaseSource::addDay(const DayData &dayData) { days_.push_back(std::make_unique(this, dayData)); setDirty(true); } +void BaseSource::addUnscheduledTask(const TaskItemData &taskData) +{ + unscheduledTasks_.push_back(std::make_unique(this, nullptr, taskData)); + setDirty(true); +} + TaskItem *BaseSource::getTaskById(int taskId) { for (auto &day : days_) { @@ -68,6 +79,11 @@ TaskItem *BaseSource::getTaskById(int taskId) } } } + for (auto &task : *unscheduledTasks()) { + if (task->id() == taskId) { + return task.get(); + } + } return nullptr; } diff --git a/external/mirai-core/src/StdFileSource.cpp b/external/mirai-core/src/StdFileSource.cpp index 4b30c55..e3241bd 100644 --- a/external/mirai-core/src/StdFileSource.cpp +++ b/external/mirai-core/src/StdFileSource.cpp @@ -21,7 +21,7 @@ void StdFileSource::save() throw std::runtime_error("can't create " + getPath()); } - const std::string content = TodoMdFormat::stringify(getName(), *days()); + const std::string content = TodoMdFormat::stringify(*this); file << content; file.close(); @@ -53,6 +53,9 @@ void StdFileSource::load() TaskItem *newTask = newDay->createTask(taskData); } } + for (const auto &taskData : result.unscheduledTasks) { + addUnscheduledTask(taskData); + } setName(result.name); }; } // namespace mirai diff --git a/external/mirai-core/src/TasksView.cpp b/external/mirai-core/src/TasksView.cpp index c7cdfa1..063db6e 100644 --- a/external/mirai-core/src/TasksView.cpp +++ b/external/mirai-core/src/TasksView.cpp @@ -36,6 +36,11 @@ FilteredDay &TasksView::operator[](int index) return filteredDays.at(index); } +std::vector &TasksView::filteredUnscheduledTasks() +{ + return filteredUnscheduledTasks_; +} + size_t TasksView::count() const { return filteredDays.size(); @@ -45,12 +50,23 @@ void TasksView::update() { auto todayDate = std::format("{:%Y-%m-%d}", std::chrono::system_clock::now()); filteredDays.clear(); + filteredUnscheduledTasks_.clear(); for (auto &file : mirai->getSources()) { if (resourcesFilter.size() > 0 && !vectorUtils::contains(resourcesFilter, file->getName())) { continue; } + + for (auto &task : *file->unscheduledTasks()) { + if (shouldHideCompletedTasks() && task->getState() == DONE) { + continue; + } + if (tagsFilter.size() > 0 && !vectorUtils::containsAll(tagsFilter, task->getTags())) { + continue; + } + filteredUnscheduledTasks_.push_back(task.get()); + } for (auto &day : *file->days()) { FilteredDay filteredDay{.day = day.get()}; for (auto &task : *day->tasks()) { diff --git a/external/mirai-core/src/TodoMd.cpp b/external/mirai-core/src/TodoMd.cpp index 69a4e2d..694ac42 100644 --- a/external/mirai-core/src/TodoMd.cpp +++ b/external/mirai-core/src/TodoMd.cpp @@ -132,13 +132,14 @@ std::string TodoMdFormat::taskToString(const TaskItem &task) return str; } -std::string -TodoMdFormat::stringify(const std::string &name, const std::vector> &days) +std::string TodoMdFormat::stringify(BaseSource &source) { + const std::string &name = source.getName(); + const std::vector> *days = source.days(); std::string result = "# " + name + "\n\n"; std::string currentDate = ""; - for (const auto &day : days) { + for (const auto &day : *days) { auto &date = day->getDate(); result += "## " + std::format("{:02d}-{:02d}-{:02d}", date.year, date.month, date.day) + "\n\n"; @@ -162,6 +163,13 @@ TodoMdFormat::stringify(const std::string &name, const std::vectorsize() > 0) { + result += "## Unscheduled\n\n"; + for (const auto &task : *(source.unscheduledTasks())) { + result += taskToString(*task) + '\n'; + } + result += '\n'; + } return result; }; @@ -181,6 +189,7 @@ MiraiMarkdownFormatParseResult TodoMdFormat::parse(const std::string &content) std::string currentDateString = ""; std::vector daysData; + std::vector unscheduledTasks; mirai::DayData *currentDay = nullptr; mirai::EventData *currentEvent = nullptr; @@ -190,6 +199,10 @@ MiraiMarkdownFormatParseResult TodoMdFormat::parse(const std::string &content) while (std::getline(contentStream, line)) { if (std::string_view{line.data(), 3} == "## ") { currentDateString = line.substr(3); + if (currentDateString == "Unscheduled") { + currentDay = nullptr; + continue; + } auto dateOpt = mirai::stringToDate(currentDateString); if (!dateOpt.has_value()) { throw std::runtime_error("Malformated date"); @@ -210,6 +223,8 @@ MiraiMarkdownFormatParseResult TodoMdFormat::parse(const std::string &content) currentEvent->tasks.push_back(taskItemData); } else if (currentDay) { currentDay->tasks.push_back(taskItemData); + } else { + unscheduledTasks.push_back(taskItemData); } } else if (cpputils::string::trim(line) == "") { currentEvent = nullptr; @@ -219,6 +234,6 @@ MiraiMarkdownFormatParseResult TodoMdFormat::parse(const std::string &content) gelinesDuration.printTimeElapsed("getlinesDuration"); stringToTaskDuration.printTimeElapsed("stringToTaskDuration"); readMdFormatDuration.printTimeElapsed("Reading MD File duration"); - return {.name = name, .days = daysData}; + return {.name = name, .days = daysData, .unscheduledTasks = unscheduledTasks}; } } // namespace mirai diff --git a/src/UiState.cpp b/src/UiState.cpp index 32721e4..58abc78 100644 --- a/src/UiState.cpp +++ b/src/UiState.cpp @@ -36,6 +36,7 @@ UiState::UiState(mirai::Mirai *miraiInstance) : miraiInstance_(miraiInstance), v { sources_ = std::make_shared>(); days_ = std::make_shared>(); + unscheduledTasks_ = std::make_shared>(); tags_ = std::make_shared>(); taskWindow_->global().set_sources(sources_); @@ -86,7 +87,7 @@ void UiState::setupTaskWindowCallbacks() mirai::TodoMdFormat::stringToTask("- [ ] " + std::string(newTaskData.title), dateStr); task->setText(taskData.text); - if (task->day()->getDate() != SlintDateToMiraiDate(date)) { + if (task->day() && task->day()->getDate() != SlintDateToMiraiDate(date)) { auto newDate = source->day(SlintDateToMiraiDate(date)); newDate->createTask({ .text = taskData.text, @@ -109,7 +110,9 @@ void UiState::setupTaskWindowCallbacks() auto task = mirai::TodoMdFormat::stringToTask("- [ ] " + std::string(newTaskData.title), dateStr); - if (newTaskData.eventId == -1) { + if (!newTaskData.scheduled) { + source->addUnscheduledTask(task); + } else if (newTaskData.eventId == -1) { auto day = source->day(SlintDateToMiraiDate(date)); assert(day); day->createTask(task); @@ -280,7 +283,12 @@ void UiState::setupCallbacks() taskWindow_->set_taskSourceIndex(sourceId); taskWindow_->set_taskId(task->id()); taskWindow_->set_taskTitle(slint::SharedString(task->getText())); - taskWindow_->set_taskDate(MiraiDateToSlintDate(task->day()->getDate())); + if (task->day() != nullptr) { + taskWindow_->set_scheduled(true); + taskWindow_->set_taskDate(MiraiDateToSlintDate(task->day()->getDate())); + } else { + taskWindow_->set_scheduled(false); + } taskWindow_->show(); }); @@ -411,6 +419,26 @@ void UiState::reloadTasks() } days_ = slintDays; mainWindow_->global().set_visible_tasks(days_); + + unscheduledTasks_->clear(); + for (int taskIndex = 0; taskIndex < view_.filteredUnscheduledTasks().size(); ++taskIndex) { + auto &task = view_.filteredUnscheduledTasks().at(taskIndex); + std::vector tags; + std::transform( + task->getTags().begin(), task->getTags().end(), std::back_inserter(tags), + [&](const std::string &tag) { + return slint::SharedString(tag); + } + ); + unscheduledTasks_->push_back({ + .sourceId = task->source()->id(), + .id = task->id(), + .title = slint::SharedString(task->getText()), + .checked = task->getState() == mirai::DONE, + .tags = std::make_shared>(tags), + }); + } + mainWindow_->global().set_unscheduled_tasks(unscheduledTasks_); } void UiState::reloadSources() diff --git a/src/UiState.h b/src/UiState.h index 3c1da5d..9a35684 100644 --- a/src/UiState.h +++ b/src/UiState.h @@ -32,6 +32,7 @@ class UiState std::shared_ptr> sources_; std::shared_ptr> tags_; std::shared_ptr> days_; + std::shared_ptr> unscheduledTasks_; slint::ComponentHandle mainWindow_ = AppWindow::create(); slint::ComponentHandle taskWindow_ = TaskWindow::create(); diff --git a/ui/Backend.slint b/ui/Backend.slint index babac59..e5029d0 100644 --- a/ui/Backend.slint +++ b/ui/Backend.slint @@ -38,6 +38,7 @@ export global Backend { in-out property<[string]> sources; in-out property<[string]> tags; in-out property<[Day]> visible_tasks; + in-out property<[TaskData]> unscheduled-tasks; callback task_clicked(int, int); callback source_clicked(int); diff --git a/ui/MainView.slint b/ui/MainView.slint index 5b3db7a..e0cbb52 100644 --- a/ui/MainView.slint +++ b/ui/MainView.slint @@ -107,6 +107,32 @@ export component MainView inherits Rectangle { } } } + if Backend.unscheduled-tasks.length > 0 : VerticalLayout { + Rectangle { + background: Palette.card-background; + border-radius: 8px; + VerticalLayout { + padding: 16px; + HorizontalLayout { + alignment: start; + VText { + text: "Unscheduled"; + color: Palette.foreground; + font-size: 1.2rem; + } + } + for task[taskIndex] in Backend.unscheduled-tasks: VerticalLayout { + padding-top: taskIndex == 0 ? 16px : 0px; + padding-bottom: 8px; + TaskLine { + task: task; + source-index: task.sourceId; + task-index: task.id; + } + } + } + } + } } } } diff --git a/ui/windows/TaskWindow.slint b/ui/windows/TaskWindow.slint index 2f16626..e96d44a 100644 --- a/ui/windows/TaskWindow.slint +++ b/ui/windows/TaskWindow.slint @@ -1,11 +1,12 @@ import { Date, ComboBox } from "std-widgets.slint"; -import { VTextInput, VButton, VDatePicker, VText, Palette } from "@vynui"; +import { VTextInput, VButton, VDatePicker, VText, VCheckBox, Palette } from "@vynui"; import { Backend } from "../Backend.slint"; export struct NewTaskData { sourceId: int, eventId: int, title: string, + scheduled: bool, date: Date } @@ -13,6 +14,7 @@ export struct SaveTaskData { sourceId: int, id: int, title: string, + scheduled: bool, date: Date, } @@ -28,6 +30,7 @@ export component TaskWindow inherits Window { in-out property taskId: -1; in-out property eventId: -1; + in-out property scheduled <=> scheduledInput.checked; in-out property taskDate <=> taskDateInput.date; in-out property taskTitle <=> taskTitleInput.text; in-out property taskSourceIndex <=> sourceInput.current-index; @@ -53,9 +56,13 @@ export component TaskWindow inherits Window { accepted => { button.clicked() } } + scheduledInput := VCheckBox { + text: "Scheduled"; + } + taskDateInput := VDatePicker { label: "Date"; - enabled: eventId == -1; + enabled: eventId == -1 && scheduledInput.checked; } button := VButton { @@ -66,6 +73,7 @@ export component TaskWindow inherits Window { sourceId: taskSourceIndex, eventId: eventId, title: taskTitle, + scheduled: scheduled, date: taskDate, }); } else { @@ -73,6 +81,7 @@ export component TaskWindow inherits Window { sourceId: taskSourceIndex, id: taskId, title: taskTitle, + scheduled: scheduled, date: taskDate, }); }