image-beautifier 1.0.1 → 1.0.2

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.
Files changed (56) hide show
  1. package/lib/image-beautifier.es.js +2852 -0
  2. package/lib/style.css +9 -0
  3. package/package.json +11 -3
  4. package/.eslintrc.cjs +0 -25
  5. package/favicon.svg +0 -20
  6. package/index.html +0 -13
  7. package/jsconfig.json +0 -12
  8. package/postcss.config.mjs +0 -6
  9. package/preview.png +0 -0
  10. package/public/vite.svg +0 -1
  11. package/src/App.jsx +0 -29
  12. package/src/assets/blur.svg +0 -8
  13. package/src/assets/color.svg +0 -1
  14. package/src/assets/demo.png +0 -0
  15. package/src/assets/pencil.png +0 -0
  16. package/src/assets/rotate.png +0 -0
  17. package/src/components/ColorPicker.jsx +0 -27
  18. package/src/components/Icon.jsx +0 -81
  19. package/src/components/editor/Editor.jsx +0 -19
  20. package/src/components/editor/HotKeys.jsx +0 -48
  21. package/src/components/editor/View.jsx +0 -167
  22. package/src/components/editor/Zoom.jsx +0 -54
  23. package/src/components/editor/layers/FrameBox.jsx +0 -51
  24. package/src/components/editor/layers/Screenshot.jsx +0 -89
  25. package/src/components/editor/layers/ShapeLine.jsx +0 -97
  26. package/src/components/editor/layers/Watermark.jsx +0 -41
  27. package/src/components/header/EmojiSelect.jsx +0 -35
  28. package/src/components/header/Header.jsx +0 -134
  29. package/src/components/header/Logo.jsx +0 -29
  30. package/src/components/header/MediaLogo.jsx +0 -10
  31. package/src/components/header/WidthDropdown.jsx +0 -74
  32. package/src/components/init/Init.jsx +0 -77
  33. package/src/components/sideBar/BackgroundSelect.jsx +0 -49
  34. package/src/components/sideBar/CropperImage.jsx +0 -73
  35. package/src/components/sideBar/CustomSize.jsx +0 -60
  36. package/src/components/sideBar/DownloadBar.jsx +0 -179
  37. package/src/components/sideBar/DrawerBar.jsx +0 -64
  38. package/src/components/sideBar/Position.jsx +0 -45
  39. package/src/components/sideBar/SideBar.jsx +0 -131
  40. package/src/components/sideBar/SizeBar.jsx +0 -114
  41. package/src/components/sideBar/Watermark.jsx +0 -59
  42. package/src/hooks/useKeyboardShortcuts.js +0 -28
  43. package/src/hooks/usePaste.js +0 -21
  44. package/src/index.js +0 -1
  45. package/src/main.jsx +0 -9
  46. package/src/stores/editor.js +0 -106
  47. package/src/stores/index.js +0 -7
  48. package/src/stores/option.js +0 -96
  49. package/src/style/main.css +0 -132
  50. package/src/utils/UndoRedoManager.js +0 -83
  51. package/src/utils/backgroundConfig.js +0 -387
  52. package/src/utils/captureScreen.js +0 -28
  53. package/src/utils/sizeConfig.js +0 -163
  54. package/src/utils/utils.js +0 -154
  55. package/tailwind.config.mjs +0 -15
  56. package/vite.config.js +0 -21
