Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat editor group #228

Closed
wants to merge 21 commits into from
Closed
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
271 changes: 271 additions & 0 deletions src/components/contextMenu/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
<template>
<ul
ref="mouseMenuRef"
class="menu-wrap"
:style="{
visibility: show,
left,
top,
zIndex,
}"
@click="handleMenu"
>
<menu-item v-for="menu in menuList" :key="menu.activeName" :nodeInfo="menu" />
</ul>
</template>

<script>
import { isEmpty, debounce } from 'lodash-es';
import select from '@/mixins/select';
import menuItem from './menuItem.vue';

const canvasDom = document.getElementById('canvas') || null;
export default {
name: 'mouseMenu',
inject: ['canvas', 'fabric'],
mixins: [select],
data() {
return {
show: 'hidden',
left: 0,
top: 0,
zIndex: -100,
menu: null,
menuList: [
// 菜单
{
type: 'copy',
activeName: 'copy',
text: this.$t('mouseMenu.copy'),
subText: 'Copy',
},
{
type: 'group',
activeName: 'group',
text: this.$t('mouseMenu.group'),
subText: 'Group',
},
// 对齐
{
type: 'center',
activeName: 'center',
text: this.$t('mouseMenu.center'),
subText: 'Center',
},
// 排序
{
type: 'sort',
activeName: '',
text: this.$t('mouseMenu.layer'),
subText: '',
children: [
{
type: 'sort',
activeName: 'up',
text: this.$t('mouseMenu.up'),
subText: 'Up',
},
{
type: 'sort',
activeName: 'down',
text: this.$t('mouseMenu.down'),
subText: 'Down',
},
{
type: 'sort',
activeName: 'upTop',
text: this.$t('mouseMenu.upTop'),
subText: 'BringToFront',
},
{
type: 'sort',
activeName: 'downTop',
text: this.$t('mouseMenu.downTop'),
subText: 'SendToBack',
},
],
},
// 删除
{
type: 'delete',
activeName: 'delete',
text: this.$t('mouseMenu.delete'),
subText: 'Delete',
},
],
};
},
components: {
menuItem,
},
computed: {
// 单选且等于组元素
isGroup() {
return this.mSelectMode === 'one' && this.mSelectOneType === 'group';
},
// 是否为多选
isMultiple() {
return this.mSelectMode === 'multiple';
},
},
mounted() {
this.$nextTick(() => {
this.menu = this.$refs.mouseMenuRef;
this.menu && (this.menu.oncontextmenu = (e) => e.preventDefault());
this.init();
});
// 监听点击 隐藏(右键点击外部和fabric右键有冲突,因为点击非canvas只有点击左键才可以隐藏)
window.addEventListener('click', debounce(this.clickHide, 200));
},

beforeMount() {
window.removeEventListener('click', this.clickHide);
},

methods: {
init() {
if (!isEmpty(this.canvas) && !isEmpty(this.canvas.c)) {
this.canvas.c.on('mouse:down', this.handleMouseUp);
} else {
this.hideMenu();
}
},

handleMouseUp(opt) {
try {
const canvas = this.canvas.c;
const activeObject = canvas.getActiveObjects();
if (!activeObject.length) return this.hideMenu();
if (opt.button === 3 && opt.target && opt.target.id !== 'workspace') {
// 显示菜单,设置右键菜单位置
// 获取菜单组件的宽高
const menuWidth = this.menu.offsetWidth;
const menuHeight = this.menu.offsetHeight;
// 当前鼠标位置
let pointX = opt.pointer.x;
let pointY = opt.pointer.y;

// 计算菜单出现的位置
// 如果鼠标靠近画布右侧,菜单就出现在鼠标指针左侧
if (canvas.width - pointX <= menuWidth) {
pointX -= menuWidth;
}
// 如果鼠标靠近画布底部,菜单就出现在鼠标指针上方
if (canvas.height - pointY <= menuHeight) {
pointY -= menuHeight;
}
this.showMenu(pointX, pointY);
} else {
this.hideMenu();
}
} catch (error) {
console.log(error);
}
},

showMenu(x, y) {
this.show = 'visible';
this.left = `${x}px`;
this.top = `${y}px`;
this.zIndex = 100;
},

hideMenu() {
this.show = 'hidden';
this.left = 0;
this.top = 0;
this.zIndex = -100;
},

clickHide(e) {
if (e.target !== canvasDom && this.show === 'visible') {
this.hideMenu();
}
},

handleMenu(e) {
const active = e.target.dataset.active || e.srcElement.dataset.active;
if (!active) return this.hideMenu();
const canvas = this.canvas.c;
const activeObject = canvas.getActiveObjects();
switch (active) {
case 'copy':
this.canvas.editor.clone();
break;
case 'delete':
activeObject && activeObject.map((item) => canvas.remove(item));
canvas.requestRenderAll();
canvas.discardActiveObject();
break;
case 'center':
this.canvas.editor.centerAlign.position('center');
break;
case 'group':
this.canvas.editor.group();
break;
case 'unGroup':
this.canvas.editor.unGroup();
break;
case 'up':
this.canvas.editor.up();
break;
case 'down':
this.canvas.editor.down();
break;
case 'upTop':
this.canvas.editor.upTop();
break;
case 'downTop':
this.canvas.editor.downTop();
break;
default:
break;
}
this.hideMenu();
},
},
};
</script>

