image-beautifier 1.0.0 → 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.
- package/README.md +6 -0
- package/lib/image-beautifier.es.js +2852 -0
- package/lib/style.css +9 -0
- package/package.json +19 -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,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
|
-
};
|
package/src/hooks/usePaste.js
DELETED
|
@@ -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 App } from 'App';
|
package/src/main.jsx
DELETED
package/src/stores/editor.js
DELETED
|
@@ -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;
|
package/src/stores/index.js
DELETED
package/src/stores/option.js
DELETED
|
@@ -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;
|
package/src/style/main.css
DELETED
|
@@ -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
|
-
}
|