Compare commits
714 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e9c0121a18 | |||
| 01aa425f81 | |||
| 38d5c7dff6 | |||
| e3b4b9b618 | |||
| a5951c58f3 | |||
| 504d6eaa9f | |||
| 6253fa30ef | |||
| 47f7cef4f4 | |||
| 72bba06e71 | |||
| 9b92c5ce38 | |||
| dfa077a1c9 | |||
| 18fb2e7d4d | |||
| a610fd53e2 | |||
| 16abce1f2d | |||
| f3b42f34a6 | |||
| 6483d324de | |||
| 5ab97050dd | |||
| 17eed70903 | |||
| 88067c03b7 | |||
| 7c1e5b913f | |||
| 0014235e91 | |||
| a39b7be1d1 | |||
| de98c5f706 | |||
| 10b496e845 | |||
| bbe7bf390d | |||
| 4777b3400a | |||
| acaa70e944 | |||
| 4049d694f7 | |||
| e155a3dacf | |||
| a224e4c4d8 | |||
| edaeda5424 | |||
| 09d974913d | |||
| f82edb290a | |||
| 3d8b33ae94 | |||
| 565ecbd436 | |||
| 3359dfcc29 | |||
| 1c2afd14dd | |||
| fe5343c1d6 | |||
| 08cfefc02a | |||
| f6d9332c48 | |||
| cc6913c854 | |||
| 8c75fbd0a4 | |||
| 0de6d62409 | |||
| 5ba7ce5b7c | |||
| e106d30852 | |||
| 30affc884b | |||
| 745717ea49 | |||
| 4efd98b758 | |||
| 36640e3710 | |||
| 311c4fd29d | |||
| f50374f983 | |||
| 82ceb7f021 | |||
| 0aba3bc1d8 | |||
| f6c984ff3c | |||
| 4091ab6b6c | |||
| fb9fd5b51a | |||
| 9389700a01 | |||
| 016c1b2233 | |||
| 38b8a08297 | |||
| c9ffd3ad99 | |||
| 61f960de28 | |||
| da1ff2cacc | |||
| 05036c682f | |||
| 7d47bc8042 | |||
| 98cfd160ef | |||
| b5e3262b67 | |||
| 009fb35c4c | |||
| 8648d3131a | |||
| 00c316c35d | |||
| 5f8de8e756 | |||
| ee5dc8fc41 | |||
| a61926988a | |||
| bd8c4dfb6b | |||
| ce9b4bc4dd | |||
| 8b12b00114 | |||
| 1775cc1d54 | |||
| e4bd09df24 | |||
| 5e8c7da4df | |||
| c85592eefe | |||
| 05861c9113 | |||
| 3508d1e315 | |||
| e3177b8054 | |||
| 03e3760152 | |||
| 4740610923 | |||
| e28a0cde55 | |||
| 5b855fd835 | |||
| a2f5704581 | |||
| f7aa9424db | |||
| aa8b47a3dd | |||
| 11911c1898 | |||
| 4814c1971d | |||
| be9569f3fb | |||
| 900e72f95a | |||
| d2827f188b | |||
| cf9903b759 | |||
| 23f96461f4 | |||
| 9f2fd26e98 | |||
| 78d837c080 | |||
| 241b9312b7 | |||
| ed70ad7378 | |||
| 00213176d8 | |||
| 406650a45a | |||
| 56750ccf3c | |||
| dfc286b393 | |||
| 49a66f72fc | |||
| 3f237689da | |||
| cf1fb483b3 | |||
| b10f5e3f67 | |||
| c4fc24c513 | |||
| 3ac9c2d95e | |||
| e5ab4dafc0 | |||
| 10ae1911c3 | |||
| 73ebcdf0d6 | |||
| 5347523921 | |||
| 7ef70b953b | |||
| ccaca524fe | |||
| dd51f91cab | |||
| 537d98b41b | |||
| 9c4cadfc04 | |||
| 2001370441 | |||
| cc87b22757 | |||
| c0a65b30ad | |||
| c07e66c086 | |||
| a0cfc1be2b | |||
| 1505454793 | |||
| e1dff66283 | |||
| 5be801a086 | |||
| 94d4b05c29 | |||
| cebb889f7e | |||
| c4ed6ed034 | |||
| ec960bfefa | |||
| 79f689dde1 | |||
| 3b3654df56 | |||
| c66f008f07 | |||
| 37d9498d90 | |||
| 1ff67093db | |||
| daed37ccb8 | |||
| d41d807b4f | |||
| d6fa5c8a55 | |||
| 2dd608dfed | |||
| a98546f605 | |||
| 3567559d4e | |||
| 216476ee45 | |||
| 3fc28c07fb | |||
| 85f6ef063d | |||
| 1e71e2d68f | |||
| b24a29895f | |||
| 0167a2ae59 | |||
| 2c867103ca | |||
| 8c289df336 | |||
| 4489920cbf | |||
| 029a85081b | |||
| 1bc739d07f | |||
| c229e218f6 | |||
| a66f4ad4bd | |||
| 1dd687dab7 | |||
| 50ff6e2745 | |||
| 811dec713b | |||
| 617d6f4bd7 | |||
| 57cd2d6252 | |||
| ec64f8d048 | |||
| ed288a9dba | |||
| 27da0a4102 | |||
| 3c01ba1a76 | |||
| 252c8833ae | |||
| f45fb6efe6 | |||
| 8cc1f8d691 | |||
| bff22b5182 | |||
| d31d47eb32 | |||
| 5fe984c39d | |||
| 7f07b0daa7 | |||
| 5de9757d46 | |||
| f89276d7b8 | |||
| 30ba034206 | |||
| fa1e5aaa7f | |||
| 870c70180f | |||
| 6d35c26b3f | |||
| be4e693a27 | |||
| 5810276156 | |||
| d10ac3f87b | |||
| 9810bc09e9 | |||
| a0a13eb2a8 | |||
| 6e996797b8 | |||
| 663092b501 | |||
| 8ea13544de | |||
| e73daa6214 | |||
| d83a833b4d | |||
| ec3a2f29f0 | |||
| cf92c60a01 | |||
| b7f51b03bc | |||
| 903e69ff77 | |||
| c4167ddaad | |||
| 50bfdb0d68 | |||
| a6cb09ff1c | |||
| e4c9f23476 | |||
| 44e5415d43 | |||
| 1c653693ed | |||
| 39c470ad7a | |||
| 1103e538a5 | |||
| c0cd4cba6f | |||
| b91120e8d4 | |||
| 005774a4c2 | |||
| 16bbfebfba | |||
| 15505cd82d | |||
| 016d80e002 | |||
| 0f3c267a48 | |||
| 589bb02411 | |||
| c0f4ece17b | |||
| c3ae3cb768 | |||
| c9e0f9d985 | |||
| e3431c2fa3 | |||
| 5979b9771e | |||
| aa61832fb2 | |||
| 2ac6e982b1 | |||
| 3204ddcf07 | |||
| c87b1c133c | |||
| 9b275ecdae | |||
| d6fd7de361 | |||
| 49d66a133e | |||
| c559f26d0e | |||
| bbe9f1bad2 | |||
| 7e1fb6472d | |||
| 0ff8d20573 | |||
| 9c1f9448dc | |||
| 43a6081dd6 | |||
| 985e961876 | |||
| 098f6de047 | |||
| 1b0f90fd68 | |||
| 12252f407b | |||
| 3b6e3f47ab | |||
| 6a9ac9b025 | |||
| ae6aa4088b | |||
| c08f431180 | |||
| 123c1f56e9 | |||
| 35ac65a864 | |||
| e9f362cc87 | |||
| 65685c23e1 | |||
| 2f74748cea | |||
| f477bd66f3 | |||
| d7d77ae8f0 | |||
| 31110a740d | |||
| b64d8b1d7f | |||
| c46006aacc | |||
| 92f81b1493 | |||
| 70213cfc8f | |||
| 8a82bf5c50 | |||
| 37405384a2 | |||
| 54ea6cc53b | |||
| 339c00d815 | |||
| ea6b4dcde2 | |||
| 2b84623d1e | |||
| c8b3afa56b | |||
| 1348f3c24c | |||
| 62208ce3e1 | |||
| 813b2481de | |||
| 27b924ba61 | |||
| b40170b8ce | |||
| 8bfa9d2734 | |||
| c7cf76d4a8 | |||
| dfd2969b3e | |||
| 0e1866fe1d | |||
| b9ae46b913 | |||
| 06e7284055 | |||
| 93289e8fca | |||
| 130d5057d4 | |||
| be492d5084 | |||
| e0bf1d736f | |||
| 5a6b71cbeb | |||
| e6934cd5e2 | |||
| b5aada0792 | |||
| 165ea83ac3 | |||
| 440a82dee4 | |||
| 9c2d3e5e26 | |||
| 6fb6abcbe5 | |||
| dc449dafd1 | |||
| ecdaeebbfb | |||
| fa958b59bd | |||
| fb3d8521cd | |||
| 608c401cf3 | |||
| 1c3da90a24 | |||
| 34567f3375 | |||
| 51bcbeb48f | |||
| cc0f9c42df | |||
| a11bf5523b | |||
| 1921c3d901 | |||
| d568469e8b | |||
| 20d5c7e8d5 | |||
| 9f289ed9de | |||
| 93ee5e480b | |||
| 98a312701f | |||
| cbcf603b63 | |||
| f976f672cf | |||
| cfc3081e8a | |||
| 99818924ee | |||
| 9bbf3a027f | |||
| 93e01902e3 | |||
| 34919aba05 | |||
| a8ee4cf57f | |||
| b39548b4c6 | |||
| 4217c22ff6 | |||
| 4ab10670c9 | |||
| 2883f88de6 | |||
| e002a61a19 | |||
| 5893376279 | |||
| 411c5e4c4d | |||
| c2a77072d2 | |||
| c8a25934a6 | |||
| 7b38355cd4 | |||
| ddc54e0b98 | |||
| 8a7003782b | |||
| 8e6464dacb | |||
| 92b1dc0afb | |||
| 7562f51e07 | |||
| 09bba99e68 | |||
| 9d674cd49b | |||
| 88a0c9ea03 | |||
| 5014e0ce3e | |||
| b7a1ee9ebc | |||
| 292ceddd66 | |||
| 4b52b80000 | |||
| 9f20664c6e | |||
| 851a6aac0e | |||
| 1f1e73c47a | |||
| 112f61ca18 | |||
| 96eeacbe2b | |||
| 3f62da879c | |||
| aa30feb875 | |||
| 9ba3c2b7c5 | |||
| 320c708e10 | |||
| efa7294f59 | |||
| ae0e092935 | |||
| c77aecbfce | |||
| 700352ec45 | |||
| 664b125ce2 | |||
| 5f4b1c9e32 | |||
| d11d3f19bd | |||
| f34f4f2738 | |||
| 15db7c2310 | |||
| f9257ed04d | |||
| 15e6ef8488 | |||
| 9ae0a57f22 | |||
| 1e38c21f8e | |||
| bdc3c19163 | |||
| d55478da54 | |||
| 82bcc55645 | |||
| 07618ebe43 | |||
| 1492834d1e | |||
| 5ab6197356 | |||
| 0a789fe551 | |||
| caa8ff23ed | |||
| ee30d1d36d | |||
| 0d9415db9d | |||
| 8020e1126f | |||
| 3439422057 | |||
| 68d2bf736f | |||
| d78c39fd8c | |||
| b1dcad86b4 | |||
| 9b6124074d | |||
| 02cbaa1e80 | |||
| a12f1321c7 | |||
| 8b67f592ac | |||
| 319d17b337 | |||
| 476eaa85da | |||
| d26099855c | |||
| e47456da17 | |||
| a464d5d0b6 | |||
| 1cfb7b5615 | |||
| ac7c2f3d03 | |||
| 638d9e6e01 | |||
| 8b9df2a396 | |||
| d7fe911bde | |||
| 0acc3d511b | |||
| 4cf465f419 | |||
| b686d317a9 | |||
| dcef541852 | |||
| abdd733f11 | |||
| 942431e882 | |||
| 1c75ea046c | |||
| f32b6daa51 | |||
| 3736d6ba5e | |||
| 9788b01f35 | |||
| 9aec991da6 | |||
| 910701ce04 | |||
| 34b462d511 | |||
| 139e93b2f0 | |||
| 0dd7e9359e | |||
| 41cf0225e3 | |||
| 962254e511 | |||
| a7f2b24bac | |||
| 1323d988af | |||
| 7c49e5c749 | |||
| cd69ec4fa3 | |||
| 4c7e9fbee2 | |||
| 1639df5616 | |||
| 810cdbd790 | |||
| 0d4f4aec4e | |||
| 6b1863d3b4 | |||
| 27f5a3b16b | |||
| 876cd8291b | |||
| d0c46e4ef3 | |||
| feb8898ebf | |||
| 4fef8c5cfd | |||
| 7d56d8e35b | |||
| 5f1a3a9c8f | |||
| 0767b3156d | |||
| 9f16379b41 | |||
| be632aaf37 | |||
| 118c87faf7 | |||
| ec1e53d566 | |||
| 6a17ee414a | |||
| 6700686e4b | |||
| e8c34dd59b | |||
| 4c2da31bb3 | |||
| c0144b99bf | |||
| a0c32fc146 | |||
| a07b641adb | |||
| 0bb869fb33 | |||
| 72389e0129 | |||
| f49529fa70 | |||
| afcc34b5cc | |||
| 655b99cac8 | |||
| cc5091e28c | |||
| 1c72362c6b | |||
| 50ad5f681b | |||
| 50bf670931 | |||
| 7a8896864f | |||
| 51fbf148d9 | |||
| a9929438cd | |||
| 5a94b6b56c | |||
| 52cfbf60d4 | |||
| 29c10f8854 | |||
| ad761e388d | |||
| 07493ab0a6 | |||
| 7441011ae7 | |||
| d0818f456d | |||
| 06ea07a021 | |||
| 36d97ad5ca | |||
| a995eb2929 | |||
| c459a3033d | |||
| b4fbcf6bee | |||
| b9e679a514 | |||
| 64d73b93e4 | |||
| db70b05088 | |||
| 9428beeae5 | |||
| f9f7172702 | |||
| f1851b304c | |||
| d2ca6f1d46 | |||
| b27297cdc6 | |||
| 0d0edd7917 | |||
| fc6f12fb22 | |||
| d24096374f | |||
| 4f1d04009a | |||
| 79e9fde937 | |||
| 0ebaf6a171 | |||
| be2012f28d | |||
| ceefc8ffc6 | |||
| 0453b6903a | |||
| 691952249b | |||
| 0ceae2852e | |||
| 6d7ff38cf2 | |||
| 1b93ccf608 | |||
| 5b1ca3711a | |||
| 877f9299e1 | |||
| 677aca7a03 | |||
| 66b31a62d0 | |||
| 34923638c5 | |||
| bb61b3dc22 | |||
| 01ecae8979 | |||
| 53175c9ed7 | |||
| bc7a76755b | |||
| 92758f3e4e | |||
| b09767c526 | |||
| 2f93fd7c36 | |||
| 8acbcc548c | |||
| 19cf34f9d4 | |||
| 8c3f519016 | |||
| e63b42278c | |||
| 66ecd2fcf8 | |||
| f0d86f2392 | |||
| 5e39510f21 | |||
| 2cb4d65f3d | |||
| 15f2e05192 | |||
| a122333aaa | |||
| 06b2186bf9 | |||
| ed10dccfe2 | |||
| a1006dddb5 | |||
| 443a32dc81 | |||
| b034b4fe2f | |||
| 27b270148b | |||
| 269c64e4ed | |||
| eaf76e27f5 | |||
| 385b881068 | |||
| cf26696d12 | |||
| cb7ea40e7c | |||
| 5aaa55197e | |||
| d86d614520 | |||
| 138ca80c10 | |||
| d11a1622f8 | |||
| 42c996e16e | |||
| 1e37d75e49 | |||
| ad34d9d402 | |||
| 8c610f8a83 | |||
| f7f3e3cc03 | |||
| d68c6f9f2e | |||
| 90a5c4fbf8 | |||
| 042be6e229 | |||
| 4923c2e204 | |||
| b94d94e116 | |||
| d629ae8fbb | |||
| 1296a2e9ec | |||
| 009d02fa68 | |||
| 4cc57e9c91 | |||
| d373c6398e | |||
| 82746a0669 | |||
| 1212c3627b | |||
| 813f16ccee | |||
| 1c3cb91ecd | |||
| 5b1735db2b | |||
| bf31ee5fd6 | |||
| 1380b42c1d | |||
| dea853d840 | |||
| d72bf0739a | |||
| 481f5c0a97 | |||
| 2b017ac6b5 | |||
| 8a733ee337 | |||
| 9dd87a48a6 | |||
| 8fabbde13b | |||
| e0a378cb81 | |||
| 0b3329ca35 | |||
| 50c77b51db | |||
| c883ed19d6 | |||
| 795791219e | |||
| f6f4660cd2 | |||
| 9576f6e91e | |||
| f5e5bbefb2 | |||
| d4323fb5e0 | |||
| 8af1ddd10d | |||
| 62f6d9a413 | |||
| 783aed5826 | |||
| 141cd819a1 | |||
| 44fa96eb49 | |||
| 778b514b65 | |||
| afd58d69e4 | |||
| 4af9849b2b | |||
| 4dac44e720 | |||
| 71871901ef | |||
| d39e7584c0 | |||
| 4e9c5612ca | |||
| c8510dd45b | |||
| c234c17352 | |||
| cfae483d9d | |||
| d01ea13de4 | |||
| 9a73ee6952 | |||
| 28eb9e8c17 | |||
| 749c91f662 | |||
| 97ac17a12a | |||
| 32fd4fa8ed | |||
| 12fe4c6ba5 | |||
| b1e9fd95ca | |||
| d83043d8f2 | |||
| 2abeca6220 | |||
| 781810ed9c | |||
| 2142847de3 | |||
| ca42c266ef | |||
| f258ef1011 | |||
| 38cb763fd3 | |||
| 3fa78241ef | |||
| 3c7bc13be9 | |||
| 2441ca35b3 | |||
| 216a3977be | |||
| 647a51af15 | |||
| 530d4ce717 | |||
| e5d81b4d5c | |||
| 6eaeca1f3d | |||
| 4220034eab | |||
| 76a8ddd354 | |||
| 0bad38a815 | |||
| 48a8aad20e | |||
| 36ad0b3014 | |||
| 95fc103eaf | |||
| f5754780a8 | |||
| 7114c3bdf9 | |||
| 5b9d599e83 | |||
| ffa4871035 | |||
| 01832ac139 | |||
| cb7ff2bb37 | |||
| 35dd194b28 | |||
| 7dac857135 | |||
| 608ccafc70 | |||
| 4cdc9ef9b3 | |||
| db60d4e453 | |||
| f5d427a04f | |||
| e4893e446c | |||
| 79ffbf3d1d | |||
| 068bb07d6e | |||
| 1c9d118ba2 | |||
| 5308796bac | |||
| 669205aa4d | |||
| 9d2c2f7945 | |||
| e3b44b0adb | |||
| 1a303a9c38 | |||
| 2befa58fce | |||
| c8ded4ddb3 | |||
| 7d211f74d1 | |||
| 0f95d41785 | |||
| 6389b530d9 | |||
| 412769ff03 | |||
| d2349741f7 | |||
| 821bf8d63a | |||
| 7b296e4863 | |||
| 1acfcf088c | |||
| e9680afdff | |||
| 9695f12322 | |||
| 4060b7457b | |||
| a68344959d | |||
| 41638d10bf | |||
| 9b4e166608 | |||
| 52a65fcad1 | |||
| da7c114d41 | |||
| 62edee0860 | |||
| 22ba5e7c94 | |||
| 48e9a9c7dd | |||
| 9dbffb0c93 | |||
| f95eb0f1c9 | |||
| f3197c1af7 | |||
| 59f04c96c5 | |||
| bf2034b80c | |||
| deffaef2b5 | |||
| 157ec003b7 | |||
| ba4021ad73 | |||
| 5edb8111a2 | |||
| e206b40468 | |||
| 6ebd9320db | |||
| 597a750fff | |||
| 1273da6e71 | |||
| eb9b57eef4 | |||
| 5aaa05d579 | |||
| 07abb0840b | |||
| b8064510e3 | |||
| a88d84e6e6 | |||
| c3c8f16793 | |||
| ce1fed8c16 | |||
| 9a8f5edd58 | |||
| 992d45c8af | |||
| c646d6dc60 | |||
| 9067c28d24 | |||
| afacea3fbb | |||
| b68b016091 | |||
| f1f4ad2188 | |||
| d3d5e7f8d7 | |||
| 0f8d7d5fe2 | |||
| 63855e93a1 | |||
| 07c1f5ab76 | |||
| 4cd605fd34 | |||
| 8f5f28ede6 | |||
| bf169d6954 | |||
| 1934c4bfda | |||
| 5a7050df02 | |||
| 9871fdffc9 | |||
| 232276d106 | |||
| 8b08a82f07 | |||
| 180d044f5d | |||
| 5611459f03 | |||
| 6eb4b51168 | |||
| a145b35ad1 | |||
| d8a5b4a2e6 | |||
| 1f9d1542f1 | |||
| 4d23fdef61 | |||
| fb978211ae | |||
| 4fd67ebd99 | |||
| 0c899b2c16 | |||
| 1be22713f9 | |||
| ad51edbe07 | |||
| 91f2427b44 | |||
| fbbb0920c5 | |||
| 66bca200b4 | |||
| 5f11f15fe1 | |||
| 96a50dd09a | |||
| dcf605aa69 | |||
| 6c7bd2a63a | |||
| 85835ac1d3 | |||
| 61038b07f9 | |||
| 68700925b0 | |||
| 46f8e3bafd | |||
| 9077fee4d6 | |||
| 35fd5054aa | |||
| 350c835873 | |||
| 707ed7ec26 | |||
| e159f18bfc | |||
| 2308fa173a | |||
| 4a82ee0b05 | |||
| 6976f5af0f | |||
| 59cb524226 | |||
| 37e1d2ba5b | |||
| 6c21e970aa | |||
| d3a4e917fb | |||
| 2481878892 | |||
| 04359fbf31 | |||
| 80ea12ed48 | |||
| 9d2d4b7d5f | |||
| 3de2862655 | |||
| abb4def848 | |||
| 04decabc46 | |||
| 334ff52084 | |||
| a931ad7a1e | |||
| 069311dcf3 | |||
| b7e8cbea20 | |||
| 9905eff383 | |||
| 48320197f9 | |||
| 9c9a835f33 | |||
| ec477b916b | |||
| dc5a1fcb9a | |||
| 20a51f980b | |||
| 7604787fbb |
@@ -0,0 +1,296 @@
|
||||
# This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist
|
||||
#
|
||||
# Copyright 2022-2024, axodotdev
|
||||
# SPDX-License-Identifier: MIT or Apache-2.0
|
||||
#
|
||||
# CI that:
|
||||
#
|
||||
# * checks for a Git Tag that looks like a release
|
||||
# * builds artifacts with dist (archives, installers, hashes)
|
||||
# * uploads those artifacts to temporary workflow zip
|
||||
# * on success, uploads the artifacts to a GitHub Release
|
||||
#
|
||||
# Note that the GitHub Release will be created with a generated
|
||||
# title/body based on your changelogs.
|
||||
|
||||
name: Release
|
||||
permissions:
|
||||
"contents": "write"
|
||||
|
||||
# This task will run whenever you push a git tag that looks like a version
|
||||
# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc.
|
||||
# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where
|
||||
# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION
|
||||
# must be a Cargo-style SemVer Version (must have at least major.minor.patch).
|
||||
#
|
||||
# If PACKAGE_NAME is specified, then the announcement will be for that
|
||||
# package (erroring out if it doesn't have the given version or isn't dist-able).
|
||||
#
|
||||
# If PACKAGE_NAME isn't specified, then the announcement will be for all
|
||||
# (dist-able) packages in the workspace with that version (this mode is
|
||||
# intended for workspaces with only one dist-able package, or with all dist-able
|
||||
# packages versioned/released in lockstep).
|
||||
#
|
||||
# If you push multiple tags at once, separate instances of this workflow will
|
||||
# spin up, creating an independent announcement for each one. However, GitHub
|
||||
# will hard limit this to 3 tags per commit, as it will assume more tags is a
|
||||
# mistake.
|
||||
#
|
||||
# If there's a prerelease-style suffix to the version, then the release(s)
|
||||
# will be marked as a prerelease.
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
tags:
|
||||
- '**[0-9]+.[0-9]+.[0-9]+*'
|
||||
|
||||
jobs:
|
||||
# Run 'dist plan' (or host) to determine what tasks we need to do
|
||||
plan:
|
||||
runs-on: "ubuntu-22.04"
|
||||
outputs:
|
||||
val: ${{ steps.plan.outputs.manifest }}
|
||||
tag: ${{ !github.event.pull_request && github.ref_name || '' }}
|
||||
tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }}
|
||||
publishing: ${{ !github.event.pull_request }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Install dist
|
||||
# we specify bash to get pipefail; it guards against the `curl` command
|
||||
# failing. otherwise `sh` won't catch that `curl` returned non-0
|
||||
shell: bash
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.0/cargo-dist-installer.sh | sh"
|
||||
- name: Cache dist
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/dist
|
||||
# sure would be cool if github gave us proper conditionals...
|
||||
# so here's a doubly-nested ternary-via-truthiness to try to provide the best possible
|
||||
# functionality based on whether this is a pull_request, and whether it's from a fork.
|
||||
# (PRs run on the *source* but secrets are usually on the *target* -- that's *good*
|
||||
# but also really annoying to build CI around when it needs secrets to work right.)
|
||||
- id: plan
|
||||
run: |
|
||||
dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json
|
||||
echo "dist ran successfully"
|
||||
cat plan-dist-manifest.json
|
||||
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: "Upload dist-manifest.json"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifacts-plan-dist-manifest
|
||||
path: plan-dist-manifest.json
|
||||
|
||||
# Build and packages all the platform-specific things
|
||||
build-local-artifacts:
|
||||
name: build-local-artifacts (${{ join(matrix.targets, ', ') }})
|
||||
# Let the initial task tell us to not run (currently very blunt)
|
||||
needs:
|
||||
- plan
|
||||
if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# Target platforms/runners are computed by dist in create-release.
|
||||
# Each member of the matrix has the following arguments:
|
||||
#
|
||||
# - runner: the github runner
|
||||
# - dist-args: cli flags to pass to dist
|
||||
# - install-dist: expression to run to install dist on the runner
|
||||
#
|
||||
# Typically there will be:
|
||||
# - 1 "global" task that builds universal installers
|
||||
# - N "local" tasks that build each platform's binaries and platform-specific installers
|
||||
matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
container: ${{ matrix.container && matrix.container.image || null }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
|
||||
steps:
|
||||
- name: enable windows longpaths
|
||||
run: |
|
||||
git config --global core.longpaths true
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Install Rust non-interactively if not already installed
|
||||
if: ${{ matrix.container }}
|
||||
run: |
|
||||
if ! command -v cargo > /dev/null 2>&1; then
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
fi
|
||||
- name: Install dist
|
||||
run: ${{ matrix.install_dist.run }}
|
||||
# Get the dist-manifest
|
||||
- name: Fetch local artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: target/distrib/
|
||||
merge-multiple: true
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
${{ matrix.packages_install }}
|
||||
- name: Build artifacts
|
||||
run: |
|
||||
# Actually do builds and make zips and whatnot
|
||||
dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
|
||||
echo "dist ran successfully"
|
||||
- id: cargo-dist
|
||||
name: Post-build
|
||||
# We force bash here just because github makes it really hard to get values up
|
||||
# to "real" actions without writing to env-vars, and writing to env-vars has
|
||||
# inconsistent syntax between shell and powershell.
|
||||
shell: bash
|
||||
run: |
|
||||
# Parse out what we just built and upload it to scratch storage
|
||||
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
|
||||
dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT"
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||
- name: "Upload artifacts"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
|
||||
path: |
|
||||
${{ steps.cargo-dist.outputs.paths }}
|
||||
${{ env.BUILD_MANIFEST_NAME }}
|
||||
|
||||
# Build and package all the platform-agnostic(ish) things
|
||||
build-global-artifacts:
|
||||
needs:
|
||||
- plan
|
||||
- build-local-artifacts
|
||||
runs-on: "ubuntu-22.04"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Install cached dist
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/
|
||||
- run: chmod +x ~/.cargo/bin/dist
|
||||
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
||||
- name: Fetch local artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: target/distrib/
|
||||
merge-multiple: true
|
||||
- id: cargo-dist
|
||||
shell: bash
|
||||
run: |
|
||||
dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
|
||||
echo "dist ran successfully"
|
||||
|
||||
# Parse out what we just built and upload it to scratch storage
|
||||
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
|
||||
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||
- name: "Upload artifacts"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifacts-build-global
|
||||
path: |
|
||||
${{ steps.cargo-dist.outputs.paths }}
|
||||
${{ env.BUILD_MANIFEST_NAME }}
|
||||
# Determines if we should publish/announce
|
||||
host:
|
||||
needs:
|
||||
- plan
|
||||
- build-local-artifacts
|
||||
- build-global-artifacts
|
||||
# Only run if we're "publishing", and only if local and global didn't fail (skipped is fine)
|
||||
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
runs-on: "ubuntu-22.04"
|
||||
outputs:
|
||||
val: ${{ steps.host.outputs.manifest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Install cached dist
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/
|
||||
- run: chmod +x ~/.cargo/bin/dist
|
||||
# Fetch artifacts from scratch-storage
|
||||
- name: Fetch artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: target/distrib/
|
||||
merge-multiple: true
|
||||
- id: host
|
||||
shell: bash
|
||||
run: |
|
||||
dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
|
||||
echo "artifacts uploaded and released successfully"
|
||||
cat dist-manifest.json
|
||||
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: "Upload dist-manifest.json"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
# Overwrite the previous copy
|
||||
name: artifacts-dist-manifest
|
||||
path: dist-manifest.json
|
||||
# Create a GitHub Release while uploading all files to it
|
||||
- name: "Download GitHub Artifacts"
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: artifacts
|
||||
merge-multiple: true
|
||||
- name: Cleanup
|
||||
run: |
|
||||
# Remove the granular manifests
|
||||
rm -f artifacts/*-dist-manifest.json
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}"
|
||||
ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}"
|
||||
ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}"
|
||||
RELEASE_COMMIT: "${{ github.sha }}"
|
||||
run: |
|
||||
# Write and read notes from a file to avoid quoting breaking things
|
||||
echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt
|
||||
|
||||
gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/*
|
||||
|
||||
announce:
|
||||
needs:
|
||||
- plan
|
||||
- host
|
||||
# use "always() && ..." to allow us to wait for all publish jobs while
|
||||
# still allowing individual publish jobs to skip themselves (for prereleases).
|
||||
# "host" however must run to completion, no skipping allowed!
|
||||
if: ${{ always() && needs.host.result == 'success' }}
|
||||
runs-on: "ubuntu-22.04"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -1,7 +1,30 @@
|
||||
# Mac OS
|
||||
.DS_Store
|
||||
|
||||
/datasets
|
||||
/datasets2
|
||||
/datasets_*
|
||||
# Builds
|
||||
target
|
||||
websites/dist
|
||||
bridge/
|
||||
/ids.txt
|
||||
|
||||
TODO.md
|
||||
# Copies
|
||||
*\ copy*
|
||||
|
||||
# Ignored
|
||||
_*
|
||||
|
||||
# Logs
|
||||
.log
|
||||
|
||||
# Environment variables/configs
|
||||
.env
|
||||
|
||||
# Profiling
|
||||
profile.json.gz
|
||||
flamegraph.svg
|
||||
*.trace
|
||||
|
||||
# AI
|
||||
.claude
|
||||
CLAUDE.md
|
||||
CLAUDE*.md
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"file_scan_exclusions": [
|
||||
// default
|
||||
"**/.git",
|
||||
"**/.svn",
|
||||
"**/.hg",
|
||||
"**/.jj",
|
||||
"**/CVS",
|
||||
"**/.DS_Store",
|
||||
"**/Thumbs.db",
|
||||
"**/.classpath",
|
||||
"**/.settings",
|
||||
// custom
|
||||
"**/lean-qr/*/index.mjs",
|
||||
"uFuzzy.mjs",
|
||||
"lightweight-charts.standalone.production.mjs",
|
||||
"**/modern-screenshot/*/index.mjs",
|
||||
"**/solidjs-signals/*/dist/prod.js"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/*"]
|
||||
package.description = "The Bitcoin Research Kit is a suite of tools designed to extract, compute and display data stored on a Bitcoin Core node"
|
||||
package.license = "MIT"
|
||||
package.edition = "2024"
|
||||
package.version = "0.0.107"
|
||||
package.homepage = "https://bitcoinresearchkit.org"
|
||||
package.repository = "https://github.com/bitcoinresearchkit/brk"
|
||||
package.readme = "README.md"
|
||||
package.rust-version = "1.89"
|
||||
|
||||
[profile.dev]
|
||||
lto = "thin"
|
||||
debug = true
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
overflow-checks = true
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
strip = true
|
||||
overflow-checks = false
|
||||
|
||||
[profile.profiling]
|
||||
inherits = "release"
|
||||
debug = true
|
||||
|
||||
[profile.dist]
|
||||
inherits = "release"
|
||||
|
||||
[profile.clippy]
|
||||
inherits = "dev"
|
||||
lto = "off"
|
||||
codegen-units = 256
|
||||
opt-level = 0
|
||||
incremental = true
|
||||
debug = false
|
||||
overflow-checks = false
|
||||
panic = "abort"
|
||||
debug-assertions = false
|
||||
|
||||
[workspace.dependencies]
|
||||
allocative = { version = "0.3.4", features = ["parking_lot"] }
|
||||
allocative_derive = "0.3.3"
|
||||
axum = "0.8.4"
|
||||
bitcoin = { version = "0.32.7", features = ["serde"] }
|
||||
bitcoincore-rpc = "0.19.0"
|
||||
brk_bundler = { version = "0.0.107", path = "crates/brk_bundler" }
|
||||
brk_cli = { version = "0.0.107", path = "crates/brk_cli" }
|
||||
brk_computer = { version = "0.0.107", path = "crates/brk_computer" }
|
||||
brk_error = { version = "0.0.107", path = "crates/brk_error" }
|
||||
brk_fetcher = { version = "0.0.107", path = "crates/brk_fetcher" }
|
||||
brk_indexer = { version = "0.0.107", path = "crates/brk_indexer" }
|
||||
brk_interface = { version = "0.0.107", path = "crates/brk_interface" }
|
||||
brk_logger = { version = "0.0.107", path = "crates/brk_logger" }
|
||||
brk_mcp = { version = "0.0.107", path = "crates/brk_mcp" }
|
||||
brk_parser = { version = "0.0.107", path = "crates/brk_parser" }
|
||||
brk_server = { version = "0.0.107", path = "crates/brk_server" }
|
||||
brk_store = { version = "0.0.107", path = "crates/brk_store" }
|
||||
brk_structs = { version = "0.0.107", path = "crates/brk_structs" }
|
||||
byteview = "=0.6.1"
|
||||
derive_deref = "1.1.1"
|
||||
fjall = "2.11.2"
|
||||
jiff = "0.2.15"
|
||||
log = "0.4.28"
|
||||
minreq = { version = "2.14.1", features = ["https", "serde_json"] }
|
||||
parking_lot = "0.12.4"
|
||||
quick_cache = "0.6.16"
|
||||
rayon = "1.11.0"
|
||||
serde = "1.0.219"
|
||||
serde_bytes = "0.11.17"
|
||||
serde_derive = "1.0.219"
|
||||
serde_json = { version = "1.0.143", features = ["float_roundtrip"] }
|
||||
tokio = { version = "1.47.1", features = ["rt-multi-thread"] }
|
||||
# vecdb = { path = "../seqdb/crates/vecdb", features = ["derive"]}
|
||||
vecdb = { version = "0.2.14", features = ["derive"]}
|
||||
zerocopy = "0.8.27"
|
||||
zerocopy-derive = "0.8.27"
|
||||
|
||||
[workspace.metadata.release]
|
||||
shared-version = true
|
||||
tag-name = "v{{version}}"
|
||||
pre-release-commit-message = "release: v{{version}}"
|
||||
tag-message = "release: v{{version}}"
|
||||
|
||||
[workspace.metadata.dist]
|
||||
cargo-dist-version = "0.30.0"
|
||||
ci = "github"
|
||||
allow-dirty = ["ci"]
|
||||
installers = []
|
||||
targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu"]
|
||||
rust-toolchain-version = "1.89"
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Satonomics
|
||||
Copyright (c) 2025 bitcoinresearchkit, kibo.money, satonomics
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,59 +1,94 @@
|
||||
# SATONOMICS
|
||||
# Bitcoin Research Kit
|
||||
|
||||
## Description
|
||||
<p align="left">
|
||||
<a href="https://github.com/bitcoinresearchkit/brk">
|
||||
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/bitcoinresearchkit/brk?style=social">
|
||||
</a>
|
||||
<a href="https://github.com/bitcoinresearchkit/brk/blob/main/LICENSE.md">
|
||||
<img src="https://img.shields.io/crates/l/brk" alt="License" />
|
||||
</a>
|
||||
<a href="https://crates.io/crates/brk">
|
||||
<img src="https://img.shields.io/crates/v/brk" alt="Version" />
|
||||
</a>
|
||||
<a href="https://docs.rs/brk">
|
||||
<img src="https://img.shields.io/docsrs/brk" alt="Documentation" />
|
||||
</a>
|
||||
<img src="https://img.shields.io/crates/size/brk" alt="Size" />
|
||||
<a href="https://deps.rs/crate/brk">
|
||||
<img src="https://deps.rs/crate/brk/latest/status.svg" alt="Dependency status">
|
||||
</a>
|
||||
<a href="https://discord.gg/WACpShCB7M">
|
||||
<img src="https://img.shields.io/discord/1350431684562124850?label=discord" alt="Discord" />
|
||||
</a>
|
||||
<a href="https://primal.net/p/nprofile1qqsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03sqxu2e6">
|
||||
<img src="https://img.shields.io/badge/nostr-purple?link=https%3A%2F%2Fprimal.net%2Fp%2Fnprofile1qqsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03sqxu2e6" alt="Nostr" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
TLDR: Free, open source, verifiable and self-hostable Bitcoin on-chain data generator and visualizer
|
||||
The Bitcoin Research Kit is a high-performance toolchain designed to parse, index, compute, serve and visualize data from a Bitcoin node, enabling users to gain deeper insights into the Bitcoin network.
|
||||
|
||||
Satonomics is an open-source suite of tools that computes, distributes, and displays on-chain data, making it freely available for anyone to use.
|
||||
In other words it's an alternative to [Glassnode](https://glassnode.com), [mempool.space](https://mempool.space/) (soon) and [electrs](https://github.com/romanz/electrs) (soon) all in one package with a particular focus on simplicity and ease of use.
|
||||
|
||||
The generated datasets are incredibly diverse and can be used for a wide range of purposes. Whether you're looking to conduct a health check on the network, gain insights into its current or past state, or leverage the data for trading purposes, these tools offer various charts, dashboards (Soon TM), and an extensive API to help you achieve your goals.
|
||||
The toolkit can be used in various ways to accommodate as many needs as possible:
|
||||
|
||||
To promote transparency and trust in the network, this project is committed to making on-chain data accessible and verifiable to all, without discrimination and is a great complimentary tool to [mempool.space](https://mempool.space).
|
||||
- **[Website](https://bitview.space)** \
|
||||
Everyone is welcome to visit the official instance and showcase of the suite's capabilities. \
|
||||
It has a wide range of functionalities including charts, tables and simulations which you can visit for free and without the need for an account.
|
||||
- **[API](https://github.com/bitcoinresearchkit/brk/tree/main/crates/brk_server#brk-server)** \
|
||||
Researchers and developers are free to use BRK's public API with  dataset variants at their disposal. \
|
||||
Just like the website, it's entirely free, with no authentication or rate-limiting.
|
||||
- **[AI](https://github.com/bitcoinresearchkit/brk/blob/main/crates/brk_mcp/README.md#brk-mcp)** \
|
||||
LLMs have to possibility to connect to BRK's backend through a [MCP](https://modelcontextprotocol.io/introduction). \
|
||||
It will give them access to the same tools as the API, with no restrictions, and allow you to have your very own data analysts.
|
||||
- **[CLI](https://crates.io/crates/brk_cli)** \
|
||||
Node runners are strongly encouraged to try out and self-host their own instance using BRK's command line interface. \
|
||||
The CLI has multiple cogs available for users to tweak to adapt to all situations with even the possibility for web developers to create their own custom website which could later on be added as an alternative front-end.
|
||||
- **[Crates](https://crates.io/crates/brk)** \
|
||||
Rust developers have access to a wide range crates, each built upon one another with its own specific purpose, enabling independent use and offering great flexibility.
|
||||
PRs are welcome, especially if their goal is to introduce additional datasets.
|
||||
|
||||
## Instances
|
||||
The primary goal of this project is to be fully-featured and accessible for everyone, regardless of their background or financial situation - whether that person is an enthusiast, researcher, miner, analyst, or simply curious.
|
||||
|
||||
Web App:
|
||||
- [app.satonomics.xyz](https://app.satonomics.xyz)
|
||||
In contrast, existing alternatives tend to be either [very costly](https://studio.glassnode.com/pricing) or missing essential features, with the vast majority being closed-source and unverifiable, which fundamentally undermines the principles of Bitcoin.
|
||||
|
||||
API:
|
||||
- [api.satonomics.xyz](https://api.satonomics.xyz)
|
||||
## Hosting as a service
|
||||
|
||||
## Structure
|
||||
If you'd like to have your own instance hosted for you please contact [hosting@bitcoinresearchkit.org](mailto:hosting@bitcoinresearchkit.org).
|
||||
|
||||
- `parser`: The backbone of the project, it does most of the work by parsing and then computing datasets from the timechain.
|
||||
- `server`: A small server which automatically creates routes to access through an API all created datasets.
|
||||
- `app`: A web app which displays the generated datasets in various charts.
|
||||
- 2 separate dedicated servers (1 GB/s each) with different ISPs and Cloudflare integration for enhanced performance and optimal availability
|
||||
- 99.99% SLA
|
||||
- Configured for speed
|
||||
- Updates delivered at your convenience
|
||||
- Direct communication for feature requests and support
|
||||
- Bitcoin Core or Knots with desired version
|
||||
- Optional subdomains
|
||||
- Logo featured in the Readme if desired
|
||||
|
||||
## Git
|
||||
Pricing: `0.01 BTC / month` *or* `0.1 BTC / year`
|
||||
|
||||
- [Repository](https://codeberg.org/satonomics/satonomics)
|
||||
- [Issues](https://gitworkshop.dev/r/naddr1qq99xct5dahx7mtfvdesz9thwden5te0wp6hyurvv4ex2mrp0yhxxmmdqgsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03srqsqqqaueek2h03/issues)
|
||||
- [Proposals](https://gitworkshop.dev/r/naddr1qq99xct5dahx7mtfvdesz9thwden5te0wp6hyurvv4ex2mrp0yhxxmmdqgsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03srqsqqqaueek2h03/proposals)
|
||||
## Acknowledgments
|
||||
|
||||
## Goals
|
||||
Deepest gratitude to the [Open Sats](https://opensats.org/) public charity. Their grant — from December 2024 to the present — has been critical in sustaining this project.
|
||||
|
||||
- Be the absolute best on-chain data source and app
|
||||
- Have as many datasets and charts as possible
|
||||
- Be self-hostable on cheap computers
|
||||
- Be runnable on a machine with 8 GB RAM (16 GB RAM is already possible right now)
|
||||
- Still being runnable 10 years from now
|
||||
- By not relying on any third-party dependencies besides price APIs (which are and should be very common and easy to update)
|
||||
- By **NOT** doing address labelling/tagging (which means **NO** exchange or any other individual address tracking), for that please use [mempool.space](https://mempool.space) or any other tool
|
||||
Heartfelt thanks go out to every donor on [Nostr](https://primal.net/p/npub1jagmm3x39lmwfnrtvxcs9ac7g300y3dusv9lgzhk2e4x5frpxlrqa73v44) and [Geyser.fund](https://geyser.fund/project/brk) whose support has ensured the availability of the [bitcoinresearchkit.org](https://bitcoinresearchkit.org) public instance.
|
||||
|
||||
## Proof of Work
|
||||
## Crates
|
||||
|
||||
Aka: Previous iterations
|
||||
- [`brk`](https://crates.io/crates/brk): A wrapper around all other `brk-*` crates
|
||||
- [`brk_bundler`](https://crates.io/crates/brk_bundler): A thin wrapper around [`rolldown`](https://rolldown.rs/)
|
||||
- [`brk_cli`](https://crates.io/crates/brk_cli): A command line interface to run a BRK instance
|
||||
- [`brk_computer`](https://crates.io/crates/brk_computer): A Bitcoin dataset computer built on top of [`brk_indexer`](https://crates.io/crates/brk_indexer)
|
||||
- [`brk_error`](https://crates.io/crates/brk_error): Errors used throughout BRK
|
||||
- [`brk_fetcher`](https://crates.io/crates/brk_fetcher): A Bitcoin price fetcher
|
||||
- [`brk_indexer`](https://crates.io/crates/brk_indexer): A Bitcoin indexer built on top of [`brk_parser`](https://crates.io/crates/brk_parser)
|
||||
- [`brk_interface`](https://crates.io/crates/brk_interface): An interface to find and format data from BRK
|
||||
- [`brk_logger`](https://crates.io/crates/brk_logger): A thin wrapper around [`env_logger`](https://crates.io/crates/env_logger)
|
||||
- [`brk_mcp`](https://crates.io/crates/brk_mcp): A bridge for LLMs to access BRK
|
||||
- [`brk_parser`](https://crates.io/crates/brk_parser): A very fast Bitcoin block parser and iterator built on top of [`bitcoin-rust`](https://crates.io/crates/bitcoin)
|
||||
- [`brk_server`](https://crates.io/crates/brk_server): A server with an API for anything from BRK
|
||||
- [`brk_store`](https://crates.io/crates/brk_store): A thin wrapper around [`fjall`](https://crates.io/crates/fjall)
|
||||
- [`brk_structs`](https://crates.io/crates/brk_structs): Structs used throughout BRK
|
||||
|
||||
The initial idea was totally different yet morphed over time into what it is today: a fully FOSS self-hostable on-chain data generator
|
||||
## Donate
|
||||
|
||||
- https://github.com/drgarlic/satonomics
|
||||
- https://github.com/drgarlic/satonomics-parser
|
||||
- https://github.com/drgarlic/satonomics-explorer
|
||||
- https://github.com/drgarlic/satonomics-server
|
||||
- https://github.com/drgarlic/satonomics-app
|
||||
- https://github.com/drgarlic/bitalisys
|
||||
- https://github.com/drgarlic/bitesque-app
|
||||
- https://github.com/drgarlic/bitesque-back
|
||||
- https://github.com/drgarlic/bitesque-front
|
||||
- https://github.com/drgarlic/bitesque-assets
|
||||
- https://github.com/drgarlic/syf
|
||||
[`bc1q09 8zsm89 m7kgyz e338vf ejhpdt 92ua9p 3peuve`](bitcoin:bc1q098zsm89m7kgyze338vfejhpdt92ua9p3peuve)
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# TODO
|
||||
|
||||
- __crates__
|
||||
- _cli_
|
||||
- check disk space on first launch
|
||||
- add custom path support for config.toml
|
||||
- maybe add bitcoind download and launch support
|
||||
- via: https://github.com/rust-bitcoin/corepc/blob/master/node
|
||||
- test read/write speed, add warning if too low (<2gb/s)
|
||||
- pull latest version and notify is out of date
|
||||
- _computer_
|
||||
- **add rollback of states (in stateful)**
|
||||
- add costs basis by percentile (percentile cost basis) back
|
||||
- add support for per index computation
|
||||
- fix min fee_rate which is always ZERO due to coinbase transaction
|
||||
- before computing multiple sources check their length, panic if not equal
|
||||
- add oracle price dataset (https://utxo.live/oracle/UTXOracle.py)
|
||||
- add address counts relative to all datasets
|
||||
- add revived/sent supply datasets
|
||||
- add `in-sats` version of all price datasets (average and co)
|
||||
- add `p2pk` group (sum of `p2pk33` and `p2pk65`)
|
||||
- add chopiness datasets
|
||||
- add utxo count, address count, supply data for by reused addresses in groups by address type
|
||||
- add more date ranges (3-6 months and more)
|
||||
- add pi cycle dataset
|
||||
- add all possible charts from:
|
||||
- https://mainnet.observer
|
||||
- https://glassnode.com
|
||||
- https://checkonchain.com
|
||||
- https://researchbitcoin.net/exciting-update-coming-to-the-bitcoin-lab/
|
||||
- https://mempool.space/research
|
||||
- _indexer_
|
||||
- parse only the needed block number
|
||||
- maybe using https://developer.bitcoin.org/reference/rpc/getblockhash.html
|
||||
- _interface_
|
||||
- create pagination enum
|
||||
- from to
|
||||
- from option<count>
|
||||
- to option<count>
|
||||
- page + option<per page> default 1000 max 1000
|
||||
- from/to/count params don’t cap all combinations
|
||||
- example: from -10,000 count 10, won’t work if underlying vec isn’t 10k or more long
|
||||
- _parser_
|
||||
- save `vec` file instead of `json`
|
||||
- support lock file, process in read only if already opened in write mode
|
||||
- if less than X (10 maybe ?) get block using rpc instead of parsing the block files
|
||||
- _server_
|
||||
- api
|
||||
- add extensions support (.json .csv …)
|
||||
- if format instead of extension then don't download file
|
||||
- add support for https (rustls)
|
||||
- __docs__
|
||||
- _README_
|
||||
- add a comparison table with alternatives
|
||||
- add contribution section where help is needed
|
||||
- documentation/mcp/datasets/different front ends
|
||||
- add faq
|
||||
- __websites__
|
||||
- _default_
|
||||
- explorer
|
||||
- blocks
|
||||
- transactions
|
||||
- addresses
|
||||
- miners
|
||||
- maybe xpubs
|
||||
- charts
|
||||
- improve names and colors
|
||||
- selected unit sometimes changes when going back end forth
|
||||
- add support for custom charts
|
||||
- price scale format depends on unit, hide digits for sats for example (if/when possible)
|
||||
- table
|
||||
- pagination
|
||||
- exports (.json, .csv,…)
|
||||
- search
|
||||
- datasets add legend, and keywords ?
|
||||
- height/address/txid
|
||||
- api
|
||||
- add api page with interactivity
|
||||
- global
|
||||
- **fix navigation/history**
|
||||
- move share button to footer ?
|
||||
- Use `ichart.createPane()` in wrapper
|
||||
- improve behavior when local storage is unavailable
|
||||
- by having a global state
|
||||
- __global__
|
||||
- check `TODO`s in codebase
|
||||
@@ -1,9 +0,0 @@
|
||||
node_modules
|
||||
charts
|
||||
dist
|
||||
dev-dist
|
||||
.DS_Store
|
||||
visualizer
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
.wrangler
|
||||
@@ -1,17 +0,0 @@
|
||||
# Satonomics - App
|
||||
|
||||
## Description
|
||||
|
||||
A web app to view the generated datasets in various charts.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Install `node`
|
||||
- Install `pnpm`
|
||||
- If using `cloudflare`, add cache rule to bypass the cache for `/sw.js`
|
||||
|
||||
## Deployment
|
||||
|
||||
```bash
|
||||
pnpm deploy-prod
|
||||
```
|
||||
@@ -1 +0,0 @@
|
||||
/* /index.html
|
||||
@@ -1,372 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="overflow-hidden bg-black text-white">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Satonomics</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="An app to visualize Bitcoin on-chain data"
|
||||
/>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<meta name="theme-color" content="#0c0a09" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="196x196"
|
||||
href="/assets/favicon-196.png"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="/assets/apple-icon-180.png" />
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-2048-2732.jpg"
|
||||
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-2732-2048.jpg"
|
||||
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1668-2388.jpg"
|
||||
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-2388-1668.jpg"
|
||||
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1536-2048.jpg"
|
||||
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-2048-1536.jpg"
|
||||
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1488-2266.jpg"
|
||||
media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-2266-1488.jpg"
|
||||
media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1640-2360.jpg"
|
||||
media="(device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-2360-1640.jpg"
|
||||
media="(device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1668-2224.jpg"
|
||||
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-2224-1668.jpg"
|
||||
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1620-2160.jpg"
|
||||
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-2160-1620.jpg"
|
||||
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1290-2796.jpg"
|
||||
media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-2796-1290.jpg"
|
||||
media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1179-2556.jpg"
|
||||
media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-2556-1179.jpg"
|
||||
media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1284-2778.jpg"
|
||||
media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-2778-1284.jpg"
|
||||
media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1170-2532.jpg"
|
||||
media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-2532-1170.jpg"
|
||||
media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1125-2436.jpg"
|
||||
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-2436-1125.jpg"
|
||||
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1242-2688.jpg"
|
||||
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-2688-1242.jpg"
|
||||
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-828-1792.jpg"
|
||||
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1792-828.jpg"
|
||||
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1242-2208.jpg"
|
||||
media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-2208-1242.jpg"
|
||||
media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-750-1334.jpg"
|
||||
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1334-750.jpg"
|
||||
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-640-1136.jpg"
|
||||
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-1136-640.jpg"
|
||||
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-2048-2732.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-2732-2048.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1668-2388.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-2388-1668.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1536-2048.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-2048-1536.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1488-2266.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-2266-1488.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1640-2360.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-2360-1640.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1668-2224.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-2224-1668.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1620-2160.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-2160-1620.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1290-2796.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-2796-1290.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1179-2556.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-2556-1179.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1284-2778.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-2778-1284.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1170-2532.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-2532-1170.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1125-2436.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-2436-1125.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1242-2688.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-2688-1242.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-828-1792.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1792-828.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1242-2208.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-2208-1242.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-750-1334.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1334-750.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-640-1136.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
href="/assets/apple-splash-dark-1136-640.jpg"
|
||||
media="(prefers-color-scheme: dark) and (device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
</head>
|
||||
<body style="font-size: 15px; line-height: 22px">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="/src/index.tsx" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"name": "satonomics",
|
||||
"description": "Satoshi Economics",
|
||||
"version": "0.1.1",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "($npm_execpath outdated || read -p \"Press enter to ignore...\") && vite --host",
|
||||
"build": "vite build",
|
||||
"check": "tsc --noEmit --skipLibCheck --pretty",
|
||||
"check-watch": "$npm_execpath check --watch",
|
||||
"format": "prettier --write './src'",
|
||||
"prod": "$npm_execpath run build && vite preview --host",
|
||||
"pages-prod": "pnpm build && pnpm wrangler pages deploy ./dist",
|
||||
"pages-dev": "pnpm build && pnpm wrangler pages deploy --branch dev ./dist",
|
||||
"assets": "pnpm pwa-asset-generator ./public/logo/white.svg ./public/assets --index ./index.html --manifest ./public/manifest.webmanifest --icon-only --favicon --background \"linear-gradient(to right bottom, rgb(249, 115, 22), rgb(154, 52, 18))\" --padding \"min(15vh, 15vw)\" --path-override \"/assets\" && pnpm pwa-asset-generator ./public/logo/white.svg ./public/assets --index ./index.html --splash-only --background \"linear-gradient(to right bottom, rgb(249, 115, 22), rgb(154, 52, 18))\" --padding \"min(33vh, 33vw)\" --path-override \"/assets\" && pnpm pwa-asset-generator ./public/logo/white.svg ./public/assets --index ./index.html --splash-only --dark-mode --background \"#0c0a09\" --padding \"min(33vh, 33vw)\" --path-override \"/assets\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@leeoniya/ufuzzy": "^1.0.14",
|
||||
"@solid-primitives/event-listener": "^2.3.3",
|
||||
"@solid-primitives/intersection-observer": "^2.1.6",
|
||||
"@solid-primitives/memo": "^1.3.8",
|
||||
"@solid-primitives/resize-observer": "^2.0.25",
|
||||
"lean-qr": "^2.3.4",
|
||||
"lightweight-charts": "^4.1.6",
|
||||
"solid-js": "^1.8.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.2.1",
|
||||
"@iconify-json/tabler": "^1.1.114",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.3.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.5",
|
||||
"pwa-asset-generator": "^6.3.1",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"typescript": "^5.5.2",
|
||||
"unplugin-auto-import": "^0.17.6",
|
||||
"unplugin-icons": "^0.19.0",
|
||||
"vite": "^5.3.1",
|
||||
"vite-plugin-pwa": "^0.20.0",
|
||||
"vite-plugin-solid": "^2.10.2",
|
||||
"workbox-window": "^7.1.0",
|
||||
"wrangler": "^3.61.0"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/** @type {import("prettier").Options} */
|
||||
export default {
|
||||
plugins: [
|
||||
'@ianvs/prettier-plugin-sort-imports',
|
||||
'prettier-plugin-tailwindcss', // MUST come last
|
||||
],
|
||||
|
||||
tailwindFunctions: ['classList'],
|
||||
|
||||
importOrder: ['<THIRD_PARTY_MODULES>', '', '^/?(~|src)/', '', '^[./]'],
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 40 KiB |
@@ -1,17 +0,0 @@
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;" fill="black">
|
||||
<g transform="matrix(1.14102,0,0,2.63158,-0.849652,5.12904)">
|
||||
<rect x="4.25" y="3.751" width="14.023" height="1.52"/>
|
||||
</g>
|
||||
<g transform="matrix(1.14102,0,0,2.63158,-0.849652,0.129039)">
|
||||
<rect x="4.25" y="3.751" width="14.023" height="1.52"/>
|
||||
</g>
|
||||
<g transform="matrix(1.14102,0,0,2.63158,-0.849652,-4.87096)">
|
||||
<rect x="4.25" y="3.751" width="14.023" height="1.52"/>
|
||||
</g>
|
||||
<g transform="matrix(0.285256,0,0,2.63158,8.78759,-9.87096)">
|
||||
<rect x="4.25" y="3.751" width="14.023" height="1.52"/>
|
||||
</g>
|
||||
<g transform="matrix(0.285256,0,0,2.63158,8.78759,10.129)">
|
||||
<rect x="4.25" y="3.751" width="14.023" height="1.52"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1004 B |
@@ -1,17 +0,0 @@
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;" fill="white">
|
||||
<g transform="matrix(1.14102,0,0,2.63158,-0.849652,5.12904)">
|
||||
<rect x="4.25" y="3.751" width="14.023" height="1.52"/>
|
||||
</g>
|
||||
<g transform="matrix(1.14102,0,0,2.63158,-0.849652,0.129039)">
|
||||
<rect x="4.25" y="3.751" width="14.023" height="1.52"/>
|
||||
</g>
|
||||
<g transform="matrix(1.14102,0,0,2.63158,-0.849652,-4.87096)">
|
||||
<rect x="4.25" y="3.751" width="14.023" height="1.52"/>
|
||||
</g>
|
||||
<g transform="matrix(0.285256,0,0,2.63158,8.78759,-9.87096)">
|
||||
<rect x="4.25" y="3.751" width="14.023" height="1.52"/>
|
||||
</g>
|
||||
<g transform="matrix(0.285256,0,0,2.63158,8.78759,10.129)">
|
||||
<rect x="4.25" y="3.751" width="14.023" height="1.52"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1004 B |
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"name": "Satonomics",
|
||||
"short_name": "Satonomics",
|
||||
"description": "Satoshi Economics",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"theme_color": "#0c0a09",
|
||||
"background_color": "#0c0a09",
|
||||
"lang": "en",
|
||||
"scope": "/",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/assets/manifest-icon-192.maskable.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/assets/manifest-icon-192.maskable.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/assets/manifest-icon-512.maskable.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/assets/manifest-icon-512.maskable.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
const texts = [
|
||||
"satonomics",
|
||||
"satonomics",
|
||||
"satonomics",
|
||||
|
||||
"stay humble, stack sats",
|
||||
"21 million",
|
||||
"cold storage",
|
||||
"utxo",
|
||||
"satoshi nakamoto",
|
||||
"hodl",
|
||||
`don't trust, verify`,
|
||||
"zap",
|
||||
"bitcoin",
|
||||
"lightning",
|
||||
"nostr",
|
||||
"freedom tech",
|
||||
"2008/10/31",
|
||||
"2009/01/03",
|
||||
"2010/05/22",
|
||||
"hodl!",
|
||||
"Hal Finney",
|
||||
"Vote for better money",
|
||||
"gradually then suddenly",
|
||||
"timechain",
|
||||
"self custody",
|
||||
"be your own bank",
|
||||
"resistance money",
|
||||
"foss",
|
||||
];
|
||||
|
||||
export const LOCAL_STORAGE_MARQUEE_KEY = "bg-marquee";
|
||||
|
||||
export function Background({
|
||||
marquee: on,
|
||||
focused,
|
||||
}: {
|
||||
marquee: Accessor<boolean>;
|
||||
focused: Accessor<boolean>;
|
||||
}) {
|
||||
createEffect(() => {
|
||||
if (on()) {
|
||||
localStorage.removeItem(LOCAL_STORAGE_MARQUEE_KEY);
|
||||
} else {
|
||||
localStorage.setItem(LOCAL_STORAGE_MARQUEE_KEY, "false");
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="absolute h-full w-full overflow-hidden opacity-[0.0333] will-change-auto">
|
||||
<div class="-m-[2rem] -space-y-1 overflow-hidden md:-m-[1rem]">
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute h-full w-full opacity-10 mix-blend-multiply">
|
||||
<Noise />
|
||||
</div>
|
||||
<div class="absolute h-full w-full opacity-10 mix-blend-hard-light">
|
||||
<Noise />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Line({
|
||||
on,
|
||||
focused,
|
||||
}: {
|
||||
on: Accessor<boolean>;
|
||||
focused: Accessor<boolean>;
|
||||
}) {
|
||||
const shuffled = shuffle([...texts]);
|
||||
shuffled.pop();
|
||||
const joined = shuffled.join(". ");
|
||||
|
||||
return (
|
||||
<div class="select-none whitespace-nowrap">
|
||||
<TextWrapper on={on} focused={focused} joined={joined} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TextWrapper({
|
||||
joined,
|
||||
on,
|
||||
focused,
|
||||
}: {
|
||||
on: Accessor<boolean>;
|
||||
focused: Accessor<boolean>;
|
||||
joined: string;
|
||||
}) {
|
||||
const seconds = joined.length * 2;
|
||||
|
||||
const wasOnceOn = createRWS(false);
|
||||
|
||||
createEffect(() => {
|
||||
if (!wasOnceOn() && on()) {
|
||||
wasOnceOn.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<p
|
||||
class="inline-block px-2 text-[5dvh] font-black uppercase leading-none"
|
||||
style={{
|
||||
...(wasOnceOn()
|
||||
? {
|
||||
animation: `marquee ${seconds}s linear infinite`,
|
||||
"animation-play-state": focused() && on() ? "running" : "paused",
|
||||
}
|
||||
: {}),
|
||||
}}
|
||||
>
|
||||
{joined} {wasOnceOn() ? joined : undefined}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
function shuffle<T>([...arr]: T[]): T[] {
|
||||
let m = arr.length;
|
||||
|
||||
while (m) {
|
||||
const i = Math.floor(Math.random() * m--);
|
||||
[arr[m], arr[i]] = [arr[i], arr[m]];
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
function Noise() {
|
||||
return (
|
||||
<svg
|
||||
class="size-full"
|
||||
viewBox="0 0 200 200"
|
||||
preserveAspectRatio="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<filter id="noiseFilter">
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency="3"
|
||||
numOctaves="3"
|
||||
stitchTiles="stitch"
|
||||
/>
|
||||
</filter>
|
||||
|
||||
<rect width="100%" height="100%" filter="url(#noiseFilter)" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
import { createResizeObserver } from "@solid-primitives/resize-observer";
|
||||
|
||||
import { classPropToString } from "/src/solid/classes";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
export function Box({
|
||||
flex = true,
|
||||
absolute,
|
||||
padded = true,
|
||||
children,
|
||||
dark,
|
||||
overflowY,
|
||||
}: {
|
||||
flex?: boolean;
|
||||
absolute?: "top" | "bottom";
|
||||
padded?: boolean;
|
||||
dark?: boolean;
|
||||
overflowY?: boolean;
|
||||
} & ParentProps) {
|
||||
const maybeScrollable = createRWS<HTMLDivElement | undefined>(undefined);
|
||||
const scrollable = createRWS(false);
|
||||
const showLeftArrow = createRWS(false);
|
||||
const showRightArrow = createRWS(false);
|
||||
|
||||
onMount(() => {
|
||||
createResizeObserver(maybeScrollable, (_, el) => {
|
||||
if (el !== maybeScrollable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollable.set(() => el.scrollWidth > el.clientWidth);
|
||||
|
||||
checkArrows();
|
||||
});
|
||||
});
|
||||
|
||||
function checkArrows() {
|
||||
const offset = 20;
|
||||
|
||||
const target = maybeScrollable()!;
|
||||
|
||||
const left = target.scrollLeft;
|
||||
const right = target.scrollWidth - target.scrollLeft - target.clientWidth;
|
||||
|
||||
showLeftArrow.set(() => left > offset);
|
||||
showRightArrow.set(() => right > offset);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class={classPropToString([
|
||||
"p-2",
|
||||
absolute
|
||||
? [
|
||||
"absolute inset-x-0",
|
||||
absolute === "top"
|
||||
? "top-0"
|
||||
: "pointer-events-none bottom-0 bg-gradient-to-b from-transparent to-black",
|
||||
]
|
||||
: "relative",
|
||||
])}
|
||||
>
|
||||
<div
|
||||
class={classPropToString([
|
||||
"pointer-events-auto relative overflow-hidden rounded-xl border border-orange-200/10 shadow-md",
|
||||
dark
|
||||
? "bg-orange-100/5 backdrop-blur-sm"
|
||||
: "bg-orange-200/10 backdrop-blur-md",
|
||||
])}
|
||||
>
|
||||
<For
|
||||
each={[
|
||||
{
|
||||
showArrow: showLeftArrow,
|
||||
side: "left-0",
|
||||
order: "",
|
||||
buttonPadding: "pl-3 pr-2",
|
||||
iconPadding: "pr-0.5",
|
||||
scrollMultiplier: -1,
|
||||
chevronIcon: IconTablerChevronLeft,
|
||||
gradientDirection: "bg-gradient-to-r",
|
||||
},
|
||||
{
|
||||
showArrow: showRightArrow,
|
||||
side: "right-0",
|
||||
order: "order-2",
|
||||
buttonPadding: "pl-2 pr-3",
|
||||
iconPadding: "pl-0.5",
|
||||
scrollMultiplier: 1,
|
||||
chevronIcon: IconTablerChevronRight,
|
||||
gradientDirection: "bg-gradient-to-l",
|
||||
},
|
||||
]}
|
||||
>
|
||||
{(obj) => (
|
||||
<Show when={scrollable() && obj.showArrow()}>
|
||||
<div
|
||||
class={[
|
||||
obj.side,
|
||||
"pointer-events-none absolute bottom-0 top-0 z-20 flex transition-opacity duration-200 ease-in-out",
|
||||
].join(" ")}
|
||||
>
|
||||
<div
|
||||
class={[
|
||||
obj.order,
|
||||
obj.buttonPadding,
|
||||
"pointer-events-auto hidden h-full items-center bg-black/90 md:flex",
|
||||
].join(" ")}
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
maybeScrollable()?.scrollBy({
|
||||
left: Math.floor(
|
||||
maybeScrollable()!.clientWidth *
|
||||
obj.scrollMultiplier *
|
||||
0.8,
|
||||
),
|
||||
behavior: "smooth",
|
||||
});
|
||||
}}
|
||||
class="rounded-full border border-orange-200/20 bg-black p-0.5 transition hover:scale-110 active:scale-100"
|
||||
>
|
||||
<Dynamic
|
||||
component={obj.chevronIcon}
|
||||
class={[`size-5 ${obj.iconPadding}`]}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class={[
|
||||
obj.gradientDirection,
|
||||
"h-full w-10 from-black/90 to-transparent",
|
||||
].join(" ")}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
)}
|
||||
</For>
|
||||
|
||||
<div
|
||||
ref={maybeScrollable.set}
|
||||
onScroll={checkArrows}
|
||||
class={classPropToString([
|
||||
flex && "flex w-full space-x-2",
|
||||
overflowY && "overflow-y-auto",
|
||||
padded && "p-1.5",
|
||||
])}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
export function Button({
|
||||
onClick,
|
||||
children,
|
||||
}: { onClick: VoidFunction } & ParentProps) {
|
||||
return (
|
||||
<button
|
||||
class="group flex w-full flex-1 items-center justify-center rounded-lg px-2 py-1.5 hover:bg-orange-200/20 active:scale-95"
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
import { generate } from "lean-qr";
|
||||
|
||||
import { chartState } from "/src/scripts/lightweightCharts/chart/state";
|
||||
import { setTimeScale } from "/src/scripts/lightweightCharts/chart/time";
|
||||
import { classPropToString } from "/src/solid/classes";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
export function Actions({
|
||||
presets,
|
||||
fullscreen,
|
||||
qrcode,
|
||||
}: {
|
||||
presets: Presets;
|
||||
qrcode: RWS<string>;
|
||||
fullscreen?: RWS<boolean>;
|
||||
}) {
|
||||
return (
|
||||
<div class="flex space-x-1">
|
||||
<Show when={fullscreen}>
|
||||
{(fullscreen) => (
|
||||
<Button
|
||||
title="Toggle fullscreen"
|
||||
icon={() =>
|
||||
fullscreen()()
|
||||
? IconTablerLayoutSidebarLeftExpand
|
||||
: IconTablerLayoutSidebarRightExpand
|
||||
}
|
||||
onClick={() => {
|
||||
const range = chartState.range;
|
||||
|
||||
fullscreen().set((b) => !b);
|
||||
|
||||
setTimeScale(range);
|
||||
}}
|
||||
classes="hidden md:block"
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
<Button
|
||||
title="Share"
|
||||
icon={() => IconTablerShare}
|
||||
onClick={() => {
|
||||
qrcode.set(() =>
|
||||
generate(document.location.href).toDataURL({
|
||||
on: [0xff, 0xff, 0xff, 0xff],
|
||||
off: [0x00, 0x00, 0x00, 0x00],
|
||||
}),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
title="Favorite"
|
||||
colors={() =>
|
||||
presets.selected().isFavorite()
|
||||
? "text-amber-500 bg-amber-500/15 hover:bg-amber-500/30"
|
||||
: ""
|
||||
}
|
||||
icon={() =>
|
||||
presets.selected().isFavorite()
|
||||
? IconTablerStarFilled
|
||||
: IconTablerStar
|
||||
}
|
||||
onClick={() => presets.selected().isFavorite.set((b) => !b)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Button({
|
||||
title,
|
||||
icon,
|
||||
colors,
|
||||
onClick,
|
||||
disabled,
|
||||
classes,
|
||||
}: {
|
||||
title: string;
|
||||
icon: () => ValidComponent;
|
||||
colors?: () => string;
|
||||
onClick: VoidFunction;
|
||||
disabled?: () => boolean;
|
||||
classes?: string;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
title={title}
|
||||
disabled={disabled?.()}
|
||||
class={classPropToString([
|
||||
colors?.() || (disabled?.() ? "" : "hover:bg-orange-200/15"),
|
||||
!disabled?.() && "group",
|
||||
classes,
|
||||
"flex-none rounded-lg p-2 disabled:opacity-50",
|
||||
])}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Dynamic
|
||||
component={icon()}
|
||||
class="size-[1.125rem] group-active:scale-90"
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { cleanChart } from "/src/scripts/lightweightCharts/chart/clean";
|
||||
import { renderChart } from "/src/scripts/lightweightCharts/chart/render";
|
||||
|
||||
export function Chart({
|
||||
presets,
|
||||
datasets,
|
||||
legendSetter,
|
||||
activeResources,
|
||||
}: {
|
||||
presets: Presets;
|
||||
datasets: Datasets;
|
||||
legendSetter: Setter<PresetLegend>;
|
||||
activeResources: Accessor<Set<ResourceDataset<any, any>>>;
|
||||
}) {
|
||||
onMount(() => {
|
||||
createEffect(() => {
|
||||
const preset = presets.selected();
|
||||
|
||||
untrack(() =>
|
||||
renderChart({
|
||||
datasets,
|
||||
preset,
|
||||
legendSetter,
|
||||
activeResources,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
onCleanup(cleanChart);
|
||||
});
|
||||
|
||||
return <div id="chart" class="h-full w-full cursor-crosshair" />;
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
const transparency = "66";
|
||||
|
||||
export function Legend({
|
||||
legend: legendList,
|
||||
}: {
|
||||
legend: Accessor<PresetLegend>;
|
||||
}) {
|
||||
const hovering = createRWS<SeriesLegend | undefined>(undefined);
|
||||
|
||||
let toggle = false;
|
||||
|
||||
return (
|
||||
<div class="flex flex-1 items-center gap-1 overflow-y-auto">
|
||||
<For each={legendList()}>
|
||||
{(legend) => {
|
||||
const initialColors = {} as any;
|
||||
const darkenColors = {} as any;
|
||||
|
||||
Object.entries(legend.series.options()).forEach(([k, v]) => {
|
||||
if (k.toLowerCase().includes("color") && v) {
|
||||
initialColors[k] = v;
|
||||
darkenColors[k] = `${v}${transparency}`;
|
||||
} else if (k === "lastValueVisible" && v) {
|
||||
initialColors[k] = v;
|
||||
darkenColors[k] = !v;
|
||||
}
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (hovering()) {
|
||||
if (hovering()?.title !== legend.title) {
|
||||
legend.series.applyOptions(darkenColors);
|
||||
}
|
||||
} else {
|
||||
legend.series.applyOptions(initialColors);
|
||||
}
|
||||
});
|
||||
|
||||
let previousClickValueOf: number = 0;
|
||||
|
||||
return (
|
||||
<Show when={!legend.disabled()}>
|
||||
<button
|
||||
onMouseEnter={() => {
|
||||
hovering.set(legend);
|
||||
}}
|
||||
onMouseLeave={() => hovering.set(undefined)}
|
||||
onClick={() => {
|
||||
const currentClickValueOf = new Date().valueOf();
|
||||
|
||||
if (currentClickValueOf - previousClickValueOf > 300) {
|
||||
legend.visible.set((visible) => !visible);
|
||||
} else {
|
||||
legendList().forEach((_legend) => {
|
||||
if (_legend.title != legend.title) {
|
||||
_legend.visible.set(toggle);
|
||||
}
|
||||
});
|
||||
|
||||
legend.visible.set(true);
|
||||
|
||||
toggle = !toggle;
|
||||
}
|
||||
|
||||
previousClickValueOf = currentClickValueOf;
|
||||
}}
|
||||
class="flex flex-none items-center space-x-1.5 rounded-full py-1.5 pl-2 pr-2.5 hover:bg-orange-200/20 active:scale-[0.975]"
|
||||
>
|
||||
<span
|
||||
class="flex size-4 flex-col overflow-hidden rounded-full"
|
||||
style={{
|
||||
opacity: legend.visible() ? 1 : 0.5,
|
||||
}}
|
||||
>
|
||||
<For
|
||||
each={
|
||||
Array.isArray(legend.color())
|
||||
? (legend.color() as string[])
|
||||
: [legend.color() as string]
|
||||
}
|
||||
>
|
||||
{(color) => (
|
||||
<span
|
||||
class="w-full flex-1"
|
||||
style={{
|
||||
"background-color": color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</span>
|
||||
<span
|
||||
class="text-white decoration-white decoration-wavy decoration-[1.5px]"
|
||||
style={{
|
||||
"text-decoration-line": !legend.visible()
|
||||
? "line-through"
|
||||
: undefined,
|
||||
"--tw-text-opacity": legend.visible() ? 1 : 0.5,
|
||||
}}
|
||||
>
|
||||
{legend.title}
|
||||
</span>
|
||||
<Show when={legend.url}>
|
||||
{(url) => (
|
||||
<a
|
||||
title="Dataset"
|
||||
class="-my-0.5 !-mr-1 inline-flex size-6 flex-col overflow-hidden rounded-full border border-orange-200/5 bg-orange-200 bg-opacity-5 p-1 pl-0.5 hover:bg-opacity-30"
|
||||
style={{
|
||||
opacity: legend.visible() ? 1 : 0.5,
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
// event.preventDefault();
|
||||
}}
|
||||
href={url()}
|
||||
target={
|
||||
url()?.startsWith("/") || url()?.startsWith("http")
|
||||
? "_blank"
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<IconTablerExternalLink />
|
||||
</a>
|
||||
)}
|
||||
</Show>
|
||||
</button>
|
||||
</Show>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import { chartState } from "/src/scripts/lightweightCharts/chart/state";
|
||||
import { GENESIS_DAY } from "/src/scripts/lightweightCharts/chart/whitespace";
|
||||
import { ONE_DAY_IN_MS } from "/src/scripts/utils/time";
|
||||
|
||||
import { Box } from "../../box";
|
||||
|
||||
export function TimeScale() {
|
||||
return (
|
||||
<Box dark padded overflowY>
|
||||
<Button onClick={() => setTimeScale()}>All Time</Button>
|
||||
<Button onClick={() => setTimeScale(7)}>1 Week</Button>
|
||||
<Button onClick={() => setTimeScale(30)}>1 Month</Button>
|
||||
<Button onClick={() => setTimeScale(30 * 6)}>6 Months</Button>
|
||||
<Button
|
||||
onClick={() =>
|
||||
setTimeScale(
|
||||
Math.ceil(
|
||||
(new Date().valueOf() -
|
||||
new Date(`${new Date().getUTCFullYear()}-01-01`).valueOf()) /
|
||||
ONE_DAY_IN_MS,
|
||||
),
|
||||
)
|
||||
}
|
||||
>
|
||||
Year To Date
|
||||
</Button>
|
||||
<Button onClick={() => setTimeScale(365)}>1 Year</Button>
|
||||
<Button onClick={() => setTimeScale(2 * 365)}>2 Years</Button>
|
||||
<Button onClick={() => setTimeScale(4 * 365)}>4 Years</Button>
|
||||
<Button onClick={() => setTimeScale(8 * 365)}>8 Years</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function Button(props: ParentProps & { onClick: VoidFunction }) {
|
||||
return (
|
||||
<button
|
||||
class="min-w-20 flex-shrink-0 flex-grow whitespace-nowrap rounded-lg px-2 py-1.5 hover:bg-white/20 active:scale-95"
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function setTimeScale(days?: number) {
|
||||
const to = new Date();
|
||||
|
||||
if (days) {
|
||||
const from = new Date();
|
||||
from.setDate(from.getUTCDate() - days);
|
||||
|
||||
chartState.chart?.timeScale().setVisibleRange({
|
||||
from: (from.getTime() / 1000) as Time,
|
||||
to: (to.getTime() / 1000) as Time,
|
||||
});
|
||||
} else {
|
||||
// chartState.chart?.timeScale().fitContent();
|
||||
chartState.chart?.timeScale().setVisibleRange({
|
||||
from: (new Date(
|
||||
// datasets.candlesticks.values()?.[0]?.date || "",
|
||||
GENESIS_DAY,
|
||||
).getTime() / 1000) as Time,
|
||||
to: (to.getTime() / 1000) as Time,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
export function Title({ presets }: { presets: Presets }) {
|
||||
return (
|
||||
<div class="flex flex-1 items-center overflow-y-auto pb-1.5 text-orange-100/50">
|
||||
<div class="flex-1 -space-y-1 whitespace-nowrap px-1 md:mt-0.5 md:-space-y-1.5">
|
||||
<h3 class="text-xs">{`/ ${[...presets.selected().path.map(({ name }) => name), presets.selected().name].join(" / ")}`}</h3>
|
||||
<h1 class="text-lg font-bold text-white md:text-xl">
|
||||
{presets.selected().title}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import { classPropToString } from "/src/solid/classes";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
import { Box } from "../box";
|
||||
import { Actions } from "./components/actions";
|
||||
import { Chart } from "./components/chart";
|
||||
import { Legend } from "./components/legend";
|
||||
import { TimeScale } from "./components/timeScale";
|
||||
import { Title } from "./components/title";
|
||||
|
||||
export function ChartFrame({
|
||||
presets,
|
||||
datasets,
|
||||
activeResources,
|
||||
hide,
|
||||
qrcode,
|
||||
standalone,
|
||||
fullscreen,
|
||||
}: {
|
||||
presets: Presets;
|
||||
hide?: Accessor<boolean>;
|
||||
qrcode: RWS<string>;
|
||||
activeResources: Accessor<Set<ResourceDataset<any, any>>>;
|
||||
datasets: Datasets;
|
||||
fullscreen?: RWS<boolean>;
|
||||
standalone: boolean;
|
||||
}) {
|
||||
const legend = createRWS<PresetLegend>([]);
|
||||
|
||||
return (
|
||||
<div
|
||||
class={classPropToString([
|
||||
standalone &&
|
||||
"rounded-2xl border border-orange-200/15 bg-gradient-to-b from-orange-100/5 to-black/10 to-80%",
|
||||
"flex size-full min-h-0 flex-1 flex-col overflow-hidden",
|
||||
])}
|
||||
style={{
|
||||
display: (hide ? !hide() : true) ? undefined : "none",
|
||||
}}
|
||||
>
|
||||
<Box flex={false} dark>
|
||||
<Title presets={presets} />
|
||||
|
||||
<div class="-mx-2 border-t border-orange-200/15" />
|
||||
|
||||
<div class="flex pt-1.5">
|
||||
<Legend legend={legend} />
|
||||
|
||||
<div class="-my-1.5 border-l border-orange-200/15 pr-1.5" />
|
||||
|
||||
<Actions presets={presets} qrcode={qrcode} fullscreen={fullscreen} />
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div class="-mt-2 min-h-0 flex-1">
|
||||
<Chart
|
||||
activeResources={activeResources}
|
||||
datasets={datasets}
|
||||
// fetchedDatasets={fetchedDatasets}
|
||||
legendSetter={legend.set}
|
||||
presets={presets}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TimeScale />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
export function Counter({
|
||||
count,
|
||||
name,
|
||||
setRef,
|
||||
}: {
|
||||
count: () => number;
|
||||
name: string;
|
||||
setRef?: Setter<HTMLDivElement | undefined>;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
ref={setRef}
|
||||
class="text-orange-100/75"
|
||||
style={{
|
||||
"border-style": count() ? "dashed" : "none",
|
||||
}}
|
||||
>
|
||||
Counted{" "}
|
||||
<span class="font-medium text-orange-400/75">
|
||||
{count().toLocaleString("en-us")}
|
||||
</span>{" "}
|
||||
{name}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { Header } from "./header";
|
||||
import { Line } from "./line";
|
||||
import { Number } from "./number";
|
||||
|
||||
export function FavoritesFrame({
|
||||
presets,
|
||||
selectedFrame,
|
||||
}: {
|
||||
presets: Presets;
|
||||
selectedFrame: Accessor<FrameName>;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
class="flex-1 overflow-y-auto"
|
||||
hidden={selectedFrame() !== "Favorites"}
|
||||
>
|
||||
<div class="flex max-h-full min-h-0 flex-1 flex-col gap-4 p-4">
|
||||
<Header title="Favorites">
|
||||
<Number number={() => presets.favorites().length} /> presets marked as
|
||||
favorites.
|
||||
</Header>
|
||||
|
||||
<div class="-mx-4 border-t border-orange-200/10" />
|
||||
|
||||
<div
|
||||
class="space-y-0.5 py-1"
|
||||
style={{
|
||||
display: !presets.favorites().length ? "none" : undefined,
|
||||
}}
|
||||
>
|
||||
<For each={presets.favorites()}>
|
||||
{(preset) => (
|
||||
<Line
|
||||
id={`favorite-${preset.id}`}
|
||||
name={preset.title}
|
||||
onClick={() => presets.select(preset)}
|
||||
active={() => presets.selected() === preset}
|
||||
header={`/ ${[...preset.path.map(({ name }) => name), preset.name].join(" / ")}`}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
|
||||
<div class="h-[25dvh] flex-none" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export function Header({ title, children }: { title: string } & ParentProps) {
|
||||
return (
|
||||
<div>
|
||||
<h3 class="text-lg font-bold md:text-xl">{title}</h3>
|
||||
<p class="text-orange-100/75">{children}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import { run } from "/src/scripts/utils/run";
|
||||
|
||||
import { Header } from "./header";
|
||||
import { Line } from "./line";
|
||||
|
||||
export function HistoryFrame({
|
||||
presets,
|
||||
selectedFrame,
|
||||
}: {
|
||||
presets: Presets;
|
||||
selectedFrame: Accessor<FrameName>;
|
||||
}) {
|
||||
return (
|
||||
<div class="flex-1 overflow-y-auto" hidden={selectedFrame() !== "History"}>
|
||||
<div class="flex max-h-full min-h-0 flex-1 flex-col p-4">
|
||||
<Header title="History">List of previously visited presets.</Header>
|
||||
|
||||
<div
|
||||
class="space-y-0.5 pt-4"
|
||||
style={{
|
||||
display: !presets.history().length ? "none" : undefined,
|
||||
}}
|
||||
>
|
||||
<For each={presets.history()}>
|
||||
{({ preset, date }, index) => (
|
||||
<>
|
||||
<Show
|
||||
when={
|
||||
index() === 0 ||
|
||||
presets.history()[index()].date.toJSON().split("T")[0] !==
|
||||
presets.history()[index() - 1].date.toJSON().split("T")[0]
|
||||
}
|
||||
>
|
||||
<div class="sticky top-[-0.5rem] z-10 -mx-4 py-2">
|
||||
<div class="border-y border-orange-200/10 bg-[rgb(25,15,15)] p-2">
|
||||
<p class="ml-2">
|
||||
<Switch fallback={date.toLocaleDateString()}>
|
||||
<Match
|
||||
when={
|
||||
new Date().toJSON().split("T")[0] ===
|
||||
date.toJSON().split("T")[0]
|
||||
}
|
||||
>
|
||||
Today
|
||||
</Match>
|
||||
<Match
|
||||
when={
|
||||
run(() => {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() - 1);
|
||||
return d;
|
||||
})
|
||||
.toJSON()
|
||||
.split("T")[0] === date.toJSON().split("T")[0]
|
||||
}
|
||||
>
|
||||
Yesterday
|
||||
</Match>
|
||||
</Switch>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
<Line
|
||||
id={`history-${preset.id}`}
|
||||
name={preset.title}
|
||||
onClick={() => presets.select(preset)}
|
||||
active={() => presets.selected() === preset}
|
||||
header={date.toLocaleTimeString()}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
|
||||
<div class="h-[25dvh] flex-none" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import { scrollIntoView } from "/src/scripts/utils/scroll";
|
||||
import { classPropToString } from "/src/solid/classes";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
export function Line({
|
||||
id,
|
||||
name: _name,
|
||||
icon,
|
||||
active,
|
||||
depth = 0,
|
||||
onClick,
|
||||
header,
|
||||
tail,
|
||||
classes: classes,
|
||||
}: {
|
||||
id: string;
|
||||
name: string;
|
||||
onClick: VoidFunction;
|
||||
active?: Accessor<boolean>;
|
||||
depth?: number;
|
||||
header?: string;
|
||||
icon?: () => JSXElement;
|
||||
tail?: () => JSXElement;
|
||||
classes?: () => string;
|
||||
} & ParentProps) {
|
||||
const ref = createRWS<HTMLButtonElement | undefined>(undefined);
|
||||
|
||||
const [name, ...nameRest] = _name.split(" - ");
|
||||
|
||||
return (
|
||||
<button
|
||||
id={id}
|
||||
class={classPropToString([
|
||||
active?.()
|
||||
? "bg-orange-500/30 backdrop-blur-sm hover:bg-orange-500/50"
|
||||
: "hover:bg-orange-500/15",
|
||||
"relative -mx-2 flex w-[calc(100%+1rem)] items-center whitespace-nowrap rounded-lg px-2 hover:backdrop-blur-sm",
|
||||
classes?.(),
|
||||
])}
|
||||
ref={ref.set}
|
||||
onClick={() => {
|
||||
onClick();
|
||||
scrollIntoView(ref(), "nearest", "instant");
|
||||
}}
|
||||
title={name}
|
||||
>
|
||||
<For each={new Array(depth)}>
|
||||
{() => (
|
||||
<span class="ml-1 h-8 w-3 flex-none border-l border-orange-200/10" />
|
||||
)}
|
||||
</For>
|
||||
<Show when={icon}>
|
||||
{(icon) => (
|
||||
<span
|
||||
class="-my-0.5 mr-1"
|
||||
// style={{
|
||||
// "margin-left": `${depth}rem`,
|
||||
// }}
|
||||
>
|
||||
{icon()()}
|
||||
</span>
|
||||
)}
|
||||
</Show>
|
||||
<span
|
||||
class={classPropToString([
|
||||
!icon && "px-1",
|
||||
"inline-flex w-full flex-col -space-y-1 truncate py-1 text-left",
|
||||
])}
|
||||
>
|
||||
<Show when={header}>
|
||||
<span
|
||||
class="truncate text-xs text-white text-opacity-50"
|
||||
innerHTML={header}
|
||||
/>
|
||||
</Show>
|
||||
<span class="space-x-1 truncate">
|
||||
<span innerHTML={name} />
|
||||
<Show when={nameRest.length}>
|
||||
<span innerHTML={" - " + nameRest.join(" - ")} class="opacity-50" />
|
||||
</Show>
|
||||
</span>
|
||||
</span>
|
||||
<Show when={tail}>
|
||||
{(absolute) => (
|
||||
<span class="ml-0.5 flex items-center">{absolute()()}</span>
|
||||
)}
|
||||
</Show>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
export function Number({ number }: { number: () => number }) {
|
||||
return (
|
||||
<span class="font-medium text-orange-400/75">
|
||||
{number().toLocaleString("en-us")}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -1,318 +0,0 @@
|
||||
import uFuzzy from "@leeoniya/ufuzzy";
|
||||
import { createVisibilityObserver } from "@solid-primitives/intersection-observer";
|
||||
|
||||
import { scrollIntoView } from "/src/scripts/utils/scroll";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
import { INPUT_PRESET_SEARCH_ID } from "../..";
|
||||
import { Box } from "./box";
|
||||
import { Button } from "./button";
|
||||
import { Line } from "./line";
|
||||
|
||||
const PER_PAGE = 100;
|
||||
|
||||
export function SearchFrame({
|
||||
presets,
|
||||
selectedFrame,
|
||||
}: {
|
||||
presets: Presets;
|
||||
selectedFrame: Accessor<FrameName>;
|
||||
}) {
|
||||
const counterRef = createRWS<HTMLDivElement | undefined>(undefined);
|
||||
|
||||
const search = createRWS("", {
|
||||
equals: false,
|
||||
});
|
||||
|
||||
const inputRef = createRWS<HTMLInputElement | undefined>(undefined);
|
||||
|
||||
const config: uFuzzy.Options = {
|
||||
intraIns: Infinity,
|
||||
intraChars: `[a-z\d' ]`,
|
||||
};
|
||||
|
||||
const fuzzyMultiInsert = new uFuzzy({
|
||||
intraIns: 1,
|
||||
});
|
||||
const fuzzyMultiInsertFuzzier = new uFuzzy(config);
|
||||
const fuzzySingleError = new uFuzzy({
|
||||
intraMode: 1,
|
||||
...config,
|
||||
});
|
||||
const fuzzySingleErrorFuzzier = new uFuzzy({
|
||||
intraMode: 1,
|
||||
...config,
|
||||
});
|
||||
|
||||
const haystack = presets.list.map(
|
||||
(preset) =>
|
||||
`${preset.title}\t/ ${[...preset.path.map(({ name }) => name), preset.name].join(" / ")}`,
|
||||
);
|
||||
|
||||
const searchResult = createMemo(() => {
|
||||
scrollIntoView(counterRef());
|
||||
|
||||
const needle = search();
|
||||
|
||||
if (!needle) return null;
|
||||
|
||||
const outOfOrder = 5;
|
||||
const infoThresh = 5_000;
|
||||
|
||||
let result = fuzzyMultiInsert.search(
|
||||
haystack,
|
||||
needle,
|
||||
undefined,
|
||||
infoThresh,
|
||||
);
|
||||
|
||||
if (!result?.[0]?.length || !result?.[1]) {
|
||||
result = fuzzyMultiInsert.search(
|
||||
haystack,
|
||||
needle,
|
||||
outOfOrder,
|
||||
infoThresh,
|
||||
);
|
||||
}
|
||||
|
||||
if (!result?.[0]?.length || !result?.[1]) {
|
||||
result = fuzzySingleError.search(
|
||||
haystack,
|
||||
needle,
|
||||
outOfOrder,
|
||||
infoThresh,
|
||||
);
|
||||
}
|
||||
|
||||
if (!result?.[0]?.length || !result?.[1]) {
|
||||
result = fuzzySingleErrorFuzzier.search(
|
||||
haystack,
|
||||
needle,
|
||||
outOfOrder,
|
||||
infoThresh,
|
||||
);
|
||||
}
|
||||
|
||||
if (!result?.[0]?.length || !result?.[1]) {
|
||||
result = fuzzyMultiInsertFuzzier.search(
|
||||
haystack,
|
||||
needle,
|
||||
undefined,
|
||||
infoThresh,
|
||||
);
|
||||
}
|
||||
|
||||
if (!result?.[0]?.length || !result?.[1]) {
|
||||
result = fuzzyMultiInsertFuzzier.search(
|
||||
haystack,
|
||||
needle,
|
||||
outOfOrder,
|
||||
infoThresh,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
const resultCount = createMemo(() => searchResult()?.[0]?.length || 0);
|
||||
|
||||
return (
|
||||
<div
|
||||
class="relative flex size-full flex-1 flex-col"
|
||||
style={{
|
||||
display: selectedFrame() !== "Search" ? "none" : undefined,
|
||||
}}
|
||||
>
|
||||
<div class="flex-1 space-y-1 overflow-y-auto p-4 pt-16">
|
||||
<p class="py-2 text-orange-100/75">
|
||||
<Show when={search()} fallback={"Write in the top bar to search."}>
|
||||
Found{" "}
|
||||
<span class="font-medium text-orange-400/75">
|
||||
{resultCount().toLocaleString("en-us")}
|
||||
</span>{" "}
|
||||
presets.
|
||||
</Show>
|
||||
</p>
|
||||
|
||||
<Show when={search()}>
|
||||
<div class="-mx-4 border-t border-orange-200/10" />
|
||||
|
||||
<div
|
||||
class="py-1"
|
||||
style={{
|
||||
display: !resultCount() ? "none" : undefined,
|
||||
}}
|
||||
>
|
||||
{(() => {
|
||||
const r = searchResult();
|
||||
|
||||
if (r) {
|
||||
return (
|
||||
<ListSection
|
||||
haystack={haystack}
|
||||
presets={presets}
|
||||
searchResult={() => r}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
})()}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<Box absolute="top" padded={false}>
|
||||
<div
|
||||
class="relative flex w-full cursor-text items-center space-x-0.5 px-3 py-2 hover:bg-orange-200/5"
|
||||
onClick={() => inputRef()?.focus()}
|
||||
>
|
||||
<IconTablerSearch />
|
||||
<input
|
||||
id={INPUT_PRESET_SEARCH_ID}
|
||||
ref={inputRef.set}
|
||||
class="w-full bg-transparent p-1 caret-orange-500 placeholder:text-orange-200/50 focus:outline-none"
|
||||
placeholder="Search by name or path"
|
||||
value={search()}
|
||||
onInput={(event) => search.set(event.target.value)}
|
||||
/>
|
||||
<span class="-mx-1 flex size-5 flex-none items-center justify-center rounded-md border border-white text-xs font-bold">
|
||||
<IconTablerSlash />
|
||||
</span>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<Box absolute="bottom">
|
||||
<Button
|
||||
onClick={() => {
|
||||
search.set("");
|
||||
inputRef()?.focus();
|
||||
}}
|
||||
>
|
||||
Clear search
|
||||
</Button>
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ListSection({
|
||||
searchResult,
|
||||
pageIndex = 0,
|
||||
haystack,
|
||||
presets,
|
||||
}: {
|
||||
searchResult: Accessor<uFuzzy.SearchResult>;
|
||||
pageIndex?: number;
|
||||
haystack: string[];
|
||||
presets: Presets;
|
||||
}) {
|
||||
const div = createRWS<HTMLDivElement | undefined>(undefined);
|
||||
|
||||
const useVisibilityObserver = createVisibilityObserver();
|
||||
|
||||
const visible = useVisibilityObserver(div);
|
||||
|
||||
const showNextPage = createMemo<boolean>(
|
||||
(previous) => previous || visible(),
|
||||
false,
|
||||
);
|
||||
|
||||
const list = createMemo(() =>
|
||||
computeList({
|
||||
searchResult: searchResult(),
|
||||
pageIndex,
|
||||
haystack,
|
||||
presets,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<For each={list()}>
|
||||
{({ preset, path, title }) => (
|
||||
<Line
|
||||
id={`search-${preset.id}`}
|
||||
name={title}
|
||||
onClick={() => presets.select(preset)}
|
||||
active={() => presets.selected() === preset}
|
||||
header={path}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
<Show when={list().length === PER_PAGE}>
|
||||
<div ref={div.set}>
|
||||
<Show when={showNextPage()}>
|
||||
<ListSection
|
||||
searchResult={searchResult}
|
||||
haystack={haystack}
|
||||
presets={presets}
|
||||
pageIndex={pageIndex + 1}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function computeList({
|
||||
searchResult,
|
||||
pageIndex,
|
||||
haystack,
|
||||
presets,
|
||||
}: {
|
||||
searchResult: uFuzzy.SearchResult;
|
||||
pageIndex: number;
|
||||
haystack: string[];
|
||||
presets: Presets;
|
||||
}) {
|
||||
let list: {
|
||||
preset: Preset;
|
||||
path: string;
|
||||
title: string;
|
||||
}[] = [];
|
||||
|
||||
let [indexes, info, order] = searchResult || [null, null, null];
|
||||
|
||||
const minIndex = pageIndex * PER_PAGE;
|
||||
|
||||
if (indexes?.length) {
|
||||
const maxIndex = Math.min(
|
||||
(order || indexes).length - 1,
|
||||
minIndex + PER_PAGE - 1,
|
||||
);
|
||||
|
||||
list = Array(maxIndex - minIndex + 1);
|
||||
|
||||
if (info && order) {
|
||||
for (let i = minIndex; i <= maxIndex; i++) {
|
||||
let infoIdx = order[i];
|
||||
|
||||
const [title, path] = uFuzzy
|
||||
.highlight(haystack[info.idx[infoIdx]], info.ranges[infoIdx])
|
||||
.split("\t");
|
||||
|
||||
list[i % 100] = {
|
||||
preset: presets.list[info.idx[infoIdx]],
|
||||
path,
|
||||
title,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
for (let i = minIndex; i <= maxIndex; i++) {
|
||||
let index = indexes[i];
|
||||
|
||||
const [title, path] = haystack[index].split("\t");
|
||||
|
||||
list[i % 100] = {
|
||||
preset: presets.list[index],
|
||||
path,
|
||||
title,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { version } from "/src/../package.json";
|
||||
|
||||
import { Header } from "./header";
|
||||
|
||||
export function SettingsFrame({
|
||||
marquee,
|
||||
selectedFrame,
|
||||
}: {
|
||||
marquee: RWS<boolean>;
|
||||
selectedFrame: Accessor<FrameName>;
|
||||
}) {
|
||||
const value = marquee();
|
||||
|
||||
return (
|
||||
<div class="flex-1 overflow-y-auto" hidden={selectedFrame() !== "Settings"}>
|
||||
<div class="space-y-4 p-4">
|
||||
<Header title="Settings" />
|
||||
|
||||
<div class="-mx-4 border-t border-orange-200/10" />
|
||||
|
||||
<div class="space-y-2">
|
||||
<p>Background</p>
|
||||
<div>Opacity</div>
|
||||
<div>
|
||||
<label class="switch">
|
||||
Scroll
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value}
|
||||
onChange={(event) => marquee.set(event.target.checked || false)}
|
||||
/>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<hr class="border-t border-orange-200/20" />
|
||||
<p>Version: {version}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { Line } from "../../line";
|
||||
|
||||
export function File({
|
||||
id,
|
||||
name,
|
||||
icon,
|
||||
active,
|
||||
depth,
|
||||
onClick,
|
||||
favorite,
|
||||
visited,
|
||||
}: {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: JSXElement;
|
||||
active: Accessor<boolean>;
|
||||
depth: number;
|
||||
onClick: VoidFunction;
|
||||
favorite: Accessor<boolean>;
|
||||
visited: Accessor<boolean>;
|
||||
}) {
|
||||
const tail = createMemo(() =>
|
||||
favorite() ? (
|
||||
<span class="rounded-full bg-yellow-950 p-1">
|
||||
<IconTablerStarFilled class="size-3 text-amber-500" />
|
||||
</span>
|
||||
) : !visited() ? (
|
||||
<span class="mx-1.5 rounded-full bg-orange-500/50 p-1 text-transparent" />
|
||||
) : undefined,
|
||||
);
|
||||
|
||||
return (
|
||||
<Line
|
||||
id={id}
|
||||
depth={depth}
|
||||
active={active}
|
||||
name={name}
|
||||
icon={() => icon}
|
||||
onClick={onClick}
|
||||
tail={tail}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function randomDegree(min = 0, max = 360) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { Line } from "../../line";
|
||||
|
||||
export function Folder({
|
||||
id,
|
||||
name,
|
||||
depth,
|
||||
open,
|
||||
onClick,
|
||||
children,
|
||||
}: {
|
||||
id: string;
|
||||
name: string;
|
||||
depth: number;
|
||||
open: Accessor<boolean>;
|
||||
onClick: VoidFunction;
|
||||
children: number;
|
||||
}) {
|
||||
const icon = createMemo(() =>
|
||||
open() ? <IconTablerFolderOpen /> : <IconTablerFolder />,
|
||||
);
|
||||
|
||||
return (
|
||||
<Line
|
||||
id={id}
|
||||
depth={depth}
|
||||
name={name}
|
||||
icon={icon}
|
||||
onClick={onClick}
|
||||
classes={() => (open() ? "text-orange-100/75" : "")}
|
||||
tail={() => (
|
||||
<Show when={!open()}>
|
||||
<span class="rounded-full bg-white bg-opacity-[0.075] px-2 py-0.5 text-xs text-neutral-400">
|
||||
{children}
|
||||
</span>
|
||||
</Show>
|
||||
)}
|
||||
></Line>
|
||||
);
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import { File } from "./file";
|
||||
import { Folder } from "./folder";
|
||||
|
||||
export function Tree({
|
||||
tree,
|
||||
selected,
|
||||
openedFolders,
|
||||
depth = 0,
|
||||
visible,
|
||||
selectPreset,
|
||||
path = [],
|
||||
favorites,
|
||||
}: {
|
||||
tree: PresetTree;
|
||||
selected: Accessor<Preset>;
|
||||
selectPreset(preset: Preset): void;
|
||||
openedFolders: RWS<Set<string>>;
|
||||
depth?: number;
|
||||
visible?: Accessor<boolean>;
|
||||
path?: FilePath;
|
||||
favorites: Accessor<Preset[]>;
|
||||
}) {
|
||||
return (
|
||||
<div style={{ display: visible?.() === false ? "none" : undefined }}>
|
||||
<For each={tree}>
|
||||
{(thing) => {
|
||||
const active = createMemo(() => thing.id === selected().id);
|
||||
const favorite = createMemo(() =>
|
||||
favorites().includes(thing as Preset),
|
||||
);
|
||||
const visited = (thing as Preset).visited;
|
||||
|
||||
if (!("tree" in thing)) {
|
||||
return (
|
||||
<File
|
||||
id={thing.id}
|
||||
name={thing.name}
|
||||
active={active}
|
||||
depth={depth}
|
||||
icon={thing.icon || IconTablerFile}
|
||||
favorite={favorite}
|
||||
visited={visited}
|
||||
onClick={() => {
|
||||
const selectedId = selected().id;
|
||||
|
||||
if (selectedId === thing.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Has been filled in createPresets
|
||||
selectPreset(thing as Preset);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const childrenVisible = createMemo(() =>
|
||||
openedFolders().has(thing.id),
|
||||
);
|
||||
|
||||
const childCount = countChildren(thing);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Folder
|
||||
id={thing.id}
|
||||
name={thing.name}
|
||||
depth={depth}
|
||||
open={childrenVisible}
|
||||
children={childCount}
|
||||
onClick={() => {
|
||||
openedFolders.set((s) => {
|
||||
if (childrenVisible()) {
|
||||
s.delete(thing.id);
|
||||
} else {
|
||||
s.add(thing.id);
|
||||
}
|
||||
|
||||
return s;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tree
|
||||
tree={thing.tree}
|
||||
selected={selected}
|
||||
depth={depth + 1}
|
||||
openedFolders={openedFolders}
|
||||
visible={childrenVisible}
|
||||
path={[...path, { name: thing.name, id: thing.id }]}
|
||||
selectPreset={selectPreset}
|
||||
favorites={favorites}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function countChildren(folder: PresetFolder) {
|
||||
let count = 0;
|
||||
|
||||
function _countChildren(tree: PartialPresetTree) {
|
||||
tree.forEach((anyPreset) => {
|
||||
if ("tree" in anyPreset) {
|
||||
_countChildren(anyPreset.tree);
|
||||
} else {
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_countChildren(folder.tree);
|
||||
|
||||
return count;
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import { scrollIntoView } from "/src/scripts/utils/scroll";
|
||||
import { sleep, tick } from "/src/scripts/utils/sleep";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
import { Box } from "../box";
|
||||
import { Button } from "../button";
|
||||
import { Header } from "../header";
|
||||
import { Number } from "../number";
|
||||
import { Tree } from "./components/tree";
|
||||
|
||||
export function TreeFrame({
|
||||
presets,
|
||||
selectedFrame,
|
||||
}: {
|
||||
presets: Presets;
|
||||
selectedFrame: Accessor<FrameName>;
|
||||
}) {
|
||||
const div = createRWS<HTMLDivElement | undefined>(undefined);
|
||||
|
||||
onMount(() => {
|
||||
goToSelected(presets);
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
class="relative flex size-full flex-1 flex-col"
|
||||
style={{
|
||||
display: selectedFrame() !== "Tree" ? "none" : undefined,
|
||||
}}
|
||||
>
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div class="flex max-h-full min-h-0 flex-1 flex-col gap-4 p-4">
|
||||
<Header title="Folders">
|
||||
<Number number={() => presets.list.length} /> presets organized in a
|
||||
tree like structure.
|
||||
</Header>
|
||||
|
||||
<div class="-mx-4 border-t border-orange-200/10" />
|
||||
|
||||
<Tree
|
||||
tree={presets.tree}
|
||||
openedFolders={presets.openedFolders}
|
||||
selected={presets.selected}
|
||||
selectPreset={presets.select}
|
||||
favorites={presets.favorites}
|
||||
/>
|
||||
|
||||
<div class="h-[50dvh] flex-none" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Box absolute="bottom">
|
||||
<Button
|
||||
onClick={() => {
|
||||
presets.openedFolders.set((s) => {
|
||||
s.clear();
|
||||
return s;
|
||||
});
|
||||
|
||||
sleep(10);
|
||||
|
||||
scrollIntoView(div());
|
||||
}}
|
||||
>
|
||||
Close all folders
|
||||
</Button>
|
||||
<Button onClick={() => goToSelected(presets)}>Go to selected</Button>
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function goToSelected(presets: Presets) {
|
||||
batch(() =>
|
||||
presets.selected().path.forEach(({ id }) => {
|
||||
presets.openedFolders.set((s) => {
|
||||
s.add(id);
|
||||
return s;
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
await tick();
|
||||
|
||||
scrollIntoView(document.getElementById(presets.selected().id), "center");
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
export function Qrcode({ qrcode }: { qrcode: RWS<string> }) {
|
||||
return (
|
||||
<Show when={qrcode()}>
|
||||
<div
|
||||
class="absolute inset-0 z-50 flex size-full justify-center bg-black"
|
||||
onClick={() => {
|
||||
qrcode.set("");
|
||||
}}
|
||||
>
|
||||
<div class="flex size-full max-w-md flex-col items-center justify-center bg-black px-8 py-16 text-lg">
|
||||
<p class="pb-16 text-2xl font-bold">Share</p>
|
||||
|
||||
<div class="flex min-h-0 w-full flex-1 flex-col">
|
||||
<p>You can scan the following QR Code with a phone:</p>
|
||||
<img
|
||||
class="aspect-square min-h-0 flex-1 grow object-contain"
|
||||
src={qrcode()}
|
||||
style={{ "image-rendering": "pixelated" }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p>Or if you prefer you can send this link instead:</p>
|
||||
<a
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
}}
|
||||
href={location.href}
|
||||
>
|
||||
{location.href}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { Clickable } from "./clickable";
|
||||
|
||||
export function Anchor(args: {
|
||||
title: string;
|
||||
href: string;
|
||||
icon?: () => ValidComponent;
|
||||
}) {
|
||||
return <Clickable {...args} />;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Anchor } from "./anchor";
|
||||
|
||||
export function AnchorAPI() {
|
||||
return (
|
||||
<Anchor
|
||||
title="API"
|
||||
icon={() => () => (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M5.13468 2.41153C3.88395 3.0478 3.37143 3.79772 3.37143 4.4186C3.37143 5.03949 3.88395 5.78941 5.13468 6.42568C6.3444 7.04109 8.06359 7.44186 10 7.44186C11.9364 7.44186 13.6556 7.04109 14.8653 6.42568C16.1161 5.78941 16.6286 5.03949 16.6286 4.4186C16.6286 3.79772 16.1161 3.0478 14.8653 2.41153C13.6556 1.79612 11.9364 1.39535 10 1.39535C8.06359 1.39535 6.3444 1.79612 5.13468 2.41153ZM16.6286 6.93694C16.2841 7.21648 15.8934 7.46274 15.4786 7.67372C14.0411 8.40502 12.1032 8.83721 10 8.83721C7.89684 8.83721 5.95889 8.40502 4.52136 7.67372C4.10664 7.46274 3.71588 7.21648 3.37143 6.93694V10C3.37143 10.6209 3.88395 11.3708 5.13468 12.0071C6.3444 12.6225 8.06359 13.0233 10 13.0233C11.9364 13.0233 13.6556 12.6225 14.8653 12.0071C16.1161 11.3708 16.6286 10.6209 16.6286 10V6.93694ZM18 4.4186C18 2.98447 16.8752 1.87393 15.4786 1.16349C14.0411 0.432186 12.1032 0 10 0C7.89684 0 5.95889 0.432186 4.52136 1.16349C3.12484 1.87393 2 2.98447 2 4.4186V15.5814C2 17.0155 3.12484 18.1261 4.52136 18.8365C5.95889 19.5678 7.89684 20 10 20C12.1032 20 14.0411 19.5678 15.4786 18.8365C16.8752 18.1261 18 17.0155 18 15.5814V4.4186ZM16.6286 12.5183C16.2841 12.7979 15.8934 13.0441 15.4786 13.2551C14.0411 13.9864 12.1032 14.4186 10 14.4186C7.89684 14.4186 5.95889 13.9864 4.52136 13.2551C4.10664 13.0441 3.71588 12.7979 3.37143 12.5183V15.5814C3.37143 16.2023 3.88395 16.9522 5.13468 17.5885C6.3444 18.2039 8.06359 18.6047 10 18.6047C11.9364 18.6047 13.6556 18.2039 14.8653 17.5885C16.1161 16.9522 16.6286 16.2023 16.6286 15.5814V12.5183ZM6.34285 10C6.34285 10.5138 5.93351 10.9302 5.42857 10.9302C4.92362 10.9302 4.51428 10.5138 4.51428 10C4.51428 9.48625 4.92362 9.06977 5.42857 9.06977C5.93351 9.06977 6.34285 9.48625 6.34285 10ZM9.0857 11.8605C9.59065 11.8605 9.99999 11.444 9.99999 10.9302C9.99999 10.4165 9.59065 10 9.0857 10C8.58076 10 8.17142 10.4165 8.17142 10.9302C8.17142 11.444 8.58076 11.8605 9.0857 11.8605ZM6.34285 15.5814C6.34285 16.0951 5.93351 16.5116 5.42857 16.5116C4.92362 16.5116 4.51428 16.0951 4.51428 15.5814C4.51428 15.0676 4.92362 14.6512 5.42857 14.6512C5.93351 14.6512 6.34285 15.0676 6.34285 15.5814ZM9.0857 17.4419C9.59065 17.4419 9.99999 17.0254 9.99999 16.5116C9.99999 15.9979 9.59065 15.5814 9.0857 15.5814C8.58076 15.5814 8.17142 15.9979 8.17142 16.5116C8.17142 17.0254 8.58076 17.4419 9.0857 17.4419Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
)}
|
||||
href="https://api.satonomics.xyz"
|
||||
/>
|
||||
);
|
||||
}
|
||||