refactor: 调整代码风格

pull/1/head
zestack 1 year ago
parent daa880fc94
commit cf5949cb7d
  1. 22
      eslint.config.js
  2. 6
      package.json
  3. 136
      src/App.vue
  4. 7
      src/assets/vue.svg
  5. 25
      src/engineer/components/Activity.vue
  6. 2
      src/engineer/components/Builder.vue
  7. 32
      src/engineer/components/Canvas.vue
  8. 46
      src/engineer/components/Configurator.vue
  9. 12
      src/engineer/components/Designer.vue
  10. 12
      src/engineer/components/Engineer.vue
  11. 25
      src/engineer/context.ts
  12. 66
      src/engineer/render.ts
  13. 12
      src/engineer/utils/background.ts
  14. 25
      src/engineer/utils/border.ts
  15. 4
      src/engineer/utils/gap.ts
  16. 12
      src/engineer/utils/insets.ts
  17. 19
      src/engineer/utils/object.ts
  18. 12
      src/engineer/utils/radii.ts
  19. 17
      src/engineer/utils/shadow.ts
  20. 6
      src/engineer/utils/unit.ts
  21. 2
      src/engineer/views/AudioView.vue
  22. 10
      src/engineer/views/ImageView.vue
  23. 2
      src/engineer/views/TextView.vue
  24. 17
      src/engineer/views/View.vue

@ -1,22 +0,0 @@
import antfu from '@antfu/eslint-config'
export default await antfu({
stylistic: {
indent: 2,
quotes: 'single',
},
typescript: true,
vue: true,
rules: {
'vue/singleline-html-element-content-newline': 'off',
'vue/html-self-closing': ['error', {
html: {
void: 'always',
normal: 'always',
component: 'always',
},
svg: 'always',
math: 'always',
}],
},
})

