Allow no-option column to be hidden

This commit is contained in:
Chen-I Lim 2020-10-26 14:35:45 -07:00
parent e82e9dba89
commit 95955a01f8
4 changed files with 117 additions and 134 deletions

View file

@ -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) {

View file

@ -105,10 +105,8 @@ class BoardComponent extends React.Component<Props, State> {
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 (
<div
@ -142,36 +140,7 @@ class BoardComponent extends React.Component<Props, State> {
className='octo-board-header'
id='mainBoardHeader'
>
{/* No value */}
<div className='octo-board-header-cell'>
<div
className='octo-label'
title={intl.formatMessage({
id: 'BoardComponent.no-property-title',
defaultMessage: 'Items with an empty {property} property will go here. This column cannot be removed.',
}, {property: boardTree.groupByProperty?.name})}
>
<FormattedMessage
id='BoardComponent.no-property'
defaultMessage='No {property}'
values={{
property: boardTree.groupByProperty?.name,
}}
/>
</div>
<Button>{`${boardTree.emptyGroupCards.length}`}</Button>
<div className='octo-spacer'/>
<Button><div className='imageOptions'/></Button>
<Button
onClick={() => {
this.addCard(undefined)
}}
><div className='imageAdd'/></Button>
</div>
{/* Visible column headers */}
{/* Column headers */}
{visibleGroups.map((group) => this.renderColumnHeader(group))}
@ -203,30 +172,11 @@ class BoardComponent extends React.Component<Props, State> {
className='octo-board-body'
id='mainBoardBody'
>
{/* No value column */}
<BoardColumn
onDrop={() => this.onDropToColumn(undefined)}
>
{boardTree.emptyGroupCards.map((card) => this.renderCard(card, visiblePropertyTemplates))}
<Button
onClick={() => {
this.addCard(undefined)
}}
>
<FormattedMessage
id='BoardComponent.neww'
defaultMessage='+ New'
/>
</Button>
</BoardColumn>
{/* Columns */}
{visibleGroups.map((group) => (
<BoardColumn
key={group.option.id}
key={group.option.id || 'empty'}
onDrop={() => this.onDropToColumn(group.option)}
>
{group.cards.map((card) => this.renderCard(card, visiblePropertyTemplates))}
@ -280,6 +230,49 @@ class BoardComponent extends React.Component<Props, State> {
const {boardTree, intl} = this.props
const {activeView} = boardTree
if (!group.option.id) {
// Empty group
return (
<div
key='empty'
className='octo-board-header-cell'
>
<div
className='octo-label'
title={intl.formatMessage({
id: 'BoardComponent.no-property-title',
defaultMessage: 'Items with an empty {property} property will go here. This column cannot be removed.',
}, {property: boardTree.groupByProperty?.name})}
>
<FormattedMessage
id='BoardComponent.no-property'
defaultMessage='No {property}'
values={{
property: boardTree.groupByProperty?.name,
}}
/>
</div>
<Button>{`${group.cards.length}`}</Button>
<div className='octo-spacer'/>
<MenuWrapper>
<Button><div className='imageOptions'/></Button>
<Menu>
<Menu.Text
id='hide'
name={intl.formatMessage({id: 'BoardComponent.hide', defaultMessage: 'Hide'})}
onClick={() => mutator.hideViewColumn(activeView, '')}
/>
</Menu>
</MenuWrapper>
<Button
onClick={() => {
this.addCard(undefined)
}}
><div className='imageAdd'/></Button>
</div>
)
}
const ref = React.createRef<HTMLDivElement>()
return (
<div
@ -365,7 +358,7 @@ class BoardComponent extends React.Component<Props, State> {
return (
<div
ref={ref}
key={group.option.id}
key={group.option.id || 'empty'}
className='octo-board-hidden-item'
onDragOver={(e) => {
if (this.draggedCards?.length < 1) {
@ -399,7 +392,7 @@ class BoardComponent extends React.Component<Props, State> {
>
<MenuWrapper>
<div
key={group.option.id}
key={group.option.id || 'empty'}
className={`octo-label ${group.option.color}`}
>
{group.option.value}
@ -433,7 +426,11 @@ class BoardComponent extends React.Component<Props, State> {
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})

View file

@ -384,22 +384,22 @@ class Mutator {
}
async hideViewColumn(view: BoardView, columnOptionId: string): Promise<void> {
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<void> {
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')
}

View file

@ -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[] {