diff --git a/src/renderer/src/components/PassageDetail/mobile/MobileWorkflowSteps.cy.tsx b/src/renderer/src/components/PassageDetail/mobile/MobileWorkflowSteps.cy.tsx index c638ff4e..bcd0eb5a 100644 --- a/src/renderer/src/components/PassageDetail/mobile/MobileWorkflowSteps.cy.tsx +++ b/src/renderer/src/components/PassageDetail/mobile/MobileWorkflowSteps.cy.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import { legacy_createStore as createStore, combineReducers } from 'redux'; import LocalizedStrings from 'react-localization'; @@ -11,24 +12,96 @@ import { PassageDetailContext, ICtxState, } from '../../../context/PassageDetailContext'; +import { UnsavedContext } from '../../../context/UnsavedContext'; import MobileWorkflowSteps from './MobileWorkflowSteps'; -const createMockMemory = (): Memory => - ({ +// Mock memory that resolves findRecord calls by a "type:id" lookup map +const createMockMemory = (records: Record = {}): Memory => { + const qb = { + findRecord: ({ type, id }: { type: string; id: string }) => ({ type, id }), + }; + return { cache: { - query: () => [], + query: (fn: any) => { + if (typeof fn !== 'function') return undefined; + const spec = fn(qb); + return spec?.type && spec?.id + ? records[`${spec.type}:${spec.id}`] + : undefined; + }, liveQuery: () => ({ subscribe: () => () => {}, query: () => [], }), }, update: () => {}, - }) as unknown as Memory; + } as unknown as Memory; +}; const mockCoordinator = { getSource: () => createMockMemory(), } as unknown as Coordinator; +const stepProgressionOrgRecord = { + type: 'organization', + id: 'test-org', + attributes: { + defaultParams: JSON.stringify({ WorkflowProgression: 'step' }), + }, +}; + +// Two passage records used in passage progression mode tests +const mockSectionPassageRecords = { + 'passage:p-1': { + id: 'p-1', + attributes: { sequencenum: 1, reference: '1:1', book: 'GEN' }, + }, + 'passage:p-2': { + id: 'p-2', + attributes: { sequencenum: 2, reference: '1:2', book: 'GEN' }, + }, +}; + +// Extended set with an earlier passage (sequencenum 0) to cover the "complete" colour state +const mockSectionPassageRecordsWithPrior = { + 'passage:p-0': { + id: 'p-0', + attributes: { sequencenum: 0, reference: '1:0', book: 'GEN' }, + }, + ...mockSectionPassageRecords, +}; + +const mockSectionWithThreePassages = { + id: 'section-1', + relationships: { + passages: { + data: [ + { type: 'passage', id: 'p-0' }, + { type: 'passage', id: 'p-1' }, + { type: 'passage', id: 'p-2' }, + ], + }, + }, +} as any; + +// Section whose passages relationship points to the records above +const mockSection = { + id: 'section-1', + relationships: { + passages: { + data: [ + { type: 'passage', id: 'p-1' }, + { type: 'passage', id: 'p-2' }, + ], + }, + }, +} as any; + +const mockCurrentPassage = { + id: 'p-1', + attributes: { sequencenum: 1, reference: '1:1', book: 'GEN' }, +} as any; + const createInitialState = ( overrides: Partial = {} ): GlobalState => ({ @@ -97,6 +170,24 @@ const mockStore = createStore( }) ); +const mockUnsavedState = { + checkSavedFn: (method: () => void) => method(), + t: {} as any, + handleSaveConfirmed: () => {}, + handleSaveRefused: () => {}, + toolChanged: () => {}, + startSave: () => {}, + startClear: () => {}, + saveCompleted: () => {}, + clearCompleted: () => {}, + waitForSave: async () => {}, + anySaving: () => false as const, + saveRequested: () => false as const, + clearRequested: () => false as const, + isChanged: () => false as const, + toolsChanged: {}, +}; + const createPassageDetailState = ( overrides: Partial = {} ): ICtxState => @@ -110,6 +201,9 @@ const createPassageDetailState = ( commentRecording: false, stepComplete: () => false, setCurrentStep: cy.stub(), + passage: mockCurrentPassage, + section: {} as any, + prjId: 'proj-1', ...overrides, }) as ICtxState; @@ -120,6 +214,10 @@ const mountMobileWorkflowSteps = ({ remoteBusy = false, recording = false, commentRecording = false, + isStepProgression = false, + section, + passage, + extraMemoryRecords = {}, }: { currentstep?: string; workflow?: ICtxState['workflow']; @@ -127,6 +225,10 @@ const mountMobileWorkflowSteps = ({ remoteBusy?: boolean; recording?: boolean; commentRecording?: boolean; + isStepProgression?: boolean; + section?: any; + passage?: any; + extraMemoryRecords?: Record; } = {}) => { const setCurrentStep = cy.stub().as('setCurrentStep'); const ctxOverrides: Partial = { @@ -135,106 +237,290 @@ const mountMobileWorkflowSteps = ({ commentRecording, setCurrentStep, stepComplete: (id: string) => completedStepIds.includes(id), + ...(section !== undefined ? { section } : {}), + ...(passage !== undefined ? { passage } : {}), }; - if (workflow) { - ctxOverrides.workflow = workflow; - } + if (workflow) ctxOverrides.workflow = workflow; const ctxState = createPassageDetailState(ctxOverrides); - const initialState = createInitialState({ remoteBusy }); + + const mem = createMockMemory({ + ...(isStepProgression + ? { 'organization:test-org': stepProgressionOrgRecord } + : {}), + ...extraMemoryRecords, + }); + const initialState = createInitialState({ remoteBusy, memory: mem }); cy.mount( - - - - - - - - - + + + + + + + + + + + + + ); }; describe('MobileWorkflowSteps', () => { - it('renders workflow steps and current label', () => { - mountMobileWorkflowSteps(); + describe('step progression mode', () => { + it('renders workflow step parallelograms and current step label', () => { + mountMobileWorkflowSteps({ isStepProgression: true }); + + cy.get('[data-cy="workflow-step"]').should('have.length', 2); + cy.get('[data-cy="workflow-step-label"]').should( + 'contain.text', + 'Record' + ); + }); - cy.get('[data-cy="workflow-step"]').should('have.length', 2); - cy.get('[data-cy="workflow-step-label"]').should('contain.text', 'Record'); - }); + it('shows current, complete, and incomplete step colors', () => { + mountMobileWorkflowSteps({ + isStepProgression: true, + workflow: [ + { id: 'step-1', label: 'Record' }, + { id: 'step-2', label: 'Review' }, + { id: 'step-3', label: 'Publish' }, + ], + completedStepIds: ['step-2'], + }); + + cy.get('[data-cy="workflow-step"]') + .eq(0) + .should('have.css', 'background-color', 'rgb(97, 97, 97)'); + cy.get('[data-cy="workflow-step"]') + .eq(1) + .should('have.css', 'background-color', 'rgb(189, 189, 189)'); + cy.get('[data-cy="workflow-step"]') + .eq(2) + .should('have.css', 'background-color', 'rgb(238, 238, 238)'); + }); - it('shows current, complete, and incomplete step colors', () => { - mountMobileWorkflowSteps({ - workflow: [ - { id: 'step-1', label: 'Record' }, - { id: 'step-2', label: 'Review' }, - { id: 'step-3', label: 'Publish' }, - ], - completedStepIds: ['step-2'], - }); - - cy.get('[data-cy="workflow-step"]') - .eq(0) - .should('have.css', 'background-color', 'rgb(97, 97, 97)'); - cy.get('[data-cy="workflow-step"]') - .eq(1) - .should('have.css', 'background-color', 'rgb(189, 189, 189)'); - cy.get('[data-cy="workflow-step"]') - .eq(2) - .should('have.css', 'background-color', 'rgb(238, 238, 238)'); - }); + it('shows a tip button in the step label area that opens a dialog', () => { + mountMobileWorkflowSteps({ isStepProgression: true }); - it('shows a tip dialog for the current step', () => { - mountMobileWorkflowSteps(); + cy.get( + '[data-cy="workflow-step-label"] [data-cy="workflow-step-tip"]' + ).click(); - cy.get('[data-cy="workflow-step-tip"]').click(); + cy.get('[role="dialog"]').should('be.visible'); + cy.contains('Record tip').should('be.visible'); + cy.contains('Close').click(); + cy.get('[role="dialog"]').should('not.exist'); + }); - cy.get('[role="dialog"]').should('be.visible'); - cy.contains('Record tip').should('be.visible'); - cy.contains('Close').click(); - cy.get('[role="dialog"]').should('not.exist'); - }); + it('selects a different step when a parallelogram is clicked', () => { + mountMobileWorkflowSteps({ isStepProgression: true }); - it('selects a different step when clicked', () => { - mountMobileWorkflowSteps(); + cy.get('[data-cy="workflow-step"]').eq(1).click(); - cy.get('[data-cy="workflow-step"]').eq(1).click(); + cy.get('@setCurrentStep').should('have.been.calledWith', 'step-2'); + }); - cy.get('@setCurrentStep').should('have.been.calledWith', 'step-2'); - }); + it('does not re-select the current step', () => { + mountMobileWorkflowSteps({ isStepProgression: true }); - it('does not re-select the current step', () => { - mountMobileWorkflowSteps(); + cy.get('[data-cy="workflow-step"]').eq(0).click(); - cy.get('[data-cy="workflow-step"]').eq(0).click(); + cy.get('@setCurrentStep').should('not.have.been.called'); + }); - cy.get('@setCurrentStep').should('not.have.been.called'); - }); + it('blocks step selection and shows wait message when remote is busy', () => { + mountMobileWorkflowSteps({ isStepProgression: true, remoteBusy: true }); - it('blocks selection and shows wait message when remote busy', () => { - mountMobileWorkflowSteps({ remoteBusy: true }); + cy.get('[data-cy="workflow-step"]').eq(1).click(); - cy.get('[data-cy="workflow-step"]').eq(1).click(); + cy.get('@setCurrentStep').should('not.have.been.called'); + cy.contains('Please wait').should('be.visible'); + }); - cy.get('@setCurrentStep').should('not.have.been.called'); - cy.contains('Please wait').should('be.visible'); - }); + it('blocks step selection while recording', () => { + mountMobileWorkflowSteps({ isStepProgression: true, recording: true }); + + cy.get('[data-cy="workflow-step"]').eq(1).click(); + + cy.get('@setCurrentStep').should('not.have.been.called'); + }); - it('blocks selection while recording', () => { - mountMobileWorkflowSteps({ recording: true }); + it('blocks step selection while comment recording', () => { + mountMobileWorkflowSteps({ + isStepProgression: true, + commentRecording: true, + }); - cy.get('[data-cy="workflow-step"]').eq(1).click(); + cy.get('[data-cy="workflow-step"]').eq(1).click(); - cy.get('@setCurrentStep').should('not.have.been.called'); + cy.get('@setCurrentStep').should('not.have.been.called'); + }); + + it('blocks passage dropdown while recording', () => { + mountMobileWorkflowSteps({ isStepProgression: true, recording: true }); + + cy.get('[data-cy="passage-dropdown"]').click(); + + cy.get('[role="menu"]').should('not.exist'); + }); + + it('blocks passage dropdown and shows wait message when remote is busy', () => { + mountMobileWorkflowSteps({ isStepProgression: true, remoteBusy: true }); + + cy.get('[data-cy="passage-dropdown"]').click(); + + cy.get('[role="menu"]').should('not.exist'); + cy.contains('Please wait').should('be.visible'); + }); + + it('dropdown shows current passage book and reference', () => { + mountMobileWorkflowSteps({ isStepProgression: true }); + + cy.get('[data-cy="passage-dropdown"]').should('contain.text', 'GEN 1:1'); + }); + + it('dropdown opens section passages as menu items', () => { + mountMobileWorkflowSteps({ + isStepProgression: true, + section: mockSection, + extraMemoryRecords: mockSectionPassageRecords, + }); + + cy.get('[data-cy="passage-dropdown"]').click(); + + cy.get('[role="menu"]').should('be.visible'); + cy.get('[role="menuitem"]').should('have.length', 2); + cy.get('[role="menuitem"]').eq(0).should('contain.text', 'GEN 1:1'); + }); + + it('renders the step label as plain text when the current step has no tip', () => { + mountMobileWorkflowSteps({ + isStepProgression: true, + currentstep: 'step-2', + }); + + cy.get('[data-cy="workflow-step-label"]').should( + 'contain.text', + 'Review' + ); + cy.get('[data-cy="workflow-step-tip"]').should('not.exist'); + }); }); - it('blocks selection while comment recording', () => { - mountMobileWorkflowSteps({ commentRecording: true }); + describe('passage progression mode', () => { + it('renders passage parallelograms for each section passage', () => { + mountMobileWorkflowSteps({ + section: mockSection, + extraMemoryRecords: mockSectionPassageRecords, + }); + + cy.get('[data-cy="passage-step"]').should('have.length', 2); + }); + + it('shows a tip button left of the dropdown that opens a dialog', () => { + mountMobileWorkflowSteps(); + + cy.get('[data-cy="workflow-step-tip"]').click(); + + cy.get('[role="dialog"]').should('be.visible'); + cy.contains('Record tip').should('be.visible'); + cy.contains('Close').click(); + cy.get('[role="dialog"]').should('not.exist'); + }); + + it('dropdown shows the current workflow step label', () => { + mountMobileWorkflowSteps(); + + cy.get('[data-cy="passage-dropdown"]').should('contain.text', 'Record'); + }); + + it('dropdown opens a menu with workflow step options', () => { + mountMobileWorkflowSteps(); - cy.get('[data-cy="workflow-step"]').eq(1).click(); + cy.get('[data-cy="passage-dropdown"]').click(); - cy.get('@setCurrentStep').should('not.have.been.called'); + cy.get('[role="menu"]').should('be.visible'); + cy.get('[role="menuitem"]').should('have.length', 2); + }); + + it('blocks passage click while recording', () => { + mountMobileWorkflowSteps({ + section: mockSection, + extraMemoryRecords: mockSectionPassageRecords, + recording: true, + }); + + cy.get('[data-cy="passage-step"]').eq(1).click(); + + cy.get('@setCurrentStep').should('not.have.been.called'); + }); + + it('blocks passage click and shows wait message when remote is busy', () => { + mountMobileWorkflowSteps({ + section: mockSection, + extraMemoryRecords: mockSectionPassageRecords, + remoteBusy: true, + }); + + cy.get('[data-cy="passage-step"]').eq(1).click(); + + cy.contains('Please wait').should('be.visible'); + }); + + it('shows current, complete, and incomplete passage step colors', () => { + mountMobileWorkflowSteps({ + section: mockSectionWithThreePassages, + extraMemoryRecords: mockSectionPassageRecordsWithPrior, + }); + + // p-0 sequencenum 0 < current sequencenum 1 → complete + cy.get('[data-cy="passage-step"]') + .eq(0) + .should('have.css', 'background-color', 'rgb(189, 189, 189)'); + // p-1 is current passage + cy.get('[data-cy="passage-step"]') + .eq(1) + .should('have.css', 'background-color', 'rgb(97, 97, 97)'); + // p-2 sequencenum 2 > current sequencenum 1 → incomplete + cy.get('[data-cy="passage-step"]') + .eq(2) + .should('have.css', 'background-color', 'rgb(238, 238, 238)'); + }); + + it('step label shows current passage book and reference', () => { + mountMobileWorkflowSteps(); + + cy.get('[data-cy="workflow-step-label"]').should( + 'contain.text', + 'GEN 1:1' + ); + }); + + it('blocks dropdown while comment recording', () => { + mountMobileWorkflowSteps({ commentRecording: true }); + + cy.get('[data-cy="passage-dropdown"]').click(); + + cy.get('[role="menu"]').should('not.exist'); + }); + + it('blocks passage click while comment recording', () => { + mountMobileWorkflowSteps({ + section: mockSection, + extraMemoryRecords: mockSectionPassageRecords, + commentRecording: true, + }); + + cy.get('[data-cy="passage-step"]').eq(1).click(); + + cy.get('@setCurrentStep').should('not.have.been.called'); + }); }); }); diff --git a/src/renderer/src/components/PassageDetail/mobile/MobileWorkflowSteps.tsx b/src/renderer/src/components/PassageDetail/mobile/MobileWorkflowSteps.tsx index d4fac589..2d9adb9d 100644 --- a/src/renderer/src/components/PassageDetail/mobile/MobileWorkflowSteps.tsx +++ b/src/renderer/src/components/PassageDetail/mobile/MobileWorkflowSteps.tsx @@ -6,19 +6,32 @@ import { DialogActions, DialogContent, DialogTitle, + IconButton, + Menu, + MenuItem, Typography, useTheme, } from '@mui/material'; import InfoIcon from '@mui/icons-material/Info'; +import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import usePassageDetailContext from '../../../context/usePassageDetailContext'; -import { useGetGlobal } from '../../../context/useGlobal'; +import { useGetGlobal, useGlobal } from '../../../context/useGlobal'; import { useSnackBar } from '../../../hoc/SnackBar'; import { sharedSelector, workflowStepsSelector } from '../../../selector'; import { shallowEqual, useSelector } from 'react-redux'; import { useWfLabel } from '../../../utils/useWfLabel'; -import { useEffect, useMemo, useRef, useState } from 'react'; -import { IWorkflowStepsStrings } from '../../../model'; +import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { IWorkflowStepsStrings, PassageD } from '../../../model'; import { toCamel } from '../../../utils/toCamel'; +import { related } from '../../../crud/related'; +import { findRecord } from '../../../crud/tryFindRecord'; +import { rememberCurrentPassage } from '../../../utils'; +import { usePassageNavigate } from '../usePassageNavigate'; +import { isPublishingTitle } from '../../../control/passageTypeFromRef'; +import { + orgDefaultWorkflowProgression, + useOrgDefaults, +} from '../../../crud/useOrgDefaults'; export default function MobileWorkflowSteps() { const { @@ -28,17 +41,38 @@ export default function MobileWorkflowSteps() { recording, commentRecording, stepComplete, + passage, + section, + prjId, } = usePassageDetailContext(); + const [memory] = useGlobal('memory'); + const passageNavigate = usePassageNavigate(() => {}, setCurrentStep); const getGlobal = useGetGlobal(); const { showMessage } = useSnackBar(); const ts = useSelector(sharedSelector, shallowEqual); const theme = useTheme(); const getWfLabel = useWfLabel(); + const { getOrgDefault } = useOrgDefaults(); + const isStepProgression = + getOrgDefault(orgDefaultWorkflowProgression) === 'step'; const t: IWorkflowStepsStrings = useSelector( workflowStepsSelector, shallowEqual ); + const sectionPassages = useMemo(() => { + const passRecIds = related(section, 'passages'); + if (!Array.isArray(passRecIds)) return []; + return passRecIds + .map((p) => findRecord(memory, 'passage', p.id) as PassageD) + .filter( + (p) => Boolean(p) && !isPublishingTitle(p?.attributes?.reference, false) + ) + .sort((a, b) => a.attributes.sequencenum - b.attributes.sequencenum); + }, [section, memory]); + const [tipOpen, setTipOpen] = useState(false); + const [passageMenuAnchor, setPassageMenuAnchor] = + useState(null); const handleSelect = (id: string) => () => { if (getGlobal('remoteBusy')) { @@ -50,105 +84,264 @@ export default function MobileWorkflowSteps() { } }; - const currentLabel = useMemo(() => { - return workflow.find((w) => w.id === currentstep)?.label ?? ''; - }, [currentstep, workflow]); + const currentLabel = useMemo( + () => workflow.find((w) => w.id === currentstep)?.label ?? '', + [currentstep, workflow] + ); const currentTip = useMemo(() => { - if (!currentLabel) { - return ''; - } + if (!currentLabel) return ''; const tipKey = toCamel(currentLabel + 'Tip'); return Object.prototype.hasOwnProperty.call(t, tipKey) ? t.getString(tipKey) : ''; }, [currentLabel, t]); + const dropdownRef = useRef(null); + const [dropdownWidth, setDropdownWidth] = useState(0); + + useLayoutEffect(() => { + const el = dropdownRef.current; + if (!el) return; + const update = () => + setDropdownWidth( + el.offsetWidth + parseFloat(window.getComputedStyle(el).marginRight) + ); + update(); + const ro = new ResizeObserver(update); + ro.observe(el); + return () => ro.disconnect(); + }, []); + const didMountRef = useRef(false); const stepRefs = useRef(new Map()); useEffect(() => { - const el = stepRefs.current.get(currentstep); - if (!el) { - return; - } + const currentId = isStepProgression ? currentstep : (passage?.id ?? ''); + const el = stepRefs.current.get(currentId); + if (!el) return; el.scrollIntoView({ behavior: didMountRef.current ? 'smooth' : 'auto', block: 'nearest', inline: 'center', }); didMountRef.current = true; - }, [currentstep, workflow.length]); + }, [ + currentstep, + passage?.id, + workflow.length, + sectionPassages.length, + isStepProgression, + ]); return ( - + - {workflow.map((step) => { - const isCurrent = step.id === currentstep; - return ( - { - if (el) { - stepRefs.current.set(step.id, el); - } else { - stepRefs.current.delete(step.id); - } - }} - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - } - }} - sx={{ - width: 36, - height: 14, - backgroundColor: isCurrent - ? theme.palette.grey[700] - : stepComplete(step.id) - ? theme.palette.grey[400] - : theme.palette.grey[200], - transform: 'skewX(-20deg)', - borderRadius: '2px', - cursor: - recording || commentRecording ? 'not-allowed' : 'pointer', - flexShrink: 0, - }} - /> - ); - })} - - {currentLabel && ( - - {currentTip ? ( - setTipOpen(true)} data-cy="workflow-step-tip" - sx={{ borderRadius: 1 }} aria-label={currentTip} + color="info" > - {getWfLabel(currentLabel) + '\u00A0'} - - + + + )} + + setPassageMenuAnchor(null)} + > + {isStepProgression + ? sectionPassages.map((p) => ( + { + const remId = p.keys?.remoteId ?? p.id; + rememberCurrentPassage(memory, remId); + passageNavigate(`/detail/${prjId}/${remId}`); + setPassageMenuAnchor(null); + }} + > + {[p.attributes.book, p.attributes.reference] + .filter(Boolean) + .join(' ')} + + )) + : workflow.map((step) => ( + { + handleSelect(step.id)(); + setPassageMenuAnchor(null); + }} + > + {getWfLabel(step.label)} + + ))} + + + {/* Workflow step parallelograms */} + + + {isStepProgression + ? workflow.map((step) => { + const isCurrent = step.id === currentstep; + return ( + { + if (el) stepRefs.current.set(step.id, el); + else stepRefs.current.delete(step.id); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + } + }} + sx={{ + flex: '0 0 80px', + height: 30, + mr: -0.25, // Overlap adjacent parallelograms so their edges meet cleanly + backgroundColor: isCurrent + ? theme.palette.grey[700] + : stepComplete(step.id) + ? theme.palette.grey[400] + : theme.palette.grey[200], + clipPath: 'polygon(10% 0%, 100% 0%, 90% 100%, 0% 100%)', + cursor: + recording || commentRecording + ? 'not-allowed' + : 'pointer', + }} + /> + ); + }) + : sectionPassages.map((p) => { + const isCurrent = p.id === passage?.id; + const isComplete = + (p.attributes.sequencenum ?? 0) < + (passage?.attributes?.sequencenum ?? 0); + return ( + { + if (recording || commentRecording) return; + if (getGlobal('remoteBusy')) { + showMessage(ts.wait); + return; + } + const remId = p.keys?.remoteId ?? p.id; + rememberCurrentPassage(memory, remId); + passageNavigate(`/detail/${prjId}/${remId}`); + }} + tabIndex={0} + ref={(el) => { + if (el) stepRefs.current.set(p.id, el); + else stepRefs.current.delete(p.id); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + } + }} + sx={{ + flex: '0 0 80px', + height: 30, + mr: -0.25, // Overlap adjacent parallelograms so their edges meet cleanly + backgroundColor: isCurrent + ? theme.palette.grey[700] + : isComplete + ? theme.palette.grey[400] + : theme.palette.grey[200], + clipPath: 'polygon(10% 0%, 100% 0%, 90% 100%, 0% 100%)', + cursor: + recording || commentRecording + ? 'not-allowed' + : 'pointer', + }} + /> + ); + })} + {/* Spacer to mirror the dropdown width so mx:auto centers the parallelograms */} + + + + + {(isStepProgression ? currentLabel : passage?.id) && ( + + {isStepProgression ? ( + currentTip ? ( + setTipOpen(true)} + data-cy="workflow-step-tip" + sx={{ + borderRadius: 1, + fontWeight: 'inherit', + fontSize: 'inherit', + }} + aria-label={currentTip} + > + {getWfLabel(currentLabel) + '\u00A0'} + + + ) : ( + getWfLabel(currentLabel) + ) ) : ( - getWfLabel(currentLabel) + [passage?.attributes?.book, passage?.attributes?.reference] + .filter(Boolean) + .join(' ') )} )}