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,25 +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 { BrowserRouter, Route, Routes } from "react-router-dom"
4
- import { createElement as h, Fragment } from 'react'
5
- import { BrowseFiles } from "./BrowseFiles"
6
- import { Dialogs } from './dialog'
7
- import useTheme from "./useTheme"
8
- import { useSnapState } from './state'
9
-
10
- function App() {
11
- useTheme()
12
- const { messageOnly } = useSnapState()
13
- if (messageOnly)
14
- return h('h1', { style: { textAlign: 'center'} }, messageOnly)
15
- return h(Fragment, {},
16
- h(BrowserRouter, {},
17
- h(Routes, {},
18
- h(Route, { path:'*', element: h(BrowseFiles) })
19
- )
20
- ),
21
- h(Dialogs)
22
- )
23
- }
24
-
25
- export default App;
@@ -1,43 +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 { Link, useLocation } from 'react-router-dom'
4
- import { createElement as h, Fragment, ReactElement } from 'react'
5
- import { hIcon } from './misc'
6
- import { state } from './state'
7
- import { reloadList } from './useFetchList'
8
-
9
- export function Breadcrumbs() {
10
- const currentPath = useLocation().pathname.slice(1,-1)
11
- let prev = ''
12
- const parent = currentPath.split('/').slice(0,-1).join('/')+'/'
13
- const breadcrumbs = currentPath ? currentPath.split('/').map(x => [prev = prev + x + '/', decodeURIComponent(x)]) : []
14
- return h(Fragment, {},
15
- h(Breadcrumb, { label: hIcon('parent', { alt:'parent folder' }), path: parent }),
16
- h(Breadcrumb, { current: !currentPath, label: hIcon('home', { alt:'home' }) }),
17
- breadcrumbs.map(([path,label]) =>
18
- h(Breadcrumb, {
19
- key: path,
20
- path,
21
- label,
22
- current: path === currentPath+'/',
23
- }) )
24
- )
25
- }
26
-
27
- function Breadcrumb({ path, label, current }:{ current?: boolean, path?: string, label?: string | ReactElement }) {
28
- const PAD = '\u00A0\u00A0' // make small elements easier to tap. Don't use min-width 'cause it requires display-inline that breaks word-wrapping
29
- if (typeof label === 'string' && label.length < 3)
30
- label = PAD+label+PAD
31
- return h(Link, {
32
- className: 'breadcrumb',
33
- to: path || '/',
34
- async onClick() {
35
- if (current) {
36
- state.remoteSearch = ''
37
- state.stopSearch?.()
38
- reloadList()
39
- }
40
- }
41
- }, label)
42
- }
43
-
@@ -1,141 +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 { Link, useLocation } from 'react-router-dom'
4
- import { createElement as h, Fragment, memo, useEffect, useMemo, useState } from 'react'
5
- import { formatBytes, hError, hfsEvent, hIcon, isMobile } from './misc'
6
- import { Checkbox, Html, Spinner } from './components'
7
- import { Head } from './Head'
8
- import { state, useSnapState } from './state'
9
- import { alertDialog } from './dialog'
10
- import useFetchList from './useFetchList'
11
- import useAuthorized from './useAuthorized'
12
-
13
- export function usePath() {
14
- return decodeURI(useLocation().pathname)
15
- }
16
-
17
- export interface DirEntry { n:string, s?:number, m?:string, c?:string,
18
- ext:string, isFolder:boolean, t?:Date } // we memoize these value for speed
19
- export type DirList = DirEntry[]
20
-
21
- export function BrowseFiles() {
22
- useFetchList()
23
- const { error, list, serverConfig } = useSnapState()
24
- return useAuthorized() && h(Fragment, {},
25
- h(Html, { code: serverConfig?.custom_header }),
26
- h(Head),
27
- hError(error)
28
- || h(list ? FilesList : Spinner))
29
- }
30
-
31
- function FilesList() {
32
- const { filteredList, list, loading, stoppedSearch } = useSnapState()
33
- const midnight = useMidnight() // as an optimization we calculate this only once per list and pass it down
34
- const pageSize = 100
35
- const [page, setPage] = useState(0)
36
- const offset = page * pageSize
37
- const theList = filteredList || list
38
- const total = theList.length
39
-
40
- useEffect(() => setPage(0), [theList])
41
- useEffect(() => document.scrollingElement?.scrollTo(0,0), [page])
42
-
43
- return h(Fragment, {},
44
- h('ul', { className: 'dir' },
45
- !list.length ? (!loading && (stoppedSearch ? "Stopped before finding anything" : "Nothing here"))
46
- : filteredList && !filteredList.length ? "No match for this filter"
47
- : theList.slice(offset, offset + pageSize).map((entry: DirEntry) =>
48
- h(Entry, { key: entry.n, midnight, ...entry })),
49
- loading && h(Spinner),
50
- ),
51
- total > pageSize && h(Paging, { total, current:page, pageSize, pageChange:setPage })
52
- )
53
- }
54
-
55
- interface PagingProps {
56
- total: number
57
- current: number
58
- pageSize: number
59
- pageChange:(newPage:number) => void
60
- }
61
- function Paging({ total, current, pageSize, pageChange }: PagingProps) {
62
- const nPages = Math.ceil(total / pageSize)
63
- const pages = []
64
- for (let i=0; i<nPages; i++)
65
- pages.push(h('button', {
66
- ...i===current && { className:'toggled' },
67
- onClick(){
68
- pageChange(i)
69
- }
70
- }, i*pageSize || 1))
71
- return h('div', { id:'paging' }, ...pages)
72
- }
73
-
74
- function useMidnight() {
75
- const [midnight, setMidnight] = useState(calcMidnight)
76
- useEffect(() => {
77
- setTimeout(()=> setMidnight(calcMidnight()), 10 * 60_000) // refresh every 10 minutes
78
- }, [])
79
- return midnight
80
-
81
- function calcMidnight() {
82
- const recent = new Date()
83
- recent.setHours(recent.getHours() - 6)
84
- const midnight = new Date()
85
- midnight.setHours(0,0,0,0)
86
- return recent < midnight ? recent : midnight
87
- }
88
- }
89
-
90
- const Entry = memo(function(entry: DirEntry & { midnight: Date }) {
91
- let { n: relativePath, isFolder } = entry
92
- const base = usePath()
93
- const { showFilter, selected } = useSnapState()
94
- const href = fixUrl(relativePath)
95
- const containerDir = isFolder ? '' : relativePath.substring(0, relativePath.lastIndexOf('/')+1)
96
- const name = relativePath.substring(containerDir.length)
97
- return h('li', { className: isFolder ? 'folder' : 'file' },
98
- showFilter && h(Checkbox, {
99
- value: selected[relativePath],
100
- onChange(v){
101
- if (v)
102
- return state.selected[relativePath] = true
103
- delete state.selected[relativePath]
104
- },
105
- }),
106
- isFolder ? h(Link, { to: base+href }, hIcon('folder'), relativePath)
107
- : h(Fragment, {},
108
- containerDir && h(Link, { to: base+fixUrl(containerDir), className:'container-folder' }, hIcon('file'), containerDir ),
109
- h('a', { href }, !containerDir && hIcon('file'), name)
110
- ),
111
- h(EntryProps, entry),
112
- h('div', { style:{ clear:'both' } })
113
- )
114
- })
115
-
116
- function fixUrl(s:string) {
117
- return s.replace(/#/g, encodeURIComponent)
118
- }
119
-
120
- const EntryProps = memo(function(entry: DirEntry & { midnight: Date }) {
121
- const { t, s } = entry
122
- const today = t && t > entry.midnight
123
- const shortTs = isMobile()
124
- const code = useMemo(()=> hfsEvent('additionalEntryProps', { entry }).join(''),
125
- [entry])
126
- return h('div', { className:'entry-props' },
127
- h(Html, { code, className:'add-props' }),
128
- s !== undefined && h(Fragment, {},
129
- h('span', { className:'entry-size' }, formatBytes(s)),
130
- " — ",
131
- ),
132
- t && h('span', {
133
- className: 'entry-ts',
134
- title: today || !shortTs ? null : t.toLocaleString(),
135
- onClick() { // mobile has no hover
136
- if (shortTs)
137
- alertDialog("Full timestamp:\n" + t.toLocaleString()).then()
138
- }
139
- }, !shortTs ? t.toLocaleString() : today ? t.toLocaleTimeString() : t.toLocaleDateString()),
140
- )
141
- })
@@ -1,45 +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, useMemo} from 'react'
4
- import { formatBytes, hIcon, prefix } from './misc'
5
- import { Spinner } from './components'
6
- import { useSnapState } from './state'
7
- import { MenuPanel } from './menu'
8
- import { Breadcrumbs } from './Breadcrumbs'
9
-
10
- export function Head() {
11
- return h('header', {},
12
- h(MenuPanel),
13
- h(Breadcrumbs),
14
- h(FolderStats),
15
- h('div', { style:{ clear:'both' }}),
16
- )
17
- }
18
-
19
- function FolderStats() {
20
- const { list, loading, filteredList, selected, stoppedSearch } = useSnapState()
21
- const stats = useMemo(() =>{
22
- let files = 0, folders = 0, size = 0
23
- for (const x of list) {
24
- if (x.isFolder)
25
- ++folders
26
- else
27
- ++files
28
- size += x.s||0
29
- }
30
- return { files, folders, size }
31
- }, [list])
32
- const sel = Object.keys(selected).length
33
- const fil = filteredList?.length
34
- return h('div', { id:'folder-stats' },
35
- stoppedSearch ? hIcon('interrupted', { title:'Search was interrupted' })
36
- : list.length>0 && loading && h(Spinner),
37
- [
38
- prefix('', stats.files,' file(s)'),
39
- prefix('', stats.folders, ' folder(s)'),
40
- stats.size ? formatBytes(stats.size) : '',
41
- sel && sel+' selected',
42
- fil !== undefined && fil < list.length && fil+' displayed',
43
- ].filter(Boolean).join(', ')
44
- )
45
- }
@@ -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 { useSnapState } from './state'
4
- import { createElement as h } from 'react'
5
- import { alertDialog, closeDialog, newDialog, promptDialog } from './dialog'
6
- import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
7
- import { apiCall } from './api'
8
- import { logout } from './login'
9
- import { MenuButton } from './menu'
10
-
11
- export default function showUserPanel() {
12
- newDialog({ Content })
13
- }
14
-
15
- function Content() {
16
- const snap = useSnapState()
17
- return h('div', { id: 'user-panel' },
18
- h('div', {}, "User: " + snap.username),
19
- h(MenuButton, {
20
- icon: 'password',
21
- label: "Change password",
22
- async onClick() {
23
- const pwd = await promptDialog("Enter new password", { type: 'password' })
24
- if (!pwd) return
25
- const check = await promptDialog("RE-enter new password", { type: 'password' })
26
- if (!check) return
27
- if (check !== pwd)
28
- return alertDialog("The second password you entered did not match the first. Procedure aborted.", 'warning')
29
- const srp6aNimbusRoutines = new SRPRoutines(new SRPParameters())
30
- const res = await createVerifierAndSalt(srp6aNimbusRoutines, snap.username, pwd)
31
- try {
32
- await apiCall('change_srp', { salt: String(res.s), verifier: String(res.v) }).catch(e => {
33
- if (e.code !== 406) // 406 = server was configured to support clear text authentication
34
- throw e
35
- return apiCall('change_password', { newPassword: pwd }) // unencrypted version
36
- })
37
- return alertDialog("Password changed")
38
- }
39
- catch(e) {
40
- return alertDialog(e as Error)
41
- }
42
- }
43
- }),
44
- h(MenuButton, {
45
- icon: 'logout',
46
- label: "Logout",
47
- onClick() {
48
- logout().then(closeDialog, alertDialog)
49
- }
50
- })
51
- )
52
- }
@@ -1,78 +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 { useEffect, useState } from 'react';
4
- import { Dict, Falsy, getCookie, working } from './misc'
5
-
6
- const PREFIX = '/~/api/'
7
-
8
- interface ApiCallOptions { noModal?:true }
9
- export function apiCall(cmd: string, params?: Dict, options: ApiCallOptions={}) : Promise<any> {
10
- const stop = options.noModal ? undefined : working()
11
- const csrf = getCsrf()
12
- if (csrf)
13
- params = { csrf, ...params }
14
- return fetch(PREFIX+cmd, {
15
- method: 'POST',
16
- headers: { 'content-type': 'application/json' },
17
- body: params && JSON.stringify(params),
18
- }).then(res => {
19
- stop?.()
20
- if (res.ok)
21
- return res.json()
22
- const msg = `Failed API ${cmd}: ${res.statusText}`
23
- console.warn(msg + (params ? ' ' + JSON.stringify(params) : ''))
24
- throw new ApiError(res.status, msg)
25
- }, err => {
26
- stop?.()
27
- if (err?.message?.includes('fetch'))
28
- throw Error("Network error")
29
- throw err
30
- })
31
- }
32
-
33
- export class ApiError extends Error {
34
- constructor(readonly code:number, message: string) {
35
- super(message);
36
- }
37
- }
38
-
39
- export function useApi(cmd: string | Falsy, params?: object) : any {
40
- const [x, setX] = useState()
41
- useEffect(()=>{
42
- setX(undefined)
43
- if (cmd)
44
- apiCall(cmd, params).then(setX, setX)
45
- }, [cmd, JSON.stringify(params)]) //eslint-disable-line
46
- return x
47
- }
48
-
49
- type EventHandler = (type:string, data?:any) => void
50
-
51
- export function apiEvents(cmd: string, params: Dict, cb:EventHandler) {
52
- const csrf = getCsrf()
53
- const processed: Record<string,string> = { csrf: csrf && JSON.stringify(csrf) }
54
- for (const k in params) {
55
- const v = params[k]
56
- if (v === undefined) continue
57
- processed[k] = JSON.stringify(v)
58
- }
59
- const source = new EventSource(PREFIX + cmd + '?' + new URLSearchParams(processed))
60
- source.onopen = () => cb('connected')
61
- source.onerror = err => cb('error', err)
62
- source.onmessage = ({ data }) => {
63
- if (!data) {
64
- cb('closed')
65
- return source.close()
66
- }
67
- try { data = JSON.parse(data) }
68
- catch {
69
- return cb('string', data)
70
- }
71
- cb('msg', data)
72
- }
73
- return source
74
- }
75
-
76
- function getCsrf() {
77
- return getCookie('csrf')
78
- }
@@ -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 { hIcon } from './misc'
4
- import { createElement as h, HTMLAttributes, ReactNode, useMemo } from 'react'
5
-
6
- export function Spinner() {
7
- return hIcon('spinner', { className:'spinner' })
8
- }
9
-
10
- export function Flex({ gap='1em', vert=false, children=null }) {
11
- return h('div', {
12
- style: {
13
- display: 'flex',
14
- gap,
15
- flexDirection: vert ? 'column' : undefined,
16
- }
17
- }, children)
18
- }
19
-
20
- export function FlexV(props:any) {
21
- return h(Flex, { vert:true, ...props })
22
- }
23
-
24
- interface CheckboxProps { children?:ReactNode, value:any, onChange?:(v:boolean)=>void }
25
- export function Checkbox({ onChange, value, children, ...props }:CheckboxProps) {
26
- return h('label', {},
27
- h('input',{
28
- type:'checkbox',
29
- onChange: ev => onChange?.(Boolean(ev.target.checked)),
30
- checked: Boolean(value),
31
- value: 1,
32
- ...props
33
- }),
34
- children
35
- )
36
- }
37
-
38
- type Options = { label:string, value:string }[]
39
- interface SelectProps { value:any, onChange?:(v:string)=>void, options:Options }
40
- export function Select({ onChange, value, options, ...props }:SelectProps) {
41
- return h('select', {
42
- onChange: ev => // @ts-ignore
43
- onChange?.(ev.target.value),
44
- value,
45
- ...props,
46
- }, options.map(({ value, label }) => h('option', { key:value, value }, label)))
47
- }
48
-
49
- export function Html({ code, ...rest }:{ code:string } & HTMLAttributes<any>) {
50
- const o = useMemo(() => ({ __html: code }), [code])
51
- if (!code)
52
- return null
53
- return h('span', { ...rest, dangerouslySetInnerHTML: o })
54
- }
@@ -1,76 +0,0 @@
1
- .dialog-backdrop {
2
- position: fixed;
3
- top: 0;
4
- bottom: 0;
5
- left: 0;
6
- right: 0;
7
- background: #888a;
8
- display: flex;
9
- justify-content: center;
10
- align-items: center;
11
- z-index: 1000;
12
- }
13
- .dialog {
14
- background: #fff; /*fallback*/
15
- background: var(--bg);
16
- padding: max(0.5em, 1vw);
17
- border-radius: 1em;
18
- position: relative;
19
- margin: 0 3vw;
20
- overflow: hidden;
21
- max-height: calc(100vh - 2em)
22
- }
23
- .dialog-icon {
24
- color: #fff;
25
- background-color: var(--color);
26
- position: absolute;
27
- top: 0;
28
- width: 1.8em;
29
- height: 1.7em;
30
- text-align: center;
31
- border-radius: .8em 0;
32
- }
33
- .dialog-closer {
34
- border-radius: 0 0.8em;
35
- right: 0;
36
- padding: 0;
37
- background-color: #c99;
38
- }
39
- .dialog-icon ~ .dialog-content {
40
- margin-top: 1.3em;
41
- }
42
- .dialog-type {
43
- left: 0;
44
- top: 0;
45
- overflow: hidden;
46
- line-height: 1.7em;
47
- }
48
- .dialog-content {
49
- overflow: auto;
50
- max-height: calc(100vh - 4em);
51
- }
52
- .dialog-content p {
53
- white-space: pre-wrap;
54
- margin: .5em 0;
55
- }
56
- .dialog-confirm .dialog-content button {
57
- margin-top: 1em;
58
- }
59
- .dialog-alert-info {
60
- --color: #282
61
- }
62
- .dialog-alert-warning {
63
- --color: #c91
64
- }
65
- .dialog-alert-error {
66
- --color: #822;
67
- }
68
-
69
- @media (max-width: 50em) {
70
- .dialog-closer {
71
- font-size: 120%
72
- }
73
- .dialog-icon ~ .dialog-content {
74
- margin-top: 2em;
75
- }
76
- }
@@ -1,105 +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, ReactElement, ReactNode, useEffect, useRef } from 'react'
4
- import './dialog.css'
5
- import { newDialog, closeDialog } from '@hfs/shared/lib/dialogs'
6
- export * from '@hfs/shared/lib/dialogs'
7
-
8
- interface PromptOptions { def?:string, type?:string }
9
- export async function promptDialog(msg: string, { def, type }:PromptOptions={}) : Promise<string | null> {
10
- return new Promise(resolve => newDialog({
11
- className: 'dialog-prompt',
12
- icon: '?',
13
- onClose: resolve,
14
- Content
15
- }) )
16
-
17
- function Content() {
18
- const ref = useRef<HTMLInputElement>()
19
- useEffect(()=>{
20
- const e = ref.current
21
- if (!e) return
22
- const inp = e as HTMLInputElement
23
- setTimeout(()=> inp.focus(),100)
24
- if (def)
25
- inp.value = def
26
- },[])
27
- return h('div', {},
28
- h('p', {}, msg),
29
- h('input', {
30
- ref,
31
- type,
32
- autoFocus: true,
33
- onKeyDown(ev: KeyboardEvent) {
34
- const { key } = ev
35
- if (key === 'Escape')
36
- return closeDialog(null)
37
- if (key === 'Enter')
38
- return go()
39
- }
40
- }),
41
- h('div', { style: { textAlign: 'right', marginTop: '.8em' } },
42
- h('button', { onClick: go }, "Continue")),
43
- )
44
-
45
- function go() {
46
- closeDialog(ref.current?.value)
47
- }
48
- }
49
- }
50
-
51
- type AlertType = 'error' | 'warning' | 'info'
52
-
53
- export async function alertDialog(msg: ReactElement | string | Error, type:AlertType='info') {
54
- if (msg instanceof Error) {
55
- msg = String(msg)
56
- type = 'error'
57
- }
58
- return new Promise(resolve => newDialog({
59
- className: 'dialog-alert-'+type,
60
- icon: '!',
61
- onClose: resolve,
62
- Content
63
- }))
64
-
65
- function Content(){
66
- if (typeof msg === 'string' || msg instanceof Error)
67
- msg = h('p', {}, String(msg))
68
- return msg
69
- }
70
- }
71
-
72
- export interface ConfirmOptions { href?: string, afterButtons?: ReactNode }
73
- export async function confirmDialog(msg: ReactElement | string, { href, afterButtons }: ConfirmOptions={}) : Promise<boolean> {
74
- if (typeof msg === 'string')
75
- msg = h('p', {}, msg)
76
- return new Promise(resolve => newDialog({
77
- className: 'dialog-confirm',
78
- icon: '?',
79
- onClose: resolve,
80
- Content
81
- }) )
82
-
83
- function Content() {
84
- return h('div', {},
85
- msg,
86
- h('div', {
87
- style: {
88
- display: 'flex',
89
- justifyContent: 'flex-end',
90
- gap: '1em'
91
- },
92
- },
93
- h('a', {
94
- href,
95
- onClick() { closeDialog(true) },
96
- }, h('button', {}, "Confirm")),
97
- h('button', {
98
- onClick() { closeDialog(false) },
99
- }, "Don't"),
100
- afterButtons,
101
- )
102
- )
103
- }
104
- }
105
-
@@ -1,46 +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, memo } from 'react'
5
-
6
- const SYS_ICONS = {
7
- login: 'user:👤',
8
- user: 'user:👤',
9
- filter: ':✂',
10
- search: ':🔍',
11
- search_off: 'cancel:❌',
12
- stop: ':⏹️',
13
- settings: 'cog:⚙',
14
- archive: 'file-archive:📦',
15
- logout: ':🚪',
16
- home: ':🏠',
17
- parent: 'level-up mirror:⬆',
18
- folder: ':📂',
19
- file: 'doc:📄',
20
- spinner: 'spin6 spinner:🎲',
21
- password: 'key:🗝️',
22
- download: ':📥',
23
- invert: 'retweet:🙃',
24
- admin: 'crown:👑',
25
- check: ':✔️',
26
- }
27
-
28
- document.fonts.ready.then(async ()=> {
29
- const fontTester = '9px "fontello"'
30
- await document.fonts.load(fontTester) // force font to be loaded even if we didn't display anything with it yet
31
- if (document.fonts.check(fontTester))
32
- state.iconsClass = ' ' // with fontello we don't need an additional class (unlike google material icons), but the empty space will cause reload
33
- })
34
-
35
- export const Icon = memo(({ name, alt, className='', ...props }: { name:string, className?:string, alt?:string, style?:any }) => {
36
- // @ts-ignore
37
- const [clazz,emoji] = (SYS_ICONS[name] || name).split(':')
38
- const { iconsClass } = useSnapState()
39
- className += ' icon ' + (iconsClass ? 'fa-'+(clazz||name) : 'emoji')
40
- return h('span',{
41
- ...props,
42
- 'aria-label': alt,
43
- role: 'img',
44
- className,
45
- }, iconsClass ? null : (emoji||'#'))
46
- })