clean js routine; concat script; try fix macos

This commit is contained in:
NaitLee 2022-05-04 12:07:14 +08:00
parent 002072b586
commit 1b4402c6d2
13 changed files with 157 additions and 99 deletions

View File

@ -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 accessibility.js main.js npx tsc $@ --allowJs --outFile main.comp.js $(cat all_js.txt)
cd .. cd ..

View File

@ -364,11 +364,10 @@ class PrinterDriver(Commander):
elif (identifier not in Models and elif (identifier not in Models and
identifier[2::3] != ':::::' and len(identifier.replace('-', '')) != 32): identifier[2::3] != ':::::' and len(identifier.replace('-', '')) != 32):
error('model-0-is-not-supported-yet', identifier, exception=PrinterError) error('model-0-is-not-supported-yet', identifier, exception=PrinterError)
scanner = BleakScanner() # scanner = BleakScanner()
devices = self.loop( devices = self.loop(
scanner.discover(self.scan_timeout) BleakScanner.discover(self.scan_timeout)
) )
devices = [dev for dev in devices if dev.name in Models]
if identifier is not None: if identifier is not None:
if identifier in Models: if identifier in Models:
devices = [dev for dev in devices if dev.name == identifier] devices = [dev for dev in devices if dev.name == identifier]
@ -694,7 +693,7 @@ def main():
'Run the `_main` routine while catching exceptions' 'Run the `_main` routine while catching exceptions'
try: try:
_main() _main()
except (BleakError, AttributeError) as e: except BleakError as e:
error_message = str(e) error_message = str(e)
if ( if (
('not turned on' in error_message) or # windows or android ('not turned on' in error_message) or # windows or android
@ -702,15 +701,15 @@ def main():
getattr(e, 'dbus_error') == 'org.bluez.Error.NotReady') getattr(e, 'dbus_error') == 'org.bluez.Error.NotReady')
): ):
fatal(I18n['please-enable-bluetooth'], code=ExitCodes.GeneralError) fatal(I18n['please-enable-bluetooth'], code=ExitCodes.GeneralError)
elif (
(isinstance(e, AttributeError) and # macos, possibly?
'CentralManagerDelegate' in error_message)
):
fatal(I18n['please-enable-bluetooth-or-try-to-reboot'], code=ExitCodes.GeneralError)
else: else:
raise raise
except PrinterError as e: except PrinterError as e:
fatal(e.message_localized, code=ExitCodes.PrinterError) fatal(e.message_localized, code=ExitCodes.PrinterError)
except RuntimeError as e:
if 'no running event loop' in str(e):
pass # non-sense
else:
raise
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -46,6 +46,13 @@ def mime(url: str):
'Get pre-defined MIME type of a certain url by extension name' 'Get pre-defined MIME type of a certain url by extension name'
return mime_type.get(url.rsplit('.', 1)[-1], mime_type['octet-stream']) return mime_type.get(url.rsplit('.', 1)[-1], mime_type['octet-stream'])
def concat_files(*paths, prefix_format='', buffer=4 * 1024 * 1024):
for path in paths:
yield prefix_format.format(path).encode('utf-8')
with open(path, 'rb') as file:
while data := file.read(buffer):
yield data
class PrinterServerHandler(BaseHTTPRequestHandler): class PrinterServerHandler(BaseHTTPRequestHandler):
'(Local) server handler for Cat Printer Web interface' '(Local) server handler for Cat Printer Web interface'
@ -64,6 +71,7 @@ class PrinterServerHandler(BaseHTTPRequestHandler):
_settings_blacklist = ( _settings_blacklist = (
'printer', 'is_android' 'printer', 'is_android'
) )
all_js: list = []
printer: PrinterDriver = PrinterDriver() printer: PrinterDriver = PrinterDriver()
@ -84,17 +92,31 @@ class PrinterServerHandler(BaseHTTPRequestHandler):
pass pass
def do_GET(self): def do_GET(self):
'Called when server get a GET http request' 'Called when server got a GET http request'
path = 'www' + self.path # prepare
if self.path == '/': path, _, _args = self.path.partition('?')
path += 'index.html' if '/..' in path or '../' in path:
if '/..' in path:
return return
if path == '/':
path += 'index.html'
# special
if path.startswith('/~'):
action = path[2:]
if action == 'every.js':
self.send_response(200)
self.send_header('Content-Type', mime(path))
self.end_headers()
for data in concat_files(*(self.all_js), prefix_format='\n// {0}\n'):
self.wfile.write(data)
return
path = 'www' + path
# not found
if not os.path.isfile(path): if not os.path.isfile(path):
self.send_response(404) self.send_response(404)
self.send_header('Content-Type', mime('txt')) self.send_header('Content-Type', mime('txt'))
self.end_headers() self.end_headers()
return return
# static
self.send_response(200) self.send_response(200)
self.send_header('Content-Type', mime(path)) self.send_header('Content-Type', mime(path))
# self.send_header('Content-Size', str(os.stat(path).st_size)) # self.send_header('Content-Size', str(os.stat(path).st_size))
@ -214,7 +236,7 @@ class PrinterServerHandler(BaseHTTPRequestHandler):
sys.exit(0) sys.exit(0)
def do_POST(self): def do_POST(self):
'Called when server get a POST http request' 'Called when server got a POST http request'
content_length = int(self.headers.get('Content-Length', -1)) content_length = int(self.headers.get('Content-Length', -1))
if (content_length < -1 or if (content_length < -1 or
content_length > self.max_payload content_length > self.max_payload
@ -239,16 +261,22 @@ class PrinterServerHandler(BaseHTTPRequestHandler):
'name': 'BleakError', 'name': 'BleakError',
'details': str(e) 'details': str(e)
}) })
except EOFError as e:
# mostly, device disconnected but not by this program
self.api_fail({
'name': 'EOFError',
'details': ''
})
except RuntimeError as e:
self.api_fail({
'name': 'RuntimeError',
'details': str(e)
})
except PrinterError as e: except PrinterError as e:
self.api_fail({ self.api_fail({
'name': e.message, 'name': e.message,
'details': e.message_localized 'details': e.message_localized
}) })
except EOFError as e:
self.api_fail({
'name': 'EOFError',
'details': ''
})
except Exception as e: except Exception as e:
self.api_fail({ self.api_fail({
'name': 'Exception', 'name': 'Exception',
@ -272,6 +300,10 @@ class PrinterServer(HTTPServer):
def finish_request(self, request, client_address): def finish_request(self, request, client_address):
if self.handler is None: if self.handler is None:
self.handler = self.handler_class(request, client_address, self) self.handler = self.handler_class(request, client_address, self)
with open(os.path.join('www', 'all_js.txt'), 'r', encoding='utf-8') as file:
for path in file.read().split('\n'):
if path != '':
self.handler.all_js.append(os.path.join('www', path))
return return
self.handler.__init__(request, client_address, self) self.handler.__init__(request, client_address, self)

View File

@ -1 +1 @@
0.4.0 0.4.1

View File

@ -39,22 +39,30 @@ function keyToLetter(key) {
return map[key] || key; return map[key] || key;
} }
function keyFromCode(code) {
const map = {
9: 'Tab'
};
return map[code] || String.fromCharCode(code);
}
function initKeyboardShortcuts() { function initKeyboardShortcuts() {
const layer = document.getElementById('keyboard-shortcuts-layer'); const layer = document.getElementById('keyboard-shortcuts-layer');
const dialog = document.getElementById('dialog'); const dialog = document.getElementById('dialog');
const keys = 'qwertyuiopasdfghjklzxcvbnm'; const keys = 'qwertyuiopasdfghjklzxcvbnm';
let focusing = false, started = false; let focusing = false, started = false;
let shortcuts = {}; let shortcuts = {};
let key, focus, inputs; let focus, inputs;
const mark_keys = () => { const mark_keys = () => {
let index; document.querySelectorAll(':focus').forEach(e => e.isSameNode(focus) || e.blur());
let index, key;
if (dialog.classList.contains('hidden')) if (dialog.classList.contains('hidden'))
inputs = Array.from(document.querySelectorAll('*[data-key]')); inputs = Array.from(document.querySelectorAll('*[data-key]'));
else inputs = Array.from(document.querySelectorAll('#dialog *[data-key]')); else inputs = Array.from(document.querySelectorAll('#dialog *[data-key]'));
/** @type {{ [key: string]: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement }} */ /** @type {{ [key: string]: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement }} */
let keys2 = keys.split(''); let keys2 = keys.split('');
shortcuts = {}; shortcuts = {};
if (focusing) shortcuts = { ESC: focus }; if (focusing) shortcuts = { 'ESC': focus };
else else
for (let input of inputs) { for (let input of inputs) {
if (isHidden(input)) continue; if (isHidden(input)) continue;
@ -69,14 +77,14 @@ function initKeyboardShortcuts() {
layer.appendChild(span); layer.appendChild(span);
} }
index = 0; index = 0;
for (let key in shortcuts) { for (key in shortcuts) {
let span = layer.children[index++]; let span = layer.children[index++];
let input = shortcuts[key]; let input = shortcuts[key];
let position = input.getBoundingClientRect(); let position = input.getBoundingClientRect();
let text = i18n(keyToLetter(key.toUpperCase())); let text = i18n(keyToLetter(key.toUpperCase()));
if (span.innerText !== text) span.innerText = text; if (span.innerText !== text) span.innerText = text;
span.style.top = position.y + 'px'; span.style.top = (position.y || position.top) + 'px';
span.style.left = position.x + 'px'; span.style.left = (position.x || position.left) + 'px';
span.style.display = ''; span.style.display = '';
} }
for (let i = index; i < layer.children.length; i++) { for (let i = index; i < layer.children.length; i++) {
@ -86,17 +94,15 @@ function initKeyboardShortcuts() {
const start = () => setInterval(mark_keys, 1000); const start = () => setInterval(mark_keys, 1000);
const types_to_click = ['submit', 'file', 'checkbox', 'radio', 'A']; const types_to_click = ['submit', 'file', 'checkbox', 'radio', 'A'];
document.body.addEventListener('keyup', (event) => { document.body.addEventListener('keyup', (event) => {
key = event.key; let key = event.key || keyFromCode(event.keyCode);
if (!started) { if (!started) {
if (key !== 'Tab') return; if (key !== 'Tab') return;
mark_keys(); mark_keys();
start(); start();
started = true; started = true;
} }
document.body.addEventListener('keyup', () => requestAnimationFrame(mark_keys)
requestAnimationFrame(mark_keys) let input = shortcuts[key];
, { once: true });
let input = shortcuts[key.toLocaleLowerCase()];
if (input) { if (input) {
if (types_to_click.includes(input.type || input.tagName)) if (types_to_click.includes(input.type || input.tagName))
input.dispatchEvent(new MouseEvent(event.shiftKey ? 'contextmenu' : 'click')); input.dispatchEvent(new MouseEvent(event.shiftKey ? 'contextmenu' : 'click'));
@ -105,7 +111,7 @@ function initKeyboardShortcuts() {
focusing = true; focusing = true;
} }
focus = input; focus = input;
} else if (key === 'Escape' && focus) { } else if ((key === 'Escape' || !event.isTrusted) && focus) {
focus.blur(); focus.blur();
focusing = !focusing; focusing = !focusing;
} }

6
www/all_js.txt Normal file
View File

@ -0,0 +1,6 @@
polyfill.js
i18n-ext.js
i18n.js
image.js
accessibility.js
main.js

View File

@ -46,7 +46,7 @@
</label> --><br /> </label> --><br />
<!-- "brightness" is historically "threshold" --> <!-- "brightness" is historically "threshold" -->
<label for="threshold" data-i18n="brightness-">Brightness:</label> <label for="threshold" data-i18n="brightness-">Brightness:</label>
<input type="range" min="0" max="256" value="86" step="1" id="threshold" data-key data-default /> <input type="range" min="0" max="256" value="86" step="16" id="threshold" data-key data-default />
<br /> <br />
<input type="checkbox" name="transparent-as-white" id="transparent-as-white" data-key checked /> <input type="checkbox" name="transparent-as-white" id="transparent-as-white" data-key checked />
<label for="transparent-as-white" data-i18n="transparent-as-white">Transparent as White</label> <label for="transparent-as-white" data-i18n="transparent-as-white">Transparent as White</label>

View File

@ -26,6 +26,18 @@
</thead> </thead>
<tbody> <tbody>
<tr>
<td><a href="~every.js">~every.js</a></td>
<td><a href="https://www.gnu.org/licenses/gpl-3.0.html">GPL-3.0</a></td>
<td><a href="main.js">main.js</a></td>
<td>Dynamic concatenation of all development scripts</td>
</tr>
<tr>
<td><a href="main.comp.js">main.comp.js</a></td>
<td><a href="https://www.gnu.org/licenses/gpl-3.0.html">GPL-3.0</a></td>
<td><a href="main.js">main.js</a></td>
<td>All following development scripts, transpiled for compatibility.</td>
</tr>
<tr> <tr>
<td><a href="loader.js">loader.js</a></td> <td><a href="loader.js">loader.js</a></td>
<td><a href="http://creativecommons.org/publicdomain/zero/1.0/legalcode">CC0-1.0</a></td> <td><a href="http://creativecommons.org/publicdomain/zero/1.0/legalcode">CC0-1.0</a></td>
@ -33,10 +45,16 @@
<td>For dynamically loading other scripts, and fallback if there are problems.</td> <td>For dynamically loading other scripts, and fallback if there are problems.</td>
</tr> </tr>
<tr> <tr>
<td><a href="image.js">image.js</a></td> <td><a href="polyfill.js">polyfill.js</a></td>
<td><a href="http://creativecommons.org/publicdomain/zero/1.0/legalcode">CC0-1.0</a></td> <td><a href="http://creativecommons.org/publicdomain/zero/1.0/legalcode">CC0-1.0</a></td>
<td><a href="image.js">image.js</a></td> <td><a href="polyfill.js">polyfill.js</a></td>
<td>Contains functions for image manipulation and public algorithms of image monochrome filters.</td> <td>Put features that are not supported by old browsers.</td>
</tr>
<tr>
<td><a href="i18n-ext.js">i18n-ext.js</a></td>
<td><a href="http://creativecommons.org/publicdomain/zero/1.0/legalcode">CC0-1.0</a></td>
<td><a href="i18n-ext.js">i18n-ext.js</a></td>
<td>I18n "extensions"</td>
</tr> </tr>
<tr> <tr>
<td><a href="i18n.js">i18n.js</a></td> <td><a href="i18n.js">i18n.js</a></td>
@ -45,10 +63,10 @@
<td>For internationalization (language support)</td> <td>For internationalization (language support)</td>
</tr> </tr>
<tr> <tr>
<td><a href="i18n-ext.js">i18n-ext.js</a></td> <td><a href="image.js">image.js</a></td>
<td><a href="http://creativecommons.org/publicdomain/zero/1.0/legalcode">CC0-1.0</a></td> <td><a href="http://creativecommons.org/publicdomain/zero/1.0/legalcode">CC0-1.0</a></td>
<td><a href="i18n-ext.js">i18n-ext.js</a></td> <td><a href="image.js">image.js</a></td>
<td>I18n "extensions"</td> <td>For canvas image manipulation</td>
</tr> </tr>
<tr> <tr>
<td><a href="accessibility.js">accessibility.js</a></td> <td><a href="accessibility.js">accessibility.js</a></td>
@ -62,18 +80,6 @@
<td><a href="main.js">main.js</a></td> <td><a href="main.js">main.js</a></td>
<td>The main script for Cat-Printer</td> <td>The main script for Cat-Printer</td>
</tr> </tr>
<tr>
<td><a href="polyfill.js">polyfill.js</a></td>
<td><a href="http://creativecommons.org/publicdomain/zero/1.0/legalcode">CC0-1.0</a></td>
<td><a href="polyfill.js">polyfill.js</a></td>
<td>Put features that are not supported by old browsers.</td>
</tr>
<tr>
<td><a href="main.comp.js">main.comp.js</a></td>
<td><a href="https://www.gnu.org/licenses/gpl-3.0.html">GPL-3.0</a></td>
<td><a href="main.js">main.js</a></td>
<td>A bundle of transpiled scripts (polyfill.js, image.js, i18n.js, main.js), for compatibility to old browsers.</td>
</tr>
</tbody> </tbody>
</table> </table>

View File

@ -119,5 +119,5 @@
"reset-configuration-": "Reset configuration?", "reset-configuration-": "Reset configuration?",
"brightness-": "Brightness:", "brightness-": "Brightness:",
"text-printing-mode": "Text Printing Mode", "text-printing-mode": "Text Printing Mode",
"please-enable-bluetooth-or-try-to-reboot": "Please enable bluetooth or try to reboot" "internal-error-please-see-terminal": "Internal error, please see terminal"
} }

View File

@ -114,5 +114,5 @@
"reset-configuration-": "要重置配置吗?", "reset-configuration-": "要重置配置吗?",
"brightness-": "亮度:", "brightness-": "亮度:",
"text-printing-mode": "文字打印模式", "text-printing-mode": "文字打印模式",
"please-enable-bluetooth-or-try-to-reboot": "请启用蓝牙或尝试重启" "internal-error-please-see-terminal": "内部错误,请检查终端"
} }

View File

@ -4,12 +4,11 @@
*/ */
(function() { (function() {
var fallbacks = [ var fallbacks;
// main scripts, which we will directly modify if (location.href.indexOf('?debug') !== -1)
'i18n-ext.js', 'i18n.js', 'image.js', 'accessibility.js', 'main.js', fallbacks = ['i18n-ext.js', 'i18n.js', 'image.js', 'accessibility.js', 'main.js'];
// "compatibility" script, produced with eg. typescript tsc else
'main.comp.js' fallbacks = ['~every.js', 'main.comp.js'];
];
var trial_count = 0; var trial_count = 0;
/** /**
* Try to load next "fallback" script, * Try to load next "fallback" script,
@ -21,6 +20,7 @@
var script = document.createElement('script'); var script = document.createElement('script');
script.addEventListener('load', function() { script.addEventListener('load', function() {
if (typeof main === 'undefined') { if (typeof main === 'undefined') {
// the script can't be 'unrun', though
script.remove(); script.remove();
try_load(); try_load();
} else { } else {

View File

@ -82,7 +82,7 @@ input[type="number"], input[type="text"] {
} }
button:hover { button:hover {
margin: 0; margin: 0;
padding: var(--span) calc(var(--span-double)); padding: var(--span) var(--span-double);
min-width: calc(6em + var(--span-double)); min-width: calc(6em + var(--span-double));
} }
button:active { button:active {

View File

@ -418,7 +418,7 @@ function applyI18nToDom(doc) {
element.firstChild.textContent = translated_string; element.firstChild.textContent = translated_string;
}); });
} }
async function initI18n() { async function initI18n(current_language) {
if (typeof i18n === 'undefined') return; if (typeof i18n === 'undefined') return;
/** @type {HTMLSelectElement} */ /** @type {HTMLSelectElement} */
let language_options = document.getElementById('select-language'); let language_options = document.getElementById('select-language');
@ -429,9 +429,6 @@ async function initI18n() {
i18n.add(value, await fetch(`/lang/${value}.json`).then(r => r.json()), true); i18n.add(value, await fetch(`/lang/${value}.json`).then(r => r.json()), true);
applyI18nToDom(); applyI18nToDom();
} }
language_options.addEventListener('change', () => {
language_options.selectedOptions.item(0).click();
});
for (let code in list) { for (let code in list) {
let option = document.createElement('option'); let option = document.createElement('option');
option.value = code; option.value = code;
@ -440,35 +437,28 @@ async function initI18n() {
/** @type {HTMLOptionElement} */ /** @type {HTMLOptionElement} */
let option = event.currentTarget; let option = event.currentTarget;
let value = option.value; let value = option.value;
// option.selected = true; option.selected = true;
language_options.selectedIndex = option.index; language_options.selectedIndex = option.index;
use_language(value); use_language(value);
Notice.note('welcome'); Notice.note('welcome');
}); });
language_options.appendChild(option); language_options.appendChild(option);
} }
apply_default: if (!navigator.languages) {
for (let code of navigator.languages) { if (!navigator.language) return;
if (list[code]) { else navigator.languages = [navigator.language, 'en-US'];
for (let option of language_options.children) {
if (option.value === code) {
// option.setAttribute('data-default', '');
option.setAttribute('data-default', '');
option.click();
i18n.useLanguage(navigator.languages[0]);
for (let language of navigator.languages) {
if (!list[language]) return;
let data = await fetch(`/lang/${language}.json`)
.then(response => response.ok ? response.json() : null);
if (data !== null) {
i18n.add(language, data);
}
}
break apply_default;
}
}
}
} }
if (current_language) {
for (let option of language_options.children)
if (option.value === current_language)
option.click();
} else for (let code of navigator.languages)
if (list[code]) for (let option of language_options.children)
if (option.value === code) {
option.setAttribute('data-default', '');
if (!current_language) option.click();
return;
}
} }
async function testI18n(lang) { async function testI18n(lang) {
@ -500,14 +490,18 @@ class Main {
this.setters = {}; this.setters = {};
// window.addEventListener('unload', () => this.exit()); // window.addEventListener('unload', () => this.exit());
this.promise = new Promise(async (resolve, reject) => { this.promise = new Promise(async (resolve, reject) => {
await initI18n();
/** @type {HTMLIFrameElement} */ /** @type {HTMLIFrameElement} */
let iframe = document.getElementById('frame'); let iframe = document.getElementById('frame');
iframe.addEventListener('load', () => { iframe.addEventListener('load', () => {
if (!iframe.contentWindow.NodeList.prototype.forEach)
iframe.contentWindow.NodeList.prototype.forEach = NodeList.prototype.forEach;
iframe.contentDocument.body.classList.value = document.body.classList.value; iframe.contentDocument.body.classList.value = document.body.classList.value;
iframe.contentDocument.body.addEventListener('keyup', (event) => { iframe.contentDocument.body.addEventListener('keyup', (event) => {
if (event.key === 'Escape') if (event.key === 'Escape' || event.keyCode === 27) {
document.body.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' })); document.body.dispatchEvent(
new KeyboardEvent('keyup', { key: 'Escape', keyCode: 27 })
);
}
}); });
applyI18nToDom(iframe.contentDocument); applyI18nToDom(iframe.contentDocument);
}); });
@ -517,6 +511,10 @@ class Main {
d.body.classList.remove(class_name) d.body.classList.remove(class_name)
); );
} }
await this.loadConfig();
await initI18n(this.settings['language']);
this.canvasController = new CanvasController(); this.canvasController = new CanvasController();
putEvent('#button-exit', 'click', () => this.exit(false), this); putEvent('#button-exit', 'click', () => this.exit(false), this);
putEvent('#button-exit', 'contextmenu', putEvent('#button-exit', 'contextmenu',
@ -531,7 +529,6 @@ class Main {
(value) => this.settings['text_mode'] = (value === 'algo-direct') (value) => this.settings['text_mode'] = (value === 'algo-direct')
); );
this.attachSetter('#transparent-as-white', 'change', 'transparent_as_white'); this.attachSetter('#transparent-as-white', 'change', 'transparent_as_white');
this.attachSetter('#select-language option', 'click', 'language');
this.attachSetter('#dry-run', 'change', 'dry_run', this.attachSetter('#dry-run', 'change', 'dry_run',
(checked) => checked && Notice.note('dry-run-test-print-process-only') (checked) => checked && Notice.note('dry-run-test-print-process-only')
); );
@ -556,7 +553,10 @@ class Main {
this.attachSetter('#flip-h', 'change', 'flip_h'); this.attachSetter('#flip-h', 'change', 'flip_h');
this.attachSetter('#flip-v', 'change', 'flip_v'); this.attachSetter('#flip-v', 'change', 'flip_v');
this.attachSetter('#dump', 'change', 'dump'); this.attachSetter('#dump', 'change', 'dump');
await this.loadConfig(); await this.activateConfig();
// one exception
this.attachSetter('#select-language option', 'click', 'language');
if (this.settings['is_android']) { if (this.settings['is_android']) {
// Android doesn't work well with select[multiple] // Android doesn't work well with select[multiple]
let div = document.createElement('div'); let div = document.createElement('div');
@ -583,13 +583,19 @@ class Main {
else return null; else return null;
} }
/** /**
* Load saved config from server, and activate all setters with corresponding values in settings. * Load saved config from server
* Please do `attachSetter` on all desired elements/inputs before calling.
* After the load, will save config to server again in order to sync default values.
* Then, if permitted, every single change will sync to server instantly
*/ */
async loadConfig() { async loadConfig() {
this.settings = await callApi('/query'); this.settings = await callApi('/query');
}
/**
* Activate all setters with corresponding values in settings.
* Before calling, please first loadConfig & do `attachSetter` on all desired elements/inputs.
* After the load, will save config to server again in order to sync default values.
* Then, if permitted, every single change will sync to server instantly
*/
async activateConfig() {
this.allowSet = false;
if (this.settings['first_run']) if (this.settings['first_run'])
Dialog.alert('#accessibility', () => this.set({ first_run: false })); Dialog.alert('#accessibility', () => this.set({ first_run: false }));
for (let key in this.settings) { for (let key in this.settings) {
@ -627,7 +633,7 @@ class Main {
* @param {(value: any) => any} callback Optional additinal post-procedure to call, with a *reasonable* value as parameter * @param {(value: any) => any} callback Optional additinal post-procedure to call, with a *reasonable* value as parameter
*/ */
attachSetter(selector, type, attribute, callback) { attachSetter(selector, type, attribute, callback) {
this.setters[attribute] = putEvent(selector, type, (event => { this.setters[attribute] = putEvent(selector, type, event => {
event.stopPropagation(); event.stopPropagation();
event.cancelBubble = true; event.cancelBubble = true;
let input = event.currentTarget; let input = event.currentTarget;
@ -647,7 +653,7 @@ class Main {
this.settings[attribute] = value; this.settings[attribute] = value;
this.set({ [attribute]: value }); this.set({ [attribute]: value });
return callback ? callback(value) : undefined; return callback ? callback(value) : undefined;
}).bind(this), this); }, this);
} }
async exit(reset) { async exit(reset) {
Notice.wait('exiting'); Notice.wait('exiting');
@ -674,6 +680,9 @@ class Main {
error_details.details.includes('not turned on') || error_details.details.includes('not turned on') ||
error_details.details.includes('WinError -2147020577') error_details.details.includes('WinError -2147020577')
) Notice.warn('please-enable-bluetooth'); ) Notice.warn('please-enable-bluetooth');
else if (
error_details.details.includes('no running event loop')
) Notice.error('internal-error-please-see-terminal');
else throw new Error('Unknown Bluetooth Problem'); else throw new Error('Unknown Bluetooth Problem');
return null; return null;
} }