Assignee field display

This commit is contained in:
eraden 2021-04-28 23:46:42 +02:00
parent e6778db137
commit f824694a05
5 changed files with 404 additions and 174 deletions

View File

@ -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;
}
}
}
}
}
}

View File

@ -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![];

View File

@ -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,
}
}
}

View File

@ -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();

View File

@ -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]
}