Compare commits
597 Commits
0.2.9
...
0.6.11-dem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
516676b484 | ||
|
|
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.10aa67e3.js" rel="prefetch"><link href="dist/js/chunk-2d216b67.2d06497a.js" rel="prefetch"><link href="dist/js/chunk-5397ae43.9dca0f90.js" rel="prefetch"><link href="dist/css/app.891a59ac.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.597033a2.css" rel="preload" as="style"><link href="dist/js/app.6ae41667.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.9c26d9dc.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.597033a2.css" rel="stylesheet"><link href="dist/css/app.891a59ac.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.9c26d9dc.js"></script><script src="dist/js/app.6ae41667.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?e577492ccbbe7e7070c3" rel="stylesheet"><link href="dist/css/app.css?e577492ccbbe7e7070c3" 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?e577492ccbbe7e7070c3"></script><script src="dist/js/app.js?e577492ccbbe7e7070c3"></script></body></html>
|
||||
BIN
qrcode.jpg
Normal file
|
After Width: | Height: | Size: 182 KiB |
17
simple-mind-map/.eslintrc.js
Normal file
@@ -0,0 +1,17 @@
|
||||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"overrides": [
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": 'babel-eslint',
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module",
|
||||
"allowImportExportEverywhere": true
|
||||
},
|
||||
"rules": {
|
||||
}
|
||||
}
|
||||
10
simple-mind-map/.prettierignore
Normal file
@@ -0,0 +1,10 @@
|
||||
src/assets
|
||||
*/.DS_Store
|
||||
dist
|
||||
example
|
||||
node_modules
|
||||
*.json
|
||||
*.md
|
||||
.eslintrc.js
|
||||
.prettierignore
|
||||
.prettierrc
|
||||
5
simple-mind-map/.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
||||
semi: false
|
||||
singleQuote: true
|
||||
printWidth: 80
|
||||
trailingComma: 'none'
|
||||
arrowParens: 'avoid'
|
||||
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": {}
|
||||
}
|
||||
42
simple-mind-map/full.js
Normal file
@@ -0,0 +1,42 @@
|
||||
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 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 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(ExportPDF)
|
||||
.usePlugin(Export)
|
||||
.usePlugin(Select)
|
||||
.usePlugin(AssociativeLine)
|
||||
.usePlugin(RichText)
|
||||
.usePlugin(TouchEvent)
|
||||
.usePlugin(NodeImgAdjust)
|
||||
.usePlugin(Search)
|
||||
|
||||
export default MindMap
|
||||
@@ -1,427 +1,424 @@
|
||||
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 {
|
||||
layoutValueList
|
||||
} from './src/utils/constant'
|
||||
import {
|
||||
SVG
|
||||
} from '@svgdotjs/svg.js'
|
||||
import xmind from './src/parse/xmind'
|
||||
import { simpleDeepClone } from './src/utils';
|
||||
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 } from './src/constants/constant'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import { simpleDeepClone, getType } from './src/utils'
|
||||
import defaultTheme, { checkIsNodeSizeIndependenceConfig } from './src/themes/default'
|
||||
import { defaultOpt } from './src/constants/defaultOptions'
|
||||
|
||||
// 默认选项配置
|
||||
const defaultOpt = {
|
||||
// 是否只读
|
||||
readonly: false,
|
||||
// 布局
|
||||
layout: 'logicalStructure',
|
||||
// 主题
|
||||
theme: 'default', // 内置主题:default(默认主题)
|
||||
// 主题配置,会和所选择的主题进行合并
|
||||
themeConfig: {},
|
||||
// 放大缩小的增量比例
|
||||
scaleRatio: 0.1,
|
||||
// 最多显示几个标签
|
||||
maxTag: 5,
|
||||
// 导出图片时的内边距
|
||||
exportPadding: 20,
|
||||
// 展开收缩按钮尺寸
|
||||
expandBtnSize: 20,
|
||||
// 节点里图片和文字的间距
|
||||
imgTextMargin: 5,
|
||||
// 节点里各种文字信息的间距,如图标和文字的间距
|
||||
textContentMargin: 2,
|
||||
// 多选节点时鼠标移动到边缘时的画布移动偏移量
|
||||
selectTranslateStep: 3,
|
||||
// 多选节点时鼠标移动距边缘多少距离时开始偏移
|
||||
selectTranslateLimit: 20,
|
||||
// 自定义节点备注内容显示
|
||||
customNoteContentShow: null
|
||||
/*
|
||||
{
|
||||
show(){},
|
||||
hide(){}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-06 11:18:47
|
||||
* @Desc: 思维导图
|
||||
*/
|
||||
// 思维导图
|
||||
class MindMap {
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-06 11:19:01
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(opt = {}) {
|
||||
// 合并选项
|
||||
this.opt = this.handleOpt(merge(defaultOpt, opt))
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
// 合并选项
|
||||
this.opt = this.handleOpt(merge(defaultOpt, opt))
|
||||
|
||||
// 容器元素
|
||||
this.el = this.opt.el
|
||||
this.elRect = this.el.getBoundingClientRect()
|
||||
// 容器元素
|
||||
this.el = this.opt.el
|
||||
this.elRect = this.el.getBoundingClientRect()
|
||||
|
||||
// 画布宽高
|
||||
this.width = this.elRect.width
|
||||
this.height = this.elRect.height
|
||||
// 画布宽高
|
||||
this.width = this.elRect.width
|
||||
this.height = this.elRect.height
|
||||
|
||||
// 画布
|
||||
this.svg = SVG().addTo(this.el).size(this.width, this.height)
|
||||
this.draw = this.svg.group()
|
||||
// 画布
|
||||
this.svg = SVG().addTo(this.el).size(this.width, this.height)
|
||||
this.draw = this.svg.group()
|
||||
|
||||
// 节点id
|
||||
this.uid = 0
|
||||
// 初始化主题
|
||||
this.initTheme()
|
||||
|
||||
// 初始化主题
|
||||
this.initTheme()
|
||||
// 初始化缓存数据
|
||||
this.initCache()
|
||||
|
||||
// 事件类
|
||||
this.event = new Event({
|
||||
mindMap: this
|
||||
})
|
||||
// 事件类
|
||||
this.event = new Event({
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 按键类
|
||||
this.keyCommand = new KeyCommand({
|
||||
mindMap: this
|
||||
})
|
||||
// 按键类
|
||||
this.keyCommand = new KeyCommand({
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 命令类
|
||||
this.command = new Command({
|
||||
mindMap: this
|
||||
})
|
||||
// 命令类
|
||||
this.command = new Command({
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 渲染类
|
||||
this.renderer = new Render({
|
||||
mindMap: this
|
||||
})
|
||||
// 渲染类
|
||||
this.renderer = new Render({
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 视图操作类
|
||||
this.view = new View({
|
||||
mindMap: this,
|
||||
draw: this.draw
|
||||
})
|
||||
// 视图操作类
|
||||
this.view = new View({
|
||||
mindMap: this,
|
||||
draw: this.draw
|
||||
})
|
||||
|
||||
// 导出类
|
||||
this.doExport = new Export({
|
||||
mindMap: this
|
||||
})
|
||||
// 批量执行类
|
||||
this.batchExecution = new BatchExecution()
|
||||
|
||||
// 选择类
|
||||
this.select = new Select({
|
||||
mindMap: this
|
||||
})
|
||||
// 注册插件
|
||||
MindMap.pluginList.forEach((plugin) => {
|
||||
this.initPlugin(plugin)
|
||||
})
|
||||
|
||||
// 拖动类
|
||||
this.drag = new Drag({
|
||||
mindMap: this
|
||||
})
|
||||
// 初始渲染
|
||||
this.render()
|
||||
setTimeout(() => {
|
||||
this.command.addHistory()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 批量执行类
|
||||
this.batchExecution = new BatchExecution()
|
||||
|
||||
// 初始渲染
|
||||
this.reRender()
|
||||
setTimeout(() => {
|
||||
this.command.addHistory()
|
||||
}, 0)
|
||||
// 配置参数处理
|
||||
handleOpt(opt) {
|
||||
// 深拷贝一份节点数据
|
||||
opt.data = simpleDeepClone(opt.data || {})
|
||||
// 检查布局配置
|
||||
if (!layoutValueList.includes(opt.layout)) {
|
||||
opt.layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
|
||||
}
|
||||
// 检查主题配置
|
||||
opt.theme = opt.theme && theme[opt.theme] ? opt.theme : 'default'
|
||||
return opt
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:15:22
|
||||
* @Desc: 配置参数处理
|
||||
*/
|
||||
handleOpt(opt) {
|
||||
// 检查布局配置
|
||||
if (!layoutValueList.includes(opt.layout)) {
|
||||
opt.layout = 'logicalStructure'
|
||||
// 渲染,部分渲染
|
||||
render(callback, source = '') {
|
||||
this.batchExecution.push('render', () => {
|
||||
this.initTheme()
|
||||
this.renderer.reRender = false
|
||||
this.renderer.render(callback, source)
|
||||
})
|
||||
}
|
||||
|
||||
// 重新渲染
|
||||
reRender(callback, source = '') {
|
||||
this.batchExecution.push('render', () => {
|
||||
this.draw.clear()
|
||||
this.initTheme()
|
||||
this.renderer.reRender = true
|
||||
this.renderer.render(callback, source)
|
||||
})
|
||||
}
|
||||
|
||||
// 容器尺寸变化,调整尺寸
|
||||
resize() {
|
||||
this.elRect = this.el.getBoundingClientRect()
|
||||
this.width = this.elRect.width
|
||||
this.height = this.elRect.height
|
||||
this.svg.size(this.width, this.height)
|
||||
}
|
||||
|
||||
// 监听事件
|
||||
on(event, fn) {
|
||||
this.event.on(event, fn)
|
||||
}
|
||||
|
||||
// 触发事件
|
||||
emit(event, ...args) {
|
||||
this.event.emit(event, ...args)
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
off(event, fn) {
|
||||
this.event.off(event, fn)
|
||||
}
|
||||
|
||||
// 初始化缓存数据
|
||||
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)
|
||||
// 设置背景样式
|
||||
Style.setBackgroundStyle(this.el, this.themeConfig)
|
||||
}
|
||||
|
||||
// 设置主题
|
||||
setTheme(theme) {
|
||||
this.renderer.clearAllActive()
|
||||
this.opt.theme = theme
|
||||
this.render(null, CONSTANTS.CHANGE_THEME)
|
||||
}
|
||||
|
||||
// 获取当前主题
|
||||
getTheme() {
|
||||
return this.opt.theme
|
||||
}
|
||||
|
||||
// 设置主题配置
|
||||
setThemeConfig(config) {
|
||||
this.opt.themeConfig = config
|
||||
// 检查改变的是否是节点大小无关的主题属性
|
||||
let res = checkIsNodeSizeIndependenceConfig(config)
|
||||
this.render(null, res ? '' : CONSTANTS.CHANGE_THEME)
|
||||
}
|
||||
|
||||
// 获取自定义主题配置
|
||||
getCustomThemeConfig() {
|
||||
return this.opt.themeConfig
|
||||
}
|
||||
|
||||
// 获取某个主题配置值
|
||||
getThemeConfig(prop) {
|
||||
return prop === undefined ? this.themeConfig : this.themeConfig[prop]
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
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
|
||||
}
|
||||
|
||||
// 设置布局结构
|
||||
setLayout(layout) {
|
||||
// 检查布局配置
|
||||
if (!layoutValueList.includes(layout)) {
|
||||
layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
|
||||
}
|
||||
this.opt.layout = layout
|
||||
this.view.reset()
|
||||
this.renderer.setLayout()
|
||||
this.render(null, CONSTANTS.CHANGE_LAYOUT)
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
execCommand(...args) {
|
||||
this.command.exec(...args)
|
||||
}
|
||||
|
||||
// 动态设置思维导图数据,纯节点数据
|
||||
setData(data) {
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.command.clearHistory()
|
||||
this.command.addHistory()
|
||||
if (this.richText) {
|
||||
this.renderer.renderTree = this.richText.handleSetData(data)
|
||||
} else {
|
||||
this.renderer.renderTree = data
|
||||
}
|
||||
this.reRender(() => {}, CONSTANTS.SET_DATA)
|
||||
}
|
||||
|
||||
// 动态设置思维导图数据,包括节点数据、布局、主题、视图
|
||||
setFullData(data) {
|
||||
if (data.root) {
|
||||
this.setData(data.root)
|
||||
}
|
||||
if (data.layout) {
|
||||
this.setLayout(data.layout)
|
||||
}
|
||||
if (data.theme) {
|
||||
if (data.theme.template) {
|
||||
this.setTheme(data.theme.template)
|
||||
}
|
||||
if (data.theme.config) {
|
||||
this.setThemeConfig(data.theme.config)
|
||||
}
|
||||
}
|
||||
if (data.view) {
|
||||
this.view.setTransformData(data.view)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取思维导图数据,节点树、主题、布局等
|
||||
getData(withConfig) {
|
||||
let nodeData = this.command.getCopyData()
|
||||
let data = {}
|
||||
if (withConfig) {
|
||||
data = {
|
||||
layout: this.getLayout(),
|
||||
root: nodeData,
|
||||
theme: {
|
||||
template: this.getTheme(),
|
||||
config: this.getCustomThemeConfig()
|
||||
},
|
||||
view: this.view.getTransformData()
|
||||
}
|
||||
} else {
|
||||
data = nodeData
|
||||
}
|
||||
return simpleDeepClone(data)
|
||||
}
|
||||
|
||||
// 导出
|
||||
async export(...args) {
|
||||
let result = await this.doExport.export(...args)
|
||||
return result
|
||||
}
|
||||
|
||||
// 转换位置
|
||||
toPos(x, y) {
|
||||
return {
|
||||
x: x - this.elRect.left,
|
||||
y: y - this.elRect.top
|
||||
}
|
||||
}
|
||||
|
||||
// 设置只读模式、编辑模式
|
||||
setMode(mode) {
|
||||
if (![CONSTANTS.MODE.READONLY, CONSTANTS.MODE.EDIT].includes(mode)) {
|
||||
return
|
||||
}
|
||||
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
|
||||
rect.height += paddingY
|
||||
draw.translate(paddingX / 2, paddingY / 2)
|
||||
// 将svg设置为实际内容的宽高
|
||||
svg.size(rect.width, rect.height)
|
||||
// 把实际内容变换
|
||||
draw.translate(-rect.x + elRect.left, -rect.y + elRect.top)
|
||||
// 克隆一份数据
|
||||
let clone = svg.clone()
|
||||
// 如果实际图形宽高超出了屏幕宽高,且存在水印的话需要重新绘制水印,否则会出现超出部分没有水印的问题
|
||||
if ((rect.width > origWidth || rect.height > origHeight) && this.watermark && this.watermark.hasWatermark()) {
|
||||
this.width = rect.width
|
||||
this.height = rect.height
|
||||
this.watermark.draw()
|
||||
clone = svg.clone()
|
||||
this.width = origWidth
|
||||
this.height = origHeight
|
||||
this.watermark.draw()
|
||||
}
|
||||
// 恢复原先的大小和变换信息
|
||||
svg.size(origWidth, origHeight)
|
||||
draw.transform(origTransform)
|
||||
|
||||
return {
|
||||
svg: clone, // 思维导图图形的整体svg元素,包括:svg(画布容器)、g(实际的思维导图组)
|
||||
svgHTML: clone.svg(), // svg字符串
|
||||
rect: {
|
||||
...rect, // 思维导图图形未缩放时的位置尺寸等信息
|
||||
ratio: rect.width / rect.height // 思维导图图形的宽高比
|
||||
},
|
||||
origWidth, // 画布宽度
|
||||
origHeight, // 画布高度
|
||||
scaleX: origTransform.scaleX, // 思维导图图形的水平缩放值
|
||||
scaleY: origTransform.scaleY // 思维导图图形的垂直缩放值
|
||||
}
|
||||
}
|
||||
|
||||
// 添加插件
|
||||
addPlugin(plugin, opt) {
|
||||
let index = MindMap.hasPlugin(plugin)
|
||||
if (index === -1) {
|
||||
MindMap.usePlugin(plugin, opt)
|
||||
this.initPlugin(plugin)
|
||||
}
|
||||
}
|
||||
|
||||
// 移除插件
|
||||
removePlugin(plugin) {
|
||||
let index = MindMap.hasPlugin(plugin)
|
||||
if (index !== -1) {
|
||||
MindMap.pluginList.splice(index, 1)
|
||||
if (this[plugin.instanceName]) {
|
||||
if (this[plugin.instanceName].beforePluginRemove) {
|
||||
this[plugin.instanceName].beforePluginRemove()
|
||||
}
|
||||
// 检查主题配置
|
||||
opt.theme = opt.theme && theme[opt.theme] ? opt.theme : 'default'
|
||||
return opt
|
||||
delete this[plugin.instanceName]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-06 18:47:29
|
||||
* @Desc: 渲染,部分渲染
|
||||
*/
|
||||
render() {
|
||||
this.batchExecution.push('render', () => {
|
||||
this.initTheme()
|
||||
this.renderer.reRender = false
|
||||
this.renderer.render()
|
||||
})
|
||||
}
|
||||
// 实例化插件
|
||||
initPlugin(plugin) {
|
||||
this[plugin.instanceName] = new plugin({
|
||||
mindMap: this,
|
||||
pluginOpt: plugin.pluginOpt
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-08 22:05:11
|
||||
* @Desc: 重新渲染
|
||||
*/
|
||||
reRender() {
|
||||
this.batchExecution.push('render', () => {
|
||||
this.draw.clear()
|
||||
this.initTheme()
|
||||
this.renderer.reRender = true
|
||||
this.renderer.render()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 21:16:52
|
||||
* @Desc: 容器尺寸变化,调整尺寸
|
||||
*/
|
||||
resize() {
|
||||
this.elRect = this.el.getBoundingClientRect()
|
||||
this.width = this.elRect.width
|
||||
this.height = this.elRect.height
|
||||
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: 设置主题
|
||||
*/
|
||||
initTheme() {
|
||||
// 合并主题配置
|
||||
this.themeConfig = merge(theme[this.opt.theme], this.opt.themeConfig)
|
||||
// 设置背景样式
|
||||
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()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-25 23:52:37
|
||||
* @Desc: 获取当前主题
|
||||
*/
|
||||
getTheme() {
|
||||
return this.opt.theme
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 13:50:17
|
||||
* @Desc: 设置主题配置
|
||||
*/
|
||||
setThemeConfig(config) {
|
||||
this.opt.themeConfig = config
|
||||
this.reRender()
|
||||
}
|
||||
|
||||
/**
|
||||
* @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: 获取当前布局结构
|
||||
*/
|
||||
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'
|
||||
}
|
||||
this.opt.layout = layout
|
||||
this.renderer.setLayout()
|
||||
this.render()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 13:01:00
|
||||
* @Desc: 执行命令
|
||||
*/
|
||||
execCommand(...args) {
|
||||
this.command.exec(...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-03 22:58:12
|
||||
* @Desc: 动态设置思维导图数据,纯节点数据
|
||||
*/
|
||||
setData(data) {
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.command.clearHistory()
|
||||
this.renderer.renderTree = data
|
||||
this.reRender()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-09-21 16:39:13
|
||||
* @Desc: 动态设置思维导图数据,包括节点数据、布局、主题、视图
|
||||
*/
|
||||
setFullData(data) {
|
||||
if (data.root) {
|
||||
this.setData(data.root)
|
||||
}
|
||||
if (data.layout) {
|
||||
this.setLayout(data.layout)
|
||||
}
|
||||
if (data.theme) {
|
||||
if (data.theme.template) {
|
||||
this.setTheme(data.theme.template)
|
||||
}
|
||||
if (data.theme.config) {
|
||||
this.setThemeConfig(data.theme.config)
|
||||
}
|
||||
}
|
||||
if (data.view) {
|
||||
this.view.setTransformData(data.view)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-24 14:42:07
|
||||
* @Desc: 获取思维导图数据,节点树、主题、布局等
|
||||
*/
|
||||
getData(withConfig) {
|
||||
let nodeData = this.command.getCopyData()
|
||||
let data = {}
|
||||
if (withConfig) {
|
||||
data = {
|
||||
layout: this.getLayout(),
|
||||
root: nodeData,
|
||||
theme: {
|
||||
template: this.getTheme(),
|
||||
config: this.getCustomThemeConfig()
|
||||
},
|
||||
view: this.view.getTransformData()
|
||||
}
|
||||
} else {
|
||||
data = nodeData
|
||||
}
|
||||
return simpleDeepClone(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:06:38
|
||||
* @Desc: 导出
|
||||
*/
|
||||
async export(...args) {
|
||||
let result = await this.doExport.export(...args)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 09:20:03
|
||||
* @Desc: 转换位置
|
||||
*/
|
||||
toPos(x, y) {
|
||||
return {
|
||||
x: x - this.elRect.left,
|
||||
y: y - this.elRect.top
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-06-08 14:12:38
|
||||
* @Desc: 设置只读模式、编辑模式
|
||||
*/
|
||||
setMode(mode) {
|
||||
if (!['readonly', 'edit'].includes(mode)) {
|
||||
return
|
||||
}
|
||||
this.opt.readonly = mode === 'readonly'
|
||||
if (this.opt.readonly) {
|
||||
// 取消当前激活的元素
|
||||
this.renderer.clearAllActive()
|
||||
}
|
||||
this.emit('mode_change', mode)
|
||||
}
|
||||
// 销毁
|
||||
destroy() {
|
||||
// 移除插件
|
||||
[...MindMap.pluginList].forEach((plugin) => {
|
||||
this[plugin.instanceName] = null
|
||||
})
|
||||
// 解绑事件
|
||||
this.event.unbind()
|
||||
// 移除画布节点
|
||||
this.svg.remove()
|
||||
// 去除给容器元素设置的背景样式
|
||||
Style.removeBackgroundStyle(this.el)
|
||||
this.el = null
|
||||
}
|
||||
}
|
||||
|
||||
MindMap.xmind = xmind
|
||||
// 插件列表
|
||||
MindMap.pluginList = []
|
||||
MindMap.usePlugin = (plugin, opt = {}) => {
|
||||
plugin.pluginOpt = opt
|
||||
MindMap.pluginList.push(plugin)
|
||||
return MindMap
|
||||
}
|
||||
MindMap.hasPlugin = (plugin) => {
|
||||
return MindMap.pluginList.findIndex((item) => {
|
||||
return item === plugin
|
||||
})
|
||||
}
|
||||
|
||||
export default MindMap
|
||||
// 定义新主题
|
||||
MindMap.defineTheme = (name, config = {}) => {
|
||||
if (theme[name]) {
|
||||
return new Error('该主题名称已存在')
|
||||
}
|
||||
theme[name] = merge(defaultTheme, config)
|
||||
}
|
||||
|
||||
export default MindMap
|
||||
|
||||
3859
simple-mind-map/package-lock.json
generated
Normal file
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.2.9",
|
||||
"version": "0.6.11-fix.1",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
@@ -17,16 +17,22 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/wanglin2/mind-map"
|
||||
},
|
||||
"scripts": {},
|
||||
"scripts": {
|
||||
"lint": "eslint src/",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"module": "index.js",
|
||||
"main": "./dist/simpleMindMap.umd.min.js",
|
||||
"__main": "./dist/simpleMindMap.umd.min.js",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.0.16",
|
||||
"canvg": "^3.0.7",
|
||||
"deepmerge": "^1.5.2",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jspdf": "^2.5.1",
|
||||
"jszip": "^3.10.1",
|
||||
"mdast-util-from-markdown": "^1.3.0",
|
||||
"quill": "^1.3.6",
|
||||
"uuid": "^9.0.0",
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"keywords": [
|
||||
@@ -35,5 +41,9 @@
|
||||
"mind-map",
|
||||
"mindMap",
|
||||
"MindMap"
|
||||
]
|
||||
],
|
||||
"devDependencies": {
|
||||
"eslint": "^8.25.0",
|
||||
"prettier": "^2.7.1"
|
||||
}
|
||||
}
|
||||
|
||||
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 (cb, ctx) {
|
||||
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,152 +0,0 @@
|
||||
import { copyRenderTree, simpleDeepClone } 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
|
||||
this.commands = {}
|
||||
this.history = []
|
||||
this.activeHistoryIndex = 0
|
||||
// 注册快捷键
|
||||
this.registerShortcutKeys()
|
||||
}
|
||||
|
||||
/**
|
||||
* @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')
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Control+y', () => {
|
||||
this.mindMap.execCommand('FORWARD')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @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') {
|
||||
return;
|
||||
}
|
||||
this.addHistory()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 13:13:01
|
||||
* @Desc: 添加命令
|
||||
*/
|
||||
add(name, fn) {
|
||||
if (this.commands[name]) {
|
||||
this.commands[name].push(fn)
|
||||
} else {
|
||||
this.commands[name] = [fn]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-15 23:02:41
|
||||
* @Desc: 移除命令
|
||||
*/
|
||||
remove(name, fn) {
|
||||
if (!this.commands[name]) {
|
||||
return
|
||||
}
|
||||
if (!fn) {
|
||||
this.commands[name] = []
|
||||
delete this.commands[name]
|
||||
} else {
|
||||
let index = this.commands[name].find((item) => {
|
||||
return item === fn;
|
||||
})
|
||||
if (index !== -1) {
|
||||
this.commands[name].splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 14:35:43
|
||||
* @Desc: 添加回退数据
|
||||
*/
|
||||
addHistory() {
|
||||
let data = this.getCopyData()
|
||||
this.history.push(simpleDeepClone(data))
|
||||
this.activeHistoryIndex = this.history.length - 1
|
||||
this.mindMap.emit('data_change', data)
|
||||
this.mindMap.emit('back_forward', this.activeHistoryIndex, this.history.length)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 22:34:53
|
||||
* @Desc: 回退
|
||||
*/
|
||||
back(step = 1) {
|
||||
if (this.activeHistoryIndex - step >= 0) {
|
||||
this.activeHistoryIndex -= step
|
||||
this.mindMap.emit('back_forward', this.activeHistoryIndex, this.history.length)
|
||||
return simpleDeepClone(this.history[this.activeHistoryIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-07-12 10:45:31
|
||||
* @Desc: 前进
|
||||
*/
|
||||
forward(step = 1) {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 15:02:58
|
||||
* @Desc: 获取渲染树数据副本
|
||||
*/
|
||||
getCopyData() {
|
||||
return copyRenderTree({}, this.mindMap.renderer.renderTree)
|
||||
}
|
||||
}
|
||||
|
||||
export default Command
|
||||
@@ -1,317 +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,268 +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() {
|
||||
const svg = this.mindMap.svg
|
||||
const draw = this.mindMap.draw
|
||||
// 保存原始信息
|
||||
const origWidth = svg.width()
|
||||
const origHeight = svg.height()
|
||||
const origTransform = draw.transform()
|
||||
const elRect = this.mindMap.el.getBoundingClientRect()
|
||||
// 去除放大缩小的变换效果
|
||||
draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY)
|
||||
// 获取变换后的位置尺寸信息,其实是getBoundingClientRect方法的包装方法
|
||||
const rect = draw.rbox()
|
||||
// 将svg设置为实际内容的宽高
|
||||
svg.size(rect.width, rect.height)
|
||||
// 把实际内容变换
|
||||
draw.translate(-rect.x + elRect.left, -rect.y + elRect.top)
|
||||
// 克隆一份数据
|
||||
const clone = svg.clone()
|
||||
// 恢复原先的大小和变换信息
|
||||
svg.size(origWidth, origHeight)
|
||||
draw.transform(origTransform)
|
||||
// 把图片的url转换成data:url类型,否则导出会丢失图片
|
||||
let imageList = clone.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: clone,
|
||||
str: clone.svg()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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, rejct) => {
|
||||
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) => {
|
||||
rejct(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, rejct) => {
|
||||
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,200 +0,0 @@
|
||||
import { keyMap } from './utils/keyMap';
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 15:20:46
|
||||
* @Desc: 快捷按键、命令处理类
|
||||
*/
|
||||
export default class KeyCommand {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 15:21:32
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(opt) {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
this.shortcutMap = {
|
||||
//Enter: [fn]
|
||||
}
|
||||
this.shortcutMapCache = {}
|
||||
this.isPause = 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() {
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (this.isPause) {
|
||||
return
|
||||
}
|
||||
Object.keys(this.shortcutMap).forEach((key) => {
|
||||
if (this.checkKey(e, key)) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
this.shortcutMap[key].forEach((fn) => {
|
||||
fn()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 19:24:53
|
||||
* @Desc: 检查键值是否符合
|
||||
*/
|
||||
checkKey(e, key) {
|
||||
let o = this.getOriginEventCodeArr(e)
|
||||
let k = this.getKeyCodeArr(key)
|
||||
if (o.length !== k.length) {
|
||||
return false
|
||||
}
|
||||
for (let i = 0; i < o.length; i++) {
|
||||
let index = k.findIndex((item) => {
|
||||
return item === o[i];
|
||||
})
|
||||
if (index === -1) {
|
||||
return false
|
||||
} else {
|
||||
k.splice(index, 1)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 19:15:19
|
||||
* @Desc: 获取事件对象里的键值数组
|
||||
*/
|
||||
getOriginEventCodeArr(e) {
|
||||
let arr = []
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
arr.push(keyMap['Control'])
|
||||
}
|
||||
if (e.altKey) {
|
||||
arr.push(keyMap['Alt'])
|
||||
}
|
||||
if (e.shiftKey) {
|
||||
arr.push(keyMap['Shift'])
|
||||
}
|
||||
if (!arr.includes(e.keyCode)) {
|
||||
arr.push(e.keyCode)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 19:40:11
|
||||
* @Desc: 获取快捷键对应的键值数组
|
||||
*/
|
||||
getKeyCodeArr(key) {
|
||||
let keyArr = key.split(/\s*\+\s*/)
|
||||
let arr = []
|
||||
keyArr.forEach((item) => {
|
||||
arr.push(keyMap[item])
|
||||
})
|
||||
return arr
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 15:23:00
|
||||
* @Desc: 添加快捷键命令
|
||||
* Enter
|
||||
* Tab | Insert
|
||||
* Shift + a
|
||||
*/
|
||||
addShortcut(key, fn) {
|
||||
key.split(/\s*\|\s*/).forEach((item) => {
|
||||
if (this.shortcutMap[item]) {
|
||||
this.shortcutMap[item].push(fn)
|
||||
} else {
|
||||
this.shortcutMap[item] = [fn]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]) {
|
||||
if (fn) {
|
||||
let index = this.shortcutMap[item].findIndex((f) => {
|
||||
return f === fn
|
||||
})
|
||||
if (index !== -1) {
|
||||
this.shortcutMap[item].splice(index, 1)
|
||||
}
|
||||
} else {
|
||||
this.shortcutMap[item] = []
|
||||
delete this.shortcutMap[item]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-14 08:49:58
|
||||
* @Desc: 获取指定快捷键的处理函数
|
||||
*/
|
||||
getShortcutFn(key) {
|
||||
let res = []
|
||||
key.split(/\s*\|\s*/).forEach((item) => {
|
||||
res = this.shortcutMap[item] || []
|
||||
})
|
||||
return res
|
||||
}
|
||||
}
|
||||
@@ -1,191 +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', (e) => {
|
||||
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,265 +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,210 +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,146 +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/img).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,193 +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
|
||||
// 清除激活节点
|
||||
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: 2021-07-13 15:48:52
|
||||
* @Desc: 平移y方向
|
||||
*/
|
||||
translateY(step) {
|
||||
this.y += step
|
||||
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
|
||||
332
simple-mind-map/src/constants/constant.js
Normal file
@@ -0,0 +1,332 @@
|
||||
// 标签颜色列表
|
||||
export const tagColorList = [
|
||||
{
|
||||
color: 'rgb(77, 65, 0)',
|
||||
background: 'rgb(255, 244, 179)'
|
||||
},
|
||||
{
|
||||
color: 'rgb(0, 50, 77)',
|
||||
background: 'rgb(179, 229, 255)'
|
||||
},
|
||||
{
|
||||
color: 'rgb(77, 0, 73)',
|
||||
background: 'rgb(255, 179, 251)'
|
||||
},
|
||||
{
|
||||
color: 'rgb(57, 77, 0)',
|
||||
background: 'rgb(236, 255, 179)'
|
||||
},
|
||||
{
|
||||
color: 'rgb(0, 77, 47)',
|
||||
background: 'rgb(179, 255, 226)'
|
||||
}
|
||||
]
|
||||
|
||||
// 主题列表
|
||||
export const themeList = [
|
||||
{
|
||||
name: '默认',
|
||||
value: 'default',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '暗色2',
|
||||
value: 'dark2',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '天清绿',
|
||||
value: 'skyGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑图经典2',
|
||||
value: 'classic2',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑图经典3',
|
||||
value: 'classic3',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '经典绿',
|
||||
value: 'classicGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '经典蓝',
|
||||
value: 'classicBlue',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '天空蓝',
|
||||
value: 'blueSky',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑残粉',
|
||||
value: 'brainImpairedPink',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '暗色',
|
||||
value: 'dark',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '泥土黄',
|
||||
value: 'earthYellow',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '清新绿',
|
||||
value: 'freshGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '清新红',
|
||||
value: 'freshRed',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '浪漫紫',
|
||||
value: 'romanticPurple',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '粉红葡萄',
|
||||
value: 'pinkGrape',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '薄荷',
|
||||
value: 'mint',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '金色vip',
|
||||
value: 'gold',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '活力橙',
|
||||
value: 'vitalityOrange',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '绿叶',
|
||||
value: 'greenLeaf',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑图经典',
|
||||
value: 'classic',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '脑图经典4',
|
||||
value: 'classic4',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '小黄人',
|
||||
value: 'minions',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '简约黑',
|
||||
value: 'simpleBlack',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '课程绿',
|
||||
value: 'courseGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '咖啡',
|
||||
value: 'coffee',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '红色精神',
|
||||
value: 'redSpirit',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '黑色幽默',
|
||||
value: 'blackHumour',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '深夜办公室',
|
||||
value: 'lateNightOffice',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '黑金',
|
||||
value: 'blackGold',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '牛油果',
|
||||
value: 'avocado',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '秋天',
|
||||
value: 'autumn',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '橙汁',
|
||||
value: 'orangeJuice',
|
||||
dark: true
|
||||
}
|
||||
]
|
||||
|
||||
// 常量
|
||||
export const CONSTANTS = {
|
||||
CHANGE_THEME: 'changeTheme',
|
||||
CHANGE_LAYOUT: 'changeLayout',
|
||||
SET_DATA: 'setData',
|
||||
TRANSFORM_TO_NORMAL_NODE: 'transformAllNodesToNormalNode',
|
||||
MODE: {
|
||||
READONLY: 'readonly',
|
||||
EDIT: 'edit'
|
||||
},
|
||||
LAYOUT: {
|
||||
LOGICAL_STRUCTURE: 'logicalStructure',
|
||||
MIND_MAP: 'mindMap',
|
||||
ORGANIZATION_STRUCTURE: 'organizationStructure',
|
||||
CATALOG_ORGANIZATION: 'catalogOrganization',
|
||||
TIMELINE: 'timeline',
|
||||
TIMELINE2: 'timeline2',
|
||||
FISHBONE: 'fishbone',
|
||||
VERTICAL_TIMELINE: 'verticalTimeline'
|
||||
},
|
||||
DIR: {
|
||||
UP: 'up',
|
||||
LEFT: 'left',
|
||||
DOWN: 'down',
|
||||
RIGHT: 'right'
|
||||
},
|
||||
KEY_DIR: {
|
||||
LEFT: 'Left',
|
||||
UP: 'Up',
|
||||
RIGHT: 'Right',
|
||||
DOWN: 'Down'
|
||||
},
|
||||
SHAPE: {
|
||||
RECTANGLE: 'rectangle',
|
||||
DIAMOND: 'diamond',
|
||||
PARALLELOGRAM: 'parallelogram',
|
||||
ROUNDED_RECTANGLE: 'roundedRectangle',
|
||||
OCTAGONAL_RECTANGLE: 'octagonalRectangle',
|
||||
OUTER_TRIANGULAR_RECTANGLE: 'outerTriangularRectangle',
|
||||
INNER_TRIANGULAR_RECTANGLE: 'innerTriangularRectangle',
|
||||
ELLIPSE: 'ellipse',
|
||||
CIRCLE: 'circle'
|
||||
},
|
||||
MOUSE_WHEEL_ACTION: {
|
||||
ZOOM: 'zoom',
|
||||
MOVE: 'move'
|
||||
},
|
||||
INIT_ROOT_NODE_POSITION: {
|
||||
LEFT: 'left',
|
||||
TOP: 'top',
|
||||
RIGHT: 'right',
|
||||
BOTTOM: 'bottom',
|
||||
CENTER: 'center'
|
||||
},
|
||||
LAYOUT_GROW_DIR: {
|
||||
LEFT: 'left',
|
||||
TOP: 'top',
|
||||
RIGHT: 'right',
|
||||
BOTTOM: 'bottom'
|
||||
},
|
||||
PASTE_TYPE: {
|
||||
CLIP_BOARD: 'clipBoard',
|
||||
CANVAS: 'canvas'
|
||||
}
|
||||
}
|
||||
|
||||
export const initRootNodePositionMap = {
|
||||
[CONSTANTS.INIT_ROOT_NODE_POSITION.LEFT]: 0,
|
||||
[CONSTANTS.INIT_ROOT_NODE_POSITION.TOP]: 0,
|
||||
[CONSTANTS.INIT_ROOT_NODE_POSITION.RIGHT]: 1,
|
||||
[CONSTANTS.INIT_ROOT_NODE_POSITION.BOTTOM]: 1,
|
||||
[CONSTANTS.INIT_ROOT_NODE_POSITION.CENTER]: 0.5,
|
||||
}
|
||||
|
||||
// 布局结构列表
|
||||
export const layoutList = [
|
||||
{
|
||||
name: '逻辑结构图',
|
||||
value: CONSTANTS.LAYOUT.LOGICAL_STRUCTURE,
|
||||
},
|
||||
{
|
||||
name: '思维导图',
|
||||
value: CONSTANTS.LAYOUT.MIND_MAP,
|
||||
},
|
||||
{
|
||||
name: '组织结构图',
|
||||
value: CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE,
|
||||
},
|
||||
{
|
||||
name: '目录组织图',
|
||||
value: CONSTANTS.LAYOUT.CATALOG_ORGANIZATION,
|
||||
},
|
||||
{
|
||||
name: '时间轴',
|
||||
value: CONSTANTS.LAYOUT.TIMELINE,
|
||||
},
|
||||
{
|
||||
name: '时间轴2',
|
||||
value: CONSTANTS.LAYOUT.TIMELINE2,
|
||||
},
|
||||
{
|
||||
name: '竖向时间轴',
|
||||
value: CONSTANTS.LAYOUT.VERTICAL_TIMELINE,
|
||||
},
|
||||
{
|
||||
name: '鱼骨图',
|
||||
value: CONSTANTS.LAYOUT.FISHBONE,
|
||||
}
|
||||
]
|
||||
export const layoutValueList = [
|
||||
CONSTANTS.LAYOUT.LOGICAL_STRUCTURE,
|
||||
CONSTANTS.LAYOUT.MIND_MAP,
|
||||
CONSTANTS.LAYOUT.CATALOG_ORGANIZATION,
|
||||
CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE,
|
||||
CONSTANTS.LAYOUT.TIMELINE,
|
||||
CONSTANTS.LAYOUT.TIMELINE2,
|
||||
CONSTANTS.LAYOUT.VERTICAL_TIMELINE,
|
||||
CONSTANTS.LAYOUT.FISHBONE
|
||||
]
|
||||
|
||||
// 节点数据中非样式的字段
|
||||
export const nodeDataNoStylePropList = [
|
||||
'text',
|
||||
'image',
|
||||
'imageTitle',
|
||||
'imageSize',
|
||||
'icon',
|
||||
'tag',
|
||||
'hyperlink',
|
||||
'hyperlinkTitle',
|
||||
'note',
|
||||
'expand',
|
||||
'isActive',
|
||||
'generalization',
|
||||
'richText',
|
||||
'resetRichText',
|
||||
'uid',
|
||||
'activeStyle'
|
||||
]
|
||||
|
||||
// 数据缓存
|
||||
export const commonCaches = {
|
||||
measureCustomNodeContentSizeEl: null
|
||||
}
|
||||
126
simple-mind-map/src/constants/defaultOptions.js
Normal file
@@ -0,0 +1,126 @@
|
||||
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,
|
||||
// 导出图片时的内边距
|
||||
exportPadding: 20,
|
||||
// 展开收缩按钮尺寸
|
||||
expandBtnSize: 20,
|
||||
// 节点里图片和文字的间距
|
||||
imgTextMargin: 5,
|
||||
// 节点里各种文字信息的间距,如图标和文字的间距
|
||||
textContentMargin: 2,
|
||||
// 多选节点时鼠标移动到边缘时的画布移动偏移量
|
||||
selectTranslateStep: 3,
|
||||
// 多选节点时鼠标移动距边缘多少距离时开始偏移
|
||||
selectTranslateLimit: 20,
|
||||
// 自定义节点备注内容显示
|
||||
customNoteContentShow: null,
|
||||
/*
|
||||
{
|
||||
show(){},
|
||||
hide(){}
|
||||
}
|
||||
*/
|
||||
// 是否开启节点自由拖拽
|
||||
enableFreeDrag: false,
|
||||
// 水印配置
|
||||
watermarkConfig: {
|
||||
text: '',
|
||||
lineSpacing: 100,
|
||||
textSpacing: 100,
|
||||
angle: 30,
|
||||
textStyle: {
|
||||
color: '#999',
|
||||
opacity: 0.5,
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
// 达到该宽度文本自动换行
|
||||
textAutoWrapWidth: 500,
|
||||
// 自定义鼠标滚轮事件处理
|
||||
// 可以传一个函数,回调参数为事件对象
|
||||
customHandleMousewheel: null,
|
||||
// 鼠标滚动的行为,如果customHandleMousewheel传了自定义函数,这个属性不生效
|
||||
mousewheelAction: CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM, // zoom(放大缩小)、move(上下移动)
|
||||
// 当mousewheelAction设为move时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位px
|
||||
mousewheelMoveStep: 100,
|
||||
// 当mousewheelAction设为zoom时,默认向前滚动是缩小,向后滚动是放大,如果该属性设为true,那么会反过来
|
||||
mousewheelZoomActionReverse: false,
|
||||
// 默认插入的二级节点的文字
|
||||
defaultInsertSecondLevelNodeText: '二级节点',
|
||||
// 默认插入的二级以下节点的文字
|
||||
defaultInsertBelowSecondLevelNodeText: '分支主题',
|
||||
// 展开收起按钮的颜色
|
||||
expandBtnStyle: {
|
||||
color: '#808080',
|
||||
fill: '#fff'
|
||||
},
|
||||
// 自定义展开收起按钮的图标
|
||||
expandBtnIcon: {
|
||||
open: '', // svg字符串
|
||||
close: ''
|
||||
},
|
||||
// 是否只有当鼠标在画布内才响应快捷键事件
|
||||
enableShortcutOnlyWhenMouseInSvg: true,
|
||||
// 初始根节点的位置
|
||||
initRootNodePosition: null,
|
||||
// 导出png、svg、pdf时的图形内边距
|
||||
exportPaddingX: 10,
|
||||
exportPaddingY: 10,
|
||||
// 节点文本编辑框的z-index
|
||||
nodeTextEditZIndex: 3000,
|
||||
// 节点备注浮层的z-index
|
||||
nodeNoteTooltipZIndex: 3000,
|
||||
// 是否在点击了画布外的区域时结束节点文本的编辑状态
|
||||
isEndNodeTextEditOnClickOuter: true,
|
||||
// 最大历史记录数
|
||||
maxHistoryCount: 1000,
|
||||
// 是否一直显示节点的展开收起按钮,默认为鼠标移上去和激活时才显示
|
||||
alwaysShowExpandBtn: false,
|
||||
// 扩展节点可插入的图标
|
||||
iconList: [
|
||||
// {
|
||||
// name: '',// 分组名称
|
||||
// type: '',// 分组的值
|
||||
// list: [// 分组下的图标列表
|
||||
// {
|
||||
// name: '',// 图标名称
|
||||
// icon:''// 图标,可以传svg或图片
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
],
|
||||
// 节点最大缓存数量
|
||||
maxNodeCacheCount: 1000,
|
||||
// 关联线默认文字
|
||||
defaultAssociativeLineText: '关联',
|
||||
// 思维导图适应画布大小时的内边距
|
||||
fitPadding: 50,
|
||||
// 是否开启按住ctrl键多选节点功能
|
||||
enableCtrlKeyNodeSelection: true,
|
||||
// 设置为左键多选节点,右键拖动画布
|
||||
useLeftKeySelectionRightKeyDrag: false,
|
||||
// 节点即将进入编辑前的回调方法,如果该方法返回true以外的值,那么将取消编辑,函数可以返回一个值,或一个Promise,回调参数为节点实例
|
||||
beforeTextEdit: null,
|
||||
// 是否开启自定义节点内容
|
||||
isUseCustomNodeContent: false,
|
||||
// 自定义返回节点内容的方法
|
||||
customCreateNodeContent: null
|
||||
}
|
||||
154
simple-mind-map/src/core/command/Command.js
Normal file
@@ -0,0 +1,154 @@
|
||||
import { copyRenderTree, simpleDeepClone, nextTick } from '../../utils'
|
||||
|
||||
// 命令类
|
||||
class Command {
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
this.commands = {}
|
||||
this.history = []
|
||||
this.activeHistoryIndex = 0
|
||||
// 注册快捷键
|
||||
this.registerShortcutKeys()
|
||||
this.addHistory = nextTick(this.addHistory, this)
|
||||
}
|
||||
|
||||
// 清空历史数据
|
||||
clearHistory() {
|
||||
this.history = []
|
||||
this.activeHistoryIndex = 0
|
||||
this.mindMap.emit('back_forward', 0, 0)
|
||||
}
|
||||
|
||||
// 注册快捷键
|
||||
registerShortcutKeys() {
|
||||
this.mindMap.keyCommand.addShortcut('Control+z', () => {
|
||||
this.mindMap.execCommand('BACK')
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Control+y', () => {
|
||||
this.mindMap.execCommand('FORWARD')
|
||||
})
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
exec(name, ...args) {
|
||||
if (this.commands[name]) {
|
||||
this.commands[name].forEach(fn => {
|
||||
fn(...args)
|
||||
})
|
||||
if (['BACK', 'FORWARD', 'SET_NODE_ACTIVE', 'CLEAR_ACTIVE_NODE'].includes(name)) {
|
||||
return
|
||||
}
|
||||
this.addHistory()
|
||||
}
|
||||
}
|
||||
|
||||
// 添加命令
|
||||
add(name, fn) {
|
||||
if (this.commands[name]) {
|
||||
this.commands[name].push(fn)
|
||||
} else {
|
||||
this.commands[name] = [fn]
|
||||
}
|
||||
}
|
||||
|
||||
// 移除命令
|
||||
remove(name, fn) {
|
||||
if (!this.commands[name]) {
|
||||
return
|
||||
}
|
||||
if (!fn) {
|
||||
this.commands[name] = []
|
||||
delete this.commands[name]
|
||||
} else {
|
||||
let index = this.commands[name].find(item => {
|
||||
return item === fn
|
||||
})
|
||||
if (index !== -1) {
|
||||
this.commands[name].splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加回退数据
|
||||
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(
|
||||
'back_forward',
|
||||
this.activeHistoryIndex,
|
||||
this.history.length
|
||||
)
|
||||
}
|
||||
|
||||
// 回退
|
||||
back(step = 1) {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
if (this.activeHistoryIndex - step >= 0) {
|
||||
this.activeHistoryIndex -= step
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 前进
|
||||
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, this.history.length)
|
||||
let data = simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
this.mindMap.emit('data_change', data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
// 获取渲染树数据副本
|
||||
getCopyData() {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
export default Command
|
||||
164
simple-mind-map/src/core/command/KeyCommand.js
Normal file
@@ -0,0 +1,164 @@
|
||||
import { keyMap } from './keyMap'
|
||||
// 快捷按键、命令处理类
|
||||
export default class KeyCommand {
|
||||
// 构造函数
|
||||
constructor(opt) {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
this.shortcutMap = {
|
||||
//Enter: [fn]
|
||||
}
|
||||
this.shortcutMapCache = {}
|
||||
this.isPause = false
|
||||
this.isInSvg = false
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
// 暂停快捷键响应
|
||||
pause() {
|
||||
this.isPause = true
|
||||
}
|
||||
|
||||
// 恢复快捷键响应
|
||||
recovery() {
|
||||
this.isPause = false
|
||||
}
|
||||
|
||||
// 保存当前注册的快捷键数据,然后清空快捷键数据
|
||||
save() {
|
||||
this.shortcutMapCache = this.shortcutMap
|
||||
this.shortcutMap = {}
|
||||
}
|
||||
|
||||
// 恢复保存的快捷键数据,然后清空缓存数据
|
||||
restore() {
|
||||
this.shortcutMap = this.shortcutMapCache
|
||||
this.shortcutMapCache = {}
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
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 || (this.mindMap.opt.enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)) {
|
||||
return
|
||||
}
|
||||
Object.keys(this.shortcutMap).forEach(key => {
|
||||
if (this.checkKey(e, key)) {
|
||||
// 粘贴事件不组织,因为要监听paste事件
|
||||
if (!this.checkKey(e, 'Control+v')) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
this.shortcutMap[key].forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 检查键值是否符合
|
||||
checkKey(e, key) {
|
||||
let o = this.getOriginEventCodeArr(e)
|
||||
let k = this.getKeyCodeArr(key)
|
||||
if (o.length !== k.length) {
|
||||
return false
|
||||
}
|
||||
for (let i = 0; i < o.length; i++) {
|
||||
let index = k.findIndex(item => {
|
||||
return item === o[i]
|
||||
})
|
||||
if (index === -1) {
|
||||
return false
|
||||
} else {
|
||||
k.splice(index, 1)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 获取事件对象里的键值数组
|
||||
getOriginEventCodeArr(e) {
|
||||
let arr = []
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
arr.push(keyMap['Control'])
|
||||
}
|
||||
if (e.altKey) {
|
||||
arr.push(keyMap['Alt'])
|
||||
}
|
||||
if (e.shiftKey) {
|
||||
arr.push(keyMap['Shift'])
|
||||
}
|
||||
if (!arr.includes(e.keyCode)) {
|
||||
arr.push(e.keyCode)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
// 获取快捷键对应的键值数组
|
||||
getKeyCodeArr(key) {
|
||||
let keyArr = key.split(/\s*\+\s*/)
|
||||
let arr = []
|
||||
keyArr.forEach(item => {
|
||||
arr.push(keyMap[item])
|
||||
})
|
||||
return arr
|
||||
}
|
||||
|
||||
// 添加快捷键命令
|
||||
/**
|
||||
* Enter
|
||||
* Tab | Insert
|
||||
* Shift + a
|
||||
*/
|
||||
addShortcut(key, fn) {
|
||||
key.split(/\s*\|\s*/).forEach(item => {
|
||||
if (this.shortcutMap[item]) {
|
||||
this.shortcutMap[item].push(fn)
|
||||
} else {
|
||||
this.shortcutMap[item] = [fn]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 移除快捷键命令
|
||||
removeShortcut(key, fn) {
|
||||
key.split(/\s*\|\s*/).forEach(item => {
|
||||
if (this.shortcutMap[item]) {
|
||||
if (fn) {
|
||||
let index = this.shortcutMap[item].findIndex(f => {
|
||||
return f === fn
|
||||
})
|
||||
if (index !== -1) {
|
||||
this.shortcutMap[item].splice(index, 1)
|
||||
}
|
||||
} else {
|
||||
this.shortcutMap[item] = []
|
||||
delete this.shortcutMap[item]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取指定快捷键的处理函数
|
||||
getShortcutFn(key) {
|
||||
let res = []
|
||||
key.split(/\s*\|\s*/).forEach(item => {
|
||||
res = this.shortcutMap[item] || []
|
||||
})
|
||||
return res
|
||||
}
|
||||
}
|
||||
69
simple-mind-map/src/core/command/keyMap.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const map = {
|
||||
Backspace: 8,
|
||||
Tab: 9,
|
||||
Enter: 13,
|
||||
|
||||
Shift: 16,
|
||||
Control: 17,
|
||||
Alt: 18,
|
||||
CapsLock: 20,
|
||||
|
||||
Esc: 27,
|
||||
|
||||
Spacebar: 32,
|
||||
|
||||
PageUp: 33,
|
||||
PageDown: 34,
|
||||
End: 35,
|
||||
Home: 36,
|
||||
|
||||
Insert: 45,
|
||||
|
||||
Left: 37,
|
||||
Up: 38,
|
||||
Right: 39,
|
||||
Down: 40,
|
||||
|
||||
Del: 46,
|
||||
|
||||
NumLock: 144,
|
||||
|
||||
Cmd: 91,
|
||||
CmdFF: 224,
|
||||
F1: 112,
|
||||
F2: 113,
|
||||
F3: 114,
|
||||
F4: 115,
|
||||
F5: 116,
|
||||
F6: 117,
|
||||
F7: 118,
|
||||
F8: 119,
|
||||
F9: 120,
|
||||
F10: 121,
|
||||
F11: 122,
|
||||
F12: 123,
|
||||
|
||||
'`': 192,
|
||||
'=': 187,
|
||||
'-': 189,
|
||||
|
||||
'/': 191,
|
||||
'.': 190
|
||||
}
|
||||
|
||||
// 数字
|
||||
for (let i = 0; i <= 9; i++) {
|
||||
map[i] = i + 48
|
||||
}
|
||||
|
||||
// 字母
|
||||
'abcdefghijklmnopqrstuvwxyz'.split('').forEach((n, index) => {
|
||||
map[n] = index + 65
|
||||
})
|
||||
|
||||
export const keyMap = map
|
||||
|
||||
export const isKey = (e, key) => {
|
||||
let code = typeof e === 'object' ? e.keyCode : e
|
||||
return map[key] === code
|
||||
}
|
||||
174
simple-mind-map/src/core/event/Event.js
Normal file
@@ -0,0 +1,174 @@
|
||||
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.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
|
||||
}
|
||||
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 (
|
||||
useLeftKeySelectionRightKeyDrag
|
||||
? this.isRightMousedown
|
||||
: this.isLeftMousedown
|
||||
) {
|
||||
e.preventDefault()
|
||||
this.emit('drag', e, this)
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标松开事件
|
||||
onMouseup(e) {
|
||||
this.isLeftMousedown = false
|
||||
this.isRightMousedown = 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
|
||||
1194
simple-mind-map/src/core/render/Render.js
Normal file
247
simple-mind-map/src/core/render/TextEdit.js
Normal file
@@ -0,0 +1,247 @@
|
||||
import { getStrWithBrFromHtml, checkNodeOuter } from '../../utils'
|
||||
|
||||
// 节点文字编辑类
|
||||
export default class TextEdit {
|
||||
// 构造函数
|
||||
constructor(renderer) {
|
||||
this.renderer = renderer
|
||||
this.mindMap = renderer.mindMap
|
||||
// 当前编辑的节点
|
||||
this.currentNode = null
|
||||
// 文本编辑框
|
||||
this.textEditNode = null
|
||||
// 隐藏的文本输入框
|
||||
this.hiddenInputEl = null
|
||||
// 文本编辑框是否显示
|
||||
this.showTextEdit = false
|
||||
// 如果编辑过程中缩放画布了,那么缓存当前编辑的内容
|
||||
this.cacheEditingText = ''
|
||||
this.bindEvent()
|
||||
this.createHiddenInput()
|
||||
}
|
||||
|
||||
// 事件
|
||||
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.on('node_active', () => {
|
||||
this.focusHiddenInput()
|
||||
})
|
||||
// 注册编辑快捷键
|
||||
this.mindMap.keyCommand.addShortcut('F2', () => {
|
||||
if (this.renderer.activeNodeList.length <= 0) {
|
||||
return
|
||||
}
|
||||
this.show(this.renderer.activeNodeList[0])
|
||||
})
|
||||
this.mindMap.on('scale', this.onScale)
|
||||
}
|
||||
|
||||
// 创建一个隐藏的文本输入框
|
||||
createHiddenInput() {
|
||||
if (this.hiddenInputEl) return
|
||||
this.hiddenInputEl = document.createElement('input')
|
||||
this.hiddenInputEl.type = 'text'
|
||||
this.hiddenInputEl.style.cssText = `
|
||||
position: fixed;
|
||||
left: -99999px;
|
||||
top: -99999px;
|
||||
`
|
||||
// 监听粘贴事件
|
||||
this.hiddenInputEl.addEventListener('paste', async event => {
|
||||
event.preventDefault()
|
||||
const text = (event.clipboardData || window.clipboardData).getData('text')
|
||||
const files = event.clipboardData.files
|
||||
let img = null
|
||||
if (files.length > 0) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (/^image\//.test(files[i].type)) {
|
||||
img = files[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
this.mindMap.emit('paste', {
|
||||
text,
|
||||
img
|
||||
})
|
||||
})
|
||||
document.body.appendChild(this.hiddenInputEl)
|
||||
}
|
||||
|
||||
// 让隐藏的文本输入框聚焦
|
||||
focusHiddenInput() {
|
||||
if (this.hiddenInputEl) this.hiddenInputEl.focus()
|
||||
}
|
||||
|
||||
// 注册临时快捷键
|
||||
registerTmpShortcut() {
|
||||
// 注册回车快捷键
|
||||
this.mindMap.keyCommand.addShortcut('Enter', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Tab', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
}
|
||||
|
||||
// 显示文本编辑框
|
||||
// isInserting:是否是刚创建的节点
|
||||
async show(node, e, isInserting = false) {
|
||||
// 使用了自定义节点内容那么不响应编辑事件
|
||||
if (node.isUseCustomNodeContent()) {
|
||||
return
|
||||
}
|
||||
let { beforeTextEdit } = this.mindMap.opt
|
||||
if (typeof beforeTextEdit === 'function') {
|
||||
let isShow = false
|
||||
try {
|
||||
isShow = await beforeTextEdit(node, isInserting)
|
||||
} catch (error) {
|
||||
isShow = false
|
||||
}
|
||||
if (!isShow) return
|
||||
}
|
||||
this.currentNode = node
|
||||
let { offsetLeft, offsetTop } = checkNodeOuter(this.mindMap, node)
|
||||
this.mindMap.view.translateXY(offsetLeft, offsetTop)
|
||||
let rect = node._textData.node.node.getBoundingClientRect()
|
||||
if (this.mindMap.richText) {
|
||||
this.mindMap.richText.showEditText(node, rect, isInserting)
|
||||
return
|
||||
}
|
||||
this.showEditTextBox(node, rect)
|
||||
}
|
||||
|
||||
// 处理画布缩放
|
||||
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) {
|
||||
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()
|
||||
})
|
||||
document.body.appendChild(this.textEditNode)
|
||||
}
|
||||
let scale = this.mindMap.view.scale
|
||||
let lineHeight = node.style.merge('lineHeight')
|
||||
let fontSize = node.style.merge('fontSize')
|
||||
let textLines = (this.cacheEditingText || node.nodeData.data.text).split(
|
||||
/\n/gim
|
||||
)
|
||||
let isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true'
|
||||
node.style.domText(this.textEditNode, scale, isMultiLine)
|
||||
this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
|
||||
this.textEditNode.innerHTML = textLines.join('<br>')
|
||||
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
|
||||
this.textEditNode.style.minHeight = rect.height + 6 + 'px'
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
this.textEditNode.style.top = rect.top + 'px'
|
||||
this.textEditNode.style.display = 'block'
|
||||
this.textEditNode.style.maxWidth =
|
||||
this.mindMap.opt.textAutoWrapWidth * scale + 'px'
|
||||
if (isMultiLine && lineHeight !== 1) {
|
||||
this.textEditNode.style.transform = `translateY(${
|
||||
-((lineHeight * fontSize - fontSize) / 2) * scale
|
||||
}px)`
|
||||
}
|
||||
this.showTextEdit = true
|
||||
// 选中文本
|
||||
if (!this.cacheEditingText) {
|
||||
this.selectNodeText()
|
||||
}
|
||||
this.cacheEditingText = ''
|
||||
}
|
||||
|
||||
// 选中文本
|
||||
selectNodeText() {
|
||||
let selection = window.getSelection()
|
||||
let range = document.createRange()
|
||||
range.selectNodeContents(this.textEditNode)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
}
|
||||
|
||||
// 获取当前正在编辑的内容
|
||||
getEditText() {
|
||||
return getStrWithBrFromHtml(this.textEditNode.innerHTML)
|
||||
}
|
||||
|
||||
// 隐藏文本编辑框
|
||||
hideEditTextBox() {
|
||||
this.currentNode = null
|
||||
if (this.mindMap.richText) {
|
||||
return this.mindMap.richText.hideEditText()
|
||||
}
|
||||
if (!this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
this.renderer.activeNodeList.forEach(node => {
|
||||
let str = this.getEditText()
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', node, str)
|
||||
if (node.isGeneralization) {
|
||||
// 概要节点
|
||||
node.generalizationBelongNode.updateGeneralization()
|
||||
}
|
||||
this.mindMap.render()
|
||||
})
|
||||
this.mindMap.emit(
|
||||
'hide_text_edit',
|
||||
this.textEditNode,
|
||||
this.renderer.activeNodeList
|
||||
)
|
||||
this.textEditNode.style.display = 'none'
|
||||
this.textEditNode.innerHTML = ''
|
||||
this.textEditNode.style.fontFamily = 'inherit'
|
||||
this.textEditNode.style.fontSize = 'inherit'
|
||||
this.textEditNode.style.fontWeight = 'normal'
|
||||
this.textEditNode.style.transform = 'translateY(0)'
|
||||
this.showTextEdit = false
|
||||
}
|
||||
}
|
||||
834
simple-mind-map/src/core/render/node/Node.js
Normal file
@@ -0,0 +1,834 @@
|
||||
import Style from './Style'
|
||||
import Shape from './Shape'
|
||||
import { asyncRun, nodeToHTML } from '../../../utils'
|
||||
import { G, Rect, ForeignObject, SVG } from '@svgdotjs/svg.js'
|
||||
import nodeGeneralizationMethods from './nodeGeneralization'
|
||||
import nodeExpandBtnMethods from './nodeExpandBtn'
|
||||
import nodeCommandWrapsMethods from './nodeCommandWraps'
|
||||
import nodeCreateContentsMethods from './nodeCreateContents'
|
||||
import { CONSTANTS } from '../../../constants/constant'
|
||||
|
||||
// 节点类
|
||||
class Node {
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
// 节点数据
|
||||
this.nodeData = this.handleData(opt.data || {})
|
||||
// id
|
||||
this.uid = opt.uid
|
||||
// 控制实例
|
||||
this.mindMap = opt.mindMap
|
||||
// 渲染实例
|
||||
this.renderer = opt.renderer
|
||||
// 渲染器
|
||||
this.draw = opt.draw || null
|
||||
// 样式实例
|
||||
this.style = new Style(this)
|
||||
// 形状实例
|
||||
this.shapeInstance = new Shape(this)
|
||||
this.shapePadding = {
|
||||
paddingX: 0,
|
||||
paddingY: 0
|
||||
}
|
||||
// 是否是根节点
|
||||
this.isRoot = opt.isRoot === undefined ? false : opt.isRoot
|
||||
// 是否是概要节点
|
||||
this.isGeneralization =
|
||||
opt.isGeneralization === undefined ? false : opt.isGeneralization
|
||||
this.generalizationBelongNode = null
|
||||
// 节点层级
|
||||
this.layerIndex = opt.layerIndex === undefined ? 0 : opt.layerIndex
|
||||
// 节点宽
|
||||
this.width = opt.width || 0
|
||||
// 节点高
|
||||
this.height = opt.height || 0
|
||||
// left
|
||||
this._left = opt.left || 0
|
||||
// top
|
||||
this._top = opt.top || 0
|
||||
// 自定义位置
|
||||
this.customLeft = opt.data.data.customLeft || undefined
|
||||
this.customTop = opt.data.data.customTop || undefined
|
||||
// 是否正在拖拽中
|
||||
this.isDrag = false
|
||||
// 父节点
|
||||
this.parent = opt.parent || null
|
||||
// 子节点
|
||||
this.children = opt.children || []
|
||||
// 节点内容的容器
|
||||
this.group = null
|
||||
this.shapeNode = null // 节点形状节点
|
||||
// 节点内容对象
|
||||
this._customNodeContent = null
|
||||
this._imgData = null
|
||||
this._iconData = null
|
||||
this._textData = null
|
||||
this._hyperlinkData = null
|
||||
this._tagData = null
|
||||
this._noteData = null
|
||||
this.noteEl = null
|
||||
this._expandBtn = null
|
||||
this._lastExpandBtnType = null
|
||||
this._showExpandBtn = false
|
||||
this._openExpandNode = null
|
||||
this._closeExpandNode = null
|
||||
this._fillExpandNode = null
|
||||
this._lines = []
|
||||
this._generalizationLine = null
|
||||
this._generalizationNode = null
|
||||
this._unVisibleRectRegionNode = null
|
||||
this._isMouseenter = false
|
||||
// 尺寸信息
|
||||
this._rectInfo = {
|
||||
imgContentWidth: 0,
|
||||
imgContentHeight: 0,
|
||||
textContentWidth: 0,
|
||||
textContentHeight: 0
|
||||
}
|
||||
// 概要节点的宽高
|
||||
this._generalizationNodeWidth = 0
|
||||
this._generalizationNodeHeight = 0
|
||||
// 各种文字信息的间距
|
||||
this.textContentItemMargin = this.mindMap.opt.textContentMargin
|
||||
// 图片和文字节点的间距
|
||||
this.blockContentMargin = this.mindMap.opt.imgTextMargin
|
||||
// 展开收缩按钮尺寸
|
||||
this.expandBtnSize = this.mindMap.opt.expandBtnSize
|
||||
// 是否是多选节点
|
||||
this.isMultipleChoice = false
|
||||
// 是否需要重新layout
|
||||
this.needLayout = false
|
||||
// 概要相关方法
|
||||
Object.keys(nodeGeneralizationMethods).forEach(item => {
|
||||
this[item] = nodeGeneralizationMethods[item].bind(this)
|
||||
})
|
||||
// 展开收起按钮相关方法
|
||||
Object.keys(nodeExpandBtnMethods).forEach(item => {
|
||||
this[item] = nodeExpandBtnMethods[item].bind(this)
|
||||
})
|
||||
// 命令的相关方法
|
||||
Object.keys(nodeCommandWrapsMethods).forEach(item => {
|
||||
this[item] = nodeCommandWrapsMethods[item].bind(this)
|
||||
})
|
||||
// 创建节点内容的相关方法
|
||||
Object.keys(nodeCreateContentsMethods).forEach(item => {
|
||||
this[item] = nodeCreateContentsMethods[item].bind(this)
|
||||
})
|
||||
// 初始化
|
||||
this.getSize()
|
||||
}
|
||||
|
||||
// 支持自定义位置
|
||||
get left() {
|
||||
return this.customLeft || this._left
|
||||
}
|
||||
|
||||
set left(val) {
|
||||
this._left = val
|
||||
}
|
||||
|
||||
get top() {
|
||||
return this.customTop || this._top
|
||||
}
|
||||
|
||||
set top(val) {
|
||||
this._top = val
|
||||
}
|
||||
|
||||
// 复位部分布局时会重新设置的数据
|
||||
reset() {
|
||||
this.children = []
|
||||
this.parent = null
|
||||
this.isRoot = false
|
||||
this.layerIndex = 0
|
||||
this.left = 0
|
||||
this.top = 0
|
||||
}
|
||||
|
||||
// 处理数据
|
||||
handleData(data) {
|
||||
data.data.expand = data.data.expand === false ? false : true
|
||||
data.data.isActive = data.data.isActive === true ? true : false
|
||||
data.children = data.children || []
|
||||
return data
|
||||
}
|
||||
|
||||
// 创建节点的各个内容对象数据
|
||||
createNodeData() {
|
||||
// 自定义节点内容
|
||||
let { isUseCustomNodeContent, customCreateNodeContent } = this.mindMap.opt
|
||||
if (isUseCustomNodeContent && customCreateNodeContent) {
|
||||
this._customNodeContent = customCreateNodeContent(this)
|
||||
}
|
||||
// 如果没有返回内容,那么还是使用内置的节点内容
|
||||
if (this._customNodeContent) return
|
||||
this._imgData = this.createImgNode()
|
||||
this._iconData = this.createIconNode()
|
||||
this._textData = this.createTextNode()
|
||||
this._hyperlinkData = this.createHyperlinkNode()
|
||||
this._tagData = this.createTagNode()
|
||||
this._noteData = this.createNoteNode()
|
||||
}
|
||||
|
||||
// 计算节点的宽高
|
||||
getSize() {
|
||||
this.updateGeneralization()
|
||||
this.createNodeData()
|
||||
let { width, height } = this.getNodeRect()
|
||||
// 判断节点尺寸是否有变化
|
||||
let changed = this.width !== width || this.height !== height
|
||||
this.width = width
|
||||
this.height = height
|
||||
return changed
|
||||
}
|
||||
|
||||
// 计算节点尺寸信息
|
||||
getNodeRect() {
|
||||
// 自定义节点内容
|
||||
if (this.isUseCustomNodeContent()) {
|
||||
let rect = this.measureCustomNodeContentSize(this._customNodeContent)
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
}
|
||||
}
|
||||
// 宽高
|
||||
let imgContentWidth = 0
|
||||
let imgContentHeight = 0
|
||||
let textContentWidth = 0
|
||||
let textContentHeight = 0
|
||||
// 存在图片
|
||||
if (this._imgData) {
|
||||
this._rectInfo.imgContentWidth = imgContentWidth = this._imgData.width
|
||||
this._rectInfo.imgContentHeight = imgContentHeight = this._imgData.height
|
||||
}
|
||||
// 图标
|
||||
if (this._iconData.length > 0) {
|
||||
textContentWidth += this._iconData.reduce((sum, cur) => {
|
||||
textContentHeight = Math.max(textContentHeight, cur.height)
|
||||
return (sum += cur.width + this.textContentItemMargin)
|
||||
}, 0)
|
||||
}
|
||||
// 文字
|
||||
if (this._textData) {
|
||||
textContentWidth += this._textData.width
|
||||
textContentHeight = Math.max(textContentHeight, this._textData.height)
|
||||
}
|
||||
// 超链接
|
||||
if (this._hyperlinkData) {
|
||||
textContentWidth += this._hyperlinkData.width
|
||||
textContentHeight = Math.max(
|
||||
textContentHeight,
|
||||
this._hyperlinkData.height
|
||||
)
|
||||
}
|
||||
// 标签
|
||||
if (this._tagData.length > 0) {
|
||||
textContentWidth += this._tagData.reduce((sum, cur) => {
|
||||
textContentHeight = Math.max(textContentHeight, cur.height)
|
||||
return (sum += cur.width + this.textContentItemMargin)
|
||||
}, 0)
|
||||
}
|
||||
// 备注
|
||||
if (this._noteData) {
|
||||
textContentWidth += this._noteData.width
|
||||
textContentHeight = Math.max(textContentHeight, this._noteData.height)
|
||||
}
|
||||
// 文字内容部分的尺寸
|
||||
this._rectInfo.textContentWidth = textContentWidth
|
||||
this._rectInfo.textContentHeight = textContentHeight
|
||||
// 间距
|
||||
let margin =
|
||||
imgContentHeight > 0 && textContentHeight > 0
|
||||
? this.blockContentMargin
|
||||
: 0
|
||||
let { paddingX, paddingY } = this.getPaddingVale()
|
||||
// 纯内容宽高
|
||||
let _width = Math.max(imgContentWidth, textContentWidth)
|
||||
let _height = imgContentHeight + textContentHeight
|
||||
// 计算节点形状需要的附加内边距
|
||||
let { paddingX: shapePaddingX, paddingY: shapePaddingY } =
|
||||
this.shapeInstance.getShapePadding(_width, _height, paddingX, paddingY)
|
||||
this.shapePadding.paddingX = shapePaddingX
|
||||
this.shapePadding.paddingY = shapePaddingY
|
||||
return {
|
||||
width: _width + paddingX * 2 + shapePaddingX * 2,
|
||||
height: _height + paddingY * 2 + margin + shapePaddingY * 2
|
||||
}
|
||||
}
|
||||
|
||||
// 定位节点内容
|
||||
layout() {
|
||||
// 清除之前的内容
|
||||
this.group.clear()
|
||||
let { width, height, textContentItemMargin } = this
|
||||
let { paddingY } = this.getPaddingVale()
|
||||
paddingY += this.shapePadding.paddingY
|
||||
// 节点形状
|
||||
this.shapeNode = this.shapeInstance.createShape()
|
||||
this.shapeNode.addClass('smm-node-shape')
|
||||
this.group.add(this.shapeNode)
|
||||
this.updateNodeShape()
|
||||
// 渲染一个隐藏的矩形区域,用来触发展开收起按钮的显示
|
||||
this.renderExpandBtnPlaceholderRect()
|
||||
// 概要节点添加一个带所属节点id的类名
|
||||
if (this.isGeneralization && this.generalizationBelongNode) {
|
||||
this.group.addClass('generalization_' + this.generalizationBelongNode.uid)
|
||||
}
|
||||
// 如果存在自定义节点内容,那么使用自定义节点内容
|
||||
if (this.isUseCustomNodeContent()) {
|
||||
let foreignObject = new ForeignObject()
|
||||
foreignObject.width(width)
|
||||
foreignObject.height(height)
|
||||
foreignObject.add(SVG(this._customNodeContent))
|
||||
this.group.add(foreignObject)
|
||||
return
|
||||
}
|
||||
// 图片节点
|
||||
let imgHeight = 0
|
||||
if (this._imgData) {
|
||||
imgHeight = this._imgData.height
|
||||
this.group.add(this._imgData.node)
|
||||
this._imgData.node.cx(width / 2).y(paddingY)
|
||||
}
|
||||
// 内容节点
|
||||
let textContentNested = new G()
|
||||
let textContentOffsetX = 0
|
||||
// icon
|
||||
let iconNested = new G()
|
||||
if (this._iconData && this._iconData.length > 0) {
|
||||
let iconLeft = 0
|
||||
this._iconData.forEach(item => {
|
||||
item.node
|
||||
.x(textContentOffsetX + iconLeft)
|
||||
.y((this._rectInfo.textContentHeight - item.height) / 2)
|
||||
iconNested.add(item.node)
|
||||
iconLeft += item.width + textContentItemMargin
|
||||
})
|
||||
textContentNested.add(iconNested)
|
||||
textContentOffsetX += iconLeft
|
||||
}
|
||||
// 文字
|
||||
if (this._textData) {
|
||||
this._textData.node.attr('data-offsetx', textContentOffsetX)
|
||||
this._textData.node.x(textContentOffsetX).y(0)
|
||||
textContentNested.add(this._textData.node)
|
||||
textContentOffsetX += this._textData.width + textContentItemMargin
|
||||
}
|
||||
// 超链接
|
||||
if (this._hyperlinkData) {
|
||||
this._hyperlinkData.node
|
||||
.x(textContentOffsetX)
|
||||
.y((this._rectInfo.textContentHeight - this._hyperlinkData.height) / 2)
|
||||
textContentNested.add(this._hyperlinkData.node)
|
||||
textContentOffsetX += this._hyperlinkData.width + textContentItemMargin
|
||||
}
|
||||
// 标签
|
||||
let tagNested = new G()
|
||||
if (this._tagData && this._tagData.length > 0) {
|
||||
let tagLeft = 0
|
||||
this._tagData.forEach(item => {
|
||||
item.node
|
||||
.x(textContentOffsetX + tagLeft)
|
||||
.y((this._rectInfo.textContentHeight - item.height) / 2)
|
||||
tagNested.add(item.node)
|
||||
tagLeft += item.width + textContentItemMargin
|
||||
})
|
||||
textContentNested.add(tagNested)
|
||||
textContentOffsetX += tagLeft
|
||||
}
|
||||
// 备注
|
||||
if (this._noteData) {
|
||||
this._noteData.node
|
||||
.x(textContentOffsetX)
|
||||
.y((this._rectInfo.textContentHeight - this._noteData.height) / 2)
|
||||
textContentNested.add(this._noteData.node)
|
||||
textContentOffsetX += this._noteData.width
|
||||
}
|
||||
// 文字内容整体
|
||||
textContentNested.translate(
|
||||
width / 2 - textContentNested.bbox().width / 2,
|
||||
imgHeight +
|
||||
paddingY +
|
||||
(imgHeight > 0 && this._rectInfo.textContentHeight > 0
|
||||
? this.blockContentMargin
|
||||
: 0)
|
||||
)
|
||||
this.group.add(textContentNested)
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnPlaceholderRect() {
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
let { width, height } = this
|
||||
if (!this._unVisibleRectRegionNode) {
|
||||
this._unVisibleRectRegionNode = new Rect()
|
||||
this._unVisibleRectRegionNode.fill({
|
||||
color: 'transparent'
|
||||
})
|
||||
}
|
||||
this.group.add(this._unVisibleRectRegionNode)
|
||||
this.renderer.layout.renderExpandBtnRect(this._unVisibleRectRegionNode, this.expandBtnSize, width, height, this)
|
||||
}
|
||||
}
|
||||
|
||||
// 给节点绑定事件
|
||||
bindGroupEvent() {
|
||||
// 单击事件,选中节点
|
||||
this.group.on('click', e => {
|
||||
this.mindMap.emit('node_click', this, e)
|
||||
if (this.isMultipleChoice) {
|
||||
e.stopPropagation()
|
||||
this.isMultipleChoice = false
|
||||
return
|
||||
}
|
||||
this.active(e)
|
||||
})
|
||||
this.group.on('mousedown', e => {
|
||||
if (this.isRoot && e.which === 3) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
if (!this.isRoot) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
// 多选和取消多选
|
||||
if (e.ctrlKey && this.mindMap.opt.enableCtrlKeyNodeSelection) {
|
||||
this.isMultipleChoice = true
|
||||
let isActive = this.nodeData.data.isActive
|
||||
if (!isActive)
|
||||
this.mindMap.emit(
|
||||
'before_node_active',
|
||||
this,
|
||||
this.renderer.activeNodeList
|
||||
)
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this, !isActive)
|
||||
this.mindMap.renderer[isActive ? 'removeActiveNode' : 'addActiveNode'](
|
||||
this
|
||||
)
|
||||
this.mindMap.emit(
|
||||
'node_active',
|
||||
isActive ? null : this,
|
||||
this.mindMap.renderer.activeNodeList
|
||||
)
|
||||
}
|
||||
this.mindMap.emit('node_mousedown', this, e)
|
||||
})
|
||||
this.group.on('mouseup', e => {
|
||||
if (!this.isRoot) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
this.mindMap.emit('node_mouseup', this, e)
|
||||
})
|
||||
this.group.on('mouseenter', e => {
|
||||
this._isMouseenter = true
|
||||
// 显示展开收起按钮
|
||||
this.showExpandBtn()
|
||||
this.mindMap.emit('node_mouseenter', this, e)
|
||||
})
|
||||
this.group.on('mouseleave', e => {
|
||||
this._isMouseenter = false
|
||||
this.hideExpandBtn()
|
||||
this.mindMap.emit('node_mouseleave', this, e)
|
||||
})
|
||||
// 双击事件
|
||||
this.group.on('dblclick', e => {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
e.stopPropagation()
|
||||
this.mindMap.emit('node_dblclick', this, e)
|
||||
})
|
||||
// 右键菜单事件
|
||||
this.group.on('contextmenu', e => {
|
||||
// 按住ctrl键点击鼠标左键不知为何触发的是contextmenu事件
|
||||
if (this.mindMap.opt.readonly || e.ctrlKey) {
|
||||
// || this.isGeneralization
|
||||
return
|
||||
}
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
if (this.nodeData.data.isActive) {
|
||||
this.renderer.clearActive()
|
||||
}
|
||||
this.active(e)
|
||||
this.mindMap.emit('node_contextmenu', e, this)
|
||||
})
|
||||
}
|
||||
|
||||
// 激活节点
|
||||
active(e) {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
e && e.stopPropagation()
|
||||
if (this.nodeData.data.isActive) {
|
||||
return
|
||||
}
|
||||
this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList)
|
||||
this.renderer.clearActive()
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this, true)
|
||||
this.renderer.addActiveNode(this)
|
||||
this.mindMap.emit('node_active', this, this.renderer.activeNodeList)
|
||||
}
|
||||
|
||||
// 更新节点
|
||||
update(isLayout = false) {
|
||||
if (!this.group) {
|
||||
return
|
||||
}
|
||||
let {
|
||||
alwaysShowExpandBtn
|
||||
} = this.mindMap.opt
|
||||
if (alwaysShowExpandBtn) {
|
||||
// 需要移除展开收缩按钮
|
||||
if (this._expandBtn && this.nodeData.children.length <= 0) {
|
||||
this.removeExpandBtn()
|
||||
} else {
|
||||
// 更新展开收起按钮
|
||||
this.renderExpandBtn()
|
||||
}
|
||||
} else {
|
||||
let { isActive, expand } = this.nodeData.data
|
||||
// 展开状态且非激活状态,且当前鼠标不在它上面,才隐藏
|
||||
if (expand && !isActive && !this._isMouseenter) {
|
||||
this.hideExpandBtn()
|
||||
} else {
|
||||
this.showExpandBtn()
|
||||
}
|
||||
}
|
||||
// 更新概要
|
||||
this.renderGeneralization()
|
||||
// 更新节点位置
|
||||
let t = this.group.transform()
|
||||
// 如果节点位置没有变化,则返回
|
||||
if (this.left === t.translateX && this.top === t.translateY) return
|
||||
this.group.translate(this.left - t.translateX, this.top - t.translateY)
|
||||
}
|
||||
|
||||
// 重新渲染节点,即重新创建节点内容、计算节点大小、计算节点内容布局、更新展开收起按钮,概要及位置
|
||||
reRender() {
|
||||
let sizeChange = this.getSize()
|
||||
this.layout()
|
||||
this.update()
|
||||
return sizeChange
|
||||
}
|
||||
|
||||
// 更新节点形状样式
|
||||
updateNodeShape() {
|
||||
if (!this.shapeNode) return
|
||||
const shape = this.getShape()
|
||||
this.style[shape === CONSTANTS.SHAPE.RECTANGLE ? 'rect' : 'shape'](
|
||||
this.shapeNode
|
||||
)
|
||||
}
|
||||
|
||||
// 递归渲染
|
||||
render(callback = () => {}) {
|
||||
// 节点
|
||||
// 重新渲染连线
|
||||
this.renderLine()
|
||||
let isLayout = false
|
||||
if (!this.group) {
|
||||
isLayout = true
|
||||
// 创建组
|
||||
this.group = new G()
|
||||
this.group.addClass('smm-node')
|
||||
this.group.css({
|
||||
cursor: 'default'
|
||||
})
|
||||
this.bindGroupEvent()
|
||||
this.draw.add(this.group)
|
||||
this.layout()
|
||||
this.update(isLayout)
|
||||
} else {
|
||||
this.draw.add(this.group)
|
||||
if (this.needLayout) {
|
||||
this.needLayout = false
|
||||
this.layout()
|
||||
}
|
||||
if (this.needRerenderExpandBtnPlaceholderRect) {
|
||||
this.needRerenderExpandBtnPlaceholderRect = false
|
||||
this.renderExpandBtnPlaceholderRect()
|
||||
}
|
||||
this.update()
|
||||
}
|
||||
// 子节点
|
||||
if (
|
||||
this.children &&
|
||||
this.children.length &&
|
||||
this.nodeData.data.expand !== false
|
||||
) {
|
||||
let index = 0
|
||||
asyncRun(
|
||||
this.children.map(item => {
|
||||
return () => {
|
||||
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) {
|
||||
asyncRun(
|
||||
this.children.map(item => {
|
||||
return () => {
|
||||
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) {
|
||||
asyncRun(
|
||||
this.children.map(item => {
|
||||
return () => {
|
||||
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) {
|
||||
asyncRun(
|
||||
this.children.map(item => {
|
||||
return () => {
|
||||
item.show()
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 连线
|
||||
renderLine(deep = false) {
|
||||
if (this.nodeData.data.expand === false) {
|
||||
return
|
||||
}
|
||||
let childrenLen = this.nodeData.children.length
|
||||
// 切换为鱼骨结构时,清空根节点和二级节点的连线
|
||||
if (
|
||||
this.mindMap.opt.layout === CONSTANTS.LAYOUT.FISHBONE &&
|
||||
(this.isRoot || this.layerIndex === 1)
|
||||
) {
|
||||
childrenLen = 0
|
||||
}
|
||||
if (childrenLen > this._lines.length) {
|
||||
// 创建缺少的线
|
||||
new Array(childrenLen - this._lines.length).fill(0).forEach(() => {
|
||||
this._lines.push(this.draw.path())
|
||||
})
|
||||
} else if (childrenLen < this._lines.length) {
|
||||
// 删除多余的线
|
||||
this._lines.slice(childrenLen).forEach(line => {
|
||||
line.remove()
|
||||
})
|
||||
this._lines = this._lines.slice(0, childrenLen)
|
||||
}
|
||||
// 画线
|
||||
this.renderer.layout.renderLine(
|
||||
this,
|
||||
this._lines,
|
||||
(line, node) => {
|
||||
// 添加样式
|
||||
this.styleLine(line, node)
|
||||
},
|
||||
this.style.getStyle('lineStyle', true)
|
||||
)
|
||||
// 子级的连线也需要更新
|
||||
if (deep && this.children && this.children.length > 0) {
|
||||
this.children.forEach(item => {
|
||||
item.renderLine(deep)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取节点形状
|
||||
getShape() {
|
||||
// 节点使用功能横线风格的话不支持设置形状,直接使用默认的矩形
|
||||
return this.mindMap.themeConfig.nodeUseLineStyle
|
||||
? CONSTANTS.SHAPE.RECTANGLE
|
||||
: this.style.getStyle('shape', false, false)
|
||||
}
|
||||
|
||||
// 检查节点是否存在自定义数据
|
||||
hasCustomPosition() {
|
||||
return this.customLeft !== undefined && this.customTop !== undefined
|
||||
}
|
||||
|
||||
// 检查节点是否存在自定义位置的祖先节点
|
||||
ancestorHasCustomPosition() {
|
||||
let node = this
|
||||
while (node) {
|
||||
if (node.hasCustomPosition()) {
|
||||
return true
|
||||
}
|
||||
node = node.parent
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 添加子节点
|
||||
addChildren(node) {
|
||||
this.children.push(node)
|
||||
}
|
||||
|
||||
// 设置连线样式
|
||||
styleLine(line, node) {
|
||||
let width =
|
||||
node.getSelfInhertStyle('lineWidth') || node.getStyle('lineWidth', true)
|
||||
let color =
|
||||
node.getSelfInhertStyle('lineColor') || node.getStyle('lineColor', true)
|
||||
let dasharray =
|
||||
node.getSelfInhertStyle('lineDasharray') ||
|
||||
node.getStyle('lineDasharray', true)
|
||||
this.style.line(line, {
|
||||
width,
|
||||
color,
|
||||
dasharray
|
||||
})
|
||||
}
|
||||
|
||||
// 移除连线
|
||||
removeLine() {
|
||||
this._lines.forEach(line => {
|
||||
line.remove()
|
||||
})
|
||||
this._lines = []
|
||||
}
|
||||
|
||||
// 检测当前节点是否是某个节点的祖先节点
|
||||
isParent(node) {
|
||||
if (this === node) {
|
||||
return false
|
||||
}
|
||||
let parent = node.parent
|
||||
while (parent) {
|
||||
if (this === parent) {
|
||||
return true
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 检测当前节点是否是某个节点的兄弟节点
|
||||
isBrother(node) {
|
||||
if (!this.parent || this === node) {
|
||||
return false
|
||||
}
|
||||
return this.parent.children.find(item => {
|
||||
return item === node
|
||||
})
|
||||
}
|
||||
|
||||
// 获取padding值
|
||||
getPaddingVale() {
|
||||
let { isActive } = this.nodeData.data
|
||||
return {
|
||||
paddingX: this.getStyle('paddingX', true, isActive),
|
||||
paddingY: this.getStyle('paddingY', true, isActive)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取某个样式
|
||||
getStyle(prop, root, isActive) {
|
||||
let v = this.style.merge(prop, root, isActive)
|
||||
return v === undefined ? '' : v
|
||||
}
|
||||
|
||||
// 获取自定义样式
|
||||
getSelfStyle(prop) {
|
||||
return this.style.getSelfStyle(prop)
|
||||
}
|
||||
|
||||
// 获取最近一个存在自身自定义样式的祖先节点的自定义样式
|
||||
getParentSelfStyle(prop) {
|
||||
if (this.parent) {
|
||||
return (
|
||||
this.parent.getSelfStyle(prop) || this.parent.getParentSelfStyle(prop)
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 获取自身可继承的自定义样式
|
||||
getSelfInhertStyle(prop) {
|
||||
return (
|
||||
this.getSelfStyle(prop) || // 自身
|
||||
this.getParentSelfStyle(prop)
|
||||
) // 父级
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
getData(key) {
|
||||
return key ? this.nodeData.data[key] || '' : this.nodeData.data
|
||||
}
|
||||
|
||||
// 是否存在自定义样式
|
||||
hasCustomStyle() {
|
||||
return this.style.hasCustomStyle()
|
||||
}
|
||||
}
|
||||
|
||||
export default Node
|
||||
229
simple-mind-map/src/core/render/node/Shape.js
Normal file
@@ -0,0 +1,229 @@
|
||||
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 { width, height } = this.node
|
||||
let node = null
|
||||
// 矩形
|
||||
if (shape === CONSTANTS.SHAPE.RECTANGLE) {
|
||||
node = new Rect().size(width, height)
|
||||
} else if (shape === CONSTANTS.SHAPE.DIAMOND) {
|
||||
// 菱形
|
||||
node = this.createDiamond()
|
||||
} else if (shape === CONSTANTS.SHAPE.PARALLELOGRAM) {
|
||||
// 平行四边形
|
||||
node = this.createParallelogram()
|
||||
} else if (shape === CONSTANTS.SHAPE.ROUNDED_RECTANGLE) {
|
||||
// 圆角矩形
|
||||
node = this.createRoundedRectangle()
|
||||
} else if (shape === CONSTANTS.SHAPE.OCTAGONAL_RECTANGLE) {
|
||||
// 八角矩形
|
||||
node = this.createOctagonalRectangle()
|
||||
} else if (shape === CONSTANTS.SHAPE.OUTER_TRIANGULAR_RECTANGLE) {
|
||||
// 外三角矩形
|
||||
node = this.createOuterTriangularRectangle()
|
||||
} else if (shape === CONSTANTS.SHAPE.INNER_TRIANGULAR_RECTANGLE) {
|
||||
// 内三角矩形
|
||||
node = this.createInnerTriangularRectangle()
|
||||
} else if (shape === CONSTANTS.SHAPE.ELLIPSE) {
|
||||
// 椭圆
|
||||
node = this.createEllipse()
|
||||
} else if (shape === CONSTANTS.SHAPE.CIRCLE) {
|
||||
// 圆
|
||||
node = this.createCircle()
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// 创建菱形
|
||||
createDiamond() {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
let topX = halfWidth
|
||||
let topY = 0
|
||||
let rightX = width
|
||||
let rightY = halfHeight
|
||||
let bottomX = halfWidth
|
||||
let bottomY = height
|
||||
let leftX = 0
|
||||
let leftY = halfHeight
|
||||
return new Polygon().plot([
|
||||
[topX, topY],
|
||||
[rightX, rightY],
|
||||
[bottomX, bottomY],
|
||||
[leftX, leftY]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建平行四边形
|
||||
createParallelogram() {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return new Polygon().plot([
|
||||
[paddingX, 0],
|
||||
[width, 0],
|
||||
[width - paddingX, height],
|
||||
[0, height]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建圆角矩形
|
||||
createRoundedRectangle() {
|
||||
let { width, height } = this.node
|
||||
let halfHeight = height / 2
|
||||
return new Path().plot(`
|
||||
M${halfHeight},0
|
||||
L${width - halfHeight},0
|
||||
A${height / 2},${height / 2} 0 0,1 ${width - halfHeight},${height}
|
||||
L${halfHeight},${height}
|
||||
A${height / 2},${height / 2} 0 0,1 ${halfHeight},${0}
|
||||
`)
|
||||
}
|
||||
|
||||
// 创建八角矩形
|
||||
createOctagonalRectangle() {
|
||||
let w = 5
|
||||
let { width, height } = this.node
|
||||
return new Polygon().plot([
|
||||
[0, w],
|
||||
[w, 0],
|
||||
[width - w, 0],
|
||||
[width, w],
|
||||
[width, height - w],
|
||||
[width - w, height],
|
||||
[w, height],
|
||||
[0, height - w]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建外三角矩形
|
||||
createOuterTriangularRectangle() {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return new Polygon().plot([
|
||||
[paddingX, 0],
|
||||
[width - paddingX, 0],
|
||||
[width, height / 2],
|
||||
[width - paddingX, height],
|
||||
[paddingX, height],
|
||||
[0, height / 2]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建内三角矩形
|
||||
createInnerTriangularRectangle() {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return new Polygon().plot([
|
||||
[0, 0],
|
||||
[width, 0],
|
||||
[width - paddingX / 2, height / 2],
|
||||
[width, height],
|
||||
[0, height],
|
||||
[paddingX / 2, height / 2]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建椭圆
|
||||
createEllipse() {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
return new Path().plot(`
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
}
|
||||
|
||||
// 创建圆
|
||||
createCircle() {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
return new Path().plot(`
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
// 形状列表
|
||||
export const shapeList = [
|
||||
CONSTANTS.SHAPE.RECTANGLE,
|
||||
CONSTANTS.SHAPE.DIAMOND,
|
||||
CONSTANTS.SHAPE.PARALLELOGRAM,
|
||||
CONSTANTS.SHAPE.ROUNDED_RECTANGLE,
|
||||
CONSTANTS.SHAPE.OCTAGONAL_RECTANGLE,
|
||||
CONSTANTS.SHAPE.OUTER_TRIANGULAR_RECTANGLE,
|
||||
CONSTANTS.SHAPE.INNER_TRIANGULAR_RECTANGLE,
|
||||
CONSTANTS.SHAPE.ELLIPSE,
|
||||
CONSTANTS.SHAPE.CIRCLE
|
||||
]
|
||||
227
simple-mind-map/src/core/render/node/Style.js
Normal file
@@ -0,0 +1,227 @@
|
||||
import { tagColorList, nodeDataNoStylePropList } from '../../../constants/constant'
|
||||
const rootProp = ['paddingX', 'paddingY']
|
||||
const backgroundStyleProps = ['backgroundColor', 'backgroundImage', 'backgroundRepeat', 'backgroundPosition', 'backgroundSize']
|
||||
|
||||
// 样式类
|
||||
class Style {
|
||||
// 设置背景样式
|
||||
static setBackgroundStyle(el, themeConfig) {
|
||||
// 缓存容器元素原本的样式
|
||||
if (!Style.cacheStyle) {
|
||||
Style.cacheStyle = {}
|
||||
let style = window.getComputedStyle(el)
|
||||
backgroundStyleProps.forEach((prop) => {
|
||||
Style.cacheStyle[prop] = style[prop]
|
||||
})
|
||||
}
|
||||
// 设置新样式
|
||||
let { backgroundColor, backgroundImage, backgroundRepeat, backgroundPosition, backgroundSize } = themeConfig
|
||||
el.style.backgroundColor = backgroundColor
|
||||
if (backgroundImage) {
|
||||
el.style.backgroundImage = `url(${backgroundImage})`
|
||||
el.style.backgroundRepeat = backgroundRepeat
|
||||
el.style.backgroundPosition = backgroundPosition
|
||||
el.style.backgroundSize = backgroundSize
|
||||
} else {
|
||||
el.style.backgroundImage = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
// 移除背景样式
|
||||
static removeBackgroundStyle(el) {
|
||||
if (!Style.cacheStyle) return
|
||||
backgroundStyleProps.forEach((prop) => {
|
||||
el.style[prop] = Style.cacheStyle[prop]
|
||||
})
|
||||
Style.cacheStyle = null
|
||||
}
|
||||
|
||||
// 构造函数
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
}
|
||||
|
||||
// 合并样式
|
||||
merge(prop, root, isActive) {
|
||||
let themeConfig = this.ctx.mindMap.themeConfig
|
||||
// 三级及以下节点
|
||||
let defaultConfig = themeConfig.node
|
||||
if (root || rootProp.includes(prop)) {
|
||||
// 直接使用最外层样式
|
||||
defaultConfig = themeConfig
|
||||
} else if (this.ctx.isGeneralization) {
|
||||
// 概要节点
|
||||
defaultConfig = themeConfig.generalization
|
||||
} else if (this.ctx.layerIndex === 0) {
|
||||
// 根节点
|
||||
defaultConfig = themeConfig.root
|
||||
} else if (this.ctx.layerIndex === 1) {
|
||||
// 二级节点
|
||||
defaultConfig = themeConfig.second
|
||||
}
|
||||
// 激活状态
|
||||
if (isActive !== undefined ? isActive : this.ctx.nodeData.data.isActive) {
|
||||
if (
|
||||
this.ctx.nodeData.data.activeStyle &&
|
||||
this.ctx.nodeData.data.activeStyle[prop] !== undefined
|
||||
) {
|
||||
return this.ctx.nodeData.data.activeStyle[prop]
|
||||
} else if (defaultConfig.active && defaultConfig.active[prop]) {
|
||||
return defaultConfig.active[prop]
|
||||
}
|
||||
}
|
||||
// 优先使用节点本身的样式
|
||||
return this.getSelfStyle(prop) !== undefined
|
||||
? this.getSelfStyle(prop)
|
||||
: defaultConfig[prop]
|
||||
}
|
||||
|
||||
// 获取某个样式值
|
||||
getStyle(prop, root, isActive) {
|
||||
return this.merge(prop, root, isActive)
|
||||
}
|
||||
|
||||
// 获取自身自定义样式
|
||||
getSelfStyle(prop) {
|
||||
return this.ctx.nodeData.data[prop]
|
||||
}
|
||||
|
||||
// 矩形
|
||||
rect(node) {
|
||||
this.shape(node)
|
||||
node.radius(this.merge('borderRadius'))
|
||||
}
|
||||
|
||||
// 矩形外的其他形状
|
||||
shape(node) {
|
||||
node.fill({
|
||||
color: this.merge('fillColor')
|
||||
})
|
||||
// 节点使用横线样式,不需要渲染非激活状态的边框样式
|
||||
// if (
|
||||
// !this.ctx.isRoot &&
|
||||
// !this.ctx.isGeneralization &&
|
||||
// this.ctx.mindMap.themeConfig.nodeUseLineStyle &&
|
||||
// !this.ctx.nodeData.data.isActive
|
||||
// ) {
|
||||
// return
|
||||
// }
|
||||
node.stroke({
|
||||
color: this.merge('borderColor'),
|
||||
width: this.merge('borderWidth'),
|
||||
dasharray: this.merge('borderDasharray')
|
||||
})
|
||||
}
|
||||
|
||||
// 文字
|
||||
text(node) {
|
||||
node
|
||||
.fill({
|
||||
color: this.merge('color')
|
||||
})
|
||||
.css({
|
||||
'font-family': this.merge('fontFamily'),
|
||||
'font-size': this.merge('fontSize'),
|
||||
'font-weight': this.merge('fontWeight'),
|
||||
'font-style': this.merge('fontStyle'),
|
||||
'text-decoration': this.merge('textDecoration')
|
||||
})
|
||||
}
|
||||
|
||||
// 生成内联样式
|
||||
createStyleText() {
|
||||
return `
|
||||
color: ${this.merge('color')};
|
||||
font-family: ${this.merge('fontFamily')};
|
||||
font-size: ${this.merge('fontSize') + 'px'};
|
||||
font-weight: ${this.merge('fontWeight')};
|
||||
font-style: ${this.merge('fontStyle')};
|
||||
text-decoration: ${this.merge('textDecoration')}
|
||||
`
|
||||
}
|
||||
|
||||
// 获取文本样式
|
||||
getTextFontStyle() {
|
||||
return {
|
||||
italic: this.merge('fontStyle') === 'italic',
|
||||
bold: this.merge('fontWeight'),
|
||||
fontSize: this.merge('fontSize'),
|
||||
fontFamily: this.merge('fontFamily')
|
||||
}
|
||||
}
|
||||
|
||||
// html文字节点
|
||||
domText(node, fontSizeScale = 1, isMultiLine) {
|
||||
node.style.fontFamily = this.merge('fontFamily')
|
||||
node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px'
|
||||
node.style.fontWeight = this.merge('fontWeight') || 'normal'
|
||||
node.style.lineHeight = !isMultiLine ? 'normal' : this.merge('lineHeight')
|
||||
node.style.fontStyle = this.merge('fontStyle')
|
||||
}
|
||||
|
||||
// 标签文字
|
||||
tagText(node, index) {
|
||||
node
|
||||
.fill({
|
||||
color: tagColorList[index].color
|
||||
})
|
||||
.css({
|
||||
'font-size': '12px'
|
||||
})
|
||||
}
|
||||
|
||||
// 标签矩形
|
||||
tagRect(node, index) {
|
||||
node.fill({
|
||||
color: tagColorList[index].background
|
||||
})
|
||||
}
|
||||
|
||||
// 内置图标
|
||||
iconNode(node) {
|
||||
node.attr({
|
||||
fill: this.merge('color')
|
||||
})
|
||||
}
|
||||
|
||||
// 连线
|
||||
line(node, { width, color, dasharray } = {}) {
|
||||
node.stroke({ width, color, dasharray }).fill({ color: 'none' })
|
||||
}
|
||||
|
||||
// 概要连线
|
||||
generalizationLine(node) {
|
||||
node
|
||||
.stroke({
|
||||
width: this.merge('generalizationLineWidth', true),
|
||||
color: this.merge('generalizationLineColor', true)
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
}
|
||||
|
||||
// 展开收起按钮
|
||||
iconBtn(node, node2, fillNode) {
|
||||
let { color, fill } = this.ctx.mindMap.opt.expandBtnStyle || {
|
||||
color: '#808080',
|
||||
fill: '#fff'
|
||||
}
|
||||
node.fill({ color: color })
|
||||
node2.fill({ color: color })
|
||||
fillNode.fill({ color: fill })
|
||||
}
|
||||
|
||||
// 是否设置了自定义的样式
|
||||
hasCustomStyle() {
|
||||
let res = false
|
||||
Object.keys(this.ctx.nodeData.data).forEach((item) => {
|
||||
if (!nodeDataNoStylePropList.includes(item)) {
|
||||
res = true
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
Style.cacheStyle = null
|
||||
|
||||
export default Style
|
||||
56
simple-mind-map/src/core/render/node/nodeCommandWraps.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// 设置数据
|
||||
function setData(data = {}) {
|
||||
this.mindMap.execCommand('SET_NODE_DATA', this, data)
|
||||
}
|
||||
|
||||
// 设置文本
|
||||
function setText(text, richText, resetRichText) {
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', this, text, richText, resetRichText)
|
||||
}
|
||||
|
||||
// 设置图片
|
||||
function setImage(imgData) {
|
||||
this.mindMap.execCommand('SET_NODE_IMAGE', this, imgData)
|
||||
}
|
||||
|
||||
// 设置图标
|
||||
function setIcon(icons) {
|
||||
this.mindMap.execCommand('SET_NODE_ICON', this, icons)
|
||||
}
|
||||
|
||||
// 设置超链接
|
||||
function setHyperlink(link, title) {
|
||||
this.mindMap.execCommand('SET_NODE_HYPERLINK', this, link, title)
|
||||
}
|
||||
|
||||
// 设置备注
|
||||
function setNote(note) {
|
||||
this.mindMap.execCommand('SET_NODE_NOTE', this, note)
|
||||
}
|
||||
|
||||
// 设置标签
|
||||
function setTag(tag) {
|
||||
this.mindMap.execCommand('SET_NODE_TAG', this, tag)
|
||||
}
|
||||
|
||||
// 设置形状
|
||||
function setShape(shape) {
|
||||
this.mindMap.execCommand('SET_NODE_SHAPE', this, shape)
|
||||
}
|
||||
|
||||
// 修改某个样式
|
||||
function setStyle(prop, value, isActive) {
|
||||
this.mindMap.execCommand('SET_NODE_STYLE', this, prop, value, isActive)
|
||||
}
|
||||
|
||||
export default {
|
||||
setData,
|
||||
setText,
|
||||
setImage,
|
||||
setIcon,
|
||||
setHyperlink,
|
||||
setNote,
|
||||
setTag,
|
||||
setShape,
|
||||
setStyle
|
||||
}
|
||||