mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-05 15:02:37 +00:00
UBERF-5595: set up attachments sizes (#4746)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
parent
0349cec3a2
commit
3e782658c6
@ -20609,12 +20609,13 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/presentation.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
|
file:projects/presentation.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
|
||||||
resolution: {integrity: sha512-aIs1NWqMRjF8+bpEPUJW3nkQFkxnv0GvllOQk4DWbDdfxuAlGkvBKiKaDwlx3cku9hulZqpwr6EyIbkotn1Rsw==, tarball: file:projects/presentation.tgz}
|
resolution: {integrity: sha512-afwb+Kuc6Gu/8xgzxYSMNmMewCvsAX46bAkIMVDBO71momZ1zBKAhLM1kFXIp1UnaOKH2vnq3myx4ZpOrjBAZw==, tarball: file:projects/presentation.tgz}
|
||||||
id: file:projects/presentation.tgz
|
id: file:projects/presentation.tgz
|
||||||
name: '@rush-temp/presentation'
|
name: '@rush-temp/presentation'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/jest': 29.5.12
|
'@types/jest': 29.5.12
|
||||||
|
'@types/png-chunks-extract': 1.0.2
|
||||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
|
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
|
||||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
|
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
|
||||||
eslint: 8.56.0
|
eslint: 8.56.0
|
||||||
@ -20625,6 +20626,7 @@ packages:
|
|||||||
eslint-plugin-svelte: 2.35.1(eslint@8.56.0)(svelte@4.2.11)(ts-node@10.9.2)
|
eslint-plugin-svelte: 2.35.1(eslint@8.56.0)(svelte@4.2.11)(ts-node@10.9.2)
|
||||||
fast-equals: 2.0.4
|
fast-equals: 2.0.4
|
||||||
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
|
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
|
||||||
|
png-chunks-extract: 1.0.0
|
||||||
prettier: 3.2.5
|
prettier: 3.2.5
|
||||||
prettier-plugin-svelte: 3.2.1(prettier@3.2.5)(svelte@4.2.11)
|
prettier-plugin-svelte: 3.2.1(prettier@3.2.5)(svelte@4.2.11)
|
||||||
sass: 1.71.1
|
sass: 1.71.1
|
||||||
@ -23328,7 +23330,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/text-editor.tgz(@types/node@20.11.19)(bufferutil@4.0.8)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(prosemirror-model@1.19.4)(ts-node@10.9.2):
|
file:projects/text-editor.tgz(@types/node@20.11.19)(bufferutil@4.0.8)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(prosemirror-model@1.19.4)(ts-node@10.9.2):
|
||||||
resolution: {integrity: sha512-/TK8U02uYlCsPKiYVNS7t+UMmDxEPWLwY05soaV+Yuu3nXRGt2m2CkEFnv8I5IAqsEzgogKhqFXAgcQHcjfG+A==, tarball: file:projects/text-editor.tgz}
|
resolution: {integrity: sha512-obPE9MHV6S63InShtdrvDbNa5/A9BgF49k6zgO3T4C+niVTKsmsKz4/Pt+aew2X5eAAxehSq69bWIjBZidydNA==, tarball: file:projects/text-editor.tgz}
|
||||||
id: file:projects/text-editor.tgz
|
id: file:projects/text-editor.tgz
|
||||||
name: '@rush-temp/text-editor'
|
name: '@rush-temp/text-editor'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import activity from '@hcengineering/activity'
|
import activity from '@hcengineering/activity'
|
||||||
import type { Attachment, Photo, SavedAttachments } from '@hcengineering/attachment'
|
import type { Attachment, AttachmentMetadata, Photo, SavedAttachments } from '@hcengineering/attachment'
|
||||||
import { type Domain, IndexKind, type Ref } from '@hcengineering/core'
|
import { type Domain, IndexKind, type Ref } from '@hcengineering/core'
|
||||||
import {
|
import {
|
||||||
type Builder,
|
type Builder,
|
||||||
@ -64,6 +64,8 @@ export class TAttachment extends TAttachedDoc implements Attachment {
|
|||||||
|
|
||||||
@Prop(TypeBoolean(), attachment.string.Pinned)
|
@Prop(TypeBoolean(), attachment.string.Pinned)
|
||||||
pinned!: boolean
|
pinned!: boolean
|
||||||
|
|
||||||
|
metadata?: AttachmentMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model(attachment.class.Photo, attachment.class.Attachment)
|
@Model(attachment.class.Photo, attachment.class.Attachment)
|
||||||
|
@ -35,7 +35,8 @@
|
|||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"ts-jest": "^29.1.1",
|
"ts-jest": "^29.1.1",
|
||||||
"@types/jest": "^29.5.5",
|
"@types/jest": "^29.5.5",
|
||||||
"svelte-eslint-parser": "^0.33.1"
|
"svelte-eslint-parser": "^0.33.1",
|
||||||
|
"@types/png-chunks-extract": "^1.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hcengineering/platform": "^0.6.9",
|
"@hcengineering/platform": "^0.6.9",
|
||||||
@ -46,6 +47,7 @@
|
|||||||
"svelte": "^4.2.5",
|
"svelte": "^4.2.5",
|
||||||
"@hcengineering/client": "^0.6.14",
|
"@hcengineering/client": "^0.6.14",
|
||||||
"@hcengineering/collaborator-client": "^0.6.0",
|
"@hcengineering/collaborator-client": "^0.6.0",
|
||||||
"fast-equals": "^2.0.3"
|
"fast-equals": "^2.0.3",
|
||||||
|
"png-chunks-extract": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
101
packages/presentation/src/image.ts
Normal file
101
packages/presentation/src/image.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
|
||||||
|
import extract from 'png-chunks-extract'
|
||||||
|
|
||||||
|
export async function getImageSize (
|
||||||
|
file: File,
|
||||||
|
src: string
|
||||||
|
): 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()
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
resolve({
|
||||||
|
width: size?.width ?? img.naturalWidth,
|
||||||
|
height: size?.height ?? img.naturalHeight,
|
||||||
|
pixelRatio: size?.pixelRatio ?? 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
img.onerror = reject
|
||||||
|
img.src = src
|
||||||
|
})
|
||||||
|
|
||||||
|
return await promise
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPng (file: File): boolean {
|
||||||
|
return file.type === 'image/png'
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPngImageSize (file: File): Promise<{ width: number, height: number, pixelRatio: number } | undefined> {
|
||||||
|
if (!isPng(file)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const buffer = await file.arrayBuffer()
|
||||||
|
const chunks = extract(new Uint8Array(buffer))
|
||||||
|
|
||||||
|
const pHYsChunk = chunks.find((chunk) => chunk.name === 'pHYs')
|
||||||
|
const iHDRChunk = chunks.find((chunk) => chunk.name === 'IHDR')
|
||||||
|
|
||||||
|
if (pHYsChunk === undefined || iHDRChunk === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// See http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
|
||||||
|
// Section 4.1.1. IHDR Image header
|
||||||
|
// Section 4.2.4.2. pHYs Physical pixel dimensions
|
||||||
|
const idhrData = parseIHDR(new DataView(iHDRChunk.data.buffer))
|
||||||
|
const physData = parsePhys(new DataView(pHYsChunk.data.buffer))
|
||||||
|
|
||||||
|
if (physData.unit === 0 && physData.ppux === physData.ppuy) {
|
||||||
|
const pixelRatio = Math.round(physData.ppux / 2834.5)
|
||||||
|
return {
|
||||||
|
width: idhrData.width,
|
||||||
|
height: idhrData.height,
|
||||||
|
pixelRatio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// See http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
|
||||||
|
// Section 4.1.1. IHDR Image header
|
||||||
|
function parseIHDR (view: DataView): { width: number, height: number } {
|
||||||
|
return {
|
||||||
|
width: view.getUint32(0),
|
||||||
|
height: view.getUint32(4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// See http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
|
||||||
|
// Section 4.2.4.2. pHYs Physical pixel dimensions
|
||||||
|
function parsePhys (view: DataView): { ppux: number, ppuy: number, unit: number } {
|
||||||
|
return {
|
||||||
|
ppux: view.getUint32(0),
|
||||||
|
ppuy: view.getUint32(4),
|
||||||
|
unit: view.getUint8(4)
|
||||||
|
}
|
||||||
|
}
|
@ -56,3 +56,4 @@ export * from './pipeline'
|
|||||||
export * from './components/extensions/manager'
|
export * from './components/extensions/manager'
|
||||||
export * from './rules'
|
export * from './rules'
|
||||||
export * from './search'
|
export * from './search'
|
||||||
|
export * from './image'
|
||||||
|
@ -34,8 +34,7 @@
|
|||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"ts-jest": "^29.1.1",
|
"ts-jest": "^29.1.1",
|
||||||
"@types/jest": "^29.5.5",
|
"@types/jest": "^29.5.5",
|
||||||
"svelte-eslint-parser": "^0.33.1",
|
"svelte-eslint-parser": "^0.33.1"
|
||||||
"@types/png-chunks-extract": "^1.0.2"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hcengineering/presentation": "^0.6.2",
|
"@hcengineering/presentation": "^0.6.2",
|
||||||
@ -80,7 +79,6 @@
|
|||||||
"rfc6902": "^5.0.1",
|
"rfc6902": "^5.0.1",
|
||||||
"diff": "^5.1.0",
|
"diff": "^5.1.0",
|
||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.6",
|
||||||
"lib0": "^0.2.88",
|
"lib0": "^0.2.88"
|
||||||
"png-chunks-extract": "^1.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,14 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
import { PDFViewer } from '@hcengineering/presentation'
|
import { PDFViewer, getImageSize } from '@hcengineering/presentation'
|
||||||
import { ImageNode, type ImageOptions as ImageNodeOptions } from '@hcengineering/text'
|
import { ImageNode, type ImageOptions as ImageNodeOptions } from '@hcengineering/text'
|
||||||
import { type IconSize, getIconSize2x, showPopup } from '@hcengineering/ui'
|
import { type IconSize, getIconSize2x, showPopup } from '@hcengineering/ui'
|
||||||
import { mergeAttributes, nodeInputRule } from '@tiptap/core'
|
import { mergeAttributes, nodeInputRule } from '@tiptap/core'
|
||||||
import { type Node as ProseMirrorNode } from '@tiptap/pm/model'
|
import { type Node as ProseMirrorNode } from '@tiptap/pm/model'
|
||||||
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
||||||
import { type EditorView } from '@tiptap/pm/view'
|
import { type EditorView } from '@tiptap/pm/view'
|
||||||
import extract from 'png-chunks-extract'
|
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -318,76 +318,27 @@ async function handleImageUpload (
|
|||||||
attachFile: FileAttachFunction,
|
attachFile: FileAttachFunction,
|
||||||
uploadUrl: string
|
uploadUrl: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const size = await getImageSize(file)
|
|
||||||
const attached = await attachFile(file)
|
const attached = await attachFile(file)
|
||||||
if (attached !== undefined) {
|
|
||||||
if (attached.type.includes('image')) {
|
|
||||||
const image = new Image()
|
|
||||||
image.onload = () => {
|
|
||||||
const node = view.state.schema.nodes.image.create({
|
|
||||||
'file-id': attached.file,
|
|
||||||
width: size?.width ?? image.naturalWidth
|
|
||||||
})
|
|
||||||
const transaction = view.state.tr.insert(pos?.pos ?? 0, node)
|
|
||||||
view.dispatch(transaction)
|
|
||||||
}
|
|
||||||
image.src = getFileUrl(attached.file, 'full', uploadUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getImageSize (file: File): Promise<{ width: number, height: number } | undefined> {
|
if (attached === undefined) {
|
||||||
if (file.type !== 'image/png') {
|
return
|
||||||
return undefined
|
}
|
||||||
|
|
||||||
|
if (!attached.type.includes('image')) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const buffer = await file.arrayBuffer()
|
const size = await getImageSize(file, getFileUrl(attached.file, 'full', uploadUrl))
|
||||||
const chunks = extract(new Uint8Array(buffer))
|
const node = view.state.schema.nodes.image.create({
|
||||||
|
'file-id': attached.file,
|
||||||
|
width: Math.round(size.width / size.pixelRatio)
|
||||||
|
})
|
||||||
|
|
||||||
const pHYsChunk = chunks.find((chunk) => chunk.name === 'pHYs')
|
const transaction = view.state.tr.insert(pos?.pos ?? 0, node)
|
||||||
const iHDRChunk = chunks.find((chunk) => chunk.name === 'IHDR')
|
|
||||||
|
|
||||||
if (pHYsChunk === undefined || iHDRChunk === undefined) {
|
view.dispatch(transaction)
|
||||||
return undefined
|
} catch (e) {
|
||||||
}
|
void setPlatformStatus(unknownError(e))
|
||||||
|
|
||||||
// See http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
|
|
||||||
// Section 4.1.1. IHDR Image header
|
|
||||||
// Section 4.2.4.2. pHYs Physical pixel dimensions
|
|
||||||
const idhrData = parseIHDR(new DataView(iHDRChunk.data.buffer))
|
|
||||||
const physData = parsePhys(new DataView(pHYsChunk.data.buffer))
|
|
||||||
|
|
||||||
if (physData.unit === 0 && physData.ppux === physData.ppuy) {
|
|
||||||
const pixelRatio = Math.round(physData.ppux / 2834.5)
|
|
||||||
return {
|
|
||||||
width: Math.round(idhrData.width / pixelRatio),
|
|
||||||
height: Math.round(idhrData.height / pixelRatio)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
// See http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
|
|
||||||
// Section 4.1.1. IHDR Image header
|
|
||||||
function parseIHDR (view: DataView): { width: number, height: number } {
|
|
||||||
return {
|
|
||||||
width: view.getUint32(0),
|
|
||||||
height: view.getUint32(4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// See http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
|
|
||||||
// Section 4.2.4.2. pHYs Physical pixel dimensions
|
|
||||||
function parsePhys (view: DataView): { ppux: number, ppuy: number, unit: number } {
|
|
||||||
return {
|
|
||||||
ppux: view.getUint32(0),
|
|
||||||
ppuy: view.getUint32(4),
|
|
||||||
unit: view.getUint8(4)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
export let hoverable = true
|
export let hoverable = true
|
||||||
export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover'
|
export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover'
|
||||||
export let withShowMore: boolean = true
|
export let withShowMore: boolean = true
|
||||||
|
export let attachmentImageSize: 'x-large' | undefined = undefined
|
||||||
export let showLinksPreview = true
|
export let showLinksPreview = true
|
||||||
export let onClick: (() => void) | undefined = undefined
|
export let onClick: (() => void) | undefined = undefined
|
||||||
export let onReply: (() => void) | undefined = undefined
|
export let onReply: (() => void) | undefined = undefined
|
||||||
@ -65,6 +66,7 @@
|
|||||||
hoverable,
|
hoverable,
|
||||||
hoverStyles,
|
hoverStyles,
|
||||||
withShowMore,
|
withShowMore,
|
||||||
|
attachmentImageSize,
|
||||||
showLinksPreview,
|
showLinksPreview,
|
||||||
onClick,
|
onClick,
|
||||||
onReply
|
onReply
|
||||||
|
@ -19,9 +19,11 @@
|
|||||||
|
|
||||||
import attachment from '../plugin'
|
import attachment from '../plugin'
|
||||||
import AttachmentList from './AttachmentList.svelte'
|
import AttachmentList from './AttachmentList.svelte'
|
||||||
|
import { AttachmentImageSize } from '../types'
|
||||||
|
|
||||||
export let value: Doc & { attachments?: number }
|
export let value: Doc & { attachments?: number }
|
||||||
export let attachments: Attachment[] | undefined = undefined
|
export let attachments: Attachment[] | undefined = undefined
|
||||||
|
export let imageSize: AttachmentImageSize = 'auto'
|
||||||
|
|
||||||
const query = createQuery()
|
const query = createQuery()
|
||||||
const savedAttachmentsQuery = createQuery()
|
const savedAttachmentsQuery = createQuery()
|
||||||
@ -57,4 +59,4 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AttachmentList attachments={resAttachments} {savedAttachmentsIds} />
|
<AttachmentList attachments={resAttachments} {savedAttachmentsIds} {imageSize} />
|
||||||
|
@ -0,0 +1,128 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { getIconSize2x, IconSize } from '@hcengineering/ui'
|
||||||
|
import { getFileUrl } from '@hcengineering/presentation'
|
||||||
|
import type { Attachment } from '@hcengineering/attachment'
|
||||||
|
|
||||||
|
import { AttachmentImageSize } from '../types'
|
||||||
|
|
||||||
|
export let value: Attachment
|
||||||
|
export let size: AttachmentImageSize = 'auto'
|
||||||
|
|
||||||
|
interface Dimensions {
|
||||||
|
width: 'auto' | number
|
||||||
|
height: 'auto' | number
|
||||||
|
}
|
||||||
|
|
||||||
|
const minSizeRem = 4
|
||||||
|
const maxSizeRem = 20
|
||||||
|
|
||||||
|
const preferredWidthMap = {
|
||||||
|
'x-large': 300
|
||||||
|
} as const
|
||||||
|
|
||||||
|
let dimensions: Dimensions
|
||||||
|
let urlSize: IconSize
|
||||||
|
|
||||||
|
$: dimensions = getDimensions(value, size)
|
||||||
|
$: urlSize = getUrlSize(size)
|
||||||
|
|
||||||
|
function getDimensions (value: Attachment, size: AttachmentImageSize): Dimensions {
|
||||||
|
if (size === 'auto') {
|
||||||
|
return {
|
||||||
|
width: 'auto',
|
||||||
|
height: 'auto'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const preferredWidth = preferredWidthMap[size]
|
||||||
|
const { metadata } = value
|
||||||
|
|
||||||
|
if (!metadata) {
|
||||||
|
return {
|
||||||
|
width: preferredWidth,
|
||||||
|
height: preferredWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { originalWidth, originalHeight } = metadata
|
||||||
|
const maxSize = maxSizeRem * parseFloat(getComputedStyle(document.documentElement).fontSize)
|
||||||
|
|
||||||
|
const width = Math.min(originalWidth, preferredWidth)
|
||||||
|
const ratio = originalHeight / originalWidth
|
||||||
|
const height = width * ratio
|
||||||
|
|
||||||
|
if (height > maxSize) {
|
||||||
|
return {
|
||||||
|
width: maxSize / ratio,
|
||||||
|
height: maxSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getObjectFit (size: Dimensions): 'contain' | 'cover' {
|
||||||
|
if (size.width === 'auto' || size.height === 'auto') {
|
||||||
|
return 'contain'
|
||||||
|
}
|
||||||
|
|
||||||
|
const minSize = minSizeRem * parseFloat(getComputedStyle(document.documentElement).fontSize)
|
||||||
|
|
||||||
|
if (size.width < minSize || size.height < minSize) {
|
||||||
|
return 'cover'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'contain'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUrlSize (size: AttachmentImageSize): IconSize {
|
||||||
|
if (size === 'auto') {
|
||||||
|
return 'large'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'x-large'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<img
|
||||||
|
src={getFileUrl(value.file, urlSize)}
|
||||||
|
style:object-fit={getObjectFit(dimensions)}
|
||||||
|
width={dimensions.width}
|
||||||
|
height={dimensions.height}
|
||||||
|
srcset={`${getFileUrl(value.file, urlSize, value.name)} 1x, ${getFileUrl(
|
||||||
|
value.file,
|
||||||
|
getIconSize2x(urlSize),
|
||||||
|
value.name
|
||||||
|
)} 2x`}
|
||||||
|
alt={value.name}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
img {
|
||||||
|
max-width: 20rem;
|
||||||
|
max-height: 20rem;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
object-fit: contain;
|
||||||
|
min-height: 4rem;
|
||||||
|
min-width: 4rem;
|
||||||
|
}
|
||||||
|
</style>
|
@ -16,16 +16,23 @@
|
|||||||
import { Attachment } from '@hcengineering/attachment'
|
import { Attachment } from '@hcengineering/attachment'
|
||||||
import { Ref } from '@hcengineering/core'
|
import { Ref } from '@hcengineering/core'
|
||||||
import { Scroller } from '@hcengineering/ui'
|
import { Scroller } from '@hcengineering/ui'
|
||||||
|
|
||||||
import AttachmentPreview from './AttachmentPreview.svelte'
|
import AttachmentPreview from './AttachmentPreview.svelte'
|
||||||
|
import { AttachmentImageSize } from '../types'
|
||||||
|
|
||||||
export let attachments: Attachment[] = []
|
export let attachments: Attachment[] = []
|
||||||
export let savedAttachmentsIds: Ref<Attachment>[] = []
|
export let savedAttachmentsIds: Ref<Attachment>[] = []
|
||||||
|
export let imageSize: AttachmentImageSize | undefined = undefined
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if attachments.length}
|
{#if attachments.length}
|
||||||
<Scroller contentDirection={'horizontal'} horizontal gap={'gap-3'}>
|
<Scroller contentDirection={'horizontal'} horizontal gap={'gap-3'}>
|
||||||
{#each attachments as attachment}
|
{#each attachments as attachment}
|
||||||
<AttachmentPreview value={attachment} isSaved={savedAttachmentsIds?.includes(attachment._id) ?? false} />
|
<AttachmentPreview
|
||||||
|
value={attachment}
|
||||||
|
isSaved={savedAttachmentsIds?.includes(attachment._id) ?? false}
|
||||||
|
{imageSize}
|
||||||
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</Scroller>
|
</Scroller>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -18,9 +18,11 @@
|
|||||||
import { Attachment } from '@hcengineering/attachment'
|
import { Attachment } from '@hcengineering/attachment'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { ActionIcon, IconAdd, Label, Loading } from '@hcengineering/ui'
|
import { ActionIcon, IconAdd, Label, Loading } from '@hcengineering/ui'
|
||||||
|
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
|
|
||||||
import { AttachmentPresenter } from '..'
|
import { AttachmentPresenter } from '..'
|
||||||
import attachment from '../plugin'
|
import attachment from '../plugin'
|
||||||
import { uploadFile } from '../utils'
|
import { getAttachmentMetadata, uploadFile } from '../utils'
|
||||||
|
|
||||||
// export let attachments: number
|
// export let attachments: number
|
||||||
export let object: Doc
|
export let object: Doc
|
||||||
@ -51,14 +53,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createAttachment (file: File) {
|
async function createAttachment (file: File) {
|
||||||
|
try {
|
||||||
const uuid = await uploadFile(file)
|
const uuid = await uploadFile(file)
|
||||||
|
const metadata = await getAttachmentMetadata(file, uuid)
|
||||||
|
|
||||||
await client.addCollection(attachment.class.Attachment, object.space, object._id, object._class, 'attachments', {
|
await client.addCollection(attachment.class.Attachment, object.space, object._id, object._class, 'attachments', {
|
||||||
name: file.name,
|
name: file.name,
|
||||||
file: uuid,
|
file: uuid,
|
||||||
type: file.type,
|
type: file.type,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
lastModified: file.lastModified
|
lastModified: file.lastModified,
|
||||||
|
metadata
|
||||||
})
|
})
|
||||||
|
} catch (e) {
|
||||||
|
void setPlatformStatus(unknownError(e))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fileSelected (): Promise<void> {
|
async function fileSelected (): Promise<void> {
|
||||||
|
@ -15,19 +15,25 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Attachment } from '@hcengineering/attachment'
|
import type { Attachment } from '@hcengineering/attachment'
|
||||||
import { getFileUrl, PDFViewer } from '@hcengineering/presentation'
|
import { PDFViewer } from '@hcengineering/presentation'
|
||||||
import { showPopup, closeTooltip, getIconSize2x } from '@hcengineering/ui'
|
import { showPopup, closeTooltip } from '@hcengineering/ui'
|
||||||
|
import { ListSelectionProvider } from '@hcengineering/view-resources'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
|
||||||
import { getType } from '../utils'
|
import { getType } from '../utils'
|
||||||
import AttachmentPresenter from './AttachmentPresenter.svelte'
|
import AttachmentPresenter from './AttachmentPresenter.svelte'
|
||||||
import AttachmentActions from './AttachmentActions.svelte'
|
import AttachmentActions from './AttachmentActions.svelte'
|
||||||
import AudioPlayer from './AudioPlayer.svelte'
|
import AudioPlayer from './AudioPlayer.svelte'
|
||||||
import { ListSelectionProvider } from '@hcengineering/view-resources'
|
import { AttachmentImageSize } from '../types'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import AttachmentImagePreview from './AttachmentImagePreview.svelte'
|
||||||
|
import AttachmentVideoPreview from './AttachmentVideoPreview.svelte'
|
||||||
|
|
||||||
export let value: Attachment
|
export let value: Attachment
|
||||||
export let isSaved: boolean = false
|
export let isSaved: boolean = false
|
||||||
export let listProvider: ListSelectionProvider | undefined = undefined
|
export let listProvider: ListSelectionProvider | undefined = undefined
|
||||||
|
export let imageSize: AttachmentImageSize = 'auto'
|
||||||
export let removable: boolean = false
|
export let removable: boolean = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
$: type = getType(value.type)
|
$: type = getType(value.type)
|
||||||
@ -49,15 +55,7 @@
|
|||||||
dispatch('open', popupInfo.id)
|
dispatch('open', popupInfo.id)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<AttachmentImagePreview {value} size={imageSize} />
|
||||||
src={getFileUrl(value.file, 'large')}
|
|
||||||
srcset={`${getFileUrl(value.file, 'large', value.name)} 1x, ${getFileUrl(
|
|
||||||
value.file,
|
|
||||||
getIconSize2x('large'),
|
|
||||||
value.name
|
|
||||||
)} 2x`}
|
|
||||||
alt={value.name}
|
|
||||||
/>
|
|
||||||
<div class="actions conner">
|
<div class="actions conner">
|
||||||
<AttachmentActions attachment={value} {isSaved} {removable} />
|
<AttachmentActions attachment={value} {isSaved} {removable} />
|
||||||
</div>
|
</div>
|
||||||
@ -71,13 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else if type === 'video'}
|
{:else if type === 'video'}
|
||||||
<div class="content buttonContainer flex-center">
|
<div class="content buttonContainer flex-center">
|
||||||
<video controls>
|
<AttachmentVideoPreview {value} />
|
||||||
<source src={getFileUrl(value.file, 'full', value.name)} />
|
|
||||||
<track kind="captions" label={value.name} />
|
|
||||||
<div class="container">
|
|
||||||
<AttachmentPresenter {value} />
|
|
||||||
</div>
|
|
||||||
</video>
|
|
||||||
<div class="actions conner">
|
<div class="actions conner">
|
||||||
<AttachmentActions attachment={value} {isSaved} {removable} />
|
<AttachmentActions attachment={value} {isSaved} {removable} />
|
||||||
</div>
|
</div>
|
||||||
@ -129,17 +121,5 @@
|
|||||||
.content {
|
.content {
|
||||||
max-width: 20rem;
|
max-width: 20rem;
|
||||||
max-height: 20rem;
|
max-height: 20rem;
|
||||||
|
|
||||||
img,
|
|
||||||
video {
|
|
||||||
max-width: 20rem;
|
|
||||||
max-height: 20rem;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -17,10 +17,17 @@
|
|||||||
import { Attachment } from '@hcengineering/attachment'
|
import { Attachment } from '@hcengineering/attachment'
|
||||||
import { Account, Class, Doc, generateId, IdMap, Ref, Space, toIdMap } from '@hcengineering/core'
|
import { Account, Class, Doc, generateId, IdMap, Ref, Space, toIdMap } from '@hcengineering/core'
|
||||||
import { IntlString, setPlatformStatus, unknownError, Asset } from '@hcengineering/platform'
|
import { IntlString, setPlatformStatus, unknownError, Asset } from '@hcengineering/platform'
|
||||||
import { createQuery, DraftController, draftsStore, getClient } from '@hcengineering/presentation'
|
import {
|
||||||
|
createQuery,
|
||||||
|
DraftController,
|
||||||
|
draftsStore,
|
||||||
|
getClient,
|
||||||
|
getFileUrl,
|
||||||
|
getImageSize
|
||||||
|
} from '@hcengineering/presentation'
|
||||||
import textEditor, { AttachIcon, type RefAction, ReferenceInput } from '@hcengineering/text-editor'
|
import textEditor, { AttachIcon, type RefAction, ReferenceInput } from '@hcengineering/text-editor'
|
||||||
import { Loading, type AnySvelteComponent } from '@hcengineering/ui'
|
import { Loading, type AnySvelteComponent } from '@hcengineering/ui'
|
||||||
import { deleteFile, uploadFile } from '../utils'
|
import { deleteFile, getAttachmentMetadata, uploadFile } from '../utils'
|
||||||
import attachment from '../plugin'
|
import attachment from '../plugin'
|
||||||
import AttachmentPresenter from './AttachmentPresenter.svelte'
|
import AttachmentPresenter from './AttachmentPresenter.svelte'
|
||||||
|
|
||||||
@ -103,6 +110,8 @@
|
|||||||
async function createAttachment (file: File) {
|
async function createAttachment (file: File) {
|
||||||
try {
|
try {
|
||||||
const uuid = await uploadFile(file)
|
const uuid = await uploadFile(file)
|
||||||
|
const metadata = await getAttachmentMetadata(file, uuid)
|
||||||
|
|
||||||
const _id: Ref<Attachment> = generateId()
|
const _id: Ref<Attachment> = generateId()
|
||||||
attachments.set(_id, {
|
attachments.set(_id, {
|
||||||
_id,
|
_id,
|
||||||
@ -117,7 +126,8 @@
|
|||||||
file: uuid,
|
file: uuid,
|
||||||
type: file.type,
|
type: file.type,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
lastModified: file.lastModified
|
lastModified: file.lastModified,
|
||||||
|
metadata
|
||||||
})
|
})
|
||||||
newAttachments.add(_id)
|
newAttachments.add(_id)
|
||||||
attachments = attachments
|
attachments = attachments
|
||||||
|
@ -20,8 +20,9 @@
|
|||||||
import { createQuery, DraftController, draftsStore, getClient } from '@hcengineering/presentation'
|
import { createQuery, DraftController, draftsStore, getClient } from '@hcengineering/presentation'
|
||||||
import textEditor, { AttachIcon, type RefAction, StyledTextBox } from '@hcengineering/text-editor'
|
import textEditor, { AttachIcon, type RefAction, StyledTextBox } from '@hcengineering/text-editor'
|
||||||
import { ButtonSize } from '@hcengineering/ui'
|
import { ButtonSize } from '@hcengineering/ui'
|
||||||
|
|
||||||
import attachment from '../plugin'
|
import attachment from '../plugin'
|
||||||
import { deleteFile, uploadFile } from '../utils'
|
import { deleteFile, getAttachmentMetadata, uploadFile } from '../utils'
|
||||||
import AttachmentsGrid from './AttachmentsGrid.svelte'
|
import AttachmentsGrid from './AttachmentsGrid.svelte'
|
||||||
|
|
||||||
export let objectId: Ref<Doc> | undefined = undefined
|
export let objectId: Ref<Doc> | undefined = undefined
|
||||||
@ -133,7 +134,9 @@
|
|||||||
if (space === undefined || objectId === undefined || _class === undefined) return
|
if (space === undefined || objectId === undefined || _class === undefined) return
|
||||||
try {
|
try {
|
||||||
const uuid = await uploadFile(file)
|
const uuid = await uploadFile(file)
|
||||||
|
const metadata = await getAttachmentMetadata(file, uuid)
|
||||||
const _id: Ref<Attachment> = generateId()
|
const _id: Ref<Attachment> = generateId()
|
||||||
|
|
||||||
attachments.set(_id, {
|
attachments.set(_id, {
|
||||||
_id,
|
_id,
|
||||||
_class: attachment.class.Attachment,
|
_class: attachment.class.Attachment,
|
||||||
@ -147,7 +150,8 @@
|
|||||||
file: uuid,
|
file: uuid,
|
||||||
type: file.type,
|
type: file.type,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
lastModified: file.lastModified
|
lastModified: file.lastModified,
|
||||||
|
metadata
|
||||||
})
|
})
|
||||||
newAttachments.add(_id)
|
newAttachments.add(_id)
|
||||||
attachments = attachments
|
attachments = attachments
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { getFileUrl } from '@hcengineering/presentation'
|
||||||
|
import type { Attachment } from '@hcengineering/attachment'
|
||||||
|
|
||||||
|
import AttachmentPresenter from './AttachmentPresenter.svelte'
|
||||||
|
|
||||||
|
export let value: Attachment
|
||||||
|
|
||||||
|
const maxSizeRem = 20
|
||||||
|
const baseSizeRem = 12
|
||||||
|
const minSizeRem = 4
|
||||||
|
|
||||||
|
$: dimensions = getDimensions(value)
|
||||||
|
|
||||||
|
function getDimensions (value: Attachment): { width: number, height: number } {
|
||||||
|
const fontSize = parseFloat(getComputedStyle(document.documentElement).fontSize)
|
||||||
|
|
||||||
|
if (!value.metadata) {
|
||||||
|
const baseSize = baseSizeRem * fontSize
|
||||||
|
return { width: baseSize, height: baseSize }
|
||||||
|
}
|
||||||
|
|
||||||
|
const { originalWidth, originalHeight } = value.metadata
|
||||||
|
const maxSize = maxSizeRem * fontSize
|
||||||
|
|
||||||
|
// For mp4 audio files, we don't have originalWidth, originalHeight
|
||||||
|
if (originalWidth === 0 || originalHeight === 0) {
|
||||||
|
return { width: maxSize, height: minSizeRem * fontSize }
|
||||||
|
}
|
||||||
|
|
||||||
|
const ratio = originalHeight / originalWidth
|
||||||
|
|
||||||
|
const width = Math.min(maxSize, originalWidth)
|
||||||
|
const height = width * ratio
|
||||||
|
|
||||||
|
if (height > maxSize) {
|
||||||
|
return { width: maxSize / ratio, height: maxSize }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { width, height }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<video controls width={dimensions.width} height={dimensions.height}>
|
||||||
|
<source src={getFileUrl(value.file, 'full', value.name)} />
|
||||||
|
<track kind="captions" label={value.name} />
|
||||||
|
<div class="container">
|
||||||
|
<AttachmentPresenter {value} />
|
||||||
|
</div>
|
||||||
|
</video>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
video {
|
||||||
|
max-width: 20rem;
|
||||||
|
max-height: 20rem;
|
||||||
|
min-width: 4rem;
|
||||||
|
min-height: 4rem;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
</style>
|
@ -43,6 +43,8 @@ import AttachmentPreview from './components/AttachmentPreview.svelte'
|
|||||||
import AttachmentsUpdatedMessage from './components/activity/AttachmentsUpdatedMessage.svelte'
|
import AttachmentsUpdatedMessage from './components/activity/AttachmentsUpdatedMessage.svelte'
|
||||||
import { deleteFile, uploadFile } from './utils'
|
import { deleteFile, uploadFile } from './utils'
|
||||||
|
|
||||||
|
export * from './types'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AddAttachment,
|
AddAttachment,
|
||||||
AttachmentDroppable,
|
AttachmentDroppable,
|
||||||
|
1
plugins/attachment-resources/src/types.ts
Normal file
1
plugins/attachment-resources/src/types.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type AttachmentImageSize = 'x-large' | 'auto'
|
@ -14,7 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { type Attachment } from '@hcengineering/attachment'
|
import { type Attachment, type AttachmentMetadata } from '@hcengineering/attachment'
|
||||||
import {
|
import {
|
||||||
type Class,
|
type Class,
|
||||||
concatLink,
|
concatLink,
|
||||||
@ -24,7 +24,7 @@ import {
|
|||||||
type Space,
|
type Space,
|
||||||
type TxOperations as Client
|
type TxOperations as Client
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import presentation from '@hcengineering/presentation'
|
import presentation, { getFileUrl, getImageSize } from '@hcengineering/presentation'
|
||||||
import { PlatformError, Severity, Status, getMetadata, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
import { PlatformError, Severity, Status, getMetadata, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
|
|
||||||
import attachment from './plugin'
|
import attachment from './plugin'
|
||||||
@ -87,13 +87,16 @@ export async function createAttachments (
|
|||||||
const file = list.item(index)
|
const file = list.item(index)
|
||||||
if (file !== null) {
|
if (file !== null) {
|
||||||
const uuid = await uploadFile(file)
|
const uuid = await uploadFile(file)
|
||||||
|
const metadata = await getAttachmentMetadata(file, uuid)
|
||||||
|
|
||||||
await client.addCollection(attachmentClass, space, objectId, objectClass, 'attachments', {
|
await client.addCollection(attachmentClass, space, objectId, objectClass, 'attachments', {
|
||||||
...extraData,
|
...extraData,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
file: uuid,
|
file: uuid,
|
||||||
type: file.type,
|
type: file.type,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
lastModified: file.lastModified
|
lastModified: file.lastModified,
|
||||||
|
metadata
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,3 +121,50 @@ export function getType (type: string): 'image' | 'video' | 'audio' | 'pdf' | 'o
|
|||||||
|
|
||||||
return 'other'
|
return 'other'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAttachmentMetadata (file: File, uuid: string): Promise<AttachmentMetadata | undefined> {
|
||||||
|
const type = getType(file.type)
|
||||||
|
|
||||||
|
if (type === 'video') {
|
||||||
|
const size = await getVideoSize(uuid)
|
||||||
|
|
||||||
|
if (size === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
originalHeight: size.height,
|
||||||
|
originalWidth: size.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'image') {
|
||||||
|
const size = await getImageSize(file, getFileUrl(uuid, 'full'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
originalHeight: size.height,
|
||||||
|
originalWidth: size.width,
|
||||||
|
pixelRatio: size.pixelRatio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getVideoSize (uuid: string): Promise<{ width: number, height: number } | undefined> {
|
||||||
|
const promise = new Promise<{ width: number, height: number }>((resolve, reject) => {
|
||||||
|
const element = document.createElement('video')
|
||||||
|
|
||||||
|
element.onloadedmetadata = () => {
|
||||||
|
const height = element.videoHeight
|
||||||
|
const width = element.videoWidth
|
||||||
|
|
||||||
|
resolve({ height, width })
|
||||||
|
}
|
||||||
|
|
||||||
|
element.onerror = reject
|
||||||
|
element.src = getFileUrl(uuid, 'full')
|
||||||
|
})
|
||||||
|
|
||||||
|
return await promise
|
||||||
|
}
|
||||||
|
@ -33,6 +33,30 @@ export interface Attachment extends AttachedDoc {
|
|||||||
pinned?: boolean // If defined and true, will be shown in top of attachments collection
|
pinned?: boolean // If defined and true, will be shown in top of attachments collection
|
||||||
|
|
||||||
readonly?: boolean // If readonly, user will not be able to remove or modify this attachment
|
readonly?: boolean // If readonly, user will not be able to remove or modify this attachment
|
||||||
|
|
||||||
|
metadata?: AttachmentMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type AttachmentMetadata = ImageMetadata | VideoMetadata
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface ImageMetadata {
|
||||||
|
originalWidth: number
|
||||||
|
originalHeight: number
|
||||||
|
pixelRatio: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface VideoMetadata {
|
||||||
|
originalWidth: number
|
||||||
|
originalHeight: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -512,6 +512,7 @@
|
|||||||
isHighlighted={isSelected}
|
isHighlighted={isSelected}
|
||||||
shouldScroll={isSelected}
|
shouldScroll={isSelected}
|
||||||
withShowMore={false}
|
withShowMore={false}
|
||||||
|
attachmentImageSize="x-large"
|
||||||
showLinksPreview={false}
|
showLinksPreview={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,10 +15,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Person, PersonAccount } from '@hcengineering/contact'
|
import { Person, PersonAccount } from '@hcengineering/contact'
|
||||||
import { personByIdStore } from '@hcengineering/contact-resources'
|
import { personByIdStore } from '@hcengineering/contact-resources'
|
||||||
import { Account, Class, Doc, getCurrentAccount, Ref, WithLookup } from '@hcengineering/core'
|
import core, { Account, Class, Doc, getCurrentAccount, Ref, WithLookup } from '@hcengineering/core'
|
||||||
import { createQuery, getClient, MessageViewer } from '@hcengineering/presentation'
|
import { createQuery, getClient, MessageViewer } from '@hcengineering/presentation'
|
||||||
import core from '@hcengineering/core/src/component'
|
import { AttachmentDocList, AttachmentImageSize } from '@hcengineering/attachment-resources'
|
||||||
import { AttachmentDocList } from '@hcengineering/attachment-resources'
|
|
||||||
import { getDocLinkTitle, LinkPresenter } from '@hcengineering/view-resources'
|
import { getDocLinkTitle, LinkPresenter } from '@hcengineering/view-resources'
|
||||||
import { Action, Button, IconEdit, ShowMore } from '@hcengineering/ui'
|
import { Action, Button, IconEdit, ShowMore } from '@hcengineering/ui'
|
||||||
import view from '@hcengineering/view'
|
import view from '@hcengineering/view'
|
||||||
@ -47,6 +46,7 @@
|
|||||||
export let inline = false
|
export let inline = false
|
||||||
export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover'
|
export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover'
|
||||||
export let withShowMore: boolean = true
|
export let withShowMore: boolean = true
|
||||||
|
export let attachmentImageSize: AttachmentImageSize = 'auto'
|
||||||
export let showLinksPreview = true
|
export let showLinksPreview = true
|
||||||
export let onClick: (() => void) | undefined = undefined
|
export let onClick: (() => void) | undefined = undefined
|
||||||
export let onReply: (() => void) | undefined = undefined
|
export let onReply: (() => void) | undefined = undefined
|
||||||
@ -193,7 +193,7 @@
|
|||||||
<ShowMore>
|
<ShowMore>
|
||||||
<div class="clear-mins">
|
<div class="clear-mins">
|
||||||
<MessageViewer message={value.message} />
|
<MessageViewer message={value.message} />
|
||||||
<AttachmentDocList {value} {attachments} />
|
<AttachmentDocList {value} {attachments} imageSize={attachmentImageSize} />
|
||||||
{#each links as link}
|
{#each links as link}
|
||||||
<LinkPresenter {link} />
|
<LinkPresenter {link} />
|
||||||
{/each}
|
{/each}
|
||||||
@ -202,7 +202,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="clear-mins">
|
<div class="clear-mins">
|
||||||
<MessageViewer message={value.message} />
|
<MessageViewer message={value.message} />
|
||||||
<AttachmentDocList {value} {attachments} />
|
<AttachmentDocList {value} {attachments} imageSize={attachmentImageSize} />
|
||||||
{#each links as link}
|
{#each links as link}
|
||||||
<LinkPresenter {link} />
|
<LinkPresenter {link} />
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
import { getDocLinkTitle } from '@hcengineering/view-resources'
|
import { getDocLinkTitle } from '@hcengineering/view-resources'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import activity from '@hcengineering/activity'
|
import activity from '@hcengineering/activity'
|
||||||
|
import { AttachmentImageSize } from '@hcengineering/attachment-resources'
|
||||||
|
|
||||||
import chunter from '../../plugin'
|
import chunter from '../../plugin'
|
||||||
import ChatMessagePresenter from '../chat-message/ChatMessagePresenter.svelte'
|
import ChatMessagePresenter from '../chat-message/ChatMessagePresenter.svelte'
|
||||||
@ -38,6 +39,7 @@
|
|||||||
export let inline = false
|
export let inline = false
|
||||||
export let withShowMore: boolean = true
|
export let withShowMore: boolean = true
|
||||||
export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover'
|
export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover'
|
||||||
|
export let attachmentImageSize: AttachmentImageSize = 'x-large'
|
||||||
export let onClick: (() => void) | undefined = undefined
|
export let onClick: (() => void) | undefined = undefined
|
||||||
export let onReply: (() => void) | undefined = undefined
|
export let onReply: (() => void) | undefined = undefined
|
||||||
|
|
||||||
@ -73,6 +75,7 @@
|
|||||||
{hoverable}
|
{hoverable}
|
||||||
{hoverStyles}
|
{hoverStyles}
|
||||||
{withShowMore}
|
{withShowMore}
|
||||||
|
{attachmentImageSize}
|
||||||
showLinksPreview={false}
|
showLinksPreview={false}
|
||||||
{onClick}
|
{onClick}
|
||||||
{onReply}
|
{onReply}
|
||||||
|
Loading…
Reference in New Issue
Block a user