Skip to content

Commit

Permalink
Merge pull request #215 from noahmalmed/drawing/addExplanationViews
Browse files Browse the repository at this point in the history
Drawing/add explanation views
  • Loading branch information
wgranger authored Nov 1, 2018
2 parents a20923b + 57dfb0b commit 6eaf679
Show file tree
Hide file tree
Showing 14 changed files with 2,301 additions and 2,150 deletions.
4,076 changes: 2,037 additions & 2,039 deletions package-lock.json

Large diffs are not rendered by default.

35 changes: 31 additions & 4 deletions src/components/Markings/DrawingClassifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ import NavBar from '../NavBar'
import * as navBarActions from '../../actions/navBar'
import DrawingModal from './DrawingModal'
import NativeImage from '../../nativeModules/NativeImage'
import ShapeInstructionsView from './components/ShapeInstructionsView';

const mapStateToProps = (state, ownProps) => {
const subjectDimensions = state.classifier.subject ? state.classifier.subjectDimensions[state.classifier.subject.id] : null

return {
isSuccess: state.classifier.isSuccess,
isFailure: state.classifier.isFailure,
Expand All @@ -36,7 +39,9 @@ const mapStateToProps = (state, ownProps) => {
needsTutorial: state.classifier.needsTutorial[ownProps.workflow.id] || false,
subject: state.classifier.subject,
shapes: state.drawing.shapes,
workflowOutOfSubjects: state.classifier.workflowOutOfSubjects
workflowOutOfSubjects: state.classifier.workflowOutOfSubjects,
numberOfShapesDrawn: R.keys(state.drawing.shapesInProgress).length,
subjectDimensions: subjectDimensions ? subjectDimensions : {naturalHeight: 1, naturalWidth: 1},
}
}

Expand All @@ -61,7 +66,9 @@ class DrawingClassifier extends Component {
subjectDimensions: {
clientHeight: 1,
clientWidth: 1
}
},
modalHasBeenClosedOnce: false,
showBlurView: true
}

this.finishTutorial = this.finishTutorial.bind(this)
Expand All @@ -87,6 +94,10 @@ class DrawingClassifier extends Component {

submitClassification() {
this.props.classifierActions.submitDrawingClassification(this.props.workflow, this.props.subject, this.state.subjectDimensions)
this.setState({
modalHasBeenClosedOnce: false,
imageIsLoaded: false
})
}

componentDidUpdate(prevProps) {
Expand Down Expand Up @@ -130,6 +141,8 @@ class DrawingClassifier extends Component {
return <OverlaySpinner overrideVisibility={this.props.isFetching} />
}

const warnForRequirements = this.state.modalHasBeenClosedOnce && R.keys(this.props.shapes).length < this.props.tools[0].min

const tutorial =
<Tutorial
projectName={this.props.project.display_name}
Expand All @@ -145,12 +158,20 @@ class DrawingClassifier extends Component {
workflowID={this.props.workflow.id}
taskHelp={this.props.help}
/>
<ShapeInstructionsView
{ ...this.props.tools[0] }
numberDrawn={this.props.numberOfShapesDrawn}
warnForRequirements={warnForRequirements}
/>
<TouchableOpacity style={styles.container} onPress={() => this.setState({isModalVisible: true})}>
<ImageWithSvgOverlay
shapes={this.props.shapes}
imageIsLoaded={this.state.imageIsLoaded}
uri={this.state.localImagePath}
onImageLayout={this.onImageLayout}
showBlurView={R.isEmpty(this.props.shapes)}
subjectDimensions={this.props.subjectDimensions}
displayToNativeRatio={this.props.subjectDimensions.naturalWidth/this.state.subjectDimensions.clientWidth}
/>
</TouchableOpacity>
</View>
Expand All @@ -168,6 +189,7 @@ class DrawingClassifier extends Component {

const submitButton =
<ClassifierButton
disabled={R.keys(this.props.shapes).length < this.props.tools[0].min}
onPress={this.submitClassification}
style={styles.submitButton}
type="answer"
Expand Down Expand Up @@ -208,8 +230,8 @@ class DrawingClassifier extends Component {
tool={this.props.tools[0]}
visible={this.state.isModalVisible}
imageSource={this.state.localImagePath}
onClose={() => this.setState({isModalVisible: false})}

onClose={() => this.setState({isModalVisible: false, modalHasBeenClosedOnce: true})}
warnForRequirements={this.state.modalHasBeenClosedOnce}
/>
</View>
)
Expand Down Expand Up @@ -301,6 +323,11 @@ DrawingClassifier.propTypes = {
setTitleForPage: PropTypes.func,
setNavbarColorForPage: PropTypes.func,
setNavbarColorForPageToDefault: PropTypes.func
}),
numberOfShapesDrawn: PropTypes.number,
subjectDimensions: PropTypes.shape({
naturalHeight: PropTypes.number,
naturalWidth: PropTypes.number
})
}

Expand Down
9 changes: 7 additions & 2 deletions src/components/Markings/DrawingModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import InstructionView from './components/InstructionView'
import * as drawingActions from '../../actions/drawing'

const mapStateToProps = state => ({
numberOfShapesDrawn: R.keys(state.drawing.shapesInProgress).length,
canUndo: state.drawing.actions.length > 0,
shouldConfirmOnClose: !R.isEmpty(state.drawing.shapes) || !R.isEmpty(state.drawing.shapesInProgress)
})
Expand Down Expand Up @@ -98,6 +99,7 @@ class DrawingModal extends Component {
drawingColor={this.props.tool.color}
source={this.props.imageSource}
mode={this.state.mode}
maxShapesDrawn={this.props.numberOfShapesDrawn >= this.props.tool.max}
/>
<DrawingButtons
onButtonSelected={this.handleDrawingButtonPress}
Expand All @@ -106,9 +108,10 @@ class DrawingModal extends Component {
/>
<InstructionView
{... this.props.tool}
numberDrawn={0}
numberDrawn={this.props.numberOfShapesDrawn}
onCancel={this.onCancel}
onSave={this.onSave}
warnForRequirements={this.props.warnForRequirements && this.props.numberOfShapesDrawn < this.props.tool.min}
/>
</View>
<CloseButton
Expand Down Expand Up @@ -177,7 +180,9 @@ DrawingModal.propTypes = {
saveEdits: PropTypes.func,
clearShapes: PropTypes.func,
undoMostRecentEdit: PropTypes.func
})
}),
warnForRequirements: PropTypes.bool,
numberOfShapesDrawn: PropTypes.number
}

