Improved file dropzones

This commit is contained in:
andrewwallacespeckle
2025-08-06 16:02:09 +01:00
parent 5729cfe12b
commit 09fe893425
3 changed files with 106 additions and 36 deletions
@@ -30,18 +30,30 @@
<PlusIcon class="w-5 h-5 text-foreground-2 rotate-45" />
</button>
</div>
<div
<FormFileUploadZone
v-if="modelValue.isExpanded && canPostComment"
ref="threadContainer"
class="sm:absolute w-full sm:w-[260px] bg-foundation dark:bg-foundation-2 border border-outline-2 sm:rounded-xl shadow-md"
ref="uploadZone"
v-slot="{ isDraggingFiles }"
:size-limit="maxSizeInBytes"
:accept="acceptValue"
:disabled="isPostingNewThread"
multiple
@files-selected="onFilesSelected"
>
<div class="relative">
<div
ref="threadContainer"
class="sm:absolute w-full sm:w-[260px] bg-foundation dark:bg-foundation-2 border sm:rounded-xl shadow-md"
:class="
isDraggingFiles ? 'border-dashed border-primary' : 'border-outline-2'
"
>
<ViewerCommentsEditor
ref="editor"
v-model="commentValue"
prompt="Add comment"
max-height="300px"
autofocus
disable-drop-zone
:disabled="isPostingNewThread"
@submit="() => onSubmit()"
@keydown="onKeyDownHandler"
@@ -63,7 +75,7 @@
/>
</div>
</div>
</div>
</FormFileUploadZone>
</ViewerCommentsPortalOrDiv>
</div>
</div>
@@ -84,6 +96,10 @@ import {
import { useMixpanel } from '~~/lib/core/composables/mp'
import { useThreadUtilities, useSelectionUtilities } from '~~/lib/viewer/composables/ui'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
import { useServerFileUploadLimit } from '~~/lib/common/composables/serverInfo'
import { UniqueFileTypeSpecifier } from '~~/lib/core/helpers/file'
import { acceptedFileExtensions } from '@speckle/shared/blobs'
import type { UploadableFileItem } from '@speckle/ui-components'
const { isEnabled: isEmbedEnabled } = useEmbed()
@@ -101,12 +117,29 @@ const props = defineProps<{
const { onKeyDownHandler, updateIsTyping, pauseAutomaticUpdates } =
useIsTypingUpdateEmitter()
const { closeAllThreads, open } = useThreadUtilities()
const { maxSizeInBytes } = useServerFileUploadLimit()
const editor = ref(null as Nullable<{ openFilePicker: () => void }>)
const editor = ref(
null as Nullable<{
openFilePicker: () => void
onFilesSelected: (payload: { files: UploadableFileItem[] }) => void
}>
)
const uploadZone = ref(null as Nullable<{ triggerPicker: () => void }>)
const commentValue = ref<CommentEditorValue>({ doc: undefined, attachments: undefined })
const threadContainer = ref(null as Nullable<HTMLElement>)
const isPostingNewThread = ref(false)
const acceptValue = [
UniqueFileTypeSpecifier.AnyImage,
UniqueFileTypeSpecifier.AnyVideo,
...acceptedFileExtensions.map((fileExtension) => `.${fileExtension}`)
].join(',')
const onFilesSelected = (payload: { files: UploadableFileItem[] }) => {
editor.value?.onFilesSelected(payload)
}
// const { style } = useExpandedThreadResponsiveLocation({
// threadContainer,
// width: 320
@@ -174,7 +207,7 @@ const onSubmit = (comment?: CommentEditorValue) => {
}
const trackAttachAndOpenFilePicker = () => {
editor.value?.openFilePicker()
uploadZone.value?.triggerPicker()
mp.track('Comment Action', { type: 'action', name: 'attach' })
}
@@ -1,32 +1,46 @@
<!-- eslint-disable vuejs-accessibility/no-autofocus -->
<template>
<div class="w-full relative flex flex-col p-2 pt-1">
<div class="border border-outline-2 rounded-lg dark:bg-foundation-2">
<ViewerCommentsEditor
ref="editor"
v-model="commentValue"
prompt="Add reply"
autofocus
@keydown="onKeyDownHandler"
@submit="onSubmit"
/>
<div class="flex justify-between items-center p-1">
<FormButton
:icon-left="PaperClipIcon"
:disabled="loading"
color="subtle"
hide-text
class="!bg-foundation dark:!bg-foundation-2"
@click="trackAttachAndOpenFilePicker()"
/>
<FormButton
:icon-left="PaperAirplaneIcon"
hide-text
:disabled="loading"
@click="onSubmit"
<FormFileUploadZone
ref="uploadZone"
v-slot="{ isDraggingFiles }"
:size-limit="maxSizeInBytes"
:accept="acceptValue"
:disabled="loading"
multiple
@files-selected="onFilesSelected"
>
<div
class="border border-outline-2 rounded-lg dark:bg-foundation-2"
:class="[isDraggingFiles && 'border-dashed border-primary']"
>
<ViewerCommentsEditor
ref="editor"
v-model="commentValue"
prompt="Add reply"
autofocus
disable-drop-zone
@keydown="onKeyDownHandler"
@submit="onSubmit"
/>
<div class="flex justify-between items-center p-1">
<FormButton
:icon-left="PaperClipIcon"
:disabled="loading"
color="subtle"
hide-text
class="!bg-foundation dark:!bg-foundation-2"
@click="trackAttachAndOpenFilePicker()"
/>
<FormButton
:icon-left="PaperAirplaneIcon"
hide-text
:disabled="loading"
@click="onSubmit"
/>
</div>
</div>
</div>
</FormFileUploadZone>
</div>
</template>
<script setup lang="ts">
@@ -42,6 +56,10 @@ import {
convertCommentEditorValueToInput,
isValidCommentContentInput
} from '~~/lib/viewer/helpers/comments'
import { useServerFileUploadLimit } from '~~/lib/common/composables/serverInfo'
import { UniqueFileTypeSpecifier } from '~~/lib/core/helpers/file'
import { acceptedFileExtensions } from '@speckle/shared/blobs'
import type { UploadableFileItem } from '@speckle/ui-components'
const props = defineProps<{
modelValue: CommentBubbleModel
@@ -54,15 +72,32 @@ const emit = defineEmits<{
const createReply = useSubmitReply()
const { onKeyDownHandler, updateIsTyping } = useIsTypingUpdateEmitter()
const { projectId } = useInjectedViewerState()
const { maxSizeInBytes } = useServerFileUploadLimit()
const loading = ref(false)
const editor = ref(null as Nullable<{ openFilePicker: () => void }>)
const editor = ref(
null as Nullable<{
openFilePicker: () => void
onFilesSelected: (payload: { files: UploadableFileItem[] }) => void
}>
)
const uploadZone = ref(null as Nullable<{ triggerPicker: () => void }>)
const commentValue = ref<CommentEditorValue>({ doc: undefined, attachments: undefined })
const threadId = computed(() => props.modelValue.id)
const acceptValue = [
UniqueFileTypeSpecifier.AnyImage,
UniqueFileTypeSpecifier.AnyVideo,
...acceptedFileExtensions.map((fileExtension) => `.${fileExtension}`)
].join(',')
const onFilesSelected = (payload: { files: UploadableFileItem[] }) => {
editor.value?.onFilesSelected(payload)
}
const mp = useMixpanel()
const trackAttachAndOpenFilePicker = () => {
editor.value?.openFilePicker()
uploadZone.value?.triggerPicker()
mp.track('Comment Action', { type: 'action', name: 'attach' })
}
@@ -6,7 +6,7 @@
v-slot="{ isDraggingFiles }"
:size-limit="maxSizeInBytes"
:accept="acceptValue"
:disabled="disabled"
:disabled="disabled || disableDropZone"
multiple
@files-selected="onFilesSelected"
>
@@ -14,7 +14,7 @@
v-model="doc"
:class="[
'rounded-t-lg py-2.5 px-3 border-b border-outline-2 text-body-2xs min-h-[40px] flex',
isDraggingFiles && 'border-dashed'
isDraggingFiles && !disableDropZone && 'border-dashed'
]"
:autofocus="autofocus"
:placeholder="prompt || 'Add comment'"
@@ -58,6 +58,7 @@ const props = defineProps<{
disabled?: boolean
autofocus?: boolean
prompt?: string
disableDropZone?: boolean
}>()
const {
@@ -124,6 +125,7 @@ watch(
)
defineExpose({
openFilePicker
openFilePicker,
onFilesSelected
})
</script>