Compare commits
2 Commits
e3580ec8f4
...
69220431d3
Author | SHA1 | Date | |
---|---|---|---|
|
69220431d3 | ||
|
cf9a3bfde8 |
@ -13,7 +13,7 @@ public:
|
|||||||
bool finished = false;
|
bool finished = false;
|
||||||
std::chrono::system_clock::time_point createTime;
|
std::chrono::system_clock::time_point createTime;
|
||||||
std::string content;
|
std::string content;
|
||||||
std::string comment;
|
std::string remark;
|
||||||
|
|
||||||
Wt::Dbo::ptr<Task> parent;
|
Wt::Dbo::ptr<Task> parent;
|
||||||
Tasks children;
|
Tasks children;
|
||||||
@ -21,7 +21,7 @@ public:
|
|||||||
template <class Action>
|
template <class Action>
|
||||||
void persist(Action &a) {
|
void persist(Action &a) {
|
||||||
Wt::Dbo::field(a, content, "content");
|
Wt::Dbo::field(a, content, "content");
|
||||||
Wt::Dbo::field(a, comment, "comment");
|
Wt::Dbo::field(a, remark, "remark");
|
||||||
Wt::Dbo::field(a, finished, "finished");
|
Wt::Dbo::field(a, finished, "finished");
|
||||||
Wt::Dbo::field(a, createTime, "create_time");
|
Wt::Dbo::field(a, createTime, "create_time");
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ Application::Application(const std::string &path) : ApplicationSettings(path), m
|
|||||||
auto task = std::make_unique<Task>();
|
auto task = std::make_unique<Task>();
|
||||||
task->createTime = system_clock::time_point(seconds(root.at("createTime").as_int64()));
|
task->createTime = system_clock::time_point(seconds(root.at("createTime").as_int64()));
|
||||||
task->content = content;
|
task->content = content;
|
||||||
task->comment = std::string(root.at("comment").as_string());
|
task->remark = std::string(root.at("comment").as_string());
|
||||||
auto t = database->add(std::move(task));
|
auto t = database->add(std::move(task));
|
||||||
Wt::Dbo::ptr<Task> parent = database->find<Task>("where id=?").bind(root.at("parentId").as_int64());
|
Wt::Dbo::ptr<Task> parent = database->find<Task>("where id=?").bind(root.at("parentId").as_int64());
|
||||||
if (parent) {
|
if (parent) {
|
||||||
|
@ -42,6 +42,15 @@ location ^~ /api/v1/auth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/api/v1/.*$ {
|
location ~ ^/api/v1/.*$ {
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header x-wiz-real-ip $remote_addr;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
proxy_pass http://local;
|
proxy_pass http://local;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,20 +6,20 @@
|
|||||||
|
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(DatabaseTest) {
|
BOOST_AUTO_TEST_CASE(DatabaseTest) {
|
||||||
auto session = Database::session();
|
auto session = Database::session();
|
||||||
|
|
||||||
Wt::Dbo ::Transaction transaction(*session);
|
Wt::Dbo ::Transaction transaction(*session);
|
||||||
|
|
||||||
auto task = std::make_unique<Task>();
|
auto task = std::make_unique<Task>();
|
||||||
task->comment = "my_comment";
|
task->remark = "my_comment";
|
||||||
task->content = "my_content";
|
task->content = "my_content";
|
||||||
|
|
||||||
auto p = session->add(std::move(task));
|
auto p = session->add(std::move(task));
|
||||||
|
|
||||||
{
|
{
|
||||||
task = std::make_unique<Task>();
|
task = std::make_unique<Task>();
|
||||||
task->comment = "my_comment1";
|
task->remark = "my_comment1";
|
||||||
task->content = "my_content1";
|
task->content = "my_content1";
|
||||||
auto c = session->add(std::move(task));
|
auto c = session->add(std::move(task));
|
||||||
p.modify()->children.insert(c);
|
p.modify()->children.insert(c);
|
||||||
@ -27,14 +27,14 @@ BOOST_AUTO_TEST_CASE(DatabaseTest) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
task = std::make_unique<Task>();
|
task = std::make_unique<Task>();
|
||||||
task->comment = "my_comment2";
|
task->remark = "my_comment2";
|
||||||
task->content = "my_content2";
|
task->content = "my_content2";
|
||||||
auto c = session->add(std::move(task));
|
auto c = session->add(std::move(task));
|
||||||
p.modify()->children.insert(c);
|
p.modify()->children.insert(c);
|
||||||
|
|
||||||
{
|
{
|
||||||
task = std::make_unique<Task>();
|
task = std::make_unique<Task>();
|
||||||
task->comment = "my_comment3";
|
task->remark = "my_comment3";
|
||||||
task->content = "my_content3";
|
task->content = "my_content3";
|
||||||
auto d = session->add(std::move(task));
|
auto d = session->add(std::move(task));
|
||||||
c.modify()->children.insert(d);
|
c.modify()->children.insert(d);
|
||||||
@ -42,7 +42,7 @@ BOOST_AUTO_TEST_CASE(DatabaseTest) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
task = std::make_unique<Task>();
|
task = std::make_unique<Task>();
|
||||||
task->comment = "my_comment4";
|
task->remark = "my_comment4";
|
||||||
task->content = "my_content4";
|
task->content = "my_content4";
|
||||||
auto d = session->add(std::move(task));
|
auto d = session->add(std::move(task));
|
||||||
c.modify()->children.insert(d);
|
c.modify()->children.insert(d);
|
||||||
@ -51,6 +51,7 @@ BOOST_AUTO_TEST_CASE(DatabaseTest) {
|
|||||||
|
|
||||||
Wt::Dbo::ptr<Task> tt = session->find<Task>("where id = 3");
|
Wt::Dbo::ptr<Task> tt = session->find<Task>("where id = 3");
|
||||||
LOG(info) << tt->parent->content;
|
LOG(info) << tt->parent->content;
|
||||||
|
BOOST_CHECK_EQUAL(tt.id(), 3);
|
||||||
LOG(info) << tt->children.size();
|
LOG(info) << tt->children.size();
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "NavigationBar.h"
|
#include "NavigationBar.h"
|
||||||
#include "RedirectPage.h"
|
#include "RedirectPage.h"
|
||||||
#include "Restful.h"
|
#include "Restful.h"
|
||||||
|
#include "TaskPage.h"
|
||||||
#include "VisitorRecordsPage.h"
|
#include "VisitorRecordsPage.h"
|
||||||
#include "WebRTCClientPage.h"
|
#include "WebRTCClientPage.h"
|
||||||
#include "model/AuthModel.h"
|
#include "model/AuthModel.h"
|
||||||
@ -50,6 +51,7 @@ Application::Application(const Wt::WEnvironment &env, bool embedded)
|
|||||||
m_root = root()->addNew<Wt::WContainerWidget>();
|
m_root = root()->addNew<Wt::WContainerWidget>();
|
||||||
m_root->addStyleClass("bulma-container bulma-is-flex-grow-1 bulma-is-flex");
|
m_root->addStyleClass("bulma-container bulma-is-flex-grow-1 bulma-is-flex");
|
||||||
} else {
|
} else {
|
||||||
|
m_extern = true;
|
||||||
std::unique_ptr<Wt::WContainerWidget> topPtr = std::make_unique<Wt::WContainerWidget>();
|
std::unique_ptr<Wt::WContainerWidget> topPtr = std::make_unique<Wt::WContainerWidget>();
|
||||||
m_root = topPtr.get();
|
m_root = topPtr.get();
|
||||||
const std::string *div = env.getParameter("div");
|
const std::string *div = env.getParameter("div");
|
||||||
@ -208,6 +210,9 @@ void Application::handlePathChange(const std::string &path) {
|
|||||||
} else if (path.starts_with("/wt/webrtc")) {
|
} else if (path.starts_with("/wt/webrtc")) {
|
||||||
m_root->clear();
|
m_root->clear();
|
||||||
auto p = m_root->addNew<WebRTCClientPage>();
|
auto p = m_root->addNew<WebRTCClientPage>();
|
||||||
|
} else if (path.starts_with("/wt/task")) {
|
||||||
|
m_root->clear();
|
||||||
|
m_root->addNew<TaskPage>();
|
||||||
} else {
|
} else {
|
||||||
m_root->clear();
|
m_root->clear();
|
||||||
m_root->addNew<HomePage>();
|
m_root->addNew<HomePage>();
|
||||||
@ -215,6 +220,10 @@ void Application::handlePathChange(const std::string &path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Application::inDocusaurus() const {
|
||||||
|
return m_extern;
|
||||||
|
}
|
||||||
|
|
||||||
Server::Server(uint16_t port, const std::string &applicationRoot, const std::string &documentRoot) {
|
Server::Server(uint16_t port, const std::string &applicationRoot, const std::string &documentRoot) {
|
||||||
try {
|
try {
|
||||||
std::vector<std::string> args;
|
std::vector<std::string> args;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
#ifndef __WEBAPPLICATION_H__
|
#ifndef __WEBAPPLICATION_H__
|
||||||
#define __WEBAPPLICATION_H__
|
#define __WEBAPPLICATION_H__
|
||||||
|
|
||||||
|
#include "Database/User.h"
|
||||||
#include "Singleton.h"
|
#include "Singleton.h"
|
||||||
#include <Wt/WApplication.h>
|
#include <Wt/WApplication.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "Database/User.h"
|
|
||||||
|
|
||||||
namespace Wt {
|
namespace Wt {
|
||||||
class WServer;
|
class WServer;
|
||||||
@ -30,6 +30,7 @@ class Application : public Wt::WApplication {
|
|||||||
public:
|
public:
|
||||||
Application(const Wt::WEnvironment &env, bool embedded);
|
Application(const Wt::WEnvironment &env, bool embedded);
|
||||||
~Application();
|
~Application();
|
||||||
|
bool inDocusaurus() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void authEvent();
|
void authEvent();
|
||||||
@ -37,6 +38,7 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<Session> m_session;
|
std::unique_ptr<Session> m_session;
|
||||||
|
bool m_extern = false;
|
||||||
Wt::JSignal<> m_startup;
|
Wt::JSignal<> m_startup;
|
||||||
|
|
||||||
Wt::WContainerWidget *m_root = nullptr;
|
Wt::WContainerWidget *m_root = nullptr;
|
||||||
|
@ -8,6 +8,7 @@ add_library(WebApplication
|
|||||||
RegistrationPage.h RegistrationPage.cpp
|
RegistrationPage.h RegistrationPage.cpp
|
||||||
NavigationBar.h NavigationBar.cpp
|
NavigationBar.h NavigationBar.cpp
|
||||||
RedirectPage.h RedirectPage.cpp
|
RedirectPage.h RedirectPage.cpp
|
||||||
|
TaskPage.h TaskPage.cpp
|
||||||
VisitorRecordsPage.h VisitorRecordsPage.cpp
|
VisitorRecordsPage.h VisitorRecordsPage.cpp
|
||||||
WebRTCClientPage.h WebRTCClientPage.cpp
|
WebRTCClientPage.h WebRTCClientPage.cpp
|
||||||
Restful.h Restful.cpp
|
Restful.h Restful.cpp
|
||||||
|
@ -15,6 +15,10 @@ HomePage::HomePage() {
|
|||||||
li->setHtmlTagName("li");
|
li->setHtmlTagName("li");
|
||||||
li->addNew<Wt::WAnchor>(Wt::WLink(Wt::LinkType::InternalPath, "/wt/login"), "登录页面");
|
li->addNew<Wt::WAnchor>(Wt::WLink(Wt::LinkType::InternalPath, "/wt/login"), "登录页面");
|
||||||
|
|
||||||
|
li = ul->addNew<Wt::WContainerWidget>();
|
||||||
|
li->setHtmlTagName("li");
|
||||||
|
li->addNew<Wt::WAnchor>(Wt::WLink(Wt::LinkType::InternalPath, "/wt/task"), "任务清单");
|
||||||
|
|
||||||
li = ul->addNew<Wt::WContainerWidget>();
|
li = ul->addNew<Wt::WContainerWidget>();
|
||||||
li->setHtmlTagName("li");
|
li->setHtmlTagName("li");
|
||||||
li->addNew<Wt::WAnchor>(Wt::WLink(Wt::LinkType::InternalPath, "/wt/visitor/analysis"), "访客数据");
|
li->addNew<Wt::WAnchor>(Wt::WLink(Wt::LinkType::InternalPath, "/wt/visitor/analysis"), "访客数据");
|
||||||
|
199
WebApplication/TaskPage.cpp
Normal file
199
WebApplication/TaskPage.cpp
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
#include "TaskPage.h"
|
||||||
|
#include "Application.h"
|
||||||
|
#include "BoostLog.h"
|
||||||
|
#include "Database/Session.h"
|
||||||
|
#include <Wt/WCheckBox.h>
|
||||||
|
#include <Wt/WContainerWidget.h>
|
||||||
|
#include <Wt/WDialog.h>
|
||||||
|
#include <Wt/WLineEdit.h>
|
||||||
|
#include <Wt/WMessageBox.h>
|
||||||
|
#include <Wt/WPushButton.h>
|
||||||
|
#include <Wt/WTemplateFormView.h>
|
||||||
|
#include <Wt/WTextArea.h>
|
||||||
|
|
||||||
|
TaskPage::TaskPage() : Wt::WTemplate(tr("Wt.Task.Home")) {
|
||||||
|
using namespace Wt;
|
||||||
|
WApplication *app = WApplication::instance();
|
||||||
|
app->messageResourceBundle().use(app->appRoot() + "webrtc");
|
||||||
|
addStyleClass("bulma-is-flex-grow-1 bulma-is-flex bulma-is-flex-direction-column");
|
||||||
|
|
||||||
|
auto addButton = bindNew<Wt::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());
|
||||||
|
if (app->inDocusaurus()) {
|
||||||
|
ret->addStyleClass("li-none-style");
|
||||||
|
}
|
||||||
|
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 RemarkField = "task-remark";
|
||||||
|
AddTaskModel() : Wt::WFormModel() {
|
||||||
|
addField(ContentField);
|
||||||
|
addField(RemarkField);
|
||||||
|
}
|
||||||
|
Task task() {
|
||||||
|
Task t;
|
||||||
|
t.content = Wt::asString(value(ContentField)).toUTF8();
|
||||||
|
t.remark = Wt::asString(value(RemarkField)).toUTF8();
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
20
WebApplication/TaskPage.h
Normal file
20
WebApplication/TaskPage.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#ifndef __TASKPAGE_H__
|
||||||
|
#define __TASKPAGE_H__
|
||||||
|
|
||||||
|
#include <Wt/Dbo/ptr.h>
|
||||||
|
#include <Wt/WTemplate.h>
|
||||||
|
|
||||||
|
class Task;
|
||||||
|
|
||||||
|
class TaskPage : public Wt::WTemplate {
|
||||||
|
public:
|
||||||
|
TaskPage();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void update();
|
||||||
|
void showAddTaskDialog(int64_t parentId = -1);
|
||||||
|
std::unique_ptr<Wt::WTemplate> createTask(const Wt::Dbo::ptr<Task> &task);
|
||||||
|
void removeTask(Wt::Dbo::ptr<Task> task);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // __TASKPAGE_H__
|
@ -3,6 +3,16 @@ body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul li ul li {
|
||||||
|
margin-left: 2.5rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.li-none-style {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.Wt-itemview .Wt-headerdiv {
|
.Wt-itemview .Wt-headerdiv {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
|
324
resources/webkit-transitions.css
Normal file
324
resources/webkit-transitions.css
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
/*
|
||||||
|
* jQuery Mobile Framework
|
||||||
|
* Copyright (c) jQuery Project
|
||||||
|
* Dual licensed under the MIT (MIT-LICENSE.txt) or GPL (GPL-LICENSE.txt) licenses.
|
||||||
|
*/
|
||||||
|
.spin {
|
||||||
|
-webkit-transform: rotate(360deg);
|
||||||
|
-webkit-animation-name: spin;
|
||||||
|
-webkit-animation-duration: 1s;
|
||||||
|
-webkit-animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
@-webkit-keyframes spin {
|
||||||
|
from {-webkit-transform: rotate(0deg);}
|
||||||
|
to {-webkit-transform: rotate(360deg);}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Transitions from jQtouch (with small modifications): http://www.jqtouch.com/
|
||||||
|
Built by David Kaneda and maintained by Jonathan Stark.
|
||||||
|
*/
|
||||||
|
.slide.in {
|
||||||
|
-webkit-transform: translate3d(0,0,0);
|
||||||
|
-webkit-animation-name: slideinfromright;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide.out {
|
||||||
|
-webkit-transform: translate3d(-100%,0,0);
|
||||||
|
-webkit-animation-name: slideouttoleft;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide.in.reverse {
|
||||||
|
-webkit-transform: translate3d(0,0,0);
|
||||||
|
-webkit-animation-name: slideinfromleft;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide.out.reverse {
|
||||||
|
-webkit-transform: translate3d(100%,0,0);
|
||||||
|
-webkit-animation-name: slideouttoright;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slideup.in {
|
||||||
|
-webkit-transform: translate3d(0,0,0);
|
||||||
|
-webkit-animation-name: slideinfrombottom;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slideup.out {
|
||||||
|
-webkit-animation-name: dontmove;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slideup.out.reverse {
|
||||||
|
-webkit-transform: translate3d(0,100%,0);
|
||||||
|
-webkit-animation-name: slideouttobottom;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slideup.in.reverse {
|
||||||
|
-webkit-animation-name: dontmove;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slidedown.in {
|
||||||
|
-webkit-transform: translate3d(0,0,0);
|
||||||
|
-webkit-animation-name: slideinfromtop;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slidedown.out {
|
||||||
|
-webkit-animation-name: dontmove;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slidedown.out.reverse {
|
||||||
|
-webkit-transform: translate3d(0,-100%,0);
|
||||||
|
-webkit-animation-name: slideouttotop;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slidedown.in.reverse {
|
||||||
|
-webkit-animation-name: dontmove;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes slideinfromright {
|
||||||
|
from { -webkit-transform: translate3d(100%,0,0); }
|
||||||
|
to { -webkit-transform: translate3d(0,0,0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes slideinfromleft {
|
||||||
|
from { -webkit-transform: translate3d(-100%,0,0); }
|
||||||
|
to { -webkit-transform: translate3d(0,0,0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes slideouttoleft {
|
||||||
|
from { -webkit-transform: translate3d(0,0,0); }
|
||||||
|
to { -webkit-transform: translate3d(-100%,0,0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes slideouttoright {
|
||||||
|
from { -webkit-transform: translate3d(0,0,0); }
|
||||||
|
to { -webkit-transform: translate3d(100%,0,0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@-webkit-keyframes slideinfromtop {
|
||||||
|
from { -webkit-transform: translate3d(0,-100%,0); }
|
||||||
|
to { -webkit-transform: translate3d(0,0,0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes slideinfrombottom {
|
||||||
|
from { -webkit-transform: translate3d(0,100%,0); }
|
||||||
|
to { -webkit-transform: translate3d(0,0,0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes slideouttobottom {
|
||||||
|
from { -webkit-transform: translate3d(0,0,0); }
|
||||||
|
to { -webkit-transform: translate3d(0,100%,0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes slideouttotop {
|
||||||
|
from { -webkit-transform: translate3d(0,0,0); }
|
||||||
|
to { -webkit-transform: translate3d(0,-100%,0); }
|
||||||
|
}
|
||||||
|
@-webkit-keyframes fadein {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes fadeout {
|
||||||
|
from { opacity: 1; }
|
||||||
|
to { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes halffadein {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 0.5; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes halffadeout {
|
||||||
|
from { opacity: 0.5; }
|
||||||
|
to { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bootstrap2fadein {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 0.8; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bootstrap2fadeout {
|
||||||
|
from { opacity: 0.8; }
|
||||||
|
to { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade.in {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-animation-name: fadein;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade.out {
|
||||||
|
-webkit-animation-name: fadeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Wt-dialogcover.fade.in,
|
||||||
|
.modal-backdrop.fade.in {
|
||||||
|
opacity: 0.5;
|
||||||
|
animation-name: halffadein;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Wt-dialogcover.fade.out,
|
||||||
|
.modal-backdrop.fade.out {
|
||||||
|
animation-name: halffadeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop.Wt-bootstrap2.fade.in {
|
||||||
|
opacity: 0.8;
|
||||||
|
animation-name: bootstrap2fadein;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop.Wt-bootstrap2.fade.out {
|
||||||
|
animation-name: bootstrap2fadeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The properties in this body rule are only necessary for the 'flip' transition.
|
||||||
|
* We need specify the perspective to create a projection matrix. This will add
|
||||||
|
* some depth as the element flips. The depth number represents the distance of
|
||||||
|
* the viewer from the z-plane. According to the CSS3 spec, 1000 is a moderate
|
||||||
|
* value.
|
||||||
|
*/
|
||||||
|
.ui-mobile-viewport-perspective {
|
||||||
|
-webkit-perspective: 1000;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-mobile-viewport-transitioning,
|
||||||
|
.ui-mobile-viewport-transitioning .ui-page {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flip {
|
||||||
|
-webkit-animation-duration: .65s;
|
||||||
|
-webkit-backface-visibility:hidden;
|
||||||
|
-webkit-transform:translate3d(0,0,0); /* Needed to work around an iOS 3.1 bug that causes listview thumbs to disappear when -webkit-visibility:hidden is used. */
|
||||||
|
}
|
||||||
|
|
||||||
|
.flip.in {
|
||||||
|
-webkit-transform: rotateY(0) scale(1);
|
||||||
|
-webkit-animation-name: flipinfromleft;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flip.out {
|
||||||
|
-webkit-transform: rotateY(-180deg) scale(.8);
|
||||||
|
-webkit-animation-name: flipouttoleft;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Shake it all about */
|
||||||
|
|
||||||
|
.flip.in.reverse {
|
||||||
|
-webkit-transform: rotateY(0) scale(1);
|
||||||
|
-webkit-animation-name: flipinfromright;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flip.out.reverse {
|
||||||
|
-webkit-transform: rotateY(180deg) scale(.8);
|
||||||
|
-webkit-animation-name: flipouttoright;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes flipinfromright {
|
||||||
|
from { -webkit-transform: rotateY(-180deg) scale(.8); }
|
||||||
|
to { -webkit-transform: rotateY(0) scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes flipinfromleft {
|
||||||
|
from { -webkit-transform: rotateY(180deg) scale(.8); }
|
||||||
|
to { -webkit-transform: rotateY(0) scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes flipouttoleft {
|
||||||
|
from { -webkit-transform: rotateY(0) scale(1); }
|
||||||
|
to { -webkit-transform: rotateY(-180deg) scale(.8); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes flipouttoright {
|
||||||
|
from { -webkit-transform: rotateY(0) scale(1); }
|
||||||
|
to { -webkit-transform: rotateY(180deg) scale(.8); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Hackish, but reliable. */
|
||||||
|
|
||||||
|
@-webkit-keyframes dontmove {
|
||||||
|
from { opacity: 1; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.pop {
|
||||||
|
-webkit-transform-origin: 50% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pop.in {
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
-webkit-animation-name: popin;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pop.out.reverse {
|
||||||
|
-webkit-transform: scale(.2);
|
||||||
|
-webkit-animation-name: popout;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pop.in.reverse {
|
||||||
|
-webkit-animation-name: dontmove;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes popin {
|
||||||
|
from {
|
||||||
|
-webkit-transform: scale(.2);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes popout {
|
||||||
|
from {
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: scale(.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.slide.in.fade {
|
||||||
|
-webkit-animation-name: slideinfromright, fadein;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide.out.fade {
|
||||||
|
-webkit-animation-name: slideinfromright, fadeout;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pop.fade.in {
|
||||||
|
-webkit-animation-name: popin, fadein;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pop.fade.out {
|
||||||
|
-webkit-animation-name: popout, fadeout;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
@ -26,4 +26,75 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
<message id="Wt.Task.Home">
|
||||||
|
<h2 class="bulma-subtitle bulma-has-text-centered">任务清单</h2>
|
||||||
|
<div class="bulma-buttons bulma-is-right">
|
||||||
|
${add-new class="bulma-button bulma-is-success"}
|
||||||
|
</div>
|
||||||
|
${tasks}
|
||||||
|
</message>
|
||||||
|
<message id="Wt.Task.Item">
|
||||||
|
<div class="bulma-is-flex bulma-has-background-black-ter bulma-p-5 bulma-mb-1">
|
||||||
|
${finished-checkbox}
|
||||||
|
<div class="bulma-is-flex-grow-1 bulma-ml-5">
|
||||||
|
<div>${content}</div>
|
||||||
|
<div>${remark}</div>
|
||||||
|
</div>
|
||||||
|
<div class="bulma-buttons">
|
||||||
|
${remove-button class="bulma-button bulma-is-danger"}
|
||||||
|
${add-child class="bulma-button bulma-is-success"}
|
||||||
|
${collapse-button class="bulma-button"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${children}
|
||||||
|
</message>
|
||||||
|
<message id="Wt.Task.Add">
|
||||||
|
<div class="bulma-field">
|
||||||
|
<label for="${id:task-content}" class="bulma-label">
|
||||||
|
${task-content-label}
|
||||||
|
</label>
|
||||||
|
<div class="bulma-control bulma-has-icons-left">
|
||||||
|
${task-content class="bulma-input" type="text"}
|
||||||
|
<span class="bulma-icon bulma-is-small bulma-is-left">
|
||||||
|
<i class="fa-solid fa-user"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
${task-content-info class="Wt-info bulma-help"}
|
||||||
|
</div>
|
||||||
|
<div class="bulma-field">
|
||||||
|
<label for="${id:task-remark}" class="bulma-label">
|
||||||
|
${task-remark-label}
|
||||||
|
</label>
|
||||||
|
<div class="bulma-control">
|
||||||
|
${task-remark class="bulma-textarea"}
|
||||||
|
</div>
|
||||||
|
${task-remark-info class="Wt-info bulma-help"}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
${submit-button class="bulma-button"}
|
||||||
|
${cancel-button class="bulma-button"}
|
||||||
|
</div>
|
||||||
|
</message>
|
||||||
|
<message id="Wt.Icon.Expand">
|
||||||
|
<span class="bulma-icon bulma-is-small">
|
||||||
|
<i class="fa-solid fa-angle-down"></i>
|
||||||
|
</span>
|
||||||
|
</message>
|
||||||
|
<message id="Wt.Icon.Fold">
|
||||||
|
<span class="bulma-icon bulma-is-small">
|
||||||
|
<i class="fa-solid fa-angle-left"></i>
|
||||||
|
</span>
|
||||||
|
</message>
|
||||||
|
<message id="Wt.Icon.Add">
|
||||||
|
<span class="bulma-icon bulma-is-small">
|
||||||
|
<i class="fa-solid fa-plus"></i>
|
||||||
|
</span>
|
||||||
|
</message>
|
||||||
|
<message id="Wt.Icon.Remove">
|
||||||
|
<span class="bulma-icon bulma-is-small">
|
||||||
|
<i class="fa-regular fa-trash-can"></i>
|
||||||
|
</span>
|
||||||
|
</message>
|
||||||
|
<message id="task-content">内容</message>
|
||||||
|
<message id="task-remark">备注</message>
|
||||||
</messages>
|
</messages>
|
Loading…
Reference in New Issue
Block a user