mirror of
https://github.com/NaitLee/Cat-Printer.git
synced 2025-05-16 23:30:15 -07:00
Frontend Big Update
This commit is contained in:
parent
205f5ad5ca
commit
33d0157b15
3
TODO
3
TODO
@ -7,12 +7,9 @@ Note: not ordered. do whatever I/you want
|
|||||||
+ Even Better CLI, e.g. invoke imagemagick if input is not PBM
|
+ Even Better CLI, e.g. invoke imagemagick if input is not PBM
|
||||||
+ Even better CUPS/IPP support
|
+ Even better CUPS/IPP support
|
||||||
+ Even better frontend usability, more functions
|
+ Even better frontend usability, more functions
|
||||||
+ A better layout for mobile?
|
|
||||||
+ Make a build guide for android:
|
+ Make a build guide for android:
|
||||||
Summary the hacks to p4a, bleak p4a recipe, p4a webview bootstrap, and AdvancedWebView
|
Summary the hacks to p4a, bleak p4a recipe, p4a webview bootstrap, and AdvancedWebView
|
||||||
+ Try to implement enough without more dependencies
|
+ Try to implement enough without more dependencies
|
||||||
+ Better Canvas mode, (re-)consider fabric.js
|
|
||||||
+ Implement Document mode, (re-)consider html2canvas.js
|
|
||||||
+ ...
|
+ ...
|
||||||
|
|
||||||
? Consider more control to something like 'energy'
|
? Consider more control to something like 'energy'
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
p4a apk --private .. --dist_name="cat-printer" --package="io.github.naitlee.catprinter" --name="Cat Printer" \
|
p4a apk --private .. --dist_name="cat-printer" --package="io.github.naitlee.catprinter" --name="Cat Printer" \
|
||||||
--icon=icon.png --version="0.2.0" --bootstrap=webview --window --requirements=android,pyjnius,bleak \
|
--icon=icon.png --version="0.3.0" --bootstrap=webview --window --requirements=android,pyjnius,bleak \
|
||||||
--blacklist-requirements=sqlite3,openssl --port=8095 --arch=arm64-v8a --blacklist="blacklist.txt" \
|
--blacklist-requirements=sqlite3,openssl --port=8095 --arch=arm64-v8a --blacklist="blacklist.txt" \
|
||||||
--presplash=blank.png --presplash-color=black --add-source="advancedwebview" --orientation=user \
|
--presplash=blank.png --presplash-color=black --add-source="advancedwebview" --orientation=user \
|
||||||
--permission=BLUETOOTH --permission=BLUETOOTH_SCAN --permission=BLUETOOTH_CONNECT \
|
--permission=BLUETOOTH --permission=BLUETOOTH_SCAN --permission=BLUETOOTH_CONNECT \
|
||||||
|
@ -6,6 +6,10 @@
|
|||||||
.pylintrc
|
.pylintrc
|
||||||
?-*.sh
|
?-*.sh
|
||||||
|
|
||||||
|
# symlinks
|
||||||
|
*.pf2
|
||||||
|
*.pbm
|
||||||
|
|
||||||
# junk
|
# junk
|
||||||
__pycache__
|
__pycache__
|
||||||
.directory
|
.directory
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
for i in $(find | grep -E '.*\.pyc'); do rm $i; done
|
for i in $(find | grep -E '.*\.pyc'); do rm $i; done
|
||||||
|
for i in $(find | grep -E '__pycache__'); do rm -d $i; done
|
||||||
python3 bundle.py $1
|
python3 bundle.py $1
|
||||||
python3 bundle.py -w $1
|
python3 bundle.py -w $1
|
||||||
python3 bundle.py -b $1
|
python3 bundle.py -b $1
|
||||||
|
30
printer.py
30
printer.py
@ -304,17 +304,19 @@ class PrinterDriver(Commander):
|
|||||||
def connect(self, name=None, address=None):
|
def connect(self, name=None, address=None):
|
||||||
''' Connect to this device, and operate on it
|
''' Connect to this device, and operate on it
|
||||||
'''
|
'''
|
||||||
|
self._pending_data = io.BytesIO()
|
||||||
if self.fake:
|
if self.fake:
|
||||||
return
|
return
|
||||||
if (self.device is not None and address is not None and
|
if (self.device is not None and address is not None and
|
||||||
(self.device.address.lower() == address.lower())):
|
(self.device.address.lower() == address.lower())):
|
||||||
return
|
return
|
||||||
|
try:
|
||||||
if self.device is not None and self.device.is_connected:
|
if self.device is not None and self.device.is_connected:
|
||||||
self.loop(
|
self.loop(self.device.stop_notify(self.rx_characteristic))
|
||||||
self.device.stop_notify(self.rx_characteristic),
|
self.loop(self.device.disconnect())
|
||||||
self.device.disconnect()
|
except: # pylint: disable=bare-except
|
||||||
)
|
pass
|
||||||
else:
|
finally:
|
||||||
self.device = None
|
self.device = None
|
||||||
if name is None and address is None:
|
if name is None and address is None:
|
||||||
return
|
return
|
||||||
@ -356,6 +358,7 @@ class PrinterDriver(Commander):
|
|||||||
devices = self.loop(
|
devices = self.loop(
|
||||||
scanner.discover(self.scan_timeout)
|
scanner.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]
|
||||||
@ -371,11 +374,11 @@ class PrinterDriver(Commander):
|
|||||||
Currently, available modes are `pbm` and `text`.
|
Currently, available modes are `pbm` and `text`.
|
||||||
If no devices were connected, scan & connect to one first.
|
If no devices were connected, scan & connect to one first.
|
||||||
'''
|
'''
|
||||||
|
self._pending_data = io.BytesIO()
|
||||||
if self.device is None:
|
if self.device is None:
|
||||||
self.scan(identifier, use_result=True)
|
self.scan(identifier, use_result=True)
|
||||||
if self.device is None and not self.fake:
|
if self.device is None and not self.fake:
|
||||||
error('no-available-devices-found', exception=PrinterError)
|
error('no-available-devices-found', exception=PrinterError)
|
||||||
self._pending_data = io.BytesIO()
|
|
||||||
if mode == 'pbm' or mode == 'default':
|
if mode == 'pbm' or mode == 'default':
|
||||||
printer_data = PrinterData(self.model.paper_width, file)
|
printer_data = PrinterData(self.model.paper_width, file)
|
||||||
self._print_bitmap(printer_data)
|
self._print_bitmap(printer_data)
|
||||||
@ -509,6 +512,8 @@ class PrinterDriver(Commander):
|
|||||||
|
|
||||||
# CLI procedure
|
# CLI procedure
|
||||||
|
|
||||||
|
Printer = None
|
||||||
|
|
||||||
def _main():
|
def _main():
|
||||||
'Main routine for direct command line execution'
|
'Main routine for direct command line execution'
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
@ -542,6 +547,8 @@ def _main():
|
|||||||
help=I18n['virtual-run-on-specified-model'])
|
help=I18n['virtual-run-on-specified-model'])
|
||||||
parser.add_argument('-m', '--dump', required=False, action='store_true',
|
parser.add_argument('-m', '--dump', required=False, action='store_true',
|
||||||
help=I18n['dump-the-traffic'])
|
help=I18n['dump-the-traffic'])
|
||||||
|
parser.add_argument('-n', '--nothing', required=False, action='store_true',
|
||||||
|
help=I18n['do-nothing'])
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
info(I18n['cat-printer'])
|
info(I18n['cat-printer'])
|
||||||
printer = PrinterDriver()
|
printer = PrinterDriver()
|
||||||
@ -556,17 +563,22 @@ def _main():
|
|||||||
if args.fake:
|
if args.fake:
|
||||||
printer.fake = args.fake
|
printer.fake = args.fake
|
||||||
printer.model = Models[args.fake]
|
printer.model = Models[args.fake]
|
||||||
|
else:
|
||||||
|
info(I18n['connecting'])
|
||||||
|
printer.scan(args.identifier, use_result=True)
|
||||||
printer.dump = args.dump
|
printer.dump = args.dump
|
||||||
if args.file == '-':
|
if args.file == '-':
|
||||||
file = sys.stdin.buffer
|
file = sys.stdin.buffer
|
||||||
else:
|
else:
|
||||||
file = open(args.file, 'rb')
|
file = open(args.file, 'rb')
|
||||||
|
if args.nothing:
|
||||||
|
global Printer
|
||||||
|
Printer = printer
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
info(I18n['connecting'])
|
|
||||||
printer.print(
|
printer.print(
|
||||||
file,
|
file,
|
||||||
mode = 'text' if args.text else 'pbm',
|
mode = 'text' if args.text else 'pbm'
|
||||||
identifier = args.identifier
|
|
||||||
)
|
)
|
||||||
info(I18n['finished'])
|
info(I18n['finished'])
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
11
server.py
11
server.py
@ -39,6 +39,7 @@ mime_type = {
|
|||||||
'txt': 'text/plain;charset=utf-8',
|
'txt': 'text/plain;charset=utf-8',
|
||||||
'json': 'application/json;charset=utf-8',
|
'json': 'application/json;charset=utf-8',
|
||||||
'png': 'image/png',
|
'png': 'image/png',
|
||||||
|
'svg': 'image/svg+xml;charset=utf-8',
|
||||||
'octet-stream': 'application/octet-stream'
|
'octet-stream': 'application/octet-stream'
|
||||||
}
|
}
|
||||||
def mime(url: str):
|
def mime(url: str):
|
||||||
@ -55,6 +56,7 @@ class PrinterServerHandler(BaseHTTPRequestHandler):
|
|||||||
settings = DictAsObject({
|
settings = DictAsObject({
|
||||||
'config_path': 'config.json',
|
'config_path': 'config.json',
|
||||||
'version': 1,
|
'version': 1,
|
||||||
|
'first_run': True,
|
||||||
'is_android': False,
|
'is_android': False,
|
||||||
'scan_timeout': 5.0,
|
'scan_timeout': 5.0,
|
||||||
'dry_run': False
|
'dry_run': False
|
||||||
@ -73,6 +75,14 @@ class PrinterServerHandler(BaseHTTPRequestHandler):
|
|||||||
def log_error(self, *_args):
|
def log_error(self, *_args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def handle_one_request(self):
|
||||||
|
try:
|
||||||
|
# this handler would have only one instance
|
||||||
|
# broken pipe could make it die. ignore
|
||||||
|
super().handle_one_request()
|
||||||
|
except BrokenPipeError:
|
||||||
|
pass
|
||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
'Called when server get a GET http request'
|
'Called when server get a GET http request'
|
||||||
path = 'www' + self.path
|
path = 'www' + self.path
|
||||||
@ -154,6 +164,7 @@ class PrinterServerHandler(BaseHTTPRequestHandler):
|
|||||||
self.printer.dump = self.settings.dump
|
self.printer.dump = self.settings.dump
|
||||||
self.printer.flip_h = self.settings.flip_h
|
self.printer.flip_h = self.settings.flip_h
|
||||||
self.printer.flip_v = self.settings.flip_v
|
self.printer.flip_v = self.settings.flip_v
|
||||||
|
self.printer.rtl = self.settings.force_rtl
|
||||||
if self.settings.printer is not None:
|
if self.settings.printer is not None:
|
||||||
name, address = self.settings.printer.split(',')
|
name, address = self.settings.printer.split(',')
|
||||||
self.printer.connect(name, address)
|
self.printer.connect(name, address)
|
||||||
|
@ -4,15 +4,35 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Python Webview Loading</title>
|
<title>Loading</title>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--fore-color: #111;
|
--fore-color: #111;
|
||||||
--back-color: #eee;
|
--back-color: #eee;
|
||||||
|
--notice-error: rgba(255, 0, 0, 0.2);
|
||||||
|
--font-size: 1.2rem;
|
||||||
|
--line-height: 1.8em;
|
||||||
|
--anim-time: 0.5s;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
background-color: var(--back-color);
|
background-color: var(--back-color);
|
||||||
color: var(--fore-color);
|
color: var(--fore-color);
|
||||||
|
font-size: var(--font-size);
|
||||||
|
line-height: var(--line-height);
|
||||||
|
}
|
||||||
|
a:link, a:visited {
|
||||||
|
color: #33f;
|
||||||
|
}
|
||||||
|
a:hover, a:active {
|
||||||
|
color: #22f;
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion) {
|
||||||
|
body {
|
||||||
|
transition-duration: 0ms !important;
|
||||||
|
transition: none;
|
||||||
|
animation-timing-function: steps(1);
|
||||||
|
animation-duration: 0ms !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:root {
|
:root {
|
||||||
@ -20,9 +40,78 @@
|
|||||||
--back-color: #333;
|
--back-color: #333;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@keyframes jump {
|
||||||
|
0% { transform: translateY(0); }
|
||||||
|
50% { transform: translateY(var(--font-size)); }
|
||||||
|
100% { transform: translateY(0); }
|
||||||
|
}
|
||||||
|
#loading-screen {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--back-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 1;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity var(--anim-time) var(--curve);
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
background-image: url('icon.svg');
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
width: 80%;
|
||||||
|
max-width: 16em;
|
||||||
|
height: 80%;
|
||||||
|
max-height: 16em;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
#loading-screen.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
#loading-screen>.dots {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
#loading-screen>.dots>span {
|
||||||
|
display: inline-block;
|
||||||
|
width: var(--font-size);
|
||||||
|
height: var(--font-size);
|
||||||
|
margin: var(--font-size);
|
||||||
|
background-color: var(--fore-color);
|
||||||
|
border-radius: calc(var(--font-size) / 2);
|
||||||
|
animation: jump 1s ease 0s infinite;
|
||||||
|
}
|
||||||
|
#loading-screen>.dots>span:nth-child(1) { animation-delay: 0s; }
|
||||||
|
#loading-screen>.dots>span:nth-child(2) { animation-delay: 0.3s; }
|
||||||
|
#loading-screen>.dots>span:nth-child(3) { animation-delay: 0.6s; }
|
||||||
|
|
||||||
|
a {
|
||||||
|
transition: all var(--anim-time) ease-out;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="loading-screen">
|
||||||
|
<div class="logo"></div>
|
||||||
|
<div class="dots">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
<noscript>
|
||||||
|
<p class="noscript">
|
||||||
|
<span>Please enable JavaScript!</span><br />
|
||||||
|
<a href="jslicense.html" data-i18n="javascript-license-information"
|
||||||
|
data-jslicense="1" >JavaScript License Information</a>
|
||||||
|
</p>
|
||||||
|
</noscript>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
40
www/about.html
Normal file
40
www/about.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>About</title>
|
||||||
|
<link rel="stylesheet" href="main.css" />
|
||||||
|
<link rel="icon" href="icon.svg" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div>
|
||||||
|
<h1 data-i18n="cat-printer">Cat Printer</h1>
|
||||||
|
<p>
|
||||||
|
<span data-i18n="home-page-">Home Page:</span>
|
||||||
|
<a target="_blank" href="https://github.com/NaitLee/Cat-Printer">GitHub</a>
|
||||||
|
</p>
|
||||||
|
<h2 data-i18n="contributors">Contributors</h2>
|
||||||
|
<dl>
|
||||||
|
<dt>
|
||||||
|
<a target="_blank" href="https://github.com/NaitLee">NaitLee</a>
|
||||||
|
</dt>
|
||||||
|
<dd data-i18n="developer">Developer</dd>
|
||||||
|
<dt>
|
||||||
|
<a target="_blank" href="https://github.com/frankenstein91">frankenstein91</a>
|
||||||
|
</dt>
|
||||||
|
<dd data-i18n="developer">Developer</dd>
|
||||||
|
<dd data-i18n="translator">Translator</dd>
|
||||||
|
<dt data-i18n="all-testers-and-users">All testers & users</dt>
|
||||||
|
<dd data-i18n="everyone-is-awesome">Everyone is awesome!</dd>
|
||||||
|
</dl>
|
||||||
|
<h2 data-i18n="license">License</h2>
|
||||||
|
<p>This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</p>
|
||||||
|
<p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</p>
|
||||||
|
<p>You should have received a copy of the GNU General Public License along with this program. If not, see <<a target="_blank" href="https://www.gnu.org/licenses/">https://www.gnu.org/licenses/</a>>.</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
13
www/i18n.js
13
www/i18n.js
@ -21,24 +21,27 @@ class I18n {
|
|||||||
* @param {Languages} language
|
* @param {Languages} language
|
||||||
*/
|
*/
|
||||||
useLanguage(language) {
|
useLanguage(language) {
|
||||||
this.language = language;
|
if (this.language)
|
||||||
|
this.database[language] = this.database[this.language];
|
||||||
if (!this.database[language])
|
if (!this.database[language])
|
||||||
this.database[language] = {};
|
this.database[language] = {};
|
||||||
|
this.language = language;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Add data as corresponding language, also to
|
* Add data as corresponding language,
|
||||||
* other (added) languages as fallback
|
* also to other (added) languages as fallback,
|
||||||
|
* or override
|
||||||
* @param {Languages} language
|
* @param {Languages} language
|
||||||
* @param {LanguageData} data
|
* @param {LanguageData} data
|
||||||
*/
|
*/
|
||||||
add(language, data) {
|
add(language, data, override = false) {
|
||||||
if (!this.database[language])
|
if (!this.database[language])
|
||||||
this.database[language] = {};
|
this.database[language] = {};
|
||||||
for (let key in data) {
|
for (let key in data) {
|
||||||
let value = data[key];
|
let value = data[key];
|
||||||
this.database[language][key] = value;
|
this.database[language][key] = value;
|
||||||
for (let lang in this.database)
|
for (let lang in this.database)
|
||||||
if (!this.database[lang][key])
|
if (override || !this.database[lang][key])
|
||||||
this.database[lang][key] = value;
|
this.database[lang][key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
www/icon.png
BIN
www/icon.png
Binary file not shown.
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
98
www/image.js
98
www/image.js
@ -69,8 +69,8 @@ function monoSteinberg(data, w, h, t) {
|
|||||||
for (let i = 0; i < w; i++) {
|
for (let i = 0; i < w; i++) {
|
||||||
p = j * w + i;
|
p = j * w + i;
|
||||||
m = data[p];
|
m = data[p];
|
||||||
n = m > t ? 255 : 0;
|
n = m > 128 ? 255 : 0;
|
||||||
o = m - n;
|
o = m - n + t;
|
||||||
data[p] = n;
|
data[p] = n;
|
||||||
adjust(i + 1, j , o * 7 / 16);
|
adjust(i + 1, j , o * 7 / 16);
|
||||||
adjust(i - 1, j + 1, o * 3 / 16);
|
adjust(i - 1, j + 1, o * 3 / 16);
|
||||||
@ -85,100 +85,6 @@ function monoSteinberg(data, w, h, t) {
|
|||||||
*/
|
*/
|
||||||
function monoHalftone(data, w, h, t) {}
|
function monoHalftone(data, w, h, t) {}
|
||||||
|
|
||||||
/**
|
|
||||||
* My own toy algorithm used in old versions. Not so natual.
|
|
||||||
* It have 2 pass, horizonally and vertically.
|
|
||||||
* @param {Uint8ClampedArray} data the grayscale data, mentioned in `monoGrayscale`. **will be modified in-place**
|
|
||||||
* @param {number} w width of image
|
|
||||||
* @param {number} h height of image
|
|
||||||
* @param {number} t threshold
|
|
||||||
*/
|
|
||||||
function monoLegacy(data, w, h, t) {
|
|
||||||
let data_h = data.slice();
|
|
||||||
let data_v = data.slice();
|
|
||||||
monoLegacyH(data_h, w, h, t);
|
|
||||||
monoLegacyV(data_v, w, h, t);
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
data[i] = data_h[i] & data_v[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function monoLegacyH(data, w, h, t) {
|
|
||||||
let v = 0, p;
|
|
||||||
for (let j = 0; j < h; j++) {
|
|
||||||
for (let i = 0; i < w; i++) {
|
|
||||||
p = j * w + i;
|
|
||||||
v += data[p];
|
|
||||||
if (v >= t) {
|
|
||||||
data[p] = 255;
|
|
||||||
v = 0;
|
|
||||||
} else data[p] = 0;
|
|
||||||
}
|
|
||||||
v = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function monoLegacyV(data, w, h, t) {
|
|
||||||
let v = 0, p;
|
|
||||||
for (let i = 0; i < w; i++) {
|
|
||||||
for (let j = 0; j < h; j++) {
|
|
||||||
p = j * w + i;
|
|
||||||
v += data[p];
|
|
||||||
if (v >= t) {
|
|
||||||
data[p] = 255;
|
|
||||||
v = 0;
|
|
||||||
} else data[p] = 0;
|
|
||||||
}
|
|
||||||
v = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Slightly modified from `monoLegacy`, but still messy.
|
|
||||||
* But, try the horizonal and vertical sub algorithm!
|
|
||||||
* @param {Uint8ClampedArray} data the grayscale data, mentioned in `monoGrayscale`. **will be modified in-place**
|
|
||||||
* @param {number} w width of image
|
|
||||||
* @param {number} h height of image
|
|
||||||
* @param {number} t threshold
|
|
||||||
*/
|
|
||||||
function monoNew(data, w, h, t) {
|
|
||||||
let data_h = data.slice();
|
|
||||||
let data_v = data.slice();
|
|
||||||
monoNewH(data_h, w, h, t);
|
|
||||||
monoNewV(data_v, w, h, t);
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
data[i] = data_h[i] & data_v[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function monoNewH(data, w, h, t) {
|
|
||||||
t = (t - 127) / 4 + 1;
|
|
||||||
let v = 0, p;
|
|
||||||
for (let j = 0; j < w; j++) {
|
|
||||||
for (let i = 0; i < h; i++) {
|
|
||||||
p = j * h + i;
|
|
||||||
v += data[p] + t;
|
|
||||||
if (v >= 255) {
|
|
||||||
data[p] = 255;
|
|
||||||
v -= 255;
|
|
||||||
} else data[p] = 0;
|
|
||||||
}
|
|
||||||
v = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function monoNewV(data, w, h, t) {
|
|
||||||
t = (t - 127) / 4 + 1;
|
|
||||||
let v = -1, p;
|
|
||||||
for (let i = 0; i < h; i++) {
|
|
||||||
for (let j = 0; j < w; j++) {
|
|
||||||
p = j * h + i;
|
|
||||||
v += data[p] + t;
|
|
||||||
if (v >= 255) {
|
|
||||||
data[p] = 255;
|
|
||||||
v -= 255;
|
|
||||||
} else data[p] = 0;
|
|
||||||
}
|
|
||||||
v = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a monochrome image data to PBM mono image file data.
|
* Convert a monochrome image data to PBM mono image file data.
|
||||||
* Returns a Blob containing the file data.
|
* Returns a Blob containing the file data.
|
||||||
|
161
www/index.html
161
www/index.html
@ -5,25 +5,21 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Cat Printer</title>
|
<title>Cat Printer</title>
|
||||||
<link rel="stylesheet" href="main.css" />
|
<link rel="stylesheet" href="main.css" />
|
||||||
<link rel="icon" href="icon.png" />
|
<link rel="icon" href="icon.svg" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main class="hard-hidden">
|
||||||
<div class="left">
|
<div class="menu-side">
|
||||||
<h1 id="title" data-i18n="cat-printer">Cat Printer</h1>
|
<h1 id="title" data-i18n="cat-printer">Cat Printer</h1>
|
||||||
<div id="notice">
|
<div id="notice">
|
||||||
<noscript>
|
|
||||||
<span class="noscript">Please enable JavaScript!</span>
|
|
||||||
</noscript>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="panel" id="panel-print">
|
<div class="menu">
|
||||||
<a href="#" data-i18n="print" data-default>Print</a>
|
<div class="panel" id="panel-print" data-default>
|
||||||
<label for="device-options" data-i18n="device-">Device:</label>
|
<label for="device-options" data-i18n="device-">Device:</label>
|
||||||
<select id="device-options">
|
<select id="device-options">
|
||||||
<!-- Initialized by script -->
|
|
||||||
</select>
|
</select>
|
||||||
<button id="device-refresh" data-i18n="refresh">Refresh</button>
|
<button id="device-refresh" data-i18n="refresh">Refresh</button>
|
||||||
<br />
|
<hr />
|
||||||
<label data-i18n="mode-">Mode:</label>
|
<label data-i18n="mode-">Mode:</label>
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" name="mode" value="mode-canvas" checked />
|
<input type="radio" name="mode" value="mode-canvas" checked />
|
||||||
@ -33,13 +29,32 @@
|
|||||||
<input type="radio" name="mode" value="mode-document" />
|
<input type="radio" name="mode" value="mode-document" />
|
||||||
<span data-i18n="document">Document</span>
|
<span data-i18n="document">Document</span>
|
||||||
</label><br /> -->
|
</label><br /> -->
|
||||||
<button id="insert-picture" data-i18n="insert-picture">Insert Picture</button><br />
|
<button id="insert-picture" data-i18n="insert-picture">Insert Picture</button>
|
||||||
|
<br />
|
||||||
|
<label data-i18n="process-as-">Process as:</label>
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="algo" value="algo-direct" />
|
||||||
|
<span data-i18n="text">Text</span>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="algo" value="algo-steinberg" checked />
|
||||||
|
<span data-i18n="picture">Picture</span>
|
||||||
|
</label>
|
||||||
|
<!-- <label>
|
||||||
|
<input type="radio" name="algo" value="algo-halftone" />
|
||||||
|
<span data-i18n="pattern">Pattern</span>
|
||||||
|
</label> --><br />
|
||||||
|
<label for="threshold" data-i18n="threshold-">Threshold:</label>
|
||||||
|
<input type="range" min="0" max="256" value="128" step="8" id="threshold" data-default />
|
||||||
|
<br />
|
||||||
|
<input type="checkbox" name="transparent-as-white" id="transparent-as-white" checked />
|
||||||
|
<label for="transparent-as-white" data-i18n="transparent-as-white">Transparent as White</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel expanded" id="panel-help">
|
<div class="panel active" id="panel-help">
|
||||||
<a href="#" data-i18n="help">Help</a>
|
|
||||||
<div>
|
<div>
|
||||||
<p data-i18n="coming-soon-">Coming Soon...</p>
|
<p data-i18n="coming-soon">Coming Soon...</p>
|
||||||
<p>
|
<p>
|
||||||
|
<a id="link-about" href="about.html" target="frame" data-i18n="about">About</a>
|
||||||
<!-- LibreJS doesn't work with dynamically inserted script tag. Going to complain -->
|
<!-- LibreJS doesn't work with dynamically inserted script tag. Going to complain -->
|
||||||
<a href="jslicense.html" data-jslicense="1"
|
<a href="jslicense.html" data-jslicense="1"
|
||||||
data-i18n="javascript-license-information">JavaScript License Information</a>
|
data-i18n="javascript-license-information">JavaScript License Information</a>
|
||||||
@ -47,42 +62,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel" id="panel-settings">
|
<div class="panel" id="panel-settings">
|
||||||
<a href="#" data-i18n="settings">Settings</a>
|
|
||||||
<div class="sub panel">
|
|
||||||
<a href="#" data-i18n="image">Image</a>
|
|
||||||
<label data-i18n="monochrome-algorithm-">Monochrome Algorithm:</label><br />
|
|
||||||
<label>
|
|
||||||
<input type="radio" name="algo" value="algo-direct" />
|
|
||||||
<span data-i18n="direct">Direct</span>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="radio" name="algo" value="algo-steinberg" checked />
|
|
||||||
<span data-i18n="floyd-steinberg">Floyd Steinberg</span>
|
|
||||||
</label><br />
|
|
||||||
<!-- <label>
|
|
||||||
<input type="radio" name="algo" value="algo-halftone" />
|
|
||||||
<span data-i18n="halftone">Halftone</span>
|
|
||||||
</label> -->
|
|
||||||
<label>
|
|
||||||
<input type="radio" name="algo" value="algo-new-h" />
|
|
||||||
<span data-i18n="wave">Wave</span>
|
|
||||||
</label>
|
|
||||||
<!-- <label>
|
|
||||||
<input type="radio" name="algo" value="algo-new-v" />
|
|
||||||
<span data-i18n="fall">Fall</span>
|
|
||||||
</label> -->
|
|
||||||
<label>
|
|
||||||
<input type="radio" name="algo" value="algo-legacy" />
|
|
||||||
<span data-i18n="legacy">Legacy</span>
|
|
||||||
</label><br />
|
|
||||||
<label for="threshold" data-i18n="threshold-">Threshold:</label>
|
|
||||||
<input type="range" min="0" max="255" value="128" id="threshold" step="8" data-default />
|
|
||||||
<br />
|
|
||||||
<input type="checkbox" name="transparent-as-white" id="transparent-as-white" checked />
|
|
||||||
<label for="transparent-as-white" data-i18n="transparent-as-white">Transparent as White</label>
|
|
||||||
</div>
|
|
||||||
<div class="sub panel">
|
|
||||||
<a href="#" data-i18n="printer">Printer</a>
|
|
||||||
<label for="scan-time" data-i18n="scan-time-">Scan Time:</label>
|
<label for="scan-time" data-i18n="scan-time-">Scan Time:</label>
|
||||||
<input type="number" name="scan-time" id="scan-time" min="1" max="10" value="3" />
|
<input type="number" name="scan-time" id="scan-time" min="1" max="10" value="3" />
|
||||||
<span data-i18n="-seconds">seconds</span>
|
<span data-i18n="-seconds">seconds</span>
|
||||||
@ -91,43 +70,97 @@
|
|||||||
<label for="flip-h" data-i18n="flip-horizontally">Flip Horizontally</label>
|
<label for="flip-h" data-i18n="flip-horizontally">Flip Horizontally</label>
|
||||||
<input type="checkbox" name="flip-v" id="flip-v" />
|
<input type="checkbox" name="flip-v" id="flip-v" />
|
||||||
<label for="flip-v" data-i18n="flip-vertically">Flip Vertically</label>
|
<label for="flip-v" data-i18n="flip-vertically">Flip Vertically</label>
|
||||||
<br />
|
<hr />
|
||||||
<input type="checkbox" name="dry-run" id="dry-run" />
|
<input type="checkbox" name="dry-run" id="dry-run" />
|
||||||
<label for="dry-run" data-i18n="dry-run">Dry Run</label>
|
<label for="dry-run" data-i18n="dry-run">Dry Run</label>
|
||||||
<input type="checkbox" name="dump" id="dump" />
|
<input type="checkbox" name="dump" id="dump" />
|
||||||
<label for="dump" data-i18n="dump-traffic">Dump Traffic</label>
|
<label for="dump" data-i18n="dump-traffic">Dump Traffic</label>
|
||||||
</div>
|
<br />
|
||||||
<div class="sub panel">
|
<button id="set-accessibility">
|
||||||
<a href="#" data-i18n="system">System</a>
|
<span>🌎</span>
|
||||||
<input type="checkbox" name="no-animation" id="no-animation" />
|
<span data-i18n="accessibility">Accessibility</span>
|
||||||
<label for="no-animation" data-i18n="disable-page-animation">Disable Page Animation</label>
|
</button>
|
||||||
</div>
|
<button class="hidden" data-panel="panel-error" data-i18n="error-message">Error Message</button>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<button id="button-exit" data-i18n="exit">Exit</button>
|
<button id="button-exit" data-i18n="exit">Exit</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel hidden" id="panel-error">
|
<div class="panel" id="panel-error">
|
||||||
<a href="#" data-i18n="error-message">Error Message</a>
|
|
||||||
<p data-i18n="you-can-seek-for-help-with-detailed-info-below">You can seek for help with detailed info below.</p>
|
<p data-i18n="you-can-seek-for-help-with-detailed-info-below">You can seek for help with detailed info below.</p>
|
||||||
<div id="error-record"></div>
|
<div id="error-record"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="compact-menu">
|
||||||
|
<div class="compact-button" data-panel="panel-print" data-i18n="print">Print</div>
|
||||||
|
<div class="compact-button active" data-panel="panel-help" data-i18n="help">Help</div>
|
||||||
|
<div class="compact-button" data-panel="panel-settings" data-i18n="settings">Settings</div>
|
||||||
|
</div>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<!-- <button id="button-preview" data-i18n="preview">Preview</button> -->
|
<!-- -->
|
||||||
<button id="button-print" data-i18n="print">Print</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="canvas-side">
|
||||||
<canvas id="control-canvas" class="disabled" width="384" height="384"></canvas>
|
<canvas id="control-canvas" class="disabled" width="384" height="384"></canvas>
|
||||||
<div id="control-document" class="disabled" contenteditable="true"></div>
|
<div id="control-document" class="disabled" contenteditable="true"></div>
|
||||||
<canvas id="preview" width="384" height="384"></canvas>
|
<canvas id="preview" width="384" height="384"></canvas>
|
||||||
<div class="center">
|
<div class="center buttons">
|
||||||
<!-- <button id="canvas-expand" data-i18n="expand">Expand</button>
|
<!-- <button id="canvas-expand" data-i18n="expand">Expand</button>
|
||||||
<button id="canvas-crop" data-i18n="crop">Crop</button> -->
|
<button id="canvas-crop" data-i18n="crop">Crop</button> -->
|
||||||
|
<!-- <button id="button-preview" data-i18n="preview">Preview</button> -->
|
||||||
|
<button id="button-print" data-i18n="print">Print</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="blank"></div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<div id="hidden" class="hidden">
|
<div id="hidden" class="hard-hidden">
|
||||||
<!-- Hidden area for putting elements -->
|
<!-- Hidden area for putting elements -->
|
||||||
|
<input type="file" id="file" />
|
||||||
|
<div id="accessibility">
|
||||||
|
<div>
|
||||||
|
<h2 data-i18n="language">Language</h2>
|
||||||
|
<div id="select-language">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 data-i18n="layout">Layout</h2>
|
||||||
|
<input type="checkbox" name="no-animation" id="no-animation" />
|
||||||
|
<label for="no-animation" data-i18n="disable-animation">Disable Animation</label>
|
||||||
|
<br />
|
||||||
|
<input type="checkbox" name="force-rtl" id="force-rtl" />
|
||||||
|
<label for="force-rtl" data-i18n="right-to-left-text-order">Right-to-left text order</label>
|
||||||
|
<br />
|
||||||
|
<input type="checkbox" name="large-font" id="large-font" />
|
||||||
|
<label for="large-font" data-i18n="large-font">Large Font</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<iframe id="frame" src="about.html" name="frame" title="frame"></iframe>
|
||||||
|
</div>
|
||||||
|
<div id="dialog" class="hidden">
|
||||||
|
<div class="shade"></div>
|
||||||
|
<div class="content">
|
||||||
|
<div id="dialog-content">
|
||||||
|
<!-- Dialog content -->
|
||||||
|
</div>
|
||||||
|
<div id="dialog-choices">
|
||||||
|
<input id="dialog-input" type="text" id="dialog-input" placeholder="">
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="loading-screen">
|
||||||
|
<div class="logo"></div>
|
||||||
|
<div class="dots">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
<noscript>
|
||||||
|
<p class="noscript">
|
||||||
|
<span>Please enable JavaScript!</span><br />
|
||||||
|
<a href="jslicense.html" data-i18n="javascript-license-information"
|
||||||
|
data-jslicense="1" >JavaScript License Information</a>
|
||||||
|
</p>
|
||||||
|
</noscript>
|
||||||
</div>
|
</div>
|
||||||
<script src="loader.js"></script>
|
<script src="loader.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -5,13 +5,13 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>JavaScript License Information</title>
|
<title>JavaScript License Information</title>
|
||||||
<link rel="stylesheet" href="main.css" />
|
<link rel="stylesheet" href="main.css" />
|
||||||
<link rel="icon" href="icon.png" />
|
<link rel="icon" href="icon.svg" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<div>
|
<div>
|
||||||
<h1>JavaScript License Information</h1>
|
<h1>JavaScript License Information</h1>
|
||||||
<p>You can see all JavaScript used along with this application are Free Software.<sup><a href="#footer-1">[1]</sup></a></p>
|
<p>You can see all JavaScript programs used along with this application are Free Software.<sup><a href="#footer-1">[1]</sup></a></p>
|
||||||
<p class="center">
|
<p class="center">
|
||||||
<a href="/">Go Back</a>
|
<a href="/">Go Back</a>
|
||||||
</p>
|
</p>
|
||||||
@ -70,12 +70,6 @@
|
|||||||
<td><a href="main.js">main.js</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>
|
<td>A bundle of transpiled scripts (polyfill.js, image.js, i18n.js, main.js), for compatibility to old browsers.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td><a href="vconsole.js">vconsole.js</a></td>
|
|
||||||
<td><a href="http://opensource.org/licenses/MIT">Expat</a></td>
|
|
||||||
<td><a href="https://github.com/Tencent/vConsole">vconsole.js</a></td>
|
|
||||||
<td>A mini console for debugging on mobile, will only load when explicitly invoke.</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
@ -90,7 +84,7 @@
|
|||||||
<a href="https://www.gnu.org/philosophy/free-sw.html">Free Software</a>
|
<a href="https://www.gnu.org/philosophy/free-sw.html">Free Software</a>
|
||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<span>Software that respects your freedom.</span>
|
<span>Software that respects your computing freedom.</span>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"$language": "Deutsch",
|
||||||
"cat-printer": "Cat Printer",
|
"cat-printer": "Cat Printer",
|
||||||
"printer": "Drucker",
|
"printer": "Drucker",
|
||||||
"device-": "Gerät:",
|
"device-": "Gerät:",
|
||||||
@ -11,13 +12,6 @@
|
|||||||
"javascript-license-information": "Informationen zur JavaScript-Lizenz",
|
"javascript-license-information": "Informationen zur JavaScript-Lizenz",
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"image": "Bild",
|
"image": "Bild",
|
||||||
"monochrome-algorithm-": "Schwarzweiß-Algorithmus:",
|
|
||||||
"direct": "Direkt",
|
|
||||||
"floyd-steinberg": "Floyd Steinberg",
|
|
||||||
"halftone": "Halbtone",
|
|
||||||
"wave": "Wave",
|
|
||||||
"fall": "Fall",
|
|
||||||
"legacy": "Legacy",
|
|
||||||
"threshold-": "Schwellwert",
|
"threshold-": "Schwellwert",
|
||||||
"transmission-speed-": "Übertragungsgeschwindigkeit:",
|
"transmission-speed-": "Übertragungsgeschwindigkeit:",
|
||||||
"low": "Gering",
|
"low": "Gering",
|
||||||
@ -26,7 +20,7 @@
|
|||||||
"transparent-as-white": "Transparent als Weiß",
|
"transparent-as-white": "Transparent als Weiß",
|
||||||
"misc": "Sonstiges",
|
"misc": "Sonstiges",
|
||||||
"system": "System",
|
"system": "System",
|
||||||
"disable-page-animation": "Seitenanimation ausschalten",
|
"disable-animation": "Seitenanimation ausschalten",
|
||||||
"exit": "Exit",
|
"exit": "Exit",
|
||||||
"error-message": "Fehlermeldung",
|
"error-message": "Fehlermeldung",
|
||||||
"preview": "Vorschau",
|
"preview": "Vorschau",
|
||||||
@ -44,7 +38,7 @@
|
|||||||
"please-check-if-the-printer-is-down": "Bitte prüfe, ob der Drucker ausgeschaltet ist",
|
"please-check-if-the-printer-is-down": "Bitte prüfe, ob der Drucker ausgeschaltet ist",
|
||||||
"printing": "Drucken…",
|
"printing": "Drucken…",
|
||||||
"finished": "Fertiggestellt",
|
"finished": "Fertiggestellt",
|
||||||
"coming-soon-": "Demnächst verfügbar…",
|
"coming-soon": "Demnächst verfügbar…",
|
||||||
"dry-run": "Testlauf",
|
"dry-run": "Testlauf",
|
||||||
"dry-run-test-print-process-only": "Testlauf: nur Probedruckvorgang",
|
"dry-run-test-print-process-only": "Testlauf: nur Probedruckvorgang",
|
||||||
"you-can-close-this-page-manually": "Sie können diese Seite manuell schließen",
|
"you-can-close-this-page-manually": "Sie können diese Seite manuell schließen",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"$language": "English (US)",
|
||||||
"cat-printer": "Cat Printer",
|
"cat-printer": "Cat Printer",
|
||||||
"printer": "Printer",
|
"printer": "Printer",
|
||||||
"device-": "Device:",
|
"device-": "Device:",
|
||||||
@ -11,13 +12,6 @@
|
|||||||
"javascript-license-information": "JavaScript License Information",
|
"javascript-license-information": "JavaScript License Information",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"image": "Image",
|
"image": "Image",
|
||||||
"monochrome-algorithm-": "Monochrome Algorithm:",
|
|
||||||
"direct": "Direct",
|
|
||||||
"floyd-steinberg": "Floyd Steinberg",
|
|
||||||
"halftone": "Halftone",
|
|
||||||
"wave": "Wave",
|
|
||||||
"fall": "Fall",
|
|
||||||
"legacy": "Legacy",
|
|
||||||
"threshold-": "Threshold",
|
"threshold-": "Threshold",
|
||||||
"transmission-speed-": "Transmission Speed:",
|
"transmission-speed-": "Transmission Speed:",
|
||||||
"low": "Low",
|
"low": "Low",
|
||||||
@ -26,7 +20,7 @@
|
|||||||
"transparent-as-white": "Transparent as White",
|
"transparent-as-white": "Transparent as White",
|
||||||
"misc": "Misc",
|
"misc": "Misc",
|
||||||
"system": "System",
|
"system": "System",
|
||||||
"disable-page-animation": "Disable Page Animation",
|
"disable-animation": "Disable Animation",
|
||||||
"exit": "Exit",
|
"exit": "Exit",
|
||||||
"error-message": "Error Message",
|
"error-message": "Error Message",
|
||||||
"preview": "Preview",
|
"preview": "Preview",
|
||||||
@ -44,7 +38,7 @@
|
|||||||
"please-check-if-the-printer-is-down": "Please check if the printer is down",
|
"please-check-if-the-printer-is-down": "Please check if the printer is down",
|
||||||
"printing": "Printing…",
|
"printing": "Printing…",
|
||||||
"finished": "Finished",
|
"finished": "Finished",
|
||||||
"coming-soon-": "Coming Soon…",
|
"coming-soon": "Coming Soon…",
|
||||||
"dry-run": "Dry Run",
|
"dry-run": "Dry Run",
|
||||||
"dry-run-test-print-process-only": "Dry Run: test print process only",
|
"dry-run-test-print-process-only": "Dry Run: test print process only",
|
||||||
"you-can-close-this-page-manually": "You can close this page manually",
|
"you-can-close-this-page-manually": "You can close this page manually",
|
||||||
@ -83,5 +77,26 @@
|
|||||||
"flip-vertically": "Flip Vertically",
|
"flip-vertically": "Flip Vertically",
|
||||||
"dump-traffic": "Dump Traffic",
|
"dump-traffic": "Dump Traffic",
|
||||||
"right-to-left-text-order": "Right-to-left text order",
|
"right-to-left-text-order": "Right-to-left text order",
|
||||||
"auto-wrap-line": "Auto wrap line"
|
"auto-wrap-line": "Auto wrap line",
|
||||||
|
"process-as-": "Process as:",
|
||||||
|
"text": "Text",
|
||||||
|
"picture": "Picture",
|
||||||
|
"pattern": "Pattern",
|
||||||
|
"large-font": "Large Font",
|
||||||
|
"accessibility": "Accessibility",
|
||||||
|
"language": "Language",
|
||||||
|
"layout": "Layout",
|
||||||
|
"ok": "OK",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"about": "About",
|
||||||
|
"home-page-": "Home Page:",
|
||||||
|
"contributors": "Contributors",
|
||||||
|
"developer": "Developer",
|
||||||
|
"translator": "Translator",
|
||||||
|
"all-testers-and-users": "All testers & users",
|
||||||
|
"everyone-is-awesome": "Everyone is awesome!",
|
||||||
|
"license": "License",
|
||||||
|
"exiting": "Exiting…"
|
||||||
}
|
}
|
5
www/lang/list.json
Normal file
5
www/lang/list.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"en-US": "English (US)",
|
||||||
|
"zh-CN": "中文(简体)",
|
||||||
|
"de-DE": "Deutsch"
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"$language": "中文(简体)",
|
||||||
"cat-printer": "猫咪打印机",
|
"cat-printer": "猫咪打印机",
|
||||||
"printer": "打印机",
|
"printer": "打印机",
|
||||||
"device-": "设备:",
|
"device-": "设备:",
|
||||||
@ -10,14 +11,6 @@
|
|||||||
"help": "帮助",
|
"help": "帮助",
|
||||||
"javascript-license-information": "JavaScript 许可证信息",
|
"javascript-license-information": "JavaScript 许可证信息",
|
||||||
"settings": "设置",
|
"settings": "设置",
|
||||||
"monochrome-algorithm-": "单色化算法:",
|
|
||||||
"direct": "直接",
|
|
||||||
"image": "图像",
|
|
||||||
"floyd-steinberg": "科学",
|
|
||||||
"halftone": "点状",
|
|
||||||
"wave": "波纹",
|
|
||||||
"fall": "下落",
|
|
||||||
"legacy": "旧版",
|
|
||||||
"threshold-": "阈值:",
|
"threshold-": "阈值:",
|
||||||
"transmission-speed-": "传输速度:",
|
"transmission-speed-": "传输速度:",
|
||||||
"low": "低",
|
"low": "低",
|
||||||
@ -26,7 +19,7 @@
|
|||||||
"transparent-as-white": "透明为白色",
|
"transparent-as-white": "透明为白色",
|
||||||
"misc": "杂项",
|
"misc": "杂项",
|
||||||
"system": "系统",
|
"system": "系统",
|
||||||
"disable-page-animation": "禁用页面动画",
|
"disable-animation": "禁用动画",
|
||||||
"exit": "退出",
|
"exit": "退出",
|
||||||
"error-message": "错误消息",
|
"error-message": "错误消息",
|
||||||
"preview": "预览",
|
"preview": "预览",
|
||||||
@ -41,7 +34,7 @@
|
|||||||
"please-check-if-the-printer-is-down": "请检查打印机是否已关闭",
|
"please-check-if-the-printer-is-down": "请检查打印机是否已关闭",
|
||||||
"printing": "打印中……",
|
"printing": "打印中……",
|
||||||
"finished": "完成",
|
"finished": "完成",
|
||||||
"coming-soon-": "即将到来……",
|
"coming-soon": "即将到来……",
|
||||||
"dry-run": "干运行",
|
"dry-run": "干运行",
|
||||||
"dry-run-test-print-process-only": "干运行:仅测试打印流程",
|
"dry-run-test-print-process-only": "干运行:仅测试打印流程",
|
||||||
"you-can-close-this-page-manually": "您可手动关闭此页面",
|
"you-can-close-this-page-manually": "您可手动关闭此页面",
|
||||||
@ -79,5 +72,26 @@
|
|||||||
"flip-vertically": "垂直翻转",
|
"flip-vertically": "垂直翻转",
|
||||||
"dump-traffic": "转储数据",
|
"dump-traffic": "转储数据",
|
||||||
"right-to-left-text-order": "从右到左的文字顺序",
|
"right-to-left-text-order": "从右到左的文字顺序",
|
||||||
"auto-wrap-line": "自动折行"
|
"auto-wrap-line": "自动折行",
|
||||||
|
"process-as-": "处理方式:",
|
||||||
|
"text": "文本",
|
||||||
|
"picture": "照片",
|
||||||
|
"pattern": "图案",
|
||||||
|
"large-font": "大字体",
|
||||||
|
"accessibility": "无障碍",
|
||||||
|
"language": "语言",
|
||||||
|
"layout": "布局",
|
||||||
|
"ok": "确定",
|
||||||
|
"cancel": "取消",
|
||||||
|
"yes": "是",
|
||||||
|
"no": "否",
|
||||||
|
"about": "关于",
|
||||||
|
"home-page-": "主页:",
|
||||||
|
"contributors": "贡献者",
|
||||||
|
"developer": "开发者",
|
||||||
|
"translator": "翻译",
|
||||||
|
"all-testers-and-users": "所有测试及正式用户",
|
||||||
|
"everyone-is-awesome": "每个人都是好样的!",
|
||||||
|
"license": "许可证",
|
||||||
|
"exiting": "退出中……"
|
||||||
}
|
}
|
386
www/main.css
386
www/main.css
@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
--font-size: 1.2em;
|
--font-size: 1.2rem;
|
||||||
|
--line-height: 1.8em;
|
||||||
--span: 8px;
|
--span: 8px;
|
||||||
--span-half: calc(var(--span) / 2);
|
--span-half: calc(var(--span) / 2);
|
||||||
--span-double: calc(var(--span) * 2);
|
--span-double: calc(var(--span) * 2);
|
||||||
@ -13,9 +14,10 @@
|
|||||||
--canvas-back: #fff;
|
--canvas-back: #fff;
|
||||||
--curve: cubic-bezier(.08,.82,.17,1);
|
--curve: cubic-bezier(.08,.82,.17,1);
|
||||||
--panel-height: 20em;
|
--panel-height: 20em;
|
||||||
--target-color: rgba(0, 255, 0, 0.2);
|
--target-color: rgba(0, 255, 255, 0.2);
|
||||||
--notice-color: rgba(0, 0, 255, 0.2);
|
--notice-wait: rgba(0, 128, 255, 0.2);
|
||||||
--notice-warning: rgba(255, 128, 0, 0.2);
|
--notice-note: rgba(0, 255, 0, 0.2);
|
||||||
|
--notice-warn: rgba(255, 128, 0, 0.2);
|
||||||
--notice-error: rgba(255, 0, 0, 0.2);
|
--notice-error: rgba(255, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,29 +29,41 @@ body.no-animation *::after {
|
|||||||
animation-timing-function: steps(1);
|
animation-timing-function: steps(1);
|
||||||
animation-duration: 0ms !important;
|
animation-duration: 0ms !important;
|
||||||
}
|
}
|
||||||
|
body.large-font,
|
||||||
|
#large-font+label {
|
||||||
|
font-size: calc(var(--font-size) * 1.2);
|
||||||
|
line-height: calc(var(--line-height) * 1.2);
|
||||||
|
}
|
||||||
|
body.force-rtl,
|
||||||
|
#force-rtl+label {
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
border: none;
|
border: none;
|
||||||
background-color: var(--back-color);
|
background-color: var(--back-color);
|
||||||
color: var(--fore-color);
|
color: var(--fore-color);
|
||||||
font-size: var(--font-size);
|
font-size: var(--font-size);
|
||||||
|
line-height: var(--line-height);
|
||||||
font-family: 'Noto Sans', 'Segoe UI', sans-serif;
|
font-family: 'Noto Sans', 'Segoe UI', sans-serif;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
h1 {
|
h1, h2 {
|
||||||
font-size: 1.5em;
|
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
margin: var(--span-half) 0;
|
margin: var(--span-half) 0;
|
||||||
}
|
}
|
||||||
|
h1 { font-size: 1.5em; }
|
||||||
|
h2 { font-size: 1.2em; }
|
||||||
a:link, a:visited {
|
a:link, a:visited {
|
||||||
color: #33f;
|
color: #33f;
|
||||||
}
|
}
|
||||||
a:hover, a:active {
|
a:hover, a:active {
|
||||||
color: #22f;
|
color: #22f;
|
||||||
}
|
}
|
||||||
a {
|
a+a {
|
||||||
transition: all var(--anim-time) ease-out;
|
margin-left: var(--font-size);
|
||||||
}
|
}
|
||||||
.center {
|
.center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -57,16 +71,23 @@ a {
|
|||||||
button, input, select, textarea {
|
button, input, select, textarea {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
color: var(--fore-color);
|
color: var(--fore-color);
|
||||||
|
/* background-color: var(--back-color); */
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
select[multiple] {
|
||||||
|
width: 8em;
|
||||||
|
padding: var(--border);
|
||||||
|
margin: var(--span-half) var(--span);
|
||||||
}
|
}
|
||||||
button, input[type="number"], input[type="text"], select {
|
button, input[type="number"], input[type="text"], select {
|
||||||
margin: var(--span-half) var(--span);
|
margin: var(--span-half) var(--span);
|
||||||
border: var(--border) solid var(--fore-color);
|
border: var(--border) solid var(--fore-color);
|
||||||
padding: var(--span-half) var(--span);
|
padding: var(--span-half) var(--span);
|
||||||
background-color: transparent;
|
|
||||||
transition: all var(--anim-time) var(--curve);
|
transition: all var(--anim-time) var(--curve);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
min-width: 6em;
|
min-width: 6em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
line-height: calc(var(--font-size) + var(--span));
|
||||||
}
|
}
|
||||||
input[type="number"], input[type="text"] {
|
input[type="number"], input[type="text"] {
|
||||||
width: 6em;
|
width: 6em;
|
||||||
@ -80,37 +101,50 @@ button:hover {
|
|||||||
button:active {
|
button:active {
|
||||||
box-shadow: 0 0 var(--span) inset var(--fore-color);
|
box-shadow: 0 0 var(--span) inset var(--fore-color);
|
||||||
}
|
}
|
||||||
|
@keyframes notice-fade {
|
||||||
|
to { background-color: transparent; }
|
||||||
|
}
|
||||||
|
@keyframes notice-wait {
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
}
|
||||||
#notice {
|
#notice {
|
||||||
min-height: var(--font-size);
|
min-height: var(--font-size);
|
||||||
background-color: var(--notice-color);
|
|
||||||
/* border: var(--border) solid var(--fore-color); */
|
|
||||||
}
|
}
|
||||||
#notice.warning, #button-exit {
|
#notice span {
|
||||||
background-color: var(--notice-warning);
|
display: block;
|
||||||
}
|
}
|
||||||
#notice.error {
|
#notice .note {
|
||||||
|
background-color: var(--notice-note);
|
||||||
|
animation: notice-fade 1s ease-out 1s 1 forwards;
|
||||||
|
}
|
||||||
|
#notice .wait {
|
||||||
|
background-color: var(--notice-wait);
|
||||||
|
animation: notice-wait 2s ease-in-out 0s infinite forwards;
|
||||||
|
}
|
||||||
|
#notice .warn {
|
||||||
|
background-color: var(--notice-warn);
|
||||||
|
animation: notice-fade 1s ease-out 1s 1 forwards;
|
||||||
|
}
|
||||||
|
#notice .error {
|
||||||
background-color: var(--notice-error);
|
background-color: var(--notice-error);
|
||||||
|
animation: notice-fade 1s ease-out 1s 1 forwards;
|
||||||
|
}
|
||||||
|
#button-exit {
|
||||||
|
background-color: var(--notice-warn);
|
||||||
}
|
}
|
||||||
.noscript {
|
.noscript {
|
||||||
|
margin: var(--span-double);
|
||||||
|
text-align: center;
|
||||||
background-color: var(--notice-error);
|
background-color: var(--notice-error);
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
main, header, footer {
|
main, header, footer {
|
||||||
max-width: 45em;
|
max-width: 45em;
|
||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
/* flex-wrap: wrap; */
|
|
||||||
}
|
}
|
||||||
main>.left {
|
main>.canvas-side {
|
||||||
flex-grow: 1;
|
|
||||||
/* position: sticky; */
|
|
||||||
/* top: 0; */
|
|
||||||
height: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
margin: var(--span);
|
|
||||||
min-width: 12em;
|
|
||||||
}
|
|
||||||
main>.right {
|
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -118,6 +152,40 @@ main>.right {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
min-width: calc(var(--paper-width) + var(--border-double) + var(--span-double));
|
min-width: calc(var(--paper-width) + var(--border-double) + var(--span-double));
|
||||||
}
|
}
|
||||||
|
main>.menu-side {
|
||||||
|
flex-grow: 1;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
margin: var(--span);
|
||||||
|
min-width: 12em;
|
||||||
|
}
|
||||||
|
main>.menu-side>.menu {
|
||||||
|
border: var(--border) solid var(--fore-color);
|
||||||
|
border-bottom: none;
|
||||||
|
margin-top: var(--span);
|
||||||
|
}
|
||||||
|
.compact-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
|
background-color: var(--back-color);
|
||||||
|
}
|
||||||
|
.compact-button {
|
||||||
|
width: max-content;
|
||||||
|
height: 2em;
|
||||||
|
flex-grow: 1;
|
||||||
|
line-height: 2em;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border-top: var(--border) solid var(--fore-color);
|
||||||
|
border-bottom: var(--border) solid transparent;
|
||||||
|
}
|
||||||
|
.compact-button.active {
|
||||||
|
border: var(--border) solid var(--fore-color);
|
||||||
|
border-top: var(--border) solid transparent;
|
||||||
|
}
|
||||||
canvas#preview, canvas#control-canvas, #control-document {
|
canvas#preview, canvas#control-canvas, #control-document {
|
||||||
border: var(--border) solid var(--fore-color);
|
border: var(--border) solid var(--fore-color);
|
||||||
background-color: var(--canvas-back);
|
background-color: var(--canvas-back);
|
||||||
@ -157,54 +225,17 @@ p {
|
|||||||
margin: var(--span) 0;
|
margin: var(--span) 0;
|
||||||
}
|
}
|
||||||
.panel {
|
.panel {
|
||||||
border: var(--border) solid currentColor;
|
|
||||||
height: calc(var(--font-size) + 8px);
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
/* scrollbar-width: thin; */
|
height: 0;
|
||||||
transition: height var(--anim-time) var(--curve);
|
|
||||||
padding: 0 var(--span);
|
|
||||||
margin: var(--span) 0;
|
|
||||||
}
|
}
|
||||||
.panel::before {
|
.panel.active {
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.panel:not(.sub)::before { content: '📌'; }
|
|
||||||
.panel.sub::before { content: '📎'; }
|
|
||||||
.panel.sub {
|
|
||||||
border-width: var(--border) 0 0 0;
|
|
||||||
}
|
|
||||||
.panel.expanded {
|
|
||||||
height: var(--panel-height);
|
height: var(--panel-height);
|
||||||
animation: delay-scrollable var(--anim-time) steps(1) 0s 1 forwards;
|
padding: var(--span-double) var(--span);
|
||||||
/* overflow-y: scroll; */
|
/* overflow-y: scroll; */
|
||||||
}
|
}
|
||||||
.panel.sub.expanded {
|
.panel.sub.active {
|
||||||
height: calc(var(--panel-height) / 2);
|
height: calc(var(--panel-height) / 2);
|
||||||
}
|
}
|
||||||
.panel>:nth-child(1),
|
|
||||||
.panel>:nth-child(1):link,
|
|
||||||
.panel>:nth-child(1):visited {
|
|
||||||
display: block;
|
|
||||||
margin: var(--span-half);
|
|
||||||
text-decoration: none;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--fore-color);
|
|
||||||
}
|
|
||||||
.panel>:nth-child(1)::before {
|
|
||||||
content: '>>';
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
opacity: 0;
|
|
||||||
width: 0;
|
|
||||||
overflow: visible;
|
|
||||||
right: 2.5em;
|
|
||||||
transition: all var(--anim-time) var(--curve);
|
|
||||||
}
|
|
||||||
.panel>:nth-child(1):hover::before,
|
|
||||||
.panel>:nth-child(1):active::before {
|
|
||||||
opacity: 1;
|
|
||||||
right: 1.5em;
|
|
||||||
}
|
|
||||||
input[type="range"] {
|
input[type="range"] {
|
||||||
width: 10em;
|
width: 10em;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
@ -218,7 +249,16 @@ input[type="range"] {
|
|||||||
.hint {
|
.hint {
|
||||||
animation: hint 3s ease-out 0.1s infinite;
|
animation: hint 3s ease-out 0.1s infinite;
|
||||||
}
|
}
|
||||||
#hidden, .hidden { display: none; }
|
.hidden {
|
||||||
|
/* visibility: hidden; */
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
#hidden, .hard-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
#error-record {
|
#error-record {
|
||||||
font-family: 'DejaVu Sans Mono', 'Consolas', monospace;
|
font-family: 'DejaVu Sans Mono', 'Consolas', monospace;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -244,11 +284,148 @@ dl {
|
|||||||
margin: var(--span) 0;
|
margin: var(--span) 0;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
dd { display: inline; }
|
||||||
|
dd+dd { margin-left: var(--font-size); }
|
||||||
hr {
|
hr {
|
||||||
border: none;
|
border: none;
|
||||||
border-top: var(--border) solid var(--fore-color);
|
border-top: var(--border) solid var(--fore-color);
|
||||||
}
|
}
|
||||||
|
#frame {
|
||||||
|
width: 100%;
|
||||||
|
height: inherit;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.blank {
|
||||||
|
height: 0em;
|
||||||
|
}
|
||||||
|
.shade {
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: var(--back-color);
|
||||||
|
opacity: 0.95;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
#dialog {
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
top: calc(50% - 10em);
|
||||||
|
text-align: center;
|
||||||
|
z-index: 2;
|
||||||
|
opacity: 2;
|
||||||
|
transition: opacity var(--anim-time) var(--curve);
|
||||||
|
}
|
||||||
|
#dialog>.content {
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 40em;
|
||||||
|
margin: auto;
|
||||||
|
border: var(--border) solid var(--fore-color);
|
||||||
|
transition: transform var(--anim-time) var(--curve);
|
||||||
|
}
|
||||||
|
#dialog.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
height: unset;
|
||||||
|
}
|
||||||
|
#dialog.hidden>.content {
|
||||||
|
transform: scaleY(0);
|
||||||
|
}
|
||||||
|
#dialog-content {
|
||||||
|
height: 10em;
|
||||||
|
margin: auto;
|
||||||
|
padding: var(--span);
|
||||||
|
}
|
||||||
|
#dialog-choices {
|
||||||
|
margin: auto;
|
||||||
|
padding: var(--span);
|
||||||
|
}
|
||||||
|
#choice-input {
|
||||||
|
max-width: 100%;
|
||||||
|
width: 16em;
|
||||||
|
}
|
||||||
|
#accessibility {
|
||||||
|
text-align: initial;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
#accessibility>*:nth-child(1) {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
#select-language {
|
||||||
|
width: calc(100% - var(--span-double));
|
||||||
|
height: 8em;
|
||||||
|
border: var(--border) solid var(--fore-color);
|
||||||
|
padding: var(--span);
|
||||||
|
margin: var(--span);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
#select-language option {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
#select-language option:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
#accessibility>*:nth-child(2) {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
@keyframes jump {
|
||||||
|
0% { transform: translateY(0); }
|
||||||
|
50% { transform: translateY(var(--font-size)); }
|
||||||
|
100% { transform: translateY(0); }
|
||||||
|
}
|
||||||
|
#loading-screen {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--back-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 1;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity var(--anim-time) var(--curve);
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
background-image: url('icon.svg');
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
width: 80%;
|
||||||
|
max-width: 16em;
|
||||||
|
height: 80%;
|
||||||
|
max-height: 16em;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
#loading-screen.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
#loading-screen>.dots {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
#loading-screen>.dots>span {
|
||||||
|
display: inline-block;
|
||||||
|
width: var(--font-size);
|
||||||
|
height: var(--font-size);
|
||||||
|
margin: var(--font-size);
|
||||||
|
background-color: var(--fore-color);
|
||||||
|
border-radius: calc(var(--font-size) / 2);
|
||||||
|
animation: jump 1s ease 0s infinite;
|
||||||
|
}
|
||||||
|
#loading-screen>.dots>span:nth-child(1) { animation-delay: 0s; }
|
||||||
|
#loading-screen>.dots>span:nth-child(2) { animation-delay: 0.3s; }
|
||||||
|
#loading-screen>.dots>span:nth-child(3) { animation-delay: 0.6s; }
|
||||||
|
|
||||||
|
a {
|
||||||
|
transition: all var(--anim-time) ease-out;
|
||||||
|
}
|
||||||
@keyframes delay-scrollable {
|
@keyframes delay-scrollable {
|
||||||
from { overflow: hidden; }
|
from { overflow: hidden; }
|
||||||
to { overflow: auto; }
|
to { overflow: auto; }
|
||||||
@ -256,23 +433,69 @@ hr {
|
|||||||
@media (max-width: 800px) {
|
@media (max-width: 800px) {
|
||||||
:root {
|
:root {
|
||||||
--panel-height: 16em;
|
--panel-height: 16em;
|
||||||
/* --font-size: 1em; */
|
--font-size: 1em;
|
||||||
}
|
}
|
||||||
main {
|
main {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
main>.left {
|
#title { display: none; }
|
||||||
/* height: 16em; */
|
main>.canvas-side {
|
||||||
overflow: auto;
|
|
||||||
width: calc(100% - var(--span-double) - var(--border-double));
|
|
||||||
}
|
|
||||||
main>.right {
|
|
||||||
min-width: unset;
|
min-width: unset;
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
position: fixed;
|
||||||
|
top: var(--line-height);
|
||||||
|
height: calc(100% - var(--panel-height) - 2em);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
main>.canvas-side>.buttons,
|
||||||
|
main>.menu-side>.buttons {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 2em;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
main>.canvas-side>.buttons button,
|
||||||
|
main>.menu-side>.buttons button {
|
||||||
|
background-color: var(--back-color);
|
||||||
|
}
|
||||||
|
main>.menu-side {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
position: fixed;
|
||||||
|
background-color: var(--back-color);
|
||||||
|
top: unset;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
height: var(--panel-height);
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
main>.menu-side>.menu {
|
||||||
|
height: var(--panel-height);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#notice {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
main>.menu-side>.compact-menu {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.blank {
|
||||||
|
height: 2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 384px) {
|
@media (max-width: 384px) {
|
||||||
canvas#preview, canvas#control-canvas, #control-document {
|
canvas#preview, canvas#control-canvas, #control-document {
|
||||||
width: calc(100% - var(--border-double));
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
@ -288,7 +511,18 @@ hr {
|
|||||||
color: #77f;
|
color: #77f;
|
||||||
}
|
}
|
||||||
canvas#preview, canvas#control-canvas, #control-document {
|
canvas#preview, canvas#control-canvas, #control-document {
|
||||||
filter: brightness(0.5);
|
filter: brightness(0.6);
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
filter: brightness(0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion) {
|
||||||
|
body {
|
||||||
|
transition-duration: 0ms !important;
|
||||||
|
transition: none;
|
||||||
|
animation-timing-function: steps(1);
|
||||||
|
animation-duration: 0ms !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
|
327
www/main.js
327
www/main.js
@ -7,7 +7,7 @@
|
|||||||
* Double-tap the "Cat Printer" title to activate
|
* Double-tap the "Cat Printer" title to activate
|
||||||
*/
|
*/
|
||||||
function debug() {
|
function debug() {
|
||||||
let script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.src = 'vconsole.js';
|
script.src = 'vconsole.js';
|
||||||
document.body.appendChild(script);
|
document.body.appendChild(script);
|
||||||
script.addEventListener('load', () => new window.VConsole());
|
script.addEventListener('load', () => new window.VConsole());
|
||||||
@ -17,15 +17,16 @@ document.getElementById('title').addEventListener('dblclick', debug);
|
|||||||
var hidden_area = document.getElementById('hidden');
|
var hidden_area = document.getElementById('hidden');
|
||||||
|
|
||||||
const hint = (function() {
|
const hint = (function() {
|
||||||
let hints = [];
|
let hints;
|
||||||
let callback = (event) => {
|
const callback = (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.currentTarget.classList.remove('hint');
|
event.currentTarget.classList.remove('hint');
|
||||||
event.currentTarget.removeEventListener('click', callback);
|
event.currentTarget.removeEventListener('click', callback);
|
||||||
}
|
}
|
||||||
return function(selector) {
|
return function(selector) {
|
||||||
|
if (hints)
|
||||||
hints.forEach(element => element.classList.remove('hint'));
|
hints.forEach(element => element.classList.remove('hint'));
|
||||||
hints = document.querySelectorAll(selector);
|
hints = typeof selector === 'string' ? document.querySelectorAll(selector) : selector;
|
||||||
hints.forEach(element => {
|
hints.forEach(element => {
|
||||||
element.classList.add('hint');
|
element.classList.add('hint');
|
||||||
element.addEventListener('click', callback);
|
element.addEventListener('click', callback);
|
||||||
@ -33,28 +34,95 @@ const hint = (function() {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
class _Notice {
|
const Notice = (function() {
|
||||||
element;
|
const notice = document.getElementById('notice');
|
||||||
constructor() {
|
let last_span;
|
||||||
this.element = document.getElementById('notice');
|
function put(message, things, class_name) {
|
||||||
|
let text = i18n(message, things) || message;
|
||||||
|
if (last_span) last_span.remove();
|
||||||
|
let span = document.createElement('span');
|
||||||
|
span.innerText = text;
|
||||||
|
span.classList.add(class_name);
|
||||||
|
notice.appendChild(span);
|
||||||
|
last_span = span;
|
||||||
}
|
}
|
||||||
_message(message, things) {
|
return {
|
||||||
this.element.innerText = i18n(message, things) || message;
|
note: (message, things) => put(message, things, 'note'),
|
||||||
|
wait: (message, things) => put(message, things, 'wait'),
|
||||||
|
warn: (message, things) => put(message, things, 'warn'),
|
||||||
|
error: (message, things) => put(message, things, 'error')
|
||||||
}
|
}
|
||||||
makeLogger(class_name) {
|
})();
|
||||||
return (message, things) => {
|
|
||||||
this.element.classList.value = class_name;
|
|
||||||
this._message(message, things);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
notice = this.makeLogger('notice');
|
|
||||||
warn = this.makeLogger('warning');
|
|
||||||
error = this.makeLogger('error');
|
|
||||||
}
|
|
||||||
|
|
||||||
const Notice = new _Notice();
|
const Dialog = (function() {
|
||||||
|
const dialog = document.getElementById('dialog');
|
||||||
|
const dialog_content = document.getElementById('dialog-content');
|
||||||
|
const dialog_choices = document.getElementById('dialog-choices');
|
||||||
|
const dialog_input = document.getElementById('dialog-input');
|
||||||
|
let last_choices;
|
||||||
|
function clean_up() {
|
||||||
|
if (last_choices)
|
||||||
|
for (let choice of last_choices)
|
||||||
|
choice.remove();
|
||||||
|
// elements
|
||||||
|
for (let element of dialog_content.children)
|
||||||
|
hidden_area.appendChild(element);
|
||||||
|
// text nodes
|
||||||
|
for (let node of dialog_content.childNodes)
|
||||||
|
node.remove();
|
||||||
|
}
|
||||||
|
function show(argument, as_string = false) {
|
||||||
|
dialog.classList.remove('hidden');
|
||||||
|
if (as_string)
|
||||||
|
dialog_content.innerText = argument;
|
||||||
|
else
|
||||||
|
dialog_content.appendChild(document.querySelector(argument));
|
||||||
|
}
|
||||||
|
function apply_callback(callback, have_input = false, ... choices) {
|
||||||
|
last_choices = [];
|
||||||
|
dialog_input.value = '';
|
||||||
|
dialog_input.style.display = have_input ? 'unset' : 'none';
|
||||||
|
for (let choice of choices) {
|
||||||
|
let button = document.createElement('button');
|
||||||
|
button.setAttribute('data-i18n', choice);
|
||||||
|
button.innerText = i18n(choice);
|
||||||
|
if (!have_input)
|
||||||
|
button.addEventListener('click', () => dialog_input.value = choice);
|
||||||
|
dialog_choices.appendChild(button);
|
||||||
|
last_choices.push(button);
|
||||||
|
}
|
||||||
|
last_choices[0].addEventListener('click', () => {
|
||||||
|
if (callback) callback(dialog_input.value);
|
||||||
|
dialog.classList.add('hidden');
|
||||||
|
});
|
||||||
|
if (last_choices.length > 1)
|
||||||
|
last_choices[1].addEventListener('click', () => {
|
||||||
|
if (callback) callback(null);
|
||||||
|
dialog.classList.add('hidden');
|
||||||
|
});
|
||||||
|
hint([last_choices[0]]);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
alert: function(selector, callback, as_string = false) {
|
||||||
|
clean_up();
|
||||||
|
apply_callback(callback, false, 'ok');
|
||||||
|
show(selector, as_string);
|
||||||
|
},
|
||||||
|
confirm: function(selector, callback, as_string = false) {
|
||||||
|
clean_up();
|
||||||
|
apply_callback(callback, false, 'yes', 'no');
|
||||||
|
show(selector, as_string);
|
||||||
|
},
|
||||||
|
prompt: function(selector, callback, as_string = false) {
|
||||||
|
clean_up();
|
||||||
|
apply_callback(callback, true, 'ok', 'cancel');
|
||||||
|
show(selector, as_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
class _ErrorHandler {
|
class _ErrorHandler {
|
||||||
|
// TODO make better
|
||||||
recordElement;
|
recordElement;
|
||||||
constructor() {
|
constructor() {
|
||||||
this.recordElement = document.getElementById('error-record');
|
this.recordElement = document.getElementById('error-record');
|
||||||
@ -65,8 +133,11 @@ class _ErrorHandler {
|
|||||||
*/
|
*/
|
||||||
report(error, output) {
|
report(error, output) {
|
||||||
Notice.error('error-happened-please-check-error-message');
|
Notice.error('error-happened-please-check-error-message');
|
||||||
let hidden_panel = this.recordElement.parentElement;
|
let button = document.querySelector('button[data-panel="panel-error"]');
|
||||||
if (hidden_panel) hidden_panel.classList.remove('hidden');
|
if (button) {
|
||||||
|
button.classList.remove('hidden');
|
||||||
|
button.click();
|
||||||
|
}
|
||||||
let div = document.createElement('div');
|
let div = document.createElement('div');
|
||||||
div.innerText = (error.stack || (error.name + ': ' + error.message)) + '\n' + output;
|
div.innerText = (error.stack || (error.name + ': ' + error.message)) + '\n' + output;
|
||||||
this.recordElement.appendChild(div);
|
this.recordElement.appendChild(div);
|
||||||
@ -150,57 +221,21 @@ function putEvent(selector, type, callback, thisArg) {
|
|||||||
return new EventPutter(selector, type, callback, thisArg);
|
return new EventPutter(selector, type, callback, thisArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
class PanelController {
|
(function() {
|
||||||
last;
|
let panels = document.querySelectorAll('.panel');
|
||||||
panels;
|
let buttons = document.querySelectorAll('*[data-panel]');
|
||||||
outerPanels;
|
panels.forEach(panel => {
|
||||||
subPanels;
|
let button = document.querySelector(`*[data-panel="${panel.id}"]`);
|
||||||
constructor(selector = '.panel') {
|
if (button) button.addEventListener('click', event => {
|
||||||
const class_expanded = 'expanded';
|
|
||||||
const class_sub = 'sub';
|
|
||||||
let panels = this.panels = [... document.querySelectorAll(selector)];
|
|
||||||
let outer_panels = this.outerPanels = panels.filter(e => !e.classList.contains(class_sub));
|
|
||||||
let sub_panels = this.subPanels = panels.filter(e => e.classList.contains(class_sub));
|
|
||||||
const expand = (panel) => panel.classList.add(class_expanded);
|
|
||||||
const fold = (panel) => {
|
|
||||||
panel.classList.remove(class_expanded);
|
|
||||||
}
|
|
||||||
const fold_all_outer = () => outer_panels.forEach(e => fold(e));
|
|
||||||
const fold_all_sub = () => sub_panels.forEach(e => fold(e));
|
|
||||||
// const fold_all = () => panels.forEach(e => e.classList.remove(class_expanded));
|
|
||||||
fold_all_outer();
|
|
||||||
putEvent(selector + '>:nth-child(1)', 'click', event => {
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.cancelBubble = true;
|
panels.forEach(p => p.classList.remove('active'));
|
||||||
let current = event.currentTarget.parentElement,
|
buttons.forEach(b => b.classList.remove('active'));
|
||||||
last = this.last;
|
panel.classList.add('active');
|
||||||
this.last = current;
|
button.classList.add('active');
|
||||||
if (!last) {
|
});
|
||||||
expand(current);
|
if (panel.hasAttribute('data-default')) button.click();
|
||||||
this.last = current;
|
});
|
||||||
return;
|
})();
|
||||||
}
|
|
||||||
let is_sub = current.classList.contains(class_sub),
|
|
||||||
last_is_sub = last.classList.contains(class_sub);
|
|
||||||
if (current.classList.contains(class_expanded)) {
|
|
||||||
fold(current);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fold_all_outer();
|
|
||||||
if (is_sub && last_is_sub) {
|
|
||||||
fold(last);
|
|
||||||
expand(current.parentElement);
|
|
||||||
last.scrollTo(0, 0);
|
|
||||||
} else if (is_sub && !last_is_sub) {
|
|
||||||
fold_all_sub();
|
|
||||||
expand(last);
|
|
||||||
} else if (!is_sub && last_is_sub) {
|
|
||||||
last.parentElement.scrollTo(0, 0);
|
|
||||||
}
|
|
||||||
expand(current);
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CanvasController {
|
class CanvasController {
|
||||||
/** @type {HTMLCanvasElement} */
|
/** @type {HTMLCanvasElement} */
|
||||||
@ -211,6 +246,7 @@ class CanvasController {
|
|||||||
isCanvas;
|
isCanvas;
|
||||||
algorithm;
|
algorithm;
|
||||||
threshold;
|
threshold;
|
||||||
|
thresholdRange;
|
||||||
transparentAsWhite;
|
transparentAsWhite;
|
||||||
previewData;
|
previewData;
|
||||||
static defaultHeight = 384;
|
static defaultHeight = 384;
|
||||||
@ -226,6 +262,7 @@ class CanvasController {
|
|||||||
this.canvas = document.getElementById('control-canvas');
|
this.canvas = document.getElementById('control-canvas');
|
||||||
this.div = document.getElementById('control-document');
|
this.div = document.getElementById('control-document');
|
||||||
this.height = CanvasController.defaultHeight;
|
this.height = CanvasController.defaultHeight;
|
||||||
|
this.thresholdRange = document.getElementById('threshold');
|
||||||
|
|
||||||
putEvent('input[name="mode"]', 'change', (event) => this.enableMode(event.currentTarget.value), this);
|
putEvent('input[name="mode"]', 'change', (event) => this.enableMode(event.currentTarget.value), this);
|
||||||
putEvent('input[name="algo"]', 'change', (event) => this.useAlgorithm(event.currentTarget.value), this);
|
putEvent('input[name="algo"]', 'change', (event) => this.useAlgorithm(event.currentTarget.value), this);
|
||||||
@ -259,6 +296,7 @@ class CanvasController {
|
|||||||
}
|
}
|
||||||
useAlgorithm(name) {
|
useAlgorithm(name) {
|
||||||
this.algorithm = name;
|
this.algorithm = name;
|
||||||
|
this.thresholdRange.value = 128;
|
||||||
this.activatePreview();
|
this.activatePreview();
|
||||||
}
|
}
|
||||||
expand(length = CanvasController.defaultHeight) {
|
expand(length = CanvasController.defaultHeight) {
|
||||||
@ -267,7 +305,7 @@ class CanvasController {
|
|||||||
crop() {}
|
crop() {}
|
||||||
activatePreview() {
|
activatePreview() {
|
||||||
let preview = this.preview;
|
let preview = this.preview;
|
||||||
let t = this.threshold;
|
let t = Math.min(this.threshold, 255);
|
||||||
if (this.isCanvas) {
|
if (this.isCanvas) {
|
||||||
let canvas = this.canvas;
|
let canvas = this.canvas;
|
||||||
let w = canvas.width, h = canvas.height;
|
let w = canvas.width, h = canvas.height;
|
||||||
@ -281,7 +319,7 @@ class CanvasController {
|
|||||||
monoDirect(mono_data, w, h, t);
|
monoDirect(mono_data, w, h, t);
|
||||||
break;
|
break;
|
||||||
case 'algo-steinberg':
|
case 'algo-steinberg':
|
||||||
monoSteinberg(mono_data, w, h, t);
|
monoSteinberg(mono_data, w, h, Math.floor(t / 2 - 64));
|
||||||
break;
|
break;
|
||||||
case 'algo-halftone':
|
case 'algo-halftone':
|
||||||
// monoHalftone(mono_data, w, h, t);
|
// monoHalftone(mono_data, w, h, t);
|
||||||
@ -291,7 +329,7 @@ class CanvasController {
|
|||||||
monoNew(mono_data, w, h, t);
|
monoNew(mono_data, w, h, t);
|
||||||
break;
|
break;
|
||||||
case 'algo-new-h':
|
case 'algo-new-h':
|
||||||
monoNewH(mono_data, w, h, t);
|
monoNewH(mono_data, w, h, Math.floor(t / 2 - 64));
|
||||||
break;
|
break;
|
||||||
case 'algo-new-v':
|
case 'algo-new-v':
|
||||||
monoNewV(mono_data, w, h, t);
|
monoNewV(mono_data, w, h, t);
|
||||||
@ -325,7 +363,7 @@ class CanvasController {
|
|||||||
context.drawImage(img, 0, 0, canvas.width, canvas.height);
|
context.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||||
this.crop();
|
this.crop();
|
||||||
this.activatePreview();
|
this.activatePreview();
|
||||||
hint('#button-print, #panel-settings');
|
hint('#button-print');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -344,10 +382,68 @@ class CanvasController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {Document} doc */
|
||||||
|
function applyI18nToDom(doc) {
|
||||||
|
doc = doc || document;
|
||||||
|
let elements = doc.querySelectorAll('*[data-i18n]');
|
||||||
|
let i18n_data, translated_string;
|
||||||
|
elements.forEach(element => {
|
||||||
|
i18n_data = element.getAttribute('data-i18n');
|
||||||
|
translated_string = i18n(i18n_data);
|
||||||
|
if (translated_string === i18n_data) return;
|
||||||
|
// element.innerText = translated_string;
|
||||||
|
if (element.firstChild.textContent !== translated_string)
|
||||||
|
element.firstChild.textContent = translated_string;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function initI18n() {
|
||||||
|
if (typeof i18n === 'undefined') return;
|
||||||
|
/** @type {HTMLOptionElement} */
|
||||||
|
let language_options = document.getElementById('select-language');
|
||||||
|
/** @type {{ [code: string]: string }} */
|
||||||
|
let list = await fetch('/lang/list.json').then(r => r.json());
|
||||||
|
let use_language = async (value) => {
|
||||||
|
i18n.useLanguage(value);
|
||||||
|
i18n.add(value, await fetch(`/lang/${value}.json`).then(r => r.json()), true);
|
||||||
|
applyI18nToDom();
|
||||||
|
}
|
||||||
|
for (let code in list) {
|
||||||
|
let option = document.createElement('option');
|
||||||
|
option.value = code;
|
||||||
|
option.innerText = list[code];
|
||||||
|
option.addEventListener('click', (event) => {
|
||||||
|
let option = event.currentTarget;
|
||||||
|
let value = option.value;
|
||||||
|
use_language(value);
|
||||||
|
});
|
||||||
|
language_options.appendChild(option);
|
||||||
|
}
|
||||||
|
apply_default:
|
||||||
|
for (let code of navigator.languages) {
|
||||||
|
if (list[code]) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Main {
|
class Main {
|
||||||
promise;
|
promise;
|
||||||
/** @type {PanelController} */
|
|
||||||
panelController;
|
|
||||||
/** @type {CanvasController} */
|
/** @type {CanvasController} */
|
||||||
canvasController;
|
canvasController;
|
||||||
deviceOptions;
|
deviceOptions;
|
||||||
@ -367,23 +463,38 @@ 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 this.initI18n();
|
await initI18n();
|
||||||
this.panelController = new PanelController();
|
/** @type {HTMLIFrameElement} */
|
||||||
|
let iframe = document.getElementById('frame');
|
||||||
|
iframe.addEventListener('load', () => {
|
||||||
|
applyI18nToDom(iframe.contentDocument);
|
||||||
|
});
|
||||||
this.canvasController = new CanvasController();
|
this.canvasController = new CanvasController();
|
||||||
putEvent('#button-exit', 'click', this.exit, this);
|
putEvent('#button-exit', 'click', this.exit, this);
|
||||||
putEvent('#button-print', 'click', this.print, this);
|
putEvent('#button-print', 'click', this.print, this);
|
||||||
putEvent('#device-refresh', 'click', this.searchDevices, this);
|
putEvent('#device-refresh', 'click', this.searchDevices, this);
|
||||||
|
putEvent('#set-accessibility', 'click', () => Dialog.alert('#accessibility'));
|
||||||
|
putEvent('#link-about', 'click', () => Dialog.alert('#frame'));
|
||||||
this.attachSetter('#scan-time', 'change', 'scan_timeout');
|
this.attachSetter('#scan-time', 'change', 'scan_timeout');
|
||||||
this.attachSetter('#device-options', 'input', 'printer');
|
this.attachSetter('#device-options', 'input', 'printer');
|
||||||
this.attachSetter('input[name="algo"]', 'change', 'mono_algorithm');
|
this.attachSetter('input[name="algo"]', 'change', 'mono_algorithm');
|
||||||
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.notice('dry-run-test-print-process-only')
|
(checked) => checked && Notice.note('dry-run-test-print-process-only')
|
||||||
);
|
);
|
||||||
this.attachSetter('#no-animation', 'change', 'no_animation',
|
this.attachSetter('#no-animation', 'change', 'no_animation',
|
||||||
(checked) => checked ? document.body.classList.add('no-animation')
|
(checked) => checked ? document.body.classList.add('no-animation')
|
||||||
: document.body.classList.remove('no-animation')
|
: document.body.classList.remove('no-animation')
|
||||||
);
|
);
|
||||||
|
this.attachSetter('#large-font', 'change', 'large_font',
|
||||||
|
(checked) => checked ? document.body.classList.add('large-font')
|
||||||
|
: document.body.classList.remove('large-font')
|
||||||
|
);
|
||||||
|
this.attachSetter('#force-rtl', 'change', 'force_rtl',
|
||||||
|
(checked) => checked ? document.body.classList.add('force-rtl')
|
||||||
|
: document.body.classList.remove('force-rtl')
|
||||||
|
);
|
||||||
this.attachSetter('#threshold', 'change', 'threshold',
|
this.attachSetter('#threshold', 'change', 'threshold',
|
||||||
(value) => this.canvasController.threshold = value
|
(value) => this.canvasController.threshold = value
|
||||||
);
|
);
|
||||||
@ -392,6 +503,8 @@ class Main {
|
|||||||
this.attachSetter('#dump', 'change', 'dump');
|
this.attachSetter('#dump', 'change', 'dump');
|
||||||
await this.loadConfig();
|
await this.loadConfig();
|
||||||
this.searchDevices();
|
this.searchDevices();
|
||||||
|
document.querySelector('main').classList.remove('hard-hidden');
|
||||||
|
document.getElementById('loading-screen').classList.add('hidden');
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -409,6 +522,8 @@ class Main {
|
|||||||
*/
|
*/
|
||||||
async loadConfig() {
|
async loadConfig() {
|
||||||
this.settings = await callApi('/query');
|
this.settings = await callApi('/query');
|
||||||
|
if (this.settings['first_run'])
|
||||||
|
Dialog.alert('#accessibility', () => this.set({ first_run: false }));
|
||||||
for (let key in this.settings) {
|
for (let key in this.settings) {
|
||||||
let value = this.settings[key];
|
let value = this.settings[key];
|
||||||
if (this.setters[key] === undefined) continue;
|
if (this.setters[key] === undefined) continue;
|
||||||
@ -423,8 +538,14 @@ class Main {
|
|||||||
if (element.value !== value) return;
|
if (element.value !== value) return;
|
||||||
element.checked = value;
|
element.checked = value;
|
||||||
break;
|
break;
|
||||||
default:
|
case 'text':
|
||||||
|
case 'number':
|
||||||
|
case 'range':
|
||||||
element.value = value;
|
element.value = value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (element.value === value)
|
||||||
|
element.click();
|
||||||
}
|
}
|
||||||
element.dispatchEvent(new Event('change'));
|
element.dispatchEvent(new Event('change'));
|
||||||
});
|
});
|
||||||
@ -461,11 +582,12 @@ class Main {
|
|||||||
}).bind(this), this);
|
}).bind(this), this);
|
||||||
}
|
}
|
||||||
async exit() {
|
async exit() {
|
||||||
|
Notice.wait('exiting');
|
||||||
await this.set(this.settings);
|
await this.set(this.settings);
|
||||||
await callApi('/exit');
|
await callApi('/exit');
|
||||||
window.close();
|
window.close();
|
||||||
// Browser may block the exit
|
// Browser may block the exit
|
||||||
Notice.notice('you-can-close-this-page-manually');
|
Notice.note('you-can-close-this-page-manually');
|
||||||
}
|
}
|
||||||
/** @param {Response} response */
|
/** @param {Response} response */
|
||||||
async bluetoothProblemHandler(response) {
|
async bluetoothProblemHandler(response) {
|
||||||
@ -473,24 +595,25 @@ class Main {
|
|||||||
let error_details = await response.json();
|
let error_details = await response.json();
|
||||||
if (
|
if (
|
||||||
error_details.name === 'org.bluez.Error.NotReady' ||
|
error_details.name === 'org.bluez.Error.NotReady' ||
|
||||||
error_details.details.indexOf('not turned on') !== -1 ||
|
error_details.name === 'org.freedesktop.DBus.Error.UnknownObject' ||
|
||||||
error_details.details.indexOf('WinError -2147020577') !== -1
|
error_details.details.includes('not turned on') ||
|
||||||
|
error_details.details.includes('WinError -2147020577')
|
||||||
) Notice.warn('please-enable-bluetooth');
|
) Notice.warn('please-enable-bluetooth');
|
||||||
else throw new Error('Unknown Bluetooth Problem');
|
else throw new Error('Unknown Bluetooth Problem');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
async searchDevices() {
|
async searchDevices() {
|
||||||
Notice.notice('scanning-for-devices');
|
Notice.wait('scanning-for-devices');
|
||||||
let search_result = await callApi('/devices', null, this.bluetoothProblemHandler);
|
let search_result = await callApi('/devices', null, this.bluetoothProblemHandler);
|
||||||
if (search_result === null) return;
|
if (search_result === null) return;
|
||||||
let devices = search_result.devices;
|
let devices = search_result.devices;
|
||||||
[... this.deviceOptions.children].forEach(e => e.remove());
|
[... this.deviceOptions.children].forEach(e => e.remove());
|
||||||
if (devices.length === 0) {
|
if (devices.length === 0) {
|
||||||
Notice.notice('no-available-devices-found');
|
Notice.note('no-available-devices-found');
|
||||||
hint('#device-refresh');
|
hint('#device-refresh');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Notice.notice('found-0-available-devices', [devices.length]);
|
Notice.note('found-0-available-devices', [devices.length]);
|
||||||
hint('#insert-picture');
|
hint('#insert-picture');
|
||||||
devices.forEach(device => {
|
devices.forEach(device => {
|
||||||
let option = document.createElement('option');
|
let option = document.createElement('option');
|
||||||
@ -501,12 +624,12 @@ class Main {
|
|||||||
this.deviceOptions.dispatchEvent(new Event('input'));
|
this.deviceOptions.dispatchEvent(new Event('input'));
|
||||||
}
|
}
|
||||||
async print() {
|
async print() {
|
||||||
Notice.notice('printing');
|
Notice.wait('printing');
|
||||||
await fetch('/print', {
|
await fetch('/print', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: this.canvasController.makePbm()
|
body: this.canvasController.makePbm()
|
||||||
}).then(async (response) => {
|
}).then(async (response) => {
|
||||||
if (response.ok) Notice.notice('finished')
|
if (response.ok) Notice.note('finished')
|
||||||
else {
|
else {
|
||||||
let error_data = await response.json();
|
let error_data = await response.json();
|
||||||
if (/address.+not found/.test(error_data.details))
|
if (/address.+not found/.test(error_data.details))
|
||||||
@ -519,28 +642,6 @@ class Main {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async initI18n() {
|
|
||||||
if (typeof i18n === 'undefined') return;
|
|
||||||
i18n.useLanguage(navigator.languages[0]);
|
|
||||||
for (let language of navigator.languages) {
|
|
||||||
let data = await fetch(`/lang/${language}.json`)
|
|
||||||
.then(response => response.ok ? response.json() : null);
|
|
||||||
if (data !== null) {
|
|
||||||
i18n.add(language, data);
|
|
||||||
console.log('Loaded language:', language);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let elements = document.querySelectorAll('*[data-i18n]');
|
|
||||||
let i18n_data, translated_string;
|
|
||||||
elements.forEach(element => {
|
|
||||||
i18n_data = element.getAttribute('data-i18n');
|
|
||||||
translated_string = i18n(i18n_data);
|
|
||||||
if (translated_string === i18n_data) return;
|
|
||||||
// element.innerText = translated_string;
|
|
||||||
if (element.firstChild.textContent !== translated_string)
|
|
||||||
element.firstChild.textContent = translated_string;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var main = new Main();
|
var main = new Main();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user