Browse Source

Merge pull request #12365 from frappe/rebrand-ui-fixes

version-14
Prssanna Desai 4 years ago
committed by GitHub
parent
commit
1c2365728d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 250 additions and 47 deletions
  1. +64
    -6
      frappe/desk/doctype/kanban_board/kanban_board.py
  2. +11
    -5
      frappe/public/js/frappe/list/list_view.js
  3. +1
    -0
      frappe/public/js/frappe/ui/page.js
  4. +81
    -6
      frappe/public/js/frappe/views/kanban/kanban_board.js
  5. +4
    -1
      frappe/public/js/frappe/views/kanban/kanban_card.html
  6. +4
    -0
      frappe/public/js/frappe/views/kanban/kanban_view.js
  7. +80
    -29
      frappe/public/scss/desk/kanban.scss
  8. +5
    -0
      frappe/public/scss/desk/page.scss

+ 64
- 6
frappe/desk/doctype/kanban_board/kanban_board.py View File

@@ -17,6 +17,10 @@ class KanbanBoard(Document):
def on_update(self): def on_update(self):
frappe.clear_cache(doctype=self.reference_doctype) frappe.clear_cache(doctype=self.reference_doctype)


def before_insert(self):
for column in self.columns:
column.order = get_order_for_column(self, column.column_name)

def validate_column_name(self): def validate_column_name(self):
for column in self.columns: for column in self.columns:
if not column.column_name: if not column.column_name:
@@ -125,6 +129,53 @@ def update_order(board_name, order):
board.save() board.save()
return board, updated_cards return board, updated_cards


@frappe.whitelist()
def update_order_for_single_card(board_name, docname, from_colname, to_colname, old_index, new_index):
'''Save the order of cards in columns'''
board = frappe.get_doc('Kanban Board', board_name)
doctype = board.reference_doctype
fieldname = board.field_name
old_index = frappe.parse_json(old_index)
new_index = frappe.parse_json(new_index)

# save current order and index of columns to be updated
from_col_order, from_col_idx = get_kanban_column_order_and_index(board, from_colname)
to_col_order, to_col_idx = get_kanban_column_order_and_index(board, to_colname)

if from_colname == to_colname:
from_col_order = to_col_order

to_col_order.insert(new_index, from_col_order.pop((old_index)))

# save updated order
board.columns[from_col_idx].order = frappe.as_json(from_col_order)
board.columns[to_col_idx].order = frappe.as_json(to_col_order)
board.save()

# update changed value in doc
frappe.set_value(doctype, docname, fieldname, to_colname)

return board

def get_kanban_column_order_and_index(board, colname):
for i, col in enumerate(board.columns):
if col.column_name == colname:
col_order = frappe.parse_json(col.order)
col_idx = i

return col_order, col_idx

@frappe.whitelist()
def add_card(board_name, docname, colname):
board = frappe.get_doc('Kanban Board', board_name)

col_order, col_idx = get_kanban_column_order_and_index(board, colname)
col_order.insert(0, docname)

board.columns[col_idx].order = frappe.as_json(col_order)

board.save()
return board


@frappe.whitelist() @frappe.whitelist()
def quick_kanban_board(doctype, board_name, field_name, project=None): def quick_kanban_board(doctype, board_name, field_name, project=None):
@@ -133,6 +184,13 @@ def quick_kanban_board(doctype, board_name, field_name, project=None):
doc = frappe.new_doc('Kanban Board') doc = frappe.new_doc('Kanban Board')
meta = frappe.get_meta(doctype) meta = frappe.get_meta(doctype)


doc.kanban_board_name = board_name
doc.reference_doctype = doctype
doc.field_name = field_name

if project:
doc.filters = '[["Task","project","=","{0}"]]'.format(project)

options = '' options = ''
for field in meta.fields: for field in meta.fields:
if field.fieldname == field_name: if field.fieldname == field_name:
@@ -149,12 +207,6 @@ def quick_kanban_board(doctype, board_name, field_name, project=None):
column_name=column column_name=column
)) ))


doc.kanban_board_name = board_name
doc.reference_doctype = doctype
doc.field_name = field_name

if project:
doc.filters = '[["Task","project","=","{0}"]]'.format(project)


if doctype in ['Note', 'ToDo']: if doctype in ['Note', 'ToDo']:
doc.private = 1 doc.private = 1
@@ -162,6 +214,12 @@ def quick_kanban_board(doctype, board_name, field_name, project=None):
doc.save() doc.save()
return doc return doc


def get_order_for_column(board, colname):
filters = [[board.reference_doctype, board.field_name, '=', colname]]
if board.filters:
filters.append(frappe.parse_json(board.filters)[0])

