mirror of
https://codeberg.org/icewind/ptouch-api.git
synced 2026-06-03 10:54:07 +02:00
initial webui work
This commit is contained in:
parent
f7910f967b
commit
71cf4dbcb2
4 changed files with 248 additions and 4 deletions
40
src/main.rs
40
src/main.rs
|
|
@ -1,11 +1,12 @@
|
||||||
mod config;
|
mod config;
|
||||||
mod status;
|
mod status;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use crate::config::{Config, ListenConfig};
|
use crate::config::{Config, ListenConfig};
|
||||||
use axum::body::Bytes;
|
use axum::body::{Body, Bytes};
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::http::StatusCode;
|
use axum::http::{header, HeaderValue, StatusCode};
|
||||||
use axum::response::{IntoResponse, Response};
|
use axum::response::{Html, IntoResponse, Response};
|
||||||
use axum::routing::{get, put};
|
use axum::routing::{get, put};
|
||||||
use axum::{Json, Router};
|
use axum::{Json, Router};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
@ -51,12 +52,15 @@ async fn main() -> MainResult {
|
||||||
|
|
||||||
let mut printer = Printer::open().await?;
|
let mut printer = Printer::open().await?;
|
||||||
printer.reload_status().await?;
|
printer.reload_status().await?;
|
||||||
|
|
||||||
let state = App {
|
let state = App {
|
||||||
printer: Arc::new(Mutex::new(printer)),
|
printer: Arc::new(Mutex::new(printer)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
|
.route("/", get(index))
|
||||||
|
.route("/label.js", get(js))
|
||||||
|
.route("/style.css", get(css))
|
||||||
.route("/status", get(status))
|
.route("/status", get(status))
|
||||||
.route("/print", put(print))
|
.route("/print", put(print))
|
||||||
.with_state(state.clone());
|
.with_state(state.clone());
|
||||||
|
|
@ -173,6 +177,34 @@ impl IntoResponse for ApiError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn index() -> Html<Cow<'static, str>> {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let html = Cow::Owned(std::fs::read_to_string("web/index.html").unwrap());
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
let html = Cow::Borrowed(include_str!("../web/index.html"));
|
||||||
|
Html(html)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn js() -> Response {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let html: Cow<'static, str> = Cow::Owned(std::fs::read_to_string("web/label.js").unwrap());
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
let html: Cow<'static, str> = Cow::Borrowed(include_str!("../web/label.js"));
|
||||||
|
let mut response = Response::new(Body::from(html));
|
||||||
|
response.headers_mut().insert(header::CONTENT_TYPE, HeaderValue::from_static("application/javascript"));
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn css() -> Response {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let html: Cow<'static, str> = Cow::Owned(std::fs::read_to_string("web/style.css").unwrap());
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
let html: Cow<'static, str> = Cow::Borrowed(include_str!("../web/style.css"));
|
||||||
|
let mut response = Response::new(Body::from(html));
|
||||||
|
response.headers_mut().insert(header::CONTENT_TYPE, HeaderValue::from_static("text/css"));
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
async fn status(State(state): State<App>) -> Result<Json<PStatus>, ApiError> {
|
async fn status(State(state): State<App>) -> Result<Json<PStatus>, ApiError> {
|
||||||
let mut printer = state.printer.lock().await;
|
let mut printer = state.printer.lock().await;
|
||||||
debug!("reloading printer status");
|
debug!("reloading printer status");
|
||||||
|
|
|
||||||
34
web/index.html
Normal file
34
web/index.html
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<link rel="icon" href="data:;base64,=">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<title>PTouch</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table id="elements">
|
||||||
|
<thead>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<td>
|
||||||
|
<input type="button" value="Add" id="add"/>
|
||||||
|
</td>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="canvas-width">Width</label>
|
||||||
|
<input type="number" min="0" id="canvas-width" value="300">
|
||||||
|
<select id="canvas-width-unit">
|
||||||
|
<option value="px" selected>px</option>
|
||||||
|
<option value="mm">mm</option>
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
<canvas id="preview" height="32" width="300"></canvas>
|
||||||
|
|
||||||
|
<script src="label.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
156
web/label.js
Normal file
156
web/label.js
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
const elementsTable = document.getElementById('elements');
|
||||||
|
/** @type HTMLCanvasElement preview */
|
||||||
|
const preview = document.getElementById('preview');
|
||||||
|
const renderCtx = preview.getContext("2d");
|
||||||
|
const elementsTableBody = elementsTable.getElementsByTagName('tbody')[0];
|
||||||
|
const addButton = document.getElementById('add');
|
||||||
|
const widthInput = document.getElementById('canvas-width');
|
||||||
|
const widthUnit = document.getElementById('canvas-width-unit');
|
||||||
|
|
||||||
|
let tapeWidthMM = 0;
|
||||||
|
let defaultFontSize = 24;
|
||||||
|
let backgroundColor = "white";
|
||||||
|
let textColor = "white";
|
||||||
|
|
||||||
|
const nextElementId = () => {
|
||||||
|
let rows = elementsTableBody.getElementsByTagName('tr');
|
||||||
|
if (rows.length === 0) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return parseInt(rows[0].dataset.elementId, 10) + 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addElement = () => {
|
||||||
|
let id = nextElementId();
|
||||||
|
let row = document.createElement('tr');
|
||||||
|
row.dataset.elementId = id;
|
||||||
|
|
||||||
|
|
||||||
|
let textCol = document.createElement('td');
|
||||||
|
textCol.classList.add('text');
|
||||||
|
let textInput = document.createElement('input');
|
||||||
|
textInput.type = "text";
|
||||||
|
textInput.addEventListener('input', render);
|
||||||
|
textCol.appendChild(textInput);
|
||||||
|
row.appendChild(textCol);
|
||||||
|
|
||||||
|
{
|
||||||
|
let sizeCol = document.createElement('td');
|
||||||
|
sizeCol.classList.add('size');
|
||||||
|
let sizeInput = document.createElement('input');
|
||||||
|
sizeInput.addEventListener('input', render);
|
||||||
|
sizeInput.type = "number";
|
||||||
|
sizeInput.value = defaultFontSize.toString();
|
||||||
|
sizeCol.appendChild(document.createTextNode("Front size"))
|
||||||
|
sizeCol.appendChild(sizeInput);
|
||||||
|
row.appendChild(sizeCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let posCol = document.createElement('td');
|
||||||
|
posCol.classList.add('pos');
|
||||||
|
let posInputX = document.createElement('input');
|
||||||
|
posInputX.addEventListener('input', render);
|
||||||
|
posInputX.type = "number";
|
||||||
|
posInputX.value = "2";
|
||||||
|
posInputX.classList.add('pos_x');
|
||||||
|
let posInputY = document.createElement('input');
|
||||||
|
posInputY.addEventListener('input', render);
|
||||||
|
posInputY.type = "number";
|
||||||
|
posInputY.value = "2";
|
||||||
|
posInputY.classList.add('pos_y');
|
||||||
|
posCol.appendChild(document.createTextNode("Position"))
|
||||||
|
posCol.appendChild(posInputX);
|
||||||
|
posCol.appendChild(posInputY);
|
||||||
|
row.appendChild(posCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let removeCol = document.createElement('td');
|
||||||
|
removeCol.classList.add('delete');
|
||||||
|
let removeInput = document.createElement('input');
|
||||||
|
removeInput.type = "button";
|
||||||
|
removeInput.value = "×";
|
||||||
|
removeInput.addEventListener('click', removeElement);
|
||||||
|
removeCol.appendChild(removeInput);
|
||||||
|
row.appendChild(removeCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
elementsTableBody.appendChild(row);
|
||||||
|
|
||||||
|
textInput.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeElement = (event) => {
|
||||||
|
let row = event.target.parentElement.parentElement;
|
||||||
|
row.remove();
|
||||||
|
render();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getElements = () => {
|
||||||
|
let rows = elementsTableBody.getElementsByTagName('tr');
|
||||||
|
let elements = [];
|
||||||
|
for (let row of rows) {
|
||||||
|
elements.push({
|
||||||
|
text: row.querySelector('.text input').value,
|
||||||
|
size: parseInt(row.querySelector('.size input').value, 10),
|
||||||
|
x: parseInt(row.querySelector('.pos_x').value, 10),
|
||||||
|
y: parseInt(row.querySelector('.pos_y').value, 10),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
const render = () => {
|
||||||
|
let elements = getElements();
|
||||||
|
let width = preview.width;
|
||||||
|
let height = preview.height;
|
||||||
|
renderCtx.fillStyle = backgroundColor;
|
||||||
|
renderCtx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
|
renderCtx.fillStyle = textColor;
|
||||||
|
for (let element of elements) {
|
||||||
|
renderCtx.font = `${element.size}px serif`;
|
||||||
|
renderCtx.fillText(element.text, element.x, height - element.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resize = () => {
|
||||||
|
let width = parseInt(widthInput.value, 10);
|
||||||
|
const pxPerMM = preview.height / tapeWidthMM;
|
||||||
|
if (widthUnit.value === "mm") {
|
||||||
|
preview.width = width * pxPerMM;
|
||||||
|
} else {
|
||||||
|
preview.width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
render()
|
||||||
|
};
|
||||||
|
|
||||||
|
addButton.addEventListener('click', addElement);
|
||||||
|
widthInput.addEventListener('input', resize);
|
||||||
|
|
||||||
|
widthUnit.addEventListener('change', () => {
|
||||||
|
const pxPerMM = preview.height / tapeWidthMM;
|
||||||
|
if (widthUnit.value === "mm") {
|
||||||
|
widthInput.value = (preview.width / pxPerMM).toFixed(0);
|
||||||
|
} else {
|
||||||
|
widthInput.value = preview.width;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const apiRoot = "";
|
||||||
|
|
||||||
|
fetch(apiRoot + '/status')
|
||||||
|
.then(res => res.json())
|
||||||
|
/** @param {{pixel_width: number, media_width: number, margin_width: number, tape_color: string, text_color: string}} status */
|
||||||
|
.then(status => {
|
||||||
|
preview.height = status.pixel_width;
|
||||||
|
defaultFontSize = status.pixel_width;
|
||||||
|
backgroundColor = status.tape_color;
|
||||||
|
textColor = status.text_color;
|
||||||
|
tapeWidthMM = status.media_width;
|
||||||
|
render();
|
||||||
|
addElement();
|
||||||
|
})
|
||||||
22
web/style.css
Normal file
22
web/style.css
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
:root {
|
||||||
|
--background-color: #959595;
|
||||||
|
--font-color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--background-color: #1e1e1e;
|
||||||
|
--font-color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body, input {
|
||||||
|
background: var(--background-color);
|
||||||
|
color: var(--font-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elements {
|
||||||
|
.pos input {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue