Complete main UI #37
6
src/assets/css/pages/tools/edit.scss
Normal file
6
src/assets/css/pages/tools/edit.scss
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[data-component=tools-edit] {
|
||||||
|
.root-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,9 +8,18 @@ interface PreviewProps {
|
|||||||
files: IFiles
|
files: IFiles
|
||||||
importMap: IImportMap
|
importMap: IImportMap
|
||||||
entryPoint: string
|
entryPoint: string
|
||||||
|
preExpansionCode?: string
|
||||||
|
postExpansionCode?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const Preview = ({ iframeKey, files, importMap, entryPoint }: PreviewProps) => {
|
const Preview = ({
|
||||||
|
iframeKey,
|
||||||
|
files,
|
||||||
|
importMap,
|
||||||
|
entryPoint,
|
||||||
|
preExpansionCode = '',
|
||||||
|
postExpansionCode = ''
|
||||||
|
}: PreviewProps) => {
|
||||||
const [errorMsg, setErrorMsg] = useState('')
|
const [errorMsg, setErrorMsg] = useState('')
|
||||||
const [compiledCode, setCompiledCode] = useState('')
|
const [compiledCode, setCompiledCode] = useState('')
|
||||||
|
|
||||||
@@ -19,14 +28,19 @@ const Preview = ({ iframeKey, files, importMap, entryPoint }: PreviewProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!Object.keys(files).length || !importMap || !entryPoint.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
Compiler.compile(files, importMap, entryPoint)
|
Compiler.compile(files, importMap, entryPoint)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
setCompiledCode(result.outputFiles[0].text)
|
setCompiledCode(
|
||||||
|
`${preExpansionCode}\n${result.outputFiles[0].text}\n${postExpansionCode}`
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.catch((e: Error) => {
|
.catch((e: Error) => {
|
||||||
setErrorMsg(`编译失败:${e.message}`)
|
setErrorMsg(`编译失败:${e.message}`)
|
||||||
})
|
})
|
||||||
}, [files, Compiler])
|
}, [files, Compiler, importMap, entryPoint])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-component={'playground-preview'}>
|
<div data-component={'playground-preview'}>
|
||||||
|
|||||||
@@ -3,4 +3,14 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
.playground-error-message {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
color: white;
|
||||||
|
background-color: #FF4D4FAA;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,9 +9,18 @@ interface OutputProps {
|
|||||||
selectedFileName: string
|
selectedFileName: string
|
||||||
importMap: IImportMap
|
importMap: IImportMap
|
||||||
entryPoint: string
|
entryPoint: string
|
||||||
|
preExpansionCode?: string
|
||||||
|
postExpansionCode?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const Output = ({ files, selectedFileName, importMap, entryPoint }: OutputProps) => {
|
const Output = ({
|
||||||
|
files,
|
||||||
|
selectedFileName,
|
||||||
|
importMap,
|
||||||
|
entryPoint,
|
||||||
|
preExpansionCode,
|
||||||
|
postExpansionCode
|
||||||
|
}: OutputProps) => {
|
||||||
const [selectedTab, setSelectedTab] = useState('Preview')
|
const [selectedTab, setSelectedTab] = useState('Preview')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -31,6 +40,8 @@ const Output = ({ files, selectedFileName, importMap, entryPoint }: OutputProps)
|
|||||||
files={files}
|
files={files}
|
||||||
importMap={importMap}
|
importMap={importMap}
|
||||||
entryPoint={entryPoint}
|
entryPoint={entryPoint}
|
||||||
|
preExpansionCode={preExpansionCode}
|
||||||
|
postExpansionCode={postExpansionCode}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{selectedTab === 'Transform' && <Transform file={files[selectedFileName]} />}
|
{selectedTab === 'Transform' && <Transform file={files[selectedFileName]} />}
|
||||||
|
|||||||
@@ -5,14 +5,4 @@
|
|||||||
> * {
|
> * {
|
||||||
width: 0 !important;
|
width: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playground-error-message {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
color: white;
|
|
||||||
background-color: #FF4D4FAA;
|
|
||||||
padding: 5px 10px;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
2
src/global.d.ts
vendored
2
src/global.d.ts
vendored
@@ -540,7 +540,7 @@ interface ToolVo {
|
|||||||
dist: ToolDataVo
|
dist: ToolDataVo
|
||||||
entryPoint: string
|
entryPoint: string
|
||||||
publish: string
|
publish: string
|
||||||
review: 'NONE' | 'PASS' | 'REJECT'
|
review: 'NONE' | 'PROCESSING' | 'PASS' | 'REJECT'
|
||||||
createTime: string
|
createTime: string
|
||||||
updateTime: string
|
updateTime: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
import Playground from '@/components/Playground'
|
||||||
|
import templates from '@/components/Playground/templates'
|
||||||
|
|
||||||
const Tools = () => {
|
const Tools = () => {
|
||||||
return <>1</>
|
const template = templates['base']
|
||||||
|
return (
|
||||||
|
<Playground
|
||||||
|
initFiles={template.files}
|
||||||
|
initTsconfigRaw={JSON.stringify(template.tsconfig, null, 2)}
|
||||||
|
initImportMapRaw={JSON.stringify(template.importMap, null, 2)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Tools
|
export default Tools
|
||||||
|
|||||||
168
src/pages/Tools/Edit.tsx
Normal file
168
src/pages/Tools/Edit.tsx
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import '@/assets/css/pages/tools/edit.scss'
|
||||||
|
import { r_tool_detail } from '@/services/tool.tsx'
|
||||||
|
import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants.ts'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import Playground from '@/components/Playground'
|
||||||
|
import FitFullscreen from '@/components/common/FitFullscreen.tsx'
|
||||||
|
import FlexBox from '@/components/common/FlexBox.tsx'
|
||||||
|
import { IFiles, IImportMap, ITsconfig } from '@/components/Playground/shared.ts'
|
||||||
|
import {
|
||||||
|
base64ToFiles,
|
||||||
|
base64ToStr,
|
||||||
|
IMPORT_MAP_FILE_NAME,
|
||||||
|
TS_CONFIG_FILE_NAME
|
||||||
|
} from '@/components/Playground/files.ts'
|
||||||
|
import LoadingMask from '@/components/common/LoadingMask.tsx'
|
||||||
|
|
||||||
|
const Edit = () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { toolId } = useParams()
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [toolData, setToolData] = useState<ToolVo>()
|
||||||
|
const [files, setFiles] = useState<IFiles>({})
|
||||||
|
const [selectedFileName, setSelectedFileName] = useState('')
|
||||||
|
const [importMapRaw, setImportMapRaw] = useState<string>('')
|
||||||
|
const [importMap, setImportMap] = useState<IImportMap>()
|
||||||
|
const [tsconfigRaw, setTsconfigRaw] = useState<string>('')
|
||||||
|
const [tsconfig, setTsconfig] = useState<ITsconfig>()
|
||||||
|
const [entryPoint, setEntryPoint] = useState('')
|
||||||
|
const [baseDist, setBaseDist] = useState('')
|
||||||
|
|
||||||
|
const handleOnChangeFileContent = (content: string, fileName: string, files: IFiles) => {
|
||||||
|
if (fileName === IMPORT_MAP_FILE_NAME) {
|
||||||
|
setImportMapRaw(content)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (fileName === TS_CONFIG_FILE_NAME) {
|
||||||
|
setTsconfigRaw(content)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
delete files[IMPORT_MAP_FILE_NAME]
|
||||||
|
delete files[TS_CONFIG_FILE_NAME]
|
||||||
|
setFiles(files)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTool = () => {
|
||||||
|
if (loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setLoading(true)
|
||||||
|
void message.loading({ content: '加载中……', key: 'LOADING', duration: 0 })
|
||||||
|
|
||||||
|
void r_tool_detail('!', toolId!, 'latest')
|
||||||
|
.then((res) => {
|
||||||
|
const response = res.data
|
||||||
|
switch (response.code) {
|
||||||
|
case DATABASE_SELECT_SUCCESS:
|
||||||
|
switch (response.data!.review) {
|
||||||
|
case 'NONE':
|
||||||
|
case 'REJECT':
|
||||||
|
setToolData(response.data!)
|
||||||
|
break
|
||||||
|
case 'PROCESSING':
|
||||||
|
void message.warning('工具审核中,请勿修改')
|
||||||
|
navigate('/')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
void message.warning('请先创建新版本后编辑工具')
|
||||||
|
navigate('/')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case DATABASE_NO_RECORD_FOUND:
|
||||||
|
void message.error('未找到指定工具')
|
||||||
|
navigate('/')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
void message.error('获取工具信息失败,请稍后重试')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
message.destroy('LOADING')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
setImportMap(JSON.parse(importMapRaw) as IImportMap)
|
||||||
|
} catch (e) {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
}, [importMapRaw])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
setTsconfig(JSON.parse(tsconfigRaw) as ITsconfig)
|
||||||
|
} catch (e) {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}, [tsconfigRaw])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!toolData) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
setBaseDist(base64ToStr(toolData.base.dist.data!))
|
||||||
|
const files = base64ToFiles(toolData.source.data!)
|
||||||
|
setFiles(files)
|
||||||
|
setImportMapRaw(files[IMPORT_MAP_FILE_NAME].value)
|
||||||
|
setTsconfigRaw(files[TS_CONFIG_FILE_NAME].value)
|
||||||
|
setEntryPoint(toolData.entryPoint)
|
||||||
|
setTimeout(() => {
|
||||||
|
setSelectedFileName(toolData.entryPoint)
|
||||||
|
}, 100)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
void message.error('载入工具失败')
|
||||||
|
}
|
||||||
|
}, [toolData])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getTool()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FitFullscreen data-component={'tools-edit'}>
|
||||||
|
<FlexBox direction={'horizontal'} className={'root-content'}>
|
||||||
|
<LoadingMask hidden={!loading}>
|
||||||
|
<Playground.CodeEditor
|
||||||
|
tsconfig={tsconfig}
|
||||||
|
files={{
|
||||||
|
...files,
|
||||||
|
[IMPORT_MAP_FILE_NAME]: {
|
||||||
|
name: IMPORT_MAP_FILE_NAME,
|
||||||
|
language: 'json',
|
||||||
|
value: importMapRaw
|
||||||
|
},
|
||||||
|
[TS_CONFIG_FILE_NAME]: {
|
||||||
|
name: TS_CONFIG_FILE_NAME,
|
||||||
|
language: 'json',
|
||||||
|
value: tsconfigRaw
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
notRemovable={[entryPoint]}
|
||||||
|
selectedFileName={selectedFileName}
|
||||||
|
onAddFile={(_, files) => setFiles(files)}
|
||||||
|
onRemoveFile={(_, files) => setFiles(files)}
|
||||||
|
onRenameFile={(_, __, files) => setFiles(files)}
|
||||||
|
onChangeFileContent={handleOnChangeFileContent}
|
||||||
|
onSelectedFileChange={setSelectedFileName}
|
||||||
|
/>
|
||||||
|
<Playground.Output
|
||||||
|
files={files}
|
||||||
|
selectedFileName={selectedFileName}
|
||||||
|
importMap={importMap!}
|
||||||
|
entryPoint={entryPoint}
|
||||||
|
postExpansionCode={baseDist}
|
||||||
|
/>
|
||||||
|
</LoadingMask>
|
||||||
|
</FlexBox>
|
||||||
|
</FitFullscreen>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Edit
|
||||||
@@ -11,8 +11,8 @@ import Card from '@/components/common/Card'
|
|||||||
|
|
||||||
const View = () => {
|
const View = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [loading, setLoading] = useState(false)
|
|
||||||
const { username, toolId, ver } = useParams()
|
const { username, toolId, ver } = useParams()
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
const [compiledCode, setCompiledCode] = useState('')
|
const [compiledCode, setCompiledCode] = useState('')
|
||||||
|
|
||||||
const render = (toolVo: ToolVo) => {
|
const render = (toolVo: ToolVo) => {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ interface CommonCardProps
|
|||||||
onOpen?: () => void
|
onOpen?: () => void
|
||||||
onEdit?: () => void
|
onEdit?: () => void
|
||||||
onPublish?: () => void
|
onPublish?: () => void
|
||||||
|
onCancelReview?: () => void
|
||||||
onDelete?: () => void
|
onDelete?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ const CommonCard = ({
|
|||||||
onOpen,
|
onOpen,
|
||||||
onEdit,
|
onEdit,
|
||||||
onPublish,
|
onPublish,
|
||||||
|
onCancelReview,
|
||||||
onDelete,
|
onDelete,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
@@ -80,14 +82,19 @@ const CommonCard = ({
|
|||||||
打开
|
打开
|
||||||
</AntdButton>
|
</AntdButton>
|
||||||
)}
|
)}
|
||||||
{onEdit && (
|
{onEdit && onPublish && (
|
||||||
<div className={'edit'}>
|
<div className={'edit'}>
|
||||||
<AntdButton.Group size={'small'}>
|
<AntdButton.Group size={'small'}>
|
||||||
<AntdButton onClick={onEdit}>编辑</AntdButton>
|
<AntdButton onClick={onEdit}>编辑</AntdButton>
|
||||||
{onPublish && <AntdButton onClick={onPublish}>发布</AntdButton>}
|
<AntdButton onClick={onPublish}>发布</AntdButton>
|
||||||
</AntdButton.Group>
|
</AntdButton.Group>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{onCancelReview && (
|
||||||
|
<AntdButton size={'small'} onClick={onCancelReview}>
|
||||||
|
取消审核
|
||||||
|
</AntdButton>
|
||||||
|
)}
|
||||||
{onDelete && (
|
{onDelete && (
|
||||||
<AntdButton size={'small'} danger onClick={onDelete}>
|
<AntdButton size={'small'} danger onClick={onDelete}>
|
||||||
删除
|
删除
|
||||||
@@ -119,13 +126,21 @@ const ToolCard = ({ tools, onDelete, onUpgrade }: ToolCardProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleOnEditTool = () => {
|
const handleOnEditTool = () => {
|
||||||
if (selectedTool.publish === '0') {
|
if (selectedTool.publish === '0' && ['NONE', 'REJECT'].includes(selectedTool.review)) {
|
||||||
return () => {}
|
return () => {
|
||||||
|
navigate(`/edit/${tools[0].toolId}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleOnPublishTool = () => {
|
const handleOnPublishTool = () => {
|
||||||
if (selectedTool.publish === '0') {
|
if (selectedTool.publish === '0' && ['NONE', 'REJECT'].includes(selectedTool.review)) {
|
||||||
|
return () => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnCancelReview = () => {
|
||||||
|
if (selectedTool.publish === '0' && selectedTool.review === 'PROCESSING') {
|
||||||
return () => {}
|
return () => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,6 +161,7 @@ const ToolCard = ({ tools, onDelete, onUpgrade }: ToolCardProps) => {
|
|||||||
onOpen={handleOnOpenTool}
|
onOpen={handleOnOpenTool}
|
||||||
onEdit={handleOnEditTool()}
|
onEdit={handleOnEditTool()}
|
||||||
onPublish={handleOnPublishTool()}
|
onPublish={handleOnPublishTool()}
|
||||||
|
onCancelReview={handleOnCancelReview()}
|
||||||
onDelete={handleOnDeleteTool}
|
onDelete={handleOnDeleteTool}
|
||||||
>
|
>
|
||||||
<AntdSelect
|
<AntdSelect
|
||||||
|
|||||||
@@ -45,6 +45,13 @@ export const tools: RouteJsonObject[] = [
|
|||||||
component: lazy(() => import('@/pages/Tools/View')),
|
component: lazy(() => import('@/pages/Tools/View')),
|
||||||
name: '查看'
|
name: '查看'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'edit/:toolId',
|
||||||
|
absolutePath: '/edit',
|
||||||
|
id: 'tools-edit',
|
||||||
|
component: lazy(() => import('@/pages/Tools/Edit')),
|
||||||
|
name: '查看'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
absolutePath: '*',
|
absolutePath: '*',
|
||||||
|
|||||||
Reference in New Issue
Block a user