openpencil/components.d.ts
Anton Soldatov 37da83aec3
Image drag-and-drop, clipboard paste, and paste-at-cursor (#92)
* Image drag-and-drop and clipboard paste onto canvas

* Paste images at cursor position, track canvas cursor in state

* Paste nodes at cursor position (Figma and internal clipboard)

* Fix review: center images at cursor, fix hasImageFiles, clean up hash/undo

* Paste at viewport center when cursor is outside canvas

* Fix Figma paste positioning: center nodes at cursor after import

* Image support: clipboard, export, drag-and-drop, paste, renderer, tools, UI

- Clipboard: embed image bytes (base64) in OpenPencil clipboard payload so
  copy/paste between documents preserves image fills
- Fig export: write images/ folder to .fig zip (both fflate and Tauri paths)
- Tauri: extend build_fig_file to accept image entries
- Renderer: implement CROP (with imageTransform) and TILE (TileMode.Repeat)
  scale modes, fix FIT to center the fitted image
- FigmaAPI: add createImage(bytes) with sync FNV-1a hash
- Tools: add set_image_fill tool for AI/MCP
- FillPicker: replace placeholder with file picker, preview, scale mode selector
- Drag-and-drop: new use-image-drop composable creates image nodes from dropped files
- Paste: keyboard paste handler detects image clipboard items
- Shared utils: extract hashImageBytes and getImageDimensions to src/utils/image.ts

* Add image tests and Yjs image sync for collaboration

Tests (18 new):
- FigmaAPI.createImage: deterministic hash, storage, format
- set_image_fill tool: all scale modes, error handling, storage
- Clipboard roundtrip: image bytes preserved, multiple images, children
- Fig export/import: zip contains images/, full round-trip

Collab:
- Add yimages Y.Map to sync graph.images via Yjs
- Observer applies remote image adds/deletes to local graph
- syncNodeToYjs pushes referenced image data alongside node props
- syncAllNodesToYjs bulk-syncs all images on room share

* Fix FillPicker: remove deleted utils/image import, use SHA-1 inline

* Extract storeImage() on editor store, use in FillPicker and placeImageNode

* Unify image hash: use sync FNV-1a everywhere, export computeImageHash

Editor store's hashBytes (async SHA-1) produced different hashes than
FigmaAPI.createImage (sync FNV-1a) for the same bytes. This meant
drag-and-drop images couldn't be deduplicated against AI tool images.

Replace hashBytes with computeImageHash from core. storeImage() is
now sync.

* Skip drawing IMAGE fills when image data is missing

When pasting from Figma, image fills reference a CDN hash but no pixel
data is included in the clipboard. Previously this rendered as a solid
black rectangle because applyImageFill bailed without setting a shader,
leaving stale paint state.

applyFill now returns false when the fill can't be applied, and callers
skip the draw call. The node still exists with the correct imageHash —
if the image data is later provided (e.g. via file re-open), it will
render correctly.

* Warn when Figma paste has missing image data

Show amber warning toast when pasted nodes reference image fills
without available bytes (Figma clipboard limitation).

Add 'warning' toast variant with tailwind-variants, extract toast
styles to src/components/ui/toast.ts.

* Use useFileDialog and useObjectUrl in FillPicker

Replace manual file input ref, click(), createObjectURL/revokeObjectURL
with vueuse composables. Remove hidden <input type=file> from template.

---------

Co-authored-by: Danila Poyarkov <dev@dannote.net>
2026-03-11 07:52:04 +03:00

131 lines
9.4 KiB
TypeScript

/* eslint-disable */
// @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
APIKeySetup: typeof import('./src/components/chat/APIKeySetup.vue')['default']
AppearanceSection: typeof import('./src/components/properties/AppearanceSection.vue')['default']
AppMenu: typeof import('./src/components/AppMenu.vue')['default']
AppSelect: typeof import('./src/components/AppSelect.vue')['default']
AppToast: typeof import('./src/components/AppToast.vue')['default']
CanvasContextMenu: typeof import('./src/components/CanvasContextMenu.vue')['default']
ChatInput: typeof import('./src/components/chat/ChatInput.vue')['default']
ChatMessage: typeof import('./src/components/chat/ChatMessage.vue')['default']
ChatPanel: typeof import('./src/components/ChatPanel.vue')['default']
CodePanel: typeof import('./src/components/CodePanel.vue')['default']
CollabPanel: typeof import('./src/components/CollabPanel.vue')['default']
ColorInput: typeof import('./src/components/ColorInput.vue')['default']
ColorPicker: typeof import('./src/components/ColorPicker.vue')['default']
DesignPanel: typeof import('./src/components/DesignPanel.vue')['default']
EditorCanvas: typeof import('./src/components/EditorCanvas.vue')['default']
EffectsSection: typeof import('./src/components/properties/EffectsSection.vue')['default']
ExportSection: typeof import('./src/components/properties/ExportSection.vue')['default']
FillPicker: typeof import('./src/components/FillPicker.vue')['default']
FillSection: typeof import('./src/components/properties/FillSection.vue')['default']
FontPicker: typeof import('./src/components/FontPicker.vue')['default']
HsvColorArea: typeof import('./src/components/HsvColorArea.vue')['default']
IconLucideALargeSmall: typeof import('~icons/lucide/a-large-small')['default']
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
IconLucideAlignCenter: typeof import('~icons/lucide/align-center')['default']
IconLucideAlignHorizontalDistributeCenter: typeof import('~icons/lucide/align-horizontal-distribute-center')['default']
IconLucideAlignHorizontalJustifyCenter: typeof import('~icons/lucide/align-horizontal-justify-center')['default']
IconLucideAlignHorizontalJustifyEnd: typeof import('~icons/lucide/align-horizontal-justify-end')['default']
IconLucideAlignHorizontalJustifyStart: typeof import('~icons/lucide/align-horizontal-justify-start')['default']
IconLucideAlignLeft: typeof import('~icons/lucide/align-left')['default']
IconLucideAlignRight: typeof import('~icons/lucide/align-right')['default']
IconLucideAlignVerticalJustifyCenter: typeof import('~icons/lucide/align-vertical-justify-center')['default']
IconLucideAlignVerticalJustifyEnd: typeof import('~icons/lucide/align-vertical-justify-end')['default']
IconLucideAlignVerticalJustifyStart: typeof import('~icons/lucide/align-vertical-justify-start')['default']
IconLucideArrowDown: typeof import('~icons/lucide/arrow-down')['default']
IconLucideArrowRight: typeof import('~icons/lucide/arrow-right')['default']
IconLucideBaseline: typeof import('~icons/lucide/baseline')['default']
IconLucideBetweenHorizontalEnd: typeof import('~icons/lucide/between-horizontal-end')['default']
IconLucideBetweenHorizontalStart: typeof import('~icons/lucide/between-horizontal-start')['default']
IconLucideBetweenVerticalEnd: typeof import('~icons/lucide/between-vertical-end')['default']
IconLucideBetweenVerticalStart: typeof import('~icons/lucide/between-vertical-start')['default']
IconLucideBlend: typeof import('~icons/lucide/blend')['default']
IconLucideBold: typeof import('~icons/lucide/bold')['default']
IconLucideBot: typeof import('~icons/lucide/bot')['default']
IconLucideCheck: typeof import('~icons/lucide/check')['default']
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
IconLucideClipboardCopy: typeof import('~icons/lucide/clipboard-copy')['default']
IconLucideCode: typeof import('~icons/lucide/code')['default']
IconLucideCopy: typeof import('~icons/lucide/copy')['default']
IconLucideEye: typeof import('~icons/lucide/eye')['default']
IconLucideEyeOff: typeof import('~icons/lucide/eye-off')['default']
IconLucideFile: typeof import('~icons/lucide/file')['default']
IconLucideFlipHorizontal: typeof import('~icons/lucide/flip-horizontal')['default']
IconLucideFlipVertical: typeof import('~icons/lucide/flip-vertical')['default']
IconLucideFolderPlus: typeof import('~icons/lucide/folder-plus')['default']
IconLucideImage: typeof import('~icons/lucide/image')['default']
IconLucideImageOff: typeof import('~icons/lucide/image-off')['default']
IconLucideItalic: typeof import('~icons/lucide/italic')['default']
IconLucideKeyRound: typeof import('~icons/lucide/key-round')['default']
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
IconLucideLayoutGrid: typeof import('~icons/lucide/layout-grid')['default']
IconLucideLink: typeof import('~icons/lucide/link')['default']
IconLucideLoaderCircle: typeof import('~icons/lucide/loader-circle')['default']
IconLucideMenu: typeof import('~icons/lucide/menu')['default']
IconLucideMessageCircle: typeof import('~icons/lucide/message-circle')['default']
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
IconLucideMoveHorizontal: typeof import('~icons/lucide/move-horizontal')['default']
IconLucideMoveVertical: typeof import('~icons/lucide/move-vertical')['default']
IconLucidePlus: typeof import('~icons/lucide/plus')['default']
IconLucideRadius: typeof import('~icons/lucide/radius')['default']
IconLucideRedo2: typeof import('~icons/lucide/redo2')['default']
IconLucideRotateCcw: typeof import('~icons/lucide/rotate-ccw')['default']
IconLucideRotateCw: typeof import('~icons/lucide/rotate-cw')['default']
IconLucideSearch: typeof import('~icons/lucide/search')['default']
IconLucideSend: typeof import('~icons/lucide/send')['default']
IconLucideSettings: typeof import('~icons/lucide/settings')['default']
IconLucideSettings2: typeof import('~icons/lucide/settings2')['default']
IconLucideShare2: typeof import('~icons/lucide/share2')['default']
IconLucideSidebar: typeof import('~icons/lucide/sidebar')['default']
IconLucideSlidersHorizontal: typeof import('~icons/lucide/sliders-horizontal')['default']
IconLucideSpace: typeof import('~icons/lucide/space')['default']
IconLucideSparkles: typeof import('~icons/lucide/sparkles')['default']
IconLucideSquare: typeof import('~icons/lucide/square')['default']
IconLucideStrikethrough: typeof import('~icons/lucide/strikethrough')['default']
IconLucideTrash2: typeof import('~icons/lucide/trash2')['default']
IconLucideTriangleAlert: typeof import('~icons/lucide/triangle-alert')['default']
IconLucideUnderline: typeof import('~icons/lucide/underline')['default']
IconLucideUndo2: typeof import('~icons/lucide/undo2')['default']
IconLucideUnlink: typeof import('~icons/lucide/unlink')['default']
IconLucideUsers: typeof import('~icons/lucide/users')['default']
IconLucideWrapText: typeof import('~icons/lucide/wrap-text')['default']
IconLucideX: typeof import('~icons/lucide/x')['default']
LayersPanel: typeof import('./src/components/LayersPanel.vue')['default']
LayerTree: typeof import('./src/components/LayerTree.vue')['default']
LayoutSection: typeof import('./src/components/properties/LayoutSection.vue')['default']
MobileDrawer: typeof import('./src/components/MobileDrawer.vue')['default']
MobileHud: typeof import('./src/components/MobileHud.vue')['default']
NodeContextMenuContent: typeof import('./src/components/NodeContextMenuContent.vue')['default']
PageSection: typeof import('./src/components/properties/PageSection.vue')['default']
PagesPanel: typeof import('./src/components/PagesPanel.vue')['default']
PositionSection: typeof import('./src/components/properties/PositionSection.vue')['default']
PropertiesPanel: typeof import('./src/components/PropertiesPanel.vue')['default']
ProviderSelect: typeof import('./src/components/chat/ProviderSelect.vue')['default']
ProviderSettings: typeof import('./src/components/chat/ProviderSettings.vue')['default']
ProviderSetup: typeof import('./src/components/chat/ProviderSetup.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SafariBanner: typeof import('./src/components/SafariBanner.vue')['default']
ScrubInput: typeof import('./src/components/ScrubInput.vue')['default']
StrokeSection: typeof import('./src/components/properties/StrokeSection.vue')['default']
TabBar: typeof import('./src/components/TabBar.vue')['default']
Toolbar: typeof import('./src/components/Toolbar.vue')['default']
TypographySection: typeof import('./src/components/properties/TypographySection.vue')['default']
VariablesDialog: typeof import('./src/components/VariablesDialog.vue')['default']
VariablesSection: typeof import('./src/components/properties/VariablesSection.vue')['default']
}
}