@ -6,19 +6,15 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc && vite build", "build": "vue-tsc && vite build",
"preview": "vite preview", "preview": "vite preview"
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}, },
"dependencies": { "dependencies": {
"vue": "^3.3.8", "vue": "^3.3.8",
"vuedraggable": "^4.1.0" "vuedraggable": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^2.1.1",
"@types/node": "^20.9.4", "@types/node": "^20.9.4",
"@vitejs/plugin-vue": "^4.5.0", "@vitejs/plugin-vue": "^4.5.0",
"eslint": "^8.54.0",
"less": "^4.2.0", "less": "^4.2.0",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^5.0.0", "vite": "^5.0.0",

@ -1,12 +1,11 @@
<script setup lang="ts"> <script lang="ts" setup>
import { ref } from 'vue' import { ref } from 'vue'
import type { Block, Module } from './engineer' import type { Block, Module } from './engineer'
import DddBuilder, { hash } from './engineer' import DddBuilder, { hash } from './engineer'
let nextId = Date.now() let nextId = Date.now()
const blocks = ref<Block[]>([ const blocks = ref<Block[]>([{
{
vid: hash(`${++nextId}`), vid: hash(`${++nextId}`),
mid: '', mid: '',
theme: { theme: {
@ -17,8 +16,7 @@ const blocks = ref<Block[]>([
type: 'text', type: 'text',
key: 'textName', key: 'textName',
}, },
}, }, {
{
vid: hash(`${++nextId}`), vid: hash(`${++nextId}`),
mid: '', mid: '',
theme: { theme: {
@ -32,8 +30,7 @@ const blocks = ref<Block[]>([
link: 'linkKey', link: 'linkKey',
radius: 24, radius: 24,
}, },
}, }, {
{
vid: hash(`${++nextId}`), vid: hash(`${++nextId}`),
mid: '', mid: '',
theme: { theme: {
@ -49,23 +46,20 @@ const blocks = ref<Block[]>([
key: 'audioKey', key: 'audioKey',
title: 'title', title: 'title',
}, },
}, }, {
{
vid: hash(`${++nextId}`), vid: hash(`${++nextId}`),
mid: '', mid: '',
theme: { theme: {
radius: 24, radius: 24,
color: 'teal', color: 'teal',
}, },
children: [ children: [{
{
theme: { theme: {
flexible: true, flexible: true,
gap: 12, gap: 12,
crossAlign: 'center', crossAlign: 'center',
}, },
children: [ children: [{
{
theme: { theme: {
padding: 12, padding: 12,
margin: 12, margin: 12,
@ -78,8 +72,7 @@ const blocks = ref<Block[]>([
}, },
children: 'left', children: 'left',
}, },
}, }, {
{
vid: hash(`${++nextId}`), vid: hash(`${++nextId}`),
theme: { theme: {
width: 'auto', width: 'auto',
@ -91,38 +84,25 @@ const blocks = ref<Block[]>([
radius: 16, radius: 16,
}, },
children: 'right222', children: 'right222',
}, }, {
{
theme: { theme: {
width: 48, width: 48,
}, },
}, }],
], }],
}, }, ...Array.from(Array.from({length: 3})).map<Block>((_, i) => ({
],
},
...Array.from(Array.from({ length: 3 })).map<Block>((_, i) => ({
vid: hash(`${++nextId}`), vid: hash(`${++nextId}`),
mid: '', mid: '',
children: [ children: [{
{
vid: hash(`${++nextId}`), vid: hash(`${++nextId}`),
theme: { theme: {
fontSize: 22, fontSize: 22,
}, },
children: `测试${i}`, children: `测试${i}`,
}, }],
], }))])
})),
])
const categories = ref([ const categories = ref([{icon: 'trash', text: '媒体'}, {icon: 'trash', text: '图表'}, {icon: 'trash', text: '商品'}, {icon: 'trash', text: '功能'}, {icon: 'trash', text: '素材'}].map(c => ({
{ icon: 'trash', text: '媒体' },
{ icon: 'trash', text: '图表' },
{ icon: 'trash', text: '商品' },
{ icon: 'trash', text: '功能' },
{ icon: 'trash', text: '素材' },
].map(c => ({
...c, ...c,
modules: Array.from(Array.from({length: 102})).map<Module>((_, i) => ({ modules: Array.from(Array.from({length: 102})).map<Module>((_, i) => ({
vid: hash(`${++nextId}`), vid: hash(`${++nextId}`),
@ -132,117 +112,88 @@ const categories = ref([
referenceCount: 0, referenceCount: 0,
image: undefined, image: undefined,
configs: [], configs: [],
children: [ children: [{
{
vid: hash(`${++nextId}`), vid: hash(`${++nextId}`),
theme: { theme: {
height: 68, height: 68,
width: 375, width: 375,
border: {color: '#eee'}, border: {color: '#eee'},
}, },
children: [ children: [String(`${c.text}${i + 1}`)],
String(`${c.text}${i + 1}`), }],
],
},
],
})), })),
}))) })))
categories.value.unshift({ categories.value.unshift({
icon: 'trash', icon: 'trash',
text: '基础', text: '基础',
modules: [ modules: [{
{
vid: hash(`${++nextId}`), vid: hash(`${++nextId}`),
mid: hash(`${++nextId}`), mid: hash(`${++nextId}`),
title: '轮播图', title: '轮播图',
maxReferenceCount: -1, maxReferenceCount: -1,
referenceCount: 0, referenceCount: 0,
image: undefined, image: undefined,
configs: [ configs: [{
{
type: 'list', type: 'list',
field: 'items', field: 'items',
label: '', label: '',
help: '最多可添加10张图片,建议宽度750px;鼠标拖拽左侧圆点可调整图片顺序', help: '最多可添加10张图片,建议宽度750px;鼠标拖拽左侧圆点可调整图片顺序',
addable: true, addable: true,
configs: [ configs: [{
{
type: 'image', type: 'image',
field: 'image', field: 'image',
// //
inlines: [ inlines: [{
{
type: 'text', type: 'text',
field: 'title', field: 'title',
label: '标题', label: '标题',
help: '选填,不超过 4 个字', help: '选填,不超过 4 个字',
}, }, {
{
type: 'text', type: 'text',
field: 'link', field: 'link',
label: '链接', label: '链接',
// help: '', // "${label}" // help: '', // "${label}"
}, }],
],
label: '图片', label: '图片',
required: true, required: true,
}, }],
], }, {
},
{
type: 'object', type: 'object',
field: 'indicator', field: 'indicator',
label: '指示器', label: '指示器',
configs: [ configs: [{
{
type: 'mark', type: 'mark',
field: 'style', field: 'style',
label: '指示器样式', label: '指示器样式',
values: [ values: [{label: '圆形', value: 'circle'}, {label: '直线', value: 'line'}, {label: '数字', value: 'number'}],
{ label: '圆形', value: 'circle' }, }, {
{ label: '直线', value: 'line' },
{ label: '数字', value: 'number' },
],
},
{
type: 'mark', type: 'mark',
field: 'position', field: 'position',
label: '指示器位置', label: '指示器位置',
values: [ values: [{label: '居左', value: 'left'}, {label: '居中', value: 'center'}, {label: '居右', value: 'right'}],
{ label: '居左', value: 'left' }, }, {
{ label: '居中', value: 'center' },
{ label: '居右', value: 'right' },
],
},
{
type: 'color', type: 'color',
field: 'color', field: 'color',
label: '指示器颜色', label: '指示器颜色',
}, }],
], }, {
},
{
type: 'object', type: 'object',
field: 'background', field: 'background',
label: '背景', label: '背景',
configs: [ configs: [{
{
type: 'bool', type: 'bool',
field: 'enabled', field: 'enabled',
label: '是否显示背景色', label: '是否显示背景色',
}, }, {
{
type: 'background', type: 'background',
field: 'value', field: 'value',
// image // image
// //
features: ['color', 'gradient'], features: ['color', 'gradient'],
label: '背景', label: '背景',
}, }],
], }],
},
],
theme: { theme: {
height: 200, height: 200,
padding: { padding: {
@ -250,8 +201,7 @@ categories.value.unshift({
}, },
color: 'cyan', color: 'cyan',
}, },
children: [ children: [{
{
vid: hash(`${++nextId}`), vid: hash(`${++nextId}`),
theme: { theme: {
width: '100%', width: '100%',
@ -261,10 +211,8 @@ categories.value.unshift({
textAlign: 'center', textAlign: 'center',
}, },
children: '内容', children: '内容',
}, }],
], }],
},
],
}) })
const data = ref<Record<string, Record<string, unknown>>>({ const data = ref<Record<string, Record<string, unknown>>>({
@ -284,8 +232,8 @@ const data = ref<Record<string, Record<string, unknown>>>({
<template> <template>
<DddBuilder <DddBuilder
:categories="categories"
:blocks="blocks" :blocks="blocks"
:categories="categories"
:sequence="nextId" :sequence="nextId"
:sources="data" :sources="data"
/> />

@ -1 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img"
class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198">
<path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path>
<path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path>
<path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 496 B

After

Width:  |  Height:  |  Size: 475 B

@ -1,4 +1,4 @@
<script setup lang="ts"> <script lang="ts" setup>
import { ref, unref } from 'vue' import { ref, unref } from 'vue'
import Draggable from 'vuedraggable' import Draggable from 'vuedraggable'
import type { Block, Module } from '../types' import type { Block, Module } from '../types'
@ -14,19 +14,20 @@ const ctx = useContext()
// ID // ID
function clone(v: any): any { function clone(v: any): any {
if (v == null || typeof v != 'object') if (v == null || typeof v != 'object') {
return v return v
}
if (Array.isArray(v)) if (Array.isArray(v)) {
return v.slice().map(clone) return v.slice().map(clone)
}
const s = Object.create(null) const s = Object.create(null)
for (const [key, val] of Object.entries(v)) { for (const [key, val] of Object.entries(v)) {
if (key === 'vid' && typeof val === 'string' && val.length === 8) if (key === 'vid' && typeof val === 'string' && val.length === 8) {
s[key] = hash(`${++ctx.nextId}`) s[key] = hash(`${++ctx.nextId}`)
else } else {
s[key] = clone(val) s[key] = clone(val)
} }
}
return s return s
} }
@ -47,8 +48,8 @@ function cloneModule(src: Module): Block {
<div <div
v-for="(c, i) in ctx.categories" v-for="(c, i) in ctx.categories"
:key="i" :key="i"
class="ddd-activity-tabs-item"
:class="{ 'is-active': currentTab === i }" :class="{ 'is-active': currentTab === i }"
class="ddd-activity-tabs-item"
@click.stop="currentTab = i" @click.stop="currentTab = i"
> >
<!-- <Icon :name="c.icon" /> --> <!-- <Icon :name="c.icon" /> -->
@ -58,17 +59,17 @@ function cloneModule(src: Module): Block {
<template v-for="(cat, idx) in ctx.categories" :key="idx"> <template v-for="(cat, idx) in ctx.categories" :key="idx">
<Draggable <Draggable
v-show="currentTab === idx" v-show="currentTab === idx"
class="ddd-activity-tabs-content"
:list="cat.modules"
:group="{ name: 'modules', pull: 'clone' }"
:clone="cloneModule" :clone="cloneModule"
:component-dat="{ sort: false }" :component-dat="{ sort: false }"
:group="{ name: 'modules', pull: 'clone' }"
:list="cat.modules"
:sort="false" :sort="false"
class="ddd-activity-tabs-content"
item-key="id" item-key="id"
> >
<template #item="{ element }"> <template #item="{ element }">
<div class="ddd-activity-module"> <div class="ddd-activity-module">
<img v-if="element.image" class="ddd-activity-module-image" :src="element.image" /> <img v-if="element.image" :src="element.image" class="ddd-activity-module-image">
<div v-else class="ddd-activity-module-image">{{ element.title }}</div> <div v-else class="ddd-activity-module-image">{{ element.title }}</div>
<div class="ddd-activity-module-title">{{ element.title }}</div> <div class="ddd-activity-module-title">{{ element.title }}</div>
<span class="ddd-activity-module-tips">释放鼠标将组件添加到此处</span> <span class="ddd-activity-module-tips">释放鼠标将组件添加到此处</span>

@ -1,4 +1,4 @@
<script setup lang="ts"> <script lang="ts" setup>
import type { UnwrapNestedRefs } from 'vue' import type { UnwrapNestedRefs } from 'vue'
import { provide, reactive, unref } from 'vue' import { provide, reactive, unref } from 'vue'
import type { Block, Category } from '../types' import type { Block, Category } from '../types'

@ -1,4 +1,4 @@
<script setup lang="ts"> <script lang="ts" setup>
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import Draggable from 'vuedraggable' import Draggable from 'vuedraggable'
import { useContext } from '../context' import { useContext } from '../context'
@ -87,36 +87,36 @@ function handleCanvasClick(e: MouseEvent): void {
<template> <template>
<div <div
class="ddd-canvas"
:class="{ :class="{
'is-focused': ctx.canvas.focused, 'is-focused': ctx.canvas.focused,
'is-active': ctx.activeViewId === '#canvas', 'is-active': ctx.activeViewId === '#canvas',
'is-hover': ctx.hoverViewId === '#canvas', 'is-hover': ctx.hoverViewId === '#canvas',
}" }"
class="ddd-canvas"
> >
<!-- 中间自定义内容 --> <!-- 中间自定义内容 -->
<!-- @change="handleDraggingOver" --> <!-- @change="handleDraggingOver" -->
<Draggable <Draggable
:key="key"
v-model="ctx.blocks" v-model="ctx.blocks"
class="ddd-canvas-viewer" :key="key"
:animation="200" :animation="200"
:disabled="false"
:component-data="{ :component-data="{
tag: 'div', tag: 'div',
type: 'transition-group', type: 'transition-group',
}" }"
:disabled="false"
:group="{ :group="{
name: 'emulator', name: 'emulator',
put: 'modules', put: 'modules',
}" }"
class="ddd-canvas-viewer"
ghost-class="is-ghost" ghost-class="is-ghost"
item-key="id" item-key="id"
@click="handleCanvasClick"
@start="handleDragStart"
@end="handleDragEnd"
@add="handleDragAdd" @add="handleDragAdd"
@change="key++" @change="key++"
@click="handleCanvasClick"
@end="handleDragEnd"
@start="handleDragStart"
> >
<template #header> <template #header>
<div v-show="ctx.page.safeAreaInsetTop && !ctx.page.fullscreen" class="ddd-canvas-safe-area-inset-top" /> <div v-show="ctx.page.safeAreaInsetTop && !ctx.page.fullscreen" class="ddd-canvas-safe-area-inset-top" />
@ -129,7 +129,7 @@ function handleCanvasClick(e: MouseEvent): void {
</template> </template>
<template #item="{ element }"> <template #item="{ element }">
<RenderBlock class="ddd-view" :block="element" /> <RenderBlock :block="element" class="ddd-view" />
</template> </template>
</Draggable> </Draggable>
@ -137,13 +137,13 @@ function handleCanvasClick(e: MouseEvent): void {
<DddView <DddView
v-show="ctx.page.header.enabled" v-show="ctx.page.header.enabled"
vid="#header" vid="#header"
class="ddd-canvas-header"
:class="{ 'is-bordered': ctx.page.header.bordered ?? true }" :class="{ 'is-bordered': ctx.page.header.bordered ?? true }"
class="ddd-canvas-header"
> >
<div v-show="ctx.page.safeAreaInsetTop && !ctx.page.fullscreen" class="ddd-canvas-safe-area-inset-top" /> <div v-show="ctx.page.safeAreaInsetTop && !ctx.page.fullscreen" class="ddd-canvas-safe-area-inset-top" />
<div <div
class="ddd-canvas-header-title"
:style="{ textAlign: ctx.page.header.centerTitle ? 'center' : undefined }" :style="{ textAlign: ctx.page.header.centerTitle ? 'center' : undefined }"
class="ddd-canvas-header-title"
v-text="ctx.page.header.title" v-text="ctx.page.header.title"
/> />
</DddView> </DddView>
@ -162,12 +162,12 @@ function handleCanvasClick(e: MouseEvent): void {
<!-- 顶部状态栏小程序胶囊按钮底部操作指示器 --> <!-- 顶部状态栏小程序胶囊按钮底部操作指示器 -->
<template v-if="!ctx.page.fullscreen"> <template v-if="!ctx.page.fullscreen">
<div class="ddd-canvas-statusbar"> <div class="ddd-canvas-statusbar">
<img style="height:20px" alt="" src="data:image/png;base64,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" /> <img alt="" src="data:image/png;base64,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" style="height:20px">
</div> </div>
<div v-if="ctx.target === 'routine'" class="ddd-canvas-routine-button"> <div v-if="ctx.target === 'routine'" class="ddd-canvas-routine-button">
<svg width="87" height="32" viewBox="0 0 87 32" xmlns="http://www.w3.org/2000/svg"> <svg height="32" viewBox="0 0 87 32" width="87" xmlns="http://www.w3.org/2000/svg">
<rect width="87" height="32" rx="16" x="0" y="0" fill="rgba(0,0,0,0.3)" /> <rect fill="rgba(0,0,0,0.3)" height="32" rx="16" width="87" x="0" y="0" />
<rect id="rect5" width="0.5" height="18.5" fill="rgba(255,255,255,0.6)" x="43.5" y="6.5" /> <rect id="rect5" fill="rgba(255,255,255,0.6)" height="18.5" width="0.5" x="43.5" y="6.5" />
<path d="M 19.5,16.25 A 3.25,3.25 0 1 1 22.75,19.5 3.25,3.25 0 0 1 19.5,16.25 Z M 28,16.5 a 2,2 0 1 1 2,2 2,2 0 0 1 -2,-2 z m -14.5,0 a 2,2 0 1 1 2,2 2,2 0 0 1 -2,-2 z" fill="#ffffff" /> <path d="M 19.5,16.25 A 3.25,3.25 0 1 1 22.75,19.5 3.25,3.25 0 0 1 19.5,16.25 Z M 28,16.5 a 2,2 0 1 1 2,2 2,2 0 0 1 -2,-2 z m -14.5,0 a 2,2 0 1 1 2,2 2,2 0 0 1 -2,-2 z" fill="#ffffff" />
<path d="m 59.187,21.813 a 8.716,8.716 0 0 1 -2.52,-6.222 8,8 0 0 1 2.52,-5.8 8.915,8.915 0 0 1 6.223,-2.458 8.288,8.288 0 0 1 5.8,2.459 8.18,8.18 0 0 1 2.457,5.799 8.918,8.918 0 0 1 -2.46,6.223 8,8 0 0 1 -5.797,2.519 8.714,8.714 0 0 1 -6.223,-2.52 z m -0.52,-5.739 a 6.521,6.521 0 0 0 6.741,6.259 6.423,6.423 0 0 0 6.259,-6.259 6.521,6.521 0 0 0 -6.259,-6.741 6.593,6.593 0 0 0 -6.741,6.741 z m 3.5,-0.241 a 3,3 0 1 1 3,3 3,3 0 0 1 -3,-3 z" fill="#ffffff" /> <path d="m 59.187,21.813 a 8.716,8.716 0 0 1 -2.52,-6.222 8,8 0 0 1 2.52,-5.8 8.915,8.915 0 0 1 6.223,-2.458 8.288,8.288 0 0 1 5.8,2.459 8.18,8.18 0 0 1 2.457,5.799 8.918,8.918 0 0 1 -2.46,6.223 8,8 0 0 1 -5.797,2.519 8.714,8.714 0 0 1 -6.223,-2.52 z m -0.52,-5.739 a 6.521,6.521 0 0 0 6.741,6.259 6.423,6.423 0 0 0 6.259,-6.259 6.521,6.521 0 0 0 -6.259,-6.741 6.593,6.593 0 0 0 -6.741,6.741 z m 3.5,-0.241 a 3,3 0 1 1 3,3 3,3 0 0 1 -3,-3 z" fill="#ffffff" />
</svg> </svg>
@ -177,7 +177,7 @@ function handleCanvasClick(e: MouseEvent): void {
</div> </div>
</template> </template>
<style scoped lang="less"> <style lang="less" scoped>
.ddd-canvas { .ddd-canvas {
--canvas-measure-line: v-bind(measureLine); --canvas-measure-line: v-bind(measureLine);

@ -1,4 +1,4 @@
<script setup lang="ts"> <script lang="ts" setup>
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import type { ConfiguratorType } from '../context' import type { ConfiguratorType } from '../context'
import { useContext, useModule } from '../context' import { useContext, useModule } from '../context'
@ -33,8 +33,8 @@ watch(() => ctx.configurator, (v) => {
<button <button
v-for="(t) in tabs" v-for="(t) in tabs"
:key="t.key" :key="t.key"
type="button"
:class="{ 'is-active': currentTab === t.key }" :class="{ 'is-active': currentTab === t.key }"
type="button"
@click="currentTab = t.key" @click="currentTab = t.key"
v-text="t.text" v-text="t.text"
/> />
@ -44,36 +44,36 @@ watch(() => ctx.configurator, (v) => {
<legend>应用</legend> <legend>应用</legend>
<div> <div>
<label> <label>
<input v-model="ctx.target" type="radio" name="ctx.target" value="routine" /> <input v-model="ctx.target" name="ctx.target" type="radio" value="routine">
<span>微信小程序</span> <span>微信小程序</span>
</label> </label>
<label> <label>
<input v-model="ctx.target" type="radio" name="ctx.target" value="app" /> <input v-model="ctx.target" name="ctx.target" type="radio" value="app">
<span>手机APP</span> <span>手机APP</span>
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<input v-model="ctx.page.fullscreen" type="checkbox" /> <input v-model="ctx.page.fullscreen" type="checkbox">
<span>全屏模式</span> <span>全屏模式</span>
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<input v-model="ctx.page.safeAreaInsetTop" type="checkbox" /> <input v-model="ctx.page.safeAreaInsetTop" type="checkbox">
<span>开启顶部安全区域</span> <span>开启顶部安全区域</span>
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<input v-model="ctx.page.safeAreaInsetBottom" type="checkbox" /> <input v-model="ctx.page.safeAreaInsetBottom" type="checkbox">
<span>开启底部安全区域</span> <span>开启底部安全区域</span>
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<span>页面背景颜色</span> <span>页面背景颜色</span>
<input v-model="ctx.page.backgroundColor" type="color" /> <input v-model="ctx.page.backgroundColor" type="color">
</label> </label>
</div> </div>
</fieldset> </fieldset>
@ -82,54 +82,54 @@ watch(() => ctx.configurator, (v) => {
<legend>头部</legend> <legend>头部</legend>
<div> <div>
<label> <label>
<input v-model="ctx.page.header.enabled" type="checkbox" /> <input v-model="ctx.page.header.enabled" type="checkbox">
<span>开启头部功能</span> <span>开启头部功能</span>
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<span>页面标题</span> <span>页面标题</span>
<input v-model="ctx.page.header.title" type="text" placeholder="页面标题" /> <input v-model="ctx.page.header.title" placeholder="页面标题" type="text">
</label> </label>
<label> <label>
<input v-model="ctx.page.header.centerTitle" type="checkbox" /> <input v-model="ctx.page.header.centerTitle" type="checkbox">
<span>标题居中</span> <span>标题居中</span>
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<input v-model="ctx.page.header.custom" type="checkbox" /> <input v-model="ctx.page.header.custom" type="checkbox">
<span>内容自定义</span> <span>内容自定义</span>
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<span>高度</span> <span>高度</span>
<input v-model="ctx.page.header.height" type="number" min="0" placeholder="头部高度,不包括状态栏" /> <input v-model="ctx.page.header.height" min="0" placeholder="头部高度,不包括状态栏" type="number">
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<span>背景颜色</span> <span>背景颜色</span>
<input v-model="ctx.page.header.color" type="color" /> <input v-model="ctx.page.header.color" type="color">
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<span>文字颜色</span> <span>文字颜色</span>
<input v-model="ctx.page.header.textColor" type="color" /> <input v-model="ctx.page.header.textColor" type="color">
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<input v-model="ctx.page.header.bordered" type="checkbox" /> <input v-model="ctx.page.header.bordered" type="checkbox">
<span>开启下边边框</span> <span>开启下边边框</span>
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<span>下边边框颜色</span> <span>下边边框颜色</span>
<input v-model="ctx.page.header.borderColor" type="color" /> <input v-model="ctx.page.header.borderColor" type="color">
</label> </label>
</div> </div>
</fieldset> </fieldset>
@ -138,38 +138,38 @@ watch(() => ctx.configurator, (v) => {
<legend>底部</legend> <legend>底部</legend>
<div> <div>
<label> <label>
<input v-model="ctx.page.footer.enabled" type="checkbox" /> <input v-model="ctx.page.footer.enabled" type="checkbox">
<span>开启底部功能</span> <span>开启底部功能</span>
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<span>高度</span> <span>高度</span>
<input v-model="ctx.page.footer.height" type="number" min="0" placeholder="头部高度,不包括状态栏" /> <input v-model="ctx.page.footer.height" min="0" placeholder="头部高度,不包括状态栏" type="number">
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<span>背景颜色</span> <span>背景颜色</span>
<input v-model="ctx.page.footer.color" type="color" /> <input v-model="ctx.page.footer.color" type="color">
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<span>文字颜色</span> <span>文字颜色</span>
<input v-model="ctx.page.footer.textColor" type="color" /> <input v-model="ctx.page.footer.textColor" type="color">
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<input v-model="ctx.page.footer.bordered" type="checkbox" /> <input v-model="ctx.page.footer.bordered" type="checkbox">
<span>开启上边框</span> <span>开启上边框</span>
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<span>上边框颜色</span> <span>上边框颜色</span>
<input v-model="ctx.page.footer.borderColor" type="color" /> <input v-model="ctx.page.footer.borderColor" type="color">
</label> </label>
</div> </div>
</fieldset> </fieldset>

@ -1,4 +1,4 @@
<script setup lang="ts"> <script lang="ts" setup>
import { ref } from 'vue' import { ref } from 'vue'
import { useContext, useScale } from '../context' import { useContext, useScale } from '../context'
import { DddView } from '../views' import { DddView } from '../views'
@ -42,12 +42,12 @@ function onMouseup(): void {
function onMousewheel(e: WheelEvent): void { function onMousewheel(e: WheelEvent): void {
if (e.ctrlKey || e.metaKey) { if (e.ctrlKey || e.metaKey) {
if (e.deltaY < 0) if (e.deltaY < 0) {
scale.incr() scale.incr()
else } else {
scale.decr() scale.decr()
} }
else { } else {
x.value -= e.deltaX x.value -= e.deltaX
y.value -= e.deltaY y.value -= e.deltaY
} }
@ -73,10 +73,10 @@ function onMouseover(e: MouseEvent): void {
<template> <template>
<div <div
class="ddd-designer" class="ddd-designer"
@mousedown.left="onMousedown" @mouseleave="onMouseup"
@mousemove="onMousemove" @mousemove="onMousemove"
@mouseup="onMouseup" @mouseup="onMouseup"
@mouseleave="onMouseup" @mousedown.left="onMousedown"
@wheel.passive="onMousewheel" @wheel.passive="onMousewheel"
> >
<DddView <DddView

@ -1,4 +1,4 @@
<script setup lang="ts"> <script lang="ts" setup>
import { useContext, useScale } from '../context' import { useContext, useScale } from '../context'
import Activity from './Activity.vue' import Activity from './Activity.vue'
import Configurator from './Configurator.vue' import Configurator from './Configurator.vue'
@ -20,17 +20,15 @@ const scale = useScale()
</div> </div>
<div style="display: flex;align-items: center"> <div style="display: flex;align-items: center">
<button <button
type="button"
:disabled="ctx.canvas.scale <= scale.min" :disabled="ctx.canvas.scale <= scale.min"
type="button"
@click="scale.decr()" @click="scale.decr()"
v-text="'-'" v-text="'-'"
/> />
<span <span style="display: inline-block;width:5em;text-align: center">{{ Math.ceil(ctx.canvas.scale * 100) }}%</span>
style="display: inline-block;width: 5em;text-align: center"
>{{ Math.ceil(ctx.canvas.scale * 100) }}%</span>
<button <button
type="button"
:disabled="ctx.canvas.scale >= scale.max" :disabled="ctx.canvas.scale >= scale.max"
type="button"
@click="scale.incr()" @click="scale.incr()"
v-text="'+'" v-text="'+'"
/> />
@ -53,7 +51,7 @@ const scale = useScale()
</div> </div>
</template> </template>
<style scoped lang="less"> <style lang="less" scoped>
.ddd-engineer { .ddd-engineer {
width: 100%; width: 100%;
height: 100%; height: 100%;

@ -11,7 +11,8 @@ export interface CanvasConfig {
scale: number scale: number
} }
export interface PageConfig extends Omit<Page, 'body'> {} export interface PageConfig extends Omit<Page, 'body'> {
}
export type ConfiguratorType = export type ConfiguratorType =
| 'page' | 'page'
@ -58,9 +59,9 @@ const blockIdKey: InjectionKey<string> = Symbol.for('ddd:block:id')
export function useContext(): UnwrapNestedRefs<EngineContext> { export function useContext(): UnwrapNestedRefs<EngineContext> {
const config = inject(contextKey) const config = inject(contextKey)
if (config == null) if (config == null) {
throw new Error('no config found') throw new Error('no config found')
}
return config return config
} }
@ -69,9 +70,10 @@ export function useParentViewId(): string | undefined {
} }
export function provideParentViewId(id: string | undefined): void { export function provideParentViewId(id: string | undefined): void {
if (id) if (id) {
provide(parentViewIdKey, id) provide(parentViewIdKey, id)
} }
}
export function useBlockId(): string | undefined { export function useBlockId(): string | undefined {
return inject(blockIdKey) return inject(blockIdKey)
@ -83,17 +85,19 @@ export function provideBlockId(id: string): void {
export function useModule() { export function useModule() {
const ctx = useContext() const ctx = useContext()
return computed((): Module | undefined => { return computed((): Module | undefined => {
for (const block of ctx.blocks) { for (const block of ctx.blocks) {
if (block.vid !== ctx.activeBlockId) if (block.vid !== ctx.activeBlockId) {
continue continue
}
for (const category of ctx.categories) { for (const category of ctx.categories) {
for (const module of category.modules) { for (const module of category.modules) {
if (module.mid === block.mid) if (module.mid === block.mid) {
return module return module
} }
} }
}
break break
} }
return undefined return undefined
@ -128,11 +132,14 @@ export function useScale() {
export function useSource<T = unknown>(source: string | undefined, fallback?: T): ComputedRef<T | undefined> { export function useSource<T = unknown>(source: string | undefined, fallback?: T): ComputedRef<T | undefined> {
const ctx = useContext() const ctx = useContext()
const blockId = useBlockId() const blockId = useBlockId()
return computed<T | undefined>(() => { return computed<T | undefined>(() => {
if (!blockId) if (!blockId) {
throw new Error('without block') throw new Error('without block')
if (!source) }
if (!source) {
return fallback return fallback
}
return valueOf(ctx.sources[blockId], source) ?? fallback return valueOf(ctx.sources[blockId], source) ?? fallback
}) })
} }

@ -1,32 +1,8 @@
import type { import type { Component, CSSProperties, Prop, VNodeChild, } from 'vue'
CSSProperties,
Component,
Prop,
VNodeChild,
} from 'vue'
import { defineComponent, h } from 'vue' import { defineComponent, h } from 'vue'
import type { import type { Axis, Block, DataRefer, Theme, View, ViewChildren, Widget, } from './types'
Axis,
Block,
DataRefer,
Theme,
View,
ViewChildren,
Widget,
} from './types'
import { provideBlockId } from './context' import { provideBlockId } from './context'
import { import { align, background, bordering, clip, gap, insets, radii, shadowing, size, unit, } from './utils'
align,
background,
bordering,
clip,
gap,
insets,
radii,
shadowing,
size,
unit,
} from './utils'
import DddView from './views/View.vue' import DddView from './views/View.vue'
import DddTextView from './views/TextView.vue' import DddTextView from './views/TextView.vue'
import DddImageView from './views/ImageView.vue' import DddImageView from './views/ImageView.vue'
@ -50,15 +26,16 @@ const views: Record<string, Component> = {
} }
export function registerWidget(some: Record<string, WidgetRenderFunction>): void { export function registerWidget(some: Record<string, WidgetRenderFunction>): void {
for (const [key, func] of Object.entries(some)) for (const [key, func] of Object.entries(some)) {
widgets[key] = func widgets[key] = func
} }
}
function renderWidget(widget: Widget, view: View, axis?: Axis): VNodeChild { function renderWidget(widget: Widget, view: View, axis?: Axis): VNodeChild {
const func = widgets[widget.name] const func = widgets[widget.name]
if (typeof func !== 'function') if (typeof func !== 'function') {
throw new Error(`unknown widget: ${widget.name}`) throw new TypeError(`unknown widget: ${widget.name}`)
}
return func(widget, view, axis) return func(widget, view, axis)
} }
@ -99,21 +76,21 @@ export function isFlexible(theme: Theme) {
} }
export function render(view?: ViewChildren, axis?: Axis): VNodeChild { export function render(view?: ViewChildren, axis?: Axis): VNodeChild {
if (view == null) if (view == null) {
return null return null
}
if (typeof view === 'string' || typeof view === 'number') if (typeof view === 'string' || typeof view === 'number') {
return view return view
}
if (Array.isArray(view)) if (Array.isArray(view)) {
return view.map(v => render(v, axis)) return view.map(v => render(v, axis))
}
if (isRefer(view)) if (isRefer(view)) {
return renderRefer(view) return renderRefer(view)
}
if (isWidget(view.theme)) if (isWidget(view.theme)) {
return renderWidget(view.theme, view, axis) return renderWidget(view.theme, view, axis)
}
const theme = view.theme const theme = view.theme
const css: CSSProperties = { const css: CSSProperties = {
// boxed & spatial // boxed & spatial
@ -133,11 +110,9 @@ export function render(view?: ViewChildren, axis?: Axis): VNodeChild {
// clip // clip
...clip(theme?.clip), ...clip(theme?.clip),
} }
// note: 在前面初始化 css 时使用了参数 axis, // note: 在前面初始化 css 时使用了参数 axis,
// 所以在这里复用它用于子视图构建 // 所以在这里复用它用于子视图构建
axis = theme?.axis axis = theme?.axis
if (theme?.flexible || (theme && theme.flexible == null && isFlexible(theme))) { if (theme?.flexible || (theme && theme.flexible == null && isFlexible(theme))) {
Object.assign(css, { Object.assign(css, {
display: 'flex', display: 'flex',
@ -148,7 +123,6 @@ export function render(view?: ViewChildren, axis?: Axis): VNodeChild {
...gap(theme?.gap), ...gap(theme?.gap),
}) })
} }
return h(DddView, { return h(DddView, {
vid: view.vid, vid: view.vid,
class: 'ddd-view', class: 'ddd-view',
@ -169,9 +143,9 @@ export const RenderBlock = defineComponent({
setup(props) { setup(props) {
provideBlockId(props.block!.vid) provideBlockId(props.block!.vid)
return () => { return () => {
if (props.block == null) if (props.block == null) {
return null return null
}
return render(props.block) return render(props.block)
} }
}, },

@ -2,15 +2,15 @@ import type { CSSProperties } from 'vue'
import type { Gradient } from '../types' import type { Gradient } from '../types'
function image(img: string | undefined, g: Gradient | undefined): string | undefined { function image(img: string | undefined, g: Gradient | undefined): string | undefined {
if (img == null && g == null) if (img == null && g == null) {
return undefined return undefined
}
if (img != null) if (img != null) {
return `url(${img})` return `url(${img})`
}
if (g == null) if (g == null) {
return undefined return undefined
}
const prefix = g.repeatable ? 'repeating-' : '' const prefix = g.repeatable ? 'repeating-' : ''
if (g.stops?.length === g.colors.length) { if (g.stops?.length === g.colors.length) {
const colors = g.colors.map((c, i) => `${c} ${g.stops![i]}`).join(', ') const colors = g.colors.map((c, i) => `${c} ${g.stops![i]}`).join(', ')

@ -4,36 +4,41 @@ import { unit } from './unit'
import { isBorderSide, isSymmetric } from './is' import { isBorderSide, isSymmetric } from './is'
function borderSide(name: string, s: BorderSide | undefined): CSSProperties | undefined { function borderSide(name: string, s: BorderSide | undefined): CSSProperties | undefined {
if (s == null) if (s == null) {
return undefined return undefined
}
const hasWidth = s.width != null const hasWidth = s.width != null
const hasColor = s.color != null const hasColor = s.color != null
const hasStyle = s.style != null const hasStyle = s.style != null
const r: Record<string, string | undefined> = {} const r: Record<string, string | undefined> = {}
if (hasColor)
if (hasColor) {
r[`${name}Color`] = s.color r[`${name}Color`] = s.color
}
if (hasWidth) if (hasWidth) {
r[`${name}Width`] = unit(s.width) r[`${name}Width`] = unit(s.width)
else if (hasColor) } else if (hasColor) {
r[`${name}Width`] = '1px' r[`${name}Width`] = '1px'
}
if (hasStyle) if (hasStyle) {
r[`${name}Style`] = s.style r[`${name}Style`] = s.style
else if (hasWidth || hasColor) } else if (hasWidth || hasColor) {
r[`${name}Style`] = 'solid' r[`${name}Style`] = 'solid'
}
return r return r
} }
export function bordering(b: Border | undefined): CSSProperties | undefined { export function bordering(b: Border | undefined): CSSProperties | undefined {
if (b == null) if (b == null) {
return undefined return undefined
}
if (isBorderSide(b)) if (isBorderSide(b)) {
return borderSide('border', b) return borderSide('border', b)
}
if (isSymmetric(b)) { if (isSymmetric(b)) {
return { return {
...borderSide('borderBlock', b.vertical), ...borderSide('borderBlock', b.vertical),

@ -5,15 +5,13 @@ export function gap(gap: number | [number, number] | undefined): CSSProperties |
if (!gap) { if (!gap) {
return undefined return undefined
} }
else if (Array.isArray(gap)) { if (Array.isArray(gap)) {
return { return {
rowGap: unit(gap[0]), rowGap: unit(gap[0]),
columnGap: unit(gap[1]), columnGap: unit(gap[1]),
} }
} }
else {
return { return {
gap: unit(gap), gap: unit(gap),
} }
} }
}

@ -4,15 +4,15 @@ import { isSymmetric } from './is'
import { unit } from './unit' import { unit } from './unit'
export function insets(name: string, i: EdgeInsets | undefined): CSSProperties | undefined { export function insets(name: string, i: EdgeInsets | undefined): CSSProperties | undefined {
if (i == null) if (i == null) {
return undefined return undefined
}
if (typeof i === 'number') if (typeof i === 'number') {
return {[name]: `${i}px`} return {[name]: `${i}px`}
}
if (typeof i === 'string') if (typeof i === 'string') {
return {[name]: i} return {[name]: i}
}
if (isSymmetric(i)) { if (isSymmetric(i)) {
return { return {
[name]: [unit(i.vertical), unit(i.horizontal)].join(' '), [name]: [unit(i.vertical), unit(i.horizontal)].join(' '),

@ -1,23 +1,24 @@
function find(obj: any, keys: string[]) { function find(obj: any, keys: string[]) {
if (!keys.length) if (!keys.length) {
return obj return obj
}
if (obj == null || typeof obj !== 'object') if (obj == null || typeof obj !== 'object') {
return undefined return undefined
}
const key = keys.shift()! const key = keys.shift()!
if (!Array.isArray(obj)) if (!Array.isArray(obj)) {
return find(obj[key], keys) return find(obj[key], keys)
}
const index = Number.parseInt(key) const index = Number.parseInt(key)
if (Number.isNaN(index)) if (Number.isNaN(index)) {
return undefined return undefined
}
return find(obj[index], keys) return find(obj[index], keys)
} }
export function valueOf(obj: any, key: string) { export function valueOf(obj: any, key: string) {
if (!key) if (!key) {
return undefined return undefined
}
return find(obj, key.split('.')) return find(obj, key.split('.'))
} }

@ -3,19 +3,19 @@ import type { Radius, RadiusDouble, RadiusPrimitive } from '../types'
import { isCorners } from './is' import { isCorners } from './is'
function stringify(radius: RadiusPrimitive | RadiusDouble | undefined): string | undefined { function stringify(radius: RadiusPrimitive | RadiusDouble | undefined): string | undefined {
if (radius == null) if (radius == null) {
return undefined return undefined
}
if (Array.isArray(radius)) if (Array.isArray(radius)) {
return radius.map(stringify).join(' ') return radius.map(stringify).join(' ')
}
return `${radius}px` return `${radius}px`
} }
export function radii(radius: Radius | undefined): CSSProperties | undefined { export function radii(radius: Radius | undefined): CSSProperties | undefined {
if (radius == null) if (radius == null) {
return undefined return undefined
}
if (isCorners(radius)) { if (isCorners(radius)) {
return { return {
borderTopLeftRadius: stringify(radius.topLeft), borderTopLeftRadius: stringify(radius.topLeft),

@ -4,25 +4,18 @@ import type { Shadow } from '../types'
const unit = (n: number | undefined): string | undefined => n == null ? undefined : n === 0 ? n.toString() : `${n}px` const unit = (n: number | undefined): string | undefined => n == null ? undefined : n === 0 ? n.toString() : `${n}px`
function stringify(s: Shadow | undefined): string | undefined { function stringify(s: Shadow | undefined): string | undefined {
if (s == null) if (s == null) {
return undefined return undefined
}
return [ return [s.inset ? 'inset' : undefined, unit(s.x), unit(s.y), s.blur != null ? unit(s.blur) : s.spread != null ? '0' : undefined, unit(s.spread), s.color]
s.inset ? 'inset' : undefined,
unit(s.x),
unit(s.y),
s.blur != null ? unit(s.blur) : s.spread != null ? '0' : undefined,
unit(s.spread),
s.color,
]
.filter(n => n != null) .filter(n => n != null)
.join(' ') .join(' ')
} }
export function shadowing(ss: Shadow | Shadow[] | undefined): CSSProperties | undefined { export function shadowing(ss: Shadow | Shadow[] | undefined): CSSProperties | undefined {
if (ss == null) if (ss == null) {
return undefined return undefined
}
if (!Array.isArray(ss)) { if (!Array.isArray(ss)) {
return { return {
boxShadow: stringify(ss), boxShadow: stringify(ss),

@ -1,7 +1,9 @@
export function unit(n: number | undefined | string) { export function unit(n: number | undefined | string) {
if (n == null) if (n == null) {
return undefined return undefined
if (typeof n !== 'number') }
if (typeof n !== 'number') {
return n return n
}
return `${n}px` return `${n}px`
} }

@ -1,4 +1,4 @@
<script setup lang="ts"> <script lang="ts" setup>
import { useSource } from '../context' import { useSource } from '../context'
defineOptions({ defineOptions({

@ -1,4 +1,4 @@
<script setup lang="ts"> <script lang="ts" setup>
import type { ObjectFit, Radius } from '../types' import type { ObjectFit, Radius } from '../types'
import { useSource } from '../context' import { useSource } from '../context'
import { radii } from '../utils' import { radii } from '../utils'
@ -23,16 +23,12 @@ const title = useSource<string>(props.title)
<template> <template>
<img <img
:src="src"
:alt="title" :alt="title"
:src="src"
:style="{ :style="{
objectFit: fit ?? 'contain', objectFit: fit ?? 'contain',
maxWidth: '100%', maxWidth: '100%',
...radii(radius), ...radii(radius),
}" }"
/> >
</template> </template>
<style scoped lang="less">
</style>

@ -1,4 +1,4 @@
<script setup lang="ts"> <script lang="ts" setup>
import { useSource } from '../context' import { useSource } from '../context'
defineOptions({ defineOptions({

@ -1,11 +1,6 @@
<script setup lang="ts"> <script lang="ts" setup>
import type { Component } from 'vue' import type { Component } from 'vue'
import { import { provideParentViewId, useBlockId, useContext, useParentViewId, } from '../context'
provideParentViewId,
useBlockId,
useContext,
useParentViewId,
} from '../context'
defineOptions({ defineOptions({
name: 'DddView', name: 'DddView',
@ -24,8 +19,9 @@ function handleMousedown(e: Event, id: string | undefined): void {
ctx.activeBlockId = blockId ctx.activeBlockId = blockId
ctx.hoverViewId = parentId ctx.hoverViewId = parentId
ctx.canvas.focused = false ctx.canvas.focused = false
if (blockId) if (blockId) {
ctx.configurator = 'block' ctx.configurator = 'block'
}
if (id) { if (id) {
e.stopPropagation() e.stopPropagation()
@ -47,9 +43,10 @@ function handleMousedown(e: Event, id: string | undefined): void {
} }
function handleMouseleave(id: string | undefined): void { function handleMouseleave(id: string | undefined): void {
if (id && ctx.hoverViewId === id && !ctx.draggingViewId) if (id && ctx.hoverViewId === id && !ctx.draggingViewId) {
ctx.hoverViewId = undefined ctx.hoverViewId = undefined
} }
}
provideParentViewId(props.vid) provideParentViewId(props.vid)
</script> </script>
@ -57,12 +54,12 @@ provideParentViewId(props.vid)
<template> <template>
<component <component
:is="is ?? 'div'" :is="is ?? 'div'"
class="ddd-view"
:class="{ :class="{
'is-active': vid && ctx.activeViewId === vid, 'is-active': vid && ctx.activeViewId === vid,
'is-hover': vid && ctx.hoverViewId === vid, 'is-hover': vid && ctx.hoverViewId === vid,
}" }"
:data-view-id="vid" :data-view-id="vid"
class="ddd-view"
@mouseleave="handleMouseleave(vid)" @mouseleave="handleMouseleave(vid)"
@mousedown.left="handleMousedown($event, vid)" @mousedown.left="handleMousedown($event, vid)"
> >

Loading…
Cancel
Save