hfs 0.26.8 → 0.26.9

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