mirror of
https://github.com/NaitLee/Cat-Printer.git
synced 2025-05-16 15:20:12 -07:00
Revised Keyboard mode
This commit is contained in:
parent
ab9ad406cb
commit
a8454cbf51
@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
cd www
|
cd www
|
||||||
npx tsc --allowJs --outFile main.comp.js polyfill.js i18n-ext.js i18n.js image.js main.js
|
npx tsc --allowJs --outFile main.comp.js polyfill.js i18n-ext.js i18n.js image.js accessibility.js main.js
|
||||||
cd ..
|
cd ..
|
||||||
|
@ -28,6 +28,7 @@ Currently:
|
|||||||
- Friendly!
|
- Friendly!
|
||||||
- Language support! You can participate in translation!
|
- Language support! You can participate in translation!
|
||||||
- Good user interface, adaptive to PC/mobile and light/dark theme!
|
- Good user interface, adaptive to PC/mobile and light/dark theme!
|
||||||
|
- Accessibility features, everyone is considered!
|
||||||
|
|
||||||
- Feature-rich!
|
- Feature-rich!
|
||||||
- Web UI, for most people!
|
- Web UI, for most people!
|
||||||
|
@ -15,12 +15,37 @@ In simple cases, you just make a copy of already-there language file, and modify
|
|||||||
You should know what's your locale "code", for example "English (US)" is `en-US`. You can look at your browser locale configuration, or gather from the Web.
|
You should know what's your locale "code", for example "English (US)" is `en-US`. You can look at your browser locale configuration, or gather from the Web.
|
||||||
After that, add an entry in `list.json`.
|
After that, add an entry in `list.json`.
|
||||||
|
|
||||||
|
## Naming
|
||||||
|
|
||||||
|
Plain:
|
||||||
|
```json
|
||||||
|
"there-is-an-apple": "There is an apple"
|
||||||
|
```
|
||||||
|
|
||||||
|
With parameter(s):
|
||||||
|
```json
|
||||||
|
"there-are-0-apples-in-1-baskets": "There are {0} apples in {1} baskets"
|
||||||
|
```
|
||||||
|
|
||||||
|
With Conditions: *(language dependent)*
|
||||||
|
```json
|
||||||
|
"0-apples": {
|
||||||
|
"single": "{0} apple",
|
||||||
|
"multiple": "{0} apples"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Special Key:
|
||||||
|
```json
|
||||||
|
"KeyboardLayout": "1234567890qwertzuiopasdfghjklyxcvbnm"
|
||||||
|
```
|
||||||
|
|
||||||
## It seems can't satisfy another grammar
|
## It seems can't satisfy another grammar
|
||||||
|
|
||||||
That's what is going to be fun:
|
That's what is going to be fun:
|
||||||
|
|
||||||
This i18n implementation didn't do built-in basics.
|
This i18n implementation didn't do built-in basics.
|
||||||
Instead the grammar details is all done by code.
|
Instead the grammar details are all done by code.
|
||||||
|
|
||||||
That doesn't mean difficulty:
|
That doesn't mean difficulty:
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
- 友好!
|
- 友好!
|
||||||
- 语言支持!您可参与翻译!
|
- 语言支持!您可参与翻译!
|
||||||
- 良好的用户界面,可适应桌面/移动端/明暗主题!
|
- 良好的用户界面,可适应桌面/移动端/明暗主题!
|
||||||
|
- 无障碍功能,考虑到每一个人!
|
||||||
|
|
||||||
- 功能丰富!
|
- 功能丰富!
|
||||||
- 网页界面,所有人都可以用!
|
- 网页界面,所有人都可以用!
|
||||||
|
99
www/accessibility.js
Normal file
99
www/accessibility.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
|
||||||
|
function isHidden(element) {
|
||||||
|
let parents = [element];
|
||||||
|
while (parents[0].parentElement)
|
||||||
|
parents.unshift(parents[0].parentElement);
|
||||||
|
return parents.some(e => {
|
||||||
|
let rect = e.getBoundingClientRect();
|
||||||
|
return (
|
||||||
|
e.classList.contains('hidden') ||
|
||||||
|
e.classList.contains('hard-hidden') ||
|
||||||
|
e.style.display == 'none' ||
|
||||||
|
rect.width == 0 || rect.height == 0 ||
|
||||||
|
rect.x < 0 || rect.y < 0 ||
|
||||||
|
e.style.visibility == 'none' ||
|
||||||
|
e.style.opacity == '0'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toLocaleKey(key) {
|
||||||
|
const qwerty = '1234567890qwertyuiopasdfghjklzxcvbnm';
|
||||||
|
let keys, index;
|
||||||
|
if (
|
||||||
|
typeof i18n === 'undefined' ||
|
||||||
|
key.length !== 1 ||
|
||||||
|
(keys = i18n('KeyboardLayout')) === 'KeyboardLayout' ||
|
||||||
|
(index = qwerty.indexOf(key)) === -1
|
||||||
|
) return key;
|
||||||
|
return keys[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
function initKeyboardShortcuts() {
|
||||||
|
const layer = document.getElementById('keyboard-shortcuts-layer');
|
||||||
|
const dialog = document.getElementById('dialog');
|
||||||
|
let key, keys = 'qwertyuiopasdfghjklzxcvbnm';
|
||||||
|
let focus, focusing = false, started = false;
|
||||||
|
let inputs, shortcuts = {};
|
||||||
|
const mark_keys = () => {
|
||||||
|
if (dialog.classList.contains('hidden'))
|
||||||
|
inputs = Array.from(document.querySelectorAll('*[data-key]'));
|
||||||
|
else inputs = Array.from(document.querySelectorAll('#dialog *[data-key]'));
|
||||||
|
/** @type {{ [key: string]: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement }} */
|
||||||
|
let key_index = 0;
|
||||||
|
shortcuts = {};
|
||||||
|
if (focusing) shortcuts = { ESC: focus };
|
||||||
|
else
|
||||||
|
for (let input of inputs) {
|
||||||
|
if (isHidden(input)) continue;
|
||||||
|
let key = toLocaleKey(input.getAttribute('data-key') || keys[key_index++]);
|
||||||
|
shortcuts[key] = input;
|
||||||
|
}
|
||||||
|
// Array.from(layer.children).forEach(e => e.remove());
|
||||||
|
for (let i = layer.children.length; i <= inputs.length; i++) {
|
||||||
|
let span = document.createElement('span');
|
||||||
|
layer.appendChild(span);
|
||||||
|
}
|
||||||
|
let index = 0;
|
||||||
|
for (let key in shortcuts) {
|
||||||
|
let span = layer.children[index++];
|
||||||
|
let input = shortcuts[key];
|
||||||
|
let position = input.getBoundingClientRect();
|
||||||
|
let text = key.toUpperCase().replace(' ', 'SPACE');
|
||||||
|
if (span.innerText !== text) span.innerText = text;
|
||||||
|
span.style.top = position.y + 'px';
|
||||||
|
span.style.left = position.x + 'px';
|
||||||
|
span.style.display = '';
|
||||||
|
}
|
||||||
|
for (let i = index; i < layer.children.length; i++) {
|
||||||
|
layer.children[i].style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const start = () => setInterval(mark_keys, 1000);
|
||||||
|
const types_to_click = ['submit', 'file', 'checkbox', 'radio', 'A'];
|
||||||
|
document.body.addEventListener('keyup', (event) => {
|
||||||
|
key = event.key;
|
||||||
|
if (!started) {
|
||||||
|
if (key !== 'Tab') return;
|
||||||
|
mark_keys();
|
||||||
|
start();
|
||||||
|
started = true;
|
||||||
|
}
|
||||||
|
document.body.addEventListener('keyup', () =>
|
||||||
|
requestAnimationFrame(mark_keys)
|
||||||
|
, { once: true });
|
||||||
|
let input = shortcuts[key];
|
||||||
|
if (input) {
|
||||||
|
if (types_to_click.includes(input.type || input.tagName))
|
||||||
|
input.click();
|
||||||
|
else {
|
||||||
|
input.focus();
|
||||||
|
focusing = true;
|
||||||
|
}
|
||||||
|
focus = input;
|
||||||
|
} else if (key === 'Escape' && focus) {
|
||||||
|
focus.blur();
|
||||||
|
focusing = !focusing;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -91,9 +91,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="compact-menu">
|
<div class="compact-menu">
|
||||||
<button class="compact-button" data-panel="panel-print" data-i18n="print" data-key="8">Print</button>
|
<button class="compact-button" data-panel="panel-print" data-i18n="print" data-key="z">Print</button>
|
||||||
<button class="compact-button active" data-panel="panel-help" data-i18n="help" data-key="9">Help</button>
|
<button class="compact-button active" data-panel="panel-help" data-i18n="help" data-key="x">Help</button>
|
||||||
<button class="compact-button" data-panel="panel-settings" data-i18n="settings" data-key="0">Settings</button>
|
<button class="compact-button" data-panel="panel-settings" data-i18n="settings" data-key="c">Settings</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<!-- -->
|
<!-- -->
|
||||||
@ -121,7 +121,7 @@
|
|||||||
<span>🌎</span>
|
<span>🌎</span>
|
||||||
<span data-i18n="language">Language</span>
|
<span data-i18n="language">Language</span>
|
||||||
</h2>
|
</h2>
|
||||||
<select multiple id="select-language" data-key="5">
|
<select multiple id="select-language" data-key="a">
|
||||||
</select>
|
</select>
|
||||||
<p data-i18n="press-tab-to-control-with-keyboard">Press Tab to control with keyboard</p>
|
<p data-i18n="press-tab-to-control-with-keyboard">Press Tab to control with keyboard</p>
|
||||||
</div>
|
</div>
|
||||||
@ -143,7 +143,7 @@
|
|||||||
<label for="large-font" data-i18n="large-font">Large Font</label>
|
<label for="large-font" data-i18n="large-font">Large Font</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<iframe id="frame" src="about:blank" name="frame" title="frame" data-key="6"></iframe>
|
<iframe id="frame" src="about:blank" name="frame" title="frame" data-key="v"></iframe>
|
||||||
</div>
|
</div>
|
||||||
<div id="dialog" class="hidden">
|
<div id="dialog" class="hidden">
|
||||||
<div class="shade"></div>
|
<div class="shade"></div>
|
||||||
@ -152,7 +152,7 @@
|
|||||||
<!-- Dialog content -->
|
<!-- Dialog content -->
|
||||||
</div>
|
</div>
|
||||||
<div id="dialog-choices">
|
<div id="dialog-choices">
|
||||||
<input id="dialog-input" type="text" id="dialog-input" placeholder="" data-key="4">
|
<input id="dialog-input" type="text" id="dialog-input" placeholder="" data-key="m">
|
||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,6 +49,12 @@
|
|||||||
<td><a href="i18n-ext.js">i18n-ext.js</a></td>
|
<td><a href="i18n-ext.js">i18n-ext.js</a></td>
|
||||||
<td>I18n "extensions"</td>
|
<td>I18n "extensions"</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="accessibility.js">accessibility.js</a></td>
|
||||||
|
<td><a href="http://creativecommons.org/publicdomain/zero/1.0/legalcode">CC0-1.0-only</a></td>
|
||||||
|
<td><a href="accessibility.js">accessibility.js</a></td>
|
||||||
|
<td>Accessibility features</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="main.js">main.js</a></td>
|
<td><a href="main.js">main.js</a></td>
|
||||||
<td><a href="https://www.gnu.org/licenses/gpl-3.0.html">GNU-GPL-3.0-or-later</a></td>
|
<td><a href="https://www.gnu.org/licenses/gpl-3.0.html">GNU-GPL-3.0-or-later</a></td>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$language": "Deutsch",
|
"$language": "Deutsch",
|
||||||
|
"KeyboardLayout": "1234567890qwertzuiopasdfghjklyxcvbnm",
|
||||||
"cat-printer": "Cat Printer",
|
"cat-printer": "Cat Printer",
|
||||||
"printer": "Drucker",
|
"printer": "Drucker",
|
||||||
"device-": "Gerät:",
|
"device-": "Gerät:",
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
var fallbacks = [
|
var fallbacks = [
|
||||||
// main scripts, which we will directly modify
|
// main scripts, which we will directly modify
|
||||||
'i18n-ext.js', 'i18n.js', 'image.js', 'main.js',
|
'i18n-ext.js', 'i18n.js', 'image.js', 'accessibility.js', 'main.js',
|
||||||
// "compatibility" script, produced with eg. typescript tsc
|
// "compatibility" script, produced with eg. typescript tsc
|
||||||
'main.comp.js'
|
'main.comp.js'
|
||||||
];
|
];
|
||||||
|
10
www/main.css
10
www/main.css
@ -434,13 +434,7 @@ hr {
|
|||||||
white-space: pre;
|
white-space: pre;
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
font-family: 'DejaVu Sans Mono', 'Consolas', monospace;
|
font-family: 'DejaVu Sans Mono', 'Consolas', monospace;
|
||||||
transform: translateY(calc(var(--font-size) * -1));
|
transform: translate(-1em, calc(var(--font-size) * -1));
|
||||||
}
|
|
||||||
body.force-rtl #keyboard-shortcuts-layer span {
|
|
||||||
transform: translate(
|
|
||||||
calc(var(--font-size) * 2),
|
|
||||||
calc(var(--font-size) * -1)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@ -580,7 +574,7 @@ body.high-contrast {
|
|||||||
transition-duration: 0s;
|
transition-duration: 0s;
|
||||||
}
|
}
|
||||||
body.high-contrast .shade { transition-duration: 0s; opacity: 1; }
|
body.high-contrast .shade { transition-duration: 0s; opacity: 1; }
|
||||||
body.high-contrast * { background-color: var(--back-color); }
|
/* body.high-contrast * { background-color: var(--back-color); } */
|
||||||
body.high-contrast .logo, canvas { filter: unset !important; }
|
body.high-contrast .logo, canvas { filter: unset !important; }
|
||||||
body.high-contrast #notice * { border: var(--border) dashed var(--fore-color); }
|
body.high-contrast #notice * { border: var(--border) dashed var(--fore-color); }
|
||||||
body.high-contrast a:any-link { color: #00f; }
|
body.high-contrast a:any-link { color: #00f; }
|
||||||
|
100
www/main.js
100
www/main.js
@ -83,11 +83,12 @@ const Dialog = (function() {
|
|||||||
last_choices = [];
|
last_choices = [];
|
||||||
dialog_input.value = '';
|
dialog_input.value = '';
|
||||||
dialog_input.style.display = have_input ? 'unset' : 'none';
|
dialog_input.style.display = have_input ? 'unset' : 'none';
|
||||||
|
const keys = 'bn,.';
|
||||||
let index = 1;
|
let index = 1;
|
||||||
for (let choice of choices) {
|
for (let choice of choices) {
|
||||||
let button = document.createElement('button');
|
let button = document.createElement('button');
|
||||||
button.setAttribute('data-i18n', choice);
|
button.setAttribute('data-i18n', choice);
|
||||||
button.setAttribute('data-key', index++);
|
button.setAttribute('data-key', keys[index++]);
|
||||||
button.innerText = i18n(choice);
|
button.innerText = i18n(choice);
|
||||||
if (!have_input)
|
if (!have_input)
|
||||||
button.addEventListener('click', () => dialog_input.value = choice);
|
button.addEventListener('click', () => dialog_input.value = choice);
|
||||||
@ -463,97 +464,6 @@ async function testI18n(lang) {
|
|||||||
, true);
|
, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isHidden(element) {
|
|
||||||
let parents = [element];
|
|
||||||
while (parents[0].parentElement)
|
|
||||||
parents.unshift(parents[0].parentElement);
|
|
||||||
return parents.some(e => {
|
|
||||||
let rect = e.getBoundingClientRect();
|
|
||||||
return (
|
|
||||||
e.classList.contains('hidden') ||
|
|
||||||
e.classList.contains('hard-hidden') ||
|
|
||||||
e.style.display == 'none' ||
|
|
||||||
rect.width == 0 || rect.height == 0 ||
|
|
||||||
rect.x < 0 || rect.y < 0 ||
|
|
||||||
e.style.visibility == 'none' ||
|
|
||||||
e.style.opacity == '0'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function initKeyboardShortcuts() {
|
|
||||||
const layer = document.getElementById('keyboard-shortcuts-layer');
|
|
||||||
const dialog = document.getElementById('dialog');
|
|
||||||
let key, keys = 'qwertyuiopasdfghjklzxcvbnm';
|
|
||||||
let focused, onbreak = false, started = false;
|
|
||||||
let inputs, shortcuts = {};
|
|
||||||
const mark_keys = () => {
|
|
||||||
if (dialog.classList.contains('hidden'))
|
|
||||||
inputs = Array.from(document.querySelectorAll('*[data-key]'));
|
|
||||||
else inputs = Array.from(document.querySelectorAll('#dialog *[data-key]'));
|
|
||||||
/** @type {{ [key: string]: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement }} */
|
|
||||||
let key_index = 0;
|
|
||||||
shortcuts = {};
|
|
||||||
for (let input of inputs) {
|
|
||||||
if (isHidden(input)) continue;
|
|
||||||
let key = input.getAttribute('data-key') || keys[key_index++];
|
|
||||||
shortcuts[key] = input;
|
|
||||||
}
|
|
||||||
Array.from(layer.children).forEach(e => e.remove());
|
|
||||||
for (let key in shortcuts) {
|
|
||||||
if (onbreak) {
|
|
||||||
let rect = focused.getBoundingClientRect();
|
|
||||||
let span = document.createElement('span');
|
|
||||||
span.innerText = i18n('ESCAPE');
|
|
||||||
span.style.top = rect.y + 'px';
|
|
||||||
span.style.left = rect.x + 'px';
|
|
||||||
layer.appendChild(span);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let input = shortcuts[key];
|
|
||||||
let position = input.getBoundingClientRect();
|
|
||||||
let span = document.createElement('span');
|
|
||||||
span.innerText = i18n(key.toUpperCase().replace(' ', 'SPACE'));
|
|
||||||
span.style.top = position.y + 'px';
|
|
||||||
span.style.right = (window.innerWidth - position.x) + 'px';
|
|
||||||
layer.appendChild(span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const start = () => setInterval(mark_keys, 1000);
|
|
||||||
document.body.addEventListener('keyup', (event) => {
|
|
||||||
document.body.addEventListener('keyup', () => requestAnimationFrame(mark_keys), { once: true });
|
|
||||||
key = event.key;
|
|
||||||
if (key === 'Tab') {
|
|
||||||
if (!started) { start(); started = true; }
|
|
||||||
mark_keys();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let input = shortcuts[key];
|
|
||||||
if (input) {
|
|
||||||
switch (input.type || input.tagName) {
|
|
||||||
case 'range':
|
|
||||||
case 'text':
|
|
||||||
case 'number':
|
|
||||||
case 'tel':
|
|
||||||
case 'date':
|
|
||||||
case 'select-one':
|
|
||||||
case 'select-multiple':
|
|
||||||
case 'DIV':
|
|
||||||
case 'TEXTAREA':
|
|
||||||
input.focus();
|
|
||||||
onbreak = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
input.click();
|
|
||||||
}
|
|
||||||
focused = input;
|
|
||||||
} else if (key === 'Escape' && focused) {
|
|
||||||
focused.blur();
|
|
||||||
onbreak = !onbreak;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class Main {
|
class Main {
|
||||||
promise;
|
promise;
|
||||||
/** @type {CanvasController} */
|
/** @type {CanvasController} */
|
||||||
@ -580,6 +490,10 @@ class Main {
|
|||||||
let iframe = document.getElementById('frame');
|
let iframe = document.getElementById('frame');
|
||||||
iframe.addEventListener('load', () => {
|
iframe.addEventListener('load', () => {
|
||||||
iframe.contentDocument.body.classList.value = document.body.classList.value;
|
iframe.contentDocument.body.classList.value = document.body.classList.value;
|
||||||
|
iframe.contentDocument.body.addEventListener('keyup', (event) => {
|
||||||
|
if (event.key === 'Escape')
|
||||||
|
document.body.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' }));
|
||||||
|
});
|
||||||
applyI18nToDom(iframe.contentDocument);
|
applyI18nToDom(iframe.contentDocument);
|
||||||
});
|
});
|
||||||
function apply_class(class_name, value) {
|
function apply_class(class_name, value) {
|
||||||
@ -626,7 +540,7 @@ class Main {
|
|||||||
await this.loadConfig();
|
await this.loadConfig();
|
||||||
if (this.settings['is_android'])
|
if (this.settings['is_android'])
|
||||||
document.getElementById('select-language').multiple = false;
|
document.getElementById('select-language').multiple = false;
|
||||||
initKeyboardShortcuts();
|
if (typeof initKeyboardShortcuts === 'function') initKeyboardShortcuts();
|
||||||
this.searchDevices();
|
this.searchDevices();
|
||||||
document.querySelector('main').classList.remove('hard-hidden');
|
document.querySelector('main').classList.remove('hard-hidden');
|
||||||
document.getElementById('loading-screen').classList.add('hidden');
|
document.getElementById('loading-screen').classList.add('hidden');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user