1
0
mirror of https://github.com/mattermost/focalboard.git synced 2025-04-11 11:19:56 +02:00

275 lines
7.9 KiB
TypeScript
Raw Normal View History

import { Block } from "./blocks/block"
2020-10-15 12:22:38 -07:00
import { Board, IPropertyOption, IPropertyTemplate } from "./blocks/board"
import { BoardView } from "./blocks/boardView"
import { Card } from "./blocks/card"
2020-10-08 09:21:27 -07:00
import { CardFilter } from "./cardFilter"
import octoClient from "./octoClient"
2020-10-08 09:21:27 -07:00
import { IBlock } from "./octoTypes"
import { OctoUtils } from "./octoUtils"
2020-10-08 09:21:27 -07:00
import { Utils } from "./utils"
2020-10-14 17:35:15 -07:00
type Group = { option: IPropertyOption, cards: Card[] }
2020-10-08 09:21:27 -07:00
class BoardTree {
board!: Board
views: BoardView[] = []
2020-10-14 17:35:15 -07:00
cards: Card[] = []
emptyGroupCards: Card[] = []
2020-10-08 09:21:27 -07:00
groups: Group[] = []
activeView?: BoardView
groupByProperty?: IPropertyTemplate
2020-10-08 20:09:34 -07:00
private searchText?: string
2020-10-14 17:35:15 -07:00
private allCards: Card[] = []
2020-10-08 09:21:27 -07:00
get allBlocks(): IBlock[] {
return [this.board, ...this.views, ...this.allCards]
}
constructor(private boardId: string) {
2020-10-08 09:21:27 -07:00
}
async sync() {
const blocks = await octoClient.getSubtree(this.boardId)
this.rebuild(OctoUtils.hydrateBlocks(blocks))
2020-10-08 09:21:27 -07:00
}
private rebuild(blocks: Block[]) {
this.board = blocks.find(block => block.type === "board") as Board
this.views = blocks.filter(block => block.type === "view") as BoardView[]
this.allCards = blocks.filter(block => block.type === "card") as Card[]
2020-10-08 09:21:27 -07:00
this.cards = []
this.ensureMinimumSchema()
}
private async ensureMinimumSchema() {
const { board } = this
let didChange = false
// At least one select property
const selectProperties = board.cardProperties.find(o => o.type === "select")
if (!selectProperties) {
const property: IPropertyTemplate = {
id: Utils.createGuid(),
name: "Status",
type: "select",
options: []
}
board.cardProperties.push(property)
didChange = true
}
// At least one view
if (this.views.length < 1) {
const view = new BoardView()
view.parentId = board.id
view.groupById = board.cardProperties.find(o => o.type === "select")?.id
this.views.push(view)
didChange = true
}
return didChange
}
setActiveView(viewId: string) {
this.activeView = this.views.find(o => o.id === viewId)
if (!this.activeView) {
Utils.logError(`Cannot find BoardView: ${viewId}`)
this.activeView = this.views[0]
}
// Fix missing group by (e.g. for new views)
if (this.activeView.viewType === "board" && !this.activeView.groupById) {
this.activeView.groupById = this.board.cardProperties.find(o => o.type === "select")?.id
}
this.applyFilterSortAndGroup()
}
2020-10-08 20:09:34 -07:00
getSearchText(): string | undefined {
return this.searchText
}
setSearchText(text?: string) {
this.searchText = text
this.applyFilterSortAndGroup()
}
2020-10-08 09:21:27 -07:00
applyFilterSortAndGroup() {
2020-10-14 17:35:15 -07:00
Utils.assert(this.allCards !== undefined)
2020-10-08 09:21:27 -07:00
this.cards = this.filterCards(this.allCards)
2020-10-14 17:35:15 -07:00
Utils.assert(this.cards !== undefined)
2020-10-08 20:09:34 -07:00
this.cards = this.searchFilterCards(this.cards)
2020-10-14 17:35:15 -07:00
Utils.assert(this.cards !== undefined)
2020-10-08 09:21:27 -07:00
this.cards = this.sortCards(this.cards)
2020-10-14 17:35:15 -07:00
Utils.assert(this.cards !== undefined)
2020-10-08 09:21:27 -07:00
if (this.activeView.groupById) {
this.setGroupByProperty(this.activeView.groupById)
} else {
Utils.assert(this.activeView.viewType !== "board")
}
2020-10-14 17:35:15 -07:00
Utils.assert(this.cards !== undefined)
2020-10-08 09:21:27 -07:00
}
2020-10-14 17:35:15 -07:00
private searchFilterCards(cards: Card[]): Card[] {
2020-10-12 08:55:59 -07:00
const searchText = this.searchText?.toLocaleLowerCase()
2020-10-08 20:09:34 -07:00
if (!searchText) { return cards.slice() }
return cards.filter(card => {
2020-10-12 08:55:59 -07:00
if (card.title?.toLocaleLowerCase().indexOf(searchText) !== -1) { return true }
2020-10-08 20:09:34 -07:00
})
}
2020-10-08 09:21:27 -07:00
private setGroupByProperty(propertyId: string) {
const { board } = this
let property = board.cardProperties.find(o => o.id === propertyId)
// TODO: Handle multi-select
if (!property || property.type !== "select") {
Utils.logError(`this.view.groupById card property not found: ${propertyId}`)
property = board.cardProperties.find(o => o.type === "select")
Utils.assertValue(property)
}
this.groupByProperty = property
this.groupCards()
}
private groupCards() {
this.groups = []
const groupByPropertyId = this.groupByProperty.id
this.emptyGroupCards = this.cards.filter(o => {
2020-10-13 16:49:29 -07:00
const propertyValue = o.properties[groupByPropertyId]
return !propertyValue || !this.groupByProperty.options.find(option => option.value === propertyValue)
2020-10-08 09:21:27 -07:00
})
const propertyOptions = this.groupByProperty.options || []
for (const option of propertyOptions) {
const cards = this.cards
.filter(o => {
2020-10-13 16:49:29 -07:00
const propertyValue = o.properties[groupByPropertyId]
return propertyValue && propertyValue === option.value
2020-10-08 09:21:27 -07:00
})
const group: Group = {
option,
cards
}
this.groups.push(group)
}
}
2020-10-14 17:35:15 -07:00
private filterCards(cards: Card[]): Card[] {
2020-10-08 09:21:27 -07:00
const { board } = this
const filterGroup = this.activeView?.filter
if (!filterGroup) { return cards.slice() }
return CardFilter.applyFilterGroup(filterGroup, board.cardProperties, cards)
}
2020-10-14 17:35:15 -07:00
private sortCards(cards: Card[]): Card[] {
2020-10-08 09:21:27 -07:00
if (!this.activeView) { Utils.assertFailure(); return cards }
const { board } = this
const { sortOptions } = this.activeView
2020-10-14 17:35:15 -07:00
let sortedCards: Card[] = []
2020-10-08 09:21:27 -07:00
if (sortOptions.length < 1) {
Utils.log(`Default sort`)
sortedCards = cards.sort((a, b) => {
const aValue = a.title || ""
const bValue = b.title || ""
// Always put empty values at the bottom
if (aValue && !bValue) { return -1 }
if (bValue && !aValue) { return 1 }
if (!aValue && !bValue) { return a.createAt - b.createAt }
return a.createAt - b.createAt
})
} else {
sortOptions.forEach(sortOption => {
if (sortOption.propertyId === "__name") {
Utils.log(`Sort by name`)
sortedCards = cards.sort((a, b) => {
const aValue = a.title || ""
const bValue = b.title || ""
// Always put empty values at the bottom, newest last
if (aValue && !bValue) { return -1 }
if (bValue && !aValue) { return 1 }
if (!aValue && !bValue) { return a.createAt - b.createAt }
let result = aValue.localeCompare(bValue)
if (sortOption.reversed) { result = -result }
return result
})
} else {
const sortPropertyId = sortOption.propertyId
const template = board.cardProperties.find(o => o.id === sortPropertyId)
2020-10-14 17:35:15 -07:00
if (!template) {
Utils.logError(`Missing template for property id: ${sortPropertyId}`)
return cards.slice()
}
Utils.log(`Sort by ${template?.name}`)
2020-10-08 09:21:27 -07:00
sortedCards = cards.sort((a, b) => {
// Always put cards with no titles at the bottom
if (a.title && !b.title) { return -1 }
if (b.title && !a.title) { return 1 }
if (!a.title && !b.title) { return a.createAt - b.createAt }
2020-10-13 16:49:29 -07:00
const aValue = a.properties[sortPropertyId] || ""
const bValue = b.properties[sortPropertyId] || ""
2020-10-08 09:21:27 -07:00
let result = 0
if (template.type === "select") {
// Always put empty values at the bottom
if (aValue && !bValue) { return -1 }
if (bValue && !aValue) { return 1 }
if (!aValue && !bValue) { return a.createAt - b.createAt }
// Sort by the option order (not alphabetically by value)
const aOrder = template.options.findIndex(o => o.value === aValue)
const bOrder = template.options.findIndex(o => o.value === bValue)
result = aOrder - bOrder
} else if (template.type === "number" || template.type === "date") {
// Always put empty values at the bottom
if (aValue && !bValue) { return -1 }
if (bValue && !aValue) { return 1 }
if (!aValue && !bValue) { return a.createAt - b.createAt }
result = Number(aValue) - Number(bValue)
} else if (template.type === "createdTime") {
result = a.createAt - b.createAt
} else if (template.type === "updatedTime") {
result = a.updateAt - b.updateAt
} else {
// Text-based sort
// Always put empty values at the bottom
if (aValue && !bValue) { return -1 }
if (bValue && !aValue) { return 1 }
if (!aValue && !bValue) { return a.createAt - b.createAt }
result = aValue.localeCompare(bValue)
}
if (sortOption.reversed) { result = -result }
return result
})
}
})
}
return sortedCards
}
}
export { BoardTree }