Switch from Qt6 to Slint

This commit is contained in:
Vyn 2024-08-16 21:35:12 +02:00
parent f8be14bcf8
commit 63bf267a22
107 changed files with 27532 additions and 2896 deletions

1
.gitignore vendored
View file

@ -2,7 +2,6 @@ build
todo.txt
todo.md
.qmlls.ini
.clangd
.cache
.nvimrc
CMakeLists.txt.user

View file

@ -1,105 +1,65 @@
cmake_minimum_required(VERSION 3.16)
project(Mirai VERSION 1.0 LANGUAGES CXX)
cmake_minimum_required(VERSION 3.21)
project(mirai LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
set(CMAKE_BUILD_RPATH_USE_ORIGIN ON)
find_package(Qt6 6.7 REQUIRED COMPONENTS Quick Xml)
include_directories(src)
include_directories(libs)
qt_standard_project_setup(REQUIRES 6.7)
# -- Slint setup
find_package(Slint QUIET)
if (NOT Slint_FOUND)
message("Slint could not be located in the CMake module search path. Downloading it from Git and building it locally")
include(FetchContent)
FetchContent_Declare(
Slint
GIT_REPOSITORY https://github.com/slint-ui/slint.git
# `release/1` will auto-upgrade to the latest Slint >= 1.0.0 and < 2.0.0
# `release/1.0` will auto-upgrade to the latest Slint >= 1.0.0 and < 1.1.0
GIT_TAG release/1
SOURCE_SUBDIR api/cpp
)
FetchContent_MakeAvailable(Slint)
endif (NOT Slint_FOUND)
# -- End of Slint setup
add_library(mirai-core
src/core/Mirai.h src/core/Mirai.cpp
src/core/TaskItem.h src/core/TaskItem.cpp
src/core/BaseResource.h src/core/BaseResource.cpp
src/core/BaseFileResource.h
src/core/StdFileResource.h
src/core/TasksView.h src/core/TasksView.cpp
src/core/TodoMd.h src/core/TodoMd.cpp
src/core/utils.h src/core/utils.cpp
lib/mirai-core/Mirai.h lib/mirai-core/Mirai.cpp
lib/mirai-core/Config.h lib/mirai-core/Config.cpp
lib/mirai-core/TaskItem.h lib/mirai-core/TaskItem.cpp
lib/mirai-core/Day.h lib/mirai-core/Day.cpp
lib/mirai-core/Event.h lib/mirai-core/Event.cpp
lib/mirai-core/DateTime.h lib/mirai-core/DateTime.cpp
lib/mirai-core/EventEmitter.h lib/mirai-core/EventEmitter.cpp
lib/mirai-core/BaseResource.h lib/mirai-core/BaseResource.cpp
lib/mirai-core/BaseFileResource.h
lib/mirai-core/StdFileResource.h
lib/mirai-core/TasksView.h lib/mirai-core/TasksView.cpp
lib/mirai-core/TodoMd.h lib/mirai-core/TodoMd.cpp
lib/mirai-core/utils.h lib/mirai-core/utils.cpp
)
qt_add_executable(mirai
src/main.cpp
MANUAL_FINALIZATION
target_include_directories(mirai-core PRIVATE "lib")
add_executable(mirai
src/main.cpp
src/UiState.cpp
src/Utils.cpp
)
set_property(TARGET mirai APPEND PROPERTY
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
)
set_property(TARGET mirai PROPERTY SLINT_EMBED_RESOURCES embed-files)
qt_finalize_executable(mirai)
target_link_libraries(mirai PRIVATE Slint::Slint)
set_source_files_properties(src/qml/styles/CatppuccinFrappe.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
)
set_source_files_properties(src/qml/styles/OneDark.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
)
set_source_files_properties(src/qml/styles/MiraiColorPalette.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
)
qt_add_qml_module(mirai
URI Mirai
#RESOURCE_PREFIX /qt/qml
QML_FILES
src/qml/Main.qml
src/qml/MainPanel.qml
src/qml/SideMenu.qml
src/qml/ThemeLoader.qml
src/qml/forms/TaskForm.qml
src/qml/forms/FilesForm.qml
src/qml/forms/TagsConfigForm.qml
src/qml/components/TaskItem.qml
src/qml/components/DatePicker.qml
src/qml/components/DateField.qml
src/qml/components/AppIcon.qml
src/qml/components/AppLineEdit.qml
src/qml/components/AppCheckbox.qml
src/qml/components/AppText.qml
src/qml/components/AppComboBox.qml
src/qml/components/AppButton.qml
src/qml/components/TabSelector.qml
src/qml/components/Tag.qml
src/qml/components/Calendar.qml
src/qml/components/Modal.qml
src/qml/styles/MiraiColorPalette.qml
src/qml/styles/CatppuccinFrappe.qml
src/qml/styles/OneDark.qml
src/qml/views/ListView.qml
src/qml/views/CalendarView.qml
SOURCES
src/Backend.h src/Backend.cpp
src/TaskItem.h src/TaskItem.cpp
src/Tag.h src/Tag.cpp
src/TasksFile.h src/TasksFile.cpp
src/AndroidFileResource.h
RESOURCES
src/images/calendar.png
src/images/add.png
src/images/settings.png
src/images/visible.png
src/images/not-visible.png
)
target_link_libraries(mirai PRIVATE Qt6::Quick Qt6::Xml)
target_include_directories(mirai PRIVATE "lib")
target_link_libraries(mirai PRIVATE mirai-core)
# Tests
add_subdirectory(libs/Catch2)
add_executable(tests
tests/format.cpp
tests/saving.cpp
slint_target_sources(
mirai ui/appwindow.slint
LIBRARY_PATHS vynui=${CMAKE_CURRENT_SOURCE_DIR}/lib/slint-vynui/index.slint
)
target_link_libraries(tests PRIVATE mirai-core)
target_link_libraries(tests PRIVATE Catch2::Catch2WithMain)
# On Windows, copy the Slint DLL next to the application binary so that it's found.
if (WIN32)
add_custom_command(TARGET mirai POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:mirai> $<TARGET_FILE_DIR:mirai> COMMAND_EXPAND_LISTS)
endif()

View file

@ -2,13 +2,12 @@
# Mirai
Mirai provides todo lists and calendar views from the same data using a simple syntax.
Mirai provides todo lists using a simple syntax.
Your data are stored on your device as readable plain text files, you can edit them
with whatever you want and sync them using third party software (like Syncthing).
![Mirai Todo](https://codeberg.org/vyn/mirai/raw/branch/main/images/presentation-todo-1.png)
![Mirai Calendar](https://codeberg.org/vyn/mirai/raw/branch/main/images/presentation-calendar-1.png)
## Download
@ -21,9 +20,8 @@ Currently only `Linux (x86_64)` is officially supported.
### Requirements
- Qt >= 6.7.0
- GCC >= 14.1.1
- CMake >= 3.29.3
- GCC >= 14.2.1
- CMake >= 3.30.2
- Ninja (or Make but you will need to adapt the commands)
- Git
@ -44,6 +42,11 @@ ninja
```
Then you should have a `mirai` executable in the `build` directory you are currently in.
## To-do
- Add CLI mode for quick adding tasks
- Add support for custom themes
## License
Copyright (C) Vyn 2024

View file

@ -1 +1 @@
cmake -DCMAKE_BUILD_TYPE=Debug -S . -B ./build -G Ninja && cd build && ninja && ./mirai
cmake -DCMAKE_BUILD_TYPE=Debug -DSLINT_STYLE="fluent" -S . -B ./build -G Ninja && cd build && ninja && ./mirai

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

After

Width:  |  Height:  |  Size: 274 KiB

Before After
Before After

View file

@ -32,7 +32,7 @@ class Timer
void stop()
{
const auto now = std::chrono::steady_clock::now();
duration += std::chrono::duration_cast<std::chrono::microseconds>(now - startTime).count();
duration += std::chrono::duration_cast<std::chrono::milliseconds>(now - startTime).count();
isRunning = false;
}
@ -48,10 +48,9 @@ class Timer
if (isRunning) {
const auto now = std::chrono::steady_clock::now();
durationToShow +=
std::chrono::duration_cast<std::chrono::microseconds>(now - startTime).count();
std::chrono::duration_cast<std::chrono::milliseconds>(now - startTime).count();
}
std::cout << "[Debug - Timer] " << message << ": " << ((double)durationToShow / 1000000)
<< " seconds" << std::endl;
std::cout << "[Debug - Timer] " << message << ": " << durationToShow << " ms" << std::endl;
}
private:
@ -59,7 +58,7 @@ class Timer
// Timer are always running when created. You can use .reset() after creation.
bool isRunning = true;
long duration;
long duration = 0;
};
} // namespace cpputils::debug

View file

@ -7,7 +7,7 @@
#ifndef MIRAI_BASE_FILE_RESOURCE_H
#define MIRAI_BASE_FILE_RESOURCE_H
#include "core/BaseResource.h"
#include "BaseResource.h"
#include <string>
namespace mirai
@ -21,7 +21,8 @@ struct BaseFileResourceConstructor {
class BaseFileResource : public BaseResource
{
public:
BaseFileResource(BaseFileResourceConstructor data) : BaseResource(data.name), path(data.path){};
BaseFileResource(BaseFileResourceConstructor data)
: BaseResource(data.name), path(data.path) {};
BaseFileResource(BaseFileResource &) = delete;
BaseFileResource(BaseFileResource &&) = delete;

View file

@ -0,0 +1,127 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "BaseResource.h"
#include "TaskItem.h"
#include "mirai-core/Day.h"
#include <algorithm>
#include <functional>
#include <iostream>
#include <memory>
#include <ostream>
namespace mirai
{
void BaseResource::setDirty(bool shouldBeDirty)
{
isDirty_ = shouldBeDirty;
}
bool BaseResource::isDirty() const
{
return isDirty_;
}
Day *BaseResource::day(const Date &date)
{
auto dayFound = std::find_if(days_.begin(), days_.end(), [&](std::unique_ptr<Day> &day) {
return day->getDate().day == date.day && day->getDate().month == date.month &&
day->getDate().year == date.year;
});
if (dayFound == days_.end()) {
auto newDay = std::make_unique<Day>(this, DayData{.date = date});
Day *dayPtr = newDay.get();
days_.push_back(std::move(newDay));
return dayPtr;
}
return dayFound->get();
}
std::vector<std::unique_ptr<Day>> *BaseResource::days()
{
return &days_;
}
void BaseResource::addDay(const DayData &dayData)
{
days_.push_back(std::make_unique<Day>(this, dayData));
setDirty(true);
}
TaskItem *BaseResource::getTaskById(int taskId)
{
for (auto &day : days_) {
for (auto &task : *day->tasks()) {
if (task->id() == taskId) {
return task.get();
}
}
for (auto &event : *day->events()) {
for (auto &task : *event->tasks()) {
if (task->id() == taskId) {
return task.get();
}
}
}
}
return nullptr;
}
void BaseResource::deleteTask(const TaskItem &taskToDelete)
{
for (auto &day : days_) {
for (auto &task : *day->tasks()) {
if (task->id() == taskToDelete.id()) {
day->deleteTask(taskToDelete);
return;
}
}
for (auto &event : *day->events()) {
for (auto &task : *event->tasks()) {
if (task->id() == taskToDelete.id()) {
event->deleteTask(taskToDelete);
return;
}
}
}
}
}
Event *BaseResource::getEventById(int eventId)
{
for (auto &day : days_) {
for (auto &event : *day->events()) {
if (event->id() == eventId) {
return event.get();
}
}
}
return nullptr;
}
/*void BaseResource::addTask(TaskItemData taskItem)*/
/*{*/
/*tasks.push_back(std::make_unique<TaskItem>(this, taskItem));*/
/*setDirty(true);*/
/*}*/
/*void BaseResource::removeTask(const TaskItem *taskToRemove)*/
/*{*/
/*auto findFunction = [&](const std::unique_ptr<TaskItem> &taskInFilter) {*/
/*return taskInFilter.get() == taskToRemove;*/
/*};*/
/*auto taskToDelete = std::remove_if(tasks_.begin(), tasks_.end(), findFunction);*/
/*if (taskToDelete == tasks_.end()) {*/
/*return;*/
/*}*/
/*tasks_.erase(taskToDelete);*/
/*setDirty(true);*/
/*}*/
int BaseResource::nextId_ = 0;
} // namespace mirai

View file

@ -7,8 +7,12 @@
#ifndef MIRAI_BASE_RESOURCE_H
#define MIRAI_BASE_RESOURCE_H
#include "core/TaskItem.h"
#include "TaskItem.h"
#include "mirai-core/DateTime.h"
#include "mirai-core/Day.h"
#include "mirai-core/Event.h"
#include <memory>
#include <optional>
#include <string>
#include <vector>
@ -17,8 +21,14 @@ namespace mirai
class BaseResource
{
public:
BaseResource(std::string name) : name(name) {};
struct AddTaskData {
std::string text;
Tags tags;
};
BaseResource(std::string name) : name_(name) {};
BaseResource(BaseResource &) = delete;
BaseResource(BaseResource &&) = delete;
@ -28,24 +38,34 @@ class BaseResource
void setName(std::string name)
{
this->name = name;
this->name_ = name;
}
const std::string &getName() const
{
return name;
return name_;
}
void addTask(TaskItemData TaskItem);
void removeTask(const TaskItem *taskToRemove);
std::vector<std::unique_ptr<TaskItem>> &getTasks();
void addDay(const DayData &dayData);
Day *day(const Date &date);
std::vector<std::unique_ptr<Day>> *days();
TaskItem *getTaskById(int taskId);
Event *getEventById(int eventId);
void deleteTask(const TaskItem &task);
void setDirty(bool shouldBeDirty);
bool isDirty() const;
int id() const
{
return id_;
}
private:
std::string name;
std::vector<std::unique_ptr<TaskItem>> tasks;
static int nextId_;
int id_ = nextId_++;
std::string name_;
std::vector<std::unique_ptr<Day>> days_;
bool isDirty_ = false;
};

35
lib/mirai-core/Config.cpp Normal file
View file

@ -0,0 +1,35 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "Config.h"
#include "nlohmann/json.hpp"
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
namespace mirai
{
Config::Config(const std::string &path) : path_(path)
{
std::ifstream file(path_);
configJson_ = nlohmann::json::parse(file);
}
std::vector<std::string> Config::sources() const
{
std::vector<std::string> sources;
assert(configJson_.is_object());
assert(configJson_["files"].is_array());
auto jsonSources = configJson_["files"];
for (const auto &filePath : jsonSources) {
assert(filePath.is_string());
sources.push_back(filePath.get<std::string>());
}
return sources;
}
}; // namespace mirai

27
lib/mirai-core/Config.h Normal file
View file

@ -0,0 +1,27 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#pragma once
#include "nlohmann/json.hpp"
#include <string>
#include <vector>
namespace mirai
{
class Config
{
public:
Config(const std::string &path);
std::vector<std::string> sources() const;
private:
std::string path_;
nlohmann::json configJson_;
};
} // namespace mirai

View file

@ -0,0 +1,37 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "mirai-core/DateTime.h"
#include <charconv>
#include <optional>
#include <ranges>
#include <string>
#include <vector>
namespace mirai
{
std::optional<mirai::Date> stringToDate(const std::string &dateStr)
{
using std::operator""sv;
auto dateSplitView =
dateStr | std::views::split("-"sv) | std::views::transform([](auto v) -> int {
int i = 0;
std::from_chars(v.data(), v.data() + v.size(), i);
return i;
});
std::vector<int> dateSplit{dateSplitView.begin(), dateSplitView.end()};
if (dateSplit.size() != 3) {
return std::nullopt;
}
auto year = dateSplit[0];
auto month = dateSplit[1];
auto day = dateSplit[2];
return mirai::Date(year, static_cast<unsigned>(month), static_cast<unsigned>(day));
}
} // namespace mirai

95
lib/mirai-core/DateTime.h Normal file
View file

@ -0,0 +1,95 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#pragma once
#include <bits/chrono.h>
#include <chrono>
#include <ctime>
#include <optional>
#include <string>
namespace mirai
{
struct Date {
explicit Date(int year, unsigned month, unsigned day) : year(year), month(month), day(day)
{
}
explicit Date(std::chrono::time_point<std::chrono::system_clock> tp)
{
auto chronoDate = std::chrono::year_month_day(std::chrono::floor<std::chrono::days>(tp));
year = static_cast<int>(chronoDate.year());
month = static_cast<unsigned>(chronoDate.month());
day = static_cast<unsigned>(chronoDate.day());
}
explicit Date(std::chrono::year_month_day chronoDate)
{
year = static_cast<int>(chronoDate.year());
month = static_cast<unsigned>(chronoDate.month());
day = static_cast<unsigned>(chronoDate.day());
}
int year;
unsigned month;
unsigned day;
bool operator==(const Date &other) const
{
return other.year == year && other.month == month && other.day == day;
}
bool operator<(const Date &other) const
{
if (year < other.year) {
return true;
}
if (year == other.year && month < other.month) {
return true;
}
if (year == other.year && month == other.month && day < other.day) {
return true;
}
return false;
}
bool operator>(const Date &other) const
{
return !(*this < other) && !(*this == other);
}
};
struct Time {
int hour;
int minute;
bool operator==(const Time &other) const
{
return other.hour == hour && other.minute == minute;
}
bool operator<(const Time &other) const
{
if (hour < other.hour) {
return true;
}
if (hour == other.hour && minute < other.minute) {
return true;
}
return false;
}
bool operator>(const Time &other) const
{
return !(*this < other) && !(*this == other);
}
};
std::optional<mirai::Date> stringToDate(const std::string &dateStr);
} // namespace mirai

89
lib/mirai-core/Day.cpp Normal file
View file

@ -0,0 +1,89 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "Day.h"
#include "mirai-core/BaseResource.h"
#include <algorithm>
#include <iostream>
#include <memory>
#include <ostream>
#include <vector>
namespace mirai
{
Day::Day(BaseResource *source, const DayData &data) : source_(source), data_(data)
{
}
Event *Day::createEvent(const EventData &eventData)
{
auto event = std::make_unique<Event>(source_, this, eventData);
mirai::Event *eventPtr = event.get();
events_.push_back(std::move(event));
onChange();
return eventPtr;
}
std::vector<std::unique_ptr<Event>> *Day::events()
{
return &events_;
}
const Date &Day::getDate() const
{
return data_.date;
}
TaskItem *Day::createTask(const TaskItemData &taskData)
{
auto task = std::make_unique<TaskItem>(source_, this, taskData);
mirai::TaskItem *taskPtr = task.get();
tasks_.push_back(std::move(task));
onChange();
return taskPtr;
}
void Day::deleteTask(const TaskItem &taskToDelete)
{
int id = taskToDelete.id();
tasks_.erase(
std::remove_if(
tasks_.begin(), tasks_.end(),
[&](const std::unique_ptr<TaskItem> &task) {
return task->id() == id;
}
),
tasks_.end()
);
onChange();
}
void Day::deleteEvent(const Event &eventToDelete)
{
int id = eventToDelete.id();
events_.erase(
std::remove_if(
events_.begin(), events_.end(),
[&](const std::unique_ptr<Event> &event) {
return event->id() == id;
}
),
events_.end()
);
onChange();
}
void Day::onChange()
{
source()->setDirty(true);
}
std::vector<std::unique_ptr<TaskItem>> *Day::tasks()
{
return &tasks_;
}
} // namespace mirai

66
lib/mirai-core/Day.h Normal file
View file

@ -0,0 +1,66 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#pragma once
#include "mirai-core/DateTime.h"
#include "mirai-core/Event.h"
#include "mirai-core/TaskItem.h"
#include "using.h"
#include <memory>
#include <string>
#include <vector>
namespace mirai
{
class BaseResource;
struct DayData {
Date date;
std::vector<EventData> events;
std::vector<TaskItemData> tasks;
};
class Day
{
public:
Day(BaseResource *source, const DayData &data);
void setDate(const Date &date);
Event *createEvent(const EventData &eventData);
TaskItem *createTask(const TaskItemData &taskData);
void deleteTask(const TaskItem &taskToDelete);
void deleteEvent(const Event &eventToDelete);
std::vector<std::unique_ptr<Event>> *events();
std::vector<std::unique_ptr<TaskItem>> *tasks();
const Date &getDate() const;
Event *getEventById(int eventId)
{
for (auto &event : events_) {
if (event->id() == eventId) {
return event.get();
}
}
return nullptr;
}
BaseResource *source()
{
return source_;
}
private:
void onChange();
BaseResource *source_;
std::vector<std::unique_ptr<Event>> events_;
std::vector<std::unique_ptr<TaskItem>> tasks_;
DayData data_;
};
} // namespace mirai

99
lib/mirai-core/Event.cpp Normal file
View file

@ -0,0 +1,99 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "Event.h"
#include "BaseResource.h"
#include "mirai-core/TaskItem.h"
#include "utils.h"
namespace mirai
{
Event::Event(BaseResource *source, Day *parent, const EventData &data)
: source_(source), day_(parent), data(data)
{
}
void Event::setText(const std::string &text)
{
this->data.description = text;
onChange();
}
const std::string &Event::getText() const
{
return data.description;
}
const Date &Event::getDate() const
{
return data.date;
}
const Time &Event::getStartTime() const
{
return data.startTime;
}
const Time &Event::getEndTime() const
{
return data.endTime;
}
const Tags &Event::getTags() const
{
return data.tags;
}
bool Event::hasTag(const std::string &tag) const
{
return vectorUtils::contains(data.tags, tag);
}
TaskItem *Event::createTask(const TaskItemData &taskData)
{
auto task = std::make_unique<TaskItem>(source_, day_, taskData);
mirai::TaskItem *taskPtr = task.get();
tasks_.push_back(std::move(task));
onChange();
return taskPtr;
}
void Event::deleteTask(const TaskItem &taskToDelete)
{
tasks_.erase(std::remove_if(
tasks_.begin(), tasks_.end(),
[&](const std::unique_ptr<TaskItem> &task) {
return task->id() == taskToDelete.id();
}
));
}
std::vector<std::unique_ptr<TaskItem>> *Event::tasks()
{
return &tasks_;
}
void Event::setStartTime(const Time &time)
{
data.startTime = time;
onChange();
}
void Event::setEndTime(const Time &time)
{
data.endTime = time;
onChange();
}
void Event::onChange()
{
source_->setDirty(true);
}
int Event::nextId = 0;
} // namespace mirai

80
lib/mirai-core/Event.h Normal file
View file

@ -0,0 +1,80 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#pragma once
#include "mirai-core/DateTime.h"
#include "mirai-core/TaskItem.h"
#include "using.h"
#include <memory>
#include <string>
#include <vector>
namespace mirai
{
class BaseResource;
struct EventData {
std::string description;
Date date;
Time startTime;
Time endTime;
Tags tags;
std::vector<TaskItemData> tasks;
};
class Day;
class Event
{
private:
public:
Event(BaseResource *source, Day *parent, const EventData &data);
Event &operator=(const EventData &newData)
{
data = newData;
onChange();
return *this;
};
void setText(const std::string &text);
void setStartTime(const Time &time);
void setEndTime(const Time &time);
TaskItem *createTask(const TaskItemData &taskData);
void deleteTask(const TaskItem &taskToDelete);
std::vector<std::unique_ptr<TaskItem>> *tasks();
const std::string &getText() const;
const Date &getDate() const;
const Time &getStartTime() const;
const Time &getEndTime() const;
const Tags &getTags() const;
bool hasTag(const std::string &tag) const;
int id() const
{
return id_;
}
Day *day()
{
return day_;
}
private:
void onChange();
static int nextId;
int id_ = nextId++;
Day *day_;
EventData data;
std::vector<std::unique_ptr<TaskItem>> tasks_;
BaseResource *source_;
};
} // namespace mirai

View file

@ -1,13 +1,7 @@
/*
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Controls
import Mirai
Text {
color: colorPalette.selected.text
}
#include "EventEmitter.h"

View file

@ -0,0 +1,28 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include <functional>
#include <vector>
template <typename T> class EventEmitter
{
public:
void registerCallback(std::function<T> func)
{
callbacks.push_back(func);
}
void emit(T data)
{
for (auto &callback : callbacks) {
callback(data);
}
}
private:
std::vector<std::function<T>> callbacks;
};

View file

@ -7,6 +7,7 @@
#include "Mirai.h"
#include "TaskItem.h"
#include "cpp-utils/debug.h"
#include "mirai-core/Config.h"
#include "utils.h"
#include <algorithm>
#include <iostream>
@ -21,18 +22,18 @@ namespace mirai
void Mirai::loadResource(std::unique_ptr<BaseResource> &&resource)
{
resource->load();
resources.push_back(std::move(resource));
resources_.push_back(std::move(resource));
reloadTags();
};
void Mirai::unloadAllResources()
{
resources.clear();
resources_.clear();
}
void Mirai::save()
{
for (auto &resource : resources) {
for (auto &resource : resources_) {
if (resource->isDirty()) {
resource->save();
resource->setDirty(false);
@ -43,25 +44,25 @@ void Mirai::save()
void Mirai::removeTask(const TaskItem *taskItem)
{
for (auto &resource : resources) {
resource->removeTask(taskItem);
for (auto &resource : resources_) {
// resource->removeTask(taskItem); // TODO REWORK
}
}
std::vector<std::unique_ptr<BaseResource>> &Mirai::getResources()
{
return resources;
return resources_;
}
std::optional<std::reference_wrapper<BaseResource>> Mirai::getResourceByName(const std::string &name
)
{
auto resourceIterator =
std::ranges::find_if(resources, [&](const std::unique_ptr<BaseResource> &resource) {
std::ranges::find_if(resources_, [&](const std::unique_ptr<BaseResource> &resource) {
return resource->getName() == name;
});
if (resourceIterator == resources.end()) {
if (resourceIterator == resources_.end()) {
return std::nullopt;
}
return *(resourceIterator->get());
@ -69,23 +70,23 @@ std::optional<std::reference_wrapper<BaseResource>> Mirai::getResourceByName(con
const std::vector<std::string> &Mirai::getTags()
{
return tags;
return tags_;
}
void Mirai::reloadTags()
{
cpputils::debug::Timer reloadingTagsDuration;
tags.clear();
for (auto &resource : resources) {
for (auto &task : resource->getTasks()) {
for (auto &tag : task->getTags()) {
if (!vectorUtils::contains(tags, tag)) {
tags.push_back(tag);
}
}
}
}
reloadingTagsDuration.printTimeElapsed("ReloadingTags");
/*cpputils::debug::Timer reloadingTagsDuration;*/ // TODO REWORK
/*tags.clear();*/
/*for (auto &resource : resources) {*/
/*for (auto &task : resource->getTasks()) {*/
/*for (auto &tag : task->getTags()) {*/
/*if (!vectorUtils::contains(tags, tag)) {*/
/*tags.push_back(tag);*/
/*}*/
/*}*/
/*}*/
/*}*/
/*reloadingTagsDuration.printTimeElapsed("ReloadingTags");*/
}
} // namespace mirai

View file

@ -7,11 +7,13 @@
#ifndef MIRAI_MIRAI_H
#define MIRAI_MIRAI_H
#include "BaseFileResource.h"
#include "TaskItem.h"
#include "TodoMd.h"
#include "core/BaseFileResource.h"
#include "mirai-core/BaseResource.h"
#include <algorithm>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <string>
@ -34,9 +36,20 @@ class Mirai
void reloadTags();
// new stuff
// Returns a non owning pointer to the requested resource or nullptr if not found.
BaseResource *getResourceById(int id)
{
if (id >= resources_.size()) {
return nullptr;
}
return resources_.at(id).get();
}
private:
std::vector<std::unique_ptr<BaseResource>> resources;
std::vector<std::string> tags;
std::vector<std::unique_ptr<BaseResource>> resources_;
std::vector<std::string> tags_;
};
} // namespace mirai
#endif

View file

@ -7,9 +7,11 @@
#ifndef MIRAI_STD_FILE_RESOURCE_H
#define MIRAI_STD_FILE_RESOURCE_H
#include "core/BaseFileResource.h"
#include "core/TodoMd.h"
#include "BaseFileResource.h"
#include "TodoMd.h"
#include <fstream>
#include <iostream>
#include <ostream>
#include <string>
namespace mirai
@ -18,7 +20,7 @@ namespace mirai
class StdFileResource : public BaseFileResource
{
public:
StdFileResource(BaseFileResourceConstructor data) : BaseFileResource(data){};
StdFileResource(BaseFileResourceConstructor data) : BaseFileResource(data) {};
StdFileResource(StdFileResource &) = delete;
StdFileResource(StdFileResource &&) = delete;
StdFileResource operator=(StdFileResource &) = delete;
@ -33,7 +35,7 @@ class StdFileResource : public BaseFileResource
throw std::runtime_error("can't create " + getPath());
}
const std::string content = TodoMdFormat::stringifyTasks(getName(), getTasks());
const std::string content = TodoMdFormat::stringify(getName(), *days());
file << content;
file.close();
@ -53,8 +55,17 @@ class StdFileResource : public BaseFileResource
}
file.close();
auto result = TodoMdFormat::parse(content);
for (const auto &task : result.tasks) {
addTask(task);
for (const auto &dayData : result.days) {
Day *newDay = day(dayData.date);
for (const auto &eventData : dayData.events) {
Event *newEvent = newDay->createEvent(eventData);
for (const auto &taskData : eventData.tasks) {
TaskItem *newTask = newEvent->createTask(taskData);
}
}
for (const auto &taskData : dayData.tasks) {
TaskItem *newTask = newDay->createTask(taskData);
}
}
setName(result.name);
};

View file

@ -6,13 +6,16 @@
#include "TaskItem.h"
#include "BaseResource.h"
#include "core/utils.h"
#include "utils.h"
#include <iostream>
namespace mirai
{
TaskItem::TaskItem(BaseResource *parent, TaskItemData data) : parent(parent), data(data)
int TaskItem::nextId = 0;
TaskItem::TaskItem(BaseResource *source, Day *day, TaskItemData data)
: source_(source), day_(day), data(data)
{
}
@ -28,12 +31,6 @@ void TaskItem::markAsUndone()
onChange();
}
void TaskItem::setDate(const std::string &date)
{
this->data.date = date;
onChange();
}
void TaskItem::setText(const std::string &text)
{
this->data.text = text;
@ -50,36 +47,11 @@ const TaskItemState &TaskItem::getState() const
return data.state;
}
const std::string &TaskItem::getDate() const
{
return data.date;
}
const std::string &TaskItem::getStartTime() const
{
return data.startTime;
}
const std::string &TaskItem::getEndTime() const
{
return data.endTime;
}
const Tags &TaskItem::getTags() const
{
return data.tags;
}
bool TaskItem::hasDate() const
{
return isDate(data.date);
}
bool TaskItem::hasTime() const
{
return getStartTime() != "" && getEndTime() != "";
}
bool TaskItem::hasTag(const std::string &tag) const
{
return vectorUtils::contains(data.tags, tag);
@ -87,9 +59,7 @@ bool TaskItem::hasTag(const std::string &tag) const
void TaskItem::onChange()
{
if (parent) {
parent->setDirty(true);
}
source()->setDirty(true);
}
} // namespace mirai

View file

@ -13,26 +13,22 @@
namespace mirai
{
class BaseResource;
class Day;
enum TaskItemState { TODO, DONE };
struct TaskItemData {
std::string text;
TaskItemState state;
std::string date;
std::string startTime;
std::string endTime;
Tags tags;
};
class BaseResource;
class TaskItem
{
friend BaseResource;
private:
public:
TaskItem(BaseResource *parent, const TaskItemData data);
TaskItem(BaseResource *source, Day *day, const TaskItemData data);
TaskItem &operator=(const TaskItemData &newData)
{
@ -51,20 +47,35 @@ class TaskItem
const std::string &getLine() const;
const std::string &getText() const;
const TaskItemState &getState() const;
const std::string &getDate() const;
const std::string &getStartTime() const;
const std::string &getEndTime() const;
const Tags &getTags() const;
bool hasTag(const std::string &tag) const;
bool hasDate() const;
bool hasTime() const;
int id() const
{
return id_;
}
BaseResource *source()
{
return source_;
}
Day *day()
{
return day_;
}
private:
void onChange();
BaseResource *parent;
static int nextId;
int id_ = nextId++;
TaskItemData data;
BaseResource *source_;
Day *day_;
};
} // namespace mirai
#endif

View file

@ -0,0 +1,176 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "TasksView.h"
#include "Mirai.h"
#include "mirai-core/TaskItem.h"
#include "utils.h"
#include <algorithm>
#include <chrono>
#include <cstddef>
#include <ctime>
#include <format>
#include <iostream>
#include <memory>
#include <ostream>
#include <stdexcept>
#include <string>
#include <vector>
namespace mirai
{
TasksView::TasksView(Mirai *miraiInstance) : mirai(miraiInstance)
{
if (!miraiInstance) {
throw std::runtime_error("NULL pointer passed in TasksView");
}
update();
}
FilteredDay &TasksView::operator[](int index)
{
return filteredDays.at(index);
}
size_t TasksView::count() const
{
return filteredDays.size();
}
void TasksView::update()
{
auto todayDate = std::format("{:%Y-%m-%d}", std::chrono::system_clock::now());
filteredDays.clear();
for (auto &file : mirai->getResources()) {
if (resourcesFilter.size() > 0 &&
!vectorUtils::contains(resourcesFilter, file->getName())) {
continue;
}
for (auto &day : *file->days()) {
FilteredDay filteredDay{.day = day.get()};
for (auto &task : *day->tasks()) {
auto taskDate = std::format(
"{}-0{}-{}", day->getDate().year, // TODO REWORK REMOVE 0{}
day->getDate().month,
day->getDate().day // TODO REWORK CLEAN THIS MESS
);
if (shouldHideCompletedTasks() && task->getState() == DONE &&
taskDate < todayDate) {
continue;
}
if (tagsFilter.size() > 0 &&
!vectorUtils::containsAll(tagsFilter, task->getTags())) {
continue;
}
filteredDay.filteredTasks.push_back(task.get());
}
for (auto &event : *day->events()) {
FilteredEvent filteredEvent{.event = event.get()};
auto eventDate = std::format(
// TODO REWORK
"{}-0{}-{}", day->getDate().year, day->getDate().month, day->getDate().day
);
for (auto &task : *event->tasks()) {
if (shouldHideCompletedTasks() && task->getState() == DONE &&
eventDate < todayDate) {
continue;
}
if (tagsFilter.size() > 0 &&
!vectorUtils::containsAll(tagsFilter, task->getTags())) {
continue;
}
filteredEvent.filteredTasks.push_back(task.get());
}
if (!filteredEvent.filteredTasks.empty() || !shouldHideCompletedTasks() ||
eventDate >= todayDate) {
filteredDay.filteredEvents.push_back(filteredEvent);
}
}
if (!filteredDay.filteredEvents.empty()) {
std::ranges::sort(
filteredDay.filteredEvents,
[](const FilteredEvent &t1, const FilteredEvent &t2) {
return t1.event->getStartTime().hour < t2.event->getStartTime().hour;
}
);
}
if (!filteredDay.filteredEvents.empty() || !filteredDay.filteredTasks.empty()) {
auto existingDay = std::ranges::find_if(filteredDays, [&](const FilteredDay &date) {
return date.day->getDate() == filteredDay.day->getDate();
});
if (existingDay == filteredDays.end()) {
filteredDays.push_back(filteredDay);
} else {
existingDay->filteredTasks.insert(
existingDay->filteredTasks.end(), filteredDay.filteredTasks.begin(),
filteredDay.filteredTasks.end()
);
existingDay->filteredEvents.insert(
existingDay->filteredEvents.end(), filteredDay.filteredEvents.begin(),
filteredDay.filteredEvents.end()
);
}
}
}
std::ranges::sort(filteredDays, [](const FilteredDay &t1, const FilteredDay &t2) {
return t1.day->getDate() < t2.day->getDate();
});
}
}
void TasksView::addTagFilter(const std::string &tag)
{
tagsFilter.push_back(tag);
update();
}
void TasksView::removeTagFilter(const std::string &tag)
{
tagsFilter
.erase(std::remove_if(tagsFilter.begin(), tagsFilter.end(), [&](const auto &tagInFilter) {
return tag == tagInFilter;
}));
update();
}
void TasksView::addResourceFilter(const std::string &fileName)
{
resourcesFilter.push_back(fileName);
update();
}
void TasksView::removeResourceFilter(const std::string &fileName)
{
resourcesFilter.erase(std::remove_if(
resourcesFilter.begin(), resourcesFilter.end(),
[&](const auto &fileInFilter) {
return fileName == fileInFilter;
}
));
update();
}
void TasksView::removeFilters()
{
tagsFilter.clear();
update();
}
const std::vector<std::string> &TasksView::getActiveTagsFilter()
{
return tagsFilter;
}
const std::vector<std::string> &TasksView::getActiveFilesFilter()
{
return resourcesFilter;
}
} // namespace mirai

View file

@ -6,21 +6,34 @@
#ifndef MIRAI_TASKSVIEW_H
#define MIRAI_TASKSVIEW_H
#include "core/Mirai.h"
#include "Mirai.h"
#include "mirai-core/TaskItem.h"
#include "using.h"
#include <cstddef>
#include <string>
#include <vector>
namespace mirai
{
struct FilteredEvent {
Event *event;
std::vector<TaskItem *> filteredTasks;
};
struct FilteredDay {
Day *day;
std::vector<FilteredEvent> filteredEvents;
std::vector<TaskItem *> filteredTasks;
};
class TasksView
{
public:
TasksView(Mirai *mirai);
TaskItem &operator[](int index);
FilteredDay &operator[](int index);
size_t count() const;
void update();
@ -32,11 +45,43 @@ class TasksView
const Tags &getActiveTagsFilter();
const std::vector<std::string> &getActiveFilesFilter();
void hideCompletedTasks(bool hide)
{
shouldHideCompletedTasks_ = hide;
}
bool shouldHideCompletedTasks() const
{
return shouldHideCompletedTasks_;
}
/*auto begin()*/ // TODO REWORK
/*{*/
/*return tasksToShow.begin();*/
/*}*/
/*auto begin() const*/
/*{*/
/*return tasksToShow.cbegin();*/
/*}*/
/*auto end() const*/
/*{*/
/*return tasksToShow.cend();*/
/*}*/
/*auto end()*/
/*{*/
/*return tasksToShow.end();*/
/*}*/
private:
std::vector<FilteredDay> filteredDays;
Mirai *mirai;
std::vector<TaskItem *> tasksToShow;
Tags tagsFilter;
std::vector<std::string> resourcesFilter;
bool shouldHideCompletedTasks_ = true;
};
} // namespace mirai
#endif

View file

@ -8,9 +8,13 @@
#include "TaskItem.h"
#include "cpp-utils/debug.h"
#include "utils.h"
#include <charconv>
#include <fstream>
#include <iostream>
#include <memory>
#include <ranges>
#include <stdexcept>
#include <string>
#include <string_view>
namespace mirai
@ -62,14 +66,59 @@ TaskItemData TodoMdFormat::stringToTask(const std::string &str, const std::strin
TaskItemData taskItem{
.text = text,
.state = str.substr(0, 5) == "- [X]" ? DONE : TODO,
.date = date,
.startTime = matches[3],
.endTime = matches[4],
.tags = extractTagsFromMetadata(matches[7])
};
return taskItem;
}
Time stringToTime(const std::string &str)
{
if (str.length() != 5) {
throw std::runtime_error("Str time length must be 5 (got '" + str + "')");
}
if (str[2] != 'h') {
throw std::runtime_error("Malformated time range");
}
Time time{};
std::from_chars(str.data(), str.data() + 2, time.hour);
std::from_chars(str.data() + 3, str.data() + 5, time.minute);
return time;
}
EventData TodoMdFormat::stringToEvent(const std::string &str, const std::string &dateString)
{
std::smatch matches;
std::regex regex("> (([0-9]{2}h[0-9]{2})-([0-9]{2}h[0-9]{2}) )(.*?)( -- (.*))?");
std::regex_match(str, matches, regex);
/*std::cout << "line " << str << std::endl;*/
/*std::cout << "M 0 " << matches[0] << std::endl;*/
/*std::cout << "M 1 " << matches[1] << std::endl;*/
/*std::cout << "M 2 " << matches[2] << std::endl;*/
/*std::cout << "M 3 " << matches[3] << std::endl;*/
/*std::cout << "M 4 " << matches[4] << std::endl;*/
/*std::cout << "M 5 " << matches[5] << std::endl;*/
/*std::cout << "M 6 " << matches[6] << std::endl;*/
std::string text = stringUtils::trim(matches[4]);
auto date = stringToDate(dateString);
if (!date.has_value()) {
throw std::runtime_error("Malformated date");
}
EventData eventData{
.description = text,
.date = date.value(),
.startTime = stringToTime(matches[2]),
.endTime = stringToTime(matches[3]),
.tags = extractTagsFromMetadata(matches[6])
};
return eventData;
}
std::string TodoMdFormat::taskToString(const TaskItem &task)
{
std::string str = task.getText();
@ -79,9 +128,6 @@ std::string TodoMdFormat::taskToString(const TaskItem &task)
str += " #" + tag;
}
}
if (task.getStartTime() != "" && task.getEndTime() != "") {
str = task.getStartTime() + "-" + task.getEndTime() + " > " + str;
}
str = (task.getState() == DONE ? "- [X] " : "- [ ] ") + str;
return str;
}

135
lib/mirai-core/TodoMd.h Normal file
View file

@ -0,0 +1,135 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef MIRAI_TODOMD_H
#define MIRAI_TODOMD_H
#include "TaskItem.h"
#include "cpp-utils/debug.h"
#include "cpp-utils/string.h"
#include "mirai-core/DateTime.h"
#include "mirai-core/Day.h"
#include "mirai-core/Event.h"
#include <format>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
namespace mirai
{
struct MiraiMarkdownFormatParseResult {
std::string name;
std::vector<DayData> days;
};
class TodoMdFormat
{
public:
static std::string
stringify(const std::string &name, const std::vector<std::unique_ptr<Day>> &days)
{
std::string result = "# " + name + "\n\n";
std::string currentDate = "";
for (const auto &day : days) {
auto &date = day->getDate();
result += "## " + std::format("{:02d}-{:02d}-{:02d}", date.year, date.month, date.day) +
"\n\n";
for (const auto &event : *(day->events())) {
auto &start = event->getStartTime();
auto &end = event->getEndTime();
result += "> " +
std::format(
"{:02d}h{:02d}-{:02d}h{:02d} {}", start.hour, start.minute, end.hour,
end.minute, event->getText()
) +
"\n";
for (const auto &task : *(event->tasks())) {
result += taskToString(*task) + '\n';
}
result += '\n';
}
for (const auto &task : *(day->tasks())) {
result += taskToString(*task) + '\n';
}
result += '\n';
}
return result;
};
static MiraiMarkdownFormatParseResult parse(const std::string &content)
{
cpputils::debug::Timer readMdFormatDuration;
std::vector<TaskItem> taskItems;
std::string line;
std::stringstream contentStream(content);
if (!std::getline(contentStream, line) || line.substr(0, 2) != "# ") {
std::cerr << "Couldn't find the task list name" << std::endl;
}
std::string name = line.substr(2);
std::string currentDateString = "";
std::vector<mirai::DayData> daysData;
mirai::DayData *currentDay = nullptr;
mirai::EventData *currentEvent = nullptr;
cpputils::debug::Timer stringToTaskDuration;
stringToTaskDuration.reset();
cpputils::debug::Timer gelinesDuration;
while (std::getline(contentStream, line)) {
if (std::string_view{line.data(), 3} == "## ") {
currentDateString = line.substr(3);
auto dateOpt = mirai::stringToDate(currentDateString);
if (!dateOpt.has_value()) {
throw std::runtime_error("Malformated date");
}
daysData.push_back({.date = dateOpt.value()});
currentDay = &daysData.back();
} else if (line.starts_with("> ")) {
auto event = stringToEvent(line, currentDateString);
if (currentDay) {
currentDay->events.push_back(event);
currentEvent = &currentDay->events.back();
}
} else if (line.starts_with("- [ ]") || line.starts_with("- [X]")) {
stringToTaskDuration.start();
TaskItemData taskItemData = stringToTask(line, currentDateString);
stringToTaskDuration.stop();
if (currentEvent) {
currentEvent->tasks.push_back(taskItemData);
} else if (currentDay) {
currentDay->tasks.push_back(taskItemData);
}
} else if (cpputils::string::trim(line) == "") {
currentEvent = nullptr;
}
}
gelinesDuration.printTimeElapsed("getlinesDuration");
stringToTaskDuration.printTimeElapsed("stringToTaskDuration");
readMdFormatDuration.printTimeElapsed("Reading MD File duration");
return {.name = name, .days = daysData};
}
static std::string taskToString(const TaskItem &task);
static TaskItemData stringToTask(const std::string &str, const std::string &date);
static EventData stringToEvent(const std::string &str, const std::string &date);
private:
static std::string fieldWithSpace(const std::string &field);
static TaskItem parseLine(const std::string &line);
static Tags extractTagsFromMetadata(std::string metadata);
};
} // namespace mirai
#endif

24767
lib/nlohmann/json.hpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,47 @@
import { Palette } from "Palette.slint";
import { VText } from "Text.slint";
export component VButton inherits Rectangle {
in property<string> text;
in property<brush> text-color: Palette.foreground;
in property<image> icon-source;
in property<brush> icon-colorize;
in property<length> icon-size: 1rem;
in property enabled <=> ta.enabled;
callback clicked;
private property<bool> active: false;
background: enabled ? Palette.control-background : Palette.control-background.darker(0.2);
border-radius: 4px;
ta := TouchArea {
mouse-cursor: pointer;
clicked => {
active = !active;
root.clicked();
}
}
HorizontalLayout {
alignment: center;
spacing: 8px;
padding: 8px;
padding-top: 4px;
padding-bottom: 4px;
if root.icon-source.width != 0 : Image {
padding: 8px;
source: icon-source;
colorize: icon-colorize;
width: icon-size;
}
if root.text != "" : VerticalLayout {
VText {
text: root.text;
color: root.text-color;
horizontal-alignment: center;
}
}
}
}

View file

@ -0,0 +1,42 @@
import { VLabeledComponent } from "LabeledComponent.slint";
import { VText } from "Text.slint";
import { Palette } from "Palette.slint";
export component VCheckBox {
in-out property<string> text;
in property <brush> color <=> textComponent.color;
in-out property<bool> checked: false;
callback toggled(bool); // toggled(state: bool)
HorizontalLayout {
spacing: 8px;
VerticalLayout {
alignment: center;
if checked : Rectangle {
background: Palette.accent;
height: 1rem;
width: self.height;
border-radius: 32px;
}
if !checked : Rectangle {
border-color: Palette.accent;
border-width: 2px;
height: 1rem;
width: self.height;
border-radius: 32px;
}
}
textComponent := VText {
text: root.text;
vertical-alignment: center;
}
}
ta := TouchArea {
clicked => {
checked = !checked;
root.toggled(checked)
}
}
}

View file

@ -0,0 +1,27 @@
import { Button, DatePickerPopup, Date, Palette } from "std-widgets.slint";
import { VLabeledComponent } from "LabeledComponent.slint";
import { VButton } from "Button.slint";
export component VDatePicker inherits VLabeledComponent {
in-out property<Date> date;
in-out property<string> dateDisplay: formatZeroPadding(date.day) + "/" + formatZeroPadding(date.month) + "/" + date.year;
pure function formatZeroPadding(number: int) -> string {
if (number < 10) {
return "0\{number}";
}
return number;
}
button := VButton {
text: dateDisplay;
enabled: root.enabled;
clicked => { taskDateInput.show() }
}
taskDateInput := DatePickerPopup {
accepted(date) => { root.date = date }
}
}

View file

@ -0,0 +1,22 @@
import { Palette } from "Palette.slint";
import { VText } from "Text.slint";
export component VLabeledComponent {
in property<string> label <=> labelComponent.text;
in property<bool> enabled: true;
VerticalLayout {
labelComponent := VText {
}
Rectangle {
background: enabled ? Palette.control-background : Palette.control-background.darker(0.2);
border-radius: 4px;
VerticalLayout {
padding: 4px;
@children
}
}
}
}

View file

@ -0,0 +1,43 @@
// OneDark
//black = "#181a1f",
//bg0 = "#282c34",
//bg1 = "#31353f",
//bg2 = "#393f4a",
//bg3 = "#3b3f4c",
//bg_d = "#21252b",
//bg_blue = "#73b8f1",
//bg_yellow = "#ebd09c",
//fg = "#abb2bf",
//purple = "#c678dd",
//green = "#98c379",
//orange = "#d19a66",
//blue = "#61afef",
//yellow = "#e5c07b",
//cyan = "#56b6c2",
//red = "#e86671",
//grey = "#5c6370",
//light_grey = "#848b98",
//dark_cyan = "#2b6f77",
//dark_red = "#993939",
//dark_yellow = "#93691d",
//dark_purple = "#8a3fa0",
//diff_add = "#31392b",
//diff_delete = "#382b2c",
//diff_change = "#1c3448",
//diff_text = "#2c5372",
// ----
export global Palette {
out property<brush> accent: Colors.cyan.darker(0.4);
out property<brush> foreground: #abb2bf;
out property<brush> foreground-hint: foreground.darker(0.2);
out property<brush> background: #282c34;
out property<brush> pane: #21252b;
out property<brush> control-foreground: #abb2bf;
out property<brush> control-background: #393f4a;
out property<brush> card-background: background.brighter(0.2);
out property<brush> green: Colors.greenyellow;
out property<brush> orange: Colors.orange;
out property<brush> red: Colors.red;
}

View file

@ -0,0 +1,27 @@
import { Palette } from "Palette.slint";
import { VButton } from "Button.slint";
export component VPopupIconMenu inherits Rectangle {
private property<length> popup-x: 200px;
private property<length> popup-y: 200px;
public function show(x: length, y: length) {
popup-x = x;
popup-y = y;
popup.show();
}
popup := PopupWindow {
x: popup-x;
y: popup-y;
Rectangle {
background: Palette.background.brighter(0.2);
border-radius: 8px;
clip: true;
HorizontalLayout {
alignment: start;
@children
}
}
}
}

31
lib/slint-vynui/Tag.slint Normal file
View file

@ -0,0 +1,31 @@
import { Palette } from "Palette.slint";
import { VText } from "Text.slint";
export component VTag inherits Rectangle {
in property text <=> text-component.text;
in property text-color <=> text-component.color;
in property size <=> text-component.font-size;
in property background-color <=> self.background;
callback clicked;
background: Palette.control-background;
border-radius: 8px;
ta := TouchArea {
mouse-cursor: pointer;
clicked => {
root.clicked();
}
}
VerticalLayout {
padding-left: 8px;
padding-right: 8px;
alignment: center;
text-component := VText {
horizontal-alignment: center;
}
}
}

View file

@ -0,0 +1,5 @@
import { Palette } from "Palette.slint";
export component VText inherits Text {
color: Palette.foreground;
}

View file

@ -0,0 +1,13 @@
import { VLabeledComponent } from "LabeledComponent.slint";
import { Palette } from "Palette.slint";
export component VTextInput inherits VLabeledComponent {
in-out property text <=> textInputComponent.text;
in-out property wrap <=> textInputComponent.wrap;
callback accepted();
textInputComponent := TextInput {
color: Palette.foreground;
accepted => { root.accepted() }
}
}

View file

@ -0,0 +1,25 @@
import { Button, TimePickerPopup, Date, Palette, Time } from "std-widgets.slint";
import { VLabeledComponent } from "LabeledComponent.slint";
import { VButton } from "Button.slint";
export component VTimePicker inherits VLabeledComponent {
in-out property<Time> time;
in-out property<string> timeDisplay: formatZeroPadding(time.hour) + "h" + formatZeroPadding(time.minute);
pure function formatZeroPadding(number: int) -> string {
if (number < 10) {
return "0\{number}";
}
return number;
}
button := VButton {
text: timeDisplay;
enabled: root.enabled;
clicked => { timePickerPopup.show() }
}
timePickerPopup := TimePickerPopup {
accepted(time) => { root.time = time }
}
}

View file

@ -0,0 +1,35 @@
import { Palette } from "Palette.slint";
import { VText } from "Text.slint";
export component ToggleButton inherits Rectangle {
in property text <=> text-component.text;
in property text-color <=> text-component.color;
in property text-alignment <=> text-component.horizontal-alignment;
callback clicked;
private property<bool> active: false;
background: root.active ? Palette.control-background
: ta.has-hover ? Palette.control-background.transparentize(0.5)
: Colors.transparent;
border-radius: 4px;
ta := TouchArea {
mouse-cursor: pointer;
clicked => {
active = !active;
root.clicked();
}
}
VerticalLayout {
padding: 8px;
padding-top: 4px;
padding-bottom: 4px;
text-component := VText {
horizontal-alignment: center;
}
}
}

View file

@ -0,0 +1,11 @@
export { Palette } from "Palette.slint";
export { VButton } from "Button.slint";
export { VText } from "Text.slint";
export { VCheckBox } from "CheckBox.slint";
export { VDatePicker } from "DatePicker.slint";
export { VTimePicker } from "TimePicker.slint";
export { VLabeledComponent } from "LabeledComponent.slint";
export { VPopupIconMenu } from "PopupIconMenu.slint";
export { VTag } from "Tag.slint";
export { VTextInput } from "TextInput.slint";
export { ToggleButton } from "ToggleButton.slint";

View file

@ -1,61 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef MIRAI_ANDROID_FILE_RESOURCE_H
#define MIRAI_ANDROID_FILE_RESOURCE_H
#include "QDebug"
#include "QFile"
#include "core/BaseFileResource.h"
#include "core/TodoMd.h"
namespace mirai
{
class AndroidFileResource : public BaseFileResource
{
public:
AndroidFileResource(BaseFileResourceConstructor data) : BaseFileResource(data){};
AndroidFileResource(AndroidFileResource &) = delete;
AndroidFileResource(AndroidFileResource &&) = delete;
AndroidFileResource operator=(AndroidFileResource &) = delete;
AndroidFileResource operator=(AndroidFileResource &&) = delete;
~AndroidFileResource() override = default;
void save() override
{
QFile file(QString::fromStdString(getPath()));
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
throw std::runtime_error("can't create " + getPath());
}
const std::string content = TodoMdFormat::stringifyTasks(getName(), getTasks());
file.write(content.c_str());
file.close();
};
void load() override
{
qDebug() << "Reading " << QString::fromStdString(getPath());
QFile file(QString::fromStdString(getPath()));
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "Can't read";
return;
}
QString content = file.readAll();
file.close();
auto result = TodoMdFormat::parse(content.toStdString());
for (const auto &task : result.tasks) {
addTask(task);
}
setName(result.name);
};
};
} // namespace mirai
#endif

View file

@ -1,381 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "Backend.h"
#include "AndroidFileResource.h"
#include "TaskItem.h"
#include "core/StdFileResource.h"
#include "core/TaskItem.h"
#include "core/TasksView.h"
#include "core/TodoMd.h"
#include "cpp-utils/debug.h"
#include <algorithm>
#include <exception>
#include <iostream>
#include <memory>
#include <ostream>
#include <qjsonarray.h>
#include <qjsonvalue.h>
#include <qlogging.h>
#include <qstandardpaths.h>
#include <qvariant.h>
#include <sstream>
Backend::Backend() : todoView(&mirai)
{
cpputils::debug::Timer startDuration;
std::cout << "Backend created" << std::endl;
cpputils::debug::Timer readConfigDuration;
QFile loadFile(getConfigFilePath());
readConfigDuration.printTimeElapsed("Read config duration");
if (loadFile.open(QIODevice::ReadOnly)) {
QByteArray loadData = loadFile.readAll();
configJson = QJsonDocument::fromJson(loadData);
loadFile.close();
if (!configJson.isObject()) {
qWarning() << "config.json is not a valid config file";
exit(1);
}
QJsonObject jsonRootObject = configJson.object();
auto jsonFilesPath = configJson["files"];
if (!jsonFilesPath.isArray()) {
qWarning() << "config.json should contains a 'files' string array";
exit(1);
}
for (const QJsonValueRef &filePath : jsonFilesPath.toArray()) {
cpputils::debug::Timer loadingFileDuration;
auto fileResource =
std::make_unique<mirai::AndroidFileResource>(mirai::BaseFileResourceConstructor{
.name = "[Can't load path]", .path = filePath.toString().toStdString()
});
mirai.loadResource(std::move(fileResource));
loadingFileDuration.printTimeElapsed(
"Loading file duration of " + filePath.toString().toStdString()
);
}
auto jsonTagsConfig = configJson["tags"];
if (jsonTagsConfig.isObject()) {
for (auto &jsonTagConfigKey : jsonTagsConfig.toObject().keys()) {
tagsConfig[jsonTagConfigKey] =
jsonTagsConfig.toObject()[jsonTagConfigKey].toObject()["color"].toString();
}
}
} else {
qWarning() << "Couldn't find existing config file";
}
cpputils::debug::Timer updatingViewDuration;
todoView.update();
updatingViewDuration.printTimeElapsed("Updating view duration");
cpputils::debug::Timer rebuildQMLTasksListDuration;
rebuildQMLTasksList();
rebuildQMLTasksListDuration.printTimeElapsed("Rebuilding QML duration");
startDuration.printTimeElapsed("Start duration");
}
void Backend::addTodoFromRawFormat(QString filePath, QString text, QString date)
{
auto resource = std::ranges::find_if(
mirai.getResources(),
[&](std::unique_ptr<mirai::BaseResource> &resource) {
auto fsResource = dynamic_cast<mirai::BaseFileResource *>(resource.get());
if (!fsResource) {
return false;
}
return fsResource->getPath() == filePath.toStdString();
}
);
if (resource == mirai.getResources().end()) {
qWarning() << "File path '" << filePath << "' doesn't exist" << Qt::endl;
return;
}
resource->get()->addTask(
mirai::TodoMdFormat::stringToTask(text.toStdString(), date.toStdString())
);
mirai.save();
todoView.update();
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::addTagFilter(QString tag)
{
todoView.addTagFilter(tag.toStdString());
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::removeTagFilter(QString tag)
{
todoView.removeTagFilter(tag.toStdString());
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::addResourceFilter(QString fileName)
{
todoView.addResourceFilter(fileName.toStdString());
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::removeResourceFilter(QString fileName)
{
todoView.removeResourceFilter(fileName.toStdString());
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::removeFilters()
{
todoView.removeFilters();
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::updateTodoFromRawFormat(int todoIndex, QString text, QString date)
{
QMLTaskItem &taskItem = QMLTasks[todoIndex];
taskItem.taskItem->setText(taskItem.taskItem->getText());
*(taskItem.taskItem) =
mirai::TodoMdFormat::stringToTask(text.toStdString(), date.toStdString());
mirai.save();
todoView.update();
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::updateTodo(int todoIndex, QString state, QString text, QString date)
{
QMLTaskItem &taskItem = QMLTasks[todoIndex];
if (state == "DONE") {
taskItem.taskItem->markAsDone();
} else if (state == "TODO") {
taskItem.taskItem->markAsUndone();
}
taskItem.taskItem->setText(text.toStdString());
taskItem.taskItem->setDate(date.toStdString());
mirai.save();
todoView.update();
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::removeTodo(int todoIndex)
{
QMLTaskItem &taskItem = QMLTasks[todoIndex];
mirai.removeTask(taskItem.taskItem);
mirai.save();
todoView.update();
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::hideCompletedTasks(bool shouldHide)
{
shouldHideCompletedTasks_ = shouldHide;
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::rebuildQMLTasksList()
{
// TODO MOVE TO ANOTHER FILE
QMLResources.clear();
for (auto &resource : mirai.getResources()) {
QMLResources.push_back({.tasksFile = resource.get()});
}
// ----
QMLTasks.clear();
std::string lastDate = "";
std::time_t t = std::time(nullptr);
std::tm tm = *std::localtime(&t);
std::stringstream currentDate;
currentDate << std::put_time(&tm, "%Y-%m-%d");
for (int i = 0; i < todoView.count(); ++i) {
mirai::TaskItem &task = todoView[i];
if (shouldHideCompletedTasks_ && task.getState() == mirai::DONE && task.hasDate() &&
task.getDate() < currentDate.str()) {
continue;
}
bool shouldShowDate = false;
if (lastDate != task.getDate()) {
lastDate = task.getDate();
shouldShowDate = true;
}
QList<QString> qStringTags;
for (auto &tag : task.getTags()) {
qStringTags.push_back(QString::fromStdString(tag));
}
QMLTasks.push_back(
{.taskItem = &task, .shouldShowDate = shouldShowDate, .tags = qStringTags}
);
}
QMLTags.clear();
for (auto &tag : mirai.getTags()) {
QMLTags.push_back({
.name = QString::fromStdString(tag),
.color = getTagColor(QString::fromStdString(tag)),
});
}
QMLActiveTagsFilter.clear();
for (auto &activeTagFilter : todoView.getActiveTagsFilter()) {
QMLActiveTagsFilter.push_back(QString::fromStdString(activeTagFilter));
}
QMLActiveResourcesFilter.clear();
for (auto &activeFileFilter : todoView.getActiveFilesFilter()) {
QMLActiveResourcesFilter.push_back(QString::fromStdString(activeFileFilter));
}
}
QVariant Backend::getTasks()
{
return QVariant::fromValue(QMLTasks);
}
QVariant Backend::getTags()
{
return QVariant::fromValue(QMLTags);
}
QVariant Backend::getActiveTagsFilter()
{
return QVariant::fromValue(QMLActiveTagsFilter);
}
QVariant Backend::getActiveResourcesFilter()
{
return QVariant::fromValue(QMLActiveResourcesFilter);
}
bool Backend::shouldHideCompletedTasks()
{
return shouldHideCompletedTasks_;
}
QVariant Backend::getResources()
{
return QVariant::fromValue(QMLResources);
}
QString Backend::getTagColor(QString tag)
{
if (!tagsConfig.contains(tag)) {
return "#c6d0f5"; // TODO: currently hard coded but should match the default Text color.
}
return tagsConfig[tag];
}
void Backend::saveTagsColor(QVariant modifiedTags)
{
auto aa = modifiedTags.toList();
for (auto bb : aa) {
auto cc = bb.toMap();
std::cout << cc["name"].toString().toStdString() << ": "
<< cc["color"].toString().toStdString() << std::endl;
tagsConfig[cc["name"].toString()] = cc["color"].toString();
}
rebuildQMLTasksList();
emit tagsChanged();
emit tasksChanged();
saveConfig();
}
void Backend::saveFilesPath(QVariant modifiedFilesPath)
{
// TODO: Make something better, rework rebuildQMLTasksList ?
auto paths = modifiedFilesPath.toStringList();
QMLTags.clear();
QMLTasks.clear();
QMLResources.clear();
emit tagsChanged();
emit tasksChanged();
emit filesChanged();
mirai.unloadAllResources();
for (auto path : paths) {
if (path == "") {
continue;
}
auto fileResource =
std::make_unique<mirai::AndroidFileResource>(mirai::BaseFileResourceConstructor{
.name = "[Can't load path]", .path = path.toStdString()
});
mirai.loadResource(std::move(fileResource));
}
todoView.update();
rebuildQMLTasksList();
emit tagsChanged();
emit tasksChanged();
emit filesChanged();
saveConfig();
}
void Backend::saveConfig()
{
QJsonObject rootJson;
QJsonArray filesJson;
for (auto &file : mirai.getResources()) {
auto fsResource = dynamic_cast<mirai::BaseFileResource *>(file.get());
if (fsResource) {
filesJson.append(QString::fromStdString(fsResource->getPath()));
}
}
rootJson["files"] = filesJson;
QJsonObject tagsJson;
for (auto &tag : tagsConfig.keys()) {
QJsonObject tagConfig;
tagConfig["color"] = getTagColor(tag);
tagsJson[tag] = tagConfig;
}
rootJson["tags"] = tagsJson;
rootJson["theme"] = configJson["theme"];
QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
QFile configFile(getConfigFilePath());
if (!configFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qWarning() << "Cannot save config: " << getConfigFilePath();
return;
}
configFile.write(QJsonDocument(rootJson).toJson());
configFile.close();
qDebug() << "Config file saved";
}
QString Backend::getConfigFilePath() const
{
QString configFolder = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
return configFolder + "/config.json";
}
QString Backend::getThemeColor(QString themeElementName)
{
if (configJson["theme"].isNull()) {
std::cerr << "Warning: theme in config is null" << std::endl;
return "";
}
if (!configJson["theme"].isObject()) {
std::cerr << "Warning: theme in config is not an object" << std::endl;
return "";
}
if (!configJson["theme"].toObject()[themeElementName].isString()) {
std::cerr << "Warning: element " << themeElementName.toStdString()
<< " in theme in config is not valid" << std::endl;
return "";
}
return configJson["theme"].toObject()[themeElementName].toString();
}

View file

@ -1,95 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef BACKEND_H
#define BACKEND_H
#include "QtCore/qcontainerfwd.h"
#include "QtCore/qtmetamacros.h"
#include "QtCore/qvariant.h"
#include "Tag.h"
#include "TasksFile.h"
#include "core/Mirai.h"
#include "core/TasksView.h"
#include <QtCore/QDateTime>
#include <QtCore/QDir>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QList>
#include <QtCore/QObject>
#include <QtCore/QString>
#include <QtQml/qqmlregistration.h>
#include <memory>
#include <qjsondocument.h>
#include <qmap.h>
#include "TaskItem.h"
class Backend : public QObject
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QVariant tasks READ getTasks NOTIFY tasksChanged)
Q_PROPERTY(QVariant files READ getResources NOTIFY filesChanged)
Q_PROPERTY(QVariant tags READ getTags NOTIFY tagsChanged)
Q_PROPERTY(QVariant activeTagsFilter READ getActiveTagsFilter NOTIFY tasksChanged)
Q_PROPERTY(QVariant activeResourcesFilter READ getActiveResourcesFilter NOTIFY tasksChanged)
Q_PROPERTY(bool shouldHideCompletedTasks READ shouldHideCompletedTasks WRITE hideCompletedTasks
NOTIFY tasksChanged)
public:
Backend();
Q_INVOKABLE void addTodoFromRawFormat(QString filePath, QString text, QString date);
Q_INVOKABLE void addTagFilter(QString tag);
Q_INVOKABLE void removeTagFilter(QString tag);
Q_INVOKABLE void addResourceFilter(QString fileName);
Q_INVOKABLE void removeResourceFilter(QString fileName);
Q_INVOKABLE void removeFilters();
Q_INVOKABLE void updateTodoFromRawFormat(int todoIndex, QString text, QString date);
Q_INVOKABLE void updateTodo(int todoIndex, QString state, QString text, QString date);
Q_INVOKABLE void removeTodo(int todoIndex);
Q_INVOKABLE void hideCompletedTasks(bool shouldHide);
Q_INVOKABLE QString getTagColor(QString tag);
Q_INVOKABLE void saveTagsColor(QVariant tags);
Q_INVOKABLE void saveFilesPath(QVariant filesPath);
Q_INVOKABLE QString getThemeColor(QString themeElementName);
private:
void rebuildQMLTasksList();
QVariant getTasks();
QVariant getResources();
QVariant getTags();
QVariant getActiveTagsFilter();
QVariant getActiveResourcesFilter();
bool shouldHideCompletedTasks();
void saveConfig();
QString getConfigFilePath() const;
mirai::Mirai mirai;
// Both Todo and Calendar view use the todoView for now.
mirai::TasksView todoView;
QJsonDocument configJson;
QList<QMLTaskItem> QMLTasks;
QList<QMLTasksFile> QMLResources;
QList<QMLTag> QMLTags;
QList<QString> QMLActiveTagsFilter;
QList<QString> QMLActiveResourcesFilter;
QMap<QString, QString> tagsConfig;
bool shouldHideCompletedTasks_ = true;
signals:
void tasksChanged();
void tagsChanged();
void filesChanged();
};
#endif

View file

@ -1,17 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "Tag.h"
QString QMLTag::getName()
{
return name;
}
QString QMLTag::getColor()
{
return color;
}

View file

@ -1,29 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef QML_TAG_H
#define QML_TAG_H
#include <QtCore/QString>
#include <QtQml/qqmlregistration.h>
#include <qobjectdefs.h>
struct QMLTag {
Q_GADGET
Q_PROPERTY(QString name READ getName)
Q_PROPERTY(QString color READ getColor)
QML_VALUE_TYPE(tag)
public:
QString getName();
QString getColor();
QString name;
QString color;
};
#endif

View file

@ -1,56 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "TaskItem.h"
#include "core/TodoMd.h"
QString QMLTaskItem::getRawFormat()
{
return QString::fromStdString(mirai::TodoMdFormat::taskToString(*taskItem));
}
QString QMLTaskItem::getText()
{
return QString::fromStdString(taskItem->getText());
}
QString QMLTaskItem::getState()
{
return QString::fromStdString(taskItem->getState() == mirai::TODO ? "TODO" : "DONE");
}
QString QMLTaskItem::getDate()
{
return QString::fromStdString(taskItem->getDate());
}
QString QMLTaskItem::getStartTime()
{
return QString::fromStdString(taskItem->getStartTime());
}
QString QMLTaskItem::getEndTime()
{
return QString::fromStdString(taskItem->getEndTime());
}
QString QMLTaskItem::getTime()
{
if (taskItem->getStartTime() != "" && taskItem->getEndTime() != "") {
return QString::fromStdString(taskItem->getStartTime() + "-" + taskItem->getEndTime());
}
return "";
}
QList<QString> QMLTaskItem::getTags()
{
return tags;
}
bool QMLTaskItem::getShouldShowDate()
{
return shouldShowDate;
}

View file

@ -1,46 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef QML_TASKITEM_H
#define QML_TASKITEM_H
#include "QtCore/qvariant.h"
#include <QtCore/QString>
#include <QtQml/qqmlregistration.h>
#include "core/TaskItem.h"
struct QMLTaskItem {
Q_GADGET
Q_PROPERTY(QString rawFormat READ getRawFormat)
Q_PROPERTY(QString text READ getText)
Q_PROPERTY(QString state READ getState)
Q_PROPERTY(QString date READ getDate)
Q_PROPERTY(QList<QString> tags READ getTags)
Q_PROPERTY(bool shouldShowDate READ getShouldShowDate)
Q_PROPERTY(QString time READ getTime)
Q_PROPERTY(QString startTime READ getStartTime)
Q_PROPERTY(QString endTime READ getEndTime)
QML_VALUE_TYPE(taskItem)
public:
QString getText();
QString getRawFormat();
QString getState();
QString getDate();
QString getTime();
QString getStartTime();
QString getEndTime();
QList<QString> getTags();
bool getShouldShowDate();
mirai::TaskItem *taskItem = nullptr;
bool shouldShowDate = false;
QList<QString> tags;
};
#endif

View file

@ -1,22 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "TasksFile.h"
#include "core/BaseFileResource.h"
QString QMLTasksFile::getName()
{
return QString::fromStdString(tasksFile->getName());
}
QString QMLTasksFile::getPath()
{
auto fileResource = dynamic_cast<mirai::BaseFileResource *>(tasksFile);
if (!fileResource) {
return "";
}
return QString::fromStdString(fileResource->getPath());
}

View file

@ -1,29 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef QML_TASKSFILE_H
#define QML_TASKSFILE_H
#include "QtCore/qvariant.h"
#include "core/BaseFileResource.h"
#include <QtCore/QString>
#include <QtQml/qqmlregistration.h>
struct QMLTasksFile {
Q_GADGET
Q_PROPERTY(QString name READ getName)
Q_PROPERTY(QString path READ getPath)
QML_VALUE_TYPE(tasksFile)
public:
QString getName();
QString getPath();
mirai::BaseResource *tasksFile;
};
#endif

433
src/UiState.cpp Normal file
View file

@ -0,0 +1,433 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "UiState.h"
#include "Utils.h"
#include "appwindow.h"
#include "mirai-core/Mirai.h"
#include "mirai-core/TaskItem.h"
#include "mirai-core/TodoMd.h"
#include "slint.h"
#include "slint_sharedvector.h"
#include "slint_string.h"
#include <algorithm>
#include <bits/chrono.h>
#include <cassert>
#include <charconv>
#include <chrono>
#include <ctime>
#include <format>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <memory>
#include <optional>
#include <ostream>
#include <ranges>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
UiState::UiState(mirai::Mirai *miraiInstance) : miraiInstance_(miraiInstance), view_(miraiInstance)
{
resources_ = std::make_shared<slint::VectorModel<slint::SharedString>>();
days_ = std::make_shared<slint::VectorModel<Day>>();
tags_ = std::make_shared<slint::VectorModel<slint::SharedString>>();
taskEditWindow_->global<Backend>().set_resources(resources_);
taskEditWindow_->global<Backend>().set_tags(tags_);
eventWindow_->global<Backend>().set_resources(resources_);
eventWindow_->global<Backend>().set_tags(tags_);
view_.update();
reloadResources();
reloadTags();
reloadTasks();
setupCallbacks();
}
std::optional<Date> stringToDate(const std::string &dateStr)
{
using std::operator""sv;
auto dateSplitView =
dateStr | std::views::split("-"sv) | std::views::transform([](auto v) -> int {
int i = 0;
std::from_chars(v.data(), v.data() + v.size(), i);
return i;
});
std::vector<int> dateSplit{dateSplitView.begin(), dateSplitView.end()};
if (dateSplit.size() != 3) {
return std::nullopt;
}
auto year = dateSplit[0];
auto month = dateSplit[1];
auto day = dateSplit[2];
return Date{.year = year, .month = month, .day = day};
}
void UiState::setupEventWindowCallbacks()
{
mainWindow_->global<Backend>().on_open_new_event_form([&]() {
auto todayDate = std::chrono::year_month_day{
std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now())
};
eventWindow_->set_eventId(-1);
eventWindow_->set_taskTitle("");
eventWindow_->set_taskDate({
.year = static_cast<int>(todayDate.year()),
// Try to directly use `unsigned`
.month = static_cast<int>(static_cast<unsigned>(todayDate.month())),
.day = static_cast<int>(static_cast<unsigned>(todayDate.day())),
});
eventWindow_->show();
});
mainWindow_->global<Backend>().on_open_edit_event_form([&](int sourceId, int eventId) {
auto resource = miraiInstance_->getResourceById(sourceId);
assert(resource);
auto event = resource->getEventById(eventId);
assert(event);
eventWindow_->global<Backend>().set_resources(resources_);
eventWindow_->global<Backend>().set_tags(tags_);
eventWindow_->set_sourceId(sourceId);
eventWindow_->set_eventId(eventId);
eventWindow_->set_taskTitle(slint::SharedString(event->getText()));
eventWindow_->set_taskDate(MiraiDateToSlintDate(event->getDate()));
eventWindow_->set_startsAt(MiraiTimeToSlintTime(event->getStartTime()));
eventWindow_->set_endsAt(MiraiTimeToSlintTime(event->getEndTime()));
eventWindow_->show();
});
mainWindow_->global<Backend>().on_delete_event_clicked([&](int sourceId, int eventId) {
auto resource = miraiInstance_->getResourceById(sourceId);
assert(resource);
auto event = resource->getEventById(eventId);
assert(event);
auto day = event->day();
assert(day);
day->deleteEvent(*event);
miraiInstance_->save();
view_.update();
reloadTasks();
});
eventWindow_->on_create([&](NewEventParams newEventParams) {
const Date &date = newEventParams.date;
const std::string dateStr = SlintDateToStdString(date);
auto resource = miraiInstance_->getResourceById(newEventParams.sourceId);
auto day = resource->day(SlintDateToMiraiDate(date));
day->createEvent({
.description = std::string(newEventParams.title),
.date = SlintDateToMiraiDate(newEventParams.date),
.startTime = SlintTimeToMiraiTime(newEventParams.startsAt),
.endTime = SlintTimeToMiraiTime(newEventParams.endsAt),
});
miraiInstance_->save();
view_.update();
reloadTasks();
eventWindow_->hide();
});
eventWindow_->on_save([&](SaveEventParams newEventParams) {
const Date &date = newEventParams.date;
const std::string dateStr = SlintDateToStdString(date);
auto resource = miraiInstance_->getResourceById(newEventParams.sourceId);
assert(resource);
auto event = resource->getEventById(newEventParams.id);
assert(event);
event->setText(std::string(newEventParams.title));
event->setStartTime(SlintTimeToMiraiTime(newEventParams.startsAt));
event->setEndTime(SlintTimeToMiraiTime(newEventParams.endsAt));
// TODO we can't change the date of the event for now.
miraiInstance_->save();
view_.update();
reloadTasks();
eventWindow_->hide();
});
}
void UiState::setupUtilsCallbacks()
{
mainWindow_->global<Backend>().on_formatDate([&](const Date &date) {
std::chrono::year_month_day chronoDate{
std::chrono::year(date.year),
std::chrono::month(date.month),
std::chrono::day(date.day),
};
return std::format("{:%d %B}", chronoDate);
});
}
void UiState::setupCallbacks()
{
mainWindow_->global<Backend>().on_task_clicked([&](int sourceId, int taskId) {
auto resource = miraiInstance_->getResourceById(sourceId);
assert(resource);
auto task = resource->getTaskById(taskId);
assert(task);
task->getState() == mirai::DONE ? task->markAsUndone() : task->markAsDone();
miraiInstance_->save();
});
mainWindow_->global<Backend>().on_source_clicked([&](int index) {
const auto &source = miraiInstance_->getResourceById(index);
const auto &sourceName = source->getName();
if (std::ranges::find(view_.getActiveFilesFilter(), sourceName) ==
view_.getActiveFilesFilter().end()) {
view_.addResourceFilter(sourceName);
} else {
view_.removeResourceFilter(sourceName);
}
view_.update();
reloadTasks();
});
mainWindow_->global<Backend>().on_tag_clicked([&](int index) {
const std::string &tag = miraiInstance_->getTags().at(index);
if (std::ranges::find(view_.getActiveTagsFilter(), tag) ==
view_.getActiveTagsFilter().end()) {
view_.addTagFilter(tag);
} else {
view_.removeTagFilter(tag);
}
view_.update();
reloadTasks();
});
mainWindow_->global<Backend>().on_delete_task_clicked([&](int sourceId, int taskId) {
auto resource = miraiInstance_->getResourceById(sourceId);
assert(resource);
auto task = resource->getTaskById(taskId);
assert(task);
resource->deleteTask(*task);
miraiInstance_->save();
view_.update();
reloadTasks();
});
mainWindow_->global<Backend>().on_open_edit_task_form([&](int sourceId, int taskId) {
auto resource = miraiInstance_->getResourceById(sourceId);
assert(resource);
auto task = resource->getTaskById(taskId);
assert(task);
taskEditWindow_->set_taskResourceIndex(sourceId);
taskEditWindow_->set_taskId(task->id());
taskEditWindow_->set_taskTitle(slint::SharedString(task->getText()));
taskEditWindow_->set_taskDate(MiraiDateToSlintDate(task->day()->getDate()));
taskEditWindow_->on_save([&](SaveTaskData newTaskData) {
auto resource = miraiInstance_->getResourceById(newTaskData.sourceId);
assert(resource);
auto task = resource->getTaskById(newTaskData.id);
assert(task);
const Date &date = newTaskData.date;
const std::string dateStr = SlintDateToStdString(date);
auto taskData = mirai::TodoMdFormat::stringToTask(
"- [ ] " + std::string(newTaskData.title), dateStr
);
task->setText(taskData.text);
if (task->day()->getDate() != SlintDateToMiraiDate(date)) {
auto newDate = resource->day(SlintDateToMiraiDate(date));
newDate->createTask({
.text = taskData.text,
.state = task->getState(),
.tags = taskData.tags,
});
auto oldDate = task->day();
resource->deleteTask(*task);
}
miraiInstance_->save();
view_.update();
reloadTasks();
taskEditWindow_->hide();
});
taskEditWindow_->show();
});
mainWindow_->global<Backend>().on_open_new_task_form([&](OpenNewTaskFormParams params) {
taskEditWindow_->global<Backend>().set_resources(resources_);
taskEditWindow_->global<Backend>().set_tags(tags_);
auto todayDate = std::chrono::year_month_day{
std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now())
};
taskEditWindow_->set_taskId(-1);
taskEditWindow_->set_eventId(params.eventId);
taskEditWindow_->set_taskResourceIndex(
params.eventSourceId == -1 ? 0 : params.eventSourceId
);
taskEditWindow_->set_taskTitle("");
if (params.eventId == -1) {
taskEditWindow_->set_taskDate({
.year = static_cast<int>(todayDate.year()),
// Try to directly use `unsigned`
.month = static_cast<int>(static_cast<unsigned>(todayDate.month())),
.day = static_cast<int>(static_cast<unsigned>(todayDate.day())),
});
} else {
auto resource = miraiInstance_->getResourceById(params.eventSourceId);
assert(resource);
auto event = resource->getEventById(params.eventId);
assert(event);
taskEditWindow_->set_taskDate(MiraiDateToSlintDate(event->getDate()));
}
taskEditWindow_->on_create([&](NewTaskData newTaskData) {
const Date &date = newTaskData.date;
const std::string dateStr = SlintDateToStdString(date);
auto resource = miraiInstance_->getResourceById(newTaskData.sourceId);
auto task = mirai::TodoMdFormat::stringToTask(
"- [ ] " + std::string(newTaskData.title), dateStr
);
if (newTaskData.eventId == -1) {
auto day = resource->day(SlintDateToMiraiDate(date));
assert(day);
day->createTask(task);
} else {
auto event = resource->getEventById(newTaskData.eventId);
assert(event);
event->createTask(task);
}
miraiInstance_->save();
view_.update();
reloadTasks();
taskEditWindow_->hide();
});
taskEditWindow_->show();
});
mainWindow_->global<Backend>().on_toggle_show_completed_tasks([&] {
view_.hideCompletedTasks(!view_.shouldHideCompletedTasks());
view_.update();
reloadTasks();
});
mainWindow_->global<Backend>().set_resources(resources_);
mainWindow_->global<Backend>().set_tags(tags_);
mainWindow_->global<Backend>().set_visible_tasks(days_);
setupEventWindowCallbacks();
setupUtilsCallbacks();
}
std::shared_ptr<slint::VectorModel<slint::SharedString>>
stdToSlintStringVector(const std::vector<std::string> &stdVector)
{
auto slintVector = std::make_shared<slint::VectorModel<slint::SharedString>>();
for (const auto &item : stdVector) {
slintVector->push_back(slint::SharedString(item));
}
return slintVector;
}
void UiState::reloadTasks()
{
days_->clear();
if (miraiInstance_->getResources().size() == 0) {
return;
}
auto todayDate = mirai::Date(std::chrono::system_clock::now());
auto &days = view_;
auto slintDays = std::make_shared<slint::VectorModel<Day>>();
for (int dayIndex = 0; dayIndex < days.count(); ++dayIndex) {
auto &currentDay = view_[dayIndex];
auto slintEvents = std::make_shared<slint::VectorModel<Event>>();
auto slintDayTasks = std::make_shared<slint::VectorModel<TaskData>>();
slintDays->push_back(Day{
.sourceId = currentDay.day->source()->id(),
.id = dayIndex,
.date = MiraiDateToSlintDate(currentDay.day->getDate()),
.events = slintEvents,
.tasks = slintDayTasks,
.isLate = currentDay.day->getDate() < todayDate,
.isToday = currentDay.day->getDate() == todayDate,
});
for (int taskIndex = 0; taskIndex < currentDay.filteredTasks.size(); ++taskIndex) {
auto &task = currentDay.filteredTasks.at(taskIndex);
std::vector<slint::SharedString> tags;
std::transform(
task->getTags().begin(), task->getTags().end(), std::back_inserter(tags),
[&](const std::string &tag) {
return slint::SharedString(tag);
}
);
slintDayTasks->push_back({
.sourceId = task->source()->id(),
.id = task->id(),
.title = slint::SharedString(task->getText()),
.checked = task->getState() == mirai::DONE,
.tags = std::make_shared<slint::VectorModel<slint::SharedString>>(tags),
});
}
for (int eventIndex = 0; eventIndex < currentDay.filteredEvents.size(); ++eventIndex) {
auto &currentEvent = currentDay.filteredEvents.at(eventIndex);
auto slintTasks = std::make_shared<slint::VectorModel<TaskData>>();
slintEvents->push_back(Event{
.sourceId = currentEvent.event->day()->source()->id(),
.id = currentEvent.event->id(),
.title = slint::SharedString(currentEvent.event->getText()),
.startsAt = MiraiTimeToSlintTime(currentEvent.event->getStartTime()),
.endsAt = MiraiTimeToSlintTime(currentEvent.event->getEndTime()),
.tasks = slintTasks,
});
for (int taskIndex = 0; taskIndex < currentEvent.filteredTasks.size(); ++taskIndex) {
auto &task = currentEvent.filteredTasks.at(taskIndex);
std::vector<slint::SharedString> tags;
std::transform(
task->getTags().begin(), task->getTags().end(), std::back_inserter(tags),
[&](const std::string &tag) {
return slint::SharedString(tag);
}
);
slintTasks->push_back({
.sourceId = task->source()->id(),
.id = task->id(),
.title = slint::SharedString(task->getText()),
.checked = task->getState() == mirai::DONE,
.tags = std::make_shared<slint::VectorModel<slint::SharedString>>(tags),
});
}
}
}
days_ = slintDays;
mainWindow_->global<Backend>().set_visible_tasks(days_);
}
void UiState::reloadResources()
{
resources_->clear();
for (const auto &resource : miraiInstance_->getResources()) {
resources_->push_back(slint::SharedString(resource->getName()));
}
}
void UiState::reloadTags()
{
tags_->clear();
for (const auto &tag : miraiInstance_->getTags()) {
tags_->push_back(slint::SharedString(tag));
}
}
void UiState::run()
{
mainWindow_->run();
}

41
src/UiState.h Normal file
View file

@ -0,0 +1,41 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#pragma once
#include "appwindow.h"
#include "mirai-core/Mirai.h"
#include "mirai-core/TasksView.h"
#include "slint.h"
class UiState
{
public:
UiState(mirai::Mirai *mirai);
void run();
void reloadResources();
void reloadTags();
void reloadTasks();
private:
void setupCallbacks();
void setupEventWindowCallbacks();
void setupUtilsCallbacks();
std::shared_ptr<slint::VectorModel<slint::SharedString>> resources_;
std::shared_ptr<slint::VectorModel<slint::SharedString>> tags_;
std::shared_ptr<slint::VectorModel<Day>> days_;
slint::ComponentHandle<AppWindow> mainWindow_ = AppWindow::create();
slint::ComponentHandle<TaskEdit> taskEditWindow_ = TaskEdit::create();
slint::ComponentHandle<EventWindow> eventWindow_ = EventWindow::create();
mirai::Mirai *miraiInstance_;
mirai::TasksView view_;
};

47
src/Utils.cpp Normal file
View file

@ -0,0 +1,47 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "Utils.h"
std::string formatZeroPadding(const int number)
{
if (number < 10) {
return "0" + std::to_string(number);
}
return std::to_string(number);
}
std::string SlintDateToStdString(const Date &date)
{
return std::to_string(date.year) + "-" + formatZeroPadding(date.month) + "-" +
formatZeroPadding(date.day);
}
mirai::Date SlintDateToMiraiDate(const Date &date)
{
return mirai::Date(
date.year, static_cast<unsigned>(date.month), static_cast<unsigned>(date.day)
);
}
Date MiraiDateToSlintDate(const mirai::Date &date)
{
return {
.year = date.year,
.month = static_cast<int>(date.month),
.day = static_cast<int>(date.day),
};
}
Time MiraiTimeToSlintTime(const mirai::Time &time)
{
return {.hour = time.hour, .minute = time.minute, .second = 0};
}
mirai::Time SlintTimeToMiraiTime(const Time &time)
{
return {.hour = time.hour, .minute = time.minute};
}

18
src/Utils.h Normal file
View file

@ -0,0 +1,18 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#pragma once
#include "appwindow.h"
#include "mirai-core/DateTime.h"
#include <string>
std::string formatZeroPadding(const int number);
std::string SlintDateToStdString(const Date &date);
mirai::Date SlintDateToMiraiDate(const Date &date);
Date MiraiDateToSlintDate(const mirai::Date &date);
Time MiraiTimeToSlintTime(const mirai::Time &time);
mirai::Time SlintTimeToMiraiTime(const Time &time);

View file

@ -1,52 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "BaseResource.h"
#include "TaskItem.h"
#include <algorithm>
#include <functional>
#include <iostream>
#include <memory>
#include <ostream>
namespace mirai
{
void BaseResource::setDirty(bool shouldBeDirty)
{
isDirty_ = shouldBeDirty;
}
bool BaseResource::isDirty() const
{
return isDirty_;
}
std::vector<std::unique_ptr<TaskItem>> &BaseResource::getTasks()
{
return tasks;
}
void BaseResource::addTask(TaskItemData taskItem)
{
tasks.push_back(std::make_unique<TaskItem>(this, taskItem));
setDirty(true);
}
void BaseResource::removeTask(const TaskItem *taskToRemove)
{
auto findFunction = [&](const std::unique_ptr<TaskItem> &taskInFilter) {
return taskInFilter.get() == taskToRemove;
};
auto taskToDelete = std::remove_if(tasks.begin(), tasks.end(), findFunction);
if (taskToDelete == tasks.end()) {
return;
}
tasks.erase(taskToDelete);
setDirty(true);
}
} // namespace mirai

View file

@ -1,124 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "TasksView.h"
#include "core/Mirai.h"
#include "utils.h"
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <memory>
#include <ostream>
#include <stdexcept>
#include <string>
namespace mirai
{
TasksView::TasksView(Mirai *miraiInstance) : mirai(miraiInstance)
{
if (!miraiInstance) {
throw std::runtime_error("NULL pointer passed in TasksView");
}
update();
}
TaskItem &TasksView::operator[](int index)
{
return *(tasksToShow.at(index));
}
size_t TasksView::count() const
{
return tasksToShow.size();
}
void TasksView::update()
{
tasksToShow.clear();
for (auto &file : mirai->getResources()) {
if (resourcesFilter.size() > 0 &&
!vectorUtils::contains(resourcesFilter, file->getName())) {
continue;
}
for (auto &task : file->getTasks()) {
if (tagsFilter.size() > 0 && !vectorUtils::containsAll(tagsFilter, task->getTags())) {
continue;
}
tasksToShow.push_back(task.get());
}
}
std::ranges::sort(tasksToShow, [](const TaskItem *t1, const TaskItem *t2) {
if (t1->hasDate() && !t2->hasDate()) {
return true;
} else if (!t1->hasDate() && t2->hasDate()) {
return false;
}
if (t1->getDate() < t2->getDate()) {
return true;
} else if (t1->getDate() > t2->getDate()) {
return false;
}
if (t1->hasTime() && !t2->hasTime()) {
return true;
} else if (!t1->hasTime() && t2->hasTime()) {
return false;
}
return t1->getStartTime() < t2->getStartTime();
});
}
void TasksView::addTagFilter(const std::string &tag)
{
tagsFilter.push_back(tag);
update();
}
void TasksView::removeTagFilter(const std::string &tag)
{
tagsFilter
.erase(std::remove_if(tagsFilter.begin(), tagsFilter.end(), [&](const auto &tagInFilter) {
return tag == tagInFilter;
}));
update();
}
void TasksView::addResourceFilter(const std::string &fileName)
{
resourcesFilter.push_back(fileName);
update();
}
void TasksView::removeResourceFilter(const std::string &fileName)
{
resourcesFilter.erase(std::remove_if(
resourcesFilter.begin(), resourcesFilter.end(),
[&](const auto &fileInFilter) {
return fileName == fileInFilter;
}
));
update();
}
void TasksView::removeFilters()
{
tagsFilter.clear();
update();
}
const std::vector<std::string> &TasksView::getActiveTagsFilter()
{
return tagsFilter;
}
const std::vector<std::string> &TasksView::getActiveFilesFilter()
{
return resourcesFilter;
}
} // namespace mirai

View file

@ -1,92 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef MIRAI_TODOMD_H
#define MIRAI_TODOMD_H
#include "TaskItem.h"
#include "cpp-utils/debug.h"
#include <sstream>
#include <string>
#include <vector>
namespace mirai
{
struct MiraiMarkdownFormatParseResult {
std::string name;
std::vector<TaskItemData> tasks;
};
class TodoMdFormat
{
public:
static std::string
stringifyTasks(const std::string &name, const std::vector<std::unique_ptr<TaskItem>> &tasks)
{
std::string result = "";
std::string currentDate = "";
result += "# " + name + "\n";
for (const auto &task : tasks) {
if (currentDate != task->getDate()) {
currentDate = task->getDate();
result += "\n## " + (task->getDate() != "" ? task->getDate() : "No date") + "\n\n";
}
result += taskToString(*task) + '\n';
}
return result;
};
static MiraiMarkdownFormatParseResult parse(const std::string &content)
{
cpputils::debug::Timer readMdFormatDuration;
std::vector<TaskItem> taskItems;
std::string line;
std::stringstream contentStream(content);
if (!std::getline(contentStream, line) || line.substr(0, 2) != "# ") {
std::cerr << "Couldn't find the task list name" << std::endl;
}
std::string name = line.substr(2);
std::string currentDate = "";
std::vector<TaskItemData> result;
cpputils::debug::Timer stringToTaskDuration;
stringToTaskDuration.reset();
cpputils::debug::Timer gelinesDuration;
while (std::getline(contentStream, line)) {
if (std::string_view{line.data(), 3} == "## ") {
currentDate = line.substr(3);
} else if (std::string_view{line.data(), 5} == "- [ ]" || std::string_view{line.data(), 5} == "- [X]") {
stringToTaskDuration.start();
TaskItemData taskItemData = stringToTask(line, currentDate);
stringToTaskDuration.stop();
taskItemData.date = currentDate;
result.push_back(taskItemData);
}
}
gelinesDuration.printTimeElapsed("getlinesDuration");
stringToTaskDuration.printTimeElapsed("stringToTaskDuration");
readMdFormatDuration.printTimeElapsed("Reading MD File duration");
return {.name = name, .tasks = result};
}
static std::string taskToString(const TaskItem &task);
static TaskItemData stringToTask(const std::string &str, const std::string &date);
private:
static std::string fieldWithSpace(const std::string &field);
static TaskItem parseLine(const std::string &line);
static Tags extractTagsFromMetadata(std::string metadata);
};
} // namespace mirai
#endif

View file

@ -4,48 +4,29 @@
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include <QtCore/QRect>
#include <QtGui/QFont>
#include <QtGui/QGuiApplication>
#include <QtGui/QScreen>
#include <QtQml/QQmlApplicationEngine>
#include <exception>
#include <iostream>
#include <ostream>
#include "UiState.h"
#include "mirai-core/Config.h"
#include "mirai-core/Mirai.h"
#include "mirai-core/StdFileResource.h"
#include "nlohmann/json.hpp"
#include <cstdlib>
#include <string>
int main(int argc, char *argv[])
int main(int argc, char **argv)
{
try {
std::cout << "Mirai started" << std::endl;
QGuiApplication app(argc, argv);
mirai::Config config(std::string(getenv("HOME")) + "/.config/mirai/config.json");
qreal refDpi = 54.;
qreal refHeight = 1440.;
qreal refWidth = 2560.;
QRect rect = QGuiApplication::primaryScreen()->geometry();
qreal height = qMax(rect.width(), rect.height());
qreal width = qMin(rect.width(), rect.height());
qreal dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch();
// auto m_ratio = qMin(height/refHeight, width/refWidth);
auto m_ratioFont =
qMin(height * refDpi / (dpi * refHeight), width * refDpi / (dpi * refWidth));
mirai::Mirai mirai;
QFont font("Helvetica");
app.setFont(font);
QQmlApplicationEngine engine;
const QUrl url(u"qrc:/qt/qml/Mirai/src/qml/Main.qml"_qs);
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreationFailed, &app,
[]() {
QCoreApplication::exit(-1);
},
Qt::QueuedConnection
for (const auto &sourceFilePath : config.sources()) {
auto file = std::make_unique<mirai::StdFileResource>(
mirai::BaseFileResourceConstructor{.name = sourceFilePath, .path = sourceFilePath}
);
engine.load(url);
return app.exec();
} catch (const std::exception &e) {
std::cout << e.what() << std::endl;
mirai.loadResource(std::move(file));
}
UiState uiState{&mirai};
uiState.run();
return 0;
}

View file

@ -1,129 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts
import Mirai
Window {
id: root
width: 640
height: 480
visible: true
title: qsTr("Hello World")
property bool isPhone: root.width < root.height
property string selectedView: "list"
Backend {
id: backend
}
ThemeLoader {
id: colorPalette
}
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
function openSettings() {
filesForm.reset()
filesFormPopup.open()
}
function newTask() {
taskForm.taskToEdit = undefined
taskForm.taskToEditIndex = -1
taskFormPopup.open()
}
function editTask(task, taskIndex) {
taskForm.taskToEdit = task
taskForm.taskToEditIndex = taskIndex
taskFormPopup.open()
}
Component {
id: sideMenuComponent
SideMenu {
id: sideMenu
}
}
Component {
id: mainPanelComponent
MainPanel {
}
}
RowLayout {
id: desktopLayout
anchors.fill: parent
spacing: 0
Loader {
sourceComponent: sideMenuComponent
Layout.fillHeight: true
Layout.fillWidth: root.isPhone
}
Loader {
sourceComponent: mainPanelComponent
Layout.fillWidth: true
Layout.fillHeight: true
}
}
SwipeView {
id: phoneLayout
anchors.fill: parent
spacing: 0
Loader {
sourceComponent: sideMenuComponent
Layout.fillHeight: true
}
Loader {
sourceComponent: mainPanelComponent
Layout.fillWidth: true
Layout.fillHeight: true
}
}
function setFittingLayout() {
if (root.isPhone) {
desktopLayout.visible = false
phoneLayout.visible = true
phoneLayout.setCurrentIndex(1)
} else {
desktopLayout.visible = true
phoneLayout.visible = false
}
}
onWidthChanged: setFittingLayout()
Component.onCompleted: setFittingLayout()
Modal {
id: taskFormPopup
fullScreen: root.isPhone
TaskForm {
id: taskForm
width: parent.width
onConfirmed: {
taskFormPopup.close()
}
onCanceled: {
taskFormPopup.close()
}
}
}
}

View file

@ -1,103 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts
import Mirai
Rectangle {
color: colorPalette.selected.background
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 16
TabSelector {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
tabs: [
{
label: "Todo",
onClick: () => {
backend.hideCompletedTasks(true) // Little workaround for now.
root.selectedView = "list"
},
selected: root.selectedView === "list"
},
{
label: "Calendar",
onClick: () => {
backend.hideCompletedTasks(false) // Little workaround for now.
root.selectedView = "calendar"
},
selected: root.selectedView === "calendar"
}
]
}
RowLayout {
AppButton {
icon.source: "qrc:/qt/qml/Mirai/src/images/add.png"
icon.color: colorPalette.selected.green
text: "Add task"
onClicked: {
root.newTask()
}
}
AppButton {
icon.source: backend.shouldHideCompletedTasks ? "qrc:/qt/qml/Mirai/src/images/not-visible.png" : "qrc:/qt/qml/Mirai/src/images/visible.png"
text: `Completed tasks`
onClicked: {
backend.hideCompletedTasks(!backend.shouldHideCompletedTasks)
}
}
}
Component {
id: listViewComponent
ListView {
}
}
Component {
id: calendarViewComponent
CalendarView {
}
}
Component {
id: gettingStartedComponent
AppText {
text: "You currently have no files loaded, you can add them by clicking on the cog icon on the left pane"
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
Loader {
sourceComponent: backend.tasks.length === 0 ? gettingStartedComponent
: selectedView === "list" ? listViewComponent
: selectedView === "calendar" ? calendarViewComponent
: undefined
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}

View file

@ -1,159 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import QtQuick.Controls
import Mirai
Rectangle {
color: colorPalette.selected.pane
implicitWidth: childrenRect.width + 20 + 30
ColumnLayout {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: 10
RowLayout {
AppText {
text: "Files"
font.pixelSize: 32
}
Item { Layout.fillWidth: true }
AppIcon {
icon.source: "qrc:/qt/qml/Mirai/src/images/settings.png"
icon.color: colorPalette.selected.textPlaceholder
onClicked: {
filesForm.reset();
filesFormPopup.open();
}
}
}
Item { Layout.preferredHeight: 16 }
Repeater {
model: backend.files
Rectangle {
Layout.preferredHeight: childrenRect.height
Layout.fillWidth: true
color: backend.activeResourcesFilter.includes(modelData.name) ? colorPalette.selected.filterSelected : mouse.hovered ? colorPalette.selected.filterHovered : "transparent"
radius: 4
AppText {
text: modelData.name
padding: 4
}
MouseArea {
anchors.fill: parent
onClicked: {
if (backend.activeResourcesFilter.includes(modelData.name)) {
backend.removeResourceFilter(modelData.name)
} else {
backend.addResourceFilter(modelData.name)
}
}
HoverHandler {
id: mouse
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
cursorShape: Qt.PointingHandCursor
}
}
}
}
Item { Layout.preferredHeight: 16 }
RowLayout {
AppText {
text: "Tags"
font.pixelSize: 32
}
Item { Layout.fillWidth: true }
AppIcon {
icon.source: "qrc:/qt/qml/Mirai/src/images/settings.png"
icon.color: colorPalette.selected.textPlaceholder
onClicked: {
tagsForm.reset();
tagsFormPopup.open();
}
}
}
Item { Layout.preferredHeight: 16 }
Repeater {
model: backend.tags
Rectangle {
Layout.preferredHeight: childrenRect.height
Layout.fillWidth: true
color: backend.activeTagsFilter.includes(modelData.name) ? colorPalette.selected.filterSelected : mouse.hovered ? colorPalette.selected.filterHovered : "transparent"
radius: 4
QtObject {
id: internal
}
AppText {
text: modelData.name
color: {
return modelData.color
}
padding: 4
}
MouseArea {
anchors.fill: parent
onClicked: {
if (backend.activeTagsFilter.includes(modelData.name)) {
backend.removeTagFilter(modelData.name)
} else {
backend.addTagFilter(modelData.name)
}
}
HoverHandler {
id: mouse
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
cursorShape: Qt.PointingHandCursor
}
}
}
}
Item {
Layout.fillHeight: true
}
Modal {
id: filesFormPopup
fullScreen: root.isPhone
FilesForm {
id: filesForm
width: parent.width
onConfirmed: (filesPath) => {
filesFormPopup.close()
console.log(filesPath)
backend.saveFilesPath(filesPath)
}
}
}
Modal {
id: tagsFormPopup
fullScreen: root.isPhone
TagsConfigForm {
id: tagsForm
width: parent.width
onConfirmed: (tags) => {
tagsFormPopup.close()
backend.saveTagsColor(tags)
}
}
}
}
}

View file

@ -1,49 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import Mirai
Item {
property QtObject selected: OneDark
function applyCustomThemeFromConfig() {
//console.log(backend.getThemeColor("text"))
console.log(Object.keys(selected))
selected.background = getThemeColor("background")
selected.pane = getThemeColor("pane")
selected.text = getThemeColor("text")
selected.textPlaceholder = getThemeColor("textPlaceholder")
selected.accent = getThemeColor("accent")
selected.fieldBackground = getThemeColor("fieldBackground")
selected.buttonIcon = getThemeColor("buttonIcon")
selected.buttonBackground = getThemeColor("buttonBackground")
selected.buttonHovered = getThemeColor("buttonHovered")
selected.filterHovered = getThemeColor("filterHovered")
selected.filterSelected = getThemeColor("filterSelected")
selected.modalBorder = getThemeColor("modalBorder")
selected.calendarLines = getThemeColor("calendarLines")
selected.calendarCurrentTime = getThemeColor("calendarCurrentTime")
selected.calendarEvent = getThemeColor("calendarEvent")
selected.green = getThemeColor("green")
selected.red = getThemeColor("red")
}
// Proxy function for backend.getThemeColor that do not override the value if
// no custom color are defined for this element
function getThemeColor(element) {
let elementColor = backend.getThemeColor(element)
if (!elementColor) {
return selected[element];
}
return elementColor;
}
Component.onCompleted: {
applyCustomThemeFromConfig()
}
}

View file

@ -1,44 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Mirai
Button {
id: control
property bool noBackgroundColor: false
icon.color: colorPalette.selected.buttonIcon
// I have a different behavior when setting padding for Android
padding: root.isPhone ? undefined : 8
contentItem: IconLabel {
spacing: control.spacing
mirrored: control.mirrored
display: control.display
icon: control.icon
text: control.text
font: control.font
color: colorPalette.selected.text
}
background: Rectangle {
color: control.noBackgroundColor ? "transparent"
: mouse.hovered ? colorPalette.selected.buttonHovered
: colorPalette.selected.buttonBackground
radius: 4
}
HoverHandler {
id: mouse
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
cursorShape: Qt.PointingHandCursor
}
}

View file

@ -1,41 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Controls
import Mirai
CheckBox {
id: control
property alias textComponent: text
contentItem: Text {
id: text
text: control.text
color: colorPalette.selected.text
verticalAlignment: Text.AlignVCenter
leftPadding: control.indicator.width + control.spacing
}
indicator: Rectangle {
x: control.leftPadding
y: control.topPadding + (control.availableHeight - height) / 2
implicitWidth: 12
implicitHeight: 12
radius: 999
color: control.checked ? colorPalette.selected.green : colorPalette.selected.text
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
implicitWidth: 10
implicitHeight: 10
radius: 999
color: control.checked ? colorPalette.selected.green : colorPalette.selected.fieldBackground
}
}
}

View file

@ -1,27 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Controls
import Mirai
ComboBox {
id: control
contentItem: AppText {
anchors.fill: parent
text: control.currentText
verticalAlignment: Text.AlignVCenter
leftPadding: 10
}
background: Rectangle {
implicitWidth: 200
implicitHeight: 32
color: colorPalette.selected.fieldBackground
radius: 4
}
}

View file

@ -1,22 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Controls
import Mirai
Button {
icon.color: colorPalette.selected.buttonIcon
background: Rectangle {
color: "transparent"
HoverHandler {
id: mouse
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
cursorShape: Qt.PointingHandCursor
}
}
}

View file

@ -1,21 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Controls
TextField {
color: colorPalette.selected.text
placeholderTextColor: colorPalette.selected.textPlaceholder
leftPadding: 10
implicitHeight: 32
background: Rectangle {
color: colorPalette.selected.fieldBackground
radius: 4
}
}

View file

@ -1,194 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import Mirai
ColumnLayout {
property int numberOfDaysPerPage: 7
id: control
spacing: 32
QtObject {
id: internal
property date weekStartDate: {
const firstDayOfTheWeek = 1 // 1 = Monday, hardcoded for now
const date = new Date()
let firstDayOfTheWeekDelta = date.getDay() - firstDayOfTheWeek
if (firstDayOfTheWeekDelta < 0) {
firstDayOfTheWeekDelta += 7
}
date.setDate(date.getDate() - firstDayOfTheWeekDelta)
return date
}
}
RowLayout {
spacing: 12
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
AppButton {
text: "<"
onClicked: {
internal.weekStartDate.setDate(internal.weekStartDate.getDate() - control.numberOfDaysPerPage)
}
}
AppText {
text: capitalize(internal.weekStartDate.toLocaleDateString(Qt.locale(), "MMMM"))
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.pointSize: 24
}
AppButton {
text: ">"
onClicked: {
internal.weekStartDate.setDate(internal.weekStartDate.getDate() + control.numberOfDaysPerPage)
}
}
}
RowLayout {
spacing: 0
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
Layout.fillHeight: true
spacing: 0
AppText {
text: ""
}
Rectangle {
color: "transparent"
Layout.preferredWidth: childrenRect.width + 10
Layout.fillHeight: true
Repeater {
model: 23 // Skip 00:00
AppText {
text: `${index + 1}:00`
y: (parent.height / 24 * (index + 1)) - (height / 2)
verticalAlignment: Text.AlignVCenter
}
}
}
}
Repeater {
model: control.numberOfDaysPerPage
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
property date day: new Date(new Date(internal.weekStartDate).setDate(internal.weekStartDate.getDate() + index))
color: "transparent"
Rectangle {
color: colorPalette.selected.calendarLines
height: parent.height
width: 1
anchors.left: parent.left
}
ColumnLayout {
anchors.fill: parent
spacing: 0
AppText {
Layout.fillWidth: true
text: capitalize(day.toLocaleDateString(Qt.locale(), "dddd dd"))
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.fillHeight: true
id: daysSurface
property real hourHeight: daysSurface.height / 24
Repeater {
model: 23 // Skip 00:00
Rectangle {
color: colorPalette.selected.calendarLines
height: 1
width: daysSurface.width
y: daysSurface.hourHeight * (index + 1)
}
}
Repeater {
model: backend.tasks.filter(task => {
if (task.startTime == "" || task.endTime == "") {
return false
}
const date = new Date(internal.weekStartDate)
date.setDate(internal.weekStartDate.getDate() + index)
return task.date === date.toLocaleDateString(Qt.locale(), "yyyy-MM-dd")
})
Rectangle {
property string name: modelData.text
property int startTime: parseInt(modelData.startTime)
property int endTime: parseInt(modelData.endTime)
color: colorPalette.selected.calendarEvent
anchors.right: parent.right
anchors.left: parent.left
anchors.rightMargin: 2
anchors.leftMargin: 2
radius: 4
y: daysSurface.hourHeight * startTime
height: (endTime - startTime) * daysSurface.hourHeight - 4
Rectangle {
anchors.fill: parent
color: backend.getTagColor(modelData.tags[0])
opacity: 0.2
}
Rectangle {
color: backend.getTagColor(modelData.tags[0])
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
topLeftRadius: parent.radius
bottomLeftRadius: parent.radius
width: 6
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 8
anchors.leftMargin: 12
AppText {
color: backend.getTagColor(modelData.tags[0])
Layout.fillWidth: true
Layout.fillHeight: true
text: name
wrapMode: Text.Wrap
}
}
}
}
Rectangle {
color: day.getFullYear() === new Date().getFullYear() &&
day.getMonth() === new Date().getMonth() &&
day.getDate() === new Date().getDate() ? colorPalette.selected.calendarCurrentTime : "transparent"
height: 2
width: parent.width
y: daysSurface.hourHeight * (new Date().getHours() + (new Date().getMinutes() / 60))
}
}
}
}
}
}
}

View file

@ -1,76 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts
import Mirai
Rectangle {
id: datePickerRoot
property alias text: newTodoDate.text
property alias textFieldComponent: newTodoDate
color: colorPalette.selected.fieldBackground
radius: 4
implicitHeight: 32
RowLayout {
anchors.fill: parent
AppIcon {
icon.source: "qrc:/qt/qml/Mirai/src/images/calendar.png"
Layout.preferredWidth: 32
Layout.preferredHeight: 32
}
AppLineEdit {
id: newTodoDate
Layout.fillWidth: true
Layout.fillHeight: true
readOnly: true
background: Rectangle {
color: "transparent"
}
MouseArea {
anchors.fill: parent
onClicked: {
datePicker.open()
}
}
}
Popup {
id: datePicker
implicitWidth: 300
implicitHeight: 300
x: datePickerRoot.x
y: datePickerRoot.y - 300 - 10
background: Rectangle {
color: colorPalette.selected.pane
border.color: colorPalette.selected.modalBorder
border.width: 2
}
DatePicker {
anchors.fill: parent
Component.onCompleted: set(new Date()) // today
onClicked: {
const formattedDate = Qt.formatDate(date, 'yyyy-MM-dd')
newTodoDate.text = formattedDate
datePicker.close()
}
onReset: {
newTodoDate.text = ""
datePicker.close()
}
}
}
}
}

View file

@ -1,124 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import Mirai
import QtQuick
ListView {
id: root
function set(date) {
selectedDate = new Date(date)
positionViewAtIndex((selectedDate.getFullYear()) * 12 + selectedDate.getMonth(), ListView.Center) // index from month year
}
signal clicked(date date);
signal reset();
property date selectedDate: new Date()
width: 500
height: 100
snapMode: ListView.SnapOneItem
orientation: Qt.Horizontal
clip: true
model: 3000 * 12 // index == months since January of the year 0
delegate: Item {
property int year: Math.floor(index / 12)
property int month: index % 12 // 0 January
property int firstDay: new Date(year, month, 1).getDay() // 0 Sunday to 6 Saturday
width: root.width
height: root.height
Column {
Item { // month year header
width: root.width
height: root.height - grid.height
AppText { // month year
anchors.centerIn: parent
text: ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'][month] + ' ' + year
font {pixelSize: 0.5 * grid.cellHeight}
}
AppButton {
text: "Remove"
onClicked: {
root.reset()
}
}
}
Grid { // 1 month calender
id: grid
width: root.width
height: 0.875 * root.height
property real cellWidth: width / columns;
property real cellHeight: height / rows // width and height of each cell in the grid.
columns: 7 // days
rows: 7
Repeater {
model: grid.columns * grid.rows // 49 cells per month
delegate: Rectangle { // index is 0 to 48
property int day: index - 7 // 0 = top left below Sunday (-7 to 41)
property int date: day - firstDay + 2 // 1-31
color: internal.selected ? colorPalette.selected.filterSelected
: mouseArea.containsMouse ? colorPalette.selected.filterHovered
: "transparent"
width: grid.cellWidth; height: grid.cellHeight
radius: 0.02 * root.height
opacity: !mouseArea.pressed? 1: 0.3
QtObject {
id: internal
property bool selected: new Date(year, month, date).toDateString() == selectedDate.toDateString() && text.text && day >= 0
}
AppText {
id: text
anchors.centerIn: parent
font.pixelSize: 0.5 * parent.height
font.bold: new Date(year, month, date).toDateString() == new Date().toDateString() // today
text: {
if(day < 0)
['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][index] // Su-Sa
else if(new Date(year, month, date).getMonth() == month)
date // 1-31
else
''
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
enabled: text.text && day >= 0
hoverEnabled: true
onClicked: {
selectedDate = new Date(year, month, date)
root.clicked(selectedDate)
}
}
}
}
}
}
}
// Component.onCompleted: set(new Date()) // today (otherwise Jan 0000)
}

View file

@ -1,26 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Controls
Popup {
property bool fullScreen: false
parent: Overlay.overlay
width: parent.width * (fullScreen ? 1 : 0.75)
height: fullScreen ? parent.height : undefined
x: Math.round((parent.width - width) / 2)
y: fullScreen ? 0 : Math.round((parent.height * 0.4) / 2)
padding: 8
background: Rectangle {
border.color: colorPalette.selected.modalBorder
border.width: 2
color: colorPalette.selected.pane
radius: 4
}
}

View file

@ -1,58 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import Mirai
RowLayout {
id: control
property var tabs: []
Repeater {
model: tabs
Item {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
AppText {
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
id: tabText
text: modelData.label
font.pointSize: 24
}
Rectangle {
Layout.fillWidth: true
color: modelData.selected ? "white" : mouse.hovered ? colorPalette.selected.filterHovered : "transparent"
implicitHeight: 2
}
}
MouseArea {
anchors.fill: parent
onClicked: {
modelData?.onClick()
}
HoverHandler {
id: mouse
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
cursorShape: Qt.PointingHandCursor
}
}
}
}
}

View file

@ -1,32 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import Mirai
Rectangle {
id: control
property string text
property string backgroundColor
property string textColor
color: backgroundColor
implicitWidth: childrenRect.width
implicitHeight: childrenRect.height
radius: 8
QtObject {
id: internal
}
AppText {
color: backend.getTagColor(control.text)
padding: 2
leftPadding: 6
rightPadding: 6
text: control.text
}
}

View file

@ -1,45 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import Mirai
RowLayout {
id: control
property taskItem task
function getFormatedText() {
if (task?.time && task.time != "") {
return `<font color=\"${colorPalette.selected.textPlaceholder}\">${task.time} \></font> ${task.text}`
}
return task.text
}
AppCheckbox {
id: checkbox
text: control.getFormatedText()
checked: task.state === 'DONE'
//textComponent.font.pointSize: 14
textComponent.color: task.date < internal.todayDate ? colorPalette.selected.red
// : task.date === internal.todayDate ? colorPalette.selected.palette.sapphire
: colorPalette.selected.text
onClicked: {
backend.updateTodo(index, modelData.state === 'DONE' ? "TODO" : "DONE", modelData.text, modelData.date)
}
}
Repeater {
model: task.tags
Tag {
Layout.alignment: Qt.AlignVCenter
text: modelData
backgroundColor: colorPalette.selected.fieldBackground
textColor: colorPalette.selected.accent
}
}
}

View file

@ -1,79 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import Mirai
// WIP
ColumnLayout {
id: form
spacing: 6
signal confirmed(paths: var)
function reset() {
internal.paths = []
internal.paths = backend.files.map(file => file.path)
}
QtObject {
id: internal
property var paths
}
AppText {
text: "Files"
font.pixelSize: 32
}
Item {
Layout.preferredHeight: 32
}
Repeater {
model: internal.paths
ColumnLayout {
AppLineEdit {
text: modelData
onTextChanged: {
internal.paths[index] = text
}
}
}
}
AppButton {
text: "Add"
icon.source: "qrc:/qt/qml/Mirai/src/images/add.png"
icon.color: colorPalette.selected.green
onClicked: {
fileDialog.open()
}
}
FileDialog {
id: fileDialog
onAccepted: {
console.log(selectedFile.toString())
internal.paths = [...internal.paths, selectedFile.toString().replace(/^file:\/\//, "")]
}
}
Item {
Layout.preferredHeight: 32
}
AppButton {
text: "Save"
onClicked: {
form.confirmed(internal.paths)
}
}
}

View file

@ -1,77 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import Mirai
ColumnLayout {
id: form
spacing: 6
signal confirmed(tags: var)
function reset() {
internal.tags = Qt.binding(function () { return [] })
internal.tags = Qt.binding(function () { return backend.tags.map(tag => {
return {name: tag.name, color: tag.color}
})})
}
QtObject {
id: internal
property var tags: []
}
Repeater {
id: tagsList
model: internal.tags
RowLayout {
Rectangle {
id: newTagColor
color: colorDialog.selectedColor
Layout.fillHeight: true
Layout.preferredWidth: 32
MouseArea {
anchors.fill: parent
onClicked: {
colorDialog.open()
}
HoverHandler {
id: mouse
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
cursorShape: Qt.PointingHandCursor
}
}
}
ColorDialog {
id: colorDialog
selectedColor: modelData.color
onAccepted: {
colorDialog.selectedColor = selectedColor
internal.tags[index].color = selectedColor
}
}
AppText {
text: modelData.name
color: colorDialog.selectedColor
}
}
}
AppButton {
text: "Save"
onClicked: {
form.confirmed(internal.tags)
}
}
}

View file

@ -1,95 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts
import Mirai
ColumnLayout {
id: form
property var taskToEdit
property int taskToEditIndex
spacing: 6
signal confirmed
signal canceled
onTaskToEditChanged: {
newTodoContent.text = taskToEdit?.rawFormat ?? "- [ ] "
newTodoDate.text = taskToEdit?.date ?? ""
}
function createTask() {
if (taskToEdit && taskToEditIndex !== undefined) {
backend.updateTodoFromRawFormat(taskToEditIndex, newTodoContent.text, newTodoDate.text)
} else {
backend.addTodoFromRawFormat(
file.currentValue,
newTodoContent.text,
newTodoDate.text != "" ? newTodoDate.text : "No date"
)
}
form.confirmed()
}
AppText {
text: "New/Edit task"
}
AppComboBox {
id: file
textRole: "text"
valueRole: "value"
// Set the initial currentIndex to the value stored in the backend.
Component.onCompleted: currentIndex = 0
model: backend.files.map(file => (
{ value: file.path, text: qsTr(file.name) }
))
onActivated: {
console.log(currentValue)
}
}
DateField {
id: newTodoDate
text: taskToEdit?.date ?? ""
textFieldComponent.placeholderText: "No date"
Layout.fillWidth: true
}
AppLineEdit {
id: newTodoContent
Layout.fillWidth: true
placeholderText: "Enter your new task..."
text: taskToEdit?.rawFormat ?? ""
Keys.onReturnPressed: {
if (newTodoContent.text == "") {
return
}
createTask()
}
}
RowLayout {
spacing: 8
AppButton {
text: "Create"
onClicked: {
createTask()
}
}
AppButton {
text: "Cancel"
onClicked: {
form.canceled()
}
}
}
}

View file

@ -1,38 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
pragma Singleton
import QtQuick 2.5
// Catppuccin Frappe theme (https://github.com/catppuccin/catppuccin)
QtObject {
property string rosewater: "#f2d5cf"
property string flamingo: "#eebebe"
property string pink: "#f4b8e4"
property string mauve: "#ca9ee6"
property string red: "#e78284"
property string maroon: "#ea999c"
property string peach: "#ef9f76"
property string yellow: "#e5c890"
property string green: "#a6d189"
property string teal: "#81c8be"
property string sky: "#99d1db"
property string sapphire: "#85c1dc"
property string blue: "#8caaee"
property string lavender: "#babbf1"
property string text: "#c6d0f5"
property string subtext1: "#b5bfe2"
property string subtext0: "#a5adce"
property string overlay2: "#949cbb"
property string overlay1: "#838ba7"
property string overlay0: "#737994"
property string surface2: "#626880"
property string surface1: "#51576d"
property string surface0: "#414559"
property string base: "#303446"
property string mantle: "#292c3c"
property string crust: "#232634"
}

View file

@ -1,27 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
pragma Singleton
import QtQuick 2.5
import Mirai
QtObject {
property QtObject palette: OneDark
property string background: CatppuccinFrappe.base
property string pane: CatppuccinFrappe.mantle
property string text: CatppuccinFrappe.text
property string textPlaceholder: CatppuccinFrappe.overlay1
property string fieldBackground: CatppuccinFrappe.surface0
property string buttonIcon: CatppuccinFrappe.sapphire
property string buttonBackground: CatppuccinFrappe.surface0
property string buttonHovered: CatppuccinFrappe.surface1
property string filterHovered: CatppuccinFrappe.surface0
property string filterSelected: CatppuccinFrappe.surface1
property string modalBorder: CatppuccinFrappe.lavender
property string calendarLines: CatppuccinFrappe.surface0
property string calendarCurrentTime: CatppuccinFrappe.red
property string calendarEvent: CatppuccinFrappe.overlay0
}

View file

@ -1,55 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
pragma Singleton
import QtQuick 2.5
//black = "#181a1f",
//bg0 = "#282c34",
//bg1 = "#31353f",
//bg2 = "#393f4a",
//bg3 = "#3b3f4c",
//bg_d = "#21252b",
//bg_blue = "#73b8f1",
//bg_yellow = "#ebd09c",
//fg = "#abb2bf",
//purple = "#c678dd",
//green = "#98c379",
//orange = "#d19a66",
//blue = "#61afef",
//yellow = "#e5c07b",
//cyan = "#56b6c2",
//red = "#e86671",
//grey = "#5c6370",
//light_grey = "#848b98",
//dark_cyan = "#2b6f77",
//dark_red = "#993939",
//dark_yellow = "#93691d",
//dark_purple = "#8a3fa0",
//diff_add = "#31392b",
//diff_delete = "#382b2c",
//diff_change = "#1c3448",
//diff_text = "#2c5372",
QtObject {
property string background: "#282c34"
property string pane: "#21252b"
property string text: "#abb2bf"
property string textPlaceholder: "#5c6370"
property string accent: "#2b6f77"
property string fieldBackground: "#393f4a"
property string buttonIcon: "#2b6f77"
property string buttonBackground: "#31353f"
property string buttonHovered: "#3b3f4c"
property string filterHovered: "#5c6370"
property string filterSelected: "#3b3f4c"
property string modalBorder: "#8a3fa0"
property string calendarLines: "#3b3f4c"
property string calendarCurrentTime: "#e86671"
property string calendarEvent: "#3b3f4c"
property string green: "#98c379"
property string red: "#e86671"
}

View file

@ -1,24 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import Mirai
ColumnLayout {
Rectangle {
color: "transparent"
Layout.preferredWidth: parent.width
Layout.fillHeight: true
Calendar {
width: parent.width
height: parent.height
numberOfDaysPerPage: root.isPhone ? 1 : 7
}
}
}

View file

@ -1,99 +0,0 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts
import Mirai
ScrollView {
id: scroll
clip: true
contentHeight: layout.height
ColumnLayout {
id: layout
anchors.right: parent.right
anchors.left: parent.left
QtObject {
id: internal
property string todayDate: Qt.formatDate(new Date(), 'yyyy-MM-dd')
property string tomorrowDate: Qt.formatDate(new Date(new Date().setDate(new Date().getDate() + 1)), 'yyyy-MM-dd')
}
Repeater {
model: backend.tasks
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
id: task
required property var modelData
required property int index
color: "transparent"
HoverHandler {
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
cursorShape: Qt.PointingHandCursor
}
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
Component {
id: dateForTasks
AppText {
topPadding: 32
bottomPadding: 16
text: task.modelData.date === internal.todayDate ? "Today"
: task.modelData.date === internal.tomorrowDate ? "Tomorrow"
: task.modelData.date
color: task.modelData.date < internal.todayDate ? colorPalette.selected.red
// : task.modelData.date === internal.todayDate ? colorPalette.selected.palette.sapphire
: colorPalette.selected.text
font.pointSize: 24
}
}
Loader {
sourceComponent: task.modelData.shouldShowDate ? dateForTasks : undefined
}
TaskItem {
id: taskItem
task: task.modelData
}
}
Menu {
id: contextMenu
MenuItem {
text: "Edit"
onClicked: {
root.editTask(task.modelData, task.index)
}
}
MenuItem {
text: "Delete"
onClicked: {
backend.removeTodo(task.index)
}
}
}
MouseArea {
id: mouse
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
contextMenu.popup()
}
}
}
}
}
}

55
ui/Backend.slint Normal file
View file

@ -0,0 +1,55 @@
import { Date, Time } from "std-widgets.slint";
export struct TaskData {
sourceId: int,
id: int,
title: string,
checked: bool,
tags: [string],
}
export struct Event {
sourceId: int,
id: int,
title: string,
startsAt: Time,
endsAt: Time,
tasks: [TaskData],
}
export struct Day {
sourceId: int,
id: int,
date: Date,
events: [Event],
tasks: [TaskData],
isLate: bool,
isToday: bool
}
struct OpenNewTaskFormParams {
eventSourceId: int,
eventId: int,
}
export global Backend {
in-out property<[string]> resources;
in-out property<[string]> tags;
in-out property<[Day]> visible_tasks;
callback task_clicked(int, int);
callback source_clicked(int);
callback tag_clicked(int);
callback open_new_task_form(OpenNewTaskFormParams);
callback open_edit_task_form(int, int);
callback open_new_event_form();
callback open_edit_event_form(int, int);
callback toggle_show_completed_tasks();
callback delete_task_clicked(int, int);
callback delete_event_clicked(int, int);
pure callback formatDate(Date) -> string;
}

90
ui/EventGroup.slint Normal file
View file

@ -0,0 +1,90 @@
import { Backend, TaskData, Event } from "Backend.slint";
import { ScrollView } from "std-widgets.slint";
import { SideBar } from "SideBar.slint";
import { VCheckBox, VButton, VTag, VPopupIconMenu, VText, Palette } from "@vynui";
import { TaskLine } from "TaskLine.slint";
import { Utils } from "Utils.slint";
export component EventGroup {
in property<Event> event;
eventPopup := VPopupIconMenu {
VButton {
icon-source: @image-url("./images/add.png");
icon-colorize: Colors.greenyellow;
icon-size: 1.5rem;
border-radius: 0;
clicked => { Backend.open_new_task_form({
eventSourceId: event.sourceId,
eventId: event.id,
})}
}
VButton {
icon-source: @image-url("./images/edit.png");
icon-colorize: Colors.grey;
icon-size: 1.5rem;
border-radius: 0;
clicked => { Backend.open_edit_event_form(event.sourceId, event.id) }
}
VButton {
icon-source: @image-url("./images/delete.png");
icon-colorize: Colors.pink;
icon-size: 1.5rem;
border-radius: 0;
clicked => { Backend.delete_event_clicked(event.sourceId, event.id) }
}
}
ta := TouchArea {
pointer-event(e) => {
if (e.button == PointerEventButton.right && e.kind == PointerEventKind.up) {
eventPopup.show(ta.mouse-x, ta.mouse-y);
}
}
}
HorizontalLayout {
VerticalLayout {
spacing: 4px;
VText {
text: Utils.timeToString(event.startsAt);
color: Palette.accent;
}
HorizontalLayout {
alignment: center;
Rectangle {
width: 4px;
background: Palette.accent;
border-radius: 8px;
}
}
VText {
text: Utils.timeToString(event.endsAt);
color: Palette.accent;
}
}
VerticalLayout {
horizontal-stretch: 1;
padding: 16px;
padding-top: 32px;
padding-bottom: 32px;
padding-right: 0px;
VText {
text: event.title;
font-size: 1.1rem;
}
for task[taskIndex] in event.tasks: VerticalLayout {
padding-top: taskIndex == 0 ? 16px : 0px;
padding-bottom: 8px;
TaskLine {
task: task;
source-index: task.sourceId;
task-index: task.id;
}
}
}
}
}

109
ui/MainView.slint Normal file
View file

@ -0,0 +1,109 @@
import { Backend, TaskData } from "Backend.slint";
import { Button, VerticalBox, CheckBox, ScrollView } from "std-widgets.slint";
import { SideBar } from "SideBar.slint";
import { TaskLine } from "TaskLine.slint";
import { EventGroup } from "EventGroup.slint";
import { VPopupIconMenu, VCheckBox, VButton, VTag, VText, Palette } from "@vynui";
export component MainView inherits Rectangle {
background: Palette.background;
private property<image> icon-visible: @image-url("./images/visible.png");
private property<image> icon-not-visible: @image-url("./images/not-visible.png");
private property<bool> completed-tasks-visible: false;
pure function formatZeroPadding(number: int) -> string {
if (number < 10) {
return "0\{number}";
}
return number;
}
VerticalLayout {
horizontal-stretch: 1;
padding: 16px;
spacing: 16px;
alignment: start;
HorizontalLayout {
horizontal-stretch: 1;
alignment: start;
spacing: 8px;
VButton {
text: "New task";
clicked => { Backend.open_new_task_form({
eventSourceId: -1,
eventId: -1,
})}
icon-source: @image-url("./images/add.png");
icon-colorize: Colors.greenyellow;
}
VButton {
text: "New event";
clicked => { Backend.open_new_event_form() }
icon-source: @image-url("./images/add.png");
icon-colorize: Colors.greenyellow;
}
VButton {
text: "Show/Hide completed tasks";
clicked => {
Backend.toggle_show_completed_tasks();
completed-tasks-visible = !completed-tasks-visible;
}
icon-source: 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;
}
Flickable {
horizontal-stretch: 1;
VerticalLayout {
alignment: start;
spacing: 16px;
for day[dayIndex] in Backend.visible_tasks: VerticalLayout {
Rectangle {
background: Palette.card-background;
border-radius: 8px;
VerticalLayout {
padding: 16px;
HorizontalLayout {
alignment: start;
VText {
text: Backend.formatDate(day.date);
color: day.isLate ? Palette.orange : Palette.foreground;
font-size: 1.2rem;
}
if day.isToday : VerticalLayout {
alignment: center;
VText {
text: " - Today";
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;
source-index: task.sourceId;
task-index: task.id;
}
}
}
}
}
}
}
}
}

35
ui/SideBar.slint Normal file
View file

@ -0,0 +1,35 @@
import { Backend } from "Backend.slint";
import { ToggleButton, VText, Palette } from "@vynui";
export component SideBar inherits Rectangle {
background: Palette.pane;
VerticalLayout {
alignment: start;
padding: 16px;
spacing: 16px;
VText {
text: "Sources";
font-size: 1.5rem;
}
VerticalLayout {
spacing: 4px;
for item[index] in Backend.resources: ToggleButton {
text: item;
text-alignment: left;
clicked => { Backend.source_clicked(index) }
}
}
/*VText {
text: "Tags";
font-size: 1.5rem;
}
VerticalLayout {
spacing: 4px;
for item[index] in Backend.tags: ToggleButton {
text: item;
text-alignment: left;
clicked => { Backend.tag_clicked(index) }
}
}*/
}
}

87
ui/TaskLine.slint Normal file
View file

@ -0,0 +1,87 @@
import { Backend, TaskData } from "Backend.slint";
import { ToggleButton } from "@vynui";
import { VPopupIconMenu, VTag, VButton, VCheckBox, Palette } from "@vynui";
export component TaskLine {
in property<TaskData> task;
in property<int> source-index: -1;
in property<int> task-index: -1;
private property<length> popup-x: 200px;
private property<length> popup-y: 200px;
init => {
if (task-index == -1) {
debug("Error: missing task-index")
}
}
popup := VPopupIconMenu {
VButton {
icon-source: @image-url("./images/edit.png");
icon-colorize: Colors.grey;
icon-size: 1.5rem;
border-radius: 0;
clicked => { Backend.open_edit_task_form(source-index, task-index) }
}
VButton {
icon-source: @image-url("./images/delete.png");
icon-colorize: Colors.pink;
icon-size: 1.5rem;
border-radius: 0;
clicked => { Backend.delete_task_clicked(source-index, task-index) }
}
}
ta := TouchArea {
clicked => {
checkbox.checked = !checkbox.checked;
Backend.task_clicked(source-index, task-index);
}
pointer-event(e) => {
if (e.button == PointerEventButton.right && e.kind == PointerEventKind.up) {
popup.show(ta.mouse-x, ta.mouse-y);
}
}
z: 10;
}
HorizontalLayout {
alignment: space-between;
HorizontalLayout {
alignment: start;
spacing: 8px;
checkbox := VCheckBox {
text: task.title;
checked: task.checked;
toggled => {
Backend.task_clicked(source-index, task-index)
}
}
for tag[tag-index] in task.tags: VTag {
text: tag;
size: 0.8rem;
}
}
HorizontalLayout {
alignment: end;
spacing: 8px;
// Not needed anymore, to remove later
/*VButton {
icon-source: @image-url("./images/edit.png");
icon-colorize: Colors.grey;
clicked => { Backend.open_edit_task_form(source-index, task-index) }
}
VButton {
icon-source: @image-url("./images/delete.png");
icon-colorize: Colors.pink;
clicked => { Backend.delete_task_clicked(source-index, task-index) }
}*/
}
}
}

14
ui/Utils.slint Normal file
View file

@ -0,0 +1,14 @@
import { Time } from "std-widgets.slint";
export global Utils {
pure function formatZeroPadding(number: int) -> string {
if (number < 10) {
return "0\{number}";
}
return number;
}
public pure function timeToString(time: Time) -> string {
return "\{formatZeroPadding(time.hour)}h\{formatZeroPadding(time.minute)}";
}
}

26
ui/appwindow.slint Normal file
View file

@ -0,0 +1,26 @@
import { Backend } from "Backend.slint";
import { Button, VerticalBox, CheckBox, Palette } from "std-widgets.slint";
import { SideBar } from "SideBar.slint";
import { MainView } from "MainView.slint";
import { TaskEdit } from "windows/TaskEdit.slint";
import { EventWindow } from "windows/EventWindow.slint";
export component AppWindow inherits Window {
title: "Mirai";
min-height: 100px;
max-height: 4000px; // needed, otherwise the window wants to fit the content (on Swaywm)
default-font-size: 16px;
in-out property<string> test;
HorizontalLayout {
SideBar {}
MainView {
horizontal-stretch: 1;
}
}
}
export { Backend, TaskEdit, EventWindow } // Export to make it visible to the C++ backend

View file

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Before After
Before After

BIN
ui/images/delete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Some files were not shown because too many files have changed in this diff Show more