mirror of
https://github.com/mattermost/focalboard.git
synced 2024-12-24 13:43:12 +02:00
Global template support
This commit is contained in:
parent
d58adf0582
commit
3531c8307d
@ -140,7 +140,7 @@ func (a *API) getContainerAllowingReadTokenForBlock(r *http.Request, blockID str
|
||||
if a.WorkspaceAuthenticator == nil {
|
||||
// Native auth: always use root workspace
|
||||
container := store.Container{
|
||||
WorkspaceID: "",
|
||||
WorkspaceID: "0",
|
||||
}
|
||||
|
||||
// Has session
|
||||
@ -160,11 +160,6 @@ func (a *API) getContainerAllowingReadTokenForBlock(r *http.Request, blockID str
|
||||
vars := mux.Vars(r)
|
||||
workspaceID := vars["workspaceID"]
|
||||
|
||||
if workspaceID == "" || workspaceID == "0" {
|
||||
// When authenticating, a workspace is required
|
||||
return nil, errors.New("No workspace specified")
|
||||
}
|
||||
|
||||
container := store.Container{
|
||||
WorkspaceID: workspaceID,
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ func TestGetParentID(t *testing.T) {
|
||||
app := New(&cfg, store, auth, wsserver, &mocks.FileBackend{}, webhook)
|
||||
|
||||
container := st.Container{
|
||||
WorkspaceID: "",
|
||||
WorkspaceID: "0",
|
||||
}
|
||||
t.Run("success query", func(t *testing.T) {
|
||||
store.EXPECT().GetParentID(gomock.Eq(container), gomock.Eq("test-id")).Return("test-parent-id", nil)
|
||||
|
@ -21,7 +21,7 @@ func (s *SQLStore) latestsBlocksSubquery(c store.Container) sq.SelectBuilder {
|
||||
FromSelect(internalQuery, "a").
|
||||
Where(sq.Eq{"rn": 1}).
|
||||
Where(sq.Eq{"delete_at": 0}).
|
||||
Where(sq.Eq{"coalesce(workspace_id, '')": c.WorkspaceID})
|
||||
Where(sq.Eq{"coalesce(workspace_id, '0')": c.WorkspaceID})
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetBlocksWithParentAndType(c store.Container, parentID string, blockType string) ([]model.Block, error) {
|
||||
|
@ -34,7 +34,7 @@ func (s *SQLStore) importInitialTemplates() error {
|
||||
}
|
||||
|
||||
globalContainer := store.Container{
|
||||
WorkspaceID: "",
|
||||
WorkspaceID: "0",
|
||||
}
|
||||
|
||||
log.Printf("Inserting %d blocks", len(archive.Blocks))
|
||||
|
@ -376,12 +376,12 @@ func _000008_teamsDownSql() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "000008_teams.down.sql", size: 140, mode: os.FileMode(420), modTime: time.Unix(1616709037, 0)}
|
||||
info := bindataFileInfo{name: "000008_teams.down.sql", size: 140, mode: os.FileMode(420), modTime: time.Unix(1616780070, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000008_teamsUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\xca\xc9\x4f\xce\x2e\xe6\x72\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xcf\x2f\xca\x2e\x2e\x48\x4c\x4e\x8d\xcf\x4c\x51\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x36\xd3\xb4\xe6\xe2\x42\xd6\x58\x9c\x91\x58\x94\x99\x97\x4e\x8e\xce\xd4\xe2\xe2\xcc\xfc\x3c\x14\x4b\x13\x4b\x4b\x32\xe2\x8b\x53\x8b\xca\x32\x93\x53\xe1\x5a\x8d\x0c\x34\xad\xb9\x00\x01\x00\x00\xff\xff\xba\x55\x30\xd8\xad\x00\x00\x00")
|
||||
var __000008_teamsUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\xca\xc9\x4f\xce\x2e\xe6\x72\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xcf\x2f\xca\x2e\x2e\x48\x4c\x4e\x8d\xcf\x4c\x51\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x36\xd3\xb4\xe6\xe2\x42\xd6\x58\x9c\x91\x58\x94\x99\x97\x4e\x8e\xce\xd4\xe2\xe2\xcc\xfc\x3c\x14\x4b\x13\x4b\x4b\x32\xe2\x8b\x53\x8b\xca\x32\x93\x53\xe1\x5a\x8d\x0c\x40\x5a\x43\x03\x5c\x1c\x43\x60\x0e\x55\x08\x76\x0d\x41\xb5\xc7\x56\x41\xdd\x40\x5d\x21\xdc\xc3\x35\xc8\x15\x43\x42\x5d\xc1\x3f\x08\x55\xd0\x33\x58\xc1\x2f\xd4\xc7\xc7\x9a\x0b\x10\x00\x00\xff\xff\x0e\xd0\xa2\xd3\x04\x01\x00\x00")
|
||||
|
||||
func _000008_teamsUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@ -396,7 +396,7 @@ func _000008_teamsUpSql() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "000008_teams.up.sql", size: 173, mode: os.FileMode(420), modTime: time.Unix(1616709035, 0)}
|
||||
info := bindataFileInfo{name: "000008_teams.up.sql", size: 260, mode: os.FileMode(420), modTime: time.Unix(1617137666, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -6,3 +6,5 @@ ADD COLUMN workspace_id VARCHAR(36);
|
||||
|
||||
ALTER TABLE sessions
|
||||
ADD COLUMN auth_service VARCHAR(20);
|
||||
|
||||
UPDATE blocks SET workspace_id = '0' WHERE workspace_id = '' OR workspace_id IS NULL;
|
||||
|
@ -376,12 +376,12 @@ func _000008_teamsDownSql() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "000008_teams.down.sql", size: 140, mode: os.FileMode(420), modTime: time.Unix(1616709044, 0)}
|
||||
info := bindataFileInfo{name: "000008_teams.down.sql", size: 140, mode: os.FileMode(420), modTime: time.Unix(1616780070, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000008_teamsUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\xca\xc9\x4f\xce\x2e\xe6\x72\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xcf\x2f\xca\x2e\x2e\x48\x4c\x4e\x8d\xcf\x4c\x51\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x36\xd3\xb4\xe6\xe2\x42\xd6\x58\x9c\x91\x58\x94\x99\x97\x4e\x8e\xce\xd4\xe2\xe2\xcc\xfc\x3c\x14\x4b\x13\x4b\x4b\x32\xe2\x8b\x53\x8b\xca\x32\x93\x53\xe1\x5a\x8d\x0c\x34\xad\xb9\x00\x01\x00\x00\xff\xff\xba\x55\x30\xd8\xad\x00\x00\x00")
|
||||
var __000008_teamsUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\xca\xc9\x4f\xce\x2e\xe6\x72\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xcf\x2f\xca\x2e\x2e\x48\x4c\x4e\x8d\xcf\x4c\x51\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x36\xd3\xb4\xe6\xe2\x42\xd6\x58\x9c\x91\x58\x94\x99\x97\x4e\x8e\xce\xd4\xe2\xe2\xcc\xfc\x3c\x14\x4b\x13\x4b\x4b\x32\xe2\x8b\x53\x8b\xca\x32\x93\x53\xe1\x5a\x8d\x0c\x40\x5a\x43\x03\x5c\x1c\x43\x60\x0e\x55\x08\x76\x0d\x41\xb5\xc7\x56\x41\xdd\x40\x5d\x21\xdc\xc3\x35\xc8\x15\x43\x42\x5d\xc1\x3f\x08\x55\xd0\x33\x58\xc1\x2f\xd4\xc7\xc7\x9a\x0b\x10\x00\x00\xff\xff\x0e\xd0\xa2\xd3\x04\x01\x00\x00")
|
||||
|
||||
func _000008_teamsUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@ -396,7 +396,7 @@ func _000008_teamsUpSql() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "000008_teams.up.sql", size: 173, mode: os.FileMode(420), modTime: time.Unix(1616709052, 0)}
|
||||
info := bindataFileInfo{name: "000008_teams.up.sql", size: 260, mode: os.FileMode(420), modTime: time.Unix(1617137662, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -6,3 +6,5 @@ ADD COLUMN workspace_id VARCHAR(36);
|
||||
|
||||
ALTER TABLE sessions
|
||||
ADD COLUMN auth_service VARCHAR(20);
|
||||
|
||||
UPDATE blocks SET workspace_id = '0' WHERE workspace_id = '' OR workspace_id IS NULL;
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
|
||||
func StoreTestBlocksStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {
|
||||
container := store.Container{
|
||||
WorkspaceID: "",
|
||||
WorkspaceID: "0",
|
||||
}
|
||||
|
||||
t.Run("InsertBlock", func(t *testing.T) {
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
|
||||
func StoreTestSharingStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {
|
||||
container := store.Container{
|
||||
WorkspaceID: "",
|
||||
WorkspaceID: "0",
|
||||
}
|
||||
|
||||
t.Run("UpsertSharingAndGetSharing", func(t *testing.T) {
|
||||
|
@ -179,11 +179,6 @@ func (ws *Server) authenticateListener(wsSession *websocketSession, workspaceID,
|
||||
|
||||
// Authenticated
|
||||
|
||||
// Special case: Default workspace is blank
|
||||
if workspaceID == "0" {
|
||||
workspaceID = ""
|
||||
}
|
||||
|
||||
wsSession.workspaceID = workspaceID
|
||||
wsSession.isAuthenticated = true
|
||||
log.Printf("authenticateListener: Authenticated, workspaceID: %s", workspaceID)
|
||||
@ -201,11 +196,6 @@ func (ws *Server) getAuthenticatedWorkspaceID(wsSession *websocketSession, comma
|
||||
return "", errors.New("No workspace")
|
||||
}
|
||||
|
||||
// Special case: Default workspace is blank
|
||||
if workspaceID == "0" {
|
||||
workspaceID = ""
|
||||
}
|
||||
|
||||
container := store.Container{
|
||||
WorkspaceID: workspaceID,
|
||||
}
|
||||
|
@ -3,9 +3,11 @@
|
||||
import React from 'react'
|
||||
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl'
|
||||
|
||||
import {MutableBoard} from '../../blocks/board'
|
||||
import {Board, MutableBoard} from '../../blocks/board'
|
||||
import {MutableBoardView} from '../../blocks/boardView'
|
||||
import mutator from '../../mutator'
|
||||
import octoClient from '../../octoClient'
|
||||
import {GlobalTemplateTree, MutableGlobalTemplateTree} from '../../viewModel/globalTemplateTree'
|
||||
import {WorkspaceTree} from '../../viewModel/workspaceTree'
|
||||
import IconButton from '../../widgets/buttons/iconButton'
|
||||
import BoardIcon from '../../widgets/icons/board'
|
||||
@ -24,13 +26,32 @@ type Props = {
|
||||
intl: IntlShape
|
||||
}
|
||||
|
||||
class SidebarAddBoardMenu extends React.Component<Props> {
|
||||
type State = {
|
||||
globalTemplateTree?: GlobalTemplateTree,
|
||||
}
|
||||
|
||||
class SidebarAddBoardMenu extends React.Component<Props, State> {
|
||||
state: State = {}
|
||||
|
||||
shouldComponentUpdate(): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.syncGlobalTemplates()
|
||||
}
|
||||
|
||||
private async syncGlobalTemplates() {
|
||||
if (octoClient.workspaceId !== '0' && !this.state.globalTemplateTree) {
|
||||
const globalTemplateTree = await MutableGlobalTemplateTree.sync()
|
||||
this.setState({globalTemplateTree})
|
||||
}
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
const {workspaceTree, intl} = this.props
|
||||
const {globalTemplateTree} = this.state
|
||||
|
||||
if (!workspaceTree) {
|
||||
return <div/>
|
||||
}
|
||||
@ -58,44 +79,9 @@ class SidebarAddBoardMenu extends React.Component<Props> {
|
||||
<Menu.Separator/>
|
||||
</>}
|
||||
|
||||
{workspaceTree.boardTemplates.map((boardTemplate) => {
|
||||
const displayName = boardTemplate.title || intl.formatMessage({id: 'Sidebar.untitled', defaultMessage: 'Untitled'})
|
||||
{workspaceTree.boardTemplates.map((boardTemplate) => this.renderBoardTemplate(boardTemplate))}
|
||||
|
||||
return (
|
||||
<Menu.Text
|
||||
key={boardTemplate.id}
|
||||
id={boardTemplate.id}
|
||||
name={displayName}
|
||||
icon={<div className='Icon'>{boardTemplate.icon}</div>}
|
||||
onClick={() => {
|
||||
this.addBoardFromTemplate(boardTemplate.id)
|
||||
}}
|
||||
rightIcon={
|
||||
<MenuWrapper stopPropagationOnToggle={true}>
|
||||
<IconButton icon={<OptionsIcon/>}/>
|
||||
<Menu position='left'>
|
||||
<Menu.Text
|
||||
icon={<EditIcon/>}
|
||||
id='edit'
|
||||
name={intl.formatMessage({id: 'Sidebar.edit-template', defaultMessage: 'Edit'})}
|
||||
onClick={() => {
|
||||
this.props.showBoard(boardTemplate.id)
|
||||
}}
|
||||
/>
|
||||
<Menu.Text
|
||||
icon={<DeleteIcon/>}
|
||||
id='delete'
|
||||
name={intl.formatMessage({id: 'Sidebar.delete-template', defaultMessage: 'Delete'})}
|
||||
onClick={async () => {
|
||||
await mutator.deleteBlock(boardTemplate, 'delete board template')
|
||||
}}
|
||||
/>
|
||||
</Menu>
|
||||
</MenuWrapper>
|
||||
}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{globalTemplateTree && globalTemplateTree.boardTemplates.map((boardTemplate) => this.renderBoardTemplate(boardTemplate, true))}
|
||||
|
||||
<Menu.Text
|
||||
id='empty-template'
|
||||
@ -115,6 +101,51 @@ class SidebarAddBoardMenu extends React.Component<Props> {
|
||||
)
|
||||
}
|
||||
|
||||
private renderBoardTemplate(boardTemplate: Board, isGlobal = false): JSX.Element {
|
||||
const {intl} = this.props
|
||||
|
||||
const displayName = boardTemplate.title || intl.formatMessage({id: 'Sidebar.untitled', defaultMessage: 'Untitled'})
|
||||
|
||||
return (
|
||||
<Menu.Text
|
||||
key={boardTemplate.id}
|
||||
id={boardTemplate.id}
|
||||
name={displayName}
|
||||
icon={<div className='Icon'>{boardTemplate.icon}</div>}
|
||||
onClick={() => {
|
||||
if (isGlobal) {
|
||||
this.addBoardFromGlobalTemplate(boardTemplate.id)
|
||||
} else {
|
||||
this.addBoardFromTemplate(boardTemplate.id)
|
||||
}
|
||||
}}
|
||||
rightIcon={!isGlobal &&
|
||||
<MenuWrapper stopPropagationOnToggle={true}>
|
||||
<IconButton icon={<OptionsIcon/>}/>
|
||||
<Menu position='left'>
|
||||
<Menu.Text
|
||||
icon={<EditIcon/>}
|
||||
id='edit'
|
||||
name={intl.formatMessage({id: 'Sidebar.edit-template', defaultMessage: 'Edit'})}
|
||||
onClick={() => {
|
||||
this.props.showBoard(boardTemplate.id)
|
||||
}}
|
||||
/>
|
||||
<Menu.Text
|
||||
icon={<DeleteIcon/>}
|
||||
id='delete'
|
||||
name={intl.formatMessage({id: 'Sidebar.delete-template', defaultMessage: 'Delete'})}
|
||||
onClick={async () => {
|
||||
await mutator.deleteBlock(boardTemplate, 'delete board template')
|
||||
}}
|
||||
/>
|
||||
</Menu>
|
||||
</MenuWrapper>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private addBoardClicked = async () => {
|
||||
const {showBoard, intl} = this.props
|
||||
|
||||
@ -143,6 +174,24 @@ class SidebarAddBoardMenu extends React.Component<Props> {
|
||||
)
|
||||
}
|
||||
|
||||
private async addBoardFromGlobalTemplate(boardTemplateId: string) {
|
||||
const oldBoardId = this.props.activeBoardId
|
||||
|
||||
await mutator.duplicateFromRootBoard(
|
||||
boardTemplateId,
|
||||
this.props.intl.formatMessage({id: 'Mutator.new-board-from-template', defaultMessage: 'new board from template'}),
|
||||
false,
|
||||
async (newBoardId) => {
|
||||
this.props.showBoard(newBoardId)
|
||||
},
|
||||
async () => {
|
||||
if (oldBoardId) {
|
||||
this.props.showBoard(oldBoardId)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private async addBoardFromTemplate(boardTemplateId: string) {
|
||||
const oldBoardId = this.props.activeBoardId
|
||||
|
||||
|
@ -6,7 +6,7 @@ import {Board, IPropertyOption, IPropertyTemplate, MutableBoard, PropertyType} f
|
||||
import {BoardView, ISortOption, MutableBoardView} from './blocks/boardView'
|
||||
import {Card, MutableCard} from './blocks/card'
|
||||
import {FilterGroup} from './blocks/filterGroup'
|
||||
import octoClient from './octoClient'
|
||||
import octoClient, {OctoClient} from './octoClient'
|
||||
import {OctoUtils} from './octoUtils'
|
||||
import undoManager from './undomanager'
|
||||
import {Utils} from './utils'
|
||||
@ -579,6 +579,39 @@ class Mutator {
|
||||
return [newBlocks, newBoard.id]
|
||||
}
|
||||
|
||||
async duplicateFromRootBoard(
|
||||
boardId: string,
|
||||
description = 'duplicate board',
|
||||
asTemplate = false,
|
||||
afterRedo?: (newBoardId: string) => Promise<void>,
|
||||
beforeUndo?: () => Promise<void>,
|
||||
): Promise<[IBlock[], string]> {
|
||||
const rootClient = new OctoClient(octoClient.serverUrl, '0')
|
||||
const blocks = await rootClient.getSubtree(boardId, 3)
|
||||
const [newBlocks1, newBoard] = OctoUtils.duplicateBlockTree(blocks, boardId) as [IBlock[], MutableBoard, Record<string, string>]
|
||||
const newBlocks = newBlocks1.filter((o) => o.type !== 'comment')
|
||||
Utils.log(`duplicateBoard: duplicating ${newBlocks.length} blocks`)
|
||||
|
||||
if (asTemplate === newBoard.isTemplate) {
|
||||
newBoard.title = `${newBoard.title} copy`
|
||||
} else if (asTemplate) {
|
||||
// Template from board
|
||||
newBoard.title = 'New board template'
|
||||
} else {
|
||||
// Board from template
|
||||
}
|
||||
newBoard.isTemplate = asTemplate
|
||||
await this.insertBlocks(
|
||||
newBlocks,
|
||||
description,
|
||||
async () => {
|
||||
await afterRedo?.(newBoard.id)
|
||||
},
|
||||
beforeUndo,
|
||||
)
|
||||
return [newBlocks, newBoard.id]
|
||||
}
|
||||
|
||||
// Other methods
|
||||
|
||||
// Not a mutator, but convenient to put here since Mutator wraps OctoClient
|
||||
|
@ -24,9 +24,7 @@ class OctoClient {
|
||||
return readToken
|
||||
}
|
||||
|
||||
workspaceId = '0'
|
||||
|
||||
constructor(serverUrl?: string) {
|
||||
constructor(serverUrl?: string, public workspaceId = '0') {
|
||||
this.serverUrl = serverUrl || window.location.origin
|
||||
Utils.log(`OctoClient serverUrl: ${this.serverUrl}`)
|
||||
}
|
||||
@ -362,6 +360,7 @@ class OctoClient {
|
||||
}
|
||||
}
|
||||
|
||||
const client = new OctoClient()
|
||||
const octoClient = new OctoClient()
|
||||
|
||||
export default client
|
||||
export {OctoClient}
|
||||
export default octoClient
|
||||
|
54
webapp/src/viewModel/globalTemplateTree.ts
Normal file
54
webapp/src/viewModel/globalTemplateTree.ts
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {IBlock} from '../blocks/block'
|
||||
import {Board} from '../blocks/board'
|
||||
import octoClient, {OctoClient} from '../octoClient'
|
||||
import {OctoUtils} from '../octoUtils'
|
||||
|
||||
interface GlobalTemplateTree {
|
||||
readonly boardTemplates: readonly Board[]
|
||||
readonly allBlocks: readonly IBlock[]
|
||||
}
|
||||
|
||||
class MutableGlobalTemplateTree implements GlobalTemplateTree {
|
||||
boardTemplates: Board[] = []
|
||||
get allBlocks(): IBlock[] {
|
||||
return [...this.boardTemplates]
|
||||
}
|
||||
|
||||
// Factory methods
|
||||
|
||||
static async sync(): Promise<GlobalTemplateTree> {
|
||||
const rootClient = new OctoClient(octoClient.serverUrl, '0')
|
||||
const rawBlocks = await rootClient.getBlocksWithType('board')
|
||||
|
||||
return this.buildTree(rawBlocks)
|
||||
}
|
||||
|
||||
static incrementalUpdate(originalTree: GlobalTemplateTree, updatedBlocks: IBlock[]): GlobalTemplateTree {
|
||||
const relevantBlocks = updatedBlocks.filter((block) => block.deleteAt !== 0 || block.type === 'board' || block.type === 'view')
|
||||
if (relevantBlocks.length < 1) {
|
||||
// No change
|
||||
return originalTree
|
||||
}
|
||||
const rawBlocks = OctoUtils.mergeBlocks(originalTree.allBlocks, relevantBlocks)
|
||||
return this.buildTree(rawBlocks)
|
||||
}
|
||||
|
||||
private static buildTree(sourceBlocks: readonly IBlock[]): MutableGlobalTemplateTree {
|
||||
const blocks = OctoUtils.hydrateBlocks(sourceBlocks)
|
||||
|
||||
const workspaceTree = new MutableGlobalTemplateTree()
|
||||
const allBoards = blocks.filter((block) => block.type === 'board') as Board[]
|
||||
workspaceTree.boardTemplates = allBoards.filter((block) => block.isTemplate).
|
||||
sort((a, b) => a.title.localeCompare(b.title)) as Board[]
|
||||
|
||||
return workspaceTree
|
||||
}
|
||||
|
||||
// private mutableCopy(): MutableWorkspaceTree {
|
||||
// return MutableWorkspaceTree.buildTree(this.allBlocks)!
|
||||
// }
|
||||
}
|
||||
|
||||
export {MutableGlobalTemplateTree, GlobalTemplateTree}
|
Loading…
Reference in New Issue
Block a user