focalboard/webapp/src/archiver.ts

125 lines
4.2 KiB
TypeScript
Raw Normal View History

2020-10-20 21:50:53 +02:00
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
2021-02-16 19:40:35 +01:00
import {IArchive} from './blocks/archive'
import {IMutableBlock} from './blocks/block'
2020-10-20 21:52:56 +02:00
import mutator from './mutator'
import {Utils} from './utils'
2021-02-16 19:40:35 +01:00
import {BoardTree} from './viewModel/boardTree'
2020-10-08 18:21:27 +02:00
class Archiver {
2020-10-20 22:36:54 +02:00
static async exportBoardTree(boardTree: BoardTree): Promise<void> {
2020-10-20 21:50:53 +02:00
const blocks = boardTree.allBlocks
2021-02-16 19:40:35 +01:00
const archive: IArchive = {
2020-10-20 21:50:53 +02:00
version: 1,
date: Date.now(),
blocks,
}
this.exportArchive(archive)
}
2020-10-20 22:36:54 +02:00
static async exportFullArchive(): Promise<void> {
2020-10-20 21:50:53 +02:00
const blocks = await mutator.exportFullArchive()
2021-02-16 19:40:35 +01:00
const archive: IArchive = {
2020-10-20 21:50:53 +02:00
version: 1,
date: Date.now(),
blocks,
}
this.exportArchive(archive)
}
2021-02-16 19:40:35 +01:00
private static exportArchive(archive: IArchive): void {
2020-10-20 21:50:53 +02:00
const content = JSON.stringify(archive)
const date = new Date()
2021-01-26 20:23:20 +01:00
const filename = `archive-${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}.focalboard`
2020-10-20 21:50:53 +02:00
const link = document.createElement('a')
2020-10-20 21:52:56 +02:00
link.style.display = 'none'
2020-10-20 21:50:53 +02:00
// const file = new Blob([content], { type: "text/json" })
// link.href = URL.createObjectURL(file)
link.href = 'data:text/json,' + encodeURIComponent(content)
link.download = filename
document.body.appendChild(link) // FireFox support
link.click()
// TODO: Remove or reuse link
}
static importFullArchive(onComplete?: () => void): void {
const input = document.createElement('input')
2020-10-20 21:52:56 +02:00
input.type = 'file'
2021-01-26 20:23:20 +01:00
input.accept = '.focalboard'
2020-10-20 21:50:53 +02:00
input.onchange = async () => {
2020-11-13 02:24:24 +01:00
const file = input.files && input.files[0]
2020-10-20 21:50:53 +02:00
const contents = await (new Response(file)).text()
Utils.log(`Import ${contents.length} bytes.`)
2021-02-16 19:40:35 +01:00
const archive: IArchive = JSON.parse(contents)
2020-10-20 21:50:53 +02:00
const {blocks} = archive
const date = new Date(archive.date)
Utils.log(`Import archive, version: ${archive.version}, date/time: ${date.toLocaleString()}, ${blocks.length} block(s).`)
// Basic error checking
let filteredBlocks = blocks.filter((o) => Boolean(o.id))
Utils.log(`Import ${filteredBlocks.length} filtered blocks with ids.`)
2020-10-20 21:50:53 +02:00
this.fixRootIds(filteredBlocks)
filteredBlocks = filteredBlocks.filter((o) => Boolean(o.rootId))
Utils.log(`Import ${filteredBlocks.length} filtered blocks with rootIds.`)
2020-10-20 21:50:53 +02:00
await mutator.importFullArchive(filteredBlocks)
Utils.log('Import completed')
onComplete?.()
2020-10-20 21:52:56 +02:00
}
2020-10-20 21:50:53 +02:00
2020-10-20 21:52:56 +02:00
input.style.display = 'none'
2020-10-20 21:50:53 +02:00
document.body.appendChild(input)
input.click()
// TODO: Remove or reuse input
}
private static fixRootIds(blocks: IMutableBlock[]) {
const blockMap = new Map(blocks.map((o) => [o.id, o]))
const maxLevels = 5
for (let i = 0; i < maxLevels; i++) {
let missingRootIds = false
blocks.forEach((o) => {
if (o.parentId) {
const parent = blockMap.get(o.parentId)
if (parent) {
o.rootId = parent.rootId
} else {
Utils.assert(`No parent for ${o.type}: ${o.id} (${o.title})`)
}
if (!o.rootId) {
missingRootIds = true
}
} else {
o.rootId = o.id
}
})
if (!missingRootIds) {
Utils.log(`fixRootIds in ${i} levels`)
break
}
}
// Check and log remaining errors
blocks.forEach((o) => {
if (!o.rootId) {
const parent = blockMap.get(o.parentId)
Utils.logError(`RootId is null: ${o.type} ${o.id}, parentId ${o.parentId}: ${o.title}, parent: ${parent?.type}, parent.rootId: ${parent?.rootId}, parent.title: ${parent?.title}`)
}
})
}
2020-10-08 18:21:27 +02:00
}
2020-10-20 21:50:53 +02:00
export {Archiver}