image-beautifier 1.0.1 → 1.0.3
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/README.md +14 -0
- package/lib/image-beautifier.es.js +3040 -0
- package/lib/style.css +9 -0
- package/package.json +17 -3
- package/.eslintrc.cjs +0 -25
- package/favicon.svg +0 -20
- package/index.html +0 -13
- package/jsconfig.json +0 -12
- package/postcss.config.mjs +0 -6
- package/preview.png +0 -0
- package/public/vite.svg +0 -1
- package/src/App.jsx +0 -29
- package/src/assets/blur.svg +0 -8
- package/src/assets/color.svg +0 -1
- package/src/assets/demo.png +0 -0
- package/src/assets/pencil.png +0 -0
- package/src/assets/rotate.png +0 -0
- package/src/components/ColorPicker.jsx +0 -27
- package/src/components/Icon.jsx +0 -81
- package/src/components/editor/Editor.jsx +0 -19
- package/src/components/editor/HotKeys.jsx +0 -48
- package/src/components/editor/View.jsx +0 -167
- package/src/components/editor/Zoom.jsx +0 -54
- package/src/components/editor/layers/FrameBox.jsx +0 -51
- package/src/components/editor/layers/Screenshot.jsx +0 -89
- package/src/components/editor/layers/ShapeLine.jsx +0 -97
- package/src/components/editor/layers/Watermark.jsx +0 -41
- package/src/components/header/EmojiSelect.jsx +0 -35
- package/src/components/header/Header.jsx +0 -134
- package/src/components/header/Logo.jsx +0 -29
- package/src/components/header/MediaLogo.jsx +0 -10
- package/src/components/header/WidthDropdown.jsx +0 -74
- package/src/components/init/Init.jsx +0 -77
- package/src/components/sideBar/BackgroundSelect.jsx +0 -49
- package/src/components/sideBar/CropperImage.jsx +0 -73
- package/src/components/sideBar/CustomSize.jsx +0 -60
- package/src/components/sideBar/DownloadBar.jsx +0 -179
- package/src/components/sideBar/DrawerBar.jsx +0 -64
- package/src/components/sideBar/Position.jsx +0 -45
- package/src/components/sideBar/SideBar.jsx +0 -131
- package/src/components/sideBar/SizeBar.jsx +0 -114
- package/src/components/sideBar/Watermark.jsx +0 -59
- package/src/hooks/useKeyboardShortcuts.js +0 -28
- package/src/hooks/usePaste.js +0 -21
- package/src/index.js +0 -1
- package/src/main.jsx +0 -9
- package/src/stores/editor.js +0 -106
- package/src/stores/index.js +0 -7
- package/src/stores/option.js +0 -96
- package/src/style/main.css +0 -132
- package/src/utils/UndoRedoManager.js +0 -83
- package/src/utils/backgroundConfig.js +0 -387
- package/src/utils/captureScreen.js +0 -28
- package/src/utils/sizeConfig.js +0 -163
- package/src/utils/utils.js +0 -154
- package/tailwind.config.mjs +0 -15
- package/vite.config.js +0 -21
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { Children, cloneElement, useEffect, useMemo } from 'react';
|
|
2
|
-
import { Frame, Image } from 'leafer-ui';
|
|
3
|
-
import blurSvg from '@assets/blur.svg';
|
|
4
|
-
|
|
5
|
-
const childrenInjectProps = (params, children) => {
|
|
6
|
-
if (children instanceof Array) {
|
|
7
|
-
return children.map((child) => {
|
|
8
|
-
return Children.toArray(child).map((element) =>
|
|
9
|
-
cloneElement(element, { ...params })
|
|
10
|
-
);
|
|
11
|
-
});
|
|
12
|
-
} else {
|
|
13
|
-
const dom = Children.toArray(children).map((element) =>
|
|
14
|
-
cloneElement(element, { ...params })
|
|
15
|
-
);
|
|
16
|
-
return dom;
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export default ({ width, height, background, parent, children, cursor }) => {
|
|
21
|
-
const frame = useMemo(() => {
|
|
22
|
-
const fra = new Frame({
|
|
23
|
-
width,
|
|
24
|
-
height,
|
|
25
|
-
overflow: 'hide',
|
|
26
|
-
fill: background,
|
|
27
|
-
cursor: 'auto'
|
|
28
|
-
});
|
|
29
|
-
fra.name = 'frame';
|
|
30
|
-
return fra;
|
|
31
|
-
}, []);
|
|
32
|
-
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
frame.width = width;
|
|
35
|
-
frame.height = height;
|
|
36
|
-
frame.fill = background;
|
|
37
|
-
}, [width, height, background]);
|
|
38
|
-
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
frame.cursor = cursor || 'auto';
|
|
41
|
-
}, [cursor]);
|
|
42
|
-
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
parent.add(frame);
|
|
45
|
-
return () => {
|
|
46
|
-
frame.remove();
|
|
47
|
-
};
|
|
48
|
-
}, [parent]);
|
|
49
|
-
|
|
50
|
-
return <>{childrenInjectProps({ parent: frame }, children)}</>;
|
|
51
|
-
};
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo } from 'react';
|
|
2
|
-
import { observer } from 'mobx-react-lite';
|
|
3
|
-
import { Image, Box } from 'leafer-ui';
|
|
4
|
-
import stores from '@stores';
|
|
5
|
-
import { computedSize, getPosition } from '@utils/utils';
|
|
6
|
-
|
|
7
|
-
export default observer(({ parent }) => {
|
|
8
|
-
const [image, box] = useMemo(() => {
|
|
9
|
-
const image = new Image({
|
|
10
|
-
url: stores.editor.img.src,
|
|
11
|
-
origin: 'center'
|
|
12
|
-
});
|
|
13
|
-
const box = new Box({
|
|
14
|
-
overflow: 'hide',
|
|
15
|
-
scale: 1,
|
|
16
|
-
children: [image]
|
|
17
|
-
});
|
|
18
|
-
return [image, box];
|
|
19
|
-
}, [parent]);
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
if (stores.option.padding === 0) {
|
|
23
|
-
box.fill = '#ffffff00'
|
|
24
|
-
} else {
|
|
25
|
-
box.fill = stores.option.paddingBg;
|
|
26
|
-
}
|
|
27
|
-
}, [stores.option.paddingBg, stores.option.padding]);
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
box.cornerRadius = stores.option.round;
|
|
31
|
-
image.cornerRadius = stores.option.round - 2;
|
|
32
|
-
}, [stores.option.round]);
|
|
33
|
-
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
const { shadow } = stores.option;
|
|
36
|
-
if (shadow === 0) {
|
|
37
|
-
box.shadow = null;
|
|
38
|
-
} else {
|
|
39
|
-
box.shadow = {
|
|
40
|
-
x: shadow * 4,
|
|
41
|
-
y: shadow * 4,
|
|
42
|
-
blur: shadow * 3,
|
|
43
|
-
color: '#00000045',
|
|
44
|
-
box: true
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
}, [stores.option.shadow]);
|
|
48
|
-
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
box.scale = stores.option.scale;
|
|
51
|
-
}, [stores.option.scale]);
|
|
52
|
-
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
image.url = stores.editor.img.src;
|
|
55
|
-
}, [stores.editor.img.src]);
|
|
56
|
-
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
image.scaleX = stores.option.scaleX ? -1 : 1;
|
|
59
|
-
}, [stores.option.scaleX]);
|
|
60
|
-
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
image.scaleY = stores.option.scaleY ? -1 : 1;
|
|
63
|
-
}, [stores.option.scaleY]);
|
|
64
|
-
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
const margin = Math.round(stores.option.frameConf.width * 0.2);
|
|
67
|
-
const { width, height } = computedSize(stores.editor.img.width, stores.editor.img.height, stores.option.frameConf.width - margin, stores.option.frameConf.height - margin);
|
|
68
|
-
image.width = width - stores.option.padding;
|
|
69
|
-
image.height = height - stores.option.padding;
|
|
70
|
-
box.width = width;
|
|
71
|
-
box.height = height;
|
|
72
|
-
box.origin = stores.option.align;
|
|
73
|
-
const { x, y } = getPosition(stores.option.align, stores.option.frameConf.width - width, stores.option.frameConf.height - height);
|
|
74
|
-
box.x = x;
|
|
75
|
-
box.y = y;
|
|
76
|
-
if (stores.option.padding > 0) {
|
|
77
|
-
image.x = stores.option.padding / 2;
|
|
78
|
-
image.y = stores.option.padding / 2;
|
|
79
|
-
}
|
|
80
|
-
}, [stores.option.frameConf.width, stores.option.frameConf.height, stores.option.padding, stores.option.align]);
|
|
81
|
-
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
parent.add(box);
|
|
84
|
-
return (() => {
|
|
85
|
-
box.remove();
|
|
86
|
-
})
|
|
87
|
-
}, [parent]);
|
|
88
|
-
return null;
|
|
89
|
-
});
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo } from 'react';
|
|
2
|
-
import { Rect, Ellipse, Line, Text } from 'leafer-ui';
|
|
3
|
-
import { Arrow } from '@leafer-in/arrow';
|
|
4
|
-
|
|
5
|
-
export default ({ parent, type, id, width, height, x, y, fill, strokeWidth, zIndex, points, editable, text }) => {
|
|
6
|
-
const shape = useMemo(() => {
|
|
7
|
-
const defaultOption = { id, x, y, zIndex }
|
|
8
|
-
if (type === 'SquareFill') {
|
|
9
|
-
return new Rect({
|
|
10
|
-
cornerRadius: 8,
|
|
11
|
-
width,
|
|
12
|
-
height,
|
|
13
|
-
fill,
|
|
14
|
-
...defaultOption
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
if (type === 'Circle') {
|
|
18
|
-
return new Ellipse({
|
|
19
|
-
stroke: fill,
|
|
20
|
-
strokeWidth,
|
|
21
|
-
width,
|
|
22
|
-
height,
|
|
23
|
-
...defaultOption
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
if (type === 'Slash') {
|
|
27
|
-
return new Line({
|
|
28
|
-
id,
|
|
29
|
-
points,
|
|
30
|
-
zIndex,
|
|
31
|
-
stroke: fill,
|
|
32
|
-
strokeWidth
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
if (type === 'MoveDownLeft') {
|
|
36
|
-
return new Arrow({
|
|
37
|
-
id,
|
|
38
|
-
points,
|
|
39
|
-
zIndex,
|
|
40
|
-
strokeCap: 'round',
|
|
41
|
-
strokeJoin: 'round',
|
|
42
|
-
stroke: fill,
|
|
43
|
-
strokeWidth
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
if (type === 'Pencil') {
|
|
47
|
-
return new Line({
|
|
48
|
-
id,
|
|
49
|
-
points,
|
|
50
|
-
zIndex,
|
|
51
|
-
curve: true,
|
|
52
|
-
stroke: fill,
|
|
53
|
-
strokeWidth
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
if (type === 'emoji') {
|
|
57
|
-
return new Text({
|
|
58
|
-
id,
|
|
59
|
-
zIndex,
|
|
60
|
-
text,
|
|
61
|
-
resizeFontSize: true,
|
|
62
|
-
fontSize: 48
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
return new Rect({
|
|
66
|
-
cornerRadius: 8,
|
|
67
|
-
stroke: fill,
|
|
68
|
-
strokeWidth,
|
|
69
|
-
width,
|
|
70
|
-
height,
|
|
71
|
-
...defaultOption
|
|
72
|
-
});
|
|
73
|
-
}, [parent]);
|
|
74
|
-
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
if (['Slash', 'MoveDownLeft', 'Pencil'].includes(type)) {
|
|
77
|
-
shape.points = points;
|
|
78
|
-
} else {
|
|
79
|
-
shape.x = x;
|
|
80
|
-
shape.y = y;
|
|
81
|
-
shape.width = width;
|
|
82
|
-
shape.height = height;
|
|
83
|
-
}
|
|
84
|
-
}, [x, y, width, height]);
|
|
85
|
-
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
shape.editable = !!editable;
|
|
88
|
-
}, [editable]);
|
|
89
|
-
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
parent.add(shape);
|
|
92
|
-
return (() => {
|
|
93
|
-
shape.remove();
|
|
94
|
-
})
|
|
95
|
-
}, [parent]);
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo } from 'react';
|
|
2
|
-
import { observer } from 'mobx-react-lite';
|
|
3
|
-
import { Rect } from 'leafer-ui';
|
|
4
|
-
import stores from '@stores';
|
|
5
|
-
|
|
6
|
-
export default observer(({ parent }) => {
|
|
7
|
-
const rect = useMemo(() => {
|
|
8
|
-
const rect = new Rect({
|
|
9
|
-
x: 0,
|
|
10
|
-
y: 0,
|
|
11
|
-
});
|
|
12
|
-
return rect;
|
|
13
|
-
}, [parent]);
|
|
14
|
-
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
rect.width = stores.option.frameConf.width;
|
|
17
|
-
rect.height = stores.option.frameConf.height;
|
|
18
|
-
}, [stores.option.frameConf.width, stores.option.frameConf.height]);
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
rect.zIndex = stores.option.waterIndex;
|
|
22
|
-
}, [stores.option.waterIndex]);
|
|
23
|
-
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
rect.fill = {
|
|
26
|
-
type: 'image',
|
|
27
|
-
url: stores.option.waterSvg,
|
|
28
|
-
mode: 'repeat',
|
|
29
|
-
format: 'svg',
|
|
30
|
-
size: Math.round(stores.option.frameConf.width / 6)
|
|
31
|
-
};
|
|
32
|
-
}, [stores.option.waterSvg, stores.option.frameConf.width]);
|
|
33
|
-
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
parent.add(rect);
|
|
36
|
-
return (() => {
|
|
37
|
-
rect.remove();
|
|
38
|
-
});
|
|
39
|
-
}, []);
|
|
40
|
-
return null;
|
|
41
|
-
});
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { Button, Popover } from 'antd';
|
|
3
|
-
import Icon from '@components/Icon';
|
|
4
|
-
import data from '@emoji-mart/data'
|
|
5
|
-
import Picker from '@emoji-mart/react'
|
|
6
|
-
|
|
7
|
-
export default ({disabled = false, toSelect, locale='en'}) => {
|
|
8
|
-
const [open, setOpen] = useState(false);
|
|
9
|
-
const hide = () => {
|
|
10
|
-
setOpen(false);
|
|
11
|
-
};
|
|
12
|
-
const handleOpenChange = (newOpen) => {
|
|
13
|
-
setOpen(newOpen);
|
|
14
|
-
};
|
|
15
|
-
const onEmojiSelect = (e) => {
|
|
16
|
-
toSelect(e.native);
|
|
17
|
-
hide();
|
|
18
|
-
}
|
|
19
|
-
return (
|
|
20
|
-
<Popover
|
|
21
|
-
content={<Picker data={data} locale={locale} onEmojiSelect={onEmojiSelect} previewPosition='none' />}
|
|
22
|
-
title=""
|
|
23
|
-
trigger="click"
|
|
24
|
-
open={open}
|
|
25
|
-
onOpenChange={handleOpenChange}
|
|
26
|
-
>
|
|
27
|
-
<Button
|
|
28
|
-
type="text"
|
|
29
|
-
shape="circle"
|
|
30
|
-
disabled={disabled}
|
|
31
|
-
icon={<Icon.Smile size={16} />}
|
|
32
|
-
></Button>
|
|
33
|
-
</Popover>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { observer } from 'mobx-react-lite';
|
|
3
|
-
import { Button, Divider, Tooltip } from 'antd';
|
|
4
|
-
import { icons } from 'lucide-react';
|
|
5
|
-
import Icon from '@components/Icon';
|
|
6
|
-
import ColorPicker from '@components/ColorPicker';
|
|
7
|
-
import { WidthDropdown } from '@components/header/WidthDropdown'
|
|
8
|
-
import { nanoid } from '@utils/utils';
|
|
9
|
-
import stores from '@stores';
|
|
10
|
-
import EmojiSelect from './EmojiSelect';
|
|
11
|
-
import Logo from './Logo';
|
|
12
|
-
import MediaLogo from './MediaLogo';
|
|
13
|
-
|
|
14
|
-
const toolList = ['Square', 'SquareFill', 'Circle', 'Slash', 'MoveDownLeft', 'Pencil', 'Smile'];
|
|
15
|
-
|
|
16
|
-
export default observer(() => {
|
|
17
|
-
const [isMove, setIsMove] = useState(false);
|
|
18
|
-
// const handleUndo = () => {
|
|
19
|
-
// console.log(JSON.stringify(stores.editor.app.toJSON()))
|
|
20
|
-
// };
|
|
21
|
-
const selectTool = (type) => {
|
|
22
|
-
if (!stores.editor.isEditing) return;
|
|
23
|
-
const { useTool } = stores.editor;
|
|
24
|
-
stores.editor.setUseTool(useTool === type ? null : type);
|
|
25
|
-
setIsMove(false);
|
|
26
|
-
}
|
|
27
|
-
const handleSelectEmoji = (emoji) => {
|
|
28
|
-
if (!stores.editor.isEditing) return;
|
|
29
|
-
const x = stores.option.frameConf.width / 2 - 24;
|
|
30
|
-
const y = stores.option.frameConf.height / 2 - 24;
|
|
31
|
-
stores.editor.setUseTool(null);
|
|
32
|
-
setIsMove(false);
|
|
33
|
-
stores.editor.addShape({
|
|
34
|
-
id: nanoid(),
|
|
35
|
-
type: 'emoji',
|
|
36
|
-
text: emoji,
|
|
37
|
-
zIndex: stores.editor.shapes.size + 1,
|
|
38
|
-
x,
|
|
39
|
-
y,
|
|
40
|
-
editable: true
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
const toggleMove = () => {
|
|
44
|
-
if (!stores.editor.isEditing) return;
|
|
45
|
-
const is = !isMove;
|
|
46
|
-
stores.editor.setUseTool(null);
|
|
47
|
-
setIsMove(is);
|
|
48
|
-
stores.editor.app.config.move.drag = is;
|
|
49
|
-
}
|
|
50
|
-
return (
|
|
51
|
-
<div className='flex items-center justify-center shrink-0 gap-3 bg-white py-2 px-5 border-b border-b-gray-50 shadow-sm relative z-[11] select-none'>
|
|
52
|
-
<div className="flex-1">
|
|
53
|
-
<Logo />
|
|
54
|
-
</div>
|
|
55
|
-
{/* Todo */}
|
|
56
|
-
{/* <div className='flex gap-1 justify-center items-center'>
|
|
57
|
-
<Tooltip placement='bottom' arrow={false} title='Undo'>
|
|
58
|
-
<Button
|
|
59
|
-
type='text'
|
|
60
|
-
shape='circle'
|
|
61
|
-
icon={<Icon.Undo size={16} />}
|
|
62
|
-
onClick={handleUndo}
|
|
63
|
-
></Button>
|
|
64
|
-
</Tooltip>
|
|
65
|
-
<Tooltip placement='bottom' arrow={false} title='Redo'>
|
|
66
|
-
<Button
|
|
67
|
-
type='text'
|
|
68
|
-
shape='circle'
|
|
69
|
-
icon={<Icon.Redo size={16} />}
|
|
70
|
-
></Button>
|
|
71
|
-
</Tooltip>
|
|
72
|
-
</div>
|
|
73
|
-
<Divider type='vertical' /> */}
|
|
74
|
-
<div className='flex gap-1 justify-center items-center'>
|
|
75
|
-
{toolList.map(item => {
|
|
76
|
-
if (item === 'Smile') return (<EmojiSelect key={item} disabled={false} toSelect={handleSelectEmoji} />)
|
|
77
|
-
let icon;
|
|
78
|
-
if (item.includes('Fill')) {
|
|
79
|
-
const type = item.replace('Fill', '');
|
|
80
|
-
const Icons = icons[type];
|
|
81
|
-
icon = <Icons size={16} fill='currentColor' />;
|
|
82
|
-
} else {
|
|
83
|
-
const Icons = icons[item];
|
|
84
|
-
icon = <Icons name={item} size={16} />;
|
|
85
|
-
}
|
|
86
|
-
return (
|
|
87
|
-
<Button
|
|
88
|
-
key={item}
|
|
89
|
-
type='text'
|
|
90
|
-
shape='circle'
|
|
91
|
-
icon={icon}
|
|
92
|
-
className={stores.editor.useTool === item && 'text-[#1677ff] bg-sky-100/50 hover:bg-sky-100 hover:text-[#1677ff]'}
|
|
93
|
-
onClick={() => selectTool(item)}
|
|
94
|
-
/>
|
|
95
|
-
)
|
|
96
|
-
})}
|
|
97
|
-
</div>
|
|
98
|
-
<Divider type='vertical' />
|
|
99
|
-
<div className='flex gap-1 justify-center items-center'>
|
|
100
|
-
<ColorPicker
|
|
101
|
-
size='small'
|
|
102
|
-
presets={[
|
|
103
|
-
{
|
|
104
|
-
label: 'Recommended',
|
|
105
|
-
colors: [
|
|
106
|
-
'#ffffff',
|
|
107
|
-
'#444444',
|
|
108
|
-
'#df4b26',
|
|
109
|
-
'#1677ff',
|
|
110
|
-
'#52C41A',
|
|
111
|
-
'#FA8C16',
|
|
112
|
-
'#FADB14',
|
|
113
|
-
'#EB2F96',
|
|
114
|
-
'#722ED1',
|
|
115
|
-
],
|
|
116
|
-
},
|
|
117
|
-
]}
|
|
118
|
-
value={stores.editor.annotateColor}
|
|
119
|
-
onChange={(e) => stores.editor.setAnnotateColor(e.toHexString())}
|
|
120
|
-
/>
|
|
121
|
-
<WidthDropdown defaultValue={stores.editor.strokeWidth} onChange={(e) => stores.editor.setStrokeWidth(e)} />
|
|
122
|
-
<Button
|
|
123
|
-
type="text"
|
|
124
|
-
shape='circle'
|
|
125
|
-
className={isMove && 'text-[#1677ff] bg-sky-100/50 hover:bg-sky-100 hover:text-[#1677ff]'}
|
|
126
|
-
icon={<Icon.Hand size={16} />}
|
|
127
|
-
onClick={toggleMove}
|
|
128
|
-
/>
|
|
129
|
-
</div>
|
|
130
|
-
{/* <Divider type='vertical' /> */}
|
|
131
|
-
<MediaLogo />
|
|
132
|
-
</div>
|
|
133
|
-
);
|
|
134
|
-
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export default () => {
|
|
2
|
-
return (
|
|
3
|
-
<h1>
|
|
4
|
-
<a href="https://shoteasy.fun" target="_blank" aria-label="ShotEasy" className="flex gap-1 text-gray-700 items-center text-xs font-semibold">
|
|
5
|
-
<svg width="20" height="20" viewBox="0 0 510 510" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
6
|
-
<g filter="url(#filter0_i_40_60)">
|
|
7
|
-
<path d="M60.1774 83.172C64.1232 61.4358 84.9426 47.0139 106.679 50.9598L271.777 80.9308C293.513 84.8767 307.935 105.696 303.989 127.432L247.462 438.817C243.516 460.553 222.696 474.975 200.96 471.029L35.8623 441.058C14.1261 437.112 -0.295702 416.293 3.65017 394.557L60.1774 83.172Z" fill="#812BED" />
|
|
8
|
-
<path d="M179.201 136.846C173.053 115.627 185.269 93.4418 206.488 87.2932L367.654 40.5909C388.872 34.4423 411.058 46.6588 417.206 67.8773L505.29 371.846C511.438 393.065 499.222 415.25 478.003 421.399L316.837 468.101C295.619 474.25 273.433 462.033 267.285 440.815L179.201 136.846Z" fill="#0DB7FF" />
|
|
9
|
-
<path d="M206.488 87.2932C185.269 93.4418 173.053 115.627 179.201 136.846L254.874 397.986L303.989 127.432C307.935 105.696 293.513 84.8767 271.777 80.9308L245.134 76.0943L206.488 87.2932Z" fill="#EC83FD" />
|
|
10
|
-
<path d="M401 404C401 416.703 390.703 427 378 427C365.297 427 355 416.703 355 404C355 391.297 365.297 381 378 381C390.703 381 401 391.297 401 404Z" fill="#0D97D3" />
|
|
11
|
-
<path d="M144 404C144 416.703 133.703 427 121 427C108.297 427 98 416.703 98 404C98 391.297 108.297 381 121 381C133.703 381 144 391.297 144 404Z" fill="#6616CB" />
|
|
12
|
-
</g>
|
|
13
|
-
<defs>
|
|
14
|
-
<filter id="filter0_i_40_60" x="3" y="39" width="503.881" height="432.679" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
|
15
|
-
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
|
16
|
-
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
|
17
|
-
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
|
18
|
-
<feOffset dx="-2" />
|
|
19
|
-
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
|
20
|
-
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.7 0" />
|
|
21
|
-
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_40_60" />
|
|
22
|
-
</filter>
|
|
23
|
-
</defs>
|
|
24
|
-
</svg>
|
|
25
|
-
<span>ShotEasy</span>
|
|
26
|
-
</a>
|
|
27
|
-
</h1>
|
|
28
|
-
)
|
|
29
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export default () => (
|
|
2
|
-
<div className="flex-1 flex justify-end items-end gap-2">
|
|
3
|
-
<a href="https://github.com/CH563/image-beautifier" target="_blank" aria-label="GitHub" className="block rounded-full p-1.5 hover:bg-slate-100">
|
|
4
|
-
<svg viewBox="64 64 896 896" focusable="false" data-icon="github" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9a127.5 127.5 0 0138.1 91v112.5c.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z"></path></svg>
|
|
5
|
-
</a>
|
|
6
|
-
<a href="https://twitter.com/LiWen563" target="_blank" aria-label="twitter:@LiWen563" className="block rounded-full p-1.5 hover:bg-slate-100">
|
|
7
|
-
<svg fill="currentColor" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><g><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"></path></g></svg>
|
|
8
|
-
</a>
|
|
9
|
-
</div>
|
|
10
|
-
)
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
-
import { Dropdown, Button } from 'antd';
|
|
3
|
-
import Icon from '@components/Icon';
|
|
4
|
-
|
|
5
|
-
const items = [
|
|
6
|
-
{
|
|
7
|
-
label: (
|
|
8
|
-
<div className='w-4 h-3 mt-1.5 items-center cursor-pointer'>
|
|
9
|
-
<i className='block w-full origin-center rounded-lg bg-slate-600 -rotate-45 h-[1px]'></i>
|
|
10
|
-
</div>
|
|
11
|
-
),
|
|
12
|
-
key: 1,
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
label: (
|
|
16
|
-
<div className='w-4 h-3 mt-1.5 items-center cursor-pointer'>
|
|
17
|
-
<i className='block w-full origin-center rounded-lg bg-slate-600 -rotate-45 h-[2px]'></i>
|
|
18
|
-
</div>
|
|
19
|
-
),
|
|
20
|
-
key: 2,
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
label: (
|
|
24
|
-
<div className='w-4 h-3 mt-1.5 items-center cursor-pointer'>
|
|
25
|
-
<i className='block w-full origin-center rounded-lg bg-slate-600 -rotate-45 h-[4px]'></i>
|
|
26
|
-
</div>
|
|
27
|
-
),
|
|
28
|
-
key: 4,
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
label: (
|
|
32
|
-
<div className='w-4 h-3 mt-1.5 items-center cursor-pointer'>
|
|
33
|
-
<i className='block w-full origin-center rounded-lg bg-slate-600 -rotate-45 h-[6px]'></i>
|
|
34
|
-
</div>
|
|
35
|
-
),
|
|
36
|
-
key: 6,
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
label: (
|
|
40
|
-
<div className='w-4 h-3 mt-1.5 items-center cursor-pointer'>
|
|
41
|
-
<i className='block w-full origin-center rounded-lg bg-slate-600 -rotate-45 h-[8px]'></i>
|
|
42
|
-
</div>
|
|
43
|
-
),
|
|
44
|
-
key: 8,
|
|
45
|
-
},
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
export const WidthDropdown = ({ defaultValue, onChange }) => {
|
|
49
|
-
const handleClick = ({ key }) => {
|
|
50
|
-
onChange(key);
|
|
51
|
-
};
|
|
52
|
-
return (
|
|
53
|
-
<Dropdown
|
|
54
|
-
menu={{ items, onClick: handleClick, selectedKeys: [defaultValue] }}
|
|
55
|
-
trigger={['click']}
|
|
56
|
-
placement='bottom'
|
|
57
|
-
>
|
|
58
|
-
<Button
|
|
59
|
-
type='text'
|
|
60
|
-
shape='circle'
|
|
61
|
-
className='px-1.5 py-0 flex items-center justify-center'
|
|
62
|
-
>
|
|
63
|
-
<div className='w-4 h-4 flex items-center cursor-pointer'>
|
|
64
|
-
<i
|
|
65
|
-
className='block w-full origin-center rounded-lg bg-slate-600 -rotate-45'
|
|
66
|
-
style={{
|
|
67
|
-
height: defaultValue + 'px',
|
|
68
|
-
}}
|
|
69
|
-
></i>
|
|
70
|
-
</div>
|
|
71
|
-
</Button>
|
|
72
|
-
</Dropdown>
|
|
73
|
-
);
|
|
74
|
-
};
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { observer } from 'mobx-react-lite';
|
|
2
|
-
import Icon from '@components/Icon';
|
|
3
|
-
import { Upload, Button, Tooltip } from 'antd';
|
|
4
|
-
import { supportImg, getImage, cn } from '@utils/utils';
|
|
5
|
-
import stores from '@stores';
|
|
6
|
-
import usePaste from '@hooks/usePaste';
|
|
7
|
-
import { captureScreen } from '@utils/captureScreen';
|
|
8
|
-
|
|
9
|
-
const { Dragger } = Upload;
|
|
10
|
-
|
|
11
|
-
export default observer(() => {
|
|
12
|
-
const getFile = async (file, type = 'blob') => {
|
|
13
|
-
const DOMURL = window.URL || window.webkitURL || window;
|
|
14
|
-
const imgUrl = type === 'blob' ? DOMURL.createObjectURL(file) : file;
|
|
15
|
-
const image = await getImage(imgUrl);
|
|
16
|
-
const width = Math.round(image.width);
|
|
17
|
-
const height = Math.round(image.height);
|
|
18
|
-
stores.editor.setImg({
|
|
19
|
-
src: imgUrl,
|
|
20
|
-
width,
|
|
21
|
-
height,
|
|
22
|
-
type: type === 'blob' ? file.type : 'image/png',
|
|
23
|
-
name: type === 'blob' ? file.name : 'ShotEasy.png'
|
|
24
|
-
});
|
|
25
|
-
if (stores.option.size.type === 'auto') {
|
|
26
|
-
const margin = Math.round(width * 0.2);
|
|
27
|
-
stores.option.setFrameSize(width + margin, height + margin);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
const beforeUpload = async (file) => {
|
|
31
|
-
await getFile(file);
|
|
32
|
-
return Promise.reject();
|
|
33
|
-
}
|
|
34
|
-
const onCapture = async () => {
|
|
35
|
-
const dataURL = await captureScreen();
|
|
36
|
-
if (!dataURL) return;
|
|
37
|
-
getFile(dataURL, 'dataURL');
|
|
38
|
-
}
|
|
39
|
-
const comingSoon = () => {
|
|
40
|
-
stores.editor.message.info('Developing, Coming soon!');
|
|
41
|
-
}
|
|
42
|
-
usePaste((file) => {
|
|
43
|
-
getFile(file);
|
|
44
|
-
});
|
|
45
|
-
return (
|
|
46
|
-
<div className='md:w-0 md:flex-1 flex flex-col justify-center items-center overflow-hidden select-none relative'>
|
|
47
|
-
<div className={cn('max-w-[600px]', stores.editor.invalid && 'invalid')}>
|
|
48
|
-
<Dragger
|
|
49
|
-
accept={supportImg.join(',')}
|
|
50
|
-
name="file"
|
|
51
|
-
showUploadList={false}
|
|
52
|
-
beforeUpload={beforeUpload}
|
|
53
|
-
rootClassName="p-4 rounded-md bg-white block shadow-sm"
|
|
54
|
-
>
|
|
55
|
-
<div className="text-center p-10">
|
|
56
|
-
<p className="text-2xl my-2 opacity-60"><Icon.ImagePlus className="mx-auto" size={36} /></p>
|
|
57
|
-
<p className="text-sm px-4">Click or Drag image to this area<br/>or Paste image</p>
|
|
58
|
-
</div>
|
|
59
|
-
</Dragger>
|
|
60
|
-
<div className="flex justify-between mt-4 py-4 px-6 rounded-md bg-white shadow-sm">
|
|
61
|
-
<Tooltip placement='top' arrow={false} title='Take a screenshot of desktop windows'>
|
|
62
|
-
<Button shape="round" type="default" size="large" icon={<Icon.Camera size={20} />} onClick={onCapture} />
|
|
63
|
-
</Tooltip>
|
|
64
|
-
<Tooltip placement='top' arrow={false} title='Beautify text'>
|
|
65
|
-
<Button shape="round" type="default" size="large" icon={<Icon.Type size={20} />} onClick={comingSoon} />
|
|
66
|
-
</Tooltip>
|
|
67
|
-
<Tooltip placement='top' arrow={false} title='Beautify Code'>
|
|
68
|
-
<Button shape="round" type="default" size="large" icon={<Icon.CodeXml size={20} onClick={comingSoon} />} />
|
|
69
|
-
</Tooltip>
|
|
70
|
-
<Tooltip placement='top' arrow={false} title='Create gif animate'>
|
|
71
|
-
<Button shape="round" type="default" size="large" icon={<Icon.ImagePlay size={20} onClick={comingSoon} />} />
|
|
72
|
-
</Tooltip>
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
)
|
|
77
|
-
});
|