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.
Files changed (163) hide show
  1. package/README.md +15 -2
  2. package/admin/assets/index-509bb1d6.js +415 -0
  3. package/admin/assets/index-60a380a7.css +1 -0
  4. package/admin/assets/sha512-738f0943.js +8 -0
  5. package/admin/index.html +3 -1
  6. package/admin/{public/logo.svg → logo.svg} +0 -0
  7. package/frontend/assets/index-6e178dfd.css +1 -0
  8. package/frontend/assets/index-aea7654e.js +85 -0
  9. package/frontend/assets/sha512-bf915587.js +8 -0
  10. package/frontend/{public/fontello.css → fontello.css} +0 -0
  11. package/frontend/{public/fontello.woff2 → fontello.woff2} +0 -0
  12. package/frontend/index.html +4 -2
  13. package/package.json +2 -6
  14. package/plugins/vhosting/plugin.js +23 -20
  15. package/src/QuickZipStream.js +285 -0
  16. package/src/ThrottledStream.js +93 -0
  17. package/src/adminApis.js +169 -0
  18. package/src/api.accounts.js +59 -0
  19. package/src/api.auth.js +128 -0
  20. package/src/api.file_list.js +110 -0
  21. package/src/api.helpers.js +32 -0
  22. package/src/api.monitor.js +104 -0
  23. package/src/api.plugins.js +128 -0
  24. package/src/api.vfs.js +167 -0
  25. package/src/apiMiddleware.js +123 -0
  26. package/src/block.js +34 -0
  27. package/src/commands.js +125 -0
  28. package/src/config.js +168 -0
  29. package/src/connections.js +57 -0
  30. package/src/const.js +94 -0
  31. package/src/crypt.js +21 -0
  32. package/src/debounceAsync.js +49 -0
  33. package/src/events.js +9 -0
  34. package/src/frontEndApis.js +38 -0
  35. package/src/github.js +104 -0
  36. package/src/index.js +57 -0
  37. package/src/listen.js +235 -0
  38. package/src/log.js +137 -0
  39. package/src/middlewares.js +195 -0
  40. package/src/misc.js +160 -0
  41. package/src/pbkdf2.js +74 -0
  42. package/src/perm.js +183 -0
  43. package/src/plugins.js +343 -0
  44. package/src/serveFile.js +105 -0
  45. package/src/serveGuiFiles.js +113 -0
  46. package/src/sse.js +30 -0
  47. package/src/throttler.js +91 -0
  48. package/src/update.js +70 -0
  49. package/src/util-files.js +163 -0
  50. package/src/util-generators.js +31 -0
  51. package/src/util-http.js +32 -0
  52. package/src/vfs.js +232 -0
  53. package/src/watchLoad.js +73 -0
  54. package/src/zip.js +73 -0
  55. package/admin/.DS_Store +0 -0
  56. package/admin/.eslintrc +0 -8
  57. package/admin/.gitignore +0 -23
  58. package/admin/package.json +0 -67
  59. package/admin/src/AccountForm.ts +0 -92
  60. package/admin/src/AccountsPage.ts +0 -143
  61. package/admin/src/App.ts +0 -83
  62. package/admin/src/ArrayField.ts +0 -84
  63. package/admin/src/ConfigPage.ts +0 -279
  64. package/admin/src/FileField.ts +0 -52
  65. package/admin/src/FileForm.ts +0 -148
  66. package/admin/src/FilePicker.ts +0 -166
  67. package/admin/src/HomePage.ts +0 -96
  68. package/admin/src/InstalledPlugins.ts +0 -158
  69. package/admin/src/LoginRequired.ts +0 -75
  70. package/admin/src/LogoutPage.ts +0 -27
  71. package/admin/src/LogsPage.ts +0 -75
  72. package/admin/src/MainMenu.ts +0 -74
  73. package/admin/src/MenuButton.ts +0 -38
  74. package/admin/src/MonitorPage.ts +0 -200
  75. package/admin/src/OnlinePlugins.ts +0 -101
  76. package/admin/src/PermField.ts +0 -80
  77. package/admin/src/PluginsPage.ts +0 -27
  78. package/admin/src/VfsMenuBar.ts +0 -58
  79. package/admin/src/VfsPage.ts +0 -124
  80. package/admin/src/VfsTree.ts +0 -95
  81. package/admin/src/addFiles.ts +0 -59
  82. package/admin/src/api.ts +0 -246
  83. package/admin/src/dialog.ts +0 -203
  84. package/admin/src/index.css +0 -21
  85. package/admin/src/index.ts +0 -10
  86. package/admin/src/md.ts +0 -31
  87. package/admin/src/misc.ts +0 -141
  88. package/admin/src/react-app-env.d.ts +0 -1
  89. package/admin/src/reportWebVitals.ts +0 -15
  90. package/admin/src/setupTests.ts +0 -5
  91. package/admin/src/state.ts +0 -40
  92. package/admin/src/theme.ts +0 -37
  93. package/admin/tsconfig.json +0 -26
  94. package/admin/vite.config.ts +0 -32
  95. package/frontend/.DS_Store +0 -0
  96. package/frontend/.eslintrc +0 -8
  97. package/frontend/.gitignore +0 -23
  98. package/frontend/package.json +0 -51
  99. package/frontend/src/App.ts +0 -25
  100. package/frontend/src/Breadcrumbs.ts +0 -43
  101. package/frontend/src/BrowseFiles.ts +0 -141
  102. package/frontend/src/Head.ts +0 -45
  103. package/frontend/src/UserPanel.ts +0 -52
  104. package/frontend/src/api.ts +0 -78
  105. package/frontend/src/components.ts +0 -54
  106. package/frontend/src/dialog.css +0 -76
  107. package/frontend/src/dialog.ts +0 -105
  108. package/frontend/src/icons.ts +0 -46
  109. package/frontend/src/index.scss +0 -307
  110. package/frontend/src/index.ts +0 -10
  111. package/frontend/src/login.ts +0 -50
  112. package/frontend/src/menu.ts +0 -188
  113. package/frontend/src/misc.ts +0 -54
  114. package/frontend/src/options.ts +0 -52
  115. package/frontend/src/react-app-env.d.ts +0 -1
  116. package/frontend/src/reportWebVitals.ts +0 -15
  117. package/frontend/src/setupTests.ts +0 -5
  118. package/frontend/src/state.ts +0 -82
  119. package/frontend/src/useAuthorized.ts +0 -17
  120. package/frontend/src/useFetchList.ts +0 -144
  121. package/frontend/src/useTheme.ts +0 -23
  122. package/frontend/tsconfig.json +0 -26
  123. package/frontend/vite.config.ts +0 -21
  124. package/src/QuickZipStream.ts +0 -279
  125. package/src/ThrottledStream.ts +0 -98
  126. package/src/adminApis.ts +0 -161
  127. package/src/api.accounts.ts +0 -78
  128. package/src/api.auth.ts +0 -131
  129. package/src/api.file_list.ts +0 -102
  130. package/src/api.helpers.ts +0 -30
  131. package/src/api.monitor.ts +0 -106
  132. package/src/api.plugins.ts +0 -139
  133. package/src/api.vfs.ts +0 -182
  134. package/src/apiMiddleware.ts +0 -124
  135. package/src/block.ts +0 -35
  136. package/src/commands.ts +0 -122
  137. package/src/config.ts +0 -166
  138. package/src/connections.ts +0 -60
  139. package/src/const.ts +0 -57
  140. package/src/crypt.ts +0 -16
  141. package/src/debounceAsync.ts +0 -51
  142. package/src/events.ts +0 -6
  143. package/src/frontEndApis.ts +0 -17
  144. package/src/github.ts +0 -102
  145. package/src/index.ts +0 -53
  146. package/src/listen.ts +0 -220
  147. package/src/log.ts +0 -128
  148. package/src/middlewares.ts +0 -176
  149. package/src/misc.ts +0 -149
  150. package/src/pbkdf2.ts +0 -83
  151. package/src/perm.ts +0 -194
  152. package/src/plugins.ts +0 -342
  153. package/src/serveFile.ts +0 -104
  154. package/src/serveGuiFiles.ts +0 -95
  155. package/src/sse.ts +0 -29
  156. package/src/throttler.ts +0 -106
  157. package/src/update.ts +0 -67
  158. package/src/util-files.ts +0 -137
  159. package/src/util-generators.ts +0 -29
  160. package/src/util-http.ts +0 -29
  161. package/src/vfs.ts +0 -258
  162. package/src/watchLoad.ts +0 -75
  163. package/src/zip.ts +0 -69
@@ -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
- }
@@ -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)) )
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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;
@@ -1,5 +0,0 @@
1
- // jest-dom adds custom jest matchers for asserting on DOM nodes.
2
- // allows you to do things like:
3
- // expect(element).toHaveTextContent(/react/i)
4
- // learn more: https://github.com/testing-library/jest-dom
5
- import '@testing-library/jest-dom';