Add compile to ToolBase management page

This commit is contained in:
2024-01-23 15:32:15 +08:00
parent e2d2710823
commit 2e331658c5
8 changed files with 276 additions and 91 deletions

View File

@@ -19,7 +19,7 @@
> *:nth-child(2) { > *:nth-child(2) {
position: sticky; position: sticky;
top: 20px; top: 20px;
height: calc(100vh - 40px); height: calc(100vh - 60px);
} }
.close-editor-btn { .close-editor-btn {

View File

@@ -2,6 +2,7 @@ import '@/components/Playground/Output/Preview/preview.scss'
import { IFiles, IImportMap } from '@/components/Playground/shared' import { IFiles, IImportMap } from '@/components/Playground/shared'
import Compiler from '@/components/Playground/compiler' import Compiler from '@/components/Playground/compiler'
import iframeRaw from '@/components/Playground/Output/Preview/iframe.html?raw' import iframeRaw from '@/components/Playground/Output/Preview/iframe.html?raw'
import { ENTRY_FILE_NAME } from '@/components/Playground/files.ts'
interface PreviewProps { interface PreviewProps {
iframeKey: string iframeKey: string
@@ -60,7 +61,7 @@ const Preview = ({ iframeKey, files, importMap }: PreviewProps) => {
}, []) }, [])
useEffect(() => { useEffect(() => {
Compiler.compile(files, importMap) Compiler.compile(files, importMap, [ENTRY_FILE_NAME])
.then((result) => { .then((result) => {
if (loaded) { if (loaded) {
iframeRef.current?.contentWindow?.postMessage({ iframeRef.current?.contentWindow?.postMessage({

View File

@@ -39,7 +39,7 @@ class Compiler {
return esbuild.transform(code, { loader }) return esbuild.transform(code, { loader })
}) })
compile = (files: IFiles, importMap: IImportMap) => compile = (files: IFiles, importMap: IImportMap, entryPoints: string[]) =>
new Promise<void>((resolve) => { new Promise<void>((resolve) => {
if (this.init) { if (this.init) {
resolve() resolve()
@@ -54,7 +54,7 @@ class Compiler {
}).then(() => { }).then(() => {
return esbuild.build({ return esbuild.build({
bundle: true, bundle: true,
entryPoints: [ENTRY_FILE_NAME], entryPoints: entryPoints,
format: 'esm', format: 'esm',
metafile: true, metafile: true,
write: false, write: false,

7
src/global.d.ts vendored
View File

@@ -481,9 +481,9 @@ interface ToolCategoryAddEditParam {
interface ToolDataVo { interface ToolDataVo {
id: string id: string
data: string data?: string
createTime: string createTime?: string
updateTime: string updateTime?: string
} }
interface ToolBaseVo { interface ToolBaseVo {
@@ -491,6 +491,7 @@ interface ToolBaseVo {
name: string name: string
source: ToolDataVo source: ToolDataVo
dist: ToolDataVo dist: ToolDataVo
compiled: boolean
enable: boolean enable: boolean
createTime: string createTime: string
updateTime: string updateTime: string

View File

@@ -37,7 +37,7 @@ const Mail = () => {
onOk: () => onOk: () =>
mailSendForm.validateFields().then( mailSendForm.validateFields().then(
() => { () => {
return new Promise((resolve) => { return new Promise<void>((resolve) => {
void r_sys_settings_mail_send({ void r_sys_settings_mail_send({
to: mailSendForm.getFieldValue('to') as string to: mailSendForm.getFieldValue('to') as string
}).then((res) => { }).then((res) => {
@@ -45,10 +45,10 @@ const Mail = () => {
if (response.success) { if (response.success) {
void message.success('发送成功') void message.success('发送成功')
resolve(true) resolve()
} else { } else {
void message.error('发送失败,请检查配置后重试') void message.error('发送失败,请检查配置后重试')
resolve(true) resolve()
} }
}) })
}) })

View File

@@ -16,12 +16,14 @@ import {
r_sys_tool_base_get, r_sys_tool_base_get,
r_sys_tool_base_update r_sys_tool_base_update
} from '@/services/system' } from '@/services/system'
import { IFile, IFiles, ITsconfig } from '@/components/Playground/shared' import { IFile, IFiles, IImportMap, ITsconfig } from '@/components/Playground/shared'
import { import {
base64ToFiles, base64ToFiles,
fileNameToLanguage, fileNameToLanguage,
filesToBase64, filesToBase64,
getFilesSize, getFilesSize,
IMPORT_MAP_FILE_NAME,
strToBase64,
TS_CONFIG_FILE_NAME TS_CONFIG_FILE_NAME
} from '@/components/Playground/files' } from '@/components/Playground/files'
import FitFullscreen from '@/components/common/FitFullscreen' import FitFullscreen from '@/components/common/FitFullscreen'
@@ -30,6 +32,8 @@ import HideScrollbar from '@/components/common/HideScrollbar'
import Card from '@/components/common/Card' import Card from '@/components/common/Card'
import CodeEditor from '@/components/Playground/CodeEditor' import CodeEditor from '@/components/Playground/CodeEditor'
import Permission from '@/components/common/Permission' import Permission from '@/components/common/Permission'
import { useState } from 'react'
import compiler from '@/components/Playground/compiler.ts'
const Base = () => { const Base = () => {
const blocker = useBlocker( const blocker = useBlocker(
@@ -55,6 +59,8 @@ const Base = () => {
const [baseDetailData, setBaseDetailData] = useState<Record<string, ToolBaseVo>>({}) const [baseDetailData, setBaseDetailData] = useState<Record<string, ToolBaseVo>>({})
const [baseDetailLoading, setBaseDetailLoading] = useState<Record<string, boolean>>({}) const [baseDetailLoading, setBaseDetailLoading] = useState<Record<string, boolean>>({})
const [tsconfig, setTsconfig] = useState<ITsconfig>() const [tsconfig, setTsconfig] = useState<ITsconfig>()
const [compiling, setCompiling] = useState(false)
const [compileForm] = AntdForm.useForm<{ entryFileName: string }>()
useBeforeUnload( useBeforeUnload(
useCallback( useCallback(
@@ -70,10 +76,6 @@ const Base = () => {
) )
const handleOnAddBtnClick = () => { const handleOnAddBtnClick = () => {
if (Object.keys(hasEdited).length) {
void message.warning('新增前请保存修改')
return
}
setIsDrawerEdit(false) setIsDrawerEdit(false)
setIsDrawerOpen(true) setIsDrawerOpen(true)
form.setFieldValue('id', undefined) form.setFieldValue('id', undefined)
@@ -93,41 +95,62 @@ const Base = () => {
{ {
title: '创建时间', title: '创建时间',
dataIndex: 'createTime', dataIndex: 'createTime',
width: '20%', width: '15em',
align: 'center', align: 'center',
render: (value: string) => utcToLocalTime(value) render: (value: string) => utcToLocalTime(value)
}, },
{ {
title: '修改时间', title: '修改时间',
dataIndex: 'updateTime', dataIndex: 'updateTime',
width: '20%', width: '15em',
align: 'center', align: 'center',
render: (value: string) => utcToLocalTime(value) render: (value: string) => utcToLocalTime(value)
}, },
{ {
title: '状态', title: '状态',
dataIndex: 'enable', width: '10em',
width: '5%',
align: 'center', align: 'center',
render: (value) => render: (_, record) => (
value ? <AntdTag color={'success'}></AntdTag> : <AntdTag></AntdTag> <>
{record.enable ? (
<AntdTag color={'success'}></AntdTag>
) : (
<AntdTag></AntdTag>
)}
{!record.compiled && <AntdTag></AntdTag>}
</>
)
}, },
{ {
title: ( title: (
<> <>
(
<a style={{ color: COLOR_PRODUCTION }} onClick={handleOnAddBtnClick}> {!Object.keys(hasEdited).length && (
<>
</a> {' '}
) (
<a style={{ color: COLOR_PRODUCTION }} onClick={handleOnAddBtnClick}>
</a>
)
</>
)}
</> </>
), ),
dataIndex: 'enable', dataIndex: 'enable',
width: '15em', width: '12em',
align: 'center', align: 'center',
render: (_, record) => ( render: (_, record) => (
<> <>
<AntdSpace size={'middle'}> <AntdSpace size={'middle'}>
{!record.compiled && !Object.keys(hasEdited).length && (
<a
style={{ color: COLOR_PRODUCTION }}
onClick={handleOnCompileBtnClick(record)}
>
</a>
)}
{hasEdited[record.id] && ( {hasEdited[record.id] && (
<Permission operationCode={'system:tool:modify:base'}> <Permission operationCode={'system:tool:modify:base'}>
<a <a
@@ -138,14 +161,16 @@ const Base = () => {
</a> </a>
</Permission> </Permission>
)} )}
<Permission operationCode={'system:tool:modify:base'}> {!Object.keys(hasEdited).length && (
<a <Permission operationCode={'system:tool:modify:base'}>
style={{ color: COLOR_PRODUCTION }} <a
onClick={handleOnEditBtnClick(record)} style={{ color: COLOR_PRODUCTION }}
> onClick={handleOnEditBtnClick(record)}
>
</a>
</Permission> </a>
</Permission>
)}
<Permission operationCode={'system:tool:delete:base'}> <Permission operationCode={'system:tool:delete:base'}>
<a <a
style={{ color: COLOR_PRODUCTION }} style={{ color: COLOR_PRODUCTION }}
@@ -160,6 +185,163 @@ const Base = () => {
} }
] ]
const handleOnCompileBtnClick = (value: ToolBaseVo) => {
return () => {
if (compiling || isLoading) {
return
}
setCompiling(true)
setIsLoading(true)
void message.loading({ content: '加载文件中', key: 'compile-loading', duration: 0 })
if (!baseDetailLoading[value.id]) {
getBaseDetail(value)
}
void new Promise<void>((resolve, reject) => {
const timer = setInterval(() => {
let loading
let data
setBaseDetailLoading((prevState) => {
loading = prevState[value.id]
return prevState
})
setBaseDetailData((prevState) => {
data = prevState[value.id]
return prevState
})
if (!loading && data) {
clearInterval(timer)
resolve()
}
if (loading !== undefined && !loading && !data) {
clearInterval(timer)
reject()
}
}, 100)
})
.then(() => {
let baseDetail: ToolBaseVo
setBaseDetailData((prevState) => {
baseDetail = prevState[value.id]
return prevState
})
message.destroy('compile-loading')
const files = base64ToFiles(baseDetail!.source.data!)
if (!Object.keys(files).includes(IMPORT_MAP_FILE_NAME)) {
void message.warning(`编译中止:未包含 ${IMPORT_MAP_FILE_NAME} 文件`)
setCompiling(false)
setIsLoading(false)
return
}
let importMap: IImportMap
try {
importMap = JSON.parse(files[IMPORT_MAP_FILE_NAME].value) as IImportMap
} catch (e) {
void message.warning(`编译中止Import Map 文件转换失败`)
setCompiling(false)
setIsLoading(false)
return
}
compileForm.setFieldValue('entryFileName', undefined)
void modal.confirm({
title: '编译',
content: (
<>
<AntdForm form={compileForm}>
<AntdForm.Item
name={'entryFileName'}
label={'入口文件'}
style={{ marginTop: 10 }}
rules={[{ required: true, message: '请选择入口文件' }]}
>
<AntdSelect
options={Object.keys(files)
.filter(
(value) =>
![
IMPORT_MAP_FILE_NAME,
TS_CONFIG_FILE_NAME
].includes(value)
)
.map((value) => ({ value, label: value }))}
/>
</AntdForm.Item>
</AntdForm>
</>
),
onOk: () =>
compileForm.validateFields().then(
() => {
return new Promise<void>((resolve) => {
resolve()
void message.loading({
content: '编译中',
key: 'compiling',
duration: 0
})
void compiler
.compile(files, importMap, [
compileForm.getFieldValue('entryFileName') as string
])
.then((result) => {
void message.destroy('compiling')
void message.loading({
content: '上传中',
key: 'uploading',
duration: 0
})
console.debug(result.outputFiles[0].text)
void r_sys_tool_base_update({
id: value.id,
dist: strToBase64(result.outputFiles[0].text)
})
.then((res) => {
const response = res.data
switch (response.code) {
case DATABASE_UPDATE_SUCCESS:
void message.success('编译成功')
getBase()
break
default:
void message.error('上传失败')
}
})
.finally(() => {
void message.destroy('uploading')
setCompiling(false)
setIsLoading(false)
})
})
.catch((e: Error) => {
void message.error(`编译失败:${e.message}`)
void message.destroy('compiling')
setCompiling(false)
setIsLoading(false)
})
})
},
() => {
return new Promise((_, reject) => {
reject('请选择入口文件')
})
}
),
onCancel: () => {
setCompiling(false)
setIsLoading(false)
}
})
})
.catch(() => {
setCompiling(false)
setIsLoading(false)
message.destroy('compile-loading')
})
}
}
const handleOnSaveBtnClick = (value: ToolBaseVo) => { const handleOnSaveBtnClick = (value: ToolBaseVo) => {
return () => { return () => {
if (isLoading) { if (isLoading) {
@@ -191,11 +373,6 @@ const Base = () => {
const handleOnEditBtnClick = (value: ToolBaseVo) => { const handleOnEditBtnClick = (value: ToolBaseVo) => {
return () => { return () => {
if (Object.keys(hasEdited).length) {
void message.warning('编辑前请保存修改')
return
}
setIsDrawerEdit(true) setIsDrawerEdit(true)
setIsDrawerOpen(true) setIsDrawerOpen(true)
form.setFieldValue('id', value.id) form.setFieldValue('id', value.id)
@@ -340,6 +517,17 @@ const Base = () => {
switch (response.code) { switch (response.code) {
case DATABASE_SELECT_SUCCESS: case DATABASE_SELECT_SUCCESS:
setBaseDetailData({ ...baseDetailData, [record.id]: response.data! }) setBaseDetailData({ ...baseDetailData, [record.id]: response.data! })
setBaseData(
baseData.map((value) =>
value.id === response.data!.id
? {
...response.data!,
source: { id: response.data!.source.id },
dist: { id: response.data!.dist.id }
}
: value
)
)
break break
default: default:
void message.error(`获取基板 ${record.name} 文件内容失败,请稍后重试`) void message.error(`获取基板 ${record.name} 文件内容失败,请稍后重试`)
@@ -355,16 +543,11 @@ const Base = () => {
let sourceFiles: IFiles | undefined = undefined let sourceFiles: IFiles | undefined = undefined
let sourceFileList: IFile[] = [] let sourceFileList: IFile[] = []
if (baseDetailVo) { if (baseDetailVo) {
sourceFiles = base64ToFiles(baseDetailVo.source.data) sourceFiles = base64ToFiles(baseDetailVo.source.data!)
sourceFileList = Object.values(sourceFiles) sourceFileList = Object.values(sourceFiles)
} }
const handleOnAddFile = () => { const handleOnAddFile = () => {
if (Object.keys(hasEdited).length) {
void message.warning('新建文件前请先保存更改')
return
}
void modal.confirm({ void modal.confirm({
title: '新建文件', title: '新建文件',
content: ( content: (
@@ -401,7 +584,7 @@ const Base = () => {
onOk: () => onOk: () =>
addFileForm.validateFields().then( addFileForm.validateFields().then(
() => { () => {
return new Promise((resolve) => { return new Promise<void>((resolve) => {
const newFileName = addFileForm.getFieldValue('fileName') as string const newFileName = addFileForm.getFieldValue('fileName') as string
setBaseDetailLoading({ ...baseDetailLoading, [record.id]: true }) setBaseDetailLoading({ ...baseDetailLoading, [record.id]: true })
@@ -428,11 +611,11 @@ const Base = () => {
setTimeout(() => { setTimeout(() => {
getBaseDetail(record) getBaseDetail(record)
}) })
resolve(true) resolve()
break break
default: default:
void message.error('添加失败,请稍后重试') void message.error('添加失败,请稍后重试')
resolve(true) resolve()
} }
}) })
.finally(() => { .finally(() => {
@@ -468,11 +651,17 @@ const Base = () => {
{ {
title: ( title: (
<> <>
(
<a style={{ color: COLOR_PRODUCTION }} onClick={handleOnAddFile}> {!Object.keys(hasEdited).length && (
<>
</a> {' '}
) (
<a style={{ color: COLOR_PRODUCTION }} onClick={handleOnAddFile}>
</a>
)
</>
)}
</> </>
), ),
dataIndex: 'enable', dataIndex: 'enable',
@@ -489,22 +678,26 @@ const Base = () => {
</a> </a>
</Permission> </Permission>
<Permission operationCode={'system:tool:modify:category'}> {!Object.keys(hasEdited).length && (
<a <Permission operationCode={'system:tool:modify:category'}>
onClick={handleOnRenameFile(record.name)} <a
style={{ color: COLOR_PRODUCTION }} onClick={handleOnRenameFile(record.name)}
> style={{ color: COLOR_PRODUCTION }}
>
</a>
</Permission> </a>
<Permission operationCode={'system:tool:delete:category'}> </Permission>
<a )}
onClick={handleOnDeleteFile(record.name)} {!Object.keys(hasEdited).length && (
style={{ color: COLOR_PRODUCTION }} <Permission operationCode={'system:tool:delete:category'}>
> <a
onClick={handleOnDeleteFile(record.name)}
</a> style={{ color: COLOR_PRODUCTION }}
</Permission> >
</a>
</Permission>
)}
</AntdSpace> </AntdSpace>
</> </>
) )
@@ -526,10 +719,6 @@ const Base = () => {
const handleOnRenameFile = (fileName: string) => { const handleOnRenameFile = (fileName: string) => {
return () => { return () => {
if (Object.keys(hasEdited).length) {
void message.warning('重命名文件前请先保存更改')
return
}
renameFileForm.setFieldValue('fileName', fileName) renameFileForm.setFieldValue('fileName', fileName)
void modal.confirm({ void modal.confirm({
title: '重命名文件', title: '重命名文件',
@@ -570,7 +759,7 @@ const Base = () => {
onOk: () => onOk: () =>
renameFileForm.validateFields().then( renameFileForm.validateFields().then(
() => { () => {
return new Promise((resolve) => { return new Promise<void>((resolve) => {
const newFileName = renameFileForm.getFieldValue( const newFileName = renameFileForm.getFieldValue(
'fileName' 'fileName'
) as string ) as string
@@ -615,8 +804,7 @@ const Base = () => {
[record.id]: false [record.id]: false
}) })
}) })
resolve()
resolve(true)
}) })
}, },
() => { () => {
@@ -631,11 +819,6 @@ const Base = () => {
const handleOnDeleteFile = (fileName: string) => { const handleOnDeleteFile = (fileName: string) => {
return () => { return () => {
if (Object.keys(hasEdited).length) {
void message.warning('删除文件前请先保存更改')
return
}
modal modal
.confirm({ .confirm({
title: '确定删除', title: '确定删除',
@@ -794,6 +977,7 @@ const Base = () => {
onChangeFileContent={handleOnChangeFileContent} onChangeFileContent={handleOnChangeFileContent}
showFileSelector={false} showFileSelector={false}
tsconfig={tsconfig} tsconfig={tsconfig}
readonly={isLoading || baseDetailLoading[editingBaseId]}
/> />
<div className={'close-editor-btn'} onClick={handleOnCloseBtnClick}> <div className={'close-editor-btn'} onClick={handleOnCloseBtnClick}>
<Icon component={IconOxygenClose} /> <Icon component={IconOxygenClose} />

View File

@@ -377,7 +377,7 @@ const Template = () => {
let sourceFiles: IFiles | undefined = undefined let sourceFiles: IFiles | undefined = undefined
let sourceFileList: IFile[] = [] let sourceFileList: IFile[] = []
if (templateDetailVo) { if (templateDetailVo) {
sourceFiles = base64ToFiles(templateDetailVo.source.data) sourceFiles = base64ToFiles(templateDetailVo.source.data!)
sourceFileList = Object.values(sourceFiles) sourceFileList = Object.values(sourceFiles)
} }
@@ -423,7 +423,7 @@ const Template = () => {
onOk: () => onOk: () =>
addFileForm.validateFields().then( addFileForm.validateFields().then(
() => { () => {
return new Promise((resolve) => { return new Promise<void>((resolve) => {
const newFileName = addFileForm.getFieldValue('fileName') as string const newFileName = addFileForm.getFieldValue('fileName') as string
setTemplateDetailLoading({ setTemplateDetailLoading({
@@ -453,11 +453,11 @@ const Template = () => {
setTimeout(() => { setTimeout(() => {
getTemplateDetail(record) getTemplateDetail(record)
}) })
resolve(true) resolve()
break break
default: default:
void message.error('添加失败,请稍后重试') void message.error('添加失败,请稍后重试')
resolve(true) resolve()
} }
}) })
.finally(() => { .finally(() => {
@@ -595,7 +595,7 @@ const Template = () => {
onOk: () => onOk: () =>
renameFileForm.validateFields().then( renameFileForm.validateFields().then(
() => { () => {
return new Promise((resolve) => { return new Promise<void>((resolve) => {
const newFileName = renameFileForm.getFieldValue( const newFileName = renameFileForm.getFieldValue(
'fileName' 'fileName'
) as string ) as string
@@ -640,8 +640,7 @@ const Template = () => {
[record.id]: false [record.id]: false
}) })
}) })
resolve()
resolve(true)
}) })
}, },
() => { () => {

View File

@@ -392,7 +392,7 @@ const User = () => {
.validateFields() .validateFields()
.then( .then(
() => { () => {
return new Promise((resolve, reject) => { return new Promise<void>((resolve, reject) => {
void r_sys_user_change_password({ void r_sys_user_change_password({
id: changePasswordForm.getFieldValue('id') as string, id: changePasswordForm.getFieldValue('id') as string,
password: changePasswordForm.getFieldValue( password: changePasswordForm.getFieldValue(
@@ -407,7 +407,7 @@ const User = () => {
const response = res.data const response = res.data
if (response.success) { if (response.success) {
void message.success('修改密码成功') void message.success('修改密码成功')
resolve(true) resolve()
} else { } else {
reject(response.msg) reject(response.msg)
} }