Download src/components/slide-renderer/Editor/Canvas/EditableElement.tsx from muthuk1/OpenMAIC-React: direct link, hf CLI and curl.
- Browser
- Download file 9.4 kB
-
https://huggingface.co/muthuk1/OpenMAIC-React/resolve/main/src/components/slide-renderer/Editor/Canvas/EditableElement.tsx
- Command line
-
hf download hf://muthuk1/OpenMAIC-React/src/components/slide-renderer/Editor/Canvas/EditableElement.tsx
-
curl -L -o EditableElement.tsx https://huggingface.co/muthuk1/OpenMAIC-React/resolve/main/src/components/slide-renderer/Editor/Canvas/EditableElement.tsx
9.4 kB
| import { useMemo } from 'react'; | |
| import { ElementTypes, type PPTElement } from '@/lib/types/slides'; | |
| import { ImageElement } from '../../components/element/ImageElement'; | |
| import { TextElement } from '../../components/element/TextElement'; | |
| import { LineElement } from '../../components/element/LineElement'; | |
| import { ShapeElement } from '../../components/element/ShapeElement'; | |
| import { ChartElement } from '../../components/element/ChartElement'; | |
| import { LatexElement } from '../../components/element/LatexElement'; | |
| import { TableElement } from '../../components/element/TableElement'; | |
| import { VideoElement } from '../../components/element/VideoElement'; | |
| import { | |
| ContextMenu, | |
| ContextMenuContent, | |
| ContextMenuItem, | |
| ContextMenuSeparator, | |
| ContextMenuShortcut, | |
| ContextMenuSub, | |
| ContextMenuSubContent, | |
| ContextMenuSubTrigger, | |
| ContextMenuTrigger, | |
| } from '@/components/ui/context-menu'; | |
| import { ElementOrderCommands, ElementAlignCommands } from '@/lib/types/edit'; | |
| import { useCanvasOperations } from '@/lib/hooks/use-canvas-operations'; | |
| export interface ContextmenuItem { | |
| text?: string; | |
| subText?: string; | |
| divider?: boolean; | |
| disable?: boolean; | |
| hide?: boolean; | |
| children?: ContextmenuItem[]; | |
| handler?: () => void; | |
| } | |
| interface EditableElementProps { | |
| readonly elementInfo: PPTElement; | |
| readonly elementIndex: number; | |
| readonly isMultiSelect: boolean; | |
| readonly selectElement: ( | |
| e: React.MouseEvent | React.TouchEvent, | |
| element: PPTElement, | |
| canMove?: boolean, | |
| ) => void; | |
| readonly openLinkDialog: () => void; | |
| } | |
| export function EditableElement({ | |
| elementInfo, | |
| elementIndex, | |
| isMultiSelect, | |
| selectElement, | |
| openLinkDialog, | |
| }: EditableElementProps) { | |
| const CurrentElementComponent = useMemo(() => { | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any -- element components have varying prop signatures | |
| const elementTypeMap: Record<string, any> = { | |
| [ElementTypes.IMAGE]: ImageElement, | |
| [ElementTypes.TEXT]: TextElement, | |
| [ElementTypes.SHAPE]: ShapeElement, | |
| [ElementTypes.LINE]: LineElement, | |
| [ElementTypes.CHART]: ChartElement, | |
| [ElementTypes.LATEX]: LatexElement, | |
| [ElementTypes.TABLE]: TableElement, | |
| [ElementTypes.VIDEO]: VideoElement, | |
| // TODO: Add other element types | |
| // [ElementTypes.AUDIO]: AudioElement, | |
| }; | |
| return elementTypeMap[elementInfo.type] || null; | |
| }, [elementInfo.type]); | |
| const { | |
| copyElement, | |
| pasteElement, | |
| cutElement, | |
| deleteElement, | |
| lockElement, | |
| unlockElement, | |
| selectAllElements, | |
| alignElementToCanvas, | |
| orderElement, | |
| combineElements, | |
| uncombineElements, | |
| } = useCanvasOperations(); | |
| const contextmenus = (): ContextmenuItem[] => { | |
| if (elementInfo.lock) { | |
| return [ | |
| { | |
| text: '解锁', | |
| handler: () => unlockElement(elementInfo), | |
| }, | |
| ]; | |
| } | |
| return [ | |
| { | |
| text: '剪切', | |
| subText: 'Ctrl + X', | |
| handler: cutElement, | |
| }, | |
| { | |
| text: '复制', | |
| subText: 'Ctrl + C', | |
| handler: copyElement, | |
| }, | |
| { | |
| text: '粘贴', | |
| subText: 'Ctrl + V', | |
| handler: pasteElement, | |
| }, | |
| { divider: true }, | |
| { | |
| text: '水平居中', | |
| handler: () => alignElementToCanvas(ElementAlignCommands.HORIZONTAL), | |
| children: [ | |
| { | |
| text: '水平垂直居中', | |
| handler: () => alignElementToCanvas(ElementAlignCommands.CENTER), | |
| }, | |
| { | |
| text: '水平居中', | |
| handler: () => alignElementToCanvas(ElementAlignCommands.HORIZONTAL), | |
| }, | |
| { | |
| text: '左对齐', | |
| handler: () => alignElementToCanvas(ElementAlignCommands.LEFT), | |
| }, | |
| { | |
| text: '右对齐', | |
| handler: () => alignElementToCanvas(ElementAlignCommands.RIGHT), | |
| }, | |
| ], | |
| }, | |
| { | |
| text: '垂直居中', | |
| handler: () => alignElementToCanvas(ElementAlignCommands.VERTICAL), | |
| children: [ | |
| { | |
| text: '水平垂直居中', | |
| handler: () => alignElementToCanvas(ElementAlignCommands.CENTER), | |
| }, | |
| { | |
| text: '垂直居中', | |
| handler: () => alignElementToCanvas(ElementAlignCommands.VERTICAL), | |
| }, | |
| { | |
| text: '顶部对齐', | |
| handler: () => alignElementToCanvas(ElementAlignCommands.TOP), | |
| }, | |
| { | |
| text: '底部对齐', | |
| handler: () => alignElementToCanvas(ElementAlignCommands.BOTTOM), | |
| }, | |
| ], | |
| }, | |
| { divider: true }, | |
| { | |
| text: '置于顶层', | |
| disable: isMultiSelect && !elementInfo.groupId, | |
| handler: () => orderElement(elementInfo, ElementOrderCommands.TOP), | |
| children: [ | |
| { | |
| text: '置于顶层', | |
| handler: () => orderElement(elementInfo, ElementOrderCommands.TOP), | |
| }, | |
| { | |
| text: '上移一层', | |
| handler: () => orderElement(elementInfo, ElementOrderCommands.UP), | |
| }, | |
| ], | |
| }, | |
| { | |
| text: '置于底层', | |
| disable: isMultiSelect && !elementInfo.groupId, | |
| handler: () => orderElement(elementInfo, ElementOrderCommands.BOTTOM), | |
| children: [ | |
| { | |
| text: '置于底层', | |
| handler: () => orderElement(elementInfo, ElementOrderCommands.BOTTOM), | |
| }, | |
| { | |
| text: '下移一层', | |
| handler: () => orderElement(elementInfo, ElementOrderCommands.DOWN), | |
| }, | |
| ], | |
| }, | |
| { divider: true }, | |
| { | |
| text: '设置链接', | |
| handler: openLinkDialog, | |
| disable: true, | |
| }, | |
| { | |
| text: elementInfo.groupId ? '取消组合' : '组合', | |
| subText: 'Ctrl + G', | |
| handler: elementInfo.groupId ? uncombineElements : combineElements, | |
| hide: !isMultiSelect, | |
| }, | |
| { | |
| text: '全选', | |
| subText: 'Ctrl + A', | |
| handler: selectAllElements, | |
| }, | |
| { | |
| text: '锁定', | |
| subText: 'Ctrl + L', | |
| handler: lockElement, | |
| }, | |
| { | |
| text: '删除', | |
| subText: 'Delete', | |
| handler: deleteElement, | |
| }, | |
| ]; | |
| }; | |
| if (!CurrentElementComponent) { | |
| return ( | |
| <div | |
| id={`editable-element-${elementInfo.id}`} | |
| className="editable-element absolute" | |
| style={{ | |
| zIndex: elementIndex, | |
| left: elementInfo.left + 'px', | |
| top: elementInfo.top + 'px', | |
| width: elementInfo.width + 'px', | |
| }} | |
| > | |
| <div className="p-2 bg-gray-100 border border-gray-300 text-xs text-gray-500"> | |
| {elementInfo.type} element (not implemented) | |
| </div> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div | |
| id={`editable-element-${elementInfo.id}`} | |
| className="editable-element absolute" | |
| style={{ | |
| zIndex: elementIndex, | |
| }} | |
| > | |
| <ContextMenu> | |
| <ContextMenuTrigger> | |
| <CurrentElementComponent elementInfo={elementInfo} selectElement={selectElement} /> | |
| </ContextMenuTrigger> | |
| <ContextMenuContent> | |
| {contextmenus().map((item, index) => { | |
| if (item.divider) { | |
| return <ContextMenuSeparator key={index} />; | |
| } | |
| // If has children, use submenu component | |
| if (item.children && item.children.length > 0) { | |
| return ( | |
| <ContextMenuSub key={index}> | |
| <ContextMenuSubTrigger disabled={item.disable} hidden={item.hide}> | |
| {item.text} | |
| {item.subText && <ContextMenuShortcut>{item.subText}</ContextMenuShortcut>} | |
| </ContextMenuSubTrigger> | |
| <ContextMenuSubContent> | |
| {item.children.map((child, childIndex) => | |
| child.divider ? ( | |
| <ContextMenuSeparator key={childIndex} /> | |
| ) : ( | |
| <ContextMenuItem | |
| key={childIndex} | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| child.handler?.(); | |
| }} | |
| disabled={child.disable} | |
| hidden={child.hide} | |
| > | |
| {child.text} | |
| {child.subText && ( | |
| <ContextMenuShortcut>{child.subText}</ContextMenuShortcut> | |
| )} | |
| </ContextMenuItem> | |
| ), | |
| )} | |
| </ContextMenuSubContent> | |
| </ContextMenuSub> | |
| ); | |
| } | |
| // Regular menu item | |
| return ( | |
| <ContextMenuItem | |
| key={index} | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| item.handler?.(); | |
| }} | |
| disabled={item.disable} | |
| hidden={item.hide} | |
| > | |
| {item.text} | |
| {item.subText && <ContextMenuShortcut>{item.subText}</ContextMenuShortcut>} | |
| </ContextMenuItem> | |
| ); | |
| })} | |
| </ContextMenuContent> | |
| </ContextMenu> | |
| </div> | |
| ); | |
| } | |