Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Showing
with 1161 additions and 638 deletions
<svg width="168" height="48" viewBox="0 0 168 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.0003 39.3334C32.4687 39.3334 39.3337 32.4684 39.3337 24.0001C39.3337 15.5317 32.4687 8.66675 24.0003 8.66675C15.532 8.66675 8.66699 15.5317 8.66699 24.0001C8.66699 32.4684 15.532 39.3334 24.0003 39.3334Z" fill="#FCEA2B"/>
<path d="M33.7297 27.76C33.7378 28.7865 33.5406 29.8042 33.1497 30.7533C24.8231 32.7733 16.1964 30.98 14.8231 30.6667C14.4505 29.7432 14.2626 28.7557 14.2697 27.76H14.3431C14.3431 27.76 24.2097 30.1533 33.6031 27.8067L33.7297 27.76Z" fill="white"/>
<path d="M33.1499 30.7534C31.9566 33.6001 28.9166 35.5734 24.0232 35.5734C19.0832 35.5734 16.0032 33.5534 14.8232 30.6667C16.1966 30.9801 24.8232 32.7734 33.1499 30.7534Z" fill="#EA5A47"/>
<path d="M24.0003 39.3334C32.4687 39.3334 39.3337 32.4684 39.3337 24.0001C39.3337 15.5317 32.4687 8.66675 24.0003 8.66675C15.532 8.66675 8.66699 15.5317 8.66699 24.0001C8.66699 32.4684 15.532 39.3334 24.0003 39.3334Z" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.0459 24.1479C20.1505 24.1479 21.0459 22.535 21.0459 20.5454C21.0459 18.5558 20.1505 16.9429 19.0459 16.9429C17.9413 16.9429 17.0459 18.5558 17.0459 20.5454C17.0459 22.535 17.9413 24.1479 19.0459 24.1479Z" fill="black"/>
<path d="M28.9541 24.1501C30.0587 24.1501 30.9541 22.5372 30.9541 20.5476C30.9541 18.558 30.0587 16.9451 28.9541 16.9451C27.8495 16.9451 26.9541 18.558 26.9541 20.5476C26.9541 22.5372 27.8495 24.1501 28.9541 24.1501Z" fill="black"/>
<path d="M33.7297 27.76C33.7378 28.7865 33.5406 29.8042 33.1497 30.7533C24.8231 32.7733 16.1964 30.98 14.8231 30.6667C14.4505 29.7432 14.2626 28.7557 14.2697 27.76H14.3431C14.3431 27.76 24.2097 30.1533 33.6031 27.8067L33.7297 27.76Z" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M33.1499 30.7534C31.9566 33.6001 28.9166 35.5734 24.0232 35.5734C19.0832 35.5734 16.0032 33.5534 14.8232 30.6667C16.1966 30.9801 24.8232 32.7734 33.1499 30.7534Z" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M78.1115 45.3335H62.667V37.5557L72.667 32.5557C70.9314 38.2468 77.6371 42.2603 81.0003 40.3335L78.1115 45.3335Z" fill="#92D3F5"/>
<path d="M90.8605 45.3332H106.305V37.5554L96.305 32.5554C98.0406 38.2466 91.3349 42.2601 87.9717 40.3332L90.8605 45.3332Z" fill="#92D3F5"/>
<path d="M79.8873 40.7393C75.7701 40.7393 72.4385 37.5467 72.4385 33.6014C76.8551 30.9209 77.2922 24.3749 77.7505 20.6249C78.0839 18.6979 78.945 18.6979 79.2505 16.7708L81.0005 4.89821C81.4172 2.66661 84.0532 2.89447 84.4867 4.66661V35.9444C84.4867 37.2341 82.5561 38.2777 81.8895 38.2777C81.3477 38.2777 79.8873 40.2974 79.8873 40.7393Z" fill="#FCEA2B"/>
<path d="M89.0856 40.7393C93.2029 40.7393 96.5345 37.5467 96.5345 33.6014C92.1178 30.9209 91.6807 24.3749 91.2224 20.6249C90.8891 18.6979 90.0279 18.6979 89.7224 16.7708L87.9724 4.89826C87.5557 2.6666 84.9198 2.89446 84.4863 4.6666V35.9444C84.4863 37.2341 86.4169 38.2777 87.0835 38.2777C87.6252 38.2777 89.0856 40.2974 89.0856 40.7393Z" fill="#FCEA2B"/>
<path d="M78.1105 45.3335H62.666V37.5557L72.666 32.5557C70.9304 38.2468 77.6361 42.2603 80.9993 40.3335L78.1105 45.3335Z" stroke="black" stroke-width="1.33333" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M79.8864 40.7395C79.8864 40.2977 81.3468 38.278 81.8884 38.278C82.5551 38.278 84.4856 37.2344 84.4856 35.9446V4.66685C84.0522 2.89472 81.4162 2.66685 80.9996 4.89845L79.2496 16.771C78.944 18.6981 78.0829 18.6981 77.7496 20.6251C77.2912 24.3751 76.8542 30.9211 72.4375 33.6016" stroke="black" stroke-width="1.33333" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M90.8605 45.3335H106.305V37.5557L96.305 32.5557C98.0406 38.2468 91.3349 42.2603 87.9717 40.3335L90.8605 45.3335Z" stroke="black" stroke-width="1.33333" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M89.0846 40.7395C89.0846 40.2977 87.6242 38.278 87.0826 38.278C86.4159 38.278 84.4854 37.2344 84.4854 35.9446V4.66685C84.9188 2.89472 87.5548 2.66685 87.9715 4.89845L89.7215 16.771C90.027 18.6981 90.8882 18.6981 91.2215 20.6251C91.6798 24.3751 92.1169 30.9211 96.5336 33.6016" stroke="black" stroke-width="1.33333" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M137.453 13.4539L145.833 21.8333L154.212 30.2128L139.859 36.1865L125.506 42.1602L131.48 27.8071L137.453 13.4539Z" fill="#F1B31C"/>
<path d="M146.666 23.3334L137.128 13.7795L130.927 28.3597L124.727 42.9397L146.666 23.3334Z" fill="#FCEA2B"/>
<path d="M130.148 30.1899L137.523 37.5646L133.836 39.0656L128.638 33.8675L130.148 30.1899Z" fill="#EA5A47"/>
<path d="M131.562 36.8317L133.836 39.0656L137.523 37.5646L134.323 34.364L131.562 36.8317Z" fill="#D22F27"/>
<path d="M133.869 21.4417L138.333 25.9061L146.133 33.7055L141.951 35.6145L136.503 30.1665L132.1 25.7631L133.869 21.4417Z" fill="#EA5A47"/>
<path d="M137.69 31.3541L141.951 35.6143L146.133 33.7054L140.907 28.4797L137.69 31.3541Z" fill="#D22F27"/>
<path d="M140.197 10.9524C140.933 10.9524 141.53 10.3642 141.53 9.63857C141.53 8.91295 140.933 8.32471 140.197 8.32471C139.46 8.32471 138.863 8.91295 138.863 9.63857C138.863 10.3642 139.46 10.9524 140.197 10.9524Z" fill="#8967AA"/>
<path d="M160.197 13.6192C160.933 13.6192 161.53 13.0309 161.53 12.3053C161.53 11.5797 160.933 10.9915 160.197 10.9915C159.46 10.9915 158.863 11.5797 158.863 12.3053C158.863 13.0309 159.46 13.6192 160.197 13.6192Z" fill="#F1B31C"/>
<path d="M158.197 27.6192C158.933 27.6192 159.53 27.031 159.53 26.3053C159.53 25.5797 158.933 24.9915 158.197 24.9915C157.46 24.9915 156.863 25.5797 156.863 26.3053C156.863 27.031 157.46 27.6192 158.197 27.6192Z" fill="#D22F27"/>
<path d="M153.775 30.4264L153.887 30.5385L139.307 36.7391L124.727 42.9398L130.927 28.3597L137.128 13.7795" stroke="black" stroke-width="1.33333" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M137.2 13.8523L145.507 22.1593L153.775 30.4267" stroke="black" stroke-width="1.33333" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M137.128 13.7795L137.2 13.852" stroke="black" stroke-width="1.33333" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M150.993 4.91162C151.149 5.21075 151.261 5.54029 151.319 5.89362C151.62 7.72515 150.355 9.50302 148.493 9.86462" stroke="black" stroke-width="1.33333" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M148.622 9.85107C148.285 9.87667 147.946 9.95594 147.616 10.0937C145.903 10.8079 145.064 12.8224 145.742 14.5932" stroke="black" stroke-width="1.33333" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M161.195 17.8113C161.129 18.1424 161.01 18.4693 160.833 18.7809C159.919 20.3959 157.818 20.9869 156.142 20.1009" stroke="black" stroke-width="1.33333" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M156.249 20.1707C155.971 19.9795 155.658 19.8287 155.314 19.7287C153.532 19.2104 151.615 20.2532 151.032 22.0578" stroke="black" stroke-width="1.33333" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="168" height="48" fill="none" xmlns:v="https://vecta.io/nano"><style><![CDATA[.B{stroke:#000}.C{stroke-width:1.333}.D{stroke-linejoin:round}.E{fill:#fcea2b}.F{fill:#ea5a47}.G{stroke-linecap:round}.H{fill:#d22f27}.I{stroke-miterlimit:10}]]></style><use xlink:href="#B" class="E"/><use xlink:href="#C" fill="#fff"/><use xlink:href="#D" class="F"/><use xlink:href="#B" class="B C D"/><path d="M19.046 24.148c1.105 0 2-1.613 2-3.602s-.895-3.602-2-3.602-2 1.613-2 3.602.895 3.602 2 3.602zm9.908.002c1.105 0 2-1.613 2-3.602s-.895-3.602-2-3.602-2 1.613-2 3.602.895 3.602 2 3.602z" fill="#000"/><g class="B C D"><use xlink:href="#C"/><use xlink:href="#D"/></g><path d="M78.112 45.334H62.667v-7.778l10-5c-1.736 5.691 4.97 9.705 8.333 7.778l-2.889 5zm12.749-.001h15.445v-7.778l-10-5c1.736 5.691-4.97 9.705-8.333 7.778l2.889 5z" fill="#92d3f5"/><g class="E"><path d="M79.887 40.739c-4.117 0-7.449-3.193-7.449-7.138 4.417-2.68 4.854-9.226 5.312-12.976.333-1.927 1.194-1.927 1.5-3.854l1.75-11.873c.417-2.232 3.053-2.004 3.486-.232v31.278c0 1.29-1.931 2.333-2.597 2.333-.542 0-2.002 2.02-2.002 2.462z"/><path d="M89.086 40.739c4.117 0 7.449-3.193 7.449-7.138-4.417-2.68-4.854-9.226-5.312-12.976-.333-1.927-1.194-1.927-1.5-3.854l-1.75-11.873c-.417-2.232-3.053-2.004-3.486-.232v31.278c0 1.29 1.931 2.333 2.597 2.333.542 0 2.002 2.02 2.002 2.462z"/></g><g class="B C D I"><path d="M78.111 45.334H62.666v-7.778l10-5c-1.736 5.691 4.97 9.705 8.333 7.778l-2.889 5z"/><path d="M79.886 40.74c0-.442 1.46-2.462 2.002-2.462.667 0 2.597-1.044 2.597-2.333V4.667c-.433-1.772-3.069-2-3.486.232l-1.75 11.873c-.306 1.927-1.167 1.927-1.5 3.854-.458 3.75-.895 10.296-5.312 12.976" class="G"/><path d="M90.861 45.334h15.445v-7.778l-10-5c1.736 5.691-4.97 9.705-8.333 7.778l2.889 5z"/><path d="M89.085 40.74c0-.442-1.46-2.462-2.002-2.462-.667 0-2.597-1.044-2.597-2.333V4.667c.433-1.772 3.069-2 3.486.232l1.75 11.873c.305 1.927 1.167 1.927 1.5 3.854.458 3.75.895 10.296 5.312 12.976" class="G"/></g><path d="M137.453 13.454l8.38 8.379 8.379 8.38-14.353 5.974-14.353 5.974 5.974-14.353 5.973-14.353z" fill="#f1b31c"/><path d="M146.666 23.333l-9.538-9.554-6.201 14.58-6.2 14.58 21.939-19.606z" class="E"/><path d="M130.148 30.19l7.375 7.375-3.687 1.501-5.198-5.198 1.51-3.678z" class="F"/><path d="M131.562 36.832l2.274 2.234 3.687-1.501-3.2-3.201-2.761 2.468z" class="H"/><path d="M133.869 21.442l4.464 4.464 7.8 7.799-4.182 1.909-5.448-5.448-4.403-4.403 1.769-4.321z" class="F"/><path d="M137.69 31.354l4.261 4.26 4.182-1.909-5.226-5.226-3.217 2.874z" class="H"/><use xlink:href="#E" fill="#8967aa"/><use xlink:href="#E" x="20" y="2.667" fill="#f1b31c"/><use xlink:href="#E" x="18" y="16.667" class="H"/><g class="B C D G I"><path d="M153.775 30.426l.112.112-14.58 6.201-14.58 6.201 12.401-29.16"/><path d="M137.2 13.852l16.575 16.574M137.128 13.78l.072.072m13.793-8.94a3.26 3.26 0 0 1 .326.982c.301 1.832-.964 3.609-2.826 3.971"/><path d="M148.622 9.851a3.25 3.25 0 0 0-1.006.243c-1.713.714-2.552 2.729-1.874 4.499m15.453 3.218a3.24 3.24 0 0 1-.362.97c-.914 1.615-3.015 2.206-4.691 1.32m.107.07a3.24 3.24 0 0 0-.935-.442c-1.782-.518-3.699.524-4.282 2.329"/></g><defs ><path id="B" d="M24 39.333c8.468 0 15.333-6.865 15.333-15.333S32.469 8.667 24 8.667 8.667 15.532 8.667 24 15.532 39.333 24 39.333z"/><path id="C" d="M33.73 27.76a7.7 7.7 0 0 1-.58 2.993c-8.327 2.02-16.953.227-18.327-.087-.373-.923-.56-1.911-.553-2.907h.073s9.867 2.393 19.26.047l.127-.047z"/><path id="D" d="M33.15 30.753c-1.193 2.847-4.233 4.82-9.127 4.82-4.94 0-8.02-2.02-9.2-4.907 1.373.313 10 2.107 18.327.087z"/><path id="E" d="M140.197 10.952c.736 0 1.333-.588 1.333-1.314s-.597-1.314-1.333-1.314-1.334.588-1.334 1.314.597 1.314 1.334 1.314z"/></defs></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="468pt" height="617pt" viewBox="0 0 468 617"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.5, written by Peter Selinger 2001-2004
</metadata>
<g transform="translate(0,617) scale(0.709091,-0.709091)"
fill="#000099" stroke="none">
<path fill="#000099" stroke="none" d="M302 838 c-14 -14 -16 -126 -3 -147 5
-8 16 -11 25 -8 12 5 16 21 16 71 0 89 -10 112 -38 84z"/>
<path fill="#000099" stroke="none" d="M521 775 c-27 -57 -32 -108 -10 -113
18 -3 84 122 75 144 -11 30 -44 15 -65 -31z"/>
<path fill="#000099" stroke="none" d="M34 797 c-8 -22 59 -158 76 -154 38 7
-11 167 -51 167 -11 0 -22 -6 -25 -13z"/>
<path fill="#000099" stroke="none" d="M254 590 c-50 -7 -128 -52 -175 -100
-98 -100 -65 -346 57 -423 63 -40 107 -50 200 -44 125 7 212 62 275 172 53 92
32 220 -51 317 -62 71 -170 99 -306 78z"/>
<path fill="#ffff63" stroke="none" d="M443 539 c47 -13 112 -70 138 -120 24
-48 26 -147 3 -190 -22 -43 -82 -108 -117 -125 -137 -71 -277 -55 -351 41 -39
52 -51 92 -51 175 1 77 19 113 82 161 80 63 198 86 296 58z"/>
<path fill="#000099" stroke="none" d="M462 367 c-5 -7 -15 -28 -21 -48 -21
-67 -100 -120 -144 -98 -30 15 -65 56 -88 102 -21 40 -51 48 -57 14 -5 -26 53
-111 96 -141 89 -62 204 -7 252 119 15 40 -15 81 -38 52z"/>
</g>
</svg>
\ No newline at end of file
<svg width="168" height="48" viewBox="0 0 168 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M100 29.3333C96.0005 29.3333 91.0831 38.7919 90.6671 42.6666C90.1058 47.8866 93.3338 47.9999 93.3338 47.9999H108V34.6666C108 34.6666 103.416 29.3333 100 29.3333Z" fill="#50A5E6"/>
<path d="M98.6551 39.078C100.808 35.3486 101.707 31.8364 100.662 31.2334C99.6178 30.6303 97.0255 33.1646 94.8723 36.894C92.7191 40.6234 91.8204 44.1356 92.865 44.7387C93.9096 45.3418 96.5019 42.8074 98.6551 39.078Z" fill="#1C6399"/>
<path d="M86.7813 0C88.356 0 89.632 1.276 89.632 2.85067C89.632 3.90267 90.5227 17.2827 90.5227 17.2827L83.9307 24V2.85067C83.9307 1.276 85.2067 0 86.7813 0Z" fill="#F9CA55"/>
<path d="M85.167 5.76406C85.167 4.29873 86.303 3.11206 87.7043 3.11206C89.1057 3.11206 90.2417 4.30006 90.2417 5.76406C90.2417 5.76406 90.955 13.6867 91.831 18.2574C93.0017 20.7454 93.6563 24.5001 93.5123 26.5881C93.5843 27.1187 101.012 33.2627 101.012 33.2627C100.638 35.6801 98.0003 41.4694 94.667 44.1414L89.9723 40.2427C86.1257 39.7041 84.0283 35.8014 84.0283 34.5574C84.0283 30.5787 85.167 5.76406 85.167 5.76406V5.76406Z" fill="#FFDC5D"/>
<path d="M68 29.3333C72 29.3333 76.9173 38.7919 77.3333 42.6666C77.8947 47.8866 74.6667 47.9999 74.6667 47.9999H60V34.6666C60 34.6666 64.584 29.3333 68 29.3333Z" fill="#50A5E6"/>
<path d="M75.1331 44.7366C76.1777 44.1335 75.279 40.6213 73.1258 36.8919C70.9726 33.1625 68.3803 30.6281 67.3357 31.2312C66.2911 31.8343 67.1898 35.3465 69.343 39.0759C71.4962 42.8053 74.0885 45.3397 75.1331 44.7366Z" fill="#1C6399"/>
<path d="M81.4146 0.0146466C79.7266 -0.154687 78.3039 1.16665 78.3039 2.81865C78.3039 3.85865 78.0586 16.5773 78.0586 16.5773L83.8599 24L83.9426 2.97998C83.9426 1.50531 82.8826 0.161313 81.4146 0.0146466V0.0146466Z" fill="#F9CA55"/>
<path d="M82.8336 5.76406C82.8336 4.29873 81.6976 3.11206 80.2963 3.11206C78.8949 3.11206 77.7589 4.30006 77.7589 5.76406C77.7589 5.76406 77.0456 13.6867 76.1696 18.2574C74.9989 20.7454 74.3443 24.5001 74.4883 26.5881C74.4163 27.1187 66.9883 33.2627 66.9883 33.2627C67.3629 35.6801 70.0003 41.4694 73.3336 44.1414L78.0283 40.2427C81.8749 39.7041 83.9723 35.8014 83.9723 34.5574C83.9723 30.5787 82.8336 5.76406 82.8336 5.76406V5.76406Z" fill="#FFDC5D"/>
<path d="M83.9443 34.4307C83.3923 34.4307 82.9443 33.9827 82.9443 33.4307V3.72266C82.9443 3.17066 83.3923 2.72266 83.9443 2.72266C84.4963 2.72266 84.9443 3.17066 84.9443 3.72266V33.432C84.9457 33.9827 84.4977 34.4307 83.9443 34.4307V34.4307Z" fill="#F9CA55"/>
</g>
<g clip-path="url(#clip1)">
<path d="M135.501 9.98415C135.352 10.1335 135.238 10.3135 135.144 10.5108L135.133 10.5002L120.178 44.1881L120.193 44.2028C119.916 44.7401 120.38 45.8335 121.33 46.7855C122.281 47.7361 123.374 48.2001 123.912 47.9228L123.925 47.9361L157.613 32.9801L157.602 32.9681C157.798 32.8748 157.978 32.7615 158.129 32.6095C160.212 30.5268 156.834 23.7735 150.588 17.5255C144.338 11.2775 137.585 7.90148 135.501 9.98415V9.98415Z" fill="#DD2E44"/>
<path d="M137.333 16L120.554 43.3413L120.178 44.188L120.193 44.2027C119.916 44.74 120.38 45.8333 121.33 46.7853C121.64 47.0947 121.961 47.3293 122.276 47.528L142.666 22.6667L137.333 16Z" fill="#EA596E"/>
<path d="M150.683 17.4214C156.909 23.6507 160.367 30.2907 158.401 32.2534C156.437 34.2187 149.797 30.7627 143.567 24.5361C137.339 18.3067 133.883 11.6641 135.847 9.70006C137.812 7.73606 144.452 11.1921 150.683 17.4214V17.4214Z" fill="#A0041E"/>
<path d="M144.786 18.1453C144.521 18.36 144.174 18.472 143.808 18.432C142.65 18.3067 141.677 17.904 140.996 17.268C140.274 16.5947 139.918 15.6907 140.016 14.7853C140.186 13.196 141.781 11.7373 144.5 12.0307C145.557 12.144 146.029 11.804 146.045 11.6413C146.064 11.48 145.676 11.0467 144.618 10.932C143.461 10.8067 142.488 10.404 141.805 9.76799C141.084 9.09466 140.726 8.19066 140.825 7.28532C140.998 5.69599 142.592 4.23732 145.308 4.53199C146.078 4.61465 146.485 4.45599 146.657 4.35332C146.794 4.26932 146.849 4.18932 146.854 4.14265C146.87 3.98132 146.488 3.54799 145.428 3.43332C144.696 3.35332 144.165 2.69732 144.246 1.96399C144.325 1.23199 144.98 0.702655 145.714 0.782655C148.43 1.07465 149.678 2.83865 149.506 4.42932C149.333 6.02132 147.74 7.47732 145.021 7.18532C144.25 7.10132 143.848 7.26132 143.674 7.36399C143.537 7.44665 143.481 7.52799 143.476 7.57332C143.458 7.73599 143.844 8.16799 144.904 8.28265C147.62 8.57599 148.868 10.3387 148.696 11.9293C148.524 13.5187 146.93 14.9773 144.213 14.6827C143.442 14.6 143.037 14.76 142.864 14.8613C142.725 14.9467 142.672 15.0267 142.666 15.072C142.649 15.2333 143.034 15.6667 144.093 15.7813C144.824 15.8613 145.356 16.5187 145.274 17.2507C145.237 17.616 145.052 17.932 144.786 18.1453V18.1453Z" fill="#AA8DD8"/>
<path d="M160.881 30.4761C163.512 29.7334 165.327 30.9067 165.759 32.4467C166.191 33.9854 165.255 35.9334 162.625 36.6734C161.599 36.9614 161.291 37.4521 161.332 37.6081C161.377 37.7654 161.899 38.0241 162.923 37.7347C165.552 36.9947 167.367 38.1681 167.799 39.7067C168.233 41.2467 167.295 43.1921 164.664 43.9334C163.639 44.2214 163.329 44.7134 163.375 44.8694C163.419 45.0254 163.939 45.2841 164.964 44.9961C165.671 44.7974 166.409 45.2094 166.608 45.9174C166.805 46.6267 166.393 47.3627 165.684 47.5627C163.056 48.3027 161.24 47.1321 160.805 45.5907C160.373 44.0521 161.311 42.1067 163.943 41.3654C164.969 41.0761 165.277 40.5867 165.232 40.4294C165.189 40.2734 164.669 40.0134 163.645 40.3014C161.013 41.0427 159.2 39.8721 158.767 38.3294C158.333 36.7907 159.271 34.8454 161.901 34.1027C162.925 33.8161 163.233 33.3227 163.191 33.1681C163.145 33.0107 162.627 32.7521 161.601 33.0401C160.892 33.2401 160.157 32.8267 159.957 32.1187C159.759 31.4121 160.172 30.6761 160.881 30.4761V30.4761Z" fill="#77B255"/>
<path d="M150.667 26.88C150.275 26.88 149.889 26.708 149.625 26.38C149.165 25.804 149.259 24.9653 149.833 24.5053C150.123 24.272 157.057 18.8267 166.855 20.228C167.585 20.332 168.091 21.0067 167.987 21.736C167.883 22.464 167.214 22.976 166.478 22.8667C157.821 21.6373 151.562 26.5387 151.501 26.588C151.253 26.7853 150.959 26.88 150.667 26.88V26.88Z" fill="#AA8DD8"/>
<path d="M127.672 21.3335C127.545 21.3335 127.416 21.3148 127.288 21.2775C126.582 21.0655 126.182 20.3228 126.394 19.6175C127.905 14.5868 129.274 6.55879 127.592 4.46545C127.404 4.22812 127.12 3.99479 126.469 4.04412C125.218 4.14012 125.337 6.77879 125.338 6.80545C125.394 7.54012 124.842 8.18012 124.109 8.23479C123.364 8.28012 122.734 7.73879 122.68 7.00412C122.542 5.16545 123.114 1.62412 126.269 1.38545C127.677 1.27879 128.846 1.76812 129.672 2.79479C132.833 6.72945 129.624 18.1361 128.949 20.3841C128.776 20.9615 128.245 21.3335 127.672 21.3335Z" fill="#77B255"/>
<path d="M154 14.6667C155.105 14.6667 156 13.7713 156 12.6667C156 11.5622 155.105 10.6667 154 10.6667C152.895 10.6667 152 11.5622 152 12.6667C152 13.7713 152.895 14.6667 154 14.6667Z" fill="#5C913B"/>
<path d="M122.667 26.6666C124.139 26.6666 125.333 25.4727 125.333 23.9999C125.333 22.5272 124.139 21.3333 122.667 21.3333C121.194 21.3333 120 22.5272 120 23.9999C120 25.4727 121.194 26.6666 122.667 26.6666Z" fill="#9266CC"/>
<path d="M163.333 28C164.438 28 165.333 27.1046 165.333 26C165.333 24.8954 164.438 24 163.333 24C162.228 24 161.333 24.8954 161.333 26C161.333 27.1046 162.228 28 163.333 28Z" fill="#5C913B"/>
<path d="M151.333 44C152.438 44 153.333 43.1046 153.333 42C153.333 40.8954 152.438 40 151.333 40C150.228 40 149.333 40.8954 149.333 42C149.333 43.1046 150.228 44 151.333 44Z" fill="#5C913B"/>
<path d="M157.334 8.00008C158.806 8.00008 160 6.80617 160 5.33341C160 3.86066 158.806 2.66675 157.334 2.66675C155.861 2.66675 154.667 3.86066 154.667 5.33341C154.667 6.80617 155.861 8.00008 157.334 8.00008Z" fill="#FFCC4D"/>
<path d="M163.333 13.3333C164.438 13.3333 165.333 12.4378 165.333 11.3333C165.333 10.2287 164.438 9.33325 163.333 9.33325C162.228 9.33325 161.333 10.2287 161.333 11.3333C161.333 12.4378 162.228 13.3333 163.333 13.3333Z" fill="#FFCC4D"/>
<path d="M159.333 18.6667C160.438 18.6667 161.333 17.7713 161.333 16.6667C161.333 15.5622 160.438 14.6667 159.333 14.6667C158.228 14.6667 157.333 15.5622 157.333 16.6667C157.333 17.7713 158.228 18.6667 159.333 18.6667Z" fill="#FFCC4D"/>
<path d="M130 33.3333C131.105 33.3333 132 32.4378 132 31.3333C132 30.2287 131.105 29.3333 130 29.3333C128.895 29.3333 128 30.2287 128 31.3333C128 32.4378 128.895 33.3333 130 33.3333Z" fill="#FFCC4D"/>
</g>
<path d="M48 24C48 37.2547 37.2547 48 24 48C10.7467 48 0 37.2547 0 24C0 10.7467 10.7467 0 24 0C37.2547 0 48 10.7467 48 24Z" fill="#FFCC4D"/>
<path d="M15.3333 23.9999C17.1743 23.9999 18.6667 20.7167 18.6667 16.6666C18.6667 12.6165 17.1743 9.33325 15.3333 9.33325C13.4924 9.33325 12 12.6165 12 16.6666C12 20.7167 13.4924 23.9999 15.3333 23.9999Z" fill="#664500"/>
<path d="M32.6663 23.9999C34.5073 23.9999 35.9997 20.7167 35.9997 16.6666C35.9997 12.6165 34.5073 9.33325 32.6663 9.33325C30.8254 9.33325 29.333 12.6165 29.333 16.6666C29.333 20.7167 30.8254 23.9999 32.6663 23.9999Z" fill="#664500"/>
<path d="M23.9997 29.3333C19.169 29.3333 15.9637 28.7706 11.9997 27.9999C11.0943 27.8253 9.33301 27.9999 9.33301 30.6666C9.33301 35.9999 15.4597 42.6666 23.9997 42.6666C32.5383 42.6666 38.6663 35.9999 38.6663 30.6666C38.6663 27.9999 36.905 27.8239 35.9997 27.9999C32.0357 28.7706 28.8303 29.3333 23.9997 29.3333Z" fill="#664500"/>
<path d="M12 30.6667C12 30.6667 16 32.0001 24 32.0001C32 32.0001 36 30.6667 36 30.6667C36 30.6667 33.3333 36.0001 24 36.0001C14.6667 36.0001 12 30.6667 12 30.6667Z" fill="white"/>
<defs>
<clipPath id="clip0">
<rect width="48" height="48" fill="white" transform="translate(60)"/>
</clipPath>
<clipPath id="clip1">
<rect width="48" height="48" fill="white" transform="translate(120)"/>
</clipPath>
</defs>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="168" height="48" fill="none" xmlns:v="https://vecta.io/nano"><g clip-path="url(#A)"><path d="M100 29.333c-3.999 0-8.917 9.459-9.333 13.333-.561 5.22 2.667 5.333 2.667 5.333H108V34.667s-4.584-5.333-8-5.333z" fill="#50a5e6"/><path d="M98.655 39.078c2.153-3.729 3.052-7.242 2.007-7.845s-3.637 1.931-5.79 5.661-3.052 7.242-2.007 7.845 3.637-1.931 5.79-5.661z" fill="#1c6399"/><path d="M86.781 0a2.85 2.85 0 0 1 2.851 2.851c0 1.052.891 14.432.891 14.432L83.931 24V2.851A2.85 2.85 0 0 1 86.781 0z" fill="#f9ca55"/><path d="M85.167 5.764c0-1.465 1.136-2.652 2.537-2.652s2.537 1.188 2.537 2.652c0 0 .713 7.923 1.589 12.493 1.171 2.488 1.825 6.243 1.681 8.331.072.531 7.5 6.675 7.5 6.675-.374 2.417-3.012 8.207-6.345 10.879l-4.695-3.899c-3.847-.539-5.944-4.441-5.944-5.685 0-3.979 1.139-28.793 1.139-28.793h0z" fill="#ffdc5d"/><path d="M68 29.333c4 0 8.917 9.459 9.333 13.333.561 5.22-2.667 5.333-2.667 5.333H60V34.667s4.584-5.333 8-5.333z" fill="#50a5e6"/><path d="M75.133 44.737c1.045-.603.146-4.115-2.007-7.845s-4.745-6.264-5.79-5.661-.146 4.115 2.007 7.845 4.745 6.264 5.79 5.661z" fill="#1c6399"/><path d="M81.415.015a2.82 2.82 0 0 0-3.111 2.804c0 1.04-.245 13.759-.245 13.759L83.86 24l.083-21.02c0-1.475-1.06-2.819-2.528-2.965h0z" fill="#f9ca55"/><path d="M82.834 5.764c0-1.465-1.136-2.652-2.537-2.652S77.759 4.3 77.759 5.764c0 0-.713 7.923-1.589 12.493-1.171 2.488-1.825 6.243-1.681 8.331-.072.531-7.5 6.675-7.5 6.675.375 2.417 3.012 8.207 6.345 10.879l4.695-3.899c3.847-.539 5.944-4.441 5.944-5.685 0-3.979-1.139-28.793-1.139-28.793h0z" fill="#ffdc5d"/><path d="M83.944 34.431a1 1 0 0 1-1-1V3.723a1 1 0 1 1 2 0v29.709a1 1 0 0 1-1 .999h0z" fill="#f9ca55"/></g><g clip-path="url(#B)"><path d="M135.501 9.984c-.149.149-.263.329-.357.527l-.011-.011-14.955 33.688.015.015c-.277.537.187 1.631 1.137 2.583s2.044 1.415 2.582 1.137l.013.013 33.688-14.956-.011-.012c.196-.093.376-.207.527-.359 2.083-2.083-1.295-8.836-7.541-15.084s-13.003-9.624-15.087-7.541h0z" fill="#dd2e44"/><path d="M137.333 16l-16.779 27.341-.376.847.015.015c-.277.537.187 1.631 1.137 2.583.31.309.631.544.946.743l20.39-24.861L137.333 16z" fill="#ea596e"/><path d="M150.683 17.421c6.226 6.229 9.684 12.869 7.718 14.832s-8.604-1.491-14.834-7.717-9.684-12.872-7.72-14.836 8.605 1.492 14.836 7.721h0z" fill="#a0041e"/><path d="M144.786 18.145a1.32 1.32 0 0 1-.978.287c-1.158-.125-2.131-.528-2.812-1.164-.722-.673-1.078-1.577-.98-2.483.17-1.589 1.765-3.048 4.484-2.755 1.057.113 1.529-.227 1.545-.389s-.369-.595-1.427-.709c-1.157-.125-2.13-.528-2.813-1.164-.721-.673-1.079-1.577-.98-2.483.173-1.589 1.767-3.048 4.483-2.753.77.083 1.177-.076 1.349-.179.137-.084.192-.164.197-.211.016-.161-.366-.595-1.426-.709-.732-.08-1.263-.736-1.182-1.469a1.33 1.33 0 0 1 1.468-1.181c2.716.292 3.964 2.056 3.792 3.647s-1.766 3.048-4.485 2.756c-.771-.084-1.173.076-1.347.179-.137.083-.193.164-.198.209-.018.163.368.595 1.428.709 2.716.293 3.964 2.056 3.792 3.647s-1.766 3.048-4.483 2.753c-.771-.083-1.176.077-1.349.179-.139.085-.192.165-.198.211-.017.161.368.595 1.427.709.731.08 1.263.737 1.181 1.469-.037.365-.222.681-.488.895h0z" fill="#aa8dd8"/><path d="M160.881 30.476c2.631-.743 4.446.431 4.878 1.971s-.504 3.487-3.134 4.227c-1.026.288-1.334.779-1.293.935s.567.416 1.591.127c2.629-.74 4.444.433 4.876 1.972s-.504 3.485-3.135 4.227c-1.025.288-1.335.78-1.289.936s.564.415 1.589.127c.707-.199 1.445.213 1.644.921s-.215 1.445-.924 1.645c-2.628.74-4.444-.431-4.879-1.972s.506-3.484 3.138-4.225c1.026-.289 1.334-.779 1.289-.936s-.563-.416-1.587-.128c-2.632.741-4.445-.429-4.878-1.972s.504-3.484 3.134-4.227c1.024-.287 1.332-.78 1.29-.935s-.564-.416-1.59-.128c-.709.2-1.444-.213-1.644-.921s.215-1.443.924-1.643h0z" fill="#77b255"/><path d="M150.667 26.88c-.392 0-.778-.172-1.042-.5-.46-.576-.366-1.415.208-1.875.29-.233 7.224-5.679 17.022-4.277.73.104 1.236.779 1.132 1.508s-.773 1.24-1.509 1.131c-8.657-1.229-14.916 3.672-14.977 3.721-.248.197-.542.292-.834.292h0z" fill="#aa8dd8"/><path d="M127.672 21.334a1.37 1.37 0 0 1-.384-.056c-.706-.212-1.106-.955-.894-1.66 1.511-5.031 2.88-13.059 1.198-15.152-.188-.237-.472-.471-1.123-.421-1.251.096-1.132 2.735-1.131 2.761.056.735-.496 1.375-1.229 1.429-.745.045-1.375-.496-1.429-1.231-.138-1.839.434-5.38 3.589-5.619 1.408-.107 2.577.383 3.403 1.409 3.161 3.935-.048 15.341-.723 17.589-.173.577-.704.949-1.277.949z" fill="#77b255"/><path d="M154 14.667a2 2 0 1 0 0-4 2 2 0 1 0 0 4z" fill="#5c913b"/><path d="M122.667 26.667c1.472 0 2.666-1.194 2.666-2.667s-1.194-2.667-2.666-2.667S120 22.527 120 24s1.194 2.667 2.667 2.667z" fill="#9266cc"/><g fill="#5c913b"><path d="M163.333 28a2 2 0 1 0 0-4 2 2 0 1 0 0 4zm-12 16a2 2 0 1 0 0-4 2 2 0 1 0 0 4z"/></g><g fill="#ffcc4d"><path d="M157.334 8C158.806 8 160 6.806 160 5.333s-1.194-2.667-2.666-2.667a2.67 2.67 0 0 0-2.667 2.667A2.67 2.67 0 0 0 157.334 8zm5.999 5.333a2 2 0 1 0 0-4 2 2 0 1 0 0 4zm-4 5.334a2 2 0 0 0 0-4 2 2 0 1 0 0 4zM130 33.333a2 2 0 1 0 0-4 2 2 0 1 0 0 4z"/></g></g><path d="M48 24c0 13.255-10.745 24-24 24S0 37.255 0 24 10.747 0 24 0s24 10.747 24 24z" fill="#ffcc4d"/><path d="M15.333 24c1.841 0 3.333-3.283 3.333-7.333s-1.492-7.333-3.333-7.333S12 12.617 12 16.667 13.492 24 15.333 24zm17.333 0C34.507 24 36 20.717 36 16.667s-1.492-7.333-3.333-7.333-3.333 3.283-3.333 7.333S30.825 24 32.666 24zM24 29.333c-4.831 0-8.036-.563-12-1.333-.905-.175-2.667 0-2.667 2.667 0 5.333 6.127 12 14.667 12s14.667-6.667 14.667-12C38.666 28 36.905 27.824 36 28c-3.964.771-7.169 1.333-12 1.333z" fill="#664500"/><path d="M12 30.667S16 32 24 32s12-1.333 12-1.333S33.333 36 24 36s-12-5.333-12-5.333z" fill="#fff"/><defs><clipPath id="A"><path fill="#fff" transform="translate(60)" d="M0 0h48v48H0z"/></clipPath><clipPath id="B"><path fill="#fff" transform="translate(120)" d="M0 0h48v48H0z"/></clipPath></defs></svg>
\ No newline at end of file
import { Channels } from "revolt.js/dist/api/objects";
import { observer } from "mobx-react-lite";
import { Channel } from "revolt.js/dist/maps/Channels";
import styled from "styled-components";
import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
import { useContext, useEffect, useState } from "preact/hooks";
import { useEffect, useState } from "preact/hooks";
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
import { FileUploader } from "../../../context/revoltjs/FileUploads";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import Button from "../../../components/ui/Button";
import InputBox from "../../../components/ui/InputBox";
interface Props {
channel:
| Channels.GroupChannel
| Channels.TextChannel
| Channels.VoiceChannel;
channel: Channel;
}
export default function Overview({ channel }: Props) {
const client = useContext(AppContext);
const Row = styled.div`
gap: 20px;
display: flex;
const [name, setName] = useState(channel.name);
.name {
flex-grow: 1;
input {
width: 100%;
}
}
`;
export default observer(({ channel }: Props) => {
const [name, setName] = useState(channel.name ?? undefined);
const [description, setDescription] = useState(channel.description ?? "");
useEffect(() => setName(channel.name), [channel.name]);
useEffect(() => setName(channel.name ?? undefined), [channel.name]);
useEffect(
() => setDescription(channel.description ?? ""),
[channel.description],
......@@ -33,18 +41,18 @@ export default function Overview({ channel }: Props) {
const [changed, setChanged] = useState(false);
function save() {
let changes: any = {};
const changes: Record<string, string | undefined> = {};
if (name !== channel.name) changes.name = name;
if (description !== channel.description)
changes.description = description;
client.channels.edit(channel._id, changes);
channel.edit(changes);
setChanged(false);
}
return (
<div className={styles.overview}>
<div className={styles.row}>
<div className="overview">
<Row>
<FileUploader
width={80}
height={80}
......@@ -52,24 +60,19 @@ export default function Overview({ channel }: Props) {
fileType="icons"
behaviour="upload"
maxFileSize={2_500_000}
onUpload={(icon) =>
client.channels.edit(channel._id, { icon })
}
previewURL={client.channels.getIconURL(
channel._id,
onUpload={(icon) => channel.edit({ icon })}
previewURL={channel.generateIconURL(
{ max_side: 256 },
true,
)}
remove={() =>
client.channels.edit(channel._id, { remove: "Icon" })
}
remove={() => channel.edit({ remove: "Icon" })}
defaultPreview={
channel.channel_type === "Group"
? "/assets/group.png"
: undefined
}
/>
<div className={styles.name}>
<div className="name">
<h3>
{channel.channel_type === "Group" ? (
<Text id="app.main.groups.name" />
......@@ -87,7 +90,7 @@ export default function Overview({ channel }: Props) {
}}
/>
</div>
</div>
</Row>
<h3>
{channel.channel_type === "Group" ? (
......@@ -114,4 +117,4 @@ export default function Overview({ channel }: Props) {
</p>
</div>
);
}
});
.overview {
.row {
gap: 20px;
display: flex;
.name {
flex-grow: 1;
input {
width: 100%;
}
}
}
}
import { Channels } from "revolt.js/dist/api/objects";
import { ChannelPermission } from "revolt.js/dist/api/permissions";
import { observer } from "mobx-react-lite";
import {
ChannelPermission,
DEFAULT_PERMISSION_DM,
} from "revolt.js/dist/api/permissions";
import { Channel } from "revolt.js/dist/maps/Channels";
import { useContext, useEffect, useState } from "preact/hooks";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import { useServer } from "../../../context/revoltjs/hooks";
import { useEffect, useState } from "preact/hooks";
import Button from "../../../components/ui/Button";
import Checkbox from "../../../components/ui/Checkbox";
import Tip from "../../../components/ui/Tip";
// ! FIXME: export from revolt.js
const DEFAULT_PERMISSION_DM =
ChannelPermission.View +
ChannelPermission.SendMessage +
ChannelPermission.ManageChannel +
ChannelPermission.VoiceCall +
ChannelPermission.InviteOthers +
ChannelPermission.EmbedLinks +
ChannelPermission.UploadFiles;
interface Props {
channel:
| Channels.GroupChannel
| Channels.TextChannel
| Channels.VoiceChannel;
channel: Channel;
}
// ! FIXME: bad code :)
export default function Permissions({ channel }: Props) {
export default observer(({ channel }: Props) => {
const [selected, setSelected] = useState("default");
const client = useContext(AppContext);
type R = { name: string; permissions: number };
let roles: { [key: string]: R } = {};
const roles: { [key: string]: R } = {};
if (channel.channel_type !== "Group") {
const server = useServer(channel.server);
const server = channel.server;
const a = server?.roles ?? {};
for (let b of Object.keys(a)) {
for (const b of Object.keys(a)) {
roles[b] = {
name: a[b].name,
permissions: a[b].permissions[1],
......@@ -73,19 +60,22 @@ export default function Permissions({ channel }: Props) {
<h2>select role</h2>
{selected}
{keys.map((id) => {
let role: R = id === "default" ? defaultRole : roles[id];
const role: R = id === "default" ? defaultRole : roles[id];
return (
<Checkbox
key={id}
checked={selected === id}
onChange={(selected) => selected && setSelected(id)}>
{role.name}
</Checkbox>
);
})}
<h2>channel per??issions</h2>
<h2>channel permissions</h2>
{Object.keys(ChannelPermission).map((perm) => {
let value =
if (perm === "View") return null;
const value =
ChannelPermission[perm as keyof typeof ChannelPermission];
if (value & DEFAULT_PERMISSION_DM) {
return (
......@@ -102,10 +92,10 @@ export default function Permissions({ channel }: Props) {
<Button
contrast
onClick={() => {
client.channels.setPermissions(channel._id, selected, p);
channel.setPermissions(selected, p);
}}>
click here to save permissions for role
</Button>
</div>
);
}
});
import { At } from "@styled-icons/boxicons-regular";
import { Envelope, Key, HelpCircle } from "@styled-icons/boxicons-solid";
import { Link, useHistory } from "react-router-dom";
import { Users } from "revolt.js/dist/api/objects";
import { At, Key, Block } from "@styled-icons/boxicons-regular";
import {
Envelope,
HelpCircle,
Lock,
Trash,
} from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { useHistory } from "react-router-dom";
import { Profile } from "revolt-api/types/Users";
import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
import { useContext, useEffect, useState } from "preact/hooks";
import { stopPropagation } from "../../../lib/stopPropagation";
import { useIntermediate } from "../../../context/intermediate/Intermediate";
import {
ClientStatus,
StatusContext,
useClient,
} from "../../../context/revoltjs/RevoltClient";
import { useForceUpdate, useSelf } from "../../../context/revoltjs/hooks";
import Tooltip from "../../../components/common/Tooltip";
import UserIcon from "../../../components/common/user/UserIcon";
import Button from "../../../components/ui/Button";
import Overline from "../../../components/ui/Overline";
import Tip from "../../../components/ui/Tip";
import CategoryButton from "../../../components/ui/fluent/CategoryButton";
export function Account() {
export const Account = observer(() => {
const { openScreen, writeClipboard } = useIntermediate();
const status = useContext(StatusContext);
const ctx = useForceUpdate();
const user = useSelf(ctx);
if (!user) return null;
const client = useClient();
const [email, setEmail] = useState("...");
const [revealEmail, setRevealEmail] = useState(false);
const [profile, setProfile] = useState<undefined | Users.Profile>(
undefined,
);
const [profile, setProfile] = useState<undefined | Profile>(undefined);
const history = useHistory();
function switchPage(to: string) {
......@@ -41,134 +45,161 @@ export function Account() {
useEffect(() => {
if (email === "..." && status === ClientStatus.ONLINE) {
ctx.client
client
.req("GET", "/auth/user")
.then((account) => setEmail(account.email));
}
if (profile === undefined && status === ClientStatus.ONLINE) {
ctx.client.users
.fetchProfile(user._id)
client
.user!.fetchProfile()
.then((profile) => setProfile(profile ?? {}));
}
}, [status]);
}, [client, email, profile, status]);
return (
<div className={styles.user}>
<div className={styles.banner}>
<UserIcon
className={styles.avatar}
target={user}
size={72}
onClick={() => switchPage("profile")}
/>
<div className={styles.userDetail}>
<div className={styles.username}>@{user.username}</div>
<div className={styles.userid}>
<Tooltip
content={
<Text id="app.settings.pages.account.unique_id" />
}>
<HelpCircle size={16} />
</Tooltip>
<Tooltip content={<Text id="app.special.copy" />}>
<a onClick={() => writeClipboard(user._id)}>
{user._id}
</a>
</Tooltip>
<div className={styles.container}>
<UserIcon
className={styles.avatar}
target={client.user!}
size={72}
onClick={() => switchPage("profile")}
/>
<div className={styles.userDetail}>
@{client.user!.username}
<div className={styles.userid}>
<Tooltip
content={
<Text id="app.settings.pages.account.unique_id" />
}>
<HelpCircle size={16} />
</Tooltip>
<Tooltip content={<Text id="app.special.copy" />}>
<a
onClick={() =>
writeClipboard(client.user!._id)
}>
{client.user!._id}
</a>
</Tooltip>
</div>
</div>
</div>
<Button onClick={() => switchPage("profile")} contrast>
<Text id="app.settings.pages.profile.edit_profile" />
</Button>
</div>
<div className={styles.details}>
<div>
{(
[
["username", user.username, <At size={24} />],
["email", email, <Envelope size={24} />],
["password", "***********", <Key size={24} />],
[
"username",
client.user!.username,
<At key="at" size={24} />,
],
["email", email, <Envelope key="envelope" size={24} />],
["password", "•••••••••", <Key key="key" size={24} />],
] as const
).map(([field, value, icon]) => (
<div>
{icon}
<div className={styles.detail}>
<div className={styles.subtext}>
<Text id={`login.${field}`} />
</div>
<p>
{field === "email" ? (
revealEmail ? (
value
) : (
<>
***********@{value.split("@").pop()}{" "}
<a
onClick={() =>
setRevealEmail(true)
}>
<Text id="app.special.modals.actions.reveal" />
</a>
</>
)
<CategoryButton
key={field}
icon={icon}
description={
field === "email" ? (
revealEmail ? (
<>
{value}{" "}
<a
onClick={(ev) =>
stopPropagation(
ev,
setRevealEmail(false),
)
}>
<Text id="app.special.modals.actions.hide" />
</a>
</>
) : (
value
)}
</p>
</div>
<div>
<Button
onClick={() =>
openScreen({
id: "modify_account",
field: field,
})
}
contrast>
<Text id="app.settings.pages.account.change_field" />
</Button>
</div>
</div>
<>
•••••••••••@{value.split("@").pop()}{" "}
<a
onClick={(ev) =>
stopPropagation(
ev,
setRevealEmail(true),
)
}>
<Text id="app.special.modals.actions.reveal" />
</a>
</>
)
) : (
value
)
}
account
action="chevron"
onClick={() =>
openScreen({
id: "modify_account",
field,
})
}>
<Text id={`login.${field}`} />
</CategoryButton>
))}
</div>
<h3>
<Text id="app.settings.pages.account.account_management.title" />
</h3>
<h5>
<Text id="app.settings.pages.account.account_management.description" />
</h5>
<h3>
<Text id="app.settings.pages.account.2fa.title" />
</h3>
<h5>
Currently work in progress, see{" "}
{/*<Text id="app.settings.pages.account.2fa.description" />*/}
Two-factor authentication is currently work-in-progress, see{" "}
{` `}
<a
href="https://gitlab.insrt.uk/insert/rauth/-/issues/2"
target="_blank">
target="_blank"
rel="noreferrer">
tracking issue here
</a>
.
</h5>
{/*<h5><Text id="app.settings.pages.account.two_factor_auth.description" /></h5>
<Button accent compact>
<Text id="app.settings.pages.account.two_factor_auth.add_auth" />
</Button>*/}
<CategoryButton
icon={<Lock size={24} color="var(--error)" />}
description={"Set up 2FA Authentication on your account."}
disabled
action="chevron">
Set up Two-factor authentication
</CategoryButton>
<h3>
<Text id="app.settings.pages.account.manage.title" />
</h3>
<h5>
<Text id="app.settings.pages.account.manage.description" />
</h5>
<div className={styles.buttons}>
{/* <Button contrast>
<Text id="app.settings.pages.account.manage.disable" />
</Button> */}
<a href="mailto:contact@revolt.chat?subject=Delete%20my%20account">
<Button error compact>
<Text id="app.settings.pages.account.manage.delete" />
</Button>
</a>
</div>
<CategoryButton
icon={<Block size={24} color="var(--error)" />}
description={
"Disable your account. You won't be able to access it unless you log back in."
}
disabled
action={<Text id="general.unavailable" />}>
<Text id="app.settings.pages.account.manage.disable" />
</CategoryButton>
<a href="mailto:contact@revolt.chat?subject=Delete%20my%20account">
<CategoryButton
icon={<Trash size={24} color="var(--error)" />}
description={
"Delete your account, including all of your data."
}
hover
action="external">
<Text id="app.settings.pages.account.manage.delete" />
</CategoryButton>
</a>
<Tip>
<span>
<Text id="app.settings.tips.account.a" />
......@@ -179,4 +210,4 @@ export function Account() {
</Tip>
</div>
);
}
});
// @ts-ignore
import { Reset, Import } from "@styled-icons/boxicons-regular";
import { Pencil } from "@styled-icons/boxicons-solid";
// @ts-expect-error shade-blend-color does not have typings.
import pSBC from "shade-blend-color";
import styles from "./Panes.module.scss";
......@@ -15,10 +17,12 @@ import { EmojiPacks, Settings } from "../../../redux/reducers/settings";
import {
DEFAULT_FONT,
DEFAULT_MONO_FONT,
Fonts,
FONTS,
FONT_KEYS,
MONOSCAPE_FONTS,
MONOSCAPE_FONT_KEYS,
MonospaceFonts,
MONOSPACE_FONTS,
MONOSPACE_FONT_KEYS,
Theme,
ThemeContext,
ThemeOptions,
......@@ -26,6 +30,7 @@ import {
import { useIntermediate } from "../../../context/intermediate/Intermediate";
import CollapsibleSection from "../../../components/common/CollapsibleSection";
import Tooltip from "../../../components/common/Tooltip";
import Button from "../../../components/ui/Button";
import Checkbox from "../../../components/ui/Checkbox";
import ColourSwatches from "../../../components/ui/ColourSwatches";
......@@ -54,12 +59,12 @@ export function Component(props: Props) {
});
}
function pushOverride(custom: Partial<Theme>) {
const pushOverride = useCallback((custom: Partial<Theme>) => {
dispatch({
type: "SETTINGS_SET_THEME_OVERRIDE",
custom,
});
}
}, []);
function setAccent(accent: string) {
setOverride({
......@@ -78,12 +83,14 @@ export function Component(props: Props) {
});
}
const setOverride = useCallback(debounce(pushOverride, 200), []) as (
custom: Partial<Theme>,
) => void;
// eslint-disable-next-line react-hooks/exhaustive-deps
const setOverride = useCallback(
debounce(pushOverride as (...args: unknown[]) => void, 200),
[pushOverride],
) as (custom: Partial<Theme>) => void;
const [css, setCSS] = useState(props.settings.theme?.custom?.css ?? "");
useEffect(() => setOverride({ css }), [css]);
useEffect(() => setOverride({ css }), [setOverride, css]);
const selected = props.settings.theme?.preset ?? "dark";
return (
......@@ -94,12 +101,15 @@ export function Component(props: Props) {
<div className={styles.themes}>
<div className={styles.theme}>
<img
loading="eager"
src={lightSVG}
draggable={false}
data-active={selected === "light"}
onClick={() =>
selected !== "light" &&
setTheme({ preset: "light" })
}
onContextMenu={(e) => e.preventDefault()}
/>
<h4>
<Text id="app.settings.pages.appearance.color.light" />
......@@ -107,11 +117,14 @@ export function Component(props: Props) {
</div>
<div className={styles.theme}>
<img
loading="eager"
src={darkSVG}
draggable={false}
data-active={selected === "dark"}
onClick={() =>
selected !== "dark" && setTheme({ preset: "dark" })
}
onContextMenu={(e) => e.preventDefault()}
/>
<h4>
<Text id="app.settings.pages.appearance.color.dark" />
......@@ -161,14 +174,15 @@ export function Component(props: Props) {
<ComboBox
value={theme.font ?? DEFAULT_FONT}
onChange={(e) =>
pushOverride({ font: e.currentTarget.value as any })
pushOverride({ font: e.currentTarget.value as Fonts })
}>
{FONT_KEYS.map((key) => (
<option value={key}>
<option value={key} key={key}>
{FONTS[key as keyof typeof FONTS].name}
</option>
))}
</ComboBox>
{/* TOFIX: Only show when a font with ligature support is selected, i.e.: Inter.*/}
<p>
<Checkbox
checked={props.settings.theme?.ligatures === true}
......@@ -194,13 +208,19 @@ export function Component(props: Props) {
className={styles.button}
onClick={() => setEmojiPack("mutant")}
data-active={emojiPack === "mutant"}>
<img src={mutantSVG} draggable={false} />
<img
loading="eager"
src={mutantSVG}
draggable={false}
onContextMenu={(e) => e.preventDefault()}
/>
</div>
<h4>
Mutant Remix{" "}
<a
href="https://mutant.revolt.chat"
target="_blank">
target="_blank"
rel="noreferrer">
(by Revolt)
</a>
</h4>
......@@ -210,7 +230,12 @@ export function Component(props: Props) {
className={styles.button}
onClick={() => setEmojiPack("twemoji")}
data-active={emojiPack === "twemoji"}>
<img src={twemojiSVG} draggable={false} />
<img
loading="eager"
src={twemojiSVG}
draggable={false}
onContextMenu={(e) => e.preventDefault()}
/>
</div>
<h4>Twemoji</h4>
</div>
......@@ -221,7 +246,12 @@ export function Component(props: Props) {
className={styles.button}
onClick={() => setEmojiPack("openmoji")}
data-active={emojiPack === "openmoji"}>
<img src={openmojiSVG} draggable={false} />
<img
loading="eager"
src={openmojiSVG}
draggable={false}
onContextMenu={(e) => e.preventDefault()}
/>
</div>
<h4>Openmoji</h4>
</div>
......@@ -230,7 +260,12 @@ export function Component(props: Props) {
className={styles.button}
onClick={() => setEmojiPack("noto")}
data-active={emojiPack === "noto"}>
<img src={notoSVG} draggable={false} />
<img
loading="eager"
src={notoSVG}
draggable={false}
onContextMenu={(e) => e.preventDefault()}
/>
</div>
<h4>Noto Emoji</h4>
</div>
......@@ -238,47 +273,61 @@ export function Component(props: Props) {
</div>
<CollapsibleSection
id="settings_advanced_appearance"
defaultValue={false}
summary={<Text id="app.settings.pages.appearance.advanced" />}>
<h3>
<Text id="app.settings.pages.appearance.overrides" />
</h3>
id="settings_overrides"
summary={<Text id="app.settings.pages.appearance.overrides" />}>
<div className={styles.actions}>
<Button contrast onClick={() => setTheme({ custom: {} })}>
<Text id="app.settings.pages.appearance.reset_overrides" />
</Button>
<Button
contrast
<Tooltip
content={
<Text id="app.settings.pages.appearance.reset_overrides" />
}>
<Button
contrast
iconbutton
onClick={() => setTheme({ custom: {} })}>
<Reset size={22} />
</Button>
</Tooltip>
<div
className={styles.code}
onClick={() => writeClipboard(JSON.stringify(theme))}>
<Text id="app.settings.pages.appearance.export_clipboard" />
</Button>
<Button
contrast
onClick={async () => {
const text = await navigator.clipboard.readText();
setOverride(JSON.parse(text));
}}>
<Text id="app.settings.pages.appearance.import_clipboard" />
</Button>
<Button
contrast
onClick={async () => {
openScreen({
id: "_input",
question: (
<Text id="app.settings.pages.appearance.import_theme" />
),
field: (
<Text id="app.settings.pages.appearance.theme_data" />
),
callback: async (string) =>
setOverride(JSON.parse(string)),
});
}}>
<Text id="app.settings.pages.appearance.import_manual" />
</Button>
<Tooltip content={<Text id="app.special.copy" />}>
{" "}
{/*TOFIX: Try to put the tooltip above the .code div without messing up the css challenge */}
{JSON.stringify(theme)}
</Tooltip>
</div>
<Tooltip
content={
<Text id="app.settings.pages.appearance.import" />
}>
<Button
contrast
iconbutton
onClick={async () => {
try {
const text =
await navigator.clipboard.readText();
setOverride(JSON.parse(text));
} catch (err) {
openScreen({
id: "_input",
question: (
<Text id="app.settings.pages.appearance.import_theme" />
),
field: (
<Text id="app.settings.pages.appearance.theme_data" />
),
callback: async (string) =>
setOverride(JSON.parse(string)),
});
}
}}>
<Import size={22} />
</Button>
</Tooltip>
</div>
<h3>App</h3>
<div className={styles.overrides}>
{(
[
......@@ -308,23 +357,34 @@ export function Component(props: Props) {
"hover",
] as const
).map((x) => (
<div className={styles.entry} key={x}>
<div
className={styles.entry}
key={x}
style={{ backgroundColor: theme[x] }}>
<div className={styles.input}>
<input
type="color"
value={theme[x]}
onChange={(v) =>
setOverride({
[x]: v.currentTarget.value,
})
}
/>
</div>
<span>{x}</span>
<div className={styles.override}>
<div
className={styles.picker}
style={{ backgroundColor: theme[x] }}>
<input
type="color"
value={theme[x]}
onChange={(v) =>
setOverride({
[x]: v.currentTarget.value,
})
}
/>
onClick={(e) =>
e.currentTarget.parentElement?.parentElement
?.querySelector("input")
?.click()
}>
<Pencil size={24} />
</div>
<InputBox
type="text"
className={styles.text}
value={theme[x]}
onChange={(y) =>
......@@ -337,22 +397,28 @@ export function Component(props: Props) {
</div>
))}
</div>
</CollapsibleSection>
<CollapsibleSection
id="settings_advanced_appearance"
defaultValue={false}
summary={<Text id="app.settings.pages.appearance.advanced" />}>
<h3>
<Text id="app.settings.pages.appearance.mono_font" />
</h3>
<ComboBox
value={theme.monoscapeFont ?? DEFAULT_MONO_FONT}
value={theme.monospaceFont ?? DEFAULT_MONO_FONT}
onChange={(e) =>
pushOverride({
monoscapeFont: e.currentTarget.value as any,
monospaceFont: e.currentTarget
.value as MonospaceFonts,
})
}>
{MONOSCAPE_FONT_KEYS.map((key) => (
<option value={key}>
{MONOSPACE_FONT_KEYS.map((key) => (
<option value={key} key={key}>
{
MONOSCAPE_FONTS[
key as keyof typeof MONOSCAPE_FONTS
MONOSPACE_FONTS[
key as keyof typeof MONOSPACE_FONTS
].name
}
</option>
......
......@@ -6,6 +6,7 @@ import { connectState } from "../../../redux/connector";
import {
AVAILABLE_EXPERIMENTS,
ExperimentOptions,
EXPERIMENTS,
} from "../../../redux/reducers/experiments";
import Checkbox from "../../../components/ui/Checkbox";
......@@ -22,6 +23,7 @@ export function Component(props: Props) {
</h3>
{AVAILABLE_EXPERIMENTS.map((key) => (
<Checkbox
key={key}
checked={(props.options?.enabled ?? []).indexOf(key) > -1}
onChange={(enabled) =>
dispatch({
......@@ -30,13 +32,9 @@ export function Component(props: Props) {
: "EXPERIMENTS_DISABLE",
key,
})
}>
<Text id={`app.settings.pages.experiments.titles.${key}`} />
<p>
<Text
id={`app.settings.pages.experiments.descriptions.${key}`}
/>
</p>
}
description={EXPERIMENTS[key].description}>
{EXPERIMENTS[key].title}
</Checkbox>
))}
{AVAILABLE_EXPERIMENTS.length === 0 && (
......
......@@ -2,7 +2,7 @@ import styles from "./Panes.module.scss";
import { Localizer, Text } from "preact-i18n";
import { useState } from "preact/hooks";
import { useSelf } from "../../../context/revoltjs/hooks";
import { useClient } from "../../../context/revoltjs/RevoltClient";
import Button from "../../../components/ui/Button";
import InputBox from "../../../components/ui/InputBox";
......@@ -10,7 +10,7 @@ import Radio from "../../../components/ui/Radio";
import TextArea from "../../../components/ui/TextArea";
export function Feedback() {
const user = useSelf();
const client = useClient();
const [other, setOther] = useState("");
const [description, setDescription] = useState("");
const [state, setState] = useState<"ready" | "sending" | "sent">("ready");
......@@ -28,7 +28,7 @@ export function Feedback() {
checked,
other,
description,
name: user?.username ?? "Unknown User",
name: client.user!.username,
}),
mode: "no-cors",
});
......@@ -57,23 +57,9 @@ export function Feedback() {
onSelect={() => setChecked("Feature Request")}>
<Text id="app.settings.pages.feedback.feature" />
</Radio>
{(location.hostname === "vite.revolt.chat" ||
location.hostname === "local.revolt.chat") && (
<Radio
disabled={state === "sending"}
checked={other === "Revite"}
onSelect={() => {
setChecked("__other_option__");
setOther("Revite");
}}>
Issues with Revite
</Radio>
)}
<Radio
disabled={state === "sending"}
checked={
checked === "__other_option__" && other !== "Revite"
}
checked={checked === "__other_option__"}
onSelect={() => setChecked("__other_option__")}>
<Localizer>
<InputBox
......@@ -84,7 +70,7 @@ export function Feedback() {
placeholder={
(
<Text id="app.settings.pages.feedback.other" />
) as any
) as unknown as string
}
/>
</Localizer>
......
......@@ -13,6 +13,7 @@ import {
import Emoji from "../../../components/common/Emoji";
import Checkbox from "../../../components/ui/Checkbox";
import Tip from "../../../components/ui/Tip";
import tokiponaSVG from "../assets/toki_pona.svg";
type Props = {
locale: Language;
......@@ -35,7 +36,11 @@ function Entry({ entry: [x, lang], locale }: { entry: Key } & Props) {
}
}}>
<div className={styles.flag}>
<Emoji size={42} emoji={lang.emoji} />
{lang.emoji === "🙂" ? (
<img src={tokiponaSVG} width={42} />
) : (
<Emoji size={42} emoji={lang.emoji} />
)}
</div>
<span className={styles.description}>{lang.display}</span>
</Checkbox>
......@@ -55,7 +60,17 @@ export function Component(props: Props) {
</h3>
<div className={styles.list}>
{languages
.filter(([, lang]) => !lang.alt)
.filter(([, lang]) => !lang.cat)
.map(([x, lang]) => (
<Entry key={x} entry={[x, lang]} {...props} />
))}
</div>
<h3>
<Text id="app.settings.pages.language.const" />
</h3>
<div className={styles.list}>
{languages
.filter(([, lang]) => lang.cat === "const")
.map(([x, lang]) => (
<Entry key={x} entry={[x, lang]} {...props} />
))}
......@@ -65,7 +80,7 @@ export function Component(props: Props) {
</h3>
<div className={styles.list}>
{languages
.filter(([, lang]) => lang.alt)
.filter(([, lang]) => lang.cat === "alt")
.map(([x, lang]) => (
<Entry key={x} entry={[x, lang]} {...props} />
))}
......@@ -76,7 +91,8 @@ export function Component(props: Props) {
</span>{" "}
<a
href="https://weblate.insrt.uk/engage/revolt/?utm_source=widget"
target="_blank">
target="_blank"
rel="noreferrer">
<Text id="app.settings.tips.languages.b" />
</a>
</Tip>
......
import { useEffect, useState } from "preact/hooks";
import Button from "../../../components/ui/Button";
import Checkbox from "../../../components/ui/Checkbox";
export function Native() {
const [config, setConfig] = useState(window.native.getConfig());
const [autoStart, setAutoStart] = useState<boolean | undefined>();
const fetchValue = () => window.native.getAutoStart().then(setAutoStart);
const [hintReload, setHintReload] = useState(false);
const [hintRelaunch, setHintRelaunch] = useState(false);
const [confirmDev, setConfirmDev] = useState(false);
useEffect(() => {
fetchValue();
}, []);
return (
<div>
<h3>App Behavior</h3>
<h5>Some options might require a restart.</h5>
<Checkbox
checked={autoStart ?? false}
disabled={typeof autoStart === "undefined"}
onChange={async (v) => {
if (v) {
await window.native.enableAutoStart();
} else {
await window.native.disableAutoStart();
}
setAutoStart(v);
}}
description="Launch Revolt when you log into your computer.">
Start with computer
</Checkbox>
<Checkbox
checked={config.discordRPC}
onChange={(discordRPC) => {
window.native.set("discordRPC", discordRPC);
setConfig({
...config,
discordRPC,
});
}}
description="Rep Revolt on your Discord status.">
Enable Discord status
</Checkbox>
<Checkbox
checked={config.build === "nightly"}
onChange={(nightly) => {
const build = nightly ? "nightly" : "stable";
window.native.set("build", build);
setHintReload(true);
setConfig({
...config,
build,
});
}}
description="Use the beta branch of Revolt.">
Revolt Nightly
</Checkbox>
<h3>Titlebar</h3>
<Checkbox
checked={!config.frame}
onChange={(frame) => {
window.native.set("frame", !frame);
setHintRelaunch(true);
setConfig({
...config,
frame: !frame,
});
}}
description={<>Let Revolt use its own window frame.</>}>
Custom window frame
</Checkbox>
<Checkbox //FIXME: In Titlebar.tsx, enable .quick css
disabled={true}
checked={!config.frame}
onChange={(frame) => {
window.native.set("frame", !frame);
setHintRelaunch(true);
setConfig({
...config,
frame: !frame,
});
}}
description="Show mute/deafen buttons on the titlebar.">
Enable quick action buttons
</Checkbox>
<h3>Advanced</h3>
<Checkbox
checked={config.hardwareAcceleration}
onChange={async (hardwareAcceleration) => {
window.native.set(
"hardwareAcceleration",
hardwareAcceleration,
);
setHintRelaunch(true);
setConfig({
...config,
hardwareAcceleration,
});
}}
description="Uses your GPU to render the app, disable if you run into visual issues.">
Hardware Acceleration
</Checkbox>
<p style={{ display: "flex", gap: "8px" }}>
<Button
contrast
compact
disabled={!hintReload}
onClick={window.native.reload}>
Reload Page
</Button>
<Button
contrast
compact
disabled={!hintRelaunch}
onClick={window.native.relaunch}>
Reload App
</Button>
</p>
<h3 style={{ marginTop: "4em" }}>Local Development Mode</h3>
{config.build === "dev" ? (
<>
<h5>Development mode is currently on.</h5>
<Button
contrast
compact
onClick={() => {
window.native.set("build", "stable");
window.native.reload();
}}>
Exit Development Mode
</Button>
</>
) : (
<>
<Checkbox
checked={confirmDev}
onChange={setConfirmDev}
description={
<>
This will change the app to the 'dev' branch,
instead loading the app from a local server on
your machine.
<br />
<b>
Without a server running,{" "}
<span style={{ color: "var(--error)" }}>
the app will not load!
</span>
</b>
</>
}>
I understand there's no going back.
</Checkbox>
<p>
<Button
error
compact
disabled={!confirmDev}
onClick={() => {
window.native.set("build", "dev");
window.native.reload();
}}>
Enter Development Mode
</Button>
</p>
</>
)}
</div>
);
}
......@@ -59,7 +59,8 @@ export function Component({ options }: Props) {
}
onChange={async (desktopEnabled) => {
if (desktopEnabled) {
let permission = await Notification.requestPermission();
const permission =
await Notification.requestPermission();
if (permission !== "granted") {
return openScreen({
id: "error",
......@@ -126,7 +127,8 @@ export function Component({ options }: Props) {
</h3>
{SOUNDS_ARRAY.map((key) => (
<Checkbox
checked={enabledSounds[key] ? true : false}
key={key}
checked={!!enabledSounds[key]}
onChange={(enabled) =>
dispatch({
type: "SETTINGS_SET_NOTIFICATION_OPTIONS",
......
.user {
.banner {
gap: 24px;
position: relative;
margin-top: 8px;
margin-bottom: 15px;
gap: 16px;
width: 100%;
padding: 1em;
padding: 12px 10px;
display: flex;
border-radius: 6px;
align-items: center;
background: var(--secondary-header);
overflow: hidden;
align-items: center;
border-radius: var(--border-radius);
.container {
display: flex;
gap: 24px;
align-items: center;
flex-direction: row;
width: 100%;
}
.userDetail {
display: flex;
flex-grow: 1;
gap: 2px;
flex-direction: column;
font-size: 1.5rem;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.avatar {
cursor: pointer;
......@@ -18,13 +40,8 @@
}
}
.username {
font-size: 1.5rem;
font-weight: 600;
}
.userid {
font-size: .875rem;
font-size: 12px;
font-weight: 600;
display: flex;
align-items: center;
......@@ -40,57 +57,28 @@
.details {
display: flex;
margin-top: 1em;
padding: 1em 0;
gap: 10px;
flex-direction: column;
/*border-top: 1px solid var(--secondary-header);
border-width: 100%;*/
> div {
gap: 12px;
padding: 4px;
/*padding: 4px;*/
padding: 8px 12px;
display: flex;
align-items: center;
flex-direction: row;
margin-bottom: 5px;
background: var(--secondary-header);
border-radius: 6px;
> svg {
flex-shrink: 0;
}
}
.detail {
flex-grow: 1;
min-width: 0;
display: flex;
flex-direction: column;
.subtext {
display: inline;
font-size: .875rem;
font-weight: 600;
color: var(--foreground);
text-transform: uppercase;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
a {
font-size: .875rem;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
p {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
}
p {
margin: 0;
font-size: 1rem;
......@@ -103,7 +91,7 @@
display: grid;
place-items: center;
grid-template-columns: minmax(auto, 100%);
> div {
width: 100%;
max-width: 560px;
......@@ -131,6 +119,20 @@
}
}
@media only screen and (max-width: 800px) {
.user {
.banner {
gap: 18px;
padding: 0;
flex-direction: column;
> button {
width: 100%;
}
}
}
}
.appearance {
.theme {
min-width: 0;
......@@ -141,12 +143,14 @@
.themes {
gap: 8px;
display: flex;
width: 100%;
img {
cursor: pointer;
border-radius: 8px;
border-radius: var(--border-radius);
transition: border 0.3s;
border: 3px solid transparent;
width: 100%;
&[data-active="true"] {
cursor: default;
......@@ -163,14 +167,13 @@
}
details {
summary {
font-size: .8125rem;
font-size: 0.8125rem;
font-weight: 700;
text-transform: uppercase;
color: var(--secondary-foreground);
cursor: pointer;
}
}
}
.emojiPack {
......@@ -190,15 +193,15 @@
}
.button {
padding: 2rem 1.5rem;
padding: 2rem 1.2rem;
display: grid;
place-items: center;
cursor: pointer;
border-radius: 8px;
transition: border 0.3s;
background: var(--hover);
border: 3px solid transparent;
border-radius: var(--border-radius);
img {
max-width: 100%;
......@@ -224,15 +227,12 @@
text-transform: unset;
a {
opacity: 0.7;
color: var(--accent);
font-weight: 600;
&:hover {
text-decoration: underline;
}
}
@media only screen and (max-width: 800px) {
......@@ -252,56 +252,92 @@
.actions {
gap: 8px;
display: flex;
flex-wrap: wrap;
margin-bottom: 8px;
margin: 18px 0 8px 0;
.code {
cursor: pointer;
display: flex;
align-items: center;
font-size: 0.875rem;
min-width: 0;
flex-grow: 1;
padding: 8px;
font-family: var(--codeblock-font);
border-radius: var(--border-radius);
background: var(--secondary-background);
> div {
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
.overrides {
row-gap: 8px;
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 16px;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
margin-bottom: 20px;
.entry {
gap: 8px;
padding: 2px;
padding: 12px;
margin-top: 8px;
.override {
display: flex;
}
border: 1px solid black;
border-radius: var(--border-radius);
span {
flex: 1;
display: block;
font-size: .875rem;
font-weight: 600;
margin-bottom: 4px;
font-size: 0.875rem;
margin-bottom: 8px;
text-transform: capitalize;
color: transparent;
background: inherit;
background-clip: text;
-webkit-background-clip: text;
filter: sepia(1) invert(1) contrast(9) grayscale(1);
}
.picker {
width: 30px;
height: 30px;
flex-shrink: 0;
border-radius: 4px;
overflow: hidden;
margin-inline-end: 4px;
.override {
gap: 8px;
display: flex;
//TOFIX - Looks wonky on Chromium
border: 1px solid black;
.picker {
width: 38px;
height: 38px;
display: grid;
cursor: pointer;
place-items: center;
border-radius: var(--border-radius);
background: var(--primary-background);
}
input[type="text"] {
width: 0;
min-width: 0;
flex-grow: 1;
}
}
.input {
width: 0;
height: 0;
position: relative;
input {
opacity: 0;
width: 30px;
height: 30px;
border: none;
display: block;
cursor: pointer;
}
}
position: relative;
.text {
border-radius: 4px;
padding: 0 4px 0;
top: 48px;
}
}
}
}
......@@ -310,21 +346,39 @@
.sessions {
.session {
display: flex;
align-items: center;
gap: 12px;
flex-direction: row;
.detail {
display: flex;
gap: 12px;
flex-grow: 1;
svg {
margin-top: 1px;
}
}
}
.entry {
margin: 8px 0;
padding: 16px;
display: flex;
border-radius: 6px;
margin: 10px 0;
flex-direction: column;
border-radius: var(--border-radius);
background: var(--secondary-header);
&[data-active="true"] {
color: var(--primary-background);
background: var(--accent);
margin-bottom: 20px;
.session .detail .info > input {
&:focus {
border-bottom: 2px solid var(--primary-background);
}
}
}
&[data-deleting="true"] {
......@@ -345,6 +399,7 @@
outline: 0;
border-radius: 0;
color: inherit;
width: 100%;
&:focus {
border-bottom: 2px solid var(--accent);
......@@ -355,25 +410,10 @@
}
}
.icon {
gap: 8px;
display: flex;
/*padding-right: 12px;*/
align-items: center;
svg {
height: 42px;
}
div svg {
height: 24px;
}
}
.label {
margin-bottom: 8px;
color: var(--primary-text);
font-size: .75rem;
font-size: 0.75rem;
font-weight: 600;
}
......@@ -383,43 +423,83 @@
flex-direction: column;
justify-content: center;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
.name {
text-transform: capitalize;
text-overflow: ellipsis;
}
.time {
font-size: .75rem;
font-size: 0.75rem;
color: var(--teriary-text);
text-overflow: ellipsis;
overflow: hidden;
}
}
}
> button {
margin-top: 20px;
}
@media only screen and (max-width: 800px) {
.session {
align-items: unset;
flex-direction: column;
gap: 20px;
> button {
width: 100%;
}
}
> button {
width: 100%;
}
}
}
.languages {
.list {
display: flex;
flex-direction: column;
margin-bottom: 1em;
gap: 8px;
.entry {
height: 50px;
display: flex;
height: 45px;
padding: 0 8px;
background: var(--secondary-header);
border-radius: var(--border-radius);
margin-top: 0;
&:hover {
background: var(--secondary-background);
}
}
.entry > span > span {
gap: 20px;
gap: 12px;
display: flex;
align-items: center;
flex-direction: row;
.flag {
display: flex;
font-size: 2.625rem;
line-height: 48px;
> div {
display: flex;
align-items: center;
justify-content: center;
}
> img {
height: 32px !important;
}
}
.description {
......@@ -435,12 +515,16 @@
flex-direction: column;
}
.experiments { /* TOFIX: Center the "No new experiments available at this time" text without having a scrollbar */
height: 100%;
.experiments {
height: calc(100% - 40px);
.empty {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
}
\ No newline at end of file
}
section {
margin-bottom: 20px;
}
import { Users } from "revolt.js/dist/api/objects";
import { Profile as ProfileI } from "revolt-api/types/Users";
import styles from "./Panes.module.scss";
import { IntlContext, Text, translate } from "preact-i18n";
import { useContext, useEffect, useState } from "preact/hooks";
import { Text } from "preact-i18n";
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
import { useTranslation } from "../../../lib/i18n";
import { UserProfile } from "../../../context/intermediate/popovers/UserProfile";
import { FileUploader } from "../../../context/revoltjs/FileUploads";
import {
ClientStatus,
StatusContext,
useClient,
} from "../../../context/revoltjs/RevoltClient";
import { useForceUpdate, useSelf } from "../../../context/revoltjs/hooks";
import AutoComplete, {
useAutoComplete,
......@@ -20,30 +21,25 @@ import AutoComplete, {
import Button from "../../../components/ui/Button";
export function Profile() {
const { intl } = useContext(IntlContext);
const status = useContext(StatusContext);
const translate = useTranslation();
const client = useClient();
const ctx = useForceUpdate();
const user = useSelf();
if (!user) return null;
const [profile, setProfile] = useState<undefined | Users.Profile>(
undefined,
);
const [profile, setProfile] = useState<undefined | ProfileI>(undefined);
// ! FIXME: temporary solution
// ! we should just announce profile changes through WS
function refreshProfile() {
ctx.client.users
.fetchProfile(user!._id)
const refreshProfile = useCallback(() => {
client
.user!.fetchProfile()
.then((profile) => setProfile(profile ?? {}));
}
}, [client.user, setProfile]);
useEffect(() => {
if (profile === undefined && status === ClientStatus.ONLINE) {
refreshProfile();
}
}, [status]);
}, [profile, status, refreshProfile]);
const [changed, setChanged] = useState(false);
function setContent(content?: string) {
......@@ -69,10 +65,9 @@ export function Profile() {
</h3>
<div className={styles.preview}>
<UserProfile
user_id={user._id}
user_id={client.user!._id}
dummy={true}
dummyProfile={profile}
onClose={() => {}}
/>
</div>
<div className={styles.row}>
......@@ -87,22 +82,15 @@ export function Profile() {
fileType="avatars"
behaviour="upload"
maxFileSize={4_000_000}
onUpload={(avatar) =>
ctx.client.users.editUser({ avatar })
}
remove={() =>
ctx.client.users.editUser({ remove: "Avatar" })
}
defaultPreview={ctx.client.users.getAvatarURL(
user._id,
onUpload={(avatar) => client.users.edit({ avatar })}
remove={() => client.users.edit({ remove: "Avatar" })}
defaultPreview={client.user!.generateAvatarURL(
{ max_side: 256 },
true,
)}
previewURL={ctx.client.users.getAvatarURL(
user._id,
previewURL={client.user!.generateAvatarURL(
{ max_side: 256 },
true,
true,
)}
/>
</div>
......@@ -117,21 +105,21 @@ export function Profile() {
fileType="backgrounds"
maxFileSize={6_000_000}
onUpload={async (background) => {
await ctx.client.users.editUser({
await client.users.edit({
profile: { background },
});
refreshProfile();
}}
remove={async () => {
await ctx.client.users.editUser({
await client.users.edit({
remove: "ProfileBackground",
});
setProfile({ ...profile, background: undefined });
}}
previewURL={
profile?.background
? ctx.client.users.getBackgroundURL(
profile,
? client.generateFileURL(
profile.background,
{ width: 1000 },
true,
)
......@@ -160,8 +148,6 @@ export function Profile() {
? "fetching"
: "placeholder"
}`,
"",
(intl as any).dictionary as Record<string, unknown>,
)}
onKeyUp={onKeyUp}
onKeyDown={onKeyDown}
......@@ -173,7 +159,7 @@ export function Profile() {
contrast
onClick={() => {
setChanged(false);
ctx.client.users.editUser({
client.users.edit({
profile: { content: profile?.content },
});
}}
......
import { HelpCircle } from "@styled-icons/boxicons-regular";
import { Chrome, Android, Apple, Windows } from "@styled-icons/boxicons-logos";
import { HelpCircle, Desktop } from "@styled-icons/boxicons-regular";
import {
Android,
Safari,
Firefoxbrowser,
Googlechrome,
Ios,
Microsoftedge,
Linux,
Macos,
Microsoftedge,
Safari,
Windows,
Opera,
} from "@styled-icons/simple-icons";
import relativeTime from "dayjs/plugin/relativeTime";
import { useHistory } from "react-router-dom";
......@@ -52,7 +50,7 @@ export function Sessions() {
);
setSessions(data);
});
}, []);
}, [client, setSessions, deviceId]);
if (typeof sessions === "undefined") {
return (
......@@ -66,15 +64,19 @@ export function Sessions() {
const name = session.friendly_name;
switch (true) {
case /firefox/i.test(name):
return <Firefoxbrowser />;
return <Firefoxbrowser size={32} />;
case /chrome/i.test(name):
return <Googlechrome />;
return <Chrome size={32} />;
case /safari/i.test(name):
return <Safari />;
return <Safari size={32} />;
case /edge/i.test(name):
return <Microsoftedge />;
return <Microsoftedge size={32} />;
case /opera/i.test(name):
return <Opera size={32} />;
case /desktop/i.test(name):
return <Desktop size={32} />;
default:
return <HelpCircle />;
return <HelpCircle size={32} />;
}
}
......@@ -82,15 +84,15 @@ export function Sessions() {
const name = session.friendly_name;
switch (true) {
case /linux/i.test(name):
return <Linux />;
return <Linux size={14} />;
case /android/i.test(name):
return <Android />;
return <Android size={14} />;
case /mac.*os/i.test(name):
return <Macos />;
return <Macos size={14} />;
case /ios/i.test(name):
return <Ios />;
return <Apple size={14} />;
case /windows/i.test(name):
return <Windows />;
return <Windows size={14} />;
default:
return null;
}
......@@ -104,7 +106,7 @@ export function Sessions() {
});
mapped.sort((a, b) => b.timestamp - a.timestamp);
let id = mapped.findIndex((x) => x.id === deviceId);
const id = mapped.findIndex((x) => x.id === deviceId);
const render = [
mapped[id],
......@@ -117,65 +119,116 @@ export function Sessions() {
<h3>
<Text id="app.settings.pages.sessions.active_sessions" />
</h3>
{render.map((session) => (
<div
className={styles.entry}
data-active={session.id === deviceId}
data-deleting={attemptingDelete.indexOf(session.id) > -1}>
{deviceId === session.id && (
<span className={styles.label}>
<Text id="app.settings.pages.sessions.this_device" />{" "}
</span>
)}
<div className={styles.session}>
<div className={styles.icon}>
{getIcon(session)}
<div>{getSystemIcon(session)}</div>
</div>
<div className={styles.info}>
<input
type="text"
className={styles.name}
value={session.friendly_name}
autocomplete="off"
/>
<span className={styles.time}>
<Text
id="app.settings.pages.sessions.created"
fields={{
time_ago: dayjs(
session.timestamp,
).fromNow(),
}}
/>
{render.map((session) => {
const systemIcon = getSystemIcon(session);
return (
<div
key={session.id}
className={styles.entry}
data-active={session.id === deviceId}
data-deleting={
attemptingDelete.indexOf(session.id) > -1
}>
{deviceId === session.id && (
<span className={styles.label}>
<Text id="app.settings.pages.sessions.this_device" />{" "}
</span>
</div>
{deviceId !== session.id && (
<Button
onClick={async () => {
setDelete([
...attemptingDelete,
session.id,
]);
await client.req(
"DELETE",
`/auth/sessions/${session.id}` as "/auth/sessions",
);
setSessions(
sessions?.filter(
(x) => x.id !== session.id,
),
);
}}
disabled={
attemptingDelete.indexOf(session.id) > -1
}>
<Text id="app.settings.pages.logOut" />
</Button>
)}
<div className={styles.session}>
<div className={styles.detail}>
<svg width={42} height={42} viewBox="0 0 32 32">
<foreignObject
x="0"
y="0"
width="32"
height="32"
mask={
systemIcon
? "url(#session)"
: undefined
}>
{getIcon(session)}
</foreignObject>
<foreignObject
x="18"
y="18"
width="14"
height="14">
{systemIcon}
</foreignObject>
</svg>
<div className={styles.info}>
<input
type="text"
className={styles.name}
value={session.friendly_name}
autocomplete="off"
style={{ pointerEvents: "none" }}
/>
<span className={styles.time}>
<Text
id="app.settings.pages.sessions.created"
fields={{
time_ago: dayjs(
session.timestamp,
).fromNow(),
}}
/>
</span>
</div>
</div>
{deviceId !== session.id && (
<Button
onClick={async () => {
setDelete([
...attemptingDelete,
session.id,
]);
await client.req(
"DELETE",
`/auth/sessions/${session.id}` as "/auth/sessions",
);
setSessions(
sessions?.filter(
(x) => x.id !== session.id,
),
);
}}
disabled={
attemptingDelete.indexOf(session.id) >
-1
}>
<Text id="app.settings.pages.logOut" />
</Button>
)}
</div>
</div>
</div>
))}
);
})}
<Button
error
onClick={async () => {
// ! FIXME: add to rAuth
const del: string[] = [];
render.forEach((session) => {
if (deviceId !== session.id) {
del.push(session.id);
}
});
setDelete(del);
for (const id of del) {
await client.req(
"DELETE",
`/auth/sessions/${id}` as "/auth/sessions",
);
}
setSessions(sessions.filter((x) => x.id === deviceId));
}}>
<Text id="app.settings.pages.sessions.logout" />
</Button>
<Tip>
<span>
<Text id="app.settings.tips.sessions.a" />
......
......@@ -26,6 +26,7 @@ export function Component(props: Props) {
] as [SyncKeys, string][]
).map(([key, title]) => (
<Checkbox
key={key}
checked={
(props.options?.disabled ?? []).indexOf(key) === -1
}
......
import { Servers } from "revolt.js/dist/api/objects";
import { XCircle } from "@styled-icons/boxicons-regular";
import { observer } from "mobx-react-lite";
import { Route } from "revolt.js/dist/api/routes";
import { Server } from "revolt.js/dist/maps/Servers";
import { useContext, useEffect, useState } from "preact/hooks";
import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
import { useEffect, useState } from "preact/hooks";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import Tip from "../../../components/ui/Tip";
import UserIcon from "../../../components/common/user/UserIcon";
import IconButton from "../../../components/ui/IconButton";
import Preloader from "../../../components/ui/Preloader";
interface Props {
server: Servers.Server;
server: Server;
}
export function Bans({ server }: Props) {
const client = useContext(AppContext);
const [bans, setBans] = useState<Servers.Ban[] | undefined>(undefined);
export const Bans = observer(({ server }: Props) => {
const [deleting, setDelete] = useState<string[]>([]);
const [data, setData] = useState<
Route<"GET", "/servers/id/bans">["response"] | undefined
>(undefined);
useEffect(() => {
client.servers.fetchBans(server._id).then((bans) => setBans(bans));
}, []);
server.fetchBans().then(setData);
}, [server, setData]);
return (
<div>
<Tip warning>This section is under construction.</Tip>
{bans?.map((x) => (
<div>
{x._id.user}: {x.reason ?? "no reason"}{" "}
<button
onClick={() =>
client.servers.unbanUser(server._id, x._id.user)
}>
unban
</button>
</div>
))}
<div className={styles.userList}>
<div className={styles.subtitle}>
<span>
<Text id="app.settings.server_pages.bans.user" />
</span>
<span class={styles.reason}>
<Text id="app.settings.server_pages.bans.reason" />
</span>
<span>
<Text id="app.settings.server_pages.bans.revoke" />
</span>
</div>
{typeof data === "undefined" && <Preloader type="ring" />}
{data?.bans.map((x) => {
const user = data.users.find((y) => y._id === x._id.user);
return (
<div
key={x._id.user}
className={styles.ban}
data-deleting={deleting.indexOf(x._id.user) > -1}>
<span>
<UserIcon attachment={user?.avatar} size={24} />
{user?.username}
</span>
<div className={styles.reason}>
{x.reason ?? (
<Text id="app.settings.server_pages.bans.no_reason" />
)}
</div>
<IconButton
onClick={async () => {
setDelete([...deleting, x._id.user]);
await server.unbanUser(x._id.user);
setData({
...data,
bans: data.bans.filter(
(y) => y._id.user !== x._id.user,
),
});
}}
disabled={deleting.indexOf(x._id.user) > -1}>
<XCircle size={24} />
</IconButton>
</div>
);
})}
</div>
);
}
});
import isEqual from "lodash.isequal";
import { observer } from "mobx-react-lite";
import { Category } from "revolt-api/types/Servers";
import { Server } from "revolt.js/dist/maps/Servers";
import { ulid } from "ulid";
import { useState } from "preact/hooks";
import ChannelIcon from "../../../components/common/ChannelIcon";
import Button from "../../../components/ui/Button";
import ComboBox from "../../../components/ui/ComboBox";
import InputBox from "../../../components/ui/InputBox";
import Tip from "../../../components/ui/Tip";
interface Props {
server: Server;
}
// ! FIXME: really bad code
export const Categories = observer(({ server }: Props) => {
const channels = server.channels.filter((x) => typeof x !== "undefined");
const [cats, setCats] = useState<Category[]>(server.categories ?? []);
const [name, setName] = useState("");
return (
<div>
<Tip warning>This section is under construction.</Tip>
<p>
<Button
contrast
disabled={isEqual(server.categories ?? [], cats)}
onClick={() => server.edit({ categories: cats })}>
save categories
</Button>
</p>
<h2>categories</h2>
{cats.map((category) => (
<div style={{ background: "var(--hover)" }} key={category.id}>
<InputBox
value={category.title}
onChange={(e) =>
setCats(
cats.map((y) =>
y.id === category.id
? {
...y,
title: e.currentTarget.value,
}
: y,
),
)
}
contrast
/>
<Button
contrast
onClick={() =>
setCats(cats.filter((x) => x.id !== category.id))
}>
delete {category.title}
</Button>
</div>
))}
<h2>create new</h2>
<p>
<InputBox
value={name}
onChange={(e) => setName(e.currentTarget.value)}
contrast
/>
<Button
contrast
onClick={() => {
setName("");
setCats([
...cats,
{
id: ulid(),
title: name,
channels: [],
},
]);
}}>
create
</Button>
</p>
<h2>channels</h2>
{channels.map((channel) => {
return (
<div
key={channel!._id}
style={{
display: "flex",
gap: "12px",
alignItems: "center",
}}>
<div style={{ flexShrink: 0 }}>
<ChannelIcon target={channel} size={24} />{" "}
<span>{channel!.name}</span>
</div>
<ComboBox
style={{ flexGrow: 1 }}
value={
cats.find((x) =>
x.channels.includes(channel!._id),
)?.id ?? "none"
}
onChange={(e) =>
setCats(
cats.map((x) => {
return {
...x,
channels: [
...x.channels.filter(
(y) => y !== channel!._id,
),
...(e.currentTarget.value ===
x.id
? [channel!._id]
: []),
],
};
}),
)
}>
<option value="none">Uncategorised</option>
{cats.map((x) => (
<option key={x.id} value={x.id}>
{x.title}
</option>
))}
</ComboBox>
</div>
);
})}
</div>
);
});
import { XCircle } from "@styled-icons/boxicons-regular";
import { Invites as InvitesNS, Servers } from "revolt.js/dist/api/objects";
import { observer } from "mobx-react-lite";
import { ServerInvite } from "revolt-api/types/Invites";
import { Server } from "revolt.js/dist/maps/Servers";
import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
import { useEffect, useState } from "preact/hooks";
import {
useChannels,
useForceUpdate,
useUsers,
} from "../../../context/revoltjs/hooks";
import { useClient } from "../../../context/revoltjs/RevoltClient";
import { getChannelName } from "../../../context/revoltjs/util";
import UserIcon from "../../../components/common/user/UserIcon";
......@@ -17,57 +15,68 @@ import IconButton from "../../../components/ui/IconButton";
import Preloader from "../../../components/ui/Preloader";
interface Props {
server: Servers.Server;
server: Server;
}
export function Invites({ server }: Props) {
const [invites, setInvites] = useState<
InvitesNS.ServerInvite[] | undefined
>(undefined);
const ctx = useForceUpdate();
export const Invites = observer(({ server }: Props) => {
const [deleting, setDelete] = useState<string[]>([]);
const users = useUsers(invites?.map((x) => x.creator) ?? [], ctx);
const channels = useChannels(invites?.map((x) => x.channel) ?? [], ctx);
const [invites, setInvites] = useState<ServerInvite[] | undefined>(
undefined,
);
const client = useClient();
const users = invites?.map((invite) => client.users.get(invite.creator));
const channels = invites?.map((invite) =>
client.channels.get(invite.channel),
);
useEffect(() => {
ctx.client.servers
.fetchInvites(server._id)
.then((invites) => setInvites(invites));
}, []);
server.fetchInvites().then(setInvites);
}, [server, setInvites]);
return (
<div className={styles.invites}>
<div className={styles.userList}>
<div className={styles.subtitle}>
<span>Invite Code</span>
<span>Invitor</span>
<span>Channel</span>
<span>Revoke</span>
<span>
<Text id="app.settings.server_pages.invites.code" />
</span>
<span>
<Text id="app.settings.server_pages.invites.invitor" />
</span>
<span>
<Text id="app.settings.server_pages.invites.channel" />
</span>
<span>
<Text id="app.settings.server_pages.invites.revoke" />
</span>
</div>
{typeof invites === "undefined" && <Preloader type="ring" />}
{invites?.map((invite) => {
let creator = users.find((x) => x?._id === invite.creator);
let channel = channels.find((x) => x?._id === invite.channel);
{invites?.map((invite, index) => {
const creator = users![index];
const channel = channels![index];
return (
<div
key={invite._id}
className={styles.invite}
data-deleting={deleting.indexOf(invite._id) > -1}>
<code>{invite._id}</code>
<span>
<UserIcon target={creator} size={24} />{" "}
{creator?.username ?? "unknown"}
{creator?.username ?? (
<Text id="app.main.channel.unknown_user" />
)}
</span>
<span>
{channel && creator
? getChannelName(ctx.client, channel, true)
: "#unknown"}
? getChannelName(channel, true)
: "#??"}
</span>
<IconButton
onClick={async () => {
setDelete([...deleting, invite._id]);
await ctx.client.deleteInvite(invite._id);
await client.deleteInvite(invite._id);
setInvites(
invites?.filter(
......@@ -83,4 +92,4 @@ export function Invites({ server }: Props) {
})}
</div>
);
}
});