/** * Copyright 2023-present DreamNum Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import type { DocumentDataModel } from '@univerjs/core'; import type { IValueOption } from '@univerjs/ui'; import type { ReactNode } from 'react'; import type { LocaleKey } from '../../locale/types'; import { BooleanNumber, ICommandService, IUniverInstanceService, LocaleService, PositionedObjectLayoutType, UniverInstanceType, } from '@univerjs/core'; import { borderClassName, clsx, Dropdown, DropdownMenu, Separator, Tooltip } from '@univerjs/design'; import { AutofillDoubleIcon, ChartIcon, MoreDownIcon, TextWrapShapeIcon, } from '@univerjs/icons'; import { IconManager, useDependency } from '@univerjs/ui'; import { useState } from 'react'; export interface IImagePopupMenuItem { label: string; index: number; commandId: string; commandParams?: object; disable: boolean; type?: 'button' | 'select'; value?: string; options?: Array<{ icon?: string; label: IValueOption['label']; value: string }>; commandParamsFactory?: (value: string) => object; hideOnClick?: boolean; icon?: string; } export interface IImagePopupMenuExtraProps { menuItems: IImagePopupMenuItem[]; variant?: 'doc-floating-toolbar' | 'doc-chart-floating-toolbar'; unitId?: string; subUnitId?: string; drawingId?: string; } export interface IImagePopupMenuProps { popup: { extraProps?: IImagePopupMenuExtraProps; }; } export function ImagePopupMenu(props: IImagePopupMenuProps) { const { popup } = props; const menuItems = popup?.extraProps?.menuItems; const commandService = useDependency(ICommandService); const localeService = useDependency(LocaleService); const [visible, setVisible] = useState(false); const [isHovered, setIsHovered] = useState(false); if (!menuItems) return null; if ( popup.extraProps?.variant === 'doc-floating-toolbar' && popup.extraProps.unitId && popup.extraProps.subUnitId && popup.extraProps.drawingId ) { return ( ); } if (popup.extraProps?.variant === 'doc-chart-floating-toolbar') { return ; } const handleMouseEnter = () => { setIsHovered(true); }; const handleMouseLeave = () => { setIsHovered(false); }; const onVisibleChange = (visible: boolean) => { setVisible(visible); }; const handleClick = (item: IImagePopupMenuItem) => { commandService.executeCommand(item.commandId, item.commandParams); setVisible(false); }; const showMore = visible || isHovered; return (
({ type: 'item', children: localeService.t(item.label as LocaleKey), disabled: item.disable, onSelect: () => handleClick(item), }))} open={visible} onOpenChange={onVisibleChange} >
{showMore && }
); }; const UPDATE_DOC_DRAWING_WRAPPING_STYLE_COMMAND_ID = 'doc.command.update-doc-drawing-wrapping-style'; enum TextWrappingStyle { INLINE = 'inline', BEHIND_TEXT = 'behindText', IN_FRONT_OF_TEXT = 'inFrontOfText', WRAP_SQUARE = 'wrapSquare', WRAP_TOP_AND_BOTTOM = 'wrapTopAndBottom', } interface IDocImageFloatingToolbarProps { menuItems: IImagePopupMenuItem[]; unitId: string; subUnitId: string; drawingId: string; } interface IToolbarDropdownOption { label: string; value: T; icon: ReactNode; } function getWrappingStyle(documentDataModel: DocumentDataModel | undefined, drawingId: string): TextWrappingStyle { const drawing = documentDataModel?.getSnapshot().drawings?.[drawingId]; if (!drawing) { return TextWrappingStyle.INLINE; } if (drawing.layoutType === PositionedObjectLayoutType.WRAP_NONE) { return drawing.behindDoc === BooleanNumber.TRUE ? TextWrappingStyle.BEHIND_TEXT : TextWrappingStyle.IN_FRONT_OF_TEXT; } if (drawing.layoutType === PositionedObjectLayoutType.WRAP_SQUARE) { return TextWrappingStyle.WRAP_SQUARE; } if (drawing.layoutType === PositionedObjectLayoutType.WRAP_TOP_AND_BOTTOM) { return TextWrappingStyle.WRAP_TOP_AND_BOTTOM; } return TextWrappingStyle.INLINE; } function ToolbarGroup(props: { children: ReactNode }) { return (
{props.children}
); } function ToolbarButton(props: { title: string; active?: boolean; disabled?: boolean; children: ReactNode; onClick: () => void; }) { return ( ); } function ToolbarDropdownButton(props: { title: string; value: T; options: Array>; onChange: (value: T) => void; }) { const [open, setOpen] = useState(false); const activeOption = props.options.find((option) => option.value === props.value) ?? props.options[0]; return ( {props.options.map((option) => ( ))} )} > ); } function DocImageFloatingToolbar(props: IDocImageFloatingToolbarProps) { const commandService = useDependency(ICommandService); const iconManager = useDependency(IconManager); const localeService = useDependency(LocaleService); const univerInstanceService = useDependency(IUniverInstanceService); const documentDataModel = univerInstanceService.getUnit(props.unitId, UniverInstanceType.UNIVER_DOC) ?? undefined; const [wrappingStyle, setWrappingStyle] = useState(() => getWrappingStyle(documentDataModel, props.drawingId)); const [hidden, setHidden] = useState(false); const actionItems = props.menuItems .filter((item) => item.type !== 'select' && item.icon) .sort((a, b) => a.index - b.index); const wrappingStyleOptions = [ { label: localeService.t('drawing-ui.image-text-wrap.inline'), value: TextWrappingStyle.INLINE, icon: , }, { label: localeService.t('drawing-ui.image-text-wrap.square'), value: TextWrappingStyle.WRAP_SQUARE, icon: , }, { label: localeService.t('drawing-ui.image-text-wrap.topAndBottom'), value: TextWrappingStyle.WRAP_TOP_AND_BOTTOM, icon: , }, { label: localeService.t('drawing-ui.image-text-wrap.behindText'), value: TextWrappingStyle.BEHIND_TEXT, icon: , }, { label: localeService.t('drawing-ui.image-text-wrap.inFrontText'), value: TextWrappingStyle.IN_FRONT_OF_TEXT, icon: , }, ]; const executeMenuItem = (item: IImagePopupMenuItem | undefined) => { if (!item || item.disable) { return; } if (item.hideOnClick) { setHidden(true); } commandService.executeCommand(item.commandId, item.commandParams); }; const updateWrappingStyle = (value: TextWrappingStyle) => { setWrappingStyle(value); commandService.executeCommand(UPDATE_DOC_DRAWING_WRAPPING_STYLE_COMMAND_ID, { unitId: props.unitId, subUnitId: props.subUnitId, drawings: [{ unitId: props.unitId, subUnitId: props.subUnitId, drawingId: props.drawingId }], wrappingStyle: value, }); }; if (hidden) { return null; } return (
event.stopPropagation()} onMouseDown={(event) => { event.stopPropagation(); event.preventDefault(); }} className={clsx(` univer-box-border univer-flex univer-items-center univer-rounded univer-bg-gray-0 univer-px-1 univer-py-1 univer-shadow-sm dark:!univer-border-gray-700 dark:!univer-bg-gray-900 `, borderClassName)} > option.value === wrappingStyle)?.label ?? localeService.t('drawing-ui.image-text-wrap.inline')} value={wrappingStyle} options={wrappingStyleOptions} onChange={updateWrappingStyle} /> {actionItems.length > 0 && ( <> {actionItems.map((item) => { const Icon = iconManager.get(item.icon!); return ( executeMenuItem(item)} > ); })} )}
); } function DocChartFloatingToolbar(props: Pick) { const commandService = useDependency(ICommandService); const iconManager = useDependency(IconManager); const localeService = useDependency(LocaleService); const [hidden, setHidden] = useState(false); const chartTypeItem = props.menuItems.find((item) => item.type === 'select'); const actionItems = props.menuItems .filter((item) => item.type !== 'select' && item.icon) .sort((a, b) => a.index - b.index); const chartTypeOptions = (chartTypeItem?.options ?? []).map((option) => { const Icon = option.icon ? iconManager.get(option.icon) : ChartIcon; return { label: String(option.label), value: option.value, icon: , }; }); const executeMenuItem = (item: IImagePopupMenuItem | undefined) => { if (!item || item.disable) { return; } if (item.hideOnClick) { setHidden(true); } commandService.executeCommand(item.commandId, item.commandParams); }; if (hidden) { return null; } return (
event.stopPropagation()} onMouseDown={(event) => { event.stopPropagation(); event.preventDefault(); }} className={clsx(` univer-box-border univer-flex univer-items-center univer-rounded univer-bg-gray-0 univer-px-1 univer-py-1 univer-shadow-sm dark:!univer-border-gray-700 dark:!univer-bg-gray-900 `, borderClassName)} > {chartTypeItem && ( <> { if (!chartTypeItem.disable) { commandService.executeCommand(chartTypeItem.commandId, chartTypeItem.commandParamsFactory?.(value)); } }} /> {actionItems.length > 0 && } )} {actionItems.length > 0 && ( {actionItems.map((item) => { const Icon = iconManager.get(item.icon!); return ( executeMenuItem(item)} > ); })} )}
); }