@ray-js/graffiti 1.0.0

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 ADDED
@@ -0,0 +1,94 @@
1
+ English | [简体中文](./README-zh_CN.md)
2
+
3
+ # @ray-js/graffiti
4
+
5
+ [![latest](https://img.shields.io/npm/v/@ray-js/graffiti/latest.svg)](https://www.npmjs.com/package/@ray-js/graffiti) [![download](https://img.shields.io/npm/dt/@ray-js/graffiti.svg)](https://www.npmjs.com/package/@ray-js/graffiti)
6
+
7
+ > Canvas Graffiti
8
+
9
+ ## Installation
10
+
11
+ ```sh
12
+ $ npm install @ray-js/graffiti
13
+ # or
14
+ $ yarn add @ray-js/graffiti
15
+ ```
16
+
17
+ ## Develop
18
+
19
+ ```sh
20
+ # install deps
21
+ yarn
22
+
23
+ # watch compile demo
24
+ yarn start:tuya
25
+ ```
26
+
27
+ ## Code Demonstration
28
+
29
+ ### Basic usage
30
+
31
+ - By changing the operation type, you can switch to pencil mode, eraser mode, paint bucket mode
32
+ - By changing `penColor`, you can pass in different pen colors
33
+ - `needStroke` is `true`, start monitoring each brush data, and get the x, y coordinate path data of each brush stroke through `onStrokeChange`
34
+ - By updating the value of `saveTrigger`, you can trigger canvas saving and return the base64 data of the canvas
35
+ - By updating the value of `clearTrigger`, you can trigger canvas clearing
36
+
37
+ ```tsx
38
+ import React, { useState } from 'react';
39
+ import { View } from '@ray-js/ray';
40
+ import Graffiti from '@ray-js/graffiti';
41
+
42
+ type IStrokeData = {
43
+ points: Array<{ x: number; y: number }>;
44
+ };
45
+
46
+ type IData = {
47
+ base64: string;
48
+ };
49
+
50
+ export function Home() {
51
+ const [actionType, setActiontype] = useState<'pencil' | 'eraser' | 'paint'>('pencil');
52
+ const [color, setColor] = useState('rgba(255, 0, 0, 1)');
53
+ const [saveTrigger, setSaveTrigger] = useState(0);
54
+ const [clearTrigger, setClearTrigger] = useState(0);
55
+
56
+ const reset = () => {
57
+ setClearTrigger(clearTrigger + 1);
58
+ };
59
+
60
+ const save = () => {
61
+ setSaveTrigger(saveTrigger + 1);
62
+ };
63
+
64
+ const handleStrokeChange = (data: IPoints) => {
65
+ console.log('handleStrokeChange', data);
66
+ };
67
+
68
+ const handleSaveData = (data: IData) => {
69
+ console.log('handleSaveData', data);
70
+ };
71
+
72
+ return (
73
+ <>
74
+ <Graffiti
75
+ needStroke
76
+ penColor={color}
77
+ actionType={actionType}
78
+ saveTrigger={saveTrigger}
79
+ clearTrigger={clearTrigger}
80
+ onStrokeChange={handleStrokeChange}
81
+ onSaveData={handleSaveData}
82
+ />
83
+ <View className="footer">
84
+ <Button className="btn" type="primary" onClick={reset}>
85
+ Reset
86
+ </Button>
87
+ <Button className="btn" type="primary" onClick={save}>
88
+ Save
89
+ </Button>
90
+ </View>
91
+ <>
92
+ );
93
+ }
94
+ ```
@@ -0,0 +1,200 @@
1
+ import Render from './index.rjs';
2
+ import { getSystemInfoSync } from '@ray-js/ray';
3
+ let _systemInfoResult = null;
4
+ export const getSystemInfoResult = () => {
5
+ if (_systemInfoResult) {
6
+ return _systemInfoResult;
7
+ }
8
+ try {
9
+ const info = getSystemInfoSync();
10
+ _systemInfoResult = info;
11
+ return _systemInfoResult;
12
+ } catch (err) {
13
+ return {
14
+ windowHeight: 667,
15
+ windowWidth: 375,
16
+ pixelRatio: 2
17
+ };
18
+ }
19
+ };
20
+
21
+ // 获取真实的px
22
+ export const getDeviceRealPx = px => {
23
+ const info = getSystemInfoResult();
24
+ return Math.round(px * (info.windowWidth / 375));
25
+ };
26
+ const WIDTH = 353;
27
+ const HEIGHT = 353;
28
+
29
+ // eslint-disable-next-line no-undef
30
+ Component({
31
+ properties: {
32
+ canvasIdPrefix: {
33
+ type: String,
34
+ value: 'ray-graffiti' // canvas
35
+ },
36
+ width: {
37
+ type: Number,
38
+ value: WIDTH // 画布宽度
39
+ },
40
+ height: {
41
+ type: Number,
42
+ value: HEIGHT // 画布高度
43
+ },
44
+ mode: {
45
+ type: String,
46
+ value: 'grid' // grid 按方格数显示长宽相同, pixel 按像素显示
47
+ },
48
+ gridSizeX: {
49
+ type: Number,
50
+ value: 32 // 每行格子数量
51
+ },
52
+ gridSizeY: {
53
+ type: Number,
54
+ value: 32 // 每列格子数量
55
+ },
56
+ pixelSizeX: {
57
+ type: Number,
58
+ value: 10 // 格子宽度
59
+ },
60
+ pixelSizeY: {
61
+ type: Number,
62
+ value: 10 // 格子高度
63
+ },
64
+ pixelGap: {
65
+ type: Number,
66
+ value: 1 // 格子间距
67
+ },
68
+ pixelShape: {
69
+ type: String,
70
+ value: 'square' // 格子形状, square方形, circle圆形
71
+ },
72
+ pixelColor: {
73
+ type: String,
74
+ value: 'rgba(255, 255, 255, 0.15)' // 格子颜色
75
+ },
76
+ penColor: {
77
+ // 画笔颜色
78
+ type: String,
79
+ value: 'rgb(255, 255, 255)',
80
+ observer(newValue) {
81
+ if (newValue && this.render) {
82
+ if (this.data.actionType === 'pencil') {
83
+ this.render.updateColor(newValue);
84
+ } else if (this.data.actionType === 'paint') {
85
+ this.render.changeBg(newValue);
86
+ }
87
+ }
88
+ }
89
+ },
90
+ actionType: {
91
+ // 操作类型 pencil画笔, eraser橡皮擦, paint油漆桶
92
+ type: String,
93
+ value: 'pencil',
94
+ observer(newValue) {
95
+ if (newValue && this.render) {
96
+ if (newValue === 'eraser') {
97
+ this.render.updateColor(this.data.pixelColor);
98
+ } else if (newValue === 'pencil') {
99
+ this.render.updateColor(this.data.penColor);
100
+ } else if (newValue === 'paint') {
101
+ this.render.changeBg(this.data.penColor);
102
+ }
103
+ }
104
+ }
105
+ },
106
+ needStroke: {
107
+ // 是否需要每一画笔数据
108
+ type: Boolean,
109
+ value: false
110
+ },
111
+ saveTrigger: {
112
+ // 保存标识
113
+ type: Number,
114
+ value: 0,
115
+ observer(newValue) {
116
+ if (newValue && this.render) {
117
+ this.render.save();
118
+ }
119
+ }
120
+ },
121
+ clearTrigger: {
122
+ // 清空标识
123
+ type: Number,
124
+ value: 0,
125
+ observer(newValue) {
126
+ if (newValue && this.render) {
127
+ this.render.clear();
128
+ }
129
+ }
130
+ }
131
+ },
132
+ data: {
133
+ realWidth: WIDTH,
134
+ realHeight: HEIGHT
135
+ },
136
+ lifetimes: {
137
+ created() {
138
+ this.render = new Render(this);
139
+ },
140
+ ready() {
141
+ let {
142
+ canvasIdPrefix,
143
+ width,
144
+ height,
145
+ gridSizeX,
146
+ gridSizeY,
147
+ pixelSizeX,
148
+ pixelSizeY,
149
+ pixelGap,
150
+ mode
151
+ } = this.data;
152
+ width = getDeviceRealPx(width);
153
+ height = getDeviceRealPx(height);
154
+ pixelSizeX = getDeviceRealPx(pixelSizeX);
155
+ pixelSizeY = getDeviceRealPx(pixelSizeY);
156
+ pixelGap = getDeviceRealPx(pixelGap);
157
+ let realPixelSizeX = pixelSizeX;
158
+ let realPixelSizeY = pixelSizeY;
159
+ if (mode === 'grid') {
160
+ realPixelSizeX = Math.floor((width - pixelGap) / gridSizeX - pixelGap);
161
+ const gridModeSizeX = (realPixelSizeX + pixelGap) * gridSizeX + pixelGap;
162
+ realPixelSizeY = Math.floor((height - pixelGap) / gridSizeY - pixelGap);
163
+ const gridModeSizeY = (realPixelSizeY + pixelGap) * gridSizeY + pixelGap;
164
+ this.setData({
165
+ realWidth: gridModeSizeX,
166
+ realHeight: gridModeSizeY
167
+ });
168
+ } else {
169
+ this.setData({
170
+ realWidth: width,
171
+ realHeight: height
172
+ });
173
+ }
174
+ this.render.initPanel({
175
+ canvasIdPrefix: canvasIdPrefix,
176
+ width: width,
177
+ height: height,
178
+ mode: this.data.mode,
179
+ gridSizeX: gridSizeX,
180
+ gridSizeY: gridSizeY,
181
+ pixelSizeX: realPixelSizeX,
182
+ pixelSizeY: realPixelSizeY,
183
+ pixelGap: pixelGap,
184
+ pixelShape: this.data.pixelShape,
185
+ pixelColor: this.data.pixelColor,
186
+ penColor: this.data.penColor
187
+ });
188
+ }
189
+ },
190
+ methods: {
191
+ touchend(data) {
192
+ if (!this.data.needStroke) return;
193
+ if (this.data.actionType === 'paint') return;
194
+ this.triggerEvent('strokeChange', data);
195
+ },
196
+ genImageData(data) {
197
+ this.triggerEvent('saveData', data);
198
+ }
199
+ }
200
+ });
@@ -0,0 +1,3 @@
1
+ {
2
+ "component": true
3
+ }
File without changes
@@ -0,0 +1,165 @@
1
+ const pixelRatio = Math.floor(getSystemInfo().pixelRatio) || 1; // 分辨率, 整数
2
+
3
+ export default Render({
4
+ async initPanel({
5
+ canvasIdPrefix,
6
+ width,
7
+ height,
8
+ mode,
9
+ gridSizeX,
10
+ gridSizeY,
11
+ pixelSizeX,
12
+ pixelSizeY,
13
+ pixelGap,
14
+ pixelShape,
15
+ pixelColor,
16
+ penColor,
17
+ }) {
18
+ let canvas = await getCanvasById(`${canvasIdPrefix}-sourceCanvas`);
19
+
20
+ // 根据屏幕分辨率动态计算canvas尺寸
21
+ if (mode === 'grid') {
22
+ const gridModeSizeX = (pixelSizeX + pixelGap) * gridSizeX + pixelGap;
23
+ const gridModeSizeY = (pixelSizeY + pixelGap) * gridSizeY + pixelGap;
24
+ canvas.width = gridModeSizeX * pixelRatio;
25
+ canvas.height = gridModeSizeY * pixelRatio;
26
+ canvas.style.width = gridModeSizeX + 'px';
27
+ canvas.style.height = gridModeSizeY + 'px';
28
+ } else {
29
+ canvas.width = width * pixelRatio;
30
+ canvas.height = height * pixelRatio;
31
+ canvas.style.width = `${width}px`;
32
+ canvas.style.height = `${height}px`;
33
+ }
34
+
35
+ const ctx = canvas.getContext('2d');
36
+ ctx.scale(pixelRatio, pixelRatio);
37
+ this.canvas = canvas;
38
+ this.ctx = ctx;
39
+
40
+ this.mode = mode;
41
+ this.width = width;
42
+ this.height = height;
43
+ this.gridSizeX = gridSizeX;
44
+ this.gridSizeY = gridSizeY;
45
+ this.pixelSizeX = pixelSizeX;
46
+ this.pixelSizeY = pixelSizeY;
47
+ this.pixelGap = pixelGap;
48
+ this.pixelShape = pixelShape;
49
+ this.pixelColor = pixelColor;
50
+ this.penColor = penColor;
51
+ this.canvasIdPrefix = canvasIdPrefix;
52
+ // 用于存储触摸开始到结束经过的方格坐标数组集合
53
+ this.touchedSquaresSet = new Set();
54
+ // 记录触摸是否开始
55
+ this.isTouchStarted = false;
56
+
57
+ // 初始化画布, 绘制像素点
58
+ this.createPixel(pixelColor);
59
+
60
+ canvas.addEventListener('touchstart', this.handleTouchstart, false);
61
+ canvas.addEventListener('touchmove', this.handleTouchmove, false);
62
+ canvas.addEventListener('touchend', this.handleTouchend, false);
63
+
64
+ ctx.imageSmoothingEnabled = true; // 开启抗锯齿
65
+ ctx.imageSmoothingQuality = 'high'; // 高质量抗锯齿
66
+ },
67
+ handleTouchstart(e) {
68
+ const { canvas, pixelGap, pixelSizeX, pixelSizeY } = this;
69
+ this.touchedSquaresSet.clear();
70
+ this.isTouchStarted = true;
71
+ const touch = e.changedTouches[0];
72
+ const rect = canvas.getBoundingClientRect();
73
+ const x = Math.floor((touch.pageX - rect.left - pixelGap) / (pixelSizeX + pixelGap));
74
+ const y = Math.floor((touch.pageY - rect.top - pixelGap) / (pixelSizeY + pixelGap));
75
+ const coordinate = `${x},${y}`;
76
+ if (!this.touchedSquaresSet.has(coordinate)) {
77
+ this.touchedSquaresSet.add(coordinate);
78
+ this.fillPixel(x, y, this.penColor);
79
+ }
80
+ },
81
+ handleTouchmove(e) {
82
+ const { canvas, pixelGap, pixelSizeX, pixelSizeY } = this;
83
+ e.preventDefault();
84
+ if (this.isTouchStarted) {
85
+ const touch = e.changedTouches[0];
86
+ const rect = canvas.getBoundingClientRect();
87
+ const x = Math.floor((touch.pageX - rect.left - pixelGap) / (pixelSizeX + pixelGap));
88
+ const y = Math.floor((touch.pageY - rect.top - pixelGap) / (pixelSizeY + pixelGap));
89
+ const coordinate = `${x},${y}`;
90
+ if (!this.touchedSquaresSet.has(coordinate)) {
91
+ this.touchedSquaresSet.add(coordinate);
92
+ this.fillPixel(x, y, this.penColor);
93
+ }
94
+ }
95
+ },
96
+ handleTouchend() {
97
+ this.isTouchStarted = false;
98
+ const touchedSquares = [];
99
+ for (const coordinateStr of this.touchedSquaresSet) {
100
+ const [x, y] = coordinateStr.split(',');
101
+ touchedSquares.push({ x: Number(x), y: Number(y) });
102
+ }
103
+ this.callMethod('touchend', { points: touchedSquares });
104
+ },
105
+ createPixel(pixelColor) {
106
+ const { gridSizeX, gridSizeY, pixelSizeX, pixelSizeY, pixelGap } = this;
107
+ let realGridSizeX = gridSizeX;
108
+ let realGridSizeY = gridSizeY;
109
+ if (this.mode !== 'grid') {
110
+ realGridSizeX = (this.width - pixelGap) / (pixelSizeX + pixelGap);
111
+ realGridSizeY = (this.height - pixelGap) / (pixelSizeY + pixelGap);
112
+ }
113
+ for (let x = 0; x < realGridSizeX; x++) {
114
+ for (let y = 0; y < realGridSizeY; y++) {
115
+ this.fillPixel(x, y, pixelColor);
116
+ }
117
+ }
118
+ },
119
+ fillPixel(x, y, color) {
120
+ const { ctx, pixelSizeX, pixelSizeY, pixelGap, pixelShape } = this;
121
+ const offsetX = pixelGap + x * (pixelSizeX + pixelGap);
122
+ const offsetY = pixelGap + y * (pixelSizeY + pixelGap);
123
+ // 清除原有填充颜色
124
+ ctx.clearRect(offsetX, offsetY, pixelSizeX, pixelSizeY);
125
+ ctx.fillStyle = color; // 填充颜色
126
+ if (pixelShape === 'square') {
127
+ ctx.fillRect(offsetX, offsetY, pixelSizeX, pixelSizeY);
128
+ } else {
129
+ const radiusX = pixelSizeX / 2;
130
+ const radiusY = pixelSizeY / 2;
131
+ // 开始绘制路径
132
+ ctx.beginPath();
133
+ // 使用arc方法绘制圆形,传入圆心x坐标、圆心y坐标、半径、起始角度(弧度制)、结束角度(弧度制)
134
+ if (radiusX === radiusY) {
135
+ ctx.arc(offsetX + radiusX, offsetY + radiusY, radiusX, 0, Math.PI * 2);
136
+ } else {
137
+ ctx.ellipse(offsetX + radiusX, offsetY + radiusY, radiusX, radiusY, 0, 0, Math.PI * 2);
138
+ // ctx.arc(offsetX + radiusX, (offsetY + radiusY) / (radiusY / radiusX), radiusX, 0, Math.PI * 2);
139
+ }
140
+ // 关闭路径
141
+ ctx.closePath();
142
+
143
+ // 执行填充操作,将圆形内部填充为设定的颜色
144
+ ctx.fill();
145
+ }
146
+ },
147
+ // 改变画笔颜色
148
+ updateColor(color) {
149
+ this.penColor = color;
150
+ },
151
+ // 油漆桶
152
+ changeBg(color) {
153
+ this.penColor = color;
154
+ this.createPixel(color);
155
+ },
156
+ // 清除画布
157
+ clear() {
158
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
159
+ this.createPixel(this.pixelColor);
160
+ },
161
+ save() {
162
+ const base64 = this.canvas.toDataURL('image/png');
163
+ this.callMethod('genImageData', { base64 });
164
+ },
165
+ });
@@ -0,0 +1,6 @@
1
+ <canvas
2
+ canvasClassName
3
+ type="2d"
4
+ canvas-id="{{`${canvasIdPrefix}-sourceCanvas`}}"
5
+ style="width: {{realWidth}}px; height: {{realHeight}}px"
6
+ />
@@ -0,0 +1,14 @@
1
+ export const tuya = {
2
+ backgroundColor: '#f2f4f6',
3
+ navigationBarTitleText: 'Ray Graffiti'
4
+ };
5
+ export const wechat = {
6
+ backgroundColor: '#f2f4f6',
7
+ navigationBarTitleText: 'Ray Graffiti'
8
+ };
9
+ export const native = {
10
+ backgroundColor: 'transparent',
11
+ isBleOfflineOverlay: false,
12
+ useSafeAreaView: true,
13
+ navigationBarTitleText: 'Ray Graffiti'
14
+ };
package/lib/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import { IProps } from './props';
3
+ declare const RayGraffiti: React.FC<IProps>;
4
+ export default RayGraffiti;
package/lib/index.js ADDED
@@ -0,0 +1,59 @@
1
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
+ import React, { useMemo } from 'react';
3
+ import { View } from '@ray-js/ray';
4
+ import Graffiti from './components';
5
+ import { getUuid } from './utils';
6
+ import { defaultProps } from './props';
7
+ const classPrefix = 'ray-graffiti';
8
+ const RayGraffiti = props => {
9
+ const canvasIdPrefix = useMemo(() => {
10
+ return `${getUuid(classPrefix)}`;
11
+ }, []);
12
+ const {
13
+ style = {},
14
+ className = '',
15
+ width,
16
+ height,
17
+ mode,
18
+ gridSizeX,
19
+ gridSizeY,
20
+ pixelSizeX,
21
+ pixelSizeY,
22
+ pixelGap,
23
+ pixelShape,
24
+ pixelColor,
25
+ penColor,
26
+ actionType,
27
+ needStroke,
28
+ saveTrigger,
29
+ clearTrigger,
30
+ onStrokeChange,
31
+ onSaveData
32
+ } = props;
33
+ return /*#__PURE__*/React.createElement(View, {
34
+ className: `${classPrefix} ${className}`,
35
+ style: _objectSpread({}, style)
36
+ }, /*#__PURE__*/React.createElement(Graffiti, {
37
+ canvasIdPrefix: canvasIdPrefix,
38
+ width: width,
39
+ height: height,
40
+ mode: mode,
41
+ gridSizeX: gridSizeX,
42
+ gridSizeY: gridSizeY,
43
+ pixelSizeX: pixelSizeX,
44
+ pixelSizeY: pixelSizeY,
45
+ pixelGap: pixelGap,
46
+ pixelShape: pixelShape,
47
+ pixelColor: pixelColor,
48
+ penColor: penColor,
49
+ actionType: actionType,
50
+ needStroke: needStroke,
51
+ saveTrigger: saveTrigger,
52
+ clearTrigger: clearTrigger,
53
+ bindstrokeChange: e => onStrokeChange(e.detail),
54
+ bindsaveData: e => onSaveData(e.detail)
55
+ }));
56
+ };
57
+ RayGraffiti.defaultProps = defaultProps;
58
+ RayGraffiti.displayName = classPrefix;
59
+ export default RayGraffiti;
package/lib/props.d.ts ADDED
@@ -0,0 +1,127 @@
1
+ /// <reference types="react" />
2
+ export interface IProps {
3
+ /**
4
+ * @description.zh 样式
5
+ * @description.en Style
6
+ * @default {}
7
+ */
8
+ style?: React.CSSProperties;
9
+ /**
10
+ * @description.zh 类名
11
+ * @description.en className
12
+ * @default ''
13
+ */
14
+ className?: string;
15
+ /**
16
+ * @description.zh 画布宽度
17
+ * @description.en Canvas width
18
+ * @default 353
19
+ */
20
+ width?: number;
21
+ /**
22
+ * @description.zh 画布高度
23
+ * @description.en Canvas height
24
+ * @default 353
25
+ */
26
+ height?: number;
27
+ /**
28
+ * @description.zh 画布显示模式(grid: 按格子数量显示, pixel: 按像素显示格子), 如果 mode 设置为 grid, 那么 pixelSizeX, pixelSizeY 属性将不生效, 如果 mode 设置为 pixel, 那么 gridSizeX, gridSizeY 属性将不生效, 推荐使用 grid 模式
29
+ * @description.en Canvas display mode (grid: display by grid number, pixel: display by pixel grid), if mode is set to grid, then pixelSizeX, pixelSizeY properties will not take effect, if mode is set to pixel, then gridSizeX, gridSizeY properties will not take effect, it is recommended to use grid mode
30
+ * @default grid
31
+ */
32
+ mode?: 'grid' | 'pixel';
33
+ /**
34
+ * @description.zh 每行格子数量
35
+ * @description.en Number of grid per row and column
36
+ * @default 32
37
+ */
38
+ gridSizeX?: number;
39
+ /**
40
+ * @description.zh 每列格子数量
41
+ * @description.en Number of grid per column
42
+ * @default 32
43
+ */
44
+ gridSizeY?: number;
45
+ /**
46
+ * @description.zh 格子宽度
47
+ * @description.en Pixel width
48
+ * @default 10
49
+ */
50
+ pixelSizeX?: number;
51
+ /**
52
+ * @description.zh 格子高度
53
+ * @description.en Pixel height
54
+ * @default 10
55
+ */
56
+ pixelSizeY?: number;
57
+ /**
58
+ * @description.zh 格子间距
59
+ * @description.en Pixel gap
60
+ * @default 1
61
+ */
62
+ pixelGap?: number;
63
+ /**
64
+ * @description.zh 格子形状, square 方形, circle 圆形
65
+ * @description.en Pixel shape
66
+ * @default square
67
+ */
68
+ pixelShape?: 'square' | 'circle';
69
+ /**
70
+ * @description.zh 网格颜色
71
+ * @description.en Grid color
72
+ * @default `rgba(255, 255, 255, 0.15)`
73
+ */
74
+ pixelColor?: string;
75
+ /**
76
+ * @description.zh 画笔颜色
77
+ * @description.en Pen color
78
+ * @default `rgb(255, 255, 255)`
79
+ */
80
+ penColor?: string;
81
+ /**
82
+ * @description.zh 操作类型, pencil 画笔, eraser 橡皮擦, paint 油漆桶
83
+ * @description.en Action type
84
+ * @default pencil
85
+ */
86
+ actionType?: 'pencil' | 'eraser' | 'paint';
87
+ /**
88
+ * @description.zh 是否监听每一画笔数据, 开启后可以通过 onStrokeChange 获取每一笔画笔数据
89
+ * @description.en Whether to listen to each stroke data, after opening, you can get each stroke data through onStrokeChange
90
+ * @default false
91
+ */
92
+ needStroke?: boolean;
93
+ /**
94
+ * @description.zh 触发保存的标识, 每次递增该值, 可以触发 onSaveData 返回保存数据
95
+ * @description.en Trigger save mark, increase this value each time, you can trigger onSaveData to return saved data
96
+ * @default 0
97
+ */
98
+ saveTrigger?: number;
99
+ /**
100
+ * @description.zh 触发清空的标识, 每次递增该值, 可以触发画笔清空
101
+ * @description.en Trigger clear mark, increase this value each time, you can trigger the pen to clear
102
+ * @default 0
103
+ */
104
+ clearTrigger?: number;
105
+ /**
106
+ * @description.zh 画笔数据更新时触发, 返回画笔经过的x,y坐标路径数据
107
+ * @description.en Triggered when the brush data is updated, returns the x,y coordinate path data of the brush.
108
+ * @default () => null
109
+ */
110
+ onStrokeChange?: (data: IStrokeData) => void;
111
+ /**
112
+ * @description.zh 保存数据时触发, 返回画布的base64数据
113
+ * @description.en Triggered when saving data, return canvas base64 data
114
+ * @default () => null
115
+ */
116
+ onSaveData?: (data: IData) => void;
117
+ }
118
+ export type IStrokeData = {
119
+ points: Array<{
120
+ x: number;
121
+ y: number;
122
+ }>;
123
+ };
124
+ export type IData = {
125
+ base64: string;
126
+ };
127
+ export declare const defaultProps: IProps;
package/lib/props.js ADDED
@@ -0,0 +1,20 @@
1
+ export const defaultProps = {
2
+ style: {},
3
+ width: 353,
4
+ height: 353,
5
+ mode: 'grid',
6
+ gridSizeX: 32,
7
+ gridSizeY: 32,
8
+ pixelSizeX: 10,
9
+ pixelSizeY: 10,
10
+ pixelGap: 1,
11
+ pixelShape: 'square',
12
+ pixelColor: 'rgba(255, 255, 255, 0.15)',
13
+ penColor: 'rgb(255, 255, 255)',
14
+ actionType: 'pencil',
15
+ needStroke: false,
16
+ saveTrigger: 0,
17
+ clearTrigger: 0,
18
+ onStrokeChange: () => null,
19
+ onSaveData: () => null
20
+ };
package/lib/utils.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare const getUuid: (prefix?: string) => string;
package/lib/utils.js ADDED
@@ -0,0 +1,8 @@
1
+ export const getUuid = function () {
2
+ let prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
3
+ const id = `${String(+new Date()).slice(-3)}_${String(Math.random()).slice(-3)}`;
4
+ if (prefix) {
5
+ return `${prefix}_${id}`;
6
+ }
7
+ return id;
8
+ };
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@ray-js/graffiti",
3
+ "version": "1.0.0",
4
+ "description": "画布涂鸦组件",
5
+ "main": "lib/index",
6
+ "files": [
7
+ "lib"
8
+ ],
9
+ "license": "MIT",
10
+ "types": "lib/index.d.ts",
11
+ "maintainers": [
12
+ "tuya_npm",
13
+ {
14
+ "name": "tuyafe",
15
+ "email": "tuyafe@tuya.com"
16
+ }
17
+ ],
18
+ "scripts": {
19
+ "lint": "eslint src --ext .js,.jsx,.ts,.tsx --fix",
20
+ "lint:style": "stylelint \"src/**/*.less\" --fix",
21
+ "build": "ray build --type=component",
22
+ "watch": "ray start --type=component --output ./example/src/lib",
23
+ "build:tuya": "ray build -t tuya ./example",
24
+ "build:wechat": "ray build ./example --target=wechat",
25
+ "build:web": "ray build ./example --target=web",
26
+ "build:native": "ray build ./example --target=native",
27
+ "start:native": "ray start ./example -t native --verbose",
28
+ "start:tuya": "ray start -t tuya ./example",
29
+ "start:wechat": "ray start ./example -t wechat --verbose",
30
+ "start:web": "ray start ./example -t web",
31
+ "prepublishOnly": "yarn build",
32
+ "release-it": "standard-version"
33
+ },
34
+ "peerDependencies": {
35
+ "@ray-js/ray": "^1.4.9"
36
+ },
37
+ "dependencies": {
38
+ "clsx": "^1.2.1"
39
+ },
40
+ "devDependencies": {
41
+ "@commitlint/cli": "^7.2.1",
42
+ "@commitlint/config-conventional": "^9.0.1",
43
+ "@ray-js/cli": "^1.4.9",
44
+ "@ray-js/components-ty-lamp": "^2.0.2",
45
+ "@ray-js/lamp-saturation-slider": "^1.1.7",
46
+ "@ray-js/panel-sdk": "^1.13.0-storage.7",
47
+ "@ray-js/ray": "^1.4.9",
48
+ "@vant/stylelint-config": "^1.4.2",
49
+ "core-js": "^3.19.1",
50
+ "eslint-config-tuya-panel": "^0.4.2",
51
+ "husky": "^1.2.0",
52
+ "lint-staged": "^10.2.11",
53
+ "standard-version": "9.3.2",
54
+ "stylelint": "^13.0.0"
55
+ },
56
+ "resolutions": {
57
+ "@ray-js/builder-mp": "1.4.15"
58
+ },
59
+ "husky": {
60
+ "hooks": {
61
+ "commit-msg": "commitlint -E HUSKY_GIT_PARAMS --config commitlint.config.js",
62
+ "pre-commit": "lint-staged"
63
+ }
64
+ },
65
+ "lint-staged": {
66
+ "*.{ts,tsx,js,jsx}": [
67
+ "eslint --fix",
68
+ "git add"
69
+ ],
70
+ "*.{json,md,yml,yaml}": [
71
+ "prettier --write",
72
+ "git add"
73
+ ]
74
+ }
75
+ }