@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.
- package/lib/main/hooks/medium-zoom.d.ts +5 -1
- package/lib/main/hooks/medium-zoom.js +6 -2
- package/lib/main/nodes/ImageComponent.d.ts +1 -2
- package/lib/main/nodes/ImageComponent.js +68 -6
- package/lib/main/nodes/ImageNode.d.ts +5 -6
- package/lib/main/ui/FrameMockup/browser.css +52 -0
- package/lib/main/ui/FrameMockup/browser.d.ts +13 -0
- package/lib/main/ui/FrameMockup/browser.js +22 -0
- package/lib/main/ui/FrameMockup/index.d.ts +115 -0
- package/lib/main/ui/FrameMockup/index.js +60 -0
- package/lib/main/ui/ImageEnhancer.d.ts +7 -5
- package/lib/main/ui/ImageEnhancer.js +14 -8
- package/lib/main/ui/ImageResizer.js +1 -5
- package/package.json +3 -3
- package/lib/main/utils/device-frame.d.ts +0 -2
- package/lib/main/utils/device-frame.js +0 -15
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import 'medium-zoom/dist/style.css';
|
|
2
|
-
|
|
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?:
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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):
|
|
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
|
-
|
|
24
|
+
frame?: string;
|
|
23
25
|
onSizeModeChange: (sizeMode: ImageSizeMode) => void;
|
|
24
|
-
onFrameChange: (
|
|
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,
|
|
3
|
-
import { useEffect, useRef
|
|
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
|
|
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
|
-
|
|
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-
|
|
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.
|
|
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.
|
|
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": "
|
|
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
|
-
];
|