Compare commits

..

9 Commits

40 changed files with 1908 additions and 2640 deletions

View File

@ -30,14 +30,14 @@
"@tsparticles/react": "^3.0.0",
"@tsparticles/slim": "^3.9.1",
"@uiw/react-codemirror": "^4.25.1",
"antd": "^5.27.0",
"antd": "^5.27.1",
"antd-style": "^3.7.1",
"axios": "^1.11.0",
"country-list": "^2.3.0",
"country-list": "^2.4.1",
"cross-env": "^10.0.0",
"dayjs": "^1.11.13",
"dayjs": "^1.11.18",
"dotenv": "^17.2.1",
"eslint": "^9.33.0",
"eslint": "^9.34.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
@ -50,13 +50,12 @@
"prettier": "^3.6.2",
"prettier-eslint": "^16.4.2",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react": "^19.1.1",
"react-country-flag": "^3.1.0",
"react-dom": "^18.2.0",
"react-dom": "^19.1.1",
"react-markdown": "^10.1.0",
"react-responsive": "^10.0.1",
"react-router-dom": "^7.8.0",
"react-stl-viewer": "^2.5.0",
"react-router-dom": "^7.8.2",
"remark-gfm": "^4.0.1",
"socket.io-client": "*",
"standard": "^17.1.2",
@ -96,12 +95,12 @@
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@vitejs/plugin-react": "^5.0.1",
"concurrently": "^9.2.0",
"electron": "^37.2.6",
"@vitejs/plugin-react": "^5.0.2",
"concurrently": "^9.2.1",
"electron": "^37.4.0",
"electron-builder": "^26.0.12",
"electron-packager": "^17.1.2",
"eslint": "^9.33.0",
"eslint": "^9.34.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
@ -116,7 +115,7 @@
"vite": "^7.1.3",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-svgo": "^2.0.0",
"vite-plugin-svgr": "^4.3.0"
"vite-plugin-svgr": "^4.5.0"
},
"build": {
"appId": "com.tombutcher.farmcontrol",

View File

@ -20,7 +20,7 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder'
const FilamentStockInfo = () => {
const location = useLocation()
const editFormRef = useRef(null)
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const filamentStockId = new URLSearchParams(location.search).get(
'filamentStockId'
@ -34,7 +34,7 @@ const FilamentStockInfo = () => {
auditLogs: true
}
)
const [editFormState, setEditFormState] = useState({
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
@ -44,19 +44,19 @@ const FilamentStockInfo = () => {
const actions = {
reload: () => {
editFormRef?.current?.fetchObject?.()
objectFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
objectFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
objectFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
objectFormRef?.current?.handleUpdate?.()
return true
}
}
@ -74,10 +74,10 @@ const FilamentStockInfo = () => {
<ObjectActions
type='filamentStock'
id={filamentStockId}
disabled={editFormState.loading}
disabled={objectFormState.loading}
/>
<ViewButton
disabled={editFormState.loading}
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Filament Stock Information' },
{ key: 'events', label: 'Filament Stock Events' },
@ -88,11 +88,11 @@ const FilamentStockInfo = () => {
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={editFormState.lock} />
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
@ -102,10 +102,10 @@ const FilamentStockInfo = () => {
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
@ -113,7 +113,7 @@ const FilamentStockInfo = () => {
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
@ -127,7 +127,7 @@ const FilamentStockInfo = () => {
id={filamentStockId}
type='filamentStock'
style={{ height: '100%' }}
ref={editFormRef}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
@ -150,7 +150,7 @@ const FilamentStockInfo = () => {
onToggle={(expanded) => updateCollapseState('events', expanded)}
collapseKey='events'
>
{editFormState.loading ? (
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
@ -180,7 +180,7 @@ const FilamentStockInfo = () => {
}
collapseKey='auditLogs'
>
{editFormState.loading ? (
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable

View File

@ -25,7 +25,7 @@ log.setLevel(config.logLevel)
const DocumentSizeInfo = () => {
const location = useLocation()
const editFormRef = useRef(null)
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const documentSizeId = new URLSearchParams(location.search).get(
'documentSizeId'
@ -38,7 +38,7 @@ const DocumentSizeInfo = () => {
auditLogs: true
}
)
const [editFormState, setEditFormState] = useState({
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
@ -48,19 +48,19 @@ const DocumentSizeInfo = () => {
const actions = {
reload: () => {
editFormRef?.current?.fetchObject?.()
objectFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
objectFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
objectFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
objectFormRef?.current?.handleUpdate?.()
return true
}
}
@ -78,10 +78,10 @@ const DocumentSizeInfo = () => {
<ObjectActions
type='documentSize'
id={documentSizeId}
disabled={editFormState.loading}
disabled={objectFormState.loading}
/>
<ViewButton
disabled={editFormState.loading}
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Document Size Information' },
{ key: 'notes', label: 'Notes' },
@ -91,11 +91,11 @@ const DocumentSizeInfo = () => {
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={editFormState.lock} />
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
@ -105,10 +105,10 @@ const DocumentSizeInfo = () => {
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
@ -116,7 +116,7 @@ const DocumentSizeInfo = () => {
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
@ -130,7 +130,7 @@ const DocumentSizeInfo = () => {
id={documentSizeId}
type='documentSize'
style={{ height: '100%' }}
ref={editFormRef}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
@ -167,7 +167,7 @@ const DocumentSizeInfo = () => {
}
collapseKey='auditLogs'
>
{editFormState.loading ? (
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable

View File

@ -21,7 +21,7 @@ log.setLevel(config.logLevel)
const DocumentTemplateDesign = () => {
const location = useLocation()
const editFormRef = useRef(null)
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const documentTemplateId = new URLSearchParams(location.search).get(
'documentTemplateId'
@ -36,7 +36,7 @@ const DocumentTemplateDesign = () => {
}
)
const [editFormState, setEditFormState] = useState({
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
@ -46,19 +46,19 @@ const DocumentTemplateDesign = () => {
const actions = {
reload: () => {
editFormRef?.current.handleFetchObject()
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
editFormRef?.current.startEditing()
objectFormRef?.current.startEditing()
return false
},
cancelEdit: () => {
editFormRef?.current.cancelEditing()
objectFormRef?.current.cancelEditing()
return true
},
finishEdit: () => {
editFormRef?.current.handleUpdate()
objectFormRef?.current.handleUpdate()
return true
}
}
@ -78,11 +78,11 @@ const DocumentTemplateDesign = () => {
<ObjectActions
type='documentTemplate'
id={documentTemplateId}
disabled={editFormState.loading}
disabled={objectFormState.loading}
visibleActions={{ edit: false }}
/>
<ViewButton
disabled={editFormState.loading}
disabled={objectFormState.loading}
items={[
{
key: 'preview',
@ -99,11 +99,11 @@ const DocumentTemplateDesign = () => {
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={editFormState.lock} />
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
@ -113,10 +113,10 @@ const DocumentTemplateDesign = () => {
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
@ -125,14 +125,14 @@ const DocumentTemplateDesign = () => {
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<ObjectForm
id={documentTemplateId}
type='documentTemplate'
style={{ height: '100%' }}
ref={editFormRef}
ref={objectFormRef}
onStateChange={(state) => {
console.log('Got edit form state change', state)
setEditFormState((prev) => ({ ...prev, ...state }))

View File

@ -25,7 +25,7 @@ log.setLevel(config.logLevel)
const DocumentTemplateInfo = () => {
const location = useLocation()
const editFormRef = useRef(null)
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const documentTemplateId = new URLSearchParams(location.search).get(
'documentTemplateId'
@ -40,7 +40,7 @@ const DocumentTemplateInfo = () => {
}
)
const [editFormState, setEditFormState] = useState({
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
@ -50,19 +50,19 @@ const DocumentTemplateInfo = () => {
const actions = {
reload: () => {
editFormRef?.current.handleFetchObject()
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
editFormRef?.current.startEditing()
objectFormRef?.current.startEditing()
return false
},
cancelEdit: () => {
editFormRef?.current.cancelEditing()
objectFormRef?.current.cancelEditing()
return true
},
finishEdit: () => {
editFormRef?.current.handleUpdate()
objectFormRef?.current.handleUpdate()
return true
}
}
@ -83,10 +83,10 @@ const DocumentTemplateInfo = () => {
<ObjectActions
type='documentTemplate'
id={documentTemplateId}
disabled={editFormState.loading}
disabled={objectFormState.loading}
/>
<ViewButton
disabled={editFormState.loading}
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'DocumentTemplate Information' },
{ key: 'notes', label: 'Notes' },
@ -96,11 +96,11 @@ const DocumentTemplateInfo = () => {
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={editFormState.lock} />
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
@ -110,10 +110,10 @@ const DocumentTemplateInfo = () => {
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
@ -122,7 +122,7 @@ const DocumentTemplateInfo = () => {
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
@ -136,7 +136,7 @@ const DocumentTemplateInfo = () => {
id={documentTemplateId}
type='documentTemplate'
style={{ height: '100%' }}
ref={editFormRef}
ref={objectFormRef}
onStateChange={(state) => {
console.log('Got edit form state change', state)
setEditFormState((prev) => ({ ...prev, ...state }))
@ -182,7 +182,7 @@ const DocumentTemplateInfo = () => {
}
collapseKey='auditLogs'
>
{editFormState.loading ? (
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable

View File

@ -26,7 +26,7 @@ log.setLevel(config.logLevel)
const FilamentInfo = () => {
const location = useLocation()
const editFormRef = useRef(null)
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const filamentId = new URLSearchParams(location.search).get('filamentId')
const [collapseState, updateCollapseState] = useCollapseState(
@ -39,7 +39,7 @@ const FilamentInfo = () => {
}
)
const [editFormState, setEditFormState] = useState({
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
@ -49,19 +49,19 @@ const FilamentInfo = () => {
const actions = {
reload: () => {
editFormRef?.current?.fetchObject?.()
objectFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
objectFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
objectFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
objectFormRef?.current?.handleUpdate?.()
return true
}
}
@ -82,10 +82,10 @@ const FilamentInfo = () => {
<ObjectActions
type='filament'
id={filamentId}
disabled={editFormState.loading}
disabled={objectFormState.loading}
/>
<ViewButton
disabled={editFormState.loading}
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Filament Information' },
{ key: 'stocks', label: 'Filament Stocks' },
@ -96,11 +96,11 @@ const FilamentInfo = () => {
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={editFormState.lock} />
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
@ -110,10 +110,10 @@ const FilamentInfo = () => {
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
@ -122,7 +122,7 @@ const FilamentInfo = () => {
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
@ -136,7 +136,7 @@ const FilamentInfo = () => {
id={filamentId}
type='filament'
style={{ height: '100%' }}
ref={editFormRef}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
@ -163,7 +163,7 @@ const FilamentInfo = () => {
onToggle={(expanded) => updateCollapseState('stocks', expanded)}
collapseKey='stocks'
>
{editFormState.loading ? (
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
@ -199,7 +199,7 @@ const FilamentInfo = () => {
}
collapseKey='auditLogs'
>
{editFormState.loading ? (
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable

View File

@ -1,19 +1,9 @@
import PropTypes from 'prop-types'
import { useState } from 'react'
import { useMediaQuery } from 'react-responsive'
import { Typography, Flex, Steps, Divider } from 'antd'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import NewObjectButtons from '../../common/NewObjectButtons'
const { Title } = Typography
import WizardView from '../../common/WizardView'
const NewFilament = ({ onOk }) => {
const [currentStep, setCurrentStep] = useState(0)
const isMobile = useMediaQuery({ maxWidth: 768 })
return (
<NewObjectForm type={'filament'}>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
@ -66,43 +56,16 @@ const NewFilament = ({ onOk }) => {
}
]
return (
<Flex gap='middle'>
{!isMobile && (
<div style={{ minWidth: '160px' }}>
<Steps
current={currentStep}
items={steps}
direction='vertical'
style={{ width: 'fit-content' }}
/>
</div>
)}
{!isMobile && (
<Divider type='vertical' style={{ height: 'unset' }} />
)}
<Flex vertical gap='middle' style={{ flexGrow: 1 }}>
<Title level={2} style={{ margin: 0 }}>
New Filament
</Title>
<div style={{ minHeight: '260px', marginBottom: 8 }}>
{steps[currentStep].content}
</div>
<NewObjectButtons
currentStep={currentStep}
totalSteps={steps.length}
onPrevious={() => setCurrentStep((prev) => prev - 1)}
onNext={() => setCurrentStep((prev) => prev + 1)}
onSubmit={() => {
handleSubmit()
onOk()
}}
formValid={formValid}
submitLoading={submitLoading}
/>
</Flex>
</Flex>
<WizardView
steps={steps}
loading={submitLoading}
formValid={formValid}
title='New Filament'
onSubmit={() => {
handleSubmit()
onOk()
}}
/>
)
}}
</NewObjectForm>

View File

@ -26,7 +26,7 @@ log.setLevel(config.logLevel)
const HostInfo = () => {
const location = useLocation()
const editFormRef = useRef(null)
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const hostId = new URLSearchParams(location.search).get('hostId')
const [collapseState, updateCollapseState] = useCollapseState('HostInfo', {
@ -37,7 +37,7 @@ const HostInfo = () => {
})
const [hostOTPOpen, setHostOTPOpen] = useState(false)
const [editFormState, setEditFormState] = useState({
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
@ -47,7 +47,7 @@ const HostInfo = () => {
const actions = {
reload: () => {
editFormRef?.current.handleFetchObject()
objectFormRef?.current.handleFetchObject()
return true
},
hostOTP: () => {
@ -55,15 +55,15 @@ const HostInfo = () => {
return true
},
edit: () => {
editFormRef?.current.startEditing()
objectFormRef?.current.startEditing()
return false
},
cancelEdit: () => {
editFormRef?.current.cancelEditing()
objectFormRef?.current.cancelEditing()
return true
},
finishEdit: () => {
editFormRef?.current.handleUpdate()
objectFormRef?.current.handleUpdate()
return true
}
}
@ -84,10 +84,10 @@ const HostInfo = () => {
<ObjectActions
type='host'
id={hostId}
disabled={editFormState.loading}
disabled={objectFormState.loading}
/>
<ViewButton
disabled={editFormState.loading}
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Host Information' },
{ key: 'notes', label: 'Notes' },
@ -97,11 +97,11 @@ const HostInfo = () => {
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={editFormState.lock} />
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
@ -111,10 +111,10 @@ const HostInfo = () => {
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
@ -123,7 +123,7 @@ const HostInfo = () => {
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
@ -137,7 +137,7 @@ const HostInfo = () => {
id={hostId}
type='host'
style={{ height: '100%' }}
ref={editFormRef}
ref={objectFormRef}
onStateChange={(state) => {
console.log('Got edit form state change', state)
setEditFormState((prev) => ({ ...prev, ...state }))
@ -179,7 +179,7 @@ const HostInfo = () => {
}
collapseKey='auditLogs'
>
{editFormState.loading ? (
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable

View File

@ -1,279 +1,80 @@
import PropTypes from 'prop-types'
import { useState, useEffect } from 'react'
import axios from 'axios'
import {
Form,
Input,
Button,
message,
Select,
Flex,
Steps,
Divider,
Upload,
Descriptions
} from 'antd'
import { UploadOutlined } from '@ant-design/icons'
import VendorSelect from '../../common/VendorSelect'
import config from '../../../../config'
const initialNewMaterialForm = {
name: '',
vendor: { id: null, name: '' },
category: '',
image: null,
url: '',
barcode: ''
}
const NewMaterial = ({ onSuccess }) => {
const [messageApi, contextHolder] = message.useMessage()
const [newMaterialLoading, setNewMaterialLoading] = useState(false)
const [currentStep, setCurrentStep] = useState(0)
const [nextEnabled, setNextEnabled] = useState(false)
const [newMaterialForm] = Form.useForm()
const [newMaterialFormValues, setNewMaterialFormValues] = useState(
initialNewMaterialForm
)
const [imageList, setImageList] = useState([])
const newMaterialFormUpdateValues = Form.useWatch([], newMaterialForm)
useEffect(() => {
newMaterialForm
.validateFields({
validateOnly: true
})
.then(() => setNextEnabled(true))
.catch(() => setNextEnabled(false))
}, [newMaterialForm, newMaterialFormUpdateValues])
const summaryItems = [
{
key: 'name',
label: 'Name',
children: newMaterialFormValues.name
},
{
key: 'vendor',
label: 'Vendor',
children: newMaterialFormValues.vendor.name
},
{
key: 'category',
label: 'Category',
children: newMaterialFormValues.category
},
{
key: 'image',
label: 'Image',
children: newMaterialFormValues.image && (
<img
src={newMaterialFormValues.image}
style={{ width: 128 }}
alt='Material'
/>
)
},
{
key: 'url',
label: 'URL',
children: newMaterialFormValues.url
},
{
key: 'barcode',
label: 'Barcode',
children: newMaterialFormValues.barcode
}
]
const handleNewMaterial = async () => {
setNewMaterialLoading(true)
try {
await axios.post(
`${config.backendUrl}/materials`,
newMaterialFormValues,
{
withCredentials: true
}
)
messageApi.success('New material created successfully.')
onSuccess()
} catch (error) {
messageApi.error('Error creating new material: ' + error.message)
} finally {
setNewMaterialLoading(false)
}
}
const getBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => resolve(reader.result)
reader.onerror = (error) => reject(error)
})
}
const handleImageUpload = async ({ file, fileList }) => {
if (fileList.length === 0) {
setImageList(fileList)
newMaterialForm.setFieldsValue({ image: '' })
return
}
const base64 = await getBase64(file)
setNewMaterialFormValues((prevValues) => ({
...prevValues,
image: base64
}))
fileList[0].name = 'Material Image'
setImageList(fileList)
newMaterialForm.setFieldsValue({ image: base64 })
}
const steps = [
{
title: 'Details',
key: 'details',
content: (
<>
<Form.Item
label='Name'
name='name'
rules={[
{
required: true,
message: 'Please enter a name.'
}
]}
>
<Input />
</Form.Item>
<Form.Item
label='Vendor'
name='vendor'
rules={[
{
required: true,
message: 'Please select a vendor.'
}
]}
>
<VendorSelect />
</Form.Item>
<Form.Item
label='Category'
name='category'
rules={[{ required: true, message: 'Please select a category' }]}
>
<Select>
<Select.Option value='Raw Material'>Raw Material</Select.Option>
<Select.Option value='Component'>Component</Select.Option>
<Select.Option value='Consumable'>Consumable</Select.Option>
<Select.Option value='Tool'>Tool</Select.Option>
<Select.Option value='Packaging'>Packaging</Select.Option>
</Select>
</Form.Item>
</>
)
},
{
title: 'Additional Info',
key: 'additional',
content: (
<>
<Form.Item label='Image' name='image'>
<Upload
listType='picture'
maxCount={1}
fileList={imageList}
onChange={handleImageUpload}
beforeUpload={() => false}
>
<Button icon={<UploadOutlined />}>Upload Image</Button>
</Upload>
</Form.Item>
<Form.Item label='URL' name='url'>
<Input />
</Form.Item>
<Form.Item label='Barcode' name='barcode'>
<Input />
</Form.Item>
</>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<>
<Descriptions
title='Material Details'
bordered
column={1}
items={summaryItems}
/>
</>
)
}
]
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewMaterial = ({ onOk }) => {
return (
<>
{contextHolder}
<Form
form={newMaterialForm}
layout='vertical'
onValuesChange={(changedValues) => {
setNewMaterialFormValues((prevValues) => ({
...prevValues,
...changedValues
}))
}}
>
<Steps
current={currentStep}
items={steps.map((item) => ({ title: item.title }))}
style={{ marginBottom: 24 }}
/>
<div style={{ minHeight: 200 }}>{steps[currentStep].content}</div>
<Divider />
<Flex justify='space-between'>
<Button
disabled={currentStep === 0}
onClick={() => setCurrentStep((prev) => prev - 1)}
>
Previous
</Button>
{currentStep < steps.length - 1 ? (
<Button
type='primary'
disabled={!nextEnabled}
onClick={() => setCurrentStep((prev) => prev + 1)}
>
Next
</Button>
) : (
<Button
type='primary'
loading={newMaterialLoading}
onClick={handleNewMaterial}
>
Create Material
</Button>
)}
</Flex>
</Form>
</>
<NewObjectForm type={'material'}>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='material'
column={1}
bordered={false}
isEditing={true}
required={true}
objectData={objectData}
/>
)
},
{
title: 'Optional',
key: 'optional',
content: (
<ObjectInfo
type='material'
column={1}
bordered={false}
isEditing={true}
required={false}
objectData={objectData}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='material'
column={1}
bordered={false}
visibleProperties={{
_id: false,
createdAt: false,
updatedAt: false
}}
isEditing={false}
objectData={objectData}
/>
)
}
]
return (
<WizardView
steps={steps}
loading={submitLoading}
formValid={formValid}
title='New Material'
onSubmit={() => {
handleSubmit()
onOk()
}}
/>
)
}}
</NewObjectForm>
)
}
NewMaterial.propTypes = {
onSuccess: PropTypes.func.isRequired
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
}
export default NewMaterial

View File

@ -1,18 +1,9 @@
import PropTypes from 'prop-types'
import { useState } from 'react'
import { useMediaQuery } from 'react-responsive'
import { Typography, Flex, Steps, Divider } from 'antd'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import NewObjectButtons from '../../common/NewObjectButtons'
const { Title } = Typography
import WizardView from '../../common/WizardView'
const NewNoteType = ({ onOk }) => {
const [currentStep, setCurrentStep] = useState(0)
const isMobile = useMediaQuery({ maxWidth: 768 })
return (
<NewObjectForm
type={'noteType'}
@ -70,43 +61,16 @@ const NewNoteType = ({ onOk }) => {
}
]
return (
<Flex gap='middle'>
{!isMobile && (
<div style={{ minWidth: '160px' }}>
<Steps
current={currentStep}
items={steps}
direction='vertical'
style={{ width: 'fit-content' }}
/>
</div>
)}
{!isMobile && (
<Divider type='vertical' style={{ height: 'unset' }} />
)}
<Flex vertical gap='middle' style={{ flexGrow: 1 }}>
<Title level={2} style={{ margin: 0 }}>
New Note Type
</Title>
<div style={{ minHeight: '260px', marginBottom: 8 }}>
{steps[currentStep].content}
</div>
<NewObjectButtons
currentStep={currentStep}
totalSteps={steps.length}
onPrevious={() => setCurrentStep((prev) => prev - 1)}
onNext={() => setCurrentStep((prev) => prev + 1)}
onSubmit={() => {
handleSubmit()
onOk()
}}
formValid={formValid}
submitLoading={submitLoading}
/>
</Flex>
</Flex>
<WizardView
steps={steps}
loading={submitLoading}
formValid={formValid}
title='New Note Type'
onSubmit={() => {
handleSubmit()
onOk()
}}
/>
)
}}
</NewObjectForm>

View File

@ -18,7 +18,7 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
const NoteTypeInfo = () => {
const location = useLocation()
const editFormRef = useRef(null)
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const noteTypeId = new URLSearchParams(location.search).get('noteTypeId')
const [collapseState, updateCollapseState] = useCollapseState(
@ -28,7 +28,7 @@ const NoteTypeInfo = () => {
auditLogs: true
}
)
const [editFormState, setEditFormState] = useState({
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
@ -38,19 +38,19 @@ const NoteTypeInfo = () => {
const actions = {
reload: () => {
editFormRef?.current?.fetchObject?.()
objectFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
objectFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
objectFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
objectFormRef?.current?.handleUpdate?.()
return true
}
}
@ -68,10 +68,10 @@ const NoteTypeInfo = () => {
<ObjectActions
type='noteType'
id={noteTypeId}
disabled={editFormState.loading}
disabled={objectFormState.loading}
/>
<ViewButton
disabled={editFormState.loading}
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Note Type Information' },
{ key: 'auditLogs', label: 'Audit Logs' }
@ -80,11 +80,11 @@ const NoteTypeInfo = () => {
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={editFormState.lock} />
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
@ -94,10 +94,10 @@ const NoteTypeInfo = () => {
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
@ -105,7 +105,7 @@ const NoteTypeInfo = () => {
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
@ -119,7 +119,7 @@ const NoteTypeInfo = () => {
id={noteTypeId}
type='noteType'
style={{ height: '100%' }}
ref={editFormRef}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
@ -145,7 +145,7 @@ const NoteTypeInfo = () => {
}
collapseKey='auditLogs'
>
{editFormState.loading ? (
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable

View File

@ -0,0 +1,51 @@
import PropTypes from 'prop-types'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewNote = ({ onOk, defaultValues = {} }) => {
return (
<NewObjectForm type={'note'} defaultValues={defaultValues}>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='note'
column={1}
visibleProperties={{ 'parent._id': false }}
bordered={false}
isEditing={true}
required={true}
objectData={objectData}
/>
)
}
]
return (
<WizardView
steps={steps}
showSteps={false}
loading={submitLoading}
formValid={formValid}
title='New Note'
onSubmit={() => {
handleSubmit()
onOk()
}}
/>
)
}}
</NewObjectForm>
)
}
NewNote.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewNote

View File

@ -0,0 +1,184 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd'
import loglevel from 'loglevel'
import config from '../../../../config'
import useCollapseState from '../../hooks/useCollapseState'
import NotesPanel from '../../common/NotesPanel'
import InfoCollapse from '../../common/InfoCollapse'
import ObjectInfo from '../../common/ObjectInfo'
import ViewButton from '../../common/ViewButton'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import ObjectForm from '../../common/ObjectForm'
import EditButtons from '../../common/EditButtons'
import LockIndicator from '../../common/LockIndicator.jsx'
import ActionHandler from '../../common/ActionHandler.jsx'
import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
const log = loglevel.getLogger('NoteInfo')
log.setLevel(config.logLevel)
const NoteInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const noteId = new URLSearchParams(location.search).get('noteId')
const [collapseState, updateCollapseState] = useCollapseState('NoteInfo', {
info: true,
notes: true,
auditLogs: true
})
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
objectFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
objectFormRef?.current?.handleUpdate?.()
return true
},
delete: () => {
objectFormRef?.current?.handleDelete?.()
return true
}
}
return (
<>
<Flex
gap='large'
vertical='true'
style={{ maxHeight: '100%', minHeight: 0 }}
>
<Flex justify={'space-between'}>
<Space size='middle'>
<Space size='small'>
<ObjectActions
type='note'
id={noteId}
disabled={objectFormState.loading}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Note Information' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
cancelEditing={() => {
actionHandlerRef.current.callAction('cancelEdit')
}}
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
<div style={{ height: '100%', overflow: 'auto' }}>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
title='Note Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
>
<ObjectForm
id={noteId}
type='note'
style={{ height: '100%' }}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => (
<ObjectInfo
loading={loading}
isEditing={isEditing}
type='note'
objectData={objectData}
/>
)}
</ObjectForm>
</InfoCollapse>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={noteId} type='note' />
</Card>
</InfoCollapse>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': noteId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</div>
</Flex>
</>
)
}
export default NoteInfo

View File

@ -20,7 +20,7 @@ import { ApiServerContext } from '../../context/ApiServerContext'
const PartInfo = () => {
const location = useLocation()
const editFormRef = useRef(null)
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const partId = new URLSearchParams(location.search).get('partId')
const { fetchObjectContent } = useContext(ApiServerContext)
@ -30,7 +30,7 @@ const PartInfo = () => {
notes: true,
auditLogs: true
})
const [editFormState, setEditFormState] = useState({
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
@ -40,24 +40,24 @@ const PartInfo = () => {
const actions = {
reload: () => {
editFormRef?.current?.fetchObject?.()
objectFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
objectFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
objectFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
objectFormRef?.current?.handleUpdate?.()
return true
},
download: () => {
if (partId && editFormRef?.current?.getObjectData) {
const objectData = editFormRef.current.getObjectData()
if (partId && objectFormRef?.current?.getObjectData) {
const objectData = objectFormRef.current.getObjectData()
fetchObjectContent(partId, 'part', `${objectData?.name || 'part'}.stl`)
return true
}
@ -77,10 +77,10 @@ const PartInfo = () => {
<ObjectActions
type='part'
id={partId}
disabled={editFormState.loading}
disabled={objectFormState.loading}
/>
<ViewButton
disabled={editFormState.loading}
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Part Information' },
{ key: 'notes', label: 'Notes' },
@ -90,11 +90,11 @@ const PartInfo = () => {
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={editFormState.lock} />
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
@ -104,10 +104,10 @@ const PartInfo = () => {
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
@ -115,7 +115,7 @@ const PartInfo = () => {
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
@ -129,7 +129,7 @@ const PartInfo = () => {
id={partId}
type='part'
style={{ height: '100%' }}
ref={editFormRef}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
@ -165,7 +165,7 @@ const PartInfo = () => {
}
collapseKey='auditLogs'
>
{editFormState.loading ? (
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable

View File

@ -21,7 +21,6 @@ import {
import { DeleteOutlined, EyeOutlined } from '@ant-design/icons'
import { AuthContext } from '../../context/AuthContext'
import PartIcon from '../../../Icons/PartIcon'
import { StlViewer } from 'react-stl-viewer'
import VendorSelect from '../../common/VendorSelect'
import config from '../../../../config'
@ -482,15 +481,7 @@ const NewProduct = ({ onOk, reset }) => {
>
<Flex style={{ minWidth: '100%', minHeight: '80vh' }}>
{previewFile && !isPreviewLoading ? (
<div style={{ flexGrow: 1 }}>
<StlViewer
url={fileUrls[previewFile.uid]}
orbitControls
shadows
style={{ height: '80vh', width: '100%' }}
modelProps={{ color: '#008675' }}
/>
</div>
<div style={{ flexGrow: 1 }}></div>
) : (
<div
style={{

View File

@ -20,7 +20,7 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
const ProductInfo = () => {
const location = useLocation()
const editFormRef = useRef(null)
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const productId = new URLSearchParams(location.search).get('productId')
const [collapseState, updateCollapseState] = useCollapseState('ProductInfo', {
@ -29,7 +29,7 @@ const ProductInfo = () => {
notes: true,
auditLogs: true
})
const [editFormState, setEditFormState] = useState({
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
@ -39,19 +39,19 @@ const ProductInfo = () => {
const actions = {
reload: () => {
editFormRef?.current?.fetchObject?.()
objectFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
objectFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
objectFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
objectFormRef?.current?.handleUpdate?.()
return true
}
}
@ -69,10 +69,10 @@ const ProductInfo = () => {
<ObjectActions
type='product'
id={productId}
disabled={editFormState.loading}
disabled={objectFormState.loading}
/>
<ViewButton
disabled={editFormState.loading}
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Product Information' },
{ key: 'parts', label: 'Product Parts' },
@ -83,11 +83,11 @@ const ProductInfo = () => {
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={editFormState.lock} />
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
@ -97,10 +97,10 @@ const ProductInfo = () => {
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
@ -108,7 +108,7 @@ const ProductInfo = () => {
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
@ -122,7 +122,7 @@ const ProductInfo = () => {
id={productId}
type='product'
style={{ height: '100%' }}
ref={editFormRef}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
@ -174,7 +174,7 @@ const ProductInfo = () => {
}
collapseKey='auditLogs'
>
{editFormState.loading ? (
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable

View File

@ -20,7 +20,7 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
const UserInfo = () => {
const location = useLocation()
const editFormRef = useRef(null)
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const userId = new URLSearchParams(location.search).get('userId')
const [collapseState, updateCollapseState] = useCollapseState('UserInfo', {
@ -28,7 +28,7 @@ const UserInfo = () => {
notes: true,
auditLogs: true
})
const [editFormState, setEditFormState] = useState({
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
@ -38,19 +38,19 @@ const UserInfo = () => {
const actions = {
reload: () => {
editFormRef?.current?.fetchObject?.()
objectFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
objectFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
objectFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
objectFormRef?.current?.handleUpdate?.()
return true
}
}
@ -68,10 +68,10 @@ const UserInfo = () => {
<ObjectActions
type='user'
id={userId}
disabled={editFormState.loading}
disabled={objectFormState.loading}
/>
<ViewButton
disabled={editFormState.loading}
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'User Information' },
{ key: 'notes', label: 'Notes' },
@ -81,11 +81,11 @@ const UserInfo = () => {
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={editFormState.lock} />
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
@ -95,10 +95,10 @@ const UserInfo = () => {
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
@ -106,7 +106,7 @@ const UserInfo = () => {
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
@ -120,7 +120,7 @@ const UserInfo = () => {
id={userId}
type='user'
style={{ height: '100%' }}
ref={editFormRef}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
@ -157,7 +157,7 @@ const UserInfo = () => {
}
collapseKey='auditLogs'
>
{editFormState.loading ? (
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable

View File

@ -1,18 +1,9 @@
import PropTypes from 'prop-types'
import { useState } from 'react'
import { useMediaQuery } from 'react-responsive'
import { Typography, Flex, Steps, Divider } from 'antd'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import NewObjectButtons from '../../common/NewObjectButtons'
const { Title } = Typography
import WizardView from '../../common/WizardView'
const NewVendor = ({ onOk }) => {
const [currentStep, setCurrentStep] = useState(0)
const isMobile = useMediaQuery({ maxWidth: 768 })
return (
<NewObjectForm type={'vendor'}>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
@ -65,43 +56,16 @@ const NewVendor = ({ onOk }) => {
}
]
return (
<Flex gap='middle'>
{!isMobile && (
<div style={{ minWidth: '160px' }}>
<Steps
current={currentStep}
items={steps}
direction='vertical'
style={{ width: 'fit-content' }}
/>
</div>
)}
{!isMobile && (
<Divider type='vertical' style={{ height: 'unset' }} />
)}
<Flex vertical gap='middle' style={{ flexGrow: 1 }}>
<Title level={2} style={{ margin: 0 }}>
New Vendor
</Title>
<div style={{ minHeight: '260px', marginBottom: 8 }}>
{steps[currentStep].content}
</div>
<NewObjectButtons
currentStep={currentStep}
totalSteps={steps.length}
onPrevious={() => setCurrentStep((prev) => prev - 1)}
onNext={() => setCurrentStep((prev) => prev + 1)}
onSubmit={() => {
handleSubmit()
onOk()
}}
formValid={formValid}
submitLoading={submitLoading}
/>
</Flex>
</Flex>
<WizardView
steps={steps}
loading={submitLoading}
formValid={formValid}
title='New Vendor'
onSubmit={() => {
handleSubmit()
onOk()
}}
/>
)
}}
</NewObjectForm>

View File

@ -24,7 +24,7 @@ log.setLevel(config.logLevel)
const VendorInfo = () => {
const location = useLocation()
const editFormRef = useRef(null)
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const vendorId = new URLSearchParams(location.search).get('vendorId')
const [collapseState, updateCollapseState] = useCollapseState('VendorInfo', {
@ -32,7 +32,7 @@ const VendorInfo = () => {
notes: true,
auditLogs: true
})
const [editFormState, setEditFormState] = useState({
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
@ -42,23 +42,23 @@ const VendorInfo = () => {
const actions = {
reload: () => {
editFormRef?.current?.handleFetchObject?.()
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
objectFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
objectFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
objectFormRef?.current?.handleUpdate?.()
return true
},
delete: () => {
editFormRef?.current?.handleDelete?.()
objectFormRef?.current?.handleDelete?.()
return true
}
}
@ -76,10 +76,10 @@ const VendorInfo = () => {
<ObjectActions
type='vendor'
id={vendorId}
disabled={editFormState.loading}
disabled={objectFormState.loading}
/>
<ViewButton
disabled={editFormState.loading}
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Vendor Information' },
{ key: 'notes', label: 'Notes' },
@ -89,11 +89,11 @@ const VendorInfo = () => {
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={editFormState.lock} />
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
@ -103,10 +103,10 @@ const VendorInfo = () => {
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
@ -114,7 +114,7 @@ const VendorInfo = () => {
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
@ -128,7 +128,7 @@ const VendorInfo = () => {
id={vendorId}
type='vendor'
style={{ height: '100%' }}
ref={editFormRef}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
@ -164,7 +164,7 @@ const VendorInfo = () => {
}
collapseKey='auditLogs'
>
{editFormState.loading ? (
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable

View File

@ -28,7 +28,7 @@ log.setLevel(config.logLevel)
const GCodeFileInfo = () => {
const location = useLocation()
const editFormRef = useRef(null)
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const gcodeFileId = new URLSearchParams(location.search).get('gcodeFileId')
const [collapseState, updateCollapseState] = useCollapseState(
@ -41,7 +41,7 @@ const GCodeFileInfo = () => {
}
)
const [editFormState, setEditFormState] = useState({
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
@ -51,19 +51,19 @@ const GCodeFileInfo = () => {
const actions = {
reload: () => {
editFormRef?.current.handleFetchObject()
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
editFormRef?.current.startEditing()
objectFormRef?.current.startEditing()
return false
},
cancelEdit: () => {
editFormRef?.current.cancelEditing()
objectFormRef?.current.cancelEditing()
return true
},
finishEdit: () => {
editFormRef?.current.handleUpdate()
objectFormRef?.current.handleUpdate()
return true
}
}
@ -84,10 +84,10 @@ const GCodeFileInfo = () => {
<ObjectActions
type='gcodeFile'
id={gcodeFileId}
disabled={editFormState.loading}
disabled={objectFormState.loading}
/>
<ViewButton
disabled={editFormState.loading}
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'GCode File Information' },
{ key: 'preview', label: 'GCode File Preview' },
@ -98,11 +98,11 @@ const GCodeFileInfo = () => {
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={editFormState.lock} />
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
@ -112,10 +112,10 @@ const GCodeFileInfo = () => {
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
@ -124,14 +124,14 @@ const GCodeFileInfo = () => {
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<ObjectForm
id={gcodeFileId}
type='gcodeFile'
style={{ height: '100%' }}
ref={editFormRef}
ref={objectFormRef}
onStateChange={(state) => {
console.log('Got edit form state change', state)
setEditFormState((prev) => ({ ...prev, ...state }))
@ -206,7 +206,7 @@ const GCodeFileInfo = () => {
}
collapseKey='auditLogs'
>
{editFormState.loading ? (
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable

View File

@ -26,7 +26,7 @@ log.setLevel(config.logLevel)
const JobInfo = () => {
const location = useLocation()
const editFormRef = useRef(null)
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const jobId = new URLSearchParams(location.search).get('jobId')
const [collapseState, updateCollapseState] = useCollapseState('JobInfo', {
@ -36,7 +36,7 @@ const JobInfo = () => {
auditLogs: true
})
const [editFormState, setEditFormState] = useState({
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
@ -46,19 +46,19 @@ const JobInfo = () => {
const actions = {
reload: () => {
editFormRef?.current.handleFetchObject()
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
editFormRef?.current.startEditing()
objectFormRef?.current.startEditing()
return false
},
cancelEdit: () => {
editFormRef?.current.cancelEditing()
objectFormRef?.current.cancelEditing()
return true
},
finishEdit: () => {
editFormRef?.current.handleUpdate()
objectFormRef?.current.handleUpdate()
return true
}
}
@ -79,10 +79,10 @@ const JobInfo = () => {
<ObjectActions
type='job'
id={jobId}
disabled={editFormState.loading}
disabled={objectFormState.loading}
/>
<ViewButton
disabled={editFormState.loading}
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Job Information' },
{ key: 'subJobs', label: 'Sub Jobs' },
@ -93,11 +93,11 @@ const JobInfo = () => {
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={editFormState.lock} />
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
@ -107,10 +107,10 @@ const JobInfo = () => {
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
@ -119,7 +119,7 @@ const JobInfo = () => {
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
@ -133,7 +133,7 @@ const JobInfo = () => {
id={jobId}
type='job'
style={{ height: '100%' }}
ref={editFormRef}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
@ -186,7 +186,7 @@ const JobInfo = () => {
}
collapseKey='auditLogs'
>
{editFormState.loading ? (
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@ log.setLevel(config.logLevel)
const PrinterInfo = () => {
const location = useLocation()
const editFormRef = useRef(null)
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const printerId = new URLSearchParams(location.search).get('printerId')
const [collapseState, updateCollapseState] = useCollapseState('PrinterInfo', {
@ -35,7 +35,7 @@ const PrinterInfo = () => {
auditLogs: true
})
const [editFormState, setEditFormState] = useState({
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
@ -45,20 +45,20 @@ const PrinterInfo = () => {
const actions = {
reload: () => {
editFormRef?.current.handleFetchObject()
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
editFormRef?.current.startEditing()
objectFormRef?.current.startEditing()
console.log('CALLING START EDITING')
return false
},
cancelEdit: () => {
editFormRef?.current.cancelEditing()
objectFormRef?.current.cancelEditing()
return true
},
finishEdit: () => {
editFormRef?.current.handleUpdate()
objectFormRef?.current.handleUpdate()
return true
}
}
@ -79,10 +79,10 @@ const PrinterInfo = () => {
<ObjectActions
type='printer'
id={printerId}
disabled={editFormState.loading}
disabled={objectFormState.loading}
/>
<ViewButton
disabled={editFormState.loading}
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Printer Information' },
{ key: 'notes', label: 'Notes' },
@ -92,11 +92,11 @@ const PrinterInfo = () => {
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={editFormState.lock} />
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
@ -106,10 +106,10 @@ const PrinterInfo = () => {
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
@ -118,7 +118,7 @@ const PrinterInfo = () => {
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
@ -132,7 +132,7 @@ const PrinterInfo = () => {
id={printerId}
type='printer'
style={{ height: '100%' }}
ref={editFormRef}
ref={objectFormRef}
onStateChange={(state) => {
console.log('Got edit form state change', state)
setEditFormState((prev) => ({ ...prev, ...state }))
@ -174,7 +174,7 @@ const PrinterInfo = () => {
}
collapseKey='auditLogs'
>
{editFormState.loading ? (
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable

View File

@ -12,7 +12,8 @@ const breadcrumbNameMap = {
developer: 'Developer',
overview: 'Overview',
info: 'Info',
design: 'Design'
design: 'Design',
control: 'Control'
}
const mainSections = ['production', 'inventory', 'management', 'developer']

View File

@ -0,0 +1,32 @@
import { Card, Splitter } from 'antd'
import PropTypes from 'prop-types'
import CodeBlockEditor from './CodeBlockEditor'
import MarkdownDisplay from './MarkdownDisplay'
const MarkdownInput = ({ value, onChange }) => {
return (
<Splitter className={'farmcontrol-splitter'} style={{ height: '100%' }}>
<Splitter.Panel>
<Card>
<CodeBlockEditor
code={value}
onChange={onChange}
language='markdown'
/>
</Card>
</Splitter.Panel>
<Splitter.Panel>
<Card style={{ height: '100%' }}>
<MarkdownDisplay content={value} />
</Card>
</Splitter.Panel>
</Splitter>
)
}
MarkdownInput.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func
}
export default MarkdownInput

View File

@ -0,0 +1,29 @@
import { Card, Flex, Typography } from 'antd'
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
import PropTypes from 'prop-types'
const { Text } = Typography
const MissingPlaceholder = ({ message }) => {
return (
<Card size='small'>
<Flex
justify='center'
gap={'small'}
style={{ height: '100%' }}
align='center'
>
<Text type='secondary'>
<InfoCircleIcon />
</Text>
<Text type='secondary'>{message}</Text>
</Flex>
</Card>
)
}
MissingPlaceholder.propTypes = {
message: PropTypes.string.isRequired
}
export default MissingPlaceholder

View File

@ -13,13 +13,15 @@ const NewObjectButtons = ({
}) => {
return (
<Flex justify='end'>
<Button
style={{ margin: '0 8px' }}
onClick={onPrevious}
disabled={currentStep === 0}
>
Previous
</Button>
{totalSteps > 1 ? (
<Button
style={{ margin: '0 8px' }}
onClick={onPrevious}
disabled={currentStep === 0}
>
Previous
</Button>
) : null}
{currentStep < totalSteps - 1 ? (
<Button type='primary' disabled={!formValid} onClick={onNext}>

View File

@ -0,0 +1,313 @@
import { useState, useContext, useCallback, useEffect, useRef } from 'react'
import PropTypes from 'prop-types'
import {
Card,
Button,
Space,
Typography,
Flex,
Tag,
Dropdown,
Divider,
Modal
} from 'antd'
import { CaretLeftFilled, LoadingOutlined } from '@ant-design/icons'
import PlusIcon from '../../Icons/PlusIcon'
import BinIcon from '../../Icons/BinIcon'
import PersonIcon from '../../Icons/PersonIcon'
import TimeDisplay from './TimeDisplay'
import MarkdownDisplay from './MarkdownDisplay'
import IdDisplay from './IdDisplay'
import MissingPlaceholder from './MissingPlaceholder'
import NewNote from '../Management/Notes/NewNote'
import { ApiServerContext } from '../context/ApiServerContext'
import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon'
import { AuthContext } from '../context/AuthContext'
import { getModelByName } from '../../../database/ObjectModels'
import { useNavigate } from 'react-router-dom'
const { Text } = Typography
const NoteItem = ({ note }) => {
const [childNotes, setChildNotes] = useState([])
const noteModel = getModelByName('note')
const infoAction = noteModel.actions.filter(
(action) => action.name == 'info'
)[0]
const InfoIcon = infoAction.icon
const [newNoteOpen, setNewNoteOpen] = useState(false)
const [deleteNoteOpen, setDeleteNoteOpen] = useState(false)
const [deleteNoteLoading, setDeleteNoteLoading] = useState(false)
const [childNotesLoading, setChildNotesLoading] = useState(false)
const [isExpanded, setIsExpanded] = useState(false)
const subscribeToObjectTypeUpdatesRef = useRef(null)
const navigate = useNavigate()
const {
deleteObject,
fetchObjects,
subscribeToObjectTypeUpdates,
connected
} = useContext(ApiServerContext)
const { userProfile, token } = useContext(AuthContext)
let transformValue = 'rotate(0deg)'
if (isExpanded) {
transformValue = 'rotate(-90deg)'
}
const handleNoteExpand = useCallback(async () => {
setChildNotesLoading(true)
try {
const childNotesData = await fetchObjects('note', {
filter: { 'parent._id': note._id }
})
setChildNotes(childNotesData.data)
} catch (error) {
console.error('Error fetching child notes:', error)
} finally {
setChildNotesLoading(false)
}
}, [note._id, fetchObjects])
const toggleExpand = async () => {
if (isExpanded == false) {
await handleNoteExpand()
setIsExpanded(true)
} else {
setChildNotes([])
setIsExpanded(false)
}
}
const handleDeleteNote = async () => {
if (token != null) {
setDeleteNoteLoading(true)
await deleteObject(note._id, 'note')
setDeleteNoteOpen(false)
setDeleteNoteLoading(false)
}
}
useEffect(() => {
if (connected == true && subscribeToObjectTypeUpdatesRef.current == null) {
if (isExpanded == true) {
subscribeToObjectTypeUpdatesRef.current = subscribeToObjectTypeUpdates(
'note',
(noteData) => {
if (noteData.parent._id == note._id) {
console.log(
'Note note added to parent:',
note._id,
'isExpanded:',
isExpanded
)
if (isExpanded == true) {
handleNoteExpand()
}
}
}
)
} else {
if (subscribeToObjectTypeUpdatesRef.current) {
subscribeToObjectTypeUpdatesRef.current()
subscribeToObjectTypeUpdatesRef.current = null
}
}
}
return () => {
if (connected == true && subscribeToObjectTypeUpdatesRef.current) {
subscribeToObjectTypeUpdatesRef.current()
subscribeToObjectTypeUpdatesRef.current = null
}
}
}, [
subscribeToObjectTypeUpdates,
connected,
handleNoteExpand,
isExpanded,
note._id
])
// Check if the current user can delete this note
const canDeleteNote = userProfile && userProfile._id === note.user._id
const dropdownItems = [
{
key: 'new',
icon: <PlusIcon />,
label: 'New Note',
onClick: () => {
setNewNoteOpen(true)
}
}
]
// Only add delete option if user owns the note
if (canDeleteNote) {
dropdownItems.push({
key: 'delete',
label: 'Delete Note',
icon: <BinIcon />,
onClick: () => {
setDeleteNoteOpen(true)
},
danger: true
})
}
return (
<Card
key={note._id}
size='small'
style={{
backgroundColor: note.noteType.color + '26',
textAlign: 'left'
}}
>
<Flex vertical gap={'small'}>
<Flex gap={'middle'} align='start'>
<Space>
<PersonIcon />
<Text style={{ whiteSpace: 'nowrap' }}>{note.user.name}:</Text>
</Space>
<div style={{ marginBottom: '4px' }}>
<MarkdownDisplay content={note.content} />
</div>
</Flex>
<Divider style={{ margin: 0 }} />
<Flex wrap gap={'small'}>
<Dropdown
menu={{ items: dropdownItems }}
trigger={['hover']}
placement='bottomLeft'
>
<Button size='small'>Actions</Button>
</Dropdown>
<Space size={'small'} style={{ marginRight: 8 }}>
<Text type='secondary'>Type:</Text>
<Tag color={note.noteType.color} style={{ margin: 0 }}>
{note.noteType.name}
</Tag>
</Space>
<Space size={'small'} style={{ marginRight: 8 }}>
<Text type='secondary'>User ID:</Text>
<IdDisplay
longId={false}
id={note.user._id}
type={'user'}
showHyperlink={true}
/>
</Space>
<Space size={'small'} style={{ marginRight: 8 }}>
<Text type='secondary'>Created At:</Text>
<TimeDisplay dateTime={note.createdAt} showSince={true} />
</Space>
<Flex style={{ flexGrow: 1 }} justify='end'>
<Space size={'small'}>
<Button
icon={<InfoIcon />}
type='text'
size='small'
onClick={() => {
navigate(infoAction.url(note._id))
}}
/>
<Button
icon={
childNotesLoading ? <LoadingOutlined /> : <CaretLeftFilled />
}
size='small'
type='text'
loading={childNotesLoading}
disabled={childNotesLoading}
style={{
transform: transformValue,
transition: 'transform 0.2s ease'
}}
onClick={toggleExpand}
/>
</Space>
</Flex>
</Flex>
{isExpanded && (
<Flex vertical gap={'small'} style={{ flexGrow: 1 }}>
{childNotes.length > 0 ? (
childNotes.map((childNote) => (
<NoteItem key={childNote._id} note={childNote} />
))
) : (
<MissingPlaceholder message={'No child notes.'} />
)}
</Flex>
)}
<Flex vertical gap={'middle'}></Flex>
</Flex>
<Modal
open={newNoteOpen}
onCancel={() => {
setNewNoteOpen(false)
}}
width={800}
closeIcon={false}
destroyOnHidden={true}
footer={null}
>
<NewNote
onOk={() => {
setNewNoteOpen(false)
}}
defaultValues={{
parent: { _id: note._id },
parentType: 'note'
}}
/>
</Modal>
<Modal
open={deleteNoteOpen}
title={
<Space size={'middle'}>
<ExclamationOctagonIcon />
Confirm Delete
</Space>
}
okText='Delete'
cancelText='Cancel'
okType='danger'
closable={false}
centered
maskClosable={false}
footer={[
<Button
key='cancel'
onClick={() => {
setDeleteNoteOpen(false)
}}
disabled={deleteNoteLoading}
>
Cancel
</Button>,
<Button
key='delete'
type='primary'
danger
onClick={handleDeleteNote}
loading={deleteNoteLoading}
disabled={deleteNoteLoading}
>
Delete
</Button>
]}
>
<Text>Are you sure you want to delete this note?</Text>
</Modal>
</Card>
)
}
NoteItem.propTypes = {
note: PropTypes.object.isRequired
}
export default NoteItem

View File

@ -1,4 +1,4 @@
import { useCallback, useContext, useEffect, useState } from 'react'
import { useCallback, useContext, useEffect, useState, useRef } from 'react'
import PropTypes from 'prop-types'
import {
Card,
@ -7,292 +7,35 @@ import {
Typography,
Flex,
Modal,
Form,
Input,
Switch,
Spin,
Alert,
message,
Divider,
Tag,
Dropdown
} from 'antd'
import { CaretLeftFilled, LoadingOutlined } from '@ant-design/icons'
import { LoadingOutlined } from '@ant-design/icons'
import PlusIcon from '../../Icons/PlusIcon'
import BinIcon from '../../Icons/BinIcon'
import PersonIcon from '../../Icons/PersonIcon'
import TimeDisplay from './TimeDisplay'
import MarkdownDisplay from './MarkdownDisplay'
import axios from 'axios'
import config from '../../../config'
import { AuthContext } from '../context/AuthContext'
import { ApiServerContext } from '../context/ApiServerContext'
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
import IdDisplay from './IdDisplay'
import ReloadIcon from '../../Icons/ReloadIcon'
import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon'
import ObjectProperty from './ObjectProperty'
import NewNote from '../Management/Notes/NewNote'
import NoteItem from './NoteItem'
const { Text, Title } = Typography
const { TextArea } = Input
const { Text } = Typography
const NoteItem = ({
note,
expandedNotes,
setExpandedNotes,
fetchData,
onNewNote,
onDeleteNote,
userProfile,
onChildNoteAdded
}) => {
const [childNotes, setChildNotes] = useState({})
const [loadingChildNotes, setLoadingChildNotes] = useState(null)
const isExpanded = expandedNotes[note._id]
const hasChildNotes = childNotes[note._id] && childNotes[note._id].length > 0
const isThisNoteLoading = loadingChildNotes === note._id
let transformValue = 'rotate(0deg)'
if (isExpanded) {
transformValue = 'rotate(-90deg)'
}
const handleNoteExpand = async (noteId) => {
const newExpandedState = !expandedNotes[noteId]
setExpandedNotes((prev) => ({
...prev,
[noteId]: newExpandedState
}))
if (newExpandedState && !childNotes[noteId]) {
setLoadingChildNotes(noteId)
try {
const childNotesData = await fetchData(noteId)
setChildNotes((prev) => ({
...prev,
[noteId]: childNotesData
}))
} catch (error) {
console.error('Error fetching child notes:', error)
} finally {
setLoadingChildNotes(null)
}
}
}
const handleNewChildNote = () => {
if (onNewNote) {
onNewNote(note._id)
}
}
const handleDeleteNote = () => {
if (onDeleteNote) {
onDeleteNote(note._id)
}
}
// Reload child notes when a new child note is added
const reloadChildNotes = useCallback(async () => {
// Always fetch child notes when this function is called
// This ensures child notes are loaded even if the parent wasn't expanded before
setLoadingChildNotes(note._id)
try {
const childNotesData = await fetchData(note._id)
setChildNotes((prev) => ({
...prev,
[note._id]: childNotesData
}))
} catch (error) {
console.error('Error fetching child notes:', error)
} finally {
setLoadingChildNotes(null)
}
}, [fetchData, note._id])
// Listen for child note additions
useEffect(() => {
if (onChildNoteAdded) {
onChildNoteAdded(note._id, reloadChildNotes)
}
}, [note._id, onChildNoteAdded, reloadChildNotes])
// Check if the current user can delete this note
const canDeleteNote = userProfile && userProfile._id === note.user._id
const dropdownItems = [
{
key: 'new',
icon: <PlusIcon />,
label: 'New Note',
onClick: handleNewChildNote
}
]
// Only add delete option if user owns the note
if (canDeleteNote) {
dropdownItems.push({
key: 'delete',
label: 'Delete Note',
icon: <BinIcon />,
onClick: handleDeleteNote,
danger: true
})
}
return (
<Card
key={note._id}
size='small'
style={{
backgroundColor: note.noteType.color + '26',
textAlign: 'left'
}}
>
<Flex vertical gap={'small'}>
<Flex gap={'middle'} align='start'>
<Space>
<PersonIcon />
<Text style={{ whiteSpace: 'nowrap' }}>{note.user.name}:</Text>
</Space>
<div style={{ marginBottom: '4px' }}>
<MarkdownDisplay content={note.content} />
</div>
</Flex>
<Divider style={{ margin: 0 }} />
<Flex wrap gap={'small'}>
<Dropdown
menu={{ items: dropdownItems }}
trigger={['hover']}
placement='bottomLeft'
>
<Button size='small'>Actions</Button>
</Dropdown>
<Space size={'small'} style={{ marginRight: 8 }}>
<Text type='secondary'>Type:</Text>
<Tag color={note.noteType.color} style={{ margin: 0 }}>
{note.noteType.name}
</Tag>
</Space>
<Space size={'small'} style={{ marginRight: 8 }}>
<Text type='secondary'>User ID:</Text>
<IdDisplay
longId={false}
id={note.user._id}
type={'user'}
showHyperlink={true}
/>
</Space>
<Space size={'small'} style={{ marginRight: 8 }}>
<Text type='secondary'>Created At:</Text>
<TimeDisplay dateTime={note.createdAt} showSince={true} />
</Space>
<Flex style={{ flexGrow: 1 }} justify='end'>
<Space size={'small'}>
<Button
icon={
isThisNoteLoading ? <LoadingOutlined /> : <CaretLeftFilled />
}
size='small'
type='text'
loading={isThisNoteLoading}
disabled={isThisNoteLoading}
style={{
transform: transformValue,
transition: 'transform 0.2s ease'
}}
onClick={() => handleNoteExpand(note._id)}
/>
</Space>
</Flex>
</Flex>
{isExpanded && (
<>
<Flex vertical gap={'small'} style={{ flexGrow: 1 }}>
{hasChildNotes ? (
childNotes[note._id].map((childNote) => (
<NoteItem
key={childNote._id}
note={childNote}
expandedNotes={expandedNotes}
setExpandedNotes={setExpandedNotes}
fetchData={fetchData}
onNewNote={onNewNote}
onDeleteNote={onDeleteNote}
userProfile={userProfile}
onChildNoteAdded={onChildNoteAdded}
/>
))
) : !isThisNoteLoading ? (
<Card size='small'>
<Flex
justify='center'
gap={'small'}
style={{ height: '100%' }}
align='center'
>
<Text type='secondary'>
<InfoCircleIcon />
</Text>
<Text type='secondary'>No child notes.</Text>
</Flex>
</Card>
) : null}
</Flex>
</>
)}
<Flex vertical gap={'middle'}></Flex>
</Flex>
</Card>
)
}
NoteItem.propTypes = {
note: PropTypes.object.isRequired,
expandedNotes: PropTypes.object.isRequired,
setExpandedNotes: PropTypes.func.isRequired,
fetchData: PropTypes.func.isRequired,
onNewNote: PropTypes.func,
onDeleteNote: PropTypes.func,
userProfile: PropTypes.object,
onChildNoteAdded: PropTypes.func
}
const NotesPanel = ({ _id, onNewNote, type }) => {
const NotesPanel = ({ _id, type }) => {
const [newNoteOpen, setNewNoteOpen] = useState(false)
const [showMarkdown, setShowMarkdown] = useState(false)
const [loading, setLoading] = useState(true)
const [initialized, setInitialized] = useState(false)
const [messageApi, contextHolder] = message.useMessage()
const [newNoteFormLoading, setNewNoteFormLoading] = useState(false)
const [newNoteFormValues, setNewNoteFormValues] = useState({})
const [deleteNoteLoading, setDeleteNoteLoading] = useState(false)
const [doneEnabled, setDoneEnabled] = useState(false)
const [error, setError] = useState(null)
const [notes, setNotes] = useState(null)
const [expandedNotes, setExpandedNotes] = useState({})
const [newNoteForm] = Form.useForm()
const [selectedParentId, setSelectedParentId] = useState(null)
const [selectedParentType, setSelectedParentType] = useState(null)
const [childNoteCallbacks, setChildNoteCallbacks] = useState({})
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false)
const [noteToDelete, setNoteToDelete] = useState(null)
const subscribeToObjectTypeUpdatesRef = useRef(null)
const newNoteFormUpdateValues = Form.useWatch([], newNoteForm)
useEffect(() => {
newNoteForm
.validateFields({
validateOnly: true
})
.then(() => setDoneEnabled(true))
.catch(() => setDoneEnabled(false))
}, [newNoteForm, newNoteFormUpdateValues])
const { token, userProfile } = useContext(AuthContext)
const { fetchNotes } = useContext(ApiServerContext)
const { token } = useContext(AuthContext)
const { fetchNotes, connected, subscribeToObjectTypeUpdates } =
useContext(ApiServerContext)
const fetchData = useCallback(
async (id) => {
@ -307,17 +50,6 @@ const NotesPanel = ({ _id, onNewNote, type }) => {
[fetchNotes]
)
const handleNewChildNote = useCallback(
(parentId) => {
setSelectedParentId(parentId)
setSelectedParentType('note')
setNewNoteOpen(true)
newNoteForm.resetFields()
setNewNoteFormValues({})
},
[newNoteForm]
)
const generateNotes = useCallback(
async (id) => {
const notesData = await fetchData(id)
@ -351,114 +83,37 @@ const NotesPanel = ({ _id, onNewNote, type }) => {
note={note}
expandedNotes={expandedNotes}
setExpandedNotes={setExpandedNotes}
fetchData={fetchData}
onNewNote={handleNewChildNote}
onDeleteNote={handleDeleteNote}
userProfile={userProfile}
onChildNoteAdded={(noteId, callback) => {
setChildNoteCallbacks((prev) => ({
...prev,
[noteId]: callback
}))
}}
/>
))
},
[fetchData, expandedNotes, userProfile, handleNewChildNote]
[fetchData, expandedNotes]
)
const handleNewNote = async () => {
setNewNoteFormLoading(true)
try {
await axios.post(
`${config.backendUrl}/notes`,
{
...newNoteFormValues,
parent: selectedParentId,
parentType: selectedParentType
},
{
withCredentials: true
}
)
setNewNoteOpen(false)
messageApi.success('Added a new note.')
// If this is a child note, expand the parent and reload child notes
if (selectedParentId) {
// Ensure parent is expanded
setExpandedNotes((prev) => ({
...prev,
[selectedParentId]: true
}))
// Add a small delay to ensure state update has taken effect
setTimeout(() => {
// Reload child notes for the parent
if (childNoteCallbacks[selectedParentId]) {
childNoteCallbacks[selectedParentId]()
}
}, 100)
} else {
// If it's a top-level note, reload all notes
setLoading(true)
handleReloadData()
}
setSelectedParentId(null)
} catch (error) {
messageApi.error('Error creating new note: ' + error.message)
} finally {
setNewNoteFormLoading(false)
}
}
const handleDeleteNote = async (noteId) => {
setNoteToDelete(noteId)
setDeleteConfirmOpen(true)
}
const confirmDeleteNote = async () => {
if (!noteToDelete) return
setDeleteNoteLoading(true)
try {
await axios.delete(`${config.backendUrl}/notes/${noteToDelete}`, {
withCredentials: true
})
messageApi.success('Note deleted successfully.')
// Reload all top-level notes
setLoading(true)
handleReloadData()
// Reload child notes for all expanded parents to ensure UI stays in sync
const expandedNoteIds = Object.keys(expandedNotes).filter(
(id) => expandedNotes[id]
)
for (const parentId of expandedNoteIds) {
if (childNoteCallbacks[parentId]) {
childNoteCallbacks[parentId]()
}
}
} catch (error) {
messageApi.error('Error deleting note: ' + error.message)
} finally {
setDeleteNoteLoading(false)
setDeleteConfirmOpen(false)
setNoteToDelete(null)
}
}
const cancelDeleteNote = () => {
setDeleteConfirmOpen(false)
setNoteToDelete(null)
}
const handleReloadData = useCallback(async () => {
console.log('GOT RELOAD DATA')
setNotes(await generateNotes(_id))
}, [_id, generateNotes])
useEffect(() => {
if (connected == true && subscribeToObjectTypeUpdatesRef.current == null) {
subscribeToObjectTypeUpdatesRef.current = subscribeToObjectTypeUpdates(
'note',
(noteData) => {
if (noteData.parent._id == _id) {
console.log('Note note added to parent:', _id)
handleReloadData()
}
}
)
}
return () => {
if (connected == true && subscribeToObjectTypeUpdatesRef.current) {
subscribeToObjectTypeUpdatesRef.current()
subscribeToObjectTypeUpdatesRef.current = null
}
}
}, [_id, subscribeToObjectTypeUpdates, connected, handleReloadData])
useEffect(() => {
if (token != null && !initialized) {
handleReloadData()
@ -466,25 +121,6 @@ const NotesPanel = ({ _id, onNewNote, type }) => {
}
}, [token, handleReloadData, initialized])
const handleModalOk = async () => {
try {
const values = await newNoteForm.validateFields()
onNewNote(values)
newNoteForm.resetFields()
setNewNoteOpen(false)
setShowMarkdown(false)
} catch (error) {
console.error('Validation failed:', error)
}
}
const handleModalCancel = () => {
newNoteForm.resetFields()
setNewNoteOpen(false)
setShowMarkdown(false)
setSelectedParentId(null)
}
const actionItems = {
items: [
{
@ -504,11 +140,7 @@ const NotesPanel = ({ _id, onNewNote, type }) => {
setLoading(true)
handleReloadData()
} else if (key === 'newNote') {
setSelectedParentId(_id)
setSelectedParentType(type)
setNewNoteOpen(true)
newNoteForm.resetFields()
setNewNoteFormValues({})
}
}
}
@ -528,11 +160,7 @@ const NotesPanel = ({ _id, onNewNote, type }) => {
icon={<PlusIcon />}
disabled={loading}
onClick={() => {
setSelectedParentId(_id)
setSelectedParentType(type)
setNewNoteOpen(true)
newNoteForm.resetFields()
setNewNoteFormValues({})
}}
/>
</Space>
@ -552,146 +180,24 @@ const NotesPanel = ({ _id, onNewNote, type }) => {
<Modal
open={newNoteOpen}
onOk={handleModalOk}
onCancel={handleModalCancel}
width={800}
closeIcon={false}
destroyOnHidden={true}
footer={false}
footer={null}
onCancel={() => {
setNewNoteOpen(false)
}}
>
<Flex vertical gap='large'>
<Flex vertical gap='middle'>
<Flex align='center' justify='space-between'>
<Title level={2} style={{ marginTop: 0, marginBottom: 4 }}>
New Note
</Title>
<Space gap={'small'}>
<Text type='secondary'>Markdown:</Text>
<Switch onChange={setShowMarkdown} size='small' />
</Space>
</Flex>
<Form
form={newNoteForm}
layout='vertical'
onFinish={handleNewNote}
initialValues={{ content: '' }}
onValuesChange={(changedValues) =>
setNewNoteFormValues((prevValues) => ({
...prevValues,
...changedValues
}))
}
>
<Flex vertical gap={'large'}>
<Flex gap='middle' wrap>
<Form.Item
name='content'
rules={[{ required: true, message: '' }]}
style={{ margin: 0, flexGrow: 1, minWidth: '300px' }}
>
<TextArea
rows={6}
placeholder='Enter note content'
style={{ resize: 'none' }}
/>
</Form.Item>
{showMarkdown && (
<Card
style={{
flexGrow: 1,
minWidth: '300px',
backgroundColor: () => {
if (newNoteFormValues?.noteType?.color) {
return newNoteFormValues.noteType.color + '26'
}
}
}}
>
<MarkdownDisplay
content={newNoteForm.getFieldValue('content') || ''}
/>
</Card>
)}
</Flex>
<Form.Item
name='noteType'
style={{ margin: 0 }}
rules={[
{ required: true, message: 'Please select a note type' }
]}
>
<ObjectProperty
type='object'
objectType='noteType'
isEditing={true}
/>
</Form.Item>
</Flex>
</Form>
</Flex>
<Flex justify='end'>
<Button
style={{ margin: '0 8px' }}
disabled={newNoteFormLoading}
onClick={() => {
setNewNoteOpen(false)
}}
>
Cancel
</Button>
<Button
type='primary'
loading={newNoteFormLoading}
onClick={() => {
newNoteForm.submit()
}}
disabled={newNoteFormLoading || !doneEnabled}
>
Done
</Button>
</Flex>
</Flex>
</Modal>
<Modal
open={deleteConfirmOpen}
title={
<Space size={'middle'}>
<ExclamationOctagonIcon />
Confirm Delete
</Space>
}
onOk={confirmDeleteNote}
onCancel={cancelDeleteNote}
okText='Delete'
cancelText='Cancel'
okType='danger'
closable={false}
centered
maskClosable={false}
footer={[
<Button
key='cancel'
onClick={cancelDeleteNote}
disabled={deleteNoteLoading}
>
Cancel
</Button>,
<Button
key='delete'
type='primary'
danger
onClick={confirmDeleteNote}
loading={deleteNoteLoading}
disabled={deleteNoteLoading}
>
Delete
</Button>
]}
>
<Text>Are you sure you want to delete this note?</Text>
<NewNote
onOk={() => {
setNewNoteOpen(false)
messageApi.success('New note added.')
}}
reset={newNoteOpen}
defaultValues={{
parent: { _id },
parentType: type
}}
/>
</Modal>
</Flex>
)

View File

@ -1,5 +1,5 @@
import PropTypes from 'prop-types'
import { Typography, Flex } from 'antd'
import { Typography, Flex, Badge } from 'antd'
import { useState, useEffect, useContext, useCallback } from 'react'
import { getModelByName } from '../../../database/ObjectModels'
import { ApiServerContext } from '../context/ApiServerContext'
@ -52,9 +52,10 @@ const ObjectDisplay = ({ object, objectType }) => {
const model = getModelByName(objectType)
const Icon = model.icon
return (
<Flex gap={'small'}>
<Flex gap={'small'} align='center'>
<Icon />
<Text>{objectData?.name ? objectData.name : null}</Text>
{objectData?.color ? <Badge color={objectData?.color} /> : null}
<Text ellipsis>{objectData?.name ? objectData.name : null}</Text>
</Flex>
)
}

View File

@ -8,11 +8,21 @@ const ObjectInfo = ({
loading = false,
isEditing = false,
type = 'unknown',
showHyperlink,
showLabels = true,
objectData = null,
properties = [],
required = undefined,
visibleProperties = {},
objectPropertyProps = {},
column = {
xs: 1,
sm: 1,
md: 1,
lg: 2,
xl: 2,
xxl: 2
},
...rest
}) => {
const allItems = getModelProperties(type)
@ -47,33 +57,29 @@ const ObjectInfo = ({
const key = item.name || item.label || idx
return {
key,
label: (
<Flex vertical style={{ height: '100%' }} justify='center'>
{item.label}
</Flex>
),
label:
showLabels == true ? (
<Flex vertical style={{ height: '100%' }} justify='center'>
{item.label}
</Flex>
) : null,
children: (
<ObjectProperty
{...item}
{...objectPropertyProps}
showHyperlink={showHyperlink}
isEditing={isEditing}
objectData={objectData}
/>
)
),
span: item?.span || undefined
}
})
return (
<Spin spinning={loading} indicator={<LoadingOutlined />}>
<Descriptions
column={{
xs: 1,
sm: 1,
md: 1,
lg: 2,
xl: 2,
xxl: 2
}}
column={column}
bordered={true}
{...rest}
items={descriptionItems}
@ -84,6 +90,9 @@ const ObjectInfo = ({
ObjectInfo.propTypes = {
loading: PropTypes.bool,
column: PropTypes.object,
showHyperlink: PropTypes.bool,
showLabels: PropTypes.bool,
indicator: PropTypes.node,
properties: PropTypes.array,
bordered: PropTypes.bool,

View File

@ -31,6 +31,7 @@ import ObjectList from './ObjectList'
import VarianceDisplay from './VarianceDisplay'
import OperationDisplay from './OperationDisplay'
import MarkdownDisplay from './MarkdownDisplay'
import MarkdownInput from './MarkdownInput'
import ObjectSelect from './ObjectSelect'
import ObjectDisplay from './ObjectDisplay'
import ObjectTypeSelect from './ObjectTypeSelect'
@ -603,6 +604,12 @@ const ObjectProperty = ({
/>
</Form.Item>
)
case 'markdown':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<MarkdownInput value={value} />
</Form.Item>
)
case 'material':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>

View File

@ -39,7 +39,7 @@ import CheckIcon from '../../Icons/CheckIcon'
import { useNavigate } from 'react-router-dom'
import QuestionCircleIcon from '../../Icons/QuestionCircleIcon'
import { AuthContext } from '../context/AuthContext'
import merge from 'lodash/merge'
import unionBy from 'lodash/unionBy'
const logger = loglevel.getLogger('DasboardTable')
logger.setLevel(config.logLevel)
@ -88,9 +88,10 @@ const ObjectTable = forwardRef(
const [lazyLoading, setLazyLoading] = useState(false)
const subscribedIdsRef = useRef([])
const [typeSubscribed, setTypeSubscribed] = useState(false)
// const [typeSubscribed, setTypeSubscribed] = useState(false)
const unsubscribesRef = useRef([])
const updateEventHandlerRef = useRef()
const subscribeToObjectTypeUpdatesRef = useRef(null)
const rowActions =
model.actions?.filter((action) => action.row == true) || []
@ -291,12 +292,17 @@ const ObjectTable = forwardRef(
const updateEventHandler = useCallback((id, updatedData) => {
console.log('GOT UPDATE FOR', id)
setPages((prevPages) =>
prevPages.map((page) => ({
...page,
items: page.items.map((item) => {
return item._id === id ? merge({}, item, updatedData) : item
})
}))
prevPages.map((page) => {
const updatedItems = unionBy(
[{ ...updatedData, _id: id }],
page.items,
'_id'
)
return {
...page,
items: updatedItems
}
})
)
}, [])
@ -363,33 +369,43 @@ const ObjectTable = forwardRef(
// Cleanup effect for component unmount
useEffect(() => {
return () => {
// Clean up all subscriptions when component unmounts
unsubscribesRef.current.forEach((unsubscribe) => {
console.log('CALLING UNSUB on unmount')
if (unsubscribe) unsubscribe()
})
unsubscribesRef.current = []
subscribedIdsRef.current = []
}
}, [])
console.log('API: Call unsub', connected)
if (connected == true && unsubscribesRef.current) {
console.log('API: Got object unsub')
// Clean up all subscriptions when component unmounts
unsubscribesRef.current.forEach((unsubscribe) => {
console.log('CALLING UNSUB on unmount')
if (unsubscribe) unsubscribe()
})
unsubscribesRef.current = []
subscribedIdsRef.current = []
useEffect(() => {
if (connected == true && typeSubscribed == false) {
const unsubscribe = subscribeToObjectTypeUpdates(type, newEventHandler)
setTypeSubscribed(true)
return () => {
if (unsubscribe && typeSubscribed == true) {
unsubscribe()
}
// Clean up type subscription
}
if (connected == true && subscribeToObjectTypeUpdatesRef.current) {
console.log('UNSUBBING type subscription on unmount')
console.log('API: Got type unsub')
subscribeToObjectTypeUpdatesRef.current()
subscribeToObjectTypeUpdatesRef.current = null
}
}
}, [
type,
subscribeToObjectTypeUpdates,
connected,
newEventHandler,
typeSubscribed
])
}, [connected])
useEffect(() => {
if (
connected == true &&
subscribeToObjectTypeUpdatesRef.current == null
) {
console.log(
'API: Subbing to updates',
subscribeToObjectTypeUpdatesRef.current
)
subscribeToObjectTypeUpdatesRef.current = subscribeToObjectTypeUpdates(
type,
newEventHandler
)
}
}, [type, subscribeToObjectTypeUpdates, connected, newEventHandler])
const updateData = useCallback(
(_id, updatedData) => {

View File

@ -1,5 +1,5 @@
// PrinterTemperaturePanel.js
import { useContext, useState, useEffect, useCallback } from 'react'
import { useContext, useState, useEffect } from 'react'
import {
Progress,
Typography,
@ -11,9 +11,10 @@ import {
Button
} from 'antd'
import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons'
import { PrintServerContext } from '../context/PrintServerContext'
import styled from 'styled-components'
import PropTypes from 'prop-types'
import { ApiServerContext } from '../context/ApiServerContext'
import merge from 'lodash/merge'
const { Text } = Typography
const { Panel } = Collapse
@ -30,90 +31,69 @@ const CustomCollapse = styled(Collapse)`
`
const PrinterTemperaturePanel = ({
printerId,
showHotEndControls = true,
showHeatedBedControls = true,
showHotEnd = true,
showHeatedBed = true,
showMoreInfo = true,
shouldUnsubscribe = true
id,
showExtruderControls = true,
showBedControls = true,
showExtruder = true,
showBed = true,
showMoreInfo = true
}) => {
const [temperatureData, setTemperatureData] = useState({
hotEnd: {},
heatedBed: {}
extruder: {
current: 0,
target: 0,
power: 0
},
bed: {
current: 0,
target: 0,
power: 0
},
pinda: 0,
ambiant: 0
})
const [hotEndTemperature, setHotEndTemperature] = useState(
temperatureData?.hotEnd?.target || 0
)
const [heatedBedTemperature, setHeatedBedTemperature] = useState(
temperatureData?.heatedBed?.target || 0
)
const { printServer } = useContext(PrintServerContext)
const { subscribeToObjectEvent, connected } = useContext(ApiServerContext)
const notifyTemperatureStatusUpdate = useCallback((statusUpdate) => {
setTemperatureData((prev) => {
const temperatureObject = {
...prev
}
if (statusUpdate?.extruder?.temperature !== undefined) {
temperatureObject.hotEnd.current = statusUpdate?.extruder?.temperature
}
if (statusUpdate?.heater_bed?.temperature !== undefined) {
temperatureObject.heatedBed.current =
statusUpdate?.heater_bed?.temperature
}
if (statusUpdate?.extruder?.target !== undefined) {
temperatureObject.hotEnd.target = statusUpdate?.extruder?.target
setHotEndTemperature(statusUpdate?.extruder?.target)
}
if (statusUpdate?.heater_bed?.target !== undefined) {
temperatureObject.heatedBed.target = statusUpdate?.heater_bed?.target
setHeatedBedTemperature(statusUpdate?.heater_bed?.target)
}
if (statusUpdate?.extruder?.power !== undefined) {
temperatureObject.hotEnd.power = statusUpdate?.extruder?.power
}
if (statusUpdate?.heater_bed?.power !== undefined) {
temperatureObject.heatedBed.power = statusUpdate?.heater_bed?.power
}
return temperatureObject
})
}, [])
// Sync input values with actual temperature targets
useEffect(() => {
if (temperatureData.extruder?.target !== undefined) {
setExtruderTarget(temperatureData.extruder.target)
}
}, [temperatureData.extruder?.target])
useEffect(() => {
const params = {
printerId,
objects: {
extruder: null,
heater_bed: null // eslint-disable-line
}
if (temperatureData.bed?.target !== undefined) {
setBedTarget(temperatureData.bed.target)
}
if (printServer?.connected == true) {
printServer.emit('printer.objects.subscribe', params)
printServer.emit('printer.objects.query', params)
printServer.on('notify_status_update', notifyTemperatureStatusUpdate)
}
return () => {
if (printServer && shouldUnsubscribe == true) {
printServer.off('notify_status_update', notifyTemperatureStatusUpdate)
printServer.emit('printer.objects.unsubscribe', params)
}
}
}, [printServer, printerId, notifyTemperatureStatusUpdate, shouldUnsubscribe])
}, [temperatureData.bed?.target])
const handleSetTemperatureClick = (target, value) => {
if (printServer) {
printServer.emit('printer.gcode.script', {
printerId,
script: `SET_HEATER_TEMPERATURE HEATER=${target} TARGET=${value}`
})
useEffect(() => {
if (id && connected) {
const temperatureEventUnsubscribe = subscribeToObjectEvent(
id,
'printer',
'temperature',
(event) => {
setTemperatureData((prev) => {
const merged = merge({}, prev, event.data)
return merged
})
}
)
return () => {
if (temperatureEventUnsubscribe) temperatureEventUnsubscribe()
}
}
}, [id, connected])
const [extruderTarget, setExtruderTarget] = useState(0)
const [bedTarget, setBedTarget] = useState(0)
const handleSetTemperature = (data) => {
if (id && connected == true) {
console.log(data)
//sendObjectAction(id, 'printer', { type: 'setTemperature', data })
}
}
@ -126,33 +106,32 @@ const PrinterTemperaturePanel = ({
<Flex vertical gap={0}>
<Text>
Hot End Power:{' '}
{Math.round((temperatureData.hotEnd.power || 0) * 100)}%
{Math.round((temperatureData.extruder.power || 0) * 100)}%
</Text>
<Progress
percent={(temperatureData.hotEnd.power || 0) * 100}
percent={(temperatureData.extruder.power || 0) * 100}
showInfo={false}
/>
</Flex>
<Flex vertical gap={0}>
<Text>
Bed Power:{' '}
{Math.round((temperatureData.heatedBed.power || 0) * 100)}%
Bed Power: {Math.round((temperatureData.bed.power || 0) * 100)}%
</Text>
<Progress
percent={(temperatureData.heatedBed.power || 0) * 100}
percent={(temperatureData.bed.power || 0) * 100}
showInfo={false}
/>
</Flex>
<Panel>
{typeof temperatureData.pindaTemp !== 'undefined' && (
{typeof temperatureData.pinda !== 'undefined' && (
<Flex vertical gap={0}>
<Text>Pinda Temp: {temperatureData.pindaTemp}°C</Text>
</Flex>
)}
{typeof temperatureData.ambiantActual !== 'undefined' && (
{typeof temperatureData.ambiant !== 'undefined' && (
<Flex vertical gap={0}>
<Text>Ambient Actual: {temperatureData.ambiantActual}°C</Text>
<Text>Ambient Actual: {temperatureData.ambiant}°C</Text>
</Flex>
)}
</Panel>
@ -165,41 +144,41 @@ const PrinterTemperaturePanel = ({
<div style={{ minWidth: 190 }}>
{temperatureData ? (
<Flex vertical gap='middle'>
{temperatureData.hotEnd && showHotEnd && (
{temperatureData.extruder && showExtruder && (
<Flex vertical gap={0}>
<Text>
Hot End: {temperatureData.hotEnd.current}°C /{' '}
{temperatureData.hotEnd.target}°C
Hot End: {temperatureData.extruder.current}°C /{' '}
{temperatureData.extruder.target}°C
</Text>
<Progress
percent={(temperatureData.hotEnd.target / 300) * 100}
percent={(temperatureData.extruder.target / 300) * 100}
strokeColor='#FF392F1D'
success={{
percent: (temperatureData.hotEnd.current / 300) * 100,
percent: (temperatureData.extruder.current / 300) * 100,
strokeColor: '#FF3B2F'
}}
showInfo={false}
/>
{showHotEndControls && (
{showExtruderControls && (
<Space direction='horizontal' style={{ marginTop: 5 }}>
<Space.Compact block size='small'>
<InputNumber
value={hotEndTemperature}
value={extruderTarget}
min={0}
max={300}
style={{ width: '120px' }}
addonAfter='°C'
onChange={(value) => setHotEndTemperature(value)}
onPressEnter={() =>
handleSetTemperatureClick('extruder', hotEndTemperature)
}
onChange={(value) => setExtruderTarget(value || 0)}
onPressEnter={handleSetTemperature({
extruder: { target: extruderTarget }
})}
/>
<Button
type='default'
style={{ width: 40 }}
onClick={() =>
handleSetTemperatureClick('extruder', hotEndTemperature)
}
onClick={handleSetTemperature({
extruder: { target: extruderTarget }
})}
>
Set
</Button>
@ -208,7 +187,9 @@ const PrinterTemperaturePanel = ({
type='default'
size='small'
style={{ width: 40 }}
onClick={() => handleSetTemperatureClick('extruder', 0)}
onClick={() =>
handleSetTemperature({ extruder: { target: 0 } })
}
>
Off
</Button>
@ -217,50 +198,44 @@ const PrinterTemperaturePanel = ({
</Flex>
)}
{temperatureData.heatedBed && showHeatedBed && (
{temperatureData.bed && showBed && (
<Flex vertical gap={0}>
<Text>
Heated Bed: {temperatureData.heatedBed.current}°C /{' '}
{temperatureData.heatedBed.target}°C
Heated Bed: {temperatureData.bed.current}°C /{' '}
{temperatureData.bed.target}°C
</Text>
<Progress
percent={(temperatureData.heatedBed.target / 300) * 100}
percent={(temperatureData.bed.target / 300) * 100}
strokeColor='#FF392F1D'
success={{
percent: (temperatureData.heatedBed.current / 300) * 100,
percent: (temperatureData.bed.current / 300) * 100,
strokeColor: '#FF3B2F'
}}
showInfo={false}
/>
{showHeatedBedControls && (
{showBedControls && (
<Space
direction='horizontal'
style={{ marginTop: 5, height: '21px' }}
>
<Space.Compact block size='small'>
<InputNumber
value={heatedBedTemperature}
value={bedTarget}
min={0}
max={300}
style={{ width: '120px' }}
addonAfter='°C'
onChange={(value) => setHeatedBedTemperature(value)}
onPressEnter={() =>
handleSetTemperatureClick(
'heater_bed',
heatedBedTemperature
)
}
onChange={(value) => setBedTarget(value || 0)}
onPressEnter={handleSetTemperature({
bed: { target: bedTarget }
})}
/>
<Button
type='default'
style={{ width: 40 }}
onClick={() =>
handleSetTemperatureClick(
'heater_bed',
heatedBedTemperature
)
}
onClick={handleSetTemperature({
bed: { target: bedTarget }
})}
>
Set
</Button>
@ -269,7 +244,11 @@ const PrinterTemperaturePanel = ({
type='default'
size='small'
style={{ width: 40 }}
onClick={() => handleSetTemperatureClick('heater_bed', 0)}
onClick={() =>
handleSetTemperature({
bed: { target: 0 }
})
}
>
Off
</Button>
@ -298,11 +277,11 @@ const PrinterTemperaturePanel = ({
}
PrinterTemperaturePanel.propTypes = {
printerId: PropTypes.string.isRequired,
showHotEndControls: PropTypes.bool,
showHeatedBedControls: PropTypes.bool,
showHotEnd: PropTypes.bool,
showHeatedBed: PropTypes.bool,
id: PropTypes.string.isRequired,
showExtruderControls: PropTypes.bool,
showBedControls: PropTypes.bool,
showExtruder: PropTypes.bool,
showBed: PropTypes.bool,
showMoreInfo: PropTypes.bool,
shouldUnsubscribe: PropTypes.bool
}

View File

@ -0,0 +1,67 @@
import PropTypes from 'prop-types'
import { useState } from 'react'
import { useMediaQuery } from 'react-responsive'
import { Typography, Flex, Steps, Divider } from 'antd'
import NewObjectButtons from './NewObjectButtons'
const { Title } = Typography
const WizardView = ({
showSteps = true,
steps,
title = 'Wizard View',
onSubmit,
formValid,
loading
}) => {
const [currentStep, setCurrentStep] = useState(0)
const isMobile = useMediaQuery({ maxWidth: 768 })
return (
<Flex gap='middle'>
{!isMobile && showSteps == true ? (
<div style={{ minWidth: '160px' }}>
<Steps
current={currentStep}
items={steps}
direction='vertical'
style={{ width: 'fit-content' }}
/>
</div>
) : null}
{!isMobile && showSteps == true ? (
<Divider type='vertical' style={{ height: 'unset' }} />
) : null}
<Flex vertical gap='middle' style={{ flexGrow: 1 }}>
<Title level={2} style={{ margin: 0 }}>
{title}
</Title>
<div style={{ minHeight: '260px', marginBottom: 8 }}>
{steps[currentStep].content}
</div>
<NewObjectButtons
currentStep={currentStep}
totalSteps={steps.length}
onPrevious={() => setCurrentStep((prev) => prev - 1)}
onNext={() => setCurrentStep((prev) => prev + 1)}
onSubmit={onSubmit}
formValid={formValid}
submitLoading={loading}
/>
</Flex>
</Flex>
)
}
WizardView.propTypes = {
onSubmit: PropTypes.func.isRequired,
formValid: PropTypes.bool.isRequired,
steps: PropTypes.array.isRequired,
showSteps: PropTypes.bool,
title: PropTypes.string,
loading: PropTypes.bool
}
export default WizardView

View File

@ -93,7 +93,9 @@ const ApiServerProvider = ({ children }) => {
})
newSocket.on('objectUpdate', handleObjectUpdate)
newSocket.on('objectEvent', handleObjectEvent)
newSocket.on('objectNew', handleObjectNew)
newSocket.on('objectDelete', handleObjectDelete)
newSocket.on('lockUpdate', handleLockUpdate)
newSocket.on('disconnect', () => {
@ -216,6 +218,37 @@ const ApiServerProvider = ({ children }) => {
}
}
const handleObjectEvent = async (data) => {
const id = data._id
const objectType = data.objectType
const callbacksRefKey = `${objectType}:${id}:events:${data.event.type}`
logger.debug('Notifying object event:', data)
if (
id &&
objectType &&
subscribedCallbacksRef.current.has(callbacksRefKey)
) {
const callbacks = subscribedCallbacksRef.current.get(callbacksRefKey)
logger.debug(
`Calling ${callbacks.length} callbacks for object:`,
callbacksRefKey
)
callbacks.forEach((callback) => {
try {
callback(data.event)
} catch (error) {
logger.error('Error in object event callback:', error)
}
})
} else {
logger.debug(
`No callbacks found for object: ${callbacksRefKey}, subscribed callbacks:`,
Array.from(subscribedCallbacksRef.current.keys())
)
}
}
const handleObjectNew = async (data) => {
logger.debug('Notifying object new:', data)
const objectType = data.objectType || 'unknown'
@ -241,6 +274,31 @@ const ApiServerProvider = ({ children }) => {
}
}
const handleObjectDelete = async (data) => {
logger.debug('Notifying object delete:', data)
const objectType = data.objectType || 'unknown'
if (objectType && subscribedCallbacksRef.current.has(objectType)) {
const callbacks = subscribedCallbacksRef.current.get(objectType)
logger.debug(
`Calling ${callbacks.length} callbacks for type:`,
objectType
)
callbacks.forEach((callback) => {
try {
callback(data.object)
} catch (error) {
logger.error('Error in object new callback:', error)
}
})
} else {
logger.debug(
`No callbacks found for object: ${objectType}, subscribed callbacks:`,
Array.from(subscribedCallbacksRef.current.keys())
)
}
}
const offObjectUpdatesEvent = useCallback((id, objectType, callback) => {
if (socketRef.current && socketRef.current.connected == true) {
const callbacksRefKey = `${objectType}:${id}`
@ -251,7 +309,10 @@ const ApiServerProvider = ({ children }) => {
.filter((cb) => cb !== callback)
if (callbacks.length === 0) {
subscribedCallbacksRef.current.delete(callbacksRefKey)
socketRef.current.emit('unsubscribe', { id: id, type: objectType })
socketRef.current.emit('unsubscribeObjectUpdate', {
id: id,
objectType: objectType
})
} else {
subscribedCallbacksRef.current.set(callbacksRefKey, callbacks)
}
@ -262,14 +323,22 @@ const ApiServerProvider = ({ children }) => {
const offObjectTypeUpdatesEvent = useCallback((objectType, callback) => {
if (socketRef.current && socketRef.current.connected == true) {
// Remove callback from the subscribed callbacks map
console.log('Unsubscribing from type')
console.log(
'Unsubscribing from type',
objectType,
subscribedCallbacksRef.current.has(objectType)
)
if (subscribedCallbacksRef.current.has(objectType)) {
const callbacks = subscribedCallbacksRef.current
.get(objectType)
.filter((cb) => cb !== callback)
console.log('API: CALLBACKS', callbacks)
if (callbacks.length === 0) {
subscribedCallbacksRef.current.delete(objectType)
socketRef.current.emit('unsubscribe', { objectType: objectType })
socketRef.current.emit('unsubscribeObjectTypeUpdate', {
objectType: objectType
})
} else {
subscribedCallbacksRef.current.set(objectType, callbacks)
}
@ -342,7 +411,7 @@ const ApiServerProvider = ({ children }) => {
}
}
)
logger.debug('Registered update event listener for object:', objectType)
logger.debug('Registered type event listener for object:', objectType)
// Return cleanup function
return () => offObjectTypeUpdatesEvent(objectType, callback)
@ -369,6 +438,84 @@ const ApiServerProvider = ({ children }) => {
}
}, [])
const offObjectEventEvent = useCallback(
(id, objectType, eventType, callback) => {
if (socketRef.current && socketRef.current.connected == true) {
const callbacksRefKey = `${objectType}:${id}:events:${eventType}`
// Remove callback from the subscribed callbacks map
if (subscribedCallbacksRef.current.has(callbacksRefKey)) {
const callbacks = subscribedCallbacksRef.current
.get(callbacksRefKey)
.filter((cb) => cb !== callback)
if (callbacks.length === 0) {
subscribedCallbacksRef.current.delete(callbacksRefKey)
socketRef.current.emit('unsubscribeObjectEvent', {
_id: id,
objectType,
eventType
})
} else {
subscribedCallbacksRef.current.set(callbacksRefKey, callbacks)
}
}
}
},
[]
)
const subscribeToObjectEvent = useCallback(
(id, objectType, eventType, callback) => {
if (socketRef.current && socketRef.current.connected == true) {
const callbacksRefKey = `${objectType}:${id}:events:${eventType}`
// Add callback to the subscribed callbacks map immediately
if (!subscribedCallbacksRef.current.has(callbacksRefKey)) {
subscribedCallbacksRef.current.set(callbacksRefKey, [])
}
const callbacksLength =
subscribedCallbacksRef.current.get(callbacksRefKey).length
if (callbacksLength <= 0) {
socketRef.current.emit(
'subscribeToObjectEvent',
{
_id: id,
objectType: objectType,
eventType: eventType
},
(result) => {
if (result.success) {
logger.info(
'Subscribed to event id:',
id,
'objectType:',
objectType,
'eventType:',
eventType
)
}
}
)
}
logger.info(
'Adding event callback id:',
id,
'objectType:',
objectType,
'eventType:',
eventType,
'callbacks length:',
callbacksLength + 1
)
subscribedCallbacksRef.current.get(callbacksRefKey).push(callback)
// Return cleanup function
return () => offObjectEventEvent(id, objectType, eventType, callback)
}
},
[offObjectUpdatesEvent]
)
const subscribeToObjectLock = useCallback(
(id, type, callback) => {
logger.debug('Subscribing to lock for object:', id, 'type:', type)
@ -548,13 +695,6 @@ const ApiServerProvider = ({ children }) => {
}
})
logger.debug('Object updated successfully')
if (socketRef.current && socketRef.current.connected == true) {
await socketRef.current.emit('update', {
_id: id,
type: type,
updatedAt: response.data.updatedAt
})
}
return response.data
} catch (err) {
console.error(err)
@ -577,13 +717,6 @@ const ApiServerProvider = ({ children }) => {
}
})
logger.debug('Object deleted successfully')
if (socketRef.current && socketRef.current.connected == true) {
await socketRef.current.emit('update', {
_id: id,
type: type,
updatedAt: response.data.updatedAt
})
}
return response.data
} catch (err) {
console.error(err)
@ -649,7 +782,7 @@ const ApiServerProvider = ({ children }) => {
try {
const response = await axios.get(`${config.backendUrl}/notes`, {
params: {
parent: parentId,
'parent._id': parentId,
sort: 'createdAt',
order: 'ascend'
},
@ -755,6 +888,7 @@ const ApiServerProvider = ({ children }) => {
createObject,
deleteObject,
subscribeToObjectUpdates,
subscribeToObjectEvent,
subscribeToObjectTypeUpdates,
subscribeToObjectLock,
fetchObject,

View File

@ -30,13 +30,6 @@ export const Note = {
type: 'dateTime',
readOnly: true
},
{
name: 'noteType',
label: 'Note Type',
type: 'object',
objectType: 'noteType',
showHyperlink: true
},
{
name: 'parent._id',
label: 'Parent ID',
@ -44,7 +37,23 @@ export const Note = {
objectType: (objectData) => {
return objectData.parentType
},
showHyperlink: true
showHyperlink: true,
required: true
},
{
name: 'content',
label: 'Content',
type: 'markdown',
required: true,
span: 2
},
{
name: 'noteType',
label: 'Note Type',
type: 'object',
objectType: 'noteType',
showHyperlink: true,
required: true
},
{
name: 'noteType._id',
@ -58,11 +67,6 @@ export const Note = {
type: 'id',
objectType: 'user'
},
{
name: 'content',
label: 'Content',
type: 'markdown'
},
{
name: 'updatedAt',
label: 'Updated At',

View File

@ -143,6 +143,21 @@ export const Printer = {
type: 'text',
required: false,
readOnly: true
},
{
name: 'currentFilamentStock',
label: 'Filament Stock',
type: 'object',
objectType: 'filamentStock',
required: true
},
{
name: 'currentFilamentStock._id',
label: 'Filament Stock ID',
type: 'id',
objectType: 'filamentStock',
showHyperlink: true,
readOnly: true
}
]
}

View File

@ -13,6 +13,7 @@ import Settings from '../components/Dashboard/Management/Settings'
import AuditLogs from '../components/Dashboard/Management/AuditLogs.jsx'
import NoteTypes from '../components/Dashboard/Management/NoteTypes.jsx'
import NoteTypeInfo from '../components/Dashboard/Management/NoteTypes/NoteTypeInfo.jsx'
import NoteInfo from '../components/Dashboard/Management/Notes/NoteInfo.jsx'
import Users from '../components/Dashboard/Management/Users.jsx'
import UserInfo from '../components/Dashboard/Management/Users/UserInfo.jsx'
import Hosts from '../components/Dashboard/Management/Hosts.jsx'
@ -68,6 +69,7 @@ const ManagementRoutes = [
path='management/notetypes/info'
element={<NoteTypeInfo />}
/>,
<Route key='note-info' path='management/notes/info' element={<NoteInfo />} />,
<Route
key='documentsizes'
path='management/documentsizes'

364
yarn.lock
View File

@ -724,7 +724,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.27.1"
"@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.7", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.6", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.1", "@babel/runtime@^7.24.4", "@babel/runtime@^7.24.7", "@babel/runtime@^7.24.8", "@babel/runtime@^7.25.6", "@babel/runtime@^7.25.7", "@babel/runtime@^7.26.0":
"@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.6", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.1", "@babel/runtime@^7.24.4", "@babel/runtime@^7.24.7", "@babel/runtime@^7.24.8", "@babel/runtime@^7.25.6", "@babel/runtime@^7.25.7", "@babel/runtime@^7.26.0":
version "7.28.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.3.tgz#75c5034b55ba868121668be5d5bb31cc64e6e61a"
integrity sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==
@ -759,33 +759,6 @@
"@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1"
"@chevrotain/cst-dts-gen@10.5.0":
version "10.5.0"
resolved "https://registry.yarnpkg.com/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz#922ebd8cc59d97241bb01b1b17561a5c1ae0124e"
integrity sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==
dependencies:
"@chevrotain/gast" "10.5.0"
"@chevrotain/types" "10.5.0"
lodash "4.17.21"
"@chevrotain/gast@10.5.0":
version "10.5.0"
resolved "https://registry.yarnpkg.com/@chevrotain/gast/-/gast-10.5.0.tgz#e4e614bc46d17a8892742f38e56cd33f1f3ad162"
integrity sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==
dependencies:
"@chevrotain/types" "10.5.0"
lodash "4.17.21"
"@chevrotain/types@10.5.0":
version "10.5.0"
resolved "https://registry.yarnpkg.com/@chevrotain/types/-/types-10.5.0.tgz#52a97d74a8cfbc197f054636d93ecd8912d33d21"
integrity sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==
"@chevrotain/utils@10.5.0":
version "10.5.0"
resolved "https://registry.yarnpkg.com/@chevrotain/utils/-/utils-10.5.0.tgz#0ee36f65b49b447fbac71b9e5af5c5c6c98ac057"
integrity sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==
"@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.3.2", "@codemirror/autocomplete@^6.7.1":
version "6.18.6"
resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz#de26e864a1ec8192a1b241eb86addbb612964ddb"
@ -1918,27 +1891,10 @@
rc-resize-observer "^1.3.1"
rc-util "^5.44.0"
"@react-three/fiber@^8.15.5":
version "8.18.0"
resolved "https://registry.yarnpkg.com/@react-three/fiber/-/fiber-8.18.0.tgz#73bfa12c82dabf889b572f9dfeefb10485532dd1"
integrity sha512-FYZZqD0UUHUswKz3LQl2Z7H24AhD14XGTsIRw3SJaXUxyfVMi+1yiZGmqTcPt/CkPpdU7rrxqcyQ1zJE5DjvIQ==
dependencies:
"@babel/runtime" "^7.17.8"
"@types/react-reconciler" "^0.26.7"
"@types/webxr" "*"
base64-js "^1.5.1"
buffer "^6.0.3"
its-fine "^1.0.6"
react-reconciler "^0.27.0"
react-use-measure "^2.1.7"
scheduler "^0.21.0"
suspend-react "^0.1.3"
zustand "^3.7.1"
"@rolldown/pluginutils@1.0.0-beta.32":
version "1.0.0-beta.32"
resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.32.tgz#40a51db68a3c967183a9d58c5f5179adf6214cce"
integrity sha512-QReCdvxiUZAPkvp1xpAg62IeNzykOFA6syH2CnClif4YmALN1XKpB39XneL80008UbtMShthSVDKmrx05N1q/g==
"@rolldown/pluginutils@1.0.0-beta.34":
version "1.0.0-beta.34"
resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.34.tgz#4421645c676926faa4574940d72fa7ce0ec7d419"
integrity sha512-LyAREkZHP5pMom7c24meKmJCdhf2hEyvam2q0unr3or9ydwDL+DJ8chTF6Av/RFPb3rH8UFBdMzO5MxTZW97oA==
"@rollup/pluginutils@^4.2.1":
version "4.2.1"
@ -1948,7 +1904,7 @@
estree-walker "^2.0.1"
picomatch "^2.2.2"
"@rollup/pluginutils@^5.1.3":
"@rollup/pluginutils@^5.2.0":
version "5.2.0"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.2.0.tgz#eac25ca5b0bdda4ba735ddaca5fbf26bd435f602"
integrity sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==
@ -2792,11 +2748,6 @@
dependencies:
undici-types "~6.21.0"
"@types/offscreencanvas@^2019.6.4":
version "2019.7.3"
resolved "https://registry.yarnpkg.com/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz#90267db13f64d6e9ccb5ae3eac92786a7c77a516"
integrity sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==
"@types/parse-json@^4.0.0":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
@ -2810,25 +2761,6 @@
"@types/node" "*"
xmlbuilder ">=11.0.1"
"@types/react-reconciler@^0.26.7":
version "0.26.7"
resolved "https://registry.yarnpkg.com/@types/react-reconciler/-/react-reconciler-0.26.7.tgz#0c4643f30821ae057e401b0d9037e03e8e9b2a36"
integrity sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==
dependencies:
"@types/react" "*"
"@types/react-reconciler@^0.28.0":
version "0.28.9"
resolved "https://registry.yarnpkg.com/@types/react-reconciler/-/react-reconciler-0.28.9.tgz#d24b4864c384e770c83275b3fe73fba00269c83b"
integrity sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==
"@types/react@*":
version "19.1.11"
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.11.tgz#a64d8ec1769fc861d22f54e6e9f360ed67b54dc8"
integrity sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==
dependencies:
csstype "^3.0.2"
"@types/responselike@^1.0.0":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.3.tgz#cc29706f0a397cfe6df89debfe4bf5cea159db50"
@ -2856,11 +2788,6 @@
resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.11.tgz#d3d6b418978c8aa202d41e5bb3483227b6ecc1bb"
integrity sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==
"@types/webxr@*":
version "0.5.22"
resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.22.tgz#d8a14c12bbfaaa4a13de21ec2d4a8197b3e1b532"
integrity sha512-Vr6Stjv5jPRqH690f5I5GLjVk8GSsoQSYJ2FVd/3jJF7KaqfwPi3ehfBS96mlQ2kPCwZaX6U0rG2+NGHBKkA/A==
"@types/yauzl@^2.9.1":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999"
@ -2944,23 +2871,18 @@
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
"@vitejs/plugin-react@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-5.0.1.tgz#02c074f3fd9a82e4676c51e0fe9603a26ca614a3"
integrity sha512-DE4UNaBXwtVoDJ0ccBdLVjFTWL70NRuWNCxEieTI3lrq9ORB9aOCQEKstwDXBl87NvFdbqh/p7eINGyj0BthJA==
"@vitejs/plugin-react@^5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-5.0.2.tgz#3b5d73fc0e4370a0fafe27154d2c208e2bca8f71"
integrity sha512-tmyFgixPZCx2+e6VO9TNITWcCQl8+Nl/E8YbAyPVv85QCc7/A3JrdfG2A8gIzvVhWuzMOVrFW1aReaNxrI6tbw==
dependencies:
"@babel/core" "^7.28.3"
"@babel/plugin-transform-react-jsx-self" "^7.27.1"
"@babel/plugin-transform-react-jsx-source" "^7.27.1"
"@rolldown/pluginutils" "1.0.0-beta.32"
"@rolldown/pluginutils" "1.0.0-beta.34"
"@types/babel__core" "^7.20.5"
react-refresh "^0.17.0"
"@webgpu/glslang@^0.0.15":
version "0.0.15"
resolved "https://registry.yarnpkg.com/@webgpu/glslang/-/glslang-0.0.15.tgz#f5ccaf6015241e6175f4b90906b053f88483d1f2"
integrity sha512-niT+Prh3Aff8Uf1MVBVUsaNjFj9rJAKDXuoHIKiQbB+6IUP/3J3JIhBNyZ7lDhytvXxw6ppgnwKZdDJ08UMj4Q==
"@xmldom/xmldom@^0.8.8":
version "0.8.11"
resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.11.tgz#b79de2d67389734c57c52595f7a7305e30c2d608"
@ -3112,7 +3034,7 @@ antd-style@^3.7.1:
"@emotion/utils" "^1.2.1"
use-merge-value "^1.2.0"
antd@^5.27.0:
antd@^5.27.1:
version "5.27.1"
resolved "https://registry.yarnpkg.com/antd/-/antd-5.27.1.tgz#5378fc017cb4057ffefe2a670f20e54b924d897d"
integrity sha512-jGMSdBN7hAMvPV27B4RhzZfL6n6yu8yDbo7oXrlJasaOqB7bSDPcjdEy1kXy3JPsny/Qazb1ykzRI4EfcByAPQ==
@ -3515,14 +3437,6 @@ buffer@^5.1.0, buffer@^5.5.0:
base64-js "^1.3.1"
ieee754 "^1.1.13"
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
builder-util-runtime@9.3.1:
version "9.3.1"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.3.1.tgz#0daedde0f6d381f2a00a50a407b166fe7dca1a67"
@ -3676,6 +3590,14 @@ chalk-template@0.4.0:
dependencies:
chalk "^4.1.2"
chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.0.1.tgz#ca57d71e82bb534a296df63bbacc4a1c22b2a4b6"
@ -3692,14 +3614,6 @@ chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^5.0.1:
version "5.6.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.0.tgz#a1a8d294ea3526dbb77660f12649a08490e33ab8"
@ -3725,18 +3639,6 @@ character-reference-invalid@^2.0.0:
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9"
integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
chevrotain@^10.1.2:
version "10.5.0"
resolved "https://registry.yarnpkg.com/chevrotain/-/chevrotain-10.5.0.tgz#9c1dc62ef0753bb562dbe521b5f72d041bad624e"
integrity sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==
dependencies:
"@chevrotain/cst-dts-gen" "10.5.0"
"@chevrotain/gast" "10.5.0"
"@chevrotain/types" "10.5.0"
"@chevrotain/utils" "10.5.0"
lodash "4.17.21"
regexp-to-ast "0.5.0"
chownr@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
@ -3922,18 +3824,17 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
concurrently@^9.2.0:
version "9.2.0"
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-9.2.0.tgz#233e3892ceb0b5db9fd49e9c8c739737a7b638b5"
integrity sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==
concurrently@^9.2.1:
version "9.2.1"
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-9.2.1.tgz#248ea21b95754947be2dad9c3e4b60f18ca4e44f"
integrity sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==
dependencies:
chalk "^4.1.2"
lodash "^4.17.21"
rxjs "^7.8.1"
shell-quote "^1.8.1"
supports-color "^8.1.1"
tree-kill "^1.2.2"
yargs "^17.7.2"
chalk "4.1.2"
rxjs "7.8.2"
shell-quote "1.8.3"
supports-color "8.1.1"
tree-kill "1.2.2"
yargs "17.7.2"
config-file-ts@0.2.8-rc1:
version "0.2.8-rc1"
@ -4018,7 +3919,7 @@ cosmiconfig@^8.1.3:
parse-json "^5.2.0"
path-type "^4.0.0"
country-list@^2.3.0:
country-list@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/country-list/-/country-list-2.4.1.tgz#468b60a91004533133352a629de07a3a9d459899"
integrity sha512-KhVV/UfUV3dSNpsWIqHTQxLpYDKPKz1UwkRjadt+YbX2PRhyCEihEoS5XgB7J7AMXpkicvl+tRHvkNI5wbji/g==
@ -4333,11 +4234,16 @@ data-view-byte-offset@^1.0.1:
es-errors "^1.3.0"
is-data-view "^1.0.1"
dayjs@^1.11.11, dayjs@^1.11.13:
dayjs@^1.11.11:
version "1.11.13"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c"
integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
dayjs@^1.11.18:
version "1.11.18"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.18.tgz#835fa712aac52ab9dec8b1494098774ed7070a11"
integrity sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==
debug@2.6.9, debug@^2.2.0:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -4577,11 +4483,6 @@ dotenv@^17.2.1:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.1.tgz#6f32e10faf014883515538dc922a0fb8765d9b32"
integrity sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==
draco3d@^1.4.1:
version "1.5.7"
resolved "https://registry.yarnpkg.com/draco3d/-/draco3d-1.5.7.tgz#94f9bce293eb8920c159dc91a4ce9124a9e899e0"
integrity sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==
dunder-proto@^1.0.0, dunder-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
@ -4668,10 +4569,10 @@ electron-to-chromium@^1.5.204:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz#609c29502fd7257b4d721e3446f3ae391a0ca1b3"
integrity sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg==
electron@^37.2.6:
version "37.3.1"
resolved "https://registry.yarnpkg.com/electron/-/electron-37.3.1.tgz#92d0299593c4302dcdf3305af917c60dad69719f"
integrity sha512-7DhktRLqhe6OJh/Bo75bTI0puUYEmIwSzMinocgO63mx3MVjtIn2tYMzLmAleNIlud2htkjpsMG2zT4PiTCloA==
electron@^37.4.0:
version "37.4.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-37.4.0.tgz#08b2eff9d6250fac7b298f17f93946ecd586f38e"
integrity sha512-HhsSdWowE5ODOeWNc/323Ug2C52mq/TqNBG+4uMeOA3G2dMXNc/nfyi0RYu1rJEgiaJLEjtHveeZZaYRYFsFCQ==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^22.7.7"
@ -5150,7 +5051,7 @@ eslint@^8.41.0, eslint@^8.57.1:
strip-ansi "^6.0.1"
text-table "^0.2.0"
eslint@^9.33.0:
eslint@^9.34.0:
version "9.34.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.34.0.tgz#0ea1f2c1b5d1671db8f01aa6b8ce722302016f7b"
integrity sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==
@ -5382,11 +5283,6 @@ fecha@^4.2.1:
resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd"
integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==
fflate@^0.6.9:
version "0.6.10"
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.6.10.tgz#5f40f9659205936a2d18abf88b2e7781662b6d43"
integrity sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==
file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@ -6035,7 +5931,7 @@ iconv-lite@0.6, iconv-lite@0.6.3, iconv-lite@^0.6.2, iconv-lite@^0.6.3:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
ieee754@^1.1.13, ieee754@^1.2.1:
ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
@ -6431,13 +6327,6 @@ iterator.prototype@^1.1.4:
has-symbols "^1.1.0"
set-function-name "^2.0.2"
its-fine@^1.0.6:
version "1.2.5"
resolved "https://registry.yarnpkg.com/its-fine/-/its-fine-1.2.5.tgz#5466c287f86a0a73e772c8d8d515626c97195dc9"
integrity sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==
dependencies:
"@types/react-reconciler" "^0.28.0"
jackspeak@^3.1.2:
version "3.4.3"
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a"
@ -6570,11 +6459,6 @@ keyv@^4.0.0, keyv@^4.5.3, keyv@^4.5.4:
dependencies:
json-buffer "3.0.1"
ktx-parse@^0.4.5:
version "0.4.5"
resolved "https://registry.yarnpkg.com/ktx-parse/-/ktx-parse-0.4.5.tgz#79905e22281a9d3e602b2ff522df1ee7d1813aa6"
integrity sha512-MK3FOody4TXbFf8Yqv7EBbySw7aPvEcPX++Ipt6Sox+/YMFvR5xaTyhfNSk1AEmMy+RYIw81ctN4IMxCB8OAlg==
lazy-val@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d"
@ -6652,7 +6536,7 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash@4.17.21, lodash@^4.17.15, lodash@^4.17.21:
lodash@^4.17.15, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -6683,7 +6567,7 @@ longest-streak@^3.0.0:
resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4"
integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==
loose-envify@^1.1.0, loose-envify@^1.4.0:
loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -7464,11 +7348,6 @@ ml-matrix@^6.10.4:
is-any-array "^2.0.1"
ml-array-rescale "^1.3.7"
mmd-parser@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mmd-parser/-/mmd-parser-1.0.4.tgz#87cc05782cb5974ca854f0303fc5147bc9d690e7"
integrity sha512-Qi0VCU46t2IwfGv5KF0+D/t9cizcDug7qnNoy9Ggk7aucp0tssV8IwTMkBlDbm+VqAf3cdQHTCARKSsuS2MYFg==
moment@^2.30.1:
version "2.30.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
@ -7669,14 +7548,6 @@ onetime@^5.1.0, onetime@^5.1.2:
dependencies:
mimic-fn "^2.1.0"
opentype.js@^1.3.3:
version "1.3.4"
resolved "https://registry.yarnpkg.com/opentype.js/-/opentype.js-1.3.4.tgz#1c0e72e46288473cc4a4c6a2dc60fd7fe6020d77"
integrity sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==
dependencies:
string.prototype.codepointat "^0.2.1"
tiny-inflate "^1.0.3"
optionator@^0.9.3:
version "0.9.4"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
@ -7984,11 +7855,6 @@ postcss@^8.5.6:
picocolors "^1.1.1"
source-map-js "^1.2.1"
potpack@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14"
integrity sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@ -8518,13 +8384,12 @@ react-country-flag@^3.1.0:
resolved "https://registry.yarnpkg.com/react-country-flag/-/react-country-flag-3.1.0.tgz#f0c4c332934a77d3e894ba4800634f7a887e53d4"
integrity sha512-JWQFw1efdv9sTC+TGQvTKXQg1NKbDU2mBiAiRWcKM9F1sK+/zjhP2yGmm8YDddWyZdXVkR8Md47rPMJmo4YO5g==
react-dom@^18.2.0:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
react-dom@^19.1.1:
version "19.1.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.1.tgz#2daa9ff7f3ae384aeb30e76d5ee38c046dc89893"
integrity sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==
dependencies:
loose-envify "^1.1.0"
scheduler "^0.23.2"
scheduler "^0.26.0"
react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
@ -8553,14 +8418,6 @@ react-markdown@^10.1.0:
unist-util-visit "^5.0.0"
vfile "^6.0.0"
react-reconciler@^0.27.0:
version "0.27.0"
resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.27.0.tgz#360124fdf2d76447c7491ee5f0e04503ed9acf5b"
integrity sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==
dependencies:
loose-envify "^1.1.0"
scheduler "^0.21.0"
react-refresh@^0.17.0:
version "0.17.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.17.0.tgz#b7e579c3657f23d04eccbe4ad2e58a8ed51e7e53"
@ -8576,7 +8433,7 @@ react-responsive@^10.0.1:
prop-types "^15.6.1"
shallow-equal "^3.1.0"
react-router-dom@^7.8.0:
react-router-dom@^7.8.2:
version "7.8.2"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.8.2.tgz#25a8fc36588189baf3bbb5e360c8ffffbd2beabc"
integrity sha512-Z4VM5mKDipal2jQ385H6UBhiiEDlnJPx6jyWsTYoZQdl5TrjxEV2a9yl3Fi60NBJxYzOTGTTHXPi0pdizvTwow==
@ -8591,25 +8448,10 @@ react-router@7.8.2:
cookie "^1.0.1"
set-cookie-parser "^2.6.0"
react-stl-viewer@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/react-stl-viewer/-/react-stl-viewer-2.5.0.tgz#c6478b5926b8a3cd2bd366101483818d891f04c9"
integrity sha512-jlYId05N0P9rKVEdOfLO1bPsS9SYfMRxjnKvhks6T/c4HU0BxnNcZpRr4gfpJU0nFL6HHgmVKhKTh1LCHNcZuA==
dependencies:
"@react-three/fiber" "^8.15.5"
three-stdlib "2.17.2"
react-use-measure@^2.1.7:
version "2.1.7"
resolved "https://registry.yarnpkg.com/react-use-measure/-/react-use-measure-2.1.7.tgz#36b8a2e7fd2fa58109ab851b3addcb0aad66ad1d"
integrity sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==
react@^18.2.0:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
dependencies:
loose-envify "^1.1.0"
react@^19.1.1:
version "19.1.1"
resolved "https://registry.yarnpkg.com/react/-/react-19.1.1.tgz#06d9149ec5e083a67f9a1e39ce97b06a03b644af"
integrity sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==
read-binary-file-arch@^1.0.6:
version "1.0.6"
@ -8658,11 +8500,6 @@ reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
get-proto "^1.0.1"
which-builtin-type "^1.2.1"
regexp-to-ast@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz#56c73856bee5e1fef7f73a00f1473452ab712a24"
integrity sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==
regexp.prototype.flags@^1.5.3, regexp.prototype.flags@^1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19"
@ -8895,7 +8732,7 @@ rw@1:
resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==
rxjs@^7.8.1:
rxjs@7.8.2:
version "7.8.2"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b"
integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==
@ -8957,19 +8794,10 @@ sax@^1.2.4, sax@^1.4.1:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f"
integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==
scheduler@^0.21.0:
version "0.21.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.21.0.tgz#6fd2532ff5a6d877b6edb12f00d8ab7e8f308820"
integrity sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==
dependencies:
loose-envify "^1.1.0"
scheduler@^0.23.2:
version "0.23.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
dependencies:
loose-envify "^1.1.0"
scheduler@^0.26.0:
version "0.26.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.26.0.tgz#4ce8a8c2a2095f13ea11bf9a445be50c555d6337"
integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==
scroll-into-view-if-needed@^3.1.0:
version "3.1.0"
@ -9125,7 +8953,7 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shell-quote@^1.8.1:
shell-quote@1.8.3:
version "1.8.3"
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b"
integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==
@ -9402,11 +9230,6 @@ string-width@^5.0.1, string-width@^5.1.2:
emoji-regex "^9.2.2"
strip-ansi "^7.0.1"
string.prototype.codepointat@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz#004ad44c8afc727527b108cd462b4d971cd469bc"
integrity sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==
string.prototype.matchall@^4.0.12:
version "4.0.12"
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz#6c88740e49ad4956b1332a911e949583a275d4c0"
@ -9592,6 +9415,13 @@ sumchecker@^3.0.1:
dependencies:
debug "^4.1.0"
supports-color@8.1.1:
version "8.1.1"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
dependencies:
has-flag "^4.0.0"
supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@ -9604,23 +9434,11 @@ supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
supports-color@^8.1.1:
version "8.1.1"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
dependencies:
has-flag "^4.0.0"
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
suspend-react@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/suspend-react/-/suspend-react-0.1.3.tgz#a52f49d21cfae9a2fb70bd0c68413d3f9d90768e"
integrity sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==
svg-parser@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5"
@ -9696,23 +9514,6 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
three-stdlib@2.17.2:
version "2.17.2"
resolved "https://registry.yarnpkg.com/three-stdlib/-/three-stdlib-2.17.2.tgz#9c2a2914a16123853531612dac8870e6e242685b"
integrity sha512-7ZLCJJogtn1D1MlUi7q0iLUbrxj7K++YxjHIIz5AZ4wX4E137BgiiTmhH4XhAuvXGRk9ph3ZtoHTfJBXhqDX3w==
dependencies:
"@babel/runtime" "^7.16.7"
"@types/offscreencanvas" "^2019.6.4"
"@webgpu/glslang" "^0.0.15"
chevrotain "^10.1.2"
draco3d "^1.4.1"
fflate "^0.6.9"
ktx-parse "^0.4.5"
mmd-parser "^1.0.4"
opentype.js "^1.3.3"
potpack "^1.0.1"
zstddec "^0.0.2"
three@^0.159.0:
version "0.159.0"
resolved "https://registry.yarnpkg.com/three/-/three-0.159.0.tgz#6576b1210805b14f0765bac41fd0e4ec18e43b2e"
@ -9735,11 +9536,6 @@ tiny-async-pool@1.3.0:
dependencies:
semver "^5.5.0"
tiny-inflate@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4"
integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==
tinyglobby@^0.2.14:
version "0.2.14"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d"
@ -9777,7 +9573,7 @@ toidentifier@1.0.1:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
tree-kill@^1.2.2:
tree-kill@1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
@ -10133,12 +9929,12 @@ vite-plugin-svgo@^2.0.0:
dependencies:
svgo "3.3.2"
vite-plugin-svgr@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/vite-plugin-svgr/-/vite-plugin-svgr-4.3.0.tgz#742f16f11375996306c696ec323e4d23f6005075"
integrity sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==
vite-plugin-svgr@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/vite-plugin-svgr/-/vite-plugin-svgr-4.5.0.tgz#253e4c703d1f0b30935c285ca8621f4857952338"
integrity sha512-W+uoSpmVkSmNOGPSsDCWVW/DDAyv+9fap9AZXBvWiQqrboJ08j2vh0tFxTD/LjwqwAd3yYSVJgm54S/1GhbdnA==
dependencies:
"@rollup/pluginutils" "^5.1.3"
"@rollup/pluginutils" "^5.2.0"
"@svgr/core" "^8.1.0"
"@svgr/plugin-jsx" "^8.1.0"
@ -10335,7 +10131,7 @@ yargs-parser@^21.1.1:
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
yargs@^17.0.1, yargs@^17.6.2, yargs@^17.7.2:
yargs@17.7.2, yargs@^17.0.1, yargs@^17.6.2:
version "17.7.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
@ -10361,16 +10157,6 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zstddec@^0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/zstddec/-/zstddec-0.0.2.tgz#57e2f28dd1ff56b750e07d158a43f0611ad9eeb4"
integrity sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA==
zustand@^3.7.1:
version "3.7.2"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.7.2.tgz#7b44c4f4a5bfd7a8296a3957b13e1c346f42514d"
integrity sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==
zwitch@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"