@@ -1,114 +0,0 @@
1
- import { useState, useRef } from 'react';
2
- import { observer } from 'mobx-react-lite';
3
- import Icon from '@components/Icon';
4
- import { Popover, Button } from 'antd';
5
- import stores from '@stores';
6
- import { cn } from '@utils/utils';
7
- import sizeConfig from '@utils/sizeConfig';
8
- import CustomSize from './CustomSize';
9
-
10
-
11
- export default observer(() => {
12
- const box = useRef(null);
13
- const [open, setOpen] = useState(false);
14
- const [height, setHeight] = useState(500);
15
- const hide = () => {
16
- setOpen(false);
17
- };
18
- const handleOpenChange = (newOpen) => {
19
- setOpen(newOpen);
20
- if (newOpen && box.current) {
21
- const { height, y } = box.current.getBoundingClientRect();
22
- const h = document.body.clientHeight - height - y - 80;
23
- setHeight(h);
24
- }
25
- };
26
- const checkSelected = (key, item) => {
27
- if (key !== stores.option.size.type) return false;
28
- if (item.height !== stores.option.frameConf.height) return false;
29
- if (item.width !== stores.option.frameConf.width) return false;
30
- return true;
31
- }
32
- const onSet = (value) => {
33
- hide();
34
- if (value.type === 'auto' && stores.editor.img.width) {
35
- const margin = Math.round(stores.editor.img.width * 0.2);
36
- stores.option.setSize({...value, width: stores.editor.img.width + margin, height: stores.editor.img.height + margin});
37
- return;
38
- }
39
- stores.option.setSize(value);
40
- };
41
- const toSelected = (key, title, item) => {
42
- hide();
43
- stores.option.setSize({
44
- type: key,
45
- title: `${ title }${ item.title ? ` ${item.title} `:' ' }${ item.w } : ${ item.h }`,
46
- width: item.width,
47
- height: item.height
48
- });
49
- }
50
- const isShowSize = stores.editor.img?.src || stores.option.size.type !== 'auto';
51
- const title = <CustomSize type={stores.option.size.type} frameWidth={stores.option.frameConf.width} frameHeight={stores.option.frameConf.height} onSet={onSet} />;
52
- const content = (
53
- <div className="border-t border-gray-200 py-2 divide-y">
54
- {sizeConfig.map(item => (
55
- <div key={item.key}>
56
- {item.key !== 'default' && <div className="font-semibold pt-2">{item.title}</div>}
57
- <section className="flex flex-wrap items-end pb-2">
58
- {item.lists.map((child, index) => (
59
- <Button
60
- key={index}
61
- type="text"
62
- className="flex-[33%] p-3 h-auto flex-col gap-0 disabled:bg-blue-500/5 disabled:border-blue-500 disabled:cursor-default disabled:text-black"
63
- disabled={checkSelected(item.key, child)}
64
- onClick={() => toSelected(item.key, item.title, child)}>
65
- <div className="py-2 px-3 w-full">
66
- <div
67
- className='border border-black/50 bg-black/10 w-full flex items-center justify-center rounded-md opacity-75'
68
- style={{ aspectRatio: child.w / child.h }}
69
- ><span>{child.w} : {child.h}</span></div>
70
- </div>
71
- {child.title && <div className="text-xs">{child.title}</div>}
72
- <div className="text-xs overflow-hidden text-gray-500">{child.width} x {child.height}</div>
73
- </Button>
74
- ))}
75
- </section>
76
- </div>
77
- ))}
78
- </div>
79
- );
80
- return (
81
- <Popover
82
- content={content}
83
- title={title}
84
- trigger='click'
85
- arrow={false}
86
- placement="bottomRight"
87
- open={open}
88
- overlayClassName="[&_.ant-popover-inner]:h-full [&_.ant-popover-inner]:overflow-x-hidden [&_.ant-popover-inner]:overflow-y-auto [&_.ant-popover-content]:h-full"
89
- overlayStyle={{
90
- width: '400px',
91
- height: `${height}px`
92
- }}
93
- onOpenChange={handleOpenChange}
94
- >
95
- <div className={cn('px-3 py-1.5 border shrink-0 border-gray-200 gap-3 shadow-sm overflow-hidden max-h-12 rounded-md hover:border-blue-500 [&_svg]:hover:text-blue-500 cursor-pointer flex items-center', open && 'shadow-md')} ref={box}>
96
- <div
97
- className='border border-black/50 bg-black/10 w-4 rounded-sm'
98
- style={{ aspectRatio: stores.option.frameConf.width / stores.option.frameConf.height }}
99
- />
100
- <div className='text-xs'>
101
- <div className='font-semibold leading-3 mb-0.5'>{stores.option.size.title}</div>
102
- {!isShowSize ?
103
- <div className='text-gray-500 leading-3'>Adaptive screenshot size</div> :
104
- <div className='text-gray-500 leading-3'>
105
- {stores.option.frameConf.width} x {stores.option.frameConf.height} px
106
- </div>
107
- }
108
- </div>
109
- <div className='flex-1'></div>
110
- {open ? <Icon.ChevronUp size={16} /> : <Icon.ChevronDown size={16} />}
111
- </div>
112
- </Popover>
113
- );
114
- });
@@ -1,59 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import { observer } from 'mobx-react-lite';
3
- import Icon from '@components/Icon';
4
- import { Radio, Switch, Input } from 'antd';
5
- import ColorPicker from '@components/ColorPicker';
6
- import stores from '@stores';
7
- import { text2Svg } from '@utils/utils';
8
-
9
- export default observer(() => {
10
- const [useWater, setUseWater] = useState(false);
11
- const [waterCont, setWaterCont] = useState('ShotEasy');
12
- const [waterColor, setWaterColor] = useState('#00000030');
13
- const [direction, setDirection] = useState(45);
14
- const handleColorChange = (color) => {
15
- setWaterColor(typeof color === 'string' ? color : color.toRgbString());
16
- };
17
- useEffect(() => {
18
- if (useWater && waterCont.trim()) {
19
- const svgImg = text2Svg({
20
- text: waterCont,
21
- color: waterColor,
22
- angleDegrees: direction
23
- });
24
- stores.option.setWaterImg(svgImg);
25
- } else {
26
- stores.option.setWaterImg(null);
27
- }
28
- }, [useWater, waterCont, waterColor, direction]);
29
- return (
30
- <>
31
- <div className="[&_label]:font-semibold [&_label]:text-sm flex gap-4 items-center justify-between">
32
- <label>Watermark</label>
33
- <Switch defaultChecked={useWater} onChange={setUseWater} size="small" className="bg-slate-200" />
34
- </div>
35
- {useWater &&
36
- <div className="[&_label]:font-semibold [&_label]:text-xs grid gap-3 pl-2 pt-2">
37
- <Input defaultValue={waterCont} placeholder="Watermark content" onChange={(e) => setWaterCont(e.target.value)} />
38
- <div className="flex items-center justify-between">
39
- <label>Color</label>
40
- <ColorPicker value={waterColor} onChange={handleColorChange} size="small" />
41
- </div>
42
- <div className="flex items-center justify-between">
43
- <label>Direction</label>
44
- <div>
45
- <Radio.Group defaultValue={direction} onChange={(e) => setDirection(e.target.value)} size="small">
46
- <Radio.Button value={-45}><Icon.ArrowUpRight size={16} className="mt-[3px]" /></Radio.Button>
47
- <Radio.Button value={45}><Icon.ArrowDownRight size={16} className="mt-[3px]" /></Radio.Button>
48
- </Radio.Group>
49
- </div>
50
- </div>
51
- <div className="flex items-center justify-between">
52
- <label>Only Background</label>
53
- <Switch size="small" onChange={(checked) => stores.option.setWaterIndex(checked ? -1 : 1)} className="bg-slate-200" />
54
- </div>
55
- </div>
56
- }
57
- </>
58
- )
59
- });
@@ -1,28 +0,0 @@
1
- import { useEffect, useRef } from "react"
2
- import { tinykeys } from "tinykeys"
3
-
4
- export default function useKeyboardShortcuts(toSave, toCopy, dependencies) {
5
- const save = useRef(toSave);
6
- const copy = useRef(toCopy);
7
-
8
- useEffect(() => {
9
- save.current = toSave;
10
- copy.current = toCopy;
11
- }, [...dependencies]);
12
-
13
- useEffect(() => {
14
- const unsubscribe = tinykeys(window, {
15
- "$mod+KeyS": event => {
16
- event.preventDefault()
17
- save.current && save.current();
18
- },
19
- "$mod+KeyC": event => {
20
- event.preventDefault()
21
- copy && copy.current();
22
- }
23
- })
24
- return () => {
25
- unsubscribe();
26
- }
27
- }, [window]);
28
- };
@@ -1,21 +0,0 @@
1
- import { useEffect } from 'react';
2
- import { supportImg } from '@utils/utils';
3
-
4
- export default (toPaste, dependencies = []) => {
5
- useEffect(() => {
6
- const getPaste = async (e) => {
7
- const data = e.clipboardData;
8
- if (!data || !data.items) return;
9
- const items = Array.from(data.items).filter((e) =>
10
- supportImg.includes(e.type)
11
- );
12
- if (!items.length) return;
13
- const file = items[0].getAsFile();
14
- toPaste && toPaste(file);
15
- };
16
- document.addEventListener('paste', getPaste, false);
17
- return () => {
18
- document.removeEventListener('paste', getPaste);
19
- };
20
- }, [...dependencies]);
21
- }
package/src/index.js DELETED
@@ -1 +0,0 @@
1
- export { default as ImageBeautifier } from './App';
package/src/main.jsx DELETED
@@ -1,9 +0,0 @@
1
- import React from 'react';
2
- import ReactDOM from 'react-dom/client';
3
- import App from './App.jsx';
4
-
5
- ReactDOM.createRoot(document.getElementById('root')).render(
6
- <React.StrictMode>
7
- <App />
8
- </React.StrictMode>
9
- );
@@ -1,106 +0,0 @@
1
- import { makeAutoObservable, toJS, action } from 'mobx';
2
- import demoPng from '@assets/demo.png'
3
- let timer;
4
- class Editor {
5
- img = {};
6
- invalid = false;
7
- app = null;
8
- scale = 100;
9
- useTool = null;
10
- annotateColor = '#ff0000';
11
- strokeWidth = 4;
12
- shapes = new Map();
13
- message = null;
14
- constructor () {
15
- makeAutoObservable(this)
16
- }
17
-
18
- get shapesList() {
19
- return Array.from(toJS(this.shapes).values());
20
- }
21
-
22
- get cursor() {
23
- return this.useTool === 'Pencil' ? 'pencil' : this.useTool ? 'crosshair' : 'auto'
24
- }
25
-
26
- get isEditing() {
27
- const is = !!this.app?.tree;
28
- if (!is) {
29
- this.message.info('Please add a image');
30
- this.setInvalid();
31
- }
32
- return is;
33
- }
34
-
35
- setInvalid() {
36
- clearTimeout(timer);
37
- this.invalid = true;
38
- timer = setTimeout(action(() => {
39
- this.invalid = false;
40
- }), 200);
41
- }
42
-
43
- setImg(value) {
44
- this.img = value;
45
- }
46
-
47
- setMessage(value) {
48
- this.message = value;
49
- }
50
-
51
- getShape(id) {
52
- return this.shapes.get(id);
53
- }
54
-
55
- addShape(shape) {
56
- this.shapes.set(shape.id, shape);
57
- }
58
-
59
- removeShape(shape) {
60
- this.shapes.delete(shape.id);
61
- }
62
-
63
- setApp(app) {
64
- this.app = app;
65
- }
66
-
67
- setScale(value) {
68
- this.scale = parseInt(value * 100);
69
- }
70
-
71
- setUseTool(value) {
72
- this.useTool = value;
73
- if (value) {
74
- this.setSelect(false);
75
- } else {
76
- this.setSelect(true);
77
- }
78
- }
79
-
80
- setSelect(value) {
81
- if (!this.app) return;
82
- this.app.editor.app.config.move.drag = false;
83
- this.app.editor.hittable = value;
84
- }
85
-
86
-
87
- setAnnotateColor(color) {
88
- this.annotateColor = color;
89
- }
90
-
91
- setStrokeWidth(value) {
92
- this.strokeWidth = value;
93
- }
94
-
95
- clearImg() {
96
- this.img = {};
97
- }
98
-
99
- destroy() {
100
- this.app?.destroy();
101
- this.app = null;
102
- }
103
- }
104
-
105
- const editor = new Editor();
106
- export default editor;
@@ -1,7 +0,0 @@
1
- import editor from './editor';
2
- import option from './option';
3
-
4
- export default {
5
- editor,
6
- option
7
- };
@@ -1,96 +0,0 @@
1
- import { makeAutoObservable, toJS } from 'mobx';
2
- import backgroundConfig from '@utils/backgroundConfig';
3
-
4
- class Option {
5
- scale = 1;
6
- scaleX = false;
7
- scaleY = false;
8
- padding = 0;
9
- paddingBg = 'rgba(255,255,255, 100)';
10
- round = 10;
11
- shadow = 3;
12
- frame = 'none';
13
- background = 'default_1';
14
- align = 'center';
15
- waterImg = null;
16
- waterIndex = 1;
17
- size = {
18
- type: 'auto',
19
- title: 'Auto'
20
- };
21
- frameConf = {
22
- width: 800,
23
- height: 600,
24
- background: {
25
- type: 'linear',
26
- from: 'left',
27
- to: 'right',
28
- stops: ['#6366f1', '#a855f7', '#ec4899']
29
- }
30
- }
31
- constructor() {
32
- makeAutoObservable(this);
33
- }
34
-
35
- get waterSvg() {
36
- return toJS(this.waterImg);
37
- }
38
-
39
- setScale(value) {
40
- this.scale = value;
41
- }
42
-
43
- setPadding(value) {
44
- this.padding = value;
45
- }
46
-
47
- setPaddingBg(value) {
48
- this.paddingBg = value;
49
- }
50
-
51
- setRound(value) {
52
- this.round = value;
53
- }
54
-
55
- setShadow(value) {
56
- this.shadow = value;
57
- }
58
-
59
- setFrameSize(width, height) {
60
- if (!width || !height) return;
61
- this.frameConf.width = width;
62
- this.frameConf.height = height;
63
- }
64
-
65
- setAlign(value) {
66
- this.align = value;
67
- }
68
-
69
- setSize(value) {
70
- this.size.type = value.type;
71
- this.size.title = value.title;
72
- this.setFrameSize(value.width, value.height)
73
- }
74
-
75
- setBackground(value) {
76
- this.background = value;
77
- this.frameConf.background = backgroundConfig[value].fill;
78
- }
79
- toggleFlip(type) {
80
- if (type === 'x') {
81
- this.scaleX = !this.scaleX;
82
- }
83
- if (type === 'y') {
84
- this.scaleY = !this.scaleY;
85
- }
86
- }
87
- setWaterImg(value) {
88
- this.waterImg = value;
89
- }
90
- setWaterIndex(value) {
91
- this.waterIndex = value;
92
- }
93
- }
94
-
95
- const option = new Option();
96
- export default option;
@@ -1,132 +0,0 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
4
-
5
- :root {
6
- --accent: 136, 58, 234;
7
- --accent-light: 224, 204, 250;
8
- --accent-dark: 49, 10, 101;
9
- --accent-gradient: linear-gradient(
10
- 45deg,
11
- rgb(var(--accent)),
12
- rgb(var(--accent-light)) 30%,
13
- white 60%
14
- );
15
- --c-bg: #fbfbfb;
16
- --c-wg: #fff;
17
- --c-wb: #c2c2c2;
18
- --c-bg-op: #fbfbfb00;
19
- --c-fg: #444444;
20
- --c-scroll: #d9d9d9;
21
- --c-scroll-hover: #bbbbbb;
22
- --ant-color-primary: #1677ff;
23
- scrollbar-color: var(--c-scrollbar) var(--c-bg);
24
- --primary-glow: conic-gradient(
25
- from 180deg at 50% 50%,
26
- #16abff33 0deg,
27
- #0885ff33 55deg,
28
- #54d6ff33 120deg,
29
- #0071ff33 160deg,
30
- transparent 360deg
31
- );
32
- }
33
-
34
- @layer base {
35
- ::-webkit-scrollbar {
36
- @apply w-1.5;
37
- @apply h-1.5;
38
- }
39
- ::-webkit-scrollbar-thumb {
40
- @apply bg-[var(--c-scroll)];
41
- @apply rounded-md;
42
- }
43
- ::-webkit-scrollbar-thumb:hover {
44
- @apply bg-[var(--c-scroll-hover)];
45
- }
46
- ::-webkit-scrollbar-track {
47
- @apply bg-[var(--c-bg)];
48
- }
49
- }
50
-
51
- @layer components {
52
- .polka {
53
- background-image: radial-gradient(var(--c-wb) 0.7px, #fcfcfc 1px);
54
- background-size: 14px 14px;
55
- }
56
- .ant-radio-wrapper-checked .h-8 {
57
- box-shadow: 0 0 0 2px #fff, 0 0 0 4px rgba(25, 0, 255, 0.582);
58
- }
59
- .position-block {
60
- @apply relative;
61
- @apply after:block;
62
- @apply after:transition-all;
63
- @apply after:w-8;
64
- @apply after:h-8;
65
- @apply after:rounded-md;
66
- @apply after:border;
67
- @apply after:border-blue-300;
68
- @apply after:bg-blue-100;
69
- @apply after:absolute;
70
- @apply after:z-10;
71
- }
72
- .position-block.top-left {
73
- @apply after:top-0;
74
- @apply after:left-0;
75
- }
76
- .position-block.top {
77
- @apply after:top-0;
78
- @apply after:left-8;
79
- }
80
- .position-block.top-right {
81
- @apply after:top-0;
82
- @apply after:right-0;
83
- }
84
- .position-block.right {
85
- @apply after:top-8;
86
- @apply after:right-0;
87
- }
88
- .position-block.bottom-right {
89
- @apply after:bottom-0;
90
- @apply after:right-0;
91
- }
92
- .position-block.bottom {
93
- @apply after:bottom-0;
94
- @apply after:left-8;
95
- }
96
- .position-block.bottom-left {
97
- @apply after:bottom-0;
98
- @apply after:left-0;
99
- }
100
- .position-block.left {
101
- @apply after:top-8;
102
- @apply after:left-0;
103
- }
104
- .position-block.center {
105
- @apply after:top-8;
106
- @apply after:left-8;
107
- }
108
- .bg-transparent {
109
- background-image:
110
- linear-gradient(45deg, rgba(0, 0, 0, 0.4) 25%, transparent 25%, transparent 75%,rgba(0, 0, 0, 0.4) 75%),
111
- linear-gradient(45deg,rgba(0, 0, 0, 0.4) 25%, transparent 25%, transparent 75%, rgba(0, 0, 0, 0.4) 75%);
112
- background-position: 0 0, 5px 5px;
113
- background-size: 10px 10px;
114
- }
115
- @keyframes shake {
116
- 0% {
117
- transform: translateX(0);
118
- }
119
- 25% {
120
- transform: translateX(0.5rem);
121
- }
122
- 75% {
123
- transform: translateX(-0.5rem);
124
- }
125
- 100% {
126
- transform: translateX(0);
127
- }
128
- }
129
- .invalid {
130
- animation: shake 0.2s ease-in-out 0s 2;
131
- }
132
- }
@@ -1,83 +0,0 @@
1
- export default class UndoRedoManager {
2
- constructor(options) {
3
- this.options = Object.assign(
4
- {
5
- limit: 50,
6
- onChange: () => {},
7
- },
8
- options || {}
9
- );
10
-
11
- // 数据堆
12
- this._stacks = [];
13
- // 指针位置
14
- this._pointer = -1;
15
- }
16
-
17
- get current() {
18
- return this._stacks[this._pointer];
19
- }
20
-
21
- get canUndo() {
22
- return this._pointer > 0;
23
- }
24
-
25
- get canRedo() {
26
- return this._pointer < this._stacks.length - 1;
27
- }
28
-
29
- get count() {
30
- return this._stacks.length;
31
- }
32
-
33
- get stacks() {
34
- return this._stacks;
35
- }
36
-
37
- destroy() {
38
- this._stacks = null;
39
- this.options = null;
40
- this._pointer = null;
41
- }
42
-
43
- add(data) {
44
- if (!this._stacks) {
45
- return;
46
- }
47
- // 应该删除指针之后的记录
48
- this._stacks.splice(
49
- this._pointer + 1,
50
- this._stacks.length - this._pointer - 1
51
- );
52
- if (this._stacks.length === this.options.limit) {
53
- // 存储达到上限,删除第一个
54
- this._stacks.shift();
55
- this._pointer = this.options.limit - 1;
56
- } else {
57
- this._pointer++;
58
- }
59
- this._stacks.push(data);
60
- this.options.onChange(this);
61
- return this.current;
62
- }
63
-
64
- undo() {
65
- if (!this.canUndo) {
66
- console.warn('not can undo');
67
- return;
68
- }
69
- this._pointer--;
70
- this.options.onChange(this);
71
- return this.current;
72
- }
73
-
74
- redo() {
75
- if (!this.canRedo) {
76
- console.warn('not can redo');
77
- return;
78
- }
79
- this._pointer++;
80
- this.options.onChange(this);
81
- return this.current;
82
- }
83
- }