Linked config-view to scanner

feature/109
Christoph Oberhofer 9 years ago
parent 7236bb7b53
commit bad79020a9

@ -12,11 +12,50 @@ import Scanner from './Scanner';
import ScanIcon from './ScanIcon'; import ScanIcon from './ScanIcon';
import ConfigView from './ConfigView'; 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 { export default class App extends React.Component {
state = { state = {
drawerOpen: false, drawerOpen: false,
scanning: false, scanning: false,
currentView: 'root', 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: [{ scannedCodes: [{
codeResult: { codeResult: {
code: "FANAVF1461710", code: "FANAVF1461710",
@ -59,6 +98,11 @@ export default class App extends React.Component {
}); });
} }
_handleConfigChange = config => {
this.setState({config: cleanConfig(config)});
console.log(config);
}
render() { render() {
return ( return (
<div> <div>
@ -67,7 +111,7 @@ export default class App extends React.Component {
open={this.state.drawerOpen} open={this.state.drawerOpen}
onRequestChange={this._onRequestChange} onRequestChange={this._onRequestChange}
> >
<ConfigView /> <ConfigView config={this.state.config} onChange={this._handleConfigChange} />
</Drawer> </Drawer>
<AppBar <AppBar
style={{position: 'fixed', top: '0px'}} style={{position: 'fixed', top: '0px'}}
@ -96,7 +140,10 @@ export default class App extends React.Component {
contentStyle={{width: '95%', maxWidth: '95%', height: '95%', maxHeight: '95%'}} contentStyle={{width: '95%', maxWidth: '95%', height: '95%', maxHeight: '95%'}}
open={this.state.scanning} open={this.state.scanning}
> >
<Scanner onDetected={this._handleResult} onCancel={this._stopScanning} /> <Scanner
config={this.state.config}
onDetected={this._handleResult}
onCancel={this._stopScanning} />
</Dialog> </Dialog>
<div style={{paddingTop: '64px'}}> <div style={{paddingTop: '64px'}}>
{this.state.currentView === 'root' && this.state.scannedCodes.map((scannedCode, i) => ( {this.state.currentView === 'root' && this.state.scannedCodes.map((scannedCode, i) => (

@ -2,22 +2,31 @@ import React from 'react';
export default (Component) => (class ConfigOption extends React.Component { export default (Component) => (class ConfigOption extends React.Component {
static propTypes = { static propTypes = {
option: React.PropTypes.string.isRequired, path: React.PropTypes.array.isRequired,
onChange: React.PropTypes.func, onChange: React.PropTypes.func,
onToggle: React.PropTypes.func,
}; };
_handleChange = (event, key, payload) => { _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() { render() {
const { const {
option, path,
onChange, onChange,
onToggle,
...rest, ...rest,
} = this.props; } = this.props;
return ( if (onChange) {
<Component {...rest} onChange={this._handleChange} /> return <Component {...rest} onChange={this._handleChange} />;
); } else if (onToggle) {
return <Component {...rest} onToggle={this._handleToggle} />;
}
return null;
} }
}); });

@ -6,68 +6,116 @@ import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem'; import MenuItem from 'material-ui/MenuItem';
import createConfigOption from './ConfigOption'; import createConfigOption from './ConfigOption';
function generateItems(keyValuePairs) { let configSections = [{
return keyValuePairs.map(([value, label]) => ( name: "Constraints",
<MenuItem key={value} value={value} primaryText={label} /> path: ["inputStream", "constraints"],
)); options: {
}
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"]], width: [[640, "640px"], [800, "800px"], [1280, "1280px"], [1920, "1920px"]],
height: [[480, "480px"], [600, "600px"], [720, "720px"], [1080, "1080px"]], height: [[480, "480px"], [600, "600px"], [720, "720px"], [1080, "1080px"]],
facingMode: [["user", "user"], ["environment", "environment"]], facingMode: [["user", "user"], ["environment", "environment"]],
aspectRatio: [[1, '1/1'], [4 / 3, '4/3'], [16 / 9, '16/9'], [21 / 9, '21/9']], aspectRatio: [[1, '1/1'], [4 / 3, '4/3'], [16 / 9, '16/9'], [21 / 9, '21/9']],
deviceId: [], deviceId: [],
}; },
}, {
const generalOptions = { 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"], frequency: [[0, "full"], [1, "1Hz"], [2, "2Hz"], [5, "5Hz"],
[10, "10Hz"], [15, "15Hz"], [20, "20Hz"]], [10, "10Hz"], [15, "15Hz"], [20, "20Hz"]],
numOfWorkers: [[0, "0"], [1, "1"], [2, "2"], [4, "4"], [8, "8"]], },
}; }, {
name: "Reader",
let items = Object.keys(configOptions).reduce((result, option) => ({ path: ["decoder", "readers"],
...result, options: {
[option]: generateItems(configOptions[option]), ean_reader: false,
}), {}); ean_8_reader: false,
upc_e_reader: false,
let generalItems = Object.keys(generalOptions).reduce((result, option) => ({ code_39_reader: false,
...result, codabar_reader: false,
[option]: generateItems(generalOptions[option]), 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 selectStyle = {
}; };
const selectedItemStyle = { const selectedItemStyle = {
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
overflow: 'hidden', 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 SelectConfigOption = createConfigOption(SelectField);
const ToggleConfigOption = createConfigOption(Toggle);
export default class ConfigView extends React.Component { export default class ConfigView extends React.Component {
static propTypes = {
onChange: React.PropTypes.func,
config: React.PropTypes.object.isRequired,
};
state = { state = {
devicesFetched: false, devicesFetched: false,
numOfWorkers: 2,
frequency: 0,
...Object
.keys(configOptions)
.reduce((result, option) => ({...result, [option]: (configOptions[option][0] || [null])[0]}), {}),
}; };
handleChange = (event, index, value, category) => { handleChange = (event, index, value, path) => {
this.setState({[category]: value}); const newState = reducer(this.props.config, path, value);
this.props.onChange(newState);
}
handleToggle = (event, value, path) => {
return this.handleChange(event, 0, value, path);
} }
componentWillMount() { componentWillMount() {
@ -80,68 +128,66 @@ export default class ConfigView extends React.Component {
videoDevice.deviceId, videoDevice.deviceId,
videoDevice.label, videoDevice.label,
])); ]));
configOptions = { const constraints = configSections
...configOptions, .map((section, index) => (section.name === "Constraints" ? {section, index} : null))
deviceId: videoDevices, .filter(sectionIndex => !!sectionIndex)[0];
}; configSections = configSections.slice();
items = { constraints.section = {
...items, ...constraints.section,
deviceId: generateItems(configOptions.deviceId), options: {
...constraints.section.options,
deviceId: [[0, "no preference"]].concat(videoDevices),
},
}; };
configSections[constraints.index] = constraints.section;
this.setState({devicesFetched: true}); this.setState({devicesFetched: true});
}); });
} }
render() { render() {
return (<div>{
configSections.map(section => (
<List>
<Subheader>{section.name}</Subheader>
{Object.keys(section.options).map(option => {
const path = section.path.concat([option]);
if (typeof section.options[option] === 'boolean') {
return ( return (
<div> <ListItem
<Subheader>Constraints</Subheader>
<div style={{paddingLeft: 16, paddingRight: 16}}>
{Object.keys(items).map(option => (
<SelectConfigOption
fullWidth={true}
key={option} key={option}
option={option} path={path}
style={selectStyle} primaryText={option}
labelStyle={selectedItemStyle} rightToggle={
value={this.state[option]} <ToggleConfigOption
onChange={this.handleChange} onToggle={this.handleToggle}
floatingLabelText={option} path={path}
> toggled={!!getConfigByPath(this.props, path)} />
{items[option]} }
</SelectConfigOption> />
))} );
</div> } else {
<List> return (
<Subheader>General</Subheader>
<ListItem primaryText="locate" secondaryText="Detect Location" rightToggle={<Toggle />} />
<div style={{paddingLeft: 16, paddingRight: 16}}> <div style={{paddingLeft: 16, paddingRight: 16}}>
{Object.keys(generalItems).map(option => (
<SelectConfigOption <SelectConfigOption
fullWidth={true} fullWidth={true}
key={option} key={option}
option={option} path={path}
style={selectStyle} style={selectStyle}
labelStyle={selectedItemStyle} labelStyle={selectedItemStyle}
value={this.state[option]} value={getConfigByPath(this.props, path)}
onChange={this.handleChange} onChange={this.handleChange}
floatingLabelText={option} floatingLabelText={option}
> >
{generalItems[option]} {section.options[option].map(([value, label]) => (
</SelectConfigOption> <MenuItem key={value} value={value} primaryText={label} />
))}
</div>
</List>
<List>
<Subheader>Readers</Subheader>
{availableReaders.map(([reader, label, enabled]) => (
<ListItem
key={reader}
primaryText={label}
rightToggle={<Toggle defaultToggled={enabled ? enabled : false} />}
/>
))} ))}
</List> </SelectConfigOption>
</div> </div>
); );
} }
})}
</List>
))
}</div>);
}
}; };

@ -5,21 +5,16 @@ export default class Scanner extends React.Component {
static propTypes = { static propTypes = {
onDetected: React.PropTypes.func, onDetected: React.PropTypes.func,
onCancel: React.PropTypes.func, onCancel: React.PropTypes.func,
config: React.PropTypes.object.isRequired,
}; };
constructor(props) { constructor(props) {
super(props); super(props);
this._scanner = Quagga this._scanner = Quagga
.decoder({readers: ['code_128_reader']}) .config(props.config)
.locator({patchSize: 'medium'})
.throttle(500)
.fromSource({ .fromSource({
...this.props.config.inputStream,
target: '.overlay__content', target: '.overlay__content',
constraints: {
width: 800,
height: 600,
facingMode: "environment",
},
}); });
} }

@ -1,4 +1,4 @@
import {merge, pick} from 'lodash'; import {merge, pick, omit} from 'lodash';
var streamRef; var streamRef;
@ -64,7 +64,14 @@ function deprecatedConstraints(videoConstraints) {
} }
function applyCameraFacing(facing, constraints) { 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); return Promise.resolve(constraints);
} }
if ( typeof MediaStreamTrack !== 'undefined' && if ( typeof MediaStreamTrack !== 'undefined' &&

@ -8,8 +8,7 @@ import Config from './config/config';
import {merge} from 'lodash'; import {merge} from 'lodash';
import {createConfigFromSource} from './input/config_factory'; import {createConfigFromSource} from './input/config_factory';
function fromSource(config, source, inputConfig = {}) { function fromConfig(config) {
config = createConfigFromSource(config, inputConfig, source);
const scanner = createScanner(); const scanner = createScanner();
return { return {
addEventListener(eventType, cb) { 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(); const defaultScanner = createScanner();
function setConfig(configuration = {}, key, config = {}) { function setConfig(configuration = {}, key, config = {}) {
@ -85,6 +89,9 @@ function createApi(configuration = Config) {
fromSource(src, inputConfig) { fromSource(src, inputConfig) {
return fromSource(configuration, src, inputConfig); return fromSource(configuration, src, inputConfig);
}, },
fromConfig(conf) {
return createApi(merge({}, configuration, conf));
},
decoder(conf) { decoder(conf) {
return setConfig(configuration, "decoder", conf); return setConfig(configuration, "decoder", conf);
}, },

@ -296,7 +296,7 @@ function createScanner() {
function startContinuousUpdate() { function startContinuousUpdate() {
var next = null, var next = null,
delay = 1000 / (_config.frequency || 60); delay = 1000 / (_config.frequency === 0 ? 60 : (_config.frequency || 60));
_stopped = false; _stopped = false;
(function frame(timestamp) { (function frame(timestamp) {

Loading…
Cancel
Save