UBERF-9759 Scroll attachments in chat (#8523)

* UBERF-9759 Scroll attachments in chat

Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>

* fix: use Array.from(...)

Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>

---------

Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
Alexander Onnikov 2025-04-11 00:27:43 +07:00 committed by GitHub
parent afbaf26078
commit 11232c503c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 126 additions and 68 deletions

View File

@ -166,6 +166,7 @@
updateToolbarPosition(readonly, board, toolbar)
}}
use:drawing={{
autoSize: imageWidth === undefined || imageHeight === undefined,
readonly,
imageWidth,
imageHeight,

View File

@ -47,10 +47,9 @@
const dispatch = createEventDispatcher()
onMount(() => {
if (fullSize) {
dispatch('fullsize')
}
$: void loadDrawings(file)
async function loadDrawings (file: Ref<Blob> | undefined): Promise<void> {
if (props.drawingAvailable === true) {
if (props.loadDrawings !== undefined) {
drawingLoading = true
@ -66,6 +65,14 @@
console.error('Failed to load drawings for file', file, error)
})
}
}
}
onMount(() => {
if (fullSize) {
dispatch('fullsize')
}
if (props.drawingAvailable === true) {
if (props.createDrawing !== undefined) {
createDrawing = props.createDrawing
props.createDrawing = async (data: any): Promise<any> => {

View File

@ -57,7 +57,7 @@ export function updatePopup (id: string, props: Partial<CompAndProps>): void {
(p: CompAndProps) => p.id === id
)
if (popupIndex !== -1) {
;(modals[popupIndex] as CompAndProps).update?.(props)
;(modals[popupIndex] as CompAndProps).update?.(props.props)
}
return modals
})

View File

@ -15,7 +15,8 @@
<script lang="ts">
import { Attachment } from '@hcengineering/attachment'
import { Ref, type WithLookup } from '@hcengineering/core'
import { Scroller } from '@hcengineering/ui'
import { ListSelectionProvider } from '@hcengineering/view-resources'
import { Scroller, updatePopup } from '@hcengineering/ui'
import { AttachmentImageSize } from '../types'
import AttachmentPreview from './AttachmentPreview.svelte'
@ -23,6 +24,22 @@
export let savedAttachmentsIds: Ref<Attachment>[] = []
export let imageSize: AttachmentImageSize | undefined = undefined
export let videoPreload = false
let attachmentPopupId = ''
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0) => {
const current = listProvider.current()
if (current === undefined) return
let pos = current + offset
if (pos < 0) pos = 0
if (pos >= attachments.length) pos = attachments.length - 1
const doc = listProvider.docs()[pos] as Attachment
if (doc !== undefined && attachmentPopupId !== '') {
listProvider.updateFocus(doc)
updatePopup(attachmentPopupId, { props: { value: doc } })
}
})
$: listProvider.update(attachments.filter((p) => p.type.startsWith('image/')))
</script>
{#if attachments.length}
@ -33,6 +50,8 @@
isSaved={savedAttachmentsIds?.includes(attachment._id) ?? false}
{imageSize}
{videoPreload}
{listProvider}
on:open={(res) => (attachmentPopupId = res.detail)}
/>
{/each}
</Scroller>

View File

@ -99,7 +99,8 @@
return
}
if (value.type.startsWith('image/') || value.type.startsWith('video/') || value.type.startsWith('audio/')) {
showAttachmentPreviewPopup(value)
const popup = showAttachmentPreviewPopup(value)
dispatch('open', popup.id)
} else {
await openAttachmentInSidebar(value)
}

View File

@ -0,0 +1,78 @@
<!--
// 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 attachment, { type Attachment, type Drawing } from '@hcengineering/attachment'
import core, { SortingOrder } from '@hcengineering/core'
import { DrawingData, FilePreviewPopup, getClient } from '@hcengineering/presentation'
import { isAttachment } from '../utils'
export let value: Attachment
export let fullSize = false
export let showIcon = true
$: drawingAvailable = value?.type?.startsWith('image/') && isAttachment(value)
async function loadDrawings (): Promise<Drawing[]> {
const client = getClient()
const drawings = await client.findAll(
attachment.class.Drawing,
{
parent: value.file,
space: value.space
},
{
sort: {
createdOn: SortingOrder.Descending
},
limit: 1
}
)
return Array.from(drawings ?? [])
}
async function createDrawing (data: DrawingData): Promise<DrawingData> {
const client = getClient()
const newId = await client.createDoc(attachment.class.Drawing, value.space, {
parent: value.file,
parentClass: core.class.Blob,
content: data.content
})
const newDrawing = await client.findOne(attachment.class.Drawing, { _id: newId })
if (newDrawing === undefined) {
throw new Error('Unable to find just created drawing')
}
return newDrawing
}
</script>
<FilePreviewPopup
file={value.file}
name={value.name}
metadata={value.metadata}
contentType={value.type}
props={{
drawingAvailable,
loadDrawings,
createDrawing
}}
{fullSize}
{showIcon}
on:open
on:close
on:update
on:fullsize
/>

View File

@ -34,7 +34,7 @@
const sel = listProvider.docs()[selected] as Attachment
if (sel !== undefined && attachmentPopupId !== '') {
listProvider.updateFocus(sel)
updatePopup(attachmentPopupId, { props: { file: sel.file, name: sel.name, contentType: sel.type } })
updatePopup(attachmentPopupId, { props: { value: sel } })
}
})
$: listProvider.update(attachments.filter((p) => p.type.startsWith('image/')))
@ -52,7 +52,13 @@
on:open={(res) => (attachmentPopupId = res.detail)}
/>
{:else}
<AttachmentPresenter value={attachment} removable={!readonly} showPreview on:remove />
<AttachmentPresenter
value={attachment}
removable={!readonly}
showPreview
on:remove
on:open={(res) => (attachmentPopupId = res.detail)}
/>
{/if}
{/each}
{#if progress}

View File

@ -14,10 +14,9 @@
// limitations under the License.
//
import { type Attachment, type Drawing } from '@hcengineering/attachment'
import core, {
import { type Attachment } from '@hcengineering/attachment'
import {
type BlobMetadata,
SortingOrder,
type Blob,
type Class,
type TxOperations as Client,
@ -30,19 +29,18 @@ import core, {
} from '@hcengineering/core'
import { getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
import {
type DrawingData,
type FileOrBlob,
getClient,
getFileMetadata,
getPreviewAlignment,
uploadFile,
FilePreviewPopup
uploadFile
} from '@hcengineering/presentation'
import { closeTooltip, showPopup, type PopupResult } from '@hcengineering/ui'
import workbench, { type WidgetTab } from '@hcengineering/workbench'
import view from '@hcengineering/view'
import attachment from './plugin'
import AttachmentPreviewPopup from './components/AttachmentPreviewPopup.svelte'
export async function createAttachments (
client: Client,
@ -158,60 +156,8 @@ export function isAttachment (value: Attachment | BlobType): value is WithLookup
}
export function showAttachmentPreviewPopup (value: WithLookup<Attachment> | BlobType): PopupResult {
const props: Record<string, any> = {}
if (value?.type?.startsWith('image/') && isAttachment(value)) {
props.drawingAvailable = true
props.loadDrawings = async (): Promise<Drawing[] | undefined> => {
const client = getClient()
const drawings = await client.findAll(
attachment.class.Drawing,
{
parent: value.file,
space: value.space
},
{
sort: {
createdOn: SortingOrder.Descending
},
limit: 1
}
)
const result = []
if (drawings !== undefined) {
for (const drawing of drawings) {
result.push(drawing)
}
}
return result
}
props.createDrawing = async (data: DrawingData): Promise<DrawingData> => {
const client = getClient()
const newId = await client.createDoc(attachment.class.Drawing, value.space, {
parent: value.file,
parentClass: core.class.Blob,
content: data.content
})
const newDrawing = await client.findOne(attachment.class.Drawing, { _id: newId })
if (newDrawing === undefined) {
throw new Error('Unable to find just created drawing')
}
return newDrawing
}
}
closeTooltip()
return showPopup(
FilePreviewPopup,
{
file: value.file,
contentType: value.type,
name: value.name,
metadata: value.metadata,
props
},
getPreviewAlignment(value.type ?? '')
)
return showPopup(AttachmentPreviewPopup, { value }, getPreviewAlignment(value.type ?? ''))
}
interface ImageDimensions {