Immutable view state objects
This commit is contained in:
parent
29fa94a937
commit
932de3a17f
7 changed files with 332 additions and 359 deletions
|
@ -8,7 +8,7 @@ import {Utils} from './utils'
|
|||
interface Archive {
|
||||
version: number
|
||||
date: number
|
||||
blocks: IBlock[]
|
||||
blocks: readonly IBlock[]
|
||||
}
|
||||
|
||||
class Archiver {
|
||||
|
|
|
@ -12,313 +12,279 @@ import {Utils} from './utils'
|
|||
|
||||
type Group = { option: IPropertyOption, cards: Card[] }
|
||||
|
||||
class BoardTree {
|
||||
board!: Board
|
||||
views: BoardView[] = []
|
||||
cards: Card[] = []
|
||||
emptyGroupCards: Card[] = []
|
||||
groups: Group[] = []
|
||||
interface BoardTree {
|
||||
readonly board: Board
|
||||
readonly views: readonly BoardView[]
|
||||
readonly cards: readonly Card[]
|
||||
readonly emptyGroupCards: readonly Card[]
|
||||
readonly groups: readonly Group[]
|
||||
readonly allBlocks: readonly IBlock[]
|
||||
|
||||
activeView?: BoardView
|
||||
groupByProperty?: IPropertyTemplate
|
||||
readonly activeView?: BoardView
|
||||
readonly groupByProperty?: IPropertyTemplate
|
||||
|
||||
private searchText?: string
|
||||
private allCards: Card[] = []
|
||||
get allBlocks(): IBlock[] {
|
||||
return [this.board, ...this.views, ...this.allCards]
|
||||
}
|
||||
|
||||
constructor(private boardId: string) {
|
||||
}
|
||||
|
||||
async sync(): Promise<void> {
|
||||
const blocks = await octoClient.getSubtree(this.boardId)
|
||||
this.rebuild(OctoUtils.hydrateBlocks(blocks))
|
||||
}
|
||||
|
||||
private rebuild(blocks: Block[]): void {
|
||||
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[]
|
||||
this.cards = []
|
||||
|
||||
this.ensureMinimumSchema()
|
||||
}
|
||||
|
||||
private async ensureMinimumSchema(): Promise<boolean> {
|
||||
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): void {
|
||||
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()
|
||||
}
|
||||
|
||||
getSearchText(): string | undefined {
|
||||
return this.searchText
|
||||
}
|
||||
|
||||
setSearchText(text?: string): void {
|
||||
this.searchText = text
|
||||
this.applyFilterSortAndGroup()
|
||||
}
|
||||
|
||||
applyFilterSortAndGroup(): void {
|
||||
Utils.assert(this.allCards !== undefined)
|
||||
|
||||
this.cards = this.filterCards(this.allCards)
|
||||
Utils.assert(this.cards !== undefined)
|
||||
this.cards = this.searchFilterCards(this.cards)
|
||||
Utils.assert(this.cards !== undefined)
|
||||
this.cards = this.sortCards(this.cards)
|
||||
Utils.assert(this.cards !== undefined)
|
||||
|
||||
if (this.activeView.groupById) {
|
||||
this.setGroupByProperty(this.activeView.groupById)
|
||||
} else {
|
||||
Utils.assert(this.activeView.viewType !== 'board')
|
||||
}
|
||||
|
||||
Utils.assert(this.cards !== undefined)
|
||||
}
|
||||
|
||||
private searchFilterCards(cards: Card[]): Card[] {
|
||||
const searchText = this.searchText?.toLocaleLowerCase()
|
||||
if (!searchText) {
|
||||
return cards.slice()
|
||||
}
|
||||
|
||||
return cards.filter((card) => {
|
||||
return (card.title?.toLocaleLowerCase().indexOf(searchText) !== -1)
|
||||
})
|
||||
}
|
||||
|
||||
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) => {
|
||||
const propertyValue = o.properties[groupByPropertyId]
|
||||
return !propertyValue || !this.groupByProperty.options.find((option) => option.value === propertyValue)
|
||||
})
|
||||
|
||||
const propertyOptions = this.groupByProperty.options || []
|
||||
for (const option of propertyOptions) {
|
||||
const cards = this.cards.
|
||||
filter((o) => {
|
||||
const propertyValue = o.properties[groupByPropertyId]
|
||||
return propertyValue && propertyValue === option.value
|
||||
})
|
||||
|
||||
const group: Group = {
|
||||
option,
|
||||
cards,
|
||||
}
|
||||
|
||||
this.groups.push(group)
|
||||
}
|
||||
}
|
||||
|
||||
private filterCards(cards: Card[]): Card[] {
|
||||
const {board} = this
|
||||
const filterGroup = this.activeView?.filter
|
||||
if (!filterGroup) {
|
||||
return cards.slice()
|
||||
}
|
||||
|
||||
return CardFilter.applyFilterGroup(filterGroup, board.cardProperties, cards)
|
||||
}
|
||||
|
||||
private sortCards(cards: Card[]): Card[] {
|
||||
if (!this.activeView) {
|
||||
Utils.assertFailure()
|
||||
return cards
|
||||
}
|
||||
const {board} = this
|
||||
const {sortOptions} = this.activeView
|
||||
let sortedCards: Card[] = []
|
||||
|
||||
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 {
|
||||
for (const sortOption of sortOptions) {
|
||||
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)
|
||||
if (!template) {
|
||||
Utils.logError(`Missing template for property id: ${sortPropertyId}`)
|
||||
return cards.slice()
|
||||
}
|
||||
Utils.log(`Sort by ${template?.name}`)
|
||||
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
|
||||
}
|
||||
|
||||
const aValue = a.properties[sortPropertyId] || ''
|
||||
const bValue = b.properties[sortPropertyId] || ''
|
||||
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
|
||||
}
|
||||
getSearchText(): string | undefined
|
||||
}
|
||||
|
||||
export {BoardTree}
|
||||
class MutableBoardTree implements BoardTree {
|
||||
board!: Board
|
||||
views: BoardView[] = []
|
||||
cards: Card[] = []
|
||||
emptyGroupCards: Card[] = []
|
||||
groups: Group[] = []
|
||||
|
||||
activeView?: BoardView
|
||||
groupByProperty?: IPropertyTemplate
|
||||
|
||||
private searchText?: string
|
||||
private allCards: Card[] = []
|
||||
get allBlocks(): IBlock[] {
|
||||
return [this.board, ...this.views, ...this.allCards]
|
||||
}
|
||||
|
||||
constructor(private boardId: string) {
|
||||
}
|
||||
|
||||
async sync() {
|
||||
const blocks = await octoClient.getSubtree(this.boardId)
|
||||
this.rebuild(OctoUtils.hydrateBlocks(blocks))
|
||||
}
|
||||
|
||||
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[]
|
||||
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()
|
||||
}
|
||||
|
||||
getSearchText(): string | undefined {
|
||||
return this.searchText
|
||||
}
|
||||
|
||||
setSearchText(text?: string) {
|
||||
this.searchText = text
|
||||
this.applyFilterSortAndGroup()
|
||||
}
|
||||
|
||||
applyFilterSortAndGroup() {
|
||||
Utils.assert(this.allCards !== undefined)
|
||||
|
||||
this.cards = this.filterCards(this.allCards)
|
||||
Utils.assert(this.cards !== undefined)
|
||||
this.cards = this.searchFilterCards(this.cards)
|
||||
Utils.assert(this.cards !== undefined)
|
||||
this.cards = this.sortCards(this.cards)
|
||||
Utils.assert(this.cards !== undefined)
|
||||
|
||||
if (this.activeView.groupById) {
|
||||
this.setGroupByProperty(this.activeView.groupById)
|
||||
} else {
|
||||
Utils.assert(this.activeView.viewType !== "board")
|
||||
}
|
||||
|
||||
Utils.assert(this.cards !== undefined)
|
||||
}
|
||||
|
||||
private searchFilterCards(cards: Card[]): Card[] {
|
||||
const searchText = this.searchText?.toLocaleLowerCase()
|
||||
if (!searchText) { return cards.slice() }
|
||||
|
||||
return cards.filter(card => {
|
||||
if (card.title?.toLocaleLowerCase().indexOf(searchText) !== -1) { return true }
|
||||
})
|
||||
}
|
||||
|
||||
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 => {
|
||||
const propertyValue = o.properties[groupByPropertyId]
|
||||
return !propertyValue || !this.groupByProperty.options.find(option => option.value === propertyValue)
|
||||
})
|
||||
|
||||
const propertyOptions = this.groupByProperty.options || []
|
||||
for (const option of propertyOptions) {
|
||||
const cards = this.cards
|
||||
.filter(o => {
|
||||
const propertyValue = o.properties[groupByPropertyId]
|
||||
return propertyValue && propertyValue === option.value
|
||||
})
|
||||
|
||||
const group: Group = {
|
||||
option,
|
||||
cards
|
||||
}
|
||||
|
||||
this.groups.push(group)
|
||||
}
|
||||
}
|
||||
|
||||
private filterCards(cards: Card[]): Card[] {
|
||||
const { board } = this
|
||||
const filterGroup = this.activeView?.filter
|
||||
if (!filterGroup) { return cards.slice() }
|
||||
|
||||
return CardFilter.applyFilterGroup(filterGroup, board.cardProperties, cards)
|
||||
}
|
||||
|
||||
private sortCards(cards: Card[]): Card[] {
|
||||
if (!this.activeView) { Utils.assertFailure(); return cards }
|
||||
const { board } = this
|
||||
const { sortOptions } = this.activeView
|
||||
let sortedCards: Card[] = []
|
||||
|
||||
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)
|
||||
if (!template) {
|
||||
Utils.logError(`Missing template for property id: ${sortPropertyId}`)
|
||||
return cards.slice()
|
||||
}
|
||||
Utils.log(`Sort by ${template?.name}`)
|
||||
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 }
|
||||
|
||||
const aValue = a.properties[sortPropertyId] || ""
|
||||
const bValue = b.properties[sortPropertyId] || ""
|
||||
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 { MutableBoardTree, BoardTree }
|
||||
|
|
|
@ -6,32 +6,35 @@ import octoClient from './octoClient'
|
|||
import {IBlock, IOrderedBlock} from './octoTypes'
|
||||
import {OctoUtils} from './octoUtils'
|
||||
|
||||
class CardTree {
|
||||
card: Card
|
||||
comments: IBlock[]
|
||||
contents: IOrderedBlock[]
|
||||
isSynched: boolean
|
||||
|
||||
constructor(private cardId: string) {
|
||||
}
|
||||
|
||||
async sync(): Promise<void> {
|
||||
const blocks = await octoClient.getSubtree(this.cardId)
|
||||
this.rebuild(OctoUtils.hydrateBlocks(blocks))
|
||||
}
|
||||
|
||||
private rebuild(blocks: Block[]): void {
|
||||
this.card = blocks.find((o) => o.id === this.cardId) as Card
|
||||
|
||||
this.comments = blocks.
|
||||
filter((block) => block.type === 'comment').
|
||||
sort((a, b) => a.createAt - b.createAt)
|
||||
|
||||
const contentBlocks = blocks.filter((block) => block.type === 'text' || block.type === 'image') as IOrderedBlock[]
|
||||
this.contents = contentBlocks.sort((a, b) => a.order - b.order)
|
||||
|
||||
this.isSynched = true
|
||||
}
|
||||
interface CardTree {
|
||||
readonly card: Card
|
||||
readonly comments: readonly IBlock[]
|
||||
readonly contents: readonly IOrderedBlock[]
|
||||
}
|
||||
|
||||
export {CardTree}
|
||||
class MutableCardTree implements CardTree {
|
||||
card: Card
|
||||
comments: IBlock[]
|
||||
contents: IOrderedBlock[]
|
||||
|
||||
constructor(private cardId: string) {
|
||||
}
|
||||
|
||||
async sync() {
|
||||
const blocks = await octoClient.getSubtree(this.cardId)
|
||||
this.rebuild(OctoUtils.hydrateBlocks(blocks))
|
||||
}
|
||||
|
||||
private rebuild(blocks: Block[]) {
|
||||
this.card = blocks.find(o => o.id === this.cardId) as Card
|
||||
|
||||
this.comments = blocks
|
||||
.filter(block => block.type === "comment")
|
||||
.sort((a, b) => a.createAt - b.createAt)
|
||||
|
||||
const contentBlocks = blocks.filter(block => block.type === "text" || block.type === "image") as IOrderedBlock[]
|
||||
this.contents = contentBlocks.sort((a, b) => a.order - b.order)
|
||||
}
|
||||
}
|
||||
|
||||
export { MutableCardTree, CardTree }
|
||||
|
|
|
@ -7,7 +7,7 @@ import {Block} from '../blocks/block'
|
|||
import {Card} from '../blocks/card'
|
||||
import {TextBlock} from '../blocks/textBlock'
|
||||
import {BoardTree} from '../boardTree'
|
||||
import {CardTree} from '../cardTree'
|
||||
import {CardTree, MutableCardTree} from '../cardTree'
|
||||
import {Menu as OldMenu, MenuOption} from '../menu'
|
||||
import mutator from '../mutator'
|
||||
import {OctoListener} from '../octoListener'
|
||||
|
@ -46,7 +46,7 @@ export default class CardDetail extends React.Component<Props, State> {
|
|||
await cardTree.sync()
|
||||
this.setState({cardTree})
|
||||
})
|
||||
const cardTree = new CardTree(this.props.card.id)
|
||||
const cardTree = new MutableCardTree(this.props.card.id)
|
||||
cardTree.sync().then(() => {
|
||||
this.setState({cardTree})
|
||||
setTimeout(() => {
|
||||
|
|
|
@ -121,7 +121,7 @@ class OctoUtils {
|
|||
return element
|
||||
}
|
||||
|
||||
static getOrderBefore(block: IOrderedBlock, blocks: IOrderedBlock[]): number {
|
||||
static getOrderBefore(block: IOrderedBlock, blocks: readonly IOrderedBlock[]): number {
|
||||
const index = blocks.indexOf(block)
|
||||
if (index === 0) {
|
||||
return block.order / 2
|
||||
|
@ -130,7 +130,7 @@ class OctoUtils {
|
|||
return (block.order + previousBlock.order) / 2
|
||||
}
|
||||
|
||||
static getOrderAfter(block: IOrderedBlock, blocks: IOrderedBlock[]): number {
|
||||
static getOrderAfter(block: IOrderedBlock, blocks: readonly IOrderedBlock[]): number {
|
||||
const index = blocks.indexOf(block)
|
||||
if (index === blocks.length - 1) {
|
||||
return block.order + 1000
|
||||
|
|
|
@ -4,7 +4,7 @@ import React from 'react'
|
|||
import ReactDOM from 'react-dom'
|
||||
|
||||
import {BoardView} from '../blocks/boardView'
|
||||
import {BoardTree} from '../boardTree'
|
||||
import {BoardTree, MutableBoardTree} from '../boardTree'
|
||||
import {CardTree} from '../cardTree'
|
||||
import {FilterComponent} from '../components/filterComponent'
|
||||
import {WorkspaceComponent} from '../components/workspaceComponent'
|
||||
|
@ -12,7 +12,7 @@ import {FlashMessage} from '../flashMessage'
|
|||
import mutator from '../mutator'
|
||||
import {OctoListener} from '../octoListener'
|
||||
import {Utils} from '../utils'
|
||||
import {WorkspaceTree} from '../workspaceTree'
|
||||
import {MutableWorkspaceTree, WorkspaceTree} from '../workspaceTree'
|
||||
|
||||
type Props = {
|
||||
}
|
||||
|
@ -20,9 +20,8 @@ type Props = {
|
|||
type State = {
|
||||
boardId: string
|
||||
viewId: string
|
||||
workspaceTree: WorkspaceTree
|
||||
boardTree?: BoardTree
|
||||
shownCardTree?: CardTree
|
||||
workspaceTree: MutableWorkspaceTree
|
||||
boardTree?: MutableBoardTree
|
||||
filterAnchorElement?: HTMLElement
|
||||
}
|
||||
|
||||
|
@ -44,7 +43,7 @@ export default class BoardPage extends React.Component<Props, State> {
|
|||
this.state = {
|
||||
boardId,
|
||||
viewId,
|
||||
workspaceTree: new WorkspaceTree(),
|
||||
workspaceTree: new MutableWorkspaceTree(),
|
||||
}
|
||||
|
||||
Utils.log(`BoardPage. boardId: ${boardId}`)
|
||||
|
@ -106,8 +105,7 @@ export default class BoardPage extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {workspaceTree, shownCardTree} = this.state
|
||||
const {board, activeView} = this.state.boardTree || {}
|
||||
const {workspaceTree} = this.state
|
||||
|
||||
if (this.state.filterAnchorElement) {
|
||||
const element = this.state.filterAnchorElement
|
||||
|
@ -178,7 +176,7 @@ export default class BoardPage extends React.Component<Props, State> {
|
|||
await workspaceTree.sync()
|
||||
|
||||
if (boardId) {
|
||||
const boardTree = new BoardTree(boardId)
|
||||
const boardTree = new MutableBoardTree(boardId)
|
||||
await boardTree.sync()
|
||||
|
||||
// Default to first view
|
||||
|
|
|
@ -5,17 +5,23 @@ import {Board} from './blocks/board'
|
|||
import octoClient from './octoClient'
|
||||
import {OctoUtils} from './octoUtils'
|
||||
|
||||
class WorkspaceTree {
|
||||
boards: Board[] = []
|
||||
|
||||
async sync(): Promise<void> {
|
||||
const blocks = await octoClient.getBlocks(undefined, 'board')
|
||||
this.rebuild(OctoUtils.hydrateBlocks(blocks))
|
||||
}
|
||||
|
||||
private rebuild(blocks: Block[]): void {
|
||||
this.boards = blocks.filter((block) => block.type === 'board') as Board[]
|
||||
}
|
||||
interface WorkspaceTree {
|
||||
readonly boards: readonly Board[]
|
||||
}
|
||||
|
||||
export {WorkspaceTree}
|
||||
class MutableWorkspaceTree {
|
||||
boards: Board[] = []
|
||||
|
||||
async sync() {
|
||||
const blocks = await octoClient.getBlocks(undefined, "board")
|
||||
this.rebuild(OctoUtils.hydrateBlocks(blocks))
|
||||
}
|
||||
|
||||
private rebuild(blocks: Block[]) {
|
||||
this.boards = blocks.filter(block => block.type === "board") as Board[]
|
||||
}
|
||||
}
|
||||
|
||||
// type WorkspaceTree = Readonly<MutableWorkspaceTree>
|
||||
|
||||
export { MutableWorkspaceTree, WorkspaceTree }
|
||||
|
|
Loading…
Reference in a new issue