From 55ee81fc6d930b645c0d703a6cf7e13d4c86dc15 Mon Sep 17 00:00:00 2001 From: ilia Date: Tue, 12 May 2026 14:01:03 -0400 Subject: [PATCH] Multi-select, paste-to-app, resizable window, polished UI - Max history default increased to 10K, options: 1K/5K/10K/50K - Tray icon uses with_id to prevent duplicates on hot-reload - Selecting an entry now pastes directly into the previous app via osascript Cmd+V simulation after hiding the window - Multi-select: Ctrl+Click toggles, Shift+Arrow extends range, Cmd+A selects all, Enter pastes all selected - Window is resizable, size persists in SQLite across opens - Window position setting: cursor, center, or any screen corner - UI cleanup: tighter spacing, cleaner dividers, compact search, type badges (TXT/IMG/FILE), PIN label, tabular timestamps - Context menu shows multi-item labels ("Paste 3 items") - All tests updated and passing (27 Rust + 33 TypeScript) Co-authored-by: Cursor --- src-tauri/Cargo.lock | 13 ++ src-tauri/Cargo.toml | 1 + src-tauri/icons/128x128.png | Bin 1233 -> 1140 bytes src-tauri/icons/128x128@2x.png | Bin 2016 -> 1725 bytes src-tauri/icons/32x32.png | Bin 712 -> 597 bytes src-tauri/icons/64x64.png | Bin 830 -> 804 bytes src-tauri/icons/Square107x107Logo.png | Bin 1377 -> 1103 bytes src-tauri/icons/Square142x142Logo.png | Bin 1590 -> 1244 bytes src-tauri/icons/Square150x150Logo.png | Bin 1509 -> 1329 bytes src-tauri/icons/Square284x284Logo.png | Bin 2769 -> 2003 bytes src-tauri/icons/Square30x30Logo.png | Bin 713 -> 458 bytes src-tauri/icons/Square310x310Logo.png | Bin 2288 -> 1958 bytes src-tauri/icons/Square44x44Logo.png | Bin 903 -> 567 bytes src-tauri/icons/Square71x71Logo.png | Bin 1226 -> 908 bytes src-tauri/icons/Square89x89Logo.png | Bin 1351 -> 890 bytes src-tauri/icons/StoreLogo.png | Bin 872 -> 674 bytes src-tauri/icons/icon.icns | Bin 22306 -> 20881 bytes src-tauri/icons/icon.ico | Bin 6203 -> 5570 bytes src-tauri/icons/icon.png | Bin 3213 -> 3111 bytes src-tauri/src/commands.rs | 36 ++++- src-tauri/src/db.rs | 32 ++++- src-tauri/src/lib.rs | 87 ++++++++++-- src-tauri/tauri.conf.json | 12 +- src/App.tsx | 197 ++++++++++++++++++++------ src/components/ClipboardList.test.tsx | 56 ++++---- src/components/ClipboardList.tsx | 158 ++++++++++++--------- src/components/ContextMenu.test.tsx | 15 +- src/components/ContextMenu.tsx | 41 ++++-- src/components/SearchBar.test.tsx | 11 +- src/components/SearchBar.tsx | 11 +- src/components/SettingsPanel.test.tsx | 28 ++-- src/components/SettingsPanel.tsx | 80 +++++++---- src/index.css | 37 +++-- src/test/factories.ts | 5 +- src/types.ts | 3 + tsconfig.json | 1 + 36 files changed, 580 insertions(+), 244 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 3739dff..e983c38 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1961,6 +1961,7 @@ dependencies = [ "tauri-plugin-autostart", "tauri-plugin-clipboard-manager", "tauri-plugin-global-shortcut", + "tokio", ] [[package]] @@ -3728,9 +3729,21 @@ dependencies = [ "mio", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "tokio-util" version = "0.7.18" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 48285a1..9261dc6 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -26,6 +26,7 @@ base64 = "0.22" log = "0.4" png = "0.17" dirs = "5" +tokio = { version = "1", features = ["time", "macros"] } [features] custom-protocol = ["tauri/custom-protocol"] diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png index 7767e38a7f33df66ddf8a8de5623684bf1e481a2..a2303055892f6c42c510c266217fb83bd1b65b72 100644 GIT binary patch literal 1140 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV6paeaSW-r^>(grwy>j2dwJP* zQ?sv2T{%2vXX%PmZE>IY=;-7+pZyK${H!lM1z#O>UKqfvt7*9Q+LW~EOJ=|Mb$i26 zg|E}zcz>%*m^r~Yt^C=|n>T0Hr^Rr4^?C~`FaV)L;l@%I$)~xK_AFi`HZS-=m$~Px z)Q9f>=Wy#f%qqY4?AbHh``_awlj~X&XRJE2Eth|hMwN-V`S+a{Gv@SeW#gD2Ht+dW znd2+vL@L93@gVw(_Tpxqi zGz3mwANJgfiLdKkqn3%#fs+rS+$vXo%6*!zbB~FEk%6(nu0hjtr>mvJ{}W3Y0zFj> zJlU_Ln0%VyKRYZuJU+WYMS`#Wp^3NS-uFND{66)(GmdrsDy6ci?-#|L4w7W=sQ!QI zcJMv@l9I1KXTRRXeR*DdYs_g9ycTsa>qpOBlocF|;+jDTm!mo~Lr-Y2XQ z6TR|&j@f>d!?&EZ_bPr_$krq_wam}ofB&+Avy1OvH{E~bMf#lldLi8=>qPHn85`x` z*UU%Cmuv`5pL=P4G`3Xz_(*Q&`ndnba}TSt zoLC`TR#bH9lF0|_z0*QVS~AN{lzg9^we{EE4Hlu+#1h z8|^DKmSo2rKRjFEe5C8t_eV~jezACdd#LN3Jtn_BbMA(ojC#-^wqQ?UtYANbRRcDw zSXv^^unGt;E$v{Bh>x!qU^(cuhw-oudrL;FV!46D7EysulPelF?yk$sJQ?nC;pEYy zM}4+4oj!fqZPIsDhK~%7RZj0=z1CA)S9eaCK>!ClrQWc2>()>%MSecMHqE@E$esp) zw>DM(KYiLHr!QdmSi5BhS6!y5RIf3U)0+K^inofkEnbqm(T%}zVTg_6!UNHu41fj= z#1jm4&c-loNInsA;s5u;X`6rU=5gATd_wp6+odWG4fw94Y2d-V59r85#&dLW7I-i47d<0n- k;+PqM*#s9+RgLSt8yd4gR73 zdz&PCb_njdzLYzbL+J;TR)<~71Ma&=V|VO)qWO+nLtJTBa185rwJeSs#(#sKuJm); zw0rW;moKIE^Gi+ES+e}x_2(s_^VfN@)@28^F)(6>&kh(&kv^#ZTVAC{QIbI*p)hWb z&A;%^|8w(eGWSHZZE4?=@AT^X%nn4z7uxJC3(Fq8*(BUp(mO3{mQu)S&h23p;um!kSn93528AxSKU{S1{FkzP6QfNr zo8EgiM1FXG&-3evUGpaaM?kB}gRg2h*JMOpUvq#U%tf-Q|C|U*~$b*Yzr4i_;vO+~>S|A6h*R(_!WIKG*<^ z4?^&gfAp4|$a;CEoP!G{GFC0fT(UO%c@xVhand+`fm{hb5JY2Zi ztG0TcP{GGv`I|y=EY=8$FK4*+Wahr>iEX<(!>k_sTB(09dcEnD-5&W&YxdoK%Jxq0 zHNzPuw%v^L4DYr2x;t_88nH{%8eUR5aH!j1U*3^cpY}>eYwH`G1BVqJ zCJOL3HVFrs|;8*y<3>5+~7;JbR?PP=MpKunc z|Km62n9HpT%zo81O#H?y@`X93W!I!{uP3e+(stWlV|4EL!K$bAf1j;7d1dRA23Dhk zF0-AMRhdjoV~9QTX>#X|Wu|8z-RaodHz$^U zp3lqp@A)}4%czq8Cb$S3uh zK)ip5<}!Hw_;PMh#X)DWyq>qaI`?$@Gziq(eXvmZG{+L1i(X<9z1P0airLlopnZY7 z%$85U#Fr}h?UdpS?%t)=y#Lz!ea)A>o{;dAX~(-Iy>IsCKKVL()|dT@xL0ZT$T0oU zV*L5i^KjNN<5@x%j(Z+%VELeEoVn5HZd_f-B>Kr=h?0TX1ia@<#*}pv3cn0%l;Cm;XRZXDfhcKts&%=``Tdn z&FfwX&$%bz&GGNvy&pf`IDP5fcYx~-i=OFc_eH%)e>bh)zzod$SmCxQ^LGor_q#mV S?jo?LV(@hJb6Mw<&;$TZGcd#e diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png index e459c41b91f0f4df285d7ef7b1593d9dec253265..156c76e1e0b9ccd4f5855740efa81b39669933da 100644 GIT binary patch delta 1357 zcmY+DdpHw%9L9fJJKKshrHzO=k?YZMX(M5rC`al39Lgd)w)40=avdEuA}TaHjB?#vb}-i{=2okA^gQSEobx{4Ki)l+C@AZLw+udMCLOLt6DR%Z?JZ zE@&yj1D%jvL%UqrO$nQEKJIpRuM=y7^^0@vePhk@4+zK)sJ0^8P`(9SAIM<%k04Ti zCbcPWr0$N^dxezzZSPntXP)2yBEhpJIU0RxJlJZ>$xzU5#DX0b_}QLe<#&kJk;7^Cv&K;k<{a?fOZgy58YWGmX`w;F<3{(VNsw zOA`4+hfjCq%ZMY^DDUhdpPkFxo_vZbBksffeeyJ51fqZb1>68FCGP;^H} z2djm#K24VkdY|DTBy|Cv+HAG#u_8IaHylL_GSmxAT30qCZv=RVph*UZ&#h1GQA(6> zeqFyHTdwG%!;{tc69e&;N^ophsV`cTb*L_7==CD9x06AFpA(G1^1_xL6cqJ5%!cUD z;V>9L0RjLJV5SATj~RD4rFcyZgoO3`^A6Zs)h%!}J-|~g4eup2?a6$~vx5@XXMOn_ zLAtics-I7yVz?iyGM+ElGa0hb{duk>k^WMe)VVEUgV_H{(5?W6r1hiPwp+$v;yBw8 z1*~kkM4J*__wgc#5XN@522j^9pb{dw&8$#ma<*>SY%G(hOHR_k!}8M=W5M8Q0=Qkn z<fg&uQ(G4+>}QWC z^Re33hnwc|mkH*1Tt=FWDH(%s6;#aUHj;B(gLuJ>Rh<4nI>zRYM;gC)I_ya#a&UW6 zl#*`%jpr;2xrU}D?V?JvCE7f8F`jcfj!xExi64bisdS5NNCHow8XB6A_ytdzDb(7# zEeh{7<@Xu|tgVoId0=$(Cx_E29qe8w(xs;KW%A*ciKUT ztHEN{|8Nng|KQr7nr`_X3GI%&u5Fo7$peG!_G#L12OpuHK&nz^LC-pT-ndi+CZ6m< zJF+e0wJH#F&lOAK^|iH2sF*ZS$`xAZ(s(l`YrFAQkQ6Z$sPx&AX>%(v%$yw{qUF`B z?&e1o_)U2JauirkovJ Kk5o8#UHcoq@r^(L literal 2016 zcmdUwdoL__*psd!|vF0q2xN% zsMD6R8LBmt%NUzdq?;|GL5WFYjY)=KE=#-Z+Ee>S``^C*yx;S@?|IJme9rfMpTpi{ zSN+K|CIbNU-PSw#0050i2w=6xwjFRmH~>1A+?>{II@t1?s3huz%WUCGO?rJm?UU-H zh+7Tcq*NeBYN-1IQo#Yfg_pstNk@Uz3zHt7*VFqfl~`>b@kn#l)r^cx{%Q8(h4(Tc zRNu)KY4WtzOhZ__;(GZk*icyQZ-Z0My8GDH^QR{1Xo#Hz2W$0@Ca7Zq zb(rlzJF=?ggA^New0ncOSlK6j%4kPmGuIGxYyJHvOhZ>?_|NHypq*d;IkcWh}jltL^hMgr9 z=EXUULhdrAsnV;8nA+Jnm7Qz4H%j`1Nt<{32-W)4(?Vijo97Zelucb?5n6y#uP%I- zl8Gl6&G3lK6Sc|X7~0<9>Q%#ZoN$xH#Syh8K4}UfV(4~Q0zPhnt&jdDTK!o2+x(YT zhK=A@y^Hhb);fZUM~>jmX_d8g=R)y0<<$kG&x6?MF=Rl*xufs}%Vg@T6mrHbo&8&iY_4pylM4gcR zPVSKBYxYSb$J1FuD4WXP5g%WnFT-M_x? zTB(2OQqQi0qPG;t4~?=(D%z9h%CXYCUGJfg_$LPemmAM>21Anw50-m|_Ua08ZP8^d zi|z3fLiShizDRto0TQ=o7HT*eE=4uhL!5y`v0RS_O(r=Y0JI%a)k`;XxwgtYPl-2=Gi3#xqJNWy+BUcbw{3tl3D zY1V`o;B(yB*NWqQc|jE~&~%m~fFts(Sb`wN4MsL@)Y38H!86GaedT;9%d@mHrImmz z9s4>s<|Y diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png index fedf6499740eea6cec1e6f15c8bd5aa4b9de6d54..f5a46292ee73eb44fa43fed86573f1602096d2da 100644 GIT binary patch delta 572 zcmV-C0>k~t1=R$QB!3)9L_t(|+T~YmtAa2T_GM{CH!G;9pa_DX|Nl4o8Uztk&sbJ!LVwu_^5Hkr8&IuQOIt3N zmjQ$jQPVUGv(%w1fh~8t9fCJKx2IAmOexKs>c)P*zqB*U=ksDZowhrjj(Z(U=D{?Z z%`^-Y3WdUfp=L8i2$Au4i~)oSWvS6uWHL2BV2OcT2YPD)iCKG)&n`z+q;v|ts2&QQw$8m5j zm)pQ63?MK!oUoPUx8r6Dpwk!ZqtoyA4^gt65t#9#*XwPs`QuAMSX%n#?4w~>5c@g6 zXNvTse-e~_j{uNMrILD`Qg=m$)SS=fj7RuY02mzMIDfYwb`Aucm=Cf26#xiM#pE*_ zfV;zvgahEn{CEz;3gAz0z=|^q?sD|6IUtwIW%Z{Hl}g3+6EIN$Ak*peEHXu(tr4M} zymJL;7?Yo{iz2$Y=b0J?@fkVk;9r4+DWW4PUDu`2Xr!KgM5L&Kv$Mo?U5Q_T8&wPd z_Hc-#KsCo}AR;4~JqTTa1qa2v3suFxZF0!{FGfVTKwt95--h2eSPHesCUcko0000< KMNUMnLSTZaBM++p delta 688 zcmV;h0#E(b1jq%DB!7=dL_t(|+T~YIYZFlrpLy?XQW8np)I>v@C;b%OdNZjMEMk?mF|}$Ao88R#X4BZ#CfOvrl86g?*atiRe!rRd zJp)sI%;_V-zw1)*@h_aQ3J_K*d+Ab1;n^$p^J`3A76BR?0DoI8;^PZ1tjyG1{ar>G7Fc>9q@e{ zAx1-qnZZ~uo0|$gZ2d?q=1>iUK+67c8(eXI_m1fZV`2h_k2&hnv(1u547w0Ob8i+~ek>2FV zCk^1Rg@52;N#|GZr}C3vtpDjYoLT^U$UYGhQ6I4(nnFzW+ol+HBHFtNvn5H2&Bf^!HgKW-9=RzUFaVe5bGH*7SsxPw#78jn?tJ!~eIsFCN W1Pt8Zc4E~40000lQ diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png index 38f62c2565595ade0182cc5f48ddc7165e7ce8e3..f87e95dc640be677df90017f75c3e3249cc18c49 100644 GIT binary patch delta 782 zcmV+p1M&R62BZd%BYy(xNklcE|hjdcFR_Id9@x=(=udnkEGyiXs+Ii(wc)_WQk!dqk2xK(lSz!{>Kg zN0gs42bPQzg|p?BWtmXVE55onF##b(II?ZrAqf@UpDG+ zzRv%3tTOC{iXJ%(epXbB-{o@oP%4##+4ym802vE8_j|qGHlZU{VmtO5}9_=*6Abj0Us(uf!#+7!61yHNrFB0j-xw~Ny3 zImdBA08m2#*u(S42@(JTKmZ6pRsnQ69do^2>uFZJSS&`^sq&QoFdB_)Y-3v@AR@aZ zb?rn5TW)^b-KM(%IL*j5v>2kr5RC!^fB+Bx+J6{H8sLZ)i-miy<8aTiF~VtMgnR%2 zAOHj)r`Rz$oDb$WC;Uu~j?2EBA*BEub2X;ZX+HPo$h)R`am_=LmRw z690v@6uo!m&3neggcfn3Qc=3|0}&U3#a|() zbYDT}Lh#pA5Q4byUnmG&xbZKvSrnBPtND7#%v|S2pSo#k+xK)r?m#|BLNe#fnLFoA z&LlABEfE5Q03kpKnBF|jHbh0!-bGQsA#Ht~vYnlds@E^l?0>=Af_D2-K1~I9uu^6bhUi2!fMw4k&)`=d)ztE` zhT$+U9GMsxW`CL?bUF^&F9&1{f(KLpRE`hek!SE%F6*cjaJq&-RFM(lk>_S@C?_gD z1!(=dkChNm$ezK+MnF-UP>B^50)zk|KnM^5{tE#_@XzdSlmsBoxdKKbu+D``v8srm zi2=hbQ%O_=1QSNVfZ?mxgAbdV_EWO~COETlCqw|=?|*xH<9fRAykC70EOW&vmCdjfmHd)}I6?6N;seA7hz}4SAU;5RfcStpB49iQ=lQSM%2ePd zt3jG79e<_kQIpK=0|DE_EyFl3>h#8)U(h5`%+k2!nG@zYso3|?siWStq0r|Tg( zEN@S^u*<1y4j%jHgn!^60Aldr*=K9XpWwuZ$F((Va^(E5)OBo)N1nmA+pYa$9GDOv mAR&jQL;*s85FiB1L;DVw`(NxDVQV4)0000+Gb4(R@v(6ePV-dCvMjL9Kxw4Z%H;(nO82qc z>YsFdsb@X?UwS3VWmmG^MCJcQc?^`-Titicejl5jyxPG$jV_qSOzrOOK2SlP%LzDD zVLEXfmr|LU?-=!}L=_o->R@TCXF*x%&#K*Sdr=g{wtsO5QlhKvVMg=2qOA07a`H3} z_V)Jv9S(=rcb&)0Wd_v>J(;OL27|%KMYrGB*tqHSdYhxs=r7kMMh)*um$&_X|DB8U z1*$i%w>cLqwZuZe)@DLid5PvZSF7wOkM>Mib4LzFs@C7O*1>@@W7Ep)d#J9WWpUk$ zvYicU=YM4i6UPolJ{?yQnZv@tyA&%i&$2yNmq%M$TlWOciG!iB(AQY_L23WEzP>(S zQhdkOczJpG&-V8AYmJM~TxBFs_OhwJzQBzfPDdIa!N{+!u6`$WQJ-*=S7qY>R)Z@L zSO{3aLcl`60u}-m0v50kun@3-)%u<#%fZdfjen_g#hsj-q*ecvvItng`T6-%yQ*lq zJi0c}ZdW|nGV7y>p*~`Wnhq<~lB_ovwfc-%rU7+VtO~4xeu6g$ECehBEMOsEAz%Ru z0ShfPETauVx^=HOeKZkOy;^t?uzY=9^F)zruf|~~b9i`oae8_hH$LjF?`r!PHKOh< zqJQp0-SGwyuz-btg@6St1S|wBV4;fR#iPShQ55CIEV2v`VMz(T-6zycOpxv2ZW!NCtYm!qC1uV4O`VaWqg?wpfq1FHZ002ovPDHLkV1fy= B89x93 delta 1359 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 delta 1499 zcmV<11tj|13APN7BYy?`NklfhJh$v`+L_=dD6cT768kGOR`~yfxR8-I+Dpvtw*#?tv7whh3XPDW`i2{n)**))$ zzfU?Yp@{bOzI*d#_RSlk08W76769S^;sD|R;s78HAPyi7Ab$=Z4glf+;sD|R;sD}2 zDqCAlb32>QwQJf{@$U$?5>74RT)E=%qPTytFd|vzeNhNPHi-D0sBwrAB6=_w+_`m2 zADyZ=ad?WMFG}OJ`XyXnrmQ&aJ>oP79|tF`T$cCDz2WHH@VD&6Njf+ zr17)MgoZ_&S^8kww2p&Di%6E8kW^XXP!E%icn=1@n~zGfrp`puEr**vkT+m+geEmQYc8wHBePyYcSIisY_HOB@qg;qC3a@jGvK&M&IJP4eQ*!**tr ztgY&NFi?4F5hwJsk?B;tF-nsh*U*1CGqg?dQJNw#apI4oC_=L}&#VfVjsyHpLf@v= z9qG1D)~!1qLAR(UVn}dOUH=P7TWt7?I5kBbEy+e437yY{9H;u#;)@%4{p+v#m*cX0 zn1=YomWxAMdgc4e(w$Gw5vL+L9i6SOt3GKd;+*vHLz-ER;~^&xIZyW4h&X@1PzBI_ zaZ+!v-KP1t;l@JJ;#lkabQ;d#lXNTp@?2+aDXnNt1>l`u{lSedAN<(wt30mIyQB)a zl>B+RW7MKCqCTNEqf4W+EDpdPRqj-b@ag7PVU4ew#y;6UWdG-*Ge$wwt2XQJcrP#QTt>5@;a9N#+BcOk&00fd_0K@^r z0mK2sQ8m=7gcq&c5a;;q%|vMBhB(K(kw-^rvb$^V$90|5!cP2rt`N8Wv<#jc=w2J- zX?_J@7@jhT6@d2eqmTdoA+~Z$t+A5HME3u1AY-wN&Ao{v?JQ~*}=$RYO0TP<6; zg&u&;CsqL{GPT;7Txe|NR`r&c>D2lC&|q;AZm_e7^#IJB-rIAbEY?#xsw#d~_3Zg< z<%`ezmqLARtguWh&GBNb6dM=hskUr&ZbjG87XEh*c_I!x&XG*Mwc_|$`E`~F z#|Ejz9mzER^L%$mtHg;H99t3(M_ygOIf}dJ%3lEP)Z5lK%5wLv3#TTFsl+A<~E|OrHCnZr@%zjQ^)Rn=kqH2vDg;k0VW`v#QNa zhNw2My&X?B)&A@>WC6-50C5140~H$(2M`Aq%0IF$^R@g1`ELLK002ovPDHLkV1nN~ B^PK+M|c{M&9KZa=4Z zxVJrI$@@EDt(d`u&~T@1jJsZI?N`~q;`X~w%C~koOlR9=l;B{HbDDo=j?9rsU&Utj zYf5vtnr+o7)|ksIFeOaxtjs-0KK=E!+zEUYC5#GUAjptp%W(XF*-b08?mI8du8Rq5 zlyle`RhxRd>}KJdiJam=lT^0xZNHiGZ_|mu?2AV~sa(-M|GCa+q2c6{M_zudnybKZ z@6G%7|8HiPEb{rrvXRSWZ`}6$yu3VrKfixQD`Tsxs{>_@n*}FM41d@6SaQoepL`Xy zSzNVSwH?Q;)(?T!s-f-Fe?OTYnPsE=3yu(*w7w+wNZI?e$e6_o~ z`{BEH?@n~&e(o>5=+*|`+a^D*8!Aovy1#7C;h>dUF24U>EE&KX_h{F;_5IS;*49@` zj$6M!7glf4eDFbB?oY4TOYdLH+IlU+-x>fGixT_vCFzI2>CujCf6GGyM{XSR0uoB$$L7l zqtso3i+v+k*EJL6r*>OQ(tq5V!gHy9lE>ugyQcLKPkP#0`y-w|e{R3|(L>&8sXu%9 zy1zdXzN~hH=?Ig94hS-Um{>X8n;wcyW#m>5Yw~tkDuAt`(0A&=DobSR(9_bOS6SZKIimzsbz|0#HM6ceq49b^pWl1<@Uc{y?QnG z-20Bu_XmXTy`EiAKqy`mwhcGbd^W zPc{dFFXxr_2U@+G@$lD_&+jx{fB*XQ=~L5{Cz48`tFKn=-@3JQJ>St)Jzu|m-FRe^ zhwr@JjrTkhqkI?sOVDDuhQk`Lol_of^YC`lxiq;m!YyEJ7$}w9di^zNmD+7ji8jC8 zci*`MUU6J=a}`tjs?YznK3OEQ{YX%Jg|@c##C>HaD(5du`gHW@(Y5Ec`{+%1yiCS? z>JrW4yl;{jlYkJG^Uyev{S(VmCO0-leD+97z98_raOtgk_v#-0{AFR0)e* z{$<0fue;(`hD7*Zp|M+7nNW<~*C=y2|13r-yIe zyt!UE<=eLD8`&%)TF3FWoDdm&sowsN!Ig%5wIv^ N@O1TaS?83{1OS`TY0Lls delta 1423 zcmV;A1#tSY3grusBYy>~Nkl!!D47C4EI=$kEC9p;!~(fwuR<}^7*;kR>QPPb1#ym7-0mO`w!{KXI&rErKZ~ULYH1Yq@=A7{oI9EQK*rMWSReBtx_Nsu-o74C1pec@k~jPnN$Z6-(h_ zXmZ{;ZUDprlaK-=lbQkyf2?Iko2r^3%kiP9L9Avld`*p5Gq0TT&9{U5kN(tuT22eq zP&qS4j5od#?e=W(@#XG?PDflEdn#q1l8jhC-LLi@{8AnCd#3btp2bd$H8QTeu0Lnp z<&WB7;kQJrdDHKk@=TcU_`liLd2Y61WU6Ybr`C*1u>^IQW?3Gif59iM#$IwQ&m01l z#Axi}5S2X^0Ac}R0b@c6-s-bd*ed+eCfjctI4)$PwiE@?a)4{lmf9DINy2y zQtSLG?pso7zL4lSf0n4OP;;@#K8uLOZ9j0_0Eh*M1&AeycC+Gb5V4qOB32p6m<_{)cT!Z1c}$a!z< zt@7 zReDGiw`HOse`T>A{$A}LA4_FS?t+<+ey#T03*~w^aPYsR{+Fn`R09VizP5+wzp$JSF2046asME0Eh*M1%Q(r d0~r@9&cBiI&ExL_Mx_7%002ovPDHLkV1jK`zV844 diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png index b38cd47ee14743fd23790ca0de7958fc3be17df1..78c0ad8f117e4b98605c40b143e49373a2ca5305 100644 GIT binary patch literal 2003 zcmcJQe^3%;7{_T*uv|1JK@0rRwahg|QzKHB+B9-bY%+8vXcHBGh0LEADjuFgBxRLT zQEIzPJu~H)NfJzAR;*=MGJmCk$-P-MTw?E!{?tSk0z@v&Q1i+lH|!okuE)~sl74f{Gn?v;<)Q~ zh8?ljYeqXxpZliZr(bg~)4-jLZy=O%HdY(PG^_{OqhZcVJ+kgFwjf@v*J* z^ef{FIFAmtoj)%q=f8I80(wHy+_ej<85@(yT|{Br%y6v@93_Ksr%%z%Q^VELOtBp| zwV^>(8mn@(GM0LEAJEi404Sue;$ri zmU{`_E{p1bPY`BzU!>lB@*bSm-o<&6C0cw7Ht2q;)-p=N$e>ckYedcL#JoRq$;=fk z@?(y{@bVO6Lm7@a0jp1hi>+O6Y=E=r!L65!W{YyLzUyl8o=?$ZEmQv8v+3zP8QZHz z?hDO0JJ~J^q#I{O%x)Jq-CU|U=Q1Hg|Hz3sHVGj`G@>yh_19Kf?}T~w1Em|5KJ-&j z+$Tzy*R?QzqEwf`je)6XwcT?dnX%krUYlPtFsp?zZy8L5j98jW6T@H7Opfu*yFbRq zT#lSbbI7Tg@?2`lM%35nlwOhL;|D*`7h!T3#ZOVWLZxy+%i(pHCMevC+Ph5ZmTt1fD>!!c$Q6^vNUlupnBJ@%>!W z{k!O}D1z_(Wndj|vLDRto)+U~;eY#=2`m_Q)7=>>7+!BeiyJqpf|gVlPRncf!! zq(lz=l0qiyie@ayE21Sq%@3M(UX^QZ*dY}B{OnZn{~`SNoi)`JEMeQLK*|%Fw8TXH z4~ISI+ZwCtn?kPjD@s3)zz^kYT&%dAoE@{QKU390N>^q2AO&B$wtfskwXLBvS#>iQ zjO|9ICxjUBs><&Sp02J%dq<}ODX6H({m-cW*EBpELw}k^WMJ{QApJVnA(RYcm8_;{ zL}-7-Jq#+Y+i!SuR9YL6d8^>d(m{hMo_bL%7Uu?IX_(1(vQ+tdAg3J%a!!#B*;|fz zOh&F>Te3JuAHo3HI@10|#BQqjU88(Aq~;^Aig?B@CI!md#;q8C@U#Jsu>{=a~GD9mPJqUs=Rr~uV^wY3c#8NX8Mlv$R_o$#V=m%yTdR0 z*P}l_@I2?BbgpD?*v|VRm>!<6r9H~c>*E>nTW-Svr$#O_9pU-5D(X6mdNowj^K60r hXYyZem*VNi+drT0!%^SEf~?CL5E6_FYTtL5^&79FSB?Mx literal 2769 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 bzDYg+%|Y8>GY)~)00000NkvXXu0mjflt|GQ delta 690 zcmV;j0!{tO1IY!DBYy&rNklvcYwLQA>x6|V3ANPrMefPyM` z1};ck5mLp0SLh3X6dr*K+z=e-9a;$vpwa`SC~+Noh8+hBr@{Gfl9q{b*f`nW&gcGP z;DpQ!E*V=bq(y+*ZD9bCY>jzn{=s7GFT>o*+Fy<~Lm5Cz@b2?Fq42LbkHkd)0a)@l^Ugnv^UF-=i=XvC7C)(s91A%3!P z@cR26ht?1=6qk3BfSfvwcH{BV^=g&;vN^`ggVj|EbY---z(~=}!!dAU8SNx3V8%@! zYr#BvSie?ga*g7farsJIk8JPoZWIZ7-ssxWf;S$}#S7-FgHmaa73@wfpTF>a*p(iV zP_bBGEq^F+{p+1S2W##&nl046Pju_1y?m^p&R6R+Q0HyASDD!b?3>=*R7Z-5!)AgZ)1Uc4|CFvn}Z Y0o!U6g;mshT>t<807*qoM6N<$f?2UtoB#j- diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png index 49691869d18b4d4b7d7ac46a7769c61b775b1a48..f5ce32da70530ce831847dd320d8432481172eea 100644 GIT binary patch delta 1499 zcmV<11tj|L5vC82BW(w5Nkli-y2KO-hIeZMO|28&sf$cH5cpJ7p!k2-~u4XSdIj zY(X&M2Oumu`_1|V$IMi#f!7`81pg3;js7`=bb(ipH~W2B;X z>eQ*%wj)#S^jOxTzjV-Z5*r=H-4$|Y*gR^JPj>YLJmEFhV zcWjk0K@&FWUbZaTe1|Whw&AqO8)?D@AZ!4_1|Vzz!UhPN5df1h2NQolGSDCX01}rk z61Ms*K#P!Mmo7fZ5FcOJkYC^5B6M&*{bEW}YiY#R(J&Udx;A|tzsB@V{AkIASd{I! zKk%1!jyY8SmesE;y%+QNC7#a3Y|-@!J`n z4C4>nG>0|Du@GO0+c$shcKhoSCr&&wK0bb+fAF^3?Pj-b-CBtsCV3+k<>#>=e>i#a z#S)&8#%DM}H`no6Y9^#A|GOegJ=Gv14m&IXyk-9?c-; zl9@~YFT=#V(k(N~%q2-CCMFtNW@5)iQl}Ssy}q{n7sk!(;V+$)KA;OAYyiRrAZ&oJ z0SFs_umK1gfUuGL_d)uHpBO)P?p)*Y<;yPZ^n?%TR#*3Mu>fJC-c@VGr^YwGI~!43 zH<7+(mJRdQhY(t}j4L>|Ajt-f;{569x+0}~N8>Syy(J4I1^ z5C`dGTzaQ5?BDm>lc5zw8gu&i`t|E~eOFZ2sHug8g=*)qU=j!$u2YYlny}#j1(UD> zE0d1`50hvD8Ul|Ae%Cf9Hdh}>EHa4~zVM|0!*y^abef#!3 zV_-WvosJ=sEC(HvE(a%*yaf$^2pb@50K#^VHH?5ADB@NcgRs?2+FO{pQSg*&2w|(w zf;@BP%(LBY_w6)IEl&E9=XteOCd#tBw79tVhqwt_oop_|_4V~`EYxcjCfm_yG@9`T z@gI{AwpyB-n_G>$Urhq1qQW+6G9zGzyM9p`*Fh!d5%e)6)}i_o_+IuZ@k3ve9TX zyWQ^m($Z2#+=Q)me`zNsCR(ejt1snwZeh~j!b#IIj5nLjJMpw*BEpu`?DdiK>eZ|F z;{IFXz^SOPjZ)ky4Wl60*=w_+CTz8p8vzKDLkAL*yaf%DM+Xj*yaf)EG6xrbfUxbJ z%m@aq?A<`v_DNv`yCM}ggl!)Vy6NfZV`W*sTNH%@a1cXH(<0`1DDE$|+wDue-LO9# z2-_Z>DkXDsbITF8?;4GUi45YLKFi8H&$E@4m1VnOgl+$9MBJ8)0fcQ7PfOW_9We-- zgAjxbK-d6;4M5lcgbhI00AT}@tpW;@YXTUPo&pV#$rpd^|D?SeFaSkSq{6lj%Cao7 zEL-nA-LMp4aPmCwrfHfB+uj?GxIKUV{Q2hW?CkMYtCd@h@bo*iDr#|iF~at&u;HFY34&v7l)~l>1K6Xp zQ#&?*#A}n`2NRPP2Q>ml0Fylj7?ZpO2pH^=e*rS)$ozn&sJQ?D002ovPDHLkV1nTS B;Jp9< delta 1820 zcmV+%2jlpr5AYF?BYz0DNklBy8ut{p@m$ z+AzEP*8VJ93-QC_9HP}FJaM`HllEu(dw1?0zF7X?Pd9Jo{?@HH3ER2e5&Br8HqQ3G zI!#CQe%>_&n~=6Vh1qO3936i!cYiDTKRUhrEGxAdwUkb#oRtlpyjYiPX`0qUUAYO{ z7h3ez+s`i4sKw}36gJ}n$C%Gvj+>hgKgR|HpNawq8-TC@2pfQ~0m23#YyiRrAZ(xO z^g9c_gazTVzw=o}PNX1gPgB&6%RG?dhVh4n+jg?xB+VB5x^!In(nciP4 z!jN*89hL^)xyrAUQy9mt`^8I(@0D{s%jHZ*Yd2wQO}+iBE48=Zp5FiC>#Mt$_T!kQ zAZ$(Ef5wp3ARAqbpW-l-IUmLr3EMU}JWT8N?oF$cb(g1gHnynAeGXDsmyuK#9)vz8V~d0> zG(CLq^s4^;v*VSkU+;Ez=AjQ|CYvME=4|ksau~)K%XskKicQ#>o~l~cXFq&y_Kc&k zo#!86YkFj~8_?|vQJV`NbyXLczWU&=Z~b}o-rimu>z_F?ZEku{&+U9C^uPM$ z?(-Kf#+~{ILf?d~S<7Ww-@l($>#|Fgu(c|OX$pC<2z~fyU2c~uVGC`~z#nH_*)hE+ z#FQM5Mn52mWp$p*4ps4yt*a2WW-t15wl!PM<#xq-tw|+kb;F*rB2SO3;>1mpOamc* z9=C1?aTsE(gt=Q8Ib8e1f6Tj208oE+Rs$0sK-Pm`@(n#@|4!g?L@ zg$tq2$=D)c3r)|zKfS7d_S2p3yzrx)Z+3m?vH`k{4hBD)yH`iS&*$BEcK$_8*c#o~ zqpr`s{jK3^js^%@Q*o>N9Jm1uTQv$1-cb;?(Ejrm7di+VfUp6=29b~&f5zic#}+vX z!88|y&4msK+XlP)&*{Sl5Av$(%GgO0C!3W|-^60LdZoWS3}ts0gss(E(7IZC^>;^q zyZ!p=?xoA!xIPiK7R8dWG=;oa#QrzG-uv<1UN^5}lkIyTY|Sd0d-Qu>iQ~oHFs2lh ztx;2XBb(1-A8o%UY+JE%e=ApfnqsaixXA|zP3q#=iY+~|y`t^+zPw#s2~X|`3}D3u zAZ#1DOddiUh8XL%Y@Q{zdo$GcshsC72G7`pZ5tdOhLeMP)9Pg1cL;z+R%|(>oTvJm zByO-NFUwqBm#L54=1x7@*sv{^cjEqRe{VM14b!w9qA?w?A>YAH{Wu%LYPlSQ?fiGg zr|$ySuU|X9bNBGYqvH?eb>-%9AZ&)ZQmaur>cd~^f8V;5H*Q9?Wf1UxNb%uf8N`b1 zoNnICCaS@PI$NoA8~L2giVX-p6$O)U12B`11{RY~0}U8}bNd%CX$cs-0Flf90000< KMNUMnLSTY*b*a|? diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png index b688bdafa5356ec9b33fb7fd5514318d10e12c8b..1347dfaa7e0ea8a9a08c35fec8ba736fc75ef20d 100644 GIT binary patch delta 543 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 M07*qoM6N<$g2_Rz761SM delta 1207 zcmV;o1W5ag2g(VMBYy;uNklvukL?UPf3X5r9(yp5l(~?;Y2tA!LiojXf(oubbsUSe(eAK4@Q0LLphf( zQ_}Bm9@7d@%AB;`2^MH`{rvsfFn=-{3B4yc97;k6VlW`npQMFFD7BF!!&O_UNr6Ig z-;W<}zw3Uvf8~l54wBs!oO+I-N-+*hlBmp*0U^+R!s9~351gu*1c9IwB=#LvEJ0Qb zg8?#jJOP{(1buc+(;6neZ#3@)P)i&hQYk^w!*j0N|;P)KmjNw z%Mk9}*Kq6mlK7aPDz)izIFwj14CccR(%`~sX$j%xw*}-mfH59SAS;CxQ_XE91Tptm zH`OWyurp?*qeq8K@H~_lOp*jJL|FQ`z>PBK)M$TnT%{7Xp4#HVr`hTkpI2}Qu|3s#nR6CM_*kXKUg`6nKu5iESVr~ZQ0`D zg>1Ek(|r@TkfVC#81|Nzg>%UM;3NVu%@CIsMK~tV?P-~3&q((&5>0{Jw7hX$e?9D-iw;WQ>}{o~&0^XGbR5LxtJ%Ms7#nIKG{ zo0RK~;sD=OnMWni^0GS*e^YJsim)nyV$Q1Rya{B*FqqqS%8g&|n$4c8>sZed@_0h# zR;->uU*v;6u zV6wDnNNWiW_giu6-!yeW8({Y}eK6%deGa!5AG8~PS-W&pPD~#noCqhviEzRie*?uw V&K=DI@7w?Y002ovPDHLkV1j_+T4VqK diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png index eaf6669f381a33c11700a064bde5e4a7b721e635..59e89106fdff60d938459e9d0830099f0fb62345 100644 GIT binary patch delta 868 zcmV-q1DpKE3i<|+BYy)yNklc!^K%~=mcO$!;|0Nwrty$;(~Ls>CT4%S?Qa% zTrMrceO6IHHfoD}-`6?QH=XMN`7js^b}wz^WRM8Cy}fPX0`iB$!OLVaXLkb5;zKHx z`m*2em5KIr1b=&;r=7}2JqO|@?$haX;7H!>cEWKSOSdzbOom}cvN}S!$!4?P@xw;$ zG=MxJf6f6|;k%Jx4KObH_lUm!Krrlbxm@7+C!f!Y)oSITy;i5w8F#x~H|%sF;Xj`+ z41b#%PVmWVlkJ@Z3%8!lX1{bQdU$v+($63k(+ZeFO@B@%p*nz8t7V(&<3HCOsZUmB z9GoiL-QC@6w_ERM=gzlHw(UtNU%`L-La;!jBjhV4z1JFy3CMt}Z=8kpzUe81kfG^x zdd~t8OPyB%Suv6+LMCL2kO`TPDMF5stSeY-#J*p4!WSFr8X!y5bl>8shog%&aiomG zl#iP8y?^1yUy(vqDE`c2oGwoCFFjrOLeO1MgiOd3ArmqoQ-n;&RK$=gl}Z8o(e$eY zv0GMSzlYv(rm%(UDa<-X=(URigbjaoTx%DotLS}l&zB5Y4Tr;@Ocx@twdBZ3o;EkG z&|MHRMaYCq$P_IcEgUUe^vmi-qwxmI>ey}xK!5GRKR!{v+${mcV(}4s8zj?3hzjF~ z1*Qv%kO`TPDMBV>LZ%3rkpJs9aHNd!MsY-qz`=SF2kYtOu$SoC1zu;Uj7B3HX96jl z17xh>YPBjBi-m>H^?W{GBpI^0_OmRKQcqTd%z8521tAkMMaYCq$W+WA%Y<-}=UCBm zPhA2}iU&R&dM!7bjf?`a{%;AVR;#_kr_7p7v$-C;Nh2ZZ0_{JT{woyfO}6UICoA%mVuX`8E_B)+`gT|?|+b~FeUoQY$8~L8Srbb zM&6r?Z?28t)-MN;BqawUthubgEWB49`WksY)#NlSW`7b3MSSxUGhiPfJ9l)P5>_ul zQ06XVf=EOl3uJ*TB9H~LKo$|m0$D^LHw7|Jwpjb$8H6&0CbUXR>X@Nn# z4NjjqgG=qU(q#_tLFS^HsFV_<=27JDY5{rv8-aeHgG|vbI~~ZnjtV&_ zWKDCkWAGukK6>)|%=3oB_MJXV&(xd_b3ewIochT7d`x;f8$*8aqJC=Sl#XYfGM;qE z(|@^(J9hZN)q6k1F=`IAtFjwLQ&>H4>DC1jXXp*l~)YNDJG;G99{)j{S$?#!}z_Rxjn+qBb0-KGsHo`s7$w3-H|hQh0` zy4N$^k3NjA)MO!#iy{?z7gqCwCP{P~Kz|ac3wGyRjlyd5HLOMnt0k;H;bHY~Xh&PO z>^3cI<%O26y7keNnaRMOeOf=BBq}NcbZKGr_O{)9`<>nI)rE4x4x|;@n z+VQ8KB+oy4UPT-nE&2{C%_g_n7+hX6tL!INnyscU{NA3;;w22d^OH|gA<4Vz%YQHM zOjr#(i~BQgzNMdL<3o>D=t)(ll+Frd5rHg_MFg@y7RVw3Ss)8!kvc=3XOp-J$2tGn zt6HuuQpKxLP0_}5Ec3l@qGzRcVTgr5nd~&VOOvj%bau^fRRdNhOsY_)=E54& zHd%gf^NWq-{6f0Ua)V~>A8c(HaQD@w$Xt=A09qN-f;<}?YEXK#PWt#60@7se;r_;3 zcfTgoYxK%4ow9LVswr_sl_MBYz530~jmKVIU>e3EG+rKVHLU=vXE0)$_%1&K;9oXa rG+E{@1hR-g7RVw3Ss)8!k%IC!Ff{JI>^@ZM00000NkvXXu0mjfeY=Q- diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png index d54fb8e88706aaf4c2131d51042ae9349558ef5d..54f577319c215c69a8a57d25a99bc7a5cae9a4cc 100644 GIT binary patch delta 651 zcmV;60(AZ82BHO!BYy&ENklC-`VI8ZgEz zLWrLvi7vGsr783b-}l}9e(&UCG!2B50fL1+0l@2Kvw1DC(|^fiGWntF`Y${?CFUq} zL3Zr``FqZJ03boW(4s+wZkndKep*+6ek#-H)Iejv6}V(UB#NRCKqmOS#c}-B>-E@I zeT4q$bV^LqwD9~BdaF*S(=D0#pQyWQ@+jQ)5$?o^HuVQn;e=EE{K6ualavd@^%!|qA57J^)iXQK7;pNe8J zTT3nJS31$*aIn$X6=U`4VAmTs*_-au*ny~BK{a?Afq(q>6h#5KJCNCI#+J*a^fay_ z;shTKhtYgKk8T3Qwr#!N@6+S)SSyghU_d<2i*L3gi^U>D6V~k>FwkAj5z>IGlY0a^ zZvY5aS?gN^0mC&>O!Y+VUBpUD2M6jAQFlPj+wIo72@upMzNpw5GA@6FU{^7sfGE<~ zHqsb|A%8`xtQCm(%jqFVqY%e&6lqkXQKjEDlE%_L19FM-_&rM$?EnJO7*xz!ylt+< z6=_sJt^ktVBDKPWWH(xcCp$36>E7a)^KaS lq=H=mQ9u+B1*A~^0WEwnwo9aY%>V!Z07*qoL delta 850 zcmV-Y1Fig`1?UElBYy)gNkl>Fp2Z0(_+Z`0kh?7~vAq3_LaK7KQA ze=%@EE>i+2f#eLN*|fB>f;dUW&dxHoT4ZJI7yC;w|ofJg1UTccgN=>f zHF>fi?v_8T0~WhPLO~e6)4Oz$jOAVP@F+0=$);adZPM*l0{OUp=wjD&(nu(XM-Qrn zrq<%C4t)INfqx$$In%G@tvrRiIgDj*qT${{BQo@n)OH0?1dzSB_1HAT)NxpL_7cc3 z1tM%R)-gsH3JU>}W#fs7h(=@eThi|LmEAcbAY}5GE}W+`#t`NLM7dpQQ2->*Uv`#d zeAo;_)N#})&9ptm;*4`=DR}WTY8V4Sx<@V%VYWyf!hhf%c3*T%Pcd2|sy9&nsYPPJ z%na3PHS&EU2L$U@_c}eredfBXgLxKv8gkzU@7~?1ITUw}XB=VN>g1R+YmIis@r)x_ z3~OmY_IeOYt&DB13PVXR%n3-!C~<6)XN%nrYist8q?wkiV~R2M`)E(sA$amIny*yI z&zH62ZhyD!-|s(cFO&n1wbqd|P@KZ@@53owuA*OQux1(Fm>{ARHlqb zZ%bS~Fc6i1M++>CG`sbIQe=yxBaOrW1k?Ndbev-(Pag`1%G6C7s;8569r6bQWoIve clt4z;KM%VqE4g6f@&Et;07*qoM6N<$f_q<+*Z=?k diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns index ca923a5db523ac184695882c0bde84d25e214728..3193631f75ef30a93d1ddb6ed3043555df245e27 100644 GIT binary patch literal 20881 zcmeHu2|SeF*Z7?=%-G2;F{Fl}BC=!~6)mKcMAq_+2-!l`u|&31RD@JY3P~zE<7+KN zB`LB*Xd#4bG4C@YvXsXE|MtGW&*ycAx#zj}+;h)8+ufeAG_!RCK>MtvnWO{&ocDru zYi(tuK4RLiYSDL3@3u@R!l4#f`GK@hhJ*emDg7B_A>+bG-+u&2y#1LXj)Vm$qd23> zzeIeJ8;H2=t=rCG1mdSJrF4KKD}aVZYz+6Y8==CJ9s!EcVV)*_Drt6P&bs#GZ3+P8(b2VZ zcv4F!mg8J+K>3m&Nz%ZPzGkkAecfEWuP=4ES#O2gt z7i)`*X!l1h6<9*iV8(8dF6KZ#=d)O|>^|2nSzbnZ#$KeKkHIY+YyDNU7&$&BW)Y&0 zI_!O2FY-bt+sTaf3x}h3SBn+o$V$Ql)#UEkycMM7Xo|c=5V$!V>Oah z;W%h<0oz6m6602B8qh~&ZL!qhtzIUj#9Y&FvI7tfbutPZ)0cs1DdPbP{t1;SbCSw_ zT2P$=XBij{1_4ebomMYSaSPV7XWhxwh65qxBHPT67hUq@_M5=&&9K+S{S~y!0I3_C zF3f4;3delo&z8D*dgAJ@Z+$%vBzVKDt<4OC&_cPZj(2ZC8Q6NgZA{FC$vv%*xzByA zlqC#~fO-b?+}n9uPH~2ubWdxutG*}sucev42Y{gg0)6wfqDrWa62wC&CiT zK;yTevsdp<-DiD}u;~r$*LSit%Xg>hY8b4&coF8$qFp1+kn!@WxX&7w+!emEN|`tJ zh-!*Hjr&yQRakgms$=Nyj{uW?-i~`LEAab18My**@1$m_Oa& zThEU~#XMElVL$mmn=?sI+>J%Y%rmg~D)n&~?u50+>WDtTcTZeHKUFvIbI?k!pjV$a z+^V%g@{49zJ+RVT&E_c+W7#s?DtvYuD$!D?+}>{O#RdX~0WBdLd-r^MW}7+vl^U+D zgqofqX6<`%tWkXlb)Rk0M%pws=^YsD=t?-K&oU-Yum~Qw?!7G1D;%8BxN*)cZjXyv zz{}X`&d{yH`v5oJJGo4>GZP{%iaC9p1E91@-%Ih~bBj;=Q?*olcV#M}YuRN-2$rJ% z;xLdVkG@!WcEn_A z*ka=2FM4ey)~N2r^CQQMAy?XeE@94>hth zkA7cip^Zbu_&W71SvZrg<;?-jvU+q6Dpos2%`q-10BB0*8y9H}2AjM(G;~NZhmib# z1mZ@OW6KZo5&Fh1%CNAk2|KfF&(_Fo=hq9KFcaNdDu%P(CZ0#v1gBpIm*J#2v+=$L zCQp46)^F$ea}U`ONmjAL{9eelh@ksnS00ai=|6ji%i@z#!B~@7xyzmPS2D4ip_vi| zZ6@%fW!Dry@V+?YF&1IeG7uR6o&dR3M~@y&R@06Rzo8{Mcu3|<=ZV{&b50EwH)I|o zvJ_r=!=|Y+CiCUviyIngEBN?4_>uo|+4piO)lYP^5LdnGQ9~&Qp*)o0!{~f?aNbYM zGq}tKP1i?z)R%V)Fwh)RM^(LH(@HN1fVw-ZuX6dMQMSX4OYzhSh1NDUaW)U-&6O;K zK-H)@c$;N!_UfWb$+5SoMG&M78Ykd7m$}<{P zA|sm-hfIIk!0Kb==-7WS#pL6Gff!mXagB7F=D=h1+&tt@<~lUV)ofh^2yd9|!dsr*;Nf%csQY!mWz`0}f3 z%Lj(ryQ*bj)g@!VC|6-OFH>N9zWdODv9EV(y=g*u2yJ!#8B8?j43m9X5LPqWTd%c+;QPB&bZ)jA-#wZlp(#b>0-T@XYyc09@;Ro|W6UeB%Ncu!hq! zjdr#H%&_Mf;0zm|jUFN_i-?P4@By#2ao}`jL4ns*k>GYctAyu1mu^PNR<}daUe2s^ z6W&{L%lr22PYx+{hsvYf*|w& zAA~KuVRsYAT>Q>lDPggCud+#n$H!za@R_hHCMKrmdZcl9JDO8a?>$Dkx=mD5P5;7} zn3$N!m#!|&uwcN4*+2{}Z~JTx%mMsa4~uXyCA}<&6Ki-7wZ55295}PH;3q1~w)+*e zbnB(vh)nEnPA9aU5Py8{t%;FQz6@^|Q|KwcmzyR{5i4uvbM=5}%l2qcRgM>3{qt6B_D;UR0>^1efr39faZ*t3 zLPjBY{YdrynYAAS!hw2Bbw7Kuw0D%^KJnY^h?e(NoNDRP#B6pL=W{zb@u6?uj9Ksz z4Pka(PJOMV`B9AGv>?Q_;~3L~+*zgIz~fTsHVs~}nS&ezk1LmsziY85s?`r+zMs<0 zidUyXa|7O&lQq{+|nerG>I)uVoQ_Q z+?C^>LeA18HnnP+H@_`SV*jI)n6(siB!&=60<>6OAps%ij~5#OU>qy_089WA1^@=n z!N|rZutI=?i;F{mpP!En&jG_oodjIM7%5?40l>|#!Y3fWCnX>R1cZgL7@?C;b17c{ zm*Tq;qf_x1^S_dzf zAd82ro`#K?g&F!|qanEf`F~a`23ArT46rcHDu#MtWME)mq~+#CG0@X9pm=y_$pzGt zXfVLUNR33I5F9)RS`>VG6k>S0u~5;V}+?Gf~JGZH;wZv9kWKl9n`f0j={bLwHU5`;9n z9s!B=XJYv8?|+J|>3os>&tjisKeO9Ulbr?@S5Gx1{U25}mp%+Zg@6(jf_&db>VyLN zBsQV+!~P~8roX4`53*kd1kJpJV3@XFYG%e?B<5*Or%C<}fK6HQPlR6-{j6LB1l^)Z zLnB-_|L~S1DClU1da~P^ z>`udagDbo19qTJ!I9?TJAH@tj+k25;!!~;$Z=*B1C@Lj&8K2*_+KuN{g(vpN)xX%A zQB@YyeuAIdJNR^uoRo^^$~0ks>rUZ@j*S87Bo1PM^p*}Df*_klLm8x$g{$VGYhX9{ z9uNz|=R6JtpbOwqmB*V4Jl$9RqoytWLVAtg3ZIR!!*X@W$;tid36gJ`n^mM(5#Xgo zLt%>T1awHet1YoPczID_TRs-9+1Zi2Y2Br?EQOe-UTnk(qqI7M?x8WM1XX}zyx-8> zpVjBR&X&eoPEqlDZBtXzH>ZtCve7ZAwN`QQad9>!mEK!=ZK6`+!#hm!lZl#j9k3cwV3#ZYmX z$hf|q(m(ngiYoy$C`u)RgTyyWo(l_-jNUT(anLO}@orvOoBgHEk(64i)GG4~;hi-p zZp7hX*Hu-iF!>?zt6qgBCF^B_j;-AQ|J3!RG57FrS8s2xV~yPxeK$r{Q)J8Ix4AAo zuim_QlhzhdJd~Uq7$kmhFhK5jzn(7F&E9ea1%>7y@3j3rF=rgBzXtUWm6}Qn$Dpzd z*VqR&p353ZD=lr!>~mOi`dZEvZbrgZ+8puI-er9?a$e{1PHc&Z4qbsyzWwA=0P*{W z4<9TN&%X7yX)!62AJ>T8)j+6H(y(%I$vp5-a}zK;ZhygE8akPJ$jroK?3~+Zp4<3M z1g&4N&bPEJcGvB7zMh`%J~(acR&e0DYkN;UO$pTrc_Jzakt?hS5CAk^O z?71}=6+K4>OmcH`Yb&!Y(cp@?aiLfKvUo(qh{r^KTwGjFnHIrZ|H8>f!m=kaF{xGd z;$ni)Pt89k`U3oD*6{nn2M;#HXx)r2t^CwM42tn))bcpBDrD7{@r>s3(YjCLUWGkx zSAr{RzYPsl9~yZpY?XG_l^qZ8Zb9qDQ!j1uT%$aaI|3mD$l{|#XWJYlRcNL#)9;%A3^YG>Ku8TdagVB4GfA4}pHzg?Fjxc8*cgehXbo2!T7d zMm#PbOU_mn~iJprE&GZ>NI$G z-&sSqRl81H-W7%*JJi}6_1|`dpFUj>I;hbG%_1+ie$>F98D55lU?f0!Hqe_-#`n4#C<)<%i7jf z`Sc1r#u=E3I)l~x4T8XR-B-on3N?6ACA59M9U!_-eWI5=SWRKeZYc^=&B4N0>( zqDJdx{}Bo9&vOJ#!lcgkb2rj8Ni(!Av8vz03JU~Et<#y>Uax0@*|jSG`|qYME<@q0R0*#6 z%aT_BJhQh_6{jrGq@>S+Ej#uJYORI4d#<92N_@#W|l(B0_fbi zW4dfQ`mcGZbmd{<{h72Q$CjE;y22NPwmJ0V-iERyY89F9EO# zVX;COfb|9RJSu=sfQLtb2LK))0E7dfe<&A#a)J3dTmS$+Dahhx)KNiYSRW}W?5t7} zCLRk^NHk{9;R_T1eDPq3ncqpqk$fo(9ea=x3`s+SgponWvS<*D%rrF2j1WL7qC-&8 zve417&`}{szjuVqxPc|Lfk?oq;83E1ZjdIQ$=7et1>Ds0)P3O8GZi&CP1pbE7`p74 zY>%KJ)kD4ic%ENRGs)Vo>H&1IJe5el$%HtZPSb>b0+wbHve1}4=3j~dJcNZOUy2ng zAFPCs6nE;dfh!vGi+jmt2}UIy#h^hfH|PyiOjBy8Yy#4f8b-H)&bf zq3q{JV#6NLpw4h!?yTi2zr`;gP?%P0RlDQHdd-#1#%Z1R8*I{y(+aZ-3XE&^qt0xX zGSy7V?Zr-{(Dm(2!D~mP>2~gbGX*#KV5OH49<>}$KXL1;P5UwNCO9;G!tly?(e2r4 z^4!FV8$f%zu*7!c%YUT5M(v8x-M`=DC|;PGDe#@NhH8G#aNgMC&LCsz_}fCx=X>`pshbf&*7+d^yiMYwg-qm}vJjp}B5D;mZ#evB$iZt?^Yj zsNu(_xN`4jsuGv$clYr7GL>x!aTw&>J)nBl*U6c2rBCMF{Cpcsoz$S2g+=+3;9lFJ zK%ZKn`h?Dji3zVWx&`j;?g!U%Njx(nsN+*;B&{3ve$|Uh@7y8QCndkCua!SL&6p?^ zSBybaB(XU=IJ|fv-K&s)VV{$~rE`VTP>j`s(Q)Vk>{Evrq5|(~Y#d~vdP~74;I{bC zKfAZ=TfZEIf=&eIe@3|QDiY12$vmlCg_^m`2Tb3>~@A7~S<%%gr4>geTFwGuj9|MllF2A}`n!9F@c=qN8h)Yd^i6wlJF z=tlhd^&bB^7Rfd-|13H$%MH9?Qt(YFg)qT-hldH*&C=C@>Qx;@)vE*pk^{{7NN|}- zia#|3cg-qvo)L$N%`--F88+QBt60++5xW@)doAg|6&K{E`T?=0TsGh~)!|kcQ8@(c}`)|HHC1z5D z9rP+BUTw5>*KO)(Sw%2-)A zNx~>uA~R#GvweTx87lQqJnz5f|GuA(i*`e*H-?i5>o*H{2JRfYs2r->^}@Q{OauP-UAbbX>8u0bK>nsJuMrNJwl0YBs=YC z8gHvNPuV6b{>ZpGvT*}B<}`+hwx&2^8gzDtOQtMfPH%8ueto*US=7CycdZ5D>z@u- zRK0mUzjki^*&tqzH$3YgucFw&^{BmGPupexD(cXtP!tnxO%>9W^Jv%BXsTV@N442$ zEQ*kr1q_$Gzq?@YHARlNr*?*(+&ar;_wVNz$DhvNRRC$q3_}N%Xi%bI`r!DW#oat| zdMkA^*IeoaZn5a|%Xp*9XOlf=$*6A%jxFA=7#}}X)t0$UzGXMRANC2$ah8#zSAS?b z#@r0)ZJh3wr)!zByR_R>03CDt?9)g0TT!2aWRBTNgqqvAVXo~Cc^{@H&z5NudIB}QoXB3O3A3t?J|-^zFup_HTQG_?-|LL{o5W=u%@zt;UIDyXm1clCLm(&yPS+ek*ry*fAGl9uxiVGbu&)WTJ2=`J;~jaqo&p~ z2A+8z-g}ZF@8Kn1dnwOjb2~<|tyigb4Kc5|_TY+XQ$FJocFL8^Pm*~eDYVIqTIHHL zNDHB>MyYRA;xCiL-7C%aT&%BPvW%-P;^qQ=VwOMAOruqgl-eC>dJ}42m0$o)mZ}Pi z?2)=96Sr|^U){A9tzJ3V#$#F<=XN$5Vr!7=LJ!RGJpV8lY+ifXeFkEYghKcpVHf-% zg^G?@QqeOej%PKOIjoAkp?I4|RP5rbv|8MWHB7FzXufwuSF=}egaVB%yEbQRH1)AES6U4K zx^|R!#=~TIPCP$QJy;GC#SF#s=L^rQr|+1K*V+e#_{=<$D_l^M!g~NSX8(Q%B1t zc=mjH)paYVN@;&%G_cUcZ6u$5QIUQ%_SDjcMPi5o5gX_0X_2$iZBk$@P6_wutw!kn`4Yb9BH@WI`p58Al#FzlGANkebBe&Bc0Dby0iFg z%Y98DQ-Qa-1p+aH4n;IZrSYIu|BYt{L{d?!W>A^=X&2R&uuigUk6HHB!h|=j-!Uo7 zwOX(x897&u&~zznyQjy+X-6qEhin~N<(tA{so-^UY+=p8fc#+HwM5!=#bU$EmDTy% z9FR8)>ppUKDRh9RNbY>Z#ib4Ynp-zc4Ozw`?z!~k9Kv6xv4!>z{bgUrV^ibT?7Pc5 z@2M<->qUt0*^U}JVfu>Qx1){Z0@hXduDeyMGT7wHc+K=^m#WJaI>x#!Lr__8{*%1i z-A;MJ1L>xNLV~_{7m$SBja-PpssSeGL1DhWJQw7EZlXBw=YrBbs9_Mh0#x|k-{&6g zAO38(Kcx?4ROO$visprn=tE8KK^ghwo$c$0NSn4;z3Ak!ijrdYk2q$_jk4;y=kQ$1 ziZ{n^yz0Ip!U7z4+8r63dv&D%q6O+|QrM!Jo7ljhXD-ffk~`(H$~2E>Kl3>+xZw0oywJ&d;w9N~OtSoo?V{o#ulwVS@ejM)V=upg%q2bNp06PBgdYWGWt@A_d&L26AV&d&)(4Ic+%h!MCW)m#c-&7dDyd84LF`PfZJa^%YmVT*)Wdx zIc*H54Kex${QEHj{cPWUgVUgDb7%jf7-zFe*ghlSRc2294j3oXuW;^RKEZ<=+jD3~ zgK^}+G4o^pvEaizm>i!nhjid!%VPU9u!o1^(Qlg3p~i>=|2RxSL>)vQqW*Ej<7i(R zo-Z7$erItPQ|}uoAy?5UH+)Um9<{bOsBaizfHu^Jy*2Dnq@xLo2rhvrRx&`a+9Nv< z?JRG=#RBaVGC6|8Mc8}dIZEvBe8~?BzBWd{@P?rsOb#> zW+43M{XGvngrJiHD9COy4gVtsNheeMyO9*qk0>7~kcohpKXe!!iigHz^Apq$L-U4$ zM^T*78`y{Fq5q)94_$xS{K2AdlaJQ*)xbaN!Izm>MnwL&lw z{^jq2MlIB7!}wN+!TMWaK%K(F_;!f(cLpq|>tDq)X(I}}vVAX~1vTHkpF3Rc-+m9V zM#1L{mr?LAKRV-Up~K_-zW=ZA9Qi-t|0_Jq|EqMkGKze7nf_Y(89(aBf<8SgD;+Ln z4`Gk}sQ-^1{1@?Uqs12!`urt+KL4oOP`i6LKKW{?on3z`pS$Zh@eN0~&@|yu$}}O) z{20e18)JHot2>-XfzM|s^-&%?v9M??=fU%am16@;#5MfjX{c$lcf<5QkGDMYc>K^$ z)bWz`o;)f_Sj^{pV4Z4>-HELB)%(Jqt z>p!-!3#+#PR_BbR11xfRKKWJ(*=$$P@=M}#O$h)#NV$f}6x1ldM| zBMPy1+$!$NEL=Hj`C|`c%HFIG4KrK&dT*r?1Z#26W)}3zMz`V{wyM$&o_RSvsI=p# z+KPa-%`5STD`hK*dQz{dyR!qDl$0c6#le;Vy^WZ!eA{WkX3#+L@c7`K zM{aifETEOMY`UI6q}R(0ZM_Bcb+=_Zrn~7WAQ1Ad`G@k?TOOVf6bkm)CS5z^xKtx8 z4yoU=?@C5w8)mm!zo?#6wY_SAiKno<^P@#k&$4DjG0yrbZ=fLFqaN}E2?|L`t71>$ z2k>j=n5uBM4rbOq(IGBK+Yl!=J6aIK71r8974z+PXb4rkXJBenwqKBJnv3tvA`6WCnb?z3c=ukhlC4Ak36QuN*k4NJxB?xoL zI@jDSV{obar(Rj?bJ%s~`3*rl#|6fpAa#WaMV`VaZ3XE z27eS2;e0}J%L9zL6Q7q&-R!dY3vePTExanBiB8DKg{*6~N{22bKGa*N6M*F730vb) zPN~vA!e?xSI&@)XSZasN8hHn;+^q8JHUfXp+x&1u8DK_ksO5oMah>dt5Y4p=gbb)BPNvFg?n>p->`7M33bU6S#ZGWFd z>`90KoLOY#bj<=LN*$g>^|$QbssBM}g2KxWl)NnVv3~GuWncmR^g{>71E;|8-1%BU z52l;}3h%_*wVP*lEn#4|wOeU#&ZkF4M&%~NzmO}7LLj@&z9sq2sEZPIYF&LhU(!jK zP;8)#mWCfy0TN)qKdUHJ0Ts`+E7TZdQV*pBSGkKPuqECh?qDM1^_B|ZnEm`GmG8{J zUKq4tvIaA6i_JqNo)b-o7QM4;O3r~;iqn(BohdE0cL&NX8$F|0vzDGK%PQj$!RK`r z>7!0M9Xm#9a<7m6aFF4Q;VONxLi1(~iGEPIL%@lPO!r_NGv>iHni!ogS_lG_SkBv@ z&1q$HwZ8IdL~0QY@$VX*GCrzlfZSf~C-)a+4jzn*FL&8h$c;wB52aYPso@I#jaSI~ zhr)XqUM9fx;O*Gf;uhD%V*StToeZ8j_NlGiu8Y{&;iB*5rQhnbrK4a@R!kKkb2)A{ zL#s&j)Q1n#<6~#r+LK=5Toyb!?V$MnRhnv7gXU5`q|y`b3{;^2Yh7A*V3e4Yh{R62 zxcY`ZH!tqp7Ob@co&p4&`B!Ny$lYfezTS(MxCG=1j8N-~ej`==0{f=?#32JBPOf}c z7tSlef`aE*@Hl~~sQ5vy^%5g}k(#e>w%)V8f^*cY&bTdg0V=!%WmHiG!-cHo;YI20 zv~_;vvWWjN#1aX2${Pr$F9-{hQq66=c;5MJygCMLdQOOM4-XWd<5s`K=>ru%1)UV4 ztQMx~AYXzu<=IiOmReKX=&}bTji9T_9=>OCtoKjsst#Vln}BQOEH+Y2oaGVS-M2n& zugu>Xy+;sLFIO)}t??+)+GCl!r$Sh48m5&+Z%4MhxW^e@2!S1{4Yz{M-LfZ_@? zp))Zj43jfkdH(&`xpsD3qWB1IuM2}6L|h-^{bA3|xwCwBwgyOxNjX8y6v_jh{<YR72h{)V|`x6qkCaykjj?D6%XUXP4da}HIeUv-WGWluW z>5M|LQVUN+&B_$WwiJ1Rz)pzKzvf{yI~j5)8*M!{^Y48Kq_?|hYt5SK$O9)xcq`snx3Lt zx=q8G+=DIEJs;Qbr>3?a@yPgOiO@wMm3%U6;ZxcuYotA%4iJmZZ?w00cWRs$T#JPM5=ddADqz7bQ01n0z&KuwWhs)j@6mkyHJjHf_t-JI4)LGG&O8f-An;TnGrRB;)M|GA~~% z(sr_wa>R)7f1LBtX=#U(i=xyqpdGqpuR`MX>x+tVjAu(?fs=KdLO`L3*B!5D0s0cG zb;awBe$O=vNdcPo{tT>>!Y_lDj>I|46@2{xo(>sxX^MrVB@d3iW)_?}dEeDdEyv_Q zUrj7dd{+ResFGB!d--G*0tnCy@f-YcR zEZ}ra8Ng-4KEDD0zNo+N^GKKq-f`IL;1(sjdOAF1LD0!UtXA;ex8;4KecO4$R3cYO9xU`60<=U1o+l7AC2 zF7DsRzv(m@Y^0tnK~+|6GpQ&{=6zk?Cr>>zVb8!SReEO_b!ixOZQmm(Qp?Fv77)?I zQc*|^`dFKIIa$zP>V@Re+Ifhhx!Zfzn>@Mxx@uMp)Z4G6#(ZD^Bx9-Okk^uA!3MYf zJ`K5z^z%Ujh+EV|Eia68J`W&7`(qpsb_1W7*yoho!>`g z+&wKJ-FNX^LqY8Ow9FdHqv%vex~r4B91v(iOPk%z?nXVVRG}y8dI`d;i6oGF4biuF zo|*Vt6@8lU4T=zOJb~jP3IjsrY#wps7$f=S%8_mxN90YA{96o5!_hp3GQURswm=_R`x?m83Fxt2MF+kv_@B;)v`#40Ej5j!4jNkFGt-nmUMJ4nz7Y3ZqBllmxnJ}c* zs}8o5M6T7^wEZGCflH)YyuB9uwZ3PC`Mv8ahv(?5LJn)64s0pC<3xvfQ7kC(Qxti`jru^_8cXn#8G?sa*|=SBh%iBXrt|2#g5wx>S^8FKT?@qpmRjR&UJ^C7oT&Gbj6gcPS9 z`XNc@KT7(LXt3kJ0KK9}krnnw;z{ZUjSHzV^R!T$@%1!o=)(_M>G}Pn^e)?+6oE~y zUI8#4ncbHaS&A2<-QolEJ7Y^ZvRtNz}KR3e;l^WH|DC@z%)8__nrbE3dXY1uKK(sKe zjMA6BVoP&Oz`32)M~nSb)Yq;%{nAT7?LvY#F!knz1}m*jQ7Bgo1+ABGb16h!XMgPag#j-4%0FmuXPWjmi*0z~Ll*k^VqYZ9QseqK&c#{>SSF-XulW9Oy-zU5W*! z8&PniRo6o*C0AdYDG{~T&^(ugt_d^uKp^oc3JlJgyfsX#aFCK*Q5AkR6~P&wdw23p zDX)Ro_4Iz-NOAs1_o1^?#aggJ*=7 znaw(=a6oWVv#Xq`vF~~sb7}gV&xI0(I5a3%<2u3ImLyuP)){4E&ts9I$mp6j`jlmy zf0ws4%M~<4hG2pwxdW5Q<6?Bz~=6bt$a%}sg$iv_W`C& z`EimCjobG^Ng|0{AQ~r1sMSB$=S<#(hrI|16FxJam6U|puP0mKh4fe}2ygyenI(wa z2(rkWnb=aL*(4_nka*?fFm7(I+V+_ku=B{MhmV_F)H-+vStSh(Xq zoH0sh@Kdn@4X(5H*lb?(7-rhMZX>IFpr;9+)CfYpg_iMzccKU_w@QvD5 zCI?RG$XdI=rsD!Ro&KJ<%40PnKfdNE1KN-w)}%??|Ry7(pD)Z^{qRzq4!zKg=}Mphz7*xci9`L z8vHph6k56uc21KpVb7wC7-HL@Fu1kgmj~g|rp#!t@rD`}mii9^if?^6`h{fi!%`{>5Vs09cr4wA21*uyH-*{wh$%)afht$>if?@^Lcx zIGKE$Og>H~AHQsAcrclKoJ>BBdQurWH{juO%ukLUV<#V%DghvR@$cV=fSJJzIp2uD z&0g#a6CD`-M#S4Y<`ZE)VyNS* z{x0SdF!WFijZa-H+t=d^y0Hu$YM+;rGXnr3!#J$ka07?qzb73QhaFYqL_9Ip(Egt= zM+0SV%Xeo0AY5(9mOzZplfTJ;|8{Kr#DF{5USltOK$`vX%PO8j1ux@6A9ZoI{IROi z9NO_QA0J0Nha-pfcQGHIx+A1dMDqzSA2HN%R(})o5knncb)4${Rxkcm=07(yf-@YO{`X>8IY45O zA-rr%cv<3v--x0AvO2za{oh?su|SEv{Qegl$fRL4etni6aoEOlI*dWRZQ?qcii_ zT)zIlo-_OQF+@iwe4Ej>K)|()1;93&K!1dd79Ie9_=cfC@2~GDe~3Z#?mIsCndf-YuT;b^7pln-MneDMvH-Gd4-eR_CdOAqyj zAUcd3#`QoK=3Hom-~fFR}D^(|CkxZT0Nc#K_`>??wRU?;>Fy19k{k%rooLIPY0 z$zSmFZg!#2zv7D@{Pov^V)$G5L-aJx?Ju8cU+{yioH96s|4|-!NILtKt&c!GwTr|l TUDQTcUxd)N%VI{kBIf@9Ex;W8 diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico index 8d8088515d662316a900fee0e0e88f84f36b6167..0b15a99fb50f60c8a083d113298ccfa136ce4072 100644 GIT binary patch literal 5570 zcmbtY30PCtwmyLXxgY{X4G0pXR%ECkAVWz2D~MA;lv@#*6opDa1cWdrP9PxQz&tBg zp$>_Nh>QURq7Vrd1PP;wVUz#~Q%Fqi4%*hf@4oiF*YhP`_C9-`eb!#<|NpgiAP5FY zLzlw7|{3R^XJr+f{eYS0wZHBHZQEwFY->jCH4)8GnCZI)owuN zLe>jqsZ%%{u5y&i>vBD$(+oY>o_?^_^})m}ld_0fd`gigKwN(bYRD~)Aba!#q1>0Y zz~!L}7ao_mOTe@A;7dCcB0FZg>8n&AZXT8UJ({#!YLD>?diP9- zv2D6kTwJ_fN&3b90Du4ePaL5WBkbU+`%1?O#^1lZL8sGQKQ?;`nqQPVg`2|Af_Dl- ziHP<*i*jjnA+5YSr*bHk|JX&}r=mOj#ZHajhn1Bj_0#3u6PIVU_qcUtw6Sh==g`7i zj8~JYM%J5}n$n}KX!-g1J~bK|8Vg7R4>w7MtS-%murG5f>)XoyN5UlXQqr~2U}mxz zdoZ-eox-jfEohFmLejcIH^|7ygsQ}iHzg=XHOyV{MSC+WSj8IirBa8p^b~0!E^2Bi0?>DE`~}3ezZta^X`2grN*Xvw z$)aOP`F0n1hhOX;h?=g$dNHJmIR?4xy2Zf+k-D=m$|>UYWxN{qnG-Mbjj?Y#_3LK> zy%TrZ3otbjn>`G5MUTcm^p?vAMvKG|#2bJckt5XX2-__DC2Ci1x$ohICb?ZKPt$x% z)WX%3g(6;OeOj59nLbG($F|4?J(oATf}cg=w{b}JEYA$wP|PBvqDXs5VAu26?}y@A z$<&4>ygp1CQP6ft4q5A>PsKl^VAw$~%d_=L0ccQ`Vfv?Qrk+%s8u0}2U<5FP zXP(lyR$|qG2vbRQ%P{}!Y&C7i0=LKAOQ|ye6Ny3UOyE*`w_`*b!Q!nML4sZrx$x=t zXaiYMU8!J^fr6R2$W}yfzNQ0|LwoETY>D4{o&OJ95VK8jg$pRI=okdS4*d--2*%$j z;KPR&>tJ!mIt~?Z(qM+PgyG%^aac7nn-;WB?}u>x zL6omtIR5tNhLI5;82l+`Khr!>PeReF;07dVky8Bhd@J`WO&dX5DVp@&&Yl&~+*a4$ zCPkwFc6e^;2shQG-4j#Aj%l}(74b$Kf-(>yN&U=yAPlMdycoUU$?>4nD0KrZFc*xc z$`_1NvyFL{WUi$$4V6B+T|AR%XUmeU?4B!^>lor*#EJ3JU+xUcf*4p??G9# zQHvDjt;uSZI7r(KR2VQ1Hp_!pGfAweK9~p#3k##O+3Yaer4&o{)48`4t0xc|Y!hoX z6wPfn`GF;-Gq|LRD(q5yhZ#BmtO_}6$2mU!y+8f)-G+YN2Uu!`>3GMC47 zb#?5SnTHzIv1p(2UM;Pb5;RNaUhe7Y1STcb*XNN3X%xBD%_T&N0VCEe&|d*oFJ2wx zh+R4unjb+3QlA&=M{`Kk0v&4x1_yT%i#SlWjwa!~EG)LgxS)(7rR+sK;FIWE%z2iL z&yHv;t@WA*qI!U3HzR&lVsim99eE$aRHg0qFd5+$bIz+8imaSH9xl(sTi4ELiFN1~ zLc*vWOR7z59yD@E`6?O@Ccv-bQMv{$5dgAB!CAMMda3X^14_7!NSek_-6g4#=m;@Z5sA)qGH=003xd1i^#mC2Yiq$lEU|@i{ zf0m@EWECq2SJ%*RK&vsVk?A5gXJN=}s&e}?NLF?8+P}b`II|kA;P0>mFCKyq1hQ-ZJ+t^>G*551B$1YYG$&a(u3 z_Br{!$r8-esYMuU6Ri*$3U2%MBo>S1ilv&cl$kx6+A8Pk0Z(c^4nMDV9Z7|l<|$9q z4sQ~LT}CqE)J~h9JX#Aq*`E8Vt&xE^8Uk2TFrZkWIR%R=4ceD#eJ z9+zvT>-lJ}0^}v+a9L`MKiPwnin5LsJTp%@)DyZ>=j4{#t8S;=Of%ZO9<|&NPYlE1 z_y_k!0h9#|fgEQR5D@SHXjNpH%l+Sfs?&_nAIBZ7>HYnAz9VP&QwP263mwC}d zR&W*k4xpnqL+U9r#q#O9IJFEQcVd?S(K@dO&q59_^aT##$9AIV181JgYqtcQP`CHE z3K$CW3I(akt(woSiP#7?H!hz1jw^8DrEUYhcC1MiMLG+am+>38)AlUu6duTBcr#s5 zjT5g@Nn}AKuTIBfFk?FuzRSpFSRb%mQU%q4#?{)fQo$ro+}rcC1N+ zcJ{c;3eI*rCd{?9O<`^+ux^t35d}1Tz$Aqk>mx0a*V3vX_F2U&&*gtg`n5n`I>$Rp zrqMXI+jw=mz)XeWRN`1T$p2IlDa-<2hz7p2awr)O4?4WP?-jeJ_%G;a1IBGu=y>;3 z&>)~A`ESrs1PmD}6^!aB;Fq3bhZaXXkEuvwSUQfUK!KvsgkKGd?YN+Ewtrz~z|f(+ z<66&Z5k`j|RBY2Cs>Z!b5tBt-vq3<)#VN#|j#>mbBPZc6fGVhduj@=T_eS1TMhqxy22sx|7&!iOWmc&#Oxws5Kmzg1VnqmuJ^G&`0EP?hY&69N7OIt%f|9Mtel zg_yCjZu+gX>jLCY5A1x1f#i=1Ndi7l;+e-Km;i;hOpwh)+o&Rkl_x%fgQ6swr;-)Y zMdl;oVVL;L0JJo6&j^_i@NH(>tboVmudH(B!`rginHfW#!C6LALYPnxA&_KsI^wP7T(AL7d-n+C{jpVUBbdeh5RKi! z$uBlML=*Jf;-H=jrfq+HaK&YCgY$3vSk1?e2^Y_R;$Jl#x#K~_U=U>krh_nyH?;=3 zH_;MwAJmYwh`3|sV|-yXc-u{HpF~lk)-$Ggx9j9akx>)yy@v?{uMG|yOTUNWvtw6m z6)F)NoX@eOx>o%;@4>kj9E?|Z?-pDNj>OUzS9njK8n6yjJE`6<%g;1bRQxvi>Q@Kt z({`sDZH(~NIn=CY^W5%Jk_Th={@gdlpRf{Orcgu2kt_&_bNM-HN}C~ zojhV)aLK(Y_;yaAlqK0eARs^BiG>fwR|{%i_JR7J26=a9;+g^ns?$+CIYQ>m-);WQ z_?3nYPU67Xr4YueunlpX7p568RqHgjO6xKs3D0Y*wlxTh#-^sGf_;6pCuiJK{b1(3 zu!Ft>9bFw{~9X*93BMmDvkG+IwR$7sTbRb_OEYw=&NzFNsx z71Ysa$ujY+_lfsRo%izgW)8gVHjc@LHJO@~=gXA6>Qg%B7ZSofcHeW{buRZG4*L7b z0*2xP=_==LJy_JalK=jri|O%R+{-OVebrt4AIF|Q8H6hQCf$#Fcf=<3os0<^ZO`Ys z4_Gp&xUEmPet8?}Cz!95j0^A0elxTy2L7BIyYB#r1=9X9=Xl5{46CZM~u{^MTbv&ysYSER?A-=&& zUr}vf_FODqEvl8o)OXKX*O&VUK$RE6d@Zi zXpKBvZqTFpSTir~w%3`D$kr0>i{pL`OF`dQAs(M_ zR3y|dTV&MC?zoo9Kf5{4p(Mhe8_V7EB7Dic`xn|vIjEOgJGjwGatJKGlRsLr z(Kn`Aa1;uspX;Cm;1-ZjIjk?`OVIui0AJwwi{=*?|Ks$eO=j@?v{mPzw@x-tN@ab0 z?9DBSEEm7x*XMFd(#&Wq;B7SpOo_xy)6e+3cPE1H_Z+8WM_P0wKx@kAsMYa3yNa(t zh=v;_!W%E&-?=$fkOfnL!@@UT+Bmk!7Y}<_ykf=w2G{?_MA{)M&ll0-_n^7S2FKjo z-06R%d8D#5*KKrpuWdqO>g=~WlEK&0)AMA1_^>}Y5mMLQF1kWtPWpg9*d#ip6*oQq zZ{G5!bMoAdA&iDr>qB8{6bZr?-g_=I4o|s|Zl~Sh4wW@9BJ^7y^?SOz*O{(QUVY^< zq+UZZ5rJb#?9nyN=Ie~lu`kx8T)_Kmfe-%-3TwQHEW(CNQ#pG!E`G)BHexXShN}Nd z<3+TY*9f*#5wf&EG>8IYVm^NFw5F}BYnQi2QyRwy-NKw52>;0qUQ~WazMH)I3RwQ1Zn_e}6v=zr?p zT>~F}-@eYS%tTbR2Da|?8FT%`g#`~q!i~P%5Li(eO{}JMU8rlCy43k)y Tiv|xTlp2rkg~0Xe|4IJ_IBQIl literal 6203 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 delta 1845 zcmW+%X;f3^7X30B=J0eu#2`?l2!coj$`A-LRE8p&GRUAw!CDy>2=oB~lN+qXK0yXA zgIF0>fl|ao9+ja*A-*buh^azBKp+S*RHhI}ASB#(@!xmWS!=KT?Y+-Ab(EX?3rxS& z4D$6l5tKf2OYyOg{uaAss}2 zWD1G#d1OCG`eEQTTd)*?C-Ht@_HgDV^^s1_ zTDwfDY|b$-mQ@r@JSuzF@L*W-th6p^J2g4O5@=81jiZ{I)^JZnwC&0et_}(!&V(*hVFBMp&Ke+tie!t;MWFYk4nrR6OVNTB48Re3qYR2sRuZOwMW z&VinO6+|In?;2&H7jEKiPDP}xOh$~4B={RaU!E>*8qMddm>;V?q1{*JVA zVBhrg$<~8|;hO4^mM1#{yKLunVku2TchI;K%4q`li zl~{1Wjtr>R*byIS!)_0XW4Es76m8w-BOA0^Dwugy=E8=Wt&R6AEFBHBjN;P zG6waKQnCG~4c!&Dn@G5j6aw*>wIR_UwD^{-J79U>P&Wq05nA^zLs7@X3?~KWvplE# z=u8ZjS9?_Xaga{t2X<^pYF-W$+^WNTs@1$zt+>kR%G9y$S@qRj2r}|FUmL) zWVt;cvUB-q5U`4zREbia>N6`k+=lLT5yX)B3uyp@seEuQbK@|;G_I4{IAb_#Uc)NG znxUH5=$|cbI**9)K>Szp?qZ`ON;#6(6O4}Tl&i+@M{Ba-v diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 38c3a53..2993c60 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -1,14 +1,14 @@ use crate::db::{ClipboardEntry, Database, Settings}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use tauri::State; +use tauri::{Manager, State}; pub struct DbState(pub Arc); pub struct PausedState(pub Arc); #[tauri::command] pub fn get_entries(db: State<'_, DbState>, limit: Option) -> Result, String> { - db.0.get_entries(limit.unwrap_or(500)) + db.0.get_entries(limit.unwrap_or(10000)) .map_err(|e| e.to_string()) } @@ -17,7 +17,7 @@ pub fn search_entries(db: State<'_, DbState>, query: String, limit: Option) if query.trim().is_empty() { return get_entries(db, limit); } - db.0.search_entries(&query, limit.unwrap_or(500)) + db.0.search_entries(&query, limit.unwrap_or(10000)) .map_err(|e| e.to_string()) } @@ -55,3 +55,33 @@ pub fn get_paused(paused: State<'_, PausedState>) -> bool { pub fn set_paused(paused: State<'_, PausedState>, value: bool) { paused.0.store(value, Ordering::Relaxed); } + +#[tauri::command] +pub fn save_window_size(db: State<'_, DbState>, width: i64, height: i64) -> Result<(), String> { + db.0.set_setting("window_width", &width.to_string()) + .map_err(|e| e.to_string())?; + db.0.set_setting("window_height", &height.to_string()) + .map_err(|e| e.to_string()) +} + +/// Hide the window then simulate Cmd+V so the content pastes into the +/// previously-focused app. The 150ms delay gives macOS time to refocus. +#[tauri::command] +pub async fn paste_and_refocus(app: tauri::AppHandle) -> Result<(), String> { + if let Some(window) = app.get_webview_window("main") { + let _ = window.hide(); + } + + tokio::time::sleep(std::time::Duration::from_millis(150)).await; + + // Use AppleScript to press Cmd+V in the frontmost app + #[cfg(target_os = "macos")] + { + let _ = std::process::Command::new("osascript") + .arg("-e") + .arg(r#"tell application "System Events" to keystroke "v" using command down"#) + .spawn(); + } + + Ok(()) +} diff --git a/src-tauri/src/db.rs b/src-tauri/src/db.rs index c8c61ce..b238a8d 100644 --- a/src-tauri/src/db.rs +++ b/src-tauri/src/db.rs @@ -18,6 +18,9 @@ pub struct Settings { pub launch_at_login: bool, pub show_images: bool, pub max_history: i64, + pub window_position: String, + pub window_width: i64, + pub window_height: i64, } impl Default for Settings { @@ -25,7 +28,10 @@ impl Default for Settings { Self { launch_at_login: false, show_images: true, - max_history: 500, + max_history: 10000, + window_position: "cursor".to_string(), + window_width: 420, + window_height: 560, } } } @@ -122,6 +128,18 @@ impl Database { "INSERT OR IGNORE INTO settings (key, value) VALUES (?1, ?2)", params!["max_history", defaults.max_history.to_string()], )?; + conn.execute( + "INSERT OR IGNORE INTO settings (key, value) VALUES (?1, ?2)", + params!["window_position", &defaults.window_position], + )?; + conn.execute( + "INSERT OR IGNORE INTO settings (key, value) VALUES (?1, ?2)", + params!["window_width", defaults.window_width.to_string()], + )?; + conn.execute( + "INSERT OR IGNORE INTO settings (key, value) VALUES (?1, ?2)", + params!["window_height", defaults.window_height.to_string()], + )?; Ok(()) } @@ -255,7 +273,10 @@ impl Database { Ok(Settings { launch_at_login: get("launch_at_login", "false") == "true", show_images: get("show_images", "true") == "true", - max_history: get("max_history", "500").parse().unwrap_or(500), + max_history: get("max_history", "10000").parse().unwrap_or(10000), + window_position: get("window_position", "cursor"), + window_width: get("window_width", "420").parse().unwrap_or(420), + window_height: get("window_height", "560").parse().unwrap_or(560), }) } @@ -285,14 +306,17 @@ mod tests { let s = db.get_settings().unwrap(); assert!(!s.launch_at_login); assert!(s.show_images); - assert_eq!(s.max_history, 500); + assert_eq!(s.max_history, 10000); + assert_eq!(s.window_position, "cursor"); + assert_eq!(s.window_width, 420); + assert_eq!(s.window_height, 560); } #[test] fn double_init_is_idempotent() { let db = test_db(); db.init_tables().unwrap(); - assert_eq!(db.get_settings().unwrap().max_history, 500); + assert_eq!(db.get_settings().unwrap().max_history, 10000); } // ── Insert & Get ──────────────────────────────────────────────── diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f9755a7..817cb35 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -14,13 +14,85 @@ use tauri::{ use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, Modifiers, Shortcut, ShortcutState}; +fn show_window(app: &tauri::AppHandle) { + if let Some(window) = app.get_webview_window("main") { + // Position window before showing based on settings + if let Some(db) = app.try_state::() { + if let Ok(settings) = db.0.get_settings() { + position_window(&window, &settings.window_position); + let _ = window.set_size(tauri::LogicalSize::new( + settings.window_width as f64, + settings.window_height as f64, + )); + } + } + let _ = window.show(); + let _ = window.set_focus(); + } +} + fn toggle_window(app: &tauri::AppHandle) { if let Some(window) = app.get_webview_window("main") { if window.is_visible().unwrap_or(false) { let _ = window.hide(); } else { - let _ = window.show(); - let _ = window.set_focus(); + show_window(app); + } + } +} + +fn position_window(window: &tauri::WebviewWindow, position: &str) { + match position { + "center" => { + let _ = window.center(); + } + "top-right" => { + if let Ok(monitor) = window.current_monitor() { + if let Some(monitor) = monitor { + let size = monitor.size(); + let scale = monitor.scale_factor(); + let win_size = window.outer_size().unwrap_or(tauri::PhysicalSize::new(420, 560)); + let x = (size.width as f64 / scale) - (win_size.width as f64 / scale) - 10.0; + let _ = window.set_position(tauri::LogicalPosition::new(x, 30.0)); + } + } + } + "top-left" => { + let _ = window.set_position(tauri::LogicalPosition::new(10.0, 30.0)); + } + "bottom-right" => { + if let Ok(monitor) = window.current_monitor() { + if let Some(monitor) = monitor { + let size = monitor.size(); + let scale = monitor.scale_factor(); + let win_size = window.outer_size().unwrap_or(tauri::PhysicalSize::new(420, 560)); + let x = (size.width as f64 / scale) - (win_size.width as f64 / scale) - 10.0; + let y = (size.height as f64 / scale) - (win_size.height as f64 / scale) - 10.0; + let _ = window.set_position(tauri::LogicalPosition::new(x, y)); + } + } + } + "bottom-left" => { + if let Ok(monitor) = window.current_monitor() { + if let Some(monitor) = monitor { + let size = monitor.size(); + let scale = monitor.scale_factor(); + let win_size = window.outer_size().unwrap_or(tauri::PhysicalSize::new(420, 560)); + let y = (size.height as f64 / scale) - (win_size.height as f64 / scale) - 10.0; + let _ = window.set_position(tauri::LogicalPosition::new(10.0, y)); + } + } + } + // "cursor" or default — position near the mouse cursor + _ => { + if let Ok(cursor) = window.cursor_position() { + let _ = window.set_position(tauri::LogicalPosition::new( + cursor.x - 210.0, + cursor.y + 10.0, + )); + } else { + let _ = window.center(); + } } } } @@ -66,16 +138,16 @@ pub fn run() { commands::set_setting, commands::get_paused, commands::set_paused, + commands::save_window_size, + commands::paste_and_refocus, ]) .setup(move |app| { - // Register the global hotkey let shortcut = Shortcut::new( Some(Modifiers::SUPER | Modifiers::SHIFT), Code::KeyV, ); app.global_shortcut().register(shortcut)?; - // Build the tray menu let show_i = MenuItem::with_id(app, "show", "Show maCopy", true, None::<&str>)?; let pause_i = CheckMenuItem::with_id(app, "pause", "Pause Monitoring", true, false, None::<&str>)?; let sep = PredefinedMenuItem::separator(app)?; @@ -84,7 +156,8 @@ pub fn run() { let menu = Menu::with_items(app, &[&show_i, &pause_i, &sep, &settings_i, &quit_i])?; - let _tray = TrayIconBuilder::new() + // Use with_id to prevent duplicate tray icons across hot-reloads + let _tray = TrayIconBuilder::with_id("macopy-tray") .icon(app.default_window_icon().unwrap().clone()) .menu(&menu) .show_menu_on_left_click(false) @@ -98,7 +171,6 @@ pub fn run() { paused_clone.store(!current, std::sync::atomic::Ordering::Relaxed); } "settings" => { - // Emit an event the frontend listens for to open settings panel if let Some(window) = app.get_webview_window("main") { let _ = window.show(); let _ = window.set_focus(); @@ -118,11 +190,9 @@ pub fn run() { }) .build(app)?; - // Hide from dock on macOS — app is menu-bar only #[cfg(target_os = "macos")] app.set_activation_policy(tauri::ActivationPolicy::Accessory); - // Close window on blur for popup-like behavior if let Some(window) = app.get_webview_window("main") { let w = window.clone(); window.on_window_event(move |event| { @@ -132,7 +202,6 @@ pub fn run() { }); } - // Start clipboard polling on a background thread clipboard::start_polling(db_for_polling, paused_for_polling); Ok(()) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index de64e8a..7c7d9e7 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -17,7 +17,9 @@ "title": "maCopy", "width": 420, "height": 560, - "resizable": false, + "resizable": true, + "minWidth": 320, + "minHeight": 300, "decorations": false, "visible": false, "alwaysOnTop": true, @@ -39,6 +41,14 @@ "core:window:allow-set-focus", "core:window:allow-close", "core:window:allow-is-visible", + "core:window:allow-set-size", + "core:window:allow-set-position", + "core:window:allow-inner-size", + "core:window:allow-outer-size", + "core:window:allow-center", + "core:window:allow-current-monitor", + "core:window:allow-cursor-position", + "core:window:allow-start-dragging", "core:event:default", "core:event:allow-emit", "core:event:allow-listen", diff --git a/src/App.tsx b/src/App.tsx index 63d5a5e..e64ccc8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,7 +14,9 @@ export default function App() { const [query, setQuery] = useState(""); const [showSettings, setShowSettings] = useState(false); const [settings, setSettings] = useState(null); - const [selectedIndex, setSelectedIndex] = useState(0); + const [selectedIds, setSelectedIds] = useState>(new Set()); + const [anchorIndex, setAnchorIndex] = useState(0); + const [focusIndex, setFocusIndex] = useState(0); const [contextMenu, setContextMenu] = useState<{ x: number; y: number; @@ -22,12 +24,14 @@ export default function App() { } | null>(null); const pollRef = useRef>(); + const resizeTimer = useRef>(); const loadEntries = useCallback(async () => { try { + const limit = settings?.max_history ?? 10000; const data: ClipboardEntry[] = query.trim() - ? await invoke("search_entries", { query, limit: settings?.max_history ?? 500 }) - : await invoke("get_entries", { limit: settings?.max_history ?? 500 }); + ? await invoke("search_entries", { query, limit }) + : await invoke("get_entries", { limit }); setEntries(data); } catch (e) { console.error("Failed to load entries:", e); @@ -47,14 +51,12 @@ export default function App() { loadSettings(); }, [loadSettings]); - // Poll for new entries while window is visible useEffect(() => { loadEntries(); pollRef.current = setInterval(loadEntries, 1000); return () => clearInterval(pollRef.current); }, [loadEntries]); - // Listen for the tray "Settings…" menu click useEffect(() => { const unlisten = listen("open-settings", () => setShowSettings(true)); return () => { @@ -62,23 +64,51 @@ export default function App() { }; }, []); - const copyAndClose = useCallback(async (entry: ClipboardEntry) => { - try { - if (entry.content_type === "image") { - // For images stored as data URIs, copy the raw base64 URI text - await writeText(entry.content); - } else { - await writeText(entry.content); - } - await getCurrentWindow().hide(); - } catch (e) { - console.error("Copy failed:", e); - } + // Persist window size on resize (debounced) + useEffect(() => { + const handleResize = () => { + clearTimeout(resizeTimer.current); + resizeTimer.current = setTimeout(async () => { + const win = getCurrentWindow(); + const size = await win.innerSize(); + const scaleFactor = await win.scaleFactor(); + const w = Math.round(size.width / scaleFactor); + const h = Math.round(size.height / scaleFactor); + invoke("save_window_size", { width: w, height: h }); + }, 500); + }; + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); }, []); + // Copy selected entries and paste into previous app + const pasteEntries = useCallback( + async (entriesToPaste: ClipboardEntry[]) => { + if (entriesToPaste.length === 0) return; + try { + const combined = entriesToPaste + .map((e) => e.content) + .join("\n"); + await writeText(combined); + await invoke("paste_and_refocus"); + } catch (e) { + console.error("Paste failed:", e); + } + }, + [] + ); + + const pasteSingle = useCallback( + (entry: ClipboardEntry) => pasteEntries([entry]), + [pasteEntries] + ); + const handleDelete = useCallback( - async (id: number) => { - await invoke("delete_entry", { id }); + async (ids: number[]) => { + for (const id of ids) { + await invoke("delete_entry", { id }); + } + setSelectedIds(new Set()); loadEntries(); }, [loadEntries] @@ -92,37 +122,96 @@ export default function App() { [loadEntries] ); + // Selection helpers + const selectOnly = useCallback((index: number, entries: ClipboardEntry[]) => { + const e = entries[index]; + if (e) { + setSelectedIds(new Set([e.id])); + setAnchorIndex(index); + setFocusIndex(index); + } + }, []); + + const selectRange = useCallback( + (from: number, to: number, entries: ClipboardEntry[]) => { + const lo = Math.min(from, to); + const hi = Math.max(from, to); + const ids = new Set(); + for (let i = lo; i <= hi; i++) { + if (entries[i]) ids.add(entries[i].id); + } + setSelectedIds(ids); + setFocusIndex(to); + }, + [] + ); + + const toggleSelect = useCallback( + (index: number, entries: ClipboardEntry[]) => { + const e = entries[index]; + if (!e) return; + setSelectedIds((prev) => { + const next = new Set(prev); + if (next.has(e.id)) next.delete(e.id); + else next.add(e.id); + return next; + }); + setAnchorIndex(index); + setFocusIndex(index); + }, + [] + ); + // Keyboard navigation useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { - // Cmd+1 through Cmd+9 for quick paste - if (e.metaKey && e.key >= "1" && e.key <= "9") { + // Cmd+1-9 quick paste + if (e.metaKey && !e.shiftKey && e.key >= "1" && e.key <= "9") { e.preventDefault(); const idx = parseInt(e.key) - 1; - if (idx < entries.length) { - copyAndClose(entries[idx]); - } + if (idx < entries.length) pasteSingle(entries[idx]); + return; + } + + // Cmd+A select all + if (e.metaKey && e.key === "a" && document.activeElement?.tagName !== "INPUT") { + e.preventDefault(); + setSelectedIds(new Set(entries.map((e) => e.id))); return; } if (e.key === "ArrowDown") { e.preventDefault(); - setSelectedIndex((i) => Math.min(i + 1, entries.length - 1)); + const next = Math.min(focusIndex + 1, entries.length - 1); + if (e.shiftKey) { + selectRange(anchorIndex, next, entries); + } else { + selectOnly(next, entries); + } } else if (e.key === "ArrowUp") { e.preventDefault(); - setSelectedIndex((i) => Math.max(i - 1, 0)); + const next = Math.max(focusIndex - 1, 0); + if (e.shiftKey) { + selectRange(anchorIndex, next, entries); + } else { + selectOnly(next, entries); + } } else if (e.key === "Enter") { e.preventDefault(); - if (entries[selectedIndex]) copyAndClose(entries[selectedIndex]); + const selected = entries.filter((e) => selectedIds.has(e.id)); + if (selected.length > 0) pasteEntries(selected); + else if (entries[focusIndex]) pasteSingle(entries[focusIndex]); } else if (e.key === "Delete" || e.key === "Backspace") { - // Only handle Delete/Backspace when search is not focused if (document.activeElement?.tagName !== "INPUT") { e.preventDefault(); - if (entries[selectedIndex]) handleDelete(entries[selectedIndex].id); + const ids = Array.from(selectedIds); + if (ids.length > 0) handleDelete(ids); } } else if (e.key === "Escape") { if (showSettings) { setShowSettings(false); + } else if (selectedIds.size > 1) { + selectOnly(focusIndex, entries); } else { getCurrentWindow().hide(); } @@ -131,17 +220,21 @@ export default function App() { window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); - }, [entries, selectedIndex, copyAndClose, handleDelete, showSettings]); + }, [ + entries, focusIndex, anchorIndex, selectedIds, showSettings, + pasteSingle, pasteEntries, handleDelete, selectOnly, selectRange, + ]); - // Close context menu on any click useEffect(() => { const close = () => setContextMenu(null); window.addEventListener("click", close); return () => window.removeEventListener("click", close); }, []); + const selectedCount = selectedIds.size; + return ( -
+
{showSettings && settings ? ( ) : ( <> - {/* Drag handle + search */} -
+
{ setQuery(v); - setSelectedIndex(0); + setFocusIndex(0); + setAnchorIndex(0); + setSelectedIds(new Set()); }} />
- {/* Entry list */}
pasteSingle(entry)} + onCtrlClick={(index) => toggleSelect(index, entries)} + onShiftClick={(index) => selectRange(anchorIndex, index, entries)} + onPlainClick={(index) => selectOnly(index, entries)} onContextMenu={(e, entry) => { e.preventDefault(); + if (!selectedIds.has(entry.id)) { + const idx = entries.findIndex((x) => x.id === entry.id); + selectOnly(idx, entries); + } setContextMenu({ x: e.clientX, y: e.clientY, entry }); }} - setSelectedIndex={setSelectedIndex} + setFocusIndex={setFocusIndex} />
- {/* Hint bar */} -
- ⌘1-9 quick paste +
+ + {selectedCount > 1 ? `${selectedCount} selected` : "⌘1-9 quick paste"} + {entries.length} items
@@ -192,9 +294,16 @@ export default function App() { x={contextMenu.x} y={contextMenu.y} entry={contextMenu.entry} - onCopy={() => copyAndClose(contextMenu.entry)} + selectedCount={selectedCount} + onCopy={() => { + const selected = entries.filter((e) => selectedIds.has(e.id)); + pasteEntries(selected.length > 0 ? selected : [contextMenu.entry]); + }} onPin={() => handleTogglePin(contextMenu.entry.id)} - onDelete={() => handleDelete(contextMenu.entry.id)} + onDelete={() => { + const ids = selectedIds.size > 0 ? Array.from(selectedIds) : [contextMenu.entry.id]; + handleDelete(ids); + }} /> )}
diff --git a/src/components/ClipboardList.test.tsx b/src/components/ClipboardList.test.tsx index 3f011c8..d932203 100644 --- a/src/components/ClipboardList.test.tsx +++ b/src/components/ClipboardList.test.tsx @@ -7,16 +7,20 @@ describe("ClipboardList", () => { beforeEach(() => resetIdCounter()); const defaultProps = { - selectedIndex: 0, + selectedIds: new Set(), + focusIndex: 0, showImages: true, onSelect: vi.fn(), + onCtrlClick: vi.fn(), + onShiftClick: vi.fn(), + onPlainClick: vi.fn(), onContextMenu: vi.fn(), - setSelectedIndex: vi.fn(), + setFocusIndex: vi.fn(), }; it("shows empty state when no entries", () => { render(); - expect(screen.getByText("No clipboard history yet")).toBeInTheDocument(); + expect(screen.getByText("No clipboard history")).toBeInTheDocument(); }); it("renders text entries", () => { @@ -35,10 +39,10 @@ describe("ClipboardList", () => { it("shows pin indicator for pinned entries", () => { const entries = [makeEntry({ pinned: true, content: "Pinned item" })]; render(); - expect(screen.getByText("Pinned item")).toBeInTheDocument(); + expect(screen.getByText("PIN")).toBeInTheDocument(); }); - it("calls onSelect when entry is clicked", () => { + it("calls onSelect when entry is clicked without modifiers", () => { const onSelect = vi.fn(); const entries = [makeEntry({ content: "Click me" })]; render(); @@ -51,33 +55,25 @@ describe("ClipboardList", () => { const onContextMenu = vi.fn(); const entries = [makeEntry({ content: "Right click me" })]; render( - + ); fireEvent.contextMenu(screen.getByText("Right click me")); expect(onContextMenu).toHaveBeenCalled(); }); - it("updates selectedIndex on mouse enter", () => { - const setSelectedIndex = vi.fn(); + it("updates focusIndex on mouse enter", () => { + const setFocusIndex = vi.fn(); const entries = [ makeEntry({ content: "First" }), makeEntry({ content: "Second" }), ]; render( - + ); fireEvent.mouseEnter(screen.getByText("Second")); - expect(setSelectedIndex).toHaveBeenCalledWith(1); + expect(setFocusIndex).toHaveBeenCalledWith(1); }); it("shows cmd+N badges for first 9 entries", () => { @@ -93,10 +89,7 @@ describe("ClipboardList", () => { it("renders image entries when showImages is true", () => { const entries = [ - makeEntry({ - content: "data:image/png;base64,abc", - content_type: "image", - }), + makeEntry({ content: "data:image/png;base64,abc", content_type: "image" }), ]; render(); const img = screen.getByAltText("Clipboard image"); @@ -106,21 +99,20 @@ describe("ClipboardList", () => { it("renders image entries as text when showImages is false", () => { const entries = [ - makeEntry({ - content: "data:image/png;base64,abc", - content_type: "image", - }), + makeEntry({ content: "data:image/png;base64,abc", content_type: "image" }), ]; render(); expect(screen.queryByAltText("Clipboard image")).not.toBeInTheDocument(); expect(screen.getByText("data:image/png;base64,abc")).toBeInTheDocument(); }); - it("shows relative time for entries", () => { - const entries = [ - makeEntry({ created_at: new Date().toISOString(), content: "Recent" }), - ]; - render(); - expect(screen.getByText("just now")).toBeInTheDocument(); + it("highlights selected entries", () => { + const entries = [makeEntry({ content: "Selected" })]; + const selectedIds = new Set([entries[0].id]); + const { container } = render( + + ); + const row = container.querySelector(".divide-y > div"); + expect(row?.className).toContain("bg-accent/10"); }); }); diff --git a/src/components/ClipboardList.tsx b/src/components/ClipboardList.tsx index f884e90..5a5e55c 100644 --- a/src/components/ClipboardList.tsx +++ b/src/components/ClipboardList.tsx @@ -3,42 +3,39 @@ import type { ClipboardEntry } from "../types"; interface Props { entries: ClipboardEntry[]; - selectedIndex: number; + selectedIds: Set; + focusIndex: number; showImages: boolean; onSelect: (entry: ClipboardEntry) => void; + onCtrlClick: (index: number) => void; + onShiftClick: (index: number) => void; + onPlainClick: (index: number) => void; onContextMenu: (e: React.MouseEvent, entry: ClipboardEntry) => void; - setSelectedIndex: (i: number) => void; + setFocusIndex: (i: number) => void; } function timeAgo(dateStr: string): string { const seconds = Math.floor( (Date.now() - new Date(dateStr).getTime()) / 1000 ); - if (seconds < 60) return "just now"; - if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`; - if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`; - return `${Math.floor(seconds / 86400)}d ago`; + if (seconds < 60) return "now"; + if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; + if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`; + return `${Math.floor(seconds / 86400)}d`; } -function EntryIcon({ type }: { type: string }) { - if (type === "image") { - return ( - - - - ); - } - if (type === "file") { - return ( - - - - ); - } +function TypeBadge({ type }: { type: string }) { + const label = type === "image" ? "IMG" : type === "file" ? "FILE" : "TXT"; + const color = + type === "image" + ? "text-purple-400" + : type === "file" + ? "text-orange-400" + : "text-text-secondary"; return ( - - - + + {label} + ); } @@ -54,18 +51,18 @@ function EntryPreview({ Clipboard image ); } const display = - entry.content.length > 200 - ? entry.content.slice(0, 200) + "…" + entry.content.length > 160 + ? entry.content.slice(0, 160) + "…" : entry.content; return ( - + {display} ); @@ -73,66 +70,87 @@ function EntryPreview({ export default function ClipboardList({ entries, - selectedIndex, + selectedIds, + focusIndex, showImages, onSelect, + onCtrlClick, + onShiftClick, + onPlainClick, onContextMenu, - setSelectedIndex, + setFocusIndex, }: Props) { const listRef = useRef(null); - // Scroll selected entry into view useEffect(() => { - const el = listRef.current?.children[selectedIndex] as HTMLElement | undefined; + const el = listRef.current?.children[focusIndex] as HTMLElement | undefined; el?.scrollIntoView?.({ block: "nearest" }); - }, [selectedIndex]); + }, [focusIndex]); if (entries.length === 0) { return ( -
- No clipboard history yet +
+ + + + No clipboard history
); } return ( -
- {entries.map((entry, i) => ( -
onSelect(entry)} - onContextMenu={(e) => onContextMenu(e, entry)} - onMouseEnter={() => setSelectedIndex(i)} - > - {/* Number badge for first 9 items */} -
- {i < 9 ? ( - - ⌘{i + 1} +
+ {entries.map((entry, i) => { + const isSelected = selectedIds.has(entry.id); + const isFocused = i === focusIndex; + + return ( +
{ + if (e.metaKey || e.ctrlKey) { + onCtrlClick(i); + } else if (e.shiftKey) { + onShiftClick(i); + } else { + onSelect(entry); + } + }} + onContextMenu={(e) => onContextMenu(e, entry)} + onMouseEnter={() => setFocusIndex(i)} + > + {/* Left column: shortcut badge or type */} +
+ {i < 9 ? ( + + ⌘{i + 1} + + ) : ( + + )} +
+ + {/* Content */} +
+ +
+ + {/* Right column: meta */} +
+ {entry.pinned && ( + PIN + )} + + {timeAgo(entry.created_at)} - ) : ( - - )} +
- -
- -
- -
- {entry.pinned && ( - - - - )} - - {timeAgo(entry.created_at)} - -
-
- ))} + ); + })}
); } diff --git a/src/components/ContextMenu.test.tsx b/src/components/ContextMenu.test.tsx index c255903..61fc6ef 100644 --- a/src/components/ContextMenu.test.tsx +++ b/src/components/ContextMenu.test.tsx @@ -8,14 +8,15 @@ describe("ContextMenu", () => { x: 100, y: 100, entry: makeEntry(), + selectedCount: 1, onCopy: vi.fn(), onPin: vi.fn(), onDelete: vi.fn(), }; - it("renders Copy, Pin, and Delete actions", () => { + it("renders Paste, Pin, and Delete actions", () => { render(); - expect(screen.getByText("Copy")).toBeInTheDocument(); + expect(screen.getByText("Paste")).toBeInTheDocument(); expect(screen.getByText("Pin")).toBeInTheDocument(); expect(screen.getByText("Delete")).toBeInTheDocument(); }); @@ -26,10 +27,16 @@ describe("ContextMenu", () => { expect(screen.getByText("Unpin")).toBeInTheDocument(); }); - it("calls onCopy when Copy is clicked", () => { + it("shows multi-select labels when count > 1", () => { + render(); + expect(screen.getByText("Paste 3 items")).toBeInTheDocument(); + expect(screen.getByText("Delete 3 items")).toBeInTheDocument(); + }); + + it("calls onCopy when Paste is clicked", () => { const onCopy = vi.fn(); render(); - fireEvent.click(screen.getByText("Copy")); + fireEvent.click(screen.getByText("Paste")); expect(onCopy).toHaveBeenCalledOnce(); }); diff --git a/src/components/ContextMenu.tsx b/src/components/ContextMenu.tsx index 650181c..f72830a 100644 --- a/src/components/ContextMenu.tsx +++ b/src/components/ContextMenu.tsx @@ -4,40 +4,51 @@ interface Props { x: number; y: number; entry: ClipboardEntry; + selectedCount: number; onCopy: () => void; onPin: () => void; onDelete: () => void; } -export default function ContextMenu({ x, y, entry, onCopy, onPin, onDelete }: Props) { - // Prevent the menu from going off-screen - const adjustedX = Math.min(x, window.innerWidth - 160); - const adjustedY = Math.min(y, window.innerHeight - 120); +export default function ContextMenu({ + x, + y, + entry, + selectedCount, + onCopy, + onPin, + onDelete, +}: Props) { + const adjustedX = Math.min(x, window.innerWidth - 170); + const adjustedY = Math.min(y, window.innerHeight - 130); + const multi = selectedCount > 1; return (
e.stopPropagation()} > - + {!multi && ( + + )}
); diff --git a/src/components/SearchBar.test.tsx b/src/components/SearchBar.test.tsx index 502fece..1eb5074 100644 --- a/src/components/SearchBar.test.tsx +++ b/src/components/SearchBar.test.tsx @@ -6,9 +6,7 @@ import SearchBar from "./SearchBar"; describe("SearchBar", () => { it("renders with placeholder text", () => { render( {}} />); - expect( - screen.getByPlaceholderText("Search clipboard history…") - ).toBeInTheDocument(); + expect(screen.getByPlaceholderText("Search…")).toBeInTheDocument(); }); it("displays the current value", () => { @@ -21,17 +19,16 @@ describe("SearchBar", () => { const user = userEvent.setup(); render(); - const input = screen.getByPlaceholderText("Search clipboard history…"); + const input = screen.getByPlaceholderText("Search…"); await user.type(input, "test"); - expect(onChange).toHaveBeenCalledTimes(4); // one per character + expect(onChange).toHaveBeenCalledTimes(4); expect(onChange).toHaveBeenLastCalledWith("t"); }); it("auto-focuses the input on mount", () => { render( {}} />); - const input = screen.getByPlaceholderText("Search clipboard history…"); - // The focus happens via setTimeout, so we check the element exists + const input = screen.getByPlaceholderText("Search…"); expect(input).toBeInTheDocument(); }); }); diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx index b58259a..7f81ac4 100644 --- a/src/components/SearchBar.tsx +++ b/src/components/SearchBar.tsx @@ -8,7 +8,6 @@ interface Props { export default function SearchBar({ value, onChange }: Props) { const inputRef = useRef(null); - // Auto-focus search when window appears useEffect(() => { const handleFocus = () => { setTimeout(() => inputRef.current?.focus(), 50); @@ -21,7 +20,7 @@ export default function SearchBar({ value, onChange }: Props) { return (
onChange(e.target.value)} - placeholder="Search clipboard history…" - className="w-full pl-9 pr-3 py-2 bg-surface-hover border border-border rounded-lg - text-sm text-text-primary placeholder:text-text-secondary - focus:outline-none focus:border-accent transition-colors" + placeholder="Search…" + className="w-full pl-8 pr-3 py-1.5 bg-surface-hover border border-border rounded-md + text-[13px] text-text-primary placeholder:text-text-secondary + focus:outline-none focus:border-accent/50 transition-colors" />
); diff --git a/src/components/SettingsPanel.test.tsx b/src/components/SettingsPanel.test.tsx index 4168c7c..32477ce 100644 --- a/src/components/SettingsPanel.test.tsx +++ b/src/components/SettingsPanel.test.tsx @@ -30,27 +30,34 @@ describe("SettingsPanel", () => { it("shows show images toggle", () => { render(); - expect(screen.getByText("Show images in history")).toBeInTheDocument(); + expect(screen.getByText("Show image previews")).toBeInTheDocument(); }); it("shows max history buttons", () => { render(); - expect(screen.getByText("100")).toBeInTheDocument(); - expect(screen.getByText("500")).toBeInTheDocument(); - expect(screen.getByText("1000")).toBeInTheDocument(); + expect(screen.getByText("1K")).toBeInTheDocument(); + expect(screen.getByText("5K")).toBeInTheDocument(); + expect(screen.getByText("10K")).toBeInTheDocument(); + expect(screen.getByText("50K")).toBeInTheDocument(); + }); + + it("shows window position buttons", () => { + render(); + expect(screen.getByText("Near cursor")).toBeInTheDocument(); + expect(screen.getByText("Center")).toBeInTheDocument(); + expect(screen.getByText("Top right")).toBeInTheDocument(); }); it("highlights current max history value", () => { - const settings = makeSettings({ max_history: 1000 }); + const settings = makeSettings({ max_history: 50000 }); render(); - const btn = screen.getByText("1000"); + const btn = screen.getByText("50K"); expect(btn.className).toContain("bg-accent"); }); it("calls onClose when close button clicked", () => { const onClose = vi.fn(); render(); - // The close button is the SVG button in the header const closeBtn = screen.getByText("Settings").parentElement!.querySelector("button")!; fireEvent.click(closeBtn); expect(onClose).toHaveBeenCalledOnce(); @@ -61,7 +68,6 @@ describe("SettingsPanel", () => { render(); const switches = screen.getAllByRole("switch"); - // Second switch is "Show images" fireEvent.click(switches[1]); await waitFor(() => { @@ -73,15 +79,15 @@ describe("SettingsPanel", () => { }); it("invokes set_setting when changing max_history", async () => { - mockInvoke.mockResolvedValue(makeSettings({ max_history: 100 })); + mockInvoke.mockResolvedValue(makeSettings({ max_history: 1000 })); render(); - fireEvent.click(screen.getByText("100")); + fireEvent.click(screen.getByText("1K")); await waitFor(() => { expect(mockInvoke).toHaveBeenCalledWith("set_setting", { key: "max_history", - value: "100", + value: "1000", }); }); }); diff --git a/src/components/SettingsPanel.tsx b/src/components/SettingsPanel.tsx index 58a0227..c1dc315 100644 --- a/src/components/SettingsPanel.tsx +++ b/src/components/SettingsPanel.tsx @@ -8,6 +8,17 @@ interface Props { onUpdate: (settings: Settings) => void; } +const HISTORY_OPTIONS = [1000, 5000, 10000, 50000]; + +const POSITION_OPTIONS: { value: string; label: string }[] = [ + { value: "cursor", label: "Near cursor" }, + { value: "center", label: "Center" }, + { value: "top-right", label: "Top right" }, + { value: "top-left", label: "Top left" }, + { value: "bottom-right", label: "Bottom right" }, + { value: "bottom-left", label: "Bottom left" }, +]; + export default function SettingsPanel({ settings, onClose, onUpdate }: Props) { const updateSetting = useCallback( async (key: string, value: string) => { @@ -26,63 +37,82 @@ export default function SettingsPanel({ settings, onClose, onUpdate }: Props) { return (
- {/* Header */} -
-

Settings

+
+

+ Settings +

- {/* Settings body */}
- {/* Launch at login */} updateSetting("launch_at_login", String(v))} /> - {/* Show images */} updateSetting("show_images", String(v))} /> - {/* Max history */}
-