mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-28 09:50:09 +00:00
Compare commits
3108 Commits
gen4-32123
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ee57bba4af | |||
| 498796e311 | |||
| 27ffd2a243 | |||
| a39f9530a0 | |||
| a7013814e5 | |||
| b7b955660a | |||
| 55fc1e743a | |||
| af61bd1ed2 | |||
| 55dd723e97 | |||
| 42b4b6c680 | |||
| b035aebec1 | |||
| 6edb15e1d2 | |||
| 3fe668ea1d | |||
| fd52cba3d5 | |||
| 2d5d81b9d6 | |||
| a503e9d45f | |||
| 9b8f0ec4c6 | |||
| 7327ffa19a | |||
| ce87a1a48e | |||
| bf3faba50c | |||
| b2c51bda01 | |||
| 39cb6d1a9c | |||
| d1a2c902bf | |||
| 5af64caa08 | |||
| aeca88b1c5 | |||
| 910a5eb929 | |||
| 8442a9ef4f | |||
| 66b75a9eae | |||
| cfe2ca04d8 | |||
| 17529b543b | |||
| 9066a9d11c | |||
| 24a7c2ecf8 | |||
| 96d544e314 | |||
| 5f58815eba | |||
| 0bc22d62a5 | |||
| faa2400a80 | |||
| 70a91ec0b6 | |||
| 875478746e | |||
| ca7d393f2c | |||
| a0a8de4864 | |||
| 40943b1015 | |||
| 1ba32c0ecd | |||
| 02cd936cb4 | |||
| 9c7f66093b | |||
| a95bc1499a | |||
| b92064ce95 | |||
| 8ec1541253 | |||
| 171f09c750 | |||
| a81de15687 | |||
| f3e7007d5c | |||
| 0d9cb95817 | |||
| e6d0c5dddd | |||
| bb0bb2f8fd | |||
| d802c93ad5 | |||
| e49327e1b8 | |||
| a17fe070b7 | |||
| b5ff27b7d3 | |||
| 01d746e30f | |||
| 7d51f55a65 | |||
| e910f45ccf | |||
| e826b5881a | |||
| 6abcecb10b | |||
| 8b643b0b36 | |||
| 28f455a732 | |||
| a832589dcf | |||
| 3f841c109a | |||
| 83623ac722 | |||
| 26ce1512b2 | |||
| a34762510a | |||
| b9100e5798 | |||
| bc61774548 | |||
| 17ccaa7a1b | |||
| ae15cda923 | |||
| ef0a40ec33 | |||
| d6dc9fc4b6 | |||
| e698f3c6f5 | |||
| 06244ddbbe | |||
| 42ead16808 | |||
| f312eee12f | |||
| 69bc6ccc2b | |||
| 586e8758b4 | |||
| ecc7b929e9 | |||
| d6abf65df9 | |||
| 5e695db2a3 | |||
| 3d20c135c5 | |||
| e28367db0d | |||
| 9fe6678b95 | |||
| c928e4767a | |||
| f76f04118c | |||
| 3c836d2741 | |||
| 9144e371f9 | |||
| 90a62f8584 | |||
| f6f719c11e | |||
| 1b7d46781e | |||
| 1eac481159 | |||
| a07a69c0f5 | |||
| f93a44bd93 | |||
| 801777234f | |||
| 27aef3db40 | |||
| e1e4324f90 | |||
| bb3f3f2ea0 | |||
| 9425b3ccc1 | |||
| c138a278e2 | |||
| 1598873ff9 | |||
| 6abd1ed3e0 | |||
| ebb6d282f1 | |||
| 78891e7d22 | |||
| 02003b3b14 | |||
| d08da5c92d | |||
| d699e22970 | |||
| eb0b1c68d6 | |||
| a4dac81edd | |||
| 69b4bb195a | |||
| 5f867f97e5 | |||
| 7253cd5246 | |||
| 8e3be4cfc5 | |||
| 78a8b763ec | |||
| 5ab2115d5e | |||
| 17a419f3d2 | |||
| b3b1d4b6dc | |||
| 2c9c0b07ec | |||
| aaa2e21241 | |||
| 8bdf587622 | |||
| 0598eab77d | |||
| 513690dd3b | |||
| 01e3f6272d | |||
| ca8314ed39 | |||
| c167638271 | |||
| 786feb8def | |||
| 79e6fe4eb4 | |||
| 23509bacc1 | |||
| 77677ea1dd | |||
| 21e7bae205 | |||
| ebbc553576 | |||
| f857c6e456 | |||
| 97de482afa | |||
| c586c36877 | |||
| 3a332225a0 | |||
| 12a024dac2 | |||
| 9264d26598 | |||
| cfc97f1ea8 | |||
| 2c35c9077b | |||
| 530e288108 | |||
| e36b8200d1 | |||
| 4c76bb0ffa | |||
| 7046e88ad8 | |||
| 4dad475e8c | |||
| 17ca0c5519 | |||
| bc43cf7614 | |||
| aa306798fc | |||
| 943f8f3291 | |||
| a4f3e2b2d6 | |||
| 2e85c0c0be | |||
| 90c08f63eb | |||
| 4a0bf2a55b | |||
| b8f46586b5 | |||
| 94794f0743 | |||
| d50c266085 | |||
| bca082630d | |||
| 4c43a86375 | |||
| 1226908868 | |||
| 5652e6979d | |||
| 20fb42aa4c | |||
| 1748a26e61 | |||
| e1cc61010f | |||
| a2e6b44856 | |||
| 516ca931c4 | |||
| 33b120d430 | |||
| 5324401d50 | |||
| f65eb0c3c3 | |||
| a8a126793c | |||
| 403f1779c4 | |||
| 85592e297a | |||
| b7d355329f | |||
| f19894bf39 | |||
| 55c80011f5 | |||
| 623689f592 | |||
| a74d745dc1 | |||
| c5623e8bb4 | |||
| 3bb4e51d42 | |||
| 6aa2abed1a | |||
| 7b529d40c7 | |||
| 7b7f680e36 | |||
| ca3ca35b84 | |||
| 4fa4a48deb | |||
| 4c6a84b8fd | |||
| bf02d1a765 | |||
| 141e2e8295 | |||
| 14a0d3c3ba | |||
| 00e36efce2 | |||
| e3b634817f | |||
| f6af695418 | |||
| 8137c5eb59 | |||
| c2e239bb8a | |||
| 9d2698f06e | |||
| 1f7357ba41 | |||
| dde38b7c6d | |||
| 297f958e07 | |||
| 334cc66f6f | |||
| d9a4322115 | |||
| 8678d79424 | |||
| 71dc5687e3 | |||
| 285c0b8b94 | |||
| f4cf1d2ee7 | |||
| ea433bee78 | |||
| bc77ed039f | |||
| d85cbe95e6 | |||
| dcfd7d2561 | |||
| 12adbfbbdd | |||
| 214c8d62c0 | |||
| f2306bb23b | |||
| dee6bb7396 | |||
| 0646305311 | |||
| b09644716d | |||
| 3fd43123ba | |||
| 250869530f | |||
| 69bbc872c6 | |||
| cf048fb3cf | |||
| 246f00e5b1 | |||
| 14efd4204b | |||
| 71cb6fc9e7 | |||
| 70fad75e40 | |||
| 22f018a095 | |||
| 42e927ce71 | |||
| ada096caa5 | |||
| c26657df8b | |||
| 8326efd989 | |||
| 53970b2d91 | |||
| bd85e5f6c3 | |||
| 3ea3639cb6 | |||
| 6655907afd | |||
| cdd6689f22 | |||
| cb6b45182f | |||
| ad6a0f8090 | |||
| bcf3cf2741 | |||
| b5c5330857 | |||
| 2ec32b861e | |||
| dae13d71e0 | |||
| fe6bdf65e5 | |||
| feb62c9dd8 | |||
| af0206224a | |||
| a119f4c5e6 | |||
| 9a700da5f8 | |||
| 13be69fcdf | |||
| 087c5f35c6 | |||
| 3a30991d87 | |||
| e96aff481e | |||
| 3ef431eae4 | |||
| 9af38ed5a0 | |||
| 6b4dfe0320 | |||
| 04936a20e7 | |||
| 321a434554 | |||
| f74fb5893b | |||
| 542c5a04e3 | |||
| 7792cb31ed | |||
| 296885c1eb | |||
| 4c20dfe2e9 | |||
| ee6fb5f7be | |||
| 0ad024d8bb | |||
| 3ee97573c0 | |||
| aee341f035 | |||
| 21b143a06e | |||
| 2742ffdc9a | |||
| 86057761c7 | |||
| 2fd6ec969f | |||
| 4b21ab209b | |||
| af43329c66 | |||
| bd21d14226 | |||
| 8229616ad7 | |||
| 53fe308fe8 | |||
| 678aef7809 | |||
| 36e4debfaa | |||
| fc7724bbd9 | |||
| 3ebe8fdc55 | |||
| 6c5852af5c | |||
| 1a0c03ee03 | |||
| ba1d3a803f | |||
| 0bfea89db7 | |||
| 7062e5c39e | |||
| e0776e0106 | |||
| 0a79cf2176 | |||
| 2676f92536 | |||
| 0bae8a9aec | |||
| f7d6637627 | |||
| 4a2daa3c55 | |||
| 054f3c8120 | |||
| a9f5f76959 | |||
| a94b9e4981 | |||
| 1ce773fb93 | |||
| 369c00e1c1 | |||
| 2ca1c03824 | |||
| b21a7a719c | |||
| 571c2a2399 | |||
| 5d753eeff3 | |||
| 9975e6864d | |||
| 8cff0ed702 | |||
| b4fe7db680 | |||
| fbe7ff0288 | |||
| 11c91cf0e9 | |||
| 1a5c385946 | |||
| b106e4c82f | |||
| 16571ecfcb | |||
| 3926336526 | |||
| 4f29d88cd3 | |||
| ce46a9496d | |||
| 78b4f86f0b | |||
| 873545bc42 | |||
| 6a2dc412bc | |||
| edada77b92 | |||
| b9b3a37547 | |||
| f8a5a9daf1 | |||
| a76f38b1b3 | |||
| f1db4ac9eb | |||
| 71849ae9b3 | |||
| af109c13f8 | |||
| e7b0c67ef1 | |||
| 09b48e1d0b | |||
| d3b1af198a | |||
| c42db539a1 | |||
| 3a9a177bc3 | |||
| 3888a5079a | |||
| 8488a4f126 | |||
| 1222f14740 | |||
| 2e6f860de5 | |||
| eed4f4afdf | |||
| 03abe6c011 | |||
| 4c8ce37618 | |||
| 0b2196b1b7 | |||
| 17ca051367 | |||
| 3c2106b8e8 | |||
| 4cfd21c4a3 | |||
| bc25606027 | |||
| d911c7190e | |||
| 8f39d3bbc3 | |||
| a9f6e8f9a5 | |||
| 85551e07d0 | |||
| 32bdf0c33d | |||
| 1ce86fd88d | |||
| 00b2664fed | |||
| d3b513a417 | |||
| 06d60a9109 | |||
| b4eb5e3101 | |||
| 076685c3fa | |||
| 67e246e14a | |||
| f3f6c820db | |||
| 33d4b2ec12 | |||
| 7009f72a41 | |||
| a3ccb3a65a | |||
| b384d1f879 | |||
| 48551ba39a | |||
| de7c09f377 | |||
| 92a092ac73 | |||
| 73fb56a585 | |||
| 6232cc58ad | |||
| a1a9a4d481 | |||
| 6ff53e913d | |||
| 5bdc27986e | |||
| 7abece217e | |||
| e2d854058b | |||
| e8e59ce5cc | |||
| e3fb0ab5d1 | |||
| a335e1be96 | |||
| 6e56333d49 | |||
| eb5cb0f23d | |||
| 2fdd3ef57a | |||
| 85a382dfaf | |||
| f4b3b91028 | |||
| 2b78fd375c | |||
| c8c4a95015 | |||
| 6306a48f0e | |||
| 903cec53c0 | |||
| 25f895aaf0 | |||
| 1ef859573e | |||
| 20ded309fe | |||
| 8c62d1d222 | |||
| 5390001e5c | |||
| a1e24d84cb | |||
| a805b30e8a | |||
| eb589f4eb9 | |||
| 6f77e6116b | |||
| 0a85322798 | |||
| 2d7c1d20bc | |||
| 1c66014642 | |||
| b09a8ad194 | |||
| 207623b3b5 | |||
| df1c389333 | |||
| 817dc61c9b | |||
| c22641b39a | |||
| 620a77e9c1 | |||
| 5c672d02f5 | |||
| a8d722e07f | |||
| 0d268054d6 | |||
| 47abbf6300 | |||
| 25154c4363 | |||
| 45a620545d | |||
| 34376878a9 | |||
| cd3f502f31 | |||
| 270cfa364e | |||
| a26cbf1228 | |||
| 316ab5cc4b | |||
| bbf952d662 | |||
| cfe1882df5 | |||
| cd4a5105e8 | |||
| cebba0420a | |||
| 0b2edb2954 | |||
| c063fa56c4 | |||
| aed1e0206d | |||
| 06298e0641 | |||
| c841aa65f3 | |||
| ee0453c3d7 | |||
| bd73391161 | |||
| 2a6514801f | |||
| 88d0f6d237 | |||
| 664361cf29 | |||
| 0c21c587b8 | |||
| 6d7d6b5cd3 | |||
| 85950eeb13 | |||
| f03a73bcec | |||
| b1230735eb | |||
| 16b254b172 | |||
| 928c325d2a | |||
| 993efe9e23 | |||
| de5fbc29c3 | |||
| 30e2af325c | |||
| be6ab4dfca | |||
| 3b4aefffd1 | |||
| a8bb3e3d76 | |||
| 3e3cd7665f | |||
| 83404e9714 | |||
| 90d0fe6b31 | |||
| 952fc89425 | |||
| d97c85e334 | |||
| 5186c8890f | |||
| 2ea570ec7a | |||
| c25e160510 | |||
| e5ef898989 | |||
| fdbb3f6e24 | |||
| 8c0b00452d | |||
| fcf10a8724 | |||
| a20759e2f0 | |||
| c3fc43453e | |||
| f51cfed35c | |||
| efb0c247ae | |||
| 2680a6160a | |||
| 10df416043 | |||
| 8f7e18c945 | |||
| 98d87b612c | |||
| 5955743c77 | |||
| 6be15ca2ff | |||
| 26650d63fe | |||
| 6d678abd87 | |||
| 8e11308ed6 | |||
| 0e0fbcb7e3 | |||
| 22c3fed074 | |||
| fc670efc14 | |||
| 3f1ce3a9e7 | |||
| fb3edf92e8 | |||
| 55c119c98b | |||
| 833d03c977 | |||
| 6a8205c32e | |||
| c2dcdc5420 | |||
| 1d553ec2ef | |||
| 16d4d26870 | |||
| b7b4bc7aef | |||
| 96e234cbfe | |||
| 6235e85f4f | |||
| f7db0fc878 | |||
| 883a5f2c29 | |||
| c4ee08d410 | |||
| 58912ebcf5 | |||
| a90b1a23a9 | |||
| c79e0575d2 | |||
| 532d40ebfd | |||
| 835fc0492d | |||
| 3c6eddb6f4 | |||
| ba2a4656b2 | |||
| f16d352237 | |||
| ed52f7dc33 | |||
| cd2dabac38 | |||
| a6dac37f01 | |||
| ee76e4eece | |||
| 0bb281c7dd | |||
| 492f98b78d | |||
| 1096395509 | |||
| 41415238e0 | |||
| d6ade16950 | |||
| 8820613fda | |||
| b03efce6b0 | |||
| 98a3669d83 | |||
| 21e1cf95df | |||
| d64bb75342 | |||
| 7fbcecc883 | |||
| f099aa68e0 | |||
| d42cd430f9 | |||
| 17176f0aa3 | |||
| 9689707990 | |||
| 08d78a3978 | |||
| 1bcf4b5e93 | |||
| d5689162ff | |||
| acebbf51ff | |||
| e04e050f18 | |||
| d74486f95c | |||
| 91bf825bc3 | |||
| d2decf9eaa | |||
| f17280597e | |||
| 98bc9ecc24 | |||
| 4e1f9fbf56 | |||
| 643a6d0eb9 | |||
| 75c78bf72a | |||
| 5acd8d2afc | |||
| 9878b72a1e | |||
| 0019c77445 | |||
| b2ba27d82f | |||
| af3f85ae4c | |||
| b84c0229dd | |||
| c024867d80 | |||
| f05b8d74d2 | |||
| c750895a4a | |||
| 9c72528908 | |||
| 9d17474e3d | |||
| ecb9d83af8 | |||
| 1824eeddb2 | |||
| 00988feb36 | |||
| 701a26919f | |||
| 4f3476ec1a | |||
| cdbdf130de | |||
| f7d345b4ea | |||
| aaecdfa1e3 | |||
| 6659614212 | |||
| af6f9e535f | |||
| 6714b0037a | |||
| 111bd361f5 | |||
| b38712e574 | |||
| ec301c6fa4 | |||
| 6b88e8f596 | |||
| 8b71280823 | |||
| e020cc64e1 | |||
| 0f10178948 | |||
| 7db2efd948 | |||
| afef0a36d4 | |||
| fbadbfe55e | |||
| fb7e4ac063 | |||
| 04420b7a3b | |||
| 8e139a80e1 | |||
| 040f3a7e3a | |||
| dafeedbaaf | |||
| d2a5e3c15e | |||
| d19b678b37 | |||
| 2a5e490117 | |||
| 5406c071a9 | |||
| e4ad465eb0 | |||
| f0b954ffff | |||
| 7750620935 | |||
| 9f4b877e37 | |||
| 10c966f387 | |||
| 0b4395bbb9 | |||
| 5a60a2810e | |||
| 8a02178a24 | |||
| 00a72832b0 | |||
| 1f941854d6 | |||
| f9930bf6b9 | |||
| 4414bd0e7a | |||
| 007e29dda3 | |||
| c9eb5b9bd1 | |||
| 610f71f452 | |||
| f850d2df18 | |||
| 7122c619f4 | |||
| 71b9b444cb | |||
| 2a75ed6530 | |||
| fa8b6d57cd | |||
| 5ed2c02032 | |||
| dfa9693c58 | |||
| dc1bd9a6f8 | |||
| 18846406ed | |||
| bc96a4df59 | |||
| 4a3b5f5f0b | |||
| 1eebfe3020 | |||
| d77c01e63e | |||
| 026ff53ab2 | |||
| 92aa68fec8 | |||
| 66e87ee33a | |||
| be9bb9126b | |||
| 84809a0e24 | |||
| 7f086b289b | |||
| d59b62c39f | |||
| fd0edaf437 | |||
| e9a3192335 | |||
| d3e54c83ac | |||
| 8bb6bd8686 | |||
| a43422566e | |||
| 1303d70774 | |||
| e8f5399fa1 | |||
| d7b77a0492 | |||
| 0eb201fb06 | |||
| 95e0e9c77a | |||
| d7d6857dae | |||
| e73d2eb65e | |||
| 2830e68d8e | |||
| bc7ee310c5 | |||
| 0a3140f888 | |||
| 9831b863ea | |||
| d5771925c4 | |||
| baa1b06dce | |||
| da63da2f2c | |||
| 4cd1da4060 | |||
| 06fa563d04 | |||
| 409e82cb26 | |||
| 49245ba9d1 | |||
| f9398462c8 | |||
| e6243fdd3e | |||
| 933b763944 | |||
| 3249dcbf77 | |||
| c4c7c78019 | |||
| e620063915 | |||
| 32f9d5450e | |||
| 65ddabb7d9 | |||
| 1849e63b63 | |||
| d4ca5ba455 | |||
| 5c73431299 | |||
| 669ee51297 | |||
| a34591065a | |||
| 43f0499843 | |||
| f93c3a31a2 | |||
| d6f2346462 | |||
| 0b583e5e1d | |||
| 34236bf334 | |||
| cc1c81747a | |||
| a948275934 | |||
| f14cb36aa0 | |||
| 67e414358c | |||
| c05970bde5 | |||
| 34395a9145 | |||
| 32bc16c9da | |||
| 1c200f0129 | |||
| 67bbf58ec2 | |||
| 48753d7c7b | |||
| 5ce56e02c3 | |||
| 375cde19c0 | |||
| 40e22c54df | |||
| 1853b36540 | |||
| 6dc70ea7c9 | |||
| f434e5af5f | |||
| dc2d302406 | |||
| 0c5c58a09d | |||
| 9f5d1d5539 | |||
| ca34894f22 | |||
| 2629c25406 | |||
| b23e9777b7 | |||
| 7e2714198e | |||
| 7846f31c59 | |||
| 18b09f05c0 | |||
| f75a392e39 | |||
| a575b45784 | |||
| df2316e54b | |||
| 1f1a1cae7e | |||
| 2f7348e3a3 | |||
| c584ec1c46 | |||
| cffc66e191 | |||
| 8b5a3aee83 | |||
| 23a1b3818d | |||
| c32144bee7 | |||
| 078c977ad3 | |||
| 532702ee80 | |||
| 1126cf979b | |||
| 35fff006fc | |||
| 642d9bf29b | |||
| 5ef2cd18af | |||
| ffe8c86a12 | |||
| 90f00b5a83 | |||
| 3963da3630 | |||
| 810a1f7110 | |||
| 280da1469a | |||
| 29c23355eb | |||
| 6db5d0c4f8 | |||
| 505a228a03 | |||
| dab43d0aa0 | |||
| 0c66d67597 | |||
| 3d67cc740a | |||
| 74483c0b70 | |||
| 16e4cb681b | |||
| 8658298936 | |||
| 96293b0b20 | |||
| f2cbd7f00f | |||
| 1c99626fec | |||
| 6b5813e45e | |||
| a97ac88bed | |||
| ea20634cd7 | |||
| 69f2d8942c | |||
| 98a46abf87 | |||
| 2a95e75a55 | |||
| cd0ff92fa8 | |||
| 8bb284332d | |||
| e17b6363ce | |||
| f3255921fd | |||
| bdef19ccf8 | |||
| 47434442e3 | |||
| dbf4b60d52 | |||
| 447bd2dfc9 | |||
| 5f47d6da7b | |||
| cf0d6cf6b4 | |||
| 240502edd2 | |||
| 7f6d8a89cc | |||
| afe1fc9505 | |||
| 68dd2b2c67 | |||
| 3dbe0430e3 | |||
| 323e1a9277 | |||
| 3a0b8609ad | |||
|
|
d15a3ef824 | ||
| 354b130fc0 | |||
| c93d069006 | |||
| 096f13f30d | |||
| 540de10ea0 | |||
| 170cfc7242 | |||
| 47a56706bc | |||
| 0f23ba8a15 | |||
| 8efdcbd3eb | |||
| 4f08c68179 | |||
| 6b8f9f7c4f | |||
| e115fb2f99 | |||
| 155419a544 | |||
| cd08827c8c | |||
| 595db957d3 | |||
| 0918cb7371 | |||
| e0a4508f22 | |||
| f2fb2c481d | |||
| 329a8b4fd5 | |||
| 5c804ff64d | |||
| 1d649283cf | |||
| 65eaddb321 | |||
| 10435aa078 | |||
| ee46e4b0da | |||
| 57ca7f769b | |||
| 672bd773a6 | |||
| b810a47fbb | |||
| 8e9aa684ab | |||
| b21cf47b23 | |||
| 8b98cacfd3 | |||
| ba775c15ea | |||
| f249c0cf01 | |||
| 94dc2c24d5 | |||
| 41191e7db8 | |||
| 131e1ebd92 | |||
| 78cb4bdff8 | |||
| d912ce2ebe | |||
| 631a612541 | |||
| 511422f8d9 | |||
| b52c3c2c0b | |||
| ed61746a64 | |||
| 8149fcf41a | |||
| 1427a327eb | |||
| f0a6b5cb8f | |||
| 1fdcef47cb | |||
| 3321eccb87 | |||
| 9b138999e5 | |||
| 790f0b5b95 | |||
| 55b9e8d811 | |||
| b0f52c82f1 | |||
| 9f4a378cc3 | |||
| e1aecdfcf4 | |||
| 44a143c130 | |||
| 5568f07ee9 | |||
| a64f9d63e4 | |||
| 608a6303fb | |||
| 2db4d84607 | |||
| 064eed381d | |||
| 0c3ad4efb2 | |||
| e90f5f9b64 | |||
| a283958a49 | |||
| 594f0cbd8e | |||
| c5aa63af3c | |||
| 559fedc965 | |||
| 0f3561a7e2 | |||
| 1fc380721f | |||
| 10a7df2a85 | |||
| 1a2864cbc7 | |||
| 2b8f60bd8f | |||
| 2211d2d23a | |||
| bcc1ace919 | |||
| 84907d9d3b | |||
| 6b31359dbd | |||
| a43c8cb23e | |||
| 241da95cc1 | |||
| fed5f32723 | |||
| ddfdb66206 | |||
| be163d3a12 | |||
| e1ccbb6e72 | |||
| d88cf94c6a | |||
| 8f7c14dd92 | |||
| 0e026c2f86 | |||
| e86a471e63 | |||
| accca62fb2 | |||
| 83a5362128 | |||
| 95e3894e4e | |||
| b0507242fb | |||
| 3ed3db026f | |||
| f067aa3222 | |||
| 718c746068 | |||
| 9f8564494c | |||
| 9b64aa0b33 | |||
| 471a20fbd0 | |||
| 086d023b22 | |||
| 5995d460df | |||
| 47e7e85582 | |||
| 7b1f8ac1cb | |||
| a3e8872f9f | |||
| f01d8195a9 | |||
| cc68b6dbd5 | |||
| 4af7ae94e9 | |||
| 7ce10df9c2 | |||
| 5fb9b0950c | |||
| 20bb124696 | |||
| 6a59eae3d7 | |||
| 6225f7ce70 | |||
| e6866f934b | |||
| a67da43f4e | |||
| a0bb0ba7f6 | |||
| 8c4561aa6b | |||
| b2c97162f2 | |||
| ffb7142cc3 | |||
| 047cead873 | |||
| c08f60756d | |||
| cc7350df11 | |||
| ef4c199f8e | |||
| 41097ce198 | |||
| cbed3a691b | |||
| 48be4f94e4 | |||
| 1fd2c7dbbe | |||
| a07b004425 | |||
| da208c6946 | |||
| 4fc06455e4 | |||
| eddcf6661c | |||
| 500e80226e | |||
| 9ff485e3d0 | |||
| 303020c367 | |||
| 7c6799f7ba | |||
| 03d8931a3a | |||
| d8660c2136 | |||
| 6b30cb7f9c | |||
| f057f3583b | |||
| ca9b1fbe15 | |||
| 1370eaa416 | |||
| 3079f1b67d | |||
| fffdf3a72e | |||
| 5d87fa8ea3 | |||
| 391837ea95 | |||
| 18bf00644f | |||
| 068f1f160a | |||
| 2a880f1c17 | |||
| fc7dba5b5f | |||
| 02edcfc050 | |||
| 383dad8aa5 | |||
| 9df71d0c65 | |||
| cf412acff2 | |||
| 587d1932ac | |||
| dcd138e7bb | |||
| 89077a9915 | |||
| f44d998f00 | |||
| 2eb9718fdb | |||
| 8ee0cb3d7b | |||
| 5b8c70f469 | |||
| aed1b8b000 | |||
| e0afa5b2e4 | |||
| cb7e8b796d | |||
| 813dd1fd4b | |||
| 341974fcc9 | |||
| 32a3e1f26c | |||
| 3d081b3902 | |||
| 3de91bdbed | |||
| 6419a83617 | |||
| fc88967873 | |||
| 7d3d7a1ff3 | |||
| c1dd4b92ec | |||
| e1d786a2fc | |||
| 7de446931c | |||
| ef51f339be | |||
| 3144076a3e | |||
| a6090eef38 | |||
| d637b00c16 | |||
| 146480c668 | |||
| bc3e7c9946 | |||
| b162bbacb0 | |||
| 487eb5c167 | |||
| 509924db53 | |||
| abb20706ae | |||
| f8fb4514a4 | |||
| e8950cebe8 | |||
| 0a71d011f5 | |||
| a20727d29d | |||
| cff0c6ad32 | |||
| 13c40e7663 | |||
| 5c14b583b9 | |||
| 6e4de4051c | |||
| f6f5c3ca8e | |||
| 3629745220 | |||
| 97af3938bd | |||
| 2f3224feb8 | |||
| 23c375de2a | |||
| 9619b1d389 | |||
| 171c5280b5 | |||
| 2b72774128 | |||
| bc4c8cb5a5 | |||
| f3ebc4c9f4 | |||
| d32b0debf1 | |||
| b36e46afcf | |||
| 06834f5a8b | |||
| 0d0074e0ff | |||
| 862de7ecbf | |||
| 029a74bad3 | |||
| 0bd10ddcca | |||
| 77842b8a04 | |||
| d12a6196cb | |||
| 82db314b47 | |||
| ca6253115c | |||
| 6e4c8e18d5 | |||
| d976860a68 | |||
| acda574fad | |||
| 249fa17cef | |||
| c2c9af5310 | |||
| a1ba3afa15 | |||
| 747c2e995b | |||
| 242b3fb904 | |||
| 765bd8b99a | |||
| 5865acdfc6 | |||
| c0dd13342a | |||
| 7ff66bff49 | |||
| ee39977ee9 | |||
| 5d0959afa4 | |||
| 30947b5dff | |||
| 7d41078ddd | |||
| 6847926441 | |||
| 7f8fe20fdd | |||
| 092b6fe8e0 | |||
| 34ccac61ef | |||
| 15bbce5162 | |||
| 5d1c53593c | |||
| 0ffcda0e4e | |||
| 0ead526ea9 | |||
| 16ecebb1c0 | |||
| aa3d5ac3b6 | |||
| bbd6969dab | |||
| 45e6b320b9 | |||
| b4b34a6969 | |||
| 3491f64c17 | |||
| b9d310724e | |||
| 1409d8df4b | |||
| 80722e92c4 | |||
| 39c79f917f | |||
| 633328b21c | |||
| 7210075ae4 | |||
| 069e52e919 | |||
| 0ace8dd7aa | |||
| 1fd1211f41 | |||
| d7bcf1cd0a | |||
| 630a7fa3cb | |||
| f8a030d709 | |||
| cc502c8f72 | |||
| 32204666ec | |||
| 7aa071eb08 | |||
| b19b8c22b5 | |||
| c9e1f8d3e6 | |||
| e115636088 | |||
| bfcd295127 | |||
| a03b20490d | |||
| 75ddc73ba6 | |||
| 3c9b552c39 | |||
| 59ef1fffb2 | |||
| d356483226 | |||
| 4921924840 | |||
| 672e309171 | |||
| 8b014c0e5b | |||
| e65a1be292 | |||
| f4b47c3555 | |||
| 45538694a7 | |||
| e8d5efb8fd | |||
| 8321d233b3 | |||
| 1ef77f9f28 | |||
| f028689caf | |||
| 7e09763694 | |||
| e64ff7513a | |||
| e498ce3471 | |||
| fe7f502af9 | |||
| bd54ac6f56 | |||
| 3d306a982c | |||
| ce1e3375a7 | |||
| 0a5b5fac0f | |||
| aaa77d7eeb | |||
| e5488e9b27 | |||
| eec7bf1839 | |||
| ff53da1554 | |||
| fc02dd19f8 | |||
| b7792c4929 | |||
| 132ba9a220 | |||
| 59d28d01ac | |||
| 568757f9f1 | |||
| 73ce4f4a30 | |||
| d6b64b2708 | |||
| 739fe4972c | |||
| e1bc176567 | |||
| 0b60d6679b | |||
| 527e5e9784 | |||
| 49c1dd33f4 | |||
| c9e6763598 | |||
| e4474ab0cb | |||
| 2b5fccd184 | |||
| f4a11b8d7d | |||
| b97f69eb96 | |||
| 8617174ecd | |||
| b6ea15a8ca | |||
| ce3e7232fa | |||
| e181bb2982 | |||
| 52a972715f | |||
| 122baa837d | |||
| 5203db794d | |||
| e44d2573cc | |||
| 47f5bf9937 | |||
| f636a5379e | |||
| 9f0e59c913 | |||
| 8fa53d4767 | |||
| 2499326d03 | |||
| ea7d01e87e | |||
| e93b800127 | |||
| a412354c0c | |||
| 0a040acf3c | |||
| 41211aed68 | |||
| ed58ff4cc8 | |||
| 6ed2d1ebd5 | |||
| 8c7e8e08f3 | |||
| dfd96f892b | |||
| 6146f41967 | |||
| 5014052c82 | |||
| 2b239c6dc3 | |||
| 12c5ac8693 | |||
| 6bb4232086 | |||
| 48a87e7724 | |||
| b2826fe58d | |||
| bd634c023e | |||
| f8c1538126 | |||
| 2b49e37dc8 | |||
| a7977554dc | |||
| 6a34d36503 | |||
| c09b055ee5 | |||
| 8b89a2c450 | |||
| 0c720efc47 | |||
| 4ac4880f3a | |||
| a4611a8b25 | |||
| 7356491b1d | |||
| 47a92dd0a0 | |||
| 85f5078fa6 | |||
| a4bf20ccc3 | |||
| 19abb66d73 | |||
| 3f8985a8f4 | |||
| 28fc726f94 | |||
| e13984fca2 | |||
| f3c3393f4b | |||
| cdce5e9503 | |||
| 21649b9173 | |||
| 65ad9a2f3f | |||
| ffb5cfb59e | |||
| 36df133917 | |||
| 4a8539bfce | |||
| 0c85d026fa | |||
| b0f8605886 | |||
| 8a23bee799 | |||
| 78802f36da | |||
| 7d521bdf14 | |||
| d0dad4811a | |||
| 48ed38ebc0 | |||
| a1e8b37142 | |||
| 6eb7e8b810 | |||
| a083ffe0fc | |||
| 9f8d3e7156 | |||
| 89f6c7db27 | |||
| 88536cc2c3 | |||
| 720ee5f5bc | |||
| 6bb254d6a6 | |||
| ff28293b87 | |||
| eec609c763 | |||
| 08d781cde0 | |||
| 21f440ed3b | |||
| e88edd4164 | |||
| 8553ca8594 | |||
| 68fd915a84 | |||
| 91cd10fada | |||
| eae2e6feb7 | |||
| ca7d6cdff6 | |||
| a3bfda9a29 | |||
| 904d00d16d | |||
| 69ff309718 | |||
| 906c9ff4d6 | |||
| c1c50fa765 | |||
| a1d0943d64 | |||
| f5a1ccce77 | |||
| 4c44cf7e8b | |||
| f9379618ca | |||
| 200485f5ed | |||
| 10f413a819 | |||
| 1f746c2a41 | |||
| bfbe66ed99 | |||
| 8e565bdad2 | |||
| d74482e037 | |||
| 031c179e4d | |||
| d68deceb4a | |||
| b0d4dd112f | |||
| a018660bd9 | |||
| ea6a1e5479 | |||
| 34350199b9 | |||
| fb35be5404 | |||
| 20e6aed9c2 | |||
| c045d67a89 | |||
| 7a56564835 | |||
| 2bd7aa5c5b | |||
| f41791c810 | |||
| 689297d967 | |||
| 33ef833a31 | |||
| 2fc4cffa85 | |||
| ac89db2c83 | |||
| d47af07fd9 | |||
| 8079465018 | |||
| 2cbbc57118 | |||
| 977a06b093 | |||
| 77ba1e2497 | |||
| 554a341526 | |||
| 2ad3ccdd7e | |||
| c26ee4e20c | |||
| d60d2e30c2 | |||
| 151b28f48e | |||
| 6593461c19 | |||
| e4078b7378 | |||
| 6d309f91a1 | |||
| 54165fca7d | |||
| 88cff84d0d | |||
| d9dd64e014 | |||
| 400a1e7044 | |||
| 2b793990cf | |||
| 0445f0f447 | |||
| c7f139249b | |||
| b9f30d7a40 | |||
| 8be7058c76 | |||
| 38f6edb08b | |||
| 04df218ee9 | |||
| 499c04a2c4 | |||
| ea773dafd4 | |||
| 618a872c02 | |||
| 2abc7ea087 | |||
| 09daaf0e28 | |||
| b46f0d164e | |||
| 2c05f20643 | |||
| 5fc0ce1d1c | |||
| 9ec2f8e733 | |||
| 2cb9a0706e | |||
| 058e88aa57 | |||
| cc9d4995b1 | |||
| a3f4454222 | |||
| 42b1616504 | |||
| b4a4bb068e | |||
| 6289076a73 | |||
| ad6cdac1da | |||
| 0641dad600 | |||
| 277a21d1e5 | |||
| 29288efff2 | |||
| cffc892aff | |||
| c9de5e4b72 | |||
| ee4b7331e3 | |||
| ed01425b83 | |||
| 1ba1d883be | |||
| e9dc3eb9e6 | |||
| 89cd1e4473 | |||
| fcdb14ac49 | |||
| b7121808cb | |||
| 4fac28a8a9 | |||
| a3298d13a9 | |||
| 1da1cb8114 | |||
| de3d2de868 | |||
| 236f748eda | |||
| b77f678805 | |||
| 75548691d5 | |||
| 9c5e7cf227 | |||
| 3beea76118 | |||
| f764914927 | |||
| 37b8ce1257 | |||
| 45a3ce44fd | |||
| 67d4d2420b | |||
| 0838c313e3 | |||
| ca81c5053e | |||
| 3f1288cc04 | |||
| 2c43cd5fc4 | |||
| 572a5f1376 | |||
| e037755d0f | |||
| 495054309f | |||
| 751038ad1d | |||
| 515796fc05 | |||
| 5bf1e40d2e | |||
| 0384afebee | |||
| bffb03b92c | |||
| 181fde2731 | |||
| e4a8c6bb57 | |||
| bf73a289c2 | |||
| cde9596299 | |||
| bf5fc8202e | |||
| 3634d40252 | |||
| 2028adef0e | |||
| 90f8f91aa4 | |||
| 1023468aa7 | |||
| 20f78d1393 | |||
| c9f30c2234 | |||
| d431807da0 | |||
| 819f3cafe9 | |||
| 4b934c52c9 | |||
| 91e6e497d8 | |||
| e3e3c175cb | |||
| eb668c5bdf | |||
| af73385a87 | |||
| 944243ea4b | |||
| 0e7ee0af2a | |||
| f3c432c5d1 | |||
| 35a8a69f8f | |||
| 4e86290ec4 | |||
| 3c7de1f2c3 | |||
| 9c6f39ea9f | |||
| cf510bf6b3 | |||
| 7aef4d6537 | |||
| fa1b936b57 | |||
| aba9bb75da | |||
| 10f6cbf651 | |||
| 4b1c5d2e45 | |||
| 9c9962c21a | |||
| 69aafbf131 | |||
| 3e40a31673 | |||
| 3724bce522 | |||
| 350d39bd89 | |||
| 3093e0e684 | |||
| 15ca09fed0 | |||
| 1f6ac64bae | |||
| 893741b98f | |||
| 65fb923216 | |||
| fb36d3660f | |||
| 9c0f48ea3b | |||
| c1eac80503 | |||
| e1535bc1ba | |||
| e278fa737c | |||
| 584f8dee65 | |||
| 74ad368a30 | |||
| 7e35bd48d9 | |||
| a49b02406c | |||
| 66df691aad | |||
| 5d8b79f8cc | |||
| 2ee6d87147 | |||
| f12b357b28 | |||
| 14b3d7aed7 | |||
| 1b98cddc2a | |||
| f69837c625 | |||
| ccd141efef | |||
| bc14c0af00 | |||
| 5571860d80 | |||
| 0527983e49 | |||
| d5a0aa8368 | |||
| 584f47c0c7 | |||
| 7f48f8460d | |||
| 2b483b4019 | |||
| fc8c745ac3 | |||
| 5d35ae1fb0 | |||
| 94ed307353 | |||
| c84a56d3a6 | |||
| 3624e63743 | |||
| 538216cfb9 | |||
| f200d24f56 | |||
| 4e5a6409bb | |||
| c73dd6265a | |||
| cebfe2be47 | |||
| 8ca86babb5 | |||
| 877740182b | |||
| 29f2639000 | |||
| d4233aaf07 | |||
| 9f463dbe40 | |||
| de606c2ec6 | |||
| 282fda76b1 | |||
| fd35af5bfc | |||
| 270118aa80 | |||
| b75bb062c1 | |||
| 0e5bbce913 | |||
| 344647c846 | |||
| 1d59795ab9 | |||
| 65988fa5c4 | |||
| ff8050a62a | |||
| 53732b5c97 | |||
| d978475c24 | |||
| 15bccab8bc | |||
| 35f91f6642 | |||
| 878c866f3f | |||
| 14e711400a | |||
| d3b155f5c0 | |||
| 3c447bb475 | |||
| 56187f5f18 | |||
| ace3bd2b52 | |||
| 410d3bb745 | |||
| 4ae0323eb2 | |||
| 2f5144f2d8 | |||
| 1b46c45918 | |||
| 556533ae7a | |||
| 3794fbb048 | |||
| 56cb5fa405 | |||
| 62245eea34 | |||
| 21106d854c | |||
| 852c343ee4 | |||
| cbb792431f | |||
| d4477ff01c | |||
| 1397d100f1 | |||
| 129c3fea8f | |||
| 952cfe7031 | |||
| f53afb410c | |||
| 8d6378d316 | |||
| e8670825cd | |||
| 146b059938 | |||
| ff12cc297b | |||
| 4aec844167 | |||
| 822ffdc5fa | |||
| 6c37374657 | |||
| a3f6a211e4 | |||
| 46f70b184d | |||
| af686c3e56 | |||
| 9499108401 | |||
| 6f2a15fa2d | |||
| 9b3a5007f3 | |||
| f8244c51d6 | |||
| 11d4db7732 | |||
| 9d80b05d75 | |||
| 5203f32bda | |||
| c5ea87c015 | |||
| 2e8150c5bb | |||
| 07a6be21d5 | |||
| cab5b5b385 | |||
| 6bc4c2fb94 | |||
| d793b03134 | |||
| 786fc542ea | |||
| 863e72f533 | |||
| 4527758a76 | |||
| abbe36c8c5 | |||
| c2f1412f84 | |||
| 5a83e76010 | |||
| d4bbbe2315 | |||
| 82f2a60dfc | |||
| 9b40429482 | |||
| 2491ca08a4 | |||
| a2a475cbad | |||
| 17c6f64c86 | |||
| fd078cbac0 | |||
| 94e2537891 | |||
| e7444df938 | |||
| adc25ca1a9 | |||
| bc483edb26 | |||
| 608ccab2c6 | |||
| e33206de20 | |||
| ad5ff5b43c | |||
| 087c4a4ccc | |||
| dcfd4d33c5 | |||
| 51369ab05a | |||
| 0d0baf39b4 | |||
| e7ba8fa505 | |||
| 03685c643c | |||
| d1746ce0d0 | |||
| d1ca404feb | |||
| db078e072a | |||
| 9740958eed | |||
| 7f85fa4543 | |||
| 9ba2d231a7 | |||
| 99eba83adf | |||
| 3e264b88c1 | |||
| 29b4b4930f | |||
| e67f52e2d8 | |||
| ad47d8036c | |||
| fa40e6a2cf | |||
| 486f951b71 | |||
| 9e7c25ed47 | |||
| 77fc5445db | |||
| 461ac40509 | |||
| 5ce24233ab | |||
| 4b8b3dd64d | |||
| 406c8506aa | |||
| c4c5488e60 | |||
| 6e05be9fee | |||
| dffd66a8ae | |||
| bed990372b | |||
| a200c41684 | |||
| 524dc3c3bf | |||
| 36c4202e23 | |||
| d6c50e5921 | |||
| c647547433 | |||
| d64b658e52 | |||
| 5f70d0f83b | |||
| 5f740adc2a | |||
| 0e21955601 | |||
| a80f3ad69a | |||
| 1cfe064c03 | |||
| e718df1bea | |||
| ce221b4b34 | |||
| ec2101bf4a | |||
| b8f20d5d7a | |||
| e1235b2dd1 | |||
| 8c7f20a946 | |||
| 24745ffda3 | |||
| dd03da42f6 | |||
| 9e79b6a0b1 | |||
| e5556fb34b | |||
| f61297a84f | |||
| 1deb1e939f | |||
| 0a5e6617c3 | |||
| e994566c3c | |||
| 2251f1f079 | |||
| 993fa97aa8 | |||
| 08850cfd5c | |||
| 98412ab28c | |||
| 0ef0630463 | |||
| f5afa85978 | |||
| 00d6bc2098 | |||
| 3a1e5b5784 | |||
| 09e4cb26ad | |||
| 74bbd02a87 | |||
| 5b7bbe1faf | |||
| a26ba942cf | |||
| af1a315d6e | |||
| e510ca7428 | |||
| ed0c942676 | |||
| b5cf08f12e | |||
| 3259870fb9 | |||
| 349cefa28a | |||
| 00b65621d1 | |||
| cb6329a9de | |||
| 9719df1316 | |||
| 5e00e7164c | |||
| 39754e2ec0 | |||
| dd9759035f | |||
| 1191d49b21 | |||
| 12b8fa5b76 | |||
| 1eafd9f1dc | |||
| fa3f86cebc | |||
| c9423b32e6 | |||
| 7c0a63e836 | |||
| de430ebef8 | |||
| 0821d0099e | |||
| 9e91723f36 | |||
| 9b930a38b1 | |||
| d4ddffc3f7 | |||
| bffc2d32ac | |||
| 69c27e21c6 | |||
| d8cdb6de04 | |||
| 29cd9bc780 | |||
| a310ab8309 | |||
| bbc9130bd6 | |||
| 6962d7c9a6 | |||
| 8240771409 | |||
| a30f80b49d | |||
| 2339c615a0 | |||
| 1050b733e2 | |||
| 648fe7f001 | |||
| 4483c1bec6 | |||
| 4a84920329 | |||
| b499255a7c | |||
| 6caf422ce7 | |||
| 0c41adf6d5 | |||
| 7de518d06b | |||
| 08480124ef | |||
| 71e2806917 | |||
| 12efd8870c | |||
| fe5db799d7 | |||
| 7d85cfb0e0 | |||
| 7422b7f0d3 | |||
| e414ad3eb3 | |||
| 133dddd84c | |||
| cc90252c3f | |||
| 7e4d976b2a | |||
| d18ade0149 | |||
| 0d68432ed3 | |||
| d2d2dbe8a3 | |||
| 46931af4da | |||
| 566f833ae6 | |||
| 5543233e56 | |||
| 292ba4bf2e | |||
| d731b03514 | |||
| 14055acf26 | |||
| b95aeb0b24 | |||
| 5608a1c3f2 | |||
| 9de887fd9b | |||
| db4758bddc | |||
| 8022c49f8a | |||
| bed106a484 | |||
| 8592b34e8c | |||
| b996a7e772 | |||
| 02c6cdd79b | |||
| 15545f181c | |||
| 79dc989832 | |||
| 90afd124e9 | |||
| b77bbb4e52 | |||
| 616d22a30f | |||
| 7f3a974cf6 | |||
| db78c9b951 | |||
| da4946b2fc | |||
| 2479cde562 | |||
| c4e087cfe1 | |||
| 2ffcd1c848 | |||
| e896cc0640 | |||
| 430687a844 | |||
| ef1e9188d8 | |||
| b4a9b1c29c | |||
| daf5e96951 | |||
| 9dde5d0326 | |||
| 6b86b7c7b5 | |||
| 3523b8b61e | |||
| 1064cb79b3 | |||
| fa4c7737f9 | |||
| 1edd5decc4 | |||
| b99a152175 | |||
| fa61d33f67 | |||
| ea8209c1f4 | |||
| 14d548d3ab | |||
| 1721639b3f | |||
| 201f87ba52 | |||
| eb2de5361d | |||
| 75f3db6828 | |||
| 5268a7d179 | |||
| c0e982a33c | |||
| 1e3884d8a8 | |||
| dfa9a741a9 | |||
| 9402c16739 | |||
| 60850313ed | |||
| 01cc2c2726 | |||
| 28a5bdc600 | |||
| 0e569b6261 | |||
| 07d6e8dba9 | |||
| fe1eb53780 | |||
| c3d6031ab0 | |||
| 4239b7a439 | |||
| 98d34f72c3 | |||
| 8bd86afd82 | |||
| 32c983f28c | |||
| 9fb74ad548 | |||
| 86b1793572 | |||
| ea6209316e | |||
| 12fe2b11a4 | |||
| b60bb38fbe | |||
| a314131066 | |||
| 6c8c0ea3b3 | |||
| 0414b40ff6 | |||
| afd63c1d49 | |||
| af95ea973d | |||
| 52691cbe17 | |||
| 836a37d5bf | |||
| 676e3c6440 | |||
| 8ebc8aa31d | |||
| 09ce8a4073 | |||
| f17030a5db | |||
| e03749131c | |||
| c3acf39c0b | |||
| edcfa129fc | |||
| 0ec9de15a1 | |||
| 8c69e7497f | |||
| a25b47b7d9 | |||
| f129191366 | |||
| 7529109a16 | |||
| 50f7c6f6e8 | |||
| 0d997f451c | |||
| e3729835fa | |||
| bb244a53af | |||
| 1d5745f0f1 | |||
| 867f9ea3b4 | |||
| a6eebf4aae | |||
| 08d5d4bad9 | |||
| fd2d757d75 | |||
| 91a1e0aa34 | |||
| 241468f387 | |||
| 517672994d | |||
| a2f7687335 | |||
| c0d70b385a | |||
| a26ea4dc0f | |||
| befeab2011 | |||
| cf26b84076 | |||
| 000379b656 | |||
| edf96ccb5c | |||
| 8385f58747 | |||
| 7b60f4d326 | |||
| 21ebcb2b58 | |||
| 3ae2014338 | |||
| 893d6a731f | |||
| 4e9f339544 | |||
| 61d829afcd | |||
| 5fa5eed740 | |||
| 993753db45 | |||
| 99e78ff65b | |||
| 5c8c25188c | |||
| eb800f9c70 | |||
| c0ecc7df80 | |||
| c885e0855c | |||
| 15c6cabe5c | |||
| a2bcc3744b | |||
| d8570aed9c | |||
| 53fa09caf4 | |||
| 586467f157 | |||
| 008f36aa2f | |||
| 3cb8b35132 | |||
| 55f800202e | |||
| f23dc1f780 | |||
| e0b91abcb1 | |||
| 2e9a9e267f | |||
| b5f28773dd | |||
| 20d0ed8741 | |||
| 7747229c18 | |||
| 65399792a1 | |||
| bc29081b9d | |||
| 5ce352d915 | |||
| e917c151fe | |||
| 45c54f931b | |||
| 530102521f | |||
| 9f2357952d | |||
| f7cc2ba306 | |||
| 62a9b43fe5 | |||
| 5a09543384 | |||
| 717a58b5b3 | |||
| f8f7f59495 | |||
| f3d7472cd4 | |||
| 38505aa948 | |||
| c0ca5bc13e | |||
| 74f83dd637 | |||
| b6e2847fda | |||
| 79b8758997 | |||
| 443742de16 | |||
| 1e238e8efe | |||
| 83a16f6e55 | |||
| 2234c8dff1 | |||
| be6fd5f14f | |||
| 012e2a377e | |||
| fbbb9a9b13 | |||
| e58f6d2166 | |||
| cbe97eba60 | |||
| 2b9234a2d3 | |||
| c05d713aa5 | |||
| 8d0fbc3e40 | |||
| f6ad09987d | |||
| a967ef014f | |||
| f5f62ccf77 | |||
| 642b3d3113 | |||
| c168b22e38 | |||
| b7a8f1a784 | |||
| 846f9a3b4c | |||
| 5b7b7f9257 | |||
| d816c3eaf4 | |||
| 9481e5a15a | |||
| 345ace8714 | |||
| 5aedf7ac30 | |||
| 7022eb93b7 | |||
| 55f5dd171b | |||
| ffd5e3b4c1 | |||
| 3e6453253c | |||
| 382de1db79 | |||
| 3400c3ab34 | |||
| 02eb0d0d44 | |||
| 7f4fd26509 | |||
| 9f11fa834b | |||
| b5f4a08cdf | |||
| 1c390e570a | |||
| 2f7bb422f2 | |||
| 095aa58ee0 | |||
| 1851a4943d | |||
| 36ac03d34f | |||
| e4a11221e9 | |||
| baec933d89 | |||
| bd97721bbc | |||
| e6f1f9e416 | |||
| 88f1d0bc0e | |||
| b6e12a804e | |||
| 85a4f3df0a | |||
| d701cdb85b | |||
| 70dcb5aafa | |||
| fbcc0b358a | |||
| 4a91709ffb | |||
| 7c123bdcca | |||
| 170b77981f | |||
| 8f4c03cfdd | |||
| adee84763e | |||
| 755a2696ed | |||
| d50f5c9c0e | |||
| 0548f0f9b3 | |||
| 98f1979146 | |||
| 29afd17e3b | |||
| fb1e73a475 | |||
| d21dc57f9c | |||
| 5192c429ab | |||
| a47556b991 | |||
| 2e81b0407e | |||
| bc9962bc64 | |||
| fb9570c175 | |||
| 7784794e45 | |||
| 7722b881ff | |||
| 6ecc5bf150 | |||
| 8c58a825d1 | |||
| d0a89fc507 | |||
| c1b1efd4d8 | |||
| 3dbb59c1e6 | |||
| f3dc41130b | |||
| 851dcbe1ae | |||
| 1667632227 | |||
| 158da70086 | |||
| 7327520c0f | |||
| 59d82686c8 | |||
| fead30ce3d | |||
| c187f40653 | |||
| 8085cbe12a | |||
| 98f1f7fde2 | |||
| 9ece16bb5b | |||
| 38c155de0c | |||
| e0bdcfd33a | |||
| 3d5f428ce8 | |||
| f237adc073 | |||
| 15f8f12b90 | |||
| 47c5c04956 | |||
| 21ce93f7b7 | |||
| 6264eb2934 | |||
| f89a4ce170 | |||
| a19391d085 | |||
| 11490cc358 | |||
| 23aeaffe5d | |||
| ec42cf7899 | |||
| 76d0b50b0d | |||
| 709d50f0f6 | |||
| 9543319a31 | |||
| fca01b2699 | |||
| b48441e2cc | |||
| 4f48d98809 | |||
| 228c5a1dc2 | |||
| 74517e5c03 | |||
| cbf420cbc4 | |||
| 26f9c46105 | |||
| 64095d10b5 | |||
| b6e0867f44 | |||
| c07dc3cfa1 | |||
| ee2e4c65d0 | |||
| a6ec9330d5 | |||
| 0a8af5628a | |||
| 3eecbd6484 | |||
| 2a99cd9889 | |||
| 9993323d5c | |||
| dd17b53a88 | |||
| 757c76b6aa | |||
| e77bca2712 | |||
| 9e4ba0c86e | |||
| 7426177b05 | |||
| 2a74b95ff5 | |||
| 9fbc8ad8c8 | |||
| 67901ddc56 | |||
| fcd670cf8b | |||
| 6324b0e83f | |||
| b4c61e3633 | |||
| 8c2cbf8333 | |||
| 292d3cb85d | |||
| fe43c62eeb | |||
| a7d1488fe7 | |||
| c061f834f0 | |||
| e54ce63dff | |||
| 2288835f5a | |||
| cad5d21697 | |||
| 96594be0db | |||
| 38b43cef81 | |||
| 405c6da334 | |||
| 298d325012 | |||
| ab43d7348b | |||
| aec843a571 | |||
| 5437d8f0de | |||
| a8081f3a7c | |||
| 3ba75223ba | |||
| 12d2815345 | |||
| 210ad80c3d | |||
| a0767d3c11 | |||
| 9728c97ad2 | |||
| fdb80358b0 | |||
| 804623b1ac | |||
| b13206c533 | |||
| 22733f9045 | |||
| a5e7e423c3 | |||
| b4b143f5be | |||
| db672e66c0 | |||
| c38e157840 | |||
| c36fc7cca9 | |||
| 58ddd97a69 | |||
| f41d77a022 | |||
| 98bff54606 | |||
| 6cd18e0eff | |||
| 9cb7bd589a | |||
| c9d3ad1736 | |||
| 84f737e554 | |||
| 16b5e1d2a2 | |||
| 39ab5a6ee9 | |||
| 299732762f | |||
| fe389e6465 | |||
| 62546edf36 | |||
| 50dbb406ea | |||
| 420765823f | |||
| 700e953026 | |||
| 5cfc23b159 | |||
| 6d7570713b | |||
| e9ba660fe6 | |||
| 2a56f82eec | |||
| 1c507a8915 | |||
| 2199acc88d | |||
| b438d901de | |||
| 9122c93000 | |||
| 67ae7218af | |||
| 9bc64d73b3 | |||
| ae2e9cdfc5 | |||
| 2ac7223d54 | |||
| 24bb04536c | |||
| fc2ee83e66 | |||
| e84afb1f32 | |||
| 0283beddf6 | |||
| d02d7c8564 | |||
| 2e99122684 | |||
| 17570a0e8b | |||
| 2a9a398734 | |||
| adf094cbec | |||
| 098715bbae | |||
| 4ee2094be5 | |||
| d5f6faf60e | |||
| 76cbfd80a2 | |||
| d554876f4c | |||
| e289890eea | |||
| 3f6bf906b8 | |||
| ed8e0cde48 | |||
| 5e998a3622 | |||
| 9ad010f231 | |||
| 1020ac363a | |||
| 09196e8c03 | |||
| ac5b947973 | |||
| fd4eb40307 | |||
| 3134485376 | |||
| aca16767ce | |||
| c407ddd658 | |||
| 2fa1e5a510 | |||
| a5aec38bcb | |||
| ab0e429189 | |||
| 8905152126 | |||
| d3a457131c | |||
| 4b65f4ee25 | |||
| afae3656f8 | |||
| 50e523718f | |||
| ac20bb957e | |||
| b4ba458b62 | |||
| 66a21fa5d9 | |||
| 1eb393cf37 | |||
| a2848932b9 | |||
| 3de5dae2b9 | |||
| ea8fdcf557 | |||
| 8ff734bf65 | |||
| aa0760fcb9 | |||
| 37e81b0cdf | |||
| 2528ba313d | |||
| 2bf500f6e7 | |||
| 6bebc1a7a2 | |||
| defefa47a1 | |||
| cfb52584da | |||
| b36716c1ad | |||
| db5e7083a7 | |||
| c35543e52f | |||
| dceaf5a2a4 | |||
| 3705bf0531 | |||
| c46b7121d2 | |||
| 6f49a438db | |||
| 9428a8745c | |||
| 1e095bf53a | |||
| c3a9f9f488 | |||
| 421c4e96aa | |||
| f974cbc10f | |||
| dba81145b0 | |||
| f64769ab68 | |||
| 3aa45cd30d | |||
| 40a1af8ba0 | |||
| 6f8c74b21e | |||
| 2b185e2ee4 | |||
| 08839257c0 | |||
| e05228b674 | |||
| 8d1556d123 | |||
| 296384ffae | |||
| 5a3b3e735d | |||
| 15711beb16 | |||
| 5c3374fca5 | |||
| 43b82d1910 | |||
| 15dca8755c | |||
| 67151cce42 | |||
| 28fd7a2e7e | |||
| 592e13d4e7 | |||
| f2233c93cf | |||
| e7520bab88 | |||
| c3a4d9adf4 | |||
| da5048b971 | |||
| a04ac505c5 | |||
| 0c1f110f50 | |||
| 18b24613ac | |||
| 39f2b03e0d | |||
| 90e1e1b595 | |||
| 5801803a73 | |||
| 19b1a9b2c4 | |||
| dd8fc05f13 | |||
| 958cae1e1b | |||
| b236545451 | |||
| 30a7c3ce83 | |||
| 0f003f71dd | |||
| 7e13cb5c6e | |||
| 5afcdfb9c4 | |||
| f5ffc93fcd | |||
| 3c18655767 | |||
| c77c450aa1 | |||
| e64d002ad8 | |||
| fb0fed655d | |||
| 582c95d3d9 | |||
| 96f05d342c | |||
| 92c90fa466 | |||
| b5403e5542 | |||
| 53f6e7c1f3 | |||
| 4a9994822f | |||
| 29ed4a312d | |||
| 97f0cd3b1a | |||
| 41cf0fd5d2 | |||
| d4d49d95d0 | |||
| b5517eac85 | |||
| 534a801f76 | |||
| e836eee6e3 | |||
| b5e0b03491 | |||
| 7bdfa7fe62 | |||
| acaf471f49 | |||
| 26a5a6ba43 | |||
| 3e1aeb3f7b | |||
| 05fa1d376c | |||
| 93dbd67da9 | |||
| ae748a66a1 | |||
| 48b6104b59 | |||
| 085af98af5 | |||
| 61e0ae2f33 | |||
| f2206e7c15 | |||
| 79da053c9b | |||
| 2db7b5c4d8 | |||
| 6284b82a97 | |||
| 6f60d79b6a | |||
| f6d999b09e | |||
| 680d080830 | |||
| 8bdeae183c | |||
| 9b8746d41f | |||
| 63a890a877 | |||
| 2733416cd4 | |||
| 8e34c76fb7 | |||
| 050c11e72b | |||
| 81d4efff98 | |||
| be09f42314 | |||
| 8320494490 | |||
| 6709499554 | |||
| 00afa9cb45 | |||
| af5634e897 | |||
| 1051704194 | |||
| f6ba8d7358 | |||
| 2b71d071b5 | |||
| 6886cb9117 | |||
| 48d1093fd5 | |||
| af776f40d8 | |||
| d88e7072bd | |||
| 7a70c77d9b | |||
| 2f14d723b2 | |||
| dacdb01bbe | |||
| ff1e1627cc | |||
| 80d7fbd4ad | |||
| b3be3ee4ec | |||
| df2b3108c6 | |||
| 40a4d61a3e | |||
| 0cfa03c057 | |||
| 35a7a16539 | |||
| 1949aa884f | |||
| ec988de624 | |||
| e09e7191f3 | |||
| 29c701e999 | |||
| debf550b0a | |||
| 93f4126567 | |||
| b1276c4770 | |||
| 80a3e1e6dd | |||
| dd91172b92 | |||
| 14d053d874 | |||
| 771b9c9be3 | |||
| 1c8a26dfea | |||
| 5f9213462c | |||
| a337fd5861 | |||
| 43c881075c | |||
| b9013b20da | |||
| 6c10c01036 | |||
| e1615cc7df | |||
| 51b3f4eba3 | |||
| 27289405fe | |||
| 740ce3dc26 | |||
| d1162a171a | |||
| 6a15dccca3 | |||
| ff509e17ac | |||
| 921c4d826a | |||
| c4318ca7f1 | |||
| 7202073b4e | |||
| ae2be01c8a | |||
| 1cd8171b0a | |||
| 45cb279a4f | |||
| ca36eef78d | |||
| 947afd3f38 | |||
| 2ecc8dbc21 | |||
| 8db38b9e94 | |||
| d7740bfd7a | |||
| 43f412f26e | |||
| 543ce468e5 | |||
| 9f1fe87509 | |||
| 8b2a135b9d | |||
| a6d31b4042 | |||
| 9c9553bb16 | |||
| 66aea36685 | |||
| 4b364b3f2a | |||
| 1f8723a0b7 | |||
| 9fdd3d373e | |||
| 5fa6adbab0 | |||
| aab21de923 | |||
| 7abd1b0f4e | |||
| 81e375d1cc | |||
| 0888315038 | |||
| 20cdb5841c | |||
| 1c6a0f1edd | |||
| d32d0d56f0 | |||
| 06a39a2e42 | |||
| 031168bb2f | |||
| 892e908594 | |||
| e144c2c0cf | |||
| 1394578088 | |||
| d498bd6263 | |||
| fb75df00d1 | |||
| 9ad1a3d3a7 | |||
| b0872ba3b2 | |||
| 8e04595035 | |||
| a443965d71 | |||
| 638650f97e | |||
| e7f28012a6 | |||
| c5b25b566f | |||
| 48f08aee09 | |||
| f7ab39dbb9 | |||
| c29da0f60c | |||
| 12a82bd7e1 | |||
| da9cb5ad81 | |||
| e973404c7d | |||
| 5bb6f2a72c | |||
| c4319647c6 | |||
| 1ebdbde2f7 | |||
| 78157ce3e5 | |||
| 149e0e4e71 | |||
| a54755b5f0 | |||
| 7274a5bcfc | |||
| 9fe6d51226 | |||
| 48d489686f | |||
| 25dbde80ad | |||
| b501bb0c7c | |||
| 5ec5075caf | |||
| 5ce232875e | |||
| 2312b8ceae | |||
| db5dd026ea | |||
| fcf64c8f3e | |||
| fa0df0c5d9 | |||
| 1b7288a4cf | |||
| e57cfeed90 | |||
| 69c86cc8dd | |||
| 4b3d3a43dd | |||
| 1bf2d55e8a | |||
| ed605fe5d8 | |||
| 5b902cd94c | |||
| ff05a7dc2e | |||
| b21e15fb23 | |||
| db57d389fd | |||
| dfa7eb486d | |||
| 6725f2fab1 | |||
| fbebdd035f | |||
| cd559642f6 | |||
| 804f7a73a2 | |||
| 559a7a9564 | |||
| 8a6659383d | |||
| c428d9c4dc | |||
| a94cffa212 | |||
| c2bccd8ae8 | |||
| bead309555 | |||
| 4553a25f2f | |||
| 419ac81cb3 | |||
| e854c49741 | |||
| d28401a2ec | |||
| 744620f79d | |||
| 2ed85ab4b3 | |||
| 26129e8efc | |||
| f4ad8d562a | |||
| c143cb7eec | |||
| dd65fcbb72 | |||
| ba5072c02e | |||
| a0f08b9531 | |||
| 2557947a15 | |||
| 4ebdf562dd | |||
| 30ccbe3295 | |||
| 087ed2d4e7 | |||
| 330bd865e1 | |||
| 050164f1a5 | |||
| 64d8273529 | |||
| 45eadeff76 | |||
| 7a66aa6c07 | |||
| 90671920da | |||
| fe0aeb51e9 | |||
| 9ddc29275e | |||
| 9992121be7 | |||
| cdc8076385 | |||
| 3c74080224 | |||
| f23858233a | |||
| d706042f51 | |||
| 4c209d6d10 | |||
| 3955ba8b97 | |||
| b38aec0ad7 | |||
| fa5b112542 | |||
| ab69925510 | |||
| a3f5248b4c | |||
| 3465334e7a | |||
| f80c5143ac | |||
| 7c6cc73bee | |||
| 54aef8ec16 | |||
| 17a3ea4930 | |||
| 119b4d39f2 | |||
| 812e547c12 | |||
| 6dc1a52109 | |||
| d5cf7500c4 | |||
| 9e0d463633 | |||
| 98cb2ff535 | |||
| cac15fb4ba | |||
| d48f1b5511 | |||
| b31bf29321 | |||
| ea842217ab | |||
| 1c9b160e7a | |||
| ca9c39293d | |||
| 80f579e31c | |||
| a54fe183f6 | |||
| 27ab1018de | |||
| ecbcfabb64 | |||
| a6052978a8 | |||
| cc5db0aecc | |||
| b9ce00ab56 | |||
| c81acad83a | |||
| fddb35b9c7 | |||
| 5264e5cd3a | |||
| 71be1ccd68 | |||
| b37daed8be | |||
| bd07af3ba0 | |||
| 0e14635281 | |||
| d183e7621c | |||
| b6817b7e3b | |||
| 6b752b099e | |||
| 7f724a5727 | |||
| 27845d02da | |||
| a8e095c09d | |||
| 904abeed68 | |||
| abd8ce9c23 | |||
| 5cb9edb0d6 | |||
| 792ad8cc08 | |||
| b43d425ebc | |||
| 0336afda01 | |||
| 456faa8028 | |||
| 1533380936 | |||
| 30be5132e6 | |||
| 924f88fdae | |||
| 5b4c7353ef | |||
| 0d3ea59f40 | |||
| 3efc6fb05e | |||
| 71c4259746 | |||
| f8dfa167c9 | |||
| acc6d6f60c | |||
| f98ebe8b76 | |||
| 1b67a5ec27 | |||
| 1ef218f681 | |||
| 01997d9bb1 | |||
| 708922cd7e | |||
| 33ee63cafa | |||
| d1c3a8c65a | |||
| 86f75e1b68 | |||
| a3f5579faa | |||
| 45a3b937be | |||
| 1ff105bcfc | |||
| 82886db092 | |||
| d456103823 | |||
| 47066f5da0 | |||
| dcdd78489c | |||
| 28fac49755 | |||
| afcdea9feb | |||
| 05a86cf36a | |||
| a9be1b04fb | |||
| 81f7b053eb | |||
| 5a1202fad9 | |||
| be5beb7962 | |||
| 4c84dae3ed | |||
| aa73283a1d | |||
| 979d8667e8 | |||
| c8cddf8ae5 | |||
| a6a7b16dba | |||
| 4debb96942 | |||
| 66c09861c6 | |||
| f4e5189b35 | |||
| 38e725a0fb | |||
| 20bc6e5dd3 | |||
| 3bc744e524 | |||
| 70b65d034f | |||
| 6b16ce2cc3 | |||
| 9a0649a9b1 | |||
| 7c310272ed | |||
| eb31e84d71 | |||
| dc510d4ae8 | |||
| 6759c38567 | |||
| 0dbe559a0d | |||
| 97fa7e7f36 | |||
| 627f971e22 | |||
| ec748636f2 | |||
| dd0b7f0842 | |||
| 50b0c4e153 | |||
| 931374641f | |||
| 7f52830f61 | |||
| 46dc51076f | |||
| b800342d72 | |||
| a516adc396 | |||
| 56063274aa | |||
| 9ba099f79d | |||
| 2827e9e679 | |||
| 5c78c95297 | |||
| e508f9848a | |||
| 7e50cf2591 | |||
| c5d54d8f7d | |||
| 0b120a1189 | |||
| fbc8e686a2 | |||
| ba32b2cce2 | |||
| af10366c22 | |||
| cabb03362a | |||
| 564f2bd144 | |||
| 7182cf3396 | |||
| 03df38478c | |||
| 782b05ee96 | |||
| 3b7beab501 | |||
| b1ecd237cb | |||
| fcaa38a6dd | |||
| 427d17b6c8 | |||
| e07e627351 | |||
| a2cd1c3557 | |||
| 8f8ed2d6c9 | |||
| 24f8cbbcec | |||
| 754094e6b3 | |||
| 70aa79d80b | |||
| e63041c570 | |||
| f0a032979c | |||
| f2019c85aa | |||
| 2a22cd975c | |||
| 4a77371662 | |||
| 65cef61880 | |||
| e879072675 | |||
| e61403a06b | |||
| 17546ea8d8 | |||
| f0df4183c7 | |||
| 592d3a68d8 | |||
| c6414e7841 | |||
| 71ac19f5a0 | |||
| 91b8d0bce0 | |||
| 830e1f9cc5 | |||
| d157c9083f | |||
| 04ab5a09e3 | |||
| 0314bbc5b2 | |||
| 9d0d1a9654 | |||
| 6292c6fe42 | |||
| 7863efebb8 | |||
| 463eb0918a | |||
| c825cb050c | |||
| b48e31f37d | |||
| 526830156a | |||
| be4777d384 | |||
| 71bcd1cd0a | |||
| 1dfbdb2f83 | |||
| 930f791d6c | |||
| e132e7ee17 | |||
| 32197fac12 | |||
| 6299bb5fc0 | |||
| 7cd7265b2b | |||
| 6cbf121cc9 | |||
| a8aaa01a24 | |||
| 9cc62b7aa3 | |||
| 4fa38da17c | |||
| 9b4dc520fe | |||
| 267d5f705b | |||
| 76cb68a725 | |||
| 8f17dff5c7 | |||
| 6f388e30df | |||
| 4d169b91b4 | |||
| 6b15446bcc | |||
| 4fe084948d | |||
| fbb8860a49 | |||
| 9517f90f1e | |||
| 46b5622d84 | |||
| e8194e6965 | |||
| 984de33b1e | |||
| 595e3499fe | |||
| 27fd29fa7e | |||
| 01dfa8dfad | |||
| bcf19a035b | |||
| eea835f065 | |||
| 2d86b1bfef | |||
| e8c26df382 | |||
| c21b2965ef | |||
| e01e6229de | |||
| e0d3fabaa0 | |||
| ee455bb41c | |||
| 948607690c | |||
| 861cef15b8 | |||
| 4d26fca249 | |||
| f85d1db130 | |||
| ff2ace4c89 | |||
| 3baf918f50 | |||
| e95a8e7b61 | |||
| 2d93ef3f48 | |||
| 845843c17f | |||
| 9e18c06806 | |||
| 74304d36f5 | |||
| 9bf7c97890 | |||
| a953a3817c | |||
| 1762192352 | |||
| a145667637 | |||
| 186a45140a | |||
| a12f3b5c81 | |||
| 764e78b4f4 | |||
| 3e8d0a6bba | |||
| 824ca10aef | |||
| 1c9805f05c | |||
| 4f6ec238d7 | |||
| e23fefa0c5 | |||
| 2af5afae01 | |||
| 7af8217a3c | |||
| fdab009bd1 | |||
| bf2f485ff7 | |||
| c074793aef | |||
| 17d0046fb5 | |||
| 1a0d70b91d | |||
| bc6387af42 | |||
| ea39e62329 | |||
| 00ec10a335 | |||
| 3d81db1bc2 | |||
| 9b88fdfbea | |||
| c1b59a7dac | |||
| 831e82a28a | |||
| 6f8ef29f2e | |||
| 35256b47e7 | |||
| 05c6e5c0c7 | |||
| 458a67b4b4 | |||
| bd9027094b | |||
| 58ca38619d | |||
| dd24e9f9c3 | |||
| 54dcae7f29 | |||
| cf1f51bde1 | |||
| 6b85163603 | |||
| 18723d5717 | |||
| 245429db3e | |||
| 11b6fd41d3 | |||
| a1bff2168e | |||
| 5191f8908e | |||
| ee941f327f | |||
| 9ea7f416de | |||
| 9af9dcdc5f | |||
| b1684c2d90 | |||
| fee35dca32 | |||
| 642611f537 | |||
| cf29dae56d | |||
| 4de04fc1c3 | |||
| 716af4ca50 | |||
| 3079786663 | |||
| b0590c95d5 | |||
| e53e991a2e | |||
| 911615ea44 | |||
| 8d4b8d282c | |||
| 563b16f93a | |||
| dba46d5148 | |||
| 1993856936 | |||
| 306a55e2be | |||
| 8be7c7cd00 | |||
| 7a065d7778 | |||
| 82fa9d290b | |||
| a45fa6d20b | |||
| 4db5f608dd | |||
| 6bf5296918 | |||
| 90276c7529 | |||
| 7848f3a4fd | |||
| a6417bdf77 | |||
| 899e42473e | |||
| 8eefbf5b6e | |||
| 041c6e3099 | |||
| 883ec67d42 | |||
| f1ef560a76 | |||
| 1acd6ed183 | |||
| c48753f75f | |||
| 664dd7c83e | |||
| 1deb302729 | |||
| d3511f1f82 | |||
| 5cee39ed70 | |||
| a01f4a049e | |||
| ab7f0a77e6 | |||
| 055775fc2b | |||
| 60dbd82309 | |||
| 0ac1d5a523 | |||
| 5762735766 | |||
| e785f82833 | |||
| 1678298443 | |||
| 72dd13a80b | |||
| 3810dccfda | |||
| d9b3942469 | |||
| 32fc5dc9b4 | |||
| 22627468eb | |||
| 796bd4d8cb | |||
| edde747468 | |||
| 0f2aab4da3 | |||
| adc727c75d | |||
| 5e4000715b | |||
| eaea7d0c0f | |||
| d33c3d5b70 | |||
| 1ddaf62b73 | |||
| 4f84e31a83 | |||
| 3d08ecc22c | |||
| d1c6ce7757 | |||
| 28295aa865 | |||
| 0cd4f3ab1a | |||
| 46b7114062 | |||
| f97fb827ae | |||
| 5e0598b1c1 | |||
| 6771972adc | |||
| f96a9cf171 | |||
| 2c56caf2e1 | |||
| 31b9e86964 | |||
| c22c34a49a | |||
| 7c890a1657 | |||
| 1b6fb7a7d1 | |||
| f1c3e58465 | |||
| b16a804958 | |||
| 43e7990143 | |||
| 5dfff9413c | |||
| 04ebffbfa0 | |||
| 9ce4e3c0c4 | |||
| e60f0568d6 | |||
| 155253f259 | |||
| 8bbc8084c7 | |||
| abae6bb3a5 | |||
| 3e9774742b | |||
| 626001b90b | |||
| 08193fd17f | |||
| fa1b7312ba | |||
| 80505f1563 | |||
| 507cb3df5a | |||
| 288b72a048 | |||
| 7ff46d2b2c | |||
| 273c053f10 | |||
| 67fb0db945 | |||
| 0e7c3a32fd | |||
| 0edc34f470 | |||
| 5342d3e7ca | |||
| 7cb1bd7c4b | |||
| 3fa4d22446 | |||
| 31f51ff6ac | |||
| f903d71382 | |||
| 3ab6b48d2e | |||
| 1790267ff5 | |||
| d4becb9b5e | |||
| d0f0894da2 | |||
| b2c9a5cb34 | |||
| c64fe92afe | |||
| dad312daa4 | |||
| 800cc11c7a | |||
| 127fad78b5 | |||
| 53c1614c82 | |||
| 12267b7be8 | |||
| a9cf6f39b6 | |||
| 81e66be661 | |||
| 41b429f370 | |||
| ce7cfaaacc | |||
| 38f26e0c28 | |||
| 2d19efaef7 | |||
| c455c21230 | |||
| 7179d57046 | |||
| 1a30e1113e | |||
| ff0b180548 | |||
| 9e71da0f4e | |||
| 9329bc8115 | |||
| d6d3a608a5 | |||
| 40bb5eef87 | |||
| 40c0fa9eef | |||
| bdc005fe9d | |||
| cbd6275db3 | |||
| 44e59e2c8d | |||
| baf062fd03 | |||
| a5bbae8ae8 | |||
| 2b0ae5c167 | |||
| c5997171e9 | |||
| 4d242f3cc8 | |||
| bb7be3e83f | |||
| 4e9c33ec09 | |||
| 804fe63040 | |||
| d524366e3e | |||
| 1319db7119 | |||
| bb8752815c | |||
| 9acb16dc3a | |||
| 0618653f75 | |||
| 28734c5ee0 | |||
| b06127acf5 | |||
| 45515c2e34 | |||
| ed68854c80 | |||
| b5d11192ac | |||
| 0cba7dc760 | |||
| 50823e69a0 | |||
| 42a141ba40 | |||
| 63ae9cc3b9 | |||
| b2c436ede2 | |||
| 9ea3f79903 | |||
| dcf13b6797 | |||
| 04305cb5c0 | |||
| 41f55f2bd8 | |||
| 7647458381 | |||
| 86a1f8bfae | |||
| bd7aaf0a68 | |||
| ad82c98a91 | |||
| 8e42e768b3 | |||
| 251268f8be | |||
| aafe5690a0 | |||
| 3f752c7425 | |||
| 72cc52ee36 | |||
| 0ba25ee2d4 | |||
| 2a355d390e | |||
| 2ffce0d69e | |||
| 2680b74450 | |||
| 185ea478bb | |||
| 0489315a40 | |||
| 852769acea | |||
| 6a8c5fb561 | |||
| 59dc0d01c5 | |||
| 1c29da7ee7 | |||
| d1caf1a0d6 | |||
| 11bbbf4458 | |||
| 689ff3e831 | |||
| aefac98381 | |||
| 82821a2bb0 | |||
| 92c54cafad | |||
| 8c3819bfbf | |||
| 520fe25116 | |||
| 04a1802e1a | |||
| e5f19dfdf3 | |||
| ee8df1330f | |||
| d726d8d2a6 | |||
| 034121a105 | |||
| e6fa2ca17a | |||
| d3baa143ee | |||
| e19b3e3466 | |||
| e0eb20f29b | |||
| 544604a2bc | |||
| 11aa8be206 | |||
| 5787941bf2 | |||
| bf6ff1c549 | |||
| 161f2f3ca5 | |||
| fa1a6a0f2b | |||
| d2beb3b8fd | |||
| 16debe3cf4 | |||
| 2c36fc11d3 | |||
| f4631e561f | |||
| 328541a27b | |||
| 5f610ca091 | |||
| f1a23fda6d | |||
| 98f0e129b5 | |||
| 256513abb7 | |||
| 3a2c8e0bf9 | |||
| 8bde82dd7b | |||
| 05d1e0944e | |||
| c8662ac9af | |||
| d76acff7c8 | |||
| 3fb1bce368 | |||
| 9fbe7512a4 | |||
| 577c514ba2 | |||
| 80e25fe45c | |||
| fa7befd9ed | |||
| 75f146c6e1 | |||
| e391375049 | |||
| 07d3ca21ef | |||
| 4b32f370ba | |||
| ab72b0d6f2 | |||
| 53e1b73607 | |||
| 80dc0869c4 | |||
| 587b5eaa34 | |||
| 1c58bdc9e3 | |||
| 6358945168 | |||
| dec295fd05 | |||
| bfd7f1ad70 | |||
| 9415271d2b | |||
| 8e2c6945d8 | |||
| 3aa03faea6 | |||
| 84b02c8f7a | |||
| 3deb743692 | |||
| 1a626b4f5a | |||
| 70a5d4fe26 | |||
| f204503464 | |||
| 9eaad5e6e8 | |||
| ff7e86f0b1 | |||
| c48529693b | |||
| d98b13f004 | |||
| a3a633096c | |||
| f5b9687d3f | |||
| b888d279ea | |||
| c13f0b3054 | |||
| 6d2da52b4e | |||
| 10738a754b | |||
| 8c53b856d0 | |||
| ea15988234 | |||
| 009943e793 | |||
| 405a3886b4 | |||
| 13c81f276e | |||
| b4c7fe4499 | |||
| 1f38835e45 | |||
| 09c33e6f1a | |||
| ed9ed63e21 | |||
| cb839de7ef | |||
| 8da34990b5 | |||
| de6850bc36 | |||
| 48f88faa85 | |||
| e4282bc4f5 | |||
| 6db23078e7 | |||
| 018f4aafd0 | |||
| 9e46e7dbab | |||
| 277f5c25e5 | |||
| fd4b663c1e | |||
| a6628c42b8 | |||
| 0e55868093 | |||
| 0fe8140fd3 | |||
| 2f0b80a215 | |||
| 52e0eac4d0 | |||
| d758dbc8d4 | |||
| e88a84bfda | |||
| c14dc89280 | |||
| f995891a52 | |||
| 849b4f3a47 | |||
| 0dec42c2b0 | |||
| 466b52ccf1 | |||
| 226a397f3b | |||
| 06b6e15b91 | |||
| d1528650e4 | |||
| 7c2cf52efa | |||
| 6ec6bc7e3c | |||
| 24f886004d | |||
| 46e21f29a9 | |||
| 394703e819 | |||
| 91c7334a14 | |||
| 3aa987d3d3 | |||
| dad135cd11 | |||
| 268ad86bdd | |||
| d0f0356edc | |||
| 0aeb494a6b | |||
| dc4eaa2973 | |||
| 848577f339 | |||
| 00b4c9d33b | |||
| 712f212f7f | |||
| aee6234631 | |||
| 2f56bf6191 | |||
| 3833545c55 | |||
| 3e5c52b676 | |||
| ebd051eab7 | |||
| 3835300fe1 | |||
| 4a9b4d5fdc | |||
| 3eb64ccdab | |||
| c843eb1da4 | |||
| 7551604d77 | |||
| 320290861b | |||
| b79ef79242 | |||
| 7580a3f57b | |||
| bbdf259c1a | |||
| 8fe6e46c94 | |||
| 220939454b | |||
| 4a4cb57eaf | |||
| d83aaaefd6 | |||
| d38eb30cfe | |||
| 9e6cce7477 | |||
| 3f72362734 | |||
| 25ab2bb5a2 | |||
| 14eb2ccbaf | |||
| 711497d0cd | |||
| 7f91b64360 | |||
| fb23c7bc27 | |||
| c0a544f57e | |||
| 3ae69acaa8 | |||
| 4c387d328f | |||
| 408e4c68aa | |||
| fa198967d7 | |||
| 63bc65b3e5 | |||
| 0f4477ba32 | |||
| 5043e19db8 | |||
| db078e4ebb | |||
| 9e440c7635 | |||
| c0f9342899 | |||
| 08492cbf35 | |||
| f8e149a8f5 | |||
| 82fcaf5f89 | |||
| fd256fcc6f | |||
| e0dbd413dc | |||
| cb1870da7c | |||
| 2aa5d44df0 | |||
| 643d5a6ecb | |||
| d13b39609a | |||
| 2c628341a6 | |||
| cdc50df278 | |||
| 37f601f77b | |||
| 89668fc080 | |||
| baabae3164 | |||
| b381f0ce7f | |||
| eb5603c81d | |||
| 8ca9c7be0a | |||
| 3c0fd893c0 | |||
| af6bc5a603 | |||
| 010f8dabf8 | |||
| c30e5ad289 | |||
| 9c4842b8bd | |||
| d5549a3813 | |||
| f44a90c7c8 | |||
| 92bf8df8f8 | |||
| 91dd994cf1 | |||
| b58b583995 | |||
| b2044eb7a1 | |||
| 0924383bcd | |||
| 9cc007a2cb | |||
| 025e2fd6cb | |||
| 29393899e5 | |||
| 1e969e2b13 | |||
| ae2cb9af7b | |||
| d5ef01b144 | |||
| ff5d1c4655 | |||
| 5050537966 | |||
| f78c56a8c5 | |||
| 3fcb62db7a | |||
| 711571bd25 | |||
| 28d9c5dc12 | |||
| 4ca661dd74 | |||
| 722b3241de | |||
| 867336f7d5 | |||
| a3f38bc6ff | |||
| 3c4b538b7a | |||
| c8645d3f6f | |||
| 55a6c106ec | |||
| 60f5431155 | |||
| 1d85f77bb1 | |||
| 77d799fac6 | |||
| 54ffa699e1 | |||
| b4d7ee058b | |||
| 63ad1dec90 | |||
| 136601849c | |||
| 48eb0cc5ed | |||
| 4c71f68de5 | |||
| e4cd77f378 | |||
| 17f4b7c448 | |||
| 664743a975 | |||
| ebb3fcdc39 | |||
| 6730ec2f24 | |||
| 4eda181d1c | |||
| 6fc5ab2dad | |||
| 10b37677b2 | |||
| 06bdde0532 | |||
| 7eb40b05d1 | |||
| 88e8c1b9cb | |||
| 72757fe2ae | |||
| 7d290bfb94 | |||
| b5bf1bf3de | |||
| 096fb9957e | |||
| 6a7f440bc7 | |||
| 0a57cb7c3a | |||
| 9ef35d3ea1 | |||
| 5517204b3b | |||
| bad240240d | |||
| c31cf6793e | |||
| 9e5f6cc1d0 | |||
| f8c2a9615e | |||
| ddcccdfe41 | |||
| f09fae92e1 | |||
| 4617fe512b | |||
| 863064c5c0 | |||
| b3d80100dd | |||
| 8aea6b748e | |||
| c2c113b244 | |||
| a2aee39c5a | |||
| 1ff33f8f77 | |||
| 4c4217a546 | |||
| 88071a29c3 | |||
| 79622eb4b1 | |||
| 2365c868fa | |||
| ef441ff473 | |||
| 2a04daa811 | |||
| 73c9e60933 | |||
| 2e19c02b0c | |||
| 045030eecb | |||
| 71bb8255dc | |||
| e1d75f775c | |||
| 28026c02bf | |||
| f9ab0b81b5 | |||
| e83de0123c | |||
| 87d6c45b02 | |||
| 00092dabbd | |||
| 98b6ae41bf | |||
| ac81e26efe | |||
| f1c4d558ea | |||
| 37f089cc91 | |||
| ff8ca76a3d | |||
| fdb280d519 | |||
| 72e1cad416 | |||
| cc82c0bddc | |||
| b95a4dcea4 | |||
| 7c493a7326 | |||
| 1c25bc223a | |||
| f6acb36370 | |||
| 0add177ac2 | |||
| 828e13d9d1 | |||
| c885ee0bcf | |||
| 36ca6c3b75 | |||
| d8d010e382 | |||
| 4c799d0708 | |||
| 3d697809f0 | |||
| 3c0bf7dcbd | |||
| 005c030d79 | |||
| 8296f08eb0 | |||
| a7675479ac | |||
| 19aedcc8cb | |||
| e19d04bfd2 | |||
| e8bdc66c18 | |||
| 7cb2497595 | |||
| 571af08a3d | |||
| 7e5bc7b629 | |||
| 4effe393fc | |||
| 448099d9e7 | |||
| 9fceef362d | |||
| 0deba5d277 | |||
| 9b84b686ab | |||
| 56a8832876 | |||
| d886704fce | |||
| 2e9288da00 | |||
| 5863626db9 | |||
| 0e8c268630 | |||
| 108f01021e | |||
| a4d4fe949f | |||
| 1bb34d8d60 | |||
| 07f881e2b8 | |||
| 73cacded7e | |||
| 5a50cd04d4 | |||
| 44368d9f78 | |||
| d5088fae68 | |||
| d578376e9e | |||
| 1597b7eb1f | |||
| cdb1507c01 | |||
| bc3f7166da | |||
| 89e04829f9 | |||
| f84f97ab59 | |||
| 0bf709fefa | |||
| 975b243ffd | |||
| 051cba3082 | |||
| 2651ed943f | |||
| fada354813 | |||
| 785d03b07e | |||
| cede9a2b06 | |||
| 56b35315f7 | |||
| 3d6c548091 | |||
| 06239472cb | |||
| 089d298692 | |||
| 66a6622ad6 | |||
| 22ffd3743c | |||
| be8d582be5 | |||
| 838e589557 | |||
| d09413f794 | |||
| daa00a452c | |||
| e35b45e960 | |||
| a2e13d1b08 | |||
| 24a36b9ee8 | |||
| 50c5daeeaa | |||
| b07624beb9 | |||
| 05d6d5c94b | |||
| d8141e0aab | |||
| fed78ba0e9 | |||
| e468ed35e4 | |||
| f0f597e5f2 | |||
| 9d0679cda0 | |||
| f0e0518883 | |||
| b86f42b43c | |||
| 4dbf6565de | |||
| 0790748ec7 | |||
| d4815e2f6c | |||
| 7ce5352b02 | |||
| 12cfc405fc | |||
| 59f47b095e | |||
| 7fe16dd1a9 | |||
| 2045812d92 | |||
| a43f341968 | |||
| 46aa45593c | |||
| 3076044ccf | |||
| 2334e0b28e | |||
| bd729aa46b | |||
| 2c650da183 | |||
| 9ea06bffdb | |||
| abc1fd64b5 | |||
| 1a0cb9c0f1 | |||
| cb2420fafe | |||
| c75dd733e5 | |||
| 0ee066261f | |||
| 42da8c67f0 | |||
| 14a326cf40 | |||
| d2c710b269 | |||
| 5f0c4ffcaf | |||
| a7734d00b4 | |||
| 53d1ececc4 | |||
| fec0f35171 | |||
| 48332700da | |||
| 2bbac274c1 | |||
| 5dc21690da | |||
| 83c67a7b39 | |||
| 86e4670cfd | |||
| 72a1ba02d8 | |||
| ab40cd52b2 | |||
| e6a26104c3 | |||
| 0d046bb90e | |||
| 5b2d7ddb85 | |||
| 08acd3b614 | |||
| 0337e15e42 | |||
| 6047218125 | |||
| ddbbf70323 | |||
| e3a694548a | |||
| d2b48a9648 | |||
| 51bd6284d4 | |||
| 5dfd470170 | |||
| 6a9ad3ba15 | |||
| 14f780a1f4 | |||
| a30920ca31 | |||
| 22d96ae827 | |||
| f98e36a4bf | |||
| 4842da32fd | |||
| b47eecec3c | |||
| 1a20893c34 | |||
| 0451f9c1b9 | |||
| e1ddf12e7c | |||
| 2295c715e6 | |||
| 1619c96a4c | |||
| f017c234b5 | |||
| 1e67e9000e | |||
| c399d4299e | |||
| 9cfab342e5 | |||
| 4d095ea351 | |||
| 19c2b2028e | |||
| 0fdea710a7 | |||
| 9ade2d1dda | |||
| a57dbc2d7e | |||
| ef0ba18f75 | |||
| e70a077177 | |||
| 6caf72c5e9 | |||
| b3fbacfcd2 | |||
| 635298aeac | |||
| 617c2d3dec | |||
| 937c50e57a | |||
| 795eb69f8c | |||
| 9fd4d389df | |||
| 7a2dec7f63 | |||
| c8355d355a | |||
| 1c7d969cd1 | |||
| 1bb316da4f | |||
| b22a1f1ae1 | |||
| ff930573f3 | |||
| a828ed4ec6 | |||
| 6bffb4db59 | |||
| 6385660b09 | |||
| f5c35ebc7e | |||
| f0f972ae12 | |||
| 787167ee51 | |||
| 286d19237d | |||
| f61926171a | |||
| 70859e9341 | |||
| 5b841845b5 | |||
| f0a73a9463 | |||
| 7156f4be99 | |||
| a1b87e2548 | |||
| 8f96973726 | |||
| c53f7c4eda | |||
| bd5fd9f505 | |||
| 239ea3d346 | |||
| 13fcd85e69 | |||
| feeee094ce | |||
| b357732259 | |||
| 1559a6fc42 | |||
| 09202c09f6 | |||
| a250c248ae | |||
| 15b915b4b5 | |||
| 633baeb0a0 | |||
| 725e297370 | |||
| d9829b7b8f | |||
| 4d3fb9b46a | |||
| daa462184b | |||
| de6ead22a8 | |||
| 62ca9e1d1d | |||
| 24408382a6 | |||
| c49aa7857b | |||
| edd0fba141 | |||
| d90b167068 | |||
| 2c954da342 | |||
| 82e10ff27e | |||
| 9618ffeab5 | |||
| 5a4525f86e | |||
| 9125e1760b | |||
| 9a5c9689ee | |||
| d58df1d2dc | |||
| aedff17ef0 | |||
| 3b1a537441 | |||
| 9ba18f33ae | |||
| 197926a897 | |||
| b8cd0a3046 | |||
| c613f8eaa0 | |||
| fa7f64871f | |||
| 9d02a2d15f | |||
| 60d10fbfe4 | |||
| e9daf8a1b7 | |||
| 5088530227 | |||
| 5ca94a26f6 | |||
| bc9178038d | |||
| b308f6b3b7 | |||
| 9f29868ff1 | |||
| 00b3cf8e60 | |||
| 96b8ebab04 | |||
| 803cbb84a5 | |||
| 11b9351e06 | |||
| 89741b9e95 | |||
| 41cf1c4216 | |||
| a6c80ddbc9 | |||
| fcac70f7de | |||
| 0e78422f35 | |||
| 104db56b48 | |||
| 9108aa6948 | |||
| f23f226475 | |||
| 3cd74e216a | |||
| 2c58605dc1 | |||
| 9a487c86e6 | |||
| 85191b261a | |||
| 83ff6380c2 | |||
| e40694b76b | |||
| 6878422e4a | |||
| 80d9153873 | |||
| eda41a4363 | |||
| dc07d95cd1 | |||
| 5603acbd3b | |||
| 55ca190585 | |||
| d9f4c52f8c | |||
| 44cd484def | |||
| f9390c9cc6 | |||
| 670fcccf73 | |||
| 8f4480a12e | |||
| c2d3126ade | |||
| a8b4c263e5 | |||
| 82d441db26 | |||
| 19dee8e43b | |||
| a90c8d569a | |||
| 7ae3eb6e8f | |||
| 1ddf71426c | |||
| 071e6686d9 | |||
| ca1db18380 | |||
| f24df1951a | |||
| 779228cda9 | |||
| 856d212aed | |||
| e8c59924e9 | |||
| be87c9831c | |||
| b15afea304 | |||
| c35be742e6 | |||
| adc3d1330b | |||
| d02c846890 | |||
| 14b04e9e4c | |||
| 2e0f8d0cb4 | |||
| 25d5fcfc60 | |||
| 5d9bbcd713 | |||
| d960b02466 | |||
| 515863f94e | |||
| 6eb8701145 | |||
| 2690654695 | |||
| b6577a4e3b | |||
| df378430c1 | |||
| 8009e55246 | |||
| 197fcf3829 | |||
| 599d572f3a | |||
| 63c14f7c4f | |||
| bebfe3131e | |||
| 08cb529fc3 | |||
| f281b3d09d | |||
| 77f830d5ee | |||
| 15e8cad1c3 | |||
| 07a75228ce | |||
| 04e2665920 | |||
| 87bc647936 | |||
| 2acbdb70f0 | |||
| c987abac35 | |||
| 150d8edcf0 | |||
| 28708baf62 | |||
| 01fc68ae92 | |||
| d885072f80 | |||
| afe5a31d90 | |||
| c4cbbe3d15 | |||
| 3a5e8bca5a | |||
| 3b9ca219c9 | |||
| be349ea90e | |||
| 14d1b9410e | |||
| 58b1672ab8 | |||
| abf9e04f63 | |||
| 7575935cf5 | |||
| d7b2d4193d | |||
| d4fd8e504b | |||
| 59a49fe68f | |||
| 18d57b2d27 | |||
| 8ec1217dec | |||
| f94a3025fe |
7
.editorconfig
Normal file
7
.editorconfig
Normal file
@ -0,0 +1,7 @@
|
||||
root = true
|
||||
|
||||
[**]
|
||||
indent_style = tab
|
||||
tab_width = 4
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
78
.github/workflows/viewer-build.yml
vendored
Normal file
78
.github/workflows/viewer-build.yml
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
name: Viewer build
|
||||
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
#push:
|
||||
# branches: [ "master" ]
|
||||
# tags: v**
|
||||
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
# XXX the mac build is broken for now -- running blind as I do
|
||||
# not have a mac to test on, but I will at least fix the
|
||||
# build...
|
||||
#os: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||
os: [ ubuntu-latest, windows-latest ]
|
||||
#os: [ macos-latest ]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: ./Viewer
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v6
|
||||
|
||||
|
||||
- name: Windows-specific
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
run: |
|
||||
choco install wget zip
|
||||
|
||||
- name: Ubuntu-specific
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
run: |
|
||||
# fixes an issue when running npx sandboxing...
|
||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
|
||||
- name: MacOS-specific
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
run: |
|
||||
brew install gnu-sed zip
|
||||
|
||||
echo PATH="$HOMEBREW_PREFIX/opt/zip/bin:$PATH" >> $GITHUB_ENV
|
||||
|
||||
|
||||
#- name: Env
|
||||
# run: |
|
||||
# echo "VERSION=$(make version)" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: Pre-Build
|
||||
run: make clean-all dev
|
||||
|
||||
- name: Build
|
||||
run: make dist
|
||||
|
||||
- name: Release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# XXX need to create if this does not exist...
|
||||
#gh release create release-latest -t "TEST"
|
||||
gh release upload release-latest ./dist/*
|
||||
|
||||
|
||||
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
.gitignore
|
||||
*.pem
|
||||
*.sw[po]
|
||||
*.py[co]
|
||||
build
|
||||
Session.vim
|
||||
*.bak
|
||||
*.dll
|
||||
*.exe
|
||||
*.pak
|
||||
*.dat
|
||||
*.zip
|
||||
node_modules
|
||||
targets
|
||||
*.egg-info
|
||||
159
Archive/README.md
Normal file
159
Archive/README.md
Normal file
@ -0,0 +1,159 @@
|
||||
# Photo archive template directory
|
||||
|
||||
The contents of this are copied to each archive drive root.
|
||||
|
||||
```shell
|
||||
$ cp -R ./media PATH_TO_ARCHIVE_DRIVE
|
||||
```
|
||||
|
||||
The scripts are stored with the archive for generational compatibility,
|
||||
both building and documenting the structure the archive was created with.
|
||||
|
||||
|
||||
## Scripts
|
||||
|
||||
```
|
||||
media
|
||||
├── img
|
||||
│ └── my
|
||||
│ └── work
|
||||
│ ├── sync-flash.sh
|
||||
│ ├── process-archive.sh
|
||||
│ ├── compress-archive.sh
|
||||
│ └── update-exif.sh
|
||||
├── README.md
|
||||
└── tree.sh
|
||||
```
|
||||
|
||||
### `README.md`
|
||||
|
||||
A basic introductory description. This is here to introduce a new user
|
||||
to the archive structure and basics.
|
||||
|
||||
|
||||
|
||||
### `sync-archive.sh`
|
||||
|
||||
Ingest media into the archive and prepare it for further steps in the
|
||||
workflow
|
||||
|
||||
This script can be run interactively:
|
||||
```shell
|
||||
$ ./sync-archive.sh
|
||||
```
|
||||
|
||||
This will:
|
||||
- Create the directory structure
|
||||
(see: [Archive directory structure](#archive-direcotry-structure))
|
||||
- Copy and verify the contents of 1 or more external media
|
||||
to the archive directory
|
||||
- Prepare the archive for further work via `process-archive.sh`
|
||||
- Compress the archive via `compress-archive.sh`
|
||||
|
||||
|
||||
### `process-archive.sh`
|
||||
|
||||
Process the syncronized data for use with [ImageGrid.Viewer]()
|
||||
|
||||
```shell
|
||||
$ ./process-archive.sh [FLAGS] PATH
|
||||
```
|
||||
|
||||
This will:
|
||||
- Extract previews from RAW files
|
||||
- Extract metadata
|
||||
- Build index
|
||||
|
||||
|
||||
|
||||
|
||||
### `compress-archive.sh`
|
||||
|
||||
Compress archived files.
|
||||
|
||||
```shell
|
||||
$ ./compress-archive.sh [FLAGS] PATH
|
||||
```
|
||||
|
||||
This is designed to selectively compress uncompressed raw files (Sony) achiving +/- 2x compression factor.
|
||||
|
||||
By default this uses filesystem compression, supporting ntfs (on windows) and btrfs transparent native compression, but can be configured to use various archiving formats.
|
||||
|
||||
|
||||
|
||||
### `update-exif.sh`
|
||||
|
||||
Update EXIF of output previews from corresponding .psd / RAW files.
|
||||
|
||||
```shell
|
||||
$ ./update-exif.sh [FLAGS] PATH
|
||||
```
|
||||
|
||||
|
||||
### `tree.sh`
|
||||
|
||||
Generte and check arcive file list.
|
||||
|
||||
This provides a basic and fast way to check high level tree consistency
|
||||
against mostly humn-error.
|
||||
|
||||
|
||||
|
||||
## Archive directory structure
|
||||
|
||||
|
||||
```
|
||||
media
|
||||
├── img
|
||||
│ ├── my
|
||||
│ │ └── work
|
||||
│ │ ├── - 20240310 - shoot directory (multi flash card)
|
||||
│ │ │ ├── 20240310.001
|
||||
│ │ │ │ ├── ...
|
||||
│ │ │ │ └── preview (RAW)
|
||||
│ │ │ ├── 20240310.002
|
||||
│ │ │ │ ├── ...
|
||||
│ │ │ │ └── preview (RAW)
|
||||
│ │ │ ├── ...
|
||||
│ │ │ └── preview (RAW)
|
||||
│ │ ├── - 20240310.001 - shoot directory (single flash card)
|
||||
│ │ │ ├── ...
|
||||
│ │ │ └── preview (RAW)
|
||||
│ │ ├── 20240310 - shoot directory (fully sorted)
|
||||
│ │ │ └── ...
|
||||
│ │ └── ...
|
||||
│ └── others
|
||||
│ └── ...
|
||||
├── video
|
||||
│ └── ...
|
||||
├── ...
|
||||
└── tree.sh
|
||||
```
|
||||
|
||||
Index root directory (single source media)
|
||||
```
|
||||
<date>.<index> - <info>/
|
||||
```
|
||||
|
||||
Index root (multiple source media)
|
||||
```
|
||||
<date> - <info>/
|
||||
```
|
||||
|
||||
Each synchronised media is stored in:
|
||||
```
|
||||
<date> - <info>/<date>.<index>/
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
Leading `"-"` indicates a not fully sorted shoot.
|
||||
|
||||
```
|
||||
- <date> - <info>/
|
||||
```
|
||||
|
||||
This is the default as created by `sync-flash.sh`, renaming (removing the leading `"- "`) should be done by the user.
|
||||
|
||||
|
||||
5
Archive/media/README.md
Normal file
5
Archive/media/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Base photo archive directory
|
||||
|
||||
This tree is created based on the specification from:
|
||||
https://github.com/flynx/ImageGrid/blob/master/Archive/README.md
|
||||
|
||||
110
Archive/media/img/my/work/compress-archive.sh
Executable file
110
Archive/media/img/my/work/compress-archive.sh
Executable file
@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# TODO make this runnable from anywhere...
|
||||
# - prepend paths with './' only if local/relative
|
||||
|
||||
BASE_PATH=.
|
||||
|
||||
|
||||
ARCH_BZIP2='bzip2 -v {}'
|
||||
ARCH_GZIP='gzip -v {}'
|
||||
# XXX should we cygpath -w all the inputs???
|
||||
|
||||
OS="$(uname -s)"
|
||||
if [[ "$OS" =~ Linux.* ]] ; then
|
||||
ARCH_FS='btrfs filesystem defragment -czstd -vf {}'
|
||||
else
|
||||
ARCH_FS='compact /c /exe:lzx {}'
|
||||
fi
|
||||
|
||||
# default...
|
||||
ARCH=$ARCH_FS
|
||||
|
||||
|
||||
EXT=ARW
|
||||
|
||||
# HACK: this is here to avoid using windows find...
|
||||
PATH=/bin:$PATH
|
||||
|
||||
|
||||
|
||||
printhelp(){
|
||||
echo "Usage: `basename $0` [ARGUMENTS] [PATH]"
|
||||
echo
|
||||
echo "Arguments:"
|
||||
echo " -h --help - print this help and exit."
|
||||
echo
|
||||
echo " -bz -bzip2 - use bzip2 to compress`[[ $ARCH == $ARCH_BZIP2 ]] && echo " (default)" || echo ""`."
|
||||
echo " -gz -gzip - use gzip to compress`[[ $ARCH == $ARCH_GZIP ]] && echo " (default)" || echo ""`."
|
||||
echo " -fs - use filesystem compression`[[ $ARCH == $ARCH_FS ]] && echo " (default)" || echo ""`."
|
||||
echo
|
||||
echo " -ext EXT - set file extension to compress (default: ${EXT})"
|
||||
echo " NOTE: only one -ext is supported now".
|
||||
echo
|
||||
}
|
||||
|
||||
# process args...
|
||||
while true ; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
printhelp
|
||||
exit
|
||||
;;
|
||||
|
||||
# archivers...
|
||||
-bz|--bzip2)
|
||||
ARCH=$ARCH_BZIP2
|
||||
shift
|
||||
;;
|
||||
-gz|--gzip)
|
||||
ARCH=$ARCH_GZIP
|
||||
shift
|
||||
;;
|
||||
-fs)
|
||||
ARCH=$ARCH_FS
|
||||
shift
|
||||
;;
|
||||
|
||||
# extension to compress...
|
||||
--ext)
|
||||
EXT=$2
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# get path...
|
||||
if [ "$1" ] ; then
|
||||
BASE_PATH=$1
|
||||
fi
|
||||
|
||||
# check if archiver exists...
|
||||
if ! which ${ARCH/ *} > /dev/null 2>&1 ; then
|
||||
echo "$0: ${ARCH/ *}: command not found." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# do the work...
|
||||
#find "$BASE_PATH" -name \*.${EXT} -exec ${ARCH} \; \
|
||||
# && echo done.
|
||||
|
||||
IFS=$'\n'
|
||||
ARWs=($(find "$BASE_PATH" -name \*.${EXT}))
|
||||
COUNT=${#ARWs[@]}
|
||||
DONE=1
|
||||
for f in "${ARWs[@]}" ; do
|
||||
echo $f
|
||||
printf 'Doing: %d/%d (%d%%)\r' $DONE $COUNT $((100 * $DONE / $COUNT))
|
||||
eval "${ARCH/\{\}/\"$f\"}" > /dev/null
|
||||
DONE=$((DONE + 1))
|
||||
done
|
||||
echo done.
|
||||
|
||||
|
||||
# vim:set nowrap nospell :
|
||||
354
Archive/media/img/my/work/process-archive.sh
Executable file
354
Archive/media/img/my/work/process-archive.sh
Executable file
@ -0,0 +1,354 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
#######################################################################
|
||||
#
|
||||
# This does not care about actual topology of the archive directory
|
||||
# that is passed it, it will find all the supported raw files and
|
||||
# create the apropriate directories one level up.
|
||||
#
|
||||
#
|
||||
#######################################################################
|
||||
|
||||
# CPU threads to keep free...
|
||||
KEEP_FREE=2
|
||||
|
||||
THREADS=`cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l`
|
||||
if [ $KEEP_FREE ] && (( THREADS > KEEP_FREE )) ; then
|
||||
THREADS=$((THREADS - KEEP_FREE))
|
||||
fi
|
||||
|
||||
|
||||
# TODO make this runnable from anywhere...
|
||||
# - prepend paths with './' only if local/relative
|
||||
|
||||
# HACK: this is here to avoid using windows find...
|
||||
PATH=/bin:$PATH
|
||||
|
||||
printhelp(){
|
||||
echo "Usage: `basename $0` [ARGUMENTS] [ARCHIVE_ROOT]"
|
||||
echo
|
||||
echo "Arguments:"
|
||||
echo " -h --help - print this help and exit."
|
||||
echo " --common-previews PATH"
|
||||
echo " - build a single preview set at PATH."
|
||||
echo " -c - build a single common path at ARCHIVE_ROOT;"
|
||||
echo " this is a shorthand for: --common-path '.'."
|
||||
echo " -l --low-res-previews"
|
||||
echo " - generate low resolution previews and store"
|
||||
echo " original previews in \"hi-res (RAW)\"."
|
||||
echo
|
||||
echo " --skip-archive - skip creating archive structure (use: exiftool)."
|
||||
echo " --skip-previews - skip creating previews (use: vips)."
|
||||
echo " --skip-cache - skip creating cache (use: buildcache)."
|
||||
echo " --skip-all - same as setting all of the above."
|
||||
echo
|
||||
echo "NOTE: common preview path is relative to ARCHIVE_ROOT."
|
||||
echo "NOTE: if no ARCHIVE_ROOT is passed then this will process all"
|
||||
echo " directories in cwd."
|
||||
# XXX this is how exiftool does things, need to figure out a workaround...
|
||||
echo "NOTE: this expects the RAW files to be located at least one level"
|
||||
echo " down the ARCHIVE_ROOT to make room for the metadata and preview"
|
||||
echo " directories."
|
||||
echo " If any raw files are found in the ARCHIVE_ROOT directly this"
|
||||
echo " will create the preview and metadata directly one level above"
|
||||
echo " that."
|
||||
echo
|
||||
}
|
||||
|
||||
# process args...
|
||||
while true ; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
printhelp
|
||||
exit
|
||||
;;
|
||||
|
||||
-c)
|
||||
COMMON_PREVIEWS="."
|
||||
shift
|
||||
;;
|
||||
--common-previews)
|
||||
COMMON_PREVIEWS="${2}"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-l|--low-res-previews)
|
||||
LOW_RES_PREVIEWS=1
|
||||
shift
|
||||
;;
|
||||
|
||||
--skip-archive)
|
||||
SKIP_ARCHIVE=yes
|
||||
echo skipping making archive...
|
||||
shift
|
||||
;;
|
||||
--skip-previews)
|
||||
SKIP_PREVIEWS=yes
|
||||
echo skipping making previews...
|
||||
shift
|
||||
;;
|
||||
--skip-cache)
|
||||
SKIP_CACHE=yes
|
||||
echo skipping making cache...
|
||||
shift
|
||||
;;
|
||||
--skip-all)
|
||||
SKIP_ARCHIVE=yes
|
||||
echo skipping making archive...
|
||||
SKIP_PREVIEWS=yes
|
||||
echo skipping making previews...
|
||||
SKIP_CACHE=yes
|
||||
echo skipping making cache...
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$1" ] ; then
|
||||
ARCHIVE_ROOT="."
|
||||
else
|
||||
ARCHIVE_ROOT="$1"
|
||||
fi
|
||||
|
||||
echo "Doing: \"$ARCHIVE_ROOT\""
|
||||
|
||||
|
||||
if [ $LOW_RES_PREVIEWS ] ; then
|
||||
RAW_PREVIEW_DIR="hi-res (RAW)"
|
||||
else
|
||||
RAW_PREVIEW_DIR="preview (RAW)"
|
||||
fi
|
||||
|
||||
|
||||
PROCESSED_PREVIEW_DIR="preview"
|
||||
METADATA_DIR="metadata"
|
||||
|
||||
PROCESSED_PREVIEW_NAME="%-:1d/${PROCESSED_PREVIEW_DIR}/%f.jpg"
|
||||
PREVIEW_NAME="%-:1d/${RAW_PREVIEW_DIR}/%f.jpg"
|
||||
JSON_NAME="%-:1d/${METADATA_DIR}/%f.json"
|
||||
|
||||
|
||||
# TODO do a version of this using exiv2...
|
||||
# - to be more flexible...
|
||||
# - check speed...
|
||||
# - give the user more options...
|
||||
# TODO use dcraw to extract/generate previews if we could not get any
|
||||
# via exiftool
|
||||
# dcraw -e $RAW
|
||||
# - try and extract a preview
|
||||
# - creates a file: $RAW-thumb.jpg
|
||||
# dcraw -c $RAW | pnmtojpeg -quality=90 > $JPG
|
||||
# - process raw and convert to jpeg (slow)
|
||||
# TODO ignore raw images located in the ARCHIVE_ROOT directly...
|
||||
|
||||
# XXX need to also copy jpg originals to the preview dir (things that
|
||||
# were shot in jpeg in-camera)...
|
||||
# XXX need to prevent overwriting of unchanged exif data...
|
||||
# when file exists??
|
||||
# XXX add PSD metadata extraction...
|
||||
# -execute '-FileModifyDate<DateTimeOriginal' -tagsfromfile @ \
|
||||
# -srcfile "$PROCESSED_PREVIEW_NAME" -overwrite_original \
|
||||
# XXX keep file dates...
|
||||
|
||||
if [ -z $SKIP_ARCHIVE ] ; then
|
||||
exiftool -if '$jpgfromraw' -b -jpgfromraw -w "$PREVIEW_NAME" \
|
||||
-execute -if '$previewimage' -b -previewimage -w "$PREVIEW_NAME" \
|
||||
-execute '-FileModifyDate<DateTimeOriginal' -addtagsfromfile @ \
|
||||
-srcfile "$PREVIEW_NAME" '-all>all' '-xmp' \
|
||||
-overwrite_original \
|
||||
-execute -j -G -w "$JSON_NAME" \
|
||||
-common_args --ext jpg -r "./$ARCHIVE_ROOT" -progress
|
||||
fi
|
||||
|
||||
|
||||
SIZE=900
|
||||
|
||||
COMPRESSION=90
|
||||
|
||||
# XXX remove this in production...
|
||||
PATH=$PATH:/mnt/d/Program\ Files/vips/bin/
|
||||
|
||||
|
||||
|
||||
# makepreview SIZE IN [OUT [SIZE [COMPRESSION]]]
|
||||
#
|
||||
# NOTE: SIZE and COMPRESSION will be set as follows (in order of priority):
|
||||
# - explicit argument
|
||||
# - global env var, if set
|
||||
# - hardcoded default value
|
||||
#
|
||||
# TODO:
|
||||
# - make this run in parallel
|
||||
# - add option --mixed-previews and check preview size once per dir
|
||||
# if it is not set...
|
||||
#
|
||||
# XXX cahnge global var names to be less generic...
|
||||
makepreview(){
|
||||
|
||||
# arguments...
|
||||
SIZE="$1"
|
||||
IN="$2"
|
||||
# output dir...
|
||||
if [ -z $OUT ] ; then
|
||||
# default...
|
||||
# XXX is this correct??? (not generic enough...)
|
||||
OUT="${IN/hi-res\ /preview }"
|
||||
else
|
||||
OUT="$3"
|
||||
fi
|
||||
# size...
|
||||
if [ -z $4 ] ; then
|
||||
if [ -z $SIZE ] ; then
|
||||
# default...
|
||||
SIZE=900
|
||||
fi
|
||||
else
|
||||
SIZE=$4
|
||||
fi
|
||||
# compression...
|
||||
if [ -z $5 ] ; then
|
||||
if [ -z $COMPRESSION ] ; then
|
||||
# default...
|
||||
COMPRESSION=90
|
||||
fi
|
||||
else
|
||||
COMPRESSION=$5
|
||||
fi
|
||||
|
||||
|
||||
# create preview dir if it does not already exist...
|
||||
DIR="`dirname \"./${OUT}\"`"
|
||||
if ! [ -e "./$DIR" ] ; then
|
||||
mkdir -p "./$DIR"
|
||||
fi
|
||||
|
||||
# create previews...
|
||||
if ! [ -e "./${OUT}" ] ; then
|
||||
|
||||
# get source size...
|
||||
W=$(vips im_header_int width "$IN")
|
||||
H=$(vips im_header_int height "$IN")
|
||||
|
||||
# NOTE: vips appends nasty unprintable \r's to values, so we need to clean them out...
|
||||
W=${W//[![:digit:]]/}
|
||||
H=${H//[![:digit:]]/}
|
||||
|
||||
# calculate the factor...
|
||||
FACTOR=$(echo "scale = 4; if($H > $W) s = $H else s = $W ; s / $SIZE" | bc -l)
|
||||
|
||||
# NOTE: bash does not do float comparisons so we cheat again ;)
|
||||
TOO_SMALL=$(echo "if($FACTOR <= 1) s = 1 else s = 0 ; s" | bc -l)
|
||||
|
||||
# the input is smaller than target size, copy as-is...
|
||||
if [[ $TOO_SMALL == 1 ]] ; then
|
||||
echo "$IN: Too small, copying as-is..."
|
||||
|
||||
cp "./$IN" "./$OUT"
|
||||
|
||||
# shrink...
|
||||
else
|
||||
echo "($FACTOR): ${OUT}:${COMPRESSION}"
|
||||
|
||||
vips im_shrink "./$IN" "./${OUT}:${COMPRESSION}" $FACTOR $FACTOR 2> /dev/null
|
||||
fi
|
||||
|
||||
touch -c -r "./$IN" "./${OUT}"
|
||||
|
||||
else
|
||||
echo "File already exists: ${OUT}"
|
||||
fi
|
||||
}
|
||||
|
||||
export SIZE COMPRESSION
|
||||
export -f makepreview
|
||||
|
||||
cd "./${ARCHIVE_ROOT}"
|
||||
|
||||
|
||||
|
||||
# make low-res previews...
|
||||
if [ -z $SKIP_PREVIEWS ] || [ $LOW_RES_PREVIEWS ] ; then
|
||||
#find . -path '*hi-res (RAW)/*.jpg' -exec bash -c 'makepreview "$SIZE" "{}"' \;
|
||||
find . -path '*hi-res (RAW)/*.jpg' -print0 \
|
||||
| xargs -0 -n 1 -P $THREADS -I {} bash -c 'makepreview "$SIZE" "{}"'
|
||||
fi
|
||||
|
||||
# collect previews to one location...
|
||||
# XXX test!!!
|
||||
if ! [ -z "$COMMON_PREVIEWS" ] ; then
|
||||
if ! [ -e "./$COMMON_PREVIEWS" ] ; then
|
||||
mkdir -p "./$COMMON_PREVIEWS"
|
||||
fi
|
||||
#if [ -z $TOTAL ] ; then
|
||||
# export TOTAL=`find . -path '*hi-res (RAW)/*.jpg' | wc -l`
|
||||
#fi
|
||||
# XXX BUG: this does not rename if target exists...
|
||||
find . -type d \
|
||||
-name 'preview (RAW)' \
|
||||
-print \
|
||||
-exec cp --backup=t -rl "{}" "./$COMMON_PREVIEWS" \;
|
||||
#-exec rm -rf "./$d"
|
||||
|
||||
# cleanup filenames... (HACK)
|
||||
# image.jpg -> image_3.jpg
|
||||
# image.jpg.~3~ -> image_2.jpg
|
||||
# image.jpg.~2~ -> image_1.jpg
|
||||
# image.jpg.~1~ -> image.jpg
|
||||
#
|
||||
i=0
|
||||
while true ; do
|
||||
i=$((i + 1))
|
||||
images=("$COMMON_PREVIEWS/preview (RAW)/"*.~$i~)
|
||||
|
||||
# break if no matches...
|
||||
if ! [ -e "${images[0]}" ] ; then
|
||||
break
|
||||
fi
|
||||
|
||||
for img in "${images[@]}" ; do
|
||||
# decrement...
|
||||
# image.jpg.~(N)~ -> image_(N-1).jpg
|
||||
mv "$img" "${img/.jpg.~${i}~/_$((i-1)).jpg}"
|
||||
|
||||
# next image does not exist...
|
||||
# image.jpg -> image_(N).jpg
|
||||
# image_0.jpg -> image.jpg
|
||||
if ! [ -e "${img/.jpg.~${i}~/.jpg.~$((i+1))~}" ] ; then
|
||||
mv "${img/.jpg.~${i}~/.jpg}" "${img/.jpg.~${i}~/_$((i)).jpg}"
|
||||
mv "${img/.jpg.~${i}~/_0.jpg}" "${img/.jpg.~${i}~/.jpg}"
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# build cache...
|
||||
if [ -z $SKIP_CACHE ] ; then
|
||||
|
||||
# ig...
|
||||
if ! [ -z `command -v ig` ] ; then
|
||||
CACHE="ig init"
|
||||
# buildcache (legacy)...
|
||||
elif [ -z `command -v buildcache` ] ; then
|
||||
# a little tweak to make build cache work...
|
||||
export PYTHONIOENCODING=UTF-8
|
||||
CACHE=buildcache
|
||||
fi
|
||||
|
||||
#if [ -z $TOTAL ] ; then
|
||||
# export TOTAL=`find . -path '*hi-res (RAW)/*.jpg' | wc -l`
|
||||
#fi
|
||||
if ! [ -z "$COMMON_PREVIEWS" ] && [ -e "./$COMMON_PREVIEWS/preview (RAW)" ] ; then
|
||||
$CACHE "./$COMMON_PREVIEWS/preview (RAW)"
|
||||
else
|
||||
find . -type d -name 'preview (RAW)' -exec $CACHE "{}" \;
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# vim:set nowrap nospell :
|
||||
401
Archive/media/img/my/work/sync-flash.sh
Executable file
401
Archive/media/img/my/work/sync-flash.sh
Executable file
@ -0,0 +1,401 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# TODO add option to continue last sync...
|
||||
# - need to store parsable log
|
||||
# - settings
|
||||
# - started/completed operations
|
||||
# - all operations should be resumable
|
||||
|
||||
VERSION=1.0
|
||||
|
||||
DATE=`date +%Y%m%d`
|
||||
COUNT=1
|
||||
TITLE=""
|
||||
|
||||
RSYNC=rsync
|
||||
#RSYNCFLAGS="-arptgoA --info=progress2,flist --human-readable"
|
||||
RSYNCFLAGS="-arpt --info=progress2,flist --human-readable"
|
||||
|
||||
CP=cp
|
||||
CPFLAGS=-Rpfv
|
||||
|
||||
# override default...
|
||||
COPY=$RSYNC
|
||||
COPYFLAGS=$RSYNCFLAGS
|
||||
|
||||
# NOTE: jdupes reports progress to stderr and output to stdout...
|
||||
# XXX need to test if this exists...
|
||||
VERIFY=jdupes
|
||||
VERIFYFLAGS="-r -u -I"
|
||||
DO_VERIFY=
|
||||
|
||||
|
||||
COMPRESS=./compress-archive.sh
|
||||
DO_COMPRESS=1
|
||||
|
||||
SNAPSHOT=../../../../snapshot.sh
|
||||
|
||||
|
||||
# Config file to contain all the default settings...
|
||||
# XXX not sure if this is a good idea...
|
||||
# ...should we also check file sec?
|
||||
##CONFIG=.sync-flash.rc
|
||||
##if ! [ -z $CONFIG ] && [ -e ~/$CONFIG ] ; then
|
||||
## # XXX executing an external file...
|
||||
## source ~/$CONFIG
|
||||
##fi
|
||||
|
||||
|
||||
# base mount dir...
|
||||
BASES=(
|
||||
/Volumes
|
||||
/cygdrive
|
||||
/run/media/$USER
|
||||
/mnt
|
||||
~/mnt
|
||||
)
|
||||
i=0
|
||||
for d in "${BASES[@]}"; do
|
||||
# normalize...
|
||||
BASES[$i]="${d%/}/"
|
||||
# remove non-existant bases...
|
||||
if ! [ -d "$d" ] ; then
|
||||
unset BASES[$i]
|
||||
elif [ -z $BASE ] ; then
|
||||
BASE=$d
|
||||
fi
|
||||
i=$(( i + 1 ))
|
||||
done
|
||||
|
||||
|
||||
while true ; do
|
||||
case "$1" in
|
||||
-h|-help|--help)
|
||||
echo "usage: `basename $0` FLAGS DRIVE [TITLE]"
|
||||
echo
|
||||
echo " -h|-help print this message and exit."
|
||||
echo " -m|-multi single base, multiple sub dirs"
|
||||
echo " for multiple flash cards in a"
|
||||
echo " single shoot."
|
||||
echo " -l|-last last flash card in set, run"
|
||||
echo " process-archive.sh after copying."
|
||||
echo " -b|-base BASE the base dir to look for drives in"
|
||||
echo " default: $BASE"
|
||||
echo " --rsync use rsync (default)"
|
||||
echo " --cp use cp"
|
||||
if ! [ -z $VERIFY ] ; then
|
||||
echo " --verify toggle copy verification"
|
||||
echo " default: `[[ $DO_VERIFY ]] && echo "on" || echo "off"`"
|
||||
fi
|
||||
if ! [ -z $COMPRESS ] ; then
|
||||
echo " --compress toggle archive compression"
|
||||
echo " default: `[[ $DO_COMPRESS ]] && echo "on" || echo "off"`"
|
||||
fi
|
||||
# notes...
|
||||
echo
|
||||
if ! [ -z $COMPRESS ] ; then
|
||||
echo "NOTE: the index is fully usable during the compression stage"
|
||||
fi
|
||||
echo "NOTE: cp under Cygwin may messup permissions, use rsync."
|
||||
echo
|
||||
exit
|
||||
;;
|
||||
|
||||
-i|--interactive)
|
||||
INTERACTIVE=1
|
||||
shift
|
||||
;;
|
||||
-m|-multi|--multi)
|
||||
MULTI=1
|
||||
shift
|
||||
;;
|
||||
-l|-last|--last)
|
||||
LAST=1
|
||||
shift
|
||||
;;
|
||||
-b|-base|--base)
|
||||
BASE=$2
|
||||
shift 2
|
||||
;;
|
||||
-cp|--cp)
|
||||
COPY=cp
|
||||
COPYFLAGS=-Rpfv
|
||||
shift
|
||||
break
|
||||
;;
|
||||
-rsync|--rsync)
|
||||
COPY=$RSYNC
|
||||
COPYFLAGS=$RSYNCFLAGS
|
||||
shift
|
||||
break
|
||||
;;
|
||||
-verify|--verify)
|
||||
DO_VERIFY=`[[ $DO_VERIFY ]] && echo "" || echo 1`
|
||||
shift
|
||||
break
|
||||
;;
|
||||
-compress|--compress)
|
||||
DO_COMPRESS=`[[ $DO_COMPRESS ]] && echo "" || echo 1`
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if ! [ -z "$2" ] ; then
|
||||
TITLE=" - $2"
|
||||
fi
|
||||
|
||||
BASE=${BASE%/}/
|
||||
DRIVE=${1}
|
||||
|
||||
__BASE=$BASE
|
||||
while true ; do
|
||||
BASE=$__BASE
|
||||
if [[ $INTERACTIVE || ! $DRIVE ]] ; then
|
||||
INTERACTIVE=1
|
||||
echo
|
||||
echo "Select/toggle an option:"
|
||||
if [ -z $MULTI_STARTED ] ; then
|
||||
if [[ $MULTI ]] ; then
|
||||
echo "0) Multiple flash cards"
|
||||
else
|
||||
echo "0) Single flash card"
|
||||
fi
|
||||
else
|
||||
echo "0) Build after this flash card: `[[ $LAST ]] && echo "yes" || echo "no"`"
|
||||
fi
|
||||
echo "1) Directoy description is: \"$TITLE\""
|
||||
echo "a-z|name) Type a drive letter, mount name in $BASE or path and start"
|
||||
echo " (paths must start with \"/\", \"./\" or \"[A-Z]:\")"
|
||||
if [[ $DRIVE ]] ; then
|
||||
echo "Enter) Copy drive ${DRIVE}"
|
||||
fi
|
||||
echo "2) Build"
|
||||
|
||||
# dynamic options...
|
||||
i=3
|
||||
OPTION_VERIFICATION=
|
||||
if ! [ -z $VERIFY ] ; then
|
||||
echo "$i) Verification is `[[ $DO_VERIFY ]] && echo "on" || echo "off"`"
|
||||
OPTION_VERIFICATION=$i
|
||||
i=$(( i + 1 ))
|
||||
fi
|
||||
|
||||
OPTION_COMPRESSION=
|
||||
if ! [ -z $COMPRESS ] ; then
|
||||
echo "$i) Compresion is `[[ $DO_COMPRESS ]] && echo "on" || echo "off"`"
|
||||
OPTION_COMPRESSION=$i
|
||||
i=$(( i + 1 ))
|
||||
fi
|
||||
|
||||
echo "$i) Quit"
|
||||
OPTION_QUIT=$i
|
||||
read -ep ": " RES
|
||||
|
||||
# NOTE: we can't use letters here as they will shadow
|
||||
# with drive letters...
|
||||
case $RES in
|
||||
# toggle multi mode...
|
||||
0)
|
||||
if [ -z $MULTI_STARTED ] ; then
|
||||
MULTI=`[[ ! $MULTI ]] && echo 1 || echo ""`
|
||||
else
|
||||
LAST=`[[ ! $LAST ]] && echo 1 || echo ""`
|
||||
fi
|
||||
continue
|
||||
;;
|
||||
1)
|
||||
read -i "${TITLE# - }" -ep "new description: " TITLE
|
||||
TITLE=" - $TITLE"
|
||||
continue
|
||||
;;
|
||||
# continue with same drive or ask again...
|
||||
"")
|
||||
if [[ ! $DRIVE ]] ; then
|
||||
echo "ERR: need a drive to copy from, no defaults."
|
||||
echo
|
||||
continue
|
||||
fi
|
||||
DRIVE=$DRIVE
|
||||
;;
|
||||
2)
|
||||
LAST=1
|
||||
break
|
||||
;;
|
||||
|
||||
# dynamic option handlers...
|
||||
"$OPTION_VERIFICATION")
|
||||
DO_VERIFY=`[[ ! $DO_VERIFY ]] && echo 1 || echo ""`
|
||||
continue
|
||||
;;
|
||||
"$OPTION_COMPRESSION")
|
||||
DO_COMPRESS=`[[ ! $DO_COMPRESS ]] && echo 1 || echo ""`
|
||||
continue
|
||||
;;
|
||||
"$OPTION_QUIT")
|
||||
exit
|
||||
;;
|
||||
|
||||
# new drive letter...
|
||||
*)
|
||||
DRIVE=$RES
|
||||
;;
|
||||
esac
|
||||
echo
|
||||
fi
|
||||
|
||||
# explicit path given...
|
||||
if [[ "${DRIVE::1}" == "/" ]] \
|
||||
|| [[ "${DRIVE::2}" == "./" ]] \
|
||||
|| [[ "${DRIVE::2}" =~ [a-zA-Z]: ]] \
|
||||
&& [ -e "$DRIVE" ] ; then
|
||||
BASE=
|
||||
fi
|
||||
|
||||
# check path...
|
||||
notfound=()
|
||||
if ! [ -z $BASE ] ; then
|
||||
for d in "${BASES[@]}"; do
|
||||
if [ -e "${d}${DRIVE}" ] ; then
|
||||
BASE=$d
|
||||
break
|
||||
else
|
||||
notfound+=("${d}${DRIVE}")
|
||||
fi
|
||||
i=$(( i + 1 ))
|
||||
done
|
||||
fi
|
||||
if ! [ -e "${BASE}${DRIVE}" ] ; then
|
||||
if [ ${#notfound[@]} == 0 ] ; then
|
||||
notfound=(${BASE}${DRIVE})
|
||||
fi
|
||||
echo
|
||||
echo "ERR: Not found:"
|
||||
for d in "${notfound[@]}" ; do
|
||||
echo "ERR: ${d}"
|
||||
done
|
||||
echo "ERR: Nothing to copy."
|
||||
echo
|
||||
if [[ $INTERACTIVE || ! $DRIVE ]] ; then
|
||||
continue
|
||||
fi
|
||||
exit
|
||||
fi
|
||||
|
||||
# XXX do a real three digit count...
|
||||
# single flash card...
|
||||
SCOUNT=`printf "%03d" $COUNT`
|
||||
if [ -z $MULTI ] ; then
|
||||
DIR="${DATE}.${SCOUNT}${TITLE}"
|
||||
while [ -e *"$DIR"* ] ; do
|
||||
COUNT=$((COUNT+1))
|
||||
SCOUNT=`printf "%03d" $COUNT`
|
||||
DIR="${DATE}.${SCOUNT}${TITLE}"
|
||||
done
|
||||
BASE_DIR=$DIR
|
||||
|
||||
# multiple flash cards shoot...
|
||||
else
|
||||
BASE_DIR="${DATE}${TITLE}/"
|
||||
DIR="${BASE_DIR}/${DATE}.${SCOUNT}"
|
||||
# get next dir index...
|
||||
while [ -e *"$DIR"* ] ; do
|
||||
COUNT=$((COUNT+1))
|
||||
SCOUNT=`printf "%03d" $COUNT`
|
||||
DIR="${BASE_DIR}/${DATE}.${SCOUNT}"
|
||||
done
|
||||
fi
|
||||
|
||||
MULTI_STARTED=1
|
||||
|
||||
# normalize paths...
|
||||
BASE_DIR="./- ${BASE_DIR}/"
|
||||
DIR="./- $DIR/"
|
||||
|
||||
mkdir -vp "$DIR"
|
||||
|
||||
while true ; do
|
||||
echo "Copying files from ${BASE}${DRIVE} (~`du -hs "${BASE}${DRIVE}" | cut -f 1`)..."
|
||||
#echo "# $COPY $COPYFLAGS ${BASE}${DRIVE}/* "$DIR""
|
||||
#echo "# 2> >(tee "${DIR}"/copy-err.log)"
|
||||
$COPY $COPYFLAGS ${BASE}${DRIVE}/* "$DIR" \
|
||||
2> >(tee "${DIR}"/copy-err.log)
|
||||
# no errors -> remove log...
|
||||
if ! [ -s "${DIR}/copy-err.log" ] ; then
|
||||
rm -f "${DIR}"/copy-err.log
|
||||
fi
|
||||
echo "Copying files: done."
|
||||
|
||||
# verify copy...
|
||||
# XXX make this more generic...
|
||||
if [ $DO_VERIFY ] && ! [ -z $VERIFY ] ; then
|
||||
echo "Verifying copied files..."
|
||||
$VERIFY $VERIFYFLAGS ${BASE}${DRIVE}/* "$DIR" \
|
||||
> >(tee "${DIR}"/verification-err.log)
|
||||
if ! [ -s "${DIR}/verification-err.log" ] ; then
|
||||
rm -f "${DIR}"/verification-err.log
|
||||
else
|
||||
echo
|
||||
echo "WARNING: found mismatching files"
|
||||
echo " (see: "${DIR}"/verification-err.log)"
|
||||
echo
|
||||
while true; do
|
||||
read -ep "[R]etry, [c]ontinue, or Ctrl-C to cancel: " ACTION
|
||||
ACTION=`echo ${ACTION,,} | xargs`
|
||||
if [[ $ACTION =~ [rc] ]] \
|
||||
|| [ -z $ACTION ] ; then
|
||||
break
|
||||
fi
|
||||
echo "Unknown input: \"$ACTION\""
|
||||
done
|
||||
if [[ $ACTION == "c" ]] ; then
|
||||
break
|
||||
else
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
echo "Verifification: done."
|
||||
break
|
||||
# no verification defined...
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# exit interactive mode...
|
||||
if [[ ! $MULTI || ! $INTERACTIVE || $LAST ]] ; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $LAST ]] ; then
|
||||
COMMON_FLAG=-c
|
||||
fi
|
||||
|
||||
if [[ ! $MULTI || $LAST ]] ; then
|
||||
echo "Building archive..."
|
||||
./process-archive.sh $COMMON_FLAG "$BASE_DIR"
|
||||
echo "Building archive: done."
|
||||
fi
|
||||
|
||||
if [[ $DO_COMPRESS ]] ; then
|
||||
echo "Compressing archive..."
|
||||
${COMPRESS} "$BASE_DIR"
|
||||
echo "Compressing archive: done."
|
||||
fi
|
||||
|
||||
if ! [ -z "$SNAPSHOT" ] \
|
||||
&& [ -e "$SNAPSHOT" ] ; then
|
||||
"$SNAPSHOT"
|
||||
fi
|
||||
|
||||
# XXX add report...
|
||||
# XXX
|
||||
|
||||
echo "`basename "$0"`: done."
|
||||
|
||||
# vim:set nowrap :
|
||||
101
Archive/media/img/my/work/update-exif.sh
Executable file
101
Archive/media/img/my/work/update-exif.sh
Executable file
@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DIR=`pwd`
|
||||
|
||||
printhelp(){
|
||||
echo "Usage: `basename $0` [ARGUMENTS] [PATH]"
|
||||
echo
|
||||
echo "Arguments:"
|
||||
echo " -h --help - print this help and exit."
|
||||
#echo " -p --psd - source metadad from psd file (default)."
|
||||
#echo " -r --raw - source metadad from raw file."
|
||||
echo
|
||||
}
|
||||
|
||||
while true ; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
printhelp
|
||||
exit
|
||||
;;
|
||||
# XXX
|
||||
-r|--raw)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
# XXX
|
||||
-p|--psd)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
# XXX TODO:
|
||||
# - add support for multiple raw formats...
|
||||
# - handle multiple hits -- preferably automatically...
|
||||
# -
|
||||
_exifup(){
|
||||
local PREVIEW_DIR=$1
|
||||
if ! [ -e "$PREVIEW_DIR" ] ; then
|
||||
return 1
|
||||
fi
|
||||
cd "${PREVIEW_DIR}"
|
||||
# XXX only jpg???
|
||||
local imgs=(*.jpg)
|
||||
# XXX
|
||||
for img in "${imgs[@]}" ; do
|
||||
local name="${img%.jpg}"
|
||||
local targets=("$(find . -name "${name}.ARW")")
|
||||
if [[ ${#targets[@]} > 1 ]] ; then
|
||||
# XXX multiple candidates -> select one...
|
||||
# XXX
|
||||
echo '!!!!!!!!!!!'
|
||||
fi
|
||||
# XXX
|
||||
exiv2 ex "${target[0]}"
|
||||
mv "${target[0]%.ARW}.exv" .
|
||||
exiv2 -k in "${img}"
|
||||
rm -f *.exv
|
||||
done
|
||||
}
|
||||
|
||||
# XXX add support for getting exif from raw...
|
||||
# ...this can lead to multiple hits, need a way to decide which
|
||||
# one to use...
|
||||
exifup(){
|
||||
local PREVIEW_DIR=$1
|
||||
if [ -e "$PREVIEW_DIR" ] ; then
|
||||
echo doing: `pwd`
|
||||
exiv2 ex *.psd 2> /dev/null
|
||||
mv *.exv "$PREVIEW_DIR" 2> /dev/null
|
||||
cd "$PREVIEW_DIR"
|
||||
exiv2 -k in *.jpg 2> /dev/null
|
||||
rm -f *.exv
|
||||
cd ..
|
||||
fi
|
||||
true
|
||||
}
|
||||
|
||||
IFS=$'\n'
|
||||
if [[ $1 != "" ]] ; then
|
||||
if ! [ -d "$1" ] ; then
|
||||
echo "\"$1\": is not a directory."
|
||||
exit 1
|
||||
fi
|
||||
DIRS=($(find "$1" -name 'preview'))
|
||||
else
|
||||
DIRS=($(find . -name 'preview'))
|
||||
fi
|
||||
for d in "${DIRS[@]}" ; do
|
||||
cd "$d"
|
||||
cd ..
|
||||
exifup ./preview/
|
||||
exifup ./hi-res/
|
||||
cd "$DIR"
|
||||
done
|
||||
|
||||
25
Archive/media/tree.sh
Executable file
25
Archive/media/tree.sh
Executable file
@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
TREE=`date +"%Y%m%d-%H%M"`
|
||||
|
||||
|
||||
if ! [ -d ./.tree ] ; then
|
||||
echo creating .tree directory...
|
||||
mkdir .tree
|
||||
attrib +H .tree
|
||||
fi
|
||||
|
||||
echo building current tree...
|
||||
tree -a -s --sort name -I '.tree*' > ./.tree/$TREE
|
||||
|
||||
echo setting LAST/CURRENT states...
|
||||
[ -e ./.tree/CURRENT ] && cp ./.tree/CURRENT ./.tree/LAST
|
||||
cp ./.tree/$TREE ./.tree/CURRENT
|
||||
|
||||
if [ -e ./.tree/LAST ] ; then
|
||||
echo diff...
|
||||
# XXX
|
||||
diff ./.tree/LAST ./.tree/CURRENT
|
||||
fi
|
||||
|
||||
|
||||
108
Archive/snapshot.sh
Executable file
108
Archive/snapshot.sh
Executable file
@ -0,0 +1,108 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
# XXX need:
|
||||
# - protocol (command) to create archive root
|
||||
# manually:
|
||||
# - copy tree
|
||||
# - run snapshot.sh
|
||||
# script:
|
||||
# XXX
|
||||
# - protocol to create snapshots
|
||||
# - sync-flash.sh ???
|
||||
# - protocol to restore stuff -- simply copy???
|
||||
# - protocol to fully delete something -- i.e. delete snapshots???
|
||||
# - a way to list deleted files
|
||||
# - a way to list available file versions
|
||||
# - a way to restore specicifc file(s)
|
||||
# - a way to maintain a set number of snapshots...
|
||||
#
|
||||
|
||||
usage(){
|
||||
echo "Usage:"
|
||||
# XXX
|
||||
echo " $(basename "$0")"
|
||||
}
|
||||
printHelp(){
|
||||
usage
|
||||
# XXX
|
||||
}
|
||||
|
||||
# handle args...
|
||||
for arg in "$@" ; do
|
||||
case $arg in
|
||||
-h|--help)
|
||||
printHelp
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
SNAPSHOT_DIR=.snapshots
|
||||
SUBVOLUME_DIR=media
|
||||
SNAPSHOT_COUNT=5
|
||||
|
||||
|
||||
# run in script dir (not cwd)...
|
||||
DIR=`dirname "$0"`
|
||||
if ! [ -z "$DIR" ] ; then
|
||||
cd "$DIR"
|
||||
fi
|
||||
|
||||
# check if on btrfs filesystem...
|
||||
# XXX also check if btrfs command is available...
|
||||
if ! btrfs filesystem usage . > /dev/null 2>&1 ; then
|
||||
exit
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# create ./media...
|
||||
# XXX check if not a directory...
|
||||
if ! [ -e "$SUBVOLUME_DIR" ] ; then
|
||||
btrfs subvolume create "$SUBVOLUME_DIR"
|
||||
# XXX build tree -- ImageGrid
|
||||
|
||||
# convert ./media to a subvolume...
|
||||
elif ! [ "$(stat --format=%i "$SUBVOLUME_DIR")" == 256 ] ; then
|
||||
mkdir bak
|
||||
mv "$SUBVOLUME_DIR" bak/
|
||||
btrfs subvolume create "$SUBVOLUME_DIR"
|
||||
cp --archive --one-file-system --reflink=always \
|
||||
./bak/"$SUBVOLUME_DIR"/{,.}* \
|
||||
"$SUBVOLUME_DIR"/
|
||||
fi
|
||||
mkdir -p "$SNAPSHOT_DIR"
|
||||
|
||||
|
||||
# XXX should this be more human readable???
|
||||
# ...a date + number maybe???
|
||||
SNAPSHOT=$((
|
||||
$( ls "$SNAPSHOT_DIR/" \
|
||||
| sort -n \
|
||||
| tail -n 1 ) \
|
||||
+ 1 ))
|
||||
|
||||
#btrfs subvolume snapshot -r "$SUBVOLUME_DIR" "${SNAPSHOT_DIR}/${SNAPSHOT}"
|
||||
btrfs subvolume snapshot "$SUBVOLUME_DIR" "${SNAPSHOT_DIR}/${SNAPSHOT}"
|
||||
|
||||
|
||||
if [[ $SNAPSHOT_COUNT =~ [0-9]* ]] \
|
||||
&& [ "$SNAPSHOT_COUNT" != 0 ] ; then
|
||||
SNAPSHOTS=($(\
|
||||
ls "$SNAPSHOT_DIR/" \
|
||||
| sort -n ))
|
||||
remove=$(( ${#SNAPSHOTS[@]} - $SNAPSHOT_COUNT - 1 ))
|
||||
while (( $remove >= 0 )) ; do
|
||||
# XXX can we avoid sudo here???
|
||||
# XXX is 'btrfs subvolume delete ...' the same as 'rm -rf ..'
|
||||
#sudo btrfs subvolume delete -c ${SNAPSHOT_DIR}/${SNAPSHOTS[$remove]}
|
||||
echo Removing snapshot: ${SNAPSHOT_DIR}/${SNAPSHOTS[$remove]}
|
||||
rm -rf ${SNAPSHOT_DIR}/${SNAPSHOTS[$remove]}
|
||||
remove=$(( $remove - 1 ))
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# vim:set nowrap nospell :
|
||||
19
README.md
Normal file
19
README.md
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
20220125:
|
||||
|
||||
About a year ago the project reached a stable state for most of my use-cases
|
||||
thus development stagnated a bit and went into "if something goes wrong
|
||||
write it down and continue on" mode.
|
||||
|
||||
During this period we've accumulated:
|
||||
- quite a list of known bugs (mostly minor)
|
||||
- quite a backlog of breaking updates of upstream software (electron)
|
||||
|
||||
In addition to this and a couple of other events I had trouble finding
|
||||
motivation to dig in.
|
||||
|
||||
It is about time to schedule a big revision run to fix the accumulated
|
||||
set of issues and implement several planned features.
|
||||
|
||||
|
||||
|
||||
9
Viewer/App.desktop.tpl
Normal file
9
Viewer/App.desktop.tpl
Normal file
@ -0,0 +1,9 @@
|
||||
[Desktop Entry]
|
||||
Name=$APP_NAME
|
||||
Comment=$COMMENT
|
||||
Categories=$CATEGORIES
|
||||
Exec=$LAUNCHER
|
||||
Icon=$ICON_PATH
|
||||
Terminal=false
|
||||
Type=Application
|
||||
StartupNotify=true
|
||||
27
Viewer/LICENSE
Normal file
27
Viewer/LICENSE
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009-2025, Alex A. Naanou
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
1006
Viewer/Makefile
Normal file
1006
Viewer/Makefile
Normal file
File diff suppressed because it is too large
Load Diff
144
Viewer/cfg/requirejs.js
Normal file
144
Viewer/cfg/requirejs.js
Normal file
@ -0,0 +1,144 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
* This can be loaded from two contexts:
|
||||
*
|
||||
* - <script src=../>
|
||||
* Needs the requirejs module already loaded...
|
||||
* Example:
|
||||
* <script src="js/require.min.js"/>
|
||||
* <script src="cfg/requirejs.js"/>
|
||||
*
|
||||
* - require(..)
|
||||
* This needs the root require(..) function...
|
||||
* Example:
|
||||
* // in the root module...
|
||||
* require('./cfg/requirejs.js')(require)
|
||||
*
|
||||
*
|
||||
**********************************************************/(function(){
|
||||
|
||||
var _requirejs = typeof(requirejs) != 'undefined' && requirejs
|
||||
|
||||
var setup = function(require, root){
|
||||
var res = {}
|
||||
var requirejs = _requirejs
|
||||
|
||||
var requirejs_cfg = {
|
||||
// XXX under electron the path seems to be one level above the
|
||||
// actual base URL, i.e. one level above the $0, hence the
|
||||
// need to correct this...
|
||||
baseUrl: root ?
|
||||
root
|
||||
: typeof(process) != 'undefined'
|
||||
&& process.versions.electron ?
|
||||
(require.main ?
|
||||
require.main.filename.split(/[\\\/]/g).slice(0, -1).join('/')
|
||||
: document.baseURI
|
||||
// remove URL protocol...
|
||||
.replace(/^[a-zA-Z]+:\/\/\/?/, '/')
|
||||
// remove leading '/' on windows drives...
|
||||
.replace(/^\/[a-zA-Z]:/, '')
|
||||
.split(/[#&]/)[0].split(/[\\\/]/g).slice(0, -1).join('/'))
|
||||
: '.',
|
||||
|
||||
// XXX this does not work on direct filesystem access...
|
||||
//urlArgs: 'bust='+Date.now(),
|
||||
|
||||
paths: {
|
||||
text: 'node_modules/requirejs-plugins/lib/text',
|
||||
json: 'node_modules/requirejs-plugins/src/json',
|
||||
|
||||
//react: 'node_modules/react/dist/react-with-addons.min.js',
|
||||
//'react-dom': 'node_modules/react-dom/dist/react-dom.min.js',
|
||||
//'ext-lib/preact': './node_modules/preact/dist/preact.dev',
|
||||
|
||||
'lib/doc': 'node_modules/ig-doc/doc',
|
||||
'lib/stoppable': 'node_modules/ig-stoppable/stoppable',
|
||||
'lib/object': 'node_modules/ig-object/object',
|
||||
'lib/types': 'node_modules/ig-types/',
|
||||
'lib/actions': 'node_modules/ig-actions/actions',
|
||||
'lib/features': 'node_modules/ig-features/features',
|
||||
//'lib/keyboard': './node_modules/ig-keyboard/keyboard',
|
||||
'object-run': 'node_modules/object-run/run',
|
||||
|
||||
'lib/argv': 'node_modules/ig-argv/argv',
|
||||
'lib/walk': 'node_modules/generic-walk/walk',
|
||||
},
|
||||
map: {
|
||||
'*': {
|
||||
// back-refs
|
||||
// ...these enable the npm modules reference each other in
|
||||
// a cross-platform manner....
|
||||
'ig-doc': 'lib/doc',
|
||||
'ig-object': 'lib/object',
|
||||
'ig-types': 'lib/types',
|
||||
'ig-actions': 'lib/actions',
|
||||
'ig-features': 'lib/features',
|
||||
'ig-stoppable': 'lib/stoppable',
|
||||
|
||||
//'ig-keyboard': 'lib/keyboard',
|
||||
|
||||
'ig-argv': 'lib/argv',
|
||||
'generic-walk': 'lib/walk',
|
||||
},
|
||||
},
|
||||
packages: [
|
||||
'lib/types',
|
||||
],
|
||||
}
|
||||
|
||||
// node contexts...
|
||||
if(typeof(process) != 'undefined'){
|
||||
var nodeRequire =
|
||||
requirejs_cfg.nodeRequire =
|
||||
global.nodeRequire
|
||||
|| global.require
|
||||
|| require
|
||||
|
||||
require('app-module-path')
|
||||
.addPath('.')
|
||||
|
||||
requirejs =
|
||||
global.requirejs =
|
||||
res.requirejs =
|
||||
global.requirejs
|
||||
// XXX this breaks for electron version 15...
|
||||
// the problem seems to be the "#!/..." at the start of r.js...
|
||||
|| require('requirejs')
|
||||
|
||||
global.nodeRequire =
|
||||
res.nodeRequire =
|
||||
nodeRequire }
|
||||
|
||||
|
||||
// browser contexts...
|
||||
if(typeof(window) != 'undefined'){
|
||||
window.nodeRequire =
|
||||
window.nodeRequire
|
||||
|| (typeof(require) != 'undefined'
|
||||
&& require !== requirejs
|
||||
&& require)
|
||||
window.requirejs = requirejs }
|
||||
|
||||
requirejs.config(requirejs_cfg)
|
||||
|
||||
return res }
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Run/export the setup...
|
||||
//
|
||||
// we can get here from two contexts...
|
||||
typeof(process) == 'undefined' ?
|
||||
// browser's <script src="..">...
|
||||
setup(require)
|
||||
// node's require(..)
|
||||
: (module.exports = setup)
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */ })()
|
||||
@ -1,398 +1,398 @@
|
||||
.panel {
|
||||
position: absolute;
|
||||
|
||||
display: inline-block;
|
||||
min-width: 200px;
|
||||
max-width: 450px;
|
||||
|
||||
font-size: 12px;
|
||||
|
||||
border: solid 2px silver;
|
||||
border-radius: 4px;
|
||||
|
||||
background: white;
|
||||
box-shadow: 5px 5px 30px -5px rgba(0, 0, 0, 0.5);
|
||||
opacity: 0.95;
|
||||
|
||||
overflow: visible;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.panel summary,
|
||||
.sub-panel summary {
|
||||
padding-left: 3px;
|
||||
background: silver
|
||||
}
|
||||
.panel summary::-webkit-details-marker,
|
||||
.sub-panel summary::-webkit-details-marker {
|
||||
color: gray;
|
||||
}
|
||||
.panel .close-button,
|
||||
.sub-panel .close-button {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
cursor: hand;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.panel .close-button:hover,
|
||||
.sub-panel .close-button:hover {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
text-shadow: 0px 0px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.sub-panel .close-button {
|
||||
right: 8px;
|
||||
}
|
||||
.panel .close-button,
|
||||
.sub-panel .close-button {
|
||||
visibility: hidden;
|
||||
}
|
||||
.panel:hover>summary .close-button,
|
||||
.sub-panel:hover .close-button {
|
||||
visibility: visible;
|
||||
}
|
||||
.panel .panel-content {
|
||||
display: block;
|
||||
|
||||
min-height: 15px;
|
||||
}
|
||||
.sub-panel,
|
||||
.sub-panel button,
|
||||
.sub-panel .state {
|
||||
margin: 1px;
|
||||
font-size: 11px;
|
||||
border: solid 1px #aaa;
|
||||
border-radius: 4px;
|
||||
/* needed for dragging */
|
||||
background: white;
|
||||
}
|
||||
.sub-panel {
|
||||
display: block;
|
||||
margin: 3px;
|
||||
border: solid 1px silver;
|
||||
box-shadow: none;
|
||||
}
|
||||
.sub-panel.blink {
|
||||
box-shadow: 0px 0px 10px 0px rgba(255,0,0,1)
|
||||
}
|
||||
.sub-panel summary {
|
||||
background: #ddd;
|
||||
/*
|
||||
background: white;
|
||||
box-shadow: 0px 0px 50px -5px rgba(0, 0, 0, 0.4);
|
||||
*/
|
||||
}
|
||||
.sub-panel .sub-panel-content {
|
||||
margin: 10px;
|
||||
/*
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
*/
|
||||
}
|
||||
|
||||
.sub-panel button:active,
|
||||
.sub-panel .state:active {
|
||||
background: silver;
|
||||
}
|
||||
|
||||
.side-panel {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
height: 100%;
|
||||
bottom: 0px;
|
||||
min-width: 10px;
|
||||
|
||||
background: white;
|
||||
opacity: 0.95;
|
||||
|
||||
box-shadow: 0px 0px 30px -5px rgba(0, 0, 0, 0.3);
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.side-panel:not(:empty):hover:after {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
content: "Double click to toggle auto-hide (now: " attr(autohide) ")";
|
||||
color: gray;
|
||||
|
||||
font-size: 10px;
|
||||
padding: 5px;
|
||||
bottom: 0px;
|
||||
|
||||
opacity: 0.5;
|
||||
}
|
||||
.side-panel.right:not(:empty):after {
|
||||
right: 0px;
|
||||
}
|
||||
.side-panel[open],
|
||||
.side-panel:not(:empty)[autohide=off],
|
||||
.side-panel[autohide=on]:not(:empty):hover {
|
||||
min-width: 200px;
|
||||
}
|
||||
.side-panel.left {
|
||||
left: 0px;
|
||||
border-right: solid 1px silver;
|
||||
}
|
||||
.side-panel.right {
|
||||
right: 0px;
|
||||
border-left: solid 1px silver;
|
||||
}
|
||||
|
||||
.side-panel[autohide=on] .sub-panel {
|
||||
display: none;
|
||||
}
|
||||
.side-panel[open] .sub-panel,
|
||||
.side-panel[autohide=on]:hover .sub-panel {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/* main controls */
|
||||
.sub-panel .control {
|
||||
white-space:nowrap;
|
||||
}
|
||||
.sub-panel .control .title {
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
cursor: move;
|
||||
}
|
||||
.sub-panel .control .slider {
|
||||
-webkit-appearance: none !important;
|
||||
width: 150px;
|
||||
height: 3px;
|
||||
border: solid 1px #ccc;
|
||||
border-radius: 2px;
|
||||
background: white;
|
||||
}
|
||||
.sub-panel .control.at-default .slider {
|
||||
}
|
||||
.sub-panel .control .slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none !important;
|
||||
height: 13px;
|
||||
width: 13px;
|
||||
/*border: solid 1px gray;*/
|
||||
border: solid 2px #aaa;
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
box-shadow: 1px 1px 10px 0px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.sub-panel .control.at-default .slider::-webkit-slider-thumb {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.sub-panel .control .value {
|
||||
-webkit-appearance: none !important;
|
||||
display: inline-block;
|
||||
width: 25px;
|
||||
text-align: right;
|
||||
font-size: 11px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
background: transparent;
|
||||
}
|
||||
.sub-panel .control input::-webkit-outer-spin-button,
|
||||
.sub-panel .control input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none !important;
|
||||
}
|
||||
.sub-panel .control .reset {
|
||||
visibility: hidden;
|
||||
border: solid 1px transparent;
|
||||
}
|
||||
.sub-panel .control:hover button.reset {
|
||||
visibility: visible;
|
||||
}
|
||||
.sub-panel .control .reset:hover {
|
||||
border: solid 1px silver;
|
||||
}
|
||||
|
||||
|
||||
/* Snapshots */
|
||||
.sub-panel .state {
|
||||
display: inline-block;
|
||||
margin: 1px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.sub-panel .state.ui-draggable-dragging {
|
||||
box-shadow: 2px 2px 10px -2px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.sub-panel .states {
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
|
||||
/* misc */
|
||||
.sub-panel hr {
|
||||
border: none;
|
||||
border-top: solid 1px silver;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* dark theme */
|
||||
.dark .panel {
|
||||
border: solid 2px #333;
|
||||
background: black;
|
||||
color: silver;
|
||||
box-shadow: 3px 3px 30px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.dark .panel summary {
|
||||
background: #333;
|
||||
}
|
||||
.dark .panel summary::-webkit-details-marker,
|
||||
.dark .sub-panel summary::-webkit-details-marker {
|
||||
color: #555;
|
||||
}
|
||||
.dark .sub-panel button,
|
||||
.dark .sub-panel .state,
|
||||
.dark .sub-panel {
|
||||
border: solid 1px #333;
|
||||
/* needed for dragging */
|
||||
background: #080808;
|
||||
color: #888;
|
||||
}
|
||||
.dark .sub-panel {
|
||||
border: solid 1px #333;
|
||||
}
|
||||
.dark .sub-panel.blink {
|
||||
box-shadow: 0px 0px 10px 0px rgba(255,255,0,1)
|
||||
}
|
||||
.dark .sub-panel summary {
|
||||
background: #333;
|
||||
color: silver;
|
||||
}
|
||||
.dark .sub-panel .state:active,
|
||||
.dark .sub-panel button:active {
|
||||
background: #222;
|
||||
}
|
||||
.dark .sub-panel .control .slider {
|
||||
border: solid 1px #555;
|
||||
background: black;
|
||||
}
|
||||
.dark .sub-panel .control.at-default .slider {
|
||||
}
|
||||
.dark .sub-panel .control .slider::-webkit-slider-thumb {
|
||||
border: solid 2px #aaa;
|
||||
background: black;
|
||||
box-shadow: 1px 1px 10px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.dark .sub-panel .control.at-default .slider::-webkit-slider-thumb {
|
||||
border: solid 1px gray;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.dark .sub-panel .control .value {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: gray;
|
||||
}
|
||||
.dark .sub-panel .control .reset:hover {
|
||||
border: solid 1px #333;
|
||||
}
|
||||
.dark .sub-panel hr {
|
||||
border: none;
|
||||
border-top: solid 1px #333;
|
||||
}
|
||||
.dark .side-panel {
|
||||
background: black;
|
||||
box-shadow: 0px 0px 30px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.dark .side-panel:not(:empty):hover:after {
|
||||
color: gray;
|
||||
}
|
||||
.dark .side-panel.left {
|
||||
border-right: solid 1px #333;
|
||||
}
|
||||
.dark .side-panel.right {
|
||||
border-left: solid 1px #333;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* gray theme */
|
||||
|
||||
.gray .panel {
|
||||
border: solid 2px #444;
|
||||
background: #333;
|
||||
color: silver;
|
||||
box-shadow: 3px 3px 30px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.gray .panel summary {
|
||||
background: #444;
|
||||
}
|
||||
.gray .panel summary::-webkit-details-marker,
|
||||
.gray .sub-panel summary::-webkit-details-marker {
|
||||
color: #555;
|
||||
}
|
||||
.gray .sub-panel button,
|
||||
.gray .sub-panel .state,
|
||||
.gray .sub-panel {
|
||||
border: solid 1px #444;
|
||||
/* needed for dragging */
|
||||
background: #333;
|
||||
color: #888;
|
||||
}
|
||||
.gray .sub-panel {
|
||||
border: solid 1px #454545;
|
||||
}
|
||||
.gray .sub-panel.blink {
|
||||
box-shadow: 0px 0px 10px 0px rgba(255,255,0,1)
|
||||
}
|
||||
.gray .sub-panel summary {
|
||||
background: #444;
|
||||
color: silver;
|
||||
}
|
||||
.gray .sub-panel .state:active,
|
||||
.gray .sub-panel button:active {
|
||||
background: #444;
|
||||
}
|
||||
.gray .sub-panel .control .slider {
|
||||
border: solid 1px #555;
|
||||
background: #222;
|
||||
}
|
||||
.gray .sub-panel .control.at-default .slider {
|
||||
}
|
||||
.gray .sub-panel .control .slider::-webkit-slider-thumb {
|
||||
border: solid 2px #aaa;
|
||||
background: #333;
|
||||
}
|
||||
.gray .sub-panel .control.at-default .slider::-webkit-slider-thumb {
|
||||
border: solid 1px gray;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.gray .sub-panel .control .value {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: gray;
|
||||
}
|
||||
.gray .sub-panel .control .reset:hover {
|
||||
border: solid 1px #444;
|
||||
}
|
||||
.gray .sub-panel hr {
|
||||
border: none;
|
||||
border-top: solid 1px #444;
|
||||
}
|
||||
.gray .side-panel {
|
||||
background: #303030;
|
||||
box-shadow: 0px 0px 30px 0px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.gray .side-panel:not(:empty):hover:after {
|
||||
color: silver;
|
||||
}
|
||||
.gray .side-panel.left {
|
||||
border-right: solid 1px #444;
|
||||
}
|
||||
.gray .side-panel.right {
|
||||
border-left: solid 1px #444;
|
||||
}
|
||||
|
||||
.panel {
|
||||
position: absolute;
|
||||
|
||||
display: inline-block;
|
||||
min-width: 200px;
|
||||
max-width: 450px;
|
||||
|
||||
font-size: 12px;
|
||||
|
||||
border: solid 2px silver;
|
||||
border-radius: 4px;
|
||||
|
||||
background: white;
|
||||
box-shadow: 5px 5px 30px -5px rgba(0, 0, 0, 0.5);
|
||||
opacity: 0.95;
|
||||
|
||||
overflow: visible;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.panel summary,
|
||||
.sub-panel summary {
|
||||
padding-left: 3px;
|
||||
background: silver
|
||||
}
|
||||
.panel summary::-webkit-details-marker,
|
||||
.sub-panel summary::-webkit-details-marker {
|
||||
color: gray;
|
||||
}
|
||||
.panel .close-button,
|
||||
.sub-panel .close-button {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
cursor: hand;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.panel .close-button:hover,
|
||||
.sub-panel .close-button:hover {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
text-shadow: 0px 0px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.sub-panel .close-button {
|
||||
right: 8px;
|
||||
}
|
||||
.panel .close-button,
|
||||
.sub-panel .close-button {
|
||||
visibility: hidden;
|
||||
}
|
||||
.panel:hover>summary .close-button,
|
||||
.sub-panel:hover .close-button {
|
||||
visibility: visible;
|
||||
}
|
||||
.panel .panel-content {
|
||||
display: block;
|
||||
|
||||
min-height: 15px;
|
||||
}
|
||||
.sub-panel,
|
||||
.sub-panel button,
|
||||
.sub-panel .state {
|
||||
margin: 1px;
|
||||
font-size: 11px;
|
||||
border: solid 1px #aaa;
|
||||
border-radius: 4px;
|
||||
/* needed for dragging */
|
||||
background: white;
|
||||
}
|
||||
.sub-panel {
|
||||
display: block;
|
||||
margin: 3px;
|
||||
border: solid 1px silver;
|
||||
box-shadow: none;
|
||||
}
|
||||
.sub-panel.blink {
|
||||
box-shadow: 0px 0px 10px 0px rgba(255,0,0,1)
|
||||
}
|
||||
.sub-panel summary {
|
||||
background: #ddd;
|
||||
/*
|
||||
background: white;
|
||||
box-shadow: 0px 0px 50px -5px rgba(0, 0, 0, 0.4);
|
||||
*/
|
||||
}
|
||||
.sub-panel .sub-panel-content {
|
||||
margin: 10px;
|
||||
/*
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
*/
|
||||
}
|
||||
|
||||
.sub-panel button:active,
|
||||
.sub-panel .state:active {
|
||||
background: silver;
|
||||
}
|
||||
|
||||
.side-panel {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
height: 100%;
|
||||
bottom: 0px;
|
||||
min-width: 10px;
|
||||
|
||||
background: white;
|
||||
opacity: 0.95;
|
||||
|
||||
box-shadow: 0px 0px 30px -5px rgba(0, 0, 0, 0.3);
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.side-panel:not(:empty):hover:after {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
content: "Double click to toggle auto-hide (now: " attr(autohide) ")";
|
||||
color: gray;
|
||||
|
||||
font-size: 10px;
|
||||
padding: 5px;
|
||||
bottom: 0px;
|
||||
|
||||
opacity: 0.5;
|
||||
}
|
||||
.side-panel.right:not(:empty):after {
|
||||
right: 0px;
|
||||
}
|
||||
.side-panel[open],
|
||||
.side-panel:not(:empty)[autohide=off],
|
||||
.side-panel[autohide=on]:not(:empty):hover {
|
||||
min-width: 200px;
|
||||
}
|
||||
.side-panel.left {
|
||||
left: 0px;
|
||||
border-right: solid 1px silver;
|
||||
}
|
||||
.side-panel.right {
|
||||
right: 0px;
|
||||
border-left: solid 1px silver;
|
||||
}
|
||||
|
||||
.side-panel[autohide=on] .sub-panel {
|
||||
display: none;
|
||||
}
|
||||
.side-panel[open] .sub-panel,
|
||||
.side-panel[autohide=on]:hover .sub-panel {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/* main controls */
|
||||
.sub-panel .control {
|
||||
white-space:nowrap;
|
||||
}
|
||||
.sub-panel .control .title {
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
cursor: move;
|
||||
}
|
||||
.sub-panel .control .slider {
|
||||
-webkit-appearance: none !important;
|
||||
width: 150px;
|
||||
height: 3px;
|
||||
border: solid 1px #ccc;
|
||||
border-radius: 2px;
|
||||
background: white;
|
||||
}
|
||||
.sub-panel .control.at-default .slider {
|
||||
}
|
||||
.sub-panel .control .slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none !important;
|
||||
height: 13px;
|
||||
width: 13px;
|
||||
/*border: solid 1px gray;*/
|
||||
border: solid 2px #aaa;
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
box-shadow: 1px 1px 10px 0px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.sub-panel .control.at-default .slider::-webkit-slider-thumb {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.sub-panel .control .value {
|
||||
-webkit-appearance: none !important;
|
||||
display: inline-block;
|
||||
width: 25px;
|
||||
text-align: right;
|
||||
font-size: 11px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
background: transparent;
|
||||
}
|
||||
.sub-panel .control input::-webkit-outer-spin-button,
|
||||
.sub-panel .control input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none !important;
|
||||
}
|
||||
.sub-panel .control .reset {
|
||||
visibility: hidden;
|
||||
border: solid 1px transparent;
|
||||
}
|
||||
.sub-panel .control:hover button.reset {
|
||||
visibility: visible;
|
||||
}
|
||||
.sub-panel .control .reset:hover {
|
||||
border: solid 1px silver;
|
||||
}
|
||||
|
||||
|
||||
/* Snapshots */
|
||||
.sub-panel .state {
|
||||
display: inline-block;
|
||||
margin: 1px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.sub-panel .state.ui-draggable-dragging {
|
||||
box-shadow: 2px 2px 10px -2px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.sub-panel .states {
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
|
||||
/* misc */
|
||||
.sub-panel hr {
|
||||
border: none;
|
||||
border-top: solid 1px silver;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* dark theme */
|
||||
.dark .panel {
|
||||
border: solid 2px #333;
|
||||
background: black;
|
||||
color: silver;
|
||||
box-shadow: 3px 3px 30px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.dark .panel summary {
|
||||
background: #333;
|
||||
}
|
||||
.dark .panel summary::-webkit-details-marker,
|
||||
.dark .sub-panel summary::-webkit-details-marker {
|
||||
color: #555;
|
||||
}
|
||||
.dark .sub-panel button,
|
||||
.dark .sub-panel .state,
|
||||
.dark .sub-panel {
|
||||
border: solid 1px #333;
|
||||
/* needed for dragging */
|
||||
background: #080808;
|
||||
color: #888;
|
||||
}
|
||||
.dark .sub-panel {
|
||||
border: solid 1px #333;
|
||||
}
|
||||
.dark .sub-panel.blink {
|
||||
box-shadow: 0px 0px 10px 0px rgba(255,255,0,1)
|
||||
}
|
||||
.dark .sub-panel summary {
|
||||
background: #333;
|
||||
color: silver;
|
||||
}
|
||||
.dark .sub-panel .state:active,
|
||||
.dark .sub-panel button:active {
|
||||
background: #222;
|
||||
}
|
||||
.dark .sub-panel .control .slider {
|
||||
border: solid 1px #555;
|
||||
background: black;
|
||||
}
|
||||
.dark .sub-panel .control.at-default .slider {
|
||||
}
|
||||
.dark .sub-panel .control .slider::-webkit-slider-thumb {
|
||||
border: solid 2px #aaa;
|
||||
background: black;
|
||||
box-shadow: 1px 1px 10px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.dark .sub-panel .control.at-default .slider::-webkit-slider-thumb {
|
||||
border: solid 1px gray;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.dark .sub-panel .control .value {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: gray;
|
||||
}
|
||||
.dark .sub-panel .control .reset:hover {
|
||||
border: solid 1px #333;
|
||||
}
|
||||
.dark .sub-panel hr {
|
||||
border: none;
|
||||
border-top: solid 1px #333;
|
||||
}
|
||||
.dark .side-panel {
|
||||
background: black;
|
||||
box-shadow: 0px 0px 30px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.dark .side-panel:not(:empty):hover:after {
|
||||
color: gray;
|
||||
}
|
||||
.dark .side-panel.left {
|
||||
border-right: solid 1px #333;
|
||||
}
|
||||
.dark .side-panel.right {
|
||||
border-left: solid 1px #333;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* gray theme */
|
||||
|
||||
.gray .panel {
|
||||
border: solid 2px #444;
|
||||
background: #333;
|
||||
color: silver;
|
||||
box-shadow: 3px 3px 30px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.gray .panel summary {
|
||||
background: #444;
|
||||
}
|
||||
.gray .panel summary::-webkit-details-marker,
|
||||
.gray .sub-panel summary::-webkit-details-marker {
|
||||
color: #555;
|
||||
}
|
||||
.gray .sub-panel button,
|
||||
.gray .sub-panel .state,
|
||||
.gray .sub-panel {
|
||||
border: solid 1px #444;
|
||||
/* needed for dragging */
|
||||
background: #333;
|
||||
color: #888;
|
||||
}
|
||||
.gray .sub-panel {
|
||||
border: solid 1px #454545;
|
||||
}
|
||||
.gray .sub-panel.blink {
|
||||
box-shadow: 0px 0px 10px 0px rgba(255,255,0,1)
|
||||
}
|
||||
.gray .sub-panel summary {
|
||||
background: #444;
|
||||
color: silver;
|
||||
}
|
||||
.gray .sub-panel .state:active,
|
||||
.gray .sub-panel button:active {
|
||||
background: #444;
|
||||
}
|
||||
.gray .sub-panel .control .slider {
|
||||
border: solid 1px #555;
|
||||
background: #222;
|
||||
}
|
||||
.gray .sub-panel .control.at-default .slider {
|
||||
}
|
||||
.gray .sub-panel .control .slider::-webkit-slider-thumb {
|
||||
border: solid 2px #aaa;
|
||||
background: #333;
|
||||
}
|
||||
.gray .sub-panel .control.at-default .slider::-webkit-slider-thumb {
|
||||
border: solid 1px gray;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.gray .sub-panel .control .value {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: gray;
|
||||
}
|
||||
.gray .sub-panel .control .reset:hover {
|
||||
border: solid 1px #444;
|
||||
}
|
||||
.gray .sub-panel hr {
|
||||
border: none;
|
||||
border-top: solid 1px #444;
|
||||
}
|
||||
.gray .side-panel {
|
||||
background: #303030;
|
||||
box-shadow: 0px 0px 30px 0px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.gray .side-panel:not(:empty):hover:after {
|
||||
color: silver;
|
||||
}
|
||||
.gray .side-panel.left {
|
||||
border-right: solid 1px #444;
|
||||
}
|
||||
.gray .side-panel.right {
|
||||
border-left: solid 1px #444;
|
||||
}
|
||||
|
||||
637
Viewer/css/experimenting.css
Executable file
637
Viewer/css/experimenting.css
Executable file
@ -0,0 +1,637 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
* This module is intended for quick and dirty tests and CSS experiments
|
||||
*
|
||||
* For production move the code from here to a more appropriate location
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
/* this will prevent odd blur effects when blurring out specific
|
||||
elements... */
|
||||
background: black;
|
||||
}
|
||||
|
||||
/* show image gid... */
|
||||
.visible-gid .image:after {
|
||||
content: attr(gid);
|
||||
display: block;
|
||||
position: relative;
|
||||
color: red;
|
||||
margin: 10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
border: solid 2px red;
|
||||
border-radius: 50%;
|
||||
background: black;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
.image.current {
|
||||
border-color: red;
|
||||
}
|
||||
*/
|
||||
|
||||
.shadow {
|
||||
margin: 0px;
|
||||
z-index: 4000;
|
||||
}
|
||||
.ui-bounds-indicators {
|
||||
z-index: 5000;
|
||||
}
|
||||
|
||||
|
||||
.viewer:not(.no-transitions) .range-offscreen-indicator:not(.no-transitions) {
|
||||
transition: all 0.1s linear;
|
||||
}
|
||||
|
||||
|
||||
/* basic animation... */
|
||||
.viewer:not(.no-transitions) {
|
||||
-webkit-transition: background-color 0.8s ease;
|
||||
-moz-transition: background-color 0.8s ease;
|
||||
-ms-transition: background-color 0.8s ease;
|
||||
-o-transition: background-color 0.8s ease;
|
||||
transition: background-color 0.8s ease;
|
||||
}
|
||||
|
||||
.viewer:not(.no-transitions) .ribbon-set:not(.no-transitions) {
|
||||
-webkit-transition: all 0.1s linear, transform 0.1s linear;
|
||||
-moz-transition: all 0.1s linear, transform 0.1s linear;
|
||||
-ms-transition: all 0.1s linear, transform 0.1s linear;
|
||||
-o-transition: all 0.1s linear, transform 0.1s linear;
|
||||
transition: all 0.1s linear, transform 0.1s linear;
|
||||
}
|
||||
.viewer:not(.no-transitions) .ribbon-locator:not(.no-transitions) {
|
||||
-webkit-transition: all 0.1s linear, transform 0.1s linear;
|
||||
-moz-transition: all 0.1s linear, transform 0.1s linear;
|
||||
-ms-transition: all 0.1s linear, transform 0.1s linear;
|
||||
-o-transition: all 0.1s linear, transform 0.1s linear;
|
||||
transition: all 0.1s linear, transform 0.1s linear;
|
||||
}
|
||||
|
||||
.viewer:not(.no-transitions) .ribbon:not(.no-transitions) {
|
||||
-webkit-transition: all 0.1s ease-out;
|
||||
-moz-transition: all 0.1s ease-out;
|
||||
-ms-transition: all 0.1s ease-out;
|
||||
-o-transition: all 0.1s ease-out;
|
||||
transition: all 0.1s ease-out;
|
||||
}
|
||||
|
||||
/* XXX not 100% sure about this...
|
||||
*/
|
||||
:not(.no-transitions) .current-marker:not(.no-transitions) {
|
||||
-webkit-transition: transform 0.1s ease-out;
|
||||
-moz-transition: transform 0.1s ease-out;
|
||||
-ms-transition: transform 0.1s ease-out;
|
||||
-o-transition: transform 0.1s ease-out;
|
||||
transition: transform 0.1s ease-out;
|
||||
}
|
||||
|
||||
/* XXX should this be !important */
|
||||
.no-transitions {
|
||||
-webkit-transition: none !important;
|
||||
-moz-transition: none !important;
|
||||
-ms-transition: none !important;
|
||||
-o-transition: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
/* XXX think of a way not to use !important */
|
||||
.single-image-mode .ribbon,
|
||||
.single-image-mode .ribbon-set {
|
||||
-webkit-transition: none !important;
|
||||
-moz-transition: none !important;
|
||||
-ms-transition: none !important;
|
||||
-o-transition: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
|
||||
/* TEST: this is mostly speed limited... */
|
||||
.ribbon {
|
||||
box-shadow: 5px 5px 50px -5px rgba(0, 0, 0, 0.2);
|
||||
|
||||
-webkit-transition: box-shadow 0.8s ease;
|
||||
-moz-transition: box-shadow 0.8s ease;
|
||||
-ms-transition: box-shadow 0.8s ease;
|
||||
-o-transition: box-shadow 0.8s ease;
|
||||
transition: box-shadow 0.8s ease;
|
||||
}
|
||||
.current.ribbon {
|
||||
box-shadow: 5px 5px 60px -5px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.single-image-mode .ribbon {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.viewer .lock-clicks {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
cursor: auto;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
.image-bw {
|
||||
/*filter: saturate(0);*/
|
||||
filter: grayscale(1);
|
||||
}
|
||||
.image-show-shadows {
|
||||
filter: contrast(0.8) brightness(3) contrast(1.5);
|
||||
}
|
||||
.image-show-highlights {
|
||||
filter: contrast(0.8) brightness(0.6) contrast(1.2);
|
||||
}
|
||||
.image-edge-detect {
|
||||
filter: url(#EdgeDetect);
|
||||
/* XXX this does not work in chrome + local file... */
|
||||
/*filter: url(filters.svg#EdgeDetect);*/
|
||||
}
|
||||
.image-shadows-and-highlights {
|
||||
filter: url(#ShadowsAndHilights);
|
||||
}
|
||||
.image-gamma-shadows {
|
||||
filter: url(#GammaShadows);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
/* scrollbar setup... */
|
||||
.browse-widget ::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
.browse-widget * {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: auto;
|
||||
}
|
||||
.browse-widget ::-webkit-scrollbar-button {
|
||||
display: none;
|
||||
}
|
||||
.browse-widget ::-webkit-scrollbar-track {
|
||||
}
|
||||
.browse-widget ::-webkit-scrollbar-track-piece {
|
||||
background: transparent;
|
||||
}
|
||||
.browse-widget ::-webkit-scrollbar-track-piece {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.browse-widget ::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.browse-widget ::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.browse-widget ::-webkit-scrollbar-corner {
|
||||
}
|
||||
.browse-widget ::-webkit-resizer {
|
||||
}
|
||||
|
||||
|
||||
.keyboard-shortcut {
|
||||
text-decoration: none !important;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.keyboard-shortcut:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: -0.2em;
|
||||
border-top: solid 0.1em silver;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.browse-widget.cloud-view .list .item {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
|
||||
/* browse pinned items... */
|
||||
.browse-widget .list .item:not(.pinned) .pin-set {
|
||||
display: none;
|
||||
}
|
||||
.browse-widget .list .item.pinned .pin-unset {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.browse-widget .list .item .sort-handle {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
|
||||
padding-left: 0.2em;
|
||||
padding-right: 0.5em;
|
||||
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
.browse-widget .list .item.pinned + :not(.pinned) {
|
||||
border-top: solid 1px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
*/
|
||||
.browse-widget .list .pinned-separator:first-child,
|
||||
.browse-widget .list .pinned-separator:last-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* Metadata viewer */
|
||||
.item-value-view .text:first-child,
|
||||
.browse-widget.table-view .list .item .text:first-child {
|
||||
width: 50%;
|
||||
font-weight: bold;
|
||||
}
|
||||
.item-value-view .text:nth-child(2),
|
||||
.browse-widget.table-view .list .item .text:nth-child(2) {
|
||||
font-style: italic;
|
||||
|
||||
-moz-user-select: auto;
|
||||
-webkit-user-select: auto;
|
||||
-o-user-select: auto;
|
||||
-ms-user-select: auto;
|
||||
user-select: auto;
|
||||
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
/* Collection list */
|
||||
.browse-widget.collection-list .list .item .text[cropped]:after {
|
||||
content: "(cropped)";
|
||||
margin-left: 5px;
|
||||
opacity: 0.5;
|
||||
font-style: italic;
|
||||
}
|
||||
.browse-widget.collection-list .list .item .text[unsaved]:after {
|
||||
content: "*";
|
||||
margin-left: 0px;
|
||||
opacity: 0.5;
|
||||
font-style: italic;
|
||||
}
|
||||
.browse-widget.collection-list .list .item .text[unsaved][cropped]:after {
|
||||
content: "* (cropped)";
|
||||
margin-left: 0px;
|
||||
opacity: 0.5;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Feature list */
|
||||
.browse-widget.feature-list .list .item[root="true"] .text:after {
|
||||
content: "*";
|
||||
margin-left: 0px;
|
||||
opacity: 0.5;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* External Editor List */
|
||||
.browse-widget.editor-list .list .item:first-child .text:after {
|
||||
content: "(primary)";
|
||||
margin-left: 5px;
|
||||
opacity: 0.5;
|
||||
font-style: italic;
|
||||
}
|
||||
/* XXX this is ugly -- use a class... */
|
||||
.browse-widget.editor-list .list .item:first-child .button:nth-child(4) {
|
||||
opacity: 0.1;
|
||||
}
|
||||
.browse-widget.editor-list .list .item:nth-child(2):not(:last-child) .text:after {
|
||||
content: "(secondary)";
|
||||
margin-left: 5px;
|
||||
opacity: 0.5;
|
||||
font-style: italic;
|
||||
}
|
||||
/* XXX this is ugly -- use a class... */
|
||||
.browse-widget.editor-list .list .item:nth-child(2) .button:nth-child(3) {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
/* slideshow interval list... */
|
||||
.browse-widget.tail-action .list .item:last-child {
|
||||
margin-top: 0.2em;
|
||||
border-top: solid 1px rgba(255,255,255, 0.2);
|
||||
}
|
||||
.browse-widget.tail-action .list .item:last-child .text {
|
||||
font-style: italic;
|
||||
}
|
||||
.browse-widget.tail-action .list .item:last-child .button {
|
||||
/*display: none;*/
|
||||
}
|
||||
|
||||
/* do not show top border if after another action or separator... */
|
||||
.browse-widget .list>.warn {
|
||||
background-color: yellow !important;
|
||||
color: red !important;
|
||||
font-weight: bolder !important;
|
||||
}
|
||||
|
||||
/* Dialog highlight experiment... */
|
||||
.browse-widget {
|
||||
border-bottom: solid 7px rgba(100, 100, 100, 0.5);
|
||||
}
|
||||
/*
|
||||
.light .browse-widget {
|
||||
border-bottom: solid 7px rgba(100, 100, 100, 0.5);
|
||||
}
|
||||
.dark .browse-widget {
|
||||
border-bottom: solid 7px rgba(100, 100, 100, 0.5);
|
||||
}
|
||||
*/
|
||||
|
||||
.browse-widget.file-browser {
|
||||
border-bottom: solid 7px rgba(255, 255, 0, 0.5);
|
||||
}
|
||||
/*
|
||||
.light .browse-widget.file-browser {
|
||||
border-bottom: solid 7px rgba(255, 255, 0, 0.5);
|
||||
}
|
||||
.dark .browse-widget.file-browser {
|
||||
border-bottom: solid 7px rgba(255, 255, 0, 0.5);
|
||||
}
|
||||
*/
|
||||
/* XXX not sure about these... */
|
||||
.browse-widget.save-history,
|
||||
.browse-widget.location-history {
|
||||
border-bottom: solid 7px rgba(0, 0, 255, 0.2);
|
||||
}
|
||||
|
||||
/* XXX experimental key mappings... */
|
||||
.browse-widget.show-keys .list .item:after {
|
||||
display: inline;
|
||||
position: relative;
|
||||
content: attr(keys);
|
||||
|
||||
float: right;
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
|
||||
opacity: 0.3;
|
||||
font-style: italic;
|
||||
}
|
||||
.browse-widget.show-keys .list .item.disabled:after {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.browse-widget.show-keys .list .item:hover:after {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.browse-widget.show-keys .list .item.disabled:hover:after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* make buttons a bit different in action browser... */
|
||||
.browse-widget.browse-actions .list .button {
|
||||
background-color: rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
.dark .browse-widget.browse-actions .list .button {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* key binding editor... */
|
||||
.browse-widget.key-bindings .list .item:not(.selected):not(.mode):nth-child(even) {
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
.browse-widget.key-bindings .list .item .button {
|
||||
background-color: rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.browse-widget.key-bindings .list>.drop-list .text:first-child {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
.browse-widget.key-bindings .list>.special-action .text:first-child {
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
.browse-widget.key-bindings .list>.info {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.browse-widget.key-bindings .list .item .text:not(:first-child) {
|
||||
display: inline;
|
||||
position: relative;
|
||||
|
||||
float: right;
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
|
||||
opacity: 0.8;
|
||||
font-style: italic;
|
||||
}
|
||||
/* NOTE: the last element is a space... */
|
||||
.browse-widget.key-bindings.browse .list .item .text:last-child {
|
||||
margin-right: 0em;
|
||||
}
|
||||
|
||||
/* key doc... */
|
||||
.browse-widget.key-bindings .list>.key[doc]:after {
|
||||
display: inline;
|
||||
content: " -- " attr(doc);
|
||||
font-style: italic;
|
||||
opacity: 0.5;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
.browse-widget.key-bindings.edit .list>.key.non-action:before {
|
||||
display: inline;
|
||||
content: " (non-action) ";
|
||||
opacity: 0.5;
|
||||
margin-left: 0.5em;
|
||||
float: none;
|
||||
}
|
||||
|
||||
|
||||
.browse-widget.key-bindings .list>.new {
|
||||
font-style: italic;
|
||||
}
|
||||
.browse-widget.key-bindings .list>.new:not(.selected) {
|
||||
opacity: 0.3;
|
||||
}
|
||||
.browse-widget.key-bindings .list>.new.selected {
|
||||
}
|
||||
|
||||
|
||||
/* dark theme... */
|
||||
.dark .browse-widget.key-bindings .list .item:not(.selected):not(.mode):nth-child(even) {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
/* light theme... */
|
||||
.light .browse-widget.key-bindings .list>.key[doc]:after {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* help... */
|
||||
.help-dialog {
|
||||
max-width: 80vw;
|
||||
max-height: 80vh;
|
||||
|
||||
overflow: auto;
|
||||
|
||||
padding: 1em;
|
||||
padding-bottom: 2em;
|
||||
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* scrollbar setup... */
|
||||
.help-dialog::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
.help-dialog::-webkit-scrollbar-button {
|
||||
display: none;
|
||||
}
|
||||
.help-dialog::-webkit-scrollbar-track {
|
||||
}
|
||||
.help-dialog::-webkit-scrollbar-track-piece {
|
||||
background: transparent;
|
||||
}
|
||||
.help-dialog::-webkit-scrollbar-track-piece {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.help-dialog::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.help-dialog::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.help-dialog::-webkit-scrollbar-corner {
|
||||
}
|
||||
.help-dialog::-webkit-resizer {
|
||||
}
|
||||
|
||||
|
||||
.help-dialog .comment {
|
||||
color: gray;
|
||||
font-style: italic;
|
||||
}
|
||||
.help-dialog .warning {
|
||||
color: blue;
|
||||
background: yellow;
|
||||
}
|
||||
|
||||
|
||||
/* metadata view */
|
||||
/* remove preview text and center image... */
|
||||
.metadata-view .item.index {
|
||||
text-align: center;
|
||||
}
|
||||
.metadata-view .item.preview {
|
||||
text-align: center;
|
||||
opacity: 1;
|
||||
}
|
||||
.metadata-view .index .text,
|
||||
.metadata-view .preview .text {
|
||||
float: initial;
|
||||
}
|
||||
.metadata-view .index .text:first-child,
|
||||
.metadata-view .preview .text:first-child {
|
||||
left: 50%;
|
||||
display: none;
|
||||
}
|
||||
.metadata-view .preview.image {
|
||||
border: rgba(200, 200, 200, 0.3) 1px solid;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.metadata-view .text+.text {
|
||||
font-style: italic;
|
||||
}
|
||||
.metadata-view .text+.text+.text {
|
||||
margin-left: 1em;
|
||||
}
|
||||
.metadata-view small span {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.metadata-view small small {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
|
||||
/* image gaps */
|
||||
.image:not(:first-of-type) {
|
||||
margin-left: var(--image-gap-size);
|
||||
}
|
||||
/* image gap indicator */
|
||||
/* XXX not sure we need this yet...
|
||||
.image:before {
|
||||
content: var(--image-gap-text);
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0.5em;
|
||||
left: 0px;
|
||||
background:gray;
|
||||
border-radius: 1em;
|
||||
padding: 0.2em 0.5em;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/* DEBUG stuff... */
|
||||
.container-center {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
|
||||
border-top: solid 1px black;
|
||||
border-left: solid 1px black;
|
||||
}
|
||||
.container-center:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -11px;
|
||||
left: -11px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
border-bottom: solid 1px black;
|
||||
border-right: solid 1px black;
|
||||
}
|
||||
.point {
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border: solid 1px blue;
|
||||
margin-top: -2px;
|
||||
margin-left: -2px;
|
||||
|
||||
background: yellow;
|
||||
|
||||
transition-origin: 50% 50%;
|
||||
|
||||
z-index: 9999;
|
||||
}
|
||||
.point {
|
||||
-webkit-transition: all 0.2s linear;
|
||||
-moz-transition: all 0.2s linear;
|
||||
transition: all 0.2s linear;
|
||||
}
|
||||
/* DEBUG end */
|
||||
|
||||
32
Viewer/css/filters.svg
Executable file
32
Viewer/css/filters.svg
Executable file
@ -0,0 +1,32 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="0%" height="0%" class="svg-filters">
|
||||
|
||||
<!-- edge detect -->
|
||||
<filter id="EdgeDetect">
|
||||
<!--feConvolveMatrix order="3 3" preserveAlpha="true" divisor="1" bias="-1.4" kernelMatrix="-1 -1 -1 -1 9 -1 -1 -1 -1"/-->
|
||||
<feConvolveMatrix order="3 3" preserveAlpha="true" kernelMatrix="-1 -1 -1 -1 8 -1 -1 -1 -1"/>
|
||||
</filter>
|
||||
|
||||
<!-- shadows and highlights
|
||||
gradient map: [blue 0-5% black 93-96% white]
|
||||
via: https://justcode.today/filters/ -->
|
||||
<filter id="ShadowsAndHilights">
|
||||
<fecolormatrix type="saturate" values="0" />
|
||||
<feComponentTransfer color-interpolation-filters="sRGB" result="cutoff">
|
||||
<feFuncR type="table" tableValues="0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.33,0.67,1,1,1,1"/>
|
||||
<feFuncG type="table" tableValues="0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.33,0.67,1,1,1,1"/>
|
||||
<feFuncB type="table" tableValues="1,0.8,0.6,0.4,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.33,0.67,1,1,1,1"/>
|
||||
</feComponentTransfer>
|
||||
<feBlend mode="difference" in="SourceGraphic" in2="cutoff"/>
|
||||
</filter>
|
||||
|
||||
<!-- gamma shadows -->
|
||||
<filter id="GammaShadows">
|
||||
<feComponentTransfer color-interpolation-filters="sRGB">
|
||||
<feFuncR type="gamma" exponent="0.3" amplitude="1.0" offset="0"></feFuncR>
|
||||
<feFuncG type="gamma" exponent="0.3" amplitude="1.0" offset="0"></feFuncG>
|
||||
<feFuncB type="gamma" exponent="0.3" amplitude="1.0" offset="0"></feFuncB>
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
|
||||
</svg>
|
||||
<!-- vim:set sw=4 ts=4 : -->
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@ -1,230 +1,273 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
/* Open Sans
|
||||
* - line spacing a bit too large...
|
||||
*/
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(fonts/Open_Sans/OpenSans-Regular.ttf)
|
||||
}
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(fonts/Open_Sans/OpenSans-Bold.ttf)
|
||||
font-weight: bold;
|
||||
}
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(fonts/Open_Sans/OpenSans-Italic.ttf)
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(fonts/Open_Sans/OpenSans-BoldItalic.ttf)
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(fonts/Open_Sans/OpenSans-ExtraBold.ttf)
|
||||
font-weight: bolder;
|
||||
}
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(fonts/Open_Sans/OpenSans-ExtraBoldItalic.ttf)
|
||||
font-weight: bolder;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(fonts/Open_Sans/OpenSans-Light.ttf)
|
||||
font-weight: lighter;
|
||||
}
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(fonts/Open_Sans/OpenSans-LightItalic.ttf)
|
||||
font-weight: lighter;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Work Sans
|
||||
* - does not have an explicit italic face but seems to look ok when
|
||||
* slanted programmatically...
|
||||
*/
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-Thin.ttf);
|
||||
font-weight: 100;
|
||||
}
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-ExtraLight.ttf);
|
||||
font-weight: 200;
|
||||
}
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-Light.ttf);
|
||||
font-weight: 300;
|
||||
}
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-Regular.ttf);
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-Medium.ttf);
|
||||
font-weight: 500;
|
||||
}
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-SemiBold.ttf);
|
||||
font-weight: 600;
|
||||
}
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-Bold.ttf);
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-ExtraBold.ttf);
|
||||
font-weight: 800;
|
||||
}
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-Black.ttf);
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Source Sans Pro */
|
||||
/*
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-Regular.ttf)
|
||||
}
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-Bold.ttf)
|
||||
font-weight: bold;
|
||||
}
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-Italic.ttf)
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-BoldItalic.ttf)
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-ExtraBold.ttf)
|
||||
font-weight: bolder;
|
||||
}
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-ExtraBoldItalic.ttf)
|
||||
font-weight: bolder;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-Light.ttf)
|
||||
font-weight: lighter;
|
||||
}
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-LightItalic.ttf)
|
||||
font-weight: lighter;
|
||||
font-style: italic;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/* Arimo
|
||||
* - bold face a bit too narrow
|
||||
*/
|
||||
/*
|
||||
@font-face {
|
||||
font-family: Arimo;
|
||||
src: url(fonts/Arimo/Arimo-Regular.ttf)
|
||||
}
|
||||
@font-face {
|
||||
font-family: Arimo;
|
||||
src: url(fonts/Arimo/Arimo-Bold.ttf)
|
||||
font-weight: bold;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Arimo;
|
||||
src: url(fonts/Arimo/Arimo-Italic.ttf)
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Arimo;
|
||||
src: url(fonts/Arimo/Arimo-BoldItalic.ttf)
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/* Roboto */
|
||||
/*
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-Regular.ttf)
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-Bold.ttf)
|
||||
font-weight: bold;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-Italic.ttf)
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-BoldItalic.ttf)
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-ExtraBold.ttf)
|
||||
font-weight: bolder;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-ExtraBoldItalic.ttf)
|
||||
font-weight: bolder;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-Light.ttf)
|
||||
font-weight: lighter;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-LightItalic.ttf)
|
||||
font-weight: lighter;
|
||||
font-style: italic;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
/* Open Sans
|
||||
* - line spacing a bit too large...
|
||||
*/
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(fonts/Open_Sans/OpenSans-Regular.ttf);
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'OpenSans-Light';
|
||||
src: url(fonts/Open_Sans/OpenSans-Light.ttf);
|
||||
}
|
||||
/*
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(fonts/Open_Sans/OpenSans-Bold.ttf);
|
||||
font-weight: bold;
|
||||
}
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(fonts/Open_Sans/OpenSans-Italic.ttf);
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(fonts/Open_Sans/OpenSans-BoldItalic.ttf);
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(fonts/Open_Sans/OpenSans-ExtraBold.ttf);
|
||||
font-weight: bolder;
|
||||
}
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(fonts/Open_Sans/OpenSans-ExtraBoldItalic.ttf);
|
||||
font-weight: bolder;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(fonts/Open_Sans/OpenSans-LightItalic.ttf);
|
||||
font-weight: lighter;
|
||||
font-style: italic;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/* Work Sans
|
||||
* - does not have an explicit italic face but seems to look ok when
|
||||
* slanted programmatically...
|
||||
*/
|
||||
/*
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-Thin.ttf);
|
||||
font-weight: 100;
|
||||
}
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-ExtraLight.ttf);
|
||||
font-weight: 200;
|
||||
}
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-Light.ttf);
|
||||
font-weight: 300;
|
||||
}
|
||||
*/
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-Regular.ttf);
|
||||
font-weight: 400;
|
||||
}
|
||||
/*
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-Medium.ttf);
|
||||
font-weight: 500;
|
||||
}
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-SemiBold.ttf);
|
||||
font-weight: 600;
|
||||
}
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-Bold.ttf);
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-ExtraBold.ttf);
|
||||
font-weight: 800;
|
||||
}
|
||||
@font-face {
|
||||
font-family: WorkSans;
|
||||
src: url(fonts/Work_Sans/WorkSans-Black.ttf);
|
||||
font-weight: 900;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/* Source Sans Pro */
|
||||
/*
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-Regular.ttf);
|
||||
}
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-Bold.ttf);
|
||||
font-weight: bold;
|
||||
}
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-Italic.ttf);
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-BoldItalic.ttf);
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-ExtraBold.ttf);
|
||||
font-weight: bolder;
|
||||
}
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-ExtraBoldItalic.ttf);
|
||||
font-weight: bolder;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-Light.ttf);
|
||||
font-weight: lighter;
|
||||
}
|
||||
@font-face {
|
||||
font-family: SourceSansPro;
|
||||
src: url(fonts/Source_Sans_Pro/SourceSansPro-LightItalic.ttf);
|
||||
font-weight: lighter;
|
||||
font-style: italic;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/* Arimo
|
||||
* - bold face a bit too narrow
|
||||
*/
|
||||
/*
|
||||
@font-face {
|
||||
font-family: Arimo;
|
||||
src: url(fonts/Arimo/Arimo-Regular.ttf);
|
||||
}
|
||||
@font-face {
|
||||
font-family: Arimo;
|
||||
src: url(fonts/Arimo/Arimo-Bold.ttf);
|
||||
font-weight: bold;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Arimo;
|
||||
src: url(fonts/Arimo/Arimo-Italic.ttf);
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Arimo;
|
||||
src: url(fonts/Arimo/Arimo-BoldItalic.ttf);
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/* Roboto */
|
||||
/*
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-Regular.ttf);
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-Bold.ttf);
|
||||
font-weight: bold;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-Italic.ttf);
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-BoldItalic.ttf);
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-ExtraBold.ttf);
|
||||
font-weight: bolder;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-ExtraBoldItalic.ttf);
|
||||
font-weight: bolder;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-Light.ttf);
|
||||
font-weight: lighter;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(fonts/Roboto/Roboto-LightItalic.ttf);
|
||||
font-weight: lighter;
|
||||
font-style: italic;
|
||||
}
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(fonts/MaterialIcons/MaterialIcons-Regular.eot); /* IE6-8 */
|
||||
src: local('Material Icons'),
|
||||
local('MaterialIcons-Regular'),
|
||||
url(fonts/MaterialIcons/MaterialIcons-Regular.woff2) format('woff2'),
|
||||
url(fonts/MaterialIcons/MaterialIcons-Regular.woff) format('woff'),
|
||||
url(fonts/MaterialIcons/MaterialIcons-Regular.ttf) format('truetype');
|
||||
}
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
/*
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
*/
|
||||
|
||||
vertical-align: middle;
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility; /* Safari and Chrome */
|
||||
font-feature-settings: 'liga'; /* IE */
|
||||
}
|
||||
.material-icons.md-16 { font-size: 16px; }
|
||||
.material-icons.md-18 { font-size: 18px; }
|
||||
.material-icons.md-24 { font-size: 24px; }
|
||||
.material-icons.md-36 { font-size: 36px; }
|
||||
.material-icons.md-48 { font-size: 48px; }
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
202
Viewer/css/fonts/MaterialIcons/LICENSE
Executable file
202
Viewer/css/fonts/MaterialIcons/LICENSE
Executable file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
BIN
Viewer/css/fonts/MaterialIcons/MaterialIcons-Regular.eot
Executable file
BIN
Viewer/css/fonts/MaterialIcons/MaterialIcons-Regular.eot
Executable file
Binary file not shown.
1
Viewer/css/fonts/MaterialIcons/MaterialIcons-Regular.ijmap
Executable file
1
Viewer/css/fonts/MaterialIcons/MaterialIcons-Regular.ijmap
Executable file
File diff suppressed because one or more lines are too long
2373
Viewer/css/fonts/MaterialIcons/MaterialIcons-Regular.svg
Executable file
2373
Viewer/css/fonts/MaterialIcons/MaterialIcons-Regular.svg
Executable file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 275 KiB |
BIN
Viewer/css/fonts/MaterialIcons/MaterialIcons-Regular.ttf
Executable file
BIN
Viewer/css/fonts/MaterialIcons/MaterialIcons-Regular.ttf
Executable file
Binary file not shown.
BIN
Viewer/css/fonts/MaterialIcons/MaterialIcons-Regular.woff
Executable file
BIN
Viewer/css/fonts/MaterialIcons/MaterialIcons-Regular.woff
Executable file
Binary file not shown.
BIN
Viewer/css/fonts/MaterialIcons/MaterialIcons-Regular.woff2
Executable file
BIN
Viewer/css/fonts/MaterialIcons/MaterialIcons-Regular.woff2
Executable file
Binary file not shown.
9
Viewer/css/fonts/MaterialIcons/README.md
Executable file
9
Viewer/css/fonts/MaterialIcons/README.md
Executable file
@ -0,0 +1,9 @@
|
||||
The recommended way to use the Material Icons font is by linking to the web font hosted on Google Fonts:
|
||||
|
||||
```html
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||
rel="stylesheet">
|
||||
```
|
||||
|
||||
Read more in our full usage guide:
|
||||
http://google.github.io/material-design-icons/#icon-font-for-the-web
|
||||
932
Viewer/css/fonts/MaterialIcons/codepoints
Executable file
932
Viewer/css/fonts/MaterialIcons/codepoints
Executable file
@ -0,0 +1,932 @@
|
||||
3d_rotation e84d
|
||||
ac_unit eb3b
|
||||
access_alarm e190
|
||||
access_alarms e191
|
||||
access_time e192
|
||||
accessibility e84e
|
||||
accessible e914
|
||||
account_balance e84f
|
||||
account_balance_wallet e850
|
||||
account_box e851
|
||||
account_circle e853
|
||||
adb e60e
|
||||
add e145
|
||||
add_a_photo e439
|
||||
add_alarm e193
|
||||
add_alert e003
|
||||
add_box e146
|
||||
add_circle e147
|
||||
add_circle_outline e148
|
||||
add_location e567
|
||||
add_shopping_cart e854
|
||||
add_to_photos e39d
|
||||
add_to_queue e05c
|
||||
adjust e39e
|
||||
airline_seat_flat e630
|
||||
airline_seat_flat_angled e631
|
||||
airline_seat_individual_suite e632
|
||||
airline_seat_legroom_extra e633
|
||||
airline_seat_legroom_normal e634
|
||||
airline_seat_legroom_reduced e635
|
||||
airline_seat_recline_extra e636
|
||||
airline_seat_recline_normal e637
|
||||
airplanemode_active e195
|
||||
airplanemode_inactive e194
|
||||
airplay e055
|
||||
airport_shuttle eb3c
|
||||
alarm e855
|
||||
alarm_add e856
|
||||
alarm_off e857
|
||||
alarm_on e858
|
||||
album e019
|
||||
all_inclusive eb3d
|
||||
all_out e90b
|
||||
android e859
|
||||
announcement e85a
|
||||
apps e5c3
|
||||
archive e149
|
||||
arrow_back e5c4
|
||||
arrow_downward e5db
|
||||
arrow_drop_down e5c5
|
||||
arrow_drop_down_circle e5c6
|
||||
arrow_drop_up e5c7
|
||||
arrow_forward e5c8
|
||||
arrow_upward e5d8
|
||||
art_track e060
|
||||
aspect_ratio e85b
|
||||
assessment e85c
|
||||
assignment e85d
|
||||
assignment_ind e85e
|
||||
assignment_late e85f
|
||||
assignment_return e860
|
||||
assignment_returned e861
|
||||
assignment_turned_in e862
|
||||
assistant e39f
|
||||
assistant_photo e3a0
|
||||
attach_file e226
|
||||
attach_money e227
|
||||
attachment e2bc
|
||||
audiotrack e3a1
|
||||
autorenew e863
|
||||
av_timer e01b
|
||||
backspace e14a
|
||||
backup e864
|
||||
battery_alert e19c
|
||||
battery_charging_full e1a3
|
||||
battery_full e1a4
|
||||
battery_std e1a5
|
||||
battery_unknown e1a6
|
||||
beach_access eb3e
|
||||
beenhere e52d
|
||||
block e14b
|
||||
bluetooth e1a7
|
||||
bluetooth_audio e60f
|
||||
bluetooth_connected e1a8
|
||||
bluetooth_disabled e1a9
|
||||
bluetooth_searching e1aa
|
||||
blur_circular e3a2
|
||||
blur_linear e3a3
|
||||
blur_off e3a4
|
||||
blur_on e3a5
|
||||
book e865
|
||||
bookmark e866
|
||||
bookmark_border e867
|
||||
border_all e228
|
||||
border_bottom e229
|
||||
border_clear e22a
|
||||
border_color e22b
|
||||
border_horizontal e22c
|
||||
border_inner e22d
|
||||
border_left e22e
|
||||
border_outer e22f
|
||||
border_right e230
|
||||
border_style e231
|
||||
border_top e232
|
||||
border_vertical e233
|
||||
branding_watermark e06b
|
||||
brightness_1 e3a6
|
||||
brightness_2 e3a7
|
||||
brightness_3 e3a8
|
||||
brightness_4 e3a9
|
||||
brightness_5 e3aa
|
||||
brightness_6 e3ab
|
||||
brightness_7 e3ac
|
||||
brightness_auto e1ab
|
||||
brightness_high e1ac
|
||||
brightness_low e1ad
|
||||
brightness_medium e1ae
|
||||
broken_image e3ad
|
||||
brush e3ae
|
||||
bubble_chart e6dd
|
||||
bug_report e868
|
||||
build e869
|
||||
burst_mode e43c
|
||||
business e0af
|
||||
business_center eb3f
|
||||
cached e86a
|
||||
cake e7e9
|
||||
call e0b0
|
||||
call_end e0b1
|
||||
call_made e0b2
|
||||
call_merge e0b3
|
||||
call_missed e0b4
|
||||
call_missed_outgoing e0e4
|
||||
call_received e0b5
|
||||
call_split e0b6
|
||||
call_to_action e06c
|
||||
camera e3af
|
||||
camera_alt e3b0
|
||||
camera_enhance e8fc
|
||||
camera_front e3b1
|
||||
camera_rear e3b2
|
||||
camera_roll e3b3
|
||||
cancel e5c9
|
||||
card_giftcard e8f6
|
||||
card_membership e8f7
|
||||
card_travel e8f8
|
||||
casino eb40
|
||||
cast e307
|
||||
cast_connected e308
|
||||
center_focus_strong e3b4
|
||||
center_focus_weak e3b5
|
||||
change_history e86b
|
||||
chat e0b7
|
||||
chat_bubble e0ca
|
||||
chat_bubble_outline e0cb
|
||||
check e5ca
|
||||
check_box e834
|
||||
check_box_outline_blank e835
|
||||
check_circle e86c
|
||||
chevron_left e5cb
|
||||
chevron_right e5cc
|
||||
child_care eb41
|
||||
child_friendly eb42
|
||||
chrome_reader_mode e86d
|
||||
class e86e
|
||||
clear e14c
|
||||
clear_all e0b8
|
||||
close e5cd
|
||||
closed_caption e01c
|
||||
cloud e2bd
|
||||
cloud_circle e2be
|
||||
cloud_done e2bf
|
||||
cloud_download e2c0
|
||||
cloud_off e2c1
|
||||
cloud_queue e2c2
|
||||
cloud_upload e2c3
|
||||
code e86f
|
||||
collections e3b6
|
||||
collections_bookmark e431
|
||||
color_lens e3b7
|
||||
colorize e3b8
|
||||
comment e0b9
|
||||
compare e3b9
|
||||
compare_arrows e915
|
||||
computer e30a
|
||||
confirmation_number e638
|
||||
contact_mail e0d0
|
||||
contact_phone e0cf
|
||||
contacts e0ba
|
||||
content_copy e14d
|
||||
content_cut e14e
|
||||
content_paste e14f
|
||||
control_point e3ba
|
||||
control_point_duplicate e3bb
|
||||
copyright e90c
|
||||
create e150
|
||||
create_new_folder e2cc
|
||||
credit_card e870
|
||||
crop e3be
|
||||
crop_16_9 e3bc
|
||||
crop_3_2 e3bd
|
||||
crop_5_4 e3bf
|
||||
crop_7_5 e3c0
|
||||
crop_din e3c1
|
||||
crop_free e3c2
|
||||
crop_landscape e3c3
|
||||
crop_original e3c4
|
||||
crop_portrait e3c5
|
||||
crop_rotate e437
|
||||
crop_square e3c6
|
||||
dashboard e871
|
||||
data_usage e1af
|
||||
date_range e916
|
||||
dehaze e3c7
|
||||
delete e872
|
||||
delete_forever e92b
|
||||
delete_sweep e16c
|
||||
description e873
|
||||
desktop_mac e30b
|
||||
desktop_windows e30c
|
||||
details e3c8
|
||||
developer_board e30d
|
||||
developer_mode e1b0
|
||||
device_hub e335
|
||||
devices e1b1
|
||||
devices_other e337
|
||||
dialer_sip e0bb
|
||||
dialpad e0bc
|
||||
directions e52e
|
||||
directions_bike e52f
|
||||
directions_boat e532
|
||||
directions_bus e530
|
||||
directions_car e531
|
||||
directions_railway e534
|
||||
directions_run e566
|
||||
directions_subway e533
|
||||
directions_transit e535
|
||||
directions_walk e536
|
||||
disc_full e610
|
||||
dns e875
|
||||
do_not_disturb e612
|
||||
do_not_disturb_alt e611
|
||||
do_not_disturb_off e643
|
||||
do_not_disturb_on e644
|
||||
dock e30e
|
||||
domain e7ee
|
||||
done e876
|
||||
done_all e877
|
||||
donut_large e917
|
||||
donut_small e918
|
||||
drafts e151
|
||||
drag_handle e25d
|
||||
drive_eta e613
|
||||
dvr e1b2
|
||||
edit e3c9
|
||||
edit_location e568
|
||||
eject e8fb
|
||||
email e0be
|
||||
enhanced_encryption e63f
|
||||
equalizer e01d
|
||||
error e000
|
||||
error_outline e001
|
||||
euro_symbol e926
|
||||
ev_station e56d
|
||||
event e878
|
||||
event_available e614
|
||||
event_busy e615
|
||||
event_note e616
|
||||
event_seat e903
|
||||
exit_to_app e879
|
||||
expand_less e5ce
|
||||
expand_more e5cf
|
||||
explicit e01e
|
||||
explore e87a
|
||||
exposure e3ca
|
||||
exposure_neg_1 e3cb
|
||||
exposure_neg_2 e3cc
|
||||
exposure_plus_1 e3cd
|
||||
exposure_plus_2 e3ce
|
||||
exposure_zero e3cf
|
||||
extension e87b
|
||||
face e87c
|
||||
fast_forward e01f
|
||||
fast_rewind e020
|
||||
favorite e87d
|
||||
favorite_border e87e
|
||||
featured_play_list e06d
|
||||
featured_video e06e
|
||||
feedback e87f
|
||||
fiber_dvr e05d
|
||||
fiber_manual_record e061
|
||||
fiber_new e05e
|
||||
fiber_pin e06a
|
||||
fiber_smart_record e062
|
||||
file_download e2c4
|
||||
file_upload e2c6
|
||||
filter e3d3
|
||||
filter_1 e3d0
|
||||
filter_2 e3d1
|
||||
filter_3 e3d2
|
||||
filter_4 e3d4
|
||||
filter_5 e3d5
|
||||
filter_6 e3d6
|
||||
filter_7 e3d7
|
||||
filter_8 e3d8
|
||||
filter_9 e3d9
|
||||
filter_9_plus e3da
|
||||
filter_b_and_w e3db
|
||||
filter_center_focus e3dc
|
||||
filter_drama e3dd
|
||||
filter_frames e3de
|
||||
filter_hdr e3df
|
||||
filter_list e152
|
||||
filter_none e3e0
|
||||
filter_tilt_shift e3e2
|
||||
filter_vintage e3e3
|
||||
find_in_page e880
|
||||
find_replace e881
|
||||
fingerprint e90d
|
||||
first_page e5dc
|
||||
fitness_center eb43
|
||||
flag e153
|
||||
flare e3e4
|
||||
flash_auto e3e5
|
||||
flash_off e3e6
|
||||
flash_on e3e7
|
||||
flight e539
|
||||
flight_land e904
|
||||
flight_takeoff e905
|
||||
flip e3e8
|
||||
flip_to_back e882
|
||||
flip_to_front e883
|
||||
folder e2c7
|
||||
folder_open e2c8
|
||||
folder_shared e2c9
|
||||
folder_special e617
|
||||
font_download e167
|
||||
format_align_center e234
|
||||
format_align_justify e235
|
||||
format_align_left e236
|
||||
format_align_right e237
|
||||
format_bold e238
|
||||
format_clear e239
|
||||
format_color_fill e23a
|
||||
format_color_reset e23b
|
||||
format_color_text e23c
|
||||
format_indent_decrease e23d
|
||||
format_indent_increase e23e
|
||||
format_italic e23f
|
||||
format_line_spacing e240
|
||||
format_list_bulleted e241
|
||||
format_list_numbered e242
|
||||
format_paint e243
|
||||
format_quote e244
|
||||
format_shapes e25e
|
||||
format_size e245
|
||||
format_strikethrough e246
|
||||
format_textdirection_l_to_r e247
|
||||
format_textdirection_r_to_l e248
|
||||
format_underlined e249
|
||||
forum e0bf
|
||||
forward e154
|
||||
forward_10 e056
|
||||
forward_30 e057
|
||||
forward_5 e058
|
||||
free_breakfast eb44
|
||||
fullscreen e5d0
|
||||
fullscreen_exit e5d1
|
||||
functions e24a
|
||||
g_translate e927
|
||||
gamepad e30f
|
||||
games e021
|
||||
gavel e90e
|
||||
gesture e155
|
||||
get_app e884
|
||||
gif e908
|
||||
golf_course eb45
|
||||
gps_fixed e1b3
|
||||
gps_not_fixed e1b4
|
||||
gps_off e1b5
|
||||
grade e885
|
||||
gradient e3e9
|
||||
grain e3ea
|
||||
graphic_eq e1b8
|
||||
grid_off e3eb
|
||||
grid_on e3ec
|
||||
group e7ef
|
||||
group_add e7f0
|
||||
group_work e886
|
||||
hd e052
|
||||
hdr_off e3ed
|
||||
hdr_on e3ee
|
||||
hdr_strong e3f1
|
||||
hdr_weak e3f2
|
||||
headset e310
|
||||
headset_mic e311
|
||||
healing e3f3
|
||||
hearing e023
|
||||
help e887
|
||||
help_outline e8fd
|
||||
high_quality e024
|
||||
highlight e25f
|
||||
highlight_off e888
|
||||
history e889
|
||||
home e88a
|
||||
hot_tub eb46
|
||||
hotel e53a
|
||||
hourglass_empty e88b
|
||||
hourglass_full e88c
|
||||
http e902
|
||||
https e88d
|
||||
image e3f4
|
||||
image_aspect_ratio e3f5
|
||||
import_contacts e0e0
|
||||
import_export e0c3
|
||||
important_devices e912
|
||||
inbox e156
|
||||
indeterminate_check_box e909
|
||||
info e88e
|
||||
info_outline e88f
|
||||
input e890
|
||||
insert_chart e24b
|
||||
insert_comment e24c
|
||||
insert_drive_file e24d
|
||||
insert_emoticon e24e
|
||||
insert_invitation e24f
|
||||
insert_link e250
|
||||
insert_photo e251
|
||||
invert_colors e891
|
||||
invert_colors_off e0c4
|
||||
iso e3f6
|
||||
keyboard e312
|
||||
keyboard_arrow_down e313
|
||||
keyboard_arrow_left e314
|
||||
keyboard_arrow_right e315
|
||||
keyboard_arrow_up e316
|
||||
keyboard_backspace e317
|
||||
keyboard_capslock e318
|
||||
keyboard_hide e31a
|
||||
keyboard_return e31b
|
||||
keyboard_tab e31c
|
||||
keyboard_voice e31d
|
||||
kitchen eb47
|
||||
label e892
|
||||
label_outline e893
|
||||
landscape e3f7
|
||||
language e894
|
||||
laptop e31e
|
||||
laptop_chromebook e31f
|
||||
laptop_mac e320
|
||||
laptop_windows e321
|
||||
last_page e5dd
|
||||
launch e895
|
||||
layers e53b
|
||||
layers_clear e53c
|
||||
leak_add e3f8
|
||||
leak_remove e3f9
|
||||
lens e3fa
|
||||
library_add e02e
|
||||
library_books e02f
|
||||
library_music e030
|
||||
lightbulb_outline e90f
|
||||
line_style e919
|
||||
line_weight e91a
|
||||
linear_scale e260
|
||||
link e157
|
||||
linked_camera e438
|
||||
list e896
|
||||
live_help e0c6
|
||||
live_tv e639
|
||||
local_activity e53f
|
||||
local_airport e53d
|
||||
local_atm e53e
|
||||
local_bar e540
|
||||
local_cafe e541
|
||||
local_car_wash e542
|
||||
local_convenience_store e543
|
||||
local_dining e556
|
||||
local_drink e544
|
||||
local_florist e545
|
||||
local_gas_station e546
|
||||
local_grocery_store e547
|
||||
local_hospital e548
|
||||
local_hotel e549
|
||||
local_laundry_service e54a
|
||||
local_library e54b
|
||||
local_mall e54c
|
||||
local_movies e54d
|
||||
local_offer e54e
|
||||
local_parking e54f
|
||||
local_pharmacy e550
|
||||
local_phone e551
|
||||
local_pizza e552
|
||||
local_play e553
|
||||
local_post_office e554
|
||||
local_printshop e555
|
||||
local_see e557
|
||||
local_shipping e558
|
||||
local_taxi e559
|
||||
location_city e7f1
|
||||
location_disabled e1b6
|
||||
location_off e0c7
|
||||
location_on e0c8
|
||||
location_searching e1b7
|
||||
lock e897
|
||||
lock_open e898
|
||||
lock_outline e899
|
||||
looks e3fc
|
||||
looks_3 e3fb
|
||||
looks_4 e3fd
|
||||
looks_5 e3fe
|
||||
looks_6 e3ff
|
||||
looks_one e400
|
||||
looks_two e401
|
||||
loop e028
|
||||
loupe e402
|
||||
low_priority e16d
|
||||
loyalty e89a
|
||||
mail e158
|
||||
mail_outline e0e1
|
||||
map e55b
|
||||
markunread e159
|
||||
markunread_mailbox e89b
|
||||
memory e322
|
||||
menu e5d2
|
||||
merge_type e252
|
||||
message e0c9
|
||||
mic e029
|
||||
mic_none e02a
|
||||
mic_off e02b
|
||||
mms e618
|
||||
mode_comment e253
|
||||
mode_edit e254
|
||||
monetization_on e263
|
||||
money_off e25c
|
||||
monochrome_photos e403
|
||||
mood e7f2
|
||||
mood_bad e7f3
|
||||
more e619
|
||||
more_horiz e5d3
|
||||
more_vert e5d4
|
||||
motorcycle e91b
|
||||
mouse e323
|
||||
move_to_inbox e168
|
||||
movie e02c
|
||||
movie_creation e404
|
||||
movie_filter e43a
|
||||
multiline_chart e6df
|
||||
music_note e405
|
||||
music_video e063
|
||||
my_location e55c
|
||||
nature e406
|
||||
nature_people e407
|
||||
navigate_before e408
|
||||
navigate_next e409
|
||||
navigation e55d
|
||||
near_me e569
|
||||
network_cell e1b9
|
||||
network_check e640
|
||||
network_locked e61a
|
||||
network_wifi e1ba
|
||||
new_releases e031
|
||||
next_week e16a
|
||||
nfc e1bb
|
||||
no_encryption e641
|
||||
no_sim e0cc
|
||||
not_interested e033
|
||||
note e06f
|
||||
note_add e89c
|
||||
notifications e7f4
|
||||
notifications_active e7f7
|
||||
notifications_none e7f5
|
||||
notifications_off e7f6
|
||||
notifications_paused e7f8
|
||||
offline_pin e90a
|
||||
ondemand_video e63a
|
||||
opacity e91c
|
||||
open_in_browser e89d
|
||||
open_in_new e89e
|
||||
open_with e89f
|
||||
pages e7f9
|
||||
pageview e8a0
|
||||
palette e40a
|
||||
pan_tool e925
|
||||
panorama e40b
|
||||
panorama_fish_eye e40c
|
||||
panorama_horizontal e40d
|
||||
panorama_vertical e40e
|
||||
panorama_wide_angle e40f
|
||||
party_mode e7fa
|
||||
pause e034
|
||||
pause_circle_filled e035
|
||||
pause_circle_outline e036
|
||||
payment e8a1
|
||||
people e7fb
|
||||
people_outline e7fc
|
||||
perm_camera_mic e8a2
|
||||
perm_contact_calendar e8a3
|
||||
perm_data_setting e8a4
|
||||
perm_device_information e8a5
|
||||
perm_identity e8a6
|
||||
perm_media e8a7
|
||||
perm_phone_msg e8a8
|
||||
perm_scan_wifi e8a9
|
||||
person e7fd
|
||||
person_add e7fe
|
||||
person_outline e7ff
|
||||
person_pin e55a
|
||||
person_pin_circle e56a
|
||||
personal_video e63b
|
||||
pets e91d
|
||||
phone e0cd
|
||||
phone_android e324
|
||||
phone_bluetooth_speaker e61b
|
||||
phone_forwarded e61c
|
||||
phone_in_talk e61d
|
||||
phone_iphone e325
|
||||
phone_locked e61e
|
||||
phone_missed e61f
|
||||
phone_paused e620
|
||||
phonelink e326
|
||||
phonelink_erase e0db
|
||||
phonelink_lock e0dc
|
||||
phonelink_off e327
|
||||
phonelink_ring e0dd
|
||||
phonelink_setup e0de
|
||||
photo e410
|
||||
photo_album e411
|
||||
photo_camera e412
|
||||
photo_filter e43b
|
||||
photo_library e413
|
||||
photo_size_select_actual e432
|
||||
photo_size_select_large e433
|
||||
photo_size_select_small e434
|
||||
picture_as_pdf e415
|
||||
picture_in_picture e8aa
|
||||
picture_in_picture_alt e911
|
||||
pie_chart e6c4
|
||||
pie_chart_outlined e6c5
|
||||
pin_drop e55e
|
||||
place e55f
|
||||
play_arrow e037
|
||||
play_circle_filled e038
|
||||
play_circle_outline e039
|
||||
play_for_work e906
|
||||
playlist_add e03b
|
||||
playlist_add_check e065
|
||||
playlist_play e05f
|
||||
plus_one e800
|
||||
poll e801
|
||||
polymer e8ab
|
||||
pool eb48
|
||||
portable_wifi_off e0ce
|
||||
portrait e416
|
||||
power e63c
|
||||
power_input e336
|
||||
power_settings_new e8ac
|
||||
pregnant_woman e91e
|
||||
present_to_all e0df
|
||||
print e8ad
|
||||
priority_high e645
|
||||
public e80b
|
||||
publish e255
|
||||
query_builder e8ae
|
||||
question_answer e8af
|
||||
queue e03c
|
||||
queue_music e03d
|
||||
queue_play_next e066
|
||||
radio e03e
|
||||
radio_button_checked e837
|
||||
radio_button_unchecked e836
|
||||
rate_review e560
|
||||
receipt e8b0
|
||||
recent_actors e03f
|
||||
record_voice_over e91f
|
||||
redeem e8b1
|
||||
redo e15a
|
||||
refresh e5d5
|
||||
remove e15b
|
||||
remove_circle e15c
|
||||
remove_circle_outline e15d
|
||||
remove_from_queue e067
|
||||
remove_red_eye e417
|
||||
remove_shopping_cart e928
|
||||
reorder e8fe
|
||||
repeat e040
|
||||
repeat_one e041
|
||||
replay e042
|
||||
replay_10 e059
|
||||
replay_30 e05a
|
||||
replay_5 e05b
|
||||
reply e15e
|
||||
reply_all e15f
|
||||
report e160
|
||||
report_problem e8b2
|
||||
restaurant e56c
|
||||
restaurant_menu e561
|
||||
restore e8b3
|
||||
restore_page e929
|
||||
ring_volume e0d1
|
||||
room e8b4
|
||||
room_service eb49
|
||||
rotate_90_degrees_ccw e418
|
||||
rotate_left e419
|
||||
rotate_right e41a
|
||||
rounded_corner e920
|
||||
router e328
|
||||
rowing e921
|
||||
rss_feed e0e5
|
||||
rv_hookup e642
|
||||
satellite e562
|
||||
save e161
|
||||
scanner e329
|
||||
schedule e8b5
|
||||
school e80c
|
||||
screen_lock_landscape e1be
|
||||
screen_lock_portrait e1bf
|
||||
screen_lock_rotation e1c0
|
||||
screen_rotation e1c1
|
||||
screen_share e0e2
|
||||
sd_card e623
|
||||
sd_storage e1c2
|
||||
search e8b6
|
||||
security e32a
|
||||
select_all e162
|
||||
send e163
|
||||
sentiment_dissatisfied e811
|
||||
sentiment_neutral e812
|
||||
sentiment_satisfied e813
|
||||
sentiment_very_dissatisfied e814
|
||||
sentiment_very_satisfied e815
|
||||
settings e8b8
|
||||
settings_applications e8b9
|
||||
settings_backup_restore e8ba
|
||||
settings_bluetooth e8bb
|
||||
settings_brightness e8bd
|
||||
settings_cell e8bc
|
||||
settings_ethernet e8be
|
||||
settings_input_antenna e8bf
|
||||
settings_input_component e8c0
|
||||
settings_input_composite e8c1
|
||||
settings_input_hdmi e8c2
|
||||
settings_input_svideo e8c3
|
||||
settings_overscan e8c4
|
||||
settings_phone e8c5
|
||||
settings_power e8c6
|
||||
settings_remote e8c7
|
||||
settings_system_daydream e1c3
|
||||
settings_voice e8c8
|
||||
share e80d
|
||||
shop e8c9
|
||||
shop_two e8ca
|
||||
shopping_basket e8cb
|
||||
shopping_cart e8cc
|
||||
short_text e261
|
||||
show_chart e6e1
|
||||
shuffle e043
|
||||
signal_cellular_4_bar e1c8
|
||||
signal_cellular_connected_no_internet_4_bar e1cd
|
||||
signal_cellular_no_sim e1ce
|
||||
signal_cellular_null e1cf
|
||||
signal_cellular_off e1d0
|
||||
signal_wifi_4_bar e1d8
|
||||
signal_wifi_4_bar_lock e1d9
|
||||
signal_wifi_off e1da
|
||||
sim_card e32b
|
||||
sim_card_alert e624
|
||||
skip_next e044
|
||||
skip_previous e045
|
||||
slideshow e41b
|
||||
slow_motion_video e068
|
||||
smartphone e32c
|
||||
smoke_free eb4a
|
||||
smoking_rooms eb4b
|
||||
sms e625
|
||||
sms_failed e626
|
||||
snooze e046
|
||||
sort e164
|
||||
sort_by_alpha e053
|
||||
spa eb4c
|
||||
space_bar e256
|
||||
speaker e32d
|
||||
speaker_group e32e
|
||||
speaker_notes e8cd
|
||||
speaker_notes_off e92a
|
||||
speaker_phone e0d2
|
||||
spellcheck e8ce
|
||||
star e838
|
||||
star_border e83a
|
||||
star_half e839
|
||||
stars e8d0
|
||||
stay_current_landscape e0d3
|
||||
stay_current_portrait e0d4
|
||||
stay_primary_landscape e0d5
|
||||
stay_primary_portrait e0d6
|
||||
stop e047
|
||||
stop_screen_share e0e3
|
||||
storage e1db
|
||||
store e8d1
|
||||
store_mall_directory e563
|
||||
straighten e41c
|
||||
streetview e56e
|
||||
strikethrough_s e257
|
||||
style e41d
|
||||
subdirectory_arrow_left e5d9
|
||||
subdirectory_arrow_right e5da
|
||||
subject e8d2
|
||||
subscriptions e064
|
||||
subtitles e048
|
||||
subway e56f
|
||||
supervisor_account e8d3
|
||||
surround_sound e049
|
||||
swap_calls e0d7
|
||||
swap_horiz e8d4
|
||||
swap_vert e8d5
|
||||
swap_vertical_circle e8d6
|
||||
switch_camera e41e
|
||||
switch_video e41f
|
||||
sync e627
|
||||
sync_disabled e628
|
||||
sync_problem e629
|
||||
system_update e62a
|
||||
system_update_alt e8d7
|
||||
tab e8d8
|
||||
tab_unselected e8d9
|
||||
tablet e32f
|
||||
tablet_android e330
|
||||
tablet_mac e331
|
||||
tag_faces e420
|
||||
tap_and_play e62b
|
||||
terrain e564
|
||||
text_fields e262
|
||||
text_format e165
|
||||
textsms e0d8
|
||||
texture e421
|
||||
theaters e8da
|
||||
thumb_down e8db
|
||||
thumb_up e8dc
|
||||
thumbs_up_down e8dd
|
||||
time_to_leave e62c
|
||||
timelapse e422
|
||||
timeline e922
|
||||
timer e425
|
||||
timer_10 e423
|
||||
timer_3 e424
|
||||
timer_off e426
|
||||
title e264
|
||||
toc e8de
|
||||
today e8df
|
||||
toll e8e0
|
||||
tonality e427
|
||||
touch_app e913
|
||||
toys e332
|
||||
track_changes e8e1
|
||||
traffic e565
|
||||
train e570
|
||||
tram e571
|
||||
transfer_within_a_station e572
|
||||
transform e428
|
||||
translate e8e2
|
||||
trending_down e8e3
|
||||
trending_flat e8e4
|
||||
trending_up e8e5
|
||||
tune e429
|
||||
turned_in e8e6
|
||||
turned_in_not e8e7
|
||||
tv e333
|
||||
unarchive e169
|
||||
undo e166
|
||||
unfold_less e5d6
|
||||
unfold_more e5d7
|
||||
update e923
|
||||
usb e1e0
|
||||
verified_user e8e8
|
||||
vertical_align_bottom e258
|
||||
vertical_align_center e259
|
||||
vertical_align_top e25a
|
||||
vibration e62d
|
||||
video_call e070
|
||||
video_label e071
|
||||
video_library e04a
|
||||
videocam e04b
|
||||
videocam_off e04c
|
||||
videogame_asset e338
|
||||
view_agenda e8e9
|
||||
view_array e8ea
|
||||
view_carousel e8eb
|
||||
view_column e8ec
|
||||
view_comfy e42a
|
||||
view_compact e42b
|
||||
view_day e8ed
|
||||
view_headline e8ee
|
||||
view_list e8ef
|
||||
view_module e8f0
|
||||
view_quilt e8f1
|
||||
view_stream e8f2
|
||||
view_week e8f3
|
||||
vignette e435
|
||||
visibility e8f4
|
||||
visibility_off e8f5
|
||||
voice_chat e62e
|
||||
voicemail e0d9
|
||||
volume_down e04d
|
||||
volume_mute e04e
|
||||
volume_off e04f
|
||||
volume_up e050
|
||||
vpn_key e0da
|
||||
vpn_lock e62f
|
||||
wallpaper e1bc
|
||||
warning e002
|
||||
watch e334
|
||||
watch_later e924
|
||||
wb_auto e42c
|
||||
wb_cloudy e42d
|
||||
wb_incandescent e42e
|
||||
wb_iridescent e436
|
||||
wb_sunny e430
|
||||
wc e63d
|
||||
web e051
|
||||
web_asset e069
|
||||
weekend e16b
|
||||
whatshot e80e
|
||||
widgets e1bd
|
||||
wifi e63e
|
||||
wifi_lock e1e1
|
||||
wifi_tethering e1e2
|
||||
work e8f9
|
||||
wrap_text e25b
|
||||
youtube_searched_for e8fa
|
||||
zoom_in e8ff
|
||||
zoom_out e900
|
||||
zoom_out_map e56b
|
||||
36
Viewer/css/fonts/MaterialIcons/material-icons.css
Executable file
36
Viewer/css/fonts/MaterialIcons/material-icons.css
Executable file
@ -0,0 +1,36 @@
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(MaterialIcons-Regular.eot); /* For IE6-8 */
|
||||
src: local('Material Icons'),
|
||||
local('MaterialIcons-Regular'),
|
||||
url(MaterialIcons-Regular.woff2) format('woff2'),
|
||||
url(MaterialIcons-Regular.woff) format('woff'),
|
||||
url(MaterialIcons-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px; /* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
/* Support for Firefox. */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
/* Support for IE. */
|
||||
font-feature-settings: 'liga';
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,67 +1,67 @@
|
||||
/*
|
||||
* Source: http://projects.lukehaas.me/css-loaders/
|
||||
*/
|
||||
.loader:before,
|
||||
.loader:after,
|
||||
.loader {
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
-webkit-animation: load7 1.8s infinite ease-in-out;
|
||||
animation: load7 1.8s infinite ease-in-out;
|
||||
}
|
||||
.loader {
|
||||
position: relative;
|
||||
|
||||
color: #ffffff;
|
||||
font-size: 8px;
|
||||
//margin: 15px auto;
|
||||
margin-bottom: 15px;
|
||||
|
||||
text-indent: -9999em;
|
||||
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
-webkit-animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
.loader:before {
|
||||
left: -1.7em;
|
||||
|
||||
-webkit-animation-delay: -0.32s;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
.loader:after {
|
||||
left: 1.7em;
|
||||
}
|
||||
.loader:before,
|
||||
.loader:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
@-webkit-keyframes load7 {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
box-shadow: 0 1em 0 -0.6em;
|
||||
}
|
||||
40% {
|
||||
box-shadow: 0 1em 0 0;
|
||||
}
|
||||
}
|
||||
@keyframes load7 {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
box-shadow: 0 1em 0 -0.6em;
|
||||
}
|
||||
40% {
|
||||
box-shadow: 0 1em 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Source: http://projects.lukehaas.me/css-loaders/
|
||||
*/
|
||||
.loader:before,
|
||||
.loader:after,
|
||||
.loader {
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
-webkit-animation: load7 1.8s infinite ease-in-out;
|
||||
animation: load7 1.8s infinite ease-in-out;
|
||||
}
|
||||
.loader {
|
||||
position: relative;
|
||||
|
||||
color: #ffffff;
|
||||
font-size: 8px;
|
||||
//margin: 15px auto;
|
||||
margin-bottom: 15px;
|
||||
|
||||
text-indent: -9999em;
|
||||
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
-webkit-animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
.loader:before {
|
||||
left: -1.7em;
|
||||
|
||||
-webkit-animation-delay: -0.32s;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
.loader:after {
|
||||
left: 1.7em;
|
||||
}
|
||||
.loader:before,
|
||||
.loader:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
@-webkit-keyframes load7 {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
box-shadow: 0 1em 0 -0.6em;
|
||||
}
|
||||
40% {
|
||||
box-shadow: 0 1em 0 0;
|
||||
}
|
||||
}
|
||||
@keyframes load7 {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
box-shadow: 0 1em 0 -0.6em;
|
||||
}
|
||||
40% {
|
||||
box-shadow: 0 1em 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
432
Viewer/css/ribbons.css
Normal file
432
Viewer/css/ribbons.css
Normal file
@ -0,0 +1,432 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
* Modern experimental ribbon layout...
|
||||
*
|
||||
*
|
||||
********************************************************* Settings ***/
|
||||
|
||||
|
||||
|
||||
/********************************************************** Viewer ***/
|
||||
.viewer {
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
/*border: solid blue 1px;*/
|
||||
box-sizing: border-box;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* XXX this is a stub for printing help/docs... */
|
||||
@media print {
|
||||
|
||||
.viewer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/****************************************** Ribbon set and locator ***/
|
||||
.ribbon-set {
|
||||
position: absolute;
|
||||
display: block;
|
||||
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
|
||||
/* NOTE: this needs for scaling/zooming to behave correctly and not
|
||||
shift the element, when its dimensions change...
|
||||
...this is because .ribbon-set will both be used for scaling
|
||||
and aligning... */
|
||||
transform-origin: top left;
|
||||
|
||||
will-change: transform;
|
||||
}
|
||||
.ribbon-locator {
|
||||
position: relative;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
|
||||
/********************************************************** Ribbon ***/
|
||||
/* XXX for some reason setting image size in vmin stops this from
|
||||
stretching in width... */
|
||||
.shadow,
|
||||
.ribbon {
|
||||
position: relative;
|
||||
display: block;
|
||||
/* XXX BUG: setting this will mess up new ribbon creation....
|
||||
display: inline-block;*/
|
||||
height: auto;
|
||||
/*min-width: 0px;*/
|
||||
width: auto;
|
||||
overflow: visible;
|
||||
white-space: nowrap;
|
||||
font-size: 0;
|
||||
|
||||
float: left;
|
||||
clear: both;
|
||||
|
||||
background: var(--ribbon-background-color);
|
||||
|
||||
margin-top: var(--ribbon-margin);
|
||||
margin-bottom: var(--ribbon-margin);
|
||||
|
||||
will-change: transform;
|
||||
}
|
||||
.ribbon:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* XXX would be good to make this sticky -- always visible... */
|
||||
.ribbon[title]:before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
content: attr(title);
|
||||
|
||||
font-size: 52pt;
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
|
||||
color: white;
|
||||
text-shadow: black 2px 2px 15px;
|
||||
|
||||
top: -2pt;
|
||||
right: 100%;
|
||||
margin-right: 20pt;
|
||||
}
|
||||
.single-image-mode .ribbon[title]:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.base-ribbon-marker {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
|
||||
color: transparent;
|
||||
font-size: 20pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
.base-ribbon-marker:after {
|
||||
content: "base ribbon";
|
||||
|
||||
display: block;
|
||||
|
||||
position: absolute;
|
||||
width: var(--image-tile-size);
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
|
||||
z-index: 1000;
|
||||
|
||||
color: white;
|
||||
background: black;
|
||||
opacity: 0.2;
|
||||
|
||||
font-size: 20pt;
|
||||
font-weight: bold;
|
||||
|
||||
transform-origin: bottom left;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.single-image-mode.viewer .base-ribbon-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*********************************************************** Image ***/
|
||||
.marker,
|
||||
.current-marker,
|
||||
.mark,
|
||||
.image {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
text-align:left;
|
||||
width: var(--image-tile-size);
|
||||
height: var(--image-tile-size);
|
||||
font-size: 12pt;
|
||||
overflow: hidden;
|
||||
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
/* XXX do we need this???
|
||||
text-shadow:
|
||||
black 0.1em 0.1em 0.4em,
|
||||
black 0.1em 0.1em; */
|
||||
|
||||
/* NOTE: we can't set the bg color here because it will get
|
||||
affected by filters... */
|
||||
background: no-repeat 50% transparent;
|
||||
background-size: contain;
|
||||
border: solid var(--image-border) transparent;
|
||||
|
||||
/* XXX we are taking care of this in code -- see if we can use this */
|
||||
image-orientation: none;
|
||||
}
|
||||
|
||||
.image {
|
||||
padding: var(--single-image-indicator-size);
|
||||
background-color: var(--image-background-color);
|
||||
}
|
||||
.single-image-mode .image {
|
||||
background-color: none;
|
||||
}
|
||||
|
||||
.image div {
|
||||
display: block;
|
||||
position: absolute;
|
||||
|
||||
max-width: var(--image-tile-size);
|
||||
width: auto;
|
||||
max-height: var(--image-tile-size);
|
||||
height: auto;
|
||||
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%,-50%);
|
||||
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
/*font-size: 2vh;*/
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.crisp-resize .image {
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
.current.image {
|
||||
border: solid 5px red;
|
||||
}
|
||||
*/
|
||||
|
||||
/* NOTE: this is essentially a ribbon... */
|
||||
.shadow {
|
||||
position: absolute;
|
||||
overflow: visible;
|
||||
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin: 0px;
|
||||
|
||||
background: black;
|
||||
|
||||
-webkit-transition: all 0.1s ease-in;
|
||||
-moz-transition: all 0.1s ease-in;
|
||||
-ms-transition: all 0.1s ease-in;
|
||||
-o-transition: all 0.1s ease-in;
|
||||
transition: all 0.1s ease-in;
|
||||
}
|
||||
.shadow {
|
||||
-webkit-transform-origin: 0 0;
|
||||
-moz-transform-origin: 0 0;
|
||||
-ms-transform-origin: 0 0;
|
||||
-o-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
.image.moving {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
.current-marker {
|
||||
display: none;
|
||||
position: absolute;
|
||||
border: solid 5px red;
|
||||
background: none;
|
||||
z-index: 100;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin-top: calc(-1 * var(--image-tile-size) / 2);
|
||||
margin-left: calc(-1 * var(--image-tile-size) / 2);
|
||||
|
||||
/* pass events through... (do we need IE10-?) */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.marker {
|
||||
width: 50px;
|
||||
border: none;
|
||||
background: no-repeat 50% transparent;
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
}
|
||||
.marker:after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
content: "marker";
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
overflow: visible;
|
||||
|
||||
width: 200px;
|
||||
height: 32px;
|
||||
top: 50%;
|
||||
margin-top: -16px;
|
||||
left: 50%;
|
||||
margin-left: -100px;
|
||||
|
||||
transform-origin: center center;
|
||||
transform: rotate(270deg) scaleY(1) scaleX(1);
|
||||
|
||||
opacity: 0.5;
|
||||
}
|
||||
.single-image-mode.viewer .marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* single image mode transitions */
|
||||
/* XXX still buggy and not too needed...
|
||||
.single-image-mode.viewer .image {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
.transition(opacity);
|
||||
}
|
||||
.single-image-mode.viewer .current.image {
|
||||
opacity: 1;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/* image turning... */
|
||||
/* NOTE: need to account for proportions after turning... */
|
||||
.image[orientation="90"] {
|
||||
transform: rotate(90deg) scaleY(1) scaleX(1);
|
||||
}
|
||||
.image[orientation="180"] {
|
||||
transform: rotate(180deg) scaleY(1) scaleX(1);
|
||||
}
|
||||
.image[orientation="270"] {
|
||||
transform: rotate(270deg) scaleY(1) scaleX(1);
|
||||
}
|
||||
|
||||
|
||||
/* Flipped vertically only... */
|
||||
/* NOTE: wee need to do all possible combinations here as we can't
|
||||
combine different parts of a transform attr from different
|
||||
classes */
|
||||
.image[flipped*="vertical"] {
|
||||
transform: rotate(0deg) scaleY(1) scaleX(-1);
|
||||
}
|
||||
.image[orientation="90"][flipped="vertical"] {
|
||||
transform: rotate(90deg) scaleY(1) scaleX(-1)
|
||||
}
|
||||
.image[orientation="180"][flipped="vertical"] {
|
||||
transform: rotate(180deg) scaleY(1) scaleX(-1)
|
||||
}
|
||||
.image[orientation="270"][flipped="vertical"] {
|
||||
transform: rotate(270deg) scaleY(1) scaleX(-1)
|
||||
}
|
||||
|
||||
|
||||
/* Flipped horizontally only... */
|
||||
.image[flipped*="horizontal"] {
|
||||
transform: rotate(0deg) scaleY(-1) scaleX(1);
|
||||
}
|
||||
.image[orientation="90"][flipped="horizontal"] {
|
||||
.transform(90deg, -1)
|
||||
}
|
||||
.image[orientation="180"][flipped="horizontal"] {
|
||||
.transform(180deg, -1)
|
||||
}
|
||||
.image[orientation="270"][flipped="horizontal"] {
|
||||
.transform(270deg, -1)
|
||||
}
|
||||
|
||||
/* Flipped vertically and horizontally... */
|
||||
.image[flipped*="vertical"][flipped*="horizontal"] {
|
||||
transform: rotate(0deg) scaleY(-1) scaleX(-1)
|
||||
}
|
||||
.image[orientation="90"][flipped*="vertical"][flipped*="horizontal"] {
|
||||
transform: rotate(90deg) scaleY(-1) scaleX(-1)
|
||||
}
|
||||
.image[orientation="180"][flipped*="vertical"][flipped*="horizontal"] {
|
||||
transform: rotate(180deg) scaleY(-1) scaleX(-1)
|
||||
}
|
||||
.image[orientation="270"][flipped*="vertical"][flipped*="horizontal"] {
|
||||
transform: rotate(270deg) scaleY(-1) scaleX(-1)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* image separators... */
|
||||
.ribbon-image-separators.viewer .image {
|
||||
box-shadow:
|
||||
9px 4px 0 -8px rgba(128,128,128,0.2),
|
||||
-9px 4px 0 -8px rgba(128,128,128,0.2);
|
||||
}
|
||||
.ribbon-image-separators.viewer .image[orientation="90"],
|
||||
.ribbon-image-separators.viewer .image[orientation="270"] {
|
||||
box-shadow:
|
||||
4px 9px 0 -8px rgba(128,128,128,0.2),
|
||||
4px -9px 0 -8px rgba(128,128,128,0.2);
|
||||
}
|
||||
.ribbon-image-separators.single-image-mode.viewer .image {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* default backgrounds */
|
||||
/* XXX not sure if we need these...
|
||||
.image {
|
||||
background-image: url(images/loading.gif);
|
||||
}
|
||||
.image[orientation="90"] {
|
||||
background-image: url(images/loading-90deg.gif);
|
||||
}
|
||||
.image[orientation="180"] {
|
||||
background-image: url(images/loading-180deg.gif);
|
||||
}
|
||||
.image[orientation="270"] {
|
||||
background-image: url(images/loading-270deg.gif);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/* separator test */
|
||||
/*
|
||||
.image.red+.image:not(.red):before {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
content: "";
|
||||
top:0px;
|
||||
left: -50px;
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
|
||||
background-color: yellow;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set spell ft=css : */
|
||||
521
Viewer/css/widget/browse.css
Executable file
521
Viewer/css/widget/browse.css
Executable file
@ -0,0 +1,521 @@
|
||||
|
||||
/********************************************************** Widget ***/
|
||||
.browse-widget {
|
||||
display: inline-block;
|
||||
min-width: 300px;
|
||||
width: initial;
|
||||
padding: 5px;
|
||||
/*font-family: opensans, sans-serif;*/
|
||||
|
||||
background: gray;
|
||||
/*color: rgba(255,255,255,0.8);*/
|
||||
color: white;
|
||||
|
||||
/* XXX for some reason this does not work..
|
||||
transition: all 0.5s linear;
|
||||
*/
|
||||
}
|
||||
/*
|
||||
.browse-widget:not(:focus) {
|
||||
opacity: 0.8;
|
||||
}
|
||||
*/
|
||||
.browse-widget .v-block {
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
height: auto;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
.browse-widget .v-block:not(:first-of-type) {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
.browse-widget .v-block:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* editable... */
|
||||
.browse-widget [contenteditable] {
|
||||
min-width: 2px;
|
||||
min-height: 2px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/************************************************************ Path ***/
|
||||
|
||||
.browse-widget .path {
|
||||
position: relative;
|
||||
|
||||
padding: 5px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
white-space: nowrap;
|
||||
padding-right: 30px;
|
||||
|
||||
|
||||
/* XXX need a way to make this automatic and depend on .browser
|
||||
setup to avoid multiple scrollbars and the need to manually
|
||||
dive into the configuration to change the container size
|
||||
limits
|
||||
*/
|
||||
max-width: 80vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* path scroll shadow... */
|
||||
.browse-widget .path:not(:hover).scrolling {
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
.browse-widget .path:not(:hover).scrolling:after {
|
||||
position: fixed;
|
||||
content: " ";
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.browse-widget .path:not(:hover).scrolling.left:not(.right):after {
|
||||
background: linear-gradient(90deg, gray, transparent 15%);
|
||||
}
|
||||
.browse-widget .path:not(:hover).scrolling.right:not(.left):after {
|
||||
background: linear-gradient(90deg, transparent 85%, gray);
|
||||
}
|
||||
.browse-widget .path:not(:hover).scrolling.left.right:after {
|
||||
background: linear-gradient(90deg, gray, transparent 15%, transparent 85%, gray);
|
||||
}
|
||||
|
||||
/* XXX not sure about this... */
|
||||
.browse-widget .path::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
.browse-widget .path {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: auto;
|
||||
}
|
||||
.browse-widget .path:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.browse-widget .path:empty {
|
||||
display: block;
|
||||
}
|
||||
.browse-widget:not(.flat) .path:not([contenteditable]):before {
|
||||
content: attr(prefix);
|
||||
}
|
||||
.browse-widget .path .dir {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
|
||||
opacity: 0.8;
|
||||
}
|
||||
.browse-widget .path .dir:after {
|
||||
content: "/";
|
||||
}
|
||||
.browse-widget .path .dir:hover ~ .dir {
|
||||
opacity: 0.2;
|
||||
}
|
||||
.browse-widget .path .dir.cur {
|
||||
opacity: 0.5;
|
||||
cursor: text;
|
||||
}
|
||||
.browse-widget .path .dir.cur:after {
|
||||
content: "";
|
||||
}
|
||||
.browse-widget .path .dir.cur:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.browse-widget .path .dir.cur[contenteditable] {
|
||||
opacity: 1;
|
||||
}
|
||||
.browse-widget .path .dir.cur:empty:not([contenteditable]) {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
height: 12px;
|
||||
background: transparent;
|
||||
opacity: 0;
|
||||
}
|
||||
.browse-widget .path .dir.cur:empty:hover:not([contenteditable]) {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.browse-widget .path .dir.cur:empty:hover:not([contenteditable]):after {
|
||||
content: "______";
|
||||
border: dashed white 1px;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/************************************************************ List ***/
|
||||
|
||||
/* XXX need to make this resizable up but only to a certain size (~80vh) */
|
||||
.browse-widget .list {
|
||||
/* XXX need a way to make this automatic and depend on .browser
|
||||
setup to avoid multiple scrollbars and the need to manually
|
||||
dive into the configuration to change the container size
|
||||
limits
|
||||
*/
|
||||
max-height: 80vh;
|
||||
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
will-change: scroll-position;
|
||||
}
|
||||
/*
|
||||
.browse-widget .list:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
top: 1px;
|
||||
left: 0px;
|
||||
background: linear-gradient(to top, rgba(128, 128, 128, 0), rgba(128, 128, 128, 1));
|
||||
}
|
||||
.browse-widget .list:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
background: linear-gradient(to bottom, rgba(128, 128, 128, 0), rgba(128, 128, 128, 1));
|
||||
}
|
||||
*/
|
||||
/*
|
||||
.browse-widget .list:empty {
|
||||
display: block;
|
||||
}
|
||||
*/
|
||||
/* XXX this is not used because it will mess up numbering and some actions...
|
||||
* ...need a more thorough avoidance of non-visible elements...
|
||||
.browse-widget.disabled-hidden:not(.flat) .list div.disabled,
|
||||
.browse-widget.not-traversable-hidden:not(.flat) .list div.not-traversable {
|
||||
display: none;
|
||||
}
|
||||
*/
|
||||
.browse-widget .list .text {
|
||||
display: inline-block;
|
||||
float:left;
|
||||
}
|
||||
.browse-widget .list .button-container {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.browse-widget .list .button {
|
||||
display: inline-block;
|
||||
float:right;
|
||||
opacity: 0.5;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
box-sizing: border-box;
|
||||
|
||||
/*border: solid 1px rgba(255,255,255, 0.3);*/
|
||||
}
|
||||
.browse-widget .list .button:focus,
|
||||
.browse-widget .list .button:hover {
|
||||
opacity: 0.9;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.browse-widget .list .button.blank {
|
||||
opacity: 0;
|
||||
}
|
||||
.browse-widget .list .item {
|
||||
padding: 5px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
cursor: pointer;
|
||||
|
||||
/* XXX need a way to make this automatic and depend on .browser
|
||||
setup to avoid multiple scrollbars and the need to manually
|
||||
dive into the configuration to change the container size
|
||||
limits
|
||||
*/
|
||||
max-width: 80vh;
|
||||
|
||||
opacity: 0.7;
|
||||
|
||||
/*white-space: nowrap;*/
|
||||
overflow: hidden;
|
||||
}
|
||||
.browse-widget .list .item[count]:after {
|
||||
display: inline-block;
|
||||
|
||||
content: "(" attr(count) ")";
|
||||
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
margin-left: 5px;
|
||||
|
||||
opacity: 0.4;
|
||||
}
|
||||
/* highlight seach... */
|
||||
.browse-widget .list .item .text b {
|
||||
background-color: rgba(0, 0, 255, 0.5);
|
||||
}
|
||||
.browse-widget .list .item.strike-out .text {
|
||||
text-decoration: line-through;
|
||||
opacity: 0.3;
|
||||
}
|
||||
.browse-widget .list .item.highlighted .text {
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
.browse-widget .list .item.highlighted .text:last-child:after {
|
||||
content: ' *';
|
||||
}
|
||||
.browse-widget:not(.flat) .list div:not(.not-traversable) .text:after {
|
||||
content: "/";
|
||||
}
|
||||
|
||||
.browse-widget .list .item:focus,
|
||||
.browse-widget .list .item.selected:focus,
|
||||
.browse-widget .list .item.selected :focus,
|
||||
.browse-widget:focus .list .item.selected,
|
||||
.browse-widget .path>.dir:hover,
|
||||
.browse-widget .list .item:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
|
||||
opacity: 0.9;
|
||||
}
|
||||
.browse-widget .list .item.selected {
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.browse-widget .list .item:focus,
|
||||
.browse-widget .list .item.selected:focus,
|
||||
.browse-widget .list .item.selected :focus,
|
||||
.browse-widget:focus .list .item.selected {
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0.1em 0.1em 0.2em;
|
||||
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* XXX need to make the next two different... */
|
||||
.browse-widget .list .item.filtered-out {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.browse-widget:not(.show-filtered-out) .list .item.filtered-out {
|
||||
display: none;
|
||||
}
|
||||
.browse-widget .list .item.disabled {
|
||||
opacity: 0.3;
|
||||
}
|
||||
.browse-widget .list .item.empty-msg,
|
||||
.browse-widget .list .item.hidden {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
/* numbers... */
|
||||
/* XXX need to show this only on devices with keyboards... */
|
||||
.browse-widget .list .item:before {
|
||||
opacity: 0.3;
|
||||
float: left;
|
||||
font-size: small;
|
||||
}
|
||||
.browse-widget.filtering .list .item:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.browse-widget .list .item:before {
|
||||
width: 12px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
|
||||
/* XXX use :nth-match(..) ass soon as it gets enough support... */
|
||||
.browse-widget:not(.no-item-numbers) .list .item:not(.heading):before {
|
||||
content: attr(shortcut-number);
|
||||
}
|
||||
|
||||
/* XXX these are messed up with groups, use :nth-match(..) ass soon as it gets enough support... */
|
||||
/*
|
||||
.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(1):before {
|
||||
content: "1";
|
||||
}
|
||||
.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(2):before {
|
||||
content: "2";
|
||||
}
|
||||
.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(3):before {
|
||||
content: "3";
|
||||
}
|
||||
.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(4):before {
|
||||
content: "4";
|
||||
}
|
||||
.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(5):before {
|
||||
content: "5";
|
||||
}
|
||||
.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(6):before {
|
||||
content: "6";
|
||||
}
|
||||
.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(7):before {
|
||||
content: "7";
|
||||
}
|
||||
.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(8):before {
|
||||
content: "8";
|
||||
}
|
||||
.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(9):before {
|
||||
content: "9";
|
||||
}
|
||||
.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(10):before {
|
||||
content: "0";
|
||||
}
|
||||
*/
|
||||
|
||||
.browse-widget .list hr.separator {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
|
||||
/* hide first/last separators... */
|
||||
.browse-widget .list hr.separator:first-child,
|
||||
.browse-widget .list hr.separator:last-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* Action... */
|
||||
.browse-widget .list .action {
|
||||
margin-top: 0.2em;
|
||||
border-top: solid 1px rgba(255,255,255, 0.2);
|
||||
}
|
||||
.browse-widget .list .action .text {
|
||||
font-style: italic;
|
||||
}
|
||||
/* do not show top border if after another action or separator... */
|
||||
.browse-widget .list .action:first-child,
|
||||
.browse-widget .list .action+.action,
|
||||
.browse-widget .list .heading+.action,
|
||||
.browse-widget .list .separator+.action {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
|
||||
/* Heading... */
|
||||
.browse-widget .list .heading {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.browse-widget .list .heading:not(:first-child) {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
.browse-widget .list .heading span:first-child {
|
||||
margin-left: -0.5em;
|
||||
}
|
||||
/* doc */
|
||||
.browse-widget .list .heading[doc]:after {
|
||||
display: block;
|
||||
content: attr(doc);
|
||||
|
||||
white-space: pre;
|
||||
font-size: small;
|
||||
font-style: italic;
|
||||
|
||||
margin-top: 2em;
|
||||
|
||||
opacity: 0.5;
|
||||
}
|
||||
.browse-widget .list .item.heading:hover {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
.browse-widget .list .item.heading.selected {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
/* dark theme... */
|
||||
.dark .browse-widget .list .heading {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
|
||||
/* Show item part on hover... */
|
||||
.browse-widget .list .item .show-on-hover {
|
||||
opacity: 0;
|
||||
}
|
||||
.browse-widget .list .item:hover .show-on-hover {
|
||||
opacity: inherit;
|
||||
}
|
||||
|
||||
/* Show item part on select... */
|
||||
.browse-widget .list .item .show-on-select {
|
||||
opacity: 0;
|
||||
}
|
||||
.browse-widget .list .selected .show-on-select {
|
||||
opacity: inherit;
|
||||
}
|
||||
|
||||
|
||||
/* Nested lists... */
|
||||
.browse-widget .list .list {
|
||||
padding-left: 20pt;
|
||||
}
|
||||
.browse-widget .list .list>.item:first-child {
|
||||
margin-left: -20pt;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/****************************************************** Cloud List ***/
|
||||
|
||||
.browse-widget.cloud-view .list .item {
|
||||
display: inline-block;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.browse-widget.cloud-view .list hr.separator {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.browse-widget.cloud-view .list .item .text:first-child:before {
|
||||
content: "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
/******************************************************** Theaming ***/
|
||||
|
||||
/* Light */
|
||||
.light .browse-widget {
|
||||
background: white;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.light .browse-widget .v-block:not(:first-of-type) {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Dark */
|
||||
.dark .browse-widget {
|
||||
background: #0a0a0a;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
/* XXX can we make this simpler??? */
|
||||
.dark .browse-widget .list .item:focus,
|
||||
.dark .browse-widget .list .item.selected:focus,
|
||||
.dark .browse-widget .list .item.selected :focus,
|
||||
.dark .browse-widget:focus .list div.selected,
|
||||
.dark .browse-widget .path .dir:hover,
|
||||
.dark .browse-widget .list .item:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
.dark .browse-widget .list div.selected {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.dark .browse-widget .list .item:focus,
|
||||
.dark .browse-widget .list .item.selected:focus,
|
||||
.dark .browse-widget .list .item.selected :focus,
|
||||
.dark .browse-widget:focus .list .item.selected {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
@ -34,6 +34,10 @@
|
||||
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0.3em 0.3em 5em;
|
||||
}
|
||||
.drawer-widget.top .content {
|
||||
top: auto;
|
||||
margin-bottom: 100%;
|
||||
}
|
||||
.drawer-widget~.drawer-widget .content {
|
||||
box-shadow: rgba(0, 0, 0, 0.05) 0.1em 0.1em 3em;
|
||||
}
|
||||
@ -41,10 +45,6 @@
|
||||
/* NOTE: this does not include text...
|
||||
...need a way to go around this...
|
||||
*/
|
||||
.blur>* {
|
||||
-webkit-filter: blur(2px) saturate(0.3);
|
||||
filter: blur(2px) saturate(0.3);
|
||||
}
|
||||
.blur>.drawer-widget {
|
||||
-webkit-filter: none;
|
||||
filter: none;
|
||||
@ -11,12 +11,14 @@
|
||||
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
|
||||
backdrop-filter: blur(2px) saturate(0.3);
|
||||
|
||||
-webkit-transform-style: preserve-3d;
|
||||
-moz-transform-style: preserve-3d;
|
||||
transform-style: preserve-3d;
|
||||
|
||||
/* XXX try to avoid this... */
|
||||
z-index: 9999;
|
||||
z-index: 5000;
|
||||
}
|
||||
.overlay-widget~.overlay-widget {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
@ -37,7 +39,7 @@
|
||||
max-width: 80vw;
|
||||
max-height: 90vh;
|
||||
|
||||
overflow: hidden;
|
||||
/*overflow: hidden;*/
|
||||
|
||||
transform: translateY(-50%) translateX(-50%);
|
||||
|
||||
@ -47,20 +49,23 @@
|
||||
box-shadow: rgba(0, 0, 0, 0.05) 0.1em 0.1em 3em;
|
||||
}
|
||||
|
||||
/* NOTE: this does not include text...
|
||||
...need a way to go around this...
|
||||
*/
|
||||
.blur>* {
|
||||
-webkit-filter: blur(2px) saturate(0.3);
|
||||
filter: blur(2px) saturate(0.3);
|
||||
}
|
||||
.blur>.overlay-widget {
|
||||
-webkit-filter: blur(0.8px) saturate(0.3);
|
||||
filter: blur(0.8px) saturate(0.3);
|
||||
}
|
||||
.blur>.overlay-widget:last-child {
|
||||
-webkit-filter: none;
|
||||
filter: none;
|
||||
/* title */
|
||||
.content>*:before {
|
||||
position: absolute;
|
||||
content: attr(dialog-title);
|
||||
|
||||
bottom: 100%;
|
||||
|
||||
color: silver;
|
||||
|
||||
font-weight: 900;
|
||||
font-style: italic;
|
||||
font-size: x-large;
|
||||
line-height: 85%;
|
||||
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
Viewer/data/blank.mp4
Normal file
BIN
Viewer/data/blank.mp4
Normal file
Binary file not shown.
62
Viewer/data/placeholder-image.svg
Normal file
62
Viewer/data/placeholder-image.svg
Normal file
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
#vertical {
|
||||
opacity: 0.25;
|
||||
}
|
||||
#corners {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.line {
|
||||
fill: none;
|
||||
stroke: #FFFFFF;
|
||||
stroke-width: 2;
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
.label {
|
||||
font-family: 'OpenSans-Light', sans-serif;
|
||||
font-size: 82px;
|
||||
}
|
||||
.stroke {
|
||||
fill: #FFFFFF;
|
||||
}
|
||||
</style>
|
||||
<g id="background">
|
||||
<rect x="0" y="0" width="1000" height="1000"/>
|
||||
</g>
|
||||
<g id="vertical">
|
||||
<g>
|
||||
<path class="stroke" d="M831,2v996H169V2H831 M833,0H167v1000h666V0L833,0z"/>
|
||||
</g>
|
||||
<line class="line" x1="167" y1="1000" x2="833" y2="0"/>
|
||||
<line class="line" x1="167" y1="0" x2="833" y2="1000"/>
|
||||
</g>
|
||||
<g id="horizontal">
|
||||
<g>
|
||||
<path class="stroke" d="M998,169v662H2V169H998 M1000,167H0v666h1000V167L1000,167z"/>
|
||||
</g>
|
||||
<line class="line" x1="1000" y1="167" x2="5" y2="833"/>
|
||||
<g>
|
||||
<line class="line" x1="0" y1="167" x2="1000" y2="833"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="corners">
|
||||
<g>
|
||||
<path class="stroke" d="M5.5,5.5l79.3,47.6L53.1,84.8L5.5,5.5 M-0.4-0.4l53,88.4L88,52.7L-0.4-0.4L-0.4-0.4z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="stroke" d="M994.5,5.5l-47.6,79.3l-31.7-31.7L994.5,5.5 M1000.4-0.4l-88.4,53L947.3,88L1000.4-0.4L1000.4-0.4z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="stroke" d="M946.9,915.2l47.6,79.3l-79.3-47.6L946.9,915.2 M947.3,912L912,947.3l88.4,53L947.3,912L947.3,912z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="stroke" d="M53.1,915.2l31.7,31.7L5.5,994.5L53.1,915.2 M52.7,912l-53,88.4l88.4-53L52.7,912L52.7,912z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text">
|
||||
<rect x="356" y="452" width="290" height="100.3"/>
|
||||
<text transform="matrix(1 0 0 1 373 528)" class="stroke label">IMAGE</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
165
Viewer/doc/BUILD-NOTES
Executable file
165
Viewer/doc/BUILD-NOTES
Executable file
@ -0,0 +1,165 @@
|
||||
In general
|
||||
----------
|
||||
|
||||
The idea is to make the project as simple as possible and the dev cycle
|
||||
as efficient as possible, for this reason only one lib manager is used
|
||||
(npm), only one build system (make) and any translation layers are
|
||||
avoided (less is likely to be phased out).
|
||||
|
||||
With this approach for most cases and for testing in the browser after
|
||||
the initial setup only refreshing the page is required to load most of
|
||||
the changes (./css/layout.less being the only exception).
|
||||
|
||||
|
||||
|
||||
General environment
|
||||
-------------------
|
||||
|
||||
ImageGrid will require the following installed and in path:
|
||||
- bash
|
||||
- make (GNU Make)
|
||||
- git
|
||||
- wget
|
||||
- zip / unzip / zipnote
|
||||
- grep / egrep / fgrep
|
||||
- sed
|
||||
|
||||
|
||||
Some more dependencies will be installed by make via npm locally.
|
||||
|
||||
|
||||
Dependencies can be checked with:
|
||||
|
||||
$ make check
|
||||
|
||||
|
||||
|
||||
Then the build system/process is generally the same on all platforms:
|
||||
|
||||
- Clone the repository:
|
||||
|
||||
$ git clone https://github.com/flynx/ImageGrid.git
|
||||
|
||||
|
||||
- Build the dev envioronment (bash):
|
||||
|
||||
$ make dev
|
||||
|
||||
|
||||
- Run in-place:
|
||||
|
||||
$ make run
|
||||
|
||||
|
||||
- Build a distro:
|
||||
|
||||
$ make dist
|
||||
|
||||
|
||||
|
||||
Windows environment
|
||||
-------------------
|
||||
|
||||
This will require a UN*X-like build environment to run make and friends.
|
||||
|
||||
|
||||
One way to go about this is (admin PowerShell):
|
||||
|
||||
- Install Chocolate
|
||||
|
||||
> Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
|
||||
|
||||
|
||||
For more info see: https://chocolatey.org/install
|
||||
|
||||
|
||||
- Install git-bash based env:
|
||||
|
||||
> choco install git nodejs wget zip sed grep
|
||||
|
||||
|
||||
Now we can proceed with the normal build.
|
||||
|
||||
|
||||
|
||||
The Makefile
|
||||
------------
|
||||
|
||||
For make help to work make requires: bash, sed, grep/fgrep and printf to
|
||||
be available in the path, run this to double check:
|
||||
|
||||
$ make check-help
|
||||
|
||||
|
||||
To check the full list of dependencies run:
|
||||
|
||||
$ make check
|
||||
|
||||
|
||||
The make file is largely self-documented so run this for more info:
|
||||
|
||||
$ make help
|
||||
|
||||
|
||||
|
||||
Sharp and other native modules for nw/electron
|
||||
----------------------------------------------
|
||||
|
||||
And for electron (done by make):
|
||||
|
||||
electron-rebuild
|
||||
|
||||
|
||||
To build sharp for a specific version of node and nwjs (outdated):
|
||||
|
||||
cd ./node_modules/sharp/
|
||||
nw-gyp rebuild --target=0.17.4 --arch=x64
|
||||
|
||||
|
||||
|
||||
Chromium flicker issue (nw/outdated)
|
||||
------------------------------------
|
||||
|
||||
The UI sometimes flickers -- at first blanks out to black then re-draws,
|
||||
this is most noticeable on white or gray backgrounds.
|
||||
|
||||
This appears to be GPU related.
|
||||
|
||||
package.json:
|
||||
"chromium-args": "--disable-gpu-compositing",
|
||||
|
||||
This will fix the issue temporarily, but we still need a better solution.
|
||||
|
||||
|
||||
|
||||
Remote debugging via DevTools (nw/outdated)
|
||||
-------------------------------------------
|
||||
|
||||
Set this in package.json:
|
||||
"chromium-args": "--remote-debugging-port=9222",
|
||||
|
||||
Then open http://localhost:9222 in chrome.
|
||||
|
||||
|
||||
|
||||
Speedup loading of app (nw/outdated)
|
||||
------------------------------------
|
||||
|
||||
One of the ways to speed up the load times when packed is to store Node's
|
||||
modules ./node_modules in a separate location, outside of the app.zip
|
||||
or package.nw
|
||||
To enable require(..) to find them:
|
||||
- > npm install --save app-module-path
|
||||
- when building the zip move all the modules out to a new location
|
||||
*except* app-module-path
|
||||
- add this line to all root js modules *before* any other
|
||||
require(..) is called:
|
||||
if(process.__nwjs){
|
||||
var path = require('path')
|
||||
require('app-module-path')
|
||||
.addPath(path.dirname(process.execPath)
|
||||
+ '/node_modules/') }
|
||||
|
||||
|
||||
|
||||
|
||||
116
Viewer/doc/DEPENDENCIES
Normal file
116
Viewer/doc/DEPENDENCIES
Normal file
@ -0,0 +1,116 @@
|
||||
|
||||
|
||||
Future tasks
|
||||
============
|
||||
|
||||
- Remove dependency on less, don't think we need it anymore...
|
||||
- Remove/merge legacy modules
|
||||
- Move generic stuff to separate libs (npm)
|
||||
- lib/keyboard.js
|
||||
- lib/toggler.js
|
||||
- Electron seems to constantly drift out of spec
|
||||
- Check if we actually need the less dependency
|
||||
|
||||
|
||||
|
||||
npm modules
|
||||
===========
|
||||
|
||||
All contexts (internal):
|
||||
- object-run - adds Object.prototype.run(..) to run function in
|
||||
in the context of an object.
|
||||
- ig-object - js object model wrapper
|
||||
a closer to JS alternative to "classes"
|
||||
- ig-actions - actions object model extension
|
||||
provides a different way to cooperatively extend
|
||||
methods
|
||||
- ig-types - JS type extensions and utilities
|
||||
- ig-argv - CLI argv parser
|
||||
- ig-features - organizes and manages sets of actions and data
|
||||
- guarantee-events - simple event cache
|
||||
- generic-walk - legacy???
|
||||
|
||||
|
||||
All contexts (external):
|
||||
- requirejs
|
||||
- requirejs-plugins - import text/json
|
||||
|
||||
|
||||
Electron / node app:
|
||||
- electron - GUI app wrapper
|
||||
- sharp - image processing (previews / basic fast metadata)
|
||||
- exif-reader - metadata parser
|
||||
NOTE: that exif-reader is currently used but
|
||||
migration to exifreader is planned.
|
||||
- exiftool - metadata reader (full metadata reader)
|
||||
- app-module-path - add to node's require search path
|
||||
- v8-compile-cache - v8 require optimization
|
||||
- cli-progress - CLI progress bar
|
||||
- colors - CLI text colors
|
||||
- fs-extra - extend node's fs module (revise)
|
||||
- fs-walk
|
||||
- glob - glob implementation
|
||||
- wildglob - glob implementation (is this used?)
|
||||
|
||||
- pouchdb - standalone in-browser CouchDB (not used yet)
|
||||
- async-json
|
||||
- json5 - extended json (comments, ...) for config read
|
||||
|
||||
|
||||
|
||||
Other / static
|
||||
==============
|
||||
|
||||
Internal (lib):
|
||||
- keyboard.js - keyboard handler library
|
||||
- jli.js - general dom utilities (index.html/global)
|
||||
- util.js - misc utilities
|
||||
- util-dom.js - dom/jquery utils, mostly editor related
|
||||
- dialogs.js -
|
||||
- panels.js
|
||||
- scroller.js
|
||||
- toggler.js
|
||||
- transform.js
|
||||
- editor.js - image "editor" (legacy)
|
||||
|
||||
- _module.js - js module template
|
||||
- _template.js - bare js template
|
||||
|
||||
components/ - web components
|
||||
canvas-waveform.html
|
||||
ig-image-graph-worker.js
|
||||
ig-image-graph.js
|
||||
|
||||
widget/ - widgets
|
||||
browse-walk.js
|
||||
browse.html
|
||||
browse.js
|
||||
browse2.html
|
||||
browse2.js
|
||||
drawer.html
|
||||
drawer.js
|
||||
overlay.html
|
||||
overlay.js
|
||||
widget.js
|
||||
|
||||
|
||||
External (ext-lib):
|
||||
- sha1.js - sha1 implementation, used for GID generation
|
||||
(imagegrid/data.js)
|
||||
- jquery.js
|
||||
- jquery-ui.js
|
||||
- jquery.ui.touch-punch.min.js
|
||||
- hammer.min.js - touch/mouse guestures
|
||||
- jquery.hammer.js
|
||||
- velocity.min.js - animation engine (used??)
|
||||
|
||||
- pouchdb.min.js - standalone in-browser CouchDB (not used yet)
|
||||
|
||||
- less.js - legacy??
|
||||
- less-1.3.3.min.js - legacy??
|
||||
- jstorage.js - legacy?
|
||||
- virtual-dom.js - legacy???
|
||||
|
||||
|
||||
|
||||
|
||||
66
Viewer/doc/HACKS
Normal file
66
Viewer/doc/HACKS
Normal file
@ -0,0 +1,66 @@
|
||||
|
||||
Hacks and fixes
|
||||
===============
|
||||
|
||||
20220126:
|
||||
node_modules/requirejs/bin/r,js
|
||||
Under Electron v14+ requirejs breaks with a SyntaxError on the
|
||||
first char of r.js ('#!/...') -- a hackish way to fix this is to
|
||||
comment it out, not yet sure why is this happening...
|
||||
STUB: patched by:
|
||||
make node_modules
|
||||
|
||||
|
||||
|
||||
20210122:
|
||||
Marking changes twice on load...
|
||||
|
||||
The way .markChanged(..) is handled around .load(..) / .loadOrRecover(..) and
|
||||
in base.js seems a bit hackish -- would be goof to do this in one spot,
|
||||
revise...
|
||||
|
||||
See:
|
||||
- features/base.js ~@1179 -- 'load' handler...
|
||||
- features/filesystem.js ~@982 -- 'loadIndex' handler...
|
||||
|
||||
|
||||
|
||||
20201104:
|
||||
Chrome v83 / Electron 9.3.3
|
||||
|
||||
WebKit canvas' .drawImage(..) ignores its and images .style.imageOrientation
|
||||
unless both are added to DOM.
|
||||
|
||||
FIX:
|
||||
- features/app.js: img2canvas(..) quietly adds the elements to DOM and then cleans up
|
||||
|
||||
TODO:
|
||||
- test in browser...
|
||||
- test in newer versions of browser / electron...
|
||||
- if not fixed report...
|
||||
|
||||
|
||||
|
||||
20200830:
|
||||
RequireJS + Electron v7+ mess up default .baseUrl
|
||||
|
||||
FIX:
|
||||
- cfg/requirejs.js: added .baseUrl inferencing.
|
||||
|
||||
TODO:
|
||||
- is this a bug or a feature and report as bug if needed
|
||||
|
||||
|
||||
|
||||
20200830:
|
||||
Chrome messing up fullscreen colors unless a <video> tag is present.
|
||||
|
||||
FIX:
|
||||
- data/blank.mp4: added
|
||||
- index.html / electron.html: added <video> tag with data/blank.mp4 as source
|
||||
|
||||
TODO:
|
||||
- report this...
|
||||
|
||||
|
||||
|
||||
139
Viewer/doc/MAKE
Normal file
139
Viewer/doc/MAKE
Normal file
@ -0,0 +1,139 @@
|
||||
|
||||
ImageGrid.Viewer Makefile
|
||||
|
||||
|
||||
To check for buld/development dependencies that make can't install itself:
|
||||
$ make check
|
||||
|
||||
To setup the development environment:
|
||||
$ make dev
|
||||
|
||||
To do a default build use:
|
||||
$ make dist
|
||||
|
||||
In some cases making things directly starting from a clean directory
|
||||
may either fail or use global version of a utility instead of the
|
||||
version specified for the app, if this happens first build the
|
||||
environment and then try again:
|
||||
$ make dev
|
||||
...
|
||||
$ make dist
|
||||
|
||||
To print full list of user make targets use:
|
||||
$ make help
|
||||
|
||||
|
||||
Variables to control the build:
|
||||
APP_NAME - Application name
|
||||
APP_BIN - App binary name (ignored for MacOS)
|
||||
|
||||
TARGET_OS - target OS (win32, linux, darwin)
|
||||
ARCH - target achitecture (ia32, x86, x64, ...)
|
||||
|
||||
ELECTRON_DOWNOAD_URL
|
||||
- URL to download electron binary
|
||||
ELECTRON_DIST - electron distribution file name pattern
|
||||
BUILD_MODE - can be "repack" or "in-place" (default)
|
||||
|
||||
|
||||
Variables to help with troubleshooting:
|
||||
IMAGEGRID_DEBUG - force show devtools on startup
|
||||
NOTE: devtools will should start automaticalky
|
||||
if loading takes too long or if it was
|
||||
started on last run,
|
||||
IMAGEGRID_FORCE_SHOW
|
||||
- force show viewer GUI on startup
|
||||
|
||||
|
||||
NOTE: when setting variables avoid using spaces and other characters
|
||||
make can get fussy about...
|
||||
NOTE: cross compilation is at this time not supported, if you try it
|
||||
and it works then 1) you got very lucky and 2) tell me about it =)
|
||||
...at least the node native packages (sharp) will likely either
|
||||
fail or will get compiled to the wrong arch and not be used, and
|
||||
some non-native packages may misbehave (though I'm not aware of
|
||||
any at this point, so report them if you encounter one).
|
||||
This is complicated by the fact that most of ImageGrid.Viewer is
|
||||
platform-agnostic and will run on almost anything and will simply
|
||||
try to ignore broken packages and features depending on them, so
|
||||
check the console log for any odd load reports...
|
||||
...but note that cross-building and packaging is only possible
|
||||
either without the native modules or with them pre-built for the
|
||||
target platform, at this point.
|
||||
|
||||
|
||||
Examples:
|
||||
# check if all dependencies are available...
|
||||
$ make check
|
||||
|
||||
# make development environment...
|
||||
$ make dev
|
||||
|
||||
# full build...
|
||||
$ make clean dist
|
||||
|
||||
# build in repack mode...
|
||||
$ BUILD_MODE=repack make dist
|
||||
|
||||
# build for darwin/macOS... (EXPERIMENTAL)
|
||||
$ TARGET_OS=darwin make clean dist
|
||||
|
||||
# run with Devtools started...
|
||||
$ IMAGEGRID_DEBUG=1 make run
|
||||
|
||||
# force show the main window...
|
||||
$ IMAGEGRID_FORCE_SHOW=1 make run
|
||||
|
||||
|
||||
Help and info:
|
||||
help: Print make target help and exit
|
||||
version: Print version and exit
|
||||
|
||||
Dependency checking:
|
||||
check: Run all dependency checks
|
||||
check-web: Run web build dependency checks
|
||||
check-help: Run help/build (Makefile) dependency checks
|
||||
|
||||
Generic targets:
|
||||
all: Run the full build chain
|
||||
doc: Build documentation
|
||||
dev: Build the development environment
|
||||
dev-npm: Install the npm global dev package (might need sudo)
|
||||
dist: Build distributable package
|
||||
test-dist: Build testing distributable package
|
||||
deploy: Run ./scripts/deploy.sh on contents of DIST_DIR
|
||||
run: Run app in-place
|
||||
|
||||
Cleanup:
|
||||
clean: Cleanup
|
||||
clean-generated: Clean generated files
|
||||
clean-dist: Clean DIST_DIR directory
|
||||
clean-all: Clean all
|
||||
|
||||
Generic components:
|
||||
js: Build JS modules
|
||||
app-dir-full: Build full app directory
|
||||
app-dir-minimal: Build minimal app directory
|
||||
|
||||
Web/Browser:
|
||||
web: Build a browser-runnable package
|
||||
|
||||
Electron:
|
||||
electron-dist: Make electron distributable
|
||||
electron-test-dist: Make electron test distributable
|
||||
electron-unpacked: Make unpacked electron app
|
||||
electron-run: Run app in electron
|
||||
|
||||
Open Desktop:
|
||||
|
||||
Patches:
|
||||
patched-requirejs: Patch requirejs (see: NOTES)
|
||||
unpatched-requirejs: Unpatch requirejs
|
||||
|
||||
CLI:
|
||||
devel-cli: Install CLI interface as a link to this tree.
|
||||
cli: install CLI interface as independent package.
|
||||
|
||||
|
||||
---
|
||||
This file was generated by: make doc/MAKE
|
||||
108
Viewer/doc/NOTES
Executable file
108
Viewer/doc/NOTES
Executable file
@ -0,0 +1,108 @@
|
||||
|
||||
|
||||
Initial build
|
||||
=============
|
||||
|
||||
The `Makefile` handles all the dependencies in all cases but the first
|
||||
run on a clean system can take a while because `make` uses `npx` to
|
||||
bootstrap the required apps/libs.
|
||||
|
||||
It is recommended for a clean setup to either run make twice or run an
|
||||
`npm install` first, this needs to be done to avoid version leaking
|
||||
from the bootstrapped or global node packages to the build as defined by
|
||||
`package.json`
|
||||
|
||||
|
||||
|
||||
Version numbers
|
||||
===============
|
||||
|
||||
The app version number is automatically synced between package.json
|
||||
(primary) and version.js, all version changes should be made in the
|
||||
former.
|
||||
|
||||
|
||||
|
||||
Debugging startup errors
|
||||
========================
|
||||
|
||||
If something goes wrong on startup -- the splash screen is shown but the
|
||||
main window is not, DevTools should be automatically started in about 5
|
||||
seconds.
|
||||
|
||||
If DevTools does not run then they can be started manually by:
|
||||
$ IMAGEGRID_DEBUG=1 ig.js gui
|
||||
or:
|
||||
$ IMAGEGRID_DEBUG=1 make run
|
||||
|
||||
Common reasons for failure on startup:
|
||||
- path set in cfg/requirejs.js (baseUrl) is not correct on this
|
||||
version on node/electron
|
||||
- electron compatibility issue
|
||||
- rarely: some feature fails to call .declareReady() and the
|
||||
startup process stalls waiting for it, this can be manually
|
||||
overridden by calling .ready()
|
||||
|
||||
|
||||
|
||||
Modules and RequireJS
|
||||
=====================
|
||||
|
||||
Most of the system is loaded via browser context RequireJS loader, in
|
||||
node/electron/nw context node-specific stuff is loaded with a second
|
||||
node-enabled RequireJS instance or node require.
|
||||
|
||||
The ideal solution would be to use one require that sees both the browser
|
||||
and node contexts, the problem is exactly in this, the browser requirejs
|
||||
does not see either node or node modules while the node require loads
|
||||
code that is not visible to devtools unless it is running at that exact
|
||||
moment.
|
||||
|
||||
Thus we are forced to use both mode requirejs loaders which may be
|
||||
confusing at times.
|
||||
|
||||
|
||||
Different loaders
|
||||
-----------------
|
||||
|
||||
There are two RequireJS instances present in most contexts in nw.js
|
||||
within the define(..) runner:
|
||||
- require(..)
|
||||
Pure browser RequireJS instance, used to load local
|
||||
modules.
|
||||
|
||||
- requirejs(..)
|
||||
Node-enabled RequireJS instance, used to load node
|
||||
modules.
|
||||
This is needed as the above require(..) overloads the
|
||||
node native loader.
|
||||
|
||||
The future
|
||||
----------
|
||||
|
||||
This seems a bit confusing, so at least the naming convention should be
|
||||
revised.
|
||||
|
||||
|
||||
|
||||
Entry points
|
||||
============
|
||||
|
||||
We have two entry points here for a reason, e.js is tuned to be as
|
||||
light/fast as possible for the general case, i.e. when run without
|
||||
arguments combining it with ig.js would make things much-much slower...
|
||||
|
||||
- index.html - Browser
|
||||
- electron e.js - electron
|
||||
<-> ig.js - parse args if given and
|
||||
optionally return control back
|
||||
to e.js...
|
||||
- ig.js - node
|
||||
-> e.js - node can spawn an electron app
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
55
Viewer/doc/PROJECT-LAYOUT
Executable file
55
Viewer/doc/PROJECT-LAYOUT
Executable file
@ -0,0 +1,55 @@
|
||||
|
||||
Basic project layout:
|
||||
---------------------
|
||||
|
||||
imagegrid/ - domain-specific modules
|
||||
|
||||
features/ - feature modules
|
||||
_feature.js - feature template
|
||||
all.js - feature loader
|
||||
this module includes all the
|
||||
feature modules to be used in the
|
||||
system.
|
||||
meta.js - feature configuration and
|
||||
meta-features
|
||||
|
||||
workers/ - workers
|
||||
|
||||
lib/ - generic library modules
|
||||
widgets/ - generic widgets
|
||||
_module.js - module template
|
||||
_template.js - generic js file template
|
||||
|
||||
ext-lib/ - external library modules
|
||||
|
||||
node_modules/ - npm modules
|
||||
ig-*/ - ImageGrid npm modules
|
||||
|
||||
css/ - css/less files
|
||||
widgets/ - widget-specific css
|
||||
|
||||
images/ - system images
|
||||
data/ - data and assets
|
||||
|
||||
doc/ - various dev documentation
|
||||
PROJECT-LAYOUT - this file
|
||||
BUILD-NOTES - notes on the build process
|
||||
NOTES - general notes
|
||||
HACKS - list of hacks around external
|
||||
"features" and bugs
|
||||
MAKE - Makefile doc, the same as printed
|
||||
by make help (generated by make doc)
|
||||
|
||||
archive/ - image archive root structure
|
||||
|
||||
experiments/ - standalone experiments
|
||||
|
||||
package.json - npm/nw configuration
|
||||
electron.html - electron-specific html
|
||||
ui.js - GUI root setup
|
||||
|
||||
ig.js - CLI entry point
|
||||
index.html - Browser/NW entry point
|
||||
e.js - electron entry point
|
||||
|
||||
|
||||
312
Viewer/e.js
Normal file
312
Viewer/e.js
Normal file
@ -0,0 +1,312 @@
|
||||
#!/usr/bin/env node
|
||||
/**********************************************************************
|
||||
*
|
||||
* ImageGrid.Viewer Electron entry point...
|
||||
*
|
||||
*
|
||||
* NOTE: this is kept as simple as possible to speed up initial loading.
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
// Global scope pollution test...
|
||||
if(process.env.IMAGEGRID_DEBUG){
|
||||
global.__global = {...global}
|
||||
global.scopeDiff = function(cur=global, base=__global){
|
||||
return Object.keys(cur)
|
||||
.filter(function(k){ return base[k] !== cur[k] })
|
||||
.reduce(function(res, k){
|
||||
res[k] = cur[k]
|
||||
return res }, {})} }
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
//require('v8-compile-cache')
|
||||
|
||||
var electron = require('electron')
|
||||
var path = require('path')
|
||||
var url = require('url')
|
||||
|
||||
var VERSION = require('./version').version
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
var app = electron.app
|
||||
var BrowserWindow = electron.BrowserWindow
|
||||
var ipcMain = electron.ipcMain
|
||||
|
||||
//
|
||||
global.ELECTRON_PACKAGED = app.isPackaged
|
||||
|
||||
// used to let e.js know that the CLI wants to start the GUI..
|
||||
global.START_GUI = false
|
||||
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
// XXX do we need multiwindow support???
|
||||
|
||||
|
||||
// Splash window...
|
||||
//
|
||||
// XXX might be nice to show load progress on splash...
|
||||
var SPLASH
|
||||
var SPLASH_TIMEOUT = 20 * 1000
|
||||
function createSplash(force=false){
|
||||
// singleton window...
|
||||
if(!force && SPLASH){
|
||||
return SPLASH }
|
||||
|
||||
// NOTE: this is done here as this does not depend on code loading,
|
||||
// thus showing the splash significantly faster...
|
||||
SPLASH = new BrowserWindow({
|
||||
// let the window to get ready before we show it to the user...
|
||||
show: false,
|
||||
|
||||
transparent: true,
|
||||
frame: false,
|
||||
center: true,
|
||||
width: 840,
|
||||
height: 540,
|
||||
|
||||
alwaysOnTop: true,
|
||||
|
||||
resizable: false,
|
||||
movable: false,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
|
||||
autoHideMenuBar: true,
|
||||
})
|
||||
SPLASH.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'splash.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
}))
|
||||
SPLASH.once('ready-to-show', function(){
|
||||
this.webContents
|
||||
// see if the splash screen is disabled...
|
||||
.executeJavaScript('localStorage.disableSplashScreen')
|
||||
.then(function(disabled){
|
||||
// update version...
|
||||
disabled
|
||||
|| SPLASH.webContents
|
||||
.executeJavaScript(
|
||||
`document.getElementById("version").innerText = "${VERSION}"`)
|
||||
// show/destroy..
|
||||
disabled ?
|
||||
SPLASH.destroy()
|
||||
: SPLASH.show() }) })
|
||||
SPLASH.on('closed',
|
||||
function(){
|
||||
SPLASH = null
|
||||
WIN
|
||||
&& WIN.webContents.executeJavaScript('document.appSplashScreen = false') })
|
||||
|
||||
// handle main window state...
|
||||
WIN
|
||||
&& WIN.webContents.executeJavaScript('document.appSplashScreen = true')
|
||||
|
||||
// auto-close splash...
|
||||
SPLASH_TIMEOUT
|
||||
&& setTimeout(
|
||||
function(){
|
||||
SPLASH
|
||||
&& SPLASH.destroy() },
|
||||
SPLASH_TIMEOUT)
|
||||
|
||||
return SPLASH }
|
||||
|
||||
|
||||
// Create main window...
|
||||
//
|
||||
// NOTE: initial window metrics are loaded by the app feature...
|
||||
// XXX should this be done here???
|
||||
//
|
||||
// XXX handle maximize corretly...
|
||||
// ...currently it does not differ visually from fullscreen -- either
|
||||
// make them the same or keep them separate visually...
|
||||
var WIN
|
||||
function createWindow(force=false){
|
||||
// singleton window...
|
||||
if(!force && WIN){
|
||||
return WIN }
|
||||
|
||||
// Create the browser window.
|
||||
WIN = new BrowserWindow({
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInWorker: true,
|
||||
contextIsolation: false,
|
||||
enableRemoteModule: true,
|
||||
},
|
||||
|
||||
// let the window get ready before we show it to the user...
|
||||
show: false,
|
||||
frame: false,
|
||||
|
||||
backgroundColor: '#333333',
|
||||
|
||||
width: 800,
|
||||
height: 600,
|
||||
|
||||
fullscreenable: true,
|
||||
|
||||
// XXX not sure about this...
|
||||
//maximizable: false,
|
||||
|
||||
//autoHideMenuBar: true,
|
||||
})
|
||||
// disable default menu...
|
||||
WIN.setMenu(null)
|
||||
WIN.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'index.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true,
|
||||
}))
|
||||
|
||||
WIN.once('ready-to-show',
|
||||
function(){
|
||||
WIN.webContents.executeJavaScript(`
|
||||
document.readyToShow = true
|
||||
|
||||
// XXX make these a prop...
|
||||
document.appFullScreen = false
|
||||
document.appDevTools = false
|
||||
`)
|
||||
// splash screen...
|
||||
WIN.webContents.executeJavaScript(
|
||||
SPLASH ?
|
||||
'document.appSplashScreen = true'
|
||||
: 'document.appSplashScreen = false') })
|
||||
WIN.on('closed',
|
||||
function(){ WIN = null })
|
||||
|
||||
// devtools...
|
||||
WIN.webContents.on('devtools-opened',
|
||||
function(){
|
||||
WIN && WIN.webContents.executeJavaScript('document.appDevTools = true') })
|
||||
WIN.webContents.on('devtools-closed',
|
||||
function(){
|
||||
WIN && WIN.webContents.executeJavaScript('document.appDevTools = false') })
|
||||
|
||||
// handle env...
|
||||
// devtools for different windows...
|
||||
process.env.IMAGEGRID_DEBUG
|
||||
&& WIN.openDevTools({mode: 'undocked'})
|
||||
// Force show window...
|
||||
process.env.IMAGEGRID_FORCE_SHOW
|
||||
&& WIN.show()
|
||||
|
||||
return WIN }
|
||||
|
||||
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
// Start the app...
|
||||
//
|
||||
function start(){
|
||||
var _start = function(){
|
||||
createSplash()
|
||||
createWindow() }
|
||||
// NOTE: by this time (arg parsing and stuff) the app may already
|
||||
// be ready...
|
||||
app.isReady() ?
|
||||
_start()
|
||||
: app.on('ready', _start) }
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Event handlers...
|
||||
|
||||
// Window states...
|
||||
ipcMain.on('show',
|
||||
function(){ WIN && WIN.show() })
|
||||
ipcMain.on('hide',
|
||||
function(){ WIN && WIN.hide() })
|
||||
|
||||
ipcMain.on('minimize',
|
||||
function(){ WIN && WIN.minimize() })
|
||||
|
||||
ipcMain.on('enterFullScreen',
|
||||
function(){
|
||||
if(WIN){
|
||||
WIN.setFullScreen(true)
|
||||
WIN.webContents.executeJavaScript('document.appFullScreen = true') } })
|
||||
ipcMain.on('exitFullScreen',
|
||||
function(){
|
||||
if(WIN){
|
||||
WIN.setFullScreen(false)
|
||||
WIN.webContents.executeJavaScript('document.appFullScreen = false') } })
|
||||
|
||||
// Splash screen...
|
||||
ipcMain.on('openSplashScreen',
|
||||
function(){
|
||||
SPLASH
|
||||
|| createSplash() })
|
||||
ipcMain.on('closeSplashScreen',
|
||||
function(){
|
||||
// force this to run after this frame avoiding races...
|
||||
setTimeout(
|
||||
function(){
|
||||
SPLASH
|
||||
&& SPLASH.destroy() },
|
||||
10) })
|
||||
|
||||
// DevTools...
|
||||
// XXX need to focus devtools here...
|
||||
// see: webContents.getAllWebContents()
|
||||
ipcMain.on('openDevTools',
|
||||
function(){
|
||||
WIN
|
||||
&& WIN.openDevTools({
|
||||
mode: 'undocked',
|
||||
activate: true,
|
||||
}) })
|
||||
ipcMain.on('closeDevTools',
|
||||
function(){ WIN && WIN.closeDevTools() })
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Event handlers (macOS)...
|
||||
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
// XXX test...
|
||||
app.on('activate', function(){
|
||||
WIN || createWindow() })
|
||||
|
||||
// Quit when all windows are closed.
|
||||
// On macOS it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
app.on('window-all-closed', function(){
|
||||
process.platform !== 'darwin'
|
||||
&& app.quit() })
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// start things up...
|
||||
|
||||
;(ELECTRON_PACKAGED ?
|
||||
process.argv.length > 1
|
||||
: process.argv.length > 2) ?
|
||||
// got some arguments -- delegate to ig.js...
|
||||
(require('./ig')
|
||||
&& global.START_GUI
|
||||
&& start())
|
||||
// start the viewer...
|
||||
: start()
|
||||
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */
|
||||
18
Viewer/experiments/_template.html
Executable file
18
Viewer/experiments/_template.html
Executable file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<style>
|
||||
</style>
|
||||
|
||||
<script src="../ext-lib/jquery.js"></script>
|
||||
<script src="../ext-lib/jquery-ui.js"></script>
|
||||
|
||||
<script src="../lib/jli.js"></script>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
213
Viewer/experiments/canvas-waveform.html
Normal file
213
Viewer/experiments/canvas-waveform.html
Normal file
@ -0,0 +1,213 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<style>
|
||||
|
||||
|
||||
.graph {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
width: attr(image-width);
|
||||
height: attr(graph-height);
|
||||
}
|
||||
.graph canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.graph .controls {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
}
|
||||
.graph .controls button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: white;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.graph .controls button.current {
|
||||
text-decoration: underline;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.graph .controls button.R:hover,
|
||||
.graph .controls button.current.R {
|
||||
background: red;
|
||||
}
|
||||
.graph .controls button.G:hover,
|
||||
.graph .controls button.current.G {
|
||||
background: green;
|
||||
}
|
||||
.graph .controls button.B:hover,
|
||||
.graph .controls button.current.B {
|
||||
background: blue;
|
||||
}
|
||||
.graph .controls button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<script src="../ext-lib/jquery.js"></script>
|
||||
<script src="../ext-lib/jquery-ui.js"></script>
|
||||
|
||||
<script src="../lib/jli.js"></script>
|
||||
|
||||
<script src="ig-image-graph.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
// XXX should we make this a web components???
|
||||
// + would make everything transparent
|
||||
// - add a tag
|
||||
// - edit props
|
||||
// - handle events
|
||||
// - not sure what is the differenence practically...
|
||||
var makeWaveform = function(img, options){
|
||||
var color_modes = ['normalized', 'white', 'color']
|
||||
|
||||
options = options || {}
|
||||
options.mode = options.mode || 'color'
|
||||
options.color = options.color || color_modes[0]
|
||||
|
||||
// XXX configurable...
|
||||
var type = 'waveform'
|
||||
var graph = waveform
|
||||
|
||||
var buttons
|
||||
|
||||
var update = function(m){
|
||||
m = options.mode = m || options.mode
|
||||
graph(img, canvas, m, options.color)
|
||||
;(buttons || [])
|
||||
.forEach(function(b){
|
||||
b.classList.contains(m) ?
|
||||
b.classList.add('current')
|
||||
: b.classList.remove('current') }) }
|
||||
|
||||
// handle img urls...
|
||||
if(typeof(img) == typeof('str')){
|
||||
var src = img
|
||||
img = document.createElement('img')
|
||||
img.onload = function(){
|
||||
container.setAttribute('image-width', img.width)
|
||||
container.setAttribute('image-height', img.height)
|
||||
update() }
|
||||
img.src = src }
|
||||
|
||||
// container...
|
||||
var container = document.createElement('div')
|
||||
container.classList.add('graph', type)
|
||||
// XXX not sure why would we need shadow dom here...
|
||||
//var shadow = container.attachShadow({mode: 'open'})
|
||||
// canvas...
|
||||
var canvas = document.createElement('canvas')
|
||||
container.appendChild(canvas)
|
||||
// controls...
|
||||
if(controls || controls === undefined){
|
||||
var controls = document.createElement('div')
|
||||
controls.classList.add('controls')
|
||||
// buttons...
|
||||
buttons = ['luminance', 'color', 'R', 'G', 'B']
|
||||
.map(function(m){
|
||||
var button = document.createElement('button')
|
||||
button.innerText = m
|
||||
button.classList.add(m)
|
||||
button.onclick = function(){
|
||||
update(m) }
|
||||
controls.appendChild(button)
|
||||
return button })
|
||||
// color mode switch...
|
||||
var button = document.createElement('button')
|
||||
button.innerText = '('+ options.color[0] +')'
|
||||
button.onclick = function(){
|
||||
options.color = color_modes[
|
||||
(color_modes.indexOf(options.color) + 1)
|
||||
% color_modes.length]
|
||||
this.innerText = '('+ options.color[0] +')'
|
||||
update() }
|
||||
controls.appendChild(button)
|
||||
// add to block...
|
||||
container.appendChild(controls) }
|
||||
|
||||
// meta stuff...
|
||||
container.setAttribute('graph-width', canvas.width)
|
||||
container.setAttribute('graph-height', canvas.height)
|
||||
container.setAttribute('image-width', img.width)
|
||||
container.setAttribute('image-height', img.height)
|
||||
|
||||
// init...
|
||||
update()
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
|
||||
|
||||
var start = function(){
|
||||
//waveform(document.getElementById('input'), document.getElementById('waveform'), 'color')
|
||||
//histogram(document.getElementById('input'), document.getElementById('histogram'), 'color')
|
||||
|
||||
//document.body.appendChild(makeWaveform(document.getElementById('input'), 'color', 'normalized'))
|
||||
document.body.appendChild(makeWaveform(document.getElementById('input')))
|
||||
|
||||
document.body.appendChild(makeWaveform('../images/splash-800x500.jpg'))
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<img id="input" src="../images/splash-800x500.jpg" onload="start()"/>
|
||||
|
||||
<br>
|
||||
|
||||
<ig-image-graph
|
||||
graph="histogram"
|
||||
src="../images/splash-800x500.jpg"
|
||||
mode="color"
|
||||
color="normalized"
|
||||
style="width: 600px; height: 300px"></ig-image-graph>
|
||||
|
||||
<ig-image-graph
|
||||
graph="waveform"
|
||||
src="../images/splash-800x500.jpg"
|
||||
mode="color"
|
||||
color="normalized" ></ig-image-graph>
|
||||
|
||||
|
||||
<!--
|
||||
<br>
|
||||
<canvas id="waveform"></canvas>
|
||||
<br>
|
||||
<button onclick="waveform(getElementById('input'), getElementById('waveform'), 'luminance')">Luminance</button>
|
||||
<button onclick="waveform(getElementById('input'), getElementById('waveform'), 'color')">Color</button>
|
||||
<button onclick="waveform(getElementById('input'), getElementById('waveform'), 'R')">R</button>
|
||||
<button onclick="waveform(getElementById('input'), getElementById('waveform'), 'G')">G</button>
|
||||
<button onclick="waveform(getElementById('input'), getElementById('waveform'), 'B')">B</button>
|
||||
|
||||
<br>
|
||||
<canvas id="histogram"></canvas>
|
||||
<br>
|
||||
<button onclick="histogram(getElementById('input'), getElementById('histogram'), 'luminance')">Luminance</button>
|
||||
<button onclick="histogram(getElementById('input'), getElementById('histogram'), 'color')">Color</button>
|
||||
<button onclick="histogram(getElementById('input'), getElementById('histogram'), 'R')">R</button>
|
||||
<button onclick="histogram(getElementById('input'), getElementById('histogram'), 'G')">G</button>
|
||||
<button onclick="histogram(getElementById('input'), getElementById('histogram'), 'B')">B</button>
|
||||
-->
|
||||
|
||||
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<!-- vim:set ts=4 sw=4 : -->
|
||||
325
Viewer/experiments/centering-and-alignment.html
Executable file
325
Viewer/experiments/centering-and-alignment.html
Executable file
@ -0,0 +1,325 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<style>
|
||||
|
||||
|
||||
/* size/proportions do not matter... */
|
||||
.viewer {
|
||||
position: relative;
|
||||
|
||||
width: 80vw;
|
||||
height: 80vh;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
background: gray;
|
||||
}
|
||||
/* center marker */
|
||||
.viewer:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
|
||||
font-size: 0pt;
|
||||
|
||||
box-sizing: border-box;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -4px;
|
||||
margin-left: -4px;
|
||||
|
||||
border: solid 2px rgba(0, 0, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
- center this to viewer vertically and horizontally
|
||||
-- top, left corner is center of viewer...
|
||||
- align vertically to center current ribbon (manual)
|
||||
*/
|
||||
.ribbon-set {
|
||||
position: relative;
|
||||
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
|
||||
transform-origin: top left;
|
||||
|
||||
transition: transform 0.1s linear;
|
||||
}
|
||||
.ribbon-locator {
|
||||
position: relative;
|
||||
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
|
||||
transition: transform 0.1s linear;
|
||||
}
|
||||
|
||||
/*
|
||||
- align image horizontally relative to ribbon-set left (manual)
|
||||
*/
|
||||
.ribbon {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: auto;
|
||||
/*min-width: 0px;*/
|
||||
width: auto;
|
||||
overflow: visible;
|
||||
white-space: nowrap;
|
||||
font-size: 0;
|
||||
|
||||
float: left;
|
||||
clear: both;
|
||||
|
||||
background: black;
|
||||
|
||||
transition: transform 0.1s linear;
|
||||
|
||||
/* XXX use vmin here... */
|
||||
margin-top: 2.5px;
|
||||
margin-bottom: 2.5px;
|
||||
/*
|
||||
margin-top: 0.5vmin;
|
||||
margin-bottom: 0.5vmin;
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
/* horizontal size/proportions do not matter... */
|
||||
.image {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
text-align:left;
|
||||
font-size: 12pt;
|
||||
overflow: hidden;
|
||||
|
||||
/* XXX use vmin here... */
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
/*
|
||||
width: 10vmin;
|
||||
height: 10vmin;
|
||||
*/
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
color: white;
|
||||
|
||||
background: no-repeat 50% transparent;
|
||||
background-size: contain;
|
||||
box-sizing: border-box;
|
||||
border: solid gray 1px;
|
||||
background-color: silver;
|
||||
}
|
||||
.current.image {
|
||||
/*border: solid red 5px;*/
|
||||
}
|
||||
|
||||
|
||||
.single-image-mode .ribbon {
|
||||
background: transparent;
|
||||
}
|
||||
.single-image-mode .image:not(.current) {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<script src="../ext-lib/jquery.js"></script>
|
||||
<script src="../ext-lib/jquery-ui.js"></script>
|
||||
|
||||
<script>
|
||||
$(function(){
|
||||
|
||||
// XXX needed when using vmin for image sizing...
|
||||
/*
|
||||
$(window)
|
||||
.resize(function(){
|
||||
$('.current.image').click()
|
||||
})
|
||||
*/
|
||||
|
||||
$('.image')
|
||||
.click(function(){
|
||||
$('.current.image').removeClass('current')
|
||||
|
||||
var image = $(this)
|
||||
var ribbon = image.parents('.ribbon').first()
|
||||
var ribbon_locator = ribbon.parents('.ribbon-locator').first()
|
||||
var ribbon_set = ribbon_locator.parents('.ribbon-set').first()
|
||||
|
||||
image
|
||||
.addClass('current')
|
||||
|
||||
var scale = ribbon_set.attr('scale') || 1
|
||||
var angle = ribbon_set.attr('angle') || 0
|
||||
|
||||
var l = image[0].offsetLeft
|
||||
var w = image[0].offsetWidth
|
||||
|
||||
var t = ribbon[0].offsetTop
|
||||
var h = ribbon[0].offsetHeight
|
||||
|
||||
// centering image...
|
||||
ribbon.css('transform', 'translateX(-'+ (l + w/2) +'px)')
|
||||
|
||||
// view angle and scale...
|
||||
ribbon_set.css('transform',
|
||||
'scale('+ scale +') '
|
||||
+'rotate('+ angle +'deg)')
|
||||
|
||||
// centering ribbons...
|
||||
ribbon_locator.css('transform', 'translateY(-'+ (t + h/2) +'px)')
|
||||
})
|
||||
|
||||
|
||||
|
||||
$('.ribbon-set')
|
||||
.draggable()
|
||||
$('.drag-reset')
|
||||
.click(function(){
|
||||
$('.ribbon-set').css({
|
||||
top: '',
|
||||
left: '',
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
var ribbon_scale
|
||||
var single_scale
|
||||
$('.single-image-toggle')
|
||||
.click(function(){
|
||||
var ribbon_set = $('.ribbon-set')
|
||||
|
||||
if($('.viewer').hasClass('single-image-mode')){
|
||||
single_scale = ribbon_set.attr('scale') || 5
|
||||
} else {
|
||||
ribbon_scale = ribbon_set.attr('scale') || 1
|
||||
}
|
||||
|
||||
$('.viewer').toggleClass('single-image-mode')
|
||||
|
||||
if($('.viewer').hasClass('single-image-mode')){
|
||||
ribbon_set.attr('scale', single_scale || 5)
|
||||
} else {
|
||||
ribbon_set.attr('scale', ribbon_scale || 1)
|
||||
}
|
||||
|
||||
$('.current.image').click()
|
||||
})
|
||||
|
||||
|
||||
|
||||
$('.zoom-reset')
|
||||
.click(function(){
|
||||
var ribbon_set = $('.ribbon-set')
|
||||
ribbon_set.attr('scale', '1')
|
||||
$('.current.image').click()
|
||||
})
|
||||
$('.zoom-in')
|
||||
.click(function(){
|
||||
var ribbon_set = $('.ribbon-set')
|
||||
var scale = (ribbon_set.attr('scale') || 1) * 1.2
|
||||
ribbon_set.attr('scale', scale)
|
||||
$('.current.image').click()
|
||||
})
|
||||
$('.zoom-out')
|
||||
.click(function(){
|
||||
var ribbon_set = $('.ribbon-set')
|
||||
var scale = (ribbon_set.attr('scale') || 1) / 1.2
|
||||
ribbon_set.attr('scale', scale)
|
||||
$('.current.image').click()
|
||||
})
|
||||
|
||||
|
||||
|
||||
$('.rotate-reset')
|
||||
.click(function(){
|
||||
var ribbon_set = $('.ribbon-set')
|
||||
ribbon_set.attr('angle', '0')
|
||||
$('.current.image').click()
|
||||
})
|
||||
$('.rotate-cw')
|
||||
.click(function(){
|
||||
var ribbon_set = $('.ribbon-set')
|
||||
var angle = parseInt(ribbon_set.attr('angle') || 0) + 10
|
||||
ribbon_set.attr('angle', angle)
|
||||
$('.current.image').click()
|
||||
})
|
||||
$('.rotate-ccw')
|
||||
.click(function(){
|
||||
var ribbon_set = $('.ribbon-set')
|
||||
var angle = parseInt(ribbon_set.attr('angle') || 0) - 10
|
||||
ribbon_set.attr('angle', angle)
|
||||
$('.current.image').click()
|
||||
})
|
||||
|
||||
|
||||
|
||||
$('.current.image').click()
|
||||
})
|
||||
</script>
|
||||
|
||||
<body>
|
||||
|
||||
<button class="single-image-toggle">▣</button>
|
||||
|
||||
<button class="zoom-in">+</button>
|
||||
<button class="zoom-reset">1x</button>
|
||||
<button class="zoom-out">-</button>
|
||||
|
||||
<button class="rotate-ccw">↺</button>
|
||||
<button class="rotate-reset">0°</button>
|
||||
<button class="rotate-cw">↻</button>
|
||||
|
||||
<button class="drag-reset">(0,0)</button>
|
||||
|
||||
<button class="rotate-reset zoom-reset drag-reset">reset all</button>
|
||||
|
||||
|
||||
<div class="viewer">
|
||||
<div class="ribbon-set">
|
||||
<div class="ribbon-locator">
|
||||
<div class="ribbon">
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
</div>
|
||||
<div class="ribbon">
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="current image"/></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
</div>
|
||||
<div class="ribbon">
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
<div class="image"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<!-- vim:set ts=4 sw=4 spell : -->
|
||||
BIN
Viewer/experiments/grayscale.jpg
Normal file
BIN
Viewer/experiments/grayscale.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
448
Viewer/experiments/ig-image-graph.js
Normal file
448
Viewer/experiments/ig-image-graph.js
Normal file
@ -0,0 +1,448 @@
|
||||
//---------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
// XXX still thinking on how to package this correctly...
|
||||
//
|
||||
//---------------------------------------------------------------------
|
||||
// image manipulation basics...
|
||||
|
||||
var Filters = {
|
||||
makeCanvas: function(w, h){
|
||||
var c = document.createElement('canvas')
|
||||
c.width = w
|
||||
c.height = h
|
||||
return c },
|
||||
|
||||
// as input takes an HTML Image object...
|
||||
getPixels: function(img, w, h){
|
||||
var w = w || img.width
|
||||
var h = h || img.height
|
||||
var c = this.makeCanvas(w, h)
|
||||
var context = c.getContext('2d')
|
||||
if(img == null){
|
||||
context.rect(0, 0, w, h)
|
||||
context.fillStyle = "black"
|
||||
context.fill()
|
||||
} else {
|
||||
context.drawImage(img, 0, 0, w, h) }
|
||||
return context.getImageData(0,0,c.width,c.height) },
|
||||
setPixels: function(c, data, w, h){
|
||||
c.width = data.width
|
||||
c.height = data.height
|
||||
var context = c.getContext('2d')
|
||||
context.putImageData(data, 0, 0) },
|
||||
|
||||
filterImage: function(filter, image, var_args){
|
||||
var args = [this.getPixels(image)]
|
||||
for(var i=2; i<arguments.length; i++){
|
||||
args.push(arguments[i])
|
||||
}
|
||||
return filter.apply(null, args) },
|
||||
|
||||
grayscale: function(pixels, args){
|
||||
var d = pixels.data
|
||||
for(var i=0; i<d.length; i+=4){
|
||||
var r = d[i]
|
||||
var g = d[i+1]
|
||||
var b = d[i+2]
|
||||
// CIE luminance for the RGB
|
||||
// The human eye is bad at seeing red and blue, so we de-emphasize them.
|
||||
var v = 0.2126*r + 0.7152*g + 0.0722*b
|
||||
d[i] = d[i+1] = d[i+2] = v }
|
||||
return pixels },
|
||||
// XXX need to resize this...
|
||||
histogram: function(pixels, mode, color){
|
||||
color = color || 'fill'
|
||||
mode = mode || 'luminance'
|
||||
|
||||
var w = 255
|
||||
var h = 255
|
||||
|
||||
// output buffer...
|
||||
var out = this.getPixels(null, w, h)
|
||||
|
||||
// pixel hit buffer...
|
||||
var count = []
|
||||
|
||||
var od = out.data
|
||||
var d = pixels.data
|
||||
|
||||
// get the stats...
|
||||
for(var i=0; i<d.length; i+=4){
|
||||
var r = d[i]
|
||||
var g = d[i+1]
|
||||
var b = d[i+2]
|
||||
|
||||
if(mode == 'luminance'){
|
||||
var v = Math.round(0.2126*r + 0.7152*g + 0.0722*b) * 4
|
||||
count[v] = count[v+1] = count[v+2] = (count[v] || 0) + 1
|
||||
|
||||
} else {
|
||||
if(mode == 'color' || mode == 'R'){
|
||||
count[r*4] = (count[r*4] || 0) + 1 }
|
||||
if(mode == 'color' || mode == 'G'){
|
||||
count[g*4+1] = (count[g*4+1] || 0) + 1 }
|
||||
if(mode == 'color' || mode == 'B'){
|
||||
count[b*4+2] = (count[b*4+2] || 0) + 1 } } }
|
||||
|
||||
var m = 255 / Math.max(...count.filter(function(){ return true }))
|
||||
|
||||
var pos = function(i, value){
|
||||
return (
|
||||
// horizontal position...
|
||||
i*4
|
||||
// value vertical offset...
|
||||
+ (255-Math.round(value*m))*w*4) }
|
||||
|
||||
// XXX would be nice to have an option to draw full columns...
|
||||
count.forEach(function(v, i){
|
||||
var j = pos(i/4, v)
|
||||
while(j < od.length){
|
||||
j += w*4
|
||||
od[j] = 255
|
||||
if(color == 'point'){
|
||||
// correct for blue visibility...
|
||||
mode != 'luminance'
|
||||
&& (i-2)%4 == 0
|
||||
&& (od[j-1] = od[j-2] = 180)
|
||||
break } } })
|
||||
|
||||
return out },
|
||||
waveform: function(pixels, mode, color){
|
||||
mode = mode || 'luminance'
|
||||
color = color || 'normalized'
|
||||
|
||||
var w = pixels.width
|
||||
|
||||
// normalize pixel ratio...
|
||||
var m = (1/pixels.height)*255
|
||||
|
||||
var offsetTop = 0
|
||||
var offsetBottom = 0
|
||||
|
||||
// output buffer...
|
||||
var out = this.getPixels(null,
|
||||
w,
|
||||
offsetTop + 255 + offsetBottom)
|
||||
|
||||
// pixel hit buffer...
|
||||
var count = []
|
||||
|
||||
var od = out.data
|
||||
var d = pixels.data
|
||||
|
||||
var pos = function(i, value){
|
||||
return (
|
||||
// top margin...
|
||||
offsetTop*w*4
|
||||
// horixontal position...
|
||||
+ i%(w*4)
|
||||
// value vertical offset...
|
||||
+ (255-Math.round(value))*w*4) }
|
||||
|
||||
var gain = 100
|
||||
|
||||
for(var i=0; i<d.length; i+=4){
|
||||
|
||||
var r = d[i]
|
||||
var g = d[i+1]
|
||||
var b = d[i+2]
|
||||
var c, j, f, x, y
|
||||
|
||||
|
||||
if(mode == 'luminance'){
|
||||
// CIE luminance for RGB
|
||||
var v = 0.2126*r + 0.7152*g + 0.0722*b
|
||||
c = count[j = pos(i, v)] = (count[j] || 0) + m
|
||||
od[j] = od[j+1] = od[j+2] = c * gain
|
||||
|
||||
} else {
|
||||
if(mode == 'color' || mode == 'R'){
|
||||
f = 0.2126
|
||||
x = 1
|
||||
y = 2
|
||||
j = pos(i, r)
|
||||
c = count[j] = (count[j] || 0) + m
|
||||
od[j] = c * gain }
|
||||
if(mode == 'color' || mode == 'G'){
|
||||
f = 0.7152
|
||||
x = -1
|
||||
y = 1
|
||||
j = pos(i, g) + 1
|
||||
c = count[j] = (count[j] || 0) + m
|
||||
od[j] = c * gain }
|
||||
if(mode == 'color' || mode == 'B'){
|
||||
f = 0.0722
|
||||
x = -2
|
||||
y = -1
|
||||
j = pos(i, b) + 2
|
||||
c = count[j] = (count[j] || 0) + m
|
||||
od[j] = c * gain }
|
||||
|
||||
// normalize...
|
||||
mode != 'color'
|
||||
&& (color == 'white' ?
|
||||
(od[j+x] = od[j+y] = c * gain)
|
||||
: color == 'normalized' ?
|
||||
(od[j+x] = od[j+y] = c * gain/2 * (1-f))
|
||||
: null) } }
|
||||
return out },
|
||||
}
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// helpers...
|
||||
|
||||
var WAVEFORM_SIZE = 1000
|
||||
var waveform = function(img, canvas, mode, color){
|
||||
var d = Filters.getPixels(img, WAVEFORM_SIZE)
|
||||
var w = Filters.waveform(d, mode, color)
|
||||
Filters.setPixels(canvas, w) }
|
||||
|
||||
var HISTOGRAM_SIZE = 1000
|
||||
var histogram = function(img, canvas, mode, color){
|
||||
var d = Filters.getPixels(img)
|
||||
var w = Filters.histogram(d, mode, color)
|
||||
Filters.setPixels(canvas, w) }
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Custom element...
|
||||
|
||||
igImageGraph_template = `
|
||||
<style>
|
||||
:host {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
background: black;
|
||||
|
||||
width: attr(image-width);
|
||||
height: attr(graph-height);
|
||||
}
|
||||
:host canvas {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
border: 2px solid gray;
|
||||
}
|
||||
:host .controls {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
left: 2px;
|
||||
}
|
||||
:host .controls button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: white;
|
||||
opacity: 0.7;
|
||||
float: right;
|
||||
}
|
||||
:host .controls button.current {
|
||||
text-decoration: underline;
|
||||
opacity: 0.9;
|
||||
}
|
||||
:host .controls button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
<canvas class="graph"></canvas>
|
||||
<div class="controls"></div>
|
||||
`
|
||||
|
||||
class igImageGraph extends HTMLElement {
|
||||
template = 'ig-image-graph'
|
||||
graphs = {
|
||||
waveform,
|
||||
histogram,
|
||||
}
|
||||
modes = ['luminance', 'color', 'R', 'G', 'B']
|
||||
color_modes = ['normalized', 'white', 'point']
|
||||
|
||||
constructor(src){
|
||||
super()
|
||||
// shadow DOM
|
||||
var shadow = this.__shadow =
|
||||
this.attachShadow({mode: 'open'})
|
||||
// get/create template...
|
||||
var tpl = document.getElementById(this.template)
|
||||
if(!tpl){
|
||||
var tpl = document.createElement('template')
|
||||
tpl.setAttribute('id', this.template)
|
||||
tpl.innerHTML = igImageGraph_template
|
||||
document.head.appendChild(tpl) }
|
||||
shadow.appendChild(tpl.content.cloneNode(true)) }
|
||||
connectedCallback(){
|
||||
this.update_controls()
|
||||
this.update() }
|
||||
|
||||
// attributes...
|
||||
get observedAttributes(){
|
||||
return [
|
||||
'src',
|
||||
'mode',
|
||||
'color',
|
||||
'nocontrols',
|
||||
'graph',
|
||||
]}
|
||||
attributeChangedCallback(name, from, to){
|
||||
name == 'nocontrols'
|
||||
&& this.update_controls()
|
||||
this.update() }
|
||||
|
||||
get graph(){
|
||||
return this.getAttribute('graph') || 'waveform' }
|
||||
set graph(value){
|
||||
value in this.graphs
|
||||
&& this.setAttribute('graph', value)
|
||||
value == ''
|
||||
&& this.removeAttribute('graph')
|
||||
this.update() }
|
||||
get src(){
|
||||
return this.getAttribute('src') }
|
||||
set src(value){
|
||||
var that = this
|
||||
this.__update_handler = this.__update_handler
|
||||
|| this.update.bind(this)
|
||||
var url = typeof(value) == typeof('str')
|
||||
// get/create image...
|
||||
var img = this.image =
|
||||
url ?
|
||||
(this.image || document.createElement('img'))
|
||||
: value
|
||||
img.removeEventListener('load', this.__update_handler)
|
||||
img.addEventListener('load', this.__update_handler)
|
||||
// set .src and img.src...
|
||||
this.setAttribute('src',
|
||||
url ?
|
||||
(img.src = value)
|
||||
: img.src) }
|
||||
get mode(){
|
||||
return this.getAttribute('mode') || 'color' }
|
||||
set mode(value){
|
||||
this.modes.includes(value)
|
||||
&& this.setAttribute('mode', value)
|
||||
value === undefined
|
||||
&& this.removeAttribute('color')
|
||||
this.update_controls()
|
||||
this.update() }
|
||||
get color(){
|
||||
return this.getAttribute('color') || 'normalized' }
|
||||
set color(value){
|
||||
this.color_modes.includes(value)
|
||||
&& this.setAttribute('color', value)
|
||||
value === undefined
|
||||
&& this.removeAttribute('color')
|
||||
this.update() }
|
||||
get nocontrols(){
|
||||
return this.getAttribute('nocontrols') != null }
|
||||
set nocontrols(value){
|
||||
value ?
|
||||
this.setAttribute('nocontrols', '')
|
||||
: this.removeAttribute('nocontrols')
|
||||
this.update_controls()
|
||||
this.update() }
|
||||
|
||||
// API...
|
||||
update_controls(){
|
||||
var that = this
|
||||
var mode = this.mode
|
||||
|
||||
var controls = this.__shadow.querySelector('.controls')
|
||||
controls.innerHTML = ''
|
||||
// modes...
|
||||
var buttons = [
|
||||
// graph...
|
||||
function(){
|
||||
var button = document.createElement('button')
|
||||
button.classList.add('update')
|
||||
//button.innerHTML = '◑'
|
||||
button.innerHTML = '◪'
|
||||
button.onclick = function(){
|
||||
that.graph = that.graph == 'waveform' ?
|
||||
'histogram'
|
||||
: 'waveform'
|
||||
that.update() }
|
||||
return button }(),
|
||||
// modes...
|
||||
(this.nocontrols ?
|
||||
[]
|
||||
: this.modes)
|
||||
// mode buttons...
|
||||
.map(function(m){
|
||||
var button = document.createElement('button')
|
||||
button.innerText = m
|
||||
button.classList.add(m, ...(m == mode ? ['current'] : []))
|
||||
button.onclick = function(){
|
||||
that.mode = m }
|
||||
return button }),
|
||||
/*
|
||||
// color mode switch...
|
||||
function(){
|
||||
var button = document.createElement('button')
|
||||
button.innerText = '('+ that.color[0] +')'
|
||||
button.onclick = function(){
|
||||
that.color = that.color_modes[
|
||||
(that.color_modes.indexOf(that.color) + 1)
|
||||
% that.color_modes.length]
|
||||
this.innerText = '('+ that.color[0] +')' }
|
||||
return button }(),
|
||||
//*/
|
||||
// reload...
|
||||
function(){
|
||||
var button = document.createElement('button')
|
||||
button.classList.add('update')
|
||||
button.innerHTML = '⟳'
|
||||
button.onclick = function(){ that.update() }
|
||||
return button }(),
|
||||
]
|
||||
.flat()
|
||||
.reverse()
|
||||
.forEach(function(button){
|
||||
controls.appendChild(button) })
|
||||
return this }
|
||||
update(){
|
||||
var that = this
|
||||
var mode = this.mode
|
||||
|
||||
// controls...
|
||||
// remove...
|
||||
if(!this.nocontrols){
|
||||
var controls = this.__shadow.querySelector('.controls')
|
||||
// current button state...
|
||||
var button = controls.querySelector('button.'+this.mode)
|
||||
button
|
||||
&& button.classList.add('current') }
|
||||
|
||||
// XXX configurable...
|
||||
var type = this.graph
|
||||
var graph = this.graphs[type]
|
||||
|
||||
var canvas = this.__shadow.querySelector('canvas')
|
||||
|
||||
if(this.image){
|
||||
graph(this.image, canvas, this.mode, this.color)
|
||||
|
||||
} else if(this.src){
|
||||
this.src = this.src }
|
||||
|
||||
return this }
|
||||
}
|
||||
window.customElements.define('ig-image-graph', igImageGraph)
|
||||
|
||||
var makeImageGraph = function(img, options){
|
||||
var g = document.createElement('ig-image-graph')
|
||||
Object.assign(g,
|
||||
options || {})
|
||||
g.src = img
|
||||
return g }
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// vim:set ts=4 sw=4 :
|
||||
237
Viewer/experiments/image-crop-edit.html
Executable file
237
Viewer/experiments/image-crop-edit.html
Executable file
@ -0,0 +1,237 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<style>
|
||||
|
||||
label {
|
||||
margin: 0.2em;
|
||||
}
|
||||
label label {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
input[type=checkbox],
|
||||
input[type=radio] {
|
||||
display: none;
|
||||
}
|
||||
input:not(:checked) ~ * {
|
||||
opacity: 0.3;
|
||||
}
|
||||
input:hover:not(:checked) ~ * {
|
||||
opacity: 0.8;
|
||||
}
|
||||
input:not(:checked) ~ sub:hover,
|
||||
input:not(:checked) ~ sub {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
sub .material-icons {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.crop {
|
||||
position: absolute;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
|
||||
border: solid 1px black;
|
||||
}
|
||||
|
||||
|
||||
.center {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin-left: -8.5px;
|
||||
margin-top: -8.5px;
|
||||
|
||||
border: solid 1px black;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.center:before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
content: "";
|
||||
|
||||
bottom: 50%;
|
||||
right: 50%;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin-right: -0.5px;
|
||||
margin-bottom: -0.5px;
|
||||
|
||||
border-bottom: solid 1px black;
|
||||
border-right: solid 1px black;
|
||||
}
|
||||
.center:after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
content: "";
|
||||
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin-top: -0.5px;
|
||||
margin-left: -0.5px;
|
||||
|
||||
border-top: solid 1px black;
|
||||
border-left: solid 1px black;
|
||||
}
|
||||
|
||||
</style>
|
||||
<link rel="stylesheet" href="http://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
|
||||
|
||||
<link rel="stylesheet" href="../css/fonts.css">
|
||||
|
||||
<script src="../ext-lib/jquery.js"></script>
|
||||
<script src="../ext-lib/jquery-ui.js"></script>
|
||||
<script src="../ext-lib/jquery.ui.touch-punch.min.js"></script>
|
||||
|
||||
<script src="../lib/jli.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
var ORIGINAL = 1
|
||||
|
||||
|
||||
$(function(){
|
||||
$('.crop')
|
||||
.resizable({
|
||||
handles: 'all',
|
||||
})
|
||||
.draggable()
|
||||
$('.crop .center')
|
||||
.draggable()
|
||||
})
|
||||
|
||||
|
||||
function setRatio(ratio){
|
||||
var crop = $('.crop')
|
||||
|
||||
crop
|
||||
.resizable("option", "aspectRatio", ratio)
|
||||
// XXX this feels like a hack but it works...
|
||||
.data('uiResizable')._aspectRatio = ratio
|
||||
|
||||
if(ratio === false){
|
||||
$('#lock-ratio')[0].checked = false
|
||||
return
|
||||
}
|
||||
|
||||
var w = crop.width()
|
||||
var h = crop.height()
|
||||
|
||||
// resize...
|
||||
var t = w + h
|
||||
h = t / (ratio + 1)
|
||||
w = t - h
|
||||
crop
|
||||
.height(h)
|
||||
.width(w)
|
||||
}
|
||||
|
||||
function flip(){
|
||||
var crop = $('.crop')
|
||||
var r = crop.resizable("option", "aspectRatio")
|
||||
|
||||
if(r > 0){
|
||||
setRatio(1/r)
|
||||
|
||||
// no aspect ratio defined, just switch...
|
||||
} else {
|
||||
var w = crop.width()
|
||||
var h = crop.height()
|
||||
|
||||
crop
|
||||
.height(w)
|
||||
.width(h)
|
||||
}
|
||||
}
|
||||
|
||||
function toggleLock(m){
|
||||
var crop = $('.crop')
|
||||
var r = m || crop.resizable("option", "aspectRatio")
|
||||
|
||||
if(r === false){
|
||||
var w = crop.width()
|
||||
var h = crop.height()
|
||||
|
||||
$('#lock-ratio')[0].checked = true
|
||||
setRatio(w/h)
|
||||
|
||||
} else {
|
||||
$('#lock-ratio')[0].checked = false
|
||||
setRatio(false)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<body>
|
||||
|
||||
<button><span title="toggle crop on/off" class="material-icons">crop</span></button>
|
||||
<label>
|
||||
<input type="radio" name="ratio" checked onclick="setRatio(false)">
|
||||
<span class="material-icons">crop_free</span>
|
||||
<sub>
|
||||
<label>
|
||||
<input id="lock-ratio" type="checkbox" onclick="toggleLock()">
|
||||
<span class="material-icons">lock</span>
|
||||
</label>
|
||||
</sub>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="ratio" onclick="setRatio(ORIGINAL)">
|
||||
<span title="original ratio" class="material-icons">crop_original</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="ratio" onclick="setRatio(1)">
|
||||
<span class="material-icons">crop_square</span>
|
||||
<span>1:1</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="ratio" onclick="setRatio(3/2)">
|
||||
<span class="material-icons">crop_3_2</span>
|
||||
<span>3:2</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="ratio" onclick="setRatio(16/9)">
|
||||
<span class="material-icons">crop_16_9</span>
|
||||
<span>16:9</span>
|
||||
</label>
|
||||
<button onclick="flip()"><span class="material-icons">crop_rotate</span></button>
|
||||
<button><span class="material-icons">clear</span></button>
|
||||
|
||||
<br>
|
||||
<pre>
|
||||
TODO:
|
||||
- rotation and rotation handle
|
||||
- touch
|
||||
- multitouch
|
||||
- center point
|
||||
</pre>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="crop">
|
||||
<div class="center">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<!-- vim:set sw=4 ts=4 : -->
|
||||
449
Viewer/experiments/native-scroll-ribbon-infinite.html
Executable file
449
Viewer/experiments/native-scroll-ribbon-infinite.html
Executable file
@ -0,0 +1,449 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
//---------------------------------------------------------------------
|
||||
//
|
||||
|
||||
-->
|
||||
|
||||
<style>
|
||||
.mark-center:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
content: "";
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
border-left: solid 2px red;
|
||||
border-top: solid 2px red;
|
||||
margin-left: -1px;
|
||||
margin-top: -1px;
|
||||
opacity: 0.8;
|
||||
z-index: 1;
|
||||
}
|
||||
.mark-center:before {
|
||||
position: absolute;
|
||||
display: block;
|
||||
content: "";
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
right: 50%;
|
||||
bottom: 50%;
|
||||
border-bottom: solid 2px red;
|
||||
border-right: solid 2px red;
|
||||
margin-bottom: -1px;
|
||||
margin-right: -1px;
|
||||
opacity: 0.8;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
/* XXX appears that there is no way to hide the scrollbar on FF...
|
||||
* ...one way around this is to use something like iScroll/Scrolly
|
||||
* on FF or where more control is needed...
|
||||
*/
|
||||
.viewer {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border: solid 1px gray;
|
||||
|
||||
width: 600px;
|
||||
height: 500px;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.scaler {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -50%;
|
||||
margin-left: -50%;
|
||||
|
||||
transform-origin: 50% 50%;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.scaler::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* This is to be used for:
|
||||
* - vrtical positioning
|
||||
* - scaling
|
||||
* (update width to fit viewer)
|
||||
*/
|
||||
.ribbon-set {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
/* This allways needs to be of viewer width, this mostly applies
|
||||
* to scaling...
|
||||
*/
|
||||
width: 100%;
|
||||
|
||||
padding-top: 50%;
|
||||
padding-bottom: 50%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ribbon-container {
|
||||
position: relative;
|
||||
display: block;
|
||||
|
||||
height: 120px;
|
||||
width: 100%;
|
||||
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.ribbon-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.ribbon-container:before {
|
||||
position: absolute;
|
||||
content: attr(index);
|
||||
}
|
||||
|
||||
|
||||
.ribbon {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
height: 100px;
|
||||
width: auto;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: visible;
|
||||
|
||||
background: silver;
|
||||
/*box-shadow: 0px 0px 25px -10px rgba(0,0,0,0.75);*/
|
||||
box-shadow: 0px 0px 25px -10px rgba(0,0,0,1);
|
||||
|
||||
/* start/end markers... */
|
||||
/*border-left: 100px solid gray;
|
||||
border-right: 100px solid gray;*/
|
||||
|
||||
margin: 10px;
|
||||
|
||||
margin-left: 50%;
|
||||
/* XXX for some reason this does not work as expected */
|
||||
margin-right: 50%;
|
||||
}
|
||||
|
||||
|
||||
.image {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
|
||||
outline: solid blue 1px;
|
||||
|
||||
background: silver;
|
||||
}
|
||||
.image:after {
|
||||
content: attr(index);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<script src="../ext-lib/jquery.js"></script>
|
||||
<script src="../ext-lib/jquery-ui.js"></script>
|
||||
|
||||
<script src="../ext-lib/velocity.min.js"></script>
|
||||
|
||||
<script src="../lib/jli.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
var scale = function(){
|
||||
var s = /scale\(([^\)]+)\)/.exec($('.scaler')[0].style.transform)
|
||||
return s ? parseFloat(s.pop()) : 1
|
||||
}
|
||||
|
||||
|
||||
// XXX when setting origin at scales different from 1, we'll need to
|
||||
// adjust offset to compensate for the shift...
|
||||
// XXX one other simplification might be adding a new element specifically
|
||||
// dedicated to scaling...
|
||||
var centerOrigin = function(){
|
||||
var H = $('.viewer').height()
|
||||
var s = $('.viewer')[0].scrollTop
|
||||
|
||||
$('.ribbon-set').css({
|
||||
'transform-origin': '50% '+ (s + H/2) +'px'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// XXX these accumolate errors...
|
||||
var zoomIn = function(c){
|
||||
c = c || 1.2
|
||||
|
||||
centerOrigin()
|
||||
$('.scaler')
|
||||
.velocity('stop')
|
||||
.velocity({
|
||||
scale: '*='+c,
|
||||
|
||||
width: '/='+c,
|
||||
height: '/='+c,
|
||||
'margin-left': '/='+c,
|
||||
'margin-top': '/='+c,
|
||||
}, {
|
||||
duration: 300,
|
||||
easing: 'linear',
|
||||
})
|
||||
}
|
||||
var zoomOut = function(c){
|
||||
c = c || 1.2
|
||||
|
||||
centerOrigin()
|
||||
$('.scaler')
|
||||
.velocity('stop')
|
||||
.velocity({
|
||||
scale: '/='+c,
|
||||
|
||||
width: '*='+c,
|
||||
height: '*='+c,
|
||||
'margin-left': '*='+c,
|
||||
'margin-top': '*='+c,
|
||||
}, {
|
||||
duration: 300,
|
||||
easing: 'linear',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// items - list of items, each item must be make(..) compatible
|
||||
// ...this can also be a function and return multiple
|
||||
// items (XXX)
|
||||
// make - item DOM constructor
|
||||
//
|
||||
// Options:
|
||||
// container - element that actually contains the items (default: 'this')
|
||||
// direction - scroll direction (default: 'vertical')
|
||||
// threshold -
|
||||
//
|
||||
// XXX horizontal scroll is still buggy -- mostly in thresholds...
|
||||
var makeScrollHandler = function(items, make, options){
|
||||
options = options || {}
|
||||
|
||||
var direction = options.direction || 'vertical'
|
||||
//var threshold = options.threshold ||
|
||||
var _container = options.container || 'this'
|
||||
|
||||
// XXX should we do an initial load here???
|
||||
|
||||
return function(evt){
|
||||
var container = _container == 'this' ?
|
||||
this
|
||||
: typeof(_container) == typeof('str') ?
|
||||
this.querySelector(_container)
|
||||
: _container
|
||||
|
||||
if(direction == 'vertical'){
|
||||
var size = this.scrollHeight
|
||||
var offset = this.scrollTop
|
||||
var visible_size = this.offsetHeight
|
||||
|
||||
var elem_scroll_attr = 'scrollTop'
|
||||
var elem_offset_attr = 'offsetTop'
|
||||
var elem_size_attr = 'offsetHeight'
|
||||
|
||||
} else {
|
||||
var size = this.scrollWidth
|
||||
var offset = this.scrollLeft
|
||||
var visible_size = this.offsetWidth
|
||||
|
||||
var elem_scroll_attr = 'scrollLeft'
|
||||
var elem_offset_attr = 'offsetLeft'
|
||||
var elem_size_attr = 'offsetWidth'
|
||||
}
|
||||
|
||||
// XXX
|
||||
var threshold = visible_size
|
||||
|
||||
var dom_items = container.children
|
||||
|
||||
// head limit -- add items to the head...
|
||||
if(offset < threshold){
|
||||
var i = parseInt(dom_items[0].getAttribute('index')) - 1
|
||||
var e = items instanceof Function ?
|
||||
items(i)
|
||||
// XXX make this support multiple items...
|
||||
: items[i]
|
||||
|
||||
// make the item(s)...
|
||||
if(e){
|
||||
// XXX need to account for situations where the whole thing is replaced...
|
||||
var c = dom_items[0]
|
||||
var pre = c[elem_offset_attr]
|
||||
|
||||
container.prepend(make(e))
|
||||
|
||||
// compensate offset for added items...
|
||||
var d = c[elem_offset_attr] - pre
|
||||
// XXX need to do this only if the browser is not compensating...
|
||||
if(direction == 'horizontal'){
|
||||
this[elem_scroll_attr] += d
|
||||
}
|
||||
|
||||
// remove hidden items from tail...
|
||||
var t = offset + visible_size + threshold
|
||||
;[].slice.call(dom_items)
|
||||
// XXX add threshold / items-to-keep-offscreen limit ...
|
||||
// XXX this is wrong for horizontal scroll...
|
||||
.filter(function(e){ return e[elem_offset_attr] > t })
|
||||
// XXX can we remove these in one go???
|
||||
.forEach(function(e){ e.remove() })
|
||||
}
|
||||
}
|
||||
|
||||
// tail limit -- add items to the tail...
|
||||
if( size - (offset + visible_size) < threshold ){
|
||||
var i = parseInt(dom_items[dom_items.length-1].getAttribute('index')) + 1
|
||||
var e = items instanceof Function ?
|
||||
items(i)
|
||||
// XXX make this support multiple items...
|
||||
: items[i]
|
||||
|
||||
if(e){
|
||||
container.append(make(e))
|
||||
|
||||
//var clone = container.cloneNode(true)
|
||||
//container.replaceWith(clone)
|
||||
|
||||
// XXX need to account for situations where the whole thing is replaced...
|
||||
var c = dom_items[dom_items.length-1]
|
||||
var pre = c[elem_offset_attr]
|
||||
|
||||
// remove hidden items for head...
|
||||
;[].slice.call(dom_items)
|
||||
// XXX add threshold / items-to-keep-offscreen limit ...
|
||||
.filter(function(e){ return e[elem_offset_attr] + e[elem_size_attr] < offset })
|
||||
// XXX can we remove these in one go???
|
||||
.forEach(function(e){ e.remove() })
|
||||
|
||||
// compensate offset for removed items...
|
||||
var d = c[elem_offset_attr] - pre
|
||||
// XXX need to do this only if the browser is not compensating...
|
||||
if(direction == 'horizontal'){
|
||||
this[elem_scroll_attr] += d
|
||||
}
|
||||
|
||||
//container.replaceWith(container)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var setup = function(){
|
||||
var H = $('.viewer').height()
|
||||
var W = $('.viewer').width()
|
||||
|
||||
var ribbon_set = $('.ribbon-set')[0]
|
||||
|
||||
|
||||
// XXX need to calculate this considering scale...
|
||||
var threshold = 300
|
||||
var ribbon_count = 10
|
||||
var image_count = 10
|
||||
|
||||
|
||||
var ribbon_container = document.createElement('div')
|
||||
ribbon_container.classList.add('ribbon-container')
|
||||
var ribbon = document.createElement('div')
|
||||
ribbon.classList.add('ribbon')
|
||||
var image = document.createElement('div')
|
||||
image.classList.add('image')
|
||||
|
||||
var makeImage = function(n){
|
||||
var i = image.cloneNode()
|
||||
i.setAttribute('index', n)
|
||||
return i
|
||||
}
|
||||
var makeRibbon = function(n){
|
||||
var r = ribbon.cloneNode()
|
||||
for(var i=0; i < image_count; i++){
|
||||
r.appendChild(makeImage(i))
|
||||
}
|
||||
|
||||
var rc = ribbon_container.cloneNode()
|
||||
rc.appendChild(r)
|
||||
rc.setAttribute('index', n)
|
||||
|
||||
$(rc).scroll(makeScrollHandler(
|
||||
function(n){ return n >= 0 ? n : undefined },
|
||||
makeImage,
|
||||
{
|
||||
container: r,
|
||||
direction: 'horizontal',
|
||||
threshold: 300,
|
||||
}))
|
||||
|
||||
return rc
|
||||
}
|
||||
|
||||
|
||||
var fragment = document.createDocumentFragment()
|
||||
for(var i=0; i < ribbon_count; i++){
|
||||
fragment.appendChild(makeRibbon(i))
|
||||
}
|
||||
ribbon_set.appendChild(fragment)
|
||||
|
||||
|
||||
// set margins to be parant and not content dependant...
|
||||
$('.scaler')
|
||||
.velocity({
|
||||
'margin-left': -W/2,
|
||||
'margin-top': -H/2,
|
||||
}, 0)
|
||||
.scroll(makeScrollHandler(
|
||||
function(n){ return n >= 0 ? n : undefined },
|
||||
makeRibbon,
|
||||
{
|
||||
container: ribbon_set,
|
||||
threshold: 300,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
$(function(){
|
||||
setup()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="viewer mark-center">
|
||||
<div class="scaler">
|
||||
<div class="ribbon-set">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<!-- vim:set sw=4 ts=4 : -->
|
||||
369
Viewer/experiments/native-scroll-ribbon.html
Executable file
369
Viewer/experiments/native-scroll-ribbon.html
Executable file
@ -0,0 +1,369 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
//---------------------------------------------------------------------
|
||||
// Experiment: use native scroll for ribbons and view...
|
||||
// Factors:
|
||||
// + the browser will do all the heavy lifting and do it faster
|
||||
// than we can ever hope to do it in JS (assumption)
|
||||
// - will require us to add an extra container per ribbon
|
||||
//
|
||||
// Experiment result:
|
||||
// - more uniform and fast across browsers
|
||||
// (except FF - can't disable scrollbars, need to cheat)
|
||||
// - less controllable (inertia, gestures, ...)
|
||||
// - is affected by scaling in a bad way - paralax...
|
||||
//
|
||||
// Conclusion:
|
||||
// - this again brings us to using code to control the scroll
|
||||
// which in turn defeats the original purpose of avoiding
|
||||
// extra complexity...
|
||||
//
|
||||
// See:
|
||||
// experiments/native-scroll-ribbon.html
|
||||
//
|
||||
|
||||
-->
|
||||
|
||||
<style>
|
||||
.mark-center:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
content: "";
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
border-left: solid 2px red;
|
||||
border-top: solid 2px red;
|
||||
margin-left: -1px;
|
||||
margin-top: -1px;
|
||||
opacity: 0.8;
|
||||
z-index: 1;
|
||||
}
|
||||
.mark-center:before {
|
||||
position: absolute;
|
||||
display: block;
|
||||
content: "";
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
right: 50%;
|
||||
bottom: 50%;
|
||||
border-bottom: solid 2px red;
|
||||
border-right: solid 2px red;
|
||||
margin-bottom: -1px;
|
||||
margin-right: -1px;
|
||||
opacity: 0.8;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
/* XXX appears that there is no way to hide the scrollbar on FF...
|
||||
* ...one way around this is to use something like iScroll/Scrolly
|
||||
* on FF or where more control is needed...
|
||||
*/
|
||||
.viewer {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border: solid 1px gray;
|
||||
|
||||
width: 600px;
|
||||
height: 500px;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.scaler {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -50%;
|
||||
margin-left: -50%;
|
||||
|
||||
transform-origin: 50% 50%;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.scaler::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* This is to be used for:
|
||||
* - vrtical positioning
|
||||
* - scaling
|
||||
* (update width to fit viewer)
|
||||
*/
|
||||
.ribbon-set {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
/* This allways needs to be of viewer width, this mostly applies
|
||||
* to scaling...
|
||||
*/
|
||||
width: 100%;
|
||||
|
||||
padding-top: 50%;
|
||||
padding-bottom: 50%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ribbon-container {
|
||||
position: relative;
|
||||
|
||||
height: 120px;
|
||||
width: 100%;
|
||||
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.ribbon-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ribbon {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
height: 100px;
|
||||
width: 1000px;
|
||||
|
||||
background: silver;
|
||||
/*box-shadow: 0px 0px 25px -10px rgba(0,0,0,0.75);*/
|
||||
box-shadow: 0px 0px 25px -10px rgba(0,0,0,1);
|
||||
|
||||
/* start/end markers... */
|
||||
border-left: 100px solid gray;
|
||||
border-right: 100px solid gray;
|
||||
|
||||
margin: 10px;
|
||||
|
||||
margin-left: 50%;
|
||||
/* XXX for some reason this does not work as expected */
|
||||
margin-right: 50%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<script src="../ext-lib/jquery.js"></script>
|
||||
<script src="../ext-lib/jquery-ui.js"></script>
|
||||
|
||||
<script src="../ext-lib/velocity.min.js"></script>
|
||||
|
||||
<script src="../ext-lib/iscroll.js"></script>
|
||||
<script src="../ext-lib/iscroll-zoom.js"></script>
|
||||
|
||||
<script src="../lib/jli.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
var scale = function(){
|
||||
var s = /scale\(([^\)]+)\)/.exec($('.scaler')[0].style.transform)
|
||||
return s ? parseFloat(s.pop()) : 1
|
||||
}
|
||||
|
||||
|
||||
// XXX when setting origin at scales different from 1, we'll need to
|
||||
// adjust offset to compensate for the shift...
|
||||
// XXX one other simplification might be adding a new element specifically
|
||||
// dedicated to scaling...
|
||||
var centerOrigin = function(){
|
||||
var H = $('.viewer').height()
|
||||
var s = $('.viewer')[0].scrollTop
|
||||
|
||||
$('.ribbon-set').css({
|
||||
'transform-origin': '50% '+ (s + H/2) +'px'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// XXX these accumolate errors...
|
||||
var zoomIn = function(c){
|
||||
c = c || 1.2
|
||||
|
||||
centerOrigin()
|
||||
$('.scaler')
|
||||
.velocity('stop')
|
||||
.velocity({
|
||||
scale: '*='+c,
|
||||
|
||||
width: '/='+c,
|
||||
height: '/='+c,
|
||||
'margin-left': '/='+c,
|
||||
'margin-top': '/='+c,
|
||||
}, {
|
||||
duration: 300,
|
||||
easing: 'linear',
|
||||
})
|
||||
}
|
||||
var zoomOut = function(c){
|
||||
c = c || 1.2
|
||||
|
||||
centerOrigin()
|
||||
$('.scaler')
|
||||
.velocity('stop')
|
||||
.velocity({
|
||||
scale: '/='+c,
|
||||
|
||||
width: '*='+c,
|
||||
height: '*='+c,
|
||||
'margin-left': '*='+c,
|
||||
'margin-top': '*='+c,
|
||||
}, {
|
||||
duration: 300,
|
||||
easing: 'linear',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
var setup = function(){
|
||||
var H = $('.viewer').height()
|
||||
var W = $('.viewer').width()
|
||||
|
||||
// set margins to be parant and not content dependant...
|
||||
$('.scaler')
|
||||
.velocity({
|
||||
'margin-left': -W/2,
|
||||
'margin-top': -H/2,
|
||||
}, 0)
|
||||
}
|
||||
|
||||
|
||||
|
||||
var ISCROLL = false
|
||||
|
||||
|
||||
$(function(){
|
||||
setup()
|
||||
|
||||
|
||||
// setup iScroll...
|
||||
if(ISCROLL){
|
||||
// Vertical scroll and zoom...
|
||||
$('.scaler')
|
||||
.css({
|
||||
overflow: 'hidden',
|
||||
})
|
||||
window.scroll_view = new IScroll('.scaler', {
|
||||
// XXX setting this to false makes zoom reset x to 0 after it's done...
|
||||
scrollX: false,
|
||||
scrollY: true,
|
||||
|
||||
disableMouse: false,
|
||||
mouseWheel: true,
|
||||
|
||||
eventPassthrough: 'horizontal',
|
||||
|
||||
zoom: true,
|
||||
})
|
||||
|
||||
var t
|
||||
$('.scaler').on('touchend mouseup', function(){
|
||||
//t = $('.ribbon-set')[0].style.transform
|
||||
|
||||
t = $('.ribbon-set').offset().left
|
||||
})
|
||||
scroll_view.on('zoomEnd', function(){
|
||||
var v = $('.viewer')
|
||||
|
||||
var s = scroll_view.scale
|
||||
var W = v.width()
|
||||
var H = v.width()
|
||||
|
||||
var w = W/s
|
||||
var h = H/s
|
||||
|
||||
var e = $('.ribbon-set')
|
||||
|
||||
// XXX compensate for offset -- scroll ribbons by to
|
||||
// place them where they where under user's fingers...
|
||||
// XXX this does not work...
|
||||
/*
|
||||
var d = (e.offset().left - t) * s
|
||||
$('.ribbon').each(function(_, r){
|
||||
$(r).velocity({
|
||||
transformX: '+='+d,
|
||||
}, 0)
|
||||
})
|
||||
*/
|
||||
|
||||
e = e[0]
|
||||
e.style.width = w + 'px'
|
||||
e.style.height = h + 'px'
|
||||
})
|
||||
|
||||
|
||||
|
||||
// Ribbon scroll...
|
||||
//
|
||||
// Problems:
|
||||
// - iScroll does not account for margins when aclculating
|
||||
// scroll width, need to patch this on .update(..)...
|
||||
// - can't scroll ribbons independently
|
||||
// - two fingers will trigger zoom
|
||||
// - for some reason if touching two ribbons while
|
||||
// zooming they will move in the same direction...
|
||||
// - scrolling a ribbon does not account for scale...
|
||||
// ...the same problem exists for native scroll...
|
||||
window.scroll_ribbon = []
|
||||
$('.ribbon-container')//.eq(0)
|
||||
.css({
|
||||
overflow: 'hidden',
|
||||
})
|
||||
.each(function(_, e){
|
||||
console.log(e)
|
||||
// XXX this calculates the scroll width incorrectly...
|
||||
scroll_ribbon.push(new IScroll(e, {
|
||||
scrollX: true,
|
||||
scrollY: false,
|
||||
|
||||
disableMouse: false,
|
||||
// XXX this only reads vertical mousewheel...
|
||||
// ...need this to work in a horizontal direction...
|
||||
//mouseWheel: true,
|
||||
}))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="viewer mark-center">
|
||||
<div class="scaler">
|
||||
<div class="ribbon-set">
|
||||
<div class="ribbon-container">
|
||||
<div class="ribbon">
|
||||
</div>
|
||||
</div>
|
||||
<div class="ribbon-container">
|
||||
<div class="ribbon">
|
||||
</div>
|
||||
</div>
|
||||
<div class="ribbon-container">
|
||||
<div class="ribbon">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
257
Viewer/experiments/preact-ribbons.html
Executable file
257
Viewer/experiments/preact-ribbons.html
Executable file
@ -0,0 +1,257 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<style>
|
||||
|
||||
.ribbon {
|
||||
position: relative;
|
||||
display: block;
|
||||
float: left;
|
||||
clear: left;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: visible;
|
||||
|
||||
margin: 5px 0px;
|
||||
|
||||
width: auto;
|
||||
}
|
||||
.base.ribbon {
|
||||
border-bottom: solid 5px red;
|
||||
}
|
||||
|
||||
.mark,
|
||||
.image {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
|
||||
outline: solid 1px blue;
|
||||
background: silver;
|
||||
|
||||
vertical-align: middle;
|
||||
}
|
||||
.image:after {
|
||||
content: attr(gid);
|
||||
}
|
||||
.current.image {
|
||||
background: gray;
|
||||
}
|
||||
|
||||
.mark {
|
||||
margin-left: -100px;
|
||||
background: none;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
.mark:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: solid 10px red;
|
||||
border-bottom-color: transparent;
|
||||
border-left-color: transparent;
|
||||
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<script src="../ext-lib/jquery.js"></script>
|
||||
<script src="../ext-lib/jquery-ui.js"></script>
|
||||
|
||||
<!-- preact.js -->
|
||||
<script src="../node_modules/preact/dist/preact.min.js"></script>
|
||||
|
||||
|
||||
<script src="../lib/jli.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
var h = preact.h
|
||||
|
||||
|
||||
var stub_data = {
|
||||
ribbon_order: ['ra', 'rb', 'rc'],
|
||||
ribbons: {
|
||||
ra: [].slice.call('abcde'),
|
||||
rb: [].slice.call('fghijklm'),
|
||||
rc: [].slice.call('opqrstuvwxyz'),
|
||||
},
|
||||
order: [].slice.call('abcdefghijklmopqrstuvwxyz'),
|
||||
|
||||
tags: {
|
||||
selected: [].slice.call('ahdtu'),
|
||||
b: [].slice.call('adxz'),
|
||||
},
|
||||
|
||||
current: 'a',
|
||||
base: 'rb',
|
||||
}
|
||||
|
||||
|
||||
// XXX needs vertical align...
|
||||
class IGRibbonSet extends preact.Component {
|
||||
render(props, state){
|
||||
var data = props.data
|
||||
var ribbons = data.ribbon_order.map(function(gid){
|
||||
return h(IGRibbon, {
|
||||
gid: gid,
|
||||
current: data.current,
|
||||
base: data.base,
|
||||
data: data
|
||||
}) })
|
||||
var s = props.scale || 1
|
||||
|
||||
return h('div',
|
||||
{
|
||||
className: 'ribbon-set',
|
||||
style: {
|
||||
transform: 'scale('+ s +', '+ s +')',
|
||||
},
|
||||
}, [
|
||||
h('div', {className: 'current-marker'}),
|
||||
h('div', {className: 'ribbon-locator'}, ribbons),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// render:
|
||||
// - ribbon
|
||||
// - images
|
||||
// - image marks
|
||||
//
|
||||
// XXX needs horizontal align...
|
||||
class IGRibbon extends preact.Component {
|
||||
render(props, state){
|
||||
var data = props.data
|
||||
var ribbon = props.gid
|
||||
|
||||
var images = data.ribbons[ribbon]
|
||||
.map(function(gid){
|
||||
var marks = data.tags.selected.indexOf(gid) >= 0 ?
|
||||
h(IGImageMark, {
|
||||
gid: gid,
|
||||
type: 'selected',
|
||||
data: data,
|
||||
})
|
||||
: []
|
||||
return [
|
||||
h(IGImage, {
|
||||
gid: gid,
|
||||
data: data,
|
||||
})].concat(marks)
|
||||
})
|
||||
.reduce(function(a, b){ return a.concat(b) })
|
||||
.filter(function(a){ return !!a })
|
||||
|
||||
var base = data.base == ribbon ? ['base'] : []
|
||||
|
||||
return h('div',
|
||||
{
|
||||
classList: ['ribbon'].concat(base).join(' '),
|
||||
|
||||
gid: props.gid,
|
||||
style: {
|
||||
// XXX offset...
|
||||
},
|
||||
}, images)
|
||||
}
|
||||
}
|
||||
|
||||
// render:
|
||||
// - image
|
||||
class IGImage extends preact.Component {
|
||||
render(props, state){
|
||||
var data = props.data
|
||||
var gid = props.gid
|
||||
|
||||
return h('div',
|
||||
{
|
||||
classList: ['image']
|
||||
.concat(data.current == gid ? ['current'] : [])
|
||||
.join(' '),
|
||||
gid: gid || '',
|
||||
style: {
|
||||
// XXX background-image...
|
||||
},
|
||||
|
||||
// XXX STUB
|
||||
onClick: function(evt){
|
||||
// toggle tag...
|
||||
if(data.current == gid){
|
||||
var selected = data.tags.selected = data.tags.selected || []
|
||||
selected.indexOf(gid) < 0 ?
|
||||
selected.push(gid)
|
||||
: selected.splice(selected.indexOf(gid), 1)
|
||||
}
|
||||
|
||||
// set current...
|
||||
data.current = gid
|
||||
|
||||
render()
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// render:
|
||||
// - image mark
|
||||
class IGImageMark extends preact.Component {
|
||||
render(props, state){
|
||||
var gid = props.gid
|
||||
var type = props.type
|
||||
var data = props.data
|
||||
|
||||
return h('div',
|
||||
{
|
||||
classList: ['mark'].concat([type]).join(' '),
|
||||
gid: gid,
|
||||
|
||||
// XXX STUB...
|
||||
// ...and there is no way to add the mark back...
|
||||
onClick: function(evt){
|
||||
data.tags[type].splice(data.tags[type].indexOf(gid), 1)
|
||||
|
||||
render()
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// XXX HACK...
|
||||
var ribbon_set_dom
|
||||
|
||||
function render(data, images, scale){
|
||||
//preact.render(ribbon_set, document.body)
|
||||
ribbon_set_dom = preact.render(
|
||||
h(IGRibbonSet, {
|
||||
data: data || stub_data,
|
||||
images: images || {},
|
||||
scale: scale || 1,
|
||||
}),
|
||||
document.getElementById('viewer'),
|
||||
ribbon_set_dom)
|
||||
}
|
||||
|
||||
|
||||
|
||||
$(function(){ render() })
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="viewer"/>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<!-- vim:set ts=4 sw=4 : -->
|
||||
58
Viewer/experiments/screenshot.js
Normal file
58
Viewer/experiments/screenshot.js
Normal file
@ -0,0 +1,58 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
const puppeteer = require('puppeteer')
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
;(async () => {
|
||||
var browser = await puppeteer.launch()
|
||||
var page = await browser.newPage()
|
||||
page.on('console', msg => console.log(' |', msg.text()))
|
||||
|
||||
await page
|
||||
.goto('file://'+ process.cwd().replace(/[\\\/]/g, '/') +'/../index.html')
|
||||
|
||||
|
||||
// Util functions...
|
||||
//
|
||||
// screenshot...
|
||||
page.exposeFunction('screenshot',
|
||||
async function(name){
|
||||
return page.screenshot({path: name || 'screenshot.png'}) })
|
||||
// exit...
|
||||
page.exposeFunction('exit',
|
||||
async function(name){
|
||||
return browser.close() })
|
||||
|
||||
|
||||
|
||||
page.evaluate(() =>
|
||||
$('.viewer')
|
||||
.on('ig.ready', async () => {
|
||||
// XXX make this scriptable...
|
||||
ig.browseActions()
|
||||
await screenshot('browseActions.png')
|
||||
ig.modal.client.close()
|
||||
|
||||
ig.browseActions('/File/')
|
||||
await screenshot('browseActions - File.png')
|
||||
ig.modal.client.close()
|
||||
|
||||
await exit()
|
||||
}))
|
||||
|
||||
//await page.screenshot({path: 'example.png'})
|
||||
//await browser.close()
|
||||
})()
|
||||
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */
|
||||
7
Viewer/ext-lib/hammer.min.js
vendored
Executable file
7
Viewer/ext-lib/hammer.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
9315
Viewer/ext-lib/jquery-ui.js
vendored
Executable file
9315
Viewer/ext-lib/jquery-ui.js
vendored
Executable file
File diff suppressed because it is too large
Load Diff
4
Viewer/ext-lib/jquery.js
vendored
Executable file
4
Viewer/ext-lib/jquery.js
vendored
Executable file
File diff suppressed because one or more lines are too long
11
Viewer/ext-lib/jquery.ui.touch-punch.min.js
vendored
Executable file
11
Viewer/ext-lib/jquery.ui.touch-punch.min.js
vendored
Executable file
@ -0,0 +1,11 @@
|
||||
/*!
|
||||
* jQuery UI Touch Punch 0.2.3
|
||||
*
|
||||
* Copyright 2011–2014, Dave Furfero
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
*
|
||||
* Depends:
|
||||
* jquery.ui.widget.js
|
||||
* jquery.ui.mouse.js
|
||||
*/
|
||||
!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);
|
||||
11
Viewer/ext-lib/pouchdb.min.js
vendored
Executable file
11
Viewer/ext-lib/pouchdb.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
@ -1,158 +1,158 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
/* SHA-1 implementation in JavaScript (c) Chris Veness 2002-2014 / MIT Licence */
|
||||
/* */
|
||||
/* - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html */
|
||||
/* http://csrc.nist.gov/groups/ST/toolkit/examples.html */
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/* jshint node:true *//* global define, escape, unescape */
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* SHA-1 hash function reference implementation.
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
var Sha1 = {};
|
||||
|
||||
|
||||
/**
|
||||
* Generates SHA-1 hash of string.
|
||||
*
|
||||
* @param {string} msg - (Unicode) string to be hashed.
|
||||
* @returns {string} Hash of msg as hex character string.
|
||||
*/
|
||||
Sha1.hash = function(msg) {
|
||||
// convert string to UTF-8, as SHA only deals with byte-streams
|
||||
msg = msg.utf8Encode();
|
||||
|
||||
// constants [§4.2.1]
|
||||
var K = [ 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 ];
|
||||
|
||||
// PREPROCESSING
|
||||
|
||||
msg += String.fromCharCode(0x80); // add trailing '1' bit (+ 0's padding) to string [§5.1.1]
|
||||
|
||||
// convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
|
||||
var l = msg.length/4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length
|
||||
var N = Math.ceil(l/16); // number of 16-integer-blocks required to hold 'l' ints
|
||||
var M = new Array(N);
|
||||
|
||||
for (var i=0; i<N; i++) {
|
||||
M[i] = new Array(16);
|
||||
for (var j=0; j<16; j++) { // encode 4 chars per integer, big-endian encoding
|
||||
M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) |
|
||||
(msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
|
||||
} // note running off the end of msg is ok 'cos bitwise ops on NaN return 0
|
||||
}
|
||||
// add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1]
|
||||
// note: most significant word would be (len-1)*8 >>> 32, but since JS converts
|
||||
// bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
|
||||
M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14]);
|
||||
M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;
|
||||
|
||||
// set initial hash value [§5.3.1]
|
||||
var H0 = 0x67452301;
|
||||
var H1 = 0xefcdab89;
|
||||
var H2 = 0x98badcfe;
|
||||
var H3 = 0x10325476;
|
||||
var H4 = 0xc3d2e1f0;
|
||||
|
||||
// HASH COMPUTATION [§6.1.2]
|
||||
|
||||
var W = new Array(80); var a, b, c, d, e;
|
||||
for (var i=0; i<N; i++) {
|
||||
|
||||
// 1 - prepare message schedule 'W'
|
||||
for (var t=0; t<16; t++) W[t] = M[i][t];
|
||||
for (var t=16; t<80; t++) W[t] = Sha1.ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1);
|
||||
|
||||
// 2 - initialise five working variables a, b, c, d, e with previous hash value
|
||||
a = H0; b = H1; c = H2; d = H3; e = H4;
|
||||
|
||||
// 3 - main loop
|
||||
for (var t=0; t<80; t++) {
|
||||
var s = Math.floor(t/20); // seq for blocks of 'f' functions and 'K' constants
|
||||
var T = (Sha1.ROTL(a,5) + Sha1.f(s,b,c,d) + e + K[s] + W[t]) & 0xffffffff;
|
||||
e = d;
|
||||
d = c;
|
||||
c = Sha1.ROTL(b, 30);
|
||||
b = a;
|
||||
a = T;
|
||||
}
|
||||
|
||||
// 4 - compute the new intermediate hash value (note 'addition modulo 2^32')
|
||||
H0 = (H0+a) & 0xffffffff;
|
||||
H1 = (H1+b) & 0xffffffff;
|
||||
H2 = (H2+c) & 0xffffffff;
|
||||
H3 = (H3+d) & 0xffffffff;
|
||||
H4 = (H4+e) & 0xffffffff;
|
||||
}
|
||||
|
||||
return Sha1.toHexStr(H0) + Sha1.toHexStr(H1) + Sha1.toHexStr(H2) +
|
||||
Sha1.toHexStr(H3) + Sha1.toHexStr(H4);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Function 'f' [§4.1.1].
|
||||
* @private
|
||||
*/
|
||||
Sha1.f = function(s, x, y, z) {
|
||||
switch (s) {
|
||||
case 0: return (x & y) ^ (~x & z); // Ch()
|
||||
case 1: return x ^ y ^ z; // Parity()
|
||||
case 2: return (x & y) ^ (x & z) ^ (y & z); // Maj()
|
||||
case 3: return x ^ y ^ z; // Parity()
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Rotates left (circular left shift) value x by n positions [§3.2.5].
|
||||
* @private
|
||||
*/
|
||||
Sha1.ROTL = function(x, n) {
|
||||
return (x<<n) | (x>>>(32-n));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hexadecimal representation of a number.
|
||||
* @private
|
||||
*/
|
||||
Sha1.toHexStr = function(n) {
|
||||
// note can't use toString(16) as it is implementation-dependant,
|
||||
// and in IE returns signed numbers when used on full words
|
||||
var s="", v;
|
||||
for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); }
|
||||
return s;
|
||||
};
|
||||
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
|
||||
/** Extend String object with method to encode multi-byte string to utf8
|
||||
* - monsur.hossa.in/2012/07/20/utf-8-in-javascript.html */
|
||||
if (typeof String.prototype.utf8Encode == 'undefined') {
|
||||
String.prototype.utf8Encode = function() {
|
||||
return unescape( encodeURIComponent( this ) );
|
||||
};
|
||||
}
|
||||
|
||||
/** Extend String object with method to decode utf8 string to multi-byte */
|
||||
if (typeof String.prototype.utf8Decode == 'undefined') {
|
||||
String.prototype.utf8Decode = function() {
|
||||
try {
|
||||
return decodeURIComponent( escape( this ) );
|
||||
} catch (e) {
|
||||
return this; // invalid UTF-8? return as-is
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
if (typeof module != 'undefined' && module.exports) module.exports = Sha1; // CommonJs export
|
||||
if (typeof define == 'function' && define.amd) define([], function() { return Sha1; }); // AMD
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
/* SHA-1 implementation in JavaScript (c) Chris Veness 2002-2014 / MIT Licence */
|
||||
/* */
|
||||
/* - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html */
|
||||
/* http://csrc.nist.gov/groups/ST/toolkit/examples.html */
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/* jshint node:true *//* global define, escape, unescape */
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* SHA-1 hash function reference implementation.
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
var Sha1 = {};
|
||||
|
||||
|
||||
/**
|
||||
* Generates SHA-1 hash of string.
|
||||
*
|
||||
* @param {string} msg - (Unicode) string to be hashed.
|
||||
* @returns {string} Hash of msg as hex character string.
|
||||
*/
|
||||
Sha1.hash = function(msg) {
|
||||
// convert string to UTF-8, as SHA only deals with byte-streams
|
||||
msg = msg.utf8Encode();
|
||||
|
||||
// constants [§4.2.1]
|
||||
var K = [ 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 ];
|
||||
|
||||
// PREPROCESSING
|
||||
|
||||
msg += String.fromCharCode(0x80); // add trailing '1' bit (+ 0's padding) to string [§5.1.1]
|
||||
|
||||
// convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
|
||||
var l = msg.length/4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length
|
||||
var N = Math.ceil(l/16); // number of 16-integer-blocks required to hold 'l' ints
|
||||
var M = new Array(N);
|
||||
|
||||
for (var i=0; i<N; i++) {
|
||||
M[i] = new Array(16);
|
||||
for (var j=0; j<16; j++) { // encode 4 chars per integer, big-endian encoding
|
||||
M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) |
|
||||
(msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
|
||||
} // note running off the end of msg is ok 'cos bitwise ops on NaN return 0
|
||||
}
|
||||
// add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1]
|
||||
// note: most significant word would be (len-1)*8 >>> 32, but since JS converts
|
||||
// bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
|
||||
M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14]);
|
||||
M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;
|
||||
|
||||
// set initial hash value [§5.3.1]
|
||||
var H0 = 0x67452301;
|
||||
var H1 = 0xefcdab89;
|
||||
var H2 = 0x98badcfe;
|
||||
var H3 = 0x10325476;
|
||||
var H4 = 0xc3d2e1f0;
|
||||
|
||||
// HASH COMPUTATION [§6.1.2]
|
||||
|
||||
var W = new Array(80); var a, b, c, d, e;
|
||||
for (var i=0; i<N; i++) {
|
||||
|
||||
// 1 - prepare message schedule 'W'
|
||||
for (var t=0; t<16; t++) W[t] = M[i][t];
|
||||
for (var t=16; t<80; t++) W[t] = Sha1.ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1);
|
||||
|
||||
// 2 - initialise five working variables a, b, c, d, e with previous hash value
|
||||
a = H0; b = H1; c = H2; d = H3; e = H4;
|
||||
|
||||
// 3 - main loop
|
||||
for (var t=0; t<80; t++) {
|
||||
var s = Math.floor(t/20); // seq for blocks of 'f' functions and 'K' constants
|
||||
var T = (Sha1.ROTL(a,5) + Sha1.f(s,b,c,d) + e + K[s] + W[t]) & 0xffffffff;
|
||||
e = d;
|
||||
d = c;
|
||||
c = Sha1.ROTL(b, 30);
|
||||
b = a;
|
||||
a = T;
|
||||
}
|
||||
|
||||
// 4 - compute the new intermediate hash value (note 'addition modulo 2^32')
|
||||
H0 = (H0+a) & 0xffffffff;
|
||||
H1 = (H1+b) & 0xffffffff;
|
||||
H2 = (H2+c) & 0xffffffff;
|
||||
H3 = (H3+d) & 0xffffffff;
|
||||
H4 = (H4+e) & 0xffffffff;
|
||||
}
|
||||
|
||||
return Sha1.toHexStr(H0) + Sha1.toHexStr(H1) + Sha1.toHexStr(H2) +
|
||||
Sha1.toHexStr(H3) + Sha1.toHexStr(H4);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Function 'f' [§4.1.1].
|
||||
* @private
|
||||
*/
|
||||
Sha1.f = function(s, x, y, z) {
|
||||
switch (s) {
|
||||
case 0: return (x & y) ^ (~x & z); // Ch()
|
||||
case 1: return x ^ y ^ z; // Parity()
|
||||
case 2: return (x & y) ^ (x & z) ^ (y & z); // Maj()
|
||||
case 3: return x ^ y ^ z; // Parity()
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Rotates left (circular left shift) value x by n positions [§3.2.5].
|
||||
* @private
|
||||
*/
|
||||
Sha1.ROTL = function(x, n) {
|
||||
return (x<<n) | (x>>>(32-n));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hexadecimal representation of a number.
|
||||
* @private
|
||||
*/
|
||||
Sha1.toHexStr = function(n) {
|
||||
// note can't use toString(16) as it is implementation-dependant,
|
||||
// and in IE returns signed numbers when used on full words
|
||||
var s="", v;
|
||||
for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); }
|
||||
return s;
|
||||
};
|
||||
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
|
||||
/** Extend String object with method to encode multi-byte string to utf8
|
||||
* - monsur.hossa.in/2012/07/20/utf-8-in-javascript.html */
|
||||
if (typeof String.prototype.utf8Encode == 'undefined') {
|
||||
String.prototype.utf8Encode = function() {
|
||||
return unescape( encodeURIComponent( this ) );
|
||||
};
|
||||
}
|
||||
|
||||
/** Extend String object with method to decode utf8 string to multi-byte */
|
||||
if (typeof String.prototype.utf8Decode == 'undefined') {
|
||||
String.prototype.utf8Decode = function() {
|
||||
try {
|
||||
return decodeURIComponent( escape( this ) );
|
||||
} catch (e) {
|
||||
return this; // invalid UTF-8? return as-is
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
if (typeof module != 'undefined' && module.exports) module.exports = Sha1; // CommonJs export
|
||||
if (typeof define == 'function' && define.amd) define([], function() { return Sha1; }); // AMD
|
||||
@ -1,48 +1,46 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
define(function(require){ var module = {}
|
||||
|
||||
//var DEBUG = DEBUG != null ? DEBUG : true
|
||||
|
||||
var actions = require('lib/actions')
|
||||
var features = require('lib/features')
|
||||
|
||||
var core = require('features/core')
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
var FeatureActions = actions.Actions({
|
||||
emptyAction: ['- Demo/Empty action',
|
||||
function(){
|
||||
// XXX
|
||||
}],
|
||||
})
|
||||
|
||||
var Feature =
|
||||
module.Feature = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
// XXX
|
||||
tag: 'feature-tag',
|
||||
depends: [
|
||||
// XXX
|
||||
],
|
||||
|
||||
actions: FeatureActions,
|
||||
|
||||
handlers: [],
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */
|
||||
return module })
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
||||
(function(require){ var module={} // make module AMD/node compatible...
|
||||
/*********************************************************************/
|
||||
|
||||
var actions = require('lib/actions')
|
||||
var features = require('lib/features')
|
||||
|
||||
var core = require('features/core')
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
var FeatureActions = actions.Actions({
|
||||
emptyAction: ['- Demo/Empty action',
|
||||
function(){
|
||||
// XXX
|
||||
}],
|
||||
})
|
||||
|
||||
var Feature =
|
||||
module.Feature = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
// XXX
|
||||
tag: 'feature-tag',
|
||||
depends: [
|
||||
// XXX
|
||||
],
|
||||
|
||||
actions: FeatureActions,
|
||||
|
||||
handlers: [],
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */ return module })
|
||||
307
Viewer/features/alias.js
Executable file
307
Viewer/features/alias.js
Executable file
@ -0,0 +1,307 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
||||
(function(require){ var module={} // make module AMD/node compatible...
|
||||
/*********************************************************************/
|
||||
|
||||
var actions = require('lib/actions')
|
||||
var features = require('lib/features')
|
||||
|
||||
var browse = require('lib/widget/browse')
|
||||
|
||||
var core = require('features/core')
|
||||
var widgets = require('features/ui-widgets')
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
var Alias =
|
||||
module.Alias = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
tag: 'alias',
|
||||
suggested: [
|
||||
'ui-alias',
|
||||
],
|
||||
|
||||
config: {
|
||||
//aliases: {
|
||||
//},
|
||||
},
|
||||
|
||||
handlers: [
|
||||
// load aliases...
|
||||
['start',
|
||||
function(){
|
||||
var that = this
|
||||
var aliases = this.config.aliases || {}
|
||||
|
||||
Object.keys(aliases)
|
||||
.forEach(function(alias){
|
||||
that.alias.apply(that, [alias].concat(aliases[alias])) })
|
||||
}],
|
||||
// store aliases in .config.aliases
|
||||
//
|
||||
// NOTE: this does not guard from overriding anything...
|
||||
// NOTE: there should not be any actions in the base action-set
|
||||
// other than the ones created by .alias(..).
|
||||
['alias',
|
||||
function(_, alias, target){
|
||||
var args = [...arguments].slice(1)
|
||||
var alias = args.shift()
|
||||
var target = args[args.length-1]
|
||||
|
||||
// remove alias...
|
||||
// XXX is this test enough??? ...see ActionSet.alias(..)
|
||||
if(arguments.length == 3
|
||||
&& (target === null || target === false)){
|
||||
var aliases = this.config.aliases || {}
|
||||
|
||||
delete aliases[alias]
|
||||
|
||||
if(Object.keys(aliases).length == 0){
|
||||
delete this.config.aliases
|
||||
}
|
||||
|
||||
// save alias...
|
||||
} else {
|
||||
var aliases = this.config.aliases = this.config.aliases || {}
|
||||
|
||||
aliases[alias] = args
|
||||
}
|
||||
}],
|
||||
|
||||
/*/ XXX not sure if this is the correct way to go...
|
||||
['selfTest',
|
||||
function(){
|
||||
var alias = [
|
||||
'testRuntimeAlias',
|
||||
'Test/',
|
||||
core.doc`Rumtime-defined test alias.
|
||||
|
||||
NOTE: this will get overwritten on start.`,
|
||||
'focusImage: "next"',
|
||||
]
|
||||
|
||||
this.alias.apply(this, alias)
|
||||
|
||||
if(!this.config.aliases
|
||||
|| !(alias[0] in this.config.aliases)
|
||||
|| this.config.aliases[alias[0]].length != alias.length-1
|
||||
|| this.config.aliases[alias[0]].filter(function(e, i){ return e != alias[i+1] }).length > 0){
|
||||
console.error('Alias save fail:',
|
||||
'\n written:', alias,
|
||||
'\n saved:', [alias[0]].concat((this.config.aliases || {})[alias[0]]))
|
||||
}
|
||||
}],
|
||||
//*/
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
var UIAliasActions = actions.Actions({
|
||||
browseAliases: ['System/Aliases...',
|
||||
core.doc`Action alias list
|
||||
|
||||
NOTE: this may affect the action menu, to see changes update the menu.`,
|
||||
widgets.makeUIDialog(function(){
|
||||
var that = this
|
||||
|
||||
// get keys for each action...
|
||||
var keys = that.getKeysForAction ? that.getKeysForAction() : {}
|
||||
// Get keys for action...
|
||||
var getKeys = function(action){
|
||||
return (keys[action] || []).join(' / ') }
|
||||
|
||||
var to_remove = []
|
||||
|
||||
return browse.makeLister(null,
|
||||
function(path, make){
|
||||
var dialog = this
|
||||
var aliases = that.config.aliases || {}
|
||||
|
||||
var names = Object.keys(aliases)
|
||||
|
||||
names.length > 0 ?
|
||||
make.EditableList(names,
|
||||
{
|
||||
new_item: false,
|
||||
to_remove: to_remove,
|
||||
editable_items: false,
|
||||
|
||||
update_merge: 'drop_changes',
|
||||
|
||||
itemopen: function(_, name){
|
||||
that.editAlias(name)
|
||||
.on('close', function(){ dialog.update() })
|
||||
},
|
||||
|
||||
each: function(name, elem){
|
||||
$(elem)
|
||||
.attr({
|
||||
keys: getKeys(name),
|
||||
action: name,
|
||||
})
|
||||
},
|
||||
})
|
||||
: make.Empty()
|
||||
|
||||
make('---')
|
||||
|
||||
make('$New...', { events: {
|
||||
open: function(){
|
||||
that.editAlias()
|
||||
.on('close', function(){ dialog.update() })
|
||||
},
|
||||
} })
|
||||
}, {
|
||||
cls: 'table-view show-keys',
|
||||
})
|
||||
.run(function(){
|
||||
// XXX this is a copy from .browseActions(..)
|
||||
this.showDoc = function(){
|
||||
var action = this.select('!').attr('action')
|
||||
action
|
||||
&& that.showDoc(action)
|
||||
}
|
||||
this.keyboard.handler('General', '?', 'showDoc')
|
||||
})
|
||||
.close(function(){
|
||||
to_remove.forEach(function(alias){
|
||||
that.alias(alias, null) }) })
|
||||
})],
|
||||
|
||||
// NOTE: this does not include an attr editor by design...
|
||||
//
|
||||
// XXX should we set white-space: pre on doc here or in css???
|
||||
// XXX edit key bindings (???)
|
||||
editAlias: ['- System/Edit alias...',
|
||||
widgets.makeUIDialog(function(alias){
|
||||
var that = this
|
||||
|
||||
var name = alias
|
||||
var data = ((that.config.aliases || {})[alias] || ['']).slice()
|
||||
|
||||
return browse.makeLister(null,
|
||||
function(path, make){
|
||||
var item_opts = {
|
||||
start_on: 'open',
|
||||
edit_text: 'last',
|
||||
clear_on_edit: false,
|
||||
reset_on_commit: false,
|
||||
abort_on_deselect: false,
|
||||
}
|
||||
|
||||
// doc fields...
|
||||
make.Editable(['$Path:', that.getActionAttr(alias, 'doc')], item_opts)
|
||||
.on('edit-commit', function(evt, text){
|
||||
if(data.length > 1 && typeof(data[0]) == typeof('str')){
|
||||
data[0] = text
|
||||
|
||||
// no previous docs...
|
||||
} else {
|
||||
data.splice(0, 0, text)
|
||||
}
|
||||
})
|
||||
var doc_opts = {
|
||||
// XXX this does not work???
|
||||
multiline: true,
|
||||
}
|
||||
doc_opts.__proto__ = item_opts
|
||||
make.Editable(['$Doc:', that.getActionAttr(alias, 'long_doc')], doc_opts)
|
||||
.on('edit-commit', function(evt, text){
|
||||
// existing .doc and .long_doc -> replace .long_doc...
|
||||
if(data.length > 2
|
||||
&& typeof(data[0]) == typeof('str')
|
||||
&& typeof(data[1] == typeof('str'))){
|
||||
data[1] = text
|
||||
|
||||
// existing .doc -> add .long_doc only...
|
||||
} else if(data.length > 1 && typeof(data[0]) == typeof('str')){
|
||||
data.splice(1, 0, text)
|
||||
|
||||
// no previous docs -> add empty .doc and set .long_doc...
|
||||
} else {
|
||||
data.splice(0, 0, '', text)
|
||||
}
|
||||
})
|
||||
// XXX HACK???
|
||||
.find('.text').last()
|
||||
.css({'white-space': 'pre'})
|
||||
|
||||
make('---')
|
||||
|
||||
// alias fields...
|
||||
make.Editable(['$Alias:', alias || ''], item_opts)
|
||||
.on('edit-commit', function(evt, text){
|
||||
name = text
|
||||
})
|
||||
make.Editable(['$Code:', ((that.config.aliases || {})[alias] || ['']).slice(-1)[0]], item_opts)
|
||||
.on('edit-commit', function(evt, text){
|
||||
data[data.length-1] = text
|
||||
})
|
||||
|
||||
make('---')
|
||||
|
||||
// delete / cancel...
|
||||
make.ConfirmAction('Delete', {
|
||||
callback: function(){
|
||||
data = [null]
|
||||
make.dialog.close()
|
||||
},
|
||||
buttons: [
|
||||
['Cancel edit', function(){
|
||||
make.dialog.close('cancel')
|
||||
}],
|
||||
],
|
||||
})
|
||||
}, {
|
||||
cls: 'table-view',
|
||||
})
|
||||
.on('close', function(_, mode){
|
||||
// do not save on cancel...
|
||||
if(mode == 'cancel'
|
||||
|| ((name == '' || name == null) && !that[name])){
|
||||
return
|
||||
}
|
||||
|
||||
// renaming the alias -> clear the old value...
|
||||
if(name != alias){
|
||||
that.alias(alias, null)
|
||||
}
|
||||
|
||||
// save the alias...
|
||||
that.alias.apply(that, [name].concat(data))
|
||||
})
|
||||
})],
|
||||
})
|
||||
|
||||
var UIAlias =
|
||||
module.UIAlias = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
tag: 'ui-alias',
|
||||
depends: [
|
||||
'alias',
|
||||
'ui',
|
||||
],
|
||||
|
||||
actions: UIAliasActions,
|
||||
|
||||
handlers: [],
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */ return module })
|
||||
@ -1,53 +1,72 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
define(function(require){ var module = {}
|
||||
|
||||
//var DEBUG = DEBUG != null ? DEBUG : true
|
||||
|
||||
// import features...
|
||||
var core = require('features/core')
|
||||
|
||||
require('features/base')
|
||||
require('features/sort')
|
||||
require('features/location')
|
||||
require('features/history')
|
||||
require('features/app')
|
||||
require('features/ui')
|
||||
require('features/ui-partial-ribbons')
|
||||
require('features/ui-single-image')
|
||||
require('features/ui-chrome')
|
||||
require('features/keyboard')
|
||||
require('features/ui-status')
|
||||
require('features/ui-marks')
|
||||
require('features/ui-widgets')
|
||||
require('features/ui-slideshow')
|
||||
require('features/external-editor')
|
||||
require('features/metadata')
|
||||
require('features/meta')
|
||||
|
||||
require('features/experimental')
|
||||
require('features/demo')
|
||||
|
||||
// node features...
|
||||
if(typeof(window) == 'undefined' || window.nodejs != null){
|
||||
require('features/filesystem')
|
||||
require('features/cli')
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
// NOTE: this is here to simplify importing...
|
||||
var ImageGridFeatures =
|
||||
module.ImageGridFeatures =
|
||||
core.ImageGridFeatures
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */
|
||||
return module })
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
||||
(function(require){ var module={} // make module AMD/node compatible...
|
||||
/*********************************************************************/
|
||||
|
||||
// import features...
|
||||
var core = require('features/core')
|
||||
|
||||
require('features/base')
|
||||
require('features/store')
|
||||
require('features/collections')
|
||||
require('features/sort')
|
||||
require('features/tags')
|
||||
require('features/marks')
|
||||
require('features/location')
|
||||
require('features/recover')
|
||||
require('features/config')
|
||||
require('features/history')
|
||||
require('features/app')
|
||||
require('features/peer')
|
||||
require('features/alias')
|
||||
require('features/comments')
|
||||
require('features/ui')
|
||||
// XXX
|
||||
require('features/ui-blank-render')
|
||||
require('features/ui-ribbons')
|
||||
// XXX
|
||||
require('features/ui-partial-ribbons-precache')
|
||||
require('features/ui-partial-ribbons-2')
|
||||
require('features/ui-single-image')
|
||||
require('features/ui-chrome')
|
||||
require('features/ui-progress')
|
||||
require('features/keyboard')
|
||||
require('features/ui-status')
|
||||
require('features/ui-ranges')
|
||||
require('features/ui-widgets')
|
||||
require('features/ui-slideshow')
|
||||
require('features/ui-drag-n-drop')
|
||||
require('features/external-editor')
|
||||
require('features/metadata')
|
||||
require('features/meta')
|
||||
|
||||
// XXX EXPERIMENTAL...
|
||||
require('features/virtual-blocks')
|
||||
|
||||
require('features/experimental')
|
||||
require('features/tests')
|
||||
require('features/demo')
|
||||
require('features/examples')
|
||||
|
||||
// node features...
|
||||
if(typeof(window) == 'undefined' || window.nodejs != null){
|
||||
require('features/filesystem')
|
||||
require('features/sharp')
|
||||
require('features/cli') }
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
// NOTE: this is here to simplify importing...
|
||||
var ImageGridFeatures =
|
||||
module.ImageGridFeatures =
|
||||
core.ImageGridFeatures
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */ return module })
|
||||
757
Viewer/features/app.js
Executable file
757
Viewer/features/app.js
Executable file
@ -0,0 +1,757 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
||||
(function(require){ var module={} // make module AMD/node compatible...
|
||||
/*********************************************************************/
|
||||
|
||||
if(typeof(process) != 'undefined'){
|
||||
var pathlib = requirejs('path')
|
||||
var url = requirejs('url')
|
||||
}
|
||||
|
||||
var electron
|
||||
try{
|
||||
electron = requirejs('electron')
|
||||
} catch(e){ }
|
||||
|
||||
var VERSION = require('version').version
|
||||
|
||||
var actions = require('lib/actions')
|
||||
var features = require('lib/features')
|
||||
var toggler = require('lib/toggler')
|
||||
|
||||
var core = require('features/core')
|
||||
var base = require('features/base')
|
||||
var widgets = require('features/ui-widgets')
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
// helpers...
|
||||
|
||||
// XXX this includes a WebKit bug patch -- see inside...
|
||||
var img2canvas =
|
||||
function({url, orientation, flipped}, callback){
|
||||
|
||||
// XXX PATCH...
|
||||
// XXX <canvas>.drawImage(..) seems to take EXIF into account, ignoring
|
||||
// the .imageOrientation setting both the canvas and image are in
|
||||
// DOM and the image needs to be added to dom before .src is set
|
||||
var PATCHED_ELEMENTS
|
||||
var PATCH = function(e){
|
||||
PATCHED_ELEMENTS =
|
||||
PATCHED_ELEMENTS
|
||||
|| document.body.appendChild(
|
||||
document.createElement('div')
|
||||
.run(function(){
|
||||
this.style.position = 'absolute'
|
||||
this.style.with = '0'
|
||||
this.style.height = '0'
|
||||
this.style.top = '200%'
|
||||
this.style.left = '0'
|
||||
this.style.opacity = 0
|
||||
this.style.overflow = 'hidden' }))
|
||||
PATCHED_ELEMENTS.appendChild(e) }
|
||||
// XXX PATCH...
|
||||
var CLEANUP_PATCH = function(){
|
||||
PATCHED_ELEMENTS
|
||||
.parentElement
|
||||
.removeChild(PATCHED_ELEMENTS) }
|
||||
|
||||
var img = new Image
|
||||
img.onload = function(){
|
||||
// XXX .naturalWidth/.naturalHeight seem to ignore .imageOrientation
|
||||
// setting and orient the image via exif while .width/.height
|
||||
// seem to respect it but only when atached to DOM...
|
||||
// XXX for some reason noticed this on Linux, need to test under
|
||||
// Windows if this is a platform-specific thing...
|
||||
//var width = this.naturalWidth
|
||||
//var height = this.naturalHeight
|
||||
var width = this.width
|
||||
var height = this.height
|
||||
|
||||
var c = document.createElement('canvas')
|
||||
c.style.imageOrientation = 'none'
|
||||
|
||||
// XXX PATCH...
|
||||
PATCH(c)
|
||||
|
||||
var ctx = c.getContext('2d')
|
||||
// prepare for rotate...
|
||||
// 90 / 270
|
||||
if(orientation == 90 || orientation == 270){
|
||||
var w = c.width = height
|
||||
var h = c.height = width
|
||||
// 0 / 180
|
||||
} else {
|
||||
var w = c.width = width
|
||||
var h = c.height = height }
|
||||
// prepare for flip...
|
||||
var x = flipped && flipped.includes('horizontal') ?
|
||||
-1
|
||||
: 1
|
||||
var y = flipped && flipped.includes('vertical') ?
|
||||
-1
|
||||
: 1
|
||||
|
||||
ctx.translate(w/2, h/2)
|
||||
ctx.rotate(orientation * Math.PI/180)
|
||||
ctx.scale(x, y)
|
||||
ctx.drawImage(this, -width/2, -height/2)
|
||||
|
||||
// XXX PATCH...
|
||||
CLEANUP_PATCH()
|
||||
|
||||
callback.call(this, c) }
|
||||
|
||||
// prevent the browser from rotating the image via exif...
|
||||
img.style.imageOrientation = 'none'
|
||||
img.crossOrigin = ''
|
||||
|
||||
// XXX PATCH...
|
||||
PATCH(img)
|
||||
|
||||
img.src = url
|
||||
return img }
|
||||
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
var Widget =
|
||||
module.Widget = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
tag: 'widget',
|
||||
depends: [
|
||||
'-ui-app-control',
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
var NWHostActions = actions.Actions({
|
||||
// window stuff...
|
||||
// XXX should this be nested in a .window object???
|
||||
get title(){
|
||||
return nw.Window.get().title },
|
||||
set title(value){
|
||||
nw.Window.get().title = value },
|
||||
get size(){
|
||||
var win = nw.Window.get()
|
||||
return [
|
||||
win.width,
|
||||
win.height,
|
||||
]
|
||||
},
|
||||
set size(value){
|
||||
var win = nw.Window.get()
|
||||
win.width = value[0]
|
||||
win.height = value[1]
|
||||
},
|
||||
get position(){
|
||||
var win = nw.Window.get()
|
||||
return [
|
||||
win.x,
|
||||
win.y,
|
||||
]
|
||||
},
|
||||
set position(value){
|
||||
var win = nw.Window.get()
|
||||
win.x = value[0]
|
||||
win.y = value[1]
|
||||
},
|
||||
|
||||
show: ['- Window/',
|
||||
function(){
|
||||
nw.Window.get().show() }],
|
||||
minimize: ['Window/Minimize',
|
||||
function(){
|
||||
nw.Window.get().minimize() }],
|
||||
toggleFullScreen: ['Window/Full screen mode',
|
||||
toggler.CSSClassToggler(
|
||||
function(){ return document.body },
|
||||
'.full-screen-mode',
|
||||
function(action){
|
||||
var that = this
|
||||
var w = nw.Window.get()
|
||||
|
||||
// change the state only if the target state is not the same
|
||||
// as the current state...
|
||||
if((w.isFullscreen ? 'on' : 'off') != action){
|
||||
this.ribbons.preventTransitions()
|
||||
|
||||
// hide the viewer to hide any animation crimes...
|
||||
this.dom[0].style.visibility = 'hidden'
|
||||
|
||||
// XXX async...
|
||||
// ...this complicates things as we need to do the next
|
||||
// bit AFTER the resize is done...
|
||||
w.toggleFullscreen()
|
||||
|
||||
setTimeout(function(){
|
||||
that
|
||||
.centerViewer()
|
||||
.focusImage()
|
||||
.ribbons
|
||||
.restoreTransitions()
|
||||
|
||||
that.dom[0].style.visibility = ''
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// NOTE: we delay this to account for window animation...
|
||||
setTimeout(function(){
|
||||
that.storeWindowGeometry()
|
||||
}, 500)
|
||||
})],
|
||||
|
||||
// XXX add ability to use devtools on background page (node context)...
|
||||
// XXX get the devtools stage...
|
||||
showDevTools: ['Interface|Development/Show Dev Tools',
|
||||
{mode: 'advancedBrowseModeAction'},
|
||||
function(action){
|
||||
if(action == '?'){
|
||||
// XXX get the devtools stage...
|
||||
return false }
|
||||
nw.Window.get().showDevTools &&
|
||||
nw.Window.get().showDevTools() }],
|
||||
showInFolder: ['File|Image/Show in $folder',
|
||||
function(image){
|
||||
image = this.images[this.data.getImage(image)]
|
||||
|
||||
var base = image.base_path || this.location.path
|
||||
var filename = image.path
|
||||
var path = pathlib.normalize(base + '/' + filename)
|
||||
|
||||
nw.Shell.showItemInFolder(path) }],
|
||||
|
||||
toggleSplashScreen: ['Interface/',
|
||||
{mode: 'advancedBrowseModeAction'},
|
||||
function(){
|
||||
}],
|
||||
})
|
||||
|
||||
var NWHost =
|
||||
module.NWHost = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
tag: 'ui-nw-host',
|
||||
exclusive: 'ui-host',
|
||||
depends: [],
|
||||
|
||||
actions: NWHostActions,
|
||||
|
||||
isApplicable: function(){ return this.runtime.nw },
|
||||
})
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
var ElectronHostActions = actions.Actions({
|
||||
// window stuff...
|
||||
// XXX should this be nested in a .window object???
|
||||
// XXX should these be props or methods???
|
||||
get title(){
|
||||
return document.title },
|
||||
set title(value){
|
||||
document.title = value },
|
||||
get size(){
|
||||
return [window.outerWidth, window.outerHeight] },
|
||||
set size(value){
|
||||
value
|
||||
&& window.resizeTo(...value) },
|
||||
get position(){
|
||||
return [window.screenX, window.screenY] },
|
||||
set position(value){
|
||||
value
|
||||
&& window.moveTo(...value) },
|
||||
|
||||
// XXX need to handle maximize correctly -- see e.js...
|
||||
// XXX do we need .hide(..) here???
|
||||
show: ['- Window/',
|
||||
function(){
|
||||
electron.ipcRenderer.send('show') }],
|
||||
minimize: ['Window/Minimize',
|
||||
function(){
|
||||
electron.ipcRenderer.send('minimize') }],
|
||||
toggleFullScreen: ['Window/Full screen mode',
|
||||
toggler.CSSClassToggler(
|
||||
function(){ return document.body },
|
||||
'.full-screen-mode',
|
||||
function(action){
|
||||
var that = this
|
||||
|
||||
// get current state...
|
||||
var state = document.appFullScreen ? 'on' : 'off'
|
||||
|
||||
// change the state only if the target state is not the same
|
||||
// as the current state...
|
||||
if(state != action){
|
||||
this.ribbons.preventTransitions()
|
||||
|
||||
// hide the viewer to hide any animation crimes...
|
||||
this.dom[0].style.visibility = 'hidden'
|
||||
|
||||
// NOTE: electrons policy that developers can't trust
|
||||
// their own code making them jump through context
|
||||
// hoops all of the time instead of in the specific
|
||||
// contexts that need isolation is crap...
|
||||
electron.ipcRenderer.send(
|
||||
state == 'on' ?
|
||||
'exitFullScreen'
|
||||
: 'enterFullScreen')
|
||||
|
||||
setTimeout(function(){
|
||||
that
|
||||
.centerViewer()
|
||||
.focusImage()
|
||||
.ribbons
|
||||
.restoreTransitions()
|
||||
// show viewer after we are done...
|
||||
that.dom[0].style.visibility = '' }, 150) } })],
|
||||
|
||||
// XXX should this be a toggler???
|
||||
showDevTools: ['Interface|Development/Show Dev Tools',
|
||||
{mode: 'advancedBrowseModeAction'},
|
||||
function(action){
|
||||
if(action == '?'){
|
||||
return document.appDevTools }
|
||||
electron.ipcRenderer.send('openDevTools') }],
|
||||
// XXX make this portable (osx, linux)...
|
||||
showInFolder: ['File|Image/Show in $folder',
|
||||
function(image){
|
||||
image = this.images[this.data.getImage(image)]
|
||||
|
||||
var base = image.base_path || this.location.path
|
||||
var filename = image.path
|
||||
var path = pathlib.normalize(base + '/' + filename)
|
||||
|
||||
requirejs('child_process')
|
||||
// XXX this is windows-specific...
|
||||
.exec(`explorer.exe /select,"${ pathlib.normalize(path) }"`)
|
||||
// XXX osx...
|
||||
//.exec('open -R '+JSON.stringify(path))
|
||||
}],
|
||||
|
||||
// XXX make this a real toggler...
|
||||
toggleSplashScreen: ['Interface/',
|
||||
{mode: 'advancedBrowseModeAction'},
|
||||
function(action){
|
||||
var splash = document.appSplashScreen
|
||||
|
||||
if(action == '?'){
|
||||
return !splash ? 'off' : 'on' }
|
||||
|
||||
if(action != 'off' && !splash){
|
||||
electron.ipcRenderer.send('openSplashScreen')
|
||||
|
||||
} else if(action != 'on' && splash){
|
||||
electron.ipcRenderer.send('closeSplashScreen') } }],
|
||||
|
||||
// XXX should this support resizing???
|
||||
copy: ['Image|Edit/Copy image',
|
||||
core.doc`Copy image
|
||||
|
||||
Copy current image (original ref)...
|
||||
.copy()
|
||||
|
||||
Copy best matching preview of current image...
|
||||
.copy(size)
|
||||
|
||||
NOTE: this will rotate and flip the image according to image metadata...
|
||||
`,
|
||||
function(size){
|
||||
var that = this
|
||||
var url = this.images.getBestPreview(this.current, size, true).url
|
||||
// prep image for copy...
|
||||
img2canvas({
|
||||
...this.images[this.current],
|
||||
url,
|
||||
}, function(c){
|
||||
electron.clipboard.write({
|
||||
bookmark: that.images.getImageFileName(),
|
||||
// compatibility -- windows paths...
|
||||
text: process.platform.startsWith('win') ?
|
||||
url.replace(/\//g, '\\')
|
||||
: url,
|
||||
image: electron.nativeImage.createFromDataURL(c.toDataURL('image/png')),
|
||||
}) }) }],
|
||||
paste: ['- Image|Edit/Paste image',
|
||||
function(){
|
||||
// XXX
|
||||
}],
|
||||
})
|
||||
|
||||
var ElectronHost =
|
||||
module.ElectronHost = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
tag: 'ui-electron-host',
|
||||
exclusive: 'ui-host',
|
||||
depends: [],
|
||||
|
||||
actions: ElectronHostActions,
|
||||
|
||||
isApplicable: function(){ return this.runtime.electron },
|
||||
})
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// XXX this needs to trigger only when fullwindow browser mode and not
|
||||
// get loaded when in widget mode...
|
||||
|
||||
var BrowserHostActions = actions.Actions({
|
||||
// window stuff...
|
||||
get title(){
|
||||
return $('title').text() },
|
||||
set title(value){
|
||||
$('title').text(value) },
|
||||
|
||||
// XXX this makes document.body fullscreen as expanding .dom breaks
|
||||
// aligning -- this might be a sign that we are not placing
|
||||
// some things relative to .dom...
|
||||
toggleFullScreen: ['Window/Full screen mode',
|
||||
toggler.CSSClassToggler(
|
||||
function(){ return document.body },
|
||||
'.full-screen-mode',
|
||||
function(action){
|
||||
var that = this
|
||||
|
||||
// get current state...
|
||||
var state = document.fullscreenElement ? 'on' : 'off'
|
||||
|
||||
// change the state only if the target state is not the same
|
||||
// as the current state...
|
||||
if(state != action){
|
||||
this.ribbons.preventTransitions()
|
||||
|
||||
// hide the viewer to hide any animation crimes...
|
||||
this.dom[0].style.visibility = 'hidden'
|
||||
|
||||
state == 'on' ?
|
||||
document.exitFullscreen()
|
||||
// XXX id document.body the right scope here???
|
||||
// ...this.dom[0] seems to break alignment...
|
||||
//: this.dom[0].requestFullscreen()
|
||||
: document.body.requestFullscreen()
|
||||
|
||||
setTimeout(function(){
|
||||
that
|
||||
.centerViewer()
|
||||
.focusImage()
|
||||
.ribbons
|
||||
.restoreTransitions()
|
||||
// show viewer after we are done...
|
||||
that.dom[0].style.visibility = '' }, 150)
|
||||
}
|
||||
})],
|
||||
|
||||
|
||||
// XXX these do not work from file://
|
||||
// XXX would be nice to add a path/title here...
|
||||
// XXX should this support resizing???
|
||||
copy: ['Image|Edit/Copy image',
|
||||
core.doc`Copy image
|
||||
|
||||
NOTE: this must be called from within an event handler...
|
||||
NOTE: this will not work with file:// paths...
|
||||
`,
|
||||
function(size){
|
||||
img2canvas({
|
||||
url: this.images.getBestPreview(this.current, size, true).url ,
|
||||
...this.images[this.current]
|
||||
}, function(c){
|
||||
c.toBlob(function(blob){
|
||||
// copy...
|
||||
// XXX would be nice to add a path/title here...
|
||||
navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
[blob.type]: blob,
|
||||
}) ]) },
|
||||
"image/png") })
|
||||
}],
|
||||
paste: ['- Image|Edit/Paste image',
|
||||
function(){
|
||||
// XXX
|
||||
}],
|
||||
})
|
||||
|
||||
// NOTE: keep this defined last as a fallback...
|
||||
var BrowserHost =
|
||||
module.BrowserHost = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
tag: 'ui-browser-host',
|
||||
exclusive: 'ui-host',
|
||||
depends: [
|
||||
// XXX remove id buttons control moves elsewhere...
|
||||
'ui-buttons',
|
||||
],
|
||||
|
||||
actions: BrowserHostActions,
|
||||
|
||||
isApplicable: function(){ return !this.runtime.widget },
|
||||
})
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
var PortableAppControl =
|
||||
module.PortableAppControl = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
tag: 'ui-portable-app-control',
|
||||
depends: [
|
||||
'ui',
|
||||
'ui-host',
|
||||
],
|
||||
|
||||
isApplicable: function(){ return this.runtime.browser },
|
||||
|
||||
config: {
|
||||
//'window-title': 'ImageGrid.Viewer (${VERSION}): ${FILENAME}',
|
||||
'window-title': '${FILENAME} - ImageGrid.Viewer (${VERSION})',
|
||||
|
||||
},
|
||||
|
||||
handlers: [
|
||||
// update window title...
|
||||
// XXX make this generic...
|
||||
['focusImage clear',
|
||||
function(){
|
||||
if(this.images){
|
||||
var img = this.images[this.current]
|
||||
this.title = (this.config['window-title']
|
||||
|| 'ImageGrid.Viewer (${VERSION}): ${FILENAME}')
|
||||
// XXX get this from the viewer...
|
||||
.replace('${VERSION}', this.version || 'gen4')
|
||||
.replace('${FILENAME}',
|
||||
img ?
|
||||
(img.name
|
||||
|| (img.path && img.path.replace(/\.[\\\/]/, ''))
|
||||
|| this.current
|
||||
|| '')
|
||||
: (this.current || ''))
|
||||
.replace('${PATH}',
|
||||
(img && img.path) ?
|
||||
(img.base_path || '.')
|
||||
+'/'+ img.path.replace(/\.[\\\/]/, '')
|
||||
: '')
|
||||
/*
|
||||
.replace('${DIR}',
|
||||
pathlib.dirname((img.base_path || '.')
|
||||
+'/'+ img.path.replace(/\.[\\\/]/, '')))
|
||||
*/
|
||||
// XXX add ...
|
||||
}
|
||||
}],
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
// XXX should we create and show the window here???
|
||||
// ...if yes, then we'll need to start the ui features later as
|
||||
// they need the dom ready...
|
||||
var WindowedAppControlActions = actions.Actions({
|
||||
config: {
|
||||
// Window state:
|
||||
// 'window': {
|
||||
// width: <number>,
|
||||
// height: <number>,
|
||||
// x: <number>,
|
||||
// y: <number>,
|
||||
//
|
||||
// fullscreen: <bool>,
|
||||
//
|
||||
// devtools: <bool>,
|
||||
// },
|
||||
|
||||
'window-delay-initial-display': 500,
|
||||
'splash-screen-delay-hide': 500,
|
||||
|
||||
'show-splash-screen': 'on',
|
||||
},
|
||||
|
||||
close: ['File|Window/Close viewer',
|
||||
function(){
|
||||
this.stop()
|
||||
window.close()
|
||||
}],
|
||||
|
||||
storeWindowGeometry: ['- Window/Store window state',
|
||||
function(){
|
||||
// store window parameters (size, state)...
|
||||
//var win = nw.Window.get()
|
||||
var size = this.size
|
||||
var position = this.position
|
||||
|
||||
// fullscreen...
|
||||
// ...avoid overwriting size...
|
||||
if(this.toggleFullScreen('?') == 'on'){
|
||||
// NOTE: this needs to be rewritten to correctly get stored
|
||||
// in the config store, especially if a default state
|
||||
// is defined...
|
||||
var cfg = this.config.window = this.config.window || {}
|
||||
cfg.fullscreen = true
|
||||
cfg.devtools = this.showDevTools
|
||||
&& this.showDevTools('?')
|
||||
|
||||
} else {
|
||||
this.config.window = {
|
||||
width: size[0],
|
||||
height: size[1],
|
||||
|
||||
x: position[0],
|
||||
y: position[1],
|
||||
|
||||
fullscreen: false,
|
||||
devtools: this.showDevTools
|
||||
&& this.showDevTools('?'),
|
||||
} } }],
|
||||
restoreWindowGeometry: ['- Window/Restore window state',
|
||||
function(){
|
||||
var that = this
|
||||
var cfg = this.config.window || {}
|
||||
|
||||
if(cfg.fullscreen){
|
||||
that.toggleFullScreen('on')
|
||||
|
||||
} else {
|
||||
var w = cfg.width || 800
|
||||
var h = cfg.height || 600
|
||||
var x = cfg.x || (screen.width - w)/2
|
||||
var y = cfg.y || (screen.height - h)/2
|
||||
|
||||
this.position = [x, y]
|
||||
this.size = [w, h] } }],
|
||||
|
||||
toggleSplashScreenShowing: ['Interface/Splash screen on start',
|
||||
{mode: 'advancedBrowseModeAction'},
|
||||
core.makeConfigToggler('show-splash-screen',
|
||||
['on', 'off'],
|
||||
function(action){
|
||||
if(action == 'on'){
|
||||
delete localStorage.disableSplashScreen
|
||||
|
||||
} else if(action == 'off'){
|
||||
localStorage.disableSplashScreen = true
|
||||
}
|
||||
})],
|
||||
})
|
||||
|
||||
var WindowedAppControl =
|
||||
module.WindowedAppControl = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
tag: 'ui-windowed-app-control',
|
||||
depends: [
|
||||
'ui',
|
||||
'ui-host',
|
||||
],
|
||||
|
||||
actions: WindowedAppControlActions,
|
||||
|
||||
// XXX BUG: when running in electron:
|
||||
// - loading this breaks the other buttons (menu, collections, ...)
|
||||
// - .toggleFullScreen(..) works in an odd manner -- ok when
|
||||
// explicitly called or button pushed but in opposite manner
|
||||
// when F11 (FIXED?)
|
||||
// - ready protocol breaks -- need to call .ready() to unstall
|
||||
// the viewer
|
||||
// ...does this all have anything to do with double init???
|
||||
isApplicable: function(){ return this.runtime.desktop },
|
||||
//isApplicable: function(){ return this.runtime.desktop && !this.runtime.electron },
|
||||
|
||||
// XXX show main window...
|
||||
handlers: [
|
||||
['start.pre',
|
||||
function(){
|
||||
// we are going to declare ready ourselves...
|
||||
this.requestReadyAnnounce('ui-windowed-app-control') }],
|
||||
// XXX should we create and show the window here???
|
||||
['start',
|
||||
function(){
|
||||
var that = this
|
||||
var cfg = this.config.window
|
||||
|
||||
// set the initial non-fullscreen window geometry...
|
||||
// NOTE: this will avoid overwriting the initial geometry
|
||||
// with the values set in e.js if .fullscreen is set...
|
||||
// NOTE: this will also set the size to which the OS will
|
||||
// resize the window in state change...
|
||||
if(cfg){
|
||||
cfg.devtools
|
||||
&& this.showDevTools()
|
||||
|
||||
var W = screen.width
|
||||
var H = screen.height
|
||||
var w = cfg.width || Math.max(0.8 * W, 600)
|
||||
var h = cfg.height || Math.max(0.8 * H, 400)
|
||||
var x = cfg.x || (W - w)/2
|
||||
var y = cfg.y || (H - h)/2
|
||||
|
||||
this.position = [x, y]
|
||||
this.size = [w, h] }
|
||||
|
||||
// restore actual window state...
|
||||
this.restoreWindowGeometry()
|
||||
|
||||
// declare we are ready when DOM is ready...
|
||||
$(function(){
|
||||
that.declareReady('ui-windowed-app-control') }) }],
|
||||
|
||||
// show window + hide splash screen...
|
||||
['ready',
|
||||
function(){
|
||||
var that = this
|
||||
// NOTE: we delay this to enable the browser time to render
|
||||
// things before we show them to the user...
|
||||
setTimeout(function(){
|
||||
// show window...
|
||||
that.show()
|
||||
|
||||
// hide splash screen...
|
||||
setTimeout(function(){
|
||||
that.toggleSplashScreen('off')
|
||||
}, (that.config['splash-screen-delay-hide'] || 0))
|
||||
}, this.config['window-delay-initial-display'] || 0) }],
|
||||
|
||||
[[
|
||||
'stop.pre',
|
||||
'toggleFullScreen.pre',
|
||||
'toggleFullScreen',
|
||||
],
|
||||
function(){ this.storeWindowGeometry() }],
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
var AppControl =
|
||||
module.AppControl = core.ImageGridFeatures.Feature('app-control', [
|
||||
'ui-windowed-app-control',
|
||||
'ui-portable-app-control',
|
||||
])
|
||||
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */ return module })
|
||||
1790
Viewer/features/base.js
Executable file
1790
Viewer/features/base.js
Executable file
File diff suppressed because it is too large
Load Diff
957
Viewer/features/cli.js
Executable file
957
Viewer/features/cli.js
Executable file
@ -0,0 +1,957 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
||||
(function(require){ var module={} // make module AMD/node compatible...
|
||||
/*********************************************************************/
|
||||
|
||||
var object = require('lib/object')
|
||||
var util = require('lib/util')
|
||||
var actions = require('lib/actions')
|
||||
var features = require('lib/features')
|
||||
|
||||
var data = require('imagegrid/data')
|
||||
var images = require('imagegrid/images')
|
||||
|
||||
var core = require('features/core')
|
||||
var base = require('features/base')
|
||||
|
||||
|
||||
//require('features/all')
|
||||
|
||||
|
||||
if(typeof(process) != 'undefined'){
|
||||
var pathlib = requirejs('path')
|
||||
var argv = requirejs('lib/argv')
|
||||
var progress = requirejs('cli-progress')
|
||||
var colors = requirejs('colors')
|
||||
|
||||
var file = require('imagegrid/file') }
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
var CLIActions = actions.Actions({
|
||||
config: {
|
||||
// XXX do we care that something is not "ready" here???
|
||||
'declare-ready-timeout': 0,
|
||||
|
||||
'progress-done-delay': 1000,
|
||||
|
||||
banner: '$APPNAME $VERSION:',
|
||||
},
|
||||
|
||||
|
||||
// docs...
|
||||
//
|
||||
// XXX do a better set of examples...
|
||||
cliExamples: [[
|
||||
'Create/init index in current directory',
|
||||
'$ $SCRIPTNAME init',
|
||||
'',
|
||||
'Export 500px previews from current index to ./preview directory',
|
||||
'$ $SCRIPTNAME export from=. to=./previews --image-size=500',
|
||||
]],
|
||||
|
||||
|
||||
// the argvparser...
|
||||
//
|
||||
// this is set by argv's Parser on .onArgs(..) in .ready(..) handler below...
|
||||
argv: undefined,
|
||||
|
||||
help: ['- System/Show action help',
|
||||
function(...actions){
|
||||
Object.entries(this.getDoc(actions))
|
||||
.forEach(function([action, [s, l]]){
|
||||
console.log(l)
|
||||
console.log('')
|
||||
}) }],
|
||||
|
||||
get cliActions(){
|
||||
return this.actions
|
||||
.filter(function(action){
|
||||
return this.getActionAttr(action, 'cli') }.bind(this)) },
|
||||
|
||||
// XXX should this be here???
|
||||
// ...move this to progress...
|
||||
// XXX we are missing some beats, is this because we do not let the
|
||||
// bar update before closing???
|
||||
// XXX need to reset this when done...
|
||||
__progress: null,
|
||||
showProgress: ['- System/',
|
||||
function(text, value, max){
|
||||
// progress display is disabled...
|
||||
if(this.__progress === false){
|
||||
return }
|
||||
|
||||
var msg = text instanceof Array ?
|
||||
text.slice(1).join(': ')
|
||||
: null
|
||||
text = text instanceof Array ?
|
||||
text[0]
|
||||
: text
|
||||
|
||||
var settings = this.__progress = this.__progress ?? {}
|
||||
var bars = settings.bars = settings.bars ?? {}
|
||||
var state = bars[text] = bars[text] ?? {}
|
||||
|
||||
if(state.timeout){
|
||||
clearTimeout(state.timeout)
|
||||
delete state.timeout }
|
||||
|
||||
// actions...
|
||||
if(value == 'reset'){
|
||||
// XXX this is not the same as ui-progress...
|
||||
// ...here we first set timeout then and close,
|
||||
// there we set to 0 and timeout and close...
|
||||
state.timeout = setTimeout(
|
||||
function(){
|
||||
//this.showProgress(text, 0, 0) }.bind(this),
|
||||
this.showProgress(text, 'close') }.bind(this),
|
||||
this.config['progress-done-delay'] || 1000)
|
||||
return }
|
||||
if(value == 'close'){
|
||||
delete bars[text]
|
||||
// check if no bars left...
|
||||
if(Object.keys(bars) == 0){
|
||||
delete this.__progress }
|
||||
return }
|
||||
|
||||
var l = Math.max(text.length, settings.__text_length || 0)
|
||||
// length changed -> update the bars...
|
||||
l != settings.__text_length
|
||||
&& Object.entries(bars)
|
||||
.forEach(function([key, value]){
|
||||
value.bar
|
||||
&& value.bar.update({text: key.padEnd(l)}) })
|
||||
settings.__text_length = l
|
||||
|
||||
// normalize max and value...
|
||||
value = state.value =
|
||||
value != null ?
|
||||
(typeof(value) == typeof('str') && /[+-][0-9]+/.test(value) ?
|
||||
(state.value || 0) + parseInt(value)
|
||||
: value)
|
||||
: state.value
|
||||
max = state.max =
|
||||
max != null ?
|
||||
(typeof(max) == typeof('str') && /[+-][0-9]+/.test(max) ?
|
||||
(state.max || 0) + parseInt(max)
|
||||
: max)
|
||||
: state.max
|
||||
|
||||
var container = settings.__multi_bar =
|
||||
settings.__multi_bar
|
||||
|| (new progress.MultiBar({
|
||||
// XXX make this simpler...
|
||||
format: '{text} {bar} {percentage}% '
|
||||
+'| ETA: {eta_formatted} | {value}/{total}',
|
||||
autopadding: true,
|
||||
stopOnComplete: true,
|
||||
forceRedraw: true,
|
||||
},
|
||||
progress.Presets.rect)
|
||||
// prepare for printing stuff...
|
||||
.run(function(){
|
||||
this.on('redraw-pre', function(){
|
||||
// XXX need to clear the line -- need to get term-width....
|
||||
// XXX this requires a full draw (forceRedraw: true)...
|
||||
//console.log('moo'.padEnd(process.stdout.columns))
|
||||
}) }))
|
||||
var bar = state.bar =
|
||||
state.bar
|
||||
|| container.create(0, 0, {text: text.padEnd(l)})
|
||||
|
||||
// XXX for some reason this does not work under electron...
|
||||
bar.setTotal(Math.max(max, value))
|
||||
bar.update(value)
|
||||
|
||||
// auto-clear when complete...
|
||||
if(value >= max){
|
||||
state.timeout = setTimeout(
|
||||
function(){
|
||||
this.showProgress(text, 'close') }.bind(this),
|
||||
this.config['progress-done-delay'] || 1000) } }],
|
||||
|
||||
// handle logger progress...
|
||||
// XXX reset is called at odd spots by the queue handler (see: features/core.js)
|
||||
// XXX this is a copy from ui-progress -- need to reuse if possible...
|
||||
handleLogItem: ['- System/',
|
||||
function(logger, path, status, ...rest){
|
||||
var msg = path.join(': ')
|
||||
var l = (rest.length == 1
|
||||
&& rest[0] instanceof Array) ?
|
||||
rest[0].length
|
||||
: rest.length
|
||||
|
||||
// only pass the relevant stuff...
|
||||
var attrs = {}
|
||||
logger.ondone
|
||||
&& (attrs.ondone = logger.ondone)
|
||||
logger.onclose
|
||||
&& (attrs.onclose = logger.onclose)
|
||||
|
||||
// get keywords...
|
||||
var {add, done, skip, reset, close, error} =
|
||||
this.config['progress-logger-keywords']
|
||||
|| {}
|
||||
// setup default aliases...
|
||||
add = new Set([...(add || []), 'added'])
|
||||
done = new Set([...(done || [])])
|
||||
skip = new Set([...(skip || []), 'skipped'])
|
||||
reset = new Set([...(reset || [])])
|
||||
close = new Set([...(close || []), 'closed'])
|
||||
error = new Set([...(error || [])])
|
||||
|
||||
// close...
|
||||
if(status == 'close' || close.has(status)){
|
||||
this.showProgress(path, 'close')
|
||||
// reset...
|
||||
// XXX this seems to be called before "Cache image metadata" is done
|
||||
// when called from .cliInitIndex(..) -- messing up the numbers...
|
||||
} else if(status == 'reset' || reset.has(status)){
|
||||
this.showProgress(path, 'reset')
|
||||
// added new item -- increase max...
|
||||
// XXX show msg in the progress bar???
|
||||
} else if(status == 'add' || add.has(status)){
|
||||
this.showProgress(path, '+0', '+'+l)
|
||||
// resolved item -- increase done...
|
||||
} else if(status == 'done' || done.has(status)){
|
||||
this.showProgress(path, '+'+l)
|
||||
// skipped item -- increase done...
|
||||
// XXX should we instead decrease max here???
|
||||
// ...if not this is the same as done -- merge...
|
||||
} else if(status == 'skip' || skip.has(status)){
|
||||
this.showProgress(path, '+'+l)
|
||||
// error...
|
||||
// XXX STUB...
|
||||
} else if(status == 'error' || error.has(status)){
|
||||
this.showProgress(['Error'].concat(msg), '+0', '+'+l) }
|
||||
}],
|
||||
|
||||
// XXX SETUP revise default...
|
||||
setupFeatures: ['- System/',
|
||||
core.doc`Load required features.
|
||||
|
||||
NOTE: this is hete because cli is designed to be loaded in a very
|
||||
limited context and for some actions will need additional
|
||||
features.
|
||||
`,
|
||||
function(...tags){
|
||||
var features = this.features.FeatureSet
|
||||
requirejs('features/all')
|
||||
features.setup(this, [
|
||||
'imagegrid-testing',
|
||||
...(tags.length == 0 ?
|
||||
this.features.input
|
||||
: tags),
|
||||
]) }],
|
||||
setupGlobals: ['- System/',
|
||||
function(){
|
||||
// setup the global ns...
|
||||
global.ig =
|
||||
global.ImageGrid =
|
||||
this
|
||||
global.help = function(...actions){
|
||||
global.ig.help(...actions) }
|
||||
global.ImageGridFeatures = core.ImageGridFeatures }],
|
||||
|
||||
|
||||
// basic code runner...
|
||||
cliDo: ['- System/CLI/run CODE',
|
||||
{cli: {
|
||||
name: '@do',
|
||||
arg: 'CODE',
|
||||
}},
|
||||
function(code){
|
||||
var AsyncFunction = (async function(){}).constructor
|
||||
|
||||
this.setupFeatures()
|
||||
this.setupGlobals()
|
||||
|
||||
AsyncFunction(code)()
|
||||
|
||||
this.stop() }],
|
||||
|
||||
// Interactive commands...
|
||||
//
|
||||
cliStartREPL: ['- System/CLI/start CLI interpreter',
|
||||
{cli: {
|
||||
name: '@repl',
|
||||
arg: 'PATH'
|
||||
//interactive: true,
|
||||
}},
|
||||
function(path, options){
|
||||
var that = this
|
||||
var package = nodeRequire('./package.json')
|
||||
|
||||
// XXX SETUP
|
||||
this.setupFeatures()
|
||||
|
||||
if(path){
|
||||
this.loadIndex(path) }
|
||||
|
||||
this.__keep_running = true
|
||||
|
||||
this.setupGlobals()
|
||||
|
||||
// start non-tty / script mode...
|
||||
if(!process.stdin.isTTY){
|
||||
var fs = nodeRequire('fs')
|
||||
var AsyncFunction = (async function(){}).constructor
|
||||
|
||||
AsyncFunction(
|
||||
fs.readFileSync(process.stdin.fd, 'utf-8'))()
|
||||
this.stop()
|
||||
|
||||
// start repl mode...
|
||||
} else {
|
||||
var repl = nodeRequire('repl')
|
||||
// print banner...
|
||||
var banner = this.banner
|
||||
|| this.config.banner
|
||||
banner
|
||||
&& process.stdin.isTTY
|
||||
&& process.stdout.isTTY
|
||||
&& console.log(banner
|
||||
.replace(/\$APPNAME/g, package.name)
|
||||
.replace(/\$AUTHOR/g, package.author)
|
||||
.replace(/\$REPO/g, package.repository)
|
||||
.replace(/\$SCRIPTNAME/g, this.argv.scriptName)
|
||||
.replace(/\$VERSION/g, this.version))
|
||||
|
||||
// start the repl...
|
||||
repl
|
||||
.start({
|
||||
prompt: 'ig> ',
|
||||
useGlobal: true,
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
})
|
||||
.on('exit', function(){
|
||||
that.stop() }) } }],
|
||||
// XXX we should open multiple paths/indexes...
|
||||
// XXX move this to a feature that requires electron...
|
||||
// ...and move electron to an optional dependency...
|
||||
cliStartGUI: ['- System/CLI/start viewer GUI',
|
||||
core.doc`
|
||||
|
||||
NOTE: this will not wait for the viewer to exit.`,
|
||||
{cli: argv && argv.Parser({
|
||||
key: '@gui',
|
||||
arg: 'PATH',
|
||||
doc: 'start viewer GUI',
|
||||
|
||||
'-version': undefined,
|
||||
'-quiet': undefined,
|
||||
|
||||
'-devtools': {
|
||||
doc: 'show DevTools',
|
||||
type: 'bool',
|
||||
},
|
||||
'-show': {
|
||||
doc: 'force show interface',
|
||||
type: 'bool',
|
||||
},
|
||||
})},
|
||||
function(path, options={}){
|
||||
var env = { ...process.env }
|
||||
path
|
||||
&& (env.IMAGEGRID_PATH =
|
||||
util.normalizePath(
|
||||
pathlib.resolve(process.cwd(), path)))
|
||||
options.devtools
|
||||
&& (env.IMAGEGRID_DEBUG = true)
|
||||
options.show
|
||||
&& (env.IMAGEGRID_FORCE_SHOW = true)
|
||||
|
||||
// already in electron...
|
||||
if(process.versions.electron){
|
||||
// XXX this feels hackish...
|
||||
global.START_GUI = true
|
||||
|
||||
// launch gui...
|
||||
} else {
|
||||
requirejs('child_process')
|
||||
.spawn(requirejs('electron'),
|
||||
[ pathlib.join(
|
||||
pathlib.dirname(nodeRequire.main.filename),
|
||||
'e.js') ],
|
||||
{
|
||||
detached: true,
|
||||
env,
|
||||
}) } }],
|
||||
|
||||
// XXX
|
||||
cliGID: ['- System/GLI/generate GID',
|
||||
{cli: {
|
||||
name: '@gid',
|
||||
arg: 'IMAGE',
|
||||
valueRequired: true,
|
||||
|
||||
// XXX REMOVE WHEN DONE...
|
||||
doc: false,
|
||||
}},
|
||||
function(path){
|
||||
// XXX
|
||||
console.warn('Not implemented yet...')
|
||||
}],
|
||||
cliListIndexes: ['- System/CLI/list indexes in PATH',
|
||||
{cli: argv && argv.Parser({
|
||||
key: '@ls',
|
||||
arg: 'PATH',
|
||||
doc: 'list indexes in PATH',
|
||||
|
||||
'-version': undefined,
|
||||
'-quiet': undefined,
|
||||
|
||||
'-r': '-recursive',
|
||||
'-recursive': {
|
||||
doc: 'list nested/recursive indexes',
|
||||
type: 'bool',
|
||||
},
|
||||
|
||||
'-n': '-nested-only',
|
||||
'-nested-only': {
|
||||
doc: 'ignore the top-level index and only list the indexes below',
|
||||
type: 'bool',
|
||||
},
|
||||
|
||||
})},
|
||||
function(path, options={}){
|
||||
var that = this
|
||||
path = path ?? '.'
|
||||
// needed to get the default index dir name...
|
||||
this.setupFeatures('fs')
|
||||
//this.setupFeatures()
|
||||
file.listIndexes(path)
|
||||
.on('end', function(paths){
|
||||
paths = paths
|
||||
.map(function(p){
|
||||
return p
|
||||
.split(that.config['index-dir'])
|
||||
.shift() })
|
||||
// normalize path...
|
||||
path.at(-1) != '/'
|
||||
&& (path += '/')
|
||||
// handle --nested-only
|
||||
options['nested-only']
|
||||
&& paths.includes(path)
|
||||
&& paths.splice(paths.indexOf(path), 1)
|
||||
paths = options.recursive ?
|
||||
paths
|
||||
: file.skipNested(paths)
|
||||
.sortAs(paths)
|
||||
for(var p of paths){
|
||||
console.log(p) } }) }],
|
||||
|
||||
// XXX check if index exists:
|
||||
// yes: warn + stup
|
||||
// no: create
|
||||
// ...add -f/-force flag...
|
||||
// XXX metadata caching and preview creation are not in sync, can
|
||||
// this be a problem???
|
||||
// ...if not, add a note...
|
||||
// XXX should we support creating multiple indexes at the same time???
|
||||
// XXX this is relatively generic, might be useful globally...
|
||||
// XXX should we use a clean index or do this in-place???
|
||||
// XXX add ability to disable sort... (???)
|
||||
cliInitIndex: ['- System/CLI/make index',
|
||||
core.doc`
|
||||
|
||||
Create index in current directory
|
||||
.cliInitIndex()
|
||||
.cliInitIndex('create')
|
||||
-> promise
|
||||
|
||||
Create index in path...
|
||||
,cliInitIndex(path)
|
||||
.cliInitIndex('create', path)
|
||||
-> promise
|
||||
|
||||
|
||||
Update index in current directory
|
||||
.cliInitIndex('update')
|
||||
-> promise
|
||||
|
||||
Update index in path...
|
||||
.cliInitIndex('update', path)
|
||||
-> promise
|
||||
|
||||
`,
|
||||
{cli: {
|
||||
name: '@init',
|
||||
arg: 'PATH',
|
||||
}},
|
||||
function(path, options){
|
||||
this.setupFeatures()
|
||||
|
||||
// get mode...
|
||||
if(path == 'create' || path == 'update'){
|
||||
var [mode, path, options] = arguments }
|
||||
mode = mode || 'create'
|
||||
// normalize path...
|
||||
path = util.normalizePath(
|
||||
path ?
|
||||
pathlib.resolve(process.cwd(), path)
|
||||
: process.cwd())
|
||||
options = options || {}
|
||||
|
||||
// XXX should we use a clean index or do this in-place???
|
||||
//var index = this.constructor(..)
|
||||
var index = this
|
||||
return (mode == 'create' ?
|
||||
index.loadImages(path)
|
||||
: index.loadNewImages(path))
|
||||
// save base index...
|
||||
.then(function(){
|
||||
return index.saveIndex() })
|
||||
// sharp stuff...
|
||||
.then(function(){
|
||||
if(index.makePreviews){
|
||||
return Promise.all([
|
||||
// NOTE: no need to call .cacheMetadata(..) as
|
||||
// it is already running after .loadImages(..)
|
||||
index.makePreviews('all') ])} })
|
||||
.then(function(){
|
||||
return index
|
||||
.sortImages()
|
||||
.saveIndex() }) }],
|
||||
// XXX does not work yet...
|
||||
// ... -h breaks things...
|
||||
cliUpdateIndex: ['- System/CLI/update index',
|
||||
{cli: {
|
||||
name: '@update',
|
||||
arg: 'PATH',
|
||||
}},
|
||||
'cliInitIndex: "update" ...'],
|
||||
|
||||
// XXX handle errors...
|
||||
cliInfo: ['- System/CLI/show information about index in PATH',
|
||||
{cli: {
|
||||
name: '@info',
|
||||
arg: 'PATH',
|
||||
}},
|
||||
function(path, options={}){
|
||||
var that = this
|
||||
path = path ?? '.'
|
||||
this.setupFeatures()
|
||||
return this.loadIndex(path)
|
||||
.then(
|
||||
async function(){
|
||||
var modified =
|
||||
Object.values(
|
||||
await that.loadSaveHistoryList())
|
||||
.map(function(log){
|
||||
return Object.keys(log) })
|
||||
.flat()
|
||||
.sort()
|
||||
.pop()
|
||||
// calculate core.doc compatible offset for nested items.
|
||||
var offset = '\t'.repeat(`
|
||||
`.split('\t').length)
|
||||
console.log(core.doc`
|
||||
Load path: ${ path }
|
||||
Index path: ${ that.location.path }
|
||||
Loaded indexes: ${
|
||||
['', ...that.location.loaded].join('\n'+offset) }
|
||||
Current image: ${ that.current }
|
||||
Image count: ${ that.data.order.length }
|
||||
Collections: ${
|
||||
that.collections ?
|
||||
['', ...Object.keys(that.collections || [])].join('\n'+offset)
|
||||
: '-' }
|
||||
Modified date: ${ modified }`) },
|
||||
function(err){
|
||||
console.error('Can\'t find or load index at:', path) }) }],
|
||||
cliListCollections: ['- System/CLI/list collections in index',
|
||||
{cli: argv && argv.Parser({
|
||||
key: '@collections',
|
||||
doc: 'list collection in index at PATH',
|
||||
arg: 'PATH',
|
||||
|
||||
'-version': undefined,
|
||||
'-quiet': undefined,
|
||||
|
||||
'-f': '-full',
|
||||
'-full': {
|
||||
doc: 'show full collection information',
|
||||
type: 'bool',
|
||||
},
|
||||
})},
|
||||
function(path, options={}){
|
||||
var that = this
|
||||
|
||||
this.setupFeatures()
|
||||
|
||||
path = path || options.value
|
||||
path = util.normalizePath(
|
||||
path ?
|
||||
pathlib.resolve(process.cwd(), path)
|
||||
: process.cwd())
|
||||
return this.loadIndex(path)
|
||||
.then(
|
||||
function(){
|
||||
for(var name of that.collection_order || []){
|
||||
// XXX revise output formatting...
|
||||
options.full ?
|
||||
console.log(that.collections[name].gid, name)
|
||||
: console.log(name) } },
|
||||
function(err){
|
||||
// XXX how do we handle rejection???
|
||||
console.error('Can\'t find or load index at:', path) }) }],
|
||||
|
||||
// XXX
|
||||
cliCloneIndex: ['- System/CLI/clone index',
|
||||
function(){
|
||||
}],
|
||||
// XXX report that we can't find an index...
|
||||
// ...or should we treat the target as an image dir???
|
||||
// XXX move options to generic object for re-use...
|
||||
// XXX how do we handle errors???
|
||||
cliExportImages: ['- System/CLI/export images',
|
||||
{cli: argv && argv.Parser({
|
||||
key: '@export',
|
||||
doc: 'export images',
|
||||
// help...
|
||||
'-help-pattern': {
|
||||
doc: 'show image filename pattern info and exit',
|
||||
priority: 89,
|
||||
handler: function(){
|
||||
this.parent.context
|
||||
// XXX SETUP
|
||||
//.setupFeatures('fs', 'commandline')
|
||||
.setupFeatures()
|
||||
.help('formatImageName')
|
||||
return argv.STOP } },
|
||||
'-version': undefined,
|
||||
'-quiet': undefined,
|
||||
// commands...
|
||||
'@from': {
|
||||
doc: 'source path',
|
||||
arg: 'PATH | from',
|
||||
default: '.',
|
||||
valueRequired: true, },
|
||||
// XXX
|
||||
'@collection': {
|
||||
doc: 'source collection (name/gid)',
|
||||
arg: 'COLLECTION | collection',
|
||||
//default: 'ALL',
|
||||
valueRequired: false, },
|
||||
//*/
|
||||
'@to': {
|
||||
doc: 'destination path',
|
||||
arg: 'PATH | path',
|
||||
required: true,
|
||||
valueRequired: true, },
|
||||
// bool options...
|
||||
// XXX these should get defaults from .config
|
||||
'-include-virtual': {
|
||||
doc: 'include virtual blocks',
|
||||
arg: '| include-virtual',
|
||||
type: 'bool',
|
||||
//value: true,
|
||||
default: true, },
|
||||
'-clean-target': {
|
||||
doc: 'cleanup target before export (backup)',
|
||||
arg: '| clean-target',
|
||||
type: 'bool',
|
||||
//value: true,
|
||||
default: true, },
|
||||
'-no-*': {
|
||||
doc: 'negate boolean option value',
|
||||
handler: function(rest, key, value, ...args){
|
||||
rest.unshift(key.replace(/^-?-no/, '') +'=false') } },
|
||||
// options...
|
||||
'-image-name': {
|
||||
doc: 'image name pattern',
|
||||
arg: 'PATTERN | preview-name-pattern',
|
||||
default: '%(fav)l%n%(-%c)c',
|
||||
valueRequired: true, },
|
||||
'-mode': {
|
||||
// XXX get doc values from system...
|
||||
doc: 'export mode, can be "resize" or "copy best match"',
|
||||
arg: 'MODE | export-mode',
|
||||
//default: 'copy best match',
|
||||
default: 'resize',
|
||||
valueRequired: true, },
|
||||
'-image-size': {
|
||||
doc: 'output image size',
|
||||
arg: 'SIZE | preview-size',
|
||||
default: 1000,
|
||||
valueRequired: true, },
|
||||
})},
|
||||
function(path, options={}){
|
||||
var that = this
|
||||
|
||||
// XXX SETUP
|
||||
this.setupFeatures()
|
||||
|
||||
path = path || options.from
|
||||
path = util.normalizePath(
|
||||
path ?
|
||||
pathlib.resolve(process.cwd(), path)
|
||||
: process.cwd())
|
||||
|
||||
var collection = options.collection
|
||||
|
||||
return this.loadIndex(path)
|
||||
.then(
|
||||
function(){
|
||||
// export collection...
|
||||
if(collection){
|
||||
if(!that.collections[collection]){
|
||||
console.error(
|
||||
'Can\'t find collection "'+collection+'" in index at:', path)
|
||||
// XXX how do we handle rejection???
|
||||
//return Promise.reject('moo')
|
||||
return }
|
||||
var resolve
|
||||
var reject
|
||||
// XXX add a timeout???
|
||||
that.one('collectionLoading.post',
|
||||
function(){
|
||||
resolve(that.exportImages(options)) })
|
||||
that.loadCollection(collection)
|
||||
return new Promise(function(res, rej){
|
||||
resolve = res
|
||||
reject = rej }) }
|
||||
// export root...
|
||||
return that.exportImages(options) },
|
||||
function(err){
|
||||
// XXX how do we handle rejection???
|
||||
console.error('Can\'t find or load index at:', path) }) }],
|
||||
|
||||
|
||||
cliRepairIndex: ['- System/CLI/repair index',
|
||||
{cli: argv && argv.Parser({
|
||||
key: '@repair',
|
||||
doc: 'repair index',
|
||||
arg: 'PATH',
|
||||
|
||||
'-version': undefined,
|
||||
'-quiet': undefined,
|
||||
|
||||
'-read-only': '-ro',
|
||||
'-ro': {
|
||||
doc: 'only show possible fixes',
|
||||
type: 'bool',
|
||||
},
|
||||
|
||||
})},
|
||||
async function(path, options){
|
||||
this.setupFeatures()
|
||||
|
||||
await this.loadIndex(path ?? '.')
|
||||
|
||||
var changes = await this.checkIndex()
|
||||
|
||||
// XXX print...
|
||||
console.log(options.ro, changes)
|
||||
|
||||
options.ro
|
||||
//|| this.saveIndexHere()
|
||||
|| console.log('save')
|
||||
}],
|
||||
|
||||
|
||||
// XXX this is still wrong...
|
||||
_cliMakeIndex: ['- System/',
|
||||
`chain: [
|
||||
"loadImages: $1",
|
||||
"saveIndex",
|
||||
"makePreviews: 'all'",
|
||||
"sortImages",
|
||||
"saveIndex", ]`],
|
||||
|
||||
cliCleanIndex: ['- System/',
|
||||
{},
|
||||
function(path, options){}],
|
||||
|
||||
/* XXX
|
||||
cliStartServer: ['- System/CLI/start as server',
|
||||
{cli: '-server'},
|
||||
function(){
|
||||
// XXX
|
||||
}],
|
||||
|
||||
// Actions...
|
||||
//
|
||||
// XXX
|
||||
// XXX this should be a nested parser...
|
||||
// args:
|
||||
// from=PATH
|
||||
// to=PATH
|
||||
// ...
|
||||
cliExportIindex: ['- System/CLI/clone index',
|
||||
{cli: {
|
||||
name: '@clone',
|
||||
arg: 'PATH',
|
||||
valueRequired: true,
|
||||
}},
|
||||
function(){
|
||||
// XXX
|
||||
}],
|
||||
cliPullChanges: ['- System/CLI/pull changes',
|
||||
{cli: {
|
||||
name: '@pull',
|
||||
arg: 'PATH',
|
||||
valueRequired: true,
|
||||
}},
|
||||
function(){
|
||||
// XXX
|
||||
}],
|
||||
cliPushChanges: ['- System/CLI/push changes',
|
||||
{cli: {
|
||||
name: '@push',
|
||||
arg: 'PATH',
|
||||
valueRequired: true,
|
||||
}},
|
||||
function(){
|
||||
// XXX
|
||||
}],
|
||||
//*/
|
||||
|
||||
})
|
||||
|
||||
|
||||
// XXX revise architecture....
|
||||
// XXX move this to the argv parser used in object.js
|
||||
var CLI =
|
||||
module.CLI = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
tag: 'commandline',
|
||||
depends: [
|
||||
'lifecycle',
|
||||
'logger',
|
||||
],
|
||||
|
||||
// XXX should this be ONLY node???
|
||||
isApplicable: function(){
|
||||
return this.runtime.node && !this.runtime.browser },
|
||||
|
||||
actions: CLIActions,
|
||||
|
||||
handlers: [
|
||||
// supress logging by default...
|
||||
['start.pre',
|
||||
function(){
|
||||
this.logger
|
||||
&& (this.logger.quiet = true) }],
|
||||
|
||||
// handle args...
|
||||
// XXX
|
||||
['ready',
|
||||
function(){
|
||||
var that = this
|
||||
|
||||
//var pkg = require('package.json')
|
||||
var pkg = nodeRequire('./package.json')
|
||||
var wait_for = []
|
||||
// XXX
|
||||
var interactive = false
|
||||
|
||||
// XXX SETUP need to setup everything that has command-line features...
|
||||
//this.setupFeatures()
|
||||
|
||||
// revise name...
|
||||
argv.Parser({
|
||||
context: this,
|
||||
|
||||
// XXX argv.js is not picking these up because
|
||||
// of the require(..) mixup...
|
||||
author: pkg.author,
|
||||
version: pkg.version,
|
||||
license: pkg.license,
|
||||
|
||||
// examples...
|
||||
examples: CLIActions.cliExamples ?
|
||||
CLIActions.cliExamples.flat()
|
||||
: null,
|
||||
|
||||
'-verbose': {
|
||||
doc: 'enable (very) verbose output',
|
||||
handler: function(){
|
||||
that.logger
|
||||
&& (that.logger.quiet = false) } },
|
||||
// XXX merge this with -quiet...
|
||||
'-no-progress': {
|
||||
doc: 'disable progress bar display',
|
||||
handler: function(){
|
||||
that.__progress = false } },
|
||||
|
||||
// XXX setup presets...
|
||||
// ...load sets of features and allow user
|
||||
// to block/add specific features...
|
||||
|
||||
// XXX config editor...
|
||||
// ...get/set persistent config values...
|
||||
|
||||
// build the action command list...
|
||||
...this.cliActions
|
||||
.reduce(function(res, action){
|
||||
var cmd = that.getActionAttr(action, 'cli')
|
||||
if(typeof(cmd) == typeof('str') || cmd === true){
|
||||
var name = cmd
|
||||
var cmd = {name} }
|
||||
var name = name === true ?
|
||||
action
|
||||
: (cmd.key || cmd.name)
|
||||
|
||||
// skip interactive commands in non-interactive
|
||||
// contexts...
|
||||
if(!interactive && cmd.interactive){
|
||||
return res }
|
||||
|
||||
res[name] = cmd instanceof argv.Parser ?
|
||||
// parser...
|
||||
cmd
|
||||
.then(function(unhandled, value, rest){
|
||||
wait_for.push(that[action](value, this)) })
|
||||
// single option definition...
|
||||
: {
|
||||
doc: (that.getActionAttr(action, 'doc') || '')
|
||||
.split(/[\\\/]/g).pop(),
|
||||
handler: function(rest, key, value){
|
||||
var res = that[action](value)
|
||||
wait_for.push(res)
|
||||
return res },
|
||||
...cmd,
|
||||
}
|
||||
|
||||
return res }, {}),
|
||||
})
|
||||
.onArgs(function(){
|
||||
that.argv = this })
|
||||
.onNoArgs(function(args){
|
||||
console.log('No args.')
|
||||
|
||||
// XXX we should either start the GUI here or print help...
|
||||
args.push('-h')
|
||||
//args.push('gui')
|
||||
})
|
||||
.stop(function(){ process.exit() })
|
||||
.error(function(){ process.exit() })
|
||||
.then(function(){
|
||||
// XXX
|
||||
})()
|
||||
|
||||
// XXX not all promises in the system resolve strictly
|
||||
// after all the work is done, some resolve before that
|
||||
// point and this calling process.exit() will interrupt
|
||||
// them...
|
||||
this.__keep_running
|
||||
|| this.afterAction(function(){ this.stop() }) }],
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */
|
||||
return module })
|
||||
3028
Viewer/features/collections.js
Executable file
3028
Viewer/features/collections.js
Executable file
File diff suppressed because it is too large
Load Diff
202
Viewer/features/comments.js
Executable file
202
Viewer/features/comments.js
Executable file
@ -0,0 +1,202 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
||||
(function(require){ var module={} // make module AMD/node compatible...
|
||||
/*********************************************************************/
|
||||
|
||||
if(typeof(process) != 'undefined'){
|
||||
var file = require('imagegrid/file')
|
||||
}
|
||||
|
||||
var actions = require('lib/actions')
|
||||
var features = require('lib/features')
|
||||
|
||||
var core = require('features/core')
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
// Comments...
|
||||
// XXX these are quite generic, might be a good idea to move them out of fs...
|
||||
|
||||
var CommentsActions = actions.Actions({
|
||||
// Format:
|
||||
// {
|
||||
// // raw loaded comments...
|
||||
// raw: {
|
||||
// <path>: <comments>,
|
||||
// ...
|
||||
// },
|
||||
//
|
||||
// <keyword>: <data>,
|
||||
// ...
|
||||
// }
|
||||
__comments: null,
|
||||
|
||||
get comments(){
|
||||
return this.__comments },
|
||||
set comments(value){
|
||||
this.__comments = value },
|
||||
})
|
||||
|
||||
var Comments =
|
||||
module.Comments = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
tag: 'comments',
|
||||
|
||||
actions: CommentsActions,
|
||||
|
||||
handlers: [
|
||||
// save/resore .comments
|
||||
['json',
|
||||
function(res){
|
||||
if(this.comments != null){
|
||||
res.comments = JSON.parse(JSON.stringify(this.comments)) } }],
|
||||
['load',
|
||||
function(_, data){
|
||||
if(data.comments != null){
|
||||
this.comments = data.comments } }],
|
||||
|
||||
// prepare comments for saving to "comments/<keyword>"...
|
||||
//
|
||||
// NOTE: this will skip the 'raw' comment field...
|
||||
// NOTE: we do not change the .json() format here, so we do not
|
||||
// need to do anything special to restore, i.e. no need for
|
||||
// doing anything on .prepareIndexForLoad(..)
|
||||
['prepareIndexForWrite',
|
||||
function(res){
|
||||
var changed = res.changes === true
|
||||
|| res.changes.comments
|
||||
|
||||
if(changed && res.raw.comments){
|
||||
var comments = res.raw.comments
|
||||
|
||||
Object.keys(comments)
|
||||
// skip the raw field...
|
||||
.filter(function(k){ return k != 'raw' })
|
||||
.forEach(function(k){
|
||||
res.index['comments/' + k] = comments[k] }) } }],
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// FS Comments...
|
||||
|
||||
// XXX split this to loader and writer???
|
||||
var FileSystemCommentsActions = actions.Actions({
|
||||
config: {
|
||||
// Comment loading delay...
|
||||
//
|
||||
// This helps prevent the comment loading process from delaying
|
||||
// showing the user the images...
|
||||
//
|
||||
// NOTE: to load without a delay set this to -1.
|
||||
'comments-delay-load': 100,
|
||||
},
|
||||
|
||||
/* XXX we do not actually need this...
|
||||
// XXX this will not save comments for merged indexes...
|
||||
saveComments: ['- File/',
|
||||
function(path, date, logger){
|
||||
if(this.location.load != 'loadIndex'
|
||||
|| this.location.loaded.length > 1){
|
||||
return
|
||||
}
|
||||
|
||||
logger = logger || this.logger
|
||||
logger = logger && logger.push('saveComments')
|
||||
|
||||
var path = this.location.path
|
||||
var comments_dir = this.config['index-dir'] +'/comments'
|
||||
var data = JSON.parse(JSON.stringify(this.comments))
|
||||
|
||||
// XXX
|
||||
return file.writeIndex(
|
||||
data,
|
||||
path +'/'+ comments_dir,
|
||||
date || Date.timeStamp(),
|
||||
this.config['index-filename-template'],
|
||||
logger)
|
||||
}],
|
||||
//*/
|
||||
loadComments: ['- File/',
|
||||
function(path, date, logger){
|
||||
if(this.location.load != 'loadIndex'){
|
||||
return }
|
||||
|
||||
logger = logger || this.logger
|
||||
logger = logger && logger.push('Load comments')
|
||||
|
||||
var that = this
|
||||
var loaded = this.location.loaded
|
||||
|
||||
// prepare empty comments...
|
||||
// XXX should we reset or just merge???
|
||||
this.comments = { raw: {} }
|
||||
|
||||
return Promise.all(loaded.map(function(path){
|
||||
var comments_dir = that.config['index-dir'] +'/comments'
|
||||
|
||||
return file.loadIndex(path +'/'+ comments_dir, false, date, logger)
|
||||
.then(function(res){
|
||||
var c = res[path +'/'+ comments_dir]
|
||||
|
||||
// no comments present...
|
||||
if(c == null){
|
||||
return res }
|
||||
|
||||
// if we have no sub-indexes just load the
|
||||
// comments as-is...
|
||||
if(loaded.length == 1){
|
||||
that.comments = JSON.parse(JSON.stringify(c))
|
||||
that.comments.raw = {path: c}
|
||||
|
||||
// sub-indexes -> let the client merge their stuff...
|
||||
} else {
|
||||
that.comments.raw[path] = c }
|
||||
|
||||
return res }) })) }],
|
||||
})
|
||||
|
||||
|
||||
var FileSystemComments =
|
||||
module.FileSystemComments = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
tag: 'fs-comments',
|
||||
depends: [
|
||||
'comments',
|
||||
'fs-loader',
|
||||
],
|
||||
|
||||
actions: FileSystemCommentsActions,
|
||||
|
||||
handlers: [
|
||||
['loadIndex',
|
||||
function(res){
|
||||
var that = this
|
||||
var delay = that.config['comments-delay-load']
|
||||
|
||||
res.then(
|
||||
function(){
|
||||
delay < 0 ?
|
||||
that.loadComments()
|
||||
: setTimeout(function(){
|
||||
that.loadComments() }, delay || 0) },
|
||||
function(){}) }],
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */ return module })
|
||||
249
Viewer/features/config.js
Executable file
249
Viewer/features/config.js
Executable file
@ -0,0 +1,249 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
* Features:
|
||||
* - config
|
||||
* general config API
|
||||
* - localstorage-config
|
||||
* maintain configuration state in localStorage
|
||||
* - fs-config
|
||||
* maintain configuration state in file system
|
||||
*
|
||||
* XXX this module need refactoring...
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
||||
(function(require){ var module={} // make module AMD/node compatible...
|
||||
/*********************************************************************/
|
||||
|
||||
var toggler = require('lib/toggler')
|
||||
var actions = require('lib/actions')
|
||||
var features = require('lib/features')
|
||||
|
||||
var core = require('features/core')
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
// XXX might be a good idea to add "sandbox" mode -- i.e. all settings
|
||||
// are saved to sessionStorage and a re-open will load the old settings...
|
||||
// XXX might be a good idea to add a .configLoaded(..) and .configChanged(..)
|
||||
// events thought it's not clear how are we going to track changes...
|
||||
var ConfigStoreActions = actions.Actions({
|
||||
config: {
|
||||
// XXX should this include path???
|
||||
// ...there should be modes:
|
||||
// - 'read-only' -- don't save...
|
||||
// - 'portable' -- use APP dir
|
||||
// - 'normal' -- use $HOME
|
||||
'config-fs-filename': '.ImageGrid.json',
|
||||
|
||||
'config-auto-save-interval': 1000*5,
|
||||
|
||||
'config-load-sequence': [
|
||||
// localStorage...
|
||||
'storage:${INSTANCE}/config',
|
||||
|
||||
// FS...
|
||||
// XXX should we load both or just one???
|
||||
'fileSync:${APP}/.ImageGrid.json',
|
||||
'fileSync:${HOME}/.ImageGrid.json',
|
||||
|
||||
// temporary config...
|
||||
// NOTE: this is active until we re-open the app...
|
||||
'session:${INSTANCE}/config',
|
||||
],
|
||||
},
|
||||
|
||||
__config_base: null,
|
||||
__config_loaded_from: null,
|
||||
|
||||
// XXX handle save order -- need to save to one location only...
|
||||
// ...use: .__config_loaded_from in reverse order (stop on session:..)
|
||||
// XXX keep record of what we loaded...
|
||||
// XXX should we only support sync stores??? (current state)
|
||||
loadConfig: ['File/Load configuration',
|
||||
core.doc`
|
||||
|
||||
NOTE: might need to reload after this.
|
||||
`,
|
||||
function(query){
|
||||
// store loaded...
|
||||
var loaded = this.__config_loaded_from = []
|
||||
|
||||
this.resetConfig()
|
||||
|
||||
// do the load...
|
||||
;(query ?
|
||||
(query instanceof Array ? query : [query])
|
||||
: (this.config['config-load-sequence'] || ['storage:config']))
|
||||
.forEach(function(query){
|
||||
query = this.parseStoreQuery(query)
|
||||
var cfg = this.loadStore(query)
|
||||
|
||||
// select store...
|
||||
cfg = query.store
|
||||
.map(function(store){ return cfg[store] })
|
||||
.filter(function(cfg){ return Object.keys(cfg).length > 0 })
|
||||
.shift() || {}
|
||||
// select key...
|
||||
cfg = query.key
|
||||
.map(function(key){ return cfg[key] })
|
||||
.filter(function(cfg){ return !!cfg })
|
||||
.shift()
|
||||
|
||||
// merge the config...
|
||||
cfg
|
||||
&& Object.assign(this.config, cfg)
|
||||
&& loaded.push(query.query)
|
||||
}.bind(this))
|
||||
}],
|
||||
storeConfig: ['File/Save configuration',
|
||||
function(query){
|
||||
// XXX
|
||||
this.saveStore(query || 'storage:${INSTANCE}/config')
|
||||
}],
|
||||
// XXX this needs to be confirmed...
|
||||
resetConfig: ['File/Reset configuration',
|
||||
core.doc`
|
||||
|
||||
NOTE: might need to reload after this.
|
||||
`,
|
||||
function(){
|
||||
var base = this.__config_base = this.__config_base || this.config
|
||||
this.config = Object.create(base)
|
||||
}],
|
||||
|
||||
// XXX use timer events... (???)
|
||||
toggleConfigAutoStore: ['File/Auto-save configuration',
|
||||
toggler.Toggler(null,
|
||||
function(_, state){
|
||||
var timer = 'config-auto-save-timer'
|
||||
|
||||
if(state == null){
|
||||
return this.isPersistentInterval(timer) || 'none'
|
||||
|
||||
} else {
|
||||
var that = this
|
||||
var interval = this.config['config-auto-save-interval']
|
||||
|
||||
// no timer interval set...
|
||||
if(!interval){
|
||||
return false
|
||||
}
|
||||
|
||||
// start/restart...
|
||||
if(state == 'running' && interval){
|
||||
this.setPersistentInterval(timer, 'storeConfig', interval*1000)
|
||||
|
||||
// stop...
|
||||
} else {
|
||||
this.clearPersistentInterval(timer)
|
||||
}
|
||||
}
|
||||
},
|
||||
'running')],
|
||||
|
||||
|
||||
// XXX does not work yet...
|
||||
toggleConfigSandbox: ['- File/',
|
||||
toggler.Toggler(null,
|
||||
function(_, state){
|
||||
|
||||
if(state == null){
|
||||
return Object.keys(this.store('session:${INSTANCE}/config').session).length > 0 || 'none'
|
||||
|
||||
} else if(state == 'sandboxed'){
|
||||
this.store('session:${INSTANCE}/config', undefined)
|
||||
|
||||
} else {
|
||||
this.storeConfig('session:${INSTANCE}/config')
|
||||
}
|
||||
},
|
||||
'sandboxed')],
|
||||
})
|
||||
|
||||
var ConfigStore =
|
||||
module.ConfigStore = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
tag: 'store-config',
|
||||
priority: 80,
|
||||
depends: [
|
||||
'timers',
|
||||
'store-localstorage',
|
||||
],
|
||||
suggested: [
|
||||
'store-fs-json-sync',
|
||||
],
|
||||
|
||||
actions: ConfigStoreActions,
|
||||
|
||||
handlers: [
|
||||
// XXX need to update rather than rewrite things...
|
||||
['prepareStoreToSave',
|
||||
function(res, query, data){
|
||||
var ls_path = '${INSTANCE}/config'
|
||||
//var ls_path = 'config'
|
||||
query = this.parseStoreQuery(query)
|
||||
|
||||
// config not requested...
|
||||
if(query.key != '*'
|
||||
&& query.key.indexOf('config')
|
||||
&& query.key.indexOf(ls_path)){
|
||||
return
|
||||
}
|
||||
|
||||
// localStorage...
|
||||
// NOTE: we do not need to clone anything here as this
|
||||
// will be done by the localStorage handler...
|
||||
if(query.store.indexOf('storage') >= 0){
|
||||
//res.data.storage[ls_path] = this.config
|
||||
res.data.storage[ls_path] = data || this.config
|
||||
}
|
||||
|
||||
if(query.store.indexOf('fileSync') >= 0){
|
||||
// XXX should this include path???
|
||||
//res.data.fileSync[this.config['config-fs-filename'] || '.ImageGrid.json'] = this.config
|
||||
res.data.fileSync[this.config['config-fs-filename'] || '.ImageGrid.json'] = data || this.config
|
||||
}
|
||||
}],
|
||||
//['prepareIndexForLoad',
|
||||
// function(){
|
||||
// }],
|
||||
// NOTE: this is sync for sync stores...
|
||||
['storeDataLoaded',
|
||||
function(_, store, data){
|
||||
var base = this.__config_base = this.__config_base || this.config
|
||||
var ls_path = '${INSTANCE}/config'
|
||||
|
||||
// XXX sort out load priority/logic...
|
||||
// - one or the other or both?
|
||||
// - what order?
|
||||
|
||||
if((data.storage || {})[ls_path]){
|
||||
var config = data.storage[ls_path] || {}
|
||||
config.__proto__ = base
|
||||
this.config = config
|
||||
}
|
||||
|
||||
if((data.fsJSONSync || {}).config){
|
||||
var config = data.fsJSONSync.config || {}
|
||||
config.__proto__ = base
|
||||
this.config = config
|
||||
}
|
||||
|
||||
// auto-start auto-save...
|
||||
this.config['config-auto-save-interval'] > 0
|
||||
&& this.toggleConfigAutoStore('?') == 'off'
|
||||
&& this.toggleConfigAutoStore()
|
||||
}],
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */ return module })
|
||||
3603
Viewer/features/core.js
Executable file
3603
Viewer/features/core.js
Executable file
File diff suppressed because it is too large
Load Diff
@ -1,86 +1,119 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
define(function(require){ var module = {}
|
||||
|
||||
//var DEBUG = DEBUG != null ? DEBUG : true
|
||||
|
||||
var actions = require('lib/actions')
|
||||
var features = require('lib/features')
|
||||
|
||||
var data = require('data')
|
||||
var images = require('images')
|
||||
|
||||
var core = require('features/core')
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
var demo_data =
|
||||
module.demo_data = {
|
||||
varsion: '3.0',
|
||||
|
||||
current: '3',
|
||||
base: 'r0',
|
||||
|
||||
order: [],
|
||||
|
||||
ribbon_order: ['r0', 'r1'],
|
||||
ribbons: {
|
||||
r1: ['1', '2', '3'],
|
||||
r0: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
|
||||
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
|
||||
'y', 'z']
|
||||
},
|
||||
|
||||
tags: {
|
||||
selected: ['b', 'z'],
|
||||
bookmark: ['1', 'c', 'z'],
|
||||
},
|
||||
|
||||
// NOTE: group gids do not have to be present in .order, they will
|
||||
// get added on .collapseGroup(..)...
|
||||
groups: {
|
||||
g0: ['a', 'b', 'c'],
|
||||
g1: ['l', 'y'],
|
||||
},
|
||||
}
|
||||
Object.keys(demo_data.ribbons).forEach(function(k){
|
||||
demo_data.order = demo_data.order.concat(demo_data.ribbons[k])
|
||||
})
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
var Demo =
|
||||
module.Demo = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
tag: 'demo',
|
||||
depends: [
|
||||
'base',
|
||||
],
|
||||
|
||||
actions: actions.Actions({
|
||||
loadDemoIndex: ['File/Load demo data',
|
||||
function(){
|
||||
this.load({
|
||||
data: data.Data(demo_data),
|
||||
images: images.Images(),
|
||||
})
|
||||
}],
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */
|
||||
return module })
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
||||
(function(require){ var module={} // make module AMD/node compatible...
|
||||
/*********************************************************************/
|
||||
|
||||
var actions = require('lib/actions')
|
||||
var features = require('lib/features')
|
||||
|
||||
var data = require('imagegrid/data')
|
||||
var images = require('imagegrid/images')
|
||||
|
||||
var core = require('features/core')
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
var demo_data =
|
||||
module.demo_data = {
|
||||
version: '3.1',
|
||||
|
||||
current: '3',
|
||||
base: 'r0',
|
||||
|
||||
order: [],
|
||||
|
||||
ribbon_order: ['r0', 'r1'],
|
||||
ribbons: {
|
||||
r1: ['1', '2', '3'],
|
||||
r0: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
|
||||
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
|
||||
'y', 'z']
|
||||
},
|
||||
|
||||
tags: {
|
||||
tags: {
|
||||
marked: ['b', 'z'],
|
||||
bookmark: ['1', 'c', 'z'],
|
||||
},
|
||||
},
|
||||
|
||||
// NOTE: group gids do not have to be present in .order, they will
|
||||
// get added on .collapseGroup(..)...
|
||||
groups: {
|
||||
g0: ['a', 'b', 'c'],
|
||||
g1: ['l', 'y'],
|
||||
},
|
||||
}
|
||||
Object.keys(demo_data.ribbons).forEach(function(k){
|
||||
demo_data.order = demo_data.order.concat(demo_data.ribbons[k])
|
||||
})
|
||||
|
||||
var demo_images =
|
||||
module.demo_images = {
|
||||
a: {
|
||||
orientation: 90,
|
||||
tags: ['test'],
|
||||
},
|
||||
d: {
|
||||
tags: ['test', 'bookmark']
|
||||
},
|
||||
f: {
|
||||
orientation: 270,
|
||||
},
|
||||
2: {
|
||||
orientation: 270,
|
||||
},
|
||||
z: {
|
||||
flipped: ['horizontal'],
|
||||
},
|
||||
}
|
||||
|
||||
// sync tags with images...
|
||||
//demo_data = data.Data(demo_data)
|
||||
// .tagsToImages(demo_images, 'merge')
|
||||
// .tagsFromImages(demo_images, 'merge')
|
||||
// .json()
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
var Demo =
|
||||
module.Demo = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
tag: 'demo',
|
||||
depends: [
|
||||
'base',
|
||||
],
|
||||
|
||||
actions: actions.Actions({
|
||||
loadDemoIndex: ['File/Load demo data',
|
||||
{mode: 'advancedBrowseModeAction'},
|
||||
function(){
|
||||
this.load({
|
||||
data: data.Data(demo_data),
|
||||
//images: images.Images(),
|
||||
images: images.Images(demo_images),
|
||||
|
||||
location: {
|
||||
path: 'Demo data',
|
||||
load: 'loadDemoIndex',
|
||||
sync: 'loadDemoIndex',
|
||||
check: true,
|
||||
} }) }],
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */ return module })
|
||||
113
Viewer/features/diff.js
Executable file
113
Viewer/features/diff.js
Executable file
@ -0,0 +1,113 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
||||
(function(require){ var module={} // make module AMD/node compatible...
|
||||
/*********************************************************************/
|
||||
|
||||
// XXX get actual diff path...
|
||||
var diff = require('lib/diff')
|
||||
|
||||
var actions = require('lib/actions')
|
||||
var features = require('lib/features')
|
||||
|
||||
var core = require('features/core')
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
// XXX get diff and save new reference state...
|
||||
var save = function(){
|
||||
var res = this.diff()
|
||||
this.__reference_state = this.josn()
|
||||
return res
|
||||
}
|
||||
|
||||
var load = function(diff){
|
||||
// XXX
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
// XXX we need a separate feature for fs stuff...
|
||||
// XXX format:
|
||||
// - we should separate:
|
||||
// .data
|
||||
// .images
|
||||
// .collections
|
||||
// ...
|
||||
// should we use the same mechanics as the original save/load???
|
||||
|
||||
var DiffActions = actions.Actions({
|
||||
// XXX this must be read-only...
|
||||
__reference_state: null,
|
||||
__changes_stack: null,
|
||||
|
||||
diff: [`- System/`,
|
||||
// XXX should this get separated diffs or one big diff???
|
||||
// ...i.e. get and arg to filter sections???
|
||||
function(){
|
||||
return diff.Diff(this.__reference_state, this.json()) }],
|
||||
|
||||
// XXX EXPERIMENTAL -- this can be another way to implement undo or
|
||||
// a way to do "pre-save manual undo points"...
|
||||
// ...the problem with undo is that .popChange(..) does not
|
||||
// care about what changed and how and will simply reload the
|
||||
// whole state...
|
||||
// XXX do we need these???
|
||||
pushChange: ['- System/',
|
||||
core.doc`Create a save point
|
||||
`,
|
||||
function(){
|
||||
var stack = this.__changes_stack = this.__changes_stack || []
|
||||
stack.push(save.call(this))
|
||||
}],
|
||||
popChange: ['- System/',
|
||||
core.doc`Restore to a save point
|
||||
`,
|
||||
function(n){
|
||||
var stack = this.__changes_stack || []
|
||||
n = n == null ?
|
||||
0
|
||||
: (n == 'all' || n == '*') ?
|
||||
stack.length
|
||||
: n
|
||||
var state = this.josn()
|
||||
stack
|
||||
&& stack
|
||||
.splice(-n-1)
|
||||
.forEach(function(d){
|
||||
state = d.unpatch(state) })
|
||||
// XXX is this the correct way to go???
|
||||
this.load(state)
|
||||
}],
|
||||
|
||||
// XXX hook into save/load workflows...
|
||||
// XXX
|
||||
})
|
||||
|
||||
var Diff =
|
||||
module.Diff = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
// XXX
|
||||
tag: 'diff',
|
||||
depends: [
|
||||
// XXX
|
||||
],
|
||||
|
||||
actions: FeatureActions,
|
||||
|
||||
handlers: [],
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */ return module })
|
||||
1117
Viewer/features/examples.js
Executable file
1117
Viewer/features/examples.js
Executable file
File diff suppressed because it is too large
Load Diff
132
Viewer/features/experimental.js
Executable file
132
Viewer/features/experimental.js
Executable file
@ -0,0 +1,132 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
||||
(function(require){ var module={} // make module AMD/node compatible...
|
||||
/*********************************************************************/
|
||||
|
||||
var toggler = require('lib/toggler')
|
||||
var actions = require('lib/actions')
|
||||
var features = require('lib/features')
|
||||
|
||||
var core = require('features/core')
|
||||
var widgets = require('features/ui-widgets')
|
||||
|
||||
var widget = require('lib/widget/widget')
|
||||
var browse = require('lib/widget/browse')
|
||||
var overlay = require('lib/widget/overlay')
|
||||
var drawer = require('lib/widget/drawer')
|
||||
|
||||
var browseWalk = require('lib/widget/browse-walk')
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
var ExperimentActions = actions.Actions({
|
||||
// NOTE: we do not want to pick and chose changes as that might lead
|
||||
// to inconsistencies...
|
||||
// ...a better way would be to go would be to:
|
||||
// - reset
|
||||
// - undo / redo
|
||||
// XXX depends on ui, ...
|
||||
// XXX would be nice to have a universal .save() action...
|
||||
// XXX can we make this into a dialog that would stop a certain
|
||||
// action until the user confirms or aborts... e.g. warn of
|
||||
// unsaved changes when navigating away...
|
||||
// XXX move this to ui-widgets...
|
||||
browseChanges: ['Experimental/$Changes...',
|
||||
core.doc`
|
||||
`,
|
||||
{dialogTitle: 'Unsaved changes'},
|
||||
widgets.makeUIDialog(function(path){
|
||||
var that = this
|
||||
var comment
|
||||
var handlers_setup = false
|
||||
return browse.makeLister(null, function(_, make){
|
||||
var keys = Object.keys(that.changes || {})
|
||||
if(keys.length == 0){
|
||||
make.Empty('No changes...')
|
||||
|
||||
// list changes...
|
||||
} else {
|
||||
keys
|
||||
.forEach(function(key){
|
||||
var opts = {}
|
||||
if(that.changes[key] instanceof Array){
|
||||
opts.attrs = {
|
||||
count: that.changes[key].length } }
|
||||
// XXX show human-readable info... (???)
|
||||
make(key, opts) })
|
||||
|
||||
// save comment...
|
||||
if(that.getSaveComment){
|
||||
make('---')
|
||||
comment = comment
|
||||
|| that.getSaveComment()
|
||||
// XXX this behaves in an odd manner...
|
||||
make.Editable(['$Comment: ', comment],
|
||||
{
|
||||
multiline: true,
|
||||
abort_keys: [
|
||||
'Esc',
|
||||
],
|
||||
})
|
||||
.on('edit-commit', function(_, text){
|
||||
that.setSaveComment(
|
||||
$(this)
|
||||
.find('.text')
|
||||
.last()
|
||||
.text()) })
|
||||
.on('edit-abort', function(){
|
||||
$(this)
|
||||
.find('.text')
|
||||
.last()
|
||||
.text(comment) }) }
|
||||
|
||||
make('---')
|
||||
make('Save', {
|
||||
open: function(){
|
||||
that.setSaveComment
|
||||
&& that.setSaveComment(comment)
|
||||
that.saveIndexHere
|
||||
&& that.saveIndexHere() },
|
||||
close: function(){
|
||||
that.off('markChanged', 'changes-dialog-updater') },
|
||||
})
|
||||
|
||||
if(!handlers_setup){
|
||||
// XXX need to clean this up in a better way...
|
||||
// XXX this should also track .changes...
|
||||
that.on('markChanged', 'changes-dialog-updater', function(){
|
||||
make.dialog.update() })
|
||||
handlers_setup = true } } }) })],
|
||||
|
||||
unsavedChanges: ['- Experimental/',
|
||||
function(){
|
||||
}],
|
||||
|
||||
|
||||
})
|
||||
|
||||
var ExperimentFeature =
|
||||
module.ExperimentFeature = core.ImageGridFeatures.Feature({
|
||||
title: '',
|
||||
doc: '',
|
||||
|
||||
tag: 'experiments',
|
||||
|
||||
//isApplicable: function(actions){ return actions.experimental },
|
||||
|
||||
actions: ExperimentActions,
|
||||
|
||||
handlers: [
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */ return module })
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user