diff --git a/assets/stylesheets/App.css b/assets/stylesheets/App.css index a01191ad..1ec414f2 100644 --- a/assets/stylesheets/App.css +++ b/assets/stylesheets/App.css @@ -3371,7 +3371,7 @@ body.objectKanbanColumnResizing * { } .dashboard-tabs-label { - max-width: 200px; + max-width: 280px; margin-top: -2px; } diff --git a/src/components/Dashboard/Management/DocumentTemplates/DocumentTemplateDesign.jsx b/src/components/Dashboard/Management/DocumentTemplates/DocumentTemplateDesign.jsx index 8239ea34..59b68c1e 100644 --- a/src/components/Dashboard/Management/DocumentTemplates/DocumentTemplateDesign.jsx +++ b/src/components/Dashboard/Management/DocumentTemplates/DocumentTemplateDesign.jsx @@ -19,6 +19,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte import ScrollBox from '../../common/ScrollBox.jsx' import TemplateEditor from '../../common/TemplateEditor.jsx' +import { useObjectNavigationTabPage } from '../../hooks/useObjectNavigationTabPage' const log = loglevel.getLogger('DocumentTemplateDesign') log.setLevel(config.logLevel) @@ -30,6 +31,11 @@ const DocumentTemplateDesign = () => { const documentTemplateId = new URLSearchParams(location.search).get( 'documentTemplateId' ) + useObjectNavigationTabPage({ + modelName: 'documentTemplate', + pageName: 'design', + objectId: documentTemplateId + }) const [collapseState, updateCollapseState] = useCollapseState( 'DocumentTemplateDesign', { diff --git a/src/components/Dashboard/Management/EmailTemplates/EmailTemplateDesign.jsx b/src/components/Dashboard/Management/EmailTemplates/EmailTemplateDesign.jsx index c8f314ae..093d8621 100644 --- a/src/components/Dashboard/Management/EmailTemplates/EmailTemplateDesign.jsx +++ b/src/components/Dashboard/Management/EmailTemplates/EmailTemplateDesign.jsx @@ -16,6 +16,7 @@ import ObjectActions from '../../common/ObjectActions' import TemplateEditor from '../../common/TemplateEditor' import ScrollBox from '../../common/ScrollBox' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' +import { useObjectNavigationTabPage } from '../../hooks/useObjectNavigationTabPage' const EmailTemplateDesign = () => { const location = useLocation() @@ -24,6 +25,11 @@ const EmailTemplateDesign = () => { const emailTemplateId = new URLSearchParams(location.search).get( 'emailTemplateId' ) + useObjectNavigationTabPage({ + modelName: 'emailTemplate', + pageName: 'design', + objectId: emailTemplateId + }) const [collapseState, updateCollapseState] = useCollapseState( 'EmailTemplateDesign', { preview: true, editor: true, notes: true } diff --git a/src/components/Dashboard/Management/Notes/NoteInfo.jsx b/src/components/Dashboard/Management/Notes/NoteInfo.jsx index 52dce1da..02be9c10 100644 --- a/src/components/Dashboard/Management/Notes/NoteInfo.jsx +++ b/src/components/Dashboard/Management/Notes/NoteInfo.jsx @@ -21,7 +21,7 @@ import ActivityIndicator from '../../common/ActivityIndicator.jsx' import ActionHandler from '../../common/ActionHandler.jsx' import ObjectActions from '../../common/ObjectActions.jsx' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' -import { useNavigationTabPage } from '../../context/NavigationTabsContext' +import { useObjectNavigationTabPage } from '../../hooks/useObjectNavigationTabPage' import ObjectTable from '../../common/ObjectTable.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx' @@ -33,11 +33,15 @@ const log = loglevel.getLogger('NoteInfo') log.setLevel(config.logLevel) const NoteInfo = () => { - useNavigationTabPage({ title: 'Info - Note', modelName: 'note' }) const location = useLocation() const objectFormRef = useRef(null) const actionHandlerRef = useRef(null) const noteId = new URLSearchParams(location.search).get('noteId') + useObjectNavigationTabPage({ + modelName: 'note', + pageName: 'info', + objectId: noteId + }) const [collapseState, updateCollapseState] = useCollapseState('NoteInfo', { info: true, content: true, diff --git a/src/components/Dashboard/Production/GCodeFiles/GCodeFilePreview.jsx b/src/components/Dashboard/Production/GCodeFiles/GCodeFilePreview.jsx index 9e2e718e..1a872f42 100644 --- a/src/components/Dashboard/Production/GCodeFiles/GCodeFilePreview.jsx +++ b/src/components/Dashboard/Production/GCodeFiles/GCodeFilePreview.jsx @@ -21,6 +21,7 @@ import MissingPlaceholder from '../../common/MissingPlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' +import { useObjectNavigationTabPage } from '../../hooks/useObjectNavigationTabPage' const log = loglevel.getLogger('GCodeFilePreview') log.setLevel(config.logLevel) @@ -29,6 +30,11 @@ const GCodeFilePreview = () => { const location = useLocation() const objectFormRef = useRef(null) const gcodeFileId = new URLSearchParams(location.search).get('gcodeFileId') + useObjectNavigationTabPage({ + modelName: 'gcodeFile', + pageName: 'preview', + objectId: gcodeFileId + }) const [collapseState, updateCollapseState] = useCollapseState( 'GCodeFilePreview', { diff --git a/src/components/Dashboard/Production/Printers/ControlPrinter.jsx b/src/components/Dashboard/Production/Printers/ControlPrinter.jsx index 7e52b8b3..1bdc6fef 100644 --- a/src/components/Dashboard/Production/Printers/ControlPrinter.jsx +++ b/src/components/Dashboard/Production/Printers/ControlPrinter.jsx @@ -37,6 +37,7 @@ import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButt import PrinterControlButtons from '../../common/PrinterControlButtons.jsx' import PrinterQueue from '../../common/PrinterQueue.jsx' import ListIcon from '../../../Icons/ListIcon.jsx' +import { useObjectNavigationTabPage } from '../../hooks/useObjectNavigationTabPage' const log = loglevel.getLogger('ControlPrinter') log.setLevel(config.logLevel) @@ -47,6 +48,11 @@ const ControlPrinter = ({ slicerIntegration = false }) => { const actionHandlerRef = useRef(null) const isMobile = useMediaQuery({ maxWidth: 768 }) const printerId = new URLSearchParams(location.search).get('printerId') + useObjectNavigationTabPage({ + modelName: 'printer', + pageName: 'control', + objectId: printerId + }) const [collapseState, updateCollapseState] = useCollapseState( 'ControlPrinter', { diff --git a/src/components/Dashboard/common/DashboardTabs.jsx b/src/components/Dashboard/common/DashboardTabs.jsx index 0911f174..d8164c1b 100644 --- a/src/components/Dashboard/common/DashboardTabs.jsx +++ b/src/components/Dashboard/common/DashboardTabs.jsx @@ -16,7 +16,7 @@ const { Text } = Typography const DashboardTabLabel = ({ tab, onClose }) => ( - + {tab.title || 'Farm Control'} { const model = getModelByName(modelName) @@ -16,9 +17,8 @@ const ModelPage = ({ modelName, pageName }) => { const { setCurrentObject, setCurrentObjectType } = useActions() const { userProfile } = useContext(AuthContext) const objectId = getObjectIdFromSearch(modelName, location.search) - const pageTitle = `${pageName.charAt(0).toUpperCase()}${pageName.slice(1)} - ${model?.label || modelName}` const isTabActive = useIsNavigationTabActive() - useNavigationTabPage({ title: pageTitle, modelName }) + useObjectNavigationTabPage({ modelName, pageName, objectId }) useEffect(() => { if (!isTabActive) return undefined diff --git a/src/components/Dashboard/context/ObjectListViewContext.jsx b/src/components/Dashboard/context/ObjectListViewContext.jsx index 043c60ad..e48e21ea 100644 --- a/src/components/Dashboard/context/ObjectListViewContext.jsx +++ b/src/components/Dashboard/context/ObjectListViewContext.jsx @@ -131,7 +131,9 @@ export const ObjectListViewProvider = ({ children, objectType, tableRef }) => { const { getViewFromUrl, persistView, setObjectListView } = useTableState() const listModel = getModelByName(objectType) useNavigationTabPage({ - title: listModel?.label ? `List - ${listModel.label}` : 'List', + title: listModel?.labelPlural + ? `List - ${listModel.labelPlural}` + : 'List', modelName: objectType }) diff --git a/src/components/Dashboard/hooks/useObjectNavigationTabPage.jsx b/src/components/Dashboard/hooks/useObjectNavigationTabPage.jsx new file mode 100644 index 00000000..51805331 --- /dev/null +++ b/src/components/Dashboard/hooks/useObjectNavigationTabPage.jsx @@ -0,0 +1,82 @@ +import { useContext, useEffect, useRef, useState } from 'react' +import { getModelByName } from '../../../database/ObjectModels' +import { ApiServerContext } from '../context/ApiServerContext' +import { AuthContext } from '../context/AuthContext' +import { useNavigationTabPage } from '../context/NavigationTabsContext' + +const formatPageLabel = (pageName) => + pageName + ? `${pageName.charAt(0).toUpperCase()}${pageName.slice(1)}` + : 'Page' + +const getObjectDisplayName = (objectData, model) => { + if (objectData?.name) return objectData.name + if (objectData?._reference && model?.prefix) { + return `${model.prefix}:${objectData._reference}` + } + return null +} + +export const formatObjectPageTabTitle = ({ + pageName, + model, + objectName +} = {}) => { + const pageLabel = formatPageLabel(pageName) + const collectionLabel = model?.labelPlural + if (objectName && collectionLabel) { + return `${pageLabel} (${objectName}) - ${collectionLabel}` + } + if (objectName) return `${pageLabel} (${objectName})` + if (collectionLabel) return `${pageLabel} - ${collectionLabel}` + return pageLabel +} + +export const useObjectNavigationTabPage = ({ + modelName, + pageName, + objectId +} = {}) => { + const model = getModelByName(modelName) + const { fetchSpotlightData, subscribeToObjectUpdates, connected } = + useContext(ApiServerContext) + const { token } = useContext(AuthContext) + const [objectName, setObjectName] = useState(null) + const fetchSpotlightDataRef = useRef(fetchSpotlightData) + fetchSpotlightDataRef.current = fetchSpotlightData + + useEffect(() => { + if (!objectId || !model?.prefix || token == null) { + setObjectName(null) + return undefined + } + + let cancelled = false + const query = `${model.prefix}:${objectId}` + + void fetchSpotlightDataRef.current(query).then((result) => { + if (cancelled) return + if (result && typeof result === 'object' && !Array.isArray(result)) { + setObjectName(getObjectDisplayName(result, model)) + } + }) + + return () => { + cancelled = true + } + }, [model, objectId, token]) + + useEffect(() => { + if (!objectId || !modelName || !connected) return undefined + return subscribeToObjectUpdates(objectId, modelName, (value) => { + setObjectName((current) => getObjectDisplayName(value, model) || current) + }) + }, [connected, model, modelName, objectId, subscribeToObjectUpdates]) + + useNavigationTabPage({ + title: formatObjectPageTabTitle({ pageName, model, objectName }), + modelName + }) +} + +export default useObjectNavigationTabPage