import { Editor, TLBookmarkShape, TLEmbedShape, useEditor, useValue } from '@bigbluebutton/editor' import React, { useMemo } from 'react' import { getEmbedInfo } from '../../utils/embeds/embeds' import { TLUiMenuSchema, compactMenuItems, menuCustom, menuGroup, menuItem, menuSubmenu, showMenuPaste, useAllowGroup, useAllowUngroup, } from './menuHelpers' import { useActions } from './useActions' import { useBreakpoint } from './useBreakpoint' import { useCanRedo } from './useCanRedo' import { useCanUndo } from './useCanUndo' import { useHasLinkShapeSelected } from './useHasLinkShapeSelected' import { useShowAutoSizeToggle } from './useShowAutoSizeToggle' /** @public */ export type TLUiMenuSchemaContextType = TLUiMenuSchema /** @internal */ export const TLUiMenuSchemaContext = React.createContext({} as TLUiMenuSchemaContextType) /** @public */ export type TLUiMenuSchemaProviderProps = { overrides?: ( editor: Editor, schema: TLUiMenuSchemaContextType, helpers: { actions: ReturnType noneSelected: boolean oneSelected: boolean twoSelected: boolean threeSelected: boolean } ) => TLUiMenuSchemaContextType children: any } /** @internal */ export function TLUiMenuSchemaProvider({ overrides, children }: TLUiMenuSchemaProviderProps) { const editor = useEditor() const actions = useActions() const breakpoint = useBreakpoint() const isMobile = breakpoint < 5 const isDarkMode = useValue('isDarkMode', () => editor.user.getIsDarkMode(), [editor]) const animationSpeed = useValue('animationSpeed', () => editor.user.getAnimationSpeed(), [editor]) const edgeScrollSpeed = useValue('edgeScrollSpeed', () => editor.user.getEdgeScrollSpeed(), [ editor, ]) const isGridMode = useValue('isGridMode', () => editor.getInstanceState().isGridMode, [editor]) const isSnapMode = useValue('isSnapMode', () => editor.user.getIsSnapMode(), [editor]) const isToolLock = useValue('isToolLock', () => editor.getInstanceState().isToolLocked, [editor]) const isFocusMode = useValue('isFocusMode', () => editor.getInstanceState().isFocusMode, [editor]) const isDebugMode = useValue('isDebugMode', () => editor.getInstanceState().isDebugMode, [editor]) const exportBackground = useValue( 'exportBackground', () => editor.getInstanceState().exportBackground, [editor] ) const emptyPage = useValue('emptyPage', () => editor.getCurrentPageShapeIds().size === 0, [ editor, ]) const selectedCount = useValue('selectedCount', () => editor.getSelectedShapeIds().length, [ editor, ]) const noneSelected = selectedCount === 0 const oneSelected = selectedCount > 0 const twoSelected = selectedCount > 1 const threeSelected = selectedCount > 2 const hasClipboardWrite = Boolean(window.navigator.clipboard?.write) const showEditLink = useHasLinkShapeSelected() const showAutoSizeToggle = useShowAutoSizeToggle() const allowGroup = useAllowGroup() const allowUngroup = useAllowUngroup() const canUndo = useCanUndo() const canRedo = useCanRedo() const isZoomedTo100 = useValue('isZoomedTo100', () => editor.getZoomLevel() === 1, [editor]) const oneEmbedSelected = useValue( 'oneEmbedSelected', () => { const onlySelectedShape = editor.getOnlySelectedShape() if (!onlySelectedShape) return false return !!( editor.isShapeOfType(onlySelectedShape, 'embed') && onlySelectedShape.props.url && !editor.isShapeOrAncestorLocked(onlySelectedShape) ) }, [] ) const oneEmbeddableBookmarkSelected = useValue( 'oneEmbeddableBookmarkSelected', () => { const onlySelectedShape = editor.getOnlySelectedShape() if (!onlySelectedShape) return false return !!( editor.isShapeOfType(onlySelectedShape, 'bookmark') && onlySelectedShape.props.url && getEmbedInfo(onlySelectedShape.props.url) && !editor.isShapeOrAncestorLocked(onlySelectedShape) ) }, [] ) const menuSchema = useMemo(() => { const menuSchema: TLUiMenuSchema = compactMenuItems([ menuGroup( 'menu', menuSubmenu( 'file', 'menu.file', menuGroup('print', menuItem(actions['print'], { disabled: emptyPage })) ), menuSubmenu( 'edit', 'menu.edit', menuGroup( 'undo-actions', menuItem(actions['undo'], { disabled: !canUndo }), menuItem(actions['redo'], { disabled: !canRedo }) ), menuGroup( 'clipboard-actions', menuItem(actions['cut'], { disabled: noneSelected }), menuItem(actions['copy'], { disabled: noneSelected }), menuItem(actions['paste'], { disabled: !showMenuPaste }) ), menuGroup( 'conversions', menuSubmenu( 'copy-as', 'menu.copy-as', menuGroup( 'copy-as-group', menuItem(actions['copy-as-svg'], { disabled: emptyPage }), menuItem(actions['copy-as-png'], { disabled: emptyPage || !hasClipboardWrite }), menuItem(actions['copy-as-json'], { disabled: emptyPage }) ), menuGroup( 'export-bg', menuItem(actions['toggle-transparent'], { checked: !exportBackground }) ) ), menuSubmenu( 'export-as', 'menu.export-as', menuGroup( 'export-as-group', menuItem(actions['export-as-svg'], { disabled: emptyPage }), menuItem(actions['export-as-png'], { disabled: emptyPage }), menuItem(actions['export-as-json'], { disabled: emptyPage }) ), menuGroup( 'export-bg', menuItem(actions['toggle-transparent'], { checked: !exportBackground }) ) ) ), menuGroup( 'set-selection-group', menuItem(actions['select-all'], { disabled: emptyPage }), menuItem(actions['select-none'], { disabled: !oneSelected }) ), menuGroup( 'selection', showAutoSizeToggle && menuItem(actions['toggle-auto-size']), showEditLink && menuItem(actions['edit-link']), menuItem(actions['duplicate'], { disabled: !oneSelected }), allowGroup && menuItem(actions['group']), allowUngroup && menuItem(actions['ungroup']), menuItem(actions['unlock-all'], { disabled: emptyPage }) ), menuGroup('delete-group', menuItem(actions['delete'], { disabled: !oneSelected })), menuGroup( 'embeds', oneEmbedSelected && menuItem(actions['open-embed-link']), oneEmbedSelected && menuItem(actions['convert-to-bookmark']), oneEmbeddableBookmarkSelected && menuItem(actions['convert-to-embed']) ) ), menuSubmenu( 'view', 'menu.view', menuGroup( 'view-actions', menuItem(actions['zoom-in']), menuItem(actions['zoom-out']), menuItem(actions['zoom-to-100'], { disabled: isZoomedTo100 }), menuItem(actions['zoom-to-fit'], { disabled: emptyPage }), menuItem(actions['zoom-to-selection'], { disabled: emptyPage || !oneSelected }) ) ) ), menuGroup('extras', menuItem(actions['insert-embed']), menuItem(actions['insert-media'])), menuGroup( 'preferences', menuSubmenu( 'preferences', 'menu.preferences', menuGroup( 'preferences-actions', menuItem(actions['toggle-snap-mode'], { checked: isSnapMode }), menuItem(actions['toggle-tool-lock'], { checked: isToolLock }), menuItem(actions['toggle-grid'], { checked: isGridMode }), menuItem(actions['toggle-dark-mode'], { checked: isDarkMode }), menuItem(actions['toggle-focus-mode'], { checked: isFocusMode }), menuItem(actions['toggle-edge-scrolling'], { checked: edgeScrollSpeed === 1 }), menuItem(actions['toggle-reduce-motion'], { checked: animationSpeed === 0 }), menuItem(actions['toggle-debug-mode'], { checked: isDebugMode }) ) ), isMobile && menuCustom('LANGUAGE_MENU', { readonlyOk: true }) ), ]) if (overrides) { return overrides(editor, menuSchema, { actions, noneSelected, oneSelected, twoSelected, threeSelected, }) } return menuSchema }, [ editor, overrides, actions, oneSelected, twoSelected, threeSelected, emptyPage, isMobile, allowGroup, allowUngroup, showEditLink, hasClipboardWrite, showAutoSizeToggle, noneSelected, canUndo, canRedo, animationSpeed, isDarkMode, isGridMode, isSnapMode, isToolLock, isFocusMode, exportBackground, isDebugMode, edgeScrollSpeed, isZoomedTo100, oneEmbeddableBookmarkSelected, oneEmbedSelected, ]) return ( {children} ) } /** @public */ export function useMenuSchema(): TLUiMenuSchema { const ctx = React.useContext(TLUiMenuSchemaContext) if (!ctx) { throw new Error('useMenuSchema must be used inside of a TLUiMenuSchemaProvider.') } return ctx }