Added scanning Barcodes with a camera #1
131
README.md
131
README.md
@@ -9,7 +9,7 @@ Webpage Playground is a lightweight, responsive web application for managing and
|
|||||||
- **Dynamic Navigation** — Easy page switching with a responsive navbar
|
- **Dynamic Navigation** — Easy page switching with a responsive navbar
|
||||||
- **Inventory Demo** — Display items using reusable item components
|
- **Inventory Demo** — Display items using reusable item components
|
||||||
- **Advanced Search** — Filter inventory by name, location, quantity range, and expiry date
|
- **Advanced Search** — Filter inventory by name, location, quantity range, and expiry date
|
||||||
- **Barcode Scanner** — Scan barcodes for inventory management (keyboard and hardware scanner support)
|
- **Barcode Scanner** — Scan barcodes using camera or keyboard (hardware scanner support)
|
||||||
- **Data Visualization** — Pie chart showing inventory distribution
|
- **Data Visualization** — Pie chart showing inventory distribution
|
||||||
- **Responsive Design** — Works on desktop, tablet, and mobile devices
|
- **Responsive Design** — Works on desktop, tablet, and mobile devices
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ Webpage Playground is a lightweight, responsive web application for managing and
|
|||||||
### Pages
|
### Pages
|
||||||
- `index.html` — Homepage with item component demo and inventory pie chart
|
- `index.html` — Homepage with item component demo and inventory pie chart
|
||||||
- `search.html` — Search and filter inventory by name, location, quantity, and expiry date
|
- `search.html` — Search and filter inventory by name, location, quantity, and expiry date
|
||||||
- `barcode.html` — Barcode scanner with console logging for testing
|
- `barcode.html` — Barcode scanner with camera and keyboard input modes
|
||||||
- `pantry.html` — Pantry inventory container
|
- `pantry.html` — Pantry inventory container
|
||||||
- `fridge.html` — Fridge inventory container
|
- `fridge.html` — Fridge inventory container
|
||||||
- `freezer.html` — Freezer inventory container
|
- `freezer.html` — Freezer inventory container
|
||||||
@@ -55,7 +55,7 @@ Advanced inventory search and filtering interface.
|
|||||||
- Search by item name (case-insensitive)
|
- Search by item name (case-insensitive)
|
||||||
- Filter by storage location (All, Pantry, Fridge, Freezer)
|
- Filter by storage location (All, Pantry, Fridge, Freezer)
|
||||||
- Filter by quantity range (min/max values)
|
- Filter by quantity range (min/max values)
|
||||||
- **NEW: Filter by expiry date range (start date and end date)**
|
- Filter by expiry date range (start date and end date)
|
||||||
- Real-time result display with item cards
|
- Real-time result display with item cards
|
||||||
- Visual expiry status indicators (Fresh, Expiring Soon, Expired)
|
- Visual expiry status indicators (Fresh, Expiring Soon, Expired)
|
||||||
- Reset button to clear all filters
|
- Reset button to clear all filters
|
||||||
@@ -78,38 +78,61 @@ Results show all items with expiry dates in that range
|
|||||||
Items display formatted dates and expiry status badges
|
Items display formatted dates and expiry status badges
|
||||||
```
|
```
|
||||||
|
|
||||||
**Available Items (20 total across all locations):**
|
|
||||||
- Pantry: Pasta (120 days), Rice (180 days), Cereal (60 days), Flour (150 days), Sugar (200 days), Salt (365 days), Olive Oil (90 days), Canned Beans (EXPIRED -10 days)
|
|
||||||
- Fridge: Milk (5 days), Cheese (30 days), Greek Yogurt (7 days), Eggs (21 days), Butter (45 days), Chicken Salad (EXPIRED -2 days)
|
|
||||||
- Freezer: Ice Cream (90 days), Frozen Vegetables (180 days), Chicken Breast (120 days), Ground Beef (150 days), Pizza (200 days), Ice (365 days)
|
|
||||||
|
|
||||||
### 📱 Barcode Scanner (`barcode.html`)
|
### 📱 Barcode Scanner (`barcode.html`)
|
||||||
Barcode capture interface with console logging for testing.
|
Dual-mode barcode scanning interface with keyboard input and camera scanning.
|
||||||
|
|
||||||
**Features:**
|
**Features:**
|
||||||
- Keyboard input simulation (type barcode + press Enter)
|
|
||||||
- Hardware barcode scanner device support (ready for integration)
|
#### Keyboard Input Mode
|
||||||
- Paste support (Ctrl/Cmd+V)
|
- Type or paste barcode values
|
||||||
- Console logging with formatted output including:
|
- Hardware barcode scanner device support
|
||||||
- Timestamp (HH:MM:SS.mmm format)
|
- Real-time console logging
|
||||||
- Barcode value
|
- Auto-focus field for seamless entry
|
||||||
- Input type detection (keyboard, hardware-scanner, keyboard-paste)
|
- Enter key to scan
|
||||||
- Metadata for debugging
|
|
||||||
- Auto-focus input field after each scan
|
#### Camera Scanning Mode (**NEW**)
|
||||||
- Visual status indicator
|
- **Real-time barcode detection** using device camera
|
||||||
- Helper instructions and "Open Console" button
|
- **Supported formats:** UPC-A, UPC-E, EAN-13, EAN-8, Code-128, Code-39, and more
|
||||||
|
- **Browser-based detection** (uses Quagga2 library - local version)
|
||||||
|
- **Mode toggle** between keyboard and camera input
|
||||||
|
- **Camera controls** with start/stop buttons
|
||||||
|
- **Real-time feedback** showing detected barcodes
|
||||||
|
- **Error handling** with user-friendly messages
|
||||||
|
- **Mobile support** — Works on iOS and Android devices with camera access
|
||||||
|
- **Console logging** of all detections with metadata
|
||||||
|
|
||||||
|
**Camera Usage:**
|
||||||
|
1. Switch to "Camera Scan" mode by clicking the button
|
||||||
|
2. Click "Start Camera" to begin
|
||||||
|
3. Browser will request camera permission (grant access)
|
||||||
|
4. Position barcode in front of camera
|
||||||
|
5. Barcode is detected automatically in real-time
|
||||||
|
6. Detected barcodes logged to console (press F12)
|
||||||
|
7. Click "Stop Camera" to end scanning
|
||||||
|
|
||||||
|
**Keyboard Usage:**
|
||||||
|
1. Stay in "Keyboard Input" mode
|
||||||
|
2. Type barcode value or paste from clipboard
|
||||||
|
3. Press Enter to complete scan
|
||||||
|
4. Barcode logged to console
|
||||||
|
|
||||||
**Console Output Format:**
|
**Console Output Format:**
|
||||||
```
|
```
|
||||||
[Barcode Scanned] 14:07:32.456 | Barcode: 5901234123457 | Input: keyboard
|
[Barcode Scanned] 14:07:32.456 | Barcode: 5901234123457 | Input: keyboard
|
||||||
|
[Barcode Scanned] 14:07:45.123 | Barcode: 123456789012 | Input: camera
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Browser Support:**
|
||||||
|
- Chrome 53+, Firefox 55+, Safari 11+, Edge 79+
|
||||||
|
- Camera access requires HTTPS or localhost
|
||||||
|
- Mobile browsers support camera scanning
|
||||||
|
|
||||||
**Testing Instructions:**
|
**Testing Instructions:**
|
||||||
1. Navigate to the Barcode Scanner page via navbar
|
1. Navigate to the Barcode Scanner page via navbar
|
||||||
2. Type a barcode value or use a barcode scanner device
|
2. Try keyboard input mode: Type a barcode and press Enter
|
||||||
3. Press Enter to complete the scan
|
3. Try camera mode: Switch to "Camera Scan" and point at a barcode
|
||||||
4. Press F12 to open Developer Tools
|
4. Press F12 to open DevTools Console
|
||||||
5. Switch to the Console tab to view scanned barcodes with metadata
|
5. View logged barcodes with timestamps and input type
|
||||||
|
|
||||||
### 📦 Storage Location Pages
|
### 📦 Storage Location Pages
|
||||||
- `pantry.html` — Pantry inventory (expandable for displaying specific items)
|
- `pantry.html` — Pantry inventory (expandable for displaying specific items)
|
||||||
@@ -143,7 +166,7 @@ python -m http.server 8000
|
|||||||
The navbar appears at the top of every page and provides links to:
|
The navbar appears at the top of every page and provides links to:
|
||||||
- Homepage — Main demo page with pie chart
|
- Homepage — Main demo page with pie chart
|
||||||
- Search — Advanced inventory search and filtering with expiry date support
|
- Search — Advanced inventory search and filtering with expiry date support
|
||||||
- Barcode Scanner — Barcode capture interface (testing via console)
|
- Barcode Scanner — Barcode capture with camera and keyboard modes
|
||||||
- Pantry, Fridge, Freezer — Individual storage location pages
|
- Pantry, Fridge, Freezer — Individual storage location pages
|
||||||
|
|
||||||
### Using the Search Page
|
### Using the Search Page
|
||||||
@@ -152,7 +175,7 @@ The navbar appears at the top of every page and provides links to:
|
|||||||
- Item name (optional)
|
- Item name (optional)
|
||||||
- Storage location (optional, defaults to "All")
|
- Storage location (optional, defaults to "All")
|
||||||
- Quantity range (optional, defaults to 0-999)
|
- Quantity range (optional, defaults to 0-999)
|
||||||
- **Expiry date range (optional) — Leave blank to ignore**
|
- Expiry date range (optional) — Leave blank to ignore
|
||||||
3. Click "Search" or press Enter
|
3. Click "Search" or press Enter
|
||||||
4. Results display as item cards using the item component with expiry status badges
|
4. Results display as item cards using the item component with expiry status badges
|
||||||
5. Click "Reset" to clear all filters
|
5. Click "Reset" to clear all filters
|
||||||
@@ -163,14 +186,33 @@ The navbar appears at the top of every page and provides links to:
|
|||||||
- Find fresh items: Set start date to tomorrow, end date to 90 days from today
|
- Find fresh items: Set start date to tomorrow, end date to 90 days from today
|
||||||
|
|
||||||
### Using the Barcode Scanner
|
### Using the Barcode Scanner
|
||||||
|
|
||||||
|
#### Keyboard Mode
|
||||||
1. Open the Barcode Scanner page from the navbar
|
1. Open the Barcode Scanner page from the navbar
|
||||||
2. Click in the input field (auto-focused)
|
2. Make sure "Keyboard Input" mode is selected
|
||||||
3. Enter a barcode:
|
3. Click in the input field (auto-focused)
|
||||||
|
4. Enter a barcode:
|
||||||
- Type manually and press Enter
|
- Type manually and press Enter
|
||||||
- Scan with a barcode scanner device
|
- Scan with a barcode scanner device
|
||||||
- Paste a value (Ctrl/Cmd+V)
|
- Paste a value (Ctrl/Cmd+V)
|
||||||
4. Press F12 to open Developer Tools Console
|
5. Press F12 to open Developer Tools Console
|
||||||
5. View scanned barcodes in the Console tab with timestamp and metadata
|
6. View scanned barcodes in the Console tab with metadata
|
||||||
|
|
||||||
|
#### Camera Mode
|
||||||
|
1. Open the Barcode Scanner page from the navbar
|
||||||
|
2. Click "Camera Scan" to switch to camera mode
|
||||||
|
3. Click "Start Camera"
|
||||||
|
4. Browser will request camera permission (grant access)
|
||||||
|
5. Position barcode in front of camera lens
|
||||||
|
6. Barcode is detected automatically and displayed
|
||||||
|
7. Press F12 to view console logs
|
||||||
|
8. Click "Stop Camera" to end
|
||||||
|
|
||||||
|
**Tips:**
|
||||||
|
- Ensure good lighting for better barcode detection
|
||||||
|
- Hold barcode steady and clearly in frame
|
||||||
|
- Device must have a working camera
|
||||||
|
- Camera mode requires HTTPS or localhost
|
||||||
|
|
||||||
### The Item Component
|
### The Item Component
|
||||||
The `item-component` is a reusable UI building block used throughout the app. See `ITEM_COMPONENT.md` for detailed documentation on using it in your own pages.
|
The `item-component` is a reusable UI building block used throughout the app. See `ITEM_COMPONENT.md` for detailed documentation on using it in your own pages.
|
||||||
@@ -251,10 +293,34 @@ export const inventoryData = [
|
|||||||
- Implement backend storage (currently using static data)
|
- Implement backend storage (currently using static data)
|
||||||
- Add user authentication
|
- Add user authentication
|
||||||
- Integrate with real barcode/UPC database
|
- Integrate with real barcode/UPC database
|
||||||
|
- Deploy camera barcode detection to server (for performance optimization)
|
||||||
- Consider a frontend framework (React, Vue, etc.) for scale
|
- Consider a frontend framework (React, Vue, etc.) for scale
|
||||||
- Add unit/integration tests
|
- Add unit/integration tests
|
||||||
- Set up CI/CD pipeline
|
- Set up CI/CD pipeline
|
||||||
- Implement database for persistent storage and expiry date tracking
|
- Implement database for persistent storage and inventory tracking
|
||||||
|
|
||||||
|
## Technical Notes
|
||||||
|
|
||||||
|
### Barcode Scanning Library
|
||||||
|
- **Library:** Quagga2 (local version v1.12.1)
|
||||||
|
- **Location:** `./quagga2/quagga2-1.12.1/docs/examples/dist/quagga.min.js` (153 KB)
|
||||||
|
- **Why Quagga2:** Improved barcode detection accuracy and performance compared to original Quagga
|
||||||
|
- **Supported Formats:** UPC-A, UPC-E, EAN-13, EAN-8, Code-128, Code-39
|
||||||
|
- **API:** Backward compatible with original Quagga.js; no code changes needed
|
||||||
|
- **Initialization:** See `barcode.html` lines 268-290 for camera initialization logic
|
||||||
|
|
||||||
|
### Camera Access
|
||||||
|
- Requires browser permission (user must grant access)
|
||||||
|
- Works over HTTPS or localhost only (security requirement)
|
||||||
|
- Uses HTML5 MediaDevices API (`getUserMedia`)
|
||||||
|
- Video constraints: 800x600 resolution, environment-facing camera
|
||||||
|
- Real-time frame processing for barcode detection
|
||||||
|
|
||||||
|
### Performance Considerations
|
||||||
|
- Camera scanning is CPU-intensive on older devices
|
||||||
|
- Frame processing happens in real-time on main thread
|
||||||
|
- Consider WebWorkers for production optimization
|
||||||
|
- Network-free operation: all detection happens client-side
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
@@ -267,7 +333,8 @@ Contributions are welcome! Suggested improvements:
|
|||||||
- [ ] Implement item editing/deletion on storage pages
|
- [ ] Implement item editing/deletion on storage pages
|
||||||
- [ ] Add barcode/UPC lookup for real products
|
- [ ] Add barcode/UPC lookup for real products
|
||||||
- [ ] Convert item-component to custom element (`<item-component>`)
|
- [ ] Convert item-component to custom element (`<item-component>`)
|
||||||
- [ ] Add more detailed inventory tracking (expiration dates calculations, locations within rooms, batch numbers, etc.)
|
- [ ] Optimize camera barcode detection for performance
|
||||||
|
- [ ] Add more barcode format support
|
||||||
- [ ] Add email/notification alerts for items expiring soon
|
- [ ] Add email/notification alerts for items expiring soon
|
||||||
- [ ] Implement CSV import/export for inventory
|
- [ ] Implement CSV import/export for inventory
|
||||||
|
|
||||||
|
|||||||
407
barcode.html
407
barcode.html
@@ -5,12 +5,13 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="main.css">
|
<link rel="stylesheet" href="main.css">
|
||||||
<title>Barcode Scanner</title>
|
<title>Barcode Scanner</title>
|
||||||
|
<script src="./quagga2/quagga2-1.12.1/docs/examples/dist/quagga.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
.scanner-container {
|
.scanner-container {
|
||||||
background: #f9f9f9;
|
background: #f9f9f9;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
max-width: 600px;
|
max-width: 800px;
|
||||||
margin: 30px auto;
|
margin: 30px auto;
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
@@ -31,6 +32,64 @@
|
|||||||
margin: 10px 0 0 0;
|
margin: 10px 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mode-toggle {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: 2px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: white;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-btn.active {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border-color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-btn:hover:not(.active) {
|
||||||
|
border-color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-section {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-section.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-section.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#video {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #000;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.barcode-input-wrapper {
|
.barcode-input-wrapper {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
@@ -60,6 +119,11 @@
|
|||||||
background-color: #fffef0;
|
background-color: #fffef0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.barcode-input.highlight {
|
||||||
|
border-color: #4CAF50;
|
||||||
|
background-color: #e8f5e9;
|
||||||
|
}
|
||||||
|
|
||||||
.scanner-status {
|
.scanner-status {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
@@ -129,10 +193,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
flex: 1;
|
|
||||||
padding: 12px 20px;
|
padding: 12px 20px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -140,6 +204,17 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #45a049;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
@@ -151,6 +226,15 @@
|
|||||||
background-color: #bbb;
|
background-color: #bbb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
|
||||||
.open-console {
|
.open-console {
|
||||||
background-color: #2196F3;
|
background-color: #2196F3;
|
||||||
color: white;
|
color: white;
|
||||||
@@ -160,6 +244,41 @@
|
|||||||
background-color: #0b7dda;
|
background-color: #0b7dda;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
background-color: #ffebee;
|
||||||
|
border: 1px solid #f5a5ac;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 12px;
|
||||||
|
color: #c62828;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detected-barcode {
|
||||||
|
background-color: #e8f5e9;
|
||||||
|
border: 2px solid #4CAF50;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detected-barcode.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detected-barcode-value {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2e7d32;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive design */
|
/* Responsive design */
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.scanner-container {
|
.scanner-container {
|
||||||
@@ -179,6 +298,15 @@
|
|||||||
.btn {
|
.btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mode-toggle {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-btn {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -191,33 +319,82 @@
|
|||||||
<div class="scanner-container">
|
<div class="scanner-container">
|
||||||
<div class="scanner-header">
|
<div class="scanner-header">
|
||||||
<h3>📱 Barcode Scanner</h3>
|
<h3>📱 Barcode Scanner</h3>
|
||||||
<p>Scan a barcode or manually enter one below</p>
|
<p>Scan barcodes using camera or keyboard input</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="scanner-status active" id="scanner-status">
|
<!-- Mode Toggle -->
|
||||||
✓ Scanner Ready - Click below and scan a barcode
|
<div class="mode-toggle">
|
||||||
|
<button type="button" class="mode-btn active" id="keyboard-mode-btn">
|
||||||
|
⌨️ Keyboard Input
|
||||||
|
</button>
|
||||||
|
<button type="button" class="mode-btn" id="camera-mode-btn">
|
||||||
|
📷 Camera Scan
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="barcode-input-wrapper">
|
<!-- Error Message -->
|
||||||
<label for="barcode-input">Barcode Input:</label>
|
<div class="error-message" id="error-message"></div>
|
||||||
<input
|
|
||||||
type="text"
|
<!-- Keyboard Input Section -->
|
||||||
id="barcode-input"
|
<div class="keyboard-section" id="keyboard-section">
|
||||||
class="barcode-input"
|
<div class="scanner-status active" id="scanner-status">
|
||||||
placeholder="Scan or type barcode here..."
|
✓ Scanner Ready - Click below and scan a barcode
|
||||||
autocomplete="off"
|
</div>
|
||||||
spellcheck="false"
|
|
||||||
>
|
<div class="barcode-input-wrapper">
|
||||||
|
<label for="barcode-input">Barcode Input:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="barcode-input"
|
||||||
|
class="barcode-input"
|
||||||
|
placeholder="Scan or type barcode here..."
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scanner-info">
|
||||||
|
<h4>How to Use (Keyboard):</h4>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Keyboard Entry:</strong> Type or paste a barcode and press Enter</li>
|
||||||
|
<li><strong>Hardware Scanner:</strong> Plug in a barcode scanner and scan directly</li>
|
||||||
|
<li><strong>Testing:</strong> Scanned barcodes are logged to the browser console (Press F12)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="scanner-info">
|
<!-- Camera Section -->
|
||||||
<h4>How to Use:</h4>
|
<div class="camera-section" id="camera-section">
|
||||||
<ul>
|
<div class="scanner-status" id="camera-status">
|
||||||
<li><strong>Keyboard Entry:</strong> Type or paste a barcode and press Enter</li>
|
📷 Camera Ready - Click "Start Camera" to begin scanning
|
||||||
<li><strong>Hardware Scanner:</strong> Plug in a barcode scanner and scan directly</li>
|
</div>
|
||||||
<li><strong>Testing:</strong> Scanned barcodes are logged to the browser console (Press F12)</li>
|
|
||||||
<li><strong>Input Focus:</strong> The input field automatically refocuses after each scan</li>
|
<video id="video" width="800" height="600"></video>
|
||||||
</ul>
|
|
||||||
|
<div class="camera-controls">
|
||||||
|
<button type="button" class="btn btn-primary" id="start-camera-btn">
|
||||||
|
▶️ Start Camera
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-danger" id="stop-camera-btn" style="display: none;">
|
||||||
|
⏹️ Stop Camera
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="detected-barcode-display" class="detected-barcode">
|
||||||
|
<div style="font-size: 12px; color: #666; margin-bottom: 5px;">Detected Barcode:</div>
|
||||||
|
<div class="detected-barcode-value" id="detected-barcode-value">-</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scanner-info">
|
||||||
|
<h4>How to Use (Camera):</h4>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Permission:</strong> Browser will request camera access on first use</li>
|
||||||
|
<li><strong>Position:</strong> Hold barcode in front of camera lens</li>
|
||||||
|
<li><strong>Detection:</strong> Barcode is detected automatically in real-time</li>
|
||||||
|
<li><strong>Supported:</strong> UPC, EAN, Code-128, Code-39, and more</li>
|
||||||
|
<li><strong>Console:</strong> All detections logged to console (Press F12)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="console-hint">
|
<div class="console-hint">
|
||||||
@@ -240,31 +417,183 @@
|
|||||||
<script type="module">
|
<script type="module">
|
||||||
import { initializeBarcodeScanner, logBarcodeToConsole } from './barcode-scanner.js';
|
import { initializeBarcodeScanner, logBarcodeToConsole } from './barcode-scanner.js';
|
||||||
|
|
||||||
|
const keyboardModeBtn = document.getElementById('keyboard-mode-btn');
|
||||||
|
const cameraModeBtn = document.getElementById('camera-mode-btn');
|
||||||
|
const keyboardSection = document.getElementById('keyboard-section');
|
||||||
|
const cameraSection = document.getElementById('camera-section');
|
||||||
const barcodeInput = document.getElementById('barcode-input');
|
const barcodeInput = document.getElementById('barcode-input');
|
||||||
const scannerStatus = document.getElementById('scanner-status');
|
const scannerStatus = document.getElementById('scanner-status');
|
||||||
|
const cameraStatus = document.getElementById('camera-status');
|
||||||
|
const video = document.getElementById('video');
|
||||||
|
const startCameraBtn = document.getElementById('start-camera-btn');
|
||||||
|
const stopCameraBtn = document.getElementById('stop-camera-btn');
|
||||||
const openConsoleBtn = document.getElementById('open-console-btn');
|
const openConsoleBtn = document.getElementById('open-console-btn');
|
||||||
const clearInputBtn = document.getElementById('clear-input-btn');
|
const clearInputBtn = document.getElementById('clear-input-btn');
|
||||||
|
const errorMessage = document.getElementById('error-message');
|
||||||
|
const detectedBarcodeDisplay = document.getElementById('detected-barcode-display');
|
||||||
|
const detectedBarcodeValue = document.getElementById('detected-barcode-value');
|
||||||
|
|
||||||
|
let cameraActive = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback when a barcode is scanned
|
* Callback when a barcode is scanned
|
||||||
* @param {string} barcode - The scanned barcode value
|
|
||||||
* @param {string} inputType - The type of input device
|
|
||||||
*/
|
*/
|
||||||
function onBarcodeScanned(barcode, inputType) {
|
function onBarcodeScanned(barcode, inputType) {
|
||||||
// Update status temporarily
|
if (inputType === 'camera') {
|
||||||
const originalStatus = scannerStatus.textContent;
|
// Camera detection feedback
|
||||||
scannerStatus.textContent = `✓ Barcode scanned: ${barcode}`;
|
detectedBarcodeValue.textContent = barcode;
|
||||||
scannerStatus.style.backgroundColor = '#a5d6a7';
|
detectedBarcodeDisplay.classList.add('show');
|
||||||
|
cameraStatus.textContent = `✓ Barcode detected: ${barcode}`;
|
||||||
// Reset after 2 seconds
|
cameraStatus.style.backgroundColor = '#a5d6a7';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
scannerStatus.textContent = originalStatus;
|
cameraStatus.textContent = '📷 Scanning...';
|
||||||
scannerStatus.style.backgroundColor = '';
|
cameraStatus.style.backgroundColor = '';
|
||||||
}, 2000);
|
detectedBarcodeDisplay.classList.remove('show');
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
// Keyboard detection feedback
|
||||||
|
const originalStatus = scannerStatus.textContent;
|
||||||
|
scannerStatus.textContent = `✓ Barcode scanned: ${barcode}`;
|
||||||
|
scannerStatus.style.backgroundColor = '#a5d6a7';
|
||||||
|
setTimeout(() => {
|
||||||
|
scannerStatus.textContent = originalStatus;
|
||||||
|
scannerStatus.style.backgroundColor = '';
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the barcode scanner
|
/**
|
||||||
initializeBarcodeScanner(barcodeInput, onBarcodeScanned);
|
* Switch to keyboard mode
|
||||||
|
*/
|
||||||
|
function switchToKeyboardMode() {
|
||||||
|
keyboardModeBtn.classList.add('active');
|
||||||
|
cameraModeBtn.classList.remove('active');
|
||||||
|
keyboardSection.classList.remove('hidden');
|
||||||
|
cameraSection.classList.remove('active');
|
||||||
|
|
||||||
|
if (cameraActive) {
|
||||||
|
stopCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
barcodeInput.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch to camera mode
|
||||||
|
*/
|
||||||
|
function switchToCameraMode() {
|
||||||
|
cameraModeBtn.classList.add('active');
|
||||||
|
keyboardModeBtn.classList.remove('active');
|
||||||
|
cameraSection.classList.add('active');
|
||||||
|
keyboardSection.classList.add('hidden');
|
||||||
|
errorMessage.classList.remove('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start camera and barcode detection
|
||||||
|
*/
|
||||||
|
async function startCamera() {
|
||||||
|
try {
|
||||||
|
cameraActive = true;
|
||||||
|
startCameraBtn.style.display = 'none';
|
||||||
|
stopCameraBtn.style.display = 'block';
|
||||||
|
cameraStatus.textContent = '📷 Scanning...';
|
||||||
|
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
|
video: { facingMode: 'environment', width: { ideal: 800 }, height: { ideal: 600 } }
|
||||||
|
});
|
||||||
|
|
||||||
|
video.srcObject = stream;
|
||||||
|
video.setAttribute('playsinline', 'true');
|
||||||
|
|
||||||
|
// Wait for video to load
|
||||||
|
await new Promise(resolve => {
|
||||||
|
video.onloadedmetadata = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
video.play();
|
||||||
|
|
||||||
|
// Initialize Quagga barcode detection
|
||||||
|
Quagga.init({
|
||||||
|
inputStream: {
|
||||||
|
name: 'Live',
|
||||||
|
type: 'LiveStream',
|
||||||
|
target: '#video',
|
||||||
|
constraints: { width: 800, height: 600, facingMode: 'environment' }
|
||||||
|
},
|
||||||
|
decoder: {
|
||||||
|
readers: ['ean_reader', 'ean_8_reader', 'upc_reader', 'upc_e_reader', 'code_128_reader', 'code_39_reader']
|
||||||
|
}
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
showError('Camera initialization error: ' + err.message);
|
||||||
|
stopCamera();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Quagga.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle barcode detection
|
||||||
|
Quagga.onDetected(function(result) {
|
||||||
|
if (result.codeResult) {
|
||||||
|
const barcode = result.codeResult.code;
|
||||||
|
logBarcodeToConsole(barcode, 'camera');
|
||||||
|
onBarcodeScanned(barcode, 'camera');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
showError('Camera access denied or not available: ' + error.message);
|
||||||
|
stopCamera();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop camera and barcode detection
|
||||||
|
*/
|
||||||
|
function stopCamera() {
|
||||||
|
cameraActive = false;
|
||||||
|
startCameraBtn.style.display = 'block';
|
||||||
|
stopCameraBtn.style.display = 'none';
|
||||||
|
cameraStatus.textContent = '📷 Camera stopped';
|
||||||
|
|
||||||
|
if (Quagga.initialized) {
|
||||||
|
Quagga.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video.srcObject) {
|
||||||
|
video.srcObject.getTracks().forEach(track => track.stop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show error message
|
||||||
|
*/
|
||||||
|
function showError(message) {
|
||||||
|
errorMessage.textContent = '❌ ' + message;
|
||||||
|
errorMessage.classList.add('show');
|
||||||
|
setTimeout(() => {
|
||||||
|
errorMessage.classList.remove('show');
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keyboard input callback
|
||||||
|
*/
|
||||||
|
function onKeyboardBarcodeScanned(barcode, inputType) {
|
||||||
|
onBarcodeScanned(barcode, inputType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize keyboard barcode scanner
|
||||||
|
initializeBarcodeScanner(barcodeInput, onKeyboardBarcodeScanned);
|
||||||
|
|
||||||
|
// Mode toggle event listeners
|
||||||
|
keyboardModeBtn.addEventListener('click', switchToKeyboardMode);
|
||||||
|
cameraModeBtn.addEventListener('click', switchToCameraMode);
|
||||||
|
|
||||||
|
// Camera control event listeners
|
||||||
|
startCameraBtn.addEventListener('click', startCamera);
|
||||||
|
stopCameraBtn.addEventListener('click', stopCamera);
|
||||||
|
|
||||||
// Clear input button
|
// Clear input button
|
||||||
clearInputBtn.addEventListener('click', () => {
|
clearInputBtn.addEventListener('click', () => {
|
||||||
@@ -272,11 +601,9 @@
|
|||||||
barcodeInput.focus();
|
barcodeInput.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Open console button - opens DevTools
|
// Open console button
|
||||||
openConsoleBtn.addEventListener('click', () => {
|
openConsoleBtn.addEventListener('click', () => {
|
||||||
console.log('%c📊 Barcode Scanner Console\n\nUse this console to view scanned barcodes and their details.\nStart scanning to see logs appear here.', 'color: #4CAF50; font-size: 14px; font-weight: bold; line-height: 1.8;');
|
console.log('%c📊 Barcode Scanner Console\n\nUse this console to view scanned barcodes and their details.\nStart scanning to see logs appear here.', 'color: #4CAF50; font-size: 14px; font-weight: bold; line-height: 1.8;');
|
||||||
// In a real app, we can't programmatically open DevTools for security reasons
|
|
||||||
// But we can log a helpful message and suggest the user press F12
|
|
||||||
alert('Press F12 (or Cmd+Option+I on Mac) to open the Developer Tools Console.\n\nScanned barcodes will appear in the Console tab.');
|
alert('Press F12 (or Cmd+Option+I on Mac) to open the Developer Tools Console.\n\nScanned barcodes will appear in the Console tab.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user