mirror of
https://github.com/serratus/quaggaJS.git
synced 2026-08-12 21:21:42 +08:00
Linked config-view to scanner
This commit is contained in:
@@ -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 (
|
||||
<div>
|
||||
@@ -67,7 +111,7 @@ export default class App extends React.Component {
|
||||
open={this.state.drawerOpen}
|
||||
onRequestChange={this._onRequestChange}
|
||||
>
|
||||
<ConfigView />
|
||||
<ConfigView config={this.state.config} onChange={this._handleConfigChange} />
|
||||
</Drawer>
|
||||
<AppBar
|
||||
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%'}}
|
||||
open={this.state.scanning}
|
||||
>
|
||||
<Scanner onDetected={this._handleResult} onCancel={this._stopScanning} />
|
||||
<Scanner
|
||||
config={this.state.config}
|
||||
onDetected={this._handleResult}
|
||||
onCancel={this._stopScanning} />
|
||||
</Dialog>
|
||||
<div style={{paddingTop: '64px'}}>
|
||||
{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 {
|
||||
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 (
|
||||
<Component {...rest} onChange={this._handleChange} />
|
||||
);
|
||||
if (onChange) {
|
||||
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 createConfigOption from './ConfigOption';
|
||||
|
||||
function generateItems(keyValuePairs) {
|
||||
return keyValuePairs.map(([value, label]) => (
|
||||
<MenuItem key={value} value={value} primaryText={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 = {
|
||||
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: [],
|
||||
};
|
||||
|
||||
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"],
|
||||
[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]),
|
||||
}), {});
|
||||
},
|
||||
}, {
|
||||
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 {
|
||||
state = {
|
||||
devicesFetched: false,
|
||||
numOfWorkers: 2,
|
||||
frequency: 0,
|
||||
...Object
|
||||
.keys(configOptions)
|
||||
.reduce((result, option) => ({...result, [option]: (configOptions[option][0] || [null])[0]}), {}),
|
||||
static propTypes = {
|
||||
onChange: React.PropTypes.func,
|
||||
config: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleChange = (event, index, value, category) => {
|
||||
this.setState({[category]: value});
|
||||
state = {
|
||||
devicesFetched: false,
|
||||
};
|
||||
|
||||
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 (<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 (
|
||||
<div>
|
||||
<Subheader>Constraints</Subheader>
|
||||
<div style={{paddingLeft: 16, paddingRight: 16}}>
|
||||
{Object.keys(items).map(option => (
|
||||
<SelectConfigOption
|
||||
fullWidth={true}
|
||||
key={option}
|
||||
option={option}
|
||||
style={selectStyle}
|
||||
labelStyle={selectedItemStyle}
|
||||
value={this.state[option]}
|
||||
onChange={this.handleChange}
|
||||
floatingLabelText={option}
|
||||
>
|
||||
{items[option]}
|
||||
</SelectConfigOption>
|
||||
))}
|
||||
</div>
|
||||
<List>
|
||||
<Subheader>General</Subheader>
|
||||
<ListItem primaryText="locate" secondaryText="Detect Location" rightToggle={<Toggle />} />
|
||||
<div style={{paddingLeft: 16, paddingRight: 16}}>
|
||||
{Object.keys(generalItems).map(option => (
|
||||
<SelectConfigOption
|
||||
fullWidth={true}
|
||||
key={option}
|
||||
option={option}
|
||||
style={selectStyle}
|
||||
labelStyle={selectedItemStyle}
|
||||
value={this.state[option]}
|
||||
onChange={this.handleChange}
|
||||
floatingLabelText={option}
|
||||
>
|
||||
{generalItems[option]}
|
||||
</SelectConfigOption>
|
||||
))}
|
||||
</div>
|
||||
</List>
|
||||
<List>
|
||||
<Subheader>Readers</Subheader>
|
||||
{availableReaders.map(([reader, label, enabled]) => (
|
||||
<ListItem
|
||||
key={reader}
|
||||
primaryText={label}
|
||||
rightToggle={<Toggle defaultToggled={enabled ? enabled : false} />}
|
||||
key={option}
|
||||
path={path}
|
||||
primaryText={option}
|
||||
rightToggle={
|
||||
<ToggleConfigOption
|
||||
onToggle={this.handleToggle}
|
||||
path={path}
|
||||
toggled={!!getConfigByPath(this.props, path)} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div style={{paddingLeft: 16, paddingRight: 16}}>
|
||||
<SelectConfigOption
|
||||
fullWidth={true}
|
||||
key={option}
|
||||
path={path}
|
||||
style={selectStyle}
|
||||
labelStyle={selectedItemStyle}
|
||||
value={getConfigByPath(this.props, path)}
|
||||
onChange={this.handleChange}
|
||||
floatingLabelText={option}
|
||||
>
|
||||
{section.options[option].map(([value, label]) => (
|
||||
<MenuItem key={value} value={value} primaryText={label} />
|
||||
))}
|
||||
</List>
|
||||
</SelectConfigOption>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</List>
|
||||
))
|
||||
}</div>);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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' &&
|
||||
|
||||
+9
-2
@@ -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);
|
||||
},
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user