├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── _redirects
├── app
├── CNAME
├── css
│ └── styles.css
├── decoder.js
├── images
│ ├── photo-camera.svg
│ ├── qrcode-scanner.svg
│ └── touch
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon.jpg
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon.ico
│ │ └── mstile-150x150.png
├── index.html
├── js
│ ├── main.js
│ ├── snackbar.js
│ └── vendor
│ │ └── qrscan.js
└── manifest.json
├── logo.png
├── package.json
├── robots.txt
└── webpack.config.js
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | node_modules
6 | .DS_Store
7 | dist
8 | .env
9 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | *.json
2 | /dist
3 | app/decoder.js
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 140,
3 | "singleQuote": true,
4 | "parser": "babylon"
5 | }
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 code-kotis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # For more active development/deployments, code is moved to [https://github.com/gokulkrishh/qrcodescan.in](https://github.com/gokulkrishh/qrcodescan.in)
3 |
4 | ###
5 |
6 | # [QR Code Scanner](https://qrcodescan.in)
7 |
8 | *QR Code Scanner - a simple, fast and useful progressive web application*
9 |
10 | ### [Live](https://qrcodescan.in)
11 |
12 | ## Features
13 |
14 | - App Shell
15 | - Offline
16 | - Secure via https
17 | - Responsive
18 | - Add to home screen & Splash screen
19 | - Supported Browser (Mobile & Desktop) - Google Chrome, Firefox, Safari, Opera, Microsoft Edge and now supports iOS as well.
20 |
21 | ## Installation
22 |
23 | 1. Clone this repo
24 |
25 | ```bash
26 | git clone https://github.com/code-kotis/qr-code-scanner
27 | ```
28 |
29 | 2. Installation
30 |
31 | ```bash
32 | npm install
33 | ```
34 |
35 | 3. Run
36 |
37 | ```bash
38 | npm run start
39 | ```
40 |
41 | 4. Build
42 |
43 | ```bash
44 | npm run build
45 | ```
46 |
47 | ### Contributions
48 |
49 | If you find a bug, please file an issue. PR's are most welcome ;)
50 |
51 | #### MIT Licensed
52 |
--------------------------------------------------------------------------------
/_redirects:
--------------------------------------------------------------------------------
1 | # Redirect default Netlify subdomain to primary domain
2 | http://qrcodescan.netlify.com/* https://www.qrcodescan.in/:splat 301!
3 | https://qrcodescan/* https://www.qrcodescan/:splat 301!
--------------------------------------------------------------------------------
/app/CNAME:
--------------------------------------------------------------------------------
1 | qrcodescan.in
2 |
--------------------------------------------------------------------------------
/app/css/styles.css:
--------------------------------------------------------------------------------
1 | /*! minireset.css v0.0.2 | MIT License | github.com/jgthms/minireset.css */
2 | html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}img,embed,object,audio{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0;text-align:left}
3 |
4 | body {
5 | font-family: Roboto, Helvetica,Arial,sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | }
8 |
9 | .app__layout {
10 | position: absolute;
11 | width: 100%;
12 | height: 100%;
13 | overflow: hidden;
14 | background-color: rgba(0, 0, 0, 0.5);
15 | }
16 |
17 | .app__header {
18 | width: 100%;
19 | height: 56px;
20 | color: #fff;
21 | display: flex;
22 | -webkit-box-align: center;
23 | -ms-flex-align: center;
24 | align-items: center;
25 | position: fixed;
26 | top: 0;
27 | left: 0;
28 | right: 0;
29 | z-index: 10;
30 | }
31 |
32 | .app__header-icon {
33 | width: 35px;
34 | height: 35px;
35 | display: -webkit-box;
36 | display: -ms-flexbox;
37 | display: flex;
38 | -webkit-box-align: center;
39 | -ms-flex-align: center;
40 | align-items: center;
41 | -webkit-box-pack: center;
42 | -ms-flex-pack: center;
43 | justify-content: center;
44 | cursor: pointer;
45 | position: absolute;
46 | right: 20px;
47 | top: 20px;
48 | }
49 |
50 | .app__header-icon:active {
51 | opacity: 0.8;
52 | }
53 |
54 | .app__header-title {
55 | margin-left: 5px;
56 | font-size: 19px;
57 | user-select: none;
58 | }
59 |
60 | .app__layout-content {
61 | height: inherit;
62 | /*margin-top: 56px;*/
63 | }
64 |
65 | .custom-menu-icon {
66 | font-size: 28px;
67 | line-height: 47px;
68 | }
69 |
70 | .custom-title,
71 | .custom-menu-icon {
72 | color: #fff;
73 | }
74 |
75 | .custom-btn {
76 | position: fixed;
77 | right: 26px;
78 | bottom: 26px;
79 | background: #448aff;
80 | border-radius: 50%;
81 | border: none;
82 | width: 56px;
83 | height: 56px;
84 | outline: none;
85 | box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
86 | z-index: 9999;
87 | }
88 |
89 | .custom-btn:active {
90 | box-shadow: none;
91 | }
92 |
93 | .custom-msg {
94 | text-align: center;
95 | width: 90%;
96 | height: 50%;
97 | overflow: auto;
98 | margin: auto;
99 | position: absolute;
100 | top: 0;
101 | left: 0;
102 | bottom: 0;
103 | right: 0;
104 | font-size: 16px;
105 | }
106 |
107 | .custom-fab-icon {
108 | color: #fff;
109 | font-size: 30px;
110 | margin-top: 2px;
111 | user-select: none;
112 | }
113 |
114 | video {
115 | transform: translateX(-50%) translateY(-50%);
116 | top: 50%;
117 | left: 50%;
118 | min-width: 100%;
119 | min-height: 100%;
120 | width: auto;
121 | height: auto;
122 | position: absolute;
123 | }
124 |
125 | #list li {
126 | list-style-type: none;
127 | text-decoration: underline;
128 | color: #00F;
129 | }
130 |
131 | .custom-copy-btn {
132 | opacity: 0;
133 | }
134 |
135 | .hide {
136 | display: none;
137 | }
138 |
139 | @-webkit-keyframes scanner {
140 | 0% {
141 | bottom: 100%;
142 | }
143 | 50% {
144 | bottom: 0%;
145 | }
146 | 100% {
147 | bottom: 100%;
148 | }
149 | }
150 |
151 | @-moz-keyframes scanner {
152 | 0% {
153 | bottom: 100%;
154 | }
155 | 50% {
156 | bottom: 0%;
157 | }
158 | 100% {
159 | bottom: 100%;
160 | }
161 | }
162 |
163 | @-o-keyframes scanner {
164 | 0% {
165 | bottom: 100%;
166 | }
167 | 50% {
168 | bottom: 0%;
169 | }
170 | 100% {
171 | bottom: 100%;
172 | }
173 | }
174 |
175 | @keyframes scanner {
176 | 0% {
177 | bottom: 100%;
178 | }
179 | 50% {
180 | bottom: 0%;
181 | }
182 | 100% {
183 | bottom: 100%;
184 | }
185 | }
186 |
187 | .custom-scanner {
188 | width: 100%;
189 | height: 2px;
190 | background: #4CAF50;
191 | position: absolute;
192 | -webkit-transition: all 200ms linear;
193 | -moz-transition: all 200ms linear;
194 | transition: all 200ms linear;
195 | -webkit-animation: scanner 3s infinite linear;
196 | -moz-animation: scanner 3s infinite linear;
197 | -o-animation: scanner 3s infinite linear;
198 | animation: scanner 3s infinite linear;
199 | box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.4);
200 | display: none;
201 | }
202 |
203 | #camera {
204 | opacity: 0;
205 | }
206 |
207 | .no-support {
208 | font-size: 20px;
209 | text-align: center;
210 | }
211 |
212 | .app__snackbar {
213 | position: fixed;
214 | bottom: 15px;
215 | left: 20px;
216 | pointer-events: none;
217 | z-index: 9999;
218 | }
219 |
220 | .app__snackbar-msg {
221 | width: 250px;
222 | min-height: 50px;
223 | background-color: #404040;
224 | color: #fff;
225 | border-radius: 3px;
226 | box-shadow: 0 0 2px rgba(0,0,0,.12), 0 2px 4px rgba(0,0,0,.24);
227 | display: -webkit-box;
228 | display: -ms-flexbox;
229 | display: flex;
230 | -webkit-box-align: center;
231 | -ms-flex-align: center;
232 | align-items: center;
233 | -webkit-box-pack: justify;
234 | -ms-flex-pack: justify;
235 | justify-content: space-between;
236 | font-size: 14px;
237 | font-weight: 500;
238 | padding-left: 15px;
239 | padding-right: 10px;
240 | word-break: break-all;
241 | -webkit-transition: opacity 3s cubic-bezier(0, 0, 0.30, 1) 0;
242 | transition: opacity 0.30s cubic-bezier(0, 0, 0.30, 1) 0;
243 | text-transform: initial;
244 | margin-bottom: 10px;
245 | z-index: 9999;
246 | }
247 |
248 | .app__snackbar--hide {
249 | opacity: 0;
250 | }
251 |
252 | .app__dialog {
253 | z-index: 12;
254 | background-color: #fff;
255 | width: 290px;
256 | height: 180px;
257 | border-radius: 2px;
258 | display: flex;
259 | position: absolute;
260 | left: 0;
261 | right: 0;
262 | bottom: 0;
263 | top: 0;
264 | margin: auto;
265 | box-shadow: 0 9px 46px 8px rgba(0,0,0,.14), 0 11px 15px -7px rgba(0,0,0,.12), 0 24px 38px 3px rgba(0,0,0,.2);
266 | }
267 |
268 | .app__dialog h5 {
269 | margin-top: 20px;
270 | margin-left: 18px;
271 | font-weight: 500;
272 | }
273 |
274 | .app__dialog input {
275 | width: 250px;
276 | margin: 20px;
277 | height: 30px;
278 | border: none;
279 | border-bottom: 1px solid rgba(0,0,0,.12);
280 | outline: none;
281 | font-size: 15px;
282 | margin-top: 25px;
283 | color: rgba(0,0,0,.54);
284 | font-weight: 500;
285 | }
286 |
287 | .app__dialog-actions {
288 | display: block;
289 | position: absolute;
290 | bottom: 13px;
291 | right: 20px;
292 | }
293 |
294 | .app__dialog-open,
295 | .app__dialog-close {
296 | border: 0;
297 | height: 35px;
298 | width: 70px;
299 | font-size: 16px;
300 | background: transparent;
301 | font-weight: 500;
302 | outline: none;
303 | cursor: pointer;
304 | }
305 |
306 | .app__dialog-open {
307 | display: none;
308 | }
309 |
310 | .app__dialog-open:active,
311 | .app__dialog-close:active {
312 | opacity: 0.9;
313 | }
314 |
315 | .app__dialog--hide {
316 | display: none;
317 | }
318 |
319 | .app__overlay {
320 | position: fixed;
321 | top: 0;
322 | bottom: 0;
323 | right: 0;
324 | left: 0;
325 | transition: all 200ms ease-in;
326 | width: 320px;
327 | height: 320px;
328 | margin: auto;
329 | }
330 |
331 |
332 | .app__overlay-left,
333 | .app__overlay-right {
334 | width: 52px;
335 | height: 340px;
336 | background: #7f7f7f;
337 | }
338 |
339 | .app__overlay-left {
340 | margin-left: -57px;
341 | margin-top: -10px;
342 | }
343 |
344 | .app__overlay-right {
345 | margin-right: -57px;
346 | margin-top: -340px;
347 | float: right;
348 | }
349 |
350 | .app__overlay {
351 | border: 0;
352 | }
353 |
354 | .app__help-text,
355 | .app__select-photos {
356 | color: #fff;
357 | position: absolute;
358 | bottom: -70px;
359 | font-size: 18px;
360 | right: 0;
361 | text-align: center;
362 | user-select: none;
363 | }
364 |
365 | .app__help-text {
366 | display: none;
367 | left: 0;
368 | }
369 |
370 | .app__dialog-overlay {
371 | position: fixed;
372 | left: 0;
373 | right: 0;
374 | bottom: 0;
375 | top: 0;
376 | background: rgba(0, 0, 0, 0.55);
377 | z-index: 11;
378 | }
379 |
380 | .camera__icon,
381 | .focus__icon {
382 | position: relative;
383 | left: 10px;
384 | display: none;
385 | }
386 |
387 | .app__select-photos {
388 | width: 58px;
389 | height: 58px;
390 | cursor: pointer;
391 | position: fixed;
392 | bottom: 20px;
393 | right: 20px;
394 | border-radius: 50%;
395 | background-color: #3F51B5;
396 | background-image: url("/images/photo-camera.svg");
397 | background-repeat: no-repeat;
398 | background-size: 26px;
399 | background-position: 16px 15px;
400 | }
401 |
402 | .app__select-photos:active {
403 | opacity: 0.8;
404 | }
405 |
406 | input[type='file'] {
407 | display: none;
408 | }
409 |
410 | #frame {
411 | width: auto;
412 | height: auto;
413 | }
414 |
--------------------------------------------------------------------------------
/app/images/photo-camera.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/images/qrcode-scanner.svg:
--------------------------------------------------------------------------------
1 |
4 |
6 |
9 |
10 |
13 |
14 |
17 |
18 |
19 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/images/touch/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-kotis/qr-code-scanner/37f153b969fbd9b5d9e9aad5571efd133b0769ff/app/images/touch/android-chrome-192x192.png
--------------------------------------------------------------------------------
/app/images/touch/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-kotis/qr-code-scanner/37f153b969fbd9b5d9e9aad5571efd133b0769ff/app/images/touch/android-chrome-512x512.png
--------------------------------------------------------------------------------
/app/images/touch/apple-touch-icon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-kotis/qr-code-scanner/37f153b969fbd9b5d9e9aad5571efd133b0769ff/app/images/touch/apple-touch-icon.jpg
--------------------------------------------------------------------------------
/app/images/touch/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-kotis/qr-code-scanner/37f153b969fbd9b5d9e9aad5571efd133b0769ff/app/images/touch/favicon-16x16.png
--------------------------------------------------------------------------------
/app/images/touch/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-kotis/qr-code-scanner/37f153b969fbd9b5d9e9aad5571efd133b0769ff/app/images/touch/favicon-32x32.png
--------------------------------------------------------------------------------
/app/images/touch/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-kotis/qr-code-scanner/37f153b969fbd9b5d9e9aad5571efd133b0769ff/app/images/touch/favicon.ico
--------------------------------------------------------------------------------
/app/images/touch/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-kotis/qr-code-scanner/37f153b969fbd9b5d9e9aad5571efd133b0769ff/app/images/touch/mstile-150x150.png
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | QR Code Scanner
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
QR Code
43 |
44 |
45 |
46 | Open
47 | Close
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
66 |
77 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/app/js/main.js:
--------------------------------------------------------------------------------
1 | import QRReader from './vendor/qrscan.js';
2 | import { snackbar } from './snackbar.js';
3 | import styles from '../css/styles.css';
4 | import isURL from 'is-url';
5 |
6 | //If service worker is installed, show offline usage notification
7 | if ('serviceWorker' in navigator) {
8 | window.addEventListener('load', () => {
9 | navigator.serviceWorker
10 | .register('/service-worker.js')
11 | .then(reg => {
12 | console.log('SW registered: ', reg);
13 | if (!localStorage.getItem('offline')) {
14 | localStorage.setItem('offline', true);
15 | snackbar.show('App is ready for offline usage.', 5000);
16 | }
17 | })
18 | .catch(regError => {
19 | console.log('SW registration failed: ', regError);
20 | });
21 | });
22 | }
23 |
24 | window.addEventListener('DOMContentLoaded', () => {
25 | //To check the device and add iOS support
26 | window.iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0;
27 | window.isMediaStreamAPISupported = navigator && navigator.mediaDevices && 'enumerateDevices' in navigator.mediaDevices;
28 | window.noCameraPermission = false;
29 |
30 | var copiedText = null;
31 | var frame = null;
32 | var selectPhotoBtn = document.querySelector('.app__select-photos');
33 | var dialogElement = document.querySelector('.app__dialog');
34 | var dialogOverlayElement = document.querySelector('.app__dialog-overlay');
35 | var dialogOpenBtnElement = document.querySelector('.app__dialog-open');
36 | var dialogCloseBtnElement = document.querySelector('.app__dialog-close');
37 | var scanningEle = document.querySelector('.custom-scanner');
38 | var textBoxEle = document.querySelector('#result');
39 | var helpTextEle = document.querySelector('.app__help-text');
40 | var infoSvg = document.querySelector('.app__header-icon svg');
41 | var videoElement = document.querySelector('video');
42 | window.appOverlay = document.querySelector('.app__overlay');
43 |
44 | //Initializing qr scanner
45 | window.addEventListener('load', event => {
46 | QRReader.init(); //To initialize QR Scanner
47 | // Set camera overlay size
48 | setTimeout(() => {
49 | setCameraOverlay();
50 | if (window.isMediaStreamAPISupported) {
51 | scan();
52 | }
53 | }, 1000);
54 |
55 | // To support other browsers who dont have mediaStreamAPI
56 | selectFromPhoto();
57 | });
58 |
59 | function setCameraOverlay() {
60 | window.appOverlay.style.borderStyle = 'solid';
61 | }
62 |
63 | function createFrame() {
64 | frame = document.createElement('img');
65 | frame.src = '';
66 | frame.id = 'frame';
67 | }
68 |
69 | //Dialog close btn event
70 | dialogCloseBtnElement.addEventListener('click', hideDialog, false);
71 | dialogOpenBtnElement.addEventListener('click', openInBrowser, false);
72 |
73 | //To open result in browser
74 | function openInBrowser() {
75 | console.log('Result: ', copiedText);
76 | window.open(copiedText, '_blank', 'toolbar=0,location=0,menubar=0');
77 | copiedText = null;
78 | hideDialog();
79 | }
80 |
81 | //Scan
82 | function scan(forSelectedPhotos = false) {
83 | if (window.isMediaStreamAPISupported && !window.noCameraPermission) {
84 | scanningEle.style.display = 'block';
85 | }
86 |
87 | if (forSelectedPhotos) {
88 | scanningEle.style.display = 'block';
89 | }
90 |
91 | QRReader.scan(result => {
92 | copiedText = result;
93 | textBoxEle.value = result;
94 | textBoxEle.select();
95 | scanningEle.style.display = 'none';
96 | if (isURL(result)) {
97 | dialogOpenBtnElement.style.display = 'inline-block';
98 | }
99 | dialogElement.classList.remove('app__dialog--hide');
100 | dialogOverlayElement.classList.remove('app__dialog--hide');
101 | const frame = document.querySelector('#frame');
102 | // if (forSelectedPhotos && frame) frame.remove();
103 | }, forSelectedPhotos);
104 | }
105 |
106 | //Hide dialog
107 | function hideDialog() {
108 | copiedText = null;
109 | textBoxEle.value = '';
110 |
111 | if (!window.isMediaStreamAPISupported) {
112 | frame.src = '';
113 | frame.className = '';
114 | }
115 |
116 | dialogElement.classList.add('app__dialog--hide');
117 | dialogOverlayElement.classList.add('app__dialog--hide');
118 | scan();
119 | }
120 |
121 | function selectFromPhoto() {
122 | //Creating the camera element
123 | var camera = document.createElement('input');
124 | camera.setAttribute('type', 'file');
125 | camera.setAttribute('capture', 'camera');
126 | camera.id = 'camera';
127 | window.appOverlay.style.borderStyle = '';
128 | selectPhotoBtn.style.display = 'block';
129 | createFrame();
130 |
131 | //Add the camera and img element to DOM
132 | var pageContentElement = document.querySelector('.app__layout-content');
133 | pageContentElement.appendChild(camera);
134 | pageContentElement.appendChild(frame);
135 |
136 | //Click of camera fab icon
137 | selectPhotoBtn.addEventListener('click', () => {
138 | scanningEle.style.display = 'none';
139 | document.querySelector('#camera').click();
140 | });
141 |
142 | //On camera change
143 | camera.addEventListener('change', event => {
144 | if (event.target && event.target.files.length > 0) {
145 | frame.className = 'app__overlay';
146 | frame.src = URL.createObjectURL(event.target.files[0]);
147 | if (!window.noCameraPermission) scanningEle.style.display = 'block';
148 | window.appOverlay.style.borderColor = 'rgb(62, 78, 184)';
149 | scan(true);
150 | }
151 | });
152 | }
153 | });
154 |
--------------------------------------------------------------------------------
/app/js/snackbar.js:
--------------------------------------------------------------------------------
1 | var snackbar = {};
2 | var snackBarElement = document.querySelector('.app__snackbar');
3 | var snackbarMsg = null;
4 |
5 | //To show notification
6 | snackbar.show = (msg, options = 4000) => {
7 | if (!msg) return;
8 |
9 | if (snackbarMsg) {
10 | snackbarMsg.remove();
11 | }
12 |
13 | snackbarMsg = document.createElement('div');
14 | snackbarMsg.className = 'app__snackbar-msg';
15 | snackbarMsg.textContent = msg;
16 | snackBarElement.appendChild(snackbarMsg);
17 |
18 | //Show toast for 3secs and hide it
19 | setTimeout(() => {
20 | snackbarMsg.remove();
21 | }, options);
22 | };
23 |
24 | exports.snackbar = snackbar;
25 |
--------------------------------------------------------------------------------
/app/js/vendor/qrscan.js:
--------------------------------------------------------------------------------
1 | import { snackbar } from '../snackbar.js';
2 |
3 | var QRReader = {};
4 |
5 | QRReader.active = false;
6 | QRReader.webcam = null;
7 | QRReader.canvas = null;
8 | QRReader.ctx = null;
9 | QRReader.decoder = null;
10 |
11 | QRReader.setCanvas = () => {
12 | QRReader.canvas = document.createElement('canvas');
13 | QRReader.ctx = QRReader.canvas.getContext('2d');
14 | };
15 |
16 | function setPhotoSourceToScan(forSelectedPhotos) {
17 | if (!forSelectedPhotos && window.isMediaStreamAPISupported) {
18 | QRReader.webcam = document.querySelector('video');
19 | } else {
20 | QRReader.webcam = document.querySelector('img');
21 | }
22 | }
23 |
24 | QRReader.init = () => {
25 | var baseurl = '';
26 | var streaming = false;
27 |
28 | // Init Webcam + Canvas
29 | setPhotoSourceToScan();
30 |
31 | QRReader.setCanvas();
32 | QRReader.decoder = new Worker(baseurl + 'decoder.js');
33 |
34 | if (window.isMediaStreamAPISupported) {
35 | // Resize webcam according to input
36 | QRReader.webcam.addEventListener(
37 | 'play',
38 | function(ev) {
39 | if (!streaming) {
40 | setCanvasProperties();
41 | streaming = true;
42 | }
43 | },
44 | false
45 | );
46 | } else {
47 | setCanvasProperties();
48 | }
49 |
50 | function setCanvasProperties() {
51 | QRReader.canvas.width = window.innerWidth;
52 | QRReader.canvas.height = window.innerHeight;
53 | }
54 |
55 | function startCapture(constraints) {
56 | navigator.mediaDevices
57 | .getUserMedia(constraints)
58 | .then(function(stream) {
59 | QRReader.webcam.srcObject = stream;
60 | QRReader.webcam.setAttribute('playsinline', true);
61 | QRReader.webcam.setAttribute('controls', true);
62 | setTimeout(() => {
63 | document.querySelector('video').removeAttribute('controls');
64 | });
65 | })
66 | .catch(function(err) {
67 | console.log('Error occurred ', err);
68 | showErrorMsg();
69 | });
70 | }
71 |
72 | if (window.isMediaStreamAPISupported) {
73 | navigator.mediaDevices
74 | .enumerateDevices()
75 | .then(function(devices) {
76 | var device = devices.filter(function(device) {
77 | var deviceLabel = device.label.split(',')[1];
78 | if (device.kind == 'videoinput') {
79 | return device;
80 | }
81 | });
82 |
83 | var constraints;
84 | if (device.length > 1) {
85 | constraints = {
86 | video: {
87 | mandatory: {
88 | sourceId: device[device.length - 1].deviceId ? device[device.length - 1].deviceId : null
89 | }
90 | },
91 | audio: false
92 | };
93 |
94 | if (window.iOS) {
95 | constraints.video.facingMode = 'environment';
96 | }
97 | startCapture(constraints);
98 | } else if (device.length) {
99 | constraints = {
100 | video: {
101 | mandatory: {
102 | sourceId: device[0].deviceId ? device[0].deviceId : null
103 | }
104 | },
105 | audio: false
106 | };
107 |
108 | if (window.iOS) {
109 | constraints.video.facingMode = 'environment';
110 | }
111 |
112 | if (!constraints.video.mandatory.sourceId && !window.iOS) {
113 | startCapture({ video: true });
114 | } else {
115 | startCapture(constraints);
116 | }
117 | } else {
118 | startCapture({ video: true });
119 | }
120 | })
121 | .catch(function(error) {
122 | showErrorMsg();
123 | console.error('Error occurred : ', error);
124 | });
125 | }
126 |
127 | function showErrorMsg() {
128 | window.noCameraPermission = true;
129 | document.querySelector('.custom-scanner').style.display = 'none';
130 | snackbar.show('Unable to access the camera', 10000);
131 | }
132 | };
133 |
134 | /**
135 | * \brief QRReader Scan Action
136 | * Call this to start scanning for QR codes.
137 | *
138 | * \param A function(scan_result)
139 | */
140 | QRReader.scan = function(callback, forSelectedPhotos) {
141 | QRReader.active = true;
142 | QRReader.setCanvas();
143 | function onDecoderMessage(event) {
144 | if (event.data.length > 0) {
145 | var qrid = event.data[0][2];
146 | QRReader.active = false;
147 | callback(qrid);
148 | }
149 | setTimeout(newDecoderFrame, 0);
150 | }
151 |
152 | QRReader.decoder.onmessage = onDecoderMessage;
153 |
154 | setTimeout(() => {
155 | setPhotoSourceToScan(forSelectedPhotos);
156 | });
157 |
158 | // Start QR-decoder
159 | function newDecoderFrame() {
160 | if (!QRReader.active) return;
161 | try {
162 | QRReader.ctx.drawImage(QRReader.webcam, 0, 0, QRReader.canvas.width, QRReader.canvas.height);
163 | var imgData = QRReader.ctx.getImageData(0, 0, QRReader.canvas.width, QRReader.canvas.height);
164 |
165 | if (imgData.data) {
166 | QRReader.decoder.postMessage(imgData);
167 | }
168 | } catch (e) {
169 | // Try-Catch to circumvent Firefox Bug #879717
170 | if (e.name == 'NS_ERROR_NOT_AVAILABLE') setTimeout(newDecoderFrame, 0);
171 | }
172 | }
173 | newDecoderFrame();
174 | };
175 |
176 | export default QRReader;
177 |
--------------------------------------------------------------------------------
/app/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "QR Scanner",
3 | "name": "QR Code Scanner",
4 | "description": "QR code scanner progressive web application",
5 | "display": "standalone",
6 | "orientation": "portrait",
7 | "icons": [
8 | {
9 | "src": "/images/touch/android-chrome-192x192.png",
10 | "sizes": "192x192",
11 | "type": "image/png"
12 | },
13 | {
14 | "src": "/images/touch/android-chrome-512x512.png",
15 | "sizes": "512x512",
16 | "type": "image/png"
17 | }],
18 | "start_url": "/index.html?utm_source=homescreen",
19 | "theme_color": "#e4e4e4",
20 | "background_color": "#fff"
21 | }
22 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-kotis/qr-code-scanner/37f153b969fbd9b5d9e9aad5571efd133b0769ff/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "qr-code-scanner",
3 | "description": "QR Code Scanner is the fastest and most user-friendly web application.",
4 | "version": "1.0.0",
5 | "scripts": {
6 | "start": "webpack-dev-server --hot --inline --open --mode=development",
7 | "build": "cross-env NODE_ENV=production webpack-cli --mode=production --config webpack.config.js",
8 | "stats": "cross-env NODE_ENV=production webpack-cli --mode=production --profile --json > stats.json",
9 | "precommit": "lint-staged",
10 | "pretty": "prettier --write 'app/**/*.js'"
11 | },
12 | "lint-staged": {
13 | "*.{js,css}": [
14 | "npm run pretty",
15 | "git add"
16 | ]
17 | },
18 | "engines": {
19 | "node": ">=4.1.1"
20 | },
21 | "dependencies": {
22 | "is-url": "^1.2.4"
23 | },
24 | "devDependencies": {
25 | "clean-webpack-plugin": "^0.1.19",
26 | "copy-webpack-plugin": "^4.5.3",
27 | "cross-env": "^5.2.0",
28 | "css-loader": "^1.0.0",
29 | "extract-text-webpack-plugin": "4.0.0-beta.0",
30 | "file-loader": "^2.0.0",
31 | "html-webpack-plugin": "^3.2.0",
32 | "husky": "^1.1.2",
33 | "lint-staged": "^7.3.0",
34 | "mini-css-extract-plugin": "^0.4.4",
35 | "optimize-css-assets-webpack-plugin": "^5.0.1",
36 | "prettier": "^1.14.3",
37 | "sitemap-webpack-plugin": "^0.8.0",
38 | "style-loader": "^0.23.1",
39 | "webpack": "^4.20.2",
40 | "webpack-cli": "^3.1.2",
41 | "webpack-dev-server": "^3.1.9",
42 | "workbox-webpack-plugin": "^3.6.2"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/robots.txt:
--------------------------------------------------------------------------------
1 | # www.robotstxt.org/
2 |
3 | # Allow crawling of all content
4 | User-agent: *
5 | Disallow:
6 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const CleanWebpackPlugin = require('clean-webpack-plugin');
3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
4 | const WorkboxPlugin = require('workbox-webpack-plugin');
5 | const HtmlWebpackPlugin = require('html-webpack-plugin');
6 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
7 | const CopyWebpackPlugin = require('copy-webpack-plugin');
8 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
9 | const SitemapPlugin = require('sitemap-webpack-plugin').default;
10 |
11 | module.exports = {
12 | entry: './app/js/main.js',
13 | output: {
14 | path: path.resolve(__dirname, 'dist'),
15 | filename: '[name].[hash].bundle.js'
16 | },
17 | devServer: {
18 | contentBase: __dirname + '/app'
19 | },
20 | optimization: {},
21 | plugins: [
22 | new CleanWebpackPlugin(['dist']),
23 | new MiniCssExtractPlugin({
24 | filename: '[name].css',
25 | chunkFilename: '[id].css'
26 | }),
27 | new WorkboxPlugin.GenerateSW({
28 | clientsClaim: true,
29 | skipWaiting: true,
30 | runtimeCaching: [{ urlPattern: new RegExp('/'), handler: 'staleWhileRevalidate' }]
31 | }),
32 | new HtmlWebpackPlugin({
33 | template: './app/index.html',
34 | minify: {
35 | collapseWhitespace: true
36 | }
37 | }),
38 | new ExtractTextPlugin({
39 | filename: 'styles.css'
40 | }),
41 | new OptimizeCssAssetsPlugin({
42 | cssProcessorPluginOptions: {
43 | preset: ['default', { discardComments: { removeAll: true } }]
44 | }
45 | }),
46 | new CopyWebpackPlugin([{ from: 'images/', to: 'images' }, 'decoder.js', 'manifest.json', 'CNAME'], {
47 | context: './app'
48 | }),
49 | new SitemapPlugin('https://qrcodescan.in', ['/'])
50 | ],
51 | module: {
52 | rules: [
53 | {
54 | test: /\.css$/,
55 | use: ExtractTextPlugin.extract({
56 | use: 'css-loader?importLoaders=1',
57 | fallback: 'style-loader'
58 | })
59 | },
60 | {
61 | test: /.*\.(gif|png|jpe?g|svg)$/i,
62 | use: ['file-loader']
63 | }
64 | ]
65 | }
66 | };
67 |
--------------------------------------------------------------------------------