diff --git a/src/components/floor-plan/modal/grid/DotLineGrid.jsx b/src/components/floor-plan/modal/grid/DotLineGrid.jsx index a8828b81..9494a6e6 100644 --- a/src/components/floor-plan/modal/grid/DotLineGrid.jsx +++ b/src/components/floor-plan/modal/grid/DotLineGrid.jsx @@ -2,21 +2,225 @@ import WithDraggable from '@/components/common/draggable/withDraggable' import QSelectBox from '@/components/common/select/QSelectBox' import { useState } from 'react' import { useMessage } from '@/hooks/useMessage' +import { canvasState, dotLineGridSettingState, dotLineIntervalSelector } from '@/store/canvasAtom' +import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil' +import { onlyNumberInputChange } from '@/util/input-utils' +import { fabric } from 'fabric' + +const TYPE = { + DOT: 'DOT', + LINE: 'LINE', +} export default function DotLineGrid(props) { // const [modalOption, setModalOption] = useRecoilState(modalState); //modal 열림닫힘 state const [close, setClose] = useState(false) const { setShowDotLineGridModal } = props + const canvas = useRecoilValue(canvasState) + + const [dotLineGridSetting, setDotLineGridSettingState] = useRecoilState(dotLineGridSettingState) + const resetDotLineGridSetting = useResetRecoilState(dotLineGridSettingState) + const interval = useRecoilValue(dotLineIntervalSelector) + const { getMessage } = useMessage() const SelectOption = [ - { id: 1, name: getMessage('modal.canvas.setting.grid.dot.line.setting.line.origin') }, - { id: 2, name: '1/2' }, + { id: 1, name: getMessage('modal.canvas.setting.grid.dot.line.setting.line.origin'), value: 1 }, + { id: 2, name: '1/2', value: 1 / 2 }, { id: 3, name: '1/4', + value: 1 / 4, }, - { id: 4, name: '1/10' }, + { id: 4, name: '1/10', value: 1 / 10 }, ] + const [selectOption, setSelectOption] = useState(SelectOption[0]) + + const HandleClickClose = () => { + // setClose(true) + // setTimeout(() => { + // setModalOption({ ...modalOption, gridoption: false }) + // setClose(false) + // }, 180) + } + + const handleCheckBoxChange = (e) => { + const { value, checked } = e.target + setDotLineGridSettingState((prev) => { + return { + ...prev, + [value]: checked, + } + }) + } + + const handleSave = () => { + // 1. 점.선 그리드 설정으로 만들어진 기존 오브젝트 제거 + canvas + ?.getObjects() + .filter((obj) => obj.name === 'lineGrid') + .forEach((obj) => canvas?.remove(obj)) + canvas + ?.getObjects() + .filter((obj) => obj.name === 'dotGrid') + .forEach((obj) => canvas?.remove(obj)) + + const horizontalInterval = interval.horizontalInterval + const verticalInterval = interval.verticalInterval + + if (dotLineGridSetting.DOT) { + const circle = new fabric.Circle({ + radius: 2, + fill: 'red', + strokeWidth: 0.7, + originX: 'center', + originY: 'center', + selectable: false, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + }) + + const patternSourceCanvas = new fabric.StaticCanvas(null, { + width: horizontalInterval, + height: verticalInterval, + }) + + patternSourceCanvas.add(circle) + + circle.set({ + left: patternSourceCanvas.width / 2, + top: patternSourceCanvas.height / 2, + }) + + patternSourceCanvas.renderAll() + + const pattern = new fabric.Pattern({ + source: patternSourceCanvas.getElement(), + repeat: 'repeat', + }) + + const backgroundPolygon = new fabric.Polygon( + [ + { x: 0, y: 0 }, + { x: canvas.width, y: 0 }, + { x: canvas.width, y: canvas.height }, + { x: 0, y: canvas.height }, + ], + { + fill: pattern, + selectable: false, + name: 'dotGrid', + }, + ) + + canvas.add(backgroundPolygon) + backgroundPolygon.sendToBack() + canvas.renderAll() + } + + if (dotLineGridSetting.LINE) { + for (let i = 0; i < canvas.height / verticalInterval + 1; i++) { + const horizontalLine = new fabric.Line( + [0, i * verticalInterval - verticalInterval / 2, canvas.width, i * verticalInterval - verticalInterval / 2], + { + stroke: 'black', + strokeWidth: 1, + selectable: true, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + name: 'lineGrid', + strokeDashArray: [5, 2], + opacity: 0.3, + direction: 'horizontal', + }, + ) + canvas.add(horizontalLine) + } + + for (let i = 0; i < canvas.width / horizontalInterval + 1; i++) { + const verticalLine = new fabric.Line( + [i * horizontalInterval - horizontalInterval / 2, 0, i * horizontalInterval - horizontalInterval / 2, canvas.height], + { + stroke: 'black', + strokeWidth: 1, + selectable: true, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + name: 'lineGrid', + strokeDashArray: [5, 2], + opacity: 0.3, + direction: 'vertical', + }, + ) + canvas.add(verticalLine) + } + } + + canvas.renderAll() + } + + const handleRadioChange = (e) => { + const { value, name, checked, selected } = e.target + + setDotLineGridSettingState((prev) => { + return { + ...prev, + INTERVAL: { + ...prev.INTERVAL, + type: Number(value), + }, + } + }) + } + + const changeInput = (value, e) => { + const { name } = e.target + setDotLineGridSettingState((prev) => { + return { + ...prev, + INTERVAL: { + ...prev.INTERVAL, + [name]: value, + }, + } + }) + } + + const changeDimension = (result) => { + const { value } = result + setDotLineGridSettingState((prev) => { + return { + ...prev, + INTERVAL: { + ...prev.INTERVAL, + dimension: value, + }, + } + }) + } + + // 초기화 + const reset = () => { + canvas + ?.getObjects() + .filter((obj) => obj.name === 'lineGrid') + .forEach((obj) => canvas?.remove(obj)) + canvas + ?.getObjects() + .filter((obj) => obj.name === 'dotGrid') + .forEach((obj) => canvas?.remove(obj)) + resetDotLineGridSetting() + setSelectOption(SelectOption[0]) + } + return (
@@ -29,55 +233,91 @@ export default function DotLineGrid(props) {
- +
- +
- +
{getMessage('modal.canvas.setting.grid.dot.line.setting.horizon')}
- + onlyNumberInputChange(e, changeInput)} + />
mm
{getMessage('modal.canvas.setting.grid.dot.line.setting.vertical')}
- + onlyNumberInputChange(e, changeInput)} + />
mm
- +
{getMessage('modal.canvas.setting.grid.dot.line.setting.ratio')}
- + onlyNumberInputChange(e, changeInput)} + />
mm
- +
- - + +
diff --git a/src/components/floor-plan/modal/outerlinesetting/OuterLineWall.jsx b/src/components/floor-plan/modal/outerlinesetting/OuterLineWall.jsx index 386e98cd..7d389540 100644 --- a/src/components/floor-plan/modal/outerlinesetting/OuterLineWall.jsx +++ b/src/components/floor-plan/modal/outerlinesetting/OuterLineWall.jsx @@ -8,8 +8,10 @@ import { useEvent } from '@/hooks/useEvent' import { adsorptionPointAddModeState, adsorptionPointModeState, + adsorptionRangeState, canvasHistoryState, canvasState, + dotLineIntervalSelector, verticalHorizontalModeState, } from '@/store/canvasAtom' import { @@ -27,22 +29,22 @@ import { distanceBetweenPoints } from '@/util/canvas-util' import { calculateAngle } from '@/util/qpolygon-utils' import { usePolygon } from '@/hooks/usePolygon' import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils' +import { useMouse } from '@/hooks/useMouse' export default function OuterLineWall(props) { const { setShowOutlineModal } = props const { getMessage } = useMessage() - const { - addCanvasMouseEventListener, - addDocumentEventListener, - removeAllMouseEventListeners, - removeAllDocumentEventListeners, - removeMouseEvent, - getIntersectMousePoint, - } = useEvent() + const { addCanvasMouseEventListener, addDocumentEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseEvent } = + useEvent() + const { getIntersectMousePoint } = useMouse() const { addLine, removeLine } = useLine() + const { addPolygonByLines } = usePolygon() const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState) const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState) + const adsorptionPointMode = useRecoilValue(adsorptionPointModeState) + const adsorptionRange = useRecoilValue(adsorptionRangeState) + const interval = useRecoilValue(dotLineIntervalSelector) // 가로 세로 간격 const length1Ref = useRef(null) const length2Ref = useRef(null) @@ -72,7 +74,7 @@ export default function OuterLineWall(props) { return () => { removeAllMouseEventListeners() } - }, [verticalHorizontalMode, points, adsorptionPointAddMode]) + }, [verticalHorizontalMode, points, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, interval]) useEffect(() => { arrow1Ref.current = arrow1 diff --git a/src/components/floor-plan/modal/setting01/SecondOption.jsx b/src/components/floor-plan/modal/setting01/SecondOption.jsx index e850f403..3aa2567d 100644 --- a/src/components/floor-plan/modal/setting01/SecondOption.jsx +++ b/src/components/floor-plan/modal/setting01/SecondOption.jsx @@ -113,11 +113,11 @@ export default function SecondOption() { // HTTP POST 요청 보내기 await post({ url: `/api/canvas-management/canvas-settings`, data: patternData }).then((res) => { toastUp({ message: getMessage(res.returnMessage), type: 'success' }) - setAdsorptionRange(option.range) }) } catch (error) { toastUp({ message: getMessage(res.returnMessage), type: 'error' }) } + setAdsorptionRange(option.range) } return ( <> diff --git a/src/hooks/useAdsorptionPoint.js b/src/hooks/useAdsorptionPoint.js new file mode 100644 index 00000000..44e2495b --- /dev/null +++ b/src/hooks/useAdsorptionPoint.js @@ -0,0 +1,45 @@ +import { useRecoilState, useRecoilValue } from 'recoil' +import { adsorptionPointAddModeState, adsorptionPointModeState, adsorptionRangeState, canvasState } from '@/store/canvasAtom' +import { fabric } from 'fabric' +import { useMouse } from '@/hooks/useMouse' + +export function useAdsorptionPoint() { + const canvas = useRecoilValue(canvasState) + const [adsorptionPointAddMode, setAdsorptionPointAddMode] = useRecoilState(adsorptionPointAddModeState) + const [adsorptionPointMode, setAdsorptionPointMode] = useRecoilState(adsorptionPointModeState) + const [adsorptionRange, setAdsorptionRange] = useRecoilState(adsorptionRangeState) + + const { getIntersectMousePoint } = useMouse() + + const getAdsorptionPoints = () => { + return canvas.getObjects().filter((obj) => obj.visible && obj.name === 'adsorptionPoint') + } + + const adsorptionPointAddModeStateEvent = (opt) => { + //흡착점 모드일 경우 + let pointer = getIntersectMousePoint(opt) + + const adsorptionPoint = new fabric.Circle({ + radius: 3, + fill: 'red', + left: pointer.x - 3, + top: pointer.y - 3, + x: pointer.x, + y: pointer.y, + selectable: false, + name: 'adsorptionPoint', + }) + + canvas.add(adsorptionPoint) + + canvas.renderAll() + } + + return { + adsorptionPointAddMode, + adsorptionPointMode, + adsorptionRange, + getAdsorptionPoints, + adsorptionPointAddModeStateEvent, + } +} diff --git a/src/hooks/useDotLineGrid.js b/src/hooks/useDotLineGrid.js new file mode 100644 index 00000000..7ccd87c6 --- /dev/null +++ b/src/hooks/useDotLineGrid.js @@ -0,0 +1,51 @@ +import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil' +import { canvasState, dotLineGridSettingState, dotLineIntervalSelector } from '@/store/canvasAtom' +import { calculateDistance } from '@/util/canvas-util' +import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint' + +export function useDotLineGrid() { + const canvas = useRecoilValue(canvasState) + const [dotLineGridSetting, setDotLineGridSettingState] = useRecoilState(dotLineGridSettingState) + const interval = useRecoilValue(dotLineIntervalSelector) // 가로 세로 간격 + + const { adsorptionRange } = useAdsorptionPoint() + + const resetDotLineGridSetting = useResetRecoilState(dotLineGridSettingState) + + const getLineGrids = () => { + return canvas.getObjects().filter((obj) => obj.name === 'lineGrid') + } + + const getClosestLineGrid = (point) => { + const lines = getLineGrids() + if (lines.length === 0) { + return null + } + + let closestLine = null + let minDistance = Infinity + + lines.forEach((line) => { + const distance = calculateDistance(point, line) + + if (distance < minDistance) { + minDistance = distance + closestLine = line + } + }) + + if (minDistance > adsorptionRange) { + return null + } + + return closestLine + } + + return { + dotLineGridSetting, + resetDotLineGridSetting, + getLineGrids, + getClosestLineGrid, + interval, + } +} diff --git a/src/hooks/useEvent.js b/src/hooks/useEvent.js index 34af6ca9..01f61d3c 100644 --- a/src/hooks/useEvent.js +++ b/src/hooks/useEvent.js @@ -9,7 +9,10 @@ import { currentMenuState, } from '@/store/canvasAtom' import { fabric } from 'fabric' -import { calculateIntersection, distanceBetweenPoints } from '@/util/canvas-util' +import { calculateDistance, calculateIntersection, distanceBetweenPoints, findClosestPoint } from '@/util/canvas-util' +import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint' +import { useMouse } from '@/hooks/useMouse' +import { useDotLineGrid } from '@/hooks/useDotLineGrid' export function useEvent() { const canvas = useRecoilValue(canvasState) @@ -17,10 +20,9 @@ export function useEvent() { const keyboardEventListeners = useRef([]) const mouseEventListeners = useRef([]) const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState) - const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState) - const adsorptionPointMode = useRecoilValue(adsorptionPointModeState) - const adsorptionRange = useRecoilValue(adsorptionRangeState) + const { adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, getAdsorptionPoints, adsorptionPointAddModeStateEvent } = useAdsorptionPoint() + const { dotLineGridSetting, interval, getClosestLineGrid } = useDotLineGrid() useEffect(() => { if (!canvas) { return @@ -35,7 +37,7 @@ export function useEvent() { canvas?.on('mouse:wheel', wheelEvent) addDefaultEvent() - }, [currentMenu, canvas, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange]) + }, [currentMenu, canvas, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, dotLineGridSetting]) const addDefaultEvent = () => { //default Event 추가 @@ -53,26 +55,6 @@ export function useEvent() { e.stopPropagation() } - const adsorptionPointAddModeStateEvent = (opt) => { - //흡착점 모드일 경우 - let pointer = getIntersectMousePoint(opt) - - const adsorptionPoint = new fabric.Circle({ - radius: 3, - fill: 'red', - left: pointer.x - 3, - top: pointer.y - 3, - x: pointer.x, - y: pointer.y, - selectable: false, - name: 'adsorptionPoint', - }) - - canvas.add(adsorptionPoint) - - canvas.renderAll() - } - const wheelEvent = (opt) => { const delta = opt.e.deltaY // 휠 이동 값 (양수면 축소, 음수면 확대) let zoom = canvas.getZoom() // 현재 줌 값 @@ -107,17 +89,61 @@ export function useEvent() { let arrivalPoint = { x: pointer.x, y: pointer.y } if (adsorptionPointMode) { - // pointer와 adsorptionPoints의 거리를 계산하여 가장 가까운 점을 찾는다. - let minDistance = adsorptionRange - let adsorptionPoint = null - adsorptionPoints.forEach((point) => { - const distance = distanceBetweenPoints(pointer, point) - if (distance < minDistance) { - minDistance = distance - adsorptionPoint = point + if (dotLineGridSetting.LINE) { + const closestLine = getClosestLineGrid(pointer) + + if (closestLine) { + const distance = calculateDistance(pointer, closestLine) + + if (distance < adsorptionRange) { + arrivalPoint = closestLine.direction === 'vertical' ? { x: closestLine.x1, y: pointer.y } : { x: pointer.x, y: closestLine.y1 } + } } - }) - if (adsorptionPoint) { + } + + if (dotLineGridSetting.DOT) { + const horizontalInterval = interval.horizontalInterval + const verticalInterval = interval.verticalInterval + + const x = pointer.x - horizontalInterval * Math.floor(pointer.x / horizontalInterval) + const y = pointer.y - verticalInterval * Math.floor(pointer.y / verticalInterval) + + const xRate = (x / horizontalInterval) * 100 + const yRate = (y / verticalInterval) * 100 + + let tempPoint + + if (xRate <= adsorptionRange && yRate <= adsorptionRange) { + tempPoint = { + x: Math.round(pointer.x / horizontalInterval) * horizontalInterval + horizontalInterval / 2, + y: Math.round(pointer.y / verticalInterval) * verticalInterval + horizontalInterval / 2, + } + } else if (xRate <= adsorptionRange && yRate >= adsorptionRange) { + tempPoint = { + x: Math.round(pointer.x / horizontalInterval) * horizontalInterval + horizontalInterval / 2, + y: Math.round(pointer.y / verticalInterval) * verticalInterval - horizontalInterval / 2, + } + } else if (xRate >= adsorptionRange && yRate <= adsorptionRange) { + tempPoint = { + x: Math.round(pointer.x / horizontalInterval) * horizontalInterval - horizontalInterval / 2, + y: Math.round(pointer.y / verticalInterval) * verticalInterval + horizontalInterval / 2, + } + } else if (xRate >= adsorptionRange && yRate >= adsorptionRange) { + tempPoint = { + x: Math.round(pointer.x / horizontalInterval) * horizontalInterval - horizontalInterval / 2, + y: Math.round(pointer.y / verticalInterval) * verticalInterval - horizontalInterval / 2, + } + } + + if (distanceBetweenPoints(pointer, tempPoint) <= adsorptionRange) { + arrivalPoint = tempPoint + } + } + + // pointer와 adsorptionPoints의 거리를 계산하여 가장 가까운 점을 찾는다. + let adsorptionPoint = findClosestPoint(pointer, adsorptionPoints) + + if (adsorptionPoint && distanceBetweenPoints(pointer, adsorptionPoint) <= adsorptionRange) { arrivalPoint = { ...adsorptionPoint } } } @@ -203,28 +229,11 @@ export function useEvent() { }) } - const getAdsorptionPoints = () => { - return canvas.getObjects().filter((obj) => obj.visible && obj.name === 'adsorptionPoint') - } - - //가로선, 세로선의 교차점을 return - const getIntersectMousePoint = (e) => { - let pointer = canvas.getPointer(e.e) - const mouseLines = canvas.getObjects().filter((obj) => obj.name === 'mouseLine') - - if (mouseLines.length < 2) { - return pointer - } - - return calculateIntersection(mouseLines[0], mouseLines[1]) ?? pointer - } - return { addDocumentEventListener, addCanvasMouseEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseEvent, - getIntersectMousePoint, } } diff --git a/src/hooks/useMouse.js b/src/hooks/useMouse.js new file mode 100644 index 00000000..d484930a --- /dev/null +++ b/src/hooks/useMouse.js @@ -0,0 +1,23 @@ +import { useRecoilValue } from 'recoil' +import { canvasState } from '@/store/canvasAtom' +import { calculateIntersection } from '@/util/canvas-util' + +export function useMouse() { + const canvas = useRecoilValue(canvasState) + + //가로선, 세로선의 교차점을 return + const getIntersectMousePoint = (e) => { + let pointer = canvas.getPointer(e.e) + const mouseLines = canvas.getObjects().filter((obj) => obj.name === 'mouseLine') + + if (mouseLines.length < 2) { + return pointer + } + + return calculateIntersection(mouseLines[0], mouseLines[1]) || pointer + } + + return { + getIntersectMousePoint, + } +} diff --git a/src/store/canvasAtom.js b/src/store/canvasAtom.js index 7f942f59..89acba2f 100644 --- a/src/store/canvasAtom.js +++ b/src/store/canvasAtom.js @@ -205,7 +205,7 @@ export const verticalHorizontalModeState = atom({ // 흡착점 모드 export const adsorptionPointModeState = atom({ key: 'adsorptionPointModeState', - default: false, + default: true, }) // 흡착점 추가모드 export const adsorptionPointAddModeState = atom({ @@ -218,3 +218,35 @@ export const adsorptionRangeState = atom({ key: 'adsorptionRangeState', default: 50, }) + +// 점,선 그리드 설정 +export const dotLineGridSettingState = atom({ + key: 'gridSettingState', + default: { + INTERVAL: { + type: 2, // 1: 가로,세로 간격 수동, 2: 비율 간격 + ratioInterval: 910, + verticalInterval: 910, + horizontalInterval: 910, + dimension: 1, // 치수 + }, + DOT: false, + LINE: false, + }, +}) + +export const dotLineIntervalSelector = selector({ + key: 'dotLineIntervalSelector', + get: ({ get }) => { + const gridSetting = get(dotLineGridSettingState) + return gridSetting.INTERVAL.type === 1 + ? { + horizontalInterval: Math.round(gridSetting.INTERVAL.horizontalInterval / 10), + verticalInterval: Math.round(gridSetting.INTERVAL.verticalInterval / 10), + } + : { + horizontalInterval: Math.round((gridSetting.INTERVAL.ratioInterval * gridSetting.INTERVAL.dimension) / 10), + verticalInterval: Math.round((gridSetting.INTERVAL.ratioInterval * gridSetting.INTERVAL.dimension) / 10), + } + }, +}) diff --git a/src/store/settingAtom.js b/src/store/settingAtom.js index 5541cc14..8c9f976d 100644 --- a/src/store/settingAtom.js +++ b/src/store/settingAtom.js @@ -50,9 +50,9 @@ export const settingModalSecondOptionsState = atom({ ], option4: [ { id: 1, column: 'adsorpRangeSmall', name: 'modal.canvas.setting.font.plan.absorption.small', selected: true, range: 10 }, - { id: 2, column: 'adsorpRangeSmallSemi', name: 'modal.canvas.setting.font.plan.absorption.small.semi', selected: false, range: 30 }, - { id: 3, column: 'adsorpRangeMedium', name: 'modal.canvas.setting.font.plan.absorption.medium', selected: false, range: 50 }, - { id: 4, column: 'adsorpRangeLarge', name: 'modal.canvas.setting.font.plan.absorption.large', selected: false, range: 80 }, + { id: 2, column: 'adsorpRangeSmallSemi', name: 'modal.canvas.setting.font.plan.absorption.small.semi', selected: false, range: 20 }, + { id: 3, column: 'adsorpRangeMedium', name: 'modal.canvas.setting.font.plan.absorption.medium', selected: false, range: 30 }, + { id: 4, column: 'adsorpRangeLarge', name: 'modal.canvas.setting.font.plan.absorption.large', selected: false, range: 40 }, ], }, dangerouslyAllowMutability: true, diff --git a/src/util/input-utils.js b/src/util/input-utils.js index e2409cab..f674e6cd 100644 --- a/src/util/input-utils.js +++ b/src/util/input-utils.js @@ -2,7 +2,7 @@ export const onlyNumberInputChange = (e, callback) => { let value = e.target.value.replace(/^0+/, '') value = value.replace(/[^-0-9]/g, '') - callback(value) + callback(value, e) } //소수점 둘째자리 숫자만 입력가능 @@ -12,9 +12,9 @@ export const onlyNumberWithDotInputChange = (e, callback) => { const pattern = /^-?(\d{1,4}([.]\d{0,2})?)?$/ if (!pattern.test(val)) { // prev에서 마지막 자리 제거 - callback(val.slice(0, val.length - 1)) + callback(val.slice(0, val.length - 1), e) return } - callback(val) + callback(val, e) }