Skip to content

Commit

Permalink
feat(viewer): add AnnotationDrawer and AnnotationViewer Component
Browse files Browse the repository at this point in the history
  • Loading branch information
LTakhyunKim committed Feb 15, 2023
1 parent 0df8f0c commit 91cfbe4
Show file tree
Hide file tree
Showing 4 changed files with 398 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { SVGProps } from 'react'
import type { Annotation, AnnotationMode } from '../../types'

export interface AnnotationDrawerProps extends SVGProps<SVGSVGElement> {
selectedAnnotation: Annotation | null
hoveredAnnotation: Annotation | null
annotations: Annotation[]

isDrawing?: boolean
isEditing?: boolean

/**
* annotation label notation flag variable
* Default value is false
*/
showAnnotationLabel?: boolean
/** When drawing is complete and a new annotation occurs */
onAdd: (annotation: Annotation) => void
onSelectAnnotation: (annotation: Annotation | null) => void

mode?: AnnotationMode
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import React, { useRef, useState } from 'react'

import { useOverlayContext } from '../../../contexts'

import { svgRootStyle } from '../../../Viewer/Viewer.styles'

import { AreaDrawer } from '../AreaDrawer'
import { TextDrawer } from '../TextDrawer'
import { RulerDrawer } from '../RulerDrawer'
import { PolylineDrawer } from '../PolylineDrawer'

import { TypingDrawer } from '../../../Viewer/TextDrawer'
import { EditPointer } from '../../../components/EditPointer'

import useEditMode from '../../hooks/useEditMode'
import useDrawingHandler from '../../../hooks/useDrawingHandler'
import useCreatingAnnotation from '../../hooks/useCreatingAnnotation'
import useCreatingDrawableAnnotation from '../../hooks/useCreatingDrawableAnnotation'

import { getEditPointPosition } from '../../utils/getEditPointPosition'

import type { Annotation, Point, TextAnnotation } from '../../types'
import type { AnnotationDrawerProps } from './AnnotationDrawer.types'

export function AnnotationDrawer({
style,
width,
height,
isDrawing = true,
isEditing = false,
showAnnotationLabel = false,
annotations,
selectedAnnotation,
hoveredAnnotation,
className,
mode = 'polygon',
onAdd,
onSelectAnnotation,
}: AnnotationDrawerProps): JSX.Element {
const svgRef = useRef<SVGSVGElement>(null)
const isSelectedAnnotation = Boolean(isEditing && selectedAnnotation)

const [tempAnnotation, setTempAnnotation] = useState<TextAnnotation>()
const handleTypingFinish = (text: string) => {
setTempAnnotation(undefined)
if (tempAnnotation && text !== '') {
onAdd({ ...tempAnnotation, label: text })
}
}

const { image, pixelToCanvas } = useOverlayContext()
const { editMode, updateEditMode, clearEditMode } = useEditMode()

const {
annotation,
movedStartPoint,
drawingStartPoint,
clearAnnotation,
setDrawingMovedPoint,
setInitialAnnotation,
setInitialDrawingPoints,
updateDrawingAnnotation,
clearDrawingAndMovedPoints,
} = useCreatingAnnotation({
mode,
image,
editMode,
selectedAnnotation,
id: annotations.length === 0 ? 1 : Math.max(...annotations.map(({ id }) => id), 0) + 1,
})

const { drawableAnnotation } = useCreatingDrawableAnnotation({
annotation,
pixelToCanvas,
})

const canvasEditingPoints =
movedStartPoint && drawingStartPoint
? ([drawingStartPoint, movedStartPoint].map(pixelToCanvas) as [Point, Point])
: null

const annotationEditPoints = getEditPointPosition({
annotation: drawableAnnotation,
editingPoints: canvasEditingPoints,
isDefaultEditPointsOfAnnotation: isSelectedAnnotation && (!editMode || editMode === 'move'),
})

// TODO: 이벤트를 분리할 수 있는 방법 고민
useDrawingHandler({
svgElement: svgRef,
hoveredDrawing: hoveredAnnotation,
setInitialPoint: (point: Point) => {
if (!isDrawing && isSelectedAnnotation) return

setInitialAnnotation(point)
setInitialDrawingPoints(point)
},
addDrawingPoint: (point) => {
if (isDrawing && isSelectedAnnotation && !editMode) return

updateDrawingAnnotation(point)
setDrawingMovedPoint(point)
},
cancelDrawing: () => {
if (isSelectedAnnotation && editMode) {
clearEditMode()
clearDrawingAndMovedPoints()
return
}
clearAnnotation()
clearEditMode()
onSelectAnnotation(null)
clearDrawingAndMovedPoints()
},
addDrewElement: () => {
if (annotation == null) return

if (annotation.type === 'text' && !annotation.label) {
// TODO: 추후 TextAnnotation 재설계 후 아래 주석과 코드 정리할 것.
// a 의 좌표가 유효하지 않을경우 setTempAnnotation 이 아예 실행되지 않도록 로직 추가
// Typing.tsx 에도 비슷한 코드가 존재하나 혹시 모를 사이드 이펙트를 고려하여 남겨둠
const [start, end] = annotation.points

if (typeof end === 'undefined' || end[0] < start[0] || end[1] < start[1]) {
return
}

if (!tempAnnotation) {
setTempAnnotation(annotation)
}

return
}

onAdd(annotation as Annotation)
},
})

return (
<>
{drawableAnnotation && (
<svg
ref={svgRef}
width={width}
height={height}
style={{ ...svgRootStyle.default, ...style }}
className={className}
>
{(drawableAnnotation.type === 'polygon' ||
drawableAnnotation.type === 'freeLine' ||
drawableAnnotation.type === 'line' ||
drawableAnnotation.type === 'arrowLine') && (
<PolylineDrawer
annotation={drawableAnnotation}
isSelectedMode={isSelectedAnnotation}
showAnnotationLabel={showAnnotationLabel}
isPolygonSelected={selectedAnnotation?.type === 'polygon'}
selectedAnnotationLabel={selectedAnnotation ? selectedAnnotation.label ?? selectedAnnotation.id : null}
setAnnotationEditMode={updateEditMode}
/>
)}
{drawableAnnotation.type === 'ruler' && drawableAnnotation.measuredValue !== 0 && (
<RulerDrawer
isSelectedMode={isSelectedAnnotation}
annotation={drawableAnnotation}
setAnnotationEditMode={updateEditMode}
/>
)}
{drawableAnnotation.type === 'area' && drawableAnnotation.measuredValue !== 0 && (
<AreaDrawer
isSelectedMode={isSelectedAnnotation}
annotation={drawableAnnotation}
setAnnotationEditMode={updateEditMode}
/>
)}
{drawableAnnotation.type === 'text' && (
<TextDrawer annotation={drawableAnnotation} setAnnotationEditMode={updateEditMode} />
)}
{annotationEditPoints && (
<>
<EditPointer
setEditMode={updateEditMode}
editMode="startPoint"
isSelectedMode={false}
isHighlightMode={isSelectedAnnotation}
editPoint={annotationEditPoints[0]}
cursorStatus={'drawing'}
/>
<EditPointer
setEditMode={updateEditMode}
editMode="endPoint"
isHighlightMode={isSelectedAnnotation}
isSelectedMode={Boolean(isSelectedAnnotation && editMode && editMode !== 'move')}
editPoint={annotationEditPoints[1]}
cursorStatus={'drawing'}
/>
</>
)}
</svg>
)}
{tempAnnotation && (
<svg width={width} height={height} style={{ ...svgRootStyle.default, ...style }}>
<TypingDrawer points={tempAnnotation.points} onFinish={handleTypingFinish} />
</svg>
)}
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { CSSProperties } from 'react'
import type { Annotation } from '../../types'

export interface AnnotationsViewerProps {
width?: number
height?: number

/** Annotation focused by user interaction such as mouse over */
elements: Annotation[]

hoveredElement: Annotation | null
selectedElement: Annotation | null

/** <svg className={}> */
className?: string

/** <svg style={}> */
style?: CSSProperties

isEditing?: boolean

onFocus?: (element: Annotation | null) => void
onClick?: (element: Annotation) => void

/**
* Draw an outline on the line
* Since the outline is expressed by drawing two lines, it can be deactivated in a performance-sensitive situation
*/
showOutline?: boolean

/**
* annotation label notation flag variable
* Default value is false
*/
showElementLabel?: boolean
}

export interface AnnotationViewerProps
extends Omit<AnnotationsViewerProps, 'width' | 'height' | 'selectedElement' | 'elements'> {
element: Annotation
showOutline: boolean
showElementLabel: boolean
}
Loading

0 comments on commit 91cfbe4

Please sign in to comment.