index_2640.html 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width,initial-scale=1">
  6. <title>PrinterCam</title>
  7. <style>
  8. body {
  9. font-family: Arial,Helvetica,sans-serif;
  10. background: #181818;
  11. color: #EFEFEF;
  12. font-size: 16px
  13. }
  14. h2 {
  15. font-size: 18px
  16. }
  17. section.main {
  18. display: flex
  19. }
  20. #menu,section.main {
  21. flex-direction: column
  22. }
  23. #menu {
  24. display: none;
  25. flex-wrap: nowrap;
  26. min-width: 340px;
  27. background: #363636;
  28. padding: 8px;
  29. border-radius: 4px;
  30. margin-top: -10px;
  31. margin-right: 10px;
  32. }
  33. #content {
  34. display: flex;
  35. flex-wrap: wrap;
  36. align-items: stretch
  37. }
  38. figure {
  39. padding: 0px;
  40. margin: 0;
  41. -webkit-margin-before: 0;
  42. margin-block-start: 0;
  43. -webkit-margin-after: 0;
  44. margin-block-end: 0;
  45. -webkit-margin-start: 0;
  46. margin-inline-start: 0;
  47. -webkit-margin-end: 0;
  48. margin-inline-end: 0
  49. }
  50. figure img {
  51. display: block;
  52. width: 100%;
  53. height: auto;
  54. border-radius: 4px;
  55. margin-top: 8px;
  56. }
  57. @media (min-width: 800px) and (orientation:landscape) {
  58. #content {
  59. display:flex;
  60. flex-wrap: nowrap;
  61. align-items: stretch
  62. }
  63. figure img {
  64. display: block;
  65. max-width: 100%;
  66. max-height: calc(100vh - 40px);
  67. width: auto;
  68. height: auto
  69. }
  70. figure {
  71. padding: 0 0 0 0px;
  72. margin: 0;
  73. -webkit-margin-before: 0;
  74. margin-block-start: 0;
  75. -webkit-margin-after: 0;
  76. margin-block-end: 0;
  77. -webkit-margin-start: 0;
  78. margin-inline-start: 0;
  79. -webkit-margin-end: 0;
  80. margin-inline-end: 0
  81. }
  82. }
  83. section#buttons {
  84. display: flex;
  85. flex-wrap: nowrap;
  86. justify-content: space-between
  87. }
  88. #nav-toggle {
  89. cursor: pointer;
  90. display: block
  91. }
  92. #nav-toggle-cb {
  93. outline: 0;
  94. opacity: 0;
  95. width: 0;
  96. height: 0
  97. }
  98. #nav-toggle-cb:checked+#menu {
  99. display: flex
  100. }
  101. .input-group {
  102. display: flex;
  103. flex-wrap: nowrap;
  104. line-height: 22px;
  105. margin: 5px 0
  106. }
  107. .input-group>label {
  108. display: inline-block;
  109. padding-right: 10px;
  110. min-width: 47%
  111. }
  112. .input-group input,.input-group select {
  113. flex-grow: 1
  114. }
  115. .range-max,.range-min {
  116. display: inline-block;
  117. padding: 0 5px
  118. }
  119. button {
  120. display: block;
  121. margin: 5px;
  122. padding: 0 12px;
  123. border: 0;
  124. line-height: 28px;
  125. cursor: pointer;
  126. color: #fff;
  127. background: #ff3034;
  128. border-radius: 5px;
  129. font-size: 16px;
  130. outline: 0
  131. }
  132. button:hover {
  133. background: #ff494d
  134. }
  135. button:active {
  136. background: #f21c21
  137. }
  138. button.disabled {
  139. cursor: default;
  140. background: #a0a0a0
  141. }
  142. input[type=range] {
  143. -webkit-appearance: none;
  144. width: 100%;
  145. height: 22px;
  146. background: #363636;
  147. cursor: pointer;
  148. margin: 0
  149. }
  150. input[type=range]:focus {
  151. outline: 0
  152. }
  153. input[type=range]::-webkit-slider-runnable-track {
  154. width: 100%;
  155. height: 2px;
  156. cursor: pointer;
  157. background: #EFEFEF;
  158. border-radius: 0;
  159. border: 0 solid #EFEFEF
  160. }
  161. input[type=range]::-webkit-slider-thumb {
  162. border: 1px solid rgba(0,0,30,0);
  163. height: 22px;
  164. width: 22px;
  165. border-radius: 50px;
  166. background: #ff3034;
  167. cursor: pointer;
  168. -webkit-appearance: none;
  169. margin-top: -11.5px
  170. }
  171. input[type=range]:focus::-webkit-slider-runnable-track {
  172. background: #EFEFEF
  173. }
  174. input[type=range]::-moz-range-track {
  175. width: 100%;
  176. height: 2px;
  177. cursor: pointer;
  178. background: #EFEFEF;
  179. border-radius: 0;
  180. border: 0 solid #EFEFEF
  181. }
  182. input[type=range]::-moz-range-thumb {
  183. border: 1px solid rgba(0,0,30,0);
  184. height: 22px;
  185. width: 22px;
  186. border-radius: 50px;
  187. background: #ff3034;
  188. cursor: pointer
  189. }
  190. input[type=range]::-ms-track {
  191. width: 100%;
  192. height: 2px;
  193. cursor: pointer;
  194. background: 0 0;
  195. border-color: transparent;
  196. color: transparent
  197. }
  198. input[type=range]::-ms-fill-lower {
  199. background: #EFEFEF;
  200. border: 0 solid #EFEFEF;
  201. border-radius: 0
  202. }
  203. input[type=range]::-ms-fill-upper {
  204. background: #EFEFEF;
  205. border: 0 solid #EFEFEF;
  206. border-radius: 0
  207. }
  208. input[type=range]::-ms-thumb {
  209. border: 1px solid rgba(0,0,30,0);
  210. height: 22px;
  211. width: 22px;
  212. border-radius: 50px;
  213. background: #ff3034;
  214. cursor: pointer;
  215. height: 2px
  216. }
  217. input[type=range]:focus::-ms-fill-lower {
  218. background: #EFEFEF
  219. }
  220. input[type=range]:focus::-ms-fill-upper {
  221. background: #363636
  222. }
  223. .switch {
  224. display: block;
  225. position: relative;
  226. line-height: 22px;
  227. font-size: 16px;
  228. height: 22px
  229. }
  230. .switch input {
  231. outline: 0;
  232. opacity: 0;
  233. width: 0;
  234. height: 0
  235. }
  236. .slider {
  237. width: 50px;
  238. height: 22px;
  239. border-radius: 22px;
  240. cursor: pointer;
  241. background-color: grey
  242. }
  243. .slider,.slider:before {
  244. display: inline-block;
  245. transition: .4s
  246. }
  247. .slider:before {
  248. position: relative;
  249. content: "";
  250. border-radius: 50%;
  251. height: 16px;
  252. width: 16px;
  253. left: 4px;
  254. top: 3px;
  255. background-color: #fff
  256. }
  257. input:checked+.slider {
  258. background-color: #ff3034
  259. }
  260. input:checked+.slider:before {
  261. -webkit-transform: translateX(26px);
  262. transform: translateX(26px)
  263. }
  264. select {
  265. border: 1px solid #363636;
  266. font-size: 14px;
  267. height: 22px;
  268. outline: 0;
  269. border-radius: 5px
  270. }
  271. .image-container {
  272. position: relative;
  273. min-width: 160px
  274. }
  275. .close {
  276. position: absolute;
  277. right: 5px;
  278. top: 5px;
  279. background: #ff3034;
  280. width: 16px;
  281. height: 16px;
  282. border-radius: 100px;
  283. color: #fff;
  284. text-align: center;
  285. line-height: 18px;
  286. cursor: pointer
  287. }
  288. .hidden {
  289. display: none
  290. }
  291. </style>
  292. </head>
  293. <body>
  294. <section class="main">
  295. <div id="logo">
  296. <label for="nav-toggle-cb" id="nav-toggle">&#9776;&nbsp;&nbsp;Toggle settings</label>
  297. </div>
  298. <div id="content">
  299. <div id="sidebar">
  300. <input type="checkbox" id="nav-toggle-cb" checked="checked">
  301. <nav id="menu">
  302. <div class="input-group" id="framesize-group">
  303. <label for="framesize">Resolution</label>
  304. <select id="framesize" class="default-action">
  305. <option value="10">UXGA(1600x1200)</option>
  306. <option value="9">SXGA(1280x1024)</option>
  307. <option value="8">XGA(1024x768)</option>
  308. <option value="7">SVGA(800x600)</option>
  309. <option value="6">VGA(640x480)</option>
  310. <option value="5" selected="selected">CIF(400x296)</option>
  311. <option value="4">QVGA(320x240)</option>
  312. <option value="3">HQVGA(240x176)</option>
  313. <option value="0">QQVGA(160x120)</option>
  314. </select>
  315. </div>
  316. <div class="input-group" id="quality-group">
  317. <label for="quality">Quality</label>
  318. <div class="range-min">10</div>
  319. <input type="range" id="quality" min="10" max="63" value="10" class="default-action">
  320. <div class="range-max">63</div>
  321. </div>
  322. <div class="input-group" id="brightness-group">
  323. <label for="brightness">Brightness</label>
  324. <div class="range-min">-2</div>
  325. <input type="range" id="brightness" min="-2" max="2" value="0" class="default-action">
  326. <div class="range-max">2</div>
  327. </div>
  328. <div class="input-group" id="contrast-group">
  329. <label for="contrast">Contrast</label>
  330. <div class="range-min">-2</div>
  331. <input type="range" id="contrast" min="-2" max="2" value="0" class="default-action">
  332. <div class="range-max">2</div>
  333. </div>
  334. <div class="input-group" id="saturation-group">
  335. <label for="saturation">Saturation</label>
  336. <div class="range-min">-2</div>
  337. <input type="range" id="saturation" min="-2" max="2" value="0" class="default-action">
  338. <div class="range-max">2</div>
  339. </div>
  340. <div class="input-group" id="hmirror-group">
  341. <label for="hmirror">H-Mirror</label>
  342. <div class="switch">
  343. <input id="hmirror" type="checkbox" class="default-action" checked="checked">
  344. <label class="slider" for="hmirror"></label>
  345. </div>
  346. </div>
  347. <div class="input-group" id="vflip-group">
  348. <label for="vflip">V-Flip</label>
  349. <div class="switch">
  350. <input id="vflip" type="checkbox" class="default-action" checked="checked">
  351. <label class="slider" for="vflip"></label>
  352. </div>
  353. </div>
  354. <div class="input-group" id="flashlight-group">
  355. <label for="flashlight">Flashlight</label>
  356. <div class="switch">
  357. <input id="flashlight" type="checkbox" class="default-action">
  358. <label class="slider" for="flashlight"></label>
  359. </div>
  360. </div>
  361. <section id="buttons">
  362. <button id="toggle-stream">Start Stream</button>
  363. </section>
  364. </nav>
  365. </div>
  366. <figure>
  367. <div id="stream-container" class="image-container hidden">
  368. <div class="close" id="close-stream">×</div>
  369. <img id="stream" src="">
  370. </div>
  371. </figure>
  372. </div>
  373. </section>
  374. <script>
  375. document.addEventListener('DOMContentLoaded', function (event) {
  376. var baseHost = document.location.origin
  377. var streamUrl = baseHost + ':81'
  378. const hide = el => {
  379. el.classList.add('hidden')
  380. }
  381. const show = el => {
  382. el.classList.remove('hidden')
  383. }
  384. const disable = el => {
  385. el.classList.add('disabled')
  386. el.disabled = true
  387. }
  388. const enable = el => {
  389. el.classList.remove('disabled')
  390. el.disabled = false
  391. }
  392. const updateValue = (el, value, updateRemote) => {
  393. updateRemote = updateRemote == null ? true : updateRemote
  394. let initialValue
  395. if (el.type === 'checkbox') {
  396. initialValue = el.checked
  397. value = !!value
  398. el.checked = value
  399. } else {
  400. initialValue = el.value
  401. el.value = value
  402. }
  403. if (updateRemote && initialValue !== value) {
  404. updateConfig(el);
  405. }
  406. }
  407. function updateConfig (el) {
  408. let value
  409. switch (el.type) {
  410. case 'checkbox':
  411. value = el.checked ? 1 : 0
  412. break
  413. case 'range':
  414. case 'select-one':
  415. value = el.value
  416. break
  417. case 'button':
  418. case 'submit':
  419. value = '1'
  420. break
  421. default:
  422. return
  423. }
  424. const query = `${baseHost}/control?var=${el.id}&val=${value}`
  425. fetch(query)
  426. .then(response => {
  427. console.log(`request to ${query} finished, status: ${response.status}`)
  428. })
  429. }
  430. document
  431. .querySelectorAll('.close')
  432. .forEach(el => {
  433. el.onclick = () => {
  434. hide(el.parentNode)
  435. }
  436. })
  437. // read initial values
  438. fetch(`${baseHost}/status`)
  439. .then(function (response) {
  440. return response.json()
  441. })
  442. .then(function (state) {
  443. document
  444. .querySelectorAll('.default-action')
  445. .forEach(el => {
  446. updateValue(el, state[el.id], false)
  447. })
  448. })
  449. const view = document.getElementById('stream')
  450. const viewContainer = document.getElementById('stream-container')
  451. const streamButton = document.getElementById('toggle-stream')
  452. const closeButton = document.getElementById('close-stream')
  453. const stopStream = () => {
  454. window.stop();
  455. streamButton.innerHTML = 'Start Stream'
  456. }
  457. const startStream = () => {
  458. view.src = `${streamUrl}/stream`
  459. show(viewContainer)
  460. streamButton.innerHTML = 'Stop Stream'
  461. }
  462. closeButton.onclick = () => {
  463. stopStream()
  464. hide(viewContainer)
  465. }
  466. streamButton.onclick = () => {
  467. const streamEnabled = streamButton.innerHTML === 'Stop Stream'
  468. if (streamEnabled) {
  469. stopStream()
  470. } else {
  471. startStream()
  472. }
  473. }
  474. // Attach default on change action
  475. document
  476. .querySelectorAll('.default-action')
  477. .forEach(el => {
  478. el.onchange = () => updateConfig(el)
  479. })
  480. // Detection and framesize
  481. const framesize = document.getElementById('framesize')
  482. framesize.onchange = () => {
  483. updateConfig(framesize)
  484. }
  485. const flashlight = document.getElementById('flashlight')
  486. flashlight.onchange = () => {
  487. updateConfig(flashlight)
  488. }
  489. })
  490. </script>
  491. </body>
  492. </html>