Compare commits
No commits in common. "a8932f1e6692963bf93b00cf71aff4573d4c47d0" and "5f776f896fee5b171f611f4f667eeba537a2b8a0" have entirely different histories.
a8932f1e66
...
5f776f896f
8
bun.lock
8
bun.lock
@ -56,7 +56,7 @@
|
|||||||
"echarts": "^6.1.0",
|
"echarts": "^6.1.0",
|
||||||
"echarts-for-react": "^3.0.6",
|
"echarts-for-react": "^3.0.6",
|
||||||
"electron-store": "^11.0.2",
|
"electron-store": "^11.0.2",
|
||||||
"gcode-preview": "3.0.0-alpha.6",
|
"gcode-preview": "^2.18.0",
|
||||||
"json-schema-traverse": "^1.0.0",
|
"json-schema-traverse": "^1.0.0",
|
||||||
"keycloak-js": "^26.2.0",
|
"keycloak-js": "^26.2.0",
|
||||||
"lodash": "^4.17.23",
|
"lodash": "^4.17.23",
|
||||||
@ -1500,7 +1500,7 @@
|
|||||||
|
|
||||||
"galactus": ["galactus@1.0.0", "", { "dependencies": { "debug": "^4.3.4", "flora-colossus": "^2.0.0", "fs-extra": "^10.1.0" } }, "sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ=="],
|
"galactus": ["galactus@1.0.0", "", { "dependencies": { "debug": "^4.3.4", "flora-colossus": "^2.0.0", "fs-extra": "^10.1.0" } }, "sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ=="],
|
||||||
|
|
||||||
"gcode-preview": ["gcode-preview@3.0.0-alpha.6", "", { "dependencies": { "lil-gui": "^0.20.0", "three": ">=0.166.0 <0.186.0" } }, "sha512-pLgIhufGNdo6ChTrFeuRsjvl/eg2rH1LK4E+qUaLsL0BBWIE/phry2zfxuXHxBSdAKA5+cbXGpCmp/RsxHgVUQ=="],
|
"gcode-preview": ["gcode-preview@2.18.0", "", { "dependencies": { "lil-gui": "0.19.2", "three": "0.159.0" } }, "sha512-uc9QYciG6ES/A6BWJpUZk4fHxCPvt5EnvDhHIHDbNdR/m3f9VkGvpSMh9HDygXjAXX0x1Lbz/e9ZGlIrYNB29A=="],
|
||||||
|
|
||||||
"generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
|
"generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
|
||||||
|
|
||||||
@ -1748,7 +1748,7 @@
|
|||||||
|
|
||||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "1.2.1", "type-check": "0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "1.2.1", "type-check": "0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||||
|
|
||||||
"lil-gui": ["lil-gui@0.20.0", "", {}, "sha512-k7Ipr0ztqslMA2XvM5z5ZaWhxQtnEOwJBfI/hmSuRh6q4iMG9L0boqqrnZSzBR1jzyJ28OMl47l65ILzRe1TdA=="],
|
"lil-gui": ["lil-gui@0.19.2", "", {}, "sha512-nU8j4ND702ouGfQZoaTN4dfXxacvGOAVK0DtmZBVcUYUAeYQXLQAjAN50igMHiba3T5jZyKEjXZU+Ntm1Qs6ZQ=="],
|
||||||
|
|
||||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||||
|
|
||||||
@ -2894,6 +2894,8 @@
|
|||||||
|
|
||||||
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||||
|
|
||||||
|
"gcode-preview/three": ["three@0.159.0", "", {}, "sha512-eCmhlLGbBgucuo4VEA9IO3Qpc7dh8Bd4VKzr7WfW4+8hMcIfoAVi1ev0pJYN9PTTsCslbcKgBwr2wNZ1EvLInA=="],
|
||||||
|
|
||||||
"get-package-info/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
"get-package-info/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||||
|
|
||||||
"get-source/data-uri-to-buffer": ["data-uri-to-buffer@2.0.2", "", {}, "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA=="],
|
"get-source/data-uri-to-buffer": ["data-uri-to-buffer@2.0.2", "", {}, "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA=="],
|
||||||
|
|||||||
@ -60,7 +60,7 @@
|
|||||||
"echarts": "^6.1.0",
|
"echarts": "^6.1.0",
|
||||||
"echarts-for-react": "^3.0.6",
|
"echarts-for-react": "^3.0.6",
|
||||||
"electron-store": "^11.0.2",
|
"electron-store": "^11.0.2",
|
||||||
"gcode-preview": "3.0.0-alpha.6",
|
"gcode-preview": "^2.18.0",
|
||||||
"json-schema-traverse": "^1.0.0",
|
"json-schema-traverse": "^1.0.0",
|
||||||
"keycloak-js": "^26.2.0",
|
"keycloak-js": "^26.2.0",
|
||||||
"lodash": "^4.17.23",
|
"lodash": "^4.17.23",
|
||||||
|
|||||||
25
pnpm-lock.yaml
generated
25
pnpm-lock.yaml
generated
@ -170,8 +170,8 @@ importers:
|
|||||||
specifier: ^11.0.2
|
specifier: ^11.0.2
|
||||||
version: 11.0.2
|
version: 11.0.2
|
||||||
gcode-preview:
|
gcode-preview:
|
||||||
specifier: 3.0.0-alpha.6
|
specifier: ^2.18.0
|
||||||
version: 3.0.0-alpha.6
|
version: 2.18.0
|
||||||
json-schema-traverse:
|
json-schema-traverse:
|
||||||
specifier: ^1.0.0
|
specifier: ^1.0.0
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
@ -3798,8 +3798,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ==}
|
resolution: {integrity: sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ==}
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
|
|
||||||
gcode-preview@3.0.0-alpha.6:
|
gcode-preview@2.18.0:
|
||||||
resolution: {integrity: sha512-pLgIhufGNdo6ChTrFeuRsjvl/eg2rH1LK4E+qUaLsL0BBWIE/phry2zfxuXHxBSdAKA5+cbXGpCmp/RsxHgVUQ==}
|
resolution: {integrity: sha512-uc9QYciG6ES/A6BWJpUZk4fHxCPvt5EnvDhHIHDbNdR/m3f9VkGvpSMh9HDygXjAXX0x1Lbz/e9ZGlIrYNB29A==}
|
||||||
|
|
||||||
generator-function@2.0.1:
|
generator-function@2.0.1:
|
||||||
resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
|
resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
|
||||||
@ -4325,8 +4325,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
lil-gui@0.20.0:
|
lil-gui@0.19.2:
|
||||||
resolution: {integrity: sha512-k7Ipr0ztqslMA2XvM5z5ZaWhxQtnEOwJBfI/hmSuRh6q4iMG9L0boqqrnZSzBR1jzyJ28OMl47l65ILzRe1TdA==}
|
resolution: {integrity: sha512-nU8j4ND702ouGfQZoaTN4dfXxacvGOAVK0DtmZBVcUYUAeYQXLQAjAN50igMHiba3T5jZyKEjXZU+Ntm1Qs6ZQ==}
|
||||||
|
|
||||||
lines-and-columns@1.2.4:
|
lines-and-columns@1.2.4:
|
||||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||||
@ -6134,6 +6134,9 @@ packages:
|
|||||||
text-table@0.2.0:
|
text-table@0.2.0:
|
||||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||||
|
|
||||||
|
three@0.159.0:
|
||||||
|
resolution: {integrity: sha512-eCmhlLGbBgucuo4VEA9IO3Qpc7dh8Bd4VKzr7WfW4+8hMcIfoAVi1ev0pJYN9PTTsCslbcKgBwr2wNZ1EvLInA==}
|
||||||
|
|
||||||
three@0.165.0:
|
three@0.165.0:
|
||||||
resolution: {integrity: sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA==}
|
resolution: {integrity: sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA==}
|
||||||
|
|
||||||
@ -10854,10 +10857,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
gcode-preview@3.0.0-alpha.6:
|
gcode-preview@2.18.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
lil-gui: 0.20.0
|
lil-gui: 0.19.2
|
||||||
three: 0.179.1
|
three: 0.159.0
|
||||||
|
|
||||||
generator-function@2.0.1: {}
|
generator-function@2.0.1: {}
|
||||||
|
|
||||||
@ -11440,7 +11443,7 @@ snapshots:
|
|||||||
prelude-ls: 1.2.1
|
prelude-ls: 1.2.1
|
||||||
type-check: 0.4.0
|
type-check: 0.4.0
|
||||||
|
|
||||||
lil-gui@0.20.0: {}
|
lil-gui@0.19.2: {}
|
||||||
|
|
||||||
lines-and-columns@1.2.4: {}
|
lines-and-columns@1.2.4: {}
|
||||||
|
|
||||||
@ -13810,6 +13813,8 @@ snapshots:
|
|||||||
|
|
||||||
text-table@0.2.0: {}
|
text-table@0.2.0: {}
|
||||||
|
|
||||||
|
three@0.159.0: {}
|
||||||
|
|
||||||
three@0.165.0: {}
|
three@0.165.0: {}
|
||||||
|
|
||||||
three@0.176.0: {}
|
three@0.176.0: {}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { GCodePreview } from 'gcode-preview'
|
import * as GCodePreview from 'gcode-preview'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Button, Card, Flex, Select, Slider } from 'antd'
|
import { Button, Card, Flex, Slider } from 'antd'
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import * as THREE from 'three'
|
import * as THREE from 'three'
|
||||||
import PlusIcon from '../../Icons/PlusIcon.jsx'
|
import PlusIcon from '../../Icons/PlusIcon.jsx'
|
||||||
@ -15,11 +15,11 @@ import usePinchZoom, {
|
|||||||
} from '../hooks/usePinchZoom.js'
|
} from '../hooks/usePinchZoom.js'
|
||||||
import { useThemeContext } from '../context/ThemeContext'
|
import { useThemeContext } from '../context/ThemeContext'
|
||||||
|
|
||||||
function toCssColor(color, fallback) {
|
function toThreeColorHex(color, fallback) {
|
||||||
try {
|
try {
|
||||||
return `#${new THREE.Color(color || fallback).getHexString()}`
|
return new THREE.Color(color || fallback).getHex()
|
||||||
} catch {
|
} catch {
|
||||||
return fallback
|
return new THREE.Color(fallback).getHex()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,21 +38,19 @@ function getPreviewThemeColors({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCameraDistance(sceneManager) {
|
function getCameraDistance(preview) {
|
||||||
const distance = sceneManager.camera.position.distanceTo(
|
const distance = preview.camera.position.distanceTo(preview.controls.target)
|
||||||
sceneManager.controls.target
|
|
||||||
)
|
|
||||||
return Number.isFinite(distance) && distance > 0 ? distance : null
|
return Number.isFinite(distance) && distance > 0 ? distance : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCameraScale(sceneManager, baseDistance) {
|
function getCameraScale(preview, baseDistance) {
|
||||||
const distance = getCameraDistance(sceneManager)
|
const distance = getCameraDistance(preview)
|
||||||
return distance == null ? null : clampPreviewScale(baseDistance / distance)
|
return distance == null ? null : clampPreviewScale(baseDistance / distance)
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyCameraScale(sceneManager, baseDistance, scale) {
|
function applyCameraScale(preview, baseDistance, scale) {
|
||||||
const { camera, controls } = sceneManager
|
const { camera, controls } = preview
|
||||||
const distance = getCameraDistance(sceneManager)
|
const distance = getCameraDistance(preview)
|
||||||
if (distance == null) return
|
if (distance == null) return
|
||||||
|
|
||||||
const nextDistance = baseDistance / scale
|
const nextDistance = baseDistance / scale
|
||||||
@ -86,15 +84,9 @@ function GCodePreviewUI(props) {
|
|||||||
const [previewScale, setPreviewScale] = useState(1)
|
const [previewScale, setPreviewScale] = useState(1)
|
||||||
const [panMode, setPanMode] = useState(false)
|
const [panMode, setPanMode] = useState(false)
|
||||||
const [isDragging, setIsDragging] = useState(false)
|
const [isDragging, setIsDragging] = useState(false)
|
||||||
const [renderMode, setRenderMode] = useState('lines')
|
|
||||||
const [travelVisibility, setTravelVisibility] = useState('hide')
|
|
||||||
const renderModeRef = useRef(renderMode)
|
|
||||||
const travelVisibilityRef = useRef(travelVisibility)
|
|
||||||
renderModeRef.current = renderMode
|
|
||||||
travelVisibilityRef.current = travelVisibility
|
|
||||||
|
|
||||||
const resizePreview = useCallback(() => {
|
const resizePreview = useCallback(() => {
|
||||||
previewRef.current?.sceneManager?.resize()
|
previewRef.current?.resize()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -107,27 +99,28 @@ function GCodePreviewUI(props) {
|
|||||||
lastSegmentColor
|
lastSegmentColor
|
||||||
})
|
})
|
||||||
|
|
||||||
previewRef.current?.dispose()
|
previewRef.current?.dispose?.()
|
||||||
const instance = new GCodePreview({
|
const instance = GCodePreview.init({
|
||||||
canvas: canvasRef.current,
|
canvas: canvasRef.current,
|
||||||
startLayer,
|
startLayer,
|
||||||
endLayer,
|
endLayer,
|
||||||
lineWidth,
|
lineWidth,
|
||||||
backgroundColor: toCssColor(themeColors.backgroundColor, '#ffffff'),
|
backgroundColor: toThreeColorHex(themeColors.backgroundColor, '#ffffff'),
|
||||||
extrusionColor: toCssColor(themeColors.extrusionColor, '#0091ff'),
|
extrusionColor: toThreeColorHex(themeColors.extrusionColor, '#0091FF'),
|
||||||
travelColor: toCssColor(themeColors.travelColor, '#8c8c8c'),
|
travelColor: toThreeColorHex(themeColors.travelColor, '#8c8c8c'),
|
||||||
topLayerColor: toCssColor(themeColors.topLayerColor, '#ff9230'),
|
topLayerColor: toThreeColorHex(themeColors.topLayerColor, '#FF9230'),
|
||||||
lastSegmentColor: toCssColor(themeColors.lastSegmentColor, '#ff69b4'),
|
lastSegmentColor: toThreeColorHex(
|
||||||
|
themeColors.lastSegmentColor,
|
||||||
|
'#FF69B4'
|
||||||
|
),
|
||||||
buildVolume: { x: 250, y: 220, z: 150 },
|
buildVolume: { x: 250, y: 220, z: 150 },
|
||||||
initialCameraPosition: [0, 400, 450],
|
initialCameraPosition: [0, 400, 450],
|
||||||
droppable: false,
|
allowDragNDrop: false
|
||||||
renderTubes: renderModeRef.current === 'pipes',
|
|
||||||
renderTravel: travelVisibilityRef.current === 'show'
|
|
||||||
})
|
})
|
||||||
previewRef.current = instance
|
previewRef.current = instance
|
||||||
|
|
||||||
const { camera, controls } = instance.sceneManager
|
const { camera, controls } = instance
|
||||||
baseDistanceRef.current = getCameraDistance(instance.sceneManager) || 1
|
baseDistanceRef.current = getCameraDistance(instance) || 1
|
||||||
// The camera dollies instead of scaling pixels, so the frustum has to
|
// The camera dollies instead of scaling pixels, so the frustum has to
|
||||||
// cover the full zoom range.
|
// cover the full zoom range.
|
||||||
camera.far = Math.max(
|
camera.far = Math.max(
|
||||||
@ -140,10 +133,7 @@ function GCodePreviewUI(props) {
|
|||||||
|
|
||||||
const handleControlsChange = () => {
|
const handleControlsChange = () => {
|
||||||
if (applyingScaleRef.current) return
|
if (applyingScaleRef.current) return
|
||||||
const scale = getCameraScale(
|
const scale = getCameraScale(instance, baseDistanceRef.current)
|
||||||
instance.sceneManager,
|
|
||||||
baseDistanceRef.current
|
|
||||||
)
|
|
||||||
if (scale == null) return
|
if (scale == null) return
|
||||||
setPreviewScale((prev) => (Math.abs(prev - scale) < 0.001 ? prev : scale))
|
setPreviewScale((prev) => (Math.abs(prev - scale) < 0.001 ? prev : scale))
|
||||||
}
|
}
|
||||||
@ -154,7 +144,7 @@ function GCodePreviewUI(props) {
|
|||||||
controls.addEventListener('start', handleControlsStart)
|
controls.addEventListener('start', handleControlsStart)
|
||||||
controls.addEventListener('end', handleControlsEnd)
|
controls.addEventListener('end', handleControlsEnd)
|
||||||
|
|
||||||
instance.sceneManager.resize()
|
instance.resize()
|
||||||
setPreview(instance)
|
setPreview(instance)
|
||||||
|
|
||||||
const pane = previewPaneRef.current
|
const pane = previewPaneRef.current
|
||||||
@ -173,8 +163,7 @@ function GCodePreviewUI(props) {
|
|||||||
controls.removeEventListener('end', handleControlsEnd)
|
controls.removeEventListener('end', handleControlsEnd)
|
||||||
setPreview(null)
|
setPreview(null)
|
||||||
setIsDragging(false)
|
setIsDragging(false)
|
||||||
instance.clear()
|
instance.dispose?.()
|
||||||
instance.dispose()
|
|
||||||
if (previewRef.current === instance) {
|
if (previewRef.current === instance) {
|
||||||
previewRef.current = null
|
previewRef.current = null
|
||||||
}
|
}
|
||||||
@ -195,39 +184,18 @@ function GCodePreviewUI(props) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!preview) return
|
if (!preview) return
|
||||||
applyingScaleRef.current = true
|
applyingScaleRef.current = true
|
||||||
applyCameraScale(
|
applyCameraScale(preview, baseDistanceRef.current, previewScale)
|
||||||
preview.sceneManager,
|
|
||||||
baseDistanceRef.current,
|
|
||||||
previewScale
|
|
||||||
)
|
|
||||||
applyingScaleRef.current = false
|
applyingScaleRef.current = false
|
||||||
}, [preview, previewScale])
|
}, [preview, previewScale])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!preview) return
|
if (!preview) return
|
||||||
const { sceneManager } = preview
|
preview.controls.mouseButtons = {
|
||||||
const renderTubes = renderMode === 'pipes'
|
|
||||||
const renderTravel = travelVisibility === 'show'
|
|
||||||
if (
|
|
||||||
sceneManager.renderTubes === renderTubes &&
|
|
||||||
sceneManager.renderTravel === renderTravel
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sceneManager.renderTubes = renderTubes
|
|
||||||
sceneManager.renderTravel = renderTravel
|
|
||||||
sceneManager.render()
|
|
||||||
}, [preview, renderMode, travelVisibility])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!preview) return
|
|
||||||
const { controls } = preview.sceneManager
|
|
||||||
controls.mouseButtons = {
|
|
||||||
LEFT: panMode ? THREE.MOUSE.PAN : THREE.MOUSE.ROTATE,
|
LEFT: panMode ? THREE.MOUSE.PAN : THREE.MOUSE.ROTATE,
|
||||||
MIDDLE: THREE.MOUSE.DOLLY,
|
MIDDLE: THREE.MOUSE.DOLLY,
|
||||||
RIGHT: panMode ? THREE.MOUSE.ROTATE : THREE.MOUSE.PAN
|
RIGHT: panMode ? THREE.MOUSE.ROTATE : THREE.MOUSE.PAN
|
||||||
}
|
}
|
||||||
controls.touches = {
|
preview.controls.touches = {
|
||||||
ONE: panMode ? THREE.TOUCH.PAN : THREE.TOUCH.ROTATE,
|
ONE: panMode ? THREE.TOUCH.PAN : THREE.TOUCH.ROTATE,
|
||||||
TWO: THREE.TOUCH.DOLLY_PAN
|
TWO: THREE.TOUCH.DOLLY_PAN
|
||||||
}
|
}
|
||||||
@ -241,49 +209,30 @@ function GCodePreviewUI(props) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const resetView = useCallback(() => {
|
const resetView = useCallback(() => {
|
||||||
preview?.sceneManager.controls.reset()
|
preview?.controls.reset()
|
||||||
setPreviewScale(1)
|
setPreviewScale(1)
|
||||||
}, [preview])
|
}, [preview])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const abortController = new AbortController()
|
let cancelled = false
|
||||||
|
|
||||||
const loadFromSrc = async () => {
|
const loadFromSrc = async () => {
|
||||||
const instance = previewRef.current
|
const instance = previewRef.current
|
||||||
if (!src || !instance) return
|
if (!src || !instance) return
|
||||||
try {
|
try {
|
||||||
const response = await fetch(src, { signal: abortController.signal })
|
const response = await fetch(src)
|
||||||
if (!response.ok || !response.body) {
|
const text = await response.text()
|
||||||
throw new Error(`Failed to load G-code (${response.status})`)
|
if (cancelled || previewRef.current !== instance) return
|
||||||
}
|
instance.processGCode(text)
|
||||||
if (abortController.signal.aborted || previewRef.current !== instance) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (instance.countLayers > 0) {
|
|
||||||
instance.clear()
|
|
||||||
instance.sceneManager.startLayer = startLayer
|
|
||||||
instance.sceneManager.endLayer = endLayer
|
|
||||||
}
|
|
||||||
const stream = response.body.pipeThrough(new TextDecoderStream())
|
|
||||||
await instance.processGCodeStream(stream)
|
|
||||||
if (abortController.signal.aborted || previewRef.current !== instance) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (typeof startLayer === 'number') {
|
|
||||||
instance.sceneManager.startLayer = startLayer
|
|
||||||
}
|
|
||||||
if (typeof endLayer === 'number') {
|
|
||||||
instance.sceneManager.endLayer = endLayer
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (abortController.signal.aborted) return
|
if (cancelled) return
|
||||||
console.error('Failed to load G-code from src', e)
|
console.error('Failed to load G-code from src', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadFromSrc()
|
loadFromSrc()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
abortController.abort()
|
cancelled = true
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
colorPink,
|
colorPink,
|
||||||
@ -304,7 +253,7 @@ function GCodePreviewUI(props) {
|
|||||||
gap={'middle'}
|
gap={'middle'}
|
||||||
style={{ width: '100%', height: '100%', minHeight: 0, ...style }}
|
style={{ width: '100%', height: '100%', minHeight: 0, ...style }}
|
||||||
>
|
>
|
||||||
<Flex gap={'small'} wrap='wrap' align='center'>
|
<Flex gap={'small'}>
|
||||||
<KeyboardShortcut
|
<KeyboardShortcut
|
||||||
shortcut={'alt+p'}
|
shortcut={'alt+p'}
|
||||||
hint={'ALT P'}
|
hint={'ALT P'}
|
||||||
@ -344,26 +293,6 @@ function GCodePreviewUI(props) {
|
|||||||
setPreviewScale((prev) => clampPreviewScale(prev - 0.05))
|
setPreviewScale((prev) => clampPreviewScale(prev - 0.05))
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Select
|
|
||||||
value={renderMode}
|
|
||||||
options={[
|
|
||||||
{ value: 'lines', label: 'Lines' },
|
|
||||||
{ value: 'pipes', label: 'Pipes' }
|
|
||||||
]}
|
|
||||||
style={{ minWidth: 77 }}
|
|
||||||
popupMatchSelectWidth={false}
|
|
||||||
onChange={setRenderMode}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
value={travelVisibility}
|
|
||||||
options={[
|
|
||||||
{ value: 'show', label: 'Show Travel' },
|
|
||||||
{ value: 'hide', label: 'Hide Travel' }
|
|
||||||
]}
|
|
||||||
style={{ minWidth: 118 }}
|
|
||||||
popupMatchSelectWidth={false}
|
|
||||||
onChange={setTravelVisibility}
|
|
||||||
/>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -22,8 +22,7 @@ const EMPTY_OBJECT = {}
|
|||||||
const areValuesEqual = (v1, v2) => {
|
const areValuesEqual = (v1, v2) => {
|
||||||
const id1 = v1 && typeof v1 === 'object' && v1._id ? v1._id : v1
|
const id1 = v1 && typeof v1 === 'object' && v1._id ? v1._id : v1
|
||||||
const id2 = v2 && typeof v2 === 'object' && v2._id ? v2._id : v2
|
const id2 = v2 && typeof v2 === 'object' && v2._id ? v2._id : v2
|
||||||
if (id1 == null || id2 == null) return id1 == id2
|
return String(id1) === String(id2)
|
||||||
return String(id1).toLowerCase() === String(id2).toLowerCase()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const toSelectValue = (id) => {
|
const toSelectValue = (id) => {
|
||||||
@ -250,15 +249,6 @@ const ObjectSelect = ({
|
|||||||
const filterRef = useRef(filter)
|
const filterRef = useRef(filter)
|
||||||
const masterFilterRef = useRef(masterFilter)
|
const masterFilterRef = useRef(masterFilter)
|
||||||
const clearedMissingValueRef = useRef(false)
|
const clearedMissingValueRef = useRef(false)
|
||||||
const loadGenerationRef = useRef(0)
|
|
||||||
const getSelectKey = useCallback(
|
|
||||||
(selectType, selectMasterFilter) =>
|
|
||||||
`${selectType}::${JSON.stringify(selectMasterFilter ?? {})}`,
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
const [committedSelectKey, setCommittedSelectKey] = useState(() =>
|
|
||||||
getSelectKey(type, masterFilter)
|
|
||||||
)
|
|
||||||
|
|
||||||
filterRef.current = filter
|
filterRef.current = filter
|
||||||
masterFilterRef.current = masterFilter
|
masterFilterRef.current = masterFilter
|
||||||
@ -376,14 +366,12 @@ const ObjectSelect = ({
|
|||||||
// Fetch the object properties tree from the API
|
// Fetch the object properties tree from the API
|
||||||
const handleFetchObjectsProperties = useCallback(
|
const handleFetchObjectsProperties = useCallback(
|
||||||
async (customFilter = filter, { replace = false } = {}) => {
|
async (customFilter = filter, { replace = false } = {}) => {
|
||||||
const generation = loadGenerationRef.current
|
|
||||||
try {
|
try {
|
||||||
const data = await fetchObjectsByProperty(type, {
|
const data = await fetchObjectsByProperty(type, {
|
||||||
properties: properties,
|
properties: properties,
|
||||||
filter: customFilter,
|
filter: customFilter,
|
||||||
masterFilter
|
masterFilter
|
||||||
})
|
})
|
||||||
if (generation !== loadGenerationRef.current) return null
|
|
||||||
|
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
setObjectPropertiesTree((prev) =>
|
setObjectPropertiesTree((prev) =>
|
||||||
@ -563,7 +551,6 @@ const ObjectSelect = ({
|
|||||||
(data) => {
|
(data) => {
|
||||||
const { treeNodes, objects } = buildTreeData(data)
|
const { treeNodes, objects } = buildTreeData(data)
|
||||||
setObjectList(objects)
|
setObjectList(objects)
|
||||||
objectListRef.current = objects
|
|
||||||
setTreeData(treeNodes)
|
setTreeData(treeNodes)
|
||||||
treeDataRef.current = treeNodes
|
treeDataRef.current = treeNodes
|
||||||
|
|
||||||
@ -687,64 +674,52 @@ const ObjectSelect = ({
|
|||||||
updateEventHandlerRef.current = updateEventHandler
|
updateEventHandlerRef.current = updateEventHandler
|
||||||
newEventHandlerRef.current = newEventHandler
|
newEventHandlerRef.current = newEventHandler
|
||||||
|
|
||||||
const findObjectById = useCallback((id) => {
|
|
||||||
if (id == null || id === '') return null
|
|
||||||
return (
|
|
||||||
objectListRef.current.find((obj) => areValuesEqual(obj._id, id)) || null
|
|
||||||
)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const onTreeSelectChange = useCallback(
|
const onTreeSelectChange = useCallback(
|
||||||
(nextValue) => {
|
(value) => {
|
||||||
const isEmptySelection = multiple
|
const isEmptySelection = multiple
|
||||||
? !Array.isArray(nextValue) || nextValue.length === 0
|
? !Array.isArray(value) || value.length === 0
|
||||||
: nextValue == null || nextValue === ''
|
: value == null || value === ''
|
||||||
|
if (
|
||||||
const pendingId = getValueId(valueRef.current)
|
isEmptySelection &&
|
||||||
const pendingInTree =
|
treeDataRef.current.length === 0 &&
|
||||||
pendingId != null &&
|
valueRef.current != null
|
||||||
(isValueInTree(treeDataRef.current, pendingId) ||
|
) {
|
||||||
findObjectById(pendingId) != null)
|
|
||||||
|
|
||||||
// TreeSelect emits empty/null when treeData is replaced or a controlled
|
|
||||||
// value is not in the tree yet. Don't wipe an external value we still own.
|
|
||||||
if (isEmptySelection && pendingId != null && !pendingInTree) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setValueNotFound(false)
|
setValueNotFound(false)
|
||||||
clearedMissingValueRef.current = false
|
clearedMissingValueRef.current = false
|
||||||
|
// Mark this as an internal change
|
||||||
if (!multiple) setIsSearching(false)
|
if (!multiple) setIsSearching(false)
|
||||||
isInternalChangeRef.current = true
|
isInternalChangeRef.current = true
|
||||||
|
|
||||||
|
// value can be a string (single) or array (multiple)
|
||||||
if (multiple) {
|
if (multiple) {
|
||||||
|
// Multiple selection
|
||||||
let selectedObjects = []
|
let selectedObjects = []
|
||||||
if (Array.isArray(nextValue)) {
|
if (Array.isArray(value)) {
|
||||||
selectedObjects = nextValue.map(findObjectById).filter(Boolean)
|
selectedObjects = value
|
||||||
|
.map((id) => objectList.find((obj) => areValuesEqual(obj._id, id)))
|
||||||
|
.filter(Boolean)
|
||||||
}
|
}
|
||||||
setTreeSelectValue(nextValue)
|
setTreeSelectValue(value)
|
||||||
onChange?.(selectedObjects)
|
onChange?.(selectedObjects)
|
||||||
return
|
} else {
|
||||||
|
// Single selection: replace the previous object instead of emitting
|
||||||
|
// undefined (lodash merge skips undefined and would keep the old value).
|
||||||
|
if (value == null || value === '') {
|
||||||
|
setTreeSelectValue(null)
|
||||||
|
onChange?.(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const selectedObject = objectList.find((obj) =>
|
||||||
|
areValuesEqual(obj._id, value)
|
||||||
|
)
|
||||||
|
setTreeSelectValue(value)
|
||||||
|
onChange?.(selectedObject ?? null)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextValue == null || nextValue === '') {
|
|
||||||
setTreeSelectValue(null)
|
|
||||||
onChange?.(null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedObject = findObjectById(nextValue)
|
|
||||||
setTreeSelectValue(nextValue)
|
|
||||||
if (selectedObject) {
|
|
||||||
onChange?.(selectedObject)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (pendingId != null && areValuesEqual(pendingId, nextValue)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
onChange?.(null)
|
|
||||||
},
|
},
|
||||||
[multiple, onChange, findObjectById]
|
[multiple, objectList, onChange]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onSearch = useCallback(
|
const onSearch = useCallback(
|
||||||
@ -856,74 +831,8 @@ const ObjectSelect = ({
|
|||||||
treeDataRef.current = treeData
|
treeDataRef.current = treeData
|
||||||
}, [treeData])
|
}, [treeData])
|
||||||
|
|
||||||
const prevValuesRef = useRef({ type, masterFilter })
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const prevValues = prevValuesRef.current
|
|
||||||
|
|
||||||
// Deep comparison for objects, simple comparison for primitives
|
|
||||||
const hasChanged =
|
|
||||||
prevValues.type !== type ||
|
|
||||||
JSON.stringify(prevValues.masterFilter) !== JSON.stringify(masterFilter)
|
|
||||||
|
|
||||||
if (hasChanged) {
|
|
||||||
loadGenerationRef.current += 1
|
|
||||||
searchRequestIdRef.current += 1
|
|
||||||
setIsSearching(false)
|
|
||||||
setSearchValue('')
|
|
||||||
setObjectPropertiesTree({})
|
|
||||||
setObjectList([])
|
|
||||||
setTreeData([])
|
|
||||||
treeDataRef.current = []
|
|
||||||
setTreeVersion((v) => v + 1)
|
|
||||||
setExpandedKeys([])
|
|
||||||
setInitialized(false)
|
|
||||||
valueRef.current = null
|
|
||||||
setTreeSelectValue(null)
|
|
||||||
setInitialLoading(true)
|
|
||||||
setReloading(false)
|
|
||||||
setError(false)
|
|
||||||
setValueNotFound(false)
|
|
||||||
clearedMissingValueRef.current = false
|
|
||||||
prevValuesRef.current = { type, masterFilter }
|
|
||||||
setCommittedSelectKey(getSelectKey(type, masterFilter))
|
|
||||||
}
|
|
||||||
}, [type, masterFilter, getSelectKey])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const currentValueIdentity = getValueIdentity(value)
|
|
||||||
const hasValueChanged =
|
|
||||||
prevValueIdentityRef.current !== currentValueIdentity
|
|
||||||
|
|
||||||
if (hasValueChanged) {
|
|
||||||
const changeSource = isInternalChangeRef.current ? 'internal' : 'external'
|
|
||||||
|
|
||||||
if (changeSource == 'external') {
|
|
||||||
loadGenerationRef.current += 1
|
|
||||||
setObjectPropertiesTree({})
|
|
||||||
setTreeData([])
|
|
||||||
treeDataRef.current = []
|
|
||||||
setInitialized(false)
|
|
||||||
setInitialLoading(true)
|
|
||||||
valueRef.current = null
|
|
||||||
clearedMissingValueRef.current = false
|
|
||||||
setValueNotFound(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
isInternalChangeRef.current = false
|
|
||||||
prevValueRef.current = value
|
|
||||||
prevValueIdentityRef.current = currentValueIdentity
|
|
||||||
}
|
|
||||||
}, [value, getValueIdentity])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (getSelectKey(type, masterFilter) !== committedSelectKey) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const generation = loadGenerationRef.current
|
|
||||||
const handleValue = async () => {
|
const handleValue = async () => {
|
||||||
if (generation !== loadGenerationRef.current) return
|
|
||||||
if (
|
if (
|
||||||
multiple &&
|
multiple &&
|
||||||
Array.isArray(value) &&
|
Array.isArray(value) &&
|
||||||
@ -932,15 +841,12 @@ const ObjectSelect = ({
|
|||||||
) {
|
) {
|
||||||
valueRef.current = value
|
valueRef.current = value
|
||||||
const fullValues = await Promise.all(value.map(fetchFullObjectIfNeeded))
|
const fullValues = await Promise.all(value.map(fetchFullObjectIfNeeded))
|
||||||
if (generation !== loadGenerationRef.current) return
|
|
||||||
const pathKeys = []
|
const pathKeys = []
|
||||||
|
|
||||||
if (fullValues.length === 0) {
|
if (fullValues.length === 0) {
|
||||||
const data = await handleFetchObjectsProperties()
|
handleFetchObjectsProperties()
|
||||||
if (generation !== loadGenerationRef.current) return
|
|
||||||
if (Array.isArray(data)) applyTreeFromData(data)
|
|
||||||
} else {
|
} else {
|
||||||
for (const fullValue of fullValues) {
|
fullValues.forEach((fullValue) => {
|
||||||
const valueFilter = { ...filter }
|
const valueFilter = { ...filter }
|
||||||
const parentKeys = []
|
const parentKeys = []
|
||||||
|
|
||||||
@ -968,10 +874,8 @@ const ObjectSelect = ({
|
|||||||
parentKeys.push(valueString)
|
parentKeys.push(valueString)
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = await handleFetchObjectsProperties(valueFilter)
|
handleFetchObjectsProperties(valueFilter)
|
||||||
if (generation !== loadGenerationRef.current) return
|
})
|
||||||
if (Array.isArray(data)) applyTreeFromData(data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setExpandedKeys([...new Set(pathKeys)])
|
setExpandedKeys([...new Set(pathKeys)])
|
||||||
@ -991,8 +895,9 @@ const ObjectSelect = ({
|
|||||||
type != 'unknown'
|
type != 'unknown'
|
||||||
) {
|
) {
|
||||||
valueRef.current = value
|
valueRef.current = value
|
||||||
|
// Check if value is a minimal object and fetch full object if needed
|
||||||
const fullValue = await fetchFullObjectIfNeeded(value)
|
const fullValue = await fetchFullObjectIfNeeded(value)
|
||||||
if (generation !== loadGenerationRef.current) return
|
// Build a new filter from value's properties that are in the properties list
|
||||||
const valueFilter = { ...filter }
|
const valueFilter = { ...filter }
|
||||||
const pathKeys = []
|
const pathKeys = []
|
||||||
const parentKeys = []
|
const parentKeys = []
|
||||||
@ -1017,6 +922,7 @@ const ObjectSelect = ({
|
|||||||
valueFilter[prop] = filterValue
|
valueFilter[prop] = filterValue
|
||||||
valueString = filterValue
|
valueString = filterValue
|
||||||
}
|
}
|
||||||
|
// Build the path key for this property level
|
||||||
const nodeKey = parentKeys
|
const nodeKey = parentKeys
|
||||||
.concat(prop + ':' + valueString)
|
.concat(prop + ':' + valueString)
|
||||||
.join('-')
|
.join('-')
|
||||||
@ -1024,10 +930,10 @@ const ObjectSelect = ({
|
|||||||
parentKeys.push(valueString)
|
parentKeys.push(valueString)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// Expand the path to the object
|
||||||
setExpandedKeys(pathKeys)
|
setExpandedKeys(pathKeys)
|
||||||
const data = await handleFetchObjectsProperties(valueFilter)
|
// Fetch with the new filter
|
||||||
if (generation !== loadGenerationRef.current) return
|
handleFetchObjectsProperties(valueFilter)
|
||||||
if (Array.isArray(data)) applyTreeFromData(data)
|
|
||||||
setTreeSelectValue(toSelectValue(valueRef.current._id))
|
setTreeSelectValue(toSelectValue(valueRef.current._id))
|
||||||
setInitialized(true)
|
setInitialized(true)
|
||||||
return
|
return
|
||||||
@ -1053,7 +959,13 @@ const ObjectSelect = ({
|
|||||||
setInitialized(true)
|
setInitialized(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleValue()
|
const timeoutId = setTimeout(() => {
|
||||||
|
handleValue()
|
||||||
|
}, 10)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
}
|
||||||
}, [
|
}, [
|
||||||
value,
|
value,
|
||||||
filter,
|
filter,
|
||||||
@ -1063,15 +975,73 @@ const ObjectSelect = ({
|
|||||||
token,
|
token,
|
||||||
fetchFullObjectIfNeeded,
|
fetchFullObjectIfNeeded,
|
||||||
type,
|
type,
|
||||||
masterFilter,
|
|
||||||
committedSelectKey,
|
|
||||||
getSelectKey,
|
|
||||||
connected,
|
connected,
|
||||||
getValueIdentity,
|
getValueIdentity,
|
||||||
multiple,
|
multiple
|
||||||
applyTreeFromData
|
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const prevValuesRef = useRef({ type, masterFilter })
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const prevValues = prevValuesRef.current
|
||||||
|
|
||||||
|
// Deep comparison for objects, simple comparison for primitives
|
||||||
|
const hasChanged =
|
||||||
|
prevValues.type !== type ||
|
||||||
|
JSON.stringify(prevValues.masterFilter) !== JSON.stringify(masterFilter)
|
||||||
|
|
||||||
|
if (hasChanged) {
|
||||||
|
searchRequestIdRef.current += 1
|
||||||
|
setIsSearching(false)
|
||||||
|
setSearchValue('')
|
||||||
|
setObjectPropertiesTree({})
|
||||||
|
setObjectList([])
|
||||||
|
setTreeData([])
|
||||||
|
setTreeVersion((v) => v + 1)
|
||||||
|
setExpandedKeys([])
|
||||||
|
setInitialized(false)
|
||||||
|
valueRef.current = null
|
||||||
|
onTreeSelectChange(null)
|
||||||
|
setTreeSelectValue(null)
|
||||||
|
setInitialLoading(true)
|
||||||
|
setReloading(false)
|
||||||
|
setError(false)
|
||||||
|
setValueNotFound(false)
|
||||||
|
clearedMissingValueRef.current = false
|
||||||
|
prevValuesRef.current = { type, masterFilter }
|
||||||
|
}
|
||||||
|
}, [type, masterFilter, onTreeSelectChange])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Check if value has actually changed
|
||||||
|
const currentValueIdentity = getValueIdentity(value)
|
||||||
|
const hasValueChanged =
|
||||||
|
prevValueIdentityRef.current !== currentValueIdentity
|
||||||
|
|
||||||
|
if (hasValueChanged) {
|
||||||
|
const changeSource = isInternalChangeRef.current ? 'internal' : 'external'
|
||||||
|
|
||||||
|
if (changeSource == 'external') {
|
||||||
|
setObjectPropertiesTree({})
|
||||||
|
setTreeData([])
|
||||||
|
treeDataRef.current = []
|
||||||
|
setInitialized(false)
|
||||||
|
setInitialLoading(true)
|
||||||
|
valueRef.current = null
|
||||||
|
clearedMissingValueRef.current = false
|
||||||
|
setValueNotFound(false)
|
||||||
|
prevValuesRef.current = { type, masterFilter }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the internal change flag
|
||||||
|
isInternalChangeRef.current = false
|
||||||
|
|
||||||
|
// Update the previous value reference
|
||||||
|
prevValueRef.current = value
|
||||||
|
prevValueIdentityRef.current = currentValueIdentity
|
||||||
|
}
|
||||||
|
}, [value, getValueIdentity, type, masterFilter])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
objectListRef.current = objectList
|
objectListRef.current = objectList
|
||||||
}, [objectList])
|
}, [objectList])
|
||||||
@ -1167,6 +1137,16 @@ const ObjectSelect = ({
|
|||||||
|
|
||||||
if (missing) {
|
if (missing) {
|
||||||
setValueNotFound(true)
|
setValueNotFound(true)
|
||||||
|
if (
|
||||||
|
value != null &&
|
||||||
|
!(Array.isArray(value) && value.length === 0) &&
|
||||||
|
!clearedMissingValueRef.current
|
||||||
|
) {
|
||||||
|
clearedMissingValueRef.current = true
|
||||||
|
isInternalChangeRef.current = true
|
||||||
|
setTreeSelectValue(multiple ? [] : null)
|
||||||
|
onChange?.(multiple ? [] : null)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1174,7 +1154,7 @@ const ObjectSelect = ({
|
|||||||
if (value != null) {
|
if (value != null) {
|
||||||
setValueNotFound(false)
|
setValueNotFound(false)
|
||||||
}
|
}
|
||||||
}, [value, multiple, objectList, treeData, valueReady])
|
}, [value, multiple, objectList, treeData, valueReady, onChange])
|
||||||
|
|
||||||
const displayPlaceholder = valueNotFound
|
const displayPlaceholder = valueNotFound
|
||||||
? `${modelLabel.charAt(0).toUpperCase()}${modelLabel.slice(1).toLowerCase()} not found.`
|
? `${modelLabel.charAt(0).toUpperCase()}${modelLabel.slice(1).toLowerCase()} not found.`
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user