hfs 0.26.8 → 0.27.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/admin/assets/index-509bb1d6.js +415 -0
- package/admin/assets/index-60a380a7.css +1 -0
- package/admin/assets/sha512-738f0943.js +8 -0
- package/admin/index.html +3 -1
- package/admin/{public/logo.svg → logo.svg} +0 -0
- package/frontend/assets/index-6e178dfd.css +1 -0
- package/frontend/assets/index-aea7654e.js +85 -0
- package/frontend/assets/sha512-bf915587.js +8 -0
- package/frontend/{public/fontello.css → fontello.css} +0 -0
- package/frontend/{public/fontello.woff2 → fontello.woff2} +0 -0
- package/frontend/index.html +4 -2
- package/package.json +2 -6
- package/plugins/vhosting/plugin.js +23 -20
- package/src/QuickZipStream.js +285 -0
- package/src/ThrottledStream.js +93 -0
- package/src/adminApis.js +169 -0
- package/src/api.accounts.js +59 -0
- package/src/api.auth.js +128 -0
- package/src/api.file_list.js +110 -0
- package/src/api.helpers.js +32 -0
- package/src/api.monitor.js +104 -0
- package/src/api.plugins.js +128 -0
- package/src/api.vfs.js +167 -0
- package/src/apiMiddleware.js +123 -0
- package/src/block.js +34 -0
- package/src/commands.js +125 -0
- package/src/config.js +168 -0
- package/src/connections.js +57 -0
- package/src/const.js +94 -0
- package/src/crypt.js +21 -0
- package/src/debounceAsync.js +49 -0
- package/src/events.js +9 -0
- package/src/frontEndApis.js +38 -0
- package/src/github.js +104 -0
- package/src/index.js +57 -0
- package/src/listen.js +235 -0
- package/src/log.js +137 -0
- package/src/middlewares.js +195 -0
- package/src/misc.js +160 -0
- package/src/pbkdf2.js +74 -0
- package/src/perm.js +183 -0
- package/src/plugins.js +343 -0
- package/src/serveFile.js +105 -0
- package/src/serveGuiFiles.js +113 -0
- package/src/sse.js +30 -0
- package/src/throttler.js +91 -0
- package/src/update.js +70 -0
- package/src/util-files.js +163 -0
- package/src/util-generators.js +31 -0
- package/src/util-http.js +32 -0
- package/src/vfs.js +232 -0
- package/src/watchLoad.js +73 -0
- package/src/zip.js +73 -0
- package/admin/.DS_Store +0 -0
- package/admin/.eslintrc +0 -8
- package/admin/.gitignore +0 -23
- package/admin/package.json +0 -67
- package/admin/src/AccountForm.ts +0 -92
- package/admin/src/AccountsPage.ts +0 -143
- package/admin/src/App.ts +0 -83
- package/admin/src/ArrayField.ts +0 -84
- package/admin/src/ConfigPage.ts +0 -279
- package/admin/src/FileField.ts +0 -52
- package/admin/src/FileForm.ts +0 -148
- package/admin/src/FilePicker.ts +0 -166
- package/admin/src/HomePage.ts +0 -96
- package/admin/src/InstalledPlugins.ts +0 -158
- package/admin/src/LoginRequired.ts +0 -75
- package/admin/src/LogoutPage.ts +0 -27
- package/admin/src/LogsPage.ts +0 -75
- package/admin/src/MainMenu.ts +0 -74
- package/admin/src/MenuButton.ts +0 -38
- package/admin/src/MonitorPage.ts +0 -200
- package/admin/src/OnlinePlugins.ts +0 -101
- package/admin/src/PermField.ts +0 -80
- package/admin/src/PluginsPage.ts +0 -27
- package/admin/src/VfsMenuBar.ts +0 -58
- package/admin/src/VfsPage.ts +0 -124
- package/admin/src/VfsTree.ts +0 -95
- package/admin/src/addFiles.ts +0 -59
- package/admin/src/api.ts +0 -246
- package/admin/src/dialog.ts +0 -203
- package/admin/src/index.css +0 -21
- package/admin/src/index.ts +0 -10
- package/admin/src/md.ts +0 -31
- package/admin/src/misc.ts +0 -141
- package/admin/src/react-app-env.d.ts +0 -1
- package/admin/src/reportWebVitals.ts +0 -15
- package/admin/src/setupTests.ts +0 -5
- package/admin/src/state.ts +0 -40
- package/admin/src/theme.ts +0 -37
- package/admin/tsconfig.json +0 -26
- package/admin/vite.config.ts +0 -32
- package/frontend/.DS_Store +0 -0
- package/frontend/.eslintrc +0 -8
- package/frontend/.gitignore +0 -23
- package/frontend/package.json +0 -51
- package/frontend/src/App.ts +0 -25
- package/frontend/src/Breadcrumbs.ts +0 -43
- package/frontend/src/BrowseFiles.ts +0 -141
- package/frontend/src/Head.ts +0 -45
- package/frontend/src/UserPanel.ts +0 -52
- package/frontend/src/api.ts +0 -78
- package/frontend/src/components.ts +0 -54
- package/frontend/src/dialog.css +0 -76
- package/frontend/src/dialog.ts +0 -105
- package/frontend/src/icons.ts +0 -46
- package/frontend/src/index.scss +0 -307
- package/frontend/src/index.ts +0 -10
- package/frontend/src/login.ts +0 -50
- package/frontend/src/menu.ts +0 -188
- package/frontend/src/misc.ts +0 -54
- package/frontend/src/options.ts +0 -52
- package/frontend/src/react-app-env.d.ts +0 -1
- package/frontend/src/reportWebVitals.ts +0 -15
- package/frontend/src/setupTests.ts +0 -5
- package/frontend/src/state.ts +0 -82
- package/frontend/src/useAuthorized.ts +0 -17
- package/frontend/src/useFetchList.ts +0 -144
- package/frontend/src/useTheme.ts +0 -23
- package/frontend/tsconfig.json +0 -26
- package/frontend/vite.config.ts +0 -21
- package/src/QuickZipStream.ts +0 -279
- package/src/ThrottledStream.ts +0 -98
- package/src/adminApis.ts +0 -161
- package/src/api.accounts.ts +0 -78
- package/src/api.auth.ts +0 -131
- package/src/api.file_list.ts +0 -102
- package/src/api.helpers.ts +0 -30
- package/src/api.monitor.ts +0 -106
- package/src/api.plugins.ts +0 -139
- package/src/api.vfs.ts +0 -182
- package/src/apiMiddleware.ts +0 -124
- package/src/block.ts +0 -35
- package/src/commands.ts +0 -122
- package/src/config.ts +0 -166
- package/src/connections.ts +0 -60
- package/src/const.ts +0 -57
- package/src/crypt.ts +0 -16
- package/src/debounceAsync.ts +0 -51
- package/src/events.ts +0 -6
- package/src/frontEndApis.ts +0 -17
- package/src/github.ts +0 -102
- package/src/index.ts +0 -53
- package/src/listen.ts +0 -220
- package/src/log.ts +0 -128
- package/src/middlewares.ts +0 -176
- package/src/misc.ts +0 -149
- package/src/pbkdf2.ts +0 -83
- package/src/perm.ts +0 -194
- package/src/plugins.ts +0 -342
- package/src/serveFile.ts +0 -104
- package/src/serveGuiFiles.ts +0 -95
- package/src/sse.ts +0 -29
- package/src/throttler.ts +0 -106
- package/src/update.ts +0 -67
- package/src/util-files.ts +0 -137
- package/src/util-generators.ts +0 -29
- package/src/util-http.ts +0 -29
- package/src/vfs.ts +0 -258
- package/src/watchLoad.ts +0 -75
- package/src/zip.ts +0 -69
package/frontend/src/index.scss
DELETED
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
:root {
|
|
2
|
-
--bg: #fff;
|
|
3
|
-
--text: #555;
|
|
4
|
-
--faint-contrast: #0002;
|
|
5
|
-
--mild-contrast: #0005;
|
|
6
|
-
--good-contrast: #000a;
|
|
7
|
-
--button-bg: #68a;
|
|
8
|
-
--button-text: #fff;
|
|
9
|
-
--focus-color: #468;
|
|
10
|
-
.theme-dark {
|
|
11
|
-
--bg: #000;
|
|
12
|
-
--text: #999;
|
|
13
|
-
--faint-contrast: #fff2;
|
|
14
|
-
--mild-contrast: #fff5;
|
|
15
|
-
--good-contrast: #fffa;
|
|
16
|
-
--button-bg: #345;
|
|
17
|
-
--button-text: #999;
|
|
18
|
-
body { color-scheme: dark; }
|
|
19
|
-
a { color:#8ac; }
|
|
20
|
-
.dialog-closer { background: #633 }
|
|
21
|
-
.dialog-icon { color: #ccc; }
|
|
22
|
-
.dialog-backdrop { background: #333b; }
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
body {
|
|
26
|
-
background: var(--bg);
|
|
27
|
-
margin: 0;
|
|
28
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
29
|
-
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
|
30
|
-
-webkit-font-smoothing: antialiased;
|
|
31
|
-
-moz-osx-font-smoothing: grayscale;
|
|
32
|
-
}
|
|
33
|
-
body, button, select, input { font-size: 12pt; }
|
|
34
|
-
#root {
|
|
35
|
-
max-width: 50em;
|
|
36
|
-
margin: auto;
|
|
37
|
-
min-height: 100vh;
|
|
38
|
-
display: flex;
|
|
39
|
-
flex-direction: column;
|
|
40
|
-
}
|
|
41
|
-
body, input {
|
|
42
|
-
color: var(--text);
|
|
43
|
-
}
|
|
44
|
-
code {
|
|
45
|
-
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
|
46
|
-
}
|
|
47
|
-
input, select {
|
|
48
|
-
padding: 0.3em 0.4em;
|
|
49
|
-
border-radius: 0.5em;
|
|
50
|
-
background: var(--bg);
|
|
51
|
-
border-color: var(--mild-contrast);
|
|
52
|
-
color: var(--good-contrast);
|
|
53
|
-
max-width: 100%; box-sizing: border-box; // avoid scrolling
|
|
54
|
-
}
|
|
55
|
-
input[type=checkbox] {
|
|
56
|
-
margin: 0 1.3em 0 0.8em;
|
|
57
|
-
transform: scale(1.7);
|
|
58
|
-
accent-color: var(--button-bg);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.hidden { display: none !important }
|
|
62
|
-
|
|
63
|
-
.icon {
|
|
64
|
-
font-size: 1.2em;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.icon.mirror:before { transform: scaleX(-1); }
|
|
68
|
-
a {
|
|
69
|
-
text-decoration: none;
|
|
70
|
-
color: #57a;
|
|
71
|
-
}
|
|
72
|
-
button {
|
|
73
|
-
background-color: var(--button-bg);
|
|
74
|
-
color: var(--button-text);
|
|
75
|
-
padding: 0.5em 1em;
|
|
76
|
-
border: transparent;
|
|
77
|
-
text-decoration: none;
|
|
78
|
-
border-radius: 0.3em;
|
|
79
|
-
vertical-align: middle;
|
|
80
|
-
cursor: pointer;
|
|
81
|
-
}
|
|
82
|
-
button.toggled {
|
|
83
|
-
opacity: .6;
|
|
84
|
-
}
|
|
85
|
-
button, .breadcrumb { /* consistent focus color */
|
|
86
|
-
&:focus-visible {
|
|
87
|
-
outline: 3px solid var(--focus-color);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
input, select, ul a {
|
|
91
|
-
&:focus-visible {
|
|
92
|
-
border-radius: .3em;
|
|
93
|
-
border-color: transparent;
|
|
94
|
-
outline: 2px solid var(--focus-color);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.error-msg {
|
|
99
|
-
background-color: #faa;
|
|
100
|
-
color: #833;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
header {
|
|
104
|
-
position: sticky;
|
|
105
|
-
top: 0;
|
|
106
|
-
background: var(--bg);
|
|
107
|
-
padding: .2em;
|
|
108
|
-
z-index: 1; /* necessary to not be covered by checkboxes */
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.ani-working { animation:1s blink infinite }
|
|
112
|
-
|
|
113
|
-
@keyframes blink {
|
|
114
|
-
0% {opacity: 1}
|
|
115
|
-
50% {opacity: 0.2}
|
|
116
|
-
}
|
|
117
|
-
@keyframes spin {
|
|
118
|
-
100% { transform: rotate(360deg); }
|
|
119
|
-
}
|
|
120
|
-
@keyframes fade-in {
|
|
121
|
-
0% {opacity: 0}
|
|
122
|
-
100% {opacity: 1}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.spinner, .icon.spinner:before {
|
|
126
|
-
animation: 1.5s spin infinite linear;
|
|
127
|
-
}
|
|
128
|
-
.icon.emoji.spinner { display: inline-block; }
|
|
129
|
-
|
|
130
|
-
.breadcrumb {
|
|
131
|
-
padding: 0.1em 0.6em 0.2em;
|
|
132
|
-
line-height: 1.8em;
|
|
133
|
-
border-radius: 0.7em;
|
|
134
|
-
background-color: var(--button-bg);
|
|
135
|
-
color: var(--button-text);
|
|
136
|
-
|
|
137
|
-
border-top: 1px solid #666;
|
|
138
|
-
margin-right: -0.1em;
|
|
139
|
-
}
|
|
140
|
-
.breadcrumb:nth-child(-n+3) .icon {
|
|
141
|
-
padding: 0 0.2em;
|
|
142
|
-
}
|
|
143
|
-
#folder-stats {
|
|
144
|
-
font-size: 90%;
|
|
145
|
-
margin: 0.4em 0 0 0.5em;
|
|
146
|
-
float: right;
|
|
147
|
-
& .icon {
|
|
148
|
-
margin-right: .3em;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
header input {
|
|
152
|
-
width: 100%;
|
|
153
|
-
margin: 0.2em auto;
|
|
154
|
-
box-sizing: border-box;
|
|
155
|
-
}
|
|
156
|
-
#filter-bar {
|
|
157
|
-
display: flex;
|
|
158
|
-
gap: .3em;
|
|
159
|
-
margin: 0.5em 0;
|
|
160
|
-
& input { flex: 1 }
|
|
161
|
-
& button {
|
|
162
|
-
padding: 0 0.5em;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
ul.dir {
|
|
167
|
-
flex: 1;
|
|
168
|
-
padding: 0;
|
|
169
|
-
margin: 0;
|
|
170
|
-
clear: both;
|
|
171
|
-
& li {
|
|
172
|
-
display: block;
|
|
173
|
-
list-style-type: none;
|
|
174
|
-
margin-bottom: 0.3em;
|
|
175
|
-
padding: 0.3em;
|
|
176
|
-
border-top: 1px solid var(--button-bg);
|
|
177
|
-
|
|
178
|
-
& a {
|
|
179
|
-
word-break: break-word;
|
|
180
|
-
padding-right: 0.3em;
|
|
181
|
-
|
|
182
|
-
& .icon {
|
|
183
|
-
margin-right: .3em;
|
|
184
|
-
}
|
|
185
|
-
&.container-folder:hover {
|
|
186
|
-
text-decoration: underline;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
& .entry-props {
|
|
190
|
-
float: right;
|
|
191
|
-
font-size: 90%;
|
|
192
|
-
margin-left: 12px;
|
|
193
|
-
margin-top: 0.2em;
|
|
194
|
-
|
|
195
|
-
& .icon {
|
|
196
|
-
margin:0 .3em;
|
|
197
|
-
}
|
|
198
|
-
& .entry-size {
|
|
199
|
-
display:inline-block;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
#menu-panel {
|
|
206
|
-
margin-bottom: 0.2em;
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
#menu-bar {
|
|
210
|
-
display: flex;
|
|
211
|
-
justify-content: space-evenly;
|
|
212
|
-
flex-wrap: wrap;
|
|
213
|
-
&>* {
|
|
214
|
-
flex: auto;
|
|
215
|
-
margin: 0.1em;
|
|
216
|
-
}
|
|
217
|
-
& button { /* no need for horizontal padding as we are using flex to grow */
|
|
218
|
-
padding-left: 0;
|
|
219
|
-
padding-right: 0;
|
|
220
|
-
}
|
|
221
|
-
&>a>button { width:100% } /* occupy whole available space */
|
|
222
|
-
}
|
|
223
|
-
#searched {
|
|
224
|
-
margin: .2em;
|
|
225
|
-
}
|
|
226
|
-
#user-panel {
|
|
227
|
-
display:flex;
|
|
228
|
-
flex-direction: column;
|
|
229
|
-
gap: 1em;
|
|
230
|
-
& a>button {
|
|
231
|
-
width: 100%;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
button label {
|
|
236
|
-
cursor: inherit;
|
|
237
|
-
margin-left: 0.5em;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
.dialog-backdrop.working {
|
|
241
|
-
font-size: 5em;
|
|
242
|
-
animation: 1s fade-in;
|
|
243
|
-
}
|
|
244
|
-
.dialog-content {
|
|
245
|
-
padding: .2em; /* give space for focus outline */
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.dialog-confirm,
|
|
249
|
-
.dialog-prompt {
|
|
250
|
-
--color: var(--button-bg);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
#paging {
|
|
254
|
-
display: flex;
|
|
255
|
-
position: sticky;
|
|
256
|
-
bottom: 0;
|
|
257
|
-
background: var(--bg);
|
|
258
|
-
gap: .5em;
|
|
259
|
-
overflow-x: auto;
|
|
260
|
-
&>button {
|
|
261
|
-
flex: 1;
|
|
262
|
-
background: var(--button-bg);
|
|
263
|
-
text-align: center;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/* Works on Firefox */
|
|
268
|
-
* {
|
|
269
|
-
scrollbar-width: thin;
|
|
270
|
-
scrollbar-color: var(--button-bg) var(--faint-contrast);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/* Works on Chrome, Edge, and Safari */
|
|
274
|
-
*::-webkit-scrollbar {
|
|
275
|
-
width: 12px;
|
|
276
|
-
}
|
|
277
|
-
*::-webkit-scrollbar-track {
|
|
278
|
-
background: var(--faint-contrast);
|
|
279
|
-
}
|
|
280
|
-
*::-webkit-scrollbar-thumb {
|
|
281
|
-
background-color: var(--button-bg);
|
|
282
|
-
border-radius: 20px;
|
|
283
|
-
border: 1px solid var(--faint-contrast)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
@media (max-width: 42em) {
|
|
287
|
-
body, button, select { font-size: 14pt; }
|
|
288
|
-
#menu-bar {
|
|
289
|
-
& button label { display: none } /* icons only */
|
|
290
|
-
}
|
|
291
|
-
#filter-bar {
|
|
292
|
-
margin: 0.2em 0;
|
|
293
|
-
}
|
|
294
|
-
#filter-bar label { display:none }
|
|
295
|
-
#filter-bar button { /* make it same size of top bar */
|
|
296
|
-
width: 17.6vw;
|
|
297
|
-
height: 2.3em;
|
|
298
|
-
}
|
|
299
|
-
.breadcrumb {
|
|
300
|
-
word-break: break-all; /* solves with very long names without spaces. 'break-word' is nicer but doesn't handle worst
|
|
301
|
-
cases like /gear/mininova/x/LOOPMASTERS%204Gig%20Pack/LOOPMASTERS_2015/BASS_HOUSE_AND_GARAGE_2_DEMOS/SOUNDS_AND_FX/
|
|
302
|
-
*/
|
|
303
|
-
}
|
|
304
|
-
.breadcrumb .icon {
|
|
305
|
-
font-size:24px;
|
|
306
|
-
}
|
|
307
|
-
}
|
package/frontend/src/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
2
|
-
|
|
3
|
-
import { createElement as h, StrictMode } from 'react'
|
|
4
|
-
import { createRoot } from 'react-dom/client'
|
|
5
|
-
import './index.scss'
|
|
6
|
-
import '@hfs/shared/src/min-crypto-polyfill'
|
|
7
|
-
import App from './App'
|
|
8
|
-
|
|
9
|
-
createRoot(document.getElementById('root')!)
|
|
10
|
-
.render( h(StrictMode, {}, h(App)) )
|
package/frontend/src/login.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
2
|
-
|
|
3
|
-
import { apiCall, ApiError } from './api'
|
|
4
|
-
import { state } from './state'
|
|
5
|
-
import { alertDialog } from './dialog'
|
|
6
|
-
import { srpSequence, working } from './misc'
|
|
7
|
-
|
|
8
|
-
export async function login(username:string, password:string) {
|
|
9
|
-
const stopWorking = working()
|
|
10
|
-
return srpSequence(username, password, apiCall).then(res => {
|
|
11
|
-
stopWorking()
|
|
12
|
-
sessionRefresher(res)
|
|
13
|
-
state.loginRequired = false
|
|
14
|
-
return res
|
|
15
|
-
}, (err: Error) => {
|
|
16
|
-
stopWorking()
|
|
17
|
-
if (err.message === 'trust')
|
|
18
|
-
err = Error("Login aborted: server identity cannot be trusted")
|
|
19
|
-
else if (err instanceof ApiError)
|
|
20
|
-
if (err.code === 401)
|
|
21
|
-
err = Error("Invalid credentials")
|
|
22
|
-
else if (err.code === 409)
|
|
23
|
-
err = Error("Cookies not working - login failed")
|
|
24
|
-
return alertDialog(err)
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// @ts-ignore
|
|
29
|
-
sessionRefresher(window.SESSION)
|
|
30
|
-
|
|
31
|
-
function sessionRefresher(response: any) {
|
|
32
|
-
if (!response) return
|
|
33
|
-
const { exp, username, adminUrl } = response
|
|
34
|
-
state.username = username
|
|
35
|
-
state.adminUrl = adminUrl
|
|
36
|
-
if (!username || !exp) return
|
|
37
|
-
const delta = new Date(exp).getTime() - Date.now()
|
|
38
|
-
const t = Math.min(delta - 30_000, 600_000)
|
|
39
|
-
console.debug('session refresh in', Math.round(t/1000))
|
|
40
|
-
setTimeout(() => apiCall('refresh_session').then(sessionRefresher), t)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function logout(){
|
|
44
|
-
return apiCall('logout').catch(res => {
|
|
45
|
-
if (res.code === 401) // we expect 401
|
|
46
|
-
state.username = ''
|
|
47
|
-
else
|
|
48
|
-
throw res
|
|
49
|
-
})
|
|
50
|
-
}
|
package/frontend/src/menu.ts
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
2
|
-
|
|
3
|
-
import { state, useSnapState } from './state'
|
|
4
|
-
import { createElement as h, useEffect, useState } from 'react'
|
|
5
|
-
import { useDebounce } from 'use-debounce'
|
|
6
|
-
import { alertDialog, confirmDialog, ConfirmOptions, promptDialog } from './dialog'
|
|
7
|
-
import { hIcon, isMobile, prefix, useStateMounted } from './misc'
|
|
8
|
-
import { login } from './login'
|
|
9
|
-
import { showOptions } from './options'
|
|
10
|
-
import showUserPanel from './UserPanel'
|
|
11
|
-
import { useNavigate } from 'react-router-dom'
|
|
12
|
-
import _ from 'lodash'
|
|
13
|
-
import { closeDialog } from '@hfs/shared/lib/dialogs'
|
|
14
|
-
|
|
15
|
-
export function MenuPanel() {
|
|
16
|
-
const { showFilter, remoteSearch, stopSearch, stoppedSearch, patternFilter, selected } = useSnapState()
|
|
17
|
-
const [filter, setFilter] = useState(patternFilter)
|
|
18
|
-
;[state.patternFilter] = useDebounce(showFilter ? filter : '', 300)
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
if (!showFilter)
|
|
21
|
-
state.selected = {}
|
|
22
|
-
}, [showFilter])
|
|
23
|
-
|
|
24
|
-
const [started1secAgo, setStarted1secAgo] = useStateMounted(false)
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
if (!stopSearch) return
|
|
27
|
-
setStarted1secAgo(false)
|
|
28
|
-
setTimeout(() => setStarted1secAgo(true), 1000)
|
|
29
|
-
}, [stopSearch, setStarted1secAgo])
|
|
30
|
-
|
|
31
|
-
//TODO do something for list > 63KB as it hit the url limit (1kb reserved for the rest for the url)
|
|
32
|
-
const list = Object.keys(selected).map(s => s.endsWith('/') ? s.slice(0,-1) : s).join('*')
|
|
33
|
-
return h('div', { id: 'menu-panel' },
|
|
34
|
-
h('div', { id: 'menu-bar' },
|
|
35
|
-
h(LoginButton),
|
|
36
|
-
h(MenuButton, {
|
|
37
|
-
icon: 'filter',
|
|
38
|
-
label: "Filter list",
|
|
39
|
-
tooltip: "Show only elements matching text you type. Works on list already got from the server. Also enables selection of files, for selective \"Download zip\".",
|
|
40
|
-
toggled: showFilter,
|
|
41
|
-
onClick() {
|
|
42
|
-
state.showFilter = !showFilter
|
|
43
|
-
}
|
|
44
|
-
}),
|
|
45
|
-
h(MenuButton, getSearchProps()),
|
|
46
|
-
h(MenuButton, {
|
|
47
|
-
icon: 'settings',
|
|
48
|
-
label: 'Options',
|
|
49
|
-
onClick: showOptions
|
|
50
|
-
}),
|
|
51
|
-
h(MenuLink, {
|
|
52
|
-
icon: 'archive',
|
|
53
|
-
label: "Download zip",
|
|
54
|
-
tooltip: list ? "Download selected elements as a single zip file"
|
|
55
|
-
: "Download whole list (unfiltered) as a single zip file. If you select some elements, only those will be downloaded.",
|
|
56
|
-
href: '?'+String(new URLSearchParams(_.pickBy({
|
|
57
|
-
get: 'zip',
|
|
58
|
-
search: remoteSearch,
|
|
59
|
-
list
|
|
60
|
-
}))),
|
|
61
|
-
...!list && {
|
|
62
|
-
confirm: remoteSearch ? 'Download ALL results of this search as ZIP archive?' : 'Download WHOLE folder as ZIP archive?',
|
|
63
|
-
confirmOptions: {
|
|
64
|
-
afterButtons: h('button', {
|
|
65
|
-
onClick() {
|
|
66
|
-
state.showFilter = true
|
|
67
|
-
closeDialog(false)
|
|
68
|
-
return alertDialog("Use checkboxes to select the files, then you can use Download-zip again")
|
|
69
|
-
},
|
|
70
|
-
}, "Select some files"),
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
),
|
|
75
|
-
remoteSearch && h('div', { id: 'searched' },
|
|
76
|
-
(stopSearch ? 'Searching' : 'Searched') + ': ' + remoteSearch + prefix(' (', stoppedSearch && 'interrupted', ')')),
|
|
77
|
-
showFilter && h('div', { id: 'filter-bar' },
|
|
78
|
-
h('input', {
|
|
79
|
-
id: 'filter',
|
|
80
|
-
placeholder: "Type here to filter the list below",
|
|
81
|
-
autoComplete: 'off',
|
|
82
|
-
value: filter,
|
|
83
|
-
autoFocus: true,
|
|
84
|
-
onChange(ev) {
|
|
85
|
-
setFilter(ev.target.value)
|
|
86
|
-
}
|
|
87
|
-
}),
|
|
88
|
-
!isMobile() && h(MenuButton, {
|
|
89
|
-
icon: 'check',
|
|
90
|
-
label: "Select all",
|
|
91
|
-
onClick() { workSel(() => true) }
|
|
92
|
-
}),
|
|
93
|
-
h(MenuButton, {
|
|
94
|
-
icon: 'invert',
|
|
95
|
-
label: 'Invert selection',
|
|
96
|
-
onClick() { workSel(x => !x) }
|
|
97
|
-
}),
|
|
98
|
-
)
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
function workSel(cb: (b:boolean) => boolean) {
|
|
102
|
-
const sel = state.selected
|
|
103
|
-
for (const { n } of state.filteredList || state.list) {
|
|
104
|
-
const was = sel[n]
|
|
105
|
-
if (was !== cb(was))
|
|
106
|
-
if (was)
|
|
107
|
-
delete sel[n]
|
|
108
|
-
else
|
|
109
|
-
sel[n] = true
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function getSearchProps() {
|
|
114
|
-
return stopSearch && started1secAgo ? {
|
|
115
|
-
icon: 'stop',
|
|
116
|
-
label: 'Stop list',
|
|
117
|
-
className: 'ani-working',
|
|
118
|
-
onClick() {
|
|
119
|
-
stopSearch()
|
|
120
|
-
state.stoppedSearch = true
|
|
121
|
-
}
|
|
122
|
-
} : state.remoteSearch && !stopSearch ? {
|
|
123
|
-
icon: 'search_off',
|
|
124
|
-
label: 'Clear search',
|
|
125
|
-
onClick() {
|
|
126
|
-
state.remoteSearch = ''
|
|
127
|
-
}
|
|
128
|
-
} : {
|
|
129
|
-
icon: 'search',
|
|
130
|
-
label: "Search deep",
|
|
131
|
-
async onClick() {
|
|
132
|
-
state.remoteSearch = await promptDialog('Search for...') || ''
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
interface MenuButtonProps {
|
|
139
|
-
icon: string,
|
|
140
|
-
label: string,
|
|
141
|
-
tooltip?: string,
|
|
142
|
-
toggled?: boolean,
|
|
143
|
-
className?: string,
|
|
144
|
-
onClick?: () => void
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function MenuButton({ icon, label, tooltip, toggled, onClick, className = '' }: MenuButtonProps) {
|
|
148
|
-
return h('button', { title: tooltip || label, onClick, className: className + ' ' + (toggled ? 'toggled' : '') },
|
|
149
|
-
hIcon(icon),
|
|
150
|
-
h('label', {}, label))
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export function MenuLink({ href, target, confirm, confirmOptions, ...rest }: MenuButtonProps & { href: string, target?: string, confirm?: string, confirmOptions?: ConfirmOptions }) {
|
|
154
|
-
return h('a', {
|
|
155
|
-
tabIndex: -1,
|
|
156
|
-
href,
|
|
157
|
-
target,
|
|
158
|
-
async onClick(ev) {
|
|
159
|
-
if (!confirm) return
|
|
160
|
-
ev.preventDefault()
|
|
161
|
-
await confirmDialog(confirm, { href, ...confirmOptions })
|
|
162
|
-
}
|
|
163
|
-
}, h(MenuButton, rest))
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function LoginButton() {
|
|
167
|
-
const snap = useSnapState()
|
|
168
|
-
const navigate = useNavigate()
|
|
169
|
-
return MenuButton(snap.username ? {
|
|
170
|
-
icon: 'user',
|
|
171
|
-
label: snap.username,
|
|
172
|
-
onClick: showUserPanel
|
|
173
|
-
} : {
|
|
174
|
-
icon: 'login',
|
|
175
|
-
label: 'Login',
|
|
176
|
-
onClick: () => loginDialog(navigate),
|
|
177
|
-
})
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export async function loginDialog(navigate: ReturnType<typeof useNavigate>) {
|
|
181
|
-
const user = await promptDialog('Username')
|
|
182
|
-
if (!user) return
|
|
183
|
-
const password = await promptDialog('Password', { type: 'password' })
|
|
184
|
-
if (!password) return
|
|
185
|
-
const res = await login(user, password)
|
|
186
|
-
if (res?.redirect)
|
|
187
|
-
navigate(res.redirect)
|
|
188
|
-
}
|
package/frontend/src/misc.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
2
|
-
|
|
3
|
-
import { createElement as h } from 'react'
|
|
4
|
-
import { Spinner } from './components'
|
|
5
|
-
import { newDialog } from './dialog'
|
|
6
|
-
import { Icon } from './icons'
|
|
7
|
-
import { Dict } from '@hfs/shared'
|
|
8
|
-
export * from '@hfs/shared'
|
|
9
|
-
|
|
10
|
-
export function hIcon(name: string, props?:any) {
|
|
11
|
-
return h(Icon, { name, ...props })
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function hError(err: Error | string | undefined) {
|
|
15
|
-
return err && h('div', { className:'error-msg' }, typeof err === 'string' ? err : err.message)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function isMobile() {
|
|
19
|
-
return window.innerWidth < 800
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
let isWorking = false // we want the 'working' thing to be singleton
|
|
23
|
-
export function working() {
|
|
24
|
-
if (isWorking)
|
|
25
|
-
return ()=>{} // noop
|
|
26
|
-
isWorking = true
|
|
27
|
-
return newDialog({
|
|
28
|
-
closable: false,
|
|
29
|
-
noFrame: true,
|
|
30
|
-
Content: Spinner,
|
|
31
|
-
reserveClosing: true,
|
|
32
|
-
className: 'working',
|
|
33
|
-
onClose(){
|
|
34
|
-
isWorking = false
|
|
35
|
-
}
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function hfsEvent(name: string, params?:Dict) {
|
|
40
|
-
const output: any[] = []
|
|
41
|
-
document.dispatchEvent(new CustomEvent('hfs.'+name, { detail:{ params, output } }))
|
|
42
|
-
return output
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const HFS: any = (window as any).HFS = {}
|
|
46
|
-
|
|
47
|
-
HFS.onEvent = (name: string, cb: (params:any, output:any) => any) => {
|
|
48
|
-
document.addEventListener('hfs.' + name, ev => {
|
|
49
|
-
const { params, output } = (ev as CustomEvent).detail
|
|
50
|
-
const res = cb(params, output)
|
|
51
|
-
if (res !== undefined && Array.isArray(output))
|
|
52
|
-
output.push(res)
|
|
53
|
-
})
|
|
54
|
-
}
|
package/frontend/src/options.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
2
|
-
|
|
3
|
-
import { newDialog } from './dialog'
|
|
4
|
-
import { state, useSnapState } from './state'
|
|
5
|
-
import { createElement as h } from 'react'
|
|
6
|
-
import { Checkbox, FlexV, Select } from './components'
|
|
7
|
-
import { hIcon } from './misc'
|
|
8
|
-
import { MenuLink } from './menu'
|
|
9
|
-
|
|
10
|
-
export function showOptions (){
|
|
11
|
-
const options = ['name', 'extension', 'size', 'time']
|
|
12
|
-
const close = newDialog({ Content })
|
|
13
|
-
|
|
14
|
-
function Content(){
|
|
15
|
-
const snap = useSnapState()
|
|
16
|
-
return h(FlexV, {},
|
|
17
|
-
snap.adminUrl && h(MenuLink, {
|
|
18
|
-
icon: 'admin',
|
|
19
|
-
label: "Admin interface",
|
|
20
|
-
href: snap.adminUrl,
|
|
21
|
-
target: 'admin',
|
|
22
|
-
}),
|
|
23
|
-
h('div', {}, "Sort by"),
|
|
24
|
-
options.map(x => h('button',{
|
|
25
|
-
key: x,
|
|
26
|
-
onClick(){
|
|
27
|
-
close(state.sortBy = x)
|
|
28
|
-
}
|
|
29
|
-
}, x, ' ', snap.sortBy===x && hIcon('check'))),
|
|
30
|
-
h(Checkbox, {
|
|
31
|
-
value: snap.invertOrder,
|
|
32
|
-
onChange(v) {
|
|
33
|
-
state.invertOrder = v
|
|
34
|
-
}
|
|
35
|
-
}, "Invert order"),
|
|
36
|
-
h(Checkbox, {
|
|
37
|
-
value: snap.foldersFirst,
|
|
38
|
-
onChange(v) {
|
|
39
|
-
state.foldersFirst = v
|
|
40
|
-
}
|
|
41
|
-
}, "Folders first"),
|
|
42
|
-
|
|
43
|
-
h(Select, {
|
|
44
|
-
options: ['', 'light', 'dark'].map(s => ({ label: "theme: " + (s || "auto"), value: s })),
|
|
45
|
-
value: snap.theme,
|
|
46
|
-
onChange(v) {
|
|
47
|
-
state.theme = v
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/// <reference types="react-scripts" />
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { ReportHandler } from 'web-vitals';
|
|
2
|
-
|
|
3
|
-
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
|
4
|
-
if (onPerfEntry) {
|
|
5
|
-
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
|
6
|
-
getCLS(onPerfEntry);
|
|
7
|
-
getFID(onPerfEntry);
|
|
8
|
-
getFCP(onPerfEntry);
|
|
9
|
-
getLCP(onPerfEntry);
|
|
10
|
-
getTTFB(onPerfEntry);
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export default reportWebVitals;
|