first commit
This commit is contained in:
163
src/Circle.tsx
Normal file
163
src/Circle.tsx
Normal 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user