diff --git a/src/client/blocks/block.ts b/src/client/blocks/block.ts index 05acfd331..df5c31269 100644 --- a/src/client/blocks/block.ts +++ b/src/client/blocks/block.ts @@ -7,7 +7,6 @@ class Block implements IBlock { parentId: string type: string title: string - order: number fields: Record = {} 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 diff --git a/src/client/blocks/commentBlock.ts b/src/client/blocks/commentBlock.ts new file mode 100644 index 000000000..6b73a5e88 --- /dev/null +++ b/src/client/blocks/commentBlock.ts @@ -0,0 +1,10 @@ +import { Block } from "./block" + +class CommentBlock extends Block { + constructor(block: any = {}) { + super(block) + this.type = "comment" + } +} + +export { CommentBlock } diff --git a/src/client/blocks/imageBlock.ts b/src/client/blocks/imageBlock.ts new file mode 100644 index 000000000..ecc1747d5 --- /dev/null +++ b/src/client/blocks/imageBlock.ts @@ -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 } diff --git a/src/client/blocks/textBlock.ts b/src/client/blocks/textBlock.ts new file mode 100644 index 000000000..5d68183dd --- /dev/null +++ b/src/client/blocks/textBlock.ts @@ -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 } diff --git a/src/client/boardTree.ts b/src/client/boardTree.ts index 3147b81ed..4bd734929 100644 --- a/src/client/boardTree.ts +++ b/src/client/boardTree.ts @@ -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() diff --git a/src/client/cardTree.ts b/src/client/cardTree.ts index efc5b2264..2cc678bbe 100644 --- a/src/client/cardTree.ts +++ b/src/client/cardTree.ts @@ -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 } diff --git a/src/client/components/cardDialog.tsx b/src/client/components/cardDialog.tsx index 05a9b5c2c..a56ae4d0b 100644 --- a/src/client/components/cardDialog.tsx +++ b/src/client/components/cardDialog.tsx @@ -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 { 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") }} /> @@ -329,8 +331,8 @@ class CardDialog extends React.Component { 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 { 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 { 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 } diff --git a/src/client/mutator.ts b/src/client/mutator.ts index a1af92f03..770426b32 100644 --- a/src/client/mutator.ts +++ b/src/client/mutator.ts @@ -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 () => { diff --git a/src/client/octoTypes.ts b/src/client/octoTypes.ts index 36892729a..8d6318ab7 100644 --- a/src/client/octoTypes.ts +++ b/src/client/octoTypes.ts @@ -6,7 +6,6 @@ interface IBlock { schema: number type: string title?: string - order: number fields: Record 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 } diff --git a/src/client/octoUtils.tsx b/src/client/octoUtils.tsx index 2d2ccaa00..533ed0383 100644 --- a/src/client/octoUtils.tsx +++ b/src/client/octoUtils.tsx @@ -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 } diff --git a/src/client/workspaceTree.ts b/src/client/workspaceTree.ts index 6e70cb898..687ff5af6 100644 --- a/src/client/workspaceTree.ts +++ b/src/client/workspaceTree.ts @@ -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[] } }