Refactor Blocks - each with its own class
This commit is contained in:
parent
469887f6fe
commit
fccade93ec
11 changed files with 112 additions and 49 deletions
|
@ -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
|
||||||
|
|
10
src/client/blocks/commentBlock.ts
Normal file
10
src/client/blocks/commentBlock.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { Block } from "./block"
|
||||||
|
|
||||||
|
class CommentBlock extends Block {
|
||||||
|
constructor(block: any = {}) {
|
||||||
|
super(block)
|
||||||
|
this.type = "comment"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { CommentBlock }
|
17
src/client/blocks/imageBlock.ts
Normal file
17
src/client/blocks/imageBlock.ts
Normal 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 }
|
14
src/client/blocks/textBlock.ts
Normal file
14
src/client/blocks/textBlock.ts
Normal 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 }
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue