ImageUploadEditorComponent ve ImageViewerEditorComponent

Bu komponentler için tıklanabilir ve preview hale getirildi.
This commit is contained in:
Sedat Öztürk 2026-06-21 21:16:54 +03:00
parent da19e48018
commit 707e835570
5 changed files with 128 additions and 42 deletions

View file

@ -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 && (

View file

@ -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,

View 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')
}
}

View file

@ -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

View file

@ -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,