Refactor Blocks - each with its own class

This commit is contained in:
Chen-I Lim 2020-10-15 13:23:33 -07:00
parent 469887f6fe
commit fccade93ec
11 changed files with 112 additions and 49 deletions

View file

@ -7,7 +7,6 @@ class Block implements IBlock {
parentId: string parentId: string
type: string type: string
title: string title: string
order: number
fields: Record<string, any> = {} fields: Record<string, any> = {}
createAt: number = Date.now() createAt: number = Date.now()
updateAt: number = 0 updateAt: number = 0
@ -38,7 +37,6 @@ class Block implements IBlock {
this.fields = block.fields ? { ...block.fields } : {} this.fields = block.fields ? { ...block.fields } : {}
this.title = block.title this.title = block.title
this.order = block.order
this.createAt = block.createAt || now this.createAt = block.createAt || now
this.updateAt = block.updateAt || now this.updateAt = block.updateAt || now

View file

@ -0,0 +1,10 @@
import { Block } from "./block"
class CommentBlock extends Block {
constructor(block: any = {}) {
super(block)
this.type = "comment"
}
}
export { CommentBlock }

View file

@ -0,0 +1,17 @@
import { IOrderedBlock } from "../octoTypes"
import { Block } from "./block"
class ImageBlock extends Block implements IOrderedBlock {
get order(): number { return this.fields.order as number }
set order(value: number) { this.fields.order = value }
get url(): string { return this.fields.url as string }
set url(value: string) { this.fields.url = value }
constructor(block: any = {}) {
super(block)
this.type = "image"
}
}
export { ImageBlock }

View file

@ -0,0 +1,14 @@
import { IOrderedBlock } from "../octoTypes"
import { Block } from "./block"
class TextBlock extends Block implements IOrderedBlock {
get order(): number { return this.fields.order as number }
set order(value: number) { this.fields.order = value }
constructor(block: any = {}) {
super(block)
this.type = "text"
}
}
export { TextBlock }

View file

@ -1,9 +1,11 @@
import { Block } from "./blocks/block"
import { Board, IPropertyOption, IPropertyTemplate } from "./blocks/board" import { Board, IPropertyOption, IPropertyTemplate } from "./blocks/board"
import { BoardView } from "./blocks/boardView" import { BoardView } from "./blocks/boardView"
import { Card } from "./blocks/card" import { Card } from "./blocks/card"
import { CardFilter } from "./cardFilter" import { CardFilter } from "./cardFilter"
import octoClient from "./octoClient" import octoClient from "./octoClient"
import { IBlock } from "./octoTypes" import { IBlock } from "./octoTypes"
import { OctoUtils } from "./octoUtils"
import { Utils } from "./utils" import { Utils } from "./utils"
type Group = { option: IPropertyOption, cards: Card[] } type Group = { option: IPropertyOption, cards: Card[] }
@ -29,21 +31,13 @@ class BoardTree {
async sync() { async sync() {
const blocks = await octoClient.getSubtree(this.boardId) const blocks = await octoClient.getSubtree(this.boardId)
this.rebuild(blocks) this.rebuild(OctoUtils.hydrateBlocks(blocks))
} }
private rebuild(blocks: IBlock[]) { private rebuild(blocks: Block[]) {
const boardBlock = blocks.find(block => block.type === "board") this.board = blocks.find(block => block.type === "board") as Board
this.views = blocks.filter(block => block.type === "view") as BoardView[]
if (boardBlock) { this.allCards = blocks.filter(block => block.type === "card") as Card[]
this.board = new Board(boardBlock)
}
const viewBlocks = blocks.filter(block => block.type === "view")
this.views = viewBlocks.map(o => new BoardView(o))
const cardBlocks = blocks.filter(block => block.type === "card")
this.allCards = cardBlocks.map(o => new Card(o))
this.cards = [] this.cards = []
this.ensureMinimumSchema() this.ensureMinimumSchema()

View file

@ -1,11 +1,13 @@
import { Block } from "./blocks/block"
import { Card } from "./blocks/card" import { Card } from "./blocks/card"
import octoClient from "./octoClient" import octoClient from "./octoClient"
import { IBlock } from "./octoTypes" import { IBlock, IOrderedBlock } from "./octoTypes"
import { OctoUtils } from "./octoUtils"
class CardTree { class CardTree {
card: Card card: Card
comments: IBlock[] comments: IBlock[]
contents: IBlock[] contents: IOrderedBlock[]
isSynched: boolean isSynched: boolean
constructor(private cardId: string) { constructor(private cardId: string) {
@ -13,19 +15,18 @@ class CardTree {
async sync() { async sync() {
const blocks = await octoClient.getSubtree(this.cardId) const blocks = await octoClient.getSubtree(this.cardId)
this.rebuild(blocks) this.rebuild(OctoUtils.hydrateBlocks(blocks))
} }
private rebuild(blocks: IBlock[]) { private rebuild(blocks: Block[]) {
this.card = new Card(blocks.find(o => o.id === this.cardId)) this.card = blocks.find(o => o.id === this.cardId) as Card
this.comments = blocks this.comments = blocks
.filter(block => block.type === "comment") .filter(block => block.type === "comment")
.sort((a, b) => a.createAt - b.createAt) .sort((a, b) => a.createAt - b.createAt)
this.contents = blocks const contentBlocks = blocks.filter(block => block.type === "text" || block.type === "image") as IOrderedBlock[]
.filter(block => block.type === "text" || block.type === "image") this.contents = contentBlocks.sort((a, b) => a.order - b.order)
.sort((a, b) => a.order - b.order)
this.isSynched = true this.isSynched = true
} }

View file

@ -6,7 +6,7 @@ import { BoardTree } from "../boardTree"
import { CardTree } from "../cardTree" import { CardTree } from "../cardTree"
import { Menu, MenuOption } from "../menu" import { Menu, MenuOption } from "../menu"
import mutator from "../mutator" import mutator from "../mutator"
import { IBlock } from "../octoTypes" import { IBlock, IOrderedBlock } from "../octoTypes"
import { OctoUtils } from "../octoUtils" import { OctoUtils } from "../octoUtils"
import { PropertyMenu } from "../propertyMenu" import { PropertyMenu } from "../propertyMenu"
import { OctoListener } from "../octoListener" import { OctoListener } from "../octoListener"
@ -14,6 +14,8 @@ import { Utils } from "../utils"
import Button from "./button" import Button from "./button"
import { Editable } from "./editable" import { Editable } from "./editable"
import { MarkdownEditor } from "./markdownEditor" import { MarkdownEditor } from "./markdownEditor"
import { TextBlock } from "../blocks/textBlock"
import { CommentBlock } from "../blocks/commentBlock"
type Props = { type Props = {
boardTree: BoardTree boardTree: BoardTree
@ -125,8 +127,8 @@ class CardDialog extends React.Component<Props, State> {
text="" text=""
placeholderText="Add a description..." placeholderText="Add a description..."
onChanged={(text) => { onChanged={(text) => {
const order = cardTree.contents.length * 1000 const block = new TextBlock({ parentId: card.id, title: text })
const block = new Block({ type: "text", parentId: card.id, title: text, order }) block.order = cardTree.contents.length * 1000
mutator.insertBlock(block, "add card text") mutator.insertBlock(block, "add card text")
}} /> }} />
</div> </div>
@ -329,8 +331,8 @@ class CardDialog extends React.Component<Props, State> {
Menu.shared.onMenuClicked = async (optionId: string, type?: string) => { Menu.shared.onMenuClicked = async (optionId: string, type?: string) => {
switch (optionId) { switch (optionId) {
case "text": case "text":
const order = cardTree.contents.length * 1000 const block = new TextBlock({ parentId: card.id })
const block = new Block({ type: "text", parentId: card.id, order }) block.order = cardTree.contents.length * 1000
await mutator.insertBlock(block, "add text") await mutator.insertBlock(block, "add text")
break break
case "image": case "image":
@ -359,11 +361,11 @@ class CardDialog extends React.Component<Props, State> {
Utils.assertValue(card) Utils.assertValue(card)
const block = new Block({ type: "comment", parentId: card.id, title: text }) const block = new CommentBlock({ parentId: card.id, title: text })
await mutator.insertBlock(block, "add comment") await mutator.insertBlock(block, "add comment")
} }
private showContentBlockMenu(e: React.MouseEvent, block: IBlock) { private showContentBlockMenu(e: React.MouseEvent, block: IOrderedBlock) {
const { card } = this.props const { card } = this.props
const { cardTree } = this.state const { cardTree } = this.state
const index = cardTree.contents.indexOf(block) const index = cardTree.contents.indexOf(block)
@ -405,10 +407,10 @@ class CardDialog extends React.Component<Props, State> {
break break
} }
case "insertAbove-text": { case "insertAbove-text": {
const newBlock = new Block({ type: "text", parentId: card.id }) const newBlock = new TextBlock({ parentId: card.id })
// TODO: Handle need to reorder all blocks
newBlock.order = OctoUtils.getOrderBefore(block, cardTree.contents) newBlock.order = OctoUtils.getOrderBefore(block, cardTree.contents)
Utils.log(`insert block ${block.id}, order: ${block.order}`) // TODO: Handle need to reorder all blocks
Utils.log(`insert block ${newBlock.id}, order: ${newBlock.order}`)
mutator.insertBlock(newBlock, "insert card text") mutator.insertBlock(newBlock, "insert card text")
break break
} }

View file

@ -2,10 +2,11 @@ import { Block } from "./blocks/block"
import { Board, IPropertyOption, IPropertyTemplate, PropertyType } from "./blocks/board" import { Board, IPropertyOption, IPropertyTemplate, PropertyType } from "./blocks/board"
import { BoardView, ISortOption } from "./blocks/boardView" import { BoardView, ISortOption } from "./blocks/boardView"
import { Card } from "./blocks/card" import { Card } from "./blocks/card"
import { ImageBlock } from "./blocks/imageBlock"
import { BoardTree } from "./boardTree" import { BoardTree } from "./boardTree"
import { FilterGroup } from "./filterGroup" import { FilterGroup } from "./filterGroup"
import octoClient from "./octoClient" import octoClient from "./octoClient"
import { IBlock } from "./octoTypes" import { IBlock, IOrderedBlock } from "./octoTypes"
import undoManager from "./undomanager" import undoManager from "./undomanager"
import { Utils } from "./utils" import { Utils } from "./utils"
@ -92,7 +93,7 @@ class Mutator {
) )
} }
async changeOrder(block: IBlock, order: number, description: string = "change order") { async changeOrder(block: IOrderedBlock, order: number, description: string = "change order") {
const oldValue = block.order const oldValue = block.order
await undoManager.perform( await undoManager.perform(
async () => { async () => {
@ -491,8 +492,9 @@ class Mutator {
return undefined return undefined
} }
const block = new Block({ type: "image", parentId, order }) const block = new ImageBlock({ parentId })
block.fields.url = url block.order = order
block.url = url
await undoManager.perform( await undoManager.perform(
async () => { async () => {

View file

@ -6,7 +6,6 @@ interface IBlock {
schema: number schema: number
type: string type: string
title?: string title?: string
order: number
fields: Record<string, any> fields: Record<string, any>
createAt: number createAt: number
@ -14,6 +13,10 @@ interface IBlock {
deleteAt: number deleteAt: number
} }
interface IOrderedBlock extends IBlock {
order: number
}
// These are methods exposed by the top-level page to components // These are methods exposed by the top-level page to components
interface IPageController { interface IPageController {
showBoard(boardId: string): void showBoard(boardId: string): void
@ -22,4 +25,4 @@ interface IPageController {
setSearchText(text?: string): void setSearchText(text?: string): void
} }
export { IBlock, IPageController } export { IBlock, IOrderedBlock, IPageController }

View file

@ -1,12 +1,16 @@
import React from "react" import React from "react"
import { IPropertyTemplate } from "./blocks/board" import { Block } from "./blocks/block"
import { ISortOption } from "./blocks/boardView" import { Board, IPropertyTemplate } from "./blocks/board"
import { BoardView, ISortOption } from "./blocks/boardView"
import { Card } from "./blocks/card" import { Card } from "./blocks/card"
import { CommentBlock } from "./blocks/commentBlock"
import { ImageBlock } from "./blocks/imageBlock"
import { TextBlock } from "./blocks/textBlock"
import { BoardTree } from "./boardTree" import { BoardTree } from "./boardTree"
import { Editable } from "./components/editable" import { Editable } from "./components/editable"
import { Menu } from "./menu" import { Menu } from "./menu"
import mutator from "./mutator" import mutator from "./mutator"
import { IBlock } from "./octoTypes" import { IBlock, IOrderedBlock } from "./octoTypes"
import { Utils } from "./utils" import { Utils } from "./utils"
class OctoUtils { class OctoUtils {
@ -103,7 +107,7 @@ class OctoUtils {
return element return element
} }
static getOrderBefore(block: IBlock, blocks: IBlock[]): number { static getOrderBefore(block: IOrderedBlock, blocks: IOrderedBlock[]): number {
const index = blocks.indexOf(block) const index = blocks.indexOf(block)
if (index === 0) { if (index === 0) {
return block.order / 2 return block.order / 2
@ -112,7 +116,7 @@ class OctoUtils {
return (block.order + previousBlock.order) / 2 return (block.order + previousBlock.order) / 2
} }
static getOrderAfter(block: IBlock, blocks: IBlock[]): number { static getOrderAfter(block: IOrderedBlock, blocks: IOrderedBlock[]): number {
const index = blocks.indexOf(block) const index = blocks.indexOf(block)
if (index === blocks.length - 1) { if (index === blocks.length - 1) {
return block.order + 1000 return block.order + 1000
@ -151,6 +155,24 @@ class OctoUtils {
} }
Menu.shared.showAtElement(e.target as HTMLElement) Menu.shared.showAtElement(e.target as HTMLElement)
} }
static hydrateBlock(block: IBlock): Block {
switch (block.type) {
case "board": { return new Board(block) }
case "view": { return new BoardView(block) }
case "card": { return new Card(block) }
case "text": { return new TextBlock(block) }
case "image": { return new ImageBlock(block) }
case "comment": { return new CommentBlock(block) }
default: {
Utils.assertFailure(`Can't hydrate unknown block type: ${block.type}`)
}
}
}
static hydrateBlocks(blocks: IBlock[]): Block[] {
return blocks.map( block => this.hydrateBlock(block) )
}
} }
export { OctoUtils } export { OctoUtils }

View file

@ -1,18 +1,18 @@
import { Block } from "./blocks/block"
import { Board } from "./blocks/board" import { Board } from "./blocks/board"
import octoClient from "./octoClient" import octoClient from "./octoClient"
import { IBlock } from "./octoTypes" import { OctoUtils } from "./octoUtils"
class WorkspaceTree { class WorkspaceTree {
boards: Board[] = [] boards: Board[] = []
async sync() { async sync() {
const blocks = await octoClient.getBlocks(undefined, "board") const blocks = await octoClient.getBlocks(undefined, "board")
this.rebuild(blocks) this.rebuild(OctoUtils.hydrateBlocks(blocks))
} }
private rebuild(blocks: IBlock[]) { private rebuild(blocks: Block[]) {
const boardBlocks = blocks.filter(block => block.type === "board") this.boards = blocks.filter(block => block.type === "board") as Board[]
this.boards = boardBlocks.map(o => new Board(o))
} }
} }