* 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 { ref, reactive, onActivated, nextTick, watch } from 'vue'
import { useCanvas, useModal, useNotify } from '@opentiny/tiny-engine-meta-register'
import { string2Ast, ast2String, insertName, formatString } from '@opentiny/tiny-engine-common/js/ast'
import { constants } from '@opentiny/tiny-engine-utils'
import { lint } from '@opentiny/tiny-engine-common/js/linter'
import { isFunction } from '@opentiny/vue-renderless/grid/static'
const { SCHEMA_DATA_TYPE } = constants
const state = reactive({
linterWorker: null,
script: '',
isChanged: false,
hasError: false,
editorSelection: null,
completionProvider: null,
hasErrorPopup: false
})
const monaco = ref(null)
let scriptAst = null
export const getMethods = () => {
const pageSchema = useCanvas().getSchema?.() || {}
pageSchema.methods = pageSchema?.methods || {}
return pageSchema.methods
}
export const getMethodNameList = () => (useCanvas().pageState.pageSchema && Object.keys(getMethods())) || []
export const getMethodContentList = () => Object.values(getMethods()).map((method) => method.value)
const getScriptString = () => {
const list = Object.entries(getMethods()).map(([name, method]) => insertName(name, method.value))
const script = list.join(`\n`)
try {
scriptAst = string2Ast(script)
} catch (error) {
useNotify({
type: 'error',
message: `代码静态检查有错误:${error}`
})
}
return script
}
const change = (value) => {
const lineBreakPattern = /\r\n/g
state.isChanged = value.replace(lineBreakPattern, '\n') !== state.script.replace(lineBreakPattern, '\n')
if (!monaco.value) {
return
}
const monacoModel = monaco.value.getEditor().getModel()
lint(monacoModel, state.linterWorker)
}
export const saveMethod = ({ name, content }) => {
if (!name) {
return
}
const methods = getMethods()
const methodItem = {
type: SCHEMA_DATA_TYPE.JSFunction,
value: content
}
useCanvas().updateSchema({ methods: { ...methods, [name]: methodItem } })
}
const saveMethods = async () => {
const { message } = useModal()
if (!state.isChanged || state.hasErrorPopup) {
return false
}
if (state.hasError) {
state.hasErrorPopup = true
message({
status: 'error',
message: '代码静态检查有错误,请先修改后再保存',
exec: () => {
state.hasErrorPopup = false
}
})
return false
}
const editorContent = monaco.value.getEditor().getValue()
const ast = string2Ast(editorContent)
const newMethods = {}
ast.program.body.forEach((declaration, index) => {
const name = declaration?.id?.name
if (
ast.program.body[index + 1]?.leadingComments &&
declaration.trailingComments === ast.program.body[index + 1].leadingComments
) {
delete declaration.trailingComments
}
const content = formatString(ast2String(declaration).trim(), 'javascript')
if (name) {
newMethods[name] = {
type: SCHEMA_DATA_TYPE.JSFunction,
value: content
}
}
})
useCanvas().updateSchema({ methods: newMethods })
useCanvas().setSaved(false)
const newScript = getScriptString()
state.script = ''
await nextTick()
state.script = newScript
state.isChanged = false
useNotify({
type: 'success',
message: '保存成功!'
})
return true
}
const close = (emit) => (callback) => {
const { confirm } = useModal()
const callbackFn = isFunction(callback) ? callback : () => emit('close')
if (!state.isChanged) {
callbackFn(true)
return
}
confirm({
title: '提示',
message: '有改动未保存,您确定保存并关闭吗?',
exec() {
callbackFn(saveMethods())
},
cancel() {
callbackFn(true)
}
})
}
const setEditorSelection = () => {
if (!state.editorSelection || !monaco.value) {
return
}
const editor = monaco.value.getEditor()
editor.setSelection(state.editorSelection)
editor.focus()
const top = editor.getTopForLineNumber(state.editorSelection.startLineNumber - 1)
editor.setScrollPosition(
{
scrollLeft: 0,
scrollTop: top
},
0
)
}
export const highlightMethod = async (name) => {
if (!name) {
return
}
const declarations = scriptAst?.program.body.filter((declaration) => name === declaration?.id?.name)
if (declarations.length === 0) {
return
}
const loc = declarations[0]?.loc
if (loc) {
state.editorSelection = {
startColumn: loc.start.column,
startLineNumber: loc.start.line,
endColumn: loc.end.column + 1,
endLineNumber: loc.end.line
}
}
if (state.editorSelection) {
await nextTick()
setEditorSelection()
}
}
export default ({ emit }) => {
watch(getScriptString, (newScript) => {
state.script = newScript
})
onActivated(() => {
nextTick(() => {
state.script = getScriptString()
monaco.value?.focus()
window.dispatchEvent(new Event('resize'))
})
})
return {
state,
monaco,
change,
saveMethods,
close: close(emit)
}
}