├── README.md ├── css ├── prism.css └── style.css ├── images └── docs │ ├── drawing.gif │ ├── input_grid_size.gif │ └── queryparams.gif ├── index.html ├── js ├── code.js └── prism.js └── serveit.py /README.md: -------------------------------------------------------------------------------- 1 | # ESPHome-display-generator 2 | 3 | **WIP** 4 | 5 | Born out of the desire to not have to code up display drawings but draw them. 6 | 7 | Currently it's hosted on GitHub Pages: https://stephengolub.github.io/ESPHome-display-generator 8 | 9 | ## Usage 10 | 11 | You can choose the dimensions and the grid will auto-resize. It'll attempt to clone existing marks into the new grid (if the size is adequate). 12 | 13 | ![Grid Size Changing via Inputs](images/docs/input_grid_size.gif) 14 | 15 | Click a cell to highlight it. Or click and drag to color multiple cells. Once your picture is set, you can click the generate button at the top to output the code for the ESPHome display component to draw. 16 | 17 | ![Drawing Demo](images/docs/drawing.gif) 18 | 19 | ### Query Parameters 20 | 21 | * **w**: will preset the width 22 | * **h**: will preset the height 23 | 24 | ![Query Param Entry Demonstration](images/docs/queryparams.gif) 25 | 26 | ### Tools 27 | 28 | There are only four tools implemented: 29 | * Pixel Toggle (pencil) 30 | * Line drawing 31 | * Rectangle 32 | * Circles 33 | 34 | 35 | 36 | ### Known Bugs 37 | 38 | * [Drawing circles is unstable](https://github.com/stephengolub/ESPHome-display-generator/issues/3) 39 | * [Near vertical line is not contiguous](https://github.com/stephengolub/ESPHome-display-generator/issues/4) 40 | 41 | ## TODO 42 | 43 | * Drawing tools: 44 | * Allow "filled" rectangles and circles 45 | * Colors 46 | -------------------------------------------------------------------------------- /css/prism.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.23.0 2 | https://prismjs.com/download.html#themes=prism-dark&languages=clike+c+cpp */ 3 | /** 4 | * prism.js Dark theme for JavaScript, CSS and HTML 5 | * Based on the slides of the talk “/Reg(exp){2}lained/” 6 | * @author Lea Verou 7 | */ 8 | 9 | code[class*="language-"], 10 | pre[class*="language-"] { 11 | color: white; 12 | background: none; 13 | text-shadow: 0 -.1em .2em black; 14 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 15 | font-size: 1em; 16 | text-align: left; 17 | white-space: pre; 18 | word-spacing: normal; 19 | word-break: normal; 20 | word-wrap: normal; 21 | line-height: 1.5; 22 | 23 | -moz-tab-size: 4; 24 | -o-tab-size: 4; 25 | tab-size: 4; 26 | 27 | -webkit-hyphens: none; 28 | -moz-hyphens: none; 29 | -ms-hyphens: none; 30 | hyphens: none; 31 | } 32 | 33 | @media print { 34 | code[class*="language-"], 35 | pre[class*="language-"] { 36 | text-shadow: none; 37 | } 38 | } 39 | 40 | pre[class*="language-"], 41 | :not(pre) > code[class*="language-"] { 42 | background: hsl(30, 20%, 25%); 43 | } 44 | 45 | /* Code blocks */ 46 | pre[class*="language-"] { 47 | padding: 1em; 48 | margin: .5em 0; 49 | overflow: auto; 50 | border: .3em solid hsl(30, 20%, 40%); 51 | border-radius: .5em; 52 | box-shadow: 1px 1px .5em black inset; 53 | } 54 | 55 | /* Inline code */ 56 | :not(pre) > code[class*="language-"] { 57 | padding: .15em .2em .05em; 58 | border-radius: .3em; 59 | border: .13em solid hsl(30, 20%, 40%); 60 | box-shadow: 1px 1px .3em -.1em black inset; 61 | white-space: normal; 62 | } 63 | 64 | .token.comment, 65 | .token.prolog, 66 | .token.doctype, 67 | .token.cdata { 68 | color: hsl(30, 20%, 50%); 69 | } 70 | 71 | .token.punctuation { 72 | opacity: .7; 73 | } 74 | 75 | .token.namespace { 76 | opacity: .7; 77 | } 78 | 79 | .token.property, 80 | .token.tag, 81 | .token.boolean, 82 | .token.number, 83 | .token.constant, 84 | .token.symbol { 85 | color: hsl(350, 40%, 70%); 86 | } 87 | 88 | .token.selector, 89 | .token.attr-name, 90 | .token.string, 91 | .token.char, 92 | .token.builtin, 93 | .token.inserted { 94 | color: hsl(75, 70%, 60%); 95 | } 96 | 97 | .token.operator, 98 | .token.entity, 99 | .token.url, 100 | .language-css .token.string, 101 | .style .token.string, 102 | .token.variable { 103 | color: hsl(40, 90%, 60%); 104 | } 105 | 106 | .token.atrule, 107 | .token.attr-value, 108 | .token.keyword { 109 | color: hsl(350, 40%, 70%); 110 | } 111 | 112 | .token.regex, 113 | .token.important { 114 | color: #e90; 115 | } 116 | 117 | .token.important, 118 | .token.bold { 119 | font-weight: bold; 120 | } 121 | .token.italic { 122 | font-style: italic; 123 | } 124 | 125 | .token.entity { 126 | cursor: help; 127 | } 128 | 129 | .token.deleted { 130 | color: red; 131 | } 132 | 133 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | --darkest-color: #000000; 3 | --lightest-color: #FFFFFF; 4 | 5 | background-color: var(--lightest-color); 6 | } 7 | 8 | body.dark-theme { 9 | --lightest-color: #000000; 10 | --darkest-color: #FFFFFF; 11 | } 12 | 13 | #gridArea { 14 | white-space: nowrap; 15 | -webkit-touch-callout: none; /* iOS Safari */ 16 | -webkit-user-select: none; /* Safari */ 17 | -khtml-user-select: none; /* Konqueror HTML */ 18 | -moz-user-select: none; /* Old versions of Firefox */ 19 | -ms-user-select: none; /* Internet Explorer/Edge */ 20 | user-select: none; /* Non-prefixed version, currently 21 | supported by Chrome, Edge, Opera and Firefox */ 22 | } 23 | 24 | #gridArea > .cell { 25 | display: inline-block; 26 | width: 10px; 27 | height: 10px; 28 | border: 1px solid var(--darkest-color); 29 | cursor: pointer; 30 | } 31 | 32 | #gridArea > .cell.on { 33 | background-color: var(--darkest-color); 34 | } 35 | 36 | #gridArea > .cell.off { 37 | background-color: var(--lightest-color); 38 | } 39 | 40 | .output-container { 41 | position: relative; 42 | } 43 | 44 | #copy_output { 45 | position: absolute; 46 | top: 8px; 47 | right: 2px; 48 | background: none; 49 | border: none; 50 | color: var(--lightest-color); 51 | font-size: 1.25em; 52 | } 53 | 54 | .hidden { 55 | display: none; 56 | } 57 | 58 | .mdi span { 59 | display: none; 60 | } 61 | 62 | .tool { 63 | background-color: var(--lightest-color); 64 | border: 1px solid var(--darkest-color); 65 | color: var(--darkest-color); 66 | border-radius: 5px; 67 | padding: 5px 8px; 68 | } 69 | 70 | .tool.active { 71 | background-color: var(--darkest-color); 72 | border: 1px solid var(--lightest-color); 73 | color: var(--lightest-color); 74 | } 75 | 76 | .number { 77 | width: 50px; 78 | text-align: center; 79 | margin: 2px; 80 | } 81 | -------------------------------------------------------------------------------- /images/docs/drawing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephengolub/ESPHome-display-generator/2bcf0a001556246f94a348047bf5f8bd17e5ad87/images/docs/drawing.gif -------------------------------------------------------------------------------- /images/docs/input_grid_size.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephengolub/ESPHome-display-generator/2bcf0a001556246f94a348047bf5f8bd17e5ad87/images/docs/input_grid_size.gif -------------------------------------------------------------------------------- /images/docs/queryparams.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephengolub/ESPHome-display-generator/2bcf0a001556246f94a348047bf5f8bd17e5ad87/images/docs/queryparams.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ESPHome Screen Drawing 9 | 10 | 11 |
12 | x 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /js/code.js: -------------------------------------------------------------------------------- 1 | const minHeight = 8; 2 | const minWidth = 8; 3 | let maxHeight = 128; 4 | let maxWidth = 128; 5 | 6 | const STATE_ON = "on"; 7 | const STATE_OFF = "off"; 8 | 9 | const TOOL_PENCIL = "pencil"; 10 | const TOOL_LINE = "line"; 11 | const TOOL_ELLIPSE = "ellipse"; 12 | const TOOL_RECTANGLE = "rectangle"; 13 | 14 | const outputs = {}; 15 | 16 | outputs[TOOL_PENCIL] = []; 17 | outputs[TOOL_LINE] = []; 18 | outputs[TOOL_ELLIPSE] = []; 19 | outputs[TOOL_RECTANGLE] = []; 20 | 21 | let grid = []; 22 | 23 | const copyRow = (destRow, srcRow) => { 24 | for (let i=0; i < destRow.length && i < srcRow.length; i++) { 25 | destRow[i] = srcRow[i]; 26 | } 27 | return destRow; 28 | }; 29 | 30 | let mouseDown = false; 31 | let destination = undefined; 32 | let currentTool = TOOL_PENCIL; 33 | let point1 = undefined; 34 | let point2 = undefined; 35 | let drawPoints = []; 36 | 37 | const resetToolPoints = () => { 38 | point1 = undefined; 39 | point2 = undefined; 40 | drawPoints = []; 41 | }; 42 | 43 | const chooseTool = (toolName) => { 44 | if ([TOOL_PENCIL, TOOL_LINE, TOOL_ELLIPSE, TOOL_RECTANGLE].indexOf(toolName) >= 0) { 45 | document.getElementById(`tool-${currentTool}`).classList.remove("active"); 46 | document.getElementById(`tool-${toolName}`).classList.add("active"); 47 | currentTool = toolName; 48 | resetToolPoints(); 49 | } 50 | }; 51 | 52 | const targetIsCell = (e) => e.target.classList.contains("cell"); 53 | const onGridClick = (e) => { 54 | if (targetIsCell(e)) { 55 | const cell = e.target; 56 | if (currentTool == TOOL_PENCIL) { 57 | cell.classList.toggle(STATE_ON); 58 | cell.classList.toggle(STATE_OFF); 59 | } 60 | } 61 | }; 62 | 63 | const setInfoBox = (text) => { 64 | document.getElementById("info").innerText = text; 65 | }; 66 | 67 | const validPoint = (coords) => { 68 | return (coords[0] >= 0 && coords[0] <= maxWidth) && (coords[1] >= 0 && coords[1] <= maxHeight); 69 | } 70 | 71 | const getDistanceBetweenPoints = (pointA, pointB) => { 72 | return Math.sqrt(((pointA[0] - pointB[0])^2) * (pointA[1] - pointB[1])^2) 73 | }; 74 | 75 | const lineBetweenPoints = (pointA, pointB) => { 76 | x1 = pointA[0]; 77 | y1 = pointA[1]; 78 | x2 = pointB[0]; 79 | y2 = pointB[1]; 80 | 81 | points = []; 82 | if (x2 == x1) { // Vertical Line 83 | let yMin = y1; 84 | let yMax = y2; 85 | if (y1 > y2) { 86 | yMin = y2; 87 | yMax = y1; 88 | } 89 | for (let y = yMin; y <= yMax && validPoint([x1, y]); y++) { 90 | points.push([x1, y]); 91 | } 92 | } else if (y2 == y1) { // Horizontal Line 93 | let xMin = x1; 94 | let xMax = x2; 95 | if (x1 > x2) { 96 | xMin = x2; 97 | xMax = x1; 98 | } 99 | for (let x = xMin; x <= xMax && validPoint([y1, x]); x++) { 100 | points.push([x, y1]); 101 | } 102 | } else { 103 | let m = (y2 - y1) / (x2 - x1); 104 | let xMin = x1; 105 | let xMax = x2; 106 | let b = y1; 107 | let y = y2; 108 | if (x1 > x2) { 109 | xMax = x1; 110 | xMin = x2; 111 | y = y1; 112 | b = y2; 113 | } 114 | for (let x = xMin; x <= xMax && validPoint([x, y]); x++) { 115 | y = m*(x - xMin) + b; 116 | points.push([Math.abs(Math.round(x)), Math.abs(Math.round(y))]); 117 | } 118 | } 119 | return points; 120 | }; 121 | 122 | const rectAroundPoints = (pointA, pointB) => { 123 | x1 = pointA[0]; 124 | y1 = pointA[1]; 125 | x2 = pointB[0]; 126 | y2 = pointB[1]; 127 | 128 | points = []; 129 | if (x2 == x1 || y2 == y1) { // Vertical or Horizontal Line 130 | return lineBetweenPoints(pointA, pointB); 131 | } else { 132 | let xMin = x1; 133 | let xMax = x2; 134 | let yMin = y1; 135 | let yMax = y2; 136 | if (x1 > x2) { 137 | xMax = x1; 138 | xMin = x2; 139 | } 140 | if (y1 > y2) { 141 | yMax = y1; 142 | yMin = y2; 143 | } 144 | for (x = xMin; x <= xMax; x++) { 145 | if (x == xMin || x == xMax) { 146 | for (y = yMin; y <= yMax; y++) { 147 | points.push([x,y]); 148 | } 149 | } 150 | points.push([x,yMin]); 151 | points.push([x,yMax]); 152 | } 153 | } 154 | return points; 155 | } 156 | 157 | const RAD = Math.PI / 180; 158 | 159 | const circleOfPoints = (centerPoint, radius) => { 160 | cX = centerPoint[0]; 161 | cY = centerPoint[1]; 162 | points = []; 163 | for (let deg=0; deg <= 360; deg++) { 164 | let x = radius * Math.sin(deg * RAD); 165 | let y = radius * Math.cos(deg * RAD); 166 | points.push([Math.round(x) + cX, Math.round(y) + cY]); 167 | } 168 | pSet = new Set(points.map(x => x.toString())); 169 | return [...pSet.values()].map(x => x.split(",").map(p => +p)).filter(validPoint); 170 | }; 171 | 172 | const onGridMouseDown = (e) => { 173 | if (targetIsCell(e)) { 174 | const cell = e.target; 175 | destination = cell.classList.contains(STATE_OFF); 176 | mouseDown = true; 177 | switch (currentTool) { 178 | case TOOL_PENCIL: 179 | setCellValue(cell, destination); 180 | break; 181 | case TOOL_LINE: 182 | point1 = coordsFromCell(cell); 183 | break; 184 | case TOOL_ELLIPSE: 185 | point1 = coordsFromCell(cell); 186 | break; 187 | case TOOL_RECTANGLE: 188 | point1 = coordsFromCell(cell); 189 | break; 190 | } 191 | } 192 | }; 193 | 194 | const onGridMouseUp = (e) => { 195 | const cell = e.target; 196 | switch (currentTool) { 197 | case TOOL_PENCIL: 198 | outputs[TOOL_PENCIL].push(coordsFromCell(cell)) 199 | break; 200 | case TOOL_LINE: 201 | outputs[TOOL_LINE].push([point1, point2]) 202 | break; 203 | case TOOL_ELLIPSE: 204 | outputs[TOOL_ELLIPSE].push([point1, point2]) 205 | break; 206 | case TOOL_RECTANGLE: 207 | outputs[TOOL_RECTANGLE].push([point1, point2]) 208 | break; 209 | } 210 | mouseDown = false; 211 | destination = undefined; 212 | resetToolPoints(); 213 | }; 214 | 215 | const coordsFromCell = cell => { 216 | return cell.id.split("_").map(x => parseInt(x, 10)); 217 | }; 218 | 219 | const setCellValueByCoords = (coords, state, cell) => { 220 | if (!validPoint(coords)) { return; } 221 | const x = coords[0]; 222 | const y = coords[1]; 223 | grid[y][x] = state; 224 | if (cell == undefined) { 225 | cell = document.getElementById(`${x}_${y}`); 226 | } 227 | if (!cell) { return; } 228 | if (state) { 229 | cell.classList.add(STATE_ON) 230 | cell.classList.remove(STATE_OFF) 231 | } else { 232 | cell.classList.add(STATE_OFF) 233 | cell.classList.remove(STATE_ON) 234 | } 235 | }; 236 | 237 | const setCellValue = (cell, state) => { 238 | setCellValueByCoords(coordsFromCell(cell), state, cell); 239 | }; 240 | 241 | const setBulkCellValueByCoords = (points, state) => { 242 | points.map(c => setCellValueByCoords(c, state)); 243 | }; 244 | 245 | const getCellValue = (coords) => { 246 | return grid[coords[1]][coords[0]]; 247 | }; 248 | 249 | const onGridMouseOver = (e) => { 250 | if (mouseDown && targetIsCell(e)) { 251 | const cell = e.target; 252 | switch (currentTool) { 253 | case TOOL_PENCIL: 254 | setCellValue(cell, destination); 255 | break; 256 | case TOOL_LINE: 257 | point2 = coordsFromCell(cell); 258 | setBulkCellValueByCoords(drawPoints, false); 259 | drawPoints = lineBetweenPoints(point1, point2).filter(c => !getCellValue(c)); 260 | for (let i in points) { 261 | setCellValueByCoords(points[i], true); 262 | } 263 | break; 264 | case TOOL_ELLIPSE: 265 | point2 = coordsFromCell(cell); 266 | setBulkCellValueByCoords(drawPoints, false); 267 | drawPoints = circleOfPoints(point1, getDistanceBetweenPoints(point1, point2)).filter(c => !getCellValue(c)); 268 | for (let i in points) { 269 | setCellValueByCoords(points[i], true); 270 | } 271 | break; 272 | case TOOL_RECTANGLE: 273 | point2 = coordsFromCell(cell); 274 | setBulkCellValueByCoords(drawPoints, false); 275 | drawPoints = rectAroundPoints(point1, point2).filter(c => !getCellValue(c)); 276 | for (let i in points) { 277 | setCellValueByCoords(points[i], true); 278 | } 279 | break; 280 | } 281 | } 282 | } 283 | 284 | const getCell = (state, x, y) => { 285 | const cell = document.createElement('div'); 286 | cell.id = `${x}_${y}`; 287 | cell.classList.add("cell"); 288 | cell.classList.add(state ? STATE_ON : STATE_OFF); 289 | return cell; 290 | }; 291 | 292 | const drawGrid = () => { 293 | const gridArea = document.getElementById("gridArea"); 294 | gridArea.innerHTML = ''; 295 | for (let y in grid) { 296 | for (let x in grid[y]) { 297 | gridArea.appendChild(getCell(grid[y][x], x, y)); 298 | } 299 | gridArea.innerHTML += '
'; 300 | } 301 | }; 302 | 303 | const setGrid = (w, h) => { 304 | // Creating new empty grid 305 | let newGrid = []; 306 | maxHeight = h; 307 | maxWidth = w; 308 | for (let y=0; y grid.length) { 316 | for (let i in grid) { 317 | copyRow(newGrid[i], grid[i]); 318 | } 319 | } 320 | grid = newGrid; 321 | drawGrid(); 322 | }; 323 | 324 | const getFieldValue = (id) => document.getElementById(id).value; 325 | 326 | const onFieldChange = () => { 327 | setGrid(+getFieldValue("width"), +getFieldValue("height")); 328 | }; 329 | 330 | const onReady = () => { 331 | const params = new URLSearchParams(window.location.search); 332 | 333 | const w = params.get("w"); 334 | const h = params.get("h"); 335 | if (w != undefined) { 336 | document.getElementById("width").value = +w; 337 | } 338 | if (h != undefined) { 339 | document.getElementById("height").value = +h; 340 | } 341 | onFieldChange(); 342 | }; 343 | 344 | if ( 345 | document.readyState === "complete" || 346 | (document.readyState !== "loading" && !document.documentElement.doScroll) 347 | ) { 348 | onReady(); 349 | } else { 350 | document.addEventListener("DOMContentLoaded", onReady); 351 | } 352 | 353 | // We're calling COLORs explicitly so that it is obvious what we are looking for. 354 | const generate = () => { 355 | let lines = ["// Lines"]; 356 | 357 | for (let i in outputs[TOOL_LINE]) { 358 | points = outputs[TOOL_LINE][i]; 359 | let x1 = points[0][0]; 360 | let y1 = points[0][1]; 361 | let x2 = points[1][0]; 362 | let y2 = points[1][1]; 363 | 364 | lines.push(`it.line(${x1},${y1},${x2},${y2},COLOR_ON);`); 365 | } 366 | 367 | lines.push("// Rectangles"); 368 | for (let i in outputs[TOOL_RECTANGLE]) { 369 | points = outputs[TOOL_RECTANGLE][i]; 370 | let x1 = points[0][0]; 371 | let y1 = points[0][1]; 372 | let x2 = points[1][0]; 373 | let y2 = points[1][1]; 374 | 375 | let w = Math.abs(x2-x1); 376 | let h = Math.abs(y2-y1); 377 | 378 | if (x1 < x2 && y1 < y2) { // Is Upper Left 379 | lines.push(`it.rectangle(${x1},${y1},${w},${h},COLOR_ON);`); 380 | } else if (x2 < x1 && y2 < y1) { // Is Lower Right 381 | lines.push(`it.rectangle(${x2},${y2},${w},${h},COLOR_ON);`); 382 | } else if (x1 < x2 && y2 < y1) { // Is Lower Left 383 | lines.push(`it.rectangle(${x1},${y2},${w},${h},COLOR_ON);`); 384 | } else { // Is Upper Right 385 | lines.push(`it.rectangle(${x2},${y1},${w},${h},COLOR_ON);`); 386 | } 387 | } 388 | 389 | lines.push("// Circles"); 390 | for (let i in outputs[TOOL_ELLIPSE]) { 391 | points = outputs[TOOL_ELLIPSE][i] 392 | let x1 = points[0][0]; 393 | let y1 = points[0][1]; 394 | let r = getDistanceBetweenPoints(points[0], points[1]); 395 | lines.push(`it.circle(${x1},${y1},${r});`); 396 | } 397 | 398 | lines.push("// Clean up points"); 399 | for (let i in outputs[TOOL_PENCIL]) { 400 | const coords = outputs[TOOL_PENCIL][i]; 401 | const x = coords[0]; 402 | const y = coords[1]; 403 | const state = grid[y][x] ? 'ON' : 'OFF'; 404 | lines.push(`it.draw_pixel_at(${x},${y},COLOR_${state});`) 405 | } 406 | 407 | document.getElementById("output").innerText = lines.join('\n'); 408 | document.querySelector(".output-container").classList.remove("hidden"); 409 | }; 410 | 411 | 412 | function copyToClipboard(el) { 413 | var el = document.getElementById("output"), 414 | oldContentEditable = el.contentEditable, 415 | oldReadOnly = el.readOnly, 416 | range = document.createRange(); 417 | 418 | el.contentEditable = true; 419 | el.readOnly = false; 420 | range.selectNodeContents(el); 421 | 422 | var s = window.getSelection(); 423 | s.removeAllRanges(); 424 | s.addRange(range); 425 | 426 | el.setSelectionRange(0, 999999); // A big number, to cover anything that could be inside the element. 427 | 428 | el.contentEditable = oldContentEditable; 429 | el.readOnly = oldReadOnly; 430 | 431 | document.execCommand('copy'); 432 | } 433 | -------------------------------------------------------------------------------- /js/prism.js: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.23.0 2 | https://prismjs.com/download.html#themes=prism-dark&languages=clike+c+cpp */ 3 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\blang(?:uage)?-([\w-]+)\b/i,n=0,e={},M={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof W?new W(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=l.reach);y+=m.value.length,m=m.next){var b=m.value;if(t.length>n.length)return;if(!(b instanceof W)){var k,x=1;if(h){if(!(k=z(v,y,n,f)))break;var w=k.index,A=k.index+k[0].length,P=y;for(P+=m.value.length;P<=w;)m=m.next,P+=m.value.length;if(P-=m.value.length,y=P,m.value instanceof W)continue;for(var E=m;E!==t.tail&&(Pl.reach&&(l.reach=N);var j=m.prev;O&&(j=I(t,j,O),y+=O.length),q(t,j,x);var C=new W(o,g?M.tokenize(S,g):S,d,S);if(m=I(t,j,C),L&&I(t,m,L),1l.reach&&(l.reach=_.reach)}}}}}}(e,a,n,a.head,0),function(e){var n=[],t=e.head.next;for(;t!==e.tail;)n.push(t.value),t=t.next;return n}(a)},hooks:{all:{},add:function(e,n){var t=M.hooks.all;t[e]=t[e]||[],t[e].push(n)},run:function(e,n){var t=M.hooks.all[e];if(t&&t.length)for(var r,a=0;r=t[a++];)r(n)}},Token:W};function W(e,n,t,r){this.type=e,this.content=n,this.alias=t,this.length=0|(r||"").length}function z(e,n,t,r){e.lastIndex=n;var a=e.exec(t);if(a&&r&&a[1]){var i=a[1].length;a.index+=i,a[0]=a[0].slice(i)}return a}function i(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function I(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function q(e,n,t){for(var r=n.next,a=0;a"+a.content+""},!u.document)return u.addEventListener&&(M.disableWorkerMessageHandler||u.addEventListener("message",function(e){var n=JSON.parse(e.data),t=n.language,r=n.code,a=n.immediateClose;u.postMessage(M.highlight(r,M.languages[t],t)),a&&u.close()},!1)),M;var t=M.util.currentScript();function r(){M.manual||M.highlightAll()}if(t&&(M.filename=t.src,t.hasAttribute("data-manual")&&(M.manual=!0)),!M.manual){var a=document.readyState;"loading"===a||"interactive"===a&&t&&t.defer?document.addEventListener("DOMContentLoaded",r):window.requestAnimationFrame?window.requestAnimationFrame(r):window.setTimeout(r,16)}return M}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); 4 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}; 5 | Prism.languages.c=Prism.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:__attribute__|_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/,function:/[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),Prism.languages.insertBefore("c","string",{macro:{pattern:/(^\s*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},Prism.languages.c.string],comment:Prism.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:Prism.languages.c}}},constant:/\b(?:__FILE__|__LINE__|__DATE__|__TIME__|__TIMESTAMP__|__func__|EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|stdin|stdout|stderr)\b/}),delete Prism.languages.c.boolean; 6 | !function(e){var t=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char8_t|char16_t|char32_t|class|compl|concept|const|consteval|constexpr|constinit|const_cast|continue|co_await|co_return|co_yield|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,n="\\b(?!)\\w+(?:\\s*\\.\\s*\\w+)*\\b".replace(//g,function(){return t.source});e.languages.cpp=e.languages.extend("c",{"class-name":[{pattern:RegExp("(\\b(?:class|concept|enum|struct|typename)\\s+)(?!)\\w+".replace(//g,function(){return t.source})),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:t,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:true|false)\b/}),e.languages.insertBefore("cpp","string",{module:{pattern:RegExp('(\\b(?:module|import)\\s+)(?:"(?:\\\\(?:\r\n|[^])|[^"\\\\\r\n])*"|<[^<>\r\n]*>|'+"(?:\\s*:\\s*)?|:\\s*".replace(//g,function(){return n})+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),e.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b[a-z_]\w*\s*<(?:[^<>]|<(?:[^<>])*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:e.languages.cpp}}}}),e.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),e.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:e.languages.extend("cpp",{})}}),e.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},e.languages.cpp["base-clause"])}(Prism); 7 | -------------------------------------------------------------------------------- /serveit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import http.server 3 | 4 | PORT = 8001 5 | 6 | class NoCacheHTTPRequestHandler( 7 | http.server.SimpleHTTPRequestHandler 8 | ): 9 | def send_response_only(self, code, message=None): 10 | super().send_response_only(code, message) 11 | self.send_header('Cache-Control', 'no-store, must-revalidate') 12 | self.send_header('Expires', '0') 13 | 14 | if __name__ == '__main__': 15 | http.server.test( 16 | HandlerClass=NoCacheHTTPRequestHandler, 17 | port=PORT 18 | ) 19 | --------------------------------------------------------------------------------