mirror of
https://github.com/NaitLee/Cat-Printer.git
synced 2025-05-16 07:10:30 -07:00
Revised Keyboard mode
This commit is contained in:
parent
ab9ad406cb
commit
a8454cbf51
@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
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 ..
|
||||
|
@ -28,6 +28,7 @@ Currently:
|
||||
- Friendly!
|
||||
- Language support! You can participate in translation!
|
||||
- Good user interface, adaptive to PC/mobile and light/dark theme!
|
||||
- Accessibility features, everyone is considered!
|
||||
|
||||
- Feature-rich!
|
||||
- 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.
|
||||
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
|
||||
|
||||
That's what is going to be fun:
|
||||
|
||||
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:
|
||||
|
||||
|
@ -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 class="compact-menu">
|
||||
<button class="compact-button" data-panel="panel-print" data-i18n="print" data-key="8">Print</button>
|
||||
<button class="compact-button active" data-panel="panel-help" data-i18n="help" data-key="9">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-print" data-i18n="print" data-key="z">Print</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="c">Settings</button>
|
||||
</div>
|
||||
<div class="center">
|
||||
<!-- -->
|
||||
@ -121,7 +121,7 @@
|
||||
<span>🌎</span>
|
||||
<span data-i18n="language">Language</span>
|
||||
</h2>
|
||||
<select multiple id="select-language" data-key="5">
|
||||
<select multiple id="select-language" data-key="a">
|
||||
</select>
|
||||
<p data-i18n="press-tab-to-control-with-keyboard">Press Tab to control with keyboard</p>
|
||||
</div>
|
||||
@ -143,7 +143,7 @@
|
||||
<label for="large-font" data-i18n="large-font">Large Font</label>
|
||||
</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 id="dialog" class="hidden">
|
||||
<div class="shade"></div>
|
||||
@ -152,7 +152,7 @@
|
||||
<!-- Dialog content -->
|
||||
</div>
|
||||
<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 />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -49,6 +49,12 @@
|
||||
<td><a href="i18n-ext.js">i18n-ext.js</a></td>
|
||||
<td>I18n "extensions"</td>
|
||||
</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>
|
||||
<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>
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"$language": "Deutsch",
|
||||
"KeyboardLayout": "1234567890qwertzuiopasdfghjklyxcvbnm",
|
||||
"cat-printer": "Cat Printer",
|
||||
"printer": "Drucker",
|
||||
"device-": "Gerät:",
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
var fallbacks = [
|
||||
// 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
|
||||
'main.comp.js'
|
||||
];
|
||||
|
10
www/main.css
10
www/main.css
@ -434,13 +434,7 @@ hr {
|
||||
white-space: pre;
|
||||
line-height: 1em;
|
||||
font-family: 'DejaVu Sans Mono', 'Consolas', monospace;
|
||||
transform: translateY(calc(var(--font-size) * -1));
|
||||
}
|
||||
body.force-rtl #keyboard-shortcuts-layer span {
|
||||
transform: translate(
|
||||
calc(var(--font-size) * 2),
|
||||
calc(var(--font-size) * -1)
|
||||
);
|
||||
transform: translate(-1em, calc(var(--font-size) * -1));
|
||||
}
|
||||
|
||||
a {
|
||||
@ -580,7 +574,7 @@ body.high-contrast {
|
||||
transition-duration: 0s;
|
||||
}
|
||||
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 #notice * { border: var(--border) dashed var(--fore-color); }
|
||||
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 = [];
|
||||
dialog_input.value = '';
|
||||
dialog_input.style.display = have_input ? 'unset' : 'none';
|
||||
const keys = 'bn,.';
|
||||
let index = 1;
|
||||
for (let choice of choices) {
|
||||
let button = document.createElement('button');
|
||||
button.setAttribute('data-i18n', choice);
|
||||
button.setAttribute('data-key', index++);
|
||||
button.setAttribute('data-key', keys[index++]);
|
||||
button.innerText = i18n(choice);
|
||||
if (!have_input)
|
||||
button.addEventListener('click', () => dialog_input.value = choice);
|
||||
@ -463,97 +464,6 @@ async function testI18n(lang) {
|
||||
, 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 {
|
||||
promise;
|
||||
/** @type {CanvasController} */
|
||||
@ -580,6 +490,10 @@ class Main {
|
||||
let iframe = document.getElementById('frame');
|
||||
iframe.addEventListener('load', () => {
|
||||
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);
|
||||
});
|
||||
function apply_class(class_name, value) {
|
||||
@ -626,7 +540,7 @@ class Main {
|
||||
await this.loadConfig();
|
||||
if (this.settings['is_android'])
|
||||
document.getElementById('select-language').multiple = false;
|
||||
initKeyboardShortcuts();
|
||||
if (typeof initKeyboardShortcuts === 'function') initKeyboardShortcuts();
|
||||
this.searchDevices();
|
||||
document.querySelector('main').classList.remove('hard-hidden');
|
||||
document.getElementById('loading-screen').classList.add('hidden');
|
||||
|
Loading…
x
Reference in New Issue
Block a user