Pe 125 128 tabs (#28)

* ready to redo tabs

* mov to tabs now

* we can do the tabs now

* tabs are done
This commit is contained in:
anandamarsh 2023-10-03 14:26:13 -07:00 committed by GitHub
parent 5a0effb691
commit 6e3b82f41b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1433 additions and 1294 deletions

View File

@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="add">
<path id="Vector" d="M8 2.25C8.41421 2.25 8.75 2.58579 8.75 3V7.25H12.9999C13.4142 7.25 13.7499 7.58579 13.7499 8C13.7499 8.41421 13.4142 8.75 12.9999 8.75H8.75V13C8.75 13.4142 8.41421 13.75 8 13.75C7.58579 13.75 7.25 13.4142 7.25 13V8.75H2.99994C2.58573 8.75 2.24994 8.41421 2.24994 8C2.24994 7.58579 2.58573 7.25 2.99994 7.25H7.25V3C7.25 2.58579 7.58579 2.25 8 2.25Z" />
</g>

Before

Width:  |  Height:  |  Size: 494 B

After

Width:  |  Height:  |  Size: 471 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="chevron left">
<path id="Vector" d="M9.37532 3.87037C9.06142 3.60011 8.58787 3.63548 8.31761 3.94938L5.24716 7.51555C5.00479 7.79706 5.005 8.21358 5.24766 8.49484L8.3181 12.0537C8.58869 12.3673 9.06228 12.4022 9.3759 12.1316C9.68952 11.861 9.72441 11.3874 9.45383 11.0738L6.80564 8.0044L9.45433 4.92809C9.72459 4.61419 9.68922 4.14064 9.37532 3.87037Z" />
</g>

Before

Width:  |  Height:  |  Size: 471 B

After

Width:  |  Height:  |  Size: 448 B

View File

@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="globe" clip-path="url(#clip0_877_3373)">
<path id="Vector" d="M2.8036 4.99805H4.93136C5.20393 4.00767 5.60992 3.09518 6.12818 2.29776C4.71686 2.76074 3.53598 3.7331 2.8036 4.99805ZM8.0002 2.30822C7.36149 3.02543 6.84135 3.94162 6.49682 4.99805H9.50369C9.15932 3.94141 8.63922 3.02533 8.0002 2.30822ZM9.8557 6.49805H6.14448C6.07205 6.98072 6.03383 7.48276 6.03383 8.00006C6.03383 8.51728 6.07222 9.01943 6.14492 9.50232H9.85562C9.928 9.01963 9.96618 8.51751 9.96618 8.00006C9.96618 7.48269 9.92803 6.98065 9.8557 6.49805ZM11.3698 9.50232H13.8106C13.9341 9.0228 14 8.51954 14 8C14 7.4806 13.9342 6.97745 13.8107 6.49805H11.3699C11.4334 6.98712 11.4662 7.48902 11.4662 8.00006C11.4662 8.51118 11.4334 9.01316 11.3698 9.50232ZM9.50346 11.0023H6.49812C6.84293 12.0578 7.36295 12.9739 8.00061 13.6912C8.63925 12.9742 9.15913 12.0585 9.50346 11.0023ZM6.13019 13.7029C5.61195 12.9053 5.20549 11.9927 4.93242 11.0023H2.80381C3.53655 12.2677 4.71812 13.2402 6.13019 13.7029ZM2.18944 9.50232H4.63058C4.5668 9.01306 4.53383 8.51107 4.53383 8.00006C4.53383 7.48905 4.56667 6.98716 4.63024 6.49805H2.18934C2.06583 6.97745 2 7.4806 2 8C2 8.51954 2.06586 9.0228 2.18944 9.50232ZM9.87246 13.702C11.2834 13.239 12.4639 12.2669 13.1962 11.0023H11.0689C10.7964 11.9925 10.3907 12.9048 9.87246 13.702ZM13.1964 4.99805C12.4642 3.73335 11.2836 2.76113 9.87267 2.29803C10.3909 3.09536 10.7967 4.00771 11.0691 4.99805H13.1964ZM0.927726 5.49789C1.95757 2.58706 4.73409 0.5 8 0.5C11.2659 0.5 14.0424 2.58706 15.0723 5.49789C15.3495 6.28142 15.5 7.12394 15.5 8C15.5 8.87621 15.3494 9.71887 15.0721 10.5025C14.0422 13.4131 11.2658 15.5 8 15.5C4.73424 15.5 1.95783 13.4131 0.92787 10.5025C0.65057 9.71887 0.5 8.87621 0.5 8C0.5 7.12394 0.650517 6.28142 0.927726 5.49789Z" />
</g>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M4.89098 9.77087V9.77086C4.3286 8.33222 3.93235 7.2147 3.75601 6.49734C3.73126 6.39663 3.70567 6.29419 3.68018 6.19212C3.60255 5.88132 3.5258 5.57398 3.47598 5.32973C3.44161 5.16123 3.40389 4.94897 3.3903 4.72925C3.37888 4.54463 3.36705 4.14366 3.54385 3.71161C3.77368 3.15001 4.25784 2.69672 4.9074 2.54164C5.40581 2.42264 5.83375 2.5229 6.03017 2.57716C6.44295 2.69117 6.88705 2.91993 7.24993 3.11436C8.28931 3.67123 12.3278 5.78776 15.401 7.39838C16.833 8.14886 18.0554 8.7895 18.6671 9.11134C18.7432 9.15137 18.8188 9.19104 18.8937 9.2303C19.3919 9.49168 19.8557 9.73496 20.1922 9.94554C20.3853 10.0663 20.6452 10.2416 20.8739 10.4739C21.0935 10.6971 21.4839 11.1752 21.5127 11.8938C21.5425 12.6396 21.1667 13.1594 20.9292 13.4154C20.696 13.6668 20.4282 13.849 20.2425 13.9665C19.9299 14.1644 19.499 14.391 19.0524 14.6259C18.9674 14.6706 18.8819 14.7156 18.7964 14.7608C18.336 15.0042 17.4592 15.4689 16.382 16.0398C13.2668 17.6907 8.47534 20.23 7.22867 20.8766C6.83779 21.0794 6.38173 21.3079 5.97464 21.4211C5.79092 21.4722 5.34287 21.5838 4.82337 21.4525C4.11014 21.2722 3.63286 20.7423 3.44392 20.1535C3.30443 19.7187 3.34347 19.3254 3.36293 19.1615C3.38727 18.9564 3.43181 18.755 3.46984 18.5964C3.50685 18.4421 3.55223 18.2701 3.59595 18.1044L3.60491 18.0705C3.65332 17.8869 3.70398 17.6941 3.75601 17.4824C3.92992 16.775 4.32011 15.6681 4.87409 14.2402C5.2331 13.3148 5.85858 12.5437 6.64523 11.9954C5.86773 11.45 5.24886 10.6863 4.89098 9.77087ZM9.08517 11.0825C9.17629 11.1002 9.30339 11.1254 9.45744 11.156C9.55411 11.1753 9.6614 11.1967 9.77707 11.2199C9.88214 11.2409 9.98231 11.261 10.0777 11.2802C11.0065 11.4674 11.4801 11.5761 11.5834 11.7458C11.6813 11.9063 11.6806 12.1091 11.5817 12.2689C11.4773 12.4379 11.003 12.5435 10.073 12.7247C9.97757 12.7433 9.87735 12.7627 9.77223 12.7831C9.65655 12.8054 9.54899 12.8262 9.45169 12.845C9.30012 12.8742 9.17348 12.8984 9.07991 12.9162C9.02647 12.9263 8.98382 12.9343 8.95347 12.9399C7.85403 13.1422 6.94382 13.8819 6.55222 14.8912C6.0629 16.1525 5.73362 17.0835 5.56436 17.6844C5.54125 17.7665 5.52112 17.8424 5.50397 17.9121C5.50295 17.9163 5.50193 17.9204 5.50091 17.9246C5.44328 18.1585 5.38775 18.369 5.33832 18.5563C5.00492 19.82 4.94919 20.0313 6.39986 19.2788C6.52235 19.2153 6.67936 19.1333 6.86588 19.0356C6.94145 18.996 7.02186 18.9538 7.10679 18.9092C8.93211 17.9507 12.8411 15.8791 15.5236 14.4575C16.6067 13.8835 17.4899 13.4155 17.9551 13.1695C18.0697 13.1089 18.1787 13.0516 18.2822 12.9971C18.2875 12.9943 18.2927 12.9915 18.298 12.9887C19.2334 12.496 19.7119 12.2383 19.7143 11.9763C19.7166 11.7169 19.2524 11.4533 18.3029 10.9533C18.2972 10.9503 18.2914 10.9473 18.2857 10.9442C18.1441 10.8697 17.9919 10.79 17.829 10.7043C17.2245 10.3863 16.0114 9.75053 14.5869 9.00396C11.9999 7.64814 8.71566 5.9269 7.11324 5.07982C7.04323 5.04281 6.97643 5.00747 6.91303 4.9739C6.7016 4.86193 6.52809 4.76968 6.39986 4.70098C4.9723 3.93613 5.03855 4.20149 5.41709 5.71773C5.44455 5.82776 5.47367 5.94437 5.50397 6.06765C5.50505 6.07202 5.50613 6.07641 5.50723 6.08082C5.52202 6.14038 5.53893 6.20423 5.55794 6.27238C5.56007 6.28001 5.56222 6.28769 5.5644 6.29543C5.73574 6.90318 6.07009 7.84321 6.56744 9.11552C6.96041 10.1208 7.86817 10.857 8.96405 11.0593C8.99092 11.0642 9.03189 11.0721 9.08517 11.0825Z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="bookmark">
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M7.5179 1H8.4821C9.2949 1 9.9506 1 10.4815 1.0433C11.0281 1.088 11.5082 1.1823 11.9525 1.4085C12.658 1.76766 13.2317 2.34095 13.5913 3.0462C13.8176 3.4901 13.912 3.9699 13.9566 4.5162C14 5.0467 14 5.7019 14 6.5142V12.0135C14 12.5104 14 12.9343 13.9695 13.2677C13.9392 13.5992 13.8703 13.9829 13.6249 14.3006C13.4636 14.5095 13.2583 14.6803 13.0235 14.801C12.7888 14.9217 12.5305 14.9894 12.2667 14.9991C11.8655 15.0139 11.5129 14.847 11.2254 14.6789C10.9362 14.51 10.591 14.2635 10.1864 13.9747L8.4941 12.7667C8.3185 12.6414 8.2171 12.5695 8.1376 12.5206C8.0888 12.4906 8.0664 12.4809 8.0603 12.4786C8.0207 12.4688 7.9793 12.4688 7.9397 12.4786C7.9337 12.4809 7.9112 12.4906 7.8624 12.5206C7.7829 12.5695 7.6814 12.6414 7.506 12.7667L5.8137 13.9747C5.409 14.2635 5.0638 14.51 4.7747 14.6789C4.4871 14.847 4.1345 15.0139 3.7333 14.9991C3.46955 14.9893 3.21117 14.9217 2.97646 14.801C2.74175 14.6803 2.53645 14.5095 2.3751 14.3006C2.1297 13.9829 2.0608 13.5992 2.0305 13.2677C2 12.9343 2 12.5104 2 12.0135V6.5142C2 5.7019 2 5.0467 2.0434 4.5162C2.088 3.9699 2.1824 3.4901 2.4087 3.0462C2.76834 2.34095 3.34201 1.76766 4.0475 1.4085C4.4918 1.1823 4.9719 1.088 5.5185 1.0433C6.0495 1 6.7051 1 7.518 1H7.5179ZM7.9415 12.478L7.9397 12.4786L7.9415 12.478ZM8.0585 12.478L8.0603 12.4786L8.0585 12.478ZM5.6407 2.5374C5.1868 2.5744 4.926 2.6435 4.7285 2.7441C4.3052 2.95959 3.96099 3.30356 3.7452 3.7267C3.6446 3.9241 3.5755 4.1847 3.5384 4.6383C3.5006 5.1006 3.5 5.6944 3.5 6.5463V11.9781C3.5 12.5201 3.5008 12.8745 3.5242 13.1312C3.5407 13.311 3.5644 13.3792 3.57 13.394C3.59567 13.4246 3.6272 13.4498 3.66275 13.4681C3.6983 13.4864 3.73715 13.4974 3.777 13.5004C3.7922 13.4963 3.8616 13.476 4.0175 13.3849C4.2402 13.2548 4.5292 13.0494 4.9705 12.7344L6.6341 11.5469C6.9171 11.3449 7.2178 11.115 7.5703 11.0258C7.85232 10.9544 8.14768 10.9544 8.4297 11.0258C8.7822 11.115 9.0829 11.3449 9.3659 11.5469L11.0295 12.7344C11.4708 13.0494 11.7598 13.2548 11.9825 13.3849C12.1384 13.476 12.2078 13.4963 12.2231 13.5004C12.2629 13.4974 12.3018 13.4864 12.3373 13.4681C12.3728 13.4498 12.4043 13.4246 12.43 13.394C12.4356 13.3792 12.4593 13.311 12.4758 13.1312C12.4993 12.8745 12.5 12.5201 12.5 11.9781V6.5463C12.5 5.6944 12.4994 5.1006 12.4616 4.6383C12.4245 4.1847 12.3554 3.9241 12.2548 3.7267C12.039 3.30357 11.6948 2.95961 11.2715 2.7441C11.074 2.6435 10.8132 2.5744 10.3593 2.5374C9.8967 2.4996 9.3025 2.499 8.45 2.499H7.55C6.6975 2.499 6.1033 2.4996 5.6407 2.5374Z" />
</g>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17 16" >
<g clip-path="url(#clip0_911_355)">
<path d="M5.19809 1.5013L5.22569 1.50133C5.24141 1.50134 5.25694 1.50135 5.27228 1.50136C5.54417 1.5015 5.75828 1.50161 5.97091 1.5351C6.22212 1.57467 6.46662 1.64888 6.69744 1.75563C6.89281 1.84599 7.07086 1.96491 7.29696 2.11593C7.30971 2.12445 7.32262 2.13307 7.33569 2.1418L9.16329 3.3615C9.24228 3.41422 9.28607 3.44332 9.32006 3.46434C9.34165 3.47769 9.35081 3.48241 9.35308 3.48351C9.37107 3.49137 9.38992 3.49708 9.40924 3.50053C9.40627 3.5 9.41272 3.50172 9.44734 3.50294C9.48728 3.50434 9.53986 3.50446 9.63482 3.50449L10.9332 3.50498C11.6063 3.50522 12.1569 3.50542 12.6043 3.54214C13.0674 3.58014 13.4856 3.661 13.8762 3.86014C14.4875 4.17181 14.9845 4.66896 15.2959 5.28037C15.4949 5.67103 15.5756 6.08931 15.6135 6.55243C15.65 6.99986 15.65 7.55039 15.65 8.22354L15.65 9.78354C15.65 10.4569 15.65 11.0076 15.6135 11.4552C15.5756 11.9185 15.4949 12.3369 15.2958 12.7276C14.9842 13.3391 14.487 13.8363 13.8755 14.1479C13.4848 14.347 13.0664 14.4277 12.6031 14.4656C12.1555 14.5022 11.6048 14.5022 10.9314 14.5021H5.36856C4.69519 14.5022 4.14449 14.5022 3.69693 14.4656C3.23366 14.4277 2.81527 14.347 2.42453 14.1479C1.813 13.8363 1.31581 13.3391 1.00422 12.7276C0.805131 12.3369 0.724396 11.9185 0.686546 11.4552C0.649978 11.0076 0.649985 10.4569 0.649994 9.78356V6.04528C0.649987 5.45218 0.649982 4.9671 0.678516 4.57146C0.708017 4.16242 0.770829 3.79125 0.926417 3.43852C1.25408 2.69567 1.84792 2.10236 2.59107 1.77537C2.94394 1.6201 3.31517 1.55762 3.72423 1.52849C4.1199 1.50032 4.60499 1.50076 5.19809 1.5013ZM3.83078 3.0247C3.50274 3.04807 3.32457 3.09141 3.19519 3.14834C2.79503 3.32441 2.47527 3.64388 2.29884 4.04388C2.24179 4.1732 2.19829 4.35134 2.17463 4.67936C2.15045 5.01465 2.14999 5.44548 2.14999 6.07289V9.75214C2.14999 10.4646 2.15058 10.9538 2.18156 11.3331C2.21184 11.7036 2.26734 11.9026 2.34073 12.0466C2.50851 12.3759 2.77623 12.6436 3.10551 12.8114C3.24955 12.8848 3.44857 12.9403 3.81907 12.9706C4.19833 13.0016 4.68756 13.0021 5.4 13.0021H10.9C11.6125 13.0021 12.1017 13.0016 12.4809 12.9706C12.8514 12.9403 13.0505 12.8848 13.1945 12.8114C13.5238 12.6436 13.7915 12.3759 13.9593 12.0466C14.0327 11.9026 14.0882 11.7036 14.1185 11.3331C14.1494 10.9538 14.15 10.4646 14.15 9.75212L14.15 8.25495C14.15 7.54275 14.1494 7.0537 14.1184 6.67457C14.0882 6.3042 14.0327 6.10523 13.9593 5.96121C13.7916 5.63199 13.524 5.3643 13.1949 5.19647C13.0509 5.12306 12.8519 5.06751 12.4816 5.03711C12.1025 5.006 11.6134 5.00523 10.9012 5.00497L9.63426 5.00449C9.62259 5.00449 9.61085 5.0045 9.59904 5.00451C9.45469 5.00465 9.30013 5.00479 9.1454 4.97714C9.01016 4.95298 8.87823 4.91297 8.75234 4.85795C8.60832 4.795 8.47987 4.70903 8.3599 4.62874C8.35009 4.62218 8.34033 4.61564 8.33063 4.60917L6.50303 3.38946C6.22049 3.2009 6.14388 3.15227 6.06779 3.11708C5.96287 3.06856 5.85173 3.03482 5.73755 3.01684C5.65473 3.00379 5.56401 3.00164 5.22433 3.00133C4.59692 3.00076 4.1661 3.00083 3.83078 3.0247Z" />
</g>
<defs>
<clipPath id="clip0_911_355">
<rect width="16" height="16" transform="translate(0.149994)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="help" clip-path="url(#clip0_877_3413)">
<g id="Vector">
<path d="M8 5.15C7.81163 5.14997 7.62858 5.21252 7.47961 5.32782C7.33065 5.44312 7.2242 5.60464 7.177 5.787C7.1274 5.97968 7.0033 6.14477 6.83198 6.24594C6.66067 6.34712 6.45618 6.3761 6.2635 6.3265C6.07082 6.2769 5.90573 6.1528 5.80456 5.98148C5.70338 5.81017 5.6744 5.60568 5.724 5.413C5.8665 4.85867 6.20641 4.3754 6.67992 4.05389C7.15344 3.73237 7.72802 3.59473 8.29582 3.66678C8.86363 3.73882 9.38561 4.01562 9.76383 4.44521C10.142 4.87479 10.3505 5.42765 10.35 6C10.35 7.036 9.784 7.578 9.387 7.948C8.997 8.311 8.801 8.498 8.742 8.906C8.70775 9.09729 8.60058 9.26782 8.44309 9.38165C8.28559 9.49549 8.09005 9.54375 7.89768 9.51627C7.7053 9.48879 7.5311 9.38771 7.41177 9.23432C7.29245 9.08094 7.23732 8.88723 7.258 8.694C7.392 7.752 7.943 7.241 8.308 6.904L8.363 6.852C8.716 6.522 8.85 6.364 8.85 6C8.85 5.88838 8.82801 5.77785 8.7853 5.67472C8.74258 5.57159 8.67997 5.47789 8.60104 5.39896C8.52211 5.32003 8.42841 5.25742 8.32528 5.2147C8.22215 5.17199 8.11162 5.15 8 5.15Z" />

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="clock backward" clip-path="url(#clip0_877_3363)">
<g id="Vector">
<path d="M8.25843 4.47461C8.67264 4.47461 9.00843 4.8104 9.00843 5.22461V7.98571L11.2296 8.92626C11.6162 9.07495 11.8091 9.5089 11.6604 9.8955C11.5117 10.2821 11.0778 10.475 10.6912 10.3263L7.98919 9.20082C7.69955 9.08942 7.50843 8.81114 7.50843 8.50081V5.22461C7.50843 4.8104 7.84421 4.47461 8.25843 4.47461Z" />

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" id="mdil-minus-circle" viewBox="0 0 24 24"><path d="M7,12H16V13H7V12M11.5,3A9.5,9.5 0 0,1 21,12.5A9.5,9.5 0 0,1 11.5,22A9.5,9.5 0 0,1 2,12.5A9.5,9.5 0 0,1 11.5,3M11.5,4A8.5,8.5 0 0,0 3,12.5A8.5,8.5 0 0,0 11.5,21A8.5,8.5 0 0,0 20,12.5A8.5,8.5 0 0,0 11.5,4Z" /></svg>

After

Width:  |  Height:  |  Size: 305 B

1
src/assets/icons/pin.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" id="mdil-pin" viewBox="0 0 24 24"><path d="M14,12.41V5H15V4H8V5H9V12.41L7,14.41V15H16V14.41L14,12.41M17,14V14H17V14L17,16H12V20.5L11.5,22L11,20.5V16H6V14H6L8,12V6H7V3H16V6H15V12L17,14V14Z" /></svg>

After

Width:  |  Height:  |  Size: 237 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" id="mdil-plus-circle" viewBox="0 0 24 24"><path d="M7,12H11V8H12V12H16V13H12V17H11V13H7V12M11.5,3A9.5,9.5 0 0,1 21,12.5A9.5,9.5 0 0,1 11.5,22A9.5,9.5 0 0,1 2,12.5A9.5,9.5 0 0,1 11.5,3M11.5,4A8.5,8.5 0 0,0 3,12.5A8.5,8.5 0 0,0 11.5,21A8.5,8.5 0 0,0 20,12.5A8.5,8.5 0 0,0 11.5,4Z" /></svg>

After

Width:  |  Height:  |  Size: 327 B

View File

@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="settings">
<g id="Vector">
<path d="M3.87111 6.59137C3.78902 6.83702 3.72823 7.09242 3.69095 7.35539C3.68706 7.38284 3.68342 7.41037 3.68005 7.43797L2.65354 7.94788C2.38137 8.08308 2.23593 8.38598 2.3006 8.68291L2.52099 9.69485C2.58566 9.99179 2.84388 10.2068 3.14762 10.2165L4.293 10.2534C4.47187 10.5434 4.68382 10.8108 4.92358 11.0503L4.69475 12.1739C4.6341 12.4717 4.78362 12.7726 5.0576 12.9041L5.99127 13.3522C6.26524 13.4838 6.59354 13.4122 6.78794 13.1786L7.52114 12.2977C7.67826 12.3149 7.83788 12.3237 7.99957 12.3237C8.16209 12.3237 8.32253 12.3148 8.48043 12.2974L9.21413 13.1779C9.40868 13.4114 9.73702 13.4827 10.0109 13.351L10.9443 12.9023C11.2182 12.7706 11.3675 12.4696 11.3067 12.1719L11.0772 11.0486C11.3077 10.8181 11.5123 10.562 11.6866 10.2847L12.8321 10.2576C13.1359 10.2504 13.3959 10.0376 13.4631 9.74123L13.6921 8.7312C13.7592 8.43482 13.6164 8.13071 13.3454 7.99321L12.3234 7.47471C12.2864 7.14922 12.2135 6.83462 12.1087 6.53498L12.8023 5.62371C12.9864 5.38188 12.9825 5.0459 12.7929 4.80838L12.1468 3.99895C11.9573 3.76144 11.6305 3.68318 11.3539 3.80904L10.3109 4.28364C10.0393 4.11309 9.74756 3.9717 9.44004 3.86387L9.1605 2.75335C9.08632 2.45864 8.82131 2.25208 8.51741 2.25208H7.48175C7.17786 2.25208 6.91285 2.45864 6.83866 2.75334L6.55911 3.86387C6.25956 3.9689 5.97498 4.10577 5.70944 4.27042L4.66908 3.78977C4.3932 3.66231 4.06599 3.73869 3.87504 3.97511L3.22432 4.78081C3.03338 5.01723 3.02757 5.35318 3.21022 5.59606L3.89867 6.51151C3.88924 6.53801 3.88005 6.56463 3.87111 6.59137ZM2.01139 6.49761C1.41559 5.70536 1.43454 4.60951 2.05738 3.83833L2.70811 3.03263C3.33096 2.26145 4.39829 2.01232 5.29819 2.42807L5.36587 2.45934L5.38405 2.38716C5.62604 1.42587 6.49047 0.752075 7.48175 0.752075H8.51741C9.50871 0.752075 10.3731 1.42588 10.6151 2.38719L10.64 2.4859L10.7326 2.44374C11.6349 2.03318 12.7008 2.28846 13.3192 3.06321L13.9652 3.87264C14.5836 4.6474 14.5963 5.74335 13.9959 6.53216L13.936 6.61086L14.0241 6.65553C14.9081 7.10405 15.3741 8.09605 15.1549 9.0628L14.926 10.0728C14.7068 11.0396 13.8586 11.7337 12.8676 11.7572L12.7535 11.7599L12.7764 11.8717C12.9748 12.8429 12.4876 13.8247 11.5942 14.2542L10.6608 14.7029C9.76741 15.1324 8.69638 14.8997 8.06179 14.1381L8.00131 14.0656L7.94087 14.1382C7.30673 14.9001 6.23586 15.1335 5.34219 14.7045L4.40851 14.2564C3.51483 13.8274 3.02709 12.8459 3.22492 11.8746L3.25623 11.7208L3.09937 11.7158C2.1086 11.6839 1.26629 10.9826 1.05534 10.0141L0.834953 9.00212C0.624003 8.03353 1.09843 7.0455 1.98622 6.6045L2.06306 6.56633L2.01139 6.49761Z" />

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,9 @@
<svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="actions">
<g id="Vector">
<path d="M5 2.54797C5 1.99569 5.44772 1.54797 6 1.54797C6.55228 1.54797 7 1.99569 7 2.54797C7 3.10026 6.55228 3.54797 6 3.54797C5.44772 3.54797 5 3.10026 5 2.54797Z" />
<path d="M5 6.00001C5 5.44772 5.44772 5.00001 6 5.00001C6.55228 5.00001 7 5.44772 7 6.00001C7 6.55229 6.55228 7.00001 6 7.00001C5.44772 7.00001 5 6.55229 5 6.00001Z" />
<path d="M5 9.45204C5 8.89975 5.44772 8.45204 6 8.45204C6.55228 8.45204 7 8.89975 7 9.45204C7 10.0043 6.55228 10.452 6 10.452C5.44772 10.452 5 10.0043 5 9.45204Z" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 627 B

View File

@ -0,0 +1,5 @@
<svg viewBox="0 0 16 16" fill="rgba(88, 193, 66, 1)" xmlns="http://www.w3.org/2000/svg">
<g id="sparkle_16">
<path id="Vector" d="M9.12147 3.54414C8.60747 1.94014 8.35147 1.13814 8.20747 1.05914C8.14865 1.02056 8.07983 1 8.00947 1C7.93912 1 7.8703 1.02056 7.81147 1.05914C7.66847 1.13914 7.41247 1.94114 6.90147 3.54714C6.59547 4.50914 6.22347 5.34314 5.78647 5.78014C5.34847 6.21814 4.51447 6.58914 3.55147 6.89514C1.94347 7.40714 1.13847 7.66314 1.05947 7.80514C1.02068 7.86408 1 7.93309 1 8.00364C1 8.0742 1.02068 8.14321 1.05947 8.20214C1.13847 8.34514 1.94147 8.60214 3.54947 9.11714C4.51247 9.42514 5.34847 9.79814 5.78647 10.2361C6.22447 10.6741 6.59647 11.5061 6.90347 12.4661C7.41347 14.0621 7.66947 14.8611 7.81147 14.9391C7.95447 15.0191 8.06447 15.0191 8.20747 14.9391C8.35047 14.8611 8.60747 14.0631 9.12047 12.4681C9.42947 11.5081 9.80247 10.6751 10.2415 10.2361C10.6815 9.79614 11.5155 9.42114 12.4765 9.10914C14.0685 8.59214 14.8645 8.33314 14.9425 8.19014C14.9809 8.13122 15.0013 8.06235 15.0011 7.99199C15.0009 7.92164 14.9802 7.85287 14.9415 7.79414C14.8615 7.65214 14.0635 7.39814 12.4675 6.89214C11.5105 6.58814 10.6795 6.21814 10.2415 5.78014C9.80347 5.34214 9.43147 4.50714 9.12147 3.54414Z" fill="#58C142"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="actions">
<path id="Vector" d="M6.79999 4C6.79999 3.33728 7.33724 2.8 7.99999 2.8C8.66271 2.8 9.19999 3.33728 9.19999 4C9.19999 4.66272 8.66271 5.2 7.99999 5.2C7.33724 5.2 6.79999 4.66272 6.79999 4ZM6.79999 8C6.79999 7.33728 7.33724 6.8 7.99999 6.8C8.66271 6.8 9.19999 7.33728 9.19999 8C9.19999 8.66274 8.66271 9.2 7.99999 9.2C7.33724 9.2 6.79999 8.66274 6.79999 8ZM6.79999 12C6.79999 11.3373 7.33724 10.8 7.99999 10.8C8.66271 10.8 9.19999 11.3373 9.19999 12C9.19999 12.6627 8.66271 13.2 7.99999 13.2C7.33724 13.2 6.79999 12.6627 6.79999 12Z" />
</g>

Before

Width:  |  Height:  |  Size: 661 B

After

Width:  |  Height:  |  Size: 638 B

View File

@ -0,0 +1,5 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="check">
<path id="Vector" d="M14.7808 3.46982C15.0736 3.76277 15.0736 4.23764 14.7806 4.53048L7.27802 12.0305C6.9851 12.3233 6.5103 12.3232 6.21744 12.0304L3.22198 9.03485C2.92909 8.74195 2.9291 8.26708 3.222 7.97419C3.51489 7.6813 3.98977 7.68131 4.28266 7.9742L6.74788 10.4395L13.7201 3.46964C14.0131 3.1768 14.4879 3.17688 14.7808 3.46982Z" fill="#C3C8C2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 476 B

View File

@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="grid 4">
<g id="Vector">
<path d="M9.63945 2.16696C9.84318 2.058 10.0452 1.99998 10.6024 1.99998L12.3975 2.00003C12.9547 2.00002 13.1568 2.058 13.3605 2.16695C13.5642 2.2759 13.7241 2.43577 13.833 2.63946C13.942 2.84316 14 3.04525 14 3.60245L14 5.39755C14 5.95476 13.942 6.15681 13.833 6.36054C13.7241 6.56425 13.5643 6.72406 13.3605 6.83302C13.1568 6.94198 12.9548 7 12.3976 7L10.6024 6.99999C10.0452 6.99999 9.84316 6.94197 9.63947 6.83303C9.43576 6.72409 9.27589 6.56422 9.16696 6.36052C9.05801 6.15682 8.99999 5.95476 8.99999 5.39757L8.99999 3.60243C8.99999 3.04523 9.058 2.84317 9.16696 2.63945C9.27592 2.43572 9.43573 2.27592 9.63945 2.16696Z" />

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -128,65 +128,6 @@
visibility: visible !important;
}
}
.terminal-wrapper {
position: relative;
.term-block {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: transparent;
z-index: 10;
}
.xterm-screen {
&::-webkit-scrollbar {
display: none;
}
}
&.focus .xterm {
.xterm-screen {
overflow-y: scroll;
overscroll-behavior: contain;
}
.xterm-viewport {
overscroll-behavior: contain;
}
}
&.focus .xterm-viewport {
&::-webkit-scrollbar {
background-color: #777;
width: 5px;
height: 5px;
}
&::-webkit-scrollbar-thumb {
background: white;
}
}
.xterm-viewport {
&::-webkit-scrollbar {
background-color: #222;
width: 5px;
height: 5px;
}
&::-webkit-scrollbar-thumb {
background: #555;
}
}
}
body .xterm .xterm-viewport {
overflow-y: auto;
width: calc(100% + 5px);
}
.checkbox-toggle {
position: relative;
@ -565,70 +506,6 @@ body .xterm .xterm-viewport {
background-color: #222;
}
#main .term-prompt {
i {
margin-right: 3px;
}
.term-prompt-branch {
color: @term-white;
}
.term-prompt-python {
color: @term-bright-magenta;
}
.term-prompt-remote {
i {
margin-right: 0;
}
}
.term-prompt-remote {
color: @term-bright-green;
&.color-green {
color: @term-bright-green;
}
&.color-red {
color: @term-bright-red;
}
&.color-blue {
color: @term-bright-blue;
}
&.color-yellow {
color: @term-bright-yellow;
}
&.color-magenta {
color: @term-bright-magenta;
}
&.color-cyan {
color: @term-bright-cyan;
}
&.color-white {
color: @term-bright-white;
}
&.color-orange {
color: @tab-orange;
}
}
.term-prompt-cwd {
color: @term-bright-green;
}
.term-prompt-end {
color: @term-bright-green;
}
}
.remote-status {
margin-right: 5px;
position: relative;

View File

@ -257,23 +257,6 @@ class InfoMessage extends React.Component<{ width: number; children: React.React
}
}
function LinkRenderer(props: any): any {
let newUrl = "https://extern?" + encodeURIComponent(props.href);
return (
<a href={newUrl} target="_blank">
{props.children}
</a>
);
}
function HeaderRenderer(props: any, hnum: number): any {
return <div className={cn("title", "is-" + hnum)}>{props.children}</div>;
}
function CodeRenderer(props: any): any {
return <code className={cn({ inline: props.inline })}>{props.children}</code>;
}
@mobxReact.observer
class Markdown extends React.Component<{ text: string; style?: any; extraClassName?: string }, {}> {
render() {

View File

@ -135,7 +135,6 @@ class TerminalRenderer extends React.Component<
render() {
let { screen, line, width, staticRender, visible, collapsed } = this.props;
let isVisible = visible.get(); // for reaction
let isPhysicalFocused = mobx
.computed(() => screen.getIsFocused(line.linenum), {
name: "computed-getIsFocused",
@ -152,7 +151,7 @@ class TerminalRenderer extends React.Component<
.get();
let cmd = screen.getCmd(line); // will not be null
let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width);
let termHeight = termHeightFromRows(usedRows, GlobalModel.termFontSize.get());
let termHeight = termHeightFromRows(usedRows <= 1 ? usedRows : usedRows + 3, GlobalModel.termFontSize.get());
let termLoaded = this.termLoaded.get();
return (
<div

View File

@ -204,6 +204,8 @@ function createMainWindow(clientData) {
titleBarStyle: "hiddenInset",
width: bounds.width,
height: bounds.height,
minWidth: 600,
minHeight: 400,
webPreferences: {
preload: path.join(getAppBasePath(), DistDir, "preload.js"),
},

View File

@ -2,6 +2,10 @@
@base-background: rgba(21, 23, 21, 1);
@base-background-dev: rgba(21, 23, 21, 1);
@base-border: rgba(241, 246, 243, 0.08);
@background-session: rgba(13, 13, 13, 0.85);
@background-session-components: rgba(48, 49, 48, 0.6);
@prompt-green: rgb(88, 193, 66);
@disabled: rgba(76, 81, 75, 1);
@term-black: #000000;
@term-red: #cc0000;
@ -13,7 +17,7 @@
@term-white: #d3d7cf;
@term-bright-black: #555753;
@term-bright-red: #ef2929;
@term-bright-green: #8ae234;
@term-bright-green: #58c142;
@term-bright-yellow: #fce94f;
@term-bright-blue: #32afff;
@term-bright-magenta: #ad7fa8;
@ -33,8 +37,6 @@
@tab-black-text: #333;
@tab-white-text: #d7d7d7;
@prompt-green: rgb(0, 177, 10);
@soft-blue: #729fcf;
@active-menu-color: rgb(0, 71, 171);
@ -51,7 +53,8 @@
}
html,
body {
body,
textarea {
overflow: hidden;
font-family: "Martian Mono", sans-serif;
font-size: 12.5px;
@ -61,8 +64,11 @@ body {
color: @base-color;
}
svg {
svg.icon {
fill: @base-color;
width: 100%;
height: 100%;
display: inline-block;
}
// styles.less
@ -72,11 +78,33 @@ svg {
cursor: pointer;
box-shadow: 0px 2px 2px 0 rgba(255, 255, 255, 0.1), 0px 4px 5px 0 rgba(255, 255, 255, 0.2),
0px 0px 5px 2.5px rgba(255, 255, 255, 0.5);
transition: box-shadow 0.2s ease;
}
}
.hideScrollbarUntillHover {
&::-webkit-scrollbar {
width: 0;
}
&:hover::-webkit-scrollbar {
width: 4px;
}
}
.glow {
&:focus,
&:hover {
border: 1px solid rgba(@prompt-green, 0.8) !important;
box-shadow: 0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
}
}
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#title-bar {
-webkit-app-region: drag;
height: 30px;
@ -144,7 +172,6 @@ a.a-block {
flex-direction: row;
height: 100%;
.session-view,
.history-view,
.bookmarks-view {
flex-grow: 1;
@ -167,7 +194,10 @@ a.a-block {
.window-view {
display: flex;
flex-direction: column;
position: relative;
position: absolute;
width: 100%;
height: calc(100% - 3em);
overflow-x: hidden;
.rendermode-tag {
position: absolute;
@ -408,70 +438,6 @@ a.a-block {
background-color: #222;
}
#main .term-prompt {
i {
margin-right: 3px;
}
.term-prompt-branch {
color: @term-white;
}
.term-prompt-python {
color: @term-bright-magenta;
}
.term-prompt-remote {
i {
margin-right: 0;
}
}
.term-prompt-remote {
color: @term-bright-green;
&.color-green {
color: @term-bright-green;
}
&.color-red {
color: @term-bright-red;
}
&.color-blue {
color: @term-bright-blue;
}
&.color-yellow {
color: @term-bright-yellow;
}
&.color-magenta {
color: @term-bright-magenta;
}
&.color-cyan {
color: @term-bright-cyan;
}
&.color-white {
color: @term-bright-white;
}
&.color-orange {
color: @tab-orange;
}
}
.term-prompt-cwd {
color: @term-bright-green;
}
.term-prompt-end {
color: @term-bright-green;
}
}
.remote-status {
margin-right: 5px;
position: relative;

View File

@ -23,6 +23,12 @@ import type {
RendererModel,
} from "../../types/types";
import cn from "classnames";
import { ReactComponent as FavouritesIcon } from "../../assets/icons/favourites.svg";
import { ReactComponent as PinIcon } from "../../assets/icons/pin.svg";
import { ReactComponent as PlusIcon } from "../../assets/icons/plus.svg";
import { ReactComponent as MinusIcon } from "../../assets/icons/minus.svg";
import type { LineContainerModel } from "../../model";
import { renderCmdText } from "../../common/common";
import { SimpleBlobRenderer } from "./renderer/simplerenderer";
@ -30,6 +36,7 @@ import { FullRenderer } from "./renderer/fullrenderer";
import { TerminalRenderer } from "../../common/terminal/Terminal";
import { isBlank } from "../../util/util";
import { PluginModel } from "../../plugins/plugins";
import { Prompt } from "../../terminal/prompt";
import * as lineutil from "./lineutil";
import "./lines.less";
@ -194,7 +201,6 @@ class LineCmd extends React.Component<
staticRender: boolean;
visible: OV<boolean>;
onHeightChange: LineHeightChangeCallbackType;
topBorder: boolean;
renderMode: RenderModeType;
overrideCollapsed: OV<boolean>;
noSelect?: boolean;
@ -460,12 +466,7 @@ class LineCmd extends React.Component<
}
getTerminalRendererHeight(cmd: Cmd): number {
let { screen, line, width, topBorder, renderMode } = this.props;
// header is 36px tall, padding+border = 6px
// zero-terminal is 0px
// terminal-wrapper overhead is 11px (margin/padding)
// inner-height, if zero-lines => 42
// else: 53+(lines*lineheight)
let { screen, line, width, renderMode } = this.props;
let height = 36 + 6; // height of zero height terminal
let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width);
if (usedRows > 0) {
@ -490,7 +491,7 @@ class LineCmd extends React.Component<
}
renderSimple() {
let { screen, line, topBorder } = this.props;
let { screen, line } = this.props;
let cmd = screen.getCmd(line);
let height: number = 0;
if (isBlank(line.renderer) || line.renderer == "terminal") {
@ -503,9 +504,7 @@ class LineCmd extends React.Component<
height = (hidePrompt ? 16 + 6 : 36 + 6) + usedRows;
}
let formattedTime = lineutil.getLineDateTimeStr(line.ts);
let mainDivCn = cn("line", "line-cmd", "line-simple", {
"top-border": topBorder,
});
let mainDivCn = cn("line", "line-cmd", "line-simple");
return (
<div
className={mainDivCn}
@ -635,14 +634,11 @@ class LineCmd extends React.Component<
};
render() {
let { screen, line, width, staticRender, visible, topBorder, renderMode } = this.props;
let model = GlobalModel;
let lineid = line.lineid;
let { screen, line, width, staticRender, visible } = this.props;
let isVisible = visible.get();
if (staticRender || !isVisible) {
return this.renderSimple();
}
let formattedTime = lineutil.getLineDateTimeStr(line.ts);
let cmd = screen.getCmd(line);
if (cmd == null) {
return (
@ -695,10 +691,10 @@ class LineCmd extends React.Component<
let mainDivCn = cn(
"line",
"line-cmd",
{ focus: isFocused },
{ selected: isSelected },
{ active: isSelected && isFocused },
{ "cmd-done": !isRunning },
{ "has-rtnstate": cmd.getRtnState() },
{ "top-border": topBorder }
{ "has-rtnstate": cmd.getRtnState() }
);
let rendererPlugin: RendererPluginType = null;
let isNoneRenderer = line.renderer == "none";
@ -716,10 +712,6 @@ class LineCmd extends React.Component<
data-linenum={line.linenum}
data-screenid={line.screenid}
>
<div
key="focus"
className={cn("focus-indicator", { selected: isSelected }, { active: isSelected && isFocused })}
/>
<div
key="header"
className={cn("line-header", { "is-expanded": isExpanded }, { "hide-prompt": hidePrompt })}
@ -735,27 +727,28 @@ class LineCmd extends React.Component<
onClick={this.clickPin}
style={{ display: "none" }}
>
<i className="fa-sharp fa-solid fa-thumbtack" />
<PinIcon className="icon" />
</div>
<div
key="bookmark"
title="Bookmark"
className={cn("line-icon", "line-bookmark")}
className={cn("line-icon", "line-bookmark", "hoverEffect")}
onClick={this.clickBookmark}
>
<i className="fa-sharp fa-regular fa-bookmark" />
<FavouritesIcon className="icon" />
</div>
<div
key="minimise"
title={`${this.isMinimised.get() ? "Maximise" : "Minimise"}`}
className={cn("line-icon", "line-minimise", this.isMinimised.get() ? "line-icon-show" : "")}
className={cn(
"line-icon",
"line-minimise",
"hoverEffect",
this.isMinimised.get() ? "line-icon-show" : ""
)}
onClick={this.clickMinimise}
>
<i
className={`fa-sharp fa-regular ${
this.isMinimised.get() ? "fa-plus-circle" : "fa-minus-circle"
}`}
/>
{this.isMinimised.get() ? <PlusIcon className="icon" /> : <MinusIcon className="icon" />}
</div>
</div>
<If condition={!this.isMinimised.get()}>
@ -798,16 +791,13 @@ class LineCmd extends React.Component<
className="cmd-rtnstate"
style={{
visibility: cmd.getStatus() == "done" ? "visible" : "hidden",
fontSize: GlobalModel.termFontSize.get(),
}}
>
<If condition={rsdiff == null || rsdiff == ""}>
<div className="cmd-rtnstate-label">state unchanged</div>
<div className="cmd-rtnstate-sep"></div>
</If>
<If condition={rsdiff != null && rsdiff != ""}>
<div className="cmd-rtnstate-label">new state</div>
<div className="cmd-rtnstate-sep"></div>
<div className="cmd-rtnstate-diff">{this.rtnStateDiff.get()}</div>
</If>
</div>
@ -833,7 +823,6 @@ class Line extends React.Component<
visible: OV<boolean>;
onHeightChange: LineHeightChangeCallbackType;
overrideCollapsed: OV<boolean>;
topBorder: boolean;
renderMode: RenderModeType;
noSelect?: boolean;
},
@ -854,85 +843,12 @@ class Line extends React.Component<
}
}
@mobxReact.observer
class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<string, string> }, {}> {
render() {
let rptr = this.props.rptr;
if (rptr == null || isBlank(rptr.remoteid)) {
return <span className={cn("term-prompt", "color-green")}>&nbsp;</span>;
}
let remote = GlobalModel.getRemote(this.props.rptr.remoteid);
let remoteStr = getRemoteStr(rptr);
let festate = this.props.festate ?? {};
let cwd = getCwdStr(remote, festate);
let isRoot = false;
if (remote && remote.remotevars) {
if (remote.remotevars["sudo"] || remote.remotevars["bestuser"] == "root") {
isRoot = true;
}
}
let remoteColorClass = isRoot ? "color-red" : "color-green";
if (remote && remote.remoteopts && remote.remoteopts.color) {
remoteColorClass = "color-" + remote.remoteopts.color;
}
// TESTING cwd shortening with triple colon character
// if (cwd.startsWith("~/work/gopath/src/github.com/scripthaus-dev")) {
// cwd = cwd.replace("~/work/gopath/src/github.com/scripthaus-dev", "\u22EEscripthaus-dev");
// }
let remoteTitle: string = null;
if (remote && remote.remotecanonicalname) {
remoteTitle = "connected to " + remote.remotecanonicalname;
}
let cwdElem = (
<span title="current directory" className="term-prompt-cwd">
<i className="fa-solid fa-sharp fa-folder-open" />
{cwd}
</span>
);
let remoteElem = (
<span title={remoteTitle} className={cn("term-prompt-remote", remoteColorClass)}>
[{remoteStr}]{" "}
</span>
);
let rootIndicatorElem = <span className="term-prompt-end">{isRoot ? "#" : "$"}</span>;
let branchElem = null;
let pythonElem = null;
if (!isBlank(festate["PROMPTVAR_GITBRANCH"])) {
let branchName = festate["PROMPTVAR_GITBRANCH"];
branchElem = (
<span title="current git branch" className="term-prompt-branch">
<i className="fa-sharp fa-solid fa-code-branch" />
{branchName}{" "}
</span>
);
}
if (!isBlank(festate["VIRTUAL_ENV"])) {
let venvDir = festate["VIRTUAL_ENV"];
let venv = getShortVEnv(venvDir);
pythonElem = (
<span title="python venv" className="term-prompt-python">
<i className="fa-brands fa-python" />
{venv}{" "}
</span>
);
}
return (
<span className="term-prompt">
{remoteElem} {pythonElem}
{branchElem}
{cwdElem} {rootIndicatorElem}
</span>
);
}
}
@mobxReact.observer
class LineText extends React.Component<
{
screen: LineContainerModel;
line: LineType;
renderMode: RenderModeType;
topBorder: boolean;
noSelect?: boolean;
},
{}
@ -962,7 +878,7 @@ class LineText extends React.Component<
}
render() {
let { screen, line, topBorder, renderMode } = this.props;
let { screen, line, renderMode } = this.props;
let formattedTime = lineutil.getLineDateTimeStr(line.ts);
let isSelected = mobx
.computed(() => screen.getSelectedLine() == line.linenum, {
@ -974,9 +890,7 @@ class LineText extends React.Component<
name: "computed-isFocused",
})
.get();
let mainClass = cn("line", "line-text", "focus-parent", {
"top-border": topBorder,
});
let mainClass = cn("line", "line-text", "focus-parent");
return (
<div
className={mainClass}
@ -998,4 +912,4 @@ class LineText extends React.Component<
}
}
export { Line, Prompt };
export { Line };

View File

@ -61,20 +61,15 @@
visibility: hidden;
cursor: pointer;
padding: 3px;
width: 2rem;
height: 2rem;
border-radius: 50%;
}
.line-icon-show {
visibility: visible;
}
.line-bookmark:hover {
color: @term-white;
}
.line-minimise:hover {
color: @term-white;
}
.line-icon + .line-icon {
margin-left: 5px;
}
@ -108,9 +103,6 @@
}
.image-wrapper {
padding: 2px 10px 5px 4px;
margin: 4px 8px 0 -4px;
.loading-div {
height: 20px;
margin-left: 50px;
@ -118,9 +110,6 @@
}
.terminal-wrapper {
padding: 2px 10px 5px 4px;
margin: 4px 8px 0 -4px;
&.zero-height {
padding: 0;
margin: 0;
@ -132,7 +121,8 @@
}
.terminal {
margin-right: 8px; /* needed to show scrollbar */
margin-right: 8px;
padding: 0.25em;
}
&.cmd-done .terminal .xterm-cursor {
@ -153,21 +143,11 @@
.cmd-rtnstate-label {
position: relative;
z-index: 2;
margin-left: 10px;
margin: 6px 0 -2px 10px;
padding: 2px 5px 2px 5px;
color: #666;
background-color: @term-black;
display: inline-block;
}
.cmd-rtnstate-sep {
height: 1px;
border-bottom: 1px solid #222;
position: relative;
top: -8px;
width: min(300px, 50%);
margin-bottom: -4px;
font-size: 0.8em;
opacity: 0.5;
}
.cmd-rtnstate-diff {
@ -179,12 +159,25 @@
}
.line {
margin: 0px 5px 0px 5px;
padding: 5px 5px 0px 12px;
margin: 1em 1em 0 1em;
padding: 1em;
border-radius: 6px;
display: flex;
overflow: hidden;
flex-shrink: 0;
position: relative;
background: @base-background;
border: 1px solid transparent;
&.selected {
border: 1px solid rgba(@base-color, 0.8) !important;
box-shadow: 0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
}
&.active {
border: 1px solid rgba(@prompt-green, 0.8) !important;
box-shadow: 0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
}
.focus-indicator {
height: calc(100% - 7px);
@ -214,7 +207,6 @@
&.has-rtnstate {
.linenum {
color: @term-white;
font-weight: bold;
}
}
@ -254,7 +246,6 @@
.avatar {
max-height: 38px;
width: 38px;
background-color: #555;
display: flex;
flex-shrink: 0;
align-items: center;

View File

@ -5,15 +5,16 @@ import { boundMethod } from "autobind-decorator";
import { If } from "tsx-control-statements/components";
import cn from "classnames";
import dayjs from "dayjs";
import type { RemoteType, RemoteInstanceType, RemotePtrType } from "../../types/types";
import type { RemoteType, RemoteInstanceType, RemotePtrType } from "../../../types/types";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel, GlobalCommandRunner } from "../../model";
import { Prompt } from "../line/linecomps";
import { renderCmdText } from "../../common/common";
import { GlobalModel, GlobalCommandRunner } from "../../../model";
import { renderCmdText } from "../../../common/common";
import { TextAreaInput } from "./TextareaInput";
import { InfoMsg } from "./InfoMsg";
import { HistoryInfo } from "./HistoryInfo";
import "./sessionview.less";
import { InfoMsg } from "../InfoMsg";
import { HistoryInfo } from "../HistoryInfo";
import { Prompt } from "../../../terminal/prompt";
import { ReactComponent as ExecIcon } from "../../../assets/icons/exec.svg";
import "./cmdInput.less";
dayjs.extend(localizedFormat);
@ -105,15 +106,7 @@ class CmdInput extends React.Component<{}, {}> {
let inputMode: string = inputModel.inputMode.get();
let textAreaInputKey = screen == null ? "null" : screen.screenId;
return (
<div
ref={this.cmdInputRef}
className={cn(
"cmd-input has-background-black",
{ "has-info": infoShow },
{ "has-history": historyShow }
)}
>
<div key="focus" className={cn("focus-indicator", { active: focusVal })} />
<div ref={this.cmdInputRef} className={cn("cmd-input", { "has-info": infoShow }, { active: focusVal })}>
<div key="minmax" onClick={this.onInfoToggle} className="input-minmax-control">
<If condition={infoShow || historyShow}>
<i className="fa-sharp fa-solid fa-chevron-down" />
@ -160,28 +153,23 @@ class CmdInput extends React.Component<{}, {}> {
</div>
</If>
<TextAreaInput key={textAreaInputKey} onHeightChange={this.handleInnerHeightUpdate} />
<div className="control cmd-exec">
<div onClick={inputModel.uiSubmitCommand} className="button" title="Run Command">
<span className="icon">
<i className="fa-sharp fa-solid fa-rocket" />
</span>
</div>
</div>
<div className="cmd-hints">
<div onClick={inputModel.toggleExpandInput} className="hint-item color-white">
<div className="control cmd-exec" onClick={inputModel.uiSubmitCommand}>
{/**<div onClick={inputModel.toggleExpandInput} className="hint-item color-white">
{inputModel.inputExpanded.get() ? "shrink" : "expand"} input ({renderCmdText("E")})
</div>
<If condition={!focusVal}>
<div onClick={this.clickFocusInputHint} className="hint-item color-white">
</div>**/}
{!focusVal && (
<div onClick={this.clickFocusInputHint} className="cmd-btn">
focus input ({renderCmdText("I")})
</div>
</If>
<If condition={focusVal}>
<div onMouseDown={this.clickHistoryHint} className="hint-item color-green">
<i className={cn("fa-sharp fa-solid", historyShow ? "fa-angle-down" : "fa-angle-up")} />{" "}
)}
{focusVal && (
<div onMouseDown={this.clickHistoryHint} className="cmd-btn">
{historyShow ? "close history (esc)" : "show history (ctrl-r)"}
</div>
</If>
)}
<ExecIcon
className={`icon ${inputModel.getCurLine().trim() === "" ? "disabled" : "hoverEffect"}`}
/>
</div>
</div>
</div>

View File

@ -3,10 +3,10 @@ import * as mobxReact from "mobx-react";
import * as mobx from "mobx";
import { boundMethod } from "autobind-decorator";
import cn from "classnames";
import { GlobalModel, GlobalCommandRunner } from "../../model";
import { getMonoFontSize } from "../../util/textmeasure";
import { isModKeyPress, hasNoModifiers } from "../../util/util";
import "./sessionview.less";
import { GlobalModel, GlobalCommandRunner } from "../../../model";
import { getMonoFontSize } from "../../../util/textmeasure";
import { isModKeyPress, hasNoModifiers } from "../../../util/util";
import "../sessionview.less";
function pageSize(div: any): number {
if (div == null) {
@ -534,9 +534,10 @@ class TextAreaInput extends React.Component<{ onHeightChange: () => void }, {}>
if (activeScreen != null) {
activeScreen.focusType.get(); // for reaction
}
let computedHeight = displayLines * 24 + 14 + 2; // 24 = height of line, 14 = padding, 2 = border
let computedInnerHeight = (displayLines + 1) * GlobalModel.termFontSize.get();
let computedOuterHeight = (displayLines + 2) * GlobalModel.termFontSize.get();
return (
<div className="control cmd-input-control is-expanded" ref={this.controlRef}>
<div className="control is-expanded" ref={this.controlRef} style={{ height: computedOuterHeight }}>
<textarea
key="main"
ref={this.mainInputRef}
@ -546,11 +547,11 @@ class TextAreaInput extends React.Component<{ onHeightChange: () => void }, {}>
id="main-cmd-input"
onFocus={this.handleMainFocus}
onBlur={this.handleMainBlur}
style={{ height: computedHeight, minHeight: computedHeight }}
style={{ height: computedInnerHeight, minHeight: computedInnerHeight }}
value={curLine}
onKeyDown={this.onKeyDown}
onChange={this.onChange}
className={cn("textarea", { "display-disabled": disabled })}
className={cn("textarea glow", { "display-disabled": disabled })}
></textarea>
<input
key="history"

View File

@ -0,0 +1,393 @@
@import "../../../index.less";
.cmd-input {
border-radius: 6px;
max-height: max(300px, 40%);
display: flex;
flex-direction: column;
position: absolute;
bottom: 16px;
right: 16px;
width: calc(100% - 32px);
padding: 12px;
z-index: 100;
background: @background-session-components;
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5), 0px 3px 8px 0px rgba(0, 0, 0, 0.35),
0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
backdrop-filter: blur(20px);
border: 1px solid transparent;
&.active {
border: 1px solid rgba(@prompt-green, 0.8) !important;
box-shadow: 0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
}
&.has-info {
padding-top: 10px;
}
.focus-indicator {
height: 90%;
top: 5%;
left: 4px;
}
&.has-history {
padding-top: 5px;
height: max(300px, 40%);
}
&.has-remote {
max-height: max(300px, 70%);
}
.remote-status-warning {
display: flex;
flex-direction: row;
color: @term-yellow;
align-items: center;
.button {
margin-left: 10px;
}
.remote-name {
}
}
.input-minmax-control {
position: absolute;
top: 5px;
right: 5px;
color: @term-white;
padding: 5px;
cursor: pointer;
}
.cmd-input-grow-spacer {
flex-grow: 1;
}
.cmd-input-context {
color: #fff;
white-space: nowrap;
}
.cmd-input-field {
position: relative;
padding-right: 22em;
.cmd-hints {
position: absolute;
bottom: -14px;
right: 0px;
}
.control {
padding: 1em 2px;
}
textarea::-webkit-scrollbar {
width: 0;
height: 0;
display: none;
}
textarea {
color: @term-bright-white;
background-color: transparent;
padding: 0.5em;
resize: none;
overflow: auto;
overflow-wrap: anywhere;
border-color: transparent;
&.display-disabled {
background-color: #444;
}
}
input.history-input {
border: 0;
padding: 0;
height: 0;
}
.cmd-quick-context .button {
background-color: #000 !important;
color: @term-white;
}
&.inputmode-global .cmd-quick-context .button {
color: @term-black;
background-color: @tab-green !important;
}
&.inputmode-comment .cmd-quick-context .button {
color: @term-black;
background-color: @tab-blue !important;
}
.cmd-exec {
position: absolute;
right: 0;
bottom: -6px;
.icon {
margin: 0 8px 0 6px;
vertical-align: middle;
width: 32px;
height: 32px;
cursor: pointer;
padding: 4px;
border-radius: 50%;
}
.icon.disabled {
fill: @disabled;
cursor: default;
}
.cmd-btn {
display: inline-block;
margin-right: 2em;
opacity: 0.5;
cursor: pointer;
&:hover {
opacity: 1;
}
}
}
}
.cmd-history {
color: @term-white;
margin-bottom: 5px;
overflow: auto;
flex-shrink: 1;
.history-title {
position: absolute;
z-index: 102;
top: 5px;
left: 20px;
background-color: @term-black;
color: @soft-blue;
padding-bottom: 4px;
display: flex;
flex-direction: row;
width: calc(100% - 40px);
overflow-x: auto;
.history-opt {
white-space: nowrap;
}
.history-clickable-opt {
white-space: nowrap;
cursor: pointer;
}
.grow-spacer {
flex: 1 0 10px;
}
.spacer {
flex: 0 0 10px;
}
}
.history-items {
margin-top: 24px;
color: @term-white;
padding-bottom: 6px;
.history-line {
white-space: pre;
}
.history-item.history-haderror {
color: mix(@term-red, @term-white, 50%);
}
.history-line:first-child {
margin-left: 0 !important;
}
.history-item {
padding-left: 5px;
cursor: pointer;
&:hover {
background-color: #222;
}
}
.history-item.is-selected {
font-weight: bold;
color: @term-bright-white;
background-color: #444;
}
.history-item.is-selected.history-haderror {
color: mix(@term-bright-red, @term-bright-white, 50%);
}
}
}
.cmd-input-info {
flex-shrink: 1;
overflow-y: auto;
margin-bottom: 5px;
.info-msg {
color: @soft-blue;
padding-bottom: 2px;
a {
color: @term-blue;
}
}
.info-title {
color: @soft-blue;
padding-bottom: 2px;
}
.info-lines {
color: @term-white;
white-space: pre;
padding-bottom: 6px;
}
.info-comps {
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding-bottom: 5px;
.info-comp {
min-width: 200px;
color: @term-white;
margin-right: 10px;
&.has-space {
text-decoration: underline dotted #777;
}
}
.metacmd-comp {
color: @term-bright-green;
}
}
.info-error {
color: @term-red;
padding-bottom: 2px;
}
.info-remote-showall {
table.remotes-table {
th {
color: @term-white;
font-weight: bold;
}
th,
td {
padding: 3px 8px 3px 8px;
}
td {
cursor: pointer;
}
tr:hover td {
background-color: #333;
}
}
}
.info-remote {
color: #d3d7cf;
.info-remote-title {
font-weight: bold;
color: @term-cyan;
}
.info-error,
.info-msg {
margin-top: 5px;
padding: 5px;
}
.remote-field {
display: flex;
flex-direction: row;
.remote-field-def {
white-space: pre;
width: 120px;
}
.remote-field-val {
white-space: pre;
display: flex;
flex-direction: row;
}
}
.remote-input-field {
display: flex;
flex-direction: row;
height: 25px;
align-items: center;
.remote-field-label {
white-space: pre;
width: 140px;
font-weight: bold;
color: @term-bright-white;
}
.undo-icon {
margin-left: 4px;
cursor: pointer;
padding: 2px;
}
.remote-field-control {
&.text-control {
}
&.text-input {
input[type="text"],
input[type="number"],
input[type="password"] {
background-color: @term-black;
color: @term-white;
width: 200px;
}
}
&.checkbox-input {
input[type="checkbox"] {
position: relative;
top: 3px;
}
}
&.select-input {
select {
width: 200px;
background-color: @term-black;
color: @term-white;
}
}
}
}
}
.info-remote-showall {
color: #d3d7cf;
}
}
}

View File

@ -0,0 +1,427 @@
import * as React from "react";
import * as mobxReact from "mobx-react";
import * as mobx from "mobx";
import { sprintf } from "sprintf-js";
import { boundMethod } from "autobind-decorator";
import { If, For } from "tsx-control-statements/components";
import cn from "classnames";
import { debounce } from "throttle-debounce";
import dayjs from "dayjs";
import type { LineType, RenderModeType, LineFactoryProps } from "../../../types/types";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel, GlobalCommandRunner, Session, ScreenLines, Screen } from "../../../model";
import { Line } from "../../line/linecomps";
import { renderCmdText } from "../../../common/common";
import { LinesView } from "../../line/linesview";
import { ReactComponent as SparkleIcon } from "../../../assets/icons/tab/sparkle.svg";
import { ReactComponent as ActionsIcon } from "../../../assets/icons/tab/actions.svg";
import { ReactComponent as AddIcon } from "../../../assets/icons/add.svg";
import "../sessionview.less";
import "./tabs.less";
dayjs.extend(localizedFormat);
type OV<V> = mobx.IObservableValue<V>;
@mobxReact.observer
class ScreenView extends React.Component<{ screen: Screen }, {}> {
render() {
let { screen } = this.props;
if (screen == null) {
return <div className="screen-view">(no screen found)</div>;
}
let fontSize = GlobalModel.termFontSize.get();
return (
<div className="screen-view" data-screenid={screen.screenId}>
<ScreenWindowView key={screen.screenId + ":" + fontSize} screen={screen} />
</div>
);
}
}
// screen is not null
@mobxReact.observer
class ScreenWindowView extends React.Component<{ screen: Screen }, {}> {
rszObs: any;
windowViewRef: React.RefObject<any>;
width: mobx.IObservableValue<number> = mobx.observable.box(0, { name: "sw-view-width" });
height: mobx.IObservableValue<number> = mobx.observable.box(0, { name: "sw-view-height" });
setSize_debounced: (width: number, height: number) => void;
renderMode: OV<RenderModeType> = mobx.observable.box("normal", { name: "renderMode" });
shareCopied: OV<boolean> = mobx.observable.box(false, { name: "sw-shareCopied" });
constructor(props: any) {
super(props);
this.setSize_debounced = debounce(1000, this.setSize.bind(this));
this.windowViewRef = React.createRef();
}
setSize(width: number, height: number): void {
let { screen } = this.props;
if (screen == null) {
return;
}
if (width == null || height == null || width == 0 || height == 0) {
return;
}
mobx.action(() => {
this.width.set(width);
this.height.set(height);
screen.screenSizeCallback({ height: height, width: width });
})();
}
componentDidMount() {
let wvElem = this.windowViewRef.current;
if (wvElem != null) {
let width = wvElem.offsetWidth;
let height = wvElem.offsetHeight;
this.setSize(width, height);
this.rszObs = new ResizeObserver(this.handleResize.bind(this));
this.rszObs.observe(wvElem);
}
}
componentWillUnmount() {
if (this.rszObs) {
this.rszObs.disconnect();
}
}
handleResize(entries: any) {
if (entries.length == 0) {
return;
}
let entry = entries[0];
let width = entry.target.offsetWidth;
let height = entry.target.offsetHeight;
mobx.action(() => {
this.setSize_debounced(width, height);
})();
}
getScreenLines(): ScreenLines {
let { screen } = this.props;
let win = GlobalModel.getScreenLinesById(screen.screenId);
if (win == null) {
win = GlobalModel.loadScreenLines(screen.screenId);
}
return win;
}
@boundMethod
toggleRenderMode() {
let renderMode = this.renderMode.get();
mobx.action(() => {
this.renderMode.set(renderMode == "normal" ? "collapsed" : "normal");
})();
}
renderError(message: string, fade: boolean) {
let { screen } = this.props;
return (
<div className="window-view" ref={this.windowViewRef} data-screenid={screen.screenId}>
<div key="lines" className="lines"></div>
<div key="window-empty" className={cn("window-empty", { "should-fade": fade })}>
<div>{message}</div>
</div>
</div>
);
}
@boundMethod
copyShareLink(): void {
let { screen } = this.props;
let shareLink = screen.getWebShareUrl();
if (shareLink == null) {
return;
}
navigator.clipboard.writeText(shareLink);
mobx.action(() => {
this.shareCopied.set(true);
})();
setTimeout(() => {
mobx.action(() => {
this.shareCopied.set(false);
})();
}, 600);
}
@boundMethod
openScreenSettings(): void {
let { screen } = this.props;
mobx.action(() => {
GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId });
})();
}
@boundMethod
buildLineComponent(lineProps: LineFactoryProps): JSX.Element {
let { screen } = this.props;
let { line, ...restProps } = lineProps;
let realLine: LineType = line as LineType;
return <Line key={realLine.lineid} screen={screen} line={realLine} {...restProps} />;
}
render() {
let { screen } = this.props;
let win = this.getScreenLines();
if (win == null || !win.loaded.get()) {
return this.renderError("...", true);
}
if (win.loadError.get() != null) {
return this.renderError(sprintf("(%s)", win.loadError.get()), false);
}
if (this.width.get() == 0) {
return this.renderError("", false);
}
let cdata = GlobalModel.clientData.get();
if (cdata == null) {
return this.renderError("loading client data", true);
}
let idx = 0;
let line: LineType = null;
let session = GlobalModel.getSessionById(screen.sessionId);
let isActive = screen.isActive();
let selectedLine = screen.getSelectedLine();
let lines = win.getNonArchivedLines();
let renderMode = this.renderMode.get();
return (
<div className="window-view" ref={this.windowViewRef}>
<div
key="rendermode-tag"
className={cn("rendermode-tag", { "is-active": isActive })}
style={{ display: "none" }}
>
<div className="render-mode" onClick={this.toggleRenderMode}>
<If condition={renderMode == "normal"}>
<i title="collapse" className="fa-sharp fa-solid fa-arrows-to-line" />
</If>
<If condition={renderMode == "collapsed"}>
<i title="expand" className="fa-sharp fa-solid fa-arrows-from-line" />
</If>
</div>
</div>
<If condition={screen.isWebShared()}>
<div key="share-tag" className="share-tag">
<If condition={this.shareCopied.get()}>
<div className="copied-indicator" />
</If>
<div className="share-tag-title">
<i title="archived" className="fa-sharp fa-solid fa-share-nodes" /> web shared
</div>
<div className="share-tag-link">
<div className="button is-prompt-green is-outlined is-small" onClick={this.copyShareLink}>
<span>copy link</span>
<span className="icon">
<i className="fa-sharp fa-solid fa-copy" />
</span>
</div>
<div
className="button is-prompt-green is-outlined is-small"
onClick={this.openScreenSettings}
>
<span>open settings</span>
<span className="icon">
<i className="fa-sharp fa-solid fa-cog" />
</span>
</div>
</div>
</div>
</If>
<If condition={lines.length > 0}>
<LinesView
screen={screen}
width={this.width.get()}
lines={lines}
renderMode={renderMode}
lineFactory={this.buildLineComponent}
/>
</If>
<If condition={lines.length == 0}>
<div key="window-empty" className="window-empty">
<div>
<code>
[session="{session.name.get()}" screen="{screen.name.get()}"]
</code>
</div>
</div>
</If>
</div>
);
}
}
@mobxReact.observer
class ScreenTabs extends React.Component<{ session: Session }, {}> {
tabsRef: React.RefObject<any> = React.createRef();
lastActiveScreenId: string = null;
scrolling: OV<boolean> = mobx.observable.box(false, { name: "screentabs-scrolling" });
stopScrolling_debounced: () => void;
constructor(props: any) {
super(props);
this.stopScrolling_debounced = debounce(1500, this.stopScrolling.bind(this));
}
@boundMethod
handleNewScreen() {
let { session } = this.props;
GlobalCommandRunner.createNewScreen();
}
@boundMethod
handleSwitchScreen(screenId: string) {
let { session } = this.props;
if (session == null) {
return;
}
if (session.activeScreenId.get() == screenId) {
return;
}
let screen = session.getScreenById(screenId);
if (screen == null) {
return;
}
GlobalCommandRunner.switchScreen(screenId);
}
componentDidMount(): void {
this.componentDidUpdate();
}
componentDidUpdate(): void {
let { session } = this.props;
let activeScreenId = session.activeScreenId.get();
if (activeScreenId != this.lastActiveScreenId && this.tabsRef.current) {
let tabElem = this.tabsRef.current.querySelector(
sprintf('.screen-tab[data-screenid="%s"]', activeScreenId)
);
if (tabElem != null) {
tabElem.scrollIntoView();
}
}
this.lastActiveScreenId = activeScreenId;
}
stopScrolling(): void {
mobx.action(() => {
this.scrolling.set(false);
})();
}
@boundMethod
handleScroll() {
if (!this.scrolling.get()) {
mobx.action(() => {
this.scrolling.set(true);
})();
}
this.stopScrolling_debounced();
}
@boundMethod
openScreenSettings(e: any, screen: Screen): void {
e.preventDefault();
e.stopPropagation();
mobx.action(() => {
GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId });
})();
}
renderTab(screen: Screen, activeScreenId: string, index: number): any {
let tabIndex = null;
if (index + 1 <= 9) {
tabIndex = <div className="tab-index">{renderCmdText(String(index + 1))}</div>;
}
let settings = (
<div onClick={(e) => this.openScreenSettings(e, screen)} title="Actions" className="tab-gear">
<ActionsIcon className="icon hoverEffect " />
</div>
);
let archived = screen.archived.get() ? (
<i title="archived" className="fa-sharp fa-solid fa-box-archive" />
) : null;
let webShared = screen.isWebShared() ? (
<i title="shared to web" className="fa-sharp fa-solid fa-share-nodes web-share-icon" />
) : null;
return (
<div
key={screen.screenId}
data-screenid={screen.screenId}
className={cn(
"screen-tab",
{ "is-active": activeScreenId == screen.screenId, "is-archived": screen.archived.get() },
"color-" + screen.getTabColor()
)}
onClick={() => this.handleSwitchScreen(screen.screenId)}
onContextMenu={(event) => this.openScreenSettings(event, screen)}
>
<SparkleIcon className="icon" />
<div className="tab-name truncate">
{archived}
{webShared}
{screen.name.get()}
</div>
{tabIndex}
{settings}
</div>
);
}
render() {
let { session } = this.props;
if (session == null) {
return null;
}
let screen: Screen = null;
let index = 0;
let showingScreens = [];
let activeScreenId = session.activeScreenId.get();
let screens = GlobalModel.getSessionScreens(session.sessionId);
for (let screen of screens) {
if (!screen.archived.get() || activeScreenId == screen.screenId) {
showingScreens.push(screen);
}
}
showingScreens.sort((a, b) => {
let aidx = a.screenIdx.get();
let bidx = b.screenIdx.get();
if (aidx < bidx) {
return -1;
}
if (aidx > bidx) {
return 1;
}
return 0;
});
return (
<div className="screen-tabs-container">
<div
className={cn("screen-tabs", { scrolling: this.scrolling.get() })}
ref={this.tabsRef}
onScroll={this.handleScroll}
>
<For each="screen" index="index" of={showingScreens}>
{this.renderTab(screen, activeScreenId, index)}
</For>
<div key="new-screen" className="screen-tab new-screen" onClick={this.handleNewScreen}>
<AddIcon className="icon hoverEffect" />
</div>
</div>
{/**<div className="cmd-hints">
<div className="hint-item color-green">move left {renderCmdText("[")}</div>
<div className="hint-item color-green">move right {renderCmdText("]")}</div>
<div className="hint-item color-green">new tab {renderCmdText("T")}</div>
</div>*/}
</div>
);
}
}
export { ScreenView, ScreenTabs };

View File

@ -1,6 +1,21 @@
@import "../../index.less";
@import "../../../index.less";
#main .screen-tabs .screen-tab {
&.color-default {
color: @tab-white-text;
&.is-active {
color: @tab-white-text;
border-radius: 12px 0px 0px 0px;
border-top: 1px solid #58c142;
background: linear-gradient(
180deg,
rgba(88, 193, 66, 0.2) 9.34%,
rgba(88, 193, 66, 0.03) 44.16%,
rgba(88, 193, 66, 0) 86.79%
);
}
}
&.color-green {
color: @tab-white-text;
background-color: desaturate(@tab-green, 50%);
@ -46,7 +61,6 @@
&.is-active {
background-color: @tab-yellow;
color: @term-black;
box-shadow: 0 3px 0 #fff inset, 0 4px 0 #000 inset;
}
}
@ -95,7 +109,6 @@
&.is-active {
color: @term-black;
background-color: @tab-white;
box-shadow: 0 3px 0 #fff inset, 0 4px 0 #000 inset;
}
}
@ -118,31 +131,9 @@
}
}
.screen-tabs-container {
position: relative;
&:hover {
z-index: 200;
}
&:hover .cmd-hints {
display: flex;
}
.cmd-hints {
position: absolute;
bottom: -18px;
left: 0px;
display: flex;
}
}
.screen-tabs {
height: 30px;
display: flex;
flex-direction: row;
border-top: 1px solid #666;
border-right: 1px solid #eee;
overflow-x: overlay;
overflow-y: visible;
align-items: center;
@ -164,31 +155,24 @@
}
.screen-tab {
height: 30px;
min-width: 80px;
width: 150px;
flex-shrink: 1;
display: flex;
justify-content: center;
height: 3em;
min-width: 14em;
max-width: 14em;
align-items: center;
font-weight: 500;
padding-left: 0;
padding-right: 0;
cursor: pointer;
position: relative;
border-right: 1px solid #777;
.icon {
width: 1.1em;
margin: 0 1em;
}
.tab-name {
flex-grow: 1;
flex-basis: 50px;
padding-left: 2px;
padding-right: 2px;
text-align: center;
width: 8rem;
}
&.is-active {
box-shadow: 0 3px 0 #fff inset;
border-top: none;
opacity: 1;
}
@ -199,49 +183,24 @@
}
}
.tab-index {
position: absolute;
right: 2px;
top: 4px;
bottom: 0;
font-weight: normal;
display: none;
padding-left: 4px;
padding-top: 6px;
background-color: inherit;
}
.tab-index,
.tab-gear {
position: absolute;
right: 5px;
top: 4px;
bottom: 0;
font-weight: normal;
display: none;
padding-left: 5px;
padding-top: 5px;
background-color: inherit;
.icon {
border-radius: 50%;
}
}
&:hover {
.tab-gear {
display: block;
&:hover {
display: block;
top: 4px;
padding-top: 4px;
right: 3px;
padding-left: 4px;
i {
}
}
}
}
}
&:hover .screen-tab .tab-index {
display: block;
font-size: 0.8em;
}
&:hover .screen-tab:hover .tab-index {
@ -249,16 +208,33 @@
}
.screen-tab.new-screen {
width: 30px;
min-width: 30px;
background-color: @term-black;
border-right: none;
color: @term-white;
width: 3em;
min-width: 3em;
cursor: pointer;
.icon {
width: 1.1em;
height: 1.1em;
border-radius: 50%;
}
}
}
.screen-tabs-container {
position: relative;
&:hover {
background-color: #666;
border-right: 1px solid #ccc;
}
z-index: 200;
}
&:hover .cmd-hints {
display: flex;
}
.cmd-hints {
position: absolute;
bottom: -18px;
left: 0px;
display: flex;
}
}

View File

@ -1,21 +1,13 @@
import * as React from "react";
import * as mobxReact from "mobx-react";
import * as mobx from "mobx";
import { sprintf } from "sprintf-js";
import { boundMethod } from "autobind-decorator";
import { If, For } from "tsx-control-statements/components";
import cn from "classnames";
import { debounce } from "throttle-debounce";
import dayjs from "dayjs";
import type { LineType, RenderModeType, LineFactoryProps } from "../../types/types";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel, GlobalCommandRunner, Session, ScreenLines, Screen } from "../../model";
import { Line } from "../line/linecomps";
import { renderCmdText } from "../../common/common";
import { LinesView } from "../line/linesview";
import { CmdInput } from "./CmdInput";
import { GlobalModel } from "../../model";
import { CmdInput } from "./CmdWindow/CmdInput";
import { ScreenView, ScreenTabs } from "./Screen/ScreenView";
import "./sessionview.less";
import "./tabs.less";
dayjs.extend(localizedFormat);
@ -37,8 +29,8 @@ class SessionView extends React.Component<{}, {}> {
let isHidden = GlobalModel.activeMainView.get() != "session";
return (
<div className={cn("session-view", { "is-hidden": isHidden })} data-sessionid={session.sessionId}>
<ScreenView screen={activeScreen} />
<ScreenTabs session={session} />
<ScreenView screen={activeScreen} />
<div style={{ height: cmdInputHeight }}></div>
<CmdInput />
</div>
@ -46,412 +38,4 @@ class SessionView extends React.Component<{}, {}> {
}
}
@mobxReact.observer
class ScreenView extends React.Component<{ screen: Screen }, {}> {
render() {
let { screen } = this.props;
if (screen == null) {
return <div className="screen-view">(no screen found)</div>;
}
let fontSize = GlobalModel.termFontSize.get();
return (
<div className="screen-view" data-screenid={screen.screenId}>
<ScreenWindowView key={screen.screenId + ":" + fontSize} screen={screen} />
</div>
);
}
}
// screen is not null
@mobxReact.observer
class ScreenWindowView extends React.Component<{ screen: Screen }, {}> {
rszObs: any;
windowViewRef: React.RefObject<any>;
width: mobx.IObservableValue<number> = mobx.observable.box(0, { name: "sw-view-width" });
height: mobx.IObservableValue<number> = mobx.observable.box(0, { name: "sw-view-height" });
setSize_debounced: (width: number, height: number) => void;
renderMode: OV<RenderModeType> = mobx.observable.box("normal", { name: "renderMode" });
shareCopied: OV<boolean> = mobx.observable.box(false, { name: "sw-shareCopied" });
constructor(props: any) {
super(props);
this.setSize_debounced = debounce(1000, this.setSize.bind(this));
this.windowViewRef = React.createRef();
}
setSize(width: number, height: number): void {
let { screen } = this.props;
if (screen == null) {
return;
}
if (width == null || height == null || width == 0 || height == 0) {
return;
}
mobx.action(() => {
this.width.set(width);
this.height.set(height);
screen.screenSizeCallback({ height: height, width: width });
})();
}
componentDidMount() {
let wvElem = this.windowViewRef.current;
if (wvElem != null) {
let width = wvElem.offsetWidth;
let height = wvElem.offsetHeight;
this.setSize(width, height);
this.rszObs = new ResizeObserver(this.handleResize.bind(this));
this.rszObs.observe(wvElem);
}
}
componentWillUnmount() {
if (this.rszObs) {
this.rszObs.disconnect();
}
}
handleResize(entries: any) {
if (entries.length == 0) {
return;
}
let entry = entries[0];
let width = entry.target.offsetWidth;
let height = entry.target.offsetHeight;
mobx.action(() => {
this.setSize_debounced(width, height);
})();
}
getScreenLines(): ScreenLines {
let { screen } = this.props;
let win = GlobalModel.getScreenLinesById(screen.screenId);
if (win == null) {
win = GlobalModel.loadScreenLines(screen.screenId);
}
return win;
}
getWindowViewStyle(): any {
return { position: "absolute", width: "100%", height: "100%", overflowX: "hidden" };
}
@boundMethod
toggleRenderMode() {
let renderMode = this.renderMode.get();
mobx.action(() => {
this.renderMode.set(renderMode == "normal" ? "collapsed" : "normal");
})();
}
renderError(message: string, fade: boolean) {
let { screen } = this.props;
return (
<div
className="window-view"
style={this.getWindowViewStyle()}
ref={this.windowViewRef}
data-screenid={screen.screenId}
>
<div key="lines" className="lines"></div>
<div key="window-empty" className={cn("window-empty", { "should-fade": fade })}>
<div>{message}</div>
</div>
</div>
);
}
@boundMethod
copyShareLink(): void {
let { screen } = this.props;
let shareLink = screen.getWebShareUrl();
if (shareLink == null) {
return;
}
navigator.clipboard.writeText(shareLink);
mobx.action(() => {
this.shareCopied.set(true);
})();
setTimeout(() => {
mobx.action(() => {
this.shareCopied.set(false);
})();
}, 600);
}
@boundMethod
openScreenSettings(): void {
let { screen } = this.props;
mobx.action(() => {
GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId });
})();
}
@boundMethod
buildLineComponent(lineProps: LineFactoryProps): JSX.Element {
let { screen } = this.props;
let { line, ...restProps } = lineProps;
let realLine: LineType = line as LineType;
return <Line key={realLine.lineid} screen={screen} line={realLine} {...restProps} />;
}
render() {
let { screen } = this.props;
let win = this.getScreenLines();
if (win == null || !win.loaded.get()) {
return this.renderError("...", true);
}
if (win.loadError.get() != null) {
return this.renderError(sprintf("(%s)", win.loadError.get()), false);
}
if (this.width.get() == 0) {
return this.renderError("", false);
}
let cdata = GlobalModel.clientData.get();
if (cdata == null) {
return this.renderError("loading client data", true);
}
let idx = 0;
let line: LineType = null;
let session = GlobalModel.getSessionById(screen.sessionId);
let isActive = screen.isActive();
let selectedLine = screen.getSelectedLine();
let lines = win.getNonArchivedLines();
let renderMode = this.renderMode.get();
return (
<div className="window-view" style={this.getWindowViewStyle()} ref={this.windowViewRef}>
<div
key="rendermode-tag"
className={cn("rendermode-tag", { "is-active": isActive })}
style={{ display: "none" }}
>
<div className="render-mode" onClick={this.toggleRenderMode}>
<If condition={renderMode == "normal"}>
<i title="collapse" className="fa-sharp fa-solid fa-arrows-to-line" />
</If>
<If condition={renderMode == "collapsed"}>
<i title="expand" className="fa-sharp fa-solid fa-arrows-from-line" />
</If>
</div>
</div>
<If condition={screen.isWebShared()}>
<div key="share-tag" className="share-tag">
<If condition={this.shareCopied.get()}>
<div className="copied-indicator" />
</If>
<div className="share-tag-title">
<i title="archived" className="fa-sharp fa-solid fa-share-nodes" /> web shared
</div>
<div className="share-tag-link">
<div className="button is-prompt-green is-outlined is-small" onClick={this.copyShareLink}>
<span>copy link</span>
<span className="icon">
<i className="fa-sharp fa-solid fa-copy" />
</span>
</div>
<div
className="button is-prompt-green is-outlined is-small"
onClick={this.openScreenSettings}
>
<span>open settings</span>
<span className="icon">
<i className="fa-sharp fa-solid fa-cog" />
</span>
</div>
</div>
</div>
</If>
<If condition={lines.length > 0}>
<LinesView
screen={screen}
width={this.width.get()}
lines={lines}
renderMode={renderMode}
lineFactory={this.buildLineComponent}
/>
</If>
<If condition={lines.length == 0}>
<div key="window-empty" className="window-empty">
<div>
<code>
[session="{session.name.get()}" screen="{screen.name.get()}"]
</code>
</div>
</div>
</If>
</div>
);
}
}
@mobxReact.observer
class ScreenTabs extends React.Component<{ session: Session }, {}> {
tabsRef: React.RefObject<any> = React.createRef();
lastActiveScreenId: string = null;
scrolling: OV<boolean> = mobx.observable.box(false, { name: "screentabs-scrolling" });
stopScrolling_debounced: () => void;
constructor(props: any) {
super(props);
this.stopScrolling_debounced = debounce(1500, this.stopScrolling.bind(this));
}
@boundMethod
handleNewScreen() {
let { session } = this.props;
GlobalCommandRunner.createNewScreen();
}
@boundMethod
handleSwitchScreen(screenId: string) {
let { session } = this.props;
if (session == null) {
return;
}
if (session.activeScreenId.get() == screenId) {
return;
}
let screen = session.getScreenById(screenId);
if (screen == null) {
return;
}
GlobalCommandRunner.switchScreen(screenId);
}
componentDidMount(): void {
this.componentDidUpdate();
}
componentDidUpdate(): void {
let { session } = this.props;
let activeScreenId = session.activeScreenId.get();
if (activeScreenId != this.lastActiveScreenId && this.tabsRef.current) {
let tabElem = this.tabsRef.current.querySelector(
sprintf('.screen-tab[data-screenid="%s"]', activeScreenId)
);
if (tabElem != null) {
tabElem.scrollIntoView();
}
}
this.lastActiveScreenId = activeScreenId;
}
stopScrolling(): void {
mobx.action(() => {
this.scrolling.set(false);
})();
}
@boundMethod
handleScroll() {
if (!this.scrolling.get()) {
mobx.action(() => {
this.scrolling.set(true);
})();
}
this.stopScrolling_debounced();
}
@boundMethod
openScreenSettings(e: any, screen: Screen): void {
e.preventDefault();
e.stopPropagation();
mobx.action(() => {
GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId });
})();
}
renderTab(screen: Screen, activeScreenId: string, index: number): any {
let tabIndex = null;
if (index + 1 <= 9) {
tabIndex = <div className="tab-index">{renderCmdText(String(index + 1))}</div>;
}
let settings = (
<div onClick={(e) => this.openScreenSettings(e, screen)} title="Settings" className="tab-gear">
<i className="fa-sharp fa-solid fa-gear" />
</div>
);
let archived = screen.archived.get() ? (
<i title="archived" className="fa-sharp fa-solid fa-box-archive" />
) : null;
let webShared = screen.isWebShared() ? (
<i title="shared to web" className="fa-sharp fa-solid fa-share-nodes web-share-icon" />
) : null;
return (
<div
key={screen.screenId}
data-screenid={screen.screenId}
className={cn(
"screen-tab",
{ "is-active": activeScreenId == screen.screenId, "is-archived": screen.archived.get() },
"color-" + screen.getTabColor()
)}
onClick={() => this.handleSwitchScreen(screen.screenId)}
onContextMenu={(event) => this.openScreenSettings(event, screen)}
>
<div className="tab-name">
{archived}
{webShared}
{screen.name.get()}
</div>
{tabIndex}
{settings}
</div>
);
}
render() {
let { session } = this.props;
if (session == null) {
return null;
}
let screen: Screen = null;
let index = 0;
let showingScreens = [];
let activeScreenId = session.activeScreenId.get();
let screens = GlobalModel.getSessionScreens(session.sessionId);
for (let screen of screens) {
if (!screen.archived.get() || activeScreenId == screen.screenId) {
showingScreens.push(screen);
}
}
showingScreens.sort((a, b) => {
let aidx = a.screenIdx.get();
let bidx = b.screenIdx.get();
if (aidx < bidx) {
return -1;
}
if (aidx > bidx) {
return 1;
}
return 0;
});
return (
<div className="screen-tabs-container">
<div
className={cn("screen-tabs", { scrolling: this.scrolling.get() })}
ref={this.tabsRef}
onScroll={this.handleScroll}
>
<For each="screen" index="index" of={showingScreens}>
{this.renderTab(screen, activeScreenId, index)}
</For>
<div key="new-screen" className="screen-tab new-screen" onClick={this.handleNewScreen}>
<i className="fa-sharp fa-solid fa-plus" />
</div>
</div>
<div className="cmd-hints">
<div className="hint-item color-green">move left {renderCmdText("[")}</div>
<div className="hint-item color-green">move right {renderCmdText("]")}</div>
<div className="hint-item color-green">new tab {renderCmdText("T")}</div>
</div>
</div>
);
}
}
export { SessionView };

View File

@ -1,371 +1,15 @@
@import "../../index.less";
.cmd-input-info,
.cmd-history {
&::-webkit-scrollbar {
background-color: #777;
width: 5px;
height: 5px;
}
&::-webkit-scrollbar-thumb {
background: white;
}
}
.cmd-input {
border-radius: 0;
border-top: 4px solid #ccc;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
border-bottom-right-radius: 10px;
max-height: max(300px, 40%);
.session-view {
flex-grow: 1;
display: flex;
flex-direction: column;
position: absolute;
bottom: 0;
width: 100%;
padding: 20px 25px 20px 20px;
z-index: 100;
&.has-info {
padding-top: 10px;
}
.focus-indicator {
height: 90%;
top: 5%;
left: 4px;
}
&.has-history {
padding-top: 5px;
height: max(300px, 40%);
}
&.has-remote {
max-height: max(300px, 70%);
}
.remote-status-warning {
display: flex;
flex-direction: row;
color: @term-yellow;
align-items: center;
.button {
margin-left: 10px;
}
.remote-name {
}
}
.input-minmax-control {
position: absolute;
top: 5px;
right: 5px;
color: @term-white;
padding: 5px;
cursor: pointer;
}
.cmd-input-grow-spacer {
flex-grow: 1;
}
.cmd-input-context {
color: #fff;
white-space: nowrap;
}
.cmd-input-field {
position: relative;
.cmd-hints {
position: absolute;
bottom: -14px;
right: 0px;
}
textarea {
color: @term-white;
background-color: @term-black;
padding-bottom: calc(0.5em - 1px);
padding-top: calc(0.5em - 1px);
resize: none;
overflow-wrap: anywhere;
&:active,
&:focus {
border-color: @term-white !important;
}
&.display-disabled {
background-color: #444;
}
}
input.history-input {
border: 0;
padding: 0;
height: 0;
}
.cmd-quick-context .button {
background-color: #000 !important;
color: @term-white;
}
&.inputmode-global .cmd-quick-context .button {
color: @term-black;
background-color: @tab-green !important;
}
&.inputmode-comment .cmd-quick-context .button {
color: @term-black;
background-color: @tab-blue !important;
}
.cmd-exec .button {
background-color: #000 !important;
color: #d3d7cf;
}
}
.cmd-history {
color: @term-white;
margin-bottom: 5px;
overflow: auto;
flex-shrink: 1;
.history-title {
position: absolute;
z-index: 102;
top: 5px;
left: 20px;
background-color: @term-black;
color: @soft-blue;
padding-bottom: 4px;
display: flex;
flex-direction: row;
width: calc(100% - 40px);
overflow-x: auto;
.history-opt {
white-space: nowrap;
}
.history-clickable-opt {
white-space: nowrap;
cursor: pointer;
}
.grow-spacer {
flex: 1 0 10px;
}
.spacer {
flex: 0 0 10px;
}
}
.history-items {
margin-top: 24px;
color: @term-white;
padding-bottom: 6px;
.history-line {
white-space: pre;
}
.history-item.history-haderror {
color: mix(@term-red, @term-white, 50%);
}
.history-line:first-child {
margin-left: 0 !important;
}
.history-item {
padding-left: 5px;
cursor: pointer;
&:hover {
background-color: #222;
}
}
.history-item.is-selected {
font-weight: bold;
color: @term-bright-white;
background-color: #444;
}
.history-item.is-selected.history-haderror {
color: mix(@term-bright-red, @term-bright-white, 50%);
}
}
}
.cmd-input-info {
flex-shrink: 1;
overflow-y: auto;
margin-bottom: 5px;
.info-msg {
color: @soft-blue;
padding-bottom: 2px;
a {
color: @term-blue;
}
}
.info-title {
color: @soft-blue;
padding-bottom: 2px;
}
.info-lines {
color: @term-white;
white-space: pre;
padding-bottom: 6px;
}
.info-comps {
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding-bottom: 5px;
.info-comp {
min-width: 200px;
color: @term-white;
margin-right: 10px;
&.has-space {
text-decoration: underline dotted #777;
}
}
.metacmd-comp {
color: @term-bright-green;
}
}
.info-error {
color: @term-red;
padding-bottom: 2px;
}
.info-remote-showall {
table.remotes-table {
th {
color: @term-white;
font-weight: bold;
}
th,
td {
padding: 3px 8px 3px 8px;
}
td {
cursor: pointer;
}
tr:hover td {
background-color: #333;
}
}
}
.info-remote {
color: #d3d7cf;
.info-remote-title {
font-weight: bold;
color: @term-cyan;
}
.info-error,
.info-msg {
margin-top: 5px;
padding: 5px;
}
.remote-field {
display: flex;
flex-direction: row;
.remote-field-def {
white-space: pre;
width: 120px;
}
.remote-field-val {
white-space: pre;
display: flex;
flex-direction: row;
}
}
.remote-input-field {
display: flex;
flex-direction: row;
height: 25px;
align-items: center;
.remote-field-label {
white-space: pre;
width: 140px;
font-weight: bold;
color: @term-bright-white;
}
.undo-icon {
margin-left: 4px;
cursor: pointer;
padding: 2px;
}
.remote-field-control {
&.text-control {
}
&.text-input {
input[type="text"],
input[type="number"],
input[type="password"] {
background-color: @term-black;
color: @term-white;
width: 200px;
}
}
&.checkbox-input {
input[type="checkbox"] {
position: relative;
top: 3px;
}
}
&.select-input {
select {
width: 200px;
background-color: @term-black;
color: @term-white;
}
}
}
}
}
.info-remote-showall {
color: #d3d7cf;
}
&.is-hidden {
display: none;
}
background: @background-session;
border: 1px solid @base-border;
border-radius: 8px;
margin: 0 16px 16px 0;
}

View File

@ -153,10 +153,10 @@ class MainSideBar extends React.Component<{}, {}> {
}
return sessionList.map((session, index) => {
const isActive = GlobalModel.activeMainView.get() == "session" && activeSessionId == session.sessionId;
/** @TODO: Handle archived sessions and talk to Mike about session settings */
return (
<>
{/** @TODO: Handle archived sessions and talk to Mike about session settings */}
<div
key={index}
className={`item hoverEffect ${isActive ? "active" : ""}`}
onClick={() => this.handleSessionClick(session.sessionId)}
>
@ -164,7 +164,6 @@ class MainSideBar extends React.Component<{}, {}> {
<span className="index">{index + 1}</span>
<span>{session.name.get()}</span>
</div>
</>
);
});
}
@ -196,7 +195,7 @@ class MainSideBar extends React.Component<{}, {}> {
return (
<div className={cn("main-sidebar", { collapsed: isCollapsed }, { "is-dev": GlobalModel.isDev })}>
<div className="arrow-container hoverEffect" onClick={this.toggleCollapsed}>
<LeftChevronIcon />
<LeftChevronIcon className="icon" />
</div>
<div className="contents">
<div className="top">
@ -228,7 +227,7 @@ class MainSideBar extends React.Component<{}, {}> {
<AddIcon />
</div>
</div>
<div className="middle">{this.getSessions()}</div>
<div className="middle hideScrollbarUntillHover">{this.getSessions()}</div>
<div className="bottom">
<div className="item hoverEffect" onClick={this.handleSettingsClick}>
<SettingsIcon className="icon" />

View File

@ -31,6 +31,11 @@
border-radius: 50%;
padding: 2px;
transition: transform 0.3s ease-in-out;
svg {
width: 16px;
height: 16px;
}
}
.top {
@ -44,7 +49,7 @@
}
.middle {
max-height: calc(100vh - 30em);
max-height: calc(100vh - 32em);
overflow-y: auto;
padding: 8px 6px;
border-bottom: 1px solid @base-border;
@ -73,7 +78,7 @@
.bottom {
position: absolute;
bottom: 6px;
bottom: 2em;
left: 0;
width: 100%;
}
@ -88,6 +93,8 @@
.icon {
margin: 0 8px 0 6px;
vertical-align: middle;
width: 16px;
height: 16px;
}
.hotkey {
float: right;

View File

@ -12,7 +12,6 @@ import {
isModKeyPress,
} from "./util/util";
import { TermWrap } from "./common/terminal/term";
import { v4 as uuidv4 } from "uuid";
import type {
SessionDataType,
LineType,
@ -451,7 +450,7 @@ class Screen {
}
getTabColor(): string {
let tabColor = "green";
let tabColor = "default";
let screenOpts = this.opts.get();
if (screenOpts != null && !isBlank(screenOpts.tabcolor)) {
tabColor = screenOpts.tabcolor;

View File

@ -329,8 +329,8 @@ class SourceCodeRenderer extends React.Component<
onMount={this.handleEditorDidMount}
options={{
scrollBeyondLastLine: false,
fontSize: GlobalModel.termFontSize.get(),
/* fontFamily: "JetBrains Mono", @check:font */
fontSize: GlobalModel.termFontSize.get() * 0.9,
/* fontFamily: "Martian Mono", */
readOnly: !this.getAllowEditing(),
}}
onChange={this.handleEditorChange}
@ -379,7 +379,10 @@ class SourceCodeRenderer extends React.Component<
</select>
{allowEditing && (
<div className="cmd-hints">
<div onClick={this.doSave} className={`hint-item ${isSave ? "save-enabled" : "save-disabled"}`}>
<div
onClick={() => this.doSave()}
className={`hint-item ${isSave ? "save-enabled" : "save-disabled"}`}
>
{`save (`}
{renderCmdText("S")}
{`)`}

View File

@ -62,6 +62,7 @@
}
.renderer-container {
margin-top: 1em;
.error-container {
color: @term-red;

156
src/terminal/prompt.tsx Normal file
View File

@ -0,0 +1,156 @@
import * as React from "react";
import * as mobxReact from "mobx-react";
import * as mobx from "mobx";
import dayjs from "dayjs";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel, LineContainerModel } from "../model";
import type { LineType, RemoteType, RemotePtrType, LineHeightChangeCallbackType } from "../../types/types";
import cn from "classnames";
import { isBlank } from "../util/util";
import { ReactComponent as FolderIcon } from "../assets/icons/folder.svg";
import "./terminal.less";
dayjs.extend(localizedFormat);
type OV<V> = mobx.IObservableValue<V>;
type OArr<V> = mobx.IObservableArray<V>;
type OMap<K, V> = mobx.ObservableMap<K, V>;
type RendererComponentProps = {
screen: LineContainerModel;
line: LineType;
width: number;
staticRender: boolean;
visible: OV<boolean>;
onHeightChange: LineHeightChangeCallbackType;
collapsed: boolean;
};
type RendererComponentType = {
new (props: RendererComponentProps): React.Component<RendererComponentProps, {}>;
};
function getShortVEnv(venvDir: string): string {
if (isBlank(venvDir)) {
return "";
}
let lastSlash = venvDir.lastIndexOf("/");
if (lastSlash == -1) {
return venvDir;
}
return venvDir.substr(lastSlash + 1);
}
function makeFullRemoteRef(ownerName: string, remoteRef: string, name: string): string {
if (isBlank(ownerName) && isBlank(name)) {
return remoteRef;
}
if (!isBlank(ownerName) && isBlank(name)) {
return ownerName + ":" + remoteRef;
}
if (isBlank(ownerName) && !isBlank(name)) {
return remoteRef + ":" + name;
}
return ownerName + ":" + remoteRef + ":" + name;
}
function getRemoteStr(rptr: RemotePtrType): string {
if (rptr == null || isBlank(rptr.remoteid)) {
return "(invalid remote)";
}
let username = isBlank(rptr.ownerid) ? null : GlobalModel.resolveUserIdToName(rptr.ownerid);
let remoteRef = GlobalModel.resolveRemoteIdToRef(rptr.remoteid);
let fullRef = makeFullRemoteRef(username, remoteRef, rptr.name);
return fullRef;
}
function replaceHomePath(path: string, homeDir: string): string {
if (path == homeDir) {
return "~";
}
if (path.startsWith(homeDir + "/")) {
return "~" + path.substr(homeDir.length);
}
return path;
}
function getCwdStr(remote: RemoteType, state: Record<string, string>): string {
if (state == null || isBlank(state.cwd)) {
return "~";
}
let cwd = state.cwd;
if (remote && remote.remotevars.home) {
cwd = replaceHomePath(cwd, remote.remotevars.home);
}
return cwd;
}
@mobxReact.observer
class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<string, string> }, {}> {
render() {
let rptr = this.props.rptr;
if (rptr == null || isBlank(rptr.remoteid)) {
return <span className={cn("term-prompt", "color-green")}>&nbsp;</span>;
}
let remote = GlobalModel.getRemote(this.props.rptr.remoteid);
let remoteStr = getRemoteStr(rptr);
let festate = this.props.festate ?? {};
let cwd = getCwdStr(remote, festate);
let isRoot = false;
if (remote && remote.remotevars) {
if (remote.remotevars["sudo"] || remote.remotevars["bestuser"] == "root") {
isRoot = true;
}
}
let remoteColorClass = isRoot ? "color-red" : "color-green";
if (remote && remote.remoteopts && remote.remoteopts.color) {
remoteColorClass = "color-" + remote.remoteopts.color;
}
let remoteTitle: string = null;
if (remote && remote.remotecanonicalname) {
remoteTitle = "connected to " + remote.remotecanonicalname;
}
let cwdElem = (
<span title="current directory" className="term-prompt-cwd">
<FolderIcon className="icon" />
{cwd}
</span>
);
let remoteElem = (
<span title={remoteTitle} className={cn("term-prompt-remote", remoteColorClass)}>
[{remoteStr}]{" "}
</span>
);
let rootIndicatorElem = <span className="term-prompt-end">{isRoot ? "#" : "$"}</span>;
let branchElem = null;
let pythonElem = null;
if (!isBlank(festate["PROMPTVAR_GITBRANCH"])) {
let branchName = festate["PROMPTVAR_GITBRANCH"];
branchElem = (
<span title="current git branch" className="term-prompt-branch">
<i className="fa-sharp fa-solid fa-code-branch" />
{branchName}{" "}
</span>
);
}
if (!isBlank(festate["VIRTUAL_ENV"])) {
let venvDir = festate["VIRTUAL_ENV"];
let venv = getShortVEnv(venvDir);
pythonElem = (
<span title="python venv" className="term-prompt-python">
<i className="fa-brands fa-python" />
{venv}{" "}
</span>
);
}
return (
<span className="term-prompt">
{remoteElem} {pythonElem}
{branchElem}
{cwdElem} {rootIndicatorElem}
</span>
);
}
}
export { Prompt };

194
src/terminal/terminal.less Normal file
View File

@ -0,0 +1,194 @@
@import "../index.less";
.term-prompt {
.icon {
margin: 0 4px 0 2px;
vertical-align: middle;
width: 1.2em;
height: 1.2em;
fill: @prompt-green;
}
.term-prompt-branch {
color: @term-white;
}
.term-prompt-python {
color: @term-bright-magenta;
}
.term-prompt-remote {
i {
margin-right: 0;
}
}
.term-prompt-remote {
color: @term-bright-green;
&.color-green {
color: @term-bright-green;
}
&.color-red {
color: @term-bright-red;
}
&.color-blue {
color: @term-bright-blue;
}
&.color-yellow {
color: @term-bright-yellow;
}
&.color-magenta {
color: @term-bright-magenta;
}
&.color-cyan {
color: @term-bright-cyan;
}
&.color-white {
color: @term-bright-white;
}
&.color-orange {
color: @tab-orange;
}
}
.term-prompt-cwd {
color: @term-bright-green;
}
.term-prompt-end {
color: @term-bright-green;
}
}
.terminal-wrapper {
position: relative;
margin-top: 1em;
.term-block {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: transparent;
z-index: 10;
}
.xterm-screen {
&::-webkit-scrollbar {
display: none;
}
}
&.focus .xterm {
.xterm-screen {
overflow-y: scroll;
overscroll-behavior: contain;
}
.xterm-viewport {
overscroll-behavior: contain;
}
}
&.focus .xterm-viewport {
&::-webkit-scrollbar {
background-color: #777;
width: 5px;
height: 5px;
}
&::-webkit-scrollbar-thumb {
background: white;
}
}
.xterm-viewport {
&::-webkit-scrollbar {
background-color: #222;
width: 5px;
height: 5px;
}
&::-webkit-scrollbar-thumb {
background: #555;
}
}
}
body .xterm .xterm-viewport {
overflow-y: auto;
width: calc(100% + 5px);
}
#main .term-prompt {
i {
margin-right: 3px;
}
.term-prompt-branch {
color: @term-white;
}
.term-prompt-python {
color: @term-bright-magenta;
}
.term-prompt-remote {
i {
margin-right: 0;
}
}
.term-prompt-remote {
color: @term-bright-green;
&.color-green {
color: @term-bright-green;
}
&.color-red {
color: @term-bright-red;
}
&.color-blue {
color: @term-bright-blue;
}
&.color-yellow {
color: @term-bright-yellow;
}
&.color-magenta {
color: @term-bright-magenta;
}
&.color-cyan {
color: @term-bright-cyan;
}
&.color-white {
color: @term-bright-white;
}
&.color-orange {
color: @tab-orange;
}
}
.term-prompt-cwd {
color: @term-bright-green;
}
.term-prompt-end {
color: @term-bright-green;
}
}