mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 11:01:54 +03:00
UBERF-7632 Upload folders to drive (#6104)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
parent
fe12d4930f
commit
434163f00a
@ -142,7 +142,7 @@
|
||||
list,
|
||||
{ objectId: object._id, objectClass: object._class },
|
||||
{},
|
||||
async (uuid, name, file, metadata) => {
|
||||
async (uuid, name, file, path, metadata) => {
|
||||
await createAttachment(uuid, name, file, metadata)
|
||||
}
|
||||
)
|
||||
@ -158,7 +158,7 @@
|
||||
files,
|
||||
{ objectId: object._id, objectClass: object._class },
|
||||
{},
|
||||
async (uuid, name, file, metadata) => {
|
||||
async (uuid, name, file, path, metadata) => {
|
||||
await createAttachment(uuid, name, file, metadata)
|
||||
}
|
||||
)
|
||||
|
@ -15,13 +15,14 @@
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { Ref, generateId } from '@hcengineering/core'
|
||||
import { Drive, Folder } from '@hcengineering/drive'
|
||||
import core, { Data, Ref } from '@hcengineering/core'
|
||||
import { type Drive, type Folder, createFolder } from '@hcengineering/drive'
|
||||
import { Card, SpaceSelector, getClient } from '@hcengineering/presentation'
|
||||
import { EditBox, FocusHandler, createFocusManager } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { ObjectBox } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import drive from '../plugin'
|
||||
|
||||
export function canClose (): boolean {
|
||||
@ -31,8 +32,6 @@
|
||||
export let space: Ref<Drive> | undefined
|
||||
export let parent: Ref<Folder> | undefined
|
||||
|
||||
const id: Ref<Folder> = generateId()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
@ -52,26 +51,12 @@
|
||||
return
|
||||
}
|
||||
|
||||
let path: Ref<Folder>[] = []
|
||||
|
||||
if (_parent != null && _parent !== drive.ids.Root) {
|
||||
const parent = await client.findOne(drive.class.Folder, { _id: _parent })
|
||||
if (parent === undefined) {
|
||||
throw new Error('parent not found')
|
||||
}
|
||||
path = [parent._id, ...parent.path]
|
||||
const data: Omit<Data<Folder>, 'path'> = {
|
||||
name: getTitle(name),
|
||||
parent: _parent ?? drive.ids.Root
|
||||
}
|
||||
|
||||
await client.createDoc(
|
||||
drive.class.Folder,
|
||||
_space,
|
||||
{
|
||||
name: getTitle(name),
|
||||
parent: _parent ?? drive.ids.Root,
|
||||
path
|
||||
},
|
||||
id
|
||||
)
|
||||
const id = await createFolder(client, _space, data)
|
||||
|
||||
dispatch('close', id)
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
import drive from '../plugin'
|
||||
import { getFolderIdFromFragment } from '../navigation'
|
||||
import { createDrive, createFolder } from '../utils'
|
||||
import { showCreateDrivePopup, showCreateFolderPopup } from '../utils'
|
||||
|
||||
export let currentSpace: Ref<Drive> | undefined
|
||||
export let currentFragment: string | undefined
|
||||
@ -57,11 +57,11 @@
|
||||
}
|
||||
|
||||
async function handleCreateDrive (): Promise<void> {
|
||||
await createDrive()
|
||||
await showCreateDrivePopup()
|
||||
}
|
||||
|
||||
async function handleCreateFolder (): Promise<void> {
|
||||
await createFolder(currentSpace, parent, true)
|
||||
await showCreateFolderPopup(currentSpace, parent, true)
|
||||
}
|
||||
|
||||
async function handleUploadFile (): Promise<void> {
|
||||
@ -71,7 +71,7 @@
|
||||
parent !== drive.ids.Root
|
||||
? { objectId: parent, objectClass: drive.class.Folder }
|
||||
: { objectId: space, objectClass: drive.class.Drive }
|
||||
await showFilesUploadPopup(target, {}, async (uuid, name, file, metadata) => {
|
||||
await showFilesUploadPopup(target, {}, async (uuid, name, file, path, metadata) => {
|
||||
try {
|
||||
const data = {
|
||||
file: uuid,
|
||||
|
@ -14,17 +14,13 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
import drive, { createFile, type Drive, type Folder } from '@hcengineering/drive'
|
||||
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { uploadFiles } from '@hcengineering/uploader'
|
||||
import { type Drive, type Folder } from '@hcengineering/drive'
|
||||
import { uploadFilesToDrive } from '../utils'
|
||||
|
||||
export let space: Ref<Drive>
|
||||
export let parent: Ref<Folder>
|
||||
export let canDrop: ((e: DragEvent) => boolean) | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let dragover = false
|
||||
let counter = 0
|
||||
|
||||
@ -66,28 +62,8 @@
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const list = e.dataTransfer?.files
|
||||
if (list !== undefined && list.length !== 0) {
|
||||
const target =
|
||||
parent !== drive.ids.Root
|
||||
? { objectId: parent, objectClass: drive.class.Folder }
|
||||
: { objectId: space, objectClass: drive.class.Drive }
|
||||
await uploadFiles(list, target, {}, async (uuid, name, file, metadata) => {
|
||||
try {
|
||||
const data = {
|
||||
file: uuid,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
lastModified: file instanceof File ? file.lastModified : Date.now(),
|
||||
name,
|
||||
metadata
|
||||
}
|
||||
|
||||
await createFile(client, space, parent, data)
|
||||
} catch (err) {
|
||||
void setPlatformStatus(unknownError(err))
|
||||
}
|
||||
})
|
||||
if (e.dataTransfer != null) {
|
||||
await uploadFilesToDrive(e.dataTransfer, space, parent)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -74,7 +74,7 @@
|
||||
maxNumberOfFiles: 1,
|
||||
hideProgress: true
|
||||
},
|
||||
async (uuid, name, file, metadata) => {
|
||||
async (uuid, name, file, path, metadata) => {
|
||||
const data = {
|
||||
file: uuid,
|
||||
name,
|
||||
|
@ -38,14 +38,14 @@ import MoveResource from './components/MoveResource.svelte'
|
||||
import ResourcePresenter from './components/ResourcePresenter.svelte'
|
||||
|
||||
import { getDriveLink, getFileLink, getFolderLink, resolveLocation } from './navigation'
|
||||
import { createFolder, renameResource, restoreFileVersion } from './utils'
|
||||
import { showCreateFolderPopup, showRenameResourcePopup, restoreFileVersion } from './utils'
|
||||
|
||||
async function CreateRootFolder (doc: Drive): Promise<void> {
|
||||
await createFolder(doc._id, drive.ids.Root)
|
||||
await showCreateFolderPopup(doc._id, drive.ids.Root)
|
||||
}
|
||||
|
||||
async function CreateChildFolder (doc: Folder): Promise<void> {
|
||||
await createFolder(doc.space, doc._id)
|
||||
await showCreateFolderPopup(doc.space, doc._id)
|
||||
}
|
||||
|
||||
async function EditDrive (drive: Drive): Promise<void> {
|
||||
@ -82,13 +82,13 @@ async function FileLinkProvider (doc: Doc): Promise<Location> {
|
||||
|
||||
async function RenameFile (doc: File | File[]): Promise<void> {
|
||||
if (!Array.isArray(doc)) {
|
||||
await renameResource(doc)
|
||||
await showRenameResourcePopup(doc)
|
||||
}
|
||||
}
|
||||
|
||||
async function RenameFolder (doc: Folder | Folder[]): Promise<void> {
|
||||
if (!Array.isArray(doc)) {
|
||||
await renameResource(doc)
|
||||
await showRenameResourcePopup(doc)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,11 +14,12 @@
|
||||
//
|
||||
|
||||
import { type Class, type Doc, type Ref, toIdMap } from '@hcengineering/core'
|
||||
import type { Drive, FileVersion, Folder, Resource } from '@hcengineering/drive'
|
||||
import drive from '@hcengineering/drive'
|
||||
import { type Asset } from '@hcengineering/platform'
|
||||
import { type Drive, type FileVersion, type Folder, type Resource, createFolder } from '@hcengineering/drive'
|
||||
import drive, { createFile } from '@hcengineering/drive'
|
||||
import { type Asset, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { type AnySvelteComponent, showPopup } from '@hcengineering/ui'
|
||||
import { uploadFiles } from '@hcengineering/uploader'
|
||||
import { openDoc } from '@hcengineering/view-resources'
|
||||
|
||||
import CreateDrive from './components/CreateDrive.svelte'
|
||||
@ -43,7 +44,11 @@ export function formatFileVersion (version: number): string {
|
||||
return `v${version}`
|
||||
}
|
||||
|
||||
export async function createFolder (space: Ref<Drive> | undefined, parent: Ref<Folder>, open = false): Promise<void> {
|
||||
export async function showCreateFolderPopup (
|
||||
space: Ref<Drive> | undefined,
|
||||
parent: Ref<Folder>,
|
||||
open = false
|
||||
): Promise<void> {
|
||||
showPopup(CreateFolder, { space, parent }, 'top', async (id) => {
|
||||
if (open && id !== undefined && id !== null) {
|
||||
await navigateToDoc(id, drive.class.Folder)
|
||||
@ -51,7 +56,7 @@ export async function createFolder (space: Ref<Drive> | undefined, parent: Ref<F
|
||||
})
|
||||
}
|
||||
|
||||
export async function createDrive (open = false): Promise<void> {
|
||||
export async function showCreateDrivePopup (open = false): Promise<void> {
|
||||
showPopup(CreateDrive, {}, 'top', async (id) => {
|
||||
if (open && id !== undefined && id !== null) {
|
||||
await navigateToDoc(id, drive.class.Folder)
|
||||
@ -59,11 +64,11 @@ export async function createDrive (open = false): Promise<void> {
|
||||
})
|
||||
}
|
||||
|
||||
export async function editDrive (drive: Drive): Promise<void> {
|
||||
export async function showEditDrivePopup (drive: Drive): Promise<void> {
|
||||
showPopup(CreateDrive, { drive })
|
||||
}
|
||||
|
||||
export async function renameResource (resource: Resource): Promise<void> {
|
||||
export async function showRenameResourcePopup (resource: Resource): Promise<void> {
|
||||
showPopup(RenamePopup, { value: resource.name, format: 'text' }, undefined, async (res) => {
|
||||
if (res != null && res !== resource.name) {
|
||||
const client = getClient()
|
||||
@ -148,3 +153,63 @@ export async function resolveParents (object: Resource): Promise<Doc[]> {
|
||||
|
||||
return parents.reverse()
|
||||
}
|
||||
|
||||
export async function uploadFilesToDrive (files: DataTransfer, space: Ref<Drive>, parent: Ref<Folder>): Promise<void> {
|
||||
const client = getClient()
|
||||
|
||||
const query = parent !== drive.ids.Root ? { space, path: parent } : { space }
|
||||
const folders = await client.findAll(drive.class.Folder, query)
|
||||
const foldersByName = new Map(folders.map((folder) => [folder.name, folder]))
|
||||
|
||||
const findParent = async (path: string | undefined): Promise<Ref<Folder>> => {
|
||||
if (path == null || path.length === 0) {
|
||||
return parent
|
||||
}
|
||||
|
||||
const segments = path.split('/').filter((p) => p.length > 0)
|
||||
if (segments.length <= 1) {
|
||||
return parent
|
||||
}
|
||||
|
||||
let current = parent
|
||||
while (segments.length > 1) {
|
||||
const name = segments.shift()
|
||||
if (name !== undefined) {
|
||||
let folder = foldersByName.get(name)
|
||||
if (folder !== undefined) {
|
||||
current = folder._id
|
||||
} else {
|
||||
current = await createFolder(client, space, { name, parent: current })
|
||||
folder = await client.findOne(drive.class.Folder, { _id: current })
|
||||
if (folder !== undefined) {
|
||||
foldersByName.set(folder.name, folder)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
const target =
|
||||
parent !== drive.ids.Root
|
||||
? { objectId: parent, objectClass: drive.class.Folder }
|
||||
: { objectId: space, objectClass: drive.class.Drive }
|
||||
|
||||
await uploadFiles(files, target, {}, async (uuid, name, file, path, metadata) => {
|
||||
const folder = await findParent(path)
|
||||
try {
|
||||
const data = {
|
||||
file: uuid,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
lastModified: file instanceof File ? file.lastModified : Date.now(),
|
||||
name,
|
||||
metadata
|
||||
}
|
||||
|
||||
await createFile(client, space, folder, data)
|
||||
} catch (err) {
|
||||
void setPlatformStatus(unknownError(err))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -13,11 +13,30 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type AttachedData, type Ref, type TxOperations, generateId } from '@hcengineering/core'
|
||||
import { type AttachedData, type Data, type Ref, type TxOperations, generateId } from '@hcengineering/core'
|
||||
|
||||
import drive from './plugin'
|
||||
import type { Drive, File, FileVersion, Folder } from './types'
|
||||
|
||||
/** @public */
|
||||
export async function createFolder (
|
||||
client: TxOperations,
|
||||
space: Ref<Drive>,
|
||||
data: Omit<Data<Folder>, 'path'>
|
||||
): Promise<Ref<Folder>> {
|
||||
let path: Array<Ref<Folder>> = []
|
||||
|
||||
if (data.parent !== drive.ids.Root) {
|
||||
const parent = await client.findOne(drive.class.Folder, { _id: data.parent })
|
||||
if (parent === undefined) {
|
||||
throw new Error('parent not found')
|
||||
}
|
||||
path = [parent._id, ...parent.path]
|
||||
}
|
||||
|
||||
return await client.createDoc(drive.class.Folder, space, { ...data, path })
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export async function createFile (
|
||||
client: TxOperations,
|
||||
|
@ -49,7 +49,9 @@ type Meta = IndexedObject<any>
|
||||
type Body = IndexedObject<any>
|
||||
|
||||
/** @public */
|
||||
export type UppyMeta = Meta
|
||||
export type UppyMeta = Meta & {
|
||||
relativePath?: string
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type UppyBody = Body & {
|
||||
@ -89,7 +91,7 @@ export function getUppy (options: FileUploadOptions, onFileUploaded?: FileUpload
|
||||
const uuid = file?.response?.body?.uuid as Ref<Blob>
|
||||
if (uuid !== undefined) {
|
||||
const metadata = await getFileMetadata(file.data, uuid)
|
||||
await onFileUploaded(uuid, file.name, file.data, metadata)
|
||||
await onFileUploaded(uuid, file.name, file.data, file.meta.relativePath, metadata)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -14,7 +14,13 @@
|
||||
//
|
||||
|
||||
import { showPopup } from '@hcengineering/ui'
|
||||
import type { FileUploadCallback, FileUploadOptions, FileUploadTarget } from '@hcengineering/uploader'
|
||||
import {
|
||||
type FileUploadCallback,
|
||||
type FileUploadOptions,
|
||||
type FileUploadTarget,
|
||||
getDataTransferFiles,
|
||||
toFileWithPath
|
||||
} from '@hcengineering/uploader'
|
||||
|
||||
import FileUploadPopup from './components/FileUploadPopup.svelte'
|
||||
|
||||
@ -38,19 +44,21 @@ export async function showFilesUploadPopup (
|
||||
|
||||
/** @public */
|
||||
export async function uploadFiles (
|
||||
files: File[] | FileList,
|
||||
files: File[] | FileList | DataTransfer,
|
||||
target: FileUploadTarget,
|
||||
options: FileUploadOptions,
|
||||
onFileUploaded: FileUploadCallback
|
||||
): Promise<void> {
|
||||
if (files.length === 0) return
|
||||
const items =
|
||||
files instanceof DataTransfer ? await getDataTransferFiles(files) : Array.from(files, (p) => toFileWithPath(p))
|
||||
|
||||
if (items.length === 0) return
|
||||
|
||||
const uppy = getUppy(options, onFileUploaded)
|
||||
|
||||
for (let index = 0; index < files.length; index++) {
|
||||
const data = files[index]
|
||||
const { name, type } = data
|
||||
uppy.addFile({ name, type, data })
|
||||
for (const data of items) {
|
||||
const { name, type, relativePath } = data
|
||||
uppy.addFile({ name, type, data, meta: { relativePath } })
|
||||
}
|
||||
|
||||
if (options.hideProgress !== true) {
|
||||
|
@ -15,6 +15,11 @@
|
||||
|
||||
import type { Blob as PlatformBlob, Class, Doc, Ref } from '@hcengineering/core'
|
||||
|
||||
/** @public */
|
||||
export interface FileWithPath extends File {
|
||||
relativePath?: string
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type UploadFilesPopupFn = (
|
||||
target: FileUploadTarget,
|
||||
@ -24,7 +29,7 @@ export type UploadFilesPopupFn = (
|
||||
|
||||
/** @public */
|
||||
export type UploadFilesFn = (
|
||||
files: File[] | FileList,
|
||||
files: File[] | FileList | DataTransfer,
|
||||
target: FileUploadTarget,
|
||||
options: FileUploadOptions,
|
||||
onFileUploaded: FileUploadCallback
|
||||
@ -48,6 +53,7 @@ export interface FileUploadOptions {
|
||||
export type FileUploadCallback = (
|
||||
uuid: Ref<PlatformBlob>,
|
||||
name: string,
|
||||
file: File | Blob,
|
||||
file: FileWithPath | Blob,
|
||||
path: string | undefined,
|
||||
metadata: Record<string, any> | undefined
|
||||
) => Promise<void>
|
||||
|
@ -16,7 +16,7 @@
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
|
||||
import uploader from './plugin'
|
||||
import type { FileUploadCallback, FileUploadOptions, FileUploadTarget } from './types'
|
||||
import type { FileUploadCallback, FileUploadOptions, FileUploadTarget, FileWithPath } from './types'
|
||||
|
||||
/** @public */
|
||||
export async function showFilesUploadPopup (
|
||||
@ -30,7 +30,7 @@ export async function showFilesUploadPopup (
|
||||
|
||||
/** @public */
|
||||
export async function uploadFiles (
|
||||
files: File[] | FileList,
|
||||
files: File[] | FileList | DataTransfer,
|
||||
target: FileUploadTarget,
|
||||
options: FileUploadOptions,
|
||||
onFileUploaded: FileUploadCallback
|
||||
@ -38,3 +38,88 @@ export async function uploadFiles (
|
||||
const fn = await getResource(uploader.function.UploadFiles)
|
||||
await fn(files, target, options, onFileUploaded)
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export async function getDataTransferFiles (dataTransfer: DataTransfer): Promise<FileWithPath[]> {
|
||||
try {
|
||||
const accumulator = []
|
||||
const entries = Array.from(dataTransfer.items, getAsEntry)
|
||||
for (const entry of entries) {
|
||||
if (entry != null) {
|
||||
const files = await fromEntry(entry)
|
||||
if (Array.isArray(files)) {
|
||||
accumulator.push(...files)
|
||||
} else {
|
||||
accumulator.push(files)
|
||||
}
|
||||
}
|
||||
}
|
||||
return accumulator
|
||||
} catch {
|
||||
return Array.from(dataTransfer.files, (file) => toFileWithPath(file))
|
||||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function toFileWithPath (file: File, path?: string): FileWithPath {
|
||||
const { webkitRelativePath } = file
|
||||
Object.defineProperty(file, 'relativePath', {
|
||||
value:
|
||||
typeof path === 'string'
|
||||
? path
|
||||
: typeof webkitRelativePath === 'string' && webkitRelativePath.length > 0
|
||||
? webkitRelativePath
|
||||
: file.name,
|
||||
writable: false,
|
||||
configurable: false,
|
||||
enumerable: true
|
||||
})
|
||||
return file
|
||||
}
|
||||
|
||||
function getAsEntry (item: DataTransferItem): FileSystemEntry | null | undefined {
|
||||
// https://developer.mozilla.org/docs/Web/API/DataTransferItem/webkitGetAsEntry
|
||||
return (item as any).getAsEntry === 'function' ? (item as any).getAsEntry() : item.webkitGetAsEntry()
|
||||
}
|
||||
|
||||
async function fromEntry (entry: FileSystemEntry): Promise<FileWithPath | FileWithPath[]> {
|
||||
return entry.isDirectory
|
||||
? await fromDirEntry(entry as FileSystemDirectoryEntry)
|
||||
: await fromFileEntry(entry as FileSystemFileEntry)
|
||||
}
|
||||
|
||||
async function fromFileEntry (entry: FileSystemFileEntry): Promise<FileWithPath> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
entry.file((file) => {
|
||||
resolve(toFileWithPath(file, entry.fullPath))
|
||||
}, reject)
|
||||
})
|
||||
}
|
||||
|
||||
async function fromDirEntry (entry: FileSystemDirectoryEntry): Promise<FileWithPath | FileWithPath[]> {
|
||||
const reader = entry.createReader()
|
||||
|
||||
return await new Promise((resolve, reject) => {
|
||||
const promises: Promise<File | File[]>[] = []
|
||||
|
||||
function readEntries (): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
reader.readEntries(async (entries: FileSystemEntry[]) => {
|
||||
if (entries.length === 0) {
|
||||
try {
|
||||
const files = await Promise.all(promises)
|
||||
resolve(files.flat())
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
} else {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryReader/readEntries
|
||||
promises.push(...entries.map(fromEntry))
|
||||
readEntries()
|
||||
}
|
||||
}, reject)
|
||||
}
|
||||
|
||||
readEntries()
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user