return frappe.as_json(frappe.get_list(board.reference_doctype, filters=filters, pluck='name'))


@frappe.whitelist() @frappe.whitelist()
def update_column_order(board_name, order): def update_column_order(board_name, order):


+ 11
- 5
frappe/public/js/frappe/list/list_view.js View File

@@ -164,7 +164,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
const match_rules_list = frappe.perm.get_match_rules(this.doctype); const match_rules_list = frappe.perm.get_match_rules(this.doctype);
if (match_rules_list.length) { if (match_rules_list.length) {
this.restricted_list = $( this.restricted_list = $(
`<button class="btn btn-default btn-xs restricted-button flex align-center">
`<button class="btn btn-xs restricted-button flex align-center">
${frappe.utils.icon('restriction', 'xs')} ${frappe.utils.icon('restriction', 'xs')}
</button>` </button>`
).click(() => this.show_restrictions(match_rules_list)).appendTo(this.page.page_form); ).click(() => this.show_restrictions(match_rules_list)).appendTo(this.page.page_form);
@@ -676,7 +676,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {


if (col.type === "Tag") { if (col.type === "Tag") {
const tags_display_class = !this.tags_shown ? 'hide' : ''; const tags_display_class = !this.tags_shown ? 'hide' : '';
let tags_html = doc._user_tags ? this.get_tags_html(doc._user_tags) : '<div class="tags-empty">-</div>';
let tags_html = doc._user_tags ? this.get_tags_html(doc._user_tags, 2) : '<div class="tags-empty">-</div>';
return ` return `
<div class="list-row-col tag-col ${tags_display_class} hidden-xs ellipsis"> <div class="list-row-col tag-col ${tags_display_class} hidden-xs ellipsis">
${tags_html} ${tags_html}
@@ -790,13 +790,19 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
`; `;
} }


get_tags_html(user_tags) {
get_tags_html(user_tags, limit, colored=false) {
let get_tag_html = tag => { let get_tag_html = tag => {
let color = '', style = '';
if (tag) { if (tag) {
return `<div class="tag-pill ellipsis" title="${tag}">${tag}</div>`;
if (colored) {
color = frappe.get_palette(tag);
style = `background-color: var(${color[0]}); color: var(${color[1]})`;
}

return `<div class="tag-pill ellipsis" title="${tag}" style="${style}">${tag}</div>`;
} }
}; };
return user_tags.split(',').slice(1, 3).map(get_tag_html).join('');
return user_tags.split(',').slice(1, limit + 1).map(get_tag_html).join('');
} }


get_meta_html(doc) { get_meta_html(doc) {


+ 1
- 0
frappe/public/js/frappe/ui/page.js View File

@@ -114,6 +114,7 @@ frappe.ui.Page = Class.extend({
this.get_main_icon(this.icon); this.get_main_icon(this.icon);


this.body = this.main = this.wrapper.find(".layout-main-section"); this.body = this.main = this.wrapper.find(".layout-main-section");
this.container = this.wrapper.find(".page-body");
this.sidebar = this.wrapper.find(".layout-side-section"); this.sidebar = this.wrapper.find(".layout-side-section");
this.footer = this.wrapper.find(".layout-footer"); this.footer = this.wrapper.find(".layout-footer");
this.indicator = this.wrapper.find(".indicator-pill"); this.indicator = this.wrapper.find(".indicator-pill");


+ 81
- 6
frappe/public/js/frappe/views/kanban/kanban_board.js View File

@@ -124,7 +124,12 @@ frappe.provide("frappe.views");
const new_cards = state.cards.slice(); const new_cards = state.cards.slice();
new_cards[index] = card; new_cards[index] = card;
updater.set({ cards: new_cards }); updater.set({ cards: new_cards });
fluxify.doAction('update_order');
const args = {
new: 1,
name: card.name,
colname: updated_doc[state.board.field_name],
};
fluxify.doAction('update_order_for_single_card', args);
}); });
} else { } else {
frappe.new_doc(this.doctype, doc); frappe.new_doc(this.doctype, doc);
@@ -155,6 +160,53 @@ frappe.provide("frappe.views");
fluxify.doAction('update_card', updated_card); fluxify.doAction('update_card', updated_card);
}); });
}, },
update_order_for_single_card: function(updater, card) {
// cache original order
const _cards = this.cards.slice();
const _columns = this.columns.slice();
let args = {};
let method_name = "";

if (card.new) {
method_name = "add_card";
args = {
board_name: this.board.name,
docname: card.name,
colname: card.colname,
};
} else {
method_name = "update_order_for_single_card";
args = {
board_name: this.board.name,
docname: card.name,
from_colname: card.from_colname,
to_colname: card.to_colname,
old_index: card.old_index,
new_index: card.new_index,
};
}

frappe.call({
method: method_prefix + method_name,
args: args,
callback: (r) => {
let board = r.message;
let updated_cards = [{'name': card.name, 'column': card.to_colname || card.colname}];
let cards = update_cards_column(updated_cards);
let columns = prepare_columns(board.columns);
updater.set({
cards: cards,
columns: columns
});
}
}).fail(function() {
// revert original order
updater.set({
cards: _cards,
columns: _columns
});
});
},
update_order: function(updater) { update_order: function(updater) {
// cache original order // cache original order
const _cards = this.cards.slice(); const _cards = this.cards.slice();
@@ -446,16 +498,24 @@ frappe.provide("frappe.views");
group: "cards", group: "cards",
animation: 150, animation: 150,
dataIdAttr: 'data-name', dataIdAttr: 'data-name',
forceFallback: true,
onStart: function() { onStart: function() {
wrapper.find('.kanban-card.add-card').fadeOut(200, function() { wrapper.find('.kanban-card.add-card').fadeOut(200, function() {
wrapper.find('.kanban-cards').height('100vh'); wrapper.find('.kanban-cards').height('100vh');
}); });
}, },
onEnd: function() {
onEnd: function(e) {
wrapper.find('.kanban-card.add-card').fadeIn(100); wrapper.find('.kanban-card.add-card').fadeIn(100);
wrapper.find('.kanban-cards').height('auto'); wrapper.find('.kanban-cards').height('auto');
// update order // update order
fluxify.doAction('update_order');
const args = {
name: $(e.item).attr('data-name'),
from_colname: $(e.from).parents('.kanban-column').attr('data-column-value'),
to_colname: $(e.to).parents('.kanban-column').attr('data-column-value'),
old_index: e.oldIndex,
new_index: e.newIndex,
};
fluxify.doAction('update_order_for_single_card', args);
}, },
onAdd: function() { onAdd: function() {
}, },
@@ -546,14 +606,24 @@ frappe.provide("frappe.views");
var opts = { var opts = {
name: card.name, name: card.name,
title: remove_img_tags(card.title), title: remove_img_tags(card.title),
disable_click: card._disable_click ? 'disable-click' : ''
disable_click: card._disable_click ? 'disable-click' : '',
creation: card.creation,
}; };
self.$card = $(frappe.render_template('kanban_card', opts)) self.$card = $(frappe.render_template('kanban_card', opts))
.appendTo(wrapper); .appendTo(wrapper);
} }


