Nice little directory browser :D
1@*
2 This file is part of Utatane.
3
4 Utatane is free software: you can redistribute it and/or modify it under
5 the terms of the GNU Affero General Public License as published by the Free
6 Software Foundation, either version 3 of the License, or (at your option)
7 any later version.
8
9 Utatane is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
12 more details.
13
14 You should have received a copy of the GNU Affero General Public License
15 along with Utatane. If not, see <http://www.gnu.org/licenses/>.
16*@
17
18@page "/api/files"
19@using System.Diagnostics.CodeAnalysis
20@layout EmptyLayout
21
22<AppTitle Partial>@( Path == "/" ? "/" : Path.Split('/').Last() )</AppTitle>
23
24<input autocomplete="off" type="text" name="path" value="@Path" id="prop-path" hx-swap-oob="true" />
25
26<Breadcrumbs Path="@Path" hx-swap-oob="true"/>
27
28<GoBackButton Path="@Path" hx-swap-oob="true" />
29
30<table>
31<tbody>
32@foreach (var row in _rows) {
33 <tr class="fsobject @row.TypeString">
34 @*<td>
35 @if (row.IsFile && !row.IsBad) {
36 <a
37 download
38 class="file-dl clickable"
39 href="@row.Href"
40 aria-label="Download file">⭳</a>
41 }
42 </td>*@
43 <td class="file-icon">@row.Icon</td>
44 <td class="file-name" data-order="@row.Name">
45 @if (row.IsBad) {
46 <s class="text-stone-500">
47 @(row.Name + row.Trail)
48 </s>
49 } else {
50 @if (row.IsFile) {
51 <a class="@(row.IsDotFile ? "dotfile" : null)"
52 href="@(row.Href + row.Trail)"
53 >
54 @row.Name
55 </a>
56 } else {
57 <a class="@(row.IsDotFile ? "dotfile" : null)"
58 href="@(row.Href + row.Trail)"
59 hx-get="/api/files?path=@row.Href"
60 hx-target="#filetable tbody"
61 hx-swap="innerHtml scroll:top"
62 hx-push-url="@(row.Href + row.Trail)"
63 hx-indicator="#filetable-body, #name-spinner, #filetable-head-time"
64 >
65 @row.Name<span class="dir-slash">/</span>
66 </a>
67 }
68 }
69 </td>
70 <td class="file-size" data-order="@row.Size">
71 @if (row.IsFile) {
72 @if (row.IsBad) {
73 <s>@Utils.FormatFileSize(row.Size)</s>
74 } else {
75 @Utils.FormatFileSize(row.Size)
76 ;
77 }
78 }
79 </td>
80 <td class="file-date" data-order="@row.TimeUnix">
81 @if (row.IsBad) {
82 <s><time>@row.TimeFmtDate @row.TimeFmtTime</time></s>
83 } else {
84 <time>@row.TimeFmtDate @row.TimeFmtTime</time>
85 }
86 </td>
87 </tr>
88}
89@* </tbody> *@
90</tbody>
91</table>
92
93@code {
94
95 [SupplyParameterFromQuery(Name = "path")]
96 public required String? Path { get; set; }
97
98 [SupplyParameterFromQuery(Name = "sort-by")]
99 public required String? SortByColumn { get; set; }
100
101 [SupplyParameterFromQuery(Name = "sort-asc")]
102 public required String SortAsc { get; set; }
103
104 private DirectoryInfo _realPath;
105 private SortByColumns _sortByColumn;
106 private SortByDirection _sortByDirection;
107 private IOrderedEnumerable<FileRow> _rows;
108
109 protected override Task OnInitializedAsync() {
110 if (String.IsNullOrEmpty(Path))
111 Path = "/";
112 else if (Path.EndsWith("/") && Path != "/")
113 Path = Path.TrimEnd('/');
114
115 // prechecked in middleware!
116 _realPath = Utils.VerifyPath(Path).Value.AsT1();
117 _sortByColumn = SortByColumn switch {
118 "name" => SortByColumns.Name,
119 "size" => SortByColumns.Size,
120 "time" => SortByColumns.Time,
121 _ => SortByColumns.Name
122 };
123 _sortByDirection = SortAsc switch {
124 "on" => SortByDirection.Ascending,
125 _ => SortByDirection.Descending
126 };
127
128 _rows = _realPath.EnumerateFileSystemInfos()
129 .Select(fsi => new FileRow(fsi, Path ?? "/"))
130 .OrderByDescending(x => !x.IsFile);
131
132 if (_sortByDirection == SortByDirection.Ascending) {
133 _rows = _sortByColumn switch {
134 SortByColumns.Name => _rows.ThenBy(x => x.Name),
135 SortByColumns.Size => _rows.ThenBy(x => x.Size),
136 SortByColumns.Time => _rows.ThenBy(x => x.TimeUnix)
137 };
138
139 if (_sortByColumn != SortByColumns.Name)
140 _rows = _rows.ThenBy(x => x.Name);
141 } else {
142 _rows = _sortByColumn switch {
143 SortByColumns.Name => _rows.ThenByDescending(x => x.Name),
144 SortByColumns.Size => _rows.ThenByDescending(x => x.Size),
145 SortByColumns.Time => _rows.ThenByDescending(x => x.TimeUnix)
146 };
147
148 if (_sortByColumn != SortByColumns.Name)
149 _rows = _rows.ThenByDescending(x => x.Name);
150 }
151
152 return base.OnInitializedAsync();
153 }
154
155 public enum SortByColumns {
156 Name,
157 Size,
158 Time,
159 }
160
161 public enum SortByDirection {
162 Ascending,
163 Descending,
164 }
165
166 public class FileRow {
167 public required FileSystemInfo Fsi { get; init; }
168 public required bool IsFile { get; init; }
169 public required bool IsBad { get; init; }
170 public required String TypeString { get; init; }
171 public required MarkupString Icon { get; init; }
172 public required String Name { get; init; }
173 public required bool IsDotFile { get; init; }
174 public required long Size { get; init; }
175 public required String TimeFmtTime { get; init; }
176 public required String TimeFmtDate { get; init; }
177 public required String TimeUnix { get; init; }
178 public required String? Href { get; init; }
179 public required char? Trail { get; init; }
180
181 [SetsRequiredMembers]
182 public FileRow(FileSystemInfo baseFsi, String currentPath) {
183 FileSystemInfo? _fsi;
184 bool _isBad = false;
185
186 try {
187 _fsi = baseFsi.UnravelLink();
188 } catch {
189 _isBad = true;
190 _fsi = baseFsi;
191 }
192
193 DateTime _dt = baseFsi.LastWriteTime.ToUniversalTime();
194
195 Fsi = _fsi;
196 IsFile = Fsi is FileInfo;
197 IsBad = _isBad || !Fsi.IsReadable();
198 TypeString = IsFile ? "file" : "directory";
199 Icon = IsBad
200 ? Utils.AbbrIcon($"Server cannot read this {TypeString}", "⚠️")
201 : Utils.GetIconForFileType(Fsi);
202 Name = baseFsi.Name; // specifically want whatever its called in the directory we're looking at
203 IsDotFile = Name.StartsWith('.');
204 Size = IsBad || !IsFile ? 0 : ((FileInfo)Fsi).Length;
205 TimeFmtDate = _dt.ToString("yyyy-MM-dd");
206 TimeFmtTime = _dt.ToString("HH:mm");
207 TimeUnix = $"{_dt.Subtract(DateTime.UnixEpoch).TotalSeconds:N0}";
208 Href = IsBad
209 ? null
210 : String.Join('/',
211 System.IO.Path.Join(currentPath, Name)
212 .Split('/')
213 .Select(Uri.EscapeDataString)
214 );
215 Trail = IsFile ? null : '/';
216 }
217 }
218}