- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
- jsx-a11y/no-static-element-interactions */}
- {cardIds.length > 0 ? t('action.addAnotherCard') : t('action.addCard')}
+ {filteredCardIds.length > 0 ? t('action.addAnotherCard') : t('action.addCard')}
)}
@@ -155,8 +229,11 @@ List.propTypes = {
id: PropTypes.string.isRequired,
index: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
+ isCollapsed: PropTypes.bool.isRequired,
isPersisted: PropTypes.bool.isRequired,
cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
+ isFiltered: PropTypes.bool.isRequired,
+ filteredCardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
canEdit: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
diff --git a/client/src/components/List/List.module.scss b/client/src/components/List/List.module.scss
index b1a7096..0a78913 100644
--- a/client/src/components/List/List.module.scss
+++ b/client/src/components/List/List.module.scss
@@ -74,7 +74,7 @@
.header {
outline: none;
- padding: 6px 36px 4px 8px;
+ padding: 4px 36px 4px 10px;
position: relative;
&:hover .target {
@@ -82,6 +82,15 @@
}
}
+ .headerCollapsed {
+ outline: none;
+ height: calc(100vh - 182px);
+
+ &:hover .target {
+ opacity: 1;
+ }
+ }
+
.headerEditable {
cursor: pointer;
}
@@ -109,12 +118,53 @@
color: #17394d;
font-weight: bold;
line-height: 20px;
- max-height: 256px;
+ max-height: 82px;
outline: none;
overflow: hidden;
overflow-wrap: break-word;
- padding: 4px 8px;
word-break: break-word;
+ display: -webkit-box;
+ -webkit-line-clamp: 4;
+ -webkit-box-orient: vertical;
+ }
+
+ .headerNameCollapsed {
+ color: #17394d;
+ font-weight: bold;
+ outline: none;
+ overflow: hidden;
+ writing-mode: vertical-rl;
+ padding: 0px 5px 10px 5px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-height: 85%;
+ }
+
+ .headerCollapseButton {
+ width: 10px;
+ height: 30px;
+ padding: 2px 0px 0px 2px;
+ position: absolute;
+ left: 2px;
+ top: 2px;
+ }
+
+ .headerCollapseButtonCollapsed {
+ height: 30px;
+ text-align: center;
+ }
+
+ .headerCardsCount {
+ color: #798d99;
+ font-size: 12px;
+ }
+
+ .headerCardsCountCollapsed {
+ color: #798d99;
+ font-size: 12px;
+ max-height: 10%;
+ writing-mode: vertical-rl;
+ padding: 0px 5px 10px 5px;
}
.innerWrapper {
@@ -122,6 +172,11 @@
width: 272px;
}
+ .innerWrapperCollapsed {
+ margin-right: 8px;
+ width: 30px;
+ }
+
.outerWrapper {
background: #dfe3e6;
border-radius: 3px;
diff --git a/client/src/containers/BoardActionsContainer.js b/client/src/containers/BoardActionsContainer.js
index 40170cb..b7d568e 100644
--- a/client/src/containers/BoardActionsContainer.js
+++ b/client/src/containers/BoardActionsContainer.js
@@ -7,6 +7,9 @@ import { BoardMembershipRoles } from '../constants/Enums';
import BoardActions from '../components/BoardActions';
const mapStateToProps = (state) => {
+ const listIds = selectors.selectListIdsForCurrentBoard(state);
+ const listCardsCount = listIds.map((list) => selectors.selectCardIdsByListId(state, list).length);
+ const cardCount = listCardsCount.reduce((sum, count) => sum + count, 0);
const allUsers = selectors.selectUsers(state);
const isCurrentUserManager = selectors.selectIsCurrentUserManagerForCurrentProject(state);
const memberships = selectors.selectMembershipsForCurrentBoard(state);
@@ -19,6 +22,7 @@ const mapStateToProps = (state) => {
!!currentUserMembership && currentUserMembership.role === BoardMembershipRoles.EDITOR;
return {
+ cardCount,
memberships,
labels,
filterUsers,
diff --git a/client/src/containers/ListContainer.js b/client/src/containers/ListContainer.js
index cb2f6a0..498fe2b 100755
--- a/client/src/containers/ListContainer.js
+++ b/client/src/containers/ListContainer.js
@@ -9,10 +9,14 @@ import List from '../components/List';
const makeMapStateToProps = () => {
const selectListById = selectors.makeSelectListById();
const selectCardIdsByListId = selectors.makeSelectCardIdsByListId();
+ const selectIsFilteredByListId = selectors.makeSelectIsFilteredByListId();
+ const selectFilteredCardIdsByListId = selectors.makeSelectFilteredCardIdsByListId();
return (state, { id, index }) => {
- const { name, isPersisted } = selectListById(state, id);
+ const { name, isPersisted, isCollapsed } = selectListById(state, id);
const cardIds = selectCardIdsByListId(state, id);
+ const isFiltered = selectIsFilteredByListId(state, id);
+ const filteredCardIds = selectFilteredCardIdsByListId(state, id);
const currentUserMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
const isCurrentUserEditor =
@@ -22,8 +26,11 @@ const makeMapStateToProps = () => {
id,
index,
name,
+ isCollapsed,
isPersisted,
cardIds,
+ isFiltered,
+ filteredCardIds,
canEdit: isCurrentUserEditor,
};
};
diff --git a/client/src/locales/en/core.js b/client/src/locales/en/core.js
index 0eb0ae3..b2f32f1 100644
--- a/client/src/locales/en/core.js
+++ b/client/src/locales/en/core.js
@@ -162,6 +162,9 @@ export default {
version: 'Version',
viewer: 'Viewer',
writeComment: 'Write a comment...',
+ card: 'card',
+ cards: 'cards',
+ of: 'of',
},
action: {
diff --git a/client/src/models/List.js b/client/src/models/List.js
index b634a66..b9973f1 100755
--- a/client/src/models/List.js
+++ b/client/src/models/List.js
@@ -10,6 +10,7 @@ export default class extends BaseModel {
id: attr(),
position: attr(),
name: attr(),
+ isCollapsed: attr(),
boardId: fk({
to: 'Board',
as: 'board',
@@ -84,6 +85,16 @@ export default class extends BaseModel {
return this.cards.orderBy('position');
}
+ getOrderedCardsModelArray() {
+ return this.getOrderedCardsQuerySet().toModelArray();
+ }
+
+ getIsFiltered() {
+ const filterUserIds = this.board.filterUsers.toRefArray().map((user) => user.id);
+ const filterLabelIds = this.board.filterLabels.toRefArray().map((label) => label.id);
+ return filterUserIds.length > 0 || filterLabelIds.length > 0;
+ }
+
getFilteredOrderedCardsModelArray() {
let cardModels = this.getOrderedCardsQuerySet().toModelArray();
@@ -93,7 +104,6 @@ export default class extends BaseModel {
if (filterUserIds.length > 0) {
cardModels = cardModels.filter((cardModel) => {
const users = cardModel.users.toRefArray();
-
return users.some((user) => filterUserIds.includes(user.id));
});
}
@@ -101,7 +111,6 @@ export default class extends BaseModel {
if (filterLabelIds.length > 0) {
cardModels = cardModels.filter((cardModel) => {
const labels = cardModel.labels.toRefArray();
-
return labels.some((label) => filterLabelIds.includes(label.id));
});
}
diff --git a/client/src/selectors/core.js b/client/src/selectors/core.js
index df7d42d..6d8cdc8 100755
--- a/client/src/selectors/core.js
+++ b/client/src/selectors/core.js
@@ -91,6 +91,7 @@ export const selectNextCardPosition = createSelector(
return listModel;
}
+ // eslint-disable-next-line prettier/prettier
return nextPosition(listModel.getFilteredOrderedCardsModelArray(), index, excludedId);
},
);
diff --git a/client/src/selectors/lists.js b/client/src/selectors/lists.js
index 1eddbd9..e918241 100644
--- a/client/src/selectors/lists.js
+++ b/client/src/selectors/lists.js
@@ -34,15 +34,53 @@ export const makeSelectCardIdsByListId = () =>
return listModel;
}
- return listModel.getFilteredOrderedCardsModelArray().map((cardModel) => cardModel.id);
+ return listModel.getOrderedCardsModelArray().map((cardModel) => cardModel.id);
},
);
export const selectCardIdsByListId = makeSelectCardIdsByListId();
+export const makeSelectIsFilteredByListId = () =>
+ createSelector(
+ orm,
+ (_, id) => id,
+ ({ List }, id) => {
+ const listModel = List.withId(id);
+
+ if (!listModel) {
+ return listModel;
+ }
+
+ return listModel.getIsFiltered();
+ },
+ );
+
+export const selectIsFilteredByListId = makeSelectIsFilteredByListId();
+
+export const makeSelectFilteredCardIdsByListId = () =>
+ createSelector(
+ orm,
+ (_, id) => id,
+ ({ List }, id) => {
+ const listModel = List.withId(id);
+
+ if (!listModel) {
+ return listModel;
+ }
+
+ return listModel.getFilteredOrderedCardsModelArray().map((cardModel) => cardModel.id);
+ },
+ );
+
+export const selectFilteredCardIdsByListId = makeSelectFilteredCardIdsByListId();
+
export default {
makeSelectListById,
selectListById,
makeSelectCardIdsByListId,
selectCardIdsByListId,
+ makeSelectIsFilteredByListId,
+ selectIsFilteredByListId,
+ makeSelectFilteredCardIdsByListId,
+ selectFilteredCardIdsByListId,
};
diff --git a/server/api/controllers/lists/create.js b/server/api/controllers/lists/create.js
index 9383f5b..49a9bce 100755
--- a/server/api/controllers/lists/create.js
+++ b/server/api/controllers/lists/create.js
@@ -22,6 +22,10 @@ module.exports = {
type: 'string',
required: true,
},
+ isCollapsed: {
+ type: 'boolean',
+ required: true,
+ },
},
exits: {
@@ -53,7 +57,7 @@ module.exports = {
throw Errors.NOT_ENOUGH_RIGHTS;
}
- const values = _.pick(inputs, ['position', 'name']);
+ const values = _.pick(inputs, ['position', 'name', 'isCollapsed']);
const list = await sails.helpers.lists.createOne.with({
values: {
diff --git a/server/api/controllers/lists/update.js b/server/api/controllers/lists/update.js
index 2cb34e2..b6e1493 100755
--- a/server/api/controllers/lists/update.js
+++ b/server/api/controllers/lists/update.js
@@ -21,6 +21,9 @@ module.exports = {
type: 'string',
isNotEmptyString: true,
},
+ isCollapsed: {
+ type: 'boolean',
+ },
},
exits: {
@@ -52,7 +55,7 @@ module.exports = {
throw Errors.NOT_ENOUGH_RIGHTS;
}
- const values = _.pick(inputs, ['position', 'name']);
+ const values = _.pick(inputs, ['position', 'name', 'isCollapsed']);
list = await sails.helpers.lists.updateOne.with({
values,
diff --git a/server/api/models/List.js b/server/api/models/List.js
index ce785b0..e2ee0ec 100755
--- a/server/api/models/List.js
+++ b/server/api/models/List.js
@@ -19,6 +19,11 @@ module.exports = {
type: 'string',
required: true,
},
+ isCollapsed: {
+ type: 'boolean',
+ required: true,
+ columnName: 'is_collapsed',
+ },
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
// ║╣ ║║║╠╩╗║╣ ║║╚═╗
diff --git a/server/db/migrations/20230112022500_add_list_isCollapsed.js b/server/db/migrations/20230112022500_add_list_isCollapsed.js
new file mode 100644
index 0000000..00b0a25
--- /dev/null
+++ b/server/db/migrations/20230112022500_add_list_isCollapsed.js
@@ -0,0 +1,9 @@
+module.exports.up = (knex) =>
+ knex.schema.alterTable('list', (table) => {
+ table.boolean('is_collapsed').notNullable().defaultTo(false);
+ });
+
+module.exports.down = (knex) =>
+ knex.schema.alterTable('list', (table) => {
+ table.dropColumn('is_collapsed');
+ });