Compare commits
703 Commits
0.2.16.1
...
0.6.15-fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
731a5a504d | ||
|
|
008e697b74 | ||
|
|
fe1745e779 | ||
|
|
e6ac9be402 | ||
|
|
b427a9ed1b | ||
|
|
a4dc9210b3 | ||
|
|
fb681de1f5 | ||
|
|
3757622521 | ||
|
|
12265be7d4 | ||
|
|
df1aed7e04 | ||
|
|
c32c9d1ba1 | ||
|
|
26d75c9203 | ||
|
|
8d1e9fa8e9 | ||
|
|
ebc99e97af | ||
|
|
efe4aa0ec2 | ||
|
|
3f659af1e1 | ||
|
|
64209a6392 | ||
|
|
8d1e5dd2e9 | ||
|
|
d218122752 | ||
|
|
9af8afca22 | ||
|
|
002ec41ba8 | ||
|
|
ffff257f57 | ||
|
|
41d0b675a0 | ||
|
|
7601d6730d | ||
|
|
f0e9b76bb5 | ||
|
|
4ec33062a4 | ||
|
|
edc63b1293 | ||
|
|
ec83976818 | ||
|
|
393410757b | ||
|
|
f20748744a | ||
|
|
067131f76d | ||
|
|
f689d333f9 | ||
|
|
b3059bb6a3 | ||
|
|
b135f6a61c | ||
|
|
2a8b71497f | ||
|
|
726ebc5e88 | ||
|
|
2c56cd453c | ||
|
|
bf2f9b2697 | ||
|
|
fbd061e8b3 | ||
|
|
680320ba76 | ||
|
|
0d992bd6b1 | ||
|
|
c80eacc5e7 | ||
|
|
6d202b4b7d | ||
|
|
94478fe9f3 | ||
|
|
5745e4567b | ||
|
|
c6a3f4ac7b | ||
|
|
f6609b3050 | ||
|
|
4d69778a50 | ||
|
|
c8dfbd3b87 | ||
|
|
c0aab1e921 | ||
|
|
6efee6e859 | ||
|
|
dd82bf0879 | ||
|
|
2d41b5f9e6 | ||
|
|
7e7b6fae9d | ||
|
|
29020daaf0 | ||
|
|
6bcead7784 | ||
|
|
9625129691 | ||
|
|
765d0ee212 | ||
|
|
099d4cd78e | ||
|
|
d9a6981df4 | ||
|
|
bbf424c6d2 | ||
|
|
5cde3b76fe | ||
|
|
75af742053 | ||
|
|
69e192ea9d | ||
|
|
5a32c1d99d | ||
|
|
e81e0a5512 | ||
|
|
2fef76c55c | ||
|
|
49735950f2 | ||
|
|
c005455144 | ||
|
|
ca4afb5440 | ||
|
|
885647cedf | ||
|
|
dc8efbe3ef | ||
|
|
888b8e725a | ||
|
|
15d65db19d | ||
|
|
2ce0d4cd11 | ||
|
|
13f3c2c20c | ||
|
|
d707329526 | ||
|
|
69faa8bb3e | ||
|
|
86b184d5c1 | ||
|
|
2b6263acb4 | ||
|
|
187c940e56 | ||
|
|
4b59bec01c | ||
|
|
9427ee550c | ||
|
|
14975e117c | ||
|
|
d6254c0cc2 | ||
|
|
4a81ce9cc2 | ||
|
|
d17191c890 | ||
|
|
ff56fe3e68 | ||
|
|
6bdcec0fca | ||
|
|
27885aabe7 | ||
|
|
e345037f9b | ||
|
|
da5290e649 | ||
|
|
516676b484 | ||
|
|
20a8934da9 | ||
|
|
0075c44b29 | ||
|
|
ec7a8cdd43 | ||
|
|
1629bb7ccf | ||
|
|
35bff6ab57 | ||
|
|
ef9d8b0ea4 | ||
|
|
5d4c5703bb | ||
|
|
11bb519db8 | ||
|
|
1952280003 | ||
|
|
c845a0b7fa | ||
|
|
f1748e7e42 | ||
|
|
49063d257b | ||
|
|
803c83ac4f | ||
|
|
0bdf9f3add | ||
|
|
10ed3d4f7c | ||
|
|
8fc7f7d32c | ||
|
|
27a0efa4e0 | ||
|
|
7d227e901a | ||
|
|
080d7489e7 | ||
|
|
b11bd5a7ef | ||
|
|
80ae38d295 | ||
|
|
a4b7915196 | ||
|
|
4d4f1b993e | ||
|
|
b814bd35ca | ||
|
|
c57361a360 | ||
|
|
f8c2a62bd6 | ||
|
|
59241717f5 | ||
|
|
e90509cac9 | ||
|
|
8a8cc26c1d | ||
|
|
1d443b9f94 | ||
|
|
60a4f443a7 | ||
|
|
72c2540dcc | ||
|
|
18cec3b75a | ||
|
|
d4aae5268e | ||
|
|
99ca0738d4 | ||
|
|
408ca6d711 | ||
|
|
99dcb55397 | ||
|
|
e11b6647b8 | ||
|
|
78f5d4ec88 | ||
|
|
20eba7b29b | ||
|
|
4c9698a147 | ||
|
|
55da8eac83 | ||
|
|
17ea049393 | ||
|
|
d2e468287d | ||
|
|
ad6085890e | ||
|
|
cd4f1b1bd8 | ||
|
|
a2acf810cb | ||
|
|
14bd7c3705 | ||
|
|
5d0c9dcab1 | ||
|
|
def2f02eea | ||
|
|
a74a60c22d | ||
|
|
74d37f2cbc | ||
|
|
6ca9a116c2 | ||
|
|
8e59677623 | ||
|
|
d06f57a5dc | ||
|
|
07be48d342 | ||
|
|
5a5c7702f5 | ||
|
|
7e1a43143d | ||
|
|
5ff9137745 | ||
|
|
8e30d4a94f | ||
|
|
4b2ebb2e1c | ||
|
|
8c79ffd723 | ||
|
|
839c79405f | ||
|
|
d645adfd37 | ||
|
|
1b1551c6e3 | ||
|
|
78638dc291 | ||
|
|
e972924143 | ||
|
|
8871d8727b | ||
|
|
55945a1a2c | ||
|
|
d5e4044fb2 | ||
|
|
4ee458c509 | ||
|
|
a76ec0dad8 | ||
|
|
f2b247e85c | ||
|
|
3aed09a8c3 | ||
|
|
17b7a023ba | ||
|
|
10cb36829f | ||
|
|
90a55bb995 | ||
|
|
08a37d8971 | ||
|
|
89983f9f47 | ||
|
|
b71a80e383 | ||
|
|
2bf146816b | ||
|
|
daf9888da4 | ||
|
|
b42cee7a2f | ||
|
|
9ecb199608 | ||
|
|
adccef5699 | ||
|
|
94230f8ec6 | ||
|
|
a161661c6b | ||
|
|
08b971cd9a | ||
|
|
b64c8f132b | ||
|
|
d6181591c5 | ||
|
|
e093cb1741 | ||
|
|
a63a92c423 | ||
|
|
2c4f065626 | ||
|
|
ae5d4dd2a6 | ||
|
|
e574883e5f | ||
|
|
1bf60c49c7 | ||
|
|
ddc173cf84 | ||
|
|
2c71c7d102 | ||
|
|
f6cb08bdaa | ||
|
|
05728de21b | ||
|
|
b51027f641 | ||
|
|
ea4fdf8290 | ||
|
|
55796b4e39 | ||
|
|
e534c3138d | ||
|
|
e185abd223 | ||
|
|
3b73f72866 | ||
|
|
0db2f47133 | ||
|
|
80c7ec0fac | ||
|
|
780ce363de | ||
|
|
eaa8929457 | ||
|
|
ad0f62a5ac | ||
|
|
31f21ce013 | ||
|
|
7a20ce2f79 | ||
|
|
b98e7a97ec | ||
|
|
0bb50b3371 | ||
|
|
a03091b28c | ||
|
|
6c33984b8c | ||
|
|
28a4be0631 | ||
|
|
b8a3be7a62 | ||
|
|
259d4028f3 | ||
|
|
fb1251afc1 | ||
|
|
06e3fd428a | ||
|
|
a48e52f1f4 | ||
|
|
f8a345d8de | ||
|
|
d900c2f122 | ||
|
|
443a714549 | ||
|
|
74618a8a2b | ||
|
|
a25bd4c556 | ||
|
|
efe205ae70 | ||
|
|
a0d7473b1f | ||
|
|
7821781f20 | ||
|
|
b90d4ddf8a | ||
|
|
314562c167 | ||
|
|
5f0a9a7ce2 | ||
|
|
98e27841ad | ||
|
|
1b6467728c | ||
|
|
4bb349b2df | ||
|
|
b262336f08 | ||
|
|
2b59087461 | ||
|
|
66c9805efc | ||
|
|
710128901a | ||
|
|
61be0f7ac4 | ||
|
|
7289b3a0ad | ||
|
|
25243e2053 | ||
|
|
060a448cd5 | ||
|
|
bdb6078df6 | ||
|
|
749a4d0e81 | ||
|
|
1749705694 | ||
|
|
eec736be4d | ||
|
|
ffdf53941a | ||
|
|
5676e952f3 | ||
|
|
e049ee6260 | ||
|
|
f1355c9d2a | ||
|
|
d696e0fdc1 | ||
|
|
c8d2f284fd | ||
|
|
aa8ecd4f60 | ||
|
|
2323fe9bc0 | ||
|
|
b9a0b16fc8 | ||
|
|
b9c340afbf | ||
|
|
ee98d7128b | ||
|
|
d37febe306 | ||
|
|
5de97f05b3 | ||
|
|
662447bc69 | ||
|
|
51c1c46287 | ||
|
|
3b03d9798b | ||
|
|
17fbef810c | ||
|
|
e798975a9f | ||
|
|
2af65e322b | ||
|
|
23f09f9b4d | ||
|
|
d69668a488 | ||
|
|
5353888965 | ||
|
|
b33fd1908a | ||
|
|
97583ffcba | ||
|
|
360eca620e | ||
|
|
6d0682e821 | ||
|
|
834bdf37c3 | ||
|
|
e3d31f69bf | ||
|
|
ac55415de1 | ||
|
|
4d91be5be6 | ||
|
|
ee467cb155 | ||
|
|
6281f2360c | ||
|
|
cd8af6ae06 | ||
|
|
45cc199d7f | ||
|
|
b04469649a | ||
|
|
ee57dbdcc5 | ||
|
|
bca744f6fc | ||
|
|
442408dacf | ||
|
|
7297f0a082 | ||
|
|
f0d90941fb | ||
|
|
830962c044 | ||
|
|
eeec71dc65 | ||
|
|
9bd87a22f3 | ||
|
|
88c9f22a15 | ||
|
|
7380fc60d5 | ||
|
|
a178b99d73 | ||
|
|
af9ee04aaf | ||
|
|
01dc98f1f8 | ||
|
|
cbd07246bd | ||
|
|
5bb23ca738 | ||
|
|
0886ba7698 | ||
|
|
a65cffa58b | ||
|
|
02e2d432dd | ||
|
|
c8d23cab40 | ||
|
|
244ced83bc | ||
|
|
5c9c3d7934 | ||
|
|
4ca6713675 | ||
|
|
3d18404fd6 | ||
|
|
98dda26bf8 | ||
|
|
cd4c5ecd83 | ||
|
|
fdc0017ccb | ||
|
|
20a5c12bbb | ||
|
|
4eacec125e | ||
|
|
34322ba6d1 | ||
|
|
8210151a7b | ||
|
|
1e00088608 | ||
|
|
ee59b8002a | ||
|
|
b2ca5d0fba | ||
|
|
470604e567 | ||
|
|
f5f665ec0a | ||
|
|
5e865a4e33 | ||
|
|
38ad33b604 | ||
|
|
e1b4146171 | ||
|
|
65151f4b0a | ||
|
|
942706fb63 | ||
|
|
5f4492d4b7 | ||
|
|
ace1f62a40 | ||
|
|
706c88c7d5 | ||
|
|
4512fb16eb | ||
|
|
e446ff12e7 | ||
|
|
4318646abe | ||
|
|
2cbfe4f0e7 | ||
|
|
b7910c4665 | ||
|
|
fc1ba24834 | ||
|
|
cf16937160 | ||
|
|
c3393abed6 | ||
|
|
2abff3e21b | ||
|
|
e39a94c5e2 | ||
|
|
a6fff7f7a3 | ||
|
|
e584081b41 | ||
|
|
b91dde8084 | ||
|
|
077478d654 | ||
|
|
2be97cc1a0 | ||
|
|
0f7dc949a4 | ||
|
|
c7e91cc9eb | ||
|
|
6eacfab9c2 | ||
|
|
e36a408275 | ||
|
|
abda5b7d06 | ||
|
|
f815f71dd7 | ||
|
|
fa2c5b420c | ||
|
|
4c19bc76a7 | ||
|
|
d08a317920 | ||
|
|
bd805836cd | ||
|
|
e804a8f2f7 | ||
|
|
8bf876d446 | ||
|
|
f2521f663e | ||
|
|
e676bff453 | ||
|
|
8f2cc72d3c | ||
|
|
ec22656bee | ||
|
|
4acf8ba2ac | ||
|
|
d45a18904e | ||
|
|
9fc2dbabd4 | ||
|
|
b83b81f52e | ||
|
|
d1e2db993e | ||
|
|
ab931901e2 | ||
|
|
9879a25f9b | ||
|
|
16fb8eb092 | ||
|
|
de859927ed | ||
|
|
7bde59f664 | ||
|
|
be9668c7b8 | ||
|
|
95fe3189d5 | ||
|
|
9c60857c6a | ||
|
|
3b7cea9ee3 | ||
|
|
3f081e5021 | ||
|
|
d959420d6e | ||
|
|
79d81b92e6 | ||
|
|
940c60f23d | ||
|
|
965ab8151e | ||
|
|
87d55b31ca | ||
|
|
bb68575aca | ||
|
|
e561e804be | ||
|
|
5a8c3aa9d3 | ||
|
|
f84639debd | ||
|
|
de77a2b613 | ||
|
|
3825c3769f | ||
|
|
876908e922 | ||
|
|
25ecde8948 | ||
|
|
2de0334e3b | ||
|
|
1e32338d23 | ||
|
|
21f487321a | ||
|
|
750af31996 | ||
|
|
dc1aaee75d | ||
|
|
2122fa59d7 | ||
|
|
5b7c65adad | ||
|
|
fabf4535a8 | ||
|
|
04566080e0 | ||
|
|
0f9d9cdb21 | ||
|
|
11538d1789 | ||
|
|
49bae6cc6c | ||
|
|
9644ba0c8d | ||
|
|
ac2df3cb2e | ||
|
|
aad1f911c8 | ||
|
|
27b50bf4ae | ||
|
|
2d22837e32 | ||
|
|
866f2071ce | ||
|
|
9cb35eac29 | ||
|
|
6ff63d9d56 | ||
|
|
43539df25b | ||
|
|
cbafc24670 | ||
|
|
cc83a18b18 | ||
|
|
5ae931da92 | ||
|
|
853b999a7d | ||
|
|
505622f3dc | ||
|
|
0a5b41e3e0 | ||
|
|
8630c47b26 | ||
|
|
6105fd6e3d | ||
|
|
d7b1c4e4fe | ||
|
|
6ccafaa771 | ||
|
|
6b15e469a2 | ||
|
|
5675e29df3 | ||
|
|
52d094a7c7 | ||
|
|
633ed68f92 | ||
|
|
388594e29a | ||
|
|
af148fef27 | ||
|
|
983e55bd1d | ||
|
|
69dab99bf7 | ||
|
|
c94f459ff9 | ||
|
|
cd3baf411b | ||
|
|
042911371c | ||
|
|
8e2fb72309 | ||
|
|
77aacccdad | ||
|
|
5413c867e3 | ||
|
|
4fc7eb084e | ||
|
|
a9b04312d8 | ||
|
|
4cd9b66653 | ||
|
|
9b7b41597f | ||
|
|
e56ff8fdf2 | ||
|
|
8d9299aed7 | ||
|
|
f8506cb75b | ||
|
|
c90ee9e892 | ||
|
|
4d21b84882 | ||
|
|
331f0bdf97 | ||
|
|
0a4898697d | ||
|
|
79ae08fc9a | ||
|
|
1795773af9 | ||
|
|
747a781ad8 | ||
|
|
fcfcb1c3d1 | ||
|
|
d412ae8cce | ||
|
|
d834b76d42 | ||
|
|
3f9da8940f | ||
|
|
26d5f69af2 | ||
|
|
6efe4a3fd6 | ||
|
|
2b4ab4a322 | ||
|
|
cca42d1f89 | ||
|
|
724fef87b1 | ||
|
|
d50c0e4cd5 | ||
|
|
c18c037642 | ||
|
|
cda1da5fd0 | ||
|
|
eba1aa3a37 | ||
|
|
81018bb615 | ||
|
|
5fa0ff7b5c | ||
|
|
cabe286ebb | ||
|
|
e337da088b | ||
|
|
de97ea9e75 | ||
|
|
cc331065eb | ||
|
|
9a8e630654 | ||
|
|
17ab977efb | ||
|
|
6bd10d9451 | ||
|
|
5313b9b69c | ||
|
|
8dcfdcc44a | ||
|
|
67422df3ff | ||
|
|
2ad7536eb7 | ||
|
|
6f56e5c4e6 | ||
|
|
5ae8ebe590 | ||
|
|
6ecb97e4e5 | ||
|
|
c8f938dd3e | ||
|
|
0d29e29162 | ||
|
|
be1b3dffce | ||
|
|
3ba2dbe415 | ||
|
|
19a96c92a9 | ||
|
|
c265e3e437 | ||
|
|
7434ac2648 | ||
|
|
63d73a73aa | ||
|
|
00f9258a4d | ||
|
|
7087b43d39 | ||
|
|
945a78b7b1 | ||
|
|
55acc19ab8 | ||
|
|
410f0be05e | ||
|
|
2d7c091071 | ||
|
|
9bf58a54ce | ||
|
|
231adeea44 | ||
|
|
0ea618af39 | ||
|
|
f66297ff99 | ||
|
|
3113bf2e1f | ||
|
|
8c114dac02 | ||
|
|
06dba79272 | ||
|
|
8441986ca7 | ||
|
|
c8b50908e1 | ||
|
|
7e11fde892 | ||
|
|
3d9f3bd7a8 | ||
|
|
46e11649b0 | ||
|
|
161ef7590c | ||
|
|
ca660a3c74 | ||
|
|
7a24872331 | ||
|
|
eb4ea9fb3a | ||
|
|
64228c02ff | ||
|
|
f184a5d948 | ||
|
|
0bf5b2d6f7 | ||
|
|
74a52ea386 | ||
|
|
9914eef5b9 | ||
|
|
f547f741f2 | ||
|
|
c26149fa42 | ||
|
|
b2aa3648eb | ||
|
|
b997604ab2 | ||
|
|
c6929b82ad | ||
|
|
b40da53aef | ||
|
|
1e5dfd97e1 | ||
|
|
97bcc22abd | ||
|
|
bd655839cb | ||
|
|
a53a4e8e1d | ||
|
|
eb01646747 | ||
|
|
d27eee0fae | ||
|
|
92ee241ed8 | ||
|
|
660906e4c5 | ||
|
|
971f0d3446 | ||
|
|
63eccede7f | ||
|
|
5bd6adb488 | ||
|
|
fbdc5e0f39 | ||
|
|
5e994322fe | ||
|
|
fd0a594b56 | ||
|
|
d7b93ba0a0 | ||
|
|
c14fb5ad9d | ||
|
|
4d82dfdf9e | ||
|
|
7d3f08bed0 | ||
|
|
60597616ec | ||
|
|
6d758a988c | ||
|
|
503506dfd4 | ||
|
|
df32655321 | ||
|
|
1c7edf47e3 | ||
|
|
4f5f9543f7 | ||
|
|
0d3c1b7417 | ||
|
|
e634fee753 | ||
|
|
81c17dc643 | ||
|
|
cb106c7fac | ||
|
|
5a9189b7fe | ||
|
|
160ed0d0e4 | ||
|
|
ed92286178 | ||
|
|
a837a6fb64 | ||
|
|
47328236f7 | ||
|
|
566147c530 | ||
|
|
54fad1ee08 | ||
|
|
8c8e283a88 | ||
|
|
4553bc37f5 | ||
|
|
41b0b21354 | ||
|
|
39b55b0cd7 | ||
|
|
ede01c069e | ||
|
|
aa56e53c4d | ||
|
|
3a723a15bf | ||
|
|
1b2da4eb72 | ||
|
|
6f351d4cee | ||
|
|
dba93d4363 | ||
|
|
b46a94bd96 | ||
|
|
fadd8217e8 | ||
|
|
b0d898054e | ||
|
|
3e84892a28 | ||
|
|
f663f8d60a | ||
|
|
0aeee0ff28 | ||
|
|
140e9cf893 | ||
|
|
89e426e522 | ||
|
|
c77062b660 | ||
|
|
bca2912390 | ||
|
|
ed7177e29d | ||
|
|
12ca3ed556 | ||
|
|
d7de86209c | ||
|
|
e53c6cd559 | ||
|
|
352711c871 | ||
|
|
d304ebc544 | ||
|
|
27291fe5c9 | ||
|
|
f714e47722 | ||
|
|
ce135dd111 | ||
|
|
fb4bfedef4 | ||
|
|
5cbe5d2906 | ||
|
|
b6c15164ac | ||
|
|
1960e96a19 | ||
|
|
7a0fd5adfb | ||
|
|
9ae40cff32 | ||
|
|
0070cab9f1 | ||
|
|
79ae876eeb | ||
|
|
ca35b84308 | ||
|
|
3b4f386900 | ||
|
|
34ef1e908e | ||
|
|
6a3f016920 | ||
|
|
d1ab67cd4c | ||
|
|
0d81f9ff9c | ||
|
|
471bd3c5e5 | ||
|
|
5d4cf8a3c3 | ||
|
|
5a5062ecaf | ||
|
|
13c4b51f69 | ||
|
|
a3ddcc93e5 | ||
|
|
365e51e2e9 | ||
|
|
fd096c4444 | ||
|
|
4c1786e127 | ||
|
|
bfeb59d43f | ||
|
|
aa998b1829 | ||
|
|
c8f5c94683 | ||
|
|
2aea7a3c88 | ||
|
|
9f16691cd6 | ||
|
|
8363819933 | ||
|
|
7d6149dbdf | ||
|
|
b088c40101 | ||
|
|
5b5aab1c9e | ||
|
|
13a4b12ad4 | ||
|
|
3e59d84e6b | ||
|
|
cd987a382a | ||
|
|
fdada41327 | ||
|
|
46d44e6753 | ||
|
|
d63d9873ac | ||
|
|
7eeb7ab51b | ||
|
|
7a2075df51 | ||
|
|
ba5ff54b9a | ||
|
|
8af44b2d45 | ||
|
|
72c989ae11 | ||
|
|
861309d517 | ||
|
|
2f0d64cf41 | ||
|
|
5df8a28403 | ||
|
|
3c9940e076 | ||
|
|
0b11aff105 | ||
|
|
3e4fe06197 | ||
|
|
8bf23f8397 | ||
|
|
10d04549fc | ||
|
|
b4193d50a3 | ||
|
|
0620d31d0a | ||
|
|
a804a5e2fa | ||
|
|
06daffbaab | ||
|
|
b901c08156 | ||
|
|
e27eb63627 | ||
|
|
990a4e5c4c | ||
|
|
7f79d4881d | ||
|
|
2d8643015f | ||
|
|
30f7713af1 | ||
|
|
7ab9db4bbd | ||
|
|
fa7548afaf | ||
|
|
676efa792a | ||
|
|
9ffa1e2744 | ||
|
|
1108b29da0 | ||
|
|
b00d5f6adb | ||
|
|
c6438668f2 | ||
|
|
608adb21e1 | ||
|
|
495cb4c62a | ||
|
|
3048cba1ae | ||
|
|
5b1f5f803e | ||
|
|
0d79e28ec9 | ||
|
|
806be0b537 | ||
|
|
c15b9be7ef | ||
|
|
f6ac2c80ed | ||
|
|
5f19509079 | ||
|
|
4677b11dfb | ||
|
|
b3e9797b0e | ||
|
|
f72deb0478 | ||
|
|
455e97074f | ||
|
|
3e52fa6585 | ||
|
|
48172bc035 | ||
|
|
09d1f82021 | ||
|
|
786e183091 | ||
|
|
0ebac39716 | ||
|
|
10b8a33ab7 | ||
|
|
72b9001b56 | ||
|
|
1efabd44ec | ||
|
|
5e66bd29ff | ||
|
|
2f1114b722 | ||
|
|
4428028146 | ||
|
|
e660a74630 | ||
|
|
8f29d63441 | ||
|
|
98afa6eb5b | ||
|
|
c06971987d | ||
|
|
e385ecf35d | ||
|
|
62fb0b15e0 | ||
|
|
19da1a4ff3 | ||
|
|
a798a40fab | ||
|
|
7982a1373f | ||
|
|
a812637cf0 | ||
|
|
53c4d3945a | ||
|
|
2e7519cf29 | ||
|
|
acad210d57 | ||
|
|
ba5807932e | ||
|
|
59d572ae18 | ||
|
|
14aa8659ee | ||
|
|
59c8ed4ef8 | ||
|
|
b886a0572d | ||
|
|
42755cac8a | ||
|
|
ab1306357e | ||
|
|
fa506f4212 | ||
|
|
364aed858f | ||
|
|
58703597e3 | ||
|
|
5d4b42d519 | ||
|
|
cd55db7ed7 | ||
|
|
ca9b3678dc | ||
|
|
395e802b6b | ||
|
|
f38b92a2e2 | ||
|
|
5c6fef71bd | ||
|
|
f3780baedc | ||
|
|
388656f6e0 | ||
|
|
df60f103cc | ||
|
|
7a977d74dc | ||
|
|
7336c57e80 | ||
|
|
66963ca3ac | ||
|
|
1bb1afd1b7 | ||
|
|
52cfc40c24 | ||
|
|
debb058889 | ||
|
|
3d5e3ac9a0 | ||
|
|
a06cb2e031 |
2
.gitignore
vendored
@@ -1,3 +1,3 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
package-lock.json
|
||||
dist_electron
|
||||
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021-2023 The MindMap Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 254 KiB |
70
index.html
@@ -1 +1,69 @@
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>一个简单的web思维导图实现</title><link href="dist/js/chunk-2d20ec02.81d632f4.js" rel="prefetch"><link href="dist/js/chunk-2d216b67.228f2009.js" rel="prefetch"><link href="dist/js/chunk-35b0a040.cb76da7d.js" rel="prefetch"><link href="dist/css/app.80644c67.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.faba1249.css" rel="preload" as="style"><link href="dist/js/app.e664c97c.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.0c7365d5.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.faba1249.css" rel="stylesheet"><link href="dist/css/app.80644c67.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="dist/js/chunk-vendors.0c7365d5.js"></script><script src="dist/js/app.e664c97c.js"></script></body></html>
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><link rel="icon" href="dist/logo.ico"><title>思绪思维导图</title><script>// 自定义静态资源的路径
|
||||
window.externalPublicPath = './dist/'
|
||||
// 接管应用
|
||||
window.takeOverApp = false</script><link href="dist/css/chunk-vendors.css?c6f9dda3bfa8385d51f4" rel="stylesheet"><link href="dist/css/app.css?c6f9dda3bfa8385d51f4" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
mindMapData: {
|
||||
root:{
|
||||
"data": {
|
||||
"text": "根节点"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
theme:{
|
||||
"template":"avocado",
|
||||
"config":{}
|
||||
},
|
||||
layout:"logicalStructure",
|
||||
config: {},
|
||||
view: null,
|
||||
},
|
||||
lang: 'zh',
|
||||
localConfig: null
|
||||
})
|
||||
}, 200)
|
||||
})
|
||||
}
|
||||
const setTakeOverAppMethods = (data) => {
|
||||
window.takeOverAppMethods = {}
|
||||
// 获取思维导图数据的函数
|
||||
window.takeOverAppMethods.getMindMapData = () => {
|
||||
return data.mindMapData
|
||||
}
|
||||
// 保存思维导图数据的函数
|
||||
window.takeOverAppMethods.saveMindMapData = (data) => {
|
||||
console.log(data)
|
||||
}
|
||||
// 获取语言的函数
|
||||
window.takeOverAppMethods.getLanguage = () => {
|
||||
return data.lang
|
||||
}
|
||||
// 保存语言的函数
|
||||
window.takeOverAppMethods.saveLanguage = (lang) => {
|
||||
console.log(lang)
|
||||
}
|
||||
// 获取本地配置的函数
|
||||
window.takeOverAppMethods.getLocalConfig = () => {
|
||||
return data.localConfig
|
||||
}
|
||||
// 保存本地配置的函数
|
||||
window.takeOverAppMethods.saveLocalConfig = (config) => {
|
||||
console.log(config)
|
||||
}
|
||||
}
|
||||
window.onload = async () => {
|
||||
if (!window.takeOverApp) return
|
||||
// 请求数据
|
||||
const data = await getDataFromBackend()
|
||||
// 设置全局的方法
|
||||
setTakeOverAppMethods(data)
|
||||
// 思维导图实例创建完成事件
|
||||
window.$bus.$on('app_inited', (mindMap) => {
|
||||
console.log(mindMap)
|
||||
})
|
||||
// 可以通过window.$bus.$on()来监听应用的一些事件
|
||||
// 实例化页面
|
||||
window.initApp()
|
||||
}</script><script src="dist/js/chunk-vendors.js?c6f9dda3bfa8385d51f4"></script><script src="dist/js/app.js?c6f9dda3bfa8385d51f4"></script></body></html>
|
||||
BIN
qrcode.jpg
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
simple-mind-map/dist/img/blueSky.3c7f8ccb.jpg
vendored
|
Before Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 25 KiB |
BIN
simple-mind-map/dist/img/classic.733f273c.jpg
vendored
|
Before Width: | Height: | Size: 23 KiB |
BIN
simple-mind-map/dist/img/classic2.cdfe2a8d.jpg
vendored
|
Before Width: | Height: | Size: 10 KiB |
BIN
simple-mind-map/dist/img/classic3.19d6c347.jpg
vendored
|
Before Width: | Height: | Size: 11 KiB |
BIN
simple-mind-map/dist/img/classic4.087902fc.jpg
vendored
|
Before Width: | Height: | Size: 18 KiB |
BIN
simple-mind-map/dist/img/classicBlue.4b8243c6.jpg
vendored
|
Before Width: | Height: | Size: 8.4 KiB |
BIN
simple-mind-map/dist/img/classicGreen.c2ae7bde.jpg
vendored
|
Before Width: | Height: | Size: 8.7 KiB |
BIN
simple-mind-map/dist/img/dark.894c1d36.jpg
vendored
|
Before Width: | Height: | Size: 9.5 KiB |
BIN
simple-mind-map/dist/img/dark2.c49dc11c.jpg
vendored
|
Before Width: | Height: | Size: 7.6 KiB |
BIN
simple-mind-map/dist/img/default.1312a3ba.jpg
vendored
|
Before Width: | Height: | Size: 9.7 KiB |
BIN
simple-mind-map/dist/img/earthYellow.c35e546d.jpg
vendored
|
Before Width: | Height: | Size: 9.1 KiB |
BIN
simple-mind-map/dist/img/freshGreen.0e344e3e.jpg
vendored
|
Before Width: | Height: | Size: 9.5 KiB |
BIN
simple-mind-map/dist/img/freshRed.1c5bde77.jpg
vendored
|
Before Width: | Height: | Size: 9.2 KiB |
BIN
simple-mind-map/dist/img/gold.3093b3c8.jpg
vendored
|
Before Width: | Height: | Size: 7.2 KiB |
BIN
simple-mind-map/dist/img/greenLeaf.6789e8fc.jpg
vendored
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 21 KiB |
BIN
simple-mind-map/dist/img/mindMap.223b38aa.jpg
vendored
|
Before Width: | Height: | Size: 25 KiB |
BIN
simple-mind-map/dist/img/minions.c2a93f9e.jpg
vendored
|
Before Width: | Height: | Size: 8.3 KiB |
BIN
simple-mind-map/dist/img/mint.7933f60a.jpg
vendored
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 26 KiB |
BIN
simple-mind-map/dist/img/pinkGrape.32c2587b.jpg
vendored
|
Before Width: | Height: | Size: 6.9 KiB |
BIN
simple-mind-map/dist/img/romanticPurple.7607e58a.jpg
vendored
|
Before Width: | Height: | Size: 9.1 KiB |
BIN
simple-mind-map/dist/img/skyGreen.4cfa829a.jpg
vendored
|
Before Width: | Height: | Size: 7.0 KiB |
BIN
simple-mind-map/dist/img/vitalityOrange.5dd9014f.jpg
vendored
|
Before Width: | Height: | Size: 7.2 KiB |
@@ -900,6 +900,17 @@ const data5 = {
|
||||
}
|
||||
}
|
||||
|
||||
// 富文本数据v0.4.0+,需要使用RichText插件才支持富文本编辑
|
||||
const richTextData = {
|
||||
"root": {
|
||||
"data": {
|
||||
"text": "<a href='http://lxqnsys.com/' target='_blank'>理想去年实验室</a>",
|
||||
"richText": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
}
|
||||
|
||||
const rootData = {
|
||||
"root": {
|
||||
"data": {
|
||||
@@ -925,5 +936,6 @@ export default {
|
||||
"layout": "logicalStructure",
|
||||
// "layout": "mindMap",
|
||||
// "layout": "catalogOrganization"
|
||||
// "layout": "organizationStructure"
|
||||
// "layout": "organizationStructure",
|
||||
"config": {}
|
||||
}
|
||||
@@ -31,6 +31,12 @@
|
||||
"isActive": false
|
||||
},
|
||||
"children": []
|
||||
}, {
|
||||
"data": {
|
||||
"text": "<a href='http://lxqnsys.com/' target='_blank'>理想去年实验室</a>",
|
||||
"richText": true
|
||||
},
|
||||
"children": []
|
||||
}]
|
||||
}]
|
||||
},
|
||||
@@ -62,5 +68,6 @@
|
||||
"sx": 0,
|
||||
"sy": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"config": {}
|
||||
}
|
||||
46
simple-mind-map/full.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import MindMap from './index'
|
||||
import MiniMap from './src/plugins/MiniMap.js'
|
||||
import Watermark from './src/plugins/Watermark.js'
|
||||
import KeyboardNavigation from './src/plugins/KeyboardNavigation.js'
|
||||
import ExportXMind from './src/plugins/ExportXMind.js'
|
||||
import ExportPDF from './src/plugins/ExportPDF.js'
|
||||
import Export from './src/plugins/Export.js'
|
||||
import Drag from './src/plugins/Drag.js'
|
||||
import Select from './src/plugins/Select.js'
|
||||
import AssociativeLine from './src/plugins/AssociativeLine'
|
||||
import RichText from './src/plugins/RichText'
|
||||
import NodeImgAdjust from './src/plugins/NodeImgAdjust.js'
|
||||
import TouchEvent from './src/plugins/TouchEvent.js'
|
||||
import Search from './src/plugins/Search.js'
|
||||
import Painter from './src/plugins/Painter.js'
|
||||
import xmind from './src/parse/xmind.js'
|
||||
import markdown from './src/parse/markdown.js'
|
||||
import icons from './src/svg/icons.js'
|
||||
import * as constants from './src/constants/constant.js'
|
||||
import themes from './src/themes/index.js'
|
||||
import * as defaultTheme from './src/themes/default.js'
|
||||
|
||||
MindMap.xmind = xmind
|
||||
MindMap.markdown = markdown
|
||||
MindMap.iconList = icons.nodeIconList
|
||||
MindMap.constants = constants
|
||||
MindMap.themes = themes
|
||||
MindMap.defaultTheme = defaultTheme
|
||||
|
||||
MindMap
|
||||
.usePlugin(MiniMap)
|
||||
.usePlugin(Watermark)
|
||||
.usePlugin(Drag)
|
||||
.usePlugin(KeyboardNavigation)
|
||||
.usePlugin(ExportXMind)
|
||||
.usePlugin(ExportPDF)
|
||||
.usePlugin(Export)
|
||||
.usePlugin(Select)
|
||||
.usePlugin(AssociativeLine)
|
||||
.usePlugin(RichText)
|
||||
.usePlugin(TouchEvent)
|
||||
.usePlugin(NodeImgAdjust)
|
||||
.usePlugin(Search)
|
||||
.usePlugin(Painter)
|
||||
|
||||
export default MindMap
|
||||
@@ -1,92 +1,52 @@
|
||||
import View from './src/View'
|
||||
import Event from './src/Event'
|
||||
import Render from './src/Render'
|
||||
import View from './src/core/view/View'
|
||||
import Event from './src/core/event/Event'
|
||||
import Render from './src/core/render/Render'
|
||||
import merge from 'deepmerge'
|
||||
import theme from './src/themes'
|
||||
import Style from './src/Style'
|
||||
import KeyCommand from './src/KeyCommand'
|
||||
import Command from './src/Command'
|
||||
import BatchExecution from './src/BatchExecution'
|
||||
import Export from './src/Export'
|
||||
import Select from './src/Select'
|
||||
import Drag from './src/Drag'
|
||||
import MiniMap from './src/MiniMap'
|
||||
import { layoutValueList } from './src/utils/constant'
|
||||
import Style from './src/core/render/node/Style'
|
||||
import KeyCommand from './src/core/command/KeyCommand'
|
||||
import Command from './src/core/command/Command'
|
||||
import BatchExecution from './src/utils/BatchExecution'
|
||||
import {
|
||||
layoutValueList,
|
||||
CONSTANTS,
|
||||
commonCaches,
|
||||
ERROR_TYPES
|
||||
} from './src/constants/constant'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import xmind from './src/parse/xmind'
|
||||
import { simpleDeepClone } from './src/utils'
|
||||
import { simpleDeepClone, getType } from './src/utils'
|
||||
import defaultTheme, {
|
||||
checkIsNodeSizeIndependenceConfig
|
||||
} from './src/themes/default'
|
||||
import { defaultOpt } from './src/constants/defaultOptions'
|
||||
|
||||
// 默认选项配置
|
||||
const defaultOpt = {
|
||||
// 是否只读
|
||||
readonly: false,
|
||||
// 布局
|
||||
layout: 'logicalStructure',
|
||||
// 主题
|
||||
theme: 'default', // 内置主题:default(默认主题)
|
||||
// 主题配置,会和所选择的主题进行合并
|
||||
themeConfig: {},
|
||||
// 放大缩小的增量比例
|
||||
scaleRatio: 0.1,
|
||||
// 最多显示几个标签
|
||||
maxTag: 5,
|
||||
// 导出图片时的内边距
|
||||
exportPadding: 20,
|
||||
// 展开收缩按钮尺寸
|
||||
expandBtnSize: 20,
|
||||
// 节点里图片和文字的间距
|
||||
imgTextMargin: 5,
|
||||
// 节点里各种文字信息的间距,如图标和文字的间距
|
||||
textContentMargin: 2,
|
||||
// 多选节点时鼠标移动到边缘时的画布移动偏移量
|
||||
selectTranslateStep: 3,
|
||||
// 多选节点时鼠标移动距边缘多少距离时开始偏移
|
||||
selectTranslateLimit: 20,
|
||||
// 自定义节点备注内容显示
|
||||
customNoteContentShow: null
|
||||
/*
|
||||
{
|
||||
show(){},
|
||||
hide(){}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-06 11:18:47
|
||||
* @Desc: 思维导图
|
||||
*/
|
||||
// 思维导图
|
||||
class MindMap {
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-06 11:19:01
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
// 合并选项
|
||||
this.opt = this.handleOpt(merge(defaultOpt, opt))
|
||||
|
||||
// 容器元素
|
||||
this.el = this.opt.el
|
||||
if (!this.el) throw new Error('缺少容器元素el')
|
||||
this.elRect = this.el.getBoundingClientRect()
|
||||
|
||||
// 画布宽高
|
||||
this.width = this.elRect.width
|
||||
this.height = this.elRect.height
|
||||
if (this.width <= 0 || this.height <= 0) throw new Error('容器元素el的宽高不能为0')
|
||||
|
||||
// 画布
|
||||
this.svg = SVG().addTo(this.el).size(this.width, this.height)
|
||||
this.draw = this.svg.group()
|
||||
|
||||
// 节点id
|
||||
this.uid = 0
|
||||
|
||||
// 初始化主题
|
||||
this.initTheme()
|
||||
|
||||
// 初始化缓存数据
|
||||
this.initCache()
|
||||
|
||||
// 事件类
|
||||
this.event = new Event({
|
||||
mindMap: this
|
||||
@@ -113,84 +73,54 @@ class MindMap {
|
||||
draw: this.draw
|
||||
})
|
||||
|
||||
// 小地图类
|
||||
this.miniMap = new MiniMap({
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 导出类
|
||||
this.doExport = new Export({
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 选择类
|
||||
this.select = new Select({
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 拖动类
|
||||
this.drag = new Drag({
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 批量执行类
|
||||
this.batchExecution = new BatchExecution()
|
||||
|
||||
// 注册插件
|
||||
MindMap.pluginList.forEach(plugin => {
|
||||
this.initPlugin(plugin)
|
||||
})
|
||||
|
||||
// 初始渲染
|
||||
this.reRender()
|
||||
this.render()
|
||||
setTimeout(() => {
|
||||
this.command.addHistory()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:15:22
|
||||
* @Desc: 配置参数处理
|
||||
*/
|
||||
// 配置参数处理
|
||||
handleOpt(opt) {
|
||||
// 深拷贝一份节点数据
|
||||
opt.data = simpleDeepClone(opt.data || {})
|
||||
// 检查布局配置
|
||||
if (!layoutValueList.includes(opt.layout)) {
|
||||
opt.layout = 'logicalStructure'
|
||||
opt.layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
|
||||
}
|
||||
// 检查主题配置
|
||||
opt.theme = opt.theme && theme[opt.theme] ? opt.theme : 'default'
|
||||
return opt
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-06 18:47:29
|
||||
* @Desc: 渲染,部分渲染
|
||||
*/
|
||||
render() {
|
||||
// 渲染,部分渲染
|
||||
render(callback, source = '') {
|
||||
this.batchExecution.push('render', () => {
|
||||
this.initTheme()
|
||||
this.renderer.reRender = false
|
||||
this.renderer.render()
|
||||
this.renderer.render(callback, source)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-08 22:05:11
|
||||
* @Desc: 重新渲染
|
||||
*/
|
||||
reRender() {
|
||||
// 重新渲染
|
||||
reRender(callback, source = '') {
|
||||
this.batchExecution.push('render', () => {
|
||||
this.draw.clear()
|
||||
this.initTheme()
|
||||
this.renderer.reRender = true
|
||||
this.renderer.render()
|
||||
this.renderer.render(callback, source)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 21:16:52
|
||||
* @Desc: 容器尺寸变化,调整尺寸
|
||||
*/
|
||||
// 容器尺寸变化,调整尺寸
|
||||
resize() {
|
||||
this.elRect = this.el.getBoundingClientRect()
|
||||
this.width = this.elRect.width
|
||||
@@ -198,38 +128,39 @@ class MindMap {
|
||||
this.svg.size(this.width, this.height)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 13:25:50
|
||||
* @Desc: 监听事件
|
||||
*/
|
||||
// 监听事件
|
||||
on(event, fn) {
|
||||
this.event.on(event, fn)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 13:51:35
|
||||
* @Desc: 触发事件
|
||||
*/
|
||||
// 触发事件
|
||||
emit(event, ...args) {
|
||||
this.event.emit(event, ...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 13:53:54
|
||||
* @Desc: 解绑事件
|
||||
*/
|
||||
// 解绑事件
|
||||
off(event, fn) {
|
||||
this.event.off(event, fn)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 13:32:43
|
||||
* @Desc: 设置主题
|
||||
*/
|
||||
// 初始化缓存数据
|
||||
initCache() {
|
||||
Object.keys(commonCaches).forEach(key => {
|
||||
let type = getType(commonCaches[key])
|
||||
let value = ''
|
||||
switch (type) {
|
||||
case 'Boolean':
|
||||
value = false
|
||||
break
|
||||
default:
|
||||
value = null
|
||||
break
|
||||
}
|
||||
commonCaches[key] = value
|
||||
})
|
||||
}
|
||||
|
||||
// 设置主题
|
||||
initTheme() {
|
||||
// 合并主题配置
|
||||
this.themeConfig = merge(theme[this.opt.theme], this.opt.themeConfig)
|
||||
@@ -237,107 +168,83 @@ class MindMap {
|
||||
Style.setBackgroundStyle(this.el, this.themeConfig)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 13:52:08
|
||||
* @Desc: 设置主题
|
||||
*/
|
||||
// 设置主题
|
||||
setTheme(theme) {
|
||||
this.renderer.clearAllActive()
|
||||
this.opt.theme = theme
|
||||
this.reRender()
|
||||
this.render(null, CONSTANTS.CHANGE_THEME)
|
||||
this.emit('view_theme_change', theme)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-25 23:52:37
|
||||
* @Desc: 获取当前主题
|
||||
*/
|
||||
// 获取当前主题
|
||||
getTheme() {
|
||||
return this.opt.theme
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 13:50:17
|
||||
* @Desc: 设置主题配置
|
||||
*/
|
||||
// 设置主题配置
|
||||
setThemeConfig(config) {
|
||||
this.opt.themeConfig = config
|
||||
this.reRender()
|
||||
// 检查改变的是否是节点大小无关的主题属性
|
||||
let res = checkIsNodeSizeIndependenceConfig(config)
|
||||
this.render(null, res ? '' : CONSTANTS.CHANGE_THEME)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-01 10:38:34
|
||||
* @Desc: 获取自定义主题配置
|
||||
*/
|
||||
// 获取自定义主题配置
|
||||
getCustomThemeConfig() {
|
||||
return this.opt.themeConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 14:01:29
|
||||
* @Desc: 获取某个主题配置值
|
||||
*/
|
||||
// 获取某个主题配置值
|
||||
getThemeConfig(prop) {
|
||||
return prop === undefined ? this.themeConfig : this.themeConfig[prop]
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-07-13 16:17:06
|
||||
* @Desc: 获取当前布局结构
|
||||
*/
|
||||
// 获取配置
|
||||
getConfig(prop) {
|
||||
return prop === undefined ? this.opt : this.opt[prop]
|
||||
}
|
||||
|
||||
// 更新配置
|
||||
updateConfig(opt = {}) {
|
||||
this.opt = this.handleOpt(merge.all([defaultOpt, this.opt, opt]))
|
||||
}
|
||||
|
||||
// 获取当前布局结构
|
||||
getLayout() {
|
||||
return this.opt.layout
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-07-13 16:17:33
|
||||
* @Desc: 设置布局结构
|
||||
*/
|
||||
// 设置布局结构
|
||||
setLayout(layout) {
|
||||
// 检查布局配置
|
||||
if (!layoutValueList.includes(layout)) {
|
||||
layout = 'logicalStructure'
|
||||
layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
|
||||
}
|
||||
this.opt.layout = layout
|
||||
this.view.reset()
|
||||
this.renderer.setLayout()
|
||||
this.render()
|
||||
this.render(null, CONSTANTS.CHANGE_LAYOUT)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 13:01:00
|
||||
* @Desc: 执行命令
|
||||
*/
|
||||
// 执行命令
|
||||
execCommand(...args) {
|
||||
this.command.exec(...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-03 22:58:12
|
||||
* @Desc: 动态设置思维导图数据,纯节点数据
|
||||
*/
|
||||
// 动态设置思维导图数据,纯节点数据
|
||||
setData(data) {
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.command.clearHistory()
|
||||
this.renderer.renderTree = data
|
||||
this.reRender()
|
||||
this.command.addHistory()
|
||||
if (this.richText) {
|
||||
this.renderer.renderTree = this.richText.handleSetData(data)
|
||||
} else {
|
||||
this.renderer.renderTree = data
|
||||
}
|
||||
this.reRender(() => {}, CONSTANTS.SET_DATA)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-09-21 16:39:13
|
||||
* @Desc: 动态设置思维导图数据,包括节点数据、布局、主题、视图
|
||||
*/
|
||||
// 动态设置思维导图数据,包括节点数据、布局、主题、视图
|
||||
setFullData(data) {
|
||||
if (data.root) {
|
||||
this.setData(data.root)
|
||||
@@ -358,12 +265,7 @@ class MindMap {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-24 14:42:07
|
||||
* @Desc: 获取思维导图数据,节点树、主题、布局等
|
||||
*/
|
||||
// 获取思维导图数据,节点树、主题、布局等
|
||||
getData(withConfig) {
|
||||
let nodeData = this.command.getCopyData()
|
||||
let data = {}
|
||||
@@ -383,21 +285,17 @@ class MindMap {
|
||||
return simpleDeepClone(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:06:38
|
||||
* @Desc: 导出
|
||||
*/
|
||||
// 导出
|
||||
async export(...args) {
|
||||
let result = await this.doExport.export(...args)
|
||||
return result
|
||||
try {
|
||||
let result = await this.doExport.export(...args)
|
||||
return result
|
||||
} catch (error) {
|
||||
this.mindMap.opt.errorHandler(ERROR_TYPES.EXPORT_ERROR, error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 09:20:03
|
||||
* @Desc: 转换位置
|
||||
*/
|
||||
// 转换位置
|
||||
toPos(x, y) {
|
||||
return {
|
||||
x: x - this.elRect.left,
|
||||
@@ -405,25 +303,140 @@ class MindMap {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-06-08 14:12:38
|
||||
* @Desc: 设置只读模式、编辑模式
|
||||
*/
|
||||
// 设置只读模式、编辑模式
|
||||
setMode(mode) {
|
||||
if (!['readonly', 'edit'].includes(mode)) {
|
||||
if (![CONSTANTS.MODE.READONLY, CONSTANTS.MODE.EDIT].includes(mode)) {
|
||||
return
|
||||
}
|
||||
this.opt.readonly = mode === 'readonly'
|
||||
this.opt.readonly = mode === CONSTANTS.MODE.READONLY
|
||||
if (this.opt.readonly) {
|
||||
// 取消当前激活的元素
|
||||
this.renderer.clearAllActive()
|
||||
}
|
||||
this.emit('mode_change', mode)
|
||||
}
|
||||
|
||||
// 获取svg数据
|
||||
getSvgData({ paddingX = 0, paddingY = 0 } = {}) {
|
||||
const svg = this.svg
|
||||
const draw = this.draw
|
||||
// 保存原始信息
|
||||
const origWidth = svg.width()
|
||||
const origHeight = svg.height()
|
||||
const origTransform = draw.transform()
|
||||
const elRect = this.el.getBoundingClientRect()
|
||||
// 去除放大缩小的变换效果
|
||||
draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY)
|
||||
// 获取变换后的位置尺寸信息,其实是getBoundingClientRect方法的包装方法
|
||||
const rect = draw.rbox()
|
||||
// 内边距
|
||||
rect.width += paddingX * 2
|
||||
rect.height += paddingY * 2
|
||||
draw.translate(paddingX, paddingY)
|
||||
// 将svg设置为实际内容的宽高
|
||||
svg.size(rect.width, rect.height)
|
||||
// 把实际内容变换
|
||||
draw.translate(-rect.x + elRect.left, -rect.y + elRect.top)
|
||||
// 克隆一份数据
|
||||
let clone = svg.clone()
|
||||
// 如果实际图形宽高超出了屏幕宽高,且存在水印的话需要重新绘制水印,否则会出现超出部分没有水印的问题
|
||||
if (
|
||||
(rect.width > origWidth || rect.height > origHeight) &&
|
||||
this.watermark &&
|
||||
this.watermark.hasWatermark()
|
||||
) {
|
||||
this.width = rect.width
|
||||
this.height = rect.height
|
||||
this.watermark.draw()
|
||||
clone = svg.clone()
|
||||
this.width = origWidth
|
||||
this.height = origHeight
|
||||
this.watermark.draw()
|
||||
}
|
||||
// 恢复原先的大小和变换信息
|
||||
svg.size(origWidth, origHeight)
|
||||
draw.transform(origTransform)
|
||||
|
||||
return {
|
||||
svg: clone, // 思维导图图形的整体svg元素,包括:svg(画布容器)、g(实际的思维导图组)
|
||||
svgHTML: clone.svg(), // svg字符串
|
||||
rect: {
|
||||
...rect, // 思维导图图形未缩放时的位置尺寸等信息
|
||||
ratio: rect.width / rect.height // 思维导图图形的宽高比
|
||||
},
|
||||
origWidth, // 画布宽度
|
||||
origHeight, // 画布高度
|
||||
scaleX: origTransform.scaleX, // 思维导图图形的水平缩放值
|
||||
scaleY: origTransform.scaleY // 思维导图图形的垂直缩放值
|
||||
}
|
||||
}
|
||||
|
||||
// 添加插件
|
||||
addPlugin(plugin, opt) {
|
||||
let index = MindMap.hasPlugin(plugin)
|
||||
if (index === -1) {
|
||||
MindMap.usePlugin(plugin, opt)
|
||||
this.initPlugin(plugin)
|
||||
}
|
||||
}
|
||||
|
||||
// 移除插件
|
||||
removePlugin(plugin) {
|
||||
let index = MindMap.hasPlugin(plugin)
|
||||
if (index !== -1) {
|
||||
MindMap.pluginList.splice(index, 1)
|
||||
if (this[plugin.instanceName]) {
|
||||
if (this[plugin.instanceName].beforePluginRemove) {
|
||||
this[plugin.instanceName].beforePluginRemove()
|
||||
}
|
||||
delete this[plugin.instanceName]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 实例化插件
|
||||
initPlugin(plugin) {
|
||||
this[plugin.instanceName] = new plugin({
|
||||
mindMap: this,
|
||||
pluginOpt: plugin.pluginOpt
|
||||
})
|
||||
}
|
||||
|
||||
// 销毁
|
||||
destroy() {
|
||||
// 移除插件
|
||||
;[...MindMap.pluginList].forEach(plugin => {
|
||||
this[plugin.instanceName] = null
|
||||
})
|
||||
// 解绑事件
|
||||
this.event.unbind()
|
||||
// 移除画布节点
|
||||
this.svg.remove()
|
||||
// 去除给容器元素设置的背景样式
|
||||
Style.removeBackgroundStyle(this.el)
|
||||
this.el = null
|
||||
}
|
||||
}
|
||||
|
||||
MindMap.xmind = xmind
|
||||
// 插件列表
|
||||
MindMap.pluginList = []
|
||||
MindMap.usePlugin = (plugin, opt = {}) => {
|
||||
plugin.pluginOpt = opt
|
||||
MindMap.pluginList.push(plugin)
|
||||
return MindMap
|
||||
}
|
||||
MindMap.hasPlugin = plugin => {
|
||||
return MindMap.pluginList.findIndex(item => {
|
||||
return item === plugin
|
||||
})
|
||||
}
|
||||
|
||||
// 定义新主题
|
||||
MindMap.defineTheme = (name, config = {}) => {
|
||||
if (theme[name]) {
|
||||
return new Error('该主题名称已存在')
|
||||
}
|
||||
theme[name] = merge(defaultTheme, config)
|
||||
}
|
||||
|
||||
export default MindMap
|
||||
|
||||
3879
simple-mind-map/package-lock.json
generated
Normal file
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.2.16",
|
||||
"version": "0.6.15-fix.1",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
@@ -22,14 +22,17 @@
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"module": "index.js",
|
||||
"main": "./dist/simpleMindMap.umd.min.js",
|
||||
"__main": "./dist/simpleMindMap.umd.min.js",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.0.16",
|
||||
"canvg": "^3.0.7",
|
||||
"deepmerge": "^1.5.2",
|
||||
"dom-to-image-more": "^3.1.6",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"jspdf": "^2.5.1",
|
||||
"jszip": "^3.10.1",
|
||||
"mdast-util-from-markdown": "^1.3.0",
|
||||
"quill": "^1.3.6",
|
||||
"uuid": "^9.0.0",
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"keywords": [
|
||||
|
||||
46
simple-mind-map/scripts/walkJsFiles.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// 遍历所有js文件
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const entryPath = path.resolve(__dirname, '../src')
|
||||
|
||||
const transform = dir => {
|
||||
let dirs = fs.readdirSync(dir)
|
||||
dirs.forEach(item => {
|
||||
let file = path.join(dir, item)
|
||||
if (fs.statSync(file).isDirectory()) {
|
||||
transform(file)
|
||||
} else if (/\.js$/.test(file)) {
|
||||
transformFile(file)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const transformFile = file => {
|
||||
console.log(file);
|
||||
let content = fs.readFileSync(file, 'utf-8')
|
||||
countCodeLines(content)
|
||||
// transformComments(file, content)
|
||||
}
|
||||
|
||||
// 统计代码行数
|
||||
let totalLines = 0
|
||||
const countCodeLines = (content) => {
|
||||
totalLines += content.split(/\n/).length
|
||||
}
|
||||
|
||||
// 转换注释类型
|
||||
const transformComments = (file, content) => {
|
||||
console.log('当前转换文件:', file)
|
||||
content = content.replace(/\/\*\*[^/]+\*\//g, str => {
|
||||
let res = /@Desc:([^\n]+)\n/g.exec(str)
|
||||
if (res.length > 0) {
|
||||
return '// ' + res[1]
|
||||
}
|
||||
})
|
||||
fs.writeFileSync(file, content)
|
||||
}
|
||||
|
||||
transform(entryPath)
|
||||
transformFile(path.join(__dirname, '../index.js'))
|
||||
console.log(totalLines);
|
||||
@@ -1,85 +0,0 @@
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-27 13:16:23
|
||||
* @Desc: 在下一个事件循环里执行任务
|
||||
*/
|
||||
const nextTick = function (fn, ctx) {
|
||||
let pending = false
|
||||
let timerFunc = null
|
||||
let handle = () => {
|
||||
pending = false
|
||||
ctx ? fn.call(ctx) : fn()
|
||||
}
|
||||
// 支持MutationObserver接口的话使用MutationObserver
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
let counter = 1
|
||||
let observer = new MutationObserver(handle)
|
||||
let textNode = document.createTextNode(counter)
|
||||
observer.observe(textNode, {
|
||||
characterData: true // 设为 true 表示监视指定目标节点或子节点树中节点所包含的字符数据的变化
|
||||
})
|
||||
timerFunc = function () {
|
||||
counter = (counter + 1) % 2 // counter会在0和1两者循环变化
|
||||
textNode.data = counter // 节点变化会触发回调handle,
|
||||
}
|
||||
} else {
|
||||
// 否则使用定时器
|
||||
timerFunc = setTimeout
|
||||
}
|
||||
return function () {
|
||||
if (pending) return
|
||||
pending = true
|
||||
timerFunc(handle, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-26 22:40:52
|
||||
* @Desc: 批量执行
|
||||
*/
|
||||
class BatchExecution {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-26 22:41:41
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor() {
|
||||
this.has = {}
|
||||
this.queue = []
|
||||
this.nextTick = nextTick(this.flush, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-27 12:54:04
|
||||
* @Desc: 添加任务
|
||||
*/
|
||||
push(name, fn) {
|
||||
if (this.has[name]) {
|
||||
return
|
||||
}
|
||||
this.has[name] = true
|
||||
this.queue.push({
|
||||
name,
|
||||
fn
|
||||
})
|
||||
this.nextTick()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-27 13:09:24
|
||||
* @Desc: 执行队列
|
||||
*/
|
||||
flush() {
|
||||
let fns = this.queue.slice(0)
|
||||
this.queue = []
|
||||
fns.forEach(({ name, fn }) => {
|
||||
this.has[name] = false
|
||||
fn()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default BatchExecution
|
||||
@@ -1,182 +0,0 @@
|
||||
import EventEmitter from 'eventemitter3'
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 14:53:09
|
||||
* @Desc: 事件类
|
||||
*/
|
||||
class Event extends EventEmitter {
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 14:53:25
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(opt = {}) {
|
||||
super()
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
this.isLeftMousedown = false
|
||||
this.mousedownPos = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
this.mousemovePos = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
this.mousemoveOffset = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
this.bindFn()
|
||||
this.bind()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 15:52:24
|
||||
* @Desc: 绑定函数上下文
|
||||
*/
|
||||
bindFn() {
|
||||
this.onDrawClick = this.onDrawClick.bind(this)
|
||||
this.onMousedown = this.onMousedown.bind(this)
|
||||
this.onMousemove = this.onMousemove.bind(this)
|
||||
this.onMouseup = this.onMouseup.bind(this)
|
||||
this.onMousewheel = this.onMousewheel.bind(this)
|
||||
this.onContextmenu = this.onContextmenu.bind(this)
|
||||
this.onSvgMousedown = this.onSvgMousedown.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 14:53:43
|
||||
* @Desc: 绑定事件
|
||||
*/
|
||||
bind() {
|
||||
this.mindMap.svg.on('click', this.onDrawClick)
|
||||
this.mindMap.el.addEventListener('mousedown', this.onMousedown)
|
||||
this.mindMap.svg.on('mousedown', this.onSvgMousedown)
|
||||
window.addEventListener('mousemove', this.onMousemove)
|
||||
window.addEventListener('mouseup', this.onMouseup)
|
||||
// 兼容火狐浏览器
|
||||
if (window.navigator.userAgent.toLowerCase().indexOf('firefox') != -1) {
|
||||
this.mindMap.el.addEventListener('DOMMouseScroll', this.onMousewheel)
|
||||
} else {
|
||||
this.mindMap.el.addEventListener('mousewheel', this.onMousewheel)
|
||||
}
|
||||
this.mindMap.svg.on('contextmenu', this.onContextmenu)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 15:40:51
|
||||
* @Desc: 解绑事件
|
||||
*/
|
||||
unbind() {
|
||||
this.mindMap.svg.off('click', this.onDrawClick)
|
||||
this.mindMap.el.removeEventListener('mousedown', this.onMousedown)
|
||||
window.removeEventListener('mousemove', this.onMousemove)
|
||||
window.removeEventListener('mouseup', this.onMouseup)
|
||||
this.mindMap.el.removeEventListener('mousewheel', this.onMousewheel)
|
||||
this.mindMap.svg.off('contextmenu', this.onContextmenu)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 13:19:39
|
||||
* @Desc: 画布的单击事件
|
||||
*/
|
||||
onDrawClick(e) {
|
||||
this.emit('draw_click', e)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-16 13:37:30
|
||||
* @Desc: svg画布的鼠标按下事件
|
||||
*/
|
||||
onSvgMousedown(e) {
|
||||
this.emit('svg_mousedown', e)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 15:17:35
|
||||
* @Desc: 鼠标按下事件
|
||||
*/
|
||||
onMousedown(e) {
|
||||
// e.preventDefault()
|
||||
// 鼠标左键
|
||||
if (e.which === 1) {
|
||||
this.isLeftMousedown = true
|
||||
}
|
||||
this.mousedownPos.x = e.clientX
|
||||
this.mousedownPos.y = e.clientY
|
||||
this.emit('mousedown', e, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 15:18:32
|
||||
* @Desc: 鼠标移动事件
|
||||
*/
|
||||
onMousemove(e) {
|
||||
// e.preventDefault()
|
||||
this.mousemovePos.x = e.clientX
|
||||
this.mousemovePos.y = e.clientY
|
||||
this.mousemoveOffset.x = e.clientX - this.mousedownPos.x
|
||||
this.mousemoveOffset.y = e.clientY - this.mousedownPos.y
|
||||
this.emit('mousemove', e, this)
|
||||
if (this.isLeftMousedown) {
|
||||
this.emit('drag', e, this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 15:18:57
|
||||
* @Desc: 鼠标松开事件
|
||||
*/
|
||||
onMouseup(e) {
|
||||
this.isLeftMousedown = false
|
||||
this.emit('mouseup', e, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 15:46:27
|
||||
* @Desc: 鼠标滚动
|
||||
*/
|
||||
onMousewheel(e) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let dir
|
||||
if ((e.wheelDeltaY || e.detail) > 0) {
|
||||
dir = 'up'
|
||||
} else {
|
||||
dir = 'down'
|
||||
}
|
||||
this.emit('mousewheel', e, dir, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-10 22:34:13
|
||||
* @Desc: 鼠标右键菜单事件
|
||||
*/
|
||||
onContextmenu(e) {
|
||||
e.preventDefault()
|
||||
this.emit('contextmenu', e)
|
||||
}
|
||||
}
|
||||
|
||||
export default Event
|
||||
@@ -1,265 +0,0 @@
|
||||
import { imgToDataUrl, downloadFile } from './utils'
|
||||
import JsPDF from 'jspdf'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
const URL = window.URL || window.webkitURL || window
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:05:16
|
||||
* @Desc: 导出类
|
||||
*/
|
||||
class Export {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:05:42
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(opt) {
|
||||
this.mindMap = opt.mindMap
|
||||
this.exportPadding = this.mindMap.opt.exportPadding
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-02 07:44:06
|
||||
* @Desc: 导出
|
||||
*/
|
||||
async export(type, isDownload = true, name = '思维导图', ...args) {
|
||||
if (this[type]) {
|
||||
let result = await this[type](name, ...args)
|
||||
if (isDownload && type !== 'pdf') {
|
||||
downloadFile(result, name + '.' + type)
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 14:57:40
|
||||
* @Desc: 获取svg数据
|
||||
*/
|
||||
async getSvgData() {
|
||||
let { svg, svgHTML } = this.mindMap.miniMap.getMiniMap()
|
||||
// 把图片的url转换成data:url类型,否则导出会丢失图片
|
||||
let imageList = svg.find('image')
|
||||
let task = imageList.map(async item => {
|
||||
let imgUlr = item.attr('href') || item.attr('xlink:href')
|
||||
let imgData = await imgToDataUrl(imgUlr)
|
||||
item.attr('href', imgData)
|
||||
})
|
||||
await Promise.all(task)
|
||||
return {
|
||||
node: svg,
|
||||
str: svgHTML
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 15:25:19
|
||||
* @Desc: svg转png
|
||||
*/
|
||||
svgToPng(svgSrc) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
|
||||
img.setAttribute('crossOrigin', 'anonymous')
|
||||
img.onload = async () => {
|
||||
try {
|
||||
let canvas = document.createElement('canvas')
|
||||
canvas.width = img.width + this.exportPadding * 2
|
||||
canvas.height = img.height + this.exportPadding * 2
|
||||
let ctx = canvas.getContext('2d')
|
||||
// 绘制背景
|
||||
await this.drawBackgroundToCanvas(ctx, canvas.width, canvas.height)
|
||||
// 图片绘制到canvas里
|
||||
ctx.drawImage(
|
||||
img,
|
||||
0,
|
||||
0,
|
||||
img.width,
|
||||
img.height,
|
||||
this.exportPadding,
|
||||
this.exportPadding,
|
||||
img.width,
|
||||
img.height
|
||||
)
|
||||
resolve(canvas.toDataURL())
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
img.onerror = e => {
|
||||
reject(e)
|
||||
}
|
||||
img.src = svgSrc
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 15:32:07
|
||||
* @Desc: 在canvas上绘制思维导图背景
|
||||
*/
|
||||
drawBackgroundToCanvas(ctx, width, height) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let {
|
||||
backgroundColor = '#fff',
|
||||
backgroundImage,
|
||||
backgroundRepeat = 'repeat'
|
||||
} = this.mindMap.themeConfig
|
||||
// 背景颜色
|
||||
ctx.save()
|
||||
ctx.rect(0, 0, width, height)
|
||||
ctx.fillStyle = backgroundColor
|
||||
ctx.fill()
|
||||
ctx.restore()
|
||||
// 背景图片
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
ctx.save()
|
||||
let img = new Image()
|
||||
img.src = backgroundImage
|
||||
img.onload = () => {
|
||||
let pat = ctx.createPattern(img, backgroundRepeat)
|
||||
ctx.rect(0, 0, width, height)
|
||||
ctx.fillStyle = pat
|
||||
ctx.fill()
|
||||
ctx.restore()
|
||||
resolve()
|
||||
}
|
||||
img.onerror = e => {
|
||||
reject(e)
|
||||
}
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:09:51
|
||||
* @Desc: 导出为png
|
||||
* 方法1.把svg的图片都转化成data:url格式,再转换
|
||||
* 方法2.把svg的图片提取出来再挨个绘制到canvas里,最后一起转换
|
||||
*/
|
||||
async png() {
|
||||
let { str } = await this.getSvgData()
|
||||
// 转换成blob数据
|
||||
let blob = new Blob([str], {
|
||||
type: 'image/svg+xml'
|
||||
})
|
||||
// 转换成data:url数据
|
||||
let svgUrl = URL.createObjectURL(blob)
|
||||
// 绘制到canvas上
|
||||
let imgDataUrl = await this.svgToPng(svgUrl)
|
||||
URL.revokeObjectURL(svgUrl)
|
||||
return imgDataUrl
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-08-08 19:23:08
|
||||
* @Desc: 导出为pdf
|
||||
*/
|
||||
async pdf(name) {
|
||||
let img = await this.png()
|
||||
let pdf = new JsPDF('', 'pt', 'a4')
|
||||
let a4Width = 595
|
||||
let a4Height = 841
|
||||
let a4Ratio = a4Width / a4Height
|
||||
let image = new Image()
|
||||
image.onload = () => {
|
||||
let imageWidth = image.width
|
||||
let imageHeight = image.height
|
||||
let imageRatio = imageWidth / imageHeight
|
||||
let w, h
|
||||
if (imageWidth <= a4Width && imageHeight <= a4Height) {
|
||||
// 使用图片原始宽高
|
||||
w = imageWidth
|
||||
h = imageHeight
|
||||
} else if (a4Ratio > imageRatio) {
|
||||
// 以a4Height为高度,缩放图片宽度
|
||||
w = imageRatio * a4Height
|
||||
h = a4Height
|
||||
} else {
|
||||
// 以a4Width为宽度,缩放图片高度
|
||||
w = a4Width
|
||||
h = a4Width / imageRatio
|
||||
}
|
||||
pdf.addImage(img, 'PNG', (a4Width - w) / 2, (a4Height - h) / 2, w, h)
|
||||
pdf.save(name)
|
||||
}
|
||||
image.src = img
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 15:32:07
|
||||
* @Desc: 在svg上绘制思维导图背景
|
||||
*/
|
||||
drawBackgroundToSvg(svg) {
|
||||
return new Promise(async resolve => {
|
||||
let {
|
||||
backgroundColor = '#fff',
|
||||
backgroundImage,
|
||||
backgroundRepeat = 'repeat'
|
||||
} = this.mindMap.themeConfig
|
||||
// 背景颜色
|
||||
svg.css('background-color', backgroundColor)
|
||||
// 背景图片
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
let imgDataUrl = await imgToDataUrl(backgroundImage)
|
||||
svg.css('background-image', `url(${imgDataUrl})`)
|
||||
svg.css('background-repeat', backgroundRepeat)
|
||||
resolve()
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 14:54:07
|
||||
* @Desc: 导出为svg
|
||||
*/
|
||||
async svg(name) {
|
||||
let { node } = await this.getSvgData()
|
||||
node.first().before(SVG(`<title>${name}</title>`))
|
||||
await this.drawBackgroundToSvg(node)
|
||||
let str = node.svg()
|
||||
// 转换成blob数据
|
||||
let blob = new Blob([str], {
|
||||
type: 'image/svg+xml'
|
||||
})
|
||||
return URL.createObjectURL(blob)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-03 22:19:17
|
||||
* @Desc: 导出为json
|
||||
*/
|
||||
json(name, withConfig = true) {
|
||||
let data = this.mindMap.getData(withConfig)
|
||||
let str = JSON.stringify(data)
|
||||
let blob = new Blob([str])
|
||||
return URL.createObjectURL(blob)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-03 22:24:24
|
||||
* @Desc: 专有文件,其实就是json文件
|
||||
*/
|
||||
smm(name, withConfig) {
|
||||
return this.json(name, withConfig)
|
||||
}
|
||||
}
|
||||
|
||||
export default Export
|
||||
@@ -1,275 +0,0 @@
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-22 21:32:50
|
||||
* @Desc: 节点形状类
|
||||
*/
|
||||
export default class Shape {
|
||||
constructor(node) {
|
||||
this.node = node
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-17 22:32:32
|
||||
* @Desc: 形状需要的padding
|
||||
*/
|
||||
getShapePadding(width, height, paddingX, paddingY) {
|
||||
const shape = this.node.getShape()
|
||||
const defaultPaddingX = 15
|
||||
const defaultPaddingY = 5
|
||||
const actWidth = width + paddingX * 2
|
||||
const actHeight = height + paddingY * 2
|
||||
const actOffset = Math.abs(actWidth - actHeight)
|
||||
switch (shape) {
|
||||
case 'roundedRectangle':
|
||||
return {
|
||||
paddingX: height > width ? (height - width) / 2 : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'diamond':
|
||||
return {
|
||||
paddingX: width / 2,
|
||||
paddingY: height / 2
|
||||
}
|
||||
case 'parallelogram':
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'outerTriangularRectangle':
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'innerTriangularRectangle':
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'ellipse':
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: paddingY <= 0 ? defaultPaddingY : 0
|
||||
}
|
||||
case 'circle':
|
||||
return {
|
||||
paddingX: actHeight > actWidth ? actOffset / 2 : 0,
|
||||
paddingY: actHeight < actWidth ? actOffset / 2 : 0
|
||||
}
|
||||
default:
|
||||
return {
|
||||
paddingX: 0,
|
||||
paddingY: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-17 22:22:53
|
||||
* @Desc: 创建形状节点
|
||||
*/
|
||||
createShape() {
|
||||
const shape = this.node.getShape()
|
||||
let { width, height } = this.node
|
||||
let node = null
|
||||
// 矩形
|
||||
if (shape === 'rectangle') {
|
||||
node = this.node.group.rect(width, height)
|
||||
} else if (shape === 'diamond') {
|
||||
// 菱形
|
||||
node = this.createDiamond()
|
||||
} else if (shape === 'parallelogram') {
|
||||
// 平行四边形
|
||||
node = this.createParallelogram()
|
||||
} else if (shape === 'roundedRectangle') {
|
||||
// 圆角矩形
|
||||
node = this.createRoundedRectangle()
|
||||
} else if (shape === 'octagonalRectangle') {
|
||||
// 八角矩形
|
||||
node = this.createOctagonalRectangle()
|
||||
} else if (shape === 'outerTriangularRectangle') {
|
||||
// 外三角矩形
|
||||
node = this.createOuterTriangularRectangle()
|
||||
} else if (shape === 'innerTriangularRectangle') {
|
||||
// 内三角矩形
|
||||
node = this.createInnerTriangularRectangle()
|
||||
} else if (shape === 'ellipse') {
|
||||
// 椭圆
|
||||
node = this.createEllipse()
|
||||
} else if (shape === 'circle') {
|
||||
// 圆
|
||||
node = this.createCircle()
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-04 09:08:54
|
||||
* @Desc: 创建菱形
|
||||
*/
|
||||
createDiamond() {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
let topX = halfWidth
|
||||
let topY = 0
|
||||
let rightX = width
|
||||
let rightY = halfHeight
|
||||
let bottomX = halfWidth
|
||||
let bottomY = height
|
||||
let leftX = 0
|
||||
let leftY = halfHeight
|
||||
return this.node.group.polygon(`
|
||||
${topX}, ${topY}
|
||||
${rightX}, ${rightY}
|
||||
${bottomX}, ${bottomY}
|
||||
${leftX}, ${leftY}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-03 16:14:12
|
||||
* @Desc: 创建平行四边形
|
||||
*/
|
||||
createParallelogram() {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${paddingX}, ${0}
|
||||
${width}, ${0}
|
||||
${width - paddingX}, ${height}
|
||||
${0}, ${height}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-03 16:50:23
|
||||
* @Desc: 创建圆角矩形
|
||||
*/
|
||||
createRoundedRectangle() {
|
||||
let { width, height } = this.node
|
||||
let halfHeight = height / 2
|
||||
return this.node.group.path(`
|
||||
M${halfHeight},0
|
||||
L${width - halfHeight},0
|
||||
A${height / 2},${height / 2} 0 0,1 ${width - halfHeight},${height}
|
||||
L${halfHeight},${height}
|
||||
A${height / 2},${height / 2} 0 0,1 ${halfHeight},${0}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 16:14:08
|
||||
* @Desc: 创建八角矩形
|
||||
*/
|
||||
createOctagonalRectangle() {
|
||||
let w = 5
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${0}, ${w}
|
||||
${w}, ${0}
|
||||
${width - w}, ${0}
|
||||
${width}, ${w}
|
||||
${width}, ${height - w}
|
||||
${width - w}, ${height}
|
||||
${w}, ${height}
|
||||
${0}, ${height - w}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 20:55:50
|
||||
* @Desc: 创建外三角矩形
|
||||
*/
|
||||
createOuterTriangularRectangle() {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${paddingX}, ${0}
|
||||
${width - paddingX}, ${0}
|
||||
${width}, ${height / 2}
|
||||
${width - paddingX}, ${height}
|
||||
${paddingX}, ${height}
|
||||
${0}, ${height / 2}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 20:59:37
|
||||
* @Desc: 创建内三角矩形
|
||||
*/
|
||||
createInnerTriangularRectangle() {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${0}, ${0}
|
||||
${width}, ${0}
|
||||
${width - paddingX / 2}, ${height / 2}
|
||||
${width}, ${height}
|
||||
${0}, ${height}
|
||||
${paddingX / 2}, ${height / 2}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 21:06:31
|
||||
* @Desc: 创建椭圆
|
||||
*/
|
||||
createEllipse() {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
return this.node.group.path(`
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 21:14:04
|
||||
* @Desc: 创建圆
|
||||
*/
|
||||
createCircle() {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
return this.node.group.path(`
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
// 形状列表
|
||||
export const shapeList = [
|
||||
'rectangle',
|
||||
'diamond',
|
||||
'parallelogram',
|
||||
'roundedRectangle',
|
||||
'octagonalRectangle',
|
||||
'outerTriangularRectangle',
|
||||
'innerTriangularRectangle',
|
||||
'ellipse',
|
||||
'circle'
|
||||
]
|
||||
@@ -1,230 +0,0 @@
|
||||
import { tagColorList } from './utils/constant'
|
||||
const rootProp = ['paddingX', 'paddingY']
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 10:09:08
|
||||
* @Desc: 样式类
|
||||
*/
|
||||
class Style {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 16:01:53
|
||||
* @Desc: 设置背景样式
|
||||
*/
|
||||
static setBackgroundStyle(el, themeConfig) {
|
||||
let { backgroundColor, backgroundImage, backgroundRepeat } = themeConfig
|
||||
el.style.backgroundColor = backgroundColor
|
||||
if (backgroundImage) {
|
||||
el.style.backgroundImage = `url(${backgroundImage})`
|
||||
el.style.backgroundRepeat = backgroundRepeat
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 10:10:11
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(ctx, themeConfig) {
|
||||
this.ctx = ctx
|
||||
this.themeConfig = themeConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-12 07:40:14
|
||||
* @Desc: 更新主题配置
|
||||
*/
|
||||
updateThemeConfig(themeConfig) {
|
||||
this.themeConfig = themeConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 12:02:55
|
||||
* @Desc: 合并样式
|
||||
*/
|
||||
merge(prop, root, isActive) {
|
||||
// 三级及以下节点
|
||||
let defaultConfig = this.themeConfig.node
|
||||
if (root || rootProp.includes(prop)) {
|
||||
// 直接使用最外层样式
|
||||
defaultConfig = this.themeConfig
|
||||
} else if (this.ctx.isGeneralization) {
|
||||
// 概要节点
|
||||
defaultConfig = this.themeConfig.generalization
|
||||
} else if (this.ctx.layerIndex === 0) {
|
||||
// 根节点
|
||||
defaultConfig = this.themeConfig.root
|
||||
} else if (this.ctx.layerIndex === 1) {
|
||||
// 二级节点
|
||||
defaultConfig = this.themeConfig.second
|
||||
}
|
||||
// 激活状态
|
||||
if (isActive !== undefined ? isActive : this.ctx.nodeData.data.isActive) {
|
||||
if (
|
||||
this.ctx.nodeData.data.activeStyle &&
|
||||
this.ctx.nodeData.data.activeStyle[prop] !== undefined
|
||||
) {
|
||||
return this.ctx.nodeData.data.activeStyle[prop]
|
||||
} else if (defaultConfig.active && defaultConfig.active[prop]) {
|
||||
return defaultConfig.active[prop]
|
||||
}
|
||||
}
|
||||
// 优先使用节点本身的样式
|
||||
return this.getSelfStyle(prop) !== undefined
|
||||
? this.getSelfStyle(prop)
|
||||
: defaultConfig[prop]
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 21:55:57
|
||||
* @Desc: 获取某个样式值
|
||||
*/
|
||||
getStyle(prop, root, isActive) {
|
||||
return this.merge(prop, root, isActive)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: flydreame
|
||||
* @Date: 2022-09-17 12:09:39
|
||||
* @Desc: 获取自身自定义样式
|
||||
*/
|
||||
getSelfStyle(prop) {
|
||||
return this.ctx.nodeData.data[prop]
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 10:12:56
|
||||
* @Desc: 矩形
|
||||
*/
|
||||
rect(node) {
|
||||
this.shape(node)
|
||||
node.radius(this.merge('borderRadius'))
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 15:04:28
|
||||
* @Desc: 矩形外的其他形状
|
||||
*/
|
||||
shape(node) {
|
||||
node
|
||||
.fill({
|
||||
color: this.merge('fillColor')
|
||||
})
|
||||
.stroke({
|
||||
color: this.merge('borderColor'),
|
||||
width: this.merge('borderWidth'),
|
||||
dasharray: this.merge('borderDasharray')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 12:07:59
|
||||
* @Desc: 文字
|
||||
*/
|
||||
text(node) {
|
||||
node
|
||||
.fill({
|
||||
color: this.merge('color')
|
||||
})
|
||||
.css({
|
||||
'font-family': this.merge('fontFamily'),
|
||||
'font-size': this.merge('fontSize'),
|
||||
'font-weight': this.merge('fontWeight'),
|
||||
'font-style': this.merge('fontStyle'),
|
||||
'text-decoration': this.merge('textDecoration')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-13 08:14:34
|
||||
* @Desc: html文字节点
|
||||
*/
|
||||
domText(node, fontSizeScale = 1) {
|
||||
node.style.fontFamily = this.merge('fontFamily')
|
||||
node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px'
|
||||
node.style.fontWeight = this.merge('fontWeight') || 'normal'
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-20 20:02:18
|
||||
* @Desc: 标签文字
|
||||
*/
|
||||
tagText(node, index) {
|
||||
node
|
||||
.fill({
|
||||
color: tagColorList[index].color
|
||||
})
|
||||
.css({
|
||||
'font-size': '12px'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-20 21:04:11
|
||||
* @Desc: 标签矩形
|
||||
*/
|
||||
tagRect(node, index) {
|
||||
node.fill({
|
||||
color: tagColorList[index].background
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-03 22:37:19
|
||||
* @Desc: 内置图标
|
||||
*/
|
||||
iconNode(node) {
|
||||
node.attr({
|
||||
fill: this.merge('color')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 14:50:49
|
||||
* @Desc: 连线
|
||||
*/
|
||||
line(node, { width, color, dasharray } = {}) {
|
||||
node.stroke({ width, color, dasharray }).fill({ color: 'none' })
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-07-30 16:19:03
|
||||
* @Desc: 概要连线
|
||||
*/
|
||||
generalizationLine(node) {
|
||||
node
|
||||
.stroke({
|
||||
width: this.merge('generalizationLineWidth', true),
|
||||
color: this.merge('generalizationLineColor', true)
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 20:03:59
|
||||
* @Desc: 按钮
|
||||
*/
|
||||
iconBtn(node, fillNode) {
|
||||
node.fill({ color: '#808080' })
|
||||
fillNode.fill({ color: '#fff' })
|
||||
}
|
||||
}
|
||||
|
||||
export default Style
|
||||
@@ -1,150 +0,0 @@
|
||||
import { getStrWithBrFromHtml } from './utils'
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-06-19 11:11:28
|
||||
* @Desc: 节点文字编辑类
|
||||
*/
|
||||
export default class TextEdit {
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-06-19 11:22:57
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(renderer) {
|
||||
this.renderer = renderer
|
||||
this.mindMap = renderer.mindMap
|
||||
// 文本编辑框
|
||||
this.textEditNode = null
|
||||
// 文本编辑框是否显示
|
||||
this.showTextEdit = false
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 13:27:04
|
||||
* @Desc: 事件
|
||||
*/
|
||||
bindEvent() {
|
||||
this.show = this.show.bind(this)
|
||||
// 节点双击事件
|
||||
this.mindMap.on('node_dblclick', this.show)
|
||||
// 点击事件
|
||||
this.mindMap.on('draw_click', () => {
|
||||
// 隐藏文本编辑框
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
// 展开收缩按钮点击事件
|
||||
this.mindMap.on('expand_btn_click', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
// 节点激活前事件
|
||||
this.mindMap.on('before_node_active', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
// 注册编辑快捷键
|
||||
this.mindMap.keyCommand.addShortcut('F2', () => {
|
||||
if (this.renderer.activeNodeList.length <= 0) {
|
||||
return
|
||||
}
|
||||
this.show(this.renderer.activeNodeList[0])
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-08-16 16:27:02
|
||||
* @Desc: 注册临时快捷键
|
||||
*/
|
||||
registerTmpShortcut() {
|
||||
// 注册回车快捷键
|
||||
this.mindMap.keyCommand.addShortcut('Enter', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-13 22:15:56
|
||||
* @Desc: 显示文本编辑框
|
||||
*/
|
||||
show(node) {
|
||||
this.showEditTextBox(node, node._textData.node.node.getBoundingClientRect())
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-13 22:13:02
|
||||
* @Desc: 显示文本编辑框
|
||||
*/
|
||||
showEditTextBox(node, rect) {
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
this.registerTmpShortcut()
|
||||
if (!this.textEditNode) {
|
||||
this.textEditNode = document.createElement('div')
|
||||
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none;`
|
||||
this.textEditNode.setAttribute('contenteditable', true)
|
||||
document.body.appendChild(this.textEditNode)
|
||||
}
|
||||
node.style.domText(this.textEditNode, this.mindMap.view.scale)
|
||||
this.textEditNode.innerHTML = node.nodeData.data.text
|
||||
.split(/\n/gim)
|
||||
.join('<br>')
|
||||
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
|
||||
this.textEditNode.style.minHeight = rect.height + 6 + 'px'
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
this.textEditNode.style.top = rect.top + 'px'
|
||||
this.textEditNode.style.display = 'block'
|
||||
this.showTextEdit = true
|
||||
// 选中文本
|
||||
this.selectNodeText()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-02 23:13:50
|
||||
* @Desc: 选中文本
|
||||
*/
|
||||
selectNodeText() {
|
||||
let selection = window.getSelection()
|
||||
let range = document.createRange()
|
||||
range.selectNodeContents(this.textEditNode)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 13:48:16
|
||||
* @Desc: 隐藏文本编辑框
|
||||
*/
|
||||
hideEditTextBox() {
|
||||
if (!this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
this.renderer.activeNodeList.forEach(node => {
|
||||
let str = getStrWithBrFromHtml(this.textEditNode.innerHTML)
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', node, str)
|
||||
if (node.isGeneralization) {
|
||||
// 概要节点
|
||||
node.generalizationBelongNode.updateGeneralization()
|
||||
}
|
||||
this.mindMap.render()
|
||||
})
|
||||
this.mindMap.emit(
|
||||
'hide_text_edit',
|
||||
this.textEditNode,
|
||||
this.renderer.activeNodeList
|
||||
)
|
||||
this.textEditNode.style.display = 'none'
|
||||
this.textEditNode.innerHTML = ''
|
||||
this.textEditNode.style.fontFamily = 'inherit'
|
||||
this.textEditNode.style.fontSize = 'inherit'
|
||||
this.textEditNode.style.fontWeight = 'normal'
|
||||
this.showTextEdit = false
|
||||
}
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 14:45:24
|
||||
* @Desc: 视图操作类
|
||||
*/
|
||||
class View {
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 14:45:40
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(opt = {}) {
|
||||
this.opt = opt
|
||||
this.mindMap = this.opt.mindMap
|
||||
this.scale = 1
|
||||
this.sx = 0
|
||||
this.sy = 0
|
||||
this.x = 0
|
||||
this.y = 0
|
||||
this.firstDrag = true
|
||||
this.setTransformData(this.mindMap.opt.viewData)
|
||||
this.bind()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 15:38:51
|
||||
* @Desc: 绑定
|
||||
*/
|
||||
bind() {
|
||||
// 快捷键
|
||||
this.mindMap.keyCommand.addShortcut('Control+=', () => {
|
||||
this.enlarge()
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Control+-', () => {
|
||||
this.narrow()
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Control+Enter', () => {
|
||||
this.reset()
|
||||
})
|
||||
this.mindMap.svg.on('dblclick', () => {
|
||||
this.reset()
|
||||
})
|
||||
// 拖动视图
|
||||
this.mindMap.event.on('mousedown', () => {
|
||||
this.sx = this.x
|
||||
this.sy = this.y
|
||||
})
|
||||
this.mindMap.event.on('drag', (e, event) => {
|
||||
if (e.ctrlKey) {
|
||||
// 按住ctrl键拖动为多选
|
||||
return
|
||||
}
|
||||
if (this.firstDrag) {
|
||||
this.firstDrag = false
|
||||
// 清除激活节点
|
||||
if (this.mindMap.renderer.activeNodeList.length > 0) {
|
||||
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
|
||||
}
|
||||
}
|
||||
this.x = this.sx + event.mousemoveOffset.x
|
||||
this.y = this.sy + event.mousemoveOffset.y
|
||||
this.transform()
|
||||
})
|
||||
this.mindMap.event.on('mouseup', () => {
|
||||
this.firstDrag = true
|
||||
})
|
||||
// 放大缩小视图
|
||||
this.mindMap.event.on('mousewheel', (e, dir) => {
|
||||
// // 放大
|
||||
if (dir === 'down') {
|
||||
this.enlarge()
|
||||
} else {
|
||||
// 缩小
|
||||
this.narrow()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-22 18:30:24
|
||||
* @Desc: 获取当前变换状态数据
|
||||
*/
|
||||
getTransformData() {
|
||||
return {
|
||||
transform: this.mindMap.draw.transform(),
|
||||
state: {
|
||||
scale: this.scale,
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
sx: this.sx,
|
||||
sy: this.sy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-22 19:54:17
|
||||
* @Desc: 动态设置变换状态数据
|
||||
*/
|
||||
setTransformData(viewData) {
|
||||
if (viewData) {
|
||||
Object.keys(viewData.state).forEach(prop => {
|
||||
this[prop] = viewData.state[prop]
|
||||
})
|
||||
this.mindMap.draw.transform({
|
||||
...viewData.transform
|
||||
})
|
||||
this.mindMap.emit('view_data_change', this.getTransformData())
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-07-13 15:49:06
|
||||
* @Desc: 平移x方向
|
||||
*/
|
||||
translateX(step) {
|
||||
this.x += step
|
||||
this.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:03:53
|
||||
* @Desc: 平移x方式到
|
||||
*/
|
||||
translateXTo(x) {
|
||||
this.x = x
|
||||
this.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-07-13 15:48:52
|
||||
* @Desc: 平移y方向
|
||||
*/
|
||||
translateY(step) {
|
||||
this.y += step
|
||||
this.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:04:10
|
||||
* @Desc: 平移y方向到
|
||||
*/
|
||||
translateYTo(y) {
|
||||
this.y = y
|
||||
this.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 17:13:14
|
||||
* @Desc: 应用变换
|
||||
*/
|
||||
transform() {
|
||||
this.mindMap.draw.transform({
|
||||
scale: this.scale,
|
||||
// origin: 'center center',
|
||||
translate: [this.x, this.y]
|
||||
})
|
||||
this.mindMap.emit('view_data_change', this.getTransformData())
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 17:41:35
|
||||
* @Desc: 恢复
|
||||
*/
|
||||
reset() {
|
||||
this.scale = 1
|
||||
this.x = 0
|
||||
this.y = 0
|
||||
this.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 17:10:34
|
||||
* @Desc: 缩小
|
||||
*/
|
||||
narrow() {
|
||||
if (this.scale - this.mindMap.opt.scaleRatio > 0.1) {
|
||||
this.scale -= this.mindMap.opt.scaleRatio
|
||||
} else {
|
||||
this.scale = 0.1
|
||||
}
|
||||
this.transform()
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 17:10:41
|
||||
* @Desc: 放大
|
||||
*/
|
||||
enlarge() {
|
||||
this.scale += this.mindMap.opt.scaleRatio
|
||||
this.transform()
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
}
|
||||
}
|
||||
|
||||
export default View
|
||||
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 26 KiB |
345
simple-mind-map/src/constants/constant.js
Normal file
@@ -0,0 +1,345 @@
|
||||
// 标签颜色列表
|
||||
export const tagColorList = [
|
||||
{
|
||||
color: 'rgb(77, 65, 0)',
|
||||
background: 'rgb(255, 244, 179)'
|
||||
},
|
||||
{
|
||||
color: 'rgb(0, 50, 77)',
|
||||
background: 'rgb(179, 229, 255)'
|
||||
},
|
||||
{
|
||||
color: 'rgb(77, 0, 73)',
|
||||
background: 'rgb(255, 179, 251)'
|
||||
},
|
||||
{
|
||||
color: 'rgb(57, 77, 0)',
|
||||
background: 'rgb(236, 255, 179)'
|
||||
},
|
||||
{
|
||||
color: 'rgb(0, 77, 47)',
|
||||
background: 'rgb(179, 255, 226)'
|
||||
}
|
||||
]
|
||||
|
||||
// 主题列表
|
||||
export const themeList = [
|
||||
{
|
||||
name: '默认',
|
||||
value: 'default',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '暗色2',
|
||||
value: 'dark2',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '天清绿',
|
||||
value: 'skyGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑图经典2',
|
||||
value: 'classic2',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑图经典3',
|
||||
value: 'classic3',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '经典绿',
|
||||
value: 'classicGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '经典蓝',
|
||||
value: 'classicBlue',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '天空蓝',
|
||||
value: 'blueSky',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑残粉',
|
||||
value: 'brainImpairedPink',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '暗色',
|
||||
value: 'dark',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '泥土黄',
|
||||
value: 'earthYellow',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '清新绿',
|
||||
value: 'freshGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '清新红',
|
||||
value: 'freshRed',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '浪漫紫',
|
||||
value: 'romanticPurple',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '粉红葡萄',
|
||||
value: 'pinkGrape',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '薄荷',
|
||||
value: 'mint',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '金色vip',
|
||||
value: 'gold',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '活力橙',
|
||||
value: 'vitalityOrange',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '绿叶',
|
||||
value: 'greenLeaf',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑图经典',
|
||||
value: 'classic',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '脑图经典4',
|
||||
value: 'classic4',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '小黄人',
|
||||
value: 'minions',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '简约黑',
|
||||
value: 'simpleBlack',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '课程绿',
|
||||
value: 'courseGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '咖啡',
|
||||
value: 'coffee',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '红色精神',
|
||||
value: 'redSpirit',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '黑色幽默',
|
||||
value: 'blackHumour',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '深夜办公室',
|
||||
value: 'lateNightOffice',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '黑金',
|
||||
value: 'blackGold',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '牛油果',
|
||||
value: 'avocado',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '秋天',
|
||||
value: 'autumn',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '橙汁',
|
||||
value: 'orangeJuice',
|
||||
dark: true
|
||||
}
|
||||
]
|
||||
|
||||
// 常量
|
||||
export const CONSTANTS = {
|
||||
CHANGE_THEME: 'changeTheme',
|
||||
CHANGE_LAYOUT: 'changeLayout',
|
||||
SET_DATA: 'setData',
|
||||
TRANSFORM_TO_NORMAL_NODE: 'transformAllNodesToNormalNode',
|
||||
MODE: {
|
||||
READONLY: 'readonly',
|
||||
EDIT: 'edit'
|
||||
},
|
||||
LAYOUT: {
|
||||
LOGICAL_STRUCTURE: 'logicalStructure',
|
||||
MIND_MAP: 'mindMap',
|
||||
ORGANIZATION_STRUCTURE: 'organizationStructure',
|
||||
CATALOG_ORGANIZATION: 'catalogOrganization',
|
||||
TIMELINE: 'timeline',
|
||||
TIMELINE2: 'timeline2',
|
||||
FISHBONE: 'fishbone',
|
||||
VERTICAL_TIMELINE: 'verticalTimeline'
|
||||
},
|
||||
DIR: {
|
||||
UP: 'up',
|
||||
LEFT: 'left',
|
||||
DOWN: 'down',
|
||||
RIGHT: 'right'
|
||||
},
|
||||
KEY_DIR: {
|
||||
LEFT: 'Left',
|
||||
UP: 'Up',
|
||||
RIGHT: 'Right',
|
||||
DOWN: 'Down'
|
||||
},
|
||||
SHAPE: {
|
||||
RECTANGLE: 'rectangle',
|
||||
DIAMOND: 'diamond',
|
||||
PARALLELOGRAM: 'parallelogram',
|
||||
ROUNDED_RECTANGLE: 'roundedRectangle',
|
||||
OCTAGONAL_RECTANGLE: 'octagonalRectangle',
|
||||
OUTER_TRIANGULAR_RECTANGLE: 'outerTriangularRectangle',
|
||||
INNER_TRIANGULAR_RECTANGLE: 'innerTriangularRectangle',
|
||||
ELLIPSE: 'ellipse',
|
||||
CIRCLE: 'circle'
|
||||
},
|
||||
MOUSE_WHEEL_ACTION: {
|
||||
ZOOM: 'zoom',
|
||||
MOVE: 'move'
|
||||
},
|
||||
INIT_ROOT_NODE_POSITION: {
|
||||
LEFT: 'left',
|
||||
TOP: 'top',
|
||||
RIGHT: 'right',
|
||||
BOTTOM: 'bottom',
|
||||
CENTER: 'center'
|
||||
},
|
||||
LAYOUT_GROW_DIR: {
|
||||
LEFT: 'left',
|
||||
TOP: 'top',
|
||||
RIGHT: 'right',
|
||||
BOTTOM: 'bottom'
|
||||
},
|
||||
PASTE_TYPE: {
|
||||
CLIP_BOARD: 'clipBoard',
|
||||
CANVAS: 'canvas'
|
||||
}
|
||||
}
|
||||
|
||||
export const initRootNodePositionMap = {
|
||||
[CONSTANTS.INIT_ROOT_NODE_POSITION.LEFT]: 0,
|
||||
[CONSTANTS.INIT_ROOT_NODE_POSITION.TOP]: 0,
|
||||
[CONSTANTS.INIT_ROOT_NODE_POSITION.RIGHT]: 1,
|
||||
[CONSTANTS.INIT_ROOT_NODE_POSITION.BOTTOM]: 1,
|
||||
[CONSTANTS.INIT_ROOT_NODE_POSITION.CENTER]: 0.5,
|
||||
}
|
||||
|
||||
// 布局结构列表
|
||||
export const layoutList = [
|
||||
{
|
||||
name: '逻辑结构图',
|
||||
value: CONSTANTS.LAYOUT.LOGICAL_STRUCTURE,
|
||||
},
|
||||
{
|
||||
name: '思维导图',
|
||||
value: CONSTANTS.LAYOUT.MIND_MAP,
|
||||
},
|
||||
{
|
||||
name: '组织结构图',
|
||||
value: CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE,
|
||||
},
|
||||
{
|
||||
name: '目录组织图',
|
||||
value: CONSTANTS.LAYOUT.CATALOG_ORGANIZATION,
|
||||
},
|
||||
{
|
||||
name: '时间轴',
|
||||
value: CONSTANTS.LAYOUT.TIMELINE,
|
||||
},
|
||||
{
|
||||
name: '时间轴2',
|
||||
value: CONSTANTS.LAYOUT.TIMELINE2,
|
||||
},
|
||||
{
|
||||
name: '竖向时间轴',
|
||||
value: CONSTANTS.LAYOUT.VERTICAL_TIMELINE,
|
||||
},
|
||||
{
|
||||
name: '鱼骨图',
|
||||
value: CONSTANTS.LAYOUT.FISHBONE,
|
||||
}
|
||||
]
|
||||
export const layoutValueList = [
|
||||
CONSTANTS.LAYOUT.LOGICAL_STRUCTURE,
|
||||
CONSTANTS.LAYOUT.MIND_MAP,
|
||||
CONSTANTS.LAYOUT.CATALOG_ORGANIZATION,
|
||||
CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE,
|
||||
CONSTANTS.LAYOUT.TIMELINE,
|
||||
CONSTANTS.LAYOUT.TIMELINE2,
|
||||
CONSTANTS.LAYOUT.VERTICAL_TIMELINE,
|
||||
CONSTANTS.LAYOUT.FISHBONE
|
||||
]
|
||||
|
||||
// 节点数据中非样式的字段
|
||||
export const nodeDataNoStylePropList = [
|
||||
'text',
|
||||
'image',
|
||||
'imageTitle',
|
||||
'imageSize',
|
||||
'icon',
|
||||
'tag',
|
||||
'hyperlink',
|
||||
'hyperlinkTitle',
|
||||
'note',
|
||||
'expand',
|
||||
'isActive',
|
||||
'generalization',
|
||||
'richText',
|
||||
'resetRichText',
|
||||
'uid',
|
||||
'activeStyle',
|
||||
'associativeLineTargets',
|
||||
'associativeLineTargetControlOffsets'
|
||||
]
|
||||
|
||||
// 数据缓存
|
||||
export const commonCaches = {
|
||||
measureCustomNodeContentSizeEl: null,
|
||||
measureRichtextNodeTextSizeEl: null
|
||||
}
|
||||
|
||||
// 错误类型
|
||||
export const ERROR_TYPES = {
|
||||
READ_CLIPBOARD_ERROR: 'read_clipboard_error',
|
||||
PARSE_PASTE_DATA_ERROR: 'parse_paste_data_error',
|
||||
CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR: 'custom_handle_clipboard_text_error',
|
||||
LOAD_CLIPBOARD_IMAGE_ERROR: 'load_clipboard_image_error',
|
||||
BEFORE_TEXT_EDIT_ERROR: 'before_text_edit_error',
|
||||
EXPORT_ERROR: 'export_error'
|
||||
}
|
||||
159
simple-mind-map/src/constants/defaultOptions.js
Normal file
@@ -0,0 +1,159 @@
|
||||
import { CONSTANTS } from './constant'
|
||||
|
||||
// 默认选项配置
|
||||
export const defaultOpt = {
|
||||
// 是否只读
|
||||
readonly: false,
|
||||
// 布局
|
||||
layout: CONSTANTS.LAYOUT.LOGICAL_STRUCTURE,
|
||||
// 如果结构为鱼骨图,那么可以通过该选项控制倾斜角度
|
||||
fishboneDeg: 45,
|
||||
// 主题
|
||||
theme: 'default', // 内置主题:default(默认主题)
|
||||
// 主题配置,会和所选择的主题进行合并
|
||||
themeConfig: {},
|
||||
// 放大缩小的增量比例
|
||||
scaleRatio: 0.2,
|
||||
// 鼠标缩放是否以鼠标当前位置为中心点,否则以画布中心点
|
||||
mouseScaleCenterUseMousePosition: true,
|
||||
// 最多显示几个标签
|
||||
maxTag: 5,
|
||||
// 展开收缩按钮尺寸
|
||||
expandBtnSize: 20,
|
||||
// 节点里图片和文字的间距
|
||||
imgTextMargin: 5,
|
||||
// 节点里各种文字信息的间距,如图标和文字的间距
|
||||
textContentMargin: 2,
|
||||
// 多选节点时鼠标移动到边缘时的画布移动偏移量
|
||||
selectTranslateStep: 3,
|
||||
// 多选节点时鼠标移动距边缘多少距离时开始偏移
|
||||
selectTranslateLimit: 20,
|
||||
// 自定义节点备注内容显示
|
||||
customNoteContentShow: null,
|
||||
/*
|
||||
{
|
||||
show(){},
|
||||
hide(){}
|
||||
}
|
||||
*/
|
||||
// 是否开启节点自由拖拽
|
||||
enableFreeDrag: false,
|
||||
// 水印配置
|
||||
watermarkConfig: {
|
||||
text: '',
|
||||
lineSpacing: 100,
|
||||
textSpacing: 100,
|
||||
angle: 30,
|
||||
textStyle: {
|
||||
color: '#999',
|
||||
opacity: 0.5,
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
// 达到该宽度文本自动换行
|
||||
textAutoWrapWidth: 500,
|
||||
// 自定义鼠标滚轮事件处理
|
||||
// 可以传一个函数,回调参数为事件对象
|
||||
customHandleMousewheel: null,
|
||||
// 鼠标滚动的行为,如果customHandleMousewheel传了自定义函数,这个属性不生效
|
||||
mousewheelAction: CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM, // zoom(放大缩小)、move(上下移动)
|
||||
// 当mousewheelAction设为move时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位px
|
||||
mousewheelMoveStep: 100,
|
||||
// 当mousewheelAction设为zoom时,默认向前滚动是缩小,向后滚动是放大,如果该属性设为true,那么会反过来
|
||||
mousewheelZoomActionReverse: false,
|
||||
// 默认插入的二级节点的文字
|
||||
defaultInsertSecondLevelNodeText: '二级节点',
|
||||
// 默认插入的二级以下节点的文字
|
||||
defaultInsertBelowSecondLevelNodeText: '分支主题',
|
||||
// 展开收起按钮的颜色
|
||||
expandBtnStyle: {
|
||||
color: '#808080',
|
||||
fill: '#fff'
|
||||
},
|
||||
// 自定义展开收起按钮的图标
|
||||
expandBtnIcon: {
|
||||
open: '', // svg字符串
|
||||
close: ''
|
||||
},
|
||||
// 是否只有当鼠标在画布内才响应快捷键事件
|
||||
enableShortcutOnlyWhenMouseInSvg: true,
|
||||
// 初始根节点的位置
|
||||
initRootNodePosition: null,
|
||||
// 导出png、svg、pdf时的图形内边距,注意是单侧内边距
|
||||
exportPaddingX: 10,
|
||||
exportPaddingY: 10,
|
||||
// 节点文本编辑框的z-index
|
||||
nodeTextEditZIndex: 3000,
|
||||
// 节点备注浮层的z-index
|
||||
nodeNoteTooltipZIndex: 3000,
|
||||
// 是否在点击了画布外的区域时结束节点文本的编辑状态
|
||||
isEndNodeTextEditOnClickOuter: true,
|
||||
// 最大历史记录数
|
||||
maxHistoryCount: 1000,
|
||||
// 是否一直显示节点的展开收起按钮,默认为鼠标移上去和激活时才显示
|
||||
alwaysShowExpandBtn: false,
|
||||
// 扩展节点可插入的图标
|
||||
iconList: [
|
||||
// {
|
||||
// name: '',// 分组名称
|
||||
// type: '',// 分组的值
|
||||
// list: [// 分组下的图标列表
|
||||
// {
|
||||
// name: '',// 图标名称
|
||||
// icon:''// 图标,可以传svg或图片
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
],
|
||||
// 节点最大缓存数量
|
||||
maxNodeCacheCount: 1000,
|
||||
// 关联线默认文字
|
||||
defaultAssociativeLineText: '关联',
|
||||
// 思维导图适应画布大小时的内边距
|
||||
fitPadding: 50,
|
||||
// 是否开启按住ctrl键多选节点功能
|
||||
enableCtrlKeyNodeSelection: true,
|
||||
// 设置为左键多选节点,右键拖动画布
|
||||
useLeftKeySelectionRightKeyDrag: false,
|
||||
// 节点即将进入编辑前的回调方法,如果该方法返回true以外的值,那么将取消编辑,函数可以返回一个值,或一个Promise,回调参数为节点实例
|
||||
beforeTextEdit: null,
|
||||
// 是否开启自定义节点内容
|
||||
isUseCustomNodeContent: false,
|
||||
// 自定义返回节点内容的方法
|
||||
customCreateNodeContent: null,
|
||||
// 指定内部一些元素(节点文本编辑元素、节点备注显示元素、关联线文本编辑元素、节点图片调整按钮元素)添加到的位置,默认添加到document.body下
|
||||
customInnerElsAppendTo: null,
|
||||
// 拖拽元素时,指示元素新位置的块的最大高度
|
||||
nodeDragPlaceholderMaxSize: 20,
|
||||
// 是否在存在一个激活节点时,当按下中文、英文、数字按键时自动进入文本编辑模式
|
||||
// 开启该特性后,需要给你的输入框绑定keydown事件,并禁止冒泡
|
||||
enableAutoEnterTextEditWhenKeydown: false,
|
||||
// 设置富文本节点编辑框和节点大小一致,形成伪原地编辑的效果
|
||||
// 需要注意的是,只有当节点内只有文本、且形状是矩形才会有比较好的效果
|
||||
richTextEditFakeInPlace: false,
|
||||
// 自定义对剪贴板文本的处理。当按ctrl+v粘贴时会读取用户剪贴板中的文本和图片,默认只会判断文本是否是普通文本和simple-mind-map格式的节点数据,如果你想处理其他思维导图的数据,比如processon、zhixi等,那么可以传递一个函数,接受当前剪贴板中的文本为参数,返回处理后的数据,可以返回两种类型:
|
||||
/*
|
||||
1.返回一个纯文本,那么会直接以该文本创建一个子节点
|
||||
|
||||
2.返回一个节点对象,格式如下:
|
||||
{
|
||||
// 代表是simple-mind-map格式的数据
|
||||
simpleMindMap: true,
|
||||
// 节点数据,同simple-mind-map节点数据格式
|
||||
data: {
|
||||
data: {
|
||||
text: ''
|
||||
},
|
||||
children: []
|
||||
}
|
||||
}
|
||||
*/
|
||||
// 如果你的处理逻辑存在异步逻辑,也可以返回一个promise
|
||||
customHandleClipboardText: null,
|
||||
// 禁止鼠标滚轮缩放,你仍旧可以使用api进行缩放
|
||||
disableMouseWheelZoom: false,
|
||||
// 错误处理函数
|
||||
errorHandler: (code, error) => {
|
||||
console.error(code, error)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,8 @@
|
||||
import { copyRenderTree, simpleDeepClone } from './utils'
|
||||
import { copyRenderTree, simpleDeepClone, nextTick } from '../../utils'
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 13:10:06
|
||||
* @Desc: 命令类
|
||||
*/
|
||||
// 命令类
|
||||
class Command {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 13:10:24
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
@@ -19,24 +11,17 @@ class Command {
|
||||
this.activeHistoryIndex = 0
|
||||
// 注册快捷键
|
||||
this.registerShortcutKeys()
|
||||
this.addHistory = nextTick(this.addHistory, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-03 23:06:55
|
||||
* @Desc: 清空历史数据
|
||||
*/
|
||||
// 清空历史数据
|
||||
clearHistory() {
|
||||
this.history = []
|
||||
this.activeHistoryIndex = 0
|
||||
this.mindMap.emit('back_forward', 0, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-02 23:23:19
|
||||
* @Desc: 注册快捷键
|
||||
*/
|
||||
// 注册快捷键
|
||||
registerShortcutKeys() {
|
||||
this.mindMap.keyCommand.addShortcut('Control+z', () => {
|
||||
this.mindMap.execCommand('BACK')
|
||||
@@ -46,28 +31,20 @@ class Command {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 13:12:30
|
||||
* @Desc: 执行命令
|
||||
*/
|
||||
// 执行命令
|
||||
exec(name, ...args) {
|
||||
if (this.commands[name]) {
|
||||
this.commands[name].forEach(fn => {
|
||||
fn(...args)
|
||||
})
|
||||
if (name === 'BACK' || name === 'FORWARD') {
|
||||
if (['BACK', 'FORWARD', 'SET_NODE_ACTIVE', 'CLEAR_ACTIVE_NODE'].includes(name)) {
|
||||
return
|
||||
}
|
||||
this.addHistory()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 13:13:01
|
||||
* @Desc: 添加命令
|
||||
*/
|
||||
// 添加命令
|
||||
add(name, fn) {
|
||||
if (this.commands[name]) {
|
||||
this.commands[name].push(fn)
|
||||
@@ -76,11 +53,7 @@ class Command {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-15 23:02:41
|
||||
* @Desc: 移除命令
|
||||
*/
|
||||
// 移除命令
|
||||
remove(name, fn) {
|
||||
if (!this.commands[name]) {
|
||||
return
|
||||
@@ -98,14 +71,23 @@ class Command {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 14:35:43
|
||||
* @Desc: 添加回退数据
|
||||
*/
|
||||
// 添加回退数据
|
||||
addHistory() {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
let data = this.getCopyData()
|
||||
// 此次数据和上次一样则不重复添加
|
||||
if (this.history.length > 0 && JSON.stringify(this.history[this.history.length - 1]) === JSON.stringify(data)) {
|
||||
return
|
||||
}
|
||||
// 删除当前历史指针后面的数据
|
||||
this.history = this.history.slice(0, this.activeHistoryIndex + 1)
|
||||
this.history.push(simpleDeepClone(data))
|
||||
// 历史记录数超过最大数量
|
||||
if (this.history.length > this.mindMap.opt.maxHistoryCount) {
|
||||
this.history.shift()
|
||||
}
|
||||
this.activeHistoryIndex = this.history.length - 1
|
||||
this.mindMap.emit('data_change', data)
|
||||
this.mindMap.emit(
|
||||
@@ -115,12 +97,11 @@ class Command {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 22:34:53
|
||||
* @Desc: 回退
|
||||
*/
|
||||
// 回退
|
||||
back(step = 1) {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
if (this.activeHistoryIndex - step >= 0) {
|
||||
this.activeHistoryIndex -= step
|
||||
this.mindMap.emit(
|
||||
@@ -128,32 +109,45 @@ class Command {
|
||||
this.activeHistoryIndex,
|
||||
this.history.length
|
||||
)
|
||||
return simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
let data = simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
this.mindMap.emit('data_change', data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-07-12 10:45:31
|
||||
* @Desc: 前进
|
||||
*/
|
||||
// 前进
|
||||
forward(step = 1) {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
let len = this.history.length
|
||||
if (this.activeHistoryIndex + step <= len - 1) {
|
||||
this.activeHistoryIndex += step
|
||||
this.mindMap.emit('back_forward', this.activeHistoryIndex)
|
||||
return simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
this.mindMap.emit('back_forward', this.activeHistoryIndex, this.history.length)
|
||||
let data = simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
this.mindMap.emit('data_change', data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 15:02:58
|
||||
* @Desc: 获取渲染树数据副本
|
||||
*/
|
||||
// 获取渲染树数据副本
|
||||
getCopyData() {
|
||||
return copyRenderTree({}, this.mindMap.renderer.renderTree)
|
||||
return copyRenderTree({}, this.mindMap.renderer.renderTree, true)
|
||||
}
|
||||
|
||||
// 移除节点数据中的uid
|
||||
removeDataUid(data) {
|
||||
data = simpleDeepClone(data)
|
||||
let walk = (root) => {
|
||||
delete root.data.uid
|
||||
if (root.children && root.children.length > 0) {
|
||||
root.children.forEach((item) => {
|
||||
walk(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
walk(data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import { keyMap } from './utils/keyMap'
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 15:20:46
|
||||
* @Desc: 快捷按键、命令处理类
|
||||
*/
|
||||
import { keyMap } from './keyMap'
|
||||
// 快捷按键、命令处理类
|
||||
export default class KeyCommand {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 15:21:32
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
// 构造函数
|
||||
constructor(opt) {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
@@ -18,63 +10,58 @@ export default class KeyCommand {
|
||||
}
|
||||
this.shortcutMapCache = {}
|
||||
this.isPause = false
|
||||
this.isInSvg = false
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-14 08:57:55
|
||||
* @Desc: 暂停快捷键响应
|
||||
*/
|
||||
// 暂停快捷键响应
|
||||
pause() {
|
||||
this.isPause = true
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-14 08:58:43
|
||||
* @Desc: 恢复快捷键响应
|
||||
*/
|
||||
// 恢复快捷键响应
|
||||
recovery() {
|
||||
this.isPause = false
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-08-16 16:29:01
|
||||
* @Desc: 保存当前注册的快捷键数据,然后清空快捷键数据
|
||||
*/
|
||||
// 保存当前注册的快捷键数据,然后清空快捷键数据
|
||||
save() {
|
||||
this.shortcutMapCache = this.shortcutMap
|
||||
this.shortcutMap = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-08-16 16:29:38
|
||||
* @Desc: 恢复保存的快捷键数据,然后清空缓存数据
|
||||
*/
|
||||
// 恢复保存的快捷键数据,然后清空缓存数据
|
||||
restore() {
|
||||
this.shortcutMap = this.shortcutMapCache
|
||||
this.shortcutMapCache = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 15:23:22
|
||||
* @Desc: 绑定事件
|
||||
*/
|
||||
// 绑定事件
|
||||
bindEvent() {
|
||||
// 只有当鼠标在画布内才响应快捷键
|
||||
this.mindMap.on('svg_mouseenter', () => {
|
||||
this.isInSvg = true
|
||||
})
|
||||
this.mindMap.on('svg_mouseleave', () => {
|
||||
if (this.mindMap.richText && this.mindMap.richText.showTextEdit) {
|
||||
return
|
||||
}
|
||||
if (this.mindMap.renderer.textEdit.showTextEdit || (this.mindMap.associativeLine && this.mindMap.associativeLine.showTextEdit)) {
|
||||
return
|
||||
}
|
||||
this.isInSvg = false
|
||||
})
|
||||
window.addEventListener('keydown', e => {
|
||||
if (this.isPause) {
|
||||
if (this.isPause || (this.mindMap.opt.enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)) {
|
||||
return
|
||||
}
|
||||
Object.keys(this.shortcutMap).forEach(key => {
|
||||
if (this.checkKey(e, key)) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
// 粘贴事件不组织,因为要监听paste事件
|
||||
if (!this.checkKey(e, 'Control+v')) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
this.shortcutMap[key].forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
@@ -83,11 +70,7 @@ export default class KeyCommand {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 19:24:53
|
||||
* @Desc: 检查键值是否符合
|
||||
*/
|
||||
// 检查键值是否符合
|
||||
checkKey(e, key) {
|
||||
let o = this.getOriginEventCodeArr(e)
|
||||
let k = this.getKeyCodeArr(key)
|
||||
@@ -107,11 +90,7 @@ export default class KeyCommand {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 19:15:19
|
||||
* @Desc: 获取事件对象里的键值数组
|
||||
*/
|
||||
// 获取事件对象里的键值数组
|
||||
getOriginEventCodeArr(e) {
|
||||
let arr = []
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
@@ -129,11 +108,12 @@ export default class KeyCommand {
|
||||
return arr
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 19:40:11
|
||||
* @Desc: 获取快捷键对应的键值数组
|
||||
*/
|
||||
// 判断是否按下了组合键
|
||||
hasCombinationKey(e) {
|
||||
return e.ctrlKey || e.metaKey || e.altKey || e.shiftKey
|
||||
}
|
||||
|
||||
// 获取快捷键对应的键值数组
|
||||
getKeyCodeArr(key) {
|
||||
let keyArr = key.split(/\s*\+\s*/)
|
||||
let arr = []
|
||||
@@ -143,10 +123,8 @@ export default class KeyCommand {
|
||||
return arr
|
||||
}
|
||||
|
||||
// 添加快捷键命令
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 15:23:00
|
||||
* @Desc: 添加快捷键命令
|
||||
* Enter
|
||||
* Tab | Insert
|
||||
* Shift + a
|
||||
@@ -161,12 +139,7 @@ export default class KeyCommand {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-07-27 14:06:16
|
||||
* @Desc: 移除快捷键命令
|
||||
*/
|
||||
// 移除快捷键命令
|
||||
removeShortcut(key, fn) {
|
||||
key.split(/\s*\|\s*/).forEach(item => {
|
||||
if (this.shortcutMap[item]) {
|
||||
@@ -185,11 +158,7 @@ export default class KeyCommand {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-14 08:49:58
|
||||
* @Desc: 获取指定快捷键的处理函数
|
||||
*/
|
||||
// 获取指定快捷键的处理函数
|
||||
getShortcutFn(key) {
|
||||
let res = []
|
||||
key.split(/\s*\|\s*/).forEach(item => {
|
||||
179
simple-mind-map/src/core/event/Event.js
Normal file
@@ -0,0 +1,179 @@
|
||||
import EventEmitter from 'eventemitter3'
|
||||
import { CONSTANTS } from '../../constants/constant'
|
||||
|
||||
// 事件类
|
||||
class Event extends EventEmitter {
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
super()
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
this.isLeftMousedown = false
|
||||
this.isRightMousedown = false
|
||||
this.isMiddleMousedown = false
|
||||
this.mousedownPos = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
this.mousemovePos = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
this.mousemoveOffset = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
this.bindFn()
|
||||
this.bind()
|
||||
}
|
||||
|
||||
// 绑定函数上下文
|
||||
bindFn() {
|
||||
this.onBodyClick = this.onBodyClick.bind(this)
|
||||
this.onDrawClick = this.onDrawClick.bind(this)
|
||||
this.onMousedown = this.onMousedown.bind(this)
|
||||
this.onMousemove = this.onMousemove.bind(this)
|
||||
this.onMouseup = this.onMouseup.bind(this)
|
||||
this.onMousewheel = this.onMousewheel.bind(this)
|
||||
this.onContextmenu = this.onContextmenu.bind(this)
|
||||
this.onSvgMousedown = this.onSvgMousedown.bind(this)
|
||||
this.onKeyup = this.onKeyup.bind(this)
|
||||
this.onMouseenter = this.onMouseenter.bind(this)
|
||||
this.onMouseleave = this.onMouseleave.bind(this)
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
bind() {
|
||||
document.body.addEventListener('click', this.onBodyClick)
|
||||
this.mindMap.svg.on('click', this.onDrawClick)
|
||||
this.mindMap.el.addEventListener('mousedown', this.onMousedown)
|
||||
this.mindMap.svg.on('mousedown', this.onSvgMousedown)
|
||||
window.addEventListener('mousemove', this.onMousemove)
|
||||
window.addEventListener('mouseup', this.onMouseup)
|
||||
this.mindMap.el.addEventListener('wheel', this.onMousewheel)
|
||||
this.mindMap.svg.on('contextmenu', this.onContextmenu)
|
||||
this.mindMap.svg.on('mouseenter', this.onMouseenter)
|
||||
this.mindMap.svg.on('mouseleave', this.onMouseleave)
|
||||
window.addEventListener('keyup', this.onKeyup)
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
unbind() {
|
||||
document.body.removeEventListener('click', this.onBodyClick)
|
||||
this.mindMap.svg.off('click', this.onDrawClick)
|
||||
this.mindMap.el.removeEventListener('mousedown', this.onMousedown)
|
||||
window.removeEventListener('mousemove', this.onMousemove)
|
||||
window.removeEventListener('mouseup', this.onMouseup)
|
||||
this.mindMap.el.removeEventListener('wheel', this.onMousewheel)
|
||||
this.mindMap.svg.off('contextmenu', this.onContextmenu)
|
||||
this.mindMap.svg.off('mouseenter', this.onMouseenter)
|
||||
this.mindMap.svg.off('mouseleave', this.onMouseleave)
|
||||
window.removeEventListener('keyup', this.onKeyup)
|
||||
}
|
||||
|
||||
// 画布的单击事件
|
||||
onDrawClick(e) {
|
||||
this.emit('draw_click', e)
|
||||
}
|
||||
|
||||
// 页面的单击事件
|
||||
onBodyClick(e) {
|
||||
this.emit('body_click', e)
|
||||
}
|
||||
|
||||
// svg画布的鼠标按下事件
|
||||
onSvgMousedown(e) {
|
||||
this.emit('svg_mousedown', e)
|
||||
}
|
||||
|
||||
// 鼠标按下事件
|
||||
onMousedown(e) {
|
||||
// 鼠标左键
|
||||
if (e.which === 1) {
|
||||
this.isLeftMousedown = true
|
||||
} else if (e.which === 3) {
|
||||
this.isRightMousedown = true
|
||||
} else if (e.which === 2) {
|
||||
this.isMiddleMousedown = true
|
||||
}
|
||||
this.mousedownPos.x = e.clientX
|
||||
this.mousedownPos.y = e.clientY
|
||||
this.emit('mousedown', e, this)
|
||||
}
|
||||
|
||||
// 鼠标移动事件
|
||||
onMousemove(e) {
|
||||
let { useLeftKeySelectionRightKeyDrag } = this.mindMap.opt
|
||||
this.mousemovePos.x = e.clientX
|
||||
this.mousemovePos.y = e.clientY
|
||||
this.mousemoveOffset.x = e.clientX - this.mousedownPos.x
|
||||
this.mousemoveOffset.y = e.clientY - this.mousedownPos.y
|
||||
this.emit('mousemove', e, this)
|
||||
if (
|
||||
this.isMiddleMousedown ||
|
||||
(useLeftKeySelectionRightKeyDrag
|
||||
? this.isRightMousedown
|
||||
: this.isLeftMousedown)
|
||||
) {
|
||||
e.preventDefault()
|
||||
this.emit('drag', e, this)
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标松开事件
|
||||
onMouseup(e) {
|
||||
this.isLeftMousedown = false
|
||||
this.isRightMousedown = false
|
||||
this.isMiddleMousedown = false
|
||||
this.emit('mouseup', e, this)
|
||||
}
|
||||
|
||||
// 鼠标滚动
|
||||
onMousewheel(e) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let dir
|
||||
// 解决mac触控板双指缩放方向相反的问题
|
||||
if (e.ctrlKey) {
|
||||
if (e.deltaY > 0) dir = CONSTANTS.DIR.UP
|
||||
if (e.deltaY < 0) dir = CONSTANTS.DIR.DOWN
|
||||
if (e.deltaX > 0) dir = CONSTANTS.DIR.LEFT
|
||||
if (e.deltaX < 0) dir = CONSTANTS.DIR.RIGHT
|
||||
} else {
|
||||
if ((e.wheelDeltaY || e.detail) > 0) dir = CONSTANTS.DIR.UP
|
||||
if ((e.wheelDeltaY || e.detail) < 0) dir = CONSTANTS.DIR.DOWN
|
||||
if ((e.wheelDeltaX || e.detail) > 0) dir = CONSTANTS.DIR.LEFT
|
||||
if ((e.wheelDeltaX || e.detail) < 0) dir = CONSTANTS.DIR.RIGHT
|
||||
}
|
||||
// 判断是否是触控板
|
||||
let isTouchPad = false
|
||||
// mac、windows
|
||||
if (e.wheelDeltaY === e.deltaY * -3 || Math.abs(e.wheelDeltaY) <= 10) {
|
||||
isTouchPad = true
|
||||
}
|
||||
this.emit('mousewheel', e, dir, this, isTouchPad)
|
||||
}
|
||||
|
||||
// 鼠标右键菜单事件
|
||||
onContextmenu(e) {
|
||||
e.preventDefault()
|
||||
this.emit('contextmenu', e)
|
||||
}
|
||||
|
||||
// 按键松开事件
|
||||
onKeyup(e) {
|
||||
this.emit('keyup', e)
|
||||
}
|
||||
|
||||
// 进入
|
||||
onMouseenter(e) {
|
||||
this.emit('svg_mouseenter', e)
|
||||
}
|
||||
|
||||
// 离开
|
||||
onMouseleave(e) {
|
||||
this.emit('svg_mouseleave', e)
|
||||
}
|
||||
}
|
||||
|
||||
export default Event
|
||||
1327
simple-mind-map/src/core/render/Render.js
Normal file
254
simple-mind-map/src/core/render/TextEdit.js
Normal file
@@ -0,0 +1,254 @@
|
||||
import { getStrWithBrFromHtml, checkNodeOuter } from '../../utils'
|
||||
import { ERROR_TYPES } from '../../constants/constant'
|
||||
|
||||
// 节点文字编辑类
|
||||
export default class TextEdit {
|
||||
// 构造函数
|
||||
constructor(renderer) {
|
||||
this.renderer = renderer
|
||||
this.mindMap = renderer.mindMap
|
||||
// 当前编辑的节点
|
||||
this.currentNode = null
|
||||
// 文本编辑框
|
||||
this.textEditNode = null
|
||||
// 文本编辑框是否显示
|
||||
this.showTextEdit = false
|
||||
// 如果编辑过程中缩放画布了,那么缓存当前编辑的内容
|
||||
this.cacheEditingText = ''
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
// 事件
|
||||
bindEvent() {
|
||||
this.show = this.show.bind(this)
|
||||
this.onScale = this.onScale.bind(this)
|
||||
// 节点双击事件
|
||||
this.mindMap.on('node_dblclick', this.show)
|
||||
// 点击事件
|
||||
this.mindMap.on('draw_click', () => {
|
||||
// 隐藏文本编辑框
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
this.mindMap.on('body_click', () => {
|
||||
// 隐藏文本编辑框
|
||||
if (this.mindMap.opt.isEndNodeTextEditOnClickOuter) {
|
||||
this.hideEditTextBox()
|
||||
}
|
||||
})
|
||||
this.mindMap.on('svg_mousedown', () => {
|
||||
// 隐藏文本编辑框
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
// 展开收缩按钮点击事件
|
||||
this.mindMap.on('expand_btn_click', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
// 节点激活前事件
|
||||
this.mindMap.on('before_node_active', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
// 注册编辑快捷键
|
||||
this.mindMap.keyCommand.addShortcut('F2', () => {
|
||||
if (this.renderer.activeNodeList.length <= 0) {
|
||||
return
|
||||
}
|
||||
this.show(this.renderer.activeNodeList[0])
|
||||
})
|
||||
this.mindMap.on('scale', this.onScale)
|
||||
// // 监听按键事件,判断是否自动进入文本编辑模式
|
||||
if (this.mindMap.opt.enableAutoEnterTextEditWhenKeydown) {
|
||||
window.addEventListener('keydown', e => {
|
||||
const activeNodeList = this.mindMap.renderer.activeNodeList
|
||||
if (activeNodeList.length <= 0 || activeNodeList.length > 1) return
|
||||
const node = activeNodeList[0]
|
||||
// 当正在输入中文或英文或数字时,如果没有按下组合键,那么自动进入文本编辑模式
|
||||
if (node && this.checkIsAutoEnterTextEditKey(e)) {
|
||||
this.show(node)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否是自动进入文本编模式的按钮
|
||||
checkIsAutoEnterTextEditKey(e) {
|
||||
const keyCode = e.keyCode
|
||||
return (
|
||||
(keyCode === 229 ||
|
||||
(keyCode >= 65 && keyCode <= 90) ||
|
||||
(keyCode >= 48 && keyCode <= 57)) &&
|
||||
!this.mindMap.keyCommand.hasCombinationKey(e)
|
||||
)
|
||||
}
|
||||
|
||||
// 注册临时快捷键
|
||||
registerTmpShortcut() {
|
||||
// 注册回车快捷键
|
||||
this.mindMap.keyCommand.addShortcut('Enter', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Tab', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
}
|
||||
|
||||
// 显示文本编辑框
|
||||
// isInserting:是否是刚创建的节点
|
||||
async show(node, e, isInserting = false) {
|
||||
// 使用了自定义节点内容那么不响应编辑事件
|
||||
if (node.isUseCustomNodeContent()) {
|
||||
return
|
||||
}
|
||||
let { beforeTextEdit } = this.mindMap.opt
|
||||
if (typeof beforeTextEdit === 'function') {
|
||||
let isShow = false
|
||||
try {
|
||||
isShow = await beforeTextEdit(node, isInserting)
|
||||
} catch (error) {
|
||||
isShow = false
|
||||
this.mindMap.opt.errorHandler(ERROR_TYPES.BEFORE_TEXT_EDIT_ERROR, error)
|
||||
}
|
||||
if (!isShow) return
|
||||
}
|
||||
this.currentNode = node
|
||||
let { offsetLeft, offsetTop } = checkNodeOuter(this.mindMap, node)
|
||||
this.mindMap.view.translateXY(offsetLeft, offsetTop)
|
||||
let rect = node._textData.node.node.getBoundingClientRect()
|
||||
if (this.mindMap.richText) {
|
||||
this.mindMap.richText.showEditText(node, rect, isInserting)
|
||||
return
|
||||
}
|
||||
this.showEditTextBox(node, rect, isInserting)
|
||||
}
|
||||
|
||||
// 处理画布缩放
|
||||
onScale() {
|
||||
if (!this.currentNode) return
|
||||
if (this.mindMap.richText) {
|
||||
this.mindMap.richText.cacheEditingText =
|
||||
this.mindMap.richText.getEditText()
|
||||
this.mindMap.richText.showTextEdit = false
|
||||
} else {
|
||||
this.cacheEditingText = this.getEditText()
|
||||
this.showTextEdit = false
|
||||
}
|
||||
this.show(this.currentNode)
|
||||
}
|
||||
|
||||
// 显示文本编辑框
|
||||
showEditTextBox(node, rect, isInserting) {
|
||||
if (this.showTextEdit) return
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
this.registerTmpShortcut()
|
||||
if (!this.textEditNode) {
|
||||
this.textEditNode = document.createElement('div')
|
||||
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
|
||||
this.textEditNode.setAttribute('contenteditable', true)
|
||||
this.textEditNode.addEventListener('keyup', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
this.textEditNode.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
this.textEditNode.addEventListener('mousedown', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
this.textEditNode.addEventListener('keydown', e => {
|
||||
if (this.checkIsAutoEnterTextEditKey(e)) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
})
|
||||
const targetNode =
|
||||
this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.textEditNode)
|
||||
}
|
||||
let scale = this.mindMap.view.scale
|
||||
let lineHeight = node.style.merge('lineHeight')
|
||||
let fontSize = node.style.merge('fontSize')
|
||||
let textLines = (this.cacheEditingText || node.nodeData.data.text).split(
|
||||
/\n/gim
|
||||
)
|
||||
let isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true'
|
||||
node.style.domText(this.textEditNode, scale, isMultiLine)
|
||||
this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
|
||||
this.textEditNode.innerHTML = textLines.join('<br>')
|
||||
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
|
||||
this.textEditNode.style.minHeight = rect.height + 6 + 'px'
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
this.textEditNode.style.top = rect.top + 'px'
|
||||
this.textEditNode.style.display = 'block'
|
||||
this.textEditNode.style.maxWidth =
|
||||
this.mindMap.opt.textAutoWrapWidth * scale + 'px'
|
||||
if (isMultiLine && lineHeight !== 1) {
|
||||
this.textEditNode.style.transform = `translateY(${
|
||||
-((lineHeight * fontSize - fontSize) / 2) * scale
|
||||
}px)`
|
||||
}
|
||||
this.showTextEdit = true
|
||||
// 选中文本
|
||||
// if (!this.cacheEditingText) {
|
||||
// this.selectNodeText()
|
||||
// }
|
||||
if (isInserting) {
|
||||
this.selectNodeText()
|
||||
} else {
|
||||
this.focus()
|
||||
}
|
||||
this.cacheEditingText = ''
|
||||
}
|
||||
|
||||
// 聚焦
|
||||
focus() {
|
||||
let selection = window.getSelection()
|
||||
let range = document.createRange()
|
||||
range.selectNodeContents(this.textEditNode)
|
||||
range.collapse()
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
}
|
||||
|
||||
// 选中文本
|
||||
selectNodeText() {
|
||||
let selection = window.getSelection()
|
||||
let range = document.createRange()
|
||||
range.selectNodeContents(this.textEditNode)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
}
|
||||
|
||||
// 获取当前正在编辑的内容
|
||||
getEditText() {
|
||||
return getStrWithBrFromHtml(this.textEditNode.innerHTML)
|
||||
}
|
||||
|
||||
// 隐藏文本编辑框
|
||||
hideEditTextBox() {
|
||||
this.currentNode = null
|
||||
if (this.mindMap.richText) {
|
||||
return this.mindMap.richText.hideEditText()
|
||||
}
|
||||
if (!this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
this.renderer.activeNodeList.forEach(node => {
|
||||
let str = this.getEditText()
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', node, str)
|
||||
if (node.isGeneralization) {
|
||||
// 概要节点
|
||||
node.generalizationBelongNode.updateGeneralization()
|
||||
}
|
||||
this.mindMap.render()
|
||||
})
|
||||
this.mindMap.emit(
|
||||
'hide_text_edit',
|
||||
this.textEditNode,
|
||||
this.renderer.activeNodeList
|
||||
)
|
||||
this.textEditNode.style.display = 'none'
|
||||
this.textEditNode.innerHTML = ''
|
||||
this.textEditNode.style.fontFamily = 'inherit'
|
||||
this.textEditNode.style.fontSize = 'inherit'
|
||||
this.textEditNode.style.fontWeight = 'normal'
|
||||
this.textEditNode.style.transform = 'translateY(0)'
|
||||
this.showTextEdit = false
|
||||
}
|
||||
}
|
||||
850
simple-mind-map/src/core/render/node/Node.js
Normal file
@@ -0,0 +1,850 @@
|
||||
import Style from './Style'
|
||||
import Shape from './Shape'
|
||||
import { G, ForeignObject, SVG } from '@svgdotjs/svg.js'
|
||||
import nodeGeneralizationMethods from './nodeGeneralization'
|
||||
import nodeExpandBtnMethods from './nodeExpandBtn'
|
||||
import nodeCommandWrapsMethods from './nodeCommandWraps'
|
||||
import nodeCreateContentsMethods from './nodeCreateContents'
|
||||
import nodeExpandBtnPlaceholderRectMethods from './nodeExpandBtnPlaceholderRect'
|
||||
import { CONSTANTS } from '../../../constants/constant'
|
||||
|
||||
// 节点类
|
||||
class Node {
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
// 节点数据
|
||||
this.nodeData = this.handleData(opt.data || {})
|
||||
// id
|
||||
this.uid = opt.uid
|
||||
// 控制实例
|
||||
this.mindMap = opt.mindMap
|
||||
// 渲染实例
|
||||
this.renderer = opt.renderer
|
||||
// 渲染器
|
||||
this.draw = opt.draw || null
|
||||
// 样式实例
|
||||
this.style = new Style(this)
|
||||
// 形状实例
|
||||
this.shapeInstance = new Shape(this)
|
||||
this.shapePadding = {
|
||||
paddingX: 0,
|
||||
paddingY: 0
|
||||
}
|
||||
// 是否是根节点
|
||||
this.isRoot = opt.isRoot === undefined ? false : opt.isRoot
|
||||
// 是否是概要节点
|
||||
this.isGeneralization =
|
||||
opt.isGeneralization === undefined ? false : opt.isGeneralization
|
||||
this.generalizationBelongNode = null
|
||||
// 节点层级
|
||||
this.layerIndex = opt.layerIndex === undefined ? 0 : opt.layerIndex
|
||||
// 节点宽
|
||||
this.width = opt.width || 0
|
||||
// 节点高
|
||||
this.height = opt.height || 0
|
||||
// left
|
||||
this._left = opt.left || 0
|
||||
// top
|
||||
this._top = opt.top || 0
|
||||
// 自定义位置
|
||||
this.customLeft = opt.data.data.customLeft || undefined
|
||||
this.customTop = opt.data.data.customTop || undefined
|
||||
// 是否正在拖拽中
|
||||
this.isDrag = false
|
||||
// 父节点
|
||||
this.parent = opt.parent || null
|
||||
// 子节点
|
||||
this.children = opt.children || []
|
||||
// 节点内容的容器
|
||||
this.group = null
|
||||
this.shapeNode = null // 节点形状节点
|
||||
// 节点内容对象
|
||||
this._customNodeContent = null
|
||||
this._imgData = null
|
||||
this._iconData = null
|
||||
this._textData = null
|
||||
this._hyperlinkData = null
|
||||
this._tagData = null
|
||||
this._noteData = null
|
||||
this.noteEl = null
|
||||
this._expandBtn = null
|
||||
this._lastExpandBtnType = null
|
||||
this._showExpandBtn = false
|
||||
this._openExpandNode = null
|
||||
this._closeExpandNode = null
|
||||
this._fillExpandNode = null
|
||||
this._lines = []
|
||||
this._generalizationLine = null
|
||||
this._generalizationNode = null
|
||||
this._unVisibleRectRegionNode = null
|
||||
this._isMouseenter = false
|
||||
// 尺寸信息
|
||||
this._rectInfo = {
|
||||
imgContentWidth: 0,
|
||||
imgContentHeight: 0,
|
||||
textContentWidth: 0,
|
||||
textContentHeight: 0
|
||||
}
|
||||
// 概要节点的宽高
|
||||
this._generalizationNodeWidth = 0
|
||||
this._generalizationNodeHeight = 0
|
||||
// 各种文字信息的间距
|
||||
this.textContentItemMargin = this.mindMap.opt.textContentMargin
|
||||
// 图片和文字节点的间距
|
||||
this.blockContentMargin = this.mindMap.opt.imgTextMargin
|
||||
// 展开收缩按钮尺寸
|
||||
this.expandBtnSize = this.mindMap.opt.expandBtnSize
|
||||
// 是否是多选节点
|
||||
this.isMultipleChoice = false
|
||||
// 是否需要重新layout
|
||||
this.needLayout = false
|
||||
// 当前是否是隐藏状态
|
||||
this.isHide = false
|
||||
// 概要相关方法
|
||||
Object.keys(nodeGeneralizationMethods).forEach(item => {
|
||||
this[item] = nodeGeneralizationMethods[item].bind(this)
|
||||
})
|
||||
// 展开收起按钮相关方法
|
||||
Object.keys(nodeExpandBtnMethods).forEach(item => {
|
||||
this[item] = nodeExpandBtnMethods[item].bind(this)
|
||||
})
|
||||
// 展开收起按钮占位元素相关方法
|
||||
Object.keys(nodeExpandBtnPlaceholderRectMethods).forEach(item => {
|
||||
this[item] = nodeExpandBtnPlaceholderRectMethods[item].bind(this)
|
||||
})
|
||||
// 命令的相关方法
|
||||
Object.keys(nodeCommandWrapsMethods).forEach(item => {
|
||||
this[item] = nodeCommandWrapsMethods[item].bind(this)
|
||||
})
|
||||
// 创建节点内容的相关方法
|
||||
Object.keys(nodeCreateContentsMethods).forEach(item => {
|
||||
this[item] = nodeCreateContentsMethods[item].bind(this)
|
||||
})
|
||||
// 初始化
|
||||
this.getSize()
|
||||
}
|
||||
|
||||
// 支持自定义位置
|
||||
get left() {
|
||||
return this.customLeft || this._left
|
||||
}
|
||||
|
||||
set left(val) {
|
||||
this._left = val
|
||||
}
|
||||
|
||||
get top() {
|
||||
return this.customTop || this._top
|
||||
}
|
||||
|
||||
set top(val) {
|
||||
this._top = val
|
||||
}
|
||||
|
||||
// 复位部分布局时会重新设置的数据
|
||||
reset() {
|
||||
this.children = []
|
||||
this.parent = null
|
||||
this.isRoot = false
|
||||
this.layerIndex = 0
|
||||
this.left = 0
|
||||
this.top = 0
|
||||
}
|
||||
|
||||
// 处理数据
|
||||
handleData(data) {
|
||||
data.data.expand = data.data.expand === false ? false : true
|
||||
data.data.isActive = data.data.isActive === true ? true : false
|
||||
data.children = data.children || []
|
||||
return data
|
||||
}
|
||||
|
||||
// 创建节点的各个内容对象数据
|
||||
createNodeData() {
|
||||
// 自定义节点内容
|
||||
let { isUseCustomNodeContent, customCreateNodeContent } = this.mindMap.opt
|
||||
if (isUseCustomNodeContent && customCreateNodeContent) {
|
||||
this._customNodeContent = customCreateNodeContent(this)
|
||||
}
|
||||
// 如果没有返回内容,那么还是使用内置的节点内容
|
||||
if (this._customNodeContent) return
|
||||
this._imgData = this.createImgNode()
|
||||
this._iconData = this.createIconNode()
|
||||
this._textData = this.createTextNode()
|
||||
this._hyperlinkData = this.createHyperlinkNode()
|
||||
this._tagData = this.createTagNode()
|
||||
this._noteData = this.createNoteNode()
|
||||
}
|
||||
|
||||
// 计算节点的宽高
|
||||
getSize() {
|
||||
this.updateGeneralization()
|
||||
this.createNodeData()
|
||||
let { width, height } = this.getNodeRect()
|
||||
// 判断节点尺寸是否有变化
|
||||
let changed = this.width !== width || this.height !== height
|
||||
this.width = width
|
||||
this.height = height
|
||||
return changed
|
||||
}
|
||||
|
||||
// 计算节点尺寸信息
|
||||
getNodeRect() {
|
||||
// 自定义节点内容
|
||||
if (this.isUseCustomNodeContent()) {
|
||||
let rect = this.measureCustomNodeContentSize(this._customNodeContent)
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
}
|
||||
}
|
||||
// 宽高
|
||||
let imgContentWidth = 0
|
||||
let imgContentHeight = 0
|
||||
let textContentWidth = 0
|
||||
let textContentHeight = 0
|
||||
// 存在图片
|
||||
if (this._imgData) {
|
||||
this._rectInfo.imgContentWidth = imgContentWidth = this._imgData.width
|
||||
this._rectInfo.imgContentHeight = imgContentHeight = this._imgData.height
|
||||
}
|
||||
// 图标
|
||||
if (this._iconData.length > 0) {
|
||||
textContentWidth += this._iconData.reduce((sum, cur) => {
|
||||
textContentHeight = Math.max(textContentHeight, cur.height)
|
||||
return (sum += cur.width + this.textContentItemMargin)
|
||||
}, 0)
|
||||
}
|
||||
// 文字
|
||||
if (this._textData) {
|
||||
textContentWidth += this._textData.width
|
||||
textContentHeight = Math.max(textContentHeight, this._textData.height)
|
||||
}
|
||||
// 超链接
|
||||
if (this._hyperlinkData) {
|
||||
textContentWidth += this._hyperlinkData.width
|
||||
textContentHeight = Math.max(
|
||||
textContentHeight,
|
||||
this._hyperlinkData.height
|
||||
)
|
||||
}
|
||||
// 标签
|
||||
if (this._tagData.length > 0) {
|
||||
textContentWidth += this._tagData.reduce((sum, cur) => {
|
||||
textContentHeight = Math.max(textContentHeight, cur.height)
|
||||
return (sum += cur.width + this.textContentItemMargin)
|
||||
}, 0)
|
||||
}
|
||||
// 备注
|
||||
if (this._noteData) {
|
||||
textContentWidth += this._noteData.width
|
||||
textContentHeight = Math.max(textContentHeight, this._noteData.height)
|
||||
}
|
||||
// 文字内容部分的尺寸
|
||||
this._rectInfo.textContentWidth = textContentWidth
|
||||
this._rectInfo.textContentHeight = textContentHeight
|
||||
// 间距
|
||||
let margin =
|
||||
imgContentHeight > 0 && textContentHeight > 0
|
||||
? this.blockContentMargin
|
||||
: 0
|
||||
let { paddingX, paddingY } = this.getPaddingVale()
|
||||
// 纯内容宽高
|
||||
let _width = Math.max(imgContentWidth, textContentWidth)
|
||||
let _height = imgContentHeight + textContentHeight
|
||||
// 计算节点形状需要的附加内边距
|
||||
let { paddingX: shapePaddingX, paddingY: shapePaddingY } =
|
||||
this.shapeInstance.getShapePadding(_width, _height, paddingX, paddingY)
|
||||
this.shapePadding.paddingX = shapePaddingX
|
||||
this.shapePadding.paddingY = shapePaddingY
|
||||
// 边框宽度,因为边框是以中线向两端发散,所以边框会超出节点
|
||||
const borderWidth = this.getBorderWidth()
|
||||
return {
|
||||
width: _width + paddingX * 2 + shapePaddingX * 2 + borderWidth,
|
||||
height: _height + paddingY * 2 + margin + shapePaddingY * 2 + borderWidth
|
||||
}
|
||||
}
|
||||
|
||||
// 定位节点内容
|
||||
layout() {
|
||||
// 清除之前的内容
|
||||
this.group.clear()
|
||||
let { width, height, textContentItemMargin } = this
|
||||
let { paddingY } = this.getPaddingVale()
|
||||
const halfBorderWidth = this.getBorderWidth() / 2
|
||||
paddingY += this.shapePadding.paddingY + halfBorderWidth
|
||||
// 节点形状
|
||||
this.shapeNode = this.shapeInstance.createShape()
|
||||
this.shapeNode.addClass('smm-node-shape')
|
||||
this.shapeNode.translate(halfBorderWidth, halfBorderWidth)
|
||||
this.group.add(this.shapeNode)
|
||||
this.updateNodeShape()
|
||||
// 渲染一个隐藏的矩形区域,用来触发展开收起按钮的显示
|
||||
this.renderExpandBtnPlaceholderRect()
|
||||
// 概要节点添加一个带所属节点id的类名
|
||||
if (this.isGeneralization && this.generalizationBelongNode) {
|
||||
this.group.addClass('generalization_' + this.generalizationBelongNode.uid)
|
||||
}
|
||||
// 如果存在自定义节点内容,那么使用自定义节点内容
|
||||
if (this.isUseCustomNodeContent()) {
|
||||
let foreignObject = new ForeignObject()
|
||||
foreignObject.width(width)
|
||||
foreignObject.height(height)
|
||||
foreignObject.add(SVG(this._customNodeContent))
|
||||
this.group.add(foreignObject)
|
||||
return
|
||||
}
|
||||
// 图片节点
|
||||
let imgHeight = 0
|
||||
if (this._imgData) {
|
||||
imgHeight = this._imgData.height
|
||||
this.group.add(this._imgData.node)
|
||||
this._imgData.node.cx(width / 2).y(paddingY)
|
||||
}
|
||||
// 内容节点
|
||||
let textContentNested = new G()
|
||||
let textContentOffsetX = 0
|
||||
// icon
|
||||
let iconNested = new G()
|
||||
if (this._iconData && this._iconData.length > 0) {
|
||||
let iconLeft = 0
|
||||
this._iconData.forEach(item => {
|
||||
item.node
|
||||
.x(textContentOffsetX + iconLeft)
|
||||
.y((this._rectInfo.textContentHeight - item.height) / 2)
|
||||
iconNested.add(item.node)
|
||||
iconLeft += item.width + textContentItemMargin
|
||||
})
|
||||
textContentNested.add(iconNested)
|
||||
textContentOffsetX += iconLeft
|
||||
}
|
||||
// 文字
|
||||
if (this._textData) {
|
||||
this._textData.node.attr('data-offsetx', textContentOffsetX)
|
||||
this._textData.node.x(textContentOffsetX).y(0)
|
||||
textContentNested.add(this._textData.node)
|
||||
textContentOffsetX += this._textData.width + textContentItemMargin
|
||||
}
|
||||
// 超链接
|
||||
if (this._hyperlinkData) {
|
||||
this._hyperlinkData.node
|
||||
.x(textContentOffsetX)
|
||||
.y((this._rectInfo.textContentHeight - this._hyperlinkData.height) / 2)
|
||||
textContentNested.add(this._hyperlinkData.node)
|
||||
textContentOffsetX += this._hyperlinkData.width + textContentItemMargin
|
||||
}
|
||||
// 标签
|
||||
let tagNested = new G()
|
||||
if (this._tagData && this._tagData.length > 0) {
|
||||
let tagLeft = 0
|
||||
this._tagData.forEach(item => {
|
||||
item.node
|
||||
.x(textContentOffsetX + tagLeft)
|
||||
.y((this._rectInfo.textContentHeight - item.height) / 2)
|
||||
tagNested.add(item.node)
|
||||
tagLeft += item.width + textContentItemMargin
|
||||
})
|
||||
textContentNested.add(tagNested)
|
||||
textContentOffsetX += tagLeft
|
||||
}
|
||||
// 备注
|
||||
if (this._noteData) {
|
||||
this._noteData.node
|
||||
.x(textContentOffsetX)
|
||||
.y((this._rectInfo.textContentHeight - this._noteData.height) / 2)
|
||||
textContentNested.add(this._noteData.node)
|
||||
textContentOffsetX += this._noteData.width
|
||||
}
|
||||
// 文字内容整体
|
||||
textContentNested.translate(
|
||||
width / 2 - textContentNested.bbox().width / 2,
|
||||
imgHeight +
|
||||
paddingY +
|
||||
(imgHeight > 0 && this._rectInfo.textContentHeight > 0
|
||||
? this.blockContentMargin
|
||||
: 0)
|
||||
)
|
||||
this.group.add(textContentNested)
|
||||
}
|
||||
|
||||
// 给节点绑定事件
|
||||
bindGroupEvent() {
|
||||
// 单击事件,选中节点
|
||||
this.group.on('click', e => {
|
||||
this.mindMap.emit('node_click', this, e)
|
||||
if (this.isMultipleChoice) {
|
||||
e.stopPropagation()
|
||||
this.isMultipleChoice = false
|
||||
return
|
||||
}
|
||||
this.active(e)
|
||||
})
|
||||
this.group.on('mousedown', e => {
|
||||
if (this.isRoot && e.which === 3 && !this.mindMap.opt.readonly) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
if (!this.isRoot && e.which !== 2 && !this.mindMap.opt.readonly) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
// 多选和取消多选
|
||||
if (e.ctrlKey && this.mindMap.opt.enableCtrlKeyNodeSelection) {
|
||||
this.isMultipleChoice = true
|
||||
let isActive = this.nodeData.data.isActive
|
||||
if (!isActive)
|
||||
this.mindMap.emit(
|
||||
'before_node_active',
|
||||
this,
|
||||
this.renderer.activeNodeList
|
||||
)
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this, !isActive)
|
||||
this.mindMap.renderer[isActive ? 'removeActiveNode' : 'addActiveNode'](
|
||||
this
|
||||
)
|
||||
this.mindMap.emit(
|
||||
'node_active',
|
||||
isActive ? null : this,
|
||||
this.mindMap.renderer.activeNodeList
|
||||
)
|
||||
}
|
||||
this.mindMap.emit('node_mousedown', this, e)
|
||||
})
|
||||
this.group.on('mouseup', e => {
|
||||
if (!this.isRoot && e.which !== 2 && !this.mindMap.opt.readonly) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
this.mindMap.emit('node_mouseup', this, e)
|
||||
})
|
||||
this.group.on('mouseenter', e => {
|
||||
this._isMouseenter = true
|
||||
// 显示展开收起按钮
|
||||
this.showExpandBtn()
|
||||
this.mindMap.emit('node_mouseenter', this, e)
|
||||
})
|
||||
this.group.on('mouseleave', e => {
|
||||
this._isMouseenter = false
|
||||
this.hideExpandBtn()
|
||||
this.mindMap.emit('node_mouseleave', this, e)
|
||||
})
|
||||
// 双击事件
|
||||
this.group.on('dblclick', e => {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
e.stopPropagation()
|
||||
this.mindMap.emit('node_dblclick', this, e)
|
||||
})
|
||||
// 右键菜单事件
|
||||
this.group.on('contextmenu', e => {
|
||||
// 按住ctrl键点击鼠标左键不知为何触发的是contextmenu事件
|
||||
if (this.mindMap.opt.readonly || e.ctrlKey) {
|
||||
// || this.isGeneralization
|
||||
return
|
||||
}
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
if (this.nodeData.data.isActive) {
|
||||
this.renderer.clearActive()
|
||||
}
|
||||
this.active(e)
|
||||
this.mindMap.emit('node_contextmenu', e, this)
|
||||
})
|
||||
}
|
||||
|
||||
// 激活节点
|
||||
active(e) {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
e && e.stopPropagation()
|
||||
if (this.nodeData.data.isActive) {
|
||||
return
|
||||
}
|
||||
this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList)
|
||||
this.renderer.clearActive()
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this, true)
|
||||
this.renderer.addActiveNode(this)
|
||||
this.mindMap.emit('node_active', this, this.renderer.activeNodeList)
|
||||
}
|
||||
|
||||
// 更新节点
|
||||
update(isLayout = false) {
|
||||
if (!this.group) {
|
||||
return
|
||||
}
|
||||
let { alwaysShowExpandBtn } = this.mindMap.opt
|
||||
if (alwaysShowExpandBtn) {
|
||||
// 需要移除展开收缩按钮
|
||||
if (this._expandBtn && this.nodeData.children.length <= 0) {
|
||||
this.removeExpandBtn()
|
||||
} else {
|
||||
// 更新展开收起按钮
|
||||
this.renderExpandBtn()
|
||||
}
|
||||
} else {
|
||||
let { isActive, expand } = this.nodeData.data
|
||||
// 展开状态且非激活状态,且当前鼠标不在它上面,才隐藏
|
||||
if (expand && !isActive && !this._isMouseenter) {
|
||||
this.hideExpandBtn()
|
||||
} else {
|
||||
this.showExpandBtn()
|
||||
}
|
||||
}
|
||||
// 更新概要
|
||||
this.renderGeneralization()
|
||||
// 更新节点位置
|
||||
let t = this.group.transform()
|
||||
// // 如果上次不在可视区内,且本次也不在,那么直接返回
|
||||
// let { left: ox, top: oy } = this.getNodePosInClient(
|
||||
// t.translateX,
|
||||
// t.translateY
|
||||
// )
|
||||
// let oldIsInClient =
|
||||
// ox > 0 && oy > 0 && ox < this.mindMap.width && oy < this.mindMap.height
|
||||
// let { left: nx, top: ny } = this.getNodePosInClient(this.left, this.top)
|
||||
// let newIsNotInClient =
|
||||
// nx + this.width < 0 ||
|
||||
// ny + this.height < 0 ||
|
||||
// nx > this.mindMap.width ||
|
||||
// ny > this.mindMap.height
|
||||
// if (!oldIsInClient && newIsNotInClient) {
|
||||
// if (!this.isHide) {
|
||||
// this.isHide = true
|
||||
// this.group.hide()
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
// // 如果当前是隐藏状态,那么先显示
|
||||
// if (this.isHide) {
|
||||
// this.isHide = false
|
||||
// this.group.show()
|
||||
// }
|
||||
// 如果节点位置没有变化,则返回
|
||||
if (this.left === t.translateX && this.top === t.translateY) return
|
||||
this.group.translate(this.left - t.translateX, this.top - t.translateY)
|
||||
}
|
||||
|
||||
// 获取节点相当于画布的位置
|
||||
getNodePosInClient(_left, _top) {
|
||||
let drawTransform = this.mindMap.draw.transform()
|
||||
let { scaleX, scaleY, translateX, translateY } = drawTransform
|
||||
let left = _left * scaleX + translateX
|
||||
let top = _top * scaleY + translateY
|
||||
return {
|
||||
left,
|
||||
top
|
||||
}
|
||||
}
|
||||
|
||||
// 重新渲染节点,即重新创建节点内容、计算节点大小、计算节点内容布局、更新展开收起按钮,概要及位置
|
||||
reRender() {
|
||||
let sizeChange = this.getSize()
|
||||
this.layout()
|
||||
this.update()
|
||||
return sizeChange
|
||||
}
|
||||
|
||||
// 更新节点形状样式
|
||||
updateNodeShape() {
|
||||
if (!this.shapeNode) return
|
||||
const shape = this.getShape()
|
||||
this.style[shape === CONSTANTS.SHAPE.RECTANGLE ? 'rect' : 'shape'](
|
||||
this.shapeNode
|
||||
)
|
||||
}
|
||||
|
||||
// 递归渲染
|
||||
render(callback = () => {}) {
|
||||
// 节点
|
||||
// 重新渲染连线
|
||||
this.renderLine()
|
||||
let isLayout = false
|
||||
if (!this.group) {
|
||||
isLayout = true
|
||||
// 创建组
|
||||
this.group = new G()
|
||||
this.group.addClass('smm-node')
|
||||
this.group.css({
|
||||
cursor: 'default'
|
||||
})
|
||||
this.bindGroupEvent()
|
||||
this.draw.add(this.group)
|
||||
this.layout()
|
||||
this.update(isLayout)
|
||||
} else {
|
||||
this.draw.add(this.group)
|
||||
if (this.needLayout) {
|
||||
this.needLayout = false
|
||||
this.layout()
|
||||
}
|
||||
this.updateExpandBtnPlaceholderRect()
|
||||
this.update()
|
||||
}
|
||||
// 子节点
|
||||
if (
|
||||
this.children &&
|
||||
this.children.length &&
|
||||
this.nodeData.data.expand !== false
|
||||
) {
|
||||
let index = 0
|
||||
this.children.forEach(item => {
|
||||
item.render(() => {
|
||||
index++
|
||||
if (index >= this.children.length) {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
// 手动插入的节点立即获得焦点并且开启编辑模式
|
||||
if (this.nodeData.inserting) {
|
||||
delete this.nodeData.inserting
|
||||
this.active()
|
||||
setTimeout(() => {
|
||||
this.mindMap.emit('node_dblclick', this, null, true)
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// 递归删除,只是从画布删除,节点容器还在,后续还可以重新插回画布
|
||||
remove() {
|
||||
if (!this.group) return
|
||||
this.group.remove()
|
||||
this.removeGeneralization()
|
||||
this.removeLine()
|
||||
// 子节点
|
||||
if (this.children && this.children.length) {
|
||||
this.children.forEach(item => {
|
||||
item.remove()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 销毁节点,不但会从画布删除,而且原节点直接置空,后续无法再插回画布
|
||||
destroy() {
|
||||
if (!this.group) return
|
||||
this.group.remove()
|
||||
this.removeGeneralization()
|
||||
this.removeLine()
|
||||
this.group = null
|
||||
}
|
||||
|
||||
// 隐藏节点
|
||||
hide() {
|
||||
this.group.hide()
|
||||
this.hideGeneralization()
|
||||
if (this.parent) {
|
||||
let index = this.parent.children.indexOf(this)
|
||||
this.parent._lines[index] && this.parent._lines[index].hide()
|
||||
this._lines.forEach(item => {
|
||||
item.hide()
|
||||
})
|
||||
}
|
||||
// 子节点
|
||||
if (this.children && this.children.length) {
|
||||
this.children.forEach(item => {
|
||||
item.hide()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 显示节点
|
||||
show() {
|
||||
if (!this.group) {
|
||||
return
|
||||
}
|
||||
this.group.show()
|
||||
this.showGeneralization()
|
||||
if (this.parent) {
|
||||
let index = this.parent.children.indexOf(this)
|
||||
this.parent._lines[index] && this.parent._lines[index].show()
|
||||
this._lines.forEach(item => {
|
||||
item.show()
|
||||
})
|
||||
}
|
||||
// 子节点
|
||||
if (this.children && this.children.length) {
|
||||
this.children.forEach(item => {
|
||||
item.show()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 连线
|
||||
renderLine(deep = false) {
|
||||
if (this.nodeData.data.expand === false) {
|
||||
return
|
||||
}
|
||||
let childrenLen = this.nodeData.children.length
|
||||
// 切换为鱼骨结构时,清空根节点和二级节点的连线
|
||||
if (
|
||||
this.mindMap.opt.layout === CONSTANTS.LAYOUT.FISHBONE &&
|
||||
(this.isRoot || this.layerIndex === 1)
|
||||
) {
|
||||
childrenLen = 0
|
||||
}
|
||||
if (childrenLen > this._lines.length) {
|
||||
// 创建缺少的线
|
||||
new Array(childrenLen - this._lines.length).fill(0).forEach(() => {
|
||||
this._lines.push(this.draw.path())
|
||||
})
|
||||
} else if (childrenLen < this._lines.length) {
|
||||
// 删除多余的线
|
||||
this._lines.slice(childrenLen).forEach(line => {
|
||||
line.remove()
|
||||
})
|
||||
this._lines = this._lines.slice(0, childrenLen)
|
||||
}
|
||||
// 画线
|
||||
this.renderer.layout.renderLine(
|
||||
this,
|
||||
this._lines,
|
||||
(line, node) => {
|
||||
// 添加样式
|
||||
this.styleLine(line, node)
|
||||
},
|
||||
this.style.getStyle('lineStyle', true)
|
||||
)
|
||||
// 子级的连线也需要更新
|
||||
if (deep && this.children && this.children.length > 0) {
|
||||
this.children.forEach(item => {
|
||||
item.renderLine(deep)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取节点形状
|
||||
getShape() {
|
||||
// 节点使用功能横线风格的话不支持设置形状,直接使用默认的矩形
|
||||
return this.mindMap.themeConfig.nodeUseLineStyle
|
||||
? CONSTANTS.SHAPE.RECTANGLE
|
||||
: this.style.getStyle('shape', false, false)
|
||||
}
|
||||
|
||||
// 检查节点是否存在自定义数据
|
||||
hasCustomPosition() {
|
||||
return this.customLeft !== undefined && this.customTop !== undefined
|
||||
}
|
||||
|
||||
// 检查节点是否存在自定义位置的祖先节点
|
||||
ancestorHasCustomPosition() {
|
||||
let node = this
|
||||
while (node) {
|
||||
if (node.hasCustomPosition()) {
|
||||
return true
|
||||
}
|
||||
node = node.parent
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 添加子节点
|
||||
addChildren(node) {
|
||||
this.children.push(node)
|
||||
}
|
||||
|
||||
// 设置连线样式
|
||||
styleLine(line, node) {
|
||||
let width =
|
||||
node.getSelfInhertStyle('lineWidth') || node.getStyle('lineWidth', true)
|
||||
let color =
|
||||
node.getSelfInhertStyle('lineColor') || node.getStyle('lineColor', true)
|
||||
let dasharray =
|
||||
node.getSelfInhertStyle('lineDasharray') ||
|
||||
node.getStyle('lineDasharray', true)
|
||||
this.style.line(line, {
|
||||
width,
|
||||
color,
|
||||
dasharray
|
||||
})
|
||||
}
|
||||
|
||||
// 移除连线
|
||||
removeLine() {
|
||||
this._lines.forEach(line => {
|
||||
line.remove()
|
||||
})
|
||||
this._lines = []
|
||||
}
|
||||
|
||||
// 检测当前节点是否是某个节点的祖先节点
|
||||
isParent(node) {
|
||||
if (this === node) {
|
||||
return false
|
||||
}
|
||||
let parent = node.parent
|
||||
while (parent) {
|
||||
if (this === parent) {
|
||||
return true
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 检测当前节点是否是某个节点的兄弟节点
|
||||
isBrother(node) {
|
||||
if (!this.parent || this === node) {
|
||||
return false
|
||||
}
|
||||
return this.parent.children.find(item => {
|
||||
return item === node
|
||||
})
|
||||
}
|
||||
|
||||
// 获取padding值
|
||||
getPaddingVale() {
|
||||
let { isActive } = this.nodeData.data
|
||||
return {
|
||||
paddingX: this.getStyle('paddingX', true, isActive),
|
||||
paddingY: this.getStyle('paddingY', true, isActive)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取某个样式
|
||||
getStyle(prop, root, isActive) {
|
||||
let v = this.style.merge(prop, root, isActive)
|
||||
return v === undefined ? '' : v
|
||||
}
|
||||
|
||||
// 获取自定义样式
|
||||
getSelfStyle(prop) {
|
||||
return this.style.getSelfStyle(prop)
|
||||
}
|
||||
|
||||
// 获取最近一个存在自身自定义样式的祖先节点的自定义样式
|
||||
getParentSelfStyle(prop) {
|
||||
if (this.parent) {
|
||||
return (
|
||||
this.parent.getSelfStyle(prop) || this.parent.getParentSelfStyle(prop)
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 获取自身可继承的自定义样式
|
||||
getSelfInhertStyle(prop) {
|
||||
return (
|
||||
this.getSelfStyle(prop) || // 自身
|
||||
this.getParentSelfStyle(prop)
|
||||
) // 父级
|
||||
}
|
||||
|
||||
// 获取节点非节点状态的边框大小
|
||||
getBorderWidth() {
|
||||
return this.style.merge('borderWidth', false, false) || 0
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
getData(key) {
|
||||
return key ? this.nodeData.data[key] || '' : this.nodeData.data
|
||||
}
|
||||
|
||||
// 是否存在自定义样式
|
||||
hasCustomStyle() {
|
||||
return this.style.hasCustomStyle()
|
||||
}
|
||||
}
|
||||
|
||||
export default Node
|
||||
232
simple-mind-map/src/core/render/node/Shape.js
Normal file
@@ -0,0 +1,232 @@
|
||||
import { Rect, Polygon, Path } from '@svgdotjs/svg.js'
|
||||
import { CONSTANTS } from '../../../constants/constant'
|
||||
|
||||
// 节点形状类
|
||||
export default class Shape {
|
||||
constructor(node) {
|
||||
this.node = node
|
||||
}
|
||||
|
||||
// 形状需要的padding
|
||||
getShapePadding(width, height, paddingX, paddingY) {
|
||||
const shape = this.node.getShape()
|
||||
const defaultPaddingX = 15
|
||||
const defaultPaddingY = 5
|
||||
const actWidth = width + paddingX * 2
|
||||
const actHeight = height + paddingY * 2
|
||||
const actOffset = Math.abs(actWidth - actHeight)
|
||||
switch (shape) {
|
||||
case CONSTANTS.SHAPE.ROUNDED_RECTANGLE:
|
||||
return {
|
||||
paddingX: height > width ? (height - width) / 2 : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case CONSTANTS.SHAPE.DIAMOND:
|
||||
return {
|
||||
paddingX: width / 2,
|
||||
paddingY: height / 2
|
||||
}
|
||||
case CONSTANTS.SHAPE.PARALLELOGRAM:
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case CONSTANTS.SHAPE.OUTER_TRIANGULAR_RECTANGLE:
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case CONSTANTS.SHAPE.INNER_TRIANGULAR_RECTANGLE:
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case CONSTANTS.SHAPE.ELLIPSE:
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: paddingY <= 0 ? defaultPaddingY : 0
|
||||
}
|
||||
case CONSTANTS.SHAPE.CIRCLE:
|
||||
return {
|
||||
paddingX: actHeight > actWidth ? actOffset / 2 : 0,
|
||||
paddingY: actHeight < actWidth ? actOffset / 2 : 0
|
||||
}
|
||||
default:
|
||||
return {
|
||||
paddingX: 0,
|
||||
paddingY: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建形状节点
|
||||
createShape() {
|
||||
const shape = this.node.getShape()
|
||||
const borderWidth = this.node.getBorderWidth()
|
||||
let { width, height } = this.node
|
||||
width -= borderWidth
|
||||
height -= borderWidth
|
||||
let node = null
|
||||
// 矩形
|
||||
if (shape === CONSTANTS.SHAPE.RECTANGLE) {
|
||||
node = new Rect().size(width, height)
|
||||
} else if (shape === CONSTANTS.SHAPE.DIAMOND) {
|
||||
// 菱形
|
||||
node = this.createDiamond()
|
||||
} else if (shape === CONSTANTS.SHAPE.PARALLELOGRAM) {
|
||||
// 平行四边形
|
||||
node = this.createParallelogram()
|
||||
} else if (shape === CONSTANTS.SHAPE.ROUNDED_RECTANGLE) {
|
||||
// 圆角矩形
|
||||
node = this.createRoundedRectangle()
|
||||
} else if (shape === CONSTANTS.SHAPE.OCTAGONAL_RECTANGLE) {
|
||||
// 八角矩形
|
||||
node = this.createOctagonalRectangle()
|
||||
} else if (shape === CONSTANTS.SHAPE.OUTER_TRIANGULAR_RECTANGLE) {
|
||||
// 外三角矩形
|
||||
node = this.createOuterTriangularRectangle()
|
||||
} else if (shape === CONSTANTS.SHAPE.INNER_TRIANGULAR_RECTANGLE) {
|
||||
// 内三角矩形
|
||||
node = this.createInnerTriangularRectangle()
|
||||
} else if (shape === CONSTANTS.SHAPE.ELLIPSE) {
|
||||
// 椭圆
|
||||
node = this.createEllipse()
|
||||
} else if (shape === CONSTANTS.SHAPE.CIRCLE) {
|
||||
// 圆
|
||||
node = this.createCircle()
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// 创建菱形
|
||||
createDiamond() {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
let topX = halfWidth
|
||||
let topY = 0
|
||||
let rightX = width
|
||||
let rightY = halfHeight
|
||||
let bottomX = halfWidth
|
||||
let bottomY = height
|
||||
let leftX = 0
|
||||
let leftY = halfHeight
|
||||
return new Polygon().plot([
|
||||
[topX, topY],
|
||||
[rightX, rightY],
|
||||
[bottomX, bottomY],
|
||||
[leftX, leftY]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建平行四边形
|
||||
createParallelogram() {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return new Polygon().plot([
|
||||
[paddingX, 0],
|
||||
[width, 0],
|
||||
[width - paddingX, height],
|
||||
[0, height]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建圆角矩形
|
||||
createRoundedRectangle() {
|
||||
let { width, height } = this.node
|
||||
let halfHeight = height / 2
|
||||
return new Path().plot(`
|
||||
M${halfHeight},0
|
||||
L${width - halfHeight},0
|
||||
A${height / 2},${height / 2} 0 0,1 ${width - halfHeight},${height}
|
||||
L${halfHeight},${height}
|
||||
A${height / 2},${height / 2} 0 0,1 ${halfHeight},${0}
|
||||
`)
|
||||
}
|
||||
|
||||
// 创建八角矩形
|
||||
createOctagonalRectangle() {
|
||||
let w = 5
|
||||
let { width, height } = this.node
|
||||
return new Polygon().plot([
|
||||
[0, w],
|
||||
[w, 0],
|
||||
[width - w, 0],
|
||||
[width, w],
|
||||
[width, height - w],
|
||||
[width - w, height],
|
||||
[w, height],
|
||||
[0, height - w]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建外三角矩形
|
||||
createOuterTriangularRectangle() {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return new Polygon().plot([
|
||||
[paddingX, 0],
|
||||
[width - paddingX, 0],
|
||||
[width, height / 2],
|
||||
[width - paddingX, height],
|
||||
[paddingX, height],
|
||||
[0, height / 2]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建内三角矩形
|
||||
createInnerTriangularRectangle() {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return new Polygon().plot([
|
||||
[0, 0],
|
||||
[width, 0],
|
||||
[width - paddingX / 2, height / 2],
|
||||
[width, height],
|
||||
[0, height],
|
||||
[paddingX / 2, height / 2]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建椭圆
|
||||
createEllipse() {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
return new Path().plot(`
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
}
|
||||
|
||||
// 创建圆
|
||||
createCircle() {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
return new Path().plot(`
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
// 形状列表
|
||||
export const shapeList = [
|
||||
CONSTANTS.SHAPE.RECTANGLE,
|
||||
CONSTANTS.SHAPE.DIAMOND,
|
||||
CONSTANTS.SHAPE.PARALLELOGRAM,
|
||||
CONSTANTS.SHAPE.ROUNDED_RECTANGLE,
|
||||
CONSTANTS.SHAPE.OCTAGONAL_RECTANGLE,
|
||||
CONSTANTS.SHAPE.OUTER_TRIANGULAR_RECTANGLE,
|
||||
CONSTANTS.SHAPE.INNER_TRIANGULAR_RECTANGLE,
|
||||
CONSTANTS.SHAPE.ELLIPSE,
|
||||
CONSTANTS.SHAPE.CIRCLE
|
||||
]
|
||||
227
simple-mind-map/src/core/render/node/Style.js
Normal file
@@ -0,0 +1,227 @@
|
||||
import { tagColorList, nodeDataNoStylePropList } from '../../../constants/constant'
|
||||
const rootProp = ['paddingX', 'paddingY']
|
||||
const backgroundStyleProps = ['backgroundColor', 'backgroundImage', 'backgroundRepeat', 'backgroundPosition', 'backgroundSize']
|
||||
|
||||
// 样式类
|
||||
class Style {
|
||||
// 设置背景样式
|
||||
static setBackgroundStyle(el, themeConfig) {
|
||||
// 缓存容器元素原本的样式
|
||||
if (!Style.cacheStyle) {
|
||||
Style.cacheStyle = {}
|
||||
let style = window.getComputedStyle(el)
|
||||
backgroundStyleProps.forEach((prop) => {
|
||||
Style.cacheStyle[prop] = style[prop]
|
||||
})
|
||||
}
|
||||
// 设置新样式
|
||||
let { backgroundColor, backgroundImage, backgroundRepeat, backgroundPosition, backgroundSize } = themeConfig
|
||||
el.style.backgroundColor = backgroundColor
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
el.style.backgroundImage = `url(${backgroundImage})`
|
||||
el.style.backgroundRepeat = backgroundRepeat
|
||||
el.style.backgroundPosition = backgroundPosition
|
||||
el.style.backgroundSize = backgroundSize
|
||||
} else {
|
||||
el.style.backgroundImage = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
// 移除背景样式
|
||||
static removeBackgroundStyle(el) {
|
||||
if (!Style.cacheStyle) return
|
||||
backgroundStyleProps.forEach((prop) => {
|
||||
el.style[prop] = Style.cacheStyle[prop]
|
||||
})
|
||||
Style.cacheStyle = null
|
||||
}
|
||||
|
||||
// 构造函数
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
}
|
||||
|
||||
// 合并样式
|
||||
merge(prop, root, isActive) {
|
||||
let themeConfig = this.ctx.mindMap.themeConfig
|
||||
// 三级及以下节点
|
||||
let defaultConfig = themeConfig.node
|
||||
if (root || rootProp.includes(prop)) {
|
||||
// 直接使用最外层样式
|
||||
defaultConfig = themeConfig
|
||||
} else if (this.ctx.isGeneralization) {
|
||||
// 概要节点
|
||||
defaultConfig = themeConfig.generalization
|
||||
} else if (this.ctx.layerIndex === 0) {
|
||||
// 根节点
|
||||
defaultConfig = themeConfig.root
|
||||
} else if (this.ctx.layerIndex === 1) {
|
||||
// 二级节点
|
||||
defaultConfig = themeConfig.second
|
||||
}
|
||||
// 激活状态
|
||||
if (isActive !== undefined ? isActive : this.ctx.nodeData.data.isActive) {
|
||||
if (
|
||||
this.ctx.nodeData.data.activeStyle &&
|
||||
this.ctx.nodeData.data.activeStyle[prop] !== undefined
|
||||
) {
|
||||
return this.ctx.nodeData.data.activeStyle[prop]
|
||||
} else if (defaultConfig.active && defaultConfig.active[prop]) {
|
||||
return defaultConfig.active[prop]
|
||||
}
|
||||
}
|
||||
// 优先使用节点本身的样式
|
||||
return this.getSelfStyle(prop) !== undefined
|
||||
? this.getSelfStyle(prop)
|
||||
: defaultConfig[prop]
|
||||
}
|
||||
|
||||
// 获取某个样式值
|
||||
getStyle(prop, root, isActive) {
|
||||
return this.merge(prop, root, isActive)
|
||||
}
|
||||
|
||||
// 获取自身自定义样式
|
||||
getSelfStyle(prop) {
|
||||
return this.ctx.nodeData.data[prop]
|
||||
}
|
||||
|
||||
// 矩形
|
||||
rect(node) {
|
||||
this.shape(node)
|
||||
node.radius(this.merge('borderRadius'))
|
||||
}
|
||||
|
||||
// 矩形外的其他形状
|
||||
shape(node) {
|
||||
node.fill({
|
||||
color: this.merge('fillColor')
|
||||
})
|
||||
// 节点使用横线样式,不需要渲染非激活状态的边框样式
|
||||
// if (
|
||||
// !this.ctx.isRoot &&
|
||||
// !this.ctx.isGeneralization &&
|
||||
// this.ctx.mindMap.themeConfig.nodeUseLineStyle &&
|
||||
// !this.ctx.nodeData.data.isActive
|
||||
// ) {
|
||||
// return
|
||||
// }
|
||||
node.stroke({
|
||||
color: this.merge('borderColor'),
|
||||
width: this.merge('borderWidth'),
|
||||
dasharray: this.merge('borderDasharray')
|
||||
})
|
||||
}
|
||||
|
||||
// 文字
|
||||
text(node) {
|
||||
node
|
||||
.fill({
|
||||
color: this.merge('color')
|
||||
})
|
||||
.css({
|
||||
'font-family': this.merge('fontFamily'),
|
||||
'font-size': this.merge('fontSize'),
|
||||
'font-weight': this.merge('fontWeight'),
|
||||
'font-style': this.merge('fontStyle'),
|
||||
'text-decoration': this.merge('textDecoration')
|
||||
})
|
||||
}
|
||||
|
||||
// 生成内联样式
|
||||
createStyleText() {
|
||||
return `
|
||||
color: ${this.merge('color')};
|
||||
font-family: ${this.merge('fontFamily')};
|
||||
font-size: ${this.merge('fontSize') + 'px'};
|
||||
font-weight: ${this.merge('fontWeight')};
|
||||
font-style: ${this.merge('fontStyle')};
|
||||
text-decoration: ${this.merge('textDecoration')}
|
||||
`
|
||||
}
|
||||
|
||||
// 获取文本样式
|
||||
getTextFontStyle() {
|
||||
return {
|
||||
italic: this.merge('fontStyle') === 'italic',
|
||||
bold: this.merge('fontWeight'),
|
||||
fontSize: this.merge('fontSize'),
|
||||
fontFamily: this.merge('fontFamily')
|
||||
}
|
||||
}
|
||||
|
||||
// html文字节点
|
||||
domText(node, fontSizeScale = 1, isMultiLine) {
|
||||
node.style.fontFamily = this.merge('fontFamily')
|
||||
node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px'
|
||||
node.style.fontWeight = this.merge('fontWeight') || 'normal'
|
||||
node.style.lineHeight = !isMultiLine ? 'normal' : this.merge('lineHeight')
|
||||
node.style.fontStyle = this.merge('fontStyle')
|
||||
}
|
||||
|
||||
// 标签文字
|
||||
tagText(node, index) {
|
||||
node
|
||||
.fill({
|
||||
color: tagColorList[index].color
|
||||
})
|
||||
.css({
|
||||
'font-size': '12px'
|
||||
})
|
||||
}
|
||||
|
||||
// 标签矩形
|
||||
tagRect(node, index) {
|
||||
node.fill({
|
||||
color: tagColorList[index].background
|
||||
})
|
||||
}
|
||||
|
||||
// 内置图标
|
||||
iconNode(node) {
|
||||
node.attr({
|
||||
fill: this.merge('color')
|
||||
})
|
||||
}
|
||||
|
||||
// 连线
|
||||
line(node, { width, color, dasharray } = {}) {
|
||||
node.stroke({ width, color, dasharray }).fill({ color: 'none' })
|
||||
}
|
||||
|
||||
// 概要连线
|
||||
generalizationLine(node) {
|
||||
node
|
||||
.stroke({
|
||||
width: this.merge('generalizationLineWidth', true),
|
||||
color: this.merge('generalizationLineColor', true)
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
}
|
||||
|
||||
// 展开收起按钮
|
||||
iconBtn(node, node2, fillNode) {
|
||||
let { color, fill } = this.ctx.mindMap.opt.expandBtnStyle || {
|
||||
color: '#808080',
|
||||
fill: '#fff'
|
||||
}
|
||||
node.fill({ color: color })
|
||||
node2.fill({ color: color })
|
||||
fillNode.fill({ color: fill })
|
||||
}
|
||||
|
||||
// 是否设置了自定义的样式
|
||||
hasCustomStyle() {
|
||||
let res = false
|
||||
Object.keys(this.ctx.nodeData.data).forEach((item) => {
|
||||
if (!nodeDataNoStylePropList.includes(item)) {
|
||||
res = true
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
Style.cacheStyle = null
|
||||
|
||||
export default Style
|
||||
62
simple-mind-map/src/core/render/node/nodeCommandWraps.js
Normal file
@@ -0,0 +1,62 @@
|
||||
// 设置数据
|
||||
function setData(data = {}) {
|
||||
this.mindMap.execCommand('SET_NODE_DATA', this, data)
|
||||
}
|
||||
|
||||
// 设置文本
|
||||
function setText(text, richText, resetRichText) {
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', this, text, richText, resetRichText)
|
||||
}
|
||||
|
||||
// 设置图片
|
||||
function setImage(imgData) {
|
||||
this.mindMap.execCommand('SET_NODE_IMAGE', this, imgData)
|
||||
}
|
||||
|
||||
// 设置图标
|
||||
function setIcon(icons) {
|
||||
this.mindMap.execCommand('SET_NODE_ICON', this, icons)
|
||||
}
|
||||
|
||||
// 设置超链接
|
||||
function setHyperlink(link, title) {
|
||||
this.mindMap.execCommand('SET_NODE_HYPERLINK', this, link, title)
|
||||
}
|
||||
|
||||
// 设置备注
|
||||
function setNote(note) {
|
||||
this.mindMap.execCommand('SET_NODE_NOTE', this, note)
|
||||
}
|
||||
|
||||
// 设置标签
|
||||
function setTag(tag) {
|
||||
this.mindMap.execCommand('SET_NODE_TAG', this, tag)
|
||||
}
|
||||
|
||||
// 设置形状
|
||||
function setShape(shape) {
|
||||
this.mindMap.execCommand('SET_NODE_SHAPE', this, shape)
|
||||
}
|
||||
|
||||
// 修改某个样式
|
||||
function setStyle(prop, value, isActive) {
|
||||
this.mindMap.execCommand('SET_NODE_STYLE', this, prop, value, isActive)
|
||||
}
|
||||
|
||||
// 修改多个样式
|
||||
function setStyles(style, isActive) {
|
||||
this.mindMap.execCommand('SET_NODE_STYLES', this, style, isActive)
|
||||
}
|
||||
|
||||
export default {
|
||||
setData,
|
||||
setText,
|
||||
setImage,
|
||||
setIcon,
|
||||
setHyperlink,
|
||||
setNote,
|
||||
setTag,
|
||||
setShape,
|
||||
setStyle,
|
||||
setStyles
|
||||
}
|
||||
367
simple-mind-map/src/core/render/node/nodeCreateContents.js
Normal file
@@ -0,0 +1,367 @@
|
||||
import {
|
||||
measureText,
|
||||
resizeImgSize,
|
||||
removeHtmlStyle,
|
||||
addHtmlStyle,
|
||||
checkIsRichText
|
||||
} from '../../../utils'
|
||||
import { Image, SVG, A, G, Rect, Text, ForeignObject } from '@svgdotjs/svg.js'
|
||||
import iconsSvg from '../../../svg/icons'
|
||||
import { CONSTANTS, commonCaches } from '../../../constants/constant'
|
||||
|
||||
// 创建图片节点
|
||||
function createImgNode() {
|
||||
let img = this.nodeData.data.image
|
||||
if (!img) {
|
||||
return
|
||||
}
|
||||
let imgSize = this.getImgShowSize()
|
||||
let node = new Image().load(img).size(...imgSize)
|
||||
if (this.nodeData.data.imageTitle) {
|
||||
node.attr('title', this.nodeData.data.imageTitle)
|
||||
}
|
||||
node.on('dblclick', e => {
|
||||
this.mindMap.emit('node_img_dblclick', this, e)
|
||||
})
|
||||
node.on('mouseenter', e => {
|
||||
this.mindMap.emit('node_img_mouseenter', this, node, e)
|
||||
})
|
||||
node.on('mouseleave', e => {
|
||||
this.mindMap.emit('node_img_mouseleave', this, node, e)
|
||||
})
|
||||
node.on('mousemove', e => {
|
||||
this.mindMap.emit('node_img_mousemove', this, node, e)
|
||||
})
|
||||
return {
|
||||
node,
|
||||
width: imgSize[0],
|
||||
height: imgSize[1]
|
||||
}
|
||||
}
|
||||
|
||||
// 获取图片显示宽高
|
||||
function getImgShowSize() {
|
||||
const { custom, width, height } = this.nodeData.data.imageSize
|
||||
// 如果是自定义了图片的宽高,那么不受最大宽高限制
|
||||
if (custom) return [width, height]
|
||||
return resizeImgSize(
|
||||
width,
|
||||
height,
|
||||
this.mindMap.themeConfig.imgMaxWidth,
|
||||
this.mindMap.themeConfig.imgMaxHeight
|
||||
)
|
||||
}
|
||||
|
||||
// 创建icon节点
|
||||
function createIconNode() {
|
||||
let _data = this.nodeData.data
|
||||
if (!_data.icon || _data.icon.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let iconSize = this.mindMap.themeConfig.iconSize
|
||||
return _data.icon.map(item => {
|
||||
let src = iconsSvg.getNodeIconListIcon(
|
||||
item,
|
||||
this.mindMap.opt.iconList || []
|
||||
)
|
||||
let node = null
|
||||
// svg图标
|
||||
if (/^<svg/.test(src)) {
|
||||
node = SVG(src)
|
||||
} else {
|
||||
// 图片图标
|
||||
node = new Image().load(src)
|
||||
}
|
||||
node.size(iconSize, iconSize)
|
||||
node.on('click', e => {
|
||||
this.mindMap.emit('node_icon_click', this, item, e)
|
||||
})
|
||||
return {
|
||||
node,
|
||||
width: iconSize,
|
||||
height: iconSize
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 创建富文本节点
|
||||
function createRichTextNode() {
|
||||
let g = new G()
|
||||
// 重新设置富文本节点内容
|
||||
let recoverText = false
|
||||
if (this.nodeData.data.resetRichText) {
|
||||
delete this.nodeData.data.resetRichText
|
||||
recoverText = true
|
||||
}
|
||||
if ([CONSTANTS.CHANGE_THEME].includes(this.mindMap.renderer.renderSource)) {
|
||||
// 如果自定义过样式则不允许覆盖
|
||||
if (!this.hasCustomStyle()) {
|
||||
recoverText = true
|
||||
}
|
||||
}
|
||||
if (recoverText) {
|
||||
let text = this.nodeData.data.text
|
||||
// 判断节点内容是否是富文本
|
||||
let isRichText = checkIsRichText(text)
|
||||
// 样式字符串
|
||||
let style = this.style.createStyleText()
|
||||
if (isRichText) {
|
||||
// 如果是富文本那么线移除内联样式
|
||||
text = removeHtmlStyle(text)
|
||||
// 再添加新的内联样式
|
||||
text = addHtmlStyle(text, 'span', style)
|
||||
} else {
|
||||
// 非富文本
|
||||
text = `<p><span style="${style}">${text}</span></p>`
|
||||
}
|
||||
this.nodeData.data.text = text
|
||||
}
|
||||
let html = `<div>${this.nodeData.data.text}</div>`
|
||||
if (!commonCaches.measureRichtextNodeTextSizeEl) {
|
||||
commonCaches.measureRichtextNodeTextSizeEl = document.createElement('div')
|
||||
}
|
||||
let div = commonCaches.measureRichtextNodeTextSizeEl
|
||||
div.innerHTML = html
|
||||
div.style.cssText = `position: fixed; left: -999999px;`
|
||||
let el = div.children[0]
|
||||
el.classList.add('smm-richtext-node-wrap')
|
||||
el.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
|
||||
el.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + 'px'
|
||||
this.mindMap.el.appendChild(div)
|
||||
let { width, height } = el.getBoundingClientRect()
|
||||
// 如果文本为空,那么需要计算一个默认高度
|
||||
if (height <= 0) {
|
||||
div.innerHTML = '<p>abc123我和你</p>'
|
||||
let elTmp = div.children[0]
|
||||
elTmp.classList.add('smm-richtext-node-wrap')
|
||||
height = elTmp.getBoundingClientRect().height
|
||||
}
|
||||
width = Math.ceil(width) + 1 // 修复getBoundingClientRect方法对实际宽度是小数的元素获取到的值是整数,导致宽度不够文本发生换行的问题
|
||||
height = Math.ceil(height)
|
||||
g.attr('data-width', width)
|
||||
g.attr('data-height', height)
|
||||
html = div.innerHTML
|
||||
let foreignObject = new ForeignObject()
|
||||
foreignObject.width(width)
|
||||
foreignObject.height(height)
|
||||
foreignObject.add(SVG(html))
|
||||
g.add(foreignObject)
|
||||
return {
|
||||
node: g,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
// 创建文本节点
|
||||
function createTextNode() {
|
||||
if (this.nodeData.data.richText) {
|
||||
return this.createRichTextNode()
|
||||
}
|
||||
let g = new G()
|
||||
let fontSize = this.getStyle('fontSize', false, this.nodeData.data.isActive)
|
||||
let lineHeight = this.getStyle(
|
||||
'lineHeight',
|
||||
false,
|
||||
this.nodeData.data.isActive
|
||||
)
|
||||
// 文本超长自动换行
|
||||
let textStyle = this.style.getTextFontStyle()
|
||||
let textArr = this.nodeData.data.text.split(/\n/gim)
|
||||
let maxWidth = this.mindMap.opt.textAutoWrapWidth
|
||||
let isMultiLine = false
|
||||
textArr.forEach((item, index) => {
|
||||
let arr = item.split('')
|
||||
let lines = []
|
||||
let line = []
|
||||
while (arr.length) {
|
||||
let str = arr.shift()
|
||||
let text = [...line, str].join('')
|
||||
if (measureText(text, textStyle).width <= maxWidth) {
|
||||
line.push(str)
|
||||
} else {
|
||||
lines.push(line.join(''))
|
||||
line = [str]
|
||||
}
|
||||
}
|
||||
if (line.length > 0) {
|
||||
lines.push(line.join(''))
|
||||
}
|
||||
if (lines.length > 1) {
|
||||
isMultiLine = true
|
||||
}
|
||||
textArr[index] = lines.join('\n')
|
||||
})
|
||||
textArr = textArr.join('\n').split(/\n/gim)
|
||||
textArr.forEach((item, index) => {
|
||||
let node = new Text().text(item)
|
||||
this.style.text(node)
|
||||
node.y(fontSize * lineHeight * index)
|
||||
g.add(node)
|
||||
})
|
||||
let { width, height } = g.bbox()
|
||||
width = Math.ceil(width)
|
||||
height = Math.ceil(height)
|
||||
g.attr('data-width', width)
|
||||
g.attr('data-height', height)
|
||||
g.attr('data-ismultiLine', isMultiLine || textArr.length > 1)
|
||||
return {
|
||||
node: g,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
// 创建超链接节点
|
||||
function createHyperlinkNode() {
|
||||
let { hyperlink, hyperlinkTitle } = this.nodeData.data
|
||||
if (!hyperlink) {
|
||||
return
|
||||
}
|
||||
let iconSize = this.mindMap.themeConfig.iconSize
|
||||
let node = new SVG()
|
||||
// 超链接节点
|
||||
let a = new A().to(hyperlink).target('_blank')
|
||||
a.node.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
if (hyperlinkTitle) {
|
||||
a.attr('title', hyperlinkTitle)
|
||||
}
|
||||
// 添加一个透明的层,作为鼠标区域
|
||||
a.rect(iconSize, iconSize).fill({ color: 'transparent' })
|
||||
// 超链接图标
|
||||
let iconNode = SVG(iconsSvg.hyperlink).size(iconSize, iconSize)
|
||||
this.style.iconNode(iconNode)
|
||||
a.add(iconNode)
|
||||
node.add(a)
|
||||
return {
|
||||
node,
|
||||
width: iconSize,
|
||||
height: iconSize
|
||||
}
|
||||
}
|
||||
|
||||
// 创建标签节点
|
||||
function createTagNode() {
|
||||
let tagData = this.nodeData.data.tag
|
||||
if (!tagData || tagData.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let nodes = []
|
||||
tagData.slice(0, this.mindMap.opt.maxTag).forEach((item, index) => {
|
||||
let tag = new G()
|
||||
// 标签文本
|
||||
let text = new Text().text(item).x(8).cy(10)
|
||||
this.style.tagText(text, index)
|
||||
let { width } = text.bbox()
|
||||
// 标签矩形
|
||||
let rect = new Rect().size(width + 16, 20)
|
||||
this.style.tagRect(rect, index)
|
||||
tag.add(rect).add(text)
|
||||
nodes.push({
|
||||
node: tag,
|
||||
width: width + 16,
|
||||
height: 20
|
||||
})
|
||||
})
|
||||
return nodes
|
||||
}
|
||||
|
||||
// 创建备注节点
|
||||
function createNoteNode() {
|
||||
if (!this.nodeData.data.note) {
|
||||
return null
|
||||
}
|
||||
let iconSize = this.mindMap.themeConfig.iconSize
|
||||
let node = new SVG().attr('cursor', 'pointer')
|
||||
// 透明的层,用来作为鼠标区域
|
||||
node.add(new Rect().size(iconSize, iconSize).fill({ color: 'transparent' }))
|
||||
// 备注图标
|
||||
let iconNode = SVG(iconsSvg.note).size(iconSize, iconSize)
|
||||
this.style.iconNode(iconNode)
|
||||
node.add(iconNode)
|
||||
// 备注tooltip
|
||||
if (!this.mindMap.opt.customNoteContentShow) {
|
||||
if (!this.noteEl) {
|
||||
this.noteEl = document.createElement('div')
|
||||
this.noteEl.style.cssText = `
|
||||
position: fixed;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 5px rgb(0 0 0 / 10%);
|
||||
display: none;
|
||||
background-color: #fff;
|
||||
z-index: ${this.mindMap.opt.nodeNoteTooltipZIndex}
|
||||
`
|
||||
const targetNode =
|
||||
this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.noteEl)
|
||||
}
|
||||
this.noteEl.innerText = this.nodeData.data.note
|
||||
}
|
||||
node.on('mouseover', () => {
|
||||
let { left, top } = node.node.getBoundingClientRect()
|
||||
if (!this.mindMap.opt.customNoteContentShow) {
|
||||
this.noteEl.style.left = left + 'px'
|
||||
this.noteEl.style.top = top + iconSize + 'px'
|
||||
this.noteEl.style.display = 'block'
|
||||
} else {
|
||||
this.mindMap.opt.customNoteContentShow.show(
|
||||
this.nodeData.data.note,
|
||||
left,
|
||||
top + iconSize
|
||||
)
|
||||
}
|
||||
})
|
||||
node.on('mouseout', () => {
|
||||
if (!this.mindMap.opt.customNoteContentShow) {
|
||||
this.noteEl.style.display = 'none'
|
||||
} else {
|
||||
this.mindMap.opt.customNoteContentShow.hide()
|
||||
}
|
||||
})
|
||||
return {
|
||||
node,
|
||||
width: iconSize,
|
||||
height: iconSize
|
||||
}
|
||||
}
|
||||
|
||||
// 测量自定义节点内容元素的宽高
|
||||
function measureCustomNodeContentSize(content) {
|
||||
if (!commonCaches.measureCustomNodeContentSizeEl) {
|
||||
commonCaches.measureCustomNodeContentSizeEl = document.createElement('div')
|
||||
commonCaches.measureCustomNodeContentSizeEl.style.cssText = `
|
||||
position: fixed;
|
||||
left: -99999px;
|
||||
top: -99999px;
|
||||
`
|
||||
this.mindMap.el.appendChild(commonCaches.measureCustomNodeContentSizeEl)
|
||||
}
|
||||
commonCaches.measureCustomNodeContentSizeEl.innerHTML = ''
|
||||
commonCaches.measureCustomNodeContentSizeEl.appendChild(content)
|
||||
let rect = commonCaches.measureCustomNodeContentSizeEl.getBoundingClientRect()
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
}
|
||||
}
|
||||
|
||||
// 是否使用的是自定义节点内容
|
||||
function isUseCustomNodeContent() {
|
||||
return !!this._customNodeContent
|
||||
}
|
||||
|
||||
export default {
|
||||
createImgNode,
|
||||
getImgShowSize,
|
||||
createIconNode,
|
||||
createRichTextNode,
|
||||
createTextNode,
|
||||
createHyperlinkNode,
|
||||
createTagNode,
|
||||
createNoteNode,
|
||||
measureCustomNodeContentSize,
|
||||
isUseCustomNodeContent
|
||||
}
|
||||
142
simple-mind-map/src/core/render/node/nodeExpandBtn.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import btnsSvg from '../../../svg/btns'
|
||||
import { SVG, Circle, G } from '@svgdotjs/svg.js'
|
||||
|
||||
// 创建展开收起按钮的内容节点
|
||||
function createExpandNodeContent() {
|
||||
if (this._openExpandNode) {
|
||||
return
|
||||
}
|
||||
let { open, close } = this.mindMap.opt.expandBtnIcon || {}
|
||||
// 展开的节点
|
||||
this._openExpandNode = SVG(open || btnsSvg.open).size(
|
||||
this.expandBtnSize,
|
||||
this.expandBtnSize
|
||||
)
|
||||
this._openExpandNode.x(0).y(-this.expandBtnSize / 2)
|
||||
// 收起的节点
|
||||
this._closeExpandNode = SVG(close || btnsSvg.close).size(
|
||||
this.expandBtnSize,
|
||||
this.expandBtnSize
|
||||
)
|
||||
this._closeExpandNode.x(0).y(-this.expandBtnSize / 2)
|
||||
// 填充节点
|
||||
this._fillExpandNode = new Circle().size(this.expandBtnSize)
|
||||
this._fillExpandNode.x(0).y(-this.expandBtnSize / 2)
|
||||
// 设置样式
|
||||
this.style.iconBtn(
|
||||
this._openExpandNode,
|
||||
this._closeExpandNode,
|
||||
this._fillExpandNode
|
||||
)
|
||||
}
|
||||
|
||||
// 创建或更新展开收缩按钮内容
|
||||
function updateExpandBtnNode() {
|
||||
let { expand } = this.nodeData.data
|
||||
// 如果本次和上次的展开状态一样则返回
|
||||
if (expand === this._lastExpandBtnType) return
|
||||
if (this._expandBtn) {
|
||||
this._expandBtn.clear()
|
||||
}
|
||||
this.createExpandNodeContent()
|
||||
let node
|
||||
if (expand === false) {
|
||||
node = this._openExpandNode
|
||||
this._lastExpandBtnType = false
|
||||
} else {
|
||||
node = this._closeExpandNode
|
||||
this._lastExpandBtnType = true
|
||||
}
|
||||
if (this._expandBtn) this._expandBtn.add(this._fillExpandNode).add(node)
|
||||
}
|
||||
|
||||
// 更新展开收缩按钮位置
|
||||
function updateExpandBtnPos() {
|
||||
if (!this._expandBtn) {
|
||||
return
|
||||
}
|
||||
this.renderer.layout.renderExpandBtn(this, this._expandBtn)
|
||||
}
|
||||
|
||||
// 创建展开收缩按钮
|
||||
function renderExpandBtn() {
|
||||
if (
|
||||
!this.nodeData.children ||
|
||||
this.nodeData.children.length <= 0 ||
|
||||
this.isRoot
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (this._expandBtn) {
|
||||
this.group.add(this._expandBtn)
|
||||
} else {
|
||||
this._expandBtn = new G()
|
||||
this._expandBtn.on('mouseover', e => {
|
||||
e.stopPropagation()
|
||||
this._expandBtn.css({
|
||||
cursor: 'pointer'
|
||||
})
|
||||
})
|
||||
this._expandBtn.on('mouseout', e => {
|
||||
e.stopPropagation()
|
||||
this._expandBtn.css({
|
||||
cursor: 'auto'
|
||||
})
|
||||
})
|
||||
this._expandBtn.on('click', e => {
|
||||
e.stopPropagation()
|
||||
// 展开收缩
|
||||
this.mindMap.execCommand(
|
||||
'SET_NODE_EXPAND',
|
||||
this,
|
||||
!this.nodeData.data.expand
|
||||
)
|
||||
this.mindMap.emit('expand_btn_click', this)
|
||||
})
|
||||
this._expandBtn.on('dblclick', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
this.group.add(this._expandBtn)
|
||||
}
|
||||
this._showExpandBtn = true
|
||||
this.updateExpandBtnNode()
|
||||
this.updateExpandBtnPos()
|
||||
}
|
||||
|
||||
// 移除展开收缩按钮
|
||||
function removeExpandBtn() {
|
||||
if (this._expandBtn && this._showExpandBtn) {
|
||||
this._expandBtn.remove()
|
||||
this._showExpandBtn = false
|
||||
}
|
||||
}
|
||||
|
||||
// 显示展开收起按钮
|
||||
function showExpandBtn() {
|
||||
if (this.mindMap.opt.alwaysShowExpandBtn) return
|
||||
setTimeout(() => {
|
||||
this.renderExpandBtn()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 隐藏展开收起按钮
|
||||
function hideExpandBtn() {
|
||||
if (this.mindMap.opt.alwaysShowExpandBtn || this._isMouseenter) return
|
||||
// 非激活状态且展开状态鼠标移出才隐藏按钮
|
||||
let { isActive, expand } = this.nodeData.data
|
||||
if (!isActive && expand) {
|
||||
setTimeout(() => {
|
||||
this.removeExpandBtn()
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
createExpandNodeContent,
|
||||
updateExpandBtnNode,
|
||||
updateExpandBtnPos,
|
||||
renderExpandBtn,
|
||||
removeExpandBtn,
|
||||
showExpandBtn,
|
||||
hideExpandBtn
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Rect } from '@svgdotjs/svg.js'
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
function renderExpandBtnPlaceholderRect() {
|
||||
// 根节点或没有子节点不需要渲染
|
||||
if (
|
||||
!this.nodeData.children ||
|
||||
this.nodeData.children.length <= 0 ||
|
||||
this.isRoot
|
||||
) {
|
||||
return
|
||||
}
|
||||
// 默认显示展开按钮的情况下也不需要渲染
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
let { width, height } = this
|
||||
if (!this._unVisibleRectRegionNode) {
|
||||
this._unVisibleRectRegionNode = new Rect()
|
||||
this._unVisibleRectRegionNode.fill({
|
||||
color: 'transparent'
|
||||
})
|
||||
}
|
||||
this.group.add(this._unVisibleRectRegionNode)
|
||||
this.renderer.layout.renderExpandBtnRect(
|
||||
this._unVisibleRectRegionNode,
|
||||
this.expandBtnSize,
|
||||
width,
|
||||
height,
|
||||
this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除展开收起按钮的隐藏占位元素
|
||||
function clearExpandBtnPlaceholderRect() {
|
||||
if (!this._unVisibleRectRegionNode) {
|
||||
return
|
||||
}
|
||||
this._unVisibleRectRegionNode.remove()
|
||||
this._unVisibleRectRegionNode = null
|
||||
}
|
||||
|
||||
// 更新展开收起按钮的隐藏占位元素
|
||||
function updateExpandBtnPlaceholderRect() {
|
||||
// 布局改变需要重新渲染
|
||||
if (this.needRerenderExpandBtnPlaceholderRect) {
|
||||
this.needRerenderExpandBtnPlaceholderRect = false
|
||||
this.renderExpandBtnPlaceholderRect()
|
||||
}
|
||||
// 没有子节点到有子节点需要渲染
|
||||
if (this.nodeData.children && this.nodeData.children.length > 0) {
|
||||
if (!this._unVisibleRectRegionNode) {
|
||||
this.renderExpandBtnPlaceholderRect()
|
||||
}
|
||||
} else {
|
||||
// 有子节点到没子节点,需要删除
|
||||
if (this._unVisibleRectRegionNode) {
|
||||
this.clearExpandBtnPlaceholderRect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
renderExpandBtnPlaceholderRect,
|
||||
clearExpandBtnPlaceholderRect,
|
||||
updateExpandBtnPlaceholderRect
|
||||
}
|
||||