Enhance app with multi-CCN support and improved UX

Features:

 Add support for multiple CCNs
🔍 Implement sidebar hiding functionality
🎨 Revamp navigation system for better flow
🖌️ Replace icons with custom-designed assets and improved naming
🚀 Streamline initial setup process
📝 Refine terminology (e.g., "forum thread" → "topic")
🛠️ Enhance forum usability and interaction
This commit is contained in:
Danny Morabito 2025-04-11 22:26:00 +02:00
parent bf3c950da0
commit 9893945f55
Signed by: dannym
GPG key ID: 7CC8056A5A04557E
34 changed files with 792 additions and 308 deletions

View file

@ -1,5 +1,5 @@
import { fileURLToPath, URL } from "node:url";
import { defineConfig, externalizeDepsPlugin } from "electron-vite"; import { defineConfig, externalizeDepsPlugin } from "electron-vite";
import { fileURLToPath, URL } from "node:url";
import { resolve } from "path"; import { resolve } from "path";
export default defineConfig({ export default defineConfig({
@ -40,6 +40,7 @@ export default defineConfig({
"@components": fileURLToPath( "@components": fileURLToPath(
new URL("./src/components", import.meta.url) new URL("./src/components", import.meta.url)
), ),
"@assets": fileURLToPath(new URL("./src/assets", import.meta.url)),
"@": fileURLToPath(new URL("./src", import.meta.url)), "@": fileURLToPath(new URL("./src", import.meta.url)),
}, },
}, },

View file

@ -48,6 +48,7 @@
"@nostr/tools": "npm:@jsr/nostr__tools", "@nostr/tools": "npm:@jsr/nostr__tools",
"@open-wc/lit-helpers": "^0.7.0", "@open-wc/lit-helpers": "^0.7.0",
"@std/encoding": "npm:@jsr/std__encoding", "@std/encoding": "npm:@jsr/std__encoding",
"iconify-icon": "^2.3.0",
"lit": "^3.2.1", "lit": "^3.2.1",
"markdown-it": "^14.1.0" "markdown-it": "^14.1.0"
} }

64
src/arx-icons.ts Normal file
View file

@ -0,0 +1,64 @@
import arborIcon from '@assets/icons/arbor.svg?raw';
import calendarIcon from '@assets/icons/calendar.svg?raw';
import howlIcon from '@assets/icons/howl.svg?raw';
import lettersIcon from '@assets/icons/letters.svg?raw';
import marketIcon from '@assets/icons/market.svg?raw';
import oracleIcon from '@assets/icons/oracle.svg?raw';
import poolIcon from '@assets/icons/pool.svg?raw';
import settingsIcon from '@assets/icons/settings.svg?raw';
import walletIcon from '@assets/icons/wallet.svg?raw';
import { addIcon } from 'iconify-icon';
addIcon('arx:letters', {
width: 52,
height: 46,
body: lettersIcon,
});
addIcon('arx:howl', {
width: 48,
height: 55,
body: howlIcon,
});
addIcon('arx:calendar', {
width: 51,
height: 51,
body: calendarIcon,
});
addIcon('arx:arbor', {
width: 40,
height: 52,
body: arborIcon,
});
addIcon('arx:market', {
width: 48,
height: 44,
body: marketIcon,
});
addIcon('arx:wallet', {
width: 50,
height: 49,
body: walletIcon,
});
addIcon('arx:pool', {
width: 49,
height: 45,
body: poolIcon,
});
addIcon('arx:oracle', {
width: 49,
height: 44,
body: oracleIcon,
});
addIcon('arx:settings', {
width: 42,
height: 44,
body: settingsIcon,
});

View file

@ -0,0 +1,15 @@
<svg
width="40.039608"
height="52.527237"
viewBox="0 0 40.039608 52.527237"
version="1.1"
id="svg1"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1" /><g
id="layer1"
transform="translate(-222.26565,-123.96073)"><path
fill="currentColor"
d="m 240.7398,165.76285 v 7.9375 c -2.12881,0 -4.50865,-0.30249 -6.60948,0.0392 -0.82036,0.13342 -1.55334,0.84386 -1.01096,1.69836 0.68907,1.08562 3.09514,0.64369 4.18086,0.64369 3.97936,0 9.38418,0.96805 13.21365,-0.0841 1.23413,-0.33908 1.23405,-1.87398 0,-2.21304 -2.02512,-0.55641 -4.76467,-0.0841 -6.86365,-0.0841 v -7.9375 c 2.82486,-0.0583 5.74458,-0.39114 8.46667,-1.17144 1.70605,-0.48904 3.31189,-1.25448 4.7625,-2.27958 9.47829,-6.69797 4.6268,-18.00061 -0.15904,-25.91773 -2.96729,-4.90873 -8.2379,-12.43543 -14.65763,-12.43338 -6.07697,0.002 -10.76388,7.13175 -13.67667,11.63963 -5.06755,7.84264 -10.47978,20.15759 -0.61083,26.80988 1.55411,1.04756 3.23299,1.75205 5.02708,2.28469 2.52513,0.74967 5.31349,1.01377 7.9375,1.06793 m 2.91042,-2.64575 c 0,-1.79717 -0.53832,-4.58229 0.61327,-6.08154 1.33511,-1.73818 3.39249,-2.96434 4.93788,-4.51221 0.51867,-0.51951 1.33859,-1.26331 1.01689,-2.08664 -0.93033,-2.38103 -5.56187,2.97579 -6.56804,3.68456 v -10.84792 c 0,-1.22749 0.35101,-3.17567 -0.6439,-4.11349 -1.02349,-0.96473 -2.01369,0.15521 -2.20609,1.20329 -0.45803,2.49515 -0.0604,5.40062 -0.0604,7.93729 -0.83903,-0.58408 -1.61249,-1.25107 -2.38125,-1.92763 -0.75509,-0.66453 -1.62186,-1.90747 -2.6446,-2.1436 -1.15689,-0.26711 -1.66045,1.06455 -1.12264,1.90965 1.42696,2.24231 5.0207,3.3653 5.98885,5.87616 1.12987,2.93031 0.15964,7.95856 0.15964,11.10208 -2.26299,-0.006 -4.6865,-0.25665 -6.87916,-0.83499 -1.48053,-0.39051 -2.92423,-0.99456 -4.23334,-1.79145 -8.95699,-5.45228 -3.2979,-17.00133 0.97769,-23.56731 2.43511,-3.73956 6.61801,-10.29751 11.72231,-10.29751 4.94348,0 9.25377,6.67706 11.64086,10.29751 4.54708,6.89642 9.76862,17.84656 0.79456,23.62734 -1.28569,0.8282 -2.75253,1.42344 -4.23333,1.80021 -2.22805,0.56689 -4.58661,0.76594 -6.87917,0.7662"
id="path2" /></g></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,15 @@
<svg
width="51.278458"
height="51.425667"
viewBox="0 0 51.278458 51.425667"
version="1.1"
id="svg1"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1" /><g
id="layer1"
transform="translate(-444.0572,-101.62492)"><path
id="path122"
fill="currentColor"
d="m 454.70902,106.8615 c -3.19761,0 -6.56741,-0.47993 -8.94601,2.12727 -2.21765,2.43078 -1.63732,5.9542 -1.63732,8.98523 v 21.16666 c 0,5.06957 -0.99214,13.56232 6.35,13.75446 10.39108,0.27191 20.82578,0.004 31.22083,0.004 3.78326,0 8.99806,1.00047 11.81927,-2.14137 2.29833,-2.55954 1.67448,-6.33069 1.67448,-9.50029 v -21.96055 c 0,-2.95962 0.71315,-6.93903 -1.01955,-9.52479 -2.10019,-3.13417 -5.44385,-2.91062 -8.77003,-2.91062 0,-1.21181 0.17592,-2.61389 -0.53163,-3.67558 -2.72743,-4.092633 -5.81805,0.83525 -5.81837,3.67558 h -18.25625 c -0.0278,-1.33976 -0.16425,-2.88647 -1.13204,-3.9122 -2.81339,-2.981842 -5.83114,0.91466 -4.95338,3.9122 m 3.17502,-2.38122 0.26458,2.38125 h -1.05833 l 0.26458,-2.38125 h 0.52917 m 24.60625,0 0.26458,2.38125 h -1.05833 l 0.26458,-2.38125 h 0.52917 m -27.78125,5.02708 c 0,1.00729 -0.26764,2.50246 0.27193,3.39937 1.71689,2.85393 2.30559,-2.34217 2.10932,-3.39937 h 25.66458 c 0,4.31724 2.64584,4.35972 2.64584,0 1.44201,0 3.10649,-0.22424 4.49791,0.21273 3.45176,1.08403 2.64584,5.45298 2.64584,8.25394 h -45.77294 c 0,-2.65657 -0.80331,-6.63763 2.11768,-8.05061 1.66857,-0.80714 4.01509,-0.41606 5.81982,-0.41606 m 22.75417,11.11247 v 6.61458 l -2.64584,1.16327 -2.98677,-0.0168 0.59163,2.55765 -0.51527,2.64583 c -0.4492,0.0358 -0.88789,0.0677 -1.32925,0.16782 -4.10143,0.93035 1.4149,3.07357 1.87474,4.5957 0.35894,1.18815 -1.41492,2.96805 -0.44608,3.79644 0.85994,0.73527 1.90867,-0.38477 2.811,-0.4085 0.88052,-0.0232 1.80753,0.60298 2.64584,0.84437 -0.11557,1.46421 0.42271,6.7452 -0.68882,7.62555 -1.2819,1.01528 -5.13339,0.31195 -6.71952,0.31195 h -15.61041 c -2.11941,0 -4.86634,0.28968 -6.47372,-1.41294 -1.87881,-1.99013 -1.1992,-5.60953 -1.1992,-8.11206 v -20.37286 h 30.69167 m 4.7625,6.61458 -2.11667,-0.26458 c 0.003,-3.41657 1.72788,-2.62019 2.11667,0.26458 M 461.3236,124.4469 c -6.03811,1.08442 -10.08007,6.48864 -9.1808,12.57709 0.66984,4.53514 6.43336,9.82676 11.29645,8.84436 1.46845,-0.29664 2.16747,-1.62781 1.62691,-3.02352 -1.40184,-3.61944 -4.00982,-5.76914 -3.07661,-10.05417 0.47148,-2.16489 3.50194,-4.6106 3.3616,-6.61336 -0.13229,-1.88806 -2.66114,-1.9758 -4.02755,-1.7304 m 0.52917,2.52293 c -0.58096,1.38147 -1.53574,2.52775 -2.02255,3.96875 -1.53579,4.54613 0.0978,8.22269 2.28713,12.17083 -5.248,-0.24038 -8.91156,-5.92399 -7.01982,-10.84792 1.1151,-2.9024 3.84279,-4.61753 6.75524,-5.29166 m 26.72292,0.79375 c -0.55031,4.94109 -4.94361,0.27062 0,0 m -9.78959,1.67365 c 7.72196,-1.2965 9.3983,10.09438 1.5875,11.1576 -7.07471,0.96304 -8.75576,-9.95407 -1.5875,-11.1576 m 9.26042,6.52843 v -1.85208 c 3.23527,3.7e-4 3.17789,1.78438 0,1.85208 m 0.79375,6.35 c -3.82709,0.93226 -2.89125,-4.41063 -0.46078,-1.06894 0.24535,0.33733 0.32929,0.68221 0.46078,1.06894 m -6.61458,0.26458 c -0.0608,0.45117 -0.12538,0.88688 -0.23806,1.32925 -0.85928,3.37347 -3.44313,-0.55408 0.23806,-1.32925" /></g></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

15
src/assets/icons/howl.svg Normal file
View file

