Added RNFS and refactor image upload to use it
This commit is contained in:
@@ -166,6 +166,7 @@ dependencies {
|
|||||||
compile 'com.amazonaws:aws-android-sdk-cognitoidentityprovider:2.2.+'
|
compile 'com.amazonaws:aws-android-sdk-cognitoidentityprovider:2.2.+'
|
||||||
compile project(':react-native-maps')
|
compile project(':react-native-maps')
|
||||||
compile project(':react-native-image-picker')
|
compile project(':react-native-image-picker')
|
||||||
|
compile project(':react-native-fs')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run this once to be able to run the application with BUCK
|
// Run this once to be able to run the application with BUCK
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import com.airbnb.android.react.maps.MapsPackage;
|
|||||||
|
|
||||||
import com.imagepicker.ImagePickerPackage;
|
import com.imagepicker.ImagePickerPackage;
|
||||||
|
|
||||||
|
import com.rnfs.RNFSPackage;
|
||||||
|
|
||||||
public class MainApplication extends Application implements ReactApplication {
|
public class MainApplication extends Application implements ReactApplication {
|
||||||
|
|
||||||
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
||||||
@@ -31,7 +33,8 @@ public class MainApplication extends Application implements ReactApplication {
|
|||||||
new MainReactPackage(),
|
new MainReactPackage(),
|
||||||
new ReactViroPackage(ReactViroPackage.ViroPlatform.GVR),
|
new ReactViroPackage(ReactViroPackage.ViroPlatform.GVR),
|
||||||
new MapsPackage(),
|
new MapsPackage(),
|
||||||
new ImagePickerPackage()
|
new ImagePickerPackage(),
|
||||||
|
new RNFSPackage()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,3 +13,6 @@ project(':react-native-maps').projectDir = new File(rootProject.projectDir, '../
|
|||||||
|
|
||||||
include ':react-native-image-picker'
|
include ':react-native-image-picker'
|
||||||
project(':react-native-image-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-picker/android')
|
project(':react-native-image-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-picker/android')
|
||||||
|
|
||||||
|
include ':react-native-fs'
|
||||||
|
project(':react-native-fs').projectDir = new File(settingsDir, '../node_modules/react-native-fs/android')
|
||||||
@@ -201,6 +201,7 @@
|
|||||||
"${BUILT_PRODUCTS_DIR}/Folly/folly.framework",
|
"${BUILT_PRODUCTS_DIR}/Folly/folly.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
|
"${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework",
|
"${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework",
|
||||||
|
"${BUILT_PRODUCTS_DIR}/RNFS/RNFS.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/React/React.framework",
|
"${BUILT_PRODUCTS_DIR}/React/React.framework",
|
||||||
"${PODS_ROOT}/../../node_modules/react-viro/ios/dist/ViroRenderer/ViroKit.framework",
|
"${PODS_ROOT}/../../node_modules/react-viro/ios/dist/ViroRenderer/ViroKit.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/glog/glog.framework",
|
"${BUILT_PRODUCTS_DIR}/glog/glog.framework",
|
||||||
@@ -216,6 +217,7 @@
|
|||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/folly.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/folly.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
|
||||||
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNFS.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ViroKit.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ViroKit.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/glog.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/glog.framework",
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ target 'DeightonAR' do
|
|||||||
pod 'ViroKit', :path => '../node_modules/react-viro/ios/dist/ViroRenderer/'
|
pod 'ViroKit', :path => '../node_modules/react-viro/ios/dist/ViroRenderer/'
|
||||||
|
|
||||||
pod 'react-native-image-picker', :path => '../node_modules/react-native-image-picker'
|
pod 'react-native-image-picker', :path => '../node_modules/react-native-image-picker'
|
||||||
|
|
||||||
|
pod 'RNFS', :path => '../node_modules/react-native-fs'
|
||||||
end
|
end
|
||||||
|
|
||||||
# See https://gist.github.com/Jpunt/3fe75effd54a702034b75ff697e47578
|
# See https://gist.github.com/Jpunt/3fe75effd54a702034b75ff697e47578
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ PODS:
|
|||||||
- React/Core
|
- React/Core
|
||||||
- React/fishhook
|
- React/fishhook
|
||||||
- React/RCTBlob
|
- React/RCTBlob
|
||||||
|
- RNFS (2.9.12):
|
||||||
|
- React
|
||||||
- ViroKit (1.0):
|
- ViroKit (1.0):
|
||||||
- AWSDynamoDB (~> 2.6.7)
|
- AWSDynamoDB (~> 2.6.7)
|
||||||
- GVRAudioSDK (= 1.120.0)
|
- GVRAudioSDK (= 1.120.0)
|
||||||
@@ -82,6 +84,7 @@ DEPENDENCIES:
|
|||||||
- React/RCTNetwork (from `../node_modules/react-native`)
|
- React/RCTNetwork (from `../node_modules/react-native`)
|
||||||
- React/RCTText (from `../node_modules/react-native`)
|
- React/RCTText (from `../node_modules/react-native`)
|
||||||
- React/RCTWebSocket (from `../node_modules/react-native`)
|
- React/RCTWebSocket (from `../node_modules/react-native`)
|
||||||
|
- RNFS (from `../node_modules/react-native-fs`)
|
||||||
- ViroKit (from `../node_modules/react-viro/ios/dist/ViroRenderer/`)
|
- ViroKit (from `../node_modules/react-viro/ios/dist/ViroRenderer/`)
|
||||||
- ViroReact (from `../node_modules/react-viro/ios/`)
|
- ViroReact (from `../node_modules/react-viro/ios/`)
|
||||||
- yoga (from `../node_modules/react-native/ReactCommon/yoga`)
|
- yoga (from `../node_modules/react-native/ReactCommon/yoga`)
|
||||||
@@ -109,6 +112,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../node_modules/react-native-image-picker"
|
:path: "../node_modules/react-native-image-picker"
|
||||||
react-native-maps:
|
react-native-maps:
|
||||||
:path: "../node_modules/react-native-maps"
|
:path: "../node_modules/react-native-maps"
|
||||||
|
RNFS:
|
||||||
|
:path: "../node_modules/react-native-fs"
|
||||||
ViroKit:
|
ViroKit:
|
||||||
:path: "../node_modules/react-viro/ios/dist/ViroRenderer/"
|
:path: "../node_modules/react-viro/ios/dist/ViroRenderer/"
|
||||||
ViroReact:
|
ViroReact:
|
||||||
@@ -130,10 +135,11 @@ SPEC CHECKSUMS:
|
|||||||
React: aa2040dbb6f317b95314968021bd2888816e03d5
|
React: aa2040dbb6f317b95314968021bd2888816e03d5
|
||||||
react-native-image-picker: 42cfe2c8435d893414f8714a81e480313cb1412b
|
react-native-image-picker: 42cfe2c8435d893414f8714a81e480313cb1412b
|
||||||
react-native-maps: 066c2afcc89e18726377bcc685315f989ca22449
|
react-native-maps: 066c2afcc89e18726377bcc685315f989ca22449
|
||||||
|
RNFS: bbb1a64eb245763daf34aea86f97c97c4e85f74c
|
||||||
ViroKit: 9631f301ef6a3f56116b23d6aac5d5c2307aa368
|
ViroKit: 9631f301ef6a3f56116b23d6aac5d5c2307aa368
|
||||||
ViroReact: 5520f26ac4654e361786c82da3b29ce0402c3c00
|
ViroReact: 5520f26ac4654e361786c82da3b29ce0402c3c00
|
||||||
yoga: a23273df0088bf7f2bb7e5d7b00044ea57a2a54a
|
yoga: a23273df0088bf7f2bb7e5d7b00044ea57a2a54a
|
||||||
|
|
||||||
PODFILE CHECKSUM: bf7fd2f2a19c210b54a09cfb216d5f930cf6601c
|
PODFILE CHECKSUM: 5c148f4a189f391c884f82181ca6fc7bf1d45d9c
|
||||||
|
|
||||||
COCOAPODS: 1.5.0
|
COCOAPODS: 1.5.0
|
||||||
|
|||||||
19
mobile/package-lock.json
generated
19
mobile/package-lock.json
generated
@@ -2051,6 +2051,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"base-64": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
|
||||||
|
"integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs="
|
||||||
|
},
|
||||||
"base64-arraybuffer": {
|
"base64-arraybuffer": {
|
||||||
"version": "0.1.5",
|
"version": "0.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
|
||||||
@@ -5214,6 +5219,15 @@
|
|||||||
"prop-types": "^15.5.10"
|
"prop-types": "^15.5.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-native-fs": {
|
||||||
|
"version": "2.9.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.9.12.tgz",
|
||||||
|
"integrity": "sha512-kppfQwMEmEerP9KImdzRi49ko0pmZtzHzIhpDSkjQVrpTPG7AEzYxdAqapaDveHM8CcN6tzqmiljrueqlBr1VA==",
|
||||||
|
"requires": {
|
||||||
|
"base-64": "^0.1.0",
|
||||||
|
"utf8": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-native-image-picker": {
|
"react-native-image-picker": {
|
||||||
"version": "0.26.7",
|
"version": "0.26.7",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-0.26.7.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-0.26.7.tgz",
|
||||||
@@ -6682,6 +6696,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"utf8": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz",
|
||||||
|
"integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY="
|
||||||
|
},
|
||||||
"util-deprecate": {
|
"util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"react": "^16.3.2",
|
"react": "^16.3.2",
|
||||||
"react-form-binder": "^2.0.0",
|
"react-form-binder": "^2.0.0",
|
||||||
"react-native": "^0.55.4",
|
"react-native": "^0.55.4",
|
||||||
|
"react-native-fs": "^2.9.12",
|
||||||
"react-native-image-picker": "^0.26.7",
|
"react-native-image-picker": "^0.26.7",
|
||||||
"react-native-iphone-x-helper": "^1.0.3",
|
"react-native-iphone-x-helper": "^1.0.3",
|
||||||
"react-native-keyboard-spacer": "^0.4.1",
|
"react-native-keyboard-spacer": "^0.4.1",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import io from "socket.io-client"
|
|||||||
import { AsyncStorage } from "react-native"
|
import { AsyncStorage } from "react-native"
|
||||||
import autobind from "autobind-decorator"
|
import autobind from "autobind-decorator"
|
||||||
import { config } from "./config"
|
import { config } from "./config"
|
||||||
|
import RNFS from "react-native-fs"
|
||||||
|
|
||||||
const authTokenKeyName = "AuthToken"
|
const authTokenKeyName = "AuthToken"
|
||||||
const backendKeyName = "Backend"
|
const backendKeyName = "Backend"
|
||||||
@@ -209,17 +210,15 @@ class API extends EventEmitter {
|
|||||||
headers.set("Authorization", "Bearer " + this.token)
|
headers.set("Authorization", "Bearer " + this.token)
|
||||||
}
|
}
|
||||||
if (method === "POST" || method === "PUT") {
|
if (method === "POST" || method === "PUT") {
|
||||||
if (requestOptions.raw) {
|
if (requestOptions.binary) {
|
||||||
const isBase64 = requestOptions.raw.base64
|
const { isBase64, offset } = requestOptions.binary
|
||||||
|
|
||||||
headers.set(
|
headers.set(
|
||||||
"Content-Type",
|
"Content-Type",
|
||||||
isBase64 ? "application/base64" : "application/octet-stream"
|
isBase64 ? "application/base64" : "application/octet-stream"
|
||||||
)
|
)
|
||||||
headers.set("Content-Length", requestOptions.raw.length)
|
headers.set("Content-Length", requestBody.length)
|
||||||
headers.set(
|
headers.set("Content-Range", `byte ${offset}`)
|
||||||
"Content-Range",
|
|
||||||
(isBase64 ? "base64" : "byte") + " " + requestOptions.raw.offset
|
|
||||||
)
|
|
||||||
fetchOptions.body = requestBody
|
fetchOptions.body = requestBody
|
||||||
} else {
|
} else {
|
||||||
headers.set("Content-Type", "application/json")
|
headers.set("Content-Type", "application/json")
|
||||||
@@ -236,7 +235,7 @@ class API extends EventEmitter {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
Promise.resolve(res),
|
Promise.resolve(res),
|
||||||
requestOptions.raw && method === "GET" ? res.blob() : res.json(),
|
requestOptions.binary && method === "GET" ? res.blob() : res.json(),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
.then((arr) => {
|
.then((arr) => {
|
||||||
@@ -429,21 +428,27 @@ class API extends EventEmitter {
|
|||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
upload(data, progressCallback) {
|
upload(path, progressCallback) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const chunkSize = 32 * 1024
|
const chunkSize = 32 * 1024
|
||||||
const uploadSize = data.length
|
let uploadSize = 0
|
||||||
const numberOfChunks = Math.ceil(uploadSize / chunkSize)
|
let numberOfChunks = 0
|
||||||
let chunk = 0
|
let chunk = 0
|
||||||
let uploadId = null
|
let uploadId = null
|
||||||
|
|
||||||
const uploadNextChunk = () => {
|
const uploadNextChunk = () => {
|
||||||
const start = chunk * chunkSize
|
const offset = chunk * chunkSize
|
||||||
const end = Math.min(uploadSize, start + chunkSize)
|
const length = Math.min(chunkSize, uploadSize - offset)
|
||||||
|
|
||||||
this.post("/assets/upload/" + uploadId, data.slice(start, end), {
|
RNFS.read(path, length, offset, "base64")
|
||||||
raw: { base64: true, length: chunkSize, offset: start },
|
.then((data) => {
|
||||||
})
|
return this.post("/assets/upload/" + uploadId, data, {
|
||||||
|
binary: {
|
||||||
|
isBase64: true,
|
||||||
|
offset,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
.then((uploadData) => {
|
.then((uploadData) => {
|
||||||
chunk++
|
chunk++
|
||||||
if (progressCallback && !progressCallback(uploadData)) {
|
if (progressCallback && !progressCallback(uploadData)) {
|
||||||
@@ -459,12 +464,18 @@ class API extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.post("/assets/upload", {
|
RNFS.stat(path)
|
||||||
uploadSize,
|
.then((stat) => {
|
||||||
contentType: "image/jpeg",
|
uploadSize = stat.size
|
||||||
chunkContentType: "application/base64",
|
numberOfChunks = Math.ceil(uploadSize / chunkSize)
|
||||||
numberOfChunks,
|
|
||||||
})
|
return this.post("/assets/upload", {
|
||||||
|
uploadSize,
|
||||||
|
contentType: "image/jpeg",
|
||||||
|
chunkContentType: "application/base64",
|
||||||
|
numberOfChunks,
|
||||||
|
})
|
||||||
|
})
|
||||||
.then((uploadData) => {
|
.then((uploadData) => {
|
||||||
uploadId = uploadData.uploadId
|
uploadId = uploadData.uploadId
|
||||||
uploadNextChunk()
|
uploadNextChunk()
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ class WorkItemSceneAR extends React.Component {
|
|||||||
return this.arScene
|
return this.arScene
|
||||||
.performARHitTestWithRay(orientation.forward)
|
.performARHitTestWithRay(orientation.forward)
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
|
if (!results) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const forward = orientation.forward
|
const forward = orientation.forward
|
||||||
const position = orientation.position
|
const position = orientation.position
|
||||||
// Default position is just one meter in front of the user.
|
// Default position is just one meter in front of the user.
|
||||||
@@ -69,9 +73,6 @@ class WorkItemSceneAR extends React.Component {
|
|||||||
[forward[0] * 1.0, forward[1] * 1.0, forward[2]] * 1.0
|
[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.
|
||||||
for (var i = 0; i < results.length; i++) {
|
for (var i = 0; i < results.length; i++) {
|
||||||
let result = results[i]
|
let result = results[i]
|
||||||
@@ -109,9 +110,7 @@ class WorkItemSceneAR extends React.Component {
|
|||||||
this.updateInitialRotation()
|
this.updateInitialRotation()
|
||||||
}, 200)
|
}, 200)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {})
|
||||||
console.log(err)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export class Activity extends React.Component {
|
|||||||
isValid: (r, v) => v !== "",
|
isValid: (r, v) => v !== "",
|
||||||
},
|
},
|
||||||
photos: {
|
photos: {
|
||||||
|
initValue: [],
|
||||||
isValid: (r, v) => v && v.length > 0,
|
isValid: (r, v) => v && v.length > 0,
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
|
|||||||
@@ -47,8 +47,6 @@ export class Home extends React.Component {
|
|||||||
workItemDistance: -1,
|
workItemDistance: -1,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.watchId = null
|
|
||||||
|
|
||||||
if (Platform.OS !== "ios") {
|
if (Platform.OS !== "ios") {
|
||||||
ensurePermissions(
|
ensurePermissions(
|
||||||
[
|
[
|
||||||
@@ -130,13 +128,6 @@ export class Home extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this.watchId) {
|
|
||||||
navigator.geolocation.clearWatch(this.watchId)
|
|
||||||
this.watchId = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleMessageDismiss() {
|
handleMessageDismiss() {
|
||||||
this.setState({ messageModal: null })
|
this.setState({ messageModal: null })
|
||||||
@@ -255,6 +246,17 @@ export class Home extends React.Component {
|
|||||||
this.setState({ showWorkItems: !this.state.showWorkItems })
|
this.setState({ showWorkItems: !this.state.showWorkItems })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleCalloutPress(workItem) {
|
||||||
|
if (api.loggedInUser.administrator) {
|
||||||
|
this.props.history.push(
|
||||||
|
`/arviewer?workItemId=${workItem._id}&workItemType=${
|
||||||
|
workItem.workItemType
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
sections,
|
sections,
|
||||||
@@ -310,7 +312,7 @@ export class Home extends React.Component {
|
|||||||
: hardhatPinImage
|
: hardhatPinImage
|
||||||
}
|
}
|
||||||
onPress={(e) => this.handleMarkerPress(e, index)}>
|
onPress={(e) => this.handleMarkerPress(e, index)}>
|
||||||
<Callout>
|
<Callout onPress={() => this.handleCalloutPress(workItem)}>
|
||||||
<View>
|
<View>
|
||||||
<Text>
|
<Text>
|
||||||
{pad(workItem.ticketNumber, 4) +
|
{pad(workItem.ticketNumber, 4) +
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { api } from "../API"
|
|||||||
import "url-search-params-polyfill"
|
import "url-search-params-polyfill"
|
||||||
import { config } from "../config"
|
import { config } from "../config"
|
||||||
import { workItemTypeEnum, formatLatLng, parseLatLng } from "../util"
|
import { workItemTypeEnum, formatLatLng, parseLatLng } from "../util"
|
||||||
|
import PropTypes from "prop-types"
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
@@ -50,6 +51,10 @@ const styles = StyleSheet.create({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export class WorkItem extends React.Component {
|
export class WorkItem extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
history: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||||
|
}
|
||||||
|
|
||||||
static bindings = {
|
static bindings = {
|
||||||
header: {
|
header: {
|
||||||
noValue: true,
|
noValue: true,
|
||||||
@@ -65,6 +70,7 @@ export class WorkItem extends React.Component {
|
|||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
},
|
},
|
||||||
photos: {
|
photos: {
|
||||||
|
initValue: [],
|
||||||
isValid: (r, v) => v && v.length > 0,
|
isValid: (r, v) => v && v.length > 0,
|
||||||
},
|
},
|
||||||
details: {
|
details: {
|
||||||
@@ -270,6 +276,13 @@ export class WorkItem extends React.Component {
|
|||||||
this.setState({ progressModal: null })
|
this.setState({ progressModal: null })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleAddActivity() {
|
||||||
|
if (this.history) {
|
||||||
|
this.history.push(`/activity?workItemId=${this.binder._id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
binder,
|
binder,
|
||||||
@@ -360,6 +373,26 @@ export class WorkItem extends React.Component {
|
|||||||
onUploadProgress={this.handleUploadProgress}
|
onUploadProgress={this.handleUploadProgress}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
{api.loggedInUser.administrator &&
|
||||||
|
binder._id && (
|
||||||
|
<View style={styles.panel}>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={this.handleAddActivity}
|
||||||
|
style={{
|
||||||
|
alignSelf: "center",
|
||||||
|
backgroundColor: "blue",
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
height: 40,
|
||||||
|
width: "100%",
|
||||||
|
backgroundColor: "#3BB0FD",
|
||||||
|
}}>
|
||||||
|
<Text style={{ alignSelf: "center", color: "black" }}>
|
||||||
|
Add Activity
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
{isIphoneX ? <View style={{ height: 30, width: "100%" }} /> : null}
|
{isIphoneX ? <View style={{ height: 30, width: "100%" }} /> : null}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
<ProgressModal
|
<ProgressModal
|
||||||
|
|||||||
@@ -53,9 +53,10 @@ export class BoundPhotoPanel extends Component {
|
|||||||
ImagePicker.showImagePicker(
|
ImagePicker.showImagePicker(
|
||||||
{
|
{
|
||||||
title: "Select Photo",
|
title: "Select Photo",
|
||||||
|
noData: true,
|
||||||
storageOptions: {
|
storageOptions: {
|
||||||
skipBackup: true,
|
skipBackup: true,
|
||||||
path: "photos",
|
path: "deighton",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
(response) => {
|
(response) => {
|
||||||
@@ -64,7 +65,7 @@ export class BoundPhotoPanel extends Component {
|
|||||||
onUploadStarted()
|
onUploadStarted()
|
||||||
}
|
}
|
||||||
api
|
api
|
||||||
.upload(response.data, this.props.onUploadProgress)
|
.upload(response.path, this.props.onUploadProgress)
|
||||||
.then((uploadData) => {
|
.then((uploadData) => {
|
||||||
if (onUploadEnded) {
|
if (onUploadEnded) {
|
||||||
onUploadEnded(true, uploadData)
|
onUploadEnded(true, uploadData)
|
||||||
@@ -74,7 +75,7 @@ export class BoundPhotoPanel extends Component {
|
|||||||
|
|
||||||
if (binder) {
|
if (binder) {
|
||||||
const value = binder.getFieldValue(name)
|
const value = binder.getFieldValue(name)
|
||||||
let newValue = value.slice(0)
|
let newValue = typeof value === "array" ? value.slice(0) : []
|
||||||
|
|
||||||
newValue[index] = uploadData.assetId
|
newValue[index] = uploadData.assetId
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,6 @@
|
|||||||
"redis": "^2.7.1",
|
"redis": "^2.7.1",
|
||||||
"redis-rstream": "^0.1.3",
|
"redis-rstream": "^0.1.3",
|
||||||
"regexp-pattern": "^1.0.4",
|
"regexp-pattern": "^1.0.4",
|
||||||
"safe-buffer": "^5.1.1",
|
|
||||||
"socket.io": "^2.0.3",
|
"socket.io": "^2.0.3",
|
||||||
"tmp-promise": "^1.0.4",
|
"tmp-promise": "^1.0.4",
|
||||||
"urlsafe-base64": "^1.0.0",
|
"urlsafe-base64": "^1.0.0",
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ import path from "path"
|
|||||||
import util from "util"
|
import util from "util"
|
||||||
import config from "config"
|
import config from "config"
|
||||||
import autobind from "autobind-decorator"
|
import autobind from "autobind-decorator"
|
||||||
import Buffer from "safe-buffer"
|
|
||||||
import B64 from "b64"
|
|
||||||
import { PassThrough } from "stream"
|
import { PassThrough } from "stream"
|
||||||
import { catchAll } from "."
|
import { catchAll } from "."
|
||||||
|
|
||||||
function pipeToGridFS(readable, writable, decoder) {
|
function pipeToGridFS(readable, writeable) {
|
||||||
const promise = new Promise((resolve, reject) => {
|
const promise = new Promise((resolve, reject) => {
|
||||||
readable.on("error", (error) => {
|
readable.on("error", (error) => {
|
||||||
reject(error)
|
reject(error)
|
||||||
@@ -23,13 +21,13 @@ function pipeToGridFS(readable, writable, decoder) {
|
|||||||
resolve(file)
|
resolve(file)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
readable.pipe(decoder).pipe(writeable)
|
readable.pipe(writeable)
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
export class AssetRoutes {
|
export class AssetRoutes {
|
||||||
static rangeRegex = /^(byte|base64) (\d+)/
|
static rangeRegex = /^byte (\d+)/
|
||||||
|
|
||||||
constructor(container) {
|
constructor(container) {
|
||||||
const app = container.app
|
const app = container.app
|
||||||
@@ -73,13 +71,13 @@ export class AssetRoutes {
|
|||||||
assetId = assetId.slice(0, extIndex)
|
assetId = assetId.slice(0, extIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
const cursor = await this.db.gridfs.findOne({ _id: assetId })
|
const cursor = await this.db.gridfs.find({ _id: assetId })
|
||||||
|
const file = await cursor.next()
|
||||||
|
|
||||||
if (!cursor) {
|
if (!file) {
|
||||||
throw createError.NotFound(`Asset ${assetId} was not found`)
|
throw createError.NotFound(`Asset ${assetId} was not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = cursor.next()
|
|
||||||
const ifNoneMatch = req.get("If-None-Match")
|
const ifNoneMatch = req.get("If-None-Match")
|
||||||
|
|
||||||
if (ifNoneMatch && ifNoneMatch === file.md5) {
|
if (ifNoneMatch && ifNoneMatch === file.md5) {
|
||||||
@@ -138,7 +136,7 @@ export class AssetRoutes {
|
|||||||
chunkContentType !== "application/base64"
|
chunkContentType !== "application/base64"
|
||||||
) {
|
) {
|
||||||
throw createError.BadRequest(
|
throw createError.BadRequest(
|
||||||
"chunkContentType must be application/octet-stream or application/base64"
|
"chunkContentType must be 'application/octet-stream' or 'application/base64'"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -175,58 +173,47 @@ export class AssetRoutes {
|
|||||||
throw createError.BadRequest(`Bad upload id ${uploadId}`)
|
throw createError.BadRequest(`Bad upload id ${uploadId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!contentType.startsWith(uploadData.chunkContentType)) {
|
|
||||||
throw createError.BadRequest(
|
|
||||||
`Content-Type ${contentType} does not match chunk type ${
|
|
||||||
uploadData.chunkContentType
|
|
||||||
}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parseInt(contentLength, 10) !== req.body.length) {
|
|
||||||
throw createError.BadRequest(
|
|
||||||
"Must supply Content-Length header matching length of request body"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let match = contentRange.match(AssetRoutes.rangeRegex)
|
|
||||||
|
|
||||||
if (!match || match.length !== 3) {
|
|
||||||
throw createError.BadRequest(
|
|
||||||
"Content-Range header must be supplied and of form '[byte|base64] <offset>'"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const [, contentOffsetUnit, contentOffset] = match
|
|
||||||
|
|
||||||
if (
|
|
||||||
(uploadData.chunkContentType === "application/octet-stream" &&
|
|
||||||
contentOffsetUnit !== "byte") ||
|
|
||||||
(uploadData.chunkContentType === "application/base64" &&
|
|
||||||
contentOffsetUnit !== "base64")
|
|
||||||
) {
|
|
||||||
throw createError.BadRequest(
|
|
||||||
`Content-Range offset unit must be ${
|
|
||||||
uploadData.chunkContentType === "application/base64"
|
|
||||||
? "base64"
|
|
||||||
: "byte"
|
|
||||||
}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let offset = Number.parseInt(contentOffset)
|
|
||||||
|
|
||||||
if (offset < 0 || offset + req.body.length > uploadData.uploadSize) {
|
|
||||||
throw createError.BadRequest(
|
|
||||||
`Illegal Content-Range ${contentOffsetType} ${contentOffset} and Content-Length ${contentLength} for upload size ${
|
|
||||||
uploadData.uploadSize
|
|
||||||
}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (!contentType.startsWith(uploadData.chunkContentType)) {
|
||||||
|
throw createError.BadRequest(
|
||||||
|
`Content-Type ${contentType} does not match chunk type ${
|
||||||
|
uploadData.chunkContentType
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseInt(contentLength, 10) !== req.body.length) {
|
||||||
|
throw createError.BadRequest(
|
||||||
|
"Must supply Content-Length header matching length of request body"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let match = contentRange.match(AssetRoutes.rangeRegex)
|
||||||
|
|
||||||
|
if (!match || match.length !== 2) {
|
||||||
|
throw createError.BadRequest(
|
||||||
|
"Content-Range header must be supplied and of form 'byte <offset>'"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, contentOffset] = match
|
||||||
|
let offset = Number.parseInt(contentOffset)
|
||||||
|
|
||||||
|
const data =
|
||||||
|
uploadData.chunkContentType === "application/base64"
|
||||||
|
? Buffer.from(req.body, "base64")
|
||||||
|
: req.body
|
||||||
|
|
||||||
|
if (offset < 0 || offset + data.length > uploadData.uploadSize) {
|
||||||
|
throw createError.BadRequest(
|
||||||
|
`Illegal Content-Range 'byte ${contentOffset}' and Content-Length ${contentLength} for upload size ${
|
||||||
|
uploadData.uploadSize
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const [, uploadedChunks] = await Promise.all([
|
const [, uploadedChunks] = await Promise.all([
|
||||||
this.rs.setrangeAsync(uploadDataId, offset, req.body),
|
this.rs.setrangeAsync(uploadDataId, offset, data),
|
||||||
this.rs.incrAsync(uploadCountId),
|
this.rs.incrAsync(uploadCountId),
|
||||||
])
|
])
|
||||||
const chunkInfo = {
|
const chunkInfo = {
|
||||||
@@ -242,11 +229,7 @@ export class AssetRoutes {
|
|||||||
{ contentType: uploadData.contentType }
|
{ contentType: uploadData.contentType }
|
||||||
)
|
)
|
||||||
|
|
||||||
const decoder =
|
const file = await pipeToGridFS(readable, writeable)
|
||||||
uploadData.chunkContentType === "application/base64"
|
|
||||||
? new B64.Decoder()
|
|
||||||
: new PassThrough()
|
|
||||||
const file = await pipeToGridFS(readable, writeable, decoder)
|
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.rs.del(uploadId),
|
this.rs.del(uploadId),
|
||||||
@@ -275,7 +258,6 @@ export class AssetRoutes {
|
|||||||
this.rs.del(uploadId)
|
this.rs.del(uploadId)
|
||||||
this.rs.del(uploadCountId)
|
this.rs.del(uploadCountId)
|
||||||
this.rs.del(uploadDataId)
|
this.rs.del(uploadDataId)
|
||||||
this.log.error(error.message)
|
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user