diff --git a/packages/design-core/package.json b/packages/design-core/package.json index 41fe12f..8294b90 100644 --- a/packages/design-core/package.json +++ b/packages/design-core/package.json @@ -89,7 +89,6 @@ "eslint-linter-browserify": "8.57.0", "file-saver": "^2.0.5", "html2canvas": "^1.4.1", - "jszip": "^3.10.1", "monaco-editor": "0.33.0", "prettier": "2.7.1", "sortablejs": "^1.14.0", diff --git a/packages/utils/package.json b/packages/utils/package.json index 1e19fae..afebc99 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -30,7 +30,9 @@ "vite": "^4.3.7", "vitest": "^1.4.0" }, - "dependencies": {}, + "dependencies": { + "jszip": "^3.10.1" + }, "peerDependencies": { "@opentiny/vue-renderless": "^3.14.0", "vue": "^3.4.15" diff --git a/packages/utils/src/fs/fszip.js b/packages/utils/src/fs/fszip.js new file mode 100644 index 0000000..4efca73 --- /dev/null +++ b/packages/utils/src/fs/fszip.js @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ + +// browser File System Access API encapsulation +import JSZIP from 'jszip' + +/** + * 下载文件到本地 + * @param {Blob} blobData 文件二进制数据 + * @param {string} fileName 文件名 + */ +export function saveAs(blobData, fileName) { + const zipLink = document.createElement('a') + zipLink.download = fileName + zipLink.style.display = 'none' + zipLink.href = URL.createObjectURL(blobData) + document.body.appendChild(zipLink) + zipLink.click() + document.body.removeChild(zipLink) +} + +/** + * 创建一个zip + */ +export const createZip = () => { + return new JSZIP() +} + +/** + * 往zip里面写入文件夹和文件 + * @param {Array} filesInfo 文件信息 + * FileInfo.filePath 文件相对路径,以'/'相连 + * FileInfo.fileContent 文件内容 + * @param {ZipExtraInfo} ZipExtraInfo zip额外信息 + * {string} zipName 打出来的zip名称 + * {JSZIP} zipHandle 创建好的zip句柄,可以不传,不传就用新的 + */ +export const writeZip = (filesInfo, { zipHandle, zipName } = {}) => { + let zip = zipHandle + if (!zipHandle) { + zip = createZip() + } + filesInfo.forEach(({ filePath, fileContent }) => { + const file = filePath.split('/') + const fileName = file.pop() + const path = file.join('/') + if (path) { + zip.folder(path).file(fileName, fileContent) + } else { + zip.file(fileName, fileContent) + } + }) + // 把打包的内容异步转成blob二进制格式 + return zip.generateAsync({ type: 'blob' }).then((content) => { + // content就是blob数据 + saveAs(content, `${zipName}.zip`) + }) +} diff --git a/packages/utils/src/fs/index.js b/packages/utils/src/fs/index.js index 2cb2bf5..26b5656 100644 --- a/packages/utils/src/fs/index.js +++ b/packages/utils/src/fs/index.js @@ -1,17 +1,23 @@ /** -* Copyright (c) 2023 - present TinyEngine Authors. -* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. -* -* Use of this source code is governed by an MIT-style license. -* -* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, -* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR -* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. -* -*/ + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ // browser File System Access API encapsulation +import { createZip, writeZip } from './fszip' + +// 支持file system api的条件:存在这个方法 && 不处于iframe中 +export const isSupportFileSystemAccess = + Object.prototype.hasOwnProperty.call(window, 'showDirectoryPicker') && window.self === window.top + /** * 获取用户选择并授权的文件夹根路径 * @param {*} options @@ -20,7 +26,7 @@ */ export const getUserBaseDirHandle = async (options = {}) => { if (!window.showOpenFilePicker) { - throw new Error('不支持的浏览器!') + return createZip() } const dirHandle = await window.showDirectoryPicker({ mode: 'readwrite', ...options }) return dirHandle @@ -166,16 +172,29 @@ export const writeFile = async (handle, { filePath, fileContent }) => { * @param {Array} filesInfo 文件信息 * FileInfo.filePath 文件相对路径 * FileInfo.fileContent 文件内容 + * @param {Boolean} supportZipCache 是否支持zip缓存,zip缓存可能会导致文件不能及时更新,默认不缓存 + * */ -export const writeFiles = async (baseDirHandle, filesInfo) => { +export const writeFiles = async ( + baseDirHandle, + filesInfo, + zipName = 'tiny-engine-generate-code', + supportZipCache = false +) => { if (!filesInfo?.length) { return } + if (!isSupportFileSystemAccess) { + const zipInfo = { zipName, zipHandle: supportZipCache && baseDirHandle } + await writeZip(filesInfo, zipInfo) + return + } + let directoryHandle = baseDirHandle if (!directoryHandle) { directoryHandle = await window.showDirectoryPicker({ mode: 'readwrite' }) } - await Promise.all(filesInfo.map((fileInfo) => writeFile(baseDirHandle, fileInfo))) + await Promise.all(filesInfo.map((fileInfo) => writeFile(directoryHandle, fileInfo))) }