You've already forked focalboard
mirror of
https://github.com/mattermost/focalboard.git
synced 2025-07-15 23:54:29 +02:00
Card templates
This commit is contained in:
@ -1,12 +1,15 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
import {Utils} from '../utils'
|
||||||
import {IBlock} from '../blocks/block'
|
import {IBlock} from '../blocks/block'
|
||||||
|
|
||||||
import {MutableBlock} from './block'
|
import {MutableBlock} from './block'
|
||||||
|
|
||||||
interface Card extends IBlock {
|
interface Card extends IBlock {
|
||||||
readonly icon: string
|
readonly icon: string
|
||||||
|
readonly isTemplate: boolean
|
||||||
readonly properties: Readonly<Record<string, string>>
|
readonly properties: Readonly<Record<string, string>>
|
||||||
|
newCardFromTemplate(): MutableCard
|
||||||
}
|
}
|
||||||
|
|
||||||
class MutableCard extends MutableBlock {
|
class MutableCard extends MutableBlock {
|
||||||
@ -17,6 +20,13 @@ class MutableCard extends MutableBlock {
|
|||||||
this.fields.icon = value
|
this.fields.icon = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isTemplate(): boolean {
|
||||||
|
return this.fields.isTemplate as boolean
|
||||||
|
}
|
||||||
|
set isTemplate(value: boolean) {
|
||||||
|
this.fields.isTemplate = value
|
||||||
|
}
|
||||||
|
|
||||||
get properties(): Record<string, string> {
|
get properties(): Record<string, string> {
|
||||||
return this.fields.properties as Record<string, string>
|
return this.fields.properties as Record<string, string>
|
||||||
}
|
}
|
||||||
@ -30,6 +40,14 @@ class MutableCard extends MutableBlock {
|
|||||||
|
|
||||||
this.properties = {...(block.fields?.properties || {})}
|
this.properties = {...(block.fields?.properties || {})}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newCardFromTemplate(): MutableCard {
|
||||||
|
const card = new MutableCard(this)
|
||||||
|
card.id = Utils.createGuid()
|
||||||
|
card.isTemplate = false
|
||||||
|
card.title = ''
|
||||||
|
return card
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {MutableCard, Card}
|
export {MutableCard, Card}
|
||||||
|
@ -6,8 +6,10 @@ import {injectIntl, IntlShape, FormattedMessage} from 'react-intl'
|
|||||||
|
|
||||||
import {BlockIcons} from '../blockIcons'
|
import {BlockIcons} from '../blockIcons'
|
||||||
import {IPropertyOption, IPropertyTemplate} from '../blocks/board'
|
import {IPropertyOption, IPropertyTemplate} from '../blocks/board'
|
||||||
|
import {IBlock} from '../blocks/block'
|
||||||
import {Card, MutableCard} from '../blocks/card'
|
import {Card, MutableCard} from '../blocks/card'
|
||||||
import {BoardTree, BoardTreeGroup} from '../viewModel/boardTree'
|
import {BoardTree, BoardTreeGroup} from '../viewModel/boardTree'
|
||||||
|
import {MutableCardTree} from '../viewModel/cardTree'
|
||||||
import {CardFilter} from '../cardFilter'
|
import {CardFilter} from '../cardFilter'
|
||||||
import {Constants} from '../constants'
|
import {Constants} from '../constants'
|
||||||
import mutator from '../mutator'
|
import mutator from '../mutator'
|
||||||
@ -150,6 +152,10 @@ class BoardComponent extends React.Component<Props, State> {
|
|||||||
showView={showView}
|
showView={showView}
|
||||||
setSearchText={this.props.setSearchText}
|
setSearchText={this.props.setSearchText}
|
||||||
addCard={() => this.addCard()}
|
addCard={() => this.addCard()}
|
||||||
|
addCardFromTemplate={this.addCardFromTemplate}
|
||||||
|
addCardTemplate={() => this.addCardTemplate()}
|
||||||
|
editCardTemplate={this.editCardTemplate}
|
||||||
|
deleteCardTemplate={this.deleteCardTemplate}
|
||||||
withGroupBy={true}
|
withGroupBy={true}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@ -473,28 +479,80 @@ class BoardComponent extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async addCard(groupByOptionId?: string): Promise<void> {
|
private addCardFromTemplate = async (cardTemplate?: Card) => {
|
||||||
|
this.addCard(undefined, cardTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async addCard(groupByOptionId?: string, cardTemplate?: Card): Promise<void> {
|
||||||
const {boardTree} = this.props
|
const {boardTree} = this.props
|
||||||
const {activeView, board} = boardTree
|
const {activeView, board} = boardTree
|
||||||
|
|
||||||
const card = new MutableCard()
|
let card: MutableCard
|
||||||
|
let blocksToInsert: IBlock[]
|
||||||
|
if (cardTemplate) {
|
||||||
|
const templateCardTree = new MutableCardTree(cardTemplate.id)
|
||||||
|
await templateCardTree.sync()
|
||||||
|
const newCardTree = templateCardTree.duplicateFromTemplate()
|
||||||
|
card = newCardTree.card
|
||||||
|
blocksToInsert = [newCardTree.card, ...newCardTree.contents]
|
||||||
|
} else {
|
||||||
|
card = new MutableCard()
|
||||||
|
blocksToInsert = [card]
|
||||||
|
}
|
||||||
|
|
||||||
card.parentId = boardTree.board.id
|
card.parentId = boardTree.board.id
|
||||||
card.properties = CardFilter.propertiesThatMeetFilterGroup(activeView.filter, board.cardProperties)
|
const propertiesThatMeetFilters = CardFilter.propertiesThatMeetFilterGroup(activeView.filter, board.cardProperties)
|
||||||
card.icon = BlockIcons.shared.randomIcon()
|
|
||||||
if (boardTree.groupByProperty) {
|
if (boardTree.groupByProperty) {
|
||||||
if (groupByOptionId) {
|
if (groupByOptionId) {
|
||||||
card.properties[boardTree.groupByProperty.id] = groupByOptionId
|
propertiesThatMeetFilters[boardTree.groupByProperty.id] = groupByOptionId
|
||||||
} else {
|
} else {
|
||||||
delete card.properties[boardTree.groupByProperty.id]
|
delete propertiesThatMeetFilters[boardTree.groupByProperty.id]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await mutator.insertBlock(card, 'add card', async () => {
|
card.properties = {...card.properties, ...propertiesThatMeetFilters}
|
||||||
this.setState({shownCard: card})
|
card.icon = BlockIcons.shared.randomIcon()
|
||||||
|
await mutator.insertBlocks(
|
||||||
|
blocksToInsert,
|
||||||
|
'add card',
|
||||||
|
async () => {
|
||||||
|
this.setState({shownCard: card})
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
this.setState({shownCard: undefined})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async addCardTemplate(groupByOptionId?: string): Promise<void> {
|
||||||
|
const {boardTree} = this.props
|
||||||
|
const {activeView, board} = boardTree
|
||||||
|
|
||||||
|
const cardTemplate = new MutableCard()
|
||||||
|
cardTemplate.isTemplate = true
|
||||||
|
cardTemplate.parentId = boardTree.board.id
|
||||||
|
cardTemplate.properties = CardFilter.propertiesThatMeetFilterGroup(activeView.filter, board.cardProperties)
|
||||||
|
if (boardTree.groupByProperty) {
|
||||||
|
if (groupByOptionId) {
|
||||||
|
cardTemplate.properties[boardTree.groupByProperty.id] = groupByOptionId
|
||||||
|
} else {
|
||||||
|
delete cardTemplate.properties[boardTree.groupByProperty.id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await mutator.insertBlock(cardTemplate, 'add card template', async () => {
|
||||||
|
this.setState({shownCard: cardTemplate})
|
||||||
}, async () => {
|
}, async () => {
|
||||||
this.setState({shownCard: undefined})
|
this.setState({shownCard: undefined})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private editCardTemplate = (cardTemplate: Card) => {
|
||||||
|
this.setState({shownCard: cardTemplate})
|
||||||
|
}
|
||||||
|
|
||||||
|
private deleteCardTemplate = (cardTemplate: Card) => {
|
||||||
|
mutator.deleteBlock(cardTemplate, 'delete card template')
|
||||||
|
}
|
||||||
|
|
||||||
private async propertyNameChanged(option: IPropertyOption, text: string): Promise<void> {
|
private async propertyNameChanged(option: IPropertyOption, text: string): Promise<void> {
|
||||||
const {boardTree} = this.props
|
const {boardTree} = this.props
|
||||||
|
|
||||||
@ -554,7 +612,6 @@ class BoardComponent extends React.Component<Props, State> {
|
|||||||
const {draggedCards, draggedHeaderOption} = this
|
const {draggedCards, draggedHeaderOption} = this
|
||||||
const optionId = option ? option.id : undefined
|
const optionId = option ? option.id : undefined
|
||||||
|
|
||||||
Utils.assertValue(mutator)
|
|
||||||
Utils.assertValue(boardTree)
|
Utils.assertValue(boardTree)
|
||||||
|
|
||||||
if (draggedCards.length > 0) {
|
if (draggedCards.length > 0) {
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
|
import {FormattedMessage} from 'react-intl'
|
||||||
|
|
||||||
import {Card} from '../blocks/card'
|
import {Card} from '../blocks/card'
|
||||||
import {BoardTree} from '../viewModel/boardTree'
|
import {BoardTree} from '../viewModel/boardTree'
|
||||||
import mutator from '../mutator'
|
import mutator from '../mutator'
|
||||||
@ -37,6 +39,14 @@ class CardDialog extends React.Component<Props> {
|
|||||||
onClose={this.props.onClose}
|
onClose={this.props.onClose}
|
||||||
toolsMenu={menu}
|
toolsMenu={menu}
|
||||||
>
|
>
|
||||||
|
{(this.props.card.isTemplate) &&
|
||||||
|
<div className='banner'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='CardDialog.editing-template'
|
||||||
|
defaultMessage="You're editing a template"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<CardDetail
|
<CardDetail
|
||||||
boardTree={this.props.boardTree}
|
boardTree={this.props.boardTree}
|
||||||
cardId={this.props.card.id}
|
cardId={this.props.card.id}
|
||||||
|
@ -24,6 +24,11 @@
|
|||||||
|
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
> .banner {
|
||||||
|
background-color: rgba(230, 220, 192, 0.9);
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
> .toolbar {
|
> .toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -26,6 +26,8 @@ import './tableComponent.scss'
|
|||||||
import {HorizontalGrip} from './horizontalGrip'
|
import {HorizontalGrip} from './horizontalGrip'
|
||||||
|
|
||||||
import {MutableBoardView} from '../blocks/boardView'
|
import {MutableBoardView} from '../blocks/boardView'
|
||||||
|
import {IBlock} from '../blocks/block'
|
||||||
|
import {MutableCardTree} from '../viewModel/cardTree'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
boardTree?: BoardTree
|
boardTree?: BoardTree
|
||||||
@ -93,7 +95,11 @@ class TableComponent extends React.Component<Props, State> {
|
|||||||
boardTree={boardTree}
|
boardTree={boardTree}
|
||||||
showView={showView}
|
showView={showView}
|
||||||
setSearchText={this.props.setSearchText}
|
setSearchText={this.props.setSearchText}
|
||||||
addCard={this.addCard}
|
addCard={this.addCardAndShow}
|
||||||
|
addCardFromTemplate={this.addCardFromTemplate}
|
||||||
|
addCardTemplate={this.addCardTemplate}
|
||||||
|
editCardTemplate={this.editCardTemplate}
|
||||||
|
deleteCardTemplate={this.deleteCardTemplate}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Main content */}
|
{/* Main content */}
|
||||||
@ -293,14 +299,34 @@ class TableComponent extends React.Component<Props, State> {
|
|||||||
return Math.max(Constants.minColumnWidth, this.props.boardTree?.activeView?.columnWidths[templateId] || 0)
|
return Math.max(Constants.minColumnWidth, this.props.boardTree?.activeView?.columnWidths[templateId] || 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private addCard = async (show = false) => {
|
private addCardAndShow = () => {
|
||||||
|
this.addCard(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private addCardFromTemplate = async (cardTemplate?: Card) => {
|
||||||
|
this.addCard(true, cardTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
private addCard = async (show = false, cardTemplate?: Card) => {
|
||||||
const {boardTree} = this.props
|
const {boardTree} = this.props
|
||||||
|
|
||||||
const card = new MutableCard()
|
let card: MutableCard
|
||||||
|
let blocksToInsert: IBlock[]
|
||||||
|
if (cardTemplate) {
|
||||||
|
const templateCardTree = new MutableCardTree(cardTemplate.id)
|
||||||
|
await templateCardTree.sync()
|
||||||
|
const newCardTree = templateCardTree.duplicateFromTemplate()
|
||||||
|
card = newCardTree.card
|
||||||
|
blocksToInsert = [newCardTree.card, ...newCardTree.contents]
|
||||||
|
} else {
|
||||||
|
card = new MutableCard()
|
||||||
|
blocksToInsert = [card]
|
||||||
|
}
|
||||||
|
|
||||||
card.parentId = boardTree.board.id
|
card.parentId = boardTree.board.id
|
||||||
card.icon = BlockIcons.shared.randomIcon()
|
card.icon = BlockIcons.shared.randomIcon()
|
||||||
await mutator.insertBlock(
|
await mutator.insertBlocks(
|
||||||
card,
|
blocksToInsert,
|
||||||
'add card',
|
'add card',
|
||||||
async () => {
|
async () => {
|
||||||
if (show) {
|
if (show) {
|
||||||
@ -313,6 +339,30 @@ class TableComponent extends React.Component<Props, State> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addCardTemplate = async () => {
|
||||||
|
const {boardTree} = this.props
|
||||||
|
|
||||||
|
const cardTemplate = new MutableCard()
|
||||||
|
cardTemplate.isTemplate = true
|
||||||
|
cardTemplate.parentId = boardTree.board.id
|
||||||
|
cardTemplate.icon = BlockIcons.shared.randomIcon()
|
||||||
|
await mutator.insertBlock(
|
||||||
|
cardTemplate,
|
||||||
|
'add card',
|
||||||
|
async () => {
|
||||||
|
this.setState({shownCard: cardTemplate})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private editCardTemplate = (cardTemplate: Card) => {
|
||||||
|
this.setState({shownCard: cardTemplate})
|
||||||
|
}
|
||||||
|
|
||||||
|
private deleteCardTemplate = (cardTemplate: Card) => {
|
||||||
|
mutator.deleteBlock(cardTemplate, 'delete card template')
|
||||||
|
}
|
||||||
|
|
||||||
private async onDropToColumn(template: IPropertyTemplate) {
|
private async onDropToColumn(template: IPropertyTemplate) {
|
||||||
const {draggedHeaderTemplate} = this
|
const {draggedHeaderTemplate} = this
|
||||||
if (!draggedHeaderTemplate) {
|
if (!draggedHeaderTemplate) {
|
||||||
|
@ -6,7 +6,7 @@ import {injectIntl, IntlShape, FormattedMessage} from 'react-intl'
|
|||||||
import {Archiver} from '../archiver'
|
import {Archiver} from '../archiver'
|
||||||
import {ISortOption, MutableBoardView} from '../blocks/boardView'
|
import {ISortOption, MutableBoardView} from '../blocks/boardView'
|
||||||
import {BlockIcons} from '../blockIcons'
|
import {BlockIcons} from '../blockIcons'
|
||||||
import {MutableCard} from '../blocks/card'
|
import {Card, MutableCard} from '../blocks/card'
|
||||||
import {IPropertyTemplate} from '../blocks/board'
|
import {IPropertyTemplate} from '../blocks/board'
|
||||||
import {BoardTree} from '../viewModel/boardTree'
|
import {BoardTree} from '../viewModel/boardTree'
|
||||||
import ViewMenu from '../components/viewMenu'
|
import ViewMenu from '../components/viewMenu'
|
||||||
@ -29,15 +29,19 @@ import {Editable} from './editable'
|
|||||||
import FilterComponent from './filterComponent'
|
import FilterComponent from './filterComponent'
|
||||||
|
|
||||||
import './viewHeader.scss'
|
import './viewHeader.scss'
|
||||||
import {sendFlashMessage} from './flashMessages'
|
|
||||||
|
|
||||||
import {Constants} from '../constants'
|
import {Constants} from '../constants'
|
||||||
|
import DeleteIcon from '../widgets/icons/delete'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
boardTree?: BoardTree
|
boardTree?: BoardTree
|
||||||
showView: (id: string) => void
|
showView: (id: string) => void
|
||||||
setSearchText: (text: string) => void
|
setSearchText: (text: string) => void
|
||||||
addCard: (show: boolean) => void
|
addCard: () => void
|
||||||
|
addCardFromTemplate: (cardTemplate?: Card) => void
|
||||||
|
addCardTemplate: () => void
|
||||||
|
editCardTemplate: (cardTemplate: Card) => void
|
||||||
|
deleteCardTemplate: (cardTemplate: Card) => void
|
||||||
withGroupBy?: boolean
|
withGroupBy?: boolean
|
||||||
intl: IntlShape
|
intl: IntlShape
|
||||||
}
|
}
|
||||||
@ -371,9 +375,10 @@ class ViewHeader extends React.Component<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</Menu>
|
</Menu>
|
||||||
</MenuWrapper>
|
</MenuWrapper>
|
||||||
|
|
||||||
<ButtonWithMenu
|
<ButtonWithMenu
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.props.addCard(true)
|
this.props.addCard()
|
||||||
}}
|
}}
|
||||||
text={(
|
text={(
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@ -391,10 +396,56 @@ class ViewHeader extends React.Component<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</b>
|
</b>
|
||||||
</Menu.Label>
|
</Menu.Label>
|
||||||
|
|
||||||
|
<Menu.Separator/>
|
||||||
|
|
||||||
|
{boardTree.cardTemplates.map((cardTemplate) => {
|
||||||
|
return (
|
||||||
|
<Menu.Text
|
||||||
|
key={cardTemplate.id}
|
||||||
|
id={cardTemplate.id}
|
||||||
|
name={cardTemplate.title || intl.formatMessage({id: 'ViewHeader.untitled', defaultMessage: 'Untitled'})}
|
||||||
|
onClick={() => {
|
||||||
|
this.props.addCardFromTemplate(cardTemplate)
|
||||||
|
}}
|
||||||
|
rightIcon={
|
||||||
|
<MenuWrapper stopPropagationOnToggle={true}>
|
||||||
|
<IconButton icon={<OptionsIcon/>}/>
|
||||||
|
<Menu position='left'>
|
||||||
|
<Menu.Text
|
||||||
|
id='edit'
|
||||||
|
name={intl.formatMessage({id: 'ViewHeader.edit-template', defaultMessage: 'Edit'})}
|
||||||
|
onClick={() => {
|
||||||
|
this.props.editCardTemplate(cardTemplate)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Menu.Text
|
||||||
|
icon={<DeleteIcon/>}
|
||||||
|
id='delete'
|
||||||
|
name={intl.formatMessage({id: 'ViewHeader.delete-template', defaultMessage: 'Delete'})}
|
||||||
|
onClick={() => {
|
||||||
|
this.props.deleteCardTemplate(cardTemplate)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
</MenuWrapper>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
<Menu.Text
|
<Menu.Text
|
||||||
id='example-template'
|
id='empty-template'
|
||||||
name={intl.formatMessage({id: 'ViewHeader.sample-templte', defaultMessage: 'Sample template'})}
|
name={intl.formatMessage({id: 'ViewHeader.empty-card', defaultMessage: 'Empty card'})}
|
||||||
onClick={() => sendFlashMessage({content: 'Not implemented yet', severity: 'low'})}
|
onClick={() => {
|
||||||
|
this.props.addCard()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Menu.Text
|
||||||
|
id='add-template'
|
||||||
|
name={intl.formatMessage({id: 'ViewHeader.add-template', defaultMessage: '+ New template'})}
|
||||||
|
onClick={() => this.props.addCardTemplate()}
|
||||||
/>
|
/>
|
||||||
</Menu>
|
</Menu>
|
||||||
</ButtonWithMenu>
|
</ButtonWithMenu>
|
||||||
|
@ -19,6 +19,7 @@ interface BoardTree {
|
|||||||
readonly board: Board
|
readonly board: Board
|
||||||
readonly views: readonly BoardView[]
|
readonly views: readonly BoardView[]
|
||||||
readonly cards: readonly Card[]
|
readonly cards: readonly Card[]
|
||||||
|
readonly cardTemplates: readonly Card[]
|
||||||
readonly allCards: readonly Card[]
|
readonly allCards: readonly Card[]
|
||||||
readonly visibleGroups: readonly Group[]
|
readonly visibleGroups: readonly Group[]
|
||||||
readonly hiddenGroups: readonly Group[]
|
readonly hiddenGroups: readonly Group[]
|
||||||
@ -37,6 +38,7 @@ class MutableBoardTree implements BoardTree {
|
|||||||
board!: MutableBoard
|
board!: MutableBoard
|
||||||
views: MutableBoardView[] = []
|
views: MutableBoardView[] = []
|
||||||
cards: MutableCard[] = []
|
cards: MutableCard[] = []
|
||||||
|
cardTemplates: MutableCard[] = []
|
||||||
visibleGroups: Group[] = []
|
visibleGroups: Group[] = []
|
||||||
hiddenGroups: Group[] = []
|
hiddenGroups: Group[] = []
|
||||||
|
|
||||||
@ -47,7 +49,7 @@ class MutableBoardTree implements BoardTree {
|
|||||||
private searchText?: string
|
private searchText?: string
|
||||||
allCards: MutableCard[] = []
|
allCards: MutableCard[] = []
|
||||||
get allBlocks(): IBlock[] {
|
get allBlocks(): IBlock[] {
|
||||||
return [this.board, ...this.views, ...this.allCards]
|
return [this.board, ...this.views, ...this.allCards, ...this.cardTemplates]
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private boardId: string) {
|
constructor(private boardId: string) {
|
||||||
@ -71,7 +73,8 @@ class MutableBoardTree implements BoardTree {
|
|||||||
private rebuild(blocks: IMutableBlock[]) {
|
private rebuild(blocks: IMutableBlock[]) {
|
||||||
this.board = blocks.find((block) => block.type === 'board') as MutableBoard
|
this.board = blocks.find((block) => block.type === 'board') as MutableBoard
|
||||||
this.views = blocks.filter((block) => block.type === 'view') as MutableBoardView[]
|
this.views = blocks.filter((block) => block.type === 'view') as MutableBoardView[]
|
||||||
this.allCards = blocks.filter((block) => block.type === 'card') as MutableCard[]
|
this.allCards = blocks.filter((block) => block.type === 'card' && !(block as Card).isTemplate) as MutableCard[]
|
||||||
|
this.cardTemplates = blocks.filter((block) => block.type === 'card' && (block as Card).isTemplate) as MutableCard[]
|
||||||
this.cards = []
|
this.cards = []
|
||||||
|
|
||||||
this.ensureMinimumSchema()
|
this.ensureMinimumSchema()
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import {Card} from '../blocks/card'
|
import {Card, MutableCard} from '../blocks/card'
|
||||||
import {IOrderedBlock} from '../blocks/orderedBlock'
|
import {IOrderedBlock} from '../blocks/orderedBlock'
|
||||||
import octoClient from '../octoClient'
|
import octoClient from '../octoClient'
|
||||||
import {IBlock} from '../blocks/block'
|
import {IBlock, MutableBlock} from '../blocks/block'
|
||||||
import {OctoUtils} from '../octoUtils'
|
import {OctoUtils} from '../octoUtils'
|
||||||
|
import {Utils} from '../utils'
|
||||||
|
|
||||||
interface CardTree {
|
interface CardTree {
|
||||||
readonly card: Card
|
readonly card: Card
|
||||||
@ -15,7 +16,7 @@ interface CardTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MutableCardTree implements CardTree {
|
class MutableCardTree implements CardTree {
|
||||||
card: Card
|
card: MutableCard
|
||||||
comments: IBlock[] = []
|
comments: IBlock[] = []
|
||||||
contents: IOrderedBlock[] = []
|
contents: IOrderedBlock[] = []
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ class MutableCardTree implements CardTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private rebuild(blocks: IBlock[]) {
|
private rebuild(blocks: IBlock[]) {
|
||||||
this.card = blocks.find((o) => o.id === this.cardId) as Card
|
this.card = blocks.find((o) => o.id === this.cardId) as MutableCard
|
||||||
|
|
||||||
this.comments = blocks.
|
this.comments = blocks.
|
||||||
filter((block) => block.type === 'comment').
|
filter((block) => block.type === 'comment').
|
||||||
@ -55,6 +56,19 @@ class MutableCardTree implements CardTree {
|
|||||||
cardTree.incrementalUpdate(this.rawBlocks)
|
cardTree.incrementalUpdate(this.rawBlocks)
|
||||||
return cardTree
|
return cardTree
|
||||||
}
|
}
|
||||||
|
|
||||||
|
duplicateFromTemplate(): MutableCardTree {
|
||||||
|
const card = this.card.newCardFromTemplate()
|
||||||
|
const contents: IOrderedBlock[] = this.contents.map((content) => {
|
||||||
|
const copy = MutableBlock.duplicate(content)
|
||||||
|
copy.parentId = card.id
|
||||||
|
return copy as IOrderedBlock
|
||||||
|
})
|
||||||
|
|
||||||
|
const cardTree = new MutableCardTree(card.id)
|
||||||
|
cardTree.incrementalUpdate([card, ...contents])
|
||||||
|
return cardTree
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {MutableCardTree, CardTree}
|
export {MutableCardTree, CardTree}
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -36,6 +38,10 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
|
|
||||||
|
* {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(90, 90, 90, 0.1);
|
background: rgba(90, 90, 90, 0.1);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user