284 lines
10 KiB
C++
284 lines
10 KiB
C++
#include "TaskPage.h"
|
|
#include "Application.h"
|
|
#include "BoostLog.h"
|
|
#include "Database/Session.h"
|
|
#include <Wt/WCheckBox.h>
|
|
#include <Wt/WComboBox.h>
|
|
#include <Wt/WContainerWidget.h>
|
|
#include <Wt/WDateEdit.h>
|
|
#include <Wt/WDialog.h>
|
|
#include <Wt/WLineEdit.h>
|
|
#include <Wt/WMessageBox.h>
|
|
#include <Wt/WPushButton.h>
|
|
#include <Wt/WStandardItemModel.h>
|
|
#include <Wt/WTemplateFormView.h>
|
|
#include <Wt/WTextArea.h>
|
|
|
|
TaskPage::TaskPage() : Wt::WTemplate(tr("Wt.Task.Home")) {
|
|
using namespace Wt;
|
|
addStyleClass("bulma-is-flex-grow-1 bulma-is-flex bulma-is-flex-direction-column");
|
|
|
|
auto addButton = bindNew<WPushButton>("add-new", "创建");
|
|
addButton->clicked().connect(this, [this]() { showAddTaskDialog(-1); });
|
|
update();
|
|
}
|
|
|
|
std::unique_ptr<Wt::WTemplate> TaskPage::createTask(const Wt::Dbo::ptr<Task> &task) {
|
|
auto ret = std::make_unique<Wt::WTemplate>(tr("Wt.Task.Item"));
|
|
auto app = dynamic_cast<WebToolkit::Application *>(Wt::WApplication::instance());
|
|
ret->bindString("content", task->content);
|
|
ret->bindString("remark", task->remark);
|
|
ret->setHtmlTagName("li");
|
|
|
|
auto checkbox = ret->bindNew<Wt::WCheckBox>("finished-checkbox");
|
|
checkbox->setChecked(task->finished);
|
|
checkbox->clicked().connect(ret.get(), [this, id = task.id(), checkbox]() {
|
|
{
|
|
auto session = Database::session();
|
|
Wt::Dbo::Transaction transaction(*session);
|
|
Wt::Dbo::ptr<Task> task = session->find<Task>().where("id = ?").bind(id);
|
|
if (task) {
|
|
task.modify()->finished = checkbox->isChecked();
|
|
}
|
|
}
|
|
update();
|
|
});
|
|
|
|
auto addButton = ret->bindNew<Wt::WPushButton>("add-child", tr("Wt.Icon.Add"), Wt::TextFormat::XHTML);
|
|
addButton->clicked().connect(ret.get(), [id = task.id(), this]() { showAddTaskDialog(id); });
|
|
|
|
auto removeButton = ret->bindNew<Wt::WPushButton>("remove-button", tr("Wt.Icon.Remove"), Wt::TextFormat::XHTML);
|
|
removeButton->clicked().connect(ret.get(), [this, id = task.id()]() {
|
|
Wt::StandardButton result = Wt::WMessageBox::show("删除任务", "是否删除任务?子任务也将一起删除",
|
|
Wt::StandardButton::Ok | Wt::StandardButton::Cancel);
|
|
if (result == Wt::StandardButton::Ok) {
|
|
{
|
|
auto session = Database::session();
|
|
Wt::Dbo::Transaction transaction(*session);
|
|
Wt::Dbo::ptr<Task> task = session->find<Task>().where("id = ?").bind(id);
|
|
if (task) {
|
|
removeTask(task);
|
|
}
|
|
}
|
|
update();
|
|
}
|
|
});
|
|
|
|
auto collapseButton = ret->bindNew<Wt::WPushButton>("collapse-button", tr("Wt.Icon.Expand"), Wt::TextFormat::XHTML);
|
|
collapseButton->setCheckable(true);
|
|
if (task->children.empty()) {
|
|
ret->bindEmpty("children");
|
|
collapseButton->addStyleClass("bulma-is-invisible");
|
|
} else {
|
|
auto list = ret->bindNew<Wt::WContainerWidget>("children");
|
|
list->setList(true);
|
|
for (auto child : task->children) {
|
|
list->addWidget(createTask(child));
|
|
}
|
|
collapseButton->clicked().connect(ret.get(), [collapseButton, list]() {
|
|
collapseButton->setText(collapseButton->isChecked() ? tr("Wt.Icon.Fold") : tr("Wt.Icon.Expand"));
|
|
if (collapseButton->isChecked()) {
|
|
list->addStyleClass("bulma-is-hidden");
|
|
} else {
|
|
list->removeStyleClass("bulma-is-hidden");
|
|
}
|
|
});
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
class AddTaskModel : public Wt::WFormModel {
|
|
public:
|
|
static constexpr Field ContentField = "task-content";
|
|
static constexpr Field DeadlineDateField = "task-deadlinedate";
|
|
static constexpr Field DeadlineTimeField = "task-deadlinetime";
|
|
static constexpr Field RemarkField = "task-remark";
|
|
AddTaskModel() : Wt::WFormModel() {
|
|
initializeModels();
|
|
addField(ContentField);
|
|
addField(DeadlineDateField);
|
|
addField(DeadlineTimeField);
|
|
addField(RemarkField);
|
|
|
|
setValidator(DeadlineDateField, createDeadlineValidator());
|
|
|
|
setValue(DeadlineDateField, Wt::WDate{});
|
|
setValue(DeadlineTimeField, Wt::WTime{});
|
|
}
|
|
|
|
std::shared_ptr<Wt::WAbstractItemModel> timeModel() {
|
|
return m_timeModel;
|
|
}
|
|
|
|
Wt::WTime timefRow(int row) {
|
|
return Wt::cpp17::any_cast<Wt::WTime>(m_timeModel->data(row, 0, Wt::ItemDataRole::User));
|
|
}
|
|
|
|
int rowOfTime(const Wt::WTime &time) {
|
|
int ret = -1;
|
|
for (int i = 0; i < m_timeModel->rowCount(); ++i) {
|
|
if (timefRow(i) == time) {
|
|
ret = i;
|
|
break;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
Task task() {
|
|
Task t;
|
|
t.content = Wt::asString(value(ContentField)).toUTF8();
|
|
t.remark = Wt::asString(value(RemarkField)).toUTF8();
|
|
auto date = Wt::cpp17::any_cast<Wt::WDate>(value(AddTaskModel::DeadlineDateField));
|
|
auto time = Wt::cpp17::any_cast<Wt::WTime>(value(AddTaskModel::DeadlineTimeField));
|
|
t.deadline = Wt::WDateTime(date, time).toTimePoint();
|
|
return t;
|
|
}
|
|
|
|
protected:
|
|
std::shared_ptr<Wt::WValidator> createDeadlineValidator() {
|
|
auto v = std::make_shared<Wt::WDateValidator>();
|
|
auto current = Wt::WDate::currentDate();
|
|
v->setBottom(current);
|
|
v->setTop(current.addYears(1));
|
|
v->setFormat("yyyy/MM/dd");
|
|
v->setMandatory(true);
|
|
return v;
|
|
}
|
|
|
|
void initializeModels() {
|
|
m_timeModel = std::make_shared<Wt::WStandardItemModel>(48, 1);
|
|
for (int i = 0; i < 48; i++) {
|
|
Wt::WTime time(i / 2, (i % 2) * 30);
|
|
m_timeModel->setData(i, 0, time.toString("HH:mm"), Wt::ItemDataRole::Display);
|
|
m_timeModel->setData(i, 0, time, Wt::ItemDataRole::User);
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<Wt::WStandardItemModel> m_timeModel;
|
|
};
|
|
|
|
class AddTaskView : public Wt::WTemplateFormView {
|
|
public:
|
|
AddTaskView(const Wt::WString &text) : Wt::WTemplateFormView(text), m_model{std::make_shared<AddTaskModel>()} {
|
|
setFormWidget(AddTaskModel::ContentField, std::make_unique<Wt::WLineEdit>());
|
|
|
|
auto remark = std::make_unique<Wt::WTextArea>();
|
|
remark->setColumns(40);
|
|
remark->setRows(5);
|
|
setFormWidget(AddTaskModel::RemarkField, std::move(remark));
|
|
|
|
auto dateEdit = std::make_unique<Wt::WDateEdit>();
|
|
auto dateEdit_ = dateEdit.get();
|
|
setFormWidget(
|
|
AddTaskModel::DeadlineDateField, std::move(dateEdit),
|
|
[this, dateEdit_] { // updateViewValue()
|
|
Wt::WDate date = Wt::cpp17::any_cast<Wt::WDate>(m_model->value(AddTaskModel::DeadlineDateField));
|
|
dateEdit_->setDate(date);
|
|
},
|
|
[this, dateEdit_] { // updateModelValue()
|
|
Wt::WDate date = dateEdit_->date();
|
|
m_model->setValue(AddTaskModel::DeadlineDateField, date);
|
|
});
|
|
|
|
auto timeEdit = std::make_unique<Wt::WComboBox>();
|
|
auto timeEdit_ = timeEdit.get();
|
|
timeEdit_->setModel(m_model->timeModel());
|
|
setFormWidget(
|
|
AddTaskModel::DeadlineTimeField, std::move(timeEdit),
|
|
[this, timeEdit_] { // updateViewValue()
|
|
Wt::WTime time = Wt::cpp17::any_cast<Wt::WTime>(m_model->value(AddTaskModel::DeadlineTimeField));
|
|
int row = m_model->rowOfTime(time);
|
|
timeEdit_->setCurrentIndex(row);
|
|
},
|
|
[this, timeEdit_] { // updateModelValue()
|
|
Wt::WTime time = m_model->timefRow(timeEdit_->currentIndex());
|
|
m_model->setValue(AddTaskModel::DeadlineTimeField, time);
|
|
});
|
|
|
|
auto button = bindWidget("submit-button", std::make_unique<Wt::WPushButton>("确定"));
|
|
button->clicked().connect(this, &AddTaskView::process);
|
|
|
|
updateView(m_model.get());
|
|
}
|
|
|
|
Wt::Signal<const Task &> &accepted() {
|
|
return m_accepted;
|
|
}
|
|
|
|
std::unique_ptr<Task> task() const {
|
|
return std::make_unique<Task>(m_model->task());
|
|
}
|
|
|
|
protected:
|
|
void process() {
|
|
updateModel(m_model.get());
|
|
if (m_model->validate()) {
|
|
m_accepted.emit(m_model->task());
|
|
// updateView(m_model.get());
|
|
} else {
|
|
LOG(error) << "validate failed.";
|
|
updateView(m_model.get());
|
|
}
|
|
}
|
|
|
|
private:
|
|
Wt::Signal<const Task &> m_accepted;
|
|
std::shared_ptr<AddTaskModel> m_model;
|
|
};
|
|
|
|
void TaskPage::showAddTaskDialog(int64_t parentId) {
|
|
auto d = std::make_unique<Wt::WDialog>("添加任务");
|
|
d->setMovable(false);
|
|
auto content = d->contents()->addNew<AddTaskView>(tr("Wt.Task.Add"));
|
|
auto cancelButton = content->bindNew<Wt::WPushButton>("cancel-button", "取消");
|
|
cancelButton->clicked().connect(content, [dialog = d.get()]() { dialog->reject(); });
|
|
content->accepted().connect(this, [dialog = d.get()](const Task &task) { dialog->accept(); });
|
|
auto code = d->exec();
|
|
if (code == Wt::DialogCode::Accepted) {
|
|
auto task = content->task();
|
|
auto session = Database::session();
|
|
if (session) {
|
|
Wt::Dbo::Transaction transaction(*session);
|
|
Wt::Dbo::ptr<Task> parent;
|
|
if (parentId >= 0) {
|
|
parent = session->find<Task>(std::format("where id = {}", parentId));
|
|
}
|
|
if (parent) {
|
|
task->parent = parent;
|
|
}
|
|
session->add(std::move(task));
|
|
}
|
|
update();
|
|
}
|
|
}
|
|
|
|
void TaskPage::update() {
|
|
auto session = Database::session();
|
|
Wt::Dbo::Transaction transaction(*session);
|
|
Tasks tasks = session->find<Task>("WHERE parent_id IS NULL");
|
|
if (tasks.empty()) {
|
|
bindEmpty("tasks");
|
|
return;
|
|
} else {
|
|
auto list = bindNew<Wt::WContainerWidget>("tasks");
|
|
list->setList(true);
|
|
auto app = dynamic_cast<WebToolkit::Application *>(Wt::WApplication::instance());
|
|
if (app->inDocusaurus()) {
|
|
list->addStyleClass("bulma-is-list-style-none");
|
|
}
|
|
for (auto &task : tasks) {
|
|
list->addWidget(createTask(task));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TaskPage::removeTask(Wt::Dbo::ptr<Task> task) {
|
|
if (!task->children.empty()) {
|
|
for (auto child : task->children) {
|
|
removeTask(child);
|
|
}
|
|
}
|
|
task.remove();
|
|
} |