Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 46 additions & 47 deletions packages/drawnix/src/components/icons.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
DuplicateIcon,
ExportImageIcon,
GithubIcon,
OpenFileIcon,
Expand All @@ -17,7 +18,7 @@ import {
import { loadFromJSON, saveAsJSON } from '../../../data/json';
import MenuItem from '../../menu/menu-item';
import MenuItemLink from '../../menu/menu-item-link';
import { saveAsImage } from '../../../utils/image';
import { copyAsImage, saveAsImage } from '../../../utils/image';
import { useDrawnix } from '../../../hooks/use-drawnix';
import { useI18n } from '../../../i18n';
import Menu from '../../menu/menu';
Expand All @@ -38,7 +39,9 @@ export const SaveToFile = () => {
icon={SaveFileIcon}
aria-label={t('menu.saveFile')}
shortcut={getShortcutKey('CtrlOrCmd+S')}
>{t('menu.saveFile')}</MenuItem>
>
{t('menu.saveFile')}
</MenuItem>
);
};
SaveToFile.displayName = 'SaveToFile';
Expand All @@ -50,7 +53,7 @@ export const OpenFile = () => {
const clearAndLoad = (
value: PlaitElement[],
viewport?: Viewport,
theme?: PlaitTheme
theme?: PlaitTheme,
) => {
board.children = value;
board.viewport = viewport || { zoom: 1 };
Expand All @@ -72,7 +75,9 @@ export const OpenFile = () => {
}}
icon={OpenFileIcon}
aria-label={t('menu.open')}
>{t('menu.open')}</MenuItem>
>
{t('menu.open')}
</MenuItem>
);
};
OpenFile.displayName = 'OpenFile';
Expand All @@ -89,13 +94,15 @@ export const SaveAsImage = () => {
saveAsImage(board, true);
}}
submenu={
<Menu onSelect={() => {
const itemSelectEvent = new CustomEvent(EVENT.MENU_ITEM_SELECT, {
bubbles: true,
cancelable: true,
});
menuContentProps.onSelect?.(itemSelectEvent);
}}>
<Menu
onSelect={() => {
const itemSelectEvent = new CustomEvent(EVENT.MENU_ITEM_SELECT, {
bubbles: true,
cancelable: true,
});
menuContentProps.onSelect?.(itemSelectEvent);
}}
>
<MenuItem
onSelect={() => {
saveAsImage(board, true);
Expand Down Expand Up @@ -145,6 +152,53 @@ export const CleanBoard = () => {
};
CleanBoard.displayName = 'CleanBoard';

export const CopyAsImage = () => {
const board = useBoard();
const menuContentProps = useContext(MenuContentPropsContext);
const { t } = useI18n();
return (
<MenuItem
icon={DuplicateIcon}
data-testid="image-export-button"
onSelect={() => {
copyAsImage(board, true);
}}
submenu={
<Menu
onSelect={() => {
const itemSelectEvent = new CustomEvent(EVENT.MENU_ITEM_SELECT, {
bubbles: true,
cancelable: true,
});
menuContentProps.onSelect?.(itemSelectEvent);
}}
>
<MenuItem
onSelect={() => {
copyAsImage(board, true);
}}
aria-label={t('menu.exportImage.png')}
>
{t('menu.exportImage.png')}
</MenuItem>
<MenuItem
onSelect={() => {
copyAsImage(board, false);
}}
aria-label={t('menu.exportImage.jpg')}
>
{t('menu.exportImage.jpg')}
</MenuItem>
</Menu>
}
shortcut={getShortcutKey('CtrlOrCmd+Shift+C')}
aria-label={t('menu.copyImage')}
>
{t('menu.copyImage')}
</MenuItem>
);
};
CopyAsImage.displayName = 'CopyAsImage';
export const Socials = () => {
return (
<MenuItemLink
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ import {
import { Island } from '../../island';
import { Popover, PopoverContent, PopoverTrigger } from '../../popover/popover';
import { useState } from 'react';
import { CleanBoard, OpenFile, SaveAsImage, SaveToFile, Socials } from './app-menu-items';
import {
CleanBoard,
OpenFile,
SaveAsImage,
SaveToFile,
Socials,
CopyAsImage,
} from './app-menu-items';
import { LanguageSwitcherMenu } from './language-switcher-menu';
import Menu from '../../menu/menu';
import MenuSeparator from '../../menu/menu-separator';
Expand Down Expand Up @@ -70,6 +77,7 @@ export const AppToolbar = () => {
<OpenFile></OpenFile>
<SaveToFile></SaveToFile>
<SaveAsImage></SaveAsImage>
<CopyAsImage></CopyAsImage>
<CleanBoard></CleanBoard>
<MenuSeparator />
<LanguageSwitcherMenu />
Expand Down Expand Up @@ -129,7 +137,6 @@ export const AppToolbar = () => {
}}
/>
)}

</Stack.Row>
</Island>
);
Expand Down
10 changes: 5 additions & 5 deletions packages/drawnix/src/i18n/translations/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,12 @@ const zhTranslations: Translations = {
'language.english': 'English',
'language.russian': 'Русский',
'language.arabic': 'عربي',

// Menu items
'menu.open': '打开',
'menu.saveFile': '保存文件',
'menu.exportImage': '导出图片',
'menu.copyImage': '复制图片',
'menu.exportImage.png': 'PNG',
'menu.exportImage.jpg': 'JPG',
'menu.cleanBoard': '清除画布',
Expand Down Expand Up @@ -117,7 +118,7 @@ const zhTranslations: Translations = {
'popupToolbar.fontColor': '字体颜色',
'popupToolbar.link': '链接',
'popupToolbar.stroke': '边框',

// Text placeholders
'textPlaceholders.link': '链接',
'textPlaceholders.text': '文本',
Expand All @@ -136,7 +137,7 @@ const zhTranslations: Translations = {
// Draw elements text
'draw.lineText': '文本',
'draw.geometryText': '文本',

// Mind map elements text
'mind.centralText': '中心主题',
'mind.abstractNodeText': '摘要',
Expand Down Expand Up @@ -168,7 +169,6 @@ const zhTranslations: Translations = {
'tutorial.appToolbar': '导出,语言设置,...',
'tutorial.creationToolbar': '选择一个工具开始你的创作',
'tutorial.themeDescription': '在明亮和黑暗主题之间切换',

};

export default zhTranslations;
export default zhTranslations;
1 change: 1 addition & 0 deletions packages/drawnix/src/i18n/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export interface Translations {
'menu.saveFile': string;
'menu.exportImage': string;
'menu.exportImage.png': string;
'menu.copyImage': string;
'menu.exportImage.jpg': string;
'menu.cleanBoard': string;
'menu.github': string;
Expand Down
11 changes: 8 additions & 3 deletions packages/drawnix/src/plugins/with-hotkey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
PlaitPointerType,
} from '@plait/core';
import { isHotkey } from 'is-hotkey';
import { addImage, saveAsImage } from '../utils/image';
import { addImage, copyAsImage, saveAsImage } from '../utils/image';
import { saveAsJSON } from '../data/json';
import { DrawnixState } from '../hooks/use-drawnix';
import { BoardCreationMode, setCreationMode } from '@plait/common';
Expand All @@ -14,7 +14,7 @@ import { FreehandShape } from './freehand/type';
import { ArrowLineShape, BasicShapes } from '@plait/draw';

export const buildDrawnixHotkeyPlugin = (
updateAppState: (appState: Partial<DrawnixState>) => void
updateAppState: (appState: Partial<DrawnixState>) => void,
) => {
const withDrawnixHotkey = (board: PlaitBoard) => {
const { globalKeyDown, keyDown } = board;
Expand All @@ -33,6 +33,11 @@ export const buildDrawnixHotkeyPlugin = (
event.preventDefault();
return;
}
if (isHotkey(['mod+shift+c'], { byKey: true })(event)) {
copyAsImage(board, true);
event.preventDefault();
return;
}
if (isHotkey(['mod+s'], { byKey: true })(event)) {
saveAsJSON(board);
event.preventDefault();
Expand Down Expand Up @@ -63,7 +68,7 @@ export const buildDrawnixHotkeyPlugin = (
if (event.key === 'v') {
BoardTransforms.updatePointerType(
board,
PlaitPointerType.selection
PlaitPointerType.selection,
);
updateAppState({ pointer: PlaitPointerType.selection });
event.preventDefault();
Expand Down
18 changes: 17 additions & 1 deletion packages/drawnix/src/utils/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,27 @@ export const saveAsImage = (board: PlaitBoard, isTransparent: boolean) => {
});
};

export const copyAsImage = (board: PlaitBoard, isTransparent: boolean) => {
const selectedElements = getSelectedElements(board);
boardToImage(board, {
elements: selectedElements.length > 0 ? selectedElements : undefined,
fillStyle: isTransparent ? 'transparent' : 'white',
}).then(async (image) => {
if (image) {
const pngImage = base64ToBlob(image);
const item = new ClipboardItem({ 'image/png': pngImage });

if (!item) return;
await navigator.clipboard.write([item]);
}
});
};

export const addImage = async (board: PlaitBoard) => {
const imageFile = await fileOpen({
description: 'Image',
extensions: Object.keys(
IMAGE_MIME_TYPES
IMAGE_MIME_TYPES,
) as (keyof typeof IMAGE_MIME_TYPES)[],
});
insertImage(board, imageFile);
Expand Down