Skip to content

Commit

Permalink
Improve file upload (#549)
Browse files Browse the repository at this point in the history
* translated display values

* implemented new upload approach

* found better solution -yey :D

* applied new design to `upload-list`

* simplified process & cleanup

* added `upload-zip` icon, fixed name for zip uploading

* Automatic frontend build

* fixed missmatching upload state, registered job in execution engine for `zip-upload`

* implemented mercure magic

* customized interfaces and props

* fixed display bug

* cleanup

* adapted `firstRun` logic

* Automatic frontend build

* further customized `Upload` component

* Automatic frontend build

* prevented reload of the `promiseCollection`

* cleanup

* Automatic frontend build

* added additional topic

* Automatic frontend build

* added multi step approach to progressbar

* Automatic frontend build

* cleanup

* build files

* Automatic frontend build

* added folder reload

* added translation key for folder reload

* build files

* Automatic frontend build

* bring back the security.md file

* renamed job to `zip-upload`

* build files

* Automatic frontend build

---------

Co-authored-by: Corepex <[email protected]>
  • Loading branch information
Corepex and Corepex authored Sep 30, 2024
1 parent d66b220 commit 24f9f0d
Show file tree
Hide file tree
Showing 38 changed files with 2,092 additions and 213 deletions.
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ Every submitted security issue is handled with top priority by following these s
5. Get a CVE identification number (may be done by the reporter or a security service provider)
6. Patch reviewing
7. Tagging a new release for supported versions
8. Publish security announcement
8. Publish security announcement
10 changes: 10 additions & 0 deletions assets/js/src/core/assets/icons/file-download-01.inline.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -11,66 +11,86 @@
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

import { Dropdown, type MenuProps } from 'antd'
import { Button, Dropdown, type MenuProps } from 'antd'
import React from 'react'
import { Icon } from '@Pimcore/components/icon/icon'
import { useTranslation } from 'react-i18next'
import { type TreeContextMenuProps } from '@Pimcore/modules/asset/tree/context-menu/context-menu'
import { UseFileUploader } from '@Pimcore/modules/element/upload/hook/use-file-uploader'
import { Upload, type UploadProps } from '@Pimcore/components/upload/upload'

export interface AssetTreeContextMenuProps {
node: TreeContextMenuProps['node']
children: React.ReactNode
uploadFiles: (event: any) => Promise<void>
fileInputRef: React.RefObject<HTMLInputElement>
uploadArchive: (event: any) => Promise<void>
archiveInputRef: React.RefObject<HTMLInputElement>
}

export const AssetTreeContextMenu = (props: AssetTreeContextMenuProps): React.JSX.Element => {
const { t } = useTranslation()
const { uploadFile: uploadFileProcessor, uploadZip: uploadZipProcessor } = UseFileUploader({ parentId: props.node?.id })
const uploadFileRef = React.useRef<HTMLButtonElement>(null)
const uploadZipRef = React.useRef<HTMLButtonElement>(null)

const items: MenuProps['items'] = [
{
label: 'Add Asset(s)',
label: t('asset.tree.context-menu.add-assets'),
key: '1',
children: [
{
icon: <Icon name={ 'upload-cloud' } />,
label: 'Upload Files',
label: t('asset.tree.context-menu.add-assets.upload-files'),
key: '1-1',
onClick: () => {
if (props.fileInputRef.current !== null) {
props.fileInputRef.current?.click()
if (uploadFileRef.current !== null) {
uploadFileRef.current?.click()
}
}
},
{
icon: <Icon name={ 'upload-cloud' } />,
label: 'Upload Zip',
icon: <Icon name={ 'upload-zip' } />,
label: t('asset.tree.context-menu.add-assets.upload-zip'),
key: '1-2',
onClick: () => {
if (props.archiveInputRef.current !== null) {
props.archiveInputRef.current?.click()
if (uploadZipRef.current !== null) {
uploadZipRef.current?.click()
}
}
}
]
}
]

const uploadFile: UploadProps = {
action: `/studio/api/assets/add/${props.node?.id}`,
name: 'file',
multiple: true,
showUploadList: false,
onChange: uploadFileProcessor
}

const uploadZip: UploadProps = {
action: `/studio/api/assets/add-zip/${props.node?.id}`,
accept: '.zip, .rar, .7zip',
name: 'zipFile',
multiple: true,
showUploadList: false,
onChange: uploadZipProcessor
}

return (
<>
<input
hidden
multiple
onChange={ props.uploadFiles }
ref={ props.fileInputRef }
type="file"
/>
<Upload { ...uploadFile }>
<Button
ref={ uploadFileRef }
style={ { display: 'none' } }
></Button>
</Upload>

<input
accept={ '.zip, .rar, .7zip' }
hidden
multiple
onChange={ props.uploadArchive }
ref={ props.archiveInputRef }
type="file"
/>
<Upload{ ...uploadZip }>
<Button
ref={ uploadZipRef }
style={ { display: 'none' } }
></Button>
</Upload>

<Dropdown
menu={ { items } }
Expand Down
8 changes: 4 additions & 4 deletions assets/js/src/core/components/tree/list/tree-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,22 @@ import { type TreeNodeProps } from '../node/tree-node'
import { TreeContext } from '../tree'
import { theme } from 'antd'
import { useStyles } from './tree-list.styles'
import { type UploadFile } from 'antd/es/upload/interface'
import { UploadList } from '@Pimcore/components/upload/upload-list/upload-list'
import { UploadContext } from '@Pimcore/modules/element/upload/upload-provider'

interface TreeListProps {
node: TreeNodeProps
uploadFileList: UploadFile[]
}

const { useToken } = theme

export const TreeList = ({ node, uploadFileList }: TreeListProps): React.JSX.Element => {
export const TreeList = ({ node }: TreeListProps): React.JSX.Element => {
const { token } = useToken()
const { styles } = useStyles()
const { renderFilter: RenderFilter, renderPager: RenderPager, renderNode: RenderNode, nodeApiHook } = useContext(TreeContext)
const { apiHookResult, dataTransformer, mergeAdditionalQueryParams } = nodeApiHook(node)
const { isLoading, isError, data } = apiHookResult
const { uploadFileList, uploadingNode } = useContext(UploadContext)!

if (isLoading === true) {
return <></>
Expand Down Expand Up @@ -59,7 +59,7 @@ export const TreeList = ({ node, uploadFileList }: TreeListProps): React.JSX.Ele
)}

<div className='tree-list'>
{uploadFileList.length > 0 && (
{uploadFileList.length > 0 && node.id === uploadingNode && (
<div
className={ ['tree-list__upload', styles['tree-list__search']].join(' ') }
style={ { paddingLeft: token.paddingSM + (node.level + 1) * 24 } }
Expand Down
24 changes: 4 additions & 20 deletions assets/js/src/core/components/tree/node/tree-node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ import { useStyles } from './tree-node.styles'
import { type nodeRef, TreeContext } from '../tree'
import { TreeList } from '../list/tree-list'
import { TreeExpander } from '../expander/tree-expander'
import { type UploadFile } from 'antd/es/upload/interface'
import { api as assetApi } from '@Pimcore/modules/asset/asset-api-slice-enhanced'
import { invalidatingTags } from '@Pimcore/app/api/pimcore/tags'
import { useAppDispatch } from '@Pimcore/app/store'
import { UseFileUploader } from '@Pimcore/modules/element/upload/hook/use-file-uploader'

export interface TreeNodeProps {
id: string
Expand Down Expand Up @@ -63,11 +60,10 @@ const TreeNode = ({
nodesRefs,
nodeOrder
} = useContext(TreeContext)
const dispatch = useAppDispatch()
const [isExpanded, setIsExpanded] = React.useState(children.length !== 0)
const [selectedIds, setSelectedIds] = selectedIdsState!
const [uploadFileList, setUploadFileList] = React.useState<UploadFile[]>([])
const treeNodeProps = { id, icon, label, internalKey, level, ...props }
const { uploadFile: uploadFileProcessor } = UseFileUploader({ parentId: id })

useEffect(() => {
return () => {
Expand Down Expand Up @@ -179,16 +175,7 @@ const TreeNode = ({
multiple: true,
openFileDialogOnClick: false,
showUploadList: false,
onChange: ({ fileList }) => {
const fileStates = fileList.map((file) => file.status)
const allFullFilled = fileStates.every(item => item === 'done')

if (allFullFilled) {
dispatch(assetApi.util.invalidateTags(invalidatingTags.ASSET_TREE_ID(parseInt(id))))
}

setUploadFileList(fileList.filter((file) => file.status === 'uploading'))
}
onChange: uploadFileProcessor
}

return (
Expand Down Expand Up @@ -225,10 +212,7 @@ const TreeNode = ({
</Flex>

{isExpanded && (
<TreeList
node={ treeNodeProps }
uploadFileList={ uploadFileList }
/>
<TreeList node={ treeNodeProps } />
)}
</div>
)
Expand Down
5 changes: 3 additions & 2 deletions assets/js/src/core/components/tree/tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { container } from '@Pimcore/app/depency-injection'
import { type ComponentRegistry } from '@Pimcore/modules/app/component-registry/component-registry'
import { serviceIds } from '@Pimcore/app/config/services'
import { type TreeContextMenuProps } from '@Pimcore/modules/asset/tree/context-menu/context-menu'
import { UploadProvider } from '@Pimcore/modules/element/upload/upload-provider'

export interface TreeSearchProps {
node: TreeNodeProps
Expand Down Expand Up @@ -140,7 +141,7 @@ const Tree = (
const TreeNode = renderNode

return (
<>
<UploadProvider>
{isLoading === false && items.length !== 0 && (
<ContextMenu node={ rightClickedNode }>
<div className={ ['tree', styles.tree].join(' ') }>
Expand All @@ -156,7 +157,7 @@ const Tree = (
</div>
</ContextMenu>
)}
</>
</UploadProvider>
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,11 @@ import { createStyles } from 'antd-style'
export const useStyles = createStyles(({ token, css }) => {
return {
uploadList: css`
.ant-upload-list {
.ant-upload-list-item-container {
padding-bottom: 4px;
.file-upload-list__file-details {
display: flex;
gap: 8px;
> .file-upload-list__file__filename {
margin: 0
}
}
}
}
margin-top: ${token.paddingSM}px;
margin-bottom: ${token.paddingSM}px;
display: flex;
flex-direction: column;
align-items: center;
`
}
})
64 changes: 16 additions & 48 deletions assets/js/src/core/components/upload/upload-list/upload-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,62 +12,30 @@
*/

import { type UploadListProps } from 'antd/es/upload'
import React, { type ReactElement } from 'react'
import AntUploadList from 'antd/es/upload/UploadList'
import { useStyles } from '@Pimcore/components/upload/upload-list/upload-list.styles'
import { type UploadFile } from 'antd/es/upload/interface'
import { Icon } from '@Pimcore/components/icon/icon'
import React from 'react'
import { Progress } from 'antd'
import { useStyles } from './upload-list.styles'

export const UploadList = (props: UploadListProps): React.JSX.Element => {
const { styles } = useStyles()

const iconRenderer = (file: UploadFile): React.JSX.Element => {
const fileType = file.type

switch (fileType) {
default:
return <Icon name={ 'image-01' } />
}
}

const itemRender = (originNode: ReactElement, file: UploadFile, fileList: object[]): React.JSX.Element => {
const icon = iconRenderer(file)

return (
<>
<div className={ 'file-upload-list__file-details' }>
{icon}

<p className={ 'file-upload-list__file__filename' }>
{file.name}
</p>
</div>

{'percent' in file && (
<div className={ 'file-upload-list__file__progress' }>
<Progress
{ ...props.progress }
aria-label={ file['aria-label'] }
aria-labelledby={ file['aria-labelledby'] }
percent={ file.percent }
showInfo={ false }
size={ [-1, 2] }
type="line"
/>
</div>
)}
</>
)
}
const items = props.items!
const totalCount = items.length
const doneCount = items.filter(file => file.status === 'done').length

return (
<div className={ styles.uploadList }>
<AntUploadList
iconRender={ iconRenderer }
itemRender={ itemRender }
{ ...props }
<Progress
{ ...props.progress }
aria-label={ 'upload progress' }
percent={ (doneCount / totalCount) * 100 }
showInfo={ false }
size={ [-1, 2] }
type="line"
/>

<span>
{doneCount}/{totalCount} files uploaded
</span>
</div>
)
}
22 changes: 22 additions & 0 deletions assets/js/src/core/components/upload/upload.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Pimcore
*
* This source file is available under two different licenses:
* - Pimcore Open Core License (POCL)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

import { createStyles } from 'antd-style'

export const useStyles = createStyles(({ token, css }) => {
return {
upload: css`
display: none
`
}
})
Loading

0 comments on commit 24f9f0d

Please sign in to comment.