Refactor Stage Context to redux (#7)
* feat: setup stage slice * refact: replace useStage() with redux
This commit is contained in:
parent
095c1ca8ec
commit
806f4a28e4
@ -1,13 +1,14 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
import Konva from 'konva'
|
import Konva from 'konva'
|
||||||
import { Group, Rect } from 'react-konva'
|
import { Group, Rect } from 'react-konva'
|
||||||
import { KonvaEventObject } from 'konva/lib/Node'
|
import { KonvaEventObject } from 'konva/lib/Node'
|
||||||
import { entities } from '../../wailsjs/wailsjs/go/models'
|
import { entities } from '../../wailsjs/wailsjs/go/models'
|
||||||
import { useProject } from '../../context/Project/provider'
|
import { useProject } from '../../context/Project/provider'
|
||||||
import AreaContextMenu from './AreaContextMenu'
|
import AreaContextMenu from './AreaContextMenu'
|
||||||
import { useStage } from './context/provider'
|
import { RootState } from '../../redux/store'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isActive: boolean,
|
isActive: boolean,
|
||||||
@ -17,8 +18,8 @@ type Props = {
|
|||||||
type coordinates = { x: number, y: number }
|
type coordinates = { x: number, y: number }
|
||||||
|
|
||||||
const Area = (props: Props) => {
|
const Area = (props: Props) => {
|
||||||
|
const { scale } = useSelector((state: RootState) => state.stage)
|
||||||
const { selectedAreaId, setSelectedAreaId } = useProject()
|
const { selectedAreaId, setSelectedAreaId } = useProject()
|
||||||
const { scale } = useStage()
|
|
||||||
const shapeRef = React.useRef<Konva.Rect>(null)
|
const shapeRef = React.useRef<Konva.Rect>(null)
|
||||||
const [isAreaContextMenuOpen, setIsAreaContextMenuOpen] = useState(false)
|
const [isAreaContextMenuOpen, setIsAreaContextMenuOpen] = useState(false)
|
||||||
const [areaContextMenuPosition, setAreaContextMenuPosition] = useState<coordinates>()
|
const [areaContextMenuPosition, setAreaContextMenuPosition] = useState<coordinates>()
|
||||||
|
|||||||
@ -1,19 +1,20 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
import { Group } from 'react-konva'
|
import { Group } from 'react-konva'
|
||||||
import { useProject } from '../../context/Project/provider'
|
import { useProject } from '../../context/Project/provider'
|
||||||
import { entities } from '../../wailsjs/wailsjs/go/models'
|
import { entities } from '../../wailsjs/wailsjs/go/models'
|
||||||
import Area from './Area'
|
import Area from './Area'
|
||||||
import ProcessedWord from './ProcessedWord'
|
import ProcessedWord from './ProcessedWord'
|
||||||
import EditingWord from './EditingWord'
|
import EditingWord from './EditingWord'
|
||||||
import { useStage } from './context/provider'
|
import { RootState } from '../../redux/store'
|
||||||
|
|
||||||
type Props = { scale: number }
|
type Props = { scale: number }
|
||||||
|
|
||||||
const Areas = ({ scale }: Props) => {
|
const Areas = ({ scale }: Props) => {
|
||||||
|
const { areProcessedWordsVisible } = useSelector((state: RootState) => state.stage)
|
||||||
const { getSelectedDocument, selectedAreaId, getProcessedAreaById } = useProject()
|
const { getSelectedDocument, selectedAreaId, getProcessedAreaById } = useProject()
|
||||||
const { isProcessedWordsVisible } = useStage()
|
|
||||||
const areas = getSelectedDocument()?.areas || []
|
const areas = getSelectedDocument()?.areas || []
|
||||||
const [editingWord, setEditingWord] = useState<entities.ProcessedWord | null>(null)
|
const [editingWord, setEditingWord] = useState<entities.ProcessedWord | null>(null)
|
||||||
const [selectedProcessedArea, setSelectedProcessedArea] = useState<entities.ProcessedArea | null>(null)
|
const [selectedProcessedArea, setSelectedProcessedArea] = useState<entities.ProcessedArea | null>(null)
|
||||||
@ -59,8 +60,8 @@ const Areas = ({ scale }: Props) => {
|
|||||||
|
|
||||||
return <Group>
|
return <Group>
|
||||||
{renderAreas(areas)}
|
{renderAreas(areas)}
|
||||||
{isProcessedWordsVisible ? renderProcessedWords() : <></>}
|
{areProcessedWordsVisible ? renderProcessedWords() : <></>}
|
||||||
{isProcessedWordsVisible ? renderEditingWord() : <></>}
|
{areProcessedWordsVisible ? renderEditingWord() : <></>}
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useRef, useState } from 'react'
|
import React, { useRef, useState } from 'react'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { Stage, Layer, Image, } from 'react-konva'
|
import { Stage, Layer, Image, } from 'react-konva'
|
||||||
import { KonvaEventObject } from 'konva/lib/Node'
|
import { KonvaEventObject } from 'konva/lib/Node'
|
||||||
import Areas from './Areas'
|
import Areas from './Areas'
|
||||||
@ -9,16 +10,25 @@ import useImage from 'use-image'
|
|||||||
import { RectangleCoordinates } from './types'
|
import { RectangleCoordinates } from './types'
|
||||||
import DrawingArea from './DrawingArea'
|
import DrawingArea from './DrawingArea'
|
||||||
import getNormalizedRectToBounds from '../../utils/getNormalizedRectToBounds'
|
import getNormalizedRectToBounds from '../../utils/getNormalizedRectToBounds'
|
||||||
import { useStage } from './context/provider'
|
|
||||||
import ContextConnections from './ContextConnections'
|
import ContextConnections from './ContextConnections'
|
||||||
import processImageRect from '../../useCases/processImageRect'
|
import processImageRect from '../../useCases/processImageRect'
|
||||||
|
import { RootState } from '../../redux/store'
|
||||||
|
import { maxScale, scaleStep, setIsDrawingArea, setScale, setStartingContextConnectionPoint } from '../../redux/features/stage/stageSlice'
|
||||||
|
|
||||||
let downClickX: number
|
let downClickX: number
|
||||||
let downClickY: number
|
let downClickY: number
|
||||||
|
|
||||||
const CanvasStage = () => {
|
const CanvasStage = () => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const {
|
||||||
|
scale, size,
|
||||||
|
isDrawingArea,
|
||||||
|
areAreasVisible,
|
||||||
|
areLinkAreaContextsVisible,
|
||||||
|
startingContextConnectionPoint
|
||||||
|
} = useSelector((state: RootState) => state.stage)
|
||||||
|
|
||||||
const { getSelectedDocument, updateDocuments, setSelectedAreaId } = useProject()
|
const { getSelectedDocument, updateDocuments, setSelectedAreaId } = useProject()
|
||||||
const { scale, scaleStep, maxScale, size, setScale, isAreasVisible, isLinkAreaContextsVisible, isDrawingArea, setIsDrawingArea, startingContextConnection, setStartingContextConnection } = useStage()
|
|
||||||
const [documentImage] = useImage(getSelectedDocument()?.path || '')
|
const [documentImage] = useImage(getSelectedDocument()?.path || '')
|
||||||
const documentRef = useRef(null)
|
const documentRef = useRef(null)
|
||||||
const [drawingAreaRect, setDrawingAreaRect] = useState<RectangleCoordinates | null>(null)
|
const [drawingAreaRect, setDrawingAreaRect] = useState<RectangleCoordinates | null>(null)
|
||||||
@ -27,13 +37,13 @@ const CanvasStage = () => {
|
|||||||
const documentHeight = documentImage?.naturalHeight || 0
|
const documentHeight = documentImage?.naturalHeight || 0
|
||||||
|
|
||||||
const handleMouseDown = (e: KonvaEventObject<MouseEvent>) => {
|
const handleMouseDown = (e: KonvaEventObject<MouseEvent>) => {
|
||||||
if (startingContextConnection) return setStartingContextConnection(null) // TODO: handle if clicking o connect
|
if (startingContextConnectionPoint) return dispatch(setStartingContextConnectionPoint(null)) // TODO: handle if clicking o connect
|
||||||
if (!e.evt.shiftKey) return e.currentTarget.startDrag()
|
if (!e.evt.shiftKey) return e.currentTarget.startDrag()
|
||||||
|
|
||||||
const position = e.currentTarget.getRelativePointerPosition()
|
const position = e.currentTarget.getRelativePointerPosition()
|
||||||
downClickX = position.x
|
downClickX = position.x
|
||||||
downClickY = position.y
|
downClickY = position.y
|
||||||
setIsDrawingArea(true)
|
dispatch(setIsDrawingArea(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseMove = (e: KonvaEventObject<MouseEvent>) => {
|
const handleMouseMove = (e: KonvaEventObject<MouseEvent>) => {
|
||||||
@ -49,7 +59,7 @@ const CanvasStage = () => {
|
|||||||
const handleMouseUp = (e: KonvaEventObject<MouseEvent>) => {
|
const handleMouseUp = (e: KonvaEventObject<MouseEvent>) => {
|
||||||
const stage = e.currentTarget
|
const stage = e.currentTarget
|
||||||
if (stage.isDragging()) stage.stopDrag()
|
if (stage.isDragging()) stage.stopDrag()
|
||||||
else if (isDrawingArea) setIsDrawingArea(false)
|
else if (isDrawingArea) dispatch(setIsDrawingArea(false))
|
||||||
|
|
||||||
if (!drawingAreaRect) return
|
if (!drawingAreaRect) return
|
||||||
|
|
||||||
@ -70,8 +80,8 @@ const CanvasStage = () => {
|
|||||||
const wheelDelta = e.evt.deltaY
|
const wheelDelta = e.evt.deltaY
|
||||||
|
|
||||||
const shouldAttemptScaleUp = (wheelDelta < 0) && scale < maxScale
|
const shouldAttemptScaleUp = (wheelDelta < 0) && scale < maxScale
|
||||||
if (shouldAttemptScaleUp) setScale(scale + scaleStep)
|
if (shouldAttemptScaleUp) dispatch(setScale(scale + scaleStep))
|
||||||
else if (scale > (scaleStep * 2)) setScale(scale - scaleStep)
|
else if (scale > (scaleStep * 2)) dispatch(setScale(scale - scaleStep))
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Stage width={size.width} height={size.height} scale={{ x: scale, y: scale }} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} onWheel={handleWheel}>
|
return <Stage width={size.width} height={size.height} scale={{ x: scale, y: scale }} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} onWheel={handleWheel}>
|
||||||
@ -90,13 +100,13 @@ const CanvasStage = () => {
|
|||||||
/>
|
/>
|
||||||
{(isDrawingArea && drawingAreaRect) ? <DrawingArea rect={drawingAreaRect} /> : <></>}
|
{(isDrawingArea && drawingAreaRect) ? <DrawingArea rect={drawingAreaRect} /> : <></>}
|
||||||
</Layer>
|
</Layer>
|
||||||
{isAreasVisible
|
{areAreasVisible
|
||||||
? <Layer id='areaLayer'>
|
? <Layer id='areaLayer'>
|
||||||
<Areas scale={scale} />
|
<Areas scale={scale} />
|
||||||
</Layer>
|
</Layer>
|
||||||
: <></>
|
: <></>
|
||||||
}
|
}
|
||||||
{isAreasVisible && isLinkAreaContextsVisible
|
{areAreasVisible && areLinkAreaContextsVisible
|
||||||
? <Layer id='contextConnections'>
|
? <Layer id='contextConnections'>
|
||||||
<ContextConnections />
|
<ContextConnections />
|
||||||
</Layer>
|
</Layer>
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
import { Group, Line } from 'react-konva'
|
import { Group, Line } from 'react-konva'
|
||||||
import { useProject } from '../../../context/Project/provider'
|
import { useProject } from '../../../context/Project/provider'
|
||||||
import { useStage } from '../context/provider'
|
import { RootState } from '../../../redux/store'
|
||||||
|
|
||||||
const ConnectionLines = () => {
|
const ConnectionLines = () => {
|
||||||
const { scale } = useStage()
|
const { scale } = useSelector((state: RootState) => state.stage)
|
||||||
const { getSelectedDocument, contextGroups } = useProject()
|
const { getSelectedDocument, contextGroups } = useProject()
|
||||||
const areas = getSelectedDocument()?.areas || []
|
const areas = getSelectedDocument()?.areas || []
|
||||||
|
|
||||||
const renderLines = () => {
|
const renderLines = () => {
|
||||||
console.log('contextGroups', contextGroups)
|
|
||||||
if (!contextGroups?.length) return <></>
|
if (!contextGroups?.length) return <></>
|
||||||
|
|
||||||
const linesAlreadyRendered = new Set<string>()
|
const linesAlreadyRendered = new Set<string>()
|
||||||
|
|||||||
@ -1,14 +1,18 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { Circle, Group } from 'react-konva'
|
import { Circle, Group } from 'react-konva'
|
||||||
import { useStage } from '../context/provider'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
||||||
import { KonvaEventObject } from 'konva/lib/Node'
|
import { KonvaEventObject } from 'konva/lib/Node'
|
||||||
import { useProject } from '../../../context/Project/provider'
|
import { useProject } from '../../../context/Project/provider'
|
||||||
|
import { RootState } from '../../../redux/store'
|
||||||
|
import { setStartingContextConnectionPoint } from '../../../redux/features/stage/stageSlice'
|
||||||
|
|
||||||
type Props = { areas: entities.Area[] }
|
type Props = { areas: entities.Area[] }
|
||||||
const ConnectionPoints = (props: Props) => {
|
const ConnectionPoints = (props: Props) => {
|
||||||
const { isLinkAreaContextsVisible, scale, startingContextConnection, setStartingContextConnection } = useStage()
|
const dispatch = useDispatch()
|
||||||
|
const { scale, areLinkAreaContextsVisible, startingContextConnectionPoint } = useSelector((state: RootState) => state.stage)
|
||||||
|
|
||||||
const { requestConnectProcessedAreas } = useProject()
|
const { requestConnectProcessedAreas } = useProject()
|
||||||
|
|
||||||
const handleContextAreaMouseDown = async (e: KonvaEventObject<MouseEvent>) => {
|
const handleContextAreaMouseDown = async (e: KonvaEventObject<MouseEvent>) => {
|
||||||
@ -18,15 +22,15 @@ const ConnectionPoints = (props: Props) => {
|
|||||||
areaId: e.currentTarget.attrs.id
|
areaId: e.currentTarget.attrs.id
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!startingContextConnection) return setStartingContextConnection(clickedConnectionPoint)
|
if (!startingContextConnectionPoint) return dispatch(setStartingContextConnectionPoint(clickedConnectionPoint))
|
||||||
|
|
||||||
if (clickedConnectionPoint.isHead === startingContextConnection.isHead
|
if (clickedConnectionPoint.isHead === startingContextConnectionPoint.isHead
|
||||||
|| clickedConnectionPoint.areaId === startingContextConnection.areaId)
|
|| clickedConnectionPoint.areaId === startingContextConnectionPoint.areaId)
|
||||||
return setStartingContextConnection(null)
|
return dispatch(setStartingContextConnectionPoint(null))
|
||||||
|
|
||||||
const headId = startingContextConnection.isHead ? startingContextConnection.areaId : clickedConnectionPoint.areaId
|
const headId = startingContextConnectionPoint.isHead ? startingContextConnectionPoint.areaId : clickedConnectionPoint.areaId
|
||||||
const tailId = !startingContextConnection.isHead ? startingContextConnection.areaId : clickedConnectionPoint.areaId
|
const tailId = !startingContextConnectionPoint.isHead ? startingContextConnectionPoint.areaId : clickedConnectionPoint.areaId
|
||||||
setStartingContextConnection(null)
|
dispatch(setStartingContextConnectionPoint(null))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await requestConnectProcessedAreas(headId, tailId)
|
await requestConnectProcessedAreas(headId, tailId)
|
||||||
@ -36,7 +40,7 @@ const ConnectionPoints = (props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderConnectingPointsForArea = (a: entities.Area) => {
|
const renderConnectingPointsForArea = (a: entities.Area) => {
|
||||||
if (!isLinkAreaContextsVisible) return <></>
|
if (!areLinkAreaContextsVisible) return <></>
|
||||||
|
|
||||||
const headConnector = <Circle
|
const headConnector = <Circle
|
||||||
key={`head-${a.id}`}
|
key={`head-${a.id}`}
|
||||||
@ -68,12 +72,12 @@ const ConnectionPoints = (props: Props) => {
|
|||||||
|
|
||||||
let connectorsToRender = []
|
let connectorsToRender = []
|
||||||
|
|
||||||
if (!startingContextConnection) connectorsToRender = [headConnector, tailConnector]
|
if (!startingContextConnectionPoint) connectorsToRender = [headConnector, tailConnector]
|
||||||
else if (startingContextConnection.isHead) connectorsToRender = [tailConnector]
|
else if (startingContextConnectionPoint.isHead) connectorsToRender = [tailConnector]
|
||||||
else connectorsToRender = [headConnector]
|
else connectorsToRender = [headConnector]
|
||||||
|
|
||||||
if (startingContextConnection?.areaId === a.id) {
|
if (startingContextConnectionPoint?.areaId === a.id) {
|
||||||
let y = (startingContextConnection.isHead ? a.startY : a.endY) * scale
|
let y = (startingContextConnectionPoint.isHead ? a.startY : a.endY) * scale
|
||||||
connectorsToRender.push(<Circle
|
connectorsToRender.push(<Circle
|
||||||
key={`active-${a.id}`}
|
key={`active-${a.id}`}
|
||||||
id={a.id}
|
id={a.id}
|
||||||
@ -81,11 +85,11 @@ const ConnectionPoints = (props: Props) => {
|
|||||||
x={((a.startX + a.endX) * scale) / 2}
|
x={((a.startX + a.endX) * scale) / 2}
|
||||||
y={y}
|
y={y}
|
||||||
strokeEnabled={false}
|
strokeEnabled={false}
|
||||||
fill={startingContextConnection.isHead ? '#dc8dec' : '#1e1e1e'}
|
fill={startingContextConnectionPoint.isHead ? '#dc8dec' : '#1e1e1e'}
|
||||||
strokeScaleEnabled={false}
|
strokeScaleEnabled={false}
|
||||||
shadowForStrokeEnabled={false}
|
shadowForStrokeEnabled={false}
|
||||||
isHead={startingContextConnection.isHead}
|
isHead={startingContextConnectionPoint.isHead}
|
||||||
onMouseDown={() => setStartingContextConnection(null)}
|
onMouseDown={() => dispatch(setStartingContextConnectionPoint(null))}
|
||||||
/>)
|
/>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,23 +1,24 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
import { Line } from 'react-konva'
|
import { Line } from 'react-konva'
|
||||||
import { Coordinates } from '../types'
|
import { Coordinates } from '../types'
|
||||||
import { useStage } from '../context/provider'
|
|
||||||
import { useProject } from '../../../context/Project/provider'
|
import { useProject } from '../../../context/Project/provider'
|
||||||
|
import { RootState } from '../../../redux/store'
|
||||||
|
|
||||||
type CurrentDrawingConnectionProps = {
|
type CurrentDrawingConnectionProps = {
|
||||||
endDrawingPosition: Coordinates | null
|
endDrawingPosition: Coordinates | null
|
||||||
}
|
}
|
||||||
const CurrentDrawingConnection = (props: CurrentDrawingConnectionProps) => {
|
const CurrentDrawingConnection = (props: CurrentDrawingConnectionProps) => {
|
||||||
|
const { scale, startingContextConnectionPoint } = useSelector((state: RootState) => state.stage)
|
||||||
const { endDrawingPosition } = props
|
const { endDrawingPosition } = props
|
||||||
const { startingContextConnection, scale } = useStage()
|
|
||||||
const { getSelectedDocument } = useProject()
|
const { getSelectedDocument } = useProject()
|
||||||
const areas = getSelectedDocument()?.areas || []
|
const areas = getSelectedDocument()?.areas || []
|
||||||
|
|
||||||
if (!startingContextConnection || !endDrawingPosition) return <></>
|
if (!startingContextConnectionPoint || !endDrawingPosition) return <></>
|
||||||
|
|
||||||
const { areaId, isHead } = startingContextConnection
|
const { areaId, isHead } = startingContextConnectionPoint
|
||||||
|
|
||||||
const area = areas.find(a => a.id === areaId)
|
const area = areas.find(a => a.id === areaId)
|
||||||
if (!area) return <></>
|
if (!area) return <></>
|
||||||
|
|||||||
@ -1,24 +1,26 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
import { Group } from 'react-konva'
|
import { Group } from 'react-konva'
|
||||||
import { useStage } from '../context/provider'
|
|
||||||
import { useProject } from '../../../context/Project/provider'
|
import { useProject } from '../../../context/Project/provider'
|
||||||
import Konva from 'konva'
|
import Konva from 'konva'
|
||||||
import { Coordinates } from '../types'
|
import { Coordinates } from '../types'
|
||||||
import CurrentDrawingConnection from './CurrentDrawingConnection'
|
import CurrentDrawingConnection from './CurrentDrawingConnection'
|
||||||
import ConnectionPoints from './ConnectionPoints'
|
import ConnectionPoints from './ConnectionPoints'
|
||||||
import ConnectionLines from './ConnectionLines'
|
import ConnectionLines from './ConnectionLines'
|
||||||
|
import { RootState } from '../../../redux/store'
|
||||||
|
|
||||||
const ContextConnections = () => {
|
const ContextConnections = () => {
|
||||||
|
const { startingContextConnectionPoint, areLinkAreaContextsVisible } = useSelector((state: RootState) => state.stage)
|
||||||
|
|
||||||
const { getSelectedDocument } = useProject()
|
const { getSelectedDocument } = useProject()
|
||||||
const { isLinkAreaContextsVisible, startingContextConnection, scale } = useStage()
|
|
||||||
const areas = getSelectedDocument()?.areas || []
|
const areas = getSelectedDocument()?.areas || []
|
||||||
|
|
||||||
const [endDrawingPosition, setEndDrawingPosition] = useState<Coordinates | null>(null)
|
const [endDrawingPosition, setEndDrawingPosition] = useState<Coordinates | null>(null)
|
||||||
|
|
||||||
const handleMouseMove = (e: MouseEvent) => {
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
if (!isLinkAreaContextsVisible || !startingContextConnection) return
|
if (!areLinkAreaContextsVisible || !startingContextConnectionPoint) return
|
||||||
setEndDrawingPosition(Konva.stages[0].getRelativePointerPosition())
|
setEndDrawingPosition(Konva.stages[0].getRelativePointerPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,10 +30,10 @@ const ContextConnections = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!startingContextConnection) setEndDrawingPosition(null)
|
if (!startingContextConnectionPoint) setEndDrawingPosition(null)
|
||||||
}, [startingContextConnection])
|
}, [startingContextConnectionPoint])
|
||||||
|
|
||||||
if (!isLinkAreaContextsVisible) return <></>
|
if (!areLinkAreaContextsVisible) return <></>
|
||||||
|
|
||||||
return <Group>
|
return <Group>
|
||||||
<ConnectionPoints areas={areas} />
|
<ConnectionPoints areas={areas} />
|
||||||
|
|||||||
@ -1,27 +1,29 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { DocumentTextIcon, LanguageIcon, LinkIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, SquaresPlusIcon } from '@heroicons/react/24/outline'
|
import { DocumentTextIcon, LanguageIcon, LinkIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, SquaresPlusIcon } from '@heroicons/react/24/outline'
|
||||||
import { useProject } from '../../../context/Project/provider'
|
import { useProject } from '../../../context/Project/provider'
|
||||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
||||||
import LanguageSelect from '../../utils/LanguageSelect'
|
import LanguageSelect from '../../utils/LanguageSelect'
|
||||||
import { useStage } from '../context/provider'
|
|
||||||
import ToolToggleButton from './ToolToggleButton'
|
import ToolToggleButton from './ToolToggleButton'
|
||||||
import processImageArea from '../../../useCases/processImageArea'
|
import processImageArea from '../../../useCases/processImageArea'
|
||||||
import { pushNotification } from '../../../redux/features/notifications/notificationQueueSlice'
|
import { pushNotification } from '../../../redux/features/notifications/notificationQueueSlice'
|
||||||
|
import { RootState } from '../../../redux/store'
|
||||||
|
import { maxScale, scaleStep, setAreAreasVisible, setAreLinkAreaContextsVisible, setAreProcessedWordsVisible, setAreTranslatedWordsVisible, setScale } from '../../../redux/features/stage/stageSlice'
|
||||||
|
|
||||||
|
|
||||||
const ToolingOverlay = () => {
|
const ToolingOverlay = () => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const { getSelectedDocument, selectedAreaId, requestUpdateArea, requestUpdateDocument, updateDocuments } = useProject()
|
|
||||||
const {
|
const {
|
||||||
scale, scaleStep, maxScale, setScale,
|
scale,
|
||||||
isLinkAreaContextsVisible, setIsLinkAreaContextsVisible,
|
areAreasVisible,
|
||||||
isAreasVisible, setIsAreasVisible,
|
areLinkAreaContextsVisible,
|
||||||
isProcessedWordsVisible, setIsProcessedWordsVisible,
|
areProcessedWordsVisible,
|
||||||
isTranslatedWordsVisible, setIsTranslatedWordsVisible,
|
areTranslatedWordsVisible,
|
||||||
} = useStage()
|
} = useSelector((state: RootState) => state.stage)
|
||||||
|
|
||||||
|
const { getSelectedDocument, selectedAreaId, requestUpdateArea, requestUpdateDocument, updateDocuments } = useProject()
|
||||||
|
|
||||||
const selectedDocument = getSelectedDocument()
|
const selectedDocument = getSelectedDocument()
|
||||||
const [selectedArea, setSelectedArea] = useState<entities.Area | undefined>()
|
const [selectedArea, setSelectedArea] = useState<entities.Area | undefined>()
|
||||||
@ -95,7 +97,7 @@ const ToolingOverlay = () => {
|
|||||||
<input
|
<input
|
||||||
id="zoomRange" type="range" min={scaleStep} max={maxScale} step={scaleStep}
|
id="zoomRange" type="range" min={scaleStep} max={maxScale} step={scaleStep}
|
||||||
value={scale} className="w-[calc(100%-50px)] h-2 bg-indigo-200 rounded-lg appearance-none cursor-pointer p-0"
|
value={scale} className="w-[calc(100%-50px)] h-2 bg-indigo-200 rounded-lg appearance-none cursor-pointer p-0"
|
||||||
onChange={(e) => { setScale(e.currentTarget.valueAsNumber) }}
|
onChange={(e) => { dispatch(setScale(e.currentTarget.valueAsNumber)) }}
|
||||||
/>
|
/>
|
||||||
<MagnifyingGlassPlusIcon className='w-4 h-4' />
|
<MagnifyingGlassPlusIcon className='w-4 h-4' />
|
||||||
</div>
|
</div>
|
||||||
@ -103,16 +105,16 @@ const ToolingOverlay = () => {
|
|||||||
|
|
||||||
{/* Right Buttons */}
|
{/* Right Buttons */}
|
||||||
<div className='absolute bottom-6 right-3 pointer-events-none'>
|
<div className='absolute bottom-6 right-3 pointer-events-none'>
|
||||||
{isAreasVisible
|
{areAreasVisible
|
||||||
? <>
|
? <>
|
||||||
<ToolToggleButton icon={LinkIcon} hint='Link Area Contexts' isActive={isLinkAreaContextsVisible} onClick={() => setIsLinkAreaContextsVisible(!isLinkAreaContextsVisible)} />
|
<ToolToggleButton icon={LinkIcon} hint='Link Area Contexts' isActive={areLinkAreaContextsVisible} onClick={() => dispatch(setAreLinkAreaContextsVisible(!areLinkAreaContextsVisible))} />
|
||||||
<ToolToggleButton icon={LanguageIcon} hint='Toggle Translations' isActive={isTranslatedWordsVisible} onClick={() => setIsTranslatedWordsVisible(!isTranslatedWordsVisible)} />
|
<ToolToggleButton icon={LanguageIcon} hint='Toggle Translations' isActive={areTranslatedWordsVisible} onClick={() => dispatch(setAreTranslatedWordsVisible(!areTranslatedWordsVisible))} />
|
||||||
<ToolToggleButton icon={DocumentTextIcon} hint='Toggle Processed' isActive={isProcessedWordsVisible} onClick={() => setIsProcessedWordsVisible(!isProcessedWordsVisible)} />
|
<ToolToggleButton icon={DocumentTextIcon} hint='Toggle Processed' isActive={areProcessedWordsVisible} onClick={() => dispatch(setAreProcessedWordsVisible(!areProcessedWordsVisible))} />
|
||||||
</>
|
</>
|
||||||
: <></>
|
: <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
<ToolToggleButton icon={SquaresPlusIcon} hint='Toggle Areas' isActive={isAreasVisible} onClick={() => setIsAreasVisible(!isAreasVisible)} />
|
<ToolToggleButton icon={SquaresPlusIcon} hint='Toggle Areas' isActive={areAreasVisible} onClick={() => dispatch(setAreAreasVisible(!areAreasVisible))} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,24 +0,0 @@
|
|||||||
import { StageContextType } from './types'
|
|
||||||
|
|
||||||
const makeDefaultStage = (): StageContextType => ({
|
|
||||||
scale: 1,
|
|
||||||
maxScale: 4,
|
|
||||||
scaleStep: 0.01,
|
|
||||||
setScale: (_) => {},
|
|
||||||
isAreasVisible: true,
|
|
||||||
setIsAreasVisible: (_) => {},
|
|
||||||
isProcessedWordsVisible: true,
|
|
||||||
setIsProcessedWordsVisible: (_) => {},
|
|
||||||
isTranslatedWordsVisible: true,
|
|
||||||
setIsTranslatedWordsVisible: (_) => {},
|
|
||||||
isLinkAreaContextsVisible: false,
|
|
||||||
setIsLinkAreaContextsVisible: (_) => {},
|
|
||||||
size: { width: 1, height: 1 },
|
|
||||||
setSize: (_) => {},
|
|
||||||
isDrawingArea: false,
|
|
||||||
setIsDrawingArea: (_) => {},
|
|
||||||
startingContextConnection: null,
|
|
||||||
setStartingContextConnection: (_) => {},
|
|
||||||
})
|
|
||||||
|
|
||||||
export default makeDefaultStage
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { createContext, useContext, useState, ReactNode, } from 'react'
|
|
||||||
import makeDefaultStage from './makeDefaultStage'
|
|
||||||
import { StageContextType, StartingContextConnection } from './types'
|
|
||||||
|
|
||||||
const StageContext = createContext<StageContextType>(makeDefaultStage())
|
|
||||||
|
|
||||||
export function useStage() {
|
|
||||||
return useContext(StageContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxScale = 4
|
|
||||||
const scaleStep = 0.01
|
|
||||||
|
|
||||||
type Props = { children: ReactNode }
|
|
||||||
export function StageProvider({ children }: Props) {
|
|
||||||
const [size, setSize] = useState({width: 1, height: 1})
|
|
||||||
const [scale, setScale] = useState(1)
|
|
||||||
const [isAreasVisible, setIsAreasVisible] = useState(true)
|
|
||||||
const [isProcessedWordsVisible, setIsProcessedWordsVisible] = useState(true)
|
|
||||||
const [isTranslatedWordsVisible, setIsTranslatedWordsVisible] = useState(true)
|
|
||||||
const [isLinkAreaContextsVisible, setIsLinkAreaContextsVisible] = useState(false)
|
|
||||||
const [isDrawingArea, setIsDrawingArea] = useState(false)
|
|
||||||
const [startingContextConnection, setStartingContextConnection] = useState<StartingContextConnection | null>(null)
|
|
||||||
|
|
||||||
const value = {
|
|
||||||
scale,
|
|
||||||
maxScale,
|
|
||||||
scaleStep,
|
|
||||||
setScale,
|
|
||||||
isAreasVisible,
|
|
||||||
setIsAreasVisible,
|
|
||||||
isProcessedWordsVisible,
|
|
||||||
setIsProcessedWordsVisible,
|
|
||||||
isTranslatedWordsVisible,
|
|
||||||
setIsTranslatedWordsVisible,
|
|
||||||
isLinkAreaContextsVisible,
|
|
||||||
setIsLinkAreaContextsVisible,
|
|
||||||
size,
|
|
||||||
setSize,
|
|
||||||
isDrawingArea,
|
|
||||||
setIsDrawingArea,
|
|
||||||
startingContextConnection,
|
|
||||||
setStartingContextConnection,
|
|
||||||
}
|
|
||||||
|
|
||||||
return <StageContext.Provider value={value}>
|
|
||||||
{ children }
|
|
||||||
</StageContext.Provider>
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
export type StartingContextConnection = {
|
|
||||||
isHead: boolean,
|
|
||||||
areaId: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type StageContextType = {
|
|
||||||
scale: number,
|
|
||||||
maxScale: number,
|
|
||||||
scaleStep: number,
|
|
||||||
setScale: (value: number) => void,
|
|
||||||
isAreasVisible: boolean,
|
|
||||||
setIsAreasVisible: (value: boolean) => void,
|
|
||||||
isProcessedWordsVisible: boolean,
|
|
||||||
setIsProcessedWordsVisible: (value: boolean) => void,
|
|
||||||
isTranslatedWordsVisible: boolean,
|
|
||||||
setIsTranslatedWordsVisible: (value: boolean) => void,
|
|
||||||
isLinkAreaContextsVisible: boolean,
|
|
||||||
setIsLinkAreaContextsVisible: (value: boolean) => void,
|
|
||||||
size: { width: number, height: number }
|
|
||||||
setSize: (size: {width: number, height: number}) => void,
|
|
||||||
isDrawingArea: boolean,
|
|
||||||
setIsDrawingArea: (value: boolean) => void,
|
|
||||||
startingContextConnection: StartingContextConnection | null,
|
|
||||||
setStartingContextConnection: (value: StartingContextConnection | null) => void,
|
|
||||||
}
|
|
||||||
@ -2,19 +2,21 @@
|
|||||||
|
|
||||||
import dynamic from 'next/dynamic'
|
import dynamic from 'next/dynamic'
|
||||||
import React, { useEffect, useRef } from 'react'
|
import React, { useEffect, useRef } from 'react'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
import ToolingOverlay from './ToolingOverlay'
|
import ToolingOverlay from './ToolingOverlay'
|
||||||
import { useStage } from './context/provider'
|
import { setSize } from '../../redux/features/stage/stageSlice'
|
||||||
|
|
||||||
const CanvasStage = dynamic(() => import('./CanvasStage'), { ssr: false })
|
const CanvasStage = dynamic(() => import('./CanvasStage'), { ssr: false })
|
||||||
|
|
||||||
const DocumentCanvas = () => {
|
const DocumentCanvas = () => {
|
||||||
const { setSize } = useStage()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
const thisRef = useRef<HTMLDivElement>(null)
|
const thisRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const handleWindowResize = () => {
|
const handleWindowResize = () => {
|
||||||
const width = thisRef?.current?.clientWidth || 0
|
const width = thisRef?.current?.clientWidth || 0
|
||||||
const height = thisRef?.current?.clientHeight || 0
|
const height = thisRef?.current?.clientHeight || 0
|
||||||
setSize({ width, height })
|
dispatch(setSize({ width, height }))
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { useNavigation } from '../../context/Navigation/provider'
|
|||||||
import { workspaces } from '../../context/Navigation/types'
|
import { workspaces } from '../../context/Navigation/types'
|
||||||
import { useProject } from '../../context/Project/provider'
|
import { useProject } from '../../context/Project/provider'
|
||||||
import DocumentCanvas from '../DocumentCanvas'
|
import DocumentCanvas from '../DocumentCanvas'
|
||||||
import { StageProvider } from '../DocumentCanvas/context/provider'
|
|
||||||
import NoSelectedDocument from './NoSelectedDocument'
|
import NoSelectedDocument from './NoSelectedDocument'
|
||||||
import TextEditor from './TextEditor'
|
import TextEditor from './TextEditor'
|
||||||
|
|
||||||
@ -16,9 +15,7 @@ const MainWorkspace = () => {
|
|||||||
if (selectedWorkspace === workspaces.TEXTEDITOR) return <TextEditor />
|
if (selectedWorkspace === workspaces.TEXTEDITOR) return <TextEditor />
|
||||||
else return !selectedDocumentId
|
else return !selectedDocumentId
|
||||||
? <NoSelectedDocument />
|
? <NoSelectedDocument />
|
||||||
: <StageProvider>
|
: <DocumentCanvas />
|
||||||
<DocumentCanvas />
|
|
||||||
</StageProvider>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return <main className=" bg-gray-100 min-h-[calc(100vh-118px)] ml-64 overflow-y-scroll">
|
return <main className=" bg-gray-100 min-h-[calc(100vh-118px)] ml-64 overflow-y-scroll">
|
||||||
|
|||||||
@ -4,11 +4,11 @@ import { NotificationProps, NotificationQueueState } from './types'
|
|||||||
|
|
||||||
const initialState: NotificationQueueState = {
|
const initialState: NotificationQueueState = {
|
||||||
currentNotification: undefined,
|
currentNotification: undefined,
|
||||||
queue: []
|
queue: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const notificationQueueSlice = createSlice({
|
export const notificationQueueSlice = createSlice({
|
||||||
name: 'propertyList',
|
name: 'notifications',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setNotifications: (state, action: PayloadAction<NotificationProps[]>) => {
|
setNotifications: (state, action: PayloadAction<NotificationProps[]>) => {
|
||||||
|
|||||||
66
frontend/redux/features/stage/stageSlice.ts
Normal file
66
frontend/redux/features/stage/stageSlice.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit'
|
||||||
|
import type { PayloadAction } from '@reduxjs/toolkit'
|
||||||
|
import { ContextConnectionPoint, StageState } from './types'
|
||||||
|
|
||||||
|
export const maxScale = 4
|
||||||
|
export const scaleStep = 0.01
|
||||||
|
|
||||||
|
const initialState: StageState = {
|
||||||
|
size: { width: 1, height: 1 },
|
||||||
|
scale: 1,
|
||||||
|
areAreasVisible: true,
|
||||||
|
areProcessedWordsVisible: true,
|
||||||
|
areTranslatedWordsVisible: false,
|
||||||
|
areLinkAreaContextsVisible: false,
|
||||||
|
isDrawingArea: false,
|
||||||
|
startingContextConnectionPoint: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stageSlice = createSlice({
|
||||||
|
name: 'stage',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setSize: (state, action: PayloadAction<{width: number, height: number}>) => {
|
||||||
|
state.size = action.payload
|
||||||
|
},
|
||||||
|
setScale: (state, action: PayloadAction<number>) => {
|
||||||
|
let clampedScale = action.payload
|
||||||
|
|
||||||
|
if (clampedScale > maxScale) clampedScale = maxScale
|
||||||
|
else if (clampedScale < scaleStep) clampedScale = scaleStep
|
||||||
|
|
||||||
|
state.scale = clampedScale
|
||||||
|
},
|
||||||
|
setAreAreasVisible: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.areAreasVisible = action.payload
|
||||||
|
},
|
||||||
|
setAreProcessedWordsVisible: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.areProcessedWordsVisible = action.payload
|
||||||
|
},
|
||||||
|
setAreTranslatedWordsVisible: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.areTranslatedWordsVisible = action.payload
|
||||||
|
},
|
||||||
|
setAreLinkAreaContextsVisible: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.areLinkAreaContextsVisible = action.payload
|
||||||
|
},
|
||||||
|
setIsDrawingArea: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.isDrawingArea = action.payload
|
||||||
|
},
|
||||||
|
setStartingContextConnectionPoint: (state, action: PayloadAction<ContextConnectionPoint | null>) => {
|
||||||
|
state.startingContextConnectionPoint = action.payload
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const {
|
||||||
|
setSize,
|
||||||
|
setScale,
|
||||||
|
setAreAreasVisible,
|
||||||
|
setAreProcessedWordsVisible,
|
||||||
|
setAreTranslatedWordsVisible,
|
||||||
|
setAreLinkAreaContextsVisible,
|
||||||
|
setIsDrawingArea,
|
||||||
|
setStartingContextConnectionPoint,
|
||||||
|
} = stageSlice.actions
|
||||||
|
|
||||||
|
export default stageSlice.reducer
|
||||||
15
frontend/redux/features/stage/types.ts
Normal file
15
frontend/redux/features/stage/types.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export type ContextConnectionPoint = {
|
||||||
|
isHead: boolean,
|
||||||
|
areaId: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StageState = {
|
||||||
|
size: { width: number, height: number },
|
||||||
|
scale: number,
|
||||||
|
areAreasVisible: boolean,
|
||||||
|
areProcessedWordsVisible: boolean,
|
||||||
|
areTranslatedWordsVisible: boolean,
|
||||||
|
areLinkAreaContextsVisible: boolean,
|
||||||
|
isDrawingArea: boolean,
|
||||||
|
startingContextConnectionPoint: ContextConnectionPoint | null
|
||||||
|
}
|
||||||
@ -1,10 +1,12 @@
|
|||||||
import { configureStore } from '@reduxjs/toolkit'
|
import { configureStore } from '@reduxjs/toolkit'
|
||||||
import notificationQueueSlice from './features/notifications/notificationQueueSlice'
|
|
||||||
import { setupListeners } from '@reduxjs/toolkit/dist/query'
|
import { setupListeners } from '@reduxjs/toolkit/dist/query'
|
||||||
|
import notificationQueueSlice from './features/notifications/notificationQueueSlice'
|
||||||
|
import stageSlice from './features/stage/stageSlice'
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
notificationQueue: notificationQueueSlice,
|
notificationQueue: notificationQueueSlice,
|
||||||
|
stage: stageSlice,
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
|
middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user