export default connect(mapStateToProps, mapDispatchToProps)(DrawingModal)
148 changes: 99 additions & 49 deletions src/components/Markings/ImageWithSvgOverlay.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { Component } from 'react'
import {
Animated,
Image,
Platform,
View
Expand All @@ -10,47 +11,61 @@ import {
Rect
} from 'react-native-svg'
import R from 'ramda'
import { connect } from 'react-redux'
import { BlurView } from 'react-native-blur';
import Icon from 'react-native-vector-icons/FontAwesome'
import SubjectLoadingIndicator from '../common/SubjectLoadingIndicator';

const mapStateToProps = (state) => {
const subjectDimensions = state.classifier.subjectDimensions[state.classifier.subject.id]
return {
subjectDimensions: subjectDimensions ? subjectDimensions : {naturalHeight: 1, naturalWidth: 1}
}
}

class ImageWithSvgOverlay extends Component {

constructor(props) {
super(props)
this.count = 0

this.state = {
imageIsLoaded: false,
imageNativeWidth: 1,
imageNativeHeight: 1,
clientWidth: 1,
clientHeight: 1,
scale: new Animated.Value(1),
containerDimensions: {
width: 1,
height: 1
},
}

this.onImageLayout = this.onImageLayout.bind(this)
this.animateScale = this.animateScale.bind(this)
}

componentDidUpdate(prevProps, prevState) {
if (prevProps.imageIsLoaded !== this.props.imageIsLoaded && !this.props.imageIsLoaded) {
this.setState({scale: new Animated.Value(0.8)})
}
if (!R.equals(prevProps.subjectDimensions, this.props.subjectDimensions) || !R.equals(prevState.containerDimensions, this.state.containerDimensions)) {
const { naturalHeight, naturalWidth } = this.props.subjectDimensions
const { height: containerHeight, width: containerWidth } = this.state.containerDimensions
const aspectRatio = Math.min(containerHeight/naturalHeight, containerWidth/naturalWidth)
const clientHeight = naturalHeight * aspectRatio
const clientWidth = naturalWidth * aspectRatio

this.props.onImageLayout({
clientHeight,
clientWidth,
})
}
}

animateScale() {
Animated.spring(
this.state.scale,
{
toValue: 1
}
).start()
}

onImageLayout({nativeEvent}) {
const { height: containerHeight, width: containerWidth } = nativeEvent.layout
const { naturalHeight, naturalWidth } = this.props.subjectDimensions
const aspectRatio = Math.min(containerHeight/naturalHeight, containerWidth/naturalWidth)
const clientHeight = naturalHeight * aspectRatio
const clientWidth = naturalWidth * aspectRatio
const { height, width } = nativeEvent.layout
this.setState({
imageIsLoaded: true,
clientHeight,
clientWidth,
})

this.props.onImageLayout({
clientHeight,
clientWidth,
containerDimensions: {
width,
height
}
})
}

Expand All @@ -65,7 +80,7 @@ class ImageWithSvgOverlay extends Component {
key={index}
fill="transparent"
stroke={shape.color}
strokeWidth={3}
strokeWidth={4 * this.props.displayToNativeRatio}
{ ... shape }
/>
)
Expand All @@ -76,40 +91,56 @@ class ImageWithSvgOverlay extends Component {
return shapeArray
}

renderBlurView() {
const expandIcon = <Icon name="arrows-alt" color="white" size={50} />

return (
<View style={styles.blurView}>
{
Platform.OS === 'ios' ?
<BlurView style={[styles.centeredContent, this.state.containerDimensions]} blurType="light" blurAmount={2}>
{ expandIcon }
</BlurView>

:
<View style={ [styles.centeredContent, styles.androidBlurView, this.state.containerDimensions] }>
{ expandIcon }
</View>
}
</View>
)
}

render() {
const pathPrefix = Platform.OS === 'android' ? 'file://' : ''
const { naturalWidth, naturalHeight } = this.props.subjectDimensions

return (
<View style={styles.container}>
<Animated.View style={[styles.container, {transform: [{scale: this.state.scale}]}]}>
{this.props.imageIsLoaded ?
<View style={styles.container} >
<Image
onLoad={() => this.animateScale()}
onLayout={this.onImageLayout}
style={styles.backgroundImage}
source={{uri: pathPrefix + this.props.uri}}
resizeMode="contain"
/>
{
this.state.imageIsLoaded ?
<View style={styles.svgContainer} >
<Svg
viewBox={`0 0 ${naturalWidth} ${naturalHeight}`}
height={this.state.clientHeight}
width={this.state.clientWidth}
>
{ this.renderShapes() }
</Svg>
</View>
:
null
}
<View style={styles.svgContainer} >
<Svg
viewBox={`0 0 ${naturalWidth} ${naturalHeight}`}
height={this.state.containerDimensions.height}
width={this.state.containerDimensions.width}
>
{ this.renderShapes() }
</Svg>
</View>
</View>
:
<SubjectLoadingIndicator />
}
</View>


{ this.props.showBlurView && this.props.imageIsLoaded && this.renderBlurView()}
</Animated.View>
)
}
}
Expand All @@ -134,14 +165,32 @@ const styles = {
left: 0,
right: 0,
bottom: 0
}
},
blurView: {
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0
},
centeredContent: {
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
},
androidBlurView: {
backgroundColor: 'rgba(255, 255, 255, 0.4)'
},
}

