Skip to content

Commit 250c837

Browse files
committed
Add capability for KeyboardEvent.code + capture
This: - Adds the capability for KeycodeLayout to also use the `code` field from KeyboardEvents. - Adds a map for `code` fields for EN_US. - Adds a package with a widget for capturing keyboard layouts. - Adds an example app for capturing keyboard layouts. TODOs: - Add tests for capture widget / example. - Figure out a pattern for adding other layouts. Should they be added to keyboard package, or to one or more separate language packs?
1 parent 66b9df5 commit 250c837

File tree

19 files changed

+995
-12
lines changed

19 files changed

+995
-12
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8"/>
5+
<script type="text/javascript" src="build/bundle.example.js"></script>
6+
</head>
7+
<body>
8+
</body>
9+
</html>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "@lumino/example-keyboard-capture",
3+
"version": "1.0.0",
4+
"private": true,
5+
"scripts": {
6+
"build": "tsc && webpack",
7+
"clean": "rimraf build",
8+
"start": "node build/server.js"
9+
},
10+
"dependencies": {
11+
"@lumino/keyboard-capture": "^1.0.0",
12+
"@lumino/signaling": "^1.10.1",
13+
"@lumino/widgets": "^1.31.1",
14+
"es6-promise": "^4.0.5"
15+
},
16+
"devDependencies": {
17+
"@lumino/messaging": "^1.10.1",
18+
"@types/node": "^10.12.19",
19+
"css-loader": "^3.4.0",
20+
"file-loader": "^5.0.2",
21+
"rimraf": "^3.0.2",
22+
"style-loader": "^1.0.2",
23+
"typescript": "~3.6.0",
24+
"webpack": "^4.41.3"
25+
}
26+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright (c) Jupyter Development Team.
2+
// Distributed under the terms of the Modified BSD License.
3+
/*-----------------------------------------------------------------------------
4+
| Copyright (c) 2019, PhosphorJS Contributors
5+
|
6+
| Distributed under the terms of the BSD 3-Clause License.
7+
|
8+
| The full license is in the file LICENSE, distributed with this software.
9+
|----------------------------------------------------------------------------*/
10+
11+
import { CaptureWidget } from '@lumino/keyboard-capture';
12+
import { Message } from '@lumino/messaging';
13+
import { ISignal, Signal } from '@lumino/signaling';
14+
import { Panel, Widget } from '@lumino/widgets';
15+
16+
import '../style/index.css';
17+
18+
export class OutputWidget extends Widget {
19+
/**
20+
*
21+
*/
22+
constructor(options?: Widget.IOptions) {
23+
super(options);
24+
this._output = document.createElement('div');
25+
this._exportButton = document.createElement('button');
26+
this._exportButton.innerText = 'Show';
27+
this._copyButton = document.createElement('button');
28+
this._copyButton.innerText = 'Copy';
29+
this._clearButton = document.createElement('button');
30+
this._clearButton.innerText = 'Clear';
31+
this.node.appendChild(this._exportButton);
32+
this.node.appendChild(this._copyButton);
33+
this.node.appendChild(this._clearButton);
34+
this.node.appendChild(this._output);
35+
this.addClass('lm-keyboardCaptureOutputArea');
36+
}
37+
38+
set value(content: string) {
39+
this._output.innerHTML = content;
40+
}
41+
42+
get action(): ISignal<this, 'display' | 'clipboard' | 'clear'> {
43+
return this._action;
44+
}
45+
46+
/**
47+
* Handle the DOM events for the widget.
48+
*
49+
* @param event - The DOM event sent to the element.
50+
*/
51+
handleEvent(event: Event): void {
52+
switch (event.type) {
53+
case 'click':
54+
if (event.target === this._exportButton) {
55+
event.stopPropagation();
56+
this._action.emit('display');
57+
} else if (event.target === this._copyButton) {
58+
event.stopPropagation();
59+
this._action.emit('clipboard');
60+
} else if (event.target === this._clearButton) {
61+
event.stopPropagation();
62+
this._action.emit('clear');
63+
}
64+
break;
65+
}
66+
}
67+
68+
/**
69+
* A message handler invoked on a `'before-attach'` message.
70+
*/
71+
protected onBeforeAttach(msg: Message): void {
72+
this._exportButton.addEventListener('click', this);
73+
this._copyButton.addEventListener('click', this);
74+
this._clearButton.addEventListener('click', this);
75+
super.onBeforeAttach(msg);
76+
}
77+
78+
/**
79+
* A message handler invoked on an `'after-detach'` message.
80+
*/
81+
protected onAfterDetach(msg: Message): void {
82+
super.onAfterDetach(msg);
83+
this._exportButton.removeEventListener('click', this);
84+
this._copyButton.removeEventListener('click', this);
85+
this._clearButton.removeEventListener('click', this);
86+
}
87+
88+
private _output: HTMLElement;
89+
private _exportButton: HTMLButtonElement;
90+
private _copyButton: HTMLButtonElement;
91+
private _clearButton: HTMLButtonElement;
92+
private _action = new Signal<this, 'display' | 'clipboard' | 'clear'>(this);
93+
}
94+
95+
/**
96+
* Initialize the applicaiton.
97+
*/
98+
async function init(): Promise<void> {
99+
// Add the text editors to a dock panel.
100+
let capture = new CaptureWidget();
101+
let output = new OutputWidget();
102+
103+
capture.node.textContent =
104+
'Focus me and hit each key on your keyboard without any modifiers';
105+
106+
// Add the dock panel to the document.
107+
let box = new Panel();
108+
box.id = 'main';
109+
box.addWidget(capture);
110+
box.addWidget(output);
111+
112+
capture.dataAdded.connect((sender, entry) => {
113+
output.value = `Added ${entry.type}: ${
114+
entry.code && `${entry.code} →`
115+
} <kbd>${entry.key}</kbd>`;
116+
});
117+
output.action.connect((sender, action) => {
118+
if (action === 'clipboard') {
119+
navigator.clipboard.writeText(capture.formatMap());
120+
} else if (action === 'clear') {
121+
capture.clear();
122+
} else {
123+
output.value = `<pre>${capture.formatMap()}</pre>`;
124+
}
125+
});
126+
127+
window.onresize = () => {
128+
box.update();
129+
};
130+
Widget.attach(box, document.body);
131+
}
132+
133+
window.onload = init;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) Jupyter Development Team.
2+
// Distributed under the terms of the Modified BSD License.
3+
/*-----------------------------------------------------------------------------
4+
| Copyright (c) 2019, PhosphorJS Contributors
5+
|
6+
| Distributed under the terms of the BSD 3-Clause License.
7+
|
8+
| The full license is in the file LICENSE, distributed with this software.
9+
|----------------------------------------------------------------------------*/
10+
11+
import * as fs from 'fs';
12+
13+
import * as http from 'http';
14+
15+
import * as path from 'path';
16+
17+
/**
18+
* Create a HTTP static file server for serving the static
19+
* assets to the user.
20+
*/
21+
let server = http.createServer((request, response) => {
22+
console.log('request starting...');
23+
24+
let filePath = '.' + request.url;
25+
if (filePath == './') {
26+
filePath = './index.html';
27+
}
28+
29+
let extname = path.extname(filePath);
30+
let contentType = 'text/html';
31+
switch (extname) {
32+
case '.js':
33+
contentType = 'text/javascript';
34+
break;
35+
case '.css':
36+
contentType = 'text/css';
37+
break;
38+
}
39+
40+
fs.readFile(filePath, (error, content) => {
41+
if (error) {
42+
console.error(`Could not find file: ${filePath}`);
43+
response.writeHead(404, { 'Content-Type': contentType });
44+
response.end();
45+
} else {
46+
response.writeHead(200, { 'Content-Type': contentType });
47+
response.end(content, 'utf-8');
48+
}
49+
});
50+
});
51+
52+
// Start the server
53+
server.listen(8000, () => {
54+
console.info(new Date() + ' Page server is listening on port 8000');
55+
});
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*-----------------------------------------------------------------------------
2+
| Copyright (c) Jupyter Development Team.
3+
| Copyright (c) 2014-2017, PhosphorJS Contributors
4+
|
5+
| Distributed under the terms of the BSD 3-Clause License.
6+
|
7+
| The full license is in the file LICENSE, distributed with this software.
8+
|----------------------------------------------------------------------------*/
9+
@import '~@lumino/widgets/style/index.css';
10+
11+
body {
12+
display: flex;
13+
flex-direction: column;
14+
position: absolute;
15+
top: 0;
16+
left: 0;
17+
right: 0;
18+
bottom: 0;
19+
margin: 0;
20+
padding: 0;
21+
}
22+
23+
#main {
24+
flex: 1 1 auto;
25+
overflow: auto;
26+
padding: 10px;
27+
}
28+
29+
.lm-keyboardCaptureArea {
30+
border-radius: 5px;
31+
border: 3px dashed #88a;
32+
padding: 6px;
33+
margin: 6px;
34+
}
35+
36+
.lm-keyboardCaptureOutputArea kbd {
37+
background-color: #eee;
38+
border-radius: 5px;
39+
border: 3px solid #b4b4b4;
40+
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
41+
0 2px 0 0 rgba(255, 255, 255, 0.7) inset;
42+
color: #333;
43+
display: inline-block;
44+
font-size: 0.85em;
45+
font-weight: 700;
46+
line-height: 1;
47+
padding: 2px 4px;
48+
white-space: nowrap;
49+
}
50+
51+
.lm-keyboardCaptureOutputArea button {
52+
margin: 4px;
53+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"compilerOptions": {
3+
"declaration": false,
4+
"noImplicitAny": true,
5+
"noEmitOnError": true,
6+
"noUnusedLocals": true,
7+
"strictNullChecks": true,
8+
"inlineSourceMap": true,
9+
"module": "commonjs",
10+
"moduleResolution": "node",
11+
"target": "ES5",
12+
"outDir": "./build",
13+
"lib": [
14+
"ES5",
15+
"es2015.collection",
16+
"DOM",
17+
"ES2015.Promise",
18+
"ES2015.Iterable"
19+
]
20+
},
21+
"include": ["src/*"]
22+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
var path = require('path');
2+
3+
module.exports = {
4+
entry: './build/index.js',
5+
mode: 'development',
6+
output: {
7+
path: __dirname + '/build/',
8+
filename: 'bundle.example.js',
9+
publicPath: './build/'
10+
},
11+
module: {
12+
rules: [
13+
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
14+
{ test: /\.png$/, use: 'file-loader' }
15+
]
16+
},
17+
plugins: []
18+
};

0 commit comments

Comments
 (0)