import React, { useEffect } from 'react'; import { getCircle } from '../../lib/paint'; import { ShadowDomContext } from '../../shadow-dom'; import { Input } from '../ui/input'; import { Button } from '../ui/button'; import { StyledPicker } from '../color-picker'; import { ErrorBoundary } from 'react-error-boundary'; export const PixelCursor = (props: { color: string; dia: number; factor: number; style?: React.CSSProperties; className?: string }) => { const { className, color, dia, factor, style } = props; const pixels = React.useMemo(() => { const result: [number, number, number][] = new Array(dia).fill(0).map((_, i) => [i, Infinity, -1]); getCircle(dia).forEach(([x, y]) => { result[y][1] = Math.min(x, result[y][1]); result[y][2] = Math.max(x, result[y][1]); }); return result.filter((x) => x[1] !== Infinity); }, [dia]); return (
{pixels.map(([y, from, to]) => (
))}
); }; export type CursorDef = | { type: 'circle'; dia: number; factor: number; } | { type: 'rect'; x: number; xFactor: number; y: number; yFactor: number; }; export const PixelContainer = (props: { color: string; cursor: CursorDef; offsetX: number; offsetY: number; children?: React.ReactNode | Iterable; showPreview?: boolean; }) => { const { color, cursor, offsetX, offsetY, children, showPreview } = props; const [cursorPos, setCursorPos] = React.useState<[number, number] | null>(null); return (
setCursorPos(null)} onMouseMoveCapture={(e) => setCursorPos([e.nativeEvent.offsetX, e.nativeEvent.offsetY])} onTouchMoveCapture={(e) => { const rect = e.currentTarget.getBoundingClientRect(); setCursorPos([e.changedTouches[0].pageX - rect.left, e.changedTouches[0].pageY - rect.top]); }} onTouchEnd={() => setCursorPos(null)} > {children} {cursorPos !== null && cursor.type === 'rect' && ( <>
)} {showPreview && !cursorPos && cursor.type === 'rect' && (
)} {cursorPos !== null && cursor.type === 'circle' && ( )} {showPreview && !cursorPos && cursor.type === 'circle' && ( )}
); }; export const ClickInput = (props: { value: string; onChange: (v: string) => void }) => { const [edit, setEdit] = React.useState(false); const doChange = (e: { target: HTMLInputElement }) => { if (e.target.value) props.onChange(e.target.value); setEdit(false); }; return edit ? ( { if (e.key === 'Enter' && !e.nativeEvent.isComposing) { doChange(e as unknown as { target: HTMLInputElement }); return false; } else { return true; } }} /> ) : ( ); }; export const ColorEditor = (props: { color: string; onChangeColor: (color: string) => void; colors: string[] }) => { const [showColorEdit, setShowColorEdit] = React.useState(false); const [colorEdited, setColorEdited] = React.useState(false); const ref = React.useRef(null); const shadowDomMeta = React.useContext(ShadowDomContext); useEffect(() => { if (showColorEdit) { const listener = (e: Event) => { if (e.target && !ref.current?.contains(e.target as Node)) { setShowColorEdit(false); } }; const dom = shadowDomMeta.root; dom?.addEventListener('click', listener); return () => dom?.removeEventListener('click', listener); } }, [showColorEdit]); const [color, setColor] = React.useState(props.color); return (
{ props.onChangeColor(e.target.value); }} />
} >
{ e.stopPropagation(); e.preventDefault(); setShowColorEdit(true); setColorEdited(false); setColor(props.color); }} >
{showColorEdit && (
{ setColorEdited(true); setColor(color.hex); }} noShadow noBorder />
)}
); };