1
0
mirror of https://github.com/mattermost/focalboard.git synced 2024-11-24 08:22:29 +02:00

Immutable blocks

This commit is contained in:
Chen-I Lim 2020-10-20 18:28:55 -07:00
parent 932de3a17f
commit aa950240d9
25 changed files with 469 additions and 339 deletions

View File

@ -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<string, any>
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}

View File

@ -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}

View File

@ -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}

View File

@ -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<Record<string, string>>
}
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}

View File

@ -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}

View File

@ -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}

View File

@ -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}

View File

@ -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}

View File

@ -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)

View File

@ -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<string, string> {
static propertiesThatMeetFilterGroup(filterGroup: FilterGroup, templates: readonly IPropertyTemplate[]): Record<string, string> {
// 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': {

View File

@ -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

View File

@ -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<BoardCardProps, BoardCardState> {
break
}
case 'duplicate': {
const newCard = Block.duplicate(card)
const newCard = MutableBlock.duplicate(card)
mutator.insertBlock(newCard, 'duplicate card')
break
}

View File

@ -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<Props, State> {
}
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<Props, State> {
)
}
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 (
<div className='octo-app'>
@ -96,7 +96,7 @@ class BoardComponent extends React.Component<Props, State> {
<Button
style={{display: (!board.icon && this.state.isHoverOnCover) ? null : 'none'}}
onClick={() => {
const newIcon = BlockIcons.shared.randomIcon()
const newIcon = BlockIcons.shared.randomIcon()
mutator.changeIcon(board, newIcon)
}}
>Add Icon</Button>
@ -203,7 +203,7 @@ class BoardComponent extends React.Component<Props, State> {
this.setState({...this.state, isSearching: true})
}}
>Search</div>
}
}
<div
className='octo-button'
onClick={(e) => {
@ -291,9 +291,9 @@ class BoardComponent extends React.Component<Props, State> {
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)}
/>),
)}
)}
</Menu>
</MenuWrapper>
<Button
@ -302,7 +302,7 @@ class BoardComponent extends React.Component<Props, State> {
}}
><div className='imageAdd'/></Button>
</div>),
)}
)}
<div className='octo-board-header-cell'>
<Button
@ -390,13 +390,13 @@ class BoardComponent extends React.Component<Props, State> {
}
async addCard(groupByValue?: string) {
const {boardTree} = this.props
const {boardTree} = this.props
const {activeView, board} = boardTree
const card = new Card()
const card = new MutableCard()
card.parentId = boardTree.board.id
card.properties = CardFilter.propertiesThatMeetFilterGroup(activeView.filter, board.cardProperties)
card.icon = BlockIcons.shared.randomIcon()
card.properties = CardFilter.propertiesThatMeetFilterGroup(activeView.filter, board.cardProperties)
card.icon = BlockIcons.shared.randomIcon()
if (boardTree.groupByProperty) {
card.properties[boardTree.groupByProperty.id] = groupByValue
}
@ -423,13 +423,13 @@ class BoardComponent extends React.Component<Props, State> {
OldMenu.shared.options = [
{id: 'exportBoardArchive', name: 'Export board archive'},
{id: 'testAdd100Cards', name: 'TEST: Add 100 cards'},
{id: 'testAdd1000Cards', name: 'TEST: Add 1,000 cards'},
{id: 'testAdd1000Cards', name: 'TEST: Add 1,000 cards'},
{id: 'testRandomizeIcons', name: 'TEST: Randomize icons'},
]
OldMenu.shared.onMenuClicked = async (id: string) => {
switch (id) {
case 'exportBoardArchive': {
OldMenu.shared.onMenuClicked = async (id: string) => {
switch (id) {
case 'exportBoardArchive': {
Archiver.exportBoardTree(boardTree)
break
}
@ -437,102 +437,102 @@ class BoardComponent extends React.Component<Props, State> {
this.testAddCards(100)
break
}
case 'testAdd1000Cards': {
case 'testAdd1000Cards': {
this.testAddCards(1000)
break
}
case 'testRandomizeIcons': {
this.testRandomizeIcons()
break
}
}
}
case 'testRandomizeIcons': {
this.testRandomizeIcons()
break
}
}
}
OldMenu.shared.showAtElement(e.target as HTMLElement)
}
private async testAddCards(count: number) {
const {boardTree} = this.props
const {board, activeView} = boardTree
const {boardTree} = this.props
const {board, activeView} = boardTree
const startCount = boardTree?.cards?.length
let optionIndex = 0
let optionIndex = 0
for (let i = 0; i < count; i++) {
const card = new Card()
card.parentId = boardTree.board.id
card.properties = CardFilter.propertiesThatMeetFilterGroup(activeView.filter, board.cardProperties)
for (let i = 0; i < count; i++) {
const card = new MutableCard()
card.parentId = boardTree.board.id
card.properties = CardFilter.propertiesThatMeetFilterGroup(activeView.filter, board.cardProperties)
if (boardTree.groupByProperty && boardTree.groupByProperty.options.length > 0) {
// Cycle through options
const option = boardTree.groupByProperty.options[optionIndex]
optionIndex = (optionIndex + 1) % boardTree.groupByProperty.options.length
card.properties[boardTree.groupByProperty.id] = option.value
const option = boardTree.groupByProperty.options[optionIndex]
optionIndex = (optionIndex + 1) % boardTree.groupByProperty.options.length
card.properties[boardTree.groupByProperty.id] = option.value
card.title = `Test Card ${startCount + i + 1}`
card.icon = BlockIcons.shared.randomIcon()
}
await mutator.insertBlock(card, 'test add card')
await mutator.insertBlock(card, 'test add card')
}
}
private async testRandomizeIcons() {
const {boardTree} = this.props
const {boardTree} = this.props
for (const card of boardTree.cards) {
mutator.changeIcon(card, BlockIcons.shared.randomIcon(), 'randomize icon')
mutator.changeIcon(card, BlockIcons.shared.randomIcon(), 'randomize icon')
}
}
private async propertiesClicked(e: React.MouseEvent) {
const {boardTree} = this.props
const {boardTree} = this.props
const {activeView} = boardTree
const selectProperties = boardTree.board.cardProperties
OldMenu.shared.options = selectProperties.map((o) => {
const isVisible = activeView.visiblePropertyIds.includes(o.id)
const isVisible = activeView.visiblePropertyIds.includes(o.id)
return {id: o.id, name: o.name, type: 'switch', isOn: isVisible}
})
})
OldMenu.shared.onMenuToggled = async (id: string, isOn: boolean) => {
const property = selectProperties.find((o) => o.id === id)
Utils.assertValue(property)
Utils.log(`Toggle property ${property.name} ${isOn}`)
OldMenu.shared.onMenuToggled = async (id: string, isOn: boolean) => {
const property = selectProperties.find((o) => o.id === id)
Utils.assertValue(property)
Utils.log(`Toggle property ${property.name} ${isOn}`)
let newVisiblePropertyIds = []
if (activeView.visiblePropertyIds.includes(id)) {
newVisiblePropertyIds = activeView.visiblePropertyIds.filter((o) => o !== id)
} else {
newVisiblePropertyIds = activeView.visiblePropertyIds.filter((o) => o !== id)
} else {
newVisiblePropertyIds = [...activeView.visiblePropertyIds, id]
}
}
await mutator.changeViewVisibleProperties(activeView, newVisiblePropertyIds)
}
OldMenu.shared.showAtElement(e.target as HTMLElement)
}
OldMenu.shared.showAtElement(e.target as HTMLElement)
}
private async groupByClicked(e: React.MouseEvent) {
const {boardTree} = this.props
const {boardTree} = this.props
const selectProperties = boardTree.board.cardProperties.filter((o) => o.type === 'select')
OldMenu.shared.options = selectProperties.map((o) => {
const selectProperties = boardTree.board.cardProperties.filter((o) => o.type === 'select')
OldMenu.shared.options = selectProperties.map((o) => {
return {id: o.id, name: o.name}
})
OldMenu.shared.onMenuClicked = async (command: string) => {
OldMenu.shared.onMenuClicked = async (command: string) => {
if (boardTree.activeView.groupById === command) {
return
}
await mutator.changeViewGroupById(boardTree.activeView, command)
}
OldMenu.shared.showAtElement(e.target as HTMLElement)
OldMenu.shared.showAtElement(e.target as HTMLElement)
}
async addGroupClicked() {
console.log('onAddGroupClicked')
const {boardTree} = this.props
const {boardTree} = this.props
const option: IPropertyOption = {
value: 'New group',
color: '#cccccc',
}
}
Utils.assert(boardTree.groupByProperty)
await mutator.insertPropertyOption(boardTree, boardTree.groupByProperty, option, 'add group')
@ -540,7 +540,7 @@ class BoardComponent extends React.Component<Props, State> {
async onDropToColumn(option: IPropertyOption) {
const {boardTree} = this.props
const {draggedCard, draggedHeaderOption} = this
const {draggedCard, draggedHeaderOption} = this
const propertyValue = option ? option.value : undefined
Utils.assertValue(mutator)
@ -551,27 +551,27 @@ class BoardComponent extends React.Component<Props, State> {
const oldValue = draggedCard.properties[boardTree.groupByProperty.id]
if (propertyValue !== oldValue) {
await mutator.changePropertyValue(draggedCard, boardTree.groupByProperty.id, propertyValue, 'drag card')
}
} else if (draggedHeaderOption) {
Utils.log(`ondrop. Header option: ${draggedHeaderOption.value}, column: ${propertyValue}`)
Utils.assertValue(boardTree.groupByProperty)
}
} else if (draggedHeaderOption) {
Utils.log(`ondrop. Header option: ${draggedHeaderOption.value}, column: ${propertyValue}`)
Utils.assertValue(boardTree.groupByProperty)
// Move option to new index
const {board} = boardTree
const options = boardTree.groupByProperty.options
const destIndex = option ? options.indexOf(option) : 0
// Move option to new index
const {board} = boardTree
const options = boardTree.groupByProperty.options
const destIndex = option ? options.indexOf(option) : 0
await mutator.changePropertyOptionOrder(board, boardTree.groupByProperty, draggedHeaderOption, destIndex)
}
}
onSearchKeyDown(e: React.KeyboardEvent) {
if (e.keyCode === 27) { // ESC: Clear search
if (e.keyCode === 27) { // ESC: Clear search
this.searchFieldRef.current.text = ''
this.setState({...this.state, isSearching: false})
this.props.setSearchText(undefined)
e.preventDefault()
}
}
}
searchChanged(text?: string) {

View File

@ -1,28 +1,28 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react'
import {BlockIcons} from '../blockIcons'
import {Block} from '../blocks/block'
import {Card} from '../blocks/card'
import {TextBlock} from '../blocks/textBlock'
import {BoardTree} from '../boardTree'
import {CardTree, MutableCardTree} from '../cardTree'
import {Menu as OldMenu, MenuOption} from '../menu'
import { BlockIcons } from '../blockIcons'
import { MutableCommentBlock } from '../blocks/commentBlock'
import { IOrderedBlock } from '../blocks/orderedBlock'
import { MutableTextBlock } from '../blocks/textBlock'
import { BoardTree } from '../boardTree'
import { CardTree, MutableCardTree } from '../cardTree'
import { Menu as OldMenu, MenuOption } from '../menu'
import mutator from '../mutator'
import {OctoListener} from '../octoListener'
import {IBlock, IOrderedBlock} from '../octoTypes'
import {OctoUtils} from '../octoUtils'
import {PropertyMenu} from '../propertyMenu'
import {Utils} from '../utils'
import { OctoListener } from '../octoListener'
import { IBlock } from '../octoTypes'
import { OctoUtils } from '../octoUtils'
import { PropertyMenu } from '../propertyMenu'
import { Utils } from '../utils'
import Button from './button'
import {Editable} from './editable'
import {MarkdownEditor} from './markdownEditor'
import { Editable } from './editable'
import { MarkdownEditor } from './markdownEditor'
type Props = {
boardTree: BoardTree
card: Card
cardId: string
}
type State = {
@ -42,13 +42,14 @@ export default class CardDetail extends React.Component<Props, State> {
componentDidMount() {
this.cardListener = new OctoListener()
this.cardListener.open(this.props.card.id, async () => {
this.cardListener.open(this.props.cardId, async (blockId) => {
Utils.log(`cardListener.onChanged: ${blockId}`)
await cardTree.sync()
this.setState({cardTree})
this.setState({...this.state, cardTree})
})
const cardTree = new MutableCardTree(this.props.card.id)
const cardTree = new MutableCardTree(this.props.cardId)
cardTree.sync().then(() => {
this.setState({cardTree})
this.setState({...this.state, cardTree})
setTimeout(() => {
if (this.titleRef.current) {
this.titleRef.current.focus()
@ -58,13 +59,13 @@ export default class CardDetail extends React.Component<Props, State> {
}
render() {
const {boardTree, card} = this.props
const {cardTree} = this.state
const {boardTree} = this.props
const {cardTree} = this.state
const {board} = boardTree
if (!cardTree) {
return null
}
const {comments} = cardTree
const {card, comments} = cardTree
const newCommentPlaceholderText = 'Add a comment...'
@ -133,8 +134,10 @@ export default class CardDetail 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 MutableTextBlock()
block.parentId = card.id
block.title = text
block.order = cardTree.contents.length * 1000
mutator.insertBlock(block, 'add card text')
}}
/>
@ -363,8 +366,9 @@ export default class CardDetail extends React.Component<Props, State> {
OldMenu.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 MutableTextBlock()
block.parentId = card.id
block.order = cardTree.contents.length * 1000
await mutator.insertBlock(block, 'add text')
break
case 'image':
@ -386,17 +390,17 @@ export default class CardDetail extends React.Component<Props, State> {
}
async sendComment(text: string) {
const {card} = this.props
const {cardId} = this.props
Utils.assertValue(card)
Utils.assertValue(cardId)
const block = new Block({type: 'comment', parentId: card.id, title: text})
const block = new MutableCommentBlock({parentId: cardId, title: text})
await mutator.insertBlock(block, 'add comment')
}
private showContentBlockMenu(e: React.MouseEvent, block: IOrderedBlock) {
const {cardTree} = this.state
const {card} = this.props
const {cardId} = this.props
const index = cardTree.contents.indexOf(block)
const options: MenuOption[] = []
@ -440,7 +444,8 @@ export default class CardDetail extends React.Component<Props, State> {
break
}
case 'insertAbove-text': {
const newBlock = new TextBlock({parentId: card.id})
const newBlock = new MutableTextBlock()
newBlock.parentId = cardId
// TODO: Handle need to reorder all blocks
newBlock.order = OctoUtils.getOrderBefore(block, cardTree.contents)
@ -451,7 +456,7 @@ export default class CardDetail extends React.Component<Props, State> {
case 'insertAbove-image': {
Utils.selectLocalFile(
(file) => {
mutator.createImageBlock(card.id, file, OctoUtils.getOrderBefore(block, cardTree.contents))
mutator.createImageBlock(cardId, file, OctoUtils.getOrderBefore(block, cardTree.contents))
},
'.jpg,.jpeg,.png')
@ -467,7 +472,8 @@ export default class CardDetail extends React.Component<Props, State> {
}
private iconClicked(e: React.MouseEvent) {
const {card} = this.props
const {cardTree} = this.state
const {card} = cardTree
OldMenu.shared.options = [
{id: 'random', name: 'Random'},

View File

@ -37,7 +37,7 @@ class CardDialog extends React.Component<Props> {
>
<CardDetail
boardTree={this.props.boardTree}
card={this.props.card}
cardId={this.props.card.id}
/>
</Dialog>
)

View File

@ -1,14 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react'
import {Archiver} from '../archiver'
import {Board} from '../blocks/board'
import {BoardTree} from '../boardTree'
import { Archiver } from '../archiver'
import { Board, MutableBoard } from '../blocks/board'
import { BoardTree } from '../boardTree'
import mutator from '../mutator'
import Menu from '../widgets/menu'
import MenuWrapper from '../widgets/menuWrapper'
import mutator from '../mutator'
import {WorkspaceTree} from '../workspaceTree'
import { WorkspaceTree } from '../workspaceTree'
type Props = {
showBoard: (id: string) => void
@ -109,7 +109,7 @@ class Sidebar extends React.Component<Props> {
const {boardTree, showBoard} = this.props
const oldBoardId = boardTree?.board?.id
const board = new Board()
const board = new MutableBoard()
await mutator.insertBlock(
board,
'add board',
@ -126,4 +126,5 @@ class Sidebar extends React.Component<Props> {
}
}
export {Sidebar}
export { Sidebar }

View File

@ -5,7 +5,7 @@ import React from 'react'
import {Archiver} from '../archiver'
import {BlockIcons} from '../blockIcons'
import {IPropertyTemplate} from '../blocks/board'
import {Card} from '../blocks/card'
import {Card, MutableCard} from '../blocks/card'
import {BoardTree} from '../boardTree'
import ViewMenu from '../components/viewMenu'
import {CsvExporter} from '../csvExporter'
@ -275,12 +275,12 @@ class TableComponent extends React.Component<Props, State> {
}
const tableRow = (<TableRow
key={card.id}
ref={tableRowRef}
boardTree={boardTree}
card={card}
focusOnMount={focusOnMount}
onKeyDown={(e) => {
key={card.id}
ref={tableRowRef}
boardTree={boardTree}
card={card}
focusOnMount={focusOnMount}
onKeyDown={(e) => {
if (e.keyCode === 13) {
// Enter: Insert new card if on last row
if (cards.length > 0 && cards[cards.length - 1] === card) {
@ -451,7 +451,7 @@ class TableComponent extends React.Component<Props, State> {
async addCard(show = false) {
const {boardTree} = this.props
const card = new Card()
const card = new MutableCard()
card.parentId = boardTree.board.id
card.icon = BlockIcons.shared.randomIcon()
await mutator.insertBlock(

View File

@ -3,7 +3,7 @@
import React from 'react'
import {Board} from '../blocks/board'
import {BoardView} from '../blocks/boardView'
import {BoardView, MutableBoardView} from '../blocks/boardView'
import {BoardTree} from '../boardTree'
import mutator from '../mutator'
import {Utils} from '../utils'
@ -35,7 +35,7 @@ export default class ViewMenu extends React.Component<Props> {
handleAddViewBoard = async (id: string) => {
const {board, boardTree, showView} = this.props
Utils.log('addview-board')
const view = new BoardView()
const view = new MutableBoardView()
view.title = 'Board View'
view.viewType = 'board'
view.parentId = board.id
@ -57,7 +57,7 @@ export default class ViewMenu extends React.Component<Props> {
const {board, boardTree, showView} = this.props
Utils.log('addview-table')
const view = new BoardView()
const view = new MutableBoardView()
view.title = 'Table View'
view.viewType = 'table'
view.parentId = board.id

View File

@ -1,14 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
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 {MutableBlock} from './blocks/block'
import {Board, IPropertyOption, IPropertyTemplate, MutableBoard, PropertyType} from './blocks/board'
import {BoardView, ISortOption, MutableBoardView} from './blocks/boardView'
import {Card, MutableCard} from './blocks/card'
import {MutableImageBlock} from './blocks/imageBlock'
import {IOrderedBlock, MutableOrderedBlock} from './blocks/orderedBlock'
import {BoardTree} from './boardTree'
import {FilterGroup} from './filterGroup'
import octoClient from './octoClient'
import {IBlock, IOrderedBlock} from './octoTypes'
import {IBlock} from './octoTypes'
import undoManager from './undomanager'
import {Utils} from './utils'
@ -67,13 +68,15 @@ class Mutator {
async changeTitle(block: IBlock, title: string, description = 'change title') {
const oldValue = block.title
const newBlock = new MutableBlock(block)
newBlock.title = title
await undoManager.perform(
async () => {
block.title = title
await octoClient.updateBlock(block)
await octoClient.updateBlock(newBlock)
},
async () => {
block.title = oldValue
await octoClient.updateBlock(block)
},
description,
@ -81,14 +84,27 @@ class Mutator {
}
async changeIcon(block: Card | Board, icon: string, description = 'change icon') {
const oldValue = block.icon
var newBlock: IBlock
switch (block.type) {
case 'card': {
const card = new MutableCard(block)
card.icon = icon
newBlock = card
break
}
case 'board': {
const board = new MutableBoard(block)
board.icon = icon
newBlock = board
break
}
}
await undoManager.perform(
async () => {
block.icon = icon
await octoClient.updateBlock(block)
await octoClient.updateBlock(newBlock)
},
async () => {
block.icon = oldValue
await octoClient.updateBlock(block)
},
description,
@ -96,14 +112,14 @@ class Mutator {
}
async changeOrder(block: IOrderedBlock, order: number, description = 'change order') {
const oldValue = block.order
const newBlock = new MutableOrderedBlock(block)
newBlock.order = order
await undoManager.perform(
async () => {
block.order = order
await octoClient.updateBlock(block)
await octoClient.updateBlock(newBlock)
},
async () => {
block.order = oldValue
await octoClient.updateBlock(block)
},
description,
@ -128,17 +144,20 @@ class Mutator {
}
}
const oldBlocks: IBlock[] = [new Board(board)]
const oldBlocks: IBlock[] = [board]
const changedBlocks: IBlock[] = [board]
board.cardProperties.splice(index, 0, template)
const newBoard = new MutableBoard(board)
newBoard.cardProperties.splice(index, 0, template)
const changedBlocks: IBlock[] = [newBoard]
let description = 'add property'
if (activeView.viewType === 'table') {
oldBlocks.push(new BoardView(activeView))
activeView.visiblePropertyIds.push(template.id)
changedBlocks.push(activeView)
oldBlocks.push(activeView)
const newActiveView = new MutableBoardView(activeView)
newActiveView.visiblePropertyIds.push(template.id)
changedBlocks.push(newActiveView)
description = 'add column'
}
@ -154,30 +173,34 @@ class Mutator {
)
}
async duplicatePropertyTemplate(boardTree: BoardTree, propertyId: string) {
async duplicatePropertyTemplate(boardTree: BoardTree, propertyId: string): Promise<IBlock[]> {
const {board, activeView} = boardTree
const oldBlocks: IBlock[] = [new Board(board)]
const oldBlocks: IBlock[] = [board]
const changedBlocks: IBlock[] = [board]
const index = board.cardProperties.findIndex((o) => o.id === propertyId)
const newBoard = new MutableBoard(board)
const changedBlocks: IBlock[] = [newBoard]
const index = newBoard.cardProperties.findIndex((o) => o.id === propertyId)
if (index === -1) {
Utils.assertFailure(`Cannot find template with id: ${propertyId}`); return
Utils.assertFailure(`Cannot find template with id: ${propertyId}`)
return
}
const srcTemplate = board.cardProperties[index]
const srcTemplate = newBoard.cardProperties[index]
const newTemplate: IPropertyTemplate = {
id: Utils.createGuid(),
name: `Copy of ${srcTemplate.name}`,
type: srcTemplate.type,
options: srcTemplate.options.slice(),
}
board.cardProperties.splice(index + 1, 0, newTemplate)
newBoard.cardProperties.splice(index + 1, 0, newTemplate)
let description = 'duplicate property'
if (activeView.viewType === 'table') {
oldBlocks.push(new BoardView(activeView))
activeView.visiblePropertyIds.push(newTemplate.id)
changedBlocks.push(activeView)
oldBlocks.push(activeView)
const newActiveView = new MutableBoardView(activeView)
newActiveView.visiblePropertyIds.push(newTemplate.id)
changedBlocks.push(newActiveView)
description = 'duplicate column'
}
@ -204,13 +227,14 @@ class Mutator {
Utils.log(`srcIndex: ${srcIndex}, destIndex: ${destIndex}`)
newValue.splice(destIndex, 0, newValue.splice(srcIndex, 1)[0])
const newBoard = new MutableBoard(board)
newBoard.cardProperties = newValue
await undoManager.perform(
async () => {
board.cardProperties = newValue
await octoClient.updateBlock(board)
await octoClient.updateBlock(newBoard)
},
async () => {
board.cardProperties = oldValue
await octoClient.updateBlock(board)
},
'reorder properties',
@ -220,23 +244,28 @@ class Mutator {
async deleteProperty(boardTree: BoardTree, propertyId: string) {
const {board, views, cards} = boardTree
const oldBlocks: IBlock[] = [new Board(board)]
const oldBlocks: IBlock[] = [board]
const changedBlocks: IBlock[] = [board]
board.cardProperties = board.cardProperties.filter((o) => o.id !== propertyId)
const newBoard = new MutableBoard(board)
const changedBlocks: IBlock[] = [newBoard]
newBoard.cardProperties = board.cardProperties.filter((o) => o.id !== propertyId)
views.forEach((view) => {
if (view.visiblePropertyIds.includes(propertyId)) {
oldBlocks.push(new BoardView(view))
view.visiblePropertyIds = view.visiblePropertyIds.filter((o) => o !== propertyId)
changedBlocks.push(view)
oldBlocks.push(view)
const newView = new MutableBoardView(view)
newView.visiblePropertyIds = view.visiblePropertyIds.filter((o) => o !== propertyId)
changedBlocks.push(newView)
}
})
cards.forEach((card) => {
if (card.properties[propertyId]) {
oldBlocks.push(new Block(card))
delete card.properties[propertyId]
changedBlocks.push(card)
oldBlocks.push(card)
const newCard = new MutableCard(card)
delete newCard.properties[propertyId]
changedBlocks.push(newCard)
}
})
@ -252,12 +281,14 @@ class Mutator {
}
async renameProperty(board: Board, propertyId: string, name: string) {
const oldBlocks: IBlock[] = [new Board(board)]
const changedBlocks: IBlock[] = [board]
const oldBlocks: IBlock[] = [board]
const newBoard = new MutableBoard(board)
const changedBlocks: IBlock[] = [newBoard]
const template = board.cardProperties.find((o) => o.id === propertyId)
const template = newBoard.cardProperties.find((o) => o.id === propertyId)
if (!template) {
Utils.assertFailure(`Can't find property template with Id: ${propertyId}`); return
Utils.assertFailure(`Can't find property template with Id: ${propertyId}`)
return
}
Utils.log(`renameProperty from ${template.name} to ${name}`)
template.name = name
@ -280,18 +311,16 @@ class Mutator {
Utils.assert(board.cardProperties.includes(template))
const oldValue = template.options
const newValue = template.options.slice()
newValue.push(option)
const newBoard = new MutableBoard(board)
const newTemplate = newBoard.cardProperties.find(o => o.id === template.id)
newTemplate.options.push(option)
await undoManager.perform(
async () => {
template.options = newValue
await octoClient.updateBlock(board)
await octoClient.updateBlock(newBoard)
},
async () => {
// TODO: Also remove property on cards
template.options = oldValue
await octoClient.updateBlock(board)
},
description,
@ -301,18 +330,17 @@ class Mutator {
async deletePropertyOption(boardTree: BoardTree, template: IPropertyTemplate, option: IPropertyOption) {
const {board} = boardTree
const oldValue = template.options.slice()
const newValue = template.options.filter((o) => o !== option)
const newBoard = new MutableBoard(board)
const newTemplate = newBoard.cardProperties.find(o => o.id === template.id)
newTemplate.options = newTemplate.options.filter(o => o.value !== option.value)
// TODO: Also remove property on cards
await undoManager.perform(
async () => {
template.options = newValue
await octoClient.updateBlock(board)
await octoClient.updateBlock(newBoard)
},
async () => {
template.options = oldValue
await octoClient.updateBlock(board)
},
'delete option',
@ -320,20 +348,18 @@ class Mutator {
}
async changePropertyOptionOrder(board: Board, template: IPropertyTemplate, option: IPropertyOption, destIndex: number) {
const oldValue = template.options
const newValue = template.options.slice()
const srcIndex = newValue.indexOf(option)
const srcIndex = template.options.indexOf(option)
Utils.log(`srcIndex: ${srcIndex}, destIndex: ${destIndex}`)
newValue.splice(destIndex, 0, newValue.splice(srcIndex, 1)[0])
const newBoard = new MutableBoard(board)
const newTemplate = newBoard.cardProperties.find(o => o.id === template.id)
newTemplate.options.splice(destIndex, 0, newTemplate.options.splice(srcIndex, 1)[0])
await undoManager.perform(
async () => {
template.options = newValue
await octoClient.updateBlock(board)
await octoClient.updateBlock(newBoard)
},
async () => {
template.options = oldValue
await octoClient.updateBlock(board)
},
'reorder options',
@ -344,18 +370,23 @@ class Mutator {
const {board, cards} = boardTree
const oldValue = option.value
const oldBlocks: IBlock[] = [new Board(board)]
const oldBlocks: IBlock[] = [board]
const changedBlocks: IBlock[] = [board]
option.value = value
const newBoard = new MutableBoard(board)
const newTemplate = newBoard.cardProperties.find(o => o.id === propertyTemplate.id)
const newOption = newTemplate.options.find(o => o.value === oldValue)
newOption.value = value
const changedBlocks: IBlock[] = [newBoard]
// Change the value on all cards that have this property too
for (const card of cards) {
const propertyValue = card.properties[propertyTemplate.id]
if (propertyValue && propertyValue === oldValue) {
oldBlocks.push(new Block(card))
card.properties[propertyTemplate.id] = value
changedBlocks.push(card)
oldBlocks.push(card)
const newCard = new MutableCard(card)
newCard.properties[propertyTemplate.id] = value
changedBlocks.push(newCard)
}
}
@ -372,16 +403,19 @@ class Mutator {
return changedBlocks
}
async changePropertyOptionColor(board: Board, option: IPropertyOption, color: string) {
async changePropertyOptionColor(board: Board, template: IPropertyTemplate, option: IPropertyOption, color: string) {
const oldValue = option.color
const newBoard = new MutableBoard(board)
const newTemplate = newBoard.cardProperties.find(o => o.id === template.id)
const newOption = newTemplate.options.find(o => o.value === option.value)
newOption.color = color
undoManager.perform(
async () => {
option.color = color
await octoClient.updateBlock(board)
await octoClient.updateBlock(newBoard)
},
async () => {
option.color = oldValue
await octoClient.updateBlock(board)
},
'change option color',
@ -390,13 +424,15 @@ class Mutator {
async changePropertyValue(card: Card, propertyId: string, value?: string, description = 'change property') {
const oldValue = card.properties[propertyId]
const newCard = new MutableCard(card)
newCard.properties[propertyId] = value
await undoManager.perform(
async () => {
card.properties[propertyId] = value
await octoClient.updateBlock(card)
await octoClient.updateBlock(newCard)
},
async () => {
card.properties[propertyId] = oldValue
await octoClient.updateBlock(card)
},
description,
@ -405,13 +441,16 @@ class Mutator {
async changePropertyType(board: Board, propertyTemplate: IPropertyTemplate, type: PropertyType) {
const oldValue = propertyTemplate.type
const newBoard = new MutableBoard(board)
const newTemplate = newBoard.cardProperties.find(o => o.id === propertyTemplate.id)
newTemplate.type = type
await undoManager.perform(
async () => {
propertyTemplate.type = type
await octoClient.updateBlock(board)
await octoClient.updateBlock(newBoard)
},
async () => {
propertyTemplate.type = oldValue
await octoClient.updateBlock(board)
},
'change property type',
@ -423,13 +462,14 @@ class Mutator {
async changeViewSortOptions(view: BoardView, sortOptions: ISortOption[]) {
const oldValue = view.sortOptions
const newView = new MutableBoardView(view)
newView.sortOptions = sortOptions
await undoManager.perform(
async () => {
view.sortOptions = sortOptions
await octoClient.updateBlock(view)
await octoClient.updateBlock(newView)
},
async () => {
view.sortOptions = oldValue
await octoClient.updateBlock(view)
},
'sort',
@ -439,13 +479,14 @@ class Mutator {
async changeViewFilter(view: BoardView, filter?: FilterGroup) {
const oldValue = view.filter
const newView = new MutableBoardView(view)
newView.filter = filter
await undoManager.perform(
async () => {
view.filter = filter
await octoClient.updateBlock(view)
await octoClient.updateBlock(newView)
},
async () => {
view.filter = oldValue
await octoClient.updateBlock(view)
},
'filter',
@ -455,13 +496,14 @@ class Mutator {
async changeViewVisibleProperties(view: BoardView, visiblePropertyIds: string[]) {
const oldValue = view.visiblePropertyIds
const newView = new MutableBoardView(view)
newView.visiblePropertyIds = visiblePropertyIds
await undoManager.perform(
async () => {
view.visiblePropertyIds = visiblePropertyIds
await octoClient.updateBlock(view)
await octoClient.updateBlock(newView)
},
async () => {
view.visiblePropertyIds = oldValue
await octoClient.updateBlock(view)
},
'hide / show property',
@ -471,13 +513,14 @@ class Mutator {
async changeViewGroupById(view: BoardView, groupById: string) {
const oldValue = view.groupById
const newView = new MutableBoardView(view)
newView.groupById = groupById
await undoManager.perform(
async () => {
view.groupById = groupById
await octoClient.updateBlock(view)
await octoClient.updateBlock(newView)
},
async () => {
view.groupById = oldValue
await octoClient.updateBlock(view)
},
'group by',
@ -500,7 +543,8 @@ class Mutator {
return undefined
}
const block = new ImageBlock({parentId})
const block = new MutableImageBlock()
block.parentId = parentId
block.order = order
block.url = url

View File

@ -1,5 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import { IMutableBlock } from './blocks/block'
import {IBlock} from './octoTypes'
import {Utils} from './utils'
@ -17,7 +18,7 @@ class OctoClient {
async getSubtree(rootId?: string): Promise<IBlock[]> {
const path = `/api/v1/blocks/${rootId}/subtree`
const response = await fetch(this.serverUrl + path)
const blocks = (await response.json() || []) as IBlock[]
const blocks = (await response.json() || []) as IMutableBlock[]
this.fixBlocks(blocks)
return blocks
}
@ -25,7 +26,7 @@ class OctoClient {
async exportFullArchive(): Promise<IBlock[]> {
const path = '/api/v1/blocks/export'
const response = await fetch(this.serverUrl + path)
const blocks = (await response.json() || []) as IBlock[]
const blocks = (await response.json() || []) as IMutableBlock[]
this.fixBlocks(blocks)
return blocks
}
@ -59,12 +60,12 @@ class OctoClient {
}
const response = await fetch(this.serverUrl + path)
const blocks = (await response.json() || []) as IBlock[]
const blocks = (await response.json() || []) as IMutableBlock[]
this.fixBlocks(blocks)
return blocks
}
fixBlocks(blocks: IBlock[]): void {
fixBlocks(blocks: IMutableBlock[]): void {
if (!blocks) {
return
}
@ -90,12 +91,12 @@ class OctoClient {
}
}
async updateBlock(block: IBlock): Promise<Response> {
async updateBlock(block: IMutableBlock): Promise<Response> {
block.updateAt = Date.now()
return await this.insertBlocks([block])
}
async updateBlocks(blocks: IBlock[]): Promise<Response> {
async updateBlocks(blocks: IMutableBlock[]): Promise<Response> {
const now = Date.now()
blocks.forEach((block) => {
block.updateAt = now

View File

@ -1,21 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
interface IBlock {
id: string
parentId: string
readonly id: string
readonly parentId: string
schema: number
type: string
title?: string
fields: Record<string, any>
readonly schema: number
readonly type: string
readonly title?: string
readonly fields: Readonly<Record<string, any>>
createAt: number
updateAt: number
deleteAt: number
}
interface IOrderedBlock extends IBlock {
order: number
readonly createAt: number
readonly updateAt: number
readonly deleteAt: number
}
// These are methods exposed by the top-level page to components
@ -26,4 +22,4 @@ interface IPageController {
setSearchText(text?: string): void
}
export {IBlock, IOrderedBlock, IPageController}
export {IBlock, IPageController}

View File

@ -2,18 +2,19 @@
// See LICENSE.txt for license information.
import React from 'react'
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 {MutableBlock} from './blocks/block'
import {Board, IPropertyTemplate, MutableBoard} from './blocks/board'
import {BoardView, ISortOption, MutableBoardView} from './blocks/boardView'
import {Card, MutableCard} from './blocks/card'
import {CommentBlock, MutableCommentBlock} from './blocks/commentBlock'
import {ImageBlock, MutableImageBlock} from './blocks/imageBlock'
import { IOrderedBlock } from './blocks/orderedBlock'
import {MutableTextBlock, TextBlock} from './blocks/textBlock'
import {BoardTree} from './boardTree'
import {Editable} from './components/editable'
import {Menu} from './menu'
import mutator from './mutator'
import {IBlock, IOrderedBlock} from './octoTypes'
import {IBlock} from './octoTypes'
import {Utils} from './utils'
class OctoUtils {
@ -76,24 +77,25 @@ class OctoUtils {
menu.showAtElement(clickedElement)
}
element = (<div
key={propertyTemplate.id}
className={`${className} ${propertyColorCssClassName}`}
tabIndex={0}
onClick={!readOnly ? (e) => {
showMenu(e.target as HTMLElement)
} : undefined}
onKeyDown={!readOnly ? (e) => {
if (e.keyCode === 13) {
element = (
<div
key={propertyTemplate.id}
className={`${className} ${propertyColorCssClassName}`}
tabIndex={0}
onClick={!readOnly ? (e) => {
showMenu(e.target as HTMLElement)
}
} : undefined}
onFocus={!readOnly ? () => {
Menu.shared.hide()
} : undefined}
>
{finalDisplayValue}
</div>)
} : undefined}
onKeyDown={!readOnly ? (e) => {
if (e.keyCode === 13) {
showMenu(e.target as HTMLElement)
}
} : undefined}
onFocus={!readOnly ? () => {
Menu.shared.hide()
} : undefined}
>
{finalDisplayValue}
</div>)
} else if (propertyTemplate.type === 'text' || propertyTemplate.type === 'number') {
if (!readOnly) {
element = (<Editable
@ -104,7 +106,7 @@ class OctoUtils {
onChanged={(text) => {
mutator.changePropertyValue(card, propertyTemplate.id, text)
}}
></Editable>)
/>)
} else {
element = (<div
key={propertyTemplate.id}
@ -170,21 +172,22 @@ class OctoUtils {
Menu.shared.showAtElement(e.target as HTMLElement)
}
static hydrateBlock(block: IBlock): Block {
static hydrateBlock(block: IBlock): MutableBlock {
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) }
case 'board': { return new MutableBoard(block) }
case 'view': { return new MutableBoardView(block) }
case 'card': { return new MutableCard(block) }
case 'text': { return new MutableTextBlock(block) }
case 'image': { return new MutableImageBlock(block) }
case 'comment': { return new MutableCommentBlock(block) }
default: {
Utils.assertFailure(`Can't hydrate unknown block type: ${block.type}`)
return new MutableBlock(block)
}
}
}
static hydrateBlocks(blocks: IBlock[]): Block[] {
static hydrateBlocks(blocks: IBlock[]): MutableBlock[] {
return blocks.map((block) => this.hydrateBlock(block))
}
}

View File

@ -162,7 +162,7 @@ export default class BoardPage extends React.Component<Props, State> {
Utils.log(`attachToBoard: ${boardId}`)
this.boardListener.open(boardId, (blockId: string) => {
console.log(`octoListener.onChanged: ${blockId}`)
Utils.log(`boardListener.onChanged: ${blockId}`)
this.sync(boardId)
})

View File

@ -1,13 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react'
import {Archiver} from '../archiver'
import {Board} from '../blocks/board'
import { Archiver } from '../archiver'
import { MutableBoard } from '../blocks/board'
import Button from '../components/button'
import octoClient from '../octoClient'
import {IBlock} from '../octoTypes'
import {Utils} from '../utils'
import { IBlock } from '../octoTypes'
import { Utils } from '../utils'
type Props = {}
@ -43,7 +43,7 @@ export default class HomePage extends React.Component<Props, State> {
}
addClicked = async () => {
const board = new Board()
const board = new MutableBoard()
await octoClient.insertBlock(board)
}

View File

@ -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} from './blocks/board'
import octoClient from './octoClient'
import { IBlock } from './octoTypes'
import {OctoUtils} from './octoUtils'
interface WorkspaceTree {
@ -17,7 +17,7 @@ class MutableWorkspaceTree {
this.rebuild(OctoUtils.hydrateBlocks(blocks))
}
private rebuild(blocks: Block[]) {
private rebuild(blocks: IBlock[]) {
this.boards = blocks.filter(block => block.type === "board") as Board[]
}
}