Refactor Stage Context to redux (#7)

* feat: setup stage slice

* refact: replace useStage() with redux
This commit is contained in:
Yehoshua Sandler 2023-09-04 11:32:54 -05:00 committed by GitHub
parent 095c1ca8ec
commit 806f4a28e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 173 additions and 170 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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))}
/>) />)
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(() => {

View File

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

View File

@ -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[]>) => {

View 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

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

View File

@ -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(),
}) })