@blocklet/editor 1.6.257 → 1.6.258

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,9 @@
1
1
  import 'medium-zoom/dist/style.css';
2
- export declare const useMediumZoom: (imageRef: React.RefObject<HTMLImageElement>) => {
2
+ interface MediumZoomOption {
3
+ enabled?: boolean;
4
+ }
5
+ export declare const useMediumZoom: (imageRef: React.RefObject<HTMLImageElement>, options?: MediumZoomOption) => {
3
6
  zoomOpened: boolean;
4
7
  openedImageRef: import("react").MutableRefObject<HTMLImageElement | null>;
5
8
  };
9
+ export {};
@@ -2,13 +2,17 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
2
2
  import { useEffect, useRef, useState } from 'react';
3
3
  import mediumZoom from 'medium-zoom/dist/pure';
4
4
  import 'medium-zoom/dist/style.css';
5
- export const useMediumZoom = (imageRef) => {
5
+ export const useMediumZoom = (imageRef, options) => {
6
6
  const [editor] = useLexicalComposerContext();
7
7
  const editable = editor.isEditable();
8
8
  const openedImageRef = useRef(null);
9
9
  const [zoomOpened, setZoomOpened] = useState(false);
10
+ const enabled = options?.enabled ?? true;
10
11
  useEffect(() => {
11
12
  if (!editable && imageRef.current) {
13
+ if (!enabled) {
14
+ return () => { };
15
+ }
12
16
  const zoom = mediumZoom(imageRef.current, { background: 'rgba(0,0,0,.5)' });
13
17
  zoom.on('opened', (e) => {
14
18
  const openedImage = document.querySelector('.medium-zoom-image--opened');
@@ -25,6 +29,6 @@ export const useMediumZoom = (imageRef) => {
25
29
  zoom.detach();
26
30
  };
27
31
  }
28
- }, [editable, imageRef.current]);
32
+ }, [editable, imageRef.current, enabled]);
29
33
  return { zoomOpened, openedImageRef };
30
34
  };
@@ -8,7 +8,6 @@
8
8
  /// <reference types="react" />
9
9
  import { LexicalEditor, NodeKey } from 'lexical';
10
10
  import './ImageNode.css';
11
- import { type DeviceFrame } from '../utils/device-frame';
12
11
  import type { ImageSizeMode } from '../../types';
13
12
  export default function ImageComponent({ file, src, altText, nodeKey, width, height, maxWidth, resizable, showCaption, caption, captionsEnabled, markerState, frame, sizeMode, }: {
14
13
  file?: File;
@@ -23,6 +22,6 @@ export default function ImageComponent({ file, src, altText, nodeKey, width, hei
23
22
  width: 'inherit' | number;
24
23
  captionsEnabled: boolean;
25
24
  markerState?: string;
26
- frame?: DeviceFrame;
25
+ frame?: string;
27
26
  sizeMode?: ImageSizeMode;
28
27
  }): JSX.Element;
@@ -17,7 +17,7 @@ import { LexicalNestedComposer } from '@lexical/react/LexicalNestedComposer';
17
17
  import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
18
18
  import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
19
19
  import { mergeRegister } from '@lexical/utils';
20
- import { Alert, Button, LinearProgress } from '@mui/material';
20
+ import { Alert, Box, Button, Dialog, LinearProgress } from '@mui/material';
21
21
  import { $getNodeByKey, $getSelection, $isNodeSelection, $setSelection, CLICK_COMMAND, COMMAND_PRIORITY_LOW, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, KEY_ENTER_COMMAND, KEY_ESCAPE_COMMAND, SELECTION_CHANGE_COMMAND, } from 'lexical';
22
22
  import { Suspense, useCallback, useEffect, useRef, useState } from 'react';
23
23
  import { createPortal } from 'react-dom';
@@ -35,6 +35,7 @@ import './ImageNode.css';
35
35
  import { ImageAnnotation, ImageAnnotationView, ImageEnhancer } from '../ui/ImageEnhancer';
36
36
  import { useImageDisplayWidth } from '../hooks/hooks';
37
37
  import { useMediumZoom } from '../hooks/medium-zoom';
38
+ import { FrameMockup, isDeviceFrame } from '../ui/FrameMockup';
38
39
  const imageCache = new Set();
39
40
  // Min size that require user confirm to upload image
40
41
  const REQUIRE_CONFIRM_UPLOAD_SIZE = 10 << 20;
@@ -63,6 +64,7 @@ function LazyImage({ altText, className, imageRef, src, width, height, }) {
63
64
  return (_jsx("img", { className: className || undefined, src: src, alt: altText, ref: imageRef, draggable: "false", ...imgProps }));
64
65
  }
65
66
  export default function ImageComponent({ file, src, altText, nodeKey, width, height, maxWidth, resizable, showCaption, caption, captionsEnabled, markerState, frame, sizeMode, }) {
67
+ const imageFrameRef = useRef(null);
66
68
  const imageRef = useRef(null);
67
69
  const buttonRef = useRef(null);
68
70
  const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey);
@@ -70,6 +72,9 @@ export default function ImageComponent({ file, src, altText, nodeKey, width, hei
70
72
  const [editor] = useLexicalComposerContext();
71
73
  const [selection, setSelection] = useState(null);
72
74
  const activeEditorRef = useRef(null);
75
+ const [markerEditing, setMarkerEditing] = useState(false);
76
+ const isDeviceFrameUsed = frame && isDeviceFrame(frame);
77
+ const [zoomImageVisible, setZoomImageVisible] = useState(false);
73
78
  const onDelete = useCallback((payload) => {
74
79
  if (isSelected && $isNodeSelection($getSelection())) {
75
80
  const event = payload;
@@ -161,11 +166,12 @@ export default function ImageComponent({ file, src, altText, nodeKey, width, hei
161
166
  }
162
167
  });
163
168
  };
164
- const setFrame = (deviceFrame) => {
169
+ // eslint-disable-next-line @typescript-eslint/no-shadow
170
+ const setFrame = (frame) => {
165
171
  editor.update(() => {
166
172
  const node = $getNodeByKey(nodeKey);
167
173
  if ($isImageNode(node)) {
168
- node.setFrame(deviceFrame);
174
+ node.setFrame(frame);
169
175
  }
170
176
  });
171
177
  };
@@ -240,7 +246,7 @@ export default function ImageComponent({ file, src, altText, nodeKey, width, hei
240
246
  }, [src, file, confirmText]);
241
247
  const placeholder = objectUrl ? (_jsx(ImageContainer, { draggable: draggable, success: !!src, loading: loading, error: error, confirmText: confirmText, uploadImage: uploadImage, placeholder: objectUrl, placeholderProps: { style: { width, height, maxWidth } } })) : null;
242
248
  const imageDisplayWidth = useImageDisplayWidth({ src: src || objectUrl, sizeMode, width });
243
- const { zoomOpened, openedImageRef } = useMediumZoom(imageRef);
249
+ const { zoomOpened, openedImageRef } = useMediumZoom(imageRef, { enabled: !frame });
244
250
  const renderImageAnnotationView = () => {
245
251
  if (!markerState)
246
252
  return null;
@@ -254,11 +260,67 @@ export default function ImageComponent({ file, src, altText, nodeKey, width, hei
254
260
  },
255
261
  }),
256
262
  } }));
263
+ // 若使用了 device frame, 禁用 marker view (目前存在冲突, device frame 对图片拉伸导致 marker view 位置不准确)
264
+ if (isDeviceFrameUsed) {
265
+ return null;
266
+ }
257
267
  return zoomOpened ? createPortal(element, document.body) : element;
258
268
  };
259
- return (_jsx(Suspense, { fallback: placeholder, children: _jsxs(_Fragment, { children: [src ? (_jsxs(ImageContainer, { draggable: draggable, children: [_jsx(LazyImage, { className: isFocused ? `focused ${$isNodeSelection(selection) ? 'draggable' : ''}` : null, src: src, altText: altText, imageRef: imageRef, width: imageDisplayWidth, height: height }), renderImageAnnotationView(), editor.isEditable() && (_jsx(ImageAnnotation, { imageRef: imageRef, markerState: markerState, onChange: setMarkerState }))] })) : (placeholder), showCaption && (_jsx("div", { className: "image-caption-container", children: _jsxs(LexicalNestedComposer, { initialEditor: caption, children: [_jsx(AutoFocusPlugin, {}), _jsx(MentionsPlugin, {}), _jsx(LinkPlugin, {}), _jsx(EmojisPlugin, {}), _jsx(HashtagPlugin, {}), _jsx(KeywordsPlugin, {}), _jsx(HistoryPlugin, { externalHistoryState: historyState }), _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "ImageNode__contentEditable" }), placeholder: editor.isEditable() ? (_jsx(Placeholder, { className: "ImageNode__placeholder", children: "Enter a caption..." })) : null, ErrorBoundary: LexicalErrorBoundary })] }) })), resizable && $isNodeSelection(selection) && isFocused && (_jsxs(_Fragment, { children: [_jsx(ImageResizer, { showCaption: showCaption, setShowCaption: setShowCaption, editor: editor, buttonRef: buttonRef, imageRef: imageRef,
269
+ const renderImage = (imgSrc) => {
270
+ if (frame) {
271
+ return (_jsxs(_Fragment, { children: [_jsxs(FrameMockup, { type: frame, className: "editor-image-frame-container",
272
+ // fix: 点击外壳无法选中 img 并进入 resize 模式
273
+ onClick: (e) => {
274
+ if (!editor.isEditable()) {
275
+ // 点击图片外壳时, 若不是编辑状态, 则放大图片 (因为需要放大的元素也包括 frame, 这里无法基于 medium-zoom 实现)
276
+ setZoomImageVisible(true);
277
+ return;
278
+ }
279
+ e.stopPropagation();
280
+ editor.update(() => {
281
+ const node = $getNodeByKey(nodeKey);
282
+ if ($isImageNode(node)) {
283
+ node.selectEnd();
284
+ }
285
+ });
286
+ if (imageRef.current) {
287
+ editor.dispatchCommand(CLICK_COMMAND, { ...e, target: imageRef.current });
288
+ }
289
+ }, sx: {
290
+ width: imageDisplayWidth,
291
+ height: 'auto!important',
292
+ overflow: 'hidden',
293
+ }, ref: imageFrameRef, children: [_jsx(LazyImage, { className: isFocused ? `focused ${$isNodeSelection(selection) ? 'draggable' : ''}` : null, src: imgSrc, altText: altText, imageRef: imageRef, width: "100%", height: "auto" }), renderImageAnnotationView()] }, `${frame}-${sizeMode}-${imageDisplayWidth}`), zoomImageVisible && (_jsx(Dialog, { open: zoomImageVisible, onClose: () => setZoomImageVisible(false), PaperProps: {
294
+ sx: {
295
+ maxWidth: '100%',
296
+ maxHeight: '100%',
297
+ overflow: 'hidden',
298
+ bgcolor: 'transparent',
299
+ boxShadow: 'none',
300
+ },
301
+ }, children: _jsxs(FrameMockup, { type: frame, ref: imageFrameRef, children: [_jsx(Box, { component: "img", src: src, alt: altText, ref: imageRef, sx: { maxWidth: '100%', maxHeight: '90vh' } }), renderImageAnnotationView()] }) }))] }));
302
+ }
303
+ return (_jsxs(_Fragment, { children: [_jsx(LazyImage, { className: isFocused ? `focused ${$isNodeSelection(selection) ? 'draggable' : ''}` : null, src: imgSrc, altText: altText, imageRef: imageRef, width: imageDisplayWidth, height: height }), renderImageAnnotationView()] }));
304
+ };
305
+ return (_jsx(Suspense, { fallback: placeholder, children: _jsxs(_Fragment, { children: [src ? (_jsxs(ImageContainer, { draggable: draggable, children: [renderImage(src), editor.isEditable() && (_jsx(ImageAnnotation, { editing: markerEditing, setEditing: setMarkerEditing, imageRef: imageRef, markerState: markerState, onChange: setMarkerState }))] })) : (placeholder), showCaption && (_jsx("div", { className: "image-caption-container", children: _jsxs(LexicalNestedComposer, { initialEditor: caption, children: [_jsx(AutoFocusPlugin, {}), _jsx(MentionsPlugin, {}), _jsx(LinkPlugin, {}), _jsx(EmojisPlugin, {}), _jsx(HashtagPlugin, {}), _jsx(KeywordsPlugin, {}), _jsx(HistoryPlugin, { externalHistoryState: historyState }), _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "ImageNode__contentEditable" }), placeholder: editor.isEditable() ? (_jsx(Placeholder, { className: "ImageNode__placeholder", children: "Enter a caption..." })) : null, ErrorBoundary: LexicalErrorBoundary })] }) })), resizable && $isNodeSelection(selection) && isFocused && (_jsxs(_Fragment, { children: [_jsx(ImageResizer, { showCaption: showCaption, setShowCaption: setShowCaption, editor: editor, buttonRef: buttonRef, imageRef: imageFrameRef.current ? imageFrameRef : imageRef,
260
306
  // maxWidth={maxWidth}
261
- onResizeStart: onResizeStart, onResizeEnd: onResizeEnd, captionsEnabled: captionsEnabled }), _jsx(ImageEnhancer, { deviceFrame: frame, sizeMode: width === 'inherit' ? sizeMode || 'best-fit' : undefined, onSizeModeChange: setSizeMode, onFrameChange: setFrame })] }))] }) }));
307
+ onResizeStart: onResizeStart, onResizeEnd: onResizeEnd, captionsEnabled: false }), _jsxs(ImageEnhancer, { frame: frame, sizeMode: width === 'inherit' ? sizeMode || 'best-fit' : undefined, onSizeModeChange: setSizeMode, onFrameChange: setFrame, children: [_jsx(Button, { disabled: !!isDeviceFrameUsed, onClick: () => setMarkerEditing(true), variant: "outlined", size: "small", sx: {
308
+ minWidth: 36,
309
+ height: 36,
310
+ ml: 1,
311
+ p: 0,
312
+ borderColor: 'grey.400',
313
+ '.MuiButton-icon': { m: 0 },
314
+ }, startIcon: _jsx("i", { className: "iconify", "data-icon": "tabler:photo-edit" }) }), captionsEnabled && (_jsx(Button, { onClick: () => {
315
+ setShowCaption(!showCaption);
316
+ }, variant: "outlined", size: "small", sx: {
317
+ minWidth: 36,
318
+ height: 36,
319
+ ml: 1,
320
+ p: 0,
321
+ borderColor: 'grey.400',
322
+ '.MuiButton-icon': { m: 0 },
323
+ }, startIcon: showCaption ? (_jsx("i", { className: "iconify", "data-icon": "tabler:heading-off" })) : (_jsx("i", { className: "iconify", "data-icon": "tabler:text-caption" })), title: showCaption ? 'Remove Caption' : 'Add Caption' }))] })] }))] }) }));
262
324
  }
