Allow no-option column to be hidden
This commit is contained in:
parent
e82e9dba89
commit
95955a01f8
4 changed files with 117 additions and 134 deletions
|
@ -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) {
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
|
||||
|
|
|
@ -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[] {
|
||||
|
|
Loading…
Reference in a new issue