ImageWithSvgOverlay.propTypes = {
subjectDimensions: PropTypes.shape({
naturalWidth: PropTypes.number,
naturalHeight: PropTypes.number
}),
displayToNativeRatio: PropTypes.number,
imageIsLoaded: PropTypes.bool,
uri: PropTypes.string,
annotations: PropTypes.arrayOf(PropTypes.shape({
Expand All @@ -153,7 +202,8 @@ ImageWithSvgOverlay.propTypes = {
height: PropTypes.number
})),
onImageLayout: PropTypes.func,
shapes: PropTypes.object
shapes: PropTypes.object,
showBlurView: PropTypes.bool
}

export default connect(mapStateToProps)(ImageWithSvgOverlay)
export default ImageWithSvgOverlay
6 changes: 4 additions & 2 deletions src/components/Markings/components/CloseButtonSVG.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import PropTypes from 'prop-types'

class CloseButtonSVG extends Component {
render() {
const side = `${this.props.displayToNativeRatio * 30}`
return (
<G
>
Expand All @@ -29,15 +30,16 @@ class CloseButtonSVG extends Component {

<Use
href="#symbol"
width="20" height="20"
width={side} height={side}
/>
<Rect onLayout={this.props.onLayout} width="20" height="20" fill="transparent" />
<Rect onLayout={this.props.onLayout} width={side} height={side} fill="transparent" />
</G>
)
}
}

CloseButtonSVG.propTypes = {
displayToNativeRatio: PropTypes.number,
color: PropTypes.string,
onLayout: PropTypes.func,
}
Expand Down
Loading

0 comments on commit 6eaf679

Please sign in to comment.