From aa950240d901ab2dd1b3a614bf00dcb227b86864 Mon Sep 17 00:00:00 2001 From: Chen-I Lim Date: Tue, 20 Oct 2020 18:28:55 -0700 Subject: [PATCH] Immutable blocks --- webapp/src/blocks/block.ts | 38 ++-- webapp/src/blocks/board.ts | 30 ++- webapp/src/blocks/boardView.ts | 15 +- webapp/src/blocks/card.ts | 13 +- webapp/src/blocks/commentBlock.ts | 10 +- webapp/src/blocks/imageBlock.ts | 10 +- webapp/src/blocks/orderedBlock.ts | 21 ++ webapp/src/blocks/textBlock.ts | 10 +- webapp/src/boardTree.ts | 20 +- webapp/src/cardFilter.ts | 10 +- webapp/src/cardTree.ts | 6 +- webapp/src/components/boardCard.tsx | 4 +- webapp/src/components/boardComponent.tsx | 140 ++++++------- webapp/src/components/cardDetail.tsx | 76 ++++---- webapp/src/components/cardDialog.tsx | 2 +- webapp/src/components/sidebar.tsx | 17 +- webapp/src/components/tableComponent.tsx | 16 +- webapp/src/components/viewMenu.tsx | 6 +- webapp/src/mutator.ts | 238 ++++++++++++++--------- webapp/src/octoClient.ts | 13 +- webapp/src/octoTypes.ts | 24 +-- webapp/src/octoUtils.tsx | 71 +++---- webapp/src/pages/boardPage.tsx | 2 +- webapp/src/pages/homePage.tsx | 12 +- webapp/src/workspaceTree.ts | 4 +- 25 files changed, 469 insertions(+), 339 deletions(-) create mode 100644 webapp/src/blocks/orderedBlock.ts diff --git a/webapp/src/blocks/block.ts b/webapp/src/blocks/block.ts index 60b131164..73a865eb6 100644 --- a/webapp/src/blocks/block.ts +++ b/webapp/src/blocks/block.ts @@ -3,7 +3,21 @@ import {IBlock} from '../octoTypes' import {Utils} from '../utils' -class Block implements IBlock { +interface IMutableBlock extends IBlock { + id: string + parentId: string + + schema: number + type: string + title?: string + fields: Record + + createAt: number + updateAt: number + deleteAt: number +} + +class MutableBlock implements IMutableBlock { id: string = Utils.createGuid() schema: number parentId: string @@ -14,25 +28,25 @@ class Block implements IBlock { updateAt = 0 deleteAt = 0 - static duplicate(block: IBlock) { + static duplicate(block: IBlock): IBlock { const now = Date.now() - const newBlock = new Block(block) - newBlock.id = Utils.createGuid() + const newBlock = new MutableBlock(block) + newBlock.id = Utils.createGuid() newBlock.title = `Copy of ${block.title}` - newBlock.createAt = now + newBlock.createAt = now newBlock.updateAt = now - newBlock.deleteAt = 0 + newBlock.deleteAt = 0 return newBlock } constructor(block: any = {}) { - const now = Date.now() + const now = Date.now() - this.id = block.id || Utils.createGuid() - this.schema = 1 - this.parentId = block.parentId + this.id = block.id || Utils.createGuid() + this.schema = 1 + this.parentId = block.parentId this.type = block.type // Shallow copy here. Derived classes must make deep copies of their known properties in their constructors. @@ -41,9 +55,9 @@ class Block implements IBlock { this.title = block.title this.createAt = block.createAt || now - this.updateAt = block.updateAt || now + this.updateAt = block.updateAt || now this.deleteAt = block.deleteAt || 0 } } -export {Block} +export {IMutableBlock, MutableBlock} diff --git a/webapp/src/blocks/board.ts b/webapp/src/blocks/board.ts index 3528d7de5..8e64399a2 100644 --- a/webapp/src/blocks/board.ts +++ b/webapp/src/blocks/board.ts @@ -1,23 +1,41 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {Block} from './block' +import { IBlock } from '../octoTypes' +import {MutableBlock} from './block' type PropertyType = 'text' | 'number' | 'select' | 'multiSelect' | 'date' | 'person' | 'file' | 'checkbox' | 'url' | 'email' | 'phone' | 'createdTime' | 'createdBy' | 'updatedTime' | 'updatedBy' interface IPropertyOption { + readonly value: string, + readonly color: string +} + +interface IMutablePropertyOption { value: string, color: string } // A template for card properties attached to a board interface IPropertyTemplate { + readonly id: string + readonly name: string + readonly type: PropertyType + readonly options: IPropertyOption[] +} + +interface IMutablePropertyTemplate extends IPropertyTemplate { id: string name: string type: PropertyType - options: IPropertyOption[] + options: IMutablePropertyOption[] } -class Board extends Block { +interface Board extends IBlock { + readonly icon: string + readonly cardProperties: readonly IPropertyTemplate[] +} + +class MutableBoard extends MutableBlock { get icon(): string { return this.fields.icon as string } @@ -25,10 +43,10 @@ class Board extends Block { this.fields.icon = value } - get cardProperties(): IPropertyTemplate[] { + get cardProperties(): IMutablePropertyTemplate[] { return this.fields.cardProperties as IPropertyTemplate[] } - set cardProperties(value: IPropertyTemplate[]) { + set cardProperties(value: IMutablePropertyTemplate[]) { this.fields.cardProperties = value } @@ -52,4 +70,4 @@ class Board extends Block { } } -export {Board, PropertyType, IPropertyOption, IPropertyTemplate} +export {Board, MutableBoard, PropertyType, IPropertyOption, IPropertyTemplate} diff --git a/webapp/src/blocks/boardView.ts b/webapp/src/blocks/boardView.ts index ac986bd6f..bd0fa8b97 100644 --- a/webapp/src/blocks/boardView.ts +++ b/webapp/src/blocks/boardView.ts @@ -1,13 +1,22 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import { IBlock } from '../octoTypes' import {FilterGroup} from '../filterGroup' -import {Block} from './block' +import {MutableBlock} from './block' type IViewType = 'board' | 'table' | 'calendar' | 'list' | 'gallery' type ISortOption = { propertyId: '__name' | string, reversed: boolean } -class BoardView extends Block { +interface BoardView extends IBlock { + readonly viewType: IViewType + readonly groupById: string + readonly sortOptions: readonly ISortOption[] + readonly visiblePropertyIds: readonly string[] + readonly filter: FilterGroup | undefined +} + +class MutableBoardView extends MutableBlock { get viewType(): IViewType { return this.fields.viewType } @@ -67,4 +76,4 @@ class BoardView extends Block { } } -export {BoardView, IViewType, ISortOption} +export {BoardView, MutableBoardView, IViewType, ISortOption} diff --git a/webapp/src/blocks/card.ts b/webapp/src/blocks/card.ts index b2896754a..47d426ea2 100644 --- a/webapp/src/blocks/card.ts +++ b/webapp/src/blocks/card.ts @@ -1,8 +1,15 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {Block} from './block' +import {IBlock} from '../octoTypes' -class Card extends Block { +import {MutableBlock} from './block' + +interface Card extends IBlock { + readonly icon: string + readonly properties: Readonly> +} + +class MutableCard extends MutableBlock { get icon(): string { return this.fields.icon as string } @@ -25,4 +32,4 @@ class Card extends Block { } } -export {Card} +export {MutableCard, Card} diff --git a/webapp/src/blocks/commentBlock.ts b/webapp/src/blocks/commentBlock.ts index 8905b4806..761b4a7f1 100644 --- a/webapp/src/blocks/commentBlock.ts +++ b/webapp/src/blocks/commentBlock.ts @@ -1,12 +1,16 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {Block} from './block' +import { IBlock } from '../octoTypes' +import {MutableBlock} from './block' -class CommentBlock extends Block { +interface CommentBlock extends IBlock { +} + +class MutableCommentBlock extends MutableBlock { constructor(block: any = {}) { super(block) this.type = 'comment' } } -export {CommentBlock} +export {CommentBlock, MutableCommentBlock} diff --git a/webapp/src/blocks/imageBlock.ts b/webapp/src/blocks/imageBlock.ts index 66401781c..0296621fe 100644 --- a/webapp/src/blocks/imageBlock.ts +++ b/webapp/src/blocks/imageBlock.ts @@ -1,10 +1,12 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {IOrderedBlock} from '../octoTypes' +import { IOrderedBlock, MutableOrderedBlock } from './orderedBlock' -import {Block} from './block' +interface ImageBlock extends IOrderedBlock { -class ImageBlock extends Block implements IOrderedBlock { +} + +class MutableImageBlock extends MutableOrderedBlock implements IOrderedBlock { get order(): number { return this.fields.order as number } @@ -25,4 +27,4 @@ class ImageBlock extends Block implements IOrderedBlock { } } -export {ImageBlock} +export {ImageBlock, MutableImageBlock} diff --git a/webapp/src/blocks/orderedBlock.ts b/webapp/src/blocks/orderedBlock.ts new file mode 100644 index 000000000..202b83db4 --- /dev/null +++ b/webapp/src/blocks/orderedBlock.ts @@ -0,0 +1,21 @@ +import { IBlock } from "../octoTypes" +import { MutableBlock } from "./block" + +interface IOrderedBlock extends IBlock { + readonly order: number +} + +class MutableOrderedBlock extends MutableBlock implements IOrderedBlock { + get order(): number { + return this.fields.order as number + } + set order(value: number) { + this.fields.order = value + } + + constructor(block: any = {}) { + super(block) + } +} + +export {IOrderedBlock, MutableOrderedBlock} diff --git a/webapp/src/blocks/textBlock.ts b/webapp/src/blocks/textBlock.ts index e8e197eb1..ced37a6a1 100644 --- a/webapp/src/blocks/textBlock.ts +++ b/webapp/src/blocks/textBlock.ts @@ -1,10 +1,12 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {IOrderedBlock} from '../octoTypes' +import { IOrderedBlock, MutableOrderedBlock } from './orderedBlock' -import {Block} from './block' +interface TextBlock extends IOrderedBlock { -class TextBlock extends Block implements IOrderedBlock { +} + +class MutableTextBlock extends MutableOrderedBlock { get order(): number { return this.fields.order as number } @@ -18,4 +20,4 @@ class TextBlock extends Block implements IOrderedBlock { } } -export {TextBlock} +export {TextBlock, MutableTextBlock} diff --git a/webapp/src/boardTree.ts b/webapp/src/boardTree.ts index 05e4df99f..d109bd444 100644 --- a/webapp/src/boardTree.ts +++ b/webapp/src/boardTree.ts @@ -1,8 +1,8 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {Block} from './blocks/block' -import {Board, IPropertyOption, IPropertyTemplate} from './blocks/board' -import {BoardView} from './blocks/boardView' +import {MutableBlock} from './blocks/block' +import {Board, IPropertyOption, IPropertyTemplate, MutableBoard} from './blocks/board' +import {BoardView, MutableBoardView} from './blocks/boardView' import {Card} from './blocks/card' import {CardFilter} from './cardFilter' import octoClient from './octoClient' @@ -28,12 +28,12 @@ interface BoardTree { class MutableBoardTree implements BoardTree { board!: Board - views: BoardView[] = [] + views: MutableBoardView[] = [] cards: Card[] = [] emptyGroupCards: Card[] = [] groups: Group[] = [] - activeView?: BoardView + activeView?: MutableBoardView groupByProperty?: IPropertyTemplate private searchText?: string @@ -50,9 +50,9 @@ class MutableBoardTree implements BoardTree { this.rebuild(OctoUtils.hydrateBlocks(blocks)) } - private rebuild(blocks: Block[]) { + private rebuild(blocks: IBlock[]) { this.board = blocks.find(block => block.type === "board") as Board - this.views = blocks.filter(block => block.type === "view") as BoardView[] + this.views = blocks.filter(block => block.type === "view") as MutableBoardView[] this.allCards = blocks.filter(block => block.type === "card") as Card[] this.cards = [] @@ -67,19 +67,21 @@ class MutableBoardTree implements BoardTree { // At least one select property const selectProperties = board.cardProperties.find(o => o.type === "select") if (!selectProperties) { + const newBoard = new MutableBoard(board) const property: IPropertyTemplate = { id: Utils.createGuid(), name: "Status", type: "select", options: [] } - board.cardProperties.push(property) + newBoard.cardProperties.push(property) + this.board = newBoard didChange = true } // At least one view if (this.views.length < 1) { - const view = new BoardView() + const view = new MutableBoardView() view.parentId = board.id view.groupById = board.cardProperties.find(o => o.type === "select")?.id this.views.push(view) diff --git a/webapp/src/cardFilter.ts b/webapp/src/cardFilter.ts index d5a9a46e9..c551aa94d 100644 --- a/webapp/src/cardFilter.ts +++ b/webapp/src/cardFilter.ts @@ -7,11 +7,11 @@ import {FilterGroup} from './filterGroup' import {Utils} from './utils' class CardFilter { - static applyFilterGroup(filterGroup: FilterGroup, templates: IPropertyTemplate[], cards: Card[]): Card[] { + static applyFilterGroup(filterGroup: FilterGroup, templates: readonly IPropertyTemplate[], cards: Card[]): Card[] { return cards.filter((card) => this.isFilterGroupMet(filterGroup, templates, card)) } - static isFilterGroupMet(filterGroup: FilterGroup, templates: IPropertyTemplate[], card: Card): boolean { + static isFilterGroupMet(filterGroup: FilterGroup, templates: readonly IPropertyTemplate[], card: Card): boolean { const {filters} = filterGroup if (filterGroup.filters.length < 1) { @@ -43,7 +43,7 @@ class CardFilter { return true } - static isClauseMet(filter: FilterClause, templates: IPropertyTemplate[], card: Card): boolean { + static isClauseMet(filter: FilterClause, templates: readonly IPropertyTemplate[], card: Card): boolean { const value = card.properties[filter.propertyId] switch (filter.condition) { case 'includes': { @@ -71,7 +71,7 @@ class CardFilter { return true } - static propertiesThatMeetFilterGroup(filterGroup: FilterGroup, templates: IPropertyTemplate[]): Record { + static propertiesThatMeetFilterGroup(filterGroup: FilterGroup, templates: readonly IPropertyTemplate[]): Record { // TODO: Handle filter groups const filters = filterGroup.filters.filter((o) => !FilterGroup.isAnInstanceOf(o)) if (filters.length < 1) { @@ -93,7 +93,7 @@ class CardFilter { return result } - static propertyThatMeetsFilterClause(filterClause: FilterClause, templates: IPropertyTemplate[]): { id: string, value?: string } { + static propertyThatMeetsFilterClause(filterClause: FilterClause, templates: readonly IPropertyTemplate[]): { id: string, value?: string } { const template = templates.find((o) => o.id === filterClause.propertyId) switch (filterClause.condition) { case 'includes': { diff --git a/webapp/src/cardTree.ts b/webapp/src/cardTree.ts index 34b1b7c97..edd39e281 100644 --- a/webapp/src/cardTree.ts +++ b/webapp/src/cardTree.ts @@ -1,9 +1,9 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {Block} from './blocks/block' import {Card} from './blocks/card' +import { IOrderedBlock } from './blocks/orderedBlock' import octoClient from './octoClient' -import {IBlock, IOrderedBlock} from './octoTypes' +import {IBlock} from './octoTypes' import {OctoUtils} from './octoUtils' interface CardTree { @@ -25,7 +25,7 @@ class MutableCardTree implements CardTree { this.rebuild(OctoUtils.hydrateBlocks(blocks)) } - private rebuild(blocks: Block[]) { + private rebuild(blocks: IBlock[]) { this.card = blocks.find(o => o.id === this.cardId) as Card this.comments = blocks diff --git a/webapp/src/components/boardCard.tsx b/webapp/src/components/boardCard.tsx index 41f55264e..e510656d9 100644 --- a/webapp/src/components/boardCard.tsx +++ b/webapp/src/components/boardCard.tsx @@ -1,8 +1,8 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React from 'react' +import { MutableBlock } from '../blocks/block' -import {Block} from '../blocks/block' import {IPropertyTemplate} from '../blocks/board' import {Card} from '../blocks/card' import {Menu} from '../menu' @@ -89,7 +89,7 @@ class BoardCard extends React.Component { break } case 'duplicate': { - const newCard = Block.duplicate(card) + const newCard = MutableBlock.duplicate(card) mutator.insertBlock(newCard, 'duplicate card') break } diff --git a/webapp/src/components/boardComponent.tsx b/webapp/src/components/boardComponent.tsx index 6166f51e7..7d5d5a007 100644 --- a/webapp/src/components/boardComponent.tsx +++ b/webapp/src/components/boardComponent.tsx @@ -5,7 +5,7 @@ import React from 'react' import {Archiver} from '../archiver' import {BlockIcons} from '../blockIcons' import {IPropertyOption} from '../blocks/board' -import {Card} from '../blocks/card' +import {Card, MutableCard} from '../blocks/card' import {BoardTree} from '../boardTree' import {CardFilter} from '../cardFilter' import ViewMenu from '../components/viewMenu' @@ -49,8 +49,8 @@ class BoardComponent extends React.Component { } componentDidUpdate(prevPros: Props, prevState: State) { - if (this.state.isSearching && !prevState.isSearching) { - this.searchFieldRef.current.focus() + if (this.state.isSearching && !prevState.isSearching) { + this.searchFieldRef.current.focus() } } @@ -63,14 +63,14 @@ class BoardComponent extends React.Component { ) } - const propertyValues = boardTree.groupByProperty?.options || [] + const propertyValues = boardTree.groupByProperty?.options || [] console.log(`${propertyValues.length} propertyValues`) - const groupByStyle = {color: '#000000'} - const {board, activeView} = boardTree + const groupByStyle = {color: '#000000'} + const {board, activeView} = boardTree const visiblePropertyTemplates = board.cardProperties.filter((template) => activeView.visiblePropertyIds.includes(template.id)) const hasFilter = activeView.filter && activeView.filter.filters?.length > 0 - const hasSort = activeView.sortOptions.length > 0 + const hasSort = activeView.sortOptions.length > 0 return (
@@ -96,7 +96,7 @@ class BoardComponent extends React.Component { @@ -203,7 +203,7 @@ class BoardComponent extends React.Component { this.setState({...this.state, isSearching: true}) }} >Search
- } + }
{ @@ -291,9 +291,9 @@ class BoardComponent extends React.Component { key={color.id} id={color.id} name={color.name} - onClick={() => mutator.changePropertyOptionColor(boardTree.board, group.option, color.id)} + onClick={() => mutator.changePropertyOptionColor(boardTree.board, boardTree.groupByProperty, group.option, color.id)} />), - )} + )}
), - )} + )}