diff --git a/webapp/src/blocks/boardView.ts b/webapp/src/blocks/boardView.ts index 4db0a5440..3cf76b46d 100644 --- a/webapp/src/blocks/boardView.ts +++ b/webapp/src/blocks/boardView.ts @@ -21,6 +21,7 @@ type BoardViewFields = { cardOrder: string[] columnWidths: Record columnCalculations: Record + defaultTemplateId: string } type BoardView = Block & { @@ -44,6 +45,7 @@ function createBoardView(block?: Block): BoardView { cardOrder: block?.fields.cardOrder?.slice() || [], columnWidths: {...(block?.fields.columnWidths || {})}, columnCalculations: {...(block?.fields.columnCalculations) || {}}, + defaultTemplateId: block?.fields.defaultTemplateId || '', }, } } diff --git a/webapp/src/components/viewHeader/emptyCardButton.tsx b/webapp/src/components/viewHeader/emptyCardButton.tsx new file mode 100644 index 000000000..b58221881 --- /dev/null +++ b/webapp/src/components/viewHeader/emptyCardButton.tsx @@ -0,0 +1,57 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react' + +import {useIntl} from 'react-intl' + +import CardIcon from '../../widgets/icons/card' +import Menu from '../../widgets/menu' + +import MenuWrapper from '../../widgets/menuWrapper' +import OptionsIcon from '../../widgets/icons/options' +import IconButton from '../../widgets/buttons/iconButton' +import CheckIcon from '../../widgets/icons/check' +import mutator from '../../mutator' +import {useAppSelector} from '../../store/hooks' +import {getCurrentView} from '../../store/views' + +type Props = { + addCard: () => void +} + +const EmptyCardButton = React.memo((props: Props) => { + const currentView = useAppSelector(getCurrentView) + const intl = useIntl() + + return ( + } + id='empty-template' + name={intl.formatMessage({id: 'ViewHeader.empty-card', defaultMessage: 'Empty card'})} + className={currentView.fields.defaultTemplateId ? '' : 'bold-menu-text'} + onClick={() => { + props.addCard() + }} + rightIcon={ + + }/> + + } + id='default' + name={intl.formatMessage({ + id: 'ViewHeader.set-default-template', + defaultMessage: 'Set as default', + })} + onClick={async () => { + await mutator.clearDefaultTemplate(currentView.id, currentView.fields.defaultTemplateId) + }} + /> + + + } + />) +}) + +export default EmptyCardButton diff --git a/webapp/src/components/viewHeader/newCardButton.tsx b/webapp/src/components/viewHeader/newCardButton.tsx index 2365c4bf5..8f4ddf802 100644 --- a/webapp/src/components/viewHeader/newCardButton.tsx +++ b/webapp/src/components/viewHeader/newCardButton.tsx @@ -6,13 +6,14 @@ import {FormattedMessage, useIntl} from 'react-intl' import {Card} from '../../blocks/card' import ButtonWithMenu from '../../widgets/buttons/buttonWithMenu' -import CardIcon from '../../widgets/icons/card' import AddIcon from '../../widgets/icons/add' import Menu from '../../widgets/menu' import {useAppSelector} from '../../store/hooks' import {getCurrentBoardTemplates} from '../../store/cards' +import {getCurrentView} from '../../store/views' import NewCardButtonTemplateItem from './newCardButtonTemplateItem' +import EmptyCardButton from './emptyCardButton' type Props = { addCard: () => void @@ -23,12 +24,17 @@ type Props = { const NewCardButton = React.memo((props: Props): JSX.Element => { const cardTemplates: Card[] = useAppSelector(getCurrentBoardTemplates) + const currentView = useAppSelector(getCurrentView) const intl = useIntl() return ( { - props.addCard() + if (currentView.fields.defaultTemplateId) { + props.addCardFromTemplate(currentView.fields.defaultTemplateId) + } else { + props.addCard() + } }} text={( { /> ))} - } - id='empty-template' - name={intl.formatMessage({id: 'ViewHeader.empty-card', defaultMessage: 'Empty card'})} - onClick={() => { - props.addCard() - }} + { + const currentView = useAppSelector(getCurrentView) const {cardTemplate} = props const intl = useIntl() const displayName = cardTemplate.title || intl.formatMessage({id: 'ViewHeader.untitled', defaultMessage: 'Untitled'}) + const isDefaultTemplate = currentView.fields.defaultTemplateId === cardTemplate.id + return ( {cardTemplate.fields.icon}} + className={isDefaultTemplate ? 'bold-menu-text' : ''} onClick={() => { props.addCardFromTemplate(cardTemplate.id) }} @@ -36,6 +43,14 @@ const NewCardButtonTemplateItem = React.memo((props: Props) => { }/> + } + id='default' + name={intl.formatMessage({id: 'ViewHeader.set-default-template', defaultMessage: 'Set as default'})} + onClick={async () => { + await mutator.setDefaultTemplate(currentView.id, currentView.fields.defaultTemplateId, cardTemplate.id) + }} + /> } id='edit' @@ -49,7 +64,12 @@ const NewCardButtonTemplateItem = React.memo((props: Props) => { id='delete' name={intl.formatMessage({id: 'ViewHeader.delete-template', defaultMessage: 'Delete'})} onClick={async () => { - await mutator.deleteBlock(cardTemplate, 'delete card template') + await mutator.performAsUndoGroup(async () => { + if (currentView.fields.defaultTemplateId === cardTemplate.id) { + await mutator.clearDefaultTemplate(currentView.id, currentView.fields.defaultTemplateId) + } + await mutator.deleteBlock(cardTemplate, 'delete card template') + }) }} /> diff --git a/webapp/src/mutator.ts b/webapp/src/mutator.ts index 67be4add4..8aa5b70d8 100644 --- a/webapp/src/mutator.ts +++ b/webapp/src/mutator.ts @@ -139,6 +139,32 @@ class Mutator { ) } + async setDefaultTemplate(blockId: string, oldTemplateId: string, templateId: string, description = 'set default template') { + await undoManager.perform( + async () => { + await octoClient.patchBlock(blockId, {updatedFields: {defaultTemplateId: templateId}}) + }, + async () => { + await octoClient.patchBlock(blockId, {updatedFields: {defaultTemplateId: oldTemplateId}}) + }, + description, + this.undoGroupId, + ) + } + + async clearDefaultTemplate(blockId: string, oldTemplateId: string, description = 'set default template') { + await undoManager.perform( + async () => { + await octoClient.patchBlock(blockId, {updatedFields: {defaultTemplateId: ''}}) + }, + async () => { + await octoClient.patchBlock(blockId, {updatedFields: {defaultTemplateId: oldTemplateId}}) + }, + description, + this.undoGroupId, + ) + } + async changeIcon(blockId: string, oldIcon: string|undefined, icon: string, description = 'change icon') { await undoManager.perform( async () => { diff --git a/webapp/src/widgets/menu/menu.scss b/webapp/src/widgets/menu/menu.scss index 0ecab4470..8498efcd0 100644 --- a/webapp/src/widgets/menu/menu.scss +++ b/webapp/src/widgets/menu/menu.scss @@ -79,6 +79,10 @@ margin-right: 0; } } + + > .menu-option.bold-menu-text { + font-weight: bold; + } } .menu-spacer {