123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723 |
- import React, { useEffect, useMemo, useRef, useState } from "react";
- import styles from "./index.less";
- import {
- Button,
- Divider,
- Dropdown,
- Flex,
- InputNumber,
- Select,
- Tooltip,
- } from "antd";
- import {
- AlignCenterOutlined,
- BoldOutlined,
- CaretDownOutlined,
- ColumnHeightOutlined,
- ItalicOutlined,
- SwapOutlined,
- UnderlineOutlined,
- } from "@ant-design/icons";
- import { fontFamilyOptions, alignOptionData, textAlignList } from "../../data";
- import { useModel } from "umi";
- import {
- alignCell,
- matchSize,
- setCellZIndex,
- handleSetEdgeStyle,
- } from "@/utils";
- import CustomColorPicker from "@/components/CustomColorPicker";
- import { ConnectorType } from "@/enum";
- import { set, cloneDeep } from "lodash-es";
- import FindReplaceModal from "@/components/FindReplaceModal";
- import { useFindReplace } from "@/hooks/useFindReplace";
- export default function ToolBar() {
- const {
- showRightPanel,
- toggleRightPanel,
- toggleFormatBrush,
- enableFormatBrush,
- pageState,
- } = useModel("appModel");
- const { canRedo, canUndo, onRedo, onUndo, selectedCell, graph } =
- useModel("graphModel");
- const [formModel, setFormModel] = useState<any>({
- fontFamily: "微软雅黑",
- fontSize: 14,
- bold: false,
- italic: false,
- underline: false,
- color: "#ffffff",
- lineHeight: 1.25,
- fillColor: "#ffffff",
- strokeColor: "#ffffff",
- strokeWidth: 1.5,
- strokeDasharray: "solid",
- startArrow: "",
- endArrow: "",
- connectorType: ConnectorType.Normal,
- });
- const {
- handleFind,
- handleReplace,
- handleReplaceAll,
- handleClose,
- handleFindNext,
- handleFindPrev,
- findCount,
- currentIndex,
- setInstance
- } = useFindReplace(graph);
- useEffect(() => {
- graph && setInstance(graph);
- }, [graph])
- const findModalRef = useRef<any>();
- const hasNode = useMemo(() => {
- return selectedCell?.find((cell) => cell.isNode());
- }, [selectedCell]);
- const hasEdge = useMemo(() => {
- return selectedCell?.find((cell) => cell.isEdge());
- }, [selectedCell]);
- const countInfo = useMemo(() => {
- let nodeCount = 0;
- selectedCell?.forEach((cell) => {
- if (cell.isNode()) nodeCount++;
- });
- // 多个节点
- return {
- isMulti: nodeCount > 1,
- nodeCount,
- };
- }, [selectedCell]);
- useEffect(() => {
- const firstNode = selectedCell?.find((item) => item.isNode());
- const firstEdge = selectedCell?.find((item) => item.isEdge());
- const nodeData = firstNode?.getData();
- const edgeData = firstEdge?.getData();
- const model = {
- fontFamily: nodeData?.text?.fontFamily || "微软雅黑",
- fontSize: nodeData?.text?.fontSize || 14,
- bold: nodeData?.text?.bold || false,
- italic: nodeData?.text?.italic || false,
- underline: nodeData?.text?.textDecoration === "underline",
- color: nodeData?.text?.color || "#ffffff",
- lineHeight: nodeData?.text?.lineHeight || 1.25,
- fillColor: nodeData?.fill?.color1 || "#ffffff",
- strokeColor:
- nodeData?.stroke?.color || edgeData?.stroke?.color || "#ffffff",
- strokeWidth: nodeData?.stroke?.width || edgeData?.stroke?.color || 1.5,
- strokeDasharray:
- nodeData?.stroke?.type || nodeData?.stroke?.type || "solid",
- startArrow: edgeData?.startArrow || "",
- endArrow: edgeData?.endArrow || "",
- connectorType: edgeData?.connectorType ?? ConnectorType.Normal,
- };
- setFormModel(model);
- }, [selectedCell]);
- // 执行修改动作
- const handleChange = (
- target: "node" | "edge" | "all",
- key: string,
- path: string,
- value: any
- ) => {
- setFormModel((state: any) => {
- const formData = {
- ...state,
- [key]: value,
- };
- selectedCell?.forEach((cell) => {
- const data = cloneDeep(cell.getData());
- set(data, path, value);
- if ((cell.isNode() && target === "node") || target === "all") {
- cell.setData(data);
- }
- if ((cell.isEdge() && target === "edge") || target === "all") {
- cell.setData(data);
- handleSetEdgeStyle(cell, formData.connectorType, pageState.jumpover);
- }
- });
- return formData;
- });
- };
- // 移动层级
- const handleSetIndex = (type: "top" | "bottom" | "up" | "down") => {
- setCellZIndex(type, selectedCell);
- };
- return (
- <div className={styles.toolBar}>
- <div className="flex justify-between items-center">
- <div className="h-40px flex items-center">
- <Tooltip placement="bottom" title="撤销">
- <Button
- type="text"
- icon={<i className="iconfont icon-undo"></i>}
- disabled={!canUndo}
- onClick={onUndo}
- />
- </Tooltip>
- <Tooltip placement="bottom" title="恢复">
- <Button
- type="text"
- icon={<i className="iconfont icon-redo"></i>}
- disabled={!canRedo}
- onClick={onRedo}
- />
- </Tooltip>
- <Tooltip
- placement="bottom"
- title={`格式刷${enableFormatBrush ? "生效中按ESC取消" : "(Ctrl+Shift+C)"}`}
- >
- <Button
- type="text"
- icon={<i className="iconfont icon-geshishua"></i>}
- disabled={!selectedCell?.length}
- className={
- enableFormatBrush && selectedCell?.length ? "active" : ""
- }
- onClick={() => graph && toggleFormatBrush(graph)}
- />
- </Tooltip>
- {/* <Tooltip placement="bottom" title="美化">
- <Button
- type="text"
- icon={<i className="iconfont icon-mofabang"></i>}
- />
- </Tooltip> */}
- <Divider type="vertical" />
- <Tooltip placement="bottom" title="字体">
- <Select
- className="w-100px"
- popupMatchSelectWidth={200}
- variant="borderless"
- suffixIcon={<CaretDownOutlined />}
- disabled={!selectedCell?.length}
- options={fontFamilyOptions}
- labelRender={(item) => item.value}
- value={formModel.fontFamily}
- onChange={(value) =>
- handleChange("all", "fontFamily", "text.fontFamily", value)
- }
- />
- </Tooltip>
- <Tooltip placement="bottom" title="字体大小">
- <InputNumber
- className="w-70px"
- variant="filled"
- min={12}
- max={30}
- disabled={!selectedCell.length}
- formatter={(value) => `${value}px`}
- value={formModel.fontSize}
- onChange={(value) =>
- handleChange("all", "fontSize", "text.fontSize", value)
- }
- />
- </Tooltip>
- <Tooltip placement="bottom" title="字体加粗">
- <Button
- type="text"
- icon={<BoldOutlined />}
- disabled={!selectedCell?.length}
- className={formModel.bold ? "active" : ""}
- onClick={() =>
- handleChange("all", "bold", "text.bold", !formModel.bold)
- }
- />
- </Tooltip>
- <Tooltip placement="bottom" title="字体倾斜">
- <Button
- type="text"
- icon={<ItalicOutlined />}
- disabled={!selectedCell?.length}
- className={formModel.italic ? "active" : ""}
- onClick={() =>
- handleChange("all", "italic", "text.italic", !formModel.italic)
- }
- />
- </Tooltip>
- <Tooltip placement="bottom" title="下划线">
- <Button
- type="text"
- icon={<UnderlineOutlined />}
- disabled={!selectedCell?.length}
- className={formModel.underline ? "active" : ""}
- onClick={() =>
- handleChange(
- "all",
- "underline",
- "text.textDecoration",
- !formModel.underline ? "underline" : "none"
- )
- }
- />
- </Tooltip>
- <CustomColorPicker
- color={formModel.color}
- onChange={(color) =>
- handleChange("all", "color", "text.color", color)
- }
- >
- <Tooltip placement="bottom" title="字体颜色">
- <Button
- type="text"
- icon={
- <div className="flex flex-col">
- <span className="iconfont icon-A text-13px"></span>
- <span
- className="w-16px h-2px border-1px border-#ff0"
- style={{ background: formModel.color }}
- ></span>
- </div>
- }
- disabled={!selectedCell?.length}
- />
- </Tooltip>
- </CustomColorPicker>
- <Tooltip placement="bottom" title="文本行高">
- <Select
- className="flex-1"
- variant="borderless"
- suffixIcon={<CaretDownOutlined className="text-12px" />}
- labelRender={() => <ColumnHeightOutlined />}
- popupMatchSelectWidth={100}
- options={[
- { label: "1.0", value: 1 },
- { label: "1.25", value: 1.25 },
- { label: "1.5", value: 1.5 },
- { label: "2.0", value: 2 },
- { label: "2.5", value: 2.5 },
- { label: "3.0", value: 3 },
- ]}
- disabled={!selectedCell?.length}
- value={formModel.lineHeight}
- onChange={(value) =>
- handleChange("all", "lineHeight", "text.lineHeight", value)
- }
- />
- </Tooltip>
- <Dropdown
- menu={{
- items: textAlignList.map((item) => {
- return {
- key: item.id,
- label: (
- <div className="w-120px">
- {item.icon}
- <span className="ml-8px">{item.name}</span>
- </div>
- ),
- onClick: () =>
- handleChange(
- "all",
- item.target,
- `text.${item.target}`,
- item.id
- ),
- };
- }),
- }}
- >
- <Tooltip placement="bottom" title="文本对齐">
- <Button type="text" className="w-50px" disabled={!hasNode}>
- <AlignCenterOutlined />
- <CaretDownOutlined className="text-12px" />
- </Button>
- </Tooltip>
- </Dropdown>
- <Divider type="vertical" />
- <CustomColorPicker
- color={formModel.fillColor}
- onChange={(color) =>
- handleChange("node", "fillColor", "fill.color1", color)
- }
- >
- <Tooltip placement="bottom" title="颜色填充">
- <Button
- type="text"
- disabled={!selectedCell?.length}
- icon={
- <div className="flex flex-col">
- <span className="iconfont icon-paint-bucket text-13px"></span>
- <span
- className="w-16px h-2px border-1px border-#ff0"
- style={{ background: formModel.fillColor }}
- ></span>
- </div>
- }
- />
- </Tooltip>
- </CustomColorPicker>
- <CustomColorPicker
- color={formModel.strokeColor}
- onChange={(color) =>
- handleChange("node", "strokeColor", "stroke.color", color)
- }
- >
- <Tooltip placement="bottom" title="连线颜色">
- <Button
- type="text"
- disabled={!selectedCell?.length}
- icon={
- <div className="flex flex-col">
- <span className="iconfont icon-bi text-13px"></span>
- <span
- className="w-16px h-2px border-1px border-#ff0"
- style={{ background: formModel.strokeColor }}
- ></span>
- </div>
- }
- />
- </Tooltip>
- </CustomColorPicker>
- <Tooltip placement="bottom" title="连线宽度">
- <Select
- className="flex-1"
- variant="borderless"
- suffixIcon={<CaretDownOutlined className="text-12px" />}
- labelRender={() => <i className="iconfont icon-xiankuan"></i>}
- popupMatchSelectWidth={100}
- options={[
- { label: "0px", value: 0 },
- { label: "0.5px", value: 0.5 },
- { label: "1px", value: 1 },
- { label: "1.5", value: 1.5 },
- { label: "2px", value: 2 },
- { label: "3px", value: 3 },
- { label: "4px", value: 4 },
- { label: "5px", value: 5 },
- { label: "6px", value: 6 },
- { label: "8px", value: 7 },
- { label: "10px", value: 10 },
- ]}
- disabled={!selectedCell?.length}
- value={formModel.strokeWidth}
- onChange={(value) =>
- handleChange("all", "strokeWidth", "stroke.width", value)
- }
- />
- </Tooltip>
- <Tooltip placement="bottom" title="线段样式">
- <Select
- className="flex-1"
- variant="borderless"
- suffixIcon={<CaretDownOutlined className="text-12px" />}
- labelRender={() => (
- <i className="iconfont icon-jixianyangshi-line"></i>
- )}
- popupMatchSelectWidth={100}
- options={[
- {
- label: (
- <img
- className="h-30px block"
- src={require("@/assets/image/line-solid.png")}
- />
- ),
- value: "solid",
- },
- {
- label: (
- <img
- className="h-30px block"
- src={require("@/assets/image/line-dashed.png")}
- />
- ),
- value: "dashed",
- },
- {
- label: (
- <img
- className="h-30px block"
- src={require("@/assets/image/line-dotted.png")}
- />
- ),
- value: "dotted",
- },
- {
- label: (
- <img
- className="h-30px block"
- src={require("@/assets/image/line-dashdot.png")}
- />
- ),
- value: "dashdot",
- },
- ]}
- disabled={!selectedCell?.length}
- value={formModel.strokeDasharray}
- onChange={(value) =>
- handleChange("all", "strokeDasharray", "stroke.type", value)
- }
- />
- </Tooltip>
- <Tooltip placement="bottom" title="连线类型">
- <Select
- className="flex-1"
- variant="borderless"
- suffixIcon={<CaretDownOutlined className="text-12px" />}
- labelRender={() => <i className="iconfont icon-lianxian"></i>}
- popupMatchSelectWidth={100}
- options={[
- {
- label: (
- <i className="iconfont icon-a-icon16lianxianleixinghuizhilianxian" />
- ),
- value: ConnectorType.Rounded,
- },
- {
- label: (
- <i className="iconfont icon-a-icon16lianxianleixingbeisaierquxian" />
- ),
- value: ConnectorType.Smooth,
- },
- {
- label: (
- <i className="iconfont icon-a-icon16lianxianleixinghuizhizhixian" />
- ),
- value: ConnectorType.Normal,
- },
- ]}
- disabled={!hasEdge}
- value={formModel.connectorType}
- onChange={(value) =>
- handleChange("edge", "connectorType", "connectorType", value)
- }
- />
- </Tooltip>
- <Divider type="vertical" />
- <Dropdown
- menu={{
- items: [
- {
- key: "top",
- onClick: () => handleSetIndex("top"),
- label: (
- <Flex className="w-180px" justify="space-between">
- <span>
- <i className="iconfont icon-zhiding1 mr-8px" />
- 置于顶层
- </span>
- <span className="text-12px color-#a6b9cd">Ctrl+]</span>
- </Flex>
- ),
- },
- {
- key: "bottom",
- onClick: () => handleSetIndex("bottom"),
- label: (
- <Flex className="w-180px" justify="space-between">
- <span>
- <i className="iconfont icon-zhidi1 mr-8px" />
- 置于底层
- </span>
- <span className="text-12px color-#a6b9cd">Ctrl+[</span>
- </Flex>
- ),
- },
- {
- key: "up",
- onClick: () => handleSetIndex("up"),
- label: (
- <Flex className="w-180px" justify="space-between">
- <span>
- <i className="iconfont icon-shangyiyiceng1 mr-8px" />
- 上移一层
- </span>
- <span className="text-12px color-#a6b9cd">
- Ctrl+Shift+]
- </span>
- </Flex>
- ),
- },
- {
- key: "dowm",
- onClick: () => handleSetIndex("down"),
- label: (
- <Flex className="w-180px" justify="space-between">
- <span>
- <i className="iconfont icon-xiayiyiceng1 mr-8px" />
- 下移一层
- </span>
- <span className="text-12px color-#a6b9cd">
- Ctrl+Shift+[
- </span>
- </Flex>
- ),
- },
- ],
- }}
- >
- <Tooltip placement="bottom" title="图层排列">
- <Button
- type="text"
- className="w-50px"
- disabled={!selectedCell?.length}
- >
- <i className="iconfont icon-zhiding1"></i>
- <CaretDownOutlined className="text-12px" />
- </Button>
- </Tooltip>
- </Dropdown>
- <Dropdown
- menu={{
- items: alignOptionData.map((item) => {
- return {
- key: item.id,
- disabled:
- ["v", "h"].includes(item.id) && countInfo.nodeCount < 3,
- label: (
- <Flex justify="space-between" className="w-180px">
- <div>
- <i className={"mr-8px iconfont " + item.icon} />
- <span>{item.name}</span>
- </div>
- <span className="text-12px color-#a6b9cd">
- {item.fastKey}
- </span>
- </Flex>
- ),
- onClick: () =>
- selectedCell && alignCell(item.id, selectedCell),
- };
- }),
- }}
- >
- <Tooltip placement="bottom" title="分布对齐">
- <Button
- type="text"
- className="w-50px"
- disabled={!countInfo.isMulti}
- >
- <i className="iconfont icon-zuoduiqi2" />
- <CaretDownOutlined className="text-12px" />
- </Button>
- </Tooltip>
- </Dropdown>
- <Dropdown
- menu={{
- items: [
- {
- key: "width",
- label: (
- <div>
- <i className="iconfont icon-gaodu mr-8px" />
- 宽度匹配
- </div>
- ),
- onClick: () =>
- selectedCell && matchSize("width", selectedCell),
- },
- {
- key: "height",
- label: (
- <div>
- <i className="iconfont icon-gaodu mr-8px" />
- 高度匹配
- </div>
- ),
- onClick: () =>
- selectedCell && matchSize("height", selectedCell),
- },
- {
- key: "auto",
- label: (
- <div>
- <i className="iconfont icon-shiyingkuangao mr-8px" />
- 宽高匹配
- </div>
- ),
- onClick: () =>
- selectedCell && matchSize("auto", selectedCell),
- },
- ],
- }}
- >
- <Tooltip placement="bottom" title="宽高匹配">
- <Button
- type="text"
- className="w-50px"
- disabled={!countInfo.isMulti}
- >
- <i className="iconfont icon-shiyingkuangao"></i>
- <CaretDownOutlined className="text-12px" />
- </Button>
- </Tooltip>
- </Dropdown>
- {/* <Dropdown menu={{ items: [] }}>
- <Button type="text" className="w-50px">
- <span>更多</span>
- <CaretDownOutlined className="text-12px" />
- </Button>
- </Dropdown> */}
- </div>
- <FindReplaceModal
- ref={findModalRef}
- current={currentIndex}
- count={findCount}
- onClose={handleClose}
- onFind={handleFind}
- onFindNext={handleFindNext}
- onFindPrev={handleFindPrev}
- onReplace={handleReplace}
- onReplaceAll={handleReplaceAll}
- />
- <div>
- <Tooltip placement="bottom" title="替换">
- <Button
- type="text"
- icon={<i className="iconfont icon-chaxun" />}
- className="m-r-16px"
- onClick={() => {
- findModalRef.current?.open();
- }}
- />
- </Tooltip>
- <Tooltip placement="bottom" title="样式">
- <Button
- type="text"
- icon={<SwapOutlined />}
- className={showRightPanel ? "active" : ""}
- onClick={() => toggleRightPanel()}
- />
- </Tooltip>
- </div>
- </div>
- </div>
- );
- }
|