Rewrite modals
Refactor query db in ws actor, add edit epic ui and some logic
This commit is contained in:
parent
82eb025359
commit
8412a113e7
@ -1,37 +0,0 @@
|
|||||||
image: archlinux
|
|
||||||
packages:
|
|
||||||
- nodejs
|
|
||||||
- rustup
|
|
||||||
- yarn
|
|
||||||
sources:
|
|
||||||
- https://git.sr.ht/~tsumanu/jirs
|
|
||||||
environment:
|
|
||||||
deploy: adrian.wozniak@ita-prog.pl
|
|
||||||
DEBUG: false
|
|
||||||
JIRS_CLIENT_PORT: 80
|
|
||||||
JIRS_CLIENT_BIND: jirs.ita-prog.pl
|
|
||||||
JIRS_SERVER_PORT: 80
|
|
||||||
JIRS_SERVER_BIND: jirs.ita-prog.pl
|
|
||||||
CI: true
|
|
||||||
secrets:
|
|
||||||
- 46f739e5-4538-45dd-a79f-bf173b7a2ed9
|
|
||||||
tasks:
|
|
||||||
- setup: |
|
|
||||||
rustup toolchain install nightly
|
|
||||||
rustup default nightly
|
|
||||||
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sudo sh
|
|
||||||
- test: |
|
|
||||||
cd ~/jirs/jirs-client
|
|
||||||
export NODE_ENV=development
|
|
||||||
wasm-pack test --node
|
|
||||||
- build: |
|
|
||||||
cd ~/jirs/jirs-client
|
|
||||||
export NODE_ENV=production
|
|
||||||
./scripts/prod.sh
|
|
||||||
export TAR_NAME=$(date -u +"%Y%m%d%H%M%s")
|
|
||||||
tar -cJvf ~/${TAR_NAME}.tar.xz ./build
|
|
||||||
cp ~/${TAR_NAME}.tar.xz ~/latest.tar.xz
|
|
||||||
scp ~/latest.tar.xz rpi.ita-prog.pl:/www/http/static/jirs-client-{TAR_NAME}.tar.xz
|
|
||||||
scp ~/latest.tar.xz rpi.ita-prog.pl:/www/http/static/jirs-client-latest.tar.xz
|
|
||||||
artifacts:
|
|
||||||
- latest.tar.gz
|
|
@ -1,2 +0,0 @@
|
|||||||
concurrency = 2
|
|
||||||
database_url = "postgres://build@localhost:5432/jirs"
|
|
@ -1,29 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name jirs.lvh.me;
|
|
||||||
|
|
||||||
charset utf-8;
|
|
||||||
root /assets;
|
|
||||||
|
|
||||||
location ~ .wasm {
|
|
||||||
default_type application/wasm;
|
|
||||||
}
|
|
||||||
|
|
||||||
location *.js {
|
|
||||||
default_type application/javascript;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
index index.html index.htm;
|
|
||||||
}
|
|
||||||
|
|
||||||
error_page 404 =200 /index.html;
|
|
||||||
|
|
||||||
location /ws/ {
|
|
||||||
proxy_pass http://server:5000;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "Upgrade";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
image: archlinux
|
|
||||||
packages:
|
|
||||||
- postgresql
|
|
||||||
- rustup
|
|
||||||
sources:
|
|
||||||
- https://git.sr.ht/~tsumanu/jirs
|
|
||||||
environment:
|
|
||||||
deploy: adrian.wozniak@ita-prog.pl
|
|
||||||
DATABASE_URL: postgres://build@localhost:5432/jirs
|
|
||||||
DEBUG: true
|
|
||||||
NODE_ENV: development
|
|
||||||
RUST_LOG: debug
|
|
||||||
JIRS_CLIENT_PORT: 7000
|
|
||||||
JIRS_CLIENT_BIND: 0.0.0.0
|
|
||||||
JIRS_SERVER_PORT: 5000
|
|
||||||
JIRS_SERVER_BIND: 0.0.0.0
|
|
||||||
secrets:
|
|
||||||
- 46f739e5-4538-45dd-a79f-bf173b7a2ed9
|
|
||||||
tasks:
|
|
||||||
- build_config: |
|
|
||||||
cp ~/jirs/.builds/db.toml ~/jirs/jirs-server/db.toml
|
|
||||||
cp ~/jirs/.builds/db.toml ~/jirs/jirs-server/db.test.toml
|
|
||||||
- setup: |
|
|
||||||
sudo mkdir -p /var/lib/postgres/data
|
|
||||||
sudo chown build /var/lib/postgres/data
|
|
||||||
initdb -D /var/lib/postgres/data
|
|
||||||
sudo mkdir -p /run/postgresql
|
|
||||||
sudo chown build /run/postgresql
|
|
||||||
pg_ctl -D /var/lib/postgres/data start
|
|
||||||
rustup toolchain install nightly
|
|
||||||
rustup default nightly
|
|
||||||
cargo install diesel_cli --no-default-features --features postgres
|
|
||||||
cd jirs/jirs-server
|
|
||||||
/home/build/.cargo/bin/diesel setup
|
|
||||||
- test: |
|
|
||||||
cd jirs/jirs-server
|
|
||||||
cargo test --bin jirs_server
|
|
||||||
- build: |
|
|
||||||
cd jirs
|
|
||||||
cargo build --all --release
|
|
||||||
strip -s ./target/release/jirs_server
|
|
||||||
tar -cJvf ~/server.tar.xz ./target/release/jirs_server
|
|
||||||
- deploy: |
|
|
||||||
cp ~/server.tar.xz ~/latest.tar.xz
|
|
||||||
scp ~/latest.tar.xz rpi.ita-prog.pl:/www/http/static/jirs-server-{TAR_NAME}.tar.xz
|
|
||||||
scp ~/latest.tar.xz rpi.ita-prog.pl:/www/http/static/jirs-server-latest.tar.xz
|
|
||||||
artifacts:
|
|
||||||
- jirs_server
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1954,6 +1954,8 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"derive_enum_iter",
|
||||||
|
"derive_enum_primitive",
|
||||||
"futures 0.1.30",
|
"futures 0.1.30",
|
||||||
"jirs-data",
|
"jirs-data",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
@ -1 +0,0 @@
|
|||||||
theme: jekyll-theme-minimal
|
|
@ -9,7 +9,7 @@ use {
|
|||||||
uuid::Uuid,
|
uuid::Uuid,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Queryable)]
|
#[derive(Serialize, Debug, Deserialize, Queryable)]
|
||||||
pub struct Issue {
|
pub struct Issue {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use {
|
use {
|
||||||
crate::{WebSocketActor, WsHandler, WsResult},
|
crate::{
|
||||||
|
db_or_debug_and_return, mail_or_debug_and_return, WebSocketActor, WsHandler, WsResult,
|
||||||
|
},
|
||||||
actix::AsyncContext,
|
actix::AsyncContext,
|
||||||
database_actor::{
|
database_actor::{
|
||||||
authorize_user::AuthorizeUser,
|
authorize_user::AuthorizeUser,
|
||||||
@ -20,43 +22,16 @@ impl WsHandler<Authenticate> for WebSocketActor {
|
|||||||
fn handle_msg(&mut self, msg: Authenticate, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: Authenticate, _ctx: &mut Self::Context) -> WsResult {
|
||||||
let Authenticate { name, email } = msg;
|
let Authenticate { name, email } = msg;
|
||||||
// TODO check attempt number, allow only 5 times per day
|
// TODO check attempt number, allow only 5 times per day
|
||||||
let user = match block_on(self.db.send(LookupUser { name, email })) {
|
let user = db_or_debug_and_return!(self, LookupUser { name, email });
|
||||||
Ok(Ok(user)) => user,
|
let token = db_or_debug_and_return!(self, CreateBindToken { user_id: user.id });
|
||||||
Ok(Err(e)) => {
|
|
||||||
log::error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let token = match block_on(self.db.send(CreateBindToken { user_id: user.id })) {
|
|
||||||
Ok(Ok(token)) => token,
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
log::error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some(bind_token) = token.bind_token.as_ref().cloned() {
|
if let Some(bind_token) = token.bind_token.as_ref().cloned() {
|
||||||
match block_on(self.mail.send(Welcome {
|
let _ = mail_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
Welcome {
|
||||||
bind_token,
|
bind_token,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
})) {
|
|
||||||
Ok(Ok(_)) => (),
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
log::error!("{}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("{}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ok(Some(WsMsg::AuthenticateSuccess))
|
Ok(Some(WsMsg::AuthenticateSuccess))
|
||||||
}
|
}
|
||||||
@ -68,17 +43,16 @@ pub struct CheckAuthToken {
|
|||||||
|
|
||||||
impl WsHandler<CheckAuthToken> for WebSocketActor {
|
impl WsHandler<CheckAuthToken> for WebSocketActor {
|
||||||
fn handle_msg(&mut self, msg: CheckAuthToken, ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: CheckAuthToken, ctx: &mut Self::Context) -> WsResult {
|
||||||
let user: jirs_data::User = match block_on(self.db.send(AuthorizeUser {
|
let user: jirs_data::User = db_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
AuthorizeUser {
|
||||||
access_token: msg.token,
|
access_token: msg.token,
|
||||||
})) {
|
},
|
||||||
Ok(Ok(u)) => u,
|
Ok(Some(WsMsg::AuthorizeLoaded(Err(
|
||||||
Ok(Err(_)) => {
|
|
||||||
return Ok(Some(WsMsg::AuthorizeLoaded(Err(
|
|
||||||
"Invalid auth token".to_string()
|
"Invalid auth token".to_string()
|
||||||
))));
|
)))),
|
||||||
}
|
Ok(Some(WsMsg::AuthorizeExpired))
|
||||||
_ => return Ok(Some(WsMsg::AuthorizeExpired)),
|
);
|
||||||
};
|
|
||||||
self.current_user = Some(user.clone());
|
self.current_user = Some(user.clone());
|
||||||
self.current_user_project = self.load_user_project().ok();
|
self.current_user_project = self.load_user_project().ok();
|
||||||
self.current_project = self.load_project().ok();
|
self.current_project = self.load_project().ok();
|
||||||
@ -94,13 +68,14 @@ pub struct CheckBindToken {
|
|||||||
|
|
||||||
impl WsHandler<CheckBindToken> for WebSocketActor {
|
impl WsHandler<CheckBindToken> for WebSocketActor {
|
||||||
fn handle_msg(&mut self, msg: CheckBindToken, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: CheckBindToken, _ctx: &mut Self::Context) -> WsResult {
|
||||||
let token: Token = match block_on(self.db.send(FindBindToken {
|
let token: Token = db_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
FindBindToken {
|
||||||
token: msg.bind_token,
|
token: msg.bind_token,
|
||||||
})) {
|
},
|
||||||
Ok(Ok(token)) => token,
|
Ok(Some(WsMsg::BindTokenBad)),
|
||||||
Ok(Err(_)) => return Ok(Some(WsMsg::BindTokenBad)),
|
Ok(None)
|
||||||
_ => return Ok(None),
|
);
|
||||||
};
|
|
||||||
Ok(Some(WsMsg::BindTokenOk(token.access_token)))
|
Ok(Some(WsMsg::BindTokenOk(token.access_token)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use {
|
use {
|
||||||
crate::{WebSocketActor, WsHandler, WsResult},
|
crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||||
futures::executor::block_on,
|
futures::executor::block_on,
|
||||||
jirs_data::{CommentId, CreateCommentPayload, IssueId, UpdateCommentPayload, WsMsg},
|
jirs_data::{CommentId, CreateCommentPayload, IssueId, UpdateCommentPayload, WsMsg},
|
||||||
};
|
};
|
||||||
@ -12,19 +12,12 @@ impl WsHandler<LoadIssueComments> for WebSocketActor {
|
|||||||
fn handle_msg(&mut self, msg: LoadIssueComments, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: LoadIssueComments, _ctx: &mut Self::Context) -> WsResult {
|
||||||
self.require_user()?;
|
self.require_user()?;
|
||||||
|
|
||||||
let comments = match block_on(self.db.send(database_actor::comments::LoadIssueComments {
|
let comments = db_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
database_actor::comments::LoadIssueComments {
|
||||||
issue_id: msg.issue_id,
|
issue_id: msg.issue_id,
|
||||||
})) {
|
|
||||||
Ok(Ok(comments)) => comments,
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
log::error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
);
|
||||||
log::error!("{}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(WsMsg::IssueCommentsLoaded(comments)))
|
Ok(Some(WsMsg::IssueCommentsLoaded(comments)))
|
||||||
}
|
}
|
||||||
@ -39,21 +32,14 @@ impl WsHandler<CreateCommentPayload> for WebSocketActor {
|
|||||||
msg.user_id = Some(user_id);
|
msg.user_id = Some(user_id);
|
||||||
}
|
}
|
||||||
let issue_id = msg.issue_id;
|
let issue_id = msg.issue_id;
|
||||||
match block_on(self.db.send(CreateComment {
|
let _ = db_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
CreateComment {
|
||||||
user_id,
|
user_id,
|
||||||
issue_id,
|
issue_id,
|
||||||
body: msg.body,
|
body: msg.body,
|
||||||
})) {
|
|
||||||
Ok(Ok(_)) => (),
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
log::error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
);
|
||||||
log::error!("{}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.handle_msg(LoadIssueComments { issue_id }, ctx)
|
self.handle_msg(LoadIssueComments { issue_id }, ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,21 +55,14 @@ impl WsHandler<UpdateCommentPayload> for WebSocketActor {
|
|||||||
body,
|
body,
|
||||||
} = msg;
|
} = msg;
|
||||||
|
|
||||||
let comment = match block_on(self.db.send(UpdateComment {
|
let comment = db_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
UpdateComment {
|
||||||
comment_id,
|
comment_id,
|
||||||
user_id,
|
user_id,
|
||||||
body,
|
body,
|
||||||
})) {
|
|
||||||
Ok(Ok(comment)) => comment,
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
log::error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
);
|
||||||
log::error!("{}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.broadcast(&WsMsg::CommentUpdated(comment));
|
self.broadcast(&WsMsg::CommentUpdated(comment));
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@ -99,20 +78,13 @@ impl WsHandler<DeleteComment> for WebSocketActor {
|
|||||||
|
|
||||||
let user_id = self.require_user()?.id;
|
let user_id = self.require_user()?.id;
|
||||||
|
|
||||||
let m = DeleteComment {
|
let n = db_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
DeleteComment {
|
||||||
comment_id: msg.comment_id,
|
comment_id: msg.comment_id,
|
||||||
user_id,
|
user_id,
|
||||||
};
|
|
||||||
match block_on(self.db.send(m)) {
|
|
||||||
Ok(Ok(n)) => Ok(Some(WsMsg::CommentDeleted(msg.comment_id, n))),
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
log::error!("{:?}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("{}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
Ok(Some(WsMsg::CommentDeleted(msg.comment_id, n)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use {
|
use {
|
||||||
crate::{WebSocketActor, WsHandler, WsResult},
|
crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||||
futures::executor::block_on,
|
futures::executor::block_on,
|
||||||
jirs_data::{EpicId, NameString, UserProject, WsMsg},
|
jirs_data::{EpicId, NameString, UserProject, WsMsg},
|
||||||
};
|
};
|
||||||
@ -9,8 +9,7 @@ pub struct LoadEpics;
|
|||||||
impl WsHandler<LoadEpics> for WebSocketActor {
|
impl WsHandler<LoadEpics> for WebSocketActor {
|
||||||
fn handle_msg(&mut self, _msg: LoadEpics, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, _msg: LoadEpics, _ctx: &mut Self::Context) -> WsResult {
|
||||||
let project_id = self.require_user_project()?.project_id;
|
let project_id = self.require_user_project()?.project_id;
|
||||||
let epics =
|
let epics = db_or_debug_and_return!(self, database_actor::epics::LoadEpics { project_id });
|
||||||
crate::query_db_or_print!(self, database_actor::epics::LoadEpics { project_id });
|
|
||||||
Ok(Some(WsMsg::EpicsLoaded(epics)))
|
Ok(Some(WsMsg::EpicsLoaded(epics)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -27,7 +26,7 @@ impl WsHandler<CreateEpic> for WebSocketActor {
|
|||||||
project_id,
|
project_id,
|
||||||
..
|
..
|
||||||
} = self.require_user_project()?;
|
} = self.require_user_project()?;
|
||||||
let epic = crate::query_db_or_print!(
|
let epic = db_or_debug_and_return!(
|
||||||
self,
|
self,
|
||||||
database_actor::epics::CreateEpic {
|
database_actor::epics::CreateEpic {
|
||||||
user_id: *user_id,
|
user_id: *user_id,
|
||||||
@ -48,7 +47,7 @@ impl WsHandler<UpdateEpic> for WebSocketActor {
|
|||||||
fn handle_msg(&mut self, msg: UpdateEpic, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: UpdateEpic, _ctx: &mut Self::Context) -> WsResult {
|
||||||
let UpdateEpic { epic_id, name } = msg;
|
let UpdateEpic { epic_id, name } = msg;
|
||||||
let UserProject { project_id, .. } = self.require_user_project()?;
|
let UserProject { project_id, .. } = self.require_user_project()?;
|
||||||
let epic = crate::query_db_or_print!(
|
let epic = db_or_debug_and_return!(
|
||||||
self,
|
self,
|
||||||
database_actor::epics::UpdateEpic {
|
database_actor::epics::UpdateEpic {
|
||||||
project_id: *project_id,
|
project_id: *project_id,
|
||||||
@ -68,7 +67,7 @@ impl WsHandler<DeleteEpic> for WebSocketActor {
|
|||||||
fn handle_msg(&mut self, msg: DeleteEpic, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: DeleteEpic, _ctx: &mut Self::Context) -> WsResult {
|
||||||
let DeleteEpic { epic_id } = msg;
|
let DeleteEpic { epic_id } = msg;
|
||||||
let UserProject { user_id, .. } = self.require_user_project()?;
|
let UserProject { user_id, .. } = self.require_user_project()?;
|
||||||
let n = crate::query_db_or_print!(
|
let n = db_or_debug_and_return!(
|
||||||
self,
|
self,
|
||||||
database_actor::epics::DeleteEpic {
|
database_actor::epics::DeleteEpic {
|
||||||
user_id: *user_id,
|
user_id: *user_id,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use {
|
use {
|
||||||
crate::{WebSocketActor, WsHandler, WsResult},
|
crate::{actor_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||||
futures::executor::block_on,
|
futures::executor::block_on,
|
||||||
jirs_data::{Code, Lang, WsMsg},
|
jirs_data::{Code, Lang, WsMsg},
|
||||||
};
|
};
|
||||||
@ -9,19 +9,14 @@ pub struct HighlightCode(pub Lang, pub Code);
|
|||||||
impl WsHandler<HighlightCode> for WebSocketActor {
|
impl WsHandler<HighlightCode> for WebSocketActor {
|
||||||
fn handle_msg(&mut self, msg: HighlightCode, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: HighlightCode, _ctx: &mut Self::Context) -> WsResult {
|
||||||
self.require_user()?.id;
|
self.require_user()?.id;
|
||||||
match block_on(self.hi.send(highlight_actor::HighlightCode {
|
let res = actor_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
hi,
|
||||||
|
highlight_actor::HighlightCode {
|
||||||
code: msg.1,
|
code: msg.1,
|
||||||
lang: msg.0,
|
lang: msg.0,
|
||||||
})) {
|
|
||||||
Ok(Ok(res)) => Ok(Some(WsMsg::HighlightedCode(res))),
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
Ok(Some(WsMsg::HighlightedCode(res)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use {
|
use {
|
||||||
crate::{server::InnerMsg, WebSocketActor, WsHandler, WsMessageSender, WsResult},
|
crate::{
|
||||||
|
db_or_debug_and_return, mail_or_debug_and_return, server::InnerMsg, WebSocketActor,
|
||||||
|
WsHandler, WsMessageSender, WsResult,
|
||||||
|
},
|
||||||
database_actor::{invitations, messages::CreateMessageReceiver},
|
database_actor::{invitations, messages::CreateMessageReceiver},
|
||||||
futures::executor::block_on,
|
futures::executor::block_on,
|
||||||
jirs_data::{
|
jirs_data::{
|
||||||
@ -15,18 +18,8 @@ impl WsHandler<ListInvitation> for WebSocketActor {
|
|||||||
Some(id) => id,
|
Some(id) => id,
|
||||||
_ => return Ok(None),
|
_ => return Ok(None),
|
||||||
};
|
};
|
||||||
let res = match block_on(self.db.send(invitations::ListInvitation { user_id })) {
|
let v = db_or_debug_and_return!(self, invitations::ListInvitation { user_id });
|
||||||
Ok(Ok(v)) => Some(WsMsg::InvitationListLoaded(v)),
|
Ok(Some(WsMsg::InvitationListLoaded(v)))
|
||||||
Ok(Err(e)) => {
|
|
||||||
log::error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("{}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,39 +38,28 @@ impl WsHandler<CreateInvitation> for WebSocketActor {
|
|||||||
let (user_id, inviter_name) = self.require_user().map(|u| (u.id, u.name.clone()))?;
|
let (user_id, inviter_name) = self.require_user().map(|u| (u.id, u.name.clone()))?;
|
||||||
|
|
||||||
let CreateInvitation { email, name, role } = msg;
|
let CreateInvitation { email, name, role } = msg;
|
||||||
let invitation =
|
let invitation = db_or_debug_and_return!(
|
||||||
match block_on(self.db.send(database_actor::invitations::CreateInvitation {
|
self,
|
||||||
|
database_actor::invitations::CreateInvitation {
|
||||||
user_id,
|
user_id,
|
||||||
project_id,
|
project_id,
|
||||||
email: email.clone(),
|
email: email.clone(),
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
role,
|
role,
|
||||||
})) {
|
},
|
||||||
Ok(Ok(invitation)) => invitation,
|
Ok(Some(WsMsg::InvitationSendFailure)),
|
||||||
Ok(Err(e)) => {
|
Ok(Some(WsMsg::InvitationSendFailure))
|
||||||
error!("{:?}", e);
|
);
|
||||||
return Ok(Some(WsMsg::InvitationSendFailure));
|
let _ = mail_or_debug_and_return!(
|
||||||
}
|
self,
|
||||||
Err(e) => {
|
mail_actor::invite::Invite {
|
||||||
error!("{}", e);
|
|
||||||
return Ok(Some(WsMsg::InvitationSendFailure));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match block_on(self.mail.send(mail_actor::invite::Invite {
|
|
||||||
bind_token: invitation.bind_token,
|
bind_token: invitation.bind_token,
|
||||||
email: invitation.email,
|
email: invitation.email,
|
||||||
inviter_name,
|
inviter_name,
|
||||||
})) {
|
},
|
||||||
Ok(Ok(_)) => (),
|
Ok(Some(WsMsg::InvitationSendFailure)),
|
||||||
Ok(Err(e)) => {
|
Ok(Some(WsMsg::InvitationSendFailure))
|
||||||
error!("{:?}", e);
|
);
|
||||||
return Ok(Some(WsMsg::InvitationSendFailure));
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{}", e);
|
|
||||||
return Ok(Some(WsMsg::InvitationSendFailure));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If user exists then send message to him
|
// If user exists then send message to him
|
||||||
if let Ok(Ok(message)) = block_on(self.db.send(database_actor::messages::CreateMessage {
|
if let Ok(Ok(message)) = block_on(self.db.send(database_actor::messages::CreateMessage {
|
||||||
@ -106,18 +88,8 @@ impl WsHandler<DeleteInvitation> for WebSocketActor {
|
|||||||
fn handle_msg(&mut self, msg: DeleteInvitation, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: DeleteInvitation, _ctx: &mut Self::Context) -> WsResult {
|
||||||
self.require_user()?;
|
self.require_user()?;
|
||||||
let DeleteInvitation { id } = msg;
|
let DeleteInvitation { id } = msg;
|
||||||
let res = match block_on(self.db.send(invitations::DeleteInvitation { id })) {
|
let _ = db_or_debug_and_return!(self, invitations::DeleteInvitation { id });
|
||||||
Ok(Ok(_)) => None,
|
Ok(None)
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,18 +101,8 @@ impl WsHandler<RevokeInvitation> for WebSocketActor {
|
|||||||
fn handle_msg(&mut self, msg: RevokeInvitation, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: RevokeInvitation, _ctx: &mut Self::Context) -> WsResult {
|
||||||
self.require_user()?;
|
self.require_user()?;
|
||||||
let RevokeInvitation { id } = msg;
|
let RevokeInvitation { id } = msg;
|
||||||
let res = match block_on(self.db.send(invitations::RevokeInvitation { id })) {
|
let _ = db_or_debug_and_return!(self, invitations::RevokeInvitation { id });
|
||||||
Ok(Ok(_)) => Some(WsMsg::InvitationRevokeSuccess(id)),
|
Ok(Some(WsMsg::InvitationRevokeSuccess(id)))
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,45 +113,34 @@ pub struct AcceptInvitation {
|
|||||||
impl WsHandler<AcceptInvitation> for WebSocketActor {
|
impl WsHandler<AcceptInvitation> for WebSocketActor {
|
||||||
fn handle_msg(&mut self, msg: AcceptInvitation, ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: AcceptInvitation, ctx: &mut Self::Context) -> WsResult {
|
||||||
let AcceptInvitation { invitation_token } = msg;
|
let AcceptInvitation { invitation_token } = msg;
|
||||||
let token = match block_on(
|
let token = db_or_debug_and_return!(
|
||||||
self.db
|
self,
|
||||||
.send(invitations::AcceptInvitation { invitation_token }),
|
invitations::AcceptInvitation { invitation_token },
|
||||||
) {
|
Ok(Some(WsMsg::InvitationAcceptFailure(invitation_token))),
|
||||||
Ok(Ok(token)) => token,
|
Ok(Some(WsMsg::InvitationAcceptFailure(invitation_token)))
|
||||||
Ok(Err(e)) => {
|
);
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(Some(WsMsg::InvitationAcceptFailure(invitation_token)));
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{}", e);
|
|
||||||
return Ok(Some(WsMsg::InvitationAcceptFailure(invitation_token)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for message in block_on(
|
for message in crate::actor_or_debug_and_fallback!(
|
||||||
self.db
|
self,
|
||||||
.send(database_actor::messages::LookupMessagesByToken {
|
db,
|
||||||
|
database_actor::messages::LookupMessagesByToken {
|
||||||
token: invitation_token,
|
token: invitation_token,
|
||||||
user_id: token.user_id,
|
user_id: token.user_id,
|
||||||
}),
|
},
|
||||||
)
|
vec![],
|
||||||
.unwrap_or_else(|_| Ok(vec![]))
|
vec![]
|
||||||
.unwrap_or_default()
|
) {
|
||||||
{
|
crate::actor_or_debug_and_ignore!(
|
||||||
match block_on(self.db.send(database_actor::messages::MarkMessageSeen {
|
self,
|
||||||
|
db,
|
||||||
|
database_actor::messages::MarkMessageSeen {
|
||||||
user_id: token.user_id,
|
user_id: token.user_id,
|
||||||
message_id: message.id,
|
message_id: message.id,
|
||||||
})) {
|
},
|
||||||
Ok(Ok(n)) => {
|
|n| {
|
||||||
ctx.send_msg(&WsMsg::MessageMarkedSeen(message.id, n));
|
ctx.send_msg(&WsMsg::MessageMarkedSeen(message.id, n));
|
||||||
}
|
}
|
||||||
Ok(Err(e)) => {
|
);
|
||||||
error!("{:?}", e);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Some(WsMsg::InvitationAcceptSuccess(token.access_token)))
|
Ok(Some(WsMsg::InvitationAcceptSuccess(token.access_token)))
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use {
|
use {
|
||||||
crate::{WebSocketActor, WsHandler, WsResult},
|
crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||||
database_actor::issue_statuses,
|
database_actor::issue_statuses,
|
||||||
futures::executor::block_on,
|
futures::executor::block_on,
|
||||||
jirs_data::{IssueStatusId, Position, TitleString, WsMsg},
|
jirs_data::{IssueStatusId, Position, TitleString, WsMsg},
|
||||||
@ -11,21 +11,8 @@ impl WsHandler<LoadIssueStatuses> for WebSocketActor {
|
|||||||
fn handle_msg(&mut self, _msg: LoadIssueStatuses, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, _msg: LoadIssueStatuses, _ctx: &mut Self::Context) -> WsResult {
|
||||||
let project_id = self.require_user_project()?.project_id;
|
let project_id = self.require_user_project()?.project_id;
|
||||||
|
|
||||||
let msg = match block_on(
|
let v = db_or_debug_and_return!(self, issue_statuses::LoadIssueStatuses { project_id });
|
||||||
self.db
|
Ok(Some(WsMsg::IssueStatusesLoaded(v)))
|
||||||
.send(issue_statuses::LoadIssueStatuses { project_id }),
|
|
||||||
) {
|
|
||||||
Ok(Ok(v)) => Some(WsMsg::IssueStatusesLoaded(v)),
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(msg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,22 +26,15 @@ impl WsHandler<CreateIssueStatus> for WebSocketActor {
|
|||||||
let project_id = self.require_user_project()?.project_id;
|
let project_id = self.require_user_project()?.project_id;
|
||||||
|
|
||||||
let CreateIssueStatus { position, name } = msg;
|
let CreateIssueStatus { position, name } = msg;
|
||||||
let msg = match block_on(self.db.send(issue_statuses::CreateIssueStatus {
|
let issue_status = db_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
issue_statuses::CreateIssueStatus {
|
||||||
project_id,
|
project_id,
|
||||||
position,
|
position,
|
||||||
name,
|
name,
|
||||||
})) {
|
|
||||||
Ok(Ok(is)) => Some(WsMsg::IssueStatusCreated(is)),
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
);
|
||||||
error!("{}", e);
|
Ok(Some(WsMsg::IssueStatusCreated(issue_status)))
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(msg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,21 +47,14 @@ impl WsHandler<DeleteIssueStatus> for WebSocketActor {
|
|||||||
let project_id = self.require_user_project()?.project_id;
|
let project_id = self.require_user_project()?.project_id;
|
||||||
|
|
||||||
let DeleteIssueStatus { issue_status_id } = msg;
|
let DeleteIssueStatus { issue_status_id } = msg;
|
||||||
let msg = match block_on(self.db.send(issue_statuses::DeleteIssueStatus {
|
let n = db_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
issue_statuses::DeleteIssueStatus {
|
||||||
issue_status_id,
|
issue_status_id,
|
||||||
project_id,
|
project_id,
|
||||||
})) {
|
|
||||||
Ok(Ok(n)) => Some(WsMsg::IssueStatusDeleted(msg.issue_status_id, n)),
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
);
|
||||||
error!("{}", e);
|
Ok(Some(WsMsg::IssueStatusDeleted(msg.issue_status_id, n)))
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(msg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,22 +73,16 @@ impl WsHandler<UpdateIssueStatus> for WebSocketActor {
|
|||||||
position,
|
position,
|
||||||
name,
|
name,
|
||||||
} = msg;
|
} = msg;
|
||||||
let msg = match block_on(self.db.send(issue_statuses::UpdateIssueStatus {
|
let issue_status = db_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
issue_statuses::UpdateIssueStatus {
|
||||||
issue_status_id,
|
issue_status_id,
|
||||||
position,
|
position,
|
||||||
name,
|
name,
|
||||||
project_id,
|
project_id,
|
||||||
})) {
|
|
||||||
Ok(Ok(is)) => Some(WsMsg::IssueStatusUpdated(is)),
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
);
|
||||||
error!("{}", e);
|
let msg = Some(WsMsg::IssueStatusUpdated(issue_status));
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some(ws_msg) = msg.as_ref() {
|
if let Some(ws_msg) = msg.as_ref() {
|
||||||
self.broadcast(ws_msg)
|
self.broadcast(ws_msg)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use {
|
use {
|
||||||
crate::{WebSocketActor, WsHandler, WsResult},
|
crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||||
database_actor::{
|
database_actor::{
|
||||||
issue_assignees::LoadAssignees,
|
issue_assignees::LoadAssignees,
|
||||||
issues::{LoadProjectIssues, UpdateIssue},
|
issues::{LoadProjectIssues, UpdateIssue},
|
||||||
@ -125,23 +125,11 @@ impl WsHandler<UpdateIssueHandler> for WebSocketActor {
|
|||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut issue: jirs_data::Issue = match block_on(self.db.send(msg)) {
|
let issue = db_or_debug_and_return!(self, msg);
|
||||||
Ok(Ok(issue)) => issue.into(),
|
let mut issue: jirs_data::Issue = issue.into();
|
||||||
_ => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let assignees: Vec<IssueAssignee> =
|
let assignees: Vec<IssueAssignee> =
|
||||||
match block_on(self.db.send(LoadAssignees { issue_id: issue.id })) {
|
db_or_debug_and_return!(self, LoadAssignees { issue_id: issue.id });
|
||||||
Ok(Ok(v)) => v,
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for assignee in assignees {
|
for assignee in assignees {
|
||||||
issue.user_ids.push(assignee.user_id);
|
issue.user_ids.push(assignee.user_id);
|
||||||
@ -170,18 +158,8 @@ impl WsHandler<CreateIssuePayload> for WebSocketActor {
|
|||||||
user_ids: msg.user_ids,
|
user_ids: msg.user_ids,
|
||||||
epic_id: msg.epic_id,
|
epic_id: msg.epic_id,
|
||||||
};
|
};
|
||||||
let m = match block_on(self.db.send(msg)) {
|
let issue = db_or_debug_and_return!(self, msg);
|
||||||
Ok(Ok(issue)) => Some(WsMsg::IssueCreated(issue.into())),
|
Ok(Some(WsMsg::IssueCreated(issue.into())))
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(m)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,21 +170,11 @@ pub struct DeleteIssue {
|
|||||||
impl WsHandler<DeleteIssue> for WebSocketActor {
|
impl WsHandler<DeleteIssue> for WebSocketActor {
|
||||||
fn handle_msg(&mut self, msg: DeleteIssue, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: DeleteIssue, _ctx: &mut Self::Context) -> WsResult {
|
||||||
self.require_user()?;
|
self.require_user()?;
|
||||||
let m = match block_on(
|
let n = db_or_debug_and_return!(
|
||||||
self.db
|
self,
|
||||||
.send(database_actor::issues::DeleteIssue { issue_id: msg.id }),
|
database_actor::issues::DeleteIssue { issue_id: msg.id }
|
||||||
) {
|
);
|
||||||
Ok(Ok(n)) => Some(WsMsg::IssueDeleted(msg.id, n)),
|
Ok(Some(WsMsg::IssueDeleted(msg.id, n)))
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(m)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,14 +184,11 @@ impl WsHandler<LoadIssues> for WebSocketActor {
|
|||||||
fn handle_msg(&mut self, _msg: LoadIssues, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, _msg: LoadIssues, _ctx: &mut Self::Context) -> WsResult {
|
||||||
let project_id = self.require_user_project()?.project_id;
|
let project_id = self.require_user_project()?.project_id;
|
||||||
|
|
||||||
let issues: Vec<jirs_data::Issue> =
|
let v = db_or_debug_and_return!(self, LoadProjectIssues { project_id });
|
||||||
match block_on(self.db.send(LoadProjectIssues { project_id })) {
|
let issues: Vec<jirs_data::Issue> = v.into_iter().map(|i| i.into()).collect();
|
||||||
Ok(Ok(v)) => v.into_iter().map(|i| i.into()).collect(),
|
|
||||||
_ => return Ok(None),
|
|
||||||
};
|
|
||||||
let mut issue_map = HashMap::new();
|
let mut issue_map = HashMap::new();
|
||||||
let mut queue = vec![];
|
let mut queue = vec![];
|
||||||
for issue in issues.into_iter() {
|
for issue in issues {
|
||||||
let f = self.db.send(LoadAssignees { issue_id: issue.id });
|
let f = self.db.send(LoadAssignees { issue_id: issue.id });
|
||||||
queue.push(f);
|
queue.push(f);
|
||||||
issue_map.insert(issue.id, issue);
|
issue_map.insert(issue.id, issue);
|
||||||
@ -238,9 +203,10 @@ impl WsHandler<LoadIssues> for WebSocketActor {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
let mut issues = vec![];
|
let mut issues = vec![];
|
||||||
for (_, issue) in issue_map.into_iter() {
|
for (_, issue) in issue_map {
|
||||||
issues.push(issue);
|
issues.push(issue);
|
||||||
}
|
}
|
||||||
|
issues.sort_by(|a, b| a.list_position.cmp(&b.list_position));
|
||||||
|
|
||||||
Ok(Some(WsMsg::ProjectIssuesLoaded(issues)))
|
Ok(Some(WsMsg::ProjectIssuesLoaded(issues)))
|
||||||
}
|
}
|
||||||
@ -252,16 +218,18 @@ impl WsHandler<SyncIssueListPosition> for WebSocketActor {
|
|||||||
fn handle_msg(&mut self, msg: SyncIssueListPosition, ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: SyncIssueListPosition, ctx: &mut Self::Context) -> WsResult {
|
||||||
let _project_id = self.require_user_project()?.project_id;
|
let _project_id = self.require_user_project()?.project_id;
|
||||||
for (issue_id, list_position, status_id, epic_id) in msg.0 {
|
for (issue_id, list_position, status_id, epic_id) in msg.0 {
|
||||||
match block_on(self.db.send(database_actor::issues::UpdateIssue {
|
crate::actor_or_debug_and_ignore!(
|
||||||
|
self,
|
||||||
|
db,
|
||||||
|
database_actor::issues::UpdateIssue {
|
||||||
issue_id,
|
issue_id,
|
||||||
list_position: Some(list_position),
|
list_position: Some(list_position),
|
||||||
issue_status_id: Some(status_id),
|
issue_status_id: Some(status_id),
|
||||||
epic_id: Some(epic_id),
|
epic_id: Some(epic_id),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})) {
|
},
|
||||||
Ok(Ok(_)) => (),
|
|_| {}
|
||||||
_ => return Ok(None),
|
);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.handle_msg(LoadIssues, ctx)
|
self.handle_msg(LoadIssues, ctx)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use {
|
use {
|
||||||
crate::{WebSocketActor, WsHandler, WsResult},
|
crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||||
database_actor::messages,
|
database_actor::messages,
|
||||||
futures::executor::block_on,
|
futures::executor::block_on,
|
||||||
jirs_data::{MessageId, WsMsg},
|
jirs_data::{MessageId, WsMsg},
|
||||||
@ -10,17 +10,8 @@ pub struct LoadMessages;
|
|||||||
impl WsHandler<LoadMessages> for WebSocketActor {
|
impl WsHandler<LoadMessages> for WebSocketActor {
|
||||||
fn handle_msg(&mut self, _msg: LoadMessages, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, _msg: LoadMessages, _ctx: &mut Self::Context) -> WsResult {
|
||||||
let user_id = self.require_user()?.id;
|
let user_id = self.require_user()?.id;
|
||||||
match block_on(self.db.send(messages::LoadMessages { user_id })) {
|
let v = db_or_debug_and_return!(self, messages::LoadMessages { user_id });
|
||||||
Ok(Ok(v)) => Ok(Some(WsMsg::MessagesLoaded(v))),
|
Ok(Some(WsMsg::MessagesLoaded(v)))
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,19 +22,13 @@ pub struct MarkMessageSeen {
|
|||||||
impl WsHandler<MarkMessageSeen> for WebSocketActor {
|
impl WsHandler<MarkMessageSeen> for WebSocketActor {
|
||||||
fn handle_msg(&mut self, msg: MarkMessageSeen, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: MarkMessageSeen, _ctx: &mut Self::Context) -> WsResult {
|
||||||
let user_id = self.require_user()?.id;
|
let user_id = self.require_user()?.id;
|
||||||
match block_on(self.db.send(messages::MarkMessageSeen {
|
let count = db_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
messages::MarkMessageSeen {
|
||||||
message_id: msg.id,
|
message_id: msg.id,
|
||||||
user_id,
|
user_id,
|
||||||
})) {
|
|
||||||
Ok(Ok(count)) => Ok(Some(WsMsg::MessageMarkedSeen(msg.id, count))),
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
Ok(Some(WsMsg::MessageMarkedSeen(msg.id, count)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use {
|
use {
|
||||||
crate::{WebSocketActor, WsHandler, WsResult},
|
crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||||
database_actor as db,
|
database_actor as db,
|
||||||
futures::executor::block_on,
|
futures::executor::block_on,
|
||||||
jirs_data::{UpdateProjectPayload, UserProject, WsMsg},
|
jirs_data::{UpdateProjectPayload, UserProject, WsMsg},
|
||||||
@ -12,38 +12,21 @@ impl WsHandler<UpdateProjectPayload> for WebSocketActor {
|
|||||||
project_id,
|
project_id,
|
||||||
..
|
..
|
||||||
} = self.require_user_project()?;
|
} = self.require_user_project()?;
|
||||||
match block_on(self.db.send(database_actor::projects::UpdateProject {
|
let _ = db_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
database_actor::projects::UpdateProject {
|
||||||
project_id: *project_id,
|
project_id: *project_id,
|
||||||
name: msg.name,
|
name: msg.name,
|
||||||
url: msg.url,
|
url: msg.url,
|
||||||
description: msg.description,
|
description: msg.description,
|
||||||
category: msg.category,
|
category: msg.category,
|
||||||
time_tracking: msg.time_tracking,
|
time_tracking: msg.time_tracking,
|
||||||
})) {
|
|
||||||
Ok(Ok(_)) => (),
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
);
|
||||||
error!("{:?}", e);
|
let projects = db_or_debug_and_return!(
|
||||||
return Ok(None);
|
self,
|
||||||
}
|
database_actor::projects::LoadProjects { user_id: *user_id }
|
||||||
};
|
);
|
||||||
let projects = match block_on(
|
|
||||||
self.db
|
|
||||||
.send(database_actor::projects::LoadProjects { user_id: *user_id }),
|
|
||||||
) {
|
|
||||||
Ok(Ok(projects)) => projects,
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(Some(WsMsg::ProjectsLoaded(projects)))
|
Ok(Some(WsMsg::ProjectsLoaded(projects)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,16 +36,7 @@ pub struct LoadProjects;
|
|||||||
impl WsHandler<LoadProjects> for WebSocketActor {
|
impl WsHandler<LoadProjects> for WebSocketActor {
|
||||||
fn handle_msg(&mut self, _msg: LoadProjects, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, _msg: LoadProjects, _ctx: &mut Self::Context) -> WsResult {
|
||||||
let user_id = self.require_user()?.id;
|
let user_id = self.require_user()?.id;
|
||||||
match block_on(self.db.send(db::projects::LoadProjects { user_id })) {
|
let v = db_or_debug_and_return!(self, db::projects::LoadProjects { user_id });
|
||||||
Ok(Ok(v)) => Ok(Some(WsMsg::ProjectsLoaded(v))),
|
Ok(Some(WsMsg::ProjectsLoaded(v)))
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use {
|
use {
|
||||||
crate::{WebSocketActor, WsHandler, WsResult},
|
crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||||
database_actor as db,
|
database_actor as db,
|
||||||
futures::executor::block_on,
|
futures::executor::block_on,
|
||||||
jirs_data::{UserProjectId, WsMsg},
|
jirs_data::{UserProjectId, WsMsg},
|
||||||
@ -10,20 +10,8 @@ pub struct LoadUserProjects;
|
|||||||
impl WsHandler<LoadUserProjects> for WebSocketActor {
|
impl WsHandler<LoadUserProjects> for WebSocketActor {
|
||||||
fn handle_msg(&mut self, _msg: LoadUserProjects, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, _msg: LoadUserProjects, _ctx: &mut Self::Context) -> WsResult {
|
||||||
let user_id = self.require_user()?.id;
|
let user_id = self.require_user()?.id;
|
||||||
match block_on(
|
let v = db_or_debug_and_return!(self, db::user_projects::LoadUserProjects { user_id });
|
||||||
self.db
|
Ok(Some(WsMsg::UserProjectsLoaded(v)))
|
||||||
.send(db::user_projects::LoadUserProjects { user_id }),
|
|
||||||
) {
|
|
||||||
Ok(Ok(v)) => Ok(Some(WsMsg::UserProjectsLoaded(v))),
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,22 +22,14 @@ pub struct SetCurrentUserProject {
|
|||||||
impl WsHandler<SetCurrentUserProject> for WebSocketActor {
|
impl WsHandler<SetCurrentUserProject> for WebSocketActor {
|
||||||
fn handle_msg(&mut self, msg: SetCurrentUserProject, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: SetCurrentUserProject, _ctx: &mut Self::Context) -> WsResult {
|
||||||
let user_id = self.require_user()?.id;
|
let user_id = self.require_user()?.id;
|
||||||
match block_on(self.db.send(db::user_projects::ChangeCurrentUserProject {
|
let user_project = db_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
db::user_projects::ChangeCurrentUserProject {
|
||||||
user_id,
|
user_id,
|
||||||
id: msg.id,
|
id: msg.id,
|
||||||
})) {
|
}
|
||||||
Ok(Ok(user_project)) => {
|
);
|
||||||
self.current_user_project = Some(user_project.clone());
|
self.current_user_project = Some(user_project.clone());
|
||||||
Ok(Some(WsMsg::UserProjectCurrentChanged(user_project)))
|
Ok(Some(WsMsg::UserProjectCurrentChanged(user_project)))
|
||||||
}
|
}
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use {
|
use {
|
||||||
crate::{handlers::auth::Authenticate, WebSocketActor, WsHandler, WsResult},
|
crate::{
|
||||||
|
db_or_debug_and_return, handlers::auth::Authenticate, WebSocketActor, WsHandler, WsResult,
|
||||||
|
},
|
||||||
database_actor::{self, users::Register as DbRegister},
|
database_actor::{self, users::Register as DbRegister},
|
||||||
futures::executor::block_on,
|
futures::executor::block_on,
|
||||||
jirs_data::{UserId, UserProject, UserRole, WsMsg},
|
jirs_data::{UserId, UserProject, UserRole, WsMsg},
|
||||||
@ -12,18 +14,8 @@ impl WsHandler<LoadProjectUsers> for WebSocketActor {
|
|||||||
use database_actor::users::LoadProjectUsers as Msg;
|
use database_actor::users::LoadProjectUsers as Msg;
|
||||||
|
|
||||||
let project_id = self.require_user_project()?.project_id;
|
let project_id = self.require_user_project()?.project_id;
|
||||||
let m = match block_on(self.db.send(Msg { project_id })) {
|
let v = db_or_debug_and_return!(self, Msg { project_id });
|
||||||
Ok(Ok(v)) => Some(WsMsg::ProjectUsersLoaded(v)),
|
Ok(Some(WsMsg::ProjectUsersLoaded(v)))
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(m)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,26 +27,24 @@ pub struct Register {
|
|||||||
impl WsHandler<Register> for WebSocketActor {
|
impl WsHandler<Register> for WebSocketActor {
|
||||||
fn handle_msg(&mut self, msg: Register, ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: Register, ctx: &mut Self::Context) -> WsResult {
|
||||||
let Register { name, email } = msg;
|
let Register { name, email } = msg;
|
||||||
let msg = match block_on(self.db.send(DbRegister {
|
let _ = db_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
DbRegister {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
email: email.clone(),
|
email: email.clone(),
|
||||||
project_id: None,
|
project_id: None,
|
||||||
role: UserRole::Owner,
|
role: UserRole::Owner,
|
||||||
})) {
|
},
|
||||||
Ok(Ok(_)) => Some(WsMsg::SignUpSuccess),
|
Ok(Some(WsMsg::SignUpPairTaken)),
|
||||||
Ok(Err(_)) => Some(WsMsg::SignUpPairTaken),
|
Ok(None)
|
||||||
Err(e) => {
|
);
|
||||||
error!("{}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.handle_msg(Authenticate { name, email }, ctx) {
|
match self.handle_msg(Authenticate { name, email }, ctx) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) => return Ok(Some(e)),
|
Err(e) => return Ok(Some(e)),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(msg)
|
Ok(Some(WsMsg::SignUpSuccess))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,13 +54,8 @@ impl WsHandler<LoadInvitedUsers> for WebSocketActor {
|
|||||||
fn handle_msg(&mut self, _msg: LoadInvitedUsers, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, _msg: LoadInvitedUsers, _ctx: &mut Self::Context) -> WsResult {
|
||||||
let user_id = self.require_user()?.id;
|
let user_id = self.require_user()?.id;
|
||||||
|
|
||||||
let users = match block_on(
|
let users =
|
||||||
self.db
|
db_or_debug_and_return!(self, database_actor::users::LoadInvitedUsers { user_id });
|
||||||
.send(database_actor::users::LoadInvitedUsers { user_id }),
|
|
||||||
) {
|
|
||||||
Ok(Ok(users)) => users,
|
|
||||||
_ => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(WsMsg::InvitedUsersLoaded(users)))
|
Ok(Some(WsMsg::InvitedUsersLoaded(users)))
|
||||||
}
|
}
|
||||||
@ -86,21 +71,14 @@ impl WsHandler<ProfileUpdate> for WebSocketActor {
|
|||||||
let user_id = self.require_user()?.id;
|
let user_id = self.require_user()?.id;
|
||||||
let ProfileUpdate { name, email } = msg;
|
let ProfileUpdate { name, email } = msg;
|
||||||
|
|
||||||
match block_on(self.db.send(database_actor::users::ProfileUpdate {
|
let _ = db_or_debug_and_return!(
|
||||||
|
self,
|
||||||
|
database_actor::users::ProfileUpdate {
|
||||||
user_id,
|
user_id,
|
||||||
name,
|
name,
|
||||||
email,
|
email,
|
||||||
})) {
|
|
||||||
Ok(Ok(_users)) => (),
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
);
|
||||||
error!("{}", e);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(WsMsg::ProfileUpdated))
|
Ok(Some(WsMsg::ProfileUpdated))
|
||||||
}
|
}
|
||||||
@ -120,23 +98,14 @@ impl WsHandler<RemoveInvitedUser> for WebSocketActor {
|
|||||||
project_id,
|
project_id,
|
||||||
..
|
..
|
||||||
} = self.require_user_project()?.clone();
|
} = self.require_user_project()?.clone();
|
||||||
match block_on(
|
let _ = db_or_debug_and_return!(
|
||||||
self.db
|
self,
|
||||||
.send(database_actor::user_projects::RemoveInvitedUser {
|
database_actor::user_projects::RemoveInvitedUser {
|
||||||
invited_id,
|
invited_id,
|
||||||
inviter_id,
|
inviter_id,
|
||||||
project_id,
|
project_id,
|
||||||
}),
|
|
||||||
) {
|
|
||||||
Ok(Ok(_users)) => Ok(Some(WsMsg::InvitedUserRemoveSuccess(invited_id))),
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
error!("{:?}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{}", e);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
Ok(Some(WsMsg::InvitedUserRemoveSuccess(invited_id)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,72 @@
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! query_db_or_print {
|
macro_rules! db_or_debug_and_return {
|
||||||
($s:expr,$msg:expr) => {
|
($s: ident, $msg: expr, $actor_err: expr, $mailbox_err: expr) => {
|
||||||
match block_on($s.db.send($msg)) {
|
$crate::actor_or_debug_and_return!($s, db, $msg, $actor_err, $mailbox_err)
|
||||||
|
};
|
||||||
|
($s: ident, $msg: expr) => {
|
||||||
|
$crate::actor_or_debug_and_return!($s, db, $msg)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! mail_or_debug_and_return {
|
||||||
|
($s: ident, $msg: expr, $actor_err: expr, $mailbox_err: expr) => {
|
||||||
|
$crate::actor_or_debug_and_return!($s, mail, $msg, $actor_err, $mailbox_err)
|
||||||
|
};
|
||||||
|
($s: ident, $msg: expr) => {
|
||||||
|
$crate::actor_or_debug_and_return!($s, mail, $msg)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! actor_or_debug_and_return {
|
||||||
|
($s: ident, $actor: ident, $msg: expr, $actor_err: expr, $mailbox_err: expr) => {
|
||||||
|
match block_on($s.$actor.send($msg)) {
|
||||||
Ok(Ok(r)) => r,
|
Ok(Ok(r)) => r,
|
||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
log::error!("{:?}", e);
|
log::error!("{:?}", e);
|
||||||
return Ok(None);
|
return $actor_err;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("{}", e);
|
log::error!("{:?}", e);
|
||||||
return Ok(None);
|
return $mailbox_err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($s: ident, $actor: ident, $msg: expr) => {
|
||||||
|
crate::actor_or_debug_and_return!($s, $actor, $msg, Ok(None), Ok(None))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! actor_or_debug_and_ignore {
|
||||||
|
($s: ident, $actor: ident, $msg: expr, $on_success: expr) => {
|
||||||
|
match block_on($s.$actor.send($msg)) {
|
||||||
|
Ok(Ok(r)) => {
|
||||||
|
$on_success(r);
|
||||||
|
}
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
log::error!("{:?}", e);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! actor_or_debug_and_fallback {
|
||||||
|
($s: ident, $actor: ident, $msg: expr, $actor_err: expr, $mailbox_err: expr) => {
|
||||||
|
match block_on($s.$actor.send($msg)) {
|
||||||
|
Ok(Ok(r)) => r,
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
log::error!("{:?}", e);
|
||||||
|
$actor_err
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{:?}", e);
|
||||||
|
$mailbox_err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -76,5 +76,11 @@ features = [
|
|||||||
"DragEvent",
|
"DragEvent",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[dependencies.derive_enum_primitive]
|
||||||
|
path = "../derive/derive_enum_primitive"
|
||||||
|
|
||||||
|
[dependencies.derive_enum_iter]
|
||||||
|
path = "../derive/derive_enum_iter"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wasm-bindgen-test = { version = "*" }
|
wasm-bindgen-test = { version = "*" }
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14.5px;
|
font-size: 14.5px;
|
||||||
padding: 0 12px;
|
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
border-color: var(--borderInputFocus);
|
border-color: var(--borderInputFocus);
|
||||||
@ -19,6 +18,16 @@
|
|||||||
> input[type=radio] {
|
> input[type=radio] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> label {
|
||||||
|
display: block;
|
||||||
|
padding: 0 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: var(--font-medium);
|
||||||
|
line-height: 2;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 14.5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .styledCheckboxChild.selected {
|
> .styledCheckboxChild.selected {
|
||||||
|
@ -109,6 +109,30 @@
|
|||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.editEpic {
|
||||||
|
padding: 35px 40px 40px;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transform {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 15px;
|
||||||
|
|
||||||
|
&.unavailable {
|
||||||
|
color: var(--textLight);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.available {
|
||||||
|
color: var(--textDark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.deleteEpic {
|
&.deleteEpic {
|
||||||
> section {
|
> section {
|
||||||
> .header {
|
> .header {
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
which rsass
|
RSASS_PATH=$(command -v rsass)
|
||||||
if [[ "$status" != "0" ]];
|
if [[ "${RSASS_PATH}" == "" ]];
|
||||||
then
|
then
|
||||||
cargo install rsass --features=commandline
|
cargo install rsass --features=commandline
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
WASM_PACK_PATH=$(command -v wasm-pack)
|
||||||
|
if [[ "${WASM_PACK_PATH}" == "" ]];
|
||||||
|
then
|
||||||
|
cargo install wasm-pack
|
||||||
|
fi
|
||||||
|
|
||||||
export PROJECT_ROOT=$(git rev-parse --show-toplevel)
|
export PROJECT_ROOT=$(git rev-parse --show-toplevel)
|
||||||
export CLIENT_ROOT=${PROJECT_ROOT}/jirs-client
|
export CLIENT_ROOT=${PROJECT_ROOT}/jirs-client
|
||||||
export HI_ROOT=${PROJECT_ROOT}/highlight/jirs-highlight
|
export HI_ROOT=${PROJECT_ROOT}/highlight/jirs-highlight
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
shared::{ToChild, ToNode},
|
shared::{IntoChild, ToNode},
|
||||||
FieldId, Msg,
|
FieldId, Msg,
|
||||||
},
|
},
|
||||||
jirs_data::TimeTracking,
|
jirs_data::TimeTracking,
|
||||||
@ -122,36 +122,51 @@ impl<'l> ToNode for ChildBuilder<'l> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StyledCheckbox<'l> {
|
pub struct StyledCheckbox<'l, Options>
|
||||||
|
where
|
||||||
|
Options: Iterator<Item = ChildBuilder<'l>>,
|
||||||
|
{
|
||||||
id: FieldId,
|
id: FieldId,
|
||||||
options: Vec<ChildBuilder<'l>>,
|
options: Option<Options>,
|
||||||
selected: u32,
|
selected: u32,
|
||||||
class_list: Vec<&'l str>,
|
class_list: Vec<&'l str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> ToNode for StyledCheckbox<'l> {
|
impl<'l, Options> ToNode for StyledCheckbox<'l, Options>
|
||||||
|
where
|
||||||
|
Options: Iterator<Item = ChildBuilder<'l>>,
|
||||||
|
{
|
||||||
fn into_node(self) -> Node<Msg> {
|
fn into_node(self) -> Node<Msg> {
|
||||||
render(self)
|
render(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledCheckbox<'l> {
|
impl<'l, Options> StyledCheckbox<'l, Options>
|
||||||
pub fn build() -> StyledCheckboxBuilder<'l> {
|
where
|
||||||
|
Options: Iterator<Item = ChildBuilder<'l>>,
|
||||||
|
{
|
||||||
|
pub fn build() -> StyledCheckboxBuilder<'l, Options> {
|
||||||
StyledCheckboxBuilder {
|
StyledCheckboxBuilder {
|
||||||
options: vec![],
|
options: None,
|
||||||
selected: 0,
|
selected: 0,
|
||||||
class_list: vec![],
|
class_list: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StyledCheckboxBuilder<'l> {
|
pub struct StyledCheckboxBuilder<'l, Options>
|
||||||
options: Vec<ChildBuilder<'l>>,
|
where
|
||||||
|
Options: Iterator<Item = ChildBuilder<'l>>,
|
||||||
|
{
|
||||||
|
options: Option<Options>,
|
||||||
selected: u32,
|
selected: u32,
|
||||||
class_list: Vec<&'l str>,
|
class_list: Vec<&'l str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledCheckboxBuilder<'l> {
|
impl<'l, Options> StyledCheckboxBuilder<'l, Options>
|
||||||
|
where
|
||||||
|
Options: Iterator<Item = ChildBuilder<'l>>,
|
||||||
|
{
|
||||||
pub fn state(mut self, state: &StyledCheckboxState) -> Self {
|
pub fn state(mut self, state: &StyledCheckboxState) -> Self {
|
||||||
self.selected = state.value;
|
self.selected = state.value;
|
||||||
self
|
self
|
||||||
@ -162,12 +177,12 @@ impl<'l> StyledCheckboxBuilder<'l> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn options(mut self, options: Vec<ChildBuilder<'l>>) -> Self {
|
pub fn options(mut self, options: Options) -> Self {
|
||||||
self.options = options;
|
self.options = Some(options);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self, field_id: FieldId) -> StyledCheckbox<'l> {
|
pub fn build(self, field_id: FieldId) -> StyledCheckbox<'l, Options> {
|
||||||
StyledCheckbox {
|
StyledCheckbox {
|
||||||
id: field_id,
|
id: field_id,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
@ -177,7 +192,10 @@ impl<'l> StyledCheckboxBuilder<'l> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(values: StyledCheckbox) -> Node<Msg> {
|
fn render<'l, Options>(values: StyledCheckbox<'l, Options>) -> Node<Msg>
|
||||||
|
where
|
||||||
|
Options: Iterator<Item = ChildBuilder<'l>>,
|
||||||
|
{
|
||||||
let StyledCheckbox {
|
let StyledCheckbox {
|
||||||
id,
|
id,
|
||||||
options,
|
options,
|
||||||
@ -185,10 +203,12 @@ fn render(values: StyledCheckbox) -> Node<Msg> {
|
|||||||
class_list,
|
class_list,
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
let opt: Vec<Node<Msg>> = options
|
let opt: Vec<Node<Msg>> = match options {
|
||||||
.into_iter()
|
Some(options) => options
|
||||||
.map(|child| child.with_id(id.clone()).try_select(selected).into_node())
|
.map(|child| child.with_id(id.clone()).try_select(selected).into_node())
|
||||||
.collect();
|
.collect(),
|
||||||
|
_ => vec![Node::Empty],
|
||||||
|
};
|
||||||
|
|
||||||
div![
|
div![
|
||||||
C!["styledCheckbox"],
|
C!["styledCheckbox"],
|
||||||
@ -197,10 +217,10 @@ fn render(values: StyledCheckbox) -> Node<Msg> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> ToChild<'l> for TimeTracking {
|
impl<'l> IntoChild<'l> for TimeTracking {
|
||||||
type Builder = ChildBuilder<'l>;
|
type Builder = ChildBuilder<'l>;
|
||||||
|
|
||||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
fn into_child(self) -> Self::Builder {
|
||||||
Self::Builder::default()
|
Self::Builder::default()
|
||||||
.label(match self {
|
.label(match self {
|
||||||
TimeTracking::Untracked => "No tracking",
|
TimeTracking::Untracked => "No tracking",
|
||||||
@ -212,11 +232,11 @@ impl<'l> ToChild<'l> for TimeTracking {
|
|||||||
TimeTracking::Fibonacci => "fibonacci",
|
TimeTracking::Fibonacci => "fibonacci",
|
||||||
TimeTracking::Hourly => "hourly",
|
TimeTracking::Hourly => "hourly",
|
||||||
})
|
})
|
||||||
.value((*self).into())
|
|
||||||
.add_class(match self {
|
.add_class(match self {
|
||||||
TimeTracking::Untracked => "untracked",
|
TimeTracking::Untracked => "untracked",
|
||||||
TimeTracking::Fibonacci => "fibonacci",
|
TimeTracking::Fibonacci => "fibonacci",
|
||||||
TimeTracking::Hourly => "hourly",
|
TimeTracking::Hourly => "hourly",
|
||||||
})
|
})
|
||||||
|
.value((self).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ pub enum Msg {
|
|||||||
AvatarUpdateFetched(String),
|
AvatarUpdateFetched(String),
|
||||||
|
|
||||||
// modals
|
// modals
|
||||||
ModalOpened(Box<ModalType>),
|
ModalOpened(ModalType),
|
||||||
ModalDropped,
|
ModalDropped,
|
||||||
ModalChanged(FieldChange),
|
ModalChanged(FieldChange),
|
||||||
|
|
||||||
@ -280,11 +280,11 @@ fn resolve_page(url: Url) -> Option<Page> {
|
|||||||
|
|
||||||
let page = match url.path()[0].as_ref() {
|
let page = match url.path()[0].as_ref() {
|
||||||
"board" => Page::Project,
|
"board" => Page::Project,
|
||||||
|
"profile" => Page::Profile,
|
||||||
"issues" => match url.path().get(1).as_ref().map(|s| s.parse::<i32>()) {
|
"issues" => match url.path().get(1).as_ref().map(|s| s.parse::<i32>()) {
|
||||||
Some(Ok(id)) => Page::EditIssue(id),
|
Some(Ok(id)) => Page::EditIssue(id),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
},
|
},
|
||||||
"profile" => Page::Profile,
|
|
||||||
"add-issue" => Page::AddIssue,
|
"add-issue" => Page::AddIssue,
|
||||||
"project-settings" => Page::ProjectSettings,
|
"project-settings" => Page::ProjectSettings,
|
||||||
"login" => Page::SignIn,
|
"login" => Page::SignIn,
|
||||||
|
4
jirs-client/src/modals/comments_delete/mod.rs
Normal file
4
jirs-client/src/modals/comments_delete/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub use {view::*, model::*};
|
||||||
|
|
||||||
|
mod view;
|
||||||
|
mod model;
|
12
jirs-client/src/modals/comments_delete/model.rs
Normal file
12
jirs-client/src/modals/comments_delete/model.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use jirs_data::CommentId;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Model {
|
||||||
|
pub comment_id: CommentId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model {
|
||||||
|
pub fn new(comment_id: CommentId) -> Self {
|
||||||
|
Self { comment_id }
|
||||||
|
}
|
||||||
|
}
|
16
jirs-client/src/modals/comments_delete/view.rs
Normal file
16
jirs-client/src/modals/comments_delete/view.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use {
|
||||||
|
crate::{model, shared::ToNode, styled_confirm_modal::StyledConfirmModal, Msg},
|
||||||
|
jirs_data::CommentId,
|
||||||
|
seed::prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn view(_model: &model::Model, modal: &super::Model) -> Node<Msg> {
|
||||||
|
let comment_id: CommentId = modal.comment_id;
|
||||||
|
StyledConfirmModal::build()
|
||||||
|
.title("Are you sure you want to delete this comment?")
|
||||||
|
.message("Once you delete, it's gone for good.")
|
||||||
|
.confirm_text("Delete comment")
|
||||||
|
.on_confirm(mouse_ev(Ev::Click, move |_| Msg::DeleteComment(comment_id)))
|
||||||
|
.build()
|
||||||
|
.into_node()
|
||||||
|
}
|
@ -10,7 +10,7 @@ pub struct Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
pub fn new(epic_id: i32, model: &mut model::Model) -> Self {
|
pub fn new(epic_id: i32, model: &model::Model) -> Self {
|
||||||
let related_issues = model.epic_issue_ids(epic_id);
|
let related_issues = model.epic_issue_ids(epic_id);
|
||||||
Self {
|
Self {
|
||||||
epic_id,
|
epic_id,
|
||||||
|
@ -1,30 +1,20 @@
|
|||||||
use {
|
use {
|
||||||
crate::{shared::go_to_board, ws::send_ws_msg, ModalType, Msg, OperationKind, ResourceKind},
|
crate::{ws::send_ws_msg, Msg, OperationKind, ResourceKind},
|
||||||
jirs_data::WsMsg,
|
jirs_data::WsMsg,
|
||||||
seed::prelude::*,
|
seed::prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
|
||||||
let modal = match model.modals.iter_mut().find_map(|modal| {
|
let modal = match &mut model.modals_mut().delete_epic {
|
||||||
if let ModalType::DeleteEpic(modal) = modal {
|
|
||||||
Some(modal)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Some(modal) => modal,
|
Some(modal) => modal,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
Msg::ModalDropped => {
|
|
||||||
go_to_board(orders);
|
|
||||||
}
|
|
||||||
Msg::DeleteEpic => {
|
Msg::DeleteEpic => {
|
||||||
send_ws_msg(WsMsg::EpicDelete(modal.epic_id), model.ws.as_ref(), orders);
|
send_ws_msg(WsMsg::EpicDelete(modal.epic_id), model.ws.as_ref(), orders);
|
||||||
}
|
}
|
||||||
Msg::ResourceChanged(ResourceKind::Epic, OperationKind::SingleRemoved, Some(_)) => {
|
Msg::ResourceChanged(ResourceKind::Epic, OperationKind::SingleRemoved, Some(_)) => {
|
||||||
go_to_board(orders);
|
|
||||||
orders.skip().send_msg(Msg::ModalDropped);
|
orders.skip().send_msg(Msg::ModalDropped);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
use crate::FieldId;
|
|
||||||
use jirs_data::EpicFieldId;
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
components::{styled_input::*, styled_select::StyledSelectState},
|
components::{styled_checkbox::StyledCheckboxState, styled_input::*},
|
||||||
model,
|
model, FieldId, Msg,
|
||||||
},
|
},
|
||||||
jirs_data::{EpicId, IssueId},
|
jirs_data::*,
|
||||||
|
seed::prelude::Orders,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
#[derive(Debug)]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
pub epic_id: EpicId,
|
pub epic_id: EpicId,
|
||||||
pub related_issues: Vec<IssueId>,
|
pub related_issues: Vec<IssueId>,
|
||||||
pub name: StyledInputState,
|
pub name: StyledInputState,
|
||||||
pub transform_into: StyledSelectState,
|
pub transform_into: StyledCheckboxState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
@ -23,6 +22,7 @@ impl Model {
|
|||||||
.get(&epic_id)
|
.get(&epic_id)
|
||||||
.map(|epic| epic.name.as_str())
|
.map(|epic| epic.name.as_str())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let related_issues = model
|
let related_issues = model
|
||||||
.issues()
|
.issues()
|
||||||
.iter()
|
.iter()
|
||||||
@ -38,10 +38,15 @@ impl Model {
|
|||||||
epic_id,
|
epic_id,
|
||||||
related_issues,
|
related_issues,
|
||||||
name: StyledInputState::new(FieldId::EditEpic(EpicFieldId::Name), name),
|
name: StyledInputState::new(FieldId::EditEpic(EpicFieldId::Name), name),
|
||||||
transform_into: StyledSelectState::new(
|
transform_into: StyledCheckboxState::new(
|
||||||
FieldId::EditEpic(EpicFieldId::StartsAt),
|
FieldId::EditEpic(EpicFieldId::TransformInto),
|
||||||
vec![],
|
0,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, msg: &Msg, _orders: &mut impl Orders<Msg>) {
|
||||||
|
self.name.update(msg);
|
||||||
|
self.transform_into.update(msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,45 @@
|
|||||||
use {crate::Msg, seed::prelude::*};
|
use {
|
||||||
|
crate::{send_ws_msg, FieldId, Msg, OperationKind, ResourceKind},
|
||||||
|
jirs_data::{EpicFieldId, WsMsg},
|
||||||
|
seed::prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn update(_msg: &Msg, model: &mut crate::model::Model, _orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
|
||||||
let _modal = crate::match_modal_mut!(model, DeleteEpic);
|
let modal = match &mut model.modals.edit_epic {
|
||||||
|
Some(modal) => modal,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
modal.update(msg, orders);
|
||||||
|
match msg {
|
||||||
|
Msg::ResourceChanged(
|
||||||
|
ResourceKind::Epic,
|
||||||
|
OperationKind::SingleLoaded | OperationKind::SingleModified,
|
||||||
|
Some(id),
|
||||||
|
) => {
|
||||||
|
let name = model
|
||||||
|
.epics_by_id
|
||||||
|
.get(id)
|
||||||
|
.map(|epic| epic.name.as_str())
|
||||||
|
.unwrap_or_default();
|
||||||
|
modal.name.value = name.to_string();
|
||||||
|
}
|
||||||
|
Msg::ResourceChanged(ResourceKind::Epic, OperationKind::ListLoaded, None) => {
|
||||||
|
let epic_id = modal.epic_id;
|
||||||
|
let name = model
|
||||||
|
.epics_by_id
|
||||||
|
.get(&epic_id)
|
||||||
|
.map(|epic| epic.name.as_str())
|
||||||
|
.unwrap_or_default();
|
||||||
|
modal.name.value = name.to_string();
|
||||||
|
}
|
||||||
|
Msg::StrInputChanged(FieldId::EditEpic(EpicFieldId::Name), s) => {
|
||||||
|
let epic_id = modal.epic_id;
|
||||||
|
send_ws_msg(
|
||||||
|
WsMsg::EpicUpdate(epic_id, s.to_string()),
|
||||||
|
model.ws.as_ref(),
|
||||||
|
orders,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,56 @@
|
|||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
components::{styled_input::*, styled_modal::*},
|
components::{
|
||||||
|
styled_button::*, styled_checkbox::*, styled_icon::Icon, styled_input::*,
|
||||||
|
styled_modal::*,
|
||||||
|
},
|
||||||
modals::epics_edit::Model,
|
modals::epics_edit::Model,
|
||||||
model,
|
model,
|
||||||
shared::ToNode,
|
shared::{IntoChild, ToNode},
|
||||||
FieldId, Msg,
|
FieldId, Msg,
|
||||||
},
|
},
|
||||||
jirs_data::EpicFieldId,
|
jirs_data::{EpicFieldId, IssueType},
|
||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub struct IssueTypeWrapper(IssueType);
|
||||||
|
|
||||||
|
impl<'l> IntoChild<'l> for IssueTypeWrapper {
|
||||||
|
type Builder = ChildBuilder<'l>;
|
||||||
|
|
||||||
|
fn into_child(self) -> Self::Builder {
|
||||||
|
Self::Builder::default()
|
||||||
|
.label(self.0.to_label())
|
||||||
|
.name(self.0.to_str())
|
||||||
|
.value(self.0.into())
|
||||||
|
.add_class(self.0.to_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn view(_model: &model::Model, modal: &Model) -> Node<Msg> {
|
pub fn view(_model: &model::Model, modal: &Model) -> Node<Msg> {
|
||||||
let transform = if modal.related_issues.is_empty() {
|
let transform = if modal.related_issues.is_empty() {
|
||||||
Node::Empty
|
transform_into_available(modal)
|
||||||
} else {
|
} else {
|
||||||
div![]
|
transform_into_unavailable(modal)
|
||||||
};
|
};
|
||||||
|
let close = StyledButton::build()
|
||||||
|
.on_click(mouse_ev("click", |ev| {
|
||||||
|
ev.stop_propagation();
|
||||||
|
ev.prevent_default();
|
||||||
|
Msg::ModalDropped
|
||||||
|
}))
|
||||||
|
.empty()
|
||||||
|
.icon(Icon::Close)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
StyledModal::build()
|
StyledModal::build()
|
||||||
.center()
|
.center()
|
||||||
.child(h1!["Edit epic"])
|
.width(600)
|
||||||
|
.add_class("editEpic")
|
||||||
|
.child(div![C!["header"], h1!["Edit epic"], close])
|
||||||
.child(
|
.child(
|
||||||
StyledInput::build()
|
StyledInput::build()
|
||||||
|
.state(&modal.name)
|
||||||
.build(FieldId::EditEpic(EpicFieldId::Name))
|
.build(FieldId::EditEpic(EpicFieldId::Name))
|
||||||
.into_node(),
|
.into_node(),
|
||||||
)
|
)
|
||||||
@ -28,3 +58,32 @@ pub fn view(_model: &model::Model, modal: &Model) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node()
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn transform_into_available(modal: &super::Model) -> Node<Msg> {
|
||||||
|
let types = StyledCheckbox::build()
|
||||||
|
.options(
|
||||||
|
IssueType::default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|ty| IssueTypeWrapper(ty).into_child()),
|
||||||
|
)
|
||||||
|
.state(&modal.transform_into)
|
||||||
|
.build(FieldId::EditEpic(EpicFieldId::TransformInto))
|
||||||
|
.into_node();
|
||||||
|
let execute = StyledButton::build().text("Transform").build().into_node();
|
||||||
|
div![C!["transform available"], div![types], div![execute]]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_into_unavailable(modal: &super::Model) -> Node<Msg> {
|
||||||
|
let (n, s) = match modal.related_issues.len() {
|
||||||
|
1 => (1.to_string(), "issue"),
|
||||||
|
n => (n.to_string(), "issues"),
|
||||||
|
};
|
||||||
|
div![
|
||||||
|
C!["transform unavailable"],
|
||||||
|
span![
|
||||||
|
C!["info"],
|
||||||
|
"This epic have related issues so you can't change it type."
|
||||||
|
],
|
||||||
|
span![C!["count"], format!("Epic have {} {}", n, s)]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
@ -1,19 +1,11 @@
|
|||||||
use {
|
use {
|
||||||
crate::{
|
crate::{model::Model, Msg, OperationKind, ResourceKind},
|
||||||
modals::issue_statuses_delete::Model as DeleteIssueStatusModal,
|
|
||||||
model::{ModalType, Model},
|
|
||||||
Msg, OperationKind, ResourceKind,
|
|
||||||
},
|
|
||||||
jirs_data::WsMsg,
|
jirs_data::WsMsg,
|
||||||
seed::prelude::*,
|
seed::prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
let _modal: &mut Box<DeleteIssueStatusModal> =
|
let _modal = match &mut model.modals_mut().delete_issue_status_modal {
|
||||||
match model.modals.iter_mut().find_map(|modal| match modal {
|
|
||||||
ModalType::DeleteIssueStatusModal(modal) => Some(modal),
|
|
||||||
_ => None,
|
|
||||||
}) {
|
|
||||||
Some(m) => m,
|
Some(m) => m,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
@ -4,14 +4,16 @@ use {
|
|||||||
styled_date_time_input::*, styled_input::*, styled_select::*, styled_select_child::*,
|
styled_date_time_input::*, styled_input::*, styled_select::*, styled_select_child::*,
|
||||||
},
|
},
|
||||||
model::IssueModal,
|
model::IssueModal,
|
||||||
shared::{ToChild, ToNode},
|
shared::{IntoChild, ToNode},
|
||||||
FieldId, Msg,
|
FieldId, Msg,
|
||||||
},
|
},
|
||||||
|
derive_enum_iter::EnumIter,
|
||||||
|
derive_enum_primitive::EnumPrimitive,
|
||||||
jirs_data::{IssueFieldId, IssuePriority},
|
jirs_data::{IssueFieldId, IssuePriority},
|
||||||
seed::prelude::*,
|
seed::prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, EnumPrimitive, EnumIter)]
|
||||||
pub enum Type {
|
pub enum Type {
|
||||||
Task,
|
Task,
|
||||||
Bug,
|
Bug,
|
||||||
@ -19,24 +21,13 @@ pub enum Type {
|
|||||||
Epic,
|
Epic,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u32> for Type {
|
impl Default for Type {
|
||||||
fn from(n: u32) -> Self {
|
fn default() -> Self {
|
||||||
match n {
|
Self::Task
|
||||||
0 => Type::Task,
|
|
||||||
1 => Type::Bug,
|
|
||||||
2 => Type::Story,
|
|
||||||
3 => Type::Epic,
|
|
||||||
_ => Type::Task,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Type {
|
impl Type {
|
||||||
pub(crate) fn ordered<'l>() -> &'l [Type] {
|
|
||||||
use Type::*;
|
|
||||||
&[Task, Bug, Story, Epic]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn submit_label(&self) -> &str {
|
pub(crate) fn submit_label(&self) -> &str {
|
||||||
use Type::*;
|
use Type::*;
|
||||||
match self {
|
match self {
|
||||||
@ -62,22 +53,17 @@ impl Type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> ToChild<'l> for Type {
|
impl<'l> IntoChild<'l> for Type {
|
||||||
type Builder = StyledSelectChildBuilder<'l>;
|
type Builder = StyledSelectChildBuilder<'l>;
|
||||||
|
|
||||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
fn into_child(self) -> Self::Builder {
|
||||||
let name = match self {
|
let name = match self {
|
||||||
Type::Task => "Task",
|
Type::Task => "Task",
|
||||||
Type::Bug => "Bug",
|
Type::Bug => "Bug",
|
||||||
Type::Story => "Story",
|
Type::Story => "Story",
|
||||||
Type::Epic => "Epic",
|
Type::Epic => "Epic",
|
||||||
};
|
};
|
||||||
let value = match self {
|
let value: u32 = self.into();
|
||||||
Type::Task => 0,
|
|
||||||
Type::Bug => 1,
|
|
||||||
Type::Story => 2,
|
|
||||||
Type::Epic => 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
let type_icon = {
|
let type_icon = {
|
||||||
use crate::components::styled_icon::*;
|
use crate::components::styled_icon::*;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
components::styled_select::StyledSelectChanged,
|
components::styled_select::StyledSelectChanged, model::IssueModal, ws::send_ws_msg,
|
||||||
model::{IssueModal, ModalType},
|
|
||||||
ws::send_ws_msg,
|
|
||||||
FieldId, Msg, OperationKind, ResourceKind,
|
FieldId, Msg, OperationKind, ResourceKind,
|
||||||
},
|
},
|
||||||
jirs_data::{IssueFieldId, UserId, WsMsg},
|
jirs_data::{IssueFieldId, UserId, WsMsg},
|
||||||
@ -10,12 +8,11 @@ use {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
|
||||||
let modal = model
|
let user_id = model.user_id().unwrap_or_default();
|
||||||
.modals
|
let project_id = model.project_id().unwrap_or_default();
|
||||||
.iter_mut()
|
|
||||||
.find(|modal| matches!(modal, ModalType::AddIssue(..)));
|
let modal = match &mut model.modals_mut().add_issue {
|
||||||
let modal = match modal {
|
Some(modal) => modal,
|
||||||
Some(ModalType::AddIssue(modal)) => modal,
|
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,8 +27,6 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
Msg::AddIssue => {
|
Msg::AddIssue => {
|
||||||
let user_id = model.user.as_ref().map(|u| u.id).unwrap_or_default();
|
|
||||||
let project_id = model.project.as_ref().map(|p| p.id).unwrap_or_default();
|
|
||||||
let type_value = modal.type_state.values.get(0).cloned().unwrap_or_default();
|
let type_value = modal.type_state.values.get(0).cloned().unwrap_or_default();
|
||||||
match type_value {
|
match type_value {
|
||||||
0 | 1 | 2 => {
|
0 | 1 | 2 => {
|
||||||
|
@ -24,7 +24,7 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.values
|
.values
|
||||||
.get(0)
|
.get(0)
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(Type::from)
|
.map(Into::into)
|
||||||
.unwrap_or_else(|| Type::Task);
|
.unwrap_or_else(|| Type::Task);
|
||||||
|
|
||||||
let issue_type_field = issue_type_field(modal);
|
let issue_type_field = issue_type_field(modal);
|
||||||
@ -127,11 +127,18 @@ fn issue_type_field(modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.text_filter(modal.type_state.text_filter.as_str())
|
.text_filter(modal.type_state.text_filter.as_str())
|
||||||
.opened(modal.type_state.opened)
|
.opened(modal.type_state.opened)
|
||||||
.valid(true)
|
.valid(true)
|
||||||
.options(Type::ordered().iter().map(|t| t.to_child().name("type")))
|
.options(Type::Task.into_iter().map(|t| t.into_child().name("type")))
|
||||||
.selected(vec![Type::from(
|
.selected(vec![{
|
||||||
modal.type_state.values.get(0).cloned().unwrap_or_default(),
|
let v: Type = modal
|
||||||
)
|
.type_state
|
||||||
.to_child()
|
.values
|
||||||
|
.get(0)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into();
|
||||||
|
v
|
||||||
|
}
|
||||||
|
.into_child()
|
||||||
.name("type")])
|
.name("type")])
|
||||||
.build(FieldId::AddIssueModal(IssueFieldId::Type))
|
.build(FieldId::AddIssueModal(IssueFieldId::Type))
|
||||||
.into_node();
|
.into_node();
|
||||||
|
@ -1 +1,6 @@
|
|||||||
|
use jirs_data::IssueId;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Model {
|
||||||
|
pub issue_id: IssueId,
|
||||||
|
}
|
||||||
|
@ -1,24 +1,12 @@
|
|||||||
use {
|
use {
|
||||||
crate::{
|
crate::{components::styled_confirm_modal::StyledConfirmModal, model, shared::ToNode, Msg},
|
||||||
components::styled_confirm_modal::StyledConfirmModal, model, model::ModalType,
|
seed::prelude::*,
|
||||||
shared::ToNode, Msg,
|
|
||||||
},
|
|
||||||
seed::{prelude::*, *},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn view(model: &model::Model) -> Node<Msg> {
|
pub fn view(model: &model::Model) -> Node<Msg> {
|
||||||
let opt_id = model
|
let issue_id = match &model.modals().delete_issue_confirm {
|
||||||
.modals
|
Some(modal) => modal.issue_id,
|
||||||
.iter()
|
_ => return Node::Empty,
|
||||||
.filter_map(|modal| match modal {
|
|
||||||
ModalType::EditIssue(issue_id, _) => Some(issue_id),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.find(|id| id.eq(id));
|
|
||||||
|
|
||||||
let issue_id = match opt_id {
|
|
||||||
Some(id) => *id,
|
|
||||||
_ => return empty![],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let handle_issue_delete = mouse_ev(Ev::Click, move |_| Msg::DeleteIssue(issue_id));
|
let handle_issue_delete = mouse_ev(Ev::Click, move |_| Msg::DeleteIssue(issue_id));
|
||||||
|
@ -9,13 +9,13 @@ use {
|
|||||||
model::{CommentForm, IssueModal},
|
model::{CommentForm, IssueModal},
|
||||||
EditIssueModalSection, FieldId, Msg,
|
EditIssueModalSection, FieldId, Msg,
|
||||||
},
|
},
|
||||||
jirs_data::{EpicId, Issue, IssueFieldId, TimeTracking, UpdateIssuePayload},
|
jirs_data::{Issue, IssueFieldId, IssueId, TimeTracking, UpdateIssuePayload},
|
||||||
seed::prelude::*,
|
seed::prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
pub id: EpicId,
|
pub id: IssueId,
|
||||||
pub link_copied: bool,
|
pub link_copied: bool,
|
||||||
pub payload: UpdateIssuePayload,
|
pub payload: UpdateIssuePayload,
|
||||||
pub top_type_state: StyledSelectState,
|
pub top_type_state: StyledSelectState,
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
components::styled_select::StyledSelectChanged,
|
components::styled_select::StyledSelectChanged,
|
||||||
modals::issues_edit::Model as EditIssueModal,
|
model::{IssueModal, Model},
|
||||||
model::{IssueModal, ModalType, Model},
|
|
||||||
ws::send_ws_msg,
|
ws::send_ws_msg,
|
||||||
EditIssueModalSection, FieldChange, FieldId, Msg, OperationKind, ResourceKind,
|
EditIssueModalSection, FieldChange, FieldId, Msg, OperationKind, ResourceKind,
|
||||||
},
|
},
|
||||||
@ -11,18 +10,22 @@ use {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
let modal: &mut EditIssueModal = match model.modals.get_mut(0) {
|
let modal = match &mut model.modals.edit_issue {
|
||||||
Some(ModalType::EditIssue(_issue_id, modal)) => modal,
|
Some(modal) => modal,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
modal.update_states(msg, orders);
|
modal.update_states(msg, orders);
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
Msg::ResourceChanged(ResourceKind::Issue, OperationKind::SingleModified, Some(id)) => {
|
Msg::ResourceChanged(ResourceKind::Issue, OperationKind::SingleModified, Some(id)) => {
|
||||||
if let Some(issue) = model.issues_by_id.get(id) {
|
let m = model.issues_by_id.get(id).cloned();
|
||||||
modal.payload = issue.clone().into();
|
if let Some(issue) = m {
|
||||||
modal.description_state.initial_text =
|
modal.description_state.initial_text = issue
|
||||||
issue.description_text.clone().unwrap_or_default();
|
.description_text
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string();
|
||||||
|
modal.payload = issue.into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ use {
|
|||||||
components::{
|
components::{
|
||||||
styled_avatar::StyledAvatar, styled_button::StyledButton, styled_editor::StyledEditor,
|
styled_avatar::StyledAvatar, styled_button::StyledButton, styled_editor::StyledEditor,
|
||||||
styled_field::StyledField, styled_icon::Icon, styled_input::StyledInput,
|
styled_field::StyledField, styled_icon::Icon, styled_input::StyledInput,
|
||||||
styled_select::StyledSelect,
|
styled_modal::*, styled_select::StyledSelect,
|
||||||
},
|
},
|
||||||
modals::{
|
modals::{
|
||||||
epic_field, issues_edit::Model as EditIssueModal, time_tracking::time_tracking_field,
|
epic_field, issues_edit::Model as EditIssueModal, time_tracking::time_tracking_field,
|
||||||
@ -20,6 +20,21 @@ use {
|
|||||||
mod comments;
|
mod comments;
|
||||||
|
|
||||||
pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||||
|
let issue_id = modal.id;
|
||||||
|
if let Some(_issue) = model.issues_by_id.get(&issue_id) {
|
||||||
|
let details = details(model, modal);
|
||||||
|
StyledModal::build()
|
||||||
|
.variant(crate::components::styled_modal::Variant::Center)
|
||||||
|
.width(1040)
|
||||||
|
.child(details)
|
||||||
|
.build()
|
||||||
|
.into_node()
|
||||||
|
} else {
|
||||||
|
Node::Empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn details(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||||
div![
|
div![
|
||||||
C!["issueDetails"],
|
C!["issueDetails"],
|
||||||
modal_header(model, modal),
|
modal_header(model, modal),
|
||||||
@ -72,12 +87,10 @@ fn modal_header(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
let close_handler = mouse_ev(Ev::Click, |ev| {
|
let close_handler = mouse_ev(Ev::Click, |ev| {
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
seed::Url::new().add_path_part("board").go_and_push();
|
|
||||||
|
|
||||||
Msg::ModalDropped
|
Msg::ModalDropped
|
||||||
});
|
});
|
||||||
let delete_confirmation_handler = mouse_ev(Ev::Click, move |_| {
|
let delete_confirmation_handler = mouse_ev(Ev::Click, move |_| {
|
||||||
Msg::ModalOpened(Box::new(ModalType::DeleteIssueConfirm(issue_id)))
|
Msg::ModalOpened(ModalType::DeleteIssueConfirm(Some(issue_id)))
|
||||||
});
|
});
|
||||||
|
|
||||||
let copy_button = StyledButton::build()
|
let copy_button = StyledButton::build()
|
||||||
|
@ -66,7 +66,7 @@ pub fn comment(model: &Model, modal: &EditIssueModal, comment: &Comment) -> Opti
|
|||||||
let comment_id = comment.id;
|
let comment_id = comment.id;
|
||||||
let delete_comment_handler = mouse_ev(Ev::Click, move |ev| {
|
let delete_comment_handler = mouse_ev(Ev::Click, move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
Msg::ModalOpened(Box::new(ModalType::DeleteCommentConfirm(comment_id)))
|
Msg::ModalOpened(ModalType::DeleteCommentConfirm(Some(comment_id)))
|
||||||
});
|
});
|
||||||
let edit_button = StyledButton::build()
|
let edit_button = StyledButton::build()
|
||||||
.add_class("editButton")
|
.add_class("editButton")
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
pub use {epic_field::*, update::*, view::*};
|
pub use {epic_field::*, update::*, view::*};
|
||||||
|
|
||||||
|
pub mod comments_delete;
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub mod debug;
|
pub mod debug;
|
||||||
pub mod epics_delete;
|
pub mod epics_delete;
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
use jirs_data::IssueId;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Model {
|
||||||
|
pub issue_id: IssueId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model {
|
||||||
|
pub fn new(issue_id: IssueId) -> Self {
|
||||||
|
Self { issue_id }
|
||||||
|
}
|
||||||
|
}
|
@ -7,14 +7,14 @@ use {
|
|||||||
styled_modal::StyledModal,
|
styled_modal::StyledModal,
|
||||||
styled_select::{StyledSelect, StyledSelectState},
|
styled_select::{StyledSelect, StyledSelectState},
|
||||||
},
|
},
|
||||||
model::{ModalType, Model},
|
model::Model,
|
||||||
shared::{
|
shared::{
|
||||||
tracking_widget::{fibonacci_values, tracking_widget},
|
tracking_widget::{fibonacci_values, tracking_widget},
|
||||||
ToChild, ToNode,
|
ToChild, ToNode,
|
||||||
},
|
},
|
||||||
EditIssueModalSection, FieldId, Msg,
|
EditIssueModalSection, FieldId, Msg,
|
||||||
},
|
},
|
||||||
jirs_data::{EpicId, IssueFieldId, TimeTracking},
|
jirs_data::{IssueFieldId, IssueId, TimeTracking},
|
||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -27,20 +27,22 @@ pub fn value_for_time_tracking(v: &Option<i32>, time_tracking_type: &TimeTrackin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(model: &Model, issue_id: EpicId) -> Node<Msg> {
|
pub fn view(model: &Model, modal: &super::Model) -> Node<Msg> {
|
||||||
|
let issue_id: IssueId = modal.issue_id;
|
||||||
if model.issues_by_id.get(&issue_id).is_none() {
|
if model.issues_by_id.get(&issue_id).is_none() {
|
||||||
return Node::Empty;
|
return Node::Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
let edit_issue_modal = match model.modals.get(0) {
|
let edit_issue_modal = match &model.modals().edit_issue {
|
||||||
Some(ModalType::EditIssue(_, modal)) => modal,
|
Some(modal) => modal,
|
||||||
_ => return empty![],
|
_ => return Node::Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
let time_tracking_type = model
|
let time_tracking_type = model
|
||||||
.project
|
.project
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| p.time_tracking)
|
.map(|p| p.time_tracking)
|
||||||
.unwrap_or_else(|| TimeTracking::Untracked);
|
.unwrap_or(TimeTracking::Untracked);
|
||||||
|
|
||||||
let modal_title = div![C!["modalTitle"], "Time tracking"];
|
let modal_title = div![C!["modalTitle"], "Time tracking"];
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use jirs_data::{CommentId, IssueStatusId};
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
model::{ModalType, Model, Page},
|
model::{ModalType, Model, Page},
|
||||||
@ -11,47 +12,37 @@ use {
|
|||||||
|
|
||||||
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
match msg {
|
match msg {
|
||||||
Msg::ModalDropped => match model.modals.pop() {
|
Msg::ModalDropped if !model.modal_stack().is_empty() => {
|
||||||
Some(ModalType::EditIssue(..)) | Some(ModalType::AddIssue(..)) => {
|
drop_modal(model, orders);
|
||||||
go_to_board(orders);
|
|
||||||
}
|
}
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
|
|
||||||
Msg::ModalChanged(FieldChange::LinkCopied(FieldId::CopyButtonLabel, true)) => {
|
Msg::ModalChanged(FieldChange::LinkCopied(FieldId::CopyButtonLabel, true)) => {
|
||||||
for modal in model.modals.iter_mut() {
|
if let Some(edit) = &mut model.modals_mut().edit_issue {
|
||||||
if let ModalType::EditIssue(_, edit) = modal {
|
|
||||||
edit.link_copied = true;
|
edit.link_copied = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Msg::ModalOpened(modal_type) => {
|
Msg::ModalOpened(modal_type) => {
|
||||||
model.modals.push(modal_type.as_ref().clone());
|
push_modal(modal_type, model, orders);
|
||||||
}
|
}
|
||||||
|
|
||||||
Msg::ResourceChanged(ResourceKind::Issue, OperationKind::ListLoaded, _) => {
|
Msg::ResourceChanged(ResourceKind::Issue, OperationKind::ListLoaded, _) => {
|
||||||
match model.page {
|
match model.page {
|
||||||
Page::EditIssue(issue_id) if model.modals.is_empty() => {
|
Page::EditIssue(issue_id) => push_edit_issue_modal(issue_id, model, orders),
|
||||||
push_edit_modal(issue_id, model, orders)
|
Page::AddIssue => push_add_issue_modal(model, orders),
|
||||||
}
|
Page::DeleteEpic(id) => push_delete_epic_modal(id, model, orders),
|
||||||
Page::AddIssue if model.modals.is_empty() => push_add_modal(model, orders),
|
Page::EditEpic(id) => push_edit_epic_modal(id, model, orders),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Msg::ChangePage(Page::EditIssue(issue_id)) => push_edit_modal(*issue_id, model, orders),
|
Msg::ChangePage(Page::EditIssue(id)) => push_edit_issue_modal(*id, model, orders),
|
||||||
|
Msg::ChangePage(Page::AddIssue) => push_add_issue_modal(model, orders),
|
||||||
Msg::ChangePage(Page::AddIssue) => push_add_modal(model, orders),
|
Msg::ChangePage(Page::DeleteEpic(id)) => push_delete_epic_modal(*id, model, orders),
|
||||||
Msg::ChangePage(Page::DeleteEpic(issue_id)) => {
|
Msg::ChangePage(Page::EditEpic(id)) => push_edit_epic_modal(*id, model, orders),
|
||||||
push_delete_epic_modal(*issue_id, model, orders)
|
|
||||||
}
|
|
||||||
Msg::ChangePage(Page::EditEpic(issue_id)) => push_edit_epic_modal(*issue_id, model, orders),
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
Msg::GlobalKeyDown { key, .. } if key.eq("#") => {
|
Msg::GlobalKeyDown { key, .. } if key.eq("#") => push_debug_modal(model),
|
||||||
model.modals.push(ModalType::DebugModal);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
Msg::GlobalKeyDown { key, .. } if key.eq(">") => {
|
Msg::GlobalKeyDown { key, .. } if key.eq(">") => {
|
||||||
@ -75,49 +66,223 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_add_modal(model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
// MODALS
|
||||||
use crate::modals::issues_create::Model;
|
|
||||||
model.modals.push(ModalType::AddIssue(Box::new(Model {
|
fn push_modal(modal_type: &ModalType, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
project_id: model.project.as_ref().map(|p| p.id),
|
match modal_type {
|
||||||
..Model::default()
|
ModalType::AddIssue(_) => push_add_issue_modal(model, orders),
|
||||||
})));
|
ModalType::EditIssue(id) => {
|
||||||
|
if let Some(id) = id {
|
||||||
|
push_edit_issue_modal(*id, model, orders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModalType::DeleteIssueConfirm(id) => {
|
||||||
|
if let Some(id) = id {
|
||||||
|
push_delete_issue_modal(*id, model, orders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModalType::DeleteEpic(id) => {
|
||||||
|
if let Some(id) = id {
|
||||||
|
push_delete_epic_modal(*id, model, orders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModalType::EditEpic(id) => {
|
||||||
|
if let Some(id) = id {
|
||||||
|
push_edit_epic_modal(*id, model, orders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModalType::DeleteCommentConfirm(id) => {
|
||||||
|
if let Some(id) = id {
|
||||||
|
push_delete_comment_modal(*id, model, orders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModalType::TimeTracking(id) => {
|
||||||
|
if let Some(id) = id {
|
||||||
|
push_time_track_modal(*id, model, orders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModalType::DeleteIssueStatusModal(id) => {
|
||||||
|
if let Some(id) = id {
|
||||||
|
push_delete_issue_status_modal(*id, model, orders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
ModalType::DebugModal(_) => push_debug_modal(model),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_edit_modal(issue_id: EpicId, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
fn drop_modal(model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
|
let modal = model.modal_stack_mut().pop().unwrap();
|
||||||
|
let modals = model.modals_mut();
|
||||||
|
match modal {
|
||||||
|
ModalType::AddIssue(_) => {
|
||||||
|
modals.add_issue = None;
|
||||||
|
}
|
||||||
|
ModalType::EditIssue(_) => {
|
||||||
|
modals.edit_issue = None;
|
||||||
|
}
|
||||||
|
ModalType::DeleteIssueConfirm(_) => {
|
||||||
|
modals.delete_issue_confirm = None;
|
||||||
|
}
|
||||||
|
ModalType::DeleteEpic(_) => {
|
||||||
|
modals.delete_epic = None;
|
||||||
|
}
|
||||||
|
ModalType::EditEpic(_) => {
|
||||||
|
modals.edit_epic = None;
|
||||||
|
}
|
||||||
|
ModalType::DeleteCommentConfirm(_) => {
|
||||||
|
modals.delete_comment_confirm = None;
|
||||||
|
}
|
||||||
|
ModalType::TimeTracking(_) => {
|
||||||
|
modals.time_tracking = None;
|
||||||
|
}
|
||||||
|
ModalType::DeleteIssueStatusModal(_) => {
|
||||||
|
modals.delete_issue_status_modal = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
ModalType::DebugModal(_) => {
|
||||||
|
modals.debug_modal = None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match modal {
|
||||||
|
ModalType::EditIssue(_)
|
||||||
|
| ModalType::AddIssue(_)
|
||||||
|
| ModalType::DeleteEpic(_)
|
||||||
|
| ModalType::EditEpic(_) => {
|
||||||
|
go_to_board(orders);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ISSUE
|
||||||
|
|
||||||
|
fn push_add_issue_modal(model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
||||||
|
use crate::modals::issues_create::Model;
|
||||||
|
if model.modals().add_issue.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
model.modal_stack_mut().push(ModalType::AddIssue(None));
|
||||||
|
model.modals_mut().add_issue = Some(Model {
|
||||||
|
project_id: model.project.as_ref().map(|p| p.id),
|
||||||
|
..Model::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_edit_issue_modal(issue_id: EpicId, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
|
if model.modals().edit_issue.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let time_tracking_type = model
|
let time_tracking_type = model
|
||||||
.project
|
.project
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| p.time_tracking)
|
.map(|p| p.time_tracking)
|
||||||
.unwrap_or(TimeTracking::Untracked);
|
.unwrap_or(TimeTracking::Untracked);
|
||||||
|
|
||||||
let modal = {
|
let modal = {
|
||||||
let issue = match model.issues_by_id.get(&issue_id) {
|
let issue = match model.issues_by_id.get(&issue_id) {
|
||||||
Some(issue) => issue,
|
Some(issue) => issue,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
ModalType::EditIssue(
|
crate::modals::issues_edit::Model::new(issue, time_tracking_type)
|
||||||
issue_id,
|
|
||||||
Box::new(crate::modals::issues_edit::Model::new(
|
|
||||||
issue,
|
|
||||||
time_tracking_type,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
send_ws_msg(
|
send_ws_msg(
|
||||||
WsMsg::IssueCommentsLoad(issue_id),
|
WsMsg::IssueCommentsLoad(issue_id),
|
||||||
model.ws.as_ref(),
|
model.ws.as_ref(),
|
||||||
orders,
|
orders,
|
||||||
);
|
);
|
||||||
model.modals.push(modal);
|
model
|
||||||
|
.modal_stack_mut()
|
||||||
|
.push(ModalType::EditIssue(Some(issue_id)));
|
||||||
|
model.modals_mut().edit_issue = Some(modal);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_edit_epic_modal(epic_id: EpicId, model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
fn push_delete_issue_modal(id: IssueId, model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
||||||
|
if model.modals().delete_issue_confirm.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
model
|
||||||
|
.modal_stack_mut()
|
||||||
|
.push(ModalType::DeleteIssueConfirm(Some(id)));
|
||||||
|
model.modals_mut().delete_issue_confirm =
|
||||||
|
Some(crate::modals::issues_delete::Model { issue_id: id });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ISSUE STATUS
|
||||||
|
|
||||||
|
fn push_delete_issue_status_modal(
|
||||||
|
id: IssueStatusId,
|
||||||
|
model: &mut Model,
|
||||||
|
_orders: &mut impl Orders<Msg>,
|
||||||
|
) {
|
||||||
|
use crate::modals::issue_statuses_delete::Model;
|
||||||
|
if model.modals().delete_issue_status_modal.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let modal = Model::new(id);
|
||||||
|
model
|
||||||
|
.modal_stack_mut()
|
||||||
|
.push(ModalType::DeleteIssueStatusModal(Some(id)));
|
||||||
|
model.modals_mut().delete_issue_status_modal = Some(modal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EPIC
|
||||||
|
|
||||||
|
fn push_edit_epic_modal(id: EpicId, model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
||||||
use crate::modals::epics_edit::Model;
|
use crate::modals::epics_edit::Model;
|
||||||
let modal = Model::new(epic_id, model);
|
if model.modals().edit_epic.is_some() {
|
||||||
model.modals.push(ModalType::EditEpic(Box::new(modal)));
|
return;
|
||||||
|
}
|
||||||
|
let modal = Model::new(id, model);
|
||||||
|
model.modal_stack_mut().push(ModalType::EditEpic(Some(id)));
|
||||||
|
model.modals_mut().edit_epic = Some(modal);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_delete_epic_modal(issue_id: IssueId, model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
fn push_delete_epic_modal(id: EpicId, model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
||||||
use crate::modals::epics_delete::Model;
|
use crate::modals::epics_delete::Model;
|
||||||
let modal = Model::new(issue_id, model);
|
if model.modals_mut().delete_epic.is_some() {
|
||||||
model.modals.push(ModalType::DeleteEpic(Box::new(modal)));
|
return;
|
||||||
|
}
|
||||||
|
model
|
||||||
|
.modal_stack_mut()
|
||||||
|
.push(ModalType::DeleteEpic(Some(id)));
|
||||||
|
model.modals_mut().delete_epic = Some(Model::new(id, model));
|
||||||
|
}
|
||||||
|
|
||||||
|
// COMMENTS
|
||||||
|
|
||||||
|
fn push_delete_comment_modal(id: CommentId, model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
||||||
|
use crate::modals::comments_delete::Model;
|
||||||
|
if model.modals_mut().delete_comment_confirm.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
model
|
||||||
|
.modal_stack_mut()
|
||||||
|
.push(ModalType::DeleteCommentConfirm(Some(id)));
|
||||||
|
model.modals_mut().delete_comment_confirm = Some(Model::new(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TIME TRACK
|
||||||
|
|
||||||
|
fn push_time_track_modal(id: IssueId, model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
||||||
|
use crate::modals::time_tracking::Model;
|
||||||
|
if model.modals_mut().time_tracking.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
model
|
||||||
|
.modal_stack_mut()
|
||||||
|
.push(ModalType::TimeTracking(Some(id)));
|
||||||
|
model.modals_mut().time_tracking = Some(Model::new(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEBUG
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
fn push_debug_modal(model: &mut Model) {
|
||||||
|
if model.modals().debug_modal.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
model.modal_stack_mut().push(ModalType::DebugModal(None));
|
||||||
|
model.modals_mut().debug_modal = Some(true);
|
||||||
}
|
}
|
||||||
|
@ -1,58 +1,69 @@
|
|||||||
use {
|
use {
|
||||||
crate::{
|
crate::{model::*, Msg},
|
||||||
components::{styled_confirm_modal::StyledConfirmModal, styled_modal::StyledModal},
|
|
||||||
model::*,
|
|
||||||
shared::ToNode,
|
|
||||||
Msg,
|
|
||||||
},
|
|
||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn view(model: &Model) -> Node<Msg> {
|
pub fn view(model: &Model) -> Node<Msg> {
|
||||||
use crate::modals::{issue_statuses_delete, issues_create, issues_edit};
|
let mut nodes = Vec::with_capacity(model.modal_stack().len());
|
||||||
let modals: Vec<Node<Msg>> = model
|
|
||||||
.modals
|
for modal_type in model.modal_stack() {
|
||||||
.iter()
|
match modal_type {
|
||||||
.map(|modal| match modal {
|
ModalType::AddIssue(_) => {
|
||||||
// epic
|
if let Some(modal) = &model.modals().add_issue {
|
||||||
ModalType::DeleteEpic(modal) => crate::modals::epics_delete::view(model, modal),
|
let node = crate::modals::issues_create::view(model, modal);
|
||||||
ModalType::EditEpic(modal) => crate::modals::epics_edit::view(model, modal),
|
nodes.push(node);
|
||||||
// issue
|
|
||||||
ModalType::EditIssue(issue_id, modal) => {
|
|
||||||
if let Some(_issue) = model.issues_by_id.get(issue_id) {
|
|
||||||
let details = issues_edit::view(model, modal.as_ref());
|
|
||||||
StyledModal::build()
|
|
||||||
.variant(crate::components::styled_modal::Variant::Center)
|
|
||||||
.width(1040)
|
|
||||||
.child(details)
|
|
||||||
.build()
|
|
||||||
.into_node()
|
|
||||||
} else {
|
|
||||||
empty![]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ModalType::DeleteIssueConfirm(_id) => crate::modals::issues_delete::view(model),
|
ModalType::EditIssue(_) => {
|
||||||
ModalType::AddIssue(modal) => issues_create::view(model, modal),
|
if let Some(modal) = &model.modals().edit_issue {
|
||||||
// comment
|
let node = crate::modals::issues_edit::view(model, modal);
|
||||||
ModalType::DeleteCommentConfirm(comment_id) => {
|
nodes.push(node);
|
||||||
let comment_id = *comment_id;
|
|
||||||
StyledConfirmModal::build()
|
|
||||||
.title("Are you sure you want to delete this comment?")
|
|
||||||
.message("Once you delete, it's gone for good.")
|
|
||||||
.confirm_text("Delete comment")
|
|
||||||
.on_confirm(mouse_ev(Ev::Click, move |_| Msg::DeleteComment(comment_id)))
|
|
||||||
.build()
|
|
||||||
.into_node()
|
|
||||||
}
|
}
|
||||||
ModalType::TimeTracking(issue_id) => {
|
|
||||||
crate::modals::time_tracking::view(model, *issue_id)
|
|
||||||
}
|
}
|
||||||
ModalType::DeleteIssueStatusModal(delete_issue_modal) => {
|
ModalType::DeleteEpic(_) => {
|
||||||
issue_statuses_delete::view(model, delete_issue_modal.delete_id)
|
if let Some(modal) = &model.modals().delete_epic {
|
||||||
|
let node = crate::modals::epics_delete::view(model, modal);
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModalType::EditEpic(_) => {
|
||||||
|
if let Some(modal) = &model.modals().edit_epic {
|
||||||
|
let node = crate::modals::epics_edit::view(model, modal);
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModalType::DeleteIssueConfirm(_) => {
|
||||||
|
if let Some(_issue_id) = &model.modals().delete_issue_confirm {
|
||||||
|
let node = crate::modals::issues_delete::view(model);
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModalType::DeleteCommentConfirm(_) => {
|
||||||
|
if let Some(modal) = &model.modals().delete_comment_confirm {
|
||||||
|
let node = crate::modals::comments_delete::view(model, modal);
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModalType::TimeTracking(_) => {
|
||||||
|
if let Some(modal) = &model.modals().time_tracking {
|
||||||
|
let node = crate::modals::time_tracking::view(model, modal);
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModalType::DeleteIssueStatusModal(_) => {
|
||||||
|
if let Some(modal) = &model.modals().delete_issue_status_modal {
|
||||||
|
let node = crate::modals::issue_statuses_delete::view(model, modal.delete_id);
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
ModalType::DebugModal => crate::modals::debug::view(model),
|
ModalType::DebugModal(_) => {
|
||||||
})
|
if let Some(true) = &model.modals().debug_modal {
|
||||||
.collect();
|
let node = crate::modals::debug::view(model);
|
||||||
section![id!["modals"], modals]
|
nodes.push(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
section![id!["modals"], nodes]
|
||||||
}
|
}
|
||||||
|
@ -24,21 +24,38 @@ pub trait IssueModal {
|
|||||||
fn update_states(&mut self, msg: &Msg, orders: &mut impl Orders<Msg>);
|
fn update_states(&mut self, msg: &Msg, orders: &mut impl Orders<Msg>);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Modals {
|
||||||
|
// issue
|
||||||
|
pub add_issue: Option<crate::modals::issues_create::Model>,
|
||||||
|
pub edit_issue: Option<crate::modals::issues_edit::Model>,
|
||||||
|
// epic
|
||||||
|
pub delete_epic: Option<crate::modals::epics_delete::Model>,
|
||||||
|
pub edit_epic: Option<crate::modals::epics_edit::Model>,
|
||||||
|
|
||||||
|
pub delete_issue_confirm: Option<crate::modals::issues_delete::Model>,
|
||||||
|
pub delete_comment_confirm: Option<crate::modals::comments_delete::Model>,
|
||||||
|
pub time_tracking: Option<crate::modals::time_tracking::Model>,
|
||||||
|
pub delete_issue_status_modal: Option<crate::modals::issue_statuses_delete::Model>,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
pub debug_modal: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum ModalType {
|
pub enum ModalType {
|
||||||
// issue
|
// issue
|
||||||
AddIssue(Box<crate::modals::issues_create::Model>),
|
AddIssue(Option<i32>),
|
||||||
EditIssue(EpicId, Box<crate::modals::issues_edit::Model>),
|
EditIssue(Option<i32>),
|
||||||
|
DeleteIssueConfirm(Option<i32>),
|
||||||
// epic
|
// epic
|
||||||
DeleteEpic(Box<crate::modals::epics_delete::Model>),
|
DeleteEpic(Option<i32>),
|
||||||
EditEpic(Box<crate::modals::epics_edit::Model>),
|
EditEpic(Option<i32>),
|
||||||
|
|
||||||
DeleteIssueConfirm(EpicId),
|
DeleteCommentConfirm(Option<i32>),
|
||||||
DeleteCommentConfirm(CommentId),
|
TimeTracking(Option<i32>),
|
||||||
TimeTracking(EpicId),
|
DeleteIssueStatusModal(Option<i32>),
|
||||||
DeleteIssueStatusModal(Box<crate::modals::issue_statuses_delete::Model>),
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
DebugModal,
|
DebugModal(Option<i32>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||||
@ -168,7 +185,8 @@ pub struct Model {
|
|||||||
pub comment_form: Option<CreateCommentForm>,
|
pub comment_form: Option<CreateCommentForm>,
|
||||||
|
|
||||||
// modals
|
// modals
|
||||||
pub modals: Vec<ModalType>,
|
modals_stack: Vec<ModalType>,
|
||||||
|
pub modals: Modals,
|
||||||
|
|
||||||
// pages
|
// pages
|
||||||
pub page: Page,
|
pub page: Page,
|
||||||
@ -226,7 +244,6 @@ impl Model {
|
|||||||
host_url,
|
host_url,
|
||||||
ws_url,
|
ws_url,
|
||||||
page_content: PageContent::Project(Box::new(ProjectPage::default())),
|
page_content: PageContent::Project(Box::new(ProjectPage::default())),
|
||||||
modals: vec![],
|
|
||||||
project: None,
|
project: None,
|
||||||
current_user_project: None,
|
current_user_project: None,
|
||||||
about_tooltip_visible: false,
|
about_tooltip_visible: false,
|
||||||
@ -246,6 +263,8 @@ impl Model {
|
|||||||
issues_by_id: Default::default(),
|
issues_by_id: Default::default(),
|
||||||
show_extras: false,
|
show_extras: false,
|
||||||
epics_by_id: Default::default(),
|
epics_by_id: Default::default(),
|
||||||
|
modals_stack: vec![],
|
||||||
|
modals: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,6 +293,16 @@ impl Model {
|
|||||||
&self.user
|
&self.user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn user_id(&self) -> Option<UserId> {
|
||||||
|
self.user.as_ref().map(|u| u.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn project_id(&self) -> Option<ProjectId> {
|
||||||
|
self.project.as_ref().map(|p| p.id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn current_user_role(&self) -> UserRole {
|
pub fn current_user_role(&self) -> UserRole {
|
||||||
self.current_user_project
|
self.current_user_project
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -293,4 +322,20 @@ impl Model {
|
|||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn modals(&self) -> &Modals {
|
||||||
|
&self.modals
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modals_mut(&mut self) -> &mut Modals {
|
||||||
|
&mut self.modals
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modal_stack(&self) -> &[ModalType] {
|
||||||
|
&self.modals_stack
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modal_stack_mut(&mut self) -> &mut Vec<ModalType> {
|
||||||
|
&mut self.modals_stack
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,66 +24,6 @@ pub struct ProjectPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectPage {
|
impl ProjectPage {
|
||||||
pub fn rebuild_visible(
|
|
||||||
&mut self,
|
|
||||||
epics: &[Epic],
|
|
||||||
statuses: &[IssueStatus],
|
|
||||||
issues: &[Issue],
|
|
||||||
user: &Option<User>,
|
|
||||||
) {
|
|
||||||
let mut map = vec![];
|
|
||||||
let epics = vec![None]
|
|
||||||
.into_iter()
|
|
||||||
.chain(epics.iter().map(|s| Some((s.id, s.name.as_str()))));
|
|
||||||
|
|
||||||
let statuses = statuses.iter().map(|s| (s.id, s.name.as_str()));
|
|
||||||
|
|
||||||
let mut issues: Vec<&Issue> = issues.iter().collect();
|
|
||||||
if self.recently_updated_filter {
|
|
||||||
let mut m = HashMap::new();
|
|
||||||
let mut sorted = vec![];
|
|
||||||
for issue in issues.into_iter() {
|
|
||||||
sorted.push((issue.id, issue.updated_at));
|
|
||||||
m.insert(issue.id, issue);
|
|
||||||
}
|
|
||||||
sorted.sort_by(|(_, a_time), (_, b_time)| a_time.cmp(b_time));
|
|
||||||
issues = sorted
|
|
||||||
.into_iter()
|
|
||||||
.take(10)
|
|
||||||
.flat_map(|(id, _)| m.remove(&id))
|
|
||||||
.collect();
|
|
||||||
issues.sort_by(|a, b| a.list_position.cmp(&b.list_position));
|
|
||||||
}
|
|
||||||
|
|
||||||
for epic in epics {
|
|
||||||
let mut per_epic_map = EpicIssuePerStatus {
|
|
||||||
epic_ref: epic.map(|(id, name)| (id, name.to_string())),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
for (current_status_id, issue_status_name) in statuses.to_owned() {
|
|
||||||
let mut per_status_map = StatusIssueIds {
|
|
||||||
status_id: current_status_id,
|
|
||||||
status_name: issue_status_name.to_string(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
for issue in issues.iter() {
|
|
||||||
if issue.epic_id == epic.map(|(id, _)| id)
|
|
||||||
&& issue_filter_status(issue, current_status_id)
|
|
||||||
&& issue_filter_with_avatars(issue, &self.active_avatar_filters)
|
|
||||||
&& issue_filter_with_text(issue, self.text_filter.as_str())
|
|
||||||
&& issue_filter_with_only_my(issue, self.only_my_filter, user)
|
|
||||||
{
|
|
||||||
per_status_map.issue_ids.push(issue.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
per_epic_map.per_status_issues.push(per_status_map);
|
|
||||||
}
|
|
||||||
map.push(per_epic_map);
|
|
||||||
}
|
|
||||||
self.visible_issues = map;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn visible_issues(
|
pub fn visible_issues(
|
||||||
page: &ProjectPage,
|
page: &ProjectPage,
|
||||||
epics: &[Epic],
|
epics: &[Epic],
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
components::styled_select::StyledSelectChanged,
|
components::styled_select::StyledSelectChanged,
|
||||||
model::{ModalType, Model, Page, PageContent},
|
model::{Model, Page, PageContent},
|
||||||
pages::project_page::model::ProjectPage,
|
pages::project_page::model::ProjectPage,
|
||||||
ws::{board_load, send_ws_msg},
|
ws::{board_load, send_ws_msg},
|
||||||
BoardPageChange, EditIssueModalSection, FieldId, Msg, OperationKind, PageChanged,
|
BoardPageChange, EditIssueModalSection, FieldId, Msg, OperationKind, PageChanged,
|
||||||
@ -54,15 +54,7 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
|||||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Type)),
|
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Type)),
|
||||||
StyledSelectChanged::Text(text),
|
StyledSelectChanged::Text(text),
|
||||||
) => {
|
) => {
|
||||||
let modal = model
|
if let Some(m) = &mut model.modals_mut().edit_issue {
|
||||||
.modals
|
|
||||||
.iter_mut()
|
|
||||||
.filter_map(|modal| match modal {
|
|
||||||
ModalType::EditIssue(_, modal) => Some(modal),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.last();
|
|
||||||
if let Some(m) = modal {
|
|
||||||
m.top_type_state.text_filter = text;
|
m.top_type_state.text_filter = text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,9 @@ use {
|
|||||||
styled_select::StyledSelect,
|
styled_select::StyledSelect,
|
||||||
styled_textarea::StyledTextarea,
|
styled_textarea::StyledTextarea,
|
||||||
},
|
},
|
||||||
modals::issue_statuses_delete::Model as DeleteIssueStatusModal,
|
|
||||||
model::{self, ModalType, Model, PageContent},
|
model::{self, ModalType, Model, PageContent},
|
||||||
pages::project_settings_page::ProjectSettingsPage,
|
pages::project_settings_page::ProjectSettingsPage,
|
||||||
shared::{inner_layout, IntoChild, ToChild, ToNode},
|
shared::{inner_layout, IntoChild, ToNode},
|
||||||
FieldId, Msg, PageChanged, ProjectFieldId, ProjectPageChange,
|
FieldId, Msg, PageChanged, ProjectFieldId, ProjectPageChange,
|
||||||
},
|
},
|
||||||
jirs_data::{IssueStatus, ProjectCategory, TimeTracking},
|
jirs_data::{IssueStatus, ProjectCategory, TimeTracking},
|
||||||
@ -51,11 +50,11 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
let category_field = category_field(page);
|
let category_field = category_field(page);
|
||||||
|
|
||||||
let time_tracking = StyledCheckbox::build()
|
let time_tracking = StyledCheckbox::build()
|
||||||
.options(vec![
|
.options(
|
||||||
TimeTracking::Untracked.to_child(),
|
TimeTracking::default()
|
||||||
TimeTracking::Fibonacci.to_child(),
|
.into_iter()
|
||||||
TimeTracking::Hourly.to_child(),
|
.map(|tt| tt.into_child()),
|
||||||
])
|
)
|
||||||
.state(&page.time_tracking)
|
.state(&page.time_tracking)
|
||||||
.add_class("timeTracking")
|
.add_class("timeTracking")
|
||||||
.build(FieldId::ProjectSettings(ProjectFieldId::TimeTracking))
|
.build(FieldId::ProjectSettings(ProjectFieldId::TimeTracking))
|
||||||
@ -336,9 +335,7 @@ fn show_column_preview(
|
|||||||
let on_delete = mouse_ev(Ev::Click, move |ev| {
|
let on_delete = mouse_ev(Ev::Click, move |ev| {
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
Msg::ModalOpened(Box::new(ModalType::DeleteIssueStatusModal(Box::new(
|
Msg::ModalOpened(ModalType::DeleteIssueStatusModal(Some(id)))
|
||||||
DeleteIssueStatusModal::new(id),
|
|
||||||
))))
|
|
||||||
});
|
});
|
||||||
let delete = StyledButton::build()
|
let delete = StyledButton::build()
|
||||||
.primary()
|
.primary()
|
||||||
|
@ -21,7 +21,7 @@ pub fn tracking_link(model: &Model, modal: &crate::modals::issues_edit::Model) -
|
|||||||
let issue_id = *id;
|
let issue_id = *id;
|
||||||
|
|
||||||
let handler = mouse_ev(Ev::Click, move |_| {
|
let handler = mouse_ev(Ev::Click, move |_| {
|
||||||
Msg::ModalOpened(Box::new(ModalType::TimeTracking(issue_id)))
|
Msg::ModalOpened(ModalType::TimeTracking(Some(issue_id)))
|
||||||
});
|
});
|
||||||
|
|
||||||
div![C!["trackingLink"], handler, tracking_widget(model, modal),]
|
div![C!["trackingLink"], handler, tracking_widget(model, modal),]
|
||||||
|
@ -276,8 +276,8 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
// comments
|
// comments
|
||||||
WsMsg::IssueCommentsLoaded(mut comments) => {
|
WsMsg::IssueCommentsLoaded(mut comments) => {
|
||||||
let issue_id = match model.modals.get(0) {
|
let issue_id = match &model.modals().edit_issue {
|
||||||
Some(ModalType::EditIssue(issue_id, _)) => *issue_id,
|
Some(modal) => modal.id,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
if comments.iter().any(|c| c.issue_id != issue_id) {
|
if comments.iter().any(|c| c.issue_id != issue_id) {
|
||||||
|
@ -77,6 +77,17 @@ insert into invitations (email, name, state, project_id, invited_by_id) values (
|
|||||||
2
|
2
|
||||||
);
|
);
|
||||||
insert into tokens (user_id, access_token, refresh_token) values (1, uuid_generate_v4(), uuid_generate_v4() );
|
insert into tokens (user_id, access_token, refresh_token) values (1, uuid_generate_v4(), uuid_generate_v4() );
|
||||||
|
insert into epics (name, project_id, user_id) VALUES (
|
||||||
|
'Foo', 1, 1
|
||||||
|
), (
|
||||||
|
'Bar', 1, 2
|
||||||
|
), (
|
||||||
|
'Foz', 2 ,1
|
||||||
|
), (
|
||||||
|
'Baz', 1, 2
|
||||||
|
), (
|
||||||
|
'Hello World', 2, 2
|
||||||
|
);
|
||||||
insert into issues(
|
insert into issues(
|
||||||
title,
|
title,
|
||||||
issue_type,
|
issue_type,
|
||||||
@ -86,7 +97,8 @@ insert into issues(
|
|||||||
description_text,
|
description_text,
|
||||||
reporter_id,
|
reporter_id,
|
||||||
project_id,
|
project_id,
|
||||||
issue_status_id
|
issue_status_id,
|
||||||
|
epic_id
|
||||||
) values (
|
) values (
|
||||||
'Foo',
|
'Foo',
|
||||||
'task',
|
'task',
|
||||||
@ -96,7 +108,8 @@ insert into issues(
|
|||||||
'foz baz',
|
'foz baz',
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
1
|
1,
|
||||||
|
NULL
|
||||||
), (
|
), (
|
||||||
'Foo2',
|
'Foo2',
|
||||||
'bug',
|
'bug',
|
||||||
@ -106,7 +119,8 @@ insert into issues(
|
|||||||
'foz baz 2',
|
'foz baz 2',
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
2
|
2,
|
||||||
|
NULL
|
||||||
), (
|
), (
|
||||||
'Foo3',
|
'Foo3',
|
||||||
'story',
|
'story',
|
||||||
@ -116,7 +130,30 @@ insert into issues(
|
|||||||
'foz baz 3',
|
'foz baz 3',
|
||||||
2,
|
2,
|
||||||
1,
|
1,
|
||||||
3
|
3,
|
||||||
|
NULL
|
||||||
|
), (
|
||||||
|
'Story 1 in Epic 1',
|
||||||
|
'story',
|
||||||
|
'low',
|
||||||
|
3,
|
||||||
|
'hello world 3',
|
||||||
|
'foz baz 3',
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
3,
|
||||||
|
1
|
||||||
|
), (
|
||||||
|
'Story 2 in Epic 1',
|
||||||
|
'story',
|
||||||
|
'low',
|
||||||
|
3,
|
||||||
|
'hello world 3',
|
||||||
|
'foz baz 3',
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
3,
|
||||||
|
1
|
||||||
);
|
);
|
||||||
insert into comments (user_id, issue_id, body) values (
|
insert into comments (user_id, issue_id, body) values (
|
||||||
1, 1, 'Vestibulum non neque at dui maximus porttitor fermentum consectetur eros.'
|
1, 1, 'Vestibulum non neque at dui maximus porttitor fermentum consectetur eros.'
|
||||||
|
Loading…
Reference in New Issue
Block a user