ImageUploadEditorComponent ve ImageViewerEditorComponent
Bu komponentler için tıklanabilir ve preview hale getirildi.
This commit is contained in:
parent
da19e48018
commit
707e835570
5 changed files with 128 additions and 42 deletions
|
|
@ -1,6 +1,11 @@
|
|||
import { ImageUploadOptionsDto } from '@/proxy/form/models'
|
||||
import { ReactElement, useRef, useState } from 'react'
|
||||
import { FaSpinner, FaUpload } from 'react-icons/fa'
|
||||
import {
|
||||
hideImageHoverPreview,
|
||||
openImageInNewTab,
|
||||
showImageHoverPreview,
|
||||
} from './imageHoverPreview'
|
||||
|
||||
const ImageUploadEditorComponent = ({
|
||||
value,
|
||||
|
|
@ -98,12 +103,17 @@ const ImageUploadEditorComponent = ({
|
|||
<img
|
||||
src={url}
|
||||
alt=""
|
||||
onMouseEnter={(event) => showImageHoverPreview(url, event)}
|
||||
onMouseMove={(event) => showImageHoverPreview(url, event)}
|
||||
onMouseLeave={hideImageHoverPreview}
|
||||
onClick={() => openImageInNewTab(url)}
|
||||
style={{
|
||||
width: thumbW,
|
||||
height: thumbH,
|
||||
objectFit: 'cover',
|
||||
borderRadius: 4,
|
||||
border: '1px solid #ddd',
|
||||
cursor: 'zoom-in',
|
||||
}}
|
||||
/>
|
||||
{!readOnly && (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import { ImageUploadOptionsDto } from '@/proxy/form/models'
|
||||
import { ReactElement } from 'react'
|
||||
import {
|
||||
hideImageHoverPreview,
|
||||
openImageInNewTab,
|
||||
showImageHoverPreview,
|
||||
} from './imageHoverPreview'
|
||||
|
||||
const parseJsonObject = (value: unknown) => {
|
||||
if (!value) return undefined
|
||||
|
|
@ -86,26 +91,6 @@ const normalizeImageValue = (value: unknown): string[] => {
|
|||
return source ? [toImageSource(source)] : []
|
||||
}
|
||||
|
||||
const openImage = (url: string) => {
|
||||
if (!url.startsWith('data:image/')) {
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const match = url.match(/^data:(image\/[^;]+);base64,(.*)$/)
|
||||
if (!match) return
|
||||
|
||||
const binary = atob(match[2])
|
||||
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0))
|
||||
const blobUrl = URL.createObjectURL(new Blob([bytes], { type: match[1] }))
|
||||
window.open(blobUrl, '_blank', 'noopener,noreferrer')
|
||||
window.setTimeout(() => URL.revokeObjectURL(blobUrl), 60000)
|
||||
} catch {
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
}
|
||||
|
||||
const ImageViewerEditorComponent = ({
|
||||
value,
|
||||
options,
|
||||
|
|
@ -130,12 +115,15 @@ const ImageViewerEditorComponent = ({
|
|||
<button
|
||||
key={`${url}-${index}`}
|
||||
type="button"
|
||||
onClick={() => openImage(url)}
|
||||
onClick={() => openImageInNewTab(url)}
|
||||
style={{ border: 0, background: 'transparent', padding: 0, cursor: 'zoom-in' }}
|
||||
>
|
||||
<img
|
||||
src={url}
|
||||
alt=""
|
||||
onMouseEnter={(event) => showImageHoverPreview(url, event)}
|
||||
onMouseMove={(event) => showImageHoverPreview(url, event)}
|
||||
onMouseLeave={hideImageHoverPreview}
|
||||
style={{
|
||||
width: thumbW,
|
||||
height: thumbH,
|
||||
|
|
|
|||
90
ui/src/views/form/editors/imageHoverPreview.ts
Normal file
90
ui/src/views/form/editors/imageHoverPreview.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
const NO_IMAGE = '/img/others/no-image.png'
|
||||
|
||||
let previewElement: HTMLDivElement | null = null
|
||||
|
||||
const getPreviewElement = () => {
|
||||
if (!previewElement) {
|
||||
const element = document.createElement('div')
|
||||
element.style.cssText = [
|
||||
'position:fixed',
|
||||
'z-index:99999',
|
||||
'display:none',
|
||||
'pointer-events:none',
|
||||
'background:#fff',
|
||||
'border:1px solid #d1d5db',
|
||||
'border-radius:8px',
|
||||
'box-shadow:0 8px 32px rgba(0,0,0,0.22)',
|
||||
'padding:4px',
|
||||
'max-width:320px',
|
||||
'max-height:320px',
|
||||
'overflow:hidden',
|
||||
'transition:opacity 0.15s ease',
|
||||
'opacity:0',
|
||||
].join(';')
|
||||
|
||||
const image = document.createElement('img')
|
||||
image.src = NO_IMAGE
|
||||
image.style.cssText =
|
||||
'display:block;max-width:312px;max-height:312px;object-fit:contain;border-radius:4px;'
|
||||
element.appendChild(image)
|
||||
document.body.appendChild(element)
|
||||
previewElement = element
|
||||
}
|
||||
|
||||
return previewElement
|
||||
}
|
||||
|
||||
export const showImageHoverPreview = (src: string, event: React.MouseEvent<HTMLElement>) => {
|
||||
const element = getPreviewElement()
|
||||
const image = element.querySelector('img') as HTMLImageElement
|
||||
image.onerror = () => {
|
||||
image.onerror = null
|
||||
image.src = NO_IMAGE
|
||||
}
|
||||
if (image.src !== src) image.src = src
|
||||
|
||||
const gap = 12
|
||||
element.style.opacity = '0'
|
||||
element.style.display = 'block'
|
||||
|
||||
const width = element.offsetWidth || 320
|
||||
const height = element.offsetHeight || 320
|
||||
let left = event.clientX + gap
|
||||
let top = event.clientY + gap
|
||||
|
||||
if (left + width > window.innerWidth - 8) left = event.clientX - width - gap
|
||||
if (top + height > window.innerHeight - 8) top = event.clientY - height - gap
|
||||
if (left < 8) left = 8
|
||||
if (top < 8) top = 8
|
||||
|
||||
element.style.left = `${left}px`
|
||||
element.style.top = `${top}px`
|
||||
element.style.opacity = '1'
|
||||
}
|
||||
|
||||
export const hideImageHoverPreview = () => {
|
||||
if (previewElement) {
|
||||
previewElement.style.opacity = '0'
|
||||
previewElement.style.display = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
export const openImageInNewTab = (url: string) => {
|
||||
if (!url.startsWith('data:image/')) {
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const match = url.match(/^data:(image\/[^;]+);base64,(.*)$/)
|
||||
if (!match) throw new Error('Unsupported image data URL')
|
||||
|
||||
const binary = atob(match[2])
|
||||
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0))
|
||||
const blobUrl = URL.createObjectURL(new Blob([bytes], { type: match[1] }))
|
||||
window.open(blobUrl, '_blank', 'noopener,noreferrer')
|
||||
window.setTimeout(() => URL.revokeObjectURL(blobUrl), 60000)
|
||||
} catch {
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,10 @@
|
|||
import { ReactElement, useRef, useState } from 'react'
|
||||
import { FaSpinner, FaUpload } from 'react-icons/fa'
|
||||
import {
|
||||
hideImageHoverPreview,
|
||||
openImageInNewTab,
|
||||
showImageHoverPreview,
|
||||
} from '../../form/editors/imageHoverPreview'
|
||||
|
||||
const ImageUploadEditorComponent = (cellElement: any): ReactElement => {
|
||||
const col = cellElement.column
|
||||
|
|
@ -101,12 +106,17 @@ const ImageUploadEditorComponent = (cellElement: any): ReactElement => {
|
|||
<img
|
||||
src={url}
|
||||
alt=""
|
||||
onMouseEnter={(event) => showImageHoverPreview(url, event)}
|
||||
onMouseMove={(event) => showImageHoverPreview(url, event)}
|
||||
onMouseLeave={hideImageHoverPreview}
|
||||
onClick={() => openImageInNewTab(url)}
|
||||
style={{
|
||||
width: thumbW,
|
||||
height: thumbH,
|
||||
objectFit: 'cover',
|
||||
borderRadius: 4,
|
||||
border: '1px solid #ddd',
|
||||
cursor: 'zoom-in',
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import { ReactElement } from 'react'
|
||||
import {
|
||||
hideImageHoverPreview,
|
||||
openImageInNewTab,
|
||||
showImageHoverPreview,
|
||||
} from '../../form/editors/imageHoverPreview'
|
||||
|
||||
const parseJsonObject = (value: unknown) => {
|
||||
if (!value) return undefined
|
||||
|
|
@ -85,26 +90,6 @@ const normalizeImageValue = (value: unknown): string[] => {
|
|||
return source ? [toImageSource(source)] : []
|
||||
}
|
||||
|
||||
const openImage = (url: string) => {
|
||||
if (!url.startsWith('data:image/')) {
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const match = url.match(/^data:(image\/[^;]+);base64,(.*)$/)
|
||||
if (!match) return
|
||||
|
||||
const binary = atob(match[2])
|
||||
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0))
|
||||
const blobUrl = URL.createObjectURL(new Blob([bytes], { type: match[1] }))
|
||||
window.open(blobUrl, '_blank', 'noopener,noreferrer')
|
||||
window.setTimeout(() => URL.revokeObjectURL(blobUrl), 60000)
|
||||
} catch {
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
}
|
||||
|
||||
const resolveTemplateValue = (templateData: any) => {
|
||||
const dataField =
|
||||
templateData?.dataField ||
|
||||
|
|
@ -174,12 +159,15 @@ const ImageViewerEditorComponent = (templateData: any): ReactElement => {
|
|||
<button
|
||||
key={`${url}-${index}`}
|
||||
type="button"
|
||||
onClick={() => openImage(url)}
|
||||
onClick={() => openImageInNewTab(url)}
|
||||
style={{ border: 0, background: 'transparent', padding: 0, cursor: 'zoom-in' }}
|
||||
>
|
||||
<img
|
||||
src={url}
|
||||
alt=""
|
||||
onMouseEnter={(event) => showImageHoverPreview(url, event)}
|
||||
onMouseMove={(event) => showImageHoverPreview(url, event)}
|
||||
onMouseLeave={hideImageHoverPreview}
|
||||
style={{
|
||||
width: thumbW,
|
||||
height: thumbH,
|
||||
|
|
|
|||
Loading…
Reference in a new issue