263
325
  const ImageContainer = ({ success, loading, error, confirmText, uploadImage, placeholder, placeholderProps, ...props }) => {
264
326
  const [showMask, setShowMask] = useState(false);
@@ -8,7 +8,6 @@
8
8
  /// <reference types="react" />
9
9
  import type { DOMConversionMap, DOMExportOutput, EditorConfig, LexicalEditor, LexicalNode, NodeKey, SerializedEditor, SerializedLexicalNode, Spread } from 'lexical';
10
10
  import { DecoratorNode } from 'lexical';
11
- import type { DeviceFrame } from '../utils/device-frame';
12
11
  import type { ImageSizeMode } from '../../types';
13
12
  export interface ImagePayload {
14
13
  file?: File;
@@ -22,7 +21,7 @@ export interface ImagePayload {
22
21
  width?: number;
23
22
  captionsEnabled?: boolean;
24
23
  markerState?: string;
25
- frame?: DeviceFrame;
24
+ frame?: string;
26
25
  sizeMode?: ImageSizeMode;
27
26
  }
28
27
  export type SerializedImageNode = Spread<{
@@ -34,7 +33,7 @@ export type SerializedImageNode = Spread<{
34
33
  src?: string;
35
34
  width?: number;
36
35
  markerState?: string;
37
- frame?: DeviceFrame;
36
+ frame?: string;
38
37
  sizeMode?: ImageSizeMode;
39
38
  type: 'image';
40
39
  version: 1;
@@ -49,7 +48,7 @@ export declare class ImageNode extends DecoratorNode<JSX.Element> {
49
48
  __showCaption: boolean;
50
49
  __caption: LexicalEditor;
51
50
  __markerState?: string;
52
- __frame?: DeviceFrame;
51
+ __frame?: string;
53
52
  __sizeMode?: ImageSizeMode;
54
53
  __captionsEnabled: boolean;
55
54
  static getType(): string;
@@ -57,12 +56,12 @@ export declare class ImageNode extends DecoratorNode<JSX.Element> {
57
56
  static importJSON(serializedNode: SerializedImageNode): ImageNode;
58
57
  exportDOM(): DOMExportOutput;
59
58
  static importDOM(): DOMConversionMap | null;
60
- constructor(src: string | undefined, altText: string, maxWidth: number, width?: 'inherit' | number, height?: 'inherit' | number, showCaption?: boolean, caption?: LexicalEditor, captionsEnabled?: boolean, key?: NodeKey, file?: File, markerState?: string, frame?: DeviceFrame, sizeMode?: ImageSizeMode);
59
+ constructor(src: string | undefined, altText: string, maxWidth: number, width?: 'inherit' | number, height?: 'inherit' | number, showCaption?: boolean, caption?: LexicalEditor, captionsEnabled?: boolean, key?: NodeKey, file?: File, markerState?: string, frame?: string, sizeMode?: ImageSizeMode);
61
60
  exportJSON(): SerializedImageNode;
62
61
  setWidthAndHeight(width: 'inherit' | number, height: 'inherit' | number): void;
63
62
  setShowCaption(showCaption: boolean): void;
64
63
  setMarkerState(markerState: string): void;
65
- setFrame(frame?: DeviceFrame): void;
64
+ setFrame(frame?: string): void;
66
65
  setSizeMode(sizeMode?: ImageSizeMode): void;
67
66
  createDOM(config: EditorConfig): HTMLElement;
68
67
  updateDOM(): false;
@@ -0,0 +1,52 @@
1
+ .browser-mockup {
2
+ border-top: 2em solid rgba(230, 230, 230, 0.7);
3
+ box-shadow: 0 0.1em 1em 0 rgba(0, 0, 0, 0.4);
4
+ position: relative;
5
+ border-radius: 3px 3px 0 0;
6
+ }
7
+
8
+ .browser-mockup:before {
9
+ display: block;
10
+ position: absolute;
11
+ content: '';
12
+ top: -1.25em;
13
+ left: 1em;
14
+ width: 0.5em;
15
+ height: 0.5em;
16
+ border-radius: 50%;
17
+ background-color: #f44;
18
+ box-shadow: 0 0 0 2px #f44, 1.5em 0 0 2px #9b3, 3em 0 0 2px #fb5;
19
+ }
20
+
21
+ .browser-mockup.with-tab:after {
22
+ display: block;
23
+ position: absolute;
24
+ content: '';
25
+ top: -2em;
26
+ left: 5.5em;
27
+ width: 20%;
28
+ height: 0em;
29
+ border-bottom: 2em solid white;
30
+ border-left: 0.8em solid transparent;
31
+ border-right: 0.8em solid transparent;
32
+ }
33
+
34
+ .browser-mockup.with-url:after {
35
+ display: block;
36
+ position: absolute;
37
+ content: '';
38
+ top: -1.6em;
39
+ left: 5.5em;
40
+ width: calc(100% - 6em);
41
+ height: 1.2em;
42
+ border-radius: 2px;
43
+ background-color: white;
44
+ }
45
+
46
+ .browser-mockup > * {
47
+ display: block;
48
+ }
49
+ .browser-mockup {
50
+ margin: 2em;
51
+ flex: 1;
52
+ }
@@ -0,0 +1,13 @@
1
+ import { PropsWithChildren } from 'react';
2
+ import './browser.css';
3
+ export declare const browsers: readonly [{
4
+ readonly id: "chrome";
5
+ readonly name: "Chrome";
6
+ }, {
7
+ readonly id: "chrome-with-url";
8
+ readonly name: "Chrome (With URL)";
9
+ }];
10
+ export declare const isBrowserType: (id: string) => boolean;
11
+ export declare function BrowserMockup({ type, ...rest }: PropsWithChildren<{
12
+ type: string;
13
+ }>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box } from '@mui/material';
3
+ import './browser.css';
4
+ function Chrome({ children, ...rest }) {
5
+ return (_jsx(Box, { className: "browser-mockup", ...rest, children: children }));
6
+ }
7
+ function ChromeWithUrl({ children, ...rest }) {
8
+ return (_jsx(Box, { className: "browser-mockup with-url", ...rest, children: children }));
9
+ }
10
+ export const browsers = [
11
+ { id: 'chrome', name: 'Chrome' },
12
+ { id: 'chrome-with-url', name: 'Chrome (With URL)' },
13
+ ];
14
+ export const isBrowserType = (id) => browsers.some((frame) => frame.id === id);
15
+ const Components = {
16
+ chrome: Chrome,
17
+ 'chrome-with-url': ChromeWithUrl,
18
+ };
19
+ export function BrowserMockup({ type, ...rest }) {
20
+ const Compnent = Components[type];
21
+ return _jsx(Compnent, { ...rest });
22
+ }
@@ -0,0 +1,115 @@
1
+ /// <reference types="react" />
2
+ export declare const deviceFrames: readonly [{
3
+ readonly id: "iphone-x";
4
+ readonly name: "iPhone X";
5
+ }, {
6
+ readonly id: "iphone-8";
7
+ readonly name: "iPhone 8";
8
+ }, {
9
+ readonly id: "ipad-pro";
10
+ readonly name: "iPad Pro";
11
+ }, {
12
+ readonly id: "imac-pro";
13
+ readonly name: "iMac Pro";
14
+ }, {
15
+ readonly id: "macbook";
16
+ readonly name: "MacBook";
17
+ }, {
18
+ readonly id: "macbook-pro";
19
+ readonly name: "MacBook Pro";
20
+ }, {
21
+ readonly id: "surface-pro";
22
+ readonly name: "Surface Pro";
23
+ }, {
24
+ readonly id: "surface-book";
25
+ readonly name: "Surface Book";
26
+ }, {
27
+ readonly id: "surface-studio";
28
+ readonly name: "Surface Studio";
29
+ }, {
30
+ readonly id: "galaxy-s8";
31
+ readonly name: "Galaxy S8";
32
+ }, {
33
+ readonly id: "google-pixel";
34
+ readonly name: "Google Pixel";
35
+ }, {
36
+ readonly id: "google-pixel-2-xl";
37
+ readonly name: "Google Pixel 2 XL";
38
+ }, {
39
+ readonly id: "apple-watch";
40
+ readonly name: "Apple Watch";
41
+ }];
42
+ export declare const isDeviceFrame: (id: string) => boolean;
43
+ export declare const shadowFrames: readonly [{
44
+ readonly id: "shadow";
45
+ readonly name: "Shadow";
46
+ }, {
47
+ readonly id: "shadow-sm";
48
+ readonly name: "Shadow Small";
49
+ }, {
50
+ readonly id: "shadow-lg";
51
+ readonly name: "Shadow Large";
52
+ }];
53
+ export declare const isShadowFrame: (id: string) => boolean;
54
+ export declare const frames: ({
55
+ readonly id: "chrome";
56
+ readonly name: "Chrome";
57
+ } | {
58
+ readonly id: "chrome-with-url";
59
+ readonly name: "Chrome (With URL)";
60
+ } | {
61
+ readonly id: "iphone-x";
62
+ readonly name: "iPhone X";
63
+ } | {
64
+ readonly id: "iphone-8";
65
+ readonly name: "iPhone 8";
66
+ } | {
67
+ readonly id: "ipad-pro";
68
+ readonly name: "iPad Pro";
69
+ } | {
70
+ readonly id: "imac-pro";
71
+ readonly name: "iMac Pro";
72
+ } | {
73
+ readonly id: "macbook";
74
+ readonly name: "MacBook";
75
+ } | {
76
+ readonly id: "macbook-pro";
77
+ readonly name: "MacBook Pro";
78
+ } | {
79
+ readonly id: "surface-pro";
80
+ readonly name: "Surface Pro";
81
+ } | {
82
+ readonly id: "surface-book";
83
+ readonly name: "Surface Book";
84
+ } | {
85
+ readonly id: "surface-studio";
86
+ readonly name: "Surface Studio";
87
+ } | {
88
+ readonly id: "galaxy-s8";
89
+ readonly name: "Galaxy S8";
90
+ } | {
91
+ readonly id: "google-pixel";
92
+ readonly name: "Google Pixel";
93
+ } | {
94
+ readonly id: "google-pixel-2-xl";
95
+ readonly name: "Google Pixel 2 XL";
96
+ } | {
97
+ readonly id: "apple-watch";
98
+ readonly name: "Apple Watch";
99
+ } | {
100
+ readonly id: "shadow";
101
+ readonly name: "Shadow";
102
+ } | {
103
+ readonly id: "shadow-sm";
104
+ readonly name: "Shadow Small";
105
+ } | {
106
+ readonly id: "shadow-lg";
107
+ readonly name: "Shadow Large";
108
+ })[];
109
+ interface FrameMockupProps {
110
+ type: string;
111
+ }
112
+ export declare const FrameMockup: import("react").ForwardRefExoticComponent<Omit<FrameMockupProps & import("@mui/system").BoxOwnProps<import("@mui/material").Theme> & import("@mui/material/OverridableComponent").CommonProps & Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
113
+ ref?: import("react").RefObject<HTMLDivElement> | ((instance: HTMLDivElement | null) => void) | null | undefined;
114
+ }, keyof import("@mui/system").BoxOwnProps<import("@mui/material").Theme> | keyof import("@mui/material/OverridableComponent").CommonProps>, "ref"> & import("react").RefAttributes<unknown>>;
115
+ export {};
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import Screenshot from '@arcblock/ux/lib/Screenshot';
3
+ import { forwardRef } from 'react';
4
+ import { Box } from '@mui/material';
5
+ import { isBrowserType, BrowserMockup, browsers } from './browser';
6
+ export const deviceFrames = [
7
+ { id: 'iphone-x', name: 'iPhone X' },
8
+ { id: 'iphone-8', name: 'iPhone 8' },
9
+ { id: 'ipad-pro', name: 'iPad Pro' },
10
+ { id: 'imac-pro', name: 'iMac Pro' },
11
+ { id: 'macbook', name: 'MacBook' },
12
+ { id: 'macbook-pro', name: 'MacBook Pro' },
13
+ { id: 'surface-pro', name: 'Surface Pro' },
14
+ { id: 'surface-book', name: 'Surface Book' },
15
+ { id: 'surface-studio', name: 'Surface Studio' },
16
+ { id: 'galaxy-s8', name: 'Galaxy S8' },
17
+ { id: 'google-pixel', name: 'Google Pixel' },
18
+ { id: 'google-pixel-2-xl', name: 'Google Pixel 2 XL' },
19
+ { id: 'apple-watch', name: 'Apple Watch' },
20
+ ];
21
+ export const isDeviceFrame = (id) => deviceFrames.some((frame) => frame.id === id);
22
+ export const shadowFrames = [
23
+ { id: 'shadow', name: 'Shadow' },
24
+ { id: 'shadow-sm', name: 'Shadow Small' },
25
+ { id: 'shadow-lg', name: 'Shadow Large' },
26
+ ];
27
+ export const isShadowFrame = (id) => shadowFrames.some((frame) => frame.id === id);
28
+ export const frames = [...deviceFrames, ...browsers, ...shadowFrames];
29
+ export const FrameMockup = forwardRef(function FrameMockup({ type, sx, children, ...rest }, ref) {
30
+ const mergedSx = [{}, ...(Array.isArray(sx) ? sx : [sx])];
31
+ const renderInner = () => {
32
+ if (isDeviceFrame(type)) {
33
+ mergedSx.push({
34
+ img: {
35
+ objectFit: 'contain!important',
36
+ bgcolor: '#000!important',
37
+ borderRadius: '0!important',
38
+ },
39
+ });
40
+ return _jsx(Screenshot, { type: type, children: children });
41
+ }
42
+ if (isBrowserType(type)) {
43
+ mergedSx.push({
44
+ img: {
45
+ borderRadius: '0!important',
46
+ },
47
+ });
48
+ return _jsx(BrowserMockup, { type: type, children: children });
49
+ }
50
+ if (isShadowFrame(type)) {
51
+ const styles = {
52
+ 'shadow-sm': { boxShadow: '0 0.1em 0.5em 0 rgba(0, 0, 0, 0.2);' },
53
+ shadow: { boxShadow: '0 0.1em 1em 0 rgba(0, 0, 0, 0.3);' },
54
+ 'shadow-lg': { boxShadow: '0 0.1em 1.5em 0 rgba(0, 0, 0, 0.4);' },
55
+ };
56
+ return _jsx(Box, { sx: { p: 3, img: styles[type] }, children: children });
57
+ }
58
+ };
59
+ return (_jsx(Box, { ref: ref, sx: mergedSx, ...rest, children: renderInner() }));
60
+ });
@@ -1,14 +1,16 @@
1
1
  import { type BoxProps } from '@mui/material';
2
+ import { PropsWithChildren } from 'react';
2
3
  import { ImageSizeMode } from '../../types';
3
- import { type DeviceFrame } from '../utils/device-frame';
4
4
  interface ImageAnnotationProps {
5
+ editing: boolean;
6
+ setEditing: (editing: boolean) => void;
5
7
  imageRef: {
6
8
  current: null | HTMLImageElement;
7
9
  };
8
10
  markerState?: string;
9
11
  onChange: (markerState: string) => void;
10
12
  }
11
- export declare function ImageAnnotation({ imageRef, markerState, onChange }: ImageAnnotationProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function ImageAnnotation({ editing, setEditing, imageRef, markerState, onChange }: ImageAnnotationProps): null;
12
14
  interface ImageAnnotationViewProps {
13
15
  imageRef: {
14
16
  current: null | HTMLImageElement;
@@ -19,9 +21,9 @@ interface ImageAnnotationViewProps {
19
21
  export declare function ImageAnnotationView({ imageRef, markerState, sx, ...rest }: ImageAnnotationViewProps & BoxProps): import("react/jsx-runtime").JSX.Element;
20
22
  interface ImageEnhancerProps {
21
23
  sizeMode?: ImageSizeMode;
22
- deviceFrame?: DeviceFrame;
24
+ frame?: string;
23
25
  onSizeModeChange: (sizeMode: ImageSizeMode) => void;
24
- onFrameChange: (deviceFrame?: DeviceFrame) => void;
26
+ onFrameChange: (frame?: string) => void;
25
27
  }
26
- export declare function ImageEnhancer({ sizeMode, onSizeModeChange }: ImageEnhancerProps): import("react/jsx-runtime").JSX.Element;
28
+ export declare function ImageEnhancer({ sizeMode, frame, onSizeModeChange, onFrameChange, children, }: PropsWithChildren<ImageEnhancerProps>): import("react/jsx-runtime").JSX.Element;
27
29
  export {};
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, IconButton, ToggleButtonGroup, ToggleButton } from '@mui/material';
3
- import { useEffect, useRef, useState } from 'react';
2
+ import { Box, ToggleButtonGroup, ToggleButton, Select, MenuItem } from '@mui/material';
3
+ import { useEffect, useRef } from 'react';
4
4
  import * as markerjs2 from 'markerjs2';
5
5
  import * as mjslive from 'markerjs-live';
6
+ import { frames } from './FrameMockup';
6
7
  const parseMarkerState = (markerState) => {
7
8
  if (!markerState)
8
9
  return null;
@@ -14,8 +15,7 @@ const parseMarkerState = (markerState) => {
14
15
  return null;
15
16
  }
16
17
  };
17
- export function ImageAnnotation({ imageRef, markerState, onChange }) {
18
- const [editing, setEditing] = useState(false);
18
+ export function ImageAnnotation({ editing, setEditing, imageRef, markerState, onChange }) {
19
19
  useEffect(() => {
20
20
  if (editing && imageRef.current) {
21
21
  const markerArea = new markerjs2.MarkerArea(imageRef.current);
@@ -37,7 +37,7 @@ export function ImageAnnotation({ imageRef, markerState, onChange }) {
37
37
  };
38
38
  }
39
39
  }, [editing]);
40
- return (_jsx(Box, { sx: { position: 'absolute', bottom: 16, right: 16 }, children: _jsx(IconButton, { onClick: () => setEditing(!editing), color: "primary", sx: { borderRadius: 0.5, ':hover': { bgcolor: 'grey.200' } }, children: _jsx("i", { className: "iconify", "data-icon": "tabler:photo-edit" }) }) }));
40
+ return null;
41
41
  }
42
42
  export function ImageAnnotationView({ imageRef, markerState, sx, ...rest }) {
43
43
  const viewRef = useRef(null);
@@ -60,17 +60,23 @@ export function ImageAnnotationView({ imageRef, markerState, sx, ...rest }) {
60
60
  if (parsed && imageRef.current && viewRef.current) {
61
61
  const markerView = new mjslive.MarkerView(imageRef.current);
62
62
  // fix error - "Failed to execute 'setRotate' on 'SVGTransform'"
63
- setTimeout(() => {
63
+ const timer = setTimeout(() => {
64
64
  markerView.targetRoot = viewRef.current;
65
65
  markerView.show(parsed);
66
66
  }, 10);
67
67
  return () => {
68
+ clearTimeout(timer);
68
69
  markerView.close();
69
70
  };
70
71
  }
71
72
  }, [markerState, imageRef]);
72
73
  return _jsx(Box, { sx: mergedSx, ref: viewRef, ...rest });
73
74
  }
74
- export function ImageEnhancer({ sizeMode, onSizeModeChange }) {
75
- return (_jsx(Box, { sx: { position: 'relative', zIndex: 'tooltip', whiteSpace: 'nowrap' }, children: _jsx(Box, { sx: { position: 'absolute', bgcolor: '#fff' }, children: _jsxs(ToggleButtonGroup, { color: "standard", value: sizeMode, size: "small", exclusive: true, onChange: (_, v) => onSizeModeChange(v), "aria-label": "Platform", children: [_jsx(ToggleButton, { value: "small", children: "Small" }), _jsx(ToggleButton, { value: "best-fit", children: "Best fit" }), _jsx(ToggleButton, { value: "original", children: "Original size" })] }) }) }));
75
+ export function ImageEnhancer({ sizeMode, frame, onSizeModeChange, onFrameChange, children, }) {
76
+ const handleFrameChange = (value) => {
77
+ onFrameChange(value === 'none' ? undefined : value);
78
+ };
79
+ return (_jsx(Box, { sx: { position: 'relative', zIndex: 'tooltip', whiteSpace: 'nowrap', mt: 1 }, children: _jsxs(Box, { sx: { position: 'absolute', display: 'flex', alignItems: 'center', bgcolor: '#fff' }, children: [_jsxs(ToggleButtonGroup, { color: "standard", value: sizeMode, size: "small", exclusive: true, onChange: (_, v) => onSizeModeChange(v), "aria-label": "Platform", children: [_jsx(ToggleButton, { value: "small", children: "Small" }), _jsx(ToggleButton, { value: "best-fit", children: "Best fit" }), _jsx(ToggleButton, { value: "original", children: "Original size" })] }), _jsxs(Select, { value: frame ?? 'none', onChange: (e) => handleFrameChange(e.target.value), sx: { ml: 1 }, children: [_jsx(MenuItem, { value: "none", children: "Select frame mockup" }), frames.map((x) => {
80
+ return (_jsx(MenuItem, { value: x.id, children: x.name }, x.id));
81
+ })] }), children] }) }));
76
82
  }
@@ -138,16 +138,12 @@ export default function ImageResizer({ onResizeStart, onResizeEnd, buttonRef, im
138
138
  };
139
139
  return (_jsxs("div", { ref: controlWrapperRef, children: [captionsEnabled && (_jsx("button", { className: "image-caption-button", ref: buttonRef, onClick: () => {
140
140
  setShowCaption(!showCaption);
141
- }, children: showCaption ? 'Remove Caption' : 'Add Caption' })), _jsx("div", { className: "image-resizer image-resizer-n", onPointerDown: (event) => {
142
- handlePointerDown(event, Direction.north);
143
- } }), _jsx("div", { className: "image-resizer image-resizer-ne", onPointerDown: (event) => {
141
+ }, children: showCaption ? 'Remove Caption' : 'Add Caption' })), _jsx("div", { className: "image-resizer image-resizer-ne", onPointerDown: (event) => {
144
142
  handlePointerDown(event, Direction.north | Direction.east);
145
143
  } }), _jsx("div", { className: "image-resizer image-resizer-e", onPointerDown: (event) => {
146
144
  handlePointerDown(event, Direction.east);
147
145
  } }), _jsx("div", { className: "image-resizer image-resizer-se", onPointerDown: (event) => {
148
146
  handlePointerDown(event, Direction.south | Direction.east);
149
- } }), _jsx("div", { className: "image-resizer image-resizer-s", onPointerDown: (event) => {
150
- handlePointerDown(event, Direction.south);
151
147
  } }), _jsx("div", { className: "image-resizer image-resizer-sw", onPointerDown: (event) => {
152
148
  handlePointerDown(event, Direction.south | Direction.west);
153
149
  } }), _jsx("div", { className: "image-resizer image-resizer-w", onPointerDown: (event) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/editor",
3
- "version": "1.6.257",
3
+ "version": "1.6.258",
4
4
  "main": "lib/index.js",
5
5
  "scripts": {
6
6
  "dev": "npm run storybook",
@@ -40,7 +40,7 @@
40
40
  "@arcblock/ux": "^2.9.77",
41
41
  "@blocklet/embed": "^0.1.11",
42
42
  "@blocklet/pages-kit": "^0.2.302",
43
- "@blocklet/pdf": "1.6.257",
43
+ "@blocklet/pdf": "1.6.258",
44
44
  "@excalidraw/excalidraw": "^0.14.2",
45
45
  "@iconify/iconify": "^3.0.1",
46
46
  "@iconify/icons-tabler": "^1.2.95",
@@ -113,5 +113,5 @@
113
113
  "react": "*",
114
114
  "react-dom": "*"
115
115
  },
116
- "gitHead": "427c78b7cf9dbaaab2f6576e8da87d761156e280"
116
+ "gitHead": "6b5469534e4b90a352a6e9af359e5bf57daac880"
117
117
  }
@@ -1,2 +0,0 @@
1
- export declare const deviceFrames: readonly ["iphone-x", "iphone-8", "ipad-pro", "imac-pro", "macbook", "macbook-pro", "surface-pro", "surface-book", "surface-studio", "galaxy-s8", "google-pixel", "google-pixel-2-xl", "apple-watch"];
2
- export type DeviceFrame = typeof deviceFrames[number];
@@ -1,15 +0,0 @@
1
- export const deviceFrames = [
2
- 'iphone-x',
3
- 'iphone-8',
4
- 'ipad-pro',
5
- 'imac-pro',
6
- 'macbook',
7
- 'macbook-pro',
8
- 'surface-pro',
9
- 'surface-book',
10
- 'surface-studio',
11
- 'galaxy-s8',
12
- 'google-pixel',
13
- 'google-pixel-2-xl',
14
- 'apple-watch',
15
- ];