mirror of
https://github.com/idrawjs/idraw
synced 2026-05-24 10:08:34 +00:00
refactor: copy renderer from @idraw/core to @idraw/renderer
This commit is contained in:
parent
f9a28c3bad
commit
cfebeb4452
42 changed files with 3049 additions and 3 deletions
20
packages/renderer/examples/features/css/index.css
Normal file
20
packages/renderer/examples/features/css/index.css
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 12px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
#mount canvas {
|
||||
/* border-right: 1px solid #aaaaaa40; */
|
||||
border: 1px solid #aaaaaa2a;
|
||||
background-image:
|
||||
linear-gradient(#aaaaaa2a 1px, transparent 0),
|
||||
linear-gradient(90deg, #aaaaaa2a 1px, transparent 0),
|
||||
linear-gradient(#aaaaaa4a 1px, transparent 0),
|
||||
linear-gradient(90deg, #aaaaaa4a 1px, transparent 0);
|
||||
background-size: 10px 10px, 10px 10px, 50px 50px, 50px 50px;
|
||||
}
|
||||
|
||||
75
packages/renderer/examples/features/lib/data/circle.js
Normal file
75
packages/renderer/examples/features/lib/data/circle.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
const data = {
|
||||
// bgColor: '#ffffff',
|
||||
elements: [
|
||||
{
|
||||
name: "circle-001",
|
||||
x: 10,
|
||||
y: 10,
|
||||
w: 100,
|
||||
h: 100,
|
||||
type: "circle",
|
||||
desc: {
|
||||
bgColor: "#f0f0f0",
|
||||
borderWidth: 2,
|
||||
borderColor: '#999999',
|
||||
|
||||
shadowColor: '#03a9f4',
|
||||
// shadowColor: '#000000',
|
||||
shadowOffsetX: 2,
|
||||
shadowOffsetY: 2,
|
||||
shadowBlur: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "circle-002",
|
||||
x: 100,
|
||||
y: 80,
|
||||
w: 200,
|
||||
h: 100,
|
||||
angle: 30,
|
||||
type: "circle",
|
||||
desc: {
|
||||
bgColor: "#f0f0f0",
|
||||
borderWidth: 2,
|
||||
borderColor: '#666666',
|
||||
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "circle-003",
|
||||
x: 200,
|
||||
y: 200,
|
||||
w: 200,
|
||||
h: 100,
|
||||
type: "circle",
|
||||
angle: 0,
|
||||
desc: {
|
||||
bgColor: "#f0f0f0",
|
||||
borderWidth: 2,
|
||||
borderColor: '#666666'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "circle-004",
|
||||
x: 220,
|
||||
y: 80,
|
||||
w: 300,
|
||||
h: 300,
|
||||
type: "circle",
|
||||
desc: {
|
||||
// bgColor: "#f0f0f0",
|
||||
bgColor: "#000000",
|
||||
borderWidth: 10,
|
||||
borderColor: '#666666',
|
||||
|
||||
shadowColor: '#03a9f4',
|
||||
// shadowColor: '#000000',
|
||||
shadowOffsetX: 2,
|
||||
shadowOffsetY: 2,
|
||||
shadowBlur: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default data;
|
||||
91
packages/renderer/examples/features/lib/data/html.js
Normal file
91
packages/renderer/examples/features/lib/data/html.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
const data = {
|
||||
// bgColor: '#ffffff',
|
||||
elements: [
|
||||
{
|
||||
name: "html-001",
|
||||
x: 40,
|
||||
y: 40,
|
||||
w: 200,
|
||||
h: 70,
|
||||
type: "html",
|
||||
angle: 0,
|
||||
desc: {
|
||||
html: `
|
||||
<div style="font-size: 20px;color: #666666">
|
||||
<span>Hello World!</span>
|
||||
</div>
|
||||
<script>
|
||||
window.alert('Hello World')
|
||||
console.log('Hello World')
|
||||
</script>
|
||||
<div style="font-size: 30px; font-weight: bold; color: #666666">
|
||||
<span>Hello World!</span>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "html-001",
|
||||
x: 200,
|
||||
y: 120,
|
||||
w: 240,
|
||||
h: 240,
|
||||
type: "html",
|
||||
angle: 0,
|
||||
desc: {
|
||||
width: 120,
|
||||
height: 80,
|
||||
html: `
|
||||
<style>
|
||||
.btn-box {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.btn {
|
||||
line-height: 1.5715;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
background-image: none;
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0 2px #00000004;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
height: 32px;
|
||||
padding: 4px 15px;
|
||||
font-size: 14px;
|
||||
border-radius: 2px;
|
||||
color: #000000d9;
|
||||
background: #fff;
|
||||
border-color: #d9d9d9;
|
||||
}
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background: #1890ff;
|
||||
border-color: #1890ff;
|
||||
text-shadow: 0 -1px 0 rgb(0 0 0 / 12%);
|
||||
box-shadow: 0 2px #0000000b;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<div class="btn-box" style="margin-top: 0;">
|
||||
<button class="btn" >
|
||||
<span>Button</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="btn-box">
|
||||
<button class="btn btn-primary">
|
||||
<span>Button Primary</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default data;
|
||||
83
packages/renderer/examples/features/lib/data/image.js
Normal file
83
packages/renderer/examples/features/lib/data/image.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
const data = {
|
||||
// bgColor: '#ffffff',
|
||||
elements: [
|
||||
{
|
||||
name: "image-001",
|
||||
x: 10,
|
||||
y: 10,
|
||||
w: 200,
|
||||
h: 100,
|
||||
type: "image",
|
||||
borderRadius: 20,
|
||||
borderWidth: 10,
|
||||
borderColor: "#bd0b64",
|
||||
// angle: 30,
|
||||
// angle: 0,
|
||||
desc: {
|
||||
src: "./../images/computer.png",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "image-002",
|
||||
x: 80,
|
||||
y: 80,
|
||||
w: 200,
|
||||
h: 120,
|
||||
// angle: 30,
|
||||
borderRadius: 20,
|
||||
borderWidth: 10,
|
||||
borderColor: "#bd0b64",
|
||||
type: "image",
|
||||
desc: {
|
||||
src: "./../images/chart.png",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "image-003",
|
||||
x: 160,
|
||||
y: 160,
|
||||
w: 200,
|
||||
h: 100,
|
||||
type: "image",
|
||||
angle: 45,
|
||||
desc: {
|
||||
src: "./../images/phone.png",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "image-004",
|
||||
x: 400 - 10,
|
||||
y: 300 - 10,
|
||||
w: 100,
|
||||
h: 100,
|
||||
type: "image",
|
||||
desc: {
|
||||
src: "./../images/building-001.png",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "image-004",
|
||||
x: 400 - 40,
|
||||
y: 300 - 40,
|
||||
w: 100,
|
||||
h: 100,
|
||||
type: "image",
|
||||
desc: {
|
||||
src: "./../images/building-002.png",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "image-004",
|
||||
x: 400 - 100,
|
||||
y: 300 - 100,
|
||||
w: 100,
|
||||
h: 100,
|
||||
type: "image",
|
||||
desc: {
|
||||
src: "./../images/building-003.png",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default data;
|
||||
43
packages/renderer/examples/features/lib/data/index.js
Normal file
43
packages/renderer/examples/features/lib/data/index.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import dataRect from "./rect.js";
|
||||
import dataImage from "./image.js";
|
||||
import dataSVG from "./svg.js";
|
||||
import dataText from "./text.js";
|
||||
import dataCircle from "./circle.js";
|
||||
|
||||
const url = new URLSearchParams(window.location.search);
|
||||
|
||||
const dataMap = {
|
||||
rect: dataRect,
|
||||
image: dataImage,
|
||||
svg: dataSVG,
|
||||
text: dataText,
|
||||
circle: dataCircle,
|
||||
};
|
||||
|
||||
export function getData() {
|
||||
return dataMap[getPageName()] || dataMap[url.get("data")] || dataMap["rect"];
|
||||
}
|
||||
|
||||
function getPageName() {
|
||||
// const pathname = window.location.pathname || '';
|
||||
// const reg = /(?<pageName>[\w+]{1,})\.html$/;
|
||||
// const page = reg.exec(pathname)?.groups?.pageName || '';
|
||||
// return page;
|
||||
|
||||
const pathname = window.location.pathname || "";
|
||||
const list = pathname.split("/");
|
||||
let pageName = list.pop() || "";
|
||||
pageName = pageName.replace(/\.html$/gi, "");
|
||||
return pageName;
|
||||
|
||||
// return getQueryString('data') || 'rect';
|
||||
}
|
||||
|
||||
// function getQueryString(name) {
|
||||
// let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
|
||||
// let r = window.location.search.substr(1).match(reg);
|
||||
// if (r != null) {
|
||||
// return decodeURIComponent(r[2]);
|
||||
// };
|
||||
// return null;
|
||||
// }
|
||||
72
packages/renderer/examples/features/lib/data/rect.js
Normal file
72
packages/renderer/examples/features/lib/data/rect.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
const data = {
|
||||
// bgColor: '#f0f0f0',
|
||||
elements: [
|
||||
{
|
||||
name: "rect-001",
|
||||
x: 10,
|
||||
y: 10,
|
||||
w: 200,
|
||||
h: 100,
|
||||
type: "rect",
|
||||
desc: {
|
||||
bgColor: "#f0f0f0",
|
||||
borderRadius: 20,
|
||||
borderWidth: 10,
|
||||
borderColor: "#bd0b64",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rect-002",
|
||||
x: 80,
|
||||
y: 80,
|
||||
w: 200,
|
||||
h: 120,
|
||||
// angle: 30,
|
||||
type: "rect",
|
||||
operation: {
|
||||
lock: true,
|
||||
},
|
||||
desc: {
|
||||
bgColor: "#cccccc",
|
||||
borderRadius: 60,
|
||||
borderWidth: 10,
|
||||
borderColor: "#bd0b64",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rect-003",
|
||||
x: 250,
|
||||
y: 150,
|
||||
w: 150,
|
||||
h: 20,
|
||||
type: "rect",
|
||||
angle: 45,
|
||||
desc: {
|
||||
bgColor: "#c0c0c0",
|
||||
borderRadius: 20,
|
||||
borderWidth: 10,
|
||||
borderColor: "#bd0b64",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rect-004",
|
||||
x: 400 - 50,
|
||||
y: 300 - 50,
|
||||
w: 200,
|
||||
h: 100,
|
||||
type: "rect",
|
||||
desc: {
|
||||
bgColor: "#e0e0e0",
|
||||
borderRadius: 20,
|
||||
borderWidth: 10,
|
||||
borderColor: "#bd0b64",
|
||||
},
|
||||
operation: {
|
||||
disbaleScale: true,
|
||||
disbaleRotate: true,
|
||||
}
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default data;
|
||||
55
packages/renderer/examples/features/lib/data/svg.js
Normal file
55
packages/renderer/examples/features/lib/data/svg.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
const data = {
|
||||
// bgColor: '#ffffff',
|
||||
elements: [
|
||||
{
|
||||
name: "svg-001",
|
||||
x: 10,
|
||||
y: 10,
|
||||
w: 200,
|
||||
h: 100,
|
||||
type: "svg",
|
||||
// angle: 30,
|
||||
// angle: 0,
|
||||
desc: {
|
||||
svg: `<svg t="1622524780663" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8365" width="200" height="200"><path d="M881 442.4H519.7v148.5h206.4c-8.9 48-35.9 88.6-76.6 115.8-34.4 23-78.3 36.6-129.9 36.6-99.9 0-184.4-67.5-214.6-158.2-7.6-23-12-47.6-12-72.9s4.4-49.9 12-72.9c30.3-90.6 114.8-158.1 214.7-158.1 56.3 0 106.8 19.4 146.6 57.4l110-110.1c-66.5-62-153.2-100-256.6-100-149.9 0-279.6 86-342.7 211.4-26 51.8-40.8 110.4-40.8 172.4S151 632.8 177 684.6C240.1 810 369.8 896 519.7 896c103.6 0 190.4-34.4 253.8-93 72.5-66.8 114.4-165.2 114.4-282.1 0-27.2-2.4-53.3-6.9-78.5z" p-id="8366" fill="#1296db"></path></svg>`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "svg-002",
|
||||
x: 80,
|
||||
y: 80,
|
||||
w: 200,
|
||||
h: 120,
|
||||
// angle: 30,
|
||||
type: "svg",
|
||||
desc: {
|
||||
svg: '<svg t="1622524813445" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8606" width="200" height="200"><path d="M852.6 367.6c16.3-36.9 32.1-90.7 32.1-131.8 0-109.1-119.5-147.6-314.5-57.9-161.4-10.8-316.8 110.5-355.6 279.7 46.3-52.3 117.4-123.4 183-151.7C316.1 378.3 246.7 470 194 565.6c-31.1 56.9-66 148.8-66 217.5 0 147.9 139.3 129.8 270.4 63 47.1 23.1 99.8 23.4 152.5 23.4 145.7 0 276.4-81.4 325.2-219H694.9c-78.8 132.9-295.2 79.5-295.2-71.2h493.2c9.6-65.4-2.5-143.6-40.3-211.7zM224.8 648.3c26.6 76.7 80.6 143.8 150.4 185-133.1 73.4-259.9 43.6-150.4-185z m174-163.3c3-82.7 75.4-142.3 156-142.3 80.1 0 153 59.6 156 142.3h-312z m276.8-281.4c32.1-15.4 72.8-33 108.8-33 47.1 0 81.4 32.6 81.4 80.6 0 30-11.1 73.5-21.9 101.8-39.3-63.5-98.9-122.4-168.3-149.4z" p-id="8607" fill="#2aa515"></path></svg>',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "svg-003",
|
||||
x: 160,
|
||||
y: 160,
|
||||
w: 200,
|
||||
h: 200,
|
||||
type: "svg",
|
||||
angle: 80,
|
||||
desc: {
|
||||
svg: '<svg t="1622524835512" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9094" width="200" height="200"><path d="M270.1 741.7c0 23.4 19.1 42.5 42.6 42.5h48.7v120.4c0 30.5 24.5 55.4 54.6 55.4 30.2 0 54.6-24.8 54.6-55.4V784.1h85v120.4c0 30.5 24.5 55.4 54.6 55.4 30.2 0 54.6-24.8 54.6-55.4V784.1h48.7c23.5 0 42.6-19.1 42.6-42.5V346.4h-486v395.3zM627.2 141.6l44.9-65c2.6-3.8 2-8.9-1.5-11.4-3.5-2.4-8.5-1.2-11.1 2.6l-46.6 67.6c-30.7-12.1-64.9-18.8-100.8-18.8-35.9 0-70.1 6.7-100.8 18.8l-46.6-67.5c-2.6-3.8-7.6-5.1-11.1-2.6-3.5 2.4-4.1 7.4-1.5 11.4l44.9 65c-71.4 33.2-121.4 96.1-127.8 169.6h486c-6.6-73.6-56.7-136.5-128-169.7zM409.5 244.1c-14.8 0-26.9-12-26.9-26.9 0-14.8 12-26.9 26.9-26.9 14.8 0 26.9 12 26.9 26.9-0.1 14.9-12.1 26.9-26.9 26.9z m208.4 0c-14.8 0-26.9-12-26.9-26.9 0-14.8 12-26.9 26.9-26.9 14.8 0 26.9 12 26.9 26.9-0.1 14.9-12.1 26.9-26.9 26.9zM841.3 344.8c-30.2 0-54.6 24.8-54.6 55.4v216.4c0 30.5 24.5 55.4 54.6 55.4 30.2 0 54.6-24.8 54.6-55.4V400.1c0.1-30.6-24.3-55.3-54.6-55.3zM182.7 344.8c-30.2 0-54.6 24.8-54.6 55.4v216.4c0 30.5 24.5 55.4 54.6 55.4 30.2 0 54.6-24.8 54.6-55.4V400.1c0-30.6-24.5-55.3-54.6-55.3z" p-id="9095" fill="#2aa515"></path></svg>',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "svg-004",
|
||||
x: 400 - 10,
|
||||
y: 300 - 100,
|
||||
w: 200,
|
||||
h: 200,
|
||||
type: "svg",
|
||||
desc: {
|
||||
svg: '<svg t="1622524892065" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9337" width="200" height="200"><path d="M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9 23.5 23.2 38.1 55.4 38.1 91v112.5c0.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z" p-id="9338"></path></svg>',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default data;
|
||||
99
packages/renderer/examples/features/lib/data/text.js
Normal file
99
packages/renderer/examples/features/lib/data/text.js
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
const data = {
|
||||
// bgColor: '#ffffff',
|
||||
elements: [
|
||||
{
|
||||
name: "text-001",
|
||||
x: 10,
|
||||
y: 10,
|
||||
w: 200,
|
||||
h: 100,
|
||||
type: "text",
|
||||
desc: {
|
||||
fontSize: 20,
|
||||
color: "#ffffff",
|
||||
text: "生活就像海洋,只有意志坚强的人,才能到达彼岸。",
|
||||
fontFamily: '',
|
||||
fontWeight: 'bold',
|
||||
borderRadius: 20,
|
||||
borderWidth: 2,
|
||||
borderColor: "#03a9f4",
|
||||
bgColor: '#f0f0f0',
|
||||
strokeColor: '#2196f3',
|
||||
strokeWidth: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "text-002",
|
||||
x: 120,
|
||||
y: 120,
|
||||
w: 100,
|
||||
h: 60,
|
||||
// angle: 30,
|
||||
type: "text",
|
||||
desc: {
|
||||
fontSize: 40,
|
||||
fontWeight: 'blod',
|
||||
text: "Hello Text",
|
||||
// color: "#999999",
|
||||
color: "#ffffff",
|
||||
borderRadius: 60,
|
||||
borderWidth: 4,
|
||||
borderColor: "#03a9f4",
|
||||
|
||||
textShadowColor: '#000000',
|
||||
textShadowOffsetX: 2,
|
||||
textShadowOffsetY: 2,
|
||||
textShadowBlur: 2,
|
||||
|
||||
shadowColor: '#000000',
|
||||
shadowOffsetX: 2,
|
||||
shadowOffsetY: 2,
|
||||
shadowBlur: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "text-003",
|
||||
x: 160,
|
||||
y: 160,
|
||||
w: 200,
|
||||
h: 100,
|
||||
type: "text",
|
||||
operation: {
|
||||
invisible: true,
|
||||
lock: true,
|
||||
},
|
||||
desc: {
|
||||
fontSize: 20,
|
||||
color: "#333333",
|
||||
text: "生活就像海洋,只有意志坚强的人,才能到达彼岸。",
|
||||
fontFamily: "",
|
||||
textAlign: "right",
|
||||
borderRadius: 20,
|
||||
borderWidth: 2,
|
||||
borderColor: "#03a9f4",
|
||||
bgColor: '#f0f0f0',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "text-004",
|
||||
x: 300,
|
||||
y: 240,
|
||||
w: 290,
|
||||
h: 120,
|
||||
type: "text",
|
||||
desc: {
|
||||
fontSize: 20,
|
||||
color: "#333333",
|
||||
text: "Life is like an ocean.\r\nOnly those with strong \nwill can reach the other shore.",
|
||||
fontFamily: "",
|
||||
textAlign: "right",
|
||||
borderRadius: 20,
|
||||
borderWidth: 2,
|
||||
borderColor: "#03a9f4",
|
||||
bgColor: '#f0f0f0',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default data;
|
||||
25
packages/renderer/examples/features/lib/main.js
Normal file
25
packages/renderer/examples/features/lib/main.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { getData } from './data/index.js';
|
||||
|
||||
const Renderer = window.iDrawRenderer;
|
||||
const data = getData();
|
||||
const canvas = document.querySelector('#canvas');
|
||||
|
||||
|
||||
const renderer = new Renderer({
|
||||
width: 600,
|
||||
height: 400,
|
||||
contextWidth: 600,
|
||||
contextHeight: 400,
|
||||
devicePixelRatio: 2,
|
||||
// onlyRender: true,
|
||||
});
|
||||
|
||||
renderer.on('drawFrame', (e) => {
|
||||
console.log('drawFrame =', e)
|
||||
})
|
||||
renderer.on('drawFrameComplete', (e) => {
|
||||
console.log('drawFrameComplete =', e)
|
||||
})
|
||||
|
||||
|
||||
renderer.render(canvas, data)
|
||||
17
packages/renderer/examples/features/main.html
Normal file
17
packages/renderer/examples/features/main.html
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<html>
|
||||
<head>
|
||||
<style></style>
|
||||
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
|
||||
<link rel="stylesheet" href="./css/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div id="mount">
|
||||
<canvas id="canvas"></canvas>
|
||||
</div>
|
||||
|
||||
<script src="./../../dist/index.global.js"></script>
|
||||
<script type="module" src="./lib/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -22,6 +22,12 @@
|
|||
"homepage": "https://github.com/idrawjs/idraw#readme",
|
||||
"author": "chenshenhai",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@idraw/types": "^0.2.0-alpha.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@idraw/util": "^0.2.0-alpha.16"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
|
|
|
|||
15
packages/renderer/src/constant/element.ts
Normal file
15
packages/renderer/src/constant/element.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
const elementTypes = {
|
||||
'text': {}, // TODO
|
||||
'rect': {}, // TODO
|
||||
'image': {}, // TODO
|
||||
'svg': {}, // TODO
|
||||
'circle': {}, // TODO
|
||||
'html': {}, // TODO
|
||||
};
|
||||
|
||||
export const elementNames = Object.keys(elementTypes);
|
||||
|
||||
|
||||
// limitQbliqueAngle
|
||||
export const LIMIT_QBLIQUE_ANGLE = 15;
|
||||
12
packages/renderer/src/constant/static.ts
Normal file
12
packages/renderer/src/constant/static.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export enum Mode {
|
||||
NULL = 'null',
|
||||
SELECT_ELEMENT = 'select-element',
|
||||
SELECT_ELEMENT_LIST = 'select-element-list',
|
||||
SELECT_ELEMENT_WRAPPER_CONTROLLER = 'select-element-wrapper-controller',
|
||||
SELECT_AREA = 'select-area',
|
||||
}
|
||||
|
||||
export enum CursorStatus {
|
||||
DRAGGING = 'dragging',
|
||||
NULL = 'null',
|
||||
}
|
||||
|
|
@ -1,5 +1,144 @@
|
|||
class Renderer {
|
||||
// TODO
|
||||
import { TypeData, TypeContext, } from '@idraw/types';
|
||||
import util from '@idraw/util';
|
||||
import { drawContext } from './lib/draw';
|
||||
import Loader from './lib/loader';
|
||||
import { RendererEvent } from './lib/renderer-event';
|
||||
|
||||
const { Context } = util;
|
||||
const { requestAnimationFrame } = window;
|
||||
const { deepClone } = util.data;
|
||||
|
||||
type QueueItem = { data: TypeData };
|
||||
enum DrawStatus {
|
||||
NULL = 'null',
|
||||
FREE = 'free',
|
||||
DRAWING = 'drawing',
|
||||
FREEZE = 'freeze',
|
||||
// STOP = 'stop',
|
||||
}
|
||||
|
||||
type Options = {
|
||||
width: number,
|
||||
height: number,
|
||||
contextWidth?: number;
|
||||
contextHeight?: number;
|
||||
devicePixelRatio: number,
|
||||
}
|
||||
|
||||
export default class Renderer extends RendererEvent {
|
||||
|
||||
private _queue: QueueItem[] = [];
|
||||
private _ctx: TypeContext | null = null;
|
||||
private _status: DrawStatus = DrawStatus.NULL;
|
||||
private _loader: Loader;
|
||||
private _opts: Options;
|
||||
|
||||
constructor(opts: Options) {
|
||||
super();
|
||||
this._opts = opts;
|
||||
this._loader = new Loader({
|
||||
maxParallelNum: 6
|
||||
});
|
||||
this._loader.on('load', (res) => {
|
||||
this._drawFrame();
|
||||
// console.log('Load: ', res);
|
||||
});
|
||||
this._loader.on('error', (res) => {
|
||||
console.log('Loader Error: ', res);
|
||||
});
|
||||
this._loader.on('complete', (res) => {
|
||||
// console.log('complete: ', res);
|
||||
});
|
||||
}
|
||||
|
||||
freeze() {
|
||||
this._status = DrawStatus.FREEZE;
|
||||
}
|
||||
|
||||
thaw() {
|
||||
this._status = DrawStatus.FREE;
|
||||
}
|
||||
|
||||
render(canvas: HTMLCanvasElement, data: TypeData, changeResourceUUIDs?: string[]): void {
|
||||
// if ([DrawStatus.STOP, DrawStatus.FREEZE].includes(this._status)) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
const { width, height, contextWidth, contextHeight, devicePixelRatio } = this._opts;
|
||||
canvas.width = width * devicePixelRatio;
|
||||
canvas.height = height * devicePixelRatio;
|
||||
const ctx2d = canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||
this._ctx = new Context(ctx2d, {
|
||||
width,
|
||||
height,
|
||||
contextWidth: contextWidth || width,
|
||||
contextHeight: contextHeight || height,
|
||||
devicePixelRatio
|
||||
})
|
||||
if ([DrawStatus.FREEZE].includes(this._status)) {
|
||||
return;
|
||||
}
|
||||
const _data: QueueItem = deepClone({ data, }) as QueueItem;
|
||||
this._queue.push(_data);
|
||||
if (this._status !== DrawStatus.DRAWING) {
|
||||
this._status = DrawStatus.DRAWING;
|
||||
this._drawFrame();
|
||||
}
|
||||
this._loader.load(data, changeResourceUUIDs || []);
|
||||
}
|
||||
|
||||
private _drawFrame() {
|
||||
if (this._status === DrawStatus.FREEZE) {
|
||||
return;
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
if (this._status === DrawStatus.FREEZE) {
|
||||
return;
|
||||
}
|
||||
const ctx = this._ctx;
|
||||
|
||||
let item: QueueItem | undefined = this._queue[0];
|
||||
let isLastFrame = false;
|
||||
if (this._queue.length > 1) {
|
||||
item = this._queue.shift();
|
||||
} else {
|
||||
isLastFrame = true;
|
||||
}
|
||||
if (this._loader.isComplete() !== true) {
|
||||
this._drawFrame();
|
||||
if (item && ctx) {
|
||||
drawContext(ctx, item.data, this._loader);
|
||||
// this._board.draw();
|
||||
}
|
||||
} else if (item && ctx) {
|
||||
drawContext(ctx, item.data, this._loader);
|
||||
// this._board.draw();
|
||||
this._retainQueueOneItem();
|
||||
if (!isLastFrame) {
|
||||
this._drawFrame();
|
||||
} else {
|
||||
this._status = DrawStatus.FREE;
|
||||
}
|
||||
} else {
|
||||
this._status = DrawStatus.FREE;
|
||||
}
|
||||
this.trigger('drawFrame', undefined)
|
||||
|
||||
if (this._loader.isComplete() === true && this._queue.length === 1 && this._status === DrawStatus.FREE) {
|
||||
this.trigger('drawFrameComplete', undefined);
|
||||
this.freeze();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _retainQueueOneItem() {
|
||||
if (this._queue.length <= 1) {
|
||||
return;
|
||||
}
|
||||
const lastOne = deepClone(this._queue[this._queue.length - 1]);
|
||||
this._queue = [lastOne];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default Renderer;
|
||||
67
packages/renderer/src/lib/calculate.ts
Normal file
67
packages/renderer/src/lib/calculate.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import {
|
||||
TypeElement,
|
||||
TypeElemDesc,
|
||||
TypePoint,
|
||||
} from '@idraw/types';
|
||||
|
||||
|
||||
export function parseRadianToAngle(radian: number): number {
|
||||
return radian / Math.PI * 180;
|
||||
}
|
||||
|
||||
export function parseAngleToRadian(angle: number): number {
|
||||
return angle / 180 * Math.PI;
|
||||
}
|
||||
|
||||
export function calcElementCenter(elem: TypeElement<keyof TypeElemDesc>): TypePoint {
|
||||
const p = {
|
||||
x: elem.x + elem.w / 2,
|
||||
y: elem.y + elem.h / 2,
|
||||
};
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
export function calcRadian(center: TypePoint, start: TypePoint, end: TypePoint): number {
|
||||
const startAngle = calcLineAngle(center, start);
|
||||
const endAngle = calcLineAngle(center, end);
|
||||
if (endAngle !== null && startAngle !== null ) {
|
||||
if (startAngle > Math.PI * 3 / 2 && endAngle < Math.PI / 2) {
|
||||
return endAngle + (Math.PI * 2 - startAngle);
|
||||
} else if (endAngle > Math.PI * 3 / 2 && startAngle < Math.PI / 2) {
|
||||
return startAngle + (Math.PI * 2 - endAngle);
|
||||
} else {
|
||||
return endAngle - startAngle;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function calcLineAngle(center: TypePoint, p: TypePoint): number | null {
|
||||
const x = p.x - center.x;
|
||||
const y = center.y - p.y;
|
||||
if (x === 0) {
|
||||
if (y < 0) {
|
||||
return Math.PI / 2;
|
||||
} else if (y > 0) {
|
||||
return Math.PI * ( 3 / 2 );
|
||||
}
|
||||
} else if (y === 0) {
|
||||
if (x < 0) {
|
||||
return Math.PI;
|
||||
} else if (x > 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (x > 0 && y < 0) {
|
||||
return Math.atan(Math.abs(y) / Math.abs(x));
|
||||
} else if (x < 0 && y < 0) {
|
||||
return Math.PI - Math.atan(Math.abs(y) / Math.abs(x));
|
||||
} else if (x < 0 && y > 0) {
|
||||
return Math.PI + Math.atan(Math.abs(y) / Math.abs(x));
|
||||
} else if (x > 0 && y > 0) {
|
||||
return Math.PI * 2 - Math.atan(Math.abs(y) / Math.abs(x));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
163
packages/renderer/src/lib/check.ts
Normal file
163
packages/renderer/src/lib/check.ts
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
|
||||
import { TypeElementAttrs } from '@idraw/types';
|
||||
import is from './is';
|
||||
|
||||
|
||||
function attrs(
|
||||
attrs: TypeElementAttrs
|
||||
): boolean {
|
||||
const { x, y, w, h, angle } = attrs;
|
||||
if (!(is.x(x) && is.y(y) && is.w(w) && is.h(h) && is.angle(angle))) {
|
||||
return false;
|
||||
}
|
||||
if (!(angle >= -360 && angle <= 360 )) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function box(
|
||||
desc: any = {},
|
||||
): boolean {
|
||||
const { borderColor, borderRadius, borderWidth } = desc;
|
||||
if (desc.hasOwnProperty('borderColor') && !is.color(borderColor)) {
|
||||
return false;
|
||||
}
|
||||
if (desc.hasOwnProperty('borderRadius') && !is.number(borderRadius)) {
|
||||
return false;
|
||||
}
|
||||
if (desc.hasOwnProperty('borderWidth') && !is.number(borderWidth)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function rectDesc(
|
||||
desc: any
|
||||
): boolean {
|
||||
const { bgColor } = desc;
|
||||
if (desc.hasOwnProperty('bgColor') && !is.color(bgColor)) {
|
||||
return false;
|
||||
}
|
||||
if (!box(desc)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function circleDesc(
|
||||
desc: any
|
||||
): boolean {
|
||||
const { bgColor, borderColor, borderWidth } = desc;
|
||||
if (desc.hasOwnProperty('bgColor') && !is.color(bgColor)) {
|
||||
return false;
|
||||
}
|
||||
if (desc.hasOwnProperty('borderColor') && !is.color(borderColor)) {
|
||||
return false;
|
||||
}
|
||||
if (desc.hasOwnProperty('borderWidth') && !is.number(borderWidth)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function imageDesc(
|
||||
desc: any
|
||||
): boolean {
|
||||
const { src } = desc;
|
||||
if (!is.imageSrc(src)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function svgDesc(
|
||||
desc: any
|
||||
): boolean {
|
||||
const { svg } = desc;
|
||||
if (!is.svg(svg)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function htmlDesc(
|
||||
desc: any
|
||||
): boolean {
|
||||
const { html } = desc;
|
||||
if (!is.html(html)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function textDesc(
|
||||
desc: any
|
||||
): boolean {
|
||||
const {
|
||||
text, color, fontSize, lineHeight, fontFamily, textAlign,
|
||||
fontWeight, bgColor, strokeWidth, strokeColor
|
||||
} = desc;
|
||||
if (!is.text(text)){
|
||||
return false;
|
||||
}
|
||||
if (!is.color(color)){
|
||||
return false;
|
||||
}
|
||||
if (!is.fontSize(fontSize)){
|
||||
return false;
|
||||
}
|
||||
if (desc.hasOwnProperty('bgColor') && !is.color(bgColor)){
|
||||
return false;
|
||||
}
|
||||
if (desc.hasOwnProperty('fontWeight') && !is.fontWeight(fontWeight)){
|
||||
return false;
|
||||
}
|
||||
if (desc.hasOwnProperty('lineHeight') && !is.lineHeight(lineHeight)){
|
||||
return false;
|
||||
}
|
||||
if (desc.hasOwnProperty('fontFamily') && !is.fontFamily(fontFamily)){
|
||||
return false;
|
||||
}
|
||||
if (desc.hasOwnProperty('textAlign') && !is.textAlign(textAlign)){
|
||||
return false;
|
||||
}
|
||||
if (desc.hasOwnProperty('strokeWidth') && !is.strokeWidth(strokeWidth)){
|
||||
return false;
|
||||
}
|
||||
if (desc.hasOwnProperty('strokeColor') && !is.color(strokeColor)){
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!box(desc)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const check = {
|
||||
attrs,
|
||||
textDesc,
|
||||
rectDesc,
|
||||
circleDesc,
|
||||
imageDesc,
|
||||
svgDesc,
|
||||
htmlDesc,
|
||||
};
|
||||
|
||||
type TypeCheck = {
|
||||
attrs: (value: any) => boolean,
|
||||
rectDesc: (value: any) => boolean,
|
||||
circleDesc: (value: any) => boolean,
|
||||
imageDesc: (value: any) => boolean,
|
||||
svgDesc: (value: any) => boolean,
|
||||
htmlDesc: (value: any) => boolean,
|
||||
textDesc: (value: any) => boolean,
|
||||
}
|
||||
|
||||
export {
|
||||
TypeCheck
|
||||
};
|
||||
|
||||
export default check;
|
||||
27
packages/renderer/src/lib/config.ts
Normal file
27
packages/renderer/src/lib/config.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { TypeConfig, TypeConfigStrict } from '@idraw/types';
|
||||
import util from '@idraw/util';
|
||||
|
||||
const defaultConfig: TypeConfigStrict = {
|
||||
elementWrapper: {
|
||||
color: '#2ab6f1',
|
||||
lockColor: '#aaaaaa',
|
||||
controllerSize: 6,
|
||||
lineWidth: 1,
|
||||
lineDash: [4, 3],
|
||||
}
|
||||
};
|
||||
|
||||
function mergeConfig(config?: TypeConfig): TypeConfigStrict {
|
||||
const result = util.data.deepClone(defaultConfig);
|
||||
if (config) {
|
||||
if (config.elementWrapper) {
|
||||
result.elementWrapper = {...result.elementWrapper, ...config.elementWrapper};
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export {
|
||||
mergeConfig,
|
||||
};
|
||||
|
||||
94
packages/renderer/src/lib/core-event.ts
Normal file
94
packages/renderer/src/lib/core-event.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import {
|
||||
TypeElement,
|
||||
TypeElemDesc,
|
||||
TypePoint,
|
||||
TypeData,
|
||||
TypeScreenData,
|
||||
} from '@idraw/types';
|
||||
|
||||
export type TypeCoreEventSelectBaseArg = {
|
||||
index: number | null;
|
||||
uuid: string | null;
|
||||
}
|
||||
|
||||
export type TypeCoreEventArgMap = {
|
||||
'error': any;
|
||||
'mouseOverScreen': TypePoint,
|
||||
'mouseLeaveScreen': void,
|
||||
'mouseOverElement': TypeCoreEventSelectBaseArg & { element: TypeElement<keyof TypeElemDesc> }
|
||||
'mouseLeaveElement': TypeCoreEventSelectBaseArg & { element: TypeElement<keyof TypeElemDesc> }
|
||||
'screenClickElement': TypeCoreEventSelectBaseArg & { element: TypeElement<keyof TypeElemDesc> }
|
||||
'screenDoubleClickElement': TypeCoreEventSelectBaseArg & { element: TypeElement<keyof TypeElemDesc> }
|
||||
'screenSelectElement': TypeCoreEventSelectBaseArg & { element: TypeElement<keyof TypeElemDesc> }
|
||||
'screenMoveElementStart': TypeCoreEventSelectBaseArg & TypePoint,
|
||||
'screenMoveElementEnd': TypeCoreEventSelectBaseArg & TypePoint,
|
||||
'screenChangeElement': TypeCoreEventSelectBaseArg & { width: number, height: number, angle: number};
|
||||
'changeData': TypeData;
|
||||
'changeScreen': TypeScreenData,
|
||||
'drawFrameComplete': void;
|
||||
'drawFrame': void;
|
||||
}
|
||||
|
||||
export interface TypeCoreEvent {
|
||||
on<T extends keyof TypeCoreEventArgMap >(key: T, callback: (p: TypeCoreEventArgMap[T]) => void): void
|
||||
off<T extends keyof TypeCoreEventArgMap >(key: T, callback: (p: TypeCoreEventArgMap[T]) => void): void
|
||||
trigger<T extends keyof TypeCoreEventArgMap >(key: T, p: TypeCoreEventArgMap[T]): void
|
||||
}
|
||||
|
||||
|
||||
export class CoreEvent implements TypeCoreEvent {
|
||||
|
||||
private _listeners: Map<string, ((p: any) => void)[]>;
|
||||
|
||||
constructor() {
|
||||
this._listeners = new Map();
|
||||
}
|
||||
|
||||
on<T extends keyof TypeCoreEventArgMap >(eventKey: T, callback: (p: TypeCoreEventArgMap[T]) => void) {
|
||||
if (this._listeners.has(eventKey)) {
|
||||
const callbacks = this._listeners.get(eventKey);
|
||||
callbacks?.push(callback);
|
||||
this._listeners.set(eventKey, callbacks || []);
|
||||
} else {
|
||||
this._listeners.set(eventKey, [callback]);
|
||||
}
|
||||
}
|
||||
|
||||
off<T extends keyof TypeCoreEventArgMap >(eventKey: T, callback: (p: TypeCoreEventArgMap[T]) => void) {
|
||||
if (this._listeners.has(eventKey)) {
|
||||
const callbacks = this._listeners.get(eventKey);
|
||||
if (Array.isArray(callbacks)) {
|
||||
for (let i = 0; i < callbacks?.length; i++) {
|
||||
if (callbacks[i] === callback) {
|
||||
callbacks.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._listeners.set(eventKey, callbacks || []);
|
||||
}
|
||||
}
|
||||
|
||||
trigger<T extends keyof TypeCoreEventArgMap >(eventKey: T, arg: TypeCoreEventArgMap[T]) {
|
||||
const callbacks = this._listeners.get(eventKey);
|
||||
if (Array.isArray(callbacks)) {
|
||||
callbacks.forEach((cb) => {
|
||||
cb(arg);
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
has<T extends keyof TypeCoreEventArgMap> (name: string) {
|
||||
if (this._listeners.has(name)) {
|
||||
const list: ((p: TypeCoreEventArgMap[T]) => void)[] | undefined = this._listeners.get(name);
|
||||
if (Array.isArray(list) && list.length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
124
packages/renderer/src/lib/diff.ts
Normal file
124
packages/renderer/src/lib/diff.ts
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import { TypeElement, TypeData, TypeElemDesc } from '@idraw/types';
|
||||
|
||||
type TypeElementMap = {
|
||||
[uuid: string]: TypeElement<keyof TypeElemDesc>
|
||||
}
|
||||
|
||||
|
||||
export function isChangeImageElementResource(
|
||||
before: TypeElement<'image'>,
|
||||
after: TypeElement<'image'>,
|
||||
): boolean {
|
||||
return (before?.desc?.src !== after?.desc?.src);
|
||||
}
|
||||
|
||||
|
||||
export function isChangeSVGElementResource(
|
||||
before: TypeElement<'svg'>,
|
||||
after: TypeElement<'svg'>,
|
||||
): boolean {
|
||||
return (before?.desc?.svg !== after?.desc?.svg);
|
||||
}
|
||||
|
||||
export function isChangeHTMLElementResource(
|
||||
before: TypeElement<'html'>,
|
||||
after: TypeElement<'html'>,
|
||||
): boolean {
|
||||
return (
|
||||
before?.desc?.html !== after?.desc?.html
|
||||
|| before?.desc?.width !== after?.desc?.width
|
||||
|| before?.desc?.height !== after?.desc?.height
|
||||
);
|
||||
}
|
||||
|
||||
export function diffElementResourceChange(
|
||||
before: TypeElement<keyof TypeElemDesc>,
|
||||
after: TypeElement<keyof TypeElemDesc>,
|
||||
): string | null {
|
||||
let result = null;
|
||||
let isChange = false;
|
||||
switch (after.type) {
|
||||
case 'image': {
|
||||
isChange = isChangeImageElementResource(
|
||||
before as TypeElement<'image'>,
|
||||
after as TypeElement<'image'>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'svg': {
|
||||
isChange = isChangeSVGElementResource(
|
||||
before as TypeElement<'svg'>,
|
||||
after as TypeElement<'svg'>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'html': {
|
||||
isChange = isChangeHTMLElementResource(
|
||||
before as TypeElement<'html'>,
|
||||
after as TypeElement<'html'>
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
if (isChange === true) {
|
||||
result = after.uuid;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function diffElementResourceChangeList(
|
||||
before: TypeData,
|
||||
after: TypeData,
|
||||
): string[] {
|
||||
const uuids: string[] = [];
|
||||
const beforeMap = parseDataElementMap(before);
|
||||
const afterMap = parseDataElementMap(after);
|
||||
for (const uuid in afterMap) {
|
||||
if (['image', 'svg', 'html'].includes(afterMap[uuid]?.type) !== true) {
|
||||
continue;
|
||||
}
|
||||
if (beforeMap[uuid]) {
|
||||
let isChange = false;
|
||||
switch (beforeMap[uuid].type) {
|
||||
case 'image': {
|
||||
isChange = isChangeImageElementResource(
|
||||
beforeMap[uuid] as TypeElement<'image'>,
|
||||
afterMap[uuid] as TypeElement<'image'>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'svg': {
|
||||
isChange = isChangeSVGElementResource(
|
||||
beforeMap[uuid] as TypeElement<'svg'>,
|
||||
afterMap[uuid] as TypeElement<'svg'>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'html': {
|
||||
isChange = isChangeHTMLElementResource(
|
||||
beforeMap[uuid] as TypeElement<'html'>,
|
||||
afterMap[uuid] as TypeElement<'html'>
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
if (isChange === true) {
|
||||
uuids.push(uuid);
|
||||
}
|
||||
} else {
|
||||
uuids.push(uuid);
|
||||
}
|
||||
}
|
||||
return uuids;
|
||||
}
|
||||
|
||||
|
||||
function parseDataElementMap(data: TypeData): TypeElementMap {
|
||||
const elemMap: TypeElementMap = {};
|
||||
data.elements.forEach((elem) => {
|
||||
elemMap[elem.uuid] = elem;
|
||||
})
|
||||
return elemMap;
|
||||
}
|
||||
111
packages/renderer/src/lib/draw/base.ts
Normal file
111
packages/renderer/src/lib/draw/base.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import {
|
||||
TypeContext,
|
||||
// TypeElemDesc,
|
||||
TypeElement,
|
||||
} from '@idraw/types';
|
||||
import util from '@idraw/util';
|
||||
import { rotateElement } from './../transform';
|
||||
import is from './../is';
|
||||
|
||||
const { istype, color } = util;
|
||||
|
||||
export function clearContext(ctx: TypeContext) {
|
||||
// ctx.setFillStyle('rgb(0 0 0 / 100%)');
|
||||
// ctx.setStrokeStyle('rgb(0 0 0 / 100%)');
|
||||
ctx.setFillStyle('#000000');
|
||||
ctx.setStrokeStyle('#000000');
|
||||
ctx.setLineDash([]);
|
||||
ctx.setGlobalAlpha(1);
|
||||
ctx.setShadowColor('#00000000');
|
||||
ctx.setShadowOffsetX(0)
|
||||
ctx.setShadowOffsetY(0);
|
||||
ctx.setShadowBlur(0);
|
||||
}
|
||||
|
||||
export function drawBgColor(ctx: TypeContext, color: string) {
|
||||
const size = ctx.getSize();
|
||||
ctx.setFillStyle(color);
|
||||
ctx.fillRect(0, 0, size.contextWidth, size.contextHeight);
|
||||
}
|
||||
|
||||
export function drawBox(
|
||||
ctx: TypeContext,
|
||||
elem: TypeElement<'text' | 'rect'>,
|
||||
pattern: string | CanvasPattern | null,
|
||||
): void {
|
||||
clearContext(ctx);
|
||||
drawBoxBorder(ctx, elem);
|
||||
clearContext(ctx);
|
||||
rotateElement(ctx, elem, () => {
|
||||
const { x, y, w, h } = elem;
|
||||
let r: number = elem.desc.borderRadius || 0;
|
||||
r = Math.min(r, w / 2, h / 2);
|
||||
if (w < r * 2 || h < r * 2) {
|
||||
r = 0;
|
||||
}
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + r, y);
|
||||
ctx.arcTo(x + w, y, x + w, y + h, r);
|
||||
ctx.arcTo(x + w, y + h, x, y + h, r);
|
||||
ctx.arcTo(x, y + h, x, y, r);
|
||||
ctx.arcTo(x, y, x + w, y, r);
|
||||
ctx.closePath();
|
||||
if (typeof pattern === 'string') {
|
||||
ctx.setFillStyle(pattern);
|
||||
} else if (['CanvasPattern'].includes(istype.type(pattern))) {
|
||||
ctx.setFillStyle(pattern as CanvasPattern);
|
||||
}
|
||||
ctx.fill();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function drawBoxBorder(
|
||||
ctx: TypeContext,
|
||||
elem: TypeElement<'text'|'rect'>,
|
||||
): void {
|
||||
clearContext(ctx);
|
||||
rotateElement(ctx, elem, () => {
|
||||
if (!(elem.desc.borderWidth && elem.desc.borderWidth > 0)) {
|
||||
return;
|
||||
}
|
||||
const bw = elem.desc.borderWidth;
|
||||
let borderColor = '#000000';
|
||||
if (color.isColorStr(elem.desc.borderColor) === true) {
|
||||
borderColor = elem.desc.borderColor as string;
|
||||
}
|
||||
const x = elem.x - bw / 2;
|
||||
const y = elem.y - bw / 2;
|
||||
const w = elem.w + bw;
|
||||
const h = elem.h + bw;
|
||||
|
||||
let r: number = elem.desc.borderRadius || 0;
|
||||
r = Math.min(r, w / 2, h / 2);
|
||||
if (r < w / 2 && r < h / 2) {
|
||||
r = r + bw / 2;
|
||||
}
|
||||
const { desc } = elem;
|
||||
if (desc.shadowColor !== undefined && util.color.isColorStr(desc.shadowColor)) {
|
||||
ctx.setShadowColor(desc.shadowColor);
|
||||
}
|
||||
if (desc.shadowOffsetX !== undefined && is.number(desc.shadowOffsetX)) {
|
||||
ctx.setShadowOffsetX(desc.shadowOffsetX);
|
||||
}
|
||||
if (desc.shadowOffsetY !== undefined && is.number(desc.shadowOffsetY)) {
|
||||
ctx.setShadowOffsetY(desc.shadowOffsetY);
|
||||
}
|
||||
if (desc.shadowBlur !== undefined && is.number(desc.shadowBlur)) {
|
||||
ctx.setShadowBlur(desc.shadowBlur);
|
||||
}
|
||||
ctx.beginPath();
|
||||
ctx.setLineWidth(bw);
|
||||
ctx.setStrokeStyle(borderColor);
|
||||
ctx.moveTo(x + r, y);
|
||||
ctx.arcTo(x + w, y, x + w, y + h, r);
|
||||
ctx.arcTo(x + w, y + h, x, y + h, r);
|
||||
ctx.arcTo(x, y + h, x, y, r);
|
||||
ctx.arcTo(x, y, x + w, y, r);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
});
|
||||
}
|
||||
77
packages/renderer/src/lib/draw/circle.ts
Normal file
77
packages/renderer/src/lib/draw/circle.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { TypeContext, TypeElement, } from '@idraw/types';
|
||||
// import util from '@idraw/util'
|
||||
import { rotateElement } from './../transform';
|
||||
// import is from './../is';
|
||||
import { clearContext } from './base';
|
||||
|
||||
export function drawCircle(ctx: TypeContext, elem: TypeElement<'circle'>) {
|
||||
clearContext(ctx);
|
||||
rotateElement(ctx, elem, (ctx) => {
|
||||
const { x, y, w, h, desc } = elem;
|
||||
const {
|
||||
bgColor = '#000000',
|
||||
borderColor = '#000000',
|
||||
borderWidth = 0,
|
||||
} = desc;
|
||||
|
||||
const a = w / 2;
|
||||
const b = h / 2;
|
||||
const centerX = x + a;
|
||||
const centerY = y + b;
|
||||
|
||||
// draw border
|
||||
if (borderWidth && borderWidth > 0) {
|
||||
|
||||
const ba = borderWidth / 2 + a;
|
||||
const bb = borderWidth / 2 + b;
|
||||
ctx.beginPath();
|
||||
ctx.setStrokeStyle(borderColor);
|
||||
ctx.setLineWidth(borderWidth);
|
||||
ctx.ellipse(centerX, centerY, ba, bb, 0, 0, 2 * Math.PI)
|
||||
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// draw content
|
||||
ctx.beginPath();
|
||||
ctx.setFillStyle(bgColor);
|
||||
ctx.ellipse(centerX, centerY, a, b, 0, 0, 2 * Math.PI)
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
// // draw shadow
|
||||
// clearContext(ctx);
|
||||
// if ((desc.shadowOffsetX !== undefined && is.number(desc.shadowOffsetX)) || desc.shadowOffsetY !== undefined && is.number(desc.shadowOffsetY)) {
|
||||
|
||||
// if (desc.shadowColor !== undefined && util.color.isColorStr(desc.shadowColor)) {
|
||||
// ctx.setShadowColor(desc.shadowColor);
|
||||
// }
|
||||
// if (desc.shadowOffsetX !== undefined && is.number(desc.shadowOffsetX)) {
|
||||
// ctx.setShadowOffsetX(desc.shadowOffsetX);
|
||||
// }
|
||||
// if (desc.shadowOffsetY !== undefined && is.number(desc.shadowOffsetY)) {
|
||||
// ctx.setShadowOffsetY(desc.shadowOffsetY);
|
||||
// }
|
||||
// if (desc.shadowBlur !== undefined && is.number(desc.shadowBlur)) {
|
||||
// ctx.setShadowBlur(desc.shadowBlur);
|
||||
// }
|
||||
|
||||
// const a = (w + borderWidth * 2) / 2;
|
||||
// const b = (h + borderWidth * 2) / 2;
|
||||
// const centerX = x + a - borderWidth;
|
||||
// const centerY = y + b - borderWidth;
|
||||
// const unit = (a > b) ? 1 / a : 1 / b;
|
||||
|
||||
// ctx.beginPath();
|
||||
// ctx.setFillStyle('#ffffff6a');
|
||||
// ctx.moveTo(centerX + a, centerY);
|
||||
// for(var i = 0; i < 2 * Math.PI; i += unit) {
|
||||
// ctx.lineTo(centerX + a * Math.cos(i), centerY + b * Math.sin(i));
|
||||
// }
|
||||
// ctx.closePath();
|
||||
// ctx.fill();
|
||||
// }
|
||||
|
||||
})
|
||||
}
|
||||
20
packages/renderer/src/lib/draw/html.ts
Normal file
20
packages/renderer/src/lib/draw/html.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import {
|
||||
TypeContext,
|
||||
TypeElement,
|
||||
} from '@idraw/types';
|
||||
import { rotateElement } from '../transform';
|
||||
import Loader from '../loader';
|
||||
|
||||
|
||||
export function drawHTML(
|
||||
ctx: TypeContext,
|
||||
elem: TypeElement<'html'>,
|
||||
loader: Loader,
|
||||
) {
|
||||
const content = loader.getContent(elem.uuid);
|
||||
rotateElement(ctx, elem, () => {
|
||||
if (content) {
|
||||
ctx.drawImage(content, elem.x, elem.y, elem.w, elem.h);
|
||||
}
|
||||
});
|
||||
}
|
||||
53
packages/renderer/src/lib/draw/image.ts
Normal file
53
packages/renderer/src/lib/draw/image.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import {
|
||||
TypeContext,
|
||||
TypeElement,
|
||||
} from '@idraw/types';
|
||||
import { rotateElement } from '../transform';
|
||||
import Loader from '../loader';
|
||||
|
||||
|
||||
export function drawImage (
|
||||
ctx: TypeContext,
|
||||
elem: TypeElement<'image'>,
|
||||
loader: Loader,
|
||||
) {
|
||||
// const desc = elem.desc as TypeElemDesc['rect'];
|
||||
const content = loader.getContent(elem.uuid);
|
||||
rotateElement(ctx, elem, () => {
|
||||
// ctx.setFillStyle(desc.color);
|
||||
// ctx.fillRect(elem.x, elem.y, elem.w, elem.h);
|
||||
if (content) {
|
||||
// ctx.drawImage(content, 0, 0, elem.w, elem.h, elem.x, elem.y, elem.w, elem.h);
|
||||
ctx.drawImage(content, elem.x, elem.y, elem.w, elem.h);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// import {
|
||||
// TypeContext,
|
||||
// TypeElement,
|
||||
// TypeHelperConfig,
|
||||
// TypeElemDesc,
|
||||
// } from '@idraw/types';
|
||||
// import Loader from '../loader';
|
||||
// import { drawBox } from './base';
|
||||
|
||||
// export function drawImage(
|
||||
// ctx: TypeContext,
|
||||
// elem: TypeElement<'image'>,
|
||||
// loader: Loader,
|
||||
// helperConfig: TypeHelperConfig
|
||||
// ) {
|
||||
// const content = loader.getPattern(elem, {
|
||||
// forceUpdate: helperConfig?.selectedElementWrapper?.uuid === elem.uuid
|
||||
// });
|
||||
// drawBox(ctx, elem, content);
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
73
packages/renderer/src/lib/draw/index.ts
Normal file
73
packages/renderer/src/lib/draw/index.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import {
|
||||
TypeContext,
|
||||
TypeData,
|
||||
TypeElement,
|
||||
// TypePoint,
|
||||
} from '@idraw/types';
|
||||
import util from '@idraw/util';
|
||||
import Loader from '../loader';
|
||||
import { clearContext, drawBgColor } from './base';
|
||||
import { drawRect } from './rect';
|
||||
import { drawImage } from './image';
|
||||
import { drawSVG } from './svg';
|
||||
import { drawHTML } from './html';
|
||||
import { drawText } from './text';
|
||||
import { drawCircle } from './circle';
|
||||
|
||||
const { isColorStr } = util.color;
|
||||
|
||||
export function drawContext(
|
||||
ctx: TypeContext,
|
||||
data: TypeData,
|
||||
loader: Loader,
|
||||
): void {
|
||||
clearContext(ctx);
|
||||
const size = ctx.getSize();
|
||||
ctx.clearRect(0, 0, size.contextWidth, size.contextHeight);
|
||||
|
||||
if (typeof data.bgColor === 'string' && isColorStr(data.bgColor)) {
|
||||
drawBgColor(ctx, data.bgColor);
|
||||
}
|
||||
|
||||
if (!(data.elements.length > 0)) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < data.elements.length; i++) {
|
||||
const elem = data.elements[i];
|
||||
if (elem?.operation?.invisible === true) {
|
||||
continue;
|
||||
}
|
||||
switch (elem.type) {
|
||||
case 'rect': {
|
||||
drawRect(ctx, elem as TypeElement<'rect'>);
|
||||
break;
|
||||
}
|
||||
case 'text': {
|
||||
drawText(ctx, elem as TypeElement<'text'>, loader);
|
||||
break;
|
||||
}
|
||||
case 'image': {
|
||||
drawImage(ctx, elem as TypeElement<'image'>, loader);
|
||||
break;
|
||||
}
|
||||
case 'svg': {
|
||||
drawSVG(ctx, elem as TypeElement<'svg'>, loader);
|
||||
break;
|
||||
}
|
||||
case 'html': {
|
||||
drawHTML(ctx, elem as TypeElement<'html'>, loader);
|
||||
break;
|
||||
}
|
||||
case 'circle': {
|
||||
drawCircle(ctx, elem as TypeElement<'circle'>);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
13
packages/renderer/src/lib/draw/rect.ts
Normal file
13
packages/renderer/src/lib/draw/rect.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import {
|
||||
TypeContext,
|
||||
TypeElement,
|
||||
} from '@idraw/types';
|
||||
import { drawBox } from './base';
|
||||
|
||||
export function drawRect(ctx: TypeContext, elem: TypeElement<'rect'>) {
|
||||
drawBox(ctx, elem, elem.desc.bgColor as string);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
47
packages/renderer/src/lib/draw/svg.ts
Normal file
47
packages/renderer/src/lib/draw/svg.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import {
|
||||
TypeContext,
|
||||
TypeElement,
|
||||
} from '@idraw/types';
|
||||
import { rotateElement } from '../transform';
|
||||
import Loader from '../loader';
|
||||
|
||||
|
||||
export function drawSVG (
|
||||
ctx: TypeContext,
|
||||
elem: TypeElement<'svg'>,
|
||||
loader: Loader,
|
||||
) {
|
||||
// const desc = elem.desc as TypeElemDesc['rect'];
|
||||
const content = loader.getContent(elem.uuid);
|
||||
rotateElement(ctx, elem, () => {
|
||||
// ctx.setFillStyle(desc.color);
|
||||
// ctx.fillRect(elem.x, elem.y, elem.w, elem.h);
|
||||
if (content) {
|
||||
// ctx.drawImage(content, 0, 0, elem.w, elem.h, elem.x, elem.y, elem.w, elem.h);
|
||||
ctx.drawImage(content, elem.x, elem.y, elem.w, elem.h);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// import {
|
||||
// TypeContext,
|
||||
// TypeElement,
|
||||
// TypeHelperConfig,
|
||||
// } from '@idraw/types';
|
||||
// import Loader from '../loader';
|
||||
// import { drawBox } from './base';
|
||||
|
||||
// export function drawSVG(
|
||||
// ctx: TypeContext,
|
||||
// elem: TypeElement<'svg'>,
|
||||
// loader: Loader,
|
||||
// helperConfig: TypeHelperConfig
|
||||
// ) {
|
||||
// const content = loader.getPattern(elem, {
|
||||
// forceUpdate: helperConfig?.selectedElementWrapper?.uuid === elem.uuid
|
||||
// });
|
||||
// drawBox(ctx, elem, content);
|
||||
// }
|
||||
|
||||
|
||||
|
||||
141
packages/renderer/src/lib/draw/text.ts
Normal file
141
packages/renderer/src/lib/draw/text.ts
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import {
|
||||
TypeContext,
|
||||
TypeElemDescText,
|
||||
TypeElement,
|
||||
} from '@idraw/types';
|
||||
import util from '@idraw/util';
|
||||
import Loader from '../loader';
|
||||
import { clearContext, drawBox } from './base';
|
||||
import { rotateElement } from './../transform';
|
||||
import is from './../is';
|
||||
|
||||
export function drawText(
|
||||
ctx: TypeContext,
|
||||
elem: TypeElement<'text'>,
|
||||
loader: Loader,
|
||||
) {
|
||||
clearContext(ctx);
|
||||
drawBox(ctx, elem, elem.desc.bgColor || 'transparent');
|
||||
rotateElement(ctx, elem, () => {
|
||||
|
||||
const desc: TypeElemDescText = {
|
||||
...{
|
||||
fontSize: 12,
|
||||
fontFamily: 'sans-serif',
|
||||
textAlign: 'center',
|
||||
},
|
||||
...elem.desc
|
||||
};
|
||||
ctx.setFillStyle(elem.desc.color);
|
||||
ctx.setTextBaseline('top');
|
||||
ctx.setFont({
|
||||
fontWeight: desc.fontWeight,
|
||||
fontSize: desc.fontSize,
|
||||
fontFamily: desc.fontFamily
|
||||
});
|
||||
const descText = desc.text.replace(/\r\n/ig, '\n');
|
||||
const fontHeight = desc.lineHeight || desc.fontSize;
|
||||
const descTextList = descText.split('\n');
|
||||
const lines: {text: string, width: number}[] = [];
|
||||
|
||||
descTextList.forEach((tempText) => {
|
||||
let lineText = '';
|
||||
let lineNum = 0;
|
||||
for (let i = 0; i < tempText.length; i++) {
|
||||
if (ctx.measureText(lineText + (tempText[i] || '')).width < ctx.calcDeviceNum(elem.w)) {
|
||||
lineText += (tempText[i] || '');
|
||||
} else {
|
||||
lines.push({
|
||||
text: lineText,
|
||||
width: ctx.calcScreenNum(ctx.measureText(lineText).width),
|
||||
});
|
||||
lineText = (tempText[i] || '');
|
||||
lineNum ++;
|
||||
}
|
||||
if ((lineNum + 1) * fontHeight > elem.h) {
|
||||
break;
|
||||
}
|
||||
if (lineText && tempText.length - 1 === i) {
|
||||
if ((lineNum + 1) * fontHeight < elem.h) {
|
||||
lines.push({
|
||||
text: lineText,
|
||||
width: ctx.calcScreenNum(ctx.measureText(lineText).width),
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// draw text lines
|
||||
{
|
||||
let _y = elem.y;
|
||||
if (lines.length * fontHeight < elem.h) {
|
||||
_y += ((elem.h - lines.length * fontHeight) / 2);
|
||||
}
|
||||
if (desc.textShadowColor !== undefined && util.color.isColorStr(desc.textShadowColor)) {
|
||||
ctx.setShadowColor(desc.textShadowColor);
|
||||
}
|
||||
if (desc.textShadowOffsetX !== undefined && is.number(desc.textShadowOffsetX)) {
|
||||
ctx.setShadowOffsetX(desc.textShadowOffsetX);
|
||||
}
|
||||
if (desc.textShadowOffsetY !== undefined && is.number(desc.textShadowOffsetY)) {
|
||||
ctx.setShadowOffsetY(desc.textShadowOffsetY);
|
||||
}
|
||||
if (desc.textShadowBlur !== undefined && is.number(desc.textShadowBlur)) {
|
||||
ctx.setShadowBlur(desc.textShadowBlur);
|
||||
}
|
||||
lines.forEach((line, i) => {
|
||||
let _x = elem.x;
|
||||
if (desc.textAlign === 'center') {
|
||||
_x = elem.x + (elem.w - line.width) / 2;
|
||||
} else if (desc.textAlign === 'right') {
|
||||
_x = elem.x + (elem.w - line.width);
|
||||
}
|
||||
ctx.fillText(line.text, _x, _y + fontHeight * i);
|
||||
});
|
||||
clearContext(ctx);
|
||||
}
|
||||
|
||||
// draw text stroke
|
||||
if (util.color.isColorStr(desc.strokeColor) && desc.strokeWidth !== undefined && desc.strokeWidth > 0) {
|
||||
let _y = elem.y;
|
||||
if (lines.length * fontHeight < elem.h) {
|
||||
_y += ((elem.h - lines.length * fontHeight) / 2);
|
||||
}
|
||||
lines.forEach((line, i) => {
|
||||
let _x = elem.x;
|
||||
if (desc.textAlign === 'center') {
|
||||
_x = elem.x + (elem.w - line.width) / 2;
|
||||
} else if (desc.textAlign === 'right') {
|
||||
_x = elem.x + (elem.w - line.width);
|
||||
}
|
||||
if (desc.strokeColor !== undefined) {
|
||||
ctx.setStrokeStyle(desc.strokeColor);
|
||||
}
|
||||
if (desc.strokeWidth !== undefined && desc.strokeWidth > 0) {
|
||||
ctx.setLineWidth(desc.strokeWidth);
|
||||
}
|
||||
ctx.strokeText(line.text, _x, _y + fontHeight * i);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// export function createTextSVG(elem: TypeElement<'text'>): string {
|
||||
// const svg = `
|
||||
// <svg xmlns="http://www.w3.org/2000/svg" width="${elem.w}" height = "${elem.h}">
|
||||
// <foreignObject width="100%" height="100%">
|
||||
// <div xmlns = "http://www.w3.org/1999/xhtml" style="font-size: ${elem.desc.size}px; color:${elem.desc.color};">
|
||||
// <span>${elem.desc.text || ''}</span>
|
||||
// </div>
|
||||
// </foreignObject>
|
||||
// </svg>
|
||||
// `;
|
||||
// return svg;
|
||||
// }
|
||||
|
||||
|
||||
5
packages/renderer/src/lib/element/element-base.ts
Normal file
5
packages/renderer/src/lib/element/element-base.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
class ElementBase {
|
||||
constructor() {
|
||||
|
||||
}
|
||||
}
|
||||
6
packages/renderer/src/lib/element/element-controller.ts
Normal file
6
packages/renderer/src/lib/element/element-controller.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
class ElementController {
|
||||
// TODO
|
||||
}
|
||||
|
||||
export default ElementController;
|
||||
26
packages/renderer/src/lib/element/element-hub.ts
Normal file
26
packages/renderer/src/lib/element/element-hub.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import ElementController from './element-controller';
|
||||
|
||||
class ElementHub {
|
||||
|
||||
private _controllerMap: Map<string, ElementController> = new Map();
|
||||
|
||||
constructor() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
register(type: string, controller: ElementController) {
|
||||
if (this._controllerMap.has(type) !== true) {
|
||||
this._controllerMap.set(type, controller)
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._controllerMap.clear();
|
||||
}
|
||||
|
||||
getDrawActions() {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
export default ElementHub;
|
||||
12
packages/renderer/src/lib/index.ts
Normal file
12
packages/renderer/src/lib/index.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export * from './draw/index';
|
||||
export * from './check';
|
||||
export * from './config';
|
||||
export * from './core-event';
|
||||
export * from './diff';
|
||||
export * from './is';
|
||||
export * from './loader-event';
|
||||
export * from './loader';
|
||||
export * from './parse';
|
||||
export * from './temp';
|
||||
export * from './transform';
|
||||
export * from './value';
|
||||
137
packages/renderer/src/lib/is.ts
Normal file
137
packages/renderer/src/lib/is.ts
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import util from "@idraw/util";
|
||||
|
||||
const { isColorStr } = util.color;
|
||||
|
||||
|
||||
|
||||
function number(value: any) {
|
||||
return (typeof value === 'number' && (value > 0 || value <= 0));
|
||||
}
|
||||
|
||||
function x(value: any) {
|
||||
return number(value);
|
||||
}
|
||||
|
||||
function y(value: any) {
|
||||
return number(value);
|
||||
}
|
||||
|
||||
function w(value: any) {
|
||||
return (typeof value === 'number' && value >= 0);
|
||||
}
|
||||
|
||||
function h(value: any) {
|
||||
return (typeof value === 'number' && value >= 0);
|
||||
}
|
||||
|
||||
function angle(value: any) {
|
||||
return (typeof value === 'number' && value >= -360 && value <= 360);
|
||||
}
|
||||
|
||||
function borderWidth(value: any) {
|
||||
return w(value);
|
||||
}
|
||||
|
||||
function borderRadius(value: any) {
|
||||
return number(value) && value >= 0;
|
||||
}
|
||||
|
||||
function color(value: any) {
|
||||
return isColorStr(value);
|
||||
}
|
||||
|
||||
function imageURL(value: any) {
|
||||
return (typeof value === 'string' && /^(http:\/\/|https:\/\/|\.\/|\/)/.test(`${value}`));
|
||||
}
|
||||
|
||||
function imageBase64(value: any) {
|
||||
return (typeof value === 'string' && /^(data:image\/)/.test(`${value}`));
|
||||
}
|
||||
|
||||
function imageSrc(value: any) {
|
||||
return (imageBase64(value) || imageURL(value));
|
||||
}
|
||||
|
||||
function svg(value: any) {
|
||||
return (typeof value === 'string' && /^(<svg[\s]{1,}|<svg>)/i.test(`${value}`.trim()) && /<\/[\s]{0,}svg>$/i.test(`${value}`.trim()));
|
||||
}
|
||||
|
||||
function html(value: any) {
|
||||
let result = false;
|
||||
if (typeof value === 'string') {
|
||||
let div: null | HTMLDivElement = document.createElement('div');
|
||||
div.innerHTML = value;
|
||||
if (div.children.length > 0) {
|
||||
result = true;
|
||||
}
|
||||
div = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function text(value: any) {
|
||||
return typeof value === 'string';
|
||||
}
|
||||
|
||||
function fontSize(value: any) {
|
||||
return number(value) && value > 0;
|
||||
}
|
||||
|
||||
function lineHeight(value: any) {
|
||||
return number(value) && value > 0;
|
||||
}
|
||||
|
||||
function strokeWidth(value: any) {
|
||||
return number(value) && value > 0;
|
||||
}
|
||||
|
||||
function textAlign(value: any) {
|
||||
return ['center', 'left', 'right'].includes(value);
|
||||
}
|
||||
|
||||
function fontFamily(value: any) {
|
||||
return typeof value === 'string' && value.length > 0;
|
||||
}
|
||||
|
||||
function fontWeight(value: any) {
|
||||
return ['bold'].includes(value);
|
||||
}
|
||||
|
||||
const is: TypeIs = {
|
||||
x, y, w, h, angle, number,
|
||||
borderWidth, borderRadius, color,
|
||||
imageSrc, imageURL, imageBase64, svg, html,
|
||||
text, fontSize, lineHeight, textAlign, fontFamily, fontWeight,
|
||||
strokeWidth,
|
||||
};
|
||||
|
||||
type TypeIs = {
|
||||
x: (value: any) => boolean,
|
||||
y: (value: any) => boolean,
|
||||
w: (value: any) => boolean,
|
||||
h: (value: any) => boolean,
|
||||
angle: (value: any) => boolean,
|
||||
number: (value: any) => boolean,
|
||||
borderWidth: (value: any) => boolean,
|
||||
borderRadius: (value: any) => boolean,
|
||||
color: (value: any) => boolean,
|
||||
imageSrc: (value: any) => boolean,
|
||||
imageURL: (value: any) => boolean,
|
||||
imageBase64: (value: any) => boolean,
|
||||
svg: (value: any) => boolean,
|
||||
html: (value: any) => boolean,
|
||||
text: (value: any) => boolean,
|
||||
fontSize: (value: any) => boolean,
|
||||
fontWeight: (value: any) => boolean,
|
||||
lineHeight: (value: any) => boolean,
|
||||
textAlign: (value: any) => boolean,
|
||||
fontFamily: (value: any) => boolean,
|
||||
strokeWidth: (value: any) => boolean,
|
||||
}
|
||||
|
||||
export default is;
|
||||
|
||||
|
||||
export {
|
||||
TypeIs,
|
||||
};
|
||||
83
packages/renderer/src/lib/loader-event.ts
Normal file
83
packages/renderer/src/lib/loader-event.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
|
||||
|
||||
export type TypeLoadData = {
|
||||
[uuid: string]: {
|
||||
type: 'image' | 'svg' | 'html',
|
||||
status: 'null' | 'loaded' | 'fail',
|
||||
content: null | HTMLImageElement | HTMLCanvasElement,
|
||||
elemW: number;
|
||||
elemH: number;
|
||||
source: string,
|
||||
error?: any,
|
||||
}
|
||||
}
|
||||
|
||||
export type TypeLoaderEventArgMap = {
|
||||
'complete': undefined;
|
||||
'load': TypeLoadData[string];
|
||||
'error': TypeLoadData[string];
|
||||
}
|
||||
|
||||
export interface TypeLoaderEvent {
|
||||
on<T extends keyof TypeLoaderEventArgMap >(key: T, callback: (p: TypeLoaderEventArgMap[T]) => void): void
|
||||
off<T extends keyof TypeLoaderEventArgMap >(key: T, callback: (p: TypeLoaderEventArgMap[T]) => void): void
|
||||
trigger<T extends keyof TypeLoaderEventArgMap >(key: T, p: TypeLoaderEventArgMap[T]): void
|
||||
}
|
||||
|
||||
|
||||
export class LoaderEvent implements TypeLoaderEvent {
|
||||
|
||||
private _listeners: Map<string, ((p: any) => void)[]>;
|
||||
|
||||
constructor() {
|
||||
this._listeners = new Map();
|
||||
}
|
||||
|
||||
on<T extends keyof TypeLoaderEventArgMap >(eventKey: T, callback: (p: TypeLoaderEventArgMap[T]) => void) {
|
||||
if (this._listeners.has(eventKey)) {
|
||||
const callbacks = this._listeners.get(eventKey);
|
||||
callbacks?.push(callback);
|
||||
this._listeners.set(eventKey, callbacks || []);
|
||||
} else {
|
||||
this._listeners.set(eventKey, [callback]);
|
||||
}
|
||||
}
|
||||
|
||||
off<T extends keyof TypeLoaderEventArgMap >(eventKey: T, callback: (p: TypeLoaderEventArgMap[T]) => void) {
|
||||
if (this._listeners.has(eventKey)) {
|
||||
const callbacks = this._listeners.get(eventKey);
|
||||
if (Array.isArray(callbacks)) {
|
||||
for (let i = 0; i < callbacks?.length; i++) {
|
||||
if (callbacks[i] === callback) {
|
||||
callbacks.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._listeners.set(eventKey, callbacks || []);
|
||||
}
|
||||
}
|
||||
|
||||
trigger<T extends keyof TypeLoaderEventArgMap >(eventKey: T, arg: TypeLoaderEventArgMap[T]) {
|
||||
const callbacks = this._listeners.get(eventKey);
|
||||
if (Array.isArray(callbacks)) {
|
||||
callbacks.forEach((cb) => {
|
||||
cb(arg);
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
has<T extends keyof TypeLoaderEventArgMap> (name: string) {
|
||||
if (this._listeners.has(name)) {
|
||||
const list: ((p: TypeLoaderEventArgMap[T]) => void)[] | undefined = this._listeners.get(name);
|
||||
if (Array.isArray(list) && list.length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
321
packages/renderer/src/lib/loader.ts
Normal file
321
packages/renderer/src/lib/loader.ts
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
import { TypeData, TypeElement } from '@idraw/types';
|
||||
import util from '@idraw/util';
|
||||
import { LoaderEvent, TypeLoadData, TypeLoaderEventArgMap } from './loader-event';
|
||||
import { filterScript } from './../util/filter';
|
||||
|
||||
const { loadImage, loadSVG, loadHTML } = util.loader;
|
||||
|
||||
type Options = {
|
||||
maxParallelNum: number
|
||||
}
|
||||
|
||||
enum LoaderStatus {
|
||||
FREE = 'free',
|
||||
LOADING = 'loading',
|
||||
COMPLETE = 'complete',
|
||||
}
|
||||
|
||||
export default class Loader {
|
||||
|
||||
private _opts: Options;
|
||||
private _event: LoaderEvent;
|
||||
// private _patternMap: {[uuid: string]: CanvasPattern} = {}
|
||||
private _currentLoadData: TypeLoadData = {};
|
||||
private _currentUUIDQueue: string[] = [];
|
||||
private _storageLoadData: TypeLoadData = {};
|
||||
private _status: LoaderStatus = LoaderStatus.FREE;
|
||||
|
||||
private _waitingLoadQueue: Array<{
|
||||
uuidQueue: string[],
|
||||
loadData: TypeLoadData,
|
||||
}> = [];
|
||||
|
||||
constructor(opts: Options) {
|
||||
this._opts = opts;
|
||||
this._event = new LoaderEvent();
|
||||
this._waitingLoadQueue = [];
|
||||
}
|
||||
|
||||
load(data: TypeData, changeResourceUUIDs: string[]): void {
|
||||
const [uuidQueue, loadData] = this._resetLoadData(data, changeResourceUUIDs);
|
||||
if (this._status === LoaderStatus.FREE || this._status === LoaderStatus.COMPLETE) {
|
||||
this._currentUUIDQueue = uuidQueue;
|
||||
this._currentLoadData = loadData;
|
||||
this._loadTask();
|
||||
} else if (this._status === LoaderStatus.LOADING && uuidQueue.length > 0) {
|
||||
this._waitingLoadQueue.push({
|
||||
uuidQueue,
|
||||
loadData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
on<T extends keyof TypeLoaderEventArgMap>(
|
||||
name: T,
|
||||
callback: (arg: TypeLoaderEventArgMap[T]
|
||||
) => void) {
|
||||
this._event.on(name, callback);
|
||||
}
|
||||
|
||||
off<T extends keyof TypeLoaderEventArgMap>(
|
||||
name: T,
|
||||
callback: (arg: TypeLoaderEventArgMap[T]
|
||||
) => void) {
|
||||
this._event.off(name, callback);
|
||||
}
|
||||
|
||||
isComplete() {
|
||||
return this._status === LoaderStatus.COMPLETE;
|
||||
}
|
||||
|
||||
getContent(uuid: string): null | HTMLImageElement | HTMLCanvasElement {
|
||||
if (this._storageLoadData[uuid]?.status === 'loaded') {
|
||||
return this._storageLoadData[uuid].content;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// getPattern(
|
||||
// elem: TypeElement<keyof TypeElemDesc>,
|
||||
// opts?: {
|
||||
// forceUpdate: boolean
|
||||
// }
|
||||
// ): null | CanvasPattern {
|
||||
// if (this._patternMap[elem.uuid] ) {
|
||||
// if (!(opts && opts.forceUpdate === true)) {
|
||||
// return this._patternMap[elem.uuid];
|
||||
// }
|
||||
// }
|
||||
// const item = this._currentLoadData[elem.uuid];
|
||||
// if (item?.status === 'loaded') {
|
||||
// const board = this._opts.board;
|
||||
// const tempCanvas = board.createCanvas();
|
||||
// const tempCtx = board.createContext(tempCanvas);
|
||||
// const image = this.getContent(elem.uuid);
|
||||
// tempCtx.drawImage(image, elem.x, elem.y, elem.w, elem.h);
|
||||
|
||||
// const canvas = board.createCanvas();
|
||||
// const ctx = board.createContext(canvas);
|
||||
// const pattern = ctx.createPattern(tempCanvas, 'no-repeat');
|
||||
// if (pattern) this._patternMap[elem.uuid] = pattern;
|
||||
// return pattern;
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
private _resetLoadData(data: TypeData, changeResourceUUIDs: string[]): [string[], TypeLoadData] {
|
||||
const loadData: TypeLoadData = {};
|
||||
const uuidQueue: string[] = [];
|
||||
|
||||
const storageLoadData = this._storageLoadData;
|
||||
|
||||
// add new load-data
|
||||
for (let i = data.elements.length - 1; i >= 0; i --) {
|
||||
const elem = data.elements[i] as TypeElement<'image' | 'svg' | 'html'>;
|
||||
if (['image', 'svg', 'html', ].includes(elem.type)) {
|
||||
if (!storageLoadData[elem.uuid]) {
|
||||
loadData[elem.uuid] = this._createEmptyLoadItem(elem);
|
||||
uuidQueue.push(elem.uuid);
|
||||
} else {
|
||||
if (changeResourceUUIDs.includes(elem.uuid)) {
|
||||
loadData[elem.uuid] = this._createEmptyLoadItem(elem);
|
||||
uuidQueue.push(elem.uuid);
|
||||
}
|
||||
// if (elem.type === 'image') {
|
||||
// const _ele = elem as TypeElement<'image'>;
|
||||
// if (_ele.desc.src !== storageLoadData[elem.uuid].source) {
|
||||
// loadData[elem.uuid] = this._createEmptyLoadItem(elem);
|
||||
// uuidQueue.push(elem.uuid);
|
||||
// }
|
||||
// } else if (elem.type === 'svg') {
|
||||
// const _ele = elem as TypeElement<'svg'>;
|
||||
// if (_ele.desc.svg !== storageLoadData[elem.uuid].source) {
|
||||
// loadData[elem.uuid] = this._createEmptyLoadItem(elem);
|
||||
// uuidQueue.push(elem.uuid);
|
||||
// }
|
||||
// } else if (elem.type === 'html') {
|
||||
// const _ele = elem as TypeElement<'html'>;
|
||||
// if (filterScript(_ele.desc.html) !== storageLoadData[elem.uuid].source) {
|
||||
// loadData[elem.uuid] = this._createEmptyLoadItem(elem);
|
||||
// uuidQueue.push(elem.uuid);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // clear unuse load-data
|
||||
// const uuids = Object.keys(currentLoadData);
|
||||
// data.elements.forEach((elem) => {
|
||||
// if (uuids.includes(elem.uuid) !== true) {
|
||||
// delete loadData[elem.uuid];
|
||||
// }
|
||||
// });
|
||||
|
||||
return [uuidQueue, loadData];
|
||||
}
|
||||
|
||||
private _createEmptyLoadItem(elem: TypeElement<'image' | 'svg' | 'html'>): TypeLoadData[string] {
|
||||
let source = '';
|
||||
|
||||
const type: TypeLoadData[string]['type'] = elem.type as TypeLoadData[string]['type'];
|
||||
let elemW: number = elem.w;
|
||||
let elemH: number = elem.h;
|
||||
if (elem.type === 'image') {
|
||||
const _elem = elem as TypeElement<'image'>;
|
||||
source = _elem.desc.src || '';
|
||||
} else if (elem.type === 'svg') {
|
||||
const _elem = elem as TypeElement<'svg'>;
|
||||
source = _elem.desc.svg || '';
|
||||
} else if (elem.type === 'html') {
|
||||
const _elem = elem as TypeElement<'html'>;
|
||||
source = filterScript(_elem.desc.html || '');
|
||||
elemW = _elem.desc.width || elem.w;
|
||||
elemH = _elem.desc.height || elem.h;
|
||||
}
|
||||
return {
|
||||
type: type,
|
||||
status: 'null',
|
||||
content: null,
|
||||
source,
|
||||
elemW,
|
||||
elemH,
|
||||
};
|
||||
}
|
||||
|
||||
private _loadTask() {
|
||||
if (this._status === LoaderStatus.LOADING) {
|
||||
return;
|
||||
}
|
||||
this._status = LoaderStatus.LOADING;
|
||||
|
||||
if (this._currentUUIDQueue.length === 0) {
|
||||
if (this._waitingLoadQueue.length === 0) {
|
||||
this._status = LoaderStatus.COMPLETE;
|
||||
this._event.trigger('complete', undefined);
|
||||
return;
|
||||
} else {
|
||||
const waitingItem = this._waitingLoadQueue.shift();
|
||||
if (waitingItem) {
|
||||
const { uuidQueue, loadData } = waitingItem;
|
||||
this._currentLoadData = loadData;
|
||||
this._currentUUIDQueue = uuidQueue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { maxParallelNum } = this._opts;
|
||||
const uuids = this._currentUUIDQueue.splice(0, maxParallelNum);
|
||||
const uuidMap: {[uuid: string]: number} = {};
|
||||
|
||||
uuids.forEach((url, i) => {
|
||||
uuidMap[url] = i;
|
||||
});
|
||||
const loadUUIDList: string[] = [];
|
||||
const _loadAction = () => {
|
||||
|
||||
if (loadUUIDList.length >= maxParallelNum) {
|
||||
return false;
|
||||
}
|
||||
if (uuids.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (let i = loadUUIDList.length; i < maxParallelNum; i++) {
|
||||
const uuid = uuids.shift();
|
||||
if (uuid === undefined) {
|
||||
break;
|
||||
}
|
||||
loadUUIDList.push(uuid);
|
||||
|
||||
this._loadElementSource(this._currentLoadData[uuid]).then((image) => {
|
||||
loadUUIDList.splice(loadUUIDList.indexOf(uuid), 1);
|
||||
const status = _loadAction();
|
||||
|
||||
this._storageLoadData[uuid] = {
|
||||
type: this._currentLoadData[uuid].type,
|
||||
status: 'loaded',
|
||||
content: image,
|
||||
source: this._currentLoadData[uuid].source,
|
||||
elemW: this._currentLoadData[uuid].elemW,
|
||||
elemH: this._currentLoadData[uuid].elemH,
|
||||
};
|
||||
|
||||
if (loadUUIDList.length === 0 && uuids.length === 0 && status === true) {
|
||||
this._status = LoaderStatus.FREE;
|
||||
this._loadTask();
|
||||
}
|
||||
this._event.trigger('load', {
|
||||
type: this._storageLoadData[uuid].type,
|
||||
status: this._storageLoadData[uuid].status,
|
||||
content: this._storageLoadData[uuid].content,
|
||||
source: this._storageLoadData[uuid].source,
|
||||
elemW: this._storageLoadData[uuid].elemW,
|
||||
elemH: this._storageLoadData[uuid].elemH,
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.warn(err);
|
||||
|
||||
loadUUIDList.splice(loadUUIDList.indexOf(uuid), 1);
|
||||
const status = _loadAction();
|
||||
|
||||
if (this._currentLoadData[uuid]) {
|
||||
this._storageLoadData[uuid] = {
|
||||
type: this._currentLoadData[uuid]?.type,
|
||||
status: 'fail',
|
||||
content: null,
|
||||
error: err,
|
||||
source: this._currentLoadData[uuid]?.source,
|
||||
elemW: this._currentLoadData[uuid]?.elemW,
|
||||
elemH: this._currentLoadData[uuid]?.elemH,
|
||||
};
|
||||
}
|
||||
|
||||
if (loadUUIDList.length === 0 && uuids.length === 0 && status === true) {
|
||||
this._status = LoaderStatus.FREE;
|
||||
this._loadTask();
|
||||
}
|
||||
|
||||
if (this._currentLoadData[uuid]) {
|
||||
this._event.trigger('error', {
|
||||
type: this._storageLoadData[uuid]?.type,
|
||||
status: this._storageLoadData[uuid]?.status,
|
||||
content: this._storageLoadData[uuid]?.content,
|
||||
source: this._storageLoadData[uuid]?.source,
|
||||
elemW: this._storageLoadData[uuid]?.elemW,
|
||||
elemH: this._storageLoadData[uuid]?.elemH,
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
return false;
|
||||
};
|
||||
_loadAction();
|
||||
}
|
||||
|
||||
private async _loadElementSource(
|
||||
params: TypeLoadData[string]
|
||||
): Promise<HTMLImageElement> {
|
||||
if (params && params.type === 'image') {
|
||||
const image = await loadImage(params.source);
|
||||
return image;
|
||||
} else if (params && params.type === 'svg') {
|
||||
const image = await loadSVG(
|
||||
params.source
|
||||
);
|
||||
return image;
|
||||
} else if (params && params.type === 'html') {
|
||||
const image = await loadHTML(
|
||||
params.source, {
|
||||
width: params.elemW, height: params.elemH
|
||||
}
|
||||
);
|
||||
return image;
|
||||
}
|
||||
throw Error('Element\'s source is not support!');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
36
packages/renderer/src/lib/parse.ts
Normal file
36
packages/renderer/src/lib/parse.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { TypeData, TypeElement, TypeElemDesc } from '@idraw/types';
|
||||
import { elementNames } from './../constant/element';
|
||||
|
||||
export function parseData(data: any): TypeData {
|
||||
const result: TypeData = {
|
||||
elements: [],
|
||||
};
|
||||
if (Array.isArray(data?.elements)) {
|
||||
data?.elements.forEach((elem: any = {}) => {
|
||||
if (isElement(elem)) {
|
||||
result.elements.push(elem);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (typeof data.bgColor === 'string') {
|
||||
result.bgColor = data.bgColor;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function isElement(
|
||||
elem: TypeElement<keyof TypeElemDesc>
|
||||
): boolean{
|
||||
if (!(isNumber(elem.x) && isNumber(elem.y) && isNumber(elem.w) && isNumber(elem.h))) {
|
||||
return false;
|
||||
}
|
||||
if (!(typeof elem.type === 'string' && elementNames.includes(elem.type))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function isNumber(num: any) {
|
||||
return (num >= 0 || num < 0);
|
||||
}
|
||||
69
packages/renderer/src/lib/renderer-event.ts
Normal file
69
packages/renderer/src/lib/renderer-event.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
|
||||
export type TypeRendererEventArgMap = {
|
||||
'drawFrame': void;
|
||||
'drawFrameComplete': void;
|
||||
}
|
||||
|
||||
export interface TypeRendererEvent {
|
||||
on<T extends keyof TypeRendererEventArgMap >(key: T, callback: (p: TypeRendererEventArgMap[T]) => void): void
|
||||
off<T extends keyof TypeRendererEventArgMap >(key: T, callback: (p: TypeRendererEventArgMap[T]) => void): void
|
||||
trigger<T extends keyof TypeRendererEventArgMap >(key: T, p: TypeRendererEventArgMap[T]): void
|
||||
}
|
||||
|
||||
|
||||
export class RendererEvent implements TypeRendererEvent {
|
||||
|
||||
private _listeners: Map<string, ((p: any) => void)[]>;
|
||||
|
||||
constructor() {
|
||||
this._listeners = new Map();
|
||||
}
|
||||
|
||||
on<T extends keyof TypeRendererEventArgMap >(eventKey: T, callback: (p: TypeRendererEventArgMap[T]) => void) {
|
||||
if (this._listeners.has(eventKey)) {
|
||||
const callbacks = this._listeners.get(eventKey);
|
||||
callbacks?.push(callback);
|
||||
this._listeners.set(eventKey, callbacks || []);
|
||||
} else {
|
||||
this._listeners.set(eventKey, [callback]);
|
||||
}
|
||||
}
|
||||
|
||||
off<T extends keyof TypeRendererEventArgMap >(eventKey: T, callback: (p: TypeRendererEventArgMap[T]) => void) {
|
||||
if (this._listeners.has(eventKey)) {
|
||||
const callbacks = this._listeners.get(eventKey);
|
||||
if (Array.isArray(callbacks)) {
|
||||
for (let i = 0; i < callbacks?.length; i++) {
|
||||
if (callbacks[i] === callback) {
|
||||
callbacks.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._listeners.set(eventKey, callbacks || []);
|
||||
}
|
||||
}
|
||||
|
||||
trigger<T extends keyof TypeRendererEventArgMap >(eventKey: T, arg: TypeRendererEventArgMap[T]) {
|
||||
const callbacks = this._listeners.get(eventKey);
|
||||
if (Array.isArray(callbacks)) {
|
||||
callbacks.forEach((cb) => {
|
||||
cb(arg);
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
has<T extends keyof TypeRendererEventArgMap> (name: string) {
|
||||
if (this._listeners.has(name)) {
|
||||
const list: ((p: TypeRendererEventArgMap[T]) => void)[] | undefined = this._listeners.get(name);
|
||||
if (Array.isArray(list) && list.length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
52
packages/renderer/src/lib/temp.ts
Normal file
52
packages/renderer/src/lib/temp.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { TypeHelperWrapperControllerDirection, TypePoint } from '@idraw/types';
|
||||
import { Mode, CursorStatus } from './../constant/static';
|
||||
|
||||
type TempDataDesc = {
|
||||
hasInited: boolean;
|
||||
onlyRender: boolean;
|
||||
mode: Mode,
|
||||
cursorStatus: CursorStatus
|
||||
selectedUUID: string | null,
|
||||
selectedUUIDList: string[],
|
||||
hoverUUID: string | null,
|
||||
selectedControllerDirection: TypeHelperWrapperControllerDirection | null,
|
||||
hoverControllerDirection: TypeHelperWrapperControllerDirection | null,
|
||||
prevPoint: TypePoint | null,
|
||||
}
|
||||
|
||||
function createData(): TempDataDesc {
|
||||
return {
|
||||
onlyRender: false,
|
||||
hasInited: false,
|
||||
mode: Mode.NULL,
|
||||
cursorStatus: CursorStatus.NULL,
|
||||
selectedUUID: null,
|
||||
selectedUUIDList: [],
|
||||
hoverUUID: null,
|
||||
selectedControllerDirection: null,
|
||||
hoverControllerDirection: null,
|
||||
prevPoint: null,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class TempData {
|
||||
|
||||
private _temp: TempDataDesc
|
||||
|
||||
constructor() {
|
||||
this._temp = createData();
|
||||
}
|
||||
|
||||
set<T extends keyof TempDataDesc >(name: T, value: TempDataDesc[T]) {
|
||||
this._temp[name] = value;
|
||||
}
|
||||
|
||||
get<T extends keyof TempDataDesc >(name: T): TempDataDesc[T] {
|
||||
return this._temp[name];
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._temp = createData();
|
||||
}
|
||||
}
|
||||
44
packages/renderer/src/lib/transform.ts
Normal file
44
packages/renderer/src/lib/transform.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import {
|
||||
TypeContext,
|
||||
TypePoint,
|
||||
TypeElement,
|
||||
TypeElemDesc,
|
||||
} from '@idraw/types';
|
||||
import { calcElementCenter, parseAngleToRadian } from './calculate';
|
||||
|
||||
function rotateElement(
|
||||
ctx: TypeContext,
|
||||
elem: TypeElement<keyof TypeElemDesc>,
|
||||
callback: (ctx: TypeContext) => void
|
||||
): void {
|
||||
const center: TypePoint = calcElementCenter(elem);
|
||||
const radian = parseAngleToRadian(elem.angle || 0);
|
||||
return rotateContext(ctx, center, radian || 0, callback);
|
||||
}
|
||||
|
||||
|
||||
function rotateContext(
|
||||
ctx: TypeContext,
|
||||
center: TypePoint | undefined,
|
||||
radian: number,
|
||||
callback: (ctx: TypeContext) => void
|
||||
): void {
|
||||
if (center && (radian > 0 || radian < 0)) {
|
||||
ctx.translate(center.x, center.y);
|
||||
ctx.rotate(radian);
|
||||
ctx.translate(- center.x, - center.y);
|
||||
}
|
||||
|
||||
callback(ctx);
|
||||
|
||||
if (center && (radian > 0 || radian < 0)) {
|
||||
ctx.translate(center.x, center.y);
|
||||
ctx.rotate(- radian);
|
||||
ctx.translate(- center.x, - center.y);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
rotateContext,
|
||||
rotateElement,
|
||||
};
|
||||
9
packages/renderer/src/lib/value.ts
Normal file
9
packages/renderer/src/lib/value.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
export function limitNum(num: number): number {
|
||||
const numStr: string = num.toFixed(2);
|
||||
return parseFloat(numStr);
|
||||
}
|
||||
|
||||
export function limitAngle(angle: number): number {
|
||||
return limitNum(angle % 360);
|
||||
}
|
||||
182
packages/renderer/src/mixins/element.ts
Normal file
182
packages/renderer/src/mixins/element.ts
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
import {
|
||||
TypeElement, TypeElemDesc, TypeElementBase,
|
||||
} from '@idraw/types';
|
||||
import util from '@idraw/util';
|
||||
import {
|
||||
_board, _data, _opts, _config, _renderer, _element, _helper,
|
||||
_tempData, _draw, _coreEvent, _emitChangeScreen, _emitChangeData,
|
||||
} from './../names';
|
||||
import { diffElementResourceChange } from './../lib/diff';
|
||||
import Core from './../index';
|
||||
import { Mode } from './../constant/static';
|
||||
|
||||
// const { time } = util;
|
||||
const { deepClone } = util.data;
|
||||
const { createUUID } = util.uuid;
|
||||
|
||||
export function getSelectedElements(core: Core): TypeElement<keyof TypeElemDesc>[] {
|
||||
const elems: TypeElement<keyof TypeElemDesc>[] = [];
|
||||
let list: string[] = [];
|
||||
const uuid = core[_tempData].get('selectedUUID');
|
||||
if (typeof uuid === 'string' && uuid) {
|
||||
list.push(uuid);
|
||||
} else {
|
||||
list = core[_tempData].get('selectedUUIDList');
|
||||
}
|
||||
list.forEach((uuid) => {
|
||||
const index = core[_helper].getElementIndexByUUID(uuid);
|
||||
if (index !== null && index >= 0) {
|
||||
const elem = core[_data]?.elements[index];
|
||||
if (elem) elems.push(elem);
|
||||
}
|
||||
});
|
||||
return deepClone(elems);
|
||||
}
|
||||
|
||||
export function getElement(core: Core, uuid: string): TypeElement<keyof TypeElemDesc>|null {
|
||||
let elem: TypeElement<keyof TypeElemDesc>|null = null;
|
||||
const index = core[_helper].getElementIndexByUUID(uuid);
|
||||
if (index !== null && core[_data].elements[index]) {
|
||||
elem = deepClone(core[_data].elements[index]);
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
export function getElementByIndex(core: Core, index: number): TypeElement<keyof TypeElemDesc>|null {
|
||||
let elem: TypeElement<keyof TypeElemDesc>|null = null;
|
||||
if (index >=0 && core[_data].elements[index]) {
|
||||
elem = deepClone(core[_data].elements[index]);
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
export function updateElement(core: Core, elem: TypeElement<keyof TypeElemDesc>) {
|
||||
const _elem = deepClone(elem) as TypeElement<keyof TypeElemDesc>;
|
||||
const data = core[_data];
|
||||
const resourceChangeUUIDs: string[] = [];
|
||||
for (let i = 0; i < data.elements.length; i++) {
|
||||
if (_elem.uuid === data.elements[i]?.uuid) {
|
||||
const result = diffElementResourceChange(data.elements[i], _elem);
|
||||
if (typeof result === 'string') {
|
||||
resourceChangeUUIDs.push(result);
|
||||
}
|
||||
data.elements[i] = _elem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
core[_emitChangeData]();
|
||||
core[_draw]({ resourceChangeUUIDs });
|
||||
}
|
||||
|
||||
export function selectElementByIndex(core: Core, index: number, opts?: { useMode?: boolean }): void {
|
||||
if (core[_tempData].get('onlyRender') === true) return;
|
||||
if (core[_data].elements[index]) {
|
||||
const uuid = core[_data].elements[index].uuid;
|
||||
if (opts?.useMode === true) {
|
||||
core[_tempData].set('mode', Mode.SELECT_ELEMENT);
|
||||
} else {
|
||||
core[_tempData].set('mode', Mode.NULL);
|
||||
}
|
||||
if (typeof uuid === 'string') {
|
||||
core[_tempData].set('selectedUUID', uuid);
|
||||
core[_tempData].set('selectedUUIDList', []);
|
||||
}
|
||||
core[_draw]();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function selectElement(core: Core, uuid: string, opts?: { useMode?: boolean }): void {
|
||||
if (core[_tempData].get('onlyRender') === true) return;
|
||||
const index = core[_helper].getElementIndexByUUID(uuid);
|
||||
if (typeof index === 'number' && index >= 0) {
|
||||
core.selectElementByIndex(index, opts);
|
||||
}
|
||||
}
|
||||
|
||||
export function moveUpElement(core: Core, uuid: string): void {
|
||||
// if (this[_onlyRender] === true) return;
|
||||
const index = core[_helper].getElementIndexByUUID(uuid);
|
||||
if (typeof index === 'number' && index >= 0 && index < core[_data].elements.length - 1) {
|
||||
const temp = core[_data].elements[index];
|
||||
core[_data].elements[index] = core[_data].elements[index + 1];
|
||||
core[_data].elements[index + 1] = temp;
|
||||
}
|
||||
core[_emitChangeData]();
|
||||
core[_draw]();
|
||||
}
|
||||
|
||||
export function moveDownElement(core: Core, uuid: string): void {
|
||||
// if (this[_onlyRender] === true) return;
|
||||
const index = core[_helper].getElementIndexByUUID(uuid);
|
||||
if (typeof index === 'number' && index > 0 && index < core[_data].elements.length) {
|
||||
const temp = core[_data].elements[index];
|
||||
core[_data].elements[index] = core[_data].elements[index - 1];
|
||||
core[_data].elements[index - 1] = temp;
|
||||
}
|
||||
core[_emitChangeData]();
|
||||
core[_draw]();
|
||||
}
|
||||
|
||||
|
||||
export function addElement(core: Core, elem: TypeElementBase<keyof TypeElemDesc>): string | null {
|
||||
// if (this[_onlyRender] === true) return null;
|
||||
const _elem = deepClone(elem);
|
||||
_elem.uuid = createUUID();
|
||||
core[_data].elements.push(_elem);
|
||||
core[_emitChangeData]();
|
||||
core[_draw]();
|
||||
return _elem.uuid;
|
||||
}
|
||||
|
||||
export function deleteElement(core: Core, uuid: string) {
|
||||
// if (this[_onlyRender] === true) return;
|
||||
const index = core[_element].getElementIndex(core[_data], uuid);
|
||||
if (index >= 0) {
|
||||
core[_data].elements.splice(index, 1);
|
||||
core[_emitChangeData]();
|
||||
core[_draw]();
|
||||
}
|
||||
}
|
||||
|
||||
export function insertElementBefore(core: Core, elem: TypeElementBase<keyof TypeElemDesc>, beforeUUID: string) {
|
||||
const index = core[_helper].getElementIndexByUUID(beforeUUID);
|
||||
if (index !== null) {
|
||||
return core.insertElementBeforeIndex(elem, index);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
export function insertElementBeforeIndex(core: Core, elem: TypeElementBase<keyof TypeElemDesc>, index: number) {
|
||||
const _elem = deepClone(elem);
|
||||
_elem.uuid = createUUID();
|
||||
if (index >= 0) {
|
||||
core[_data].elements.splice(index, 0, _elem);
|
||||
core[_emitChangeData]();
|
||||
core[_draw]();
|
||||
return _elem.uuid;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
export function insertElementAfter(core: Core, elem: TypeElementBase<keyof TypeElemDesc>, beforeUUID: string) {
|
||||
const index = core[_helper].getElementIndexByUUID(beforeUUID);
|
||||
if (index !== null) {
|
||||
return core.insertElementAfterIndex(elem, index);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function insertElementAfterIndex(core: Core, elem: TypeElementBase<keyof TypeElemDesc>, index: number) {
|
||||
const _elem = deepClone(elem);
|
||||
_elem.uuid = createUUID();
|
||||
if (index >= 0) {
|
||||
core[_data].elements.splice(index + 1, 0, _elem);
|
||||
core[_emitChangeData]();
|
||||
core[_draw]();
|
||||
return _elem.uuid;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
298
packages/renderer/src/mixins/event.ts
Normal file
298
packages/renderer/src/mixins/event.ts
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
import { TypePoint, TypeHelperWrapperControllerDirection } from '@idraw/types';
|
||||
import util from '@idraw/util';
|
||||
import Core from './../index';
|
||||
import {
|
||||
_board, _data, _opts, _config, _renderer, _element, _helper,
|
||||
_tempData, _draw, _coreEvent, _mapper,
|
||||
_emitChangeScreen, _emitChangeData,
|
||||
} from './../names';
|
||||
import { Mode, CursorStatus } from './../constant/static';
|
||||
|
||||
const { time } = util;
|
||||
const { deepClone } = util.data;
|
||||
|
||||
export function initEvent(core: Core): void {
|
||||
|
||||
if (core[_tempData].get('hasInited') === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
core[_board].on('hover', time.throttle(handleHover(core), 32));
|
||||
core[_board].on('leave', time.throttle(handleLeave(core), 32));
|
||||
core[_board].on('point', time.throttle(handleClick(core), 16));
|
||||
core[_board].on('doubleClick', handleDoubleClick(core));
|
||||
if (core[_tempData].get('onlyRender') === true) {
|
||||
return;
|
||||
}
|
||||
core[_board].on('point', handlePoint(core));
|
||||
core[_board].on('moveStart', handleMoveStart(core));
|
||||
core[_board].on('move', time.throttle(handleMove(core), 16));
|
||||
core[_board].on('moveEnd', handleMoveEnd(core));
|
||||
|
||||
core[_renderer].on('drawFrame', () => {
|
||||
core[_coreEvent].trigger('drawFrame', undefined);
|
||||
});
|
||||
core[_renderer].on('drawFrameComplete', () => {
|
||||
core[_coreEvent].trigger('drawFrameComplete', undefined);
|
||||
})
|
||||
|
||||
core[_tempData].set('hasInited', true);
|
||||
}
|
||||
|
||||
|
||||
function handleDoubleClick(core: Core) {
|
||||
return function ( point: TypePoint) {
|
||||
const [index, uuid] = core[_element].isPointInElement(point, core[_data]);
|
||||
if (index >= 0 && uuid) {
|
||||
const elem = deepClone(core[_data].elements?.[index]);
|
||||
if (elem?.operation?.invisible !== true) {
|
||||
core[_coreEvent].trigger(
|
||||
'screenDoubleClickElement',
|
||||
{ index, uuid, element: deepClone(core[_data].elements?.[index])}
|
||||
);
|
||||
}
|
||||
}
|
||||
core[_draw]();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handlePoint(core: Core) {
|
||||
return function(point: TypePoint): void {
|
||||
if (!core[_mapper].isEffectivePoint(point)) {
|
||||
return;
|
||||
}
|
||||
if (core[_helper].isPointInElementList(point, core[_data])) {
|
||||
// Coontroll Element-List
|
||||
core[_tempData].set('mode', Mode.SELECT_ELEMENT_LIST);
|
||||
} else {
|
||||
const {
|
||||
uuid, selectedControllerDirection
|
||||
} = core[_helper].isPointInElementWrapperController(point, core[_data]);
|
||||
if (uuid && selectedControllerDirection) {
|
||||
// Controll Element-Wrapper
|
||||
core[_tempData].set('mode', Mode.SELECT_ELEMENT_WRAPPER_CONTROLLER);
|
||||
core[_tempData].set('selectedControllerDirection', selectedControllerDirection);
|
||||
core[_tempData].set('selectedUUID', uuid);
|
||||
} else {
|
||||
const [index, uuid] = core[_element].isPointInElement(point, core[_data]);
|
||||
if (index >= 0 && core[_data].elements[index]?.operation?.invisible !== true) {
|
||||
// Controll Element
|
||||
core.selectElementByIndex(index, { useMode: true });
|
||||
if (typeof uuid === 'string' && core[_coreEvent].has('screenSelectElement')) {
|
||||
core[_coreEvent].trigger(
|
||||
'screenSelectElement',
|
||||
{ index, uuid, element: deepClone(core[_data].elements?.[index])}
|
||||
);
|
||||
core[_emitChangeScreen]();
|
||||
}
|
||||
core[_tempData].set('mode', Mode.SELECT_ELEMENT);
|
||||
} else {
|
||||
// Controll Area
|
||||
core[_tempData].set('selectedUUIDList', []);
|
||||
core[_tempData].set('selectedUUID', null);
|
||||
core[_tempData].set('mode', Mode.SELECT_AREA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
core[_draw]();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleClick(core: Core) {
|
||||
return function(point: TypePoint): void {
|
||||
const [index, uuid] = core[_element].isPointInElement(point, core[_data]);
|
||||
if (index >= 0 && uuid) {
|
||||
core[_coreEvent].trigger(
|
||||
'screenClickElement',
|
||||
{ index, uuid, element: deepClone(core[_data].elements?.[index])}
|
||||
);
|
||||
}
|
||||
core[_draw]();
|
||||
}
|
||||
}
|
||||
|
||||
function handleMoveStart(core: Core) {
|
||||
return function(point: TypePoint): void {
|
||||
core[_tempData].set('prevPoint', point);
|
||||
const uuid = core[_tempData].get('selectedUUID');
|
||||
|
||||
if (core[_tempData].get('mode') === Mode.SELECT_ELEMENT_LIST) {
|
||||
// TODO
|
||||
} else if (core[_tempData].get('mode') === Mode.SELECT_ELEMENT) {
|
||||
if (typeof uuid === 'string' && core[_coreEvent].has('screenMoveElementStart')) {
|
||||
core[_coreEvent].trigger('screenMoveElementStart', {
|
||||
index: core[_element].getElementIndex(core[_data], uuid),
|
||||
uuid,
|
||||
x: point.x,
|
||||
y: point.y
|
||||
});
|
||||
}
|
||||
} else if (core[_tempData].get('mode') === Mode.SELECT_AREA) {
|
||||
core[_helper].startSelectArea(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleMove(core: Core) {
|
||||
return function(point: TypePoint): void {
|
||||
if (core[_tempData].get('mode') === Mode.SELECT_ELEMENT_LIST) {
|
||||
dragElements(core, core[_tempData].get('selectedUUIDList'), point, core[_tempData].get('prevPoint'));
|
||||
core[_draw]();
|
||||
core[_tempData].set('cursorStatus', CursorStatus.DRAGGING);
|
||||
} else if (typeof core[_tempData].get('selectedUUID') === 'string') {
|
||||
if (core[_tempData].get('mode') === Mode.SELECT_ELEMENT) {
|
||||
dragElements(core, [core[_tempData].get('selectedUUID') as string], point, core[_tempData].get('prevPoint'));
|
||||
core[_draw]();
|
||||
core[_tempData].set('cursorStatus', CursorStatus.DRAGGING);
|
||||
} else if (core[_tempData].get('mode') === Mode.SELECT_ELEMENT_WRAPPER_CONTROLLER && core[_tempData].get('selectedControllerDirection')) {
|
||||
transfromElement(
|
||||
core,
|
||||
core[_tempData].get('selectedUUID') as string,
|
||||
point,
|
||||
core[_tempData].get('prevPoint'),
|
||||
core[_tempData].get('selectedControllerDirection') as TypeHelperWrapperControllerDirection
|
||||
);
|
||||
core[_tempData].set('cursorStatus', CursorStatus.DRAGGING)
|
||||
}
|
||||
} else if (core[_tempData].get('mode') === Mode.SELECT_AREA) {
|
||||
core[_helper].changeSelectArea(point);
|
||||
core[_draw]();
|
||||
}
|
||||
core[_tempData].set('prevPoint', point)
|
||||
}
|
||||
}
|
||||
|
||||
function dragElements(core: Core, uuids: string[], point: TypePoint, prevPoint: TypePoint|null): void {
|
||||
if (!prevPoint) {
|
||||
return;
|
||||
}
|
||||
uuids.forEach((uuid) => {
|
||||
const idx = core[_helper].getElementIndexByUUID(uuid);
|
||||
if (idx === null) return;
|
||||
const elem = core[_data].elements[idx];
|
||||
if (elem?.operation?.lock !== true && elem?.operation?.invisible !== true) {
|
||||
core[_element].dragElement(core[_data], uuid, point, prevPoint, core[_board].getContext().getTransform().scale);
|
||||
}
|
||||
});
|
||||
core[_draw]();
|
||||
}
|
||||
|
||||
|
||||
function handleMoveEnd(core: Core) {
|
||||
return function (point: TypePoint): void {
|
||||
const uuid = core[_tempData].get('selectedUUID');
|
||||
if (typeof uuid === 'string') {
|
||||
const index = core[_element].getElementIndex(core[_data], uuid);
|
||||
const elem = core[_data].elements[index];
|
||||
if (elem) {
|
||||
if (core[_coreEvent].has('screenMoveElementEnd')) {
|
||||
core[_coreEvent].trigger('screenMoveElementEnd', {
|
||||
index,
|
||||
uuid,
|
||||
x: point.x,
|
||||
y: point.y
|
||||
});
|
||||
}
|
||||
if (core[_coreEvent].has('screenChangeElement')) {
|
||||
core[_coreEvent].trigger('screenChangeElement', {
|
||||
index,
|
||||
uuid,
|
||||
width: elem.w,
|
||||
height: elem.h,
|
||||
angle: elem.angle || 0
|
||||
});
|
||||
}
|
||||
core[_emitChangeData]();
|
||||
}
|
||||
} else if (core[_tempData].get('mode') === Mode.SELECT_AREA) {
|
||||
const uuids = core[_helper].calcSelectedElements(core[_data]);
|
||||
if (uuids.length > 0) {
|
||||
core[_tempData].set('selectedUUIDList', uuids);
|
||||
core[_tempData].set('selectedUUID', null);
|
||||
} else {
|
||||
core[_tempData].set('mode', Mode.NULL);
|
||||
}
|
||||
core[_helper].clearSelectedArea();
|
||||
core[_draw]();
|
||||
}
|
||||
|
||||
if (core[_tempData].get('mode') !== Mode.SELECT_ELEMENT) {
|
||||
core[_tempData].set('selectedUUID', null);
|
||||
}
|
||||
core[_tempData].set('cursorStatus', CursorStatus.NULL);
|
||||
core[_tempData].set('mode', Mode.NULL);
|
||||
}
|
||||
}
|
||||
|
||||
function handleHover(core: Core) {
|
||||
return function (point: TypePoint): void {
|
||||
let isMouseOverElement: boolean = false;
|
||||
|
||||
if (core[_tempData].get('mode') === Mode.SELECT_AREA) {
|
||||
if (core[_tempData].get('onlyRender') !== true) core[_board].resetCursor();
|
||||
} else if (core[_tempData].get('cursorStatus') === CursorStatus.NULL) {
|
||||
const { cursor, elementUUID } = core[_mapper].judgePointCursor(point, core[_data]);
|
||||
if (core[_tempData].get('onlyRender') !== true) core[_board].setCursor(cursor);
|
||||
if (elementUUID) {
|
||||
const index: number | null = core[_helper].getElementIndexByUUID(elementUUID);
|
||||
if (index !== null && index >= 0) {
|
||||
const elem = core[_data].elements[index];
|
||||
if (elem?.operation?.lock === true || elem?.operation?.invisible === true) {
|
||||
core[_board].resetCursor();
|
||||
return;
|
||||
}
|
||||
if (core[_tempData].get('hoverUUID') !== elem.uuid) {
|
||||
const preIndex = core[_helper].getElementIndexByUUID(core[_tempData].get('hoverUUID') || '');
|
||||
if (preIndex !== null && core[_data].elements[preIndex]) {
|
||||
core[_coreEvent].trigger('mouseLeaveElement', {
|
||||
uuid: core[_tempData].get('hoverUUID'),
|
||||
index: preIndex,
|
||||
element: core[_data].elements[preIndex]
|
||||
});
|
||||
}
|
||||
}
|
||||
if (elem) {
|
||||
core[_coreEvent].trigger('mouseOverElement', { uuid: elem.uuid, index, element: elem, });
|
||||
core[_tempData].set('hoverUUID', elem.uuid);
|
||||
isMouseOverElement = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isMouseOverElement !== true && core[_tempData].get('hoverUUID') !== null) {
|
||||
const uuid = core[_tempData].get('hoverUUID');
|
||||
const index: number | null = core[_helper].getElementIndexByUUID(uuid || '');
|
||||
if (index !== null) core[_coreEvent].trigger('mouseLeaveElement', { uuid, index, element: core[_data].elements[index] })
|
||||
core[_tempData].set('hoverUUID', null);
|
||||
}
|
||||
if (core[_coreEvent].has('mouseOverScreen')) core[_coreEvent].trigger('mouseOverScreen', point);
|
||||
}
|
||||
}
|
||||
|
||||
function handleLeave(core: Core) {
|
||||
return function(): void {
|
||||
if (core[_coreEvent].has('mouseLeaveScreen')) {
|
||||
core[_coreEvent].trigger('mouseLeaveScreen', undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function transfromElement(
|
||||
core: Core,
|
||||
uuid: string, point: TypePoint, prevPoint: TypePoint|null, direction: TypeHelperWrapperControllerDirection
|
||||
): null | {
|
||||
width: number,
|
||||
height: number,
|
||||
angle: number,
|
||||
} {
|
||||
if (!prevPoint) {
|
||||
return null;
|
||||
}
|
||||
const result = core[_element].transformElement(core[_data], uuid, point, prevPoint, core[_board].getContext().getTransform().scale, direction);
|
||||
core[_draw]();
|
||||
return result;
|
||||
}
|
||||
4
packages/renderer/src/util/filter.ts
Normal file
4
packages/renderer/src/util/filter.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
export function filterScript(html: string) {
|
||||
return html.replace(/<script[\s\S]*?<\/script>/ig, '');
|
||||
}
|
||||
Loading…
Reference in a new issue