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
type: string
title: string
order: number
fields: Record<string, any> = {}
createAt: number = Date.now()
updateAt: number = 0
@ -38,7 +37,6 @@ class Block implements IBlock {
this.fields = block.fields ? { ...block.fields } : {}
this.title = block.title
this.order = block.order
this.createAt = block.createAt || 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 { BoardView } from "./blocks/boardView"
import { Card } from "./blocks/card"
import { CardFilter } from "./cardFilter"
import octoClient from "./octoClient"
import { IBlock } from "./octoTypes"
import { OctoUtils } from "./octoUtils"
import { Utils } from "./utils"
type Group = { option: IPropertyOption, cards: Card[] }
@ -29,21 +31,13 @@ class BoardTree {
async sync() {
const blocks = await octoClient.getSubtree(this.boardId)
this.rebuild(blocks)
this.rebuild(OctoUtils.hydrateBlocks(blocks))
}
private rebuild(blocks: IBlock[]) {
const boardBlock = blocks.find(block => block.type === "board")
if (boardBlock) {
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))
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()

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,16 @@
import React from "react"
import { IPropertyTemplate } from "./blocks/board"
import { ISortOption } from "./blocks/boardView"
import { Block } from "./blocks/block"
import { Board, IPropertyTemplate } from "./blocks/board"
import { BoardView, ISortOption } from "./blocks/boardView"
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 { Editable } from "./components/editable"
import { Menu } from "./menu"
import mutator from "./mutator"
import { IBlock } from "./octoTypes"
import { IBlock, IOrderedBlock } from "./octoTypes"
import { Utils } from "./utils"
class OctoUtils {
@ -103,7 +107,7 @@ class OctoUtils {
return element
}
static getOrderBefore(block: IBlock, blocks: IBlock[]): number {
static getOrderBefore(block: IOrderedBlock, blocks: IOrderedBlock[]): number {
const index = blocks.indexOf(block)
if (index === 0) {
return block.order / 2
@ -112,7 +116,7 @@ class OctoUtils {
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)
if (index === blocks.length - 1) {
return block.order + 1000
@ -151,6 +155,24 @@ class OctoUtils {
}
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 }

View file

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