From bad79020a9a51f4d3dd329c3f9563d65bf4e4638 Mon Sep 17 00:00:00 2001 From: Christoph Oberhofer Date: Mon, 11 Jul 2016 11:31:13 +0200 Subject: [PATCH] Linked config-view to scanner --- example/sandbox/src/components/App.js | 51 +++- .../sandbox/src/components/ConfigOption.js | 21 +- example/sandbox/src/components/ConfigView.js | 254 +++++++++++------- example/sandbox/src/components/Scanner.js | 11 +- src/input/camera_access.js | 11 +- src/quagga.js | 11 +- src/scanner.js | 2 +- 7 files changed, 236 insertions(+), 125 deletions(-) diff --git a/example/sandbox/src/components/App.js b/example/sandbox/src/components/App.js index 6c2cd3c..e66b204 100644 --- a/example/sandbox/src/components/App.js +++ b/example/sandbox/src/components/App.js @@ -12,11 +12,50 @@ import Scanner from './Scanner'; import ScanIcon from './ScanIcon'; import ConfigView from './ConfigView'; +const cleanConfig = config => { + if (typeof config.inputStream.constraints.deviceId === 'number') { + config.inputStream.constraints.deviceId = null; + } + return config; +}; + export default class App extends React.Component { state = { drawerOpen: false, scanning: false, currentView: 'root', + config: { + frequency: 5, + numOfWorkers: 2, + locate: true, + inputStream: { + name: "Live", + type: "LiveStream", + constraints: { + width: 640, + height: 480, + deviceId: 0, + facingMode: "environment", + }, + area: { + top: "0%", + right: "0%", + left: "0%", + bottom: "0%", + }, + }, + decoder: { + readers: [ + 'ean_reader', + 'code_39_reader', + 'code_128_reader', + ], + }, + locator: { + halfSample: true, + patchSize: "medium", + }, + }, scannedCodes: [{ codeResult: { code: "FANAVF1461710", @@ -59,6 +98,11 @@ export default class App extends React.Component { }); } + _handleConfigChange = config => { + this.setState({config: cleanConfig(config)}); + console.log(config); + } + render() { return (
@@ -67,7 +111,7 @@ export default class App extends React.Component { open={this.state.drawerOpen} onRequestChange={this._onRequestChange} > - + - +
{this.state.currentView === 'root' && this.state.scannedCodes.map((scannedCode, i) => ( diff --git a/example/sandbox/src/components/ConfigOption.js b/example/sandbox/src/components/ConfigOption.js index caedfe9..eac5a78 100644 --- a/example/sandbox/src/components/ConfigOption.js +++ b/example/sandbox/src/components/ConfigOption.js @@ -2,22 +2,31 @@ import React from 'react'; export default (Component) => (class ConfigOption extends React.Component { static propTypes = { - option: React.PropTypes.string.isRequired, + path: React.PropTypes.array.isRequired, onChange: React.PropTypes.func, + onToggle: React.PropTypes.func, }; _handleChange = (event, key, payload) => { - this.props.onChange(event, key, payload, this.props.option); + this.props.onChange(event, key, payload, this.props.path); + } + + _handleToggle = (event, value) => { + this.props.onToggle(event, value, this.props.path); } render() { const { - option, + path, onChange, + onToggle, ...rest, } = this.props; - return ( - - ); + if (onChange) { + return ; + } else if (onToggle) { + return ; + } + return null; } }); diff --git a/example/sandbox/src/components/ConfigView.js b/example/sandbox/src/components/ConfigView.js index 48c75fc..3452fb6 100644 --- a/example/sandbox/src/components/ConfigView.js +++ b/example/sandbox/src/components/ConfigView.js @@ -6,68 +6,116 @@ import SelectField from 'material-ui/SelectField'; import MenuItem from 'material-ui/MenuItem'; import createConfigOption from './ConfigOption'; -function generateItems(keyValuePairs) { - return keyValuePairs.map(([value, label]) => ( - - )); -} - -const availableReaders = [ - ['ean_reader', "EAN-13", true], - ['ean_8_reader', "EAN-8"], - ["upc_e_reader", "UPC-E"], - ["code_39_reader", "CODE 39", true], - ["codabar_reader", "Codabar"], - ["code_128_reader", "CODE 128", true], - ["i2of5_reader", "ITF", true], -]; - -let configOptions = { - width: [[640, "640px"], [800, "800px"], [1280, "1280px"], [1920, "1920px"]], - height: [[480, "480px"], [600, "600px"], [720, "720px"], [1080, "1080px"]], - facingMode: [["user", "user"], ["environment", "environment"]], - aspectRatio: [[1, '1/1'], [4 / 3, '4/3'], [16 / 9, '16/9'], [21 / 9, '21/9']], - deviceId: [], -}; - -const generalOptions = { - frequency: [[0, "full"], [1, "1Hz"], [2, "2Hz"], [5, "5Hz"], - [10, "10Hz"], [15, "15Hz"], [20, "20Hz"]], - numOfWorkers: [[0, "0"], [1, "1"], [2, "2"], [4, "4"], [8, "8"]], -}; - -let items = Object.keys(configOptions).reduce((result, option) => ({ - ...result, - [option]: generateItems(configOptions[option]), -}), {}); - -let generalItems = Object.keys(generalOptions).reduce((result, option) => ({ - ...result, - [option]: generateItems(generalOptions[option]), -}), {}); +let configSections = [{ + name: "Constraints", + path: ["inputStream", "constraints"], + options: { + width: [[640, "640px"], [800, "800px"], [1280, "1280px"], [1920, "1920px"]], + height: [[480, "480px"], [600, "600px"], [720, "720px"], [1080, "1080px"]], + facingMode: [["user", "user"], ["environment", "environment"]], + aspectRatio: [[1, '1/1'], [4 / 3, '4/3'], [16 / 9, '16/9'], [21 / 9, '21/9']], + deviceId: [], + }, +}, { + name: "General", + path: [], + options: { + locate: true, + numOfWorkers: [[0, "0"], [1, "1"], [2, "2"], [4, "4"], [8, "8"]], + frequency: [[0, "full"], [1, "1Hz"], [2, "2Hz"], [5, "5Hz"], + [10, "10Hz"], [15, "15Hz"], [20, "20Hz"]], + }, +}, { + name: "Reader", + path: ["decoder", "readers"], + options: { + ean_reader: false, + ean_8_reader: false, + upc_e_reader: false, + code_39_reader: false, + codabar_reader: false, + code_128_reader: false, + i2of5_reader: false, + }, +}, { + name: "Locator", + path: ["locator"], + options: { + halfSample: true, + patchSize: [ + ["x-small", "x-small"], + ["small", "small"], + ["medium", "medium"], + ["large", "large"], + ["x-large", "x-large"], + ], + }, +}]; const selectStyle = { }; + const selectedItemStyle = { whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden', }; +const reducer = function reduce(state, path, value) { + if (path.length === 1 && typeof value === 'boolean' && Array.isArray(state)) { + const index = state.indexOf(path[0]); + if (index !== -1 && value || index === -1 && !value) { + return state; + } else if (index !== -1 && !value) { + const newArray = state.slice(); + newArray.splice(index, 1); + return newArray; + } else { + return state.concat([path[0]]); + } + } + if (path.length === 0) { + return value; + } + const [part, ...rest] = path; + return Object.assign({}, state, {[part]: reduce(state[part], rest, value)}); +}; + +const get = function get(object, path) { + if (Array.isArray(object) && (typeof path[0] === 'string')) { + return object.indexOf(path[0]) !== -1; + } + if (path.length === 0) { + return object; + } + const [part, ...rest] = path; + return get(object[part], rest); +}; + +const getConfigByPath = function(object, path) { + return get(object, ['config'].concat(path)); +}; + const SelectConfigOption = createConfigOption(SelectField); +const ToggleConfigOption = createConfigOption(Toggle); export default class ConfigView extends React.Component { + static propTypes = { + onChange: React.PropTypes.func, + config: React.PropTypes.object.isRequired, + }; + state = { devicesFetched: false, - numOfWorkers: 2, - frequency: 0, - ...Object - .keys(configOptions) - .reduce((result, option) => ({...result, [option]: (configOptions[option][0] || [null])[0]}), {}), }; - handleChange = (event, index, value, category) => { - this.setState({[category]: value}); + handleChange = (event, index, value, path) => { + const newState = reducer(this.props.config, path, value); + this.props.onChange(newState); + } + + handleToggle = (event, value, path) => { + return this.handleChange(event, 0, value, path); } componentWillMount() { @@ -80,68 +128,66 @@ export default class ConfigView extends React.Component { videoDevice.deviceId, videoDevice.label, ])); - configOptions = { - ...configOptions, - deviceId: videoDevices, - }; - items = { - ...items, - deviceId: generateItems(configOptions.deviceId), + const constraints = configSections + .map((section, index) => (section.name === "Constraints" ? {section, index} : null)) + .filter(sectionIndex => !!sectionIndex)[0]; + configSections = configSections.slice(); + constraints.section = { + ...constraints.section, + options: { + ...constraints.section.options, + deviceId: [[0, "no preference"]].concat(videoDevices), + }, }; + configSections[constraints.index] = constraints.section; this.setState({devicesFetched: true}); }); } + render() { - return ( -
- Constraints -
- {Object.keys(items).map(option => ( - - {items[option]} - - ))} -
- - General - } /> -
- {Object.keys(generalItems).map(option => ( - - {generalItems[option]} - - ))} -
-
- - Readers - {availableReaders.map(([reader, label, enabled]) => ( - } - /> - ))} - -
- ); + return (
{ + configSections.map(section => ( + + {section.name} + {Object.keys(section.options).map(option => { + const path = section.path.concat([option]); + if (typeof section.options[option] === 'boolean') { + return ( + + } + /> + ); + } else { + return ( +
+ + {section.options[option].map(([value, label]) => ( + + ))} + +
+ ); + } + })} +
+ )) + }
); } }; diff --git a/example/sandbox/src/components/Scanner.js b/example/sandbox/src/components/Scanner.js index 67ede1a..0c40f75 100644 --- a/example/sandbox/src/components/Scanner.js +++ b/example/sandbox/src/components/Scanner.js @@ -5,21 +5,16 @@ export default class Scanner extends React.Component { static propTypes = { onDetected: React.PropTypes.func, onCancel: React.PropTypes.func, + config: React.PropTypes.object.isRequired, }; constructor(props) { super(props); this._scanner = Quagga - .decoder({readers: ['code_128_reader']}) - .locator({patchSize: 'medium'}) - .throttle(500) + .config(props.config) .fromSource({ + ...this.props.config.inputStream, target: '.overlay__content', - constraints: { - width: 800, - height: 600, - facingMode: "environment", - }, }); } diff --git a/src/input/camera_access.js b/src/input/camera_access.js index 6f8149d..881cf24 100644 --- a/src/input/camera_access.js +++ b/src/input/camera_access.js @@ -1,4 +1,4 @@ -import {merge, pick} from 'lodash'; +import {merge, pick, omit} from 'lodash'; var streamRef; @@ -64,7 +64,14 @@ function deprecatedConstraints(videoConstraints) { } function applyCameraFacing(facing, constraints) { - if (typeof constraints.video.deviceId !== 'undefined' || !facing){ + if (typeof constraints.video.deviceId === 'string' && constraints.video.deviceId.length > 0) { + return Promise.resolve({ + ...constraints, + video: { + ...omit(constraints.video, "facingMode") + } + }); + } else if (!facing) { return Promise.resolve(constraints); } if ( typeof MediaStreamTrack !== 'undefined' && diff --git a/src/quagga.js b/src/quagga.js index bd65413..eaec652 100644 --- a/src/quagga.js +++ b/src/quagga.js @@ -8,8 +8,7 @@ import Config from './config/config'; import {merge} from 'lodash'; import {createConfigFromSource} from './input/config_factory'; -function fromSource(config, source, inputConfig = {}) { - config = createConfigFromSource(config, inputConfig, source); +function fromConfig(config) { const scanner = createScanner(); return { addEventListener(eventType, cb) { @@ -73,6 +72,11 @@ function fromSource(config, source, inputConfig = {}) { }; } +function fromSource(config, source, inputConfig = {}) { + config = createConfigFromSource(config, inputConfig, source); + return fromConfig(config); +} + const defaultScanner = createScanner(); function setConfig(configuration = {}, key, config = {}) { @@ -85,6 +89,9 @@ function createApi(configuration = Config) { fromSource(src, inputConfig) { return fromSource(configuration, src, inputConfig); }, + fromConfig(conf) { + return createApi(merge({}, configuration, conf)); + }, decoder(conf) { return setConfig(configuration, "decoder", conf); }, diff --git a/src/scanner.js b/src/scanner.js index bb2d9fd..dc4fe0c 100644 --- a/src/scanner.js +++ b/src/scanner.js @@ -296,7 +296,7 @@ function createScanner() { function startContinuousUpdate() { var next = null, - delay = 1000 / (_config.frequency || 60); + delay = 1000 / (_config.frequency === 0 ? 60 : (_config.frequency || 60)); _stopped = false; (function frame(timestamp) {