Skip to content

Commit

Permalink
Merge pull request #3749 from illacloud/feat/add-signature-widget-aft…
Browse files Browse the repository at this point in the history
…er-mobile

Feat/add signature widget after mobile
  • Loading branch information
Wangtaofeng authored Mar 6, 2024
2 parents f07a8d2 + 4aafcbf commit 261ec5e
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 147 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ export interface SignatureWidgetProps
size: string
mode: PADDING_MODE
}
backgroundColor?: string
}

export interface ICustomRef {
clear: () => void
}
Original file line number Diff line number Diff line change
Expand Up @@ -237,18 +237,19 @@ export const SIGNATURE_PANEL_CONFIG: PanelConfig[] = [
useCustomLayout: true,
defaultValue: "#ffffffff",
},
// TODO: WTF i18n
{
id: `${baseWidgetName}-style-penColor`,
labelName: i18n.t("penColor"),
labelName: i18n.t("editor.inspect.setter_label.signature.penColor"),
attrName: "penColor",
setterType: "COLOR_PICKER_SETTER",
useCustomLayout: true,
defaultValue: "grayBlue",
},
{
id: `${baseWidgetName}-style-contentColor`,
labelName: i18n.t("contentColor"),
labelName: i18n.t(
"editor.inspect.setter_label.signature.contentColor",
),
attrName: "guideColor",
setterType: "COLOR_PICKER_SETTER",
useCustomLayout: true,
Expand Down
19 changes: 10 additions & 9 deletions apps/builder/src/widgetLibrary/PC/SignatureWidget/signature.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { FC, useCallback, useEffect } from "react"
import { FC, useCallback, useEffect, useRef } from "react"
import { InvalidMessage } from "@/widgetLibrary/PC/PublicSector/InvalidMessage"
import { handleValidateCheck } from "@/widgetLibrary/PC/PublicSector/InvalidMessage/utils"
import { Label } from "@/widgetLibrary/PC/PublicSector/Label"
import { TooltipWrapper } from "@/widgetLibrary/PC/PublicSector/TooltipWrapper"
import { SignatureWidgetProps } from "./interface"
import { ICustomRef, SignatureWidgetProps } from "./interface"
import SignatureCanvas from "./signatureCanvas"
import {
containerStyle,
Expand All @@ -29,15 +29,14 @@ export const SignatureWidget: FC<SignatureWidgetProps> = (props) => {
displayName,
customRule,
padding,
backgroundColor,
handleUpdateMultiExecutionResult,
triggerEventHandler,
updateComponentRuntimeProps,
deleteComponentRuntimeProps,
} = props

const showValidationMessage = Boolean(
!hideValidationMessage && validateMessage,
)
const customRef = useRef<ICustomRef>(null)

const getValidateMessage = useCallback(
(value: unknown) => {
Expand Down Expand Up @@ -91,6 +90,7 @@ export const SignatureWidget: FC<SignatureWidgetProps> = (props) => {
useEffect(() => {
updateComponentRuntimeProps({
clearValue: () => {
customRef.current?.clear()
handleUpdateMultiExecutionResult([
{
displayName,
Expand All @@ -114,12 +114,12 @@ export const SignatureWidget: FC<SignatureWidgetProps> = (props) => {
},
])
},
setDisabled: () => {
setDisabled: (disabled?: boolean) => {
handleUpdateMultiExecutionResult([
{
displayName,
value: {
disabled: true,
disabled: disabled,
},
},
])
Expand All @@ -138,9 +138,9 @@ export const SignatureWidget: FC<SignatureWidgetProps> = (props) => {
])

return (
<div css={containerStyle(padding?.size)}>
<div css={containerStyle(padding?.size, backgroundColor)}>
<TooltipWrapper tooltipText={tooltipText} tooltipDisabled={!tooltipText}>
<div css={labelWrapperStyle(labelPosition, showValidationMessage)}>
<div css={labelWrapperStyle(labelPosition)}>
<Label
labelFull={labelFull}
label={label}
Expand All @@ -155,6 +155,7 @@ export const SignatureWidget: FC<SignatureWidgetProps> = (props) => {
/>
<SignatureCanvas
{...props}
ref={customRef}
handleUpdateSignature={handleUpdateSignature}
/>
{!hideValidationMessage && (
Expand Down
264 changes: 140 additions & 124 deletions apps/builder/src/widgetLibrary/PC/SignatureWidget/signatureCanvas.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { FC, MouseEvent, TouchEvent, useEffect, useRef, useState } from "react"
import {
MouseEvent,
TouchEvent,
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState,
} from "react"
import useMeasure from "react-use-measure"
import { RefreshIcon, getSpecialThemeColor } from "@illa-design/react"
import { WrappedSignatureProps } from "./interface"
import { ICustomRef, WrappedSignatureProps } from "./interface"
import {
canvasContainerStyle,
canvasStyle,
Expand All @@ -21,137 +29,145 @@ interface SignatureCanvasProps
| "handleUpdateSignature"
> {}

const SignatureCanvas: FC<SignatureCanvasProps> = ({
value,
guideColor = "grayBlue",
penColor = "grayBlue",
text,
disabled,
handleUpdateSignature,
}) => {
const [isDrawing, setIsDrawing] = useState(false)
const canvasRef = useRef<HTMLCanvasElement>(null)
const [boundsRef, bound] = useMeasure()
const ctx = useRef<CanvasRenderingContext2D | null>()
const startX = useRef(0)
const startY = useRef(0)
const [dpr, setDpr] = useState(1)

const handleStartDraw = (
e: MouseEvent<HTMLCanvasElement> | TouchEvent<HTMLCanvasElement>,
const SignatureCanvas = forwardRef<ICustomRef, SignatureCanvasProps>(
(
{
value,
guideColor = "grayBlue",
penColor = "grayBlue",
text,
disabled,
handleUpdateSignature,
},
ref,
) => {
e.stopPropagation()
setIsDrawing(true)
if (!ctx.current) {
ctx.current = canvasRef.current?.getContext("2d")
}
if (!canvasRef.current || !ctx.current) return
if ("offsetX" in e.nativeEvent && "offsetY" in e.nativeEvent) {
startX.current = e.nativeEvent.offsetX
startY.current = e.nativeEvent.offsetY
} else {
const touch = (e as TouchEvent).touches[0]
const touchTarget = touch.target as HTMLCanvasElement
startX.current = touch.clientX - touchTarget.getBoundingClientRect().left
startY.current = touch.clientY - touchTarget.getBoundingClientRect().top
}
ctx.current.lineWidth = 2 * dpr
ctx.current.lineCap = "round"
ctx.current.strokeStyle = getSpecialThemeColor(penColor)
ctx.current.beginPath()
}
const [isDrawing, setIsDrawing] = useState(false)
const canvasRef = useRef<HTMLCanvasElement>(null)
const [boundsRef, bound] = useMeasure()
const ctx = useRef<CanvasRenderingContext2D | null>()
const startX = useRef(0)
const startY = useRef(0)
const [dpr, setDpr] = useState(1)

const handleDraw = (
e: MouseEvent<HTMLCanvasElement> | TouchEvent<HTMLCanvasElement>,
) => {
e.stopPropagation()
const currentCtx = ctx.current
if (!isDrawing || !currentCtx) return
let offsetX, offsetY
if ("offsetX" in e.nativeEvent && "offsetY" in e.nativeEvent) {
offsetX = e.nativeEvent.offsetX
offsetY = e.nativeEvent.offsetY
} else {
const touch = (e as TouchEvent).touches[0]
const touchTarget = touch.target as HTMLCanvasElement
offsetX = touch.clientX - touchTarget.getBoundingClientRect().left
offsetY = touch.clientY - touchTarget.getBoundingClientRect().top
}
currentCtx.moveTo(startX.current * dpr, startY.current * dpr)
currentCtx.lineTo(offsetX * dpr, offsetY * dpr)
startX.current = offsetX
startY.current = offsetY
currentCtx.stroke()
}
useImperativeHandle(ref, () => ({
clear: () => handleReset(),
}))

const handleEndDraw = (
e: MouseEvent<HTMLCanvasElement> | TouchEvent<HTMLCanvasElement>,
) => {
e.stopPropagation()
if (!isDrawing) return
setIsDrawing(false)
ctx.current?.closePath()
const data = canvasRef.current?.toDataURL("image/png")
const rep = /^data:image\/png[^,]+base64,/
handleUpdateSignature(data, data?.replace(rep, ""))
}
const handleStartDraw = (
e: MouseEvent<HTMLCanvasElement> | TouchEvent<HTMLCanvasElement>,
) => {
e.stopPropagation()
setIsDrawing(true)
if (!ctx.current) {
ctx.current = canvasRef.current?.getContext("2d")
}
if (!canvasRef.current || !ctx.current) return
if ("offsetX" in e.nativeEvent && "offsetY" in e.nativeEvent) {
startX.current = e.nativeEvent.offsetX
startY.current = e.nativeEvent.offsetY
} else {
const touch = (e as TouchEvent).touches[0]
const touchTarget = touch.target as HTMLCanvasElement
startX.current =
touch.clientX - touchTarget.getBoundingClientRect().left
startY.current = touch.clientY - touchTarget.getBoundingClientRect().top
}
ctx.current.lineWidth = 2 * dpr
ctx.current.lineCap = "round"
ctx.current.strokeStyle = getSpecialThemeColor(penColor)
ctx.current.beginPath()
}

const handleReset = () => {
if (!ctx.current) return
ctx.current.clearRect(
0,
0,
canvasRef.current?.width || 0,
canvasRef.current?.height || 0,
)
handleUpdateSignature()
}
const handleDraw = (
e: MouseEvent<HTMLCanvasElement> | TouchEvent<HTMLCanvasElement>,
) => {
e.stopPropagation()
const currentCtx = ctx.current
if (!isDrawing || !currentCtx) return
let offsetX, offsetY
if ("offsetX" in e.nativeEvent && "offsetY" in e.nativeEvent) {
offsetX = e.nativeEvent.offsetX
offsetY = e.nativeEvent.offsetY
} else {
const touch = (e as TouchEvent).touches[0]
const touchTarget = touch.target as HTMLCanvasElement
offsetX = touch.clientX - touchTarget.getBoundingClientRect().left
offsetY = touch.clientY - touchTarget.getBoundingClientRect().top
}
currentCtx.moveTo(startX.current * dpr, startY.current * dpr)
currentCtx.lineTo(offsetX * dpr, offsetY * dpr)
startX.current = offsetX
startY.current = offsetY
currentCtx.stroke()
}

useEffect(() => {
const el = canvasRef.current
const preventDefault = (e: Event) => {
e.preventDefault()
const handleEndDraw = (
e: MouseEvent<HTMLCanvasElement> | TouchEvent<HTMLCanvasElement>,
) => {
e.stopPropagation()
if (!isDrawing) return
setIsDrawing(false)
ctx.current?.closePath()
const data = canvasRef.current?.toDataURL("image/png")
const rep = /^data:image\/png[^,]+base64,/
handleUpdateSignature(data, data?.replace(rep, ""))
}
el?.addEventListener("touchstart", preventDefault, { passive: false })
return () => {
el?.removeEventListener("touchstart", preventDefault)

const handleReset = () => {
if (!ctx.current) return
ctx.current.clearRect(
0,
0,
canvasRef.current?.width || 0,
canvasRef.current?.height || 0,
)
handleUpdateSignature()
}
}, [])

useEffect(() => {
setDpr(window.devicePixelRatio)
}, [])
useEffect(() => {
const el = canvasRef.current
const preventDefault = (e: Event) => {
e.preventDefault()
}
el?.addEventListener("touchstart", preventDefault, { passive: false })
return () => {
el?.removeEventListener("touchstart", preventDefault)
}
}, [])

useEffect(() => {
setDpr(window.devicePixelRatio)
}, [])

useEffect(() => {
console.log("bound", bound.width)
}, [bound.width])
return (
<div css={canvasContainerStyle(disabled)} ref={boundsRef}>
{!value && !isDrawing && (
<span css={signTextStyle(guideColor)}>{text}</span>
)}
<RefreshIcon
css={resetIconStyle(guideColor)}
onClick={handleReset}
size="24px"
/>
<canvas
ref={canvasRef}
css={canvasStyle}
width={dpr * bound.width}
height={dpr * bound.height}
onMouseDownCapture={handleStartDraw}
onMouseMoveCapture={handleDraw}
onMouseUp={handleEndDraw}
onMouseOut={handleEndDraw}
onTouchStart={handleStartDraw}
onTouchMove={handleDraw}
onTouchEnd={handleEndDraw}
/>
<div css={lineStyle(guideColor)} />
</div>
)
},
)

return (
<div css={canvasContainerStyle(disabled)} ref={boundsRef}>
{!value && !isDrawing && (
<span css={signTextStyle(guideColor)}>{text}</span>
)}
<RefreshIcon
css={resetIconStyle(guideColor)}
onClick={handleReset}
size="24px"
/>
<canvas
ref={canvasRef}
css={canvasStyle}
width={dpr * bound.width}
height={dpr * bound.height}
onMouseDownCapture={handleStartDraw}
onMouseMoveCapture={handleDraw}
onMouseUp={handleEndDraw}
onMouseOut={handleEndDraw}
onTouchStart={handleStartDraw}
onTouchMove={handleDraw}
onTouchEnd={handleEndDraw}
/>
<div css={lineStyle(guideColor)} />
</div>
)
}
SignatureCanvas.displayName = "SignatureCanvas"

export default SignatureCanvas
Loading

0 comments on commit 261ec5e

Please sign in to comment.