disable zip button when missing permission
Massimo Melina committed
Oct 4, 2023 at 21:28 UTC
c7348b8552dcf575baf7212d2a2c1e4a308cf541
8 files changed
+31
-24
frontend/src/BrowseFiles.ts
+2
-2
@@ -40,7 +40,7 @@ export function BrowseFiles() {
40
}
41
42
function FilesList() {
43
- const { filteredList, list, loading, stoppedSearch, can_upload } = useSnapState()
43
+ const { filteredList, list, loading, stoppedSearch, props } = useSnapState()
44
const midnight = useMidnight() // as an optimization we calculate this only once per list and pass it down
45
const pageSize = 100
46
const [page, setPage] = useState(0)
@@ -102,7 +102,7 @@ function FilesList() {
102
h('ul', {
103
ref,
104
className: 'dir',
105
- ...acceptDropFiles(files => can_upload ? enqueue(files.map(file => ({ file })))
105
+ ...acceptDropFiles(files => props?.can_upload ? enqueue(files.map(file => ({ file })))
106
: alertDialog(t("Upload not available"), 'warning') )
107
},
108
msgInstead ? h('p', {}, msgInstead)
frontend/src/fileMenu.ts
+3
-3
@@ -26,7 +26,7 @@ export function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (FileMe
26
const cantDownload = entry.cantOpen || isFolder && entry.p?.includes('r') // folders needs both list and read
27
const menu = [
28
!cantDownload && { id: 'download', label: t`Download`, href: uri + (isFolder ? '?get=zip' : '?dl'), icon: 'download' },
29
- state.can_upload && { id: 'comment', label: t`Comment`, icon: 'comment', onClick: () => editComment(entry) },
29
+ state.props?.can_upload && { id: 'comment', label: t`Comment`, icon: 'comment', onClick: () => editComment(entry) },
30
...addToMenu.map(x => {
31
if (x === 'open') {
32
if (entry.cantOpen) return
@@ -34,7 +34,7 @@ export function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (FileMe
34
return !isFolder ? open : h(Link, { to: uri, onClick: () => close() }, hIcon(open.icon), open.label)
35
}
36
if (x === 'delete')
37
- return (state.can_delete || entry.p?.includes('d')) && {
37
+ return (state.props?.can_delete || entry.p?.includes('d')) && {
38
id: 'delete',
39
label: t`Delete`,
40
icon: 'delete',
@@ -49,7 +49,7 @@ export function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (FileMe
49
}
50
return x
51
}),
52
- state.can_delete && { id: 'rename', label: t`Rename`, icon: 'edit', onClick: () => rename(entry) },
52
+ state.props?.can_delete && { id: 'rename', label: t`Rename`, icon: 'edit', onClick: () => rename(entry) },
53
isFolder && { id: 'list', label: t`Get list`, href: uri + '?get=list&folders=*', icon: 'list' },
54
]
55
const props = [
frontend/src/index.scss
+4
@@ -111,6 +111,10 @@ button {
111
vertical-align: middle;
112
cursor: pointer;
113
&:hover { outline: 1px solid var(--mild-contrast); }
114
+ transition: background-color .5s;
115
+ &[disabled] {
116
+ background-color: var(--faint-contrast);
117
+ }
118
}
119
button.toggled {
120
color: #fff;
frontend/src/menu.ts
+4
-2
@@ -3,7 +3,7 @@
3
import { state, useSnapState } from './state'
4
import { ComponentPropsWithoutRef, createElement as h, Fragment, useEffect, useMemo, useState } from 'react'
5
import { alertDialog, confirmDialog, ConfirmOptions, promptDialog } from './dialog'
6
-import { err2msg, ErrorMsg, hIcon, onlyTruthy, prefix, useStateMounted, working } from './misc'
6
+import { defaultPerms, err2msg, ErrorMsg, hIcon, onlyTruthy, prefix, useStateMounted, VfsPerms, working } from './misc'
7
import { loginDialog } from './login'
8
import { showOptions } from './options'
9
import showUserPanel from './UserPanel'
@@ -17,7 +17,8 @@ import { reloadList } from './useFetchList'
17
import { t, useI18N } from './i18n'
18
19
export function MenuPanel() {
20
- const { showFilter, remoteSearch, stopSearch, stoppedSearch, selected, can_upload, can_delete } = useSnapState()
20
+ const { showFilter, remoteSearch, stopSearch, stoppedSearch, selected, props } = useSnapState()
21
+ const { can_upload, can_delete, can_archive } = props ? { ...defaultPerms, ...props } : {} as VfsPerms
22
const { uploading, qs } = useSnapshot(uploadState)
23
useEffect(() => {
24
if (!showFilter)
@@ -87,6 +88,7 @@ export function MenuPanel() {
88
id: 'zip-button',
89
icon: 'archive',
90
label: t`Zip`,
91
+ disabled: !can_archive,
92
tooltip: list ? t('zip_tooltip_selected', "Download selected elements as a single zip file")
93
: t('zip_tooltip_whole', "Download whole list (unfiltered) as a single zip file. If you select some elements, only those will be downloaded."),
94
href: '?'+String(new URLSearchParams(_.pickBy({
frontend/src/state.ts
+7
-3
@@ -27,11 +27,15 @@ export const state = proxy<{
27
adminUrl?: string,
28
loginRequired?: boolean, // force user to login before proceeding
29
messageOnly?: string, // no gui, just show this message
30
- can_upload?: boolean
31
- can_delete?: boolean
32
- accept?: string
30
+ props?: {
31
+ can_upload?: boolean
32
+ accept?: string
33
+ can_delete?: boolean
34
+ can_archive?: boolean
35
+ }
36
tilesSize: number
37
}>({
38
+ props: {},
39
tilesSize: getHFS().tilesSize || 0,
40
iconsReady: false,
41
username: '',
frontend/src/upload.ts
+6
-6
@@ -100,7 +100,7 @@ export function showUpload() {
100
function Content(){
101
const adding = useSnapshot(uploadState.adding)
102
const { qs, paused, eta, skipExisting } = useSnapshot(uploadState)
103
- const { can_upload, accept } = useSnapState()
103
+ const { props } = useSnapState()
104
const etaStr = useMemo(() => !eta ? '' : formatTime(eta*1000, 0, 2), [eta])
105
const inQ = _.sumBy(qs, q => q.entries.length) - (uploadState.uploading ? 1 : 0)
106
const queueStr = inQ && t('in_queue', { n: inQ }, "{n} in queue")
@@ -108,12 +108,12 @@ export function showUpload() {
108
109
return h(FlexV, { gap: 0, props: acceptDropFiles(more => uploadState.adding.push(...more.map(f => ({ file: ref(f) })))) },
110
h(FlexV, { className: 'upload-toolbar' },
111
- !can_upload ? t('no_upload_here', "No upload permission for the current folder")
111
+ !props?.can_upload ? t('no_upload_here', "No upload permission for the current folder")
112
: h(FlexV, {},
113
h(Flex, { justifyContent: 'center', flexWrap: 'wrap', alignItems: 'center' },
114
h('button', {
115
className: 'upload-files',
116
- onClick: () => pickFiles({ accept: normalizeAccept(accept) })
116
+ onClick: () => pickFiles({ accept: normalizeAccept(props?.accept) })
117
}, t`Pick files`),
118
!isMobile() && h('button', {
119
className: 'upload-folder',
@@ -252,9 +252,9 @@ export async function enqueue(entries: ToUpload[]) {
252
}
253
254
function simulateBrowserAccept(f: File) {
255
- const { accept } = state
256
- if (!accept) return true
257
- return normalizeAccept(accept)!.split(/ *[|,] */).some(pattern =>
255
+ const { props } = state
256
+ if (!props?.accept) return true
257
+ return normalizeAccept(props?.accept)!.split(/ *[|,] */).some(pattern =>
258
pattern.startsWith('.') ? f.name.endsWith(pattern)
259
: f.type.match(pattern.replace('.','\\.').replace('*', '.*')) // '.' for .ext and '*' for 'image/*'
260
)
frontend/src/useFetchList.ts
+2
-5
@@ -47,8 +47,7 @@ export default function useFetchList() {
47
state.selected = {}
48
state.loading = true
49
state.error = undefined
50
- state.can_upload = undefined
51
- state.can_delete = undefined
50
+ state.props = undefined
51
// buffering entries is necessary against burst of events that will hang the browser
52
const buffer: DirList = []
53
const flush = () => {
@@ -94,11 +93,9 @@ export default function useFetchList() {
93
if (uri && !uri.endsWith('/')) // now we know it was a folder for sure
94
return navigate(uri + '/')
95
if (op === 'props') {
97
- Object.assign(state, _.pick(par, ['can_upload', 'can_delete', 'accept']))
96
+ state.props = par
97
continue
98
}
100
- state.can_upload ??= false
101
- state.can_delete ??= false
99
if (op === 'add')
100
buffer.push(new DirEntry(par.n, par))
101
}
src/api.file_list.ts
+3
-3
@@ -37,12 +37,12 @@ export const get_file_list: ApiHandler = async ({ uri, offset, limit, search, c
37
const can_upload = hasPermission(node, 'can_upload', ctx)
38
const fakeChild = applyParentToChild({}, node) // we want to know if we want to delete children
39
const can_delete = hasPermission(fakeChild, 'can_delete', ctx)
40
- const props = { can_upload, can_delete, accept: node.accept }
40
+ const can_archive = hasPermission(fakeChild, 'can_archive', ctx)
41
+ const props = { can_archive, can_upload, can_delete, accept: node.accept }
42
if (!list)
43
return { ...props, list: await asyncGeneratorToArray(produceEntries()) }
44
setTimeout(async () => {
44
- if (can_upload || can_delete)
45
- list.props(props)
45
+ list.props(props)
46
for await (const entry of produceEntries())
47
list.add(entry)
48
list.close()