提交 a7e16da8 编写于 作者: Q qq_41923622

Fri Jan 24 10:44:00 CST 2025 inscode

上级 669eac6d
run = "npm i && npm run dev"
language = "node"
[deployment]
build = "npm i && npm run build"
run = "npm run preview"
[env]
PATH = "/root/${PROJECT_DIR}/.config/npm/node_global/bin:/root/${PROJECT_DIR}/node_modules/.bin:${PATH}"
XDG_CONFIG_HOME = "/root/.config"
......
basic.jpg

480.7 KB

export function setupCounter(element: HTMLButtonElement): void
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<style>
html,body{
margin: 0;
border: 0;
height: 100%;
}
</style>
<body style="background-color: gainsboro;">
<div id="app"></div>
<script type="module" src="src/main.ts"></script>
</body>
</html>
console.log("欢迎来到 InsCode");
\ No newline at end of file
{
"name": "nodejs",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@types/node": "^18.0.6",
"node-fetch": "^3.2.6"
}
"name": "online-image-editor",
"private": true,
"version": "0.0.0",
"type": "module",
"files": [
"dist",
"index.d.ts"
],
"main": "./dist/counter.umd.cjs",
"module": "./dist/counter.js",
"types": "./index.d.ts",
"exports": {
"types": "./index.d.ts",
"import": "./dist/counter.js",
"require": "./dist/counter.umd.cjs"
},
"scripts": {
"dev": "vite",
"build": "tsc && vite build"
},
"devDependencies": {
"typescript": "^5.4.5",
"vite": "^5.4.8",
"vite-plugin-copy": "^0.1.6",
"vite-plugin-static-copy": "^2.0.0"
},
"dependencies": {
"fabric": "^6.4.3",
"rollup-plugin-copy": "^3.5.0"
}
\ No newline at end of file
}
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1703559162011" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1064" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 96c229.76 0 416 186.24 416 416s-186.24 416-416 416S96 741.76 96 512 282.24 96 512 96zM526.933333 298.666667h-29.866666a17.066667 17.066667 0 0 0-17.066667 17.066666v164.266667H315.733333a17.066667 17.066667 0 0 0-17.066666 17.066667v29.866666c0 9.386667 7.68 17.066667 17.066666 17.066667h164.266667v164.266667c0 9.386667 7.68 17.066667 17.066667 17.066666h29.866666a17.066667 17.066667 0 0 0 17.066667-17.066666v-164.266667h164.266667a17.066667 17.066667 0 0 0 17.066666-17.066667v-29.866666a17.066667 17.066667 0 0 0-17.066666-17.066667h-164.266667V315.733333a17.066667 17.066667 0 0 0-17.066667-17.066666z" fill="#515151" fill-opacity=".87" p-id="1065"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1695113090426" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12842" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M822.7 767.6L626.4 571.3l163.1-90.6c35.1-19.5 32-71-5.3-86.1l-551.6-223c-39.1-15.8-78 23.1-62.2 62.2l223 551.6c15.1 37.3 66.6 40.4 86.1 5.3l90.6-163.1 196.3 196.3c7.8 7.8 18 11.7 28.2 11.7s20.4-3.9 28.2-11.7c15.5-15.5 15.5-40.7-0.1-56.3z" fill="#515151" p-id="12843"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1703837617436" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1516" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M191.996412 319.999467m59.736381-59.736381l8.527708-8.527707q59.736381-59.736381 119.472761 0l392.529117 392.529116q59.736381 59.736381 0 119.472762l-8.527708 8.527708q-59.736381 59.736381-119.472762 0l-392.529116-392.529117q-59.736381-59.736381 0-119.472762Z" fill="#515151" p-id="1517"></path><path d="M191.996412 704.000876m59.736381-59.736381l392.529116-392.529116q59.736381-59.736381 119.472762 0l8.527708 8.527707q59.736381 59.736381 0 119.472762l-392.529117 392.529117q-59.736381 59.736381-119.472761 0l-8.527708-8.527708q-59.736381-59.736381 0-119.472762Z" fill="#515151" p-id="1518"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#515151" d="M512 960c-247.039484 0-448-200.960516-448-448S264.960516 64 512 64 960 264.960516 960 512 759.039484 960 512 960zM512 128c-211.744443 0-384 172.255557-384 384s172.255557 384 384 384 384-172.255557 384-384S723.744443 128 512 128z" /></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1703837610361" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1310" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M341.09 469.09l72.78 72.78a48.27 48.27 0 0 0 68.26 0L743.4 280.6a48.26 48.26 0 0 1 55.71-9l37 18.5a48.26 48.26 0 0 1 12.54 77.29L478.54 737.46a48.27 48.27 0 0 1-64.27 3.54l-217-173.6c-29.86-23.9-21.4-71.38 14.89-83.47l79.54-26.52a48.26 48.26 0 0 1 49.39 11.68z" fill="#515151" p-id="1311"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#515151" d="M884.731 698.366H772.909V288.365c0-20.584-16.685-37.275-37.269-37.275H325.639V139.274c0-20.582-16.693-37.273-37.275-37.273-20.582 0-37.275 16.691-37.275 37.273V251.09H139.275c-20.584 0-37.275 16.691-37.275 37.275 0 20.582 16.691 37.273 37.275 37.273H251.09v410.003c0 20.582 16.693 37.269 37.275 37.269h410.003v111.82c0 20.583 16.685 37.269 37.267 37.269 20.589 0 37.275-16.685 37.275-37.269V772.91h111.822c20.582 0 37.269-16.687 37.269-37.269-0.001-20.588-16.688-37.275-37.27-37.275z m-559.092 0V325.638h372.728v372.728H325.639z m0 0" /></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694850868080" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6322" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M170.666667 768m42.666666 0l597.333334 0q42.666667 0 42.666666 42.666667l0 0q0 42.666667-42.666666 42.666666l-597.333334 0q-42.666667 0-42.666666-42.666666l0 0q0-42.666667 42.666666-42.666667Z" fill="#515151" p-id="6323"></path><path d="M170.666667 853.333333m0-42.666666l0-85.333334q0-42.666667 42.666666-42.666666l0 0q42.666667 0 42.666667 42.666666l0 85.333334q0 42.666667-42.666667 42.666666l0 0q-42.666667 0-42.666666-42.666666Z" fill="#515151" p-id="6324"></path><path d="M768 853.333333m0-42.666666l0-85.333334q0-42.666667 42.666667-42.666666l0 0q42.666667 0 42.666666 42.666666l0 85.333334q0 42.666667-42.666666 42.666666l0 0q-42.666667 0-42.666667-42.666666Z" fill="#515151" p-id="6325"></path><path d="M512 640a42.666667 42.666667 0 0 1-24.746667-7.68l-170.666666-120.32a42.666667 42.666667 0 0 1-10.24-59.306667 42.666667 42.666667 0 0 1 59.733333-10.24L512 544.426667l145.066667-109.226667a42.666667 42.666667 0 0 1 51.2 68.266667l-170.666667 128a42.666667 42.666667 0 0 1-25.6 8.533333z" fill="#515151" p-id="6326"></path><path d="M512 554.666667a42.666667 42.666667 0 0 1-42.666667-42.666667V170.666667a42.666667 42.666667 0 0 1 85.333334 0v341.333333a42.666667 42.666667 0 0 1-42.666667 42.666667z" fill="#515151" p-id="6327"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694850318596" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16905" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M819.2 102.4h-56.53504c-18.76992 0-45.0048 10.87488-58.2656 24.13568l-51.2 51.2L629.07392 153.6c-26.5728-26.53184-70.00064-26.53184-96.57344 0L266.99776 419.10272a34.16064 34.16064 0 0 0 0 48.27136l12.07296 12.07296 289.62816-289.6384 36.22912 36.23936-362.02496 362.02496-0.03072-0.03072-92.17024 92.23168c-26.5728 26.5216-48.30208 78.97088-48.27136 116.50048L102.4 921.6h124.8256c37.49888 0 89.96864-21.72928 116.50048-48.26112l92.24192-92.20096 461.49632-461.49632C910.72512 306.37056 921.6 280.13568 921.6 261.36576V204.8L819.2 102.4zM295.46496 825.06752c-13.74208 13.73184-48.80384 28.30336-68.23936 28.30336l-56.53504-0.03072V796.7744c-0.03072-19.42528 14.49984-54.46656 28.27264-68.22912l43.9296-43.93984 96.53248 96.54272-43.96032 43.91936z m92.23168-92.20096l-96.53248-96.53248 362.02496-362.0352 96.57344 96.57344-362.06592 361.99424z m410.33728-410.2656l-96.5632-96.60416 72.3968-72.3968 96.60416 96.60416-72.43776 72.3968z" p-id="16906" fill="#515151"></path></svg>
\ No newline at end of file
<svg t="1731509655874" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4494" width="200" height="200"><path d="M99.5 284.8h61.3v-74l260.3 260.3 43.4-43.4L198.8 162h84.8v-61.4H99.5zM869.1 825.8L603.4 560 560 603.4l260.3 260.4h-73.9v61.3h184.1V741h-61.4zM160.1 742.4H98.7v184.1h184.2v-61.3h-68.6l263.1-263.1-43.4-43.4-273.9 273.9zM742 102v61.3h90.2l-263 263.1 43.4 43.4 252.2-252.2v68.5h61.4V102z" fill="#333333" p-id="4495"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1702545991424" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4272" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M640 896h85.333333v-85.333333h-85.333333v85.333333z m170.666667-512h85.333333v-85.333333h-85.333333v85.333333zM128 213.333333v597.333334c0 47.146667 38.186667 85.333333 85.333333 85.333333h170.666667v-85.333333h-170.666667V213.333333h170.666667V128h-170.666667c-47.146667 0-85.333333 38.186667-85.333333 85.333333z m682.666667-85.333333v85.333333h85.333333c0-47.146667-38.186667-85.333333-85.333333-85.333333zM469.333333 981.333333h85.333334V42.666667h-85.333334v938.666666z m341.333334-256h85.333333v-85.333333h-85.333333v85.333333z m-170.666667-512h85.333333V128h-85.333333v85.333333z m170.666667 341.333334h85.333333v-85.333334h-85.333333v85.333334z m0 341.333333c47.146667 0 85.333333-38.186667 85.333333-85.333333h-85.333333v85.333333z" p-id="4273" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694849855144" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9603" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M210.773333 522.24a42.666667 42.666667 0 0 0-51.626666 32.426667l-20.906667 82.773333A42.666667 42.666667 0 0 0 170.666667 687.786667a42.666667 42.666667 0 0 0 51.626666-31.146667l20.906667-82.773333a42.666667 42.666667 0 0 0-32.426667-51.626667z m725.333334 320.853333l-170.666667-682.666666A42.666667 42.666667 0 0 0 725.333333 128h-128a42.666667 42.666667 0 0 0-42.666666 42.666667v682.666666a42.666667 42.666667 0 0 0 42.666666 42.666667h298.666667a42.666667 42.666667 0 0 0 33.706667-16.213333 42.666667 42.666667 0 0 0 8.96-36.693334zM640 810.666667V213.333333h52.053333l149.333334 597.333334zM273.066667 273.92a42.666667 42.666667 0 0 0-52.053334 31.146667L200.533333 387.84a42.666667 42.666667 0 0 0 31.146667 51.626667h10.24a42.666667 42.666667 0 0 0 42.666667-32.426667l20.48-82.773333a42.666667 42.666667 0 0 0-32-50.346667zM320.426667 213.333333h85.333333a42.666667 42.666667 0 0 0 0-85.333333h-85.333333a42.666667 42.666667 0 0 0 0 85.333333zM180.906667 810.666667a42.666667 42.666667 0 0 0-85.333334-10.24l-10.24 42.666666a42.666667 42.666667 0 0 0 7.68 36.693334A42.666667 42.666667 0 0 0 128 896h42.666667a42.666667 42.666667 0 0 0 10.24-85.333333zM426.666667 277.76a42.666667 42.666667 0 0 0-42.666667 42.666667v85.333333a42.666667 42.666667 0 0 0 85.333333 0v-85.333333a42.666667 42.666667 0 0 0-42.666666-42.666667z m0 256a42.666667 42.666667 0 0 0-42.666667 42.666667v85.333333a42.666667 42.666667 0 0 0 85.333333 0v-85.333333a42.666667 42.666667 0 0 0-42.666666-42.666667z m0 256a42.666667 42.666667 0 0 0-36.693334 20.906667H341.333333a42.666667 42.666667 0 0 0 0 85.333333h85.333334a42.666667 42.666667 0 0 0 42.666666-42.666667v-20.906666a42.666667 42.666667 0 0 0-42.666666-42.666667z" p-id="9604" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694849870986" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9778" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M563.626667 243.2a42.666667 42.666667 0 0 0 10.24 0l82.773333-20.906667A42.666667 42.666667 0 0 0 687.786667 170.666667a42.666667 42.666667 0 0 0-51.626667-31.146667L554.666667 159.146667a42.666667 42.666667 0 0 0 10.24 85.333333zM405.76 384h-85.333333a42.666667 42.666667 0 1 0 0 85.333333h85.333333a42.666667 42.666667 0 0 0 0-85.333333z m170.666667 0a42.666667 42.666667 0 1 0 0 85.333333h85.333333a42.666667 42.666667 0 0 0 0-85.333333zM315.306667 305.066667h10.24l82.773333-20.48a42.666667 42.666667 0 0 0-20.48-84.053334l-82.773333 20.48a42.666667 42.666667 0 0 0 10.24 85.333334zM170.666667 448.426667a42.666667 42.666667 0 0 0 42.666666-42.666667v-85.333333a42.666667 42.666667 0 0 0-85.333333 0v85.333333a42.666667 42.666667 0 0 0 42.666667 42.666667z m709.12-354.133334a42.666667 42.666667 0 0 0-36.693334-8.96l-42.666666 10.24a42.666667 42.666667 0 0 0-31.146667 51.626667 42.666667 42.666667 0 0 0 42.666667 32.426667A42.666667 42.666667 0 0 0 896 170.666667V128a42.666667 42.666667 0 0 0-16.213333-33.706667zM853.333333 298.666667a42.666667 42.666667 0 0 0-42.666666 42.666666v48.64a42.666667 42.666667 0 0 0 21.76 79.36H853.333333a42.666667 42.666667 0 0 0 42.666667-42.666666V341.333333a42.666667 42.666667 0 0 0-42.666667-42.666666z m0 256H170.666667a42.666667 42.666667 0 0 0-42.666667 42.666666v128a42.666667 42.666667 0 0 0 32.426667 42.666667l682.666666 170.666667a42.666667 42.666667 0 0 0 10.24 0 42.666667 42.666667 0 0 0 42.666667-42.666667v-298.666667a42.666667 42.666667 0 0 0-42.666667-42.666666z m-42.666666 286.72l-597.333334-149.333334V640h597.333334z" p-id="9779" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694854421840" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="28174" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M870.4 204.8c-18.6368 0-36.1472 5.0176-51.2 13.7728l0-64.9728c0-56.4736-45.9264-102.4-102.4-102.4-21.0944 0-40.6528 6.4-56.9856 17.3568-14.0288-39.8848-52.0192-68.5568-96.6144-68.5568s-82.6368 28.672-96.6144 68.5568c-16.2816-10.9568-35.8912-17.3568-56.9856-17.3568-56.4736 0-102.4 45.9264-102.4 102.4l0 377.4976-68.9152-119.4496c-13.3632-24.32-35.1744-41.6256-61.3888-48.7936-25.5488-6.9632-52.1216-3.2768-74.8544 10.3424-46.4384 27.8528-64.1536 90.8288-39.424 140.3904 1.536 3.1232 34.2016 70.0416 136.192 273.92 48.0256 96 100.7104 164.6592 156.6208 203.9808 43.8784 30.8736 74.1888 32.4608 79.8208 32.4608l256 0c43.5712 0 84.0704-14.1824 120.4224-42.0864 34.1504-26.2656 63.7952-64.256 88.064-112.8448 47.8208-95.6416 73.1136-227.9424 73.1136-382.6688l0-179.2c0-56.4736-45.9264-102.4-102.4-102.4zM921.6 486.4c0 146.7904-23.3984 271.1552-67.6864 359.7312-28.8768 57.7536-80.5888 126.6688-162.7136 126.6688l-255.488 0c-1.9968-0.1536-23.552-2.56-56.064-26.88-32.4096-24.2688-82.176-75.3664-135.0656-181.248-103.7824-207.5648-135.68-272.9472-135.9872-273.5616-0.0512-0.1024-0.0512-0.1536-0.1024-0.2048-12.8512-25.7536-3.7376-59.4944 19.9168-73.6768 10.6496-6.4 23.0912-8.0896 35.072-4.864 12.7488 3.4816 23.4496 12.0832 30.0544 24.1664 0.1024 0.1536 0.2048 0.3584 0.3072 0.512l79.9232 138.496c16.3328 29.8496 34.7136 42.3936 54.6304 37.3248 19.968-5.0688 30.0544-25.0368 30.0544-59.2384l0-400.0256c0-28.2112 22.9888-51.2 51.2-51.2s51.2 22.9888 51.2 51.2l0 332.8c0 14.1312 11.4688 25.6 25.6 25.6s25.6-11.4688 25.6-25.6l0-384c0-28.2112 22.9888-51.2 51.2-51.2s51.2 22.9888 51.2 51.2l0 384c0 14.1312 11.4688 25.6 25.6 25.6s25.6-11.4688 25.6-25.6l0-332.8c0-28.2112 22.9888-51.2 51.2-51.2s51.2 22.9888 51.2 51.2l0 384c0 14.1312 11.4688 25.6 25.6 25.6s25.6-11.4688 25.6-25.6l0-230.4c0-28.2112 22.9888-51.2 51.2-51.2s51.2 22.9888 51.2 51.2l0 179.2z" fill="#515151" p-id="28175"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1702973104380" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4243" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M533.333333 85.333333C384 85.333333 251.733333 166.4 183.466667 290.133333L85.333333 192 85.333333 469.333333l277.333333 0L243.2 349.866667C298.666667 243.2 405.333333 170.666667 533.333333 170.666667c174.933333 0 320 145.066667 320 320 0 174.933333-145.066667 320-320 320-140.8 0-256-89.6-302.933333-213.333333L140.8 597.333333c46.933333 170.666667 204.8 298.666667 392.533333 298.666667 226.133333 0 405.333333-183.466667 405.333333-405.333333S755.2 85.333333 533.333333 85.333333zM469.333333 298.666667l0 217.6 200.533333 119.466667 34.133333-55.466667-170.666667-102.4L533.333333 298.666667 469.333333 298.666667z" opacity="0.9" p-id="4244" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#515151" d="M512 212h300v300H512zM212 512h300v300H212z" /><path fill="#515151" d="M812 992H212a180 180 0 0 1-180-180V212a180 180 0 0 1 180-180h600a180 180 0 0 1 180 180v600a180 180 0 0 1-180 180zM212 152a60 60 0 0 0-60 60v600a60 60 0 0 0 60 60h600a60 60 0 0 0 60-60V212a60 60 0 0 0-60-60z" /></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1702438231396" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2364" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M85.344 106.656H256v170.656H85.344V106.656zM85.344 448H256v170.656H85.344V448zM256 277.344h170.656V448H256V277.344zM426.656 106.656h170.656v170.656h-170.656V106.656zM426.656 448h170.656v170.656h-170.656V448zM597.344 277.344H768V448h-170.656V277.344zM597.344 768h-170.656v-149.344H256.032V768H85.376v170.656h170.656v-149.344h170.656v149.344h170.656v-149.344H768v149.344h170.656V768H768v-149.344h-170.656zM768 106.656h170.656v170.656H768V106.656zM768 448h170.656v170.656H768V448z" p-id="2365" fill="#515151" data-spm-anchor-id="a313x.search_index.0.i1.70153a81pH8VDw" class=""></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1701141069715" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5133" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M841.34 959.36H182.66c-65.06 0-117.99-52.94-117.99-118.02V182.69c0-65.08 52.94-118.04 117.99-118.04h658.68c65.06 0 117.99 52.96 117.99 118.04v658.65c0 65.08-52.93 118.02-117.99 118.02zM182.66 142.17c-22.31 0-40.51 18.18-40.51 40.51v658.65c0 22.34 18.2 40.49 40.51 40.49h658.68c22.31 0 40.51-18.15 40.51-40.49V182.69c0-22.34-18.2-40.51-40.51-40.51H182.66z" fill="#515151" p-id="5134"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694850987960" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2591" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M765.013333 509.013333C487.253333 235.392 67.968 391.466667 65.792 697.941333l7.765333 2.645334c176.853333-218.112 417.024-291.84 605.866667-106.453334 1.706667 1.706667 1.792 4.608 0.085333 6.314667L561.92 718.08a4.266667 4.266667 0 0 0 2.986667 7.296h326.826666a4.266667 4.266667 0 0 0 4.266667-4.266667V394.24a4.224 4.224 0 0 0-7.253333-2.986667l-117.76 117.76a4.096 4.096 0 0 1-5.888 0z" p-id="2592" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1702708510489" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5331" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M831.167365 607.851819c-15.128556-6.096582-32.063506 1.354796-38.160088 16.483352-46.288864 115.609261-156.478942 190.348842-281.120177 190.348842-104.770893 0-199.606615-52.837045-254.475854-138.640794l193.735832 39.514885c15.805954 3.161191 31.386108-6.999779 34.547299-22.805734 3.161191-15.805954-6.999779-31.386108-22.805733-34.547298l-252.895259-51.933848h-0.903198c-0.677398-0.225799-1.580595-0.225799-2.257993-0.2258H202.993605c-0.677398 0-1.354796 0-2.032194 0.2258-0.451599 0-0.677398 0-1.128996 0.225799-0.677398 0-1.128997 0.225799-1.806395 0.225799-0.451599 0-0.903197 0.225799-1.128997 0.2258-0.451599 0.225799-1.128997 0.225799-1.580595 0.451599-0.451599 0.225799-0.903197 0.225799-1.354796 0.451598-0.225799 0.225799-0.677398 0.225799-0.903198 0.2258-0.225799 0-0.225799 0.225799-0.451598 0.225799l-1.354796 0.677398-1.354796 0.677398c-0.451599 0.225799-0.677398 0.451599-1.128997 0.677398-0.451599 0.225799-1.128997 0.677398-1.580595 0.903197-0.225799 0-0.225799 0.225799-0.451599 0.2258-0.225799 0.225799-0.451599 0.225799-0.451599 0.451598-0.451599 0.451599-0.903197 0.677398-1.580595 1.128997-0.225799 0.225799-0.451599 0.451599-0.903197 0.677398l-1.354797 1.354796c-0.225799 0.225799-0.451599 0.451599-0.677398 0.903197-0.451599 0.451599-0.677398 0.903197-1.128996 1.354796-0.225799 0.225799-0.451599 0.677398-0.677398 0.903198-0.225799 0.451599-0.677398 0.903197-0.903198 1.354796-0.225799 0.451599-0.451599 0.677398-0.677398 1.128996l-0.677398 1.354797c-0.225799 0.451599-0.451599 0.677398-0.451598 1.128996-0.225799 0.451599-0.451599 0.903197-0.677398 1.580596-0.225799 0.451599-0.225799 0.677398-0.451599 1.128996-0.225799 0.677398-0.451599 1.128997-0.451599 1.806395 0 0.451599-0.225799 0.677398-0.225799 1.128997-0.225799 0.677398-0.225799 1.354796-0.451599 2.257993v1.128997L141.350386 887.842999c-2.032194 16.031753 9.257773 30.70871 25.289526 32.966703 1.354796 0.225799 2.483793 0.225799 3.838589 0.2258 14.451158 0 27.095921-10.838368 29.128114-25.515326l21.676737-168.672105c23.483131 31.837707 52.385447 59.836825 85.577949 82.868357 60.514223 41.772878 131.189416 63.675413 205.025799 63.675413s144.511577-22.128335 205.0258-63.675413c58.933627-40.643881 104.093495-97.319515 130.737817-163.704521 5.870783-15.128556-1.354796-32.289305-16.483352-38.160088zM857.134289 103.190298c-16.031753-2.032194-30.70871 9.257773-32.966704 25.289526l-21.676736 168.672105c-23.483131-31.837707-52.385447-59.836825-85.577949-82.642557-60.514223-41.772878-131.189416-63.675413-205.0258-63.675414-73.610584 0-144.511577 22.128335-205.025799 63.675414-58.933627 40.643881-104.093495 97.319515-130.737817 163.70452-6.096582 15.128556 1.354796 32.063506 16.483352 38.160088 15.128556 6.096582 32.063506-1.354796 38.160088-16.483352 46.288864-115.609261 156.478942-190.348842 281.120176-190.348842 104.770893 0 199.606615 52.837045 254.475855 138.640794L572.627122 308.441896c-15.805954-3.161191-31.386108 6.999779-34.547298 22.805734-3.161191 15.805954 6.999779 31.386108 22.805733 34.547298l253.572657 51.708049h0.451599c0.677398 0.225799 1.580595 0.225799 2.257993 0.451599h0.451599c0.903197 0 1.806395 0.225799 2.709592 0.225799h2.257993c0.451599 0 0.903197 0 1.128997-0.225799 0.451599 0 0.903197-0.225799 1.354796-0.2258 0.451599 0 0.677398-0.225799 1.128996-0.225799s0.903197-0.225799 1.580596-0.451599c0.225799 0 0.677398-0.225799 0.903197-0.225799 0.677398-0.225799 1.354796-0.451599 1.806395-0.677398 0.225799 0 0.225799 0 0.451599-0.2258 0.903197-0.225799 1.580595-0.677398 2.483792-1.128996 0.225799-0.225799 0.451599-0.225799 0.903198-0.451599s0.903197-0.451599 1.354796-0.903197c0.225799-0.225799 0.677398-0.451599 0.903197-0.677398 0.225799 0 0.225799-0.225799 0.451599-0.2258l0.451598-0.451598c0.451599-0.451599 1.128997-0.677398 1.580596-1.128997l0.677398-0.677398 1.354796-1.354796c0.225799-0.225799 0.451599-0.451599 0.677398-0.903197 0.451599-0.451599 0.677398-0.903197 1.128997-1.354796 0.225799-0.225799 0.451599-0.677398 0.677398-0.903198 0.225799-0.451599 0.677398-0.903197 0.903197-1.354796 0.225799-0.451599 0.451599-0.677398 0.677398-1.128996l0.677398-1.354797c0.225799-0.451599 0.451599-0.903197 0.677398-1.128996l0.677398-1.354796c0.225799-0.451599 0.225799-0.903197 0.451599-1.354796 0.225799-0.451599 0.225799-1.128997 0.451598-1.580596 0-0.451599 0.225799-0.677398 0.2258-1.128996 0.225799-0.677398 0.225799-1.354796 0.451598-2.032194 0-0.225799 0-0.451599 0.2258-0.677398v-0.2258l32.966703-256.282249c2.709592-16.257552-8.806174-30.934509-24.837927-32.966703z" fill="#515151" p-id="5332" data-spm-anchor-id="a313x.search_index.0.i5.79943a813nilqH" class="selected"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694850121784" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10964" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M480.5 251.2c13-1.6 25.9-2.4 38.8-2.5v63.9c0 6.5 7.5 10.1 12.6 6.1L660 217.6c4-3.2 4-9.2 0-12.3l-128-101c-5.1-4-12.6-0.4-12.6 6.1l-0.2 64c-118.6 0.5-235.8 53.4-314.6 154.2-69.6 89.2-95.7 198.6-81.1 302.4h74.9c-0.9-5.3-1.7-10.7-2.4-16.1-5.1-42.1-2.1-84.1 8.9-124.8 11.4-42.2 31-81.1 58.1-115.8 27.2-34.7 60.3-63.2 98.4-84.3 37-20.6 76.9-33.6 119.1-38.8z" p-id="10965" fill="#515151"></path><path d="M880 418H352c-17.7 0-32 14.3-32 32v414c0 17.7 14.3 32 32 32h528c17.7 0 32-14.3 32-32V450c0-17.7-14.3-32-32-32z m-44 402H396V494h440v326z" p-id="10966" fill="#515151"></path></svg>
\ No newline at end of file
<svg t="1731509731687" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5465" width="200" height="200"><path d="M672 352h160a32 32 0 0 1 0 64H640a32 32 0 0 1-32-32V192a32 32 0 0 1 64 0z" p-id="5466"></path><path d="M662.72 406.72a32 32 0 0 1-45.44-45.44l224-224a32 32 0 1 1 45.44 45.44zM352 352V192a32 32 0 0 1 64 0v192a32 32 0 0 1-32 32H192a32 32 0 0 1 0-64z" p-id="5467"></path><path d="M406.72 361.28a32 32 0 0 1-45.44 45.44l-224-224a32 32 0 0 1 45.44-45.44zM672 672v160a32 32 0 0 1-64 0V640a32 32 0 0 1 32-32h192a32 32 0 0 1 0 64z" p-id="5468"></path><path d="M617.28 662.72a32 32 0 0 1 45.44-45.44l224 224a32 32 0 0 1-45.44 45.44zM192 672a32 32 0 0 1 0-64h192a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V672z" p-id="5469"></path><path d="M361.28 617.28a32 32 0 0 1 45.44 45.44l-224 224a32 32 0 0 1-45.44-45.44z" p-id="5470"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1703559147194" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5088" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 96c229.76 0 416 186.24 416 416s-186.24 416-416 416S96 741.76 96 512 282.24 96 512 96z m196.266667 384H315.733333a17.066667 17.066667 0 0 0-17.066666 17.066667v29.866666c0 9.386667 7.68 17.066667 17.066666 17.066667h392.533334a17.066667 17.066667 0 0 0 17.066666-17.066667v-29.866666a17.066667 17.066667 0 0 0-17.066666-17.066667z" fill="#515151" fill-opacity=".87" p-id="5089"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694849832460" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8562" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M853.333333 170.666667H170.666667a42.666667 42.666667 0 0 0-42.666667 42.666666v128a42.666667 42.666667 0 0 0 85.333333 0V256h256v554.666667H384a42.666667 42.666667 0 0 0 0 85.333333h256a42.666667 42.666667 0 0 0 0-85.333333h-85.333333V256h256v85.333333a42.666667 42.666667 0 0 0 85.333333 0V213.333333a42.666667 42.666667 0 0 0-42.666667-42.666666z" p-id="8563" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#515151" d="M799.5 179c24.6 0 44.5 20 44.5 44.5v575.9c0 24.6-20 44.5-44.5 44.5h-576c-24.6 0-44.5-20-44.5-44.5V223.5c0-24.6 20-44.5 44.5-44.5h576m0-120h-576C132.7 59 59 132.7 59 223.5v575.9C59 890.3 132.7 964 223.5 964h575.9c90.9 0 164.5-73.7 164.5-164.5v-576C964 132.7 890.3 59 799.5 59z" /></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694850976834" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7598" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M258.986667 509.013333c277.76-273.621333 697.045333-117.546667 699.306666 188.885334l-7.765333 2.645333c-176.853333-218.112-417.024-291.84-605.866667-106.453333a4.522667 4.522667 0 0 0-0.085333 6.314666l117.589333 117.589334a4.266667 4.266667 0 0 1-2.986666 7.253333H132.266667a4.266667 4.266667 0 0 1-4.266667-4.266667V394.24c0-3.84 4.608-5.717333 7.253333-2.986667l117.76 117.76c1.621333 1.706667 4.266667 1.621333 5.888 0z" p-id="7599" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694850896169" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6500" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M853.333333 256m-42.666666 0l-597.333334 0q-42.666667 0-42.666666-42.666667l0 0q0-42.666667 42.666666-42.666666l597.333334 0q42.666667 0 42.666666 42.666666l0 0q0 42.666667-42.666666 42.666667Z" fill="#515151" p-id="6501"></path><path d="M853.333333 170.666667m0 42.666666l0 85.333334q0 42.666667-42.666666 42.666666l0 0q-42.666667 0-42.666667-42.666666l0-85.333334q0-42.666667 42.666667-42.666666l0 0q42.666667 0 42.666666 42.666666Z" fill="#515151" p-id="6502"></path><path d="M256 170.666667m0 42.666666l0 85.333334q0 42.666667-42.666667 42.666666l0 0q-42.666667 0-42.666666-42.666666l0-85.333334q0-42.666667 42.666666-42.666666l0 0q42.666667 0 42.666667 42.666666Z" fill="#515151" p-id="6503"></path><path d="M341.333333 597.333333a42.666667 42.666667 0 0 1-34.133333-17.066666 42.666667 42.666667 0 0 1 8.533333-59.733334l170.666667-128a42.666667 42.666667 0 0 1 50.346667 0l170.666666 120.32a42.666667 42.666667 0 0 1 10.24 59.306667 42.666667 42.666667 0 0 1-59.733333 10.24L512 479.573333 366.933333 588.8a42.666667 42.666667 0 0 1-25.6 8.533333z" fill="#515151" p-id="6504"></path><path d="M512 896a42.666667 42.666667 0 0 1-42.666667-42.666667v-341.333333a42.666667 42.666667 0 0 1 85.333334 0v341.333333a42.666667 42.666667 0 0 1-42.666667 42.666667z" fill="#515151" p-id="6505"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1695106630007" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6574" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M637 443H519V309c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v134H325c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h118v134c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V519h118c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z" p-id="6575" fill="#515151"></path><path d="M921 867L775 721c122.1-148.9 113.6-369.5-26-509-148-148.1-388.4-148.1-537 0-148.1 148.6-148.1 389 0 537 139.5 139.6 360.1 148.1 509 26l146 146c3.2 2.8 8.3 2.8 11 0l43-43c2.8-2.7 2.8-7.8 0-11zM696 696c-118.8 118.7-311.2 118.7-430 0-118.7-118.8-118.7-311.2 0-430 118.8-118.7 311.2-118.7 430 0 118.7 118.8 118.7 311.2 0 430z" p-id="6576" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1695106635608" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6749" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M637 443H325c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h312c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z" p-id="6750" fill="#515151"></path><path d="M921 867L775 721c122.1-148.9 113.6-369.5-26-509-148-148.1-388.4-148.1-537 0-148.1 148.6-148.1 389 0 537 139.5 139.6 360.1 148.1 509 26l146 146c3.2 2.8 8.3 2.8 11 0l43-43c2.8-2.7 2.8-7.8 0-11zM696 696c-118.8 118.7-311.2 118.7-430 0-118.7-118.8-118.7-311.2 0-430 118.8-118.7 311.2-118.7 430 0 118.7 118.8 118.7 311.2 0 430z" p-id="6751" fill="#515151"></path></svg>
\ No newline at end of file
import { FabricObject, Point } from "fabric";
import { CanvasDetailedProps, FabricCanvasProps, FlipXUndoProps, FlipYUndoProps, RotateProps } from "./history";
import ImageEditor from "./image_editor";
import { OperatorProps, OperatorType } from "./image_editor_operator";
import MosaicOperator from "./operator/mosaic_operator";
import TextOperator from "./operator/text_operator";
import { getAbsolutePosition } from "./uitls";
const COLOR_MAP = {
RED: '#FF0000',
ORANGLE: '#FFA500',
BLUE: '#1A9BFF',
GREEN: '#1AAF19',
BLACK: '#323232',
GREY: '#808080',
WHITE: '#FFFFFF'
}
const DEFAULT_FUNCTION = () => { }
export const pxielToNumber = (length: string) => {
if (length == null) {
return 0;
}
length = length.replace('px', '');
if (length == '') {
return 0;
}
return Number(length);
}
const toNumber = (str: string) => {
if (str == '') {
str = '0';
}
return Number(str);
}
export default class ElementManager {
public static HAS_CURSOR_CSS_ADDED = false;
private static COLOR_ACTIVE_FLAG = "color_in_active";
private static ACTIVE_SIZE_COLOR = '#1AAD19';
private static DEACTIVE_SIZE_COLOR = '#C8C8C8';
private imageEditor: ImageEditor | null = null;
readonly canvasWrapper: HTMLDivElement;
readonly canvas: HTMLCanvasElement;
private fabricWrapperEl: HTMLDivElement | null = null;
private northResizer: HTMLDivElement;
private northWestResizer: HTMLDivElement;
private westResizer: HTMLDivElement;
private southWestResizer: HTMLDivElement;
private southResizer: HTMLDivElement;
private southEastResizer: HTMLDivElement;
private eastResizer: HTMLDivElement;
private northEastResizer: HTMLDivElement;
private topInResize: boolean = false;
// fw Fabric Wrapper
private topChange = { y: NaN, top: NaN, height: NaN, fwTop: NaN, changeHeight: NaN };
private topStartFunc: (e: MouseEvent) => void = DEFAULT_FUNCTION;
private topMoveFunc: (e: MouseEvent) => void = DEFAULT_FUNCTION;
private topFinsihFunc: (e: MouseEvent) => void = DEFAULT_FUNCTION;
private leftInResize: boolean = false;
private leftChange = { x: NaN, left: NaN, width: NaN, fwLeft: NaN, changeWidth: NaN };
private leftStartFunc: (e: MouseEvent) => void = DEFAULT_FUNCTION;
private leftMoveFunc: (e: MouseEvent) => void = DEFAULT_FUNCTION;
private leftFinishFunc: (e: MouseEvent) => void = DEFAULT_FUNCTION;
private bottomInResize: boolean = false;
private bottomChange = { y: NaN, height: NaN, top: NaN, leftRightTop: NaN, changeHeight: NaN };
private bottomStartFunc: (e: MouseEvent) => void = DEFAULT_FUNCTION;
private bottomMoveFunc: (e: MouseEvent) => void = DEFAULT_FUNCTION;
private bottomFinishFunc: (e: MouseEvent) => void = DEFAULT_FUNCTION;
private rightInResize: boolean = false;
private rightChange = { x: NaN, width: NaN, left: NaN, topBottomLeft: NaN, changeWidth: NaN };
private rightStartFunc: (e: MouseEvent) => void = DEFAULT_FUNCTION;
private rightMoveFunc: (e: MouseEvent) => void = DEFAULT_FUNCTION;
private rightFinishFunc: (e: MouseEvent) => void = DEFAULT_FUNCTION;
private squareSize: number = NaN;
readonly wrapper: HTMLDivElement;
private screenshotCanvas: HTMLCanvasElement;
private screenshotResizer: {
northWest: HTMLDivElement,
north: HTMLDivElement,
northEast: HTMLDivElement,
east: HTMLDivElement,
southEast: HTMLDivElement,
south: HTMLDivElement,
southWest: HTMLDivElement,
west: HTMLDivElement
};
private screenshotToolbar: HTMLDivElement;
private screenshotConfirmButton: HTMLDivElement;
private screenshotCancelButton: HTMLDivElement;
private toolbar: HTMLDivElement;
private rectangleMenu: HTMLDivElement;
private ellipseMenu: HTMLDivElement;
private arrowMenu: HTMLDivElement;
private drawMenu: HTMLDivElement;
private textMenu: HTMLDivElement;
private mosaicMenu: HTMLDivElement;
private shrinkMenu: HTMLDivElement;
private extendMenu: HTMLDivElement;
private flipXMenu: HTMLDivElement;
private flipYMenu: HTMLDivElement;
private rotateClockwiseMenu: HTMLDivElement;
private rotateCounterClockwiseMenu: HTMLDivElement;
private cropMenu: HTMLDivElement;
private undoMenu: HTMLDivElement;
private redoMenu: HTMLDivElement;
private resetMenu: HTMLDivElement;
private cancelMenu: HTMLDivElement;
private confirmMenu: HTMLDivElement;
private optionBar: HTMLDivElement;
private small: HTMLSpanElement;
private normal: HTMLSpanElement;
private big: HTMLSpanElement;
private red: HTMLSpanElement;
private orangle: HTMLSpanElement;
private blue: HTMLSpanElement;
private green: HTMLSpanElement;
private black: HTMLSpanElement;
private white: HTMLSpanElement;
private grey: HTMLSpanElement;
private sizeOptions: HTMLSpanElement;
private colorOptions: HTMLSpanElement;
private optionArrow: HTMLDivElement;
private menuMap = new Map();
private eleColorMap = new Map();
private colorEleMap = new Map();
constructor(options: any) {
this.wrapper = options.wrapper;
this.canvas = options.canvas;
this.screenshotCanvas = options.screenshotCanvas;
this.screenshotResizer = options.screenshotResizer;
this.screenshotToolbar = options.screenshotToolbar.toolbar;
this.screenshotConfirmButton = options.screenshotToolbar.screenshot.confirm;
this.screenshotCancelButton = options.screenshotToolbar.screenshot.cancel;
this.canvasWrapper = options.canvasWrapper;
this.northResizer = options.northResizer;
this.northWestResizer = options.northWestResizer;
this.westResizer = options.westResizer;
this.southWestResizer = options.southWestResizer;
this.southResizer = options.southResizer;
this.southEastResizer = options.southEastResizer;
this.eastResizer = options.eastResizer;
this.northEastResizer = options.northEastResizer;
this.fixResizerPosition();
this.squareSize = this.southResizer.getBoundingClientRect().width;
this.toolbar = options.toolbar;
this.rectangleMenu = options.rectangleMenu;
this.menuMap.set(OperatorType.RECT, this.rectangleMenu);
this.ellipseMenu = options.ellipseMenu;
this.menuMap.set(OperatorType.ELLIPSE, this.ellipseMenu);
this.arrowMenu = options.arrowMenu;
this.menuMap.set(OperatorType.ARROW, this.arrowMenu);
this.drawMenu = options.drawMenu;
this.menuMap.set(OperatorType.DRAW, this.drawMenu);
this.textMenu = options.textMenu;
this.menuMap.set(OperatorType.TEXT, this.textMenu);
this.mosaicMenu = options.mosaicMenu;
this.menuMap.set(OperatorType.MOSAIC, this.mosaicMenu);
this.shrinkMenu = options.shrinkMenu;
this.extendMenu = options.extendMenu;
this.flipXMenu = options.flipXMenu;
this.flipYMenu = options.flipYMenu;
this.rotateClockwiseMenu = options.rotateClockwiseMenu;
this.rotateCounterClockwiseMenu = options.rotateCounterClockwiseMenu;
this.cropMenu = options.cropMenu;
this.undoMenu = options.undoMenu;
this.redoMenu = options.redoMenu;
this.resetMenu = options.resetMenu;
this.cancelMenu = options.cancelMenu;
this.confirmMenu = options.confirmMenu;
const ele = this.createOperatorOptionBar();
this.optionBar = ele.optionBar;
this.small = ele.small;
this.normal = ele.normal;
this.big = ele.big;
this.red = ele.red;
this.orangle = ele.orangle;
this.green = ele.green;
this.blue = ele.blue;
this.black = ele.black;
this.white = ele.white;
this.grey = ele.grey;
this.sizeOptions = ele.sizeOptions;
this.colorOptions = ele.colorOptions;
this.optionArrow = ele.arrow;
this.eleColorMap.set(this.red, COLOR_MAP.RED);
this.eleColorMap.set(this.orangle, COLOR_MAP.ORANGLE);
this.eleColorMap.set(this.green, COLOR_MAP.GREEN);
this.eleColorMap.set(this.blue, COLOR_MAP.BLUE);
this.eleColorMap.set(this.black, COLOR_MAP.BLACK);
this.eleColorMap.set(this.white, COLOR_MAP.WHITE);
this.eleColorMap.set(this.grey, COLOR_MAP.GREY);
this.colorEleMap.set(COLOR_MAP.RED, this.red);
this.colorEleMap.set(COLOR_MAP.ORANGLE, this.orangle);
this.colorEleMap.set(COLOR_MAP.GREEN, this.green);
this.colorEleMap.set(COLOR_MAP.BLUE, this.black);
this.colorEleMap.set(COLOR_MAP.BLACK, this.black);
this.colorEleMap.set(COLOR_MAP.WHITE, this.white);
this.colorEleMap.set(COLOR_MAP.GREY, this.grey);
}
init(imageEditor: ImageEditor) {
this.imageEditor = imageEditor;
this.fabricWrapperEl = imageEditor.getCanvas().wrapperEl;
this.initResizers();
this.fixToolbarPosition();
this.appendHoverCSS();
}
appendHoverCSS() {
if (ElementManager.HAS_CURSOR_CSS_ADDED) {
return;
}
const style = document.createElement('style');
const css = `
.north-cursor-resize:hover, .south-cursor-resize:hover{
cursor: ns-resize;
}
.west-cursor-resize:hover, .east-cursor-resize:hover{
cursor: ew-resize;
}
.north-east-cursor-resize:hover, .south-west-cursor-resize:hover{
cursor: nesw-resize;
}
.north-west-cursor-resize:hover, .south-east-cursor-resize:hover{
cursor: nwse-resize;
}
`
style.appendChild(document.createTextNode(css));
document.head.appendChild(style);
ElementManager.HAS_CURSOR_CSS_ADDED = true;
this.northResizer.classList.add('north-cursor-resize');
this.westResizer.classList.add('west-cursor-resize');
this.southResizer.classList.add('south-cursor-resize');
this.eastResizer.classList.add('east-cursor-resize');
this.screenshotResizer.north.classList.add('north-cursor-resize');
this.screenshotResizer.northWest.classList.add('north-west-cursor-resize');
this.screenshotResizer.west.classList.add('west-cursor-resize');
this.screenshotResizer.southWest.classList.add('south-west-cursor-resize');
this.screenshotResizer.south.classList.add('south-cursor-resize');
this.screenshotResizer.southEast.classList.add('south-east-cursor-resize');
this.screenshotResizer.east.classList.add('east-cursor-resize');
this.screenshotResizer.northEast.classList.add('north-east-cursor-resize');
}
createOperatorOptionBar() {
const wrapper = document.createElement("div");
// 默认隐藏
wrapper.style.display = 'none';
wrapper.style.backgroundColor = 'white';
wrapper.style.position = 'absolute';
wrapper.style.borderRadius = '4px';
// 解决行高预留空白问题
wrapper.style.fontSize = '0';
const sizeOptions = document.createElement("span");
const colorOptions = document.createElement("span");
sizeOptions.style.display = 'inline-block';
colorOptions.style.display = 'inline-block';
wrapper.append(sizeOptions);
wrapper.append(colorOptions);
const small = document.createElement("span");
small.style.width = '8px';
small.style.height = '8px';
small.style.margin = '16px 0 16px 16px';
small.style.backgroundColor = ElementManager.DEACTIVE_SIZE_COLOR;
small.style.display = 'inline-block';
small.style.borderRadius = '50%';
const normal = document.createElement("span");
normal.style.width = '12px';
normal.style.height = '12px';
normal.style.margin = '14px 0 14px 14px';
normal.style.backgroundColor = ElementManager.DEACTIVE_SIZE_COLOR;
normal.style.display = 'inline-block';
normal.style.borderRadius = '50%';
const big = document.createElement("span");
big.style.width = '16px';
big.style.height = '16px';
big.style.margin = '12px 16px 12px 14px';
big.style.backgroundColor = ElementManager.DEACTIVE_SIZE_COLOR;
big.style.display = 'inline-block';
big.style.borderRadius = '50%';
sizeOptions.append(small);
small.classList.add('online-image-editor-operator-option');
sizeOptions.append(normal);
normal.classList.add('online-image-editor-operator-option');
sizeOptions.append(big);
big.classList.add('online-image-editor-operator-option');
const red = document.createElement("span");
red.style.backgroundColor = COLOR_MAP.RED;
const orangle = document.createElement("span");
orangle.style.backgroundColor = COLOR_MAP.ORANGLE;
const blue = document.createElement("span");
blue.style.backgroundColor = COLOR_MAP.BLUE;
const green = document.createElement("span");
green.style.backgroundColor = COLOR_MAP.GREEN;
const black = document.createElement("span");
black.style.backgroundColor = COLOR_MAP.BLACK;
const white = document.createElement("span");
white.style.backgroundColor = COLOR_MAP.WHITE;
const grey = document.createElement("span");
grey.style.backgroundColor = COLOR_MAP.GREY;
colorOptions.append(red);
colorOptions.append(orangle);
colorOptions.append(blue);
colorOptions.append(green);
colorOptions.append(black);
colorOptions.append(white);
colorOptions.append(grey);
const colors = [red, orangle, blue, green, black, white, grey];
for (const color of colors) {
const style = color.style;
style.display = 'inline-block';
style.width = '20px';
style.height = '20px';
style.margin = '10px 0 10px 8px';
style.boxSizing = 'border-box';
color.classList.add('online-image-editor-operator-option');
}
red.style.margin = '10px 0 10px 0';
grey.style.marginRight = '8px';
white.style.border = 'solid 1px #E6E6E6';
white.style.boxSizing = 'border-box';
const arrowWrapper = document.createElement('div');
const arrow = document.createElement('div');
arrow.style.position = 'absolute';
arrow.style.left = '142px';
arrow.style.top = '-8px';
arrow.style.borderTopWidth = '0';
arrow.classList.add('online-image-editor-operator-option-arrow');
arrowWrapper.append(arrow);
wrapper.append(arrowWrapper);
const style = document.createElement('style');
const css = `
.online-image-editor-operator-option:hover{
cursor: pointer;
}
.online-image-editor-operator-option-arrow:after{
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-width: 8px;
content: "";
top:1px;
margin-left:-8px;
border-top-width:0;
border-bottom-color: #FFF;
}
`
style.appendChild(document.createTextNode(css));
document.head.appendChild(style);
document.body.append(wrapper);
return {
optionBar: wrapper,
small, normal, big, red, orangle, green, blue, black, white, grey,
sizeOptions, colorOptions, arrow
};
}
bindEvents() {
const imageEditor = this.imageEditor!;
this.rectangleMenu.onclick = () => { this.switchOperator(OperatorType.RECT) };
this.ellipseMenu.onclick = () => { this.switchOperator(OperatorType.ELLIPSE) };
this.arrowMenu.onclick = () => { this.switchOperator(OperatorType.ARROW) };
this.drawMenu.onclick = () => { this.switchOperator(OperatorType.DRAW) };
this.mosaicMenu.onclick = () => { this.switchOperator(OperatorType.MOSAIC) };
this.textMenu.onclick = () => { this.switchOperator(OperatorType.TEXT) };
this.shrinkMenu.onclick = () => { this.shrinkCanvasToBackgroundImage(); }
this.extendMenu.onclick = () => { this.extendsCanvas(); }
this.flipXMenu.onclick = () => { this.flipHorizontal(); }
this.flipYMenu.onclick = () => { this.flipVertical(); }
this.rotateClockwiseMenu.onclick = () => { this.rotateClockwise(); }
this.rotateCounterClockwiseMenu.onclick = () => { this.rotateCounterClockwise(); }
this.cropMenu.onclick = () => { this.cropImage(); }
this.undoMenu.onclick = () => { imageEditor.getHistory().undo(); }
this.redoMenu.onclick = () => { imageEditor.getHistory().redo(); }
this.resetMenu.onclick = () => { this.resetImageEditor(); }
this.confirmMenu.onclick = () => { this.downloadAreaImage(); }
}
switchOperator(type: OperatorType) {
const imageEditor = this.imageEditor!;
const previous = imageEditor.getOperatorType();
if (imageEditor.getOperatorType() == type) {
imageEditor.changeOperatorType(OperatorType.NONE);
} else {
imageEditor.changeOperatorType(type);
}
const current = imageEditor.getOperatorType();
if (previous != OperatorType.NONE) {
const preEle = this.menuMap.get(previous);
preEle.style.backgroundColor = 'transparent';
this.hideOptionBar();
}
if (current != OperatorType.NONE) {
const currEle = this.menuMap.get(current);
currEle.style.backgroundColor = '#FFF';
this.showOptionBar(currEle);
}
}
hideOptionBar() {
this.optionBar.style.display = 'none';
}
showOptionBarDirect() {
this.optionBar.style.display = 'inline-block';
}
showOptionBar(currEle: HTMLDivElement) {
const imageEditor = this.imageEditor!;
this.adjustOptionBarPosition(currEle)
const operator = imageEditor.getActiveOperator();
// 马赛克不显示颜色选项
const isMosaic = operator instanceof MosaicOperator;
if (!isMosaic) {
this.showFullOptions();
const that = this;
const color = operator.getOperatorColor();
const eles = this.eleColorMap.keys();
for (const ele of eles) {
const eleColor = this.eleColorMap.get(ele);
if (eleColor == color) {
this.activeColor(ele);
} else {
this.deactiveColor(ele);
}
}
this.red.onclick = () => { that.changeColor(operator, COLOR_MAP.RED, that.red) };
this.orangle.onclick = () => { that.changeColor(operator, COLOR_MAP.ORANGLE, that.orangle) };
this.green.onclick = () => { that.changeColor(operator, COLOR_MAP.GREEN, that.green) };
this.blue.onclick = () => { that.changeColor(operator, COLOR_MAP.BLUE, that.blue) };
this.black.onclick = () => { that.changeColor(operator, COLOR_MAP.BLACK, that.black) };
this.white.onclick = () => { that.changeColor(operator, COLOR_MAP.WHITE, that.white) };
this.grey.onclick = () => { that.changeColor(operator, COLOR_MAP.GREY, that.grey) };
} else {
this.showSizeOptions();
}
let s = 2, n = 4, b = 6;
if (operator instanceof MosaicOperator) {
s = 10, n = 20, b = 40;
} else if (operator instanceof TextOperator) {
s = 15, n = 20, b = 25;
}
const size = operator.getOperatorSize();
switch (size) {
case s: this.selectSize(this.small); break;
case n: this.selectSize(this.normal); break;
case b: this.selectSize(this.big); break;
}
const that = this;
this.small.onclick = () => { operator.setOperatorSize(s); that.selectSize(that.small); }
this.normal.onclick = () => { operator.setOperatorSize(n); that.selectSize(that.normal); }
this.big.onclick = () => { operator.setOperatorSize(b); that.selectSize(that.big); }
}
showSizeOptions() {
const isColorVisiable = this.imageEditor!.getOperatorType() != OperatorType.MOSAIC;
if (!isColorVisiable) {
this.colorOptions.style.display = 'none';
// 196 / 2,196是整个颜色区域的宽度,去除之后,大小选择框,要向右移动这么多
let left = Number(this.optionBar.style.left.replace('px', ''));
left = left + (196) / 2;
let width = this.optionBar.getBoundingClientRect().width;
this.optionBar.style.left = left + 'px';
const arrowLeft = width / 2;
this.optionArrow.style.left = arrowLeft + 'px';
}
}
showFullOptions() {
const isColorVisiable = this.colorOptions.style.display == 'inline-block';
if (!isColorVisiable) {
this.colorOptions.style.display = 'inline-block';
let arrowLeft = Number(this.optionArrow.style.left.replace('px', ''));
arrowLeft = arrowLeft + 168 / 2;
this.optionArrow.style.left = arrowLeft + 'px';
}
}
changeColor(operator: OperatorProps, color: string, ele: HTMLSpanElement) {
operator.setOperatorColor(color);
const colors = [this.red, this.orangle, this.green, this.blue, this.black, this.white, this.grey];
for (const color of colors) {
if (color == ele) {
this.activeColor(color);
} else {
this.deactiveColor(color);
}
}
}
activeColor(ele: HTMLSpanElement) {
ele.setAttribute(ElementManager.COLOR_ACTIVE_FLAG, 'true');
if (ele != this.white) {
ele.style.border = '6px solid ' + this.eleColorMap.get(ele);
ele.style.backgroundColor = 'white';
} else {
ele.style.border = '6px solid #E6E6E6';
ele.style.backgroundColor = 'white';
}
}
deactiveColor(ele: HTMLSpanElement) {
if (ele.getAttribute(ElementManager.COLOR_ACTIVE_FLAG)) {
if (ele != this.white) {
ele.style.border = '0';
ele.style.backgroundColor = this.eleColorMap.get(ele);
} else {
ele.style.border = 'solid 1px #E6E6E6';
ele.style.backgroundColor = 'white';
}
ele.removeAttribute(ElementManager.COLOR_ACTIVE_FLAG);
}
}
selectSize(ele: HTMLSpanElement) {
const sizes = [this.small, this.normal, this.big];
for (const size of sizes) {
if (ele == size) {
this.activeSize(size);
} else {
this.deactiveSize(size);
}
}
}
activeSize(ele: HTMLSpanElement) {
ele.style.backgroundColor = ElementManager.ACTIVE_SIZE_COLOR;
}
deactiveSize(ele: HTMLSpanElement) {
ele.style.backgroundColor = ElementManager.DEACTIVE_SIZE_COLOR;
}
hideToolbar() {
this.toolbar.style.display = 'none';
this.hideOptionBar();
}
showToolbar() {
this.toolbar.style.display = 'block';
const optType = this.imageEditor!.getOperatorType();
// 对于要显示的toolbar,直接显示
if (optType != OperatorType.NONE) {
this.showOptionBarDirect();
}
this.fixToolbarPosition();
}
initResizers() {
const that = this;
/********************** 开始处理头部的拉伸箭头 *****************/
this.northResizer.removeEventListener('mousedown', this.topStartFunc)
window.removeEventListener('mousemove', this.topMoveFunc);
window.removeEventListener('mouseup', this.topFinsihFunc);
this.topStartFunc = (event: MouseEvent) => {
that.topInResize = true;
let fwTop = this.fabricWrapperEl!.style.top.replace('px', '');
if (fwTop == '') {
fwTop = '0';
}
const top = this.canvasWrapper.style.top.replace('px', '');
const height = this.canvasWrapper.style.height.replace('px', '');
that.topChange.y = event.pageY;
that.topChange.top = Number(top);
that.topChange.height = Number(height);
that.topChange.fwTop = Number(fwTop);
that.topChange.changeHeight = 0;
const body = document.querySelector('body');
body!.style.cursor = 'n-resize'
that.hideToolbar();
}
this.topMoveFunc = (event: MouseEvent) => {
if (!that.topInResize) return;
that.changeHeightFromTop(event.pageY);
}
this.topFinsihFunc = (_event: MouseEvent) => {
if (!that.topInResize) {
return;
}
that.finishResize();
that.showToolbar();
}
this.northResizer.addEventListener('mousedown', this.topStartFunc)
window.addEventListener('mousemove', this.topMoveFunc);
window.addEventListener('mouseup', this.topFinsihFunc);
/********************** 开始处理左侧的拉伸箭头 *****************/
this.westResizer.removeEventListener('mousedown', this.leftStartFunc)
window.removeEventListener('mousemove', this.leftMoveFunc);
window.removeEventListener('mouseup', this.leftFinishFunc);
this.leftStartFunc = (event: MouseEvent) => {
that.leftInResize = true;
let fwLeft = this.fabricWrapperEl!.style.left.replace('px', '');
if (fwLeft == '') {
fwLeft = '0';
}
let left = this.canvasWrapper.style.left.replace('px', '');
const width = this.canvasWrapper.style.width.replace('px', '')
that.leftChange.x = event.pageX;
that.leftChange.left = Number(left);
that.leftChange.width = Number(width);
that.leftChange.fwLeft = Number(fwLeft);
that.leftChange.changeWidth = 0;
const body = document.querySelector('body');
body!.style.cursor = 'w-resize'
that.hideToolbar();
}
this.leftMoveFunc = (event: MouseEvent) => {
if (!that.leftInResize) return;
that.changeHeightFromLeft(event.pageX);
}
this.leftFinishFunc = (_event: MouseEvent) => {
if (!that.leftInResize) return;
that.finishResize();
that.showToolbar();
}
this.westResizer.addEventListener('mousedown', this.leftStartFunc)
window.addEventListener('mousemove', this.leftMoveFunc);
window.addEventListener('mouseup', this.leftFinishFunc);
/********************** 开始处理底部的拉伸箭头 *****************/
this.southResizer.removeEventListener('mousedown', this.bottomStartFunc)
window.removeEventListener('mousemove', this.bottomMoveFunc);
window.removeEventListener('mouseup', this.bottomFinishFunc);
this.bottomStartFunc = (event: MouseEvent) => {
that.bottomInResize = true;
const height = this.canvasWrapper.style.height.replace('px', '');
const top = this.southResizer.style.top.replace('px', '');
const leftRightTop = this.westResizer.style.top.replace('px', '');
that.bottomChange.height = Number(height);
that.bottomChange.y = event.pageY;
that.bottomChange.top = Number(top);
that.bottomChange.leftRightTop = Number(leftRightTop);
that.bottomChange.changeHeight = 0;
const body = document.querySelector('body');
body!.style.cursor = 's-resize'
that.hideToolbar();
}
this.bottomMoveFunc = (event: MouseEvent) => {
if (!that.bottomInResize) return;
that.changeHeightFromBottom(event.pageY);
}
this.bottomFinishFunc = (_event: MouseEvent) => {
if (!that.bottomInResize) return;
that.finishResize();
that.showToolbar();
}
this.southResizer.addEventListener('mousedown', this.bottomStartFunc)
window.addEventListener('mousemove', this.bottomMoveFunc);
window.addEventListener('mouseup', this.bottomFinishFunc);
/********************** 开始处理右侧的拉伸箭头 *****************/
this.southResizer.removeEventListener('mousedown', this.rightStartFunc)
window.removeEventListener('mousemove', this.rightMoveFunc);
window.removeEventListener('mouseup', this.rightFinishFunc);
this.rightStartFunc = (event: MouseEvent) => {
that.rightInResize = true;
const width = this.canvasWrapper.style.width.replace('px', '');
const left = this.eastResizer.style.left.replace('px', '');
const topBottomLeft = this.northResizer.style.left.replace('px', '');
that.rightChange.width = Number(width)
that.rightChange.x = event.pageX;
that.rightChange.left = Number(left);
that.rightChange.topBottomLeft = Number(topBottomLeft);
that.rightChange.changeWidth = 0;
const body = document.querySelector('body');
body!.style.cursor = 'e-resize'
that.hideToolbar();
}
this.rightMoveFunc = (event: MouseEvent) => {
if (!that.rightInResize) return;
that.changeWidthFromRight(event.pageX);
}
this.rightFinishFunc = (_event: MouseEvent) => {
if (!that.rightInResize) return;
that.finishResize();
that.showToolbar();
}
this.eastResizer.addEventListener('mousedown', this.rightStartFunc)
window.addEventListener('mousemove', this.rightMoveFunc);
window.addEventListener('mouseup', this.rightFinishFunc);
/********************** 拉伸箭头的部分处理结束 *****************/
}
changeHeightFromLeft(pageX: number) {
const currentX = this.leftChange.x;
const oldLeft = this.leftChange.left;
const oldWidth = this.leftChange.width;
// 用当前Y值,减去开始的Y值,得到的一段长度是top增加的值,和高度减少的值
let changedWidth = pageX - currentX;
let newWidth = oldWidth - changedWidth;
if (newWidth < 80) {
newWidth = 80;
changedWidth = oldWidth - newWidth;
}
const newLeft = Number(oldLeft) + changedWidth;
const newFwLeft = this.leftChange.fwLeft - changedWidth;
this.canvasWrapper.style.width = newWidth + 'px';
this.canvasWrapper.style.left = newLeft + 'px';
this.westResizer.style.left = (newLeft - this.squareSize) + 'px';
this.fabricWrapperEl!.style.left = newFwLeft + 'px';
this.leftChange.changeWidth = changedWidth;
this.fixResizerPosition();
}
changeHeightFromTop(pageY: number) {
const currentY = this.topChange.y;
const oldTop = this.topChange.top;
const oldHeight = this.topChange.height;
// 用当前Y值,减去开始的Y值,得到的一段长度是top增加的值,和高度减少的值
let changedHeight = pageY - currentY;
let newHeight = oldHeight - changedHeight;
if (newHeight < 80) {
newHeight = 80;
changedHeight = oldHeight - newHeight;
}
const newTop = Number(oldTop) + changedHeight;
const newFwTop = this.topChange.fwTop - changedHeight;
this.canvasWrapper.style.height = newHeight + 'px';
this.canvasWrapper.style.top = newTop + 'px';
this.northResizer.style.top = (newTop - this.squareSize) + 'px';
this.fabricWrapperEl!.style.top = (newFwTop) + 'px';
this.topChange.changeHeight = changedHeight;
this.fixResizerPosition();
}
changeHeightFromBottom(pageY: number) {
const currentY = this.bottomChange.y;
const oldHeight = this.bottomChange.height;
let changedHeight = pageY - currentY;
let newHeight = oldHeight + changedHeight;
// 给个最小值
if (newHeight < 80) {
newHeight = 80;
changedHeight = newHeight - oldHeight;
}
this.canvasWrapper.style.height = newHeight + 'px';
this.bottomChange.changeHeight = changedHeight;
this.fixResizerPosition();
}
changeWidthFromRight(pageX: number) {
const currentY = this.rightChange.x;
const oldWidth = this.rightChange.width;
let changedWidth = pageX - currentY;
let newWidth = Number(oldWidth) + changedWidth;
if (newWidth < 80) {
newWidth = 80;
changedWidth = newWidth - oldWidth;
}
const newLeft = this.rightChange.left + changedWidth;
this.canvasWrapper.style.width = newWidth + 'px';
this.eastResizer.style.left = newLeft + 'px';
// bottom和top也要跟着同步改变
this.northResizer.style.left = this.rightChange.topBottomLeft + (changedWidth / 2) + 'px';
this.southResizer.style.left = this.rightChange.topBottomLeft + (changedWidth / 2) + 'px';
this.rightChange.changeWidth = changedWidth;
this.fixResizerPosition();
}
finishResize() {
const body = document.querySelector('body');
body!.style.cursor = 'default'
if (this.topInResize) {
this.topInResize = false;
this.expandTopToBaseMap();
}
if (this.bottomInResize) {
this.bottomInResize = false;
this.expandBottomToBaseMap();
}
if (this.leftInResize) {
this.leftInResize = false;
this.expandLeftToBaseMap();
}
if (this.rightInResize) {
this.rightInResize = false;
this.expandRightToBaseMap();
}
}
expandTopToBaseMap() {
let fabricTopStr = this.fabricWrapperEl?.style.top.replace('px', '');
let fabricHeightStr = this.fabricWrapperEl?.style.height.replace('px', '');
if (fabricTopStr == '') {
fabricTopStr = '0';
}
const fabricTop = Number(fabricTopStr);
const fabricHeight = Number(fabricHeightStr);
// 小于0不用考虑,大于0要考虑,将大于0部分的宽度扩展出来
if (fabricTop > 0) {
this.fabricWrapperEl!.style.top = '0';
const newHeight = fabricHeight + fabricTop;
this.imageEditor!.setCanvasHeight(newHeight);
this.imageEditor!.transformY(fabricTop);
}
}
expandLeftToBaseMap() {
let fabricLeftStr = this.fabricWrapperEl?.style.left.replace('px', '');
let fabricWidthStr = this.fabricWrapperEl?.style.width.replace('px', '');
if (fabricLeftStr == '') {
fabricLeftStr = '0';
}
const fabricLeft = Number(fabricLeftStr);
const fabricWidth = Number(fabricWidthStr);
// 小于0不用考虑,大于0要考虑,将大于0部分的宽度扩展出来
if (fabricLeft > 0) {
this.fabricWrapperEl!.style.left = '0';
const newWidth = fabricWidth + fabricLeft;
this.imageEditor!.setCanvasWidth(newWidth);
this.imageEditor!.transformX(fabricLeft);
}
}
expandBottomToBaseMap() {
let fabricTopStr = this.fabricWrapperEl?.style.top.replace('px', '');
if (fabricTopStr == '') {
fabricTopStr = '0'
}
const fabricTop = Number(fabricTopStr);
const fabricHeightStr = this.fabricWrapperEl?.style.height.replace('px', '');
const fabircHeight = Number(fabricHeightStr);
const heightExcludeLeftTop = fabircHeight + fabricTop;
const wrapperHeightStr = this.canvasWrapper.style.height.replace('px', '');
const wrapperHeight = Number(wrapperHeightStr);
let newHeight = fabircHeight;
if (heightExcludeLeftTop < wrapperHeight) {
const diffHeight = wrapperHeight - heightExcludeLeftTop;
newHeight = fabircHeight + diffHeight;
}
this.imageEditor?.setCanvasHeight(newHeight);
}
expandRightToBaseMap() {
let fabricLeftStr = this.fabricWrapperEl?.style.left.replace('px', '');
if (fabricLeftStr == '') {
fabricLeftStr = '0';
}
const fabricLeft = Number(fabricLeftStr);
const fabricWidthStr = this.fabricWrapperEl?.style.width.replace('px', '');
// 可以肯定的是Top和left必然是小于0的,大于0的直接扩展了
const fabricWidth = Number(fabricWidthStr);
// 这里的可见范围不是全部的可见范围,不包括因为left、top是负数而被隐藏的那一部分
// 但是包括右边和下边被隐藏的部分
const widthExcludeLeftTop = fabricWidth + fabricLeft;
const wrapperWidthStr = this.canvasWrapper.style.width.replace('px', '');
const wrapperWidth = Number(wrapperWidthStr);
// 如果wrapper大于可见区域,那么要计算差值,然后在canvas上加上这个差值
let newWidth = fabricWidth;
if (widthExcludeLeftTop < wrapperWidth) {
const diffWidth = wrapperWidth - widthExcludeLeftTop;
newWidth = fabricWidth + diffWidth;
}
this.imageEditor?.setCanvasWidth(newWidth)
}
// 四周如果有白板,就把白板都缩了,其它的不同,相当于只动画板,不动画布
shrinkCanvasToBackgroundImage() {
const canvas = this.imageEditor!.getCanvas();
const image = canvas.backgroundImage!;
const point = image.getXY();
const { visiableHeight, visiableWidth, left, top } = this.getCanvasAreaInfo();
const shrinkRight = point.x + image.width - left < visiableWidth;
const shrinkBottom = point.y + image.height - top < visiableHeight;
const cutWrapperLeft = point.x > left ? point.x - left : 0;
const cutWrapperTop = point.y > top ? point.y - top : 0;
const cutWrapperRight = shrinkRight ? visiableWidth - (point.x + image.width - left) : 0;
const cutWrapperBottom = shrinkBottom ? visiableHeight - (point.y + image.height - top) : 0;
const wrapperWidth = pxielToNumber(this.canvasWrapper.style.width)
const wrapperHeight = pxielToNumber(this.canvasWrapper.style.height)
const wrapperTop = pxielToNumber(this.canvasWrapper.style.top)
const wrapperLeft = pxielToNumber(this.canvasWrapper.style.left)
this.canvasWrapper.style.width = wrapperWidth - cutWrapperRight - cutWrapperLeft + 'px';
this.canvasWrapper.style.height = wrapperHeight - cutWrapperBottom - cutWrapperTop + 'px';
// 视口向下移动的时候,画板不要动,也就是说画板left、top要相应的变化
this.canvasWrapper.style.top = wrapperTop + cutWrapperTop + 'px';
this.canvasWrapper.style.left = wrapperLeft + cutWrapperLeft + 'px';
this.fabricWrapperEl!.style.top = -top - cutWrapperTop + 'px';
this.fabricWrapperEl!.style.left = -left - cutWrapperLeft + 'px';
this.fixComponentsPosition();
}
// 如果图片没有显示全,那么先显示全图片
extendsCanvas() {
const canvas = this.imageEditor!.getCanvas();
const image = canvas.backgroundImage!;
const point = image.getXY();
const { visiableHeight, visiableWidth, left, top, canvasHeight, canvasWidth } = this.getCanvasAreaInfo();
const topExtend = point.y - top;
const leftExtend = point.x - left;
const bottomExtend = visiableHeight - (point.y + image.height - top);
const rightExtend = visiableWidth - (point.x + image.width - left);
const wrapperWidth = pxielToNumber(this.canvasWrapper.style.width)
const wrapperHeight = pxielToNumber(this.canvasWrapper.style.height)
const wrapperTop = pxielToNumber(this.canvasWrapper.style.top)
const wrapperLeft = pxielToNumber(this.canvasWrapper.style.left)
let maxXExtend = Math.max(leftExtend, rightExtend);
let maxYExtend = Math.max(topExtend, bottomExtend);
let minXExtend = Math.min(leftExtend, rightExtend);
let minYExtend = Math.min(topExtend, bottomExtend);
let xExtend = 0, yExtend = 0;
if (maxXExtend >= 0 && maxXExtend !== minXExtend) {
xExtend = maxXExtend;
} else if (maxXExtend >= 0 && maxXExtend === minXExtend) {
xExtend = maxXExtend + Math.round(image.width * 0.2);
}
if (maxYExtend >= 0 && maxYExtend !== minYExtend) {
yExtend = maxYExtend;
} else if (maxYExtend >= 0 && maxYExtend === minYExtend) {
yExtend = maxYExtend + Math.round(image.height * 0.15);
}
const extendLeft = xExtend - leftExtend;
const extendRight = xExtend - rightExtend;
const extendTop = yExtend - topExtend;
const extendBottom = yExtend - bottomExtend;
const newWrapperHeight = wrapperHeight + extendBottom + extendTop;
const newWrapperWidth = wrapperWidth + extendLeft + extendRight;
this.canvasWrapper.style.width = newWrapperWidth + 'px';
this.canvasWrapper.style.height = newWrapperHeight + 'px';
this.canvasWrapper.style.top = wrapperTop - extendTop + 'px';
this.canvasWrapper.style.left = wrapperLeft - extendLeft + 'px';
let newLeft = 0, newTop = 0;
// 如果只是扩展现有的
if (extendLeft < left) {
newLeft = extendLeft - left;
this.fabricWrapperEl!.style.left = newLeft + 'px';
} else if (extendLeft >= left) {
const newExtend = extendLeft - left;
this.fabricWrapperEl!.style.left = '0'
this.imageEditor!.transformX(newExtend);
}
if (extendTop < top) {
newTop = extendTop - top;
this.fabricWrapperEl!.style.top = newTop + 'px';
} else if (extendTop >= top) {
const newExtend = extendTop - top;
this.fabricWrapperEl!.style.top = '0'
this.imageEditor!.transformY(newExtend);
}
const extendWidth = newWrapperWidth - (canvasWidth - left);
const extendHeight = newWrapperHeight - (canvasHeight - top);
const newCanvasWidth = extendWidth > 0 ? canvasWidth + extendWidth : canvasWidth;
const newCanvasHeight = extendHeight > 0 ? canvasHeight + extendHeight : canvasHeight
this.imageEditor!.setCanvasDims(newCanvasWidth, newCanvasHeight);
this.fixComponentsPosition();
}
flipHorizontal() {
// 左右翻转时,上下是不要动的,然后左侧多余的部分移动到右侧,右侧多余的部分移动到左侧
const { right, canvasWidth } = this.getCanvasAreaInfo();
const previous = new FlipXUndoProps();
const current = new FlipXUndoProps();
previous.fabricWrapperEl = this.fabricWrapperEl!;
current.fabricWrapperEl = this.fabricWrapperEl!;
previous.left = this.fabricWrapperEl!.style.left;
// 内容翻转,左偏移变右偏移
this.fabricWrapperEl!.style.left = -right + 'px';
current.left = this.fabricWrapperEl!.style.left;
const canvas = this.imageEditor?.getCanvas()!;
// 翻转状态换一下
const backgroundImage = canvas.backgroundImage!;
current.backgroundImage = previous.backgroundImage = backgroundImage;
const flipX = backgroundImage!.flipX;
previous.flipX = flipX;
backgroundImage!.flipX = !flipX;
current.flipX = backgroundImage!.flipX;
// 左右偏移互换一下
const x = backgroundImage.getX();
const biWidth = backgroundImage.width;
const newX = canvasWidth - biWidth - x;
backgroundImage.setX(newX);
previous.x = x;
current.x = newX;
const objs = canvas.getObjects() ?? [];
for (const obj of objs) {
previous.objs.push(obj);
current.objs.push(obj);
const objFlipX = obj.flipX;
previous.objFlipX.push(objFlipX);
const x = obj.getX()
previous.objX.push(x);
const width = obj.width;
const nx = canvasWidth - width - x;
obj.flipX = !objFlipX;
obj.setX(nx);
current.objFlipX.push(obj.flipX);
current.objX.push(nx);
canvas.setActiveObject(obj);
}
canvas.renderAll();
this.imageEditor!.getHistory().recordFlipXAction(previous, current);
}
flipVertical() {
// 上下翻转时,左右是不要动的,然后上侧多余的部分移动到下侧,下侧多余的部分移动到上侧
const { bottom, canvasHeight } = this.getCanvasAreaInfo();
const previous = new FlipYUndoProps();
const current = new FlipYUndoProps();
previous.fabricWrapperEl = this.fabricWrapperEl!;
current.fabricWrapperEl = this.fabricWrapperEl!;
// 内容翻转,下偏移变上偏移
previous.top = this.fabricWrapperEl!.style.top;
this.fabricWrapperEl!.style.top = -bottom + 'px';
current.top = this.fabricWrapperEl!.style.top;
const canvas = this.imageEditor?.getCanvas()!;
// 翻转状态换一下
const backgroundImage = canvas.backgroundImage!;
current.backgroundImage = previous.backgroundImage = backgroundImage;
const flipY = backgroundImage!.flipY;
previous.flipY = flipY;
backgroundImage!.flipY = !flipY;
current.flipY = backgroundImage!.flipY;
// 上下偏移互换一下
const y = backgroundImage.getY();
const biHeight = backgroundImage.height;
const newY = canvasHeight - biHeight - y;
backgroundImage.setY(newY);
previous.y = y;
current.y = newY;
const objs = canvas.getObjects() ?? [];
for (const obj of objs) {
previous.objs.push(obj);
current.objs.push(obj);
const objFlipY = obj.flipY;
previous.objFlipY.push(objFlipY);
const y = obj.getY()
previous.objY.push(y);
const height = obj.height;
const ny = canvasHeight - height - y;
obj.flipY = !objFlipY;
obj.setY(ny);
current.objFlipY.push(obj.flipY);
current.objY.push(ny);
canvas.setActiveObject(obj);
}
canvas.renderAll();
this.imageEditor!.getHistory().recordFlipYAction(previous, current);
}
// 顺时针旋转90度
rotateClockwise() {
const previous = new RotateProps();
const current = new RotateProps();
previous.canvasWrapper = current.canvasWrapper = this.canvasWrapper;
previous.fabricWrapperEl = current.fabricWrapperEl = this.fabricWrapperEl!;
previous.imageEditor = current.imageEditor = this.imageEditor!;
const canvasArea = this.getCanvasAreaInfo();
const { left, bottom } = canvasArea;
const { canvasWidth, canvasHeight } = canvasArea;
const { visiableHeight, visiableWidth } = canvasArea;
const canvas = this.imageEditor!.getCanvas()!;
previous.canvasHeight = canvasHeight; previous.canvasWidth = canvasWidth;
// 顺时针旋转90度的时候,长高要做一个互换
this.imageEditor!.setCanvasDims(canvasHeight, canvasWidth);
current.canvasHeight = canvasWidth; current.canvasWidth = canvasHeight;
const fwStyle = this.fabricWrapperEl!.style;
previous.left = fwStyle.left;
previous.top = fwStyle.top;
fwStyle.left = (-1) * bottom + 'px';
fwStyle.top = (-1) * left + 'px';
current.left = fwStyle.left;
current.top = fwStyle.top;
// 可见区域也要一同进行变换
previous.width = this.canvasWrapper.style.width;
previous.height = this.canvasWrapper.style.height;
this.canvasWrapper.style.width = visiableHeight + 'px';
this.canvasWrapper.style.height = visiableWidth + 'px';
current.width = this.canvasWrapper.style.width;
current.height = this.canvasWrapper.style.height;
const image = canvas.backgroundImage!
const objs = canvas.getObjects() as FabricObject[] ?? [];
objs.unshift(image);
// 还要考虑自带旋转度数的对象
const oriAngle = image.angle;
for (const obj of objs) {
const { x, y } = obj.getXY();
// 旋转时是按照左上角的顶点进行旋转的
// 所以要注意到左上角的位置
let newX, newY;
// 旋转90度的时候,left变成top(y),bottom变成left(x)
if (oriAngle == 0) {
newY = x;
// 底部的长度是canvas的长度减去top,减去图片高度
const imgBottom = canvasHeight - y - obj.height;
// 按照左上角旋转过后,左上角的顶点去了右上角因此还要向右移动一个宽度的位置
newX = imgBottom + obj.height;
} else if (oriAngle == 90) {
// 旋转时,依旧是left(x)变为top(y),bottom变成left(x)
// 但是由于现在已经旋转了,定位的顶点在右上角,所以实际left值要减去一个图片的宽度
const imgLeft = x - obj.height;
// 实际的bottom值依旧是画板高度减去y值减去底图高度
const imgBottom = canvasHeight - y - obj.width;
// 但是因为旋转了180度,所以要向右平移一个宽度,向下平移一个高度
newX = imgBottom + obj.width;
newY = imgLeft + obj.height;
} else if (oriAngle == 180) {
// 同上
const imgLeft = x - obj.width;
const imgBottom = canvasHeight - y;
newX = imgBottom;
newY = imgLeft + obj.width;
} else if (oriAngle == 270) {
// 同上
const imgLeft = x;
const imgBottom = canvasHeight - y;
newX = imgBottom;
newY = imgLeft;
} else {
throw Error('不满足预期的度数' + oriAngle)
}
const angle = obj.angle;
const newAngle = (angle + 90) % 360;
obj.set('angle', newAngle);
obj.setXY(new Point(newX, newY));
previous.objs.push(obj);
previous.objAngle.push(angle);
previous.objPos.push({ x, y })
current.objs.push(obj);
current.objAngle.push(newAngle);
current.objPos.push({ x: newX, y: newY });
}
const shapes = canvas.getObjects() as FabricObject[] ?? [];
for (const shape of shapes) {
canvas.setActiveObject(shape);
shape.setCoords();
}
canvas.renderAll();
previous.canvasWrapperProps = this.calculateCanvasWrapper();
// 旋转后,整个图会回到界面的正中央
this.moveCanvasToCenter();
current.canvasWrapperProps = this.calculateCanvasWrapper();
this.imageEditor!.getHistory().recordRotateAction(previous, current);
}
calculateCanvasWrapper() {
const prop = new CanvasDetailedProps();
prop.canvasWrapper = this.canvasWrapper;
prop.canvasWrapperLeft = this.canvasWrapper.style.left;
prop.canvasWrapperTop = this.canvasWrapper.style.top;
prop.canvasWrapperHeight = this.canvasWrapper.style.height;
prop.canvasWrapperWidth = this.canvasWrapper.style.width;
prop.topResizer = this.northResizer;
prop.topResizerLeft = this.northResizer.style.left;
prop.topResizerTop = this.northResizer.style.top;
prop.leftResizer = this.westResizer;
prop.leftResizerLeft = this.westResizer.style.left;
prop.leftResizerTop = this.westResizer.style.top;
prop.bottomResizer = this.southResizer;
prop.bottomResizerLeft = this.southResizer.style.left;
prop.bottomResizerTop = this.southResizer.style.top;
prop.rightResizer = this.eastResizer;
prop.rightResizerLeft = this.eastResizer.style.left;
prop.rightResizerTop = this.eastResizer.style.top;
prop.toolbar = this.toolbar;
prop.toolbarLeft = this.toolbar.style.left;
prop.toolbarTop = this.toolbar.style.top;
prop.optionBar = this.optionBar;
prop.optionBarLeft = this.optionBar.style.left;
prop.optionBarTop = this.optionBar.style.top;
return prop;
}
// 逆时针旋转90度
rotateCounterClockwise() {
const previous = new RotateProps();
const current = new RotateProps();
previous.canvasWrapper = current.canvasWrapper = this.canvasWrapper;
previous.fabricWrapperEl = current.fabricWrapperEl = this.fabricWrapperEl!;
previous.imageEditor = current.imageEditor = this.imageEditor!;
const canvasArea = this.getCanvasAreaInfo();
const { right, top } = canvasArea;
const { canvasWidth, canvasHeight } = canvasArea;
const { visiableHeight, visiableWidth } = canvasArea;
const canvas = this.imageEditor!.getCanvas()!;
previous.canvasHeight = canvasHeight; previous.canvasWidth = canvasWidth;
// 逆时针旋转90度的时候,长高要做一个互换
this.imageEditor!.setCanvasDims(canvasHeight, canvasWidth);
current.canvasHeight = canvasWidth; current.canvasWidth = canvasHeight;
const fwStyle = this.fabricWrapperEl!.style;
previous.left = fwStyle.left;
previous.top = fwStyle.top;
fwStyle.left = (-1) * top + 'px';
fwStyle.top = (-1) * right + 'px';
current.left = fwStyle.left;
current.top = fwStyle.top;
// 可见区域也要一同进行变换
previous.width = this.canvasWrapper.style.width;
previous.height = this.canvasWrapper.style.height;
this.canvasWrapper.style.width = visiableHeight + 'px';
this.canvasWrapper.style.height = visiableWidth + 'px';
current.width = this.canvasWrapper.style.width;
current.height = this.canvasWrapper.style.height;
const image = canvas.backgroundImage!
const objs = canvas.getObjects() as FabricObject[] ?? [];
objs.unshift(image);
// 还要考虑自带旋转度数的对象
const oriAngle = image.angle;
for (const obj of objs) {
const { x, y } = obj.getXY();
// 旋转时是按照左上角的顶点进行旋转的
// 所以要注意到左上角的位置
let newX, newY;
// 直接参考顺时针
if (oriAngle == 0) {
newX = y;
const imageRight = canvasWidth - x - obj.width;
newY = imageRight + obj.width;
} else if (oriAngle == 270) {
const imgTop = y - obj.width;
const imgRight = canvasWidth - x - obj.height;
newX = imgTop + obj.width;
newY = imgRight + obj.height;
} else if (oriAngle == 180) {
const imgTop = y - obj.height;
const imgRight = canvasWidth - x;
newX = imgTop + obj.height;
newY = imgRight;
} else if (oriAngle == 90) {
const imgRight = canvasWidth - x;
const imgTop = y;
newX = imgTop;
newY = imgRight;
} else {
throw Error('不满足预期的度数' + oriAngle)
}
const angle = obj.angle;
const newAngle = (angle - 90 + 360) % 360;
obj.set('angle', newAngle);
obj.setXY(new Point(newX, newY));
previous.objs.push(obj);
previous.objAngle.push(angle);
previous.objPos.push({ x, y })
current.objs.push(obj);
current.objAngle.push(newAngle);
current.objPos.push({ x: newX, y: newY });
}
const shapes = canvas.getObjects() as FabricObject[] ?? [];
for (const shape of shapes) {
canvas.setActiveObject(shape);
shape.setCoords();
}
canvas.renderAll();
previous.canvasWrapperProps = this.calculateCanvasWrapper();
// 旋转后,整个图会回到界面的正中央
this.moveCanvasToCenter();
current.canvasWrapperProps = this.calculateCanvasWrapper();
this.imageEditor!.getHistory().recordRotateAction(previous, current);
}
moveCanvasToCenter() {
// 先旋转,旋转完看看会不会出滚动条
// 如果会出滚动条,那么就要考虑把滚动条的值算进去了
// 如果没有出现滚动条,那么就不用考虑滚动条了
const { width: parentWidth, height: parentHeight } = this.wrapper.getBoundingClientRect();
const { width: wrapperWidth, height: wrapperHeight, left: wrapperLeft, top: wrapperTop } = this.canvasWrapper.getBoundingClientRect();
let cwLeft, cwTop;
if (parentWidth <= wrapperWidth) {
cwLeft = 0;
this.canvasWrapper.style.left = String(cwLeft);
} else {
const extra = parentWidth - wrapperWidth;
cwLeft = extra / 2;
this.canvasWrapper.style.left = cwLeft + 'px';
}
if (parentHeight <= wrapperHeight) {
cwTop = 0;
this.canvasWrapper.style.top = String(cwTop);
} else {
const extra = parentHeight - wrapperHeight;
cwTop = extra / 2;
this.canvasWrapper.style.top = cwTop + 'px';
}
this.fixComponentsPosition();
}
fixComponentsPosition() {
this.fixToolbarPosition();
this.fixResizerPosition();
}
fixResizerPosition() {
const cwTop = pxielToNumber(this.canvasWrapper.style.top)
const cwLeft = pxielToNumber(this.canvasWrapper.style.left)
const wrapperWidth = pxielToNumber(this.canvasWrapper.style.width)
const wrapperHeight = pxielToNumber(this.canvasWrapper.style.height)
// 12是拉伸按钮的大小,6是拉伸大小的一半,用来做偏移用
const northResizerTop = cwTop - 12;
const northResizerLeft = cwLeft + wrapperWidth / 2 - 6;
const westResizerTop = cwTop + wrapperHeight / 2 - 6;
const westResizerLeft = cwLeft - 12;
const southResizerTop = cwTop + wrapperHeight;
const southResizerLeft = cwLeft + wrapperWidth / 2 - 6;
const eastResizerTop = cwTop + wrapperHeight / 2 - 6;
const eastResizerLeft = cwLeft + wrapperWidth;
this.northResizer.style.top = northResizerTop + 'px';
this.northResizer.style.left = northResizerLeft + 'px';
this.northWestResizer.style.top = northResizerTop + 'px';
this.northWestResizer.style.left = westResizerLeft + 'px';
this.westResizer.style.top = westResizerTop + 'px';
this.westResizer.style.left = westResizerLeft + 'px';
this.southWestResizer.style.top = southResizerTop + 'px';
this.southWestResizer.style.left = westResizerLeft + 'px';
this.southResizer.style.top = southResizerTop + 'px';
this.southResizer.style.left = southResizerLeft + 'px';
this.southEastResizer.style.top = southResizerTop + 'px';
this.southEastResizer.style.left = eastResizerLeft + 'px';
this.eastResizer.style.top = eastResizerTop + 'px';
this.eastResizer.style.left = eastResizerLeft + 'px';
this.northEastResizer.style.top = northResizerTop + 'px';
this.northEastResizer.style.left = eastResizerLeft + 'px';
}
fixToolbarPosition() {
const top = pxielToNumber(this.canvasWrapper.style.top);
const left = pxielToNumber(this.canvasWrapper.style.left);
const width = pxielToNumber(this.canvasWrapper.style.width);
const toolbarWidth = pxielToNumber(this.toolbar.style.width) + 2 * pxielToNumber(this.toolbar.style.padding);
const height = pxielToNumber(this.canvasWrapper.style.height);
// 20是一个间隔高度
this.toolbar.style.top = top + height + 20 + 'px';
this.toolbar.style.left = left + width / 2 - toolbarWidth / 2 + 'px';
const current = this.imageEditor!.getOperatorType()
const currEle = this.menuMap.get(current);
this.adjustOptionBarPosition(currEle);
if (current == OperatorType.MOSAIC) {
this.showSizeOptions();
}
}
adjustOptionBarPosition(currEle?: HTMLDivElement) {
if (currEle == null) {
return;
}
const pos = getAbsolutePosition(currEle);
const optionBar = this.optionBar;
optionBar.style.display = 'inline-block';
optionBar.style.left = Math.round(pos.x - 130) + 'px';
optionBar.style.top = Math.round(pos.y + 36) + 'px';
}
getCanvasAreaInfo() {
// 完整的canvas区域,包括可见与不可见的区域
const canvasWidth = toNumber(this.fabricWrapperEl!.style.width.replace('px', ''));
const canvasHeight = toNumber(this.fabricWrapperEl!.style.height.replace('px', ''));
// canvas在左侧和上侧隐藏的区域,值是left和top的绝对值
const left = (-1) * toNumber(this.fabricWrapperEl!.style.left.replace('px', ''));
const top = (-1) * toNumber(this.fabricWrapperEl!.style.top.replace('px', ''));
const visiableWidth = toNumber(this.canvasWrapper!.style.width.replace('px', ''));
const visiableHeight = toNumber(this.canvasWrapper!.style.height.replace('px', ''));
// canvas的宽高 减去 可见区域的长度 减去 左侧上侧的区域
// 就能得到右侧和下侧部分的长度
const right = canvasWidth - visiableWidth - left;
const bottom = canvasHeight - visiableHeight - top;
return {
canvasWidth, canvasHeight, visiableWidth, visiableHeight, top, bottom, left, right
}
}
getScreenshotCanvas() {
return this.screenshotCanvas;
}
getScreenshotResizers() {
return this.screenshotResizer;
}
getScreenshotToolbar() {
return this.screenshotToolbar;
}
getScreenshotConfirmButton() {
return this.screenshotConfirmButton;
}
getScreenshotCancelButton() {
return this.screenshotCancelButton;
}
getFabricWrapper() {
return this.fabricWrapperEl;
}
// 不能用两个canvas,两个canvas会带来闪烁的问题,直接在原来的canvas上操作
cropImage() {
this.hideToolbar();
const width = pxielToNumber(this.canvasWrapper!.style.width);
const height = pxielToNumber(this.canvasWrapper!.style.height);
const screenshot = this.imageEditor!.getScreenshoter();
const left = pxielToNumber(this.canvasWrapper!.style.left);
const top = pxielToNumber(this.canvasWrapper!.style.top);
screenshot.initMask(left, top, width, height);
}
resetWrapper(width: number, height: number) {
this.canvasWrapper.style.width = width + 'px';
this.canvasWrapper.style.height = height + 'px';
this.moveCanvasToCenter();
}
hideResizer() {
this.northResizer.style.display = 'none';
this.westResizer.style.display = 'none';
this.southResizer.style.display = 'none';
this.eastResizer.style.display = 'none';
}
showResizer() {
this.northResizer.style.display = 'block';
this.westResizer.style.display = 'block';
this.southResizer.style.display = 'block';
this.eastResizer.style.display = 'block';
}
calculateCanvasInfo() {
const info = new FabricCanvasProps();
info.fabricWrapperEl = this.fabricWrapperEl!;
info.fabricWrapperElLeft = this.fabricWrapperEl!.style.left;
info.fabricWrapperElTop = this.fabricWrapperEl!.style.top;
const canvas = this.imageEditor!.getCanvas();
info.canvasWidth = canvas.width;
info.canvasHeight = canvas.height;
info.canvasBackgroundColor = canvas.backgroundColor as string;
info.canvasBackgroundImage = canvas.backgroundImage;
info.objects = canvas.getObjects();
return info;
}
resetImageEditor() {
const canvas = this.imageEditor!.getCanvas()
const image = canvas.backgroundImage!;
const width = image.width;
const height = image.height;
this.imageEditor!.setCanvasDims(width, height);
image.setXY(new Point(0, 0));
const objects = canvas.getObjects()
for (const o of objects) {
canvas.remove(o);
}
this.fabricWrapperEl!.style.width = '0';
this.fabricWrapperEl!.style.height = '0';
this.canvasWrapper.style.width = canvas.width + 'px';
this.canvasWrapper.style.height = canvas.height + 'px';
this.canvasWrapper.style.left = this.imageEditor!.initWrapperLeft;
this.canvasWrapper.style.top = this.imageEditor!.initWrapperTop;
this.fixComponentsPosition();
this.imageEditor!.getHistory().clearRedoStack();
}
downloadAreaImage() {
const width = pxielToNumber(this.canvasWrapper.style.width);
const height = pxielToNumber(this.canvasWrapper.style.height);
const left = pxielToNumber(this.fabricWrapperEl!.style.left)
const top = pxielToNumber(this.fabricWrapperEl!.style.top)
const start = new Point(left, top);
const end = new Point(left + width, top + height);
const image = this.imageEditor!.getAreaImageInfo(start, end);
const link = document.createElement("a");
link.href = image;
link.download = 'image.png';
link.click();
}
}
\ No newline at end of file
import { Canvas, Ellipse, FabricObject, Point, TDegree } from "fabric";
import ImageEditor from "./image_editor";
interface OperationAction {
undo(): void;
redo(): void;
}
enum State {
CAN_UNDO, CAN_REDO
}
class CreateAction implements OperationAction {
protected canvas: Canvas;
protected object: FabricObject;
protected state: State = State.CAN_UNDO;
constructor(canvas: Canvas, object: FabricObject) {
this.canvas = canvas;
this.object = object;
}
undo() {
if (this.state == State.CAN_UNDO) {
this.canvas.remove(this.object);
this.canvas.renderAll();
this.state = State.CAN_REDO;
}
}
redo() {
if (this.state == State.CAN_REDO) {
this.canvas.add(this.object);
this.canvas.renderAll();
this.state = State.CAN_UNDO;
}
}
}
class RemoveAction implements OperationAction {
protected canvas: Canvas;
protected object: FabricObject;
protected state: State = State.CAN_UNDO;
constructor(canvas: Canvas, object: FabricObject) {
this.canvas = canvas;
this.object = object;
}
undo() {
if (this.state == State.CAN_UNDO) {
this.canvas.add(this.object);
this.canvas.renderAll();
this.state = State.CAN_REDO;
}
}
redo() {
if (this.state == State.CAN_REDO) {
this.canvas.remove(this.object);
this.canvas.renderAll();
this.state = State.CAN_UNDO;
}
}
}
class MoveAction implements OperationAction {
protected canvas: Canvas;
protected object: FabricObject;
protected previousX: number;
protected previousY: number;
protected currentX: number;
protected currentY: number;
constructor(canvas: Canvas, object: FabricObject, previousX: number, previousY: number) {
this.object = object;
this.previousX = previousX;
this.previousY = previousY;
this.currentX = object.getX();
this.currentY = object.getY();
this.canvas = canvas;
}
undo(): void {
this.object.setX(this.previousX);
this.object.setY(this.previousY);
this.canvas.renderAll();
}
redo(): void {
this.object.setX(this.currentX);
this.object.setY(this.currentY);
this.canvas.renderAll();
}
}
class ScaleAction implements OperationAction {
protected canvas: Canvas;
protected object: FabricObject;
protected previousWidth: number;
protected previousHeight: number;
protected previousX: number;
protected previousY: number;
protected currentWidth: number;
protected currentHeight: number;
protected currentX: number;
protected currentY: number;
constructor(canvas: Canvas, object: FabricObject, previousWidth: number, previousHeight: number, previousX: number, previousY: number) {
this.canvas = canvas;
this.object = object;
this.previousWidth = previousWidth;
this.previousHeight = previousHeight;
this.previousX = previousX;
this.previousY = previousY;
this.currentWidth = object.get('width');
this.currentHeight = object.get('height');
this.currentX = object.getX();
this.currentY = object.getY();
}
undo(): void {
this.object.set({ width: this.previousWidth, height: this.previousHeight });
this.object.setX(this.previousX);
this.object.setY(this.previousY);
this.canvas.renderAll();
}
redo(): void {
this.object.set({ width: this.currentWidth, height: this.currentHeight });
this.object.setX(this.currentX);
this.object.setY(this.currentY);
this.canvas.renderAll();
}
}
class EllipseScaleAction extends ScaleAction {
protected previousRX: number;
protected previousRY: number;
protected currentRX: number;
protected currentRY: number;
constructor(canvas: Canvas, object: Ellipse, previousWidth: number, previousHeight: number
, previousX: number, previousY: number, previousRX: number, previousRY: number) {
super(canvas, object, previousWidth, previousHeight, previousX, previousY);
this.previousRX = previousRX;
this.previousRY = previousRY;
console.log(this.previousRX)
console.log(this.previousRY)
this.currentRX = object.rx;
this.currentRY = object.ry;
}
undo(): void {
const obj = this.object as Ellipse;
obj.rx = this.previousRX;
obj.ry = this.previousRY;
super.undo();
}
redo(): void {
const obj = this.object as Ellipse;
obj.rx = this.currentRX;
obj.ry = this.currentRY;
super.redo();
}
}
class RatioScaleAction implements OperationAction {
protected canvas: Canvas;
protected object: FabricObject;
protected previousScaleX: number;
protected previousScaleY: number;
protected previousX: number;
protected previousY: number;
protected currentScaleX: number;
protected currentScaleY: number;
protected currentX: number;
protected currentY: number;
constructor(canvas: Canvas, object: FabricObject, previousScaleX: number, previousScaleY: number, previousX: number, previousY: number) {
this.canvas = canvas;
this.object = object;
this.previousScaleX = previousScaleX;
this.previousScaleY = previousScaleY;
this.previousX = previousX;
this.previousY = previousY;
this.currentScaleX = object.scaleX;
this.currentScaleY = object.scaleY;
this.currentX = object.getX();
this.currentY = object.getY();
}
undo(): void {
this.object.scaleX = this.previousScaleX;
this.object.scaleY = this.previousScaleY;
this.object.setX(this.previousX);
this.object.setY(this.previousY);
this.canvas.renderAll();
}
redo(): void {
this.object.scaleX = this.currentScaleX;
this.object.scaleY = this.currentScaleY;
this.object.setX(this.currentX);
this.object.setY(this.currentY);
this.canvas.renderAll();
}
}
export class FlipXUndoProps {
fabricWrapperEl?: HTMLDivElement;
left: string = '';
backgroundImage?: FabricObject;
flipX: boolean = false;
x: number = 0;
objs: FabricObject[] = [];
objX: number[] = [];
objFlipX: boolean[] = [];
}
class FlipXAction implements OperationAction {
private canvas: Canvas;
private previous: FlipXUndoProps;
private current: FlipXUndoProps;
private hasUndo = false;
constructor(canvas: Canvas, previous: FlipXUndoProps, current: FlipXUndoProps) {
this.canvas = canvas;
this.previous = previous;
this.current = current;
}
undo(): void {
if (this.hasUndo) {
return;
}
const previous = this.previous;
const bi = previous.backgroundImage;
const fabricWrapperEl = previous.fabricWrapperEl;
fabricWrapperEl!.style.left = previous.left;
bi!.flipX = previous.flipX;
bi!.setX(previous.x);
for (const index in previous.objs) {
const obj = previous.objs[index];
const flipX = previous.objFlipX[index];
const x = previous.objX[index];
obj.flipX = flipX;
obj.setX(x);
obj.setCoords();
}
this.canvas.renderAll();
this.hasUndo = true;
}
redo(): void {
if (!this.hasUndo) {
return;
}
const current = this.current;
const bi = current.backgroundImage;
const fabricWrapperEl = current.fabricWrapperEl;
fabricWrapperEl!.style.left = current.left;
bi!.flipX = current.flipX;
bi!.setX(current.x);
for (const index in current.objs) {
const obj = current.objs[index];
const flipX = current.objFlipX[index];
const x = current.objX[index];
obj.flipX = flipX;
obj.setX(x);
obj.setCoords();
}
this.canvas.renderAll();
this.hasUndo = false
}
}
export class FlipYUndoProps {
fabricWrapperEl?: HTMLDivElement;
top: string = '';
backgroundImage?: FabricObject;
flipY: boolean = false;
y: number = 0;
objs: FabricObject[] = [];
objY: number[] = [];
objFlipY: boolean[] = [];
}
class FlipYAction implements OperationAction {
private canvas: Canvas;
private previous: FlipYUndoProps;
private current: FlipYUndoProps;
private hasUndo = false;
constructor(canvas: Canvas, previous: FlipYUndoProps, current: FlipYUndoProps) {
this.canvas = canvas;
this.previous = previous;
this.current = current;
}
undo(): void {
if (this.hasUndo) {
return;
}
const previous = this.previous;
const bi = previous.backgroundImage;
const fabricWrapperEl = previous.fabricWrapperEl;
fabricWrapperEl!.style.top = previous.top;
bi!.flipY = previous.flipY;
bi!.setY(previous.y);
for (const index in previous.objs) {
const obj = previous.objs[index];
const flipY = previous.objFlipY[index];
const y = previous.objY[index];
obj.flipY = flipY;
obj.setY(y);
obj.setCoords();
}
this.canvas.renderAll();
this.hasUndo = true;
}
redo(): void {
if (!this.hasUndo) {
return;
}
const current = this.current;
const bi = current.backgroundImage;
const fabricWrapperEl = current.fabricWrapperEl;
fabricWrapperEl!.style.top = current.top;
bi!.flipY = current.flipY;
bi!.setY(current.y);
for (const index in current.objs) {
const obj = current.objs[index];
const flipY = current.objFlipY[index];
const y = current.objY[index];
obj.flipY = flipY;
obj.setY(y);
obj.setCoords();
}
this.canvas.renderAll();
this.hasUndo = false
}
}
export class CanvasWrapperProps {
canvasWrapper?: HTMLDivElement;
canvasWrapperLeft = '';
canvasWrapperTop = '';
topResizer?: HTMLDivElement;
topResizerLeft = '';
topResizerTop = '';
leftResizer?: HTMLDivElement;
leftResizerLeft = '';
leftResizerTop = '';
bottomResizer?: HTMLDivElement;
bottomResizerTop = '';
bottomResizerLeft = '';
rightResizer?: HTMLDivElement;
rightResizerTop = '';
rightResizerLeft = '';
toolbar?: HTMLDivElement;
toolbarLeft = '';
toolbarTop = '';
optionBar?: HTMLDivElement;
optionBarLeft = '';
optionBarTop = '';
}
export class CanvasDetailedProps extends CanvasWrapperProps {
canvasWrapperHeight = ''
canvasWrapperWidth = '';
}
export class FabricCanvasProps {
fabricWrapperEl?: HTMLDivElement;
fabricWrapperElLeft = '';
fabricWrapperElTop = '';
canvasWidth = 0;
canvasHeight = 0;
canvasBackgroundColor = '';
canvasBackgroundImage?: FabricObject
objects = [] as FabricObject[]
}
export class RotateProps {
fabricWrapperEl?: HTMLDivElement;
canvasWrapper?: HTMLDivElement;
imageEditor?: ImageEditor;
left: string = '';
top: string = '';
canvasHeight: number = 0;
canvasWidth: number = 0;
height: string = '';
width: string = '';
objs: FabricObject[] = [];
objPos: any[] = [];
objAngle: TDegree[] = [];
canvasWrapperProps?: CanvasWrapperProps;
}
abstract class CanvasSetAction implements OperationAction {
undo(): void {
}
redo(): void {
}
resetCanvasWrapper(prop: CanvasWrapperProps) {
prop.canvasWrapper!.style.top = prop.canvasWrapperTop;
prop.canvasWrapper!.style.left = prop.canvasWrapperLeft;
prop.topResizer!.style.top = prop.topResizerTop;
prop.topResizer!.style.left = prop.topResizerLeft;
prop.leftResizer!.style.top = prop.leftResizerTop;
prop.leftResizer!.style.left = prop.leftResizerLeft;
prop.bottomResizer!.style.top = prop.bottomResizerTop;
prop.bottomResizer!.style.left = prop.bottomResizerLeft;
prop.rightResizer!.style.top = prop.rightResizerTop;
prop.rightResizer!.style.left = prop.rightResizerLeft;
prop.toolbar!.style.top = prop.toolbarTop;
prop.toolbar!.style.left = prop.toolbarLeft;
prop.optionBar!.style.top = prop.optionBarTop;
prop.optionBar!.style.left = prop.optionBarLeft;
}
}
class RotationAction extends CanvasSetAction {
private previous: RotateProps;
private current: RotateProps;
private canvas: Canvas;
private hasUndo = false;
constructor(canvas: Canvas, previous: RotateProps, current: RotateProps) {
super();
this.canvas = canvas;
this.current = current;
this.previous = previous;
}
undo(): void {
if (this.hasUndo) {
return;
}
const previous = this.previous;
previous.imageEditor!.setCanvasDims(previous.canvasWidth, previous.canvasHeight);
const fwStyle = previous.fabricWrapperEl!.style;
fwStyle.left = previous.left;
fwStyle.top = previous.top;
const canvasWrapper = previous.canvasWrapper!;
canvasWrapper.style.width = previous.width;
canvasWrapper.style.height = previous.height;
for (const index in previous.objs) {
const obj = previous.objs[index];
const xy = previous.objPos[index];
const angle = previous.objAngle[index];
obj.set('angle', angle)
obj.setXY(new Point(xy.x, xy.y));
obj.setCoords();
}
this.canvas.renderAll();
this.resetCanvasWrapper(previous.canvasWrapperProps!);
this.hasUndo = true;
}
redo(): void {
if (!this.hasUndo) {
return;
}
const current = this.current;
current.imageEditor!.setCanvasDims(current.canvasWidth, current.canvasHeight);
const fwStyle = current.fabricWrapperEl!.style;
fwStyle.left = current.left;
fwStyle.top = current.top;
const canvasWrapper = current.canvasWrapper!;
canvasWrapper.style.width = current.width;
canvasWrapper.style.height = current.height;
for (const index in current.objs) {
const obj = current.objs[index];
const xy = current.objPos[index];
const angle = current.objAngle[index];
obj.set('angle', angle)
obj.setXY(new Point(xy.x, xy.y));
obj.setCoords();
}
this.canvas.renderAll();
this.resetCanvasWrapper(current.canvasWrapperProps!);
this.hasUndo = false;
}
}
class CropAction extends CanvasSetAction {
private previousWrapper: CanvasDetailedProps;
private previousCanvas: FabricCanvasProps;
private cropWrapper: CanvasDetailedProps;
private cropCanvas: FabricCanvasProps;
private canvas: Canvas;
private hasUndo = false;
constructor(canvas: Canvas, previousWrapper: CanvasDetailedProps, previousCanvas: FabricCanvasProps
, cropWrapper: CanvasDetailedProps, cropCanvas: FabricCanvasProps
) {
super();
this.canvas = canvas;
this.previousWrapper = previousWrapper;
this.previousCanvas = previousCanvas;
this.cropWrapper = cropWrapper;
this.cropCanvas = cropCanvas;
}
undo(): void {
if (this.hasUndo) {
return;
}
this.clearCanvas();
super.resetCanvasWrapper(this.previousWrapper);
const pw = this.previousWrapper;
pw.canvasWrapper!.style.width = pw.canvasWrapperWidth;
pw.canvasWrapper!.style.height = pw.canvasWrapperHeight;
const pc = this.previousCanvas;
pc.fabricWrapperEl!.style.left = pc.fabricWrapperElLeft;
pc.fabricWrapperEl!.style.top = pc.fabricWrapperElTop;
this.canvas.setDimensions({ width: pc.canvasWidth, height: pc.canvasHeight });
this.canvas.backgroundColor = pc.canvasBackgroundColor;
this.canvas.backgroundImage = pc.canvasBackgroundImage!;
for (const object of pc.objects) {
this.canvas.add(object);
object.setCoords();
}
this.canvas.renderAll();
this.hasUndo = true;
}
redo(): void {
if (!this.hasUndo) {
return;
}
this.clearCanvas();
super.resetCanvasWrapper(this.cropWrapper);
const cw = this.cropWrapper;
cw.canvasWrapper!.style.width = cw.canvasWrapperWidth;
cw.canvasWrapper!.style.height = cw.canvasWrapperHeight;
const cc = this.cropCanvas;
cc.fabricWrapperEl!.style.left = cc.fabricWrapperElLeft;
cc.fabricWrapperEl!.style.top = cc.fabricWrapperElTop;
this.canvas.setDimensions({ width: cc.canvasWidth, height: cc.canvasHeight });
this.canvas.backgroundColor = cc.canvasBackgroundColor;
this.canvas.backgroundImage = cc.canvasBackgroundImage!;
for (const object of cc.objects) {
this.canvas.add(object);
object.setCoords();
}
this.canvas.renderAll();
this.hasUndo = false;
}
clearCanvas() {
// 不太清楚fabric会不会destory这些对象,所以防止万一起见,还是先删除了
const canvas = this.canvas;
canvas.backgroundImage = undefined;
const objects = canvas.getObjects();
for (const obj of objects) {
canvas.remove(obj);
}
this.canvas.clear();
}
}
export default class OperationHistory {
protected undoStack: OperationAction[];
protected redoStack: OperationAction[];
protected canvas: Canvas;
constructor(canvas: Canvas) {
this.undoStack = [];
this.redoStack = [];
this.canvas = canvas;
}
getCanvas() {
return this.canvas;
}
redo(): boolean {
if (this.redoStack.length > 0) {
const opr = this.redoStack.pop()!;
opr.redo();
this.undoStack.push(opr!);
return true;
}
return false;
}
undo(): boolean {
if (this.undoStack.length > 0) {
const opr = this.undoStack.pop()!;
opr.undo();
this.redoStack.push(opr);
return true;
}
return false;
}
recordCreateAction(object: FabricObject) {
this.undoStack.push(new CreateAction(this.canvas, object));
this.clearRedoStack();
}
recordRemoveAction(object: FabricObject) {
this.undoStack.push(new RemoveAction(this.canvas, object));
this.clearRedoStack();
}
recordMoveAction(object: FabricObject, previousX: number, previousY: number) {
this.undoStack.push(new MoveAction(this.canvas, object, previousX, previousY));
this.clearRedoStack();
}
// 放缩可能会改变图形的位置
recordScaleAction(object: FabricObject, previousWidth: number, previousHeight: number,
previousX: number, previousY: number) {
this.undoStack.push(new ScaleAction(this.canvas, object, previousWidth, previousHeight, previousX, previousY));
this.clearRedoStack();
}
recordEllipseScaleAction(object: Ellipse, previousWidth: number, previousHeight: number,
previousX: number, previousY: number, previousRX: number, previousRY: number) {
this.undoStack.push(new EllipseScaleAction(this.canvas, object, previousWidth, previousHeight, previousX, previousY, previousRX, previousRY));
this.clearRedoStack();
}
recordRatioScaleAction(object: FabricObject, previousScaleX: number, previousScaleY: number,
previousX: number, previousY: number) {
this.undoStack.push(new RatioScaleAction(this.canvas, object, previousScaleX, previousScaleY,
previousX, previousY));
this.clearRedoStack();
}
recordFlipXAction(previous: FlipXUndoProps, current: FlipXUndoProps) {
this.undoStack.push(new FlipXAction(this.canvas, previous, current));
this.clearRedoStack();
}
recordFlipYAction(previous: FlipYUndoProps, current: FlipYUndoProps) {
this.undoStack.push(new FlipYAction(this.canvas, previous, current));
this.clearRedoStack();
}
recordRotateAction(previous: RotateProps, current: RotateProps) {
this.undoStack.push(new RotationAction(this.canvas, previous, current));
this.clearRedoStack();
}
recordCropAction(wrapper: CanvasDetailedProps, canvas: FabricCanvasProps, cropWrapper: CanvasDetailedProps, cropCanvas: FabricCanvasProps) {
this.undoStack.push(new CropAction(this.canvas, wrapper, canvas, cropWrapper, cropCanvas));
this.clearRedoStack();
}
clearRedoStack() {
this.redoStack = [];
}
clearStack(){
this.redoStack = []
this.undoStack = []
}
}
\ No newline at end of file
import { Canvas, FabricImage, FabricObject, Point, StaticCanvas } from "fabric";
import ArrowOperator from "./operator/arrow_operator";
import DrawOperator from "./operator/draw_operator";
import EllipseOperator from "./operator/ellipse_operator";
import { OperatorType } from "./image_editor_operator";
import ElementManager from "./element_manager";
import MosaicOperator from "./operator/mosaic_operator";
import RectangleOperator from "./operator/rect_operator";
import TextOperator from "./operator/text_operator";
import OperationHistory from "./history";
import { Screenshoter } from "./screenshoter";
import { ImageEditorShortcutManager } from "./shortcut_manager";
export default class ImageEditor {
private canvas: Canvas;
private screenshoter: Screenshoter;
private history: OperationHistory;
private operatorType: OperatorType = OperatorType.NONE;
private elementManager: ElementManager;
private rectOperator: RectangleOperator;
private ellipseOperator: EllipseOperator;
private arrowOperator: ArrowOperator;
private drawOperator: DrawOperator;
private mosaicOperator: MosaicOperator;
private textOperator: TextOperator;
private operatorMap = new Map();
readonly initWrapperLeft: string;
readonly initWrapperTop: string;
private shortcutManager: ImageEditorShortcutManager;
constructor(canvas: Canvas, elementManager: ElementManager) {
this.elementManager = elementManager;
this.canvas = canvas;
this.history = new OperationHistory(canvas);
this.rectOperator = new RectangleOperator(this);
this.ellipseOperator = new EllipseOperator(this);
this.arrowOperator = new ArrowOperator(this);
this.drawOperator = new DrawOperator(this);
this.mosaicOperator = new MosaicOperator(this);
this.textOperator = new TextOperator(this);
this.bindOperators();
this.operatorMap.set(OperatorType.RECT, this.rectOperator);
this.operatorMap.set(OperatorType.ELLIPSE, this.ellipseOperator);
this.operatorMap.set(OperatorType.ARROW, this.arrowOperator);
this.operatorMap.set(OperatorType.TEXT, this.textOperator);
this.operatorMap.set(OperatorType.DRAW, this.drawOperator);
this.operatorMap.set(OperatorType.MOSAIC, this.mosaicOperator);
this.canvas.selection = false;
this.screenshoter = new Screenshoter();
const canvasWrapper = elementManager.canvasWrapper;
this.initWrapperLeft = canvasWrapper.style.left;
this.initWrapperTop = canvasWrapper.style.top;
this.shortcutManager = new ImageEditorShortcutManager(this);
}
init() {
this.elementManager.init(this);
this.elementManager.bindEvents();
this.screenshoter.init(this, this.elementManager);
}
bindOperators() {
const rectOperator = this.rectOperator;
this.canvas.on('mouse:down', rectOperator.handleMouseDown.bind(rectOperator));
this.canvas.on('mouse:move', rectOperator.handleMouseMove.bind(rectOperator));
this.canvas.on('mouse:up', rectOperator.handleMouseUp.bind(rectOperator));
const ellipseOperator = this.ellipseOperator;
this.canvas.on('mouse:down', ellipseOperator.handleMouseDown.bind(ellipseOperator));
this.canvas.on('mouse:move', ellipseOperator.handleMouseMove.bind(ellipseOperator));
this.canvas.on('mouse:up', ellipseOperator.handleMouseUp.bind(ellipseOperator));
const arrowOperator = this.arrowOperator;
this.canvas.on('mouse:down', arrowOperator.handleMouseDown.bind(arrowOperator));
this.canvas.on('mouse:move', arrowOperator.handleMouseMove.bind(arrowOperator));
this.canvas.on('mouse:up', arrowOperator.handleMouseUp.bind(arrowOperator));
const textOperator = this.textOperator;
this.canvas.on('mouse:down:before', textOperator.handleMouseDownBefore.bind(textOperator))
this.canvas.on('mouse:down', textOperator.handleMouseDown.bind(textOperator));
this.canvas.on('mouse:up', textOperator.handleMouseUp.bind(textOperator));
}
getCanvas() {
return this.canvas;
}
getActiveOperator() {
return this.operatorMap.get(this.operatorType);
}
getOperatorType() {
return this.operatorType;
}
changeOperatorType(type: OperatorType) {
// 如果要修改的type和当前的是一样的话,那么就不变
if (this.operatorType == type) {
return;
}
const previous = this.operatorType;
const current = type;
switch (previous) {
case OperatorType.MOSAIC: this.mosaicOperator.endMosaicMode(); break
case OperatorType.DRAW: this.drawOperator.endDrawMode(); break;
}
switch (current) {
case OperatorType.MOSAIC: this.mosaicOperator.startMosaicMode(); break
case OperatorType.DRAW: this.drawOperator.startDrawMode(); break;
}
this.operatorType = current;
if (current == OperatorType.NONE) {
this.canvas.getObjects().forEach((obj: FabricObject) => {
// 重新调整完后,要将对象激活一下,这或许是个坑?
this.canvas.setActiveObject(obj);
})
}
}
getHistory(): OperationHistory {
return this.history;
}
setCanvasHeight(height: number) {
this.canvas.setDimensions({ height })
}
setCanvasWidth(width: number) {
this.canvas.setDimensions({ width })
}
setCanvasDims(width: number, height: number) {
this.canvas.setDimensions({ height, width });
}
transformX(fabricLeft: number) {
const x = this.canvas.backgroundImage!.getX()
this.canvas.backgroundImage?.setX(x + fabricLeft)
let objs = this.canvas.getObjects();
if (objs == null) {
objs = [];
}
for (const obj of objs) {
obj.left += fabricLeft;
obj.setCoords();
}
this.canvas.renderAll();
}
transformY(fabricTop: number) {
const y = this.canvas.backgroundImage!.getY()
this.canvas.backgroundImage?.setY(y + fabricTop)
let objs = this.canvas.getObjects();
if (objs == null) {
objs = [];
}
for (const obj of objs) {
obj.top += fabricTop;
obj.setCoords();
}
this.canvas.renderAll();
}
getAreaImageInfo(start: Point, bottom: Point) {
const width = bottom.x - start.x;
const height = bottom.y - start.y;
const tempCanvas = new StaticCanvas(undefined, { width, height });
tempCanvas.add(new FabricImage(this.canvas.lowerCanvasEl, {
left: 0,
top: 0,
}))
const image = tempCanvas.toDataURL({
format: 'png',
left: start.x,
top: start.y,
width, height,
multiplier: 1
});
return image;
}
async renderToCanvas(imageDataUrl: string) {
const canvas = this.canvas;
const objects = canvas.getObjects();
for (const object of objects) {
canvas.remove(object);
}
canvas.backgroundImage = undefined;
canvas.clear();
const elementManger = this.elementManager;
let ret;
await FabricImage.fromURL(imageDataUrl).then(img => {
ret = img;
const width = img.width;
const height = img.height;
canvas.setDimensions({ width, height })
img.setX(0);
img.setY(0);
canvas.backgroundImage = img;
canvas.backgroundColor = '#FFF';
const style = elementManger.getFabricWrapper()!.style;
style.left = '0px';
style.top = '0px';
elementManger.resetWrapper(width, height);
canvas.renderAll();
})
return ret;
}
getScreenshoter() {
return this.screenshoter;
}
// 保存状态,后面还原直接用
storeCanvasState() {
const wrapperInfo = this.elementManager.calculateCanvasWrapper();
const canvasInfo = this.elementManager.calculateCanvasInfo();
return {
wrapper: wrapperInfo,
canvas: canvasInfo
}
}
destory() {
this.shortcutManager.destroy();
}
removeActiveObjects() {
const active = this.canvas!.getActiveObject();
if (active) {
this.canvas.remove(active);
this.history.recordRemoveAction(active);
}
}
}
\ No newline at end of file
export interface ImageEditorOperator {
handleMouseDownBefore?(event: any): void;
handleMouseDown?(event: any): void;
handleMouseMove?(event: any): void;
handleMouseUp?(event: any): void;
}
export interface OperatorProps {
setOperatorSize(width: number): void;
setOperatorColor(color: string): void;
getOperatorSize(): number;
getOperatorColor(): string;
}
export const DEFAULT_COLOR = '#FF0000';
export const DEFAULT_STROKE_WIDTH = 4;
export enum OperatorType {
RECT, ELLIPSE, ARROW, DRAW, TEXT, MOSAIC, NONE
}
import { Canvas, FabricImage } from 'fabric';
import ElementManager from './element_manager';
import ImageEditor from './image_editor';
class ImageEditorHelper {
static currentImageEditor: ImageEditor | undefined;
static dpr = window.devicePixelRatio || 1;
static CANVAS_DEFAULT_WIDTH = 100;
static CANVAS_DEFAULT_HEIGHT = 100;
static createImageEditor(imageUrl: string) {
const elements = this.createElement()
const eleManager = new ElementManager(elements);
const resizer = (canvas: Canvas, width: number, height: number) => {
this.resizeCanvas(canvas, eleManager, width, height);
}
const canvas = this.initCanvas(elements.canvas, imageUrl, resizer);
const editor = new ImageEditor(canvas, eleManager);
editor.init();
return editor;
}
private static createElement(): Record<string, any> {
const width = this.CANVAS_DEFAULT_WIDTH, height = this.CANVAS_DEFAULT_HEIGHT;
const wrapper = document.getElementById("app")!;
wrapper.style.width = '100%';
wrapper.style.height = '100%';
wrapper.style.position = 'absolute';
wrapper.style.visibility = 'hidden';
document.body.appendChild(wrapper);
const toolbar = document.createElement("div");
const toolbarMenu = this.initToolbar(toolbar);
// 不考虑滚动条的事,出现滚动条的话,就给点偏差
// canvasWrapper,包裹画板
const canvasWrapper = document.createElement("div");
canvasWrapper.style.backgroundColor = 'white';
canvasWrapper.style.position = 'relative';
canvasWrapper.style.overflow = 'hidden';
const resizers = this.createCanvasResizer(wrapper);
// 添加一张用于截图的canvas,以及8个resizer
const screenshotCanvas = document.createElement("canvas")
screenshotCanvas.style.display = 'none';
screenshotCanvas.style.left = '0';
screenshotCanvas.style.top = '0';
screenshotCanvas.style.position = 'absolute';
// 通过wrapper的拉伸,应该是可以的拉伸底图,底图是白色的
// 拉伸的过程中看不出来,等拉伸完统一结算
// 拉伸下右不需要考虑太多,拉伸上左要让图片进行偏移
// 给的默认值,不需要考虑太多
const canvas = document.createElement("canvas")
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
canvas.width = width;
canvas.height = height;
canvasWrapper.append(canvas);
wrapper.appendChild(canvasWrapper)
wrapper.appendChild(toolbar)
wrapper.appendChild(screenshotCanvas);
document.body.appendChild(wrapper);
const screenshotResizer = this.createScreenshotResizers(wrapper);
const screenshotToolbar = this.createScreenshotToolbar(wrapper);
const rets = {
...toolbarMenu, canvas, canvasWrapper, ...resizers, toolbar, wrapper, screenshotCanvas, screenshotResizer, screenshotToolbar
} as any;
return rets;
}
static createScreenshotToolbar(parent: HTMLElement) {
const toolbar = document.createElement("div");
const style = toolbar.style;
style.position = 'absolute';
style.backgroundColor = 'rgb(229,230,231)'
style.borderRadius = '4px 4px 4px 4px';
style.height = '24px';
style.width = '64px';
style.paddingTop = '4px';
style.paddingBottom = '4px';
style.display = 'none';
const cancelScreenshot = this.appendMenu(toolbar, './assets/cancel.svg', 8, 0);
const confirmScreenshot = this.appendMenu(toolbar, './assets/confirm.svg', 0, 0);
parent.appendChild(toolbar);
return {
toolbar,
screenshot: {
confirm: confirmScreenshot,
cancel: cancelScreenshot
}
};
}
static createScreenshotResizers(parent: HTMLElement) {
const createResizer = () => {
const resizer = document.createElement("div");
const style = resizer.style;
style.left = '0';
style.top = '0';
style.position = 'absolute';
style.width = '8px';
style.height = '8px';
style.boxSizing = 'border-box';
style.border = '1px solid #19a918'
style.transform = 'translate(-50%,-50%)';
style.display = 'none';
resizer.addEventListener('dragstart', function (event) {
event.preventDefault();
})
resizer.draggable = false;
// style.display = 'none'
parent.appendChild(resizer);
return resizer;
}
const northWest = createResizer();
const north = createResizer();
const northEast = createResizer();
const east = createResizer();
const southEast = createResizer();
const south = createResizer();
const southWest = createResizer();
const west = createResizer();
return {
northWest, north, northEast, east, southEast, south, southWest, west
}
}
// topbar和bottombar都要做固定width,居中
// 不要考虑其它的,尾部也是一样的
private static initToolbar(toolbar: HTMLElement): Record<string, HTMLElement> {
toolbar.style.padding = "8px";
toolbar.style.backgroundColor = "#e5e6e7";
toolbar.style.borderRadius = "4px 4px 4px 4px";
toolbar.style.height = "24px";
toolbar.style.width = "690px";
toolbar.style.position = "absolute";
const ret = {} as any;
ret.rectangleMenu = this.appendMenu(toolbar, './assets/rect.svg');
ret.ellipseMenu = this.appendMenu(toolbar, './assets/circle.svg');
ret.arrowMenu = this.appendMenu(toolbar, './assets/arrow.svg');
ret.drawMenu = this.appendMenu(toolbar, './assets/draw.svg');
ret.textMenu = this.appendMenu(toolbar, './assets/text.svg');
ret.mosaicMenu = this.appendMenu(toolbar, './assets/mosaic.svg');
ret.shrinkMenu = this.appendMenu(toolbar, './assets/shrink.svg', 42);
ret.extendMenu = this.appendMenu(toolbar, './assets/extend.svg');
ret.flipXMenu = this.appendMenu(toolbar, './assets/flipX.svg');
ret.flipYMenu = this.appendMenu(toolbar, './assets/flipY.svg');
ret.rotateCounterClockwiseMenu = this.appendMenu(toolbar, './assets/rotate.svg');
ret.rotateCounterClockwiseMenu.style.transform = 'rotateY(180deg)';
ret.rotateClockwiseMenu = this.appendMenu(toolbar, './assets/rotate.svg');
ret.cropMenu = this.appendMenu(toolbar, './assets/crop.svg');
ret.undoMenu = this.appendMenu(toolbar, './assets/undo.svg', 38);
ret.redoMenu = this.appendMenu(toolbar, './assets/redo.svg');
ret.resetMenu = this.appendMenu(toolbar, './assets/reset.svg');
ret.cancaleMenu = this.appendMenu(toolbar, './assets/cancel.svg', 36);
ret.confirmMenu = this.appendMenu(toolbar, './assets/confirm.svg', 0, 0);
return ret;
}
private static appendMenu(topbar: HTMLElement, url: string, marginLeft = 0, marginRight = 8): HTMLElement {
const menu = document.createElement("div")
menu.style.display = "inline-block";
menu.style.width = "24px";
menu.style.height = "24px";
menu.style.marginRight = marginRight + 'px';
menu.style.borderRadius = "4px"
menu.style.lineHeight = "1";
if (marginLeft != 0) {
menu.style.marginLeft = marginLeft + "px"
}
const icon = document.createElement("i");
icon.style.display = "block";
icon.style.width = "24px";
icon.style.height = "24px";
icon.style.backgroundSize = "100% 100%";
icon.style.backgroundRepeat = "no-repeat";
icon.style.cursor = "pointer";
icon.style.opacity = "0.8";
icon.style.backgroundImage = `url('${url}')`
menu.appendChild(icon);
topbar.appendChild(menu);
return menu;
}
private static initCanvas(dom: HTMLCanvasElement, imageUrl: string, resizer: (canvas: Canvas, width: number, height: number) => void): fabric.Canvas {
// 随便给个默认值,后面初始化的时候改掉
const canvas = new Canvas(dom, {
width: this.CANVAS_DEFAULT_WIDTH, height: this.CANVAS_DEFAULT_HEIGHT
})
FabricImage.fromURL(imageUrl).then(img => {
// 使用setX和setY
img.setX(0);
img.setY(0);
canvas.backgroundImage = img;
canvas.backgroundColor = '#FFF';
// 设置完需要渲染一下
canvas.renderAll();
const width = img.width, height = img.height;
resizer(canvas, width, height);
})
return canvas;
}
static resizeCanvas(fbCanvas: Canvas, manager: ElementManager, width: number, height: number) {
const dpr = this.dpr;
const wrapper = manager.wrapper;
const canvasWrapper = manager.canvasWrapper;
const canvas = manager.canvas;
canvasWrapper.style.width = width + 'px';
canvasWrapper.style.height = height + 'px';
// top和left都要好好计算一下
const rect = wrapper.getBoundingClientRect();
const wrapperWidth = rect.width;
const wrapperHeight = rect.height;
let leftOffset = (wrapperWidth - width) / 2;
if (leftOffset <= 20) {
leftOffset = 20;
}
let topOffset = (wrapperHeight - height) / 2;
if (topOffset <= 20) {
topOffset = 20;
}
canvasWrapper.style.left = leftOffset + 'px';
canvasWrapper.style.top = topOffset + 'px';
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
canvas.width = Math.round(width * dpr);
canvas.height = Math.round(height * dpr);
fbCanvas.setDimensions({ width, height })
manager.fixComponentsPosition();
wrapper.style.visibility = 'visible';
}
private static createCanvasResizer(wrapper: HTMLElement) {
const squareSize = 12;
const northResizer = document.createElement('div');
const northWestResizer = document.createElement('div');
const westResizer = document.createElement('div');
const southWestResizer = document.createElement('div');
const southResizer = document.createElement('div');
const southEastResizer = document.createElement('div');
const eastResizer = document.createElement('div');
const northEastResizer = document.createElement('div');
function format(ele: HTMLDivElement) {
ele.style.width = squareSize + 'px';
ele.style.height = squareSize + 'px';
ele.style.backgroundColor = 'white';
ele.style.position = 'absolute'
ele.style.border = 'solid 1px #000';
ele.style.boxSizing = 'border-box'
ele.draggable = false;
ele.addEventListener('dragstart', function (event) {
event.preventDefault();
})
}
format(northResizer);
format(northWestResizer);
format(westResizer);
format(southWestResizer);
format(southResizer);;
format(southEastResizer);
format(eastResizer);
format(northEastResizer);
wrapper.appendChild(northResizer);
wrapper.appendChild(northWestResizer);
wrapper.appendChild(westResizer);
wrapper.appendChild(southWestResizer);
wrapper.appendChild(southResizer);
wrapper.appendChild(southEastResizer);
wrapper.appendChild(eastResizer);
wrapper.appendChild(northEastResizer);
return {
northResizer, northWestResizer, westResizer, southWestResizer,
southResizer, southEastResizer, eastResizer, northEastResizer
}
}
}
ImageEditorHelper.currentImageEditor = ImageEditorHelper.createImageEditor('/basic.jpg');
\ No newline at end of file
import { Canvas } from "fabric";
import ImageEditor from "../image_editor";
import { DEFAULT_COLOR, DEFAULT_STROKE_WIDTH, ImageEditorOperator, OperatorProps, OperatorType } from "../image_editor_operator";
import Arrow from "./fabric_arrow";
import FabricObjectChangeHelper from "./move_helper";
export default class ArrowOperator implements ImageEditorOperator, OperatorProps {
private imageEditor: ImageEditor;
private canvas: Canvas;
private start: boolean;
private current: Arrow | undefined = undefined;
private startX: number;
private startY: number;
private color: string = DEFAULT_COLOR;
private strokeWidth: number = DEFAULT_STROKE_WIDTH;
constructor(imageEditor: ImageEditor) {
this.imageEditor = imageEditor;
this.canvas = imageEditor.getCanvas();
this.start = false;
this.startX = 0;
this.startY = 0;
}
getOperatorSize(): number {
return this.strokeWidth;
}
getOperatorColor(): string {
return this.color;
}
setOperatorSize(width: number): void {
this.strokeWidth = width;
}
setOperatorColor(color: string): void {
this.color = color;
}
handleMouseDown(event: any): void {
let refuse = this.canvas.getActiveObject() != undefined || this.start;
refuse = refuse || this.imageEditor.getOperatorType() != OperatorType.ARROW;
if (refuse) {
return
}
this.start = true;
const canvas = this.canvas;
canvas.requestRenderAll();
let point = canvas.getScenePoint(event.e);
const points: [number, number, number, number] = [point.x, point.y, point.x, point.y];
this.startX = point.x;
this.startY = point.y;
const arrow = new Arrow(points, {
strokeWidth: this.strokeWidth,
stroke: this.color,
lockScalingFlip: true
})
this.current = arrow;
canvas.add(arrow);
}
handleMouseMove(event: any): void {
if (!this.start) {
return;
}
const pointer = this.canvas.getScenePoint(event.e);
this.current?.set({
x2: pointer.x,
y2: pointer.y
})
this.canvas.renderAll();
}
handleMouseUp(event: any): void {
if (this.imageEditor.getOperatorType() != OperatorType.ARROW || !this.start) {
return;
}
this.start = false;
const canvas = this.canvas;
const pointer = canvas.getScenePoint(event.e);
const notMeetMin = Math.abs(pointer.x - this.startX) < 8 && Math.abs(pointer.y - this.startY) < 8;
if (notMeetMin && this.current) {
this.canvas.remove(this.current);
} else {
const lastXY = this.current?.getXY();
const lastSize = {
width: this.current!.width,
height: this.current!.height
}
this.current!.set('lastXY', lastXY);
this.current!.set('lastDim', lastSize);
FabricObjectChangeHelper.listenMove(this.current!, this.imageEditor.getHistory());
FabricObjectChangeHelper.listenScale(this.current!, this.imageEditor.getHistory());
this.imageEditor.getHistory().recordCreateAction(this.current!);
}
}
}
\ No newline at end of file
import { BaseBrush, Canvas, PencilBrush, Shadow } from "fabric";
import OperationHistory from "../history";
import ImageEditor from "../image_editor";
import { DEFAULT_COLOR, DEFAULT_STROKE_WIDTH, OperatorProps } from "../image_editor_operator";
import FabricObjectChangeHelper from "./move_helper";
export default class DrawOperator implements OperatorProps {
private imageEditor: ImageEditor;
private canvas: Canvas;
private history: OperationHistory;
private color: string = DEFAULT_COLOR;
private strokeWidth: number = DEFAULT_STROKE_WIDTH;
private recorder: (event: any) => void;
private brush: BaseBrush | undefined;
constructor(imageEditor: ImageEditor) {
this.imageEditor = imageEditor;
this.canvas = imageEditor.getCanvas();
this.history = imageEditor.getHistory();
this.recorder = this.recordPathCreate.bind(this);
}
getOperatorSize(): number {
return this.strokeWidth;
}
getOperatorColor(): string {
return this.color;
}
setOperatorSize(width: number): void {
this.strokeWidth = width;
this.brush!.width = width;
}
setOperatorColor(color: string): void {
this.color = color;
this.brush!.color = color;
}
recordPathCreate(event: any) {
const path = event.path;
path.hoverCursor = 'default';
path.lockScalingFlip = true;
this.canvas.renderAll();
const lastXY = path.getXY();
const lastScale = {
x: path.scaleX,
y: path.scaleY
}
path.set('lastXY', lastXY);
path.set('lastScale', lastScale);
FabricObjectChangeHelper.listenMove(path, this.imageEditor.getHistory());
FabricObjectChangeHelper.listenRatioScale(path, this.imageEditor.getHistory());
this.history.recordCreateAction(path);
}
startDrawMode(): void {
const canvas = this.canvas;
canvas.isDrawingMode = true;
canvas.freeDrawingBrush = new PencilBrush(canvas);
this.brush = canvas.freeDrawingBrush;
let brush = canvas.freeDrawingBrush;
brush.color = this.color;
brush.width = this.strokeWidth;
brush.shadow = new Shadow({ blur: 2, offsetX: 0, offsetY: 0, color: '#333' })
this.canvas.on('path:created', this.recorder);
}
endDrawMode(): void {
this.canvas.isDrawingMode = false;
this.canvas.off('path:created', this.recorder);
}
}
\ No newline at end of file
import { Canvas, Ellipse } from "fabric";
import ImageEditor from "../image_editor";
import { DEFAULT_COLOR, DEFAULT_STROKE_WIDTH, ImageEditorOperator, OperatorProps, OperatorType } from "../image_editor_operator";
import FabricObjectChangeHelper from "./move_helper";
export default class EllipseOperator implements ImageEditorOperator, OperatorProps {
private imageEditor: ImageEditor;
private canvas: Canvas;
private start: boolean;
private current: Ellipse | undefined = undefined;
private startX: number;
private startY: number;
private strokeWidth: number = DEFAULT_STROKE_WIDTH;
private color: string = DEFAULT_COLOR;
constructor(imageEditor: ImageEditor) {
this.imageEditor = imageEditor;
this.canvas = imageEditor.getCanvas();
this.start = false;
this.startX = 0;
this.startY = 0;
}
getOperatorSize(): number {
return this.strokeWidth;
}
getOperatorColor(): string {
return this.color;
}
setOperatorSize(width: number): void {
this.strokeWidth = width;
}
setOperatorColor(color: string): void {
this.color = color;
}
handleMouseDown(event: any): void {
const canvas = this.canvas;
if (canvas.getActiveObject() != undefined) {
return;
}
if (this.imageEditor.getOperatorType() != OperatorType.ELLIPSE) {
return;
}
if (this.start) {
return;
}
this.start = true;
let pointer = canvas.getScenePoint(event.e);
this.startX = pointer.x;
this.startY = pointer.y;
this.current = new Ellipse({
left: this.startX,
top: this.startY,
rx: 0,
ry: 0,
fill: 'transparent',
stroke: this.color,
strokeWidth: this.strokeWidth,
lockScalingFlip: true
})
canvas.add(this.current);
}
handleMouseMove(event: any): void {
if (!this.start) {
return;
}
let pointer = this.canvas.getScenePoint(event.e);
let rx = Math.abs(pointer.x - this.startX) / 2;
let ry = Math.abs(pointer.y - this.startY) / 2;
if (rx > this.strokeWidth / 2) {
rx = rx - this.strokeWidth / 2
}
if (ry > this.strokeWidth / 2) {
ry = ry - this.strokeWidth / 2
}
let top = pointer.y < this.startY ? pointer.y : this.startY;
let left = pointer.x < this.startX ? pointer.x : this.startX;
this.current?.set('rx', rx);
this.current?.set('ry', ry);
this.current?.set('top', top);
this.current?.set('left', left);
this.canvas.requestRenderAll();
}
handleMouseUp(event: any): void {
if (!this.start || this.imageEditor.getOperatorType() != OperatorType.ELLIPSE) {
return
}
this.start = false;
let pointer = this.canvas.getScenePoint(event.e);
if (pointer.x == this.startX || pointer.y == this.startY) {
this.canvas.remove(this.current!);
} else {
const lastXY = this.current?.getXY();
const lastSize = {
width: this.current!.width,
height: this.current!.height
}
const lastRXY = {
rx: this.current!.rx,
ry: this.current!.ry
}
this.current!.set('lastXY', lastXY);
this.current!.set('lastDim', lastSize);
this.current!.set('lastRXY', lastRXY);
FabricObjectChangeHelper.listenMove(this.current!, this.imageEditor.getHistory());
FabricObjectChangeHelper.listenEllipseScale(this.current!, this.imageEditor.getHistory());
this.imageEditor.getHistory().recordCreateAction(this.current!);
}
this.current = undefined;
}
}
\ No newline at end of file
/* eslint-disable no-param-reassign */
import { Line } from 'fabric';
// 原来的createClass不太好使了,现在改成使用extends
// 由上到下,由左到右
export default class Arrow extends Line {
private arrowWidth: number = 4;
constructor([x1, y1, x2, y2] = [0, 0, 0, 0], options: any = {}) {
super([x1, y1, x2, y2], options);
}
// 8种情况的分解 是在所难免的
_render(ctx: CanvasRenderingContext2D): void {
super._render(ctx);
ctx.save();
// 角度要重新计算一下,应该根据宽高来搞
const xDiff = this.x2 - this.x1;
const yDiff = this.y2 - this.y1;
let y = yDiff > 0 ? this.height : -this.height;
let x = xDiff > 0 ? this.width : -this.width;
if (xDiff == 0) {
x = 0;
}
if (yDiff == 0) {
y = 0;
}
const angle = Math.atan2(y, x);
// 画一个三角形,然后将其移动到合适的位置去
this.translateArrow(ctx);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(this.arrowWidth * 2, 0);
ctx.lineTo(-this.arrowWidth * 2, this.arrowWidth * 2);
ctx.lineTo(-this.arrowWidth * 2, -this.arrowWidth * 2);
ctx.closePath();
ctx.fillStyle = (String)(this.stroke);
ctx.fill();
ctx.restore();
}
translateArrow(ctx: CanvasRenderingContext2D) {
const diffX = this.x2 - this.x1, diffY = this.y2 - this.y1;
if (diffX == 0 && diffY > 0) {
ctx.translate(0, this.height / 2);
} else if (diffX == 0 && diffY < 0) {
ctx.translate(0, -this.height / 2);
} else if (diffY == 0 && diffX > 0) {
ctx.translate(this.width / 2, 0);
} else if (diffY == 0 && diffX < 0) {
ctx.translate(-this.width / 2, 0);
} else if (diffX > 0 && diffY > 0) {
ctx.translate(this.width / 2, this.height / 2);
} else if (diffX > 0 && diffY < 0) {
ctx.translate(this.width / 2, -this.height / 2);
} else if (diffX < 0 && diffY > 0) {
ctx.translate(-this.width / 2, this.height / 2);
} else if (diffX < 0 && diffY < 0) {
ctx.translate(-this.width / 2, -this.height / 2);
}
}
}
\ No newline at end of file
import { Canvas, PatternBrush, Shadow } from "fabric";
import ImageEditor from "../image_editor";
import OperationHistory from "../history";
import { DEFAULT_COLOR, OperatorProps } from "../image_editor_operator";
const blockSize = 5;
function mosaicify(imageData: any) {
const { data } = imageData;
const iLen = imageData.height;
const jLen = imageData.width;
let index;
let i, _i, _iLen, j, _j, _jLen, r, g, b, a;
for (i = 0; i < iLen; i += blockSize) {
for (j = 0; j < jLen; j += blockSize) {
index = i * 4 * jLen + j * 4;
r = data[index];
g = data[index + 1];
b = data[index + 2];
a = data[index + 3];
_iLen = Math.min(i + blockSize, iLen);
_jLen = Math.min(j + blockSize, jLen);
for (_i = i; _i < _iLen; _i++) {
for (_j = j; _j < _jLen; _j++) {
index = _i * 4 * jLen + _j * 4;
data[index] = r;
data[index + 1] = g;
data[index + 2] = b;
data[index + 3] = a;
}
}
}
}
}
export default class MosaicOperator implements OperatorProps {
private canvas: Canvas;
private history: OperationHistory;
// 10小 20中 40大
private width: number = 20;
private mosaicBrush: PatternBrush | undefined;
private recorder: (event: any) => void;
constructor(imageEditor: ImageEditor) {
this.canvas = imageEditor.getCanvas();
this.history = imageEditor.getHistory();
this.recorder = this.recordPathCreate.bind(this);
}
getOperatorSize(): number {
return this.width;
}
getOperatorColor(): string {
return DEFAULT_COLOR;
}
setOperatorSize(width: number): void {
this.width = width;
this.mosaicBrush!.width = width;
}
setOperatorColor(): void {
// ignore
}
recordPathCreate(event: any) {
const path = event.path;
path.selectable = false;
path.evented = false;
path.hoverCursor = 'default';
path.lockScalingFlip = true
this.canvas.renderAll();
this.history.recordCreateAction(path);
}
startMosaicMode() {
const canvas = this.canvas;
canvas.isDrawingMode = true;
const mosaicBrush = new PatternBrush(canvas);
this.mosaicBrush = mosaicBrush;
canvas.freeDrawingBrush = mosaicBrush;
mosaicBrush.width = this.width;
mosaicBrush.shadow = new Shadow({
blur: 0,
offsetX: 0,
offsetY: 0,
affectStroke: true,
});
mosaicBrush.getPatternSrc = function () {
// 创立一个暂存 canvas 来绘製要画的图案
const cropping = {
left: 0,
top: 0,
width: canvas.width,
height: canvas.height,
};
const imageCanvas = canvas.toCanvasElement(1, cropping);
const imageCtx = imageCanvas.getContext('2d')!;
const imageData = imageCtx.getImageData(
0,
0,
imageCanvas.width,
imageCanvas.height,
);
mosaicify(imageData);
imageCtx.putImageData(imageData, 0, 0);
const patternCanvas = document.createElement('canvas'); // 这里的ceateElement一定要使用fabric内置的方法
const patternCtx = patternCanvas.getContext('2d')!;
patternCanvas.width = canvas.width || 0;
patternCanvas.height = canvas.height || 0;
patternCtx.drawImage(
imageCanvas,
0,
0,
imageCanvas.width,
imageCanvas.height,
cropping.left,
cropping.top,
cropping.width,
cropping.height,
);
return patternCanvas;
};
this.canvas.on('path:created', this.recorder);
}
endMosaicMode() {
this.canvas.isDrawingMode = false;
this.canvas.off('path:created', this.recorder);
}
}
\ No newline at end of file
import { Ellipse, FabricObject } from "fabric";
import OperationHistory from "../history";
export default class FabricObjectChangeHelper {
static listenMove(obj: FabricObject, history: OperationHistory) {
obj.on('moving', () => {
if (!obj.get('movingFlag')) {
obj.set('movingFlag', true);
}
})
obj.on('mouseup', () => {
if (!obj.get('movingFlag')) {
return;
}
obj.set('movingFlag', undefined);
const pos = obj.get('lastXY');
obj.set('lastXY', obj.getXY());
history.recordMoveAction(obj, pos.x, pos.y);
})
}
static listenScale(obj: FabricObject, history: OperationHistory) {
obj.on('scaling', () => {
if (!obj.get('scalingFlag')) {
obj.set('scalingFlag', true);
}
const { scaleX, scaleY } = obj;
// 这个地方还不能四舍五入,在结束的地方四舍五入比较好
// 四舍五入导致向左上拉的时候,位置会出现偏差
obj.set({
width: obj.width * scaleX,
height: obj.height * scaleY,
scaleX: 1, // 重置缩放
scaleY: 1
});
// 缓存会导致图像不能正确的放缩
obj.objectCaching = false;
})
obj.on('mouseup', () => {
// 还原缓存
obj.objectCaching = true;
const flag = obj.get('scalingFlag')
if (!flag) {
return
}
obj.set('scalingFlag', undefined);
const dim = obj.get('lastDim');
const pos = obj.get('lastXY');
const width = dim.width;
const height = dim.height;
const currWidth = obj.width;
const currHeight = obj.height;
const currDim = {
width: currWidth,
height: currHeight
}
obj.set('lastDim', currDim);
obj.set('lastXY', obj.getXY());
history.recordScaleAction(obj, width, height, pos.x, pos.y)
})
}
static listenEllipseScale(obj: Ellipse, history: OperationHistory) {
obj.on('scaling', () => {
if (!obj.get('scalingFlag')) {
obj.set('scalingFlag', true);
}
const { scaleX, scaleY } = obj;
const strokeWidth = obj.strokeWidth;
let rx = obj.width * scaleX / 2;
let ry = obj.height * scaleY / 2;
if (rx > strokeWidth / 2) {
rx = rx - strokeWidth / 2;
}
if (ry > strokeWidth / 2) {
ry = ry - strokeWidth / 2;
}
obj.set({
rx, ry,
width: obj.width * scaleX,
height: obj.height * scaleY,
scaleX: 1, // 重置缩放
scaleY: 1
});
// 缓存会导致图像不能正确的放缩
obj.objectCaching = false;
})
obj.on('mouseup', () => {
// 还原缓存
obj.objectCaching = true;
const flag = obj.get('scalingFlag')
if (!flag) {
return
}
obj.set('scalingFlag', undefined);
const dim = obj.get('lastDim');
const pos = obj.get('lastXY');
const rxy = obj.get('lastRXY');
console.log(rxy)
const width = dim.width;
const height = dim.height;
const rx = rxy.rx;
const ry = rxy.ry;
const currWidth = obj.width;
const currHeight = obj.height;
const currDim = {
width: currWidth,
height: currHeight
}
const currRXY = {
rx: obj.rx,
ry: obj.ry
}
obj.set('lastDim', currDim);
obj.set('lastXY', obj.getXY());
obj.set('lastRXY', currRXY)
history.recordEllipseScaleAction(obj, width, height, pos.x, pos.y, rx, ry)
})
}
// 框选,椭圆,箭头,都不是等比例缩放
// 文字、画笔是等比例缩放,要考虑一下等比例缩放的问题
static listenRatioScale(obj: FabricObject, history: OperationHistory) {
obj.on('scaling', () => {
if (!obj.get('scalingFlag')) {
obj.set('scalingFlag', true);
}
})
obj.on('mouseup', () => {
// 还原缓存
obj.objectCaching = true;
const flag = obj.get('scalingFlag')
if (!flag) {
return
}
obj.set('scalingFlag', undefined);
const scale = obj.get('lastScale');
const pos = obj.get('lastXY');
const scaleX = scale.x;
const scaleY = scale.y;
const currScale = {
x: obj.scaleX,
y: obj.scaleY
}
obj.set('lastScale', currScale);
obj.set('lastXY', obj.getXY());
history.recordRatioScaleAction(obj, scaleX, scaleY, pos.x, pos.y)
})
}
}
\ No newline at end of file
import { Canvas, Rect } from "fabric";
import ImageEditor from "../image_editor";
import { DEFAULT_COLOR, DEFAULT_STROKE_WIDTH, ImageEditorOperator, OperatorProps, OperatorType } from "../image_editor_operator";
import FabricObjectChangeHelper from "./move_helper";
export default class RectangleOperator implements ImageEditorOperator, OperatorProps {
private imageEditor: ImageEditor;
private canvas: Canvas;
private start: boolean;
private startX: number;
private startY: number;
private strokeWidth: number = DEFAULT_STROKE_WIDTH;
private color: string = DEFAULT_COLOR;
private current: Rect | undefined;
constructor(imageEditor: ImageEditor) {
this.imageEditor = imageEditor;
this.canvas = imageEditor.getCanvas();
this.start = false;
this.startX = 0;
this.startY = 0;
}
getOperatorSize(): number {
return this.strokeWidth;
}
getOperatorColor(): string {
return this.color;
}
setOperatorSize(width: number): void {
this.strokeWidth = width;
}
setOperatorColor(color: string): void {
this.color = color;
}
handleMouseDown(event: any): void {
const canvas = this.canvas;
if (canvas.getActiveObject() != undefined) {
return;
}
if (this.imageEditor.getOperatorType() != OperatorType.RECT) {
return;
}
if (this.start) {
return;
}
this.start = true;
let pointer = canvas.getScenePoint(event.e);
this.startX = pointer.x;
this.startY = pointer.y;
this.current = new Rect({
left: this.startX,
top: this.startY,
width: 0,
height: 0,
fill: 'transparent',
stroke: this.color,
strokeWidth: this.strokeWidth,
lockScalingFlip: true
})
canvas.add(this.current);
}
handleMouseMove(event: any): void {
if (!this.start) {
return;
}
let pointer = this.canvas.getScenePoint(event.e);
let width = Math.abs(pointer.x - this.startX);
let height = Math.abs(pointer.y - this.startY);
const left = pointer.x < this.startX ? pointer.x : this.startX;
const top = pointer.y < this.startY ? pointer.y : this.startY;
this.current?.set('width', Math.round(width));
this.current?.set('height', Math.round(height));
this.current?.set('top', Math.round(top));
this.current?.set('left', Math.round(left));
this.canvas.requestRenderAll();
}
handleMouseUp(event: any): void {
if (!this.start || this.imageEditor.getOperatorType() != OperatorType.RECT) {
return;
}
this.start = false;
let pointer = this.canvas.getScenePoint(event.e);
let width = Math.abs(pointer.x - this.startX);
let height = Math.abs(pointer.y - this.startY);
if (width <= 0 || height <= 0) {
this.canvas.remove(this.current!);
} else {
const lastXY = this.current?.getXY();
const lastSize = {
width: this.current!.width,
height: this.current!.height
}
this.current!.set('lastXY', lastXY);
this.current!.set('lastDim', lastSize);
FabricObjectChangeHelper.listenMove(this.current!, this.imageEditor.getHistory());
FabricObjectChangeHelper.listenScale(this.current!, this.imageEditor.getHistory());
this.imageEditor.getHistory().recordCreateAction(this.current!);
}
}
}
\ No newline at end of file
import { Canvas, IText } from "fabric";
import ImageEditor from "../image_editor";
import { DEFAULT_COLOR, ImageEditorOperator, OperatorProps, OperatorType } from "../image_editor_operator";
import FabricObjectChangeHelper from "./move_helper";
export default class TextOperator implements ImageEditorOperator, OperatorProps {
private imageEditor: ImageEditor;
private canvas: Canvas;
private start: boolean = false;
private startX: number;
private startY: number;
private allowCreate: boolean = true;
private fontSize: number = 20;
private color: string = DEFAULT_COLOR;
constructor(imageEditor: ImageEditor) {
this.imageEditor = imageEditor;
this.canvas = imageEditor.getCanvas();
this.startX = 0;
this.startY = 0;
}
getOperatorSize(): number {
return this.fontSize;
}
getOperatorColor(): string {
return this.color;
}
setOperatorSize(fontSize: number): void {
this.fontSize = fontSize;
}
setOperatorColor(color: string): void {
this.color = color;
}
handleMouseDownBefore(): void {
if (this.imageEditor.getOperatorType() != OperatorType.TEXT) {
return
}
if (this.canvas.getActiveObject()) {
this.allowCreate = false;
} else {
this.allowCreate = true;
}
}
handleMouseDown(event: any): void {
if (!this.allowCreate) {
return;
}
const canvas = this.canvas;
if (canvas.getActiveObject() || this.imageEditor.getOperatorType() != OperatorType.TEXT) {
return;
}
const pointer = canvas.getScenePoint(event.e);
this.startX = pointer.x;
this.startY = pointer.y;
this.start = true;
}
handleMouseUp(event: any): void {
if (!this.allowCreate) {
return;
}
if (!this.start || this.imageEditor.getOperatorType() != OperatorType.TEXT) {
return
}
this.start = false;
const canvas = this.canvas;
const pointer = canvas.getScenePoint(event.e);
const width = Math.abs(pointer.x - this.startX);
const height = Math.abs(pointer.y - this.startY);
if (width * width + height * height > 100) {
return;
}
const text = new IText('请输入内容', {
left: pointer.x,
top: pointer.y,
fontSize: this.fontSize,
fill: this.color,
lockScalingFlip: true
} as any);
text.setControlVisible('mt', false);
text.setControlVisible('mb', false);
text.setControlVisible('ml', false);
text.setControlVisible('mr', false);
canvas.add(text);
canvas.setActiveObject(text);
canvas.renderAll();
const lastXY = text.getXY();
const lastScale = {
x: text.scaleX,
y: text.scaleY
}
text.set('lastXY', lastXY);
text.set('lastScale', lastScale);
FabricObjectChangeHelper.listenMove(text, this.imageEditor.getHistory());
FabricObjectChangeHelper.listenRatioScale(text, this.imageEditor.getHistory());
this.imageEditor.getHistory().recordCreateAction(text);
}
}
\ No newline at end of file
import { Point } from "fabric";
import ElementManager, { pxielToNumber } from "./element_manager";
import ImageEditor from "./image_editor";
const DEFAULT_MOUSE_DOWN_FUNC = (_e: MouseEvent) => { };
export class Screenshoter {
private mouseMoving = DEFAULT_MOUSE_DOWN_FUNC;
private mouseUp = DEFAULT_MOUSE_DOWN_FUNC;
private resizerPosX = 0;
private resizerPosY = 0;
private startX = 0;
private startY = 0;
private movingX = 0;
private movingY = 0;
private width = 0;
private height = 0;
private maxLeft = 0;
private minLeft = 0;
private maxTop = 0;
private minTop = 0;
// 正在激活的
private activeResizer = 'none';
private mask?: HTMLCanvasElement;
private maskLeft = 0;
private maskTop = 0;
private fabricWrapperEl?: HTMLDivElement;
private imageEditor?: ImageEditor;
private elementManager?: ElementManager;
private clipArea = { startX: 0, startY: 0, width: 0, height: 0 }
private dragger = {
isClipAreaInDrag: false,
startX: 0,
startY: 0,
width: 0,
height: 0,
// 记录鼠标一开始落在的位置
pointerDownX: 0,
pointerDownY: 0,
// 做临时变量用,拖拽结束的时候需要用到
currentX: 0,
currentY: 0,
}
private dragRecord: Record<string, any> = {
northWestTop: 0,
northWestLeft: 0,
northTop: 0,
northLeft: 0,
northEastTop: 0,
northEastLeft: 0,
eastTop: 0,
eastLeft: 0,
southTop: 0,
southLeft: 0,
southWestTop: 0,
southWestLeft: 0,
westTop: 0,
westLeft: 0
}
private cursorInClipArea = false;
private screenshotResizer: {
northWest: HTMLDivElement,
north: HTMLDivElement,
northEast: HTMLDivElement,
east: HTMLDivElement,
southEast: HTMLDivElement,
south: HTMLDivElement,
southWest: HTMLDivElement,
west: HTMLDivElement
} | undefined;
private toolbar: HTMLDivElement | undefined;
private confirm: HTMLDivElement | undefined;
private cancel: HTMLDivElement | undefined;
private confirmFunc = () => { };
private cancelFunc = () => { };
private mouseDownNorthWest = DEFAULT_MOUSE_DOWN_FUNC;
private mouseDownNorth = DEFAULT_MOUSE_DOWN_FUNC;
private mouseDownNorthEast = DEFAULT_MOUSE_DOWN_FUNC;
private mouseDownEast = DEFAULT_MOUSE_DOWN_FUNC;
private mouseDownSouthEast = DEFAULT_MOUSE_DOWN_FUNC;
private mosueDownSouth = DEFAULT_MOUSE_DOWN_FUNC;
private mouseDownSouthWest = DEFAULT_MOUSE_DOWN_FUNC;
private mouseDownWest = DEFAULT_MOUSE_DOWN_FUNC;
private canvasMouseDownFunc = DEFAULT_MOUSE_DOWN_FUNC;
private canvasMouseMoveFunc = DEFAULT_MOUSE_DOWN_FUNC;
private canvasMouseUpFunc = DEFAULT_MOUSE_DOWN_FUNC;
init(imageEditor: ImageEditor, manager: ElementManager) {
this.elementManager = manager;
this.imageEditor = imageEditor;
this.fabricWrapperEl = manager.getFabricWrapper()!;
this.mask = manager.getScreenshotCanvas();
this.screenshotResizer = manager.getScreenshotResizers();
this.toolbar = manager.getScreenshotToolbar();
this.confirm = manager.getScreenshotConfirmButton();
this.cancel = manager.getScreenshotCancelButton();
const that = this;
const recordResizer = (name: string, style: CSSStyleDeclaration, e: MouseEvent) => {
that.activeResizer = name;
that.startX = e.pageX;
that.startY = e.pageY;
that.resizerPosX = pxielToNumber(style.left);
that.resizerPosY = pxielToNumber(style.top);
}
const resizer = that.screenshotResizer!;
that.mouseDownNorthWest = (e: MouseEvent) => { recordResizer('northwest', resizer.northWest.style, e) }
this.screenshotResizer.northWest.addEventListener('pointerdown', this.mouseDownNorthWest);
that.mouseDownNorth = (e: MouseEvent) => { recordResizer('north', resizer.north.style, e) }
this.screenshotResizer.north.addEventListener('pointerdown', this.mouseDownNorth);
that.mouseDownNorthEast = (e: MouseEvent) => { recordResizer('northeast', resizer.northEast.style, e) }
this.screenshotResizer.northEast.addEventListener('pointerdown', this.mouseDownNorthEast);
that.mouseDownEast = (e: MouseEvent) => { recordResizer('east', resizer.east.style, e) }
this.screenshotResizer.east.addEventListener('pointerdown', this.mouseDownEast);
that.mouseDownSouthEast = (e: MouseEvent) => { recordResizer('southeast', resizer.southEast.style, e) }
this.screenshotResizer.southEast.addEventListener('pointerdown', this.mouseDownSouthEast);
that.mosueDownSouth = (e: MouseEvent) => { recordResizer('south', resizer.south.style, e) }
this.screenshotResizer.south.addEventListener('pointerdown', this.mosueDownSouth);
that.mouseDownSouthWest = (e: MouseEvent) => { recordResizer('southwest', resizer.southWest.style, e) }
this.screenshotResizer.southWest.addEventListener('pointerdown', this.mouseDownSouthWest);
that.mouseDownWest = (e: MouseEvent) => { recordResizer('west', resizer.west.style, e) }
this.screenshotResizer.west.addEventListener('pointerdown', this.mouseDownWest);
this.cancel.removeEventListener('click', this.cancelFunc);
this.confirm.removeEventListener('click', this.confirmFunc);
this.cancelFunc = this.cancelScreenshot.bind(this);
this.confirmFunc = this.confirmScreenshot.bind(this);
this.cancel.addEventListener('click', this.cancelFunc);
this.confirm.addEventListener('click', this.confirmFunc);
}
handleDragArea() {
const canvas = this.mask!;
canvas.removeEventListener('mousemove', this.canvasMouseMoveFunc);
canvas.removeEventListener('mousedown', this.canvasMouseDownFunc);
document.removeEventListener('mouseup', this.canvasMouseUpFunc);
this.canvasMouseMoveFunc = (event: MouseEvent) => {
const clipArea = this.clipArea;
const xRange = [clipArea.startX, clipArea.startX + clipArea.width];
const yRange = [clipArea.startY, clipArea.startY + clipArea.height];
const currentX = Math.round(event.pageX - this.maskLeft);
const currentY = Math.round(event.pageY - this.maskTop);
const xInRange = currentX >= xRange[0] && currentX <= xRange[1];
const yInRange = currentY >= yRange[0] && currentY <= yRange[1];
const currentCursor = canvas.style.cursor;
this.cursorInClipArea = xInRange && yInRange;
if (xInRange && yInRange && currentCursor != 'move') {
canvas.style.cursor = 'move';
} else if ((!xInRange || !yInRange) && currentCursor != 'default' && !this.dragger.isClipAreaInDrag) {
canvas.style.cursor = 'default';
}
if (this.dragger.isClipAreaInDrag) {
const x = event.pageX;
const y = event.pageY;
let changeX = x - this.dragger.pointerDownX;
let changeY = y - this.dragger.pointerDownY;
if (changeX + clipArea.startX < 0) {
changeX = -clipArea.startX;
} else if (clipArea.startX + changeX + clipArea.width > this.width) {
changeX = this.width - clipArea.width - clipArea.startX;
}
if (changeY + clipArea.startY < 0) {
changeY = -clipArea.startY;
} else if (clipArea.startY + changeY + clipArea.height > this.height) {
changeY = this.height - clipArea.height - clipArea.startY;
}
this.transferClipArea(changeX, changeY);
this.adjustToolbarPosition();
}
}
this.canvasMouseDownFunc = (event: MouseEvent) => {
if (this.cursorInClipArea === false) {
return;
}
this.dragger.isClipAreaInDrag = true;
this.dragger.pointerDownX = event.pageX;
this.dragger.pointerDownY = event.pageY;
const resizers = this.screenshotResizer!
Object.entries(resizers).forEach((value) => {
const eleName = value[0];
const ele = value[1];
this.dragRecord[eleName + 'Left'] = pxielToNumber(ele.style.left);
this.dragRecord[eleName + 'Top'] = pxielToNumber(ele.style.top);
})
this.dragger.height = this.clipArea.height;
this.dragger.width = this.clipArea.width;
this.dragger.startX = this.clipArea.startX;
this.dragger.startY = this.clipArea.startY;
}
this.canvasMouseUpFunc = (_event: MouseEvent) => {
console.log(this.dragger.isClipAreaInDrag);
if (!this.dragger.isClipAreaInDrag) {
return;
}
this.dragger.isClipAreaInDrag = false;
this.clipArea.startX = this.dragger.currentX;
this.clipArea.startY = this.dragger.currentY;
}
document.addEventListener('mousemove', this.canvasMouseMoveFunc)
canvas.addEventListener('mousedown', this.canvasMouseDownFunc)
document.addEventListener('mouseup', this.canvasMouseUpFunc);
}
transferClipArea(changeX: number, changeY: number) {
const resizers = this.screenshotResizer!
Object.entries(resizers).forEach((value) => {
const eleName = value[0];
const left = this.dragRecord[eleName + 'Left'];
const top = this.dragRecord[eleName + 'Top'];
value[1].style.left = left + changeX + 'px';
value[1].style.top = top + changeY + 'px';
})
const startX = this.dragger.startX + changeX;
const startY = this.dragger.startY + changeY;
const width = this.dragger.width;
const height = this.dragger.height;
const context = this.mask!.getContext('2d')!;
context.clearRect(0, 0, this.width, this.height);
context.fillStyle = 'rgba(0,0,0,0.4)';
context.fillRect(0, 0, this.width, this.height);
context.clearRect(startX, startY, width, height);
this.dragger.currentX = startX;
this.dragger.currentY = startY;
}
updateClipArea() {
}
getClipAreaRect() {
const resizer = this.screenshotResizer!;
const neTop = pxielToNumber(resizer.northEast.style.top);
const nwTop = pxielToNumber(resizer.northWest.style.top);
const swTop = pxielToNumber(resizer.southWest.style.top);
const seTop = pxielToNumber(resizer.southEast.style.top);
const nwLeft = pxielToNumber(resizer.northWest.style.left)
const neLeft = pxielToNumber(resizer.northEast.style.left);
const swLeft = pxielToNumber(resizer.southWest.style.left);
const seLeft = pxielToNumber(resizer.southEast.style.left);
const maxTop = Math.max(neTop, nwTop, swTop, seTop);
const minTop = Math.min(neTop, nwTop, swTop, seTop);
const maxLeft = Math.max(nwLeft, neLeft, swLeft, seLeft);
const minLeft = Math.min(nwLeft, neLeft, swLeft, seLeft);
// 超出的部分,实际上是不显式的
const canvasLeft = Math.abs(pxielToNumber(this.fabricWrapperEl!.style.left));
const canvasTop = Math.abs(pxielToNumber(this.fabricWrapperEl!.style.top));
const maskLeft = Math.abs(pxielToNumber(this.mask!.style.left))
const maskTop = Math.abs(pxielToNumber(this.mask!.style.top))
const top = minTop - maskTop + canvasTop;
const left = minLeft - maskLeft + canvasLeft;
const width = maxLeft - minLeft;
const height = maxTop - minTop;
return { top, left, width, height }
}
// TODO 结束的时候要把所有的事件全部都干掉
async confirmScreenshot() {
const storeState = this.imageEditor!.storeCanvasState();
const { top, left, width, height } = this.getClipAreaRect();
const start = new Point(left, top);
const end = new Point(left + width, height + top);
const image = this.imageEditor!.getAreaImageInfo(start, end);
this.handleScreenshotFinished();
await this.imageEditor!.renderToCanvas(image);
const cropState = this.imageEditor!.storeCanvasState();
this.imageEditor!.getHistory().recordCropAction(storeState.wrapper, storeState.canvas, cropState.wrapper, cropState.canvas);
}
cancelScreenshot() {
this.handleScreenshotFinished();
}
handleScreenshotFinished() {
this.toolbar!.style.display = 'none';
const resizer = this.screenshotResizer!;
Object.entries(resizer).forEach(([_k, v]) => {
v.style.display = 'none';
})
this.activeResizer = 'none';
this.mask!.style.display = 'none';
this.elementManager!.showResizer();
this.elementManager?.showToolbar();
document.removeEventListener('pointermove', this.mouseMoving);
document.removeEventListener('pointerup', this.mouseUp);
document.removeEventListener('mouseup', this.canvasMouseUpFunc);
}
adjustToolbarPosition() {
const toolbar = this.toolbar!;
const resizer = this.screenshotResizer!;
if (toolbar.style.display == 'none') {
toolbar.style.display = 'block';
}
const neTop = pxielToNumber(resizer.northEast.style.top);
const nwTop = pxielToNumber(resizer.northWest.style.top);
const swTop = pxielToNumber(resizer.southWest.style.top);
const seTop = pxielToNumber(resizer.southEast.style.top);
const nwLeft = pxielToNumber(resizer.northWest.style.left)
const neLeft = pxielToNumber(resizer.northEast.style.left);
const swLeft = pxielToNumber(resizer.southWest.style.left);
const seLeft = pxielToNumber(resizer.southEast.style.left);
const maxTop = Math.max(neTop, nwTop, swTop, seTop);
const maxLeft = Math.max(nwLeft, neLeft, swLeft, seLeft);
// 64为toolbar的宽度
toolbar.style.left = (maxLeft - 64) + 'px';
// 加10为了防止工具条太高
toolbar.style.top = maxTop + 10 + 'px';
}
resizeArea() {
const resizer = this.screenshotResizer!;
const changeX = this.movingX - this.startX;
const changeY = this.movingY - this.startY;
let newLeft = this.resizerPosX + changeX;
let newTop = this.resizerPosY + changeY;
if (newLeft < this.minLeft) {
newLeft = this.minLeft;
} else if (newLeft > this.maxLeft) {
newLeft = this.maxLeft;
}
if (newTop < this.minTop) {
newTop = this.minTop;
} else if (newTop > this.maxTop) {
newTop = this.maxTop;
}
if (this.activeResizer == 'northwest') {
resizer.northWest.style.left = newLeft + 'px';
resizer.northWest.style.top = newTop + 'px';
resizer.west.style.left = newLeft + 'px';
resizer.north.style.top = newTop + 'px';
resizer.southWest.style.left = newLeft + 'px';
resizer.northEast.style.top = newTop + 'px';
} else if (this.activeResizer == 'north') {
resizer.north.style.top = newTop + 'px';
resizer.northEast.style.top = newTop + 'px';
resizer.northWest.style.top = newTop + 'px';
} else if (this.activeResizer == 'northeast') {
resizer.northEast.style.left = newLeft + 'px';
resizer.northEast.style.top = newTop + 'px';
resizer.north.style.top = newTop + 'px';
resizer.northWest.style.top = newTop + 'px';
resizer.east.style.left = newLeft + 'px';
resizer.southEast.style.left = newLeft + 'px';
} else if (this.activeResizer == 'east') {
resizer.east.style.left = newLeft + 'px';
resizer.northEast.style.left = newLeft + 'px';
resizer.southEast.style.left = newLeft + 'px';
} else if (this.activeResizer == 'southeast') {
resizer.southEast.style.left = newLeft + 'px';
resizer.southEast.style.top = newTop + 'px';
resizer.east.style.left = newLeft + 'px';
resizer.south.style.top = newTop + 'px';
resizer.northEast.style.left = newLeft + 'px';
resizer.southWest.style.top = newTop + 'px';
} else if (this.activeResizer == 'south') {
resizer.south.style.top = newTop + 'px';
resizer.southEast.style.top = newTop + 'px';
resizer.southWest.style.top = newTop + 'px';
} else if (this.activeResizer == 'southwest') {
resizer.southWest.style.left = newLeft + 'px';
resizer.southWest.style.top = newTop + 'px';
resizer.west.style.left = newLeft + 'px';
resizer.south.style.top = newTop + 'px';
resizer.northWest.style.left = newLeft + 'px';
resizer.southEast.style.top = newTop + 'px';
} else if (this.activeResizer == 'west') {
resizer.west.style.left = newLeft + 'px';
resizer.southWest.style.left = newLeft + 'px';
resizer.northWest.style.left = newLeft + 'px';
}
// 调整时,中点位置要重新计算
this.formatCenterResizer();
const canvasLeft = pxielToNumber(this.mask!.style.left);
const canvasTop = pxielToNumber(this.mask!.style.top);
const northWestLeft = pxielToNumber(resizer.northWest.style.left);
const northWestTop = pxielToNumber(resizer.northWest.style.top);
const southEastLeft = pxielToNumber(resizer.southEast.style.left);
const southEastTop = pxielToNumber(resizer.southEast.style.top);
const width = Math.round(southEastLeft - northWestLeft);
const height = Math.round(southEastTop - northWestTop);
const context = this.mask!.getContext('2d')!;
context.clearRect(0, 0, this.width, this.height);
context.fillStyle = 'rgba(0,0,0,0.4)';
context.fillRect(0, 0, this.width, this.height);
// 然后将中间的设置为空白的,完全学习微信
const startX = northWestLeft - canvasLeft;
const startY = northWestTop - canvasTop;
context.clearRect(startX, startY, width, height);
this.clipArea.startX = startX;
this.clipArea.startY = startY;
this.clipArea.width = width;
this.clipArea.height = height;
this.adjustToolbarPosition();
}
formatCenterResizer() {
const resizer = this.screenshotResizer!;
const nwLeft = pxielToNumber(resizer.northWest.style.left)
const nwTop = pxielToNumber(resizer.northWest.style.top);
const neLeft = pxielToNumber(resizer.northEast.style.left);
const swTop = pxielToNumber(resizer.southWest.style.top);
const verticalLeft = (nwLeft + neLeft) / 2;
const horizontalTop = (nwTop + swTop) / 2;
resizer.north.style.left = verticalLeft + 'px';
resizer.south.style.left = verticalLeft + 'px';
resizer.west.style.top = horizontalTop + 'px';
resizer.east.style.top = horizontalTop + 'px';
}
initMask(left: number, top: number, width: number, height: number) {
this.width = width;
this.height = height;
const mask = this.mask!;
mask.style.left = left + 'px';
mask.style.top = top + 'px';
mask.style.width = this.width + 'px';
mask.style.height = this.height + 'px';
mask.style.display = 'block';
mask.width = this.width;
mask.height = this.height;
this.maskLeft = left;
this.maskTop = top;
this.minLeft = left;
this.maxLeft = this.minLeft + width;
this.minTop = top;
this.maxTop = this.minTop + height;
const context = mask.getContext('2d')!;
context.fillStyle = 'rgba(0,0,0,0.4)';
context.fillRect(0, 0, this.width, this.height);
const cropLeft = Math.round(this.width * 0.2);
const cropTop = Math.round(this.height * 0.2);
// 然后将中间的设置为空白的,完全学习微信
const startX = cropLeft;
const startY = cropTop;
const cropWidth = Math.round(this.width * 0.6);
const cropHeight = Math.round(this.height * 0.6);
context.clearRect(startX, startY, cropWidth, cropHeight);
this.clipArea = {
startX, startY,
width: cropWidth,
height: cropHeight
}
const resizer = this.screenshotResizer!;
resizer.northWest.style.left = left + cropLeft + 'px';
resizer.northWest.style.top = top + + cropTop + 'px';
resizer.north.style.left = left + cropLeft + Math.round(cropWidth / 2) + 'px';
resizer.north.style.top = top + + cropTop + 'px';
resizer.northEast.style.left = left + cropLeft + cropWidth + 'px';
resizer.northEast.style.top = top + + cropTop + 'px';
resizer.east.style.left = left + cropLeft + cropWidth + 'px';
resizer.east.style.top = top + + cropTop + + Math.round(cropHeight / 2) + 'px';
resizer.southEast.style.left = left + cropLeft + cropWidth + 'px';
resizer.southEast.style.top = top + + cropTop + cropHeight + 'px';
resizer.south.style.left = left + cropLeft + Math.round(cropWidth / 2) + 'px';
resizer.south.style.top = top + + cropTop + + cropHeight + 'px';
resizer.southWest.style.left = left + cropLeft + 'px';
resizer.southWest.style.top = top + + cropTop + cropHeight + 'px';
resizer.west.style.left = left + cropLeft + 'px';
resizer.west.style.top = top + + cropTop + Math.round(cropHeight / 2) + 'px';
Object.entries(resizer).forEach(([_k, v]) => {
v.style.display = 'block';
})
document.removeEventListener('pointermove', this.mouseMoving);
document.removeEventListener('pointerup', this.mouseUp);
this.mouseMoving = (e: MouseEvent) => {
if (this.activeResizer != 'none') {
this.movingX = e.pageX;
this.movingY = e.pageY;
this.resizeArea();
}
};
document.addEventListener('pointermove', this.mouseMoving);
this.mouseUp = () => {
this.activeResizer = 'none';
}
document.addEventListener('pointerup', this.mouseUp);
this.handleDragArea();
this.elementManager!.hideResizer();
this.adjustToolbarPosition();
}
}
\ No newline at end of file
import ImageEditor from "./image_editor";
export class ImageEditorShortcutManager {
protected imageEditor: ImageEditor;
protected keyboardEventHandler: (event: KeyboardEvent) => void;
constructor(imageEditor: ImageEditor) {
this.imageEditor = imageEditor;
this.keyboardEventHandler = this.handleKeyboardEvent.bind(this);
document.addEventListener('keydown', this.keyboardEventHandler)
}
handleKeyboardEvent(event: KeyboardEvent) {
const noControlKey = !event.ctrlKey && !event.shiftKey && !event.altKey
const ctrlOnly = event.ctrlKey && !event.shiftKey && !event.altKey;
if (event.key === 'Delete' && noControlKey) {
this.imageEditor.removeActiveObjects();
} else if (ctrlOnly) {
console.log(this.imageEditor)
switch (event.key) {
case 'z':
this.imageEditor!.getHistory().undo();
break;
case 'y':
this.imageEditor!.getHistory().redo();
break;
}
}
}
destroy() {
document.removeEventListener('keydown', this.keyboardEventHandler);
}
}
\ No newline at end of file
export function getAbsolutePosition(element: any) {
const rect = element.getBoundingClientRect();
// 结合页面的滚动距离来计算相对于整个文档的绝对位置
const x = rect.left + window.scrollX;
const y = rect.top + window.scrollY;
return { x, y };
}
\ No newline at end of file
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
import { defineConfig } from 'vite'
import { viteStaticCopy } from 'vite-plugin-static-copy'
export default defineConfig({
plugins: [
viteStaticCopy({
targets: [
{ src: 'src/assets/*', dest: 'assets/' }, // 将 src/assets 下的文件复制到 dist/assets
{ src: 'basic.jpg', dest: '.' }, // 将 src/assets 下的文件复制到 dist/assets
]
})
],
build: {
}
})
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@esbuild/linux-x64@0.21.5":
version "0.21.5"
resolved "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz"
integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
"@mapbox/node-pre-gyp@^1.0.0":
version "1.0.11"
resolved "https://registry.npmmirror.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz"
integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==
dependencies:
detect-libc "^2.0.0"
https-proxy-agent "^5.0.0"
make-dir "^3.1.0"
node-fetch "^2.6.7"
nopt "^5.0.0"
npmlog "^5.0.1"
rimraf "^3.0.2"
semver "^7.3.5"
tar "^6.1.11"
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
dependencies:
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
version "2.0.5"
resolved "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
"@nodelib/fs.walk@^1.2.3":
version "1.2.8"
resolved "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz"
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
dependencies:
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@rollup/rollup-linux-x64-gnu@4.24.0":
version "4.24.0"
resolved "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz"
integrity sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==
"@rollup/rollup-linux-x64-musl@4.24.0":
version "4.24.0"
resolved "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz"
integrity sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==
"@tootallnate/once@2":
version "2.0.0"
resolved "https://registry.npmmirror.com/@tootallnate/once/-/once-2.0.0.tgz"
integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
"@types/estree@1.0.6":
version "1.0.6"
resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz"
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
"@types/fs-extra@^8.0.1":
version "8.1.5"
resolved "https://registry.npmmirror.com/@types/fs-extra/-/fs-extra-8.1.5.tgz"
integrity sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==
dependencies:
"@types/node" "*"
"@types/glob@^7.1.1":
version "7.2.0"
resolved "https://registry.npmmirror.com/@types/glob/-/glob-7.2.0.tgz"
integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==
dependencies:
"@types/minimatch" "*"
"@types/node" "*"
"@types/minimatch@*":
version "5.1.2"
resolved "https://registry.npmmirror.com/@types/minimatch/-/minimatch-5.1.2.tgz"
integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==
"@types/node@*":
version "22.7.8"
resolved "https://registry.npmmirror.com/@types/node/-/node-22.7.8.tgz"
integrity sha512-a922jJy31vqR5sk+kAdIENJjHblqcZ4RmERviFsER4WJcEONqxKcjNOlk0q7OUfrF5sddT+vng070cdfMlrPLg==
dependencies:
undici-types "~6.19.2"
abab@^2.0.6:
version "2.0.6"
resolved "https://registry.npmmirror.com/abab/-/abab-2.0.6.tgz"
integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
abbrev@1:
version "1.1.1"
resolved "https://registry.npmmirror.com/abbrev/-/abbrev-1.1.1.tgz"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
acorn-globals@^7.0.0:
version "7.0.1"
resolved "https://registry.npmmirror.com/acorn-globals/-/acorn-globals-7.0.1.tgz"
integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==
dependencies:
acorn "^8.1.0"
acorn-walk "^8.0.2"
acorn-walk@^8.0.2:
version "8.3.4"
resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.4.tgz"
integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==
dependencies:
acorn "^8.11.0"
acorn@^8.1.0, acorn@^8.11.0, acorn@^8.8.1:
version "8.12.1"
resolved "https://registry.npmmirror.com/acorn/-/acorn-8.12.1.tgz"
integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
agent-base@6:
version "6.0.2"
resolved "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz"
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
dependencies:
debug "4"
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
anymatch@~3.1.2:
version "3.1.3"
resolved "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz"
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
"aproba@^1.0.3 || ^2.0.0":
version "2.0.0"
resolved "https://registry.npmmirror.com/aproba/-/aproba-2.0.0.tgz"
integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
are-we-there-yet@^2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz"
integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==
dependencies:
delegates "^1.0.0"
readable-stream "^3.6.0"
array-union@^2.1.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
binary-extensions@^2.0.0:
version "2.3.0"
resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@^3.0.3, braces@~3.0.2:
version "3.0.3"
resolved "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz"
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
dependencies:
fill-range "^7.1.1"
canvas@^2.11.2:
version "2.11.2"
resolved "https://registry.npmmirror.com/canvas/-/canvas-2.11.2.tgz"
integrity sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==
dependencies:
"@mapbox/node-pre-gyp" "^1.0.0"
nan "^2.17.0"
simple-get "^3.0.3"
chokidar@^3.5.3:
version "3.6.0"
resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz"
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
glob-parent "~5.1.2"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.6.0"
optionalDependencies:
fsevents "~2.3.2"
chownr@^2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/chownr/-/chownr-2.0.0.tgz"
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
color-support@^1.1.2:
version "1.1.3"
resolved "https://registry.npmmirror.com/color-support/-/color-support-1.1.3.tgz"
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
colorette@^1.1.0:
version "1.4.0"
resolved "https://registry.npmmirror.com/colorette/-/colorette-1.4.0.tgz"
integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
console-control-strings@^1.0.0, console-control-strings@^1.1.0:
version "1.1.0"
resolved "https://registry.npmmirror.com/console-control-strings/-/console-control-strings-1.1.0.tgz"
integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
cssom@^0.5.0:
version "0.5.0"
resolved "https://registry.npmmirror.com/cssom/-/cssom-0.5.0.tgz"
integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==
cssom@~0.3.6:
version "0.3.8"
resolved "https://registry.npmmirror.com/cssom/-/cssom-0.3.8.tgz"
integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
cssstyle@^2.3.0:
version "2.3.0"
resolved "https://registry.npmmirror.com/cssstyle/-/cssstyle-2.3.0.tgz"
integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==
dependencies:
cssom "~0.3.6"
data-urls@^3.0.2:
version "3.0.2"
resolved "https://registry.npmmirror.com/data-urls/-/data-urls-3.0.2.tgz"
integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==
dependencies:
abab "^2.0.6"
whatwg-mimetype "^3.0.0"
whatwg-url "^11.0.0"
debug@4:
version "4.3.7"
resolved "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz"
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
dependencies:
ms "^2.1.3"
decimal.js@^10.4.2:
version "10.4.3"
resolved "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.4.3.tgz"
integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
decompress-response@^4.2.0:
version "4.2.1"
resolved "https://registry.npmmirror.com/decompress-response/-/decompress-response-4.2.1.tgz"
integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==
dependencies:
mimic-response "^2.0.0"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/delegates/-/delegates-1.0.0.tgz"
integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==
detect-libc@^2.0.0:
version "2.0.3"
resolved "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.0.3.tgz"
integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz"
integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
dependencies:
path-type "^4.0.0"
domexception@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/domexception/-/domexception-4.0.0.tgz"
integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==
dependencies:
webidl-conversions "^7.0.0"
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
entities@^4.4.0:
version "4.5.0"
resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
esbuild@^0.21.3:
version "0.21.5"
resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz"
integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==
optionalDependencies:
"@esbuild/aix-ppc64" "0.21.5"
"@esbuild/android-arm" "0.21.5"
"@esbuild/android-arm64" "0.21.5"
"@esbuild/android-x64" "0.21.5"
"@esbuild/darwin-arm64" "0.21.5"
"@esbuild/darwin-x64" "0.21.5"
"@esbuild/freebsd-arm64" "0.21.5"
"@esbuild/freebsd-x64" "0.21.5"
"@esbuild/linux-arm" "0.21.5"
"@esbuild/linux-arm64" "0.21.5"
"@esbuild/linux-ia32" "0.21.5"
"@esbuild/linux-loong64" "0.21.5"
"@esbuild/linux-mips64el" "0.21.5"
"@esbuild/linux-ppc64" "0.21.5"
"@esbuild/linux-riscv64" "0.21.5"
"@esbuild/linux-s390x" "0.21.5"
"@esbuild/linux-x64" "0.21.5"
"@esbuild/netbsd-x64" "0.21.5"
"@esbuild/openbsd-x64" "0.21.5"
"@esbuild/sunos-x64" "0.21.5"
"@esbuild/win32-arm64" "0.21.5"
"@esbuild/win32-ia32" "0.21.5"
"@esbuild/win32-x64" "0.21.5"
escodegen@^2.0.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/escodegen/-/escodegen-2.1.0.tgz"
integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==
dependencies:
esprima "^4.0.1"
estraverse "^5.2.0"
esutils "^2.0.2"
optionalDependencies:
source-map "~0.6.1"
esprima@^4.0.1:
version "4.0.1"
resolved "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
estraverse@^5.2.0:
version "5.3.0"
resolved "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz"
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
fabric@^6.4.3:
version "6.4.3"
resolved "https://registry.npmmirror.com/fabric/-/fabric-6.4.3.tgz"
integrity sha512-z/bJna3kWOBv+wmvVK4XxUQgCXLGb//VaSr5xPFIP708obH7472uuVsWbXam+xq+y21bLBtr4OHO1HuJyYi4FQ==
optionalDependencies:
canvas "^2.11.2"
jsdom "^20.0.1"
fast-glob@^3.0.3, fast-glob@^3.2.11, fast-glob@^3.2.7:
version "3.3.2"
resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.2.tgz"
integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
glob-parent "^5.1.2"
merge2 "^1.3.0"
micromatch "^4.0.4"
fastq@^1.6.0:
version "1.17.1"
resolved "https://registry.npmmirror.com/fastq/-/fastq-1.17.1.tgz"
integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==
dependencies:
reusify "^1.0.4"
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz"
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
dependencies:
to-regex-range "^5.0.1"
form-data@^4.0.0:
version "4.0.1"
resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.1.tgz"
integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
fs-extra@^11.1.0:
version "11.2.0"
resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.2.0.tgz"
integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz"
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-minipass@^2.0.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/fs-minipass/-/fs-minipass-2.1.0.tgz"
integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
dependencies:
minipass "^3.0.0"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
gauge@^3.0.0:
version "3.0.2"
resolved "https://registry.npmmirror.com/gauge/-/gauge-3.0.2.tgz"
integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==
dependencies:
aproba "^1.0.3 || ^2.0.0"
color-support "^1.1.2"
console-control-strings "^1.0.0"
has-unicode "^2.0.1"
object-assign "^4.1.1"
signal-exit "^3.0.0"
string-width "^4.2.3"
strip-ansi "^6.0.1"
wide-align "^1.1.2"
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
glob@^7.1.3:
version "7.2.3"
resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.1.1"
once "^1.3.0"
path-is-absolute "^1.0.0"
globby@10.0.1:
version "10.0.1"
resolved "https://registry.npmmirror.com/globby/-/globby-10.0.1.tgz"
integrity sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==
dependencies:
"@types/glob" "^7.1.1"
array-union "^2.1.0"
dir-glob "^3.0.1"
fast-glob "^3.0.3"
glob "^7.1.3"
ignore "^5.1.1"
merge2 "^1.2.3"
slash "^3.0.0"
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.11"
resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
has-unicode@^2.0.1:
version "2.0.1"
resolved "https://registry.npmmirror.com/has-unicode/-/has-unicode-2.0.1.tgz"
integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==
html-encoding-sniffer@^3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz"
integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==
dependencies:
whatwg-encoding "^2.0.0"
http-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz"
integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==
dependencies:
"@tootallnate/once" "2"
agent-base "6"
debug "4"
https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1:
version "5.0.1"
resolved "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz"
integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
dependencies:
agent-base "6"
debug "4"
iconv-lite@0.6.3:
version "0.6.3"
resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
ignore@^5.1.1:
version "5.3.2"
resolved "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz"
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz"
integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
dependencies:
once "^1.3.0"
wrappy "1"
inherits@^2.0.3, inherits@2:
version "2.0.4"
resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-plain-object@^3.0.0:
version "3.0.1"
resolved "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-3.0.1.tgz"
integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==
is-potential-custom-element-name@^1.0.1:
version "1.0.1"
resolved "https://registry.npmmirror.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz"
integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
jsdom@^20.0.1:
version "20.0.3"
resolved "https://registry.npmmirror.com/jsdom/-/jsdom-20.0.3.tgz"
integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==
dependencies:
abab "^2.0.6"
acorn "^8.8.1"
acorn-globals "^7.0.0"
cssom "^0.5.0"
cssstyle "^2.3.0"
data-urls "^3.0.2"
decimal.js "^10.4.2"
domexception "^4.0.0"
escodegen "^2.0.0"
form-data "^4.0.0"
html-encoding-sniffer "^3.0.0"
http-proxy-agent "^5.0.0"
https-proxy-agent "^5.0.1"
is-potential-custom-element-name "^1.0.1"
nwsapi "^2.2.2"
parse5 "^7.1.1"
saxes "^6.0.0"
symbol-tree "^3.2.4"
tough-cookie "^4.1.2"
w3c-xmlserializer "^4.0.0"
webidl-conversions "^7.0.0"
whatwg-encoding "^2.0.0"
whatwg-mimetype "^3.0.0"
whatwg-url "^11.0.0"
ws "^8.11.0"
xml-name-validator "^4.0.0"
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz"
integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==
optionalDependencies:
graceful-fs "^4.1.6"
jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz"
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
dependencies:
universalify "^2.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
make-dir@^3.1.0:
version "3.1.0"
resolved "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz"
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
dependencies:
semver "^6.0.0"
merge2@^1.2.3, merge2@^1.3.0:
version "1.4.1"
resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
micromatch@^4.0.4:
version "4.0.8"
resolved "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
dependencies:
braces "^3.0.3"
picomatch "^2.3.1"
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.12:
version "2.1.35"
resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
mimic-response@^2.0.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-2.1.0.tgz"
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
minimatch@^3.1.1:
version "3.1.2"
resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
dependencies:
brace-expansion "^1.1.7"
minipass@^3.0.0:
version "3.3.6"
resolved "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz"
integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==
dependencies:
yallist "^4.0.0"
minipass@^5.0.0:
version "5.0.0"
resolved "https://registry.npmmirror.com/minipass/-/minipass-5.0.0.tgz"
integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
minizlib@^2.1.1:
version "2.1.2"
resolved "https://registry.npmmirror.com/minizlib/-/minizlib-2.1.2.tgz"
integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
dependencies:
minipass "^3.0.0"
yallist "^4.0.0"
mkdirp@^1.0.3:
version "1.0.4"
resolved "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
ms@^2.1.3:
version "2.1.3"
resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
nan@^2.17.0:
version "2.22.0"
resolved "https://registry.npmmirror.com/nan/-/nan-2.22.0.tgz"
integrity sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==
nanoid@^3.3.7:
version "3.3.7"
resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
node-fetch@^2.6.7:
version "2.7.0"
resolved "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies:
whatwg-url "^5.0.0"
nopt@^5.0.0:
version "5.0.0"
resolved "https://registry.npmmirror.com/nopt/-/nopt-5.0.0.tgz"
integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
dependencies:
abbrev "1"
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
npmlog@^5.0.1:
version "5.0.1"
resolved "https://registry.npmmirror.com/npmlog/-/npmlog-5.0.1.tgz"
integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==
dependencies:
are-we-there-yet "^2.0.0"
console-control-strings "^1.1.0"
gauge "^3.0.0"
set-blocking "^2.0.0"
nwsapi@^2.2.2:
version "2.2.13"
resolved "https://registry.npmmirror.com/nwsapi/-/nwsapi-2.2.13.tgz"
integrity sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
once@^1.3.0, once@^1.3.1:
version "1.4.0"
resolved "https://registry.npmmirror.com/once/-/once-1.4.0.tgz"
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
dependencies:
wrappy "1"
parse5@^7.1.1:
version "7.1.2"
resolved "https://registry.npmmirror.com/parse5/-/parse5-7.1.2.tgz"
integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
dependencies:
entities "^4.4.0"
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
picocolors@^1.0.0, picocolors@^1.1.0:
version "1.1.0"
resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.0.tgz"
integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
postcss@^8.4.43:
version "8.4.47"
resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.47.tgz"
integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==
dependencies:
nanoid "^3.3.7"
picocolors "^1.1.0"
source-map-js "^1.2.1"
psl@^1.1.33:
version "1.9.0"
resolved "https://registry.npmmirror.com/psl/-/psl-1.9.0.tgz"
integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
punycode@^2.1.1:
version "2.3.1"
resolved "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
querystringify@^2.1.1:
version "2.2.0"
resolved "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
readable-stream@^3.6.0:
version "3.6.2"
resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz"
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
dependencies:
picomatch "^2.2.1"
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
dependencies:
glob "^7.1.3"
rollup-plugin-copy@^3.5.0:
version "3.5.0"
resolved "https://registry.npmmirror.com/rollup-plugin-copy/-/rollup-plugin-copy-3.5.0.tgz"
integrity sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==
dependencies:
"@types/fs-extra" "^8.0.1"
colorette "^1.1.0"
fs-extra "^8.1.0"
globby "10.0.1"
is-plain-object "^3.0.0"
rollup@^4.20.0:
version "4.24.0"
resolved "https://registry.npmmirror.com/rollup/-/rollup-4.24.0.tgz"
integrity sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==
dependencies:
"@types/estree" "1.0.6"
optionalDependencies:
"@rollup/rollup-android-arm-eabi" "4.24.0"
"@rollup/rollup-android-arm64" "4.24.0"
"@rollup/rollup-darwin-arm64" "4.24.0"
"@rollup/rollup-darwin-x64" "4.24.0"
"@rollup/rollup-linux-arm-gnueabihf" "4.24.0"
"@rollup/rollup-linux-arm-musleabihf" "4.24.0"
"@rollup/rollup-linux-arm64-gnu" "4.24.0"
"@rollup/rollup-linux-arm64-musl" "4.24.0"
"@rollup/rollup-linux-powerpc64le-gnu" "4.24.0"
"@rollup/rollup-linux-riscv64-gnu" "4.24.0"
"@rollup/rollup-linux-s390x-gnu" "4.24.0"
"@rollup/rollup-linux-x64-gnu" "4.24.0"
"@rollup/rollup-linux-x64-musl" "4.24.0"
"@rollup/rollup-win32-arm64-msvc" "4.24.0"
"@rollup/rollup-win32-ia32-msvc" "4.24.0"
"@rollup/rollup-win32-x64-msvc" "4.24.0"
fsevents "~2.3.2"
run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz"
integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
dependencies:
queue-microtask "^1.2.2"
safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
"safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
saxes@^6.0.0:
version "6.0.0"
resolved "https://registry.npmmirror.com/saxes/-/saxes-6.0.0.tgz"
integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==
dependencies:
xmlchars "^2.2.0"
semver@^6.0.0:
version "6.3.1"
resolved "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.3.5:
version "7.6.3"
resolved "https://registry.npmmirror.com/semver/-/semver-7.6.3.tgz"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz"
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
signal-exit@^3.0.0:
version "3.0.7"
resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
simple-concat@^1.0.0:
version "1.0.1"
resolved "https://registry.npmmirror.com/simple-concat/-/simple-concat-1.0.1.tgz"
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
simple-get@^3.0.3:
version "3.1.1"
resolved "https://registry.npmmirror.com/simple-get/-/simple-get-3.1.1.tgz"
integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==
dependencies:
decompress-response "^4.2.0"
once "^1.3.1"
simple-concat "^1.0.0"
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz"
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
dependencies:
safe-buffer "~5.2.0"
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
symbol-tree@^3.2.4:
version "3.2.4"
resolved "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz"
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
tar@^6.1.11:
version "6.2.1"
resolved "https://registry.npmmirror.com/tar/-/tar-6.2.1.tgz"
integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==
dependencies:
chownr "^2.0.0"
fs-minipass "^2.0.0"
minipass "^5.0.0"
minizlib "^2.1.1"
mkdirp "^1.0.3"
yallist "^4.0.0"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
tough-cookie@^4.1.2:
version "4.1.4"
resolved "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-4.1.4.tgz"
integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==
dependencies:
psl "^1.1.33"
punycode "^2.1.1"
universalify "^0.2.0"
url-parse "^1.5.3"
tr46@^3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/tr46/-/tr46-3.0.0.tgz"
integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==
dependencies:
punycode "^2.1.1"
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
typescript@^5.4.5:
version "5.6.3"
resolved "https://registry.npmmirror.com/typescript/-/typescript-5.6.3.tgz"
integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==
undici-types@~6.19.2:
version "6.19.8"
resolved "https://registry.npmmirror.com/undici-types/-/undici-types-6.19.8.tgz"
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
universalify@^0.2.0:
version "0.2.0"
resolved "https://registry.npmmirror.com/universalify/-/universalify-0.2.0.tgz"
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
universalify@^2.0.0:
version "2.0.1"
resolved "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz"
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
url-parse@^1.5.3:
version "1.5.10"
resolved "https://registry.npmmirror.com/url-parse/-/url-parse-1.5.10.tgz"
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"
util-deprecate@^1.0.1:
version "1.0.2"
resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
vite-plugin-copy@^0.1.6:
version "0.1.6"
resolved "https://registry.npmmirror.com/vite-plugin-copy/-/vite-plugin-copy-0.1.6.tgz"
integrity sha512-bqIaefZOE2Jx8P5wJuHKL5GzCERa/pcwdUQWaocyTNXgalN2xkxXH7LmqRJ34V2OlKF2F9E/zj0zITS7U6PpUg==
dependencies:
fast-glob "^3.2.7"
vite-plugin-static-copy@^2.0.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/vite-plugin-static-copy/-/vite-plugin-static-copy-2.1.0.tgz"
integrity sha512-n8lEOIVM00Y/zronm0RG8RdPyFd0SAAFR0sii3NWmgG3PSCyYMsvUNRQTlb3onp1XeMrKIDwCrPGxthKvqX9OQ==
dependencies:
chokidar "^3.5.3"
fast-glob "^3.2.11"
fs-extra "^11.1.0"
picocolors "^1.0.0"
vite@^5.4.8:
version "5.4.8"
resolved "https://registry.npmmirror.com/vite/-/vite-5.4.8.tgz"
integrity sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==
dependencies:
esbuild "^0.21.3"
postcss "^8.4.43"
rollup "^4.20.0"
optionalDependencies:
fsevents "~2.3.3"
w3c-xmlserializer@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz"
integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==
dependencies:
xml-name-validator "^4.0.0"
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
webidl-conversions@^7.0.0:
version "7.0.0"
resolved "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz"
integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
whatwg-encoding@^2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz"
integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==
dependencies:
iconv-lite "0.6.3"
whatwg-mimetype@^3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz"
integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==
whatwg-url@^11.0.0:
version "11.0.0"
resolved "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-11.0.0.tgz"
integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==
dependencies:
tr46 "^3.0.0"
webidl-conversions "^7.0.0"
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz"
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
wide-align@^1.1.2:
version "1.1.5"
resolved "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz"
integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==
dependencies:
string-width "^1.0.2 || 2 || 3 || 4"
wrappy@1:
version "1.0.2"
resolved "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
ws@^8.11.0:
version "8.18.0"
resolved "https://registry.npmmirror.com/ws/-/ws-8.18.0.tgz"
integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
xml-name-validator@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz"
integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==
xmlchars@^2.2.0:
version "2.2.0"
resolved "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册