diff --git a/mobile/src/ARViewer/ARViewer.js b/mobile/src/ARViewer/ARViewer.js index f5787f0..b98019b 100644 --- a/mobile/src/ARViewer/ARViewer.js +++ b/mobile/src/ARViewer/ARViewer.js @@ -31,6 +31,7 @@ const shapes = { resources: [require("./models/clipboard.mtl")], }, } + const distance = (vectorA, vectorB) => { return Math.sqrt( (vectorB[0] - vectorA[0]) * (vectorB[0] - vectorA[0]) + @@ -46,56 +47,55 @@ class WorkItemSceneAR extends React.Component { this.state = { position: [0, 0, 0], rotation: [0, 0, 0], - scale: [0.15, 0.15, 0.15], + scale: [0.2, 0.2, 0.2], shouldBillboard: true, + trackingInitialized: false, + haveAnchor: false, } } @autobind handleLoadEnd() { this.arScene.getCameraOrientationAsync().then((orientation) => { + console.log(orientation) return this.arScene .performARHitTestWithRay(orientation.forward) .then((results) => { - // Default position is just 1.5 meters in front of the user. const forward = orientation.forward - const defaultPosition = [ - forward[0] * 1.5, - forward[1] * 1.5, - forward[2] * 1.5, - ] + const position = orientation.position + // Default position is just one meter in front of the user. + const defaultPosition = + [forward[0] * 1.0, forward[1] * 1.0, forward[2]] * 1.0 let hitResultPosition = null + console.log(orientation) + console.log(results) + // Filter the hit test results based on the position. - if (results.length > 0) { - for (var i = 0; i < results.length; i++) { - let result = results[i] + for (var i = 0; i < results.length; i++) { + let result = results[i] - if (result.type == "ExistingPlaneUsingExtent") { - let distance = Math.sqrt( - (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[2] - position[2]) * - (result.transform.position[2] - position[2]) - ) - if (distance > 0.2 && distance < 10) { - // If we found a plane greater than .2 and less than 10 meters away then choose it! - hitResultPosition = result.transform.position - break - } - } else if (result.type == "FeaturePoint" && !hitResultPosition) { - // If we haven't found a plane and this feature point is within range, - // then we'll use it as the initial display point - let distance = this._distance( - position, - result.transform.position - ) + if (result.type == "ExistingPlaneUsingExtent") { + let distance = Math.sqrt( + (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[2] - position[2]) * + (result.transform.position[2] - position[2]) + ) + if (distance > 0.2 && distance < 10) { + // If we found a plane greater than .2 and less than 10 meters away then choose it! + hitResultPosition = result.transform.position + break + } + } else if (result.type == "FeaturePoint" && !hitResultPosition) { + // If we haven't found a plane and this feature point is within range, + // then we'll use it as the initial display point + let d = distance(position, result.transform.position) - if (distance > 0.2 && distance < 10) { - hitResultPosition = result.transform.position - } + if (d > 0.2 && d < 10) { + hitResultPosition = result.transform.position } } } @@ -108,6 +108,9 @@ class WorkItemSceneAR extends React.Component { this.updateInitialRotation() }, 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 handleClick(position, source) { const { workItemId } = this.props @@ -142,11 +155,22 @@ class WorkItemSceneAR extends React.Component { } render() { - const { position, scale, rotation, shouldBillboard } = this.state + const { + position, + scale, + rotation, + shouldBillboard, + trackingInitialized, + haveAnchor, + } = this.state const shape = shapes[this.props.workItemType] return ( - (this.arScene = ref)}> + (this.arScene = ref)} + onTrackingInitialized={this.handleTrackingInitialized} + onAnchorFound={this.handleAnchorFound}> (this.arNode = ref)} @@ -165,16 +189,18 @@ class WorkItemSceneAR extends React.Component { shadowFarZ={6} shadowOpacity={0.9} /> - {shape && ( - - )} + {shape && + trackingInitialized && + haveAnchor && ( + + )} v && v.length > 0, - initValue: [], }, status: { isValid: (r, v) => v !== "", diff --git a/mobile/src/Auth/Login.js b/mobile/src/Auth/Login.js index 7ccd2d1..d92be93 100644 --- a/mobile/src/Auth/Login.js +++ b/mobile/src/Auth/Login.js @@ -33,7 +33,6 @@ export class Login extends React.Component { }, rememberMe: { alwaysGet: true, - initValue: true, isValid: true, }, login: { @@ -76,7 +75,10 @@ export class Login extends React.Component { constructor(props) { super(props) this.state = { - binder: new FormBinder({ email: config.defaultUser }, Login.bindings), + binder: new FormBinder( + { email: config.defaultUser, rememberMe: true }, + Login.bindings + ), messageModal: null, apiModal: null, waitModal: null, diff --git a/mobile/src/WorkItem/WorkItem.js b/mobile/src/WorkItem/WorkItem.js index 9433c2d..fec7ecd 100644 --- a/mobile/src/WorkItem/WorkItem.js +++ b/mobile/src/WorkItem/WorkItem.js @@ -66,7 +66,6 @@ export class WorkItem extends React.Component { }, photos: { isValid: (r, v) => v && v.length > 0, - initValue: [], }, details: { isValid: (r, v) => v !== "", @@ -84,7 +83,6 @@ export class WorkItem extends React.Component { binder: new FormBinder({}, WorkItem.bindings), messageModal: null, waitModal: null, - region: config.initialRegion, } const { search } = this.props.location @@ -104,10 +102,15 @@ export class WorkItem extends React.Component { longitudeDelta: 0.01, } + if (this.mapView) { + this.mapView.animateToRegion(region) + } else { + this.goToRegion = region + } + workItem.location = formatLatLng(lat, lng) this.setState({ 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 handleStartAddressLookup(latLng) { api @@ -217,7 +228,6 @@ export class WorkItem extends React.Component { if (this.addressInput) { this.addressInput.handleChangeText(address) } - this.setState({ region: this.mapView.region }) }) .catch(() => { if (this.addressInput) { @@ -237,7 +247,7 @@ export class WorkItem extends React.Component { } render() { - const { binder, messageModal, waitModal, region } = this.state + const { binder, messageModal, waitModal } = this.state return ( @@ -284,8 +294,9 @@ export class WorkItem extends React.Component { showsIndoors={false} zoomControlEnabled={false} rotateEnabled={false} - region={region} + initialRegion={config.initialRegion} onRegionChange={this.handleRegionChange} + onMapReady={this.handleOnMapReady} /> { + const renderPhoto = (index) => { const assetId = assetIds[index] - const { photoSize } = this.state if (assetId) { console.log(api.makeImageUrl(assetId)) @@ -118,9 +116,8 @@ export class BoundPhotoPanel extends Component { {photoSize && ( - {renderPhoto(0, photoSize, photoSpacing)} - {renderPhoto(1, photoSize)} + {renderPhoto(0)} + {renderPhoto(1)} )} {photoSize && ( - {renderPhoto(2, photoSize, photoSpacing)} - {renderPhoto(3, photoSize)} + {renderPhoto(2)} + {renderPhoto(3)} )} diff --git a/server/package-lock.json b/server/package-lock.json index 7629f1e..f77b245 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1596,6 +1596,11 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "deepmerge": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.1.0.tgz", + "integrity": "sha512-Q89Z26KAfA3lpPGhbF6XMfYAm3jIV3avViy6KOJ2JLzFbeWHOvPQUu5aSJIWXap3gDZC2y1eF5HXEPI2wGqgvw==" + }, "default-require-extensions": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", diff --git a/server/package.json b/server/package.json index e62a807..948b71e 100644 --- a/server/package.json +++ b/server/package.json @@ -30,6 +30,7 @@ "config": "^1.25.1", "cors": "^2.8.3", "credential": "^2.0.0", + "deepmerge": "^2.1.0", "express": "^4.15.2", "gridfs-stream": "jlyonsmith/gridfs-stream", "handlebars": "^4.0.10", diff --git a/server/src/api/routes/ActivityRoutes.js b/server/src/api/routes/ActivityRoutes.js index f0a852e..031ce92 100644 --- a/server/src/api/routes/ActivityRoutes.js +++ b/server/src/api/routes/ActivityRoutes.js @@ -111,13 +111,7 @@ export class ActivityRoutes { } let Activity = this.db.Activity - let activityUpdates = null - - try { - activityUpdates = new Activity(req.body) - } catch (err) { - throw createError.BadRequest("Invalid data") - } + let activityUpdates = req.body 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()) } diff --git a/server/src/api/routes/WorkItemRoutes.js b/server/src/api/routes/WorkItemRoutes.js index 7eca99e..99457da 100644 --- a/server/src/api/routes/WorkItemRoutes.js +++ b/server/src/api/routes/WorkItemRoutes.js @@ -1,6 +1,7 @@ import passport from "passport" import createError from "http-errors" import autobind from "autobind-decorator" +import merge from "deepmerge" import { catchAll } from "." @autobind @@ -146,15 +147,8 @@ export class WorkItemRoutes { } let WorkItem = this.db.WorkItem - let workItemUpdates = null - - try { - workItemUpdates = new WorkItem(req.body) - } catch (err) { - throw createError.BadRequest("Invalid data") - } - - const foundWorkItem = await WorkItem.findById(workItemUpdates._id) + let workItemUpdates = req.body + let foundWorkItem = await WorkItem.findById(workItemUpdates._id) if (!foundWorkItem) { 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()) } diff --git a/server/src/database/schemas/workItem.js b/server/src/database/schemas/workItem.js index 8d1e289..7c59edf 100644 --- a/server/src/database/schemas/workItem.js +++ b/server/src/database/schemas/workItem.js @@ -17,7 +17,7 @@ export const workItemSchema = new Schema( coordinates: [Number], }, address: String, - photos: [Schema.Types.ObjectId], + photos: [{ type: Schema.Types.ObjectId }], details: String, ticketNumber: { type: Number, default: 0 }, },