Compare commits

..

45 Commits

Author SHA1 Message Date
flop 6871748164 fix: bugfixes so its clean 1 week ago
flop a6fbd31320 fix: imports for browser 1 week ago
flop 96852c9105 fix: remove old files 1 week ago
flop aec5200f6f feat: fix exports 1 week ago
flop c3ab9cd1b8 feat: go for dist instead of public folder 1 week ago
flop d34b6f3035 feat: changes for library exports 1 week ago
flop 480b10f764 refactor(editor): split out web editor 1 week ago
flop 5071016ded gatest wifi 1 week ago
flop d1878f491d chore: gpn24 wifi update 1 week ago
flop 1cbaee1dd2 chore: gpn24 animated wifi default image 1 week ago
flop 86a51d3cf9 feat: drag and drop support 1 week ago
flop e44da7cb20 chore: GPN24 default screen 1 week ago
flop e992a4c8e4 chore: template updates for gpn24 1 week ago
flop f543016595 chore: updates for 0.text 1 week ago
flop 213ead59b2 feat: update line support 1 week ago
flop 9a3e435cfc chore: updated template for gpn 1 week ago
flop 3d7daf8b74 chore: updated template for gpn24 1 week ago
flop 97a1feb06e chore: more example bin files 1 week ago
flop 79fa6e2fc8 feat: updated .bin file 1 week ago
flop 211480d61f updates 1 week ago
flop 9cdd672342 fix(kulturanzeiger_avj): updated formats 1 week ago
flop 3e497f512b feat(browser): add two more flags to text scroll 1 week ago
flop 9a1d9c1d6a fix: top align fonts 1 week ago
flop 6e169930f7 original files 1 week ago
flop 28667774dd feat(php): updates to live 1 week ago
flop 178243a9cc feat(php): update live 1 week ago
flop 9e31555d6f feat(cpp): add nix build stuff 1 week ago
flop 1fb38e3e0d feat(php): live script 1 week ago
flop 1831ef6f95 feat: fix dark/light style 1 week ago
flop d6c712dfc3 feat: vibe color 1 week ago
flop f674459af9 fix: selection bug 1 week ago
flop cca5be7e23 fix: font rendering 1 week ago
flop ef63b862ae fix: default values 1 week ago
flop 5bce254f65 fix: redraw on reloads 1 week ago
flop de45362fe4 feat: clear all 1 week ago
flop c4bd427eff feat: session persistence 1 week ago
flop e8f5d5e451 fix: animation support 1 week ago
flop 71ade5290a feat: prevent easy reloading 1 week ago
flop 958ebdf4f0 feat: create nice image defaults 1 week ago
flop ef07183a66 feat: image draw editor support 1 week ago
flop 15b94d51f9 feat: image editor support 1 week ago
flop 7316a9d3a8 fix: sections change instead of deleteing 1 week ago
flop a20213b6e8 fix: timestamps for timespan sections 1 week ago
flop 4c90223474 refactor: move to mithril 1 week ago
flop 0db0e11815 fix: multiple adjustments to rendering and updating fields 1 week ago
  1. 0
      120px-GPN24.png
  2. 0
      120px-GPN24_.png
  3. 0
      120px-GPN24_wifi0.png
  4. 0
      120px-GPN24_wifi1.png
  5. 0
      120px-GPN24_wifi2.png
  6. 0
      120px-GPN24_wifi3.png
  7. 0
      300px-GPN24.png
  8. 0
      300px-GPN24_g.png
  9. 0
      300px-GPN24_g.svg
  10. 0
      300px-GPN24_gray.png
  11. 0
      300px-GPN24_gray2.png
  12. 0
      300px-GPN24_gray3.png
  13. 0
      300px-GPN24_gray3.svg
  14. 0
      300px-GPN24_gray4.svg
  15. BIN
      cpp/Kulturanzeiger/Artikel1.bin
  16. BIN
      cpp/Kulturanzeiger/Artikel1.png
  17. BIN
      cpp/Kulturanzeiger/Artikel21.bin
  18. BIN
      cpp/Kulturanzeiger/Artikel21.png
  19. BIN
      cpp/Kulturanzeiger/Frosch161.bin
  20. BIN
      cpp/Kulturanzeiger/Frosch161.png
  21. BIN
      cpp/Kulturanzeiger/LaufSpruch1.bin
  22. BIN
      cpp/Kulturanzeiger/LaufSpruch1.png
  23. BIN
      cpp/Kulturanzeiger/LaufSpruch2.bin
  24. BIN
      cpp/Kulturanzeiger/LaufSpruch2.png
  25. BIN
      cpp/Kulturanzeiger/LaufSpruch3.bin
  26. BIN
      cpp/Kulturanzeiger/LaufSpruch3.png
  27. BIN
      cpp/Kulturanzeiger/MvAVJ.bin
  28. BIN
      cpp/Kulturanzeiger/MvAVJ.png
  29. BIN
      cpp/Kulturanzeiger/NotWidersetzen.bin
  30. BIN
      cpp/Kulturanzeiger/NotWidersetzen.png
  31. BIN
      cpp/Kulturanzeiger/Schildkroete.bin
  32. BIN
      cpp/Kulturanzeiger/Schildkroete.png
  33. BIN
      cpp/Kulturanzeiger/Widersetzen.bin
  34. BIN
      cpp/Kulturanzeiger/Widersetzen.png
  35. BIN
      cpp/Kulturanzeiger/Widersetzen1.bin
  36. BIN
      cpp/Kulturanzeiger/Widersetzen1.png
  37. BIN
      cpp/Kulturanzeiger/Zitat1.bin
  38. BIN
      cpp/Kulturanzeiger/Zitat1.png
  39. BIN
      cpp/Kulturanzeiger/Zitat2.bin
  40. BIN
      cpp/Kulturanzeiger/Zitat2.png
  41. BIN
      cpp/Kulturanzeiger/Zitat3.bin
  42. BIN
      cpp/Kulturanzeiger/Zitat3.png
  43. 0
      franzwerk.bin
  44. 0
      gpn24.bin
  45. 2
      php/monoformat_structured.php
  46. 29
      php/schedule.php
  47. 0
      template.bin
  48. 0
      time.bin
  49. 0
      time_nice.bin
  50. 1042
      ts-editor/dist_old/index.html
  51. 23
      ts-editor/index.html
  52. 68
      ts-editor/mobile.css
  53. 102
      ts-editor/src/browser.ts
  54. 41
      ts-editor/style.css
  55. 1042
      ts/dist_old/index.html
  56. 5
      ts/src/driver.ts
  57. BIN
      u8g2_font_5x7_tf.u8g2font

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 403 B

