diff --git a/webapp/src/blocks/boardView.ts b/webapp/src/blocks/boardView.ts index 2eb675b81..625710d20 100644 --- a/webapp/src/blocks/boardView.ts +++ b/webapp/src/blocks/boardView.ts @@ -13,7 +13,8 @@ interface BoardView extends IBlock { readonly groupById: string readonly sortOptions: readonly ISortOption[] readonly visiblePropertyIds: readonly string[] - readonly hiddenColumnIds: readonly string[] + readonly visibleOptionIds: readonly string[] + readonly hiddenOptionIds: readonly string[] readonly filter: FilterGroup | undefined } @@ -46,11 +47,18 @@ class MutableBoardView extends MutableBlock { this.fields.visiblePropertyIds = value } - get hiddenColumnIds(): string[] { - return this.fields.hiddenColumnIds + get visibleOptionIds(): string[] { + return this.fields.visibleOptionIds } - set hiddenColumnIds(value: string[]) { - this.fields.hiddenColumnIds = value + set visibleOptionIds(value: string[]) { + this.fields.visibleOptionIds = value + } + + get hiddenOptionIds(): string[] { + return this.fields.hiddenOptionIds + } + set hiddenOptionIds(value: string[]) { + this.fields.hiddenOptionIds = value } get filter(): FilterGroup | undefined { @@ -67,7 +75,8 @@ class MutableBoardView extends MutableBlock { this.sortOptions = block.fields?.sortOptions?.map((o: ISortOption) => ({...o})) || [] // Deep clone this.visiblePropertyIds = block.fields?.visiblePropertyIds?.slice() || [] - this.hiddenColumnIds = block.fields?.hiddenColumnIds?.slice() || [] + this.visibleOptionIds = block.fields?.visibleOptionIds?.slice() || [] + this.hiddenOptionIds = block.fields?.hiddenOptionIds?.slice() || [] this.filter = new FilterGroup(block.fields?.filter) if (!this.viewType) { diff --git a/webapp/src/components/boardComponent.tsx b/webapp/src/components/boardComponent.tsx index 1011a3b2e..ce6bd33a4 100644 --- a/webapp/src/components/boardComponent.tsx +++ b/webapp/src/components/boardComponent.tsx @@ -105,10 +105,8 @@ class BoardComponent extends React.Component { const propertyValues = boardTree.groupByProperty?.options || [] Utils.log(`${propertyValues.length} propertyValues`) - const {board, activeView} = boardTree + const {board, activeView, visibleGroups, hiddenGroups} = boardTree const visiblePropertyTemplates = board.cardProperties.filter((template) => activeView.visiblePropertyIds.includes(template.id)) - const visibleGroups = boardTree.groups.filter((group) => !group.isHidden) - const hiddenGroups = boardTree.groups.filter((group) => group.isHidden) return (
{ className='octo-board-header' id='mainBoardHeader' > - - {/* No value */} - -
-
- -
- -
- - -
- - {/* Visible column headers */} + {/* Column headers */} {visibleGroups.map((group) => this.renderColumnHeader(group))} @@ -203,30 +172,11 @@ class BoardComponent extends React.Component { className='octo-board-body' id='mainBoardBody' > - - {/* No value column */} - - this.onDropToColumn(undefined)} - > - {boardTree.emptyGroupCards.map((card) => this.renderCard(card, visiblePropertyTemplates))} - - - {/* Columns */} {visibleGroups.map((group) => ( this.onDropToColumn(group.option)} > {group.cards.map((card) => this.renderCard(card, visiblePropertyTemplates))} @@ -280,6 +230,49 @@ class BoardComponent extends React.Component { const {boardTree, intl} = this.props const {activeView} = boardTree + if (!group.option.id) { + // Empty group + return ( +
+
+ +
+ +
+ + + + mutator.hideViewColumn(activeView, '')} + /> + + + +
+ ) + } + const ref = React.createRef() return (
{ return (
{ if (this.draggedCards?.length < 1) { @@ -399,7 +392,7 @@ class BoardComponent extends React.Component { >
{group.option.value} @@ -433,7 +426,11 @@ class BoardComponent extends React.Component { card.properties = CardFilter.propertiesThatMeetFilterGroup(activeView.filter, board.cardProperties) card.icon = BlockIcons.shared.randomIcon() if (boardTree.groupByProperty) { - card.properties[boardTree.groupByProperty.id] = groupByOptionId + if (groupByOptionId) { + card.properties[boardTree.groupByProperty.id] = groupByOptionId + } else { + delete card.properties[boardTree.groupByProperty.id] + } } await mutator.insertBlock(card, 'add card', async () => { this.setState({shownCard: card}) diff --git a/webapp/src/mutator.ts b/webapp/src/mutator.ts index 9cf617320..7b8c80a47 100644 --- a/webapp/src/mutator.ts +++ b/webapp/src/mutator.ts @@ -384,22 +384,22 @@ class Mutator { } async hideViewColumn(view: BoardView, columnOptionId: string): Promise { - if (view.hiddenColumnIds.includes(columnOptionId)) { + if (view.hiddenOptionIds.includes(columnOptionId)) { return } const newView = new MutableBoardView(view) - newView.hiddenColumnIds.push(columnOptionId) + newView.hiddenOptionIds.push(columnOptionId) await this.updateBlock(newView, view, 'hide column') } async unhideViewColumn(view: BoardView, columnOptionId: string): Promise { - if (!view.hiddenColumnIds.includes(columnOptionId)) { + if (!view.hiddenOptionIds.includes(columnOptionId)) { return } const newView = new MutableBoardView(view) - newView.hiddenColumnIds = newView.hiddenColumnIds.filter((o) => o !== columnOptionId) + newView.hiddenOptionIds = newView.hiddenOptionIds.filter((o) => o !== columnOptionId) await this.updateBlock(newView, view, 'show column') } diff --git a/webapp/src/viewModel/boardTree.ts b/webapp/src/viewModel/boardTree.ts index 747a29d2e..a39b7b5ab 100644 --- a/webapp/src/viewModel/boardTree.ts +++ b/webapp/src/viewModel/boardTree.ts @@ -12,7 +12,6 @@ import {Utils} from '../utils' type Group = { option: IPropertyOption cards: Card[] - isHidden: boolean } interface BoardTree { @@ -20,8 +19,8 @@ interface BoardTree { readonly views: readonly BoardView[] readonly cards: readonly Card[] readonly allCards: readonly Card[] - readonly emptyGroupCards: readonly Card[] - readonly groups: readonly Group[] + readonly visibleGroups: readonly Group[] + readonly hiddenGroups: readonly Group[] readonly allBlocks: readonly IBlock[] readonly activeView?: BoardView @@ -34,8 +33,8 @@ class MutableBoardTree implements BoardTree { board!: MutableBoard views: MutableBoardView[] = [] cards: MutableCard[] = [] - emptyGroupCards: MutableCard[] = [] - groups: Group[] = [] + visibleGroups: Group[] = [] + hiddenGroups: Group[] = [] activeView?: MutableBoardView groupByProperty?: IPropertyTemplate @@ -92,46 +91,6 @@ class MutableBoardTree implements BoardTree { didChange = true } - /* - // TODO: Remove fixup code. Fix board cardProperties schema - for (const template of this.board.cardProperties) { - if (template.type === 'select') { - for (const option of template.options) { - if (!option.id) { - option.id = Utils.createGuid() - Utils.log(`FIXUP template ${template.name}, option: ${option.value}, guid: ${option.id}`) - } - } - } - } - - // TODO: Remove fixup code. Fix card schema - for (const card of this.allCards) { - if (card.schema < 2) { - card.schema = 2 - for (const propertyId in card.properties) { - if (!Object.prototype.hasOwnProperty.call(card.properties, propertyId)) { - continue - } - const template = board.cardProperties.find((o) => o.id === propertyId) - if (!template) { - Utils.log(`No template with id: ${propertyId}`) - } - if (template?.type === 'select') { - const value = card.properties[propertyId] - const option = template.options.find((o) => o.value === value) - if (!option) { - Utils.assertFailure(`No option for template: ${template.name} with option value: ${value}`) - } - if (option) { - card.properties[propertyId] = option?.id - } - Utils.log(`FIXUP card ${template.name}, option: ${option?.value}, guid: ${option?.id}`) - } - } - } - } -*/ return didChange } @@ -146,6 +105,7 @@ class MutableBoardTree implements BoardTree { if (this.activeView.viewType === 'board' && !this.activeView.groupById) { this.activeView.groupById = this.board.cardProperties.find((o) => o.type === 'select')?.id } + this.applyFilterSortAndGroup() } @@ -207,32 +167,49 @@ class MutableBoardTree implements BoardTree { } private groupCards() { - this.groups = [] + const {activeView, groupByProperty} = this - const groupByPropertyId = this.groupByProperty.id + const unassignedOptionIds = groupByProperty.options + .filter(o => !activeView.visibleOptionIds.includes(o.id) && !activeView.hiddenOptionIds.includes(o.id)) + .map(o => o.id) + const visibleOptionIds = [...activeView.visibleOptionIds, ...unassignedOptionIds] + const {hiddenOptionIds} = activeView - this.emptyGroupCards = this.cards.filter((o) => { - const optionId = o.properties[groupByPropertyId] - return !optionId || !this.groupByProperty.options.find((option) => option.id === optionId) - }) - - const propertyOptions = this.groupByProperty.options || [] - for (const option of propertyOptions) { - const cards = this.cards. - filter((o) => { - const optionId = o.properties[groupByPropertyId] - return optionId && optionId === option.id - }) - - const isHidden = this.activeView.hiddenColumnIds.includes(option.id) - const group: Group = { - option, - cards, - isHidden, - } - - this.groups.push(group) + // If the empty group positon is not explicitly specified, make it the first visible column + if (!activeView.visibleOptionIds.includes('') && !activeView.hiddenOptionIds.includes('')) { + visibleOptionIds.unshift('') } + + this.visibleGroups = this.groupCardsByOptions(visibleOptionIds, groupByProperty) + this.hiddenGroups = this.groupCardsByOptions(hiddenOptionIds, groupByProperty) + } + + private groupCardsByOptions(optionIds: string[], groupByProperty: IPropertyTemplate) { + const groups = [] + for (const optionId of optionIds) { + if (optionId) { + const option = groupByProperty.options.find(o => o.id === optionId) + const cards = this.cards.filter((o) => optionId === o.properties[groupByProperty.id]) + const group: Group = { + option, + cards + } + groups.push(group) + } else { + // Empty group + const emptyGroupCards = this.cards.filter((o) => { + const optionId = o.properties[groupByProperty.id] + return !optionId || !this.groupByProperty.options.find((option) => option.id === optionId) + }) + const group: Group = { + option: {id: '', value: `No ${groupByProperty.name}`, color: ''}, + cards: emptyGroupCards + } + groups.push(group) + } + } + + return groups } private filterCards(cards: MutableCard[]): Card[] {