mirror of
https://codeberg.org/vyn/mirai.git
synced 2025-07-02 01:13:19 +00:00
Add a Calendar view on the right side
This commit is contained in:
parent
a80515ff90
commit
f1ac8a42d1
18 changed files with 406 additions and 130 deletions
|
@ -36,6 +36,8 @@ class MarkdownDataProvider : public DataProvider
|
||||||
std::string toMarkdown();
|
std::string toMarkdown();
|
||||||
MarkdownData parseMarkdown(const std::string &content);
|
MarkdownData parseMarkdown(const std::string &content);
|
||||||
|
|
||||||
|
std::string path() const;
|
||||||
|
|
||||||
void save() override;
|
void save() override;
|
||||||
void load() override;
|
void load() override;
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,8 @@ class Source
|
||||||
void load();
|
void load();
|
||||||
|
|
||||||
std::string name() const;
|
std::string name() const;
|
||||||
|
std::string type() const;
|
||||||
|
DataProvider *dataProvider();
|
||||||
|
|
||||||
void createTask(const createTaskParams &task);
|
void createTask(const createTaskParams &task);
|
||||||
void removeTask(const Task &task);
|
void removeTask(const Task &task);
|
||||||
|
|
|
@ -18,6 +18,11 @@
|
||||||
namespace mirai
|
namespace mirai
|
||||||
{
|
{
|
||||||
|
|
||||||
|
std::string MarkdownDataProvider::path() const
|
||||||
|
{
|
||||||
|
return filePath_;
|
||||||
|
}
|
||||||
|
|
||||||
std::string MarkdownDataProvider::name() const
|
std::string MarkdownDataProvider::name() const
|
||||||
{
|
{
|
||||||
return data.name;
|
return data.name;
|
||||||
|
|
13
external/mirai-core/src/Source.cpp
vendored
13
external/mirai-core/src/Source.cpp
vendored
|
@ -82,6 +82,8 @@ void Source::createEvent(const createEventParams &eventToCreate)
|
||||||
.id = generateUniqueId(),
|
.id = generateUniqueId(),
|
||||||
.dayId = day.value().id,
|
.dayId = day.value().id,
|
||||||
.title = eventToCreate.title,
|
.title = eventToCreate.title,
|
||||||
|
.startsAt = eventToCreate.startsAt,
|
||||||
|
.endsAt = eventToCreate.endsAt,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -144,4 +146,15 @@ std::string Source::name() const
|
||||||
{
|
{
|
||||||
return data->name();
|
return data->name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Source::type() const
|
||||||
|
{
|
||||||
|
// There is only 1 type for now
|
||||||
|
return "MarkdownFile";
|
||||||
|
}
|
||||||
|
|
||||||
|
DataProvider *Source::dataProvider()
|
||||||
|
{
|
||||||
|
return data;
|
||||||
|
}
|
||||||
} // namespace mirai
|
} // namespace mirai
|
||||||
|
|
3
external/mirai-core/src/View.cpp
vendored
3
external/mirai-core/src/View.cpp
vendored
|
@ -43,6 +43,9 @@ std::vector<Task> View::getTasksForDate(const Date &date)
|
||||||
|
|
||||||
std::vector<Event> View::getEventsForDate(const Date &date)
|
std::vector<Event> View::getEventsForDate(const Date &date)
|
||||||
{
|
{
|
||||||
|
if (!dates.contains(date)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
return dates.at(date).events;
|
return dates.at(date).events;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
external/selenite
vendored
2
external/selenite
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit cbab9dabe784bd3c799d23ead5d2b4d942ec4081
|
Subproject commit 33d6a9dee8437979b5a9bf5a716a4053f3ebf2fa
|
|
@ -5,10 +5,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "AppWindowBackend.h"
|
#include "AppWindowBackend.h"
|
||||||
|
#include "AppWindow.h"
|
||||||
#include "SeleniteSetup.h"
|
#include "SeleniteSetup.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "appwindow.h"
|
#include "mirai-core/DataProvider.h"
|
||||||
|
#include "mirai-core/DateTime.h"
|
||||||
#include "mirai-core/Day.h"
|
#include "mirai-core/Day.h"
|
||||||
|
#include "mirai-core/MarkdownDataProvider.h"
|
||||||
#include "mirai-core/Mirai.h"
|
#include "mirai-core/Mirai.h"
|
||||||
#include "slint.h"
|
#include "slint.h"
|
||||||
#include "slint_string.h"
|
#include "slint_string.h"
|
||||||
|
@ -34,6 +37,7 @@ AppWindowBackend::AppWindowBackend(mirai::Mirai *miraiInstance)
|
||||||
{
|
{
|
||||||
sources_ = std::make_shared<slint::VectorModel<ui::Source>>();
|
sources_ = std::make_shared<slint::VectorModel<ui::Source>>();
|
||||||
days_ = std::make_shared<slint::VectorModel<ui::Day>>();
|
days_ = std::make_shared<slint::VectorModel<ui::Day>>();
|
||||||
|
calendar_ = std::make_shared<slint::VectorModel<ui::CalendarDay>>();
|
||||||
unscheduledTasks_ = std::make_shared<slint::VectorModel<ui::TaskData>>();
|
unscheduledTasks_ = std::make_shared<slint::VectorModel<ui::TaskData>>();
|
||||||
tags_ = std::make_shared<slint::VectorModel<slint::SharedString>>();
|
tags_ = std::make_shared<slint::VectorModel<slint::SharedString>>();
|
||||||
auto sourcesNames = std::make_shared<slint::MapModel<ui::Source, slint::SharedString>>(
|
auto sourcesNames = std::make_shared<slint::MapModel<ui::Source, slint::SharedString>>(
|
||||||
|
@ -43,14 +47,23 @@ AppWindowBackend::AppWindowBackend(mirai::Mirai *miraiInstance)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const auto palette =
|
const auto palettePath = std::string(getenv("HOME")) + "/.config/evalyte/theme.json";
|
||||||
selenite::parseJson(std::string(getenv("HOME")) + "/.config/evalyte/theme.json");
|
const auto palette = selenite::parseJson(palettePath);
|
||||||
if (palette.has_value()) {
|
if (palette.has_value()) {
|
||||||
std::cerr << "Warning, no evalyte/theme.json found" << std::endl;
|
std::cerr << "Warning, no " << palettePath << " found" << std::endl;
|
||||||
setSelenitePalette(mainWindow_, palette.value());
|
setSelenitePalette(mainWindow_->global<ui::Palette>(), palette.value());
|
||||||
|
setSelenitePalette(settingsWindow_->global<ui::Palette>(), palette.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
mainWindow_->global<ui::Backend>().set_sources(sourcesNames);
|
mainWindow_->global<ui::Backend>().set_sources(sourcesNames);
|
||||||
|
settingsWindow_->global<ui::Backend>().set_sources(sourcesNames);
|
||||||
|
|
||||||
|
mainWindow_->global<ui::Backend>().set_sources_selected(sources_);
|
||||||
|
settingsWindow_->global<ui::Backend>().set_sources_selected(sources_);
|
||||||
|
|
||||||
|
mainWindow_->global<ui::Backend>().set_tags(tags_);
|
||||||
|
mainWindow_->global<ui::Backend>().set_days(days_);
|
||||||
|
mainWindow_->global<ui::Backend>().set_calendar(calendar_);
|
||||||
|
|
||||||
view_.setAllSources();
|
view_.setAllSources();
|
||||||
view_.update();
|
view_.update();
|
||||||
|
@ -92,10 +105,17 @@ void AppWindowBackend::setupUtilsCallbacks()
|
||||||
};
|
};
|
||||||
return std::format("{:%B %d}", chronoDate);
|
return std::format("{:%B %d}", chronoDate);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mainWindow_->global<ui::Backend>().on_format_date_relative([&](const ui::Date &date) {
|
||||||
|
return formatDateRelative(date);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppWindowBackend::setupCallbacks()
|
void AppWindowBackend::setupCallbacks()
|
||||||
{
|
{
|
||||||
|
mainWindow_->global<ui::Backend>().on_settings_clicked([&]() {
|
||||||
|
settingsWindow_->show();
|
||||||
|
});
|
||||||
mainWindow_->global<ui::Backend>().on_task_clicked([&](int sourceId, int taskId) {
|
mainWindow_->global<ui::Backend>().on_task_clicked([&](int sourceId, int taskId) {
|
||||||
auto source = miraiInstance_->getSourceById(sourceId);
|
auto source = miraiInstance_->getSourceById(sourceId);
|
||||||
assert(source);
|
assert(source);
|
||||||
|
@ -142,10 +162,6 @@ void AppWindowBackend::setupCallbacks()
|
||||||
reloadTasks();
|
reloadTasks();
|
||||||
});
|
});
|
||||||
|
|
||||||
mainWindow_->global<ui::Backend>().set_sources_selected(sources_);
|
|
||||||
mainWindow_->global<ui::Backend>().set_tags(tags_);
|
|
||||||
mainWindow_->global<ui::Backend>().set_days(days_);
|
|
||||||
|
|
||||||
mainWindow_->global<ui::Backend>().on_save_task([&](ui::SaveTaskData newTaskData) {
|
mainWindow_->global<ui::Backend>().on_save_task([&](ui::SaveTaskData newTaskData) {
|
||||||
auto source = miraiInstance_->getSourceById(newTaskData.sourceId);
|
auto source = miraiInstance_->getSourceById(newTaskData.sourceId);
|
||||||
assert(source);
|
assert(source);
|
||||||
|
@ -326,6 +342,42 @@ void AppWindowBackend::reloadTasks()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
mainWindow_->global<ui::Backend>().set_unscheduled_tasks(unscheduledTasks_);
|
mainWindow_->global<ui::Backend>().set_unscheduled_tasks(unscheduledTasks_);
|
||||||
|
|
||||||
|
calendar_->clear();
|
||||||
|
for (int dayIndex = 0; dayIndex < 7; ++dayIndex) {
|
||||||
|
std::chrono::year_month_day nextDate =
|
||||||
|
std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now()) +
|
||||||
|
std::chrono::days{dayIndex};
|
||||||
|
auto currentDate = mirai::Date{nextDate};
|
||||||
|
auto events = view_.getEventsForDate(currentDate);
|
||||||
|
auto slintEvents = std::make_shared<slint::VectorModel<ui::CalendarDayEvent>>();
|
||||||
|
auto relativeDaysDiff = std::chrono::duration_cast<std::chrono::days>(
|
||||||
|
std::chrono::sys_days(currentDate.toStdChrono()) -
|
||||||
|
std::chrono::sys_days(todayDate.toStdChrono())
|
||||||
|
)
|
||||||
|
.count();
|
||||||
|
if (relativeDaysDiff < 0 || relativeDaysDiff >= 3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::vector<mirai::Event> eventsForDate = view_.getEventsForDate(currentDate);
|
||||||
|
for (int eventIndex = 0; eventIndex < eventsForDate.size(); ++eventIndex) {
|
||||||
|
auto ¤tEvent = eventsForDate.at(eventIndex);
|
||||||
|
slintEvents->push_back(ui::CalendarDayEvent{
|
||||||
|
.title = slint::SharedString(currentEvent.title()),
|
||||||
|
.startsAt = MiraiTimeToSlintTime(currentEvent.startsAt()),
|
||||||
|
.endsAt = MiraiTimeToSlintTime(currentEvent.endsAt()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto calendarDay = ui::CalendarDay{
|
||||||
|
.events = slintEvents,
|
||||||
|
.date = MiraiDateToSlintDate(currentDate),
|
||||||
|
.header =
|
||||||
|
slint::SharedString(capitalize(formatDateRelative(MiraiDateToSlintDate(currentDate))
|
||||||
|
))
|
||||||
|
};
|
||||||
|
calendar_->push_back(calendarDay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppWindowBackend::reloadSources()
|
void AppWindowBackend::reloadSources()
|
||||||
|
@ -334,9 +386,12 @@ void AppWindowBackend::reloadSources()
|
||||||
bool noSourceSelected = miraiInstance_->getSources().size() == view_.activeSourceCount();
|
bool noSourceSelected = miraiInstance_->getSources().size() == view_.activeSourceCount();
|
||||||
for (const auto &source : miraiInstance_->getSources()) {
|
for (const auto &source : miraiInstance_->getSources()) {
|
||||||
bool isSourceSelected = view_.isSourceSelected(*source);
|
bool isSourceSelected = view_.isSourceSelected(*source);
|
||||||
|
mirai::MarkdownDataProvider *sourceProvider =
|
||||||
|
dynamic_cast<mirai::MarkdownDataProvider *>(source->dataProvider());
|
||||||
sources_->push_back(
|
sources_->push_back(
|
||||||
{.name = slint::SharedString(source->name()),
|
{.name = slint::SharedString(source->name()),
|
||||||
.selected = isSourceSelected && !noSourceSelected}
|
.selected = isSourceSelected && !noSourceSelected,
|
||||||
|
.path = slint::SharedString(sourceProvider->path())}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
mainWindow_->global<ui::Backend>().set_no_source_selected(noSourceSelected);
|
mainWindow_->global<ui::Backend>().set_no_source_selected(noSourceSelected);
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "appwindow.h"
|
#include "AppWindow.h"
|
||||||
#include "mirai-core/Mirai.h"
|
#include "mirai-core/Mirai.h"
|
||||||
#include "mirai-core/View.h"
|
#include "mirai-core/View.h"
|
||||||
#include "slint.h"
|
#include "slint.h"
|
||||||
|
@ -29,9 +29,11 @@ class AppWindowBackend
|
||||||
std::shared_ptr<slint::VectorModel<ui::Source>> sources_;
|
std::shared_ptr<slint::VectorModel<ui::Source>> sources_;
|
||||||
std::shared_ptr<slint::VectorModel<slint::SharedString>> tags_;
|
std::shared_ptr<slint::VectorModel<slint::SharedString>> tags_;
|
||||||
std::shared_ptr<slint::VectorModel<ui::Day>> days_;
|
std::shared_ptr<slint::VectorModel<ui::Day>> days_;
|
||||||
|
std::shared_ptr<slint::VectorModel<ui::CalendarDay>> calendar_;
|
||||||
std::shared_ptr<slint::VectorModel<ui::TaskData>> unscheduledTasks_;
|
std::shared_ptr<slint::VectorModel<ui::TaskData>> unscheduledTasks_;
|
||||||
|
|
||||||
slint::ComponentHandle<ui::AppWindow> mainWindow_ = ui::AppWindow::create();
|
slint::ComponentHandle<ui::AppWindow> mainWindow_ = ui::AppWindow::create();
|
||||||
|
slint::ComponentHandle<ui::SettingsWindow> settingsWindow_ = ui::SettingsWindow::create();
|
||||||
|
|
||||||
mirai::Mirai *miraiInstance_;
|
mirai::Mirai *miraiInstance_;
|
||||||
mirai::View view_;
|
mirai::View view_;
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "appwindow.h"
|
#include "AppWindow.h"
|
||||||
#include "selenite/palette.h"
|
#include "selenite/palette.h"
|
||||||
#include "slint_color.h"
|
#include "slint_color.h"
|
||||||
|
|
||||||
|
@ -15,10 +15,8 @@ slint::Color seleniteColorToSlint(const selenite::Color &color)
|
||||||
return slint::Color::from_rgb_uint8(color.r, color.g, color.b);
|
return slint::Color::from_rgb_uint8(color.r, color.g, color.b);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSelenitePalette(slint::ComponentHandle<ui::AppWindow> ui, const selenite::Palette &palette)
|
void setSelenitePalette(const ui::Palette &uiPalette, const selenite::Palette &palette)
|
||||||
{
|
{
|
||||||
auto &uiPalette = ui->global<ui::Palette>();
|
|
||||||
|
|
||||||
uiPalette.set_background(seleniteColorToSlint(palette.background));
|
uiPalette.set_background(seleniteColorToSlint(palette.background));
|
||||||
uiPalette.set_pane(seleniteColorToSlint(palette.pane));
|
uiPalette.set_pane(seleniteColorToSlint(palette.pane));
|
||||||
uiPalette.set_foreground(seleniteColorToSlint(palette.foreground));
|
uiPalette.set_foreground(seleniteColorToSlint(palette.foreground));
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
#include <cctype>
|
||||||
|
#include <format>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
std::string formatZeroPadding(const int number)
|
std::string formatZeroPadding(const int number)
|
||||||
{
|
{
|
||||||
|
@ -14,6 +17,29 @@ std::string formatZeroPadding(const int number)
|
||||||
return std::to_string(number);
|
return std::to_string(number);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string formatDateRelative(const ui::Date &date)
|
||||||
|
{
|
||||||
|
auto todayDate = mirai::Date(std::chrono::system_clock::now());
|
||||||
|
auto relativeDaysDiff = std::chrono::duration_cast<std::chrono::days>(
|
||||||
|
std::chrono::sys_days(SlintDateToMiraiDate(date).toStdChrono()) -
|
||||||
|
std::chrono::sys_days(todayDate.toStdChrono())
|
||||||
|
)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
if (relativeDaysDiff == 0) {
|
||||||
|
return std::string("today");
|
||||||
|
} else if (relativeDaysDiff == 1) {
|
||||||
|
return std::string("tomorrow");
|
||||||
|
}
|
||||||
|
return std::format("in {} days", relativeDaysDiff);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string capitalize(std::string str)
|
||||||
|
{
|
||||||
|
str[0] = static_cast<char>(toupper(str[0]));
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
std::string SlintDateToStdString(const ui::Date &date)
|
std::string SlintDateToStdString(const ui::Date &date)
|
||||||
{
|
{
|
||||||
return std::to_string(date.year) + "-" + formatZeroPadding(date.month) + "-" +
|
return std::to_string(date.year) + "-" + formatZeroPadding(date.month) + "-" +
|
||||||
|
|
|
@ -6,11 +6,13 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "appwindow.h"
|
#include "AppWindow.h"
|
||||||
#include "mirai-core/DateTime.h"
|
#include "mirai-core/DateTime.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
std::string formatZeroPadding(const int number);
|
std::string formatZeroPadding(const int number);
|
||||||
|
std::string formatDateRelative(const ui::Date &date);
|
||||||
|
std::string capitalize(std::string str);
|
||||||
std::string SlintDateToStdString(const ui::Date &date);
|
std::string SlintDateToStdString(const ui::Date &date);
|
||||||
mirai::Date SlintDateToMiraiDate(const ui::Date &date);
|
mirai::Date SlintDateToMiraiDate(const ui::Date &date);
|
||||||
ui::Date MiraiDateToSlintDate(const mirai::Date &date);
|
ui::Date MiraiDateToSlintDate(const mirai::Date &date);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Backend } from "Backend.slint";
|
||||||
import { Button, VerticalBox, CheckBox } from "std-widgets.slint";
|
import { Button, VerticalBox, CheckBox } from "std-widgets.slint";
|
||||||
import { SideBar } from "./components/SideBar.slint";
|
import { SideBar } from "./components/SideBar.slint";
|
||||||
import { MainView } from "MainView.slint";
|
import { MainView } from "MainView.slint";
|
||||||
|
import { SettingsWindow } from "SettingsWindow.slint";
|
||||||
import { Palette } from "@selenite";
|
import { Palette } from "@selenite";
|
||||||
|
|
||||||
export component AppWindow inherits Window {
|
export component AppWindow inherits Window {
|
||||||
|
@ -11,8 +12,6 @@ export component AppWindow inherits Window {
|
||||||
max-height: 4000px; // needed, otherwise the window wants to fit the content (on Swaywm)
|
max-height: 4000px; // needed, otherwise the window wants to fit the content (on Swaywm)
|
||||||
default-font-size: 16px;
|
default-font-size: 16px;
|
||||||
|
|
||||||
in-out property<string> test;
|
|
||||||
|
|
||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
SideBar {}
|
SideBar {}
|
||||||
MainView {
|
MainView {
|
||||||
|
@ -22,4 +21,4 @@ export component AppWindow inherits Window {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Backend, Palette } // Export to make it visible to the C++ backend
|
export { Backend, Palette, SettingsWindow } // Export to make it visible to the C++ backend
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Date, Time } from "std-widgets.slint";
|
import { Date, Time } from "std-widgets.slint";
|
||||||
|
import { CalendarDay } from "components/Calendar.slint";
|
||||||
|
|
||||||
export struct NewTaskData {
|
export struct NewTaskData {
|
||||||
sourceId: int,
|
sourceId: int,
|
||||||
|
@ -35,7 +36,8 @@ export struct SaveEventParams {
|
||||||
|
|
||||||
export struct Source {
|
export struct Source {
|
||||||
name: string,
|
name: string,
|
||||||
selected: bool
|
selected: bool,
|
||||||
|
path: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export struct TaskData {
|
export struct TaskData {
|
||||||
|
@ -78,11 +80,13 @@ export global Backend {
|
||||||
in-out property<bool> no-source-selected;
|
in-out property<bool> no-source-selected;
|
||||||
in-out property<[string]> tags;
|
in-out property<[string]> tags;
|
||||||
in-out property<[Day]> days;
|
in-out property<[Day]> days;
|
||||||
|
in-out property<[CalendarDay]> calendar;
|
||||||
in-out property<[TaskData]> unscheduled-tasks;
|
in-out property<[TaskData]> unscheduled-tasks;
|
||||||
|
|
||||||
callback task-clicked(int, int);
|
callback task-clicked(int, int);
|
||||||
callback source-clicked(int);
|
callback source-clicked(int);
|
||||||
callback tag-clicked(int);
|
callback tag-clicked(int);
|
||||||
|
callback settings-clicked();
|
||||||
|
|
||||||
callback open-new-task-form(OpenNewTaskFormParams);
|
callback open-new-task-form(OpenNewTaskFormParams);
|
||||||
callback open-edit-task-form(int, int);
|
callback open-edit-task-form(int, int);
|
||||||
|
@ -98,4 +102,6 @@ export global Backend {
|
||||||
callback save-event(SaveEventParams);
|
callback save-event(SaveEventParams);
|
||||||
|
|
||||||
pure callback format-date(Date) -> string;
|
pure callback format-date(Date) -> string;
|
||||||
|
pure callback format-date-relative(Date) -> string;
|
||||||
|
pure callback capitalize-string(string) -> string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Backend, TaskData } from "Backend.slint";
|
||||||
import { Button, VerticalBox, CheckBox, ScrollView, ComboBox } from "std-widgets.slint";
|
import { Button, VerticalBox, CheckBox, ScrollView, ComboBox } from "std-widgets.slint";
|
||||||
import { TaskLine } from "./components/TaskLine.slint";
|
import { TaskLine } from "./components/TaskLine.slint";
|
||||||
import { EventGroup } from "./components/EventGroup.slint";
|
import { EventGroup } from "./components/EventGroup.slint";
|
||||||
|
import { Calendar } from "./components/Calendar.slint";
|
||||||
import { VPopupIconMenu, VDatePicker, VTimePicker, VCheckBox, VButton, VTag, VText, VTextInput, Svg, Palette } from "@selenite";
|
import { VPopupIconMenu, VDatePicker, VTimePicker, VCheckBox, VButton, VTag, VText, VTextInput, Svg, Palette } from "@selenite";
|
||||||
import { NewTaskData, SaveTaskData } from "Backend.slint";
|
import { NewTaskData, SaveTaskData } from "Backend.slint";
|
||||||
import { CreateTaskOrEvent } from "components/CreateTaskOrEvent.slint";
|
import { CreateTaskOrEvent } from "components/CreateTaskOrEvent.slint";
|
||||||
|
@ -13,111 +14,113 @@ export component MainView inherits Rectangle {
|
||||||
private property<string> icon-not-visible: Svg.not-visible;
|
private property<string> icon-not-visible: Svg.not-visible;
|
||||||
private property<bool> completed-tasks-visible: false;
|
private property<bool> completed-tasks-visible: false;
|
||||||
|
|
||||||
VerticalLayout {
|
HorizontalLayout {
|
||||||
horizontal-stretch: 1;
|
|
||||||
padding: 16px;
|
|
||||||
spacing: 16px;
|
|
||||||
alignment: start;
|
|
||||||
HorizontalLayout {
|
|
||||||
horizontal-stretch: 1;
|
|
||||||
alignment: start;
|
|
||||||
spacing: 8px;
|
|
||||||
VButton {
|
|
||||||
text: "Show/Hide completed tasks";
|
|
||||||
clicked => {
|
|
||||||
Backend.toggle-show-completed-tasks();
|
|
||||||
completed-tasks-visible = !completed-tasks-visible;
|
|
||||||
}
|
|
||||||
icon-svg: completed-tasks-visible ? icon-visible : icon-not-visible;
|
|
||||||
icon-colorize: Palette.control-foreground;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
horizontal-stretch: 1;
|
|
||||||
background: Palette.background.brighter(0.2);
|
|
||||||
height: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateTaskOrEvent {
|
VerticalLayout {
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Flickable {
|
|
||||||
horizontal-stretch: 1;
|
horizontal-stretch: 1;
|
||||||
VerticalLayout {
|
padding: 16px;
|
||||||
|
spacing: 16px;
|
||||||
|
HorizontalLayout {
|
||||||
|
horizontal-stretch: 1;
|
||||||
alignment: start;
|
alignment: start;
|
||||||
spacing: 16px;
|
spacing: 8px;
|
||||||
if Backend.days.length == 0 && Backend.unscheduled-tasks.length == 0 : VText {
|
VButton {
|
||||||
text: "There is no task to show";
|
text: "Show/Hide completed tasks";
|
||||||
horizontal-alignment: center;
|
clicked => {
|
||||||
vertical-alignment: center;
|
Backend.toggle-show-completed-tasks();
|
||||||
|
completed-tasks-visible = !completed-tasks-visible;
|
||||||
|
}
|
||||||
|
icon-svg: completed-tasks-visible ? icon-visible : icon-not-visible;
|
||||||
|
icon-colorize: Palette.control-foreground;
|
||||||
}
|
}
|
||||||
for day[dayIndex] in Backend.days: VerticalLayout {
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
background: Palette.card-background;
|
horizontal-stretch: 1;
|
||||||
border-radius: 8px;
|
background: Palette.background.brighter(0.2);
|
||||||
VerticalLayout {
|
height: 1px;
|
||||||
padding: 16px;
|
}
|
||||||
HorizontalLayout {
|
|
||||||
alignment: start;
|
CreateTaskOrEvent {
|
||||||
VText {
|
|
||||||
text: Backend.format-date(day.date);
|
}
|
||||||
color: day.isLate ? Palette.orange : Palette.foreground;
|
|
||||||
font-size: 1.2rem;
|
Flickable {
|
||||||
}
|
horizontal-stretch: 1;
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
alignment: center;
|
alignment: start;
|
||||||
|
spacing: 16px;
|
||||||
|
if Backend.days.length == 0 && Backend.unscheduled-tasks.length == 0 : VText {
|
||||||
|
text: "There is no task to show";
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
for day[dayIndex] in Backend.days: VerticalLayout {
|
||||||
|
Rectangle {
|
||||||
|
background: Palette.card-background;
|
||||||
|
border-radius: 8px;
|
||||||
|
VerticalLayout {
|
||||||
|
padding: 16px;
|
||||||
|
HorizontalLayout {
|
||||||
|
alignment: start;
|
||||||
VText {
|
VText {
|
||||||
text: day.relativeDaysDiff == 0 ? " - today" :
|
text: Backend.format-date(day.date);
|
||||||
day.relativeDaysDiff == 1 ? " - tomorrow" :
|
color: day.isLate ? Palette.orange : Palette.foreground;
|
||||||
day.relativeDaysDiff == -1 ? " - yesterday" :
|
font-size: 1.2rem;
|
||||||
day.relativeDaysDiff > 0 ? " - in \{day.relativeDaysDiff} days" :
|
}
|
||||||
" - \{-day.relativeDaysDiff} days ago";
|
VerticalLayout {
|
||||||
color: Palette.foreground-hint;
|
alignment: center;
|
||||||
font-size: 1rem;
|
VText {
|
||||||
|
text: day.relativeDaysDiff == 0 ? " - today" :
|
||||||
|
day.relativeDaysDiff == 1 ? " - tomorrow" :
|
||||||
|
day.relativeDaysDiff == -1 ? " - yesterday" :
|
||||||
|
day.relativeDaysDiff > 0 ? " - in \{day.relativeDaysDiff} days" :
|
||||||
|
" - \{-day.relativeDaysDiff} days ago";
|
||||||
|
color: Palette.foreground-hint;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for event[eventIndex] in day.events: VerticalLayout {
|
||||||
|
padding-top: 16px;
|
||||||
|
EventGroup {
|
||||||
|
event: event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for task[taskIndex] in day.tasks: VerticalLayout {
|
||||||
|
padding-top: taskIndex == 0 ? 16px : 0px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
TaskLine {
|
||||||
|
task: task;
|
||||||
|
date: day.date;
|
||||||
|
source-index: task.sourceId;
|
||||||
|
task-index: task.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for event[eventIndex] in day.events: VerticalLayout {
|
|
||||||
padding-top: 16px;
|
|
||||||
EventGroup {
|
|
||||||
event: event;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for task[taskIndex] in day.tasks: VerticalLayout {
|
|
||||||
padding-top: taskIndex == 0 ? 16px : 0px;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
TaskLine {
|
|
||||||
task: task;
|
|
||||||
date: day.date;
|
|
||||||
source-index: task.sourceId;
|
|
||||||
task-index: task.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if Backend.unscheduled-tasks.length > 0 : VerticalLayout {
|
||||||
if Backend.unscheduled-tasks.length > 0 : VerticalLayout {
|
Rectangle {
|
||||||
Rectangle {
|
background: Palette.card-background;
|
||||||
background: Palette.card-background;
|
border-radius: 8px;
|
||||||
border-radius: 8px;
|
VerticalLayout {
|
||||||
VerticalLayout {
|
padding: 16px;
|
||||||
padding: 16px;
|
HorizontalLayout {
|
||||||
HorizontalLayout {
|
alignment: start;
|
||||||
alignment: start;
|
VText {
|
||||||
VText {
|
text: "Unscheduled";
|
||||||
text: "Unscheduled";
|
color: Palette.foreground;
|
||||||
color: Palette.foreground;
|
font-size: 1.2rem;
|
||||||
font-size: 1.2rem;
|
}
|
||||||
}
|
}
|
||||||
}
|
for task[taskIndex] in Backend.unscheduled-tasks: VerticalLayout {
|
||||||
for task[taskIndex] in Backend.unscheduled-tasks: VerticalLayout {
|
padding-top: taskIndex == 0 ? 16px : 0px;
|
||||||
padding-top: taskIndex == 0 ? 16px : 0px;
|
padding-bottom: 8px;
|
||||||
padding-bottom: 8px;
|
TaskLine {
|
||||||
TaskLine {
|
task: task;
|
||||||
task: task;
|
source-index: task.sourceId;
|
||||||
source-index: task.sourceId;
|
task-index: task.id;
|
||||||
task-index: task.id;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,5 +128,10 @@ export component MainView inherits Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Calendar {
|
||||||
|
init => { debug("cal len", Backend.calendar.length) }
|
||||||
|
days: Backend.calendar;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
29
ui/SettingsWindow.slint
Normal file
29
ui/SettingsWindow.slint
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { Backend } from "Backend.slint";
|
||||||
|
import { Button, VerticalBox, CheckBox } from "std-widgets.slint";
|
||||||
|
import { SideBar } from "./components/SideBar.slint";
|
||||||
|
import { MainView } from "MainView.slint";
|
||||||
|
import { VText, VTextInput, Palette } from "@selenite";
|
||||||
|
|
||||||
|
export component SettingsWindow inherits Window {
|
||||||
|
|
||||||
|
title: "Mirai - Settings";
|
||||||
|
min-height: 100px;
|
||||||
|
max-height: 4000px; // needed, otherwise the window wants to fit the content (on Swaywm)
|
||||||
|
default-font-size: 16px;
|
||||||
|
background: Palette.background;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
padding: 16px;
|
||||||
|
spacing: 8px;
|
||||||
|
for source[source-index] in Backend.sources-selected: VerticalLayout {
|
||||||
|
VText {
|
||||||
|
text: source.name;
|
||||||
|
}
|
||||||
|
VTextInput {
|
||||||
|
text: source.path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Backend, Palette } // Export to make it visible to the C++ backend
|
112
ui/components/Calendar.slint
Normal file
112
ui/components/Calendar.slint
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
import { ScrollView, Date, Time } from "std-widgets.slint";
|
||||||
|
import { VCheckBox, VButton, VActionButton, Svg, VTag, VPopupIconMenu, VText, Palette } from "@selenite";
|
||||||
|
import { Utils } from "../Utils.slint";
|
||||||
|
|
||||||
|
export struct CalendarDayEvent {
|
||||||
|
title: string,
|
||||||
|
startsAt: Time,
|
||||||
|
endsAt: Time
|
||||||
|
}
|
||||||
|
|
||||||
|
export struct CalendarDay {
|
||||||
|
events: [CalendarDayEvent],
|
||||||
|
date: Date,
|
||||||
|
header: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CalendarDateDisplayFormat {
|
||||||
|
Relative,
|
||||||
|
Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
export component Calendar inherits Rectangle {
|
||||||
|
in property<[CalendarDay]> days;
|
||||||
|
in property <CalendarDateDisplayFormat> format;
|
||||||
|
private property <length> header-height: 64px;
|
||||||
|
private property <length> available-day-space: self.height - header-height;
|
||||||
|
private property <length> day-start-y: header-height;
|
||||||
|
private property <length> hour-spacing: available-day-space / 24;
|
||||||
|
background: Palette.pane;
|
||||||
|
|
||||||
|
HorizontalLayout {
|
||||||
|
Rectangle {
|
||||||
|
//background: red;
|
||||||
|
width: 48px;
|
||||||
|
VerticalLayout {
|
||||||
|
y: 0;
|
||||||
|
height: header-height;
|
||||||
|
padding-right: 8px;
|
||||||
|
VText {
|
||||||
|
vertical-alignment: center;
|
||||||
|
horizontal-alignment: right;
|
||||||
|
text: "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for index in 24: VerticalLayout {
|
||||||
|
y: day-start-y + index * hour-spacing - (hour-spacing / 2);
|
||||||
|
height: hour-spacing;
|
||||||
|
padding-right: 8px;
|
||||||
|
VText {
|
||||||
|
vertical-alignment: center;
|
||||||
|
horizontal-alignment: right;
|
||||||
|
text: "\{index}h";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
//background: green;
|
||||||
|
HorizontalLayout {
|
||||||
|
for day[day-index] in root.days: Rectangle {
|
||||||
|
if day-index > 0 : Rectangle {
|
||||||
|
x: 0;
|
||||||
|
y: header-height - 32px;
|
||||||
|
width: 1px;
|
||||||
|
height: parent.height;
|
||||||
|
background: Palette.card-background.transparentize(0.5);
|
||||||
|
}
|
||||||
|
VerticalLayout {
|
||||||
|
y: 0;
|
||||||
|
height: header-height;
|
||||||
|
padding-right: 8px;
|
||||||
|
VText {
|
||||||
|
vertical-alignment: center;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
text: day.header;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for hour[hour-index] in 24 : Rectangle {
|
||||||
|
background: Palette.card-background.transparentize(0.5);
|
||||||
|
x: 0px;
|
||||||
|
width: parent.width;
|
||||||
|
y: day-start-y + hour-spacing * hour-index;
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
for event[event-index] in day.events : Rectangle {
|
||||||
|
background: Palette.card-background;
|
||||||
|
border-radius: 4px;
|
||||||
|
x: 8px;
|
||||||
|
width: parent.width - 16px;
|
||||||
|
y: day-start-y + hour-spacing * event.startsAt.hour;
|
||||||
|
height: hour-spacing * (event.endsAt.hour - event.startsAt.hour) - 2px;
|
||||||
|
clip: true;
|
||||||
|
HorizontalLayout {
|
||||||
|
Rectangle {
|
||||||
|
width: 4px;
|
||||||
|
background: Palette.accent;
|
||||||
|
}
|
||||||
|
VerticalLayout {
|
||||||
|
padding: 16px;
|
||||||
|
VText {
|
||||||
|
text: event.title;
|
||||||
|
wrap: TextWrap.word-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,7 +62,7 @@ export component EventGroup {
|
||||||
VText {
|
VText {
|
||||||
text: Utils.time-to-string(event.endsAt);
|
text: Utils.time-to-string(event.endsAt);
|
||||||
color: Palette.accent;
|
color: Palette.accent;
|
||||||
font-size: 0.8rem;
|
font-size: 0.9rem;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { Backend } from "../Backend.slint";
|
import { Backend } from "../Backend.slint";
|
||||||
import { ToggleButton, VText, Palette } from "@selenite";
|
import { VButton, ToggleButton, VText, Svg, Palette } from "@selenite";
|
||||||
|
|
||||||
export component SideBar inherits Rectangle {
|
export component SideBar inherits Rectangle {
|
||||||
background: Palette.pane;
|
background: Palette.pane;
|
||||||
|
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
alignment: start;
|
height: parent.height;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
spacing: 16px;
|
spacing: 16px;
|
||||||
VText {
|
VText {
|
||||||
|
@ -13,19 +13,33 @@ export component SideBar inherits Rectangle {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
spacing: 4px;
|
alignment: space-between;
|
||||||
ToggleButton {
|
vertical-stretch: 1;
|
||||||
text: "All";
|
VerticalLayout {
|
||||||
text-alignment: left;
|
vertical-stretch: 1;
|
||||||
active: Backend.no-source-selected;
|
spacing: 4px;
|
||||||
clicked => { Backend.source-clicked(-1) }
|
ToggleButton {
|
||||||
}
|
text: "All";
|
||||||
for item[index] in Backend.sources-selected: ToggleButton {
|
text-alignment: left;
|
||||||
text: item.name;
|
active: Backend.no-source-selected;
|
||||||
text-alignment: left;
|
clicked => { Backend.source-clicked(-1) }
|
||||||
active: item.selected;
|
}
|
||||||
clicked => { Backend.source-clicked(index) }
|
for item[index] in Backend.sources-selected: ToggleButton {
|
||||||
|
text: item.name;
|
||||||
|
text-alignment: left;
|
||||||
|
active: item.selected;
|
||||||
|
clicked => { Backend.source-clicked(index) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
/*VerticalLayout {
|
||||||
|
spacing: 4px;
|
||||||
|
VButton {
|
||||||
|
icon-svg: Svg.cog;
|
||||||
|
text: "Settings";
|
||||||
|
background: transparent;
|
||||||
|
clicked => { Backend.settings-clicked() }
|
||||||
|
}
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue