From 07b8131d71af023823a5cf9b025233677ce2424f Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 9 Feb 2021 17:15:53 +0530 Subject: [PATCH 1/9] fix: kanban style --- frappe/public/scss/desk/kanban.scss | 46 +++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/frappe/public/scss/desk/kanban.scss b/frappe/public/scss/desk/kanban.scss index 13551c4c17..902164ff10 100644 --- a/frappe/public/scss/desk/kanban.scss +++ b/frappe/public/scss/desk/kanban.scss @@ -15,7 +15,7 @@ .kanban { display: flex; - overflow: auto; + overflow-y: hidden; -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ @@ -32,6 +32,8 @@ border-radius: var(--border-radius); padding: var(--padding-md); min-height: calc(100vh - 250px); + max-height: calc(75vh - 10px); + // overflow: hidden; &.add-new-column { order: 1; @@ -138,7 +140,6 @@ } .kanban-cards { - min-height: 100px; max-height: calc(100vh - 250px); margin: -5px; padding: 5px; @@ -191,10 +192,20 @@ } } - .kanban-card-title { - max-width: 90%; - font-size: $font-size-base; - font-weight: 500; + .kanban-title-area { + margin-bottom: var(--margin-md); + + .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 { @@ -248,12 +259,35 @@ } .kanban-card-meta { + + .list-comment-count { + width: 30px; + } + + .like-action:not(.liked) { + .icon use { + stroke: var(--text-muted); + } + } + .kanban-assignments { display: flex; float: right; .avatar { cursor: default; + width: 22px; + height: 22px; + } + + .avatar-action { + width: 22px; + height: 22px; + + .icon { + width: 12px; + height: 12px; + } } } } From 0d29d46f7d6c95f67060c70878a874f5a02074ff Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 9 Feb 2021 17:16:58 +0530 Subject: [PATCH 2/9] feat: add likes and creation to kanban --- frappe/public/js/frappe/views/kanban/kanban_board.js | 12 +++++++++--- .../public/js/frappe/views/kanban/kanban_card.html | 5 ++++- frappe/public/js/frappe/views/kanban/kanban_view.js | 1 + 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/views/kanban/kanban_board.js b/frappe/public/js/frappe/views/kanban/kanban_board.js index 66ae842ae7..e5f28659f4 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_board.js +++ b/frappe/public/js/frappe/views/kanban/kanban_board.js @@ -446,6 +446,7 @@ frappe.provide("frappe.views"); group: "cards", animation: 150, dataIdAttr: 'data-name', + forceFallback: true, onStart: function() { wrapper.find('.kanban-card.add-card').fadeOut(200, function() { wrapper.find('.kanban-cards').height('100vh'); @@ -546,14 +547,14 @@ frappe.provide("frappe.views"); var opts = { name: card.name, 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)) .appendTo(wrapper); } function render_card_meta() { - var html = ""; if (card.comment_count > 0) html += ` @@ -563,7 +564,10 @@ frappe.provide("frappe.views"); const $assignees_group = get_assignees_group(); - html += ``; + html += ` + + ${cur_list.get_like_html(card)} + `; if (card.color && frappe.ui.color.validate_hex(card.color)) { const $div = $('
'); @@ -630,6 +634,8 @@ frappe.provide("frappe.views"); doctype: state.doctype, name: card.name, 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], assigned_list: card.assigned_list || assigned_list, comment_count: card.comment_count || comment_count, diff --git a/frappe/public/js/frappe/views/kanban/kanban_card.html b/frappe/public/js/frappe/views/kanban/kanban_card.html index 108dadce8c..b67488f46f 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_card.html +++ b/frappe/public/js/frappe/views/kanban/kanban_card.html @@ -2,9 +2,12 @@
-
+
{{ title }}
+
+ {{ creation }} +
diff --git a/frappe/public/js/frappe/views/kanban/kanban_view.js b/frappe/public/js/frappe/views/kanban/kanban_view.js index 46348b0d11..096c882d74 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_view.js +++ b/frappe/public/js/frappe/views/kanban/kanban_view.js @@ -74,6 +74,7 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { setup_view() { this.setup_realtime_updates(); + this.setup_like(); } set_fields() { From 0f9e77c401c8bdf47b770d3a322f20da5402e6b4 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 11 Feb 2021 10:24:46 +0530 Subject: [PATCH 3/9] refactor: optimize updating of order for kanban board --- .../desk/doctype/kanban_board/kanban_board.py | 73 +++++++++++++++++-- .../js/frappe/views/kanban/kanban_board.js | 72 +++++++++++++++++- 2 files changed, 135 insertions(+), 10 deletions(-) diff --git a/frappe/desk/doctype/kanban_board/kanban_board.py b/frappe/desk/doctype/kanban_board/kanban_board.py index f1ad41db6c..9152e952ab 100644 --- a/frappe/desk/doctype/kanban_board/kanban_board.py +++ b/frappe/desk/doctype/kanban_board/kanban_board.py @@ -17,6 +17,10 @@ class KanbanBoard(Document): def on_update(self): 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): for column in self.columns: if not column.column_name: @@ -125,6 +129,56 @@ def update_order(board_name, order): board.save() 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 + for i, col in enumerate(board.columns): + 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) + doctype = board.reference_doctype + fieldname = board.field_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() def quick_kanban_board(doctype, board_name, field_name, project=None): @@ -133,6 +187,13 @@ def quick_kanban_board(doctype, board_name, field_name, project=None): doc = frappe.new_doc('Kanban Board') 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 = '' for field in meta.fields: if field.fieldname == field_name: @@ -149,12 +210,6 @@ def quick_kanban_board(doctype, board_name, field_name, project=None): 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']: doc.private = 1 @@ -162,6 +217,12 @@ def quick_kanban_board(doctype, board_name, field_name, project=None): doc.save() 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() def update_column_order(board_name, order): diff --git a/frappe/public/js/frappe/views/kanban/kanban_board.js b/frappe/public/js/frappe/views/kanban/kanban_board.js index e5f28659f4..495482afd5 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_board.js +++ b/frappe/public/js/frappe/views/kanban/kanban_board.js @@ -124,7 +124,12 @@ frappe.provide("frappe.views"); const new_cards = state.cards.slice(); new_cards[index] = card; 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 { frappe.new_doc(this.doctype, doc); @@ -155,6 +160,53 @@ frappe.provide("frappe.views"); 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) { // cache original order const _cards = this.cards.slice(); @@ -447,16 +499,26 @@ frappe.provide("frappe.views"); animation: 150, dataIdAttr: 'data-name', forceFallback: true, - onStart: function() { + onStart: function(e) { wrapper.find('.kanban-card.add-card').fadeOut(200, function() { wrapper.find('.kanban-cards').height('100vh'); }); }, - onEnd: function() { + onEnd: function(e) { wrapper.find('.kanban-card.add-card').fadeIn(100); wrapper.find('.kanban-cards').height('auto'); // update order - fluxify.doAction('update_order'); + let from_col = $(e.from).parents('.kanban-column').attr('data-column-value'); + let to_col = $(e.to).parents('.kanban-column').attr('data-column-value'); + let docname = $(e.item).attr('data-name'); + const args = { + name: docname, + from_colname: from_col, + to_colname: to_col, + old_index: e.oldIndex, + new_index: e.newIndex, + }; + fluxify.doAction('update_order_for_single_card', args); }, onAdd: function() { }, @@ -555,6 +617,8 @@ frappe.provide("frappe.views"); } function render_card_meta() { + let html = ''; + if (card.comment_count > 0) html += ` From 8d847ee52ea94b59ae68777930e1c607260144a7 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 11 Feb 2021 11:09:55 +0530 Subject: [PATCH 4/9] feat: add tags to kanban --- frappe/public/js/frappe/list/list_view.js | 16 +++++++++++----- .../js/frappe/views/kanban/kanban_board.js | 10 +++++++++- frappe/public/scss/desk/kanban.scss | 15 ++++++++++++++- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 2f1f278ecc..66d3198e17 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -164,7 +164,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { const match_rules_list = frappe.perm.get_match_rules(this.doctype); if (match_rules_list.length) { this.restricted_list = $( - `` ).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") { const tags_display_class = !this.tags_shown ? 'hide' : ''; - let tags_html = doc._user_tags ? this.get_tags_html(doc._user_tags) : '
-
'; + let tags_html = doc._user_tags ? this.get_tags_html(doc._user_tags, 2) : '
-
'; return `