Fix NetworkMachineIndicator and machines dynamically showing in CommandBar (#4311)
This commit is contained in:
@ -23,6 +23,7 @@ export function LowerRightControls({
|
||||
}) {
|
||||
const location = useLocation()
|
||||
const filePath = useAbsoluteFilePath()
|
||||
|
||||
const linkOverrideClassName =
|
||||
'!text-chalkboard-70 hover:!text-chalkboard-80 dark:!text-chalkboard-40 dark:hover:!text-chalkboard-30'
|
||||
|
||||
|
123
src/components/MachineManagerProvider.tsx
Normal file
123
src/components/MachineManagerProvider.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import { createContext, useEffect, useState } from 'react'
|
||||
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { CommandsContext } from 'components/CommandBar/CommandBarProvider'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { components } from 'lib/machine-api'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { toSync } from 'lib/utils'
|
||||
|
||||
export type MachinesListing = Array<
|
||||
components['schemas']['MachineInfoResponse']
|
||||
>
|
||||
|
||||
export interface MachineManager {
|
||||
machines: MachinesListing
|
||||
machineApiIp: string | null
|
||||
currentMachine: components['schemas']['MachineInfoResponse'] | null
|
||||
noMachinesReason: () => string | undefined
|
||||
setCurrentMachine: (
|
||||
m: components['schemas']['MachineInfoResponse'] | null
|
||||
) => void
|
||||
}
|
||||
|
||||
export const MachineManagerContext = createContext<MachineManager>({
|
||||
machines: [],
|
||||
machineApiIp: null,
|
||||
currentMachine: null,
|
||||
setCurrentMachine: (
|
||||
_: components['schemas']['MachineInfoResponse'] | null
|
||||
) => {},
|
||||
noMachinesReason: () => undefined,
|
||||
})
|
||||
|
||||
export const MachineManagerProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) => {
|
||||
const [machines, setMachines] = useState<MachinesListing>([])
|
||||
const [machineApiIp, setMachineApiIp] = useState<string | null>(null)
|
||||
const [currentMachine, setCurrentMachine] = useState<
|
||||
components['schemas']['MachineInfoResponse'] | null
|
||||
>(null)
|
||||
|
||||
const commandBarActor = CommandsContext.useActorRef()
|
||||
|
||||
// Get the reason message for why there are no machines.
|
||||
const noMachinesReason = (): string | undefined => {
|
||||
if (machines.length > 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (machineApiIp === null) {
|
||||
return 'Machine API server was not discovered'
|
||||
}
|
||||
|
||||
return 'Machine API server was discovered, but no machines are available'
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDesktop()) return
|
||||
|
||||
const update = async () => {
|
||||
const _machineApiIp = await window.electron.getMachineApiIp()
|
||||
if (_machineApiIp === null) return
|
||||
|
||||
setMachineApiIp(_machineApiIp)
|
||||
|
||||
const _machines = await window.electron.listMachines(_machineApiIp)
|
||||
setMachines(_machines)
|
||||
}
|
||||
|
||||
// Start a background job to update the machines every ten seconds.
|
||||
// If MDNS is already watching, this timeout will wait until it's done to trigger the
|
||||
// finding again.
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
const timeoutLoop = () => {
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(
|
||||
toSync(async () => {
|
||||
await update()
|
||||
timeoutLoop()
|
||||
}, reportRejection),
|
||||
1000
|
||||
)
|
||||
}
|
||||
timeoutLoop()
|
||||
update().catch(reportRejection)
|
||||
}, [])
|
||||
|
||||
// Update engineCommandManager's copy of this data.
|
||||
useEffect(() => {
|
||||
const machineManagerNext = {
|
||||
machines,
|
||||
machineApiIp,
|
||||
currentMachine,
|
||||
noMachinesReason,
|
||||
setCurrentMachine,
|
||||
}
|
||||
|
||||
engineCommandManager.machineManager = machineManagerNext
|
||||
|
||||
commandBarActor.send({
|
||||
type: 'Set machine manager',
|
||||
data: machineManagerNext,
|
||||
})
|
||||
}, [machines, machineApiIp, currentMachine])
|
||||
|
||||
return (
|
||||
<MachineManagerContext.Provider
|
||||
value={{
|
||||
machines,
|
||||
machineApiIp,
|
||||
currentMachine,
|
||||
setCurrentMachine,
|
||||
noMachinesReason,
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
{children}{' '}
|
||||
</MachineManagerContext.Provider>
|
||||
)
|
||||
}
|
@ -1,5 +1,11 @@
|
||||
import { useMachine } from '@xstate/react'
|
||||
import React, { createContext, useEffect, useMemo, useRef } from 'react'
|
||||
import React, {
|
||||
createContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useContext,
|
||||
} from 'react'
|
||||
import {
|
||||
Actor,
|
||||
AnyStateMachine,
|
||||
@ -28,7 +34,7 @@ import {
|
||||
editorManager,
|
||||
sceneEntitiesManager,
|
||||
} from 'lib/singletons'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
||||
import {
|
||||
@ -140,6 +146,8 @@ export const ModelingMachineProvider = ({
|
||||
// >
|
||||
// )
|
||||
|
||||
const machineManager = useContext(MachineManagerContext)
|
||||
|
||||
const [modelingState, modelingSend, modelingActor] = useMachine(
|
||||
modelingMachine.provide({
|
||||
actions: {
|
||||
@ -408,7 +416,7 @@ export const ModelingMachineProvider = ({
|
||||
return {}
|
||||
}
|
||||
),
|
||||
Make: ({ event }) => {
|
||||
Make: ({ context, event }) => {
|
||||
if (event.type !== 'Make') return
|
||||
// Check if we already have an export intent.
|
||||
if (engineCommandManager.exportInfo) {
|
||||
@ -422,7 +430,21 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
|
||||
// Set the current machine.
|
||||
machineManager.currentMachine = event.data.machine
|
||||
// Due to our use of singeton pattern, we need to do this to reliably
|
||||
// update this object across React and non-React boundary.
|
||||
// We need to do this eagerly because of the exportToEngine call below.
|
||||
if (engineCommandManager.machineManager === null) {
|
||||
console.warn(
|
||||
"engineCommandManager.machineManager is null. It shouldn't be at this point. Aborting operation."
|
||||
)
|
||||
return
|
||||
} else {
|
||||
engineCommandManager.machineManager.currentMachine =
|
||||
event.data.machine
|
||||
}
|
||||
|
||||
// Update the rest of the UI that needs to know the current machine
|
||||
context.machineManager.setCurrentMachine(event.data.machine)
|
||||
|
||||
const format: Models['OutputFormat_type'] = {
|
||||
type: 'stl',
|
||||
@ -995,6 +1017,7 @@ export const ModelingMachineProvider = ({
|
||||
...modelingMachineDefaultContext.store,
|
||||
...persistedContext,
|
||||
},
|
||||
machineManager,
|
||||
},
|
||||
// devTools: true,
|
||||
}
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { Resizable } from 're-resizable'
|
||||
import { MouseEventHandler, useCallback, useEffect, useMemo } from 'react'
|
||||
import {
|
||||
MouseEventHandler,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useContext,
|
||||
} from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { SidebarAction, SidebarType, sidebarPanes } from './ModelingPanes'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
@ -13,7 +19,7 @@ import { CustomIconName } from 'components/CustomIcon'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||
|
||||
interface ModelingSidebarProps {
|
||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||
@ -29,6 +35,7 @@ function getPlatformString(): 'web' | 'desktop' {
|
||||
}
|
||||
|
||||
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
const machineManager = useContext(MachineManagerContext)
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const kclContext = useKclContext()
|
||||
const { settings } = useSettingsAuthContext()
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { useContext } from 'react'
|
||||
import Tooltip from './Tooltip'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { components } from 'lib/machine-api'
|
||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
|
||||
export const NetworkMachineIndicator = ({
|
||||
@ -9,9 +11,12 @@ export const NetworkMachineIndicator = ({
|
||||
}: {
|
||||
className?: string
|
||||
}) => {
|
||||
const machineCount = machineManager.machineCount()
|
||||
const reason = machineManager.noMachinesReason()
|
||||
const machines = machineManager.machines
|
||||
const {
|
||||
noMachinesReason,
|
||||
machines,
|
||||
machines: { length: machineCount },
|
||||
} = useContext(MachineManagerContext)
|
||||
const reason = noMachinesReason()
|
||||
|
||||
return isDesktop() ? (
|
||||
<Popover className="relative">
|
||||
@ -47,34 +52,36 @@ export const NetworkMachineIndicator = ({
|
||||
</div>
|
||||
{machineCount > 0 && (
|
||||
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
||||
{machines.map((machine) => {
|
||||
return (
|
||||
<li key={machine.id} className={'px-2 py-4 gap-1 last:mb-0 '}>
|
||||
<p className="">{machine.id.toUpperCase()}</p>
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
{machine.make_model.model}
|
||||
</p>
|
||||
{machine.extra &&
|
||||
machine.extra.type === 'bambu' &&
|
||||
machine.extra.nozzle_diameter && (
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
Nozzle Diameter: {machine.extra.nozzle_diameter}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
{`Status: ${machine.state.state
|
||||
.charAt(0)
|
||||
.toUpperCase()}${machine.state.state.slice(1)}`}
|
||||
{machine.state.state === 'failed' && machine.state.message
|
||||
? ` (${machine.state.message})`
|
||||
: ''}
|
||||
{machine.state.state === 'running' && machine.progress
|
||||
? ` (${Math.round(machine.progress)}%)`
|
||||
: ''}
|
||||
</p>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
{machines.map(
|
||||
(machine: components['schemas']['MachineInfoResponse']) => {
|
||||
return (
|
||||
<li key={machine.id} className={'px-2 py-4 gap-1 last:mb-0 '}>
|
||||
<p className="">{machine.id.toUpperCase()}</p>
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
{machine.make_model.model}
|
||||
</p>
|
||||
{machine.extra &&
|
||||
machine.extra.type === 'bambu' &&
|
||||
machine.extra.nozzle_diameter && (
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
Nozzle Diameter: {machine.extra.nozzle_diameter}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
{`Status: ${machine.state.state
|
||||
.charAt(0)
|
||||
.toUpperCase()}${machine.state.state.slice(1)}`}
|
||||
{machine.state.state === 'failed' && machine.state.message
|
||||
? ` (${machine.state.message})`
|
||||
: ''}
|
||||
{machine.state.state === 'running' && machine.progress
|
||||
? ` (${Math.round(machine.progress)}%)`
|
||||
: ''}
|
||||
</p>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</Popover.Panel>
|
||||
|
@ -4,14 +4,14 @@ import { type IndexLoaderData } from 'lib/types'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import { isDesktop } from '../lib/isDesktop'
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Fragment, useMemo } from 'react'
|
||||
import { Fragment, useMemo, useContext } from 'react'
|
||||
import { Logo } from './Logo'
|
||||
import { APP_NAME } from 'lib/constants'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { useLspContext } from './LspProvider'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
import Tooltip from './Tooltip'
|
||||
@ -96,6 +96,8 @@ function ProjectMenuPopover({
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const filePath = useAbsoluteFilePath()
|
||||
const machineManager = useContext(MachineManagerContext)
|
||||
|
||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||
const { onProjectClose } = useLspContext()
|
||||
const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
|
||||
@ -106,7 +108,7 @@ function ProjectMenuPopover({
|
||||
(c) => c.name === obj.name && c.groupId === obj.groupId
|
||||
)
|
||||
)
|
||||
const machineCount = machineManager.machineCount()
|
||||
const machineCount = machineManager.machines.length
|
||||
|
||||
// We filter this memoized list so that no orphan "break" elements are rendered.
|
||||
const projectMenuItems = useMemo<(ActionButtonProps | 'break')[]>(
|
||||
|
Reference in New Issue
Block a user