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 = [
|
||||
"bincode",
|
||||
"chrono",
|
||||
"derive_enum_iter",
|
||||
"derive_enum_primitive",
|
||||
"futures 0.1.30",
|
||||
"jirs-data",
|
||||
"js-sys",
|
||||
|
@ -1 +0,0 @@
|
||||
theme: jekyll-theme-minimal
|
@ -9,7 +9,7 @@ use {
|
||||
uuid::Uuid,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Queryable)]
|
||||
#[derive(Serialize, Debug, Deserialize, Queryable)]
|
||||
pub struct Issue {
|
||||
pub id: i32,
|
||||
pub title: String,
|
||||
|
@ -1,5 +1,7 @@
|
||||
use {
|
||||
crate::{WebSocketActor, WsHandler, WsResult},
|
||||
crate::{
|
||||
db_or_debug_and_return, mail_or_debug_and_return, WebSocketActor, WsHandler, WsResult,
|
||||
},
|
||||
actix::AsyncContext,
|
||||
database_actor::{
|
||||
authorize_user::AuthorizeUser,
|
||||
@ -20,43 +22,16 @@ impl WsHandler<Authenticate> for WebSocketActor {
|
||||
fn handle_msg(&mut self, msg: Authenticate, _ctx: &mut Self::Context) -> WsResult {
|
||||
let Authenticate { name, email } = msg;
|
||||
// TODO check attempt number, allow only 5 times per day
|
||||
let user = match block_on(self.db.send(LookupUser { name, email })) {
|
||||
Ok(Ok(user)) => user,
|
||||
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);
|
||||
}
|
||||
};
|
||||
let user = db_or_debug_and_return!(self, LookupUser { name, email });
|
||||
let token = db_or_debug_and_return!(self, CreateBindToken { user_id: user.id });
|
||||
if let Some(bind_token) = token.bind_token.as_ref().cloned() {
|
||||
match block_on(self.mail.send(Welcome {
|
||||
bind_token,
|
||||
email: user.email,
|
||||
})) {
|
||||
Ok(Ok(_)) => (),
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{}", e);
|
||||
return Ok(None);
|
||||
let _ = mail_or_debug_and_return!(
|
||||
self,
|
||||
Welcome {
|
||||
bind_token,
|
||||
email: user.email,
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Ok(Some(WsMsg::AuthenticateSuccess))
|
||||
}
|
||||
@ -68,17 +43,16 @@ pub struct CheckAuthToken {
|
||||
|
||||
impl WsHandler<CheckAuthToken> for WebSocketActor {
|
||||
fn handle_msg(&mut self, msg: CheckAuthToken, ctx: &mut Self::Context) -> WsResult {
|
||||
let user: jirs_data::User = match block_on(self.db.send(AuthorizeUser {
|
||||
access_token: msg.token,
|
||||
})) {
|
||||
Ok(Ok(u)) => u,
|
||||
Ok(Err(_)) => {
|
||||
return Ok(Some(WsMsg::AuthorizeLoaded(Err(
|
||||
"Invalid auth token".to_string()
|
||||
))));
|
||||
}
|
||||
_ => return Ok(Some(WsMsg::AuthorizeExpired)),
|
||||
};
|
||||
let user: jirs_data::User = db_or_debug_and_return!(
|
||||
self,
|
||||
AuthorizeUser {
|
||||
access_token: msg.token,
|
||||
},
|
||||
Ok(Some(WsMsg::AuthorizeLoaded(Err(
|
||||
"Invalid auth token".to_string()
|
||||
)))),
|
||||
Ok(Some(WsMsg::AuthorizeExpired))
|
||||
);
|
||||
self.current_user = Some(user.clone());
|
||||
self.current_user_project = self.load_user_project().ok();
|
||||
self.current_project = self.load_project().ok();
|
||||
@ -94,13 +68,14 @@ pub struct CheckBindToken {
|
||||
|
||||
impl WsHandler<CheckBindToken> for WebSocketActor {
|
||||
fn handle_msg(&mut self, msg: CheckBindToken, _ctx: &mut Self::Context) -> WsResult {
|
||||
let token: Token = match block_on(self.db.send(FindBindToken {
|
||||
token: msg.bind_token,
|
||||
})) {
|
||||
Ok(Ok(token)) => token,
|
||||
Ok(Err(_)) => return Ok(Some(WsMsg::BindTokenBad)),
|
||||
_ => return Ok(None),
|
||||
};
|
||||
let token: Token = db_or_debug_and_return!(
|
||||
self,
|
||||
FindBindToken {
|
||||
token: msg.bind_token,
|
||||
},
|
||||
Ok(Some(WsMsg::BindTokenBad)),
|
||||
Ok(None)
|
||||
);
|
||||
Ok(Some(WsMsg::BindTokenOk(token.access_token)))
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use {
|
||||
crate::{WebSocketActor, WsHandler, WsResult},
|
||||
crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||
futures::executor::block_on,
|
||||
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 {
|
||||
self.require_user()?;
|
||||
|
||||
let comments = match block_on(self.db.send(database_actor::comments::LoadIssueComments {
|
||||
issue_id: msg.issue_id,
|
||||
})) {
|
||||
Ok(Ok(comments)) => comments,
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{:?}", e);
|
||||
return Ok(None);
|
||||
let comments = db_or_debug_and_return!(
|
||||
self,
|
||||
database_actor::comments::LoadIssueComments {
|
||||
issue_id: msg.issue_id,
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
Ok(Some(WsMsg::IssueCommentsLoaded(comments)))
|
||||
}
|
||||
@ -39,21 +32,14 @@ impl WsHandler<CreateCommentPayload> for WebSocketActor {
|
||||
msg.user_id = Some(user_id);
|
||||
}
|
||||
let issue_id = msg.issue_id;
|
||||
match block_on(self.db.send(CreateComment {
|
||||
user_id,
|
||||
issue_id,
|
||||
body: msg.body,
|
||||
})) {
|
||||
Ok(Ok(_)) => (),
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{:?}", e);
|
||||
return Ok(None);
|
||||
let _ = db_or_debug_and_return!(
|
||||
self,
|
||||
CreateComment {
|
||||
user_id,
|
||||
issue_id,
|
||||
body: msg.body,
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
);
|
||||
self.handle_msg(LoadIssueComments { issue_id }, ctx)
|
||||
}
|
||||
}
|
||||
@ -69,21 +55,14 @@ impl WsHandler<UpdateCommentPayload> for WebSocketActor {
|
||||
body,
|
||||
} = msg;
|
||||
|
||||
let comment = match block_on(self.db.send(UpdateComment {
|
||||
comment_id,
|
||||
user_id,
|
||||
body,
|
||||
})) {
|
||||
Ok(Ok(comment)) => comment,
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{:?}", e);
|
||||
return Ok(None);
|
||||
let comment = db_or_debug_and_return!(
|
||||
self,
|
||||
UpdateComment {
|
||||
comment_id,
|
||||
user_id,
|
||||
body,
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
);
|
||||
self.broadcast(&WsMsg::CommentUpdated(comment));
|
||||
Ok(None)
|
||||
}
|
||||
@ -99,20 +78,13 @@ impl WsHandler<DeleteComment> for WebSocketActor {
|
||||
|
||||
let user_id = self.require_user()?.id;
|
||||
|
||||
let m = DeleteComment {
|
||||
comment_id: msg.comment_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)
|
||||
let n = db_or_debug_and_return!(
|
||||
self,
|
||||
DeleteComment {
|
||||
comment_id: msg.comment_id,
|
||||
user_id,
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
);
|
||||
Ok(Some(WsMsg::CommentDeleted(msg.comment_id, n)))
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use {
|
||||
crate::{WebSocketActor, WsHandler, WsResult},
|
||||
crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||
futures::executor::block_on,
|
||||
jirs_data::{EpicId, NameString, UserProject, WsMsg},
|
||||
};
|
||||
@ -9,8 +9,7 @@ pub struct LoadEpics;
|
||||
impl WsHandler<LoadEpics> for WebSocketActor {
|
||||
fn handle_msg(&mut self, _msg: LoadEpics, _ctx: &mut Self::Context) -> WsResult {
|
||||
let project_id = self.require_user_project()?.project_id;
|
||||
let epics =
|
||||
crate::query_db_or_print!(self, database_actor::epics::LoadEpics { project_id });
|
||||
let epics = db_or_debug_and_return!(self, database_actor::epics::LoadEpics { project_id });
|
||||
Ok(Some(WsMsg::EpicsLoaded(epics)))
|
||||
}
|
||||
}
|
||||
@ -27,7 +26,7 @@ impl WsHandler<CreateEpic> for WebSocketActor {
|
||||
project_id,
|
||||
..
|
||||
} = self.require_user_project()?;
|
||||
let epic = crate::query_db_or_print!(
|
||||
let epic = db_or_debug_and_return!(
|
||||
self,
|
||||
database_actor::epics::CreateEpic {
|
||||
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 {
|
||||
let UpdateEpic { epic_id, name } = msg;
|
||||
let UserProject { project_id, .. } = self.require_user_project()?;
|
||||
let epic = crate::query_db_or_print!(
|
||||
let epic = db_or_debug_and_return!(
|
||||
self,
|
||||
database_actor::epics::UpdateEpic {
|
||||
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 {
|
||||
let DeleteEpic { epic_id } = msg;
|
||||
let UserProject { user_id, .. } = self.require_user_project()?;
|
||||
let n = crate::query_db_or_print!(
|
||||
let n = db_or_debug_and_return!(
|
||||
self,
|
||||
database_actor::epics::DeleteEpic {
|
||||
user_id: *user_id,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use {
|
||||
crate::{WebSocketActor, WsHandler, WsResult},
|
||||
crate::{actor_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||
futures::executor::block_on,
|
||||
jirs_data::{Code, Lang, WsMsg},
|
||||
};
|
||||
@ -9,19 +9,14 @@ pub struct HighlightCode(pub Lang, pub Code);
|
||||
impl WsHandler<HighlightCode> for WebSocketActor {
|
||||
fn handle_msg(&mut self, msg: HighlightCode, _ctx: &mut Self::Context) -> WsResult {
|
||||
self.require_user()?.id;
|
||||
match block_on(self.hi.send(highlight_actor::HighlightCode {
|
||||
code: msg.1,
|
||||
lang: msg.0,
|
||||
})) {
|
||||
Ok(Ok(res)) => Ok(Some(WsMsg::HighlightedCode(res))),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
Ok(None)
|
||||
let res = actor_or_debug_and_return!(
|
||||
self,
|
||||
hi,
|
||||
highlight_actor::HighlightCode {
|
||||
code: msg.1,
|
||||
lang: msg.0,
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
);
|
||||
Ok(Some(WsMsg::HighlightedCode(res)))
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
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},
|
||||
futures::executor::block_on,
|
||||
jirs_data::{
|
||||
@ -15,18 +18,8 @@ impl WsHandler<ListInvitation> for WebSocketActor {
|
||||
Some(id) => id,
|
||||
_ => return Ok(None),
|
||||
};
|
||||
let res = match block_on(self.db.send(invitations::ListInvitation { user_id })) {
|
||||
Ok(Ok(v)) => Some(WsMsg::InvitationListLoaded(v)),
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{:?}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
let v = db_or_debug_and_return!(self, invitations::ListInvitation { user_id });
|
||||
Ok(Some(WsMsg::InvitationListLoaded(v)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 CreateInvitation { email, name, role } = msg;
|
||||
let invitation =
|
||||
match block_on(self.db.send(database_actor::invitations::CreateInvitation {
|
||||
let invitation = db_or_debug_and_return!(
|
||||
self,
|
||||
database_actor::invitations::CreateInvitation {
|
||||
user_id,
|
||||
project_id,
|
||||
email: email.clone(),
|
||||
name: name.clone(),
|
||||
role,
|
||||
})) {
|
||||
Ok(Ok(invitation)) => invitation,
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(Some(WsMsg::InvitationSendFailure));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
return Ok(Some(WsMsg::InvitationSendFailure));
|
||||
}
|
||||
};
|
||||
match block_on(self.mail.send(mail_actor::invite::Invite {
|
||||
bind_token: invitation.bind_token,
|
||||
email: invitation.email,
|
||||
inviter_name,
|
||||
})) {
|
||||
Ok(Ok(_)) => (),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(Some(WsMsg::InvitationSendFailure));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
return Ok(Some(WsMsg::InvitationSendFailure));
|
||||
}
|
||||
}
|
||||
},
|
||||
Ok(Some(WsMsg::InvitationSendFailure)),
|
||||
Ok(Some(WsMsg::InvitationSendFailure))
|
||||
);
|
||||
let _ = mail_or_debug_and_return!(
|
||||
self,
|
||||
mail_actor::invite::Invite {
|
||||
bind_token: invitation.bind_token,
|
||||
email: invitation.email,
|
||||
inviter_name,
|
||||
},
|
||||
Ok(Some(WsMsg::InvitationSendFailure)),
|
||||
Ok(Some(WsMsg::InvitationSendFailure))
|
||||
);
|
||||
|
||||
// If user exists then send message to him
|
||||
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 {
|
||||
self.require_user()?;
|
||||
let DeleteInvitation { id } = msg;
|
||||
let res = match block_on(self.db.send(invitations::DeleteInvitation { id })) {
|
||||
Ok(Ok(_)) => None,
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
let _ = db_or_debug_and_return!(self, invitations::DeleteInvitation { id });
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,18 +101,8 @@ impl WsHandler<RevokeInvitation> for WebSocketActor {
|
||||
fn handle_msg(&mut self, msg: RevokeInvitation, _ctx: &mut Self::Context) -> WsResult {
|
||||
self.require_user()?;
|
||||
let RevokeInvitation { id } = msg;
|
||||
let res = match block_on(self.db.send(invitations::RevokeInvitation { id })) {
|
||||
Ok(Ok(_)) => Some(WsMsg::InvitationRevokeSuccess(id)),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
let _ = db_or_debug_and_return!(self, invitations::RevokeInvitation { id });
|
||||
Ok(Some(WsMsg::InvitationRevokeSuccess(id)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,45 +113,34 @@ pub struct AcceptInvitation {
|
||||
impl WsHandler<AcceptInvitation> for WebSocketActor {
|
||||
fn handle_msg(&mut self, msg: AcceptInvitation, ctx: &mut Self::Context) -> WsResult {
|
||||
let AcceptInvitation { invitation_token } = msg;
|
||||
let token = match block_on(
|
||||
self.db
|
||||
.send(invitations::AcceptInvitation { invitation_token }),
|
||||
) {
|
||||
Ok(Ok(token)) => token,
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(Some(WsMsg::InvitationAcceptFailure(invitation_token)));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
return Ok(Some(WsMsg::InvitationAcceptFailure(invitation_token)));
|
||||
}
|
||||
};
|
||||
let token = db_or_debug_and_return!(
|
||||
self,
|
||||
invitations::AcceptInvitation { invitation_token },
|
||||
Ok(Some(WsMsg::InvitationAcceptFailure(invitation_token))),
|
||||
Ok(Some(WsMsg::InvitationAcceptFailure(invitation_token)))
|
||||
);
|
||||
|
||||
for message in block_on(
|
||||
self.db
|
||||
.send(database_actor::messages::LookupMessagesByToken {
|
||||
token: invitation_token,
|
||||
user_id: token.user_id,
|
||||
}),
|
||||
)
|
||||
.unwrap_or_else(|_| Ok(vec![]))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
match block_on(self.db.send(database_actor::messages::MarkMessageSeen {
|
||||
for message in crate::actor_or_debug_and_fallback!(
|
||||
self,
|
||||
db,
|
||||
database_actor::messages::LookupMessagesByToken {
|
||||
token: invitation_token,
|
||||
user_id: token.user_id,
|
||||
message_id: message.id,
|
||||
})) {
|
||||
Ok(Ok(n)) => {
|
||||
},
|
||||
vec![],
|
||||
vec![]
|
||||
) {
|
||||
crate::actor_or_debug_and_ignore!(
|
||||
self,
|
||||
db,
|
||||
database_actor::messages::MarkMessageSeen {
|
||||
user_id: token.user_id,
|
||||
message_id: 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)))
|
||||
|
@ -1,5 +1,5 @@
|
||||
use {
|
||||
crate::{WebSocketActor, WsHandler, WsResult},
|
||||
crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||
database_actor::issue_statuses,
|
||||
futures::executor::block_on,
|
||||
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 {
|
||||
let project_id = self.require_user_project()?.project_id;
|
||||
|
||||
let msg = match block_on(
|
||||
self.db
|
||||
.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)
|
||||
let v = db_or_debug_and_return!(self, issue_statuses::LoadIssueStatuses { project_id });
|
||||
Ok(Some(WsMsg::IssueStatusesLoaded(v)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,22 +26,15 @@ impl WsHandler<CreateIssueStatus> for WebSocketActor {
|
||||
let project_id = self.require_user_project()?.project_id;
|
||||
|
||||
let CreateIssueStatus { position, name } = msg;
|
||||
let msg = match block_on(self.db.send(issue_statuses::CreateIssueStatus {
|
||||
project_id,
|
||||
position,
|
||||
name,
|
||||
})) {
|
||||
Ok(Ok(is)) => Some(WsMsg::IssueStatusCreated(is)),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
let issue_status = db_or_debug_and_return!(
|
||||
self,
|
||||
issue_statuses::CreateIssueStatus {
|
||||
project_id,
|
||||
position,
|
||||
name,
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
Ok(msg)
|
||||
);
|
||||
Ok(Some(WsMsg::IssueStatusCreated(issue_status)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,21 +47,14 @@ impl WsHandler<DeleteIssueStatus> for WebSocketActor {
|
||||
let project_id = self.require_user_project()?.project_id;
|
||||
|
||||
let DeleteIssueStatus { issue_status_id } = msg;
|
||||
let msg = match block_on(self.db.send(issue_statuses::DeleteIssueStatus {
|
||||
issue_status_id,
|
||||
project_id,
|
||||
})) {
|
||||
Ok(Ok(n)) => Some(WsMsg::IssueStatusDeleted(msg.issue_status_id, n)),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
let n = db_or_debug_and_return!(
|
||||
self,
|
||||
issue_statuses::DeleteIssueStatus {
|
||||
issue_status_id,
|
||||
project_id,
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
Ok(msg)
|
||||
);
|
||||
Ok(Some(WsMsg::IssueStatusDeleted(msg.issue_status_id, n)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,22 +73,16 @@ impl WsHandler<UpdateIssueStatus> for WebSocketActor {
|
||||
position,
|
||||
name,
|
||||
} = msg;
|
||||
let msg = match block_on(self.db.send(issue_statuses::UpdateIssueStatus {
|
||||
issue_status_id,
|
||||
position,
|
||||
name,
|
||||
project_id,
|
||||
})) {
|
||||
Ok(Ok(is)) => Some(WsMsg::IssueStatusUpdated(is)),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
let issue_status = db_or_debug_and_return!(
|
||||
self,
|
||||
issue_statuses::UpdateIssueStatus {
|
||||
issue_status_id,
|
||||
position,
|
||||
name,
|
||||
project_id,
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
);
|
||||
let msg = Some(WsMsg::IssueStatusUpdated(issue_status));
|
||||
if let Some(ws_msg) = msg.as_ref() {
|
||||
self.broadcast(ws_msg)
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use {
|
||||
crate::{WebSocketActor, WsHandler, WsResult},
|
||||
crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||
database_actor::{
|
||||
issue_assignees::LoadAssignees,
|
||||
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)) {
|
||||
Ok(Ok(issue)) => issue.into(),
|
||||
_ => return Ok(None),
|
||||
};
|
||||
let issue = db_or_debug_and_return!(self, msg);
|
||||
let mut issue: jirs_data::Issue = issue.into();
|
||||
|
||||
let assignees: Vec<IssueAssignee> =
|
||||
match block_on(self.db.send(LoadAssignees { issue_id: issue.id })) {
|
||||
Ok(Ok(v)) => v,
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
db_or_debug_and_return!(self, LoadAssignees { issue_id: issue.id });
|
||||
|
||||
for assignee in assignees {
|
||||
issue.user_ids.push(assignee.user_id);
|
||||
@ -170,18 +158,8 @@ impl WsHandler<CreateIssuePayload> for WebSocketActor {
|
||||
user_ids: msg.user_ids,
|
||||
epic_id: msg.epic_id,
|
||||
};
|
||||
let m = match block_on(self.db.send(msg)) {
|
||||
Ok(Ok(issue)) => Some(WsMsg::IssueCreated(issue.into())),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
Ok(m)
|
||||
let issue = db_or_debug_and_return!(self, msg);
|
||||
Ok(Some(WsMsg::IssueCreated(issue.into())))
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,21 +170,11 @@ pub struct DeleteIssue {
|
||||
impl WsHandler<DeleteIssue> for WebSocketActor {
|
||||
fn handle_msg(&mut self, msg: DeleteIssue, _ctx: &mut Self::Context) -> WsResult {
|
||||
self.require_user()?;
|
||||
let m = match block_on(
|
||||
self.db
|
||||
.send(database_actor::issues::DeleteIssue { issue_id: msg.id }),
|
||||
) {
|
||||
Ok(Ok(n)) => Some(WsMsg::IssueDeleted(msg.id, n)),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
Ok(m)
|
||||
let n = db_or_debug_and_return!(
|
||||
self,
|
||||
database_actor::issues::DeleteIssue { issue_id: msg.id }
|
||||
);
|
||||
Ok(Some(WsMsg::IssueDeleted(msg.id, n)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,14 +184,11 @@ impl WsHandler<LoadIssues> for WebSocketActor {
|
||||
fn handle_msg(&mut self, _msg: LoadIssues, _ctx: &mut Self::Context) -> WsResult {
|
||||
let project_id = self.require_user_project()?.project_id;
|
||||
|
||||
let issues: Vec<jirs_data::Issue> =
|
||||
match block_on(self.db.send(LoadProjectIssues { project_id })) {
|
||||
Ok(Ok(v)) => v.into_iter().map(|i| i.into()).collect(),
|
||||
_ => return Ok(None),
|
||||
};
|
||||
let v = db_or_debug_and_return!(self, LoadProjectIssues { project_id });
|
||||
let issues: Vec<jirs_data::Issue> = v.into_iter().map(|i| i.into()).collect();
|
||||
let mut issue_map = HashMap::new();
|
||||
let mut queue = vec![];
|
||||
for issue in issues.into_iter() {
|
||||
for issue in issues {
|
||||
let f = self.db.send(LoadAssignees { issue_id: issue.id });
|
||||
queue.push(f);
|
||||
issue_map.insert(issue.id, issue);
|
||||
@ -238,9 +203,10 @@ impl WsHandler<LoadIssues> for WebSocketActor {
|
||||
};
|
||||
}
|
||||
let mut issues = vec![];
|
||||
for (_, issue) in issue_map.into_iter() {
|
||||
for (_, issue) in issue_map {
|
||||
issues.push(issue);
|
||||
}
|
||||
issues.sort_by(|a, b| a.list_position.cmp(&b.list_position));
|
||||
|
||||
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 {
|
||||
let _project_id = self.require_user_project()?.project_id;
|
||||
for (issue_id, list_position, status_id, epic_id) in msg.0 {
|
||||
match block_on(self.db.send(database_actor::issues::UpdateIssue {
|
||||
issue_id,
|
||||
list_position: Some(list_position),
|
||||
issue_status_id: Some(status_id),
|
||||
epic_id: Some(epic_id),
|
||||
..Default::default()
|
||||
})) {
|
||||
Ok(Ok(_)) => (),
|
||||
_ => return Ok(None),
|
||||
};
|
||||
crate::actor_or_debug_and_ignore!(
|
||||
self,
|
||||
db,
|
||||
database_actor::issues::UpdateIssue {
|
||||
issue_id,
|
||||
list_position: Some(list_position),
|
||||
issue_status_id: Some(status_id),
|
||||
epic_id: Some(epic_id),
|
||||
..Default::default()
|
||||
},
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
|
||||
self.handle_msg(LoadIssues, ctx)
|
||||
|
@ -1,5 +1,5 @@
|
||||
use {
|
||||
crate::{WebSocketActor, WsHandler, WsResult},
|
||||
crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||
database_actor::messages,
|
||||
futures::executor::block_on,
|
||||
jirs_data::{MessageId, WsMsg},
|
||||
@ -10,17 +10,8 @@ pub struct LoadMessages;
|
||||
impl WsHandler<LoadMessages> for WebSocketActor {
|
||||
fn handle_msg(&mut self, _msg: LoadMessages, _ctx: &mut Self::Context) -> WsResult {
|
||||
let user_id = self.require_user()?.id;
|
||||
match block_on(self.db.send(messages::LoadMessages { user_id })) {
|
||||
Ok(Ok(v)) => Ok(Some(WsMsg::MessagesLoaded(v))),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
Ok(None)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
let v = db_or_debug_and_return!(self, messages::LoadMessages { user_id });
|
||||
Ok(Some(WsMsg::MessagesLoaded(v)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,19 +22,13 @@ pub struct MarkMessageSeen {
|
||||
impl WsHandler<MarkMessageSeen> for WebSocketActor {
|
||||
fn handle_msg(&mut self, msg: MarkMessageSeen, _ctx: &mut Self::Context) -> WsResult {
|
||||
let user_id = self.require_user()?.id;
|
||||
match block_on(self.db.send(messages::MarkMessageSeen {
|
||||
message_id: msg.id,
|
||||
user_id,
|
||||
})) {
|
||||
Ok(Ok(count)) => Ok(Some(WsMsg::MessageMarkedSeen(msg.id, count))),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
Ok(None)
|
||||
let count = db_or_debug_and_return!(
|
||||
self,
|
||||
messages::MarkMessageSeen {
|
||||
message_id: msg.id,
|
||||
user_id,
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
);
|
||||
Ok(Some(WsMsg::MessageMarkedSeen(msg.id, count)))
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use {
|
||||
crate::{WebSocketActor, WsHandler, WsResult},
|
||||
crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||
database_actor as db,
|
||||
futures::executor::block_on,
|
||||
jirs_data::{UpdateProjectPayload, UserProject, WsMsg},
|
||||
@ -12,38 +12,21 @@ impl WsHandler<UpdateProjectPayload> for WebSocketActor {
|
||||
project_id,
|
||||
..
|
||||
} = self.require_user_project()?;
|
||||
match block_on(self.db.send(database_actor::projects::UpdateProject {
|
||||
project_id: *project_id,
|
||||
name: msg.name,
|
||||
url: msg.url,
|
||||
description: msg.description,
|
||||
category: msg.category,
|
||||
time_tracking: msg.time_tracking,
|
||||
})) {
|
||||
Ok(Ok(_)) => (),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
let _ = db_or_debug_and_return!(
|
||||
self,
|
||||
database_actor::projects::UpdateProject {
|
||||
project_id: *project_id,
|
||||
name: msg.name,
|
||||
url: msg.url,
|
||||
description: msg.description,
|
||||
category: msg.category,
|
||||
time_tracking: msg.time_tracking,
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
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);
|
||||
}
|
||||
};
|
||||
);
|
||||
let projects = db_or_debug_and_return!(
|
||||
self,
|
||||
database_actor::projects::LoadProjects { user_id: *user_id }
|
||||
);
|
||||
Ok(Some(WsMsg::ProjectsLoaded(projects)))
|
||||
}
|
||||
}
|
||||
@ -53,16 +36,7 @@ pub struct LoadProjects;
|
||||
impl WsHandler<LoadProjects> for WebSocketActor {
|
||||
fn handle_msg(&mut self, _msg: LoadProjects, _ctx: &mut Self::Context) -> WsResult {
|
||||
let user_id = self.require_user()?.id;
|
||||
match block_on(self.db.send(db::projects::LoadProjects { user_id })) {
|
||||
Ok(Ok(v)) => Ok(Some(WsMsg::ProjectsLoaded(v))),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
Ok(None)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{:?}", e);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
let v = db_or_debug_and_return!(self, db::projects::LoadProjects { user_id });
|
||||
Ok(Some(WsMsg::ProjectsLoaded(v)))
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use {
|
||||
crate::{WebSocketActor, WsHandler, WsResult},
|
||||
crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult},
|
||||
database_actor as db,
|
||||
futures::executor::block_on,
|
||||
jirs_data::{UserProjectId, WsMsg},
|
||||
@ -10,20 +10,8 @@ pub struct LoadUserProjects;
|
||||
impl WsHandler<LoadUserProjects> for WebSocketActor {
|
||||
fn handle_msg(&mut self, _msg: LoadUserProjects, _ctx: &mut Self::Context) -> WsResult {
|
||||
let user_id = self.require_user()?.id;
|
||||
match block_on(
|
||||
self.db
|
||||
.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)
|
||||
}
|
||||
}
|
||||
let v = db_or_debug_and_return!(self, db::user_projects::LoadUserProjects { user_id });
|
||||
Ok(Some(WsMsg::UserProjectsLoaded(v)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,22 +22,14 @@ pub struct SetCurrentUserProject {
|
||||
impl WsHandler<SetCurrentUserProject> for WebSocketActor {
|
||||
fn handle_msg(&mut self, msg: SetCurrentUserProject, _ctx: &mut Self::Context) -> WsResult {
|
||||
let user_id = self.require_user()?.id;
|
||||
match block_on(self.db.send(db::user_projects::ChangeCurrentUserProject {
|
||||
user_id,
|
||||
id: msg.id,
|
||||
})) {
|
||||
Ok(Ok(user_project)) => {
|
||||
self.current_user_project = Some(user_project.clone());
|
||||
Ok(Some(WsMsg::UserProjectCurrentChanged(user_project)))
|
||||
let user_project = db_or_debug_and_return!(
|
||||
self,
|
||||
db::user_projects::ChangeCurrentUserProject {
|
||||
user_id,
|
||||
id: msg.id,
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
Ok(None)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
);
|
||||
self.current_user_project = Some(user_project.clone());
|
||||
Ok(Some(WsMsg::UserProjectCurrentChanged(user_project)))
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
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},
|
||||
futures::executor::block_on,
|
||||
jirs_data::{UserId, UserProject, UserRole, WsMsg},
|
||||
@ -12,18 +14,8 @@ impl WsHandler<LoadProjectUsers> for WebSocketActor {
|
||||
use database_actor::users::LoadProjectUsers as Msg;
|
||||
|
||||
let project_id = self.require_user_project()?.project_id;
|
||||
let m = match block_on(self.db.send(Msg { project_id })) {
|
||||
Ok(Ok(v)) => Some(WsMsg::ProjectUsersLoaded(v)),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
Ok(m)
|
||||
let v = db_or_debug_and_return!(self, Msg { project_id });
|
||||
Ok(Some(WsMsg::ProjectUsersLoaded(v)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,26 +27,24 @@ pub struct Register {
|
||||
impl WsHandler<Register> for WebSocketActor {
|
||||
fn handle_msg(&mut self, msg: Register, ctx: &mut Self::Context) -> WsResult {
|
||||
let Register { name, email } = msg;
|
||||
let msg = match block_on(self.db.send(DbRegister {
|
||||
name: name.clone(),
|
||||
email: email.clone(),
|
||||
project_id: None,
|
||||
role: UserRole::Owner,
|
||||
})) {
|
||||
Ok(Ok(_)) => Some(WsMsg::SignUpSuccess),
|
||||
Ok(Err(_)) => Some(WsMsg::SignUpPairTaken),
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
let _ = db_or_debug_and_return!(
|
||||
self,
|
||||
DbRegister {
|
||||
name: name.clone(),
|
||||
email: email.clone(),
|
||||
project_id: None,
|
||||
role: UserRole::Owner,
|
||||
},
|
||||
Ok(Some(WsMsg::SignUpPairTaken)),
|
||||
Ok(None)
|
||||
);
|
||||
|
||||
match self.handle_msg(Authenticate { name, email }, ctx) {
|
||||
Ok(_) => (),
|
||||
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 {
|
||||
let user_id = self.require_user()?.id;
|
||||
|
||||
let users = match block_on(
|
||||
self.db
|
||||
.send(database_actor::users::LoadInvitedUsers { user_id }),
|
||||
) {
|
||||
Ok(Ok(users)) => users,
|
||||
_ => return Ok(None),
|
||||
};
|
||||
let users =
|
||||
db_or_debug_and_return!(self, database_actor::users::LoadInvitedUsers { user_id });
|
||||
|
||||
Ok(Some(WsMsg::InvitedUsersLoaded(users)))
|
||||
}
|
||||
@ -86,21 +71,14 @@ impl WsHandler<ProfileUpdate> for WebSocketActor {
|
||||
let user_id = self.require_user()?.id;
|
||||
let ProfileUpdate { name, email } = msg;
|
||||
|
||||
match block_on(self.db.send(database_actor::users::ProfileUpdate {
|
||||
user_id,
|
||||
name,
|
||||
email,
|
||||
})) {
|
||||
Ok(Ok(_users)) => (),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
let _ = db_or_debug_and_return!(
|
||||
self,
|
||||
database_actor::users::ProfileUpdate {
|
||||
user_id,
|
||||
name,
|
||||
email,
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
Ok(Some(WsMsg::ProfileUpdated))
|
||||
}
|
||||
@ -120,23 +98,14 @@ impl WsHandler<RemoveInvitedUser> for WebSocketActor {
|
||||
project_id,
|
||||
..
|
||||
} = self.require_user_project()?.clone();
|
||||
match block_on(
|
||||
self.db
|
||||
.send(database_actor::user_projects::RemoveInvitedUser {
|
||||
invited_id,
|
||||
inviter_id,
|
||||
project_id,
|
||||
}),
|
||||
) {
|
||||
Ok(Ok(_users)) => Ok(Some(WsMsg::InvitedUserRemoveSuccess(invited_id))),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
Ok(None)
|
||||
let _ = db_or_debug_and_return!(
|
||||
self,
|
||||
database_actor::user_projects::RemoveInvitedUser {
|
||||
invited_id,
|
||||
inviter_id,
|
||||
project_id,
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
);
|
||||
Ok(Some(WsMsg::InvitedUserRemoveSuccess(invited_id)))
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,72 @@
|
||||
#[macro_export]
|
||||
macro_rules! query_db_or_print {
|
||||
($s:expr,$msg:expr) => {
|
||||
match block_on($s.db.send($msg)) {
|
||||
macro_rules! db_or_debug_and_return {
|
||||
($s: ident, $msg: expr, $actor_err: expr, $mailbox_err: expr) => {
|
||||
$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(Err(e)) => {
|
||||
log::error!("{:?}", e);
|
||||
return Ok(None);
|
||||
return $actor_err;
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
return Ok(None);
|
||||
log::error!("{:?}", e);
|
||||
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",
|
||||
]
|
||||
|
||||
[dependencies.derive_enum_primitive]
|
||||
path = "../derive/derive_enum_primitive"
|
||||
|
||||
[dependencies.derive_enum_iter]
|
||||
path = "../derive/derive_enum_iter"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = { version = "*" }
|
||||
|
@ -10,7 +10,6 @@
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
font-size: 14.5px;
|
||||
padding: 0 12px;
|
||||
|
||||
&:focus {
|
||||
border-color: var(--borderInputFocus);
|
||||
@ -19,6 +18,16 @@
|
||||
> input[type=radio] {
|
||||
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 {
|
||||
|
@ -109,6 +109,30 @@
|
||||
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 {
|
||||
> section {
|
||||
> .header {
|
||||
|
@ -1,11 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
which rsass
|
||||
if [[ "$status" != "0" ]];
|
||||
RSASS_PATH=$(command -v rsass)
|
||||
if [[ "${RSASS_PATH}" == "" ]];
|
||||
then
|
||||
cargo install rsass --features=commandline
|
||||
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 CLIENT_ROOT=${PROJECT_ROOT}/jirs-client
|
||||
export HI_ROOT=${PROJECT_ROOT}/highlight/jirs-highlight
|
||||
|
@ -1,6 +1,6 @@
|
||||
use {
|
||||
crate::{
|
||||
shared::{ToChild, ToNode},
|
||||
shared::{IntoChild, ToNode},
|
||||
FieldId, Msg,
|
||||
},
|
||||
jirs_data::TimeTracking,
|
||||
@ -122,36 +122,51 @@ impl<'l> ToNode for ChildBuilder<'l> {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StyledCheckbox<'l> {
|
||||
pub struct StyledCheckbox<'l, Options>
|
||||
where
|
||||
Options: Iterator<Item = ChildBuilder<'l>>,
|
||||
{
|
||||
id: FieldId,
|
||||
options: Vec<ChildBuilder<'l>>,
|
||||
options: Option<Options>,
|
||||
selected: u32,
|
||||
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> {
|
||||
render(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'l> StyledCheckbox<'l> {
|
||||
pub fn build() -> StyledCheckboxBuilder<'l> {
|
||||
impl<'l, Options> StyledCheckbox<'l, Options>
|
||||
where
|
||||
Options: Iterator<Item = ChildBuilder<'l>>,
|
||||
{
|
||||
pub fn build() -> StyledCheckboxBuilder<'l, Options> {
|
||||
StyledCheckboxBuilder {
|
||||
options: vec![],
|
||||
options: None,
|
||||
selected: 0,
|
||||
class_list: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StyledCheckboxBuilder<'l> {
|
||||
options: Vec<ChildBuilder<'l>>,
|
||||
pub struct StyledCheckboxBuilder<'l, Options>
|
||||
where
|
||||
Options: Iterator<Item = ChildBuilder<'l>>,
|
||||
{
|
||||
options: Option<Options>,
|
||||
selected: u32,
|
||||
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 {
|
||||
self.selected = state.value;
|
||||
self
|
||||
@ -162,12 +177,12 @@ impl<'l> StyledCheckboxBuilder<'l> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn options(mut self, options: Vec<ChildBuilder<'l>>) -> Self {
|
||||
self.options = options;
|
||||
pub fn options(mut self, options: Options) -> Self {
|
||||
self.options = Some(options);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self, field_id: FieldId) -> StyledCheckbox<'l> {
|
||||
pub fn build(self, field_id: FieldId) -> StyledCheckbox<'l, Options> {
|
||||
StyledCheckbox {
|
||||
id: field_id,
|
||||
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 {
|
||||
id,
|
||||
options,
|
||||
@ -185,10 +203,12 @@ fn render(values: StyledCheckbox) -> Node<Msg> {
|
||||
class_list,
|
||||
} = values;
|
||||
|
||||
let opt: Vec<Node<Msg>> = options
|
||||
.into_iter()
|
||||
.map(|child| child.with_id(id.clone()).try_select(selected).into_node())
|
||||
.collect();
|
||||
let opt: Vec<Node<Msg>> = match options {
|
||||
Some(options) => options
|
||||
.map(|child| child.with_id(id.clone()).try_select(selected).into_node())
|
||||
.collect(),
|
||||
_ => vec![Node::Empty],
|
||||
};
|
||||
|
||||
div![
|
||||
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>;
|
||||
|
||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
||||
fn into_child(self) -> Self::Builder {
|
||||
Self::Builder::default()
|
||||
.label(match self {
|
||||
TimeTracking::Untracked => "No tracking",
|
||||
@ -212,11 +232,11 @@ impl<'l> ToChild<'l> for TimeTracking {
|
||||
TimeTracking::Fibonacci => "fibonacci",
|
||||
TimeTracking::Hourly => "hourly",
|
||||
})
|
||||
.value((*self).into())
|
||||
.add_class(match self {
|
||||
TimeTracking::Untracked => "untracked",
|
||||
TimeTracking::Fibonacci => "fibonacci",
|
||||
TimeTracking::Hourly => "hourly",
|
||||
})
|
||||
.value((self).into())
|
||||
}
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ pub enum Msg {
|
||||
AvatarUpdateFetched(String),
|
||||
|
||||
// modals
|
||||
ModalOpened(Box<ModalType>),
|
||||
ModalOpened(ModalType),
|
||||
ModalDropped,
|
||||
ModalChanged(FieldChange),
|
||||
|
||||
@ -280,11 +280,11 @@ fn resolve_page(url: Url) -> Option<Page> {
|
||||
|
||||
let page = match url.path()[0].as_ref() {
|
||||
"board" => Page::Project,
|
||||
"profile" => Page::Profile,
|
||||
"issues" => match url.path().get(1).as_ref().map(|s| s.parse::<i32>()) {
|
||||
Some(Ok(id)) => Page::EditIssue(id),
|
||||
_ => return None,
|
||||
},
|
||||
"profile" => Page::Profile,
|
||||
"add-issue" => Page::AddIssue,
|
||||
"project-settings" => Page::ProjectSettings,
|
||||
"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 {
|
||||
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);
|
||||
Self {
|
||||
epic_id,
|
||||
|
@ -1,30 +1,20 @@
|
||||
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,
|
||||
seed::prelude::*,
|
||||
};
|
||||
|
||||
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| {
|
||||
if let ModalType::DeleteEpic(modal) = modal {
|
||||
Some(modal)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
let modal = match &mut model.modals_mut().delete_epic {
|
||||
Some(modal) => modal,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
match msg {
|
||||
Msg::ModalDropped => {
|
||||
go_to_board(orders);
|
||||
}
|
||||
Msg::DeleteEpic => {
|
||||
send_ws_msg(WsMsg::EpicDelete(modal.epic_id), model.ws.as_ref(), orders);
|
||||
}
|
||||
Msg::ResourceChanged(ResourceKind::Epic, OperationKind::SingleRemoved, Some(_)) => {
|
||||
go_to_board(orders);
|
||||
orders.skip().send_msg(Msg::ModalDropped);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -1,19 +1,18 @@
|
||||
use crate::FieldId;
|
||||
use jirs_data::EpicFieldId;
|
||||
use {
|
||||
crate::{
|
||||
components::{styled_input::*, styled_select::StyledSelectState},
|
||||
model,
|
||||
components::{styled_checkbox::StyledCheckboxState, styled_input::*},
|
||||
model, FieldId, Msg,
|
||||
},
|
||||
jirs_data::{EpicId, IssueId},
|
||||
jirs_data::*,
|
||||
seed::prelude::Orders,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub struct Model {
|
||||
pub epic_id: EpicId,
|
||||
pub related_issues: Vec<IssueId>,
|
||||
pub name: StyledInputState,
|
||||
pub transform_into: StyledSelectState,
|
||||
pub transform_into: StyledCheckboxState,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
@ -23,6 +22,7 @@ impl Model {
|
||||
.get(&epic_id)
|
||||
.map(|epic| epic.name.as_str())
|
||||
.unwrap_or_default();
|
||||
|
||||
let related_issues = model
|
||||
.issues()
|
||||
.iter()
|
||||
@ -38,10 +38,15 @@ impl Model {
|
||||
epic_id,
|
||||
related_issues,
|
||||
name: StyledInputState::new(FieldId::EditEpic(EpicFieldId::Name), name),
|
||||
transform_into: StyledSelectState::new(
|
||||
FieldId::EditEpic(EpicFieldId::StartsAt),
|
||||
vec![],
|
||||
transform_into: StyledCheckboxState::new(
|
||||
FieldId::EditEpic(EpicFieldId::TransformInto),
|
||||
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>) {
|
||||
let _modal = crate::match_modal_mut!(model, DeleteEpic);
|
||||
pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
|
||||
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 {
|
||||
crate::{
|
||||
components::{styled_input::*, styled_modal::*},
|
||||
components::{
|
||||
styled_button::*, styled_checkbox::*, styled_icon::Icon, styled_input::*,
|
||||
styled_modal::*,
|
||||
},
|
||||
modals::epics_edit::Model,
|
||||
model,
|
||||
shared::ToNode,
|
||||
shared::{IntoChild, ToNode},
|
||||
FieldId, Msg,
|
||||
},
|
||||
jirs_data::EpicFieldId,
|
||||
jirs_data::{EpicFieldId, IssueType},
|
||||
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> {
|
||||
let transform = if modal.related_issues.is_empty() {
|
||||
Node::Empty
|
||||
transform_into_available(modal)
|
||||
} 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()
|
||||
.center()
|
||||
.child(h1!["Edit epic"])
|
||||
.width(600)
|
||||
.add_class("editEpic")
|
||||
.child(div![C!["header"], h1!["Edit epic"], close])
|
||||
.child(
|
||||
StyledInput::build()
|
||||
.state(&modal.name)
|
||||
.build(FieldId::EditEpic(EpicFieldId::Name))
|
||||
.into_node(),
|
||||
)
|
||||
@ -28,3 +58,32 @@ pub fn view(_model: &model::Model, modal: &Model) -> Node<Msg> {
|
||||
.build()
|
||||
.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,22 +1,14 @@
|
||||
use {
|
||||
crate::{
|
||||
modals::issue_statuses_delete::Model as DeleteIssueStatusModal,
|
||||
model::{ModalType, Model},
|
||||
Msg, OperationKind, ResourceKind,
|
||||
},
|
||||
crate::{model::Model, Msg, OperationKind, ResourceKind},
|
||||
jirs_data::WsMsg,
|
||||
seed::prelude::*,
|
||||
};
|
||||
|
||||
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
let _modal: &mut Box<DeleteIssueStatusModal> =
|
||||
match model.modals.iter_mut().find_map(|modal| match modal {
|
||||
ModalType::DeleteIssueStatusModal(modal) => Some(modal),
|
||||
_ => None,
|
||||
}) {
|
||||
Some(m) => m,
|
||||
_ => return,
|
||||
};
|
||||
let _modal = match &mut model.modals_mut().delete_issue_status_modal {
|
||||
Some(m) => m,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
match msg {
|
||||
Msg::DeleteIssueStatus(issue_status_id) => {
|
||||
|
@ -4,14 +4,16 @@ use {
|
||||
styled_date_time_input::*, styled_input::*, styled_select::*, styled_select_child::*,
|
||||
},
|
||||
model::IssueModal,
|
||||
shared::{ToChild, ToNode},
|
||||
shared::{IntoChild, ToNode},
|
||||
FieldId, Msg,
|
||||
},
|
||||
derive_enum_iter::EnumIter,
|
||||
derive_enum_primitive::EnumPrimitive,
|
||||
jirs_data::{IssueFieldId, IssuePriority},
|
||||
seed::prelude::*,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, EnumPrimitive, EnumIter)]
|
||||
pub enum Type {
|
||||
Task,
|
||||
Bug,
|
||||
@ -19,24 +21,13 @@ pub enum Type {
|
||||
Epic,
|
||||
}
|
||||
|
||||
impl From<u32> for Type {
|
||||
fn from(n: u32) -> Self {
|
||||
match n {
|
||||
0 => Type::Task,
|
||||
1 => Type::Bug,
|
||||
2 => Type::Story,
|
||||
3 => Type::Epic,
|
||||
_ => Type::Task,
|
||||
}
|
||||
impl Default for Type {
|
||||
fn default() -> Self {
|
||||
Self::Task
|
||||
}
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub(crate) fn ordered<'l>() -> &'l [Type] {
|
||||
use Type::*;
|
||||
&[Task, Bug, Story, Epic]
|
||||
}
|
||||
|
||||
pub(crate) fn submit_label(&self) -> &str {
|
||||
use Type::*;
|
||||
match self {
|
||||
@ -62,22 +53,17 @@ impl Type {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'l> ToChild<'l> for Type {
|
||||
impl<'l> IntoChild<'l> for Type {
|
||||
type Builder = StyledSelectChildBuilder<'l>;
|
||||
|
||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
||||
fn into_child(self) -> Self::Builder {
|
||||
let name = match self {
|
||||
Type::Task => "Task",
|
||||
Type::Bug => "Bug",
|
||||
Type::Story => "Story",
|
||||
Type::Epic => "Epic",
|
||||
};
|
||||
let value = match self {
|
||||
Type::Task => 0,
|
||||
Type::Bug => 1,
|
||||
Type::Story => 2,
|
||||
Type::Epic => 3,
|
||||
};
|
||||
let value: u32 = self.into();
|
||||
|
||||
let type_icon = {
|
||||
use crate::components::styled_icon::*;
|
||||
|
@ -1,8 +1,6 @@
|
||||
use {
|
||||
crate::{
|
||||
components::styled_select::StyledSelectChanged,
|
||||
model::{IssueModal, ModalType},
|
||||
ws::send_ws_msg,
|
||||
components::styled_select::StyledSelectChanged, model::IssueModal, ws::send_ws_msg,
|
||||
FieldId, Msg, OperationKind, ResourceKind,
|
||||
},
|
||||
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>) {
|
||||
let modal = model
|
||||
.modals
|
||||
.iter_mut()
|
||||
.find(|modal| matches!(modal, ModalType::AddIssue(..)));
|
||||
let modal = match modal {
|
||||
Some(ModalType::AddIssue(modal)) => modal,
|
||||
let user_id = model.user_id().unwrap_or_default();
|
||||
let project_id = model.project_id().unwrap_or_default();
|
||||
|
||||
let modal = match &mut model.modals_mut().add_issue {
|
||||
Some(modal) => modal,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
@ -30,8 +27,6 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
|
||||
);
|
||||
}
|
||||
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();
|
||||
match type_value {
|
||||
0 | 1 | 2 => {
|
||||
|
@ -24,7 +24,7 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
||||
.values
|
||||
.get(0)
|
||||
.cloned()
|
||||
.map(Type::from)
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|| Type::Task);
|
||||
|
||||
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())
|
||||
.opened(modal.type_state.opened)
|
||||
.valid(true)
|
||||
.options(Type::ordered().iter().map(|t| t.to_child().name("type")))
|
||||
.selected(vec![Type::from(
|
||||
modal.type_state.values.get(0).cloned().unwrap_or_default(),
|
||||
)
|
||||
.to_child()
|
||||
.options(Type::Task.into_iter().map(|t| t.into_child().name("type")))
|
||||
.selected(vec![{
|
||||
let v: Type = modal
|
||||
.type_state
|
||||
.values
|
||||
.get(0)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.into();
|
||||
v
|
||||
}
|
||||
.into_child()
|
||||
.name("type")])
|
||||
.build(FieldId::AddIssueModal(IssueFieldId::Type))
|
||||
.into_node();
|
||||
|
@ -1 +1,6 @@
|
||||
use jirs_data::IssueId;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Model {
|
||||
pub issue_id: IssueId,
|
||||
}
|
||||
|
@ -1,24 +1,12 @@
|
||||
use {
|
||||
crate::{
|
||||
components::styled_confirm_modal::StyledConfirmModal, model, model::ModalType,
|
||||
shared::ToNode, Msg,
|
||||
},
|
||||
seed::{prelude::*, *},
|
||||
crate::{components::styled_confirm_modal::StyledConfirmModal, model, shared::ToNode, Msg},
|
||||
seed::prelude::*,
|
||||
};
|
||||
|
||||
pub fn view(model: &model::Model) -> Node<Msg> {
|
||||
let opt_id = model
|
||||
.modals
|
||||
.iter()
|
||||
.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 issue_id = match &model.modals().delete_issue_confirm {
|
||||
Some(modal) => modal.issue_id,
|
||||
_ => return Node::Empty,
|
||||
};
|
||||
|
||||
let handle_issue_delete = mouse_ev(Ev::Click, move |_| Msg::DeleteIssue(issue_id));
|
||||
|
@ -9,13 +9,13 @@ use {
|
||||
model::{CommentForm, IssueModal},
|
||||
EditIssueModalSection, FieldId, Msg,
|
||||
},
|
||||
jirs_data::{EpicId, Issue, IssueFieldId, TimeTracking, UpdateIssuePayload},
|
||||
jirs_data::{Issue, IssueFieldId, IssueId, TimeTracking, UpdateIssuePayload},
|
||||
seed::prelude::*,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||
pub struct Model {
|
||||
pub id: EpicId,
|
||||
pub id: IssueId,
|
||||
pub link_copied: bool,
|
||||
pub payload: UpdateIssuePayload,
|
||||
pub top_type_state: StyledSelectState,
|
||||
|
@ -1,8 +1,7 @@
|
||||
use {
|
||||
crate::{
|
||||
components::styled_select::StyledSelectChanged,
|
||||
modals::issues_edit::Model as EditIssueModal,
|
||||
model::{IssueModal, ModalType, Model},
|
||||
model::{IssueModal, Model},
|
||||
ws::send_ws_msg,
|
||||
EditIssueModalSection, FieldChange, FieldId, Msg, OperationKind, ResourceKind,
|
||||
},
|
||||
@ -11,18 +10,22 @@ use {
|
||||
};
|
||||
|
||||
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
let modal: &mut EditIssueModal = match model.modals.get_mut(0) {
|
||||
Some(ModalType::EditIssue(_issue_id, modal)) => modal,
|
||||
let modal = match &mut model.modals.edit_issue {
|
||||
Some(modal) => modal,
|
||||
_ => return,
|
||||
};
|
||||
modal.update_states(msg, orders);
|
||||
|
||||
match msg {
|
||||
Msg::ResourceChanged(ResourceKind::Issue, OperationKind::SingleModified, Some(id)) => {
|
||||
if let Some(issue) = model.issues_by_id.get(id) {
|
||||
modal.payload = issue.clone().into();
|
||||
modal.description_state.initial_text =
|
||||
issue.description_text.clone().unwrap_or_default();
|
||||
let m = model.issues_by_id.get(id).cloned();
|
||||
if let Some(issue) = m {
|
||||
modal.description_state.initial_text = issue
|
||||
.description_text
|
||||
.as_deref()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
modal.payload = issue.into();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ use {
|
||||
components::{
|
||||
styled_avatar::StyledAvatar, styled_button::StyledButton, styled_editor::StyledEditor,
|
||||
styled_field::StyledField, styled_icon::Icon, styled_input::StyledInput,
|
||||
styled_select::StyledSelect,
|
||||
styled_modal::*, styled_select::StyledSelect,
|
||||
},
|
||||
modals::{
|
||||
epic_field, issues_edit::Model as EditIssueModal, time_tracking::time_tracking_field,
|
||||
@ -20,6 +20,21 @@ use {
|
||||
mod comments;
|
||||
|
||||
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![
|
||||
C!["issueDetails"],
|
||||
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| {
|
||||
ev.prevent_default();
|
||||
ev.stop_propagation();
|
||||
seed::Url::new().add_path_part("board").go_and_push();
|
||||
|
||||
Msg::ModalDropped
|
||||
});
|
||||
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()
|
||||
|
@ -66,7 +66,7 @@ pub fn comment(model: &Model, modal: &EditIssueModal, comment: &Comment) -> Opti
|
||||
let comment_id = comment.id;
|
||||
let delete_comment_handler = mouse_ev(Ev::Click, move |ev| {
|
||||
ev.stop_propagation();
|
||||
Msg::ModalOpened(Box::new(ModalType::DeleteCommentConfirm(comment_id)))
|
||||
Msg::ModalOpened(ModalType::DeleteCommentConfirm(Some(comment_id)))
|
||||
});
|
||||
let edit_button = StyledButton::build()
|
||||
.add_class("editButton")
|
||||
|
@ -1,5 +1,6 @@
|
||||
pub use {epic_field::*, update::*, view::*};
|
||||
|
||||
pub mod comments_delete;
|
||||
#[cfg(debug_assertions)]
|
||||
pub mod debug;
|
||||
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_select::{StyledSelect, StyledSelectState},
|
||||
},
|
||||
model::{ModalType, Model},
|
||||
model::Model,
|
||||
shared::{
|
||||
tracking_widget::{fibonacci_values, tracking_widget},
|
||||
ToChild, ToNode,
|
||||
},
|
||||
EditIssueModalSection, FieldId, Msg,
|
||||
},
|
||||
jirs_data::{EpicId, IssueFieldId, TimeTracking},
|
||||
jirs_data::{IssueFieldId, IssueId, TimeTracking},
|
||||
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() {
|
||||
return Node::Empty;
|
||||
}
|
||||
|
||||
let edit_issue_modal = match model.modals.get(0) {
|
||||
Some(ModalType::EditIssue(_, modal)) => modal,
|
||||
_ => return empty![],
|
||||
let edit_issue_modal = match &model.modals().edit_issue {
|
||||
Some(modal) => modal,
|
||||
_ => return Node::Empty,
|
||||
};
|
||||
|
||||
let time_tracking_type = model
|
||||
.project
|
||||
.as_ref()
|
||||
.map(|p| p.time_tracking)
|
||||
.unwrap_or_else(|| TimeTracking::Untracked);
|
||||
.unwrap_or(TimeTracking::Untracked);
|
||||
|
||||
let modal_title = div![C!["modalTitle"], "Time tracking"];
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
use jirs_data::{CommentId, IssueStatusId};
|
||||
use {
|
||||
crate::{
|
||||
model::{ModalType, Model, Page},
|
||||
@ -11,47 +12,37 @@ use {
|
||||
|
||||
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
match msg {
|
||||
Msg::ModalDropped => match model.modals.pop() {
|
||||
Some(ModalType::EditIssue(..)) | Some(ModalType::AddIssue(..)) => {
|
||||
go_to_board(orders);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Msg::ModalDropped if !model.modal_stack().is_empty() => {
|
||||
drop_modal(model, orders);
|
||||
}
|
||||
|
||||
Msg::ModalChanged(FieldChange::LinkCopied(FieldId::CopyButtonLabel, true)) => {
|
||||
for modal in model.modals.iter_mut() {
|
||||
if let ModalType::EditIssue(_, edit) = modal {
|
||||
edit.link_copied = true;
|
||||
}
|
||||
if let Some(edit) = &mut model.modals_mut().edit_issue {
|
||||
edit.link_copied = true;
|
||||
}
|
||||
}
|
||||
|
||||
Msg::ModalOpened(modal_type) => {
|
||||
model.modals.push(modal_type.as_ref().clone());
|
||||
push_modal(modal_type, model, orders);
|
||||
}
|
||||
|
||||
Msg::ResourceChanged(ResourceKind::Issue, OperationKind::ListLoaded, _) => {
|
||||
match model.page {
|
||||
Page::EditIssue(issue_id) if model.modals.is_empty() => {
|
||||
push_edit_modal(issue_id, model, orders)
|
||||
}
|
||||
Page::AddIssue if model.modals.is_empty() => push_add_modal(model, orders),
|
||||
Page::EditIssue(issue_id) => push_edit_issue_modal(issue_id, model, orders),
|
||||
Page::AddIssue => push_add_issue_modal(model, orders),
|
||||
Page::DeleteEpic(id) => push_delete_epic_modal(id, 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::AddIssue) => push_add_modal(model, orders),
|
||||
Msg::ChangePage(Page::DeleteEpic(issue_id)) => {
|
||||
push_delete_epic_modal(*issue_id, model, orders)
|
||||
}
|
||||
Msg::ChangePage(Page::EditEpic(issue_id)) => push_edit_epic_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::DeleteEpic(id)) => push_delete_epic_modal(*id, model, orders),
|
||||
Msg::ChangePage(Page::EditEpic(id)) => push_edit_epic_modal(*id, model, orders),
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
Msg::GlobalKeyDown { key, .. } if key.eq("#") => {
|
||||
model.modals.push(ModalType::DebugModal);
|
||||
}
|
||||
Msg::GlobalKeyDown { key, .. } if key.eq("#") => push_debug_modal(model),
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
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>) {
|
||||
use crate::modals::issues_create::Model;
|
||||
model.modals.push(ModalType::AddIssue(Box::new(Model {
|
||||
project_id: model.project.as_ref().map(|p| p.id),
|
||||
..Model::default()
|
||||
})));
|
||||
// MODALS
|
||||
|
||||
fn push_modal(modal_type: &ModalType, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
match modal_type {
|
||||
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
|
||||
.project
|
||||
.as_ref()
|
||||
.map(|p| p.time_tracking)
|
||||
.unwrap_or(TimeTracking::Untracked);
|
||||
|
||||
let modal = {
|
||||
let issue = match model.issues_by_id.get(&issue_id) {
|
||||
Some(issue) => issue,
|
||||
_ => return,
|
||||
};
|
||||
ModalType::EditIssue(
|
||||
issue_id,
|
||||
Box::new(crate::modals::issues_edit::Model::new(
|
||||
issue,
|
||||
time_tracking_type,
|
||||
)),
|
||||
)
|
||||
crate::modals::issues_edit::Model::new(issue, time_tracking_type)
|
||||
};
|
||||
send_ws_msg(
|
||||
WsMsg::IssueCommentsLoad(issue_id),
|
||||
model.ws.as_ref(),
|
||||
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;
|
||||
let modal = Model::new(epic_id, model);
|
||||
model.modals.push(ModalType::EditEpic(Box::new(modal)));
|
||||
if model.modals().edit_epic.is_some() {
|
||||
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;
|
||||
let modal = Model::new(issue_id, model);
|
||||
model.modals.push(ModalType::DeleteEpic(Box::new(modal)));
|
||||
if model.modals_mut().delete_epic.is_some() {
|
||||
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 {
|
||||
crate::{
|
||||
components::{styled_confirm_modal::StyledConfirmModal, styled_modal::StyledModal},
|
||||
model::*,
|
||||
shared::ToNode,
|
||||
Msg,
|
||||
},
|
||||
crate::{model::*, Msg},
|
||||
seed::{prelude::*, *},
|
||||
};
|
||||
|
||||
pub fn view(model: &Model) -> Node<Msg> {
|
||||
use crate::modals::{issue_statuses_delete, issues_create, issues_edit};
|
||||
let modals: Vec<Node<Msg>> = model
|
||||
.modals
|
||||
.iter()
|
||||
.map(|modal| match modal {
|
||||
// epic
|
||||
ModalType::DeleteEpic(modal) => crate::modals::epics_delete::view(model, modal),
|
||||
ModalType::EditEpic(modal) => crate::modals::epics_edit::view(model, modal),
|
||||
// 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![]
|
||||
let mut nodes = Vec::with_capacity(model.modal_stack().len());
|
||||
|
||||
for modal_type in model.modal_stack() {
|
||||
match modal_type {
|
||||
ModalType::AddIssue(_) => {
|
||||
if let Some(modal) = &model.modals().add_issue {
|
||||
let node = crate::modals::issues_create::view(model, modal);
|
||||
nodes.push(node);
|
||||
}
|
||||
}
|
||||
ModalType::DeleteIssueConfirm(_id) => crate::modals::issues_delete::view(model),
|
||||
ModalType::AddIssue(modal) => issues_create::view(model, modal),
|
||||
// comment
|
||||
ModalType::DeleteCommentConfirm(comment_id) => {
|
||||
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::EditIssue(_) => {
|
||||
if let Some(modal) = &model.modals().edit_issue {
|
||||
let node = crate::modals::issues_edit::view(model, modal);
|
||||
nodes.push(node);
|
||||
}
|
||||
}
|
||||
ModalType::TimeTracking(issue_id) => {
|
||||
crate::modals::time_tracking::view(model, *issue_id)
|
||||
ModalType::DeleteEpic(_) => {
|
||||
if let Some(modal) = &model.modals().delete_epic {
|
||||
let node = crate::modals::epics_delete::view(model, modal);
|
||||
nodes.push(node);
|
||||
}
|
||||
}
|
||||
ModalType::DeleteIssueStatusModal(delete_issue_modal) => {
|
||||
issue_statuses_delete::view(model, delete_issue_modal.delete_id)
|
||||
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)]
|
||||
ModalType::DebugModal => crate::modals::debug::view(model),
|
||||
})
|
||||
.collect();
|
||||
section![id!["modals"], modals]
|
||||
ModalType::DebugModal(_) => {
|
||||
if let Some(true) = &model.modals().debug_modal {
|
||||
let node = crate::modals::debug::view(model);
|
||||
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>);
|
||||
}
|
||||
|
||||
#[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 {
|
||||
// issue
|
||||
AddIssue(Box<crate::modals::issues_create::Model>),
|
||||
EditIssue(EpicId, Box<crate::modals::issues_edit::Model>),
|
||||
AddIssue(Option<i32>),
|
||||
EditIssue(Option<i32>),
|
||||
DeleteIssueConfirm(Option<i32>),
|
||||
// epic
|
||||
DeleteEpic(Box<crate::modals::epics_delete::Model>),
|
||||
EditEpic(Box<crate::modals::epics_edit::Model>),
|
||||
DeleteEpic(Option<i32>),
|
||||
EditEpic(Option<i32>),
|
||||
|
||||
DeleteIssueConfirm(EpicId),
|
||||
DeleteCommentConfirm(CommentId),
|
||||
TimeTracking(EpicId),
|
||||
DeleteIssueStatusModal(Box<crate::modals::issue_statuses_delete::Model>),
|
||||
DeleteCommentConfirm(Option<i32>),
|
||||
TimeTracking(Option<i32>),
|
||||
DeleteIssueStatusModal(Option<i32>),
|
||||
#[cfg(debug_assertions)]
|
||||
DebugModal,
|
||||
DebugModal(Option<i32>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||
@ -168,7 +185,8 @@ pub struct Model {
|
||||
pub comment_form: Option<CreateCommentForm>,
|
||||
|
||||
// modals
|
||||
pub modals: Vec<ModalType>,
|
||||
modals_stack: Vec<ModalType>,
|
||||
pub modals: Modals,
|
||||
|
||||
// pages
|
||||
pub page: Page,
|
||||
@ -226,7 +244,6 @@ impl Model {
|
||||
host_url,
|
||||
ws_url,
|
||||
page_content: PageContent::Project(Box::new(ProjectPage::default())),
|
||||
modals: vec![],
|
||||
project: None,
|
||||
current_user_project: None,
|
||||
about_tooltip_visible: false,
|
||||
@ -246,6 +263,8 @@ impl Model {
|
||||
issues_by_id: Default::default(),
|
||||
show_extras: false,
|
||||
epics_by_id: Default::default(),
|
||||
modals_stack: vec![],
|
||||
modals: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,6 +293,16 @@ impl Model {
|
||||
&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 {
|
||||
self.current_user_project
|
||||
.as_ref()
|
||||
@ -293,4 +322,20 @@ impl Model {
|
||||
})
|
||||
.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 {
|
||||
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(
|
||||
page: &ProjectPage,
|
||||
epics: &[Epic],
|
||||
|
@ -1,7 +1,7 @@
|
||||
use {
|
||||
crate::{
|
||||
components::styled_select::StyledSelectChanged,
|
||||
model::{ModalType, Model, Page, PageContent},
|
||||
model::{Model, Page, PageContent},
|
||||
pages::project_page::model::ProjectPage,
|
||||
ws::{board_load, send_ws_msg},
|
||||
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)),
|
||||
StyledSelectChanged::Text(text),
|
||||
) => {
|
||||
let modal = model
|
||||
.modals
|
||||
.iter_mut()
|
||||
.filter_map(|modal| match modal {
|
||||
ModalType::EditIssue(_, modal) => Some(modal),
|
||||
_ => None,
|
||||
})
|
||||
.last();
|
||||
if let Some(m) = modal {
|
||||
if let Some(m) = &mut model.modals_mut().edit_issue {
|
||||
m.top_type_state.text_filter = text;
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,9 @@ use {
|
||||
styled_select::StyledSelect,
|
||||
styled_textarea::StyledTextarea,
|
||||
},
|
||||
modals::issue_statuses_delete::Model as DeleteIssueStatusModal,
|
||||
model::{self, ModalType, Model, PageContent},
|
||||
pages::project_settings_page::ProjectSettingsPage,
|
||||
shared::{inner_layout, IntoChild, ToChild, ToNode},
|
||||
shared::{inner_layout, IntoChild, ToNode},
|
||||
FieldId, Msg, PageChanged, ProjectFieldId, ProjectPageChange,
|
||||
},
|
||||
jirs_data::{IssueStatus, ProjectCategory, TimeTracking},
|
||||
@ -51,11 +50,11 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
||||
let category_field = category_field(page);
|
||||
|
||||
let time_tracking = StyledCheckbox::build()
|
||||
.options(vec![
|
||||
TimeTracking::Untracked.to_child(),
|
||||
TimeTracking::Fibonacci.to_child(),
|
||||
TimeTracking::Hourly.to_child(),
|
||||
])
|
||||
.options(
|
||||
TimeTracking::default()
|
||||
.into_iter()
|
||||
.map(|tt| tt.into_child()),
|
||||
)
|
||||
.state(&page.time_tracking)
|
||||
.add_class("timeTracking")
|
||||
.build(FieldId::ProjectSettings(ProjectFieldId::TimeTracking))
|
||||
@ -336,9 +335,7 @@ fn show_column_preview(
|
||||
let on_delete = mouse_ev(Ev::Click, move |ev| {
|
||||
ev.prevent_default();
|
||||
ev.stop_propagation();
|
||||
Msg::ModalOpened(Box::new(ModalType::DeleteIssueStatusModal(Box::new(
|
||||
DeleteIssueStatusModal::new(id),
|
||||
))))
|
||||
Msg::ModalOpened(ModalType::DeleteIssueStatusModal(Some(id)))
|
||||
});
|
||||
let delete = StyledButton::build()
|
||||
.primary()
|
||||
|
@ -21,7 +21,7 @@ pub fn tracking_link(model: &Model, modal: &crate::modals::issues_edit::Model) -
|
||||
let issue_id = *id;
|
||||
|
||||
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),]
|
||||
|
@ -276,8 +276,8 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
}
|
||||
// comments
|
||||
WsMsg::IssueCommentsLoaded(mut comments) => {
|
||||
let issue_id = match model.modals.get(0) {
|
||||
Some(ModalType::EditIssue(issue_id, _)) => *issue_id,
|
||||
let issue_id = match &model.modals().edit_issue {
|
||||
Some(modal) => modal.id,
|
||||
_ => return,
|
||||
};
|
||||
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
|
||||
);
|
||||
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(
|
||||
title,
|
||||
issue_type,
|
||||
@ -86,7 +97,8 @@ insert into issues(
|
||||
description_text,
|
||||
reporter_id,
|
||||
project_id,
|
||||
issue_status_id
|
||||
issue_status_id,
|
||||
epic_id
|
||||
) values (
|
||||
'Foo',
|
||||
'task',
|
||||
@ -96,7 +108,8 @@ insert into issues(
|
||||
'foz baz',
|
||||
1,
|
||||
1,
|
||||
1
|
||||
1,
|
||||
NULL
|
||||
), (
|
||||
'Foo2',
|
||||
'bug',
|
||||
@ -106,7 +119,8 @@ insert into issues(
|
||||
'foz baz 2',
|
||||
1,
|
||||
1,
|
||||
2
|
||||
2,
|
||||
NULL
|
||||
), (
|
||||
'Foo3',
|
||||
'story',
|
||||
@ -116,7 +130,30 @@ insert into issues(
|
||||
'foz baz 3',
|
||||
2,
|
||||
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 (
|
||||
1, 1, 'Vestibulum non neque at dui maximus porttitor fermentum consectetur eros.'
|
||||
|
Loading…
Reference in New Issue
Block a user