diff --git a/client/components/lists/list.css b/client/components/lists/list.css index 4b26f0c05..f24ac22f1 100644 --- a/client/components/lists/list.css +++ b/client/components/lists/list.css @@ -7,7 +7,7 @@ border-left: 1px solid #ccc; padding: 0; float: left; - min-width: 100px; /* TODO(mark-i-m): hardcoded? */ + /* min-width: 100px; TODO(mark-i-m): hardcoded? */ /*max-width: 270px;*/ /* Reverted incomplete change list width: */ /* https://github.com/wekan/wekan/issues/4558 */ @@ -82,6 +82,16 @@ text-overflow: ellipsis; word-wrap: break-word; } +.list-rotated { + width: 10px; + margin-top: 10px; + margin-left: 0; + margin-right: 0; + transform: rotate(90deg); + position: relative; + text-overflow: ellipsis; + white-space: nowrap; +} .list-header .list-header-watch-icon { padding-left: 10px; color: #a6a6a6; @@ -99,6 +109,23 @@ color: #a6a6a6; margin-right: 15px; } +.list-header .list-header-collapse-right { + color: #a6a6a6; +} +.list-header .list-header-collapse-left { + color: #a6a6a6; + margin-right: 15px; +} +.list-header .list-header-uncollapse-left { + color: #a6a6a6; +} +.list-header .list-header-uncollapse-right { + color: #a6a6a6; +} +.list-header .list-header-collapse { + color: #a6a6a6; + margin-right: 15px; +} .list-header .highlight { color: #ce1414; } diff --git a/client/components/lists/list.jade b/client/components/lists/list.jade index 748c53538..e7f06c6ca 100644 --- a/client/components/lists/list.jade +++ b/client/components/lists/list.jade @@ -1,6 +1,7 @@ template(name='list') .list.js-list(id="js-list-{{_id}}" - style="width:{{listWidth}}px;") + style="{{#unless collapsed}}width:{{listWidth}}px;{{/unless}}" + class="{{#if collapsed}}list-collapsed{{/if}}") +listHeader +listBody diff --git a/client/components/lists/listBody.jade b/client/components/lists/listBody.jade index 3d319f1ca..22a11fec8 100644 --- a/client/components/lists/listBody.jade +++ b/client/components/lists/listBody.jade @@ -1,37 +1,38 @@ template(name="listBody") - .list-body - .minicards.clearfix.js-minicards(class="{{#if reachedWipLimit}}js-list-full{{/if}}") - if cards.length - +inlinedForm(autoclose=false position="top") - +addCardForm(listId=_id position="top") - ul.sidebar-list - each customFieldsSum - li - +viewer - = name - if $eq customFieldsSum.type "number" + unless collapsed + .list-body + .minicards.clearfix.js-minicards(class="{{#if reachedWipLimit}}js-list-full{{/if}}") + if cards.length + +inlinedForm(autoclose=false position="top") + +addCardForm(listId=_id position="top") + ul.sidebar-list + each customFieldsSum + li +viewer - = value - if $eq customFieldsSum.type "currency" - +viewer - = formattedCurrencyCustomFieldValue(value) - each (cardsWithLimit (idOrNull ../../_id)) - a.minicard-wrapper.js-minicard(href=originRelativeUrl - class="{{#if cardIsSelected}}is-selected{{/if}}" - class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") - if MultiSelection.isActive - .materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection( - class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") - +minicard(this) - if (showSpinner (idOrNull ../../_id)) - +spinnerList + = name + if $eq customFieldsSum.type "number" + +viewer + = value + if $eq customFieldsSum.type "currency" + +viewer + = formattedCurrencyCustomFieldValue(value) + each (cardsWithLimit (idOrNull ../../_id)) + a.minicard-wrapper.js-minicard(href=originRelativeUrl + class="{{#if cardIsSelected}}is-selected{{/if}}" + class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") + if MultiSelection.isActive + .materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection( + class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") + +minicard(this) + if (showSpinner (idOrNull ../../_id)) + +spinnerList - if canSeeAddCard - +inlinedForm(autoclose=false position="bottom") - +addCardForm(listId=_id position="bottom") - else - a.open-minicard-composer.js-card-composer.js-open-inlined-form(title="{{_ 'add-card-to-bottom-of-list'}}") - i.fa.fa-plus + if canSeeAddCard + +inlinedForm(autoclose=false position="bottom") + +addCardForm(listId=_id position="bottom") + else + a.open-minicard-composer.js-card-composer.js-open-inlined-form(title="{{_ 'add-card-to-bottom-of-list'}}") + i.fa.fa-plus template(name="spinnerList") .sk-spinner.sk-spinner-list( diff --git a/client/components/lists/listHeader.jade b/client/components/lists/listHeader.jade index d11d4a529..e724cdaa4 100644 --- a/client/components/lists/listHeader.jade +++ b/client/components/lists/listHeader.jade @@ -5,22 +5,43 @@ template(name="listHeader") +inlinedForm +editListTitleForm else + unless isMiniScreen + if collapsed + a.js-collapse(title="{{_ 'uncollapse'}}") + i.fa.fa-arrow-left.list-header-uncollapse-left + i.fa.fa-arrow-right.list-header-uncollapse-right if isMiniScreen if currentList a.list-header-left-icon.fa.fa-angle-left.js-unselect-list - h2.list-header-name( - title="{{ moment modifiedAt 'LLL' }}" - class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}") - +viewer - = title - if wipLimit.enabled - | ( - span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}} - |/#{wipLimit.value}) - - if showCardsCountForList cards.length - span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}} - + unless isMiniScreen + if collapsed + if showCardsCountForList cards.length + br + span.cardCount {{cardsCount}} + if isMiniScreen + h2.list-header-name( + title="{{ moment modifiedAt 'LLL' }}" + class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}") + +viewer + = title + if wipLimit.enabled + | ( + span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}} + |/#{wipLimit.value}) + unless isMiniScreen + div(class="{{#if collapsed}}list-rotated{{/if}}") + h2.list-header-name( + title="{{ moment modifiedAt 'LLL' }}" + class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}") + +viewer + = title + if wipLimit.enabled + | ( + span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}} + |/#{wipLimit.value}) + unless collapsed + if showCardsCountForList cards.length + span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}} if isMiniScreen if currentList if isWatching @@ -36,16 +57,20 @@ template(name="listHeader") else if currentUser.isBoardMember if isWatching i.list-header-watch-icon.fa.fa-eye - div.list-header-menu - unless currentUser.isCommentOnly - //if isBoardAdmin - // a.fa.js-list-star.list-header-plus-top(class="fa-star{{#unless starred}}-o{{/unless}}") - if canSeeAddCard - a.js-add-card.fa.fa-plus.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}") - a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") - if currentUser.isBoardAdmin - if isTouchScreenOrShowDesktopDragHandles - a.list-header-handle.handle.fa.fa-arrows.js-list-handle + unless collapsed + div.list-header-menu + unless currentUser.isCommentOnly + //if isBoardAdmin + // a.fa.js-list-star.list-header-plus-top(class="fa-star{{#unless starred}}-o{{/unless}}") + if canSeeAddCard + a.js-add-card.fa.fa-plus.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}") + a.js-collapse(title="{{_ 'collapse'}}") + i.fa.fa-arrow-right.list-header-collapse-right + i.fa.fa-arrow-left.list-header-collapse-left + a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") + if currentUser.isBoardAdmin + if isTouchScreenOrShowDesktopDragHandles + a.list-header-handle.handle.fa.fa-arrows.js-list-handle template(name="editListTitleForm") .list-composer diff --git a/client/components/lists/listHeader.js b/client/components/lists/listHeader.js index 71381e17e..a8f63900a 100644 --- a/client/components/lists/listHeader.js +++ b/client/components/lists/listHeader.js @@ -31,6 +31,17 @@ BlazeComponent.extendComponent({ return !status; } }, + collapsed(check = undefined) { + const list = Template.currentData(); + const status = list.isCollapsed(); + if (check === undefined) { + // just check + return status; + } else { + list.collapse(!status); + return !status; + } + }, editTitle(event) { event.preventDefault(); const newTitle = this.childComponents('inlinedForm')[0] @@ -104,6 +115,10 @@ BlazeComponent.extendComponent({ event.preventDefault(); this.starred(!this.starred()); }, + 'click .js-collapse'(event) { + event.preventDefault(); + this.collapsed(!this.collapsed()); + }, 'click .js-open-list-menu': Popup.open('listAction'), 'click .js-add-card.list-header-plus-top'(event) { const listDom = $(event.target).parents( diff --git a/imports/i18n/data/en.i18n.json b/imports/i18n/data/en.i18n.json index 0485b50c5..e38b230c4 100644 --- a/imports/i18n/data/en.i18n.json +++ b/imports/i18n/data/en.i18n.json @@ -1246,5 +1246,7 @@ "show-checklist-at-minicard": "Show checklist at minicard", "show-subtasks-field": "Show subtasks field", "convert-to-markdown": "Convert to markdown", - "import-board-zip": "Add .zip file that has board JSON files, and board name subdirectories with attachments" + "import-board-zip": "Add .zip file that has board JSON files, and board name subdirectories with attachments", + "collapse": "Collapse", + "uncollapse": "Uncollapse" } diff --git a/models/lists.js b/models/lists.js index 49b456f2a..126b7ea1e 100644 --- a/models/lists.js +++ b/models/lists.js @@ -156,6 +156,13 @@ Lists.attachSchema( type: String, defaultValue: 'list', }, + collapsed: { + /** + * is the list collapsed + */ + type: Boolean, + defaultValue: false, + }, }), ); @@ -286,6 +293,10 @@ Lists.helpers({ return this.starred === true; }, + isCollapsed() { + return this.collapsed === true; + }, + absoluteUrl() { const card = ReactiveCache.getCard({ listId: this._id }); return card && card.absoluteUrl(); @@ -306,6 +317,9 @@ Lists.mutations({ star(enable = true) { return { $set: { starred: !!enable } }; }, + collapse(enable = true) { + return { $set: { collapsed: !!enable } }; + }, archive() { if (this.isTemplateList()) { diff --git a/models/swimlanes.js b/models/swimlanes.js index 7c8cac595..1e94b3c97 100644 --- a/models/swimlanes.js +++ b/models/swimlanes.js @@ -107,6 +107,13 @@ Swimlanes.attachSchema( type: String, defaultValue: 'swimlane', }, + collapsed: { + /** + * is the swimlane collapsed + */ + type: Boolean, + defaultValue: false, + }, }), ); @@ -232,6 +239,10 @@ Swimlanes.helpers({ return ret; }, + isCollapsed() { + return this.collapsed === true; + }, + board() { return ReactiveCache.getBoard(this.boardId); }, @@ -274,6 +285,10 @@ Swimlanes.mutations({ return { $set: { title } }; }, + collapse(enable = true) { + return { $set: { collapsed: !!enable } }; + }, + archive() { if (this.isTemplateSwimlane()) { this.myLists().forEach(list => { diff --git a/server/publications/boards.js b/server/publications/boards.js index dee05959f..0f1ce2544 100644 --- a/server/publications/boards.js +++ b/server/publications/boards.js @@ -61,6 +61,7 @@ Meteor.publishRelations('boards', function() { title: 1, boardId: 1, archived: 1, + collapsed: 1, sort: 1 } }, @@ -221,7 +222,7 @@ Meteor.publishRelations('board', function(boardId, isArchived) { ), function(boardId, board) { this.cursor(ReactiveCache.getLists({ boardId, archived: isArchived }, {}, true)); - this.cursor(ReactiveCache.getSwimlanes({ boardId, archived: isArchived }, {}, true)); + this.cursor(ReactiveCache.getSwimlanes({ boardId, collapsed: 1, archived: isArchived }, {}, true)); this.cursor(ReactiveCache.getIntegrations({ boardId }, {}, true)); this.cursor(ReactiveCache.getCardCommentReactions({ boardId }, {}, true)); this.cursor(