Compare commits
863 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f2aed7e8a | ||
|
|
288ceafa92 | ||
|
|
99dc443142 | ||
|
|
b6440eba1a | ||
|
|
545e46babc | ||
|
|
b95b6af1b1 | ||
|
|
ccef5fc581 | ||
|
|
ed82fe5a61 | ||
|
|
1550f032d9 | ||
|
|
7d2758a21c | ||
|
|
e4fab73017 | ||
|
|
1beb03eaa6 | ||
|
|
20f67efd58 | ||
|
|
8c3d66eb3c | ||
|
|
a4f6006efd | ||
|
|
1d297350cc | ||
|
|
07650f8978 | ||
|
|
b26e5625ce | ||
|
|
1e38731ecb | ||
|
|
ef9b9804cb | ||
|
|
9fe321a127 | ||
|
|
464e57d019 | ||
|
|
50f125471e | ||
|
|
6bbee4a5cc | ||
|
|
5052c0427a | ||
|
|
9528631ed1 | ||
|
|
2a816f62fa | ||
|
|
8596e3356d | ||
|
|
7422af7f3b | ||
|
|
0f047da78b | ||
|
|
f866abb34c | ||
|
|
01d7e36990 | ||
|
|
a9493b9c16 | ||
|
|
a59a283d74 | ||
|
|
aafcba8bb7 | ||
|
|
11ea7d452c | ||
|
|
47d21d85fb | ||
|
|
518b7642a0 | ||
|
|
a9ea4b8e33 | ||
|
|
443465eb86 | ||
|
|
e1172c8d0d | ||
|
|
7a2605fdad | ||
|
|
a97d549d69 | ||
|
|
fcf48ca3dc | ||
|
|
c0ad18cff8 | ||
|
|
73e7855575 | ||
|
|
69ef7faf49 | ||
|
|
036f845968 | ||
|
|
740e2e3410 | ||
|
|
ba44f69f9f | ||
|
|
977799a3ac | ||
|
|
76ddecee50 | ||
|
|
0ab495a161 | ||
|
|
fefbcfbbee | ||
|
|
c77c7403da | ||
|
|
b0044bb5fa | ||
|
|
e61749c1b3 | ||
|
|
43216ed925 | ||
|
|
385873b6e8 | ||
|
|
5583642961 | ||
|
|
e063724ab6 | ||
|
|
19d4788489 | ||
|
|
1452fd2a28 | ||
|
|
c296b99d5a | ||
|
|
e9de1e675f | ||
|
|
b3e254c0ee | ||
|
|
bd3d470e40 | ||
|
|
21564445d6 | ||
|
|
e53d1b1948 | ||
|
|
c428f166a6 | ||
|
|
f19704ec41 | ||
|
|
d47f013f84 | ||
|
|
612d675a19 | ||
|
|
13f571e0b5 | ||
|
|
b09d408ab3 | ||
|
|
ea95ae2b5d | ||
|
|
2590e21807 | ||
|
|
fb41653d79 | ||
|
|
4c9c34a0ea | ||
|
|
0d5602b832 | ||
|
|
af87f84ce8 | ||
|
|
a96c16aa45 | ||
|
|
e29e1c26e5 | ||
|
|
fee38cbe8a | ||
|
|
58ca173234 | ||
|
|
aee10c6810 | ||
|
|
fe43be3451 | ||
|
|
21868c7f44 | ||
|
|
5585d2a4f7 | ||
|
|
1d04038609 | ||
|
|
305c5e9f70 | ||
|
|
4ac91003bb | ||
|
|
44410900aa | ||
|
|
239b369391 | ||
|
|
d9638fef98 | ||
|
|
ecb9482b82 | ||
|
|
cd5556aad5 | ||
|
|
063af62cf6 | ||
|
|
a4611d11a8 | ||
|
|
5d1f460a94 | ||
|
|
075e578bb4 | ||
|
|
32c17921ca | ||
|
|
7c470c9b88 | ||
|
|
e472b840f1 | ||
|
|
726460c812 | ||
|
|
7ccf796c34 | ||
|
|
c183306ab2 | ||
|
|
29bb27aa23 | ||
|
|
7f9d03c8af | ||
|
|
c3a0e09f6d | ||
|
|
1d497b2f13 | ||
|
|
f43a2ebdef | ||
|
|
4c3f3cb1ab | ||
|
|
a404a71ba2 | ||
|
|
6b4c118a2b | ||
|
|
1157257911 | ||
|
|
f2642c9d63 | ||
|
|
91bc0351b8 | ||
|
|
df2dc96ba5 | ||
|
|
3d86650f22 | ||
|
|
a31e93bacf | ||
|
|
a3362e44fe | ||
|
|
f7f234b4cb | ||
|
|
48f1de5c25 | ||
|
|
c533459da1 | ||
|
|
d99895b845 | ||
|
|
c65393d1bc | ||
|
|
5d133f74cf | ||
|
|
5997c98b8f | ||
|
|
217d66f692 | ||
|
|
d14d887c1a | ||
|
|
e36e238c2f | ||
|
|
1b0646af6d | ||
|
|
a24f7a73c8 | ||
|
|
b35dd282ec | ||
|
|
8c0c2c5bc4 | ||
|
|
3763cd0efc | ||
|
|
4ce9533763 | ||
|
|
43ecdafdd1 | ||
|
|
5ff8704778 | ||
|
|
d206d6dd99 | ||
|
|
850b9ed936 | ||
|
|
26508b5a62 | ||
|
|
061c82459e | ||
|
|
d2bf5be7c7 | ||
|
|
b726aa30e5 | ||
|
|
6546ec090e | ||
|
|
4dc5754f1d | ||
|
|
7f199e6c2f | ||
|
|
2548ac4eb4 | ||
|
|
60e503ab1f | ||
|
|
c718cbc030 | ||
|
|
beb2b550f0 | ||
|
|
d3e1389f10 | ||
|
|
68cb24c0b6 | ||
|
|
e3959107bd | ||
|
|
4b3c81ab91 | ||
|
|
76b5d9d11d | ||
|
|
22d0fe5ac4 | ||
|
|
8b8d549abd | ||
|
|
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.2abc5635.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.faba1249.css" rel="preload" as="style"><link href="dist/js/app.938c190b.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.b39e98f0.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.faba1249.css" rel="stylesheet"><link href="dist/css/app.2abc5635.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.b39e98f0.js"></script><script src="dist/js/app.938c190b.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?29889d7ca2f7c720050d" rel="stylesheet"><link href="dist/css/app.css?29889d7ca2f7c720050d" 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?29889d7ca2f7c720050d"></script><script src="dist/js/app.js?29889d7ca2f7c720050d"></script></body></html>
|
||||
152
simple-mind-map/bin/wsServer.mjs
Normal file
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import ws from 'ws'
|
||||
import http from 'http'
|
||||
import * as map from 'lib0/map'
|
||||
|
||||
const wsReadyStateConnecting = 0
|
||||
const wsReadyStateOpen = 1
|
||||
const wsReadyStateClosing = 2 // eslint-disable-line
|
||||
const wsReadyStateClosed = 3 // eslint-disable-line
|
||||
|
||||
const pingTimeout = 30000
|
||||
|
||||
const port = process.env.PORT || 4444
|
||||
// @ts-ignore
|
||||
const wss = new ws.Server({ noServer: true })
|
||||
|
||||
const server = http.createServer((request, response) => {
|
||||
response.writeHead(200, { 'Content-Type': 'text/plain' })
|
||||
response.end('okay')
|
||||
})
|
||||
|
||||
/**
|
||||
* Map froms topic-name to set of subscribed clients.
|
||||
* @type {Map<string, Set<any>>}
|
||||
*/
|
||||
const topics = new Map()
|
||||
|
||||
/**
|
||||
* @param {any} conn
|
||||
* @param {object} message
|
||||
*/
|
||||
const send = (conn, message) => {
|
||||
if (
|
||||
conn.readyState !== wsReadyStateConnecting &&
|
||||
conn.readyState !== wsReadyStateOpen
|
||||
) {
|
||||
conn.close()
|
||||
}
|
||||
try {
|
||||
conn.send(JSON.stringify(message))
|
||||
} catch (e) {
|
||||
conn.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup a new client
|
||||
* @param {any} conn
|
||||
*/
|
||||
const onconnection = conn => {
|
||||
/**
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
const subscribedTopics = new Set()
|
||||
let closed = false
|
||||
// Check if connection is still alive
|
||||
let pongReceived = true
|
||||
const pingInterval = setInterval(() => {
|
||||
if (!pongReceived) {
|
||||
conn.close()
|
||||
clearInterval(pingInterval)
|
||||
} else {
|
||||
pongReceived = false
|
||||
try {
|
||||
conn.ping()
|
||||
} catch (e) {
|
||||
conn.close()
|
||||
}
|
||||
}
|
||||
}, pingTimeout)
|
||||
conn.on('pong', () => {
|
||||
pongReceived = true
|
||||
})
|
||||
conn.on('close', () => {
|
||||
subscribedTopics.forEach(topicName => {
|
||||
const subs = topics.get(topicName) || new Set()
|
||||
subs.delete(conn)
|
||||
if (subs.size === 0) {
|
||||
topics.delete(topicName)
|
||||
}
|
||||
})
|
||||
subscribedTopics.clear()
|
||||
closed = true
|
||||
})
|
||||
conn.on(
|
||||
'message',
|
||||
/** @param {object} message */ message => {
|
||||
if (typeof message === 'string') {
|
||||
message = JSON.parse(message)
|
||||
}
|
||||
if (message && message.type && !closed) {
|
||||
switch (message.type) {
|
||||
case 'subscribe':
|
||||
/** @type {Array<string>} */ ;(message.topics || []).forEach(
|
||||
topicName => {
|
||||
if (typeof topicName === 'string') {
|
||||
// add conn to topic
|
||||
const topic = map.setIfUndefined(
|
||||
topics,
|
||||
topicName,
|
||||
() => new Set()
|
||||
)
|
||||
topic.add(conn)
|
||||
// add topic to conn
|
||||
subscribedTopics.add(topicName)
|
||||
}
|
||||
}
|
||||
)
|
||||
break
|
||||
case 'unsubscribe':
|
||||
/** @type {Array<string>} */ ;(message.topics || []).forEach(
|
||||
topicName => {
|
||||
const subs = topics.get(topicName)
|
||||
if (subs) {
|
||||
subs.delete(conn)
|
||||
}
|
||||
}
|
||||
)
|
||||
break
|
||||
case 'publish':
|
||||
if (message.topic) {
|
||||
const receivers = topics.get(message.topic)
|
||||
if (receivers) {
|
||||
message.clients = receivers.size
|
||||
receivers.forEach(receiver => send(receiver, message))
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'ping':
|
||||
send(conn, { type: 'pong' })
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
wss.on('connection', onconnection)
|
||||
|
||||
server.on('upgrade', (request, socket, head) => {
|
||||
// You may check auth of request here..
|
||||
/**
|
||||
* @param {any} ws
|
||||
*/
|
||||
const handleAuth = ws => {
|
||||
wss.emit('connection', ws, request)
|
||||
}
|
||||
wss.handleUpgrade(request, socket, head, handleAuth)
|
||||
})
|
||||
|
||||
server.listen(port)
|
||||
|
||||
console.log('Signaling server running on localhost:', port)
|
||||
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": {}
|
||||
}
|
||||
49
simple-mind-map/full.js
Normal file
@@ -0,0 +1,49 @@
|
||||
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 Scrollbar from './src/plugins/Scrollbar.js'
|
||||
import Formula from './src/plugins/Formula.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)
|
||||
.usePlugin(Scrollbar)
|
||||
.usePlugin(Formula)
|
||||
|
||||
export default MindMap
|
||||
@@ -1,69 +1,32 @@
|
||||
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,
|
||||
cssContent
|
||||
} from './src/constants/constant'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import xmind from './src/parse/xmind'
|
||||
import { simpleDeepClone } from './src/utils'
|
||||
import { simpleDeepClone, getType, getObjectChangedProps } 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: 构造函数
|
||||
*
|
||||
* @param {defaultOpt} opt
|
||||
*/
|
||||
constructor(opt = {}) {
|
||||
// 合并选项
|
||||
@@ -71,22 +34,29 @@ class MindMap {
|
||||
|
||||
// 容器元素
|
||||
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')
|
||||
|
||||
// 添加css
|
||||
this.cssEl = null
|
||||
this.addCss()
|
||||
|
||||
// 画布
|
||||
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 +83,67 @@ 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(this.opt.fit ? () => this.view.fit() : () => {})
|
||||
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() {
|
||||
// 添加必要的css样式到页面
|
||||
addCss() {
|
||||
this.cssEl = document.createElement('style')
|
||||
this.cssEl.type = 'text/css'
|
||||
this.cssEl.innerHTML = cssContent
|
||||
document.head.appendChild(this.cssEl)
|
||||
}
|
||||
|
||||
// 移除css
|
||||
removeCss() {
|
||||
document.head.removeChild(this.cssEl)
|
||||
}
|
||||
|
||||
// 渲染,部分渲染
|
||||
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 +151,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 +191,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) {
|
||||
// 计算改变了的配置
|
||||
const changedConfig = getObjectChangedProps(this.themeConfig, config)
|
||||
this.opt.themeConfig = config
|
||||
this.reRender()
|
||||
// 检查改变的是否是节点大小无关的主题属性
|
||||
let res = checkIsNodeSizeIndependenceConfig(changedConfig)
|
||||
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) {
|
||||
data = simpleDeepClone(data || {})
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.command.clearHistory()
|
||||
this.renderer.renderTree = data
|
||||
this.reRender()
|
||||
this.command.addHistory()
|
||||
this.renderer.setData(data)
|
||||
this.reRender(() => {}, CONSTANTS.SET_DATA)
|
||||
this.emit('set_data', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-09-21 16:39:13
|
||||
* @Desc: 动态设置思维导图数据,包括节点数据、布局、主题、视图
|
||||
*/
|
||||
// 动态设置思维导图数据,包括节点数据、布局、主题、视图
|
||||
setFullData(data) {
|
||||
if (data.root) {
|
||||
this.setData(data.root)
|
||||
@@ -358,12 +288,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 +308,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.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 +326,148 @@ 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()
|
||||
// 添加必要的样式
|
||||
clone.add(SVG(`<style>${cssContent}</style>`))
|
||||
// 如果实际图形宽高超出了屏幕宽高,且存在水印的话需要重新绘制水印,否则会出现超出部分没有水印的问题
|
||||
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 => {
|
||||
if (this[plugin.instanceName].beforePluginDestroy) {
|
||||
this[plugin.instanceName].beforePluginDestroy()
|
||||
}
|
||||
this[plugin.instanceName] = null
|
||||
})
|
||||
// 解绑事件
|
||||
this.event.unbind()
|
||||
// 移除画布节点
|
||||
this.svg.remove()
|
||||
// 去除给容器元素设置的背景样式
|
||||
Style.removeBackgroundStyle(this.el)
|
||||
this.el.innerHTML = ''
|
||||
this.el = null
|
||||
this.removeCss()
|
||||
}
|
||||
}
|
||||
|
||||
MindMap.xmind = xmind
|
||||
// 插件列表
|
||||
MindMap.pluginList = []
|
||||
MindMap.usePlugin = (plugin, opt = {}) => {
|
||||
if (MindMap.hasPlugin(plugin) !== -1) return MindMap
|
||||
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
|
||||
|
||||
4444
simple-mind-map/package-lock.json
generated
Normal file
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.2.16",
|
||||
"version": "0.7.3",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
@@ -12,6 +12,8 @@
|
||||
"url": "http://lxqnsys.com/"
|
||||
}
|
||||
],
|
||||
"types": "./types/index.d.ts",
|
||||
"typings": "./types/index.d.ts",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -19,18 +21,26 @@
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint src/",
|
||||
"format": "prettier --write ."
|
||||
"format": "prettier --write .",
|
||||
"types": "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017",
|
||||
"wsServe": "node ./bin/wsServer.mjs"
|
||||
},
|
||||
"module": "index.js",
|
||||
"main": "./dist/simpleMindMap.umd.min.js",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.0.16",
|
||||
"canvg": "^3.0.7",
|
||||
"deepmerge": "^1.5.2",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"jspdf": "^2.5.1",
|
||||
"jszip": "^3.10.1",
|
||||
"xml-js": "^1.6.11"
|
||||
"katex": "^0.16.8",
|
||||
"mdast-util-from-markdown": "^1.3.0",
|
||||
"quill": "^1.3.6",
|
||||
"tern": "^0.24.3",
|
||||
"uuid": "^9.0.0",
|
||||
"xml-js": "^1.6.11",
|
||||
"y-webrtc": "^10.2.5",
|
||||
"yjs": "^13.6.8"
|
||||
},
|
||||
"keywords": [
|
||||
"javascript",
|
||||
|
||||
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,296 +0,0 @@
|
||||
import { bfsWalk, throttle } from './utils'
|
||||
import Base from './layouts/Base'
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-23 17:38:55
|
||||
* @Desc: 节点拖动类
|
||||
*/
|
||||
class Drag extends Base {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-10 22:35:16
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor({ mindMap }) {
|
||||
super(mindMap.renderer)
|
||||
this.mindMap = mindMap
|
||||
this.reset()
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-23 19:33:56
|
||||
* @Desc: 复位
|
||||
*/
|
||||
reset() {
|
||||
// 当前拖拽节点
|
||||
this.node = null
|
||||
// 当前重叠节点
|
||||
this.overlapNode = null
|
||||
// 当前上一个同级节点
|
||||
this.prevNode = null
|
||||
// 当前下一个同级节点
|
||||
this.nextNode = null
|
||||
// 画布的变换数据
|
||||
this.drawTransform = null
|
||||
// 克隆节点
|
||||
this.clone = null
|
||||
// 连接线
|
||||
this.line = null
|
||||
// 同级位置占位符
|
||||
this.placeholder = null
|
||||
// 鼠标按下位置和节点左上角的偏移量
|
||||
this.offsetX = 0
|
||||
this.offsetY = 0
|
||||
// 克隆节点左上角的坐标
|
||||
this.cloneNodeLeft = 0
|
||||
this.cloneNodeTop = 0
|
||||
// 当前鼠标是否按下
|
||||
this.isMousedown = false
|
||||
// 拖拽的鼠标位置变量
|
||||
this.mouseDownX = 0
|
||||
this.mouseDownY = 0
|
||||
this.mouseMoveX = 0
|
||||
this.mouseMoveY = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-10 22:36:36
|
||||
* @Desc: 绑定事件
|
||||
*/
|
||||
bindEvent() {
|
||||
this.checkOverlapNode = throttle(this.checkOverlapNode, 300, this)
|
||||
this.mindMap.on('node_mousedown', (node, e) => {
|
||||
if (this.mindMap.opt.readonly || node.isGeneralization) {
|
||||
return
|
||||
}
|
||||
if (e.which !== 1 || node.isRoot) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
// 计算鼠标按下的位置距离节点左上角的距离
|
||||
this.drawTransform = this.mindMap.draw.transform()
|
||||
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
|
||||
this.offsetX = e.clientX - (node.left * scaleX + translateX)
|
||||
this.offsetY = e.clientY - (node.top * scaleY + translateY)
|
||||
//
|
||||
this.node = node
|
||||
this.isMousedown = true
|
||||
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
|
||||
this.mouseDownX = x
|
||||
this.mouseDownY = y
|
||||
})
|
||||
this.mindMap.on('mousemove', e => {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
|
||||
this.mouseMoveX = x
|
||||
this.mouseMoveY = y
|
||||
if (
|
||||
Math.abs(x - this.mouseDownX) <= 10 &&
|
||||
Math.abs(y - this.mouseDownY) <= 10 &&
|
||||
!this.node.isDrag
|
||||
) {
|
||||
return
|
||||
}
|
||||
this.mindMap.renderer.clearAllActive()
|
||||
this.onMove(x, y)
|
||||
})
|
||||
this.onMouseup = this.onMouseup.bind(this)
|
||||
this.mindMap.on('node_mouseup', this.onMouseup)
|
||||
this.mindMap.on('mouseup', this.onMouseup)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-23 19:38:02
|
||||
* @Desc: 鼠标松开事件
|
||||
*/
|
||||
onMouseup(e) {
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
this.isMousedown = false
|
||||
let _nodeIsDrag = this.node.isDrag
|
||||
this.node.isDrag = false
|
||||
this.node.show()
|
||||
this.removeCloneNode()
|
||||
// 存在重叠子节点,则移动作为其子节点
|
||||
if (this.overlapNode) {
|
||||
this.mindMap.renderer.setNodeActive(this.overlapNode, false)
|
||||
this.mindMap.execCommand('MOVE_NODE_TO', this.node, this.overlapNode)
|
||||
} else if (this.prevNode) {
|
||||
// 存在前一个相邻节点,作为其下一个兄弟节点
|
||||
this.mindMap.renderer.setNodeActive(this.prevNode, false)
|
||||
this.mindMap.execCommand('INSERT_AFTER', this.node, this.prevNode)
|
||||
} else if (this.nextNode) {
|
||||
// 存在下一个相邻节点,作为其前一个兄弟节点
|
||||
this.mindMap.renderer.setNodeActive(this.nextNode, false)
|
||||
this.mindMap.execCommand('INSERT_BEFORE', this.node, this.nextNode)
|
||||
} else if (_nodeIsDrag) {
|
||||
// 自定义位置
|
||||
let { x, y } = this.mindMap.toPos(
|
||||
e.clientX - this.offsetX,
|
||||
e.clientY - this.offsetY
|
||||
)
|
||||
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
|
||||
x = (x - translateX) / scaleX
|
||||
y = (y - translateY) / scaleY
|
||||
this.node.left = x
|
||||
this.node.top = y
|
||||
this.node.customLeft = x
|
||||
this.node.customTop = y
|
||||
this.mindMap.execCommand('SET_NODE_CUSTOM_POSITION', this.node, x, y)
|
||||
this.mindMap.render()
|
||||
}
|
||||
this.reset()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-23 19:34:53
|
||||
* @Desc: 创建克隆节点
|
||||
*/
|
||||
createCloneNode() {
|
||||
if (!this.clone) {
|
||||
// 节点
|
||||
this.clone = this.node.group.clone()
|
||||
this.clone.opacity(0.5)
|
||||
this.clone.css('z-index', 99999)
|
||||
this.node.isDrag = true
|
||||
this.node.hide()
|
||||
// 连接线
|
||||
this.line = this.draw.path()
|
||||
this.line.opacity(0.5)
|
||||
this.node.styleLine(this.line, this.node)
|
||||
// 同级位置占位符
|
||||
this.placeholder = this.draw.rect().fill({
|
||||
color: this.node.style.merge('lineColor', true)
|
||||
})
|
||||
this.mindMap.draw.add(this.clone)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-23 19:35:16
|
||||
* @Desc: 移除克隆节点
|
||||
*/
|
||||
removeCloneNode() {
|
||||
if (!this.clone) {
|
||||
return
|
||||
}
|
||||
this.clone.remove()
|
||||
this.line.remove()
|
||||
this.placeholder.remove()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-23 18:53:47
|
||||
* @Desc: 拖动中
|
||||
*/
|
||||
onMove(x, y) {
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
this.createCloneNode()
|
||||
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
|
||||
this.cloneNodeLeft = x - this.offsetX
|
||||
this.cloneNodeTop = y - this.offsetY
|
||||
x = (this.cloneNodeLeft - translateX) / scaleX
|
||||
y = (this.cloneNodeTop - translateY) / scaleY
|
||||
let t = this.clone.transform()
|
||||
this.clone.translate(x - t.translateX, y - t.translateY)
|
||||
// 连接线
|
||||
let parent = this.node.parent
|
||||
this.line.plot(
|
||||
this.quadraticCurvePath(
|
||||
parent.left + parent.width / 2,
|
||||
parent.top + parent.height / 2,
|
||||
x + this.node.width / 2,
|
||||
y + this.node.height / 2
|
||||
)
|
||||
)
|
||||
this.checkOverlapNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 10:20:43
|
||||
* @Desc: 检测重叠节点
|
||||
*/
|
||||
checkOverlapNode() {
|
||||
if (!this.drawTransform) {
|
||||
return
|
||||
}
|
||||
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
|
||||
let checkRight = this.cloneNodeLeft + this.node.width * scaleX
|
||||
let checkBottom = this.cloneNodeTop + this.node.height * scaleX
|
||||
this.overlapNode = null
|
||||
this.prevNode = null
|
||||
this.nextNode = null
|
||||
this.placeholder.size(0, 0)
|
||||
bfsWalk(this.mindMap.renderer.root, node => {
|
||||
if (node.nodeData.data.isActive) {
|
||||
this.mindMap.renderer.setNodeActive(node, false)
|
||||
}
|
||||
if (node === this.node || this.node.isParent(node)) {
|
||||
return
|
||||
}
|
||||
if (this.overlapNode || (this.prevNode && this.nextNode)) {
|
||||
return
|
||||
}
|
||||
let { left, top, width, height } = node
|
||||
let _left = left
|
||||
let _top = top
|
||||
let _bottom = top + height
|
||||
let right = (left + width) * scaleX + translateX
|
||||
let bottom = (top + height) * scaleY + translateY
|
||||
left = left * scaleX + translateX
|
||||
top = top * scaleY + translateY
|
||||
// 检测是否重叠
|
||||
if (!this.overlapNode) {
|
||||
if (
|
||||
left <= checkRight &&
|
||||
right >= this.cloneNodeLeft &&
|
||||
top <= checkBottom &&
|
||||
bottom >= this.cloneNodeTop
|
||||
) {
|
||||
this.overlapNode = node
|
||||
}
|
||||
}
|
||||
// 检测兄弟节点位置
|
||||
if (!this.prevNode && !this.nextNode && this.node.isBrother(node)) {
|
||||
if (left <= checkRight && right >= this.cloneNodeLeft) {
|
||||
if (this.cloneNodeTop > bottom && this.cloneNodeTop <= bottom + 10) {
|
||||
this.prevNode = node
|
||||
this.placeholder.size(node.width, 10).move(_left, _bottom)
|
||||
} else if (checkBottom < top && checkBottom >= top - 10) {
|
||||
this.nextNode = node
|
||||
this.placeholder.size(node.width, 10).move(_left, _top - 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if (this.overlapNode) {
|
||||
this.mindMap.renderer.setNodeActive(this.overlapNode, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Drag
|
||||
@@ -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,198 +0,0 @@
|
||||
import { bfsWalk, throttle } from './utils'
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-10 22:34:51
|
||||
* @Desc: 选择节点类
|
||||
*/
|
||||
|
||||
class Select {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-10 22:35:16
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor({ mindMap }) {
|
||||
this.mindMap = mindMap
|
||||
this.rect = null
|
||||
this.isMousedown = false
|
||||
this.mouseDownX = 0
|
||||
this.mouseDownY = 0
|
||||
this.mouseMoveX = 0
|
||||
this.mouseMoveY = 0
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-10 22:36:36
|
||||
* @Desc: 绑定事件
|
||||
*/
|
||||
bindEvent() {
|
||||
this.checkInNodes = throttle(this.checkInNodes, 500, this)
|
||||
this.mindMap.on('mousedown', e => {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
if (!e.ctrlKey && e.which !== 3) {
|
||||
return
|
||||
}
|
||||
this.isMousedown = true
|
||||
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
|
||||
this.mouseDownX = x
|
||||
this.mouseDownY = y
|
||||
this.createRect(x, y)
|
||||
})
|
||||
this.mindMap.on('mousemove', e => {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
|
||||
this.mouseMoveX = x
|
||||
this.mouseMoveY = y
|
||||
if (
|
||||
Math.abs(x - this.mouseDownX) <= 10 &&
|
||||
Math.abs(y - this.mouseDownY) <= 10
|
||||
) {
|
||||
return
|
||||
}
|
||||
clearTimeout(this.autoMoveTimer)
|
||||
this.onMove(x, y)
|
||||
})
|
||||
this.mindMap.on('mouseup', () => {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
this.mindMap.emit(
|
||||
'node_active',
|
||||
null,
|
||||
this.mindMap.renderer.activeNodeList
|
||||
)
|
||||
clearTimeout(this.autoMoveTimer)
|
||||
this.isMousedown = false
|
||||
if (this.rect) this.rect.remove()
|
||||
this.rect = null
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-13 07:55:49
|
||||
* @Desc: 鼠标移动事件
|
||||
*/
|
||||
onMove(x, y) {
|
||||
// 绘制矩形
|
||||
this.rect.plot([
|
||||
[this.mouseDownX, this.mouseDownY],
|
||||
[this.mouseMoveX, this.mouseDownY],
|
||||
[this.mouseMoveX, this.mouseMoveY],
|
||||
[this.mouseDownX, this.mouseMoveY]
|
||||
])
|
||||
this.checkInNodes()
|
||||
// 检测边缘移动
|
||||
let step = this.mindMap.opt.selectTranslateStep
|
||||
let limit = this.mindMap.opt.selectTranslateLimit
|
||||
let count = 0
|
||||
// 左边缘
|
||||
if (x <= this.mindMap.elRect.left + limit) {
|
||||
this.mouseDownX += step
|
||||
this.mindMap.view.translateX(step)
|
||||
count++
|
||||
}
|
||||
// 右边缘
|
||||
if (x >= this.mindMap.elRect.right - limit) {
|
||||
this.mouseDownX -= step
|
||||
this.mindMap.view.translateX(-step)
|
||||
count++
|
||||
}
|
||||
// 上边缘
|
||||
if (y <= this.mindMap.elRect.top + limit) {
|
||||
this.mouseDownY += step
|
||||
this.mindMap.view.translateY(step)
|
||||
count++
|
||||
}
|
||||
// 下边缘
|
||||
if (y >= this.mindMap.elRect.bottom - limit) {
|
||||
this.mouseDownY -= step
|
||||
this.mindMap.view.translateY(-step)
|
||||
count++
|
||||
}
|
||||
if (count > 0) {
|
||||
this.startAutoMove(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-22 08:02:23
|
||||
* @Desc: 开启自动移动
|
||||
*/
|
||||
startAutoMove(x, y) {
|
||||
this.autoMoveTimer = setTimeout(() => {
|
||||
this.onMove(x, y)
|
||||
}, 20)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 10:19:37
|
||||
* @Desc: 创建矩形
|
||||
*/
|
||||
createRect(x, y) {
|
||||
this.rect = this.mindMap.svg
|
||||
.polygon()
|
||||
.stroke({
|
||||
color: '#0984e3'
|
||||
})
|
||||
.fill({
|
||||
color: 'rgba(9,132,227,0.3)'
|
||||
})
|
||||
.plot([[x, y]])
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 10:20:43
|
||||
* @Desc: 检测在选区里的节点
|
||||
*/
|
||||
checkInNodes() {
|
||||
let { scaleX, scaleY, translateX, translateY } =
|
||||
this.mindMap.draw.transform()
|
||||
let minx = Math.min(this.mouseDownX, this.mouseMoveX)
|
||||
let miny = Math.min(this.mouseDownY, this.mouseMoveY)
|
||||
let maxx = Math.max(this.mouseDownX, this.mouseMoveX)
|
||||
let maxy = Math.max(this.mouseDownY, this.mouseMoveY)
|
||||
bfsWalk(this.mindMap.renderer.root, node => {
|
||||
let { left, top, width, height } = node
|
||||
let right = (left + width) * scaleX + translateX
|
||||
let bottom = (top + height) * scaleY + translateY
|
||||
left = left * scaleX + translateX
|
||||
top = top * scaleY + translateY
|
||||
if (left >= minx && right <= maxx && top >= miny && bottom <= maxy) {
|
||||
this.mindMap.batchExecution.push('activeNode' + node.uid, () => {
|
||||
if (node.nodeData.data.isActive) {
|
||||
return
|
||||
}
|
||||
this.mindMap.renderer.setNodeActive(node, true)
|
||||
this.mindMap.renderer.addActiveNode(node)
|
||||
})
|
||||
} else if (node.nodeData.data.isActive) {
|
||||
this.mindMap.batchExecution.push('activeNode' + node.uid, () => {
|
||||
if (!node.nodeData.data.isActive) {
|
||||
return
|
||||
}
|
||||
this.mindMap.renderer.setNodeActive(node, false)
|
||||
this.mindMap.renderer.removeActiveNode(node)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default Select
|
||||
@@ -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 |
353
simple-mind-map/src/constants/constant.js
Normal file
@@ -0,0 +1,353 @@
|
||||
// 主题列表
|
||||
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'
|
||||
},
|
||||
SCROLL_BAR_DIR: {
|
||||
VERTICAL: 'vertical',
|
||||
HORIZONTAL: 'horizontal'
|
||||
}
|
||||
}
|
||||
|
||||
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',
|
||||
'associativeLinePoint',
|
||||
'associativeLineText'
|
||||
]
|
||||
|
||||
// 数据缓存
|
||||
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'
|
||||
}
|
||||
|
||||
// a4纸的宽高
|
||||
export const a4Size = {
|
||||
width: 592.28,
|
||||
height: 841.89
|
||||
}
|
||||
|
||||
// css
|
||||
export const cssContent = `
|
||||
/* 鼠标hover和激活时渲染的矩形 */
|
||||
.smm-hover-node{
|
||||
display: none;
|
||||
opacity: 0.6;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.smm-node:not(.smm-node-dragging):hover .smm-hover-node{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.smm-node.active .smm-hover-node{
|
||||
display: block;
|
||||
opacity: 1;
|
||||
stroke-width: 2;
|
||||
}
|
||||
`
|
||||
213
simple-mind-map/src/constants/defaultOptions.js
Normal file
@@ -0,0 +1,213 @@
|
||||
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',
|
||||
fontSize: 13,
|
||||
strokeColor: '#333333'
|
||||
},
|
||||
// 自定义展开收起按钮的图标
|
||||
expandBtnIcon: {
|
||||
open: '', // svg字符串
|
||||
close: ''
|
||||
},
|
||||
// 处理收起节点数量
|
||||
expandBtnNumHandler: num => {
|
||||
return num
|
||||
},
|
||||
// 是否显示带数量的收起按钮
|
||||
isShowExpandNum: true,
|
||||
// 是否只有当鼠标在画布内才响应快捷键事件
|
||||
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)
|
||||
},
|
||||
// 设置导出图片和svg时,针对富文本节点内容,也就是嵌入到svg中的html节点的默认样式覆盖
|
||||
// 如果不覆盖,会发生偏移问题
|
||||
resetCss: `
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`,
|
||||
// 开启鼠标双击复位思维导图位置及缩放
|
||||
enableDblclickReset: false,
|
||||
// 导出图片时canvas的缩放倍数,该配置会和window.devicePixelRatio值取最大值
|
||||
minExportImgCanvasScale: 2,
|
||||
// 节点鼠标hover和激活时显示的矩形边框的颜色
|
||||
hoverRectColor: 'rgb(94, 200, 248)',
|
||||
// 节点鼠标hover和激活时显示的矩形边框距节点内容的距离
|
||||
hoverRectPadding: 2,
|
||||
// 双击节点进入节点文本编辑时是否默认选中文本,默认只在创建新节点时会选中
|
||||
selectTextOnEnterEditText: false,
|
||||
// 删除节点后激活相邻节点
|
||||
deleteNodeActive: true,
|
||||
// 拖拽节点时鼠标移动到画布边缘是否开启画布自动移动
|
||||
autoMoveWhenMouseInEdgeOnDrag: true,
|
||||
// 是否首次加载fit view
|
||||
fit: false,
|
||||
// 拖拽多个节点时随鼠标移动的示意矩形的样式配置
|
||||
dragMultiNodeRectConfig: {
|
||||
width: 40,
|
||||
height: 20,
|
||||
fill: '' // 填充颜色,如果不传默认使用连线的颜色
|
||||
},
|
||||
// 节点拖拽时新位置的示意矩形的填充颜色,如果不传默认使用连线的颜色
|
||||
dragPlaceholderRectFill: '',
|
||||
// 节点拖拽时的透明度配置
|
||||
dragOpacityConfig: {
|
||||
cloneNodeOpacity: 0.5, // 跟随鼠标移动的克隆节点或矩形的透明度
|
||||
beingDragNodeOpacity: 0.3 // 被拖拽节点的透明度
|
||||
},
|
||||
// 自定义标签的颜色
|
||||
// {pass: 'green, unpass: 'red'}
|
||||
tagsColorMap: {},
|
||||
// 节点协作样式配置
|
||||
cooperateStyle: {
|
||||
avatarSize: 22,// 头像大小
|
||||
fontSize: 12,// 如果是文字头像,那么文字的大小
|
||||
}
|
||||
}
|
||||
@@ -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,24 @@ 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 +57,7 @@ class Command {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-15 23:02:41
|
||||
* @Desc: 移除命令
|
||||
*/
|
||||
// 移除命令
|
||||
remove(name, fn) {
|
||||
if (!this.commands[name]) {
|
||||
return
|
||||
@@ -98,14 +75,27 @@ 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 +105,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 +117,49 @@ 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,65 @@ 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 +77,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 +97,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 +115,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 +130,8 @@ export default class KeyCommand {
|
||||
return arr
|
||||
}
|
||||
|
||||
// 添加快捷键命令
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 15:23:00
|
||||
* @Desc: 添加快捷键命令
|
||||
* Enter
|
||||
* Tab | Insert
|
||||
* Shift + a
|
||||
@@ -161,12 +146,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 +165,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
|
||||
1482
simple-mind-map/src/core/render/Render.js
Normal file
245
simple-mind-map/src/core/render/TextEdit.js
Normal file
@@ -0,0 +1,245 @@
|
||||
import {
|
||||
getStrWithBrFromHtml,
|
||||
checkNodeOuter,
|
||||
focusInput,
|
||||
selectAllInput,
|
||||
htmlEscape
|
||||
} 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, e, false, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否是自动进入文本编模式的按钮
|
||||
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:是否是刚创建的节点
|
||||
// isFromKeyDown:是否是在按键事件进入的编辑
|
||||
async show(node, e, isInserting = false, isFromKeyDown = 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, isFromKeyDown)
|
||||
return
|
||||
}
|
||||
this.showEditTextBox(node, rect, isInserting, isFromKeyDown)
|
||||
}
|
||||
|
||||
// 处理画布缩放
|
||||
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, isFromKeyDown) {
|
||||
if (this.showTextEdit) return
|
||||
const { nodeTextEditZIndex, textAutoWrapWidth, selectTextOnEnterEditText } =
|
||||
this.mindMap.opt
|
||||
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)
|
||||
.map(item => {
|
||||
return htmlEscape(item)
|
||||
})
|
||||
let isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true'
|
||||
node.style.domText(this.textEditNode, scale, isMultiLine)
|
||||
this.textEditNode.style.zIndex = 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 = textAutoWrapWidth * scale + 'px'
|
||||
if (isMultiLine && lineHeight !== 1) {
|
||||
this.textEditNode.style.transform = `translateY(${
|
||||
-((lineHeight * fontSize - fontSize) / 2) * scale
|
||||
}px)`
|
||||
}
|
||||
this.showTextEdit = true
|
||||
// 选中文本
|
||||
// if (!this.cacheEditingText) {
|
||||
// selectAllInput(this.textEditNode)
|
||||
// }
|
||||
if (isInserting || (selectTextOnEnterEditText && !isFromKeyDown)) {
|
||||
selectAllInput(this.textEditNode)
|
||||
} else {
|
||||
focusInput(this.textEditNode)
|
||||
}
|
||||
this.cacheEditingText = ''
|
||||
}
|
||||
|
||||
// 获取当前正在编辑的内容
|
||||
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
|
||||
}
|
||||
}
|
||||
951
simple-mind-map/src/core/render/node/Node.js
Normal file
@@ -0,0 +1,951 @@
|
||||
import Style from './Style'
|
||||
import Shape from './Shape'
|
||||
import { G, ForeignObject, SVG, Rect } 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 nodeCooperateMethods from './nodeCooperate'
|
||||
import { CONSTANTS } from '../../../constants/constant'
|
||||
|
||||
// 节点类
|
||||
class Node {
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
// 节点数据
|
||||
this.nodeData = this.handleData(opt.data || {})
|
||||
// uid
|
||||
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.userList = []
|
||||
// 节点内容的容器
|
||||
this.group = null
|
||||
this.shapeNode = null // 节点形状节点
|
||||
this.hoverNode = null // 节点hover和激活的节点
|
||||
// 节点内容对象
|
||||
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._userListGroup = 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)
|
||||
})
|
||||
// 协同相关
|
||||
if (this.mindMap.cooperate) {
|
||||
Object.keys(nodeCooperateMethods).forEach((item) => {
|
||||
this[item] = nodeCooperateMethods[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()
|
||||
const { hoverRectPadding } = this.mindMap.opt
|
||||
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.style.shape(this.shapeNode)
|
||||
this.group.add(this.shapeNode)
|
||||
// 渲染一个隐藏的矩形区域,用来触发展开收起按钮的显示
|
||||
this.renderExpandBtnPlaceholderRect()
|
||||
// 创建协同头像节点
|
||||
if (this.createUserListNode) this.createUserListNode()
|
||||
// 概要节点添加一个带所属节点id的类名
|
||||
if (this.isGeneralization && this.generalizationBelongNode) {
|
||||
this.group.addClass('generalization_' + this.generalizationBelongNode.uid)
|
||||
}
|
||||
// 激活hover和激活边框
|
||||
const addHoverNode = () => {
|
||||
this.hoverNode = new Rect()
|
||||
.size(width + hoverRectPadding * 2, height + hoverRectPadding * 2)
|
||||
.x(-hoverRectPadding)
|
||||
.y(-hoverRectPadding)
|
||||
this.hoverNode.addClass('smm-hover-node')
|
||||
this.style.hoverNode(this.hoverNode, width, height)
|
||||
this.group.add(this.hoverNode)
|
||||
}
|
||||
// 如果存在自定义节点内容,那么使用自定义节点内容
|
||||
if (this.isUseCustomNodeContent()) {
|
||||
let foreignObject = new ForeignObject()
|
||||
foreignObject.width(width)
|
||||
foreignObject.height(height)
|
||||
foreignObject.add(this._customNodeContent)
|
||||
this.group.add(foreignObject)
|
||||
addHoverNode()
|
||||
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)
|
||||
addHoverNode()
|
||||
}
|
||||
|
||||
// 给节点绑定事件
|
||||
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 => {
|
||||
const {
|
||||
readonly,
|
||||
enableCtrlKeyNodeSelection,
|
||||
useLeftKeySelectionRightKeyDrag
|
||||
} = this.mindMap.opt
|
||||
// 只读模式不需要阻止冒泡
|
||||
if (!readonly) {
|
||||
if (this.isRoot) {
|
||||
// 根节点,右键拖拽画布模式下不需要阻止冒泡
|
||||
if (e.which === 3 && !useLeftKeySelectionRightKeyDrag) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
} else {
|
||||
// 非根节点,且按下的是非鼠标中键,需要阻止事件冒泡
|
||||
if (e.which !== 2) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
}
|
||||
// 多选和取消多选
|
||||
if (e.ctrlKey && 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 => {
|
||||
if (this.isDrag) return
|
||||
this._isMouseenter = true
|
||||
// 显示展开收起按钮
|
||||
this.showExpandBtn()
|
||||
this.mindMap.emit('node_mouseenter', this, e)
|
||||
})
|
||||
this.group.on('mouseleave', e => {
|
||||
if (!this._isMouseenter) return
|
||||
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 => {
|
||||
const { readonly, useLeftKeySelectionRightKeyDrag } = this.mindMap.opt
|
||||
// 按住ctrl键点击鼠标左键不知为何触发的是contextmenu事件
|
||||
if (readonly || e.ctrlKey) {
|
||||
return
|
||||
}
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
// 如果是多选节点结束,那么不要触发右键菜单事件
|
||||
if (
|
||||
this.mindMap.select &&
|
||||
!useLeftKeySelectionRightKeyDrag &&
|
||||
this.mindMap.select.hasSelectRange()
|
||||
) {
|
||||
return
|
||||
}
|
||||
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() {
|
||||
if (!this.group) {
|
||||
return
|
||||
}
|
||||
this.updateNodeActive()
|
||||
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()
|
||||
// 更新协同头像
|
||||
if (this.updateUserListNode) this.updateUserListNode()
|
||||
// 更新节点位置
|
||||
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
|
||||
}
|
||||
|
||||
// 更新节点激活状态
|
||||
updateNodeActive() {
|
||||
if (!this.group) return
|
||||
const isActive = this.nodeData.data.isActive
|
||||
this.group[isActive ? 'addClass' : 'removeClass']('active')
|
||||
}
|
||||
|
||||
// 递归渲染
|
||||
render(callback = () => {}) {
|
||||
// 节点
|
||||
// 重新渲染连线
|
||||
this.renderLine()
|
||||
if (!this.group) {
|
||||
// 创建组
|
||||
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()
|
||||
} 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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 设置节点透明度
|
||||
// 包括连接线和下级节点
|
||||
setOpacity(val) {
|
||||
// 自身及连线
|
||||
this.group.opacity(val)
|
||||
this._lines.forEach(line => {
|
||||
line.opacity(val)
|
||||
})
|
||||
// 子节点
|
||||
this.children.forEach(item => {
|
||||
item.setOpacity(val)
|
||||
})
|
||||
// 概要节点
|
||||
if (this._generalizationNode) {
|
||||
this._generalizationLine.opacity(val)
|
||||
this._generalizationNode.group.opacity(val)
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏子节点
|
||||
hideChildren() {
|
||||
this._lines.forEach(item => {
|
||||
item.hide()
|
||||
})
|
||||
if (this.children && this.children.length) {
|
||||
this.children.forEach(item => {
|
||||
item.hide()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 显示子节点
|
||||
showChildren() {
|
||||
this._lines.forEach(item => {
|
||||
item.show()
|
||||
})
|
||||
if (this.children && this.children.length) {
|
||||
this.children.forEach(item => {
|
||||
item.show()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 被拖拽中
|
||||
startDrag() {
|
||||
this.isDrag = true
|
||||
this.group.addClass('smm-node-dragging')
|
||||
}
|
||||
|
||||
// 拖拽结束
|
||||
endDrag() {
|
||||
this.isDrag = false
|
||||
this.group.removeClass('smm-node-dragging')
|
||||
}
|
||||
|
||||
// 连线
|
||||
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.uid === node.uid) {
|
||||
return false
|
||||
}
|
||||
let parent = node.parent
|
||||
while (parent) {
|
||||
if (this.uid === parent.uid) {
|
||||
return true
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 检测当前节点是否是某个节点的兄弟节点
|
||||
isBrother(node) {
|
||||
if (!this.parent || this.uid === node.uid) {
|
||||
return false
|
||||
}
|
||||
return this.parent.children.find(item => {
|
||||
return item.uid === node.uid
|
||||
})
|
||||
}
|
||||
|
||||
// 获取padding值
|
||||
getPaddingVale() {
|
||||
let { isActive } = this.nodeData.data
|
||||
return {
|
||||
paddingX: this.getStyle('paddingX', true, isActive),
|
||||
paddingY: this.getStyle('paddingY', true, isActive)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取某个样式
|
||||
getStyle(prop, root) {
|
||||
let v = this.style.merge(prop, root)
|
||||
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) || 0
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
getData(key) {
|
||||
return key ? this.nodeData.data[key] || '' : this.nodeData.data
|
||||
}
|
||||
|
||||
// 是否存在自定义样式
|
||||
hasCustomStyle() {
|
||||
return this.style.hasCustomStyle()
|
||||
}
|
||||
}
|
||||
|
||||
export default Node
|
||||
260
simple-mind-map/src/core/render/node/Shape.js
Normal file
@@ -0,0 +1,260 @@
|
||||
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()
|
||||
let node = null
|
||||
// 矩形
|
||||
if (shape === CONSTANTS.SHAPE.RECTANGLE) {
|
||||
node = this.createRect()
|
||||
} 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
|
||||
}
|
||||
|
||||
// 获取节点减去节点边框宽度、hover节点边框宽度后的尺寸
|
||||
getNodeSize() {
|
||||
const borderWidth = this.node.getBorderWidth()
|
||||
let { width, height } = this.node
|
||||
width -= borderWidth
|
||||
height -= borderWidth
|
||||
return {
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
// 创建矩形
|
||||
createRect() {
|
||||
let { width, height } = this.getNodeSize()
|
||||
let borderRadius = this.node.style.merge('borderRadius')
|
||||
return new Path().plot(`
|
||||
M${borderRadius},0
|
||||
L${width - borderRadius},0
|
||||
C${width - borderRadius},0 ${width},${0} ${width},${borderRadius}
|
||||
L${width},${height - borderRadius}
|
||||
C${width},${height - borderRadius} ${width},${height} ${
|
||||
width - borderRadius
|
||||
},${height}
|
||||
L${borderRadius},${height}
|
||||
C${borderRadius},${height} ${0},${height} ${0},${height - borderRadius}
|
||||
L${0},${borderRadius}
|
||||
C${0},${borderRadius} ${0},${0} ${borderRadius},${0}
|
||||
Z
|
||||
`)
|
||||
}
|
||||
|
||||
// 创建菱形
|
||||
createDiamond() {
|
||||
let { width, height } = this.getNodeSize()
|
||||
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.getNodeSize()
|
||||
return new Polygon().plot([
|
||||
[paddingX, 0],
|
||||
[width, 0],
|
||||
[width - paddingX, height],
|
||||
[0, height]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建圆角矩形
|
||||
createRoundedRectangle() {
|
||||
let { width, height } = this.getNodeSize()
|
||||
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.getNodeSize()
|
||||
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.getNodeSize()
|
||||
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.getNodeSize()
|
||||
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.getNodeSize()
|
||||
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.getNodeSize()
|
||||
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
|
||||
]
|
||||
247
simple-mind-map/src/core/render/node/Style.js
Normal file
@@ -0,0 +1,247 @@
|
||||
import {
|
||||
checkIsNodeStyleDataKey,
|
||||
generateColorByContent
|
||||
} from '../../../utils/index'
|
||||
|
||||
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) {
|
||||
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
|
||||
}
|
||||
// 优先使用节点本身的样式
|
||||
return this.getSelfStyle(prop) !== undefined
|
||||
? this.getSelfStyle(prop)
|
||||
: defaultConfig[prop]
|
||||
}
|
||||
|
||||
// 获取某个样式值
|
||||
getStyle(prop, root) {
|
||||
return this.merge(prop, root)
|
||||
}
|
||||
|
||||
// 获取自身自定义样式
|
||||
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) {
|
||||
node
|
||||
.fill({
|
||||
color: '#fff'
|
||||
})
|
||||
.css({
|
||||
'font-size': '12px'
|
||||
})
|
||||
}
|
||||
|
||||
// 标签矩形
|
||||
tagRect(node, text, color) {
|
||||
node.fill({
|
||||
color: color || generateColorByContent(text.node.textContent)
|
||||
})
|
||||
}
|
||||
|
||||
// 内置图标
|
||||
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, fontSize, fontColor } = this.ctx.mindMap.opt
|
||||
.expandBtnStyle || {
|
||||
color: '#808080',
|
||||
fill: '#fff',
|
||||
fontSize: 12,
|
||||
strokeColor: '#333333',
|
||||
fontColor: '#333333'
|
||||
}
|
||||
node.fill({ color: color })
|
||||
node2.fill({ color: color })
|
||||
fillNode.fill({ color: fill })
|
||||
if (this.ctx.mindMap.opt.isShowExpandNum) {
|
||||
node.attr({ 'font-size': fontSize, 'font-color': fontColor })
|
||||
}
|
||||
}
|
||||
|
||||
// 是否设置了自定义的样式
|
||||
hasCustomStyle() {
|
||||
let res = false
|
||||
Object.keys(this.ctx.nodeData.data).forEach(item => {
|
||||
if (checkIsNodeStyleDataKey(item)) {
|
||||
res = true
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// hover和激活节点
|
||||
hoverNode(node) {
|
||||
const { hoverRectColor } = this.ctx.mindMap.opt
|
||||
node.radius(5).fill('none').stroke({
|
||||
color: hoverRectColor
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
this.mindMap.execCommand('SET_NODE_STYLE', this, prop, value)
|
||||
}
|
||||
|
||||
// 修改多个样式
|
||||
function setStyles(style) {
|
||||
this.mindMap.execCommand('SET_NODE_STYLES', this, style)
|
||||
}
|
||||
|
||||
export default {
|
||||
setData,
|
||||
setText,
|
||||
setImage,
|
||||
setIcon,
|
||||
setHyperlink,
|
||||
setNote,
|
||||
setTag,
|
||||
setShape,
|
||||
setStyle,
|
||||
setStyles
|
||||
}
|
||||
104
simple-mind-map/src/core/render/node/nodeCooperate.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import { Circle, G, Text, Image } from '@svgdotjs/svg.js'
|
||||
import { generateColorByContent } from '../../../utils/index'
|
||||
|
||||
// 协同相关功能
|
||||
|
||||
// 创建容器
|
||||
function createUserListNode() {
|
||||
// 如果没有注册协作插件,那么需要创建
|
||||
if (!this.mindMap.cooperate) return
|
||||
this._userListGroup = new G()
|
||||
this.group.add(this._userListGroup)
|
||||
}
|
||||
|
||||
// 创建文本头像
|
||||
function createTextAvatar(item) {
|
||||
const { avatarSize, fontSize } = this.mindMap.opt.cooperateStyle
|
||||
const g = new G()
|
||||
const str = item.isMore ? item.name : String(item.name)[0]
|
||||
// 圆
|
||||
const circle = new Circle().size(avatarSize, avatarSize)
|
||||
circle.fill({
|
||||
color: item.color || generateColorByContent(str)
|
||||
})
|
||||
// 文本
|
||||
const text = new Text()
|
||||
.text(str)
|
||||
.fill({
|
||||
color: '#fff'
|
||||
})
|
||||
.css({
|
||||
'font-size': fontSize
|
||||
})
|
||||
.dx(-fontSize / 2)
|
||||
.dy((avatarSize - fontSize) / 2)
|
||||
g.add(circle).add(text)
|
||||
return g
|
||||
}
|
||||
|
||||
// 创建图片头像
|
||||
function createImageAvatar(item) {
|
||||
const { avatarSize } = this.mindMap.opt.cooperateStyle
|
||||
return new Image().load(item.avatar).size(avatarSize, avatarSize)
|
||||
}
|
||||
|
||||
// 更新渲染
|
||||
function updateUserListNode() {
|
||||
if (!this._userListGroup) return
|
||||
const { avatarSize } = this.mindMap.opt.cooperateStyle
|
||||
this._userListGroup.clear()
|
||||
// 根据当前节点长度计算最多能显示几个
|
||||
const length = this.userList.length
|
||||
const maxShowCount = Math.floor(this.width / avatarSize)
|
||||
const list = []
|
||||
if (length > maxShowCount) {
|
||||
// 如果当前用户数量比最多能显示的多,最后需要显示一个提示信息
|
||||
list.push(...this.userList.slice(0, maxShowCount - 1), {
|
||||
isMore: true,
|
||||
name: '+' + (length - maxShowCount + 1)
|
||||
})
|
||||
} else {
|
||||
list.push(...this.userList)
|
||||
}
|
||||
list.forEach((item, index) => {
|
||||
let node = null
|
||||
if (item.avatar) {
|
||||
node = this.createImageAvatar(item)
|
||||
} else {
|
||||
node = this.createTextAvatar(item)
|
||||
}
|
||||
node.x(index * avatarSize).cy(-avatarSize / 2)
|
||||
this._userListGroup.add(node)
|
||||
})
|
||||
}
|
||||
|
||||
// 添加用户
|
||||
function addUser(userInfo) {
|
||||
if (
|
||||
this.userList.find(item => {
|
||||
return item.id == userInfo.id
|
||||
})
|
||||
)
|
||||
return
|
||||
this.userList.push(userInfo)
|
||||
this.updateUserListNode()
|
||||
}
|
||||
|
||||
// 移除用户
|
||||
function removeUser(userInfo) {
|
||||
const index = this.userList.findIndex(item => {
|
||||
return item.id == userInfo.id
|
||||
})
|
||||
if (index === -1) return
|
||||
this.userList.splice(index, 1)
|
||||
this.updateUserListNode()
|
||||
}
|
||||
|
||||
export default {
|
||||
createUserListNode,
|
||||
updateUserListNode,
|
||||
createTextAvatar,
|
||||
createImageAvatar,
|
||||
addUser,
|
||||
removeUser
|
||||
}
|
||||