@ -0,0 +1,15 @@
<svg
width="48.373081"
height="55.578983"
viewBox="0 0 48.373081 55.578983"
version="1.1"
id="svg1"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1" /><g
id="layer1"
transform="translate(-373.7764,-99.170626)"><path
id="path12"
fill="currentColor"
d="m 391.20902,126.44066 c -1.65061,5.81649 -6.57957,9.99022 -7.40833,16.13958 h -0.26458 c -0.70233,-2.574 -1.05834,-4.72503 -1.05834,-7.40833 0,-0.91748 0.30246,-2.56821 -0.42423,-3.27892 -1.98111,-1.93753 -6.02782,3.49441 -6.77444,4.86642 -2.6164,4.80797 -1.82557,11.02536 2.44659,14.69376 5.38053,4.62011 13.77321,3.03332 20.3625,3.03332 1.99816,0 5.59497,0.68633 7.3479,-0.38544 3.2062,-1.96033 -1.00132,-5.98592 -3.11457,-6.49372 1.37462,-3.10568 0.61187,-6.23649 -2.11687,-8.36254 -1.18526,-0.92348 -4.20673,-2.22995 -5.33474,-0.5598 -0.3831,0.56723 -0.24599,1.31264 0.38258,1.61996 2.17754,1.06461 5.34675,1.12394 5.24145,4.54426 -0.0404,1.31071 -1.99403,3.02525 -1.12611,4.2319 0.95602,1.32913 3.15171,1.20121 4.27661,2.75955 h -8.20208 c -2.22213,0 -6.1103,0.75614 -8.14166,-0.15965 -1.68246,-0.7585 -1.15186,-5.21443 -1.11488,-6.71952 0.1643,-6.68824 5.64564,-11.79048 7.48468,-17.97716 0.34158,-1.14911 2.3585,-1.5222 2.04419,-2.89716 -0.22297,-0.97539 -1.33211,-1.05518 -2.12442,-0.88092 -3.64794,0.80236 -6.27697,0.74986 -9.26041,-1.79267 2.5149,-0.6029 5.67696,-2.47893 7.30503,-4.50179 1.11193,-1.38155 1.60271,-2.96203 3.01473,-4.14248 1.92884,-1.61252 4.81542,-1.83106 6.56478,-3.56188 1.23981,-1.22669 2.30228,-2.94073 3.14131,-4.4626 0.34074,-0.61805 0.70504,-1.81659 1.45113,-2.06195 1.74285,-0.57317 2.84227,4.66962 3.0131,5.76612 0.30589,1.9634 -1.3697,7.26806 2.49742,5.72773 0.80851,-0.32204 1.54116,-0.72323 2.38125,-0.96523 -0.30214,3.01128 -2.85342,5.20777 -3.35343,8.20208 -0.59063,3.53693 1.57536,6.1743 1.98172,9.525 0.6255,5.15776 -1.94628,9.08762 0.39585,14.2875 0.60995,1.35414 1.32317,3.2015 2.58806,4.07368 1.57915,1.08889 2.69637,0.73071 3.94405,2.5409 -1.7015,0 -3.94675,0.38139 -5.55605,-0.2364 -2.68845,-1.03206 -4.06019,-5.47157 -4.89091,-7.96568 -0.5215,-1.56574 -0.79029,-6.42303 -2.53171,-6.96859 -2.77029,-0.86787 -0.8155,5.15342 -0.5457,6.17484 1.5426,5.83998 3.90584,11.64166 10.61395,11.64166 1.30735,0 4.73467,0.61552 5.3966,-0.85418 1.84801,-4.10311 -3.05996,-5.32934 -5.04505,-7.1664 -1.15578,-1.06959 -1.74698,-2.94667 -2.19322,-4.41483 -1.17665,-3.87125 0.89951,-7.67037 0.47282,-11.64167 -0.29835,-2.77677 -2.45166,-5.37784 -1.98458,-8.20208 0.60756,-3.67366 5.17031,-7.58113 2.7769,-11.60247 -0.98085,-1.648 -3.00281,-0.0197 -4.18597,0.48997 0,-3.34987 -0.55609,-7.48707 -2.93819,-10.05008 -0.78501,-0.84462 -2.126,-2.105701 -3.41079,-1.837387 -2.15564,0.450182 -3.75492,4.948827 -4.99462,6.595587 -1.81798,2.41493 -4.54008,2.71975 -6.91265,4.44138 -1.8192,1.32009 -2.4261,3.16301 -3.80747,4.79537 -1.40352,1.65855 -3.7454,2.91133 -5.71753,3.76173 -1.02516,0.44206 -2.60665,0.75598 -2.79752,2.08707 -0.16634,1.16002 0.98399,2.17763 1.7402,2.88162 2.46269,2.29261 5.25211,2.67054 8.46565,2.67054 m -11.37708,8.99583 c 0,6.07723 3.14634,10.37028 4.23333,16.13959 -7.70219,-0.67671 -10.2839,-11.67289 -4.23333,-16.13959 m 20.90229,-22.21639 c -2.12454,1.03159 -2.17034,4.79076 0.51753,3.38344 1.75412,-0.91844 1.98561,-4.59887 -0.51753,-3.38344" /></g></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1,15 @@
<svg
width="52.115841"
height="46.76223"
viewBox="0 0 52.115841 46.76223"
version="1.1"
id="svg1"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1" /><g
id="layer1"
transform="translate(-358.17974,-252.02887)"><path
fill="currentColor"
d="m 397.28739,264.99344 c -1.40599,-10.88693 -10.92028,-12.96458 -20.10834,-12.96458 -4.77787,0 -9.23968,0.12302 -13.22916,3.14847 -5.98978,4.54239 -7.06816,14.53774 -4.37808,21.19319 0.87644,2.16838 3.01313,3.76017 3.79277,5.82083 0.76549,2.02328 -1.67974,4.4397 -0.38891,6.55803 1.98627,3.25961 7.47102,-1.61671 9.44088,-2.72993 1.00571,-0.56836 2.59306,-1.07839 3.48777,-0.0398 1.8852,2.18832 2.50599,4.7496 4.9789,6.65502 5.0549,3.89489 10.2043,1.33597 15.61042,2.95757 2.09565,0.62862 5.95608,4.40808 8.16288,2.80671 1.94012,-1.40786 0.0334,-4.3627 0.8403,-6.1534 1.1982,-2.65904 3.37225,-4.44576 4.18413,-7.40833 2.39052,-8.72298 -2.28592,-19.63397 -12.39356,-19.84375 m -32.01457,21.96041 c 0.21828,-1.62096 1.3393,-3.67673 0.93972,-5.29166 -0.47287,-1.91116 -2.7399,-3.24108 -3.63577,-5.02709 -2.33028,-4.64562 -2.1349,-11.31022 0.25785,-15.87499 3.45112,-6.5839 11.61756,-6.5495 18.04862,-6.10909 3.85358,0.2639 7.3102,0.74311 10.30813,3.51206 5.70409,5.26841 4.98496,17.72041 -1.3123,22.23213 -5.28123,3.78378 -10.67902,1.36889 -16.40416,2.44903 -3.06714,0.57866 -5.22896,3.25149 -8.20209,4.10961 m 37.57082,8.73125 c -2.04656,-0.85206 -4.14391,-2.51759 -6.35,-2.85407 -7.08037,-1.07991 -14.21872,1.18216 -17.72709,-7.20009 3.35658,0 6.64649,-0.11779 9.78959,-1.44581 6.67288,-2.81944 8.99583,-9.86083 8.99583,-16.54585 1.75384,0.362 3.49724,0.80936 5.02708,1.77552 5.25432,3.3183 6.33047,11.09689 3.88607,16.48072 -0.86529,1.90581 -3.1352,3.40125 -3.70274,5.29167 -0.37206,1.23929 0.0813,3.17596 0.0813,4.49791"
id="path115" /></g></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,15 @@
<svg
width="48.970749"
height="44.663296"
viewBox="0 0 48.970749 44.663296"
version="1.1"
id="svg1"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1" /><g
id="layer1"
transform="translate(25.898366,-196.75692)"><path
fill="currentColor"
d="m -21.117011,197.13272 c -3.45885,0.67086 -3.807992,6.52957 -4.525681,9.40107 -0.902247,3.60992 0.844618,5.38243 1.213694,8.73125 0.58923,5.34638 0.07305,11.3194 -0.106159,16.71387 -0.07531,2.26682 -0.608849,5.71499 0.886026,7.61617 2.102019,2.67332 7.457615,1.59913 10.469619,1.59913 H 7.7225714 c 3.2456176,0 7.1526626,0.59189 10.3187486,-0.14005 4.375573,-1.01153 3.175,-6.53031 3.175,-9.91412 0,-5.16609 -0.609507,-10.75231 0.03512,-15.875 0.201086,-1.59798 1.391367,-2.89721 1.69325,-4.49792 0.558615,-2.96199 -0.854219,-6.16306 -1.562594,-8.99583 -0.299759,-1.19873 -0.470473,-2.69748 -1.334959,-3.64393 -1.975126,-2.16242 -7.549519,-1.11857 -10.2078956,-1.11857 H -10.798262 c -3.301582,0 -7.074319,-0.50534 -10.318749,0.12393 m 11.112499,2.52191 c -2.91e-4,2.63414 -0.290882,5.32196 -0.604703,7.9375 -0.247568,2.06334 -0.542584,3.97835 -2.068896,5.53154 -2.767129,2.81588 -8.186093,2.62792 -10.180536,-1.03363 -1.311467,-2.4077 -0.255178,-5.22629 0.388096,-7.67291 0.295443,-1.12368 0.533001,-3.77403 1.502777,-4.49057 0.728285,-0.53813 2.173364,-0.27193 3.025763,-0.27193 h 7.937499 m 14.2874994,0 c 0.165571,3.85616 1.912711,8.5829 -0.140862,12.17083 -2.656019,4.64049 -9.80335,4.18364 -11.827653,-0.79375 -1.39569,-3.43174 0.316966,-7.79636 0.326849,-11.37708 h 11.641666 m 2.645834,0 H 14.86632 c 0.87204,0 2.285471,-0.25816 3.040056,0.27193 1.044247,0.73359 1.203358,3.07405 1.491341,4.22598 0.605793,2.42317 1.707026,5.26187 0.495686,7.67272 -2.274384,4.52654 -8.827816,4.00979 -11.2503056,2e-4 -0.865515,-1.43257 -0.862148,-2.89417 -1.072827,-4.49792 -0.327119,-2.49012 -0.620658,-5.16136 -0.641449,-7.67291 m -16.404169,13.49372 c 4.260269,5.23092 11.640007,6.34338 15.610416,0 h 0.529166 c 2.837,4.90474 7.4612446,4.7251 12.1708326,3.43959 v 15.875 c 0,1.46606 0.585478,4.59285 -0.6439,5.66118 -1.120709,0.97391 -4.041638,0.42423 -5.441516,0.42423 H -2.0670116 c 0.565368,-3.14441 0.529441,-7.4078 0.112283,-10.58333 -0.138693,-1.05578 0.02283,-2.23507 -0.573263,-3.17091 -1.848815,-2.90259 -11.4023664,-2.98201 -13.4259684,-0.25724 -0.92976,1.25193 -0.665134,3.01952 -0.665134,4.48648 v 9.525 c -1.247905,0 -3.874985,0.49472 -4.830688,-0.46098 -1.048153,-1.04815 -0.460979,-4.24842 -0.460979,-5.62443 v -15.875 c 5.667189,1.54629 8.233166,0.67686 12.4354164,-3.43959 m 14.034551,10.25053 c -1.985647,0.80916 -1.959909,7.20925 -0.873574,8.77066 1.154754,1.65973 6.5150086,1.58136 8.2661896,0.8907 2.147063,-0.84675 2.401343,-7.32205 1.130604,-9.00358 -1.105214,-1.46251 -6.9241566,-1.30942 -8.5232196,-0.65778 m -9.007468,15.14949 h -9.7895824 v -8.99583 l 0.311945,-3.54452 4.4505544,-0.42423 4.602852,0.42423 0.424231,3.27994 v 9.26041 m 15.3458316,-12.96458 0.264584,5.02708 H 5.6059044 v -5.02708 H 10.89757"
id="path10" /></g></svg>

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1,18 @@
<svg
width="49.721642"
height="44.373489"
viewBox="0 0 49.721642 44.373489"
version="1.1"
id="svg1"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1" /><g
id="layer1"
transform="translate(-444.72745,-194.40505)"><path
fill="currentColor"
d="m 491.75067,225.13027 -3.96875,2.38125 c -0.22685,-2.9761 -5.58574,-3.14716 -7.9375,-3.47408 -6.07972,-0.8452 -12.68821,-0.87866 -18.78542,-0.15454 -1.5143,0.17983 -4.89981,0.0374 -5.92168,1.33045 -0.78418,0.99227 0.10538,1.97416 1.16327,1.99438 2.74686,0.0525 5.99388,-0.50795 8.72716,-0.86683 2.60533,-0.34208 5.31206,-0.15246 7.9375,-0.1523 4.07722,2.6e-4 7.93593,0.76801 11.90625,1.5875 v 0.26458 c -11.98028,2.47148 -26.22182,3.49475 -37.30625,-2.64583 3.44524,-2.92695 8.40903,-3.12235 12.7,-3.62862 1.00808,-0.11895 4.0722,0.25753 3.51573,-1.65161 -0.53347,-1.83022 -4.03519,-0.97906 -5.36781,-0.85011 -3.66057,0.35424 -7.59129,0.78401 -10.84792,2.65891 -1.2606,0.72575 -2.81276,1.8729 -2.83671,3.47143 -0.0943,6.29245 6.15471,10.34595 11.56796,11.73966 8.49806,2.18791 18.26142,2.2538 26.72292,-0.17496 3.97842,-1.14195 7.83997,-2.90127 10.13072,-6.53762 4.91681,-7.80494 -5.29097,-10.50363 -11.18906,-11.06757 -1.47074,-0.14062 -5.8316,-1.16661 -6.43941,0.76312 -0.57251,1.81765 2.24448,1.59158 3.26441,1.72551 4.816,0.63234 8.47059,1.42518 12.96459,3.28728 m -43.12709,3.70416 c 3.73629,2.17255 8.46641,2.76565 12.7,3.2189 10.32641,1.10555 19.73081,-0.12344 29.63334,-2.95431 -2.12089,3.00253 -5.80719,4.50058 -9.26042,5.38966 -7.30844,1.88163 -15.09278,1.92904 -22.48958,0.53407 -2.97728,-0.56149 -6.0578,-1.51909 -8.46667,-3.41489 -0.85772,-0.67502 -2.141,-1.62332 -2.11667,-2.77343"
id="path129" /><path
fill="currentColor"
d="m 467.67358,194.55187 c -5.6648,0.81116 -10.88544,3.57401 -15.34583,7.0862 -1.35571,1.0675 -4.01038,2.80903 -4.14413,4.70675 -0.14477,2.05424 3.00037,4.12525 4.40871,5.23592 4.921,3.8809 11.94882,8.02768 18.52084,7.0862 5.88618,-0.84325 11.30586,-3.66932 15.875,-7.40732 1.37344,-1.1236 3.90056,-2.69592 4.07899,-4.65021 0.19818,-2.17061 -3.12808,-4.19348 -4.60816,-5.32229 -5.07524,-3.87072 -12.15861,-7.68414 -18.78542,-6.73525 m 6.61459,3.32632 c 5.27311,1.71872 10.01662,4.63232 14.02291,8.46666 v 0.26459 c -4.92688,4.53757 -11.53209,9.29021 -18.52083,9.51356 -6.96364,0.22257 -14.11713,-4.85812 -18.78542,-9.51356 v -0.26459 c 3.48114,-3.40932 8.55121,-7.351 13.49375,-8.20208 -2.0146,2.87121 -4.06992,4.94266 -3.65824,8.73125 1.05633,9.72119 15.90908,10.10578 17.10319,0.26458 0.2598,-2.14121 -0.0999,-4.27577 -1.30187,-6.08541 -0.70629,-1.06334 -2.05345,-1.93721 -2.35349,-3.175 m -6.08542,1.93906 c 7.71615,-1.35602 9.98382,10.50121 2.38125,11.95993 -7.57672,1.45375 -9.97408,-10.62561 -2.38125,-11.95993"
id="path128" /></g></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

