Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/canvas/DesignCanvas/src/DesignCanvas.vue
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ export default {
const schemaItem = useCanvas().getNodeById(id)

const pageSchema = getSchema()
if (!schemaItem) {
pageSchema.id = 'body'
}

// 如果选中的节点是画布,就设置成默认选中最外层schema
useProperties().getProps(schemaItem || pageSchema, parent)
Expand Down
34 changes: 14 additions & 20 deletions packages/canvas/container/src/CanvasContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,15 @@ import { onMounted, ref, computed, onUnmounted, watch, watchEffect } from 'vue'
import { iframeMonitoring } from '@opentiny/tiny-engine-common/js/monitor'
import { useTranslate, useCanvas, useMessage, useResource } from '@opentiny/tiny-engine-meta-register'
import { NODE_UID, NODE_LOOP, DESIGN_MODE } from '../../common'
import { registerHotkeyEvent, removeHotkeyEvent, multiSelectedStates } from './keyboard'
import { registerHotkeyEvent, removeHotkeyEvent } from './keyboard'
import CanvasMenu, { closeMenu, openMenu } from './components/CanvasMenu.vue'
import CanvasAction from './components/CanvasAction.vue'
import CanvasRouterJumper from './components/CanvasRouterJumper.vue'
import CanvasViewerSwitcher from './components/CanvasViewerSwitcher.vue'
import CanvasResize from './components/CanvasResize.vue'
import CanvasDivider from './components/CanvasDivider.vue'
import CanvasResizeBorder from './components/CanvasResizeBorder.vue'
import { useMultiSelect } from './composables/useMultiSelect'
import {
canvasState,
onMouseUp,
Expand All @@ -66,18 +67,15 @@ import {
selectState,
lineState,
removeNodeById,
updateRect,
syncNodeScroll,
getElement,
dragStart,
selectNode,
initCanvas,
clearLineState,
querySelectById,
getCurrent,
canvasApi,
getMultiState,
setMultiState,
handleMultiState
canvasApi
} from './container'

export default {
Expand Down Expand Up @@ -109,9 +107,10 @@ export default {
const containerPanel = ref(null)
const insertContainer = ref(false)

const multiStateLength = computed(() => multiSelectedStates.value.length)
const { multiSelectedStates, multiStateLength, setMultiSelection, getMultiSelectionState, toggleMultiSelection } =
useMultiSelect()

const setCurrentNode = async (event, doc = null) => {
const setCurrentNode = async (event) => {
const { clientX, clientY } = event
const element = getElement(event.target)
closeMenu()
Expand All @@ -121,8 +120,10 @@ export default {
const currentElement = querySelectById(getCurrent().schema?.id)

if (!currentElement?.contains(element) || event.button === 0) {
const selectedState = getMultiState(element, doc)
setMultiState(multiSelectedStates, selectedState)
const selectedState = getMultiSelectionState(element)
if (selectedState) {
setMultiSelection(selectedState)
}

const loopId = element.getAttribute(NODE_LOOP)
if (loopId) {
Expand Down Expand Up @@ -210,18 +211,11 @@ export default {
return
}

// 多选组合键触发
if (element) {
const selectedState = getMultiState(element, doc)
if ((event.ctrlKey || event.metaKey) && event.button === 0) {
handleMultiState(multiSelectedStates, selectedState)
return
}
}
if (toggleMultiSelection(event, element)) return

insertPosition.value = false
insertContainer.value = false
setCurrentNode(event, doc)
setCurrentNode(event)
target.value = event.target
})
})
Expand Down Expand Up @@ -265,7 +259,7 @@ export default {

registerHotkeyEvent(doc)

win.addEventListener('scroll', updateRect, true)
win.addEventListener('scroll', syncNodeScroll, true)
}
}
// 设置弹窗
Expand Down
126 changes: 126 additions & 0 deletions packages/canvas/container/src/composables/useMultiSelect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { ref, computed, toRaw } from 'vue'
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
import { NODE_TAG, NODE_UID } from '../../../common'
import { getRect, getDocument } from '../container'

const initMultiState = { id: 'body' }

// 初始化多选节点
const multiSelectedStates = ref([])

// 节点位置缓存
let nodeRectCache = new WeakMap()

// 获取带缓存的节点位置
const getCachedRect = (element) => {
if (nodeRectCache.has(element)) {
return nodeRectCache.get(element)
}
const rect = getRect(element)
nodeRectCache.set(element, rect)
return rect
}

export const useMultiSelect = () => {
// 记录最后选择的节点
const lastSelectedNode = ref(null)

const multiStateLength = computed(() => multiSelectedStates.value.length)

// 初始化多选节点
const initMultiSelect = () => {
multiSelectedStates.value = [initMultiState]
}

// 设置多选节点
const setMultiSelection = (nodes) => {
if (Array.isArray(nodes)) {
multiSelectedStates.value = nodes
} else if (nodes && typeof nodes === 'object') {
multiSelectedStates.value = [nodes]
} else {
multiSelectedStates.value = []
}
}

// 添加节点到多选列表
const addMultiSelection = (node) => {
if (!node || typeof node !== 'object') return

if (!multiSelectedStates.value.some((state) => state.id === node.id)) {
multiSelectedStates.value.push(node)
}
}

// 获取多选节点(带缓存)
const getMultiSelectionState = (element) => {
if (!element) {
return null
}

// 使用缓存的位置信息
const { top, left, width, height } = getCachedRect(element)
const nodeTag = element?.getAttribute(NODE_TAG)
const nodeId = element?.getAttribute(NODE_UID) || 'body'

// 获取节点信息
const { node } = useCanvas().getNodeWithParentById(nodeId) || {}
lastSelectedNode.value = nodeId

return {
id: nodeId,
componentName: nodeTag,
doc: getDocument(element),
top,
left,
width,
height,
schema: toRaw(node)
}
}

const clearMultiSelection = () => {
multiSelectedStates.value = []
lastSelectedNode.value = null
nodeRectCache = new WeakMap() // 清空缓存
}

// 处理多选节点
const toggleMultiSelection = (event, element) => {
const isCtrlKey = event.ctrlKey || event.metaKey
const selectState = getMultiSelectionState(element)

if (!selectState) {
return false // 如果没有有效的 selectState,返回 false
}

const nodeId = selectState?.id
const isExistNode = multiSelectedStates.value.some((state) => state.id === nodeId)

if (isCtrlKey && event.button === 0) {
// 按住Ctrl或Meta键时,切换多选状态
if (isExistNode && nodeId) {
const exList = toRaw(multiSelectedStates.value).filter((state) => state.id !== nodeId)
setMultiSelection(exList)
} else {
addMultiSelection(selectState)
}
return true
} else {
// 没有按住Ctrl或Meta键时,清除所有多选状态并添加当前节点
clearMultiSelection()
addMultiSelection(selectState)
return false
}
}

return {
multiSelectedStates,
multiStateLength,
initMultiSelect,
setMultiSelection,
getMultiSelectionState,
toggleMultiSelection,
clearMultiSelection
}
}
92 changes: 33 additions & 59 deletions packages/canvas/container/src/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { useCanvas, useLayout, useTranslate, useMaterial } from '@opentiny/tiny-
import { utils } from '@opentiny/tiny-engine-utils'
import { isVsCodeEnv } from '@opentiny/tiny-engine-common/js/environments'
import Builtin from '../../render/src/builtin/builtin.json' //TODO 画布内外应该分开
import { useMultiSelect } from './composables/useMultiSelect'

export const POSITION = Object.freeze({
TOP: 'top',
Expand Down Expand Up @@ -123,63 +124,6 @@ export const lineState = reactive({
...initialLineState
})

// 获取多选节点
export const getMultiState = (element, doc) => {
const { top, left, width, height } = element.getBoundingClientRect()
const nodeTag = element?.getAttribute(NODE_TAG)
const nodeId = element?.getAttribute(NODE_UID)

const { node, parent } = useCanvas().getNodeWithParentById(nodeId) || {}

if (node && parent) {
return {
id: nodeId,
componentName: nodeTag,
doc,
top,
left,
width,
height,
schema: toRaw(node),
parent: toRaw(parent)
}
}
}

// 设置多选节点
export function setMultiState(multiSelectedStates, node, append = false) {
if (!node || typeof node !== 'object') {
multiSelectedStates.value = []
return
}

if (append) {
const nodeIds = new Set(multiSelectedStates.value.map((state) => state.id))
if (!nodeIds.has(node.id)) {
multiSelectedStates.value = [...toRaw(multiSelectedStates.value), node]
}
} else {
if (Array.isArray(node)) {
multiSelectedStates.value = node
} else {
multiSelectedStates.value = [node]
}
}
}

// 处理多选节点
export function handleMultiState(multiSelectedStates, selectState) {
const nodeId = selectState?.id
const isExistNode = multiSelectedStates.value.map((state) => state.id).includes(nodeId)

if (nodeId && isExistNode) {
const exList = multiSelectedStates.value.filter((state) => state.id !== nodeId)
setMultiState(multiSelectedStates, exList)
} else {
setMultiState(multiSelectedStates, selectState, true)
}
}

export const clearHover = () => {
Object.assign(hoverState, initialRectState, { slot: null })
Object.assign(inactiveHoverState, initialRectState, { slot: null })
Expand Down Expand Up @@ -312,7 +256,7 @@ export const getInactiveElement = (element) => {
return undefined
}

const getRect = (element) => {
export const getRect = (element) => {
if (element === getDocument().body) {
const { innerWidth: width, innerHeight: height } = getWindow()
return {
Expand All @@ -328,6 +272,7 @@ const getRect = (element) => {
}
return element.getBoundingClientRect()
}

const insertAfter = ({ parent, node, data }) => {
if (!data.id) {
data.id = utils.guid()
Expand Down Expand Up @@ -446,7 +391,10 @@ export const scrollToNode = (element) => {

return nextTick()
}
const setSelectRect = (element) => {

const { clearMultiSelection, initMultiSelect, multiSelectedStates, multiStateLength } = useMultiSelect()

const setSelectRect = (element, multiNodeId) => {
element = element || getDocument().body

const { left, height, top, width } = getRect(element)
Expand All @@ -460,6 +408,14 @@ const setSelectRect = (element) => {
componentName,
doc: getDocument()
})

if (multiNodeId) {
multiSelectedStates.value.map((state) => {
if (state.id === multiNodeId) {
return Object.assign(state, selectState)
}
})
}
}

export const updateRect = (id) => {
Expand All @@ -477,6 +433,18 @@ export const updateRect = (id) => {
}
}

export const syncNodeScroll = (id) => {
if (multiStateLength.value > 1) {
multiSelectedStates.value.forEach((state) => {
const multiNodeId = state.id
const element = querySelectById(multiNodeId)
setTimeout(() => setSelectRect(element, multiNodeId))
})
} else {
updateRect(id)
}
}

export const getConfigure = (targetName) => {
const material = getController().getMaterial(targetName)

Expand Down Expand Up @@ -772,6 +740,11 @@ export const selectNode = async (id, type) => {
canvasState.loopId = loopId
}

if (type === 'clickTree') {
clearMultiSelection()
initMultiSelect()
}

const { node, parent } = useCanvas().getNodeWithParentById(id) || {}

let element = querySelectById(id, type)
Expand Down Expand Up @@ -930,6 +903,7 @@ export const canvasDispatch = (name, data, doc = getDocument()) => {
export const canvasApi = {
dragStart,
updateRect,
syncNodeScroll,
dragMove,
setLocales,
getRenderer,
Expand Down
Loading