提交 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 { 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 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: {
}
})
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册