15
src/assets/icons/pool.svg Normal file
View file

@ -0,0 +1,15 @@
<svg
width="49.911552"
height="45.77253"
viewBox="0 0 49.911552 45.77253"
version="1.1"
id="svg1"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1" /><g
id="layer1"
transform="translate(-485.34034,-171.20574)"><path
id="path159"
fill="currentColor"
d="m 510.05511,178.81945 c -1.57359,-3.32099 -3.43658,-6.0272 -7.14375,-7.13251 -1.11516,-0.33251 -4.92197,-1.29014 -4.60856,1.01954 0.1702,1.25432 2.09877,1.18853 3.02106,1.41376 1.96804,0.48062 3.66152,1.67255 4.77148,3.37629 3.0845,4.73458 2.63686,12.30605 2.63686,17.72708 0,1.4274 -0.73794,6.02034 0.61327,6.86774 2.51315,1.5761 2.03256,-3.70262 2.03256,-4.75107 0,-5.95317 -0.8977,-14.27484 2.31796,-19.57917 1.72918,-2.85234 3.96526,-2.76914 6.6491,-3.94036 0.71881,-0.3137 1.17005,-1.06666 0.76352,-1.82023 -0.71508,-1.32557 -2.79882,-0.65876 -3.90975,-0.34504 -3.71421,1.04893 -5.66234,3.83846 -7.14375,7.16397 m 17.21507,-3.92221 c -0.99661,0.13061 -1.18065,1.65229 -1.71837,2.30693 -0.83745,1.01958 -4.12299,2.14557 -1.96375,3.71337 0.66871,0.48554 1.41572,0.70732 1.96375,1.37007 0.50444,0.61004 0.71417,2.44829 1.70776,2.41841 1.04042,-0.0313 1.24734,-1.76087 1.75346,-2.41921 0.8246,-1.07264 4.69432,-1.98951 2.17976,-3.59597 -0.62197,-0.39736 -1.24973,-0.6658 -1.76287,-1.22289 -0.61726,-0.67012 -0.99006,-2.724 -2.15974,-2.57071 m -33.2487,6.61458 -1.42744,2.57152 -2.63155,1.7247 -0.83027,1.16551 2.39063,1.55667 1.93599,2.94595 1.1447,0.48589 1.38049,-2.29061 2.4137,-1.86659 0.83028,-1.04872 -2.61009,-1.99868 -1.4593,-2.72259 -1.13714,-0.52305 m 8.09613,13.29488 c -6.3301,0.83154 -18.56886,1.09803 -16.55646,10.20647 0.26528,1.20067 0.59588,2.33664 1.14734,3.43958 0.72856,1.45711 1.66268,2.77971 2.97473,3.76745 2.77796,2.09133 6.16528,3.04505 9.52398,3.69295 8.31578,1.6041 18.6872,1.56689 26.72291,-1.29353 4.81129,-1.71265 11.53362,-8.26771 8.61039,-14.10437 -2.3416,-4.67538 -11.23232,-5.15563 -15.75414,-5.57605 -1.28286,-0.11929 -4.09721,-0.87491 -5.131,0.17945 -1.84779,1.88454 2.08062,2.30005 3.01434,2.41064 4.05841,0.48069 8.3526,0.63299 12.17083,2.26837 0.96704,0.41418 3.4983,1.39203 2.93125,2.83038 -0.98846,2.50724 -6.19528,3.40554 -8.4875,3.86688 -8.51983,1.71469 -17.67578,1.70394 -26.19375,0 -2.49523,-0.49916 -5.26496,-1.13312 -7.40812,-2.56786 -0.85323,-0.57119 -2.04453,-1.48615 -1.22351,-2.56906 2.26966,-2.99359 9.42041,-3.47696 12.86496,-3.72969 1.24799,-0.0916 3.62291,0.14476 4.6784,-0.55999 0.70002,-0.4674 0.82631,-1.80742 0,-2.21426 -0.98988,-0.48737 -2.82848,-0.1861 -3.88465,-0.0474 m -12.7,12.05853 c 4.04223,2.15276 8.98457,2.83523 13.49375,3.19767 9.99933,0.80373 18.7626,-0.4682 28.31042,-3.19767 -1.59402,3.62953 -5.12554,5.02355 -8.73125,5.91963 -7.23833,1.79889 -15.43116,1.70377 -22.75417,0.4955 -2.77592,-0.45802 -10.89255,-2.45631 -10.31875,-6.41513 m 29.63334,-4.49788 -9.26042,0.786 -8.73125,-0.786 c -0.002,-0.60492 0.16226,-1.68559 -0.42423,-2.08563 -1.15689,-0.78913 -4.90382,0.46519 -4.74412,2.08563 0.21736,2.20568 4.57866,2.52966 6.22668,2.79447 5.28009,0.84841 10.87014,0.90351 16.13959,-0.0466 1.6202,-0.29211 4.2504,-0.44755 5.40374,-1.76307 1.8744,-2.13796 -4.92551,-5.6829 -4.60999,-0.98484" /></g></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,15 @@
<svg
width="42.581379"
height="44.50452"
viewBox="0 0 42.581379 44.50452"
version="1.1"
id="svg1"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1" /><g
id="layer1"
transform="translate(-513.56562,-191.36311)"><path
fill="currentColor"
d="m 532.49652,191.64146 c -4.39731,0.63872 -2.56173,4.78288 -5.56034,6.77403 -2.95736,1.96376 -6.07138,-1.80106 -8.96704,0.62532 -2.11951,1.77602 -5.29879,6.30373 -3.8726,9.1561 0.92264,1.84528 3.27214,2.7776 3.30975,5.02708 0.0453,2.71082 -2.46408,3.38655 -3.49184,5.55625 -1.33779,2.82418 1.47505,6.68912 3.32321,8.70347 2.92261,3.18543 6.27113,-0.65416 9.43394,1.28067 2.11104,1.29141 1.45151,3.5743 2.78568,5.35006 1.6224,2.1594 5.66911,1.84637 8.06632,1.52236 4.52395,-0.61143 2.75201,-5.10792 5.82492,-6.97348 2.81455,-1.70871 5.88816,1.56386 8.71655,-0.72699 2.33704,-1.89287 5.23956,-6.44838 3.60434,-9.41966 -1.04248,-1.89421 -3.32097,-2.59314 -3.32097,-5.0281 0,-2.42603 2.32857,-3.3277 3.31055,-5.29166 1.41947,-2.83893 -1.32225,-7.02785 -3.36753,-8.90581 -2.89674,-2.65977 -5.74302,1.04072 -8.42807,-0.5608 -2.4985,-1.49027 -1.75431,-3.83447 -3.33649,-5.85045 -1.56372,-1.99246 -5.79257,-1.56344 -8.03038,-1.23839 m 0.53323,2.62358 c 1.26588,-0.26796 4.10261,-0.47757 5.15264,0.40055 1.01087,0.84538 0.88937,2.61199 1.406,3.74072 0.64467,1.40842 2.18735,2.31554 3.49144,3.05209 2.55735,1.44442 5.74141,-1.99739 7.73641,0.18435 1.00156,1.0953 2.8298,3.382 2.49333,4.96666 -0.3292,1.55039 -2.09313,2.41535 -2.88959,3.70416 -0.79737,1.29027 -0.6337,3.82596 -0.42281,5.29167 0.31074,2.15969 3.53793,2.95804 3.51553,5.03096 -0.0161,1.49228 -1.81055,3.95999 -2.84611,4.96174 -1.57495,1.52354 -3.45152,-0.16397 -5.20551,-0.37094 -1.48638,-0.17537 -3.04285,0.75743 -4.22925,1.55525 -2.05375,1.3811 -1.64232,5.78247 -3.97773,6.22506 -1.3731,0.26022 -4.37865,0.46774 -5.43683,-0.58348 -0.90976,-0.90377 -0.77784,-2.47932 -1.32332,-3.58923 -0.66636,-1.35586 -2.00913,-2.19111 -3.28912,-2.89999 -2.63062,-1.45688 -6.41482,1.51188 -8.26558,-0.57898 -0.9788,-1.1058 -3.26811,-3.92646 -2.52354,-5.49338 0.98865,-2.08056 3.48183,-2.68094 3.63394,-5.31534 0.087,-1.50666 0.15038,-3.19558 -0.74374,-4.4875 -0.81381,-1.17589 -2.58529,-1.9438 -2.85039,-3.45 -0.17283,-0.98192 0.54289,-1.88054 1.04813,-2.64584 0.65538,-0.99273 1.36908,-2.54787 2.56804,-2.96798 1.77899,-0.62334 3.70635,1.24635 5.54564,1.04874 1.23405,-0.1326 2.45479,-1.05871 3.43857,-1.75879 2.01717,-1.4355 1.62963,-5.52428 3.97385,-6.0205 m 1.58341,11.17888 c -11.03424,0.44841 -9.60319,16.76802 1.32292,16.23614 10.25302,-0.4991 8.86751,-16.65024 -1.32292,-16.23614 m -0.79375,2.84019 c 6.86754,-1.22632 9.37097,9.42319 2.38125,10.67357 -6.97499,1.24773 -9.38056,-9.42375 -2.38125,-10.67357"
id="path126" /></g></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1,15 @@
<svg
width="50.380646"
height="49.466183"
viewBox="0 0 50.380646 49.466183"
version="1.1"
id="svg1"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1" /><g
id="layer1"
transform="translate(-376.19943,-188.11239)"><path
fill="currentColor"
d="m 422.95902,228.57028 c 4.14241,-0.83637 4.12926,-10.03343 2.91185,-13.22916 -0.52062,-1.36665 -1.50643,-2.31078 -2.91185,-2.64584 0,-3.09737 0.0819,-6.54769 -3.43958,-7.67291 4.46137,-10.3694 -7.14017,-20.11134 -16.93333,-15.90869 -2.23451,0.95892 -4.08497,2.48283 -5.4113,4.5316 -0.72822,1.12489 -1.11596,3.49858 -2.5407,3.88465 -5.05834,1.37065 -15.21975,-2.97233 -17.72442,3.52369 -1.14751,2.97611 -0.51733,6.9166 -0.51733,10.05416 0,6.47222 -0.44959,13.12572 0.0239,19.57917 0.18713,2.55057 1.61147,4.91294 3.94487,6.04866 2.57935,1.25545 6.45127,0.56592 9.26041,0.56592 h 20.90209 c 2.71375,0 7.40537,0.89471 9.78938,-0.53162 2.99788,-1.79356 2.64603,-5.18964 2.64603,-8.19963 M 411.58196,204.4932 c 0.45959,-1.73781 0.54573,-2.70426 -0.52917,-4.23333 v -0.26459 c 1.0056,-1.8109 0.19175,-3.05081 -1.32291,-4.23333 -0.0447,-1.55397 -1.20998,-1.28905 -1.5875,0 h -0.79375 c -0.24581,-1.53679 -1.34169,-1.53679 -1.5875,0 -2.55964,0.52269 -1.11284,6.36621 -2.11667,8.73125 -1.27255,0 -3.81969,0.48852 -4.89132,-0.27193 -1.19179,-0.84572 -0.99866,-3.49263 -0.90563,-4.75515 0.37831,-5.13388 5.07913,-8.76536 10.03028,-8.73003 4.99877,0.0357 9.54681,3.86879 9.77917,8.99461 0.0553,1.22021 0.22947,3.67552 -0.9191,4.49057 -1.11502,0.79125 -3.82869,0.27193 -5.1559,0.27193 m -5.29171,-4.76293 v -1.85208 c 3.82488,0 3.78187,1.85166 0,1.85208 m -11.1125,0.52917 0.52917,4.23333 c -4.23316,0 -8.46689,0.0205 -12.7,-1.9e-4 -1.39253,-0.007 -3.58331,-0.22971 -3.58331,-2.11648 0,-1.93423 2.16781,-2.11431 3.58331,-2.11666 h 12.17083 m 11.1125,3.175 v -1.85209 c 4.29192,0 4.29192,1.85209 0,1.85209 m 14.02292,10.84791 c -3.5483,0 -7.75573,-0.72841 -10.8469,1.35069 -4.60095,3.09455 -4.4004,10.37749 0.79273,12.79023 2.90295,1.3487 6.93221,0.67575 10.05417,0.67575 0,1.914 0.46958,4.38936 -1.59159,5.40477 -2.31284,1.13937 -6.19823,0.41606 -8.72716,0.41606 h -19.84375 c -2.76357,0 -6.89675,0.77783 -9.24898,-0.96544 -2.41027,-1.7863 -1.86352,-5.14562 -1.86352,-7.76581 v -19.84375 c 3.92504,1.04929 7.85467,0.79375 11.90625,0.79375 h 17.19792 7.67291 c 1.07294,0 2.46288,-0.21416 3.38303,0.46099 1.78508,1.30974 1.11489,4.76059 1.11489,6.68276 m 3.175,2.64584 c 0,2.24401 0.75836,6.18376 -0.31195,8.19045 -0.75367,1.41303 -2.2735,1.33401 -3.6568,1.33455 -2.99197,0.001 -7.22064,0.82449 -9.46845,-1.61241 -2.3274,-2.5232 -0.8511,-6.74842 2.3247,-7.66414 1.19515,-0.34462 2.47492,-0.24845 3.70417,-0.24845 h 7.40833 m -10.57925,2.80793 c -2.62779,0.64098 -1.68539,4.89676 1.05302,4.25048 2.77349,-0.65454 1.67838,-4.91675 -1.05302,-4.25048"
id="path125" /></g></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -60,12 +60,12 @@ export class ArborForumCategory extends LitElement {
<div class="category-header"> <div class="category-header">
<span>${this.title}</span> <span>${this.title}</span>
<arx-button <arx-button
label="New Topic" label="New Thread"
href="/arbor/new-topic/${this.id}" href="/arbor/new-thread/${this.id}"
></arx-button> ></arx-button>
</div> </div>
<slot> <slot>
<div class="empty-state">No topics yet...</div> <div class="empty-state">No threads yet...</div>
</slot> </slot>
</div> </div>
`; `;

View file

@ -12,7 +12,7 @@ import '@components/MarkdownContent';
@customElement('arx-forum-post') @customElement('arx-forum-post')
export class ForumPost extends LitElement { export class ForumPost extends LitElement {
@property({ type: String }) override id = ''; @property({ type: String }) override id = '';
@property({ type: String }) topicId = ''; @property({ type: String }) threadId = '';
@property({ type: String }) npub = ''; @property({ type: String }) npub = '';
@property({ type: Date }) date = new Date(); @property({ type: Date }) date = new Date();
@property({ type: String }) content = ''; @property({ type: String }) content = '';
@ -217,12 +217,12 @@ export class ForumPost extends LitElement {
} }
private _copyPermalink() { private _copyPermalink() {
const permalink = `eve://phora/topics/${this.topicId}#post-${this.id}`; const permalink = `eve://phora/threads/${this.threadId}#post-${this.id}`;
navigator.clipboard.writeText(permalink); navigator.clipboard.writeText(permalink);
} }
override render() { override render() {
const permalink = `eve://phora/topics/${this.topicId}#post-${this.id}`; const permalink = `eve://phora/threads/${this.threadId}#post-${this.id}`;
const postClasses = { const postClasses = {
post: true, post: true,

View file

@ -1,14 +1,14 @@
import { LitElement, css, html } from 'lit'; import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js'; import { customElement, property } from 'lit/decorators.js';
import '@components/EveLink';
import formatDateTime from '@/utils/formatDateTime'; import formatDateTime from '@/utils/formatDateTime';
import '@components/EveLink';
@customElement('arx-arbor-forum-topic') @customElement('arx-arbor-forum-thread')
export class ArborForumTopic extends LitElement { export class ArborForumThread extends LitElement {
static override styles = [ static override styles = [
css` css`
.topic { .thread {
display: grid; display: grid;
grid-template-columns: 3fr 1fr; grid-template-columns: 3fr 1fr;
padding: 1.75rem; padding: 1.75rem;
@ -25,7 +25,7 @@ export class ArborForumTopic extends LitElement {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
} }
.topic:hover { .thread:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: calc(var(--depth) * 4px) calc(var(--depth) * 4px) box-shadow: calc(var(--depth) * 4px) calc(var(--depth) * 4px)
calc(var(--depth) * 8px) calc(var(--depth) * 8px)
@ -35,7 +35,7 @@ export class ArborForumTopic extends LitElement {
oklch(from var(--color-base-100) l c h / 0.5); oklch(from var(--color-base-100) l c h / 0.5);
} }
.topic-icon { .thread-icon {
inline-size: 40px; inline-size: 40px;
block-size: 40px; block-size: 40px;
background: var(--color-accent); background: var(--color-accent);
@ -48,7 +48,7 @@ export class ArborForumTopic extends LitElement {
oklch(from var(--color-base-content) l c h / 0.15); oklch(from var(--color-base-content) l c h / 0.15);
} }
.topic-icon::after { .thread-icon::after {
content: ""; content: "";
position: absolute; position: absolute;
inset: 0; inset: 0;
@ -59,13 +59,13 @@ export class ArborForumTopic extends LitElement {
); );
} }
.topic-info { .thread-info {
display: flex; display: flex;
gap: 1.5rem; gap: 1.5rem;
align-items: flex-start; align-items: flex-start;
} }
.topic-details { .thread-details {
flex: 1; flex: 1;
} }
@ -85,7 +85,7 @@ export class ArborForumTopic extends LitElement {
transform: translateX(4px); transform: translateX(4px);
} }
.topic-details p { .thread-details p {
margin: 0; margin: 0;
font-size: 0.975rem; font-size: 0.975rem;
line-height: 1.6; line-height: 1.6;
@ -156,7 +156,7 @@ export class ArborForumTopic extends LitElement {
} }
@media (max-width: 968px) { @media (max-width: 968px) {
.topic { .thread {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 1.5rem; gap: 1.5rem;
padding: 1.25rem; padding: 1.25rem;
@ -188,13 +188,13 @@ export class ArborForumTopic extends LitElement {
override render() { override render() {
return html` return html`
<div class="topic"> <div class="thread">
<div class="topic-info"> <div class="thread-info">
<div class="topic-icon"></div> <div class="thread-icon"></div>
<div class="topic-details"> <div class="thread-details">
<arx-eve-link <arx-eve-link
class="${this.status}" class="${this.status}"
href="/arbor/topics/${this.id}" href="/arbor/threads/${this.id}"
> >
${this.title} ${this.title}
</arx-eve-link> </arx-eve-link>

View file

@ -116,9 +116,12 @@ export class EveDialog extends LitElement {
override connectedCallback() { override connectedCallback() {
super.connectedCallback(); super.connectedCallback();
document.addEventListener('keydown', this._handleKeyDown); document.addEventListener('keydown', this._handleKeyDown);
this.rootWindow = document.body const router = document.querySelector('arx-eve-router');
.querySelector('arx-eve-router')! if (router) {
.shadowRoot!.querySelector('.window') as HTMLDivElement; this.rootWindow = router.shadowRoot!.querySelector('.window') as HTMLDivElement;
} else {
this.rootWindow = document.body as HTMLDivElement;
}
} }
override disconnectedCallback() { override disconnectedCallback() {
@ -168,7 +171,7 @@ export class EveDialog extends LitElement {
override render() { override render() {
return html` return html`
<div <div
class="${classMap({ active: this.open })}" class=${classMap({ active: this.open })}
@click=${this._handleOverlayClick} @click=${this._handleOverlayClick}
style="--dialog-width: ${this.width}; --dialog-max-width: ${this.maxWidth};" style="--dialog-width: ${this.width}; --dialog-max-width: ${this.maxWidth};"
> >

View file

@ -11,7 +11,7 @@ export class StyledSelect<T> extends LitElement {
@property() name = ''; @property() name = '';
@property({ type: Boolean }) required = false; @property({ type: Boolean }) required = false;
@property() label = ''; @property() label = '';
@property({ type: Array<T> }) options = []; @property({ type: Array }) options: T[] = [];
@property() valueMapper?: (option: T) => string; @property() valueMapper?: (option: T) => string;
@property() textMapper?: (option: T) => string; @property() textMapper?: (option: T) => string;
@ -106,15 +106,17 @@ export class StyledSelect<T> extends LitElement {
private _getOptionValue(option: T) { private _getOptionValue(option: T) {
if (option === undefined || option === null) return ''; if (option === undefined || option === null) return '';
if (this.valueMapper) return this.valueMapper(option); if (this.valueMapper) return this.valueMapper(option);
if (typeof option === 'object' && 'value' in option) return option.value; if (typeof option === 'object' && 'value' in (option as object))
return option; return (option as unknown as { value: string }).value;
return String(option);
} }
private _getOptionText(option: T) { private _getOptionText(option: T) {
if (option === undefined || option === null) return ''; if (option === undefined || option === null) return '';
if (this.textMapper) return this.textMapper(option); if (this.textMapper) return this.textMapper(option);
if (typeof option === 'object' && 'label' in option) return option.label; if (typeof option === 'object' && 'label' in (option as object))
return option; return (option as unknown as { label: string }).label;
return String(option);
} }
override render() { override render() {
@ -144,7 +146,7 @@ export class StyledSelect<T> extends LitElement {
const optionText = this._getOptionText(option); const optionText = this._getOptionText(option);
return html` return html`
<option <option
value=${optionValue} .value=${optionValue}
?selected=${optionValue === this.value} ?selected=${optionValue === this.value}
> >
${optionText} ${optionText}

View file

@ -1,5 +1,6 @@
import { LitElement, css, html } from 'lit'; import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js'; import { customElement, property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { when } from 'lit/directives/when.js'; import { when } from 'lit/directives/when.js';
import { ArxInputChangeEvent } from './Input'; import { ArxInputChangeEvent } from './Input';
@ -134,7 +135,7 @@ export class StyledTextarea extends LitElement {
return html` return html`
${when(this.label, () => html`<label for="textarea-${this.name}">${this.label}</label>`)} ${when(this.label, () => html`<label for="textarea-${this.name}">${this.label}</label>`)}
<div class="${hasMaxlength ? 'has-maxlength' : ''}"> <div class=${classMap({ 'has-maxlength': hasMaxlength })}>
<textarea <textarea
id="textarea-${this.name}" id="textarea-${this.name}"
.value=${this.value} .value=${this.value}
@ -142,7 +143,7 @@ export class StyledTextarea extends LitElement {
?required=${this.required} ?required=${this.required}
placeholder=${this.placeholder} placeholder=${this.placeholder}
rows=${this.rows} rows=${this.rows}
maxlength=${this.maxlength} .maxlength=${Number.parseInt(this.maxlength) || -1}
name=${this.name} name=${this.name}
@input=${this._handleInput} @input=${this._handleInput}
@focus=${this._handleFocus} @focus=${this._handleFocus}

View file

@ -172,7 +172,7 @@ export class Header extends LitElement {
</div> </div>
<div class="nav-buttons"> <div class="nav-buttons">
<button @click=${this._goToWallet}> <button @click=${this._goToWallet}>
<iconify-icon icon="material-symbols:wallet"></iconify-icon> <iconify-icon icon="arx:wallet"></iconify-icon>
</button> </button>
</div> </div>
</header> </header>
@ -290,11 +290,11 @@ export class Header extends LitElement {
window.location.hash = 'arbor'; window.location.hash = 'arbor';
break; break;
case 892: case 892:
window.location.hash = `arbor/topics/${suggestion.id}`; window.location.hash = `arbor/threads/${suggestion.id}`;
break; break;
case 893: { case 893: {
const threadId = suggestion.tags.find((tag) => tag[0] === 'E')?.[1]!; const threadId = suggestion.tags.find((tag) => tag[0] === 'E')?.[1]!;
window.location.hash = `arbor/topics/${threadId}`; window.location.hash = `arbor/threads/${threadId}`;
break; break;
} }
default: default:

View file

@ -1,10 +1,7 @@
import { ndk, setSigner } from '@/ndk';
import type { ArxInputChangeEvent } from '@components/General/Input'; import type { ArxInputChangeEvent } from '@components/General/Input';
import { animate } from '@lit-labs/motion'; import { animate } from '@lit-labs/motion';
import { randomBytes } from '@noble/ciphers/webcrypto'; import { randomBytes } from '@noble/ciphers/webcrypto';
import { NDKEvent, NDKKind, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import * as nip06 from '@nostr/tools/nip06'; import * as nip06 from '@nostr/tools/nip06';
import * as nip19 from '@nostr/tools/nip19';
import * as nip49 from '@nostr/tools/nip49'; import * as nip49 from '@nostr/tools/nip49';
import * as nostrTools from '@nostr/tools/pure'; import * as nostrTools from '@nostr/tools/pure';
import { encodeBase64 } from '@std/encoding/base64'; import { encodeBase64 } from '@std/encoding/base64';
@ -12,18 +9,23 @@ import { LitElement, css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js'; import { customElement, state } from 'lit/decorators.js';
import { when } from 'lit/directives/when.js'; import { when } from 'lit/directives/when.js';
import { ndk, setSigner } from '@/ndk';
import '@components/General/Button'; import '@components/General/Button';
import '@components/General/Fieldset'; import '@components/General/Fieldset';
import '@components/General/Input'; import '@components/General/Input';
import '@components/LoadingView'; import '@components/LoadingView';
import '@components/ProgressSteps'; import '@components/ProgressSteps';
import '@components/RelayLogs'; import '@components/RelayLogs';
import '@components/Setup/CCNList';
import { NDKEvent, NDKKind, NDKPrivateKeySigner, NDKRelay, NDKRelaySet } from '@nostr-dev-kit/ndk';
import { nip19 } from '@nostr/tools';
@customElement('arx-initial-setup') @customElement('arx-initial-setup')
export class InitialSetup extends LitElement { export class InitialSetup extends LitElement {
@state() private currentPage = 1; @state() private currentPage = 1;
@state() private isAnimating = false; @state() private isAnimating = false;
@state() private seedPhrase = ''; @state() private seedPhrase = '';
@state() private communityName = '';
@state() private userName = ''; @state() private userName = '';
@state() private profileImage = ''; @state() private profileImage = '';
@state() private lightningAddress = ''; @state() private lightningAddress = '';
@ -32,8 +34,10 @@ export class InitialSetup extends LitElement {
pid: null, pid: null,
logs: [], logs: [],
}; };
@state() private selectedCCN: string | undefined;
@state() private ccnList: { name: string; pubkey: string }[] = [];
private readonly pageLabels = ['Welcome', 'Seed Phrase', 'Relay Setup', 'Profile', 'Complete']; private readonly pageLabels = ['Welcome', 'Relay Setup', 'Seed Phrase', 'Profile', 'Complete'];
get encryptionPassphrase() { get encryptionPassphrase() {
let encryptionPassphrase = localStorage.getItem('encryption_key'); let encryptionPassphrase = localStorage.getItem('encryption_key');
@ -212,10 +216,30 @@ export class InitialSetup extends LitElement {
}, 300); }, 300);
} }
private nextStep() {
if (this.currentPage === 1) return this.handleNavigation(2);
if (this.currentPage === 2) {
if (this.selectedCCN) return this.handleNavigation(4);
return this.handleNavigation(3);
}
if (this.currentPage === 3) return this.handleNavigation(4);
if (this.currentPage === 4) return this.goToFinalStep();
}
private previousStep() {
if (this.currentPage === 2) return this.handleNavigation(1);
if (this.currentPage === 3) return this.handleNavigation(2);
if (this.currentPage === 4) return this.handleNavigation(3);
}
private onSeedPhraseInput(event: ArxInputChangeEvent) { private onSeedPhraseInput(event: ArxInputChangeEvent) {
this.seedPhrase = event.detail.value; this.seedPhrase = event.detail.value;
} }
private onCommunityNameInput(event: ArxInputChangeEvent) {
this.communityName = event.detail.value;
}
private generateSeedPhrase() { private generateSeedPhrase() {
this.seedPhrase = nip06.generateSeedWords(); this.seedPhrase = nip06.generateSeedWords();
} }
@ -227,7 +251,12 @@ export class InitialSetup extends LitElement {
return true; return true;
} }
private renderPageOne() { private isValidCommunityName() {
const trimmedName = this.communityName.trim();
return trimmedName.length > 0;
}
private renderWelcomePage() {
return html` return html`
<main class="welcome-container" ${animate()}> <main class="welcome-container" ${animate()}>
<arx-progress-steps <arx-progress-steps
@ -253,35 +282,139 @@ export class InitialSetup extends LitElement {
</p> </p>
</section> </section>
<section>
<arx-button
label="Share Feedback"
@click=${() => window.open('https://arx-ccn.com/eve-feedback')}
>
<iconify-icon slot="prefix" icon="mdi:feedback"></iconify-icon>
</arx-button>
<arx-button
variant="secondary"
label="Report a Bug"
@click=${() => window.open('https://arx-ccn.com/report-eve-bug')}
>
<iconify-icon slot="prefix" icon="mdi:bug"></iconify-icon>
</arx-button>
</section>
<div class="navigation"> <div class="navigation">
<span></span> <span></span>
<arx-button <arx-button
variant="primary" variant="primary"
label="Next" label="Next"
@click=${() => this.handleNavigation(2)} @click=${() => this.nextStep()}
></arx-button> ></arx-button>
</div> </div>
</main> </main>
`; `;
} }
private renderPageTwo() { private renderRelaySetupPage() {
return html`
<main class="welcome-container" ${animate()}>
<arx-progress-steps
.currentPage=${this.currentPage}
.pageLabels=${this.pageLabels}
></arx-progress-steps>
<section>
<h2>Configure Eve Relay</h2>
<p>
During this alpha phase, manual relay configuration is required.
This process will be automated in future releases.
</p>
<p>Please press the button below to start the relay.</p>
<arx-button
variant="primary"
label=${this.relayStatus.running ? 'Relay Running' : 'Start Relay'}
?disabled=${this.relayStatus.running}
@click=${() => this.startRelay()}
>
${when(
this.relayStatus.running,
() => html`<iconify-icon slot="prefix" icon="mdi:check-circle"></iconify-icon>`,
)}
</arx-button>
${when(
this.relayStatus.running,
() => html`
<p class="note">
<iconify-icon icon="mdi:information"></iconify-icon>
Relay is running with PID: ${this.relayStatus.pid}
</p>
<arx-relay-logs .logs=${this.relayStatus.logs}></arx-relay-logs>
<arx-setup-ccn-list @ccn-selected=${this.onCCNSelected} .ccns=${this.ccnList}></arx-setup-ccn-list>`,
)}
<p>
Having trouble? Our team is here to help if you encounter any
issues.
</p>
<p>Click Continue once the relay is running.</p>
</section>
<div class="navigation">
<arx-button
@click=${() => this.previousStep()}
variant="secondary"
label="Back"
>
</arx-button>
<arx-button
@click=${() => this.nextStep()}
variant="primary"
?disabled=${!this.relayStatus.running}
label=${this.selectedCCN ? 'Continue' : 'Create CCN'}
>
</arx-button>
</div>
</main>
`;
}
private onCCNSelected(e: CustomEvent<{ ccn: string }>) {
this.selectedCCN = e.detail.ccn;
}
private async startRelay() {
await window.relay.start(this.encryptionPassphrase);
await this.updateRelayStatus();
await new Promise((resolve) => {
const ws = new WebSocket('ws://localhost:6942');
ws.onopen = () => {
ws.send(JSON.stringify(['CCN', 'LIST']));
};
ws.onmessage = ({ data }) => {
const responseData = JSON.parse(data);
if (responseData[0] !== 'OK' || responseData[1] !== 'CCN LIST' || responseData[2] !== true) return;
this.ccnList = JSON.parse(responseData[3]);
resolve(true);
ws.close();
};
ws.onerror = () => {
resolve(false);
};
});
}
private async updateRelayStatus() {
this.relayStatus = await window.relay.getStatus();
if (this.relayStatus.running) setTimeout(() => this.updateRelayStatus(), 2000);
}
private async createCCN() {
await new Promise((resolve) => {
const ws = new WebSocket('ws://localhost:6942');
ws.onopen = () => {
ws.send(
JSON.stringify([
'CCN',
'CREATE',
{
name: this.communityName,
seed: this.seedPhrase,
},
]),
);
};
ws.onmessage = ({ data }) => {
const responseData = JSON.parse(data);
if (responseData[0] !== 'OK' || responseData[1] !== 'CCN CREATED' || responseData[2] !== true) return;
resolve(true);
ws.close();
this.selectedCCN = JSON.parse(responseData[3]).pubkey;
};
ws.onerror = () => {
resolve(false);
};
});
this.handleNavigation(4);
}
private renderSeedPhrasePage() {
return html` return html`
<main class="welcome-container" ${animate()}> <main class="welcome-container" ${animate()}>
<arx-progress-steps <arx-progress-steps
@ -334,89 +467,34 @@ export class InitialSetup extends LitElement {
</div> </div>
</section> </section>
<div class="navigation">
<arx-button
@click=${() => this.handleNavigation(1)}
label="Back"
variant="secondary"
>
</arx-button>
<arx-button
@click=${() => this.handleNavigation(3)}
?disabled=${!this.isValidSeedPhrase()}
label="Continue"
variant="primary"
>
</arx-button>
</div>
</main>
`;
}
private async startRelay() {
await window.relay.writeSeed(this.seedPhrase);
await window.relay.start(this.encryptionPassphrase);
this.updateRelayStatus();
}
private async updateRelayStatus() {
this.relayStatus = await window.relay.getStatus();
if (this.relayStatus.running) setTimeout(() => this.updateRelayStatus(), 2000);
}
private renderPageThree() {
return html`
<main class="welcome-container" ${animate()}>
<arx-progress-steps
.currentPage=${this.currentPage}
.pageLabels=${this.pageLabels}
></arx-progress-steps>
<section> <section>
<h2>Configure Eve Relay</h2> <h3>Community Name</h3>
<p> <p>
During this alpha phase, manual relay configuration is required. Enter a name for your community. This will be used to identify your
This process will be automated in future releases. community in Eve
</p> </p>
<p>Please press the button below to start the relay.</p>
<arx-button <arx-input
variant="primary" type="text"
label=${this.relayStatus.running ? 'Relay Running' : 'Start Relay'} placeholder="Enter Community Name..."
?disabled=${this.relayStatus.running} .value=${this.communityName}
@click=${() => this.startRelay()} @change=${this.onCommunityNameInput}
> ></arx-input>
${when(
this.relayStatus.running,
() => html`<iconify-icon slot="prefix" icon="mdi:check-circle"></iconify-icon>`,
)}
</arx-button>
${when(
this.relayStatus.running,
() => html`
<p class="note">
<iconify-icon icon="mdi:information"></iconify-icon>
Relay is running with PID: ${this.relayStatus.pid}
</p>
<arx-relay-logs .logs=${this.relayStatus.logs}></arx-relay-logs>`,
)}
<p>
Having trouble? Our team is here to help if you encounter any
issues.
</p>
<p>Click Continue once the relay is running.</p>
</section> </section>
<div class="navigation"> <div class="navigation">
<arx-button <arx-button
@click=${() => this.handleNavigation(2)} @click=${() => this.previousStep()}
variant="secondary"
label="Back" label="Back"
variant="secondary"
> >
</arx-button> </arx-button>
<arx-button <arx-button
@click=${() => this.handleNavigation(4)} @click=${() => this.createCCN()}
variant="primary" ?disabled=${!this.isValidSeedPhrase() || !this.isValidCommunityName()}
?disabled=${!this.relayStatus.running}
label="Continue" label="Continue"
variant="primary"
> >
</arx-button> </arx-button>
</div> </div>
@ -432,7 +510,7 @@ export class InitialSetup extends LitElement {
this.profileImage = e.detail.value; this.profileImage = e.detail.value;
} }
private renderPageFour() { private renderProfileSetupPage() {
return html` return html`
<main class="welcome-container" ${animate()}> <main class="welcome-container" ${animate()}>
<arx-progress-steps <arx-progress-steps
@ -473,13 +551,13 @@ export class InitialSetup extends LitElement {
</section> </section>
<div class="navigation"> <div class="navigation">
<arx-button <arx-button
@click=${() => this.handleNavigation(3)} @click=${() => this.previousStep()}
variant="secondary" variant="secondary"
label="Back" label="Back"
> >
</arx-button> </arx-button>
<arx-button <arx-button
@click=${() => this.goToFinalStep()} @click=${() => this.nextStep()}
variant="primary" variant="primary"
label="Next" label="Next"
> >
@ -490,17 +568,34 @@ export class InitialSetup extends LitElement {
} }
private async goToFinalStep() { private async goToFinalStep() {
await new Promise((resolve) => {
const ws = new WebSocket('ws://localhost:6942');
ws.onopen = () => {
ws.send(
JSON.stringify([
'CCN',
'ACTIVATE',
{
pubkey: this.selectedCCN,
},
]),
);
};
ws.onmessage = ({ data }) => {
const responseData = JSON.parse(data);
if (responseData[0] !== 'OK' || responseData[1] !== 'CCN ACTIVATED' || responseData[2] !== true) return;
resolve(true);
};
ws.onerror = () => {
resolve(false);
};
});
const randomPrivateKey = nostrTools.generateSecretKey(); const randomPrivateKey = nostrTools.generateSecretKey();
const encryptedNsec = nip49.encrypt(randomPrivateKey, this.encryptionPassphrase); const encryptedNsec = nip49.encrypt(randomPrivateKey, this.encryptionPassphrase);
const npub = nip19.npubEncode(nostrTools.getPublicKey(randomPrivateKey)); const npub = nip19.npubEncode(nostrTools.getPublicKey(randomPrivateKey));
this.lightningAddress = `${npub}@npub.cash`; this.lightningAddress = `${npub}@npub.cash`;
localStorage.setItem('ncryptsec', encryptedNsec);
setSigner(new NDKPrivateKeySigner(randomPrivateKey)); setSigner(new NDKPrivateKeySigner(randomPrivateKey));
await ndk.connect(5000); await ndk.connect(10000);
const event = new NDKEvent(ndk); const event = new NDKEvent(ndk);
event.kind = NDKKind.Metadata; event.kind = NDKKind.Metadata;
event.content = JSON.stringify({ event.content = JSON.stringify({
@ -509,12 +604,13 @@ export class InitialSetup extends LitElement {
lud16: this.lightningAddress, lud16: this.lightningAddress,
}); });
await event.sign(); await event.sign();
await event.publish(); const relaySet = new NDKRelaySet(new Set([new NDKRelay('ws://localhost:6942', undefined, ndk)]), ndk);
await event.publish(relaySet, 10000);
localStorage.setItem('ncryptsec', encryptedNsec);
this.handleNavigation(5); this.handleNavigation(5);
} }
private renderPageFive() { private renderFinalPage() {
return html` return html`
<main class="welcome-container" ${animate()}> <main class="welcome-container" ${animate()}>
<arx-progress-steps <arx-progress-steps
@ -539,7 +635,7 @@ export class InitialSetup extends LitElement {
<div class="navigation"> <div class="navigation">
<arx-button <arx-button
@click=${() => this.handleNavigation(4)} @click=${() => this.previousStep()}
variant="secondary" variant="secondary"
label="Back" label="Back"
> >
@ -571,15 +667,15 @@ export class InitialSetup extends LitElement {
override render() { override render() {
switch (this.currentPage) { switch (this.currentPage) {
case 1: case 1:
return this.renderPageOne(); return this.renderWelcomePage();
case 2: case 2:
return this.renderPageTwo(); return this.renderRelaySetupPage();
case 3: case 3:
return this.renderPageThree(); return this.renderSeedPhrasePage();
case 4: case 4:
return this.renderPageFour(); return this.renderProfileSetupPage();
case 5: case 5:
return this.renderPageFive(); return this.renderFinalPage();
default: default:
return html`<div class="welcome-container"> return html`<div class="welcome-container">
<arx-loading-view></arx-loading-view> <arx-loading-view></arx-loading-view>

View file

@ -0,0 +1,56 @@
import '@components/General/Button';
import '@components/General/Select';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
@customElement('arx-setup-ccn-list')
export class CCNList extends LitElement {
@property({ type: Array }) ccns: { name: string; pubkey: string }[] = [];
static override styles = css`
:host {
display: block;
}
`;
@state() private selectedCCN: string | undefined;
private _valueMapper = (ccn: { name: string; pubkey: string }) => ccn.pubkey;
private _textMapper = (ccn: { name: string; pubkey: string }) => ccn.name;
private _handleCCNSelected(pubkey: string | undefined) {
this.selectedCCN = pubkey;
this.dispatchEvent(new CustomEvent('ccn-selected', { detail: { ccn: this.selectedCCN } }));
}
override render() {
if (this.ccns.length === 0)
return html`
<p>No CCNs found in the relay. You will be asked to setup a CCN in the next step.</p>
`;
if (this.ccns.length === 1)
return html`
<div>
<h2>CCN Found</h2>
<p>We found one CCN in your relay, which likely means you've used Eve before or there was an update introducing new setup steps. You can use this CCN to proceed with the setup, or create a new one.</p>
<p>CCN Name: ${this.ccns[0].name}</p>
<arx-button variant=${this.selectedCCN === this.ccns[0].pubkey ? 'accent' : 'secondary'} @click="${() => this._handleCCNSelected(this.ccns[0].pubkey)}" label="Use This CCN"></arx-button>
<arx-button variant=${this.selectedCCN === undefined ? 'accent' : 'secondary'} @click="${() => this._handleCCNSelected(undefined)}" label="Create New CCN"></arx-button>
</div>
`;
return html`
<div>
<h2>CCNs</h2>
<p>We found the following CCNs in your relay, which likely means you've used Eve before or there was an update introducing new setup steps. You can quickly choose one of these CCNs to proceed with the setup, or create a new one.</p>
<arx-select
.options="${this.ccns}"
.valueMapper="${this._valueMapper}"
.textMapper="${this._textMapper}"
@change="${(e: CustomEvent<{ value: string }>) => this._handleCCNSelected(e.detail.value)}"
></arx-select>
<arx-button variant=${this.selectedCCN === undefined ? 'accent' : 'secondary'} @click="${() => this._handleCCNSelected(undefined)}" label="Create New CCN"></arx-button>
</div>
`;
}
}

View file

@ -20,31 +20,31 @@ export default class Sidebar extends LitElement {
apps = [ apps = [
{ {
id: 1, id: 1,
href: 'beacon', href: 'calendar',
name: 'Beacon', name: 'Calendar',
color: '#FF8C00', color: '#FF8C00',
icon: 'fa-solid:sun', icon: 'arx:calendar',
}, },
{ {
id: 2, id: 2,
href: 'arbor', href: 'arbor',
name: 'Arbor', name: 'Arbor',
color: '#FF4040', color: '#FF4040',
icon: 'fa-solid:tree', icon: 'arx:arbor',
}, },
{ {
id: 3, id: 3,
href: 'wallet', href: 'wallet',
name: 'Wallet', name: 'Wallet',
color: '#1E90FF', color: '#1E90FF',
icon: 'fa-solid:spa', icon: 'arx:wallet',
}, },
{ {
id: 4, id: 4,
href: 'settings', href: 'settings',
name: 'Settings', name: 'Settings',
color: '#7B68EE', color: '#7B68EE',
icon: 'fa-solid:tools', icon: 'arx:settings',
}, },
]; ];

View file

@ -1,19 +1,10 @@
import path from 'node:path';
import { is, optimizer } from '@electron-toolkit/utils'; import { is, optimizer } from '@electron-toolkit/utils';
import { BrowserWindow, app, ipcMain, shell } from 'electron'; import { BrowserWindow, app, ipcMain, shell } from 'electron';
import fs from 'node:fs';
import path from 'node:path';
import { RelayManager } from './relayManager'; import { RelayManager } from './relayManager';
const relay = new RelayManager(); const relay = new RelayManager();
ipcMain.handle('relay:writeSeed', async (_, ...args: string[]) => {
if (!args[0]) throw new Error('No seed provided');
const seed = args[0] as string;
const configPath = relay.getRelayConfigPath();
fs.mkdirSync(configPath, { recursive: true });
fs.writeFileSync(path.join(configPath, 'ccn.seed'), seed);
});
ipcMain.handle('relay:start', (_, ...args: string[]) => { ipcMain.handle('relay:start', (_, ...args: string[]) => {
if (!args[0]) throw new Error('No encryption key provided'); if (!args[0]) throw new Error('No encryption key provided');
const encryptionKey = args[0]; const encryptionKey = args[0];

View file

@ -5,7 +5,6 @@ if (process.contextIsolated) {
try { try {
contextBridge.exposeInMainWorld('electron', electronAPI); contextBridge.exposeInMainWorld('electron', electronAPI);
contextBridge.exposeInMainWorld('relay', { contextBridge.exposeInMainWorld('relay', {
writeSeed: (seed: string) => ipcRenderer.invoke('relay:writeSeed', seed),
start: (encryptionKey: string) => ipcRenderer.invoke('relay:start', encryptionKey), start: (encryptionKey: string) => ipcRenderer.invoke('relay:start', encryptionKey),
stop: () => ipcRenderer.invoke('relay:stop'), stop: () => ipcRenderer.invoke('relay:stop'),
getStatus: () => ipcRenderer.invoke('relay:status'), getStatus: () => ipcRenderer.invoke('relay:status'),

View file

@ -1,8 +1,8 @@
import { is } from '@electron-toolkit/utils';
import { app } from 'electron';
import { type ChildProcess, spawn } from 'node:child_process'; import { type ChildProcess, spawn } from 'node:child_process';
import { existsSync, rmdirSync } from 'node:fs'; import { existsSync, rmdirSync } from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
import { is } from '@electron-toolkit/utils';
import { app } from 'electron';
type PackageEnvironment = 'flatpak' | 'appimage' | 'system' | 'mac' | 'dev'; type PackageEnvironment = 'flatpak' | 'appimage' | 'system' | 'mac' | 'dev';
@ -144,20 +144,6 @@ export class RelayManager {
this.process = spawn('nc', ['localhost', '6942']); this.process = spawn('nc', ['localhost', '6942']);
this.devRunning = true; this.devRunning = true;
if (this.process.stdout) {
this.process.stdout.on('data', (data: Buffer) => {
const logLine = data.toString().trim();
this.addLog(`[DEV] ${logLine}`);
});
}
if (this.process.stderr) {
this.process.stderr.on('data', (data: Buffer) => {
const logLine = data.toString().trim();
this.addLog(`[DEV] ERROR: ${logLine}`);
});
}
this.process.on('error', (err: Error) => { this.process.on('error', (err: Error) => {
this.addLog(`[DEV] Failed to start netcat: ${err.message}`); this.addLog(`[DEV] Failed to start netcat: ${err.message}`);
this.process = null; this.process = null;
@ -210,9 +196,7 @@ export class RelayManager {
this.process.on('exit', this.handleProcessExit.bind(this)); this.process.on('exit', this.handleProcessExit.bind(this));
if (this.process.pid) { if (this.process.pid) this.restartAttempts = 0;
this.restartAttempts = 0;
}
} catch (error) { } catch (error) {
console.error(`Error starting Relay: ${error instanceof Error ? error.message : 'Unknown error'}`); console.error(`Error starting Relay: ${error instanceof Error ? error.message : 'Unknown error'}`);
this.restartProcess(); this.restartProcess();

View file

@ -1,3 +1,4 @@
import '@/arx-icons';
import '@components/Breadcrumbs'; import '@components/Breadcrumbs';
import '@components/ErrorView'; import '@components/ErrorView';
import '@components/Header'; import '@components/Header';
@ -8,6 +9,8 @@ import '@routes/router';
import type EveRouter from '@routes/router'; import type EveRouter from '@routes/router';
import './style.css'; import './style.css';
let availableCCNs: { name: string; pubkey: string }[] = [];
function checkRelayUp() { function checkRelayUp() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {
@ -19,7 +22,66 @@ function checkRelayUp() {
ws.onopen = () => { ws.onopen = () => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
ws.close(); ws.send(JSON.stringify(['CCN', 'LIST']));
};
ws.onmessage = async (message) => {
const data = JSON.parse(message.data);
console.log(data);
if (data[0] !== 'OK' || data[1] !== 'CCN LIST' || data[2] !== true) return;
availableCCNs = JSON.parse(data[3]);
if (availableCCNs.length === 0) throw new Error('No CCNs found');
if (availableCCNs.length === 1) {
ws.send(
JSON.stringify([
'CCN',
'ACTIVATE',
{
pubkey: availableCCNs[0].pubkey,
},
]),
);
return resolve(true);
}
if (localStorage.getItem('selectedCCN')) {
const selected = JSON.parse(localStorage.getItem('selectedCCN')!) as { date: number; pubkey: string };
if (Date.now() - selected.date < 1000 * 60 * 60 * 24) {
ws.send(JSON.stringify(['CCN', 'ACTIVATE', { pubkey: selected.pubkey }]));
return resolve(true);
}
}
await new Promise((resolve) => {
let selectedCCN: string | null = availableCCNs[0].pubkey;
const selectBox = document.createElement('arx-select');
selectBox.options = availableCCNs.map((ccn) => ({ label: ccn.name, value: ccn.pubkey }));
selectBox.addEventListener('change', (e) => {
selectedCCN = e.target.value;
});
const okButton = document.createElement('arx-button');
okButton.label = 'OK';
okButton.addEventListener('click', () => {
if (selectedCCN) {
ws.send(
JSON.stringify([
'CCN',
'ACTIVATE',
{
pubkey: selectedCCN,
},
]),
);
localStorage.setItem('selectedCCN', JSON.stringify({ date: Date.now(), pubkey: selectedCCN }));
resolve(true);
document.body.removeChild(dialog);
}
});
const dialog = document.createElement('arx-dialog');
dialog.title = 'Select CCN';
dialog.open = true;
dialog.appendChild(selectBox);
dialog.appendChild(okButton);
document.body.appendChild(dialog);
});
resolve(true); resolve(true);
}; };

View file

@ -5,7 +5,6 @@ interface RelayStatus {
} }
interface RelayBridge { interface RelayBridge {
writeSeed: (seed: string) => Promise<void>;
start: (encryptionKey: string) => Promise<void>; start: (encryptionKey: string) => Promise<void>;
stop: () => Promise<void>; stop: () => Promise<void>;
getStatus: () => Promise<RelayStatus>; getStatus: () => Promise<RelayStatus>;

View file

@ -5,14 +5,14 @@ import { customElement, state } from 'lit/decorators.js';
import { map } from 'lit/directives/map.js'; import { map } from 'lit/directives/map.js';
import { when } from 'lit/directives/when.js'; import { when } from 'lit/directives/when.js';
import '@components/Breadcrumbs';
import '@components/Arbor/ForumTopic';
import '@components/Arbor/ForumCategory'; import '@components/Arbor/ForumCategory';
import '@components/Arbor/ForumThread';
import '@components/Breadcrumbs';
import '@components/General/Prompt'; import '@components/General/Prompt';
import type { EvePrompt } from '@components/General/Prompt'; import type { EvePrompt } from '@components/General/Prompt';
interface ForumTopic { interface ForumThread {
id: string; id: string;
title: string; title: string;
author: string; author: string;
@ -23,7 +23,7 @@ interface ForumTopic {
interface ForumCategory { interface ForumCategory {
id: string; id: string;
name: string; name: string;
topics: ForumTopic[]; threads: ForumThread[];
} }
@customElement('arx-arbor-home') @customElement('arx-arbor-home')
@ -33,7 +33,7 @@ export class ArborForum extends LitElement {
private isSaving = false; private isSaving = false;
private topicsQuery: NDKSubscription | undefined; private threadsQuery: NDKSubscription | undefined;
@state() @state()
private forum: NDKEvent | null = null; private forum: NDKEvent | null = null;
@ -42,6 +42,10 @@ export class ArborForum extends LitElement {
:host { :host {
display: block; display: block;
} }
arx-button {
margin-bottom: calc(var(--spacing-sm) / 2);
}
`; `;
override async connectedCallback() { override async connectedCallback() {
@ -92,11 +96,11 @@ export class ArborForum extends LitElement {
{ {
id: dtag, id: dtag,
name: newCategory, name: newCategory,
topics: [], threads: [],
}, },
]; ];
this.loadTopics(); this.loadThreads();
} catch (error) { } catch (error) {
console.error('Failed to create category:', error); console.error('Failed to create category:', error);
alert('Failed to create category'); alert('Failed to create category');
@ -128,17 +132,17 @@ export class ArborForum extends LitElement {
{ {
id: categoryId, id: categoryId,
name: tag[2], name: tag[2],
topics: [], threads: [],
}, },
]; ];
} }
} }
this.loadTopics(); this.loadThreads();
} }
private async loadTopics() { private async loadThreads() {
if (this.topicsQuery) this.topicsQuery.stop(); if (this.threadsQuery) this.threadsQuery.stop();
this.topicsQuery = ndk this.threadsQuery = ndk
.subscribe({ .subscribe({
kinds: [892 as NDKKind], kinds: [892 as NDKKind],
'#d': this.categories.map((category) => `60891:${category.id}`), '#d': this.categories.map((category) => `60891:${category.id}`),
@ -148,9 +152,9 @@ export class ArborForum extends LitElement {
(category) => category.id === event.tags.find((tag) => tag[0] === 'd')?.[1].split(':')[1], (category) => category.id === event.tags.find((tag) => tag[0] === 'd')?.[1].split(':')[1],
); );
if (categoryId === -1) return; if (categoryId === -1) return;
if (this.categories[categoryId].topics.find((topic) => topic.id === event.id)) return; if (this.categories[categoryId].threads.find((thread) => thread.id === event.id)) return;
this.categories[categoryId].topics = [ this.categories[categoryId].threads = [
...this.categories[categoryId].topics, ...this.categories[categoryId].threads,
{ {
id: event.id, id: event.id,
title: event.tags.find((tag) => tag[0] === 'name')?.[1] || '', title: event.tags.find((tag) => tag[0] === 'name')?.[1] || '',
@ -187,18 +191,18 @@ export class ArborForum extends LitElement {
this.categories, this.categories,
(category) => html` (category) => html`
<arx-arbor-forum-category id=${category.id} title=${category.name}> <arx-arbor-forum-category id=${category.id} title=${category.name}>
${when(category.topics.length === 0, () => html` <div style="padding: 1rem 1.5rem">No topics...</div> `)} ${when(category.threads.length === 0, () => html` <div style="padding: 1rem 1.5rem">No threads...</div> `)}
${map( ${map(
category.topics, category.threads,
(topic) => html` (thread) => html`
<arx-arbor-forum-topic <arx-arbor-forum-thread
id=${topic.id} id=${thread.id}
title=${topic.title} title=${thread.title}
description=${topic.description} description=${thread.description}
lastPostBy=${topic.author} lastPostBy=${thread.author}
lastPostTime=${topic.created_at} lastPostTime=${thread.created_at}
> >
</arx-arbor-forum-topic> </arx-arbor-forum-thread>
`, `,
)} )}
</arx-arbor-forum-category> </arx-arbor-forum-category>

View file

@ -9,13 +9,13 @@ import '@components/General/Textarea';
@customElement('arx-arbor-post-creator') @customElement('arx-arbor-post-creator')
export class ArborPostCreator extends LitElement { export class ArborPostCreator extends LitElement {
@property({ type: String }) @property({ type: String })
topicId = ''; threadId = '';
@state() @state()
private postContent = ''; private postContent = '';
@state() @state()
private topic: NDKEvent | null = null; private thread: NDKEvent | null = null;
@state() @state()
private isCreating = false; private isCreating = false;
@ -34,7 +34,7 @@ export class ArborPostCreator extends LitElement {
gap: 1rem; gap: 1rem;
} }
.topic-id { .thread-id {
color: #666; color: #666;
font-family: monospace; font-family: monospace;
} }
@ -58,24 +58,24 @@ export class ArborPostCreator extends LitElement {
override connectedCallback() { override connectedCallback() {
super.connectedCallback(); super.connectedCallback();
this.loadTopic(); this.loadThread();
} }
private async loadTopic() { private async loadThread() {
try { try {
await getSigner(); await getSigner();
this.topic = await ndk.fetchEvent(this.topicId); this.thread = await ndk.fetchEvent(this.threadId);
if (!this.topic) { if (!this.thread) {
throw new Error('Could not load topic'); throw new Error('Could not load thread');
} }
} catch (error) { } catch (error) {
console.error('Failed to load topic:', error); console.error('Failed to load thread:', error);
} }
} }
private async doCreatePost() { private async doCreatePost() {
if (this.isCreating) return; if (this.isCreating) return;
if (!this.topic) return; if (!this.thread) return;
if (this.postContent.length < 10) { if (this.postContent.length < 10) {
this.error = 'Post content must be at least 10 characters long'; this.error = 'Post content must be at least 10 characters long';
@ -88,9 +88,9 @@ export class ArborPostCreator extends LitElement {
const event = new NDKEvent(ndk); const event = new NDKEvent(ndk);
event.kind = 893; event.kind = 893;
event.tags = [ event.tags = [
['A', this.topic.tags.find((tag) => tag[0] === 'd')?.[1] || ''], ['A', this.thread.tags.find((tag) => tag[0] === 'd')?.[1] || ''],
['E', this.topicId], ['E', this.threadId],
['p', this.topic.pubkey], ['p', this.thread.pubkey],
]; ];
event.content = this.postContent; event.content = this.postContent;
@ -117,7 +117,7 @@ export class ArborPostCreator extends LitElement {
override render() { override render() {
return html` return html`
<div class="container"> <div class="container">
<div class="topic-id">Topic ID: ${this.topicId}</div> <div class="thread-id">Thread ID: ${this.threadId}</div>
<arx-textarea <arx-textarea
placeholder="Post. You can use Markdown here." placeholder="Post. You can use Markdown here."

View file

@ -8,16 +8,16 @@ import '@components/General/Button';
import '@components/General/Input'; import '@components/General/Input';
import '@components/General/Textarea'; import '@components/General/Textarea';
@customElement('arx-arbor-topic-creator') @customElement('arx-arbor-thread-creator')
export class ArborTopicCreator extends LitElement { export class ArborThreadCreator extends LitElement {
@property({ type: String }) @property({ type: String })
categoryId = ''; categoryId = '';
@state() @state()
private newTopic = ''; private newThread = '';
@state() @state()
private topicContent = ''; private threadContent = '';
@state() @state()
private isCreating = false; private isCreating = false;
@ -39,16 +39,16 @@ export class ArborTopicCreator extends LitElement {
} }
`; `;
private async doCreateTopic() { private async doCreateThread() {
if (this.isCreating) return; if (this.isCreating) return;
if (this.newTopic.length < 3) { if (this.newThread.length < 3) {
alert('Topic title must be at least 3 characters long'); alert('Thread title must be at least 3 characters long');
return; return;
} }
if (this.topicContent.length < 10) { if (this.threadContent.length < 10) {
alert('Topic content must be at least 10 characters long'); alert('Thread content must be at least 10 characters long');
return; return;
} }
@ -59,40 +59,27 @@ export class ArborTopicCreator extends LitElement {
const event = new NDKEvent(ndk); const event = new NDKEvent(ndk);
event.kind = 892; event.kind = 892;
event.tags = [ event.tags = [
['name', this.newTopic], ['name', this.newThread],
['d', `60891:${this.categoryId}`], ['d', `60891:${this.categoryId}`],
]; ];
event.content = this.topicContent; event.content = this.threadContent;
await event.sign(); await event.sign();
await event.publish(); await event.publish();
location.hash = `/arbor/${event.id}`;
this.dispatchEvent(
new CustomEvent('topic-created', {
bubbles: true,
composed: true,
detail: {
topicId: event.id,
categoryId: this.categoryId,
},
}),
);
this.newTopic = '';
this.topicContent = '';
} catch (error) { } catch (error) {
console.error('Failed to create topic:', error); console.error('Failed to create thread:', error);
alert('Failed to create topic'); alert('Failed to create thread');
} finally { } finally {
this.isCreating = false; this.isCreating = false;
} }
} }
private handleTopicInput(e: ArxInputChangeEvent) { private handleThreadInput(e: ArxInputChangeEvent) {
this.newTopic = e.detail.value; this.newThread = e.detail.value;
} }
private handleContentInput(e: ArxInputChangeEvent) { private handleContentInput(e: ArxInputChangeEvent) {
this.topicContent = e.detail.value; this.threadContent = e.detail.value;
} }
override render() { override render() {
@ -101,15 +88,15 @@ export class ArborTopicCreator extends LitElement {
<arx-input <arx-input
type="text" type="text"
placeholder="New Topic" placeholder="New Thread"
.value=${this.newTopic} .value=${this.newThread}
@change=${this.handleTopicInput} @change=${this.handleThreadInput}
?disabled=${this.isCreating} ?disabled=${this.isCreating}
></arx-input> ></arx-input>
<arx-textarea <arx-textarea
placeholder="Topic. You can use Markdown here." placeholder="Thread. You can use Markdown here."
.value=${this.topicContent} .value=${this.threadContent}
@change=${this.handleContentInput} @change=${this.handleContentInput}
?disabled=${this.isCreating} ?disabled=${this.isCreating}
></arx-textarea> ></arx-textarea>
@ -123,7 +110,7 @@ export class ArborTopicCreator extends LitElement {
<arx-button <arx-button
label=${this.isCreating ? 'Creating...' : 'Create'} label=${this.isCreating ? 'Creating...' : 'Create'}
@click=${this.doCreateTopic} @click=${this.doCreateThread}
?disabled=${this.isCreating} ?disabled=${this.isCreating}
> >
</arx-button> </arx-button>

View file

@ -3,8 +3,8 @@ import type { NDKKind, NDKSubscription } from '@nostr-dev-kit/ndk';
import { LitElement, css, html } from 'lit'; import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js'; import { customElement, property, state } from 'lit/decorators.js';
import '@components/Breadcrumbs';
import '@components/Arbor/ForumPost'; import '@components/Arbor/ForumPost';
import '@components/Breadcrumbs';
import '@components/General/Button'; import '@components/General/Button';
import { map } from 'lit/directives/map.js'; import { map } from 'lit/directives/map.js';
@ -17,10 +17,10 @@ interface ForumPost {
content: string; content: string;
} }
@customElement('arx-arbor-topic-view') @customElement('arx-arbor-thread-view')
export class ArborTopicView extends LitElement { export class ArborThreadView extends LitElement {
@property({ type: String }) @property({ type: String })
topicId = ''; threadId = '';
@state() @state()
override title = ''; override title = '';
@ -36,7 +36,7 @@ export class ArborTopicView extends LitElement {
margin: 0 auto; margin: 0 auto;
} }
.topic-container { .thread-container {
background: var(--color-base-100); background: var(--color-base-100);
border-radius: var(--radius-box); border-radius: var(--radius-box);
border: var(--border) solid var(--color-base-300); border: var(--border) solid var(--color-base-300);
@ -144,7 +144,7 @@ export class ArborTopicView extends LitElement {
override async connectedCallback() { override async connectedCallback() {
super.connectedCallback(); super.connectedCallback();
await this.loadTopic(); await this.loadThread();
} }
override disconnectedCallback() { override disconnectedCallback() {
@ -154,13 +154,13 @@ export class ArborTopicView extends LitElement {
} }
} }
private async loadTopic() { private async loadThread() {
try { try {
await getSigner(); await getSigner();
const event = await ndk.fetchEvent(this.topicId); const event = await ndk.fetchEvent(this.threadId);
if (!event) { if (!event) {
throw new Error('Could not load topic'); throw new Error('Could not load thread');
} }
this.title = event.tags.find((tag) => tag[0] === 'name')?.[1] || ''; this.title = event.tags.find((tag) => tag[0] === 'name')?.[1] || '';
@ -178,7 +178,7 @@ export class ArborTopicView extends LitElement {
this.subscription = ndk this.subscription = ndk
.subscribe({ .subscribe({
kinds: [893 as NDKKind], kinds: [893 as NDKKind],
'#E': [this.topicId], '#E': [this.threadId],
}) })
.on('event', (event) => { .on('event', (event) => {
this.posts = [ this.posts = [
@ -192,8 +192,8 @@ export class ArborTopicView extends LitElement {
]; ];
}); });
} catch (error) { } catch (error) {
console.error('Failed to load topic:', error); console.error('Failed to load thread:', error);
alert('Could not load topic'); alert('Could not load thread');
} }
} }
@ -203,9 +203,9 @@ export class ArborTopicView extends LitElement {
return html` return html`
<arx-breadcrumbs .items=${breadcrumbItems}></arx-breadcrumbs> <arx-breadcrumbs .items=${breadcrumbItems}></arx-breadcrumbs>
<div class="topic-container"> <div class="thread-container">
<div class="header"> <div class="header">
<span>${this.title || 'Loading topic...'}</span> <span>${this.title || 'Loading thread...'}</span>
</div> </div>
<div class="posts-container"> <div class="posts-container">
@ -218,7 +218,7 @@ export class ArborTopicView extends LitElement {
(post) => html` (post) => html`
<arx-forum-post <arx-forum-post
id=${post.id} id=${post.id}
.topicId=${this.topicId} .threadId=${this.threadId}
.npub=${post.npub} .npub=${post.npub}
.date=${post.date} .date=${post.date}
.content=${post.content} .content=${post.content}
@ -229,7 +229,7 @@ export class ArborTopicView extends LitElement {
</div> </div>
<div class="actions"> <div class="actions">
<arx-button label="New Post" href="/arbor/new-post/${this.topicId}"> <arx-button label="New Post" href="/arbor/new-post/${this.threadId}">
New Post New Post
</arx-button> </arx-button>
</div> </div>

View file

@ -26,56 +26,57 @@ export class Home extends LitElement {
href: 'letters', href: 'letters',
name: 'Letters', name: 'Letters',
color: '#FF3E96', color: '#FF3E96',
icon: 'fa-solid:leaf', icon: 'arx:letters',
}, },
{ {
id: 1, id: 1,
href: 'messages', href: 'howl',
name: 'Murmur', name: 'Howl',
color: '#00CD66', color: '#00CD66',
icon: 'fa-solid:seedling', icon: 'arx:howl',
}, },
{ {
id: 2, id: 2,
href: 'beacon', href: 'calendar',
name: 'Beacon', name: 'Calendar',
color: '#FF8C00', color: '#FF8C00',
icon: 'fa-solid:sun', icon: 'arx:calendar',
}, },
{ {
id: 3, id: 3,
href: 'arbor', href: 'arbor',
name: 'Arbor', name: 'Arbor',
color: '#FF4040', color: '#FF4040',
icon: 'fa-solid:tree', icon: 'arx:arbor',
}, },
{ {
id: 5, id: 5,
href: 'grove', href: 'market',
name: 'Grove', name: 'Market',
color: '#9370DB', color: '#9370DB',
icon: 'fa-solid:store-alt', icon: 'arx:market',
}, },
{ {
id: 6, id: 6,
href: 'wallet', href: 'wallet',
name: 'Wallet', name: 'Wallet',
color: '#1E90FF', color: '#1E90FF',
icon: 'fa-solid:spa', icon: 'arx:wallet',
}, },
{ {
id: 7, id: 7,
href: 'oracle', href: 'pool',
name: 'Oracle', name: 'Pool',
color: '#FFD700', color: '#7aD700',
icon: 'bxs:landscape', icon: 'arx:pool',
}, },
{ {
id: 8, id: 8,
href: 'settings', href: 'settings',
name: 'Settings', name: 'Settings',
color: '#7B68EE', color: '#7B68EE',
icon: 'fa-solid:tools', icon: 'arx:settings',
},
}, },
]; ];

View file

@ -3,8 +3,8 @@ import '@components/Sidebar';
import '@routes/404Page'; import '@routes/404Page';
import '@routes/Arbor/Home'; import '@routes/Arbor/Home';
import '@routes/Arbor/NewPost'; import '@routes/Arbor/NewPost';
import '@routes/Arbor/NewTopic'; import '@routes/Arbor/NewThread';
import '@routes/Arbor/TopicView'; import '@routes/Arbor/ThreadView';
import '@routes/Calendar'; import '@routes/Calendar';
import '@routes/Home'; import '@routes/Home';
import '@routes/Profile'; import '@routes/Profile';
@ -16,6 +16,7 @@ import type { NDKUserProfile } from '@nostr-dev-kit/ndk';
import { spread } from '@open-wc/lit-helpers'; import { spread } from '@open-wc/lit-helpers';
import { LitElement, css } from 'lit'; import { LitElement, css } from 'lit';
import { customElement, property, state } from 'lit/decorators.js'; import { customElement, property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { keyed } from 'lit/directives/keyed.js'; import { keyed } from 'lit/directives/keyed.js';
import { type Ref, createRef, ref } from 'lit/directives/ref.js'; import { type Ref, createRef, ref } from 'lit/directives/ref.js';
import { when } from 'lit/directives/when.js'; import { when } from 'lit/directives/when.js';
@ -42,7 +43,7 @@ export default class EveRouter extends LitElement {
component: literal`arx-eve-home`, component: literal`arx-eve-home`,
}, },
{ {
pattern: 'beacon', pattern: 'calendar',
params: {}, params: {},
component: literal`arx-calendar-route`, component: literal`arx-calendar-route`,
}, },
@ -57,20 +58,25 @@ export default class EveRouter extends LitElement {
component: literal`arx-arbor-home`, component: literal`arx-arbor-home`,
}, },
{ {
pattern: 'arbor/new-topic/:categoryId', pattern: 'arbor/new-thread/:categoryId',
params: {}, params: {},
component: literal`arx-arbor-topic-creator`, component: literal`arx-arbor-thread-creator`,
}, },
{ {
pattern: 'arbor/topics/:topicId', pattern: 'arbor/threads/:threadId',
params: {}, params: {},
component: literal`arx-arbor-topic-view`, component: literal`arx-arbor-thread-view`,
}, },
{ {
pattern: 'arbor/new-post/:topicId', pattern: 'arbor/new-post/:threadId',
params: {}, params: {},
component: literal`arx-arbor-post-creator`, component: literal`arx-arbor-post-creator`,
}, },
{
pattern: 'howl',
params: {},
component: literal`arx-howl-route`,
},
{ {
pattern: 'settings', pattern: 'settings',
params: {}, params: {},
@ -112,6 +118,9 @@ export default class EveRouter extends LitElement {
@state() @state()
private userNpub = ''; private userNpub = '';
@state()
private sidebarVisible = true;
static override styles = css` static override styles = css`
:host { :host {
position: fixed; position: fixed;
@ -121,8 +130,13 @@ export default class EveRouter extends LitElement {
height: 100%; height: 100%;
display: grid; display: grid;
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
grid-template-columns: 100px 1fr; grid-template-columns: auto 1fr;
overflow: hidden; overflow: hidden;
transition: grid-template-columns 0.3s ease;
}
:host([sidebar-hidden]) {
grid-template-columns: 0 1fr;
} }
::-webkit-scrollbar { ::-webkit-scrollbar {
@ -173,6 +187,21 @@ export default class EveRouter extends LitElement {
arx-sidebar { arx-sidebar {
grid-column: 1; grid-column: 1;
grid-row: 1 / span 2; grid-row: 1 / span 2;
transition: width 0.3s ease, opacity 0.3s ease, padding 0.3s ease, transform 0.3s ease;
width: 100px;
opacity: 1;
overflow: hidden;
background: var(--color-base-100);
border-right: 1px solid var(--color-base-300);
}
arx-sidebar.hidden {
width: 0;
opacity: 0;
padding: 0;
pointer-events: none;
transform: translateX(-20px);
border-right: none;
} }
.window-content { .window-content {
@ -187,6 +216,7 @@ export default class EveRouter extends LitElement {
transition: var(--transition); transition: var(--transition);
backface-visibility: hidden; backface-visibility: hidden;
filter: blur(0px); filter: blur(0px);
grid-row: 1;
} }
.window-content::after { .window-content::after {
@ -218,6 +248,44 @@ export default class EveRouter extends LitElement {
grid-column: 2; grid-column: 2;
grid-row: 1; grid-row: 1;
} }
.sidebar-toggle {
position: fixed;
top: 50%;
left: 100px;
transform: translateY(-50%);
z-index: 1000;
background: color-mix(in srgb, var(--color-primary) 70%, transparent);
backdrop-filter: blur(5px);
color: var(--color-base);
border: none;
width: 20px;
height: 50px;
border-top-right-radius: var(--radius-btn);
border-bottom-right-radius: var(--radius-btn);
border-top-left-radius: 0;
border-bottom-left-radius: 0;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
transition: all var(--transition);
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
}
:host([sidebar-hidden]) .sidebar-toggle {
left: 0;
border-top-left-radius: var(--radius-btn);
border-bottom-left-radius: var(--radius-btn);
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.sidebar-toggle:hover {
/* background: var(--color-primary-focus); */
background: color-mix(in srgb, var(--color-primary) 90%, transparent);
}
`; `;
constructor() { constructor() {
@ -233,6 +301,7 @@ export default class EveRouter extends LitElement {
if (this.ccnSetup) { if (this.ccnSetup) {
this.loadUserProfile(); this.loadUserProfile();
} }
this.updateSidebarAttribute();
} }
override disconnectedCallback(): void { override disconnectedCallback(): void {
@ -403,11 +472,36 @@ export default class EveRouter extends LitElement {
} }
} }
private toggleSidebar() {
this.sidebarVisible = !this.sidebarVisible;
this.updateSidebarAttribute();
}
private updateSidebarAttribute() {
if (this.sidebarVisible) {
this.removeAttribute('sidebar-hidden');
} else {
this.setAttribute('sidebar-hidden', '');
}
}
override render() { override render() {
if (!this.ccnSetup) return this.renderSetup(); if (!this.ccnSetup) return this.renderSetup();
return html` return html`
<button class="sidebar-toggle" @click=${this.toggleSidebar} title=${when(
this.sidebarVisible,
() => 'Hide Sidebar',
() => 'Show Sidebar',
)}>
${when(
this.sidebarVisible,
() => html`<iconify-icon icon="mdi:chevron-left"></iconify-icon>`,
() => html`<iconify-icon icon="mdi:chevron-right"></iconify-icon>`,
)}
</button>
<arx-sidebar <arx-sidebar
class=${classMap({ hidden: !this.sidebarVisible })}
.currentPath=${this.currentPath} .currentPath=${this.currentPath}
.userNpub=${this.userNpub} .userNpub=${this.userNpub}
.userProfile=${this.userProfile} .userProfile=${this.userProfile}
@ -422,8 +516,8 @@ export default class EveRouter extends LitElement {
@go-forward=${this.goForward} @go-forward=${this.goForward}
title="Eve" title="Eve"
></arx-header> ></arx-header>
<div class="window ${this.currentRoute.pattern === 'home' ? 'hide-overflow' : ''}"> <div class=${classMap({ window: true, 'hide-overflow': this.currentRoute.pattern === 'home' })}>
<div ${ref(this.windowContentRef)} class="window-content ${this.isTransitioning ? 'transitioning' : ''}"> <div ${ref(this.windowContentRef)} class=${classMap({ 'window-content': true, transitioning: this.isTransitioning })}>
${when( ${when(
this.isTransitioning, this.isTransitioning,
() => html`<arx-loading-view></arx-loading-view>`, () => html`<arx-loading-view></arx-loading-view>`,

View file

@ -29,7 +29,8 @@
"@routes/*": ["./src/routes/*"], "@routes/*": ["./src/routes/*"],
"@styles/*": ["./src/styles/*"], "@styles/*": ["./src/styles/*"],
"@components/*": ["./src/components/*"], "@components/*": ["./src/components/*"],
"@widgets/*": ["./src/components/Widgets/*"] "@widgets/*": ["./src/components/Widgets/*"],
"@assets/*": ["./src/assets/*"],
} }
}, },
"include": ["./src/**/*"] "include": ["./src/**/*"]