@ninesstudios/whiteboard 0.0.1
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 +148 -0
- package/dist/whiteboard.css +1 -0
- package/dist/whiteboard.es.js +4145 -0
- package/dist/whiteboard.umd.js +54 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Whiteboard
|
|
2
|
+
|
|
3
|
+
A tailored React Whiteboard component that supports drawing, shapes, text, images, and more. It comes with built-in state management and a customizable toolbar.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Drawing Tools**: Pencil, Text, Rectangle, Circle, Triangle, Diamond, Star, Heart, Hexagon, Octagon, Arrow.
|
|
8
|
+
- **Image Support**: Insert and manipulate images on the canvas.
|
|
9
|
+
- **Canvas Controls**: Pan and Zoom functionality.
|
|
10
|
+
- **Grid System**: Toggleable background grid.
|
|
11
|
+
- **Selection**: Select, move, and modify elements.
|
|
12
|
+
- **Export**: Export the whiteboard content as a PNG image.
|
|
13
|
+
- **Locking Mechanism**: Set the whiteboard to read-only mode with custom messages.
|
|
14
|
+
- **Action Handling**: Hook into internal actions for external syncing or logging.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
Install the package and its peer dependencies:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install whiteboard react react-dom
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
> **Note**: If this package is not yet published to npm, you can install it locally or link it.
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Basic Usage
|
|
29
|
+
|
|
30
|
+
Import the `Whiteboard` component and the stylesheet. The component takes up 100% of the parent container's width and height.
|
|
31
|
+
|
|
32
|
+
```jsx
|
|
33
|
+
import React from 'react';
|
|
34
|
+
import Whiteboard from 'whiteboard';
|
|
35
|
+
import 'whiteboard/style.css'; // Import styles
|
|
36
|
+
|
|
37
|
+
function App() {
|
|
38
|
+
return (
|
|
39
|
+
<div style={{ width: '100vw', height: '100vh', border: '1px solid #ccc' }}>
|
|
40
|
+
<Whiteboard />
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default App;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Advanced Usage
|
|
49
|
+
|
|
50
|
+
You can control the whiteboard state and handle events using props and a ref.
|
|
51
|
+
|
|
52
|
+
```jsx
|
|
53
|
+
import React, { useRef } from 'react';
|
|
54
|
+
import Whiteboard from 'whiteboard';
|
|
55
|
+
import 'whiteboard/style.css';
|
|
56
|
+
|
|
57
|
+
function App() {
|
|
58
|
+
const whiteboardRef = useRef(null);
|
|
59
|
+
|
|
60
|
+
const handleAction = (action) => {
|
|
61
|
+
console.log('Action occurred:', action);
|
|
62
|
+
// Send action to server for real-time collaboration
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const handleLockChange = (isLocked) => {
|
|
66
|
+
console.log('Lock state changed:', isLocked);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div style={{ width: '100vw', height: '100vh' }}>
|
|
71
|
+
<Whiteboard
|
|
72
|
+
ref={whiteboardRef}
|
|
73
|
+
onAction={handleAction}
|
|
74
|
+
isLocked={false}
|
|
75
|
+
lockText="This board is currently locked"
|
|
76
|
+
onLockChange={handleLockChange}
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Programmatic Updates (applyAction)
|
|
84
|
+
|
|
85
|
+
You can programmatically apply actions to the whiteboard using the `applyAction` method exposed via the ref. This is essential for implementing real-time collaboration where you need to apply actions received from a server.
|
|
86
|
+
|
|
87
|
+
```jsx
|
|
88
|
+
import React, { useEffect, useRef } from 'react';
|
|
89
|
+
import Whiteboard from 'whiteboard';
|
|
90
|
+
|
|
91
|
+
function App() {
|
|
92
|
+
const whiteboardRef = useRef(null);
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
// Example: mimicking receiving an action from a websocket
|
|
96
|
+
const mockIncomingAction = {
|
|
97
|
+
type: 'whiteboard/addElement',
|
|
98
|
+
payload: {
|
|
99
|
+
id: 'remote-1',
|
|
100
|
+
type: 'rectangle',
|
|
101
|
+
x: 200,
|
|
102
|
+
y: 200,
|
|
103
|
+
width: 100,
|
|
104
|
+
height: 100,
|
|
105
|
+
fillColor: '#FF0000'
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Apply the action to the whiteboard
|
|
110
|
+
if (whiteboardRef.current) {
|
|
111
|
+
// In a real app, this would be inside a socket event listener
|
|
112
|
+
setTimeout(() => {
|
|
113
|
+
whiteboardRef.current.applyAction(mockIncomingAction);
|
|
114
|
+
}, 1000);
|
|
115
|
+
}
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<div style={{ width: '100vw', height: '100vh' }}>
|
|
120
|
+
<Whiteboard ref={whiteboardRef} />
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## API Reference
|
|
127
|
+
|
|
128
|
+
### Props
|
|
129
|
+
|
|
130
|
+
| Prop | Type | Default | Description |
|
|
131
|
+
|------|------|---------|-------------|
|
|
132
|
+
| `onAction` | `(action: object) => void` | `undefined` | Callback fired whenever a state-changing action occurs (e.g., drawing, moving). |
|
|
133
|
+
| `isLocked` | `boolean` | `false` | When `true`, disables all editing tools and shows a lock indicator. |
|
|
134
|
+
| `lockText` | `string` | `undefined` | Custom text to display on the lock indicator when `isLocked` is true. |
|
|
135
|
+
| `onLockChange` | `(isLocked: boolean) => void` | `undefined` | Callback fired when the lock state changes. |
|
|
136
|
+
|
|
137
|
+
### Ref Methods
|
|
138
|
+
|
|
139
|
+
You can access these methods by passing a `ref` to the `Whiteboard` component.
|
|
140
|
+
|
|
141
|
+
| Method | Arguments | Description |
|
|
142
|
+
|--------|-----------|-------------|
|
|
143
|
+
| `applyAction` | `(action: object)` | Dispatches an action to the internal Redux store. Useful for applying remote updates. |
|
|
144
|
+
| `canvas` | - | Returns the underlying HTMLCanvasElement. |
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.watermark{position:absolute;bottom:0;right:0;padding:8px 12px;background-color:#fffc;border-top-left-radius:8px;font-family:sans-serif;font-size:14px;color:#666;pointer-events:none;z-index:1000;-webkit-user-select:none;user-select:none;text-decoration:none}.toolbar{position:absolute;left:10px;top:50%;transform:translateY(-50%);width:44px;padding:1px;margin:10px auto;z-index:1000;border:1px solid #ccc;background:#fff;border-radius:8px;display:flex;flex-direction:column;justify-content:space-between;align-items:center;box-shadow:0 2px 8px #0000001a}.toolbar>button:first-child{border-top-left-radius:8px;border-top-right-radius:8px}.toolbar>button:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}.toolbar>.language-container:last-child button{border-bottom-left-radius:8px;border-bottom-right-radius:8px}.ellipsis-container{position:relative}.shapes-popup{position:absolute;left:50px;top:50%;transform:translateY(-50%);background:#fff;border:1px solid #ccc;border-radius:8px;box-shadow:0 4px 12px #00000026;padding:8px;min-width:160px;z-index:1001}.shapes-popup-header{font-size:12px;font-weight:600;color:#666;padding:4px 8px 8px;border-bottom:1px solid #eee;margin-bottom:8px}.shapes-popup-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:4px}.shapes-popup-grid button{width:36px;height:36px;padding:0;display:flex;align-items:center;justify-content:center;font-size:16px}.shapes-popup-grid button:hover{background:#e9e9e9}.shapes-popup-grid button.active{background:#d0e8ff;color:#06c}.language-container{position:relative}.language-popup{position:absolute;left:50px;top:50%;transform:translateY(-50%);background:#fff;border:1px solid #ccc;border-radius:8px;box-shadow:0 4px 12px #00000026;padding:8px;min-width:140px;z-index:1001}.language-popup-header{font-size:12px;font-weight:600;color:#666;padding:4px 8px 8px;border-bottom:1px solid #eee;margin-bottom:8px}.language-options{display:flex;flex-direction:column;gap:4px}.language-options button{width:100%;height:32px;padding:0 8px;display:flex;align-items:center;justify-content:flex-start;font-size:14px;gap:8px;border-radius:4px}.language-options button:hover{background:#e9e9e9}.language-options button.active{background:#d0e8ff;color:#06c}.prop-tools{position:absolute;bottom:10px;left:10px;padding:1px;margin:10px auto;z-index:1000;border:1px solid #ccc;background:#fff;border-radius:8px;display:flex;flex-direction:row;justify-content:space-between;align-items:center;box-shadow:0 2px 8px #0000001a}.prop-tools>button:first-child{border-top-left-radius:8px;border-top-right-radius:8px}.prop-tools>button:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}button{width:40px;height:40px;border:none;border-radius:4px;background-color:#fff;cursor:pointer;transition:background .3s;text-align:center;line-height:40px;display:flex;justify-content:center;align-items:center}button:hover{background:#e9e9e9}button.active{background:#d0e8ff;color:#06c}select{width:36px;height:36px;border:none;border-radius:4px;background-color:#fff;cursor:pointer;text-align:center;line-height:36px;display:flex;justify-content:center;align-items:center;font-size:14px}.prop-tool-item{min-width:80px;margin:2px 4px;display:flex;flex-direction:column;justify-content:space-between;align-items:center}.prop-tool-item>*{text-align:center;flex:auto;min-height:30px;width:100%;box-sizing:border-box;padding-left:4px;padding-right:4px}.prop-tool-item>span{font-size:12px;color:#555;margin-bottom:4px;display:flex;justify-content:center;align-items:center;text-align:center;white-space:nowrap}.layer-buttons{display:flex;flex-direction:row;gap:4px;justify-content:center}.layer-buttons button{width:36px;height:36px;padding:0;display:flex;justify-content:center;align-items:center}*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;min-height:100vh;display:flex;justify-content:center;align-items:center;background:#f5f5f5}html,body{width:100%;height:100%;margin:0;padding:0;overflow:hidden}#root{width:100%;height:100%}
|