diff --git a/client/components/lists/list.css b/client/components/lists/list.css index 5e503a4d7..4b26f0c05 100644 --- a/client/components/lists/list.css +++ b/client/components/lists/list.css @@ -7,8 +7,8 @@ border-left: 1px solid #ccc; padding: 0; float: left; - min-width: 270px; - max-width: 270px; + min-width: 100px; /* TODO(mark-i-m): hardcoded? */ + /*max-width: 270px;*/ /* Reverted incomplete change list width: */ /* https://github.com/wekan/wekan/issues/4558 */ /* Orinal width: 270px. Changes not saved yet: */ @@ -179,6 +179,9 @@ #js-wip-limit-edit div { float: left; } +#js-list-width-edit .list-width-error { + display: none; +} @media screen and (max-width: 800px) { .mini-list { flex: 0 0 60px; diff --git a/client/components/lists/list.jade b/client/components/lists/list.jade index c02e0dd6e..748c53538 100644 --- a/client/components/lists/list.jade +++ b/client/components/lists/list.jade @@ -1,5 +1,6 @@ template(name='list') - .list.js-list(id="js-list-{{_id}}") + .list.js-list(id="js-list-{{_id}}" + style="width:{{listWidth}}px;") +listHeader +listBody diff --git a/client/components/lists/list.js b/client/components/lists/list.js index 902df3427..a451c6b5b 100644 --- a/client/components/lists/list.js +++ b/client/components/lists/list.js @@ -194,6 +194,12 @@ BlazeComponent.extendComponent({ }); }); }, + + listWidth() { + const user = Meteor.user(); + const list = Template.currentData(); + return user.getListWidth(list.boardId, list._id); + }, }).register('list'); Template.miniList.events({ diff --git a/client/components/lists/listHeader.jade b/client/components/lists/listHeader.jade index 90f3ea750..d4671c9c8 100644 --- a/client/components/lists/listHeader.jade +++ b/client/components/lists/listHeader.jade @@ -62,6 +62,11 @@ template(name="listActionPopup") i.fa.fa-arrow-down | {{_ 'add-card-to-bottom-of-list'}} hr + ul.pop-over-list + li + a.js-set-list-width + i.fa.fa-arrows-h + | {{_ 'set-list-width'}} ul.pop-over-list li a.js-toggle-watch-list @@ -156,6 +161,19 @@ template(name="wipLimitErrorPopup") p {{_ 'wipLimitErrorPopup-dialog-pt2'}} button.full.js-back-view(type="submit") {{_ 'cancel'}} +template(name="setListWidthPopup") + #js-list-width-edit + label {{_ 'set-list-width-value'}} + p + input.list-width-value(type="number" value="{{ listWidthValue }}" min="100") + input.list-width-apply(type="submit" value="{{_ 'apply'}}") + input.list-width-error + +template(name="listWidthErrorPopup") + .list-width-invalid + p {{_ 'list-width-error-message'}} + button.full.js-back-view(type="submit") {{_ 'cancel'}} + template(name="setListColorPopup") form.edit-label .palette-colors: each colors diff --git a/client/components/lists/listHeader.js b/client/components/lists/listHeader.js index 339672d15..71381e17e 100644 --- a/client/components/lists/listHeader.js +++ b/client/components/lists/listHeader.js @@ -153,6 +153,7 @@ Template.listActionPopup.events({ }); Popup.back(); }, + 'click .js-set-list-width': Popup.open('setListWidth'), 'click .js-set-color-list': Popup.open('setListColor'), 'click .js-select-cards'() { const cardIds = this.allCards().map(card => card._id); @@ -320,3 +321,41 @@ BlazeComponent.extendComponent({ ]; }, }).register('setListColorPopup'); + +BlazeComponent.extendComponent({ + applyListWidth() { + const list = Template.currentData(); + const board = list.boardId; + const width = parseInt( + Template.instance() + .$('.list-width-value') + .val(), + 10, + ); + + // FIXME(mark-i-m): where do we put constants? + if (width < 100 || !width) { + Template.instance() + .$('.list-width-error') + .click(); + } else { + Meteor.call('applyListWidth', board, list._id, width); + Popup.back(); + } + }, + + listWidthValue() { + const list = Template.currentData(); + const board = list.boardId; + return Meteor.user().getListWidth(board, list._id); + }, + + events() { + return [ + { + 'click .list-width-apply': this.applyListWidth, + 'click .list-width-error': Popup.open('listWidthError'), + }, + ]; + }, +}).register('setListWidthPopup'); diff --git a/client/components/swimlanes/swimlaneHeader.jade b/client/components/swimlanes/swimlaneHeader.jade index 0911ee06b..a92331341 100644 --- a/client/components/swimlanes/swimlaneHeader.jade +++ b/client/components/swimlanes/swimlaneHeader.jade @@ -44,6 +44,9 @@ template(name="swimlaneActionPopup") li: a.js-set-swimlane-color i.fa.fa-paint-brush | {{_ 'select-color'}} + li: a.js-set-swimlane-height + i.fa.fa-arrows-v + | {{_ 'set-swimlane-height'}} unless this.isTemplateContainer hr ul.pop-over-list @@ -82,6 +85,19 @@ template(name="setSwimlaneColorPopup") button.primary.confirm.js-submit {{_ 'save'}} button.js-remove-color.negate.wide.right {{_ 'unset-color'}} +template(name="setSwimlaneHeightPopup") + #js-swimlane-height-edit + label {{_ 'set-swimlane-height-value'}} + p + input.swimlane-height-value(type="number" value="{{ swimlaneHeightValue }}" min="100") + input.swimlane-height-apply(type="submit" value="{{_ 'apply'}}") + input.swimlane-height-error + +template(name="swimlaneHeightErrorPopup") + .swimlane-height-invalid + p {{_ 'swimlane-height-error-message'}} + button.full.js-back-view(type="submit") {{_ 'cancel'}} + template(name="swimlaneDeletePopup") p {{_ "swimlane-delete-pop"}} button.js-confirm.negate.full(type="submit") {{_ 'delete'}} diff --git a/client/components/swimlanes/swimlaneHeader.js b/client/components/swimlanes/swimlaneHeader.js index 479dc514a..929c7fa94 100644 --- a/client/components/swimlanes/swimlaneHeader.js +++ b/client/components/swimlanes/swimlaneHeader.js @@ -37,6 +37,7 @@ Template.swimlaneFixedHeader.helpers({ Template.swimlaneActionPopup.events({ 'click .js-set-swimlane-color': Popup.open('setSwimlaneColor'), + 'click .js-set-swimlane-height': Popup.open('setSwimlaneHeight'), 'click .js-close-swimlane'(event) { event.preventDefault(); this.archive(); @@ -129,3 +130,45 @@ BlazeComponent.extendComponent({ ]; }, }).register('setSwimlaneColorPopup'); + +BlazeComponent.extendComponent({ + onCreated() { + this.currentSwimlane = this.currentData(); + }, + + applySwimlaneHeight() { + const swimlane = this.currentData(); + const board = swimlane.boardId; + const height = parseInt( + Template.instance() + .$('.swimlane-height-value') + .val(), + 10, + ); + + // FIXME(mark-i-m): where do we put constants? + if (height < 100 || !height) { + Template.instance() + .$('.swimlane-height-error') + .click(); + } else { + Meteor.call('applySwimlaneHeight', board, swimlane._id, height); + Popup.back(); + } + }, + + swimlaneHeightValue() { + const swimlane = this.currentData(); + const board = swimlane.boardId; + return Meteor.user().getSwimlaneHeight(board, swimlane._id); + }, + + events() { + return [ + { + 'click .swimlane-height-apply': this.applySwimlaneHeight, + 'click .swimlane-height-error': Popup.open('swimlaneHeightError'), + }, + ]; + }, +}).register('setSwimlaneHeightPopup'); diff --git a/client/components/swimlanes/swimlanes.css b/client/components/swimlanes/swimlanes.css index 39aa42031..60d3fa0b0 100644 --- a/client/components/swimlanes/swimlanes.css +++ b/client/components/swimlanes/swimlanes.css @@ -116,6 +116,9 @@ left: 87vw; font-size: 24px; } +#js-swimlane-height-edit .swimlane-height-error { + display: none; +} .list-group { height: 100%; } diff --git a/client/components/swimlanes/swimlanes.jade b/client/components/swimlanes/swimlanes.jade index e517da882..02f8e8ec7 100644 --- a/client/components/swimlanes/swimlanes.jade +++ b/client/components/swimlanes/swimlanes.jade @@ -2,7 +2,8 @@ template(name="swimlane") .swimlane +swimlaneHeader unless collapseSwimlane - .swimlane.js-lists.js-swimlane(id="swimlane-{{_id}}") + .swimlane.js-lists.js-swimlane(id="swimlane-{{_id}}" + style="height:{{swimlaneHeight}}px;") if isMiniScreen if currentListIsInThisSwimlane _id +list(currentList) diff --git a/client/components/swimlanes/swimlanes.js b/client/components/swimlanes/swimlanes.js index cafb68649..82af48c1d 100644 --- a/client/components/swimlanes/swimlanes.js +++ b/client/components/swimlanes/swimlanes.js @@ -223,6 +223,12 @@ BlazeComponent.extendComponent({ }, ]; }, + + swimlaneHeight() { + const user = Meteor.user(); + const swimlane = Template.currentData(); + return user.getSwimlaneHeight(swimlane.boardId, swimlane._id); + }, }).register('swimlane'); BlazeComponent.extendComponent({ diff --git a/imports/i18n/data/en.i18n.json b/imports/i18n/data/en.i18n.json index 0f3e8326d..76f2c66b3 100644 --- a/imports/i18n/data/en.i18n.json +++ b/imports/i18n/data/en.i18n.json @@ -85,6 +85,14 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "setListWidthPopup-title": "Set List Width", + "set-list-width": "Set List Width", + "set-list-width-value": "List Width (pixels)", + "list-width-error-message": "List width must be a positive integer >=100. TODO(mark-i-m): hard-coded constants", + "setSwimlaneHeightPopup-title": "Set Swimlane Height", + "set-swimlane-height": "Set Swimlane Height", + "set-swimlane-height-value": "Swimlane Height (pixels)", + "swimlane-height-error-message": "Swimlane height must be a positive integer >=100. TODO(mark-i-m): hard-coded constants", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Add Checklist", diff --git a/models/lists.js b/models/lists.js index 2925736d3..789f0f2de 100644 --- a/models/lists.js +++ b/models/lists.js @@ -80,21 +80,6 @@ Lists.attachSchema( // XXX We should probably provide a default optional: true, }, - width: { - /** - * list width, default 270px - */ - type: String, - defaultValue: '270px', - optional: true, - }, - height: { - /** - * list height - */ - type: String, - optional: true, - }, updatedAt: { /** * last update of the list diff --git a/models/users.js b/models/users.js index 0f5587420..4e3056ede 100644 --- a/models/users.js +++ b/models/users.js @@ -422,6 +422,24 @@ Users.attachSchema( type: String, defaultValue: '', }, + 'profile.listWidths': { + /** + * User-specified width of each list (or nothing if default). + * profile[boardId][listId] = width; + */ + type: Object, + defaultValue: {}, + blackbox: true, + }, + 'profile.swimlaneHeights': { + /** + * User-specified heights of each swimlane (or nothing if default). + * profile[boardId][swimlaneId] = height; + */ + type: Object, + defaultValue: {}, + blackbox: true, + }, services: { /** * services field of the user @@ -758,6 +776,32 @@ Users.helpers({ return this._getListSortBy()[1]; }, + getListWidths() { + const { listWidths = {} } = this.profile || {}; + return listWidths; + }, + getListWidth(boardId, listId) { + const listWidths = this.getListWidths(); + if (listWidths[boardId] && listWidths[boardId][listId]) { + return listWidths[boardId][listId]; + } else { + return 270; //TODO(mark-i-m): default? + } + }, + + getSwimlaneHeights() { + const { swimlaneHeights = {} } = this.profile || {}; + return swimlaneHeights; + }, + getSwimlaneHeight(boardId, listId) { + const swimlaneHeights = this.getSwimlaneHeights(); + if (swimlaneHeights[boardId] && swimlaneHeights[boardId][listId]) { + return swimlaneHeights[boardId][listId]; + } else { + return 270; //TODO(mark-i-m): default? + } + }, + /** returns all confirmed move and copy dialog field values *
  • the board, swimlane and list id is stored for each board */ @@ -1135,6 +1179,32 @@ Users.mutations({ }, }; }, + + setListWidth(boardId, listId, width) { + let currentWidths = this.getListWidths(); + if (!currentWidths[boardId]) { + currentWidths[boardId] = {}; + } + currentWidths[boardId][listId] = width; + return { + $set: { + 'profile.listWidths': currentWidths, + }, + }; + }, + + setSwimlaneHeight(boardId, swimlaneId, height) { + let currentHeights = this.getSwimlaneHeights(); + if (!currentHeights[boardId]) { + currentHeights[boardId] = {}; + } + currentHeights[boardId][swimlaneId] = height; + return { + $set: { + 'profile.swimlaneHeights': currentHeights, + }, + }; + }, }); Meteor.methods({ @@ -1178,6 +1248,20 @@ Meteor.methods({ check(startDay, Number); ReactiveCache.getCurrentUser().setStartDayOfWeek(startDay); }, + applyListWidth(boardId, listId, width) { + check(boardId, String); + check(listId, String); + check(width, Number); + const user = Meteor.user(); + user.setListWidth(boardId, listId, width); + }, + applySwimlaneHeight(boardId, swimlaneId, height) { + check(boardId, String); + check(swimlaneId, String); + check(height, Number); + const user = Meteor.user(); + user.setSwimlaneHeight(boardId, swimlaneId, height); + }, }); if (Meteor.isServer) {