mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 11:42:30 +03:00
UBERF-7605 Use uppy for file upload in more places (#6091)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
parent
702f30ee2a
commit
c34b3a7ad2
@ -47,6 +47,7 @@
|
||||
"@hcengineering/ui": "^0.6.15",
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/text": "^0.6.5",
|
||||
"@hcengineering/uploader": "^0.6.0",
|
||||
"svelte": "^4.2.12",
|
||||
"@hcengineering/client": "^0.6.18",
|
||||
"@hcengineering/collaborator-client": "^0.6.4",
|
||||
|
@ -15,16 +15,16 @@
|
||||
|
||||
import extract from 'png-chunks-extract'
|
||||
|
||||
export async function getImageSize (
|
||||
file: File,
|
||||
src: string
|
||||
): Promise<{ width: number, height: number, pixelRatio: number }> {
|
||||
export async function getImageSize (file: Blob): Promise<{ width: number, height: number, pixelRatio: number }> {
|
||||
const size = isPng(file) ? await getPngImageSize(file) : undefined
|
||||
|
||||
const promise = new Promise<{ width: number, height: number, pixelRatio: number }>((resolve, reject) => {
|
||||
const img = new Image()
|
||||
|
||||
const src = URL.createObjectURL(file)
|
||||
|
||||
img.onload = () => {
|
||||
URL.revokeObjectURL(src)
|
||||
resolve({
|
||||
width: size?.width ?? img.naturalWidth,
|
||||
height: size?.height ?? img.naturalHeight,
|
||||
@ -39,11 +39,11 @@ export async function getImageSize (
|
||||
return await promise
|
||||
}
|
||||
|
||||
function isPng (file: File): boolean {
|
||||
function isPng (file: Blob): boolean {
|
||||
return file.type === 'image/png'
|
||||
}
|
||||
|
||||
async function getPngImageSize (file: File): Promise<{ width: number, height: number, pixelRatio: number } | undefined> {
|
||||
async function getPngImageSize (file: Blob): Promise<{ width: number, height: number, pixelRatio: number } | undefined> {
|
||||
if (!isPng(file)) {
|
||||
return undefined
|
||||
}
|
||||
|
@ -51,6 +51,7 @@
|
||||
"@hcengineering/text-editor": "^0.6.0",
|
||||
"@hcengineering/text-editor-resources": "^0.6.0",
|
||||
"@hcengineering/ui": "^0.6.15",
|
||||
"@hcengineering/uploader": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"filesize": "^8.0.3",
|
||||
|
@ -13,11 +13,18 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import attachment, { Attachment, BlobMetadata } from '@hcengineering/attachment'
|
||||
import contact from '@hcengineering/contact'
|
||||
import core, { Account, Doc, Ref, generateId, type Blob } from '@hcengineering/core'
|
||||
import { IntlString, getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { KeyedAttribute, createQuery, getClient, uploadFile } from '@hcengineering/presentation'
|
||||
import {
|
||||
FileOrBlob,
|
||||
KeyedAttribute,
|
||||
createQuery,
|
||||
getClient,
|
||||
getFileMetadata,
|
||||
uploadFile
|
||||
} from '@hcengineering/presentation'
|
||||
import { getCollaborationUser, getObjectLinkFragment } from '@hcengineering/view-resources'
|
||||
import textEditor, { type RefAction, type TextEditorHandler } from '@hcengineering/text-editor'
|
||||
import {
|
||||
@ -29,6 +36,7 @@
|
||||
getModelRefActions
|
||||
} from '@hcengineering/text-editor-resources'
|
||||
import { AnySvelteComponent, getEventPositionElement, getPopupPositionElement, navigate } from '@hcengineering/ui'
|
||||
import { uploadFiles } from '@hcengineering/uploader'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
import AttachmentsGrid from './AttachmentsGrid.svelte'
|
||||
@ -124,23 +132,60 @@
|
||||
|
||||
async function fileSelected (): Promise<void> {
|
||||
if (readonly) return
|
||||
progress = true
|
||||
|
||||
const list = inputFile.files
|
||||
if (list === null || list.length === 0) return
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const file = list.item(index)
|
||||
if (file !== null) {
|
||||
await createAttachment(file)
|
||||
|
||||
progress = true
|
||||
|
||||
await uploadFiles(
|
||||
list,
|
||||
{ objectId: object._id, objectClass: object._class },
|
||||
{},
|
||||
async (uuid, name, file, metadata) => {
|
||||
await createAttachment(uuid, name, file, metadata)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
inputFile.value = ''
|
||||
progress = false
|
||||
}
|
||||
|
||||
async function createAttachment (file: File): Promise<{ file: Ref<Blob>, type: string } | undefined> {
|
||||
async function attachFiles (files: File[] | FileList): Promise<void> {
|
||||
progress = true
|
||||
if (files.length > 0) {
|
||||
await uploadFiles(
|
||||
files,
|
||||
{ objectId: object._id, objectClass: object._class },
|
||||
{},
|
||||
async (uuid, name, file, metadata) => {
|
||||
await createAttachment(uuid, name, file, metadata)
|
||||
}
|
||||
)
|
||||
}
|
||||
progress = false
|
||||
}
|
||||
|
||||
async function attachFile (file: File): Promise<{ file: Ref<Blob>, type: string } | undefined> {
|
||||
try {
|
||||
const uuid = await uploadFile(file)
|
||||
const metadata = await getFileMetadata(file, uuid)
|
||||
await createAttachment(uuid, file.name, file, metadata)
|
||||
return { file: uuid, type: file.type }
|
||||
} catch (err: any) {
|
||||
await setPlatformStatus(unknownError(err))
|
||||
}
|
||||
}
|
||||
|
||||
async function createAttachment (
|
||||
uuid: Ref<Blob>,
|
||||
name: string,
|
||||
file: FileOrBlob,
|
||||
metadata: BlobMetadata | undefined
|
||||
): Promise<void> {
|
||||
try {
|
||||
const _id: Ref<Attachment> = generateId()
|
||||
|
||||
const attachmentDoc: Attachment = {
|
||||
_id,
|
||||
_class: attachment.class.Attachment,
|
||||
@ -150,11 +195,12 @@
|
||||
space: object.space,
|
||||
attachedTo: object._id,
|
||||
attachedToClass: object._class,
|
||||
name: file.name,
|
||||
name,
|
||||
file: uuid,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
lastModified: file.lastModified
|
||||
metadata,
|
||||
lastModified: file instanceof File ? file.lastModified : Date.now()
|
||||
}
|
||||
|
||||
await client.addCollection(
|
||||
@ -166,7 +212,6 @@
|
||||
attachmentDoc,
|
||||
attachmentDoc._id
|
||||
)
|
||||
return { file: uuid, type: file.type }
|
||||
} catch (err: any) {
|
||||
await setPlatformStatus(unknownError(err))
|
||||
}
|
||||
@ -195,31 +240,34 @@
|
||||
return
|
||||
}
|
||||
|
||||
progress = true
|
||||
|
||||
const items = evt.clipboardData?.items ?? []
|
||||
const files = []
|
||||
for (const index in items) {
|
||||
const item = items[index]
|
||||
if (item.kind === 'file') {
|
||||
const blob = item.getAsFile()
|
||||
if (blob !== null) {
|
||||
await createAttachment(blob)
|
||||
files.push(blob)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
await attachFiles(files)
|
||||
}
|
||||
|
||||
progress = false
|
||||
}
|
||||
|
||||
export async function fileDrop (e: DragEvent): Promise<void> {
|
||||
if (readonly) return
|
||||
progress = true
|
||||
|
||||
const list = e.dataTransfer?.files
|
||||
if (list !== undefined && list.length !== 0) {
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const file = list.item(index)
|
||||
if (file !== null) {
|
||||
await createAttachment(file)
|
||||
}
|
||||
}
|
||||
await attachFiles(list)
|
||||
}
|
||||
progress = false
|
||||
}
|
||||
|
||||
async function removeAttachment (attachment: Attachment): Promise<void> {
|
||||
@ -274,9 +322,7 @@
|
||||
{boundary}
|
||||
{refActions}
|
||||
{readonly}
|
||||
attachFile={async (file) => {
|
||||
return await createAttachment(file)
|
||||
}}
|
||||
{attachFile}
|
||||
on:open-document={async (event) => {
|
||||
const doc = await client.findOne(event.detail._class, { _id: event.detail._id })
|
||||
if (doc != null) {
|
||||
|
@ -409,7 +409,7 @@
|
||||
return await createAttachment(file)
|
||||
}}
|
||||
/>
|
||||
{#if (attachments.size > 0 && enableAttachments) || progress}
|
||||
{#if attachments.size > 0 && enableAttachments}
|
||||
<AttachmentsGrid
|
||||
attachments={Array.from(attachments.values())}
|
||||
{progress}
|
||||
|
@ -15,15 +15,18 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Attachment } from '@hcengineering/attachment'
|
||||
import { Class, Data, Doc, DocumentQuery, Ref, Space } from '@hcengineering/core'
|
||||
import { Blob, Class, Data, Doc, DocumentQuery, Ref, Space } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { Icon, Label, resizeObserver, Scroller, Spinner, Button, IconAdd } from '@hcengineering/ui'
|
||||
import view, { BuildModelKey } from '@hcengineering/view'
|
||||
import { Table } from '@hcengineering/view-resources'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import attachment from '../plugin'
|
||||
import { createAttachments } from '../utils'
|
||||
import { uploadFiles } from '@hcengineering/uploader'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import attachment from '../plugin'
|
||||
import { createAttachment } from '../utils'
|
||||
|
||||
import AttachmentDroppable from './AttachmentDroppable.svelte'
|
||||
import IconAttachments from './icons/Attachments.svelte'
|
||||
import UploadDuo from './icons/UploadDuo.svelte'
|
||||
@ -49,19 +52,23 @@
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function fileSelected () {
|
||||
async function fileSelected (): Promise<void> {
|
||||
const list = inputFile.files
|
||||
if (list === null || list.length === 0) return
|
||||
|
||||
loading++
|
||||
try {
|
||||
await createAttachments(
|
||||
client,
|
||||
list,
|
||||
{ objectClass: object?._class ?? _class, objectId, space },
|
||||
attachmentClass,
|
||||
attachmentClassOptions
|
||||
)
|
||||
await uploadFiles(list, { objectId, objectClass: object?._class ?? _class }, {}, async (uuid, name, file) => {
|
||||
await createAttachment(
|
||||
client,
|
||||
uuid,
|
||||
name,
|
||||
file,
|
||||
{ objectClass: object?._class ?? _class, objectId, space },
|
||||
attachmentClass,
|
||||
attachmentClassOptions
|
||||
)
|
||||
})
|
||||
} finally {
|
||||
loading--
|
||||
}
|
||||
@ -71,11 +78,11 @@
|
||||
dispatch('attached')
|
||||
}
|
||||
|
||||
function openFile () {
|
||||
function openFile (): void {
|
||||
inputFile.click()
|
||||
}
|
||||
|
||||
function updateContent (evt: CustomEvent) {
|
||||
function updateContent (evt: CustomEvent): void {
|
||||
attachments = evt.detail.length
|
||||
dispatch('attachments', evt.detail)
|
||||
}
|
||||
|
@ -15,9 +15,17 @@
|
||||
//
|
||||
|
||||
import { type Attachment } from '@hcengineering/attachment'
|
||||
import { type Class, type TxOperations as Client, type Data, type Doc, type Ref, type Space } from '@hcengineering/core'
|
||||
import {
|
||||
type Blob,
|
||||
type Class,
|
||||
type TxOperations as Client,
|
||||
type Data,
|
||||
type Doc,
|
||||
type Ref,
|
||||
type Space
|
||||
} from '@hcengineering/core'
|
||||
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { getFileMetadata, uploadFile } from '@hcengineering/presentation'
|
||||
import { type FileOrBlob, getFileMetadata, uploadFile } from '@hcengineering/presentation'
|
||||
|
||||
import attachment from './plugin'
|
||||
|
||||
@ -28,23 +36,12 @@ export async function createAttachments (
|
||||
attachmentClass: Ref<Class<Attachment>> = attachment.class.Attachment,
|
||||
extraData: Partial<Data<Attachment>> = {}
|
||||
): Promise<void> {
|
||||
const { objectClass, objectId, space } = attachTo
|
||||
try {
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const file = list.item(index)
|
||||
if (file !== null) {
|
||||
const uuid = await uploadFile(file)
|
||||
const metadata = await getFileMetadata(file, uuid)
|
||||
|
||||
await client.addCollection(attachmentClass, space, objectId, objectClass, 'attachments', {
|
||||
...extraData,
|
||||
name: file.name,
|
||||
file: uuid,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
lastModified: file.lastModified,
|
||||
metadata
|
||||
})
|
||||
await createAttachment(client, uuid, file.name, file, attachTo, attachmentClass, extraData)
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
@ -52,6 +49,33 @@ export async function createAttachments (
|
||||
}
|
||||
}
|
||||
|
||||
export async function createAttachment (
|
||||
client: Client,
|
||||
uuid: Ref<Blob>,
|
||||
name: string,
|
||||
file: FileOrBlob,
|
||||
attachTo: { objectClass: Ref<Class<Doc>>, space: Ref<Space>, objectId: Ref<Doc> },
|
||||
attachmentClass: Ref<Class<Attachment>> = attachment.class.Attachment,
|
||||
extraData: Partial<Data<Attachment>> = {}
|
||||
): Promise<void> {
|
||||
const { objectClass, objectId, space } = attachTo
|
||||
try {
|
||||
const metadata = await getFileMetadata(file, uuid)
|
||||
|
||||
await client.addCollection(attachmentClass, space, objectId, objectClass, 'attachments', {
|
||||
...extraData,
|
||||
name,
|
||||
file: uuid,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
lastModified: file instanceof File ? file.lastModified : Date.now(),
|
||||
metadata
|
||||
})
|
||||
} catch (err: any) {
|
||||
await setPlatformStatus(unknownError(err))
|
||||
}
|
||||
}
|
||||
|
||||
export function getType (type: string): 'image' | 'text' | 'json' | 'video' | 'audio' | 'pdf' | 'other' {
|
||||
if (type.startsWith('image/')) {
|
||||
return 'image'
|
||||
|
@ -13,10 +13,10 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AccountRole, Blob, Ref, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
|
||||
import { AccountRole, Ref, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
|
||||
import { createFile, type Drive } from '@hcengineering/drive'
|
||||
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { createQuery, FileOrBlob, getClient, getFileMetadata } from '@hcengineering/presentation'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Button, ButtonWithDropdown, IconAdd, IconDropdown, Loading, SelectPopupValueType } from '@hcengineering/ui'
|
||||
import { showFilesUploadPopup } from '@hcengineering/uploader'
|
||||
|
||||
@ -71,11 +71,10 @@
|
||||
parent !== drive.ids.Root
|
||||
? { objectId: parent, objectClass: drive.class.Folder }
|
||||
: { objectId: space, objectClass: drive.class.Drive }
|
||||
await showFilesUploadPopup(target, {}, async (uuid: string, name: string, file: FileOrBlob) => {
|
||||
await showFilesUploadPopup(target, {}, async (uuid, name, file, metadata) => {
|
||||
try {
|
||||
const metadata = await getFileMetadata(file, uuid as Ref<Blob>)
|
||||
const data = {
|
||||
file: uuid as Ref<Blob>,
|
||||
file: uuid,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
lastModified: file instanceof File ? file.lastModified : Date.now(),
|
||||
|
@ -13,10 +13,10 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { type Blob, type Ref } from '@hcengineering/core'
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
import drive, { createFile, type Drive, type Folder } from '@hcengineering/drive'
|
||||
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { FileOrBlob, getClient, getFileMetadata } from '@hcengineering/presentation'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { uploadFiles } from '@hcengineering/uploader'
|
||||
|
||||
export let space: Ref<Drive>
|
||||
@ -72,11 +72,10 @@
|
||||
parent !== drive.ids.Root
|
||||
? { objectId: parent, objectClass: drive.class.Folder }
|
||||
: { objectId: space, objectClass: drive.class.Drive }
|
||||
await uploadFiles(list, target, {}, async (uuid: string, name: string, file: FileOrBlob) => {
|
||||
await uploadFiles(list, target, {}, async (uuid, name, file, metadata) => {
|
||||
try {
|
||||
const metadata = await getFileMetadata(file, uuid as Ref<Blob>)
|
||||
const data = {
|
||||
file: uuid as Ref<Blob>,
|
||||
file: uuid,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
lastModified: file instanceof File ? file.lastModified : Date.now(),
|
||||
|
@ -1,46 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let multiple: boolean = false
|
||||
|
||||
export function upload (): void {
|
||||
inputFile.click()
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let inputFile: HTMLInputElement
|
||||
|
||||
async function fileSelected (): Promise<void> {
|
||||
const files = inputFile.files
|
||||
if (files === null || files.length === 0) {
|
||||
return
|
||||
}
|
||||
dispatch('selected', files)
|
||||
}
|
||||
</script>
|
||||
|
||||
<input
|
||||
bind:this={inputFile}
|
||||
{multiple}
|
||||
id="file"
|
||||
name="file"
|
||||
type="file"
|
||||
style="display: none"
|
||||
disabled={inputFile == null}
|
||||
on:change={fileSelected}
|
||||
/>
|
@ -13,11 +13,12 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { WithLookup, type Ref } from '@hcengineering/core'
|
||||
import { type File, type FileVersion } from '@hcengineering/drive'
|
||||
import { type Ref, type WithLookup } from '@hcengineering/core'
|
||||
import { createFileVersion, type File as DriveFile, type FileVersion } from '@hcengineering/drive'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { createQuery, getBlobHref } from '@hcengineering/presentation'
|
||||
import { createQuery, getBlobHref, getClient } from '@hcengineering/presentation'
|
||||
import { Button, IconMoreH } from '@hcengineering/ui'
|
||||
import { showFilesUploadPopup } from '@hcengineering/uploader'
|
||||
import view from '@hcengineering/view'
|
||||
import { showMenu } from '@hcengineering/view-resources'
|
||||
|
||||
@ -28,9 +29,8 @@
|
||||
import IconUpload from './icons/FileUpload.svelte'
|
||||
|
||||
import drive from '../plugin'
|
||||
import { replaceOneFile } from '../utils'
|
||||
|
||||
export let _id: Ref<File>
|
||||
export let _id: Ref<DriveFile>
|
||||
export let readonly: boolean = false
|
||||
export let embedded: boolean = false
|
||||
export let kind: 'default' | 'modern' = 'default'
|
||||
@ -39,12 +39,13 @@
|
||||
return false
|
||||
}
|
||||
|
||||
let object: WithLookup<File> | undefined = undefined
|
||||
let object: WithLookup<DriveFile> | undefined = undefined
|
||||
let version: FileVersion | undefined = undefined
|
||||
let download: HTMLAnchorElement
|
||||
let upload: HTMLInputElement
|
||||
|
||||
const client = getClient()
|
||||
const query = createQuery()
|
||||
|
||||
$: query.query(
|
||||
drive.class.File,
|
||||
{ _id },
|
||||
@ -66,17 +67,27 @@
|
||||
}
|
||||
|
||||
function handleUploadFile (): void {
|
||||
if (object != null && upload != null) {
|
||||
upload.click()
|
||||
}
|
||||
}
|
||||
if (object != null) {
|
||||
void showFilesUploadPopup(
|
||||
{ objectId: object._id, objectClass: object._class },
|
||||
{
|
||||
maxNumberOfFiles: 1,
|
||||
hideProgress: true
|
||||
},
|
||||
async (uuid, name, file, metadata) => {
|
||||
const data = {
|
||||
file: uuid,
|
||||
name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
lastModified: file instanceof File ? file.lastModified : Date.now(),
|
||||
metadata
|
||||
}
|
||||
|
||||
async function handleFileSelected (): Promise<void> {
|
||||
const files = upload.files
|
||||
if (object != null && files !== null && files.length > 0) {
|
||||
await replaceOneFile(object._id, files[0])
|
||||
await createFileVersion(client, _id, data)
|
||||
}
|
||||
)
|
||||
}
|
||||
upload.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -92,16 +103,6 @@
|
||||
on:close
|
||||
on:update
|
||||
>
|
||||
<input
|
||||
bind:this={upload}
|
||||
id="file"
|
||||
name="file"
|
||||
type="file"
|
||||
style="display: none"
|
||||
disabled={upload == null}
|
||||
on:change={handleFileSelected}
|
||||
/>
|
||||
|
||||
<svelte:fragment slot="title">
|
||||
<FileHeader {object} />
|
||||
</svelte:fragment>
|
||||
|
@ -14,10 +14,10 @@
|
||||
//
|
||||
|
||||
import { type Class, type Doc, type Ref, toIdMap } from '@hcengineering/core'
|
||||
import type { Drive, File as DriveFile, FileVersion, Folder, Resource } from '@hcengineering/drive'
|
||||
import drive, { createFile, createFileVersion } from '@hcengineering/drive'
|
||||
import { type Asset, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { getClient, getFileMetadata, uploadFile } from '@hcengineering/presentation'
|
||||
import type { Drive, FileVersion, Folder, Resource } from '@hcengineering/drive'
|
||||
import drive from '@hcengineering/drive'
|
||||
import { type Asset } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { type AnySvelteComponent, showPopup } from '@hcengineering/ui'
|
||||
import { openDoc } from '@hcengineering/view-resources'
|
||||
|
||||
@ -63,47 +63,6 @@ export async function editDrive (drive: Drive): Promise<void> {
|
||||
showPopup(CreateDrive, { drive })
|
||||
}
|
||||
|
||||
export async function uploadFiles (list: FileList, space: Ref<Drive>, parent: Ref<Folder>): Promise<void> {
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const file = list.item(index)
|
||||
if (file !== null) {
|
||||
await uploadOneFile(file, space, parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function uploadOneFile (file: File, space: Ref<Drive>, parent: Ref<Folder>): Promise<void> {
|
||||
const client = getClient()
|
||||
|
||||
try {
|
||||
const uuid = await uploadFile(file)
|
||||
const metadata = await getFileMetadata(file, uuid)
|
||||
|
||||
const { name, size, type, lastModified } = file
|
||||
const data = { file: uuid, name, size, type, lastModified, metadata }
|
||||
|
||||
await createFile(client, space, parent, data)
|
||||
} catch (e) {
|
||||
void setPlatformStatus(unknownError(e))
|
||||
}
|
||||
}
|
||||
|
||||
export async function replaceOneFile (existing: Ref<DriveFile>, file: File): Promise<void> {
|
||||
const client = getClient()
|
||||
|
||||
try {
|
||||
const uuid = await uploadFile(file)
|
||||
const metadata = await getFileMetadata(file, uuid)
|
||||
|
||||
const { name, size, type, lastModified } = file
|
||||
const data = { file: uuid, name, size, type, lastModified, metadata }
|
||||
|
||||
await createFileVersion(client, existing, data)
|
||||
} catch (e) {
|
||||
void setPlatformStatus(unknownError(e))
|
||||
}
|
||||
}
|
||||
|
||||
export async function renameResource (resource: Resource): Promise<void> {
|
||||
showPopup(RenamePopup, { value: resource.name, format: 'text' }, undefined, async (res) => {
|
||||
if (res != null && res !== resource.name) {
|
||||
|
@ -349,7 +349,7 @@
|
||||
return
|
||||
}
|
||||
|
||||
const size = await getImageSize(file, getFileUrl(attached.file))
|
||||
const size = await getImageSize(file)
|
||||
|
||||
editor.commands.insertContent(
|
||||
{
|
||||
|
@ -250,7 +250,7 @@
|
||||
return
|
||||
}
|
||||
|
||||
const size = await getImageSize(file, getFileUrl(attached.file))
|
||||
const size = await getImageSize(file)
|
||||
|
||||
editor.editorHandler.insertContent(
|
||||
{
|
||||
|
@ -150,7 +150,7 @@ async function handleImageUpload (
|
||||
|
||||
try {
|
||||
const url = getFileUrl(attached.file)
|
||||
const size = await getImageSize(file, url)
|
||||
const size = await getImageSize(file)
|
||||
const node = view.state.schema.nodes.image.create({
|
||||
'file-id': attached.file,
|
||||
src: url,
|
||||
|
@ -17,6 +17,9 @@
|
||||
|
||||
import { type Uppy } from '@uppy/core'
|
||||
import Dashboard from '@uppy/dashboard'
|
||||
import ScreenCapture from '@uppy/screen-capture'
|
||||
import Webcam from '@uppy/webcam'
|
||||
|
||||
import { onMount, onDestroy, createEventDispatcher } from 'svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
@ -34,6 +37,8 @@
|
||||
onMount(() => {
|
||||
uppy.on('upload', handleUpload)
|
||||
|
||||
uppy.use(ScreenCapture)
|
||||
uppy.use(Webcam)
|
||||
uppy.use(Dashboard, {
|
||||
id: 'huly:Dashboard',
|
||||
target: container,
|
||||
|
@ -17,8 +17,9 @@
|
||||
import {
|
||||
Button,
|
||||
IconClose,
|
||||
ProgressCircle,
|
||||
Label,
|
||||
ProgressCircle,
|
||||
Scroller,
|
||||
humanReadableFileSize as filesize,
|
||||
tooltip
|
||||
} from '@hcengineering/ui'
|
||||
@ -110,98 +111,110 @@
|
||||
|
||||
<div class="antiPopup upload-popup">
|
||||
<div class="upload-popup__header flex-row-center flex-gap-1">
|
||||
<Label label={uploader.string.UploadingTo} params={{ files: files.length }} />
|
||||
<ObjectPresenter
|
||||
objectId={upload.target.objectId}
|
||||
_class={upload.target.objectClass}
|
||||
shouldShowAvatar={false}
|
||||
accent
|
||||
noUnderline
|
||||
/>
|
||||
<div class="label overflow-label">
|
||||
<Label label={uploader.string.UploadingTo} params={{ files: files.length }} />
|
||||
</div>
|
||||
<div class="flex flex-grow overflow-label">
|
||||
<ObjectPresenter
|
||||
objectId={upload.target.objectId}
|
||||
_class={upload.target.objectClass}
|
||||
shouldShowAvatar={false}
|
||||
accent
|
||||
noUnderline
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-col flex-gap-4">
|
||||
{#each files as file}
|
||||
{#if file.progress}
|
||||
{@const error = getFileError(file)}
|
||||
{@const percentage = getFilePercentage(file)}
|
||||
<Scroller>
|
||||
<div class="upload-popup__content flex-col flex-no-shrink flex-gap-4">
|
||||
{#each files as file}
|
||||
{#if file.progress}
|
||||
{@const error = getFileError(file)}
|
||||
{@const percentage = getFilePercentage(file)}
|
||||
|
||||
<div class="upload-file-row flex-row-center justify-start flex-gap-4">
|
||||
<div class="upload-file-row__status w-4">
|
||||
{#if error}
|
||||
<IconError size={'small'} fill={'var(--negative-button-default)'} />
|
||||
{:else if file.progress.uploadComplete}
|
||||
<IconCompleted size={'small'} fill={'var(--positive-button-default)'} />
|
||||
{:else}
|
||||
<ProgressCircle value={percentage} size={'small'} primary />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="upload-file-row__content flex-col flex-gap-1">
|
||||
<div class="label overflow-label" use:tooltip={{ label: getEmbeddedLabel(file.name) }}>{file.name}</div>
|
||||
<div class="flex-row-center flex-gap-2 text-sm">
|
||||
<div class="upload-file-row flex-row-center justify-start flex-gap-4">
|
||||
<div class="upload-file-row__status w-4">
|
||||
{#if error}
|
||||
<Label label={uploader.status.Error} />
|
||||
<span class="label overflow-label" use:tooltip={{ label: getEmbeddedLabel(error) }}>{error}</span>
|
||||
{:else if file.progress.uploadStarted != null}
|
||||
{#if file.progress.uploadComplete}
|
||||
<Label label={uploader.status.Completed} />
|
||||
<IconError size={'small'} fill={'var(--negative-button-default)'} />
|
||||
{:else if file.progress.uploadComplete}
|
||||
<IconCompleted size={'small'} fill={'var(--positive-button-default)'} />
|
||||
{:else}
|
||||
<ProgressCircle value={percentage} size={'small'} primary />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="upload-file-row__content flex-col flex-gap-1">
|
||||
<div class="label overflow-label" use:tooltip={{ label: getEmbeddedLabel(file.name) }}>{file.name}</div>
|
||||
<div class="flex-row-center flex-gap-2 text-sm">
|
||||
{#if error}
|
||||
<Label label={uploader.status.Error} />
|
||||
<span class="label overflow-label" use:tooltip={{ label: getEmbeddedLabel(error) }}>{error}</span>
|
||||
{:else if file.progress.uploadStarted != null}
|
||||
{#if file.progress.uploadComplete}
|
||||
<Label label={uploader.status.Completed} />
|
||||
{#if file.progress.bytesTotal}
|
||||
<span>{filesize(file.progress.bytesTotal)}</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<Label label={uploader.status.Uploading} />
|
||||
{#if file.progress.bytesTotal}
|
||||
<span>{filesize(file.progress.bytesUploaded)} / {filesize(file.progress.bytesTotal)}</span>
|
||||
{:else}
|
||||
<span>{filesize(file.progress.bytesUploaded)}}</span>
|
||||
{/if}
|
||||
{/if}
|
||||
{:else}
|
||||
<Label label={uploader.status.Waiting} />
|
||||
{#if file.progress.bytesTotal}
|
||||
<span>{filesize(file.progress.bytesTotal)}</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<Label label={uploader.status.Uploading} />
|
||||
{#if file.progress.bytesTotal}
|
||||
<span>{filesize(file.progress.bytesUploaded)} / {filesize(file.progress.bytesTotal)}</span>
|
||||
{:else}
|
||||
<span>{filesize(file.progress.bytesUploaded)}}</span>
|
||||
{/if}
|
||||
{/if}
|
||||
{:else}
|
||||
<Label label={uploader.status.Waiting} />
|
||||
{#if file.progress.bytesTotal}
|
||||
<span>{filesize(file.progress.bytesTotal)}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="upload-file-row__tools flex-row-center">
|
||||
{#if error}
|
||||
<Button
|
||||
kind={'icon'}
|
||||
icon={IconRetry}
|
||||
iconProps={{ size: 'small' }}
|
||||
showTooltip={{ label: uploader.string.Retry }}
|
||||
on:click={() => {
|
||||
handleRetryFile(file)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{#if !file.progress.uploadComplete && individualCancellation}
|
||||
<Button
|
||||
kind={'icon'}
|
||||
icon={IconClose}
|
||||
iconProps={{ size: 'small' }}
|
||||
showTooltip={{ label: uploader.string.Cancel }}
|
||||
on:click={() => {
|
||||
handleCancelFile(file)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="upload-file-row__tools flex-row-center">
|
||||
{#if error}
|
||||
<Button
|
||||
kind={'icon'}
|
||||
icon={IconRetry}
|
||||
iconProps={{ size: 'small' }}
|
||||
showTooltip={{ label: uploader.string.Retry }}
|
||||
on:click={() => {
|
||||
handleRetryFile(file)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{#if !file.progress.uploadComplete && individualCancellation}
|
||||
<Button
|
||||
kind={'icon'}
|
||||
icon={IconClose}
|
||||
iconProps={{ size: 'small' }}
|
||||
showTooltip={{ label: uploader.string.Cancel }}
|
||||
on:click={() => {
|
||||
handleCancelFile(file)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</Scroller>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.upload-popup {
|
||||
padding: var(--spacing-2);
|
||||
max-height: 30rem;
|
||||
|
||||
.upload-popup__header {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.upload-popup__content {
|
||||
margin: 0.5rem;
|
||||
margin-right: 0.625rem;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-file-row {
|
||||
|
@ -13,15 +13,13 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { generateId } from '@hcengineering/core'
|
||||
import { type Blob, type Ref, generateId } from '@hcengineering/core'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import presentation, { getFileMetadata } from '@hcengineering/presentation'
|
||||
import { getCurrentLanguage } from '@hcengineering/theme'
|
||||
import type { FileUploadCallback, FileUploadOptions } from '@hcengineering/uploader'
|
||||
|
||||
import Uppy, { type IndexedObject, type UppyOptions } from '@uppy/core'
|
||||
import ScreenCapture from '@uppy/screen-capture'
|
||||
import Webcam from '@uppy/webcam'
|
||||
import XHR from '@uppy/xhr-upload'
|
||||
|
||||
import En from '@uppy/locales/lib/en_US'
|
||||
@ -60,31 +58,38 @@ export type UppyBody = Body & {
|
||||
|
||||
/** @public */
|
||||
export function getUppy (options: FileUploadOptions, onFileUploaded?: FileUploadCallback): Uppy<UppyMeta, UppyBody> {
|
||||
const id = generateId()
|
||||
const locale = getUppyLocale(getCurrentLanguage())
|
||||
const uppy = new Uppy<UppyMeta, UppyBody>({ id, locale, ...options })
|
||||
.use(ScreenCapture)
|
||||
.use(Webcam)
|
||||
.use(XHR, {
|
||||
endpoint: getMetadata(presentation.metadata.UploadURL) ?? '',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + (getMetadata(presentation.metadata.Token) as string)
|
||||
},
|
||||
getResponseData: (body: string): UppyBody => {
|
||||
return {
|
||||
uuid: body
|
||||
}
|
||||
const uppyOptions: Partial<UppyOptions> = {
|
||||
id: generateId(),
|
||||
locale: getUppyLocale(getCurrentLanguage()),
|
||||
allowMultipleUploadBatches: false,
|
||||
restrictions: {
|
||||
maxFileSize: options.maxFileSize,
|
||||
maxNumberOfFiles: options.maxNumberOfFiles,
|
||||
allowedFileTypes: options.allowedFileTypes
|
||||
}
|
||||
}
|
||||
|
||||
const uppy = new Uppy<UppyMeta, UppyBody>(uppyOptions).use(XHR, {
|
||||
endpoint: getMetadata(presentation.metadata.UploadURL) ?? '',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + (getMetadata(presentation.metadata.Token) as string)
|
||||
},
|
||||
getResponseData: (body: string): UppyBody => {
|
||||
return {
|
||||
uuid: body
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (onFileUploaded != null) {
|
||||
uppy.addPostProcessor(async (fileIds: string[]) => {
|
||||
for (const fileId of fileIds) {
|
||||
const file = uppy.getFile(fileId)
|
||||
const uuid = file?.response?.body?.uuid
|
||||
const uuid = file?.response?.body?.uuid as Ref<Blob>
|
||||
if (uuid !== undefined) {
|
||||
await onFileUploaded(uuid, file.name, file.data)
|
||||
const metadata = await getFileMetadata(file.data, uuid)
|
||||
await onFileUploaded(uuid, file.name, file.data, metadata)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import type { Blob as PlatformBlob, Class, Doc, Ref } from '@hcengineering/core'
|
||||
|
||||
/** @public */
|
||||
export type UploadFilesPopupFn = (
|
||||
@ -40,9 +40,14 @@ export interface FileUploadTarget {
|
||||
export interface FileUploadOptions {
|
||||
maxFileSize?: number
|
||||
maxNumberOfFiles?: number
|
||||
allowedFileTypes?: string
|
||||
allowedFileTypes?: string[] | null
|
||||
hideProgress?: boolean
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type FileUploadCallback = (uuid: string, name: string, file: File | Blob) => Promise<void>
|
||||
export type FileUploadCallback = (
|
||||
uuid: Ref<PlatformBlob>,
|
||||
name: string,
|
||||
file: File | Blob,
|
||||
metadata: Record<string, any> | undefined
|
||||
) => Promise<void>
|
||||
|
@ -14,10 +14,10 @@
|
||||
//
|
||||
|
||||
import { type Blob, type Ref } from '@hcengineering/core'
|
||||
import { type BlobMetadata, getFileUrl, getImageSize } from '@hcengineering/presentation'
|
||||
import { type BlobMetadata, getImageSize } from '@hcengineering/presentation'
|
||||
|
||||
export async function blobImageMetadata (file: File, blob: Ref<Blob>): Promise<BlobMetadata | undefined> {
|
||||
const size = await getImageSize(file, getFileUrl(blob, 'full'))
|
||||
const size = await getImageSize(file)
|
||||
|
||||
return {
|
||||
originalHeight: size.height,
|
||||
@ -27,7 +27,7 @@ export async function blobImageMetadata (file: File, blob: Ref<Blob>): Promise<B
|
||||
}
|
||||
|
||||
export async function blobVideoMetadata (file: File, blob: Ref<Blob>): Promise<BlobMetadata | undefined> {
|
||||
const size = await getVideoSize(blob)
|
||||
const size = await getVideoSize(file)
|
||||
|
||||
if (size === undefined) {
|
||||
return undefined
|
||||
@ -39,19 +39,22 @@ export async function blobVideoMetadata (file: File, blob: Ref<Blob>): Promise<B
|
||||
}
|
||||
}
|
||||
|
||||
async function getVideoSize (uuid: Ref<Blob>): Promise<{ width: number, height: number } | undefined> {
|
||||
async function getVideoSize (file: File): Promise<{ width: number, height: number } | undefined> {
|
||||
const promise = new Promise<{ width: number, height: number }>((resolve, reject) => {
|
||||
const element = document.createElement('video')
|
||||
|
||||
const src = URL.createObjectURL(file)
|
||||
|
||||
element.onloadedmetadata = () => {
|
||||
const height = element.videoHeight
|
||||
const width = element.videoWidth
|
||||
|
||||
URL.revokeObjectURL(src)
|
||||
resolve({ height, width })
|
||||
}
|
||||
|
||||
element.onerror = reject
|
||||
element.src = getFileUrl(uuid)
|
||||
element.src = src
|
||||
})
|
||||
|
||||
return await promise
|
||||
|
Loading…
Reference in New Issue
Block a user