Compare commits
731 Commits
kibo-v0.1.1
...
v0.0.109
| Author | SHA1 | Date | |
|---|---|---|---|
| 3831ef7b25 | |||
| 8127337a09 | |||
| 9a59c2e541 | |||
| 27adca5653 | |||
| 2c5b502da9 | |||
| 23f6397a97 | |||
| 43117825d7 | |||
| cc5701ea62 | |||
| 9524eafea1 | |||
| c28a0f96f7 | |||
| 301dee96dc | |||
| 185fc7b6ed | |||
| 6d194dbb71 | |||
| d34f4bdd12 | |||
| 17dc4bde5e | |||
| ce50b14591 | |||
| f7bd319954 | |||
| 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,137 @@
|
||||
# Changelog Generation Prompt
|
||||
|
||||
Update docs/CHANGELOG.md for ALL latest releases not present in the file. Use ONLY git commands - no other sources.
|
||||
|
||||
## MANDATORY PROCESS - FOLLOW EXACTLY - NO EXCEPTIONS:
|
||||
1. Run `git tag --list --sort=version:refname` to get releases in order
|
||||
2. Process EXACTLY ONE release at a time
|
||||
3. For EACH SINGLE release: run `git diff [previous-tag]..[current-tag]`
|
||||
4. **MANDATORY ANALYSIS STEP**: Before writing ANY changelog entry, you MUST:
|
||||
- Analyze each file change and explain what the code is doing
|
||||
- Identify the purpose and impact of each modification
|
||||
- Group related changes together logically
|
||||
- State clearly what functionality is being added, removed, or modified
|
||||
- If you cannot understand what a change does from the diff, explicitly say so
|
||||
5. Only AFTER completing the analysis, write the detailed changelog entry
|
||||
6. Update the CHANGELOG.md file with that ONE entry
|
||||
7. STOP. Ask me if you should continue to the next release.
|
||||
|
||||
## CRITICAL CONSTRAINTS:
|
||||
- **NEVER EVER** process multiple releases in one go, even if there are many
|
||||
- **NEVER** say "let me continue more efficiently by processing multiple releases"
|
||||
- **NEVER** batch releases together for any reason
|
||||
- If you feel tempted to process multiple releases, **STOP** and process only one
|
||||
- Context window concerns do **NOT** justify batching - process one release only
|
||||
|
||||
## ABSOLUTE REQUIREMENTS:
|
||||
- **NEVER** read commit messages, PR descriptions, existing changelog, or any text documentation
|
||||
- Use **ONLY** the actual code changes shown in git diff output
|
||||
- Process releases **ONE BY ONE** - I don't care if there are 100 releases
|
||||
- **MANDATORY**: Before writing changelog entries, demonstrate understanding by analyzing what each code change accomplishes
|
||||
- Be **HIGHLY DESCRIPTIVE** about what each code change does and why it matters
|
||||
- Don't be conservative - write detailed explanations of the impact and purpose of changes
|
||||
- **If you don't understand a change from the code diff alone, DO NOT GUESS - say so explicitly**
|
||||
|
||||
## SOURCE OF TRUTH:
|
||||
- `git diff` output is the **ONLY** source of truth
|
||||
- If you can't determine what a change does from the code diff alone, say so explicitly
|
||||
- Ignore **ALL** text/documentation - focus purely on code additions, deletions, and modifications
|
||||
|
||||
## CHANGELOG FILE REQUIREMENTS:
|
||||
- Add a header at the top of the CHANGELOG.md file: `<!-- This changelog was generated by Claude Code -->`
|
||||
- Ensure this header appears before any changelog entries
|
||||
|
||||
## CHANGELOG WRITING RULES:
|
||||
|
||||
### RELEASE TITLE FORMAT:
|
||||
**MUST** use this exact format: `## [vX.Y.Z](https://github.com/bitcoinresearchkit/brk/releases/tag/vX.Y.Z) - YYYY-MM-DD`
|
||||
Use the actual release date from git tag information
|
||||
|
||||
### ABSOLUTELY FORBIDDEN PATTERNS:
|
||||
- **NEVER** mention line counts (e.g., "with 138 lines", "1,290 lines removed")
|
||||
- **NEVER** use vague action words: "Enhanced", "Improved", "Updated", "Expanded", "Restructured", "Refactored", "Modified", "Adjusted"
|
||||
- **NEVER** write sections about Cargo.lock or dependency updates unless they represent major functional changes
|
||||
- **NEVER** use the format "Action: File with vague description"
|
||||
- **NEVER** mention version bumps of local crates (e.g., "Updated all crate versions from 0.0.61 to 0.0.62") - this is implied by the release version
|
||||
- **NEVER** mention dependency version changes in external crates unless they enable new functionality visible in the code
|
||||
- **NEVER** write entries like "Updated dependencies" or "Cargo.lock maintenance"
|
||||
|
||||
### REQUIRED WRITING STYLE:
|
||||
- Write what the code **actually DOES**, not that it was "enhanced" or "improved"
|
||||
- Be **specific about functionality**: "Added transaction validation logic", "Implemented caching for API responses"
|
||||
- Focus on **business/functional impact**: "Enables users to...", "Fixes issue where...", "Adds support for..."
|
||||
- **Mandatory structure**: Group by: Breaking Changes, New Features, Bug Fixes, Internal Changes
|
||||
- Include GitHub file links for major changes (max 5 per entry)
|
||||
- **Skip entirely**: minor dependency bumps, Cargo.lock changes, and local crate version bumps
|
||||
|
||||
### MANDATORY ANALYSIS WORKFLOW:
|
||||
**BEFORE writing any changelog entry, you MUST:**
|
||||
|
||||
1. **Code Comprehension Check**: Go through each modified file and explain:
|
||||
- What specific functionality is being added/removed/changed
|
||||
- What the new/modified functions/structs/methods do
|
||||
- How the changes affect the overall system behavior
|
||||
|
||||
2. **Impact Assessment**: For each change, determine:
|
||||
- Is this a new feature, bug fix, breaking change, or internal improvement?
|
||||
- What user-facing or system behavior changes result from this code?
|
||||
- What problem does this change solve?
|
||||
|
||||
3. **Logical Grouping**: Organize related changes together:
|
||||
- Group files that work together to implement a single feature
|
||||
- Separate breaking changes from additions
|
||||
- Distinguish user-facing changes from internal refactoring
|
||||
|
||||
4. **Understanding Verification**: Before writing changelog text, state:
|
||||
- "I understand this change does X because the code shows Y"
|
||||
- If unclear: "I cannot determine the purpose of this change from the diff alone"
|
||||
|
||||
**ONLY AFTER completing this analysis should you write the changelog entry.**
|
||||
|
||||
### WHAT TO FOCUS ON (IN ORDER OF PRIORITY):
|
||||
1. **New functionality** - What can users now do that they couldn't before?
|
||||
2. **Breaking changes** - What existing functionality changed or was removed?
|
||||
3. **Bug fixes** - What specific problems were resolved?
|
||||
4. **Internal changes** - New modules, significant refactoring, architecture changes
|
||||
5. **Skip completely** - Dependency updates, version bumps, Cargo.lock changes
|
||||
|
||||
### VERBOSITY REQUIREMENTS:
|
||||
- **Minimum 3-4 bullet points per section** when changes exist
|
||||
- **Each bullet point should be 1-2 sentences** explaining both what changed and why it matters
|
||||
- **For new features**: Explain what the feature does and what problem it solves
|
||||
- **For bug fixes**: Describe the problem that was fixed (inferred from the code changes)
|
||||
- **For internal changes**: Explain the architectural or structural improvement
|
||||
|
||||
### EXAMPLES OF GOOD vs BAD:
|
||||
|
||||
#### ❌ BAD EXAMPLES:
|
||||
- "Enhanced: Chain analysis with sophisticated blockchain processing capabilities"
|
||||
- "Updated: brk_rolldown from 0.0.1 to 0.1.0 with comprehensive bundling improvements"
|
||||
- "Version Bump: Updated all crate versions from 0.0.61 to 0.0.62"
|
||||
- "Improved error handling"
|
||||
- "Refactored codebase"
|
||||
- "Updated dependencies"
|
||||
|
||||
#### ✅ GOOD EXAMPLES WITH ANALYSIS:
|
||||
|
||||
**Analysis**: "Looking at the diff, I see a new `TransactionAnalyzer` struct was added with methods `calculate_fee()` and `is_coinbase()`. The struct takes transaction data and provides analysis methods. This enables users to programmatically analyze transaction properties."
|
||||
|
||||
**Changelog**: "Added new `TransactionAnalyzer` struct that provides methods for computing transaction fees and detecting coinbase transactions"
|
||||
|
||||
**Analysis**: "The diff shows error handling was added around block parsing where previously there was an unwrap(). Now it returns a Result and handles the empty block case explicitly. This prevents panics when processing malformed blocks."
|
||||
|
||||
**Changelog**: "Fixed panic when processing blocks with zero transactions by adding explicit empty block handling and proper error propagation"
|
||||
|
||||
**Analysis**: "I see a new caching layer was implemented with a HashMap storing block hashes as keys and block data as values. The API endpoints now check this cache before making network requests. This should improve performance for repeated queries."
|
||||
|
||||
**Changelog**: "Implemented new caching layer for blockchain queries, reducing API response time by storing frequently accessed block data in memory"
|
||||
|
||||
#### ❌ BAD EXAMPLES (NO UNDERSTANDING):
|
||||
- "Enhanced error handling" (What specific errors? How were they enhanced?)
|
||||
- "Improved performance" (What was improved? How?)
|
||||
- "Updated transaction logic" (What specific logic? What changed?)
|
||||
|
||||
## FINAL REMINDER:
|
||||
**PROCESS ONLY ONE RELEASE. THEN STOP AND WAIT FOR MY CONFIRMATION.**
|
||||
|
||||
You must be thorough and verbose - if there are code changes, there should be substantial changelog content explaining what those changes accomplish.
|
||||
@@ -0,0 +1,172 @@
|
||||
# README Generation Prompt
|
||||
|
||||
Generate a professional, comprehensive README.md for each crate based SOLELY on code analysis. Use NO external documentation, commit messages, or existing READMEs.
|
||||
|
||||
## MANDATORY PROCESS - FOLLOW EXACTLY:
|
||||
1. **IGNORE EXISTING DOCS**: Do NOT read any .md, .txt, .rst, or documentation files in the crate directory
|
||||
2. **CODE-ONLY ANALYSIS**: Examine ONLY these files:
|
||||
- All .rs files in src/ directory and subdirectories
|
||||
- Cargo.toml for dependencies and metadata
|
||||
- Code structure and organization
|
||||
3. **MANDATORY CODE ANALYSIS**: Before writing ANY README content, you MUST:
|
||||
- Examine all Rust files in src/ directory
|
||||
- Identify the main structs, enums, traits, and functions
|
||||
- Understand the crate's architecture and data flow
|
||||
- Determine the crate's purpose from its implementation
|
||||
- Map dependencies to understand external integrations
|
||||
4. **FRESH PERSPECTIVE**: Write the README as if you're the first person to document this crate
|
||||
5. Generate one complete README.md per crate
|
||||
6. Focus on one crate at a time for thorough analysis
|
||||
|
||||
## ABSOLUTE REQUIREMENTS:
|
||||
- **SOURCE OF TRUTH**: Use ONLY the actual Rust code - no external docs, comments may provide hints but focus on implementation
|
||||
- **CRITICAL**: DO NOT read any existing README.md, CHANGELOG.md, or documentation files
|
||||
- **IGNORE ALL TEXT FILES**: .md, .txt, .rst files are FORBIDDEN sources - treat them as if they don't exist
|
||||
- **CODE ONLY**: Focus exclusively on .rs files, Cargo.toml, and code structure
|
||||
- **PROFESSIONAL GRADE**: Write as if this will be published on crates.io for other developers
|
||||
- **PROGRAMMER FOCUSED**: Assume audience knows Rust and relevant domain concepts
|
||||
- **IMPLEMENTATION-BASED**: Describe what the code actually does, not what comments claim it should do
|
||||
- **If you cannot determine functionality from code alone, state this explicitly**
|
||||
|
||||
## README STRUCTURE (MANDATORY):
|
||||
|
||||
### 1. CRATE HEADER
|
||||
```markdown
|
||||
# Crate Name
|
||||
|
||||
Brief one-line description of what this crate does (max 80 chars).
|
||||
|
||||
[](https://crates.io/crates/CRATE_NAME)
|
||||
[](https://docs.rs/CRATE_NAME)
|
||||
```
|
||||
|
||||
### 2. OVERVIEW SECTION
|
||||
- **Purpose**: What problem does this crate solve?
|
||||
- **Key Features**: 3-5 bullet points of main capabilities (derived from code analysis)
|
||||
- **Target Use Cases**: Who would use this and for what?
|
||||
|
||||
### 3. INSTALLATION
|
||||
```toml
|
||||
[dependencies]
|
||||
crate_name = "X.Y.Z"
|
||||
```
|
||||
|
||||
### 4. QUICK START / USAGE
|
||||
- **Minimal working example** showing the primary API
|
||||
- **Common patterns** observed in the code
|
||||
- **Key structs/traits** that users will interact with
|
||||
|
||||
### 5. API OVERVIEW
|
||||
- **Core Types**: Main structs, enums, traits with brief descriptions
|
||||
- **Key Methods**: Most important public functions
|
||||
- **Module Structure**: Brief overview of how code is organized
|
||||
|
||||
### 6. FEATURES (if applicable)
|
||||
- Cargo features and what they enable
|
||||
- Optional dependencies and their purpose
|
||||
|
||||
### 7. EXAMPLES
|
||||
- 2-3 practical code examples showing different use cases
|
||||
- Based on public API analysis, not existing examples
|
||||
|
||||
## WRITING REQUIREMENTS:
|
||||
|
||||
### TONE AND STYLE:
|
||||
- **Concise but comprehensive**: Every sentence must add value
|
||||
- **Technical precision**: Use exact terminology, avoid marketing speak
|
||||
- **Active voice**: "Provides X" not "X is provided"
|
||||
- **Present tense**: "The crate handles..." not "The crate will handle..."
|
||||
|
||||
### FORBIDDEN PATTERNS:
|
||||
- **NEVER** use vague terms: "powerful", "flexible", "robust", "comprehensive", "advanced"
|
||||
- **NEVER** write marketing copy: "cutting-edge", "state-of-the-art", "enterprise-grade"
|
||||
- **NEVER** make claims you can't verify from code: "blazingly fast", "memory efficient"
|
||||
- **NEVER** copy-paste from existing documentation or comments
|
||||
- **NEVER** read or reference existing README.md files - pretend they don't exist
|
||||
- **NEVER** use phrases like "as mentioned in the documentation" or "according to the docs"
|
||||
- **NEVER** let existing documentation influence your analysis or writing
|
||||
|
||||
### REQUIRED SPECIFICITY:
|
||||
- **Data structures**: Mention specific types (HashMap, Vec, etc.)
|
||||
- **Algorithms**: Reference actual implementations found in code
|
||||
- **Integration points**: Specific traits implemented, dependencies used
|
||||
- **Error handling**: How errors are represented and handled
|
||||
- **Async/sync**: Clearly state if operations are blocking or async
|
||||
|
||||
### ANTI-BIAS PROTOCOL:
|
||||
|
||||
### BEFORE STARTING ANY ANALYSIS:
|
||||
1. **Explicitly ignore**: Any README.md, CHANGELOG.md, docs/, documentation files
|
||||
2. **File filtering**: Only examine .rs and Cargo.toml files
|
||||
3. **Fresh eyes approach**: Analyze the code as if you've never seen this crate before
|
||||
4. **Independent thinking**: Form your own understanding purely from code inspection
|
||||
|
||||
### IF YOU ACCIDENTALLY READ EXISTING DOCS:
|
||||
- Stop immediately and restart your analysis
|
||||
- Consciously disregard any information from documentation files
|
||||
- Base all descriptions solely on what you observe in the code
|
||||
- Ask yourself: "What would I think this code does if I had no documentation?"
|
||||
|
||||
### VALIDATION CHECKS:
|
||||
- **Unique descriptions**: Your descriptions should differ significantly from any existing docs
|
||||
- **Code-derived insights**: Every feature mentioned must be visible in the source code
|
||||
- **Independent voice**: Write in your own technical style, not mimicking existing documentation
|
||||
- **Fresh examples**: Create new code examples based on API analysis, not existing samples
|
||||
|
||||
## CODE ANALYSIS DEPTH:
|
||||
**You MUST analyze and understand:**
|
||||
1. **Public API surface**: All pub structs, functions, traits, modules
|
||||
2. **Core abstractions**: Main data types and their relationships
|
||||
3. **Error types**: Custom errors, Result patterns, panic conditions
|
||||
4. **Dependencies**: How external crates are integrated
|
||||
5. **Feature flags**: Conditional compilation and optional functionality
|
||||
6. **Async patterns**: Use of futures, tokio, async-std, etc.
|
||||
7. **Serialization**: Serde implementations, custom serialization
|
||||
8. **Performance characteristics**: Algorithm complexity where obvious
|
||||
|
||||
### EXAMPLE STRUCTURE ANALYSIS OUTPUT:
|
||||
```markdown
|
||||
## Code Analysis Summary
|
||||
**Main Types**: `BlockProcessor`, `Transaction`, `ValidationError`
|
||||
**Core Trait**: `Validator` - implemented by `BasicValidator` and `StrictValidator`
|
||||
**Async Support**: All processing methods return `impl Future`
|
||||
**Error Handling**: Custom `ValidationError` enum with specific error types
|
||||
**Dependencies**: Uses `tokio` for async runtime, `serde` for serialization
|
||||
**Architecture**: Pipeline pattern with configurable validation stages
|
||||
```
|
||||
|
||||
## EXAMPLES OF QUALITY:
|
||||
|
||||
### ❌ BAD (VAGUE):
|
||||
```markdown
|
||||
# My Crate
|
||||
A powerful and flexible library for blockchain operations.
|
||||
|
||||
## Features
|
||||
- Fast processing
|
||||
- Easy to use
|
||||
- Robust error handling
|
||||
```
|
||||
|
||||
### ✅ GOOD (SPECIFIC):
|
||||
```markdown
|
||||
# brk-chain-analyzer
|
||||
Bitcoin blockchain analysis tools for transaction pattern detection.
|
||||
|
||||
## Overview
|
||||
Provides utilities for analyzing Bitcoin transaction data, detecting address clustering patterns, and computing blockchain statistics. Built around a streaming parser that processes block data without loading entire blocks into memory.
|
||||
|
||||
## Key Types
|
||||
- `TransactionAnalyzer`: Stateful analyzer for computing fees, detecting coinbase transactions
|
||||
- `ClusterDetector`: Implements common input ownership heuristics for address clustering
|
||||
- `BlockStream`: Async iterator over blockchain data with configurable batch sizes
|
||||
```
|
||||
|
||||
## FINAL REQUIREMENTS:
|
||||
- **One README per crate** - don't combine multiple crates
|
||||
- **Minimum 200 words** - be thorough but concise
|
||||
- **Maximum 800 words** - stay focused and relevant
|
||||
- **Code examples must be syntactically correct** and compilable
|
||||
- **All claims must be verifiable** from the source code
|
||||
|
||||
**PROCESS ONE CRATE AT A TIME. ANALYZE THE CODE THOROUGHLY BEFORE WRITING.**
|
||||
@@ -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,28 @@
|
||||
# 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/settings*
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## v. 0.1.1 | 849240 - 2024/06/24
|
||||
|
||||
### Parser
|
||||
|
||||
- Fixed overflow in `Price` struct which caused many Realized Caps and Realized Prices to have completely bogus data
|
||||
- Fixed Realized Cap computation which was using rounded prices instead normal ones
|
||||
|
||||
### Server
|
||||
|
||||
- Added the chunk, date and time in the terminal logs
|
||||
|
||||
### App
|
||||
|
||||
- Chart
|
||||
- Added double click option on a legend to toggle the visibility of all other series
|
||||
- Added highlight effect to a legend by darkening the color of all the other series on the chart while hovering it with the mouse
|
||||
- Added an API link in the legend for each dataset where applicable (when isn't generated locally)
|
||||
- Save fullscreen preference in local storage and url
|
||||
- Improved resize bar on desktop
|
||||
- Changed resize button logo
|
||||
- Changed the share button to visible on small screen too
|
||||
- Improved share screen
|
||||
- Fixed time range shifting not being the one in url params or saved in local storage
|
||||
- Fixed time range shifting on series toggling via the legend
|
||||
- Fixed time range shifting on fullscreen
|
||||
- Fixed time range shifting on resize of the sidebar
|
||||
- Set default view at first load to last 6 months
|
||||
- Added some padding around the datasets (year 1970 to 2100)
|
||||
- History
|
||||
- Changed background for the sticky dates from blur to a solid color as it didn't appear properly in Firefox
|
||||
- Build
|
||||
- Tried to add lazy loads to have split chunks after build, to have much faster load times and they worked great ! But they completely broke Safari on iOS, we can't have nice things
|
||||
- Removed many libraries and did some things manually instead to improve build size
|
||||
- Strip
|
||||
- Temporarily removed the Home button on the strip bar on desktop as there is no landing page yet
|
||||
- Settings
|
||||
- Add version
|
||||
- PWA
|
||||
- Fixed background update
|
||||
- Changed update check frequency to 1 minute (~1kb to fetch every minute which is very reasonable)
|
||||
- Added a nice banner to ask the user to install the update
|
||||
- Misc
|
||||
- Removed tracker even though it was a very privacy friendly as it appeared to not be working properly
|
||||
|
||||
### Price
|
||||
|
||||
- Deleted old price datasets and their backups
|
||||
@@ -0,0 +1,93 @@
|
||||
[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.109"
|
||||
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"
|
||||
codegen-units = 16
|
||||
opt-level = 2
|
||||
split-debuginfo = "unpacked"
|
||||
|
||||
[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
|
||||
debug = false
|
||||
overflow-checks = false
|
||||
panic = "abort"
|
||||
debug-assertions = false
|
||||
|
||||
[workspace.dependencies]
|
||||
allocative = { version = "0.3.4", features = ["parking_lot"] }
|
||||
axum = "0.8.4"
|
||||
bitcoin = { version = "0.32.7", features = ["serde"] }
|
||||
bitcoincore-rpc = "0.19.0"
|
||||
brk_bundler = { version = "0.0.109", path = "crates/brk_bundler" }
|
||||
brk_cli = { version = "0.0.109", path = "crates/brk_cli" }
|
||||
brk_computer = { version = "0.0.109", path = "crates/brk_computer" }
|
||||
brk_error = { version = "0.0.109", path = "crates/brk_error" }
|
||||
brk_fetcher = { version = "0.0.109", path = "crates/brk_fetcher" }
|
||||
brk_indexer = { version = "0.0.109", path = "crates/brk_indexer" }
|
||||
brk_interface = { version = "0.0.109", path = "crates/brk_interface" }
|
||||
brk_logger = { version = "0.0.109", path = "crates/brk_logger" }
|
||||
brk_mcp = { version = "0.0.109", path = "crates/brk_mcp" }
|
||||
brk_parser = { version = "0.0.109", path = "crates/brk_parser" }
|
||||
brk_server = { version = "0.0.109", path = "crates/brk_server" }
|
||||
brk_store = { version = "0.0.109", path = "crates/brk_store" }
|
||||
brk_structs = { version = "0.0.109", 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.225"
|
||||
serde_bytes = "0.11.19"
|
||||
serde_derive = "1.0.225"
|
||||
serde_json = { version = "1.0.145", features = ["float_roundtrip"] }
|
||||
sonic-rs = "0.5.4"
|
||||
tokio = { version = "1.47.1", features = ["rt-multi-thread"] }
|
||||
# vecdb = { path = "../seqdb/crates/vecdb", features = ["derive"]}
|
||||
vecdb = { version = "0.2.16", 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,59 +0,0 @@
|
||||
# SATONOMICS
|
||||
|
||||
## Description
|
||||
|
||||
TLDR: Free, open source, verifiable and self-hostable Bitcoin on-chain data generator and visualizer
|
||||
|
||||
Satonomics is an open-source suite of tools that computes, distributes, and displays on-chain data, making it freely available for anyone to 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.
|
||||
|
||||
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).
|
||||
|
||||
## Instances
|
||||
|
||||
Web App:
|
||||
- [app.satonomics.xyz](https://app.satonomics.xyz)
|
||||
|
||||
API:
|
||||
- [api.satonomics.xyz](https://api.satonomics.xyz)
|
||||
|
||||
## Structure
|
||||
|
||||
- `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.
|
||||
|
||||
## Git
|
||||
|
||||
- [Repository](https://codeberg.org/satonomics/satonomics)
|
||||
- [Issues](https://gitworkshop.dev/r/naddr1qq99xct5dahx7mtfvdesz9thwden5te0wp6hyurvv4ex2mrp0yhxxmmdqgsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03srqsqqqaueek2h03/issues)
|
||||
- [Proposals](https://gitworkshop.dev/r/naddr1qq99xct5dahx7mtfvdesz9thwden5te0wp6hyurvv4ex2mrp0yhxxmmdqgsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03srqsqqqaueek2h03/proposals)
|
||||
|
||||
## Goals
|
||||
|
||||
- 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
|
||||
|
||||
## Proof of Work
|
||||
|
||||
Aka: Previous iterations
|
||||
|
||||
The initial idea was totally different yet morphed over time into what it is today: a fully FOSS self-hostable on-chain data generator
|
||||
|
||||
- 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
|
||||
@@ -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"
|
||||
/>
|
||||
);
|
||||
}
|
||||