first commit

This commit is contained in:
Mathieu Bruyen
2022-05-02 15:12:06 +02:00
commit 4ddc04d017
23 changed files with 3849 additions and 0 deletions

163
src/Circle.tsx Normal file
View File

@@ -0,0 +1,163 @@
import {useCallback, Fragment} from 'react';
import {v4 as uuidv4} from 'uuid';
import {Editor, StringField, NumberField, OverlayHandle} from './Editor';
import type {SvgItem} from './App';
import type {Action} from './Editor';
export type Circle = {
type: 'circle',
id: string,
stroke: string,
strokeWidth: number,
cx: number,
cy: number,
r: number,
}
type CircleRenderProps = {
circle: Circle,
}
export function CircleRender({circle}: CircleRenderProps) {
return <circle
cx={circle.cx}
cy={circle.cy}
r={circle.r}
stroke={circle.stroke}
strokeWidth={circle.strokeWidth}
fill="none" />;
}
export function CircleOverlaySVG({circle}: CircleRenderProps) {
return <path
d={`M${circle.cx} ${circle.cy} L${circle.cx + circle.r} ${circle.cy}`}
stroke="grey"
strokeWidth={circle.strokeWidth}
strokeDasharray="2 1" />;
}
type CircleEditorProps = {
circle: Circle,
onChange: Action<SvgItem>,
}
export function CircleEditor({circle, onChange}: CircleEditorProps) {
const onCxChange = useCallback((cx: number) => {
onChange((item) => {
if (item.type !== 'circle') {
return item;
}
return {
...item,
cx,
};
});
}, [onChange]);
const onCyChange = useCallback((cy: number) => {
onChange((item) => {
if (item.type !== 'circle') {
return item;
}
return {
...item,
cy,
};
});
}, [onChange]);
const onRChange = useCallback((r: number) => {
onChange((item) => {
if (item.type !== 'circle') {
return item;
}
return {
...item,
r,
};
});
}, [onChange]);
const onStrokeChange = useCallback((stroke: string) => {
onChange((item) => {
if (item.type !== 'circle') {
return item;
}
return {
...item,
stroke,
};
});
}, [onChange]);
const onStrokeWidthChange = useCallback((strokeWidth: number) => {
onChange((item) => {
if (item.type !== 'circle') {
return item;
}
return {
...item,
strokeWidth,
};
});
}, [onChange]);
return <Editor tag="circle">
<NumberField name="cx" value={circle.cx} onChange={onCxChange} step={0.1} />
<NumberField name="cy" value={circle.cy} onChange={onCyChange} step={0.1} />
<NumberField name="r" value={circle.r} onChange={onRChange} step={0.1} />
<StringField name="stroke" value={circle.stroke} onChange={onStrokeChange} />
<NumberField name="stroke-width" value={circle.strokeWidth} onChange={onStrokeWidthChange} step={0.1} />
</Editor>;
}
type CircleOverlayDOMProps = {
circle: Circle,
width: number,
height: number,
onChange: Action<SvgItem>,
}
export function CircleOverlayDOM({circle, width, height, onChange}: CircleOverlayDOMProps) {
const onCChange = useCallback((cx: number, cy: number) => {
onChange((item) => {
if (item.type !== 'circle') {
return item;
}
return {
...item,
cx,
cy,
};
});
}, [ onChange]);
const onRChange = useCallback((rx: number, ry: number) => {
onChange((item) => {
if (item.type !== 'circle') {
return item;
}
const r = Math.sqrt((rx - circle.cx) * (rx - circle.cx) + (ry - circle.cy) * (ry - circle.cy));
return {
...item,
r,
};
});
}, [onChange]);
return <Fragment>
<OverlayHandle x={circle.cx} y={circle.cy} width={width} height={height} onChange={onCChange} />
<OverlayHandle x={circle.cx + circle.r} y={circle.cy} width={width} height={height} onChange={onRChange} />
</Fragment>;
}
export function normalizeCircle(circle: any): Circle | null {
if (!circle) {
return null;
}
return {
type: 'circle',
id: typeof circle.id === 'string' ? circle.id : uuidv4(),
cx: typeof circle.cx === 'number' ? circle.cx : 0,
cy: typeof circle.cy === 'number' ? circle.cy : 0,
r: typeof circle.r === 'number' ? circle.r : 0,
stroke: typeof circle.stroke === 'string' ? circle.stroke : 'black',
strokeWidth: typeof circle.strokeWidth === 'number' ? circle.strokeWidth : 0,
};
}