Switch from Qt6 to Slint
1
.gitignore
vendored
|
@ -2,7 +2,6 @@ build
|
|||
todo.txt
|
||||
todo.md
|
||||
.qmlls.ini
|
||||
.clangd
|
||||
.cache
|
||||
.nvimrc
|
||||
CMakeLists.txt.user
|
||||
|
|
138
CMakeLists.txt
|
@ -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()
|
||||
|
|
13
README.md
|
@ -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).
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## 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
|
||||
|
|
|
@ -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
|
||||
|
|
Before Width: | Height: | Size: 195 KiB |
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 274 KiB |
|
@ -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
|
||||
|
|
@ -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;
|
127
lib/mirai-core/BaseResource.cpp
Normal 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
|
|
@ -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
|
@ -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
|
@ -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
|
37
lib/mirai-core/DateTime.cpp
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
|
@ -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"
|
28
lib/mirai-core/EventEmitter.h
Normal 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;
|
||||
};
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
};
|
|
@ -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
|
|
@ -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
|
176
lib/mirai-core/TasksView.cpp
Normal 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
|
|
@ -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
|
|
@ -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
|
@ -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 = ¤tDay->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
47
lib/slint-vynui/Button.slint
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
42
lib/slint-vynui/CheckBox.slint
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
27
lib/slint-vynui/DatePicker.slint
Normal 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 }
|
||||
}
|
||||
}
|
22
lib/slint-vynui/LabeledComponent.slint
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
lib/slint-vynui/Palette.slint
Normal 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;
|
||||
}
|
27
lib/slint-vynui/PopupIconMenu.slint
Normal 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
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
5
lib/slint-vynui/Text.slint
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { Palette } from "Palette.slint";
|
||||
|
||||
export component VText inherits Text {
|
||||
color: Palette.foreground;
|
||||
}
|
13
lib/slint-vynui/TextInput.slint
Normal 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() }
|
||||
}
|
||||
}
|
25
lib/slint-vynui/TimePicker.slint
Normal 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 }
|
||||
}
|
||||
}
|
35
lib/slint-vynui/ToggleButton.slint
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
11
lib/slint-vynui/index.slint
Normal 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";
|
|
@ -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
|
381
src/Backend.cpp
|
@ -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();
|
||||
}
|
|
@ -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
|
17
src/Tag.cpp
|
@ -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;
|
||||
}
|
29
src/Tag.h
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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());
|
||||
}
|
|
@ -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
|
@ -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 ¤tDay = 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 ¤tEvent = 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
|
@ -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
|
@ -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
|
@ -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);
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
57
src/main.cpp
|
@ -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;
|
||||
}
|
||||
|
|
129
src/qml/Main.qml
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
BIN
ui/images/delete.png
Normal file
After Width: | Height: | Size: 2 KiB |