function get_tags_html(card) {
return card.tags
? `<div class="kanban-tags">
${cur_list.get_tags_html(card.tags, 3, true)}
</div>`
: '';
}

function render_card_meta() { function render_card_meta() {
var html = "";
let html = get_tags_html(card);

if (card.comment_count > 0) if (card.comment_count > 0)
html += html +=
`<span class="list-comment-count small text-muted "> `<span class="list-comment-count small text-muted ">
@@ -563,7 +633,10 @@ frappe.provide("frappe.views");


const $assignees_group = get_assignees_group(); const $assignees_group = get_assignees_group();


html += `<span class="kanban-assignments"></span>`;
html += `
<span class="kanban-assignments"></span>
${cur_list.get_like_html(card)}
`;


if (card.color && frappe.ui.color.validate_hex(card.color)) { if (card.color && frappe.ui.color.validate_hex(card.color)) {
const $div = $('<div>'); const $div = $('<div>');
@@ -630,6 +703,8 @@ frappe.provide("frappe.views");
doctype: state.doctype, doctype: state.doctype,
name: card.name, name: card.name,
title: card[state.card_meta.title_field.fieldname], title: card[state.card_meta.title_field.fieldname],
creation: moment(card.creation).format('MMM DD, YYYY'),
tags: card._user_tags,
column: card[state.board.field_name], column: card[state.board.field_name],
assigned_list: card.assigned_list || assigned_list, assigned_list: card.assigned_list || assigned_list,
comment_count: card.comment_count || comment_count, comment_count: card.comment_count || comment_count,


+ 4
- 1
frappe/public/js/frappe/views/kanban/kanban_card.html View File

@@ -2,9 +2,12 @@
<a class="kanban-card-redirect" href="#"> <a class="kanban-card-redirect" href="#">
<div class="kanban-card content"> <div class="kanban-card content">
<div class="kanban-title-area"> <div class="kanban-title-area">
<div class="kanban-card-title ellipsis" title="{{title}}">
<div class="kanban-card-title" title="{{title}}">
{{ title }} {{ title }}
</div> </div>
<div class="kanban-card-creation">
{{ creation }}
</div>
</div> </div>
<div class="kanban-card-meta"> <div class="kanban-card-meta">
</div> </div>


+ 4
- 0
frappe/public/js/frappe/views/kanban/kanban_view.js View File

@@ -73,7 +73,11 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
} }


setup_view() { setup_view() {
if (this.board.columns.length > 5) {
this.page.container.addClass('full-width');
}
this.setup_realtime_updates(); this.setup_realtime_updates();
this.setup_like();
} }


set_fields() { set_fields() {


+ 80
- 29
frappe/public/scss/desk/kanban.scss View File

@@ -15,7 +15,7 @@


.kanban { .kanban {
display: flex; display: flex;
overflow: auto;
overflow-y: hidden;


-ms-overflow-style: none; /* IE and Edge */ -ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */ scrollbar-width: none; /* Firefox */
@@ -32,19 +32,7 @@
border-radius: var(--border-radius); border-radius: var(--border-radius);
padding: var(--padding-md); padding: var(--padding-md);
min-height: calc(100vh - 250px); min-height: calc(100vh - 250px);

&.add-new-column {
order: 1;
}

&:hover {
.add-card {
background-color: var(--kanban-new-card-hover-bg);
box-shadow: var(--shadow-xs);
}

background-color: var(--kanban-new-card-bg);
}
max-height: calc(75vh - 10px);


.add-card { .add-card {
@include flex(flex, center, center, null); @include flex(flex, center, center, null);
@@ -70,6 +58,17 @@
} }
} }


.kanban-column:not(.add-new-column) {
&:hover {
.add-card {
background-color: var(--kanban-new-card-hover-bg);
box-shadow: var(--shadow-xs);
}

background-color: var(--kanban-new-card-bg);
}
}

.kanban-column-header { .kanban-column-header {
@include flex(flex, space-between, null, null); @include flex(flex, space-between, null, null);
margin-top: 0; margin-top: 0;
@@ -138,7 +137,6 @@
} }


.kanban-cards { .kanban-cards {
min-height: 100px;
max-height: calc(100vh - 250px); max-height: calc(100vh - 250px);
margin: -5px; margin: -5px;
padding: 5px; padding: 5px;
@@ -191,10 +189,20 @@
} }
} }


.kanban-card-title {
max-width: 90%;
font-size: $font-size-base;
font-weight: 500;
.kanban-title-area {
margin-bottom: 12px;

.kanban-card-title {
max-width: 90%;
font-size: var(--text-md);
font-weight: 500;
}

.kanban-card-creation {
font-size: var(--text-md);
color: var(--text-muted);
margin-top: var(--margin-xs);
}
} }


.kanban-card-edit { .kanban-card-edit {
@@ -232,28 +240,71 @@
} }
} }


