Fix update bug with work items and activities. Fix placement of item in AR view

This commit is contained in:
John Lyon-Smith
2018-05-01 09:44:23 -07:00
parent edb078b38a
commit 0dce0b5858
11 changed files with 126 additions and 89 deletions

View File

@@ -31,6 +31,7 @@ const shapes = {
resources: [require("./models/clipboard.mtl")], resources: [require("./models/clipboard.mtl")],
}, },
} }
const distance = (vectorA, vectorB) => { const distance = (vectorA, vectorB) => {
return Math.sqrt( return Math.sqrt(
(vectorB[0] - vectorA[0]) * (vectorB[0] - vectorA[0]) + (vectorB[0] - vectorA[0]) * (vectorB[0] - vectorA[0]) +
@@ -46,56 +47,55 @@ class WorkItemSceneAR extends React.Component {
this.state = { this.state = {
position: [0, 0, 0], position: [0, 0, 0],
rotation: [0, 0, 0], rotation: [0, 0, 0],
scale: [0.15, 0.15, 0.15], scale: [0.2, 0.2, 0.2],
shouldBillboard: true, shouldBillboard: true,
trackingInitialized: false,
haveAnchor: false,
} }
} }
@autobind @autobind
handleLoadEnd() { handleLoadEnd() {
this.arScene.getCameraOrientationAsync().then((orientation) => { this.arScene.getCameraOrientationAsync().then((orientation) => {
console.log(orientation)
return this.arScene return this.arScene
.performARHitTestWithRay(orientation.forward) .performARHitTestWithRay(orientation.forward)
.then((results) => { .then((results) => {
// Default position is just 1.5 meters in front of the user.
const forward = orientation.forward const forward = orientation.forward
const defaultPosition = [ const position = orientation.position
forward[0] * 1.5, // Default position is just one meter in front of the user.
forward[1] * 1.5, const defaultPosition =
forward[2] * 1.5, [forward[0] * 1.0, forward[1] * 1.0, forward[2]] * 1.0
]
let hitResultPosition = null let hitResultPosition = null
console.log(orientation)
console.log(results)
// Filter the hit test results based on the position. // Filter the hit test results based on the position.
if (results.length > 0) { for (var i = 0; i < results.length; i++) {
for (var i = 0; i < results.length; i++) { let result = results[i]
let result = results[i]
if (result.type == "ExistingPlaneUsingExtent") { if (result.type == "ExistingPlaneUsingExtent") {
let distance = Math.sqrt( let distance = Math.sqrt(
(result.transform.position[0] - position[0]) * (result.transform.position[0] - position[0]) *
(result.transform.position[0] - position[0]) + (result.transform.position[0] - position[0]) +
(result.transform.position[1] - position[1]) * (result.transform.position[1] - position[1]) *
(result.transform.position[1] - position[1]) + (result.transform.position[1] - position[1]) +
(result.transform.position[2] - position[2]) * (result.transform.position[2] - position[2]) *
(result.transform.position[2] - position[2]) (result.transform.position[2] - position[2])
) )
if (distance > 0.2 && distance < 10) { if (distance > 0.2 && distance < 10) {
// If we found a plane greater than .2 and less than 10 meters away then choose it! // If we found a plane greater than .2 and less than 10 meters away then choose it!
hitResultPosition = result.transform.position hitResultPosition = result.transform.position
break break
} }
} else if (result.type == "FeaturePoint" && !hitResultPosition) { } else if (result.type == "FeaturePoint" && !hitResultPosition) {
// If we haven't found a plane and this feature point is within range, // If we haven't found a plane and this feature point is within range,
// then we'll use it as the initial display point // then we'll use it as the initial display point
let distance = this._distance( let d = distance(position, result.transform.position)
position,
result.transform.position
)
if (distance > 0.2 && distance < 10) { if (d > 0.2 && d < 10) {
hitResultPosition = result.transform.position hitResultPosition = result.transform.position
}
} }
} }
} }
@@ -108,6 +108,9 @@ class WorkItemSceneAR extends React.Component {
this.updateInitialRotation() this.updateInitialRotation()
}, 200) }, 200)
}) })
.catch((err) => {
console.log(err)
})
}) })
} }
@@ -132,6 +135,16 @@ class WorkItemSceneAR extends React.Component {
}) })
} }
@autobind
handleTrackingInitialized() {
this.setState({ trackingInitialized: true })
}
@autobind
handleAnchorFound() {
this.setState({ haveAnchor: true })
}
@autobind @autobind
handleClick(position, source) { handleClick(position, source) {
const { workItemId } = this.props const { workItemId } = this.props
@@ -142,11 +155,22 @@ class WorkItemSceneAR extends React.Component {
} }
render() { render() {
const { position, scale, rotation, shouldBillboard } = this.state const {
position,
scale,
rotation,
shouldBillboard,
trackingInitialized,
haveAnchor,
} = this.state
const shape = shapes[this.props.workItemType] const shape = shapes[this.props.workItemType]
return ( return (
<ViroARScene ref={(ref) => (this.arScene = ref)}> <ViroARScene
anchorDetectionTypes="PlanesHorizontal"
ref={(ref) => (this.arScene = ref)}
onTrackingInitialized={this.handleTrackingInitialized}
onAnchorFound={this.handleAnchorFound}>
<ViroAmbientLight color="#ffffff" intensity={200} /> <ViroAmbientLight color="#ffffff" intensity={200} />
<ViroNode <ViroNode
ref={(ref) => (this.arNode = ref)} ref={(ref) => (this.arNode = ref)}
@@ -165,16 +189,18 @@ class WorkItemSceneAR extends React.Component {
shadowFarZ={6} shadowFarZ={6}
shadowOpacity={0.9} shadowOpacity={0.9}
/> />
{shape && ( {shape &&
<Viro3DObject trackingInitialized &&
position={[0, 0, -1]} haveAnchor && (
source={shape.source} <Viro3DObject
resources={shape.resources} position={[0, 0, -1]}
type="OBJ" source={shape.source}
onLoadEnd={this.handleLoadEnd} resources={shape.resources}
onClick={this.handleClick} type="OBJ"
/> onLoadEnd={this.handleLoadEnd}
)} onClick={this.handleClick}
/>
)}
<ViroSurface <ViroSurface
rotation={[-90, 0, 0]} rotation={[-90, 0, 0]}
position={[0, -0.001, 0]} position={[0, -0.001, 0]}

View File

@@ -67,7 +67,6 @@ export class Activity extends React.Component {
}, },
photos: { photos: {
isValid: (r, v) => v && v.length > 0, isValid: (r, v) => v && v.length > 0,
initValue: [],
}, },
status: { status: {
isValid: (r, v) => v !== "", isValid: (r, v) => v !== "",

View File

@@ -33,7 +33,6 @@ export class Login extends React.Component {
}, },
rememberMe: { rememberMe: {
alwaysGet: true, alwaysGet: true,
initValue: true,
isValid: true, isValid: true,
}, },
login: { login: {
@@ -76,7 +75,10 @@ export class Login extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
binder: new FormBinder({ email: config.defaultUser }, Login.bindings), binder: new FormBinder(
{ email: config.defaultUser, rememberMe: true },
Login.bindings
),
messageModal: null, messageModal: null,
apiModal: null, apiModal: null,
waitModal: null, waitModal: null,

View File

@@ -66,7 +66,6 @@ export class WorkItem extends React.Component {
}, },
photos: { photos: {
isValid: (r, v) => v && v.length > 0, isValid: (r, v) => v && v.length > 0,
initValue: [],
}, },
details: { details: {
isValid: (r, v) => v !== "", isValid: (r, v) => v !== "",
@@ -84,7 +83,6 @@ export class WorkItem extends React.Component {
binder: new FormBinder({}, WorkItem.bindings), binder: new FormBinder({}, WorkItem.bindings),
messageModal: null, messageModal: null,
waitModal: null, waitModal: null,
region: config.initialRegion,
} }
const { search } = this.props.location const { search } = this.props.location
@@ -104,10 +102,15 @@ export class WorkItem extends React.Component {
longitudeDelta: 0.01, longitudeDelta: 0.01,
} }
if (this.mapView) {
this.mapView.animateToRegion(region)
} else {
this.goToRegion = region
}
workItem.location = formatLatLng(lat, lng) workItem.location = formatLatLng(lat, lng)
this.setState({ this.setState({
binder: new FormBinder(workItem, WorkItem.bindings), binder: new FormBinder(workItem, WorkItem.bindings),
region,
}) })
} }
}) })
@@ -209,6 +212,14 @@ export class WorkItem extends React.Component {
) )
} }
@autobind
handleOnMapReady() {
if (this.goToRegion && this.mapView) {
this.mapView.animateToRegion(this.goToRegion)
this.goToRegion = null
}
}
@autobind @autobind
handleStartAddressLookup(latLng) { handleStartAddressLookup(latLng) {
api api
@@ -217,7 +228,6 @@ export class WorkItem extends React.Component {
if (this.addressInput) { if (this.addressInput) {
this.addressInput.handleChangeText(address) this.addressInput.handleChangeText(address)
} }
this.setState({ region: this.mapView.region })
}) })
.catch(() => { .catch(() => {
if (this.addressInput) { if (this.addressInput) {
@@ -237,7 +247,7 @@ export class WorkItem extends React.Component {
} }
render() { render() {
const { binder, messageModal, waitModal, region } = this.state const { binder, messageModal, waitModal } = this.state
return ( return (
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
@@ -284,8 +294,9 @@ export class WorkItem extends React.Component {
showsIndoors={false} showsIndoors={false}
zoomControlEnabled={false} zoomControlEnabled={false}
rotateEnabled={false} rotateEnabled={false}
region={region} initialRegion={config.initialRegion}
onRegionChange={this.handleRegionChange} onRegionChange={this.handleRegionChange}
onMapReady={this.handleOnMapReady}
/> />
<Icon <Icon
name="target" name="target"

View File

@@ -2,8 +2,8 @@ import React from "react"
import { Platform } from "react-native" import { Platform } from "react-native"
export const config = { export const config = {
//localIPAddr: "192.168.1.175", localIPAddr: "192.168.1.175",
localIPAddr: "192.168.1.14", //localIPAddr: "192.168.1.14",
viroAPIKey: "06F37B6A-74DA-4A83-965A-7DE2209A5C46", viroAPIKey: "06F37B6A-74DA-4A83-965A-7DE2209A5C46",
googleGeocodeAPIKey: "AIzaSyCs4JVT6gysnY5dAJ7KjVJYeykLv_xz1GI", googleGeocodeAPIKey: "AIzaSyCs4JVT6gysnY5dAJ7KjVJYeykLv_xz1GI",
googleGeocodeURL: "https://maps.googleapis.com/maps/api/geocode/json", googleGeocodeURL: "https://maps.googleapis.com/maps/api/geocode/json",

View File

@@ -94,8 +94,7 @@ export class BoundPhotoPanel extends Component {
handleLayout(e) { handleLayout(e) {
if (!this.state.photoSize) { if (!this.state.photoSize) {
const { layout } = e.nativeEvent const { layout } = e.nativeEvent
const { screenWidth, screenHeight } = getScreenPortraitDimensions() const ratio = 3 / 4
const ratio = screenWidth / screenHeight
const width = (layout.width - photoSpacing) / 2 const width = (layout.width - photoSpacing) / 2
const height = width / ratio const height = width / ratio
@@ -106,9 +105,8 @@ export class BoundPhotoPanel extends Component {
render() { render() {
const { photoSize, value: assetIds } = this.state const { photoSize, value: assetIds } = this.state
const renderPhoto = (index, size, paddingRight) => { const renderPhoto = (index) => {
const assetId = assetIds[index] const assetId = assetIds[index]
const { photoSize } = this.state
if (assetId) { if (assetId) {
console.log(api.makeImageUrl(assetId)) console.log(api.makeImageUrl(assetId))
@@ -118,9 +116,8 @@ export class BoundPhotoPanel extends Component {
<TouchableOpacity <TouchableOpacity
key={assetId || "blank" + index.toString()} key={assetId || "blank" + index.toString()}
style={{ style={{
width: size.width, width: photoSize.width,
height: size.height, height: photoSize.height,
paddingRight,
borderWidth: 2, borderWidth: 2,
borderColor: "gray", borderColor: "gray",
borderRadius: 4, borderRadius: 4,
@@ -157,14 +154,14 @@ export class BoundPhotoPanel extends Component {
</Text> </Text>
{photoSize && ( {photoSize && (
<View style={styles.photoRow}> <View style={styles.photoRow}>
{renderPhoto(0, photoSize, photoSpacing)} {renderPhoto(0)}
{renderPhoto(1, photoSize)} {renderPhoto(1)}
</View> </View>
)} )}
{photoSize && ( {photoSize && (
<View style={styles.photoRow}> <View style={styles.photoRow}>
{renderPhoto(2, photoSize, photoSpacing)} {renderPhoto(2)}
{renderPhoto(3, photoSize)} {renderPhoto(3)}
</View> </View>
)} )}
</View> </View>

View File

@@ -1596,6 +1596,11 @@
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true "dev": true
}, },
"deepmerge": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.1.0.tgz",
"integrity": "sha512-Q89Z26KAfA3lpPGhbF6XMfYAm3jIV3avViy6KOJ2JLzFbeWHOvPQUu5aSJIWXap3gDZC2y1eF5HXEPI2wGqgvw=="
},
"default-require-extensions": { "default-require-extensions": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz",

View File

@@ -30,6 +30,7 @@
"config": "^1.25.1", "config": "^1.25.1",
"cors": "^2.8.3", "cors": "^2.8.3",
"credential": "^2.0.0", "credential": "^2.0.0",
"deepmerge": "^2.1.0",
"express": "^4.15.2", "express": "^4.15.2",
"gridfs-stream": "jlyonsmith/gridfs-stream", "gridfs-stream": "jlyonsmith/gridfs-stream",
"handlebars": "^4.0.10", "handlebars": "^4.0.10",

View File

@@ -111,13 +111,7 @@ export class ActivityRoutes {
} }
let Activity = this.db.Activity let Activity = this.db.Activity
let activityUpdates = null let activityUpdates = req.body
try {
activityUpdates = new Activity(req.body)
} catch (err) {
throw createError.BadRequest("Invalid data")
}
const foundActivity = await Activity.findById(activityUpdates._id) const foundActivity = await Activity.findById(activityUpdates._id)
@@ -127,9 +121,13 @@ export class ActivityRoutes {
) )
} }
foundActivity.merge(activityUpdates) // Strip off all BSON types
foundActivity = JSON.parse(JSON.stringify(foundActivity))
const savedActivity = await foundActivity.save() const mergedActivity = Activity.hydrate(
merge(foundActivity, activityUpdates)
)
const savedActivity = await mergedActivity.save()
res.json(savedActivity.toClient()) res.json(savedActivity.toClient())
} }

View File

@@ -1,6 +1,7 @@
import passport from "passport" import passport from "passport"
import createError from "http-errors" import createError from "http-errors"
import autobind from "autobind-decorator" import autobind from "autobind-decorator"
import merge from "deepmerge"
import { catchAll } from "." import { catchAll } from "."
@autobind @autobind
@@ -146,15 +147,8 @@ export class WorkItemRoutes {
} }
let WorkItem = this.db.WorkItem let WorkItem = this.db.WorkItem
let workItemUpdates = null let workItemUpdates = req.body
let foundWorkItem = await WorkItem.findById(workItemUpdates._id)
try {
workItemUpdates = new WorkItem(req.body)
} catch (err) {
throw createError.BadRequest("Invalid data")
}
const foundWorkItem = await WorkItem.findById(workItemUpdates._id)
if (!foundWorkItem) { if (!foundWorkItem) {
return next( return next(
@@ -162,9 +156,13 @@ export class WorkItemRoutes {
) )
} }
foundWorkItem.merge(workItemUpdates) // Strip off all BSON types
foundWorkItem = JSON.parse(JSON.stringify(foundWorkItem))
const savedWorkItem = await foundWorkItem.save() const mergedWorkItem = WorkItem.hydrate(
merge(foundWorkItem, workItemUpdates)
)
const savedWorkItem = await mergedWorkItem.save()
res.json(savedWorkItem.toClient()) res.json(savedWorkItem.toClient())
} }

View File

@@ -17,7 +17,7 @@ export const workItemSchema = new Schema(
coordinates: [Number], coordinates: [Number],
}, },
address: String, address: String,
photos: [Schema.Types.ObjectId], photos: [{ type: Schema.Types.ObjectId }],
details: String, details: String,
ticketNumber: { type: Number, default: 0 }, ticketNumber: { type: Number, default: 0 },
}, },