Skip to content

Commit

Permalink
feat(codesandbox): support preview both Block and Page
Browse files Browse the repository at this point in the history
  • Loading branch information
xiejay97 committed Nov 5, 2024
1 parent faec041 commit 326bd45
Show file tree
Hide file tree
Showing 13 changed files with 322 additions and 232 deletions.
2 changes: 1 addition & 1 deletion mockServer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"lint": "eslint --fix ."
},
"dependencies": {
"@opentiny/tiny-engine-dsl-vue": "^1.0.6",
"@opentiny/tiny-engine-dsl-vue": "workspace:*",
"@seald-io/nedb": "^4.0.2",
"fs-extra": "^11.1.1",
"glob": "^10.3.4",
Expand Down
File renamed without changes.
41 changes: 41 additions & 0 deletions packages/controller/js/generate-files/http.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* 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.
*
*/

import { useHttp } from '@opentiny/tiny-engine-http'

const http = useHttp()

const HEADER_LOWCODE_ORG = 'x-lowcode-org'

// 获取页面/区块预览的代码
export const fetchCode = async ({ platform, app, pageInfo, tenant } = {}) =>
http.post(
'/app-center/api/schema2code',
{ platform, app, pageInfo },
{
headers: { [HEADER_LOWCODE_ORG]: tenant }
}
)

// 获取页面依赖的关联应用数据: i18n/dataSource等
export const fetchMetaData = async ({ platform, app, type, id, history, tenant } = {}) =>
id
? http.get('/app-center/api/preview/metadata', {
headers: { [HEADER_LOWCODE_ORG]: tenant },
params: { platform, app, type, id, history }
})
: {}

// 获取页面列表
export const fetchPageList = (appId) => http.get(`/app-center/api/pages/list/${appId}`)

export const fetchBlockSchema = async (blockName) => http.get(`/material-center/api/block?label=${blockName}`)
177 changes: 177 additions & 0 deletions packages/controller/js/generate-files/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/**
* 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.
*
*/

import { constants } from '@opentiny/tiny-engine-utils'
import { useHttp } from '@opentiny/tiny-engine-http'
import useCanvas from '../../src/useCanvas'
import useBlock from '../../src/useBlock'
import useLayout from '../../src/useLayout'
import useEditorInfo from '../../src/useEditorInfo'
import { parseRequiredBlocks } from '../block'
import { getGlobalConfig } from '../../src/globalConfig'
import { fetchMetaData, fetchPageList, fetchBlockSchema } from './http'

const { PREVIEW_SANDBOX } = constants

const getBlocksSchema = async (pageSchema, blockSet = new Set()) => {
let res = []

const blockNames = parseRequiredBlocks(pageSchema)
const promiseList = blockNames
.filter((name) => {
if (blockSet.has(name)) {
return false
}

blockSet.add(name)

return true
})
.map((name) => fetchBlockSchema(name))
const schemaList = await Promise.allSettled(promiseList)
const extraList = []

schemaList.forEach((item) => {
if (item.status === 'fulfilled' && item.value?.[0]?.content) {
res.push(item.value[0].content)
extraList.push(getBlocksSchema(item.value[0].content, blockSet))
}
})
;(await Promise.allSettled(extraList)).forEach((item) => {
if (item.status === 'fulfilled' && item.value) {
res.push(...item.value)
}
})

return res
}

const getAllPageDetails = async (pageList) => {
const detailPromise = pageList.map(({ id }) => useLayout().getPluginApi('AppManage').getPageById(id))
const detailList = await Promise.allSettled(detailPromise)

return detailList
.map((item) => {
if (item.status === 'fulfilled' && item.value) {
return item.value
}
})
.filter((item) => Boolean(item))
}

const getParams = () => {
const { isBlock, getCurrentPage, canvasApi } = useCanvas()
const { getCurrentBlock } = useBlock()

const { getSchema } = canvasApi.value
const params = {
framework: getGlobalConfig()?.dslMode,
platform: getGlobalConfig()?.platformId,
pageInfo: {
schema: getSchema()
}
}
const paramsMap = new URLSearchParams(location.search)
params.app = paramsMap.get('id')
params.tenant = paramsMap.get('tenant')

if (isBlock()) {
const block = getCurrentBlock()
params.id = block?.id
params.pageInfo.name = block?.label
params.type = 'Block'
} else {
const page = getCurrentPage()
params.id = page?.id
params.pageInfo.name = page?.name
params.type = 'Page'
}

return params
}

export const getPreGenerateInfo = async (instance, sandbox = PREVIEW_SANDBOX.Web) => {
const params = getParams()
const { id, pageId } = useEditorInfo().useInfo()
const currentPage = await useLayout().getPluginApi('AppManage').getPageById(pageId)
const promises = [
useHttp().get(`/app-center/v1/api/apps/schema/${id}`),
fetchMetaData(params),
fetchPageList(params.app)
]

const [appData, metaData, pageList] = await Promise.all(promises)
const pageDetailList = await getAllPageDetails(pageList)

const blockSet = new Set()
const list = pageDetailList.map((page) => getBlocksSchema(page.page_content, blockSet))
const blocks = await Promise.allSettled(list)

const blockSchema = []
blocks.forEach((item) => {
if (item.status === 'fulfilled' && Array.isArray(item.value)) {
blockSchema.push(...item.value)
}
})

const appSchema = {
// metaData 包含dataSource、utils、i18n、globalState
...metaData,
// 页面 schema
pageSchema: pageDetailList.map((item) => {
const { page_content, ...meta } = item
return {
...page_content,
meta: {
...meta,
isHome: sandbox === PREVIEW_SANDBOX.Web ? meta.isHome : meta.id === currentPage?.id,
router: meta.route
}
}
}),
blockSchema,
// 物料数据
componentsMap: [...(appData.componentsMap || [])],

meta: {
...(appData.meta || {})
}
}

const res = await instance.generate(appSchema)

const { genResult = [] } = res || {}
const fileRes = genResult.map(({ fileContent, fileName, path, fileType }) => {
const slash = path.endsWith('/') || path === '.' ? '' : '/'
let filePath = `${path}${slash}`
if (filePath.startsWith('./')) {
filePath = filePath.slice(2)
}
if (filePath.startsWith('.')) {
filePath = filePath.slice(1)
}

if (filePath.startsWith('/')) {
filePath = filePath.slice(1)
}

return {
fileContent,
filePath: `${filePath}${fileName}`,
fileType
}
})

return fileRes
}

export default getPreGenerateInfo
2 changes: 1 addition & 1 deletion packages/design-core/src/preview/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ export default {
version: '0.0.0',
private: true,
scripts: {
dev: 'vite',
build: 'vite build',
dev: 'vite --host',
lint: 'pnpm run lint:js && pnpm run lint:style',
'lint:fix': 'pnpm run lint:js -- --fix && pnpm run lint:style -- --fix',
'lint:js': 'eslint --ext .js,.vue --ignore-path .gitignore src',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ export const codesandboxFiles = {
"start-app": {
"name": "Run Dev Server",
"command": "pnpm run dev",
"runAtStart": true,
"preview": {
"port": 3000
}
"runAtStart": true
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion packages/design-core/src/preview/src/preview/usePreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import { provide, inject } from 'vue'
import { ReplStore } from '@vue/repl'
import vueJsx from '@vue/babel-plugin-jsx'
import { transformSync } from '@babel/core'
import { genSFCWithDefaultPlugin, parseRequiredBlocks } from '@opentiny/tiny-engine-dsl-vue'
import { parseRequiredBlocks } from '@opentiny/tiny-engine-controller/js/block'
import { genSFCWithDefaultPlugin } from '@opentiny/tiny-engine-dsl-vue'
import importMap from './importMap'
import srcFiles from './srcFiles'
import generateMetaFiles, { processAppJsCode } from './generate'
Expand Down
4 changes: 3 additions & 1 deletion packages/toolbars/codesandbox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
"@opentiny/tiny-engine-canvas": "workspace:*",
"@opentiny/tiny-engine-common": "workspace:*",
"@opentiny/tiny-engine-controller": "workspace:*",
"@opentiny/tiny-engine-utils": "workspace:*"
"@opentiny/tiny-engine-dsl-vue": "workspace:*",
"@opentiny/tiny-engine-utils": "workspace:*",
"codesandbox": "^2.2.3"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
Expand Down
68 changes: 61 additions & 7 deletions packages/toolbars/codesandbox/src/Main.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@
</template>

<script>
import { reactive } from 'vue'
import { Popover } from '@opentiny/vue'
import { previewPage, previewBlock } from '@opentiny/tiny-engine-controller/js/preview'
import { previewBlock } from '@opentiny/tiny-engine-controller/js/preview'
import getPreGenerateInfo from '@opentiny/tiny-engine-controller/js/generate-files'
import { getGlobalConfig, useBlock, useCanvas, useLayout, useNotify } from '@opentiny/tiny-engine-controller'
import { constants } from '@opentiny/tiny-engine-utils'
import { generateApp } from '@opentiny/tiny-engine-dsl-vue'
import { getParameters } from 'codesandbox/lib/api/define'
import { codesandboxFiles } from './codesandboxFiles'
const { PREVIEW_SANDBOX } = constants
Expand All @@ -33,10 +38,14 @@ export default {
}
},
setup() {
const { isBlock, getCurrentPage, canvasApi } = useCanvas()
const state = reactive({
generating: false
})
const { isBlock, canvasApi } = useCanvas()
const { getCurrentBlock } = useBlock()
const preview = () => {
const preview = async () => {
if (useLayout().isEmptyPage()) {
useNotify({
type: 'warning',
Expand All @@ -60,10 +69,55 @@ export default {
params.pageInfo.name = block?.label
previewBlock(params, PREVIEW_SANDBOX.CodeSandbox)
} else {
const page = getCurrentPage()
params.id = page?.id
params.pageInfo.name = page?.name
previewPage(params, PREVIEW_SANDBOX.CodeSandbox)
if (state.generating) {
useNotify({ type: 'info', title: '代码生成中, 请稍后...' })
return
} else {
useNotify({ type: 'info', title: '代码生成中...' })
state.generating = true
}
try {
const instance = generateApp()
const fileRes = await getPreGenerateInfo(instance, PREVIEW_SANDBOX.CodeSandbox)
const files = {}
fileRes.forEach((file) => {
// 使用 pnpm 包管理
if (file.filePath === 'README.md') {
files[file.filePath] = { content: file.fileContent.replace(/npm /g, 'pnpm ') }
} else {
files[file.filePath] = { content: file.fileContent }
}
})
Object.assign(files, codesandboxFiles)
const parameters = getParameters({ files, template: 'vue-cli' })
const form = document.createElement('form')
form.method = 'POST'
form.action = 'https://codesandbox.io/api/v1/sandboxes/define'
form.target = '_blank'
const parametersInput = document.createElement('input')
parametersInput.name = 'parameters'
parametersInput.value = parameters
const queryInput = document.createElement('input')
queryInput.name = 'query'
queryInput.value = 'module=/src/App.vue'
const environmentInput = document.createElement('input')
environmentInput.name = 'environment'
environmentInput.value = 'server'
form.appendChild(parametersInput)
form.appendChild(queryInput)
form.appendChild(environmentInput)
document.body.append(form)
form.submit()
document.body.removeChild(form)
} catch (error) {
// eslint-disable-next-line no-console
console.error(error)
useNotify({ type: 'error', title: '代码生成失败', message: error?.message || error })
state.generating = false
}
}
}
Expand Down
27 changes: 27 additions & 0 deletions packages/toolbars/codesandbox/src/codesandboxFiles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export const codesandboxFiles = {
'.codesandbox/tasks.json': {
content: String.raw`{
// These tasks will run in order when initializing your CodeSandbox project.
"setupTasks": [
{
"name": "pnpm install",
"command": "pnpm install"
}
],
// These tasks can be run from CodeSandbox. Running one will open a log in the app.
"tasks": {
"install-dependencies": {
"name": "Install Dependencies",
"command": "pnpm install"
},
"start-app": {
"name": "Run Dev Server",
"command": "pnpm run dev",
"runAtStart": true
}
}
}
`
}
}
Loading

0 comments on commit 326bd45

Please sign in to comment.