.add-new-column {
display: flex;
justify-content: center;
align-items: center;
min-height: 65px;
.kanban-column.add-new-column {
color: var(--text-muted); color: var(--text-muted);
border: 1px dashed var(--gray-400);
max-height: 80px;
background-color: transparent; background-color: transparent;
}
order: 1;


.add-new-column:hover {
background-color: var(--kanban-column-bg);
&:hover {
background-color: none;
}

.kanban-column-title.compose-column {
@include flex(flex, center, center, null);
min-height: 65px;
border-radius: var(--border-radius);
border: 1px dashed var(--gray-400);
font-size: var(--text-base);

&:hover {
background-color: var(--kanban-column-bg);
}
}
} }


.kanban-card-meta { .kanban-card-meta {

.list-comment-count {
width: 30px;
}

.like-action:not(.liked) {
.icon use {
stroke: var(--text-muted);
}
}

.kanban-tags {
font-size: var(--text-sm);
margin-bottom: 8px;

.tag-pill {
border-radius: 100px;
height: 22px;
width: auto;
padding: 2px 8px;
margin-right: var(--margin-xs);
}
}

.kanban-assignments { .kanban-assignments {
display: flex; display: flex;
float: right; float: right;


.avatar { .avatar {
cursor: default; cursor: default;
width: 22px;
height: 22px;
}

.avatar-action {
width: 22px;
height: 22px;

.icon {
width: 12px;
height: 12px;
}
} }
} }
} }


+ 5
- 0
frappe/public/scss/desk/page.scss View File

@@ -44,6 +44,11 @@


.page-container { .page-container {
background-color: var(--bg-color); background-color: var(--bg-color);

.page-body.full-width {
width: 100%;
max-width: 100%;
}
} }


.custom-actions { .custom-actions {


Loading…
Cancel
Save