diff --git a/src/components/LayersPanel.vue b/src/components/LayersPanel.vue index 0048f66..1cbcc49 100644 --- a/src/components/LayersPanel.vue +++ b/src/components/LayersPanel.vue @@ -35,10 +35,11 @@ import { computed, nextTick, onBeforeUnmount, ref } from "vue"; import type { LayerInfo, LayerKind } from "@/types"; import { useEditor } from "@/editor/useEditor"; import { useLayers } from "@/editor/useLayers"; +import { shapeMeta } from "@/editor/shapes"; import { IconImage, IconText, - IconRect, + IconBackgroundTool, IconEye, IconEyeOff, IconLocked, @@ -65,11 +66,16 @@ const clamp = (v: number, lo: number, hi: number) => Math.max(lo, Math.min(hi, v /* ------------------------------------------------ 图标 */ -/** 行首的类型图标。`other` 兜底用「多层」那个,表示不属于以上三类的对象。 */ +/** 行首的类型图标。形状五种直接借形状表里的那份,和工具栏图标保持一致。 */ const KIND_ICON: Record = { + background: IconBackgroundTool, image: IconImage, text: IconText, - rect: IconRect, + rect: shapeMeta("rect").icon, + line: shapeMeta("line").icon, + arrow: shapeMeta("arrow").icon, + ellipse: shapeMeta("ellipse").icon, + polygon: shapeMeta("polygon").icon, other: IconTemplate, }; diff --git a/src/components/TopBar.vue b/src/components/TopBar.vue index 6cbfd9e..7ee767a 100644 --- a/src/components/TopBar.vue +++ b/src/components/TopBar.vue @@ -48,15 +48,15 @@ const { canUndo, canRedo } = useHistory(); const TOOLS = [ { cmd: "toolSelect", label: "选择工具", icon: IconSelect }, { cmd: "toolText", label: "文字工具", icon: IconText }, - { cmd: "toolRect", label: "矩形工具", icon: IconRect }, - { cmd: "toolImage", label: "图片工具", icon: IconImage }, + { cmd: "toolShape", label: "形状工具", icon: IconRect }, + { cmd: "toolBackground", label: "背景工具", icon: IconImage }, ] as const; const TOOL_OF: Record = { toolSelect: "select", toolText: "text", - toolRect: "rect", - toolImage: "image", + toolShape: "shape", + toolBackground: "background", }; function toolOf(cmd: string): ToolId { diff --git a/src/components/Workspace.vue b/src/components/Workspace.vue index 26a039a..8162f89 100644 --- a/src/components/Workspace.vue +++ b/src/components/Workspace.vue @@ -93,8 +93,8 @@ function onPointerDown(e: PointerEvent) { const scene = c.getScenePoint(e); void p; if (tool.value === "text") addText(); - else if (tool.value === "rect") addRect({ x: scene.x, y: scene.y }); - if (tool.value !== "image") { + else if (tool.value === "shape") addRect({ x: scene.x, y: scene.y }); + if (tool.value !== "background") { tool.value = "select"; commit(); } diff --git a/src/components/topbar/BackgroundMenu.vue b/src/components/topbar/BackgroundMenu.vue new file mode 100644 index 0000000..46feb3e --- /dev/null +++ b/src/components/topbar/BackgroundMenu.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/components/topbar/CanvasMenu.vue b/src/components/topbar/CanvasMenu.vue new file mode 100644 index 0000000..bca8c2b --- /dev/null +++ b/src/components/topbar/CanvasMenu.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/components/topbar/ShapeMenu.vue b/src/components/topbar/ShapeMenu.vue new file mode 100644 index 0000000..d6aa6b4 --- /dev/null +++ b/src/components/topbar/ShapeMenu.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/components/ui/Dropdown.vue b/src/components/ui/Dropdown.vue new file mode 100644 index 0000000..b280bf1 --- /dev/null +++ b/src/components/ui/Dropdown.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/src/editor/commands.ts b/src/editor/commands.ts index 57aebab..02a65f2 100644 --- a/src/editor/commands.ts +++ b/src/editor/commands.ts @@ -178,8 +178,8 @@ export const COMMANDS: Command[] = [ /* ---------------- 工具 ---------------- */ { id: "toolSelect", label: "选择工具", keys: ["v"], enabled: () => true, run: () => (ed.tool.value = "select") }, { id: "toolText", label: "文字工具", keys: ["t"], enabled: () => true, run: () => (ed.tool.value = "text") }, - { id: "toolRect", label: "矩形工具", keys: ["r"], enabled: () => true, run: () => (ed.tool.value = "rect") }, - { id: "toolImage", label: "图片工具", keys: ["shift+k"], enabled: () => true, run: () => (ed.tool.value = "image") }, + { id: "toolShape", label: "形状工具", keys: ["r"], enabled: () => true, run: () => (ed.tool.value = "shape") }, + { id: "toolBackground", label: "背景工具", keys: ["shift+k"], enabled: () => true, run: () => (ed.tool.value = "background") }, /* ---------------- 视图 ---------------- */ { id: "zoomIn", label: "放大", keys: ["+", "="], enabled: () => true, run: () => zoomBy(1.2) }, diff --git a/src/editor/shapes.ts b/src/editor/shapes.ts new file mode 100644 index 0000000..75bde96 --- /dev/null +++ b/src/editor/shapes.ts @@ -0,0 +1,33 @@ +import type { ShapeKind } from "@/types"; +import { + IconShapeRect, + IconShapeLine, + IconShapeArrow, + IconShapeEllipse, + IconShapePolygon, +} from "@/ui/icons"; + +/** + * 形状工具能画的五种形状。 + * + * 这张表同时喂三处:工具栏形状按钮(显示当前形状的图标)、形状下拉(列出五项)、 + * 图层面板(行首图标)。**图标是用户 2026-09-05 点名指定的,不要自己换。** + */ +export interface ShapeMeta { + kind: ShapeKind; + label: string; + /** 图标组件。类型留 unknown,避免这张表依赖 unplugin-icons 的组件类型。 */ + icon: unknown; +} + +export const SHAPES: ShapeMeta[] = [ + { kind: "rect", label: "矩形", icon: IconShapeRect }, + { kind: "line", label: "直线", icon: IconShapeLine }, + { kind: "arrow", label: "箭头", icon: IconShapeArrow }, + { kind: "ellipse", label: "圆形", icon: IconShapeEllipse }, + { kind: "polygon", label: "多边形", icon: IconShapePolygon }, +]; + +export function shapeMeta(kind: ShapeKind): ShapeMeta { + return SHAPES.find((s) => s.kind === kind) ?? SHAPES[0]; +} diff --git a/src/editor/useBackground.ts b/src/editor/useBackground.ts new file mode 100644 index 0000000..8d8654a --- /dev/null +++ b/src/editor/useBackground.ts @@ -0,0 +1,36 @@ +import type { FabricObject } from "fabric"; +import type { BackgroundAsset } from "@/types"; + +/** + * 【T13 待实现】专用背景图层。 + * + * **背景不是「再加一张图片图层」**,这是这条线的全部要点:全画布最多一个背景图层, + * 它永远在最底(`sendObjectToBack`),换背景是**替换**它而不是往上堆。 + * 对象上用 `obj.isBackground = true` 标记(已登记进 customProperties,能存进模板)。 + * + * 契约: + * - `current()` 返回当前背景对象,没有返回 null。找法是遍历 `canvas.getObjects()` + * 找 `isBackground` 为真的那个,**不要另存一份引用**——`loadFromJSON` 之后 + * 画布上的是新对象,缓存的引用会指向一个已经不在画布上的旧对象。 + * - `setBackground(asset)` 装一份素材当背景:有旧的就换掉,按 `fit` 铺,置底, + * `registerLayer()`,`bumpRevision()`,然后 `commit()` 记一步历史。 + * - `setSolid(color)` 纯色背景,用一个盖满画板的 Rect。 + * - `clear()` 删掉背景图层。 + * - 背景默认**不可选中**(`selectable: false`),否则点画布空白处老是选中它; + * 但它要出现在图层面板里,可以在那里选中、锁定、隐藏、删除。 + * - 画板尺寸变了要重新铺(listen `useEditor().size`)。 + */ +export function useBackground() { + return { + current(): FabricObject | null { + return null; + }, + async setBackground(_asset: BackgroundAsset): Promise { + return null; + }, + setSolid(_color: string): FabricObject | null { + return null; + }, + clear(): void {}, + }; +} diff --git a/src/editor/useEditor.ts b/src/editor/useEditor.ts index d8d75a3..ff785bb 100644 --- a/src/editor/useEditor.ts +++ b/src/editor/useEditor.ts @@ -1,6 +1,17 @@ -import { Canvas, FabricImage, FabricObject, Rect, Textbox, type TPointerEvent } from "fabric"; +import { + ActiveSelection, + Canvas, + Ellipse, + FabricImage, + FabricObject, + Line, + Polygon, + Rect, + Textbox, + type TPointerEvent, +} from "fabric"; import { ref, shallowRef } from "vue"; -import type { CanvasSize, LayerKind, SelectionKind, ToolId } from "@/types"; +import type { CanvasSize, LayerKind, SelectionKind, ShapeKind, ToolId } from "@/types"; import { DEFAULT_SIZE } from "./canvasSize"; /** @@ -13,7 +24,7 @@ import { DEFAULT_SIZE } from "./canvasSize"; */ /** 自定义属性要登记在这里,`canvas.toJSON()` 才会带上(fabric v7 的 customProperties 机制)。 */ -FabricObject.customProperties = ["layerId", "layerName", "layerLocked"]; +FabricObject.customProperties = ["layerId", "layerName", "layerLocked", "isBackground", "shapeKind"]; declare module "fabric" { interface FabricObject { @@ -23,6 +34,17 @@ declare module "fabric" { layerName?: string; /** 锁定:不可选中、不可移动。和 fabric 自带的 selectable/evented 联动。 */ layerLocked?: boolean; + /** + * 这是**那一个**专用背景图层。全画布最多一个,永远在最底层, + * 换背景是替换它而不是再加一层。归 `useBackground` 管,别处不要自己置位。 + */ + isBackground?: boolean; + /** + * 形状工具画出来的形状是哪一种。fabric 的类推不出「箭头」——它就是一个 + * Polygon/Path,和多边形同类;这个字段把用户的意图记下来,图层图标和 + * 右栏属性都读它。 + */ + shapeKind?: ShapeKind; } } @@ -32,6 +54,8 @@ const selectionKind = ref("none"); const size = ref({ width: DEFAULT_SIZE.width, height: DEFAULT_SIZE.height }); /** 当前工具。`select` 是默认态。 */ const tool = ref("select"); +/** 形状工具当前要画哪一种形状。工具栏形状按钮右边的下拉改它。 */ +const shapeKind = ref("rect"); /** * 画布内容的版本号。对象增删改、层序变化都会 +1。 * 图层面板、属性检查器 watch 它来重新推导自己的视图—— @@ -49,23 +73,54 @@ export function ensureLayerId(obj: FabricObject): string { export function layerKindOf(obj: FabricObject | null | undefined): LayerKind { if (!obj) return "other"; + // 背景要先判:它本身是一张 FabricImage 或一个 Rect,光看类型分不出来。 + if (obj.isBackground) return "background"; + if (obj.shapeKind) return obj.shapeKind; if (obj instanceof FabricImage) return "image"; if (obj instanceof Textbox) return "text"; + if (obj instanceof Ellipse) return "ellipse"; + if (obj instanceof Line) return "line"; + if (obj instanceof Polygon) return "polygon"; if (obj instanceof Rect) return "rect"; return "other"; } +/** 这个种类算不算「形状」——形状共用同一组外观属性(填充、描边、圆角)。 */ +export function isShapeKind(kind: LayerKind): boolean { + return kind === "rect" || kind === "line" || kind === "arrow" || kind === "ellipse" || kind === "polygon"; +} + +const KIND_NAME: Record = { + background: "背景", + image: "图片", + text: "文字", + rect: "矩形", + line: "直线", + arrow: "箭头", + ellipse: "圆形", + polygon: "多边形", + other: "对象", +}; + export function defaultLayerName(obj: FabricObject): string { const kind = layerKindOf(obj); if (kind === "text") return (obj as Textbox).text?.slice(0, 20) || "文字"; - if (kind === "image") return "图片"; - if (kind === "rect") return "矩形"; - return "对象"; + return KIND_NAME[kind]; } +/** + * 选中态:右栏据此决定显示哪几组属性。 + * + * 五种形状塌成一个 `shape`——它们共用同一组外观属性,右栏没必要为每种各写一份。 + * 背景图层归到 `image`:它就是一张图,滤镜之类的都适用。 + */ function kindOf(obj: FabricObject | null | undefined): SelectionKind { + if (!obj) return "none"; + if (obj instanceof ActiveSelection) return "multi"; const k = layerKindOf(obj); - return k === "other" ? (obj ? "other" : "none") : k === "rect" ? "other" : k; + if (k === "background" || k === "image") return "image"; + if (k === "text") return "text"; + return isShapeKind(k) ? "shape" : "other"; } function syncSelection() { @@ -285,6 +340,7 @@ export function useEditor() { selectionKind, size, tool, + shapeKind, revision, mount, unmount, diff --git a/src/editor/useShapeTool.ts b/src/editor/useShapeTool.ts new file mode 100644 index 0000000..5f1644e --- /dev/null +++ b/src/editor/useShapeTool.ts @@ -0,0 +1,24 @@ +import type { Canvas } from "fabric"; + +/** + * 【T12 待实现】形状工具的拖拽创建。 + * + * 现状(要被替换掉的):Workspace 里点一下画布就在点击处放一个固定大小的矩形。 + * 目标:按住左键从起点拖到终点松开,拖出多大就是多大,五种形状都走这一条路径。 + * + * 契约: + * - `attachShapeTool(canvas)` 在画布挂载后调一次,返回解绑函数,卸载时调。 + * - 只在 `useEditor().tool === "shape"` 时接管指针事件;其余时间完全让开, + * 不要影响选择/平移。 + * - 画什么看 `useEditor().shapeKind`。 + * - 拖拽过程中要有实时预览(把对象加进画布边拖边改,松手才 `commit()` 记一步历史)。 + * - **按住 Shift 约束成 1:1**(正方形 / 正圆 / 45° 倍数的直线和箭头)。 + * 注意这只管**新建**;已有对象缩放时的 Shift 语义保持现状,不要动。 + * - 松手后回到 `select` 工具并选中新对象(和 Figma 一致)。 + * - 拖拽距离小于阈值(约 4px)当作误触:放一个默认尺寸的形状,不要留下一个 0×0 的东西。 + * - 新对象要 `registerLayer()`,并把 `obj.shapeKind` 置成当前形状—— + * 箭头和多边形在 fabric 里都是 Polygon,光看类型分不出来。 + */ +export function attachShapeTool(_canvas: Canvas): () => void { + return () => {}; +} diff --git a/src/sources/providers.ts b/src/sources/providers.ts new file mode 100644 index 0000000..753f8fe --- /dev/null +++ b/src/sources/providers.ts @@ -0,0 +1,21 @@ +import type { PhotoProvider, PhotoProviderId } from "@/types"; + +/** 图库调用失败(没配 key、网络错误、被限流)。界面据此显示一句话,不让路径崩掉。 */ +export class PhotoProviderError extends Error { + constructor(message: string) { + super(message); + this.name = "PhotoProviderError"; + } +} + +/** + * 【T14 待实现】图库注册表。 + * + * 顺序即界面上标签页的顺序。**Pexels 在前**:Unsplash demo key 每小时只有 50 次 + * 请求,开发期点几下就没了;Pexels 是每小时 200 次、每月 2 万次。 + */ +export const PHOTO_PROVIDERS: PhotoProvider[] = []; + +export function providerById(id: PhotoProviderId): PhotoProvider | undefined { + return PHOTO_PROVIDERS.find((p) => p.id === id); +} diff --git a/src/sources/unsplash.ts b/src/sources/unsplash.ts index ea9339c..28ed0ab 100644 --- a/src/sources/unsplash.ts +++ b/src/sources/unsplash.ts @@ -50,7 +50,7 @@ export function toAsset(photo: UnsplashPhoto): BackgroundAsset { url: photo.pageUrl, }; return { - kind: "unsplash", + kind: "stock", src: photo.regularUrl, width: photo.width, height: photo.height, diff --git a/src/style.css b/src/style.css index 5fa5ded..b314885 100644 --- a/src/style.css +++ b/src/style.css @@ -91,3 +91,69 @@ label { font-size: 16px; display: block; } + +/* ================================================================ 下拉菜单 + * 顶栏三个下拉(画布编辑 / 形状 / 背景)的行样式。 + * + * **必须是全局类,不能写进 Dropdown.vue 的 scoped 样式**——菜单内容是插槽, + * 在使用方的作用域里渲染,Dropdown 自己的 scoped 样式够不到它。 + */ + +/* 一行可点的菜单项:图标 + 名字 +(可选)右侧键位/角标 */ +.menu-row { + display: flex; + align-items: center; + gap: 9px; + width: 100%; + height: 30px; + padding: 0 9px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--fg); + font-size: 12px; + text-align: left; + cursor: pointer; +} + +.menu-row:hover:not(:disabled) { + background: color-mix(in srgb, var(--fg) 10%, transparent); +} + +.menu-row.on { + color: var(--accent); +} + +.menu-row:disabled { + color: var(--fg-dim); + opacity: 0.5; + cursor: default; +} + +/* 行内靠右的次要信息:键位提示、当前值、子菜单角标 */ +.menu-row .menu-aside { + margin-left: auto; + color: var(--fg-dim); + font-size: 11px; + white-space: nowrap; +} + +/* 一段的小标题 */ +.menu-title { + padding: 8px 9px 4px; + color: var(--fg-dim); + font-size: 10px; + font-weight: 600; + letter-spacing: 0.08em; +} + +.menu-sep { + height: 1px; + margin: 5px 6px; + background: var(--line); +} + +/* 放非菜单行内容(输入框、卡片网格)的区域 */ +.menu-block { + padding: 4px 9px 8px; +} diff --git a/src/types.ts b/src/types.ts index 01c54aa..89abff1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,7 @@ /** * 全仓共享的类型契约。 * - * **这个文件是四条并行开发线之间唯一的接口。** 每条线只实现自己目录下的文件, + * **这个文件是并行开发各线之间唯一的接口。** 每条线只实现自己目录下的文件, * 通过这里的类型互相衔接,因此: * * - 并行开发期间**不要改动本文件**。需要新增字段先在任务书里提出,由主控统一改。 @@ -98,7 +98,7 @@ export interface FilterDef { /* ---------------------------------------------------------------- 背景来源(T4) */ -export type BackgroundSourceKind = "upload" | "geometry" | "unsplash"; +export type BackgroundSourceKind = "upload" | "geometry" | "stock"; /** * 素材署名。**不画进图里**——由界面输出成可复制的文本,用户自己贴进文章。 @@ -121,6 +121,54 @@ export interface BackgroundAsset { attribution?: Attribution; } +/* ---------------------------------------------------------------- 图库(Pexels / Unsplash) */ + +/** 支持的图库。dev 期优先用 Pexels——Unsplash demo key 每小时只有 50 次请求。 */ +export type PhotoProviderId = "pexels" | "unsplash"; + +/** + * 一条图库搜索结果,两家 API 的响应都归一到这个形状。 + * + * 两家的差异只有一处漏出来:`downloadLocation`。Unsplash 的 API 条款要求 + * 用户**选定**某张图时打一次下载统计端点,Pexels 没有这个要求,所以是可选的。 + */ +export interface StockPhoto { + id: string; + provider: PhotoProviderId; + width: number; + height: number; + /** 网格缩略图。 */ + thumbUrl: string; + /** 装进画布用的尺寸。约 1600px 长边,别取原图——原图动辄 6000px。 */ + fullUrl: string; + /** 原图页,署名链接用。Unsplash 要求带 utm_source。 */ + pageUrl: string; + authorName: string; + alt: string | null; + /** 仅 Unsplash:选定该图时要触发的统计端点。 */ + downloadLocation?: string; +} + +/** 图库当前能不能用。没配 key 一律 "missing",界面显示一句提示,不报错不崩。 */ +export type PhotoProviderStatus = "missing" | "ready"; + +/** + * 图库适配器。两家各实现一份,界面只认这个接口, + * 不在组件里写 `if (provider === "pexels")`。 + */ +export interface PhotoProvider { + id: PhotoProviderId; + label: string; + /** 申请 key 的页面,界面在 status 为 missing 时把它显示出来。 */ + keyUrl: string; + status(): PhotoProviderStatus; + /** `page` 从 1 起。key 缺失或网络失败时抛 `PhotoProviderError`。 */ + search(query: string, page: number): Promise; + toAsset(photo: StockPhoto): BackgroundAsset; + /** 只有 Unsplash 有实现;调用方无条件调,不判断是哪家。 */ + notifyDownload?(photo: StockPhoto): void; +} + /* ---------------------------------------------------------------- 导出与模板(T5) */ export type ExportFormat = "webp" | "png"; @@ -157,8 +205,11 @@ export interface Template { /** 右侧栏面板。选中对象的类型决定显示哪几个。 */ export type PanelId = "canvas" | "source" | "filter" | "text" | "export"; -/** 画布当前选中了什么,用来决定右栏显示哪些面板。 */ -export type SelectionKind = "none" | "image" | "text" | "other"; +/** + * 画布当前选中了什么,用来决定右栏显示哪些面板。 + * `shape` 覆盖矩形/直线/箭头/圆/多边形——它们共用同一组外观属性(填充、描边、圆角)。 + */ +export type SelectionKind = "none" | "image" | "text" | "shape" | "multi" | "other"; /* ================================================================ * 以下是 Figma 式编辑器改造(2026-09-05)新增的契约。 @@ -167,13 +218,41 @@ export type SelectionKind = "none" | "image" | "text" | "other"; /* ---------------------------------------------------------------- 工具 */ -/** 顶部工具栏的当前工具。`select` 是默认态,其余是「下一次点画布要放什么」。 */ -export type ToolId = "select" | "text" | "rect" | "image"; +/** + * 顶部工具栏的当前工具。`select` 是默认态。 + * + * `shape` 不再是「点一下放一个矩形」,而是**按住拖出来**的一族形状, + * 具体画什么看 `ShapeKind`(工具栏那个下拉里选)。 + * + * 原来的 `image` 工具去掉了:它点了没反应,而且「插入一张图」这件事 + * 拖进来就能做。它的位置换成 `background`——改的是那一个专用背景图层。 + */ +export type ToolId = "select" | "text" | "shape" | "background"; + +/** + * 形状工具当前要画的形状。工具栏形状按钮右侧的下拉切换它, + * 切换后按钮图标跟着变(和 Figma / Penpot 一致)。 + */ +export type ShapeKind = "rect" | "line" | "arrow" | "ellipse" | "polygon"; /* ---------------------------------------------------------------- 图层 */ -/** 图层的种类,决定列表里的图标和右栏显示哪些属性组。 */ -export type LayerKind = "image" | "text" | "rect" | "other"; +/** + * 图层的种类,决定列表里的图标和右栏显示哪些属性组。 + * + * `background` 是**那一个专用背景图层**,不是普通图片图层:它永远在最底, + * 换背景是替换它而不是再加一层,见 `useBackground`。 + */ +export type LayerKind = + | "background" + | "image" + | "text" + | "rect" + | "line" + | "arrow" + | "ellipse" + | "polygon" + | "other"; /** * 图层列表里的一行。**这是 fabric 对象的只读投影**,不是另一份状态—— diff --git a/src/ui/icons.ts b/src/ui/icons.ts index 2b5bc5d..ed3b6b6 100644 --- a/src/ui/icons.ts +++ b/src/ui/icons.ts @@ -17,8 +17,20 @@ * 尺寸和颜色由 CSS 控制:插件给每个图标挂了 `.icon` 类,规则在 style.css。 */ -/* ---- 工具 ---- */ +/* ---- 顶栏工具(图标由用户 2026-09-05 指定,别自己换) ---- */ +export { default as IconCanvasEdit } from "~icons/hugeicons/align-selection"; export { default as IconSelect } from "~icons/hugeicons/cursor-01"; +export { default as IconTextTool } from "~icons/hugeicons/text"; +export { default as IconBackgroundTool } from "~icons/hugeicons/image-composition"; + +/* ---- 形状(形状工具下拉里的五种) ---- */ +export { default as IconShapeRect } from "~icons/hugeicons/solid-line-02"; +export { default as IconShapeLine } from "~icons/hugeicons/liner"; +export { default as IconShapeArrow } from "~icons/hugeicons/arrow-up-right-01"; +export { default as IconShapeEllipse } from "~icons/hugeicons/circle"; +export { default as IconShapePolygon } from "~icons/hugeicons/triangle"; + +/* ---- 图层行/分组用的类型图标 ---- */ export { default as IconText } from "~icons/hugeicons/text-font"; export { default as IconRect } from "~icons/hugeicons/square"; export { default as IconImage } from "~icons/hugeicons/image-01"; @@ -75,7 +87,15 @@ export { default as IconTextRight } from "~icons/hugeicons/text-align-right"; export { default as IconFilter } from "~icons/hugeicons/blur"; export { default as IconTemplate } from "~icons/hugeicons/layers-01"; +/* ---- 图库 ---- */ +export { default as IconSearch } from "~icons/hugeicons/search-01"; +export { default as IconUpload } from "~icons/hugeicons/image-upload"; +export { default as IconGeometry } from "~icons/hugeicons/grid"; + /* ---- 通用 ---- */ export { default as IconChevronRight } from "~icons/hugeicons/chevron-right"; export { default as IconChevronDown } from "~icons/hugeicons/chevron-down"; export { default as IconPlus } from "~icons/hugeicons/plus-sign"; +export { default as IconCheck } from "~icons/hugeicons/tick-01"; +export { default as IconClose } from "~icons/hugeicons/cancel-01"; +export { default as IconLoading } from "~icons/hugeicons/reload";