diff --git a/external/mirai-core/src/MarkdownDataProvider.Parser.cpp b/external/mirai-core/src/MarkdownDataProvider.Parser.cpp index d96518d..f793e6c 100644 --- a/external/mirai-core/src/MarkdownDataProvider.Parser.cpp +++ b/external/mirai-core/src/MarkdownDataProvider.Parser.cpp @@ -5,6 +5,7 @@ */ #include "MarkdownDataProvider.h" +#include "DataProvider.h" #include "cpp-utils/debug.h" #include "utils.h" #include @@ -109,6 +110,19 @@ std::string markdown_data_provider::to_markdown() int next_task_index = 0; int next_event_index = 0; + std::sort(_data.tasks.begin(), _data.tasks.end(), [](const task_data &t1, const task_data &t2) { + if (!t1.due_date.has_value() && !t2.due_date.has_value()) { + return false; + } + if (t1.due_date.has_value() && !t2.due_date.has_value()) { + return true; + } + if (!t1.due_date.has_value() && t2.due_date.has_value()) { + return false; + } + return t1.due_date < t2.due_date; + }); + while (next_event_index < _data.events.size() || next_task_index < _data.tasks.size()) { std::optional current_date = std::nullopt; // First we find the lowest date between the events and the tasks @@ -117,7 +131,9 @@ std::string markdown_data_provider::to_markdown() } if (_data.tasks.size() > 0 && next_task_index < _data.tasks.size()) { auto &task = _data.tasks.at(next_task_index); - if (task.due_date.has_value() && task.due_date < current_date.value()) { + if ((task.due_date.has_value() && current_date.has_value() + && task.due_date < current_date) + || !current_date.has_value()) { current_date = _data.tasks.at(next_task_index).due_date; } } @@ -132,12 +148,13 @@ std::string markdown_data_provider::to_markdown() } else { result += "## Unscheduled\n\n"; } - + bool should_add_newline_before_tasks = false; while (next_event_index < _data.events.size()) { auto ¤t_event = _data.events.at(next_event_index); if (current_event.date != current_date) { break; } + should_add_newline_before_tasks = true; auto &start = current_event.starts_at; auto &end = current_event.ends_at; result += "> " @@ -147,16 +164,19 @@ std::string markdown_data_provider::to_markdown() ) + "\n"; next_event_index++; - current_event = _data.events.at(next_event_index); } + + if (should_add_newline_before_tasks) { + result += "\n"; + } + while (next_task_index < _data.tasks.size()) { auto ¤t_task = _data.tasks.at(next_task_index); if (current_task.due_date != current_date) { break; } - taskToString(current_task); - next_event_index++; - current_task = _data.tasks.at(next_task_index); + result += taskToString(current_task) + "\n"; + next_task_index++; } result += '\n'; } diff --git a/external/mirai-core/src/MarkdownDataProvider.cpp b/external/mirai-core/src/MarkdownDataProvider.cpp index ce676bc..dac2331 100644 --- a/external/mirai-core/src/MarkdownDataProvider.cpp +++ b/external/mirai-core/src/MarkdownDataProvider.cpp @@ -30,13 +30,13 @@ std::string markdown_data_provider::name() const void markdown_data_provider::save() { + const std::string content = to_markdown(); + std::ofstream file(_file_path); if (!file.is_open()) { throw std::runtime_error("can't create " + _file_path); } - const std::string content = to_markdown(); - file << content; file.close(); set_dirty(false); @@ -67,9 +67,8 @@ task_data markdown_data_provider::insert_task(const task_data &taskData) void markdown_data_provider::update_task(int taskId, updatable_task_data updateData) { - auto taskData = ptrFindFirst(_data.tasks, [&](const task_data &task) { - return task.id == taskId; - }); + auto taskData = + ptrFindFirst(_data.tasks, [&](const task_data &task) { return task.id == taskId; }); assert(taskData != nullptr); // Shouldn't be possible if (updateData.title.has_value()) { taskData->title = updateData.title.value(); @@ -85,23 +84,17 @@ void markdown_data_provider::update_task(int taskId, updatable_task_data updateD std::optional markdown_data_provider::get_task_by_id(int taskId) { - return findFirst(_data.tasks, [&](const task_data &task) { - return task.id == taskId; - }); + return findFirst(_data.tasks, [&](const task_data &task) { return task.id == taskId; }); } std::vector markdown_data_provider::get_tasks_without_date() { - return findAll(_data.tasks, [](const task_data &t) { - return t.due_date == std::nullopt; - }); + return findAll(_data.tasks, [](const task_data &t) { return t.due_date == std::nullopt; }); } std::vector markdown_data_provider::get_tasks_by_date(Date date) { - return findAll(_data.tasks, [&](const task_data &t) { - return t.due_date == date; - }); + return findAll(_data.tasks, [&](const task_data &t) { return t.due_date == date; }); } std::vector markdown_data_provider::get_tasks() @@ -114,9 +107,7 @@ void markdown_data_provider::remove_task_by_id(int taskId) _data.tasks.erase( std::remove_if( _data.tasks.begin(), _data.tasks.end(), - [&](const task_data &task) { - return task.id == taskId; - } + [&](const task_data &task) { return task.id == taskId; } ), _data.tasks.end() ); @@ -132,9 +123,8 @@ event_data markdown_data_provider::insertEvent(const event_data &eventData) void markdown_data_provider::update_event(int eventId, updatable_event_data updateData) { - auto eventData = ptrFindFirst(_data.events, [&](const event_data &event) { - return event.id == eventId; - }); + auto eventData = + ptrFindFirst(_data.events, [&](const event_data &event) { return event.id == eventId; }); assert(eventData != nullptr); // Shouldn't be possible if (updateData.title.has_value()) { eventData->title = updateData.title.value(); @@ -156,9 +146,7 @@ void markdown_data_provider::remove_event_by_id(int eventId) _data.events.erase( std::remove_if( _data.events.begin(), _data.events.end(), - [&](const event_data &event) { - return event.id == eventId; - } + [&](const event_data &event) { return event.id == eventId; } ), _data.events.end() ); @@ -167,16 +155,12 @@ void markdown_data_provider::remove_event_by_id(int eventId) std::optional markdown_data_provider::get_event_by_id(int eventId) { - return findFirst(_data.events, [&](const event_data &event) { - return event.id == eventId; - }); + return findFirst(_data.events, [&](const event_data &event) { return event.id == eventId; }); } std::vector markdown_data_provider::get_events_by_date(Date date) { - return findAll(_data.events, [&](const event_data &event) { - return event.date == date; - }); + return findAll(_data.events, [&](const event_data &event) { return event.date == date; }); } std::vector markdown_data_provider::get_events() diff --git a/src/components/TaskLine.slint b/src/components/TaskLine.slint index 5369bf5..2825d5b 100644 --- a/src/components/TaskLine.slint +++ b/src/components/TaskLine.slint @@ -13,8 +13,6 @@ export struct TaskLineEditData { export component TaskLine inherits VerticalLayout { in property title; - in property source-name; - in property source-color; in property hide-source-name; in property scheduled; in property date; @@ -92,10 +90,6 @@ export component TaskLine inherits VerticalLayout { root.toggle-check() } } - if !hide-source-name : source-name := VTag { - text-color: root.source-color; - text: "\{root.source-name}"; - } } } } diff --git a/src/modals/AddEventModal.slint b/src/modals/AddEventModal.slint index 0105ba2..629bbaa 100644 --- a/src/modals/AddEventModal.slint +++ b/src/modals/AddEventModal.slint @@ -4,6 +4,8 @@ import { VButton, VText, VDatePicker } from "../../external/selenite/components/ import { AppActions } from "../shared/Actions.slint"; import { Modal } from "../../external/selenite/components/Modal.slint"; import { VTimePicker } from "../../external/selenite/components/TimePicker.slint"; +import { ComboBox } from "std-widgets.slint"; +import { AppModels } from "../shared/Models.slint"; export component AddEventModal inherits Modal { public function open() { @@ -13,6 +15,9 @@ export component AddEventModal inherits Modal { VerticalLayout { padding: 16px; spacing: 8px; + sourceInput := ComboBox { + model: AppModels.available-sources-strings; + } titleInput := VTextInput { label: "title"; } @@ -31,10 +36,11 @@ export component AddEventModal inherits Modal { text: "Create"; clicked => { AppActions.create-event({ + source-id: AppModels.get-source-id-from-name(sourceInput.current-value), title: titleInput.text, date: dateInput.date, - startsAt: startTimeInput.time, - endsAt: endTimeInput.time + starts-at: startTimeInput.time, + ends-at: endTimeInput.time }); root.close(); } diff --git a/src/shared/Actions.slint b/src/shared/Actions.slint index f0f003e..9330197 100644 --- a/src/shared/Actions.slint +++ b/src/shared/Actions.slint @@ -22,10 +22,11 @@ struct OpenNewTaskFormParams { } export struct CreateEventParams { + source-id: int, title: string, date: Date, - startsAt: Time, - endsAt: Time + starts-at: Time, + ends-at: Time } export global AppActions { diff --git a/src/shared/Models.slint b/src/shared/Models.slint index ba26e08..35cf012 100644 --- a/src/shared/Models.slint +++ b/src/shared/Models.slint @@ -62,6 +62,12 @@ export struct SidebarView { all-active: bool } +export struct AvailableSource { + id: int, + name: string, + color: color +} + export enum CalendarDateDisplayFormat { Relative, Normal @@ -71,6 +77,8 @@ export global AppModels { in-out property sidebar-view; in-out property tasks-view; in-out property calendar-view; + in-out property<[AvailableSource]> available-sources; + in-out property<[string]> available-sources-strings; callback get-source-id-from-name(string) -> int; pure callback get-source-name-from-id(int) -> string; diff --git a/src/windows/AppWindow/AppWindow.cpp b/src/windows/AppWindow/AppWindow.cpp index e35a0ba..f107cc8 100644 --- a/src/windows/AppWindow/AppWindow.cpp +++ b/src/windows/AppWindow/AppWindow.cpp @@ -140,22 +140,15 @@ void AppWindow::setupCallbacks() }); actions().on_source_clicked([&](int index) { - // index with value -1 is equal to no selection, aka "All" - if (index == -1) { - show_all_sources(); - // view_.setAllSources(); + const mirai::source *const source = miraiInstance_->getSourceById(index); + assert(source != nullptr); + if (should_show_source(*source)) { + hide_source(*source); } else { - hide_all_sources(); - // view_.removeSources(); - const mirai::source *source = miraiInstance_->getSourceById(index); show_source(*source); - // view_.addSource(*source); } - // models().set_default_source_index(index == -1 ? 0 : index); + update_views(); - // view_.update(); - reloadSources(); - reloadTasks(); }); actions().on_add_source([&](slint::SharedString name, slint::SharedString path) { @@ -248,19 +241,16 @@ void AppWindow::setupCallbacks() actions().on_create_event([&](ui::CreateEventParams newEventParams) { const ui::Date &date = newEventParams.date; const std::string dateStr = SlintDateToStdString(date); - // const auto sourceId = models().get_default_source_index(); - // auto source = miraiInstance_->getSourceById(sourceId == -1 ? 0 : sourceId); + auto source = miraiInstance_->getSourceById(newEventParams.source_id); - /* source->create_event({*/ - /*.title = std::string(newEventParams.title),*/ - /*.date = SlintDateToMiraiDate(newEventParams.date),*/ - /*.starts_at = SlintTimeToMiraiTime(newEventParams.startsAt),*/ - /*.ends_at = SlintTimeToMiraiTime(newEventParams.endsAt),*/ - /*});*/ + source->create_event({ + .title = std::string(newEventParams.title), + .date = SlintDateToMiraiDate(newEventParams.date), + .starts_at = SlintTimeToMiraiTime(newEventParams.starts_at), + .ends_at = SlintTimeToMiraiTime(newEventParams.ends_at), + }); miraiInstance_->save(); - // view_.update(); update_views(); - reloadTasks(); }); } @@ -432,22 +422,48 @@ void AppWindow::update_views() update_sidebar_view(); update_calendar_view(); update_tasks_view(); + update_available_sources(); } +void AppWindow::update_available_sources() +{ + const auto &sources = miraiInstance_->getSources(); + auto available_sources = std::make_shared>(); + auto available_sources_strings = std::make_shared>(); + for (auto &source : sources) { + mirai::markdown_data_provider *sourceProvider = + dynamic_cast(source->data_provider()); + const auto color = + selenite::hexStringToColor(miraiInstance_->getSourceById(source->id)->color()); + auto source_color = slint::Color::from_rgb_uint8(color.r, color.g, color.b); + + available_sources->push_back( + {.id = source->id, .name = slint::SharedString(source->name()), .color = source_color} + ); + available_sources_strings->push_back(slint::SharedString(source->name())); + } + + models().set_available_sources(available_sources); + models().set_available_sources_strings(available_sources_strings); +}; + void AppWindow::update_sidebar_view() { const auto &sources = miraiInstance_->getSources(); auto new_sources = std::make_shared>(); - std::println("sources"); for (auto &source : sources) { - std::println("source: {}", source->name()); mirai::markdown_data_provider *sourceProvider = dynamic_cast(source->data_provider()); - - new_sources->push_back( - {.id = source->id, .name = slint::SharedString(source->name()), .active = false} - ); + const auto color = + selenite::hexStringToColor(miraiInstance_->getSourceById(source->id)->color()); + auto source_color = slint::Color::from_rgb_uint8(color.r, color.g, color.b); + new_sources->push_back({ + .id = source->id, + .name = slint::SharedString(source->name()), + .color = source_color, + .active = _source_filters.contains(source->id) && _source_filters.at(source->id), + }); } _sidebar_view.sources = new_sources; @@ -457,7 +473,7 @@ void AppWindow::update_sidebar_view() } std::vector -get_all_events_from_sources(std::vector> &sources) +merge_all_events_from_sources(std::vector> &sources) { std::vector all_events; for (auto &source : sources) { @@ -482,7 +498,7 @@ void AppWindow::update_calendar_view() auto new_slint_dates = std::make_shared>(); auto &sources = miraiInstance_->getSources(); - auto all_events = get_all_events_from_sources(sources); + auto all_events = merge_all_events_from_sources(sources); for (int day_index = 0; day_index < 7; ++day_index) { std::chrono::year_month_day next_date = @@ -501,6 +517,11 @@ void AppWindow::update_calendar_view() auto events_for_date = get_events_for_date(all_events, current_date); for (int event_index = 0; event_index < events_for_date.size(); ++event_index) { auto ¤t_event = events_for_date.at(event_index); + // TODO directly remove the source instead of this workaround after data layer refacto + auto source = miraiInstance_->getSourceById(current_event.source_id()); + if (!should_show_source(*source)) { + continue; + } new_slint_events->push_back( ui::CalendarViewEvent{ .source_id = current_event.source_id(), @@ -613,26 +634,29 @@ void AppWindow::update_tasks_view() } auto new_slint_sources = std::make_shared>(); - for (auto &source : current_date_group.sources) { + for (auto &source_group : current_date_group.sources) { auto new_slint_source = ui::TasksViewSource(); auto new_slint_tasks = std::make_shared>(); - for (auto &task : source.tasks) { + auto source = miraiInstance_->getSourceById(source_group.id); + if (!should_show_source(*source)) { + continue; + } + for (auto &task : source_group.tasks) { if (task.has_due_date() && task.due_date() < today) { continue; } new_slint_tasks->push_back( {.id = task.id(), - .source_id = source.id, + .source_id = source_group.id, .title = slint::SharedString(task.title()), .checked = task.checked()} ); } new_slint_source.tasks = new_slint_tasks; - new_slint_source.id = source.id; + new_slint_source.id = source_group.id; new_slint_source.name = - slint::SharedString(miraiInstance_->getSourceById(source.id)->name()); - const auto color = - selenite::hexStringToColor(miraiInstance_->getSourceById(source.id)->color()); + slint::SharedString(miraiInstance_->getSourceById(source_group.id)->name()); + const auto color = selenite::hexStringToColor(source->color()); new_slint_source.color = slint::Color::from_rgb_uint8(color.r, color.g, color.b); if (new_slint_source.tasks->row_count() > 0) { new_slint_sources->push_back(new_slint_source); @@ -659,10 +683,26 @@ void AppWindow::update_tasks_view() void AppWindow::show_source(const mirai::source &source) { + _source_filters.insert_or_assign(source.id, true); +} + +void AppWindow::hide_source(const mirai::source &source) +{ + _source_filters.insert_or_assign(source.id, false); +} + +bool AppWindow::should_show_source(const mirai::source &source) const +{ + const int source_id = source.id; + return _source_filters.contains(source_id) && _source_filters.at(source_id); } void AppWindow::show_all_sources() { + auto &sources = miraiInstance_->getSources(); + for (auto &source : sources) { + show_source(*source); + } } void AppWindow::hide_all_sources() diff --git a/src/windows/AppWindow/AppWindow.h b/src/windows/AppWindow/AppWindow.h index f02af42..b30ad28 100644 --- a/src/windows/AppWindow/AppWindow.h +++ b/src/windows/AppWindow/AppWindow.h @@ -10,6 +10,7 @@ #include "mirai-core/Source.h" #include "slint.h" #include "ui.h" +#include #include class AppWindow @@ -28,10 +29,13 @@ class AppWindow void update_sidebar_view(); void update_calendar_view(); void update_tasks_view(); + void update_available_sources(); void show_all_sources(); void show_source(const mirai::source &source); + void hide_source(const mirai::source &source); void hide_all_sources(); + bool should_show_source(const mirai::source &source) const; void show_completed_tasks(); void hide_completed_tasks(); @@ -50,6 +54,6 @@ class AppWindow mirai::Mirai *miraiInstance_; - std::vector _source_filters; + std::unordered_map _source_filters; bool _should_hide_completed_tasks; }; diff --git a/src/windows/AppWindow/views/SideBar.slint b/src/windows/AppWindow/views/SideBar.slint index ee6f4ba..2432145 100644 --- a/src/windows/AppWindow/views/SideBar.slint +++ b/src/windows/AppWindow/views/SideBar.slint @@ -43,20 +43,32 @@ export component SideBar inherits Rectangle { VerticalLayout { vertical-stretch: 1; spacing: 4px; - ToggleButton { - text: "All"; - text-alignment: left; - active: AppModels.sidebar-view.all-active; - clicked => { AppActions.source-clicked(-1) } + for source[index] in AppModels.sidebar-view.sources: HorizontalLayout { + alignment: space-between; + VText { + text: source.name; + } + Rectangle { + height: 16px; + width: 16px; + border-color: source.color; + border-width: 1px; + border-radius: 4px; - - } - for source[index] in AppModels.sidebar-view.sources: ToggleButton { - text: source.name; - text-alignment: left; - active: source.active; - clicked => { AppActions.source-clicked(source.id) } - right-clicked => { editSourcePopup.edit(source.id) } + Rectangle { + x: 2px; + y: 2px; + background: source.active ? source.color : Colors.transparent; + border-radius: 2px; + width: parent.width - (self.x * 2); + height: parent.height - (self.y * 2); + } + + TouchArea { + clicked => { AppActions.source-clicked(source.id) } + //right-clicked => { editSourcePopup.edit(source.id) } + } + } } } VerticalLayout { diff --git a/src/windows/AppWindow/views/TasksView.slint b/src/windows/AppWindow/views/TasksView.slint index b1aa0dd..e38f35c 100644 --- a/src/windows/AppWindow/views/TasksView.slint +++ b/src/windows/AppWindow/views/TasksView.slint @@ -36,7 +36,7 @@ export component MainView inherits Rectangle { } CreateTaskOrEvent { - sources: AppModels.tasks-view.sources; + sources: AppModels.available-sources-strings; create-task(data) => { AppActions.create-task({ sourceId: data.sourceId, @@ -67,6 +67,7 @@ export component MainView inherits Rectangle { VText { text: day.no-date ? "Unscheduled" : Utils.format-date(day.due-date); color: day.is-late ? Palette.orange : Palette.foreground; + font-weight: 800; font-size: 1.2rem; } //VerticalLayout { @@ -83,35 +84,52 @@ export component MainView inherits Rectangle { //} } - for source[source-index] in day.sources: VerticalLayout { - VText { - text: source.name; + for source[source-index] in day.sources: Rectangle { + Rectangle { + x: 0; + y: 0; + width: 1px; + height: 100%; + background: source.color; + border-radius: 4px; } - if source.tasks.length > 0 : Rectangle { - VerticalLayout { - for task[taskIndex] in source.tasks: VerticalLayout { - padding-top: taskIndex == 0 ? 16px : 0px; - padding-bottom: 8px; - TaskLine { - title: task.title; - source-name: AppModels.get-source-name-from-id(task.source-id); - source-color: AppModels.get-source-color-from-id-as-color(task.source-id); - checked: task.checked; - allow-edit-date: true; - delete => { - AppActions.delete-task-clicked(task.source-id, task.id) - } - toggle-check => { - AppActions.task-clicked(task.source-id, task.id); - } - edited(data) => { - AppActions.save-task({ - id: task.id, - sourceId: task.source-id, - title: data.title, - scheduled: data.scheduled, - date: data.date - }) + Rectangle { + width: 100%; + height: 100%; + background: source.color; + opacity: 0.05; + } + VerticalLayout { + padding: 16px; + VText { + text: source.name; + color: source.color; + font-weight: 800; + } + if source.tasks.length > 0 : Rectangle { + VerticalLayout { + for task[taskIndex] in source.tasks: VerticalLayout { + padding-top: taskIndex == 0 ? 16px : 0px; + padding-bottom: 8px; + TaskLine { + title: task.title; + checked: task.checked; + allow-edit-date: true; + delete => { + AppActions.delete-task-clicked(task.source-id, task.id) + } + toggle-check => { + AppActions.task-clicked(task.source-id, task.id); + } + edited(data) => { + AppActions.save-task({ + id: task.id, + sourceId: task.source-id, + title: data.title, + scheduled: data.scheduled, + date: data.date + }) + } } } }