Commit 2dfa310c authored by Petr D. Svoboda's avatar Petr D. Svoboda

Added basic state manipulation logic

parent a22c4505
......@@ -5,7 +5,8 @@
"dependencies": {
"react": "^16.7.0-alpha.0",
"react-dom": "^16.7.0-alpha.0",
"react-scripts-ts": "3.1.0"
"react-scripts-ts": "3.1.0",
"uuid": "^3.3.2"
},
"scripts": {
"start": "react-scripts-ts start",
......@@ -14,8 +15,9 @@
"eject": "react-scripts-ts eject"
},
"devDependencies": {
"@types/react": "^16.4.18",
"@types/react": "^16.7.3",
"@types/react-dom": "^16.0.9",
"@types/uuid": "^3.4.4",
"typescript": "^3.1.6"
}
}
import * as React from 'react'
import { useState } from 'react'
import './App.css'
import ElementButton from './ElementButton'
export const SelectContext = React.createContext(null)
function App() {
const [selected] = useState(null)
return (
<div className="App">
<SelectContext.Provider value={selected}>
<div className="Toolbar">
<ElementButton label="State" />
<ElementButton label="Transition" />
<div className="Divider" />
<button className="Button">Import</button>
<button className="Button">Export</button>
<div className="Divider" />
<button className="Button">Show hint</button>
</div>
</SelectContext.Provider>
<svg className="Canvas">
<circle id="c" r={50} cy={100} cx={100} fill="red" />
</svg>
</div>
)
}
export default App
import * as React from 'react'
import { useState, useEffect } from 'react'
interface Props {
label: string
}
function moveMouse(e: MouseEvent) {
const c = document.getElementById('c') as SVGCircleElement | null
if (c) {
c.setAttribute('cx', e.x.toString())
c.setAttribute('cy', e.y.toString())
}
}
function ElementButton(props: Props) {
const [active, setActive] = useState(false)
useEffect(
() => {
if (active) {
document.addEventListener('mousemove', moveMouse)
document.addEventListener('mousedown', e => {
e.stopPropagation()
setActive(false)
})
}
return () => {
document.removeEventListener('mousedown', e => {
e.stopPropagation()
setActive(false)
})
document.removeEventListener('mousemove', moveMouse)
}
},
[active]
)
function handleClick() {
active ? setActive(false) : setActive(true)
console.log(props.label + ' click')
}
return (
<button className="Button" onClick={handleClick} disabled={active}>
{props.label}
</button>
)
}
export default ElementButton
......@@ -32,3 +32,7 @@
.Button:last-child {
margin-bottom: 0;
}
.State:hover {
cursor: pointer;
}
\ No newline at end of file
import * as React from 'react'
import './App.css'
import Canvas from './Canvas'
import Toolbar from './Toolbar'
import CanvasProvider from '../state/CanvasContext'
import ToolbarProvider from '../state/ToolbarContext'
function App() {
return (
<ToolbarProvider>
<div className="App">
<Toolbar />
<CanvasProvider>
<Canvas />
</CanvasProvider>
</div>
</ToolbarProvider>
)
}
export default App
import * as React from 'react'
import { useState, useContext } from 'react'
import * as uuid from 'uuid'
import { CanvasContext } from '../state/CanvasContext'
import { ToolbarContext } from '../state/ToolbarContext'
import State from './State'
export interface Position {
x: number
y: number
}
export const id = 'canvas'
function isInRect(position: Position, rect: ClientRect | DOMRect) {
return position.x > rect.left &&
position.x < rect.left + rect.width &&
position.y > rect.top &&
position.y < rect.top + rect.height
? true
: false
}
function Canvas() {
const toolbarContext = useContext(ToolbarContext)!
const canvasContext = useContext(CanvasContext)!
const [isIn, setIsIn] = useState(false)
const [states, setStates] = useState<any[]>([])
function handleMove(e: React.MouseEvent<SVGSVGElement>) {
const rect = e.currentTarget.getBoundingClientRect()
const position: Position = {
x: e.clientX,
y: e.clientY
}
setIsIn(isInRect(position, rect))
canvasContext.setPosition({
x: position.x - rect.left,
y: position.y - rect.top
})
}
function handleMouseDown(e: React.MouseEvent<SVGSVGElement>) {
e.stopPropagation()
}
function handleClick(e: React.MouseEvent<SVGSVGElement>) {
if (toolbarContext.stateSelected) {
const rect = e.currentTarget.getBoundingClientRect()
const stateId = uuid.v4()
setStates([
...states,
<State
key={stateId}
id={stateId}
initialPosition={{
x: e.clientX - rect.left,
y: e.clientY - rect.top
}}
initialStatus="idle"
/>
])
}
}
function handleContextMenu(e: React.MouseEvent<SVGSVGElement>) {
e.preventDefault()
if (toolbarContext.stateSelected) {
toolbarContext.handleStateSelectedToggle(false)
}
}
return (
<svg
id={id}
className="Canvas"
onMouseMove={handleMove}
onMouseDown={handleMouseDown}
onClick={handleClick}
onContextMenu={handleContextMenu}
>
{toolbarContext.stateSelected &&
isIn && (
<State
key="state"
id="state"
initialPosition={canvasContext.position}
initialStatus="initial"
/>
)}
{states}
</svg>
)
}
export default Canvas
// function useCursor(): Position {
// const [cursor, setCursor] = useState<Position>({ x: 0, y: 0 })
// useEffect(() => {
// document.addEventListener('mousemove', e =>
// setCursor({
// x: e.layerX,
// y: e.layerY
// })
// )
// return () => {
// document.removeEventListener('mousemove', e =>
// setCursor({
// x: e.layerX,
// y: e.layerY
// })
// )
// }
// })
// return cursor
// }
import * as React from 'react'
interface Props {
label: string
selected: boolean
onClick: () => void
}
function ElementButton(props: Props) {
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
e.stopPropagation()
props.onClick()
console.log(props.label + ' click')
}
function handleMouseDown(e: React.MouseEvent<HTMLButtonElement>) {
e.stopPropagation()
}
return (
<button
className="Button"
onClick={handleClick}
onMouseDown={handleMouseDown}
>
{props.label}
</button>
)
}
export default ElementButton
import * as React from 'react'
import { useState, useContext, useEffect } from 'react'
import { Position } from './Canvas'
import { CanvasContext } from '../state/CanvasContext'
type Status = 'initial' | 'idle' | 'drag'
interface Props {
id: string
initialPosition: Position
initialStatus: Status
}
function State(props: Props) {
const { id, initialPosition, initialStatus } = props
const context = useContext(CanvasContext)!
const [position, setPosition] = useState(initialPosition)
const [status, setStatus] = useState(initialStatus)
useEffect(
() => {
setPosition(initialPosition)
},
[initialPosition]
)
const selected = status !== 'initial' && context.selectedStates.includes(id)
function handleClick() {
if (status === 'idle') {
context.selectState(id)
}
}
function handleMouseDown(e: React.MouseEvent<SVGCircleElement>) {
setStatus('drag')
}
function handleMouseUp(e: React.MouseEvent<SVGCircleElement>) {
setStatus('idle')
}
function handleMouseMove() {
if (status === 'drag') {
setPosition(context.position)
}
}
return (
<circle
id={id}
className="State"
r={50}
cx={position.x}
cy={position.y}
fill="red"
stroke={selected ? 'yellow' : ''}
strokeWidth={4}
onClick={handleClick}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseMove={handleMouseMove}
/>
)
}
export default State
import * as React from 'react'
import { useContext } from 'react'
import ElementButton from './ElementButton'
import { ToolbarContext } from '../state/ToolbarContext'
function Toolbar() {
const context = useContext(ToolbarContext)!
function handleMouseDown(e: React.MouseEvent<HTMLDivElement>) {
context.handleStateSelectedToggle(false)
context.handleTransitionSelectedToggle(false)
}
return (
<div className="Toolbar" onMouseDown={handleMouseDown}>
<ElementButton
label="State"
selected={context.stateSelected}
onClick={context.handleStateSelectedToggle}
/>
<ElementButton
label="Transition"
selected={context.transitionSelected}
onClick={context.handleTransitionSelectedToggle}
/>
<div className="Divider" />
<button className="Button">Import</button>
<button className="Button">Export</button>
<div className="Divider" />
<button className="Button">Show hint</button>
</div>
)
}
export default Toolbar
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import App from './App'
import App from './components/App'
import './index.css'
import registerServiceWorker from './registerServiceWorker'
......
import * as React from 'react';
declare module 'react' {
// React 16.6
function memo<Props>(component: React.StatelessComponent<Props>): React.StatelessComponent<Props>;
function lazy<P, Component extends React.ComponentType<P>>(
importFn: () => Promise<Component | {default: Component}>,
): Component;
const Suspense: React.ComponentType<{fallback?: React.ReactNode}>;
// React 16.7
type StateUpdateFunction<State> = (newState: State | ((oldState: State) => State)) => void;
function useState<State>(
initialState: State | (() => State),
): [State, StateUpdateFunction<State>];
function useEffect(
f: () => void | Promise<void> | (() => void | Promise<void>),
keys?: any[],
): void;
function useMutationEffect(
f: () => void | Promise<void> | (() => void | Promise<void>),
keys?: any[],
): void;
function useLayoutEffect(
f: () => void | Promise<void> | (() => void | Promise<void>),
keys?: any[],
): void;
function useContext<Context>(context: React.Context<Context>): Context;
type Reducer<State, Action> = (state: State, action: Action) => State;
function useReducer<State, Action>(
reducer: Reducer<State, Action>,
initialState: State,
initialAction?: Action,
): [State, (action: Action) => void];
function useCallback<Callback extends Function, R>(f: Callback, keys?: any[]): Callback;
function useMemo<Value>(f: () => Value, keys?: any[]): Value;
function useRef<T>(): {current: T | null};
function useRef<T>(initial: T): {current: T};
function useImperativeMethods<Ref, ImperativeMethods>(
ref: React.Ref<Ref> | undefined,
f: () => ImperativeMethods,
keys?: any[],
): void;
}
\ No newline at end of file
import * as React from 'react'
import { createContext, useState } from 'react'
interface Position {
x: number
y: number
}
interface ICanvasContext {
selectedStates: string[]
selectState: (id?: string) => void
position: Position
setPosition: React.Dispatch<React.SetStateAction<Position>>
}
export const CanvasContext = createContext<ICanvasContext | null>(null)
interface Provider {
children: JSX.Element
}
export default function ContextProvider({ children }: Provider) {
const [states, setStates] = useState<string[]>([])
const [position, setPosition] = useState<Position>({ x: 0, y: 0 })
function handleSelectStates(id: string) {
if (states.includes(id)) {
setStates(states.filter(s => s !== id))
} else if (states.length < 2) {
setStates([...states, id])
} else {
setStates([states.pop()!, id])
}
}
const context: ICanvasContext = {
selectedStates: states,
selectState: handleSelectStates,
position,
setPosition
}
return (
<CanvasContext.Provider value={context}>
{children}
</CanvasContext.Provider>
)
}
import * as React from 'react'
import { createContext, useState } from 'react'
interface Position {
x: number
y: number
}
interface ICursorContext {
position: Position
setPosition: React.Dispatch<React.SetStateAction<Position>>
}
export const CursorContext = createContext<ICursorContext | null>(null)
interface Provider {
children: JSX.Element
}
export default function ContextProvider({ children }: Provider) {
const [position, setPosition] = useState<Position>({ x: 0, y: 0 })
const context: ICursorContext = {
position,
setPosition
}
return (
<CursorContext.Provider value={context}>
{children}
</CursorContext.Provider>
)
}
import * as React from 'react'
import { createContext, useState } from 'react'
interface IToolbarContext {
stateSelected: boolean
handleStateSelectedToggle: (value?: boolean) => void
transitionSelected: boolean
handleTransitionSelectedToggle: (value?: boolean) => void
}
export const ToolbarContext = createContext<IToolbarContext | null>(null)
interface Provider {
children: JSX.Element
}
export default function ContextProvider({ children }: Provider) {
const [stateSelected, setStateSelected] = useState(false)
const [transitionSelected, setTransitionSelected] = useState(false)
function handleStateSelectedToggle(value?: boolean) {
if (value) {
setStateSelected(value)
} else {
setStateSelected(!stateSelected)
}
}
function handleTransitionSelectedToggle(value?: boolean) {
if (value) {
setTransitionSelected(value)
} else {
setTransitionSelected(!transitionSelected)
}
}
const context = {
stateSelected,
handleStateSelectedToggle,
transitionSelected,
handleTransitionSelectedToggle
}
return (
<ToolbarContext.Provider value={context}>
{children}
</ToolbarContext.Provider>
)
}
......@@ -4,7 +4,7 @@
"outDir": "build/dist",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"lib": ["esnext", "dom"],
"sourceMap": true,
"allowJs": true,
"jsx": "react",
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment