Skip to content

Commit f41ba27

Browse files
committed
feat: restructure
1 parent 8d11fe7 commit f41ba27

23 files changed

Lines changed: 16558 additions & 110 deletions

File tree

README.md

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,81 @@
1-
[![test][test-badge]][test]
1+
[![test][test-badge]][test]
22

3-
# pointerdriver
3+
# pointerdriver
44

5-
> Synthesizes and dispatches events.
6-
> Mimics the event stream as if it were executed
7-
> by an actual human driving the interaction.
5+
Synthesize pointer, touch, and gesture events on any page.
86

9-
```sh
10-
npm i github:TheProfs/pointerdriver
7+
```bash
8+
npx github:TheProfs/pointerdriver
119
```
1210

13-
## Usage
11+
## Import in browser console
12+
13+
Open your project in the browser,
14+
then paste in the DevTools console:
1415

1516
```js
16-
import {
17-
DragMotion, GlideMotion, StrokeMotion,
18-
PinchMotion, TwistMotion
19-
} from 'pointerdriver'
17+
const {
18+
DragMotion, GlideMotion, StrokeMotion,
19+
PinchMotion, TwistMotion
20+
} = await import('http://127.0.0.1:5619/pointerdriver.js')
21+
```
22+
23+
## Drive the page
2024

25+
```js
2126
await new DragMotion(document.querySelector('#el'), [
2227
[30, 50, 0],
2328
[60, 80, 16],
2429
]).perform()
2530

26-
await new PinchMotion(document.querySelector('#el'), 2, {
27-
x: 60, y: 80
31+
await new PinchMotion(document.querySelector('#el'), 2, {
32+
x: 60, y: 80
2833
}).perform()
2934

30-
await new TwistMotion(document.querySelector('#el'), 45, {
31-
x: 60, y: 80
35+
await new TwistMotion(document.querySelector('#el'), 45, {
36+
x: 60, y: 80
3237
}).perform()
3338
```
3439

35-
- `DragMotion`, `GlideMotion` and `StrokeMotion` take points as `[x, y, ms]`.
36-
- `ms` is the timestamp offset since the start of the motion; and must be
37-
*monotonically increasing* and *non-negative*.
38-
- `PinchMotion` takes `scale` and `{ x, y, distance, steps }`.
39-
- `TwistMotion` takes `degrees` and `{ x, y, radius, steps }` (can be negative).
40+
| Motion | Device | Gesture |
41+
|--------|--------|---------|
42+
| `DragMotion` | mouse | drag across surface |
43+
| `GlideMotion` | finger | finger draw on surface |
44+
| `StrokeMotion` | pen | stylus stroke on surface |
45+
| `PinchMotion` | 2 fingers | pinch together/apart |
46+
| `TwistMotion` | 2 fingers | rotate around center |
4047

41-
## Spec
48+
- `DragMotion`, `GlideMotion` and `StrokeMotion`
49+
take points as `[x, y, ms]`.
50+
- `ms` is the timestamp offset since the start of the motion;
51+
must be *monotonically increasing* and *non-negative*.
52+
- `PinchMotion` takes `scale` and `{ x, y, distance, steps }`.
53+
- `TwistMotion` takes `degrees` and `{ x, y, radius, steps }`.
4254

43-
Full spec: `docs/spec/spec.md`.
55+
## Demo
4456

45-
## Server
57+
A PaperJS sketch-pad for testing motions visually.
4658

47-
Run a local static server:
59+
1. Start the pointerdriver server: `npx github:TheProfs/pointerdriver`
60+
2. Start the demo: `cd docs/demo && npm start`
61+
3. Open the demo in a browser.
62+
4. Import `pointerdriver` in the browser console (see above).
63+
5. Drive the `<canvas>` with motions.
4864

49-
```bash
50-
npx github:TheProfs/pointerdriver
51-
```
65+
## Spec
5266

53-
Open `http://127.0.0.1:5619/`.
67+
Full spec: `docs/spec/spec.md`.
5468

55-
## Test
69+
## Run tests
5670

5771
```bash
5872
npm test
5973
```
6074

61-
## License
75+
## License
6276

63-
MIT
77+
[MIT][license]
6478

65-
[test-badge]: https://github.com/TheProfs/pointerdriver/actions/workflows/test.yml/badge.svg
79+
[test-badge]: https://github.com/TheProfs/pointerdriver/actions/workflows/test.yml/badge.svg
6680
[test]: https://github.com/TheProfs/pointerdriver/actions/workflows/test.yml
81+
[license]: https://opensource.org/licenses/MIT

bin/pointerdriver.js

Lines changed: 76 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,88 @@
11
#!/usr/bin/env node
2-
import { spawn } from 'node:child_process'
3-
import { resolve } from 'node:path'
2+
3+
import { createServer } from 'node:http'
4+
import { readFile } from 'node:fs/promises'
5+
import { extname, join, resolve } from 'node:path'
46
import { fileURLToPath } from 'node:url'
57

6-
const here = fileURLToPath(new URL('.', import.meta.url))
7-
const root = resolve(here, '..')
8+
const root = resolve(fileURLToPath(import.meta.url), '../..')
9+
const flag = process.argv[2]
810

9-
const host = '127.0.0.1'
10-
const port = 5619
11+
if (['-h', '--help'].includes(flag)) {
12+
console.log(`
13+
pointerdriver
1114
12-
const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx'
15+
Synthesize pointer, touch, and gesture events on any page.
1316
14-
const args = [
15-
'--yes',
16-
'serve',
17-
'--listen',
18-
`tcp://${host}:${port}`,
19-
'--cors',
20-
root,
21-
]
17+
Usage:
18+
npx pointerdriver start the module server
19+
npx pointerdriver -s|--skill print the agent skill reference
20+
npx pointerdriver -h|--help show this help
21+
`)
22+
} else if (['-s', '--skill'].includes(flag)) {
23+
const skill = resolve(root, 'bin/skill.md')
2224

23-
const child = spawn(npx, args, { stdio: 'inherit' })
25+
process.stdout.write(await readFile(skill, 'utf8'))
26+
} else {
2427

25-
const exit = code =>
26-
process.exit(typeof code === 'number' ? code : 1)
28+
const host = process.env.HOST || '127.0.0.1'
29+
const port = +process.env.PORT || 5619
2730

28-
process.on('SIGINT', () => child.kill('SIGINT'))
29-
process.on('SIGTERM', () => child.kill('SIGTERM'))
31+
const types = {
32+
'.html': 'text/html',
33+
'.js': 'text/javascript',
34+
'.css': 'text/css',
35+
'.json': 'application/json',
36+
'.png': 'image/png',
37+
'.jpg': 'image/jpeg',
38+
'.svg': 'image/svg+xml',
39+
'.ico': 'image/x-icon',
40+
'.woff2': 'font/woff2',
41+
}
3042

31-
child.on('error', err => {
32-
console.error(err?.message ?? String(err))
33-
exit(1)
34-
})
43+
const rewriteImports = source => source
44+
.replace(
45+
/from\s+['"]#(\w+)['"]/g,
46+
(_, name) => `from '/src/${name}/index.js'`
47+
)
48+
49+
const server = createServer(async (req, res) => {
50+
const url = new URL(req.url, `http://${host}`)
51+
52+
const pathname = url.pathname === '/pointerdriver.js'
53+
? '/index.js'
54+
: url.pathname === '/'
55+
? '/index.html'
56+
: url.pathname
57+
58+
const path = join(root, pathname)
59+
60+
try {
61+
const data = await readFile(path)
62+
const mime = types[extname(path)] || 'application/octet-stream'
3563

36-
child.on('exit', (code, signal) => {
37-
if (signal) exit(1)
38-
exit(code)
64+
const body = mime === 'text/javascript'
65+
? rewriteImports(data.toString())
66+
: data
67+
68+
res.writeHead(200, {
69+
'Content-Type': mime,
70+
'Access-Control-Allow-Origin': '*',
71+
})
72+
73+
res.end(body)
74+
} catch {
75+
res.writeHead(404, { 'Content-Type': 'text/plain' })
76+
res.end('Not found')
77+
}
3978
})
79+
80+
server.listen(port, host, () =>
81+
console.log(`http://${host}:${port}/pointerdriver.js`))
82+
83+
const shutdown = () => server.close()
84+
85+
process
86+
.on('SIGINT', shutdown)
87+
.on('SIGTERM', shutdown)
88+
}

0 commit comments

Comments
 (0)