diff --git a/eslint.config.mjs b/eslint.config.mjs index a81481f..6851bc9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -55,6 +55,7 @@ export default tsEslint.config( noSortAlphabetically: true } ], + '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-empty-object-type': 'off', '@typescript-eslint/no-unsafe-declaration-merging': 'warn' } diff --git a/package.json b/package.json index 8f18ff0..bdeda0c 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "PaperBookReader", + "name": "@open-source-bazaar/paper-book-reader", "version": "0.4.0", "description": "App Project scaffold of WebCell v3", "keywords": [ @@ -23,6 +23,7 @@ "dom-renderer": "^2.3.0", "iterable-observer": "^1.1.0", "mobx": "^6.13.3", + "tesseract.js": "^5.1.1", "web-cell": "^3.0.0", "web-utility": "^4.4.0" }, @@ -49,6 +50,7 @@ "postcss": "^8.4.47", "prettier": "^3.3.3", "prettier-plugin-css-order": "^2.1.2", + "process": "^0.11.10", "typescript": "~5.6.2", "typescript-eslint": "^8.7.0", "workbox-cli": "^7.1.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a96b013..6d1b46d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: mobx: specifier: ^6.13.3 version: 6.13.3 + tesseract.js: + specifier: ^5.1.1 + version: 5.1.1 web-cell: specifier: ^3.0.0 version: 3.0.0(typescript@5.6.2) @@ -102,6 +105,9 @@ importers: prettier-plugin-css-order: specifier: ^2.1.2 version: 2.1.2(postcss@8.4.47)(prettier@3.3.3) + process: + specifier: ^0.11.10 + version: 0.11.10 typescript: specifier: ~5.6.2 version: 5.6.2 @@ -1539,6 +1545,9 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + bmp-js@0.1.0: + resolution: {integrity: sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -2281,6 +2290,9 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + idb-keyval@6.2.1: + resolution: {integrity: sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==} + idb@7.1.1: resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} @@ -2375,6 +2387,9 @@ packages: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} engines: {node: '>= 0.4'} + is-electron@2.2.2: + resolution: {integrity: sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2495,6 +2510,9 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -2851,6 +2869,15 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-gyp-build-optional-packages@5.1.1: resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==} hasBin: true @@ -2926,6 +2953,10 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} + opencollective-postinstall@2.0.3: + resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==} + hasBin: true + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -3083,6 +3114,10 @@ packages: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} engines: {node: '>=6'} + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -3447,6 +3482,12 @@ packages: engines: {node: '>=10'} hasBin: true + tesseract.js-core@5.1.1: + resolution: {integrity: sha512-KX3bYSU5iGcO1XJa+QGPbi+Zjo2qq6eBhNjSGR5E5q0JtzkoipJKOUQD7ph8kFyteCEfEQ0maWLu8MCXtvX5uQ==} + + tesseract.js@5.1.1: + resolution: {integrity: sha512-lzVl/Ar3P3zhpUT31NjqeCo1f+D5+YfpZ5J62eo2S14QNVOmHBTtbchHm/YAbOOOzCegFnKf4B3Qih9LuldcYQ==} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -3472,6 +3513,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} @@ -3616,6 +3660,9 @@ packages: validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + wasm-feature-detect@1.8.0: + resolution: {integrity: sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==} + wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} @@ -3638,9 +3685,15 @@ packages: peerDependencies: typescript: '>=4.1' + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} @@ -3756,6 +3809,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zlibjs@0.3.1: + resolution: {integrity: sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==} + snapshots: '@ampproject/remapping@2.3.0': @@ -5679,6 +5735,8 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + bmp-js@0.1.0: {} + boolbase@1.0.0: {} boot-cell@2.0.0-beta.29(iterable-observer@1.1.0)(typescript@5.6.2): @@ -6526,6 +6584,8 @@ snapshots: safer-buffer: 2.1.2 optional: true + idb-keyval@6.2.1: {} + idb@7.1.1: {} ieee754@1.2.1: {} @@ -6621,6 +6681,8 @@ snapshots: dependencies: has-tostringtag: 1.0.2 + is-electron@2.2.2: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.0.2: @@ -6707,6 +6769,8 @@ snapshots: is-unicode-supported@0.1.0: {} + is-url@1.2.4: {} + is-weakmap@2.0.2: {} is-weakref@1.0.2: @@ -7062,6 +7126,10 @@ snapshots: node-addon-api@7.1.1: {} + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + node-gyp-build-optional-packages@5.1.1: dependencies: detect-libc: 2.0.3 @@ -7142,6 +7210,8 @@ snapshots: dependencies: mimic-function: 5.0.1 + opencollective-postinstall@2.0.3: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -7305,6 +7375,8 @@ snapshots: pretty-bytes@5.6.0: {} + process@0.11.10: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -7707,6 +7779,23 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + tesseract.js-core@5.1.1: {} + + tesseract.js@5.1.1: + dependencies: + bmp-js: 0.1.0 + idb-keyval: 6.2.1 + is-electron: 2.2.2 + is-url: 1.2.4 + node-fetch: 2.7.0 + opencollective-postinstall: 2.0.3 + regenerator-runtime: 0.13.11 + tesseract.js-core: 5.1.1 + wasm-feature-detect: 1.8.0 + zlibjs: 0.3.1 + transitivePeerDependencies: + - encoding + text-table@0.2.0: {} through@2.3.8: {} @@ -7725,6 +7814,8 @@ snapshots: dependencies: is-number: 7.0.0 + tr46@0.0.3: {} + tr46@1.0.1: dependencies: punycode: 2.3.1 @@ -7873,6 +7964,8 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 + wasm-feature-detect@1.8.0: {} + wcwidth@1.0.1: dependencies: defaults: 1.0.4 @@ -7898,8 +7991,15 @@ snapshots: regenerator-runtime: 0.14.1 typescript: 5.6.2 + webidl-conversions@3.0.1: {} + webidl-conversions@4.0.2: {} + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + whatwg-url@7.1.0: dependencies: lodash.sortby: 4.7.0 @@ -8113,3 +8213,5 @@ snapshots: decamelize: 1.2.0 yocto-queue@0.1.0: {} + + zlibjs@0.3.1: {} diff --git a/src/component/Camera.tsx b/src/component/Camera.tsx new file mode 100644 index 0000000..211217b --- /dev/null +++ b/src/component/Camera.tsx @@ -0,0 +1,63 @@ +import { component, observer, WebCell, WebCellProps } from 'web-cell'; +import { stringifyCSS } from 'web-utility'; + +export interface CameraViewProps extends WebCellProps { + onCapture?: (event: CustomEvent) => any; +} + +export interface CameraView extends WebCell {} + +@component({ tagName: 'camera-view', mode: 'open' }) +@observer +export class CameraView + extends HTMLElement + implements WebCell +{ + cssCode = stringifyCSS({ + ':host': { display: 'block' }, + video: { width: '100%', height: '100%' } + }); + + async init(video?: HTMLVideoElement) { + const { width, height } = getComputedStyle(video); + + const stream = await navigator.mediaDevices.getUserMedia({ + video: { width: parseFloat(width), height: parseFloat(height) } + }); + + video.srcObject = stream; + video.play(); + } + + handleCapture = async (event: MouseEvent) => { + const video = event.currentTarget as HTMLVideoElement; + + if (video.paused) return video.play(); + + video.pause(); + + const { width, height } = getComputedStyle(video); + + const canvas = new OffscreenCanvas( + parseFloat(width), + parseFloat(height) + ); + const context = canvas.getContext('2d'); + + context.drawImage(video, 0, 0); + + const blob = await canvas.convertToBlob(); + + this.emit('capture', blob); + }; + + render() { + return ( + <> + + +