Before

Width:  |  Height:  |  Size: 408 B

After

Width:  |  Height:  |  Size: 408 B

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

@ -26,7 +26,7 @@ function serializePixels(array $pixels): string {
function unserializePixels(int $n, string $serialized): array {
$nBytes = intdiv($n + 7, 8);
if (strlen($serialized) != $nBytes) {
$actual = strlen($serialized);
$actual = strlen($serializd);
throw new \ValueError("Could not unserialize a pixel bitmap: the size $actual does not equal the expected $nBytes");
}
$result = array_fill(0, $n, false);

@ -1,11 +1,5 @@
<?php
$roomMapping = [
];
$passThroughMapping = [
];
include_once("monoformat_schema.php");
include_once("monoformat_structured.php");
@ -29,27 +23,8 @@ function replaceTalkInfo($str, $talks) {
}, $str);
}
if (isset($_GET["mac"]) and array_key_exists($_GET["mac"], $passThroughMapping)) {
$fn = $passThroughMapping[$_GET["mac"]];
if (is_dir($fn)) {
$d = opendir($fn);
$files = [];
while (($e = readdir($d)) !== false) {
if (str_ends_with($e, ".bin")) {
array_push($files, $fn . "/" . $e);
}
}
sort($files);
if (!count($files)) {
die("No files found");
}
$n = (time() / 60) % count($files);
$fn = $files[$n];
}
Header("Content-Type: application/octet-stream");
readfile($fn);
exit(0);
}
$roomMapping = [
];
if (!isset($_GET["mac"]) or !array_key_exists($_GET["mac"], $roomMapping)) {
/* We didn't find this device in the mapping, so let's just

File diff suppressed because it is too large Load Diff

@ -5,12 +5,12 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>MonoDisplay Editor</title>
<script>!function () { var t = localStorage.getItem('theme'); t && document.documentElement.setAttribute('data-theme', t) }();</script>
<link href="style.css" rel="stylesheet" />
<link href="mobile.css" rel="stylesheet" />
<script>!function(){var t=localStorage.getItem('theme');t&&document.documentElement.setAttribute('data-theme',t)}();</script>
<link href="style.css" rel="stylesheet"/>
</head>
<body>
<div id="topbar">
<span class="app-name">MonoDisplay</span>
<div class="tb-sep"></div>
@ -52,13 +52,10 @@
<div class="tb-sep"></div>
<button class="tb-btn" id="theme-toggle" onclick="cycleTheme()">⊙ auto</button>
</div>
<div id="main">
<div id="left">
<span class="pv-label">preview · <select onchange="changeDisplayLayout()">
<option>120 × 60</option>
<option>120 × 40</option>
</select></span>
</span>
<span class="pv-label">preview · 120 × 60</span>
<div id="display-box">
<canvas id="canvas_root" width="120" height="60"></canvas>
</div>
@ -90,27 +87,25 @@
</div>
</div>
</div>
<div id="right">
<div id="right-hdr">
<span class="rh-title">sections</span>
<button class="tb-btn" id="mob-add-section" onclick="addSection()">
<svg viewBox="0 0 24 24">
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
</button>
</div>
<div id="sections-wrap">
<div class="empty-state">No sections yet.<br />Use <b>Add section</b> to get started.</div>
</div>
</div>
</div>
<!-- confirm dialog -->
<div id="confirm-overlay">
<div id="confirm-box">
<div id="confirm-msg"></div>
<div id="confirm-btns"></div>
</div>
</div>
<script src="./public/mono-display.js"></script>
</body>

@ -1,68 +0,0 @@
@media (max-width: 640px) {
#topbar {
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
gap: 6px;
padding: 0 8px;
}
#topbar::-webkit-scrollbar {
display: none;
}
#topbar>* {
flex-shrink: 0;
}
#topbar .tb-btn {
font-size: 0;
gap: 0;
padding: 3px 8px;
}
#topbar .tb-btn svg {
width: 15px;
height: 15px;
}
#topbar #theme-toggle {
font-size: 11px;
}
#topbar button[onclick="addSection()"] {
display: none;
}
#mob-add-section {
display: flex;
}
#topbar [style*="flex:1"] {
display: none;
}
#main {
flex-direction: column;
overflow-y: auto;
}
#left,
#right {
width: 100%;
border-right: none;
flex-shrink: 0;
}
#left {
border-bottom: 1px solid var(--bd-inner);
padding: 10px;
gap: 8px;
}
#display-box {
aspect-ratio: 2/1;
width: 100%;
}
}

@ -26,7 +26,7 @@ import {
} from "libmonoformat";
let W = 120, H = 60;
const W = 120, H = 60;
// --- State -------------------------------------------------------------------
let file: MonoDisplayFile = new MonoDisplayFile([]);
@ -185,15 +185,6 @@ async function guardDirty(): Promise<boolean> {
);
}
function changeDisplayLayout() {
const sel = document.querySelector('.pv-label select') as HTMLSelectElement;
const [w, h] = sel.value.split('×').map(s => parseInt(s.trim()));
W = w as number;
H = h as number;
((window as any)._mdDriver as MonoDisplayDriver).setSize(W, H);
((window as any)._mdDriver as MonoDisplayDriver).load(() => Promise.resolve(file.toBuffer()));
}
// --- Helpers -----------------------------------------------------------------
function newSec(): MonoFormatElementsAlways {
return {
@ -434,7 +425,7 @@ function triggerPreview() {
function buildPreview() {
if (!(window as any)._mdDriver)
(window as any)._mdDriver = new MonoDisplayDriver("canvas_root", { onColor: "#EC0", offColor: "#000", fps: 25 });
changeDisplayLayout();
(window as any)._mdDriver.load(() => Promise.resolve(file.toBuffer()));
}
// --- Load / Export -----------------------------------------------------------
@ -527,99 +518,82 @@ const PixelCanvas: m.Component<{ img: MonoFormatPixelImage; onpaint: () => void
vnode.state.drawing = false;
vnode.state.drawValue = 1;
},
view({ attrs: { img, onpaint }, state: s }) {
function pixelFromCoords(canvas: HTMLCanvasElement, clientX: number, clientY: number) {
view({ attrs: { img, onpaint }, state }) {
function pixelFromEvent(e: MouseEvent): { x: number; y: number } | null {
const canvas = e.currentTarget as HTMLCanvasElement;
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const x = Math.floor((clientX - rect.left) * scaleX / PIXEL_SCALE);
const y = Math.floor((clientY - rect.top) * scaleY / PIXEL_SCALE);
const x = Math.floor((e.clientX - rect.left) * scaleX / PIXEL_SCALE);
const y = Math.floor((e.clientY - rect.top) * scaleY / PIXEL_SCALE);
if (x < 0 || x >= img.width || y < 0 || y >= img.height) return null;
return { x, y };
}
function paintAt(canvas: HTMLCanvasElement, clientX: number, clientY: number) {
const p = pixelFromCoords(canvas, clientX, clientY);
function paint(e: MouseEvent) {
const p = pixelFromEvent(e);
if (!p) return;
img.pixels[getPixelIndex(img, p.x, p.y)] = s.drawValue;
img.pixels[getPixelIndex(img, p.x, p.y)] = state.drawValue;
onpaint();
drawPixelCanvas(canvas, img);
}
function paint(e: MouseEvent) {
paintAt(e.currentTarget as HTMLCanvasElement, e.clientX, e.clientY);
}
function attachTouch(canvas: HTMLCanvasElement) {
canvas.addEventListener("touchstart", (e: TouchEvent) => {
e.preventDefault();
s.drawing = true;
const touch = e.changedTouches[0];
const p = pixelFromCoords(canvas, touch.clientX, touch.clientY);
if (p) s.drawValue = img.pixels[getPixelIndex(img, p.x, p.y)] ? 0 : 1;
paintAt(canvas, touch.clientX, touch.clientY);
}, { passive: false });
canvas.addEventListener("touchmove", (e: TouchEvent) => {
e.preventDefault();
if (!s.drawing) return;
const touch = e.changedTouches[0];
paintAt(canvas, touch.clientX, touch.clientY);
}, { passive: false });
canvas.addEventListener("touchend", (e) => { e.preventDefault(); s.drawing = false; }, { passive: false });
canvas.addEventListener("touchcancel", (e) => { e.preventDefault(); s.drawing = false; }, { passive: false });
drawPixelCanvas(e.currentTarget as HTMLCanvasElement, img);
}
return m("canvas.pixel-editor", {
width: img.width * PIXEL_SCALE,
height: img.height * PIXEL_SCALE,
oncreate: ({ dom }: m.VnodeDOM) => {
const canvas = dom as HTMLCanvasElement;
drawPixelCanvas(canvas, img);
attachTouch(canvas);
},
oncreate: ({ dom }: m.VnodeDOM) => drawPixelCanvas(dom as HTMLCanvasElement, img),
onupdate: ({ dom }: m.VnodeDOM) => drawPixelCanvas(dom as HTMLCanvasElement, img),
onmousedown: (e: MouseEvent) => {
s.drawing = true;
const p = pixelFromCoords(e.currentTarget as HTMLCanvasElement, e.clientX, e.clientY);
if (p) s.drawValue = img.pixels[getPixelIndex(img, p.x, p.y)] ? 0 : 1;
state.drawing = true;
const p = pixelFromEvent(e);
if (p) state.drawValue = img.pixels[getPixelIndex(img, p.x, p.y)] ? 0 : 1;
paint(e);
},
onmousemove: (e: MouseEvent) => { if (s.drawing) paint(e); },
onmouseup: () => { s.drawing = false; },
onmouseleave: () => { s.drawing = false; },
onmousemove: (e: MouseEvent) => { if (state.drawing) paint(e); },
onmouseup: () => { state.drawing = false; },
onmouseleave: () => { state.drawing = false; },
ondragover: (e: DragEvent) => {
e.preventDefault();
e.dataTransfer!.dropEffect = "copy";
},
ondrop: (e: DragEvent) => {
e.preventDefault();
const file = e.dataTransfer?.files[0];
if (!file || !file.type.startsWith("image/")) return;
const url = URL.createObjectURL(file);
const htmlImg = new Image();
htmlImg.onload = () => {
// Draw the dropped image into an offscreen canvas to read pixel data
const offscreen = document.createElement("canvas");
offscreen.width = htmlImg.width;
offscreen.height = htmlImg.height;
const ctx = offscreen.getContext("2d")!;
ctx.drawImage(htmlImg, 0, 0);
const imageData = ctx.getImageData(0, 0, htmlImg.width, htmlImg.height);
// Convert to your pixel format — adjust this to match your img structure
const scale = Math.min(1, 120 / htmlImg.width, 60 / htmlImg.height);
const w = Math.round(htmlImg.width * scale);
const h = Math.round(htmlImg.height * scale);
offscreen.width = w;
offscreen.height = h;
ctx.drawImage(htmlImg, 0, 0, w, h);
const imageData = ctx.getImageData(0, 0, w, h);
ctx.drawImage(htmlImg, 0, 0, w, h); // scales the image down while drawing
img.width = w;
img.height = h;
// then read imageData from the scaled offscreen canvas as before
img.pixels = new Uint8Array(w * h).map((_, i) => {
const r = imageData.data[i * 4];
const g = imageData.data[i * 4 + 1];
const b = imageData.data[i * 4 + 2];
const a = imageData.data[i * 4 + 3];
const r = imageData.data[i * 4] || 0;
const g = imageData.data[i * 4 + 1] || 0;
const b = imageData.data[i * 4 + 2] || 0;
const a = imageData.data[i * 4 + 3] || 0;
// transparent = 0, dark pixels = 1, light pixels = 0
return a > 128 && (r * 299 + g * 587 + b * 114) < 128_000 ? 0 : 1;
});
URL.revokeObjectURL(url);
m.redraw();
};
@ -942,7 +916,7 @@ async function clearAll() {
}
// Expose globals referenced by topbar inline handlers
Object.assign(window, { loadBin, exportBin, addSection, clearAll, cycleTheme, changeDisplayLayout });
Object.assign(window, { loadBin, exportBin, addSection, clearAll, cycleTheme });
// window.addEventListener("beforeunload", (e) => {
// if (isDirty) e.preventDefault();

@ -11,7 +11,7 @@
--bg-input: #0a0a0a;
--bg-canvas: #000;
--bg-xhover: #1e1010;
--bg-overlay: rgba(0, 0, 0, .6);
--bg-overlay: rgba(0,0,0,.6);
--bd: #2a2a2a;
--bd-inner: #1e1e1e;
--bd-deep: #1c1c1c;
@ -49,7 +49,7 @@
--bg-input: #fff;
--bg-canvas: #fff;
--bg-xhover: #ffe8e8;
--bg-overlay: rgba(0, 0, 0, .4);
--bg-overlay: rgba(0,0,0,.4);
--bd: #d0d0d0;
--bd-inner: #ddd;
--bd-deep: #e0e0e0;
@ -87,7 +87,7 @@
--bg-input: #fff;
--bg-canvas: #fff;
--bg-xhover: #ffe8e8;
--bg-overlay: rgba(0, 0, 0, .4);
--bg-overlay: rgba(0,0,0,.4);
--bd: #d0d0d0;
--bd-inner: #ddd;
--bd-deep: #e0e0e0;
@ -252,8 +252,7 @@ body {
gap: 5px;
margin-top: 6px
}
#demo-btns>div {
#demo-btns > div {
display: contents;
}
@ -264,20 +263,17 @@ canvas.pixel-editor {
max-width: 100%;
border: 1px solid var(--bd-inner);
}
.anim-editor {
display: flex;
flex-direction: column;
gap: 4px;
}
.anim-frame-tabs {
display: flex;
flex-wrap: wrap;
gap: 3px;
align-items: center;
}
.anim-frame-tab {
display: flex;
align-items: center;
@ -289,12 +285,10 @@ canvas.pixel-editor {
font-size: 11px;
user-select: none;
}
.anim-frame-tab.active {
border-color: var(--accent, #EC0);
color: var(--accent, #EC0);
}
.x-btn-sm {
background: none;
border: none;
@ -305,10 +299,7 @@ canvas.pixel-editor {
line-height: 1;
opacity: 0.6;
}
.x-btn-sm:hover {
opacity: 1;
}
.x-btn-sm:hover { opacity: 1; }
#sec-meta {
background: var(--bg-sunken);
@ -689,25 +680,3 @@ select.meta-val {
.cb.primary:hover {
background: var(--bg-active)
}
#mob-add-section {
display: none;
}
canvas.pixel-editor {
touch-action: none;
/* belt-and-suspenders alongside preventDefault */
-webkit-user-select: none;
user-select: none;
}
.pv-label select {
background: var(--bg-input);
color: var(--ac);
border: 1px solid var(--bd-inner);
border-radius: 3px;
padding: 1px 14px;
font-family: monospace;
font-size: 10px;
letter-spacing: .1em;
}

File diff suppressed because it is too large Load Diff

@ -88,11 +88,6 @@ export class MonoDisplayDriver {
}
}
setSize(w: number, h: number) {
this.opts.displayHeight = h;
this.opts.displayWidth = w;
}
stop(): void { this.renderer?.stop(); }
}

Binary file not shown.
Loading…
Cancel
Save