<style lang="less" scoped>
.menu-wrap {
width: 196px;
padding: 8px 0;
position: absolute;
border: 1px solid #e8eaec;
left: 0;
top: 0;
border-radius: 4px;
visibility: hidden;
list-style: none;
/* 隐藏菜单 */
z-index: -100;
box-shadow: 0 8px 8px 0 rgba(0, 0, 0, 0.08);
background: #fff;

& > li {
color: #33383e;
cursor: pointer;
padding: 6px 10px;

span {
float: right;
color: #bdbdbd;
}

border-bottom: 1px solid #e8eaec;

&:hover {
background-color: #f1f3f4;
}

&:last-child {
border-bottom: none;
}
}

.del {
color: red;
}
}
</style>
97 changes: 97 additions & 0 deletions src/core/EditorGroup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
/*
* @Author: 秦少卫
* @Date: 2023-04-20 02:15:09
* @LastEditors: 白召策
* @LastEditTime: 2023-05-31 11:38:33
* @Description: 编辑组内文字
*/

import { fabric } from 'fabric';
import { v4 as uuid } from 'uuid';

class EditorGroup {
canvas: fabric.Canvas;
constructor(canvas: fabric.Canvas) {
this.canvas = canvas;
this._init();
}

// 组内文本输入
_init() {
this.canvas.on('mouse:down', (opt) => {
if (opt.target && opt.target.type === 'group') {
const clickObj = this._getGroupObj(opt);
if (!clickObj) return;
clickObj.selectable = true;
clickObj.hasControls = false;
this.canvas.setActiveObject(clickObj);
}
});

this.canvas.on('mouse:dblclick', (opt) => {
if (opt.target && opt.target.type === 'group') {
const clickObj = this._getGroupObj(opt);
if (!clickObj) return;
clickObj.selectable = true;
clickObj.hasControls = false;
if (this.isText(clickObj)) {
this._bedingEditingEvent(clickObj, opt);
this.canvas.setActiveObject(clickObj);
clickObj.enterEditing();
return;
}
}
});
}

// 获取点击区域内的组内元素
_getGroupObj(opt: fabric.IEvent<MouseEvent>) {
const pointer = this.canvas.getPointer(opt.e, true);
const clickObj = this.canvas._searchPossibleTargets(opt.target?._objects, pointer);
return clickObj;
}

// 绑定编辑取消事件
_bedingEditingEvent(textObject: fabric.IText, opt: fabric.IEvent<MouseEvent>) {
if (!opt.target) return;
const left = opt.target.left;
const top = opt.target.top;
const ids = this._unGroup(opt.target) || [];

const resetGroup = () => {
const groupArr = this.canvas.getObjects().filter((item) => item.id && ids.includes(item.id));
// 删除元素
groupArr.forEach((item) => this.canvas.remove(item));
// 生成新组
const group = new fabric.Group([...groupArr]);
group.set('left', left);
group.set('top', top);
group.set('id', uuid());
textObject.off('editing:exited', resetGroup);
this.canvas.add(group);
this.canvas.discardActiveObject().renderAll();
};
// 绑定取消事件
textObject.on('editing:exited', resetGroup);
}

// 拆分组合并返回ID
_unGroup(activeObj: fabric.Group) {
const ids: string[] = [];
if (!activeObj) return;
activeObj.getObjects().forEach((item) => {
const id = uuid();
ids.push(id);
item.set('id', id);
});
activeObj.toActiveSelection();
return ids;
}

isText(obj: fabric.Object) {
return obj.type && ['i-text', 'text', 'textbox'].includes(obj.type);
}
}

export default EditorGroup;
4 changes: 2 additions & 2 deletions src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/*
* @Author: 秦少卫
* @Date: 2023-02-03 23:29:34
* @LastEditors: 秦少卫
* @LastEditTime: 2023-07-05 01:01:48
* @LastEditors: bamzc
* @LastEditTime: 2023-08-07 17:53:56
* @Description: 核心入口文件
*/
import Editor from './core';
Expand Down
6 changes: 3 additions & 3 deletions src/core/plugin/ControlsPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/*
* @Author: 秦少卫
* @Date: 2023-06-13 23:00:43
* @Date: 2023-01-09 22:49:02
* @LastEditors: 秦少卫
* @LastEditTime: 2023-06-13 23:09:59
* @Description: 控制条插件
* @LastEditTime: 2023-06-04 10:50:10
* @Description: 控制条样式
*/

import Editor from '../core';
Expand Down
Loading