From 3da246b3caced0794da6825dc24230ae3ca4fec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Thu, 3 Feb 2022 13:08:35 +0100 Subject: [PATCH] Icon, Tab state persistence --- src/Core/FileTime.Core/Models/IContainer.cs | 2 +- .../FileTime.Core/Models/VirtualContainer.cs | 2 +- .../FileTime.Core/Providers/TopContainer.cs | 2 +- .../FileTime.Core/Timeline/TimeContainer.cs | 4 +- .../FileTime.Core/Timeline/TimeProvider.cs | 2 +- src/GuiApp/Assets/filetime.ico | Bin 0 -> 34774 bytes src/GuiApp/Assets/filetime.svg | 12 ++ src/GuiApp/Assets/filetime.xcf | Bin 0 -> 17414 bytes src/GuiApp/Assets/filetime2.xcf | Bin 0 -> 23445 bytes .../FileTime.Avalonia/Application/AppState.cs | 9 ++ .../FileTime.Avalonia/Assets/filetime.ico | Bin 0 -> 33552 bytes .../FileTime.Avalonia/Assets/filetime2.ico | Bin 0 -> 67646 bytes .../FileTime.Avalonia/Assets/filetime3.ico | Bin 0 -> 101846 bytes .../FileTime.Avalonia.csproj | 4 + .../Models/Persistence/PersistenceRoot.cs | 7 + .../Models/Persistence/TabState.cs | 18 +++ .../Models/Persistence/TabStates.cs | 10 ++ .../Services/StatePersistenceService.cs | 131 ++++++++++++++++++ src/GuiApp/FileTime.Avalonia/Startup.cs | 1 + .../ViewModels/MainPageViewModel.cs | 72 ++++++++-- .../FileTime.Avalonia/Views/MainWindow.axaml | 10 +- .../Views/MainWindow.axaml.cs | 6 + .../LocalContentProvider.cs | 4 +- .../FileTime.Providers.Local/LocalFolder.cs | 4 +- .../SmbContentProvider.cs | 4 +- .../FileTime.Providers.Smb/SmbFolder.cs | 4 +- .../FileTime.Providers.Smb/SmbServer.cs | 2 +- .../FileTime.Providers.Smb/SmbShare.cs | 4 +- 28 files changed, 283 insertions(+), 31 deletions(-) create mode 100644 src/GuiApp/Assets/filetime.ico create mode 100644 src/GuiApp/Assets/filetime.svg create mode 100644 src/GuiApp/Assets/filetime.xcf create mode 100644 src/GuiApp/Assets/filetime2.xcf create mode 100644 src/GuiApp/FileTime.Avalonia/Assets/filetime.ico create mode 100644 src/GuiApp/FileTime.Avalonia/Assets/filetime2.ico create mode 100644 src/GuiApp/FileTime.Avalonia/Assets/filetime3.ico create mode 100644 src/GuiApp/FileTime.Avalonia/Models/Persistence/PersistenceRoot.cs create mode 100644 src/GuiApp/FileTime.Avalonia/Models/Persistence/TabState.cs create mode 100644 src/GuiApp/FileTime.Avalonia/Models/Persistence/TabStates.cs create mode 100644 src/GuiApp/FileTime.Avalonia/Services/StatePersistenceService.cs diff --git a/src/Core/FileTime.Core/Models/IContainer.cs b/src/Core/FileTime.Core/Models/IContainer.cs index 5f73be8..fcd1797 100644 --- a/src/Core/FileTime.Core/Models/IContainer.cs +++ b/src/Core/FileTime.Core/Models/IContainer.cs @@ -10,7 +10,7 @@ namespace FileTime.Core.Models Task?> GetElements(CancellationToken token = default); Task Refresh(); - Task GetByPath(string path); + Task GetByPath(string path, bool acceptDeepestMatch = false); Task CreateContainer(string name); Task CreateElement(string name); diff --git a/src/Core/FileTime.Core/Models/VirtualContainer.cs b/src/Core/FileTime.Core/Models/VirtualContainer.cs index dec2531..6db050d 100644 --- a/src/Core/FileTime.Core/Models/VirtualContainer.cs +++ b/src/Core/FileTime.Core/Models/VirtualContainer.cs @@ -76,7 +76,7 @@ namespace FileTime.Core.Models ?.ToList().AsReadOnly(); } - public async Task GetByPath(string path) => await BaseContainer.GetByPath(path); + public async Task GetByPath(string path, bool acceptDeepestMatch = false) => await BaseContainer.GetByPath(path, acceptDeepestMatch); public IContainer? GetParent() => BaseContainer.GetParent(); diff --git a/src/Core/FileTime.Core/Providers/TopContainer.cs b/src/Core/FileTime.Core/Providers/TopContainer.cs index aa18c23..52387f4 100644 --- a/src/Core/FileTime.Core/Providers/TopContainer.cs +++ b/src/Core/FileTime.Core/Providers/TopContainer.cs @@ -49,7 +49,7 @@ namespace FileTime.Core.Providers public Task Delete() => throw new NotImplementedException(); - public Task GetByPath(string path) => throw new NotImplementedException(); + public Task GetByPath(string path, bool acceptDeepestMatch = false) => throw new NotImplementedException(); public IContainer? GetParent() => null; diff --git a/src/Core/FileTime.Core/Timeline/TimeContainer.cs b/src/Core/FileTime.Core/Timeline/TimeContainer.cs index 236a961..5b85160 100644 --- a/src/Core/FileTime.Core/Timeline/TimeContainer.cs +++ b/src/Core/FileTime.Core/Timeline/TimeContainer.cs @@ -46,7 +46,7 @@ namespace FileTime.Core.Timeline public Task Delete() => Task.CompletedTask; - public async Task GetByPath(string path) + public async Task GetByPath(string path, bool acceptDeepestMatch = false) { var paths = path.Split(Constants.SeparatorChar); @@ -59,7 +59,7 @@ namespace FileTime.Core.Timeline if (item is IContainer container) { - return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1))); + return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)), acceptDeepestMatch); } return null; diff --git a/src/Core/FileTime.Core/Timeline/TimeProvider.cs b/src/Core/FileTime.Core/Timeline/TimeProvider.cs index 1687a3a..75d05fc 100644 --- a/src/Core/FileTime.Core/Timeline/TimeProvider.cs +++ b/src/Core/FileTime.Core/Timeline/TimeProvider.cs @@ -50,7 +50,7 @@ namespace FileTime.Core.Timeline public Task Delete() => throw new NotSupportedException(); - public Task GetByPath(string path) + public Task GetByPath(string path, bool acceptDeepestMatch = false) { throw new NotImplementedException(); } diff --git a/src/GuiApp/Assets/filetime.ico b/src/GuiApp/Assets/filetime.ico new file mode 100644 index 0000000000000000000000000000000000000000..0285c3018f1b919fae24fd3ee4371d21b398ac4f GIT binary patch literal 34774 zcmeG_30M?Ix;5O)0K;H79^RM`vI!n>iCHg5MDPNIcp-u;Nq+@X9O)zTe*geperXfGQCq zKikM=fX%=kCj+|5fDU9L4qY8-fb9@CFdv$zf|S8ROH)%*8{x14oiQA>;bfd2lfmhT z2LmLp#nI5+>IL?{{(0uD2odn!>9swT53xW5U3gBDN0RBMuxCSwU4xHE#Xp#aHc^wTLv}knX90FY} z9JFy1J0k^BD7{LtPbWg0VxQEni5Y@+DDtF#@Pi5h@)I!-ddc_z5hq2S;FBsbx)e^Y z67uN7R8LR*>8GoYS9BpaVt|DfbVrB5KSEwE9m>nnPsr2#B5;gOAPgmgJl;e(OAAs6 z5(ExT@t>sW_fiotK!>B_%3A>F7QjQQ)Kn1{%Zo&}I3iJZDuW@|gzi(|BPH;Khur%V zcz-f`?tCzNzAKpR;s%^t-GS4>#lYEpDR6RM4xAUS0M1K&fbjZLAS~|&0XO=9@P-Ji zh6j3PTn!IlHM}umfz1G$f!|#QOjsC{3<#iB;y?z7Iw}zu;stT$oA7avT@tLY6!R)5 zNC5?m1ep*Za8w#dfpj~n8}%39X=C?5*h!a56X?&*=V_2vjMiZA>xu6kVWCggZ_MD3 z^!)|ReLe#_orQuA1V7au5<`X<{O3{PH6ZS7@z@#sq2}KJE`Z>G^~FH&Q^tdVCNVm& zu|fDRoiwT8W*|NK5D$Yt(xUz9&%@8dfX~Ov{unYw_z$l~t{!?c(BbijyK#u=aSgOl zD*`<<_N4sbYN0%9@RZHJ%KWPMgXWX|5%PFME25!zrTj;zLQs*PU{`s1V-k=kW2i7V z&cG$r)K5T)##p5ZF!HdCFGB|Krzf-~j6j7lBs?TM9Du$E2>Ad!LD%zWamfeZ5yEw8 zIMGg&13<^@5c=q)BK?pb^P4UQ;iYWafPO!-C6sSfan zfQQ28)cFgsXHvJwgK#}P33z<{J@C%Fj<2cb{{sF2zsD3-K7-Gw=;&kD*I`asU^DPX z$bbZR3ls#xZ8>$Lac~zwik((r_E)FFIOx%cWo<}~H>BV-^B!Lql!6COPXF^3c{T3Fu*}NCg%dAke4q+r820XGAP0fVNR)kiX>P%n}MOu z0JeNMkf)yW2+)!wg77|Z*^8H>v2{@Z?8T)7|C7*(xWn=IIarC(593D*!Kg$F!Kg$F z!6H!v;A3Uwl9gCFz$dg$9D(`<-XJSnCw86#X3txItra^h_5^dh1AzOd`+@ss2fzo> zhk?h@Jm8fMfB%wE3cOBV1HRwg1b+YbE8O4I1B4a5AOKrKhU>?0kCPDYZzA?NA@eM- z8DKN;@@BxC1vUe02G|U+8DKNOW`NBAn*lZhLz)3obNFXE7{@Eh#05CD#!r)n! zXdY_&@-iTCa!@O>Gcc%TN#(K}AUOuP#_Yf#8zt2bv3WSRFaqh|VAs?#FQ9FbrlZEn zd(CJcO}g^HfbvzRd)_WYp?^^-M8J4R!pC_Sybt?5n|OWX!*!@vq$cBYTdLnx7c$E9!rx=3is_7E{Lg znbE$Y_?5c<rq-1ASG3#HH3J80{; z*S|nl@{1mMeCn@`cH&NK8%jrXUsp@{{rGCAL5%=4i||H_Wc*x z|3hQ{1;($a|A!?1FED<_@yGT*esRJ{WT^XpNai21{|9~kWgLIx_$T^d&`L)8Wc-6> zmr%@T|HZ}6Y@Usu=$=6?nbWWE`EQ`Sl4bq{^}j6hFDQQ5=3hwsvdya*zdVJi(T1rr zz*I9KpIZ(4@)fE`Ro%Xd1{AQrqN)Q5^r=i%(Z0(16$+^2fI@9*FsR^w8rqc*QQ83| zbg9LnqyuW{R#FJtfQmMu%K1}L70bqEfXx7#0X73{2G|U+8DKNOX5i(@zyQB?gjFZ1 zfd9f$49+MW5#$yy4Ty?gh=VtV({Ou{Nc;jFoIaX{!&k;&FB&IQ!dJ=MBI>Aw^a5#| zPKWq&(cvp>Sb3=gtD0Xne}C@rqv-lmbp6ulaDKQYH5>t)KPcB{IE^l%($Sl##PYC) z11^ufvkGg#>Dargq;MFB7;rjtObqz^G@3v~_^IFwyItRq8GV38*(kV006_ea~$2V7lxrXhN17Ey*8u>0nnQ;*YUl@pB()4 zW>Ct_;E@Gw*SfrJ3UhxJCYOF|?UKFbFR>>7@}0lWSbKI!=Mm#ojhjt(T3yzNJh14) zMNTP+2{wXHJw9FJyGPVKx9IeyuJLwZdscjv>g9U&P!NBwuhsqZo2{)OWzW9ziRUM^ zHU&-#+SXLJaoa9vUO9+(n$~flV0Xm~&w7hU>!r>kroW-%|6zSk$4R3;H|vnv$}Y>2 z3FAy7asygQ-69i@78ggg*G0XX(|X8he$L4S56^wuYP;Xw$qEE_Wj{_X{95qMBrEIg zx_~p$VB_&BP?4~X;av5z2h`SB)V!Y3JIDvV_vPP;CS$shrQMsy`rf+hp>+Fpy=(} zb;n()$(=em)}(4r*0lVJF3XrC*Shek1lwH=w+g*|dSffjrMWKj{s|G{wt*0f@6~fAx3aQwG^)eee-iBO(HWIo{3Xcv z$_31uk$TxHZugsUOQodd~%Kg{GASUU*`kl+YYJ_Q)DL*Vq;7q7wY`Pn4VI zr)Jn`f$psFtDfASn%S8dHex*ZF<;y5>e>4x+4t96I9guyA5f5zvVQJ^EiE@zoImf8 z0SatRH2$zZq1$QMC4CTcu=}&AlPfP*HERPWw^LIpoU`+?Iwxs> zJWvnBZ2#xhFM%>=!31j8>b#hJiT?fQrcQ z#88d#E2j$XSJ~xExSckl)MZpt-;U24{GVQr_OQ>smS4U!?(WmBQ)etz96!D;J|)q! zWXHiP@mKD|#g}$2em>jK-@w3!-080Ad5z8I!mIM~s_vEkX8Wcv>}>sG`^TR&tj!Ji z>$Uu#bX9|*C!)mVz2ViH9{#)f^t$!+YdVA*Du3{>-`um;)No4krT?EU%F}ny3^~c5yg`Fs^Z95%yb!Xl(-@Fr@MP`1( zy*U+4H6@>=U9tCEuy$C)+fU|yk@NYIXzPpci&%{ z)t6UPeSOvHQ*Nt-H6IAtj+s3kn-H+-*Rn6ac06?ddzW-DFWU0!zN1BfbM7_S`t%BH z?R6)M7Z$73VA zE8WvAvk%n$mRum(S!1;-qkH(wiNQCnW*F)_ZvqN3Bk z)PI1`!O5S`eeeJW`@%LJ4B6@bLj5FbH+F%xwAKGxjbYIB& zMFIO7JKIhLMQJwHrL>i$mwh^^A9EXK23Oc-S<6Kw*$Nn^O7^_{-?1Z$GgzkUPjD)X9JlJd`W}Z_6P)%;ORiXR_2)zRzlJ94)+@N$ zaxrOz*|1ANJKd)^#dZq5`L^}Yt?Ej9=QsF;r~BUC5xLF1thX*REU4q#-Q0rIO7A2u zlh@z!8hP=Te_I&rb*$K3XKwVS-HwYFCRsd)HTT(?mUccksD3!*HT&{DmmOS1I`=G( V-qk*A^#JkBo$czF{=V + + + + + + + + + + \ No newline at end of file diff --git a/src/GuiApp/Assets/filetime.xcf b/src/GuiApp/Assets/filetime.xcf new file mode 100644 index 0000000000000000000000000000000000000000..635c49227f5b58bd15422ca39d52639a2e33d0f7 GIT binary patch literal 17414 zcmdU0&2J+~6|eqsJ8^6$Yll`Q%*Hce$b?Zg$z<6q+QWt&3B;$6xF8OQ*^JX;luWEi zfZ3BrfL8LA$jCxIt;CHBe*kyj$fb#d{0AUFTJ33Pkec7Cs*bDMPIr=?c03jR>b+N0 z{obqZs&2<^95idscK2$(*x1};jA;@ejQtLn12jsl0Qm<y4W9v&RjpBzKxs^56-u|+np?DS&fts_h*VwNgHxBBX?@*Z>K&|crw}^lLIb!q-Kij&fD!G3?HmIFy5;RE8#?{t+NQvW$%8YAVHDQZz)p147DZ%`YBGkYW z`Z(uAG5id&UzERSBOTu7Da`4=g^2~X> zTDAatR8FNLpZw&$_GVeJ>mx38r#*PvnepK5NfGg)gzgzl-LoX%ZGs#;H>Est9Est93>Ko#>HTzA$(?M2+FVD48>s%P;kxBv#` zr0M0G24EunvT~q@Xi3p?Sw5;~PU;&)2W3U=KgX%@O^%9tgH<_DLw5iJbK7+DZ38fo zepy-7RZ{d^mXGS0gD<3G6djZe`dHUrm=+cx-7-GcH7r9+TVNv-t5}>c3VeaVXc>(9 z?9#|vgdK^{BKFSPlr>3<*iYdP;i`)m9fp-q{JtXWfG5aBtoSqVLD&I9Kq&r7P#iGy z->{qgTM^9r1*Gu!biudTO&%|Li{M4P4NisS+c>%wBy9GoWz`n(6~$H>ew1EE>c0pL zKToa6TeOE31s1#7S$wlQ@1a#i+*$C@sytpiSp+ZQZEz|q&&Kh+Ac0nW#jew1DZ zbt9b@q2cF)v1sU;sacw#={BHY5!dia0=xN;ZYu*`PY|6HAHwW zLCgNk+53w76L)ysfq#UnuKOiCtAxro6yZ5|f?W42a^1gu18G5c4u*hG`MsddR|LKB zcR}Cew{hZIR-SG5(bJc~DaxyetY%3grQnxwwJeuxHeN357q58_HBr%p9ET3_Rh&Rp z5p9WxT=2@crk0zi`B=HgIy;R%kF2Fpx!{&@Wi7Yi=~%h2AJj*-ZkUE;7=~U349zeK zsnWuu0R0y^;asoWvKTY^?Eq}qkILyS2n|uaVOiF3qV~Aqxf33X+&j@=-;4SY zmF?0x-zU??QN3ZApH(9})p+-#fwk})8eSvp<~rXk)5h_3c#A}4?s#{xfwl0ALyHXg zwa)k0v~g5#SmrJAn$2_izR&N1&*`1ilcf0Uq82Eru0;}srV{jN_x za!|tZ*}Yt83whUH{)s5~;J=c-7(7UA#@h{aS?1sQk;uRE7eQC(=Q9W^9}9Z#AA+vE zCFom!6Ljmxf^JLtwQmdgz3&S8{yzo%`o5(97W9KXLGeyudX}yC%Nc@O=)po9y2!sW zTC<`aW!*My%QkGi%(CFuag)0`TGQ|{$NHS=tk*JGS9s3Y5`4Bbc>anw==RBu-QP|N zA3Dwjx6y-*H0WaYh(W8A^ag}`p}~tdsS#DGQx zM%rltaFL#okQW)9vfOj4XU^EzmcIGi9q4smUHfjkHA9?)JB6UUAXuVrH0chMp# z;NXn}YCJJ%(fV6DI|b154Bv$vestz?L$rmP!- zHGEri+?5Qm)P^+|>$Gp5Vs|_jt=zkK)0AynJG`y!4tCLV0YC6IDBB)zcn=KUsMMPe z{JznJIuMT=1>*K`4iS2QLT`qoCNjQ-t=yxze(Owf1C)PE^-kG>pT|l>k znS}P5m^iUc6TLrT>}Kp=_7<|PL2CU8D?s9md)=SEz^qQ3MX$4tjAvNt@tLWAExqP1 zmGBlJGqlXK7T}||)WjQ%%m5!#YKM>RQX6kjG6Q@}sh#cyxFd|?^pE>o8DF;2fbLFX zxF6siSjHEpG@yIe7;X@_LzdfEiBo_^K+<|NId=o$Ns_;r=O^s%lXEu^o?QERCE+YE zId=o$$+e$tGTq-N|9qddhx8=|HWFrWx_oIPVdSS9Y0^ffqlIiFodtvMcuD6mdc(@~ zC7fF5-6)mAcS!zyF#gQYGB-r2*gYGixb6785~V_xv)xJfH|LB;VfybU@ne+NA>$E< O4~>KW)gXRHg8d($*8I=_ literal 0 HcmV?d00001 diff --git a/src/GuiApp/Assets/filetime2.xcf b/src/GuiApp/Assets/filetime2.xcf new file mode 100644 index 0000000000000000000000000000000000000000..d4acb620e291f1c575e33336ec6b08084d1b64d8 GIT binary patch literal 23445 zcmeHP4RBq>b>8p#;*w(#IEye`w z7COU7F@J_hhE7@-m$=YCVp3BuV7EypG_E_HHYo;*WWD=5{7j(GA(@(=X@N?5cTc~w z_g?ARmH->s5+ql%d(Q6NyL$+(N%Bt(UZ{E^%!;Y@4<$uz> zBT?RV#ns>9JGrW9_qH31u7$=e-CNz(e_M0wj@xhSD!+C6jon-0;Tql8vi-*Gx0Kf? zu<#aR^9|*7>aRYI+ndL@U0<&L`1WnL-h9KB?wgGA`Z|D=S~s~sFbR54Dt04qJX`{r;Tp3DEBr{J={nTP+`G47$t|0}n?T!csy88l5~ zlTQrO+fN)l>VJaX_CN8-+aSPGPIH+X1&E&TH&egAIo*#c6yNsu(+B?k^aq||(9@D} zelnZN1qL)RposxZ3}_yC;Hjtl56}_+1D_l*h47TqT;@grlODi;2QlD5RH1mpe~@1A zA56dCDF!_)8RsV{m1U|S4Gd{uNCQI}n^s+Wt$!75@~`@2lOcqsoaQn&3K+BsLv~`w zPE?_|$=^v^{GI77o?_6`l5u{L^elsil%W-PV!0;h9tg8{nxMzSH2m>eAs&xV`Y{_k z{<*;25gNW5@5BnNQ?3Y>v*`3LVH4Com}P?6KhH*9>LkAz zxY=)poCIn=qwGGXd7c>$_qL5-JZ|TO_I;b$4@KCBi7aC}z6DU{B)<{3&u@gB1nNGc z>^`S?o*57KwvAvsZs&#eeV3O$e>xZn_yd7qzb_zsU@TDv7)!Y$f_H^;_K*j|Rp+FW zzR{+O_||8Ao80DneVYl4#p!InO`ePA(yBZKmj%wZsVyDj9;&o;Zhen$Erd}fa%q;x zqf!y05-}H>(>$VYXNVfg5OI93K?)7|D1xL70ml|32d@pRNUt3lTC1%{UppvQXReUf z4hEci9f9WbilLzuT64N%P%h7GmK~q#-gteUIzH3HDT^W!G)bl@LUL%ewmiLVP_E2e zF4w66x$-nB#;v?o>quWWDA#3n$m`VO=zkFX4|4yWF$Z&<@$djz<~St`gn5o#0%ahH z?=j8%d>p);2ToU0SX2_rfs;&`krbH_hzWt1rGj3`GbvRfC|PCF00d*wHy{`jg2B=h zG(-J9GlHb`1IJb^`&I{*r#t%lJHU(8eaUw4Vs&4@xwis>S>E5j9D-TVm%IXkS@F5< zjo0U?<1ANTJWvoF^f4-cSaj#I)wnCI9fPzI9t9@Cs-QZfWygUbUD50s{AGc2T;EKB*4 zu8gvEN|cElgA%jEbmS0HBHt(x)duNMI}&pZT95%a&#-e!pvy$S223a8^VUVT5UksM%NtO7WFlPf{}2!u;xGaLF6OZ)FFFlrVqM9%x zyzS3mjt4O9niG|%shK9Mngj2bePq3Vpe6!lmW3(UjI(v#xqG+6Mz#9*gwR z2i(0&i3`y;n$QO^n;MKmeP@7!dOBve&%vA+b9-J4IW|stg8-3 z*;kP~6oK1t;d6twSbgv3`G#!5(hqoKDz;v3J&opC5|X>?((5rvYHrH4Qrw(O2*3zO9v zX|c&d)v1(zk*ZD6jn-Df$l6F-dD1QsE=1R8L0e-sF&LRPo&gTx?e;vW0;hk{n}AVQ zBSaJy4agyAeSzWAT|rx+#*XM3+2*Si)>SEKYYH_&b`Ix3Q}~W=u8_s=$CUn1E$G`b zRArT*9~Lr}5etzYUG6U;tQPmbS{kNtChJ;~Rq@ow#JAo)@zDA?afwAz6WIq+{B zxc@5ze~5YhHRiJ&a65FY!V_S{i9%>=YX=v>7+sqsthNC;AIuwQjS4GR-w|J9b<_u0 z9=Lj)#M(yIh^M+?%J~MCf<8`Xt8N7Ftmi6T>M8UB=j5mx#an^ns^gOr_v!`030Lu7 z*s$y`ZCLeZHjMwwhVbi!@qo}l9k%v>0$sfA_^su!8O>(ra0 zngb=9Jo}ToPkkEHOqqN>`)PULm+&b3@<9I6V5}(z?#^zLR~|ssfh+Ud?5exK{mwJKUz(Rs?$k5bwZyufA?M)`~b0jX1~uUgV|pUNO}c!ZwO#B8_O|l198ASA-nYX z2IP>?7aEX5*CHcZ$m{hwV{}V*_asWF{o^cijdwI0j=)mh66?gOm_QBhQ=O>kjJ3Em zkW34zWM>OY#%?v0>Q1Bhlv$k}kIIag-V=gY0%bw+7mH#4^s9RImhF=_hYCfyaMxQy zbI2O%MT@lm8!1_(3VFivwV#*={5zrPLN5MoT%kEaM*anx3YtfS+%UXA)n?(W>1P)x zG=i<@gGCC(UUd0jokFoets2G#HFPJib;AurX&~!|8?o4Z)j&_?KZy&l=+{wH)T>3G zo{NlFqo-P=ytHXYfrXxh6yNN1wR{509Rp2z%GC-2pc1b&C;`~ zYfsDPlYXqe&lEAH;g;4W?;b!^|LP*fR8_OQOI3A%t9MPIsxe4CeL~*(j)!aga2A-w z$irZP;wqelTXV$)hV0smcND8K?5soY};U>{bDpN>yW!Ix-iVPJ6y*nup=OF465Cn7@=Ak67VM|8zkg#s7Es zH!tqFDx3pFFg^6EWkGgwSReOhW?_M_hVKtWut->g*ZW{Kk^FTr1IxPIr(wmAFHKY6 z;|c|aS``>xYlHPu3uvN@8Yo4knAK%-j9Bf^(rb)A)V zsEn^cgNNdA9F8L>m9Ln!DVv3qn9b@PJ`2W1w7PSL(xw;$_QiBnknHqw?&$Qe!sbso zR6v(-R=I6=johX;!mUOsmtBbl_}NHrfO_7*n)>F%BB1@F`YSiY9u)Zr^n`!4jG$f z!+C%D#t>XDIbzrsU3SM0YWKld^#|fX#lUs)5v9f%MT|`yHSY7NmwV(dSXE6wQpA{| zUY$=ABWJB`ycOB4*U+w(6eB_R#Rk zoG%c-aGz2LHx@BArFFzHbl1D&W32QZyRV2bMZG#>C`Qiu-&pIvw@yR5T2__$@i-$q z$b=%yR@EU?()ej=I~4i4b5Pm_ z86t`RGIUP_{wN~qa2@~|8ZxmZTg*4ox}jr-nXl7k79teh;VGunP8>;4i@7tX4*-Lg zD)4@R0?&ms*8PW8DHGu5)52JhH(?iBsQl%r0N)z^gyD z%PVVbc#VpZ7GnL^?DG1J4!qNbI7kRTfp~@rF%#kHvuU;{rLp2rQ zkbzSnLgr>5ERj9H@HB6n z$GJYwC=hau0;JcV5J&7UtxXv5T4u)>Mde=}pi6cM&*jwfsZ66kL`~ z|27+pwlcnFm4Vr^=kJA8H668ckO|MEl^#^&`S< z4wc3AAYO=2$*)nSxCmjB7gI?50R_b+2<5(%{Ngh5iOccxjU}q*I8I6UU<7@`fLdsx zZyb-XO;u;QZnXxOTeeSaG_rzH7{Sa$q7=qJMBv51FbYDjF9C*e@C%2_uqBJjO_AbB z#Bn6RTu1yL7I;1cSb%)GScsfGPLzNZjNI#X;^Ten5((JL$Te{x;?6DvhN&T5@FHNC zoDoDq%%Hf`@FQ7>7rxxU1PwLn8fxR0oq91Ua<;Om7o#G_sL0v&rd~{zXgAy9)XP>m z?PmL&df8H^Ue*+8H^v5zNdw2|z%gy$7#}z$4jdGa)hobexf^^IyL)o+&3;N^;wC=D z2=@K17($fkNUV8ELq+Gg;DNoWfVXOOnHU%|>8G)s;HOFZ6kn$J|W?|E6XO94}ZWX5*VPc^e_Cn(ydnV{%A=b*@;fT=+; zV(oOjW|XyqqE5{KMdvvOMGgf_4NI<+Dq+W!!;YIR=HQ2A6)-cZU|UoxTaE+9ieSgF zEm!E+avVVB+HwVBYL6_37_>^v^2n5i4xQ&h`Qo4n`kqb?lIgM=dTG`APh_XqQ4T@$c)&D8pHf2CoiC(_ul%)2iF%? z;%75y>lfE$;1eS2t?s~VKF)usaTb;=Id~I$pX7snh?V4HzGAFh^j-Es(S{=A^26DM zSi+kjVm=T*(i18m0-QuDmfw=VHyJR}gmk$3$$KfyLE8*lAod;F| zdq$Jx=$I1Y68(<*H{ki1L=KF%va(;wP3*Pe#GWiS#EKnV9CpRwSL_Jma4ZhX;_$4w z2-D&Sl+o3NQYz%h&h79g1VkYgC^Z@;MZ`X5{-;{VkFpPX9-^z}BFUrpDKmnNUQbnW z1Xh0~M`OKOfygXLsd5g}I#$LZTgPXah@U!Uri$_3QLL9!i05+1;8S}Q>~yLt!S=Aa zVN!@x>OtI;9Ykg4h-d1W%Yjx2H{1%*UwRc{vEc7TU>0qwFlM2q9N}8@hh+%dqOMs6 z!pOEHO7LGh@Gm&J_?M=N8-$=H5Ml>7?Nay#l`r62`rcLHQIDy-g>&hDl>^ymE9Wva zz>#foiR3^x`Yz`(^v@jmCUaiqAUIk0634^I=%G0rvE|$ONf`%lRUP6}7Hjd7gQXBp zIBrh6u6=GW;7975o1$H{p+87U7a94zEl3I`S`m4ko$Lg9b3 z9iG7l8^;TsLk=8u94-?0KY4w{I02#8N^l%PsZu@~0o(AqpX0M}WP&#f|6#{UC685z zf|Qj^z05SQ=l%3$)2_AOCHU@jj0}H&0ekax<34 zNPRO1z?;Np2i>#9OoRjDT`E%Hz*{OrOi$xQ+N9V(^f-thdPwEHDqpAa+89_X-}Nz| zM?eM9*Fgoz<9-HoJ;)&07o5TVX?ZCag19Y^Lh^91h~3!z(~H@ueRKwBBKd4~DIDI` x$G70$n9WA9VmAI8r}cmPn?M+)-}%YbJP@`fy}G$6mkGm#@GtitLyX4V{y(+Kr#Jur literal 0 HcmV?d00001 diff --git a/src/GuiApp/FileTime.Avalonia/Application/AppState.cs b/src/GuiApp/FileTime.Avalonia/Application/AppState.cs index 9485dee..b670312 100644 --- a/src/GuiApp/FileTime.Avalonia/Application/AppState.cs +++ b/src/GuiApp/FileTime.Avalonia/Application/AppState.cs @@ -16,6 +16,7 @@ namespace FileTime.Avalonia.Application private ObservableCollection _tabs = new(); [Property] + [PropertyCallMethod(nameof(SelectedTabChanged))] private TabContainer _selectedTab; [Property] @@ -62,6 +63,14 @@ namespace FileTime.Avalonia.Application } } + private void SelectedTabChanged() + { + foreach(var tab in Tabs) + { + tab.IsSelected = tab == SelectedTab; + } + } + private async Task TabItemMarked(TabState tabState, AbsolutePath item) { var tabContainer = Tabs.FirstOrDefault(t => t.TabState == tabState); diff --git a/src/GuiApp/FileTime.Avalonia/Assets/filetime.ico b/src/GuiApp/FileTime.Avalonia/Assets/filetime.ico new file mode 100644 index 0000000000000000000000000000000000000000..217f28f2be5bd90c86752e1819781582a493126a GIT binary patch literal 33552 zcmeI5e@IZQH7RLiYOiKaLDmln7OvW$+y>Q_tk{+jNSL_>Nh~v3rNh!e1(U~`qMI{qc8=$r>-pY$?tSOnJ>Q=CJ-q9= z-{0qa?tS;Zd!Jqj36LPk$|AJSAUo0ssV0Qv<#G9F1|dIEU4DL4zLAhO=n@KXdCe_^ z&_Qb0>hofRn?u8zu;8l8|gLb|N zg^l&`wac0LI?Nx-ZjH}cf$^5wS~0W7s_$xN^{CM*Ypq14mCdQ+`Qe2@RB}2~jX!^JMFD$N z0iGXTNC=7BR6m6W>{Xr~`Q6>QB{h}<+LM9+zkXu(gjqB1&#@y#Iw6a=4>K-wexm1` zsN{zii<0Bmb!vz%^>i`0PQ~voDKKnEV2Kb=o&HgW1QJOA+~PJ6#25`ifNL_x%ZDE@ zXi4xIKgQ1ykdT2@FMhz$MOe)7bM$7vN}~jv8n5{~y`372=LB4Kub65teldfoLvqE7 z{ezEONUV3$!10R+H*aB|%ZTxu04HYt6Y^WQ@yZ)-%J1jl9L4;vH6GrOvrDk1EQ$K| z2q?=Da9za18+LX{c)9oX2q?S92@CXxlJN2BVFZ-C;?RZ!mO24`-!OhZNBC(r6Z=^Z zS{xpx_!%$Jv#r5o*3Wli_lfK2LT?fq-6zK1XJXK>A%UeqASu37#6chdB!C2v01`j~ zNB{{WoIs-Qm)IwW^%F|~`9lJUr7wQfcWw*|rM}x$vFZ5dX3p1iU*<>0x#K{Xbsg!TOLuytwM-fz@&Q z>vD^ipI`p!qc1%F>G)uMNI-YH>af-Efz|cje>%B!e6T(wfZJb>fX=?9;{)mA{;x+r zeqMh0$7=*w9}t6GpubOgRtPcsuY!a~!tHb>t>(4&{JAbdgb@Q_M9Vym_1iTn~+2X|?{%xU|21FMd*poP4r7VdE;=KChxYvks$#jfCP{L5e0I{atkyV2KoPoH?DXIJFPZ*f&r+K6O*c7sDOd z`wy%o8LJ$x)r09Hrw2$$;mnHTZI>R%tP6MCyJ4!}7|E-2yk6d$+ZBntS=rE5J6U)+ z`*3%48rinY@j5j{iZ(WOzoaxe-MQw}#gzVmGc^y6Pxp@1^^LxJwXSVT>sJ@*>dKx~ z&Cxk)Z%sd?zrUq#qB&z^;*-dIdors`vi47&zu}kSdp>Au&;M&*=f%R#$mi>nG@X3C zH7#Z7n|F=pC-8sF!Ahi1XY!*@R*rw9Y9^Lx(|4GL=ApigX literal 0 HcmV?d00001 diff --git a/src/GuiApp/FileTime.Avalonia/Assets/filetime2.ico b/src/GuiApp/FileTime.Avalonia/Assets/filetime2.ico new file mode 100644 index 0000000000000000000000000000000000000000..7a8deeaf54d0ce79ac334315b8bc9c1e6deaf054 GIT binary patch literal 67646 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 literal 0 HcmV?d00001 diff --git a/src/GuiApp/FileTime.Avalonia/FileTime.Avalonia.csproj b/src/GuiApp/FileTime.Avalonia/FileTime.Avalonia.csproj index 6fc54e2..da357e2 100644 --- a/src/GuiApp/FileTime.Avalonia/FileTime.Avalonia.csproj +++ b/src/GuiApp/FileTime.Avalonia/FileTime.Avalonia.csproj @@ -3,12 +3,16 @@ WinExe net6.0 enable + Assets\filetime.ico + + + diff --git a/src/GuiApp/FileTime.Avalonia/Models/Persistence/PersistenceRoot.cs b/src/GuiApp/FileTime.Avalonia/Models/Persistence/PersistenceRoot.cs new file mode 100644 index 0000000..b9c45c1 --- /dev/null +++ b/src/GuiApp/FileTime.Avalonia/Models/Persistence/PersistenceRoot.cs @@ -0,0 +1,7 @@ +namespace FileTime.Avalonia.Models.Persistence +{ + public class PersistenceRoot + { + public TabStates? TabStates { get; set; } + } +} \ No newline at end of file diff --git a/src/GuiApp/FileTime.Avalonia/Models/Persistence/TabState.cs b/src/GuiApp/FileTime.Avalonia/Models/Persistence/TabState.cs new file mode 100644 index 0000000..f132da7 --- /dev/null +++ b/src/GuiApp/FileTime.Avalonia/Models/Persistence/TabState.cs @@ -0,0 +1,18 @@ +using FileTime.Avalonia.Application; + +namespace FileTime.Avalonia.Models.Persistence +{ + public class TabState + { + public string? Path { get; set; } + public int Number { get; set; } + + public TabState() { } + + public TabState(TabContainer tab) + { + Path = tab.CurrentLocation.Item.FullName; + Number = tab.TabNumber; + } + } +} \ No newline at end of file diff --git a/src/GuiApp/FileTime.Avalonia/Models/Persistence/TabStates.cs b/src/GuiApp/FileTime.Avalonia/Models/Persistence/TabStates.cs new file mode 100644 index 0000000..124b881 --- /dev/null +++ b/src/GuiApp/FileTime.Avalonia/Models/Persistence/TabStates.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace FileTime.Avalonia.Models.Persistence +{ + public class TabStates + { + public List? Tabs { get; set; } + public int? ActiveTabNumber { get; set; } + } +} \ No newline at end of file diff --git a/src/GuiApp/FileTime.Avalonia/Services/StatePersistenceService.cs b/src/GuiApp/FileTime.Avalonia/Services/StatePersistenceService.cs new file mode 100644 index 0000000..be30289 --- /dev/null +++ b/src/GuiApp/FileTime.Avalonia/Services/StatePersistenceService.cs @@ -0,0 +1,131 @@ +using System.Linq; +using System.Net; +using System.Text; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; +using FileTime.Avalonia.Application; +using FileTime.Avalonia.Models.Persistence; +using System; +using FileTime.Core.Components; +using FileTime.Core.Providers; +using FileTime.Providers.Local; +using FileTime.Core.Models; + +namespace FileTime.Avalonia.Services +{ + public class StatePersistenceService + { + private readonly AppState _appState; + private readonly ItemNameConverterService _itemNameConverterService; + private readonly JsonSerializerOptions _jsonOptions; + private readonly string _settingsPath; + private readonly IEnumerable _contentProviders; + private readonly LocalContentProvider _localContentProvider; + + public StatePersistenceService( + AppState appState, + ItemNameConverterService itemNameConverterService, + IEnumerable contentProviders, + LocalContentProvider localContentProvider) + { + _appState = appState; + _itemNameConverterService = itemNameConverterService; + _contentProviders = contentProviders; + _localContentProvider = localContentProvider; + _settingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FileTime", "savedState.json"); + + _jsonOptions = new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true + }; + } + + public async Task LoadStatesAsync() + { + if (!File.Exists(_settingsPath)) return; + + using var stateReader = File.OpenRead(_settingsPath); + var state = await JsonSerializer.DeserializeAsync(stateReader); + if (state != null) + { + await RestoreTabs(state.TabStates); + } + } + + public async Task SaveStatesAsync() + { + var state = new PersistenceRoot + { + TabStates = SerializeTabStates() + }; + var settingsDirectory = new DirectoryInfo(string.Join(Path.DirectorySeparatorChar, _settingsPath.Split(Path.DirectorySeparatorChar)[0..^1])); + if (!settingsDirectory.Exists) settingsDirectory.Create(); + using var stateWriter = File.OpenWrite(_settingsPath); + await JsonSerializer.SerializeAsync(stateWriter, state, _jsonOptions); + } + + private TabStates SerializeTabStates() + { + var tabStates = new List(); + foreach (var tab in _appState.Tabs) + { + tabStates.Add(new TabState(tab)); + } + + return new TabStates() + { + Tabs = tabStates, + ActiveTabNumber = _appState.SelectedTab.TabNumber + }; + } + + private async Task RestoreTabs(TabStates? tabStates) + { + if (tabStates == null + || tabStates.Tabs == null) + { + return false; + } + + foreach (var tab in tabStates.Tabs) + { + if (tab.Path == null) continue; + + IItem? pathItem = null; + foreach (var contentProvider in _contentProviders) + { + if (contentProvider.CanHandlePath(tab.Path)) + { + pathItem = await contentProvider.GetByPath(tab.Path, true); + if (pathItem != null) break; + } + } + + var container = pathItem switch + { + IContainer c => c, + IElement e => e.GetParent(), + _ => null + }; + + if (container == null) continue; + + var newTab = new Tab(); + await newTab.Init(container); + + var newTabContainer = new TabContainer(newTab, _localContentProvider, _itemNameConverterService); + await newTabContainer.Init(tab.Number); + _appState.Tabs.Add(newTabContainer); + } + + if (_appState.Tabs.FirstOrDefault(t => t.TabNumber == tabStates.ActiveTabNumber) is TabContainer tabContainer) + { + _appState.SelectedTab = tabContainer; + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/GuiApp/FileTime.Avalonia/Startup.cs b/src/GuiApp/FileTime.Avalonia/Startup.cs index 6a75ab1..4c22887 100644 --- a/src/GuiApp/FileTime.Avalonia/Startup.cs +++ b/src/GuiApp/FileTime.Avalonia/Startup.cs @@ -23,6 +23,7 @@ namespace FileTime.Avalonia serviceCollection = serviceCollection .AddLogging() .AddSingleton() + .AddSingleton() .AddSingleton(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) diff --git a/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs b/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs index 87fb3d1..ab39212 100644 --- a/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs +++ b/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs @@ -32,6 +32,7 @@ namespace FileTime.Avalonia.ViewModels [ViewModel] [Inject(typeof(LocalContentProvider))] [Inject(typeof(AppState), PropertyAccessModifier = AccessModifier.Public)] + [Inject(typeof(StatePersistenceService), PropertyName = "StatePersistence", PropertyAccessModifier = AccessModifier.Public)] [Inject(typeof(ItemNameConverterService))] public partial class MainPageViewModel { @@ -90,6 +91,7 @@ namespace FileTime.Avalonia.ViewModels var inputInterface = (BasicInputHandler)App.ServiceProvider.GetService()!; inputInterface.InputHandler = ReadInputs; App.ServiceProvider.GetService(); + await StatePersistence.LoadStatesAsync(); _timeRunner.CommandsChanged += UpdateParalellCommands; InitCommandBindings(); @@ -100,16 +102,21 @@ namespace FileTime.Avalonia.ViewModels _keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.PageDown) }); _keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.PageUp) }); _keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.F4, alt: true) }); + _keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.LWin) }); + _keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.RWin) }); AllShortcut = _commandBindings.Concat(_universalCommandBindings).ToList(); - var tab = new Tab(); - await tab.Init(LocalContentProvider); + if (AppState.Tabs.Count == 0) + { + var tab = new Tab(); + await tab.Init(LocalContentProvider); - var tabContainer = new TabContainer(tab, LocalContentProvider, ItemNameConverterService); - await tabContainer.Init(1); - tabContainer.IsSelected = true; - AppState.Tabs.Add(tabContainer); + var tabContainer = new TabContainer(tab, LocalContentProvider, ItemNameConverterService); + await tabContainer.Init(1); + tabContainer.IsSelected = true; + AppState.Tabs.Add(tabContainer); + } var driveInfos = new List(); foreach (var drive in DriveInfo.GetDrives().Where(d => d.DriveType == DriveType.Fixed)) @@ -355,11 +362,6 @@ namespace FileTime.Avalonia.ViewModels } AppState.SelectedTab = tabContainer; - - foreach (var tab2 in AppState.Tabs) - { - tab2.IsSelected = tab2.TabNumber == tabContainer!.TabNumber; - } } public async Task CloseTab() @@ -656,6 +658,42 @@ namespace FileTime.Avalonia.ViewModels return Task.CompletedTask; } + private Task OpenInDefaultFileExplorer() + { + if (AppState.SelectedTab.CurrentLocation.Container is LocalFolder localFolder) + { + var path = localFolder.Directory.FullName; + if (path != null) + { + Process.Start("explorer.exe", "\"" + path + "\""); + } + } + + return Task.CompletedTask; + } + + private async Task CopyPath() + { + string? textToCopy = null; + if (AppState.SelectedTab.CurrentLocation.Container is LocalFolder localFolder) + { + textToCopy = localFolder.Directory.FullName; + } + if (AppState.SelectedTab.CurrentLocation.Container is LocalFile localFile) + { + textToCopy = localFile.File.FullName; + } + else if (AppState.SelectedTab.CurrentLocation.Container.FullName is string fullName) + { + textToCopy = fullName; + } + + if(textToCopy != null && global::Avalonia.Application.Current?.Clipboard is not null) + { + await global::Avalonia.Application.Current.Clipboard.SetTextAsync(textToCopy); + } + } + private Task ShowAllShortcut2() { ShowAllShortcut = true; @@ -1081,6 +1119,18 @@ namespace FileTime.Avalonia.ViewModels FileTime.App.Core.Command.Commands.Dummy, new KeyWithModifiers[] { new KeyWithModifiers(Key.F1) }, ShowAllShortcut2), + //TODO REMOVE + new CommandBinding( + "open in default file browser", + FileTime.App.Core.Command.Commands.Dummy, + new KeyWithModifiers[] { new KeyWithModifiers(Key.O), new KeyWithModifiers(Key.E) }, + OpenInDefaultFileExplorer), + //TODO REMOVE + new CommandBinding( + "copy path", + FileTime.App.Core.Command.Commands.Dummy, + new KeyWithModifiers[] { new KeyWithModifiers(Key.C), new KeyWithModifiers(Key.P) }, + CopyPath), }; var universalCommandBindings = new List() { diff --git a/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml b/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml index 782c612..2cea96e 100644 --- a/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml +++ b/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml @@ -6,12 +6,13 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:FileTime.Avalonia.ViewModels" xmlns:local="using:FileTime.Avalonia.Views" - Title="FileTime.Avalonia" + Title="FileTime" d:DesignHeight="450" d:DesignWidth="800" - Icon="/Assets/avalonia-logo.ico" + Icon="/Assets/filetime.ico" InputElement.KeyDown="OnKeyDown" InputElement.KeyUp="OnKeyUp" + Closed="OnWindowClosed" mc:Ignorable="d"> - + + + diff --git a/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml.cs b/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml.cs index 188e216..59c91db 100644 --- a/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml.cs +++ b/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml.cs @@ -6,6 +6,7 @@ using Avalonia.Markup.Xaml; using FileTime.Avalonia.Misc; using FileTime.Avalonia.Models; using FileTime.Avalonia.ViewModels; +using System; using System.Linq; namespace FileTime.Avalonia.Views @@ -107,5 +108,10 @@ namespace FileTime.Avalonia.Views e.Handled = true; } } + + private void OnWindowClosed(object sender, EventArgs e) + { + ViewModel?.StatePersistence.SaveStatesAsync().Wait(); + } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs index cd52962..22d5838 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs @@ -47,7 +47,7 @@ namespace FileTime.Providers.Local _items = _rootContainers.Cast().ToList().AsReadOnly(); } - public async Task GetByPath(string path) + public async Task GetByPath(string path, bool acceptDeepestMatch = false) { path = path.Replace(Path.DirectorySeparatorChar, Constants.SeparatorChar).TrimEnd(Constants.SeparatorChar); var pathParts = (IsCaseInsensitive ? path.ToLower() : path).TrimStart(Constants.SeparatorChar).Split(Constants.SeparatorChar); @@ -64,7 +64,7 @@ namespace FileTime.Providers.Local } var remainingPath = string.Join(Constants.SeparatorChar, pathParts.Skip(1)); - return remainingPath.Length == 0 ? rootContainer : await rootContainer.GetByPath(remainingPath); + return remainingPath.Length == 0 ? rootContainer : await rootContainer.GetByPath(remainingPath, acceptDeepestMatch); } public async Task Refresh() => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty); diff --git a/src/Providers/FileTime.Providers.Local/LocalFolder.cs b/src/Providers/FileTime.Providers.Local/LocalFolder.cs index 5d62216..2e64404 100644 --- a/src/Providers/FileTime.Providers.Local/LocalFolder.cs +++ b/src/Providers/FileTime.Providers.Local/LocalFolder.cs @@ -88,7 +88,7 @@ namespace FileTime.Providers.Local return _elements; } - public async Task GetByPath(string path) + public async Task GetByPath(string path, bool acceptDeepestMatch = false) { var paths = path.Split(Constants.SeparatorChar); @@ -101,7 +101,7 @@ namespace FileTime.Providers.Local if (item is IContainer container) { - return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1))); + return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)), acceptDeepestMatch); } return null; diff --git a/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs b/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs index 4f43d81..830864c 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs @@ -64,7 +64,7 @@ namespace FileTime.Providers.Smb throw new NotSupportedException(); } - public async Task GetByPath(string path) + public async Task GetByPath(string path, bool acceptDeepestMatch = false) { if (path == null) return this; @@ -78,7 +78,7 @@ namespace FileTime.Providers.Smb } var remainingPath = string.Join(Constants.SeparatorChar, pathParts.Skip(1)); - return remainingPath.Length == 0 ? rootContainer : await rootContainer.GetByPath(remainingPath); + return remainingPath.Length == 0 ? rootContainer : await rootContainer.GetByPath(remainingPath, acceptDeepestMatch); } public IContainer? GetParent() => _parent; diff --git a/src/Providers/FileTime.Providers.Smb/SmbFolder.cs b/src/Providers/FileTime.Providers.Smb/SmbFolder.cs index 34a5d10..8156c65 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbFolder.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbFolder.cs @@ -51,7 +51,7 @@ namespace FileTime.Providers.Smb public Task Clone() => Task.FromResult((IContainer)this); - public async Task GetByPath(string path) + public async Task GetByPath(string path, bool acceptDeepestMatch = false) { var paths = path.Split(Constants.SeparatorChar); @@ -64,7 +64,7 @@ namespace FileTime.Providers.Smb if (item is IContainer container) { - return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1))); + return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)), acceptDeepestMatch); } return null; diff --git a/src/Providers/FileTime.Providers.Smb/SmbServer.cs b/src/Providers/FileTime.Providers.Smb/SmbServer.cs index f1faca2..9fbe85e 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbServer.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbServer.cs @@ -77,7 +77,7 @@ namespace FileTime.Providers.Smb return Task.CompletedTask; } - public Task GetByPath(string path) + public Task GetByPath(string path, bool acceptDeepestMatch = false) { throw new NotImplementedException(); } diff --git a/src/Providers/FileTime.Providers.Smb/SmbShare.cs b/src/Providers/FileTime.Providers.Smb/SmbShare.cs index 8d1a222..9c09818 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbShare.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbShare.cs @@ -70,7 +70,7 @@ namespace FileTime.Providers.Smb throw new NotImplementedException(); } - public async Task GetByPath(string path) + public async Task GetByPath(string path, bool acceptDeepestMatch = false) { var paths = path.Split(Constants.SeparatorChar); @@ -83,7 +83,7 @@ namespace FileTime.Providers.Smb if (item is IContainer container) { - return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1))); + return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)), acceptDeepestMatch); } return null;