提交 7b0100e0 编写于 作者: Q qq_41923622

Fri Jan 24 13:11:00 CST 2025 inscode

上级 aa50c4cb
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
basic.jpg

480.7 KB

export function setupCounter(element: HTMLButtonElement): void
...@@ -2,12 +2,19 @@ ...@@ -2,12 +2,19 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title> <title>Vite App</title>
</head> </head>
<body> <style>
html,body{
margin: 0;
border: 0;
height: 100%;
}
</style>
<body style="background-color: gainsboro;">
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.js"></script> <script type="module" src="src/main.ts"></script>
</body> </body>
</html> </html>
{ {
"name": "vuejs-with-vite", "name": "online-image-editor",
"version": "0.0.1", "private": true,
"scripts": { "version": "0.0.0",
"dev": "vite --host", "type": "module",
"build": "vite build", "files": [
"preview": "vite preview --port 4173" "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"
}, },
"dependencies": { "scripts": {
"guess": "^1.0.2", "dev": "vite",
"vue": "^3.2.37" "build": "tsc && vite build"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^3.0.1", "typescript": "^5.4.5",
"vite": "^5.0.1" "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"
} }
} }
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'
</script>
<template>
<header>
<img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
</div>
</header>
<main>
<TheWelcome />
</main>
</template>
<style scoped>
header {
line-height: 1.5;
}
.logo {
display: block;
margin: 0 auto 2rem;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
}
</style>
<?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
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
position: relative;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition: color 0.5s, background-color 0.5s;
line-height: 1.6;
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
<?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
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69" xmlns:v="https://vecta.io/nano"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
\ No newline at end of file
@import "./base.css";
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
font-weight: normal;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
}
<?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
<script setup>
defineProps({
msg: {
type: String,
required: true
}
})
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
You’ve successfully created a project with
<a target="_blank" href="https://vitejs.dev/">Vite</a> +
<a target="_blank" href="https://vuejs.org/">Vue 3</a>.
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
top: -10px;
}
h3 {
font-size: 1.2rem;
}
.greetings h1,
.greetings h3 {
text-align: center;
}
@media (min-width: 1024px) {
.greetings h1,
.greetings h3 {
text-align: left;
}
}
</style>
<script setup>
import WelcomeItem from './WelcomeItem.vue'
import DocumentationIcon from './icons/IconDocumentation.vue'
import ToolingIcon from './icons/IconTooling.vue'
import EcosystemIcon from './icons/IconEcosystem.vue'
import CommunityIcon from './icons/IconCommunity.vue'
import SupportIcon from './icons/IconSupport.vue'
</script>
<template>
<WelcomeItem>
<template #icon>
<DocumentationIcon />
</template>
<template #heading>Documentation</template>
Vue’s
<a target="_blank" href="https://vuejs.org/">official documentation</a>
provides you with all information you need to get started.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<ToolingIcon />
</template>
<template #heading>Tooling</template>
This project is served and bundled with
<a href="https://vitejs.dev/guide/features.html" target="_blank">Vite</a>. The recommended IDE
setup is <a href="https://code.visualstudio.com/" target="_blank">VSCode</a> +
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>. If you need to test
your components and web pages, check out
<a href="https://www.cypress.io/" target="_blank">Cypress</a> and
<a href="https://on.cypress.io/component" target="_blank"
>Cypress Component Testing</a
>.
<br />
More instructions are available in <code>README.md</code>.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<EcosystemIcon />
</template>
<template #heading>Ecosystem</template>
Get official tools and libraries for your project:
<a target="_blank" href="https://pinia.vuejs.org/">Pinia</a>,
<a target="_blank" href="https://router.vuejs.org/">Vue Router</a>,
<a target="_blank" href="https://test-utils.vuejs.org/">Vue Test Utils</a>, and
<a target="_blank" href="https://github.com/vuejs/devtools">Vue Dev Tools</a>. If you need more
resources, we suggest paying
<a target="_blank" href="https://github.com/vuejs/awesome-vue">Awesome Vue</a>
a visit.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<CommunityIcon />
</template>
<template #heading>Community</template>
Got stuck? Ask your question on
<a target="_blank" href="https://chat.vuejs.org">Vue Land</a>, our official Discord server, or
<a target="_blank" href="https://stackoverflow.com/questions/tagged/vue.js">StackOverflow</a>.
You should also subscribe to
<a target="_blank" href="https://news.vuejs.org">our mailing list</a> and follow the official
<a target="_blank" href="https://twitter.com/vuejs">@vuejs</a>
twitter account for latest news in the Vue world.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<SupportIcon />
</template>
<template #heading>Support Vue</template>
As an independent project, Vue relies on community backing for its sustainability. You can help
us by
<a target="_blank" href="https://vuejs.org/sponsor/">becoming a sponsor</a>.
</WelcomeItem>
</template>
<template>
<div class="item">
<i>
<slot name="icon"></slot>
</i>
<div class="details">
<h3>
<slot name="heading"></slot>
</h3>
<slot></slot>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>
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 { createApp } from 'vue'
import App from './App.vue'
import './assets/main.css'
createApp(App).mount('#app')
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 = {};
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.width = "400px";
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({
server: {
host: true,
allowedHosts: true
},
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.
先完成此消息的编辑!
想要评论请 注册