From e25d43200fe65cc9f41872466f0f97adf949e63a Mon Sep 17 00:00:00 2001 From: kodi Date: Thu, 12 Mar 2026 20:13:50 +0100 Subject: [PATCH] feat: theme - 02 --- .../settings_service.cpython-313.pyc | Bin 6094 -> 6224 bytes .../backend/app/services/settings_service.py | 17 +++++- webui/backend/data/tasks.db | Bin 81920 -> 106496 bytes .../test_api_settings_golden.cpython-313.pyc | Bin 13950 -> 14572 bytes .../test_ui_smoke_golden.cpython-313.pyc | Bin 19051 -> 21324 bytes .../tests/golden/test_api_settings_golden.py | 7 +++ .../tests/golden/test_ui_smoke_golden.py | 28 ++++++++- webui/html/app.js | 22 ++++++-- webui/html/base.css | 18 ++++++ webui/html/index.html | 8 +++ webui/html/theme-catppuccin-soft.css | 49 ++++++++++++++++ webui/html/theme-commander-electric.css | 53 ++++++++++++++++++ webui/html/theme-fluent-neon.css | 53 ++++++++++++++++++ webui/html/theme-nord-arctic.css | 49 ++++++++++++++++ 14 files changed, 295 insertions(+), 9 deletions(-) create mode 100644 webui/html/theme-catppuccin-soft.css create mode 100644 webui/html/theme-commander-electric.css create mode 100644 webui/html/theme-fluent-neon.css create mode 100644 webui/html/theme-nord-arctic.css diff --git a/webui/backend/app/services/__pycache__/settings_service.cpython-313.pyc b/webui/backend/app/services/__pycache__/settings_service.cpython-313.pyc index 40f690510d2c18f38036c5c82a079a5df769d517..808d9e4cfddf9c454b5b6d87551cf5d48810d0b6 100644 GIT binary patch delta 1387 zcmZuxO>7fK6yDj`>%{;6i8lspz=Xv%1c{V{01j%5!e1&8{#qfe*6T?yvUln18ZJof zsXcK(uLmTgs;w%uoO9#SLl3MTAER(tW29!w^>7ndGaS2hobn5OsxMa{wiko^CWB!!d`@Z2vZ!C zQI>(l$g>UdYRrbRzmMu52$W{@1u2NZucJ1p9XsJFFdqpsOh zwQtZCWmBN}wp&cESVy3@O4B|34(Z{zqVf1TYUfiXg+Q z;C{aPL}*%a*EtJkLPc@{+M(KDEz@EW-h|f9&hP-9WPyJiZ_~U(8J{hp${2@XIy4+a z&cxQ;QZ-AhnP#&JKZU1B9=?xe;B~lKnBZ)MxWbP%6{}kn55Sd3ft-W85oI&-x5%}_ z6-DBU-FZya6dN3B`S#wyoed(TUd!NRZ6+-%~;BP(7v4T|Mo5AKIn9K7|-Vy zcp`QY!Go}^kU8ka_e$5f&-w&>iM-tNkklpeX3Uoe_XJ-k+=~iPxigw!SlvD88mfE_ zPZOo`7ksFAHkXO!%LpG&a}0_}h1`V8$Ar}+lU$i~)L<|0{~;3;7{;Biu(oyVbs zpdolUC=&aWf1DA^X8k^?4g8L0*)%98EJ83fx#WgW4Ds}d^#PILXuB1K2G*e{tAUxC z|7sV;;(=ht5oS3!LCeIGjAn-RQx*F aOAG1jgR7lnp(DipmaY>bzZDQ&)&BtB?L)l) delta 1223 zcmZvb%TE(g6o=>bF~yeYb4qEUwtyv!s6cp{5*`sul~<&Qx`0~B05yHYI|C-VVPkY5 z3v*(^(w&m%LSg4d{sGg(M9CzLF~+qOVl*)^-ZK?UfJx>z=iYP9ckbg%q4iyheNPaq z9D7dwex2TOKDPfLW-yU%xJ$kf0d5)I6Z>YL@wuV)6bqsulrTy?N&^$Af_lNgr-%<0 z`2O01dBwt<;qpTR7#)FazK*!z3tuF`&27t~VF00;Nu|rOW?o2U6#3W+HmVo*Vz5n- z)dYLhbZ(J`V98or8%8+R)f|bHWF|eIRON)aBxhyX08g!fV1)T31NC8uh03>=WkppC zY!nXD0DQD|k~$D9r zag5!|KAJg`zavvN8o=ZeOb)8x#Azm4b-cWGB`(vIbV@##NGgfBd_EI}&-PXZEjs==%SELtKuc;g>&@G!9vo;V_85Z*YXf0=(A*W03446E<2t8#j9^wLmbDmFYe9H%3P zDNXRe86h!v>1@#{YXdsLjs&B=02*Vq<|<<@OpH!UCdMX)j$=s}7=cI58e1x#$1-z_GOy~LRWd&$Z<1BD7d#bRsx6vD1TTs(7jnwgU6|8D3N^c`QV3tos4 znSl@D#ZVCQ473WPu`$LOq#6g<-qa6KZ=k0eO&(0qK&Dr!ZSgZ<)xZU#K>Ou=efc2_-O%>+Ao{{6-B?GfcBD40;(&u!G z>ZvY>`krO=o74|jrz|1rfUH&YvZh$h$bB@&9E80J{%s{AA*9IFf90A#aU(@;u*h}p e@|<&=tXA%LYu2w7#7Kd2{p4>D64>QXb?9G!2M0y~ diff --git a/webui/backend/app/services/settings_service.py b/webui/backend/app/services/settings_service.py index b09c259..67bb70c 100644 --- a/webui/backend/app/services/settings_service.py +++ b/webui/backend/app/services/settings_service.py @@ -6,7 +6,17 @@ from backend.app.db.settings_repository import SettingsRepository from backend.app.security.path_guard import PathGuard -VALID_THEMES = {"default", "macos-soft", "midnight", "graphite", "windows11"} +VALID_THEMES = { + "default", + "macos-soft", + "midnight", + "graphite", + "windows11", + "commander-electric", + "nord-arctic", + "catppuccin-soft", + "fluent-neon", +} VALID_COLOR_MODES = {"dark", "light"} @@ -86,7 +96,10 @@ class SettingsService: raise AppError( status_code=400, code="invalid_request", - message="Theme must be one of: default, macos-soft, midnight, graphite, windows11", + message=( + "Theme must be one of: default, macos-soft, midnight, graphite, windows11, " + "commander-electric, nord-arctic, catppuccin-soft, fluent-neon" + ), ) return normalized diff --git a/webui/backend/data/tasks.db b/webui/backend/data/tasks.db index c68b04ca72d2ac6f62e661088d75632bfb46543a..85b6b85c8a2cfac32deb1ad562750593643097ad 100644 GIT binary patch delta 15810 zcmb_j32+=`cAnPEXzpoj`IHY?YuVCRZmI9X#@MnnSOTMCG_s*>GxTW$7Pc|$Zq^0E zh$AFqu}vpYASXvEM-{N(%0iYypm%y3oY=$i zoG1wD5idET$g(1-H+sqifnjC!22VN5$r7U$C%qJCg%w0jz1~yK%d)_$hu!5ukHRq` zC#bfUT$V*nl8UP3slbVfD5<8WoS}J{RShq>Bntw|sJf?IkR-9lshXz(!*dF+Ugsqj zWsasrwcsh|1&$TfYrW)xBGE;jQ4e`4@CwUI>OoIABPfcX9`KTLio{Bys(Q*rzNpBG zdX1+7BXNwPUhO4kWm=JVb-$;aXBd`K_j$^hA|r{kI^m@N<4d%x<~`+LA1A5fUUFKd zIgwR!p7Ks!l37I^b64Pd6mUUFP)E*3O z4uyg6x)d0!zWvag=P!u+3OeV>GcJDJp_Uvw~(( zq9Gd;uZt|DuqF+EB#yTgS>kkKH&QIZ@nM_1tx*Q8i7>XMQ<`N-6ffAes2c*Da8@7E zS<@Ma1e#8B3@@0Bwhbv-qNR(X zY*HetfTF3elx8X>B^f#^*{sb-U^-G1Et9t_S)gE{i(og4R$j7b_>IZYBClIAED_R# zacxF7ILfvl@bD}LjsfOTG(cf>O%YjJl(3==fka?5fR8-L6c|c_l?82<3Ujv@2SsZT(vdthx&eBc8qBTmfc$hy4Y|
lBip~NK>>KMhAbR7#j8= z#4d|c6i}CRnS;rQLlkAZja9Bdy=(M*by1WAFgPTQJ6Aw|tlESMD& zX3{iaYza1_1O1uVZshENU(_l$F&uA8SRu@uaY=x#77z~~Un^Cf4*g3jDS**+n zjG}O+V0NDU@NZW)?;4053dH^`b}sgpu~V^c#ttP{Cj*I}CtgeZP2!IdcP4H~j3wkm zTcSDspYb>1&&3~!|5p51e1H71_@;O=_Gywm8+~DQ%(?RXCB+KiQU_KA1ve(9_In9- zdkOly1iKarikR-HI~PijKu|C967292THL~-kXw1eI${~OSZ2XEdgSQMg{h;ab!77J z^|t_{SpxrO|7~mzW|*4xE!SI9mT`1yO1sIlZl>@EXir6^J-EN_o{)#B-b|tJ8K4qa#c0 z7#&$g=i`7RObqYPFV{G`-rF3Rs=)h~Pu4lyFUM-=D&*7+0u6UNnQ=u2j9a7?b^6E{ zM%Pl>gweH>(KR}i;ZQE4Te^Ej$>|+Wu|4I>TrVRE(??O3K7|4zf z?HTTwGS{46bKV&n-rVA%%&cx_wj32|{NEKJEy|+c z4xZs?Sx}$3X*2QrvCT^Y0|9biFTt;@uH<~->26~6aM?gt z_-g4oE(tJ-CA9N_Xf>u`&V|!_^LVx|*FT8IPFKgSe20b{U2MqdAd4+;FL8KGH%2#~ z&>_p)b0hm_?Jn#e$`6iA#%yOT@1(*be%^vrnNJp zI|*U6`{47Z(^KnW5Ki`Dgptx}JdIv_dJdf?EVT6T(Sgeg`-ZanvpMW@ORI}K=X>OL zBa8W6>1YogJymMMqx<0KGh;gmK6TZ|a9{sOAv-XXA1RN&ygGipsG;kZ3<^UaO6Uw1 z1i1G#WdF$i2j1Ua34AlfE@H*ZA9oYU#cj2=;(>yt<#?dQZV#2q1KQV1FCW^QE#$8m z%@&6GM}|?TsPof>;)_}R!`&DYPymd{PwbyM`c0r5PUn4;D+744mq;#p)|8HK!DB*T z$7A}4=yd@-S`8vIrEa2Y@u6iae;Dl?Uv!(bbS5TN!WM%pJLD65-1DYxG$(I%`rf2_D<&^EmVw#2efLae2k}hMas| zj%9v|C4_P;2wW^bIC0hP;l820_-On_)op5DBfeK-bM52pRAYiX^2*6ykmVdJR6J*wcC)68PPO`fh zCkRj?p?<;tBnAqohwvmGjj zFaw=MPoC%=V0$=`N97zh^Eio@MGuiov#^DyC$FBjPdl+M{J${9+J1F69K0P&YCYC^ z%-im=j;n3uDwV5EpPhm8R?i;Y1+5N!Dxq;_-FoH|ll zXHjcgYa80I^C)q>RpmmUt5pKzLEJr)g#(y}U^q5`tpb9Kts=@+`QXXr2vSd!cRld_ zGR&20Cn(F{BBU!g7JSQN%2`nsAOhs`7#xsl3@%s(_w~4Sdv(@bx%JAz$^x43qN~)+ zj7k~RRV4Eq?;axd4okDTa|z{>%0MS4T(8_Ax;miYSEj_5>$EHobv-xm74;FC>f ziRS|^pDFz*WH1IW!K`L5{1hN@l;1ZrkR5?!)Lhz9%V5kGBHya;Znw=Xs-+YCbuj#t zH|M|@&*!pzR}BsCsZXSoVay5}!}yKe7{YT5G|$=-Zi7-9&5UD(qqJ@tt_w>J`vOWf@=lo!f`j;FNQuVyg4^ zLu;K4SHxy7ULFJ9co`lrRzev}K|mGiMg?kQsB=|uRb7tz;oDpB>9zQ^^TZB3T6Gut zsdD4dMt)qo(>$QAcOL(7Vzknc!Afw**B#)zw%d~SEd z{N{*gwL1bKUImEz2KMz0_oEzbHL31`)K%$X5T2AU!qug2Jg0uTG7!KP1Q2KX3*&<$ zxqSabejJ~KwfvBq!kp9CW9OU@F`VkL@oS|*&RLzahoQ$fhXp)NHR;zVt>KZ?SW*o) zv#bG`xNWgSR5>kle;|Etsx9#+@g>oJh}_iN6nZ465bp)O*zm=MFREtLf^W@?N*AfC88pj4hpW2?dZ*xSW*^38%&WkD zonU>#Y>G~V$|P=KbwfalEOfiLf`hUjEVep*$j0CxqGRG8gB=Watww=8gj&zg9J@t! z=Uwf}S%9@3+Jyi?#6x$N8`nT0MO5H2IB8*k^YWXGxIhTCykUUz$iSCZZ*e|8SeN50 z_wc&RlF%b7kUVq0S3oBo59!b8yy@nojVMGAI_(zW5ObY%A%|$qx90KH30!%h)NP*S zau>3zNPxtN`{hs;M7#t;21*YC6+td?TiHVpA=!XB1Q%l@XaU@)$oe@a$~-EvVrs=y z&9*rV#yuYOpuSE>&$=4MGLT8~5J`+AsdvV8ymmlmu=7Kz2BDU@~*Eyw;&f+2}uJFs9|E?k)nvmUVArd+d;M~2nfQ<%Hhi!(k zHqI!1ZF0AD&Ssd;{_2=T$GaWxqBVD=iE~&6RHDkSFt`~CFJFl8a)Fx&)*X`NYP{*Y z0WR=Qfdoga4gLx~d>Ub69}+Jl1F46|L#e^oo?tZjYUC>-3tj9jkyz-5;a?Ui)+!6=z%`u8B?+`eHe zZi4qS@dea>>WA-MixnHx;SgaxgcOTD%3Q`lq}eDeqo`~lML&DzI;4n77SM#hc(3y{ zNV9Q@pJuN{ihh#f`!UE$tMe6$^*ZmvkMz;)FcG{naPmy)TRHRtKSX>IKiS7?1=l<3 z#!M(QiXZR%YGW=G`tS1RA9iFy&6fsVJyUwcIk)L#C~{XIccwJ#+`swrP_(P8^Hpai z6#Y&hbEdS(8S8@2pOtma!{^ws^7rdAxlnvr`SXLBlcB`jMB7uPHH5=;=R(PoWx*5O zuZEJJrAPmh+apLBe4Ma7Wo9cgmO1|kmSgC`a=5I^r^HRc{6e#y(7IW^-=1j)I+J` zsX^FPZ1QuoWno92Oy-iANDSJl--1_B?uM69xI{dDA^vRS@%Sn9o=JRJ?8E3-?8VrF zv1744k-K8+qn}03M<0b5DgS%w-rsKa{T9j*WVfF*?Ttv$Pa^dOq*(NESzr$%%|^r> z@pc=J?c+@l6DcC1S8PP|45aBN8>AyeKQux^incB50-WE@W1HHYm#K^s`nwLI-?6t) z&aTZF=lPzD)Axf8V%T{WzWLt9jB{^i&bhrS#w0 z2!B3ykxbtQwOe0Kw z`=*R@;PMV4;+*cG9PMWv;XN2l`<)%mXjj6VQ1^m85IEdI88s6!5;Pd>`)e0&TX6gL-A>8CMh58MnG;b zH4d7BS@)y@3$J_0Q0lz-25cM@j4*dZO>uC0@NwLgOPvi1N8NKcq-TW*Zj7s*SoH+5 h^%yYc#q`|FgscvF_1IM;f|YLU8B$jg1F!g~{|DFCvtcF(D~%ccDhIhp1J68Rze+`hV&f=u=m7%E`VB< zEdC;G5hu7wu?1x?KZPlynYpTXlxcRUoM3o`UW6DJhLygS zn_<}E1PxcaU=_~RAPKg4-M7uK75{NU5l-lWRQ$0WG_UoO3c2`69fAFK%&-}|yCK!f z46Crws|}kW2On!Ay$_E-J&tz~De10?C)L%q< z7b)wSpkSf9bH55paDNkwEu7y~sBBEgdfu+$(c_SUg%0x6#GNX;D8x7KQrU zO>5sDmcr1i9*HPD5+n%Y=$t2}7$p%(iO@%q`5-0SLLwhT86*l1aA zlA6$f5QSbV(PFU12a3#1_!4_{>9swm}#Y!SZ+ lXL-%=f~)eK-opi|lHmd`(6UCMcxA1u5wkcspk=~D*gtOo0lEMH diff --git a/webui/backend/tests/golden/__pycache__/test_api_settings_golden.cpython-313.pyc b/webui/backend/tests/golden/__pycache__/test_api_settings_golden.cpython-313.pyc index 3f4b71942ca6b5a7620f9d840a16918640d574e2..b586f810cbef230ad5cc5abd43376a14686468da 100644 GIT binary patch delta 757 zcmaLT-Afcv7zXfpcgIopOZQu+4PB*9qA1srxsA4UT5XnHTSu2ri!w4p zu#gus3dJC(t4OA9f*`yIf-crJO8f)5>O!dN=shPex@j&Re&;>sIWx?!-1jlp52w>6 z&{KAqdSL&$%Wk>xg_cXgdBFfxFf1w_sl3iw|o^HC(l%S}r%q&E+9^xV$7UmyhJ* z@{{~r0X1L*N5qJ{?Z+p3{HDbkXv7PO8}G^8nChs+2E~SbVV`fxqg~18)1#L%Ml_Sl zXcLC6;l*$h^x(P@#MQ90H?3)z!o+xLG;=jIRMc}5DSb3GoX+dH$?XAL4|gX1b7GBt z?s%5srm*D=E+-ZfYk}ttrNd`R$#m(^P}!^DU|R$WAxSD7Irq705T`@!xYAOM1C1-} zOU(vF1nC*OM#oU|2|B8ni~Ak}yfC9p$DwwTwu~tj(=4vx%z-6%g|U{P+1`8u?Y!M^afiBCH=;Uh0B z9PHgKY{ovoCsx4M@dr?c{hgO1W2{wVah=5si#ZnaEbg;F7R$KR*#iZ1cO5hPyNUph zaHCs+m-wxFINoQ4Bs@IzH5AxpP_)M91B!vbBCobj&4hC1=m>SOrlwTIs&u DwYScQ delta 567 zcmaD;_%Da=GcPX}0}#~w-jq2}c_SY;Q~enrcPc{+LlI*zLlILjV-a((STK_mLl#RB z3y{SOX0Zl~2TKIAg4kJ%MQlKEHn2E5ki`yWaR6ByU=}Bk#R+C{0a;vN7B`T^4QBBG zSv+7CFObD66$-M556I*L3-SY5{9u*FGoE}; zhiCBxEk-tos>#>1_cCq>WFaA5V|I_8(m~P3Re8V$PTX9#N;I7X^>r$B}}I=icel|nxRqz(p3c_>Oe#b zh-e29Js@HNh?p|j!OVg&YjUNTImh0#8^e}d7lv$HSLfwzf6PvHrInYE!3hHz=cnuvoNdiqD6}5D8?x+fy?4W%M@2A zCM2|+kEw=~Qb`JhTM{Xr;;&~9A+;S ziu@=0gQ~cFD?#*O_Q?v^N?Svdz;A`oz(Mf%S+oLfzZWGao~OuBOj1lyOjBG1XTXcr zqfbjZLM-3R!Ez)q!~0#TD|UvU_2CCLCOzp9)1n%ah}g@Ju)qbq~82 zE7t!UD+4Rb%vLPyV*|`q46G~@%a0G5^!;`}nd0JQUoMr*Bz&c-7qlXKqHcKCKStQgBs?j%) z1>PRLHqo}1Vwt|iWpaMqF-lv@xV5>d%xkXi3dXTx*diU2qR*Ok{_dtK!qUO=(25jntS)k(u0Krm8qBlO{ylM2(pindu#7 zrivpn>G4&tJw6M%NmEPTjdT?cfj`&*wx9uqf@a=0v8`V3IwJT^iCw2^t_9JxAgrtk z1YK(acI?`_g~OwweY9qq7H!kQ{Gve6cJ4LfuDe(8heda|)-^A7%?nE@fnZl!z|LLg zA>qil=p3&(W<p=SeRYqY70zu`@x?t(^tCF^fN=6vv;?uKQ{<^*O(rW>G?xJ M>2H%5PER}j0T)7bcK`qY delta 878 zcmYL{ZAcSg9Ki3nySdwRYwAqP7q;1!ONY*wFO{O?+sdKyJm2-4dp&iVY-ds=2?ZGx z1pkQWL-;A^gOFYy>VuyO`jTNJ1P0PKjUohtpg_8NZqa@C|Ng)K%RLX=y?ctjJwV1o zgF#Qo&&xmS!is0jctnxk4U&=GW5AbZhI>^e7}uD=W!T4Dbzzr$#%%jCS^hJr+1yP@Ukh7k@{I;6OWwlcvSU(H z=_V<4SSrY}oyL=+ZaRq6Z5%anqzD2X9+<#LpOT*2ib?ovFQFJ#2fyvbNDmW^af= z84k%@s`=L)QLEsu9l|%?sC6I>e#<1MGQeeFpW?L8d$t@g*g~8R?t3bAVK%}>8_9$o zet4|1#js^(Ou&`{lXZoZo}Gr4JPXZbvolJP$K|lI|G#0BaK>S|?kdFUhxxH!GLaT7 z1FhZOHom{B-Pg`{wEDc=-gef~WEr)E!jZuItZ1vU*y4d;GVMwyBdQ6Zgbm7Gsxzj}( None: + response = self._request("POST", "/api/settings", {"selected_theme": "commander-electric"}) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["selected_theme"], "commander-electric") + self.assertEqual(response.json()["selected_color_mode"], "dark") + def test_settings_selected_color_mode_persistence(self) -> None: response = self._request("POST", "/api/settings", {"selected_color_mode": "light"}) diff --git a/webui/backend/tests/golden/test_ui_smoke_golden.py b/webui/backend/tests/golden/test_ui_smoke_golden.py index fafa613..8773702 100644 --- a/webui/backend/tests/golden/test_ui_smoke_golden.py +++ b/webui/backend/tests/golden/test_ui_smoke_golden.py @@ -32,6 +32,10 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertIn('/ui/theme-midnight.css', body) self.assertIn('/ui/theme-graphite.css', body) self.assertIn('/ui/theme-windows11.css', body) + self.assertIn('/ui/theme-commander-electric.css', body) + self.assertIn('/ui/theme-nord-arctic.css', body) + self.assertIn('/ui/theme-catppuccin-soft.css', body) + self.assertIn('/ui/theme-fluent-neon.css', body) self.assertIn('id="workspace"', body) self.assertIn('id="footer-bar"', body) self.assertIn('id="title-zone-actions"', body) @@ -82,6 +86,10 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertIn('value="midnight"', body) self.assertIn('value="graphite"', body) self.assertIn('value="windows11"', body) + self.assertIn('value="commander-electric"', body) + self.assertIn('value="nord-arctic"', body) + self.assertIn('value="catppuccin-soft"', body) + self.assertIn('value="fluent-neon"', body) self.assertNotIn('id="settings-selected-color-mode"', body) self.assertIn('id="settings-startup-path-left"', body) self.assertIn('id="settings-startup-path-right"', body) @@ -138,11 +146,19 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertTrue((static_root / "theme-midnight.css").exists()) self.assertTrue((static_root / "theme-graphite.css").exists()) self.assertTrue((static_root / "theme-windows11.css").exists()) + self.assertTrue((static_root / "theme-commander-electric.css").exists()) + self.assertTrue((static_root / "theme-nord-arctic.css").exists()) + self.assertTrue((static_root / "theme-catppuccin-soft.css").exists()) + self.assertTrue((static_root / "theme-fluent-neon.css").exists()) app_js = (static_root / "app.js").read_text(encoding="utf-8") self.assertIn('currentPath: "/Volumes"', app_js) self.assertIn('selectedTheme: "default"', app_js) self.assertIn('selectedColorMode: "dark"', app_js) - self.assertIn('const VALID_THEME_FAMILIES = ["default", "macos-soft", "midnight", "graphite", "windows11"];', app_js) + self.assertIn('const VALID_THEME_FAMILIES = [', app_js) + self.assertIn('"commander-electric"', app_js) + self.assertIn('"nord-arctic"', app_js) + self.assertIn('"catppuccin-soft"', app_js) + self.assertIn('"fluent-neon"', app_js) self.assertIn('document.documentElement.dataset.themeFamily', app_js) self.assertIn('document.documentElement.dataset.colorMode', app_js) self.assertIn('function effectiveThemeKey(theme, colorMode)', app_js) @@ -218,6 +234,10 @@ class UiSmokeGoldenTest(unittest.TestCase): midnight_theme_css = (static_root / "theme-midnight.css").read_text(encoding="utf-8") graphite_theme_css = (static_root / "theme-graphite.css").read_text(encoding="utf-8") windows_theme_css = (static_root / "theme-windows11.css").read_text(encoding="utf-8") + commander_theme_css = (static_root / "theme-commander-electric.css").read_text(encoding="utf-8") + nord_theme_css = (static_root / "theme-nord-arctic.css").read_text(encoding="utf-8") + catppuccin_theme_css = (static_root / "theme-catppuccin-soft.css").read_text(encoding="utf-8") + fluent_theme_css = (static_root / "theme-fluent-neon.css").read_text(encoding="utf-8") self.assertIn('#theme-toggle', base_css) self.assertIn('.settings-card', base_css) self.assertIn('.settings-tabs', base_css) @@ -226,6 +246,8 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertIn('.entry-media-icon.video', base_css) self.assertIn('.entry-media-icon.pdf', base_css) self.assertIn('.entry-media-svg', base_css) + self.assertIn('.entry-media-svg.is-filled', base_css) + self.assertIn('.entry-media-detail', base_css) self.assertIn('.entry-media-icon.file', base_css) self.assertIn('.editor-card', base_css) self.assertIn('.editor-host', base_css) @@ -236,6 +258,10 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertIn(':root[data-theme-family="midnight"][data-color-mode="dark"]', midnight_theme_css) self.assertIn(':root[data-theme-family="graphite"][data-color-mode="dark"]', graphite_theme_css) self.assertIn(':root[data-theme-family="windows11"][data-color-mode="dark"]', windows_theme_css) + self.assertIn(':root[data-theme-family="commander-electric"][data-color-mode="dark"]', commander_theme_css) + self.assertIn(':root[data-theme-family="nord-arctic"][data-color-mode="dark"]', nord_theme_css) + self.assertIn(':root[data-theme-family="catppuccin-soft"][data-color-mode="dark"]', catppuccin_theme_css) + self.assertIn(':root[data-theme-family="fluent-neon"][data-color-mode="dark"]', fluent_theme_css) app_js_url = app.url_path_for("ui", path="/app.js") base_css_url = app.url_path_for("ui", path="/base.css") diff --git a/webui/html/app.js b/webui/html/app.js index 7a32e61..80a6bad 100644 --- a/webui/html/app.js +++ b/webui/html/app.js @@ -60,7 +60,17 @@ let settingsState = { selectedTheme: "default", selectedColorMode: "dark", }; -const VALID_THEME_FAMILIES = ["default", "macos-soft", "midnight", "graphite", "windows11"]; +const VALID_THEME_FAMILIES = [ + "default", + "macos-soft", + "midnight", + "graphite", + "windows11", + "commander-electric", + "nord-arctic", + "catppuccin-soft", + "fluent-neon", +]; const VALID_COLOR_MODES = ["dark", "light"]; let searchState = { pane: "left", @@ -473,11 +483,11 @@ function iconTypeForEntry(entry) { function mediaIconSvg(type) { const icons = { - folder: '', - file: '', - image: '', - video: '', - pdf: '', + folder: '', + file: '', + image: '', + video: '', + pdf: '', text: '', markdown: '', json: '', diff --git a/webui/html/base.css b/webui/html/base.css index 6c2a127..8caa504 100644 --- a/webui/html/base.css +++ b/webui/html/base.css @@ -316,6 +316,24 @@ button:disabled { stroke-linejoin: round; } +.entry-media-svg.is-filled { + fill: currentColor; + stroke: currentColor; +} + +.entry-media-svg .entry-media-detail { + fill: none; + stroke: var(--color-surface); + stroke-width: 1.45; + stroke-linecap: round; + stroke-linejoin: round; +} + +.entry-media-svg .entry-media-detail-solid { + fill: var(--color-surface); + stroke: none; +} + .entry-media-icon.folder { color: color-mix(in srgb, #d1a85e 72%, var(--color-text-muted)); } diff --git a/webui/html/index.html b/webui/html/index.html index e197f90..ccf2473 100644 --- a/webui/html/index.html +++ b/webui/html/index.html @@ -10,6 +10,10 @@ + + + +
@@ -124,6 +128,10 @@ + + + +
diff --git a/webui/html/theme-catppuccin-soft.css b/webui/html/theme-catppuccin-soft.css new file mode 100644 index 0000000..93fe679 --- /dev/null +++ b/webui/html/theme-catppuccin-soft.css @@ -0,0 +1,49 @@ +:root[data-theme-family="catppuccin-soft"][data-color-mode="dark"] { + --color-page-bg: #1e1f2c; + --color-surface: #2b2d42; + --color-surface-elevated: #353853; + --color-border: #585b7b; + --color-border-strong: #b4befe; + --color-text-primary: #f4f1fb; + --color-text-muted: #c4bedb; + --color-accent: #f5c2e7; + --color-accent-contrast: #44253c; + --color-selection-bg: #4a466a; + --color-selection-border: #f2a6d9; + --color-current-row-bg: #3d3a58; + --color-current-row-border: #89dceb; + --color-active-pane-border: #89dceb; + --color-button-bg: #44425f; + --color-button-hover: #514e71; + --color-button-secondary-bg: #373550; + --color-list-header-bg: rgba(245, 194, 231, 0.08); + --color-list-row-hover: rgba(245, 194, 231, 0.08); + --color-danger: #f2a6b3; + --color-danger-bg: #5b3040; + --color-overlay-bg: rgba(12, 10, 20, 0.62); +} + +:root[data-theme-family="catppuccin-soft"][data-color-mode="light"] { + --color-page-bg: #f7f2fb; + --color-surface: #fff9fd; + --color-surface-elevated: #f4ebf7; + --color-border: #dccde6; + --color-border-strong: #c49ecf; + --color-text-primary: #3d314c; + --color-text-muted: #7d6d8f; + --color-accent: #b85ba0; + --color-accent-contrast: #ffffff; + --color-selection-bg: #f0dff1; + --color-selection-border: #d695c5; + --color-current-row-bg: #f7ebf4; + --color-current-row-border: #97ccd6; + --color-active-pane-border: #97ccd6; + --color-button-bg: #f2e5f0; + --color-button-hover: #ead8e6; + --color-button-secondary-bg: #f8f0f7; + --color-list-header-bg: rgba(184, 91, 160, 0.05); + --color-list-row-hover: rgba(184, 91, 160, 0.05); + --color-danger: #b34d69; + --color-danger-bg: #fbe7ec; + --color-overlay-bg: rgba(35, 22, 44, 0.22); +} diff --git a/webui/html/theme-commander-electric.css b/webui/html/theme-commander-electric.css new file mode 100644 index 0000000..b243724 --- /dev/null +++ b/webui/html/theme-commander-electric.css @@ -0,0 +1,53 @@ +:root[data-theme-family="commander-electric"][data-color-mode="dark"] { + --color-page-bg: #08111f; + --color-surface: #0e2344; + --color-surface-elevated: #13305f; + --color-border: #29528f; + --color-border-strong: #67b8ff; + --color-text-primary: #edf6ff; + --color-text-muted: #9fc1ea; + --color-accent: #53d5ff; + --color-accent-contrast: #032239; + --color-selection-bg: #184784; + --color-selection-border: #66d6ff; + --color-current-row-bg: #123563; + --color-current-row-border: #ffe36a; + --color-active-pane-border: #ffe36a; + --color-button-bg: #174177; + --color-button-hover: #205394; + --color-button-secondary-bg: #102d58; + --color-list-header-bg: rgba(83, 213, 255, 0.08); + --color-list-row-hover: rgba(83, 213, 255, 0.1); + --color-danger: #ffb1a1; + --color-danger-bg: #5f261f; + --color-overlay-bg: rgba(2, 8, 18, 0.7); + --shadow-panel: 0 8px 20px rgba(0, 12, 30, 0.24); + --shadow-elevated: 0 18px 40px rgba(0, 12, 34, 0.36); +} + +:root[data-theme-family="commander-electric"][data-color-mode="light"] { + --color-page-bg: #dceaff; + --color-surface: #f8fbff; + --color-surface-elevated: #dfeeff; + --color-border: #8bb0e3; + --color-border-strong: #3478d6; + --color-text-primary: #0e2344; + --color-text-muted: #4f6891; + --color-accent: #006fd6; + --color-accent-contrast: #ffffff; + --color-selection-bg: #cae2ff; + --color-selection-border: #4ba5ff; + --color-current-row-bg: #e7f2ff; + --color-current-row-border: #d0aa19; + --color-active-pane-border: #d0aa19; + --color-button-bg: #d5e8ff; + --color-button-hover: #c0dcff; + --color-button-secondary-bg: #e8f2ff; + --color-list-header-bg: rgba(0, 111, 214, 0.06); + --color-list-row-hover: rgba(0, 111, 214, 0.06); + --color-danger: #b23c2f; + --color-danger-bg: #fde9e4; + --color-overlay-bg: rgba(8, 23, 45, 0.24); + --shadow-panel: 0 8px 18px rgba(28, 66, 120, 0.12); + --shadow-elevated: 0 18px 36px rgba(28, 66, 120, 0.18); +} diff --git a/webui/html/theme-fluent-neon.css b/webui/html/theme-fluent-neon.css new file mode 100644 index 0000000..351b0d3 --- /dev/null +++ b/webui/html/theme-fluent-neon.css @@ -0,0 +1,53 @@ +:root[data-theme-family="fluent-neon"][data-color-mode="dark"] { + --color-page-bg: #11161d; + --color-surface: #1a2430; + --color-surface-elevated: #202d3c; + --color-border: #38506a; + --color-border-strong: #67c9ff; + --color-text-primary: #edf7ff; + --color-text-muted: #9fb4c7; + --color-accent: #3fb4ff; + --color-accent-contrast: #05233d; + --color-selection-bg: #1f4460; + --color-selection-border: #70d2ff; + --color-current-row-bg: #1a364d; + --color-current-row-border: #8fbeff; + --color-active-pane-border: #70d2ff; + --color-button-bg: #21405a; + --color-button-hover: #29506f; + --color-button-secondary-bg: #1a3146; + --color-list-header-bg: rgba(63, 180, 255, 0.08); + --color-list-row-hover: rgba(63, 180, 255, 0.08); + --color-danger: #f1a5a5; + --color-danger-bg: #53292f; + --color-overlay-bg: rgba(8, 12, 18, 0.64); + --shadow-panel: 0 10px 24px rgba(0, 20, 40, 0.24); + --shadow-elevated: 0 20px 44px rgba(0, 22, 44, 0.34); +} + +:root[data-theme-family="fluent-neon"][data-color-mode="light"] { + --color-page-bg: #eff7ff; + --color-surface: #ffffff; + --color-surface-elevated: #e8f3ff; + --color-border: #c8ddf4; + --color-border-strong: #66b8ff; + --color-text-primary: #132536; + --color-text-muted: #667f95; + --color-accent: #0d89ec; + --color-accent-contrast: #ffffff; + --color-selection-bg: #dff1ff; + --color-selection-border: #6cc6ff; + --color-current-row-bg: #edf7ff; + --color-current-row-border: #8fbfff; + --color-active-pane-border: #4bb4ff; + --color-button-bg: #dfeeff; + --color-button-hover: #d0e6ff; + --color-button-secondary-bg: #edf5ff; + --color-list-header-bg: rgba(13, 137, 236, 0.05); + --color-list-row-hover: rgba(13, 137, 236, 0.05); + --color-danger: #bb3845; + --color-danger-bg: #fde8eb; + --color-overlay-bg: rgba(18, 34, 50, 0.22); + --shadow-panel: 0 10px 22px rgba(33, 102, 176, 0.12); + --shadow-elevated: 0 20px 42px rgba(33, 102, 176, 0.18); +} diff --git a/webui/html/theme-nord-arctic.css b/webui/html/theme-nord-arctic.css new file mode 100644 index 0000000..1e4715a --- /dev/null +++ b/webui/html/theme-nord-arctic.css @@ -0,0 +1,49 @@ +:root[data-theme-family="nord-arctic"][data-color-mode="dark"] { + --color-page-bg: #1b222d; + --color-surface: #242d3a; + --color-surface-elevated: #2b3645; + --color-border: #435366; + --color-border-strong: #88a6c8; + --color-text-primary: #ecf2f9; + --color-text-muted: #adc0d4; + --color-accent: #88c0d0; + --color-accent-contrast: #16303d; + --color-selection-bg: #344359; + --color-selection-border: #8ab8ca; + --color-current-row-bg: #2d394b; + --color-current-row-border: #a6bdd7; + --color-active-pane-border: #9cc4de; + --color-button-bg: #314052; + --color-button-hover: #3a4b60; + --color-button-secondary-bg: #293545; + --color-list-header-bg: rgba(136, 192, 208, 0.06); + --color-list-row-hover: rgba(136, 192, 208, 0.07); + --color-danger: #e6a3a3; + --color-danger-bg: #54343a; + --color-overlay-bg: rgba(12, 18, 24, 0.58); +} + +:root[data-theme-family="nord-arctic"][data-color-mode="light"] { + --color-page-bg: #edf3f8; + --color-surface: #fbfdff; + --color-surface-elevated: #e8eef5; + --color-border: #c7d4e2; + --color-border-strong: #88a6c8; + --color-text-primary: #243240; + --color-text-muted: #667789; + --color-accent: #4f7c98; + --color-accent-contrast: #ffffff; + --color-selection-bg: #dde8f2; + --color-selection-border: #94adc6; + --color-current-row-bg: #eef4f9; + --color-current-row-border: #b1c2d4; + --color-active-pane-border: #6f8fab; + --color-button-bg: #e7eef6; + --color-button-hover: #dae5f0; + --color-button-secondary-bg: #f2f6fa; + --color-list-header-bg: rgba(79, 124, 152, 0.05); + --color-list-row-hover: rgba(79, 124, 152, 0.05); + --color-danger: #b44b56; + --color-danger-bg: #f8e9eb; + --color-overlay-bg: rgba(18, 28, 39, 0.22); +}