<template>
<component :is="CanvasLayout" :class="{ 'not-selected': getMoveDragBarState() }">
<template #header>
<component v-if="!isBlock()" :is="CanvasRouteBar"></component>
</template>
<template #container>
<component
:is="CanvasContainer.entry"
:controller="controller"
:materials-panel="materialsPanel"
:canvas-src="canvasSrc"
:canvas-src-doc="canvasSrcDoc"
@remove="removeNode"
@selected="nodeSelected"
>
</component>
</template>
<template #footer>
<component :is="CanvasBreadcrumb" :data="footData"></component>
</template>
</component>
</template>
<script>
import { ref, watch, onUnmounted, onMounted, computed } from 'vue'
import {
useProperties,
useCanvas,
useLayout,
useMaterial,
useHistory,
useModal,
usePage,
useMessage,
getMergeMeta,
getOptions,
getMetaApi,
META_SERVICE,
META_APP,
useNotify
} from '@opentiny/tiny-engine-meta-register'
import { constants } from '@opentiny/tiny-engine-utils'
import * as ast from '@opentiny/tiny-engine-common/js/ast'
import { initCanvas } from '../../init-canvas/init-canvas'
import { useMultiSelect } from '../../container/src/composables/useMultiSelect'
import { getImportMapData } from './importMap'
import meta from '../meta'
const { PAGE_STATUS } = constants
const componentType = {
Block: '区块',
Page: '页面'
}
export default {
setup() {
const registry = getMergeMeta('engine.canvas')
const materialsPanel = getMergeMeta('engine.plugins.materials')?.entry
const { CanvasRouteBar, CanvasBreadcrumb } = registry.components
const CanvasLayout = registry.layout.entry
const [CanvasContainer] = registry.metas
const footData = ref([])
const canvasRef = ref(null)
let showModal = false
const { canvasSrc = '' } = getOptions(meta.id) || {}
const canvasSrcDoc = ref('')
const { getMoveDragBarState, getFixedPanelsStatus, closePlugin, closeSetting } = useLayout()
useMessage().subscribe({
topic: 'init_canvas_deps',
subscriber: 'canvas_design_canvas',
callback: (deps) => {
if (canvasSrc) {
return
}
const { importMap, importStyles, importScripts } = getImportMapData(deps)
canvasSrcDoc.value = initCanvas(importMap, importStyles, importScripts).html
}
})
const removeNode = (node) => {
const { pageState } = useCanvas()
footData.value = useCanvas().getNodePath(node?.id)
pageState.currentSchema = {}
pageState.properties = null
useProperties().getProps(null, null)
}
const isBlock = useCanvas().isBlock
watch(
[() => useCanvas().isSaved(), () => useLayout().layoutState.pageStatus, () => useCanvas().getPageSchema()],
([isSaved, pageStatus, pageSchema], [oldIsSaved, _oldPageStatus, oldPageSchema]) => {
if (
[PAGE_STATUS.Guest, PAGE_STATUS.Occupy].includes(useLayout().layoutState.pageStatus.state) ||
!pageSchema?.componentName
) {
return
}
const pageInfo = pageStatus?.data
const message = {
empty: () => '应用下暂无页面,需新建页面后体验画布功能',
release: (type) => `当前${componentType[type]}未锁定,点击右上角 “锁定” 图标后编辑${componentType[type]}`,
lock: (type) =>
`当前${componentType[type]}被 ${pageInfo?.username || ''} 锁定,您可以创建新页面,如需编辑请先联系他解锁${
componentType[type]
},然后再锁定该${componentType[type]}后编辑!`
}
const renderMsg = message[pageStatus.state](pageSchema.componentName)
const showConfirm = !isSaved || pageSchema !== oldPageSchema
if (!showConfirm || showModal || (useHistory().historyState?.index === 0 && isSaved !== oldIsSaved)) {
return
}
const resetState = () => {
useHistory().go(-1, false)
useCanvas().setSaved(true)
removeNode()
}
const callback = () => {
showModal = false
if (!isSaved && oldIsSaved) {
resetState()
}
}
showModal = true
useModal().confirm({
title: '提示',
message: renderMsg,
exec: callback,
cancel: callback,
hide: () => {
showModal = false
}
})
}
)
const { multiSelectedStates } = useMultiSelect()
const multiStateLength = computed(() => multiSelectedStates.value.length)
const nodeSelected = (node, parent, type, id) => {
const { leftPanelFixed, rightPanelFixed } = getFixedPanelsStatus()
const { toolbars } = useLayout().layoutState
if (type !== 'clickTree') {
if (!leftPanelFixed) {
closePlugin()
}
if (!rightPanelFixed) {
closeSetting(true)
}
}
const { getSchema, getNodePath } = useCanvas()
const schemaItem = useCanvas().getNodeById(id)
const pageSchema = getSchema()
if (!schemaItem) {
pageSchema.id = 'body'
}
useProperties().getProps(schemaItem || pageSchema, parent)
const multiSchemas = multiSelectedStates.value.map(({ schema }) => schema)
const currentSchema = multiStateLength.value > 1 ? multiSchemas : schemaItem || pageSchema
useCanvas().setCurrentSchema(currentSchema)
footData.value = getNodePath(schemaItem?.id)
toolbars.visiblePopover = false
}
let canvasResizeObserver = null
watch(
() => [useCanvas().isCanvasApiReady.value, canvasRef.value],
([ready]) => {
if (!ready || !canvasRef.value) {
return
}
document.removeEventListener('canvasResize', useCanvas().canvasApi.value.updateRect)
canvasResizeObserver?.disconnect?.()
document.addEventListener('canvasResize', useCanvas().canvasApi.value.updateRect)
canvasResizeObserver = new ResizeObserver(useCanvas().canvasApi.value.updateRect).observe(canvasRef.value)
}
)
onUnmounted(() => {
document.removeEventListener('canvasResize', useCanvas().canvasApi.value.updateRect)
canvasResizeObserver?.disconnect?.()
})
const { addToCallbackFns: addHistoryDataChangedCallback } = (function () {
const callbackFns = new Set()
const { subscribe, unsubscribe } = useMessage()
let sub
onMounted(() => {
sub = subscribe({
topic: 'locationHistoryChanged',
subscriber: 'canvas_design_canvas_controller',
callback: (value) => callbackFns.forEach((cb) => cb(value))
})
})
onUnmounted(() => {
if (sub) {
unsubscribe(sub)
}
})
function addToCallbackFns(cb) {
callbackFns.add(cb)
return () => callbackFns.delete(cb)
}
return {
addToCallbackFns
}
})()
const baseInfoKeys = Object.keys(getMetaApi(META_SERVICE.GlobalService).getBaseInfo())
function replaceKey(key) {
const existingKey = baseInfoKeys.find((eKey) => eKey.toLowerCase() === key.toLowerCase())
if (existingKey) {
return existingKey
}
return key
}
const postUrlChanged = () => {
getMetaApi(META_SERVICE.GlobalService).postLocationHistoryChanged(
Object.fromEntries(
Array.from(new URLSearchParams(window.location.search)).map(([key, value]) => [replaceKey(key), value])
)
)
}
onMounted(() => {
window.addEventListener('popstate', postUrlChanged)
})
onUnmounted(() => {
window.removeEventListener('popstate', postUrlChanged)
useMessage().unsubscribe({
topic: 'init_canvas_deps',
subscriber: 'canvas_design_canvas'
})
})
return {
removeNode,
canvasSrc,
canvasSrcDoc,
nodeSelected,
footData,
materialsPanel,
controller: {
getMaterial: useMaterial().getMaterial,
addHistory: useHistory().addHistory,
request: getMetaApi(META_SERVICE.Http).getHttp(),
getPageById: getMetaApi(META_APP.AppManage)?.getPageById,
getPageAncestors: usePage().getAncestors,
getBaseInfo: () => getMetaApi(META_SERVICE.GlobalService).getBaseInfo(),
addHistoryDataChangedCallback,
updatePreviewId: getMetaApi(META_SERVICE.GlobalService).updatePreviewId,
ast,
getBlockByName: useMaterial().getBlockByName,
useModal,
useMessage,
useNotify,
enableTailwindCSS: getMergeMeta('engine.config')?.enableTailwindCSS
},
isBlock,
getMoveDragBarState,
CanvasLayout,
canvasRef,
CanvasContainer,
CanvasRouteBar,
CanvasBreadcrumb
}
}
}
</script>
<style lang="less" scoped>
.not-selected {
pointer-events: none;
user-select: none;
}
</style>