Compare commits
2090 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8383002b5c | |||
| 8c37b91b5b | |||
| 5a387eef96 | |||
| c27759189d | |||
| a33a399989 | |||
| 6c7c76958f | |||
| 6980abe331 | |||
| 5fc92cdd10 | |||
| 4bcc39a7ae | |||
| a044a8bc21 | |||
| f15a293a69 | |||
| 448a695503 | |||
| 3915893ee6 | |||
| 914e6eea2e | |||
| 3434ab6227 | |||
| 90ba3f7fa6 | |||
| f05bcf6cc8 | |||
| 21d5ee25ea | |||
| 13c73fbfe4 | |||
| 97dbedfed5 | |||
| e5ef1c6472 | |||
| 17945a9298 | |||
| 17c0ecc0f5 | |||
| 8c2fc15d81 | |||
| ac399773d0 | |||
| 199674feca | |||
| cb6af3e21f | |||
| efe0bb6ae2 | |||
| 4a1403eed3 | |||
| 77a7628453 | |||
| 21186d1edb | |||
| 1cb77b4f48 | |||
| a6f92a883c | |||
| 2c0b09a9ad | |||
| 003fffca2d | |||
| 28b719e9db | |||
| 4d8fbef455 | |||
| e55f8e8ea1 | |||
| 8ad987a64b | |||
| b0aff1e378 | |||
| 6239ac0b88 | |||
| 875fa9c6f0 | |||
| a7b020b0ff | |||
| b614517728 | |||
| 329e9bd933 | |||
| 73f27a9423 | |||
| ffee3aa564 | |||
| 9268ebb580 | |||
| 40fc4e89e0 | |||
| 6fd4a4f036 | |||
| 483991b85c | |||
| a0d24ea6cc | |||
| 9613285513 | |||
| 649a939a57 | |||
| f20e360c0b | |||
| c1e599971e | |||
| 21c93d38c7 | |||
| 4902559cfa | |||
| 49b22dc184 | |||
| f1f75ff015 | |||
| 6e68fa9cfc | |||
| c68596b91e | |||
| 7d52573595 | |||
| 6ac34ba8aa | |||
| 9136f1dbe3 | |||
| 98cfdb53c3 | |||
| e17387c99e | |||
| d30ed9e726 | |||
| 988af60a30 | |||
| 8b53456b5d | |||
| d23193527e | |||
| b58376847e | |||
| d0551db70a | |||
| e416151a56 | |||
| b02b496b88 | |||
| e31dbbcedc | |||
| 09ad1bac86 | |||
| a2ac761f70 | |||
| 9e5ad7e2b4 | |||
| 4f015fb155 | |||
| b4526473fa | |||
| 3ab9fcc857 | |||
| 7ef231a4c7 | |||
| da3a1f7dea | |||
| 81001542b8 | |||
| aacf5b696c | |||
| 062c0feb65 | |||
| 92f97eab78 | |||
| 31120eab09 | |||
| 9b7d718f78 | |||
| 9132c9eb14 | |||
| adebf8ec38 | |||
| 854e14f9b0 | |||
| d7faac257c | |||
| 1b3c5294d9 | |||
| f7802ba4d0 | |||
| 8e84dce73f | |||
| 620da0e645 | |||
| c4657ebb86 | |||
| 24cbaf69ea | |||
| f3ef2f7aec | |||
| 77dff81217 | |||
| 004364c3a9 | |||
| 877d702978 | |||
| a8bc9f442b | |||
| f09f05ab3c | |||
| ef5377f85c | |||
| 5dbdd7bf12 | |||
| d73e3eb0ea | |||
| b7d03d694c | |||
| b7d4983af5 | |||
| 919cf8a736 | |||
| 8a7d2ddf30 | |||
| b1640f44c2 | |||
| 6e61538dec | |||
| f789a919e1 | |||
| b9651240df | |||
| 5266368f79 | |||
| 42854ff924 | |||
| 48b0e09278 | |||
| f05062f902 | |||
| 532aa03c56 | |||
| b891cb6fe3 | |||
| 0683779e09 | |||
| b949b1651a | |||
| 259ce3dd11 | |||
| cab703b98e | |||
| a57e0496ee | |||
| df66a49ba5 | |||
| 245a29f797 | |||
| 4e8f574fdb | |||
| a5d6135f0c | |||
| 12c7fb1713 | |||
| 4dd56a4e5d | |||
| abe5992d21 | |||
| 8d74b6ee30 | |||
| 8b5797ac66 | |||
| 231b850f74 | |||
| 7003bb8426 | |||
| c4b5be783e | |||
| c38829f7d8 | |||
| fdf3633d8c | |||
| 3916476af7 | |||
| edc94547ab | |||
| 8a8cf9a679 | |||
| 21f3b99838 | |||
| 3bfa0b1d85 | |||
| de55fd43a0 | |||
| 6e68e53b1e | |||
| 4b8d89854e | |||
| 8298f7d5bb | |||
| 816153a1db | |||
| 1e8ddf4754 | |||
| d73e87b980 | |||
| b64b0a3618 | |||
| d52ae90b56 | |||
| 0045f97569 | |||
| 41be1e901d | |||
| 0a14d96e10 | |||
| 57c3fe358b | |||
| c255db6ff3 | |||
| f6484f759e | |||
| b16636d06b | |||
| b50049c822 | |||
| 220a316d70 | |||
| dbac80509a | |||
| bb82eb4891 | |||
| c1d584e3e7 | |||
| f776dac04d | |||
| b56f99fff9 | |||
| 4151dfaf1f | |||
| 836e0b0fde | |||
| 6e7a0e36f4 | |||
| a7fe7937a5 | |||
| dba59b92ac | |||
| 0d6ffa6de6 | |||
| 9edf5ee36c | |||
| 585b712c4c | |||
| d6f20a365e | |||
| f7befe387c | |||
| b1a34b7a64 | |||
| d556cf7741 | |||
| 76a046cca6 | |||
| d6385aea1c | |||
| 31bf2b5af1 | |||
| de613c8a4b | |||
| 84b626d344 | |||
| 2ca7d2a337 | |||
| ea03e8e7c7 | |||
| 778894482f | |||
| 1035f7ef92 | |||
| 6e4f8bc982 | |||
| 36e47cec43 | |||
| 8b2bc352a6 | |||
| 256b3c0887 | |||
| 77cf91e802 | |||
| ea55eb1738 | |||
| 302ef307fe | |||
| 3f3a10de7d | |||
| c73123cd23 | |||
| 73935e0f22 | |||
| 0dfe835eef | |||
| 1ec07421b5 | |||
| 441e600b5d | |||
| bd4b4bd233 | |||
| f0343c98b5 | |||
| 36ff945384 | |||
| 057fb35d00 | |||
| bcbece5bd6 | |||
| 1b01e4a5c5 | |||
| 5aad206e0d | |||
| 74344c4782 | |||
| f280358f56 | |||
| 243201502b | |||
| 8c090218d1 | |||
| 6ca1c598d4 | |||
| 5eff424b2d | |||
| 0c078ef863 | |||
| fea6576dfb | |||
| 998d829783 | |||
| 226a6942dc | |||
| 52a3d184c7 | |||
| 44cdab063c | |||
| 24bc2cf138 | |||
| e6b707ea51 | |||
| 6183011952 | |||
| d80a59556e | |||
| ebbe018bae | |||
| bad05f77b5 | |||
| ea375a9ce6 | |||
| d3382d7856 | |||
| acf0c17d7a | |||
| 83568c8b33 | |||
| cc80529498 | |||
| 002970921d | |||
| ea74592e89 | |||
| fa38d3d664 | |||
| cffedb8cb7 | |||
| c77beb662e | |||
| 77dd83c2d6 | |||
| ceb99cea2b | |||
| 04a8224484 | |||
| 2f54e9d7b3 | |||
| 1165e5bbf2 | |||
| 24c6ca60a3 | |||
| 8f3b65c6d4 | |||
| 4835e3db50 | |||
| 7a30c93ff5 | |||
| 1fca17d557 | |||
| 67a70fd6e4 | |||
| 2e8660de26 | |||
| 069a478559 | |||
| c70d8e1c4e | |||
| 379d514bb5 | |||
| 25dc6a00d3 | |||
| eb2f65e6e5 | |||
| 16c86cd02d | |||
| f5a657d5c3 | |||
| dfc23b4428 | |||
| b62055e705 | |||
| 3190211f9a | |||
| e138f600a1 | |||
| 2173b82fb4 | |||
| ceeb9bffba | |||
| ba70d67c89 | |||
| 74341cc162 | |||
| fc4a3a1194 | |||
| c33ac03255 | |||
| 66afe271c5 | |||
| 9279783fc3 | |||
| 3cf1ea438b | |||
| 277e46030d | |||
| af19ffb736 | |||
| 1f00962c47 | |||
| 742a62b6ff | |||
| 140e92eeda | |||
| 34c2dab82a | |||
| fa2366c373 | |||
| e2d898df14 | |||
| cacc67b11d | |||
| aef860a8cc | |||
| c890f95092 | |||
| 7c9a5f8265 | |||
| 7eafcb02cb | |||
| d4517d3c53 | |||
| 62a9a097aa | |||
| e7fa1caf6c | |||
| e347443a0a | |||
| b77e540863 | |||
| df4168e8fa | |||
| 576f86ce48 | |||
| c3335c0c24 | |||
| 26f79da2f9 | |||
| 11cb1815ad | |||
| a6fdb07e8b | |||
| ffdb3cd431 | |||
| 5f9b01ef04 | |||
| ca53624149 | |||
| 6484450ad3 | |||
| 7c97b683ea | |||
| 378b3e73f2 | |||
| a2f21357b6 | |||
| 79e03dc0d5 | |||
| e0f35450e1 | |||
| cc9f58fb6a | |||
| e8be85141b | |||
| 1bf57ea2f5 | |||
| 18b737b22b | |||
| 4ec3332808 | |||
| b2290ecf65 | |||
| 55c336daab | |||
| f2ed3f619c | |||
| 114f400bd9 | |||
| 506d1118b2 | |||
| 20441543f0 | |||
| 2dbc47ac3c | |||
| bbe99649cd | |||
| 04806d2004 | |||
| 759a635026 | |||
| e68bd9286f | |||
| a6b31e82c0 | |||
| bc3e37e541 | |||
| 1124bb6bfa | |||
| b19c19d73c | |||
| feb3fad4da | |||
| 3db745d684 | |||
| b4089ae62e | |||
| 97bdf78b08 | |||
| dc9fb26260 | |||
| 97b0972fdf | |||
| ea23145349 | |||
| f7013196f7 | |||
| 9d0baa0799 | |||
| 91c2fa4eee | |||
| 837311b2b7 | |||
| bc53eab669 | |||
| 55eb811193 | |||
| f7aa107a62 | |||
| 56a65af9a7 | |||
| de3f5d16a2 | |||
| d325a58f17 | |||
| fd137ae787 | |||
| bb1b893961 | |||
| 01c247ac29 | |||
| 1fa75640ee | |||
| 6e0191af6b | |||
| 9ac4070e4e | |||
| 2590ea5bdb | |||
| 290e031f77 | |||
| 9bc9af5eec | |||
| 8faffe9d4e | |||
| 95dfad64ce | |||
| c9dd98fa8d | |||
| 3ae99c82cc | |||
| 016abafee4 | |||
| dca606cf5f | |||
| e7c77b173d | |||
| 68b588ee7b | |||
| b4c753dfb4 | |||
| 037bcf6007 | |||
| b2e0e7b9e2 | |||
| 03c1399768 | |||
| 9b3a59f089 | |||
| e8f824a046 | |||
| 4772b9cce7 | |||
| 74b64bd21f | |||
| 3934270ed2 | |||
| 664ec925e9 | |||
| 3806454b18 | |||
| bea92f3483 | |||
| 133542d7fa | |||
| 7a363f7f79 | |||
| 2322efcc00 | |||
| 3cc830b37b | |||
| 7a1a9be3ab | |||
| 082c9ed005 | |||
| dfd327d7fc | |||
| 3354850216 | |||
| d330d0bf26 | |||
| 13b9b597ce | |||
| 96491f3bf5 | |||
| 4f99f11a33 | |||
| 134c90508d | |||
| 863829bb41 | |||
| 9802ab92e0 | |||
| b7eebeecb4 | |||
| a406778d35 | |||
| c9cef8ca86 | |||
| c099870352 | |||
| ad921dcfe7 | |||
| 0bdf8ea81b | |||
| 079c3cf1a6 | |||
| e29b4f3ff3 | |||
| b67476ae19 | |||
| 2e4b841e7e | |||
| 1dd49c6d93 | |||
| a948c0d7fb | |||
| 62e79ff5b3 | |||
| 6300a06a44 | |||
| a80c4e709f | |||
| 08f7afc3f1 | |||
| 1e65cee4ad | |||
| bbe262df7e | |||
| 7d0de8b5a3 | |||
| 244da46ac2 | |||
| bd6a4bf298 | |||
| ef6f3adc3f | |||
| 2047fb3a3a | |||
| 57594d7e4d | |||
| ba60aa6eb0 | |||
| 11c7c0f83f | |||
| 1108433b99 | |||
| 3ac4802d44 | |||
| 884d015b88 | |||
| 69ed0fc1ec | |||
| 4a9d492571 | |||
| 8a9a4c8b0f | |||
| 2ce6088e74 | |||
| 0ad74d9f7f | |||
| c76021194a | |||
| 5c3056aea0 | |||
| 7deb5b1aae | |||
| a7bd58a6bd | |||
| f690612fae | |||
| 59d1c4b4ab | |||
| ccd9398a53 | |||
| 1db9ed8169 | |||
| 7fbef51237 | |||
| efe3308325 | |||
| 4c874fa50b | |||
| 1704ad37d2 | |||
| a019c27c8b | |||
| 5119652ca5 | |||
| 1fb0ea29a3 | |||
| 1ca9fbd023 | |||
| 9eb70de1bc | |||
| fccfca8890 | |||
| 06e12e2899 | |||
| 559822686c | |||
| c46ac64a9c | |||
| ea7233bc1b | |||
| a6bc7552f4 | |||
| 7b04f18d4e | |||
| 99f0e25acf | |||
| 7b7ce4320b | |||
| 1023ef87f7 | |||
| 84365130df | |||
| 4097e6d20c | |||
| 940a8024ac | |||
| 15196b39c3 | |||
| 81864d4947 | |||
| 73a4a044ee | |||
| 8155aac25d | |||
| 6e8d1f5d9e | |||
| 30852376dc | |||
| ad986043aa | |||
| 17c020284d | |||
| 1d280a3a35 | |||
| 8394c6d6d6 | |||
| e2da207965 | |||
| 6f00a332c1 | |||
| 1c8bdcf352 | |||
| 31fe3c858e | |||
| c06be8f502 | |||
| 2d51eb73b5 | |||
| 30bc0b03e5 | |||
| dd9cb1825e | |||
| 6d5c4b4e08 | |||
| 3d0a960337 | |||
| 705f4fd155 | |||
| ac98a76797 | |||
| 13396f418b | |||
| e70696cd6f | |||
| d8bbee8c76 | |||
| 3e701718d3 | |||
| 656863a7d0 | |||
| 3e3db27296 | |||
| 86cdb231d0 | |||
| 5d6c9bb76b | |||
| d02f1e99c8 | |||
| 26b6de9149 | |||
| 2b3d1c11de | |||
| 51e3377b38 | |||
| 99ec2d7609 | |||
| 469dbd1497 | |||
| b1513ca587 | |||
| b1e384e52d | |||
| 14cb2e836b | |||
| 1c50edd8be | |||
| 87570e16ce | |||
| abf4bf0254 | |||
| 092fd1c3ee | |||
| c1105df271 | |||
| 8292b2ef25 | |||
| d032293a0d | |||
| d038beb07a | |||
| edac024ad9 | |||
| dc422a2ccc | |||
| bcb7002b22 | |||
| 1a585d353f | |||
| b16ec37df3 | |||
| d2abadd69b | |||
| 432cc3825e | |||
| 9ef764ffa1 | |||
| c2ca1c9702 | |||
| 07de74b775 | |||
| a7d0f022dc | |||
| 2e03cb1db1 | |||
| 7f82312b96 | |||
| 10ba2db0f1 | |||
| 8c99492518 | |||
| b5b2d97786 | |||
| 3d601189f1 | |||
| 681b674eb2 | |||
| c838b9d077 | |||
| 703e710006 | |||
| 3ab1c59a05 | |||
| 45dc3cb537 | |||
| 2f8c368eef | |||
| 78302ca426 | |||
| 0675a369c3 | |||
| 85f92ca9a5 | |||
| f252eb068a | |||
| 8c65075823 | |||
| 7d78f677b8 | |||
| 41e9e9cc04 | |||
| fefd8424b7 | |||
| 60bdc64730 | |||
| 7b6b2a30e6 | |||
| 9b55eff11d | |||
| 07a03d3e60 | |||
| cf3063f3e4 | |||
| 1d46818eec | |||
| ec3a826e01 | |||
| 97d5b12732 | |||
| e0cb877162 | |||
| 698478d826 | |||
| f722f3516b | |||
| 27a9f965a9 | |||
| a95effabdd | |||
| 08eb9d892d | |||
| d7530d0c4c | |||
| 967243e705 | |||
| 47eac7e70e | |||
| f35075de9d | |||
| b983cbe094 | |||
| 3ea6da4790 | |||
| b658bf4fd0 | |||
| 4aa12638f1 | |||
| 54e9e3b10d | |||
| 12a13bb97b | |||
| 8b35926b2d | |||
| 1d71f809cc | |||
| a9fefadec9 | |||
| bdf4b7e495 | |||
| 12c8384347 | |||
| 272782ba4c | |||
| a75d7876e2 | |||
| 890f3e890c | |||
| 1c2e513c34 | |||
| ed239929f3 | |||
| 2d65224389 | |||
| 418810d542 | |||
| 4a2d015b97 | |||
| 15cb1aa5f2 | |||
| 7226187ac6 | |||
| 6de2e49047 | |||
| f0b31c15a6 | |||
| 80decf5656 | |||
| 376790330d | |||
| 200317a0f5 | |||
| 92a405f833 | |||
| 2a3e759890 | |||
| 46e0b04dd1 | |||
| 88a8d8fa4b | |||
| bf5c4ab188 | |||
| 14d314e0a2 | |||
| 324d514f10 | |||
| e11e1989ad | |||
| a5b99c25ed | |||
| 3c908feb95 | |||
| 840eca88c8 | |||
| fa7caf3592 | |||
| 654e15f845 | |||
| 7a81344aea | |||
| 803d267687 | |||
| c8e4d18364 | |||
| aeecca1260 | |||
| 49aaa2d58b | |||
| dd22b119b7 | |||
| 42984ea5fa | |||
| ce0147091a | |||
| 884abf5b0e | |||
| 5cc1c7093a | |||
| 87aae93f4b | |||
| 77002b59b2 | |||
| 5601f72d59 | |||
| afcc8050ce | |||
| 17c043668c | |||
| 2495fc7b26 | |||
| 61404ff852 | |||
| a62ae3c6e0 | |||
| 28f65b3790 | |||
| ce4b83dbe0 | |||
| 7a24faece1 | |||
| 22555b835b | |||
| b2648fecac | |||
| 15a6799d7b | |||
| 13b24aeb7c | |||
| 674ce28420 | |||
| 60c07c3738 | |||
| 2b7f3c1f73 | |||
| 04ff617e3c | |||
| 5ee247d423 | |||
| 0bd2c4eff7 | |||
| 0408729a1d | |||
| 61e9a456af | |||
| 93f89e7a41 | |||
| bb26c9ce55 | |||
| bd2107378b | |||
| 0b0714c166 | |||
| 696ed84f4c | |||
| c8a467af9d | |||
| 8f6383cbe6 | |||
| 25e64dd6f8 | |||
| 745bc65379 | |||
| a4868215f2 | |||
| 8e2d8045c0 | |||
| 5b290ef5c0 | |||
| b68a50431f | |||
| 4f8c7d66e1 | |||
| 48bf15457d | |||
| d85bea7872 | |||
| 02fe33da5a | |||
| 3f17cc705b | |||
| 8078d0f137 | |||
| 51ed642c35 | |||
| 842e1c0d6c | |||
| f76cfe648f | |||
| 1b66473ae0 | |||
| f0bae76b78 | |||
| ff58675069 | |||
| b3cb53b482 | |||
| 4eb9773398 | |||
| 748ab0fff2 | |||
| ae22162020 | |||
| 7390e59ab0 | |||
| 57e0331c58 | |||
| 862374bec4 | |||
| 266c5c0dc8 | |||
| 9fee9a1d6a | |||
| f65341d906 | |||
| c11ca7a3f7 | |||
| b7f3a38826 | |||
| 6ebe50e71b | |||
| 6ce2aea000 | |||
| 60c341927f | |||
| ed03d40ff3 | |||
| 6c616f2b6b | |||
| 84f026f1dd | |||
| 89c9c0d9ef | |||
| 9d631cc980 | |||
| 786d4fc719 | |||
| 9c68a5350b | |||
| e26bf83bb0 | |||
| 52583a6092 | |||
| de9056739b | |||
| 9c611698c7 | |||
| a6357abc15 | |||
| b83facb14e | |||
| 2dc16c13bc | |||
| a46f8b36a0 | |||
| c0453ea1fc | |||
| 41b8f9c962 | |||
| e69175e5c4 | |||
| 76babf4dd3 | |||
| 5d6f53dc58 | |||
| 72a49b7bf2 | |||
| c4230a15c9 | |||
| 84167bbcfc | |||
| 7f154ba1db | |||
| 7abb7e2ef1 | |||
| 04d21ac890 | |||
| a6992874f8 | |||
| 1b2db81dde | |||
| 17e6d23650 | |||
| 2aa1ba537f | |||
| 981ca7629e | |||
| 1aaef3d554 | |||
| 2fd83a4a80 | |||
| fc89dce7c2 | |||
| 62b85a4bbd | |||
| 927d85ea68 | |||
| 89ccf700c3 | |||
| b3c29f2e27 | |||
| 06af63a10a | |||
| 50742e5e4d | |||
| 570c701b93 | |||
| a1c4d53d7c | |||
| 3250b81a23 | |||
| fb914734c8 | |||
| be6eb73671 | |||
| 221a0f8e85 | |||
| d5e5ec8c46 | |||
| b10215aec9 | |||
| ea20f84d91 | |||
| 008e843b39 | |||
| 172dd4f81b | |||
| 4697fb4664 | |||
| c9e6ba972b | |||
| 758f414302 | |||
| 0fb5c04deb | |||
| 47885685fe | |||
| 8e87dddcd2 | |||
| 4649649cf3 | |||
| 68a74be279 | |||
| 483e0757b7 | |||
| 9abce33257 | |||
| 337d4d7288 | |||
| bde0680f81 | |||
| ec579288a8 | |||
| e2c222a156 | |||
| 23de7e3575 | |||
| 18a8f41bf3 | |||
| 63585911a7 | |||
| acafdcc991 | |||
| 9d14f16fe2 | |||
| 89eb93aa67 | |||
| fbfac18ca0 | |||
| 78b2f94bae | |||
| 89f0a401ef | |||
| 0776071454 | |||
| 56fb29fe15 | |||
| d7ed3884d6 | |||
| ba673ccf41 | |||
| 44c1071d49 | |||
| 7789240f64 | |||
| 7eee02dc94 | |||
| 965957aa1b | |||
| 65a3917f62 | |||
| 6ba4a57bbc | |||
| 4e13817509 | |||
| a8330fa2e9 | |||
| be0d444db9 | |||
| ecf3c1ad04 | |||
| 6bf439f493 | |||
| 94b52941af | |||
| a80fb33880 | |||
| 30e4c35165 | |||
| 9bd1214567 | |||
| c28a2bd792 | |||
| 45a7e586c7 | |||
| 5903bab81f | |||
| 295f8f56e3 | |||
| 4cc8af7ff0 | |||
| bca2dde497 | |||
| a8a72159f0 | |||
| 8ea7cb6ac2 | |||
| 2b721b111b | |||
| d7e080a579 | |||
| 6429c242e4 | |||
| aa7b593b65 | |||
| fabac7e2bb | |||
| 9b5ee56a09 | |||
| c78d3d74c2 | |||
| b1459901d0 | |||
| aba56ac340 | |||
| 3d4e1872f1 | |||
| 7a4d33b7da | |||
| afb65eb908 | |||
| df5daeeb75 | |||
| 792779e5b2 | |||
| 3313a0a182 | |||
| 88dd53302f | |||
| e972adfbcb | |||
| ab6425e0d9 | |||
| a81ee5b3db | |||
| fae08a5285 | |||
| eeb4a25d7a | |||
| fdf07180dd | |||
| 6387a6fcda | |||
| a6e400629a | |||
| 8172b7c776 | |||
| ca4d2ac4a2 | |||
| abd9f850eb | |||
| 3aef6f5e05 | |||
| 2f957374e3 | |||
| 3f8cabc7e0 | |||
| 73d1dc8f48 | |||
| 2bfb78a257 | |||
| 783e5c43a5 | |||
| d7e8330016 | |||
| 80d3836e9e | |||
| c26d1c348e | |||
| 95c307cc0b | |||
| 6e7299e445 | |||
| 01fa047591 | |||
| 8c741c1fb7 | |||
| b0bfc8d5ed | |||
| ad8d8f94ff | |||
| c99bd2bb63 | |||
| 468df7dad7 | |||
| 3c2e4a0990 | |||
| 87b717f6a9 | |||
| 51dc45988e | |||
| 2f8b986f1f | |||
| 7f01e7acb6 | |||
| d011d2ba8a | |||
| a922654c17 | |||
| e70a486362 | |||
| 1683bc8418 | |||
| b6368fb0e4 | |||
| ddfa9c2676 | |||
| f3ddd5a11a | |||
| 39e8b2359e | |||
| 4fb73c155b | |||
| 3287603d0a | |||
| 1abb317054 | |||
| 719e37c26b | |||
| 45d2f99fd7 | |||
| f75fdded98 | |||
| 22a6d87771 | |||
| 2479da4bbc | |||
| c22424d798 | |||
| bbb30b7c25 | |||
| 71cbe12cee | |||
| 011effa047 | |||
| b1648dd702 | |||
| 81621cb9d0 | |||
| 3f460d7a5c | |||
| 6fe34c1250 | |||
| d5cac938c2 | |||
| 6222fabdd4 | |||
| 8ed4f25499 | |||
| 3afd4641cd | |||
| e9fde97453 | |||
| 441f8b6e26 | |||
| 8042b917a0 | |||
| 343e7281fe | |||
| c64e6a4554 | |||
| 8190bf275c | |||
| e792f2637d | |||
| 3cd26323dc | |||
| 40e1c4d467 | |||
| 86c22d373a | |||
| cb7a76efc5 | |||
| 545425c4d3 | |||
| eb1aaa10e4 | |||
| daf0db312b | |||
| 8b2bc2f064 | |||
| 2e343cbbf9 | |||
| a75f0470bc | |||
| 287d31a3a9 | |||
| 430fff0515 | |||
| fbbb7b8ad7 | |||
| 54b74d7411 | |||
| 82dba31b2a | |||
| 38d7e782e0 | |||
| 05e5ceeb43 | |||
| fcbbad71d2 | |||
| 6f9161439f | |||
| aa0c4fb841 | |||
| cff50d1f81 | |||
| b8581b6368 | |||
| 094f1223d1 | |||
| 6802476afa | |||
| a0539dce9b | |||
| bf2b8fec35 | |||
| 082c9a03ec | |||
| 4ccdd862ba | |||
| e5a20ed0fc | |||
| a72022704e | |||
| 2acc7ada30 | |||
| e61882c331 | |||
| 61c145689a | |||
| 40ea9664a7 | |||
| bccd2d0f3e | |||
| b918809dca | |||
| e7c2e92879 | |||
| cf03c921a7 | |||
| 2c45c839b1 | |||
| 83f5fc58d1 | |||
| 1a267f1e5a | |||
| c3fd5551d8 | |||
| 1baa74bb69 | |||
| 261ecefb17 | |||
| d81d8f7c5d | |||
| de1af12157 | |||
| 803cbbdee9 | |||
| 008477d5fa | |||
| 7f0d29ebd2 | |||
| 11bf8cffb2 | |||
| 87b9ebc7e1 | |||
| b89d27411b | |||
| 24c37f1d3e | |||
| 58b0a0a3be | |||
| ed58873a82 | |||
| a8dcbd4af0 | |||
| 4b2f29442a | |||
| dfab0d7ddf | |||
| ce1998b701 | |||
| 92c631d6ce | |||
| 01009f9e35 | |||
| 74eca093da | |||
| e7d89e65b2 | |||
| 24b2bffe8e | |||
| d2906476c8 | |||
| 2b31a355ae | |||
| b602048186 | |||
| 4fb7031060 | |||
| 4ef77c2e72 | |||
| 72ba1a9f58 | |||
| 0c3938b652 | |||
| 16f80adb0d | |||
| 637b6bb4f9 | |||
| 12e91927a5 | |||
| 8f6f22ba7f | |||
| b520d6a334 | |||
| b7bff30445 | |||
| 0d790bbd80 | |||
| 8394e99127 | |||
| 16ae32bfd7 | |||
| cc6423e384 | |||
| 06cc587599 | |||
| 9d36088f1d | |||
| f9f73b015c | |||
| 77ffd696bb | |||
| bdb8bed053 | |||
| 4b17c8e96e | |||
| 8109711f4e | |||
| 327eba3fa7 | |||
| f3dccb2e99 | |||
| 7112d58e7c | |||
| 6e0aad853c | |||
| c64b1d0846 | |||
| 6eba16ce99 | |||
| 2f6f719843 | |||
| f34bac49e4 | |||
| 39cdc6231f | |||
| 745e24da70 | |||
| a4f4f4fb15 | |||
| 3833c229c6 | |||
| 3dc3e9b5a8 | |||
| 12eeb79e9f | |||
| e08c84f70d | |||
| 10f55d5b65 | |||
| 761992d089 | |||
| 991a823700 | |||
| 61dbcf762c | |||
| 60b0149a9c | |||
| 501c034cfa | |||
| 11593db23c | |||
| a9ae2a004f | |||
| 7b2a31781c | |||
| ab73eb9632 | |||
| 8ade170b4e | |||
| 91c3d1161d | |||
| bb63805e87 | |||
| 8caf04990c | |||
| f28dd0a965 | |||
| b5d83268e5 | |||
| 19adc42122 | |||
| 8afee913be | |||
| 82d101365f | |||
| 73e679f268 | |||
| 372a8a1de1 | |||
| 171cc34c91 | |||
| fbd217d674 | |||
| f24960ab26 | |||
| d1a871c8f6 | |||
| 086b97906b | |||
| db76a11347 | |||
| 729b10cce8 | |||
| 4cbd25ccb3 | |||
| 0ef7b738a9 | |||
| 33b5563601 | |||
| f6f799f534 | |||
| 8c3f1360e1 | |||
| fafbabd603 | |||
| 254e38895c | |||
| aa0bb22cdd | |||
| 5bfd0c7b2f | |||
| c3885c170c | |||
| 88a7cce269 | |||
| a1fccc2905 | |||
| a889041896 | |||
| 8d778aba2c | |||
| ba5db63e0b | |||
| 0259d83429 | |||
| b6999e57ae | |||
| 04184cf731 | |||
| 04f98eb9e7 | |||
| 869dd045af | |||
| 339ebb862e | |||
| 7b8598bf9f | |||
| ab1c460225 | |||
| 298164d6ed | |||
| 703a8a4e0d | |||
| deaf5fcbf6 | |||
| 18b46689f1 | |||
| edbe3f8311 | |||
| 94c4c3e2d4 | |||
| 077f72737d | |||
| ae089a9390 | |||
| 0f087c2aa6 | |||
| 19d2ffb48e | |||
| a9bb6f73de | |||
| ca124732fa | |||
| 11da14aeab | |||
| 7d5f037c85 | |||
| f62b415227 | |||
| f2b8d4014e | |||
| 3ef047fb41 | |||
| aa3be4ab0d | |||
| 7500049ea2 | |||
| 44600df75c | |||
| dec2a15773 | |||
| 4da78a04cb | |||
| 98ec204bab | |||
| cd9499b064 | |||
| f5e824be86 | |||
| c76254f4f9 | |||
| 77ac6f88ca | |||
| 7e10911991 | |||
| 161b67b09d | |||
| 9614536a29 | |||
| 8ba7aab468 | |||
| 4ea9f79de1 | |||
| b7f7025d97 | |||
| 20d5dd2668 | |||
| adc2d02fbb | |||
| ef2dbdc93b | |||
| 9721ec1f0b | |||
| 4d069d87d7 | |||
| 0308f9ce65 | |||
| 6bcd3cb217 | |||
| 7f83c490db | |||
| bbf9c895b8 | |||
| ffa2063c52 | |||
| d464b1f78e | |||
| 92cb071408 | |||
| 9141b60d03 | |||
| df0a196931 | |||
| a8cb1dd495 | |||
| adc4966d49 | |||
| 1b2ea8c522 | |||
| 7c8bdb489b | |||
| 16458fbb42 | |||
| c72839cdcb | |||
| a31ad5803c | |||
| cf800cbd36 | |||
| 3c06ef0b1a | |||
| 2c3b921f09 | |||
| 402ecc66ae | |||
| e25f56a9b5 | |||
| b28fa887a2 | |||
| f83a02e619 | |||
| eaad089d68 | |||
| a2fda16df9 | |||
| e9e8ff57ae | |||
| 1fd95a2f2e | |||
| 228584ee48 | |||
| c651df0f6e | |||
| 5cced9baf2 | |||
| 5dd26e554b | |||
| 696fd3e8cd | |||
| 1d19b705d3 | |||
| d54074cb57 | |||
| cc054aeb75 | |||
| c32eaed534 | |||
| fa23f73ec4 | |||
| fbf6a13f92 | |||
| 3eba662772 | |||
| e007ee271f | |||
| 59a8d65a89 | |||
| 793519ae1b | |||
| 5c44062aa2 | |||
| fe03a2c2c0 | |||
| d1d74a3770 | |||
| 16108bf779 | |||
| 5bebf077e4 | |||
| efcc01ad6b | |||
| e8a4b45446 | |||
| 6cba297d77 | |||
| b149dc3cb9 | |||
| 1ee4f934b9 | |||
| f7b4adb85d | |||
| da3c41567d | |||
| 5f73795220 | |||
| 36bf1122c6 | |||
| 402f8bb9f9 | |||
| eef2a915fa | |||
| 26ee4d172f | |||
| 08eb450446 | |||
| ecae7d818c | |||
| 47ffccff68 | |||
| 0ba5286c94 | |||
| db8e1e3589 | |||
| d0764a6a77 | |||
| 7b6c347d6b | |||
| d58fec180c | |||
| ac4e4877a1 | |||
| ddea61245d | |||
| 98915bcff2 | |||
| 8e0f0450df | |||
| 1d9a669829 | |||
| 97bcf168b7 | |||
| e81198165b | |||
| b03b8da586 | |||
| d497e07187 | |||
| f284a99194 | |||
| 7d6dd6f805 | |||
| d70e28d198 | |||
| 4c6f138e5c | |||
| 5974841a7b | |||
| 5df54439ba | |||
| 8f627c6b7f | |||
| 3f28bc5c6c | |||
| 903fa699ca | |||
| d9e8f64699 | |||
| 8ad6ca8d41 | |||
| 4017efc65e | |||
| 6ed15d52de | |||
| 5246a2f79b | |||
| b670932dd7 | |||
| ddf30bd96b | |||
| 7e92428485 | |||
| 4c16050c85 | |||
| 2ed035525a | |||
| 1b89b0d7b6 | |||
| 0364821f0b | |||
| fa9d6f37ae | |||
| a4777904b9 | |||
| 0b3c2f95c5 | |||
| 8f54955432 | |||
| 034db2fc27 | |||
| 59141b0241 | |||
| 2e43b96a2c | |||
| edff96299c | |||
| 8d9587b790 | |||
| ad0cf2a849 | |||
| f928946c61 | |||
| 6cc9d0bee2 | |||
| 287e6bb91f | |||
| 3509036d85 | |||
| 752901dbb9 | |||
| e3add94546 | |||
| 8d31c74b39 | |||
| e6f608d206 | |||
| d80b9e29a7 | |||
| 092674465d | |||
| b2e3a5bb18 | |||
| 147a2f957e | |||
| ce56f2919c | |||
| 18059102a3 | |||
| 4542fcccbb | |||
| d12009d30c | |||
| d85e0fbd13 | |||
| f7a5f9ae6b | |||
| 3d4c3d0acc | |||
| c106c41048 | |||
| cea777e8b2 | |||
| 6f069b73da | |||
| c80af8c984 | |||
| d51cbd0682 | |||
| 7e94c945ff | |||
| bd9095b4c2 | |||
| c135bf8fb8 | |||
| f6aebb15b4 | |||
| 2b4f88becd | |||
| 7fbd3639b6 | |||
| 9e52c2c31d | |||
| 46a1e5ff14 | |||
| fa7c1fd646 | |||
| c347809eea | |||
| dd09a42f02 | |||
| db8ffd05ea | |||
| 045c29ca20 | |||
| 4ea6d19602 | |||
| df00447c41 | |||
| db5b1caea7 | |||
| 8432954b8d | |||
| 4a000e23e9 | |||
| b42db51346 | |||
| 1ef968ea93 | |||
| 5b50537333 | |||
| 8c624b48fb | |||
| ad329fc2c8 | |||
| f045b8f659 | |||
| 00a9da45bb | |||
| b7ecd12f8c | |||
| a8a3164ee7 | |||
| b1aec69e98 | |||
| d713a8c5a4 | |||
| 3895faf941 | |||
| bf336fdb10 | |||
| c96c26288c | |||
| 83558c089d | |||
| 26acb72e17 | |||
| 4724a432ab | |||
| 5b9c1627c0 | |||
| d06c2585cf | |||
| 3e96e51efd | |||
| 9afa7191c4 | |||
| dfddec5586 | |||
| 93022424b3 | |||
| aed77a187f | |||
| 583f47c98b | |||
| ff528b3f9d | |||
| 6d98d96ccb | |||
| 6f8e08c21a | |||
| 3a7417f493 | |||
| 66164a0c33 | |||
| 0d7a478e16 | |||
| 9caef310df | |||
| 688f742c16 | |||
| 38dd083cdc | |||
| 851bd1c3c6 | |||
| c91b8a1a7a | |||
| 69c45764b2 | |||
| 07a62d2f78 | |||
| 46b064b0a8 | |||
| 1d3b96bb65 | |||
| 916425b7f5 | |||
| 1c570ca242 | |||
| ae41108971 | |||
| 395e7609a4 | |||
| 185c9dc284 | |||
| a9b0ac0595 | |||
| d9e3524211 | |||
| f40ef15a24 | |||
| 4d7544eefb | |||
| 92bb458345 | |||
| 92e695ef7c | |||
| 45ff3fbb9b | |||
| ba252e92d3 | |||
| 4778f459e3 | |||
| c997854ae0 | |||
| 65b5322ad5 | |||
| dd43bf0476 | |||
| 15c808eebd | |||
| 28748a99ca | |||
| fb37d3b9e6 | |||
| e1f10e054c | |||
| c670357c01 | |||
| 92dce9b36e | |||
| aa2b62e8da | |||
| a34a65f354 | |||
| eea5f8496c | |||
| 091a256bcc | |||
| 14c9ce3ce1 | |||
| fd1e5e13fe | |||
| 19685b8f95 | |||
| a9da8dc10c | |||
| fcfba7a978 | |||
| 2c8c4351dd | |||
| 79264b187d | |||
| 52eba9117b | |||
| 37a69032f6 | |||
| dd16b4f5a1 | |||
| e9c20255a6 | |||
| 630b4edf91 | |||
| 04ef638e17 | |||
| 35bad240c2 | |||
| 0cb4749cf3 | |||
| 7c8281eb00 | |||
| 1957288219 | |||
| c4d2b15c48 | |||
| beebfb0dae | |||
| d67172b795 | |||
| 2b012bc042 | |||
| 84da68f950 | |||
| ad97fd13bf | |||
| ebf6d0d5f7 | |||
| 0f3b4b4384 | |||
| 226cf46d6b | |||
| 5689e33541 | |||
| 15e59e7117 | |||
| 6b538f6662 | |||
| ad6808b696 | |||
| 8347b18efc | |||
| f92b132f0a | |||
| 79057d6757 | |||
| a9b5ee8f62 | |||
| 65da2c449e | |||
| 0ef8c12650 | |||
| 1dfa48aecb | |||
| b5d1713b43 | |||
| 102421997d | |||
| 43370bb8d1 | |||
| 310f50fcde | |||
| 9b5d157333 | |||
| 53fbb7a0bd | |||
| fe92117c4e | |||
| 4a0d13457a | |||
| 1ad6405003 | |||
| 75e7e36011 | |||
| 933c695b8c | |||
| b34f7142f6 | |||
| 7392fa8165 | |||
| 349d417869 | |||
| b8e94cac9c | |||
| cd90490b8d | |||
| a9cc94ccb6 | |||
| 17f137af09 | |||
| ea7d0bbf71 | |||
| 176f1cf405 | |||
| 2ee93bbcc1 | |||
| ba0629000e | |||
| 313049f873 | |||
| d1a1b17d29 | |||
| eb91d2ce0a | |||
| 71c77da898 | |||
| 1b96c70386 | |||
| 703cecbd4f | |||
| e20bf41cc7 | |||
| 0f7b9524f9 | |||
| 53a9e901ec | |||
| 9a79fc71ed | |||
| e7f7a8038f | |||
| 5361f8ae6f | |||
| ec3bcddc9d | |||
| 326a9f1d75 | |||
| 8c07e9b8a3 | |||
| 406d01febf | |||
| 98663422b1 | |||
| 879dfcea28 | |||
| 40edb4c8de | |||
| 07b6d142ad | |||
| 5716b3e3cf | |||
| 1b242a07e1 | |||
| 1516a73228 | |||
| eef00eb28f | |||
| a5ac282ab6 | |||
| dbb88d4999 | |||
| 39addad3a1 | |||
| 87fd9a4470 | |||
| 7a9156449f | |||
| 6e2938e9c1 | |||
| 39e1498b26 | |||
| cc2dc9c1a7 | |||
| fa87198131 | |||
| 9b70b952f6 | |||
| b29fca1cdb | |||
| 501af800ce | |||
| 979be117c6 | |||
| 13594371bb | |||
| 9628d1b27f | |||
| fd0bd0b343 | |||
| 9ea58bfdad | |||
| c54d140107 | |||
| 98d4a4213c | |||
| a7d1de5ee3 | |||
| fab9e4b265 | |||
| d5fa46033e | |||
| 32c179bbf9 | |||
| 99f9886876 | |||
| c1fcaff28a | |||
| 2da5e8d71b | |||
| e80e64a287 | |||
| 0344e51ef7 | |||
| 9fd31b0584 | |||
| c6a48f79da | |||
| 110d050cc6 | |||
| 18188ef235 | |||
| 3e1c397132 | |||
| cda1f0b77d | |||
| afd49f154c | |||
| 505c620103 | |||
| 5d9e340d6d | |||
| f705c4575c | |||
| 1508bd806f | |||
| 1c542e0615 | |||
| ed5bcbde18 | |||
| 084cbcd362 | |||
| ad8369cfe9 | |||
| 7d36b3993e | |||
| ead0854c8e | |||
| 60b5655574 | |||
| 9037630990 | |||
| ba23fc9506 | |||
| 938ca7c0ea | |||
| a034f7a9a0 | |||
| d4c4fcfd74 | |||
| 5b457f2a8a | |||
| adc22441fc | |||
| 1ba59136c6 | |||
| 77702b2ac9 | |||
| 0d6c814908 | |||
| 42ce58db60 | |||
| 1ef56033fb | |||
| 8c1d3c0248 | |||
| a725a12d25 | |||
| f67db9beed | |||
| f22fd7b5d1 | |||
| 060664260b | |||
| 9da5ec7413 | |||
| 076240f7cd | |||
| d97a4ad243 | |||
| 56209d8491 | |||
| 61fe7197f5 | |||
| 1881bfd69e | |||
| 4287b7def5 | |||
| 27d683a7cc | |||
| 38902c8f88 | |||
| d8e8142ff4 | |||
| fdaf9cb3ef | |||
| b2fe091c88 | |||
| 8548244cef | |||
| 91a5881600 | |||
| 93fd043b14 | |||
| 47948ef530 | |||
| 61fa618673 | |||
| 545aecc6ae | |||
| ee41f53286 | |||
| cc8036f736 | |||
| 67a982e6e4 | |||
| b16000f8e5 | |||
| a142ce0542 | |||
| c87b8b65fc | |||
| 7332df1d56 | |||
| de90c17ab1 | |||
| 3d54edf25b | |||
| 111819dbf3 | |||
| 3665753ba2 | |||
| 75e38aa8f3 | |||
| 29dc6938c3 | |||
| 304933b02b | |||
| 49f268bb62 | |||
| d0793c546d | |||
| 375aec6f6c | |||
| 12ec527dcd | |||
| 52d95173d7 | |||
| 67e3d51106 | |||
| 9081aea926 | |||
| 02c6caf465 | |||
| 1a9ddf1969 | |||
| 41bcae9d05 | |||
| 28109a0c97 | |||
| d8825f0a73 | |||
| 82c045a243 | |||
| 4d11f69282 | |||
| 664e4ea048 | |||
| 6d21c5e24e | |||
| ba21b6884d | |||
| 60065aab3e | |||
| 4d978936ac | |||
| 4ea585633b | |||
| dee0bd7f4d | |||
| edb206ffa5 | |||
| 21932056f3 | |||
| 0c3809b789 | |||
| d5ae596ad6 | |||
| 42db7e60f2 | |||
| 85827820a4 | |||
| fad2571edb | |||
| c53b6de68c | |||
| c86eb12168 | |||
| fb31f8df11 | |||
| 147b4fc5ed | |||
| cdcb2d2af0 | |||
| ff086e497c | |||
| a2b711da05 | |||
| 40ecfbefa3 | |||
| 2097a1250b | |||
| 22025f9b00 | |||
| 80c1badba5 | |||
| be308ba280 | |||
| fa7f3d44e4 | |||
| 9bb00489fe | |||
| 33948c604c | |||
| b8fe5bbd66 | |||
| 3fcc01c253 | |||
| 658d48c17b | |||
| 8d76c014c8 | |||
| 26cd125534 | |||
| 1bc23af61a | |||
| a2aa6b48a8 | |||
| 5784c974f7 | |||
| 80503dc42e | |||
| d76195f7ae | |||
| 2fe482b802 | |||
| 16283f4643 | |||
| cd7a801400 | |||
| f6d8558d07 | |||
| de20b506f0 | |||
| ea1d52292e | |||
| 463e506ca3 | |||
| 3bae64a2c7 | |||
| 5d13c8b543 | |||
| c6c0789794 | |||
| d7df39290f | |||
| b64cefad46 | |||
| 4690ab3c30 | |||
| b61c7490b3 | |||
| 9e511d29a6 | |||
| a9cdc36967 | |||
| 96c51cd7ff | |||
| 636769c0ef | |||
| 7beb2a3cc5 | |||
| 52d318054c | |||
| 5ee2ad6f3d | |||
| 097d414d0d | |||
| e0ce119374 | |||
| da9a5242cb | |||
| 0211b38be9 | |||
| 512e9b21cd | |||
| 9181ad55ae | |||
| a1cb9e1b28 | |||
| ea09a3e798 | |||
| 21fa146f6f | |||
| 1a3130041f | |||
| 7095a5890c | |||
| 4a86d1aa05 | |||
| 19fe7223fb | |||
| 8867fde3f2 | |||
| 588d1822b1 | |||
| d2b3b38d9e | |||
| 206ae3d9ed | |||
| a67b7a2fd0 | |||
| f5a08b225c | |||
| c807f97c29 | |||
| 8382413137 | |||
| c6acafd3a6 | |||
| 9d43056361 | |||
| 52f8e24954 | |||
| dac05fec84 | |||
| cb4ed77572 | |||
| 3427cbdc2e | |||
| ec0fb0c3cf | |||
| 357983179c | |||
| 78b66c141e | |||
| 3ac88bb400 | |||
| 27493b857c | |||
| e5a976abcf | |||
| 9e89bf5303 | |||
| 54eb330711 | |||
| e9014c6f5d | |||
| 620bcf6fab | |||
| c03bbd9e13 | |||
| b428b4ec5b | |||
| 876cc116ea | |||
| 0ba00c08ef | |||
| d41ecd9d55 | |||
| 35d351ce6b | |||
| 5d3af58361 | |||
| 8554b92f42 | |||
| 76363b4263 | |||
| b4250b9161 | |||
| 441f40886f | |||
| e8e8ad5d63 | |||
| 454078ec82 | |||
| 63f29c2686 | |||
| 280445f613 | |||
| d7f6433b53 | |||
| 22da4cc408 | |||
| 4e185ef584 | |||
| fd9b4b4ba6 | |||
| 2f79bbcb0f | |||
| 2c70d243df | |||
| 7710b77255 | |||
| d1eed23399 | |||
| ff9ce4df44 | |||
| b7c52842f2 | |||
| 365459f649 | |||
| e6354f724f | |||
| 14eedca939 | |||
| 83529cfe09 | |||
| bd4e1c1810 | |||
| 5665fcc823 | |||
| 710a9014dd | |||
| 108c6b2b17 | |||
| 70735943c3 | |||
| d01a24f879 | |||
| ec2d5043ff | |||
| 15082c2c52 | |||
| 44278d68b4 | |||
| 40eeb9db66 | |||
| a5eb1dfca7 | |||
| 8719677f11 | |||
| 8f01b6c5fe | |||
| 9e63a3f49b | |||
| 84edb7bbe6 | |||
| c86ce00a17 | |||
| aba6c18a25 | |||
| 62c81e6d44 | |||
| 68ea323855 | |||
| f94d81ad20 | |||
| 063f8b1751 | |||
| 35cfd26ece | |||
| f331cb1b4d | |||
| 502e205071 | |||
| 975b563b8d | |||
| cc88630859 | |||
| cce0b930ec | |||
| 723c825df2 | |||
| 5b4c966354 | |||
| 69023f98d9 | |||
| dce5722e96 | |||
| cbdbc93274 | |||
| 042b153684 | |||
| 4054423721 | |||
| 2e63a6eaca | |||
| 9eca7eb2ee | |||
| 1489fb645e | |||
| 5c0ca841d7 | |||
| a4d3d3ff9c | |||
| c3b67f4a4b | |||
| cde5daf19a | |||
| 904b2c0988 | |||
| f9907e2ec6 | |||
| 7329c0097c | |||
| 0478949305 | |||
| 34172a54fe | |||
| 6dd629eda5 | |||
| 636dd2b8d5 | |||
| b20dbc5202 | |||
| ee45104eb9 | |||
| 152be89860 | |||
| 6b5a375542 | |||
| 03d049846d | |||
| 6ed084bb94 | |||
| f5332b63a9 | |||
| 87356215c3 | |||
| 07c7acc37a | |||
| 4a6d9dee67 | |||
| 52eb93e59c | |||
| 2245a018e6 | |||
| 8b327fd715 | |||
| 7a7289a4c8 | |||
| ceb259819f | |||
| fdfbaedbd7 | |||
| 9591fbf146 | |||
| b2a09dbf6d | |||
| 11a5a7fdbe | |||
| 745b798d89 | |||
| 7bac32e3c4 | |||
| 880ca08571 | |||
| 85d4d8a71e | |||
| abb8ed0bcc | |||
| bea0d10a6c | |||
| 80d943af23 | |||
| 90b85e469d | |||
| 0d419c1323 | |||
| f7a2ecc2e0 | |||
| 5fdbf83f32 | |||
| c513ad9291 | |||
| a2dfa55dbd | |||
| 78f8d9a271 | |||
| b25a5aac8c | |||
| 25cc70035e | |||
| e5d70d5a0e | |||
| 066b0d1f8a | |||
| 71b2e56187 | |||
| 457832fbbe | |||
| 00554e066b | |||
| 9f9775e585 | |||
| 9cf70ae74e | |||
| 4f046987c6 | |||
| 699666c23b | |||
| 6a8cdd0155 | |||
| ce61f0e2b7 | |||
| b3cb0bf93f | |||
| 07328eea2d | |||
| 5f7a6f2e07 | |||
| 39d54cc493 | |||
| d31f40408b | |||
| 7f88d9ae27 | |||
| 1c1dd81474 | |||
| 53a498c581 | |||
| bfb6346812 | |||
| bd2fa1e427 | |||
| 6c4bfeff29 | |||
| 299a77eea8 | |||
| 351a00fb1e | |||
| 2140e1ebe1 | |||
| 497a820ba2 | |||
| 7182f3554a | |||
| 506ba52502 | |||
| 90c1630af4 | |||
| 5934abd448 | |||
| 86f432ef01 | |||
| 4913019c5c | |||
| a6316797e6 | |||
| 00d7549bde | |||
| 8fc33f5649 | |||
| 729b544675 | |||
| 865415a6c0 | |||
| 32e8324275 | |||
| 842f6dd726 | |||
| 59f7d11df3 | |||
| dcd2f52c59 | |||
| b45413c232 | |||
| b4481ff680 | |||
| d7d7a3919f | |||
| f8e4732dcd | |||
| 1b92ed66b7 | |||
| 9ba74b9504 | |||
| 8cd49f12d1 | |||
| c48ca9ee89 | |||
| 43bcf71bf5 | |||
| ee48813df1 | |||
| f4a67e2822 | |||
| f03733da04 | |||
| 207560bcc7 | |||
| f38313ff2c | |||
| b5d9e21f37 | |||
| 5d42a8957e | |||
| b276c60909 | |||
| d03564bce9 | |||
| 41409c39d5 | |||
| aa9c5107d0 | |||
| 7a141822e7 | |||
| a4fd301d5a | |||
| b2ef8b96ef | |||
| baa9e1003e | |||
| 5d17a586d5 | |||
| 36b97b2adc | |||
| f13bffa834 | |||
| 4394566ed3 | |||
| de95c24f66 | |||
| 6058cb9cff | |||
| 6b20d3e268 | |||
| ce037a437a | |||
| 1349dab6d4 | |||
| d3d31925ee | |||
| d9fa77c86b | |||
| dad01235b0 | |||
| 52d9c26076 | |||
| 1a0236237d | |||
| 58bb7a5ebc | |||
| efbab58bca | |||
| eedbc7a863 | |||
| 626cda63ff | |||
| 7b7a9d93aa | |||
| f5c68dac61 | |||
| 2b6b106771 | |||
| 047586883e | |||
| 0c1a25dd6b | |||
| 7840a9a713 | |||
| 28f96ffcd3 | |||
| 04b2663183 | |||
| 1a11027871 | |||
| 854a00803e | |||
| 11ae5f157f | |||
| ecfe05139e | |||
| 4a392e03a7 | |||
| a66037f886 | |||
| 58c399dcbe | |||
| f33e617f44 | |||
| 3e976c1026 | |||
| c15c75075c | |||
| bf8d988c75 | |||
| 6fb7af3d46 | |||
| 1c2860c180 | |||
| fc41f10c37 | |||
| dedf366866 | |||
| edb10096d6 | |||
| ba42a1e6c9 | |||
| f5f989d140 | |||
| a0058c104d | |||
| 7f1f322d04 | |||
| 57ac8d8771 | |||
| bcd0509eff | |||
| 624af87795 | |||
| 616df56657 | |||
| 4efd6abb56 | |||
| 8c657a4ccf | |||
| db582f6c88 | |||
| a7b861f83c | |||
| 19b1c7ae8b | |||
| 192cec68c7 | |||
| 3541522fc6 | |||
| 4afa66f3f3 | |||
| 7fde157184 | |||
| a1935e8299 | |||
| 50e993fd89 | |||
| a25ec6b0af | |||
| efeb99aaac | |||
| f19118432d | |||
| b69eac2886 | |||
| 4efc4e7f34 | |||
| 4755eb06e2 | |||
| e4dc1c0b4e | |||
| 89f54c2b4a | |||
| abf52f0d49 | |||
| 044df3f09c | |||
| 271829f9c1 | |||
| 13625b37a8 | |||
| cceadd2a3d | |||
| 781191196f | |||
| eb0cf27218 | |||
| 1706341283 | |||
| 71f46b3bff | |||
| 4c39798682 | |||
| b861aa385d | |||
| 156e8a2686 | |||
| f88acf1375 | |||
| be770d4607 | |||
| 7565f624c9 | |||
| 65d56297fd | |||
| 86caac4a1d | |||
| d382ac4fa2 | |||
| f9ceeaad44 | |||
| 0691293973 | |||
| 9491b48eb6 | |||
| 6c7f63270f | |||
| a62f793288 | |||
| 5fc715b7d0 | |||
| 901b54b829 | |||
| 0efa1127a3 | |||
| 28fc53799a | |||
| bdc4af7cd4 | |||
| c68996ff20 | |||
| f517a63cda | |||
| fefb9b490e | |||
| 383e1d5368 | |||
| 4e4ce36e76 | |||
| 19983a113e | |||
| 8eaac465ff | |||
| 17b2d744ba | |||
| 166e0d400a | |||
| 5cf409ce0b | |||
| e37e847a8b | |||
| 32c77d3743 | |||
| 45e368d9e7 | |||
| 97496302fb | |||
| 675959e615 | |||
| f641b282b6 | |||
| e92a05683c | |||
| 93bef2e144 | |||
| 44e331ae96 | |||
| f295c85668 | |||
| 6f843bface | |||
| 2dea6076f0 | |||
| 78c6c7b571 | |||
| a50bec9326 | |||
| 7d193d7de6 | |||
| 5bbb3e6a6c | |||
| 32c1ddab46 | |||
| 15198c32ea | |||
| 476503338f | |||
| ba6303dca0 | |||
| 23432465ac | |||
| 1f0b5cebeb | |||
| 1f83c84a7e | |||
| e209af9c57 | |||
| a714d312a8 | |||
| 4a776c1fe8 | |||
| da86f07cbe | |||
| 48e0ca887d | |||
| 0f3b6c4cec | |||
| 9ce9631135 | |||
| c73a5c3a7e | |||
| 62d35127b1 | |||
| 09f17caabe | |||
| c5355d7497 | |||
| d5142ad82c | |||
| 428b278c78 | |||
| 33c1e04934 | |||
| 6c075d80ba | |||
| 38713108e0 | |||
| 6b75fea813 | |||
| de6fc84fec | |||
| ecd276a3a0 | |||
| 86fbd0cebc | |||
| 6a97919f7a | |||
| f2e7e17bd1 | |||
| eedd6cee1d | |||
| 25e11fa9de | |||
| 4742600b86 | |||
| b3e4850413 | |||
| 1085f6c57a | |||
| fd8d13447c | |||
| 758a3d3f99 | |||
| fd8b4d94d6 | |||
| fa7dae8177 | |||
| 21638218c6 | |||
| a29a414e8a | |||
| 251c7f399e | |||
| 1da55f2011 | |||
| 856125edc2 | |||
| 32ef30ebd8 | |||
| 8972e42fee | |||
| 91f206aad0 | |||
| 5626babcdf | |||
| 98175152db | |||
| 817ade3e34 | |||
| ab5493f8c4 | |||
| 796bd99377 | |||
| a0dc60a403 | |||
| a92e58abf1 | |||
| e8f0793feb | |||
| 2d7eff8205 | |||
| 6412892985 | |||
| 69ae0ffc71 | |||
| 854be23cfb | |||
| 02e143217e | |||
| 4feec82b03 | |||
| 938ca648f1 | |||
| ee018ea287 | |||
| 8ab60ad000 | |||
| 17eaf34500 | |||
| 2485958f6e | |||
| 7ac3a22fa6 | |||
| 285f211f50 | |||
| e9ec424625 | |||
| 1e96477127 | |||
| 87ffa48265 | |||
| 8b64df435c | |||
| dcf88ee510 | |||
| b21a15376d | |||
| 3e87ed4f9b | |||
| a6cd9c955b | |||
| 86b52b76ed | |||
| 853f380e5d | |||
| 027686a3b5 | |||
| 79b87e59be | |||
| 3a37e55162 | |||
| 1ff40c0016 | |||
| 4f7c1021c8 | |||
| e687c78ba8 | |||
| 2c2f46a0d8 | |||
| 29fa565258 | |||
| 51f3d410c9 | |||
| a0d159da9d | |||
| 9edb58ee27 | |||
| a8de65ab0e | |||
| ef8a894489 | |||
| 992f388a56 | |||
| a54e7206a0 | |||
| c4e74b21fc | |||
| 7b9e07bdda | |||
| 19f5204253 | |||
| c4ad5c5f81 | |||
| e888118a20 | |||
| 7afce136f6 | |||
| 71c4dadeee | |||
| c6a95c99ef | |||
| a6d44685b3 | |||
| a8ce61dad9 | |||
| 0f47d1473d | |||
| 2134ee516a | |||
| 6c60306bee | |||
| 816748833c | |||
| cac0fca3bb | |||
| 5ed28b121e | |||
| c4180eba6f | |||
| 0065a1f81f | |||
| e1f3a6ada4 | |||
| ed7ffbcb13 | |||
| 6a8d9db407 | |||
| 81140837f1 | |||
| 70232645a8 | |||
| 4d3b3ef3df | |||
| d203314424 | |||
| b95da2d8d5 | |||
| a8c5ccda17 | |||
| 86b996637c | |||
| 1480d4a14e | |||
| 8586735ca8 | |||
| b8013afd64 | |||
| 812bc83939 | |||
| d6829744bb | |||
| a7a03544b6 | |||
| ffa8729d10 | |||
| ce291d3a77 | |||
| 297dbad60c | |||
| 54982218f5 | |||
| c14f4df494 | |||
| f53f23aa4b | |||
| 10c59fb4b4 | |||
| a5cffc5ea4 | |||
| 200c299ce7 | |||
| 8547276549 | |||
| b09afcf725 | |||
| a2860be7a3 | |||
| de9d026e3a | |||
| 27a1baae7b | |||
| f2584309d1 | |||
| 831fd09615 | |||
| e0e22a09c0 | |||
| 60c6e0632d | |||
| 6da13c4fc0 | |||
| ec330aea69 | |||
| 318682b63a | |||
| dd53d7ff0a | |||
| b03207d287 | |||
| 7db87b7c36 | |||
| 54d0aaca04 | |||
| 352aa886c8 | |||
| 8fada629f0 | |||
| 34bb3cd0bd | |||
| 5555a67422 | |||
| 3631d35946 | |||
| 460d4a5ac1 | |||
| b7b5db18fd | |||
| 58d877af6b | |||
| ab20e0d8f2 | |||
| 42e55c51b0 | |||
| ca82c24c76 | |||
| 66874e7a85 | |||
| e365a94f71 | |||
| 81298e5980 | |||
| 4eb920553b | |||
| 1be5408302 | |||
| 1e9a7b037a | |||
| e79ed438ef | |||
| ff658409be | |||
| 9c26353fed | |||
| 2a3ce9549a | |||
| 8093f55b4f | |||
| 4a46892486 | |||
| 35fafc39a8 | |||
| 3976970bb5 | |||
| 1a3ab49849 | |||
| 32d75ae23d | |||
| 29c6844390 | |||
| 9c6eee59a8 | |||
| 4ff2be67d3 | |||
| 47d3eb13ce | |||
| 5de3043544 | |||
| 4b1d544c6b | |||
| 6cb8fe3a6e | |||
| 1acf2e044e | |||
| 9e9e960fa3 | |||
| e767873ff1 | |||
| b57762afed | |||
| 24526c5a48 | |||
| ed1b62ec50 | |||
| 98e727cf55 | |||
| 6c4ed850b0 | |||
| db6b9da945 | |||
| 52c930624a | |||
| b7ddcb5419 | |||
| 34f27c8e8d | |||
| 3f7055327b | |||
| 51fb13b448 | |||
| 5273990305 | |||
| a44badddaa | |||
| 3e8d186dc8 | |||
| 51f8a0541d | |||
| 808da0a3a7 | |||
| 1c522840cd | |||
| 6fb8506551 | |||
| 7bf0438960 | |||
| fe02ec1852 | |||
| 4020925af1 | |||
| 863b42f516 | |||
| 7f6b03be27 | |||
| 880770b718 | |||
| ed74f98919 | |||
| 4a1e42d5ee | |||
| b21e608eea | |||
| 671ec2fba6 | |||
| 293a89575a | |||
| 542812eb74 | |||
| e44cb715d0 | |||
| f7a1a1c851 | |||
| 757c08cd02 | |||
| 15fcd50151 | |||
| 867a747853 | |||
| 02dfbb54b6 | |||
| c42c6c8dfe | |||
| 316d568cbc | |||
| 0b72b1f1b3 | |||
| 25b52def92 | |||
| ab67ea48f0 | |||
| 912c1b91e4 | |||
| d4f1d21634 | |||
| 6c0e7c2e64 | |||
| e054a50b55 | |||
| ff005e6398 | |||
| 74cc8e7dda | |||
| 254d70a787 | |||
| 28b728822c | |||
| 5a53471bcd | |||
| a3fee5d77c | |||
| 6f961225d7 | |||
| 1b0b4f7505 | |||
| fac66fafbd | |||
| b9d6ac3047 | |||
| e7c723de46 | |||
| 10629b253c | |||
| dace6ad486 | |||
| 4c70f35714 | |||
| 7b4d13c16f | |||
| 0e561ad24e | |||
| d09d8223c9 | |||
| 6dafde9735 |
@@ -0,0 +1,158 @@
|
||||
{
|
||||
"$schema": "https://www.schemastore.org/all-contributors.json",
|
||||
"projectName": "community",
|
||||
"projectOwner": "InkCanvasForClass",
|
||||
"files": [
|
||||
"README.md"
|
||||
],
|
||||
"commitType": "docs",
|
||||
"commitConvention": "angular",
|
||||
"contributorsPerLine": 5,
|
||||
"contributors": [
|
||||
{
|
||||
"login": "CJKmkp",
|
||||
"name": "CJK_mkp",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/113243675?v=4",
|
||||
"profile": "https://github.com/CJKmkp",
|
||||
"contributions": [
|
||||
"maintenance",
|
||||
"doc",
|
||||
"code",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "CreeperAWA",
|
||||
"name": "CreeperAWA",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/134939494?v=4",
|
||||
"profile": "https://github.com/CreeperAWA",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "2-2-3-trimethylpentane",
|
||||
"name": "2,2,3-三甲基戊烷",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/141403762?v=4",
|
||||
"profile": "https://github.com/2-2-3-trimethylpentane",
|
||||
"contributions": [
|
||||
"blog",
|
||||
"doc",
|
||||
"design",
|
||||
"test",
|
||||
"tutorial",
|
||||
"video"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Alan-CRL",
|
||||
"name": "Alan-CRL",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/92425617?v=4",
|
||||
"profile": "https://github.com/Alan-CRL",
|
||||
"contributions": [
|
||||
"code",
|
||||
"infra",
|
||||
"doc",
|
||||
"financial"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MKStoler1024",
|
||||
"name": "MKStoler1024",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/158786854?v=4",
|
||||
"profile": "https://github.com/MKStoler1024",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "awesome-iwb",
|
||||
"name": "Awesome Iwb",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/184760810?v=4",
|
||||
"profile": "https://github.com/awesome-iwb",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "PrefacedCorg",
|
||||
"name": "PrefacedCorg",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/129855423?v=4",
|
||||
"profile": "https://github.com/PrefacedCorg",
|
||||
"contributions": [
|
||||
"code",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Jursin",
|
||||
"name": "Jursin",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/127487914?v=4",
|
||||
"profile": "http://blog.jursin.top",
|
||||
"contributions": [
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Tayasui-rainnya",
|
||||
"name": "tayasui rainnya!",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/156585442?v=4",
|
||||
"profile": "https://github.com/Tayasui-rainnya",
|
||||
"contributions": [
|
||||
"design",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "doudou0720",
|
||||
"name": "doudou0720",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/98651603?v=4",
|
||||
"profile": "https://github.com/doudou0720",
|
||||
"contributions": [
|
||||
"code",
|
||||
"blog",
|
||||
"infra"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "PANDAJSR",
|
||||
"name": "PANDAJSR",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/170189561?v=4",
|
||||
"profile": "https://github.com/PANDAJSR",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "LiuYan-xwx",
|
||||
"name": "流焰xwx",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/66517348?v=4",
|
||||
"profile": "http://lyxwx.top",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Super-Yyt",
|
||||
"name": "Super-Yyt",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/206630707?v=4",
|
||||
"profile": "https://github.com/Super-Yyt",
|
||||
"contributions": [
|
||||
"infra",
|
||||
"blog"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Hao3288",
|
||||
"name": "NoobHao",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/119276078?v=4",
|
||||
"profile": "https://github.com/Hao3288",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"repoType": "github"
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"image": "mcr.microsoft.com/devcontainers/dotnet",
|
||||
"postCreateCommand": "dotnet restore",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-dotnettools.csdevkit",
|
||||
"ms-dotnettools.csharp"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
name: Bug 报告 | Bug Report
|
||||
description: 反馈软件缺陷或异常 | Report a bug to help us improve
|
||||
type: Bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢你的反馈!请详细填写以下内容,便于我们定位问题。
|
||||
Thank you for your feedback! Please fill out the following information to help us locate the issue.
|
||||
|
||||
在报告问题之前,请确保你的软件已经更新到最新Beta版本,否则我们可能会无条件直接关闭该Issue,感谢配合!
|
||||
Before reporting the issue, please make sure your software has been updated to the latest Beta version. Otherwise, we may unconditionally close this Issue without any further notice. Thank you for your cooperation!
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: 软件版本 | App Version
|
||||
description: 可在设置中的“关于”界面查看 | You can find it on the "About" interface in the settings
|
||||
placeholder: 例如 v1.2.3 | e.g. v1.2.3
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: 操作系统及版本 | OS & Version
|
||||
placeholder: 例如 Windows 10 22H2 64位 | e.g. Windows 10 22H2 64bit
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 问题描述 | Description
|
||||
description: 简要描述遇到的问题 | Briefly describe the problem
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: 复现步骤 | Steps to Reproduce
|
||||
description: 如何复现该问题?如有必要可附截图/录屏/触发该问题的文件 | How to reproduce this bug? Screenshots/recordings/specific files if needed
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: 期望结果 | Expected Behavior
|
||||
description: 你期望的正确行为或结果 | What did you expect to happen?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: extra
|
||||
attributes:
|
||||
label: 其他补充信息 | Additional Info
|
||||
description: 其他相关信息(如日志文件、崩溃日志文件、配置文件、特殊环境等)| Any other context, logs, crash logs, configs, special environment, etc.
|
||||
validations:
|
||||
required: false
|
||||
- type: upload
|
||||
id: files
|
||||
attributes:
|
||||
label: 上传有关文件 | Upload relevant files
|
||||
description: "你可以在此处上传相关文件 | You can upload relevant files here."
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,44 @@
|
||||
name: 功能请求 | Feature Request
|
||||
description: 提出你对本项目的功能建议 | Suggest an idea for this project
|
||||
type: Feature
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢你的建议!请详细描述你的需求。
|
||||
Thank you for your suggestion! Please describe your needs in detail.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 功能描述 | Description
|
||||
description: 请描述你希望添加的功能 | Describe the feature you want
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: motivation
|
||||
attributes:
|
||||
label: 需求动机 | Motivation
|
||||
description: 为什么需要这个功能?| Why do you need this feature?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: design
|
||||
attributes:
|
||||
label: 期望设计 | Expected Design
|
||||
description: (可选)描述或画出你期望的界面或交互 | (Optional) Describe or sketch the expected UI/UX
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: extra
|
||||
attributes:
|
||||
label: 其他补充信息 | Additional Info
|
||||
description: 其他补充说明或建议 | Any other context or suggestions
|
||||
validations:
|
||||
required: false
|
||||
- type: upload
|
||||
id: files
|
||||
attributes:
|
||||
label: 上传有关文件 | Upload relevant files
|
||||
description: "你可以在此处上传相关文件 | You can upload relevant files here."
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: (Markdown Template) Bug 报告 | Bug Report
|
||||
about: 反馈软件缺陷或异常 | Report a bug to help us improve
|
||||
title: "[Version x.x.x] <your title>"
|
||||
type: Bug
|
||||
---
|
||||
|
||||
<!---请注意,你正在使用Markdown格式的Issue模板,如果你删除该模板的框架、更改问题的tag/类型/受理人或者不按照规范填写,你的Issue可能被直接关闭,如果你对Markdown不熟悉,请使用位于该选项下方的反馈入口继续反馈,感谢配合!-->
|
||||
|
||||
<!---感谢你的反馈!请详细填写以下内容,便于我们定位问题。-->
|
||||
<!---Thank you for your feedback! Please fill out the following information to help us locate the issue.-->
|
||||
|
||||
<!---在报告问题之前,请确保你的软件已经更新到最新Beta版本,否则我们可能会无条件直接关闭该Issue,感谢配合!-->
|
||||
<!---Before reporting the issue, please make sure your software has been updated to the latest Beta version. Otherwise, we may unconditionally close this Issue without any further notice. Thank you for your cooperation!-->
|
||||
|
||||
### 软件版本 | App Version (必填 | Required)
|
||||
<!---可在设置中的"关于"界面查看 | You can find it on the "About" interface in the settings-->
|
||||
<!---例如 v1.2.3 | e.g. v1.2.3-->
|
||||
|
||||
### 操作系统及版本 | OS & Version (必填 | Required)
|
||||
<!---例如 Windows 10 22H2 64位 | e.g. Windows 10 22H2 64bit-->
|
||||
|
||||
### 问题描述 | Description (必填 | Required)
|
||||
<!---简要描述遇到的问题 | Briefly describe the problem-->
|
||||
|
||||
### 复现步骤 | Steps to Reproduce (必填 | Required)
|
||||
<!---如何复现该问题?如有必要可附截图/录屏/触发该问题的文件 | How to reproduce this bug? Screenshots/recordings/specific files if needed-->
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
### 期望结果 | Expected Behavior (必填 | Required)
|
||||
<!---你期望的正确行为或结果 | What did you expect to happen?-->
|
||||
|
||||
### 其他补充信息 | Additional Info (可选 | Optional)
|
||||
<!---其他相关信息(如日志文件、崩溃日志文件、配置文件、特殊环境等)| Any other context, logs, crash logs, configs, special environment, etc.-->
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: (Markdown Template) 功能请求 | Feature Request
|
||||
about: 提出你对本项目的功能建议 | Suggest an idea for this project
|
||||
title: "[Feature Request] "
|
||||
type: Feature
|
||||
---
|
||||
|
||||
<!---请注意,你正在使用Markdown格式的Issue模板,如果你删除该模板的框架、更改问题的tag/类型/受理人或者不按照规范填写,你的Issue可能被直接关闭,如果你对Markdown不熟悉,请使用位于该选项下方的反馈入口继续反馈,感谢配合!-->
|
||||
|
||||
<!---感谢你的建议!请详细描述你的需求。-->
|
||||
<!---Thank you for your suggestion! Please describe your needs in detail.-->
|
||||
|
||||
### 功能描述 | Description (必填 | Required)
|
||||
<!---请描述你希望添加的功能 | Describe the feature you want-->
|
||||
|
||||
### 需求动机 | Motivation (必填 | Required)
|
||||
<!---为什么需要这个功能?| Why do you need this feature?-->
|
||||
|
||||
### 期望设计 | Expected Design (可选 | Optional)
|
||||
<!---描述或画出你期望的界面或交互 | Describe or sketch the expected UI/UX-->
|
||||
|
||||
### 其他补充信息 | Additional Info (可选 | Optional)
|
||||
<!---其他补充说明或建议 | Any other context or suggestions-->
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
|
||||
## Reproduction
|
||||
|
||||
|
||||
## Expected behavior
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
||||
## Additional context
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Description
|
||||
@@ -1,35 +1,106 @@
|
||||
name: .NET Build
|
||||
name: .NET Build & Package
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main,beta ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: [ net6 ]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-${{ github.head_ref || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
build-and-package:
|
||||
name: Build & Package
|
||||
runs-on: windows-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
architecture: [AnyCPU, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Setup MSbuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
- name: Setup NuGet
|
||||
uses: NuGet/setup-nuget@v2.0.1
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Restore NuGet Packages
|
||||
run: nuget restore "Ink Canvas.sln"
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v3
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
cache: true
|
||||
cache-dependency-path: '**/packages.lock.json'
|
||||
|
||||
- name: Restore Package
|
||||
run: dotnet restore "Ink Canvas.sln" --locked-mode
|
||||
|
||||
- name: Build the Solution
|
||||
env:
|
||||
DLASS_SENTRY_DSN: ${{ secrets.DLASS_SENTRY_DSN }}
|
||||
run: msbuild /p:platform="${{ matrix.architecture }}" /p:configuration="Debug" /p:GitFlow="$GITFLOW" "Ink Canvas/InkCanvasForClass.csproj" /m /p:UseMultiToolTask=true /p:EnforceProcessCountAcrossBuilds=true /verbosity:minimal -maxcpucount /p:RunAnalyzers=false
|
||||
|
||||
- name: Build the Solution
|
||||
run: |
|
||||
msbuild -t:restore /p:GitFlow="Github Action"
|
||||
msbuild /p:platform="Any CPU" /p:configuration="Release" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj"
|
||||
- name: Check if exe file is generated
|
||||
id: check-exe
|
||||
run: |
|
||||
$exePath = "Ink Canvas\bin\Debug\${{ matrix.architecture }}\net6.0-windows10.0.19041.0\InkCanvasForClass.exe"
|
||||
|
||||
if (Test-Path $exePath) {
|
||||
echo "build_success=true" >> $env:GITHUB_OUTPUT
|
||||
} else {
|
||||
echo "build_success=false" >> $env:GITHUB_OUTPUT
|
||||
|
||||
if ("${{ github.event_name }}" -eq "workflow_dispatch") {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
- name: Upload to artifact
|
||||
uses: actions/upload-artifact@v4.5.0
|
||||
with:
|
||||
name: InkCanvasForClass
|
||||
path: "Ink Canvas/bin/Any CPU/Release/net472/"
|
||||
- name: Create Package (if build succeeded)
|
||||
id: create-archive
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
env:
|
||||
GITHUB_SHA: ${{ github.sha }}
|
||||
GITHUB_RUN_NUMBER: ${{ github.run_number }}
|
||||
run: |
|
||||
$shortSha = $env:GITHUB_SHA.Substring(0, 7)
|
||||
$version = "debug-$shortSha-$env:GITHUB_RUN_NUMBER"
|
||||
echo "archive_name=$version" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Upload Artifact (if build succeeded)
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: InkCanvasForClass.CE.debug.${{ matrix.architecture }}
|
||||
path: "Ink Canvas/bin/Debug/${{ matrix.architecture }}/net6.0-windows10.0.19041.0/*"
|
||||
|
||||
- name: Create Summary
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
echo "# Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "${{ steps.check-exe.outputs.build_success }}" = "true" ]; then
|
||||
echo "## ✅ Build Successful" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Run:** #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version:** ${{ steps.create-archive.outputs.archive_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Architecture:** ${{ matrix.architecture }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[Download Artifact](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[Nightly.link Download](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/InkCanvasForClass.CE.debug.${{ matrix.architecture }}.zip) \([GhProxy Fastly Mirror](https://cdn.gh-proxy.com/nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/InkCanvasForClass.CE.debug.${{ matrix.architecture }}.zip) / [GhProxy Mirror](https://gh-proxy.com/nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/InkCanvasForClass.CE.debug.${{ matrix.architecture }}.zip)\)" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "## ❌ Build Failed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Event:** ${{ github.event_name }} (${{ github.event.action || 'N/A' }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Architecture:** ${{ matrix.architecture }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Run:** #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Check build logs for details." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
name: PR Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
branches: [ main, net6 ]
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-${{ github.head_ref || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-and-package:
|
||||
name: Build & Package
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
architecture: [AnyCPU, x86]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v3
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v5
|
||||
|
||||
- name: Restore Package
|
||||
run: dotnet restore "Ink Canvas.sln" --locked-mode
|
||||
|
||||
- name: Build the Solution
|
||||
run: msbuild /p:platform="${{ matrix.architecture }}" /p:configuration="Debug" /p:GitFlow="$GITFLOW" "Ink Canvas/InkCanvasForClass.csproj" /m /p:UseMultiToolTask=true /p:EnforceProcessCountAcrossBuilds=true /verbosity:minimal -maxcpucount /p:RunAnalyzers=false
|
||||
|
||||
- name: Check if exe file is generated
|
||||
id: check-exe
|
||||
run: |
|
||||
$exePath = "Ink Canvas\bin\Debug\${{ matrix.architecture }}\net6.0-windows10.0.19041.0\InkCanvasForClass.exe"
|
||||
|
||||
if (Test-Path $exePath) {
|
||||
echo "build_success=true" >> $env:GITHUB_OUTPUT
|
||||
} else {
|
||||
echo "build_success=false" >> $env:GITHUB_OUTPUT
|
||||
|
||||
if ("${{ github.event_name }}" -eq "workflow_dispatch") {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
- name: Create Package (if build succeeded)
|
||||
id: create-archive
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
env:
|
||||
GITHUB_SHA: ${{ github.sha }}
|
||||
GITHUB_RUN_NUMBER: ${{ github.run_number }}
|
||||
run: |
|
||||
$shortSha = $env:GITHUB_SHA.Substring(0, 7)
|
||||
$version = "debug-$shortSha-$env:GITHUB_RUN_NUMBER"
|
||||
echo "archive_name=$version" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Upload Artifact (if build succeeded)
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: InkCanvasForClass.CE.debug.${{ matrix.architecture }}
|
||||
path: "Ink Canvas/bin/Debug/${{ matrix.architecture }}/net6.0-windows10.0.19041.0/*"
|
||||
|
||||
|
||||
- name: Create Summary
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
echo "# Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "${{ steps.check-exe.outputs.build_success }}" = "true" ]; then
|
||||
echo "## ✅ Build Successful" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Run:** #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version:** ${{ steps.create-archive.outputs.archive_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Architecture:** ${{ matrix.architecture }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[Download Artifact](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[Nightly.link Download](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/InkCanvasForClass.CE.debug.${{ matrix.architecture }}.zip) \([GhProxy Fastly Mirror](https://cdn.gh-proxy.com/nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/InkCanvasForClass.CE.debug.${{ matrix.architecture }}.zip) / [GhProxy Mirror](https://gh-proxy.com/nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/InkCanvasForClass.CE.debug.${{ matrix.architecture }}.zip)\)" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "## ❌ Build Failed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Event:** ${{ github.event_name }} (${{ github.event.action || 'N/A' }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Architecture:** ${{ matrix.architecture }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Run:** #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Check build logs for details." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
@@ -0,0 +1,754 @@
|
||||
name: Pre-release and Changelog
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_type:
|
||||
description: 'Version bump type'
|
||||
required: true
|
||||
default: 'patch'
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
- build
|
||||
prerelease:
|
||||
description: 'Create as pre-release'
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
draft:
|
||||
description: 'Create as draft release'
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-${{ github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: windows-latest
|
||||
outputs:
|
||||
tag_name: ${{ steps.get_tag.outputs.tag_name }}
|
||||
version: ${{ steps.get_tag.outputs.version }}
|
||||
is_prerelease: ${{ steps.release_type.outputs.is_prerelease }}
|
||||
changelog: ${{ steps.read_changelog.outputs.changelog }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
# ========== 获取当前版本 ==========
|
||||
- name: Get current version from Git tag
|
||||
id: get_version
|
||||
run: |
|
||||
# 获取最新的tag
|
||||
$latestTag = git describe --tags --abbrev=0 2>$null
|
||||
if ($latestTag) {
|
||||
$version = $latestTag
|
||||
echo "Found latest tag: $latestTag"
|
||||
} else {
|
||||
# 如果没有tag,使用默认值
|
||||
$version = "1.0.0.0"
|
||||
echo "No tag found, using default version: $version"
|
||||
}
|
||||
echo "current_version=$version" >> $env:GITHUB_OUTPUT
|
||||
echo "Current version: $version"
|
||||
|
||||
# ========== 处理版本号和标签名 ==========
|
||||
- name: Get tag name and version
|
||||
id: get_tag
|
||||
run: |
|
||||
if ("${{ github.event_name }}" -eq "push") {
|
||||
# 从 push tag 事件获取原始标签名
|
||||
$tagName = "${{ github.ref }}".Replace("refs/tags/", "")
|
||||
$cleanVersion = $tagName
|
||||
|
||||
echo "tag_name=$tagName" >> $env:GITHUB_OUTPUT
|
||||
echo "version=$cleanVersion" >> $env:GITHUB_OUTPUT
|
||||
echo "Using pushed tag: $tagName, version: $cleanVersion"
|
||||
} else {
|
||||
# 从 workflow_dispatch 计算新版本(4位格式)
|
||||
$currentVersion = "${{ steps.get_version.outputs.current_version }}"
|
||||
$versionParts = $currentVersion.Split('.')
|
||||
|
||||
# 确保版本号格式正确(至少4部分)
|
||||
if ($versionParts.Length -ge 4) {
|
||||
$major = [int]$versionParts[0]
|
||||
$minor = [int]$versionParts[1]
|
||||
$patch = [int]$versionParts[2]
|
||||
$build = [int]$versionParts[3]
|
||||
} else {
|
||||
# 如果版本号格式不正确,补充为4位
|
||||
if ($versionParts.Length -ge 3) {
|
||||
$major = [int]$versionParts[0]
|
||||
$minor = [int]$versionParts[1]
|
||||
$patch = [int]$versionParts[2]
|
||||
$build = 0
|
||||
} else {
|
||||
# 如果版本号格式不正确,抛出错误
|
||||
echo "Error: Invalid version format. Expected format: x.y.z.w (e.g., 1.7.18.0)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
$versionType = "${{ github.event.inputs.version_type }}"
|
||||
$isPrerelease = "${{ github.event.inputs.prerelease }}" -eq "true"
|
||||
|
||||
switch ($versionType) {
|
||||
"major" {
|
||||
$major++
|
||||
$minor = 0
|
||||
$patch = 0
|
||||
$build = 0
|
||||
}
|
||||
"minor" {
|
||||
$minor++
|
||||
$patch = 0
|
||||
$build = 0
|
||||
}
|
||||
"patch" {
|
||||
$patch++
|
||||
$build = 0
|
||||
}
|
||||
"build" {
|
||||
$build++
|
||||
}
|
||||
}
|
||||
|
||||
# 生成新版本号(4位格式,如1.7.18.0)
|
||||
$newVersion = "$major.$minor.$patch.$build"
|
||||
|
||||
# 根据是否为预发布决定版本号最后一位
|
||||
# 如果是预发布,确保最后一位不为0(使用1)
|
||||
if ($isPrerelease -and $build -eq 0) {
|
||||
$build = 1
|
||||
$newVersion = "$major.$minor.$patch.$build"
|
||||
}
|
||||
$tagName = $newVersion
|
||||
|
||||
echo "tag_name=$tagName" >> $env:GITHUB_OUTPUT
|
||||
echo "version=$newVersion" >> $env:GITHUB_OUTPUT
|
||||
echo "New tag: $tagName, version: $newVersion"
|
||||
}
|
||||
|
||||
- name: Determine release type
|
||||
id: release_type
|
||||
run: |
|
||||
if ("${{ github.event_name }}" -eq "push") {
|
||||
# 根据版本号最后一位确定是否为预发布版本
|
||||
# 最后一位为0表示正式版本,非0表示预发布版本
|
||||
$version = "${{ steps.get_tag.outputs.version }}"
|
||||
$versionParts = $version.Split('.')
|
||||
if ($versionParts.Length -ge 4) {
|
||||
$build = [int]$versionParts[3]
|
||||
if ($build -eq 0) {
|
||||
echo "is_prerelease=false" >> $env:GITHUB_OUTPUT
|
||||
echo "This is a release"
|
||||
} else {
|
||||
echo "is_prerelease=true" >> $env:GITHUB_OUTPUT
|
||||
echo "This is a pre-release (beta)"
|
||||
}
|
||||
} else {
|
||||
echo "is_prerelease=false" >> $env:GITHUB_OUTPUT
|
||||
echo "This is a release (invalid version format)"
|
||||
}
|
||||
} else {
|
||||
# workflow_dispatch 方式
|
||||
echo "is_prerelease=${{ github.event.inputs.prerelease }}" >> $env:GITHUB_OUTPUT
|
||||
}
|
||||
|
||||
# ========== 使用 git-cliff 生成变更日志 ==========
|
||||
- name: Generate changelog with git-cliff (for pushed tag)
|
||||
if: github.event_name == 'push'
|
||||
id: git_cliff_tag
|
||||
uses: orhun/git-cliff-action@v4
|
||||
with:
|
||||
config: build/cliff.toml # 使用项目build目录的 cliff.toml 配置
|
||||
args: --latest --tag ${{ steps.get_tag.outputs.tag_name }} --output CHANGELOG.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate changelog with git-cliff (for workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
id: git_cliff_unreleased
|
||||
uses: orhun/git-cliff-action@v4
|
||||
with:
|
||||
config: build/cliff.toml
|
||||
args: --unreleased --tag ${{ steps.get_tag.outputs.tag_name }} --output CHANGELOG.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Read changelog content
|
||||
id: read_changelog
|
||||
run: |
|
||||
$changelogContent = Get-Content -Path CHANGELOG.md -Raw
|
||||
echo "changelog<<EOF" >> $env:GITHUB_OUTPUT
|
||||
echo $changelogContent >> $env:GITHUB_OUTPUT
|
||||
echo "EOF" >> $env:GITHUB_OUTPUT
|
||||
|
||||
build:
|
||||
needs: prepare
|
||||
if: success()
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
architecture: [AnyCPU, x86]
|
||||
outputs:
|
||||
archive_name: ${{ steps.create_archive.outputs.archive_name }}
|
||||
zip_size: ${{ steps.calculate_size.outputs.zip_size }}
|
||||
installer_size: ${{ steps.calculate_installer_size.outputs.installer_size }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v3
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
cache: true
|
||||
cache-dependency-path: '**/packages.lock.json'
|
||||
|
||||
- name: Restore Package
|
||||
run: dotnet restore "Ink Canvas.sln" --locked-mode
|
||||
|
||||
- name: Build the Solution (Release)
|
||||
env:
|
||||
DLASS_SENTRY_DSN: ${{ secrets.DLASS_SENTRY_DSN }}
|
||||
run: |
|
||||
msbuild /p:platform="${{ matrix.architecture }}" /p:configuration="Release" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj" /m /p:UseMultiToolTask=true /p:EnforceProcessCountAcrossBuilds=true /verbosity:minimal -maxcpucount /p:RunAnalyzers=false
|
||||
|
||||
- name: Check if exe file is generated
|
||||
id: check-exe
|
||||
run: |
|
||||
$exePath = "Ink Canvas\bin\Release\${{ matrix.architecture }}\net6.0-windows10.0.19041.0\InkCanvasForClass.exe"
|
||||
|
||||
if (Test-Path $exePath) {
|
||||
echo "build_success=true" >> $env:GITHUB_OUTPUT
|
||||
} else {
|
||||
echo "build_success=false" >> $env:GITHUB_OUTPUT
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Install Inno Setup Unofficial Language Files
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
run: |
|
||||
# 创建临时目录用于下载文件
|
||||
New-Item -ItemType Directory -Path "temp_lang" -Force
|
||||
|
||||
# 下载英语英国版语言文件
|
||||
Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/refs/heads/main/Files/Languages/Unofficial/EnglishBritish.isl" -OutFile "temp_lang/EnglishBritish.isl"
|
||||
|
||||
# 下载简体中文版语言文件
|
||||
Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/refs/heads/main/Files/Languages/Unofficial/ChineseSimplified.isl" -OutFile "temp_lang/ChineseSimplified.isl"
|
||||
|
||||
# 将文件移动到 Inno Setup 的语言目录
|
||||
Move-Item -Path "temp_lang/EnglishBritish.isl" -Destination "C:/Program Files (x86)/Inno Setup 6/Languages/EnglishBritish.isl" -Force
|
||||
Move-Item -Path "temp_lang/ChineseSimplified.isl" -Destination "C:/Program Files (x86)/Inno Setup 6/Languages/ChineseSimplified.isl" -Force
|
||||
|
||||
# 清理临时目录
|
||||
Remove-Item -Path "temp_lang" -Recurse -Force
|
||||
|
||||
- name: Create Release Archive
|
||||
id: create_archive
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$architecture = "${{ matrix.architecture }}"
|
||||
|
||||
# 根据架构生成文件名后缀
|
||||
if ($architecture -eq "AnyCPU") {
|
||||
$suffix = "-x64"
|
||||
} else {
|
||||
$suffix = ""
|
||||
}
|
||||
|
||||
$archiveName = "InkCanvasForClass.CE.$version$suffix.zip"
|
||||
|
||||
# 创建发布目录
|
||||
New-Item -ItemType Directory -Path "release" -Force
|
||||
|
||||
# 复制发布文件(使用架构特定的路径)
|
||||
Copy-Item "Ink Canvas\bin\Release\$architecture\net6.0-windows10.0.19041.0\*" "release/" -Recurse -Force
|
||||
|
||||
# 创建压缩包
|
||||
Compress-Archive -Path "release/*" -DestinationPath $archiveName -Force
|
||||
|
||||
echo "archive_name=$archiveName" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Prepare Inno Setup script
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$architecture = "${{ matrix.architecture }}"
|
||||
|
||||
# 更新 ISS 文件中的版本信息
|
||||
$issPath = "build/InkCanvasForClass CE.iss"
|
||||
$issContent = Get-Content -Path $issPath -Raw
|
||||
|
||||
# 替换版本信息
|
||||
$issContent = $issContent -replace '#define MyAppVersion ".*"', "#define MyAppVersion `"$version`""
|
||||
|
||||
# 替换源文件路径为相对路径(考虑到ISS文件在build目录下,需要返回上级目录)
|
||||
$issContent = $issContent -replace 'Source: ".*\\{#MyAppExeName}";', 'Source: "../release/{#MyAppExeName}";'
|
||||
$issContent = $issContent -replace 'Source: ".*\\InkCanvasForClass.exe.config";', 'Source: "../release/InkCanvasForClass.exe.config";'
|
||||
|
||||
# 更新输出目录为当前目录
|
||||
$issContent = $issContent -replace 'OutputDir=.*', 'OutputDir=.'
|
||||
|
||||
# 更新默认安装目录
|
||||
$issContent = $issContent -replace 'DefaultDirName=.*', 'DefaultDirName={autopf}/{#MyAppName}'
|
||||
|
||||
# 更新许可证文件路径为相对路径(考虑到ISS文件在build目录下,需要返回上级目录)
|
||||
$issContent = $issContent -replace 'LicenseFile=.*', 'LicenseFile=../LICENSE'
|
||||
|
||||
# 保存修改后的 ISS 文件
|
||||
$issContent | Set-Content -Path $issPath -Encoding UTF8
|
||||
|
||||
- name: Build MSI installer with Inno Setup
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
uses: Minionguyjpro/Inno-Setup-Action@v1.2.7
|
||||
with:
|
||||
path: build/InkCanvasForClass CE.iss
|
||||
options: /O.
|
||||
|
||||
- name: Rename installer file
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$architecture = "${{ matrix.architecture }}"
|
||||
|
||||
# 根据架构生成文件名后缀
|
||||
if ($architecture -eq "AnyCPU") {
|
||||
$suffix = "-x64"
|
||||
} else {
|
||||
$suffix = ""
|
||||
}
|
||||
|
||||
$setupFile = "InkCanvasForClass CE Setup.exe"
|
||||
$newSetupName = "InkCanvasForClass.CE.$version$suffix.Setup.exe"
|
||||
|
||||
if (Test-Path $setupFile) {
|
||||
Rename-Item -Path $setupFile -NewName $newSetupName
|
||||
} else {
|
||||
Write-Error "Setup file not found: $setupFile"
|
||||
}
|
||||
|
||||
- name: Calculate archive size
|
||||
id: calculate_size
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$architecture = "${{ matrix.architecture }}"
|
||||
|
||||
# 根据架构生成文件名后缀
|
||||
if ($architecture -eq "AnyCPU") {
|
||||
$suffix = "-x64"
|
||||
} else {
|
||||
$suffix = ""
|
||||
}
|
||||
|
||||
$archiveName = "InkCanvasForClass.CE.$version$suffix.zip"
|
||||
|
||||
# 获取文件大小(字节)
|
||||
$fileSize = (Get-Item $archiveName).Length
|
||||
|
||||
echo "zip_size=$fileSize" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Calculate installer size
|
||||
id: calculate_installer_size
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$architecture = "${{ matrix.architecture }}"
|
||||
|
||||
# 根据架构生成文件名后缀
|
||||
if ($architecture -eq "AnyCPU") {
|
||||
$suffix = "-x64"
|
||||
} else {
|
||||
$suffix = ""
|
||||
}
|
||||
|
||||
$installerName = "InkCanvasForClass.CE.$version$suffix.Setup.exe"
|
||||
|
||||
if (Test-Path $installerName) {
|
||||
# 获取文件大小(字节)
|
||||
$fileSize = (Get-Item $installerName).Length
|
||||
|
||||
echo "installer_size=$fileSize" >> $env:GITHUB_OUTPUT
|
||||
} else {
|
||||
Write-Error "Installer file not found: $installerName"
|
||||
}
|
||||
|
||||
- name: Upload Build Artifacts
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$architecture = "${{ matrix.architecture }}"
|
||||
|
||||
# 根据架构生成文件名后缀
|
||||
if ($architecture -eq "AnyCPU") {
|
||||
$suffix = "-x64"
|
||||
} else {
|
||||
$suffix = ""
|
||||
}
|
||||
|
||||
$zipFile = "InkCanvasForClass.CE.$version$suffix.zip"
|
||||
$setupFile = "InkCanvasForClass.CE.$version$suffix.Setup.exe"
|
||||
|
||||
echo "zip_file=$zipFile" >> $env:GITHUB_OUTPUT
|
||||
echo "setup_file=$setupFile" >> $env:GITHUB_OUTPUT
|
||||
id: get_file_names
|
||||
|
||||
- name: Upload Build Artifacts
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: build-files-${{ needs.prepare.outputs.version }}-${{ matrix.architecture }}
|
||||
path: |
|
||||
${{ steps.get_file_names.outputs.zip_file }}
|
||||
${{ steps.get_file_names.outputs.setup_file }}
|
||||
|
||||
- name: Create Build Summary
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
echo "# Release Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "${{ steps.check-exe.outputs.build_success }}" = "true" ]; then
|
||||
echo "## ✅ Release Build Successful" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version:** ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Tag:** \`${{ needs.prepare.outputs.tag_name }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Release Type:** ${{ needs.prepare.outputs.is_prerelease == 'true' && 'Pre-release' || 'Release' }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Architecture:** ${{ matrix.architecture }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Run:** #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ -n "${{ steps.calculate_size.outputs.zip_size }}" ]; then
|
||||
echo "**Archive Size:** ${{ steps.calculate_size.outputs.zip_size }} bytes" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ -n "${{ steps.calculate_installer_size.outputs.installer_size }}" ]; then
|
||||
echo "**Installer Size:** ${{ steps.calculate_installer_size.outputs.installer_size }} bytes" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
else
|
||||
echo "## ❌ Release Build Failed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version:** ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Tag:** \`${{ needs.prepare.outputs.tag_name }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Architecture:** ${{ matrix.architecture }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Event:** ${{ github.event_name }} (${{ github.event.action || 'N/A' }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Run:** #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Check build logs for details." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
sign:
|
||||
needs: [prepare, build]
|
||||
if: success()
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Download Build Artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: build-files-${{ needs.prepare.outputs.version }}-*
|
||||
merge-multiple: false
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Sign release artifacts with sigstore-python
|
||||
uses: sigstore/gh-action-sigstore-python@v3.3.0
|
||||
with:
|
||||
inputs: |
|
||||
build-files-${{ needs.prepare.outputs.version }}-AnyCPU/*.zip
|
||||
build-files-${{ needs.prepare.outputs.version }}-AnyCPU/*.exe
|
||||
build-files-${{ needs.prepare.outputs.version }}-x86/*.zip
|
||||
build-files-${{ needs.prepare.outputs.version }}-x86/*.exe
|
||||
release-signing-artifacts: true
|
||||
upload-signing-artifacts: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload Signed Artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: signed-files-${{ needs.prepare.outputs.version }}
|
||||
path: |
|
||||
build-files-${{ needs.prepare.outputs.version }}-AnyCPU/*.sigstore.json
|
||||
build-files-${{ needs.prepare.outputs.version }}-x86/*.sigstore.json
|
||||
|
||||
release:
|
||||
needs: [prepare, build, sign]
|
||||
if: success()
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
outputs:
|
||||
enhanced_changelog: ${{steps.enhanced_changelog.outputs.enhanced_changelog}}
|
||||
|
||||
steps:
|
||||
- name: Download Build Artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: build-files-${{ needs.prepare.outputs.version }}-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download Signed Artifacts (if exists)
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: signed-files-${{ needs.prepare.outputs.version }}
|
||||
continue-on-error: true
|
||||
|
||||
- name: Create enhanced changelog with file table
|
||||
id: enhanced_changelog
|
||||
run: |
|
||||
version='${{ needs.prepare.outputs.version }}'
|
||||
|
||||
# 读取git-cliff生成的changelog内容
|
||||
originalChangelog='${{ needs.prepare.outputs.changelog }}'
|
||||
|
||||
# 检查是否为预发布版本,如果是则添加警告提示
|
||||
if [ '${{ needs.prepare.outputs.is_prerelease }}' = "true" ]; then
|
||||
warningText=$'\n> [!CAUTION]\n'
|
||||
warningText+=$'> **注意:此版本为预览或测试版**\n'
|
||||
warningText+=$'> \n'
|
||||
warningText+=$'> 请注意,这是一个预览/测试版本,使用时可能出现BUG,常规用户建议使用预览版或正式版\n\n'
|
||||
originalChangelog="${warningText}${originalChangelog}"
|
||||
fi
|
||||
|
||||
# 构建文件信息表格
|
||||
fileTable=$'\n## 文件信息 (File Information)\n'
|
||||
fileTable+=$'| 文件名 | 大小 |\n'
|
||||
fileTable+=$'|--------|------|\n'
|
||||
|
||||
# AnyCPU (x64) 架构文件
|
||||
if [ -f "InkCanvasForClass.CE.$version-x64.zip" ]; then
|
||||
zipSize=$(wc -c < "InkCanvasForClass.CE.$version-x64.zip")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'-x64.zip | '"$zipSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
if [ -f "InkCanvasForClass.CE.$version-x64.Setup.exe" ]; then
|
||||
installerSize=$(wc -c < "InkCanvasForClass.CE.$version-x64.Setup.exe")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'-x64.Setup.exe | '"$installerSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
if [ -f "InkCanvasForClass.CE.$version-x64.zip.sigstore.json" ]; then
|
||||
sigstoreSize=$(wc -c < "InkCanvasForClass.CE.$version-x64.zip.sigstore.json")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'-x64.zip.sigstore.json | '"$sigstoreSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
if [ -f "InkCanvasForClass.CE.$version-x64.Setup.exe.sigstore.json" ]; then
|
||||
sigstoreSize=$(wc -c < "InkCanvasForClass.CE.$version-x64.Setup.exe.sigstore.json")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'-x64.Setup.exe.sigstore.json | '"$sigstoreSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
# x86 架构文件
|
||||
if [ -f "InkCanvasForClass.CE.$version.zip" ]; then
|
||||
zipSize=$(wc -c < "InkCanvasForClass.CE.$version.zip")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'.zip | '"$zipSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
if [ -f "InkCanvasForClass.CE.$version.Setup.exe" ]; then
|
||||
installerSize=$(wc -c < "InkCanvasForClass.CE.$version.Setup.exe")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'.Setup.exe | '"$installerSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
if [ -f "InkCanvasForClass.CE.$version.zip.sigstore.json" ]; then
|
||||
sigstoreSize=$(wc -c < "InkCanvasForClass.CE.$version.zip.sigstore.json")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'.zip.sigstore.json | '"$sigstoreSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
if [ -f "InkCanvasForClass.CE.$version.Setup.exe.sigstore.json" ]; then
|
||||
sigstoreSize=$(wc -c < "InkCanvasForClass.CE.$version.Setup.exe.sigstore.json")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'.Setup.exe.sigstore.json | '"$sigstoreSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
fileTable+=$'\n*文件大小信息由GitHub Actions自动生成*\n'
|
||||
|
||||
# 将表格附加到原始changelog
|
||||
enhancedChangelog="${originalChangelog}${fileTable}"
|
||||
|
||||
# 输出增强版changelog内容
|
||||
echo "enhanced_changelog<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$enhancedChangelog" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "Enhanced changelog created with file information table"
|
||||
|
||||
- name: Display Release Info
|
||||
run: |
|
||||
echo "=== Creating Release ==="
|
||||
echo "Version: ${{ needs.prepare.outputs.version }}"
|
||||
echo "Tag: ${{ needs.prepare.outputs.tag_name }}"
|
||||
echo "Pre-release: ${{ needs.prepare.outputs.is_prerelease }}"
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ needs.prepare.outputs.tag_name }}
|
||||
name: ICC CE ${{ needs.prepare.outputs.version }}
|
||||
body: |
|
||||
${{ steps.enhanced_changelog.outputs.enhanced_changelog }}
|
||||
draft: ${{ github.event.inputs.draft || false }}
|
||||
prerelease: ${{ needs.prepare.outputs.is_prerelease == 'true' }}
|
||||
files: |
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}-x64.zip
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}-x64.Setup.exe
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}-x64.zip.sigstore.json
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}-x64.Setup.exe.sigstore.json
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip.sigstore.json
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe.sigstore.json
|
||||
fail_on_unmatched_files: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
post_release:
|
||||
needs: [prepare, release]
|
||||
if: success() && github.event.inputs.draft != 'true'
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download Build Artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: build-files-${{ needs.prepare.outputs.version }}-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Get beta token
|
||||
uses: octo-sts/action@main
|
||||
id: octo-sts-beta
|
||||
with:
|
||||
scope: InkCanvasForClass/community-beta
|
||||
identity: repo-sync
|
||||
|
||||
- name: Get download token
|
||||
uses: octo-sts/action@main
|
||||
id: octo-sts-downloads
|
||||
with:
|
||||
scope: InkCanvasForClass/downloads
|
||||
identity: repo-sync
|
||||
|
||||
- name: Sync downloads repos(Universal)
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.octo-sts-downloads.outputs.token }}
|
||||
run: |
|
||||
set -e
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
REPO_DIR=$(mktemp -d)
|
||||
git clone --depth 1 --filter=blob:none --branch main https://x-access-token:${{ steps.octo-sts-downloads.outputs.token }}@github.com/InkCanvasForClass/downloads.git $REPO_DIR
|
||||
|
||||
cd $REPO_DIR
|
||||
IS_PRERELEASE="${{ needs.prepare.outputs.is_prerelease }}"
|
||||
VERSION="${{ needs.prepare.outputs.version }}"
|
||||
X64_ZIP_FILE="$GITHUB_WORKSPACE/InkCanvasForClass.CE.$VERSION-x64.zip"
|
||||
X86_ZIP_FILE="$GITHUB_WORKSPACE/InkCanvasForClass.CE.$VERSION.zip"
|
||||
|
||||
if [ "$IS_PRERELEASE" == "true" ]; then
|
||||
mkdir -p Beta
|
||||
if [ -f "$X64_ZIP_FILE" ]; then
|
||||
cp "$X64_ZIP_FILE" Beta/
|
||||
git add Beta/InkCanvasForClass.CE.$VERSION-x64.zip
|
||||
fi
|
||||
if [ -f "$X86_ZIP_FILE" ]; then
|
||||
cp "$X86_ZIP_FILE" Beta/
|
||||
git add Beta/InkCanvasForClass.CE.$VERSION.zip
|
||||
fi
|
||||
git commit -m "Add $VERSION PreRelease"
|
||||
else
|
||||
mkdir -p Release Beta
|
||||
if [ -f "$X64_ZIP_FILE" ]; then
|
||||
cp "$X64_ZIP_FILE" Release/
|
||||
cp "$X64_ZIP_FILE" Beta/
|
||||
git add Release/InkCanvasForClass.CE.$VERSION-x64.zip Beta/InkCanvasForClass.CE.$VERSION-x64.zip
|
||||
fi
|
||||
if [ -f "$X86_ZIP_FILE" ]; then
|
||||
cp "$X86_ZIP_FILE" Release/
|
||||
cp "$X86_ZIP_FILE" Beta/
|
||||
git add Release/InkCanvasForClass.CE.$VERSION.zip Beta/InkCanvasForClass.CE.$VERSION.zip
|
||||
fi
|
||||
git commit -m "Add $VERSION Release"
|
||||
fi
|
||||
git push origin main
|
||||
|
||||
- name: Update AutomaticUpdateVersionControl in beta repo
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.octo-sts-beta.outputs.token }}
|
||||
run: |
|
||||
CONTENT=$(echo -n "${{ needs.prepare.outputs.version }}" | base64 -w0)
|
||||
|
||||
SHA=$(gh api /repos/InkCanvasForClass/community-beta/contents/AutomaticUpdateVersionControl.txt --jq '.sha' 2>/dev/null || echo "")
|
||||
|
||||
gh api \
|
||||
--method PUT \
|
||||
/repos/InkCanvasForClass/community-beta/contents/AutomaticUpdateVersionControl.txt \
|
||||
-f message="Update AutomaticUpdateVersionControl.txt" \
|
||||
-f content="$CONTENT" \
|
||||
-f branch="main" \
|
||||
${SHA:+-f sha="$SHA"}
|
||||
|
||||
- name: Create GitHub Release on beta repo
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ needs.prepare.outputs.tag_name }}
|
||||
name: ICC CE ${{ needs.prepare.outputs.version }}
|
||||
body: |
|
||||
${{ needs.release.outputs.enhanced_changelog }}
|
||||
draft: false
|
||||
prerelease: ${{ needs.prepare.outputs.is_prerelease == 'true' }}
|
||||
files: |
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}-x64.zip
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip
|
||||
fail_on_unmatched_files: false
|
||||
repository: "InkCanvasForClass/community-beta"
|
||||
token: ${{ steps.octo-sts-beta.outputs.token }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.octo-sts-beta.outputs.token }}
|
||||
|
||||
- name: Update community repo AutomaticUpdateVersionControl
|
||||
if: ${{needs.prepare.outputs.is_prerelease == 'false'}}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
CONTENT=$(echo -n "${{ needs.prepare.outputs.version }}" | base64 -w0)
|
||||
|
||||
SHA=$(gh api /repos/InkCanvasForClass/community/contents/AutomaticUpdateVersionControl.txt --jq '.sha' 2>/dev/null || echo "")
|
||||
|
||||
gh api \
|
||||
--method PUT \
|
||||
/repos/InkCanvasForClass/community/contents/AutomaticUpdateVersionControl.txt \
|
||||
-f message="Update AutomaticUpdateVersionControl.txt" \
|
||||
-f content="$CONTENT" \
|
||||
-f branch="net6" \
|
||||
${SHA:+-f sha="$SHA"}
|
||||
+433
-3
@@ -1,3 +1,433 @@
|
||||
/Ink Canvas/obj
|
||||
/Ink Canvas/bin
|
||||
/.vs
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.env
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
|
||||
[Dd]ebug/x64/
|
||||
[Dd]ebugPublic/x64/
|
||||
[Rr]elease/x64/
|
||||
[Rr]eleases/x64/
|
||||
bin/x64/
|
||||
obj/x64/
|
||||
|
||||
[Dd]ebug/x86/
|
||||
[Dd]ebugPublic/x86/
|
||||
[Rr]elease/x86/
|
||||
[Rr]eleases/x86/
|
||||
bin/x86/
|
||||
obj/x86/
|
||||
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
[Aa][Rr][Mm]64[Ee][Cc]/
|
||||
bld/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Build results on 'Bin' directories
|
||||
**/[Bb]in/*
|
||||
# Uncomment if you have tasks that rely on *.refresh files to move binaries
|
||||
# (https://github.com/github/gitignore/pull/3736)
|
||||
#!**/[Bb]in/*.refresh
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
*.trx
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Approval Tests result files
|
||||
*.received.*
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.idb
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||
!Directory.Build.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
**/.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
**/.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
**/.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
**/__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
#tools/**
|
||||
#!tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
MSBuild_Logs/
|
||||
|
||||
# AWS SAM Build and Temporary Artifacts folder
|
||||
.aws-sam
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
**/.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
**/.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
**/.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Telemetry DSN configuration file (contains sensitive information)
|
||||
telemetry_dsn.txt
|
||||
**/telemetry_dsn.txt
|
||||
.trae/skills/migrate-toggle-switch/SKILL.md
|
||||
|
||||
Generated
+1
@@ -2,5 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/../alpha" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,183 +0,0 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|c:\\users\\administrator\\desktop\\icc ce\\icc ce 1.2.5\\ink canvas\\mainwindow_cs\\mw_floatingbaricons.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|solutionrelative:ink canvas\\mainwindow_cs\\mw_floatingbaricons.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\privacy.txt||{8B382828-6202-11D1-8870-0000F87579D2}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:privacy.txt||{8B382828-6202-11D1-8870-0000F87579D2}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Manual.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Manual.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\LICENSE||{8B382828-6202-11D1-8870-0000F87579D2}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:LICENSE||{8B382828-6202-11D1-8870-0000F87579D2}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas.sln.DotSettings.user||{FA3CD31E-987B-443A-9B81-186104E8DAC1}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Ink Canvas.sln.DotSettings.user||{FA3CD31E-987B-443A-9B81-186104E8DAC1}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|c:\\users\\administrator\\desktop\\icc ce\\icc ce 1.2.5\\ink canvas\\mainwindow_cs\\mw_ppt.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|solutionrelative:ink canvas\\mainwindow_cs\\mw_ppt.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|c:\\users\\administrator\\desktop\\icc ce\\icc ce 1.2.5\\ink canvas\\mainwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
|
||||
"RelativeMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|solutionrelative:ink canvas\\mainwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "MW_FloatingBarIcons.cs",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs",
|
||||
"RelativeDocumentMoniker": "Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs*",
|
||||
"RelativeToolTip": "Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs*",
|
||||
"ViewState": "AgIAAFgHAAAAAAAAAAAgwGcHAAAIAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-05-31T10:49:24.719Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "README.md",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\README.md",
|
||||
"RelativeDocumentMoniker": "README.md",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\README.md",
|
||||
"RelativeToolTip": "README.md",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|",
|
||||
"WhenOpened": "2025-05-31T10:48:22.883Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 7,
|
||||
"Title": "MainWindow.xaml",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow.xaml",
|
||||
"RelativeDocumentMoniker": "Ink Canvas\\MainWindow.xaml",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow.xaml",
|
||||
"RelativeToolTip": "Ink Canvas\\MainWindow.xaml",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
|
||||
"WhenOpened": "2025-05-24T13:22:56.715Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 9,
|
||||
"Title": "Microsoft.Common.CurrentVersion.targets",
|
||||
"DocumentMoniker": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"RelativeDocumentMoniker": "..\\..\\..\\..\\..\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"ToolTip": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"RelativeToolTip": "..\\..\\..\\..\\..\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"ViewState": "AgIAAGsJAAAAAAAAAAAQwIEJAAAEAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003801|",
|
||||
"WhenOpened": "2025-05-24T13:06:01.053Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 6,
|
||||
"Title": "MW_PPT.cs",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"RelativeDocumentMoniker": "Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"RelativeToolTip": "Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"ViewState": "AgIAAFgAAAAAAAAAAAAUwHQAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-05-24T13:04:47.205Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 8,
|
||||
"Title": "README.md",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\README.md",
|
||||
"RelativeDocumentMoniker": "..\\icc-0610.2.3\\README.md",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\README.md",
|
||||
"RelativeToolTip": "..\\icc-0610.2.3\\README.md",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|",
|
||||
"WhenOpened": "2025-05-24T13:04:01.407Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"Title": "privacy.txt",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\privacy.txt",
|
||||
"RelativeDocumentMoniker": "privacy.txt",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\privacy.txt",
|
||||
"RelativeToolTip": "privacy.txt",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003109|",
|
||||
"WhenOpened": "2025-05-24T13:04:01.337Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 3,
|
||||
"Title": "Manual.md",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Manual.md",
|
||||
"RelativeDocumentMoniker": "Manual.md",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Manual.md",
|
||||
"RelativeToolTip": "Manual.md",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|",
|
||||
"WhenOpened": "2025-05-24T13:04:00.986Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 4,
|
||||
"Title": "LICENSE",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\LICENSE",
|
||||
"RelativeDocumentMoniker": "LICENSE",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\LICENSE",
|
||||
"RelativeToolTip": "LICENSE",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|",
|
||||
"WhenOpened": "2025-05-24T13:04:00.902Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 5,
|
||||
"Title": "Ink Canvas.sln.DotSettings.user",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas.sln.DotSettings.user",
|
||||
"RelativeDocumentMoniker": "Ink Canvas.sln.DotSettings.user",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas.sln.DotSettings.user",
|
||||
"RelativeToolTip": "Ink Canvas.sln.DotSettings.user",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003464|",
|
||||
"WhenOpened": "2025-05-24T13:04:00.792Z",
|
||||
"EditorCaption": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|c:\\users\\administrator\\desktop\\icc ce\\icc ce 1.2.5\\ink canvas\\mainwindow_cs\\mw_floatingbaricons.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|solutionrelative:ink canvas\\mainwindow_cs\\mw_floatingbaricons.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\privacy.txt||{8B382828-6202-11D1-8870-0000F87579D2}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:privacy.txt||{8B382828-6202-11D1-8870-0000F87579D2}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Manual.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Manual.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\LICENSE||{8B382828-6202-11D1-8870-0000F87579D2}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:LICENSE||{8B382828-6202-11D1-8870-0000F87579D2}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas.sln.DotSettings.user||{FA3CD31E-987B-443A-9B81-186104E8DAC1}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Ink Canvas.sln.DotSettings.user||{FA3CD31E-987B-443A-9B81-186104E8DAC1}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|c:\\users\\administrator\\desktop\\icc ce\\icc ce 1.2.5\\ink canvas\\mainwindow_cs\\mw_ppt.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|solutionrelative:ink canvas\\mainwindow_cs\\mw_ppt.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|c:\\users\\administrator\\desktop\\icc ce\\icc ce 1.2.5\\ink canvas\\mainwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
|
||||
"RelativeMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|solutionrelative:ink canvas\\mainwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "MW_FloatingBarIcons.cs",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs",
|
||||
"RelativeDocumentMoniker": "Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs",
|
||||
"RelativeToolTip": "Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs",
|
||||
"ViewState": "AgIAAFgHAAAAAAAAAAAgwGcHAAAIAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-05-31T10:49:24.719Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "README.md",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\README.md",
|
||||
"RelativeDocumentMoniker": "README.md",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\README.md",
|
||||
"RelativeToolTip": "README.md",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|",
|
||||
"WhenOpened": "2025-05-31T10:48:22.883Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 7,
|
||||
"Title": "MainWindow.xaml",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow.xaml",
|
||||
"RelativeDocumentMoniker": "Ink Canvas\\MainWindow.xaml",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow.xaml",
|
||||
"RelativeToolTip": "Ink Canvas\\MainWindow.xaml",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
|
||||
"WhenOpened": "2025-05-24T13:22:56.715Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 9,
|
||||
"Title": "Microsoft.Common.CurrentVersion.targets",
|
||||
"DocumentMoniker": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"RelativeDocumentMoniker": "..\\..\\..\\..\\..\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"ToolTip": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"RelativeToolTip": "..\\..\\..\\..\\..\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"ViewState": "AgIAAGsJAAAAAAAAAAAQwIEJAAAEAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003801|",
|
||||
"WhenOpened": "2025-05-24T13:06:01.053Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 6,
|
||||
"Title": "MW_PPT.cs",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"RelativeDocumentMoniker": "Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"RelativeToolTip": "Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"ViewState": "AgIAAFgAAAAAAAAAAAAUwHQAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-05-24T13:04:47.205Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 8,
|
||||
"Title": "README.md",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\README.md",
|
||||
"RelativeDocumentMoniker": "..\\icc-0610.2.3\\README.md",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\README.md",
|
||||
"RelativeToolTip": "..\\icc-0610.2.3\\README.md",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|",
|
||||
"WhenOpened": "2025-05-24T13:04:01.407Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"Title": "privacy.txt",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\privacy.txt",
|
||||
"RelativeDocumentMoniker": "privacy.txt",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\privacy.txt",
|
||||
"RelativeToolTip": "privacy.txt",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003109|",
|
||||
"WhenOpened": "2025-05-24T13:04:01.337Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 3,
|
||||
"Title": "Manual.md",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Manual.md",
|
||||
"RelativeDocumentMoniker": "Manual.md",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Manual.md",
|
||||
"RelativeToolTip": "Manual.md",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|",
|
||||
"WhenOpened": "2025-05-24T13:04:00.986Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 4,
|
||||
"Title": "LICENSE",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\LICENSE",
|
||||
"RelativeDocumentMoniker": "LICENSE",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\LICENSE",
|
||||
"RelativeToolTip": "LICENSE",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|",
|
||||
"WhenOpened": "2025-05-24T13:04:00.902Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 5,
|
||||
"Title": "Ink Canvas.sln.DotSettings.user",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas.sln.DotSettings.user",
|
||||
"RelativeDocumentMoniker": "Ink Canvas.sln.DotSettings.user",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas.sln.DotSettings.user",
|
||||
"RelativeToolTip": "Ink Canvas.sln.DotSettings.user",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003464|",
|
||||
"WhenOpened": "2025-05-24T13:04:00.792Z",
|
||||
"EditorCaption": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"ExpandedNodes": [
|
||||
""
|
||||
],
|
||||
"PreviewInSolutionExplorer": false
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,290 +0,0 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\System.ValueTuple.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Settings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\OSVersionExt.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Office.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.Wpf.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Newtonsoft.Json.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.Plugins.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\InkCanvasForClass.exe.config||{FA3CD31E-987B-443A-9B81-186104E8DAC1}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IAWinFX.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IALoader.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IACore.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "System.ValueTuple.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:48.138Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "Settings.json",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Settings.json",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Settings.json",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Settings.json",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Settings.json",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.878Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"Title": "OSVersionExt.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.837Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 3,
|
||||
"Title": "Office.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Office.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Office.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Office.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Office.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.774Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 4,
|
||||
"Title": "NHotkey.Wpf.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.718Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 5,
|
||||
"Title": "NHotkey.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\NHotkey.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\NHotkey.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.662Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 6,
|
||||
"Title": "Newtonsoft.Json.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.589Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 7,
|
||||
"Title": "Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.932Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 8,
|
||||
"Title": "MdXaml.Plugins.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.838Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 9,
|
||||
"Title": "MdXaml.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\MdXaml.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\MdXaml.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.776Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 10,
|
||||
"Title": "iNKORE.UI.WPF.Modern.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.573Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 11,
|
||||
"Title": "iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.432Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 12,
|
||||
"Title": "iNKORE.UI.WPF.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:42.807Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 13,
|
||||
"Title": "InkCanvasForClass.exe.config",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000601|",
|
||||
"WhenOpened": "2025-05-24T13:02:27.288Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 14,
|
||||
"Title": "ICSharpCode.AvalonEdit.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.847Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 15,
|
||||
"Title": "IAWinFX.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.816Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 16,
|
||||
"Title": "IALoader.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IALoader.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\IALoader.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IALoader.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\IALoader.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.784Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 17,
|
||||
"Title": "IACore.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IACore.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\IACore.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IACore.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\IACore.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.753Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 18,
|
||||
"Title": "Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.113Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\icc.png||{177559E0-D141-11D0-92DF-00A0C9138C45}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:icc.png||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\AutomaticUpdateVersionControl.txt||{8B382828-6202-11D1-8870-0000F87579D2}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:AutomaticUpdateVersionControl.txt||{8B382828-6202-11D1-8870-0000F87579D2}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\.gitignore||{3B902123-F8A7-4915-9F01-361F908088D0}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:.gitignore||{3B902123-F8A7-4915-9F01-361F908088D0}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\System.ValueTuple.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Settings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\OSVersionExt.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Office.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.Wpf.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Newtonsoft.Json.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.Plugins.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\InkCanvasForClass.exe.config||{FA3CD31E-987B-443A-9B81-186104E8DAC1}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IAWinFX.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IALoader.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IACore.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "icc.png - PNG [256x256, 32 \u4F4D, PNG]",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\icc.png",
|
||||
"RelativeDocumentMoniker": "icc.png",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\icc.png - PNG [256x256, 32 \u4F4D, PNG]",
|
||||
"RelativeToolTip": "icc.png - PNG [256x256, 32 \u4F4D, PNG]",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001533|",
|
||||
"WhenOpened": "2025-05-24T13:03:45.63Z",
|
||||
"EditorCaption": " - PNG [256x256, 32 \u4F4D, PNG]"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "AutomaticUpdateVersionControl.txt",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\AutomaticUpdateVersionControl.txt",
|
||||
"RelativeDocumentMoniker": "AutomaticUpdateVersionControl.txt",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\AutomaticUpdateVersionControl.txt",
|
||||
"RelativeToolTip": "AutomaticUpdateVersionControl.txt",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003109|",
|
||||
"WhenOpened": "2025-05-24T13:03:45.517Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"Title": ".gitignore",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\.gitignore",
|
||||
"RelativeDocumentMoniker": ".gitignore",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\.gitignore",
|
||||
"RelativeToolTip": ".gitignore",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|",
|
||||
"WhenOpened": "2025-05-24T13:03:43.13Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 3,
|
||||
"Title": "System.ValueTuple.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:48.138Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 4,
|
||||
"Title": "Settings.json",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Settings.json",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Settings.json",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Settings.json",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Settings.json",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.878Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 5,
|
||||
"Title": "OSVersionExt.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.837Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 6,
|
||||
"Title": "Office.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Office.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Office.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Office.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Office.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.774Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 7,
|
||||
"Title": "NHotkey.Wpf.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.718Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 8,
|
||||
"Title": "NHotkey.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\NHotkey.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\NHotkey.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.662Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 9,
|
||||
"Title": "Newtonsoft.Json.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.589Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 10,
|
||||
"Title": "Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.932Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 11,
|
||||
"Title": "MdXaml.Plugins.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.838Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 12,
|
||||
"Title": "MdXaml.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\MdXaml.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\MdXaml.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.776Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 13,
|
||||
"Title": "iNKORE.UI.WPF.Modern.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.573Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 14,
|
||||
"Title": "iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.432Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 15,
|
||||
"Title": "iNKORE.UI.WPF.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:42.807Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 16,
|
||||
"Title": "InkCanvasForClass.exe.config",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000601|",
|
||||
"WhenOpened": "2025-05-24T13:02:27.288Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 17,
|
||||
"Title": "ICSharpCode.AvalonEdit.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.847Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 18,
|
||||
"Title": "IAWinFX.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.816Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 19,
|
||||
"Title": "IALoader.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IALoader.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\IALoader.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IALoader.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\IALoader.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.784Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 20,
|
||||
"Title": "IACore.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IACore.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\IACore.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IACore.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\IACore.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.753Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 21,
|
||||
"Title": "Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.113Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\icc.png||{177559E0-D141-11D0-92DF-00A0C9138C45}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:icc.png||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\AutomaticUpdateVersionControl.txt||{8B382828-6202-11D1-8870-0000F87579D2}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:AutomaticUpdateVersionControl.txt||{8B382828-6202-11D1-8870-0000F87579D2}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\.gitignore||{3B902123-F8A7-4915-9F01-361F908088D0}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:.gitignore||{3B902123-F8A7-4915-9F01-361F908088D0}"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "icc.png",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\icc.png",
|
||||
"RelativeDocumentMoniker": "icc.png",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\icc.png",
|
||||
"RelativeToolTip": "icc.png",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001533|",
|
||||
"WhenOpened": "2025-05-24T13:12:49.619Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "AutomaticUpdateVersionControl.txt",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\AutomaticUpdateVersionControl.txt",
|
||||
"RelativeDocumentMoniker": "AutomaticUpdateVersionControl.txt",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\AutomaticUpdateVersionControl.txt",
|
||||
"RelativeToolTip": "AutomaticUpdateVersionControl.txt",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003109|",
|
||||
"WhenOpened": "2025-05-24T13:12:49.575Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"Title": ".gitignore",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\.gitignore",
|
||||
"RelativeDocumentMoniker": ".gitignore",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\.gitignore",
|
||||
"RelativeToolTip": ".gitignore",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|",
|
||||
"WhenOpened": "2025-05-24T13:12:49.025Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\icc.png||{177559E0-D141-11D0-92DF-00A0C9138C45}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:icc.png||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\AutomaticUpdateVersionControl.txt||{8B382828-6202-11D1-8870-0000F87579D2}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:AutomaticUpdateVersionControl.txt||{8B382828-6202-11D1-8870-0000F87579D2}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\.gitignore||{3B902123-F8A7-4915-9F01-361F908088D0}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:.gitignore||{3B902123-F8A7-4915-9F01-361F908088D0}"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 1,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "icc.png - PNG [1328x1328, 32 \u4F4D, PNG]",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\icc.png",
|
||||
"RelativeDocumentMoniker": "icc.png",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\icc.png - PNG [1328x1328, 32 \u4F4D, PNG]",
|
||||
"RelativeToolTip": "icc.png - PNG [1328x1328, 32 \u4F4D, PNG]",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001533|",
|
||||
"WhenOpened": "2025-05-24T13:12:49.619Z",
|
||||
"EditorCaption": " - PNG [1328x1328, 32 \u4F4D, PNG]"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "AutomaticUpdateVersionControl.txt",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\AutomaticUpdateVersionControl.txt",
|
||||
"RelativeDocumentMoniker": "AutomaticUpdateVersionControl.txt",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\AutomaticUpdateVersionControl.txt",
|
||||
"RelativeToolTip": "AutomaticUpdateVersionControl.txt",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003109|",
|
||||
"WhenOpened": "2025-05-24T13:12:49.575Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"Title": ".gitignore",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\.gitignore",
|
||||
"RelativeDocumentMoniker": ".gitignore",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\.gitignore",
|
||||
"RelativeToolTip": ".gitignore",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|",
|
||||
"WhenOpened": "2025-05-24T13:12:49.025Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
1.6.2
|
||||
1.7.18.0
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 550 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 MiB |
+57
-13
@@ -1,10 +1,14 @@
|
||||
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.33530.505
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.4.11626.88 stable
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InkCanvasForClass", "Ink Canvas\InkCanvasForClass.csproj", "{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.PluginSdk", "InkCanvas.PluginSdk\InkCanvas.PluginSdk.csproj", "{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.Controls", "InkCanvas.Controls\InkCanvas.Controls.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -23,22 +27,62 @@ Global
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x64.Build.0 = Debug|x64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x86.Build.0 = Debug|x86
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x64.ActiveCfg = Release|x64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x64.Build.0 = Release|x64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.ActiveCfg = Release|x86
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.Build.0 = Release|x86
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x64.Build.0 = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/PencilsConfiguration/ActualSeverity/@EntryValue">WARNING</s:String></wpf:ResourceDictionary>
|
||||
File diff suppressed because one or more lines are too long
+64
-33
@@ -1,21 +1,23 @@
|
||||
<Application x:Class="Ink_Canvas.App"
|
||||
<Application x:Class="Ink_Canvas.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Ink_Canvas"
|
||||
xmlns:tb="http://www.hardcodet.net/taskbar"
|
||||
xmlns:props="clr-namespace:Ink_Canvas.Properties"
|
||||
xmlns:tb="clr-namespace:H.NotifyIcon;assembly=H.NotifyIcon.Wpf"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
StartupUri="MainWindow.xaml">
|
||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||
>
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<Style TargetType="ui:ScrollViewerEx">
|
||||
<EventSetter Event="PreviewMouseWheel" Handler="ScrollViewer_PreviewMouseWheel"/>
|
||||
</Style>
|
||||
<ContextMenu Opened="SysTrayMenu_Opened" x:Shared="false" x:Key="SysTrayMenu" Padding="6" ui:ThemeManager.RequestedTheme="Light">
|
||||
<ContextMenu Opened="SysTrayMenu_Opened" Closed="SysTrayMenu_Closed" x:Shared="false" x:Key="SysTrayMenu" Padding="6" ui:ThemeManager.RequestedTheme="Light">
|
||||
<MenuItem IsCheckable="True" IsChecked="False" Checked="HideICCMainWindowTrayIconMenuItem_Checked" Unchecked="HideICCMainWindowTrayIconMenuItem_UnChecked" Name="HideICCMainWindowTrayIconMenuItem">
|
||||
<MenuItem.Header>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<TextBlock Name="HideICCMainWindowTrayIconMenuItemHeaderText" FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="隐藏ICC主窗口" />
|
||||
</ui:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<Image Width="28" Height="28" Margin="-2">
|
||||
@@ -31,12 +33,42 @@
|
||||
</Image>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator Margin="0,3" />
|
||||
<MenuItem>
|
||||
<MenuItem Name="TempShowMainWindowTrayIconMenuItem" Click="TempShowMainWindowTrayIconMenuItem_Clicked">
|
||||
<MenuItem.Header>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<TextBlock FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="{x:Static props:Strings.Tray_TempShowMainWindow}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<Image Width="28" Height="28" Margin="-2">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#27272a" Geometry="F0 M24,24z M0,0z M5,6C4.73478,6 4.48043,6.10536 4.29289,6.29289 4.10536,6.48043 4,6.73478 4,7L4,17C4,17.2652 4.10536,17.5196 4.29289,17.7071 4.48043,17.8946 4.73478,18 5,18L19,18C19.2652,18 19.5196,17.8946 19.7071,17.7071 19.8946,17.5196 20,17.2652 20,17L20,7C20,6.73478 19.8946,6.48043 19.7071,6.29289 19.5196,6.10536 19.2652,6 19,6L5,6z M2.87868,4.87868C3.44129,4.31607,4.20435,4,5,4L19,4C19.7957,4 20.5587,4.31607 21.1213,4.87868 21.6839,5.44129 22,6.20435 22,7L22,17C22,17.7957 21.6839,18.5587 21.1213,19.1213 20.5587,19.6839 19.7957,20 19,20L5,20C4.20435,20 3.44129,19.6839 2.87868,19.1213 2.31607,18.5587 2,17.7956 2,17L2,7C2,6.20435,2.31607,5.44129,2.87868,4.87868z M5,8C5,7.44772,5.44772,7,6,7L6.01,7C6.56228,7 7.01,7.44772 7.01,8 7.01,8.55228 6.56228,9 6.01,9L6,9C5.44772,9,5,8.55228,5,8z M9,7C8.44772,7 8,7.44772 8,8 8,8.55228 8.44772,9 9,9L9.01,9C9.56228,9 10.01,8.55228 10.01,8 10.01,7.44772 9.56228,7 9.01,7L9,7z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Name="OpenSettingsTrayIconMenuItem" Click="OpenSettingsTrayIconMenuItem_Clicked">
|
||||
<MenuItem.Header>
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<TextBlock FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="{x:Static props:Strings.Tray_OpenSettings}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<Image Width="28" Height="28" Margin="-2" Source="/Resources/Icons-Fluent/ic_fluent_settings_24_regular.png" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator Margin="0,3" />
|
||||
<MenuItem Name="DisableAllHotkeysMenuItem" Click="DisableAllHotkeysMenuItem_Clicked">
|
||||
<MenuItem.Header>
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<TextBlock FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="禁用所有快捷键" />
|
||||
</ui:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<Image Width="28" Height="28" Margin="-2">
|
||||
@@ -54,9 +86,9 @@
|
||||
</MenuItem>
|
||||
<MenuItem Name="ForceFullScreenTrayIconMenuItem" Click="ForceFullScreenTrayIconMenuItem_Clicked">
|
||||
<MenuItem.Header>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<TextBlock FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="强制全屏化" />
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="4" Margin="16,0,0,0">
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="4" Margin="16,0,0,0">
|
||||
<Border Padding="4,2,4,1" Background="#e4e4e7" BorderBrush="#a1a1aa" BorderThickness="1" CornerRadius="2.5">
|
||||
<TextBlock FontSize="8" Foreground="#3f3f46" FontWeight="Bold" Text="CTRL" />
|
||||
</Border>
|
||||
@@ -64,8 +96,8 @@
|
||||
<Border Padding="4,2,4,1" Background="#e4e4e7" BorderBrush="#a1a1aa" BorderThickness="1" CornerRadius="2.5">
|
||||
<TextBlock FontSize="8" Foreground="#3f3f46" FontWeight="Bold" Text="F" />
|
||||
</Border>
|
||||
</ui:SimpleStackPanel>
|
||||
</ui:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<Image Width="28" Height="28" Margin="-2">
|
||||
@@ -87,9 +119,9 @@
|
||||
</MenuItem>
|
||||
<MenuItem Name="FoldFloatingBarTrayIconMenuItem" Click="FoldFloatingBarTrayIconMenuItem_Clicked">
|
||||
<MenuItem.Header>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<TextBlock Name="FoldFloatingBarTrayIconMenuItemHeaderText" FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="切换为收纳模式" />
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="4" Margin="16,0,0,0">
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="4" Margin="16,0,0,0">
|
||||
<Border Padding="4,2,4,1" Background="#e4e4e7" BorderBrush="#a1a1aa" BorderThickness="1" CornerRadius="2.5">
|
||||
<TextBlock FontSize="8" Foreground="#3f3f46" FontWeight="Bold" Text="CTRL" />
|
||||
</Border>
|
||||
@@ -97,8 +129,8 @@
|
||||
<Border Padding="4,2,4,1" Background="#e4e4e7" BorderBrush="#a1a1aa" BorderThickness="1" CornerRadius="2.5">
|
||||
<TextBlock FontSize="8" Foreground="#3f3f46" FontWeight="Bold" Text="S" />
|
||||
</Border>
|
||||
</ui:SimpleStackPanel>
|
||||
</ui:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<Grid>
|
||||
@@ -132,9 +164,9 @@
|
||||
</MenuItem>
|
||||
<MenuItem Name="ResetFloatingBarPositionTrayIconMenuItem" Click="ResetFloatingBarPositionTrayIconMenuItem_Clicked">
|
||||
<MenuItem.Header>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<TextBlock FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="重置工具栏位置" />
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="4" Margin="16,0,0,0">
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="4" Margin="16,0,0,0">
|
||||
<Border Padding="4,2,4,1" Background="#e4e4e7" BorderBrush="#a1a1aa" BorderThickness="1" CornerRadius="2.5">
|
||||
<TextBlock FontSize="8" Foreground="#3f3f46" FontWeight="Bold" Text="CTRL" />
|
||||
</Border>
|
||||
@@ -142,8 +174,8 @@
|
||||
<Border Padding="4,2,4,1" Background="#e4e4e7" BorderBrush="#a1a1aa" BorderThickness="1" CornerRadius="2.5">
|
||||
<TextBlock FontSize="8" Foreground="#3f3f46" FontWeight="Bold" Text="T" />
|
||||
</Border>
|
||||
</ui:SimpleStackPanel>
|
||||
</ui:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<Image Width="28" Height="28" Margin="-2">
|
||||
@@ -163,9 +195,9 @@
|
||||
<Separator Margin="0,3" />
|
||||
<MenuItem Name="RestartAppTrayIconMenuItem" Click="RestartAppTrayIconMenuItem_Clicked">
|
||||
<MenuItem.Header>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<TextBlock FontSize="14" FontWeight="Bold" VerticalAlignment="Center" Foreground="#2563eb" Text="重启软件" />
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="4" Margin="16,0,0,0">
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="4" Margin="16,0,0,0">
|
||||
<Border Padding="4,2,4,1" Background="#e4e4e7" BorderBrush="#a1a1aa" BorderThickness="1" CornerRadius="2.5">
|
||||
<TextBlock FontSize="8" Foreground="#3f3f46" FontWeight="Bold" Text="CTRL" />
|
||||
</Border>
|
||||
@@ -173,8 +205,8 @@
|
||||
<Border Padding="4,2,4,1" Background="#e4e4e7" BorderBrush="#a1a1aa" BorderThickness="1" CornerRadius="2.5">
|
||||
<TextBlock FontSize="8" Foreground="#3f3f46" FontWeight="Bold" Text="R" />
|
||||
</Border>
|
||||
</ui:SimpleStackPanel>
|
||||
</ui:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<Image Width="24" Height="24">
|
||||
@@ -195,9 +227,9 @@
|
||||
</MenuItem>
|
||||
<MenuItem Name="CloseAppTrayIconMenuItem" Click="CloseAppTrayIconMenuItem_Clicked">
|
||||
<MenuItem.Header>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<TextBlock FontSize="14" FontWeight="Bold" VerticalAlignment="Center" Foreground="#dc2626" Text="退出软件" />
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="4" Margin="16,0,0,0">
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="4" Margin="16,0,0,0">
|
||||
<Border Padding="4,2,4,1" Background="#e4e4e7" BorderBrush="#a1a1aa" BorderThickness="1" CornerRadius="2.5">
|
||||
<TextBlock FontSize="8" Foreground="#3f3f46" FontWeight="Bold" Text="CTRL" />
|
||||
</Border>
|
||||
@@ -205,8 +237,8 @@
|
||||
<Border Padding="4,2,4,1" Background="#e4e4e7" BorderBrush="#a1a1aa" BorderThickness="1" CornerRadius="2.5">
|
||||
<TextBlock FontSize="8" Foreground="#3f3f46" FontWeight="Bold" Text="Q" />
|
||||
</Border>
|
||||
</ui:SimpleStackPanel>
|
||||
</ui:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<Image Width="24" Height="24">
|
||||
@@ -231,13 +263,12 @@
|
||||
ToolTipText="InkCanvasForClass"
|
||||
ContextMenu="{StaticResource SysTrayMenu}"
|
||||
IconSource="/Resources/icc.ico"/>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ui:ThemeResources RequestedTheme="Light"/>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ui:ThemeResources/>
|
||||
<ui:XamlControlsResources />
|
||||
<ResourceDictionary Source="Resources/SeewoImageDictionary.xaml"/>
|
||||
<ResourceDictionary Source="Resources/DrawShapeImageDictionary.xaml"/>
|
||||
<ResourceDictionary Source="Resources/IconImageDictionary.xaml"/>
|
||||
<ResourceDictionary Source="Resources/Styles/Light.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
+1479
-613
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
[assembly: System.Runtime.Versioning.SupportedOSPlatform("windows")]
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("InkCanvasForClass")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Dubi906w")]
|
||||
[assembly: AssemblyCompany("CJK_mkp")]
|
||||
[assembly: AssemblyProduct("InkCanvasForClass")]
|
||||
[assembly: AssemblyCopyright("Copyright © HARKOTEK Studio 2024")]
|
||||
[assembly: AssemblyCopyright("Copyright © CJK_mkp 2025-2026")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
@@ -19,14 +20,8 @@ using System.Windows;
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
//In order to begin building localizable applications, set
|
||||
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
|
||||
//inside a <PropertyGroup>. For example, if you are using US english
|
||||
//in your source files, set the <UICulture> to en-US. Then uncomment
|
||||
//the NeutralResourceLanguage attribute below. Update the "en-US" in
|
||||
//the line below to match the UICulture setting in the project file.
|
||||
|
||||
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
|
||||
// i18n: 默认/回退语言为简体中文,与 Strings.resx 默认文案一致。
|
||||
[assembly: System.Resources.NeutralResourcesLanguage("zh-CN", System.Resources.UltimateResourceFallbackLocation.MainAssembly)]
|
||||
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
@@ -49,5 +44,5 @@ using System.Windows;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.6.2.0")]
|
||||
[assembly: AssemblyFileVersion("1.6.2.0")]
|
||||
[assembly: AssemblyVersion("1.7.18.10")]
|
||||
[assembly: AssemblyFileVersion("1.7.18.10")]
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Controls
|
||||
{
|
||||
[TemplatePart(Name = PartCloseImage, Type = typeof(UIElement))]
|
||||
[TemplatePart(Name = PartAnimationRoot, Type = typeof(UIElement))]
|
||||
public class BoardMenuFrame : ContentControl
|
||||
{
|
||||
private const string PartCloseImage = "PART_CloseImage";
|
||||
private const string PartAnimationRoot = "PART_AnimationRoot";
|
||||
|
||||
public static readonly DependencyProperty TitleProperty =
|
||||
DependencyProperty.Register(nameof(Title), typeof(object), typeof(BoardMenuFrame), new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty TitleFontSizeProperty =
|
||||
DependencyProperty.Register(nameof(TitleFontSize), typeof(double), typeof(BoardMenuFrame), new PropertyMetadata(11d));
|
||||
|
||||
public static readonly DependencyProperty HeaderHeightProperty =
|
||||
DependencyProperty.Register(nameof(HeaderHeight), typeof(double), typeof(BoardMenuFrame), new PropertyMetadata(48d));
|
||||
|
||||
public static readonly DependencyProperty PanelCornerRadiusProperty =
|
||||
DependencyProperty.Register(nameof(PanelCornerRadius), typeof(CornerRadius), typeof(BoardMenuFrame), new PropertyMetadata(new CornerRadius(5)));
|
||||
|
||||
public static readonly DependencyProperty HeaderCornerRadiusProperty =
|
||||
DependencyProperty.Register(nameof(HeaderCornerRadius), typeof(CornerRadius), typeof(BoardMenuFrame), new PropertyMetadata(new CornerRadius(6, 6, 0, 0)));
|
||||
|
||||
public static readonly DependencyProperty PanelBackgroundProperty =
|
||||
DependencyProperty.Register(nameof(PanelBackground), typeof(Brush), typeof(BoardMenuFrame), new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty HeaderBackgroundProperty =
|
||||
DependencyProperty.Register(nameof(HeaderBackground), typeof(Brush), typeof(BoardMenuFrame),
|
||||
new PropertyMetadata(new SolidColorBrush((Color)ColorConverter.ConvertFromString("#2563eb"))));
|
||||
|
||||
public static readonly DependencyProperty HeaderBorderBrushProperty =
|
||||
DependencyProperty.Register(nameof(HeaderBorderBrush), typeof(Brush), typeof(BoardMenuFrame),
|
||||
new PropertyMetadata(new SolidColorBrush((Color)ColorConverter.ConvertFromString("#1e3a8a"))));
|
||||
|
||||
public static readonly DependencyProperty IsOpenProperty =
|
||||
DependencyProperty.Register(nameof(IsOpen), typeof(bool), typeof(BoardMenuFrame), new PropertyMetadata(false));
|
||||
|
||||
public static readonly DependencyProperty PlacementTargetProperty =
|
||||
DependencyProperty.Register(nameof(PlacementTarget), typeof(UIElement), typeof(BoardMenuFrame), new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty PlacementProperty =
|
||||
DependencyProperty.Register(nameof(Placement), typeof(PlacementMode), typeof(BoardMenuFrame), new PropertyMetadata(PlacementMode.Custom));
|
||||
|
||||
public static readonly DependencyProperty CustomPopupPlacementCallbackProperty =
|
||||
DependencyProperty.Register(nameof(CustomPopupPlacementCallback), typeof(CustomPopupPlacementCallback), typeof(BoardMenuFrame),
|
||||
new PropertyMetadata((CustomPopupPlacementCallback)PlaceCenteredAbove));
|
||||
|
||||
private static CustomPopupPlacement[] PlaceCenteredAbove(Size popupSize, Size targetSize, Point offset)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new CustomPopupPlacement(
|
||||
new Point((targetSize.Width - popupSize.Width) / 2 + offset.X,
|
||||
-popupSize.Height + offset.Y),
|
||||
PopupPrimaryAxis.Horizontal),
|
||||
new CustomPopupPlacement(
|
||||
new Point((targetSize.Width - popupSize.Width) / 2 + offset.X,
|
||||
targetSize.Height - offset.Y),
|
||||
PopupPrimaryAxis.Horizontal)
|
||||
};
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PopupHorizontalOffsetProperty =
|
||||
DependencyProperty.Register(nameof(PopupHorizontalOffset), typeof(double), typeof(BoardMenuFrame), new PropertyMetadata(0d));
|
||||
|
||||
public static readonly DependencyProperty PopupVerticalOffsetProperty =
|
||||
DependencyProperty.Register(nameof(PopupVerticalOffset), typeof(double), typeof(BoardMenuFrame), new PropertyMetadata(-4d));
|
||||
|
||||
public object Title { get => GetValue(TitleProperty); set => SetValue(TitleProperty, value); }
|
||||
public double TitleFontSize { get => (double)GetValue(TitleFontSizeProperty); set => SetValue(TitleFontSizeProperty, value); }
|
||||
public double HeaderHeight { get => (double)GetValue(HeaderHeightProperty); set => SetValue(HeaderHeightProperty, value); }
|
||||
public CornerRadius PanelCornerRadius { get => (CornerRadius)GetValue(PanelCornerRadiusProperty); set => SetValue(PanelCornerRadiusProperty, value); }
|
||||
public CornerRadius HeaderCornerRadius { get => (CornerRadius)GetValue(HeaderCornerRadiusProperty); set => SetValue(HeaderCornerRadiusProperty, value); }
|
||||
public Brush PanelBackground { get => (Brush)GetValue(PanelBackgroundProperty); set => SetValue(PanelBackgroundProperty, value); }
|
||||
public Brush HeaderBackground { get => (Brush)GetValue(HeaderBackgroundProperty); set => SetValue(HeaderBackgroundProperty, value); }
|
||||
public Brush HeaderBorderBrush { get => (Brush)GetValue(HeaderBorderBrushProperty); set => SetValue(HeaderBorderBrushProperty, value); }
|
||||
public bool IsOpen { get => (bool)GetValue(IsOpenProperty); set => SetValue(IsOpenProperty, value); }
|
||||
public UIElement PlacementTarget { get => (UIElement)GetValue(PlacementTargetProperty); set => SetValue(PlacementTargetProperty, value); }
|
||||
public PlacementMode Placement { get => (PlacementMode)GetValue(PlacementProperty); set => SetValue(PlacementProperty, value); }
|
||||
public CustomPopupPlacementCallback CustomPopupPlacementCallback
|
||||
{
|
||||
get => (CustomPopupPlacementCallback)GetValue(CustomPopupPlacementCallbackProperty);
|
||||
set => SetValue(CustomPopupPlacementCallbackProperty, value);
|
||||
}
|
||||
public double PopupHorizontalOffset { get => (double)GetValue(PopupHorizontalOffsetProperty); set => SetValue(PopupHorizontalOffsetProperty, value); }
|
||||
public double PopupVerticalOffset { get => (double)GetValue(PopupVerticalOffsetProperty); set => SetValue(PopupVerticalOffsetProperty, value); }
|
||||
|
||||
public event MouseButtonEventHandler CloseMouseDown;
|
||||
public event MouseButtonEventHandler CloseMouseUp;
|
||||
|
||||
public UIElement AnimationTarget { get; private set; }
|
||||
|
||||
private UIElement _closeImage;
|
||||
|
||||
static BoardMenuFrame()
|
||||
{
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(BoardMenuFrame), new FrameworkPropertyMetadata(typeof(BoardMenuFrame)));
|
||||
VisibilityProperty.OverrideMetadata(typeof(BoardMenuFrame),
|
||||
new FrameworkPropertyMetadata(Visibility.Collapsed, OnVisibilityChanged));
|
||||
}
|
||||
|
||||
private static void OnVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((BoardMenuFrame)d).IsOpen = (Visibility)e.NewValue == Visibility.Visible;
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
if (_closeImage != null)
|
||||
{
|
||||
_closeImage.MouseDown -= CloseImage_MouseDown;
|
||||
_closeImage.MouseUp -= CloseImage_MouseUp;
|
||||
}
|
||||
_closeImage = GetTemplateChild(PartCloseImage) as UIElement;
|
||||
if (_closeImage != null)
|
||||
{
|
||||
_closeImage.MouseDown += CloseImage_MouseDown;
|
||||
_closeImage.MouseUp += CloseImage_MouseUp;
|
||||
}
|
||||
AnimationTarget = GetTemplateChild(PartAnimationRoot) as UIElement;
|
||||
}
|
||||
|
||||
private void CloseImage_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
CloseMouseDown?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
private void CloseImage_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
CloseMouseUp?.Invoke(sender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Ink_Canvas.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 画布上的多页 PDF:仅显示当前页;翻页与页码由主窗口 PDF 侧栏控制(无 XAML 文件)。
|
||||
/// </summary>
|
||||
public class PdfEmbeddedView : UserControl
|
||||
{
|
||||
private readonly Image _pageImage;
|
||||
|
||||
private string _pdfPath;
|
||||
private uint _pageCount;
|
||||
private uint _currentIndex;
|
||||
private bool _compressLargePictures;
|
||||
private bool _isPagingBusy;
|
||||
private bool _layoutSizeCommitted;
|
||||
|
||||
/// <summary>页码或可翻页状态变化(用于更新侧栏)。</summary>
|
||||
public event EventHandler PageNavigationStateChanged;
|
||||
|
||||
public PdfEmbeddedView()
|
||||
{
|
||||
MinWidth = 80;
|
||||
MinHeight = 60;
|
||||
|
||||
var grid = new Grid { ClipToBounds = true };
|
||||
_pageImage = new Image
|
||||
{
|
||||
Stretch = Stretch.Uniform,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
grid.Children.Add(_pageImage);
|
||||
Content = grid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化并显示指定页;由 MainWindow 在 UI 线程创建后调用。
|
||||
/// </summary>
|
||||
/// <param name="initialPageIndex">从 0 开始的页码,超出范围时夹紧到合法区间。</param>
|
||||
public async Task InitializeAsync(string pdfFilePath, uint pageCount, bool compressLargePictures, uint initialPageIndex = 0)
|
||||
{
|
||||
_pdfPath = pdfFilePath ?? throw new ArgumentNullException(nameof(pdfFilePath));
|
||||
_pageCount = pageCount;
|
||||
_compressLargePictures = compressLargePictures;
|
||||
if (_pageCount == 0)
|
||||
_currentIndex = 0;
|
||||
else
|
||||
_currentIndex = initialPageIndex >= _pageCount ? _pageCount - 1 : initialPageIndex;
|
||||
|
||||
await ShowPageAsync(_currentIndex);
|
||||
}
|
||||
|
||||
public string PdfPath => _pdfPath;
|
||||
|
||||
public uint PageCount => _pageCount;
|
||||
|
||||
public uint CurrentPageIndex => _currentIndex;
|
||||
|
||||
public string PageLabelText => _pageCount == 0 ? "" : $"{_currentIndex + 1} / {_pageCount}";
|
||||
|
||||
public bool CanGoPrevious => !_isPagingBusy && _pageCount > 1 && _currentIndex > 0;
|
||||
|
||||
public bool CanGoNext => !_isPagingBusy && _pageCount > 1 && _currentIndex < _pageCount - 1;
|
||||
|
||||
public async Task GoToPreviousPageAsync()
|
||||
{
|
||||
await GoRelativeAsync(-1);
|
||||
}
|
||||
|
||||
public async Task GoToNextPageAsync()
|
||||
{
|
||||
await GoRelativeAsync(1);
|
||||
}
|
||||
|
||||
private void NotifyPageNavigationStateChanged()
|
||||
{
|
||||
PageNavigationStateChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async Task GoRelativeAsync(int delta)
|
||||
{
|
||||
if (_isPagingBusy || _pageCount <= 1)
|
||||
return;
|
||||
int next = (int)_currentIndex + delta;
|
||||
if (next < 0 || next >= _pageCount)
|
||||
return;
|
||||
_currentIndex = (uint)next;
|
||||
await ShowPageAsync(_currentIndex);
|
||||
}
|
||||
|
||||
private async Task ShowPageAsync(uint pageIndex)
|
||||
{
|
||||
_isPagingBusy = true;
|
||||
NotifyPageNavigationStateChanged();
|
||||
try
|
||||
{
|
||||
BitmapSource raw = await PdfWinRtHelper.RenderPageToBitmapSourceAsync(_pdfPath, pageIndex);
|
||||
if (raw == null)
|
||||
return;
|
||||
|
||||
BitmapSource display = ApplyCompressionIfNeeded(raw);
|
||||
_pageImage.Source = display;
|
||||
if (!_layoutSizeCommitted)
|
||||
{
|
||||
bool callerSized = !double.IsNaN(Width) && Width > 0 && !double.IsNaN(Height) && Height > 0;
|
||||
if (!callerSized)
|
||||
{
|
||||
Width = display.PixelWidth;
|
||||
Height = display.PixelHeight;
|
||||
}
|
||||
|
||||
_layoutSizeCommitted = true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isPagingBusy = false;
|
||||
NotifyPageNavigationStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private BitmapSource ApplyCompressionIfNeeded(BitmapSource rendered)
|
||||
{
|
||||
int width = rendered.PixelWidth;
|
||||
int height = rendered.PixelHeight;
|
||||
if (_compressLargePictures && (width > 1920 || height > 1080))
|
||||
{
|
||||
double scaleX = 1920.0 / width;
|
||||
double scaleY = 1080.0 / height;
|
||||
double scale = Math.Min(scaleX, scaleY);
|
||||
return new TransformedBitmap(rendered, new ScaleTransform(scale, scale));
|
||||
}
|
||||
|
||||
return rendered;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<UserControl x:Class="Ink_Canvas.Controls.PptNavBar"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||
xmlns:local="clr-namespace:Ink_Canvas.Controls"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="50" d:DesignWidth="200">
|
||||
<UserControl.Resources>
|
||||
<SolidColorBrush x:Key="PptNavBarItemForeground" Color="#71717a"/>
|
||||
<DataTemplate x:Key="PptPreviewItemTemplate">
|
||||
<Grid Margin="2,4">
|
||||
<Border CornerRadius="8" Background="Transparent" Padding="6">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="28"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
FontSize="12" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PptNavBarItemForeground}"
|
||||
Text="{Binding SlideNumber}"/>
|
||||
<Image Grid.Column="1" Source="{Binding Thumbnail}" Stretch="Uniform"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<Style x:Key="PptPreviewItemContainerStyle" TargetType="ListBoxItem">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border x:Name="Bd" CornerRadius="8" Background="Transparent"
|
||||
BorderBrush="Transparent" BorderThickness="2">
|
||||
<ContentPresenter/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#1affffff"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="#66CCFF"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Border x:Name="RootBorder" BorderThickness="1" BorderBrush="#a1a1aa"
|
||||
Background="#f4f4f5" Opacity="1" CornerRadius="6">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="#000000" BlurRadius="14" ShadowDepth="2" Opacity="0.16"/>
|
||||
</Border.Effect>
|
||||
<DockPanel x:Name="LayoutRoot" LastChildFill="True">
|
||||
<ListBox x:Name="PreviewList"
|
||||
Visibility="Collapsed"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Padding="6,8,6,4"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ItemTemplate="{StaticResource PptPreviewItemTemplate}"
|
||||
ItemContainerStyle="{StaticResource PptPreviewItemContainerStyle}"
|
||||
MouseUp="PreviewList_MouseUp"/>
|
||||
<ikw:SimpleStackPanel x:Name="ButtonRow" Orientation="Horizontal"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Border x:Name="PreviousButtonBorder" Width="50" Height="50"
|
||||
MouseDown="PreviousButton_MouseDown"
|
||||
MouseUp="PreviousButton_MouseUp"
|
||||
MouseLeave="PreviousButton_MouseLeave"
|
||||
CornerRadius="5 5 0 0" Background="Transparent">
|
||||
<Grid>
|
||||
<Border x:Name="PreviousButtonFeedbackBorder" Margin="3" CornerRadius="5"
|
||||
Background="#18181b" Opacity="0"/>
|
||||
<Image Height="28" Width="28">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing x:Name="PreviousButtonGeometry" Brush="#27272a"/>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border x:Name="PageButtonBorder" Visibility="Visible" TextBlock.Foreground="#171717"
|
||||
MouseDown="PageButton_MouseDown"
|
||||
MouseUp="PageButton_MouseUp"
|
||||
MouseLeave="PageButton_MouseLeave"
|
||||
MinWidth="50" MinHeight="50" Background="Transparent">
|
||||
<Grid>
|
||||
<Border x:Name="PageButtonFeedbackBorder" Margin="0,3" CornerRadius="5"
|
||||
Background="#18181b" Opacity="0"/>
|
||||
<ikw:SimpleStackPanel VerticalAlignment="Center" Orientation="Vertical"
|
||||
Spacing="0.5" Margin="12,0">
|
||||
<TextBlock x:Name="PageNowText" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="17" FontWeight="Bold" Text="?"/>
|
||||
<TextBlock x:Name="PageTotalText" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="10" Text="/ ?"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border x:Name="NextButtonBorder" Width="50" Height="50"
|
||||
MouseDown="NextButton_MouseDown"
|
||||
MouseUp="NextButton_MouseUp"
|
||||
MouseLeave="NextButton_MouseLeave"
|
||||
CornerRadius="0 0 5 5" Background="Transparent">
|
||||
<Grid>
|
||||
<Border x:Name="NextButtonFeedbackBorder" Margin="3" CornerRadius="5"
|
||||
Background="#18181b" Opacity="0"/>
|
||||
<Image VerticalAlignment="Center" HorizontalAlignment="Center" Height="28" Width="28">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing x:Name="NextButtonGeometry" Brush="#27272a"/>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ikw:SimpleStackPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,393 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// PPT 翻页 + 增强预览一体化控件。
|
||||
/// 通过 <see cref="Direction"/> 切换底部条 (LB/RB) 与侧边条 (LS/RS) 布局,
|
||||
/// 预览列表内嵌于同一个 Border,展开时占据按钮组之外的剩余空间。
|
||||
/// </summary>
|
||||
public partial class PptNavBar : UserControl
|
||||
{
|
||||
public sealed class PreviewItem
|
||||
{
|
||||
public int SlideNumber { get; set; }
|
||||
public BitmapImage Thumbnail { get; set; }
|
||||
}
|
||||
|
||||
public enum NavDirection
|
||||
{
|
||||
LeftBottom,
|
||||
RightBottom,
|
||||
LeftSide,
|
||||
RightSide
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DirectionProperty = DependencyProperty.Register(
|
||||
nameof(Direction), typeof(NavDirection), typeof(PptNavBar),
|
||||
new PropertyMetadata(NavDirection.LeftBottom, OnDirectionChanged));
|
||||
|
||||
public static readonly DependencyProperty CurrentSlideProperty = DependencyProperty.Register(
|
||||
nameof(CurrentSlide), typeof(int), typeof(PptNavBar),
|
||||
new PropertyMetadata(0, OnPageChanged));
|
||||
|
||||
public static readonly DependencyProperty TotalSlidesProperty = DependencyProperty.Register(
|
||||
nameof(TotalSlides), typeof(int), typeof(PptNavBar),
|
||||
new PropertyMetadata(0, OnPageChanged));
|
||||
|
||||
public static readonly DependencyProperty PreviewItemsProperty = DependencyProperty.Register(
|
||||
nameof(PreviewItems), typeof(IList<PreviewItem>), typeof(PptNavBar),
|
||||
new PropertyMetadata(null, OnPreviewItemsChanged));
|
||||
|
||||
public static readonly DependencyProperty IsPreviewExpandedProperty = DependencyProperty.Register(
|
||||
nameof(IsPreviewExpanded), typeof(bool), typeof(PptNavBar),
|
||||
new PropertyMetadata(false, OnIsPreviewExpandedChanged));
|
||||
|
||||
public NavDirection Direction
|
||||
{
|
||||
get => (NavDirection)GetValue(DirectionProperty);
|
||||
set => SetValue(DirectionProperty, value);
|
||||
}
|
||||
|
||||
public int CurrentSlide
|
||||
{
|
||||
get => (int)GetValue(CurrentSlideProperty);
|
||||
set => SetValue(CurrentSlideProperty, value);
|
||||
}
|
||||
|
||||
public int TotalSlides
|
||||
{
|
||||
get => (int)GetValue(TotalSlidesProperty);
|
||||
set => SetValue(TotalSlidesProperty, value);
|
||||
}
|
||||
|
||||
public IList<PreviewItem> PreviewItems
|
||||
{
|
||||
get => (IList<PreviewItem>)GetValue(PreviewItemsProperty);
|
||||
set => SetValue(PreviewItemsProperty, value);
|
||||
}
|
||||
|
||||
public bool IsPreviewExpanded
|
||||
{
|
||||
get => (bool)GetValue(IsPreviewExpandedProperty);
|
||||
set => SetValue(IsPreviewExpandedProperty, value);
|
||||
}
|
||||
|
||||
public event EventHandler PreviousClick;
|
||||
public event EventHandler NextClick;
|
||||
public event EventHandler PageClick;
|
||||
public event EventHandler<int> SlideSelected;
|
||||
public event EventHandler PreviousPressedDown;
|
||||
public event EventHandler NextPressedDown;
|
||||
public event EventHandler PressEnded;
|
||||
public event EventHandler<bool> PreviewExpandedChanged;
|
||||
|
||||
// 静态几何(左下/右下:水平箭头;左侧/右侧:垂直箭头)
|
||||
private static readonly Geometry HArrowLeft = Geometry.Parse("F0 M24,24z M0,0z M3.3994,12.9642C2.86687,12.4317,2.86687,11.5683,3.3994,11.0358L9.94485,4.49031C10.4774,3.95777 11.3408,3.95777 11.8733,4.49031 12.4059,5.02284 12.4059,5.88625 11.8733,6.41878L7.65575,10.6364 19.6364,10.6364C20.3895,10.6364 21,11.2469 21,12 21,12.7531 20.3895,13.3636 19.6364,13.3636L7.65575,13.3636 11.8733,17.5812C12.4059,18.1137 12.4059,18.9772 11.8733,19.5097 11.3408,20.0422 10.4774,20.0422 9.94485,19.5097L3.3994,12.9642z");
|
||||
private static readonly Geometry HArrowRight = Geometry.Parse("F0 M24,24z M0,0z M20.6006,12.9642C21.1331,12.4317,21.1331,11.5683,20.6006,11.0358L14.0551,4.49031C13.5226,3.95777 12.6592,3.95777 12.1267,4.49031 11.5941,5.02284 11.5941,5.88625 12.1267,6.41878L16.3443,10.6364 4.36364,10.6364C3.61052,10.6364 3,11.2469 3,12 3,12.7531 3.61052,13.3636 4.36364,13.3636L16.3443,13.3636 12.1267,17.5812C11.5941,18.1137 11.5941,18.9772 12.1267,19.5097 12.6592,20.0422 13.5226,20.0422 14.0551,19.5097L20.6006,12.9642z");
|
||||
private static readonly Geometry VArrowUp = Geometry.Parse("F0 M24,24z M0,0z M11.0357,3.3994C11.5682,2.86687,12.4316,2.86687,12.9641,3.3994L19.5096,9.94485C20.0421,10.4774 20.0421,11.3408 19.5096,11.8733 18.9771,12.4059 18.1137,12.4059 17.5811,11.8733L13.3635,7.65575 13.3635,19.6364C13.3635,20.3895 12.753,21 11.9999,21 11.2468,21 10.6363,20.3895 10.6363,19.6364L10.6363,7.65575 6.41869,11.8733C5.88616,12.4059 5.02275,12.4059 4.49022,11.8733 3.95769,11.3408 3.95769,10.4774 4.49022,9.94485L11.0357,3.3994z");
|
||||
private static readonly Geometry VArrowDown = Geometry.Parse("F0 M24,24z M0,0z M11.0357,20.6006C11.5682,21.1331,12.4316,21.1331,12.9641,20.6006L19.5096,14.0551C20.0421,13.5226 20.0421,12.6592 19.5096,12.1267 18.9771,11.5941 18.1137,11.5941 17.5811,12.1267L13.3635,16.3443 13.3635,4.36364C13.3635,3.61052 12.753,3 11.9999,3 11.2468,3 10.6363,3.61052 10.6363,4.36364L10.6363,16.3443 6.41869,12.1267C5.88616,11.5941 5.02275,11.5941 4.49022,12.1267 3.95769,12.6592 3.95769,13.5226 4.49022,14.0551L11.0357,20.6006z");
|
||||
|
||||
public PptNavBar()
|
||||
{
|
||||
InitializeComponent();
|
||||
ApplyDirection(Direction);
|
||||
}
|
||||
|
||||
private static void OnDirectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is PptNavBar bar) bar.ApplyDirection((NavDirection)e.NewValue);
|
||||
}
|
||||
|
||||
private static void OnPageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is PptNavBar bar) bar.RefreshPageText();
|
||||
}
|
||||
|
||||
private static void OnPreviewItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is PptNavBar bar)
|
||||
{
|
||||
bar.PreviewList.ItemsSource = e.NewValue as IList<PreviewItem>;
|
||||
bar.SyncPreviewSelection();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnIsPreviewExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is PptNavBar bar)
|
||||
{
|
||||
bool expanded = (bool)e.NewValue;
|
||||
bar.PreviewList.Visibility = expanded ? Visibility.Visible : Visibility.Collapsed;
|
||||
bar.ApplyLayout();
|
||||
if (expanded)
|
||||
{
|
||||
bar.SyncPreviewSelection();
|
||||
bar.HookOutsideClick();
|
||||
}
|
||||
else
|
||||
{
|
||||
bar.UnhookOutsideClick();
|
||||
}
|
||||
bar.PreviewExpandedChanged?.Invoke(bar, expanded);
|
||||
}
|
||||
}
|
||||
|
||||
private Window _hookedWindow;
|
||||
private void HookOutsideClick()
|
||||
{
|
||||
if (_hookedWindow != null) return;
|
||||
_hookedWindow = Window.GetWindow(this);
|
||||
if (_hookedWindow != null)
|
||||
{
|
||||
_hookedWindow.PreviewMouseDown += OnWindowPreviewMouseDown;
|
||||
}
|
||||
}
|
||||
private void UnhookOutsideClick()
|
||||
{
|
||||
if (_hookedWindow != null)
|
||||
{
|
||||
_hookedWindow.PreviewMouseDown -= OnWindowPreviewMouseDown;
|
||||
_hookedWindow = null;
|
||||
}
|
||||
}
|
||||
private void OnWindowPreviewMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is DependencyObject d && !IsDescendantOf(d, this))
|
||||
{
|
||||
IsPreviewExpanded = false;
|
||||
}
|
||||
}
|
||||
private static bool IsDescendantOf(DependencyObject child, DependencyObject ancestor)
|
||||
{
|
||||
while (child != null)
|
||||
{
|
||||
if (ReferenceEquals(child, ancestor)) return true;
|
||||
child = System.Windows.Media.VisualTreeHelper.GetParent(child)
|
||||
?? System.Windows.LogicalTreeHelper.GetParent(child);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ApplyDirection(NavDirection dir) => ApplyLayout();
|
||||
|
||||
private void ApplyLayout()
|
||||
{
|
||||
var dir = Direction;
|
||||
bool expanded = IsPreviewExpanded;
|
||||
|
||||
// 重置可能在不同状态下被设置的属性
|
||||
ButtonRow.ClearValue(WidthProperty);
|
||||
ButtonRow.ClearValue(HeightProperty);
|
||||
ButtonRow.ClearValue(HorizontalAlignmentProperty);
|
||||
PreviewList.ClearValue(WidthProperty);
|
||||
PreviewList.ClearValue(HeightProperty);
|
||||
PreviewList.ClearValue(MaxHeightProperty);
|
||||
PreviewList.ClearValue(MaxWidthProperty);
|
||||
PreviewList.ClearValue(HorizontalAlignmentProperty);
|
||||
ClearValue(HeightProperty);
|
||||
ClearValue(MaxHeightProperty);
|
||||
|
||||
double availableHeight = ComputeAvailableHeight();
|
||||
|
||||
switch (dir)
|
||||
{
|
||||
case NavDirection.LeftBottom:
|
||||
case NavDirection.RightBottom:
|
||||
DockPanel.SetDock(PreviewList, Dock.Top);
|
||||
DockPanel.SetDock(ButtonRow, Dock.Bottom);
|
||||
ButtonRow.Orientation = Orientation.Horizontal;
|
||||
ButtonRow.Height = 50;
|
||||
if (expanded)
|
||||
{
|
||||
// 预览面板拉宽到 280,贴向同侧角落
|
||||
PreviewList.Width = 280;
|
||||
PreviewList.MaxHeight = Math.Max(200, availableHeight - 50);
|
||||
PreviewList.HorizontalAlignment = dir == NavDirection.LeftBottom
|
||||
? HorizontalAlignment.Left
|
||||
: HorizontalAlignment.Right;
|
||||
// 按钮组宽度限制为原始内容宽度,并贴向同侧,保持按钮位置不变
|
||||
ButtonRow.HorizontalAlignment = dir == NavDirection.LeftBottom
|
||||
? HorizontalAlignment.Left
|
||||
: HorizontalAlignment.Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
PreviewList.SetBinding(WidthProperty, new System.Windows.Data.Binding(nameof(ButtonRow.ActualWidth)) { Source = ButtonRow });
|
||||
PreviewList.MaxHeight = 380;
|
||||
}
|
||||
PreviousButtonGeometry.Geometry = HArrowLeft;
|
||||
NextButtonGeometry.Geometry = HArrowRight;
|
||||
break;
|
||||
|
||||
case NavDirection.LeftSide:
|
||||
DockPanel.SetDock(PreviewList, Dock.Right);
|
||||
DockPanel.SetDock(ButtonRow, Dock.Left);
|
||||
ButtonRow.Orientation = Orientation.Vertical;
|
||||
ButtonRow.Width = 50;
|
||||
PreviewList.Width = 240;
|
||||
PreviewList.MaxHeight = 480;
|
||||
PreviousButtonGeometry.Geometry = VArrowUp;
|
||||
NextButtonGeometry.Geometry = VArrowDown;
|
||||
break;
|
||||
case NavDirection.RightSide:
|
||||
DockPanel.SetDock(PreviewList, Dock.Left);
|
||||
DockPanel.SetDock(ButtonRow, Dock.Right);
|
||||
ButtonRow.Orientation = Orientation.Vertical;
|
||||
ButtonRow.Width = 50;
|
||||
PreviewList.Width = 240;
|
||||
PreviewList.MaxHeight = 480;
|
||||
PreviousButtonGeometry.Geometry = VArrowUp;
|
||||
NextButtonGeometry.Geometry = VArrowDown;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private double ComputeAvailableHeight()
|
||||
{
|
||||
var window = Window.GetWindow(this);
|
||||
double h = window != null ? window.ActualHeight : SystemParameters.PrimaryScreenHeight;
|
||||
return Math.Max(240, h - 12);
|
||||
}
|
||||
|
||||
private void RefreshPageText()
|
||||
{
|
||||
if (CurrentSlide > 0 && TotalSlides > 0)
|
||||
{
|
||||
PageNowText.Text = CurrentSlide.ToString();
|
||||
PageTotalText.Text = $"/ {TotalSlides}";
|
||||
}
|
||||
else
|
||||
{
|
||||
PageNowText.Text = "?";
|
||||
PageTotalText.Text = "/ ?";
|
||||
}
|
||||
SyncPreviewSelection();
|
||||
}
|
||||
|
||||
private void SyncPreviewSelection()
|
||||
{
|
||||
if (PreviewItems == null || CurrentSlide <= 0) return;
|
||||
foreach (var item in PreviewItems)
|
||||
{
|
||||
if (item.SlideNumber == CurrentSlide)
|
||||
{
|
||||
PreviewList.SelectedItem = item;
|
||||
PreviewList.ScrollIntoView(item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetFeedback(Border feedback, double opacity) => feedback.Opacity = opacity;
|
||||
|
||||
private object _lastDown;
|
||||
|
||||
private void PreviousButton_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_lastDown = sender;
|
||||
SetFeedback(PreviousButtonFeedbackBorder, 0.15);
|
||||
PreviousPressedDown?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void PreviousButton_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
SetFeedback(PreviousButtonFeedbackBorder, 0);
|
||||
PressEnded?.Invoke(this, EventArgs.Empty);
|
||||
if (_lastDown != sender) return;
|
||||
_lastDown = null;
|
||||
PreviousClick?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void PreviousButton_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
SetFeedback(PreviousButtonFeedbackBorder, 0);
|
||||
_lastDown = null;
|
||||
PressEnded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void NextButton_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_lastDown = sender;
|
||||
SetFeedback(NextButtonFeedbackBorder, 0.15);
|
||||
NextPressedDown?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void NextButton_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
SetFeedback(NextButtonFeedbackBorder, 0);
|
||||
PressEnded?.Invoke(this, EventArgs.Empty);
|
||||
if (_lastDown != sender) return;
|
||||
_lastDown = null;
|
||||
NextClick?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void NextButton_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
SetFeedback(NextButtonFeedbackBorder, 0);
|
||||
_lastDown = null;
|
||||
PressEnded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void PageButton_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_lastDown = sender;
|
||||
SetFeedback(PageButtonFeedbackBorder, 0.15);
|
||||
}
|
||||
|
||||
private void PageButton_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
SetFeedback(PageButtonFeedbackBorder, 0);
|
||||
if (_lastDown != sender) return;
|
||||
_lastDown = null;
|
||||
PageClick?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void PageButton_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
SetFeedback(PageButtonFeedbackBorder, 0);
|
||||
_lastDown = null;
|
||||
}
|
||||
|
||||
private void PreviewList_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (PreviewList.SelectedItem is PreviewItem item)
|
||||
{
|
||||
SlideSelected?.Invoke(this, item.SlideNumber);
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyTheme(bool isDark)
|
||||
{
|
||||
var fgBrush = isDark ? Brushes.White : new SolidColorBrush(Color.FromRgb(39, 39, 42));
|
||||
var feedbackBrush = isDark ? Brushes.White : new SolidColorBrush(Color.FromRgb(24, 24, 27));
|
||||
var bgBrush = isDark
|
||||
? new SolidColorBrush(Color.FromRgb(39, 39, 42))
|
||||
: new SolidColorBrush(Color.FromRgb(244, 244, 245));
|
||||
var borderBrush = isDark
|
||||
? new SolidColorBrush(Color.FromRgb(82, 82, 91))
|
||||
: new SolidColorBrush(Color.FromRgb(161, 161, 170));
|
||||
|
||||
PreviousButtonGeometry.Brush = fgBrush;
|
||||
NextButtonGeometry.Brush = fgBrush;
|
||||
PreviousButtonFeedbackBorder.Background = feedbackBrush;
|
||||
NextButtonFeedbackBorder.Background = feedbackBrush;
|
||||
PageButtonFeedbackBorder.Background = feedbackBrush;
|
||||
PageNowText.Foreground = fgBrush;
|
||||
PageTotalText.Foreground = fgBrush;
|
||||
RootBorder.Background = bgBrush;
|
||||
RootBorder.BorderBrush = borderBrush;
|
||||
Resources["PptNavBarItemForeground"] = fgBrush;
|
||||
}
|
||||
|
||||
public void SetPageButtonVisibility(Visibility v) => PageButtonBorder.Visibility = v;
|
||||
public void SetBarOpacity(double opacity) => RootBorder.Opacity = opacity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<UserControl x:Class="Ink_Canvas.Controls.QuickDrawFloatingButtonControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Width="65" Height="45">
|
||||
|
||||
<Border Background="{DynamicResource QuickDrawFloatingButtonBackground}"
|
||||
CornerRadius="8"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource QuickDrawFloatingButtonBorderBrush}">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="Black" Direction="315" ShadowDepth="3" Opacity="0.3" BlurRadius="5"/>
|
||||
</Border.Effect>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="22"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 拖动区域 -->
|
||||
<Border Grid.Column="0"
|
||||
MouseLeftButtonDown="DragArea_MouseLeftButtonDown"
|
||||
MouseMove="DragArea_MouseMove"
|
||||
MouseLeftButtonUp="DragArea_MouseLeftButtonUp"
|
||||
Cursor="SizeAll"
|
||||
Background="Transparent">
|
||||
<Grid VerticalAlignment="Center" Height="14" IsHitTestVisible="False">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 三个白色横线 -->
|
||||
<Border Grid.Row="0" Background="{DynamicResource QuickDrawFloatingButtonIconForeground}" Height="2" Width="10"
|
||||
HorizontalAlignment="Center"
|
||||
CornerRadius="1" Opacity="0.8" IsHitTestVisible="False"/>
|
||||
<Border Grid.Row="2" Background="{DynamicResource QuickDrawFloatingButtonIconForeground}" Height="2" Width="10"
|
||||
HorizontalAlignment="Center"
|
||||
CornerRadius="1" Opacity="0.8" IsHitTestVisible="False"/>
|
||||
<Border Grid.Row="4" Background="{DynamicResource QuickDrawFloatingButtonIconForeground}" Height="2" Width="10"
|
||||
HorizontalAlignment="Center"
|
||||
CornerRadius="1" Opacity="0.8" IsHitTestVisible="False"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 半透明分割线 -->
|
||||
<Rectangle Grid.Column="1" Width="1" Fill="#20FFFFFF" Margin="0,8,0,8"/>
|
||||
|
||||
<!-- 按钮区域 -->
|
||||
<Border Grid.Column="2"
|
||||
MouseLeftButtonDown="FloatingButton_Click"
|
||||
Cursor="Hand"
|
||||
Background="Transparent">
|
||||
<Grid IsHitTestVisible="False">
|
||||
<Path Data="M5 7C5 8.06087 5.42143 9.07828 6.17157 9.82843C6.92172 10.5786 7.93913 11 9 11C10.0609 11 11.0783 10.5786 11.8284 9.82843C12.5786 9.07828 13 8.06087 13 7C13 5.93913 12.5786 4.92172 11.8284 4.17157C11.0783 3.42143 10.0609 3 9 3C7.93913 3 6.92172 3.42143 6.17157 4.17157C5.42143 4.92172 5 5.93913 5 7Z M3 21V19C3 17.9391 3.42143 16.9217 4.17157 16.1716C4.92172 15.4214 5.93913 15 7 15H11C12.0609 15 13.0783 15.4214 13.8284 16.1716C14.5786 16.9217 15 17.9391 15 19V21 M16 3.13C16.8604 3.35031 17.623 3.85071 18.1676 4.55232C18.7122 5.25392 19.0078 6.11683 19.0078 7.005C19.0078 7.89318 18.7122 8.75608 18.1676 9.45769C17.623 10.1593 16.8604 10.6597 16 10.88 M21 21V19C20.9949 18.1172 20.6979 17.2608 20.1553 16.5644C19.6126 15.868 18.8548 15.3707 18 15.15"
|
||||
Stroke="{DynamicResource QuickDrawFloatingButtonIconForeground}"
|
||||
StrokeThickness="2"
|
||||
StrokeLineJoin="Round"
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsHitTestVisible="False"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using HorizontalAlignment = System.Windows.HorizontalAlignment;
|
||||
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
|
||||
using VerticalAlignment = System.Windows.VerticalAlignment;
|
||||
|
||||
namespace Ink_Canvas.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 快抽悬浮按钮控件
|
||||
/// </summary>
|
||||
public partial class QuickDrawFloatingButtonControl : UserControl
|
||||
{
|
||||
private bool _isDragging = false;
|
||||
private Point _dragStartPoint;
|
||||
private Point _controlStartPoint;
|
||||
|
||||
public QuickDrawFloatingButtonControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 快抽按钮点击事件
|
||||
/// </summary>
|
||||
private void FloatingButton_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果正在拖动,不触发点击事件
|
||||
if (_isDragging) return;
|
||||
|
||||
// 打开快抽窗口
|
||||
var quickDrawWindow = new QuickDrawWindow();
|
||||
quickDrawWindow.ShowDialog();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Helpers.LogHelper.WriteLogToFile($"打开快抽窗口失败: {ex.Message}", Helpers.LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拖动区域鼠标按下事件
|
||||
/// </summary>
|
||||
private void DragArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_isDragging = false;
|
||||
|
||||
// 记录鼠标在屏幕上的初始位置
|
||||
_dragStartPoint = this.PointToScreen(e.GetPosition(this));
|
||||
|
||||
// 记录控件的初始位置
|
||||
var parent = this.Parent as FrameworkElement;
|
||||
if (parent != null)
|
||||
{
|
||||
var transform = this.TransformToVisual(parent);
|
||||
var currentPos = transform.Transform(new Point(0, 0));
|
||||
_controlStartPoint = currentPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
var currentMargin = this.Margin;
|
||||
_controlStartPoint = new Point(
|
||||
double.IsNaN(currentMargin.Left) ? 0 : currentMargin.Left,
|
||||
double.IsNaN(currentMargin.Top) ? 0 : currentMargin.Top);
|
||||
}
|
||||
|
||||
((UIElement)sender).CaptureMouse();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拖动区域鼠标移动事件
|
||||
/// </summary>
|
||||
private void DragArea_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed && ((UIElement)sender).IsMouseCaptured)
|
||||
{
|
||||
// 获取鼠标在屏幕上的当前位置
|
||||
Point currentScreenPoint = this.PointToScreen(e.GetPosition(this));
|
||||
Vector diff = currentScreenPoint - _dragStartPoint;
|
||||
|
||||
if (!_isDragging && (Math.Abs(diff.X) > 3 || Math.Abs(diff.Y) > 3))
|
||||
{
|
||||
_isDragging = true;
|
||||
// 切换到绝对定位模式
|
||||
this.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
this.VerticalAlignment = VerticalAlignment.Top;
|
||||
}
|
||||
|
||||
if (_isDragging)
|
||||
{
|
||||
// 计算新位置
|
||||
var parent = this.Parent as FrameworkElement;
|
||||
if (parent != null)
|
||||
{
|
||||
// 计算屏幕坐标相对于父容器的位置
|
||||
var parentPoint = parent.PointFromScreen(currentScreenPoint);
|
||||
var startParentPoint = parent.PointFromScreen(_dragStartPoint);
|
||||
|
||||
// 计算相对于初始位置的偏移
|
||||
double offsetX = parentPoint.X - startParentPoint.X;
|
||||
double offsetY = parentPoint.Y - startParentPoint.Y;
|
||||
|
||||
// 新位置 = 初始位置 + 偏移
|
||||
double newLeft = _controlStartPoint.X + offsetX;
|
||||
double newTop = _controlStartPoint.Y + offsetY;
|
||||
|
||||
// 限制在父容器范围内
|
||||
newLeft = Math.Max(0, Math.Min(newLeft, parent.ActualWidth - this.ActualWidth));
|
||||
newTop = Math.Max(0, Math.Min(newTop, parent.ActualHeight - this.ActualHeight));
|
||||
|
||||
// 更新Margin
|
||||
this.Margin = new Thickness(newLeft, newTop, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拖动区域鼠标释放事件
|
||||
/// </summary>
|
||||
private void DragArea_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (((UIElement)sender).IsMouseCaptured)
|
||||
{
|
||||
((UIElement)sender).ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
if (_isDragging)
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() => { _isDragging = false; }),
|
||||
DispatcherPriority.Background);
|
||||
}
|
||||
else
|
||||
{
|
||||
_isDragging = false;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar
|
||||
{
|
||||
/// <summary>
|
||||
/// 工具栏按钮插件与宿主之间的桥梁。Phase 1 粗粒度暴露 MainWindow,后续收窄。
|
||||
/// </summary>
|
||||
public interface IToolbarHost
|
||||
{
|
||||
MainWindow Window { get; }
|
||||
|
||||
/// <summary>按 id 登记按钮的 view 实例(供 MainWindow 字段回填和互相查找)。</summary>
|
||||
void RegisterView(string id, FrameworkElement view);
|
||||
|
||||
/// <summary>按 id 获取之前注册的 view。不存在返回 null。</summary>
|
||||
FrameworkElement FindView(string id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar
|
||||
{
|
||||
/// <summary>
|
||||
/// 一个工具栏按钮(或任意浮动栏/白板栏条目)的插件化契约。
|
||||
/// 实现类必须有无参构造函数,启动时会被 ToolbarRegistry 反射实例化。
|
||||
/// </summary>
|
||||
public interface IToolbarItem
|
||||
{
|
||||
/// <summary>稳定、唯一的 id,用于持久化用户配置。不要随便改。</summary>
|
||||
string Id { get; }
|
||||
|
||||
ToolbarSlot DefaultSlot { get; }
|
||||
|
||||
/// <summary>同一 slot 内的默认顺序,小的在前。</summary>
|
||||
int DefaultOrder { get; }
|
||||
|
||||
bool DefaultVisible { get; }
|
||||
|
||||
ToolbarInsertPosition DefaultPosition { get; }
|
||||
|
||||
/// <summary>仅当 Position 为 BeforeAnchor/AfterAnchor 时有意义,对应 XAML 里 x:Name。</summary>
|
||||
string DefaultAnchorName { get; }
|
||||
|
||||
/// <summary>构造 UI 元素并接线所有行为。</summary>
|
||||
FrameworkElement BuildView(IToolbarHost host);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// 清空按钮。位置:夹在颜色面板与 StackPanelCanvasControls 之间,
|
||||
/// 所以用 BeforeAnchor 锚到 StackPanelCanvasControls。
|
||||
/// </summary>
|
||||
internal sealed class ClearToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.clear";
|
||||
public override string LocalizationKey => "FloatingBar_Clear";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarMain;
|
||||
public override int DefaultOrder => 0;
|
||||
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.BeforeAnchor;
|
||||
public override string DefaultAnchorName => "StackPanelCanvasControls";
|
||||
|
||||
protected override string IconBrushResourceKey => "RedBrush";
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.SymbolIconDelete_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachSymbolIconDelete(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class CursorToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.cursor";
|
||||
public override string LocalizationKey => "FloatingBar_Mouse";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarMain;
|
||||
public override int DefaultOrder => 100;
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.CursorIcon_Click(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachCursorIconView(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class CursorWithDelToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.cursorWithDel";
|
||||
public override string LocalizationKey => "FloatingBar_ClearAndMouse";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||
public override int DefaultOrder => 320;
|
||||
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.Append;
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.CursorWithDelIcon_Click(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachCursorWithDelBtn(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class EraserByStrokesToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.eraserByStrokes";
|
||||
public override string LocalizationKey => "FloatingBar_StrokeEraser";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||
public override int DefaultOrder => 110;
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.EraserIconByStrokes_Click(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachEraserByStrokesIcon(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class EraserToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.eraser";
|
||||
public override string LocalizationKey => "FloatingBar_AreaEraser";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||
public override int DefaultOrder => 100;
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.EraserIcon_Click(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachEraserIcon(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class FoldToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.fold";
|
||||
public override string LocalizationKey => "FloatingBar_Hide";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarEnd;
|
||||
public override int DefaultOrder => 120;
|
||||
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.AfterAnchor;
|
||||
public override string DefaultAnchorName => "FloatingBarEndSeparator";
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.FoldFloatingBar_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachFoldIcon(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class PenToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.pen";
|
||||
public override string LocalizationKey => "FloatingBar_Annotate";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarMain;
|
||||
public override int DefaultOrder => 110;
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.PenIcon_Click(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachPenIconView(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class RedoToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.redo";
|
||||
public override string LocalizationKey => "Board_Redo";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||
public override int DefaultOrder => 310;
|
||||
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.Append;
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.SymbolIconRedo_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
{
|
||||
host.Window.AttachSymbolIconRedo(view);
|
||||
view.SetBinding(System.Windows.UIElement.IsEnabledProperty,
|
||||
new System.Windows.Data.Binding("IsEnabled") { ElementName = "BtnRedo" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class SelectToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.select";
|
||||
public override string LocalizationKey => "FloatingBar_LassoSelect";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||
public override int DefaultOrder => 120;
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.SymbolIconSelect_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachSymbolIconSelect(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class ShapeDrawToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.shapeDraw";
|
||||
public override string LocalizationKey => "FloatingBar_Geometry";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||
public override int DefaultOrder => 130;
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.ImageDrawShape_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachShapeDrawBtn(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Ink_Canvas.Properties;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用 ToolbarImageButton 工具栏条目基类——大幅减少每个按钮的样板代码。
|
||||
/// 派生类通常只需给 Id / 本地化键 / Slot / Order / 点击处理 / Attach 回填。
|
||||
/// </summary>
|
||||
internal abstract class ToolbarImageButtonItemBase : IToolbarItem
|
||||
{
|
||||
public abstract string Id { get; }
|
||||
public abstract string LocalizationKey { get; }
|
||||
public abstract ToolbarSlot DefaultSlot { get; }
|
||||
public abstract int DefaultOrder { get; }
|
||||
public virtual bool DefaultVisible => true;
|
||||
public virtual ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.Prepend;
|
||||
public virtual string DefaultAnchorName => null;
|
||||
|
||||
/// <summary>DynamicResource 名称,用于 IconBrush。默认为 null(使用控件自带前景色)。</summary>
|
||||
protected virtual string IconBrushResourceKey => null;
|
||||
|
||||
protected abstract void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e);
|
||||
|
||||
/// <summary>构建后调用,用于回填 MainWindow 的原命名属性(partial 扩展里的 Attach*)。可选。</summary>
|
||||
protected virtual void AfterBuild(IToolbarHost host, ToolbarImageButton view) { }
|
||||
|
||||
public FrameworkElement BuildView(IToolbarHost host)
|
||||
{
|
||||
var btn = new ToolbarImageButton
|
||||
{
|
||||
Label = Strings.GetString(LocalizationKey) ?? LocalizationKey
|
||||
};
|
||||
if (!string.IsNullOrEmpty(IconBrushResourceKey))
|
||||
{
|
||||
if (btn.TryFindResource(IconBrushResourceKey) is Brush brush) btn.IconBrush = brush;
|
||||
else btn.SetResourceReference(ToolbarImageButton.IconBrushProperty, IconBrushResourceKey);
|
||||
}
|
||||
btn.ButtonMouseUp += (s, e) => OnClick(host, s, e);
|
||||
AfterBuild(host, btn);
|
||||
return btn;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class ToolsToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.tools";
|
||||
public override string LocalizationKey => "Board_Tools";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarEnd;
|
||||
public override int DefaultOrder => 110;
|
||||
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.AfterAnchor;
|
||||
public override string DefaultAnchorName => "FloatingBarEndSeparator";
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.SymbolIconTools_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachToolsBtn(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class UndoToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.undo";
|
||||
public override string LocalizationKey => "Board_Undo";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||
public override int DefaultOrder => 300;
|
||||
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.Append;
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.SymbolIconUndo_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
{
|
||||
host.Window.AttachSymbolIconUndo(view);
|
||||
view.SetBinding(System.Windows.UIElement.IsEnabledProperty,
|
||||
new System.Windows.Data.Binding("IsEnabled") { ElementName = "BtnUndo" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class WhiteboardToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.whiteboard";
|
||||
public override string LocalizationKey => "FloatingBar_Whiteboard";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarEnd;
|
||||
public override int DefaultOrder => 100;
|
||||
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.AfterAnchor;
|
||||
public override string DefaultAnchorName => "FloatingBarEndSeparator";
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.ImageBlackboard_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachWhiteboardBtn(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar
|
||||
{
|
||||
/// <summary>
|
||||
/// MainWindow 版的 IToolbarHost 实现。Phase 1 直接把 MainWindow 引用暴露给插件,
|
||||
/// 插件可通过 host.Window 访问私有/内部成员(partial class 扩展或 internal 字段)。
|
||||
/// 后续阶段逐步把具体行为抽成 Host 上的方法/事件,收窄这个接口。
|
||||
/// </summary>
|
||||
public sealed class ToolbarHost : IToolbarHost
|
||||
{
|
||||
private readonly Dictionary<string, FrameworkElement> _views = new Dictionary<string, FrameworkElement>();
|
||||
|
||||
public ToolbarHost(MainWindow window)
|
||||
{
|
||||
Window = window;
|
||||
}
|
||||
|
||||
public MainWindow Window { get; }
|
||||
|
||||
public void RegisterView(string id, FrameworkElement view)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id) || view == null) return;
|
||||
_views[id] = view;
|
||||
}
|
||||
|
||||
public FrameworkElement FindView(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id)) return null;
|
||||
return _views.TryGetValue(id, out var v) ? v : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Ink_Canvas.Controls.Toolbar
|
||||
{
|
||||
public enum ToolbarInsertPosition
|
||||
{
|
||||
/// <summary>从容器头部依次插入;Order 小的在前。</summary>
|
||||
Prepend,
|
||||
/// <summary>追加到容器末尾。</summary>
|
||||
Append,
|
||||
/// <summary>插入到由 AnchorName 指定的已有元素之前。</summary>
|
||||
BeforeAnchor,
|
||||
/// <summary>插入到由 AnchorName 指定的已有元素之后(同一锚点多项按 Order 依次排列)。</summary>
|
||||
AfterAnchor
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar
|
||||
{
|
||||
/// <summary>
|
||||
/// 单个工具栏按钮的用户配置(可见性、顺序、所属 slot、插入位置)。
|
||||
/// 由 Settings.Toolbar 持久化。
|
||||
/// </summary>
|
||||
public class ToolbarItemConfig
|
||||
{
|
||||
[JsonProperty("visible")]
|
||||
public bool Visible { get; set; } = true;
|
||||
|
||||
[JsonProperty("order")]
|
||||
public int Order { get; set; }
|
||||
|
||||
[JsonProperty("slot")]
|
||||
public ToolbarSlot Slot { get; set; } = ToolbarSlot.FloatingBarMain;
|
||||
|
||||
[JsonProperty("position")]
|
||||
public ToolbarInsertPosition Position { get; set; } = ToolbarInsertPosition.Prepend;
|
||||
|
||||
[JsonProperty("anchorName")]
|
||||
public string AnchorName { get; set; }
|
||||
}
|
||||
|
||||
public class ToolbarLayoutSettings
|
||||
{
|
||||
[JsonProperty("items")]
|
||||
public Dictionary<string, ToolbarItemConfig> Items { get; set; } = new Dictionary<string, ToolbarItemConfig>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Ink_Canvas.Helpers;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar
|
||||
{
|
||||
/// <summary>
|
||||
/// 扫描当前程序集里的 IToolbarItem 实现,按用户配置(Settings.Toolbar)排序/过滤后注入到目标容器。
|
||||
/// </summary>
|
||||
public static class ToolbarRegistry
|
||||
{
|
||||
private static List<IToolbarItem> _items;
|
||||
|
||||
public static IReadOnlyList<IToolbarItem> Discover()
|
||||
{
|
||||
if (_items != null) return _items;
|
||||
|
||||
var itemType = typeof(IToolbarItem);
|
||||
_items = Assembly.GetExecutingAssembly()
|
||||
.GetTypes()
|
||||
.Where(t => !t.IsAbstract && !t.IsInterface && itemType.IsAssignableFrom(t))
|
||||
.Select(t =>
|
||||
{
|
||||
try { return (IToolbarItem)Activator.CreateInstance(t); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: 实例化 {t.FullName} 失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.Where(i => i != null)
|
||||
.ToList();
|
||||
return _items;
|
||||
}
|
||||
|
||||
/// <summary>按 slot 分配工具栏条目到对应容器。调用者负责清空目标容器里要被接管的旧内容。</summary>
|
||||
public static void Populate(IToolbarHost host, IDictionary<ToolbarSlot, Panel> slots, ToolbarLayoutSettings layout)
|
||||
{
|
||||
if (host == null || slots == null) return;
|
||||
layout = layout ?? new ToolbarLayoutSettings();
|
||||
|
||||
var grouped = new Dictionary<ToolbarSlot, List<(IToolbarItem item, ToolbarItemConfig cfg)>>();
|
||||
foreach (var item in Discover())
|
||||
{
|
||||
if (!layout.Items.TryGetValue(item.Id, out var cfg))
|
||||
{
|
||||
cfg = new ToolbarItemConfig
|
||||
{
|
||||
Visible = item.DefaultVisible,
|
||||
Order = item.DefaultOrder,
|
||||
Slot = item.DefaultSlot,
|
||||
Position = item.DefaultPosition,
|
||||
AnchorName = item.DefaultAnchorName
|
||||
};
|
||||
}
|
||||
if (!cfg.Visible) continue;
|
||||
if (!grouped.TryGetValue(cfg.Slot, out var list))
|
||||
{
|
||||
list = new List<(IToolbarItem, ToolbarItemConfig)>();
|
||||
grouped[cfg.Slot] = list;
|
||||
}
|
||||
list.Add((item, cfg));
|
||||
}
|
||||
|
||||
foreach (var kv in grouped)
|
||||
{
|
||||
if (!slots.TryGetValue(kv.Key, out var container) || container == null) continue;
|
||||
InjectIntoContainer(host, container, kv.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void InjectIntoContainer(IToolbarHost host, Panel container,
|
||||
List<(IToolbarItem item, ToolbarItemConfig cfg)> entries)
|
||||
{
|
||||
// 按 Position 分桶,每桶内按 Order 升序。
|
||||
var prepend = entries.Where(e => e.cfg.Position == ToolbarInsertPosition.Prepend).OrderBy(e => e.cfg.Order).ToList();
|
||||
var append = entries.Where(e => e.cfg.Position == ToolbarInsertPosition.Append).OrderBy(e => e.cfg.Order).ToList();
|
||||
var before = entries.Where(e => e.cfg.Position == ToolbarInsertPosition.BeforeAnchor).ToList();
|
||||
var after = entries.Where(e => e.cfg.Position == ToolbarInsertPosition.AfterAnchor).ToList();
|
||||
|
||||
var prependIndex = 0;
|
||||
foreach (var entry in prepend)
|
||||
{
|
||||
var view = BuildAndRegister(host, entry.item);
|
||||
if (view == null) continue;
|
||||
container.Children.Insert(prependIndex++, view);
|
||||
}
|
||||
|
||||
foreach (var entry in append)
|
||||
{
|
||||
var view = BuildAndRegister(host, entry.item);
|
||||
if (view == null) continue;
|
||||
container.Children.Add(view);
|
||||
}
|
||||
|
||||
foreach (var group in before.GroupBy(e => e.cfg.AnchorName))
|
||||
{
|
||||
var anchor = FindNamedChild(container, group.Key);
|
||||
if (anchor == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: 未找到锚点 '{group.Key}' (BeforeAnchor)", LogHelper.LogType.Warning);
|
||||
continue;
|
||||
}
|
||||
var idx = container.Children.IndexOf(anchor);
|
||||
foreach (var entry in group.OrderBy(e => e.cfg.Order))
|
||||
{
|
||||
var view = BuildAndRegister(host, entry.item);
|
||||
if (view == null) continue;
|
||||
container.Children.Insert(idx++, view);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var group in after.GroupBy(e => e.cfg.AnchorName))
|
||||
{
|
||||
var anchor = FindNamedChild(container, group.Key);
|
||||
if (anchor == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: 未找到锚点 '{group.Key}' (AfterAnchor)", LogHelper.LogType.Warning);
|
||||
continue;
|
||||
}
|
||||
var idx = container.Children.IndexOf(anchor) + 1;
|
||||
foreach (var entry in group.OrderBy(e => e.cfg.Order))
|
||||
{
|
||||
var view = BuildAndRegister(host, entry.item);
|
||||
if (view == null) continue;
|
||||
container.Children.Insert(idx++, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static UIElement FindNamedChild(Panel container, string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) return null;
|
||||
foreach (UIElement child in container.Children)
|
||||
{
|
||||
if (child is FrameworkElement fe && fe.Name == name) return child;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static FrameworkElement BuildAndRegister(IToolbarHost host, IToolbarItem item)
|
||||
{
|
||||
try
|
||||
{
|
||||
var view = item.BuildView(host);
|
||||
if (view == null) return null;
|
||||
host.RegisterView(item.Id, view);
|
||||
return view;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: 构建 {item.Id} 失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Ink_Canvas.Controls.Toolbar
|
||||
{
|
||||
public enum ToolbarSlot
|
||||
{
|
||||
FloatingBarMain,
|
||||
FloatingBarCanvasControls,
|
||||
FloatingBarEnd,
|
||||
BlackboardLeft,
|
||||
BlackboardRight
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Costura ExcludeAssemblies="IACore|IALoader|IAWinFX" />
|
||||
</Weavers>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,23 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using Ink_Canvas.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal class AnimationsHelper
|
||||
{
|
||||
private static UIElement ResolveAnimationTarget(UIElement element)
|
||||
{
|
||||
if (element is BoardMenuFrame frame)
|
||||
{
|
||||
frame.ApplyTemplate();
|
||||
return frame.AnimationTarget ?? element;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
public static void ShowWithFadeIn(UIElement element, double duration = 0.15)
|
||||
{
|
||||
if (element.Visibility == Visibility.Visible) return;
|
||||
@@ -36,14 +47,17 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
if (element.Visibility == Visibility.Visible) return;
|
||||
|
||||
if (element == null)
|
||||
throw new ArgumentNullException(nameof(element));
|
||||
|
||||
if (element.Visibility == Visibility.Visible) return;
|
||||
|
||||
element.Visibility = Visibility.Visible;
|
||||
|
||||
var target = ResolveAnimationTarget(element);
|
||||
|
||||
var sb = new Storyboard();
|
||||
|
||||
// 渐变动画
|
||||
var fadeInAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 0.5,
|
||||
@@ -54,10 +68,9 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(UIElement.OpacityProperty));
|
||||
|
||||
// 滑动动画
|
||||
var slideAnimation = new DoubleAnimation
|
||||
{
|
||||
From = element.RenderTransform.Value.OffsetY + 10, // 滑动距离
|
||||
From = 10,
|
||||
To = 0,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
@@ -68,12 +81,11 @@ namespace Ink_Canvas.Helpers
|
||||
sb.Children.Add(fadeInAnimation);
|
||||
sb.Children.Add(slideAnimation);
|
||||
|
||||
element.Visibility = Visibility.Visible;
|
||||
element.RenderTransform = new TranslateTransform();
|
||||
target.RenderTransform = new TranslateTransform();
|
||||
|
||||
sb.Begin((FrameworkElement)element);
|
||||
sb.Begin((FrameworkElement)target);
|
||||
}
|
||||
catch { }
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
public static void ShowWithSlideFromLeftAndFade(UIElement element, double duration = 0.25)
|
||||
@@ -113,7 +125,7 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
sb.Begin((FrameworkElement)element);
|
||||
}
|
||||
catch { }
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
public static void ShowWithScaleFromLeft(UIElement element, double duration = 0.2)
|
||||
@@ -156,7 +168,7 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
sb.Begin((FrameworkElement)element);
|
||||
}
|
||||
catch { }
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
public static void ShowWithScaleFromRight(UIElement element, double duration = 0.2)
|
||||
@@ -200,21 +212,22 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
sb.Begin((FrameworkElement)element);
|
||||
}
|
||||
catch { }
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
public static void HideWithSlideAndFade(UIElement element, double duration = 0.15)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (element.Visibility == Visibility.Collapsed) return;
|
||||
|
||||
if (element == null)
|
||||
throw new ArgumentNullException(nameof(element));
|
||||
|
||||
if (element.Visibility == Visibility.Collapsed) return;
|
||||
|
||||
var target = ResolveAnimationTarget(element);
|
||||
|
||||
var sb = new Storyboard();
|
||||
|
||||
// 渐变动画
|
||||
var fadeOutAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 1,
|
||||
@@ -224,11 +237,10 @@ namespace Ink_Canvas.Helpers
|
||||
fadeOutAnimation.EasingFunction = new CubicEase();
|
||||
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(UIElement.OpacityProperty));
|
||||
|
||||
// 滑动动画
|
||||
var slideAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 0,
|
||||
To = element.RenderTransform.Value.OffsetY + 10, // 滑动距离
|
||||
To = 10,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
slideAnimation.EasingFunction = new CubicEase();
|
||||
@@ -243,10 +255,10 @@ namespace Ink_Canvas.Helpers
|
||||
element.Visibility = Visibility.Collapsed;
|
||||
};
|
||||
|
||||
element.RenderTransform = new TranslateTransform();
|
||||
sb.Begin((FrameworkElement)element);
|
||||
target.RenderTransform = new TranslateTransform();
|
||||
sb.Begin((FrameworkElement)target);
|
||||
}
|
||||
catch { }
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
public static void HideWithFadeOut(UIElement element, double duration = 0.15)
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
using Ink_Canvas.Windows.SettingsViews.Helpers;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Principal;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public static class AppRestartHelper
|
||||
{
|
||||
public static bool IsRunningAsAdmin()
|
||||
{
|
||||
try
|
||||
{
|
||||
var identity = WindowsIdentity.GetCurrent();
|
||||
var principal = new WindowsPrincipal(identity);
|
||||
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void RestartApp(bool asAdmin)
|
||||
{
|
||||
try
|
||||
{
|
||||
App.IsAppExitByUser = true;
|
||||
|
||||
(Application.Current as App)?.ReleaseMutexForRestart();
|
||||
|
||||
string exePath = Process.GetCurrentProcess().MainModule.FileName;
|
||||
|
||||
if (asAdmin)
|
||||
{
|
||||
var psi = new ProcessStartInfo(exePath) { UseShellExecute = true, Verb = "runas" };
|
||||
Process.Start(psi);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 当前已是管理员时,直接通过用户令牌降权启动,避免经由 explorer 中转的延迟
|
||||
if (IsRunningAsAdmin() && UIAccessHelper.RestartAsNormalUser())
|
||||
{
|
||||
Application.Current.Shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
Process.Start("explorer.exe", "\"" + exePath + "\"");
|
||||
}
|
||||
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"重启应用时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void RestartWithCurrentPrivileges()
|
||||
{
|
||||
RestartApp(IsRunningAsAdmin());
|
||||
}
|
||||
|
||||
public static void RestartAsAdmin()
|
||||
{
|
||||
RestartApp(true);
|
||||
}
|
||||
|
||||
public static void RestartAsNormal()
|
||||
{
|
||||
RestartApp(false);
|
||||
}
|
||||
|
||||
public static void SwitchToUIATopMostAndRestart()
|
||||
{
|
||||
try
|
||||
{
|
||||
SettingsManager.Settings.Advanced.EnableUIAccessTopMost = true;
|
||||
|
||||
if (!SettingsManager.Settings.Advanced.IsAlwaysOnTop)
|
||||
{
|
||||
SettingsManager.Settings.Advanced.IsAlwaysOnTop = true;
|
||||
}
|
||||
|
||||
SettingsManager.SaveSettingsToFile();
|
||||
|
||||
App.IsUIAccessTopMostEnabled = true;
|
||||
RestartApp(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"切换到UIA置顶模式时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void SwitchToNormalTopMostAndRestart()
|
||||
{
|
||||
try
|
||||
{
|
||||
SettingsManager.Settings.Advanced.EnableUIAccessTopMost = false;
|
||||
SettingsManager.SaveSettingsToFile();
|
||||
|
||||
App.IsUIAccessTopMostEnabled = false;
|
||||
RestartApp(IsRunningAsAdmin());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"切换到普通置顶模式时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 自动备份管理器
|
||||
/// 负责管理配置文件的自动备份功能
|
||||
/// </summary>
|
||||
public static class AutoBackupManager
|
||||
{
|
||||
private static readonly string BackupDir = Path.Combine(App.RootPath, "Backups");
|
||||
private static readonly string SettingsFile = Path.Combine(App.RootPath, "Configs", "Settings.json");
|
||||
private static readonly string BackupPrefix = "Settings_AutoBackup_";
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否需要执行自动备份
|
||||
/// </summary>
|
||||
/// <param name="settings">设置对象</param>
|
||||
/// <returns>如果需要备份返回<see langword="true"/>,否则返回<see langword="false"/></returns>
|
||||
public static bool ShouldPerformAutoBackup(Settings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果自动备份功能未启用,不执行备份
|
||||
if (!settings.Advanced.IsAutoBackupEnabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果从未备份过,需要创建首次备份
|
||||
if (settings.Advanced.LastAutoBackupTime == DateTime.MinValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查是否已超过备份间隔
|
||||
var daysSinceLastBackup = (DateTime.Now - settings.Advanced.LastAutoBackupTime).TotalDays;
|
||||
return daysSinceLastBackup >= settings.Advanced.AutoBackupIntervalDays;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"检查自动备份条件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行自动备份
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 为主配置文件创建一次自动备份并在成功后更新并保存设置中的最后备份时间。
|
||||
/// </remarks>
|
||||
/// <param name="settings">应用的设置对象;在成功备份后会更新 settings.Advanced.LastAutoBackupTime 并调用保存操作。</param>
|
||||
/// <returns><see langword="true"/> 表示备份成功,<see langword="false"/> 表示备份失败或被跳过。</returns>
|
||||
public static bool PerformAutoBackup(Settings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保备份目录存在
|
||||
if (!Directory.Exists(BackupDir))
|
||||
{
|
||||
ProcessProtectionManager.WithWriteAccess(BackupDir, () => Directory.CreateDirectory(BackupDir));
|
||||
}
|
||||
|
||||
// 检查主配置文件是否存在
|
||||
if (!File.Exists(SettingsFile))
|
||||
{
|
||||
LogHelper.WriteLogToFile("主配置文件不存在,跳过自动备份", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建备份文件名(使用当前日期时间)
|
||||
string backupFileName = $"{BackupPrefix}{DateTime.Now:yyyyMMdd_HHmmss}.json";
|
||||
string backupPath = Path.Combine(BackupDir, backupFileName);
|
||||
|
||||
// 复制主配置文件到备份位置
|
||||
ProcessProtectionManager.WithWriteAccess(backupPath, () => File.Copy(SettingsFile, backupPath, true));
|
||||
|
||||
// 更新最后备份时间
|
||||
settings.Advanced.LastAutoBackupTime = DateTime.Now;
|
||||
MainWindow.SaveSettingsToFile();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"执行自动备份时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从备份恢复配置文件
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 从最新可用的自动备份恢复主设置文件(Settings.json)。如果当前设置文件存在,会先将其复制到备份目录并加上时间戳作为“损坏”的备份副本,然后用最新备份覆盖原文件。
|
||||
/// </remarks>
|
||||
/// <returns><see langword="true"/> 如果恢复成功,<see langword="false"/> 否则。</returns>
|
||||
public static bool TryRestoreFromBackup()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保备份目录存在
|
||||
if (!Directory.Exists(BackupDir))
|
||||
{
|
||||
LogHelper.WriteLogToFile("备份目录不存在,无法从备份恢复", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找最新的备份文件
|
||||
var backupFiles = Directory.GetFiles(BackupDir, $"{BackupPrefix}*.json")
|
||||
.OrderByDescending(f => File.GetCreationTime(f))
|
||||
.ToArray();
|
||||
|
||||
if (backupFiles.Length == 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile("没有找到可用的备份文件", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 尝试使用最新的备份文件
|
||||
string latestBackup = backupFiles[0];
|
||||
|
||||
// 验证备份文件是否有效
|
||||
try
|
||||
{
|
||||
string backupJson = File.ReadAllText(latestBackup);
|
||||
var testSettings = JsonConvert.DeserializeObject<Settings>(backupJson);
|
||||
if (testSettings == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("备份文件内容无效,无法恢复", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"备份文件验证失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 备份当前损坏的配置文件(如果存在)
|
||||
if (File.Exists(SettingsFile))
|
||||
{
|
||||
string corruptedBackup = Path.Combine(BackupDir, $"Settings_Corrupted_{DateTime.Now:yyyyMMdd_HHmmss}.json");
|
||||
ProcessProtectionManager.WithWriteAccess(corruptedBackup, () => File.Copy(SettingsFile, corruptedBackup, true));
|
||||
}
|
||||
|
||||
// 从备份恢复配置文件
|
||||
ProcessProtectionManager.WithWriteAccess(SettingsFile, () => File.Copy(latestBackup, SettingsFile, true));
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从备份恢复配置文件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理过期的备份文件
|
||||
/// 保留最近30天的备份文件
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 删除备份目录中按“备份前缀”匹配且创建时间早于 30 天的自动备份文件(即自动备份文件的命名前缀),不会删除诸如 Settings_Corrupted_*.json 之类的其他备份或错误状态文件。
|
||||
/// 如果备份目录不存在则不执行任何操作;删除操作在受写入保护的上下文中执行,任何错误会被记录但不会抛出异常。
|
||||
/// </remarks>
|
||||
public static void CleanupOldBackups()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(BackupDir))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cutoffDate = DateTime.Now.AddDays(-30);
|
||||
var backupFiles = Directory.GetFiles(BackupDir, $"{BackupPrefix}*.json");
|
||||
|
||||
int deletedCount = 0;
|
||||
foreach (var file in backupFiles)
|
||||
{
|
||||
if (File.GetCreationTime(file) < cutoffDate)
|
||||
{
|
||||
ProcessProtectionManager.WithWriteAccess(file, () => File.Delete(file));
|
||||
deletedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (deletedCount > 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理过期备份文件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化自动备份功能
|
||||
/// 在应用程序启动时调用
|
||||
/// </summary>
|
||||
/// <param name="settings">设置对象</param>
|
||||
public static void Initialize(Settings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查是否需要执行自动备份
|
||||
if (ShouldPerformAutoBackup(settings))
|
||||
{
|
||||
PerformAutoBackup(settings);
|
||||
}
|
||||
|
||||
// 清理过期备份
|
||||
CleanupOldBackups();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化自动备份功能时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Automatically shrinks text to fit available width.
|
||||
/// Supports TextBlock and Label.
|
||||
/// Only shrinks, never enlarges above MaxFontSize.
|
||||
/// </summary>
|
||||
public static class AutoFontSizeHelper
|
||||
{
|
||||
public static readonly DependencyProperty IsEnabledProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"IsEnabled",
|
||||
typeof(bool),
|
||||
typeof(AutoFontSizeHelper),
|
||||
new PropertyMetadata(false, OnIsEnabledChanged));
|
||||
|
||||
public static void SetIsEnabled(DependencyObject element, bool value) => element.SetValue(IsEnabledProperty, value);
|
||||
public static bool GetIsEnabled(DependencyObject element) => (bool)element.GetValue(IsEnabledProperty);
|
||||
|
||||
public static readonly DependencyProperty MinFontSizeProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"MinFontSize",
|
||||
typeof(double),
|
||||
typeof(AutoFontSizeHelper),
|
||||
new PropertyMetadata(6d, OnSizingPropertyChanged));
|
||||
|
||||
public static void SetMinFontSize(DependencyObject element, double value) => element.SetValue(MinFontSizeProperty, value);
|
||||
public static double GetMinFontSize(DependencyObject element) => (double)element.GetValue(MinFontSizeProperty);
|
||||
|
||||
public static readonly DependencyProperty MaxFontSizeProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"MaxFontSize",
|
||||
typeof(double),
|
||||
typeof(AutoFontSizeHelper),
|
||||
new PropertyMetadata(double.NaN, OnSizingPropertyChanged));
|
||||
|
||||
public static void SetMaxFontSize(DependencyObject element, double value) => element.SetValue(MaxFontSizeProperty, value);
|
||||
public static double GetMaxFontSize(DependencyObject element) => (double)element.GetValue(MaxFontSizeProperty);
|
||||
|
||||
public static readonly DependencyProperty StepProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"Step",
|
||||
typeof(double),
|
||||
typeof(AutoFontSizeHelper),
|
||||
new PropertyMetadata(0.5d, OnSizingPropertyChanged));
|
||||
|
||||
public static void SetStep(DependencyObject element, double value) => element.SetValue(StepProperty, value);
|
||||
public static double GetStep(DependencyObject element) => (double)element.GetValue(StepProperty);
|
||||
|
||||
private static readonly DependencyProperty IsAdjustingProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"IsAdjusting",
|
||||
typeof(bool),
|
||||
typeof(AutoFontSizeHelper),
|
||||
new PropertyMetadata(false));
|
||||
|
||||
private static void SetIsAdjusting(DependencyObject element, bool value) => element.SetValue(IsAdjustingProperty, value);
|
||||
private static bool GetIsAdjusting(DependencyObject element) => (bool)element.GetValue(IsAdjustingProperty);
|
||||
|
||||
private static readonly DependencyProperty OriginalFontSizeProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"OriginalFontSize",
|
||||
typeof(double),
|
||||
typeof(AutoFontSizeHelper),
|
||||
new PropertyMetadata(double.NaN));
|
||||
|
||||
private static void SetOriginalFontSize(DependencyObject element, double value) => element.SetValue(OriginalFontSizeProperty, value);
|
||||
private static double GetOriginalFontSize(DependencyObject element) => (double)element.GetValue(OriginalFontSizeProperty);
|
||||
|
||||
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (!(d is FrameworkElement fe)) return;
|
||||
if (!(fe is TextBlock) && !(fe is Label)) return;
|
||||
|
||||
if ((bool)e.NewValue)
|
||||
{
|
||||
var originalFontSize = GetElementFontSize(fe);
|
||||
if (!double.IsNaN(originalFontSize) && originalFontSize > 0)
|
||||
{
|
||||
SetOriginalFontSize(fe, originalFontSize);
|
||||
}
|
||||
|
||||
fe.SizeChanged += Element_OnSizeChanged;
|
||||
fe.Loaded += Element_OnLoaded;
|
||||
fe.Unloaded += Element_OnUnloaded;
|
||||
TryHookContentChanged(fe, true);
|
||||
|
||||
fe.Dispatcher.BeginInvoke(new Action(() => TryAdjust(fe)), DispatcherPriority.Loaded);
|
||||
}
|
||||
else
|
||||
{
|
||||
fe.SizeChanged -= Element_OnSizeChanged;
|
||||
fe.Loaded -= Element_OnLoaded;
|
||||
fe.Unloaded -= Element_OnUnloaded;
|
||||
TryHookContentChanged(fe, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnSizingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is FrameworkElement fe && GetIsEnabled(fe))
|
||||
{
|
||||
fe.Dispatcher.BeginInvoke(new Action(() => TryAdjust(fe)), DispatcherPriority.Loaded);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Element_OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement fe)
|
||||
{
|
||||
fe.Dispatcher.BeginInvoke(new Action(() => TryAdjust(fe)), DispatcherPriority.Loaded);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Element_OnUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// No extra cleanup required here.
|
||||
}
|
||||
|
||||
private static void Element_OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement fe) TryAdjust(fe);
|
||||
}
|
||||
|
||||
private static void Element_OnTextChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement fe)
|
||||
{
|
||||
fe.Dispatcher.BeginInvoke(new Action(() => TryAdjust(fe)), DispatcherPriority.Loaded);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryHookContentChanged(FrameworkElement fe, bool add)
|
||||
{
|
||||
try
|
||||
{
|
||||
DependencyPropertyDescriptor dpd = null;
|
||||
if (fe is TextBlock)
|
||||
{
|
||||
dpd = DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty, typeof(TextBlock));
|
||||
}
|
||||
else if (fe is Label)
|
||||
{
|
||||
dpd = DependencyPropertyDescriptor.FromProperty(ContentControl.ContentProperty, typeof(ContentControl));
|
||||
}
|
||||
|
||||
if (dpd == null) return;
|
||||
if (add) dpd.AddValueChanged(fe, Element_OnTextChanged);
|
||||
else dpd.RemoveValueChanged(fe, Element_OnTextChanged);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore descriptor issues in rare runtime cases.
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryAdjust(FrameworkElement fe)
|
||||
{
|
||||
if (fe == null) return;
|
||||
if (!GetIsEnabled(fe)) return;
|
||||
if (GetIsAdjusting(fe)) return;
|
||||
|
||||
var text = GetElementText(fe);
|
||||
if (string.IsNullOrEmpty(text)) return;
|
||||
|
||||
if (!ShouldAutoScaleForCurrentCulture(text))
|
||||
{
|
||||
RestoreOriginalFontSize(fe);
|
||||
return;
|
||||
}
|
||||
|
||||
var availableWidth = GetAvailableWidth(fe);
|
||||
if (double.IsNaN(availableWidth) || availableWidth <= 1) return;
|
||||
|
||||
var min = GetMinFontSize(fe);
|
||||
if (double.IsNaN(min) || min <= 0) min = 6d;
|
||||
|
||||
var step = GetStep(fe);
|
||||
if (double.IsNaN(step) || step < 0.1) step = 0.5d;
|
||||
|
||||
var current = GetElementFontSize(fe);
|
||||
if (double.IsNaN(current) || current <= 0) return;
|
||||
|
||||
var max = GetMaxFontSize(fe);
|
||||
if (double.IsNaN(max) || max <= 0) max = current;
|
||||
// Never enlarge: auto-fit should only reduce font size when needed.
|
||||
if (max > current) max = current;
|
||||
|
||||
var startFont = Math.Min(current, max);
|
||||
if (startFont < min) startFont = min;
|
||||
|
||||
SetIsAdjusting(fe, true);
|
||||
try
|
||||
{
|
||||
var font = startFont;
|
||||
var desired = MeasureTextWidth(fe, text, font);
|
||||
if (desired <= 0) return;
|
||||
|
||||
while (font > min && desired > availableWidth + 0.5)
|
||||
{
|
||||
font = Math.Max(min, font - step);
|
||||
desired = MeasureTextWidth(fe, text, font);
|
||||
if (desired <= 0) break;
|
||||
}
|
||||
|
||||
// Hard-fit fallback: when very narrow slots (e.g., 28px) still overflow at MinFontSize,
|
||||
// keep shrinking proportionally so text always fits in the available width.
|
||||
if (desired > availableWidth + 0.5)
|
||||
{
|
||||
var hardFont = font;
|
||||
for (var i = 0; i < 6 && desired > availableWidth + 0.5; i++)
|
||||
{
|
||||
var ratio = availableWidth / Math.Max(1.0, desired);
|
||||
hardFont = Math.Max(1.0, hardFont * ratio);
|
||||
desired = MeasureTextWidth(fe, text, hardFont);
|
||||
if (desired <= 0) break;
|
||||
}
|
||||
|
||||
font = hardFont;
|
||||
}
|
||||
|
||||
if (!double.IsNaN(font) && font > 0 && Math.Abs(current - font) > 0.01)
|
||||
{
|
||||
SetElementFontSize(fe, font);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetIsAdjusting(fe, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetElementText(FrameworkElement fe)
|
||||
{
|
||||
if (fe is TextBlock tb) return tb.Text;
|
||||
if (fe is Label label) return label.Content as string ?? label.Content?.ToString();
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool ShouldAutoScaleForCurrentCulture(string text)
|
||||
{
|
||||
// Requirement: auto-scale for English UI only, keep Chinese font size unchanged.
|
||||
var culture = CultureInfo.CurrentUICulture;
|
||||
var name = culture?.Name ?? string.Empty;
|
||||
if (name.StartsWith("en", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback: if actual rendered text is Latin-heavy, still auto-scale.
|
||||
// This avoids clipping when culture detection is out of sync.
|
||||
if (string.IsNullOrWhiteSpace(text)) return false;
|
||||
foreach (var ch in text)
|
||||
{
|
||||
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void RestoreOriginalFontSize(FrameworkElement fe)
|
||||
{
|
||||
var original = GetOriginalFontSize(fe);
|
||||
if (double.IsNaN(original) || original <= 0) return;
|
||||
|
||||
var current = GetElementFontSize(fe);
|
||||
if (double.IsNaN(current) || current <= 0) return;
|
||||
|
||||
if (Math.Abs(current - original) > 0.01)
|
||||
{
|
||||
SetElementFontSize(fe, original);
|
||||
}
|
||||
}
|
||||
|
||||
private static double GetElementFontSize(FrameworkElement fe)
|
||||
{
|
||||
if (fe is TextBlock tb) return tb.FontSize;
|
||||
if (fe is Label label) return label.FontSize;
|
||||
return double.NaN;
|
||||
}
|
||||
|
||||
private static void SetElementFontSize(FrameworkElement fe, double value)
|
||||
{
|
||||
if (fe is TextBlock tb) tb.FontSize = value;
|
||||
else if (fe is Label label) label.FontSize = value;
|
||||
}
|
||||
|
||||
private static double GetAvailableWidth(FrameworkElement fe)
|
||||
{
|
||||
double width = double.PositiveInfinity;
|
||||
|
||||
// Explicit width on the element itself should be a hard cap.
|
||||
if (!double.IsNaN(fe.Width) && !double.IsInfinity(fe.Width) && fe.Width > 1)
|
||||
{
|
||||
width = Math.Min(width, fe.Width - fe.Margin.Left - fe.Margin.Right);
|
||||
}
|
||||
|
||||
if (!double.IsNaN(fe.MaxWidth) && !double.IsInfinity(fe.MaxWidth) && fe.MaxWidth > 1)
|
||||
{
|
||||
width = Math.Min(width, fe.MaxWidth - fe.Margin.Left - fe.Margin.Right);
|
||||
}
|
||||
|
||||
// Prefer the real layout slot first. This is usually the most accurate
|
||||
// "space actually assigned by layout" for the element.
|
||||
var slot = LayoutInformation.GetLayoutSlot(fe);
|
||||
if (!double.IsNaN(slot.Width) && !double.IsInfinity(slot.Width))
|
||||
{
|
||||
var slotWidth = slot.Width - fe.Margin.Left - fe.Margin.Right;
|
||||
if (slotWidth > 1) width = Math.Min(width, slotWidth);
|
||||
}
|
||||
|
||||
if (fe.ActualWidth > 1) width = Math.Min(width, fe.ActualWidth);
|
||||
|
||||
// Immediate parent may be a StackPanel that does not constrain width.
|
||||
// Walk a few ancestors and take the tightest finite width as fallback.
|
||||
DependencyObject ancestor = fe.Parent ?? VisualTreeHelper.GetParent(fe);
|
||||
var depth = 0;
|
||||
while (ancestor != null && depth < 8)
|
||||
{
|
||||
if (ancestor is FrameworkElement af && af.ActualWidth > 1)
|
||||
{
|
||||
var candidate = af.ActualWidth;
|
||||
|
||||
// If ancestor sets explicit width, treat it as a stronger cap.
|
||||
if (!double.IsNaN(af.Width) && !double.IsInfinity(af.Width) && af.Width > 1)
|
||||
{
|
||||
candidate = Math.Min(candidate, af.Width);
|
||||
}
|
||||
|
||||
if (!double.IsNaN(af.MaxWidth) && !double.IsInfinity(af.MaxWidth) && af.MaxWidth > 1)
|
||||
{
|
||||
candidate = Math.Min(candidate, af.MaxWidth);
|
||||
}
|
||||
|
||||
if (ancestor is Control ac)
|
||||
{
|
||||
candidate -= ac.Padding.Left + ac.Padding.Right;
|
||||
candidate -= ac.BorderThickness.Left + ac.BorderThickness.Right;
|
||||
}
|
||||
else if (ancestor is Border ab)
|
||||
{
|
||||
candidate -= ab.Padding.Left + ab.Padding.Right;
|
||||
candidate -= ab.BorderThickness.Left + ab.BorderThickness.Right;
|
||||
}
|
||||
|
||||
if (candidate > 1) width = Math.Min(width, candidate);
|
||||
}
|
||||
|
||||
ancestor = (ancestor as FrameworkElement)?.Parent ?? VisualTreeHelper.GetParent(ancestor);
|
||||
depth++;
|
||||
}
|
||||
|
||||
if (double.IsInfinity(width) || double.IsNaN(width) || width <= 1) return -1;
|
||||
|
||||
// Keep width as inner text area.
|
||||
if (fe is Control control)
|
||||
{
|
||||
width -= control.Padding.Left + control.Padding.Right;
|
||||
width -= control.BorderThickness.Left + control.BorderThickness.Right;
|
||||
}
|
||||
else if (fe is Border border)
|
||||
{
|
||||
width -= border.Padding.Left + border.Padding.Right;
|
||||
width -= border.BorderThickness.Left + border.BorderThickness.Right;
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
private static double MeasureTextWidth(FrameworkElement fe, string text, double fontSize)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dpi = VisualTreeHelper.GetDpi(fe);
|
||||
var culture = CultureInfo.CurrentUICulture;
|
||||
|
||||
if (fe.Language != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
culture = fe.Language.GetEquivalentCulture();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
var fontFamily = SystemFonts.MessageFontFamily;
|
||||
var fontStyle = FontStyles.Normal;
|
||||
var fontWeight = FontWeights.Normal;
|
||||
var fontStretch = FontStretches.Normal;
|
||||
Brush foreground = Brushes.Black;
|
||||
var flowDirection = FlowDirection.LeftToRight;
|
||||
|
||||
if (fe is TextBlock tb)
|
||||
{
|
||||
fontFamily = tb.FontFamily;
|
||||
fontStyle = tb.FontStyle;
|
||||
fontWeight = tb.FontWeight;
|
||||
fontStretch = tb.FontStretch;
|
||||
foreground = tb.Foreground;
|
||||
flowDirection = tb.FlowDirection;
|
||||
}
|
||||
else if (fe is Label label)
|
||||
{
|
||||
fontFamily = label.FontFamily;
|
||||
fontStyle = label.FontStyle;
|
||||
fontWeight = label.FontWeight;
|
||||
fontStretch = label.FontStretch;
|
||||
foreground = label.Foreground;
|
||||
flowDirection = label.FlowDirection;
|
||||
}
|
||||
|
||||
var typeface = new Typeface(fontFamily, fontStyle, fontWeight, fontStretch);
|
||||
var formatted = new FormattedText(
|
||||
text,
|
||||
culture,
|
||||
flowDirection,
|
||||
typeface,
|
||||
fontSize,
|
||||
foreground,
|
||||
dpi.PixelsPerDip);
|
||||
|
||||
return formatted.WidthIncludingTrailingWhitespace;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2411
-121
File diff suppressed because it is too large
Load Diff
@@ -1,23 +1,23 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 防止窗口进入全屏状态的辅助类
|
||||
/// </summary>
|
||||
public static partial class AvoidFullScreenHelper
|
||||
public static class AvoidFullScreenHelper
|
||||
{
|
||||
private static readonly DependencyProperty IsAvoidFullScreenEnabledProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"IsAvoidFullScreenEnabled",
|
||||
typeof(bool),
|
||||
"IsAvoidFullScreenEnabled",
|
||||
typeof(bool),
|
||||
typeof(AvoidFullScreenHelper));
|
||||
|
||||
private static bool _isBoardMode = false;
|
||||
private static bool _isBoardMode;
|
||||
public static void SetBoardMode(bool isBoardMode)
|
||||
{
|
||||
_isBoardMode = isBoardMode;
|
||||
@@ -121,20 +121,20 @@ namespace Ink_Canvas.Helpers
|
||||
private static Rect GetWorkingArea(Rect windowRect)
|
||||
{
|
||||
// 获取所有显示器
|
||||
var screens = System.Windows.Forms.Screen.AllScreens;
|
||||
|
||||
var screens = Screen.AllScreens;
|
||||
|
||||
// 确定窗口主要位于哪个显示器上
|
||||
System.Windows.Forms.Screen targetScreen = null;
|
||||
Screen targetScreen = null;
|
||||
double maxIntersection = 0;
|
||||
|
||||
|
||||
foreach (var screen in screens)
|
||||
{
|
||||
var screenRect = new Rect(
|
||||
screen.WorkingArea.X,
|
||||
screen.WorkingArea.Y,
|
||||
screen.WorkingArea.Width,
|
||||
screen.WorkingArea.X,
|
||||
screen.WorkingArea.Y,
|
||||
screen.WorkingArea.Width,
|
||||
screen.WorkingArea.Height);
|
||||
|
||||
|
||||
var intersection = Rect.Intersect(windowRect, screenRect);
|
||||
if (intersection.Width * intersection.Height > maxIntersection)
|
||||
{
|
||||
@@ -142,11 +142,11 @@ namespace Ink_Canvas.Helpers
|
||||
targetScreen = screen;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 如果没找到,使用主显示器
|
||||
if (targetScreen == null)
|
||||
targetScreen = System.Windows.Forms.Screen.PrimaryScreen;
|
||||
|
||||
targetScreen = Screen.PrimaryScreen;
|
||||
|
||||
return new Rect(
|
||||
targetScreen.WorkingArea.X,
|
||||
targetScreen.WorkingArea.Y,
|
||||
@@ -159,21 +159,21 @@ namespace Ink_Canvas.Helpers
|
||||
// 调整尺寸以适应工作区域
|
||||
if (windowRect.Width > workingArea.Width)
|
||||
windowRect.Width = workingArea.Width;
|
||||
|
||||
|
||||
if (windowRect.Height > workingArea.Height)
|
||||
windowRect.Height = workingArea.Height;
|
||||
|
||||
|
||||
// 调整位置以确保窗口完全在工作区域内
|
||||
if (windowRect.Left < workingArea.Left)
|
||||
windowRect.X = workingArea.Left;
|
||||
else if (windowRect.Right > workingArea.Right)
|
||||
windowRect.X = workingArea.Right - windowRect.Width;
|
||||
|
||||
|
||||
if (windowRect.Top < workingArea.Top)
|
||||
windowRect.Y = workingArea.Top;
|
||||
else if (windowRect.Bottom > workingArea.Bottom)
|
||||
windowRect.Y = workingArea.Bottom - windowRect.Height;
|
||||
|
||||
|
||||
return windowRect;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,603 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 上传队列项数据(用于序列化)
|
||||
/// </summary>
|
||||
public class UploadQueueItemData
|
||||
{
|
||||
[JsonProperty("file_path")]
|
||||
public string FilePath { get; set; }
|
||||
|
||||
[JsonProperty("retry_count")]
|
||||
public int RetryCount { get; set; }
|
||||
|
||||
[JsonProperty("added_time")]
|
||||
public DateTime AddedTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传队列项
|
||||
/// </summary>
|
||||
public class UploadQueueItem
|
||||
{
|
||||
public string FilePath { get; set; }
|
||||
public int RetryCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通用上传队列基类
|
||||
/// </summary>
|
||||
public abstract class BaseUploadQueue : IDisposable
|
||||
{
|
||||
protected const int BATCH_SIZE = 10; // 批量上传大小
|
||||
protected const int MAX_RETRY_COUNT = 3; // 最大重试次数
|
||||
|
||||
/// <summary>
|
||||
/// 上传队列
|
||||
/// </summary>
|
||||
protected readonly ConcurrentQueue<UploadQueueItem> _uploadQueue = new ConcurrentQueue<UploadQueueItem>();
|
||||
|
||||
/// <summary>
|
||||
/// 队列处理锁,防止并发处理
|
||||
/// </summary>
|
||||
protected readonly SemaphoreSlim _queueProcessingLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// 队列保存锁,防止并发保存
|
||||
/// </summary>
|
||||
protected readonly SemaphoreSlim _queueSaveLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// 是否已初始化队列
|
||||
/// </summary>
|
||||
protected bool _isQueueInitialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已释放资源
|
||||
/// </summary>
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// 队列文件名
|
||||
/// </summary>
|
||||
protected abstract string QueueFileName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 允许的文件扩展名
|
||||
/// </summary>
|
||||
protected virtual HashSet<string> AllowedExtensions => new HashSet<string> { ".png", ".icstk", ".xml", ".zip" };
|
||||
|
||||
/// <summary>
|
||||
/// 获取队列文件路径
|
||||
/// </summary>
|
||||
protected string GetQueueFilePath()
|
||||
{
|
||||
var configsDir = Path.Combine(App.RootPath, "Configs");
|
||||
if (!Directory.Exists(configsDir))
|
||||
{
|
||||
Directory.CreateDirectory(configsDir);
|
||||
}
|
||||
return Path.Combine(configsDir, QueueFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取最大文件大小
|
||||
/// </summary>
|
||||
/// <param name="extension">文件扩展名</param>
|
||||
/// <returns>最大文件大小(字节)</returns>
|
||||
protected virtual long GetMaxFileSize(string extension)
|
||||
{
|
||||
return extension == ".zip" ? 50 * 1024 * 1024 : 10 * 1024 * 1024;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化上传队列
|
||||
/// </summary>
|
||||
public void InitializeQueue()
|
||||
{
|
||||
if (_isQueueInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var queueFilePath = GetQueueFilePath();
|
||||
if (!File.Exists(queueFilePath))
|
||||
{
|
||||
_isQueueInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var jsonContent = File.ReadAllText(queueFilePath);
|
||||
if (string.IsNullOrWhiteSpace(jsonContent))
|
||||
{
|
||||
_isQueueInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var queueData = JsonConvert.DeserializeObject<List<UploadQueueItemData>>(jsonContent);
|
||||
if (queueData == null || queueData.Count == 0)
|
||||
{
|
||||
_isQueueInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int restoredCount = 0;
|
||||
int skippedCount = 0;
|
||||
|
||||
foreach (var item in queueData)
|
||||
{
|
||||
// 验证文件是否存在
|
||||
if (!File.Exists(item.FilePath))
|
||||
{
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 验证文件格式和大小
|
||||
if (!IsValidFile(item.FilePath))
|
||||
{
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 恢复队列项
|
||||
_uploadQueue.Enqueue(new UploadQueueItem
|
||||
{
|
||||
FilePath = item.FilePath,
|
||||
RetryCount = item.RetryCount
|
||||
});
|
||||
restoredCount++;
|
||||
}
|
||||
|
||||
_isQueueInitialized = true;
|
||||
|
||||
if (restoredCount > 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 已恢复上传队列:{restoredCount}个文件,跳过{skippedCount}个无效文件", LogHelper.LogType.Event);
|
||||
// 如果恢复了队列,触发处理
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessUploadQueueAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 恢复上传队列后处理时出错: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (skippedCount > 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 队列恢复完成:跳过{skippedCount}个无效文件", LogHelper.LogType.Event);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 恢复上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
_isQueueInitialized = true; // 即使出错也标记为已初始化,避免重复尝试
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存队列到文件
|
||||
/// </summary>
|
||||
protected async Task SaveQueueToFileAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!await _queueSaveLock.WaitAsync(1000, cancellationToken)) // 最多等待1秒
|
||||
{
|
||||
return; // 如果无法获取锁,跳过保存(避免阻塞)
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var queueData = new List<UploadQueueItemData>();
|
||||
|
||||
// 将队列转换为可序列化的格式
|
||||
foreach (var item in _uploadQueue)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
queueData.Add(new UploadQueueItemData
|
||||
{
|
||||
FilePath = item.FilePath,
|
||||
RetryCount = item.RetryCount,
|
||||
AddedTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
var queueFilePath = GetQueueFilePath();
|
||||
|
||||
// 如果队列为空,清空文件
|
||||
if (queueData.Count == 0)
|
||||
{
|
||||
ClearQueueFile();
|
||||
return;
|
||||
}
|
||||
|
||||
var jsonContent = JsonConvert.SerializeObject(queueData, Formatting.Indented);
|
||||
|
||||
// 使用进程保护的写入门控,避免安全面板中"进程文件保护"占用导致无法写入
|
||||
var tempFilePath = queueFilePath + ".tmp";
|
||||
ProcessProtectionManager.WithWriteAccess(queueFilePath, () =>
|
||||
{
|
||||
File.WriteAllText(tempFilePath, jsonContent);
|
||||
if (File.Exists(queueFilePath))
|
||||
File.Delete(queueFilePath);
|
||||
File.Move(tempFilePath, queueFilePath);
|
||||
});
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 取消操作,静默处理
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 保存上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueSaveLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空队列文件
|
||||
/// </summary>
|
||||
protected void ClearQueueFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
var queueFilePath = GetQueueFilePath();
|
||||
ProcessProtectionManager.WithWriteAccess(queueFilePath, () =>
|
||||
{
|
||||
if (File.Exists(queueFilePath))
|
||||
File.WriteAllText(queueFilePath, "[]");
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 清空队列文件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将文件加入上传队列
|
||||
/// </summary>
|
||||
protected void EnqueueFile(string filePath, int retryCount = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_uploadQueue.Enqueue(new UploadQueueItem
|
||||
{
|
||||
FilePath = filePath,
|
||||
RetryCount = retryCount
|
||||
});
|
||||
|
||||
// 异步保存队列到文件
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await SaveQueueToFileAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 取消操作,静默处理
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 保存上传队列时出错(后台任务): {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}, cancellationToken);
|
||||
|
||||
// 触发队列处理
|
||||
_ = ProcessUploadQueueAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理上传队列,批量上传文件
|
||||
/// </summary>
|
||||
protected async Task ProcessUploadQueueAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 使用信号量防止并发处理
|
||||
if (!await _queueProcessingLock.WaitAsync(0, cancellationToken))
|
||||
{
|
||||
return; // 已有处理任务在运行
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var filesToUpload = new List<UploadQueueItem>();
|
||||
|
||||
// 从队列中取出最多BATCH_SIZE个文件
|
||||
int count = 0;
|
||||
while (count < BATCH_SIZE && _uploadQueue.TryDequeue(out UploadQueueItem item))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 再次检查文件是否存在
|
||||
if (File.Exists(item.FilePath) && IsValidFile(item.FilePath))
|
||||
{
|
||||
filesToUpload.Add(item);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (filesToUpload.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否启用
|
||||
if (!IsUploadEnabled())
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败:上传未启用", LogHelper.LogType.Error);
|
||||
// 将文件重新加入队列
|
||||
foreach (var item in filesToUpload)
|
||||
{
|
||||
EnqueueFile(item.FilePath, item.RetryCount, cancellationToken);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 并发上传所有文件,并处理失败重试
|
||||
var uploadTasks = filesToUpload.Select(async item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var success = await UploadFileInternalAsync(item.FilePath, cancellationToken);
|
||||
if (!success)
|
||||
{
|
||||
// 检查是否是可重试的错误
|
||||
if (IsRetryableError(item.FilePath))
|
||||
{
|
||||
// 检查重试次数
|
||||
if (item.RetryCount < MAX_RETRY_COUNT)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败,将重试 ({item.RetryCount + 1}/{MAX_RETRY_COUNT}): {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Event);
|
||||
EnqueueFile(item.FilePath, item.RetryCount + 1, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败,已达到最大重试次数: {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 取消操作,将文件重新加入队列
|
||||
EnqueueFile(item.FilePath, item.RetryCount, cancellationToken);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 检查是否是可重试的错误(超时、网络错误等)
|
||||
var errorMessage = ex.Message.ToLower();
|
||||
bool isRetryable = errorMessage.Contains("超时") ||
|
||||
errorMessage.Contains("timeout") ||
|
||||
errorMessage.Contains("网络错误") ||
|
||||
errorMessage.Contains("network") ||
|
||||
errorMessage.Contains("408") || // 请求超时
|
||||
errorMessage.Contains("423") || // 资源锁定
|
||||
errorMessage.Contains("429") || // 请求过多
|
||||
errorMessage.Contains("500") || // 服务器错误
|
||||
errorMessage.Contains("502") || // 网关错误
|
||||
errorMessage.Contains("503") || // 服务不可用
|
||||
errorMessage.Contains("504"); // 网关超时
|
||||
|
||||
if (isRetryable && IsRetryableError(item.FilePath))
|
||||
{
|
||||
// 检查重试次数
|
||||
if (item.RetryCount < MAX_RETRY_COUNT)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败({ex.Message}),将重试 ({item.RetryCount + 1}/{MAX_RETRY_COUNT}): {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Error);
|
||||
EnqueueFile(item.FilePath, item.RetryCount + 1, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败,已达到最大重试次数: {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败(不可重试): {Path.GetFileName(item.FilePath)} - {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
await Task.WhenAll(uploadTasks).ConfigureAwait(false);
|
||||
|
||||
// 上传完成后保存队列状态
|
||||
await SaveQueueToFileAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 检查队列中是否还有文件,如果有就继续处理
|
||||
if (_uploadQueue.Count > 0)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessUploadQueueAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 继续处理上传队列时出错: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueProcessingLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证文件是否有效
|
||||
/// </summary>
|
||||
protected virtual bool IsValidFile(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileExtension = Path.GetExtension(filePath).ToLower();
|
||||
if (!AllowedExtensions.Contains(fileExtension))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
long maxSize = GetMaxFileSize(fileExtension);
|
||||
if (fileInfo.Length > maxSize)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败:文件过大({fileInfo.Length / 1024 / 1024:F2}MB),超过{maxSize / 1024 / 1024}MB限制", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断错误是否可重试
|
||||
/// </summary>
|
||||
protected bool IsRetryableError(string filePath)
|
||||
{
|
||||
// 检查文件是否存在
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return false; // 文件不存在,不可重试
|
||||
}
|
||||
|
||||
// 检查文件是否有效
|
||||
if (!IsValidFile(filePath))
|
||||
{
|
||||
return false; // 文件无效,不可重试
|
||||
}
|
||||
|
||||
// 检查是否启用
|
||||
if (!IsUploadEnabled())
|
||||
{
|
||||
return false; // 上传未启用,不可重试
|
||||
}
|
||||
|
||||
// 其他错误(超时、网络错误等)可以重试
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查上传是否启用
|
||||
/// </summary>
|
||||
protected abstract bool IsUploadEnabled();
|
||||
|
||||
/// <summary>
|
||||
/// 内部上传方法,执行实际上传操作
|
||||
/// </summary>
|
||||
protected abstract Task<bool> UploadFileInternalAsync(string filePath, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 异步上传文件
|
||||
/// </summary>
|
||||
public async Task<bool> UploadFileAsync(string filePath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 检查是否启用
|
||||
if (!IsUploadEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 基本验证
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败:文件不存在 - {filePath}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsValidFile(filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保队列已初始化
|
||||
if (!_isQueueInitialized)
|
||||
{
|
||||
InitializeQueue();
|
||||
}
|
||||
|
||||
// 加入队列
|
||||
EnqueueFile(filePath, 0, cancellationToken);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传被取消: {Path.GetFileName(filePath)}", LogHelper.LogType.Event);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 加入上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
/// <param name="disposing">是否手动释放</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_queueProcessingLock?.Dispose();
|
||||
_queueSaveLock?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 析构函数
|
||||
/// </summary>
|
||||
~BaseUploadQueue()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
using AForge.Video;
|
||||
using AForge.Video.DirectShow;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public class CameraService : IDisposable
|
||||
{
|
||||
private VideoCaptureDevice _videoSource;
|
||||
private bool _isCapturing;
|
||||
private Bitmap _currentFrame;
|
||||
private readonly object _frameLock = new object();
|
||||
private Dispatcher _dispatcher;
|
||||
|
||||
// 新增属性
|
||||
private int _rotationAngle = 0; // 0=0度,1=90度,2=180度,3=270度
|
||||
private int _resolutionWidth = 640;
|
||||
private int _resolutionHeight = 480;
|
||||
|
||||
public event EventHandler<Bitmap> FrameReceived;
|
||||
public event EventHandler<string> ErrorOccurred;
|
||||
|
||||
public bool IsCapturing => _isCapturing;
|
||||
public List<FilterInfo> AvailableCameras { get; private set; }
|
||||
public FilterInfo CurrentCamera { get; private set; }
|
||||
|
||||
// 新增属性
|
||||
public int RotationAngle
|
||||
{
|
||||
get => _rotationAngle;
|
||||
set => _rotationAngle = Math.Max(0, Math.Min(3, value));
|
||||
}
|
||||
|
||||
public int ResolutionWidth
|
||||
{
|
||||
get => _resolutionWidth;
|
||||
set => _resolutionWidth = Math.Max(320, Math.Min(3840, value));
|
||||
}
|
||||
|
||||
public int ResolutionHeight
|
||||
{
|
||||
get => _resolutionHeight;
|
||||
set => _resolutionHeight = Math.Max(240, Math.Min(2160, value));
|
||||
}
|
||||
|
||||
public CameraService()
|
||||
{
|
||||
_dispatcher = Dispatcher.CurrentDispatcher;
|
||||
AvailableCameras = new List<FilterInfo>();
|
||||
RefreshCameraList();
|
||||
}
|
||||
|
||||
public CameraService(int rotationAngle, int resolutionWidth, int resolutionHeight)
|
||||
{
|
||||
_dispatcher = Dispatcher.CurrentDispatcher;
|
||||
AvailableCameras = new List<FilterInfo>();
|
||||
_rotationAngle = rotationAngle;
|
||||
_resolutionWidth = resolutionWidth;
|
||||
_resolutionHeight = resolutionHeight;
|
||||
RefreshCameraList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新可用摄像头列表
|
||||
/// </summary>
|
||||
public void RefreshCameraList()
|
||||
{
|
||||
try
|
||||
{
|
||||
AvailableCameras.Clear();
|
||||
var videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
|
||||
|
||||
foreach (FilterInfo device in videoDevices)
|
||||
{
|
||||
AvailableCameras.Add(device);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"刷新摄像头列表失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
ErrorOccurred?.Invoke(this, $"刷新摄像头列表失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始摄像头预览
|
||||
/// </summary>
|
||||
/// <param name="cameraIndex">摄像头索引</param>
|
||||
public bool StartPreview(int cameraIndex = 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (AvailableCameras.Count == 0)
|
||||
{
|
||||
RefreshCameraList();
|
||||
if (AvailableCameras.Count == 0)
|
||||
{
|
||||
ErrorOccurred?.Invoke(this, "未找到可用的摄像头设备");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (cameraIndex < 0 || cameraIndex >= AvailableCameras.Count)
|
||||
{
|
||||
ErrorOccurred?.Invoke(this, "摄像头索引超出范围");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 停止当前预览
|
||||
StopPreview();
|
||||
|
||||
CurrentCamera = AvailableCameras[cameraIndex];
|
||||
_videoSource = new VideoCaptureDevice(CurrentCamera.MonikerString);
|
||||
|
||||
// 设置视频源事件处理
|
||||
_videoSource.NewFrame += VideoSource_NewFrame;
|
||||
|
||||
// 启动视频源
|
||||
_videoSource.Start();
|
||||
|
||||
_isCapturing = true;
|
||||
LogHelper.WriteLogToFile($"开始摄像头预览: {CurrentCamera.Name}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动摄像头预览失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
ErrorOccurred?.Invoke(this, $"启动摄像头预览失败: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止摄像头预览
|
||||
/// </summary>
|
||||
public void StopPreview()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_videoSource != null && _videoSource.IsRunning)
|
||||
{
|
||||
_videoSource.SignalToStop();
|
||||
_videoSource.WaitForStop();
|
||||
_videoSource.NewFrame -= VideoSource_NewFrame;
|
||||
_videoSource = null;
|
||||
}
|
||||
|
||||
_isCapturing = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"停止摄像头预览失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到指定摄像头
|
||||
/// </summary>
|
||||
/// <param name="cameraIndex">摄像头索引</param>
|
||||
public bool SwitchCamera(int cameraIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (cameraIndex < 0 || cameraIndex >= AvailableCameras.Count)
|
||||
{
|
||||
ErrorOccurred?.Invoke(this, "摄像头索引超出范围");
|
||||
return false;
|
||||
}
|
||||
|
||||
return StartPreview(cameraIndex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"切换摄像头失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
ErrorOccurred?.Invoke(this, $"切换摄像头失败: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前帧的BitmapSource(WPF格式),直接返回可用的WPF位图
|
||||
/// </summary>
|
||||
public BitmapSource GetCurrentFrameAsBitmapSource()
|
||||
{
|
||||
lock (_frameLock)
|
||||
{
|
||||
if (_currentFrame == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// 验证位图有效性
|
||||
if (_currentFrame.Width <= 0 || _currentFrame.Height <= 0)
|
||||
return null;
|
||||
|
||||
// 使用更安全的方法转换位图
|
||||
var bitmapData = _currentFrame.LockBits(
|
||||
new Rectangle(0, 0, _currentFrame.Width, _currentFrame.Height),
|
||||
ImageLockMode.ReadOnly,
|
||||
_currentFrame.PixelFormat);
|
||||
|
||||
try
|
||||
{
|
||||
// 根据像素格式选择合适的WPF像素格式
|
||||
System.Windows.Media.PixelFormat wpfPixelFormat;
|
||||
switch (_currentFrame.PixelFormat)
|
||||
{
|
||||
case PixelFormat.Format24bppRgb:
|
||||
wpfPixelFormat = System.Windows.Media.PixelFormats.Bgr24;
|
||||
break;
|
||||
case PixelFormat.Format32bppArgb:
|
||||
wpfPixelFormat = System.Windows.Media.PixelFormats.Bgra32;
|
||||
break;
|
||||
case PixelFormat.Format32bppRgb:
|
||||
wpfPixelFormat = System.Windows.Media.PixelFormats.Bgr32;
|
||||
break;
|
||||
default:
|
||||
wpfPixelFormat = System.Windows.Media.PixelFormats.Bgr24;
|
||||
break;
|
||||
}
|
||||
|
||||
var bitmapSource = BitmapSource.Create(
|
||||
bitmapData.Width,
|
||||
bitmapData.Height,
|
||||
_currentFrame.HorizontalResolution,
|
||||
_currentFrame.VerticalResolution,
|
||||
wpfPixelFormat,
|
||||
null,
|
||||
bitmapData.Scan0,
|
||||
bitmapData.Stride * bitmapData.Height,
|
||||
bitmapData.Stride);
|
||||
|
||||
bitmapSource.Freeze();
|
||||
return bitmapSource;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentFrame.UnlockBits(bitmapData);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"转换帧为BitmapSource失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 视频源新帧事件处理
|
||||
/// </summary>
|
||||
private void VideoSource_NewFrame(object sender, NewFrameEventArgs eventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_frameLock)
|
||||
{
|
||||
// 释放之前的帧
|
||||
_currentFrame?.Dispose();
|
||||
|
||||
// 创建新的位图,避免Clone的问题
|
||||
var sourceFrame = eventArgs.Frame;
|
||||
|
||||
if (sourceFrame != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var width = sourceFrame.Width;
|
||||
var height = sourceFrame.Height;
|
||||
|
||||
if (width > 0 && height > 0)
|
||||
{
|
||||
// 应用旋转
|
||||
Bitmap rotatedFrame = ApplyRotation(sourceFrame);
|
||||
|
||||
int targetWidth = _resolutionWidth;
|
||||
int targetHeight = _resolutionHeight;
|
||||
|
||||
if (_rotationAngle == 1 || _rotationAngle == 3)
|
||||
{
|
||||
targetWidth = _resolutionHeight;
|
||||
targetHeight = _resolutionWidth;
|
||||
}
|
||||
|
||||
_currentFrame = ResizeImageWithAspectRatio(rotatedFrame, targetWidth, targetHeight);
|
||||
|
||||
rotatedFrame?.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentFrame = null;
|
||||
}
|
||||
}
|
||||
catch (Exception frameEx)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理源帧失败: {frameEx.Message}", LogHelper.LogType.Error);
|
||||
_currentFrame = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentFrame = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 在UI线程中触发事件
|
||||
_dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
FrameReceived?.Invoke(this, _currentFrame);
|
||||
}));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理新帧失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
ErrorOccurred?.Invoke(this, $"处理新帧失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取摄像头名称列表
|
||||
/// </summary>
|
||||
public List<string> GetCameraNames()
|
||||
{
|
||||
return AvailableCameras.Select(camera => camera.Name).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否有可用摄像头
|
||||
/// </summary>
|
||||
public bool HasAvailableCameras()
|
||||
{
|
||||
if (AvailableCameras.Count == 0)
|
||||
{
|
||||
RefreshCameraList();
|
||||
}
|
||||
return AvailableCameras.Count > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用旋转到图像
|
||||
/// </summary>
|
||||
private Bitmap ApplyRotation(Bitmap source)
|
||||
{
|
||||
if (_rotationAngle == 0)
|
||||
return new Bitmap(source);
|
||||
|
||||
var rotationType = RotateFlipType.RotateNoneFlipNone;
|
||||
switch (_rotationAngle)
|
||||
{
|
||||
case 1: rotationType = RotateFlipType.Rotate90FlipNone; break;
|
||||
case 2: rotationType = RotateFlipType.Rotate180FlipNone; break;
|
||||
case 3: rotationType = RotateFlipType.Rotate270FlipNone; break;
|
||||
}
|
||||
|
||||
var rotated = new Bitmap(source);
|
||||
rotated.RotateFlip(rotationType);
|
||||
return rotated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调整图像大小
|
||||
/// </summary>
|
||||
private Bitmap ResizeImageWithAspectRatio(Bitmap source, int targetWidth, int targetHeight)
|
||||
{
|
||||
if (source.Width == targetWidth && source.Height == targetHeight)
|
||||
return new Bitmap(source);
|
||||
|
||||
double scaleX = (double)targetWidth / source.Width;
|
||||
double scaleY = (double)targetHeight / source.Height;
|
||||
double scale = Math.Min(scaleX, scaleY);
|
||||
|
||||
// 计算实际尺寸
|
||||
int actualWidth = (int)(source.Width * scale);
|
||||
int actualHeight = (int)(source.Height * scale);
|
||||
|
||||
var resized = new Bitmap(actualWidth, actualHeight, PixelFormat.Format24bppRgb);
|
||||
using (var graphics = Graphics.FromImage(resized))
|
||||
{
|
||||
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
||||
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
|
||||
graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
|
||||
graphics.DrawImage(source, 0, 0, actualWidth, actualHeight);
|
||||
}
|
||||
return resized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调整图像大小
|
||||
/// </summary>
|
||||
private Bitmap ResizeImage(Bitmap source, int width, int height)
|
||||
{
|
||||
if (source.Width == width && source.Height == height)
|
||||
return new Bitmap(source);
|
||||
|
||||
var resized = new Bitmap(width, height, PixelFormat.Format24bppRgb);
|
||||
using (var graphics = Graphics.FromImage(resized))
|
||||
{
|
||||
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
||||
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
|
||||
graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
|
||||
graphics.DrawImage(source, 0, 0, width, height);
|
||||
}
|
||||
return resized;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopPreview();
|
||||
|
||||
lock (_frameLock)
|
||||
{
|
||||
_currentFrame?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public class ComPPTLinkManager : IPPTLinkManager
|
||||
{
|
||||
private readonly PPTManager _inner;
|
||||
|
||||
public ComPPTLinkManager()
|
||||
{
|
||||
_inner = new PPTManager();
|
||||
|
||||
_inner.SlideShowBegin += wn => SlideShowBegin?.Invoke(wn);
|
||||
_inner.SlideShowNextSlide += wn => SlideShowNextSlide?.Invoke(wn);
|
||||
_inner.SlideShowEnd += pres => SlideShowEnd?.Invoke(pres);
|
||||
_inner.PresentationOpen += pres => PresentationOpen?.Invoke(pres);
|
||||
_inner.PresentationClose += pres => PresentationClose?.Invoke(pres);
|
||||
_inner.PPTConnectionChanged += connected => PPTConnectionChanged?.Invoke(connected);
|
||||
_inner.SlideShowStateChanged += inSlideShow => SlideShowStateChanged?.Invoke(inSlideShow);
|
||||
}
|
||||
|
||||
#region IPPTLinkManager 事件
|
||||
public event Action<object> SlideShowBegin;
|
||||
public event Action<object> SlideShowNextSlide;
|
||||
public event Action<object> SlideShowEnd;
|
||||
public event Action<object> PresentationOpen;
|
||||
public event Action<object> PresentationClose;
|
||||
public event Action<bool> PPTConnectionChanged;
|
||||
public event Action<bool> SlideShowStateChanged;
|
||||
#endregion
|
||||
|
||||
#region IPPTLinkManager 属性
|
||||
public bool IsConnected => _inner.IsConnected;
|
||||
|
||||
public bool IsInSlideShow => _inner.IsInSlideShow;
|
||||
|
||||
public bool IsSupportWPS
|
||||
{
|
||||
get => _inner.IsSupportWPS;
|
||||
set => _inner.IsSupportWPS = value;
|
||||
}
|
||||
|
||||
public bool SkipAnimationsWhenNavigating
|
||||
{
|
||||
get => _inner.SkipAnimationsWhenNavigating;
|
||||
set => _inner.SkipAnimationsWhenNavigating = value;
|
||||
}
|
||||
|
||||
public int SlidesCount => _inner.SlidesCount;
|
||||
|
||||
public object PPTApplication => _inner.PPTApplication;
|
||||
#endregion
|
||||
|
||||
#region 生命周期管理
|
||||
/// <summary>
|
||||
/// 开始监控本地 PowerPoint 的连接与运行状态,并在状态变化时触发相应事件。
|
||||
/// </summary>
|
||||
public void StartMonitoring() => _inner.StartMonitoring();
|
||||
|
||||
/// <summary>
|
||||
/// 停止对 PowerPoint 的监控,断开当前连接并停止触发相关事件。
|
||||
/// </summary>
|
||||
public void StopMonitoring() => _inner.StopMonitoring();
|
||||
|
||||
/// <summary>
|
||||
/// 强制断开当前 COM PPT 连接并停止对其监控,同时写入事件日志。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 会向日志记录一条事件信息并调用内部管理器停止监控;该方法不会重新启动监控或重新初始化内部管理器实例。
|
||||
/// </remarks>
|
||||
public void ReloadConnection()
|
||||
{
|
||||
LogHelper.WriteLogToFile("COM PPT 执行热重载:强制断开并重新连接", LogHelper.LogType.Event);
|
||||
_inner.StopMonitoring();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 放映控制
|
||||
public bool TryStartSlideShow() => _inner.TryStartSlideShow();
|
||||
|
||||
public bool TryEndSlideShow() => _inner.TryEndSlideShow();
|
||||
#endregion
|
||||
|
||||
#region 导航控制
|
||||
public bool TryNavigateToSlide(int slideNumber) => _inner.TryNavigateToSlide(slideNumber);
|
||||
|
||||
public bool TryNavigateNext() => _inner.TryNavigateNext();
|
||||
|
||||
public bool TryNavigatePrevious() => _inner.TryNavigatePrevious();
|
||||
#endregion
|
||||
|
||||
#region 查询
|
||||
public int GetCurrentSlideNumber() => _inner.GetCurrentSlideNumber();
|
||||
|
||||
public string GetPresentationName() => _inner.GetPresentationName();
|
||||
|
||||
public bool TryShowSlideNavigation() => _inner.TryShowSlideNavigation();
|
||||
|
||||
public object GetCurrentActivePresentation() => _inner.GetCurrentActivePresentation();
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
_inner?.Dispose();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供多配置文件保存、切换与热重载支持。
|
||||
/// 方案保存在 Configs/Profiles 目录下,当前生效的配置仍为 Configs/Settings.json。
|
||||
/// </summary>
|
||||
public static class ConfigProfileManager
|
||||
{
|
||||
private static readonly string ProfilesDir = Path.Combine(App.RootPath, "Configs", "Profiles");
|
||||
private static readonly string SettingsFilePath = Path.Combine(App.RootPath, "Configs", "Settings.json");
|
||||
private const string ProfileExtension = ".json";
|
||||
|
||||
/// <summary>将配置文件名称转为安全文件名(去掉非法字符)。</summary>
|
||||
private static string ToSafeFileName(string profileName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(profileName)) return "未命名";
|
||||
var invalid = Path.GetInvalidFileNameChars();
|
||||
var name = string.Join("_", profileName.Trim().Split(invalid, StringSplitOptions.RemoveEmptyEntries));
|
||||
return string.IsNullOrEmpty(name) ? "未命名" : name;
|
||||
}
|
||||
|
||||
/// <summary>确保配置文件目录存在。</summary>
|
||||
public static void EnsureProfilesDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(ProfilesDir))
|
||||
{
|
||||
ProcessProtectionManager.WithWriteAccess(ProfilesDir, () => Directory.CreateDirectory(ProfilesDir));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建配置文件目录失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>获取所有配置文件名称(不含扩展名),按名称排序。</summary>
|
||||
public static IReadOnlyList<string> ListProfileNames()
|
||||
{
|
||||
try
|
||||
{
|
||||
EnsureProfilesDirectory();
|
||||
if (!Directory.Exists(ProfilesDir)) return Array.Empty<string>();
|
||||
var files = Directory.GetFiles(ProfilesDir, "*" + ProfileExtension);
|
||||
return files
|
||||
.Select(f => Path.GetFileNameWithoutExtension(f))
|
||||
.Where(n => !string.IsNullOrEmpty(n))
|
||||
.OrderBy(n => n, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"列举配置文件失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>获取某配置文件对应的文件路径。</summary>
|
||||
public static string GetProfilePath(string profileName)
|
||||
{
|
||||
var safe = ToSafeFileName(profileName);
|
||||
return Path.Combine(ProfilesDir, safe + ProfileExtension);
|
||||
}
|
||||
|
||||
/// <summary>将当前配置的 JSON 内容保存为指定名称的配置文件。</summary>
|
||||
/// <param name="profileName">配置文件显示名称(会转为安全文件名)。</param>
|
||||
/// <param name="settingsJson">已序列化好的 Settings JSON 字符串。</param>
|
||||
/// <returns>成功返回 true。</returns>
|
||||
public static bool SaveAsProfile(string profileName, string settingsJson)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(settingsJson))
|
||||
{
|
||||
LogHelper.WriteLogToFile("配置文件保存失败:内容为空", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
EnsureProfilesDirectory();
|
||||
var path = GetProfilePath(profileName);
|
||||
ProcessProtectionManager.WithWriteAccess(path, () => File.WriteAllText(path, settingsJson));
|
||||
LogHelper.WriteLogToFile($"配置文件已保存: {ToSafeFileName(profileName)}", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存配置文件失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>将指定配置文件应用到当前配置(覆盖 Configs/Settings.json),供主窗口随后热重载。</summary>
|
||||
/// <param name="profileName">配置文件名称(与 ListProfileNames 中一致,或与保存时使用的显示名一致)。</param>
|
||||
/// <returns>成功返回 true;文件不存在或复制失败返回 false。</returns>
|
||||
public static bool ApplyProfile(string profileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = GetProfilePath(profileName);
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"配置文件文件不存在: {path}", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
var json = File.ReadAllText(path);
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
LogHelper.WriteLogToFile("配置文件内容为空", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
// 可选:校验是否为合法 Settings JSON
|
||||
try
|
||||
{
|
||||
JsonConvert.DeserializeObject<Settings>(json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"配置文件格式无效: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
var configsDir = Path.GetDirectoryName(SettingsFilePath);
|
||||
if (!string.IsNullOrEmpty(configsDir) && !Directory.Exists(configsDir))
|
||||
{
|
||||
ProcessProtectionManager.WithWriteAccess(configsDir, () => Directory.CreateDirectory(configsDir));
|
||||
}
|
||||
ProcessProtectionManager.WithWriteAccess(SettingsFilePath, () => File.WriteAllText(SettingsFilePath, json));
|
||||
LogHelper.WriteLogToFile($"已应用配置文件: {profileName}(请热重载以生效)", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用配置文件失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>删除指定名称的配置文件。</summary>
|
||||
public static bool DeleteProfile(string profileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = GetProfilePath(profileName);
|
||||
if (!File.Exists(path)) return true;
|
||||
ProcessProtectionManager.WithWriteAccess(path, () => File.Delete(path));
|
||||
LogHelper.WriteLogToFile($"已删除配置文件: {profileName}", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"删除配置文件失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Converter
|
||||
{
|
||||
@@ -9,125 +10,170 @@ namespace Ink_Canvas.Converter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((bool)value == true)
|
||||
if ((bool)value)
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((bool)value == true)
|
||||
if ((bool)value)
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
public class VisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
Visibility visibility = (Visibility)value;
|
||||
if (visibility == Visibility.Visible)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
Visibility visibility = (Visibility)value;
|
||||
if (visibility == Visibility.Visible)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
return Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
public class IntNumberToString : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "无限制";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((double)value).ToString() + "人";
|
||||
}
|
||||
|
||||
return ((double)value) + "人";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "无限制";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((double)value).ToString() + "人";
|
||||
}
|
||||
|
||||
return ((double)value) + "人";
|
||||
}
|
||||
}
|
||||
|
||||
public class IntNumberToString2 : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "自动截图";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((double)value).ToString() + "条";
|
||||
}
|
||||
|
||||
return ((double)value) + "条";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "自动截图";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((double)value).ToString() + "条";
|
||||
}
|
||||
|
||||
return ((double)value) + "条";
|
||||
}
|
||||
}
|
||||
|
||||
public class IsEnabledToOpacityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
bool isChecked = (bool)value;
|
||||
if (isChecked == true)
|
||||
if (isChecked)
|
||||
{
|
||||
return 1d;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0.35;
|
||||
}
|
||||
|
||||
return 0.35;
|
||||
}
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public class InverseBooleanToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((bool)value)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((bool)value)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
return Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
public class RippleEffectTranslationConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is double d)
|
||||
{
|
||||
return -d / 2;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class StringToGeometryConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (value is string geometryString && !string.IsNullOrEmpty(geometryString))
|
||||
{
|
||||
return Geometry.Parse(geometryString);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public static class DebugConsoleManager
|
||||
{
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool AllocConsole();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool FreeConsole();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr GetConsoleWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool DeleteMenu(IntPtr hMenu, uint uPosition, uint uFlags);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool SetConsoleTitle(string lpConsoleTitle);
|
||||
|
||||
private const int SW_HIDE = 0;
|
||||
private const int SW_SHOW = 5;
|
||||
private const uint SC_CLOSE = 0xF060;
|
||||
private const uint MF_BYCOMMAND = 0x00000000;
|
||||
|
||||
private static bool _allocated;
|
||||
|
||||
public static bool IsVisible { get; private set; }
|
||||
|
||||
public static void Show()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_allocated)
|
||||
{
|
||||
if (GetConsoleWindow() == IntPtr.Zero)
|
||||
{
|
||||
if (!AllocConsole()) return;
|
||||
}
|
||||
_allocated = true;
|
||||
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
SetConsoleTitle("InkCanvasForClass - Debug Console");
|
||||
|
||||
// 移除关闭菜单,避免用户点 X 时直接结束进程
|
||||
var hWnd = GetConsoleWindow();
|
||||
if (hWnd != IntPtr.Zero)
|
||||
{
|
||||
var hMenu = GetSystemMenu(hWnd, false);
|
||||
if (hMenu != IntPtr.Zero) DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var hWnd = GetConsoleWindow();
|
||||
if (hWnd != IntPtr.Zero) ShowWindow(hWnd, SW_SHOW);
|
||||
}
|
||||
IsVisible = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[DebugConsoleManager] Show failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Hide()
|
||||
{
|
||||
try
|
||||
{
|
||||
var hWnd = GetConsoleWindow();
|
||||
if (hWnd != IntPtr.Zero) ShowWindow(hWnd, SW_HIDE);
|
||||
IsVisible = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[DebugConsoleManager] Hide failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteLine(string line)
|
||||
{
|
||||
if (!IsVisible) return;
|
||||
try { Console.WriteLine(line); }
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,64 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Helpers {
|
||||
internal class DelAutoSavedFiles {
|
||||
public static void DeleteFilesOlder(string directoryPath, int daysThreshold) {
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal class DelAutoSavedFiles
|
||||
{
|
||||
public static void DeleteFilesOlder(string directoryPath, int daysThreshold)
|
||||
{
|
||||
string[] extensionsToDel = { ".icstk", ".png" };
|
||||
if (Directory.Exists(directoryPath)) {
|
||||
if (Directory.Exists(directoryPath))
|
||||
{
|
||||
// 获取目录中的所有子目录
|
||||
string[] subDirectories = Directory.GetDirectories(directoryPath, "*", SearchOption.AllDirectories);
|
||||
foreach (string subDirectory in subDirectories) {
|
||||
try {
|
||||
foreach (string subDirectory in subDirectories)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取子目录下的所有文件
|
||||
string[] files = Directory.GetFiles(subDirectory);
|
||||
foreach (string filePath in files) {
|
||||
foreach (string filePath in files)
|
||||
{
|
||||
// 获取文件的创建日期
|
||||
DateTime creationDate = File.GetCreationTime(filePath);
|
||||
// 获取文件的扩展名
|
||||
string fileExtension = Path.GetExtension(filePath);
|
||||
// 如果文件的创建日期早于指定天数且是要删除的扩展名,则删除文件
|
||||
if (creationDate < DateTime.Now.AddDays(-daysThreshold)) {
|
||||
if (creationDate < DateTime.Now.AddDays(-daysThreshold))
|
||||
{
|
||||
if (Array.Exists(extensionsToDel, ext => ext.Equals(fileExtension, StringComparison.OrdinalIgnoreCase))
|
||||
|| Path.GetFileName(filePath).Equals("Position", StringComparison.OrdinalIgnoreCase)) {
|
||||
|| Path.GetFileName(filePath).Equals("Position", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LogHelper.WriteLogToFile("DelAutoSavedFiles | 处理文件时出错: " + ex.ToString(), LogHelper.LogType.Error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("DelAutoSavedFiles | 处理文件时出错: " + ex, LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
try { // 递归删除空文件夹
|
||||
try
|
||||
{ // 递归删除空文件夹
|
||||
DeleteEmptyFolders(directoryPath);
|
||||
} catch (Exception ex) {
|
||||
LogHelper.WriteLogToFile("DelAutoSavedFiles | 处理文件时出错: " + ex.ToString(), LogHelper.LogType.Error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("DelAutoSavedFiles | 处理文件时出错: " + ex, LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeleteEmptyFolders(string directoryPath) {
|
||||
foreach (string dir in Directory.GetDirectories(directoryPath)) {
|
||||
private static void DeleteEmptyFolders(string directoryPath)
|
||||
{
|
||||
foreach (string dir in Directory.GetDirectories(directoryPath))
|
||||
{
|
||||
DeleteEmptyFolders(dir);
|
||||
if (Directory.GetFiles(dir).Length == 0 && Directory.GetDirectories(dir).Length == 0) {
|
||||
if (Directory.GetFiles(dir).Length == 0 && Directory.GetDirectories(dir).Length == 0)
|
||||
{
|
||||
Directory.Delete(dir, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
@@ -18,10 +14,13 @@ namespace Ink_Canvas.Helpers
|
||||
/// <param name="inv">同步的對象,一般傳入控件,不需要可null</param>
|
||||
public void DebounceAction(int timeMs, ISynchronizeInvoke inv, Action action)
|
||||
{
|
||||
lock (this) {
|
||||
if (_timerDebounce == null) {
|
||||
lock (this)
|
||||
{
|
||||
if (_timerDebounce == null)
|
||||
{
|
||||
_timerDebounce = new Timer(timeMs) { AutoReset = false };
|
||||
_timerDebounce.Elapsed += (o, e) => {
|
||||
_timerDebounce.Elapsed += (o, e) =>
|
||||
{
|
||||
_timerDebounce.Stop(); _timerDebounce.Close(); _timerDebounce = null;
|
||||
InvokeAction(action, inv);
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,484 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Dlass API 客户端,用于与服务端通信
|
||||
/// </summary>
|
||||
public class DlassApiClient : IDisposable
|
||||
{
|
||||
private const string DEFAULT_BASE_URL = "https://dlass.tech";
|
||||
private readonly string _appId;
|
||||
private readonly string _appSecret;
|
||||
private readonly string _baseUrl;
|
||||
private HttpClient _httpClient;
|
||||
private string _accessToken;
|
||||
private DateTime _tokenExpiresAt;
|
||||
|
||||
private string _userToken;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 Dlass API 客户端
|
||||
/// </summary>
|
||||
/// <param name="appId">应用ID</param>
|
||||
/// <param name="appSecret">应用密钥</param>
|
||||
/// <param name="baseUrl">API基础URL,如果为空则使用默认URL</param>
|
||||
/// <param name="userToken">用户Token,如果提供则优先使用用户token而不是App Secret</param>
|
||||
public DlassApiClient(string appId, string appSecret, string baseUrl = null, string userToken = null)
|
||||
{
|
||||
_appId = appId ?? throw new ArgumentNullException(nameof(appId));
|
||||
_appSecret = appSecret ?? throw new ArgumentNullException(nameof(appSecret));
|
||||
_userToken = userToken;
|
||||
_baseUrl = baseUrl ?? DEFAULT_BASE_URL;
|
||||
|
||||
_baseUrl = _baseUrl.TrimEnd('/');
|
||||
if (!_baseUrl.StartsWith("http://") && !_baseUrl.StartsWith("https://"))
|
||||
{
|
||||
_baseUrl = "https://" + _baseUrl;
|
||||
}
|
||||
|
||||
_httpClient = new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(_baseUrl),
|
||||
Timeout = TimeSpan.FromSeconds(30)
|
||||
};
|
||||
_httpClient.DefaultRequestHeaders.Add("User-Agent", "InkCanvas/1.0");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取访问令牌(Access Token)
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
public async Task<string> GetAccessTokenAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_userToken))
|
||||
{
|
||||
return _userToken;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_accessToken) && DateTime.Now < _tokenExpiresAt.AddMinutes(-5))
|
||||
{
|
||||
return _accessToken;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var requestData = new
|
||||
{
|
||||
app_id = _appId,
|
||||
app_secret = _appSecret,
|
||||
grant_type = "client_credentials"
|
||||
};
|
||||
|
||||
var json = JsonConvert.SerializeObject(requestData);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync("/oauth/token", content, cancellationToken);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var tokenResponse = JsonConvert.DeserializeObject<TokenResponse>(responseContent);
|
||||
_accessToken = tokenResponse.AccessToken;
|
||||
_tokenExpiresAt = DateTime.Now.AddSeconds(tokenResponse.ExpiresIn ?? 3600);
|
||||
return _accessToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"获取Access Token失败: {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
throw new Exception($"获取Access Token时网络错误: {httpEx.Message}", httpEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"获取Access Token时出错: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送GET请求
|
||||
/// </summary>
|
||||
/// <param name="endpoint">API端点</param>
|
||||
/// <param name="requireAuth">是否需要认证</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
public async Task<T> GetAsync<T>(string endpoint, bool requireAuth = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
string token = null;
|
||||
if (requireAuth)
|
||||
{
|
||||
token = await GetAccessTokenAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
|
||||
|
||||
if (requireAuth && !string.IsNullOrEmpty(token))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_userToken))
|
||||
{
|
||||
request.Headers.Add("X-User-Token", token);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
}
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
return JsonConvert.DeserializeObject<T>(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"API请求失败: {response.StatusCode} - {content}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {httpEx.Message}", httpEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送POST请求
|
||||
/// </summary>
|
||||
/// <param name="endpoint">API端点</param>
|
||||
/// <param name="data">请求数据</param>
|
||||
/// <param name="requireAuth">是否需要认证</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
public async Task<T> PostAsync<T>(string endpoint, object data = null, bool requireAuth = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
string token = null;
|
||||
if (requireAuth)
|
||||
{
|
||||
token = await GetAccessTokenAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||
|
||||
if (requireAuth && !string.IsNullOrEmpty(token))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_userToken))
|
||||
{
|
||||
request.Headers.Add("X-User-Token", token);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
}
|
||||
}
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(data);
|
||||
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
return JsonConvert.DeserializeObject<T>(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"API请求失败: {response.StatusCode} - {content}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {httpEx.Message}", httpEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送PUT请求
|
||||
/// </summary>
|
||||
/// <param name="endpoint">API端点</param>
|
||||
/// <param name="data">请求数据</param>
|
||||
/// <param name="requireAuth">是否需要认证</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
public async Task<T> PutAsync<T>(string endpoint, object data = null, bool requireAuth = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
string token = null;
|
||||
if (requireAuth)
|
||||
{
|
||||
token = await GetAccessTokenAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Put, endpoint);
|
||||
|
||||
if (requireAuth && !string.IsNullOrEmpty(token))
|
||||
{
|
||||
// 如果是用户token,使用X-User-Token header
|
||||
if (!string.IsNullOrEmpty(_userToken))
|
||||
{
|
||||
request.Headers.Add("X-User-Token", token);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
}
|
||||
}
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(data);
|
||||
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
return JsonConvert.DeserializeObject<T>(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"API请求失败: {response.StatusCode} - {content}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {httpEx.Message}", httpEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送DELETE请求
|
||||
/// </summary>
|
||||
/// <param name="endpoint">API端点</param>
|
||||
/// <param name="requireAuth">是否需要认证</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
public async Task<bool> DeleteAsync(string endpoint, bool requireAuth = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
string token = null;
|
||||
if (requireAuth)
|
||||
{
|
||||
token = await GetAccessTokenAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Delete, endpoint);
|
||||
|
||||
if (requireAuth && !string.IsNullOrEmpty(token))
|
||||
{
|
||||
// 如果是用户token,使用X-User-Token header
|
||||
if (!string.IsNullOrEmpty(_userToken))
|
||||
{
|
||||
request.Headers.Add("X-User-Token", token);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
}
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传笔记文件
|
||||
/// </summary>
|
||||
/// <param name="endpoint">上传端点</param>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="boardId">白板ID</param>
|
||||
/// <param name="secretKey">白板密钥</param>
|
||||
/// <param name="title">笔记标题(可选)</param>
|
||||
/// <param name="description">笔记描述(可选)</param>
|
||||
/// <param name="tags">笔记标签(可选)</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
public async Task<T> UploadNoteAsync<T>(string endpoint, string filePath, string boardId, string secretKey, string title = null, string description = null, string tags = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException($"文件不存在: {filePath}");
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||
|
||||
// 设置白板认证头
|
||||
request.Headers.Add("X-Board-ID", boardId);
|
||||
request.Headers.Add("X-Secret-Key", secretKey);
|
||||
|
||||
// 创建multipart/form-data内容
|
||||
var content = new MultipartFormDataContent();
|
||||
|
||||
// 添加文件
|
||||
var fileContent = new ByteArrayContent(File.ReadAllBytes(filePath));
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
|
||||
content.Add(fileContent, "file", fileName);
|
||||
|
||||
// 添加可选参数
|
||||
if (!string.IsNullOrEmpty(title))
|
||||
{
|
||||
content.Add(new StringContent(title), "title");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(description))
|
||||
{
|
||||
content.Add(new StringContent(description), "description");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(tags))
|
||||
{
|
||||
content.Add(new StringContent(tags), "tags");
|
||||
}
|
||||
|
||||
request.Content = content;
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(responseContent))
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
return JsonConvert.DeserializeObject<T>(responseContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"上传文件失败: {response.StatusCode} - {responseContent}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
throw new Exception($"上传文件时网络错误: {httpEx.Message}", httpEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"上传文件时出错: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_httpClient?.Dispose();
|
||||
}
|
||||
|
||||
#region 内部类
|
||||
|
||||
/// <summary>
|
||||
/// Token响应模型
|
||||
/// </summary>
|
||||
private class TokenResponse
|
||||
{
|
||||
[JsonProperty("access_token")]
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
[JsonProperty("expires_in")]
|
||||
public int? ExpiresIn { get; set; }
|
||||
|
||||
[JsonProperty("token_type")]
|
||||
public string TokenType { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Dlass上传队列
|
||||
/// </summary>
|
||||
public class DlassUploadQueue : BaseUploadQueue
|
||||
{
|
||||
private const string APP_ID = "app_WkjocWqsrVY7T6zQV2CfiA";
|
||||
private const string APP_SECRET = "o7dx5b5ASGUMcM72PCpmRQYAhSijqaOVHoGyBK0IxbA";
|
||||
|
||||
/// <summary>
|
||||
/// 队列文件名
|
||||
/// </summary>
|
||||
protected override string QueueFileName => "DlassUploadQueue.json";
|
||||
|
||||
/// <summary>
|
||||
/// 上传笔记响应模型
|
||||
/// </summary>
|
||||
public class UploadNoteResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[JsonProperty("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
[JsonProperty("note_id")]
|
||||
public int? NoteId { get; set; }
|
||||
|
||||
[JsonProperty("filename")]
|
||||
public string Filename { get; set; }
|
||||
|
||||
[JsonProperty("file_path")]
|
||||
public string FilePath { get; set; }
|
||||
|
||||
[JsonProperty("file_url")]
|
||||
public string FileUrl { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 白板信息模型(用于查找白板)
|
||||
/// </summary>
|
||||
private class WhiteboardInfo
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("board_id")]
|
||||
public string BoardId { get; set; }
|
||||
|
||||
[JsonProperty("secret_key")]
|
||||
public string SecretKey { get; set; }
|
||||
|
||||
[JsonProperty("class_name")]
|
||||
public string ClassName { get; set; }
|
||||
|
||||
[JsonProperty("class_id")]
|
||||
public int ClassId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 认证响应模型
|
||||
/// </summary>
|
||||
private class AuthWithTokenResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[JsonProperty("whiteboards")]
|
||||
public List<WhiteboardInfo> Whiteboards { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查上传是否启用
|
||||
/// </summary>
|
||||
protected override bool IsUploadEnabled()
|
||||
{
|
||||
return MainWindow.Settings?.Dlass?.IsAutoUploadNotes == true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部上传方法,执行实际上传操作
|
||||
/// </summary>
|
||||
protected override async Task<bool> UploadFileInternalAsync(string filePath, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 再次检查文件是否存在(可能在队列等待时被删除)
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取白板信息
|
||||
var whiteboard = await GetWhiteboardInfo(cancellationToken);
|
||||
if (whiteboard == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取API基础URL和用户Token
|
||||
var apiBaseUrl = MainWindow.Settings?.Dlass?.ApiBaseUrl ?? "https://dlass.tech";
|
||||
var userToken = MainWindow.Settings?.Dlass?.UserToken;
|
||||
|
||||
// 准备上传参数
|
||||
var fileName = Path.GetFileNameWithoutExtension(filePath);
|
||||
var fileExtension = Path.GetExtension(filePath).ToLower();
|
||||
var title = fileName;
|
||||
string fileType;
|
||||
string tags;
|
||||
if (fileExtension == ".zip")
|
||||
{
|
||||
fileType = "多页面墨迹压缩包";
|
||||
tags = "自动上传,多页面,zip,压缩包";
|
||||
}
|
||||
else if (fileExtension == ".icstk")
|
||||
{
|
||||
fileType = "墨迹文件";
|
||||
tags = "自动上传,墨迹,icstk";
|
||||
}
|
||||
else if (fileExtension == ".xml")
|
||||
{
|
||||
fileType = "XML文件";
|
||||
tags = "自动上传,xml";
|
||||
}
|
||||
else
|
||||
{
|
||||
fileType = "笔记";
|
||||
tags = "自动上传,笔记,png";
|
||||
}
|
||||
var description = $"自动上传的{fileType} - {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
|
||||
|
||||
// 创建API客户端并上传文件
|
||||
var apiClient = new DlassApiClient(APP_ID, APP_SECRET, apiBaseUrl, userToken);
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var uploadResult = await apiClient.UploadNoteAsync<UploadNoteResponse>(
|
||||
"/api/whiteboard/upload_note",
|
||||
filePath,
|
||||
whiteboard.BoardId,
|
||||
whiteboard.SecretKey,
|
||||
title,
|
||||
description,
|
||||
tags,
|
||||
cancellationToken);
|
||||
|
||||
if (uploadResult != null && uploadResult.Success)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
apiClient.Dispose();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取白板信息
|
||||
/// </summary>
|
||||
private async Task<WhiteboardInfo> GetWhiteboardInfo(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var selectedClassName = MainWindow.Settings?.Dlass?.SelectedClassName;
|
||||
if (string.IsNullOrEmpty(selectedClassName))
|
||||
{
|
||||
LogHelper.WriteLogToFile("[DlassUploadQueue] 上传失败:未选择班级", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
var userToken = MainWindow.Settings?.Dlass?.UserToken;
|
||||
if (string.IsNullOrEmpty(userToken))
|
||||
{
|
||||
LogHelper.WriteLogToFile("[DlassUploadQueue] 上传失败:未设置用户Token", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
var apiBaseUrl = MainWindow.Settings?.Dlass?.ApiBaseUrl ?? "https://dlass.tech";
|
||||
|
||||
// 创建API客户端并获取白板信息
|
||||
var apiClient = new DlassApiClient(APP_ID, APP_SECRET, apiBaseUrl, userToken);
|
||||
try
|
||||
{
|
||||
var authData = new
|
||||
{
|
||||
app_id = APP_ID,
|
||||
app_secret = APP_SECRET,
|
||||
user_token = userToken
|
||||
};
|
||||
|
||||
var authResult = await apiClient.PostAsync<AuthWithTokenResponse>("/api/whiteboard/framework/auth-with-token", authData, requireAuth: false, cancellationToken: cancellationToken);
|
||||
|
||||
if (authResult == null || !authResult.Success || authResult.Whiteboards == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("[DlassUploadQueue] 上传失败:无法获取白板信息", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 查找匹配班级的白板
|
||||
var whiteboard = authResult.Whiteboards
|
||||
.FirstOrDefault(w => !string.IsNullOrEmpty(w.ClassName) && w.ClassName == selectedClassName);
|
||||
|
||||
if (whiteboard == null || string.IsNullOrEmpty(whiteboard.BoardId) || string.IsNullOrEmpty(whiteboard.SecretKey))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[DlassUploadQueue] 上传失败:未找到班级'{selectedClassName}'对应的白板", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return whiteboard;
|
||||
}
|
||||
finally
|
||||
{
|
||||
apiClient.Dispose();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
@@ -70,7 +71,7 @@ namespace Ink_Canvas.Helpers
|
||||
[FieldOffset(8)]
|
||||
private DateTime date;
|
||||
[FieldOffset(8)]
|
||||
private System.Runtime.InteropServices.ComTypes.FILETIME filetime;
|
||||
private FILETIME filetime;
|
||||
|
||||
[FieldOffset(8)]
|
||||
private Blob blobVal;
|
||||
@@ -115,7 +116,7 @@ namespace Ink_Canvas.Helpers
|
||||
case VarEnum.VT_BLOB:
|
||||
return GetBlob();
|
||||
}
|
||||
throw new NotImplementedException("PropVariant " + ve.ToString());
|
||||
throw new NotImplementedException("PropVariant " + ve);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,17 +145,17 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
#region "Interfaces"
|
||||
|
||||
[ComImport(), Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[ComImport, Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IPropertyStore
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetCount([Out(), In()] ref uint cProps);
|
||||
void GetCount([Out, In] ref uint cProps);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetAt([In()] uint iProp, ref PropertyKey pkey);
|
||||
void GetAt([In] uint iProp, ref PropertyKey pkey);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetValue([In()] ref PropertyKey key, ref PropVariant pv);
|
||||
void GetValue([In] ref PropertyKey key, ref PropVariant pv);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void SetValue([In()] ref PropertyKey key, [In()] ref PropVariant pv);
|
||||
void SetValue([In] ref PropertyKey key, [In] ref PropVariant pv);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void Commit();
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public static class ExternalCallerLauncher
|
||||
{
|
||||
private static readonly string[] ClassIslandProtocols =
|
||||
{
|
||||
"classisland://plugins/IslandCaller/Simple/1",
|
||||
"classisland://plugins/IslandCaller/Simple",
|
||||
"classisland://plugins/IslandCaller/Run"
|
||||
};
|
||||
|
||||
public static string[] GetProtocolsByType(int externalCallerType)
|
||||
{
|
||||
switch (externalCallerType)
|
||||
{
|
||||
case 0:
|
||||
return ClassIslandProtocols;
|
||||
case 1:
|
||||
return new[]
|
||||
{
|
||||
"secrandom://roll_call/quick_draw",
|
||||
"secrandom://direct_extraction"
|
||||
};
|
||||
case 2:
|
||||
return new[] { "namepicker://" };
|
||||
default:
|
||||
return ClassIslandProtocols;
|
||||
}
|
||||
}
|
||||
|
||||
public static string[] GetProtocolsByName(string externalCallerName)
|
||||
{
|
||||
switch (externalCallerName)
|
||||
{
|
||||
case "ClassIsland":
|
||||
return ClassIslandProtocols;
|
||||
case "SecRandom":
|
||||
return new[]
|
||||
{
|
||||
"secrandom://roll_call/quick_draw",
|
||||
"secrandom://direct_extraction"
|
||||
};
|
||||
case "NamePicker":
|
||||
return new[] { "namepicker://" };
|
||||
default:
|
||||
return ClassIslandProtocols;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryLaunch(IEnumerable<string> protocols, out Exception lastException)
|
||||
{
|
||||
lastException = null;
|
||||
if (protocols == null) return false;
|
||||
|
||||
foreach (var protocol in protocols)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(protocol)) continue;
|
||||
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = protocol,
|
||||
UseShellExecute = true
|
||||
});
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lastException = ex;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,691 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件关联管理器,用于注册和处理.icstk文件的关联
|
||||
/// </summary>
|
||||
public static class FileAssociationManager
|
||||
{
|
||||
private const string FileExtension = ".icstk";
|
||||
private const string FileTypeName = "InkCanvasStrokesFile";
|
||||
private const string AppName = "Ink Canvas";
|
||||
private const string AppDescription = "Ink Canvas Strokes File";
|
||||
|
||||
// IPC相关常量
|
||||
private const string IpcMutexName = "InkCanvasFileAssociationIpc";
|
||||
private const string IpcEventName = "InkCanvasFileAssociationEvent";
|
||||
private const string IpcFilePrefix = "InkCanvasFileAssociation_";
|
||||
private const string IpcBoardModePrefix = "InkCanvasBoardMode_";
|
||||
private const string IpcShowModePrefix = "InkCanvasShowMode_";
|
||||
private const string IpcUriCommandPrefix = "InkCanvasUriCommand_";
|
||||
private const int IpcTimeout = 5000; // 5秒超时
|
||||
|
||||
/// <summary>
|
||||
/// 注册.icstk文件关联
|
||||
/// </summary>
|
||||
public static bool RegisterFileAssociation()
|
||||
{
|
||||
try
|
||||
{
|
||||
string exePath = Process.GetCurrentProcess().MainModule.FileName;
|
||||
|
||||
// 注册文件类型
|
||||
using (RegistryKey fileTypeKey = Registry.ClassesRoot.CreateSubKey(FileTypeName))
|
||||
{
|
||||
fileTypeKey.SetValue("", AppDescription);
|
||||
fileTypeKey.SetValue("FriendlyTypeName", AppDescription);
|
||||
|
||||
// 设置默认图标
|
||||
using (RegistryKey defaultIconKey = fileTypeKey.CreateSubKey("DefaultIcon"))
|
||||
{
|
||||
defaultIconKey.SetValue("", $"\"{exePath}\",0");
|
||||
}
|
||||
|
||||
// 设置打开命令
|
||||
using (RegistryKey shellKey = fileTypeKey.CreateSubKey("shell"))
|
||||
using (RegistryKey openKey = shellKey.CreateSubKey("open"))
|
||||
using (RegistryKey commandKey = openKey.CreateSubKey("command"))
|
||||
{
|
||||
commandKey.SetValue("", $"\"{exePath}\" \"%1\"");
|
||||
}
|
||||
}
|
||||
|
||||
// 注册文件扩展名
|
||||
using (RegistryKey extensionKey = Registry.ClassesRoot.CreateSubKey(FileExtension))
|
||||
{
|
||||
extensionKey.SetValue("", FileTypeName);
|
||||
}
|
||||
|
||||
// 刷新系统文件关联缓存
|
||||
RefreshSystemFileAssociations();
|
||||
|
||||
LogHelper.WriteLogToFile($"成功注册{FileExtension}文件关联", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
catch (SecurityException ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注册文件关联时权限不足: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注册文件关联时访问被拒绝: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注册文件关联时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销.icstk文件关联
|
||||
/// </summary>
|
||||
public static bool UnregisterFileAssociation()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 删除文件扩展名关联
|
||||
Registry.ClassesRoot.DeleteSubKeyTree(FileExtension, false);
|
||||
|
||||
// 删除文件类型定义
|
||||
Registry.ClassesRoot.DeleteSubKeyTree(FileTypeName, false);
|
||||
|
||||
// 刷新系统文件关联缓存
|
||||
RefreshSystemFileAssociations();
|
||||
|
||||
LogHelper.WriteLogToFile($"成功注销{FileExtension}文件关联", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注销文件关联时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查文件关联是否已注册
|
||||
/// </summary>
|
||||
public static bool IsFileAssociationRegistered()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (RegistryKey extensionKey = Registry.ClassesRoot.OpenSubKey(FileExtension))
|
||||
{
|
||||
if (extensionKey == null) return false;
|
||||
|
||||
string fileType = extensionKey.GetValue("") as string;
|
||||
if (string.IsNullOrEmpty(fileType)) return false;
|
||||
|
||||
using (RegistryKey fileTypeKey = Registry.ClassesRoot.OpenSubKey(fileType))
|
||||
{
|
||||
if (fileTypeKey == null) return false;
|
||||
|
||||
using (RegistryKey shellKey = fileTypeKey.OpenSubKey("shell\\open\\command"))
|
||||
{
|
||||
if (shellKey == null) return false;
|
||||
|
||||
string command = shellKey.GetValue("") as string;
|
||||
if (string.IsNullOrEmpty(command)) return false;
|
||||
|
||||
// 检查命令是否指向当前应用程序
|
||||
string currentExePath = Process.GetCurrentProcess().MainModule.FileName;
|
||||
return command.Contains(currentExePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"检查文件关联状态时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示文件关联状态
|
||||
/// </summary>
|
||||
public static void ShowFileAssociationStatus()
|
||||
{
|
||||
bool isRegistered = IsFileAssociationRegistered();
|
||||
LogHelper.WriteLogToFile($"{FileExtension}文件关联状态: {(isRegistered ? "已注册" : "未注册")}", LogHelper.LogType.Event);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新系统文件关联缓存
|
||||
/// </summary>
|
||||
private static void RefreshSystemFileAssociations()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 通知系统文件关联已更改
|
||||
SHChangeNotify(0x08000000, 0, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"刷新文件关联缓存时出错: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理命令行参数中的文件路径
|
||||
/// </summary>
|
||||
/// <param name="args">命令行参数</param>
|
||||
/// <returns>找到的.icstk文件路径,如果没有找到则返回null</returns>
|
||||
public static string GetIcstkFileFromArgs(string[] args)
|
||||
{
|
||||
if (args == null || args.Length == 0) return null;
|
||||
|
||||
foreach (string arg in args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(arg)) continue;
|
||||
|
||||
// 检查是否为.icstk文件
|
||||
if (Path.GetExtension(arg).Equals(FileExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// 检查文件是否存在
|
||||
if (File.Exists(arg))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从命令行参数中找到.icstk文件: {arg}", LogHelper.LogType.Event);
|
||||
return arg;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"命令行参数中的.icstk文件不存在: {arg}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试通过IPC将文件路径发送给已运行的实例
|
||||
/// </summary>
|
||||
/// <param name="filePath">要打开的文件路径</param>
|
||||
/// <returns>是否成功发送</returns>
|
||||
public static bool TrySendFileToExistingInstance(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile($"尝试通过IPC发送文件路径给已运行实例: {filePath}", LogHelper.LogType.Event);
|
||||
|
||||
// 创建IPC文件
|
||||
string tempDir = Path.GetTempPath();
|
||||
string ipcFileName = IpcFilePrefix + Guid.NewGuid().ToString("N") + ".tmp";
|
||||
string ipcFilePath = Path.Combine(tempDir, ipcFileName);
|
||||
|
||||
// 写入文件路径到IPC文件
|
||||
File.WriteAllText(ipcFilePath, filePath, Encoding.UTF8);
|
||||
|
||||
// 创建事件通知已运行实例
|
||||
using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
|
||||
{
|
||||
ipcEvent.Set();
|
||||
}
|
||||
|
||||
// 等待一段时间让已运行实例处理文件
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// 清理IPC文件
|
||||
try
|
||||
{
|
||||
if (File.Exists(ipcFilePath))
|
||||
{
|
||||
File.Delete(ipcFilePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile("IPC文件路径发送完成", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"通过IPC发送文件路径失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试通过IPC将白板模式命令发送给已运行的实例
|
||||
/// </summary>
|
||||
/// <returns>是否成功发送</returns>
|
||||
public static bool TrySendBoardModeCommandToExistingInstance()
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("尝试通过IPC发送白板模式命令给已运行实例", LogHelper.LogType.Event);
|
||||
|
||||
// 创建IPC文件
|
||||
string tempDir = Path.GetTempPath();
|
||||
string ipcFileName = IpcBoardModePrefix + Guid.NewGuid().ToString("N") + ".tmp";
|
||||
string ipcFilePath = Path.Combine(tempDir, ipcFileName);
|
||||
|
||||
// 写入白板模式命令到IPC文件
|
||||
File.WriteAllText(ipcFilePath, "BOARD_MODE", Encoding.UTF8);
|
||||
|
||||
// 创建事件通知已运行实例
|
||||
using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
|
||||
{
|
||||
ipcEvent.Set();
|
||||
}
|
||||
|
||||
// 等待一段时间让已运行实例处理命令
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// 清理IPC文件
|
||||
try
|
||||
{
|
||||
if (File.Exists(ipcFilePath))
|
||||
{
|
||||
File.Delete(ipcFilePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile("IPC白板模式命令发送完成", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"通过IPC发送白板模式命令失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试通过IPC将展开浮动栏命令发送给已运行的实例
|
||||
/// </summary>
|
||||
/// <returns>是否成功发送</returns>
|
||||
public static bool TrySendShowModeCommandToExistingInstance()
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("尝试通过IPC发送展开浮动栏命令给已运行实例", LogHelper.LogType.Event);
|
||||
|
||||
// 创建IPC文件
|
||||
string tempDir = Path.GetTempPath();
|
||||
string ipcFileName = IpcShowModePrefix + Guid.NewGuid().ToString("N") + ".tmp";
|
||||
string ipcFilePath = Path.Combine(tempDir, ipcFileName);
|
||||
|
||||
// 写入展开浮动栏命令到IPC文件
|
||||
File.WriteAllText(ipcFilePath, "SHOW_MODE", Encoding.UTF8);
|
||||
|
||||
// 创建事件通知已运行实例
|
||||
using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
|
||||
{
|
||||
ipcEvent.Set();
|
||||
}
|
||||
|
||||
// 等待一段时间让已运行实例处理命令
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// 清理IPC文件
|
||||
try
|
||||
{
|
||||
if (File.Exists(ipcFilePath))
|
||||
{
|
||||
File.Delete(ipcFilePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile("IPC展开浮动栏命令发送完成", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"通过IPC发送展开浮动栏命令失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试通过IPC将URI命令发送给已运行的实例
|
||||
/// </summary>
|
||||
/// <param name="uri">URI命令</param>
|
||||
/// <returns>是否成功发送</returns>
|
||||
public static bool TrySendUriCommandToExistingInstance(string uri)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile($"尝试通过IPC发送URI命令给已运行实例: {uri}", LogHelper.LogType.Event);
|
||||
|
||||
// 创建IPC文件
|
||||
string tempDir = Path.GetTempPath();
|
||||
string ipcFileName = IpcUriCommandPrefix + Guid.NewGuid().ToString("N") + ".tmp";
|
||||
string ipcFilePath = Path.Combine(tempDir, ipcFileName);
|
||||
|
||||
// 写入URI命令到IPC文件
|
||||
File.WriteAllText(ipcFilePath, uri, Encoding.UTF8);
|
||||
|
||||
// 创建事件通知已运行实例
|
||||
using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
|
||||
{
|
||||
ipcEvent.Set();
|
||||
}
|
||||
|
||||
// 等待一段时间让已运行实例处理命令
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// 清理IPC文件
|
||||
try
|
||||
{
|
||||
if (File.Exists(ipcFilePath))
|
||||
{
|
||||
File.Delete(ipcFilePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile("IPC URI命令发送完成", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"通过IPC发送URI命令失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动IPC监听器,等待其他实例发送文件路径
|
||||
/// </summary>
|
||||
public static void StartIpcListener()
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread ipcThread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("启动IPC监听器", LogHelper.LogType.Event);
|
||||
|
||||
using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// 等待IPC事件
|
||||
if (ipcEvent.WaitOne(IpcTimeout))
|
||||
{
|
||||
// 处理IPC文件
|
||||
ProcessIpcFiles();
|
||||
|
||||
// 重置事件
|
||||
ipcEvent.Reset();
|
||||
}
|
||||
|
||||
// 检查应用是否还在运行
|
||||
if (Application.Current == null || Application.Current.Dispatcher == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"IPC监听器出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcThread.IsBackground = true;
|
||||
ipcThread.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动IPC监听器失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理IPC文件
|
||||
/// </summary>
|
||||
private static void ProcessIpcFiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
string tempDir = Path.GetTempPath();
|
||||
|
||||
// 处理文件路径IPC文件
|
||||
string[] ipcFiles = Directory.GetFiles(tempDir, IpcFilePrefix + "*.tmp");
|
||||
foreach (string ipcFile in ipcFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 读取文件路径
|
||||
string filePath = File.ReadAllText(ipcFile, Encoding.UTF8);
|
||||
|
||||
if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"IPC接收到文件路径: {filePath}", LogHelper.LogType.Event);
|
||||
|
||||
// 在UI线程中处理文件打开
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取主窗口并打开文件
|
||||
if (Application.Current.MainWindow is MainWindow mainWindow)
|
||||
{
|
||||
mainWindow.OpenSingleStrokeFile(filePath);
|
||||
mainWindow.ShowNotification($"已加载墨迹文件: {Path.GetFileName(filePath)}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"IPC处理文件打开失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// 删除IPC文件
|
||||
File.Delete(ipcFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
|
||||
// 尝试删除损坏的IPC文件
|
||||
try
|
||||
{
|
||||
if (File.Exists(ipcFile))
|
||||
{
|
||||
File.Delete(ipcFile);
|
||||
}
|
||||
}
|
||||
catch (Exception innerEx) { System.Diagnostics.Debug.WriteLine(innerEx); }
|
||||
}
|
||||
}
|
||||
|
||||
// 处理白板模式命令IPC文件
|
||||
string[] boardModeFiles = Directory.GetFiles(tempDir, IpcBoardModePrefix + "*.tmp");
|
||||
foreach (string ipcFile in boardModeFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 读取命令内容
|
||||
string command = File.ReadAllText(ipcFile, Encoding.UTF8);
|
||||
|
||||
if (command == "BOARD_MODE")
|
||||
{
|
||||
LogHelper.WriteLogToFile("IPC接收到白板模式命令", LogHelper.LogType.Event);
|
||||
|
||||
// 在UI线程中处理白板模式切换
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取主窗口并切换到白板模式
|
||||
if (Application.Current.MainWindow is MainWindow mainWindow)
|
||||
{
|
||||
mainWindow.SwitchToBoardMode();
|
||||
mainWindow.ShowNotification("已切换到白板模式");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"IPC处理白板模式切换失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// 删除IPC文件
|
||||
File.Delete(ipcFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理白板模式IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
|
||||
// 尝试删除损坏的IPC文件
|
||||
try
|
||||
{
|
||||
if (File.Exists(ipcFile))
|
||||
{
|
||||
File.Delete(ipcFile);
|
||||
}
|
||||
}
|
||||
catch (Exception innerEx) { System.Diagnostics.Debug.WriteLine(innerEx); }
|
||||
}
|
||||
}
|
||||
|
||||
// 处理展开浮动栏命令IPC文件
|
||||
string[] showModeFiles = Directory.GetFiles(tempDir, IpcShowModePrefix + "*.tmp");
|
||||
foreach (string ipcFile in showModeFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 读取命令内容
|
||||
string command = File.ReadAllText(ipcFile, Encoding.UTF8);
|
||||
|
||||
if (command == "SHOW_MODE")
|
||||
{
|
||||
LogHelper.WriteLogToFile("IPC接收到展开浮动栏命令", LogHelper.LogType.Event);
|
||||
|
||||
// 在UI线程中处理展开浮动栏
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取主窗口并展开浮动栏
|
||||
if (Application.Current.MainWindow is MainWindow mainWindow)
|
||||
{
|
||||
// 如果当前处于收纳模式,则展开浮动栏
|
||||
if (mainWindow.isFloatingBarFolded)
|
||||
{
|
||||
await mainWindow.UnFoldFloatingBar(new object());
|
||||
}
|
||||
mainWindow.ShowNotification("已退出收纳模式并恢复浮动栏");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"IPC处理展开浮动栏失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// 删除IPC文件
|
||||
File.Delete(ipcFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理展开浮动栏IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
|
||||
// 尝试删除损坏的IPC文件
|
||||
try
|
||||
{
|
||||
if (File.Exists(ipcFile))
|
||||
{
|
||||
File.Delete(ipcFile);
|
||||
}
|
||||
}
|
||||
catch (Exception innerEx) { System.Diagnostics.Debug.WriteLine(innerEx); }
|
||||
}
|
||||
}
|
||||
|
||||
// 处理URI命令IPC文件
|
||||
string[] uriCommandFiles = Directory.GetFiles(tempDir, IpcUriCommandPrefix + "*.tmp");
|
||||
foreach (string ipcFile in uriCommandFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 读取命令内容
|
||||
string uri = File.ReadAllText(ipcFile, Encoding.UTF8);
|
||||
|
||||
if (!string.IsNullOrEmpty(uri))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"IPC接收到URI命令: {uri}", LogHelper.LogType.Event);
|
||||
|
||||
// 在UI线程中处理URI命令
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取主窗口并处理URI命令
|
||||
if (Application.Current.MainWindow is MainWindow mainWindow)
|
||||
{
|
||||
mainWindow.HandleUriCommand(uri);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"IPC处理URI命令失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// 删除IPC文件
|
||||
File.Delete(ipcFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理URI命令IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
|
||||
// 尝试删除损坏的IPC文件
|
||||
try
|
||||
{
|
||||
if (File.Exists(ipcFile))
|
||||
{
|
||||
File.Delete(ipcFile);
|
||||
}
|
||||
}
|
||||
catch (Exception innerEx) { System.Diagnostics.Debug.WriteLine(innerEx); }
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理IPC文件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("shell32.dll")]
|
||||
private static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user