Assignee field display
This commit is contained in:
parent
e6778db137
commit
f824694a05
@ -1,178 +1,229 @@
|
||||
#issuesAndFilters {
|
||||
display: block;
|
||||
display: block;
|
||||
|
||||
> .filters {
|
||||
> .filters {
|
||||
margin: {
|
||||
top: 5px;
|
||||
bottom: 5px;
|
||||
}
|
||||
|
||||
> .pseudoInput {
|
||||
display: flex;
|
||||
|
||||
> .part {
|
||||
display: flex;
|
||||
line-height: 24px;
|
||||
background: var(--borderLightest);
|
||||
padding: 8px 14px;
|
||||
cursor: pointer;
|
||||
margin: {
|
||||
top: 5px;
|
||||
bottom: 5px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
> span {
|
||||
word-wrap: break-spaces;
|
||||
word-break: keep-all;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
> .pseudoInput {
|
||||
display: flex;
|
||||
&:hover {
|
||||
> .styledButton {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
> .styledButton {
|
||||
margin: {
|
||||
left: 5px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
};
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
background: var(--borderLightest);
|
||||
|
||||
> .styledIcon {
|
||||
color: var(----secondary);
|
||||
background: var(--borderLightest);
|
||||
}
|
||||
|
||||
display: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--borderInputFocus);
|
||||
background: var(--borderLightest);
|
||||
|
||||
> .styledIcon {
|
||||
line-height: 39px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
> .styledSelect {
|
||||
display: inline;
|
||||
min-width: 20px;
|
||||
|
||||
> .valueContainer {
|
||||
min-height: 100%;
|
||||
}
|
||||
color: var(----secondary);
|
||||
background: var(--borderLightest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .container {
|
||||
> .styledIcon {
|
||||
line-height: 39px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
> .styledSelect {
|
||||
display: inline;
|
||||
min-width: 20px;
|
||||
|
||||
> .valueContainer {
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
--listWidth: 338px;
|
||||
|
||||
> .issueInfo {
|
||||
width: calc(100% - var(--listWidth));
|
||||
padding: {
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
top: 5px;
|
||||
bottom: 5px;
|
||||
};
|
||||
|
||||
> .header {
|
||||
display: grid;
|
||||
grid-template-areas: "icon link" "icon name";
|
||||
grid-template-columns: 48px;
|
||||
|
||||
> .logo {
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
> .path {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
> span {
|
||||
margin: 0 5px;
|
||||
font-family: var(--font-medium);
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
line-height: 2;
|
||||
appearance: none;
|
||||
cursor: none;
|
||||
user-select: none;
|
||||
font-size: 14.5px;
|
||||
}
|
||||
|
||||
> .styledLink, > span {
|
||||
color: var(--textLink);
|
||||
|
||||
> span {
|
||||
color: var(--textLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .title {
|
||||
}
|
||||
}
|
||||
|
||||
> .issueBody {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
--listWidth: 338px;
|
||||
> .details {
|
||||
list-style: none;
|
||||
|
||||
> .issueInfo {
|
||||
width: calc(100% - var(--listWidth));
|
||||
padding: {
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
top: 5px;
|
||||
bottom: 5px;
|
||||
};
|
||||
> .line {
|
||||
--lineWidth: 460px;
|
||||
--nameWidth: 150px;
|
||||
|
||||
> .header {
|
||||
display: grid;
|
||||
grid-template-areas: "icon link" "icon name";
|
||||
grid-template-columns: 48px;
|
||||
|
||||
> .logo {
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
> .path {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
> span {
|
||||
margin: 0 5px;
|
||||
font-family: var(--font-medium);
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
line-height: 2;
|
||||
appearance: none;
|
||||
cursor: none;
|
||||
user-select: none;
|
||||
font-size: 14.5px;
|
||||
}
|
||||
|
||||
> .styledLink, > span {
|
||||
color: var(--textLink);
|
||||
|
||||
> span {
|
||||
color: var(--textLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .title {
|
||||
}
|
||||
}
|
||||
|
||||
> .issueBody {
|
||||
display: flex;
|
||||
|
||||
> .details {
|
||||
list-style: none;
|
||||
|
||||
> .line {
|
||||
--lineWidth: 460px;
|
||||
--nameWidth: 150px;
|
||||
|
||||
list-style: none;
|
||||
width: var(--lineWidth);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: {
|
||||
top: 2px;
|
||||
bottom: 2px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
};
|
||||
font-size: 14px;
|
||||
|
||||
> .detailsTitle {
|
||||
color: var(--textLight);
|
||||
width: var(--nameWidth);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
> .detailsValue {
|
||||
width: calc(var(--lineWidth) - var(--nameWidth));
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .issuesList {
|
||||
width: var(--listWidth);
|
||||
list-style: none;
|
||||
width: var(--lineWidth);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: {
|
||||
top: 2px;
|
||||
bottom: 2px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
};
|
||||
font-size: 14px;
|
||||
|
||||
> .listItem {
|
||||
list-style: none;
|
||||
|
||||
> .issue {
|
||||
display: grid;
|
||||
grid-template-areas: "type number" "priority name";
|
||||
grid-template-columns: 32px auto;
|
||||
border: {
|
||||
bottom: 1px solid var(--borderLight);
|
||||
}
|
||||
padding: {
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
top: 5px;
|
||||
bottom: 5px;
|
||||
};
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
background-color: var(--backgroundLightPrimary);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--backgroundLightest);
|
||||
|
||||
&.active {
|
||||
background-color: var(--backgroundLightPrimary);
|
||||
}
|
||||
}
|
||||
|
||||
> .type {
|
||||
grid-area: type;
|
||||
}
|
||||
|
||||
> .number {
|
||||
grid-area: number;
|
||||
|
||||
> .issueLink {
|
||||
display: inline;
|
||||
cursor: alias;
|
||||
}
|
||||
}
|
||||
|
||||
> .priority {
|
||||
grid-area: priority;
|
||||
}
|
||||
|
||||
> .name {
|
||||
grid-area: name;
|
||||
}
|
||||
}
|
||||
> .detailsTitle {
|
||||
color: var(--textLight);
|
||||
width: var(--nameWidth);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
> .detailsValue {
|
||||
width: calc(var(--lineWidth) - var(--nameWidth));
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .issuesList {
|
||||
width: var(--listWidth);
|
||||
list-style: none;
|
||||
|
||||
> .listItem {
|
||||
list-style: none;
|
||||
|
||||
> .issue {
|
||||
display: grid;
|
||||
grid-template-areas: "type number" "priority name";
|
||||
grid-template-columns: 32px auto;
|
||||
border: {
|
||||
bottom: 1px solid var(--borderLight);
|
||||
}
|
||||
padding: {
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
top: 5px;
|
||||
bottom: 5px;
|
||||
};
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
background-color: var(--backgroundLightPrimary);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--backgroundLightest);
|
||||
|
||||
&.active {
|
||||
background-color: var(--backgroundLightPrimary);
|
||||
}
|
||||
}
|
||||
|
||||
> .type {
|
||||
grid-area: type;
|
||||
}
|
||||
|
||||
> .number {
|
||||
grid-area: number;
|
||||
|
||||
> .issueLink {
|
||||
display: inline;
|
||||
cursor: alias;
|
||||
}
|
||||
}
|
||||
|
||||
> .priority {
|
||||
grid-area: priority;
|
||||
}
|
||||
|
||||
> .name {
|
||||
grid-area: name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ impl StyledDateTimeInput {
|
||||
let calendar_start = StyledDateTimeInput::calendar_start(start);
|
||||
let calendar_end = StyledDateTimeInput::calendar_end(end);
|
||||
let current_month_range = start..=end;
|
||||
let current = calendar_start;
|
||||
// let current = calendar_start;
|
||||
let mut weeks = vec![];
|
||||
let mut current_week = vec![];
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use jirs_data::{Issue, IssueId};
|
||||
use jirs_data::{Issue, IssueId, UserId, UsernameString};
|
||||
use seed::app::Orders;
|
||||
|
||||
use crate::components::styled_select::StyledSelectState;
|
||||
@ -9,7 +9,7 @@ pub struct IssuesAndFiltersPage {
|
||||
pub visible_issues: Vec<IssueId>,
|
||||
pub active_id: Option<IssueId>,
|
||||
pub current_jql_part: StyledSelectState,
|
||||
pub jql_parts: Vec<String>,
|
||||
pub jql_parts: Vec<JqlPart>,
|
||||
}
|
||||
|
||||
impl IssuesAndFiltersPage {
|
||||
@ -37,6 +37,24 @@ impl IssuesAndFiltersPage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum JqlPart {
|
||||
Field(FieldOption),
|
||||
Op(OpOption),
|
||||
Value(JqlValueOption),
|
||||
}
|
||||
|
||||
impl JqlPart {
|
||||
pub fn to_label(&self) -> &str {
|
||||
match self {
|
||||
JqlPart::Field(f) => f.to_label(),
|
||||
JqlPart::Op(op) => op.to_label(),
|
||||
JqlPart::Value(v) => v.to_label(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FieldOption {
|
||||
None,
|
||||
Assignee,
|
||||
@ -45,14 +63,14 @@ pub enum FieldOption {
|
||||
impl FieldOption {
|
||||
pub fn to_label(&self) -> &'static str {
|
||||
match self {
|
||||
FieldOption::None => "none",
|
||||
FieldOption::None => " ",
|
||||
FieldOption::Assignee => "Assignee",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_name(&self) -> &'static str {
|
||||
match self {
|
||||
FieldOption::None => " ",
|
||||
FieldOption::None => "none",
|
||||
FieldOption::Assignee => "assignee",
|
||||
}
|
||||
}
|
||||
@ -73,3 +91,71 @@ impl From<u32> for FieldOption {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OpOption {
|
||||
None,
|
||||
Eq,
|
||||
NotEq,
|
||||
}
|
||||
|
||||
impl OpOption {
|
||||
pub fn to_label(&self) -> &'static str {
|
||||
match self {
|
||||
OpOption::None => " ",
|
||||
OpOption::Eq => "=",
|
||||
OpOption::NotEq => "!=",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_name(&self) -> &'static str {
|
||||
match self {
|
||||
OpOption::None => "none",
|
||||
OpOption::Eq => "equal",
|
||||
OpOption::NotEq => "notEqual",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_value(&self) -> u32 {
|
||||
match self {
|
||||
OpOption::None => 0,
|
||||
OpOption::Eq => 1,
|
||||
OpOption::NotEq => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for OpOption {
|
||||
fn from(n: u32) -> Self {
|
||||
match n {
|
||||
1 => OpOption::Eq,
|
||||
2 => OpOption::NotEq,
|
||||
_ => OpOption::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum JqlValueOption {
|
||||
User(UserId, UsernameString),
|
||||
}
|
||||
|
||||
impl JqlValueOption {
|
||||
pub fn to_label(&self) -> &str {
|
||||
match self {
|
||||
JqlValueOption::User(_id, name) => name.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_name(&self) -> &'static str {
|
||||
match self {
|
||||
JqlValueOption::User(_, _) => "user",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_value(&self) -> u32 {
|
||||
match self {
|
||||
JqlValueOption::User(id, _) => (*id) as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use seed::prelude::*;
|
||||
|
||||
use crate::components::styled_select::StyledSelectChanged;
|
||||
use crate::model::{Model, PageContent};
|
||||
use crate::pages::issues_and_filters::FieldOption;
|
||||
use crate::pages::issues_and_filters::{FieldOption, JqlPart, JqlValueOption, OpOption};
|
||||
use crate::ws::board_load;
|
||||
use crate::{FieldId, IssuesAndFiltersId, Msg, OperationKind, Page, ResourceKind};
|
||||
|
||||
@ -55,10 +55,20 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
||||
match page.jql_parts.len() % 3 {
|
||||
0 => {
|
||||
let field = FieldOption::from(n);
|
||||
page.jql_parts.push(field.to_label().to_string());
|
||||
page.jql_parts.push(JqlPart::Field(field));
|
||||
}
|
||||
1 => {
|
||||
let field = OpOption::from(n);
|
||||
page.jql_parts.push(JqlPart::Op(field));
|
||||
}
|
||||
2 => {
|
||||
let u = match model.users.get(n as usize) {
|
||||
Some(u) => u,
|
||||
_ => return,
|
||||
};
|
||||
let field = JqlValueOption::User(u.id, u.name.clone());
|
||||
page.jql_parts.push(JqlPart::Value(field));
|
||||
}
|
||||
1 => {}
|
||||
2 => {}
|
||||
_ => {}
|
||||
}
|
||||
page.current_jql_part.reset();
|
||||
|
@ -1,19 +1,21 @@
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
|
||||
use crate::components::styled_button::ButtonVariant;
|
||||
use crate::components::styled_icon::{Icon, StyledIcon};
|
||||
use crate::components::styled_select::{SelectVariant, StyledSelect};
|
||||
use crate::components::styled_select_child::StyledSelectOption;
|
||||
use crate::model::Model;
|
||||
use crate::pages::issues_and_filters::{FieldOption, IssuesAndFiltersPage};
|
||||
use crate::pages::issues_and_filters::{FieldOption, IssuesAndFiltersPage, JqlPart};
|
||||
use crate::styled_button::StyledButton;
|
||||
use crate::Msg;
|
||||
|
||||
pub fn filters(_model: &Model, page: &IssuesAndFiltersPage) -> Node<Msg> {
|
||||
let search_input = pseudo_input(page);
|
||||
pub fn filters(model: &Model, page: &IssuesAndFiltersPage) -> Node<Msg> {
|
||||
let search_input = pseudo_input(model, page);
|
||||
div![C!["filters"], search_input]
|
||||
}
|
||||
|
||||
fn pseudo_input(page: &IssuesAndFiltersPage) -> Node<Msg> {
|
||||
fn pseudo_input(model: &Model, page: &IssuesAndFiltersPage) -> Node<Msg> {
|
||||
let input = StyledSelect {
|
||||
id: page.current_jql_part.field_id(),
|
||||
text_filter: page.current_jql_part.text_filter.as_str(),
|
||||
@ -26,9 +28,10 @@ fn pseudo_input(page: &IssuesAndFiltersPage) -> Node<Msg> {
|
||||
.current_jql_part
|
||||
.values
|
||||
.iter()
|
||||
.map(|n| field_select_option(*n))
|
||||
.enumerate()
|
||||
.map(|(idx, n)| field_select_option(model, idx, *n, &page.jql_parts))
|
||||
.collect(),
|
||||
options: Some(vec![field_select_option(1)].into_iter()),
|
||||
options: Some(options(model, &page.jql_parts).into_iter()),
|
||||
is_multi: false,
|
||||
opened: page.current_jql_part.opened,
|
||||
}
|
||||
@ -36,12 +39,81 @@ fn pseudo_input(page: &IssuesAndFiltersPage) -> Node<Msg> {
|
||||
div![
|
||||
C!["pseudoInput"],
|
||||
StyledIcon::from(Icon::Search).render(),
|
||||
page.jql_parts
|
||||
.iter()
|
||||
.map(|s| s.to_label())
|
||||
.map(removable_part)
|
||||
.collect::<Vec<Node<Msg>>>(),
|
||||
input
|
||||
]
|
||||
}
|
||||
|
||||
fn field_select_option<'l>(n: u32) -> StyledSelectOption<'l> {
|
||||
let v = FieldOption::from(n);
|
||||
fn options<'l, 'm: 'l>(model: &'m Model, parts: &[JqlPart]) -> Vec<StyledSelectOption<'l>> {
|
||||
log::info!("{} {:?}", parts.len(), parts);
|
||||
if parts.is_empty() {
|
||||
vec![field_select_option(model, 0, 1, &parts)]
|
||||
} else if parts.len() % 3 == 2
|
||||
&& matches!(
|
||||
parts.get(parts.len() - 2),
|
||||
Some(JqlPart::Field(FieldOption::Assignee))
|
||||
)
|
||||
{
|
||||
model
|
||||
.users
|
||||
.iter()
|
||||
.map(|u| StyledSelectOption {
|
||||
name: Some("user"),
|
||||
icon: None,
|
||||
text: Some(u.name.as_str()),
|
||||
value: u.id as u32,
|
||||
class_list: "",
|
||||
variant: SelectVariant::Empty,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
vec![field_select_option(model, 0, 1, &parts)]
|
||||
}
|
||||
}
|
||||
|
||||
fn field_select_option<'l, 'm: 'l>(
|
||||
model: &'m Model,
|
||||
idx: usize,
|
||||
option_value: u32,
|
||||
parts: &[JqlPart],
|
||||
) -> StyledSelectOption<'l> {
|
||||
if parts.is_empty() {
|
||||
return build_field_select_option(option_value);
|
||||
}
|
||||
match (parts.len(), parts.len() % 3) {
|
||||
(_, 1) => {
|
||||
return build_field_select_option(option_value);
|
||||
}
|
||||
(_, 2)
|
||||
if matches!(
|
||||
parts.get(idx - 1),
|
||||
Some(JqlPart::Field(FieldOption::Assignee))
|
||||
) =>
|
||||
{
|
||||
let user = model
|
||||
.users_by_id
|
||||
.get(&(option_value as i32))
|
||||
.map(|u| (u.id, u.name.as_str()))
|
||||
.unwrap_or_default();
|
||||
StyledSelectOption {
|
||||
name: Some("user"),
|
||||
icon: None,
|
||||
text: Some(user.1),
|
||||
value: user.0 as u32,
|
||||
class_list: "",
|
||||
variant: SelectVariant::Empty,
|
||||
}
|
||||
}
|
||||
_ => build_field_select_option(option_value),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_field_select_option<'l>(option_value: u32) -> StyledSelectOption<'l> {
|
||||
let v = FieldOption::from(option_value);
|
||||
StyledSelectOption {
|
||||
name: Some(v.to_name()),
|
||||
icon: None,
|
||||
@ -51,3 +123,14 @@ fn field_select_option<'l>(n: u32) -> StyledSelectOption<'l> {
|
||||
variant: SelectVariant::Empty,
|
||||
}
|
||||
}
|
||||
|
||||
fn removable_part(part: &str) -> Node<Msg> {
|
||||
let remove = StyledButton {
|
||||
variant: ButtonVariant::Empty,
|
||||
icon: Some(StyledIcon::from(Icon::Trash).render()),
|
||||
on_click: None,
|
||||
..Default::default()
|
||||
}
|
||||
.render();
|
||||
div![C!["part"], span![part], remove]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user