Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UBERF-5595: set up attachments sizes #4746

Merged
merged 7 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
430 changes: 216 additions & 214 deletions common/config/rush/pnpm-lock.yaml

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions models/attachment/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
Prop,
TypeAttachment,
TypeBoolean,
TypeNumber,
TypeRef,
TypeString,
TypeTimestamp,
Expand Down Expand Up @@ -64,6 +65,15 @@ export class TAttachment extends TAttachedDoc implements Attachment {

@Prop(TypeBoolean(), attachment.string.Pinned)
pinned!: boolean

@Prop(TypeNumber(), attachment.string.Width)
originalWidth?: number

@Prop(TypeNumber(), attachment.string.Height)
originalHeight?: number

@Prop(TypeNumber(), attachment.string.Ratio)
pixelRatio?: number
}

@Model(attachment.class.Photo, attachment.class.Attachment)
Expand Down
5 changes: 4 additions & 1 deletion models/attachment/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ export default mergeIds(attachmentId, attachment, {
PinAttachment: '' as IntlString,
UnPinAttachment: '' as IntlString,
FilterAttachments: '' as IntlString,
RemovedAttachment: '' as IntlString
RemovedAttachment: '' as IntlString,
Width: '' as IntlString,
Height: '' as IntlString,
Ratio: '' as IntlString
},
ids: {
TxAttachmentCreate: '' as Ref<TxViewlet>,
Expand Down
6 changes: 4 additions & 2 deletions packages/presentation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@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": {
"@hcengineering/platform": "^0.6.9",
Expand All @@ -45,6 +46,7 @@
"svelte": "^4.2.5",
"@hcengineering/client": "^0.6.14",
"@hcengineering/collaborator-client": "^0.6.0",
"fast-equals": "^2.0.3"
"fast-equals": "^2.0.3",
"png-chunks-extract": "^1.0.0"
}
}
101 changes: 101 additions & 0 deletions packages/presentation/src/image.ts
Original file line number Diff line number Diff line change
@@ -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)
}
}
1 change: 1 addition & 0 deletions packages/presentation/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,4 @@ export * from './pipeline'
export * from './components/extensions/manager'
export * from './rules'
export * from './search'
export * from './image'
6 changes: 2 additions & 4 deletions packages/text-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5",
"svelte-eslint-parser": "^0.33.1",
"@types/png-chunks-extract": "^1.0.2"
"svelte-eslint-parser": "^0.33.1"
},
"dependencies": {
"@hcengineering/presentation": "^0.6.2",
Expand Down Expand Up @@ -78,7 +77,6 @@
"rfc6902": "^5.0.1",
"diff": "^5.1.0",
"slugify": "^1.6.6",
"lib0": "^0.2.88",
"png-chunks-extract": "^1.0.0"
"lib0": "^0.2.88"
}
}
81 changes: 16 additions & 65 deletions packages/text-editor/src/components/extension/imageExt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
// See the License for the specific language governing permissions and
// 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 { type IconSize, getIconSize2x, showPopup } from '@hcengineering/ui'
import { mergeAttributes, nodeInputRule } from '@tiptap/core'
import { type Node as ProseMirrorNode } from '@tiptap/pm/model'
import { Plugin, PluginKey } from '@tiptap/pm/state'
import { type EditorView } from '@tiptap/pm/view'
import extract from 'png-chunks-extract'
import { setPlatformStatus, unknownError } from '@hcengineering/platform'

/**
* @public
Expand Down Expand Up @@ -318,76 +318,27 @@ async function handleImageUpload (
attachFile: FileAttachFunction,
uploadUrl: string
): Promise<void> {
const size = await getImageSize(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 (file.type !== 'image/png') {
return undefined
if (attached === undefined) {
return
}

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: Math.round(idhrData.width / pixelRatio),
height: Math.round(idhrData.height / pixelRatio)
}
}
} catch (err) {
console.error(err)
return undefined
if (!attached.type.includes('image')) {
return
}

return undefined
}
try {
const size = await getImageSize(file, getFileUrl(attached.file, 'full', uploadUrl))
const node = view.state.schema.nodes.image.create({
'file-id': attached.file,
width: Math.round(size.width / size.pixelRatio)
})

// 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)
}
}
const transaction = view.state.tr.insert(pos?.pos ?? 0, node)

// 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)
view.dispatch(transaction)
} catch (e) {
void setPlatformStatus(unknownError(e))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
export let hoverable = true
export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover'
export let withShowMore: boolean = true
export let attachmentImageSize: 'x-large' | undefined = undefined
export let onClick: (() => void) | undefined = undefined
export let onReply: (() => void) | undefined = undefined

Expand Down Expand Up @@ -64,6 +65,7 @@
hoverable,
hoverStyles,
withShowMore,
attachmentImageSize,
onClick,
onReply
}}
Expand Down
5 changes: 4 additions & 1 deletion plugins/attachment-assets/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@
"PinAttachment": "Mark important",
"UnPinAttachment": "Mark less important",
"FilterAttachments": "Attachments",
"RemovedAttachment": "Removed attachment"
"RemovedAttachment": "Removed attachment",
"Width": "Width",
"Height": "Height",
"Ratio": "Ratio"
},
"status": {
"FileTooLarge": "File too large"
Expand Down
5 changes: 4 additions & 1 deletion plugins/attachment-assets/lang/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@
"PinAttachment": "Пометить как важное",
"UnPinAttachment": "Убрать пометку важное",
"FilterAttachments": "Вложения",
"RemovedAttachment": "Удалил(а) вложение"
"RemovedAttachment": "Удалил(а) вложение",
"Width": "Ширина",
"Height": "Высота",
"Ratio": "Соотношение"
},
"status": {
"FileTooLarge": "Файл слишком большой"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@

import attachment from '../plugin'
import AttachmentList from './AttachmentList.svelte'
import { AttachmentImageSize } from '../types'

export let value: Doc & { attachments?: number }
export let attachments: Attachment[] | undefined = undefined
export let imageSize: AttachmentImageSize = 'auto'

const query = createQuery()
const savedAttachmentsQuery = createQuery()
Expand Down Expand Up @@ -57,4 +59,4 @@
})
</script>

<AttachmentList attachments={resAttachments} {savedAttachmentsIds} />
<AttachmentList attachments={resAttachments} {savedAttachmentsIds} {imageSize} />
Loading
Loading